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/fractionelement.cc

658 lines
20 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 <klocale.h>
#include "elementvisitor.h"
#include "formulaelement.h"
#include "formulacursor.h"
#include "fractionelement.h"
#include "sequenceelement.h"
KFORMULA_NAMESPACE_BEGIN
using namespace std;
FractionElement::FractionElement(BasicElement* parent) : BasicElement(parent),
m_lineThicknessType( NoSize ),
m_numAlign( NoHorizontalAlign ),
m_denomAlign( NoHorizontalAlign ),
m_customBevelled( false )
{
numerator = new SequenceElement(this);
denominator = new SequenceElement(this);
}
FractionElement::~FractionElement()
{
delete denominator;
delete numerator;
}
FractionElement::FractionElement( const FractionElement& other )
: BasicElement( other ),
m_lineThicknessType( other.m_lineThicknessType ),
m_lineThickness( other.m_lineThickness ),
m_numAlign( other.m_numAlign ),
m_denomAlign( other.m_denomAlign ),
m_customBevelled( other.m_customBevelled ),
m_bevelled( other.m_bevelled )
{
numerator = new SequenceElement( *( other.numerator ) );
denominator = new SequenceElement( *( other.denominator ) );
numerator->setParent( this );
denominator->setParent( this );
}
bool FractionElement::accept( ElementVisitor* visitor )
{
return visitor->visit( this );
}
void FractionElement::entered( SequenceElement* child )
{
if ( child == numerator ) {
formula()->tell( i18n( "Numerator" ) );
}
else {
formula()->tell( i18n( "Denominator" ) );
}
}
BasicElement* FractionElement::goToPos( FormulaCursor* cursor, bool& handled,
const LuPixelPoint& point, const LuPixelPoint& parentOrigin )
{
BasicElement* e = BasicElement::goToPos(cursor, handled, point, parentOrigin);
if (e != 0) {
LuPixelPoint myPos(parentOrigin.x() + getX(),
parentOrigin.y() + getY());
e = numerator->goToPos(cursor, handled, point, myPos);
if (e != 0) {
return e;
}
e = denominator->goToPos(cursor, handled, point, myPos);
if (e != 0) {
return e;
}
luPixel dx = point.x() - myPos.x();
luPixel dy = point.y() - myPos.y();
// the positions after the numerator / denominator
if ((dx > numerator->getX()) &&
(dy < numerator->getHeight())) {
numerator->moveLeft(cursor, this);
handled = true;
return numerator;
}
else if ((dx > denominator->getX()) &&
(dy > denominator->getY())) {
denominator->moveLeft(cursor, this);
handled = true;
return denominator;
}
return this;
}
return 0;
}
/**
* Calculates our width and height and
* our children's parentPosition.
*/
void FractionElement::calcSizes( const ContextStyle& context,
ContextStyle::TextStyle tstyle,
ContextStyle::IndexStyle istyle,
StyleAttributes& style )
{
ContextStyle::TextStyle i_tstyle = context.convertTextStyleFraction( tstyle );
ContextStyle::IndexStyle u_istyle = context.convertIndexStyleUpper( istyle );
ContextStyle::IndexStyle l_istyle = context.convertIndexStyleLower( istyle );
double factor = style.sizeFactor();
numerator->calcSizes( context, i_tstyle, u_istyle, style );
denominator->calcSizes( context, i_tstyle, l_istyle, style );
luPixel distY = context.ptToPixelY( context.getThinSpace( tstyle, factor ) );
double linethickness = lineThickness( context, factor );
setWidth( QMAX( numerator->getWidth(), denominator->getWidth() ) );
setHeight( numerator->getHeight() + denominator->getHeight() +
2*distY + linethickness );
setBaseline( qRound( numerator->getHeight() + distY + .5*linethickness
+ context.axisHeight( tstyle, factor ) ) );
numerator->setX( ( getWidth() - numerator->getWidth() ) / 2 );
denominator->setX( ( getWidth() - denominator->getWidth() ) / 2 );
numerator->setY( 0 );
denominator->setY( getHeight() - denominator->getHeight() );
}
/**
* Draws the whole element including its children.
* The `parentOrigin' is the point this element's parent starts.
* We can use our parentPosition to get our own origin then.
*/
void FractionElement::draw( QPainter& painter, const LuPixelRect& r,
const ContextStyle& context,
ContextStyle::TextStyle tstyle,
ContextStyle::IndexStyle istyle,
StyleAttributes& style,
const LuPixelPoint& parentOrigin )
{
LuPixelPoint myPos( parentOrigin.x()+getX(), parentOrigin.y()+getY() );
//if ( !LuPixelRect( myPos.x(), myPos.y(), getWidth(), getHeight() ).intersects( r ) )
// return;
numerator->draw( painter, r, context,
context.convertTextStyleFraction( tstyle ),
context.convertIndexStyleUpper( istyle ), style, myPos);
if (denominator) { // Can be temporarily 0 see FractionElement::remove
denominator->draw( painter, r, context,
context.convertTextStyleFraction( tstyle ),
context.convertIndexStyleLower( istyle ), style,
myPos);
}
if ( withLine() ) {
// TODO: thickness
double factor = style.sizeFactor();
double linethickness = lineThickness( context, factor );
painter.setPen( QPen( style.color(),
context.layoutUnitToPixelY( linethickness ) ) );
painter.drawLine( context.layoutUnitToPixelX( myPos.x() ),
context.layoutUnitToPixelY( myPos.y() + axis( context, tstyle, factor ) ),
context.layoutUnitToPixelX( myPos.x() + getWidth() ),
context.layoutUnitToPixelY( myPos.y() + axis( context, tstyle, factor ) ) );
}
}
void FractionElement::dispatchFontCommand( FontCommand* cmd )
{
numerator->dispatchFontCommand( cmd );
denominator->dispatchFontCommand( cmd );
}
/**
* Enters this element while moving to the left starting inside
* the element `from'. Searches for a cursor position inside
* this element or to the left of it.
*/
void FractionElement::moveLeft(FormulaCursor* cursor, BasicElement* from)
{
if (cursor->isSelectionMode()) {
getParent()->moveLeft(cursor, this);
}
else {
bool linear = cursor->getLinearMovement();
if (from == getParent()) {
if (linear) {
denominator->moveLeft(cursor, this);
}
else {
numerator->moveLeft(cursor, this);
}
}
else if (from == denominator) {
numerator->moveLeft(cursor, this);
}
else {
getParent()->moveLeft(cursor, this);
}
}
}
/**
* Enters this element while moving to the right starting inside
* the element `from'. Searches for a cursor position inside
* this element or to the right of it.
*/
void FractionElement::moveRight(FormulaCursor* cursor, BasicElement* from)
{
if (cursor->isSelectionMode()) {
getParent()->moveRight(cursor, this);
}
else {
bool linear = cursor->getLinearMovement();
if (from == getParent()) {
numerator->moveRight(cursor, this);
}
else if (from == numerator) {
if (linear) {
denominator->moveRight(cursor, this);
}
else {
getParent()->moveRight(cursor, this);
}
}
else {
getParent()->moveRight(cursor, this);
}
}
}
/**
* Enters this element while moving up starting inside
* the element `from'. Searches for a cursor position inside
* this element or above it.
*/
void FractionElement::moveUp(FormulaCursor* cursor, BasicElement* from)
{
if (cursor->isSelectionMode()) {
getParent()->moveUp(cursor, this);
}
else {
if (from == getParent()) {
denominator->moveRight(cursor, this);
}
else if (from == denominator) {
numerator->moveRight(cursor, this);
}
else {
getParent()->moveUp(cursor, this);
}
}
}
/**
* Enters this element while moving down starting inside
* the element `from'. Searches for a cursor position inside
* this element or below it.
*/
void FractionElement::moveDown(FormulaCursor* cursor, BasicElement* from)
{
if (cursor->isSelectionMode()) {
getParent()->moveDown(cursor, this);
}
else {
if (from == getParent()) {
numerator->moveRight(cursor, this);
}
else if (from == numerator) {
denominator->moveRight(cursor, this);
}
else {
getParent()->moveDown(cursor, this);
}
}
}
/**
* Reinserts the denominator if it has been removed.
*/
void FractionElement::insert(FormulaCursor* cursor,
QPtrList<BasicElement>& newChildren,
Direction direction)
{
if (cursor->getPos() == denominatorPos) {
denominator = static_cast<SequenceElement*>(newChildren.take(0));
denominator->setParent(this);
if (direction == beforeCursor) {
denominator->moveLeft(cursor, this);
}
else {
denominator->moveRight(cursor, this);
}
cursor->setSelection(false);
formula()->changed();
}
}
/**
* Removes all selected children and returns them. Places the
* cursor to where the children have been.
*
* We remove ourselve if we are requested to remove our numerator.
*
* It is possible to remove the denominator. But after this we
* are senseless and the caller is required to replace us.
*/
void FractionElement::remove(FormulaCursor* cursor,
QPtrList<BasicElement>& removedChildren,
Direction direction)
{
switch (cursor->getPos()) {
case numeratorPos:
getParent()->selectChild(cursor, this);
getParent()->remove(cursor, removedChildren, direction);
break;
case denominatorPos:
removedChildren.append(denominator);
formula()->elementRemoval(denominator);
denominator = 0;
cursor->setTo(this, denominatorPos);
formula()->changed();
break;
}
}
/**
* Returns wether the element has no more useful
* children (except its main child) and should therefore
* be replaced by its main child's content.
*/
bool FractionElement::isSenseless()
{
return denominator == 0;
}
// main child
//
// If an element has children one has to become the main one.
SequenceElement* FractionElement::getMainChild()
{
return numerator;
}
// void FractionElement::setMainChild(SequenceElement* child)
// {
// formula()->elementRemoval(numerator);
// numerator = child;
// numerator->setParent(this);
// formula()->changed();
// }
/**
* Sets the cursor to select the child. The mark is placed before,
* the position behind it.
*/
void FractionElement::selectChild(FormulaCursor* cursor, BasicElement* child)
{
if (child == numerator) {
cursor->setTo(this, numeratorPos);
}
else if (child == denominator) {
cursor->setTo(this, denominatorPos);
}
}
/**
* Appends our attributes to the dom element.
*/
void FractionElement::writeDom(QDomElement element)
{
BasicElement::writeDom(element);
QDomDocument doc = element.ownerDocument();
if (!withLine()) element.setAttribute("NOLINE", 1);
QDomElement num = doc.createElement("NUMERATOR");
num.appendChild(numerator->getElementDom(doc));
element.appendChild(num);
QDomElement den = doc.createElement("DENOMINATOR");
den.appendChild(denominator->getElementDom(doc));
element.appendChild(den);
}
/**
* Reads our attributes from the element.
* Returns false if it failed.
*/
bool FractionElement::readAttributesFromDom(QDomElement element)
{
if (!BasicElement::readAttributesFromDom(element)) {
return false;
}
QString lineStr = element.attribute("NOLINE");
if(!lineStr.isNull()) {
m_lineThicknessType = RelativeSize;
m_lineThickness = lineStr.toInt();
}
return true;
}
/**
* Reads our attributes from the MathML element.
* Returns false if it failed.
*/
bool FractionElement::readAttributesFromMathMLDom(const QDomElement& element)
{
if ( ! BasicElement::readAttributesFromMathMLDom( element ) ) {
return false;
}
QString linethicknessStr = element.attribute( "linethickness" ).lower();
if ( ! linethicknessStr.isNull() ) {
if ( linethicknessStr == "thin" ) {
m_lineThicknessType = RelativeSize;
m_lineThickness = 0.5; // ### Arbitrary size
}
else if ( linethicknessStr == "medium" ) {
m_lineThicknessType = RelativeSize;
m_lineThickness = 1.0;
}
else if ( linethicknessStr == "thick" ) {
m_lineThicknessType = RelativeSize;
m_lineThickness = 2.0; // ### Arbitrary size
}
else {
m_lineThickness = getSize( linethicknessStr, &m_lineThicknessType );
}
}
QString numalignStr = element.attribute( "numalign" ).lower();
if ( ! numalignStr.isNull() ) {
if ( numalignStr == "left" ) {
m_numAlign = LeftHorizontalAlign;
}
else if ( numalignStr == "center" ) {
m_numAlign = CenterHorizontalAlign;
}
else if ( numalignStr == "right" ) {
m_numAlign = RightHorizontalAlign;
}
}
QString denomalignStr = element.attribute( "denomalign" ).lower();
if ( ! denomalignStr.isNull() ) {
if ( denomalignStr == "left" ) {
m_denomAlign = LeftHorizontalAlign;
}
else if ( denomalignStr == "center" ) {
m_denomAlign = CenterHorizontalAlign;
}
else if ( denomalignStr == "right" ) {
m_denomAlign = RightHorizontalAlign;
}
}
QString bevelledStr = element.attribute( "bevelled" ).lower();
if ( ! bevelledStr.isNull() ) {
m_customBevelled = true;
if ( bevelledStr == "true" ) {
m_bevelled = true;
}
else {
m_bevelled = false;
}
}
return true;
}
/**
* Reads our content from the node. Sets the node to the next node
* that needs to be read.
* Returns false if it failed.
*/
bool FractionElement::readContentFromDom(QDomNode& node)
{
if (!BasicElement::readContentFromDom(node)) {
return false;
}
if ( !buildChild( numerator, node, "NUMERATOR" ) ) {
kdWarning( DEBUGID ) << "Empty numerator in FractionElement." << endl;
return false;
}
node = node.nextSibling();
if ( !buildChild( denominator, node, "DENOMINATOR" ) ) {
kdWarning( DEBUGID ) << "Empty denominator in FractionElement." << endl;
return false;
}
node = node.nextSibling();
return true;
}
/**
* Reads our content from the MathML node. Sets the node to the next node
* that needs to be read.
* Returns false if it failed.
*/
int FractionElement::readContentFromMathMLDom(QDomNode& node)
{
if ( BasicElement::readContentFromMathMLDom( node ) == -1 ) {
return -1;
}
int numeratorNumber = numerator->buildMathMLChild( node );
if ( numeratorNumber == -1 ) {
kdWarning( DEBUGID ) << "Empty numerator in FractionElement." << endl;
return -1;
}
for (int i = 0; i < numeratorNumber; i++ ) {
if ( node.isNull() ) {
return -1;
}
node = node.nextSibling();
}
if ( denominator->buildMathMLChild( node ) == -1 ) {
kdWarning( DEBUGID ) << "Empty denominator in FractionElement." << endl;
return -1;
}
return 1;
}
QString FractionElement::toLatex()
{
if ( withLine() ) {
return "\\frac{" + numerator->toLatex() +"}{" + denominator->toLatex() + "}";
}
else {
return "{" + numerator->toLatex() + "\\atop " + denominator->toLatex() + "}";
}
}
QString FractionElement::formulaString()
{
return "(" + numerator->formulaString() + ")/(" + denominator->formulaString() + ")";
}
void FractionElement::writeMathMLAttributes( QDomElement& element ) const
{
switch ( m_lineThicknessType ) {
case AbsoluteSize:
element.setAttribute( "linethickness", QString( "%1pt" ).arg( m_lineThickness ) );
break;
case RelativeSize:
element.setAttribute( "linethickness", QString( "%1%" ).arg( m_lineThickness * 100.0 ) );
break;
case PixelSize:
element.setAttribute( "linethickness", QString( "%1px" ).arg( m_lineThickness ) );
break;
default:
break;
}
switch ( m_numAlign ) {
case LeftHorizontalAlign:
element.setAttribute( "numalign", "left" );
break;
case CenterHorizontalAlign:
element.setAttribute( "numalign", "center" );
break;
case RightHorizontalAlign:
element.setAttribute( "numalign", "right" );
break;
default:
break;
}
switch ( m_denomAlign ) {
case LeftHorizontalAlign:
element.setAttribute( "denomalign", "left" );
break;
case CenterHorizontalAlign:
element.setAttribute( "denomalign", "center" );
break;
case RightHorizontalAlign:
element.setAttribute( "denomalign", "right" );
break;
default:
break;
}
if ( m_customBevelled ) {
element.setAttribute( "bevelled", m_bevelled ? "true" : "false" );
}
}
void FractionElement::writeMathMLContent( QDomDocument& doc,
QDomElement& element,
bool oasisFormat ) const
{
numerator->writeMathML( doc, element, oasisFormat );
denominator->writeMathML( doc, element, oasisFormat );
}
double FractionElement::lineThickness( const ContextStyle& context, double factor )
{
double linethickness = context.getLineWidth( factor );
switch ( m_lineThicknessType ) {
case AbsoluteSize:
linethickness = context.ptToLayoutUnitPixX( m_lineThickness );
break;
case RelativeSize:
linethickness *= m_lineThickness;
break;
case PixelSize:
linethickness = m_lineThickness;
break;
default:
break;
}
return linethickness;
}
KFORMULA_NAMESPACE_END