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