diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /starmath/source/cursor.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'starmath/source/cursor.cxx')
-rw-r--r-- | starmath/source/cursor.cxx | 1606 |
1 files changed, 1606 insertions, 0 deletions
diff --git a/starmath/source/cursor.cxx b/starmath/source/cursor.cxx new file mode 100644 index 0000000000..6f8e9b0af0 --- /dev/null +++ b/starmath/source/cursor.cxx @@ -0,0 +1,1606 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include <cursor.hxx> +#include <visitors.hxx> +#include <document.hxx> +#include <view.hxx> +#include <comphelper/string.hxx> +#include <comphelper/lok.hxx> +#include <editeng/editeng.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <osl/diagnose.h> +#include <sfx2/lokhelper.hxx> +#include <vcl/transfer.hxx> +#include <vcl/unohelp2.hxx> + +void SmCursor::Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor){ + SmCaretPosGraphEntry* NewPos = nullptr; + switch(direction) + { + case MoveLeft: + if (mpPosition) + NewPos = mpPosition->Left; + OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!"); + break; + case MoveRight: + if (mpPosition) + NewPos = mpPosition->Right; + OSL_ENSURE(NewPos, "NewPos shouldn't be NULL here!"); + break; + case MoveUp: + //Implementation is practically identical to MoveDown, except for a single if statement + //so I've implemented them together and added a direction == MoveDown to the if statements. + case MoveDown: + if (mpPosition) + { + SmCaretLine from_line = SmCaretPos2LineVisitor(pDev, mpPosition->CaretPos).GetResult(), + best_line, //Best approximated line found so far + curr_line; //Current line + tools::Long dbp_sq = 0; //Distance squared to best line + for(const auto &pEntry : *mpGraph) + { + //Reject it if it's the current position + if(pEntry->CaretPos == mpPosition->CaretPos) continue; + //Compute caret line + curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult(); + //Reject anything above if we're moving down + if(curr_line.GetTop() <= from_line.GetTop() && direction == MoveDown) continue; + //Reject anything below if we're moving up + if(curr_line.GetTop() + curr_line.GetHeight() >= from_line.GetTop() + from_line.GetHeight() + && direction == MoveUp) continue; + //Compare if it to what we have, if we have anything yet + if(NewPos){ + //Compute distance to current line squared, multiplied with a horizontal factor + tools::Long dp_sq = curr_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR + + curr_line.SquaredDistanceY(from_line); + //Discard current line if best line is closer + if(dbp_sq <= dp_sq) continue; + } + //Take current line as the best + best_line = curr_line; + NewPos = pEntry.get(); + //Update distance to best line + dbp_sq = best_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR + + best_line.SquaredDistanceY(from_line); + } + } + break; + default: + assert(false); + } + if(NewPos){ + mpPosition = NewPos; + if(bMoveAnchor) + mpAnchor = NewPos; + RequestRepaint(); + } +} + +void SmCursor::MoveTo(OutputDevice* pDev, const Point& pos, bool bMoveAnchor) +{ + SmCaretPosGraphEntry* NewPos = nullptr; + tools::Long dp_sq = 0, //Distance to current line squared + dbp_sq = 1; //Distance to best line squared + for(const auto &pEntry : *mpGraph) + { + OSL_ENSURE(pEntry->CaretPos.IsValid(), "The caret position graph may not have invalid positions!"); + //Compute current line + SmCaretLine curr_line = SmCaretPos2LineVisitor(pDev, pEntry->CaretPos).GetResult(); + //Compute squared distance to current line + dp_sq = curr_line.SquaredDistanceX(pos) + curr_line.SquaredDistanceY(pos); + //If we have a position compare to it + if(NewPos){ + //If best line is closer, reject current line + if(dbp_sq <= dp_sq) continue; + } + //Accept current position as the best + NewPos = pEntry.get(); + //Update distance to best line + dbp_sq = dp_sq; + } + if(NewPos){ + mpPosition = NewPos; + if(bMoveAnchor) + mpAnchor = NewPos; + RequestRepaint(); + } +} + +void SmCursor::BuildGraph(){ + //Save the current anchor and position + SmCaretPos _anchor, _position; + //Release mpGraph if allocated + if(mpGraph){ + if(mpAnchor) + _anchor = mpAnchor->CaretPos; + if(mpPosition) + _position = mpPosition->CaretPos; + mpGraph.reset(); + //Reset anchor and position as they point into an old graph + mpAnchor = nullptr; + mpPosition = nullptr; + } + + //Build the new graph + mpGraph.reset(SmCaretPosGraphBuildingVisitor(mpTree).takeGraph()); + + //Restore anchor and position pointers + if(_anchor.IsValid() || _position.IsValid()){ + for(const auto &pEntry : *mpGraph) + { + if(_anchor == pEntry->CaretPos) + mpAnchor = pEntry.get(); + if(_position == pEntry->CaretPos) + mpPosition = pEntry.get(); + } + } + //Set position and anchor to first caret position + auto it = mpGraph->begin(); + assert(it != mpGraph->end()); + if(!mpPosition) + mpPosition = it->get(); + if(!mpAnchor) + mpAnchor = mpPosition; + + assert(mpPosition); + assert(mpAnchor); + OSL_ENSURE(mpPosition->CaretPos.IsValid(), "Position must be valid"); + OSL_ENSURE(mpAnchor->CaretPos.IsValid(), "Anchor must be valid"); +} + +bool SmCursor::SetCaretPosition(SmCaretPos pos){ + for(const auto &pEntry : *mpGraph) + { + if(pEntry->CaretPos == pos) + { + mpPosition = pEntry.get(); + mpAnchor = pEntry.get(); + return true; + } + } + return false; +} + +void SmCursor::AnnotateSelection() const { + //TODO: Manage a state, reset it upon modification and optimize this call + SmSetSelectionVisitor(mpAnchor->CaretPos, mpPosition->CaretPos, mpTree); +} + +void SmCursor::Draw(OutputDevice& pDev, Point Offset, bool isCaretVisible){ + SmCaretDrawingVisitor(pDev, GetPosition(), Offset, isCaretVisible); +} + +tools::Rectangle SmCursor::GetCaretRectangle(OutputDevice& rOutDev) const +{ + return SmCaretRectanglesVisitor(rOutDev, GetPosition()).getCaret(); +} + +tools::Rectangle SmCursor::GetSelectionRectangle(OutputDevice& rOutDev) const +{ + AnnotateSelection(); + return SmSelectionRectanglesVisitor(rOutDev, mpTree).GetSelection(); +} + +void SmCursor::DeletePrev(OutputDevice* pDev){ + //Delete only a selection if there's a selection + if(HasSelection()){ + Delete(); + return; + } + + SmNode* pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + SmStructureNode* pLineParent = pLine->GetParent(); + int nLineOffsetIdx = pLineParent->IndexOfSubNode(pLine); + assert(nLineOffsetIdx >= 0); + + //If we're in front of a node who's parent is a TABLE + if (pLineParent->GetType() == SmNodeType::Table && mpPosition->CaretPos.nIndex == 0 && nLineOffsetIdx > 0) + { + size_t nLineOffset = nLineOffsetIdx; + //Now we can merge with nLineOffset - 1 + BeginEdit(); + //Line to merge things into, so we can delete pLine + SmNode* pMergeLine = pLineParent->GetSubNode(nLineOffset-1); + OSL_ENSURE(pMergeLine, "pMergeLine cannot be NULL!"); + SmCaretPos PosAfterDelete; + //Convert first line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pMergeLine, *pLineList); + if(!pLineList->empty()){ + //Find iterator to patch + SmNodeList::iterator patchPoint = pLineList->end(); + --patchPoint; + //Convert second line to list + NodeToList(pLine, *pLineList); + //Patch the line list + ++patchPoint; + PosAfterDelete = PatchLineList(pLineList.get(), patchPoint); + //Parse the line + pLine = SmNodeListParser().Parse(pLineList.get()); + } + pLineList.reset(); + pLineParent->SetSubNode(nLineOffset-1, pLine); + //Delete the removed line slot + SmNodeArray lines(pLineParent->GetNumSubNodes()-1); + for (size_t i = 0; i < pLineParent->GetNumSubNodes(); ++i) + { + if(i < nLineOffset) + lines[i] = pLineParent->GetSubNode(i); + else if(i > nLineOffset) + lines[i-1] = pLineParent->GetSubNode(i); + } + pLineParent->SetSubNodes(std::move(lines)); + //Rebuild graph + mpAnchor = nullptr; + mpPosition = nullptr; + BuildGraph(); + AnnotateSelection(); + //Set caret position + if(!SetCaretPosition(PosAfterDelete)) + SetCaretPosition(SmCaretPos(pLine, 0)); + //Finish editing + EndEdit(); + + //TODO: If we're in an empty (sub/super/*) script + /*}else if(pLineParent->GetType() == SmNodeType::SubSup && + nLineOffset != 0 && + pLine->GetType() == SmNodeType::Expression && + pLine->GetNumSubNodes() == 0){ + //There's a (sub/super) script we can delete + //Consider selecting the entire script if GetNumSubNodes() != 0 or pLine->GetType() != SmNodeType::Expression + //TODO: Handle case where we delete a limit + */ + + //Else move select, and delete if not complex + }else{ + Move(pDev, MoveLeft, false); + if(!HasComplexSelection()) + Delete(); + } +} + +void SmCursor::Delete(){ + //Return if we don't have a selection to delete + if(!HasSelection()) + return; + + //Enter edit section + BeginEdit(); + + //Set selected on nodes + AnnotateSelection(); + + //Find an arbitrary selected node + SmNode* pSNode = FindSelectedNode(mpTree); + assert(pSNode); + + //Find the topmost node of the line that holds the selection + SmNode* pLine = FindTopMostNodeInLine(pSNode, true); + OSL_ENSURE(pLine != mpTree, "Shouldn't be able to select the entire tree"); + + //Get the parent of the line + SmStructureNode* pLineParent = pLine->GetParent(); + //Find line offset in parent + int nLineOffset = pLineParent->IndexOfSubNode(pLine); + assert(nLineOffset >= 0); + + //Position after delete + SmCaretPos PosAfterDelete; + + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selected nodes and delete them... + SmNodeList::iterator patchIt = TakeSelectedNodesFromList(pLineList.get()); + + //Get the position to set after delete + PosAfterDelete = PatchLineList(pLineList.get(), patchIt); + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nLineOffset, PosAfterDelete); +} + +void SmCursor::InsertNodes(std::unique_ptr<SmNodeList> pNewNodes){ + if(pNewNodes->empty()){ + return; + } + + //Begin edit section + BeginEdit(); + + //Get the current position + const SmCaretPos pos = mpPosition->CaretPos; + + //Find top most of line that holds position + SmNode* pLine = FindTopMostNodeInLine(pos.pSelectedNode); + const bool bSelectedIsTopMost = pLine == pos.pSelectedNode; + + //Find line parent and line index in parent + SmStructureNode* pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); // deletes pLine, potentially deleting pos.pSelectedNode + + //Find iterator for place to insert nodes + SmNodeList::iterator it = bSelectedIsTopMost ? pLineList->begin() + : FindPositionInLineList(pLineList.get(), pos); + + //Insert all new nodes + SmNodeList::iterator newIt, + patchIt = it, // (pointless default value, fixes compiler warnings) + insIt; + for(newIt = pNewNodes->begin(); newIt != pNewNodes->end(); ++newIt){ + insIt = pLineList->insert(it, *newIt); + if(newIt == pNewNodes->begin()) + patchIt = insIt; + } + //Patch the places we've changed stuff + PatchLineList(pLineList.get(), patchIt); + SmCaretPos PosAfterInsert = PatchLineList(pLineList.get(), it); + //Release list, we've taken the nodes + pNewNodes.reset(); + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert); +} + +SmNodeList::iterator SmCursor::FindPositionInLineList(SmNodeList* pLineList, + const SmCaretPos& rCaretPos) +{ + //Find iterator for position + SmNodeList::iterator it = std::find(pLineList->begin(), pLineList->end(), rCaretPos.pSelectedNode); + if (it != pLineList->end()) + { + if((*it)->GetType() == SmNodeType::Text) + { + //Split textnode if needed + if(rCaretPos.nIndex > 0) + { + SmTextNode* pText = static_cast<SmTextNode*>(rCaretPos.pSelectedNode); + if (rCaretPos.nIndex == pText->GetText().getLength()) + return ++it; + OUString str1 = pText->GetText().copy(0, rCaretPos.nIndex); + OUString str2 = pText->GetText().copy(rCaretPos.nIndex); + pText->ChangeText(str1); + ++it; + //Insert str2 as new text node + assert(!str2.isEmpty()); + SmTextNode* pNewText = new SmTextNode(pText->GetToken(), pText->GetFontDesc()); + pNewText->ChangeText(str2); + it = pLineList->insert(it, pNewText); + } + }else + ++it; + //it now pointer to the node following pos, so pLineList->insert(it, ...) will insert correctly + return it; + } + //If we didn't find pSelectedNode, it must be because the caret is in front of the line + return pLineList->begin(); +} + +SmCaretPos SmCursor::PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter) { + //The nodes we should consider merging + SmNode *prev = nullptr, + *next = nullptr; + if(aIter != pLineList->end()) + next = *aIter; + if(aIter != pLineList->begin()) { + --aIter; + prev = *aIter; + ++aIter; + } + + //Check if there's textnodes to merge + if( prev && + next && + prev->GetType() == SmNodeType::Text && + next->GetType() == SmNodeType::Text && + ( prev->GetToken().eType != TNUMBER || + next->GetToken().eType == TNUMBER) ){ + SmTextNode *pText = static_cast<SmTextNode*>(prev), + *pOldN = static_cast<SmTextNode*>(next); + SmCaretPos retval(pText, pText->GetText().getLength()); + OUString newText = pText->GetText() + pOldN->GetText(); + pText->ChangeText(newText); + delete pOldN; + pLineList->erase(aIter); + return retval; + } + + //Check if there's a SmPlaceNode to remove: + if(prev && next && prev->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(next->GetToken())){ + --aIter; + aIter = pLineList->erase(aIter); + delete prev; + //Return caret pos in front of aIter + if(aIter != pLineList->begin()) + --aIter; //Thus find node before aIter + if(aIter == pLineList->begin()) + return SmCaretPos(); + return SmCaretPos::GetPosAfter(*aIter); + } + if(prev && next && next->GetType() == SmNodeType::Place && !SmNodeListParser::IsOperator(prev->GetToken())){ + aIter = pLineList->erase(aIter); + delete next; + return SmCaretPos::GetPosAfter(prev); + } + + //If we didn't do anything return + if(!prev) //return an invalid to indicate we're in front of line + return SmCaretPos(); + return SmCaretPos::GetPosAfter(prev); +} + +SmNodeList::iterator SmCursor::TakeSelectedNodesFromList(SmNodeList *pLineList, + SmNodeList *pSelectedNodes) { + SmNodeList::iterator retval; + SmNodeList::iterator it = pLineList->begin(); + while(it != pLineList->end()){ + if((*it)->IsSelected()){ + //Split text nodes + if((*it)->GetType() == SmNodeType::Text) { + SmTextNode* pText = static_cast<SmTextNode*>(*it); + OUString aText = pText->GetText(); + //Start and lengths of the segments, 2 is the selected segment + int start2 = pText->GetSelectionStart(), + start3 = pText->GetSelectionEnd(), + len1 = start2 - 0, + len2 = start3 - start2, + len3 = aText.getLength() - start3; + SmToken aToken = pText->GetToken(); + sal_uInt16 eFontDesc = pText->GetFontDesc(); + //If we need make segment 1 + if(len1 > 0) { + OUString str = aText.copy(0, len1); + pText->ChangeText(str); + ++it; + } else {//Remove it if not needed + it = pLineList->erase(it); + delete pText; + } + //Set retval to be right after the selection + retval = it; + //if we need make segment 3 + if(len3 > 0) { + OUString str = aText.copy(start3, len3); + SmTextNode* pSeg3 = new SmTextNode(aToken, eFontDesc); + pSeg3->ChangeText(str); + retval = pLineList->insert(it, pSeg3); + } + //If we need to save the selected text + if(pSelectedNodes && len2 > 0) { + OUString str = aText.copy(start2, len2); + SmTextNode* pSeg2 = new SmTextNode(aToken, eFontDesc); + pSeg2->ChangeText(str); + pSelectedNodes->push_back(pSeg2); + } + } else { //if it's not textnode + SmNode* pNode = *it; + retval = it = pLineList->erase(it); + if(pSelectedNodes) + pSelectedNodes->push_back(pNode); + else + delete pNode; + } + } else + ++it; + } + return retval; +} + +void SmCursor::InsertSubSup(SmSubSup eSubSup) { + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find Parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //TODO: Consider handling special cases where parent is an SmOperNode, + // Maybe this method should be able to add limits to an SmOperNode... + + //We begin modifying the tree here + BeginEdit(); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selection, and/or find iterator for current position + std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList); + SmNodeList::iterator it; + if(HasSelection()) + it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get()); + else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //Find node that this should be applied to + SmNode* pSubject; + bool bPatchLine = !pSelectedNodesList->empty(); //If the line should be patched later + if(it != pLineList->begin()) { + --it; + pSubject = *it; + ++it; + } else { + //Create a new place node + pSubject = new SmPlaceNode(); + pSubject->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + it = pLineList->insert(it, pSubject); + ++it; + bPatchLine = true; //We've modified the line it should be patched later. + } + + //Wrap the subject in a SmSubSupNode + SmSubSupNode* pSubSup; + if(pSubject->GetType() != SmNodeType::SubSup){ + SmToken token; + token.nGroup = TG::Power; + pSubSup = new SmSubSupNode(token); + pSubSup->SetBody(pSubject); + *(--it) = pSubSup; + ++it; + }else + pSubSup = static_cast<SmSubSupNode*>(pSubject); + //pSubject shouldn't be referenced anymore, pSubSup is the SmSubSupNode in pLineList we wish to edit. + //and it pointer to the element following pSubSup in pLineList. + pSubject = nullptr; + + //Patch the line if we noted that was needed previously + if(bPatchLine) + PatchLineList(pLineList.get(), it); + + //Convert existing, if any, sub-/superscript line to list + SmNode *pScriptLine = pSubSup->GetSubSup(eSubSup); + std::unique_ptr<SmNodeList> pScriptLineList(new SmNodeList); + NodeToList(pScriptLine, *pScriptLineList); + + //Add selection to pScriptLineList + unsigned int nOldSize = pScriptLineList->size(); + pScriptLineList->insert(pScriptLineList->end(), pSelectedNodesList->begin(), pSelectedNodesList->end()); + pSelectedNodesList.reset(); + + //Patch pScriptLineList if needed + if(0 < nOldSize && nOldSize < pScriptLineList->size()) { + SmNodeList::iterator iPatchPoint = pScriptLineList->begin(); + std::advance(iPatchPoint, nOldSize); + PatchLineList(pScriptLineList.get(), iPatchPoint); + } + + //Find caret pos, that should be used after sub-/superscription. + SmCaretPos PosAfterScript; //Leave invalid for first position + if (!pScriptLineList->empty()) + PosAfterScript = SmCaretPos::GetPosAfter(pScriptLineList->back()); + + //Parse pScriptLineList + pScriptLine = SmNodeListParser().Parse(pScriptLineList.get()); + pScriptLineList.reset(); + + //Insert pScriptLine back into the tree + pSubSup->SetSubSup(eSubSup, pScriptLine); + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterScript, pScriptLine); +} + +void SmCursor::InsertBrackets(SmBracketType eBracketType) { + BeginEdit(); + + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selection, and/or find iterator for current position + std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList); + SmNodeList::iterator it; + if(HasSelection()) + it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get()); + else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //If there's no selected nodes, create a place node + std::unique_ptr<SmNode> pBodyNode; + SmCaretPos PosAfterInsert; + if(pSelectedNodesList->empty()) { + pBodyNode.reset(new SmPlaceNode()); + PosAfterInsert = SmCaretPos(pBodyNode.get(), 1); + } else + pBodyNode.reset(SmNodeListParser().Parse(pSelectedNodesList.get())); + + pSelectedNodesList.reset(); + + //Create SmBraceNode + SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5); + SmBraceNode *pBrace = new SmBraceNode(aTok); + pBrace->SetScaleMode(SmScaleMode::Height); + std::unique_ptr<SmNode> pLeft( CreateBracket(eBracketType, true) ), + pRight( CreateBracket(eBracketType, false) ); + std::unique_ptr<SmBracebodyNode> pBody(new SmBracebodyNode(SmToken())); + pBody->SetSubNodes(std::move(pBodyNode), nullptr); + pBrace->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Insert into line + pLineList->insert(it, pBrace); + //Patch line (I think this is good enough) + SmCaretPos aAfter = PatchLineList(pLineList.get(), it); + if( !PosAfterInsert.IsValid() ) + PosAfterInsert = aAfter; + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert); +} + +SmNode *SmCursor::CreateBracket(SmBracketType eBracketType, bool bIsLeft) { + SmToken aTok; + if(bIsLeft){ + switch(eBracketType){ + case SmBracketType::Round: + aTok = SmToken(TLPARENT, MS_LPARENT, "(", TG::LBrace, 5); + break; + case SmBracketType::Square: + aTok = SmToken(TLBRACKET, MS_LBRACKET, "[", TG::LBrace, 5); + break; + case SmBracketType::Curly: + aTok = SmToken(TLBRACE, MS_LBRACE, "lbrace", TG::LBrace, 5); + break; + } + } else { + switch(eBracketType) { + case SmBracketType::Round: + aTok = SmToken(TRPARENT, MS_RPARENT, ")", TG::RBrace, 5); + break; + case SmBracketType::Square: + aTok = SmToken(TRBRACKET, MS_RBRACKET, "]", TG::RBrace, 5); + break; + case SmBracketType::Curly: + aTok = SmToken(TRBRACE, MS_RBRACE, "rbrace", TG::RBrace, 5); + break; + } + } + SmNode* pRetVal = new SmMathSymbolNode(aTok); + pRetVal->SetScaleMode(SmScaleMode::Height); + return pRetVal; +} + +bool SmCursor::InsertRow() { + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //Discover the context of this command + SmTableNode *pTable = nullptr; + SmMatrixNode *pMatrix = nullptr; + int nTableIndex = nParentIndex; + if(pLineParent->GetType() == SmNodeType::Table) + pTable = static_cast<SmTableNode*>(pLineParent); + //If it's wrapped in a SmLineNode, we can still insert a newline + else if(pLineParent->GetType() == SmNodeType::Line && + pLineParent->GetParent() && + pLineParent->GetParent()->GetType() == SmNodeType::Table) { + //NOTE: This hack might give problems if we stop ignoring SmAlignNode + pTable = static_cast<SmTableNode*>(pLineParent->GetParent()); + nTableIndex = pTable->IndexOfSubNode(pLineParent); + assert(nTableIndex >= 0); + } + if(pLineParent->GetType() == SmNodeType::Matrix) + pMatrix = static_cast<SmMatrixNode*>(pLineParent); + + //If we're not in a context that supports InsertRow, return sal_False + if(!pTable && !pMatrix) + return false; + + //Now we start editing + BeginEdit(); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Find position in line + SmNodeList::iterator it; + if(HasSelection()) { + //Take the selected nodes and delete them... + it = TakeSelectedNodesFromList(pLineList.get()); + } else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //New caret position after inserting the newline/row in whatever context + SmCaretPos PosAfterInsert; + + //If we're in the context of a table + if(pTable) { + std::unique_ptr<SmNodeList> pNewLineList(new SmNodeList); + //Move elements from pLineList to pNewLineList + SmNodeList& rLineList = *pLineList; + pNewLineList->splice(pNewLineList->begin(), rLineList, it, rLineList.end()); + //Make sure it is valid again + it = pLineList->end(); + if(it != pLineList->begin()) + --it; + if(pNewLineList->empty()) + pNewLineList->push_front(new SmPlaceNode()); + //Parse new line + std::unique_ptr<SmNode> pNewLine(SmNodeListParser().Parse(pNewLineList.get())); + pNewLineList.reset(); + //Wrap pNewLine in SmLineNode if needed + if(pLineParent->GetType() == SmNodeType::Line) { + std::unique_ptr<SmLineNode> pNewLineNode(new SmLineNode(SmToken(TNEWLINE, '\0', "newline"))); + pNewLineNode->SetSubNodes(std::move(pNewLine), nullptr); + pNewLine = std::move(pNewLineNode); + } + //Get position + PosAfterInsert = SmCaretPos(pNewLine.get(), 0); + //Move other nodes if needed + for( int i = pTable->GetNumSubNodes(); i > nTableIndex + 1; i--) + pTable->SetSubNode(i, pTable->GetSubNode(i-1)); + + //Insert new line + pTable->SetSubNode(nTableIndex + 1, pNewLine.release()); + + //Check if we need to change token type: + if(pTable->GetNumSubNodes() > 2 && pTable->GetToken().eType == TBINOM) { + SmToken tok = pTable->GetToken(); + tok.eType = TSTACK; + pTable->SetToken(tok); + } + } + //If we're in the context of a matrix + else { + //Find position after insert and patch the list + PosAfterInsert = PatchLineList(pLineList.get(), it); + //Move other children + sal_uInt16 rows = pMatrix->GetNumRows(); + sal_uInt16 cols = pMatrix->GetNumCols(); + int nRowStart = (nParentIndex - nParentIndex % cols) + cols; + for( int i = pMatrix->GetNumSubNodes() + cols - 1; i >= nRowStart + cols; i--) + pMatrix->SetSubNode(i, pMatrix->GetSubNode(i - cols)); + for( int i = nRowStart; i < nRowStart + cols; i++) { + SmPlaceNode *pNewLine = new SmPlaceNode(); + if(i == nParentIndex + cols) + PosAfterInsert = SmCaretPos(pNewLine, 0); + pMatrix->SetSubNode(i, pNewLine); + } + pMatrix->SetRowCol(rows + 1, cols); + } + + //Finish editing + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, PosAfterInsert); + //FinishEdit is actually used to handle situations where parent is an instance of + //SmSubSupNode. In this case parent should always be a table or matrix, however, for + //code reuse we just use FinishEdit() here too. + return true; +} + +void SmCursor::InsertFraction() { + AnnotateSelection(); + + //Find line + SmNode *pLine; + if(HasSelection()) { + SmNode *pSNode = FindSelectedNode(mpTree); + assert(pSNode); + pLine = FindTopMostNodeInLine(pSNode, true); + } else + pLine = FindTopMostNodeInLine(mpPosition->CaretPos.pSelectedNode); + + //Find Parent and offset in parent + SmStructureNode *pLineParent = pLine->GetParent(); + int nParentIndex = pLineParent->IndexOfSubNode(pLine); + assert(nParentIndex >= 0); + + //We begin modifying the tree here + BeginEdit(); + + //Convert line to list + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pLine, *pLineList); + + //Take the selection, and/or find iterator for current position + std::unique_ptr<SmNodeList> pSelectedNodesList(new SmNodeList); + SmNodeList::iterator it; + if(HasSelection()) + it = TakeSelectedNodesFromList(pLineList.get(), pSelectedNodesList.get()); + else + it = FindPositionInLineList(pLineList.get(), mpPosition->CaretPos); + + //Create pNum, and pDenom + bool bEmptyFraction = pSelectedNodesList->empty(); + std::unique_ptr<SmNode> pNum( bEmptyFraction + ? new SmPlaceNode() + : SmNodeListParser().Parse(pSelectedNodesList.get()) ); + std::unique_ptr<SmNode> pDenom(new SmPlaceNode()); + pSelectedNodesList.reset(); + + //Create new fraction + SmBinVerNode *pFrac = new SmBinVerNode(SmToken(TOVER, '\0', "over", TG::Product, 0)); + std::unique_ptr<SmNode> pRect(new SmRectangleNode(SmToken())); + pFrac->SetSubNodes(std::move(pNum), std::move(pRect), std::move(pDenom)); + + //Insert in pLineList + SmNodeList::iterator patchIt = pLineList->insert(it, pFrac); + PatchLineList(pLineList.get(), patchIt); + PatchLineList(pLineList.get(), it); + + //Finish editing + SmNode *pSelectedNode = bEmptyFraction ? pFrac->GetSubNode(0) : pFrac->GetSubNode(2); + FinishEdit(std::move(pLineList), pLineParent, nParentIndex, SmCaretPos(pSelectedNode, 1)); +} + +void SmCursor::InsertText(const OUString& aString) +{ + BeginEdit(); + + Delete(); + + SmToken token; + token.eType = TIDENT; + token.cMathChar = u""_ustr; + token.nGroup = TG::NONE; + token.nLevel = 5; + token.aText = aString; + + SmTextNode* pText = new SmTextNode(token, FNT_VARIABLE); + pText->SetText(aString); + pText->AdjustFontDesc(); + pText->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + std::unique_ptr<SmNodeList> pList(new SmNodeList); + pList->push_front(pText); + InsertNodes(std::move(pList)); + + EndEdit(); +} + +void SmCursor::InsertElement(SmFormulaElement element){ + BeginEdit(); + + Delete(); + + //Create new node + SmNode* pNewNode = nullptr; + switch(element){ + case BlankElement: + { + SmToken token; + token.eType = TBLANK; + token.nGroup = TG::Blank; + token.aText = "~"; + SmBlankNode* pBlankNode = new SmBlankNode(token); + pBlankNode->IncreaseBy(token); + pNewNode = pBlankNode; + }break; + case FactorialElement: + { + SmToken token(TFACT, MS_FACT, "fact", TG::UnOper, 5); + pNewNode = new SmMathSymbolNode(token); + }break; + case PlusElement: + { + SmToken token; + token.eType = TPLUS; + token.setChar(MS_PLUS); + token.nGroup = TG::UnOper | TG::Sum; + token.nLevel = 5; + token.aText = "+"; + pNewNode = new SmMathSymbolNode(token); + }break; + case MinusElement: + { + SmToken token; + token.eType = TMINUS; + token.setChar(MS_MINUS); + token.nGroup = TG::UnOper | TG::Sum; + token.nLevel = 5; + token.aText = "-"; + pNewNode = new SmMathSymbolNode(token); + }break; + case CDotElement: + { + SmToken token; + token.eType = TCDOT; + token.setChar(MS_CDOT); + token.nGroup = TG::Product; + token.aText = "cdot"; + pNewNode = new SmMathSymbolNode(token); + }break; + case EqualElement: + { + SmToken token; + token.eType = TASSIGN; + token.setChar(MS_ASSIGN); + token.nGroup = TG::Relation; + token.aText = "="; + pNewNode = new SmMathSymbolNode(token); + }break; + case LessThanElement: + { + SmToken token; + token.eType = TLT; + token.setChar(MS_LT); + token.nGroup = TG::Relation; + token.aText = "<"; + pNewNode = new SmMathSymbolNode(token); + }break; + case GreaterThanElement: + { + SmToken token; + token.eType = TGT; + token.setChar(MS_GT); + token.nGroup = TG::Relation; + token.aText = ">"; + pNewNode = new SmMathSymbolNode(token); + }break; + case PercentElement: + { + SmToken token; + token.eType = TTEXT; + token.setChar(MS_PERCENT); + token.nGroup = TG::NONE; + token.aText = "\"%\""; + pNewNode = new SmMathSymbolNode(token); + }break; + } + assert(pNewNode); + + //Prepare the new node + pNewNode->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Insert new node + std::unique_ptr<SmNodeList> pList(new SmNodeList); + pList->push_front(pNewNode); + InsertNodes(std::move(pList)); + + EndEdit(); +} + +void SmCursor::InsertSpecial(std::u16string_view _aString) +{ + BeginEdit(); + Delete(); + + OUString aString( comphelper::string::strip(_aString, ' ') ); + + //Create instance of special node + SmToken token; + token.eType = TSPECIAL; + token.cMathChar = u""_ustr; + token.nGroup = TG::NONE; + token.nLevel = 5; + token.aText = aString; + SmSpecialNode* pSpecial = new SmSpecialNode(token); + + //Prepare the special node + pSpecial->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Insert the node + std::unique_ptr<SmNodeList> pList(new SmNodeList); + pList->push_front(pSpecial); + InsertNodes(std::move(pList)); + + EndEdit(); +} + +void SmCursor::InsertCommandText(const OUString& aCommandText) { + //Parse the sub expression + auto xSubExpr = mpDocShell->GetParser()->ParseExpression(aCommandText); + + //Prepare the subtree + xSubExpr->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + + //Convert subtree to list + SmNode* pSubExpr = xSubExpr.release(); + std::unique_ptr<SmNodeList> pLineList(new SmNodeList); + NodeToList(pSubExpr, *pLineList); + + BeginEdit(); + + //Delete any selection + Delete(); + + //Insert it + InsertNodes(std::move(pLineList)); + + EndEdit(); +} + +void SmCursor::Copy(vcl::Window* pWindow) +{ + if(!HasSelection()) + return; + + AnnotateSelection(); + //Find selected node + SmNode* pSNode = FindSelectedNode(mpTree); + assert(pSNode); + //Find visual line + SmNode* pLine = FindTopMostNodeInLine(pSNode, true); + assert(pLine); + + //Clone selected nodes + // TODO: Simplify all this cloning since we only need a formula string now. + SmClipboard aClipboard; + if(IsLineCompositionNode(pLine)) + CloneLineToClipboard(static_cast<SmStructureNode*>(pLine), &aClipboard); + else{ + //Special care to only clone selected text + if(pLine->GetType() == SmNodeType::Text) { + SmTextNode *pText = static_cast<SmTextNode*>(pLine); + std::unique_ptr<SmTextNode> pClone(new SmTextNode( pText->GetToken(), pText->GetFontDesc() )); + int start = pText->GetSelectionStart(), + length = pText->GetSelectionEnd() - pText->GetSelectionStart(); + pClone->ChangeText(pText->GetText().copy(start, length)); + pClone->SetScaleMode(pText->GetScaleMode()); + aClipboard.push_front(std::move(pClone)); + } else { + SmCloningVisitor aCloneFactory; + aClipboard.push_front(std::unique_ptr<SmNode>(aCloneFactory.Clone(pLine))); + } + } + + // Parse list of nodes to a tree + SmNodeListParser parser; + SmNode* pTree(parser.Parse(CloneList(aClipboard).get())); + + // Parse the tree to a string + OUString aString; + SmNodeToTextVisitor(pTree, aString); + + //Set clipboard + auto xClipboard(pWindow ? pWindow->GetClipboard() : GetSystemClipboard()); + vcl::unohelper::TextDataObject::CopyStringTo(aString, xClipboard); +} + +void SmCursor::Paste(vcl::Window* pWindow) +{ + BeginEdit(); + Delete(); + + auto xClipboard(pWindow ? pWindow->GetClipboard() : GetSystemClipboard()); + auto aDataHelper(TransferableDataHelper::CreateFromClipboard(xClipboard)); + if (aDataHelper.GetTransferable().is()) + { + // TODO: Support MATHML + auto nId = SotClipboardFormatId::STRING; + if (aDataHelper.HasFormat(nId)) + { + OUString aString; + if (aDataHelper.GetString(nId, aString)) + InsertCommandText(aString); + } + } + + EndEdit(); +} + +std::unique_ptr<SmNodeList> SmCursor::CloneList(SmClipboard &rClipboard){ + SmCloningVisitor aCloneFactory; + std::unique_ptr<SmNodeList> pClones(new SmNodeList); + + for(auto &xNode : rClipboard){ + SmNode *pClone = aCloneFactory.Clone(xNode.get()); + pClones->push_back(pClone); + } + + return pClones; +} + +SmNode* SmCursor::FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected){ + assert(pSNode); + //Move up parent until we find a node who's + //parent is NULL or isn't selected and not a type of: + // SmExpressionNode + // SmLineNode + // SmBinHorNode + // SmUnHorNode + // SmAlignNode + // SmFontNode + while(pSNode->GetParent() && + ((MoveUpIfSelected && + pSNode->GetParent()->IsSelected()) || + IsLineCompositionNode(pSNode->GetParent()))) + pSNode = pSNode->GetParent(); + //Now we have the selection line node + return pSNode; +} + +SmNode* SmCursor::FindSelectedNode(SmNode* pNode){ + if(pNode->GetNumSubNodes() == 0) + return nullptr; + for(auto pChild : *static_cast<SmStructureNode*>(pNode)) + { + if(!pChild) + continue; + if(pChild->IsSelected()) + return pChild; + SmNode* pRetVal = FindSelectedNode(pChild); + if(pRetVal) + return pRetVal; + } + return nullptr; +} + +void SmCursor::LineToList(SmStructureNode* pLine, SmNodeList& list){ + for(auto pChild : *pLine) + { + if (!pChild) + continue; + switch(pChild->GetType()){ + case SmNodeType::Line: + case SmNodeType::UnHor: + case SmNodeType::Expression: + case SmNodeType::BinHor: + case SmNodeType::Align: + case SmNodeType::Font: + LineToList(static_cast<SmStructureNode*>(pChild), list); + break; + case SmNodeType::Error: + delete pChild; + break; + default: + list.push_back(pChild); + } + } + pLine->ClearSubNodes(); + delete pLine; +} + +void SmCursor::CloneLineToClipboard(SmStructureNode* pLine, SmClipboard* pClipboard){ + SmCloningVisitor aCloneFactory; + for(auto pChild : *pLine) + { + if (!pChild) + continue; + if( IsLineCompositionNode( pChild ) ) + CloneLineToClipboard( static_cast<SmStructureNode*>(pChild), pClipboard ); + else if( pChild->IsSelected() && pChild->GetType() != SmNodeType::Error ) { + //Only clone selected text from SmTextNode + if(pChild->GetType() == SmNodeType::Text) { + SmTextNode *pText = static_cast<SmTextNode*>(pChild); + std::unique_ptr<SmTextNode> pClone(new SmTextNode( pChild->GetToken(), pText->GetFontDesc() )); + int start = pText->GetSelectionStart(), + length = pText->GetSelectionEnd() - pText->GetSelectionStart(); + pClone->ChangeText(pText->GetText().copy(start, length)); + pClone->SetScaleMode(pText->GetScaleMode()); + pClipboard->push_back(std::move(pClone)); + } else + pClipboard->push_back(std::unique_ptr<SmNode>(aCloneFactory.Clone(pChild))); + } + } +} + +bool SmCursor::IsLineCompositionNode(SmNode const * pNode){ + switch(pNode->GetType()){ + case SmNodeType::Line: + case SmNodeType::UnHor: + case SmNodeType::Expression: + case SmNodeType::BinHor: + case SmNodeType::Align: + case SmNodeType::Font: + return true; + default: + return false; + } +} + +int SmCursor::CountSelectedNodes(SmNode* pNode){ + if(pNode->GetNumSubNodes() == 0) + return 0; + int nCount = 0; + for(auto pChild : *static_cast<SmStructureNode*>(pNode)) + { + if (!pChild) + continue; + if(pChild->IsSelected() && !IsLineCompositionNode(pChild)) + nCount++; + nCount += CountSelectedNodes(pChild); + } + return nCount; +} + +bool SmCursor::HasComplexSelection(){ + if(!HasSelection()) + return false; + AnnotateSelection(); + + return CountSelectedNodes(mpTree) > 1; +} + +void SmCursor::FinishEdit(std::unique_ptr<SmNodeList> pLineList, + SmStructureNode* pParent, + int nParentIndex, + SmCaretPos PosAfterEdit, + SmNode* pStartLine) { + //Store number of nodes in line for later + int entries = pLineList->size(); + + //Parse list of nodes to a tree + SmNodeListParser parser; + std::unique_ptr<SmNode> pLine(parser.Parse(pLineList.get())); + pLineList.reset(); + + //Check if we're making the body of a subsup node bigger than one + if(pParent->GetType() == SmNodeType::SubSup && + nParentIndex == 0 && + entries > 1) { + //Wrap pLine in scalable round brackets + SmToken aTok(TLEFT, '\0', "left", TG::NONE, 5); + std::unique_ptr<SmBraceNode> pBrace(new SmBraceNode(aTok)); + pBrace->SetScaleMode(SmScaleMode::Height); + std::unique_ptr<SmNode> pLeft( CreateBracket(SmBracketType::Round, true) ), + pRight( CreateBracket(SmBracketType::Round, false) ); + std::unique_ptr<SmBracebodyNode> pBody(new SmBracebodyNode(SmToken())); + pBody->SetSubNodes(std::move(pLine), nullptr); + pBrace->SetSubNodes(std::move(pLeft), std::move(pBody), std::move(pRight)); + pBrace->Prepare(mpDocShell->GetFormat(), *mpDocShell, 0); + pLine = std::move(pBrace); + //TODO: Consider the following alternative behavior: + //Consider the line: A + {B + C}^D lsub E + //Here pLineList is B, + and C and pParent is a subsup node with + //both RSUP and LSUB set. Imagine the user just inserted "B +" in + //the body of the subsup node... + //The most natural thing to do would be to make the line like this: + //A + B lsub E + C ^ D + //E.g. apply LSUB and LSUP to the first element in pLineList and RSUP + //and RSUB to the last element in pLineList. But how should this act + //for CSUP and CSUB ??? + //For this reason and because brackets was faster to implement, this solution + //have been chosen. It might be worth working on the other solution later... + } + + //Set pStartLine if NULL + if(!pStartLine) + pStartLine = pLine.get(); + + //Insert it back into the parent + pParent->SetSubNode(nParentIndex, pLine.release()); + + //Rebuild graph of caret position + mpAnchor = nullptr; + mpPosition = nullptr; + BuildGraph(); + AnnotateSelection(); //Update selection annotation! + + //Set caret position + if(!SetCaretPosition(PosAfterEdit)) + SetCaretPosition(SmCaretPos(pStartLine, 0)); + + //End edit section + EndEdit(); +} + +void SmCursor::BeginEdit(){ + if(mnEditSections++ > 0) return; + + mbIsEnabledSetModifiedSmDocShell = mpDocShell->IsEnableSetModified(); + if( mbIsEnabledSetModifiedSmDocShell ) + mpDocShell->EnableSetModified( false ); +} + +void SmCursor::EndEdit(){ + if(--mnEditSections > 0) return; + + mpDocShell->SetFormulaArranged(false); + //Okay, I don't know what this does... :) + //It's used in SmDocShell::SetText and with places where everything is modified. + //I think it does some magic, with sfx, but everything is totally undocumented so + //it's kinda hard to tell... + if ( mbIsEnabledSetModifiedSmDocShell ) + mpDocShell->EnableSetModified( mbIsEnabledSetModifiedSmDocShell ); + //I think this notifies people around us that we've modified this document... + mpDocShell->SetModified(); + //I think SmDocShell uses this value when it sends an update graphics event + //Anyway comments elsewhere suggests it needs to be updated... + mpDocShell->mnModifyCount++; + + //TODO: Consider copying the update accessibility code from SmDocShell::SetText in here... + //This somehow updates the size of SmGraphicView if it is running in embedded mode + if( mpDocShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + mpDocShell->OnDocumentPrinterChanged(nullptr); + + //Request a repaint... + RequestRepaint(); + + //Update the edit engine and text of the document + OUString formula; + SmNodeToTextVisitor(mpTree, formula); + mpDocShell->maText = formula; + mpDocShell->GetEditEngine().QuickInsertText( formula, ESelection( 0, 0, EE_PARA_ALL, EE_TEXTPOS_ALL ) ); + mpDocShell->GetEditEngine().QuickFormatDoc(); +} + +void SmCursor::RequestRepaint() +{ + if (SmViewShell *pViewSh = SmGetActiveView()) + { + if (comphelper::LibreOfficeKit::isActive()) + { + pViewSh->SendCaretToLOK(); + } + else if ( SfxObjectCreateMode::EMBEDDED == mpDocShell->GetCreateMode() ) + mpDocShell->Repaint(); + else + pViewSh->GetGraphicWidget().Invalidate(); + } +} + +bool SmCursor::IsAtTailOfBracket(SmBracketType eBracketType) const +{ + const SmCaretPos pos = GetPosition(); + if (!pos.IsValid()) { + return false; + } + + SmNode* pNode = pos.pSelectedNode; + + if (pNode->GetType() == SmNodeType::Text) { + SmTextNode* pTextNode = static_cast<SmTextNode*>(pNode); + if (pos.nIndex < pTextNode->GetText().getLength()) { + // The cursor is on a text node and at the middle of it. + return false; + } + } else { + if (pos.nIndex < 1) { + return false; + } + } + + while (true) { + SmStructureNode* pParentNode = pNode->GetParent(); + if (!pParentNode) { + // There's no brace body node in the ancestors. + return false; + } + + int index = pParentNode->IndexOfSubNode(pNode); + assert(index >= 0); + if (static_cast<size_t>(index + 1) != pParentNode->GetNumSubNodes()) { + // The cursor is not at the tail at one of ancestor nodes. + return false; + } + + pNode = pParentNode; + if (pNode->GetType() == SmNodeType::Bracebody) { + // Found the brace body node. + break; + } + } + + SmStructureNode* pBraceNodeTmp = pNode->GetParent(); + if (!pBraceNodeTmp || pBraceNodeTmp->GetType() != SmNodeType::Brace) { + // Brace node is invalid. + return false; + } + + SmBraceNode* pBraceNode = static_cast<SmBraceNode*>(pBraceNodeTmp); + SmMathSymbolNode* pClosingNode = pBraceNode->ClosingBrace(); + if (!pClosingNode) { + // Couldn't get closing symbol node. + return false; + } + + // Check if the closing brace matches eBracketType. + SmTokenType eClosingTokenType = pClosingNode->GetToken().eType; + switch (eBracketType) { + case SmBracketType::Round: if (eClosingTokenType != TRPARENT) { return false; } break; + case SmBracketType::Square: if (eClosingTokenType != TRBRACKET) { return false; } break; + case SmBracketType::Curly: if (eClosingTokenType != TRBRACE) { return false; } break; + default: + return false; + } + + return true; +} + +/////////////////////////////////////// SmNodeListParser + +SmNode* SmNodeListParser::Parse(SmNodeList* list){ + pList = list; + //Delete error nodes + SmNodeList::iterator it = pList->begin(); + while(it != pList->end()) { + if((*it)->GetType() == SmNodeType::Error){ + //Delete and erase + delete *it; + it = pList->erase(it); + }else + ++it; + } + SmNode* retval = Expression(); + pList = nullptr; + return retval; +} + +SmNode* SmNodeListParser::Expression(){ + SmNodeArray NodeArray; + //Accept as many relations as there is + while(Terminal()) + NodeArray.push_back(Relation()); + + //Create SmExpressionNode, I hope SmToken() will do :) + SmStructureNode* pExpr = new SmExpressionNode(SmToken()); + pExpr->SetSubNodes(std::move(NodeArray)); + return pExpr; +} + +SmNode* SmNodeListParser::Relation(){ + //Read a sum + std::unique_ptr<SmNode> pLeft(Sum()); + //While we have tokens and the next is a relation + while(Terminal() && IsRelationOperator(Terminal()->GetToken())){ + //Take the operator + std::unique_ptr<SmNode> pOper(Take()); + //Find the right side of the relation + std::unique_ptr<SmNode> pRight(Sum()); + //Create new SmBinHorNode + std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken())); + pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight)); + pLeft = std::move(pNewNode); + } + return pLeft.release(); +} + +SmNode* SmNodeListParser::Sum(){ + //Read a product + std::unique_ptr<SmNode> pLeft(Product()); + //While we have tokens and the next is a sum + while(Terminal() && IsSumOperator(Terminal()->GetToken())){ + //Take the operator + std::unique_ptr<SmNode> pOper(Take()); + //Find the right side of the sum + std::unique_ptr<SmNode> pRight(Product()); + //Create new SmBinHorNode + std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken())); + pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight)); + pLeft = std::move(pNewNode); + } + return pLeft.release(); +} + +SmNode* SmNodeListParser::Product(){ + //Read a Factor + std::unique_ptr<SmNode> pLeft(Factor()); + //While we have tokens and the next is a product + while(Terminal() && IsProductOperator(Terminal()->GetToken())){ + //Take the operator + std::unique_ptr<SmNode> pOper(Take()); + //Find the right side of the operation + std::unique_ptr<SmNode> pRight(Factor()); + //Create new SmBinHorNode + std::unique_ptr<SmStructureNode> pNewNode(new SmBinHorNode(SmToken())); + pNewNode->SetSubNodes(std::move(pLeft), std::move(pOper), std::move(pRight)); + pLeft = std::move(pNewNode); + } + return pLeft.release(); +} + +SmNode* SmNodeListParser::Factor(){ + //Read unary operations + if(!Terminal()) + return Error(); + //Take care of unary operators + else if(IsUnaryOperator(Terminal()->GetToken())) + { + SmStructureNode *pUnary = new SmUnHorNode(SmToken()); + std::unique_ptr<SmNode> pOper(Terminal()), + pArg; + + if(Next()) + pArg.reset(Factor()); + else + pArg.reset(Error()); + + pUnary->SetSubNodes(std::move(pOper), std::move(pArg)); + return pUnary; + } + return Postfix(); +} + +SmNode* SmNodeListParser::Postfix(){ + if(!Terminal()) + return Error(); + std::unique_ptr<SmNode> pArg; + if(IsPostfixOperator(Terminal()->GetToken())) + pArg.reset(Error()); + else if(IsOperator(Terminal()->GetToken())) + return Error(); + else + pArg.reset(Take()); + while(Terminal() && IsPostfixOperator(Terminal()->GetToken())) { + std::unique_ptr<SmStructureNode> pUnary(new SmUnHorNode(SmToken()) ); + std::unique_ptr<SmNode> pOper(Take()); + pUnary->SetSubNodes(std::move(pArg), std::move(pOper)); + pArg = std::move(pUnary); + } + return pArg.release(); +} + +SmNode* SmNodeListParser::Error(){ + return new SmErrorNode(SmToken()); +} + +bool SmNodeListParser::IsOperator(const SmToken &token) { + return IsRelationOperator(token) || + IsSumOperator(token) || + IsProductOperator(token) || + IsUnaryOperator(token) || + IsPostfixOperator(token); +} + +bool SmNodeListParser::IsRelationOperator(const SmToken &token) { + return bool(token.nGroup & TG::Relation); +} + +bool SmNodeListParser::IsSumOperator(const SmToken &token) { + return bool(token.nGroup & TG::Sum); +} + +bool SmNodeListParser::IsProductOperator(const SmToken &token) { + return token.nGroup & TG::Product && + token.eType != TWIDESLASH && + token.eType != TWIDEBACKSLASH && + token.eType != TUNDERBRACE && + token.eType != TOVERBRACE && + token.eType != TOVER; +} + +bool SmNodeListParser::IsUnaryOperator(const SmToken &token) { + return token.nGroup & TG::UnOper && + (token.eType == TPLUS || + token.eType == TMINUS || + token.eType == TPLUSMINUS || + token.eType == TMINUSPLUS || + token.eType == TNEG || + token.eType == TUOPER); +} + +bool SmNodeListParser::IsPostfixOperator(const SmToken &token) { + return token.eType == TFACT; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |