/* -*- 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(constauto &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(constauto &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(constauto &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");
}
void SmCursor::AnnotateSelection() const { //TODO: Manage a state, reset it upon modification and optimize this call
SmSetSelectionVisitor(mpAnchor->CaretPos, mpPosition->CaretPos, mpTree);
}
//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); elseif(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;
//Get the current position const SmCaretPos pos = mpPosition->CaretPos;
//Find top most of line that holds position
SmNode* pLine = FindTopMostNodeInLine(pos.pSelectedNode); constbool 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);
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();
}
//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;
}
//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);
//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());
//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()));
//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;
//Find parent and offset in parent
SmStructureNode *pLineParent = pLine->GetParent(); int nParentIndex = pLineParent->IndexOfSubNode(pLine);
assert(nParentIndex >= 0 && nParentIndex < INT_MAX);
//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 elseif(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 && nTableIndex < INT_MAX);
} 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) returnfalse;
//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', u"newline"_ustr)));
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. returntrue;
}
//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 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);
//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);
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);
}
}
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;
}
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: returntrue; default: returnfalse;
}
}
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', u"left"_ustr, 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));
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::All());
mpDocShell->GetEditEngine().QuickFormatDoc();
}
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. returnfalse;
}
} else { if (pos.nIndex < 1) { returnfalse;
}
}
while (true) {
SmStructureNode* pParentNode = pNode->GetParent(); if (!pParentNode) { // There's no brace body node in the ancestors. returnfalse;
}
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. returnfalse;
}
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. returnfalse;
}
SmBraceNode* pBraceNode = static_cast<SmBraceNode*>(pBraceNodeTmp);
SmMathSymbolNode* pClosingNode = pBraceNode->ClosingBrace(); if (!pClosingNode) { // Couldn't get closing symbol node. returnfalse;
}
// Check if the closing brace matches eBracketType.
SmTokenType eClosingTokenType = pClosingNode->GetToken().eType; switch (eBracketType) { case SmBracketType::Round: if (eClosingTokenType != TRPARENT) { returnfalse; } break; case SmBracketType::Square: if (eClosingTokenType != TRBRACKET) { returnfalse; } break; case SmBracketType::Curly: if (eClosingTokenType != TRBRACE) { returnfalse; } break; default: returnfalse;
}
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 elseif(IsUnaryOperator(Terminal()->GetToken()))
{
SmStructureNode *pUnary = new SmUnHorNode(SmToken());
std::unique_ptr<SmNode> pOper(Terminal()),
pArg;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.