Coverage Report

Created: 2025-07-07 10:01

/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: */