1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #include "visitors.hxx"
11 #include "document.hxx"
13 #include "accessibility.hxx"
14 #include <comphelper/string.hxx>
17 void SmCursor::Move(OutputDevice
* pDev
, SmMovementDirection direction
, bool bMoveAnchor
){
18 SmCaretPosGraphEntry
* NewPos
= nullptr;
23 NewPos
= mpPosition
->Left
;
24 OSL_ENSURE(NewPos
, "NewPos shouldn't be NULL here!");
28 NewPos
= mpPosition
->Right
;
29 OSL_ENSURE(NewPos
, "NewPos shouldn't be NULL here!");
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.
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;
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
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
);
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
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
110 void SmCursor::BuildGraph(){
111 //Save the current anchor and position
112 SmCaretPos _anchor
, _position
;
113 //Release mpGraph if allocated
116 _anchor
= mpAnchor
->CaretPos
;
118 _position
= mpPosition
->CaretPos
;
120 //Reset anchor and position as they point into an old graph
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());
142 mpPosition
= it
->get();
144 mpAnchor
= mpPosition
;
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();
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
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
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();
200 //Convert second line to list
201 NodeToList(pLine
, pLineList
);
202 //Patch the line list
204 PosAfterDelete
= PatchLineList(pLineList
, patchPoint
);
206 pLine
= SmNodeListParser().Parse(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
++){
214 lines
[i
] = pLineParent
->GetSubNode(i
);
215 else if(i
> nLineOffset
)
216 lines
[i
-1] = pLineParent
->GetSubNode(i
);
218 pLineParent
->SetSubNodes(lines
);
221 mpPosition
= nullptr;
225 if(!SetCaretPosition(PosAfterDelete
))
226 SetCaretPosition(SmCaretPos(pLine
, 0));
230 //TODO: If we're in an empty (sub/super/*) script
231 /*}else if(pLineParent->GetType() == NSUBSUP &&
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
242 this->Move(pDev
, MoveLeft
, false);
243 if(!this->HasComplexSelection())
248 void SmCursor::Delete(){
249 //Return if we don't have a selection to delete
256 //Set selected on nodes
259 //Find an arbitrary selected node
260 SmNode
* pSNode
= FindSelectedNode(mpTree
);
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
);
285 FinishEdit(pLineList
, pLineParent
, nLineOffset
, PosAfterDelete
);
288 void SmCursor::InsertNodes(SmNodeList
* pNewNodes
){
289 if(pNewNodes
->empty()){
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)
318 for(newIt
= pNewNodes
->begin(); newIt
!= pNewNodes
->end(); ++newIt
){
319 insIt
= pLineList
->insert(it
, *newIt
);
320 if(newIt
== pNewNodes
->begin())
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
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())
350 OUString str1
= pText
->GetText().copy(0, rCaretPos
.Index
);
351 OUString str2
= pText
->GetText().copy(rCaretPos
.Index
);
352 pText
->ChangeText(str1
);
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
);
362 //it now pointer to the node following pos, so pLineList->insert(it, ...) will insert correctly
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,
375 if(aIter
!= pLineList
->end())
377 if(aIter
!= pLineList
->begin()) {
383 //Check if there's textnodes to merge
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());
394 newText
+= pText
->GetText();
395 newText
+= pOldN
->GetText();
396 pText
->ChangeText(newText
);
398 pLineList
->erase(aIter
);
402 //Check if there's a SmPlaceNode to remove:
403 if(prev
&& next
&& prev
->GetType() == NPLACE
&& !SmNodeListParser::IsOperator(next
->GetToken())){
405 aIter
= pLineList
->erase(aIter
);
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())
412 return SmCaretPos::GetPosAfter(*aIter
);
414 if(prev
&& next
&& next
->GetType() == NPLACE
&& !SmNodeListParser::IsOperator(prev
->GetToken())){
415 aIter
= pLineList
->erase(aIter
);
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
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()){
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(),
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
447 OUString str
= aText
.copy(start1
, len1
);
448 pText
->ChangeText(str
);
450 } else {//Remove it if not needed
451 it
= pLineList
->erase(it
);
454 //Set retval to be right after the selection
456 //if we need make segment 3
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
472 retval
= it
= pLineList
->erase(it
);
474 pSelectedNodes
->push_back(pNode
);
484 void SmCursor::InsertSubSup(SmSubSup eSubSup
) {
490 SmNode
*pSNode
= FindSelectedNode(mpTree
);
492 pLine
= FindTopMostNodeInLine(pSNode
, true);
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
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
;
514 it
= TakeSelectedNodesFromList(pLineList
, pSelectedNodesList
);
516 it
= FindPositionInLineList(pLineList
, mpPosition
->CaretPos
);
518 //Find node that this should be applied to
520 bool bPatchLine
= pSelectedNodesList
->size() > 0; //If the line should be patched later
521 if(it
!= pLineList
->begin()) {
526 //Create a new place node
527 pSubject
= new SmPlaceNode();
528 pSubject
->Prepare(mpDocShell
->GetFormat(), *mpDocShell
);
529 it
= pLineList
->insert(it
, pSubject
);
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
){
538 token
.nGroup
= TG::Power
;
539 pSubSup
= new SmSubSupNode(token
);
540 pSubSup
->SetBody(pSubject
);
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.
549 //Patch the line if we noted that was needed previously
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
);
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
);
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
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
613 token
.nGroup
= TG::Limit
;
614 pSubSup
= new SmSubSupNode(token
);
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
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
);
636 pSubSup
->SetSubSup(eSubSup
, pLine
);
639 //Rebuild graph of caret positions
644 if(!SetCaretPosition(PosAfterLimit
))
645 SetCaretPosition(SmCaretPos(pLine
, 0));
652 void SmCursor::InsertBrackets(SmBracketType eBracketType
) {
660 SmNode
*pSNode
= FindSelectedNode(mpTree
);
662 pLine
= FindTopMostNodeInLine(pSNode
, true);
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
;
678 it
= TakeSelectedNodesFromList(pLineList
, pSelectedNodesList
);
680 it
= FindPositionInLineList(pLineList
, mpPosition
->CaretPos
);
682 //If there's no selected nodes, create a place node
684 SmCaretPos PosAfterInsert
;
685 if(pSelectedNodesList
->empty()) {
686 pBodyNode
= new SmPlaceNode();
687 PosAfterInsert
= SmCaretPos(pBodyNode
, 1);
689 pBodyNode
= SmNodeListParser().Parse(pSelectedNodesList
);
691 delete pSelectedNodesList
;
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
);
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
;
712 FinishEdit(pLineList
, pLineParent
, nParentIndex
, PosAfterInsert
);
715 SmNode
*SmCursor::CreateBracket(SmBracketType eBracketType
, bool bIsLeft
) {
718 switch(eBracketType
){
720 aTok
= SmToken(TNONE
, '\0', "none", TG::LBrace
| TG::RBrace
, 0);
723 aTok
= SmToken(TLPARENT
, MS_LPARENT
, "(", TG::LBrace
, 5);
726 aTok
= SmToken(TLBRACKET
, MS_LBRACKET
, "[", TG::LBrace
, 5);
728 case DoubleSquareBrackets
:
729 aTok
= SmToken(TLDBRACKET
, MS_LDBRACKET
, "ldbracket", TG::LBrace
, 5);
732 aTok
= SmToken(TLLINE
, MS_VERTLINE
, "lline", TG::LBrace
, 5);
734 case DoubleLineBrackets
:
735 aTok
= SmToken(TLDLINE
, MS_DVERTLINE
, "ldline", TG::LBrace
, 5);
738 aTok
= SmToken(TLBRACE
, MS_LBRACE
, "lbrace", TG::LBrace
, 5);
741 aTok
= SmToken(TLANGLE
, MS_LMATHANGLE
, "langle", TG::LBrace
, 5);
744 aTok
= SmToken(TLCEIL
, MS_LCEIL
, "lceil", TG::LBrace
, 5);
747 aTok
= SmToken(TLFLOOR
, MS_LFLOOR
, "lfloor", TG::LBrace
, 5);
751 switch(eBracketType
) {
753 aTok
= SmToken(TNONE
, '\0', "none", TG::LBrace
| TG::RBrace
, 0);
756 aTok
= SmToken(TRPARENT
, MS_RPARENT
, ")", TG::RBrace
, 5);
759 aTok
= SmToken(TRBRACKET
, MS_RBRACKET
, "]", TG::RBrace
, 5);
761 case DoubleSquareBrackets
:
762 aTok
= SmToken(TRDBRACKET
, MS_RDBRACKET
, "rdbracket", TG::RBrace
, 5);
765 aTok
= SmToken(TRLINE
, MS_VERTLINE
, "rline", TG::RBrace
, 5);
767 case DoubleLineBrackets
:
768 aTok
= SmToken(TRDLINE
, MS_DVERTLINE
, "rdline", TG::RBrace
, 5);
771 aTok
= SmToken(TRBRACE
, MS_RBRACE
, "rbrace", TG::RBrace
, 5);
774 aTok
= SmToken(TRANGLE
, MS_RMATHANGLE
, "rangle", TG::RBrace
, 5);
777 aTok
= SmToken(TRCEIL
, MS_RCEIL
, "rceil", TG::RBrace
, 5);
780 aTok
= SmToken(TRFLOOR
, MS_RFLOOR
, "rfloor", TG::RBrace
, 5);
784 SmNode
* pRetVal
= new SmMathSymbolNode(aTok
);
785 pRetVal
->SetScaleMode(SCALE_HEIGHT
);
789 bool SmCursor::InsertRow() {
795 SmNode
*pSNode
= FindSelectedNode(mpTree
);
797 pLine
= FindTopMostNodeInLine(pSNode
, true);
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
)
828 //Now we start editing
831 //Convert line to list
832 SmNodeList
*pLineList
= NodeToList(pLine
);
834 //Find position in line
835 SmNodeList::iterator it
;
837 //Take the selected nodes and delete them...
838 it
= TakeSelectedNodesFromList(pLineList
);
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
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())
854 if(pNewLineList
->empty())
855 pNewLineList
->push_front(new SmPlaceNode());
857 SmNode
*pNewLine
= SmNodeListParser().Parse(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
;
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));
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();
878 pTable
->SetToken(tok
);
881 //If we're in the context of a matrix
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
);
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.
908 void SmCursor::InsertFraction() {
914 SmNode
*pSNode
= FindSelectedNode(mpTree
);
916 pLine
= FindTopMostNodeInLine(pSNode
, true);
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
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
;
935 it
= TakeSelectedNodesFromList(pLineList
, pSelectedNodesList
);
937 it
= FindPositionInLineList(pLineList
, mpPosition
->CaretPos
);
939 //Create pNum, and pDenom
940 bool bEmptyFraction
= pSelectedNodesList
->empty();
941 SmNode
*pNum
= bEmptyFraction
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
);
959 SmNode
*pSelectedNode
= bEmptyFraction
? pNum
: pDenom
;
960 FinishEdit(pLineList
, pLineParent
, nParentIndex
, SmCaretPos(pSelectedNode
, 1));
963 void SmCursor::InsertText(const OUString
& aString
)
970 token
.eType
= TIDENT
;
971 token
.cMathChar
= '\0';
972 token
.nGroup
= TG::NONE
;
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
);
988 void SmCursor::InsertElement(SmFormulaElement element
){
994 SmNode
* pNewNode
= nullptr;
999 token
.eType
= TBLANK
;
1000 token
.nGroup
= TG::Blank
;
1002 SmBlankNode
* pBlankNode
= new SmBlankNode(token
);
1003 pBlankNode
->IncreaseBy(token
);
1004 pNewNode
= pBlankNode
;
1006 case FactorialElement
:
1008 SmToken
token(TFACT
, MS_FACT
, "fact", TG::UnOper
, 5);
1009 pNewNode
= new SmMathSymbolNode(token
);
1014 token
.eType
= TPLUS
;
1015 token
.cMathChar
= MS_PLUS
;
1016 token
.nGroup
= TG::UnOper
| TG::Sum
;
1019 pNewNode
= new SmMathSymbolNode(token
);
1024 token
.eType
= TMINUS
;
1025 token
.cMathChar
= MS_MINUS
;
1026 token
.nGroup
= TG::UnOper
| TG::Sum
;
1029 pNewNode
= new SmMathSymbolNode(token
);
1034 token
.eType
= TCDOT
;
1035 token
.cMathChar
= MS_CDOT
;
1036 token
.nGroup
= TG::Product
;
1037 token
.aText
= "cdot";
1038 pNewNode
= new SmMathSymbolNode(token
);
1043 token
.eType
= TASSIGN
;
1044 token
.cMathChar
= MS_ASSIGN
;
1045 token
.nGroup
= TG::Relation
;
1047 pNewNode
= new SmMathSymbolNode(token
);
1049 case LessThanElement
:
1053 token
.cMathChar
= MS_LT
;
1054 token
.nGroup
= TG::Relation
;
1056 pNewNode
= new SmMathSymbolNode(token
);
1058 case GreaterThanElement
:
1062 token
.cMathChar
= MS_GT
;
1063 token
.nGroup
= TG::Relation
;
1065 pNewNode
= new SmMathSymbolNode(token
);
1067 case PercentElement
:
1070 token
.eType
= TTEXT
;
1071 token
.cMathChar
= MS_PERCENT
;
1072 token
.nGroup
= TG::NONE
;
1073 token
.aText
= "\"%\"";
1074 pNewNode
= new SmMathSymbolNode(token
);
1079 //Prepare the new node
1080 pNewNode
->Prepare(mpDocShell
->GetFormat(), *mpDocShell
);
1083 SmNodeList
* pList
= new SmNodeList();
1084 pList
->push_front(pNewNode
);
1090 void SmCursor::InsertSpecial(const OUString
& _aString
)
1095 OUString aString
= comphelper::string::strip(_aString
, ' ');
1097 //Create instance of special node
1099 token
.eType
= TSPECIAL
;
1100 token
.cMathChar
= '\0';
1101 token
.nGroup
= TG::NONE
;
1103 token
.aText
= aString
;
1104 SmSpecialNode
* pSpecial
= new SmSpecialNode(token
);
1106 //Prepare the special node
1107 pSpecial
->Prepare(mpDocShell
->GetFormat(), *mpDocShell
);
1110 SmNodeList
* pList
= new SmNodeList();
1111 pList
->push_front(pSpecial
);
1117 void SmCursor::InsertCommand(sal_uInt16 nCommand
) {
1129 if(InsertLimit(CSUB
))
1133 InsertCommandText(SM_RESSTR(nCommand
));
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
);
1150 //Delete any selection
1154 InsertNodes(pLineList
);
1159 void SmCursor::Copy(){
1163 AnnotateSelection();
1164 //Find selected node
1165 SmNode
* pSNode
= FindSelectedNode(mpTree
);
1168 SmNode
* pLine
= FindTopMostNodeInLine(pSNode
, true);
1171 //Clone selected nodes
1172 SmClipboard aClipboard
;
1173 if(IsLineCompositionNode(pLine
))
1174 CloneLineToClipboard(static_cast<SmStructureNode
*>(pLine
), &aClipboard
);
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
));
1186 SmCloningVisitor aCloneFactory
;
1187 aClipboard
.push_front(std::unique_ptr
<SmNode
>(aCloneFactory
.Clone(pLine
)));
1192 if (aClipboard
.size() > 0)
1193 maClipboard
= std::move(aClipboard
);
1196 void SmCursor::Paste() {
1200 if(maClipboard
.size() > 0)
1201 InsertNodes(CloneList(maClipboard
));
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
);
1218 SmNode
* SmCursor::FindTopMostNodeInLine(SmNode
* pSNode
, bool MoveUpIfSelected
){
1220 //Move up parent until we find a node who's
1221 //parent is NULL or isn't selected and not a type of:
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
1237 SmNode
* SmCursor::FindSelectedNode(SmNode
* pNode
){
1238 if(pNode
->GetNumSubNodes() == 0)
1240 for(auto pChild
: *static_cast<SmStructureNode
*>(pNode
))
1244 if(pChild
->IsSelected())
1246 SmNode
* pRetVal
= FindSelectedNode(pChild
);
1253 SmNodeList
* SmCursor::LineToList(SmStructureNode
* pLine
, SmNodeList
* list
){
1254 for(auto pChild
: *pLine
)
1258 switch(pChild
->GetType()){
1265 LineToList(static_cast<SmStructureNode
*>(pChild
), list
);
1271 list
->push_back(pChild
);
1274 SmNodeArray
emptyArray(0);
1275 pLine
->SetSubNodes(emptyArray
);
1280 void SmCursor::CloneLineToClipboard(SmStructureNode
* pLine
, SmClipboard
* pClipboard
){
1281 SmCloningVisitor aCloneFactory
;
1282 for(auto pChild
: *pLine
)
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
));
1299 pClipboard
->push_back(std::unique_ptr
<SmNode
>(aCloneFactory
.Clone(pChild
)));
1304 bool SmCursor::IsLineCompositionNode(SmNode
* pNode
){
1305 switch(pNode
->GetType()){
1318 int SmCursor::CountSelectedNodes(SmNode
* pNode
){
1319 if(pNode
->GetNumSubNodes() == 0)
1322 for(auto pChild
: *static_cast<SmStructureNode
*>(pNode
))
1326 if(pChild
->IsSelected() && !IsLineCompositionNode(pChild
))
1328 nCount
+= CountSelectedNodes(pChild
);
1333 bool SmCursor::HasComplexSelection(){
1336 AnnotateSelection();
1338 return CountSelectedNodes(mpTree
) > 1;
1341 void SmCursor::FinishEdit(SmNodeList
* pLineList
,
1342 SmStructureNode
* pParent
,
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
);
1354 //Check if we're making the body of a subsup node bigger than one
1355 if(pParent
->GetType() == NSUBSUP
&&
1356 nParentIndex
== 0 &&
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
);
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
1387 //Insert it back into the parent
1388 pParent
->SetSubNode(nParentIndex
, pLine
);
1390 //Rebuild graph of caret position
1392 mpPosition
= nullptr;
1394 AnnotateSelection(); //Update selection annotation!
1396 //Set caret position
1397 if(!SetCaretPosition(PosAfterEdit
))
1398 SetCaretPosition(SmCaretPos(pStartLine
, 0));
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...
1436 //Update the edit engine and text of the document
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();
1448 if ( SfxObjectCreateMode::EMBEDDED
== mpDocShell
->GetCreateMode() )
1449 mpDocShell
->Repaint();
1451 pViewSh
->GetGraphicWindow().Invalidate();
1455 bool SmCursor::IsAtTailOfBracket(SmBracketType eBracketType
, SmBraceNode
** ppBraceNode
) const {
1456 const SmCaretPos pos
= GetPosition();
1457 if (!pos
.IsValid()) {
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.
1470 if (pos
.Index
< 1) {
1476 SmStructureNode
* pParentNode
= pNode
->GetParent();
1478 // There's no brace body node in the ancestors.
1482 int index
= pParentNode
->IndexOfSubNode(pNode
);
1484 if (index
+ 1 != pParentNode
->GetNumSubNodes()) {
1485 // The cursor is not at the tail at one of ancestor nodes.
1489 pNode
= pParentNode
;
1490 if (pNode
->GetType() == NBRACEBODY
) {
1491 // Found the brace body node.
1496 SmStructureNode
* pBraceNodeTmp
= pNode
->GetParent();
1497 if (!pBraceNodeTmp
|| pBraceNodeTmp
->GetType() != NBRACE
) {
1498 // Brace node is invalid.
1502 SmBraceNode
* pBraceNode
= static_cast<SmBraceNode
*>(pBraceNodeTmp
);
1503 SmMathSymbolNode
* pClosingNode
= pBraceNode
->ClosingBrace();
1504 if (!pClosingNode
) {
1505 // Couldn't get closing symbol node.
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;
1527 *ppBraceNode
= pBraceNode
;
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;
1543 /////////////////////////////////////// SmNodeListParser
1545 SmNode
* SmNodeListParser::Parse(SmNodeList
* list
){
1547 //Delete error nodes
1548 SmNodeList::iterator it
= pList
->begin();
1549 while(it
!= pList
->end()) {
1550 if((*it
)->GetType() == NERROR
){
1553 it
= pList
->erase(it
);
1557 SmNode
* retval
= Expression();
1562 SmNode
* SmNodeListParser::Expression(){
1563 SmNodeArray NodeArray
;
1564 //Accept as many relations as there is
1566 NodeArray
.push_back(Relation());
1568 //Create SmExpressionNode, I hope SmToken() will do :)
1569 SmStructureNode
* pExpr
= new SmExpressionNode(SmToken());
1570 pExpr
->SetSubNodes(NodeArray
);
1574 SmNode
* SmNodeListParser::Relation(){
1576 SmNode
* pLeft
= Sum();
1577 //While we have tokens and the next is a relation
1578 while(Terminal() && IsRelationOperator(Terminal()->GetToken())){
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
);
1591 SmNode
* SmNodeListParser::Sum(){
1593 SmNode
* pLeft
= Product();
1594 //While we have tokens and the next is a sum
1595 while(Terminal() && IsSumOperator(Terminal()->GetToken())){
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
);
1608 SmNode
* SmNodeListParser::Product(){
1610 SmNode
* pLeft
= Factor();
1611 //While we have tokens and the next is a product
1612 while(Terminal() && IsProductOperator(Terminal()->GetToken())){
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
);
1625 SmNode
* SmNodeListParser::Factor(){
1626 //Read unary operations
1629 //Take care of unary operators
1630 else if(IsUnaryOperator(Terminal()->GetToken()))
1632 SmStructureNode
*pUnary
= new SmUnHorNode(SmToken());
1633 SmNode
*pOper
= Terminal(),
1641 pUnary
->SetSubNodes(pOper
, pArg
);
1647 SmNode
* SmNodeListParser::Postfix(){
1650 SmNode
*pArg
= nullptr;
1651 if(IsPostfixOperator(Terminal()->GetToken()))
1653 else if(IsOperator(Terminal()->GetToken()))
1657 while(Terminal() && IsPostfixOperator(Terminal()->GetToken())) {
1658 SmStructureNode
*pUnary
= new SmUnHorNode(SmToken());
1659 SmNode
*pOper
= Take();
1660 pUnary
->SetSubNodes(pArg
, pOper
);
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: */