You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/lib/kformula/formulacursor.cc

747 lines
19 KiB

/* This file is part of the KDE project
Copyright (C) 2001 Andrea Rizzi <rizzi@kde.org>
Ulrich Kuettler <ulrich.kuettler@mailbox.tu-dresden.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <qpainter.h>
#include <kdebug.h>
#include <assert.h>
#include "formulacursor.h"
#include "formulaelement.h"
#include "indexelement.h"
#include "matrixelement.h"
#include "rootelement.h"
#include "sequenceelement.h"
#include "symbolelement.h"
#include "textelement.h"
KFORMULA_NAMESPACE_BEGIN
FormulaCursor::FormulaCursor(FormulaElement* element)
: selectionFlag(false), linearMovement(false),
hasChangedFlag(true), readOnly(false)
{
//setTo(element, 0);
element->goInside( this );
}
void FormulaCursor::setTo(BasicElement* element, uint cursor, int mark)
{
hasChangedFlag = true;
current = element;
cursorPos = cursor;
if ((mark == -1) && selectionFlag) {
return;
}
if (mark != -1) {
setSelection(true);
}
markPos = mark;
}
void FormulaCursor::setPos(uint pos)
{
hasChangedFlag = true;
cursorPos = pos;
}
void FormulaCursor::setMark(int mark)
{
hasChangedFlag = true;
markPos = mark;
}
void FormulaCursor::calcCursorSize( const ContextStyle& context, bool smallCursor )
{
// We only draw the cursor if its normalized.
SequenceElement* sequence = dynamic_cast<SequenceElement*>(current);
if (sequence != 0) {
sequence->calcCursorSize( context, this, smallCursor );
}
}
void FormulaCursor::draw( QPainter& painter, const ContextStyle& context,
StyleAttributes& style, bool smallCursor, bool activeCursor )
{
//if (readOnly && !isSelection())
//return;
// We only draw the cursor if its normalized.
SequenceElement* sequence = dynamic_cast<SequenceElement*>(current);
if (sequence != 0) {
sequence->drawCursor( painter, context, style, this, smallCursor, activeCursor );
}
}
void FormulaCursor::handleSelectState(int flag)
{
if (flag & SelectMovement) {
if (!isSelection()) {
setMark(getPos());
setSelection(true);
}
}
else {
setSelection(false);
}
}
void FormulaCursor::moveLeft(int flag)
{
BasicElement* element = getElement();
handleSelectState(flag);
if (flag & WordMovement) {
SequenceElement* sequence = dynamic_cast<SequenceElement*>(current);
if (sequence != 0) {
sequence->moveWordLeft(this);
}
else {
element->moveHome(this);
}
}
else {
element->moveLeft(this, element);
}
}
void FormulaCursor::moveRight(int flag)
{
BasicElement* element = getElement();
handleSelectState(flag);
if (flag & WordMovement) {
SequenceElement* sequence = dynamic_cast<SequenceElement*>(current);
if (sequence != 0) {
sequence->moveWordRight(this);
}
else {
element->moveEnd(this);
}
}
else {
element->moveRight(this, element);
}
}
void FormulaCursor::moveUp(int flag)
{
BasicElement* element = getElement();
handleSelectState(flag);
element->moveUp(this, element);
}
void FormulaCursor::moveDown(int flag)
{
BasicElement* element = getElement();
handleSelectState(flag);
element->moveDown(this, element);
}
void FormulaCursor::moveHome(int flag)
{
BasicElement* element = getElement();
handleSelectState(flag);
if (flag & WordMovement) {
element->formula()->moveHome(this);
}
else {
element->moveHome(this);
}
}
void FormulaCursor::moveEnd(int flag)
{
BasicElement* element = getElement();
handleSelectState(flag);
if (flag & WordMovement) {
element->formula()->moveEnd(this);
}
else {
element->moveEnd(this);
}
}
bool FormulaCursor::isHome() const
{
return ( getElement() == getElement()->formula() ) && ( getPos() == 0 );
}
bool FormulaCursor::isEnd() const
{
return ( getElement() == getElement()->formula() ) &&
( getPos() == normal()->countChildren() );
}
void FormulaCursor::mousePress( const LuPixelPoint& pos, int flag )
{
FormulaElement* formula = getElement()->formula();
formula->goToPos( this, pos );
if (flag & SelectMovement) {
setSelection(true);
if (getMark() == -1) {
setMark(getPos());
}
}
else {
setSelection(false);
setMark(getPos());
}
}
void FormulaCursor::mouseMove( const LuPixelPoint& point, int )
{
setSelection(true);
BasicElement* element = getElement();
int mark = getMark();
FormulaElement* formula = getElement()->formula();
formula->goToPos( this, point );
BasicElement* newElement = getElement();
int pos = getPos();
BasicElement* posChild = 0;
BasicElement* markChild = 0;
while (element != newElement) {
posChild = newElement;
newElement = newElement->getParent();
if (newElement == 0) {
posChild = 0;
newElement = getElement();
markChild = element;
element = element->getParent();
}
}
if (dynamic_cast<SequenceElement*>(element) == 0) {
element = element->getParent();
element->selectChild(this, newElement);
}
else {
if (posChild != 0) {
element->selectChild(this, posChild);
pos = getPos();
}
if (markChild != 0) {
element->selectChild(this, markChild);
mark = getMark();
}
if (pos == mark) {
if ((posChild == 0) && (markChild != 0)) {
mark++;
}
else if ((posChild != 0) && (markChild == 0)) {
mark--;
}
}
else if (pos < mark) {
if (posChild != 0) {
pos--;
}
}
setTo(element, pos, mark);
}
}
void FormulaCursor::mouseRelease( const LuPixelPoint&, int )
{
//mouseSelectionFlag = false;
}
/**
* Moves the cursor inside the element. Selection is turned off.
*/
void FormulaCursor::goInsideElement(BasicElement* element)
{
element->goInside(this);
}
/**
* Moves the cursor to a normal position. That is somewhere
* inside a SequenceElement.
* You need to call this after each removal because the cursor
* might point to some non existing place.
*/
void FormulaCursor::normalize(Direction direction)
{
BasicElement* element = getElement();
element->normalize(this, direction);
}
/**
* Inserts the child at the current position.
* Ignores the selection.
*/
void FormulaCursor::insert(BasicElement* child, Direction direction)
{
QPtrList<BasicElement> list;
list.append(child);
insert(list, direction);
}
void FormulaCursor::insert(QPtrList<BasicElement>& children,
Direction direction)
{
assert( !isReadOnly() );
BasicElement* element = getElement();
element->insert(this, children, direction);
}
/**
* Removes the current selected children and returns them.
* The cursor needs to be normal (that is be inside a SequenceElement)
* for this to have any effect.
*/
void FormulaCursor::remove(QPtrList<BasicElement>& children,
Direction direction)
{
assert( !isReadOnly() );
SequenceElement* sequence = normal();
if (sequence != 0) {
// If there is no child to remove in the sequence
// remove the sequence instead.
if (sequence->countChildren() == 0) {
BasicElement* parent = sequence->getParent();
if (parent != 0) {
parent->selectChild(this, sequence);
parent->remove(this, children, direction);
return;
}
}
else {
sequence->remove(this, children, direction);
}
}
}
/**
* Replaces the current selection with the supplied element.
* The replaced elements become the new element's main child's content.
*/
void FormulaCursor::replaceSelectionWith(BasicElement* element,
Direction direction)
{
assert( !isReadOnly() );
QPtrList<BasicElement> list;
// we suppres deletion here to get an error if something
// was left in the list.
//list.setAutoDelete(true);
//remove(list, direction);
if (isSelection()) {
getElement()->remove(this, list, direction);
}
insert(element, direction);
SequenceElement* mainChild = element->getMainChild();
if (mainChild != 0) {
mainChild->goInside(this);
insert(list);
/*
BasicElement* parent = element->getParent();
if (direction == beforeCursor) {
parent->moveRight(this, element);
}
else {
parent->moveLeft(this, element);
}
*/
element->selectChild(this, mainChild);
}
}
/**
* Replaces the element the cursor points to with its main child's
* content.
*/
BasicElement* FormulaCursor::replaceByMainChildContent(Direction direction)
{
assert( !isReadOnly() );
QPtrList<BasicElement> childrenList;
QPtrList<BasicElement> list;
BasicElement* element = getElement();
SequenceElement* mainChild = element->getMainChild();
if ((mainChild != 0) && (mainChild->countChildren() > 0)) {
mainChild->selectAllChildren(this);
remove(childrenList);
}
element->getParent()->selectChild(this, element);
setSelection(false);
remove(list);
insert(childrenList, direction);
if (list.count() > 0) {
return list.take(0);
}
return 0;
}
/**
* Trys to find the element we are the main child of and replace
* it with our content.
*
* This is simply another form of replaceByMainChildContent. You
* use this one if the cursor is normalized and inside the main child.
*/
BasicElement* FormulaCursor::removeEnclosingElement(Direction direction)
{
assert( !isReadOnly() );
BasicElement* parent = getElement()->getParent();
if (parent != 0) {
if (getElement() == parent->getMainChild()) {
parent->selectChild(this, getElement());
return replaceByMainChildContent(direction);
}
}
return 0;
}
/**
* Returns wether the element the cursor points to should be replaced.
* Elements are senseless as soon as they only contain a main child.
*/
bool FormulaCursor::elementIsSenseless()
{
BasicElement* element = getElement();
return element->isSenseless();
}
/**
* Returns the child the cursor points to. Depending on the
* direction this might be the child before or after the
* cursor.
*
* Might be 0 is there is no such child.
*/
BasicElement* FormulaCursor::getActiveChild(Direction direction)
{
return getElement()->getChild(this, direction);
}
BasicElement* FormulaCursor::getSelectedChild()
{
if (isSelection()) {
if ((getSelectionEnd() - getSelectionStart()) > 1) {
return 0;
}
return getActiveChild((getPos() > getMark()) ?
beforeCursor :
afterCursor);
}
else {
return getActiveChild(beforeCursor);
}
}
void FormulaCursor::selectActiveElement()
{
if ( !isSelection() && getPos() > 0 ) {
setSelection( true );
setMark( getPos() - 1 );
}
}
/**
* Tells whether we currently point to the given elements
* main child and to the place behind its last child.
*/
bool FormulaCursor::pointsAfterMainChild(BasicElement* element)
{
if (element != 0) {
SequenceElement* mainChild = element->getMainChild();
return (getElement() == mainChild) &&
((mainChild->countChildren() == getPos()) || (0 == getPos()));
}
return false;
}
/**
* Returns the IndexElement the cursor is on or 0
* if there is non.
*/
IndexElement* FormulaCursor::getActiveIndexElement()
{
IndexElement* element = dynamic_cast<IndexElement*>(getSelectedChild());
if ((element == 0) && !isSelection()) {
element = dynamic_cast<IndexElement*>(getElement()->getParent());
if (!pointsAfterMainChild(element)) {
return 0;
}
}
return element;
}
/**
* Returns the RootElement the cursor is on or 0
* if there is non.
*/
RootElement* FormulaCursor::getActiveRootElement()
{
RootElement* element = dynamic_cast<RootElement*>(getSelectedChild());
if ((element == 0) && !isSelection()) {
element = dynamic_cast<RootElement*>(getElement()->getParent());
if (!pointsAfterMainChild(element)) {
return 0;
}
}
return element;
}
/**
* @returns the SymbolElement the cursor is on or 0
* if there is non.
*/
SymbolElement* FormulaCursor::getActiveSymbolElement()
{
SymbolElement* element = dynamic_cast<SymbolElement*>(getSelectedChild());
if ((element == 0) && !isSelection()) {
element = dynamic_cast<SymbolElement*>(getElement()->getParent());
if (!pointsAfterMainChild(element)) {
return 0;
}
}
return element;
}
/**
* @returns the NameSequence the cursor is on or 0
* if there is non.
*/
NameSequence* FormulaCursor::getActiveNameSequence()
{
NameSequence* element = dynamic_cast<NameSequence*>( getSelectedChild() );
if ( ( element == 0 ) && !isSelection() ) {
element = dynamic_cast<NameSequence*>( getElement() );
if ( !pointsAfterMainChild( element ) ) {
return 0;
}
}
return element;
}
/**
* @returns the TextElement the cursor is on or 0.
*/
TextElement* FormulaCursor::getActiveTextElement()
{
return dynamic_cast<TextElement*>(getSelectedChild());
}
MatrixElement* FormulaCursor::getActiveMatrixElement()
{
MatrixElement* element = dynamic_cast<MatrixElement*>(getSelectedChild());
if ( ( element != 0 ) && !isSelection() ) {
normal()->selectChild( this, element );
}
// if ((element == 0) && !isSelection()) {
// element = dynamic_cast<MatrixElement*>(getElement()->getParent());
// if (!pointsAfterMainChild(element)) {
// return 0;
// }
// }
return element;
}
/**
* The element is going to leave the formula with and all its children.
*/
void FormulaCursor::elementWillVanish(BasicElement* element)
{
BasicElement* child = getElement();
if (element && child == element->getParent()) {
child->childWillVanish(this, element);
return;
}
while (child != 0) {
if (child == element) {
// This is meant to catch all cursors that did not
// cause the deletion.
child->getParent()->moveLeft(this, child);
setSelection(false);
hasChangedFlag = true;
return;
}
child = child->getParent();
}
}
/**
* A new formula has been loaded. Our current element has to change.
*/
void FormulaCursor::formulaLoaded(FormulaElement* rootElement)
{
//current = rootElement;
//setPos(0);
rootElement->goInside( this );
setMark(-1);
setSelection(false);
}
bool FormulaCursor::isReadOnly() const
{
if ( readOnly ) {
return true;
}
const SequenceElement* sequence = normal();
if ( sequence != 0 ) {
bool ro = sequence->readOnly( this );
//kdDebug() << k_funcinfo << "readOnly=" << ro << endl;
return ro;
}
return false;
}
/**
* Stores the currently selected elements inside a dom.
*/
void FormulaCursor::copy( QDomDocument& doc )
{
if (isSelection()) {
SequenceElement* sequence = normal();
if (sequence != 0) {
QDomElement root = doc.createElementNS( "http://www.w3.org/1998/Math/MathML",
"math" );
doc.appendChild( root );
QDomElement de = doc.createElement( "mrow" );
root.appendChild( de );
sequence->getChildrenMathMLDom(doc, de, getSelectionStart(), getSelectionEnd());
}
else {
// This must never happen.
qFatal("A not normalized cursor is selecting.");
}
}
}
/**
* Inserts the elements that could be read from the dom into
* the list. Returns true on success.
*/
bool FormulaCursor::buildElementsFromDom( QDomElement root, QPtrList<BasicElement>& list )
{
assert( !isReadOnly() );
SequenceElement* sequence = normal();
if (sequence != 0) {
QDomElement e = root.firstChild().toElement();
if (sequence->buildChildrenFromDom(list, e.firstChild())) {
return true;
}
}
return false;
}
/**
* Inserts the elements that could be read from the MathML dom into
* the list. Returns true on success.
*/
bool FormulaCursor::buildElementsFromMathMLDom( QDomElement root, QPtrList<BasicElement>& list )
{
assert( !isReadOnly() );
SequenceElement* sequence = normal();
if (sequence != 0) {
QDomElement e = root.firstChild().toElement();
if (sequence->buildChildrenFromMathMLDom(list, e.firstChild())) {
return true;
}
}
return false;
}
/**
* Creates a new CursorData object that describes the cursor.
* It's up to the caller to delete this object.
*/
FormulaCursor::CursorData* FormulaCursor::getCursorData()
{
return new CursorData(current, cursorPos, markPos,
selectionFlag, linearMovement, readOnly);
}
// Keep in sync with 'setCursorData'
FormulaCursor& FormulaCursor::operator= (const FormulaCursor& other)
{
current = other.current;
cursorPos = other.cursorPos;
markPos = other.markPos;
selectionFlag = other.selectionFlag;
linearMovement = other.linearMovement;
readOnly = other.readOnly;
hasChangedFlag = true;
return *this;
}
/**
* Sets the cursor to where the CursorData points to. No checking is done
* so you better make sure the point exists.
*/
void FormulaCursor::setCursorData(FormulaCursor::CursorData* data)
{
current = data->current;
cursorPos = data->cursorPos;
markPos = data->markPos;
selectionFlag = data->selectionFlag;
linearMovement = data->linearMovement;
readOnly = data->readOnly;
hasChangedFlag = true;
}
/**
* Returns the sequence the cursor is in if we are normal. If not returns 0.
*/
SequenceElement* FormulaCursor::normal()
{
return dynamic_cast<SequenceElement*>(current);
}
const SequenceElement* FormulaCursor::normal() const
{
return dynamic_cast<SequenceElement*>(current);
}
KFORMULA_NAMESPACE_END