Bug Summary

File:home/maarten/src/libreoffice/core/starmath/source/cursor.cxx
Warning:line 353, column 41
Use of memory after it is freed

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-unknown-linux-gnu -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name cursor.cxx -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -mframe-pointer=all -fmath-errno -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -fno-split-dwarf-inlining -debugger-tuning=gdb -resource-dir /usr/lib64/clang/11.0.0 -D BOOST_ERROR_CODE_HEADER_ONLY -D BOOST_SYSTEM_NO_DEPRECATED -D CPPU_ENV=gcc3 -D LINUX -D OSL_DEBUG_LEVEL=1 -D SAL_LOG_INFO -D SAL_LOG_WARN -D UNIX -D UNX -D X86_64 -D _PTHREADS -D _REENTRANT -D SM_DLLIMPLEMENTATION -D EXCEPTIONS_ON -D LIBO_INTERNAL_ONLY -I /home/maarten/src/libreoffice/core/workdir/UnpackedTarball/icu/source -I /home/maarten/src/libreoffice/core/workdir/UnpackedTarball/icu/source/i18n -I /home/maarten/src/libreoffice/core/workdir/UnpackedTarball/icu/source/common -I /home/maarten/src/libreoffice/core/external/boost/include -I /home/maarten/src/libreoffice/core/workdir/UnpackedTarball/boost -I /home/maarten/src/libreoffice/core/starmath/inc -I /home/maarten/src/libreoffice/core/workdir/SdiTarget/starmath/sdi -I /home/maarten/src/libreoffice/core/include -I /usr/lib/jvm/java-11-openjdk-11.0.9.10-0.0.ea.fc33.x86_64/include -I /usr/lib/jvm/java-11-openjdk-11.0.9.10-0.0.ea.fc33.x86_64/include/linux -I /home/maarten/src/libreoffice/core/config_host -I /home/maarten/src/libreoffice/core/workdir/CustomTarget/officecfg/registry -I /home/maarten/src/libreoffice/core/workdir/CustomTarget/oox/generated -I /home/maarten/src/libreoffice/core/workdir/UnoApiHeadersTarget/udkapi/normal -I /home/maarten/src/libreoffice/core/workdir/UnoApiHeadersTarget/offapi/normal -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/10/../../../../include/c++/10 -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/10/../../../../include/c++/10/x86_64-redhat-linux -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/10/../../../../include/c++/10/backward -internal-isystem /usr/local/include -internal-isystem /usr/lib64/clang/11.0.0/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -O0 -Wno-missing-braces -std=c++17 -fdeprecated-macro -fdebug-compilation-dir /home/maarten/src/libreoffice/core -ferror-limit 19 -fvisibility hidden -fvisibility-inlines-hidden -stack-protector 2 -fgnuc-version=4.2.1 -fcxx-exceptions -fexceptions -debug-info-kind=constructor -analyzer-output=html -faddrsig -o /home/maarten/tmp/wis/scan-build-libreoffice/output/report/2020-10-07-141433-9725-1 -x c++ /home/maarten/src/libreoffice/core/starmath/source/cursor.cxx

/home/maarten/src/libreoffice/core/starmath/source/cursor.cxx

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

/home/maarten/src/libreoffice/core/starmath/inc/cursor.hxx

1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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#ifndef INCLUDED_STARMATH_INC_CURSOR_HXX
10#define INCLUDED_STARMATH_INC_CURSOR_HXX
11
12#include "node.hxx"
13#include "caret.hxx"
14
15#include <cassert>
16#include <list>
17#include <memory>
18
19/** Factor to multiple the squared horizontal distance with
20 * Used for Up and Down movement.
21 */
22#define HORIZONTICAL_DISTANCE_FACTOR10 10
23
24/** Enum of direction for movement */
25enum SmMovementDirection{
26 MoveUp,
27 MoveDown,
28 MoveLeft,
29 MoveRight
30};
31
32/** Enum of elements that can inserted into a formula */
33enum SmFormulaElement{
34 BlankElement,
35 FactorialElement,
36 PlusElement,
37 MinusElement,
38 CDotElement,
39 EqualElement,
40 LessThanElement,
41 GreaterThanElement,
42 PercentElement
43};
44
45/** Bracket types that can be inserted */
46enum class SmBracketType {
47 /** Round brackets, left command "(" */
48 Round,
49 /**Square brackets, left command "[" */
50 Square,
51 /** Curly brackets, left command "lbrace" */
52 Curly,
53};
54
55/** A list of nodes */
56typedef std::list<SmNode*> SmNodeList;
57
58typedef std::list<std::unique_ptr<SmNode>> SmClipboard;
59
60class SmDocShell;
61
62/** Formula cursor
63 *
64 * This class is used to represent a cursor in a formula, which can be used to manipulate
65 * a formula programmatically.
66 * @remarks This class is a very intimate friend of SmDocShell.
67 */
68class SmCursor{
69public:
70 SmCursor(SmNode* tree, SmDocShell* pShell)
71 : mpAnchor(nullptr)
72 , mpPosition(nullptr)
73 , mpTree(tree)
74 , mpDocShell(pShell)
75 , mnEditSections(0)
76 , mbIsEnabledSetModifiedSmDocShell(false)
77 {
78 //Build graph
79 BuildGraph();
80 }
81
82 /** Get position */
83 const SmCaretPos& GetPosition() const { return mpPosition->CaretPos; }
84
85 /** True, if the cursor has a selection */
86 bool HasSelection() const { return mpAnchor != mpPosition; }
87
88 /** Move the position of this cursor */
89 void Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor = true);
90
91 /** Move to the caret position closest to a given point */
92 void MoveTo(OutputDevice* pDev, const Point& pos, bool bMoveAnchor);
93
94 /** Delete the current selection or do nothing */
95 void Delete();
96
97 /** Delete selection, previous element or merge lines
98 *
99 * This method implements the behaviour of backspace.
100 */
101 void DeletePrev(OutputDevice* pDev);
102
103 /** Insert text at the current position */
104 void InsertText(const OUString& aString);
105
106 /** Insert an element into the formula */
107 void InsertElement(SmFormulaElement element);
108
109 /** Insert command text translated into line entries at position
110 *
111 * Note: This method uses the parser to translate a command text into a
112 * tree, then it copies line entries from this tree into the current tree.
113 * Will not work for commands such as newline or ##, if position is in a matrix.
114 * This will work for stuff like "A intersection B". But stuff spanning multiple lines
115 * or dependent on the context which position is placed in will not work!
116 */
117 void InsertCommandText(const OUString& aCommandText);
118
119 /** Insert a special node created from aString
120 *
121 * Used for handling insert request from the "catalog" dialog.
122 * The provided string should be formatted as the desired command: %phi
123 * Note: this method ONLY supports commands defined in Math.xcu
124 *
125 * For more complex expressions use InsertCommandText, this method doesn't
126 * use SmParser, this means that it's faster, but not as strong.
127 */
128 void InsertSpecial(const OUString& aString);
129
130 /** Create sub-/super script
131 *
132 * If there's a selection, it will be move into the appropriate sub-/super scription
133 * of the node in front of it. If there's no node in front of position (or the selection),
134 * a sub-/super scription of a new SmPlaceNode will be made.
135 *
136 * If there's is an existing subscription of the node, the caret will be moved into it,
137 * and any selection will replace it.
138 */
139 void InsertSubSup(SmSubSup eSubSup);
140
141 /** Insert a new row or newline
142 *
143 * Inserts a new row if position is in a matrix or stack command.
144 * Otherwise a newline is inserted if we're in a toplevel line.
145 *
146 * @returns True, if a new row/line could be inserted.
147 *
148 * @remarks If the caret is placed in a subline of a command that doesn't support
149 * this operator the method returns FALSE, and doesn't do anything.
150 */
151 bool InsertRow();
152
153 /** Insert a fraction, use selection as numerator */
154 void InsertFraction();
155
156 /** Create brackets around current selection, or new SmPlaceNode */
157 void InsertBrackets(SmBracketType eBracketType);
158
159 /** Copy the current selection */
160 void Copy();
161 /** Cut the current selection */
162 void Cut(){
163 Copy();
164 Delete();
165 }
166 /** Paste the clipboard */
167 void Paste();
168
169 /** Returns true if more than one node is selected
170 *
171 * This method is used for implementing backspace and delete.
172 * If one of these causes a complex selection, e.g. a node with
173 * subnodes or similar, this should not be deleted immediately.
174 */
175 bool HasComplexSelection();
176
177 /** Finds the topmost node in a visual line
178 *
179 * If MoveUpIfSelected is true, this will move up to the parent line
180 * if the parent of the current line is selected.
181 */
182 static SmNode* FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected = false);
183
184 /** Draw the caret */
185 void Draw(OutputDevice& pDev, Point Offset, bool isCaretVisible);
186
187 bool IsAtTailOfBracket(SmBracketType eBracketType) const;
188
189private:
190 friend class SmDocShell;
191
192 SmCaretPosGraphEntry *mpAnchor,
193 *mpPosition;
194 /** Formula tree */
195 SmNode* mpTree;
196 /** Owner of the formula tree */
197 SmDocShell* mpDocShell;
198 /** Graph over caret position in the current tree */
199 std::unique_ptr<SmCaretPosGraph> mpGraph;
200 /** Clipboard holder */
201 SmClipboard maClipboard;
202
203 /** Returns a node that is selected, if any could be found */
204 SmNode* FindSelectedNode(SmNode* pNode);
205
206 /** Is this one of the nodes used to compose a line
207 *
208 * These are SmExpression, SmBinHorNode, SmUnHorNode etc.
209 */
210 static bool IsLineCompositionNode(SmNode const * pNode);
211
212 /** Count number of selected nodes, excluding line composition nodes
213 *
214 * Note this function doesn't count line composition nodes and it
215 * does count all subnodes as well as the owner nodes.
216 *
217 * Used by SmCursor::HasComplexSelection()
218 */
219 int CountSelectedNodes(SmNode* pNode);
220
221 /** Convert a visual line to a list
222 *
223 * Note this method will delete all the nodes that will no longer be needed.
224 * that includes pLine!
225 * This method also deletes SmErrorNode's as they're just meta info in the line.
226 */
227 static void LineToList(SmStructureNode* pLine, SmNodeList& rList);
228
229 /** Auxiliary function for calling LineToList on a node
230 *
231 * This method sets pNode = NULL and remove it from its parent.
232 * (Assuming it has a parent, and is a child of it).
233 */
234 static void NodeToList(SmNode*& rpNode, SmNodeList& rList){
235 //Remove from parent and NULL rpNode
236 SmNode* pNode = rpNode;
237 if(rpNode
14.1
'rpNode' is non-null
14.1
'rpNode' is non-null
&& rpNode->GetParent()){ //Don't remove this, correctness relies on it
15
Taking true branch
238 int index = rpNode->GetParent()->IndexOfSubNode(rpNode);
239 assert(index >= 0)(static_cast <bool> (index >= 0) ? void (0) : __assert_fail
("index >= 0", "/home/maarten/src/libreoffice/core/starmath/inc/cursor.hxx"
, 239, __extension__ __PRETTY_FUNCTION__))
;
16
Assuming 'index' is >= 0
17
'?' condition is true
240 rpNode->GetParent()->SetSubNode(index, nullptr);
241 }
242 rpNode = nullptr;
243 //Create line from node
244 if(pNode
17.1
'pNode' is non-null
17.1
'pNode' is non-null
&& IsLineCompositionNode(pNode)){
18
Taking true branch
245 LineToList(static_cast<SmStructureNode*>(pNode), rList);
19
Calling 'SmCursor::LineToList'
21
Returning; memory was released via 1st parameter
246 return;
247 }
248 if(pNode)
249 rList.push_front(pNode);
250 }
251
252 /** Clone a visual line to a clipboard
253 *
254 * ... but the selected part only.
255 * Doesn't clone SmErrorNodes, which are ignored as they are context dependent metadata.
256 */
257 static void CloneLineToClipboard(SmStructureNode* pLine, SmClipboard* pClipboard);
258
259 /** Build pGraph over caret positions */
260 void BuildGraph();
261
262 /** Insert new nodes in the tree after position */
263 void InsertNodes(std::unique_ptr<SmNodeList> pNewNodes);
264
265 /** tries to set position to a specific SmCaretPos
266 *
267 * @returns false on failure to find the position in pGraph.
268 */
269 bool SetCaretPosition(SmCaretPos pos);
270
271 /** Set selected on nodes of the tree */
272 void AnnotateSelection();
273
274 /** Clone list of nodes in a clipboard (creates a deep clone) */
275 static std::unique_ptr<SmNodeList> CloneList(SmClipboard &rClipboard);
276
277 /** Find an iterator pointing to the node in pLineList following rCaretPos
278 *
279 * If rCaretPos.pSelectedNode cannot be found it is assumed that it's in front of pLineList,
280 * thus not an element in pLineList. In this case this method returns an iterator to the
281 * first element in pLineList.
282 *
283 * If the current position is inside an SmTextNode, this node will be split in two, for this
284 * reason you should beaware that iterators to elements in pLineList may be invalidated, and
285 * that you should call PatchLineList() with this iterator if no action is taken.
286 */
287 static SmNodeList::iterator FindPositionInLineList(SmNodeList* pLineList,
288 const SmCaretPos& rCaretPos);
289
290 /** Patch a line list after modification, merge SmTextNode, remove SmPlaceNode etc.
291 *
292 * @param pLineList The line list to patch
293 * @param aIter Iterator pointing to the element that needs to be patched with its previous.
294 *
295 * When the list is patched text nodes before and after aIter will be merged.
296 * If there's an, in the context, inappropriate SmPlaceNode before or after aIter it will also be
297 * removed.
298 *
299 * @returns A caret position equivalent to one selecting the node before aIter, the method returns
300 * an invalid SmCaretPos to indicate placement in front of the line.
301 */
302 static SmCaretPos PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter);
303
304 /** Take selected nodes from a list
305 *
306 * Puts the selected nodes into pSelectedNodes, or if pSelectedNodes is NULL deletes
307 * the selected nodes.
308 * Note: If there's a selection inside an SmTextNode this node will be split, and it
309 * will not be merged when the selection have been taken. Use PatchLineList on the
310 * iterator returns to fix this.
311 *
312 * @returns An iterator pointing to the element following the selection taken.
313 */
314 static SmNodeList::iterator TakeSelectedNodesFromList(SmNodeList *pLineList,
315 SmNodeList *pSelectedNodes = nullptr);
316
317 /** Create an instance of SmMathSymbolNode usable for brackets */
318 static SmNode *CreateBracket(SmBracketType eBracketType, bool bIsLeft);
319
320 /** The number of times BeginEdit have been called
321 * Used to allow nesting of BeginEdit() and EndEdit() sections
322 */
323 int mnEditSections;
324 /** Holds data for BeginEdit() and EndEdit() */
325 bool mbIsEnabledSetModifiedSmDocShell;
326 /** Begin edit section where the tree will be modified */
327 void BeginEdit();
328 /** End edit section where the tree will be modified */
329 void EndEdit();
330 /** Finish editing
331 *
332 * Finishes editing by parsing pLineList and inserting back into pParent at nParentIndex.
333 * This method also rebuilds the graph, annotates the selection, sets caret position and
334 * Calls EndEdit.
335 *
336 * @remarks Please note that this method will delete pLineList, as the elements are taken.
337 *
338 * @param pLineList List the constitutes the edited line.
339 * @param pParent Parent to which the line should be inserted.
340 * @param nParentIndex Index in parent where the line should be inserted.
341 * @param PosAfterEdit Caret position to look for after rebuilding graph.
342 * @param pStartLine Line to take first position in, if PosAfterEdit cannot be found,
343 * leave it NULL for pLineList.
344 */
345 void FinishEdit(std::unique_ptr<SmNodeList> pLineList,
346 SmStructureNode* pParent,
347 int nParentIndex,
348 SmCaretPos PosAfterEdit,
349 SmNode* pStartLine = nullptr);
350 /** Request the formula is repainted */
351 void RequestRepaint();
352};
353
354/** Minimalistic recursive decent SmNodeList parser
355 *
356 * This parser is used to take a list of nodes that constitutes a line
357 * and parse them to a tree of SmBinHorNode, SmUnHorNode and SmExpression.
358 *
359 * Please note, this will not handle all kinds of nodes, only nodes that
360 * constitutes and entry in a line.
361 *
362 * Below is an EBNF representation of the grammar used for this parser:
363 * \code
364 * Expression -> Relation*
365 * Relation -> Sum [(=|<|>|...) Sum]*
366 * Sum -> Product [(+|-) Product]*
367 * Product -> Factor [(*|/) Factor]*
368 * Factor -> [+|-|-+|...]* Factor | Postfix
369 * Postfix -> node [!]*
370 * \endcode
371 */
372class SmNodeListParser{
373public:
374 /** Create an instance of SmNodeListParser */
375 SmNodeListParser(){
376 pList = nullptr;
377 }
378 /** Parse a list of nodes to an expression.
379 *
380 * Old error nodes will be deleted.
381 */
382 SmNode* Parse(SmNodeList* list);
383 /** True, if the token is an operator */
384 static bool IsOperator(const SmToken &token);
385 /** True, if the token is a relation operator */
386 static bool IsRelationOperator(const SmToken &token);
387 /** True, if the token is a sum operator */
388 static bool IsSumOperator(const SmToken &token);
389 /** True, if the token is a product operator */
390 static bool IsProductOperator(const SmToken &token);
391 /** True, if the token is a unary operator */
392 static bool IsUnaryOperator(const SmToken &token);
393 /** True, if the token is a postfix operator */
394 static bool IsPostfixOperator(const SmToken &token);
395private:
396 SmNodeList* pList;
397 /** Get the current terminal */
398 SmNode* Terminal(){
399 if (!pList->empty())
400 return pList->front();
401 return nullptr;
402 }
403 /** Move to next terminal */
404 SmNode* Next(){
405 pList->pop_front();
406 return Terminal();
407 }
408 /** Take the current terminal */
409 SmNode* Take(){
410 SmNode* pRetVal = Terminal();
411 Next();
412 return pRetVal;
413 }
414 SmNode* Expression();
415 SmNode* Relation();
416 SmNode* Sum();
417 SmNode* Product();
418 SmNode* Factor();
419 SmNode* Postfix();
420 static SmNode* Error();
421};
422
423
424#endif // INCLUDED_STARMATH_INC_CURSOR_HXX
425
426/* vim:set shiftwidth=4 softtabstop=4 expandtab: */