cURL: follow redirects
[LibreOffice.git] / starmath / source / cursor.cxx
blobfadb103c8b0ff53bf45ab2cfb08ae8e01230f928
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 "accessibility.hxx"
14 #include <comphelper/string.hxx>
15 #include <cassert>
17 void SmCursor::Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor){
18 SmCaretPosGraphEntry* NewPos = nullptr;
19 switch(direction)
21 case MoveLeft:
22 if (mpPosition)
23 NewPos = mpPosition->Left;
24 OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!");
25 break;
26 case MoveRight:
27 if (mpPosition)
28 NewPos = mpPosition->Right;
29 OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!");
30 break;
31 case MoveUp:
32 //Implementation is practically identical to MoveDown, except for a single if statement
33 //so I've implemented them together and added a direction == MoveDown to the if statements.
34 case MoveDown:
35 if (mpPosition)
37 SmCaretLine from_line = SmCaretPos2LineVisitor(pDev, mpPosition->CaretPos).GetResult(),
38 best_line, //Best approximated line found so far
39 curr_line; //Current line
40 long dbp_sq = 0; //Distance squared to best line
41 for(auto &pEntry : *mpGraph)
43 //Reject it if it's the current position
44 if(pEntry->CaretPos == mpPosition->CaretPos) continue;
45 //Compute caret line
46 curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult();
47 //Reject anything above if we're moving down
48 if(curr_line.GetTop() <= from_line.GetTop() && direction == MoveDown) continue;
49 //Reject anything below if we're moving up
50 if(curr_line.GetTop() + curr_line.GetHeight() >= from_line.GetTop() + from_line.GetHeight()
51 && direction == MoveUp) continue;
52 //Compare if it to what we have, if we have anything yet
53 if(NewPos){
54 //Compute distance to current line squared, multiplied with a horizontal factor
55 long dp_sq = curr_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
56 curr_line.SquaredDistanceY(from_line);
57 //Discard current line if best line is closer
58 if(dbp_sq <= dp_sq) continue;
60 //Take current line as the best
61 best_line = curr_line;
62 NewPos = pEntry.get();
63 //Update distance to best line
64 dbp_sq = best_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
65 best_line.SquaredDistanceY(from_line);
68 break;
69 default:
70 assert(false);
72 if(NewPos){
73 mpPosition = NewPos;
74 if(bMoveAnchor)
75 mpAnchor = NewPos;
76 RequestRepaint();
80 void SmCursor::MoveTo(OutputDevice* pDev, const Point& pos, bool bMoveAnchor)
82 SmCaretPosGraphEntry* NewPos = nullptr;
83 long dp_sq = 0, //Distance to current line squared
84 dbp_sq = 1; //Distance to best line squared
85 for(auto &pEntry : *mpGraph)
87 OSL_ENSURE(pEntry->CaretPos.IsValid(), "The caret position graph may not have invalid positions!");
88 //Compute current line
89 SmCaretLine curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult();
90 //Compute squared distance to current line
91 dp_sq = curr_line.SquaredDistanceX(pos) + curr_line.SquaredDistanceY(pos);
92 //If we have a position compare to it
93 if(NewPos){
94 //If best line is closer, reject current line
95 if(dbp_sq <= dp_sq) continue;
97 //Accept current position as the best
98 NewPos = pEntry.get();
99 //Update distance to best line
100 dbp_sq = dp_sq;
102 if(NewPos){
103 mpPosition = NewPos;
104 if(bMoveAnchor)
105 mpAnchor = NewPos;
106 RequestRepaint();
110 void SmCursor::BuildGraph(){
111 //Save the current anchor and position
112 SmCaretPos _anchor, _position;
113 //Release mpGraph if allocated
114 if(mpGraph){
115 if(mpAnchor)
116 _anchor = mpAnchor->CaretPos;
117 if(mpPosition)
118 _position = mpPosition->CaretPos;
119 mpGraph.reset();
120 //Reset anchor and position as they point into an old graph
121 mpAnchor = nullptr;
122 mpPosition = nullptr;
125 //Build the new graph
126 mpGraph.reset(SmCaretPosGraphBuildingVisitor(mpTree).takeGraph());
128 //Restore anchor and position pointers
129 if(_anchor.IsValid() || _position.IsValid()){
130 for(auto &pEntry : *mpGraph)
132 if(_anchor == pEntry->CaretPos)
133 mpAnchor = pEntry.get();
134 if(_position == pEntry->CaretPos)
135 mpPosition = pEntry.get();
138 //Set position and anchor to first caret position
139 auto it = mpGraph->begin();
140 assert(it != mpGraph->end());
141 if(!mpPosition)
142 mpPosition = it->get();
143 if(!mpAnchor)
144 mpAnchor = mpPosition;
146 assert(mpPosition);
147 assert(mpAnchor);
148 OSL_ENSURE(mpPosition->CaretPos.IsValid(), "Position must be valid");
149 OSL_ENSURE(mpAnchor->CaretPos.IsValid(), "Anchor must be valid");
152 bool SmCursor::SetCaretPosition(SmCaretPos pos){
153 for(auto &pEntry : *mpGraph)
155 if(pEntry->CaretPos == pos)
157 mpPosition = pEntry.get();
158 mpAnchor = pEntry.get();
159 return true;
162 return false;
165 void SmCursor::AnnotateSelection(){
166 //TODO: Manage a state, reset it upon modification and optimize this call
167 SmSetSelectionVisitor(mpAnchor->CaretPos, mpPosition->CaretPos, mpTree);
170 void SmCursor::Draw(OutputDevice& pDev, Point Offset, bool isCaretVisible){
171 SmCaretDrawingVisitor(pDev, GetPosition(), Offset, isCaretVisible);
174 void SmCursor::DeletePrev(OutputDevice* pDev){
175 //Delete only a selection if there's a selection
176 if(HasSelection()){
177 Delete();
178 return;
181 SmNode* pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
182 SmStructureNode* pLineParent = pLine->GetParent();
183 int nLineOffset = pLineParent->IndexOfSubNode(pLine);
184 assert(nLineOffset >= 0);
186 //If we're in front of a node who's parent is a TABLE
187 if(pLineParent->GetType() == NTABLE && mpPosition->CaretPos.Index == 0 && nLineOffset > 0){
188 //Now we can merge with nLineOffset - 1
189 BeginEdit();
190 //Line to merge things into, so we can delete pLine
191 SmNode* pMergeLine = pLineParent->GetSubNode(nLineOffset-1);
192 OSL_ENSURE(pMergeLine, "pMergeLine cannot be NULL!");
193 SmCaretPos PosAfterDelete;
194 //Convert first line to list
195 SmNodeList *pLineList = NodeToList(pMergeLine);
196 if(!pLineList->empty()){
197 //Find iterator to patch
198 SmNodeList::iterator patchPoint = pLineList->end();
199 --patchPoint;
200 //Convert second line to list
201 NodeToList(pLine, pLineList);
202 //Patch the line list
203 ++patchPoint;
204 PosAfterDelete = PatchLineList(pLineList, patchPoint);
205 //Parse the line
206 pLine = SmNodeListParser().Parse(pLineList);
208 delete pLineList;
209 pLineParent->SetSubNode(nLineOffset-1, pLine);
210 //Delete the removed line slot
211 SmNodeArray lines(pLineParent->GetNumSubNodes()-1);
212 for(int i = 0; i < pLineParent->GetNumSubNodes(); i++){
213 if(i < nLineOffset)
214 lines[i] = pLineParent->GetSubNode(i);
215 else if(i > nLineOffset)
216 lines[i-1] = pLineParent->GetSubNode(i);
218 pLineParent->SetSubNodes(lines);
219 //Rebuild graph
220 mpAnchor = nullptr;
221 mpPosition = nullptr;
222 BuildGraph();
223 AnnotateSelection();
224 //Set caret position
225 if(!SetCaretPosition(PosAfterDelete))
226 SetCaretPosition(SmCaretPos(pLine, 0));
227 //Finish editing
228 EndEdit();
230 //TODO: If we're in an empty (sub/super/*) script
231 /*}else if(pLineParent->GetType() == NSUBSUP &&
232 nLineOffset != 0 &&
233 pLine->GetType() == NEXPRESSION &&
234 pLine->GetNumSubNodes() == 0){
235 //There's a (sub/super) script we can delete
236 //Consider selecting the entire script if GetNumSubNodes() != 0 or pLine->GetType() != NEXPRESSION
237 //TODO: Handle case where we delete a limit
240 //Else move select, and delete if not complex
241 }else{
242 this->Move(pDev, MoveLeft, false);
243 if(!this->HasComplexSelection())
244 Delete();
248 void SmCursor::Delete(){
249 //Return if we don't have a selection to delete
250 if(!HasSelection())
251 return;
253 //Enter edit section
254 BeginEdit();
256 //Set selected on nodes
257 AnnotateSelection();
259 //Find an arbitrary selected node
260 SmNode* pSNode = FindSelectedNode(mpTree);
261 assert(pSNode);
263 //Find the topmost node of the line that holds the selection
264 SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
265 OSL_ENSURE(pLine != mpTree, "Shouldn't be able to select the entire tree");
267 //Get the parent of the line
268 SmStructureNode* pLineParent = pLine->GetParent();
269 //Find line offset in parent
270 int nLineOffset = pLineParent->IndexOfSubNode(pLine);
271 assert(nLineOffset >= 0);
273 //Position after delete
274 SmCaretPos PosAfterDelete;
276 SmNodeList* pLineList = NodeToList(pLine);
278 //Take the selected nodes and delete them...
279 SmNodeList::iterator patchIt = TakeSelectedNodesFromList(pLineList);
281 //Get the position to set after delete
282 PosAfterDelete = PatchLineList(pLineList, patchIt);
284 //Finish editing
285 FinishEdit(pLineList, pLineParent, nLineOffset, PosAfterDelete);
288 void SmCursor::InsertNodes(SmNodeList* pNewNodes){
289 if(pNewNodes->empty()){
290 delete pNewNodes;
291 return;
294 //Begin edit section
295 BeginEdit();
297 //Get the current position
298 const SmCaretPos pos = mpPosition->CaretPos;
300 //Find top most of line that holds position
301 SmNode* pLine = FindTopMostNodeInLine(pos.pSelectedNode);
303 //Find line parent and line index in parent
304 SmStructureNode* pLineParent = pLine->GetParent();
305 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
306 assert(nParentIndex >= 0);
308 //Convert line to list
309 SmNodeList* pLineList = NodeToList(pLine);
311 //Find iterator for place to insert nodes
312 SmNodeList::iterator it = FindPositionInLineList(pLineList, pos);
314 //Insert all new nodes
315 SmNodeList::iterator newIt,
316 patchIt = it, // (pointless default value, fixes compiler warnings)
317 insIt;
318 for(newIt = pNewNodes->begin(); newIt != pNewNodes->end(); ++newIt){
319 insIt = pLineList->insert(it, *newIt);
320 if(newIt == pNewNodes->begin())
321 patchIt = insIt;
323 //Patch the places we've changed stuff
324 PatchLineList(pLineList, patchIt);
325 SmCaretPos PosAfterInsert = PatchLineList(pLineList, it);
326 //Release list, we've taken the nodes
327 delete pNewNodes;
328 pNewNodes = nullptr;
330 //Finish editing
331 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
334 SmNodeList::iterator SmCursor::FindPositionInLineList(SmNodeList* pLineList,
335 const SmCaretPos& rCaretPos)
337 //Find iterator for position
338 SmNodeList::iterator it;
339 for(it = pLineList->begin(); it != pLineList->end(); ++it){
340 if(*it == rCaretPos.pSelectedNode)
342 if((*it)->GetType() == NTEXT)
344 //Split textnode if needed
345 if(rCaretPos.Index > 0)
347 SmTextNode* pText = static_cast<SmTextNode*>(rCaretPos.pSelectedNode);
348 if (rCaretPos.Index == pText->GetText().getLength())
349 return ++it;
350 OUString str1 = pText->GetText().copy(0, rCaretPos.Index);
351 OUString str2 = pText->GetText().copy(rCaretPos.Index);
352 pText->ChangeText(str1);
353 ++it;
354 //Insert str2 as new text node
355 assert(!str2.isEmpty());
356 SmTextNode* pNewText = new SmTextNode(pText->GetToken(), pText->GetFontDesc());
357 pNewText->ChangeText(str2);
358 it = pLineList->insert(it, pNewText);
360 }else
361 ++it;
362 //it now pointer to the node following pos, so pLineList->insert(it, ...) will insert correctly
363 return it;
367 //If we didn't find pSelectedNode, it must be because the caret is in front of the line
368 return pLineList->begin();
371 SmCaretPos SmCursor::PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter) {
372 //The nodes we should consider merging
373 SmNode *prev = nullptr,
374 *next = nullptr;
375 if(aIter != pLineList->end())
376 next = *aIter;
377 if(aIter != pLineList->begin()) {
378 --aIter;
379 prev = *aIter;
380 ++aIter;
383 //Check if there's textnodes to merge
384 if( prev &&
385 next &&
386 prev->GetType() == NTEXT &&
387 next->GetType() == NTEXT &&
388 ( prev->GetToken().eType != TNUMBER ||
389 next->GetToken().eType == TNUMBER) ){
390 SmTextNode *pText = static_cast<SmTextNode*>(prev),
391 *pOldN = static_cast<SmTextNode*>(next);
392 SmCaretPos retval(pText, pText->GetText().getLength());
393 OUString newText;
394 newText += pText->GetText();
395 newText += pOldN->GetText();
396 pText->ChangeText(newText);
397 delete pOldN;
398 pLineList->erase(aIter);
399 return retval;
402 //Check if there's a SmPlaceNode to remove:
403 if(prev && next && prev->GetType() == NPLACE && !SmNodeListParser::IsOperator(next->GetToken())){
404 --aIter;
405 aIter = pLineList->erase(aIter);
406 delete prev;
407 //Return caret pos in front of aIter
408 if(aIter != pLineList->begin())
409 --aIter; //Thus find node before aIter
410 if(aIter == pLineList->begin())
411 return SmCaretPos();
412 return SmCaretPos::GetPosAfter(*aIter);
414 if(prev && next && next->GetType() == NPLACE && !SmNodeListParser::IsOperator(prev->GetToken())){
415 aIter = pLineList->erase(aIter);
416 delete next;
417 return SmCaretPos::GetPosAfter(prev);
420 //If we didn't do anything return
421 if(!prev) //return an invalid to indicate we're in front of line
422 return SmCaretPos();
423 return SmCaretPos::GetPosAfter(prev);
426 SmNodeList::iterator SmCursor::TakeSelectedNodesFromList(SmNodeList *pLineList,
427 SmNodeList *pSelectedNodes) {
428 SmNodeList::iterator retval;
429 SmNodeList::iterator it = pLineList->begin();
430 while(it != pLineList->end()){
431 if((*it)->IsSelected()){
432 //Split text nodes
433 if((*it)->GetType() == NTEXT) {
434 SmTextNode* pText = static_cast<SmTextNode*>(*it);
435 OUString aText = pText->GetText();
436 //Start and lengths of the segments, 2 is the selected segment
437 int start2 = pText->GetSelectionStart(),
438 start3 = pText->GetSelectionEnd(),
439 len1 = start2 - 0,
440 len2 = start3 - start2,
441 len3 = aText.getLength() - start3;
442 SmToken aToken = pText->GetToken();
443 sal_uInt16 eFontDesc = pText->GetFontDesc();
444 //If we need make segment 1
445 if(len1 > 0) {
446 int start1 = 0;
447 OUString str = aText.copy(start1, len1);
448 pText->ChangeText(str);
449 ++it;
450 } else {//Remove it if not needed
451 it = pLineList->erase(it);
452 delete pText;
454 //Set retval to be right after the selection
455 retval = it;
456 //if we need make segment 3
457 if(len3 > 0) {
458 OUString str = aText.copy(start3, len3);
459 SmTextNode* pSeg3 = new SmTextNode(aToken, eFontDesc);
460 pSeg3->ChangeText(str);
461 retval = pLineList->insert(it, pSeg3);
463 //If we need to save the selected text
464 if(pSelectedNodes && len2 > 0) {
465 OUString str = aText.copy(start2, len2);
466 SmTextNode* pSeg2 = new SmTextNode(aToken, eFontDesc);
467 pSeg2->ChangeText(str);
468 pSelectedNodes->push_back(pSeg2);
470 } else { //if it's not textnode
471 SmNode* pNode = *it;
472 retval = it = pLineList->erase(it);
473 if(pSelectedNodes)
474 pSelectedNodes->push_back(pNode);
475 else
476 delete pNode;
478 } else
479 ++it;
481 return retval;
484 void SmCursor::InsertSubSup(SmSubSup eSubSup) {
485 AnnotateSelection();
487 //Find line
488 SmNode *pLine;
489 if(HasSelection()) {
490 SmNode *pSNode = FindSelectedNode(mpTree);
491 assert(pSNode);
492 pLine = FindTopMostNodeInLine(pSNode, true);
493 } else
494 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
496 //Find Parent and offset in parent
497 SmStructureNode *pLineParent = pLine->GetParent();
498 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
499 assert(nParentIndex >= 0);
501 //TODO: Consider handling special cases where parent is an SmOperNode,
502 // Maybe this method should be able to add limits to an SmOperNode...
504 //We begin modifying the tree here
505 BeginEdit();
507 //Convert line to list
508 SmNodeList* pLineList = NodeToList(pLine);
510 //Take the selection, and/or find iterator for current position
511 SmNodeList* pSelectedNodesList = new SmNodeList();
512 SmNodeList::iterator it;
513 if(HasSelection())
514 it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
515 else
516 it = FindPositionInLineList(pLineList, mpPosition->CaretPos);
518 //Find node that this should be applied to
519 SmNode* pSubject;
520 bool bPatchLine = pSelectedNodesList->size() > 0; //If the line should be patched later
521 if(it != pLineList->begin()) {
522 --it;
523 pSubject = *it;
524 ++it;
525 } else {
526 //Create a new place node
527 pSubject = new SmPlaceNode();
528 pSubject->Prepare(mpDocShell->GetFormat(), *mpDocShell);
529 it = pLineList->insert(it, pSubject);
530 ++it;
531 bPatchLine = true; //We've modified the line it should be patched later.
534 //Wrap the subject in a SmSubSupNode
535 SmSubSupNode* pSubSup;
536 if(pSubject->GetType() != NSUBSUP){
537 SmToken token;
538 token.nGroup = TG::Power;
539 pSubSup = new SmSubSupNode(token);
540 pSubSup->SetBody(pSubject);
541 *(--it) = pSubSup;
542 ++it;
543 }else
544 pSubSup = static_cast<SmSubSupNode*>(pSubject);
545 //pSubject shouldn't be referenced anymore, pSubSup is the SmSubSupNode in pLineList we wish to edit.
546 //and it pointer to the element following pSubSup in pLineList.
547 pSubject = nullptr;
549 //Patch the line if we noted that was needed previously
550 if(bPatchLine)
551 PatchLineList(pLineList, it);
553 //Convert existing, if any, sub-/superscript line to list
554 SmNode *pScriptLine = pSubSup->GetSubSup(eSubSup);
555 SmNodeList* pScriptLineList = NodeToList(pScriptLine);
557 //Add selection to pScriptLineList
558 unsigned int nOldSize = pScriptLineList->size();
559 pScriptLineList->insert(pScriptLineList->end(), pSelectedNodesList->begin(), pSelectedNodesList->end());
560 delete pSelectedNodesList;
561 pSelectedNodesList = nullptr;
563 //Patch pScriptLineList if needed
564 if(0 < nOldSize && nOldSize < pScriptLineList->size()) {
565 SmNodeList::iterator iPatchPoint = pScriptLineList->begin();
566 std::advance(iPatchPoint, nOldSize);
567 PatchLineList(pScriptLineList, iPatchPoint);
570 //Find caret pos, that should be used after sub-/superscription.
571 SmCaretPos PosAfterScript; //Leave invalid for first position
572 if(pScriptLineList->size() > 0)
573 PosAfterScript = SmCaretPos::GetPosAfter(pScriptLineList->back());
575 //Parse pScriptLineList
576 pScriptLine = SmNodeListParser().Parse(pScriptLineList);
577 delete pScriptLineList;
578 pScriptLineList = nullptr;
580 //Insert pScriptLine back into the tree
581 pSubSup->SetSubSup(eSubSup, pScriptLine);
583 //Finish editing
584 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterScript, pScriptLine);
587 bool SmCursor::InsertLimit(SmSubSup eSubSup) {
588 //Find a subject to set limits on
589 SmOperNode *pSubject = nullptr;
590 //Check if pSelectedNode might be a subject
591 if(mpPosition->CaretPos.pSelectedNode->GetType() == NOPER)
592 pSubject = static_cast<SmOperNode*>(mpPosition->CaretPos.pSelectedNode);
593 else {
594 //If not, check if parent of the current line is a SmOperNode
595 SmNode *pLineNode = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
596 if(pLineNode->GetParent() && pLineNode->GetParent()->GetType() == NOPER)
597 pSubject = static_cast<SmOperNode*>(pLineNode->GetParent());
600 //Abort operation if we're not in the appropriate context
601 if(!pSubject)
602 return false;
604 BeginEdit();
606 //Find the sub sup node
607 SmSubSupNode *pSubSup = nullptr;
608 //Check if there's already one there...
609 if(pSubject->GetSubNode(0)->GetType() == NSUBSUP)
610 pSubSup = static_cast<SmSubSupNode*>(pSubject->GetSubNode(0));
611 else { //if not create a new SmSubSupNode
612 SmToken token;
613 token.nGroup = TG::Limit;
614 pSubSup = new SmSubSupNode(token);
615 //Set it's body
616 pSubSup->SetBody(pSubject->GetSubNode(0));
617 //Replace the operation of the SmOperNode
618 pSubject->SetSubNode(0, pSubSup);
621 //Create the limit, if needed
622 SmCaretPos PosAfterLimit;
623 SmNode *pLine = nullptr;
624 if(!pSubSup->GetSubSup(eSubSup)){
625 pLine = new SmPlaceNode();
626 pSubSup->SetSubSup(eSubSup, pLine);
627 PosAfterLimit = SmCaretPos(pLine, 1);
628 //If it's already there... let's move the caret
629 } else {
630 pLine = pSubSup->GetSubSup(eSubSup);
631 SmNodeList* pLineList = NodeToList(pLine);
632 if(pLineList->size() > 0)
633 PosAfterLimit = SmCaretPos::GetPosAfter(pLineList->back());
634 pLine = SmNodeListParser().Parse(pLineList);
635 delete pLineList;
636 pSubSup->SetSubSup(eSubSup, pLine);
639 //Rebuild graph of caret positions
640 BuildGraph();
641 AnnotateSelection();
643 //Set caret position
644 if(!SetCaretPosition(PosAfterLimit))
645 SetCaretPosition(SmCaretPos(pLine, 0));
647 EndEdit();
649 return true;
652 void SmCursor::InsertBrackets(SmBracketType eBracketType) {
653 BeginEdit();
655 AnnotateSelection();
657 //Find line
658 SmNode *pLine;
659 if(HasSelection()) {
660 SmNode *pSNode = FindSelectedNode(mpTree);
661 assert(pSNode);
662 pLine = FindTopMostNodeInLine(pSNode, true);
663 } else
664 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
666 //Find parent and offset in parent
667 SmStructureNode *pLineParent = pLine->GetParent();
668 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
669 assert(nParentIndex >= 0);
671 //Convert line to list
672 SmNodeList *pLineList = NodeToList(pLine);
674 //Take the selection, and/or find iterator for current position
675 SmNodeList *pSelectedNodesList = new SmNodeList();
676 SmNodeList::iterator it;
677 if(HasSelection())
678 it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
679 else
680 it = FindPositionInLineList(pLineList, mpPosition->CaretPos);
682 //If there's no selected nodes, create a place node
683 SmNode *pBodyNode;
684 SmCaretPos PosAfterInsert;
685 if(pSelectedNodesList->empty()) {
686 pBodyNode = new SmPlaceNode();
687 PosAfterInsert = SmCaretPos(pBodyNode, 1);
688 } else
689 pBodyNode = SmNodeListParser().Parse(pSelectedNodesList);
691 delete pSelectedNodesList;
693 //Create SmBraceNode
694 SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5);
695 SmBraceNode *pBrace = new SmBraceNode(aTok);
696 pBrace->SetScaleMode(SCALE_HEIGHT);
697 SmNode *pLeft = CreateBracket(eBracketType, true),
698 *pRight = CreateBracket(eBracketType, false);
699 SmBracebodyNode *pBody = new SmBracebodyNode(SmToken());
700 pBody->SetSubNodes(pBodyNode, nullptr);
701 pBrace->SetSubNodes(pLeft, pBody, pRight);
702 pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell);
704 //Insert into line
705 pLineList->insert(it, pBrace);
706 //Patch line (I think this is good enough)
707 SmCaretPos aAfter = PatchLineList(pLineList, it);
708 if( !PosAfterInsert.IsValid() )
709 PosAfterInsert = aAfter;
711 //Finish editing
712 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
715 SmNode *SmCursor::CreateBracket(SmBracketType eBracketType, bool bIsLeft) {
716 SmToken aTok;
717 if(bIsLeft){
718 switch(eBracketType){
719 case NoneBrackets:
720 aTok = SmToken(TNONE, '\0', "none", TG::LBrace | TG::RBrace, 0);
721 break;
722 case RoundBrackets:
723 aTok = SmToken(TLPARENT, MS_LPARENT, "(", TG::LBrace, 5);
724 break;
725 case SquareBrackets:
726 aTok = SmToken(TLBRACKET, MS_LBRACKET, "[", TG::LBrace, 5);
727 break;
728 case DoubleSquareBrackets:
729 aTok = SmToken(TLDBRACKET, MS_LDBRACKET, "ldbracket", TG::LBrace, 5);
730 break;
731 case LineBrackets:
732 aTok = SmToken(TLLINE, MS_VERTLINE, "lline", TG::LBrace, 5);
733 break;
734 case DoubleLineBrackets:
735 aTok = SmToken(TLDLINE, MS_DVERTLINE, "ldline", TG::LBrace, 5);
736 break;
737 case CurlyBrackets:
738 aTok = SmToken(TLBRACE, MS_LBRACE, "lbrace", TG::LBrace, 5);
739 break;
740 case AngleBrackets:
741 aTok = SmToken(TLANGLE, MS_LMATHANGLE, "langle", TG::LBrace, 5);
742 break;
743 case CeilBrackets:
744 aTok = SmToken(TLCEIL, MS_LCEIL, "lceil", TG::LBrace, 5);
745 break;
746 case FloorBrackets:
747 aTok = SmToken(TLFLOOR, MS_LFLOOR, "lfloor", TG::LBrace, 5);
748 break;
750 } else {
751 switch(eBracketType) {
752 case NoneBrackets:
753 aTok = SmToken(TNONE, '\0', "none", TG::LBrace | TG::RBrace, 0);
754 break;
755 case RoundBrackets:
756 aTok = SmToken(TRPARENT, MS_RPARENT, ")", TG::RBrace, 5);
757 break;
758 case SquareBrackets:
759 aTok = SmToken(TRBRACKET, MS_RBRACKET, "]", TG::RBrace, 5);
760 break;
761 case DoubleSquareBrackets:
762 aTok = SmToken(TRDBRACKET, MS_RDBRACKET, "rdbracket", TG::RBrace, 5);
763 break;
764 case LineBrackets:
765 aTok = SmToken(TRLINE, MS_VERTLINE, "rline", TG::RBrace, 5);
766 break;
767 case DoubleLineBrackets:
768 aTok = SmToken(TRDLINE, MS_DVERTLINE, "rdline", TG::RBrace, 5);
769 break;
770 case CurlyBrackets:
771 aTok = SmToken(TRBRACE, MS_RBRACE, "rbrace", TG::RBrace, 5);
772 break;
773 case AngleBrackets:
774 aTok = SmToken(TRANGLE, MS_RMATHANGLE, "rangle", TG::RBrace, 5);
775 break;
776 case CeilBrackets:
777 aTok = SmToken(TRCEIL, MS_RCEIL, "rceil", TG::RBrace, 5);
778 break;
779 case FloorBrackets:
780 aTok = SmToken(TRFLOOR, MS_RFLOOR, "rfloor", TG::RBrace, 5);
781 break;
784 SmNode* pRetVal = new SmMathSymbolNode(aTok);
785 pRetVal->SetScaleMode(SCALE_HEIGHT);
786 return pRetVal;
789 bool SmCursor::InsertRow() {
790 AnnotateSelection();
792 //Find line
793 SmNode *pLine;
794 if(HasSelection()) {
795 SmNode *pSNode = FindSelectedNode(mpTree);
796 assert(pSNode);
797 pLine = FindTopMostNodeInLine(pSNode, true);
798 } else
799 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
801 //Find parent and offset in parent
802 SmStructureNode *pLineParent = pLine->GetParent();
803 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
804 assert(nParentIndex >= 0);
806 //Discover the context of this command
807 SmTableNode *pTable = nullptr;
808 SmMatrixNode *pMatrix = nullptr;
809 int nTableIndex = nParentIndex;
810 if(pLineParent->GetType() == NTABLE)
811 pTable = static_cast<SmTableNode*>(pLineParent);
812 //If it's wrapped in a SmLineNode, we can still insert a newline
813 else if(pLineParent->GetType() == NLINE &&
814 pLineParent->GetParent() &&
815 pLineParent->GetParent()->GetType() == NTABLE) {
816 //NOTE: This hack might give problems if we stop ignoring SmAlignNode
817 pTable = static_cast<SmTableNode*>(pLineParent->GetParent());
818 nTableIndex = pTable->IndexOfSubNode(pLineParent);
819 assert(nTableIndex >= 0);
821 if(pLineParent->GetType() == NMATRIX)
822 pMatrix = static_cast<SmMatrixNode*>(pLineParent);
824 //If we're not in a context that supports InsertRow, return sal_False
825 if(!pTable && !pMatrix)
826 return false;
828 //Now we start editing
829 BeginEdit();
831 //Convert line to list
832 SmNodeList *pLineList = NodeToList(pLine);
834 //Find position in line
835 SmNodeList::iterator it;
836 if(HasSelection()) {
837 //Take the selected nodes and delete them...
838 it = TakeSelectedNodesFromList(pLineList);
839 } else
840 it = FindPositionInLineList(pLineList, mpPosition->CaretPos);
842 //New caret position after inserting the newline/row in whatever context
843 SmCaretPos PosAfterInsert;
845 //If we're in the context of a table
846 if(pTable) {
847 SmNodeList *pNewLineList = new SmNodeList();
848 //Move elements from pLineList to pNewLineList
849 pNewLineList->splice(pNewLineList->begin(), *pLineList, it, pLineList->end());
850 //Make sure it is valid again
851 it = pLineList->end();
852 if(it != pLineList->begin())
853 --it;
854 if(pNewLineList->empty())
855 pNewLineList->push_front(new SmPlaceNode());
856 //Parse new line
857 SmNode *pNewLine = SmNodeListParser().Parse(pNewLineList);
858 delete pNewLineList;
859 //Wrap pNewLine in SmLineNode if needed
860 if(pLineParent->GetType() == NLINE) {
861 SmLineNode *pNewLineNode = new SmLineNode(SmToken(TNEWLINE, '\0', "newline"));
862 pNewLineNode->SetSubNodes(pNewLine, nullptr);
863 pNewLine = pNewLineNode;
865 //Get position
866 PosAfterInsert = SmCaretPos(pNewLine, 0);
867 //Move other nodes if needed
868 for( int i = pTable->GetNumSubNodes(); i > nTableIndex + 1; i--)
869 pTable->SetSubNode(i, pTable->GetSubNode(i-1));
871 //Insert new line
872 pTable->SetSubNode(nTableIndex + 1, pNewLine);
874 //Check if we need to change token type:
875 if(pTable->GetNumSubNodes() > 2 && pTable->GetToken().eType == TBINOM) {
876 SmToken tok = pTable->GetToken();
877 tok.eType = TSTACK;
878 pTable->SetToken(tok);
881 //If we're in the context of a matrix
882 else {
883 //Find position after insert and patch the list
884 PosAfterInsert = PatchLineList(pLineList, it);
885 //Move other children
886 sal_uInt16 rows = pMatrix->GetNumRows();
887 sal_uInt16 cols = pMatrix->GetNumCols();
888 int nRowStart = (nParentIndex - nParentIndex % cols) + cols;
889 for( int i = pMatrix->GetNumSubNodes() + cols - 1; i >= nRowStart + cols; i--)
890 pMatrix->SetSubNode(i, pMatrix->GetSubNode(i - cols));
891 for( int i = nRowStart; i < nRowStart + cols; i++) {
892 SmPlaceNode *pNewLine = new SmPlaceNode();
893 if(i == nParentIndex + cols)
894 PosAfterInsert = SmCaretPos(pNewLine, 0);
895 pMatrix->SetSubNode(i, pNewLine);
897 pMatrix->SetRowCol(rows + 1, cols);
900 //Finish editing
901 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
902 //FinishEdit is actually used to handle siturations where parent is an instance of
903 //SmSubSupNode. In this case parent should always be a table or matrix, however, for
904 //code reuse we just use FinishEdit() here too.
905 return true;
908 void SmCursor::InsertFraction() {
909 AnnotateSelection();
911 //Find line
912 SmNode *pLine;
913 if(HasSelection()) {
914 SmNode *pSNode = FindSelectedNode(mpTree);
915 assert(pSNode);
916 pLine = FindTopMostNodeInLine(pSNode, true);
917 } else
918 pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode);
920 //Find Parent and offset in parent
921 SmStructureNode *pLineParent = pLine->GetParent();
922 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
923 assert(nParentIndex >= 0);
925 //We begin modifying the tree here
926 BeginEdit();
928 //Convert line to list
929 SmNodeList* pLineList = NodeToList(pLine);
931 //Take the selection, and/or find iterator for current position
932 SmNodeList* pSelectedNodesList = new SmNodeList();
933 SmNodeList::iterator it;
934 if(HasSelection())
935 it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
936 else
937 it = FindPositionInLineList(pLineList, mpPosition->CaretPos);
939 //Create pNum, and pDenom
940 bool bEmptyFraction = pSelectedNodesList->empty();
941 SmNode *pNum = bEmptyFraction
942 ? new SmPlaceNode()
943 : SmNodeListParser().Parse(pSelectedNodesList);
944 SmNode *pDenom = new SmPlaceNode();
945 delete pSelectedNodesList;
946 pSelectedNodesList = nullptr;
948 //Create new fraction
949 SmBinVerNode *pFrac = new SmBinVerNode(SmToken(TOVER, '\0', "over", TG::Product, 0));
950 SmNode *pRect = new SmRectangleNode(SmToken());
951 pFrac->SetSubNodes(pNum, pRect, pDenom);
953 //Insert in pLineList
954 SmNodeList::iterator patchIt = pLineList->insert(it, pFrac);
955 PatchLineList(pLineList, patchIt);
956 PatchLineList(pLineList, it);
958 //Finish editing
959 SmNode *pSelectedNode = bEmptyFraction ? pNum : pDenom;
960 FinishEdit(pLineList, pLineParent, nParentIndex, SmCaretPos(pSelectedNode, 1));
963 void SmCursor::InsertText(const OUString& aString)
965 BeginEdit();
967 Delete();
969 SmToken token;
970 token.eType = TIDENT;
971 token.cMathChar = '\0';
972 token.nGroup = TG::NONE;
973 token.nLevel = 5;
974 token.aText = aString;
976 SmTextNode* pText = new SmTextNode(token, FNT_VARIABLE);
977 pText->SetText(aString);
978 pText->AdjustFontDesc();
979 pText->Prepare(mpDocShell->GetFormat(), *mpDocShell);
981 SmNodeList* pList = new SmNodeList();
982 pList->push_front(pText);
983 InsertNodes(pList);
985 EndEdit();
988 void SmCursor::InsertElement(SmFormulaElement element){
989 BeginEdit();
991 Delete();
993 //Create new node
994 SmNode* pNewNode = nullptr;
995 switch(element){
996 case BlankElement:
998 SmToken token;
999 token.eType = TBLANK;
1000 token.nGroup = TG::Blank;
1001 token.aText = "~";
1002 SmBlankNode* pBlankNode = new SmBlankNode(token);
1003 pBlankNode->IncreaseBy(token);
1004 pNewNode = pBlankNode;
1005 }break;
1006 case FactorialElement:
1008 SmToken token(TFACT, MS_FACT, "fact", TG::UnOper, 5);
1009 pNewNode = new SmMathSymbolNode(token);
1010 }break;
1011 case PlusElement:
1013 SmToken token;
1014 token.eType = TPLUS;
1015 token.cMathChar = MS_PLUS;
1016 token.nGroup = TG::UnOper | TG::Sum;
1017 token.nLevel = 5;
1018 token.aText = "+";
1019 pNewNode = new SmMathSymbolNode(token);
1020 }break;
1021 case MinusElement:
1023 SmToken token;
1024 token.eType = TMINUS;
1025 token.cMathChar = MS_MINUS;
1026 token.nGroup = TG::UnOper | TG::Sum;
1027 token.nLevel = 5;
1028 token.aText = "-";
1029 pNewNode = new SmMathSymbolNode(token);
1030 }break;
1031 case CDotElement:
1033 SmToken token;
1034 token.eType = TCDOT;
1035 token.cMathChar = MS_CDOT;
1036 token.nGroup = TG::Product;
1037 token.aText = "cdot";
1038 pNewNode = new SmMathSymbolNode(token);
1039 }break;
1040 case EqualElement:
1042 SmToken token;
1043 token.eType = TASSIGN;
1044 token.cMathChar = MS_ASSIGN;
1045 token.nGroup = TG::Relation;
1046 token.aText = "=";
1047 pNewNode = new SmMathSymbolNode(token);
1048 }break;
1049 case LessThanElement:
1051 SmToken token;
1052 token.eType = TLT;
1053 token.cMathChar = MS_LT;
1054 token.nGroup = TG::Relation;
1055 token.aText = "<";
1056 pNewNode = new SmMathSymbolNode(token);
1057 }break;
1058 case GreaterThanElement:
1060 SmToken token;
1061 token.eType = TGT;
1062 token.cMathChar = MS_GT;
1063 token.nGroup = TG::Relation;
1064 token.aText = ">";
1065 pNewNode = new SmMathSymbolNode(token);
1066 }break;
1067 case PercentElement:
1069 SmToken token;
1070 token.eType = TTEXT;
1071 token.cMathChar = MS_PERCENT;
1072 token.nGroup = TG::NONE;
1073 token.aText = "\"%\"";
1074 pNewNode = new SmMathSymbolNode(token);
1075 }break;
1077 assert(pNewNode);
1079 //Prepare the new node
1080 pNewNode->Prepare(mpDocShell->GetFormat(), *mpDocShell);
1082 //Insert new node
1083 SmNodeList* pList = new SmNodeList();
1084 pList->push_front(pNewNode);
1085 InsertNodes(pList);
1087 EndEdit();
1090 void SmCursor::InsertSpecial(const OUString& _aString)
1092 BeginEdit();
1093 Delete();
1095 OUString aString = comphelper::string::strip(_aString, ' ');
1097 //Create instance of special node
1098 SmToken token;
1099 token.eType = TSPECIAL;
1100 token.cMathChar = '\0';
1101 token.nGroup = TG::NONE;
1102 token.nLevel = 5;
1103 token.aText = aString;
1104 SmSpecialNode* pSpecial = new SmSpecialNode(token);
1106 //Prepare the special node
1107 pSpecial->Prepare(mpDocShell->GetFormat(), *mpDocShell);
1109 //Insert the node
1110 SmNodeList* pList = new SmNodeList();
1111 pList->push_front(pSpecial);
1112 InsertNodes(pList);
1114 EndEdit();
1117 void SmCursor::InsertCommand(sal_uInt16 nCommand) {
1118 switch(nCommand){
1119 case RID_NEWLINE:
1120 InsertRow();
1121 break;
1122 case RID_FROMX:
1123 InsertLimit(CSUB);
1124 break;
1125 case RID_TOX:
1126 InsertLimit(CSUP);
1127 break;
1128 case RID_FROMXTOY:
1129 if(InsertLimit(CSUB))
1130 InsertLimit(CSUP);
1131 break;
1132 default:
1133 InsertCommandText(SM_RESSTR(nCommand));
1134 break;
1138 void SmCursor::InsertCommandText(const OUString& aCommandText) {
1139 //Parse the sub expression
1140 SmNode* pSubExpr = SmParser().ParseExpression(aCommandText);
1142 //Prepare the subtree
1143 pSubExpr->Prepare(mpDocShell->GetFormat(), *mpDocShell);
1145 //Convert subtree to list
1146 SmNodeList* pLineList = NodeToList(pSubExpr);
1148 BeginEdit();
1150 //Delete any selection
1151 Delete();
1153 //Insert it
1154 InsertNodes(pLineList);
1156 EndEdit();
1159 void SmCursor::Copy(){
1160 if(!HasSelection())
1161 return;
1163 AnnotateSelection();
1164 //Find selected node
1165 SmNode* pSNode = FindSelectedNode(mpTree);
1166 assert(pSNode);
1167 //Find visual line
1168 SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
1169 assert(pLine);
1171 //Clone selected nodes
1172 SmClipboard aClipboard;
1173 if(IsLineCompositionNode(pLine))
1174 CloneLineToClipboard(static_cast<SmStructureNode*>(pLine), &aClipboard);
1175 else{
1176 //Special care to only clone selected text
1177 if(pLine->GetType() == NTEXT) {
1178 SmTextNode *pText = static_cast<SmTextNode*>(pLine);
1179 std::unique_ptr<SmTextNode> pClone(new SmTextNode( pText->GetToken(), pText->GetFontDesc() ));
1180 int start = pText->GetSelectionStart(),
1181 length = pText->GetSelectionEnd() - pText->GetSelectionStart();
1182 pClone->ChangeText(pText->GetText().copy(start, length));
1183 pClone->SetScaleMode(pText->GetScaleMode());
1184 aClipboard.push_front(std::move(pClone));
1185 } else {
1186 SmCloningVisitor aCloneFactory;
1187 aClipboard.push_front(std::unique_ptr<SmNode>(aCloneFactory.Clone(pLine)));
1191 //Set clipboard
1192 if (aClipboard.size() > 0)
1193 maClipboard = std::move(aClipboard);
1196 void SmCursor::Paste() {
1197 BeginEdit();
1198 Delete();
1200 if(maClipboard.size() > 0)
1201 InsertNodes(CloneList(maClipboard));
1203 EndEdit();
1206 SmNodeList* SmCursor::CloneList(SmClipboard &rClipboard){
1207 SmCloningVisitor aCloneFactory;
1208 SmNodeList* pClones = new SmNodeList();
1210 for(auto &xNode : rClipboard){
1211 SmNode *pClone = aCloneFactory.Clone(xNode.get());
1212 pClones->push_back(pClone);
1215 return pClones;
1218 SmNode* SmCursor::FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected){
1219 assert(pSNode);
1220 //Move up parent until we find a node who's
1221 //parent is NULL or isn't selected and not a type of:
1222 // SmExpressionNode
1223 // SmLineNode
1224 // SmBinHorNode
1225 // SmUnHorNode
1226 // SmAlignNode
1227 // SmFontNode
1228 while(pSNode->GetParent() &&
1229 ((MoveUpIfSelected &&
1230 pSNode->GetParent()->IsSelected()) ||
1231 IsLineCompositionNode(pSNode->GetParent())))
1232 pSNode = pSNode->GetParent();
1233 //Now we have the selection line node
1234 return pSNode;
1237 SmNode* SmCursor::FindSelectedNode(SmNode* pNode){
1238 if(pNode->GetNumSubNodes() == 0)
1239 return nullptr;
1240 for(auto pChild : *static_cast<SmStructureNode*>(pNode))
1242 if(!pChild)
1243 continue;
1244 if(pChild->IsSelected())
1245 return pChild;
1246 SmNode* pRetVal = FindSelectedNode(pChild);
1247 if(pRetVal)
1248 return pRetVal;
1250 return nullptr;
1253 SmNodeList* SmCursor::LineToList(SmStructureNode* pLine, SmNodeList* list){
1254 for(auto pChild : *pLine)
1256 if (!pChild)
1257 continue;
1258 switch(pChild->GetType()){
1259 case NLINE:
1260 case NUNHOR:
1261 case NEXPRESSION:
1262 case NBINHOR:
1263 case NALIGN:
1264 case NFONT:
1265 LineToList(static_cast<SmStructureNode*>(pChild), list);
1266 break;
1267 case NERROR:
1268 delete pChild;
1269 break;
1270 default:
1271 list->push_back(pChild);
1274 SmNodeArray emptyArray(0);
1275 pLine->SetSubNodes(emptyArray);
1276 delete pLine;
1277 return list;
1280 void SmCursor::CloneLineToClipboard(SmStructureNode* pLine, SmClipboard* pClipboard){
1281 SmCloningVisitor aCloneFactory;
1282 for(auto pChild : *pLine)
1284 if (!pChild)
1285 continue;
1286 if( IsLineCompositionNode( pChild ) )
1287 CloneLineToClipboard( static_cast<SmStructureNode*>(pChild), pClipboard );
1288 else if( pChild->IsSelected() && pChild->GetType() != NERROR ) {
1289 //Only clone selected text from SmTextNode
1290 if(pChild->GetType() == NTEXT) {
1291 SmTextNode *pText = static_cast<SmTextNode*>(pChild);
1292 std::unique_ptr<SmTextNode> pClone(new SmTextNode( pChild->GetToken(), pText->GetFontDesc() ));
1293 int start = pText->GetSelectionStart(),
1294 length = pText->GetSelectionEnd() - pText->GetSelectionStart();
1295 pClone->ChangeText(pText->GetText().copy(start, length));
1296 pClone->SetScaleMode(pText->GetScaleMode());
1297 pClipboard->push_back(std::move(pClone));
1298 } else
1299 pClipboard->push_back(std::unique_ptr<SmNode>(aCloneFactory.Clone(pChild)));
1304 bool SmCursor::IsLineCompositionNode(SmNode* pNode){
1305 switch(pNode->GetType()){
1306 case NLINE:
1307 case NUNHOR:
1308 case NEXPRESSION:
1309 case NBINHOR:
1310 case NALIGN:
1311 case NFONT:
1312 return true;
1313 default:
1314 return false;
1318 int SmCursor::CountSelectedNodes(SmNode* pNode){
1319 if(pNode->GetNumSubNodes() == 0)
1320 return 0;
1321 int nCount = 0;
1322 for(auto pChild : *static_cast<SmStructureNode*>(pNode))
1324 if (!pChild)
1325 continue;
1326 if(pChild->IsSelected() && !IsLineCompositionNode(pChild))
1327 nCount++;
1328 nCount += CountSelectedNodes(pChild);
1330 return nCount;
1333 bool SmCursor::HasComplexSelection(){
1334 if(!HasSelection())
1335 return false;
1336 AnnotateSelection();
1338 return CountSelectedNodes(mpTree) > 1;
1341 void SmCursor::FinishEdit(SmNodeList* pLineList,
1342 SmStructureNode* pParent,
1343 int nParentIndex,
1344 SmCaretPos PosAfterEdit,
1345 SmNode* pStartLine) {
1346 //Store number of nodes in line for later
1347 int entries = pLineList->size();
1349 //Parse list of nodes to a tree
1350 SmNodeListParser parser;
1351 SmNode* pLine = parser.Parse(pLineList);
1352 delete pLineList;
1354 //Check if we're making the body of a subsup node bigger than one
1355 if(pParent->GetType() == NSUBSUP &&
1356 nParentIndex == 0 &&
1357 entries > 1) {
1358 //Wrap pLine in scalable round brackets
1359 SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5);
1360 SmBraceNode *pBrace = new SmBraceNode(aTok);
1361 pBrace->SetScaleMode(SCALE_HEIGHT);
1362 SmNode *pLeft = CreateBracket(RoundBrackets, true),
1363 *pRight = CreateBracket(RoundBrackets, false);
1364 SmBracebodyNode *pBody = new SmBracebodyNode(SmToken());
1365 pBody->SetSubNodes(pLine, nullptr);
1366 pBrace->SetSubNodes(pLeft, pBody, pRight);
1367 pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell);
1368 pLine = pBrace;
1369 //TODO: Consider the following alternative behavior:
1370 //Consider the line: A + {B + C}^D lsub E
1371 //Here pLineList is B, + and C and pParent is a subsup node with
1372 //both RSUP and LSUB set. Imagine the user just inserted "B +" in
1373 //the body of the subsup node...
1374 //The most natural thing to do would be to make the line like this:
1375 //A + B lsub E + C ^ D
1376 //E.g. apply LSUB and LSUP to the first element in pLineList and RSUP
1377 //and RSUB to the last element in pLineList. But how should this act
1378 //for CSUP and CSUB ???
1379 //For this reason and because brackets was faster to implement, this solution
1380 //have been chosen. It might be worth working on the other solution later...
1383 //Set pStartLine if NULL
1384 if(!pStartLine)
1385 pStartLine = pLine;
1387 //Insert it back into the parent
1388 pParent->SetSubNode(nParentIndex, pLine);
1390 //Rebuild graph of caret position
1391 mpAnchor = nullptr;
1392 mpPosition = nullptr;
1393 BuildGraph();
1394 AnnotateSelection(); //Update selection annotation!
1396 //Set caret position
1397 if(!SetCaretPosition(PosAfterEdit))
1398 SetCaretPosition(SmCaretPos(pStartLine, 0));
1400 //End edit section
1401 EndEdit();
1404 void SmCursor::BeginEdit(){
1405 if(mnEditSections++ > 0) return;
1407 mbIsEnabledSetModifiedSmDocShell = mpDocShell->IsEnableSetModified();
1408 if( mbIsEnabledSetModifiedSmDocShell )
1409 mpDocShell->EnableSetModified( false );
1412 void SmCursor::EndEdit(){
1413 if(--mnEditSections > 0) return;
1415 mpDocShell->SetFormulaArranged(false);
1416 //Okay, I don't know what this does... :)
1417 //It's used in SmDocShell::SetText and with places where everything is modified.
1418 //I think it does some magic, with sfx, but everything is totally undocumented so
1419 //it's kinda hard to tell...
1420 if ( mbIsEnabledSetModifiedSmDocShell )
1421 mpDocShell->EnableSetModified( mbIsEnabledSetModifiedSmDocShell );
1422 //I think this notifies people around us that we've modified this document...
1423 mpDocShell->SetModified();
1424 //I think SmDocShell uses this value when it sends an update graphics event
1425 //Anyway comments elsewhere suggests it need to be updated...
1426 mpDocShell->mnModifyCount++;
1428 //TODO: Consider copying the update accessibility code from SmDocShell::SetText in here...
1429 //This somehow updates the size of SmGraphicView if it is running in embedded mode
1430 if( mpDocShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED )
1431 mpDocShell->OnDocumentPrinterChanged(nullptr);
1433 //Request a repaint...
1434 RequestRepaint();
1436 //Update the edit engine and text of the document
1437 OUString formula;
1438 SmNodeToTextVisitor(mpTree, formula);
1439 //mpTree->CreateTextFromNode(formula);
1440 mpDocShell->maText = formula;
1441 mpDocShell->GetEditEngine().QuickInsertText( formula, ESelection( 0, 0, EE_PARA_ALL, EE_TEXTPOS_ALL ) );
1442 mpDocShell->GetEditEngine().QuickFormatDoc();
1445 void SmCursor::RequestRepaint(){
1446 SmViewShell *pViewSh = SmGetActiveView();
1447 if( pViewSh ) {
1448 if ( SfxObjectCreateMode::EMBEDDED == mpDocShell->GetCreateMode() )
1449 mpDocShell->Repaint();
1450 else
1451 pViewSh->GetGraphicWindow().Invalidate();
1455 bool SmCursor::IsAtTailOfBracket(SmBracketType eBracketType, SmBraceNode** ppBraceNode) const {
1456 const SmCaretPos pos = GetPosition();
1457 if (!pos.IsValid()) {
1458 return false;
1461 SmNode* pNode = pos.pSelectedNode;
1463 if (pNode->GetType() == NTEXT) {
1464 SmTextNode* pTextNode = static_cast<SmTextNode*>(pNode);
1465 if (pos.Index < pTextNode->GetText().getLength()) {
1466 // The cursor is on a text node and at the middle of it.
1467 return false;
1469 } else {
1470 if (pos.Index < 1) {
1471 return false;
1475 while (true) {
1476 SmStructureNode* pParentNode = pNode->GetParent();
1477 if (!pParentNode) {
1478 // There's no brace body node in the ancestors.
1479 return false;
1482 int index = pParentNode->IndexOfSubNode(pNode);
1483 assert(index >= 0);
1484 if (index + 1 != pParentNode->GetNumSubNodes()) {
1485 // The cursor is not at the tail at one of ancestor nodes.
1486 return false;
1489 pNode = pParentNode;
1490 if (pNode->GetType() == NBRACEBODY) {
1491 // Found the brace body node.
1492 break;
1496 SmStructureNode* pBraceNodeTmp = pNode->GetParent();
1497 if (!pBraceNodeTmp || pBraceNodeTmp->GetType() != NBRACE) {
1498 // Brace node is invalid.
1499 return false;
1502 SmBraceNode* pBraceNode = static_cast<SmBraceNode*>(pBraceNodeTmp);
1503 SmMathSymbolNode* pClosingNode = pBraceNode->ClosingBrace();
1504 if (!pClosingNode) {
1505 // Couldn't get closing symbol node.
1506 return false;
1509 // Check if the closing brace matches eBracketType.
1510 SmTokenType eClosingTokenType = pClosingNode->GetToken().eType;
1511 switch (eBracketType) {
1512 case NoneBrackets: if (eClosingTokenType != TNONE) { return false; } break;
1513 case RoundBrackets: if (eClosingTokenType != TRPARENT) { return false; } break;
1514 case SquareBrackets: if (eClosingTokenType != TRBRACKET) { return false; } break;
1515 case DoubleSquareBrackets: if (eClosingTokenType != TRDBRACKET) { return false; } break;
1516 case LineBrackets: if (eClosingTokenType != TRLINE) { return false; } break;
1517 case DoubleLineBrackets: if (eClosingTokenType != TRDLINE) { return false; } break;
1518 case CurlyBrackets: if (eClosingTokenType != TRBRACE) { return false; } break;
1519 case AngleBrackets: if (eClosingTokenType != TRANGLE) { return false; } break;
1520 case CeilBrackets: if (eClosingTokenType != TRCEIL) { return false; } break;
1521 case FloorBrackets: if (eClosingTokenType != TRFLOOR) { return false; } break;
1522 default:
1523 return false;
1526 if (ppBraceNode) {
1527 *ppBraceNode = pBraceNode;
1530 return true;
1533 void SmCursor::MoveAfterBracket(SmBraceNode* pBraceNode)
1535 mpPosition->CaretPos.pSelectedNode = pBraceNode;
1536 mpPosition->CaretPos.Index = 1;
1537 mpAnchor->CaretPos.pSelectedNode = pBraceNode;
1538 mpAnchor->CaretPos.Index = 1;
1539 RequestRepaint();
1543 /////////////////////////////////////// SmNodeListParser
1545 SmNode* SmNodeListParser::Parse(SmNodeList* list){
1546 pList = list;
1547 //Delete error nodes
1548 SmNodeList::iterator it = pList->begin();
1549 while(it != pList->end()) {
1550 if((*it)->GetType() == NERROR){
1551 //Delete and erase
1552 delete *it;
1553 it = pList->erase(it);
1554 }else
1555 ++it;
1557 SmNode* retval = Expression();
1558 pList = nullptr;
1559 return retval;
1562 SmNode* SmNodeListParser::Expression(){
1563 SmNodeArray NodeArray;
1564 //Accept as many relations as there is
1565 while(Terminal())
1566 NodeArray.push_back(Relation());
1568 //Create SmExpressionNode, I hope SmToken() will do :)
1569 SmStructureNode* pExpr = new SmExpressionNode(SmToken());
1570 pExpr->SetSubNodes(NodeArray);
1571 return pExpr;
1574 SmNode* SmNodeListParser::Relation(){
1575 //Read a sum
1576 SmNode* pLeft = Sum();
1577 //While we have tokens and the next is a relation
1578 while(Terminal() && IsRelationOperator(Terminal()->GetToken())){
1579 //Take the operator
1580 SmNode* pOper = Take();
1581 //Find the right side of the relation
1582 SmNode* pRight = Sum();
1583 //Create new SmBinHorNode
1584 SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
1585 pNewNode->SetSubNodes(pLeft, pOper, pRight);
1586 pLeft = pNewNode;
1588 return pLeft;
1591 SmNode* SmNodeListParser::Sum(){
1592 //Read a product
1593 SmNode* pLeft = Product();
1594 //While we have tokens and the next is a sum
1595 while(Terminal() && IsSumOperator(Terminal()->GetToken())){
1596 //Take the operator
1597 SmNode* pOper = Take();
1598 //Find the right side of the sum
1599 SmNode* pRight = Product();
1600 //Create new SmBinHorNode
1601 SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
1602 pNewNode->SetSubNodes(pLeft, pOper, pRight);
1603 pLeft = pNewNode;
1605 return pLeft;
1608 SmNode* SmNodeListParser::Product(){
1609 //Read a Factor
1610 SmNode* pLeft = Factor();
1611 //While we have tokens and the next is a product
1612 while(Terminal() && IsProductOperator(Terminal()->GetToken())){
1613 //Take the operator
1614 SmNode* pOper = Take();
1615 //Find the right side of the operation
1616 SmNode* pRight = Factor();
1617 //Create new SmBinHorNode
1618 SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
1619 pNewNode->SetSubNodes(pLeft, pOper, pRight);
1620 pLeft = pNewNode;
1622 return pLeft;
1625 SmNode* SmNodeListParser::Factor(){
1626 //Read unary operations
1627 if(!Terminal())
1628 return Error();
1629 //Take care of unary operators
1630 else if(IsUnaryOperator(Terminal()->GetToken()))
1632 SmStructureNode *pUnary = new SmUnHorNode(SmToken());
1633 SmNode *pOper = Terminal(),
1634 *pArg;
1636 if(Next())
1637 pArg = Factor();
1638 else
1639 pArg = Error();
1641 pUnary->SetSubNodes(pOper, pArg);
1642 return pUnary;
1644 return Postfix();
1647 SmNode* SmNodeListParser::Postfix(){
1648 if(!Terminal())
1649 return Error();
1650 SmNode *pArg = nullptr;
1651 if(IsPostfixOperator(Terminal()->GetToken()))
1652 pArg = Error();
1653 else if(IsOperator(Terminal()->GetToken()))
1654 return Error();
1655 else
1656 pArg = Take();
1657 while(Terminal() && IsPostfixOperator(Terminal()->GetToken())) {
1658 SmStructureNode *pUnary = new SmUnHorNode(SmToken());
1659 SmNode *pOper = Take();
1660 pUnary->SetSubNodes(pArg, pOper);
1661 pArg = pUnary;
1663 return pArg;
1666 SmNode* SmNodeListParser::Error(){
1667 return new SmErrorNode(SmToken());
1670 bool SmNodeListParser::IsOperator(const SmToken &token) {
1671 return IsRelationOperator(token) ||
1672 IsSumOperator(token) ||
1673 IsProductOperator(token) ||
1674 IsUnaryOperator(token) ||
1675 IsPostfixOperator(token);
1678 bool SmNodeListParser::IsRelationOperator(const SmToken &token) {
1679 return bool(token.nGroup & TG::Relation);
1682 bool SmNodeListParser::IsSumOperator(const SmToken &token) {
1683 return bool(token.nGroup & TG::Sum);
1686 bool SmNodeListParser::IsProductOperator(const SmToken &token) {
1687 return token.nGroup & TG::Product &&
1688 token.eType != TWIDESLASH &&
1689 token.eType != TWIDEBACKSLASH &&
1690 token.eType != TUNDERBRACE &&
1691 token.eType != TOVERBRACE &&
1692 token.eType != TOVER;
1695 bool SmNodeListParser::IsUnaryOperator(const SmToken &token) {
1696 return token.nGroup & TG::UnOper &&
1697 (token.eType == TPLUS ||
1698 token.eType == TMINUS ||
1699 token.eType == TPLUSMINUS ||
1700 token.eType == TMINUSPLUS ||
1701 token.eType == TNEG ||
1702 token.eType == TUOPER);
1705 bool SmNodeListParser::IsPostfixOperator(const SmToken &token) {
1706 return token.eType == TFACT;
1709 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */