382b82541a
this is the inline starmath editing where you can edit the formula directly in the view window instead of the command window. Currently requires experimental to be enabled. reproduce by clicking in initially empty formula and enter a character. In practice the deleted pos.pSelectedNode is not actually used-after-free in SmCursor::FindPositionInLineList because it is not found by the std::find of pLineList. Change-Id: I57476a8eb073914099c5026dd33dc75b20288d52 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/140003 Tested-by: Jenkins Reviewed-by: Caolán McNamara <caolanm@redhat.com>
1563 lines
54 KiB
C++
1563 lines
54 KiB
C++
/* -*- 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 <editeng/editeng.hxx>
|
|
#include <osl/diagnose.h>
|
|
|
|
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(){
|
|
//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);
|
|
}
|
|
|
|
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"";
|
|
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"";
|
|
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(){
|
|
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
|
|
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)));
|
|
}
|
|
}
|
|
|
|
//Set clipboard
|
|
if (!aClipboard.empty())
|
|
maClipboard = std::move(aClipboard);
|
|
}
|
|
|
|
void SmCursor::Paste() {
|
|
BeginEdit();
|
|
Delete();
|
|
|
|
if (!maClipboard.empty())
|
|
InsertNodes(CloneList(maClipboard));
|
|
|
|
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(){
|
|
SmViewShell *pViewSh = SmGetActiveView();
|
|
if( pViewSh ) {
|
|
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: */
|