tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / starmath / source / cursor.cxx
blob7762fe9bd21fa76c591593f6f2c91731488e9584
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>
19 #include <vcl/transfer.hxx>
20 #include <vcl/unohelp2.hxx>
22 void SmCursor::Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor){
23 SmCaretPosGraphEntry* NewPos = nullptr;
24 switch(direction)
26 case MoveLeft:
27 if (mpPosition)
28 NewPos = mpPosition->Left;
29 OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!");
30 break;
31 case MoveRight:
32 if (mpPosition)
33 NewPos = mpPosition->Right;
34 OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!");
35 break;
36 case MoveUp:
37 //Implementation is practically identical to MoveDown, except for a single if statement
38 //so I've implemented them together and added a direction == MoveDown to the if statements.
39 case MoveDown:
40 if (mpPosition)
42 SmCaretLine from_line = SmCaretPos2LineVisitor(pDev, mpPosition->CaretPos).GetResult(),
43 best_line, //Best approximated line found so far
44 curr_line; //Current line
45 tools::Long dbp_sq = 0; //Distance squared to best line
46 for(const auto &pEntry : *mpGraph)
48 //Reject it if it's the current position
49 if(pEntry->CaretPos == mpPosition->CaretPos) continue;
50 //Compute caret line
51 curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult();
52 //Reject anything above if we're moving down
53 if(curr_line.GetTop() <= from_line.GetTop() && direction == MoveDown) continue;
54 //Reject anything below if we're moving up
55 if(curr_line.GetTop() + curr_line.GetHeight() >= from_line.GetTop() + from_line.GetHeight()
56 && direction == MoveUp) continue;
57 //Compare if it to what we have, if we have anything yet
58 if(NewPos){
59 //Compute distance to current line squared, multiplied with a horizontal factor
60 tools::Long dp_sq = curr_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
61 curr_line.SquaredDistanceY(from_line);
62 //Discard current line if best line is closer
63 if(dbp_sq <= dp_sq) continue;
65 //Take current line as the best
66 best_line = curr_line;
67 NewPos = pEntry.get();
68 //Update distance to best line
69 dbp_sq = best_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
70 best_line.SquaredDistanceY(from_line);
73 break;
74 default:
75 assert(false);
77 if(NewPos){
78 mpPosition = NewPos;
79 if(bMoveAnchor)
80 mpAnchor = NewPos;
81 RequestRepaint();
85 void SmCursor::MoveTo(OutputDevice* pDev, const Point& pos, bool bMoveAnchor)
87 SmCaretPosGraphEntry* NewPos = nullptr;
88 tools::Long dp_sq = 0, //Distance to current line squared
89 dbp_sq = 1; //Distance to best line squared
90 for(const auto &pEntry : *mpGraph)
92 OSL_ENSURE(pEntry->CaretPos.IsValid(), "The caret position graph may not have invalid positions!");
93 //Compute current line
94 SmCaretLine curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult();
95 //Compute squared distance to current line
96 dp_sq = curr_line.SquaredDistanceX(pos) + curr_line.SquaredDistanceY(pos);
97 //If we have a position compare to it
98 if(NewPos){
99 //If best line is closer, reject current line
100 if(dbp_sq <= dp_sq) continue;
102 //Accept current position as the best
103 NewPos = pEntry.get();
104 //Update distance to best line
105 dbp_sq = dp_sq;
107 if(NewPos){
108 mpPosition = NewPos;
109 if(bMoveAnchor)
110 mpAnchor = NewPos;
111 RequestRepaint();
115 void SmCursor::BuildGraph(){
116 //Save the current anchor and position
117 SmCaretPos _anchor, _position;
118 //Release mpGraph if allocated
119 if(mpGraph){
120 if(mpAnchor)
121 _anchor = mpAnchor->CaretPos;
122 if(mpPosition)
123 _position = mpPosition->CaretPos;
124 mpGraph.reset();
125 //Reset anchor and position as they point into an old graph
126 mpAnchor = nullptr;
127 mpPosition = nullptr;
130 //Build the new graph
131 mpGraph.reset(SmCaretPosGraphBuildingVisitor(mpTree).takeGraph());
133 //Restore anchor and position pointers
134 if(_anchor.IsValid() || _position.IsValid()){
135 for(const auto &pEntry : *mpGraph)
137 if(_anchor == pEntry->CaretPos)
138 mpAnchor = pEntry.get();
139 if(_position == pEntry->CaretPos)
140 mpPosition = pEntry.get();
143 //Set position and anchor to first caret position
144 auto it = mpGraph->begin();
145 assert(it != mpGraph->end());
146 if(!mpPosition)
147 mpPosition = it->get();
148 if(!mpAnchor)
149 mpAnchor = mpPosition;
151 assert(mpPosition);
152 assert(mpAnchor);
153 OSL_ENSURE(mpPosition->CaretPos.IsValid(), "Position must be valid");
154 OSL_ENSURE(mpAnchor->CaretPos.IsValid(), "Anchor must be valid");
157 bool SmCursor::SetCaretPosition(SmCaretPos pos){
158 for(const auto &pEntry : *mpGraph)
160 if(pEntry->CaretPos == pos)
162 mpPosition = pEntry.get();
163 mpAnchor = pEntry.get();
164 return true;
167 return false;
170 void SmCursor::AnnotateSelection() const {
171 //TODO: Manage a state, reset it upon modification and optimize this call
172 SmSetSelectionVisitor(mpAnchor->CaretPos, mpPosition->CaretPos, mpTree);
175 void SmCursor::Draw(OutputDevice& pDev, Point Offset, bool isCaretVisible){
176 SmCaretDrawingVisitor(pDev, GetPosition(), Offset, isCaretVisible);
179 tools::Rectangle SmCursor::GetCaretRectangle(OutputDevice& rOutDev) const
181 return SmCaretRectanglesVisitor(rOutDev, GetPosition()).getCaret();
184 tools::Rectangle SmCursor::GetSelectionRectangle(OutputDevice& rOutDev) const
186 AnnotateSelection();
187 return SmSelectionRectanglesVisitor(rOutDev, mpTree).GetSelection();
190 void SmCursor::DeletePrev(OutputDevice* pDev){
191 //Delete only a selection if there's a selection
192 if(HasSelection()){
193 Delete();
194 return;
197 SmNode* pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
198 SmStructureNode* pLineParent = pLine->GetParent();
199 int nLineOffsetIdx = pLineParent->IndexOfSubNode(pLine);
200 assert(nLineOffsetIdx >= 0);
202 //If we're in front of a node who's parent is a TABLE
203 if (pLineParent->GetType() == SmNodeType::Table && mpPosition->CaretPos.nIndex == 0 && nLineOffsetIdx > 0)
205 size_t nLineOffset = nLineOffsetIdx;
206 //Now we can merge with nLineOffset - 1
207 BeginEdit();
208 //Line to merge things into, so we can delete pLine
209 SmNode* pMergeLine = pLineParent->GetSubNode(nLineOffset-1);
210 OSL_ENSURE(pMergeLine, "pMergeLine cannot be NULL!");
211 SmCaretPos PosAfterDelete;
212 //Convert first line to list
213 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
214 NodeToList(pMergeLine, *pLineList);
215 if(!pLineList->empty()){
216 //Find iterator to patch
217 SmNodeList::iterator patchPoint = pLineList->end();
218 --patchPoint;
219 //Convert second line to list
220 NodeToList(pLine, *pLineList);
221 //Patch the line list
222 ++patchPoint;
223 PosAfterDelete = PatchLineList(pLineList.get(), patchPoint);
224 //Parse the line
225 pLine = SmNodeListParser().Parse(pLineList.get());
227 pLineList.reset();
228 pLineParent->SetSubNode(nLineOffset-1, pLine);
229 //Delete the removed line slot
230 SmNodeArray lines(pLineParent->GetNumSubNodes()-1);
231 for (size_t i = 0; i < pLineParent->GetNumSubNodes(); ++i)
233 if(i < nLineOffset)
234 lines[i] = pLineParent->GetSubNode(i);
235 else if(i > nLineOffset)
236 lines[i-1] = pLineParent->GetSubNode(i);
238 pLineParent->SetSubNodes(std::move(lines));
239 //Rebuild graph
240 mpAnchor = nullptr;
241 mpPosition = nullptr;
242 BuildGraph();
243 AnnotateSelection();
244 //Set caret position
245 if(!SetCaretPosition(PosAfterDelete))
246 SetCaretPosition(SmCaretPos(pLine, 0));
247 //Finish editing
248 EndEdit();
250 //TODO: If we're in an empty (sub/super/*) script
251 /*}else if(pLineParent->GetType() == SmNodeType::SubSup &&
252 nLineOffset != 0 &&
253 pLine->GetType() == SmNodeType::Expression &&
254 pLine->GetNumSubNodes() == 0){
255 //There's a (sub/super) script we can delete
256 //Consider selecting the entire script if GetNumSubNodes() != 0 or pLine->GetType() != SmNodeType::Expression
257 //TODO: Handle case where we delete a limit
260 //Else move select, and delete if not complex
261 }else{
262 Move(pDev, MoveLeft, false);
263 if(!HasComplexSelection())
264 Delete();
268 void SmCursor::Delete(){
269 //Return if we don't have a selection to delete
270 if(!HasSelection())
271 return;
273 //Enter edit section
274 BeginEdit();
276 //Set selected on nodes
277 AnnotateSelection();
279 //Find an arbitrary selected node
280 SmNode* pSNode = FindSelectedNode(mpTree);
281 assert(pSNode);
283 //Find the topmost node of the line that holds the selection
284 SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
285 OSL_ENSURE(pLine != mpTree, "Shouldn't be able to select the entire tree");
287 //Get the parent of the line
288 SmStructureNode* pLineParent = pLine->GetParent();
289 //Find line offset in parent
290 int nLineOffset = pLineParent->IndexOfSubNode(pLine);
291 assert(nLineOffset >= 0);
293 //Position after delete
294 SmCaretPos PosAfterDelete;
296 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
297 NodeToList(pLine, *pLineList);
299 //Take the selected nodes and delete them...
300 SmNodeList::iterator patchIt = TakeSelectedNodesFromList(pLineList.get());
302 //Get the position to set after delete
303 PosAfterDelete = PatchLineList(pLineList.get(), patchIt);
305 //Finish editing
306 FinishEdit(std::move(pLineList), pLineParent, nLineOffset, PosAfterDelete);
309 void SmCursor::InsertNodes(std::unique_ptr<SmNodeList> pNewNodes){
310 if(pNewNodes->empty()){
311 return;
314 //Begin edit section
315 BeginEdit();
317 //Get the current position
318 const SmCaretPos pos = mpPosition->CaretPos;
320 //Find top most of line that holds position
321 SmNode* pLine = FindTopMostNodeInLine(pos.pSelectedNode);
322 const bool bSelectedIsTopMost = pLine == pos.pSelectedNode;
324 //Find line parent and line index in parent
325 SmStructureNode* pLineParent = pLine->GetParent();
326 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
327 assert(nParentIndex >= 0);
329 //Convert line to list
330 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
331 NodeToList(pLine, *pLineList); // deletes pLine, potentially deleting pos.pSelectedNode
333 //Find iterator for place to insert nodes
334 SmNodeList::iterator it = bSelectedIsTopMost ? pLineList->begin()
335 : FindPositionInLineList(pLineList.get(), pos);
337 //Insert all new nodes
338 SmNodeList::iterator newIt,
339 patchIt = it, // (pointless default value, fixes compiler warnings)
340 insIt;
341 for(newIt = pNewNodes->begin(); newIt != pNewNodes->end(); ++newIt){
342 insIt = pLineList->insert(it, *newIt);
343 if(newIt == pNewNodes->begin())
344 patchIt = insIt;
346 //Patch the places we've changed stuff
347 PatchLineList(pLineList.get(), patchIt);
348 SmCaretPos PosAfterInsert = PatchLineList(pLineList.get(), it);
349 //Release list, we've taken the nodes
350 pNewNodes.reset();
352 //Finish editing
353 FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert);
356 SmNodeList::iterator SmCursor::FindPositionInLineList(SmNodeList* pLineList,
357 const SmCaretPos& rCaretPos)
359 //Find iterator for position
360 SmNodeList::iterator it = std::find(pLineList->begin(), pLineList->end(), rCaretPos.pSelectedNode);
361 if (it != pLineList->end())
363 if((*it)->GetType() == SmNodeType::Text)
365 //Split textnode if needed
366 if(rCaretPos.nIndex > 0)
368 SmTextNode* pText = static_cast<SmTextNode*>(rCaretPos.pSelectedNode);
369 if (rCaretPos.nIndex == pText->GetText().getLength())
370 return ++it;
371 OUString str1 = pText->GetText().copy(0, rCaretPos.nIndex);
372 OUString str2 = pText->GetText().copy(rCaretPos.nIndex);
373 pText->ChangeText(str1);
374 ++it;
375 //Insert str2 as new text node
376 assert(!str2.isEmpty());
377 SmTextNode* pNewText = new SmTextNode(pText->GetToken(), pText->GetFontDesc());
378 pNewText->ChangeText(str2);
379 it = pLineList->insert(it, pNewText);
381 }else
382 ++it;
383 //it now pointer to the node following pos, so pLineList->insert(it, ...) will insert correctly
384 return it;
386 //If we didn't find pSelectedNode, it must be because the caret is in front of the line
387 return pLineList->begin();
390 SmCaretPos SmCursor::PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter) {
391 //The nodes we should consider merging
392 SmNode *prev = nullptr,
393 *next = nullptr;
394 if(aIter != pLineList->end())
395 next = *aIter;
396 if(aIter != pLineList->begin()) {
397 --aIter;
398 prev = *aIter;
399 ++aIter;
402 //Check if there's textnodes to merge
403 if( prev &&
404 next &&
405 prev->GetType() == SmNodeType::Text &&
406 next->GetType() == SmNodeType::Text &&
407 ( prev->GetToken().eType != TNUMBER ||
408 next->GetToken().eType == TNUMBER) ){
409 SmTextNode *pText = static_cast<SmTextNode*>(prev),
410 *pOldN = static_cast<SmTextNode*>(next);
411 SmCaretPos retval(pText, pText->GetText().getLength());
412 OUString newText = pText->GetText() + pOldN->GetText();
413 pText->ChangeText(newText);
414 delete pOldN;
415 pLineList->erase(aIter);
416 return retval;
419 //Check if there's a SmPlaceNode to remove:
420 if(prev && next && prev->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(next->GetToken())){
421 --aIter;
422 aIter = pLineList->erase(aIter);
423 delete prev;
424 //Return caret pos in front of aIter
425 if(aIter != pLineList->begin())
426 --aIter; //Thus find node before aIter
427 if(aIter == pLineList->begin())
428 return SmCaretPos();
429 return SmCaretPos::GetPosAfter(*aIter);
431 if(prev && next && next->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(prev->GetToken())){
432 aIter = pLineList->erase(aIter);
433 delete next;
434 return SmCaretPos::GetPosAfter(prev);
437 //If we didn't do anything return
438 if(!prev) //return an invalid to indicate we're in front of line
439 return SmCaretPos();
440 return SmCaretPos::GetPosAfter(prev);
443 SmNodeList::iterator SmCursor::TakeSelectedNodesFromList(SmNodeList *pLineList,
444 SmNodeList *pSelectedNodes) {
445 SmNodeList::iterator retval;
446 SmNodeList::iterator it = pLineList->begin();
447 while(it != pLineList->end()){
448 if((*it)->IsSelected()){
449 //Split text nodes
450 if((*it)->GetType() == SmNodeType::Text) {
451 SmTextNode* pText = static_cast<SmTextNode*>(*it);
452 OUString aText = pText->GetText();
453 //Start and lengths of the segments, 2 is the selected segment
454 int start2 = pText->GetSelectionStart(),
455 start3 = pText->GetSelectionEnd(),
456 len1 = start2 - 0,
457 len2 = start3 - start2,
458 len3 = aText.getLength() - start3;
459 SmToken aToken = pText->GetToken();
460 sal_uInt16 eFontDesc = pText->GetFontDesc();
461 //If we need make segment 1
462 if(len1 > 0) {
463 OUString str = aText.copy(0, len1);
464 pText->ChangeText(str);
465 ++it;
466 } else {//Remove it if not needed
467 it = pLineList->erase(it);
468 delete pText;
470 //Set retval to be right after the selection
471 retval = it;
472 //if we need make segment 3
473 if(len3 > 0) {
474 OUString str = aText.copy(start3, len3);
475 SmTextNode* pSeg3 = new SmTextNode(aToken, eFontDesc);
476 pSeg3->ChangeText(str);
477 retval = pLineList->insert(it, pSeg3);
479 //If we need to save the selected text
480 if(pSelectedNodes && len2 > 0) {
481 OUString str = aText.copy(start2, len2);
482 SmTextNode* pSeg2 = new SmTextNode(aToken, eFontDesc);
483 pSeg2->ChangeText(str);
484 pSelectedNodes->push_back(pSeg2);
486 } else { //if it's not textnode
487 SmNode* pNode = *it;
488 retval = it = pLineList->erase(it);
489 if(pSelectedNodes)
490 pSelectedNodes->push_back(pNode);
491 else
492 delete pNode;
494 } else
495 ++it;
497 return retval;
500 void SmCursor::InsertSubSup(SmSubSup eSubSup) {
501 AnnotateSelection();
503 //Find line
504 SmNode *pLine;
505 if(HasSelection()) {
506 SmNode *pSNode = FindSelectedNode(mpTree);
507 assert(pSNode);
508 pLine = FindTopMostNodeInLine(pSNode, true);
509 } else
510 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
512 //Find Parent and offset in parent
513 SmStructureNode *pLineParent = pLine->GetParent();
514 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
515 assert(nParentIndex >= 0);
517 //TODO: Consider handling special cases where parent is an SmOperNode,
518 // Maybe this method should be able to add limits to an SmOperNode...
520 //We begin modifying the tree here
521 BeginEdit();
523 //Convert line to list
524 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
525 NodeToList(pLine, *pLineList);
527 //Take the selection, and/or find iterator for current position
528 std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList);
529 SmNodeList::iterator it;
530 if(HasSelection())
531 it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get());
532 else
533 it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos);
535 //Find node that this should be applied to
536 SmNode* pSubject;
537 bool bPatchLine = !pSelectedNodesList->empty(); //If the line should be patched later
538 if(it != pLineList->begin()) {
539 --it;
540 pSubject = *it;
541 ++it;
542 } else {
543 //Create a new place node
544 pSubject = new SmPlaceNode();
545 pSubject->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
546 it = pLineList->insert(it, pSubject);
547 ++it;
548 bPatchLine = true; //We've modified the line it should be patched later.
551 //Wrap the subject in a SmSubSupNode
552 SmSubSupNode* pSubSup;
553 if(pSubject->GetType() != SmNodeType::SubSup){
554 SmToken token;
555 token.nGroup = TG::Power;
556 pSubSup = new SmSubSupNode(token);
557 pSubSup->SetBody(pSubject);
558 *(--it) = pSubSup;
559 ++it;
560 }else
561 pSubSup = static_cast<SmSubSupNode*>(pSubject);
562 //pSubject shouldn't be referenced anymore, pSubSup is the SmSubSupNode in pLineList we wish to edit.
563 //and it pointer to the element following pSubSup in pLineList.
564 pSubject = nullptr;
566 //Patch the line if we noted that was needed previously
567 if(bPatchLine)
568 PatchLineList(pLineList.get(), it);
570 //Convert existing, if any, sub-/superscript line to list
571 SmNode *pScriptLine = pSubSup->GetSubSup(eSubSup);
572 std::unique_ptr<SmNodeList> pScriptLineList(new SmNodeList);
573 NodeToList(pScriptLine, *pScriptLineList);
575 //Add selection to pScriptLineList
576 unsigned int nOldSize = pScriptLineList->size();
577 pScriptLineList->insert(pScriptLineList->end(), pSelectedNodesList->begin(), pSelectedNodesList->end());
578 pSelectedNodesList.reset();
580 //Patch pScriptLineList if needed
581 if(0 < nOldSize && nOldSize < pScriptLineList->size()) {
582 SmNodeList::iterator iPatchPoint = pScriptLineList->begin();
583 std::advance(iPatchPoint, nOldSize);
584 PatchLineList(pScriptLineList.get(), iPatchPoint);
587 //Find caret pos, that should be used after sub-/superscription.
588 SmCaretPos PosAfterScript; //Leave invalid for first position
589 if (!pScriptLineList->empty())
590 PosAfterScript = SmCaretPos::GetPosAfter(pScriptLineList->back());
592 //Parse pScriptLineList
593 pScriptLine = SmNodeListParser().Parse(pScriptLineList.get());
594 pScriptLineList.reset();
596 //Insert pScriptLine back into the tree
597 pSubSup->SetSubSup(eSubSup, pScriptLine);
599 //Finish editing
600 FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterScript, pScriptLine);
603 void SmCursor::InsertBrackets(SmBracketType eBracketType) {
604 BeginEdit();
606 AnnotateSelection();
608 //Find line
609 SmNode *pLine;
610 if(HasSelection()) {
611 SmNode *pSNode = FindSelectedNode(mpTree);
612 assert(pSNode);
613 pLine = FindTopMostNodeInLine(pSNode, true);
614 } else
615 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
617 //Find parent and offset in parent
618 SmStructureNode *pLineParent = pLine->GetParent();
619 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
620 assert(nParentIndex >= 0);
622 //Convert line to list
623 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
624 NodeToList(pLine, *pLineList);
626 //Take the selection, and/or find iterator for current position
627 std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList);
628 SmNodeList::iterator it;
629 if(HasSelection())
630 it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get());
631 else
632 it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos);
634 //If there's no selected nodes, create a place node
635 std::unique_ptr<SmNode> pBodyNode;
636 SmCaretPos PosAfterInsert;
637 if(pSelectedNodesList->empty()) {
638 pBodyNode.reset(new SmPlaceNode());
639 PosAfterInsert = SmCaretPos(pBodyNode.get(), 1);
640 } else
641 pBodyNode.reset(SmNodeListParser().Parse(pSelectedNodesList.get()));
643 pSelectedNodesList.reset();
645 //Create SmBraceNode
646 SmToken aTok(TLEFT, '\0', u"left"_ustr, TG::NONE, 5);
647 SmBraceNode *pBrace = new SmBraceNode(aTok);
648 pBrace->SetScaleMode(SmScaleMode::Height);
649 std::unique_ptr<SmNode> pLeft( CreateBracket(eBracketType, true) ),
650 pRight( CreateBracket(eBracketType, false) );
651 std::unique_ptr<SmBracebodyNode> pBody(new SmBracebodyNode(SmToken()));
652 pBody->SetSubNodes(std::move(pBodyNode), nullptr);
653 pBrace->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight));
654 pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
656 //Insert into line
657 pLineList->insert(it, pBrace);
658 //Patch line (I think this is good enough)
659 SmCaretPos aAfter = PatchLineList(pLineList.get(), it);
660 if( !PosAfterInsert.IsValid() )
661 PosAfterInsert = aAfter;
663 //Finish editing
664 FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert);
667 SmNode *SmCursor::CreateBracket(SmBracketType eBracketType, bool bIsLeft) {
668 SmToken aTok;
669 if(bIsLeft){
670 switch(eBracketType){
671 case SmBracketType::Round:
672 aTok = SmToken(TLPARENT, MS_LPARENT, u"("_ustr, TG::LBrace, 5);
673 break;
674 case SmBracketType::Square:
675 aTok = SmToken(TLBRACKET, MS_LBRACKET, u"["_ustr, TG::LBrace, 5);
676 break;
677 case SmBracketType::Curly:
678 aTok = SmToken(TLBRACE, MS_LBRACE, u"lbrace"_ustr, TG::LBrace, 5);
679 break;
681 } else {
682 switch(eBracketType) {
683 case SmBracketType::Round:
684 aTok = SmToken(TRPARENT, MS_RPARENT, u")"_ustr, TG::RBrace, 5);
685 break;
686 case SmBracketType::Square:
687 aTok = SmToken(TRBRACKET, MS_RBRACKET, u"]"_ustr, TG::RBrace, 5);
688 break;
689 case SmBracketType::Curly:
690 aTok = SmToken(TRBRACE, MS_RBRACE, u"rbrace"_ustr, TG::RBrace, 5);
691 break;
694 SmNode* pRetVal = new SmMathSymbolNode(aTok);
695 pRetVal->SetScaleMode(SmScaleMode::Height);
696 return pRetVal;
699 bool SmCursor::InsertRow() {
700 AnnotateSelection();
702 //Find line
703 SmNode *pLine;
704 if(HasSelection()) {
705 SmNode *pSNode = FindSelectedNode(mpTree);
706 assert(pSNode);
707 pLine = FindTopMostNodeInLine(pSNode, true);
708 } else
709 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
711 //Find parent and offset in parent
712 SmStructureNode *pLineParent = pLine->GetParent();
713 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
714 assert(nParentIndex >= 0 && nParentIndex < INT_MAX);
716 //Discover the context of this command
717 SmTableNode *pTable = nullptr;
718 SmMatrixNode *pMatrix = nullptr;
719 int nTableIndex = nParentIndex;
720 if(pLineParent->GetType() == SmNodeType::Table)
721 pTable = static_cast<SmTableNode*>(pLineParent);
722 //If it's wrapped in a SmLineNode, we can still insert a newline
723 else if(pLineParent->GetType() == SmNodeType::Line &&
724 pLineParent->GetParent() &&
725 pLineParent->GetParent()->GetType() == SmNodeType::Table) {
726 //NOTE: This hack might give problems if we stop ignoring SmAlignNode
727 pTable = static_cast<SmTableNode*>(pLineParent->GetParent());
728 nTableIndex = pTable->IndexOfSubNode(pLineParent);
729 assert(nTableIndex >= 0 && nTableIndex < INT_MAX);
731 if(pLineParent->GetType() == SmNodeType::Matrix)
732 pMatrix = static_cast<SmMatrixNode*>(pLineParent);
734 //If we're not in a context that supports InsertRow, return sal_False
735 if(!pTable && !pMatrix)
736 return false;
738 //Now we start editing
739 BeginEdit();
741 //Convert line to list
742 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
743 NodeToList(pLine, *pLineList);
745 //Find position in line
746 SmNodeList::iterator it;
747 if(HasSelection()) {
748 //Take the selected nodes and delete them...
749 it = TakeSelectedNodesFromList(pLineList.get());
750 } else
751 it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos);
753 //New caret position after inserting the newline/row in whatever context
754 SmCaretPos PosAfterInsert;
756 //If we're in the context of a table
757 if(pTable) {
758 std::unique_ptr<SmNodeList> pNewLineList(new SmNodeList);
759 //Move elements from pLineList to pNewLineList
760 SmNodeList& rLineList = *pLineList;
761 pNewLineList->splice(pNewLineList->begin(), rLineList, it, rLineList.end());
762 //Make sure it is valid again
763 it = pLineList->end();
764 if(it != pLineList->begin())
765 --it;
766 if(pNewLineList->empty())
767 pNewLineList->push_front(new SmPlaceNode());
768 //Parse new line
769 std::unique_ptr<SmNode> pNewLine(SmNodeListParser().Parse(pNewLineList.get()));
770 pNewLineList.reset();
771 //Wrap pNewLine in SmLineNode if needed
772 if(pLineParent->GetType() == SmNodeType::Line) {
773 std::unique_ptr<SmLineNode> pNewLineNode(new SmLineNode(SmToken(TNEWLINE, '\0', u"newline"_ustr)));
774 pNewLineNode->SetSubNodes(std::move(pNewLine), nullptr);
775 pNewLine = std::move(pNewLineNode);
777 //Get position
778 PosAfterInsert = SmCaretPos(pNewLine.get(), 0);
779 //Move other nodes if needed
780 for( int i = pTable->GetNumSubNodes(); i > nTableIndex + 1; i--)
781 pTable->SetSubNode(i, pTable->GetSubNode(i-1));
783 //Insert new line
784 pTable->SetSubNode(nTableIndex + 1, pNewLine.release());
786 //Check if we need to change token type:
787 if(pTable->GetNumSubNodes() > 2 && pTable->GetToken().eType == TBINOM) {
788 SmToken tok = pTable->GetToken();
789 tok.eType = TSTACK;
790 pTable->SetToken(tok);
793 //If we're in the context of a matrix
794 else {
795 //Find position after insert and patch the list
796 PosAfterInsert = PatchLineList(pLineList.get(), it);
797 //Move other children
798 sal_uInt16 rows = pMatrix->GetNumRows();
799 sal_uInt16 cols = pMatrix->GetNumCols();
800 int nRowStart = (nParentIndex - nParentIndex % cols) + cols;
801 for( int i = pMatrix->GetNumSubNodes() + cols - 1; i >= nRowStart + cols; i--)
802 pMatrix->SetSubNode(i, pMatrix->GetSubNode(i - cols));
803 for( int i = nRowStart; i < nRowStart + cols; i++) {
804 SmPlaceNode *pNewLine = new SmPlaceNode();
805 if(i == nParentIndex + cols)
806 PosAfterInsert = SmCaretPos(pNewLine, 0);
807 pMatrix->SetSubNode(i, pNewLine);
809 pMatrix->SetRowCol(rows + 1, cols);
812 //Finish editing
813 FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert);
814 //FinishEdit is actually used to handle situations where parent is an instance of
815 //SmSubSupNode. In this case parent should always be a table or matrix, however, for
816 //code reuse we just use FinishEdit() here too.
817 return true;
820 void SmCursor::InsertFraction() {
821 AnnotateSelection();
823 //Find line
824 SmNode *pLine;
825 if(HasSelection()) {
826 SmNode *pSNode = FindSelectedNode(mpTree);
827 assert(pSNode);
828 pLine = FindTopMostNodeInLine(pSNode, true);
829 } else
830 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
832 //Find Parent and offset in parent
833 SmStructureNode *pLineParent = pLine->GetParent();
834 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
835 assert(nParentIndex >= 0);
837 //We begin modifying the tree here
838 BeginEdit();
840 //Convert line to list
841 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
842 NodeToList(pLine, *pLineList);
844 //Take the selection, and/or find iterator for current position
845 std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList);
846 SmNodeList::iterator it;
847 if(HasSelection())
848 it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get());
849 else
850 it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos);
852 //Create pNum, and pDenom
853 bool bEmptyFraction = pSelectedNodesList->empty();
854 std::unique_ptr<SmNode> pNum( bEmptyFraction
855 ? new SmPlaceNode()
856 : SmNodeListParser().Parse(pSelectedNodesList.get()) );
857 std::unique_ptr<SmNode> pDenom(new SmPlaceNode());
858 pSelectedNodesList.reset();
860 //Create new fraction
861 SmBinVerNode *pFrac = new SmBinVerNode(SmToken(TOVER, '\0', u"over"_ustr, TG::Product, 0));
862 std::unique_ptr<SmNode> pRect(new SmRectangleNode(SmToken()));
863 pFrac->SetSubNodes(std::move(pNum), std::move(pRect), std::move(pDenom));
865 //Insert in pLineList
866 SmNodeList::iterator patchIt = pLineList->insert(it, pFrac);
867 PatchLineList(pLineList.get(), patchIt);
868 PatchLineList(pLineList.get(), it);
870 //Finish editing
871 SmNode *pSelectedNode = bEmptyFraction ? pFrac->GetSubNode(0) : pFrac->GetSubNode(2);
872 FinishEdit(std::move(pLineList), pLineParent, nParentIndex, SmCaretPos(pSelectedNode, 1));
875 void SmCursor::InsertText(const OUString& aString)
877 BeginEdit();
879 Delete();
881 SmToken token;
882 token.eType = TIDENT;
883 token.cMathChar = u""_ustr;
884 token.nGroup = TG::NONE;
885 token.nLevel = 5;
886 token.aText = aString;
888 SmTextNode* pText = new SmTextNode(token, FNT_VARIABLE);
889 pText->SetText(aString);
890 pText->AdjustFontDesc();
891 pText->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
893 std::unique_ptr<SmNodeList> pList(new SmNodeList);
894 pList->push_front(pText);
895 InsertNodes(std::move(pList));
897 EndEdit();
900 void SmCursor::InsertElement(SmFormulaElement element){
901 BeginEdit();
903 Delete();
905 //Create new node
906 SmNode* pNewNode = nullptr;
907 switch(element){
908 case BlankElement:
910 SmToken token;
911 token.eType = TBLANK;
912 token.nGroup = TG::Blank;
913 token.aText = "~";
914 SmBlankNode* pBlankNode = new SmBlankNode(token);
915 pBlankNode->IncreaseBy(token);
916 pNewNode = pBlankNode;
917 }break;
918 case FactorialElement:
920 SmToken token(TFACT, MS_FACT, u"fact"_ustr, TG::UnOper, 5);
921 pNewNode = new SmMathSymbolNode(token);
922 }break;
923 case PlusElement:
925 SmToken token;
926 token.eType = TPLUS;
927 token.setChar(MS_PLUS);
928 token.nGroup = TG::UnOper | TG::Sum;
929 token.nLevel = 5;
930 token.aText = "+";
931 pNewNode = new SmMathSymbolNode(token);
932 }break;
933 case MinusElement:
935 SmToken token;
936 token.eType = TMINUS;
937 token.setChar(MS_MINUS);
938 token.nGroup = TG::UnOper | TG::Sum;
939 token.nLevel = 5;
940 token.aText = "-";
941 pNewNode = new SmMathSymbolNode(token);
942 }break;
943 case CDotElement:
945 SmToken token;
946 token.eType = TCDOT;
947 token.setChar(MS_CDOT);
948 token.nGroup = TG::Product;
949 token.aText = "cdot";
950 pNewNode = new SmMathSymbolNode(token);
951 }break;
952 case EqualElement:
954 SmToken token;
955 token.eType = TASSIGN;
956 token.setChar(MS_ASSIGN);
957 token.nGroup = TG::Relation;
958 token.aText = "=";
959 pNewNode = new SmMathSymbolNode(token);
960 }break;
961 case LessThanElement:
963 SmToken token;
964 token.eType = TLT;
965 token.setChar(MS_LT);
966 token.nGroup = TG::Relation;
967 token.aText = "<";
968 pNewNode = new SmMathSymbolNode(token);
969 }break;
970 case GreaterThanElement:
972 SmToken token;
973 token.eType = TGT;
974 token.setChar(MS_GT);
975 token.nGroup = TG::Relation;
976 token.aText = ">";
977 pNewNode = new SmMathSymbolNode(token);
978 }break;
979 case PercentElement:
981 SmToken token;
982 token.eType = TTEXT;
983 token.setChar(MS_PERCENT);
984 token.nGroup = TG::NONE;
985 token.aText = "\"%\"";
986 pNewNode = new SmMathSymbolNode(token);
987 }break;
989 assert(pNewNode);
991 //Prepare the new node
992 pNewNode->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
994 //Insert new node
995 std::unique_ptr<SmNodeList> pList(new SmNodeList);
996 pList->push_front(pNewNode);
997 InsertNodes(std::move(pList));
999 EndEdit();
1002 void SmCursor::InsertSpecial(std::u16string_view _aString)
1004 BeginEdit();
1005 Delete();
1007 OUString aString( comphelper::string::strip(_aString, ' ') );
1009 //Create instance of special node
1010 SmToken token;
1011 token.eType = TSPECIAL;
1012 token.cMathChar = u""_ustr;
1013 token.nGroup = TG::NONE;
1014 token.nLevel = 5;
1015 token.aText = aString;
1016 SmSpecialNode* pSpecial = new SmSpecialNode(token);
1018 //Prepare the special node
1019 pSpecial->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
1021 //Insert the node
1022 std::unique_ptr<SmNodeList> pList(new SmNodeList);
1023 pList->push_front(pSpecial);
1024 InsertNodes(std::move(pList));
1026 EndEdit();
1029 void SmCursor::InsertCommandText(const OUString& aCommandText) {
1030 //Parse the sub expression
1031 auto xSubExpr = mpDocShell->GetParser()->ParseExpression(aCommandText);
1033 //Prepare the subtree
1034 xSubExpr->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
1036 //Convert subtree to list
1037 SmNode* pSubExpr = xSubExpr.release();
1038 std::unique_ptr<SmNodeList> pLineList(new SmNodeList);
1039 NodeToList(pSubExpr, *pLineList);
1041 BeginEdit();
1043 //Delete any selection
1044 Delete();
1046 //Insert it
1047 InsertNodes(std::move(pLineList));
1049 EndEdit();
1052 void SmCursor::Copy(vcl::Window* pWindow)
1054 if(!HasSelection())
1055 return;
1057 AnnotateSelection();
1058 //Find selected node
1059 SmNode* pSNode = FindSelectedNode(mpTree);
1060 assert(pSNode);
1061 //Find visual line
1062 SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
1063 assert(pLine);
1065 //Clone selected nodes
1066 // TODO: Simplify all this cloning since we only need a formula string now.
1067 SmClipboard aClipboard;
1068 if(IsLineCompositionNode(pLine))
1069 CloneLineToClipboard(static_cast<SmStructureNode*>(pLine), &aClipboard);
1070 else{
1071 //Special care to only clone selected text
1072 if(pLine->GetType() == SmNodeType::Text) {
1073 SmTextNode *pText = static_cast<SmTextNode*>(pLine);
1074 std::unique_ptr<SmTextNode> pClone(new SmTextNode( pText->GetToken(), pText->GetFontDesc() ));
1075 int start = pText->GetSelectionStart(),
1076 length = pText->GetSelectionEnd() - pText->GetSelectionStart();
1077 pClone->ChangeText(pText->GetText().copy(start, length));
1078 pClone->SetScaleMode(pText->GetScaleMode());
1079 aClipboard.push_front(std::move(pClone));
1080 } else {
1081 SmCloningVisitor aCloneFactory;
1082 aClipboard.push_front(std::unique_ptr<SmNode>(aCloneFactory.Clone(pLine)));
1086 // Parse list of nodes to a tree
1087 SmNodeListParser parser;
1088 SmNode* pTree(parser.Parse(CloneList(aClipboard).get()));
1090 // Parse the tree to a string
1091 OUString aString;
1092 SmNodeToTextVisitor(pTree, aString);
1094 //Set clipboard
1095 auto xClipboard(pWindow ? pWindow->GetClipboard() : GetSystemClipboard());
1096 vcl::unohelper::TextDataObject::CopyStringTo(aString, xClipboard);
1099 void SmCursor::Paste(vcl::Window* pWindow)
1101 BeginEdit();
1102 Delete();
1104 auto xClipboard(pWindow ? pWindow->GetClipboard() : GetSystemClipboard());
1105 auto aDataHelper(TransferableDataHelper::CreateFromClipboard(xClipboard));
1106 if (aDataHelper.GetTransferable().is())
1108 // TODO: Support MATHML
1109 auto nId = SotClipboardFormatId::STRING;
1110 if (aDataHelper.HasFormat(nId))
1112 OUString aString;
1113 if (aDataHelper.GetString(nId, aString))
1114 InsertCommandText(aString);
1118 EndEdit();
1121 std::unique_ptr<SmNodeList> SmCursor::CloneList(SmClipboard &rClipboard){
1122 SmCloningVisitor aCloneFactory;
1123 std::unique_ptr<SmNodeList> pClones(new SmNodeList);
1125 for(auto &xNode : rClipboard){
1126 SmNode *pClone = aCloneFactory.Clone(xNode.get());
1127 pClones->push_back(pClone);
1130 return pClones;
1133 SmNode* SmCursor::FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected){
1134 assert(pSNode);
1135 //Move up parent until we find a node who's
1136 //parent is NULL or isn't selected and not a type of:
1137 // SmExpressionNode
1138 // SmLineNode
1139 // SmBinHorNode
1140 // SmUnHorNode
1141 // SmAlignNode
1142 // SmFontNode
1143 while(pSNode->GetParent() &&
1144 ((MoveUpIfSelected &&
1145 pSNode->GetParent()->IsSelected()) ||
1146 IsLineCompositionNode(pSNode->GetParent())))
1147 pSNode = pSNode->GetParent();
1148 //Now we have the selection line node
1149 return pSNode;
1152 SmNode* SmCursor::FindSelectedNode(SmNode* pNode){
1153 if(pNode->GetNumSubNodes() == 0)
1154 return nullptr;
1155 for(auto pChild : *static_cast<SmStructureNode*>(pNode))
1157 if(!pChild)
1158 continue;
1159 if(pChild->IsSelected())
1160 return pChild;
1161 SmNode* pRetVal = FindSelectedNode(pChild);
1162 if(pRetVal)
1163 return pRetVal;
1165 return nullptr;
1168 void SmCursor::LineToList(SmStructureNode* pLine, SmNodeList& list){
1169 for(auto pChild : *pLine)
1171 if (!pChild)
1172 continue;
1173 switch(pChild->GetType()){
1174 case SmNodeType::Line:
1175 case SmNodeType::UnHor:
1176 case SmNodeType::Expression:
1177 case SmNodeType::BinHor:
1178 case SmNodeType::Align:
1179 case SmNodeType::Font:
1180 LineToList(static_cast<SmStructureNode*>(pChild), list);
1181 break;
1182 case SmNodeType::Error:
1183 delete pChild;
1184 break;
1185 default:
1186 list.push_back(pChild);
1189 pLine->ClearSubNodes();
1190 delete pLine;
1193 void SmCursor::CloneLineToClipboard(SmStructureNode* pLine, SmClipboard* pClipboard){
1194 SmCloningVisitor aCloneFactory;
1195 for(auto pChild : *pLine)
1197 if (!pChild)
1198 continue;
1199 if( IsLineCompositionNode( pChild ) )
1200 CloneLineToClipboard( static_cast<SmStructureNode*>(pChild), pClipboard );
1201 else if( pChild->IsSelected() && pChild->GetType() != SmNodeType::Error ) {
1202 //Only clone selected text from SmTextNode
1203 if(pChild->GetType() == SmNodeType::Text) {
1204 SmTextNode *pText = static_cast<SmTextNode*>(pChild);
1205 std::unique_ptr<SmTextNode> pClone(new SmTextNode( pChild->GetToken(), pText->GetFontDesc() ));
1206 int start = pText->GetSelectionStart(),
1207 length = pText->GetSelectionEnd() - pText->GetSelectionStart();
1208 pClone->ChangeText(pText->GetText().copy(start, length));
1209 pClone->SetScaleMode(pText->GetScaleMode());
1210 pClipboard->push_back(std::move(pClone));
1211 } else
1212 pClipboard->push_back(std::unique_ptr<SmNode>(aCloneFactory.Clone(pChild)));
1217 bool SmCursor::IsLineCompositionNode(SmNode const * pNode){
1218 switch(pNode->GetType()){
1219 case SmNodeType::Line:
1220 case SmNodeType::UnHor:
1221 case SmNodeType::Expression:
1222 case SmNodeType::BinHor:
1223 case SmNodeType::Align:
1224 case SmNodeType::Font:
1225 return true;
1226 default:
1227 return false;
1231 int SmCursor::CountSelectedNodes(SmNode* pNode){
1232 if(pNode->GetNumSubNodes() == 0)
1233 return 0;
1234 int nCount = 0;
1235 for(auto pChild : *static_cast<SmStructureNode*>(pNode))
1237 if (!pChild)
1238 continue;
1239 if(pChild->IsSelected() && !IsLineCompositionNode(pChild))
1240 nCount++;
1241 nCount += CountSelectedNodes(pChild);
1243 return nCount;
1246 bool SmCursor::HasComplexSelection(){
1247 if(!HasSelection())
1248 return false;
1249 AnnotateSelection();
1251 return CountSelectedNodes(mpTree) > 1;
1254 void SmCursor::FinishEdit(std::unique_ptr<SmNodeList> pLineList,
1255 SmStructureNode* pParent,
1256 int nParentIndex,
1257 SmCaretPos PosAfterEdit,
1258 SmNode* pStartLine) {
1259 //Store number of nodes in line for later
1260 int entries = pLineList->size();
1262 //Parse list of nodes to a tree
1263 SmNodeListParser parser;
1264 std::unique_ptr<SmNode> pLine(parser.Parse(pLineList.get()));
1265 pLineList.reset();
1267 //Check if we're making the body of a subsup node bigger than one
1268 if(pParent->GetType() == SmNodeType::SubSup &&
1269 nParentIndex == 0 &&
1270 entries > 1) {
1271 //Wrap pLine in scalable round brackets
1272 SmToken aTok(TLEFT, '\0', u"left"_ustr, TG::NONE, 5);
1273 std::unique_ptr<SmBraceNode> pBrace(new SmBraceNode(aTok));
1274 pBrace->SetScaleMode(SmScaleMode::Height);
1275 std::unique_ptr<SmNode> pLeft( CreateBracket(SmBracketType::Round, true) ),
1276 pRight( CreateBracket(SmBracketType::Round, false) );
1277 std::unique_ptr<SmBracebodyNode> pBody(new SmBracebodyNode(SmToken()));
1278 pBody->SetSubNodes(std::move(pLine), nullptr);
1279 pBrace->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight));
1280 pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
1281 pLine = std::move(pBrace);
1282 //TODO: Consider the following alternative behavior:
1283 //Consider the line: A + {B + C}^D lsub E
1284 //Here pLineList is B, + and C and pParent is a subsup node with
1285 //both RSUP and LSUB set. Imagine the user just inserted "B +" in
1286 //the body of the subsup node...
1287 //The most natural thing to do would be to make the line like this:
1288 //A + B lsub E + C ^ D
1289 //E.g. apply LSUB and LSUP to the first element in pLineList and RSUP
1290 //and RSUB to the last element in pLineList. But how should this act
1291 //for CSUP and CSUB ???
1292 //For this reason and because brackets was faster to implement, this solution
1293 //have been chosen. It might be worth working on the other solution later...
1296 //Set pStartLine if NULL
1297 if(!pStartLine)
1298 pStartLine = pLine.get();
1300 //Insert it back into the parent
1301 pParent->SetSubNode(nParentIndex, pLine.release());
1303 //Rebuild graph of caret position
1304 mpAnchor = nullptr;
1305 mpPosition = nullptr;
1306 BuildGraph();
1307 AnnotateSelection(); //Update selection annotation!
1309 //Set caret position
1310 if(!SetCaretPosition(PosAfterEdit))
1311 SetCaretPosition(SmCaretPos(pStartLine, 0));
1313 //End edit section
1314 EndEdit();
1317 void SmCursor::BeginEdit(){
1318 if(mnEditSections++ > 0) return;
1320 mbIsEnabledSetModifiedSmDocShell = mpDocShell->IsEnableSetModified();
1321 if( mbIsEnabledSetModifiedSmDocShell )
1322 mpDocShell->EnableSetModified( false );
1325 void SmCursor::EndEdit(){
1326 if(--mnEditSections > 0) return;
1328 mpDocShell->SetFormulaArranged(false);
1329 //Okay, I don't know what this does... :)
1330 //It's used in SmDocShell::SetText and with places where everything is modified.
1331 //I think it does some magic, with sfx, but everything is totally undocumented so
1332 //it's kinda hard to tell...
1333 if ( mbIsEnabledSetModifiedSmDocShell )
1334 mpDocShell->EnableSetModified( mbIsEnabledSetModifiedSmDocShell );
1335 //I think this notifies people around us that we've modified this document...
1336 mpDocShell->SetModified();
1337 //I think SmDocShell uses this value when it sends an update graphics event
1338 //Anyway comments elsewhere suggests it needs to be updated...
1339 mpDocShell->mnModifyCount++;
1341 //TODO: Consider copying the update accessibility code from SmDocShell::SetText in here...
1342 //This somehow updates the size of SmGraphicView if it is running in embedded mode
1343 if( mpDocShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED )
1344 mpDocShell->OnDocumentPrinterChanged(nullptr);
1346 //Request a repaint...
1347 RequestRepaint();
1349 //Update the edit engine and text of the document
1350 OUString formula;
1351 SmNodeToTextVisitor(mpTree, formula);
1352 mpDocShell->maText = formula;
1353 mpDocShell->GetEditEngine().QuickInsertText(formula, ESelection::All());
1354 mpDocShell->GetEditEngine().QuickFormatDoc();
1357 void SmCursor::RequestRepaint()
1359 if (SmViewShell *pViewSh = SmGetActiveView())
1361 if (comphelper::LibreOfficeKit::isActive())
1363 pViewSh->SendCaretToLOK();
1365 else if ( SfxObjectCreateMode::EMBEDDED == mpDocShell->GetCreateMode() )
1366 mpDocShell->Repaint();
1367 else
1368 pViewSh->GetGraphicWidget().Invalidate();
1372 bool SmCursor::IsAtTailOfBracket(SmBracketType eBracketType) const
1374 const SmCaretPos pos = GetPosition();
1375 if (!pos.IsValid()) {
1376 return false;
1379 SmNode* pNode = pos.pSelectedNode;
1381 if (pNode->GetType() == SmNodeType::Text) {
1382 SmTextNode* pTextNode = static_cast<SmTextNode*>(pNode);
1383 if (pos.nIndex < pTextNode->GetText().getLength()) {
1384 // The cursor is on a text node and at the middle of it.
1385 return false;
1387 } else {
1388 if (pos.nIndex < 1) {
1389 return false;
1393 while (true) {
1394 SmStructureNode* pParentNode = pNode->GetParent();
1395 if (!pParentNode) {
1396 // There's no brace body node in the ancestors.
1397 return false;
1400 int index = pParentNode->IndexOfSubNode(pNode);
1401 assert(index >= 0);
1402 if (static_cast<size_t>(index + 1) != pParentNode->GetNumSubNodes()) {
1403 // The cursor is not at the tail at one of ancestor nodes.
1404 return false;
1407 pNode = pParentNode;
1408 if (pNode->GetType() == SmNodeType::Bracebody) {
1409 // Found the brace body node.
1410 break;
1414 SmStructureNode* pBraceNodeTmp = pNode->GetParent();
1415 if (!pBraceNodeTmp || pBraceNodeTmp->GetType() != SmNodeType::Brace) {
1416 // Brace node is invalid.
1417 return false;
1420 SmBraceNode* pBraceNode = static_cast<SmBraceNode*>(pBraceNodeTmp);
1421 SmMathSymbolNode* pClosingNode = pBraceNode->ClosingBrace();
1422 if (!pClosingNode) {
1423 // Couldn't get closing symbol node.
1424 return false;
1427 // Check if the closing brace matches eBracketType.
1428 SmTokenType eClosingTokenType = pClosingNode->GetToken().eType;
1429 switch (eBracketType) {
1430 case SmBracketType::Round: if (eClosingTokenType != TRPARENT) { return false; } break;
1431 case SmBracketType::Square: if (eClosingTokenType != TRBRACKET) { return false; } break;
1432 case SmBracketType::Curly: if (eClosingTokenType != TRBRACE) { return false; } break;
1433 default:
1434 return false;
1437 return true;
1440 /////////////////////////////////////// SmNodeListParser
1442 SmNode* SmNodeListParser::Parse(SmNodeList* list){
1443 pList = list;
1444 //Delete error nodes
1445 SmNodeList::iterator it = pList->begin();
1446 while(it != pList->end()) {
1447 if((*it)->GetType() == SmNodeType::Error){
1448 //Delete and erase
1449 delete *it;
1450 it = pList->erase(it);
1451 }else
1452 ++it;
1454 SmNode* retval = Expression();
1455 pList = nullptr;
1456 return retval;
1459 SmNode* SmNodeListParser::Expression(){
1460 SmNodeArray NodeArray;
1461 //Accept as many relations as there is
1462 while(Terminal())
1463 NodeArray.push_back(Relation());
1465 //Create SmExpressionNode, I hope SmToken() will do :)
1466 SmStructureNode* pExpr = new SmExpressionNode(SmToken());
1467 pExpr->SetSubNodes(std::move(NodeArray));
1468 return pExpr;
1471 SmNode* SmNodeListParser::Relation(){
1472 //Read a sum
1473 std::unique_ptr<SmNode> pLeft(Sum());
1474 //While we have tokens and the next is a relation
1475 while(Terminal() && IsRelationOperator(Terminal()->GetToken())){
1476 //Take the operator
1477 std::unique_ptr<SmNode> pOper(Take());
1478 //Find the right side of the relation
1479 std::unique_ptr<SmNode> pRight(Sum());
1480 //Create new SmBinHorNode
1481 std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken()));
1482 pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight));
1483 pLeft = std::move(pNewNode);
1485 return pLeft.release();
1488 SmNode* SmNodeListParser::Sum(){
1489 //Read a product
1490 std::unique_ptr<SmNode> pLeft(Product());
1491 //While we have tokens and the next is a sum
1492 while(Terminal() && IsSumOperator(Terminal()->GetToken())){
1493 //Take the operator
1494 std::unique_ptr<SmNode> pOper(Take());
1495 //Find the right side of the sum
1496 std::unique_ptr<SmNode> pRight(Product());
1497 //Create new SmBinHorNode
1498 std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken()));
1499 pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight));
1500 pLeft = std::move(pNewNode);
1502 return pLeft.release();
1505 SmNode* SmNodeListParser::Product(){
1506 //Read a Factor
1507 std::unique_ptr<SmNode> pLeft(Factor());
1508 //While we have tokens and the next is a product
1509 while(Terminal() && IsProductOperator(Terminal()->GetToken())){
1510 //Take the operator
1511 std::unique_ptr<SmNode> pOper(Take());
1512 //Find the right side of the operation
1513 std::unique_ptr<SmNode> pRight(Factor());
1514 //Create new SmBinHorNode
1515 std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken()));
1516 pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight));
1517 pLeft = std::move(pNewNode);
1519 return pLeft.release();
1522 SmNode* SmNodeListParser::Factor(){
1523 //Read unary operations
1524 if(!Terminal())
1525 return Error();
1526 //Take care of unary operators
1527 else if(IsUnaryOperator(Terminal()->GetToken()))
1529 SmStructureNode *pUnary = new SmUnHorNode(SmToken());
1530 std::unique_ptr<SmNode> pOper(Terminal()),
1531 pArg;
1533 if(Next())
1534 pArg.reset(Factor());
1535 else
1536 pArg.reset(Error());
1538 pUnary->SetSubNodes(std::move(pOper), std::move(pArg));
1539 return pUnary;
1541 return Postfix();
1544 SmNode* SmNodeListParser::Postfix(){
1545 if(!Terminal())
1546 return Error();
1547 std::unique_ptr<SmNode> pArg;
1548 if(IsPostfixOperator(Terminal()->GetToken()))
1549 pArg.reset(Error());
1550 else if(IsOperator(Terminal()->GetToken()))
1551 return Error();
1552 else
1553 pArg.reset(Take());
1554 while(Terminal() && IsPostfixOperator(Terminal()->GetToken())) {
1555 std::unique_ptr<SmStructureNode> pUnary(new SmUnHorNode(SmToken()) );
1556 std::unique_ptr<SmNode> pOper(Take());
1557 pUnary->SetSubNodes(std::move(pArg), std::move(pOper));
1558 pArg = std::move(pUnary);
1560 return pArg.release();
1563 SmNode* SmNodeListParser::Error(){
1564 return new SmErrorNode(SmToken());
1567 bool SmNodeListParser::IsOperator(const SmToken &token) {
1568 return IsRelationOperator(token) ||
1569 IsSumOperator(token) ||
1570 IsProductOperator(token) ||
1571 IsUnaryOperator(token) ||
1572 IsPostfixOperator(token);
1575 bool SmNodeListParser::IsRelationOperator(const SmToken &token) {
1576 return bool(token.nGroup & TG::Relation);
1579 bool SmNodeListParser::IsSumOperator(const SmToken &token) {
1580 return bool(token.nGroup & TG::Sum);
1583 bool SmNodeListParser::IsProductOperator(const SmToken &token) {
1584 return token.nGroup & TG::Product &&
1585 token.eType != TWIDESLASH &&
1586 token.eType != TWIDEBACKSLASH &&
1587 token.eType != TUNDERBRACE &&
1588 token.eType != TOVERBRACE &&
1589 token.eType != TOVER;
1592 bool SmNodeListParser::IsUnaryOperator(const SmToken &token) {
1593 return token.nGroup & TG::UnOper &&
1594 (token.eType == TPLUS ||
1595 token.eType == TMINUS ||
1596 token.eType == TPLUSMINUS ||
1597 token.eType == TMINUSPLUS ||
1598 token.eType == TNEG ||
1599 token.eType == TUOPER);
1602 bool SmNodeListParser::IsPostfixOperator(const SmToken &token) {
1603 return token.eType == TFACT;
1606 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */