bump product version to 5.0.4.1
[LibreOffice.git] / starmath / source / cursor.cxx
blobde483e5d196af244a32554cc53bb9c66c9badccb
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>
16 void SmCursor::Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor){
17 SmCaretPosGraphEntry* NewPos = NULL;
18 switch(direction){
19 case MoveLeft:
21 NewPos = position->Left;
22 OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!");
23 }break;
24 case MoveRight:
26 NewPos = position->Right;
27 OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!");
28 }break;
29 case MoveUp:
30 //Implementation is practically identical to MoveDown, except for a single if statement
31 //so I've implemented them together and added a direction == MoveDown to the if statements.
32 case MoveDown:
34 SmCaretLine from_line = SmCaretPos2LineVisitor(pDev, position->CaretPos).GetResult(),
35 best_line, //Best approximated line found so far
36 curr_line; //Current line
37 long dbp_sq = 0; //Distance squared to best line
38 SmCaretPosGraphIterator it = pGraph->GetIterator();
39 while(it.Next()){
40 //Reject it if it's the current position
41 if(it->CaretPos == position->CaretPos) continue;
42 //Compute caret line
43 curr_line = SmCaretPos2LineVisitor(pDev, it->CaretPos).GetResult();
44 //Reject anything above if we're moving down
45 if(curr_line.GetTop() <= from_line.GetTop() && direction == MoveDown) continue;
46 //Reject anything below if we're moving up
47 if(curr_line.GetTop() + curr_line.GetHeight() >= from_line.GetTop() + from_line.GetHeight()
48 && direction == MoveUp) continue;
49 //Compare if it to what we have, if we have anything yet
50 if(NewPos){
51 //Compute distance to current line squared, multiplied with a horizontal factor
52 long dp_sq = curr_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
53 curr_line.SquaredDistanceY(from_line);
54 //Discard current line if best line is closer
55 if(dbp_sq <= dp_sq) continue;
57 //Take current line as the best
58 best_line = curr_line;
59 NewPos = it.Current();
60 //Update distance to best line
61 dbp_sq = best_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
62 best_line.SquaredDistanceY(from_line);
64 }break;
65 default:
66 SAL_WARN("starmath", "Movement direction not supported!");
68 if(NewPos){
69 position = NewPos;
70 if(bMoveAnchor)
71 anchor = NewPos;
72 RequestRepaint();
76 void SmCursor::MoveTo(OutputDevice* pDev, Point pos, bool bMoveAnchor){
77 SmCaretLine best_line, //Best line found so far, when iterating
78 curr_line; //Current line, when iterating
79 SmCaretPosGraphEntry* NewPos = NULL;
80 long dp_sq = 0, //Distance to current line squared
81 dbp_sq = 1; //Distance to best line squared
82 SmCaretPosGraphIterator it = pGraph->GetIterator();
83 while(it.Next()){
84 OSL_ENSURE(it->CaretPos.IsValid(), "The caret position graph may not have invalid positions!");
85 //Compute current line
86 curr_line = SmCaretPos2LineVisitor(pDev, it->CaretPos).GetResult();
87 //If we have a position compare to it
88 if(NewPos){
89 //Compute squared distance to current line
90 dp_sq = curr_line.SquaredDistanceX(pos) + curr_line.SquaredDistanceY(pos);
91 //If best line is closer, reject current line
92 if(dbp_sq <= dp_sq) continue;
94 //Accept current position as the best
95 best_line = curr_line;
96 NewPos = it.Current();
97 //Update distance to best line
98 dbp_sq = best_line.SquaredDistanceX(pos) + best_line.SquaredDistanceY(pos);
100 if(NewPos){
101 position = NewPos;
102 if(bMoveAnchor)
103 anchor = NewPos;
104 RequestRepaint();
108 void SmCursor::BuildGraph(){
109 //Save the current anchor and position
110 SmCaretPos _anchor, _position;
111 //Release pGraph if allocated
112 if(pGraph){
113 if(anchor)
114 _anchor = anchor->CaretPos;
115 if(position)
116 _position = position->CaretPos;
117 delete pGraph;
118 //Reset anchor and position as they point into an old graph
119 anchor = NULL;
120 position = NULL;
123 //Build the new graph
124 pGraph = SmCaretPosGraphBuildingVisitor(pTree).takeGraph();
126 //Restore anchor and position pointers
127 if(_anchor.IsValid() || _position.IsValid()){
128 SmCaretPosGraphIterator it = pGraph->GetIterator();
129 while(it.Next()){
130 if(_anchor == it->CaretPos)
131 anchor = it.Current();
132 if(_position == it->CaretPos)
133 position = it.Current();
136 //Set position and anchor to first caret position
137 SmCaretPosGraphIterator it = pGraph->GetIterator();
138 if(!position)
139 position = it.Next();
140 if(!anchor)
141 anchor = position;
143 OSL_ENSURE(position->CaretPos.IsValid(), "Position must be valid");
144 OSL_ENSURE(anchor->CaretPos.IsValid(), "Anchor must be valid");
147 bool SmCursor::SetCaretPosition(SmCaretPos pos, bool moveAnchor){
148 SmCaretPosGraphIterator it = pGraph->GetIterator();
149 while(it.Next()){
150 if(it->CaretPos == pos){
151 position = it.Current();
152 if(moveAnchor)
153 anchor = it.Current();
154 return true;
157 return false;
160 void SmCursor::AnnotateSelection(){
161 //TODO: Manage a state, reset it upon modification and optimize this call
162 SmSetSelectionVisitor(anchor->CaretPos, position->CaretPos, pTree);
165 void SmCursor::Draw(OutputDevice& pDev, Point Offset, bool isCaretVisible){
166 SmCaretDrawingVisitor(pDev, GetPosition(), Offset, isCaretVisible);
169 void SmCursor::DeletePrev(OutputDevice* pDev){
170 //Delete only a selection if there's a selection
171 if(HasSelection()){
172 Delete();
173 return;
176 SmNode* pLine = FindTopMostNodeInLine(position->CaretPos.pSelectedNode);
177 SmStructureNode* pLineParent = pLine->GetParent();
178 int nLineOffset = pLineParent->IndexOfSubNode(pLine);
180 //If we're in front of a node who's parent is a TABLE
181 if(pLineParent->GetType() == NTABLE && position->CaretPos.Index == 0 && nLineOffset > 0){
182 //Now we can merge with nLineOffset - 1
183 BeginEdit();
184 //Line to merge things into, so we can delete pLine
185 SmNode* pMergeLine = pLineParent->GetSubNode(nLineOffset-1);
186 OSL_ENSURE(pMergeLine, "pMergeLine cannot be NULL!");
187 SmCaretPos PosAfterDelete;
188 //Convert first line to list
189 SmNodeList *pLineList = NodeToList(pMergeLine);
190 if(!pLineList->empty()){
191 //Find iterator to patch
192 SmNodeList::iterator patchPoint = pLineList->end();
193 --patchPoint;
194 //Convert second line to list
195 NodeToList(pLine, pLineList);
196 //Patch the line list
197 ++patchPoint;
198 PosAfterDelete = PatchLineList(pLineList, patchPoint);
199 //Parse the line
200 pLine = SmNodeListParser().Parse(pLineList);
202 delete pLineList;
203 pLineParent->SetSubNode(nLineOffset-1, pLine);
204 //Delete the removed line slot
205 SmNodeArray lines(pLineParent->GetNumSubNodes()-1);
206 for(int i = 0; i < pLineParent->GetNumSubNodes(); i++){
207 if(i < nLineOffset)
208 lines[i] = pLineParent->GetSubNode(i);
209 else if(i > nLineOffset)
210 lines[i-1] = pLineParent->GetSubNode(i);
212 pLineParent->SetSubNodes(lines);
213 //Rebuild graph
214 anchor = NULL;
215 position = NULL;
216 BuildGraph();
217 AnnotateSelection();
218 //Set caret position
219 if(!SetCaretPosition(PosAfterDelete, true))
220 SetCaretPosition(SmCaretPos(pLine, 0), true);
221 //Finish editing
222 EndEdit();
224 //TODO: If we're in an empty (sub/super/*) script
225 /*}else if(pLineParent->GetType() == NSUBSUP &&
226 nLineOffset != 0 &&
227 pLine->GetType() == NEXPRESSION &&
228 pLine->GetNumSubNodes() == 0){
229 //There's a (sub/super) script we can delete
230 //Consider selecting the entire script if GetNumSubNodes() != 0 or pLine->GetType() != NEXPRESSION
231 //TODO: Handle case where we delete a limit
234 //Else move select, and delete if not complex
235 }else{
236 this->Move(pDev, MoveLeft, false);
237 if(!this->HasComplexSelection())
238 Delete();
242 void SmCursor::Delete(){
243 //Return if we don't have a selection to delete
244 if(!HasSelection())
245 return;
247 //Enter edit section
248 BeginEdit();
250 //Set selected on nodes
251 AnnotateSelection();
253 //Find an arbitrary selected node
254 SmNode* pSNode = FindSelectedNode(pTree);
255 OSL_ENSURE(pSNode != NULL, "There must be a selection when HasSelection is true!");
257 //Find the topmost node of the line that holds the selection
258 SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
259 OSL_ENSURE(pLine != pTree, "Shouldn't be able to select the entire tree");
261 //Get the parent of the line
262 SmStructureNode* pLineParent = pLine->GetParent();
263 //Find line offset in parent
264 int nLineOffset = pLineParent->IndexOfSubNode(pLine);
265 if (nLineOffset == -1)
267 SAL_WARN("starmath", "pLine must be a child of its parent!");
268 return;
271 //Position after delete
272 SmCaretPos PosAfterDelete;
274 SmNodeList* pLineList = NodeToList(pLine);
276 //Take the selected nodes and delete them...
277 SmNodeList::iterator patchIt = TakeSelectedNodesFromList(pLineList);
279 //Get the position to set after delete
280 PosAfterDelete = PatchLineList(pLineList, patchIt);
282 //Finish editing
283 FinishEdit(pLineList, pLineParent, nLineOffset, PosAfterDelete);
286 void SmCursor::InsertNodes(SmNodeList* pNewNodes){
287 if(pNewNodes->empty()){
288 delete pNewNodes;
289 return;
292 //Begin edit section
293 BeginEdit();
295 //Position after insert should be after pNewNode
296 SmCaretPos PosAfterInsert = SmCaretPos(pNewNodes->back(), 1);
298 //Get the current position
299 const SmCaretPos pos = position->CaretPos;
301 //Find top most of line that holds position
302 SmNode* pLine = FindTopMostNodeInLine(pos.pSelectedNode, false);
304 //Find line parent and line index in parent
305 SmStructureNode* pLineParent = pLine->GetParent();
306 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
307 OSL_ENSURE(nParentIndex != -1, "pLine must be a subnode of pLineParent!");
308 if (nParentIndex == -1)
310 delete pNewNodes;
311 return;
314 //Convert line to list
315 SmNodeList* pLineList = NodeToList(pLine);
317 //Find iterator for place to insert nodes
318 SmNodeList::iterator it = FindPositionInLineList(pLineList, pos);
320 //Insert all new nodes
321 SmNodeList::iterator newIt,
322 patchIt = it, // (pointless default value, fixes compiler warnings)
323 insIt;
324 for(newIt = pNewNodes->begin(); newIt != pNewNodes->end(); ++newIt){
325 insIt = pLineList->insert(it, *newIt);
326 if(newIt == pNewNodes->begin())
327 patchIt = insIt;
328 if((*newIt)->GetType() == NTEXT)
329 PosAfterInsert = SmCaretPos(*newIt, static_cast<SmTextNode*>(*newIt)->GetText().getLength());
330 else
331 PosAfterInsert = SmCaretPos(*newIt, 1);
333 //Patch the places we've changed stuff
334 PatchLineList(pLineList, patchIt);
335 PosAfterInsert = PatchLineList(pLineList, it);
336 //Release list, we've taken the nodes
337 delete pNewNodes;
338 pNewNodes = NULL;
340 //Finish editing
341 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
344 SmNodeList::iterator SmCursor::FindPositionInLineList(SmNodeList* pLineList, SmCaretPos aCaretPos) {
345 //Find iterator for position
346 SmNodeList::iterator it;
347 for(it = pLineList->begin(); it != pLineList->end(); ++it){
348 if(*it == aCaretPos.pSelectedNode){
349 if((*it)->GetType() == NTEXT){
350 //Split textnode if needed
351 if(aCaretPos.Index > 0){
352 SmTextNode* pText = static_cast<SmTextNode*>(aCaretPos.pSelectedNode);
353 OUString str1 = pText->GetText().copy(0, aCaretPos.Index);
354 OUString str2 = pText->GetText().copy(aCaretPos.Index);
355 pText->ChangeText(str1);
356 ++it;
357 //Insert str2 as new text node
358 if(!str2.isEmpty()){
359 SmTextNode* pNewText = new SmTextNode(pText->GetToken(), pText->GetFontDesc());
360 pNewText->ChangeText(str2);
361 it = pLineList->insert(it, pNewText);
364 }else
365 ++it;
366 //it now pointer to the node following pos, so pLineList->insert(it, ...) will insert correctly
367 return it;
371 //If we didn't find pSelectedNode, it must be because the caret is in front of the line
372 return pLineList->begin();
375 SmCaretPos SmCursor::PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter) {
376 //The nodes we should consider merging
377 SmNode *prev = NULL,
378 *next = NULL;
379 if(aIter != pLineList->end())
380 next = *aIter;
381 if(aIter != pLineList->begin()) {
382 --aIter;
383 prev = *aIter;
384 ++aIter;
387 //Check if there's textnodes to merge
388 if( prev &&
389 next &&
390 prev->GetType() == NTEXT &&
391 next->GetType() == NTEXT &&
392 ( prev->GetToken().eType != TNUMBER ||
393 next->GetToken().eType == TNUMBER) ){
394 SmTextNode *pText = static_cast<SmTextNode*>(prev),
395 *pOldN = static_cast<SmTextNode*>(next);
396 SmCaretPos retval(pText, pText->GetText().getLength());
397 OUString newText;
398 newText += pText->GetText();
399 newText += pOldN->GetText();
400 pText->ChangeText(newText);
401 delete pOldN;
402 pLineList->erase(aIter);
403 return retval;
406 //Check if there's a SmPlaceNode to remove:
407 if(prev && next && prev->GetType() == NPLACE && !SmNodeListParser::IsOperator(next->GetToken())){
408 --aIter;
409 aIter = pLineList->erase(aIter);
410 delete prev;
411 //Return caret pos in front of aIter
412 if(aIter != pLineList->begin())
413 --aIter; //Thus find node before aIter
414 if(aIter == pLineList->begin())
415 return SmCaretPos();
416 if((*aIter)->GetType() == NTEXT)
417 return SmCaretPos(*aIter, static_cast<SmTextNode*>(*aIter)->GetText().getLength());
418 return SmCaretPos(*aIter, 1);
420 if(prev && next && next->GetType() == NPLACE && !SmNodeListParser::IsOperator(prev->GetToken())){
421 aIter = pLineList->erase(aIter);
422 delete next;
423 if(prev->GetType() == NTEXT)
424 return SmCaretPos(prev, static_cast<SmTextNode*>(prev)->GetText().getLength());
425 return SmCaretPos(prev, 1);
428 //If we didn't do anything return
429 if(!prev) //return an invalid to indicate we're in front of line
430 return SmCaretPos();
431 if(prev->GetType() == NTEXT)
432 return SmCaretPos(prev, static_cast<SmTextNode*>(prev)->GetText().getLength());
433 return SmCaretPos(prev, 1);
436 SmNodeList::iterator SmCursor::TakeSelectedNodesFromList(SmNodeList *pLineList,
437 SmNodeList *pSelectedNodes) {
438 SmNodeList::iterator retval;
439 SmNodeList::iterator it = pLineList->begin();
440 while(it != pLineList->end()){
441 if((*it)->IsSelected()){
442 //Split text nodes
443 if((*it)->GetType() == NTEXT) {
444 SmTextNode* pText = static_cast<SmTextNode*>(*it);
445 OUString aText = pText->GetText();
446 //Start and lengths of the segments, 2 is the selected segment
447 int start2 = pText->GetSelectionStart(),
448 start3 = pText->GetSelectionEnd(),
449 len1 = start2 - 0,
450 len2 = start3 - start2,
451 len3 = aText.getLength() - start3;
452 SmToken aToken = pText->GetToken();
453 sal_uInt16 eFontDesc = pText->GetFontDesc();
454 //If we need make segment 1
455 if(len1 > 0) {
456 int start1 = 0;
457 OUString str = aText.copy(start1, len1);
458 pText->ChangeText(str);
459 ++it;
460 } else {//Remove it if not needed
461 it = pLineList->erase(it);
462 delete pText;
464 //Set retval to be right after the selection
465 retval = it;
466 //if we need make segment 3
467 if(len3 > 0) {
468 OUString str = aText.copy(start3, len3);
469 SmTextNode* pSeg3 = new SmTextNode(aToken, eFontDesc);
470 pSeg3->ChangeText(str);
471 retval = pLineList->insert(it, pSeg3);
473 //If we need to save the selected text
474 if(pSelectedNodes && len2 > 0) {
475 OUString str = aText.copy(start2, len2);
476 SmTextNode* pSeg2 = new SmTextNode(aToken, eFontDesc);
477 pSeg2->ChangeText(str);
478 pSelectedNodes->push_back(pSeg2);
480 } else { //if it's not textnode
481 SmNode* pNode = *it;
482 retval = it = pLineList->erase(it);
483 if(pSelectedNodes)
484 pSelectedNodes->push_back(pNode);
485 else
486 delete pNode;
488 } else
489 ++it;
491 return retval;
494 void SmCursor::InsertSubSup(SmSubSup eSubSup) {
495 AnnotateSelection();
497 //Find line
498 SmNode *pLine;
499 if(HasSelection()) {
500 SmNode *pSNode = FindSelectedNode(pTree);
501 OSL_ENSURE(pSNode != NULL, "There must be a selected node when HasSelection is true!");
502 pLine = FindTopMostNodeInLine(pSNode, true);
503 } else
504 pLine = FindTopMostNodeInLine(position->CaretPos.pSelectedNode, false);
506 //Find Parent and offset in parent
507 SmStructureNode *pLineParent = pLine->GetParent();
508 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
509 OSL_ENSURE(nParentIndex != -1, "pLine must be a subnode of pLineParent!");
511 //TODO: Consider handling special cases where parent is an SmOperNode,
512 // Maybe this method should be able to add limits to an SmOperNode...
514 //We begin modifying the tree here
515 BeginEdit();
517 //Convert line to list
518 SmNodeList* pLineList = NodeToList(pLine);
520 //Take the selection, and/or find iterator for current position
521 SmNodeList* pSelectedNodesList = new SmNodeList();
522 SmNodeList::iterator it;
523 if(HasSelection())
524 it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
525 else
526 it = FindPositionInLineList(pLineList, position->CaretPos);
528 //Find node that this should be applied to
529 SmNode* pSubject;
530 bool bPatchLine = pSelectedNodesList->size() > 0; //If the line should be patched later
531 if(it != pLineList->begin()) {
532 --it;
533 pSubject = *it;
534 ++it;
535 } else {
536 //Create a new place node
537 pSubject = new SmPlaceNode();
538 pSubject->Prepare(pDocShell->GetFormat(), *pDocShell);
539 it = pLineList->insert(it, pSubject);
540 ++it;
541 bPatchLine = true; //We've modified the line it should be patched later.
544 //Wrap the subject in a SmSubSupNode
545 SmSubSupNode* pSubSup;
546 if(pSubject->GetType() != NSUBSUP){
547 SmToken token;
548 token.nGroup = TGPOWER;
549 pSubSup = new SmSubSupNode(token);
550 pSubSup->SetBody(pSubject);
551 *(--it) = pSubSup;
552 ++it;
553 }else
554 pSubSup = static_cast<SmSubSupNode*>(pSubject);
555 //pSubject shouldn't be referenced anymore, pSubSup is the SmSubSupNode in pLineList we wish to edit.
556 //and it pointer to the element following pSubSup in pLineList.
557 pSubject = NULL;
559 //Patch the line if we noted that was needed previously
560 if(bPatchLine)
561 PatchLineList(pLineList, it);
563 //Convert existing, if any, sub-/superscript line to list
564 SmNode *pScriptLine = pSubSup->GetSubSup(eSubSup);
565 SmNodeList* pScriptLineList = NodeToList(pScriptLine);
567 //Add selection to pScriptLineList
568 unsigned int nOldSize = pScriptLineList->size();
569 pScriptLineList->insert(pScriptLineList->end(), pSelectedNodesList->begin(), pSelectedNodesList->end());
570 delete pSelectedNodesList;
571 pSelectedNodesList = NULL;
573 //Patch pScriptLineList if needed
574 if(0 < nOldSize && nOldSize < pScriptLineList->size()) {
575 SmNodeList::iterator iPatchPoint = pScriptLineList->begin();
576 std::advance(iPatchPoint, nOldSize);
577 PatchLineList(pScriptLineList, iPatchPoint);
580 //Find caret pos, that should be used after sub-/superscription.
581 SmCaretPos PosAfterScript; //Leave invalid for first position
582 if(pScriptLineList->size() > 0)
583 PosAfterScript = SmCaretPos::GetPosAfter(pScriptLineList->back());
585 //Parse pScriptLineList
586 pScriptLine = SmNodeListParser().Parse(pScriptLineList);
587 delete pScriptLineList;
588 pScriptLineList = NULL;
590 //Insert pScriptLine back into the tree
591 pSubSup->SetSubSup(eSubSup, pScriptLine);
593 //Finish editing
594 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterScript, pScriptLine);
597 bool SmCursor::InsertLimit(SmSubSup eSubSup, bool bMoveCaret) {
598 //Find a subject to set limits on
599 SmOperNode *pSubject = NULL;
600 //Check if pSelectedNode might be a subject
601 if(position->CaretPos.pSelectedNode->GetType() == NOPER)
602 pSubject = static_cast<SmOperNode*>(position->CaretPos.pSelectedNode);
603 else {
604 //If not, check if parent of the current line is a SmOperNode
605 SmNode *pLineNode = FindTopMostNodeInLine(position->CaretPos.pSelectedNode, false);
606 if(pLineNode->GetParent() && pLineNode->GetParent()->GetType() == NOPER)
607 pSubject = static_cast<SmOperNode*>(pLineNode->GetParent());
610 //Abort operation if we're not in the appropriate context
611 if(!pSubject)
612 return false;
614 BeginEdit();
616 //Find the sub sup node
617 SmSubSupNode *pSubSup = NULL;
618 //Check if there's already one there...
619 if(pSubject->GetSubNode(0)->GetType() == NSUBSUP)
620 pSubSup = static_cast<SmSubSupNode*>(pSubject->GetSubNode(0));
621 else { //if not create a new SmSubSupNode
622 SmToken token;
623 token.nGroup = TGLIMIT;
624 pSubSup = new SmSubSupNode(token);
625 //Set it's body
626 pSubSup->SetBody(pSubject->GetSubNode(0));
627 //Replace the operation of the SmOperNode
628 pSubject->SetSubNode(0, pSubSup);
631 //Create the limit, if needed
632 SmCaretPos PosAfterLimit;
633 SmNode *pLine = NULL;
634 if(!pSubSup->GetSubSup(eSubSup)){
635 pLine = new SmPlaceNode();
636 pSubSup->SetSubSup(eSubSup, pLine);
637 PosAfterLimit = SmCaretPos(pLine, 1);
638 //If it's already there... let's move the caret
639 } else if(bMoveCaret){
640 pLine = pSubSup->GetSubSup(eSubSup);
641 SmNodeList* pLineList = NodeToList(pLine);
642 if(pLineList->size() > 0)
643 PosAfterLimit = SmCaretPos::GetPosAfter(pLineList->back());
644 pLine = SmNodeListParser().Parse(pLineList);
645 delete pLineList;
646 pSubSup->SetSubSup(eSubSup, pLine);
649 //Rebuild graph of caret positions
650 BuildGraph();
651 AnnotateSelection();
653 //Set caret position
654 if(bMoveCaret)
655 if(!SetCaretPosition(PosAfterLimit, true))
656 SetCaretPosition(SmCaretPos(pLine, 0), true);
658 EndEdit();
660 return true;
663 void SmCursor::InsertBrackets(SmBracketType eBracketType) {
664 BeginEdit();
666 AnnotateSelection();
668 //Find line
669 SmNode *pLine;
670 if(HasSelection()) {
671 SmNode *pSNode = FindSelectedNode(pTree);
672 OSL_ENSURE(pSNode != NULL, "There must be a selected node if HasSelection()");
673 pLine = FindTopMostNodeInLine(pSNode, true);
674 } else
675 pLine = FindTopMostNodeInLine(position->CaretPos.pSelectedNode, false);
677 //Find parent and offset in parent
678 SmStructureNode *pLineParent = pLine->GetParent();
679 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
680 OSL_ENSURE( nParentIndex != -1, "pLine must be a subnode of pLineParent!");
681 if (nParentIndex < 0)
682 return;
684 //Convert line to list
685 SmNodeList *pLineList = NodeToList(pLine);
687 //Take the selection, and/or find iterator for current position
688 SmNodeList *pSelectedNodesList = new SmNodeList();
689 SmNodeList::iterator it;
690 if(HasSelection())
691 it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
692 else
693 it = FindPositionInLineList(pLineList, position->CaretPos);
695 //If there's no selected nodes, create a place node
696 SmNode *pBodyNode;
697 SmCaretPos PosAfterInsert;
698 if(pSelectedNodesList->empty()) {
699 pBodyNode = new SmPlaceNode();
700 PosAfterInsert = SmCaretPos(pBodyNode, 1);
701 } else
702 pBodyNode = SmNodeListParser().Parse(pSelectedNodesList);
704 delete pSelectedNodesList;
706 //Create SmBraceNode
707 SmToken aTok(TLEFT, '\0', "left", 0, 5);
708 SmBraceNode *pBrace = new SmBraceNode(aTok);
709 pBrace->SetScaleMode(SCALE_HEIGHT);
710 SmNode *pLeft = CreateBracket(eBracketType, true),
711 *pRight = CreateBracket(eBracketType, false);
712 SmBracebodyNode *pBody = new SmBracebodyNode(SmToken());
713 pBody->SetSubNodes(pBodyNode, NULL);
714 pBrace->SetSubNodes(pLeft, pBody, pRight);
715 pBrace->Prepare(pDocShell->GetFormat(), *pDocShell);
717 //Insert into line
718 pLineList->insert(it, pBrace);
719 //Patch line (I think this is good enough)
720 SmCaretPos pAfter = PatchLineList(pLineList, it);
721 if( !PosAfterInsert.IsValid() )
722 PosAfterInsert = pAfter;
724 //Finish editing
725 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
728 SmNode *SmCursor::CreateBracket(SmBracketType eBracketType, bool bIsLeft) {
729 SmToken aTok;
730 if(bIsLeft){
731 switch(eBracketType){
732 case NoneBrackets:
733 aTok = SmToken(TNONE, '\0', "none", TGLBRACES | TGRBRACES, 0);
734 break;
735 case RoundBrackets:
736 aTok = SmToken(TLPARENT, MS_LPARENT, "(", TGLBRACES, 5);
737 break;
738 case SquareBrackets:
739 aTok = SmToken(TLBRACKET, MS_LBRACKET, "[", TGLBRACES, 5);
740 break;
741 case DoubleSquareBrackets:
742 aTok = SmToken(TLDBRACKET, MS_LDBRACKET, "ldbracket", TGLBRACES, 5);
743 break;
744 case LineBrackets:
745 aTok = SmToken(TLLINE, MS_VERTLINE, "lline", TGLBRACES, 5);
746 break;
747 case DoubleLineBrackets:
748 aTok = SmToken(TLDLINE, MS_DVERTLINE, "ldline", TGLBRACES, 5);
749 break;
750 case CurlyBrackets:
751 aTok = SmToken(TLBRACE, MS_LBRACE, "lbrace", TGLBRACES, 5);
752 break;
753 case AngleBrackets:
754 aTok = SmToken(TLANGLE, MS_LMATHANGLE, "langle", TGLBRACES, 5);
755 break;
756 case CeilBrackets:
757 aTok = SmToken(TLCEIL, MS_LCEIL, "lceil", TGLBRACES, 5);
758 break;
759 case FloorBrackets:
760 aTok = SmToken(TLFLOOR, MS_LFLOOR, "lfloor", TGLBRACES, 5);
761 break;
763 } else {
764 switch(eBracketType) {
765 case NoneBrackets:
766 aTok = SmToken(TNONE, '\0', "none", TGLBRACES | TGRBRACES, 0);
767 break;
768 case RoundBrackets:
769 aTok = SmToken(TRPARENT, MS_RPARENT, ")", TGRBRACES, 5);
770 break;
771 case SquareBrackets:
772 aTok = SmToken(TRBRACKET, MS_RBRACKET, "]", TGRBRACES, 5);
773 break;
774 case DoubleSquareBrackets:
775 aTok = SmToken(TRDBRACKET, MS_RDBRACKET, "rdbracket", TGRBRACES, 5);
776 break;
777 case LineBrackets:
778 aTok = SmToken(TRLINE, MS_VERTLINE, "rline", TGRBRACES, 5);
779 break;
780 case DoubleLineBrackets:
781 aTok = SmToken(TRDLINE, MS_DVERTLINE, "rdline", TGRBRACES, 5);
782 break;
783 case CurlyBrackets:
784 aTok = SmToken(TRBRACE, MS_RBRACE, "rbrace", TGRBRACES, 5);
785 break;
786 case AngleBrackets:
787 aTok = SmToken(TRANGLE, MS_RMATHANGLE, "rangle", TGRBRACES, 5);
788 break;
789 case CeilBrackets:
790 aTok = SmToken(TRCEIL, MS_RCEIL, "rceil", TGRBRACES, 5);
791 break;
792 case FloorBrackets:
793 aTok = SmToken(TRFLOOR, MS_RFLOOR, "rfloor", TGRBRACES, 5);
794 break;
797 SmNode* pRetVal = new SmMathSymbolNode(aTok);
798 pRetVal->SetScaleMode(SCALE_HEIGHT);
799 return pRetVal;
802 bool SmCursor::InsertRow() {
803 AnnotateSelection();
805 //Find line
806 SmNode *pLine;
807 if(HasSelection()) {
808 SmNode *pSNode = FindSelectedNode(pTree);
809 OSL_ENSURE(pSNode != NULL, "There must be a selected node if HasSelection()");
810 pLine = FindTopMostNodeInLine(pSNode, true);
811 } else
812 pLine = FindTopMostNodeInLine(position->CaretPos.pSelectedNode, false);
814 //Find parent and offset in parent
815 SmStructureNode *pLineParent = pLine->GetParent();
816 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
818 if (nParentIndex == -1)
820 SAL_WARN("starmath", "pLine must be a subnode of pLineParent!");
821 return false;
824 //Discover the context of this command
825 SmTableNode *pTable = NULL;
826 SmMatrixNode *pMatrix = NULL;
827 int nTableIndex = nParentIndex;
828 if(pLineParent->GetType() == NTABLE)
829 pTable = static_cast<SmTableNode*>(pLineParent);
830 //If it's warped in a SmLineNode, we can still insert a newline
831 else if(pLineParent->GetType() == NLINE &&
832 pLineParent->GetParent() &&
833 pLineParent->GetParent()->GetType() == NTABLE) {
834 //NOTE: This hack might give problems if we stop ignoring SmAlignNode
835 pTable = static_cast<SmTableNode*>(pLineParent->GetParent());
836 nTableIndex = pTable->IndexOfSubNode(pLineParent);
837 OSL_ENSURE(nTableIndex != -1, "pLineParent must be a child of its parent!");
839 if(pLineParent->GetType() == NMATRIX)
840 pMatrix = static_cast<SmMatrixNode*>(pLineParent);
842 //If we're not in a context that supports InsertRow, return sal_False
843 if(!pTable && !pMatrix)
844 return false;
846 //Now we start editing
847 BeginEdit();
849 //Convert line to list
850 SmNodeList *pLineList = NodeToList(pLine);
852 //Find position in line
853 SmNodeList::iterator it;
854 if(HasSelection()) {
855 //Take the selected nodes and delete them...
856 it = TakeSelectedNodesFromList(pLineList);
857 } else
858 it = FindPositionInLineList(pLineList, position->CaretPos);
860 //New caret position after inserting the newline/row in whatever context
861 SmCaretPos PosAfterInsert;
863 //If we're in the context of a table
864 if(pTable) {
865 SmNodeList *pNewLineList = new SmNodeList();
866 //Move elements from pLineList to pNewLineList
867 pNewLineList->splice(pNewLineList->begin(), *pLineList, it, pLineList->end());
868 //Make sure it is valid again
869 it = pLineList->end();
870 if(it != pLineList->begin())
871 --it;
872 if(pNewLineList->empty())
873 pNewLineList->push_front(new SmPlaceNode());
874 //Parse new line
875 SmNode *pNewLine = SmNodeListParser().Parse(pNewLineList);
876 delete pNewLineList;
877 //Wrap pNewLine in SmLineNode if needed
878 if(pLineParent->GetType() == NLINE) {
879 SmLineNode *pNewLineNode = new SmLineNode(SmToken(TNEWLINE, '\0', "newline"));
880 pNewLineNode->SetSubNodes(pNewLine, NULL);
881 pNewLine = pNewLineNode;
883 //Get position
884 PosAfterInsert = SmCaretPos(pNewLine, 0);
885 //Move other nodes if needed
886 for( int i = pTable->GetNumSubNodes(); i > nTableIndex + 1; i--)
887 pTable->SetSubNode(i, pTable->GetSubNode(i-1));
889 //Insert new line
890 pTable->SetSubNode(nTableIndex + 1, pNewLine);
892 //Check if we need to change token type:
893 if(pTable->GetNumSubNodes() > 2 && pTable->GetToken().eType == TBINOM) {
894 SmToken tok = pTable->GetToken();
895 tok.eType = TSTACK;
896 pTable->SetToken(tok);
899 //If we're in the context of a matrix
900 else {
901 //Find position after insert and patch the list
902 PosAfterInsert = PatchLineList(pLineList, it);
903 //Move other children
904 sal_uInt16 rows = pMatrix->GetNumRows();
905 sal_uInt16 cols = pMatrix->GetNumCols();
906 int nRowStart = (nParentIndex - nParentIndex % cols) + cols;
907 for( int i = pMatrix->GetNumSubNodes() + cols - 1; i >= nRowStart + cols; i--)
908 pMatrix->SetSubNode(i, pMatrix->GetSubNode(i - cols));
909 for( int i = nRowStart; i < nRowStart + cols; i++) {
910 SmPlaceNode *pNewLine = new SmPlaceNode();
911 if(i == nParentIndex + cols)
912 PosAfterInsert = SmCaretPos(pNewLine, 0);
913 pMatrix->SetSubNode(i, pNewLine);
915 pMatrix->SetRowCol(rows + 1, cols);
918 //Finish editing
919 FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
920 //FinishEdit is actually used to handle siturations where parent is an instance of
921 //SmSubSupNode. In this case parent should always be a table or matrix, however, for
922 //code reuse we just use FinishEdit() here too.
923 return true;
926 void SmCursor::InsertFraction() {
927 AnnotateSelection();
929 //Find line
930 SmNode *pLine;
931 if(HasSelection()) {
932 SmNode *pSNode = FindSelectedNode(pTree);
933 OSL_ENSURE(pSNode != NULL, "There must be a selected node when HasSelection is true!");
934 pLine = FindTopMostNodeInLine(pSNode, true);
935 } else
936 pLine = FindTopMostNodeInLine(position->CaretPos.pSelectedNode, false);
938 //Find Parent and offset in parent
939 SmStructureNode *pLineParent = pLine->GetParent();
940 int nParentIndex = pLineParent->IndexOfSubNode(pLine);
941 if (nParentIndex == -1)
943 SAL_WARN("starmath", "pLine must be a subnode of pLineParent!");
944 return;
947 //We begin modifying the tree here
948 BeginEdit();
950 //Convert line to list
951 SmNodeList* pLineList = NodeToList(pLine);
953 //Take the selection, and/or find iterator for current position
954 SmNodeList* pSelectedNodesList = new SmNodeList();
955 SmNodeList::iterator it;
956 if(HasSelection())
957 it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
958 else
959 it = FindPositionInLineList(pLineList, position->CaretPos);
961 //Create pNum, and pDenom
962 bool bEmptyFraction = pSelectedNodesList->empty();
963 SmNode *pNum = bEmptyFraction
964 ? new SmPlaceNode()
965 : SmNodeListParser().Parse(pSelectedNodesList);
966 SmNode *pDenom = new SmPlaceNode();
967 delete pSelectedNodesList;
968 pSelectedNodesList = NULL;
970 //Create new fraction
971 SmBinVerNode *pFrac = new SmBinVerNode(SmToken(TOVER, '\0', "over", TGPRODUCT, 0));
972 SmNode *pRect = new SmRectangleNode(SmToken());
973 pFrac->SetSubNodes(pNum, pRect, pDenom);
975 //Insert in pLineList
976 SmNodeList::iterator patchIt = pLineList->insert(it, pFrac);
977 PatchLineList(pLineList, patchIt);
978 PatchLineList(pLineList, it);
980 //Finish editing
981 SmNode *pSelectedNode = bEmptyFraction ? pNum : pDenom;
982 FinishEdit(pLineList, pLineParent, nParentIndex, SmCaretPos(pSelectedNode, 1));
985 void SmCursor::InsertText(const OUString& aString)
987 BeginEdit();
989 Delete();
991 SmToken token;
992 token.eType = TIDENT;
993 token.cMathChar = '\0';
994 token.nGroup = 0;
995 token.nLevel = 5;
996 token.aText = aString;
998 SmTextNode* pText = new SmTextNode(token, FNT_VARIABLE);
1000 //Prepare the new node
1001 pText->Prepare(pDocShell->GetFormat(), *pDocShell);
1002 pText->AdjustFontDesc();
1004 SmNodeList* pList = new SmNodeList();
1005 pList->push_front(pText);
1006 InsertNodes(pList);
1008 EndEdit();
1011 void SmCursor::InsertElement(SmFormulaElement element){
1012 BeginEdit();
1014 Delete();
1016 //Create new node
1017 SmNode* pNewNode = NULL;
1018 switch(element){
1019 case BlankElement:
1021 SmToken token;
1022 token.nGroup = TGBLANK;
1023 token.aText = "~";
1024 pNewNode = new SmBlankNode(token);
1025 }break;
1026 case FactorialElement:
1028 SmToken token(TFACT, MS_FACT, "fact", TGUNOPER, 5);
1029 pNewNode = new SmMathSymbolNode(token);
1030 }break;
1031 case PlusElement:
1033 SmToken token;
1034 token.eType = TPLUS;
1035 token.cMathChar = MS_PLUS;
1036 token.nGroup = TGUNOPER | TGSUM;
1037 token.nLevel = 5;
1038 token.aText = "+";
1039 pNewNode = new SmMathSymbolNode(token);
1040 }break;
1041 case MinusElement:
1043 SmToken token;
1044 token.eType = TMINUS;
1045 token.cMathChar = MS_MINUS;
1046 token.nGroup = TGUNOPER | TGSUM;
1047 token.nLevel = 5;
1048 token.aText = "-";
1049 pNewNode = new SmMathSymbolNode(token);
1050 }break;
1051 case CDotElement:
1053 SmToken token;
1054 token.eType = TCDOT;
1055 token.cMathChar = MS_CDOT;
1056 token.nGroup = TGPRODUCT;
1057 token.aText = "cdot";
1058 pNewNode = new SmMathSymbolNode(token);
1059 }break;
1060 case EqualElement:
1062 SmToken token;
1063 token.eType = TASSIGN;
1064 token.cMathChar = MS_ASSIGN;
1065 token.nGroup = TGRELATION;
1066 token.aText = "=";
1067 pNewNode = new SmMathSymbolNode(token);
1068 }break;
1069 case LessThanElement:
1071 SmToken token;
1072 token.eType = TLT;
1073 token.cMathChar = MS_LT;
1074 token.nGroup = TGRELATION;
1075 token.aText = "<";
1076 pNewNode = new SmMathSymbolNode(token);
1077 }break;
1078 case GreaterThanElement:
1080 SmToken token;
1081 token.eType = TGT;
1082 token.cMathChar = MS_GT;
1083 token.nGroup = TGRELATION;
1084 token.aText = ">";
1085 pNewNode = new SmMathSymbolNode(token);
1086 }break;
1087 case PercentElement:
1089 SmToken token;
1090 token.eType = TTEXT;
1091 token.cMathChar = MS_PERCENT;
1092 token.nGroup = 0;
1093 token.aText = "\"%\"";
1094 pNewNode = new SmMathSymbolNode(token);
1095 }break;
1096 default:
1097 SAL_WARN("starmath", "Element unknown!");
1099 OSL_ENSURE(pNewNode != NULL, "No new node was created!");
1100 if(!pNewNode)
1101 return;
1103 //Prepare the new node
1104 pNewNode->Prepare(pDocShell->GetFormat(), *pDocShell);
1106 //Insert new node
1107 SmNodeList* pList = new SmNodeList();
1108 pList->push_front(pNewNode);
1109 InsertNodes(pList);
1111 EndEdit();
1114 void SmCursor::InsertSpecial(const OUString& _aString)
1116 BeginEdit();
1117 Delete();
1119 OUString aString = comphelper::string::strip(_aString, ' ');
1121 //Create instance of special node
1122 SmToken token;
1123 token.eType = TSPECIAL;
1124 token.cMathChar = '\0';
1125 token.nGroup = 0;
1126 token.nLevel = 5;
1127 token.aText = aString;
1128 SmSpecialNode* pSpecial = new SmSpecialNode(token);
1130 //Prepare the special node
1131 pSpecial->Prepare(pDocShell->GetFormat(), *pDocShell);
1133 //Insert the node
1134 SmNodeList* pList = new SmNodeList();
1135 pList->push_front(pSpecial);
1136 InsertNodes(pList);
1138 EndEdit();
1141 void SmCursor::InsertCommand(sal_uInt16 nCommand) {
1142 switch(nCommand){
1143 case RID_NEWLINE:
1144 InsertRow();
1145 break;
1146 case RID_FROMX:
1147 InsertLimit(CSUB, true);
1148 break;
1149 case RID_TOX:
1150 InsertLimit(CSUP, true);
1151 break;
1152 case RID_FROMXTOY:
1153 if(InsertLimit(CSUB, true))
1154 InsertLimit(CSUP, true);
1155 break;
1156 default:
1157 InsertCommandText(SM_RESSTR(nCommand));
1158 break;
1162 void SmCursor::InsertCommandText(const OUString& aCommandText) {
1163 //Parse the sub expression
1164 SmNode* pSubExpr = SmParser().ParseExpression(aCommandText);
1166 //Prepare the subtree
1167 pSubExpr->Prepare(pDocShell->GetFormat(), *pDocShell);
1169 //Convert subtree to list
1170 SmNodeList* pLineList = NodeToList(pSubExpr);
1172 BeginEdit();
1174 //Delete any selection
1175 Delete();
1177 //Insert it
1178 InsertNodes(pLineList);
1180 EndEdit();
1183 void SmCursor::Copy(){
1184 if(!HasSelection())
1185 return;
1187 //Find selected node
1188 SmNode* pSNode = FindSelectedNode(pTree);
1189 //Find visual line
1190 SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
1192 //Clone selected nodes
1193 SmNodeList* pList;
1194 if(IsLineCompositionNode(pLine))
1195 pList = CloneLineToList(static_cast<SmStructureNode*>(pLine), true);
1196 else{
1197 pList = new SmNodeList();
1198 //Special care to only clone selected text
1199 if(pLine->GetType() == NTEXT) {
1200 SmTextNode *pText = static_cast<SmTextNode*>(pLine);
1201 SmTextNode *pClone = new SmTextNode( pText->GetToken(), pText->GetFontDesc() );
1202 int start = pText->GetSelectionStart(),
1203 length = pText->GetSelectionEnd() - pText->GetSelectionStart();
1204 pClone->ChangeText(pText->GetText().copy(start, length));
1205 pClone->SetScaleMode(pText->GetScaleMode());
1206 pList->push_front(pClone);
1207 } else {
1208 SmCloningVisitor aCloneFactory;
1209 pList->push_front(aCloneFactory.Clone(pLine));
1213 //Set clipboard
1214 if (pList->size() > 0)
1215 SetClipboard(pList);
1216 else
1217 delete pList;
1220 void SmCursor::Paste() {
1221 BeginEdit();
1222 Delete();
1224 if(pClipboard && pClipboard->size() > 0)
1225 InsertNodes(CloneList(pClipboard));
1227 EndEdit();
1230 SmNodeList* SmCursor::CloneList(SmNodeList* pList){
1231 SmCloningVisitor aCloneFactory;
1232 SmNodeList* pClones = new SmNodeList();
1234 SmNodeList::iterator it;
1235 for(it = pList->begin(); it != pList->end(); ++it){
1236 SmNode *pClone = aCloneFactory.Clone(*it);
1237 pClones->push_back(pClone);
1240 return pClones;
1243 void SmCursor::SetClipboard(SmNodeList* pList){
1244 if(pClipboard){
1245 //Delete all nodes on the clipboard
1246 SmNodeList::iterator it;
1247 for(it = pClipboard->begin(); it != pClipboard->end(); ++it)
1248 delete (*it);
1249 delete pClipboard;
1251 pClipboard = pList;
1254 SmNode* SmCursor::FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected){
1255 //If we haven't got a subnode
1256 if(!pSNode)
1257 return NULL;
1259 //Move up parent until we find a node who's
1260 //parent is NULL or isn't selected and not a type of:
1261 // SmExpressionNode
1262 // SmLineNode
1263 // SmBinHorNode
1264 // SmUnHorNode
1265 // SmAlignNode
1266 // SmFontNode
1267 while(pSNode->GetParent() &&
1268 ((MoveUpIfSelected &&
1269 pSNode->GetParent()->IsSelected()) ||
1270 IsLineCompositionNode(pSNode->GetParent())))
1271 pSNode = pSNode->GetParent();
1272 //Now we have the selection line node
1273 return pSNode;
1276 SmNode* SmCursor::FindSelectedNode(SmNode* pNode){
1277 SmNodeIterator it(pNode);
1278 while(it.Next()){
1279 if(it->IsSelected())
1280 return it.Current();
1281 SmNode* pRetVal = FindSelectedNode(it.Current());
1282 if(pRetVal)
1283 return pRetVal;
1285 return NULL;
1288 SmNodeList* SmCursor::LineToList(SmStructureNode* pLine, SmNodeList* list){
1289 SmNodeIterator it(pLine);
1290 while(it.Next()){
1291 switch(it->GetType()){
1292 case NLINE:
1293 case NUNHOR:
1294 case NEXPRESSION:
1295 case NBINHOR:
1296 case NALIGN:
1297 case NFONT:
1298 LineToList(static_cast<SmStructureNode*>(it.Current()), list);
1299 break;
1300 case NERROR:
1301 delete it.Current();
1302 break;
1303 default:
1304 list->push_back(it.Current());
1307 SmNodeArray emptyArray(0);
1308 pLine->SetSubNodes(emptyArray);
1309 delete pLine;
1310 return list;
1313 SmNodeList* SmCursor::CloneLineToList(SmStructureNode* pLine, bool bOnlyIfSelected, SmNodeList* pList){
1314 SmCloningVisitor aCloneFactory;
1315 SmNodeIterator it(pLine);
1316 while(it.Next()){
1317 if( IsLineCompositionNode( it.Current() ) )
1318 CloneLineToList( static_cast<SmStructureNode*>(it.Current()), bOnlyIfSelected, pList );
1319 else if( (!bOnlyIfSelected || it->IsSelected()) && it->GetType() != NERROR ) {
1320 //Only clone selected text from SmTextNode
1321 if(it->GetType() == NTEXT) {
1322 SmTextNode *pText = static_cast<SmTextNode*>(it.Current());
1323 SmTextNode *pClone = new SmTextNode( it->GetToken(), pText->GetFontDesc() );
1324 int start = pText->GetSelectionStart(),
1325 length = pText->GetSelectionEnd() - pText->GetSelectionStart();
1326 pClone->ChangeText(pText->GetText().copy(start, length));
1327 pClone->SetScaleMode(pText->GetScaleMode());
1328 pList->push_back(pClone);
1329 } else
1330 pList->push_back(aCloneFactory.Clone(it.Current()));
1333 return pList;
1336 bool SmCursor::IsLineCompositionNode(SmNode* pNode){
1337 switch(pNode->GetType()){
1338 case NLINE:
1339 case NUNHOR:
1340 case NEXPRESSION:
1341 case NBINHOR:
1342 case NALIGN:
1343 case NFONT:
1344 return true;
1345 default:
1346 return false;
1350 int SmCursor::CountSelectedNodes(SmNode* pNode){
1351 int nCount = 0;
1352 SmNodeIterator it(pNode);
1353 while(it.Next()){
1354 if(it->IsSelected() && !IsLineCompositionNode(it.Current()))
1355 nCount++;
1356 nCount += CountSelectedNodes(it.Current());
1358 return nCount;
1361 bool SmCursor::HasComplexSelection(){
1362 if(!HasSelection())
1363 return false;
1364 AnnotateSelection();
1366 return CountSelectedNodes(pTree) > 1;
1369 void SmCursor::FinishEdit(SmNodeList* pLineList,
1370 SmStructureNode* pParent,
1371 int nParentIndex,
1372 SmCaretPos PosAfterEdit,
1373 SmNode* pStartLine) {
1374 //Store number of nodes in line for later
1375 int entries = pLineList->size();
1377 //Parse list of nodes to a tree
1378 SmNodeListParser parser;
1379 SmNode* pLine = parser.Parse(pLineList);
1380 delete pLineList;
1382 //Check if we're making the body of a subsup node bigger than one
1383 if(pParent->GetType() == NSUBSUP &&
1384 nParentIndex == 0 &&
1385 entries > 1) {
1386 //Wrap pLine in scalable round brackets
1387 SmToken aTok(TLEFT, '\0', "left", 0, 5);
1388 SmBraceNode *pBrace = new SmBraceNode(aTok);
1389 pBrace->SetScaleMode(SCALE_HEIGHT);
1390 SmNode *pLeft = CreateBracket(RoundBrackets, true),
1391 *pRight = CreateBracket(RoundBrackets, false);
1392 SmBracebodyNode *pBody = new SmBracebodyNode(SmToken());
1393 pBody->SetSubNodes(pLine, NULL);
1394 pBrace->SetSubNodes(pLeft, pBody, pRight);
1395 pBrace->Prepare(pDocShell->GetFormat(), *pDocShell);
1396 pLine = pBrace;
1397 //TODO: Consider the following alternative behavior:
1398 //Consider the line: A + {B + C}^D lsub E
1399 //Here pLineList is B, + and C and pParent is a subsup node with
1400 //both RSUP and LSUB set. Imagine the user just inserted "B +" in
1401 //the body of the subsup node...
1402 //The most natural thing to do would be to make the line like this:
1403 //A + B lsub E + C ^ D
1404 //E.g. apply LSUB and LSUP to the first element in pLineList and RSUP
1405 //and RSUB to the last eleent in pLineList. But how should this act
1406 //for CSUP and CSUB ???
1407 //For this reason and because brackets was faster to implement, this solution
1408 //have been chosen. It might be worth working on the other solution later...
1411 //Set pStartLine if NULL
1412 if(!pStartLine)
1413 pStartLine = pLine;
1415 //Insert it back into the parent
1416 pParent->SetSubNode(nParentIndex, pLine);
1418 //Rebuild graph of caret position
1419 anchor = NULL;
1420 position = NULL;
1421 BuildGraph();
1422 AnnotateSelection(); //Update selection annotation!
1424 //Set caret position
1425 if(!SetCaretPosition(PosAfterEdit, true))
1426 SetCaretPosition(SmCaretPos(pStartLine, 0), true);
1428 //End edit section
1429 EndEdit();
1432 void SmCursor::BeginEdit(){
1433 if(nEditSections++ > 0) return;
1435 bIsEnabledSetModifiedSmDocShell = pDocShell->IsEnableSetModified();
1436 if( bIsEnabledSetModifiedSmDocShell )
1437 pDocShell->EnableSetModified( false );
1440 void SmCursor::EndEdit(){
1441 if(--nEditSections > 0) return;
1443 pDocShell->SetFormulaArranged(false);
1444 //Okay, I don't know what this does... :)
1445 //It's used in SmDocShell::SetText and with places where everything is modified.
1446 //I think it does some magic, with sfx, but everything is totally undocumented so
1447 //it's kinda hard to tell...
1448 if ( bIsEnabledSetModifiedSmDocShell )
1449 pDocShell->EnableSetModified( bIsEnabledSetModifiedSmDocShell );
1450 //I think this notifies people around us that we've modified this document...
1451 pDocShell->SetModified(true);
1452 //I think SmDocShell uses this value when it sends an update graphics event
1453 //Anyway comments elsewhere suggests it need to be updated...
1454 pDocShell->nModifyCount++;
1456 //TODO: Consider copying the update accessability code from SmDocShell::SetText in here...
1457 //This somehow updates the size of SmGraphicView if it is running in embedded mode
1458 if( pDocShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED )
1459 pDocShell->OnDocumentPrinterChanged(0);
1461 //Request a replaint...
1462 RequestRepaint();
1464 //Update the edit engine and text of the document
1465 OUString formula;
1466 SmNodeToTextVisitor(pTree, formula);
1467 //pTree->CreateTextFromNode(formula);
1468 pDocShell->aText = formula;
1469 pDocShell->GetEditEngine().QuickInsertText( formula, ESelection( 0, 0, EE_PARA_ALL, EE_TEXTPOS_ALL ) );
1470 pDocShell->GetEditEngine().QuickFormatDoc();
1473 void SmCursor::RequestRepaint(){
1474 SmViewShell *pViewSh = SmGetActiveView();
1475 if( pViewSh ) {
1476 if ( SfxObjectCreateMode::EMBEDDED == pDocShell->GetCreateMode() )
1477 pDocShell->Repaint();
1478 else
1479 pViewSh->GetGraphicWindow().Invalidate();
1483 bool SmCursor::IsAtTailOfBracket(SmBracketType eBracketType, SmBraceNode** ppBraceNode) const {
1484 const SmCaretPos pos = GetPosition();
1485 if (!pos.IsValid()) {
1486 return false;
1489 SmNode* pNode = pos.pSelectedNode;
1491 if (pNode->GetType() == NTEXT) {
1492 SmTextNode* pTextNode = static_cast<SmTextNode*>(pNode);
1493 if (pos.Index < pTextNode->GetText().getLength()) {
1494 // The cursor is on a text node and at the middle of it.
1495 return false;
1497 } else {
1498 if (pos.Index < 1) {
1499 return false;
1503 while (true) {
1504 SmStructureNode* pParentNode = pNode->GetParent();
1505 if (!pParentNode) {
1506 // There's no brace body node in the ancestors.
1507 return false;
1510 sal_uInt16 index = pNode->FindIndex();
1511 if (index + 1 != pParentNode->GetNumSubNodes()) {
1512 // The cursor is not at the tail at one of ancestor nodes.
1513 return false;
1516 pNode = pParentNode;
1517 if (pNode->GetType() == NBRACEBODY) {
1518 // Found the brace body node.
1519 break;
1523 SmStructureNode* pBraceNodeTmp = pNode->GetParent();
1524 if (!pBraceNodeTmp || pBraceNodeTmp->GetType() != NBRACE) {
1525 // Brace node is invalid.
1526 return false;
1529 SmBraceNode* pBraceNode = static_cast<SmBraceNode*>(pBraceNodeTmp);
1530 SmMathSymbolNode* pClosingNode = pBraceNode->ClosingBrace();
1531 if (!pClosingNode) {
1532 // Couldn't get closing symbol node.
1533 return false;
1536 // Check if the closing brace matches eBracketType.
1537 SmTokenType eClosingTokenType = pClosingNode->GetToken().eType;
1538 switch (eBracketType) {
1539 case NoneBrackets: if (eClosingTokenType != TNONE) { return false; } break;
1540 case RoundBrackets: if (eClosingTokenType != TRPARENT) { return false; } break;
1541 case SquareBrackets: if (eClosingTokenType != TRBRACKET) { return false; } break;
1542 case DoubleSquareBrackets: if (eClosingTokenType != TRDBRACKET) { return false; } break;
1543 case LineBrackets: if (eClosingTokenType != TRLINE) { return false; } break;
1544 case DoubleLineBrackets: if (eClosingTokenType != TRDLINE) { return false; } break;
1545 case CurlyBrackets: if (eClosingTokenType != TRBRACE) { return false; } break;
1546 case AngleBrackets: if (eClosingTokenType != TRANGLE) { return false; } break;
1547 case CeilBrackets: if (eClosingTokenType != TRCEIL) { return false; } break;
1548 case FloorBrackets: if (eClosingTokenType != TRFLOOR) { return false; } break;
1549 default:
1550 return false;
1553 if (ppBraceNode) {
1554 *ppBraceNode = static_cast<SmBraceNode*>(pBraceNode);
1557 return true;
1560 void SmCursor::MoveAfterBracket(SmBraceNode* pBraceNode, bool bMoveAnchor)
1562 position->CaretPos.pSelectedNode = pBraceNode;
1563 position->CaretPos.Index = 1;
1564 if (bMoveAnchor) {
1565 anchor->CaretPos.pSelectedNode = pBraceNode;
1566 anchor->CaretPos.Index = 1;
1568 RequestRepaint();
1572 /////////////////////////////////////// SmNodeListParser
1574 SmNode* SmNodeListParser::Parse(SmNodeList* list, bool bDeleteErrorNodes){
1575 pList = list;
1576 if(bDeleteErrorNodes){
1577 //Delete error nodes
1578 SmNodeList::iterator it = pList->begin();
1579 while(it != pList->end()) {
1580 if((*it)->GetType() == NERROR){
1581 //Delete and erase
1582 delete *it;
1583 it = pList->erase(it);
1584 }else
1585 ++it;
1588 SmNode* retval = Expression();
1589 pList = NULL;
1590 return retval;
1593 SmNode* SmNodeListParser::Expression(){
1594 SmNodeArray NodeArray;
1595 //Accept as many relations as there is
1596 while(Terminal())
1597 NodeArray.push_back(Relation());
1599 //Create SmExpressionNode, I hope SmToken() will do :)
1600 SmStructureNode* pExpr = new SmExpressionNode(SmToken());
1601 pExpr->SetSubNodes(NodeArray);
1602 return pExpr;
1605 SmNode* SmNodeListParser::Relation(){
1606 //Read a sum
1607 SmNode* pLeft = Sum();
1608 //While we have tokens and the next is a relation
1609 while(Terminal() && IsRelationOperator(Terminal()->GetToken())){
1610 //Take the operator
1611 SmNode* pOper = Take();
1612 //Find the right side of the relation
1613 SmNode* pRight = Sum();
1614 //Create new SmBinHorNode
1615 SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
1616 pNewNode->SetSubNodes(pLeft, pOper, pRight);
1617 pLeft = pNewNode;
1619 return pLeft;
1622 SmNode* SmNodeListParser::Sum(){
1623 //Read a product
1624 SmNode* pLeft = Product();
1625 //While we have tokens and the next is a sum
1626 while(Terminal() && IsSumOperator(Terminal()->GetToken())){
1627 //Take the operator
1628 SmNode* pOper = Take();
1629 //Find the right side of the sum
1630 SmNode* pRight = Product();
1631 //Create new SmBinHorNode
1632 SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
1633 pNewNode->SetSubNodes(pLeft, pOper, pRight);
1634 pLeft = pNewNode;
1636 return pLeft;
1639 SmNode* SmNodeListParser::Product(){
1640 //Read a Factor
1641 SmNode* pLeft = Factor();
1642 //While we have tokens and the next is a product
1643 while(Terminal() && IsProductOperator(Terminal()->GetToken())){
1644 //Take the operator
1645 SmNode* pOper = Take();
1646 //Find the right side of the operation
1647 SmNode* pRight = Factor();
1648 //Create new SmBinHorNode
1649 SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
1650 pNewNode->SetSubNodes(pLeft, pOper, pRight);
1651 pLeft = pNewNode;
1653 return pLeft;
1656 SmNode* SmNodeListParser::Factor(){
1657 //Read unary operations
1658 if(!Terminal())
1659 return Error();
1660 //Take care of unary operators
1661 else if(IsUnaryOperator(Terminal()->GetToken()))
1663 SmStructureNode *pUnary = new SmUnHorNode(SmToken());
1664 SmNode *pOper = Terminal(),
1665 *pArg;
1667 if(Next())
1668 pArg = Factor();
1669 else
1670 pArg = Error();
1672 pUnary->SetSubNodes(pOper, pArg);
1673 return pUnary;
1675 return Postfix();
1678 SmNode* SmNodeListParser::Postfix(){
1679 if(!Terminal())
1680 return Error();
1681 SmNode *pArg = NULL;
1682 if(IsPostfixOperator(Terminal()->GetToken()))
1683 pArg = Error();
1684 else if(IsOperator(Terminal()->GetToken()))
1685 return Error();
1686 else
1687 pArg = Take();
1688 while(Terminal() && IsPostfixOperator(Terminal()->GetToken())) {
1689 SmStructureNode *pUnary = new SmUnHorNode(SmToken());
1690 SmNode *pOper = Take();
1691 pUnary->SetSubNodes(pArg, pOper);
1692 pArg = pUnary;
1694 return pArg;
1697 SmNode* SmNodeListParser::Error(){
1698 return new SmErrorNode(PE_UNEXPECTED_TOKEN, SmToken());
1701 bool SmNodeListParser::IsOperator(const SmToken &token) {
1702 return IsRelationOperator(token) ||
1703 IsSumOperator(token) ||
1704 IsProductOperator(token) ||
1705 IsUnaryOperator(token) ||
1706 IsPostfixOperator(token);
1709 bool SmNodeListParser::IsRelationOperator(const SmToken &token) {
1710 return token.nGroup & TGRELATION;
1713 bool SmNodeListParser::IsSumOperator(const SmToken &token) {
1714 return token.nGroup & TGSUM;
1717 bool SmNodeListParser::IsProductOperator(const SmToken &token) {
1718 return token.nGroup & TGPRODUCT &&
1719 token.eType != TWIDESLASH &&
1720 token.eType != TWIDEBACKSLASH &&
1721 token.eType != TUNDERBRACE &&
1722 token.eType != TOVERBRACE &&
1723 token.eType != TOVER;
1726 bool SmNodeListParser::IsUnaryOperator(const SmToken &token) {
1727 return token.nGroup & TGUNOPER &&
1728 (token.eType == TPLUS ||
1729 token.eType == TMINUS ||
1730 token.eType == TPLUSMINUS ||
1731 token.eType == TMINUSPLUS ||
1732 token.eType == TNEG ||
1733 token.eType == TUOPER);
1736 bool SmNodeListParser::IsPostfixOperator(const SmToken &token) {
1737 return token.eType == TFACT;
1740 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */