bump product version to 6.1.0.2
[LibreOffice.git] / starmath / source / cursor.cxx
blob1d0e987e02cccb6c826a02a7d05f484229d7ead3
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 <memory>
10 #include <cursor.hxx>
11 #include <visitors.hxx>
12 #include <document.hxx>
13 #include <view.hxx>
14 #include <comphelper/string.hxx>
15 #include <editeng/editeng.hxx>
16 #include <cassert>
18 void SmCursor::Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor){
19 SmCaretPosGraphEntry* NewPos = nullptr;
20 switch(direction)
22 case MoveLeft:
23 if (mpPosition)
24 NewPos = mpPosition->Left;
25 OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!");
26 break;
27 case MoveRight:
28 if (mpPosition)
29 NewPos = mpPosition->Right;
30 OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!");
31 break;
32 case MoveUp:
33 //Implementation is practically identical to MoveDown, except for a single if statement
34 //so I've implemented them together and added a direction == MoveDown to the if statements.
35 case MoveDown:
36 if (mpPosition)
38 SmCaretLine from_line = SmCaretPos2LineVisitor(pDev, mpPosition->CaretPos).GetResult(),
39 best_line, //Best approximated line found so far
40 curr_line; //Current line
41 long dbp_sq = 0; //Distance squared to best line
42 for(auto &pEntry : *mpGraph)
44 //Reject it if it's the current position
45 if(pEntry->CaretPos == mpPosition->CaretPos) continue;
46 //Compute caret line
47 curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult();
48 //Reject anything above if we're moving down
49 if(curr_line.GetTop() <= from_line.GetTop() && direction == MoveDown) continue;
50 //Reject anything below if we're moving up
51 if(curr_line.GetTop() + curr_line.GetHeight() >= from_line.GetTop() + from_line.GetHeight()
52 && direction == MoveUp) continue;
53 //Compare if it to what we have, if we have anything yet
54 if(NewPos){
55 //Compute distance to current line squared, multiplied with a horizontal factor
56 long dp_sq = curr_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
57 curr_line.SquaredDistanceY(from_line);
58 //Discard current line if best line is closer
59 if(dbp_sq <= dp_sq) continue;
61 //Take current line as the best
62 best_line = curr_line;
63 NewPos = pEntry.get();
64 //Update distance to best line
65 dbp_sq = best_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
66 best_line.SquaredDistanceY(from_line);
69 break;
70 default:
71 assert(false);
73 if(NewPos){
74 mpPosition = NewPos;
75 if(bMoveAnchor)
76 mpAnchor = NewPos;
77 RequestRepaint();
81 void SmCursor::MoveTo(OutputDevice* pDev, const Point& pos, bool bMoveAnchor)
83 SmCaretPosGraphEntry* NewPos = nullptr;
84 long dp_sq = 0, //Distance to current line squared
85 dbp_sq = 1; //Distance to best line squared
86 for(auto &pEntry : *mpGraph)
88 OSL_ENSURE(pEntry->CaretPos.IsValid(), "The caret position graph may not have invalid positions!");
89 //Compute current line
90 SmCaretLine curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult();
91 //Compute squared distance to current line
92 dp_sq = curr_line.SquaredDistanceX(pos) + curr_line.SquaredDistanceY(pos);
93 //If we have a position compare to it
94 if(NewPos){
95 //If best line is closer, reject current line
96 if(dbp_sq <= dp_sq) continue;
98 //Accept current position as the best
99 NewPos = pEntry.get();
100 //Update distance to best line
101 dbp_sq = dp_sq;
103 if(NewPos){
104 mpPosition = NewPos;
105 if(bMoveAnchor)
106 mpAnchor = NewPos;
107 RequestRepaint();
111 void SmCursor::BuildGraph(){
112 //Save the current anchor and position
113 SmCaretPos _anchor, _position;
114 //Release mpGraph if allocated
115 if(mpGraph){
116 if(mpAnchor)
117 _anchor = mpAnchor->CaretPos;
118 if(mpPosition)
119 _position = mpPosition->CaretPos;
120 mpGraph.reset();
121 //Reset anchor and position as they point into an old graph
122 mpAnchor = nullptr;
123 mpPosition = nullptr;
126 //Build the new graph
127 mpGraph.reset(SmCaretPosGraphBuildingVisitor(mpTree).takeGraph());
129 //Restore anchor and position pointers
130 if(_anchor.IsValid() || _position.IsValid()){
131 for(auto &pEntry : *mpGraph)
133 if(_anchor == pEntry->CaretPos)
134 mpAnchor = pEntry.get();
135 if(_position == pEntry->CaretPos)
136 mpPosition = pEntry.get();
139 //Set position and anchor to first caret position
140 auto it = mpGraph->begin();
141 assert(it != mpGraph->end());
142 if(!mpPosition)
143 mpPosition = it->get();
144 if(!mpAnchor)
145 mpAnchor = mpPosition;
147 assert(mpPosition);
148 assert(mpAnchor);
149 OSL_ENSURE(mpPosition->CaretPos.IsValid(), "Position must be valid");
150 OSL_ENSURE(mpAnchor->CaretPos.IsValid(), "Anchor must be valid");
153 bool SmCursor::SetCaretPosition(SmCaretPos pos){
154 for(auto &pEntry : *mpGraph)
156 if(pEntry->CaretPos == pos)
158 mpPosition = pEntry.get();
159 mpAnchor = pEntry.get();
160 return true;
163 return false;
166 void SmCursor::AnnotateSelection(){
167 //TODO: Manage a state, reset it upon modification and optimize this call
168 SmSetSelectionVisitor(mpAnchor->CaretPos, mpPosition->CaretPos, mpTree);
171 void SmCursor::Draw(OutputDevice& pDev, Point Offset, bool isCaretVisible){
172 SmCaretDrawingVisitor(pDev, GetPosition(), Offset, isCaretVisible);
175 void SmCursor::DeletePrev(OutputDevice* pDev){
176 //Delete only a selection if there's a selection
177 if(HasSelection()){
178 Delete();
179 return;
182 SmNode* pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
183 SmStructureNode* pLineParent = pLine->GetParent();
184 int nLineOffsetIdx = pLineParent->IndexOfSubNode(pLine);
185 assert(nLineOffsetIdx >= 0);
187 //If we're in front of a node who's parent is a TABLE
188 if (pLineParent->GetType() == SmNodeType::Table && mpPosition->CaretPos.nIndex == 0 && nLineOffsetIdx > 0)
190 size_t nLineOffset = nLineOffsetIdx;
191 //Now we can merge with nLineOffset - 1
192 BeginEdit();
193 //Line to merge things into, so we can delete pLine
194 SmNode* pMergeLine = pLineParent->GetSubNode(nLineOffset-1);
195 OSL_ENSURE(pMergeLine, "pMergeLine cannot be NULL!");
196 SmCaretPos PosAfterDelete;
197 //Convert first line to list
198 SmNodeList *pLineList = NodeToList(pMergeLine);
199 if(!pLineList->empty()){
200 //Find iterator to patch
201 SmNodeList::iterator patchPoint = pLineList->end();
202 --patchPoint;
203 //Convert second line to list
204 NodeToList(pLine, pLineList);
205 //Patch the line list
206 ++patchPoint;
207 PosAfterDelete = PatchLineList(pLineList, patchPoint);
208 //Parse the line
209 pLine = SmNodeListParser().Parse(pLineList);
211 delete pLineList;
212 pLineParent->SetSubNode(nLineOffset-1, pLine);
213 //Delete the removed line slot
214 SmNodeArray lines(pLineParent->GetNumSubNodes()-1);
215 for (size_t i = 0; i < pLineParent->GetNumSubNodes(); ++i)
217 if(i < nLineOffset)
218 lines[i] = pLineParent->GetSubNode(i);
219 else if(i > nLineOffset)
220 lines[i-1] = pLineParent->GetSubNode(i);
222 pLineParent->SetSubNodes(std::move(lines));
223 //Rebuild graph
224 mpAnchor = nullptr;
225 mpPosition = nullptr;
226 BuildGraph();
227 AnnotateSelection();
228 //Set caret position
229 if(!SetCaretPosition(PosAfterDelete))
230 SetCaretPosition(SmCaretPos(pLine, 0));
231 //Finish editing
232 EndEdit();
234 //TODO: If we're in an empty (sub/super/*) script
235 /*}else if(pLineParent->GetType() == SmNodeType::SubSup &&
236 nLineOffset != 0 &&
237 pLine->GetType() == SmNodeType::Expression &&
238 pLine->GetNumSubNodes() == 0){
239 //There's a (sub/super) script we can delete
240 //Consider selecting the entire script if GetNumSubNodes() != 0 or pLine->GetType() != SmNodeType::Expression
241 //TODO: Handle case where we delete a limit
244 //Else move select, and delete if not complex
245 }else{
246 Move(pDev, MoveLeft, false);
247 if(!HasComplexSelection())
248 Delete();
252 void SmCursor::Delete(){
253 //Return if we don't have a selection to delete
254 if(!HasSelection())
255 return;
257 //Enter edit section
258 BeginEdit();
260 //Set selected on nodes
261 AnnotateSelection();
263 //Find an arbitrary selected node
264 SmNode* pSNode = FindSelectedNode(mpTree);
265 assert(pSNode);
267 //Find the topmost node of the line that holds the selection
268 SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
269 OSL_ENSURE(pLine != mpTree, "Shouldn't be able to select the entire tree");
271 //Get the parent of the line
272 SmStructureNode* pLineParent = pLine->GetParent();
273 //Find line offset in parent
274 int nLineOffset = pLineParent->IndexOfSubNode(pLine);
275 assert(nLineOffset >= 0);
277 //Position after delete
278 SmCaretPos PosAfterDelete;
280 SmNodeList* pLineList = NodeToList(pLine);
282 //Take the selected nodes and delete them...
283 SmNodeList::iterator patchIt = TakeSelectedNodesFromList(pLineList);
285 //Get the position to set after delete
286 PosAfterDelete = PatchLineList(pLineList, patchIt);
288 //Finish editing
289 FinishEdit(pLineList, pLineParent, nLineOffset, PosAfterDelete);
292 void SmCursor::InsertNodes(SmNodeList* pNewNodes){
293 if(pNewNodes->empty()){
294 delete pNewNodes;
295 return;
298 //Begin edit section
299 BeginEdit();
301 //Get the current position
302 const SmCaretPos pos = mpPosition->CaretPos;
304 //Find top most of line that holds position
305 SmNode* pLine = FindTopMostNodeInLine(pos.pSelectedNode);
307 //Find line parent and line index in parent
308 SmStructureNode* pLineParent = pLine->GetParent();
309 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
310 assert(nParentIndex >= 0);
312 //Convert line to list
313 SmNodeList* pLineList = NodeToList(pLine);
315 //Find iterator for place to insert nodes
316 SmNodeList::iterator it = FindPositionInLineList(pLineList, pos);
318 //Insert all new nodes
319 SmNodeList::iterator newIt,
320 patchIt = it, // (pointless default value, fixes compiler warnings)
321 insIt;
322 for(newIt = pNewNodes->begin(); newIt != pNewNodes->end(); ++newIt){
323 insIt = pLineList->insert(it, *newIt);
324 if(newIt == pNewNodes->begin())
325 patchIt = insIt;
327 //Patch the places we've changed stuff
328 PatchLineList(pLineList, patchIt);
329 SmCaretPos PosAfterInsert = PatchLineList(pLineList, it);
330 //Release list, we've taken the nodes
331 delete pNewNodes;
332 pNewNodes = nullptr;
334 //Finish editing
335 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
338 SmNodeList::iterator SmCursor::FindPositionInLineList(SmNodeList* pLineList,
339 const SmCaretPos& rCaretPos)
341 //Find iterator for position
342 SmNodeList::iterator it;
343 for(it = pLineList->begin(); it != pLineList->end(); ++it){
344 if(*it == rCaretPos.pSelectedNode)
346 if((*it)->GetType() == SmNodeType::Text)
348 //Split textnode if needed
349 if(rCaretPos.nIndex > 0)
351 SmTextNode* pText = static_cast<SmTextNode*>(rCaretPos.pSelectedNode);
352 if (rCaretPos.nIndex == pText->GetText().getLength())
353 return ++it;
354 OUString str1 = pText->GetText().copy(0, rCaretPos.nIndex);
355 OUString str2 = pText->GetText().copy(rCaretPos.nIndex);
356 pText->ChangeText(str1);
357 ++it;
358 //Insert str2 as new text node
359 assert(!str2.isEmpty());
360 SmTextNode* pNewText = new SmTextNode(pText->GetToken(), pText->GetFontDesc());
361 pNewText->ChangeText(str2);
362 it = pLineList->insert(it, pNewText);
364 }else
365 ++it;
366 //it now pointer to the node following pos, so pLineList->insert(it, ...) will insert correctly
367 return it;
371 //If we didn't find pSelectedNode, it must be because the caret is in front of the line
372 return pLineList->begin();
375 SmCaretPos SmCursor::PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter) {
376 //The nodes we should consider merging
377 SmNode *prev = nullptr,
378 *next = nullptr;
379 if(aIter != pLineList->end())
380 next = *aIter;
381 if(aIter != pLineList->begin()) {
382 --aIter;
383 prev = *aIter;
384 ++aIter;
387 //Check if there's textnodes to merge
388 if( prev &&
389 next &&
390 prev->GetType() == SmNodeType::Text &&
391 next->GetType() == SmNodeType::Text &&
392 ( prev->GetToken().eType != TNUMBER ||
393 next->GetToken().eType == TNUMBER) ){
394 SmTextNode *pText = static_cast<SmTextNode*>(prev),
395 *pOldN = static_cast<SmTextNode*>(next);
396 SmCaretPos retval(pText, pText->GetText().getLength());
397 OUString newText;
398 newText += pText->GetText();
399 newText += pOldN->GetText();
400 pText->ChangeText(newText);
401 delete pOldN;
402 pLineList->erase(aIter);
403 return retval;
406 //Check if there's a SmPlaceNode to remove:
407 if(prev && next && prev->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(next->GetToken())){
408 --aIter;
409 aIter = pLineList->erase(aIter);
410 delete prev;
411 //Return caret pos in front of aIter
412 if(aIter != pLineList->begin())
413 --aIter; //Thus find node before aIter
414 if(aIter == pLineList->begin())
415 return SmCaretPos();
416 return SmCaretPos::GetPosAfter(*aIter);
418 if(prev && next && next->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(prev->GetToken())){
419 aIter = pLineList->erase(aIter);
420 delete next;
421 return SmCaretPos::GetPosAfter(prev);
424 //If we didn't do anything return
425 if(!prev) //return an invalid to indicate we're in front of line
426 return SmCaretPos();
427 return SmCaretPos::GetPosAfter(prev);
430 SmNodeList::iterator SmCursor::TakeSelectedNodesFromList(SmNodeList *pLineList,
431 SmNodeList *pSelectedNodes) {
432 SmNodeList::iterator retval;
433 SmNodeList::iterator it = pLineList->begin();
434 while(it != pLineList->end()){
435 if((*it)->IsSelected()){
436 //Split text nodes
437 if((*it)->GetType() == SmNodeType::Text) {
438 SmTextNode* pText = static_cast<SmTextNode*>(*it);
439 OUString aText = pText->GetText();
440 //Start and lengths of the segments, 2 is the selected segment
441 int start2 = pText->GetSelectionStart(),
442 start3 = pText->GetSelectionEnd(),
443 len1 = start2 - 0,
444 len2 = start3 - start2,
445 len3 = aText.getLength() - start3;
446 SmToken aToken = pText->GetToken();
447 sal_uInt16 eFontDesc = pText->GetFontDesc();
448 //If we need make segment 1
449 if(len1 > 0) {
450 OUString str = aText.copy(0, len1);
451 pText->ChangeText(str);
452 ++it;
453 } else {//Remove it if not needed
454 it = pLineList->erase(it);
455 delete pText;
457 //Set retval to be right after the selection
458 retval = it;
459 //if we need make segment 3
460 if(len3 > 0) {
461 OUString str = aText.copy(start3, len3);
462 SmTextNode* pSeg3 = new SmTextNode(aToken, eFontDesc);
463 pSeg3->ChangeText(str);
464 retval = pLineList->insert(it, pSeg3);
466 //If we need to save the selected text
467 if(pSelectedNodes && len2 > 0) {
468 OUString str = aText.copy(start2, len2);
469 SmTextNode* pSeg2 = new SmTextNode(aToken, eFontDesc);
470 pSeg2->ChangeText(str);
471 pSelectedNodes->push_back(pSeg2);
473 } else { //if it's not textnode
474 SmNode* pNode = *it;
475 retval = it = pLineList->erase(it);
476 if(pSelectedNodes)
477 pSelectedNodes->push_back(pNode);
478 else
479 delete pNode;
481 } else
482 ++it;
484 return retval;
487 void SmCursor::InsertSubSup(SmSubSup eSubSup) {
488 AnnotateSelection();
490 //Find line
491 SmNode *pLine;
492 if(HasSelection()) {
493 SmNode *pSNode = FindSelectedNode(mpTree);
494 assert(pSNode);
495 pLine = FindTopMostNodeInLine(pSNode, true);
496 } else
497 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
499 //Find Parent and offset in parent
500 SmStructureNode *pLineParent = pLine->GetParent();
501 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
502 assert(nParentIndex >= 0);
504 //TODO: Consider handling special cases where parent is an SmOperNode,
505 // Maybe this method should be able to add limits to an SmOperNode...
507 //We begin modifying the tree here
508 BeginEdit();
510 //Convert line to list
511 SmNodeList* pLineList = NodeToList(pLine);
513 //Take the selection, and/or find iterator for current position
514 SmNodeList* pSelectedNodesList = new SmNodeList;
515 SmNodeList::iterator it;
516 if(HasSelection())
517 it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
518 else
519 it = FindPositionInLineList(pLineList, mpPosition->CaretPos);
521 //Find node that this should be applied to
522 SmNode* pSubject;
523 bool bPatchLine = !pSelectedNodesList->empty(); //If the line should be patched later
524 if(it != pLineList->begin()) {
525 --it;
526 pSubject = *it;
527 ++it;
528 } else {
529 //Create a new place node
530 pSubject = new SmPlaceNode();
531 pSubject->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
532 it = pLineList->insert(it, pSubject);
533 ++it;
534 bPatchLine = true; //We've modified the line it should be patched later.
537 //Wrap the subject in a SmSubSupNode
538 SmSubSupNode* pSubSup;
539 if(pSubject->GetType() != SmNodeType::SubSup){
540 SmToken token;
541 token.nGroup = TG::Power;
542 pSubSup = new SmSubSupNode(token);
543 pSubSup->SetBody(pSubject);
544 *(--it) = pSubSup;
545 ++it;
546 }else
547 pSubSup = static_cast<SmSubSupNode*>(pSubject);
548 //pSubject shouldn't be referenced anymore, pSubSup is the SmSubSupNode in pLineList we wish to edit.
549 //and it pointer to the element following pSubSup in pLineList.
550 pSubject = nullptr;
552 //Patch the line if we noted that was needed previously
553 if(bPatchLine)
554 PatchLineList(pLineList, it);
556 //Convert existing, if any, sub-/superscript line to list
557 SmNode *pScriptLine = pSubSup->GetSubSup(eSubSup);
558 SmNodeList* pScriptLineList = NodeToList(pScriptLine);
560 //Add selection to pScriptLineList
561 unsigned int nOldSize = pScriptLineList->size();
562 pScriptLineList->insert(pScriptLineList->end(), pSelectedNodesList->begin(), pSelectedNodesList->end());
563 delete pSelectedNodesList;
564 pSelectedNodesList = nullptr;
566 //Patch pScriptLineList if needed
567 if(0 < nOldSize && nOldSize < pScriptLineList->size()) {
568 SmNodeList::iterator iPatchPoint = pScriptLineList->begin();
569 std::advance(iPatchPoint, nOldSize);
570 PatchLineList(pScriptLineList, iPatchPoint);
573 //Find caret pos, that should be used after sub-/superscription.
574 SmCaretPos PosAfterScript; //Leave invalid for first position
575 if (!pScriptLineList->empty())
576 PosAfterScript = SmCaretPos::GetPosAfter(pScriptLineList->back());
578 //Parse pScriptLineList
579 pScriptLine = SmNodeListParser().Parse(pScriptLineList);
580 delete pScriptLineList;
581 pScriptLineList = nullptr;
583 //Insert pScriptLine back into the tree
584 pSubSup->SetSubSup(eSubSup, pScriptLine);
586 //Finish editing
587 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterScript, pScriptLine);
590 void SmCursor::InsertBrackets(SmBracketType eBracketType) {
591 BeginEdit();
593 AnnotateSelection();
595 //Find line
596 SmNode *pLine;
597 if(HasSelection()) {
598 SmNode *pSNode = FindSelectedNode(mpTree);
599 assert(pSNode);
600 pLine = FindTopMostNodeInLine(pSNode, true);
601 } else
602 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
604 //Find parent and offset in parent
605 SmStructureNode *pLineParent = pLine->GetParent();
606 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
607 assert(nParentIndex >= 0);
609 //Convert line to list
610 SmNodeList *pLineList = NodeToList(pLine);
612 //Take the selection, and/or find iterator for current position
613 SmNodeList *pSelectedNodesList = new SmNodeList;
614 SmNodeList::iterator it;
615 if(HasSelection())
616 it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
617 else
618 it = FindPositionInLineList(pLineList, mpPosition->CaretPos);
620 //If there's no selected nodes, create a place node
621 SmNode *pBodyNode;
622 SmCaretPos PosAfterInsert;
623 if(pSelectedNodesList->empty()) {
624 pBodyNode = new SmPlaceNode();
625 PosAfterInsert = SmCaretPos(pBodyNode, 1);
626 } else
627 pBodyNode = SmNodeListParser().Parse(pSelectedNodesList);
629 delete pSelectedNodesList;
631 //Create SmBraceNode
632 SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5);
633 SmBraceNode *pBrace = new SmBraceNode(aTok);
634 pBrace->SetScaleMode(SmScaleMode::Height);
635 SmNode *pLeft = CreateBracket(eBracketType, true),
636 *pRight = CreateBracket(eBracketType, false);
637 SmBracebodyNode *pBody = new SmBracebodyNode(SmToken());
638 pBody->SetSubNodes(pBodyNode, nullptr);
639 pBrace->SetSubNodes(pLeft, pBody, pRight);
640 pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
642 //Insert into line
643 pLineList->insert(it, pBrace);
644 //Patch line (I think this is good enough)
645 SmCaretPos aAfter = PatchLineList(pLineList, it);
646 if( !PosAfterInsert.IsValid() )
647 PosAfterInsert = aAfter;
649 //Finish editing
650 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
653 SmNode *SmCursor::CreateBracket(SmBracketType eBracketType, bool bIsLeft) {
654 SmToken aTok;
655 if(bIsLeft){
656 switch(eBracketType){
657 case SmBracketType::Round:
658 aTok = SmToken(TLPARENT, MS_LPARENT, "(", TG::LBrace, 5);
659 break;
660 case SmBracketType::Square:
661 aTok = SmToken(TLBRACKET, MS_LBRACKET, "[", TG::LBrace, 5);
662 break;
663 case SmBracketType::Curly:
664 aTok = SmToken(TLBRACE, MS_LBRACE, "lbrace", TG::LBrace, 5);
665 break;
667 } else {
668 switch(eBracketType) {
669 case SmBracketType::Round:
670 aTok = SmToken(TRPARENT, MS_RPARENT, ")", TG::RBrace, 5);
671 break;
672 case SmBracketType::Square:
673 aTok = SmToken(TRBRACKET, MS_RBRACKET, "]", TG::RBrace, 5);
674 break;
675 case SmBracketType::Curly:
676 aTok = SmToken(TRBRACE, MS_RBRACE, "rbrace", TG::RBrace, 5);
677 break;
680 SmNode* pRetVal = new SmMathSymbolNode(aTok);
681 pRetVal->SetScaleMode(SmScaleMode::Height);
682 return pRetVal;
685 bool SmCursor::InsertRow() {
686 AnnotateSelection();
688 //Find line
689 SmNode *pLine;
690 if(HasSelection()) {
691 SmNode *pSNode = FindSelectedNode(mpTree);
692 assert(pSNode);
693 pLine = FindTopMostNodeInLine(pSNode, true);
694 } else
695 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
697 //Find parent and offset in parent
698 SmStructureNode *pLineParent = pLine->GetParent();
699 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
700 assert(nParentIndex >= 0);
702 //Discover the context of this command
703 SmTableNode *pTable = nullptr;
704 SmMatrixNode *pMatrix = nullptr;
705 int nTableIndex = nParentIndex;
706 if(pLineParent->GetType() == SmNodeType::Table)
707 pTable = static_cast<SmTableNode*>(pLineParent);
708 //If it's wrapped in a SmLineNode, we can still insert a newline
709 else if(pLineParent->GetType() == SmNodeType::Line &&
710 pLineParent->GetParent() &&
711 pLineParent->GetParent()->GetType() == SmNodeType::Table) {
712 //NOTE: This hack might give problems if we stop ignoring SmAlignNode
713 pTable = static_cast<SmTableNode*>(pLineParent->GetParent());
714 nTableIndex = pTable->IndexOfSubNode(pLineParent);
715 assert(nTableIndex >= 0);
717 if(pLineParent->GetType() == SmNodeType::Matrix)
718 pMatrix = static_cast<SmMatrixNode*>(pLineParent);
720 //If we're not in a context that supports InsertRow, return sal_False
721 if(!pTable && !pMatrix)
722 return false;
724 //Now we start editing
725 BeginEdit();
727 //Convert line to list
728 SmNodeList *pLineList = NodeToList(pLine);
730 //Find position in line
731 SmNodeList::iterator it;
732 if(HasSelection()) {
733 //Take the selected nodes and delete them...
734 it = TakeSelectedNodesFromList(pLineList);
735 } else
736 it = FindPositionInLineList(pLineList, mpPosition->CaretPos);
738 //New caret position after inserting the newline/row in whatever context
739 SmCaretPos PosAfterInsert;
741 //If we're in the context of a table
742 if(pTable) {
743 SmNodeList *pNewLineList = new SmNodeList;
744 //Move elements from pLineList to pNewLineList
745 pNewLineList->splice(pNewLineList->begin(), *pLineList, it, pLineList->end());
746 //Make sure it is valid again
747 it = pLineList->end();
748 if(it != pLineList->begin())
749 --it;
750 if(pNewLineList->empty())
751 pNewLineList->push_front(new SmPlaceNode());
752 //Parse new line
753 SmNode *pNewLine = SmNodeListParser().Parse(pNewLineList);
754 delete pNewLineList;
755 //Wrap pNewLine in SmLineNode if needed
756 if(pLineParent->GetType() == SmNodeType::Line) {
757 SmLineNode *pNewLineNode = new SmLineNode(SmToken(TNEWLINE, '\0', "newline"));
758 pNewLineNode->SetSubNodes(pNewLine, nullptr);
759 pNewLine = pNewLineNode;
761 //Get position
762 PosAfterInsert = SmCaretPos(pNewLine, 0);
763 //Move other nodes if needed
764 for( int i = pTable->GetNumSubNodes(); i > nTableIndex + 1; i--)
765 pTable->SetSubNode(i, pTable->GetSubNode(i-1));
767 //Insert new line
768 pTable->SetSubNode(nTableIndex + 1, pNewLine);
770 //Check if we need to change token type:
771 if(pTable->GetNumSubNodes() > 2 && pTable->GetToken().eType == TBINOM) {
772 SmToken tok = pTable->GetToken();
773 tok.eType = TSTACK;
774 pTable->SetToken(tok);
777 //If we're in the context of a matrix
778 else {
779 //Find position after insert and patch the list
780 PosAfterInsert = PatchLineList(pLineList, it);
781 //Move other children
782 sal_uInt16 rows = pMatrix->GetNumRows();
783 sal_uInt16 cols = pMatrix->GetNumCols();
784 int nRowStart = (nParentIndex - nParentIndex % cols) + cols;
785 for( int i = pMatrix->GetNumSubNodes() + cols - 1; i >= nRowStart + cols; i--)
786 pMatrix->SetSubNode(i, pMatrix->GetSubNode(i - cols));
787 for( int i = nRowStart; i < nRowStart + cols; i++) {
788 SmPlaceNode *pNewLine = new SmPlaceNode();
789 if(i == nParentIndex + cols)
790 PosAfterInsert = SmCaretPos(pNewLine, 0);
791 pMatrix->SetSubNode(i, pNewLine);
793 pMatrix->SetRowCol(rows + 1, cols);
796 //Finish editing
797 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
798 //FinishEdit is actually used to handle situations where parent is an instance of
799 //SmSubSupNode. In this case parent should always be a table or matrix, however, for
800 //code reuse we just use FinishEdit() here too.
801 return true;
804 void SmCursor::InsertFraction() {
805 AnnotateSelection();
807 //Find line
808 SmNode *pLine;
809 if(HasSelection()) {
810 SmNode *pSNode = FindSelectedNode(mpTree);
811 assert(pSNode);
812 pLine = FindTopMostNodeInLine(pSNode, true);
813 } else
814 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
816 //Find Parent and offset in parent
817 SmStructureNode *pLineParent = pLine->GetParent();
818 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
819 assert(nParentIndex >= 0);
821 //We begin modifying the tree here
822 BeginEdit();
824 //Convert line to list
825 SmNodeList* pLineList = NodeToList(pLine);
827 //Take the selection, and/or find iterator for current position
828 SmNodeList* pSelectedNodesList = new SmNodeList;
829 SmNodeList::iterator it;
830 if(HasSelection())
831 it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
832 else
833 it = FindPositionInLineList(pLineList, mpPosition->CaretPos);
835 //Create pNum, and pDenom
836 bool bEmptyFraction = pSelectedNodesList->empty();
837 SmNode *pNum = bEmptyFraction
838 ? new SmPlaceNode()
839 : SmNodeListParser().Parse(pSelectedNodesList);
840 SmNode *pDenom = new SmPlaceNode();
841 delete pSelectedNodesList;
842 pSelectedNodesList = nullptr;
844 //Create new fraction
845 SmBinVerNode *pFrac = new SmBinVerNode(SmToken(TOVER, '\0', "over", TG::Product, 0));
846 SmNode *pRect = new SmRectangleNode(SmToken());
847 pFrac->SetSubNodes(pNum, pRect, pDenom);
849 //Insert in pLineList
850 SmNodeList::iterator patchIt = pLineList->insert(it, pFrac);
851 PatchLineList(pLineList, patchIt);
852 PatchLineList(pLineList, it);
854 //Finish editing
855 SmNode *pSelectedNode = bEmptyFraction ? pNum : pDenom;
856 FinishEdit(pLineList, pLineParent, nParentIndex, SmCaretPos(pSelectedNode, 1));
859 void SmCursor::InsertText(const OUString& aString)
861 BeginEdit();
863 Delete();
865 SmToken token;
866 token.eType = TIDENT;
867 token.cMathChar = '\0';
868 token.nGroup = TG::NONE;
869 token.nLevel = 5;
870 token.aText = aString;
872 SmTextNode* pText = new SmTextNode(token, FNT_VARIABLE);
873 pText->SetText(aString);
874 pText->AdjustFontDesc();
875 pText->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
877 SmNodeList* pList = new SmNodeList;
878 pList->push_front(pText);
879 InsertNodes(pList);
881 EndEdit();
884 void SmCursor::InsertElement(SmFormulaElement element){
885 BeginEdit();
887 Delete();
889 //Create new node
890 SmNode* pNewNode = nullptr;
891 switch(element){
892 case BlankElement:
894 SmToken token;
895 token.eType = TBLANK;
896 token.nGroup = TG::Blank;
897 token.aText = "~";
898 SmBlankNode* pBlankNode = new SmBlankNode(token);
899 pBlankNode->IncreaseBy(token);
900 pNewNode = pBlankNode;
901 }break;
902 case FactorialElement:
904 SmToken token(TFACT, MS_FACT, "fact", TG::UnOper, 5);
905 pNewNode = new SmMathSymbolNode(token);
906 }break;
907 case PlusElement:
909 SmToken token;
910 token.eType = TPLUS;
911 token.cMathChar = MS_PLUS;
912 token.nGroup = TG::UnOper | TG::Sum;
913 token.nLevel = 5;
914 token.aText = "+";
915 pNewNode = new SmMathSymbolNode(token);
916 }break;
917 case MinusElement:
919 SmToken token;
920 token.eType = TMINUS;
921 token.cMathChar = MS_MINUS;
922 token.nGroup = TG::UnOper | TG::Sum;
923 token.nLevel = 5;
924 token.aText = "-";
925 pNewNode = new SmMathSymbolNode(token);
926 }break;
927 case CDotElement:
929 SmToken token;
930 token.eType = TCDOT;
931 token.cMathChar = MS_CDOT;
932 token.nGroup = TG::Product;
933 token.aText = "cdot";
934 pNewNode = new SmMathSymbolNode(token);
935 }break;
936 case EqualElement:
938 SmToken token;
939 token.eType = TASSIGN;
940 token.cMathChar = MS_ASSIGN;
941 token.nGroup = TG::Relation;
942 token.aText = "=";
943 pNewNode = new SmMathSymbolNode(token);
944 }break;
945 case LessThanElement:
947 SmToken token;
948 token.eType = TLT;
949 token.cMathChar = MS_LT;
950 token.nGroup = TG::Relation;
951 token.aText = "<";
952 pNewNode = new SmMathSymbolNode(token);
953 }break;
954 case GreaterThanElement:
956 SmToken token;
957 token.eType = TGT;
958 token.cMathChar = MS_GT;
959 token.nGroup = TG::Relation;
960 token.aText = ">";
961 pNewNode = new SmMathSymbolNode(token);
962 }break;
963 case PercentElement:
965 SmToken token;
966 token.eType = TTEXT;
967 token.cMathChar = MS_PERCENT;
968 token.nGroup = TG::NONE;
969 token.aText = "\"%\"";
970 pNewNode = new SmMathSymbolNode(token);
971 }break;
973 assert(pNewNode);
975 //Prepare the new node
976 pNewNode->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
978 //Insert new node
979 SmNodeList* pList = new SmNodeList;
980 pList->push_front(pNewNode);
981 InsertNodes(pList);
983 EndEdit();
986 void SmCursor::InsertSpecial(const OUString& _aString)
988 BeginEdit();
989 Delete();
991 OUString aString = comphelper::string::strip(_aString, ' ');
993 //Create instance of special node
994 SmToken token;
995 token.eType = TSPECIAL;
996 token.cMathChar = '\0';
997 token.nGroup = TG::NONE;
998 token.nLevel = 5;
999 token.aText = aString;
1000 SmSpecialNode* pSpecial = new SmSpecialNode(token);
1002 //Prepare the special node
1003 pSpecial->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
1005 //Insert the node
1006 SmNodeList* pList = new SmNodeList;
1007 pList->push_front(pSpecial);
1008 InsertNodes(pList);
1010 EndEdit();
1013 void SmCursor::InsertCommandText(const OUString& aCommandText) {
1014 //Parse the sub expression
1015 auto xSubExpr = SmParser().ParseExpression(aCommandText);
1017 //Prepare the subtree
1018 xSubExpr->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
1020 //Convert subtree to list
1021 SmNode* pSubExpr = xSubExpr.release();
1022 SmNodeList* pLineList = NodeToList(pSubExpr);
1024 BeginEdit();
1026 //Delete any selection
1027 Delete();
1029 //Insert it
1030 InsertNodes(pLineList);
1032 EndEdit();
1035 void SmCursor::Copy(){
1036 if(!HasSelection())
1037 return;
1039 AnnotateSelection();
1040 //Find selected node
1041 SmNode* pSNode = FindSelectedNode(mpTree);
1042 assert(pSNode);
1043 //Find visual line
1044 SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
1045 assert(pLine);
1047 //Clone selected nodes
1048 SmClipboard aClipboard;
1049 if(IsLineCompositionNode(pLine))
1050 CloneLineToClipboard(static_cast<SmStructureNode*>(pLine), &aClipboard);
1051 else{
1052 //Special care to only clone selected text
1053 if(pLine->GetType() == SmNodeType::Text) {
1054 SmTextNode *pText = static_cast<SmTextNode*>(pLine);
1055 std::unique_ptr<SmTextNode> pClone(new SmTextNode( pText->GetToken(), pText->GetFontDesc() ));
1056 int start = pText->GetSelectionStart(),
1057 length = pText->GetSelectionEnd() - pText->GetSelectionStart();
1058 pClone->ChangeText(pText->GetText().copy(start, length));
1059 pClone->SetScaleMode(pText->GetScaleMode());
1060 aClipboard.push_front(std::move(pClone));
1061 } else {
1062 SmCloningVisitor aCloneFactory;
1063 aClipboard.push_front(std::unique_ptr<SmNode>(aCloneFactory.Clone(pLine)));
1067 //Set clipboard
1068 if (!aClipboard.empty())
1069 maClipboard = std::move(aClipboard);
1072 void SmCursor::Paste() {
1073 BeginEdit();
1074 Delete();
1076 if (!maClipboard.empty())
1077 InsertNodes(CloneList(maClipboard));
1079 EndEdit();
1082 SmNodeList* SmCursor::CloneList(SmClipboard &rClipboard){
1083 SmCloningVisitor aCloneFactory;
1084 SmNodeList* pClones = new SmNodeList;
1086 for(auto &xNode : rClipboard){
1087 SmNode *pClone = aCloneFactory.Clone(xNode.get());
1088 pClones->push_back(pClone);
1091 return pClones;
1094 SmNode* SmCursor::FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected){
1095 assert(pSNode);
1096 //Move up parent until we find a node who's
1097 //parent is NULL or isn't selected and not a type of:
1098 // SmExpressionNode
1099 // SmLineNode
1100 // SmBinHorNode
1101 // SmUnHorNode
1102 // SmAlignNode
1103 // SmFontNode
1104 while(pSNode->GetParent() &&
1105 ((MoveUpIfSelected &&
1106 pSNode->GetParent()->IsSelected()) ||
1107 IsLineCompositionNode(pSNode->GetParent())))
1108 pSNode = pSNode->GetParent();
1109 //Now we have the selection line node
1110 return pSNode;
1113 SmNode* SmCursor::FindSelectedNode(SmNode* pNode){
1114 if(pNode->GetNumSubNodes() == 0)
1115 return nullptr;
1116 for(auto pChild : *static_cast<SmStructureNode*>(pNode))
1118 if(!pChild)
1119 continue;
1120 if(pChild->IsSelected())
1121 return pChild;
1122 SmNode* pRetVal = FindSelectedNode(pChild);
1123 if(pRetVal)
1124 return pRetVal;
1126 return nullptr;
1129 SmNodeList* SmCursor::LineToList(SmStructureNode* pLine, SmNodeList* list){
1130 for(auto pChild : *pLine)
1132 if (!pChild)
1133 continue;
1134 switch(pChild->GetType()){
1135 case SmNodeType::Line:
1136 case SmNodeType::UnHor:
1137 case SmNodeType::Expression:
1138 case SmNodeType::BinHor:
1139 case SmNodeType::Align:
1140 case SmNodeType::Font:
1141 LineToList(static_cast<SmStructureNode*>(pChild), list);
1142 break;
1143 case SmNodeType::Error:
1144 delete pChild;
1145 break;
1146 default:
1147 list->push_back(pChild);
1150 pLine->ClearSubNodes();
1151 delete pLine;
1152 return list;
1155 void SmCursor::CloneLineToClipboard(SmStructureNode* pLine, SmClipboard* pClipboard){
1156 SmCloningVisitor aCloneFactory;
1157 for(auto pChild : *pLine)
1159 if (!pChild)
1160 continue;
1161 if( IsLineCompositionNode( pChild ) )
1162 CloneLineToClipboard( static_cast<SmStructureNode*>(pChild), pClipboard );
1163 else if( pChild->IsSelected() && pChild->GetType() != SmNodeType::Error ) {
1164 //Only clone selected text from SmTextNode
1165 if(pChild->GetType() == SmNodeType::Text) {
1166 SmTextNode *pText = static_cast<SmTextNode*>(pChild);
1167 std::unique_ptr<SmTextNode> pClone(new SmTextNode( pChild->GetToken(), pText->GetFontDesc() ));
1168 int start = pText->GetSelectionStart(),
1169 length = pText->GetSelectionEnd() - pText->GetSelectionStart();
1170 pClone->ChangeText(pText->GetText().copy(start, length));
1171 pClone->SetScaleMode(pText->GetScaleMode());
1172 pClipboard->push_back(std::move(pClone));
1173 } else
1174 pClipboard->push_back(std::unique_ptr<SmNode>(aCloneFactory.Clone(pChild)));
1179 bool SmCursor::IsLineCompositionNode(SmNode const * pNode){
1180 switch(pNode->GetType()){
1181 case SmNodeType::Line:
1182 case SmNodeType::UnHor:
1183 case SmNodeType::Expression:
1184 case SmNodeType::BinHor:
1185 case SmNodeType::Align:
1186 case SmNodeType::Font:
1187 return true;
1188 default:
1189 return false;
1193 int SmCursor::CountSelectedNodes(SmNode* pNode){
1194 if(pNode->GetNumSubNodes() == 0)
1195 return 0;
1196 int nCount = 0;
1197 for(auto pChild : *static_cast<SmStructureNode*>(pNode))
1199 if (!pChild)
1200 continue;
1201 if(pChild->IsSelected() && !IsLineCompositionNode(pChild))
1202 nCount++;
1203 nCount += CountSelectedNodes(pChild);
1205 return nCount;
1208 bool SmCursor::HasComplexSelection(){
1209 if(!HasSelection())
1210 return false;
1211 AnnotateSelection();
1213 return CountSelectedNodes(mpTree) > 1;
1216 void SmCursor::FinishEdit(SmNodeList* pLineList,
1217 SmStructureNode* pParent,
1218 int nParentIndex,
1219 SmCaretPos PosAfterEdit,
1220 SmNode* pStartLine) {
1221 //Store number of nodes in line for later
1222 int entries = pLineList->size();
1224 //Parse list of nodes to a tree
1225 SmNodeListParser parser;
1226 SmNode* pLine = parser.Parse(pLineList);
1227 delete pLineList;
1229 //Check if we're making the body of a subsup node bigger than one
1230 if(pParent->GetType() == SmNodeType::SubSup &&
1231 nParentIndex == 0 &&
1232 entries > 1) {
1233 //Wrap pLine in scalable round brackets
1234 SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5);
1235 SmBraceNode *pBrace = new SmBraceNode(aTok);
1236 pBrace->SetScaleMode(SmScaleMode::Height);
1237 SmNode *pLeft = CreateBracket(SmBracketType::Round, true),
1238 *pRight = CreateBracket(SmBracketType::Round, false);
1239 SmBracebodyNode *pBody = new SmBracebodyNode(SmToken());
1240 pBody->SetSubNodes(pLine, nullptr);
1241 pBrace->SetSubNodes(pLeft, pBody, pRight);
1242 pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0);
1243 pLine = pBrace;
1244 //TODO: Consider the following alternative behavior:
1245 //Consider the line: A + {B + C}^D lsub E
1246 //Here pLineList is B, + and C and pParent is a subsup node with
1247 //both RSUP and LSUB set. Imagine the user just inserted "B +" in
1248 //the body of the subsup node...
1249 //The most natural thing to do would be to make the line like this:
1250 //A + B lsub E + C ^ D
1251 //E.g. apply LSUB and LSUP to the first element in pLineList and RSUP
1252 //and RSUB to the last element in pLineList. But how should this act
1253 //for CSUP and CSUB ???
1254 //For this reason and because brackets was faster to implement, this solution
1255 //have been chosen. It might be worth working on the other solution later...
1258 //Set pStartLine if NULL
1259 if(!pStartLine)
1260 pStartLine = pLine;
1262 //Insert it back into the parent
1263 pParent->SetSubNode(nParentIndex, pLine);
1265 //Rebuild graph of caret position
1266 mpAnchor = nullptr;
1267 mpPosition = nullptr;
1268 BuildGraph();
1269 AnnotateSelection(); //Update selection annotation!
1271 //Set caret position
1272 if(!SetCaretPosition(PosAfterEdit))
1273 SetCaretPosition(SmCaretPos(pStartLine, 0));
1275 //End edit section
1276 EndEdit();
1279 void SmCursor::BeginEdit(){
1280 if(mnEditSections++ > 0) return;
1282 mbIsEnabledSetModifiedSmDocShell = mpDocShell->IsEnableSetModified();
1283 if( mbIsEnabledSetModifiedSmDocShell )
1284 mpDocShell->EnableSetModified( false );
1287 void SmCursor::EndEdit(){
1288 if(--mnEditSections > 0) return;
1290 mpDocShell->SetFormulaArranged(false);
1291 //Okay, I don't know what this does... :)
1292 //It's used in SmDocShell::SetText and with places where everything is modified.
1293 //I think it does some magic, with sfx, but everything is totally undocumented so
1294 //it's kinda hard to tell...
1295 if ( mbIsEnabledSetModifiedSmDocShell )
1296 mpDocShell->EnableSetModified( mbIsEnabledSetModifiedSmDocShell );
1297 //I think this notifies people around us that we've modified this document...
1298 mpDocShell->SetModified();
1299 //I think SmDocShell uses this value when it sends an update graphics event
1300 //Anyway comments elsewhere suggests it need to be updated...
1301 mpDocShell->mnModifyCount++;
1303 //TODO: Consider copying the update accessibility code from SmDocShell::SetText in here...
1304 //This somehow updates the size of SmGraphicView if it is running in embedded mode
1305 if( mpDocShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED )
1306 mpDocShell->OnDocumentPrinterChanged(nullptr);
1308 //Request a repaint...
1309 RequestRepaint();
1311 //Update the edit engine and text of the document
1312 OUString formula;
1313 SmNodeToTextVisitor(mpTree, formula);
1314 //mpTree->CreateTextFromNode(formula);
1315 mpDocShell->maText = formula;
1316 mpDocShell->GetEditEngine().QuickInsertText( formula, ESelection( 0, 0, EE_PARA_ALL, EE_TEXTPOS_ALL ) );
1317 mpDocShell->GetEditEngine().QuickFormatDoc();
1320 void SmCursor::RequestRepaint(){
1321 SmViewShell *pViewSh = SmGetActiveView();
1322 if( pViewSh ) {
1323 if ( SfxObjectCreateMode::EMBEDDED == mpDocShell->GetCreateMode() )
1324 mpDocShell->Repaint();
1325 else
1326 pViewSh->GetGraphicWindow().Invalidate();
1330 bool SmCursor::IsAtTailOfBracket(SmBracketType eBracketType, SmBraceNode** ppBraceNode) const {
1331 const SmCaretPos pos = GetPosition();
1332 if (!pos.IsValid()) {
1333 return false;
1336 SmNode* pNode = pos.pSelectedNode;
1338 if (pNode->GetType() == SmNodeType::Text) {
1339 SmTextNode* pTextNode = static_cast<SmTextNode*>(pNode);
1340 if (pos.nIndex < pTextNode->GetText().getLength()) {
1341 // The cursor is on a text node and at the middle of it.
1342 return false;
1344 } else {
1345 if (pos.nIndex < 1) {
1346 return false;
1350 while (true) {
1351 SmStructureNode* pParentNode = pNode->GetParent();
1352 if (!pParentNode) {
1353 // There's no brace body node in the ancestors.
1354 return false;
1357 int index = pParentNode->IndexOfSubNode(pNode);
1358 assert(index >= 0);
1359 if (static_cast<size_t>(index + 1) != pParentNode->GetNumSubNodes()) {
1360 // The cursor is not at the tail at one of ancestor nodes.
1361 return false;
1364 pNode = pParentNode;
1365 if (pNode->GetType() == SmNodeType::Bracebody) {
1366 // Found the brace body node.
1367 break;
1371 SmStructureNode* pBraceNodeTmp = pNode->GetParent();
1372 if (!pBraceNodeTmp || pBraceNodeTmp->GetType() != SmNodeType::Brace) {
1373 // Brace node is invalid.
1374 return false;
1377 SmBraceNode* pBraceNode = static_cast<SmBraceNode*>(pBraceNodeTmp);
1378 SmMathSymbolNode* pClosingNode = pBraceNode->ClosingBrace();
1379 if (!pClosingNode) {
1380 // Couldn't get closing symbol node.
1381 return false;
1384 // Check if the closing brace matches eBracketType.
1385 SmTokenType eClosingTokenType = pClosingNode->GetToken().eType;
1386 switch (eBracketType) {
1387 case SmBracketType::Round: if (eClosingTokenType != TRPARENT) { return false; } break;
1388 case SmBracketType::Square: if (eClosingTokenType != TRBRACKET) { return false; } break;
1389 case SmBracketType::Curly: if (eClosingTokenType != TRBRACE) { return false; } break;
1390 default:
1391 return false;
1394 if (ppBraceNode) {
1395 *ppBraceNode = pBraceNode;
1398 return true;
1401 void SmCursor::MoveAfterBracket(SmBraceNode* pBraceNode)
1403 mpPosition->CaretPos.pSelectedNode = pBraceNode;
1404 mpPosition->CaretPos.nIndex = 1;
1405 mpAnchor->CaretPos.pSelectedNode = pBraceNode;
1406 mpAnchor->CaretPos.nIndex = 1;
1407 RequestRepaint();
1411 /////////////////////////////////////// SmNodeListParser
1413 SmNode* SmNodeListParser::Parse(SmNodeList* list){
1414 pList = list;
1415 //Delete error nodes
1416 SmNodeList::iterator it = pList->begin();
1417 while(it != pList->end()) {
1418 if((*it)->GetType() == SmNodeType::Error){
1419 //Delete and erase
1420 delete *it;
1421 it = pList->erase(it);
1422 }else
1423 ++it;
1425 SmNode* retval = Expression();
1426 pList = nullptr;
1427 return retval;
1430 SmNode* SmNodeListParser::Expression(){
1431 SmNodeArray NodeArray;
1432 //Accept as many relations as there is
1433 while(Terminal())
1434 NodeArray.push_back(Relation());
1436 //Create SmExpressionNode, I hope SmToken() will do :)
1437 SmStructureNode* pExpr = new SmExpressionNode(SmToken());
1438 pExpr->SetSubNodes(std::move(NodeArray));
1439 return pExpr;
1442 SmNode* SmNodeListParser::Relation(){
1443 //Read a sum
1444 SmNode* pLeft = Sum();
1445 //While we have tokens and the next is a relation
1446 while(Terminal() && IsRelationOperator(Terminal()->GetToken())){
1447 //Take the operator
1448 SmNode* pOper = Take();
1449 //Find the right side of the relation
1450 SmNode* pRight = Sum();
1451 //Create new SmBinHorNode
1452 SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
1453 pNewNode->SetSubNodes(pLeft, pOper, pRight);
1454 pLeft = pNewNode;
1456 return pLeft;
1459 SmNode* SmNodeListParser::Sum(){
1460 //Read a product
1461 SmNode* pLeft = Product();
1462 //While we have tokens and the next is a sum
1463 while(Terminal() && IsSumOperator(Terminal()->GetToken())){
1464 //Take the operator
1465 SmNode* pOper = Take();
1466 //Find the right side of the sum
1467 SmNode* pRight = Product();
1468 //Create new SmBinHorNode
1469 SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
1470 pNewNode->SetSubNodes(pLeft, pOper, pRight);
1471 pLeft = pNewNode;
1473 return pLeft;
1476 SmNode* SmNodeListParser::Product(){
1477 //Read a Factor
1478 SmNode* pLeft = Factor();
1479 //While we have tokens and the next is a product
1480 while(Terminal() && IsProductOperator(Terminal()->GetToken())){
1481 //Take the operator
1482 SmNode* pOper = Take();
1483 //Find the right side of the operation
1484 SmNode* pRight = Factor();
1485 //Create new SmBinHorNode
1486 SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
1487 pNewNode->SetSubNodes(pLeft, pOper, pRight);
1488 pLeft = pNewNode;
1490 return pLeft;
1493 SmNode* SmNodeListParser::Factor(){
1494 //Read unary operations
1495 if(!Terminal())
1496 return Error();
1497 //Take care of unary operators
1498 else if(IsUnaryOperator(Terminal()->GetToken()))
1500 SmStructureNode *pUnary = new SmUnHorNode(SmToken());
1501 SmNode *pOper = Terminal(),
1502 *pArg;
1504 if(Next())
1505 pArg = Factor();
1506 else
1507 pArg = Error();
1509 pUnary->SetSubNodes(pOper, pArg);
1510 return pUnary;
1512 return Postfix();
1515 SmNode* SmNodeListParser::Postfix(){
1516 if(!Terminal())
1517 return Error();
1518 SmNode *pArg = nullptr;
1519 if(IsPostfixOperator(Terminal()->GetToken()))
1520 pArg = Error();
1521 else if(IsOperator(Terminal()->GetToken()))
1522 return Error();
1523 else
1524 pArg = Take();
1525 while(Terminal() && IsPostfixOperator(Terminal()->GetToken())) {
1526 SmStructureNode *pUnary = new SmUnHorNode(SmToken());
1527 SmNode *pOper = Take();
1528 pUnary->SetSubNodes(pArg, pOper);
1529 pArg = pUnary;
1531 return pArg;
1534 SmNode* SmNodeListParser::Error(){
1535 return new SmErrorNode(SmToken());
1538 bool SmNodeListParser::IsOperator(const SmToken &token) {
1539 return IsRelationOperator(token) ||
1540 IsSumOperator(token) ||
1541 IsProductOperator(token) ||
1542 IsUnaryOperator(token) ||
1543 IsPostfixOperator(token);
1546 bool SmNodeListParser::IsRelationOperator(const SmToken &token) {
1547 return bool(token.nGroup & TG::Relation);
1550 bool SmNodeListParser::IsSumOperator(const SmToken &token) {
1551 return bool(token.nGroup & TG::Sum);
1554 bool SmNodeListParser::IsProductOperator(const SmToken &token) {
1555 return token.nGroup & TG::Product &&
1556 token.eType != TWIDESLASH &&
1557 token.eType != TWIDEBACKSLASH &&
1558 token.eType != TUNDERBRACE &&
1559 token.eType != TOVERBRACE &&
1560 token.eType != TOVER;
1563 bool SmNodeListParser::IsUnaryOperator(const SmToken &token) {
1564 return token.nGroup & TG::UnOper &&
1565 (token.eType == TPLUS ||
1566 token.eType == TMINUS ||
1567 token.eType == TPLUSMINUS ||
1568 token.eType == TMINUSPLUS ||
1569 token.eType == TNEG ||
1570 token.eType == TUOPER);
1573 bool SmNodeListParser::IsPostfixOperator(const SmToken &token) {
1574 return token.eType == TFACT;
1577 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */