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