1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 #ifndef INCLUDED_STARMATH_INC_CURSOR_HXX
10 #define INCLUDED_STARMATH_INC_CURSOR_HXX
11 
12 #include "node.hxx"
13 #include "caret.hxx"
14 
15 #include <cassert>
16 #include <list>
17 #include <memory>
18 
19 /** Factor to multiple the squared horizontal distance with
20  * Used for Up and Down movement.
21  */
22 #define HORIZONTICAL_DISTANCE_FACTOR        10
23 
24 /** Enum of direction for movement */
25 enum SmMovementDirection{
26     MoveUp,
27     MoveDown,
28     MoveLeft,
29     MoveRight
30 };
31 
32 /** Enum of elements that can inserted into a formula */
33 enum SmFormulaElement{
34     BlankElement,
35     FactorialElement,
36     PlusElement,
37     MinusElement,
38     CDotElement,
39     EqualElement,
40     LessThanElement,
41     GreaterThanElement,
42     PercentElement
43 };
44 
45 /** Bracket types that can be inserted */
46 enum class SmBracketType {
47     /** Round brackets, left command "(" */
48     Round,
49     /**Square brackets, left command "[" */
50     Square,
51     /** Curly brackets, left command "lbrace" */
52     Curly,
53 };
54 
55 /** A list of nodes */
56 typedef std::list<SmNode*> SmNodeList;
57 
58 typedef std::list<std::unique_ptr<SmNode>> SmClipboard;
59 
60 class SmDocShell;
61 
62 /** Formula cursor
63  *
64  * This class is used to represent a cursor in a formula, which can be used to manipulate
65  * a formula programmatically.
66  * @remarks This class is a very intimate friend of SmDocShell.
67  */
68 class SmCursor{
69 public:
SmCursor(SmNode * tree,SmDocShell * pShell)70     SmCursor(SmNode* tree, SmDocShell* pShell)
71         : mpAnchor(nullptr)
72         , mpPosition(nullptr)
73         , mpTree(tree)
74         , mpDocShell(pShell)
75         , mnEditSections(0)
76         , mbIsEnabledSetModifiedSmDocShell(false)
77     {
78         //Build graph
79         BuildGraph();
80     }
81 
82     /** Get position */
GetPosition() const83     const SmCaretPos& GetPosition() const { return mpPosition->CaretPos; }
84 
85     /** True, if the cursor has a selection */
HasSelection() const86     bool HasSelection() const { return mpAnchor != mpPosition; }
87 
88     /** Move the position of this cursor */
89     void Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor = true);
90 
91     /** Move to the caret position closet to a given point */
92     void MoveTo(OutputDevice* pDev, const Point& pos, bool bMoveAnchor);
93 
94     /** Delete the current selection or do nothing */
95     void Delete();
96 
97     /** Delete selection, previous element or merge lines
98      *
99      * This method implements the behaviour of backspace.
100      */
101     void DeletePrev(OutputDevice* pDev);
102 
103     /** Insert text at the current position */
104     void InsertText(const OUString& aString);
105 
106     /** Insert an element into the formula */
107     void InsertElement(SmFormulaElement element);
108 
109     /** Insert command text translated into line entries at position
110      *
111      * Note: This method uses the parser to translate a command text into a
112      * tree, then it copies line entries from this tree into the current tree.
113      * Will not work for commands such as newline or ##, if position is in a matrix.
114      * This will work for stuff like "A intersection B". But stuff spanning multiple lines
115      * or dependent on the context which position is placed in will not work!
116      */
117     void InsertCommandText(const OUString& aCommandText);
118 
119     /** Insert a special node created from aString
120      *
121      * Used for handling insert request from the "catalog" dialog.
122      * The provided string should be formatted as the desired command: %phi
123      * Note: this method ONLY supports commands defined in Math.xcu
124      *
125      * For more complex expressions use InsertCommandText, this method doesn't
126      * use SmParser, this means that it's faster, but not as strong.
127      */
128     void InsertSpecial(const OUString& aString);
129 
130     /** Create sub-/super script
131      *
132      * If there's a selection, it will be move into the appropriate sub-/super scription
133      * of the node in front of it. If there's no node in front of position (or the selection),
134      * a sub-/super scription of a new SmPlaceNode will be made.
135      *
136      * If there's is an existing subscription of the node, the caret will be moved into it,
137      * and any selection will replace it.
138      */
139     void InsertSubSup(SmSubSup eSubSup);
140 
141     /** Insert a new row or newline
142      *
143      * Inserts a new row if position is in a matrix or stack command.
144      * Otherwise a newline is inserted if we're in a toplevel line.
145      *
146      * @returns True, if a new row/line could be inserted.
147      *
148      * @remarks If the caret is placed in a subline of a command that doesn't support
149      *          this operator the method returns FALSE, and doesn't do anything.
150      */
151     bool InsertRow();
152 
153     /** Insert a fraction, use selection as numerator */
154     void InsertFraction();
155 
156     /** Create brackets around current selection, or new SmPlaceNode */
157     void InsertBrackets(SmBracketType eBracketType);
158 
159     /** Copy the current selection */
160     void Copy();
161     /** Cut the current selection */
Cut()162     void Cut(){
163         Copy();
164         Delete();
165     }
166     /** Paste the clipboard */
167     void Paste();
168 
169     /** Returns true if more than one node is selected
170      *
171      * This method is used for implementing backspace and delete.
172      * If one of these causes a complex selection, e.g. a node with
173      * subnodes or similar, this should not be deleted immediately.
174      */
175     bool HasComplexSelection();
176 
177     /** Finds the topmost node in a visual line
178      *
179      * If MoveUpIfSelected is true, this will move up to the parent line
180      * if the parent of the current line is selected.
181      */
182     static SmNode* FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected = false);
183 
184     /** Draw the caret */
185     void Draw(OutputDevice& pDev, Point Offset, bool isCaretVisible);
186 
187     bool IsAtTailOfBracket(SmBracketType eBracketType, SmBraceNode** ppBraceNode) const;
188     void MoveAfterBracket(SmBraceNode* pBraceNode);
189 
190 private:
191     friend class SmDocShell;
192 
193     SmCaretPosGraphEntry    *mpAnchor,
194                             *mpPosition;
195     /** Formula tree */
196     SmNode* mpTree;
197     /** Owner of the formula tree */
198     SmDocShell* mpDocShell;
199     /** Graph over caret position in the current tree */
200     std::unique_ptr<SmCaretPosGraph> mpGraph;
201     /** Clipboard holder */
202     SmClipboard maClipboard;
203 
204     /** Returns a node that is selected, if any could be found */
205     SmNode* FindSelectedNode(SmNode* pNode);
206 
207     /** Is this one of the nodes used to compose a line
208      *
209      * These are SmExpression, SmBinHorNode, SmUnHorNode etc.
210      */
211     static bool IsLineCompositionNode(SmNode const * pNode);
212 
213     /** Count number of selected nodes, excluding line composition nodes
214      *
215      * Note this function doesn't count line composition nodes and it
216      * does count all subnodes as well as the owner nodes.
217      *
218      * Used by SmCursor::HasComplexSelection()
219      */
220     int CountSelectedNodes(SmNode* pNode);
221 
222     /** Convert a visual line to a list
223      *
224      * Note this method will delete all the nodes that will no longer be needed.
225      * that includes pLine!
226      * This method also deletes SmErrorNode's as they're just meta info in the line.
227      */
228     static void LineToList(SmStructureNode* pLine, SmNodeList& rList);
229 
230     /** Auxiliary function for calling LineToList on a node
231      *
232      * This method sets pNode = NULL and remove it from its parent.
233      * (Assuming it has a parent, and is a child of it).
234      */
NodeToList(SmNode * & rpNode,SmNodeList & rList)235     static void NodeToList(SmNode*& rpNode, SmNodeList& rList){
236         //Remove from parent and NULL rpNode
237         SmNode* pNode = rpNode;
238         if(rpNode && rpNode->GetParent()){    //Don't remove this, correctness relies on it
239             int index = rpNode->GetParent()->IndexOfSubNode(rpNode);
240             assert(index >= 0);
241             rpNode->GetParent()->SetSubNode(index, nullptr);
242         }
243         rpNode = nullptr;
244         //Create line from node
245         if(pNode && IsLineCompositionNode(pNode)){
246             LineToList(static_cast<SmStructureNode*>(pNode), rList);
247             return;
248         }
249         if(pNode)
250             rList.push_front(pNode);
251     }
252 
253     /** Clone a visual line to a clipboard
254      *
255      * ... but the selected part only.
256      * Doesn't clone SmErrorNodes, which are ignored as they are context dependent metadata.
257      */
258     static void CloneLineToClipboard(SmStructureNode* pLine, SmClipboard* pClipboard);
259 
260     /** Build pGraph over caret positions */
261     void BuildGraph();
262 
263     /** Insert new nodes in the tree after position */
264     void InsertNodes(std::unique_ptr<SmNodeList> pNewNodes);
265 
266     /** tries to set position to a specific SmCaretPos
267      *
268      * @returns false on failure to find the position in pGraph.
269      */
270     bool SetCaretPosition(SmCaretPos pos);
271 
272     /** Set selected on nodes of the tree */
273     void AnnotateSelection();
274 
275     /** Clone list of nodes in a clipboard (creates a deep clone) */
276     static std::unique_ptr<SmNodeList> CloneList(SmClipboard &rClipboard);
277 
278     /** Find an iterator pointing to the node in pLineList following rCaretPos
279      *
280      * If rCaretPos.pSelectedNode cannot be found it is assumed that it's in front of pLineList,
281      * thus not an element in pLineList. In this case this method returns an iterator to the
282      * first element in pLineList.
283      *
284      * If the current position is inside an SmTextNode, this node will be split in two, for this
285      * reason you should beaware that iterators to elements in pLineList may be invalidated, and
286      * that you should call PatchLineList() with this iterator if no action is taken.
287      */
288     static SmNodeList::iterator FindPositionInLineList(SmNodeList* pLineList,
289                                                        const SmCaretPos& rCaretPos);
290 
291     /** Patch a line list after modification, merge SmTextNode, remove SmPlaceNode etc.
292      *
293      * @param pLineList The line list to patch
294      * @param aIter     Iterator pointing to the element that needs to be patched with its previous.
295      *
296      * When the list is patched text nodes before and after aIter will be merged.
297      * If there's an, in the context, inappropriate SmPlaceNode before or after aIter it will also be
298      * removed.
299      *
300      * @returns A caret position equivalent to one selecting the node before aIter, the method returns
301      *          an invalid SmCaretPos to indicate placement in front of the line.
302      */
303      static SmCaretPos PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter);
304 
305     /** Take selected nodes from a list
306      *
307      * Puts the selected nodes into pSelectedNodes, or if pSelectedNodes is NULL deletes
308      * the selected nodes.
309      * Note: If there's a selection inside an SmTextNode this node will be split, and it
310      * will not be merged when the selection have been taken. Use PatchLineList on the
311      * iterator returns to fix this.
312      *
313      * @returns An iterator pointing to the element following the selection taken.
314      */
315     static SmNodeList::iterator TakeSelectedNodesFromList(SmNodeList *pLineList,
316                                                          SmNodeList *pSelectedNodes = nullptr);
317 
318     /** Create an instance of SmMathSymbolNode usable for brackets */
319     static SmNode *CreateBracket(SmBracketType eBracketType, bool bIsLeft);
320 
321     /** The number of times BeginEdit have been called
322      * Used to allow nesting of BeginEdit() and EndEdit() sections
323      */
324     int mnEditSections;
325     /** Holds data for BeginEdit() and EndEdit() */
326     bool mbIsEnabledSetModifiedSmDocShell;
327     /** Begin edit section where the tree will be modified */
328     void BeginEdit();
329     /** End edit section where the tree will be modified */
330     void EndEdit();
331     /** Finish editing
332      *
333      * Finishes editing by parsing pLineList and inserting back into pParent at nParentIndex.
334      * This method also rebuilds the graph, annotates the selection, sets caret position and
335      * Calls EndEdit.
336      *
337      * @remarks Please note that this method will delete pLineList, as the elements are taken.
338      *
339      * @param pLineList     List the constitutes the edited line.
340      * @param pParent       Parent to which the line should be inserted.
341      * @param nParentIndex  Index in parent where the line should be inserted.
342      * @param PosAfterEdit  Caret position to look for after rebuilding graph.
343      * @param pStartLine    Line to take first position in, if PosAfterEdit cannot be found,
344      *                      leave it NULL for pLineList.
345      */
346     void FinishEdit(std::unique_ptr<SmNodeList> pLineList,
347                     SmStructureNode* pParent,
348                     int nParentIndex,
349                     SmCaretPos PosAfterEdit,
350                     SmNode* pStartLine = nullptr);
351     /** Request the formula is repainted */
352     void RequestRepaint();
353 };
354 
355 /** Minimalistic recursive decent SmNodeList parser
356  *
357  * This parser is used to take a list of nodes that constitutes a line
358  * and parse them to a tree of SmBinHorNode, SmUnHorNode and SmExpression.
359  *
360  * Please note, this will not handle all kinds of nodes, only nodes that
361  * constitutes and entry in a line.
362  *
363  * Below is an EBNF representation of the grammar used for this parser:
364  * \code
365  * Expression   -> Relation*
366  * Relation     -> Sum [(=|<|>|...) Sum]*
367  * Sum          -> Product [(+|-) Product]*
368  * Product      -> Factor [(*|/) Factor]*
369  * Factor       -> [+|-|-+|...]* Factor | Postfix
370  * Postfix      -> node [!]*
371  * \endcode
372  */
373 class SmNodeListParser{
374 public:
375     /** Create an instance of SmNodeListParser */
SmNodeListParser()376     SmNodeListParser(){
377         pList = nullptr;
378     }
379     /** Parse a list of nodes to an expression.
380      *
381      * Old error nodes will be deleted.
382      */
383     SmNode* Parse(SmNodeList* list);
384     /** True, if the token is an operator */
385     static bool IsOperator(const SmToken &token);
386     /** True, if the token is a relation operator */
387     static bool IsRelationOperator(const SmToken &token);
388     /** True, if the token is a sum operator */
389     static bool IsSumOperator(const SmToken &token);
390     /** True, if the token is a product operator */
391     static bool IsProductOperator(const SmToken &token);
392     /** True, if the token is a unary operator */
393     static bool IsUnaryOperator(const SmToken &token);
394     /** True, if the token is a postfix operator */
395     static bool IsPostfixOperator(const SmToken &token);
396 private:
397     SmNodeList* pList;
398     /** Get the current terminal */
Terminal()399     SmNode* Terminal(){
400         if (!pList->empty())
401             return pList->front();
402         return nullptr;
403     }
404     /** Move to next terminal */
Next()405     SmNode* Next(){
406         pList->pop_front();
407         return Terminal();
408     }
409     /** Take the current terminal */
Take()410     SmNode* Take(){
411         SmNode* pRetVal = Terminal();
412         Next();
413         return pRetVal;
414     }
415     SmNode* Expression();
416     SmNode* Relation();
417     SmNode* Sum();
418     SmNode* Product();
419     SmNode* Factor();
420     SmNode* Postfix();
421     static SmNode* Error();
422 };
423 
424 
425 #endif // INCLUDED_STARMATH_INC_CURSOR_HXX
426 
427 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
428