1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 #ifndef INCLUDED_STARMATH_INC_CARET_HXX
10 #define INCLUDED_STARMATH_INC_CARET_HXX
14 /** Representation of caret position with an equation */
16 SmCaretPos(SmNode
* selectedNode
= NULL
, int iIndex
= 0) {
17 pSelectedNode
= selectedNode
;
21 SmNode
* pSelectedNode
;
22 /** Index within the selected node
24 * 0: Position in front of a node
25 * 1: Position after a node or after first char in SmTextNode
26 * n: Position after n char in SmTextNode
28 * Notice how there's special cases for SmTextNode.
30 //TODO: Special cases for SmBlankNode is needed
31 //TODO: Consider forgetting about the todo above... As it's really unpleasant.
33 /** True, if this is a valid caret position */
34 bool IsValid() const { return pSelectedNode
!= NULL
; }
35 bool operator!=(SmCaretPos pos
) const {
36 return pos
.pSelectedNode
!= pSelectedNode
|| Index
!= pos
.Index
;
38 bool operator==(SmCaretPos pos
) const {
39 return pos
.pSelectedNode
== pSelectedNode
&& Index
== pos
.Index
;
41 /** Get the caret position after pNode, regardless of pNode
43 * Gets the caret position following pNode, this is SmCaretPos(pNode, 1).
44 * Unless pNode is an instance of SmTextNode, then the index is the text length.
46 static SmCaretPos
GetPosAfter(SmNode
* pNode
) {
47 if(pNode
&& pNode
->GetType() == NTEXT
)
48 return SmCaretPos(pNode
, ((SmTextNode
*)pNode
)->GetText().getLength());
49 return SmCaretPos(pNode
, 1);
53 /** A line that represents a caret */
56 SmCaretLine(long left
= 0, long top
= 0, long height
= 0) {
61 long GetTop() const {return _top
;}
62 long GetLeft() const {return _left
;}
63 long GetHeight() const {return _height
;}
64 long SquaredDistanceX(SmCaretLine line
) const{
65 return (GetLeft() - line
.GetLeft()) * (GetLeft() - line
.GetLeft());
67 long SquaredDistanceX(Point pos
) const{
68 return (GetLeft() - pos
.X()) * (GetLeft() - pos
.X());
70 long SquaredDistanceY(SmCaretLine line
) const{
71 long d
= GetTop() - line
.GetTop();
73 d
= (d
* -1) - GetHeight();
75 d
= d
- line
.GetHeight();
80 long SquaredDistanceY(Point pos
) const{
81 long d
= GetTop() - pos
.Y();
83 d
= (d
* -1) - GetHeight();
96 /** An entry in SmCaretPosGraph */
97 struct SmCaretPosGraphEntry
{
98 SmCaretPosGraphEntry(SmCaretPos pos
= SmCaretPos(),
99 SmCaretPosGraphEntry
* left
= NULL
,
100 SmCaretPosGraphEntry
* right
= NULL
){
105 /** Caret position */
107 /** Entry to the left visually */
108 SmCaretPosGraphEntry
* Left
;
109 /** Entry to the right visually */
110 SmCaretPosGraphEntry
* Right
;
111 void SetRight(SmCaretPosGraphEntry
* right
){
114 void SetLeft(SmCaretPosGraphEntry
* left
){
119 /** Define SmCaretPosGraph to be less than one page 4096 */
120 #define SmCaretPosGraphSize 255
122 class SmCaretPosGraph
;
124 /** Iterator for SmCaretPosGraph */
125 class SmCaretPosGraphIterator
{
127 SmCaretPosGraphIterator(SmCaretPosGraph
* graph
){
132 /** Get the next entry, NULL if none */
133 SmCaretPosGraphEntry
* Next();
134 /** Get the current entry, NULL if none */
135 SmCaretPosGraphEntry
* Current(){
138 /** Get the current entry, NULL if none */
139 SmCaretPosGraphEntry
* operator->(){
143 /** Next entry to return */
146 SmCaretPosGraph
* pGraph
;
148 SmCaretPosGraphEntry
* pEntry
;
152 /** A graph over all caret positions
153 * @remarks Graphs can only grow, entries cannot be removed!
155 class SmCaretPosGraph
{
162 /** Add a caret position
163 * @remarks If Left and/or Right are set NULL, they will point back to the entry.
165 SmCaretPosGraphEntry
* Add(SmCaretPosGraphEntry entry
);
166 /** Add a caret position
167 * @remarks If left and/or right are set NULL, they will point back to the entry.
169 SmCaretPosGraphEntry
* Add(SmCaretPos pos
,
170 SmCaretPosGraphEntry
* left
= NULL
,
171 SmCaretPosGraphEntry
* right
= NULL
){
172 SAL_WARN_IF( pos
.Index
< 0, "starmath", "Index shouldn't be -1!" );
173 return Add(SmCaretPosGraphEntry(pos
, left
, right
));
175 /** Get an iterator for this graph */
176 SmCaretPosGraphIterator
GetIterator(){
177 return SmCaretPosGraphIterator(this);
179 friend class SmCaretPosGraphIterator
;
181 /** Next graph, to be used when this graph is full */
182 SmCaretPosGraph
* pNext
;
183 /** Next free entry in graph */
185 /** Entries in this graph segment */
186 SmCaretPosGraphEntry Graph
[SmCaretPosGraphSize
];
189 /** \page visual_formula_editing Visual Formula Editing
190 * A visual formula editor allows users to easily edit formulas without having to learn and
191 * use complicated commands. A visual formula editor is a WYSIWYG editor. For OpenOffice Math
192 * this essentially means that you can click on the formula image, to get a caret, which you
193 * can move with arrow keys, and use to modify the formula by entering text, clicking buttons
194 * or using shortcuts.
196 * \subsection formula_trees Formula Trees
197 * A formula in OpenOffice Math is a tree of nodes, take for instance the formula
198 * "A + {B cdot C} over D", it looks like this
199 * \f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$. The tree for this formula
205 * label= "Equation: \"A + {B cdot C} over D\"";
207 * n0 [label="SmTableNode (1)"];
208 * n0 -> n1 [label="0"];
209 * n1 [label="SmLineNode (2)"];
210 * n1 -> n2 [label="0"];
211 * n2 [label="SmExpressionNode (3)"];
212 * n2 -> n3 [label="0"];
213 * n3 [label="SmBinHorNode (4)"];
214 * n3 -> n4 [label="0"];
215 * n4 [label="SmTextNode: A (5)"];
216 * n3 -> n5 [label="1"];
217 * n5 [label="SmMathSymbolNode: (6)"];
218 * n3 -> n6 [label="2"];
219 * n6 [label="SmBinVerNode (7)"];
220 * n6 -> n7 [label="0"];
221 * n7 [label="SmExpressionNode (8)"];
222 * n7 -> n8 [label="0"];
223 * n8 [label="SmBinHorNode (9)"];
224 * n8 -> n9 [label="0"];
225 * n9 [label="SmTextNode: B (10)"];
226 * n8 -> n10 [label="1"];
227 * n10 [label="SmMathSymbolNode: ⋅ (11)"];
228 * n8 -> n11 [label="2"];
229 * n11 [label="SmTextNode: C (12)"];
230 * n6 -> n12 [label="1"];
231 * n12 [label="SmRectangleNode (13)"];
232 * n6 -> n13 [label="2"];
233 * n13 [label="SmTextNode: D (14)"];
237 * The vertices are nodes, their label says what kind of node and the number in parentheses is
238 * the identifier of the node (In practices a pointer is used instead of the id). The direction
239 * of the edges tells which node is parent and which is child. The label of the edges are the
240 * child node index number, given to SmNode::GetSubNode() of the parent to get the child node.
243 * \subsection visual_lines Visual Lines
245 * Inorder to do caret movement in visual lines, we need a definition of caret position and
246 * visual line. In a tree such as the above there are three visual lines. There's the outer most
247 * line, with entries such as
248 * \f$\mbox{A}\f$, \f$ + \f$ and \f$ \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$. Then there's
249 * the numerator line of the fraction it has entries \f$ \mbox{B} \f$, \f$ \cdot \f$ and \f$ \mbox{C} \f$.
250 * And last by not least there's the denominator line of the fraction it's only entry is \f$ \mbox{D} \f$.
252 * For visual editing it should be possible to place a caret on both sides of any line entry,
253 * consider a line entry a character or construction that in a line is treated as a character.
254 * Imagine the caret is placed to the right of the plus sign (id: 6), now if user presses
255 * backspace this should delete the plus sign (id: 6), and if the user presses delete this
256 * should delete the entire fraction (id: 7). This is because the caret is in the outer most
257 * line where the fraction is considered a line entry.
259 * However, inorder to prevent users from accidentally deleting large subtrees, just because
260 * they logically placed there caret a in the wrong line, require that complex constructions
261 * such as a fraction is selected before it is deleted. Thus in this case it wouldn't be
262 * deleted, but only selected and then deleted if the user hit delete again. Anyway, this is
263 * slightly off topic for now.
265 * Important about visual lines is that they don't always have an SmExpressionNode as root
266 * and the entries in a visual line is all the nodes of a subtree ordered left to right that
267 * isn't either an SmExpressionNode, SmBinHorNode or SmUnHorNode.
270 * \subsection caret_positions Caret Positions
272 * A caret position in OpenOffice Math is representated by an instance of SmCaretPos.
273 * That is a caret position is a node and an index related to this node. For most nodes the
274 * index 0, means caret is in front of this node, the index 1 means caret is after this node.
275 * For SmTextNode the index is the caret position after the specified number of characters,
276 * imagine an SmTextNode with the number 1337. The index 3 in such SmTextNode would mean a
277 * caret placed right before 7, e.g. "133|7".
279 * For SmExpressionNode, SmBinHorNode and SmUnHorNode the only legal index is 0, which means
280 * in front of the node. Actually the index 0 may only because for the first caret position
281 * in a visual line. From the example above, consider the following subtree that constitutes
287 * label= "Subtree that constitutes a visual line";
289 * n7 [label="SmExpressionNode (8)"];
290 * n7 -> n8 [label="0"];
291 * n8 [label="SmBinHorNode (9)"];
292 * n8 -> n9 [label="0"];
293 * n9 [label="SmTextNode: B (10)"];
294 * n8 -> n10 [label="1"];
295 * n10 [label="SmMathSymbolNode: ⋅ (11)"];
296 * n8 -> n11 [label="2"];
297 * n11 [label="SmTextNode: C (12)"];
300 * Here the caret positions are:
303 * <TR><TD><B>Caret position:</B></TD><TD><B>Example:</B></TD>
305 * <TD>{id: 8, index: 0}</TD>
306 * <TD>\f$ \mid \mbox{C} \cdot \mbox{C} \f$</TD>
308 * <TD>{id: 10, index: 1}</TD>
309 * <TD>\f$ \mbox{C} \mid \cdot \mbox{C} \f$</TD>
311 * <TD>{id: 11, index: 1}</TD>
312 * <TD>\f$ \mbox{C} \cdot \mid \mbox{C} \f$</TD>
314 * <TD>{id: 12, index: 1}</TD>
315 * <TD>\f$ \mbox{C} \cdot \mbox{C} \mid \f$</TD>
319 * Where \f$ \mid \f$ is used to denote caret position.
321 * With these exceptions included in the definition the id and index: {id: 11, index: 0} does
322 * \b not constitute a caret position in the given context. Note the method
323 * SmCaretPos::IsValid() does not check if this invariant holds true, but code in SmCaret,
324 * SmSetSelectionVisitor and other places depends on this invariant to hold.
327 * \subsection caret_movement Caret Movement
329 * As the placement of caret positions depends very much on the context within which a node
330 * appears it is not trivial to find all caret positions and determine which follows which.
331 * In OpenOffice Math this is done by the SmCaretPosGraphBuildingVisitor. This visitor builds
332 * graph (an instnce of SmCaretPosGraph) over the caret positions. For details on how this
333 * graph is build, and how new methods should be implemented see SmCaretPosGraphBuildingVisitor.
335 * The result of the SmCaretPosGraphBuildingVisitor is a graph over the caret positions in a
336 * formula, representated by an instance of SmCaretPosGraph. Each entry (instances of SmCaretPosGraphEntry)
337 * has a pointer to the entry to the left and right of itself. This way we can easily find
338 * the caret position to a right or left of a given caret position. Note each caret position
339 * only appears once in this graph.
341 * When searching for a caret position after a left click on the formula this map is also used.
342 * We simply iterate over all entries, uses the SmCaretPos2LineVisitor to find a line for each
343 * caret position. Then the distance from the click to the line is computed and we choose the
344 * caret position closest to the click.
346 * For up and down movement, we also iterator over all caret positions and use SmCaretPos2LineVisitor
347 * to find a line for each caret position. Then we compute the distance from the current
348 * caret position to every other caret position and chooses the one closest that is either
349 * above or below the current caret position, depending on whether we're doing up or down movement.
351 * This result of this approach to caret movement is that we have logically predictable
352 * movement for left and right, whilst leftclick, up and down movement depends on the sizes
353 * and placement of all node and may be less logically predictable. This solution also means
354 * that we only have one complex visitor generating the graph, imagine the nightmare if we
355 * had a visitor for movement in each direction.
357 * Making up and down movement independent of node sizes and placement wouldn't necessarily
358 * be a good thing either. Consider the formula \f$ \frac{1+2+3+4+5}{6} \f$, if the caret is
359 * placed as displayed here: \f$ \frac{1+2+3+4+5}{6 \mid} \f$, up movement should move to right
360 * after "3": \f$ \frac{1+2+3|+4+5}{6} \f$. However, such a move depends on the sizes and placement
361 * of all nodes in the fraction.
364 * \subsubsection caretpos_graph_example Example of Caret Position Graph
366 * If we consider the formula
367 * \f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$ from \ref formula_trees.
368 * It has the following caret positions:
372 * <TD><B>Caret position:</B></TD>
373 * <TD><B>Example:</B></TD>
375 * <TD>{id: 3, index: 0}</TD>
376 * <TD>\f$ \mid\mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
378 * <TD>{id: 5, index: 1}</TD>
379 * <TD>\f$ \mbox{A}\mid + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
381 * <TD>{id: 6, index: 1}</TD>
382 * <TD>\f$ \mbox{A} + \mid \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
384 * <TD>{id: 8, index: 0}</TD>
385 * <TD>\f$ \mbox{A} + \frac{ \mid \mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
387 * <TD>{id: 10, index: 1}</TD>
388 * <TD>\f$ \mbox{A} + \frac{\mbox{B} \mid \cdot \mbox{C}}{\mbox{D}} \f$</TD>
390 * <TD>{id: 11, index: 1}</TD>
391 * <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mid \mbox{C}}{\mbox{D}} \f$</TD>
393 * <TD>{id: 12, index: 1}</TD>
394 * <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C} \mid}{\mbox{D}} \f$</TD>
396 * <TD>{id: 14, index: 0}</TD>
397 * <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mid \mbox{D}} \f$</TD>
399 * <TD>{id: 14, index: 1}</TD>
400 * <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D} \mid} \f$</TD>
402 * <TD>{id: 7, index: 1}</TD>
403 * <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \mid \f$</TD>
407 * Below is a directed graph over the caret postions and how you can move between them.
411 * label= "Caret Position Graph";
413 * p0 [label = "{id: 3, index: 0}"];
414 * p0 -> p1 [fontsize = 10.0, label = "right"];
415 * p1 [label = "{id: 5, index: 1}"];
416 * p1 -> p0 [fontsize = 10.0, label = "left"];
417 * p1 -> p2 [fontsize = 10.0, label = "right"];
418 * p2 [label = "{id: 6, index: 1}"];
419 * p2 -> p1 [fontsize = 10.0, label = "left"];
420 * p2 -> p3 [fontsize = 10.0, label = "right"];
421 * p3 [label = "{id: 8, index: 0}"];
422 * p3 -> p2 [fontsize = 10.0, label = "left"];
423 * p3 -> p4 [fontsize = 10.0, label = "right"];
424 * p4 [label = "{id: 10, index: 1}"];
425 * p4 -> p3 [fontsize = 10.0, label = "left"];
426 * p4 -> p5 [fontsize = 10.0, label = "right"];
427 * p5 [label = "{id: 11, index: 1}"];
428 * p5 -> p4 [fontsize = 10.0, label = "left"];
429 * p5 -> p6 [fontsize = 10.0, label = "right"];
430 * p6 [label = "{id: 12, index: 1}"];
431 * p6 -> p5 [fontsize = 10.0, label = "left"];
432 * p6 -> p9 [fontsize = 10.0, label = "right"];
433 * p7 [label = "{id: 14, index: 0}"];
434 * p7 -> p2 [fontsize = 10.0, label = "left"];
435 * p7 -> p8 [fontsize = 10.0, label = "right"];
436 * p8 [label = "{id: 14, index: 1}"];
437 * p8 -> p7 [fontsize = 10.0, label = "left"];
438 * p8 -> p9 [fontsize = 10.0, label = "right"];
439 * p9 [label = "{id: 7, index: 1}"];
440 * p9 -> p6 [fontsize = 10.0, label = "left"];
445 /* TODO: Write documentation about the following keywords:
449 * - Talk about how the visitor does this
451 * Modifying a Visual Line:
452 * - Find top most non-compo of the line (e.g. The subtree that constitutes a line)
453 * - Make the line into a list
454 * - Edit the list, add/remove/modify nodes
455 * - Parse the list back into a subtree
456 * - Insert the new subtree where the old was taken
459 #endif // INCLUDED_STARMATH_INC_CARET_HXX
461 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */