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..63846fd --- /dev/null +++ starmath/inc/caret.hxx @@ -0,0 +1,164 @@ +#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 (FUCK!!!) + 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; + } +}; + +/** 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; +}; + +/////////////////////////////// SmCaretPosMap//////////////////////////////// + +/** An entry in SmCaretPosMap */ +struct SmCaretPosMapEntry{ + SmCaretPosMapEntry(SmCaretPos pos = SmCaretPos(), + SmCaretPosMapEntry* left = NULL, + SmCaretPosMapEntry* right = NULL){ + CaretPos = pos; + Left = left; + Right = right; + } + /** Caret position */ + SmCaretPos CaretPos; + /** Entry to the left visually */ + SmCaretPosMapEntry* Left; + /** Entry to the right visually */ + SmCaretPosMapEntry* Right; + void SetRight(SmCaretPosMapEntry* right){ + Right = right; + } + void SetLeft(SmCaretPosMapEntry* left){ + Left = left; + } +}; + +/** Define SmCaretPosMap to be less than one page 4096 */ +#define SmCaretPosMapSize 255 + +class SmCaretPosMap; + +/** Iterator for SmCaretPosMap */ +class SmCaretPosMapIterator{ +public: + SmCaretPosMapIterator(SmCaretPosMap* map){ + pMap = map; + nOffset = 0; + pEntry = NULL; + } + /** Get the next entry, NULL if none */ + SmCaretPosMapEntry* Next(); + /** Get the current entry, NULL if none */ + SmCaretPosMapEntry* Current(){ + return pEntry; + } + /** Get the current entry, NULL if none */ + SmCaretPosMapEntry* operator->(){ + return pEntry; + } +private: + /** Next entry to return */ + int nOffset; + /** Current map */ + SmCaretPosMap* pMap; + /** Current entry */ + SmCaretPosMapEntry* pEntry; +}; + + +/** A map over all caret positions + * @remarks Maps can only grow, entries cannot be removed! + */ +class SmCaretPosMap{ +public: + SmCaretPosMap(){ + pNext = NULL; + nOffset = 0; + } + ~SmCaretPosMap(); + SmCaretPosMapEntry* Add(SmCaretPosMapEntry entry); + SmCaretPosMapEntry* Add(SmCaretPos pos, + SmCaretPosMapEntry* left = NULL, + SmCaretPosMapEntry* right = NULL){ + j_assert(pos.Index >= 0, "Index shouldn't be -1!"); + return Add(SmCaretPosMapEntry(pos, left, right)); + } + /** Get an iterator for this map */ + SmCaretPosMapIterator GetIterator(){ + return SmCaretPosMapIterator(this); + } + friend class SmCaretPosMapIterator; +private: + /** Next map, to be used when this map is full */ + SmCaretPosMap* pNext; + /** Next free entry in map */ + int nOffset; + /** Entries in this map segment */ + SmCaretPosMapEntry Map[SmCaretPosMapSize]; +}; + +#endif /* CARET_H */ diff --git starmath/inc/cursor.hxx starmath/inc/cursor.hxx new file mode 100644 index 0000000..7311ade --- /dev/null +++ starmath/inc/cursor.hxx @@ -0,0 +1,218 @@ +#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 +}; + +/** 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. + */ +class SmCursor{ +public: + SmCursor(SmNode* tree, SmDocShell* pShell){ + //Initialize members + pTree = tree; + anchor = NULL; + position = NULL; + pMap = NULL; + pDocShell = pShell; + //Build map + BuildMap(); + } + + ~SmCursor(){ + if(pMap) + delete pMap; + pMap = 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); + + /** 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); +private: + friend class SmDocShell; + + SmCaretPosMapEntry *anchor, + *position; + /** Formula tree */ + SmNode* pTree; + /** Owner of the formula tree */ + SmDocShell* pDocShell; + /** Map over caret position in the current tree */ + SmCaretPosMap* pMap; + + /** 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! + */ + SmNodeList* LineToList(SmStructureNode* pLine, SmNodeList* pList = new SmNodeList()); + + /** Build pMap over caret position */ + void BuildMap(); + + /** Insert a new node in the tree after position */ + void InsertNode(SmNode* pNewNode); + + /** tries to set position to a specific SmCaretPos + * + * @returns false on failure to find the position in pMap. + */ + bool SetCaretPosition(SmCaretPos pos, bool moveAnchor = false); + + /** Set selected on nodes of the tree */ + void AnnotateSelection(); + + /** Draw the caret */ + void Draw(OutputDevice* pDev, Point Offset); +}; + +/** 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 */ + SmNode* Parse(SmNodeList* list); +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..df37620 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) @@ -218,6 +226,11 @@ public: virtual void SetVisArea (const Rectangle & rVisArea); virtual void SetModified(BOOL bModified); + + /** Get a cursor for modifying this document + * @remarks The cursor will be deleted with this document! + */ + SmCursor* GetCursor(); }; 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..e31fda5 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){ + pNode = node; + nSize = pNode->GetNumSubNodes(); + nIndex = 0; + pChildNode = NULL; + } + /** Get the subnode or NULL if none */ + SmNode* Next(){ + while(nIndex < nSize){ + if(NULL != (pChildNode = pNode->GetSubNode(nIndex++))) + 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; +}; + +//////////////////////////////////////////////////////////////////////////////// +/** 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 +340,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 +397,10 @@ public: //////////////////////////////////////////////////////////////////////////////// - +/** Draws a rectangle + * + * Used for drawing the line in the OVER and OVERSTRIKE commands. + */ class SmRectangleNode : public SmGraphicNode { Size aToSize; @@ -270,15 +418,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 +441,8 @@ public: SmPolyLineNode(const SmToken &rNodeToken); long GetWidth() const { return nWidth; } + Size GetToSize() const { return aToSize; } + Polygon GetPolygon() const { return aPoly; } virtual void AdaptToX(const OutputDevice &rDev, ULONG nWidth); virtual void AdaptToY(const OutputDevice &rDev, ULONG nHeight); @@ -298,17 +452,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 +494,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 +521,22 @@ public: #ifdef SM_RECT_DEBUG using SmRect::Draw; #endif - virtual void Draw(OutputDevice &rDev, const Point &rPosition) const; + virtual void GetAccessibleText( String &rText ) const; + void Accept(SmVisitor* pVisitor); }; //////////////////////////////////////////////////////////////////////////////// - +/** Special node for user defined characters + * + * Node used for pre- and user-defined characters from: + * officecfg/registry/data/org/openoffice/Office/Math.xcu + * + * This is just single characters, I think. + */ class SmSpecialNode : public SmTextNode { protected: @@ -363,7 +555,8 @@ public: #ifdef SM_RECT_DEBUG using SmRect::Draw; #endif - virtual void Draw(OutputDevice &rDev, const Point &rPosition) const; + + void Accept(SmVisitor* pVisitor); }; @@ -378,12 +571,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 +601,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 +622,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: @@ -442,12 +652,17 @@ public: 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 +674,18 @@ 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 command and possibly also others. When used for root node, it's + * children are instances of SmLineNode. + */ class SmTableNode : public SmStructureNode { public: @@ -476,12 +697,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 +722,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 +743,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 +763,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 +796,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 +823,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 +855,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 +885,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 +944,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 +955,35 @@ 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) ); }; 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 +995,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 +1019,7 @@ public: virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat); long GetBodyHeight() const { return nBodyHeight; } + void Accept(SmVisitor* pVisitor); }; @@ -704,13 +1032,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 +1064,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 +1094,16 @@ 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 + * + * TODO: Document this node whenever somebody figures out how it works. + */ class SmAlignNode : public SmStructureNode { public: @@ -756,12 +1112,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 +1137,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 +1167,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 +1199,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; @@ -846,6 +1225,7 @@ public: 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/view.hxx starmath/inc/view.hxx index eab2444..9fd6842 100644 --- starmath/inc/view.hxx +++ starmath/inc/view.hxx @@ -38,6 +38,7 @@ #include "edit.hxx" #include "node.hxx" #include "accessibility.hxx" +#include "cursor.hxx" class Menu; class DataChangedEvent; @@ -50,7 +51,6 @@ class SmViewShell; class SmGraphicWindow : public ScrollableWindow { Point aFormulaDrawPos; - Rectangle aCursorRect; ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessible > xAccessible; @@ -59,14 +59,9 @@ class SmGraphicWindow : public ScrollableWindow SmViewShell *pViewShell; USHORT nZoom; short nModifyCount; - BOOL bIsCursorVisible; protected: void SetFormulaDrawPos(const Point &rPos) { aFormulaDrawPos = rPos; } - void SetIsCursorVisible(BOOL bVis) { bIsCursorVisible = bVis; } - using Window::SetCursor; - void SetCursor(const SmNode *pNode); - void SetCursor(const Rectangle &rRect); virtual void DataChanged( const DataChangedEvent& ); virtual void Paint(const Rectangle&); @@ -97,10 +92,6 @@ public: using ScrollableWindow::SetTotalSize; void SetTotalSize(); - BOOL IsCursorVisible() const { return bIsCursorVisible; } - void ShowCursor(BOOL bShow); - const SmNode * SetCursorPos(USHORT nRow, USHORT nCol); - void ApplyColorConfigValues( const svtools::ColorConfig &rColorCfg ); // for Accessibility diff --git starmath/inc/visitors.hxx starmath/inc/visitors.hxx new file mode 100644 index 0000000..f3d50d2 --- /dev/null +++ starmath/inc/visitors.hxx @@ -0,0 +1,411 @@ +#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 *pDevice, SmCaretPos position, Point offset) { + pDev = pDevice; + pos = position; + Offset = offset; + j_assert(position.IsValid(), "Cannot draw invalid position!"); + if(!position.IsValid()) + return; + + //Save device state + pDev->Push( PUSH_FONT | PUSH_MAPMODE | PUSH_LINECOLOR | PUSH_FILLCOLOR | PUSH_TEXTCOLOR ); + + pos.pSelectedNode->Accept(this); + + //Restore device state + pDev->Pop(); + } + void Visit(SmTextNode* node); +private: + OutputDevice *pDev; + 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 + } + /** Draws a selection if there's any */ + void DrawSelection(SmNode* node); + + /** 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; + HasSelected = false; + } + void Visit(SmBinHorNode* node); + void Visit(SmUnHorNode* node); + void Visit(SmFontNode* node); + void Visit(SmPlaceNode* node); + void Visit(SmTextNode* node); + void Visit(SmSpecialNode* node); + void Visit(SmGlyphSpecialNode* node); + void Visit(SmMathSymbolNode* node); + void Visit(SmErrorNode* 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); + void VisitTextNode(SmTextNode* 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; + /** True, if something have been selected + * To understand this state consider the selection: sqrt{ A + B [} + C ] + * where [ denote StartPos and ] denotes EndPos. In this example the + * sqrt shouldn't be selected. Simply because nothing within the sqrt is + * selected. + */ + BOOL HasSelected; +}; + + +/////////////////////////////// SmCaretPosMapBuildingVisitor //////////////////////////////// + +/* Notes on SmCaretPos and their placement + +Index: + 0 Position infront of a node + 1+ Position inside or after a node + +Nodes that doesn't form line entries: + SmExpressionNode + SmBinHorNode + + +*/ + + +/** A visitor for building a SmCaretPosMap */ +class SmCaretPosMapBuildingVisitor : public SmVisitor{ +public: + SmCaretPosMapBuildingVisitor(){ + pRightMost = NULL; + pMap = new SmCaretPosMap(); + } + /* 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 : SmCaretPosMapEntry* + * + * 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); + SmCaretPosMap* Map(){ + return pMap; + } +private: + SmCaretPosMapEntry* pRightMost; + SmCaretPosMap* pMap; + + //Auxiliary methods + /** Method for implementing every method that derivates from SmTextNode */ + void VisitTextNode(SmTextNode* node); + //TODO: Check SmSpecialNode::Draw to see in which cases it doesn't use GetText(), check relevancy for all visitors + //TODO: Treat derivates of SmTextNode as a single char!! +}; + + +#endif /* SMVISITORS_H */ diff --git starmath/source/caret.cxx starmath/source/caret.cxx new file mode 100644 index 0000000..eb0c2f1 --- /dev/null +++ starmath/source/caret.cxx @@ -0,0 +1,35 @@ +#include "caret.hxx" + +/////////////////////////////// SmCaretPosMap //////////////////////////////// + +SmCaretPosMapEntry* SmCaretPosMapIterator::Next(){ + if(nOffset >= pMap->nOffset){ + if(pMap->pNext){ + pMap = pMap->pNext; + nOffset = 0; + pEntry = Next(); + }else + pEntry = NULL; + }else + pEntry = pMap->Map + nOffset++; + return pEntry; +} + +SmCaretPosMapEntry* SmCaretPosMap::Add(SmCaretPosMapEntry entry){ + if(nOffset >= SmCaretPosMapSize){ + if(!pNext) + pNext = new SmCaretPosMap(); + return pNext->Add(entry); + }else{ + Map[nOffset] = entry; + return Map + nOffset++; + } +} + +SmCaretPosMap::~SmCaretPosMap(){ + if(pNext) + delete pNext; + pNext = NULL; +} + + diff --git starmath/source/cursor.cxx starmath/source/cursor.cxx new file mode 100644 index 0000000..85c1066 --- /dev/null +++ starmath/source/cursor.cxx @@ -0,0 +1,733 @@ +#include "cursor.hxx" +#include "parse.hxx" +#include "visitors.hxx" +#include "document.hxx" + +void SmCursor::Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor){ + SmCaretPosMapEntry* 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 + SmCaretPosMapIterator it = pMap->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; + } +} + +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 + SmCaretPosMapEntry* NewPos = NULL; + long dp_sq, //Distance to current line squared + dbp_sq; //Distance to best line squared + SmCaretPosMapIterator it = pMap->GetIterator(); + while(it.Next()){ + j_assert(it->CaretPos.IsValid(), "The caret position map 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; + } +} + +void SmCursor::BuildMap(){ + //Save the current anchor and position + SmCaretPos _anchor, _position; + //Release pMap if allocated + if(pMap){ + if(anchor) + _anchor = anchor->CaretPos; + if(position) + _position = position->CaretPos; + delete pMap; + //Reset anchor and position as they point into an old map + anchor = NULL; + position = NULL; + } + pMap = NULL; + + //Build the new map + SmCaretPosMapBuildingVisitor builder; + pTree->Accept(&builder); + pMap = builder.Map(); + + //Restore anchor and position pointers + if(_anchor.IsValid() || _position.IsValid()){ + SmCaretPosMapIterator it = pMap->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 + SmCaretPosMapIterator it = pMap->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){ + SmCaretPosMapIterator it = pMap->GetIterator(); + while(it.Next()){ + if(it->CaretPos == pos){ + position = it.Current(); + if(moveAnchor) + anchor = it.Current(); + return true; + } + } + return false; +} + +void SmCursor::AnnotateSelection(){ + 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; + + //Set selected on nodes + AnnotateSelection(); + + //Find an arbirary selected node + SmNode* pSNode = FindSelectedNode(pTree); + + //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; + + //TODO: Consider merging text nodes, if I delete something that is between them. + + //If the line has children + if(pLine->GetNumSubNodes() > 0){ + //Convert line into list of nodes + SmNodeList* pLineList = LineToList((SmStructureNode*)pLine); + pLine = NULL; //pLine have been deleted by LineToList + + //Delete selected nodes from list of nodes + bool MeetSelection = false; + std::list::iterator it = pLineList->begin(); + while(it != pLineList->end()){ + if((*it)->IsSelected()){ + if((*it)->GetType() == NTEXT){ + SmTextNode* pText = (SmTextNode*)*it; + XubString newStr; + newStr += pText->GetText().Copy(0, pText->GetSelectionStart()); + newStr += pText->GetText().Copy(pText->GetSelectionEnd()); + if(newStr.Len() > 0){ + pText->ChangeText(newStr); + if(!MeetSelection && pText->GetSelectionStart()) + PosAfterDelete = SmCaretPos(*it, pText->GetSelectionStart()); + MeetSelection = true; + ++it; + continue; + } + } + + MeetSelection = true; + delete (*it); + it = pLineList->erase(it); + }else{ + if(!MeetSelection){ + //Set next position to position right after this one + if((*it)->GetType() == NTEXT) + PosAfterDelete = SmCaretPos(*it, ((SmTextNode*)(*it))->GetText().Len()); + else + PosAfterDelete = SmCaretPos(*it, 1); + } + ++it; + } + } + + + //Parse list of nodes to a tree + SmNodeListParser parser; + pLine = parser.Parse(pLineList); + delete pLineList; + + //Handle special case where pLine isn't an SmStructureNode, this can happen!!! + //example: A over B, if A is selected this will be the case. + }else{ + //If pLine handle the special case for textnode or delete it + if(pLine->GetType() == NTEXT){ + j_assert(pLine->IsSelected(), "If pLine is a TextNode it must be selected!"); + + SmTextNode* pText = (SmTextNode*)pLine; + XubString newStr; + newStr += pText->GetText().Copy(0, pText->GetSelectionStart()); + newStr += pText->GetText().Copy(pText->GetSelectionEnd()); + //If there's any text left + if(newStr.Len() > 0){ + //Set the new string + pText->ChangeText(newStr); + //Set position after delete + PosAfterDelete = SmCaretPos(pText, pText->GetSelectionStart()); + goto SkipDelete; //Skip delete, yes I used goto :) + } + } + //Delete pLine and insert empty expression + delete pLine; + pLine = new SmExpressionNode(SmToken()); + PosAfterDelete = SmCaretPos(pLine, 0); //Set position + } +SkipDelete: + + //Insert it back into the parent + pLineParent->SetSubNode(nLineOffset, pLine); + + //Rebuild map of caret position + anchor = NULL; + position = NULL; + BuildMap(); + AnnotateSelection(); //Update selection annotation! + + //Set caret position + if(!SetCaretPosition(PosAfterDelete, true)) + SetCaretPosition(SmCaretPos(pLine, 0), true); + + //Mark pTree as not arranged + pDocShell->SetFormulaArranged(false); +} + +void SmCursor::InsertNode(SmNode* pNewNode){ + //Position after insert should be after pNewNode + SmCaretPos PosAfterInsert = SmCaretPos(pNewNode, 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); + } + + //Insert new node + SmNodeList::iterator it; + for(it = pLineList->begin(); it != pLineList->end(); it++){ + if(*it == pos.pSelectedNode){ + if((*it)->GetType() == NTEXT){ + //If node we wish to insert is a textnode, we have special case + if(pNewNode->GetType() == NTEXT){ + SmTextNode *pText = (SmTextNode*)(*it), + *pNewText = (SmTextNode*)pNewNode; + //Insert pNewText in pText + XubString str; + str += pText->GetText().Copy(0, pos.Index); + str += pNewText->GetText(); + str += pText->GetText().Copy(pos.Index); + pText->ChangeText(str); + //set PosAfterInsert + PosAfterInsert = SmCaretPos(pText, pos.Index + pNewText->GetText().Len()); + //delete pNewNode and set pNewNode = NULL + delete pNewNode; + pNewNode = NULL; + break; + }else{ //Split textnode if needed + if(position->CaretPos.Index > 0){ + SmTextNode* pText = (SmTextNode*)pos.pSelectedNode; + XubString str1 = pText->GetText().Copy(0, pos.Index); + XubString str2 = pText->GetText().Copy(pos.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; + //Handle special case, where we insert text infront of a textnode, if so merge them :) + if((*it)->GetType() == NTEXT && pNewNode->GetType() == NTEXT){ + SmTextNode *pText = (SmTextNode*)(*it), + *pNewText = (SmTextNode*)pNewNode; + //Insert pNewText in pText + XubString str = pNewText->GetText(); + str += pText->GetText(); + pText->ChangeText(str); + //set PosAfterInsert + PosAfterInsert = SmCaretPos(pText, pNewText->GetText().Len()); + //delete pNewNode and set pNewNode = NULL + delete pNewNode; + pNewNode = NULL; + }else //If no special case + pLineList->insert(it, pNewNode); + pNewNode = NULL; //Indicated that we've inserted it + break; + } + } + //If pNewNode wasn't inserted + if(pNewNode){ + //If we're inserting text infront of a textnode, merge them + if(pNewNode->GetType() == NTEXT && + pLineList->front() && //Check that it's not an empty expression + pLineList->front()->GetType() == NTEXT){ + SmTextNode *pText = (SmTextNode*)pLineList->front(), + *pNewText = (SmTextNode*)pNewNode; + //Insert pNewText in pText + XubString str = pNewText->GetText(); + str += pText->GetText(); + pText->ChangeText(str); + //set PosAfterInsert + PosAfterInsert = SmCaretPos(pText, pNewText->GetText().Len()); + //delete pNewNode and set pNewNode = NULL + delete pNewNode; + pNewNode = NULL; + }else + pLineList->push_front(pNewNode); + } + pNewNode = NULL; //We've inserted it! + + //Parse line + SmNodeListParser parser; + pLine = parser.Parse(pLineList); + delete pLineList; + + //Insert it back into the parent + pLineParent->SetSubNode(nParentIndex, pLine); + + //Rebuild map of caret position + anchor = NULL; + position = NULL; + BuildMap(); + AnnotateSelection(); //Update selection annotation! + + //Set caret position + if(!SetCaretPosition(PosAfterInsert, true)) + SetCaretPosition(SmCaretPos(pLine, 0), true); + + //Mark pTree as not arranged + pDocShell->SetFormulaArranged(false); +} + +void SmCursor::InsertText(XubString aString){ + 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); + + InsertNode(pText); +} + +void SmCursor::InsertElement(SmFormulaElement element){ + 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 + InsertNode(pNewNode); +} + +void SmCursor::Copy(){ + //TODO: Clone the current selection +} + +void SmCursor::Paste(){ + Delete(); + //TODO: Paste the clipboard... +} + +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 + // SmBracebodyNode + // 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; + default: + list->push_back(it.Current()); + } + } + SmNodeArray emptyArray(0); + pLine->SetSubNodes(emptyArray); + delete pLine; + return list; +} + +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; +} + +/////////////////////////////////////// SmNodeListParser /////////////////////////////////////// + +SmNode* SmNodeListParser::Parse(SmNodeList* list){ + pList = list; + 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() && Token()->GetToken().nGroup & TGRELATION){ + //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() && Token()->GetToken().nGroup & TGSUM){ + //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() && Token()->GetToken().nGroup & TGPRODUCT && + Token()->GetToken().eType != TWIDESLASH && + Token()->GetToken().eType != TWIDEBACKSLASH && + Token()->GetToken().eType != TUNDERBRACE && + Token()->GetToken().eType != TOVERBRACE && + Token()->GetToken().eType != TBOPER && + Token()->GetToken().eType != TOVER){ + //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(); + else if(Token()->GetToken().nGroup & TGUNOPER && + (Token()->GetToken().eType == TPLUS || + Token()->GetToken().eType == TMINUS || + Token()->GetToken().eType == TPLUSMINUS || + Token()->GetToken().eType == TMINUSPLUS || + Token()->GetToken().eType == TNEG || + Token()->GetToken().eType == TFACT)) + { + 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; + } + return Take(); +} + +SmNode* SmNodeListParser::Error(){ + return new SmErrorNode(PE_UNEXPECTED_TOKEN, SmToken()); +} diff --git starmath/source/document.cxx starmath/source/document.cxx index 259c26d..93fac41 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,7 +435,8 @@ 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) { @@ -477,8 +479,18 @@ 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) + pCursor->AnnotateSelection(); + + //Drawing using visitor + DrawingVisitor(rDev, rPosition, pTree); + + //Draw cursor if any + if(pCursor) + pCursor->Draw(&rDev, rPosition); + // rDev.SetLayoutMode( nLayoutMode ); rDev.SetDigitLanguage( nDigitLang ); @@ -519,6 +531,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 +715,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 +745,11 @@ SmDocShell::~SmDocShell() EndListening(aFormat); EndListening(*pp->GetConfig()); + + if(pCursor) + delete pCursor; + pCursor = NULL; + delete pEditEngine; SfxItemPool::Free(pEditEngineItemPool); delete pTree; @@ -751,6 +781,7 @@ BOOL SmDocShell::ConvertFrom(SfxMedium &rMedium) { delete pTree; pTree = 0; + InvalidateCursor(); } Reference xModel(GetModel()); SmXMLImportWrapper aEquation(xModel); diff --git starmath/source/edit.cxx starmath/source/edit.cxx index 1994271..3cee9bc 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,11 @@ void SmEditWindow::GetFocus() EditEngine *pEditEngine = GetEditEngine(); if (pEditEngine) pEditEngine->SetStatusEventHdl( LINK(this, SmEditWindow, EditStatusHdl) ); + + //Let's copy text from visual editor + XubString data; + ((SmNode*)GetDoc()->GetFormulaTree())->CreateTextFromNode(data); + SetText(data); } @@ -925,13 +890,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..f4a17a1 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 { @@ -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..c8f5ffb 100644 --- starmath/source/parse.cxx +++ starmath/source/parse.cxx @@ -1124,6 +1124,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); diff --git starmath/source/view.cxx starmath/source/view.cxx index f00036f..0b0539c 100644 --- starmath/source/view.cxx +++ starmath/source/view.cxx @@ -65,6 +65,7 @@ #include #include #include +#include #include "view.hxx" #include "config.hxx" @@ -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. @@ -174,47 +174,18 @@ void SmGraphicWindow::MouseButtonDown(const MouseEvent& rMEvt) - GetFormulaDrawPos()); // 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()); + Invalidate(); } } + GrabFocus(); } void SmGraphicWindow::GetFocus() { + pViewShell->GetEditWindow()->Flush(); /* if (xAccessible.is()) { @@ -239,70 +210,7 @@ void SmGraphicWindow::LoseFocus() aOldValue, aNewValue ); } } - -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; -} - - +#include void SmGraphicWindow::Paint(const Rectangle&) { DBG_ASSERT(pViewShell, "Sm : NULL pointer"); @@ -313,22 +221,6 @@ void SmGraphicWindow::Paint(const Rectangle&) rDoc.Draw(*this, aPoint); //! 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); - } } @@ -340,11 +232,81 @@ 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: + 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; + default: + { + if(rKEvt.GetCharCode() == ' '){ + rCursor.InsertElement(BlankElement); + }else{ + XubString str; + str.Assign(rKEvt.GetCharCode()); + if(str.Len() > 0){ + rCursor.InsertText(rKEvt.GetCharCode()); + }else if (! (GetView() && GetView()->KeyInput(rKEvt)) ) + ScrollableWindow::KeyInput(rKEvt); + } + } + } + Invalidate(); } @@ -1418,7 +1380,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: diff --git starmath/source/visitors.cxx starmath/source/visitors.cxx new file mode 100644 index 0000000..8b6e7ab --- /dev/null +++ starmath/source/visitors.cxx @@ -0,0 +1,1537 @@ +#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; + + pDev->SetFont(node->GetFont()); + + //Find the line + SmNode* pLine = SmCursor::FindTopMostNodeInLine(node); + + //Find coordinates + long left = node->GetLeft() + pDev->GetTextWidth(node->GetText(), 0, i) + Offset.X(); + long top = pLine->GetTop() + Offset.Y(); + long height = pLine->GetHeight(); + + //Set color + pDev->SetLineColor(Color( COL_BLACK )); + + //Draw vertical line + Point p1(left, top); + Point p2(left, top + height); + pDev->DrawLine(p1, p2); +} + +void SmCaretDrawingVisitor::DefaultVisit(SmNode* node){ + pDev->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 + pDev->SetLineColor(Color( COL_BLACK )); + + //Draw vertical line + Point p1(left, top); + Point p2(left, top + height); + pDev->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) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmBraceNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmBracebodyNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmOperNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmAlignNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmAttributNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmFontNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmUnHorNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmBinHorNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmBinVerNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmBinDiagonalNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmSubSupNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmMatrixNode* node) { + DrawSelection(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) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmErrorNode* node) { + DrawSpecialNode(node); +} + +void DrawingVisitor::Visit(SmLineNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmExpressionNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmRootNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmVerticalBraceNode* node) { + DrawSelection(node); + DrawChildren(node); +} + +void DrawingVisitor::Visit(SmRootSymbolNode* node) { + if (node->IsPhantom()) + return; + + DrawSelection(node); + + // 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; + + DrawSelection(node); + + 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; + + DrawSelection(node); + + 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; + + if(node->IsSelected()){ + rDev.Push(PUSH_LINECOLOR | PUSH_FILLCOLOR | PUSH_TEXTCOLOR | PUSH_FONT); + + rDev.SetFont(node->GetFont()); + + 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); + + rDev.SetLineColor(); + rDev.SetFillColor(Color(COL_LIGHTGRAY)); + rDev.DrawRect(rect); + + rDev.Pop(); + } + + 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); +} + +void DrawingVisitor::DrawSelection(SmNode* node){ + if(node->IsSelected() && !node->IsPhantom()){ + //TODO: Detect if parent already drew the selection background, + // this can probably be done with a parameter and a nasty invariant. + + //Save device state + rDev.Push(PUSH_LINECOLOR | PUSH_FILLCOLOR); + //Change colors + rDev.SetLineColor(); + rDev.SetFillColor(Color(COL_LIGHTGRAY)); + + //TODO: Use to get top and height FindTopMostNodeInLine + //Find the line + //SmNode* pLine = SmCursor::FindTopMostNodeInLine(node, true); + //TODO: Do this for textnodes too! + + //Find rectangle + Rectangle rect = node->AsRectangle(); + + //Draw rectangle + rDev.DrawRect(rect); + //Restore device state + rDev.Pop(); + } +} + +/////////////////////////////// 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; + + //Set selected + node->SetSelected(IsSelecting); + HasSelected = IsSelecting || HasSelected; + + //Visit children + SmNode *pNode; + USHORT nSize = node->GetNumSubNodes(); + for (USHORT i = 0; i < nSize; i++) + if (NULL != (pNode = node->GetSubNode(i))) + pNode->Accept(this); + + //If state changed + if(WasSelecting != IsSelecting && HasSelected){ + //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; + HasSelected = false; + } + //Change state if EndPos is after of this node + if(EndPos.pSelectedNode == node && EndPos.Index == 1){ + IsSelecting = !IsSelecting; + HasSelected = false; + } +} + +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; + + //Set HasSelected + HasSelected = IsSelecting || HasSelected; + + //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; + HasSelected = false; + } + //Change state if EndPos is after of this node + if(EndPos.pSelectedNode == node && EndPos.Index == 1){ + IsSelecting = !IsSelecting; + HasSelected = false; + } +} + +void SetSelectionVisitor::VisitTextNode(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 + HasSelected = true; + }else if(IsSelecting && i1 != -1){ + start = 0; + end = i1; + IsSelecting = false; + HasSelected = true; + }else if(IsSelecting && i2 != -1){ + start = 0; + end = i2; + IsSelecting = false; + HasSelected = true; + }else if(!IsSelecting && i1 != -1){ + start = i1; + end = node->GetText().Len(); + IsSelecting = true; + HasSelected = true; + }else if(!IsSelecting && i2 != -1){ + start = i2; + end = node->GetText().Len(); + IsSelecting = true; + HasSelected = true; + }else if(IsSelecting){ + start = 0; + end = node->GetText().Len(); + HasSelected = true; + }else{ + node->SetSelected(false); + start = 0; + end = 0; + HasSelected = false; + } + node->SetSelected(start != end); + node->SetSelectionStart(start); + node->SetSelectionEnd(end); +} + +void SetSelectionVisitor::Visit(SmTextNode* node) { + VisitTextNode(node); +} + +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); +} + +void SetSelectionVisitor::Visit(SmPlaceNode* node) { + VisitTextNode(node); +} + +void SetSelectionVisitor::Visit(SmSpecialNode* node) { + VisitTextNode(node); +} + +void SetSelectionVisitor::Visit(SmGlyphSpecialNode* node) { + VisitTextNode(node); +} + +void SetSelectionVisitor::Visit(SmMathSymbolNode* node) { + VisitTextNode(node); +} + +void SetSelectionVisitor::Visit(SmErrorNode* node) { + VisitTextNode(node); +} + + +/////////////////////////////// SmCaretPosMapBuildingVisitor //////////////////////////////// + + +//Needs special care: +void SmCaretPosMapBuildingVisitor::Visit(SmTableNode* node){ + //Children are SmLineNodes + SmNodeIterator it(node); + while(it.Next()) + it->Accept(this); +} +void SmCaretPosMapBuildingVisitor::Visit(SmLineNode* node){ + pRightMost = NULL; + SmNodeIterator it(node); + while(it.Next()){ + if(!pRightMost) + pRightMost = pMap->Add(SmCaretPos(it.Current(), 0)); + it->Accept(this); + } +} + +/** Build SmCaretPosMap 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 + * + * Map between these, where "left" is before the SmSubSupNode and "right" is after: + * \dot + * digraph Map{ + * left -> H; + * H -> right; + * LSUP -> H; + * LSUB -> H; + * CSUP -> right; + * CSUB -> right; + * RSUP -> right; + * RSUB -> right; + * } + * \enddot + * + * TODO: Handle context dependent situation where SmSubSupNode is a child of SmOperNode + */ +void SmCaretPosMapBuildingVisitor::Visit(SmSubSupNode* node){ + SmCaretPosMapEntry *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 = pMap->Add(SmCaretPos(node->GetBody(), 0), left); + left->SetRight(bodyLeft); //TODO: Don't make this if LSUP or LSUB are NULL + + //Create right + right = pMap->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))){ + SmCaretPosMapEntry *cLeft; //Child left + cLeft = pMap->Add(SmCaretPos(pChild, 0), left); + + pRightMost = cLeft; + pChild->Accept(this); + + pRightMost->SetRight(bodyLeft); + } + //If there's an LSUB + if((pChild = node->GetSubSup(LSUB))){ + SmCaretPosMapEntry *cLeft; //Child left + cLeft = pMap->Add(SmCaretPos(pChild, 0), left); + + pRightMost = cLeft; + pChild->Accept(this); + + pRightMost->SetRight(bodyLeft); + } + //If there's an CSUP + if((pChild = node->GetSubSup(CSUP))){ + SmCaretPosMapEntry *cLeft; //Child left + cLeft = pMap->Add(SmCaretPos(pChild, 0), left); + + pRightMost = cLeft; + pChild->Accept(this); + + pRightMost->SetRight(right); + } + //If there's an CSUB + if((pChild = node->GetSubSup(CSUB))){ + SmCaretPosMapEntry *cLeft; //Child left + cLeft = pMap->Add(SmCaretPos(pChild, 0), left); + + pRightMost = cLeft; + pChild->Accept(this); + + pRightMost->SetRight(right); + } + //If there's an RSUP + if((pChild = node->GetSubSup(RSUP))){ + SmCaretPosMapEntry *cLeft; //Child left + cLeft = pMap->Add(SmCaretPos(pChild, 0), bodyRight); + + pRightMost = cLeft; + pChild->Accept(this); + + pRightMost->SetRight(right); + } + //If there's an RSUB + if((pChild = node->GetSubSup(RSUB))){ + SmCaretPosMapEntry *cLeft; //Child left + cLeft = pMap->Add(SmCaretPos(pChild, 0), bodyRight); + + pRightMost = cLeft; + pChild->Accept(this); + + pRightMost->SetRight(right); + } + + //Set return parameters + pRightMost = right; +} + +void SmCaretPosMapBuildingVisitor::Visit(SmMatrixNode* node){ + SmCaretPosMapEntry *left = pRightMost, + *right = pMap->Add(SmCaretPos(node, 1)); + + for (USHORT i = 0; i < node->GetNumRows(); i++){ + + SmCaretPosMapEntry* r = left; + + for (USHORT j = 0; j < node->GetNumCols(); j++){ + SmNode* pNode = node->GetSubNode(i * node->GetNumCols() + j); + + pRightMost = pMap->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; +} + +void SmCaretPosMapBuildingVisitor::Visit(SmTextNode* node){ + VisitTextNode(node); +} + +/** Build SmCaretPosMap for SmTextNode + * + * Lines in an SmTextNode: + * \code + * A B C + * \endcode + * Where A B and C are characters in the text. + * + * Map between these, where "left" is before the SmTextNode and "right" is after: + * \dot + * digraph Map{ + * left -> A; + * A -> B + * B -> right; + * } + * \enddot + * Notice that C and right is the same position here. + */ +void SmCaretPosMapBuildingVisitor::VisitTextNode(SmTextNode* node){ + j_assert(node->GetText().Len() > 0, "Empty SmTextNode is bad"); + + int size = node->GetText().Len(); + for(int i = 1; i <= size; i++){ + SmCaretPosMapEntry* pRight = pRightMost; + pRightMost = pMap->Add(SmCaretPos(node, i), pRight); + pRight->SetRight(pRightMost); + } +} + +/** Build SmCaretPosMap for SmBinVerNode + * + * Lines in an SmBinVerNode: + * \code + * A + * ----- + * B + * \endcode + * + * Map between these, where "left" is before the SmBinVerNode and "right" is after: + * \dot + * digraph Map{ + * left -> A; + * A -> right; + * B -> right; + * } + * \enddot + */ +void SmCaretPosMapBuildingVisitor::Visit(SmBinVerNode* node){ + //None if these children can be NULL, see SmBinVerNode::Arrange + SmNode *pNum = node->GetSubNode(0), + *pDenom = node->GetSubNode(2); + + SmCaretPosMapEntry *left, + *right, + *numLeft, + *denomLeft; + + //Set left + left = pRightMost; + j_assert(pRightMost, "There must be a position infront of this"); + + //Create right + right = pMap->Add(SmCaretPos(node, 1)); + + //Create numLeft + numLeft = pMap->Add(SmCaretPos(pNum, 0), left); + left->SetRight(numLeft); + + //Visit pNum + pRightMost = numLeft; + pNum->Accept(this); + pRightMost->SetRight(right); + right->SetLeft(pRightMost); + + //Create denomLeft + denomLeft = pMap->Add(SmCaretPos(pDenom, 0), left); + + //Visit pDenom + pRightMost = denomLeft; + pDenom->Accept(this); + pRightMost->SetRight(right); + + //Set return parameter + pRightMost = right; +} + +/** Build SmCaretPosMap for SmVerticalBraceNode + * + * Lines in an SmVerticalBraceNode: + * \code + * pScript + * ________ + * / \ + * pBody + * \endcode + * + */ +void SmCaretPosMapBuildingVisitor::Visit(SmVerticalBraceNode* node){ + SmNode *pBody = node->GetSubNode(0), + *pScript = node->GetSubNode(2); + //None of these children can be NULL + + SmCaretPosMapEntry *left, + *bodyLeft, + *scriptLeft, + *right; + + left = pRightMost; + + //Create right + right = pMap->Add(SmCaretPos(node, 1)); + + //Create bodyLeft + bodyLeft = pMap->Add(SmCaretPos(pBody, 0), left); + left->SetRight(bodyLeft); + pRightMost = bodyLeft; + pBody->Accept(this); + pRightMost->SetRight(right); + right->SetLeft(pRightMost); + + //Create script + scriptLeft = pMap->Add(SmCaretPos(pScript, 0), left); + pRightMost = scriptLeft; + pScript->Accept(this); + pRightMost->SetRight(right); + + //Set return value + pRightMost = right; +} + +/** Build SmCaretPosMap 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 SmCaretPosMapBuildingVisitor::Visit(SmBinDiagonalNode* node){ + SmNode *A = node->GetSubNode(0), + *B = node->GetSubNode(1); + + SmCaretPosMapEntry *left, + *leftA, + *rightA, + *leftB, + *right; + left = pRightMost; + + //Create right + right = pMap->Add(SmCaretPos(node, 1)); + + //Create left A + leftA = pMap->Add(SmCaretPos(A, 0), left); + left->SetRight(leftA); + + //Visit A + pRightMost = leftA; + A->Accept(this); + rightA = pRightMost; + + //Create left B + leftB = pMap->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 SmCaretPosMapBuildingVisitor::Visit(SmOperNode* node){ + SmNodeIterator it(node); + while(it.Next()) + it->Accept(this); + //TODO: Consider adding caret pos that selects the entire SmOperNode +} +void SmCaretPosMapBuildingVisitor::Visit(SmBinHorNode* node){ + SmNodeIterator it(node); + while(it.Next()) + it->Accept(this); +} +void SmCaretPosMapBuildingVisitor::Visit(SmUnHorNode* node){ + // Unary operator node + SmNodeIterator it(node); + while(it.Next()) + it->Accept(this); + +} + +void SmCaretPosMapBuildingVisitor::Visit(SmExpressionNode* node){ + SmNodeIterator it(node); + while(it.Next()) + it->Accept(this); +} + +void SmCaretPosMapBuildingVisitor::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 SmCaretPosMap 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 SmCaretPosMapBuildingVisitor::Visit(SmBracebodyNode* node){ + SmNodeIterator it(node); + while(it.Next()) + it->Accept(this); +} + +/** Build SmCaretPosMap for SmAlignNode + * Acts as an SmExpressionNode, as it only has one child this okay + */ +void SmCaretPosMapBuildingVisitor::Visit(SmAlignNode* node){ + SmNodeIterator it(node); + while(it.Next()) + it->Accept(this); +} + +/** Build SmCaretPosMap for SmRootNode + * + * Lines in an SmRootNode: + * \code + * _________ + * A/ + * \/ B + * + * \endcode + * A: pExtra (optional, can be NULL), + * B: pBody + * + * Map between these, where "left" is before the SmRootNode and "right" is after: + * \dot + * digraph Map{ + * left -> B; + * B -> right; + * A -> B; + * } + * \enddot + */ +void SmCaretPosMapBuildingVisitor::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"); + + SmCaretPosMapEntry *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 = pMap->Add(SmCaretPos(pBody, 0), left); + left->SetRight(bodyLeft); + + //Create right + right = pMap->Add(SmCaretPos(node, 1)); + + //Visit body + pRightMost = bodyLeft; + pBody->Accept(this); + bodyRight = pRightMost; + bodyRight->SetRight(right); + right->SetLeft(bodyRight); + + //Visit pExtra + if(pExtra){ + pRightMost = pMap->Add(SmCaretPos(pExtra, 0), left); + pExtra->Accept(this); + pRightMost->SetRight(bodyLeft); + } + + pRightMost = right; +} + +/** Build SmCaretPosMap for SmPlaceNode + * Consider this a single character. + */ +void SmCaretPosMapBuildingVisitor::Visit(SmPlaceNode* node){ + SmCaretPosMapEntry* right = pMap->Add(SmCaretPos(node, 1), pRightMost); + pRightMost->SetRight(right); + pRightMost = right; +} + +/** Build SmCaretPosMap for SmErrorNode + * Consider this a single character. + */ +void SmCaretPosMapBuildingVisitor::Visit(SmErrorNode* node){ + SmCaretPosMapEntry* right = pMap->Add(SmCaretPos(node, 1), pRightMost); + pRightMost->SetRight(right); + pRightMost = right; +} + +/** Build SmCaretPosMap for SmBlankNode + * Consider this a single character, as it is only a blank space + */ +void SmCaretPosMapBuildingVisitor::Visit(SmBlankNode* node){ + SmCaretPosMapEntry* right = pMap->Add(SmCaretPos(node, 1), pRightMost); + pRightMost->SetRight(right); + pRightMost = right; +} + +/** Build SmCaretPosMap for SmBraceNode + * + * Lines in an SmBraceNode: + * \code + * | | + * | B | + * | | + * \endcode + * B: Body + * + * Map between these, where "left" is before the SmBraceNode and "right" is after: + * \dot + * digraph Map{ + * left -> B; + * B -> right; + * } + * \enddot + */ +void SmCaretPosMapBuildingVisitor::Visit(SmBraceNode* node){ + SmNode* pBody = node->GetSubNode(1); + + SmCaretPosMapEntry *left = pRightMost, + *right = pMap->Add(SmCaretPos(node, 1)); + + pRightMost = pMap->Add(SmCaretPos(pBody, 0), left); + left->SetRight(pRightMost); + + pBody->Accept(this); + pRightMost->SetRight(right); + right->SetLeft(pRightMost); + + pRightMost = right; +} + +/** Build SmCaretPosMap 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 SmCaretPosMapBuildingVisitor::Visit(SmAttributNode* node){ + SmNode *pAttr = node->GetSubNode(0), + *pBody = node->GetSubNode(1); + //None of the children can be NULL + + SmCaretPosMapEntry *left = pRightMost, + *attrLeft, + *bodyLeft, + *bodyRight, + *right; + + //Creating bodyleft + bodyLeft = pMap->Add(SmCaretPos(pBody, 0), left); + left->SetRight(bodyLeft); + + //Creating right + right = pMap->Add(SmCaretPos(node, 1)); + + //Visit the body + pRightMost = bodyLeft; + pBody->Accept(this); + bodyRight = pRightMost; + bodyRight->SetRight(right); + right->SetLeft(bodyRight); + + //Create attrLeft + attrLeft = pMap->Add(SmCaretPos(pAttr, 0), left); + + //Visit attribute + pRightMost = attrLeft; + pAttr->Accept(this); + pRightMost->SetRight(right); + + //Set return value + pRightMost = right; +} + +//Probably done by baseclass +void SmCaretPosMapBuildingVisitor::Visit(SmSpecialNode* node){ + VisitTextNode(node); +} +void SmCaretPosMapBuildingVisitor::Visit(SmGlyphSpecialNode* node){ + VisitTextNode(node); +} +void SmCaretPosMapBuildingVisitor::Visit(SmMathSymbolNode* node){ + VisitTextNode(node); +} + +void SmCaretPosMapBuildingVisitor::Visit(SmRootSymbolNode*){ + //Do nothing +} +void SmCaretPosMapBuildingVisitor::Visit(SmRectangleNode*){ + //Do nothing +} +void SmCaretPosMapBuildingVisitor::Visit(SmPolyLineNode*){ + //Do nothing +}