cursor.hxx 14.9 KB
Newer Older
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 6 7
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 */
9 10
#ifndef INCLUDED_STARMATH_INC_CURSOR_HXX
#define INCLUDED_STARMATH_INC_CURSOR_HXX
11 12 13 14

#include "node.hxx"
#include "caret.hxx"

15
#include <cassert>
16
#include <list>
17
#include <memory>
18

19
/** Factor to multiple the squared horizontal distance with
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
 * Used for Up and Down movement.
 */
#define HORIZONTICAL_DISTANCE_FACTOR        10

/** Enum of direction for movement */
enum SmMovementDirection{
    MoveUp,
    MoveDown,
    MoveLeft,
    MoveRight
};

/** Enum of elements that can inserted into a formula */
enum SmFormulaElement{
    BlankElement,
    FactorialElement,
    PlusElement,
    MinusElement,
    CDotElement,
    EqualElement,
    LessThanElement,
41 42
    GreaterThanElement,
    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 56 57
};

/** A list of nodes */
typedef std::list<SmNode*> SmNodeList;

58 59
typedef std::list<std::unique_ptr<SmNode>> SmClipboard;

60 61 62 63 64 65
class SmDocShell;

/** Formula cursor
 *
 * This class is used to represent a cursor in a formula, which can be used to manipulate
 * an formula programmatically.
Andrea Gelmini's avatar
Andrea Gelmini committed
66
 * @remarks This class is a very intimate friend of SmDocShell.
67 68 69
 */
class SmCursor{
public:
70
    SmCursor(SmNode* tree, SmDocShell* pShell)
71 72 73 74 75 76
        : mpAnchor(nullptr)
        , mpPosition(nullptr)
        , mpTree(tree)
        , mpDocShell(pShell)
        , mnEditSections(0)
        , mbIsEnabledSetModifiedSmDocShell(false)
77
    {
78 79 80 81 82
        //Build graph
        BuildGraph();
    }

    /** Get position */
83
    const SmCaretPos& GetPosition() const { return mpPosition->CaretPos; }
84 85

    /** True, if the cursor has a selection */
86
    bool HasSelection() { return mpAnchor != mpPosition; }
87 88 89 90 91

    /** Move the position of this cursor */
    void Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor = true);

    /** Move to the caret position closet to a given point */
92
    void MoveTo(OutputDevice* pDev, const Point& pos, bool bMoveAnchor);
93 94 95 96

    /** Delete the current selection or do nothing */
    void Delete();

97 98 99 100 101 102
    /** Delete selection, previous element or merge lines
     *
     * This method implements the behaviour of backspace.
     */
    void DeletePrev(OutputDevice* pDev);

103
    /** Insert text at the current position */
104
    void InsertText(const OUString& aString);
105 106 107 108 109 110 111 112 113

    /** Insert an element into the formula */
    void InsertElement(SmFormulaElement element);

    /** Insert command text translated into line entries at position
     *
     * Note: This method uses the parser to translate a command text into a
     * tree, then it copies line entries from this tree into the current tree.
     * Will not work for commands such as newline or ##, if position is in a matrix.
Andrea Gelmini's avatar
Andrea Gelmini committed
114
     * This will work for stuff like "A intersection B". But stuff spanning multiple lines
115 116
     * or dependent on the context which position is placed in will not work!
     */
117
    void InsertCommandText(const OUString& aCommandText);
118 119 120 121

    /** Insert a special node created from aString
     *
     * Used for handling insert request from the "catalog" dialog.
Julien Nabet's avatar
Julien Nabet committed
122
     * The provided string should be formatted as the desired command: %phi
123 124 125 126 127
     * Note: this method ONLY supports commands defined in Math.xcu
     *
     * For more complex expressions use InsertCommandText, this method doesn't
     * use SmParser, this means that it's faster, but not as strong.
     */
128
    void InsertSpecial(const OUString& aString);
129 130 131 132

    /** Create sub-/super script
     *
     * If there's a selection, it will be move into the appropriate sub-/super scription
Takeshi Abe's avatar
Takeshi Abe committed
133
     * of the node in front of it. If there's no node in front of position (or the selection),
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
     * a sub-/super scription of a new SmPlaceNode will be made.
     *
     * If there's is an existing subscription of the node, the caret will be moved into it,
     * and any selection will replace it.
     */
    void InsertSubSup(SmSubSup eSubSup);

    /** Insert a new row or newline
     *
     * Inserts a new row if position is in an matrix or stack command.
     * Otherwise a newline is inserted if we're in a toplevel line.
     *
     * @returns True, if a new row/line could be inserted.
     *
     * @remarks If the caret is placed in a subline of a command that doesn't support
     *          this operator the method returns FALSE, and doesn't do anything.
     */
151
    bool InsertRow();
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172

    /** Insert a fraction, use selection as numerator */
    void InsertFraction();

    /** Create brackets around current selection, or new SmPlaceNode */
    void InsertBrackets(SmBracketType eBracketType);

    /** Copy the current selection */
    void Copy();
    /** Cut the current selection */
    void Cut(){
        Copy();
        Delete();
    }
    /** Paste the clipboard */
    void Paste();

    /** Returns true if more than one node is selected
     *
     * This method is used for implementing backspace and delete.
     * If one of these causes a complex selection, e.g. a node with
Andrea Gelmini's avatar
Andrea Gelmini committed
173
     * subnodes or similar, this should not be deleted immediately.
174 175 176 177 178 179 180 181 182 183 184
     */
    bool HasComplexSelection();

    /** Finds the topmost node in a visual line
     *
     * If MoveUpIfSelected is true, this will move up to the parent line
     * if the parent of the current line is selected.
     */
    static SmNode* FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected = false);

    /** 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 191 192
private:
    friend class SmDocShell;

193 194
    SmCaretPosGraphEntry    *mpAnchor,
                            *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 205 206 207 208 209 210

    /** Returns a node that is selected, if any could be found */
    SmNode* FindSelectedNode(SmNode* pNode);

    /** Is this one of the nodes used to compose a line
     *
     * These are SmExpression, SmBinHorNode, SmUnHorNode etc.
     */
211
    static bool IsLineCompositionNode(SmNode const * pNode);
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227

    /** Count number of selected nodes, excluding line composition nodes
     *
     * Note this function doesn't count line composition nodes and it
     * does count all subnodes as well as the owner nodes.
     *
     * Used by SmCursor::HasComplexSelection()
     */
    int CountSelectedNodes(SmNode* pNode);

    /** Convert a visual line to a list
     *
     * Note this method will delete all the nodes that will no longer be needed.
     * that includes pLine!
     * This method also deletes SmErrorNode's as they're just meta info in the line.
     */
228
    static void LineToList(SmStructureNode* pLine, SmNodeList& rList);
229

230 231
    /** Auxiliary function for calling LineToList on a node
     *
Tor Lillqvist's avatar
Tor Lillqvist committed
232
     * This method sets pNode = NULL and remove it from its parent.
233 234
     * (Assuming it has a parent, and is a child of it).
     */
235
    static void NodeToList(SmNode*& rpNode, SmNodeList& rList){
236 237 238 239
        //Remove from parent and NULL rpNode
        SmNode* pNode = rpNode;
        if(rpNode && rpNode->GetParent()){    //Don't remove this, correctness relies on it
            int index = rpNode->GetParent()->IndexOfSubNode(rpNode);
240 241
            assert(index >= 0);
            rpNode->GetParent()->SetSubNode(index, nullptr);
242
        }
243
        rpNode = nullptr;
244
        //Create line from node
245 246 247 248
        if(pNode && IsLineCompositionNode(pNode)){
            LineToList(static_cast<SmStructureNode*>(pNode), rList);
            return;
        }
249
        if(pNode)
250
            rList.push_front(pNode);
251 252
    }

253
    /** Clone a visual line to a clipboard
254
     *
255 256
     * ... but the selected part only.
     * Doesn't clone SmErrorNodes, which are ignored as they are context dependent metadata.
257
     */
258
    static void CloneLineToClipboard(SmStructureNode* pLine, SmClipboard* pClipboard);
259 260 261 262 263

    /** Build pGraph over caret positions */
    void BuildGraph();

    /** Insert new nodes in the tree after position */
264
    void InsertNodes(std::unique_ptr<SmNodeList> pNewNodes);
265 266 267 268 269

    /** tries to set position to a specific SmCaretPos
     *
     * @returns false on failure to find the position in pGraph.
     */
270
    bool SetCaretPosition(SmCaretPos pos);
271 272 273 274

    /** Set selected on nodes of the tree */
    void AnnotateSelection();

275
    /** Clone list of nodes in a clipboard (creates a deep clone) */
276
    static std::unique_ptr<SmNodeList> CloneList(SmClipboard &rClipboard);
277

Takeshi Abe's avatar
Takeshi Abe committed
278
    /** Find an iterator pointing to the node in pLineList following rCaretPos
279
     *
Takeshi Abe's avatar
Takeshi Abe committed
280
     * If rCaretPos.pSelectedNode cannot be found it is assumed that it's in front of pLineList,
281 282 283 284 285 286 287
     * thus not an element in pLineList. In this case this method returns an iterator to the
     * first element in pLineList.
     *
     * If the current position is inside an SmTextNode, this node will be split in two, for this
     * reason you should beaware that iterators to elements in pLineList may be invalidated, and
     * that you should call PatchLineList() with this iterator if no action is taken.
     */
Takeshi Abe's avatar
Takeshi Abe committed
288 289
    static SmNodeList::iterator FindPositionInLineList(SmNodeList* pLineList,
                                                       const SmCaretPos& rCaretPos);
290 291 292 293

    /** Patch a line list after modification, merge SmTextNode, remove SmPlaceNode etc.
     *
     * @param pLineList The line list to patch
Andrea Gelmini's avatar
Andrea Gelmini committed
294
     * @param aIter     Iterator pointing to the element that needs to be patched with its previous.
295 296 297 298 299 300
     *
     * When the list is patched text nodes before and after aIter will be merged.
     * If there's an, in the context, inappropriate SmPlaceNode before or after aIter it will also be
     * removed.
     *
     * @returns A caret position equivalent to one selecting the node before aIter, the method returns
Takeshi Abe's avatar
Takeshi Abe committed
301
     *          an invalid SmCaretPos to indicate placement in front of the line.
302 303 304 305 306 307 308 309 310 311 312 313 314 315
     */
     static SmCaretPos PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter);

    /** Take selected nodes from a list
     *
     * Puts the selected nodes into pSelectedNodes, or if pSelectedNodes is NULL deletes
     * the selected nodes.
     * Note: If there's a selection inside an SmTextNode this node will be split, and it
     * will not be merged when the selection have been taken. Use PatchLineList on the
     * iterator returns to fix this.
     *
     * @returns An iterator pointing to the element following the selection taken.
     */
    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 322 323

    /** The number of times BeginEdit have been called
     * Used to allow nesting of BeginEdit() and EndEdit() sections
     */
324
    int mnEditSections;
325
    /** Holds data for BeginEdit() and EndEdit() */
326
    bool mbIsEnabledSetModifiedSmDocShell;
327 328 329 330 331 332 333
    /** Begin edit section where the tree will be modified */
    void BeginEdit();
    /** End edit section where the tree will be modified */
    void EndEdit();
    /** Finish editing
     *
     * Finishes editing by parsing pLineList and inserting back into pParent at nParentIndex.
Andrea Gelmini's avatar
Andrea Gelmini committed
334
     * This method also rebuilds the graph, annotates the selection, sets caret position and
335 336 337 338 339 340 341 342 343 344 345
     * Calls EndEdit.
     *
     * @remarks Please note that this method will delete pLineList, as the elements are taken.
     *
     * @param pLineList     List the constitutes the edited line.
     * @param pParent       Parent to which the line should be inserted.
     * @param nParentIndex  Index in parent where the line should be inserted.
     * @param PosAfterEdit  Caret position to look for after rebuilding graph.
     * @param pStartLine    Line to take first position in, if PosAfterEdit cannot be found,
     *                      leave it NULL for pLineList.
     */
346
    void FinishEdit(std::unique_ptr<SmNodeList> pLineList,
347 348 349
                    SmStructureNode* pParent,
                    int nParentIndex,
                    SmCaretPos PosAfterEdit,
350
                    SmNode* pStartLine = nullptr);
351 352 353 354 355 356
    /** Request the formula is repainted */
    void RequestRepaint();
};

/** Minimalistic recursive decent SmNodeList parser
 *
357
 * This parser is used to take a list of nodes that constitutes a line
358 359 360
 * and parse them to a tree of SmBinHorNode, SmUnHorNode and SmExpression.
 *
 * Please note, this will not handle all kinds of nodes, only nodes that
361
 * constitutes and entry in a line.
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
 *
 * Below is an EBNF representation of the grammar used for this parser:
 * \code
 * Expression   -> Relation*
 * Relation     -> Sum [(=|<|>|...) Sum]*
 * Sum          -> Product [(+|-) Product]*
 * Product      -> Factor [(*|/) Factor]*
 * Factor       -> [+|-|-+|...]* Factor | Postfix
 * Postfix      -> node [!]*
 * \endcode
 */
class SmNodeListParser{
public:
    /** Create an instance of SmNodeListParser */
    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 397 398 399
private:
    SmNodeList* pList;
    /** Get the current terminal */
    SmNode* Terminal(){
400
        if (!pList->empty())
401
            return pList->front();
402
        return nullptr;
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
    }
    /** Move to next terminal */
    SmNode* Next(){
        pList->pop_front();
        return Terminal();
    }
    /** Take the current terminal */
    SmNode* Take(){
        SmNode* pRetVal = Terminal();
        Next();
        return pRetVal;
    }
    SmNode* Expression();
    SmNode* Relation();
    SmNode* Sum();
    SmNode* Product();
    SmNode* Factor();
    SmNode* Postfix();
Noel Grandin's avatar
Noel Grandin committed
421
    static SmNode* Error();
422 423 424
};


425
#endif // INCLUDED_STARMATH_INC_CURSOR_HXX
426 427

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */