File: | home/maarten/src/libreoffice/core/starmath/source/cursor.cxx |
Warning: | line 353, column 41 Use of memory after it is freed |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 | |||||
19 | void 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 | |||||
82 | void 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 | |||||
112 | void 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 | |||||
154 | bool 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 | |||||
167 | void SmCursor::AnnotateSelection(){ | ||||
168 | //TODO: Manage a state, reset it upon modification and optimize this call | ||||
169 | SmSetSelectionVisitor(mpAnchor->CaretPos, mpPosition->CaretPos, mpTree); | ||||
170 | } | ||||
171 | |||||
172 | void SmCursor::Draw(OutputDevice& pDev, Point Offset, bool isCaretVisible){ | ||||
173 | SmCaretDrawingVisitor(pDev, GetPosition(), Offset, isCaretVisible); | ||||
174 | } | ||||
175 | |||||
176 | void 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 | |||||
254 | void 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 | |||||
295 | void SmCursor::InsertNodes(std::unique_ptr<SmNodeList> pNewNodes){ | ||||
296 | if(pNewNodes->empty()){ | ||||
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); | ||||
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__)); | ||||
313 | |||||
314 | //Convert line to list | ||||
315 | std::unique_ptr<SmNodeList> pLineList(new SmNodeList); | ||||
316 | NodeToList(pLine, *pLineList); | ||||
317 | |||||
318 | //Find iterator for place to insert nodes | ||||
319 | SmNodeList::iterator it = FindPositionInLineList(pLineList.get(), pos); | ||||
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 | |||||
340 | SmNodeList::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()) | ||||
346 | { | ||||
347 | if((*it)->GetType() == SmNodeType::Text) | ||||
348 | { | ||||
349 | //Split textnode if needed | ||||
350 | if(rCaretPos.nIndex > 0) | ||||
351 | { | ||||
352 | SmTextNode* pText = static_cast<SmTextNode*>(rCaretPos.pSelectedNode); | ||||
353 | if (rCaretPos.nIndex == pText->GetText().getLength()) | ||||
| |||||
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 | |||||
374 | SmCaretPos 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 | |||||
427 | SmNodeList::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 | |||||
484 | void 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 | |||||
587 | void 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 | |||||
651 | SmNode *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 | |||||
683 | bool 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 | |||||
804 | void 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 | |||||
859 | void 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 | |||||
884 | void 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 | |||||
986 | void 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 | |||||
1013 | void 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 | |||||
1036 | void 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 | |||||
1073 | void SmCursor::Paste() { | ||||
1074 | BeginEdit(); | ||||
1075 | Delete(); | ||||
1076 | |||||
1077 | if (!maClipboard.empty()) | ||||
| |||||
1078 | InsertNodes(CloneList(maClipboard)); | ||||
1079 | |||||
1080 | EndEdit(); | ||||
1081 | } | ||||
1082 | |||||
1083 | std::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 | |||||
1095 | SmNode* 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__)); | ||||
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() && | ||||
1106 | ((MoveUpIfSelected
| ||||
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 | |||||
1114 | SmNode* 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 | |||||
1130 | void 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; | ||||
1153 | } | ||||
1154 | |||||
1155 | void 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 | |||||
1179 | bool SmCursor::IsLineCompositionNode(SmNode const * pNode){ | ||||
1180 | switch(pNode->GetType()){ | ||||
1181 | case SmNodeType::Line: | ||||
1182 | case SmNodeType::UnHor: | ||||
1183 | case SmNodeType::Expression: | ||||
1184 | case SmNodeType::BinHor: | ||||
1185 | case SmNodeType::Align: | ||||
1186 | case SmNodeType::Font: | ||||
1187 | return true; | ||||
1188 | default: | ||||
1189 | return false; | ||||
1190 | } | ||||
1191 | } | ||||
1192 | |||||
1193 | int SmCursor::CountSelectedNodes(SmNode* pNode){ | ||||
1194 | if(pNode->GetNumSubNodes() == 0) | ||||
1195 | return 0; | ||||
1196 | int nCount = 0; | ||||
1197 | for(auto pChild : *static_cast<SmStructureNode*>(pNode)) | ||||
1198 | { | ||||
1199 | if (!pChild) | ||||
1200 | continue; | ||||
1201 | if(pChild->IsSelected() && !IsLineCompositionNode(pChild)) | ||||
1202 | nCount++; | ||||
1203 | nCount += CountSelectedNodes(pChild); | ||||
1204 | } | ||||
1205 | return nCount; | ||||
1206 | } | ||||
1207 | |||||
1208 | bool SmCursor::HasComplexSelection(){ | ||||
1209 | if(!HasSelection()) | ||||
1210 | return false; | ||||
1211 | AnnotateSelection(); | ||||
1212 | |||||
1213 | return CountSelectedNodes(mpTree) > 1; | ||||
1214 | } | ||||
1215 | |||||
1216 | void 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 | |||||
1279 | void SmCursor::BeginEdit(){ | ||||
1280 | if(mnEditSections++ > 0) return; | ||||
1281 | |||||
1282 | mbIsEnabledSetModifiedSmDocShell = mpDocShell->IsEnableSetModified(); | ||||
1283 | if( mbIsEnabledSetModifiedSmDocShell ) | ||||
1284 | mpDocShell->EnableSetModified( false ); | ||||
1285 | } | ||||
1286 | |||||
1287 | void 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 | |||||
1320 | void SmCursor::RequestRepaint(){ | ||||
1321 | SmViewShell *pViewSh = SmGetActiveView(); | ||||
1322 | if( pViewSh ) { | ||||
1323 | if ( SfxObjectCreateMode::EMBEDDED == mpDocShell->GetCreateMode() ) | ||||
1324 | mpDocShell->Repaint(); | ||||
1325 | else | ||||
1326 | pViewSh->GetGraphicWindow().Invalidate(); | ||||
1327 | } | ||||
1328 | } | ||||
1329 | |||||
1330 | bool 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 | |||||
1400 | SmNode* 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 | |||||
1417 | SmNode* 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 | |||||
1429 | SmNode* 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 | |||||
1446 | SmNode* 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 | |||||
1463 | SmNode* 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 | |||||
1480 | SmNode* 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 | |||||
1502 | SmNode* 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 | |||||
1521 | SmNode* SmNodeListParser::Error(){ | ||||
1522 | return new SmErrorNode(SmToken()); | ||||
1523 | } | ||||
1524 | |||||
1525 | bool SmNodeListParser::IsOperator(const SmToken &token) { | ||||
1526 | return IsRelationOperator(token) || | ||||
1527 | IsSumOperator(token) || | ||||
1528 | IsProductOperator(token) || | ||||
1529 | IsUnaryOperator(token) || | ||||
1530 | IsPostfixOperator(token); | ||||
1531 | } | ||||
1532 | |||||
1533 | bool SmNodeListParser::IsRelationOperator(const SmToken &token) { | ||||
1534 | return bool(token.nGroup & TG::Relation); | ||||
1535 | } | ||||
1536 | |||||
1537 | bool SmNodeListParser::IsSumOperator(const SmToken &token) { | ||||
1538 | return bool(token.nGroup & TG::Sum); | ||||
1539 | } | ||||
1540 | |||||
1541 | bool 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 | |||||
1550 | bool 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 | |||||
1560 | bool SmNodeListParser::IsPostfixOperator(const SmToken &token) { | ||||
1561 | return token.eType == TFACT; | ||||
1562 | } | ||||
1563 | |||||
1564 | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |
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 */ | ||||
25 | enum SmMovementDirection{ | ||||
26 | MoveUp, | ||||
27 | MoveDown, | ||||
28 | MoveLeft, | ||||
29 | MoveRight | ||||
30 | }; | ||||
31 | |||||
32 | /** Enum of elements that can inserted into a formula */ | ||||
33 | enum 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 */ | ||||
46 | enum 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 */ | ||||
56 | typedef std::list<SmNode*> SmNodeList; | ||||
57 | |||||
58 | typedef std::list<std::unique_ptr<SmNode>> SmClipboard; | ||||
59 | |||||
60 | class 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 | */ | ||||
68 | class SmCursor{ | ||||
69 | public: | ||||
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 | |||||
189 | private: | ||||
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
| ||||
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__)); | ||||
240 | rpNode->GetParent()->SetSubNode(index, nullptr); | ||||
241 | } | ||||
242 | rpNode = nullptr; | ||||
243 | //Create line from node | ||||
244 | if(pNode
| ||||
245 | LineToList(static_cast<SmStructureNode*>(pNode), rList); | ||||
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 | */ | ||||
372 | class SmNodeListParser{ | ||||
373 | public: | ||||
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); | ||||
395 | private: | ||||
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: */ |