Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / starmath / source / cursor.cxx
blobe8623517bcd725e086293f41b63accbce2e11658
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
9 #include <cursor.hxx>
10 #include <visitors.hxx>
11 #include <document.hxx>
12 #include <view.hxx>
13 #include <comphelper/string.hxx>
14 #include <comphelper/lok.hxx>
15 #include <editeng/editeng.hxx>
16 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
17 #include <osl/diagnose.h>
18 #include <sfx2/lokhelper.hxx>
20 void SmCursor::Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor){
21 SmCaretPosGraphEntry* NewPos = nullptr;
22 switch(direction)
24 case MoveLeft:
25 if (mpPosition)
26 NewPos = mpPosition->Left;
27 OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!");
28 break;
29 case MoveRight:
30 if (mpPosition)
31 NewPos = mpPosition->Right;
32 OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!");
33 break;
34 case MoveUp:
35 //Implementation is practically identical to MoveDown, except for a single if statement
36 //so I've implemented them together and added a direction == MoveDown to the if statements.
37 case MoveDown:
38 if (mpPosition)
40 SmCaretLine from_line = SmCaretPos2LineVisitor(pDev, mpPosition->CaretPos).GetResult(),
41 best_line, //Best approximated line found so far
42 curr_line; //Current line
43 tools::Long dbp_sq = 0; //Distance squared to best line
44 for(const auto &pEntry : *mpGraph)
46 //Reject it if it's the current position
47 if(pEntry->CaretPos == mpPosition->CaretPos) continue;
48 //Compute caret line
49 curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult();
50 //Reject anything above if we're moving down
51 if(curr_line.GetTop() <= from_line.GetTop() && direction == MoveDown) continue;
52 //Reject anything below if we're moving up
53 if(curr_line.GetTop() + curr_line.GetHeight() >= from_line.GetTop() + from_line.GetHeight()
54 && direction == MoveUp) continue;
55 //Compare if it to what we have, if we have anything yet
56 if(NewPos){
57 //Compute distance to current line squared, multiplied with a horizontal factor
58 tools::Long dp_sq = curr_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
59 curr_line.SquaredDistanceY(from_line);
60 //Discard current line if best line is closer
61 if(dbp_sq <= dp_sq) continue;
63 //Take current line as the best
64 best_line = curr_line;
65 NewPos = pEntry.get();
66 //Update distance to best line
67 dbp_sq = best_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
68 best_line.SquaredDistanceY(from_line);
71 break;
72 default:
73 assert(false);
75 if(NewPos){
76 mpPosition = NewPos;
77 if(bMoveAnchor)
78 mpAnchor = NewPos;
79 RequestRepaint();
83 void SmCursor::MoveTo(OutputDevice* pDev, const Point& pos, bool bMoveAnchor)
85 SmCaretPosGraphEntry* NewPos = nullptr;
86 tools::Long dp_sq = 0, //Distance to current line squared
87 dbp_sq = 1; //Distance to best line squared
88 for(const auto &pEntry : *mpGraph)
90 OSL_ENSURE(pEntry->CaretPos.IsValid(), "The caret position graph may not have invalid positions!");
91 //Compute current line
92 SmCaretLine curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult();
93 //Compute squared distance to current line
94 dp_sq = curr_line.SquaredDistanceX(pos) + curr_line.SquaredDistanceY(pos);
95 //If we have a position compare to it
96 if(NewPos){
97 //If best line is closer, reject current line
98 if(dbp_sq <= dp_sq) continue;
100 //Accept current position as the best
101 NewPos = pEntry.get();
102 //Update distance to best line
103 dbp_sq = dp_sq;
105 if(NewPos){
106 mpPosition = NewPos;
107 if(bMoveAnchor)
108 mpAnchor = NewPos;
109 RequestRepaint();
113 void SmCursor::BuildGraph(){
114 //Save the current anchor and position
115 SmCaretPos _anchor, _position;
116 //Release mpGraph if allocated
117 if(mpGraph){
118 if(mpAnchor)
119 _anchor = mpAnchor->CaretPos;
120 if(mpPosition)
121 _position = mpPosition->CaretPos;
122 mpGraph.reset();
123 //Reset anchor and position as they point into an old graph
124 mpAnchor = nullptr;
125 mpPosition = nullptr;
128 //Build the new graph
129 mpGraph.reset(SmCaretPosGraphBuildingVisitor(mpTree).takeGraph());
131 //Restore anchor and position pointers
132 if(_anchor.IsValid() || _position.IsValid()){
133 for(const auto &pEntry : *mpGraph)
135 if(_anchor == pEntry->CaretPos)
136 mpAnchor = pEntry.get();
137 if(_position == pEntry->CaretPos)
138 mpPosition = pEntry.get();
141 //Set position and anchor to first caret position
142 auto it = mpGraph->begin();
143 assert(it != mpGraph->end());
144 if(!mpPosition)
145 mpPosition = it->get();
146 if(!mpAnchor)
147 mpAnchor = mpPosition;
149 assert(mpPosition);
150 assert(mpAnchor);
151 OSL_ENSURE(mpPosition->CaretPos.IsValid(), "Position must be valid");
152 OSL_ENSURE(mpAnchor->CaretPos.IsValid(), "Anchor must be valid");
155 bool SmCursor::SetCaretPosition(SmCaretPos pos){
156 for(const auto &pEntry : *mpGraph)
158 if(pEntry->CaretPos == pos)
160 mpPosition = pEntry.get();
161 mpAnchor = pEntry.get();
162 return true;
165 return false;
168 void SmCursor::AnnotateSelection() const {
169 //TODO: Manage a state, reset it upon modification and optimize this call
170 SmSetSelectionVisitor(mpAnchor->CaretPos, mpPosition->CaretPos, mpTree);
173 void SmCursor::Draw(OutputDevice& pDev, Point Offset, bool isCaretVisible){
174 SmCaretDrawingVisitor(pDev, GetPosition(), Offset, isCaretVisible);
177 tools::Rectangle SmCursor::GetCaretRectangle(OutputDevice& rOutDev) const
179 return SmCaretRectanglesVisitor(rOutDev, GetPosition()).getCaret();
182 tools::Rectangle SmCursor::GetSelectionRectangle(OutputDevice& rOutDev) const
184 AnnotateSelection();
185 return SmSelectionRectanglesVisitor(rOutDev, mpTree).GetSelection();
188 void SmCursor::DeletePrev(OutputDevice* pDev){
189 //Delete only a selection if there's a selection
190 if(HasSelection()){
191 Delete();
192 return;
195 SmNode* pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
196 SmStructureNode* pLineParent = pLine->GetParent();
197 int nLineOffsetIdx = pLineParent->IndexOfSubNode(pLine);
198 assert(nLineOffsetIdx >= 0);
200 //If we're in front of a node who's parent is a TABLE
201 if (pLineParent->GetType() == SmNodeType::Table && mpPosition->CaretPos.nIndex == 0 && nLineOffsetIdx > 0)
203 size_t nLineOffset = nLineOffsetIdx;
204 //Now we can merge with nLineOffset - 1
205 BeginEdit();
206 //Line to merge things into, so we can delete pLine
207 SmNode* pMergeLine = pLineParent->GetSubNode(nLineOffset-1);
208 OSL_ENSURE(pMergeLine, "pMergeLine cannot be NULL!");
209 SmCaretPos PosAfterDelete;
210 //Convert first line to list
211 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
212 NodeToList(pMergeLine, *pLineList);
213 if(!pLineList->empty()){
214 //Find iterator to patch
215 SmNodeList::iterator patchPoint = pLineList->end();
216 --patchPoint;
217 //Convert second line to list
218 NodeToList(pLine, *pLineList);
219 //Patch the line list
220 ++patchPoint;
221 PosAfterDelete = PatchLineList(pLineList.get(), patchPoint);
222 //Parse the line
223 pLine = SmNodeListParser().Parse(pLineList.get());
225 pLineList.reset();
226 pLineParent->SetSubNode(nLineOffset-1, pLine);
227 //Delete the removed line slot
228 SmNodeArray lines(pLineParent->GetNumSubNodes()-1);
229 for (size_t i = 0; i < pLineParent->GetNumSubNodes(); ++i)
231 if(i < nLineOffset)
232 lines[i] = pLineParent->GetSubNode(i);
233 else if(i > nLineOffset)
234 lines[i-1] = pLineParent->GetSubNode(i);
236 pLineParent->SetSubNodes(std::move(lines));
237 //Rebuild graph
238 mpAnchor = nullptr;
239 mpPosition = nullptr;
240 BuildGraph();
241 AnnotateSelection();
242 //Set caret position
243 if(!SetCaretPosition(PosAfterDelete))
244 SetCaretPosition(SmCaretPos(pLine, 0));
245 //Finish editing
246 EndEdit();
248 //TODO: If we're in an empty (sub/super/*) script
249 /*}else if(pLineParent->GetType() == SmNodeType::SubSup &&
250 nLineOffset != 0 &&
251 pLine->GetType() == SmNodeType::Expression &&
252 pLine->GetNumSubNodes() == 0){
253 //There's a (sub/super) script we can delete
254 //Consider selecting the entire script if GetNumSubNodes() != 0 or pLine->GetType() != SmNodeType::Expression
255 //TODO: Handle case where we delete a limit
258 //Else move select, and delete if not complex
259 }else{
260 Move(pDev, MoveLeft, false);
261 if(!HasComplexSelection())
262 Delete();
266 void SmCursor::Delete(){
267 //Return if we don't have a selection to delete
268 if(!HasSelection())
269 return;
271 //Enter edit section
272 BeginEdit();
274 //Set selected on nodes
275 AnnotateSelection();
277 //Find an arbitrary selected node
278 SmNode* pSNode = FindSelectedNode(mpTree);
279 assert(pSNode);
281 //Find the topmost node of the line that holds the selection
282 SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
283 OSL_ENSURE(pLine != mpTree, "Shouldn't be able to select the entire tree");
285 //Get the parent of the line
286 SmStructureNode* pLineParent = pLine->GetParent();
287 //Find line offset in parent
288 int nLineOffset = pLineParent->IndexOfSubNode(pLine);
289 assert(nLineOffset >= 0);
291 //Position after delete
292 SmCaretPos PosAfterDelete;
294 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
295 NodeToList(pLine, *pLineList);
297 //Take the selected nodes and delete them...
298 SmNodeList::iterator patchIt = TakeSelectedNodesFromList(pLineList.get());
300 //Get the position to set after delete
301 PosAfterDelete = PatchLineList(pLineList.get(), patchIt);
303 //Finish editing
304 FinishEdit(std::move(pLineList), pLineParent, nLineOffset, PosAfterDelete);
307 void SmCursor::InsertNodes(std::unique_ptr<SmNodeList> pNewNodes){
308 if(pNewNodes->empty()){
309 return;
312 //Begin edit section
313 BeginEdit();
315 //Get the current position
316 const SmCaretPos pos = mpPosition->CaretPos;
318 //Find top most of line that holds position
319 SmNode* pLine = FindTopMostNodeInLine(pos.pSelectedNode);
320 const bool bSelectedIsTopMost = pLine == pos.pSelectedNode;
322 //Find line parent and line index in parent
323 SmStructureNode* pLineParent = pLine->GetParent();
324 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
325 assert(nParentIndex >= 0);
327 //Convert line to list
328 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
329 NodeToList(pLine, *pLineList); // deletes pLine, potentially deleting pos.pSelectedNode
331 //Find iterator for place to insert nodes
332 SmNodeList::iterator it = bSelectedIsTopMost ? pLineList->begin()
333 : FindPositionInLineList(pLineList.get(), pos);
335 //Insert all new nodes
336 SmNodeList::iterator newIt,
337 patchIt = it, // (pointless default value, fixes compiler warnings)
338 insIt;
339 for(newIt = pNewNodes->begin(); newIt != pNewNodes->end(); ++newIt){
340 insIt = pLineList->insert(it, *newIt);
341 if(newIt == pNewNodes->begin())
342 patchIt = insIt;
344 //Patch the places we've changed stuff
345 PatchLineList(pLineList.get(), patchIt);
346 SmCaretPos PosAfterInsert = PatchLineList(pLineList.get(), it);
347 //Release list, we've taken the nodes
348 pNewNodes.reset();
350 //Finish editing
351 FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert);
354 SmNodeList::iterator SmCursor::FindPositionInLineList(SmNodeList* pLineList,
355 const SmCaretPos& rCaretPos)
357 //Find iterator for position
358 SmNodeList::iterator it = std::find(pLineList->begin(), pLineList->end(), rCaretPos.pSelectedNode);
359 if (it != pLineList->end())
361 if((*it)->GetType() == SmNodeType::Text)
363 //Split textnode if needed
364 if(rCaretPos.nIndex > 0)
366 SmTextNode* pText = static_cast<SmTextNode*>(rCaretPos.pSelectedNode);
367 if (rCaretPos.nIndex == pText->GetText().getLength())
368 return ++it;
369 OUString str1 = pText->GetText().copy(0, rCaretPos.nIndex);
370 OUString str2 = pText->GetText().copy(rCaretPos.nIndex);
371 pText->ChangeText(str1);
372 ++it;
373 //Insert str2 as new text node
374 assert(!str2.isEmpty());
375 SmTextNode* pNewText = new SmTextNode(pText->GetToken(), pText->GetFontDesc());
376 pNewText->ChangeText(str2);
377 it = pLineList->insert(it, pNewText);
379 }else
380 ++it;
381 //it now pointer to the node following pos, so pLineList->insert(it, ...) will insert correctly
382 return it;
384 //If we didn't find pSelectedNode, it must be because the caret is in front of the line
385 return pLineList->begin();
388 SmCaretPos SmCursor::PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter) {
389 //The nodes we should consider merging
390 SmNode *prev = nullptr,
391 *next = nullptr;
392 if(aIter != pLineList->end())
393 next = *aIter;
394 if(aIter != pLineList->begin()) {
395 --aIter;
396 prev = *aIter;
397 ++aIter;
400 //Check if there's textnodes to merge
401 if( prev &&
402 next &&
403 prev->GetType() == SmNodeType::Text &&
404 next->GetType() == SmNodeType::Text &&
405 ( prev->GetToken().eType != TNUMBER ||
406 next->GetToken().eType == TNUMBER) ){
407 SmTextNode *pText = static_cast<SmTextNode*>(prev),
408 *pOldN = static_cast<SmTextNode*>(next);
409 SmCaretPos retval(pText, pText->GetText().getLength());
410 OUString newText = pText->GetText() + pOldN->GetText();
411 pText->ChangeText(newText);
412 delete pOldN;
413 pLineList->erase(aIter);
414 return retval;
417 //Check if there's a SmPlaceNode to remove:
418 if(prev && next && prev->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(next->GetToken())){
419 --aIter;
420 aIter = pLineList->erase(aIter);
421 delete prev;
422 //Return caret pos in front of aIter
423 if(aIter != pLineList->begin())
424 --aIter; //Thus find node before aIter
425 if(aIter == pLineList->begin())
426 return SmCaretPos();
427 return SmCaretPos::GetPosAfter(*aIter);
429 if(prev && next && next->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(prev->GetToken())){
430 aIter = pLineList->erase(aIter);
431 delete next;
432 return SmCaretPos::GetPosAfter(prev);
435 //If we didn't do anything return
436 if(!prev) //return an invalid to indicate we're in front of line
437 return SmCaretPos();
438 return SmCaretPos::GetPosAfter(prev);
441 SmNodeList::iterator SmCursor::TakeSelectedNodesFromList(SmNodeList *pLineList,
442 SmNodeList *pSelectedNodes) {
443 SmNodeList::iterator retval;
444 SmNodeList::iterator it = pLineList->begin();
445 while(it != pLineList->end()){
446 if((*it)->IsSelected()){
447 //Split text nodes
448 if((*it)->GetType() == SmNodeType::Text) {
449 SmTextNode* pText = static_cast<SmTextNode*>(*it);
450 OUString aText = pText->GetText();
451 //Start and lengths of the segments, 2 is the selected segment
452 int start2 = pText->GetSelectionStart(),
453 start3 = pText->GetSelectionEnd(),
454 len1 = start2 - 0,
455 len2 = start3 - start2,
456 len3 = aText.getLength() - start3;
457 SmToken aToken = pText->GetToken();
458 sal_uInt16 eFontDesc = pText->GetFontDesc();
459 //If we need make segment 1
460 if(len1 > 0) {
461 OUString str = aText.copy(0, len1);
462 pText->ChangeText(str);
463 ++it;
464 } else {//Remove it if not needed
465 it = pLineList->erase(it);
466 delete pText;
468 //Set retval to be right after the selection
469 retval = it;
470 //if we need make segment 3
471 if(len3 > 0) {
472 OUString str = aText.copy(start3, len3);
473 SmTextNode* pSeg3 = new SmTextNode(aToken, eFontDesc);
474 pSeg3->ChangeText(str);
475 retval = pLineList->insert(it, pSeg3);
477 //If we need to save the selected text
478 if(pSelectedNodes && len2 > 0) {
479 OUString str = aText.copy(start2, len2);
480 SmTextNode* pSeg2 = new SmTextNode(aToken, eFontDesc);
481 pSeg2->ChangeText(str);
482 pSelectedNodes->push_back(pSeg2);
484 } else { //if it's not textnode
485 SmNode* pNode = *it;
486 retval = it = pLineList->erase(it);
487 if(pSelectedNodes)
488 pSelectedNodes->push_back(pNode);
489 else
490 delete pNode;
492 } else
493 ++it;
495 return retval;
498 void SmCursor::InsertSubSup(SmSubSup eSubSup) {
499 AnnotateSelection();
501 //Find line
502 SmNode *pLine;
503 if(HasSelection()) {
504 SmNode *pSNode = FindSelectedNode(mpTree);
505 assert(pSNode);
506 pLine = FindTopMostNodeInLine(pSNode, true);
507 } else
508 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
510 //Find Parent and offset in parent
511 SmStructureNode *pLineParent = pLine->GetParent();
512 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
513 assert(nParentIndex >= 0);
515 //TODO: Consider handling special cases where parent is an SmOperNode,
516 // Maybe this method should be able to add limits to an SmOperNode...
518 //We begin modifying the tree here
519 BeginEdit();
521 //Convert line to list
522 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
523 NodeToList(pLine, *pLineList);
525 //Take the selection, and/or find iterator for current position
526 std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList);
527 SmNodeList::iterator it;
528 if(HasSelection())
529 it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get());
530 else
531 it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos);
533 //Find node that this should be applied to
534 SmNode* pSubject;
535 bool bPatchLine = !pSelectedNodesList->empty(); //If the line should be patched later
536 if(it != pLineList->begin()) {
537 --it;
538 pSubject = *it;
539 ++it;
540 } else {
541 //Create a new place node
542 pSubject = new SmPlaceNode();
543 pSubject->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
544 it = pLineList->insert(it, pSubject);
545 ++it;
546 bPatchLine = true; //We've modified the line it should be patched later.
549 //Wrap the subject in a SmSubSupNode
550 SmSubSupNode* pSubSup;
551 if(pSubject->GetType() != SmNodeType::SubSup){
552 SmToken token;
553 token.nGroup = TG::Power;
554 pSubSup = new SmSubSupNode(token);
555 pSubSup->SetBody(pSubject);
556 *(--it) = pSubSup;
557 ++it;
558 }else
559 pSubSup = static_cast<SmSubSupNode*>(pSubject);
560 //pSubject shouldn't be referenced anymore, pSubSup is the SmSubSupNode in pLineList we wish to edit.
561 //and it pointer to the element following pSubSup in pLineList.
562 pSubject = nullptr;
564 //Patch the line if we noted that was needed previously
565 if(bPatchLine)
566 PatchLineList(pLineList.get(), it);
568 //Convert existing, if any, sub-/superscript line to list
569 SmNode *pScriptLine = pSubSup->GetSubSup(eSubSup);
570 std::unique_ptr<SmNodeList> pScriptLineList(new SmNodeList);
571 NodeToList(pScriptLine, *pScriptLineList);
573 //Add selection to pScriptLineList
574 unsigned int nOldSize = pScriptLineList->size();
575 pScriptLineList->insert(pScriptLineList->end(), pSelectedNodesList->begin(), pSelectedNodesList->end());
576 pSelectedNodesList.reset();
578 //Patch pScriptLineList if needed
579 if(0 < nOldSize && nOldSize < pScriptLineList->size()) {
580 SmNodeList::iterator iPatchPoint = pScriptLineList->begin();
581 std::advance(iPatchPoint, nOldSize);
582 PatchLineList(pScriptLineList.get(), iPatchPoint);
585 //Find caret pos, that should be used after sub-/superscription.
586 SmCaretPos PosAfterScript; //Leave invalid for first position
587 if (!pScriptLineList->empty())
588 PosAfterScript = SmCaretPos::GetPosAfter(pScriptLineList->back());
590 //Parse pScriptLineList
591 pScriptLine = SmNodeListParser().Parse(pScriptLineList.get());
592 pScriptLineList.reset();
594 //Insert pScriptLine back into the tree
595 pSubSup->SetSubSup(eSubSup, pScriptLine);
597 //Finish editing
598 FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterScript, pScriptLine);
601 void SmCursor::InsertBrackets(SmBracketType eBracketType) {
602 BeginEdit();
604 AnnotateSelection();
606 //Find line
607 SmNode *pLine;
608 if(HasSelection()) {
609 SmNode *pSNode = FindSelectedNode(mpTree);
610 assert(pSNode);
611 pLine = FindTopMostNodeInLine(pSNode, true);
612 } else
613 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
615 //Find parent and offset in parent
616 SmStructureNode *pLineParent = pLine->GetParent();
617 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
618 assert(nParentIndex >= 0);
620 //Convert line to list
621 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
622 NodeToList(pLine, *pLineList);
624 //Take the selection, and/or find iterator for current position
625 std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList);
626 SmNodeList::iterator it;
627 if(HasSelection())
628 it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get());
629 else
630 it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos);
632 //If there's no selected nodes, create a place node
633 std::unique_ptr<SmNode> pBodyNode;
634 SmCaretPos PosAfterInsert;
635 if(pSelectedNodesList->empty()) {
636 pBodyNode.reset(new SmPlaceNode());
637 PosAfterInsert = SmCaretPos(pBodyNode.get(), 1);
638 } else
639 pBodyNode.reset(SmNodeListParser().Parse(pSelectedNodesList.get()));
641 pSelectedNodesList.reset();
643 //Create SmBraceNode
644 SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5);
645 SmBraceNode *pBrace = new SmBraceNode(aTok);
646 pBrace->SetScaleMode(SmScaleMode::Height);
647 std::unique_ptr<SmNode> pLeft( CreateBracket(eBracketType, true) ),
648 pRight( CreateBracket(eBracketType, false) );
649 std::unique_ptr<SmBracebodyNode> pBody(new SmBracebodyNode(SmToken()));
650 pBody->SetSubNodes(std::move(pBodyNode), nullptr);
651 pBrace->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight));
652 pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
654 //Insert into line
655 pLineList->insert(it, pBrace);
656 //Patch line (I think this is good enough)
657 SmCaretPos aAfter = PatchLineList(pLineList.get(), it);
658 if( !PosAfterInsert.IsValid() )
659 PosAfterInsert = aAfter;
661 //Finish editing
662 FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert);
665 SmNode *SmCursor::CreateBracket(SmBracketType eBracketType, bool bIsLeft) {
666 SmToken aTok;
667 if(bIsLeft){
668 switch(eBracketType){
669 case SmBracketType::Round:
670 aTok = SmToken(TLPARENT, MS_LPARENT, "(", TG::LBrace, 5);
671 break;
672 case SmBracketType::Square:
673 aTok = SmToken(TLBRACKET, MS_LBRACKET, "[", TG::LBrace, 5);
674 break;
675 case SmBracketType::Curly:
676 aTok = SmToken(TLBRACE, MS_LBRACE, "lbrace", TG::LBrace, 5);
677 break;
679 } else {
680 switch(eBracketType) {
681 case SmBracketType::Round:
682 aTok = SmToken(TRPARENT, MS_RPARENT, ")", TG::RBrace, 5);
683 break;
684 case SmBracketType::Square:
685 aTok = SmToken(TRBRACKET, MS_RBRACKET, "]", TG::RBrace, 5);
686 break;
687 case SmBracketType::Curly:
688 aTok = SmToken(TRBRACE, MS_RBRACE, "rbrace", TG::RBrace, 5);
689 break;
692 SmNode* pRetVal = new SmMathSymbolNode(aTok);
693 pRetVal->SetScaleMode(SmScaleMode::Height);
694 return pRetVal;
697 bool SmCursor::InsertRow() {
698 AnnotateSelection();
700 //Find line
701 SmNode *pLine;
702 if(HasSelection()) {
703 SmNode *pSNode = FindSelectedNode(mpTree);
704 assert(pSNode);
705 pLine = FindTopMostNodeInLine(pSNode, true);
706 } else
707 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
709 //Find parent and offset in parent
710 SmStructureNode *pLineParent = pLine->GetParent();
711 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
712 assert(nParentIndex >= 0);
714 //Discover the context of this command
715 SmTableNode *pTable = nullptr;
716 SmMatrixNode *pMatrix = nullptr;
717 int nTableIndex = nParentIndex;
718 if(pLineParent->GetType() == SmNodeType::Table)
719 pTable = static_cast<SmTableNode*>(pLineParent);
720 //If it's wrapped in a SmLineNode, we can still insert a newline
721 else if(pLineParent->GetType() == SmNodeType::Line &&
722 pLineParent->GetParent() &&
723 pLineParent->GetParent()->GetType() == SmNodeType::Table) {
724 //NOTE: This hack might give problems if we stop ignoring SmAlignNode
725 pTable = static_cast<SmTableNode*>(pLineParent->GetParent());
726 nTableIndex = pTable->IndexOfSubNode(pLineParent);
727 assert(nTableIndex >= 0);
729 if(pLineParent->GetType() == SmNodeType::Matrix)
730 pMatrix = static_cast<SmMatrixNode*>(pLineParent);
732 //If we're not in a context that supports InsertRow, return sal_False
733 if(!pTable && !pMatrix)
734 return false;
736 //Now we start editing
737 BeginEdit();
739 //Convert line to list
740 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
741 NodeToList(pLine, *pLineList);
743 //Find position in line
744 SmNodeList::iterator it;
745 if(HasSelection()) {
746 //Take the selected nodes and delete them...
747 it = TakeSelectedNodesFromList(pLineList.get());
748 } else
749 it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos);
751 //New caret position after inserting the newline/row in whatever context
752 SmCaretPos PosAfterInsert;
754 //If we're in the context of a table
755 if(pTable) {
756 std::unique_ptr<SmNodeList> pNewLineList(new SmNodeList);
757 //Move elements from pLineList to pNewLineList
758 SmNodeList& rLineList = *pLineList;
759 pNewLineList->splice(pNewLineList->begin(), rLineList, it, rLineList.end());
760 //Make sure it is valid again
761 it = pLineList->end();
762 if(it != pLineList->begin())
763 --it;
764 if(pNewLineList->empty())
765 pNewLineList->push_front(new SmPlaceNode());
766 //Parse new line
767 std::unique_ptr<SmNode> pNewLine(SmNodeListParser().Parse(pNewLineList.get()));
768 pNewLineList.reset();
769 //Wrap pNewLine in SmLineNode if needed
770 if(pLineParent->GetType() == SmNodeType::Line) {
771 std::unique_ptr<SmLineNode> pNewLineNode(new SmLineNode(SmToken(TNEWLINE, '\0', "newline")));
772 pNewLineNode->SetSubNodes(std::move(pNewLine), nullptr);
773 pNewLine = std::move(pNewLineNode);
775 //Get position
776 PosAfterInsert = SmCaretPos(pNewLine.get(), 0);
777 //Move other nodes if needed
778 for( int i = pTable->GetNumSubNodes(); i > nTableIndex + 1; i--)
779 pTable->SetSubNode(i, pTable->GetSubNode(i-1));
781 //Insert new line
782 pTable->SetSubNode(nTableIndex + 1, pNewLine.release());
784 //Check if we need to change token type:
785 if(pTable->GetNumSubNodes() > 2 && pTable->GetToken().eType == TBINOM) {
786 SmToken tok = pTable->GetToken();
787 tok.eType = TSTACK;
788 pTable->SetToken(tok);
791 //If we're in the context of a matrix
792 else {
793 //Find position after insert and patch the list
794 PosAfterInsert = PatchLineList(pLineList.get(), it);
795 //Move other children
796 sal_uInt16 rows = pMatrix->GetNumRows();
797 sal_uInt16 cols = pMatrix->GetNumCols();
798 int nRowStart = (nParentIndex - nParentIndex % cols) + cols;
799 for( int i = pMatrix->GetNumSubNodes() + cols - 1; i >= nRowStart + cols; i--)
800 pMatrix->SetSubNode(i, pMatrix->GetSubNode(i - cols));
801 for( int i = nRowStart; i < nRowStart + cols; i++) {
802 SmPlaceNode *pNewLine = new SmPlaceNode();
803 if(i == nParentIndex + cols)
804 PosAfterInsert = SmCaretPos(pNewLine, 0);
805 pMatrix->SetSubNode(i, pNewLine);
807 pMatrix->SetRowCol(rows + 1, cols);
810 //Finish editing
811 FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert);
812 //FinishEdit is actually used to handle situations where parent is an instance of
813 //SmSubSupNode. In this case parent should always be a table or matrix, however, for
814 //code reuse we just use FinishEdit() here too.
815 return true;
818 void SmCursor::InsertFraction() {
819 AnnotateSelection();
821 //Find line
822 SmNode *pLine;
823 if(HasSelection()) {
824 SmNode *pSNode = FindSelectedNode(mpTree);
825 assert(pSNode);
826 pLine = FindTopMostNodeInLine(pSNode, true);
827 } else
828 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
830 //Find Parent and offset in parent
831 SmStructureNode *pLineParent = pLine->GetParent();
832 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
833 assert(nParentIndex >= 0);
835 //We begin modifying the tree here
836 BeginEdit();
838 //Convert line to list
839 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
840 NodeToList(pLine, *pLineList);
842 //Take the selection, and/or find iterator for current position
843 std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList);
844 SmNodeList::iterator it;
845 if(HasSelection())
846 it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get());
847 else
848 it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos);
850 //Create pNum, and pDenom
851 bool bEmptyFraction = pSelectedNodesList->empty();
852 std::unique_ptr<SmNode> pNum( bEmptyFraction
853 ? new SmPlaceNode()
854 : SmNodeListParser().Parse(pSelectedNodesList.get()) );
855 std::unique_ptr<SmNode> pDenom(new SmPlaceNode());
856 pSelectedNodesList.reset();
858 //Create new fraction
859 SmBinVerNode *pFrac = new SmBinVerNode(SmToken(TOVER, '\0', "over", TG::Product, 0));
860 std::unique_ptr<SmNode> pRect(new SmRectangleNode(SmToken()));
861 pFrac->SetSubNodes(std::move(pNum), std::move(pRect), std::move(pDenom));
863 //Insert in pLineList
864 SmNodeList::iterator patchIt = pLineList->insert(it, pFrac);
865 PatchLineList(pLineList.get(), patchIt);
866 PatchLineList(pLineList.get(), it);
868 //Finish editing
869 SmNode *pSelectedNode = bEmptyFraction ? pFrac->GetSubNode(0) : pFrac->GetSubNode(2);
870 FinishEdit(std::move(pLineList), pLineParent, nParentIndex, SmCaretPos(pSelectedNode, 1));
873 void SmCursor::InsertText(const OUString& aString)
875 BeginEdit();
877 Delete();
879 SmToken token;
880 token.eType = TIDENT;
881 token.cMathChar = u"";
882 token.nGroup = TG::NONE;
883 token.nLevel = 5;
884 token.aText = aString;
886 SmTextNode* pText = new SmTextNode(token, FNT_VARIABLE);
887 pText->SetText(aString);
888 pText->AdjustFontDesc();
889 pText->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
891 std::unique_ptr<SmNodeList> pList(new SmNodeList);
892 pList->push_front(pText);
893 InsertNodes(std::move(pList));
895 EndEdit();
898 void SmCursor::InsertElement(SmFormulaElement element){
899 BeginEdit();
901 Delete();
903 //Create new node
904 SmNode* pNewNode = nullptr;
905 switch(element){
906 case BlankElement:
908 SmToken token;
909 token.eType = TBLANK;
910 token.nGroup = TG::Blank;
911 token.aText = "~";
912 SmBlankNode* pBlankNode = new SmBlankNode(token);
913 pBlankNode->IncreaseBy(token);
914 pNewNode = pBlankNode;
915 }break;
916 case FactorialElement:
918 SmToken token(TFACT, MS_FACT, "fact", TG::UnOper, 5);
919 pNewNode = new SmMathSymbolNode(token);
920 }break;
921 case PlusElement:
923 SmToken token;
924 token.eType = TPLUS;
925 token.setChar(MS_PLUS);
926 token.nGroup = TG::UnOper | TG::Sum;
927 token.nLevel = 5;
928 token.aText = "+";
929 pNewNode = new SmMathSymbolNode(token);
930 }break;
931 case MinusElement:
933 SmToken token;
934 token.eType = TMINUS;
935 token.setChar(MS_MINUS);
936 token.nGroup = TG::UnOper | TG::Sum;
937 token.nLevel = 5;
938 token.aText = "-";
939 pNewNode = new SmMathSymbolNode(token);
940 }break;
941 case CDotElement:
943 SmToken token;
944 token.eType = TCDOT;
945 token.setChar(MS_CDOT);
946 token.nGroup = TG::Product;
947 token.aText = "cdot";
948 pNewNode = new SmMathSymbolNode(token);
949 }break;
950 case EqualElement:
952 SmToken token;
953 token.eType = TASSIGN;
954 token.setChar(MS_ASSIGN);
955 token.nGroup = TG::Relation;
956 token.aText = "=";
957 pNewNode = new SmMathSymbolNode(token);
958 }break;
959 case LessThanElement:
961 SmToken token;
962 token.eType = TLT;
963 token.setChar(MS_LT);
964 token.nGroup = TG::Relation;
965 token.aText = "<";
966 pNewNode = new SmMathSymbolNode(token);
967 }break;
968 case GreaterThanElement:
970 SmToken token;
971 token.eType = TGT;
972 token.setChar(MS_GT);
973 token.nGroup = TG::Relation;
974 token.aText = ">";
975 pNewNode = new SmMathSymbolNode(token);
976 }break;
977 case PercentElement:
979 SmToken token;
980 token.eType = TTEXT;
981 token.setChar(MS_PERCENT);
982 token.nGroup = TG::NONE;
983 token.aText = "\"%\"";
984 pNewNode = new SmMathSymbolNode(token);
985 }break;
987 assert(pNewNode);
989 //Prepare the new node
990 pNewNode->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
992 //Insert new node
993 std::unique_ptr<SmNodeList> pList(new SmNodeList);
994 pList->push_front(pNewNode);
995 InsertNodes(std::move(pList));
997 EndEdit();
1000 void SmCursor::InsertSpecial(std::u16string_view _aString)
1002 BeginEdit();
1003 Delete();
1005 OUString aString( comphelper::string::strip(_aString, ' ') );
1007 //Create instance of special node
1008 SmToken token;
1009 token.eType = TSPECIAL;
1010 token.cMathChar = u"";
1011 token.nGroup = TG::NONE;
1012 token.nLevel = 5;
1013 token.aText = aString;
1014 SmSpecialNode* pSpecial = new SmSpecialNode(token);
1016 //Prepare the special node
1017 pSpecial->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
1019 //Insert the node
1020 std::unique_ptr<SmNodeList> pList(new SmNodeList);
1021 pList->push_front(pSpecial);
1022 InsertNodes(std::move(pList));
1024 EndEdit();
1027 void SmCursor::InsertCommandText(const OUString& aCommandText) {
1028 //Parse the sub expression
1029 auto xSubExpr = mpDocShell->GetParser()->ParseExpression(aCommandText);
1031 //Prepare the subtree
1032 xSubExpr->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
1034 //Convert subtree to list
1035 SmNode* pSubExpr = xSubExpr.release();
1036 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
1037 NodeToList(pSubExpr, *pLineList);
1039 BeginEdit();
1041 //Delete any selection
1042 Delete();
1044 //Insert it
1045 InsertNodes(std::move(pLineList));
1047 EndEdit();
1050 void SmCursor::Copy(){
1051 if(!HasSelection())
1052 return;
1054 AnnotateSelection();
1055 //Find selected node
1056 SmNode* pSNode = FindSelectedNode(mpTree);
1057 assert(pSNode);
1058 //Find visual line
1059 SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
1060 assert(pLine);
1062 //Clone selected nodes
1063 SmClipboard aClipboard;
1064 if(IsLineCompositionNode(pLine))
1065 CloneLineToClipboard(static_cast<SmStructureNode*>(pLine), &aClipboard);
1066 else{
1067 //Special care to only clone selected text
1068 if(pLine->GetType() == SmNodeType::Text) {
1069 SmTextNode *pText = static_cast<SmTextNode*>(pLine);
1070 std::unique_ptr<SmTextNode> pClone(new SmTextNode( pText->GetToken(), pText->GetFontDesc() ));
1071 int start = pText->GetSelectionStart(),
1072 length = pText->GetSelectionEnd() - pText->GetSelectionStart();
1073 pClone->ChangeText(pText->GetText().copy(start, length));
1074 pClone->SetScaleMode(pText->GetScaleMode());
1075 aClipboard.push_front(std::move(pClone));
1076 } else {
1077 SmCloningVisitor aCloneFactory;
1078 aClipboard.push_front(std::unique_ptr<SmNode>(aCloneFactory.Clone(pLine)));
1082 //Set clipboard
1083 if (!aClipboard.empty())
1084 maClipboard = std::move(aClipboard);
1087 void SmCursor::Paste() {
1088 BeginEdit();
1089 Delete();
1091 if (!maClipboard.empty())
1092 InsertNodes(CloneList(maClipboard));
1094 EndEdit();
1097 std::unique_ptr<SmNodeList> SmCursor::CloneList(SmClipboard &rClipboard){
1098 SmCloningVisitor aCloneFactory;
1099 std::unique_ptr<SmNodeList> pClones(new SmNodeList);
1101 for(auto &xNode : rClipboard){
1102 SmNode *pClone = aCloneFactory.Clone(xNode.get());
1103 pClones->push_back(pClone);
1106 return pClones;
1109 SmNode* SmCursor::FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected){
1110 assert(pSNode);
1111 //Move up parent until we find a node who's
1112 //parent is NULL or isn't selected and not a type of:
1113 // SmExpressionNode
1114 // SmLineNode
1115 // SmBinHorNode
1116 // SmUnHorNode
1117 // SmAlignNode
1118 // SmFontNode
1119 while(pSNode->GetParent() &&
1120 ((MoveUpIfSelected &&
1121 pSNode->GetParent()->IsSelected()) ||
1122 IsLineCompositionNode(pSNode->GetParent())))
1123 pSNode = pSNode->GetParent();
1124 //Now we have the selection line node
1125 return pSNode;
1128 SmNode* SmCursor::FindSelectedNode(SmNode* pNode){
1129 if(pNode->GetNumSubNodes() == 0)
1130 return nullptr;
1131 for(auto pChild : *static_cast<SmStructureNode*>(pNode))
1133 if(!pChild)
1134 continue;
1135 if(pChild->IsSelected())
1136 return pChild;
1137 SmNode* pRetVal = FindSelectedNode(pChild);
1138 if(pRetVal)
1139 return pRetVal;
1141 return nullptr;
1144 void SmCursor::LineToList(SmStructureNode* pLine, SmNodeList& list){
1145 for(auto pChild : *pLine)
1147 if (!pChild)
1148 continue;
1149 switch(pChild->GetType()){
1150 case SmNodeType::Line:
1151 case SmNodeType::UnHor:
1152 case SmNodeType::Expression:
1153 case SmNodeType::BinHor:
1154 case SmNodeType::Align:
1155 case SmNodeType::Font:
1156 LineToList(static_cast<SmStructureNode*>(pChild), list);
1157 break;
1158 case SmNodeType::Error:
1159 delete pChild;
1160 break;
1161 default:
1162 list.push_back(pChild);
1165 pLine->ClearSubNodes();
1166 delete pLine;
1169 void SmCursor::CloneLineToClipboard(SmStructureNode* pLine, SmClipboard* pClipboard){
1170 SmCloningVisitor aCloneFactory;
1171 for(auto pChild : *pLine)
1173 if (!pChild)
1174 continue;
1175 if( IsLineCompositionNode( pChild ) )
1176 CloneLineToClipboard( static_cast<SmStructureNode*>(pChild), pClipboard );
1177 else if( pChild->IsSelected() && pChild->GetType() != SmNodeType::Error ) {
1178 //Only clone selected text from SmTextNode
1179 if(pChild->GetType() == SmNodeType::Text) {
1180 SmTextNode *pText = static_cast<SmTextNode*>(pChild);
1181 std::unique_ptr<SmTextNode> pClone(new SmTextNode( pChild->GetToken(), pText->GetFontDesc() ));
1182 int start = pText->GetSelectionStart(),
1183 length = pText->GetSelectionEnd() - pText->GetSelectionStart();
1184 pClone->ChangeText(pText->GetText().copy(start, length));
1185 pClone->SetScaleMode(pText->GetScaleMode());
1186 pClipboard->push_back(std::move(pClone));
1187 } else
1188 pClipboard->push_back(std::unique_ptr<SmNode>(aCloneFactory.Clone(pChild)));
1193 bool SmCursor::IsLineCompositionNode(SmNode const * pNode){
1194 switch(pNode->GetType()){
1195 case SmNodeType::Line:
1196 case SmNodeType::UnHor:
1197 case SmNodeType::Expression:
1198 case SmNodeType::BinHor:
1199 case SmNodeType::Align:
1200 case SmNodeType::Font:
1201 return true;
1202 default:
1203 return false;
1207 int SmCursor::CountSelectedNodes(SmNode* pNode){
1208 if(pNode->GetNumSubNodes() == 0)
1209 return 0;
1210 int nCount = 0;
1211 for(auto pChild : *static_cast<SmStructureNode*>(pNode))
1213 if (!pChild)
1214 continue;
1215 if(pChild->IsSelected() && !IsLineCompositionNode(pChild))
1216 nCount++;
1217 nCount += CountSelectedNodes(pChild);
1219 return nCount;
1222 bool SmCursor::HasComplexSelection(){
1223 if(!HasSelection())
1224 return false;
1225 AnnotateSelection();
1227 return CountSelectedNodes(mpTree) > 1;
1230 void SmCursor::FinishEdit(std::unique_ptr<SmNodeList> pLineList,
1231 SmStructureNode* pParent,
1232 int nParentIndex,
1233 SmCaretPos PosAfterEdit,
1234 SmNode* pStartLine) {
1235 //Store number of nodes in line for later
1236 int entries = pLineList->size();
1238 //Parse list of nodes to a tree
1239 SmNodeListParser parser;
1240 std::unique_ptr<SmNode> pLine(parser.Parse(pLineList.get()));
1241 pLineList.reset();
1243 //Check if we're making the body of a subsup node bigger than one
1244 if(pParent->GetType() == SmNodeType::SubSup &&
1245 nParentIndex == 0 &&
1246 entries > 1) {
1247 //Wrap pLine in scalable round brackets
1248 SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5);
1249 std::unique_ptr<SmBraceNode> pBrace(new SmBraceNode(aTok));
1250 pBrace->SetScaleMode(SmScaleMode::Height);
1251 std::unique_ptr<SmNode> pLeft( CreateBracket(SmBracketType::Round, true) ),
1252 pRight( CreateBracket(SmBracketType::Round, false) );
1253 std::unique_ptr<SmBracebodyNode> pBody(new SmBracebodyNode(SmToken()));
1254 pBody->SetSubNodes(std::move(pLine), nullptr);
1255 pBrace->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight));
1256 pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
1257 pLine = std::move(pBrace);
1258 //TODO: Consider the following alternative behavior:
1259 //Consider the line: A + {B + C}^D lsub E
1260 //Here pLineList is B, + and C and pParent is a subsup node with
1261 //both RSUP and LSUB set. Imagine the user just inserted "B +" in
1262 //the body of the subsup node...
1263 //The most natural thing to do would be to make the line like this:
1264 //A + B lsub E + C ^ D
1265 //E.g. apply LSUB and LSUP to the first element in pLineList and RSUP
1266 //and RSUB to the last element in pLineList. But how should this act
1267 //for CSUP and CSUB ???
1268 //For this reason and because brackets was faster to implement, this solution
1269 //have been chosen. It might be worth working on the other solution later...
1272 //Set pStartLine if NULL
1273 if(!pStartLine)
1274 pStartLine = pLine.get();
1276 //Insert it back into the parent
1277 pParent->SetSubNode(nParentIndex, pLine.release());
1279 //Rebuild graph of caret position
1280 mpAnchor = nullptr;
1281 mpPosition = nullptr;
1282 BuildGraph();
1283 AnnotateSelection(); //Update selection annotation!
1285 //Set caret position
1286 if(!SetCaretPosition(PosAfterEdit))
1287 SetCaretPosition(SmCaretPos(pStartLine, 0));
1289 //End edit section
1290 EndEdit();
1293 void SmCursor::BeginEdit(){
1294 if(mnEditSections++ > 0) return;
1296 mbIsEnabledSetModifiedSmDocShell = mpDocShell->IsEnableSetModified();
1297 if( mbIsEnabledSetModifiedSmDocShell )
1298 mpDocShell->EnableSetModified( false );
1301 void SmCursor::EndEdit(){
1302 if(--mnEditSections > 0) return;
1304 mpDocShell->SetFormulaArranged(false);
1305 //Okay, I don't know what this does... :)
1306 //It's used in SmDocShell::SetText and with places where everything is modified.
1307 //I think it does some magic, with sfx, but everything is totally undocumented so
1308 //it's kinda hard to tell...
1309 if ( mbIsEnabledSetModifiedSmDocShell )
1310 mpDocShell->EnableSetModified( mbIsEnabledSetModifiedSmDocShell );
1311 //I think this notifies people around us that we've modified this document...
1312 mpDocShell->SetModified();
1313 //I think SmDocShell uses this value when it sends an update graphics event
1314 //Anyway comments elsewhere suggests it needs to be updated...
1315 mpDocShell->mnModifyCount++;
1317 //TODO: Consider copying the update accessibility code from SmDocShell::SetText in here...
1318 //This somehow updates the size of SmGraphicView if it is running in embedded mode
1319 if( mpDocShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED )
1320 mpDocShell->OnDocumentPrinterChanged(nullptr);
1322 //Request a repaint...
1323 RequestRepaint();
1325 //Update the edit engine and text of the document
1326 OUString formula;
1327 SmNodeToTextVisitor(mpTree, formula);
1328 mpDocShell->maText = formula;
1329 mpDocShell->GetEditEngine().QuickInsertText( formula, ESelection( 0, 0, EE_PARA_ALL, EE_TEXTPOS_ALL ) );
1330 mpDocShell->GetEditEngine().QuickFormatDoc();
1333 void SmCursor::RequestRepaint()
1335 if (SmViewShell *pViewSh = SmGetActiveView())
1337 if (comphelper::LibreOfficeKit::isActive())
1339 pViewSh->SendCaretToLOK();
1341 else if ( SfxObjectCreateMode::EMBEDDED == mpDocShell->GetCreateMode() )
1342 mpDocShell->Repaint();
1343 else
1344 pViewSh->GetGraphicWidget().Invalidate();
1348 bool SmCursor::IsAtTailOfBracket(SmBracketType eBracketType) const
1350 const SmCaretPos pos = GetPosition();
1351 if (!pos.IsValid()) {
1352 return false;
1355 SmNode* pNode = pos.pSelectedNode;
1357 if (pNode->GetType() == SmNodeType::Text) {
1358 SmTextNode* pTextNode = static_cast<SmTextNode*>(pNode);
1359 if (pos.nIndex < pTextNode->GetText().getLength()) {
1360 // The cursor is on a text node and at the middle of it.
1361 return false;
1363 } else {
1364 if (pos.nIndex < 1) {
1365 return false;
1369 while (true) {
1370 SmStructureNode* pParentNode = pNode->GetParent();
1371 if (!pParentNode) {
1372 // There's no brace body node in the ancestors.
1373 return false;
1376 int index = pParentNode->IndexOfSubNode(pNode);
1377 assert(index >= 0);
1378 if (static_cast<size_t>(index + 1) != pParentNode->GetNumSubNodes()) {
1379 // The cursor is not at the tail at one of ancestor nodes.
1380 return false;
1383 pNode = pParentNode;
1384 if (pNode->GetType() == SmNodeType::Bracebody) {
1385 // Found the brace body node.
1386 break;
1390 SmStructureNode* pBraceNodeTmp = pNode->GetParent();
1391 if (!pBraceNodeTmp || pBraceNodeTmp->GetType() != SmNodeType::Brace) {
1392 // Brace node is invalid.
1393 return false;
1396 SmBraceNode* pBraceNode = static_cast<SmBraceNode*>(pBraceNodeTmp);
1397 SmMathSymbolNode* pClosingNode = pBraceNode->ClosingBrace();
1398 if (!pClosingNode) {
1399 // Couldn't get closing symbol node.
1400 return false;
1403 // Check if the closing brace matches eBracketType.
1404 SmTokenType eClosingTokenType = pClosingNode->GetToken().eType;
1405 switch (eBracketType) {
1406 case SmBracketType::Round: if (eClosingTokenType != TRPARENT) { return false; } break;
1407 case SmBracketType::Square: if (eClosingTokenType != TRBRACKET) { return false; } break;
1408 case SmBracketType::Curly: if (eClosingTokenType != TRBRACE) { return false; } break;
1409 default:
1410 return false;
1413 return true;
1416 /////////////////////////////////////// SmNodeListParser
1418 SmNode* SmNodeListParser::Parse(SmNodeList* list){
1419 pList = list;
1420 //Delete error nodes
1421 SmNodeList::iterator it = pList->begin();
1422 while(it != pList->end()) {
1423 if((*it)->GetType() == SmNodeType::Error){
1424 //Delete and erase
1425 delete *it;
1426 it = pList->erase(it);
1427 }else
1428 ++it;
1430 SmNode* retval = Expression();
1431 pList = nullptr;
1432 return retval;
1435 SmNode* SmNodeListParser::Expression(){
1436 SmNodeArray NodeArray;
1437 //Accept as many relations as there is
1438 while(Terminal())
1439 NodeArray.push_back(Relation());
1441 //Create SmExpressionNode, I hope SmToken() will do :)
1442 SmStructureNode* pExpr = new SmExpressionNode(SmToken());
1443 pExpr->SetSubNodes(std::move(NodeArray));
1444 return pExpr;
1447 SmNode* SmNodeListParser::Relation(){
1448 //Read a sum
1449 std::unique_ptr<SmNode> pLeft(Sum());
1450 //While we have tokens and the next is a relation
1451 while(Terminal() && IsRelationOperator(Terminal()->GetToken())){
1452 //Take the operator
1453 std::unique_ptr<SmNode> pOper(Take());
1454 //Find the right side of the relation
1455 std::unique_ptr<SmNode> pRight(Sum());
1456 //Create new SmBinHorNode
1457 std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken()));
1458 pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight));
1459 pLeft = std::move(pNewNode);
1461 return pLeft.release();
1464 SmNode* SmNodeListParser::Sum(){
1465 //Read a product
1466 std::unique_ptr<SmNode> pLeft(Product());
1467 //While we have tokens and the next is a sum
1468 while(Terminal() && IsSumOperator(Terminal()->GetToken())){
1469 //Take the operator
1470 std::unique_ptr<SmNode> pOper(Take());
1471 //Find the right side of the sum
1472 std::unique_ptr<SmNode> pRight(Product());
1473 //Create new SmBinHorNode
1474 std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken()));
1475 pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight));
1476 pLeft = std::move(pNewNode);
1478 return pLeft.release();
1481 SmNode* SmNodeListParser::Product(){
1482 //Read a Factor
1483 std::unique_ptr<SmNode> pLeft(Factor());
1484 //While we have tokens and the next is a product
1485 while(Terminal() && IsProductOperator(Terminal()->GetToken())){
1486 //Take the operator
1487 std::unique_ptr<SmNode> pOper(Take());
1488 //Find the right side of the operation
1489 std::unique_ptr<SmNode> pRight(Factor());
1490 //Create new SmBinHorNode
1491 std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken()));
1492 pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight));
1493 pLeft = std::move(pNewNode);
1495 return pLeft.release();
1498 SmNode* SmNodeListParser::Factor(){
1499 //Read unary operations
1500 if(!Terminal())
1501 return Error();
1502 //Take care of unary operators
1503 else if(IsUnaryOperator(Terminal()->GetToken()))
1505 SmStructureNode *pUnary = new SmUnHorNode(SmToken());
1506 std::unique_ptr<SmNode> pOper(Terminal()),
1507 pArg;
1509 if(Next())
1510 pArg.reset(Factor());
1511 else
1512 pArg.reset(Error());
1514 pUnary->SetSubNodes(std::move(pOper), std::move(pArg));
1515 return pUnary;
1517 return Postfix();
1520 SmNode* SmNodeListParser::Postfix(){
1521 if(!Terminal())
1522 return Error();
1523 std::unique_ptr<SmNode> pArg;
1524 if(IsPostfixOperator(Terminal()->GetToken()))
1525 pArg.reset(Error());
1526 else if(IsOperator(Terminal()->GetToken()))
1527 return Error();
1528 else
1529 pArg.reset(Take());
1530 while(Terminal() && IsPostfixOperator(Terminal()->GetToken())) {
1531 std::unique_ptr<SmStructureNode> pUnary(new SmUnHorNode(SmToken()) );
1532 std::unique_ptr<SmNode> pOper(Take());
1533 pUnary->SetSubNodes(std::move(pArg), std::move(pOper));
1534 pArg = std::move(pUnary);
1536 return pArg.release();
1539 SmNode* SmNodeListParser::Error(){
1540 return new SmErrorNode(SmToken());
1543 bool SmNodeListParser::IsOperator(const SmToken &token) {
1544 return IsRelationOperator(token) ||
1545 IsSumOperator(token) ||
1546 IsProductOperator(token) ||
1547 IsUnaryOperator(token) ||
1548 IsPostfixOperator(token);
1551 bool SmNodeListParser::IsRelationOperator(const SmToken &token) {
1552 return bool(token.nGroup & TG::Relation);
1555 bool SmNodeListParser::IsSumOperator(const SmToken &token) {
1556 return bool(token.nGroup & TG::Sum);
1559 bool SmNodeListParser::IsProductOperator(const SmToken &token) {
1560 return token.nGroup & TG::Product &&
1561 token.eType != TWIDESLASH &&
1562 token.eType != TWIDEBACKSLASH &&
1563 token.eType != TUNDERBRACE &&
1564 token.eType != TOVERBRACE &&
1565 token.eType != TOVER;
1568 bool SmNodeListParser::IsUnaryOperator(const SmToken &token) {
1569 return token.nGroup & TG::UnOper &&
1570 (token.eType == TPLUS ||
1571 token.eType == TMINUS ||
1572 token.eType == TPLUSMINUS ||
1573 token.eType == TMINUSPLUS ||
1574 token.eType == TNEG ||
1575 token.eType == TUOPER);
1578 bool SmNodeListParser::IsPostfixOperator(const SmToken &token) {
1579 return token.eType == TFACT;
1582 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */