Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/starmath/inc/caret.hxx
Line
Count
Source
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 <sal/config.h>
13
#include "node.hxx"
14
15
/** Representation of caret position with an equation */
16
struct SmCaretPos
17
{
18
    SmCaretPos(SmNode* selectedNode = nullptr, int iIndex = 0)
19
0
        : pSelectedNode(selectedNode)
20
0
        , nIndex(iIndex)
21
0
    {
22
0
        assert(nIndex >= 0);
23
0
    }
24
25
    /** Selected node */
26
    SmNode* pSelectedNode;
27
28
    /** Index (invariant: non-negative) within the selected node
29
     *
30
     * 0: Position in front of a node
31
     * 1: Position after a node or after first char in SmTextNode
32
     * n: Position after n char in SmTextNode
33
     *
34
     * Notice how there's special cases for SmTextNode.
35
     */
36
    //TODO: Special cases for SmBlankNode is needed
37
    //TODO: Consider forgetting about the todo above... As it's really unpleasant.
38
    int nIndex;
39
40
    /** True, if this is a valid caret position */
41
0
    bool IsValid() const { return pSelectedNode != nullptr; }
42
    bool operator==(const SmCaretPos& pos) const
43
0
    {
44
0
        return pos.pSelectedNode == pSelectedNode && nIndex == pos.nIndex;
45
0
    }
46
    /** Get the caret position after pNode, regardless of pNode
47
     *
48
     * Gets the caret position following pNode, this is SmCaretPos(pNode, 1).
49
     * Unless pNode is an instance of SmTextNode, then the index is the text length.
50
     */
51
    static SmCaretPos GetPosAfter(SmNode* pNode)
52
0
    {
53
0
        if (pNode && pNode->GetType() == SmNodeType::Text)
54
0
            return SmCaretPos(pNode, static_cast<SmTextNode*>(pNode)->GetText().getLength());
55
0
        return SmCaretPos(pNode, 1);
56
0
    }
57
};
58
59
/** A line that represents a caret */
60
class SmCaretLine
61
{
62
public:
63
    SmCaretLine(tools::Long left = 0, tools::Long top = 0, tools::Long height = 0)
64
0
    {
65
0
        _top = top;
66
0
        _left = left;
67
0
        _height = height;
68
0
    }
69
0
    tools::Long GetTop() const { return _top; }
70
0
    tools::Long GetLeft() const { return _left; }
71
0
    tools::Long GetHeight() const { return _height; }
72
    tools::Long SquaredDistanceX(const SmCaretLine& line) const
73
0
    {
74
0
        return (GetLeft() - line.GetLeft()) * (GetLeft() - line.GetLeft());
75
0
    }
76
    tools::Long SquaredDistanceX(const Point& pos) const
77
0
    {
78
0
        return (GetLeft() - pos.X()) * (GetLeft() - pos.X());
79
0
    }
80
    tools::Long SquaredDistanceY(const SmCaretLine& line) const
81
0
    {
82
0
        tools::Long d = GetTop() - line.GetTop();
83
0
        if (d < 0)
84
0
            d = (d * -1) - GetHeight();
85
0
        else
86
0
            d = d - line.GetHeight();
87
0
        if (d < 0)
88
0
            return 0;
89
0
        return d * d;
90
0
    }
91
    tools::Long SquaredDistanceY(const Point& pos) const
92
0
    {
93
0
        tools::Long d = GetTop() - pos.Y();
94
0
        if (d < 0)
95
0
            d = (d * -1) - GetHeight();
96
0
        if (d < 0)
97
0
            return 0;
98
0
        return d * d;
99
0
    }
100
101
private:
102
    tools::Long _top;
103
    tools::Long _left;
104
    tools::Long _height;
105
};
106
107
// SmCaretPosGraph
108
109
/** An entry in SmCaretPosGraph */
110
struct SmCaretPosGraphEntry
111
{
112
    SmCaretPosGraphEntry(SmCaretPos pos, SmCaretPosGraphEntry* left, SmCaretPosGraphEntry* right)
113
0
        : CaretPos{ pos }
114
0
        , Left{ left }
115
0
        , Right{ right }
116
0
    {
117
0
    }
118
    /** Caret position */
119
    const SmCaretPos CaretPos;
120
    /** Entry to the left visually */
121
    SmCaretPosGraphEntry* Left;
122
    /** Entry to the right visually */
123
    SmCaretPosGraphEntry* Right;
124
0
    void SetRight(SmCaretPosGraphEntry* right) { Right = right; }
125
0
    void SetLeft(SmCaretPosGraphEntry* left) { Left = left; }
126
};
127
128
/** A graph over all caret positions
129
 * @remarks Graphs can only grow, entries cannot be removed!
130
 */
131
class SmCaretPosGraph
132
{
133
public:
134
    SmCaretPosGraph();
135
136
    ~SmCaretPosGraph();
137
138
    /** Add a caret position
139
     *  @remarks If left is NULL, they will point back to the entry.
140
     */
141
    SmCaretPosGraphEntry* Add(SmCaretPos pos, SmCaretPosGraphEntry* left = nullptr);
142
143
    std::vector<std::unique_ptr<SmCaretPosGraphEntry>>::iterator begin()
144
0
    {
145
0
        return mvEntries.begin();
146
0
    }
147
148
0
    std::vector<std::unique_ptr<SmCaretPosGraphEntry>>::iterator end() { return mvEntries.end(); }
149
150
private:
151
    std::vector<std::unique_ptr<SmCaretPosGraphEntry>> mvEntries;
152
};
153
154
/** \page visual_formula_editing Visual Formula Editing
155
 * A visual formula editor allows users to easily edit formulas without having to learn and
156
 * use complicated commands. A visual formula editor is a WYSIWYG editor. For OpenOffice Math
157
 * this essentially means that you can click on the formula image, to get a caret, which you
158
 * can move with arrow keys, and use to modify the formula by entering text, clicking buttons
159
 * or using shortcuts.
160
 *
161
 * \subsection formula_trees Formula Trees
162
 * A formula in OpenOffice Math is a tree of nodes, take for instance the formula
163
 * "A + {B cdot C} over D", it looks like this
164
 * \f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$. The tree for this formula
165
 * looks like this:
166
 *
167
 * \dot
168
 * digraph {
169
 * labelloc = "t";
170
 * label= "Equation: \"A  + {B  cdot C} over D\"";
171
 * size = "9,9";
172
 * n0 [label="SmTableNode (1)"];
173
 * n0 -> n1 [label="0"];
174
 * n1 [label="SmLineNode (2)"];
175
 * n1 -> n2 [label="0"];
176
 * n2 [label="SmExpressionNode (3)"];
177
 * n2 -> n3 [label="0"];
178
 * n3 [label="SmBinHorNode (4)"];
179
 * n3 -> n4 [label="0"];
180
 * n4 [label="SmTextNode: A (5)"];
181
 * n3 -> n5 [label="1"];
182
 * n5 [label="SmMathSymbolNode: + (6)"];
183
 * n3 -> n6 [label="2"];
184
 * n6 [label="SmBinVerNode (7)"];
185
 * n6 -> n7 [label="0"];
186
 * n7 [label="SmExpressionNode (8)"];
187
 * n7 -> n8 [label="0"];
188
 * n8 [label="SmBinHorNode (9)"];
189
 * n8 -> n9 [label="0"];
190
 * n9 [label="SmTextNode: B (10)"];
191
 * n8 -> n10 [label="1"];
192
 * n10 [label="SmMathSymbolNode: &#183; (11)"];
193
 * n8 -> n11 [label="2"];
194
 * n11 [label="SmTextNode: C (12)"];
195
 * n6 -> n12 [label="1"];
196
 * n12 [label="SmRectangleNode (13)"];
197
 * n6 -> n13 [label="2"];
198
 * n13 [label="SmTextNode: D (14)"];
199
 * }
200
 * \enddot
201
 *
202
 * The vertices are nodes, their label says what kind of node and the number in parentheses is
203
 *  the identifier of the node (In practices a pointer is used instead of the id). The direction
204
 *  of the edges tells which node is parent and which is child. The label of the edges are the
205
 *  child node index number, given to SmNode::GetSubNode() of the parent to get the child node.
206
 *
207
 *
208
 * \subsection visual_lines Visual Lines
209
 *
210
 * Inorder to do caret movement in visual lines, we need a definition of caret position and
211
 * visual line. In a tree such as the above there are three visual lines. There's the outer most
212
 * line, with entries such as
213
 * \f$\mbox{A}\f$, \f$ + \f$ and \f$ \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$. Then there's
214
 *  the numerator line of the fraction it has entries \f$ \mbox{B} \f$, \f$ \cdot \f$ and \f$ \mbox{C} \f$.
215
 *  And last by not least there's the denominator line of the fraction it's only entry is \f$ \mbox{D} \f$.
216
 *
217
 * For visual editing it should be possible to place a caret on both sides of any line entry,
218
 * consider a line entry a character or construction that in a line is treated as a character.
219
 *  Imagine the caret is placed to the right of the plus sign (id: 6), now if user presses
220
 * backspace this should delete the plus sign (id: 6), and if the user presses delete this
221
 * should delete the entire fraction (id: 7). This is because the caret is in the outer most
222
 * line where the fraction is considered a line entry.
223
 *
224
 * However, inorder to prevent users from accidentally deleting large subtrees, just because
225
 * they logically placed there caret a in the wrong line, require that complex constructions
226
 * such as a fraction is selected before it is deleted. Thus in this case it wouldn't be
227
 * deleted, but only selected and then deleted if the user hit delete again. Anyway, this is
228
 * slightly off topic for now.
229
 *
230
 * Important about visual lines is that they don't always have an SmExpressionNode as root
231
 * and the entries in a visual line is all the nodes of a subtree ordered left to right that
232
 *  isn't either an SmExpressionNode, SmBinHorNode or SmUnHorNode.
233
 *
234
 *
235
 * \subsection caret_positions Caret Positions
236
 *
237
 * A caret position in OpenOffice Math is represented by an instance of SmCaretPos.
238
 * That is a caret position is a node and an index related to this node. For most nodes the
239
 * index 0, means caret is in front of this node, the index 1 means caret is after this node.
240
 * For SmTextNode the index is the caret position after the specified number of characters,
241
 * imagine an SmTextNode with the number 1337. The index 3 in such SmTextNode would mean a
242
 * caret placed right before 7, e.g. "133|7".
243
 *
244
 * For SmExpressionNode, SmBinHorNode and SmUnHorNode the only legal index is 0, which means
245
 * in front of the node. Actually the index 0 may only because for the first caret position
246
 * in a visual line. From the example above, consider the following subtree that constitutes
247
 * a visual line:
248
 *
249
 * \dot
250
 * digraph {
251
 * labelloc = "t";
252
 * label= "Subtree that constitutes a visual line";
253
 * size = "7,5";
254
 * n7 [label="SmExpressionNode (8)"];
255
 * n7 -> n8 [label="0"];
256
 * n8 [label="SmBinHorNode (9)"];
257
 * n8 -> n9 [label="0"];
258
 * n9 [label="SmTextNode: B (10)"];
259
 * n8 -> n10 [label="1"];
260
 * n10 [label="SmMathSymbolNode: &#183; (11)"];
261
 * n8 -> n11 [label="2"];
262
 * n11 [label="SmTextNode: C (12)"];
263
 * }
264
 * \enddot
265
 * Here the caret positions are:
266
 *
267
 * <TABLE>
268
 * <TR><TD><B>Caret position:</B></TD><TD><B>Example:</B></TD>
269
 * </TR><TR>
270
 *     <TD>{id: 8, index: 0}</TD>
271
 *     <TD>\f$ \mid \mbox{C} \cdot \mbox{C} \f$</TD>
272
 * </TR><TR>
273
 *     <TD>{id: 10, index: 1}</TD>
274
 *     <TD>\f$ \mbox{C} \mid \cdot \mbox{C} \f$</TD>
275
 * </TR><TR>
276
 *     <TD>{id: 11, index: 1}</TD>
277
 *     <TD>\f$ \mbox{C} \cdot \mid \mbox{C} \f$</TD>
278
 * </TR><TR>
279
 *     <TD>{id: 12, index: 1}</TD>
280
 *     <TD>\f$ \mbox{C} \cdot \mbox{C} \mid \f$</TD>
281
 * </TR><TR>
282
 * </TABLE>
283
 *
284
 * Where \f$ \mid \f$ is used to denote caret position.
285
 *
286
 * With these exceptions included in the definition the id and index: {id: 11, index: 0} does
287
 * \b not constitute a caret position in the given context. Note the method
288
 * SmCaretPos::IsValid() does not check if this invariant holds true, but code in SmCaret,
289
 * SmSetSelectionVisitor and other places depends on this invariant to hold.
290
 *
291
 *
292
 * \subsection caret_movement Caret Movement
293
 *
294
 * As the placement of caret positions depends very much on the context within which a node
295
 * appears it is not trivial to find all caret positions and determine which follows which.
296
 * In OpenOffice Math this is done by the SmCaretPosGraphBuildingVisitor. This visitor builds
297
 * graph (an instance of SmCaretPosGraph) over the caret positions. For details on how this
298
 * graph is built, and how new methods should be implemented see SmCaretPosGraphBuildingVisitor.
299
 *
300
 * The result of the SmCaretPosGraphBuildingVisitor is a graph over the caret positions in a
301
 * formula, represented by an instance of SmCaretPosGraph. Each entry (instances of SmCaretPosGraphEntry)
302
 * has a pointer to the entry to the left and right of itself. This way we can easily find
303
 * the caret position to a right or left of a given caret position. Note each caret position
304
 * only appears once in this graph.
305
 *
306
 * When searching for a caret position after a left click on the formula this map is also used.
307
 * We simply iterate over all entries, uses the SmCaretPos2LineVisitor to find a line for each
308
 * caret position. Then the distance from the click to the line is computed and we choose the
309
 * caret position closest to the click.
310
 *
311
 * For up and down movement, we also iterator over all caret positions and use SmCaretPos2LineVisitor
312
 * to find a line for each caret position. Then we compute the distance from the current
313
 * caret position to every other caret position and chooses the one closest that is either
314
 * above or below the current caret position, depending on whether we're doing up or down movement.
315
 *
316
 * This result of this approach to caret movement is that we have logically predictable
317
 * movement for left and right, whilst leftclick, up and down movement depends on the sizes
318
 * and placement of all node and may be less logically predictable. This solution also means
319
 * that we only have one complex visitor generating the graph, imagine the nightmare if we
320
 * had a visitor for movement in each direction.
321
 *
322
 * Making up and down movement independent of node sizes and placement wouldn't necessarily
323
 * be a good thing either. Consider the formula \f$ \frac{1+2+3+4+5}{6} \f$, if the caret is
324
 * placed as displayed here: \f$ \frac{1+2+3+4+5}{6 \mid} \f$, up movement should move to right
325
 * after "3": \f$ \frac{1+2+3|+4+5}{6} \f$. However, such a move depends on the sizes and placement
326
 * of all nodes in the fraction.
327
 *
328
 *
329
 * \subsubsection caretpos_graph_example Example of Caret Position Graph
330
 *
331
 * If we consider the formula
332
 * \f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$ from \ref formula_trees.
333
 * It has the following caret positions:
334
 *
335
 * <TABLE>
336
 * <TR>
337
 *     <TD><B>Caret position:</B></TD>
338
 *     <TD><B>Example:</B></TD>
339
 * </TR><TR>
340
 *     <TD>{id: 3, index: 0}</TD>
341
 *     <TD>\f$ \mid\mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
342
 * </TR><TR>
343
 *     <TD>{id: 5, index: 1}</TD>
344
 *     <TD>\f$ \mbox{A}\mid + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
345
 * </TR><TR>
346
 *     <TD>{id: 6, index: 1}</TD>
347
 *     <TD>\f$ \mbox{A} + \mid \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
348
 * </TR><TR>
349
 *     <TD>{id: 8, index: 0}</TD>
350
 *     <TD>\f$ \mbox{A} + \frac{ \mid \mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
351
 * </TR><TR>
352
 *     <TD>{id: 10, index: 1}</TD>
353
 *     <TD>\f$ \mbox{A} + \frac{\mbox{B} \mid \cdot \mbox{C}}{\mbox{D}} \f$</TD>
354
 * </TR><TR>
355
 *     <TD>{id: 11, index: 1}</TD>
356
 *     <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mid \mbox{C}}{\mbox{D}} \f$</TD>
357
 * </TR><TR>
358
 *     <TD>{id: 12, index: 1}</TD>
359
 *     <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C} \mid}{\mbox{D}} \f$</TD>
360
 * </TR><TR>
361
 *     <TD>{id: 14, index: 0}</TD>
362
 *     <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mid \mbox{D}} \f$</TD>
363
 * </TR><TR>
364
 *     <TD>{id: 14, index: 1}</TD>
365
 *     <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D} \mid} \f$</TD>
366
 * </TR><TR>
367
 *     <TD>{id: 7, index: 1}</TD>
368
 *     <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \mid \f$</TD>
369
 * </TR>
370
 * </TABLE>
371
 *
372
 * Below is a directed graph over the caret positions and how you can move between them.
373
 * \dot
374
 * digraph {
375
 *     labelloc = "t";
376
 *     label= "Caret Position Graph";
377
 *     size = "4,6";
378
 *     p0 [label = "{id: 3, index: 0}"];
379
 *     p0 -> p1 [fontsize = 10.0, label = "right"];
380
 *     p1 [label = "{id: 5, index: 1}"];
381
 *     p1 -> p0 [fontsize = 10.0, label = "left"];
382
 *     p1 -> p2 [fontsize = 10.0, label = "right"];
383
 *     p2 [label = "{id: 6, index: 1}"];
384
 *     p2 -> p1 [fontsize = 10.0, label = "left"];
385
 *     p2 -> p3 [fontsize = 10.0, label = "right"];
386
 *     p3 [label = "{id: 8, index: 0}"];
387
 *     p3 -> p2 [fontsize = 10.0, label = "left"];
388
 *     p3 -> p4 [fontsize = 10.0, label = "right"];
389
 *     p4 [label = "{id: 10, index: 1}"];
390
 *     p4 -> p3 [fontsize = 10.0, label = "left"];
391
 *     p4 -> p5 [fontsize = 10.0, label = "right"];
392
 *     p5 [label = "{id: 11, index: 1}"];
393
 *     p5 -> p4 [fontsize = 10.0, label = "left"];
394
 *     p5 -> p6 [fontsize = 10.0, label = "right"];
395
 *     p6 [label = "{id: 12, index: 1}"];
396
 *     p6 -> p5 [fontsize = 10.0, label = "left"];
397
 *     p6 -> p9 [fontsize = 10.0, label = "right"];
398
 *     p7 [label = "{id: 14, index: 0}"];
399
 *     p7 -> p2 [fontsize = 10.0, label = "left"];
400
 *     p7 -> p8 [fontsize = 10.0, label = "right"];
401
 *     p8 [label = "{id: 14, index: 1}"];
402
 *     p8 -> p7 [fontsize = 10.0, label = "left"];
403
 *     p8 -> p9 [fontsize = 10.0, label = "right"];
404
 *     p9 [label = "{id: 7, index: 1}"];
405
 *     p9 -> p6 [fontsize = 10.0, label = "left"];
406
 * }
407
 * \enddot
408
 */
409
410
/* TODO: Write documentation about the following keywords:
411
 *
412
 * Visual Selections:
413
 *  - Show images
414
 *  - Talk about how the visitor does this
415
 *
416
 * Modifying a Visual Line:
417
 *  - Find top most non-compo of the line (e.g. The subtree that constitutes a line)
418
 *  - Make the line into a list
419
 *  - Edit the list, add/remove/modify nodes
420
 *  - Parse the list back into a subtree
421
 *  - Insert the new subtree where the old was taken
422
 */
423
424
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */