kformula/flake

MultiscriptElement.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE project
00002    Copyright (C) 2006 Martin Pfeiffer <hubipete@gmx.net>
00003                  2009 Jeremias Epperlein <jeeree@web.de>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License as published by the Free Software Foundation; either
00008    version 2 of the License, or (at your option) any later version.
00009 
00010    This library is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013    Library General Public License for more details.
00014 
00015    You should have received a copy of the GNU Library General Public License
00016    along with this library; see the file COPYING.LIB.  If not, write to
00017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018    Boston, MA 02110-1301, USA.
00019 */
00020 
00021 #include "MultiscriptElement.h"
00022 #include "AttributeManager.h"
00023 #include <KoXmlWriter.h>
00024 #include <KoXmlReader.h>
00025 #include <QPainter>
00026 #include "FormulaCursor.h"
00027 #include "kdebug.h"
00028 
00029 MultiscriptElement::MultiscriptElement( BasicElement* parent ) : FixedElement( parent )
00030 {
00031     m_baseElement = new RowElement( this );
00032 }
00033 
00034 MultiscriptElement::~MultiscriptElement()
00035 {
00036     delete m_baseElement;
00037     //Delete all of the scripts
00038     while (!m_preScripts.isEmpty())
00039         delete m_preScripts.takeFirst();
00040     while (!m_postScripts.isEmpty())
00041         delete m_postScripts.takeFirst();
00042 }
00043 
00044 void MultiscriptElement::paint( QPainter& painter, AttributeManager* am )
00045 { 
00046     Q_UNUSED(painter)
00047     Q_UNUSED(am)
00048     /*do nothing as this element has no visual representation*/
00049 }
00050 
00051 void MultiscriptElement::ensureEvenNumberElements() {
00052     if(m_postScripts.size() % 2 == 1) {
00053         //Odd number - at a None element on the end
00054         m_postScripts.append(NULL);
00055     }
00056     if(m_preScripts.size() % 2 == 1) {
00057         //Odd number - at a None element on the end
00058         m_preScripts.append(NULL);
00059     }
00060 }
00061 
00062 void MultiscriptElement::layout( const AttributeManager* am )
00063 {
00064     // Get the minimum amount of shifting
00065     double subscriptshift   = am->doubleOf( "subscriptshift", this ); 
00066     double superscriptshift = am->doubleOf( "superscriptshift", this );
00067     //Add half a thin space between both sup and superscript, so there is a minimum
00068     //of a whole thin space between them.
00069     double halfthinspace   = am->layoutSpacing( this )/2.0;
00070 
00071     //First make sure that there are an even number of elements in both subscript and superscript
00072     ensureEvenNumberElements();
00073     
00074     // Go through all the superscripts (pre and post) and find the maximum heights;
00075     // BaseLine is the distance from the top to the baseline.
00076     // Depth is the distance from the baseline to the bottom
00077     double maxSuperScriptDepth     = 0.0;
00078     double maxSuperScriptBaseLine  = 0.0;
00079     double maxSubScriptDepth       = 0.0;
00080     double maxSubScriptBaseLine    = 0.0;
00081     bool isSuperscript = true;  //Toggle after each element time
00082     foreach( BasicElement *script, m_postScripts ) {
00083         isSuperscript = !isSuperscript;  //Toggle each time
00084         if(!script)
00085             continue;  // Null means no element - just a blank
00086         if(isSuperscript) {
00087             maxSuperScriptDepth = qMax( script->height() - script->baseLine(), maxSuperScriptDepth );
00088             maxSuperScriptBaseLine = qMax( script->baseLine(), maxSuperScriptBaseLine );
00089         } else {
00090             //Find out how much the subscript sticks below the baseline
00091             maxSubScriptDepth = qMax( script->height() - script->baseLine(), maxSubScriptDepth );
00092             maxSubScriptBaseLine = qMax( script->baseLine(), maxSubScriptBaseLine );
00093         }
00094     }
00095     foreach( BasicElement *script, m_preScripts ) {
00096         isSuperscript = !isSuperscript;  //Toggle each time
00097         if(!script)
00098             continue;  // Null means no element - just a blank
00099         if(isSuperscript) {
00100             maxSuperScriptDepth = qMax( script->height() - script->baseLine(), maxSuperScriptDepth );
00101             maxSuperScriptBaseLine = qMax( script->baseLine(), maxSuperScriptBaseLine );
00102         } else {
00103             //Find out how much the subscript sticks below the baseline
00104             maxSubScriptDepth = qMax( script->height() - script->baseLine(), maxSubScriptDepth );
00105             maxSubScriptBaseLine = qMax( script->baseLine(), maxSubScriptBaseLine );
00106         }
00107     }
00108     // The yOffsetBase is the amount the base element is moved down to make
00109     // room for the superscript
00110     double yOffsetBase = 0;
00111     if(maxSuperScriptDepth + maxSuperScriptBaseLine > 0) {
00112         yOffsetBase = maxSuperScriptDepth + maxSuperScriptBaseLine - m_baseElement->height()/2.0 + halfthinspace;
00113         yOffsetBase = qMax( yOffsetBase, superscriptshift );
00114     }
00115     // The yOffsetSub is the amount the subscript elements /baseline/ are moved down.
00116     double yOffsetSub = yOffsetBase + maxSubScriptBaseLine +
00117                 qMax( m_baseElement->height()/2 + halfthinspace, 
00118                       m_baseElement->height() - maxSubScriptBaseLine
00119                           + subscriptshift );
00120     
00121     double xOffset = 0.0;  //We increment this as we go along, to keep track of where to place elements
00122     double lastSuperScriptWidth= 0.0;
00123     // Now we have all the information needed to start putting elements in place.
00124     // We start from the far left, and work to the far right.
00125     for( int i = m_preScripts.size()-1; i >= 0; i--) {
00126         //We start from the end, and work in.
00127         //m_preScripts[0] is subscript etc.  So even i is subscript, odd i is superscript
00128         if( i%2 == 0) {
00129             // i is even, so subscript
00130             if(!m_preScripts[i]) {
00131                 xOffset += lastSuperScriptWidth;
00132             } else {
00133                 // For a given vertical line, this is processed after the superscript
00134                 double offset = qMax(0.0, (lastSuperScriptWidth - m_preScripts[i]->width())/2.0);
00135                 m_preScripts[i]->setOrigin( QPointF( 
00136                             offset + xOffset, 
00137                             yOffsetSub - m_preScripts[i]->baseLine() ) );
00138                 xOffset += qMax(lastSuperScriptWidth, m_preScripts[i]->width());
00139             }
00140             if(i!=0)  //No halfthinspace between the first element and the base element
00141                 xOffset += halfthinspace;
00142         } else {
00143             // i is odd, so superscript
00144             // For a given vertical line, we process the superscript first, then 
00145             // the subscript.  We need to look at the subscript (i-1) as well
00146             // to find out how to align them
00147             if( !m_preScripts[i] )
00148                 lastSuperScriptWidth = 0.0;
00149             else {
00150                 lastSuperScriptWidth = m_preScripts[i]->width();
00151                 double offset = 0.0;
00152                 if(m_preScripts[i-1]) //the subscript directly below us. 
00153                     offset = qMax(0.0, (m_preScripts[i-1]->width() - lastSuperScriptWidth)/2.0);
00154                 m_preScripts[i]->setOrigin( QPointF(
00155                             offset + xOffset,
00156                             maxSuperScriptBaseLine - m_preScripts[i]->baseLine()));
00157             }
00158         }
00159     }
00160 
00161     //We have placed all the prescripts now.  So now place the base element
00162     m_baseElement->setOrigin( QPointF( xOffset, yOffsetBase ) );
00163     xOffset += m_baseElement->width();
00164     double lastSubScriptWidth = 0.0;
00165     //Now we can draw the post scripts.  This code is very similar, but this time we will parse
00166     //the subscript before the superscript
00167     for( int i = 0; i < m_postScripts.size(); i++) {
00168         //We start from the start, and work out.
00169         //m_preScripts[0] is subscript etc.  So even i is subscript, odd i is superscript
00170         if( i%2 == 0) {
00171             // i is even, so subscript
00172             // For a given vertical line, we process the subscript first, then 
00173             // the superscript.  We need to look at the superscript (i+1) as well
00174             // to find out how to align them
00175  
00176             if(!m_postScripts[i]) {
00177                 lastSubScriptWidth = 0.0;
00178             } else {
00179                 lastSubScriptWidth = m_postScripts[i]->width();
00180                 // For a given vertical line, this is processed after the superscript
00181                 double offset = 0.0;
00182                 if(m_postScripts.size() > i+1 && m_postScripts[i+1] != NULL) //the subscript directly below us. 
00183                     offset = qMax(0.0, (m_postScripts[i+1]->width() - lastSubScriptWidth)/2.0);
00184                 m_postScripts[i]->setOrigin( QPointF( 
00185                             offset + xOffset,
00186                             yOffsetSub - m_postScripts[i]->baseLine() ) );
00187             }
00188         } else {
00189             // i is odd, so superscript
00190            if( !m_postScripts[i] )
00191                 xOffset += lastSubScriptWidth;
00192            else {
00193                 double offset = qMax(0.0, (lastSubScriptWidth - m_postScripts[i]->width())/2.0);
00194                 m_postScripts[i]->setOrigin( QPointF(
00195                             offset + xOffset,
00196                             maxSuperScriptBaseLine - m_postScripts[i]->baseLine()));
00197                 xOffset += qMax(lastSubScriptWidth, m_postScripts[i]->width());
00198             }
00199            if(i != m_postScripts.size()-1)
00200                xOffset += halfthinspace; //Don't add an unneeded space at the very end
00201         }
00202     }
00203 
00204 
00205     //Finally, set our boundingbox
00206     setWidth( xOffset );
00207     setHeight( yOffsetSub + maxSubScriptDepth );
00208     setBaseLine( yOffsetBase + m_baseElement->baseLine() );
00209 }
00210 
00211 bool MultiscriptElement::acceptCursor( const FormulaCursor& cursor )
00212 {
00213     Q_UNUSED( cursor )
00214     return false;
00215 }
00216 
00217 const QList<BasicElement*> MultiscriptElement::childElements() const
00218 {
00219     QList<BasicElement*> list;
00220     for (int i=m_preScripts.count()-2;i>=0; i-=2 ) {
00221         if(m_preScripts[i]) list << m_preScripts[i];
00222         if(m_preScripts[i+1]) list << m_preScripts[i+1];
00223     }
00224     list << m_baseElement;
00225     foreach( BasicElement* tmp, m_postScripts ) {
00226         if(tmp)
00227             list << tmp;
00228     }
00229 
00230 
00231     return list;
00232 }
00233 
00234 QString MultiscriptElement::attributesDefaultValue( const QString& attribute ) const
00235 {
00236     Q_UNUSED( attribute )
00237     return QString();
00238 }
00239 
00240 ElementType MultiscriptElement::elementType() const
00241 {
00242     return MultiScript;
00243 }
00244 
00245 bool MultiscriptElement::readMathMLContent( const KoXmlElement& parent )
00246 {
00247     QString name = parent.tagName().toLower();
00248     BasicElement* tmpElement = 0;
00249     KoXmlElement tmp;
00250     bool prescript = false; //When we see a mprescripts tag, we enable this
00251     bool baseElement = true;
00252     forEachElement( tmp, parent ) { 
00253         if(tmp.tagName() == "none") {
00254             //In mathml, we read subscript, then superscript, etc.  To skip one,
00255             //you use "none"
00256             //To represent "none" we use a NULL pointer
00257             if(prescript)
00258                 m_preScripts.append(NULL);
00259             else
00260                 m_postScripts.append(NULL);
00261             continue;
00262         } else if(tmp.tagName() == "mprescripts") {
00263             prescript = true;  
00264             //In mathml, when we see this tag, all the elements after it are
00265             // for prescripts
00266             continue;
00267         }
00268         
00269         tmpElement = ElementFactory::createElement( tmp.tagName(), this );
00270         if( !tmpElement->readMathML( tmp ) )
00271             return false;
00272         if( baseElement ) {  //Very first element is the base
00273             delete m_baseElement; 
00274             m_baseElement = tmpElement;
00275             baseElement = true;
00276         }
00277         else if( prescript)
00278             m_preScripts.append( tmpElement );
00279         else 
00280             m_postScripts.append( tmpElement );
00281     }
00282     ensureEvenNumberElements();
00283     Q_ASSERT(m_baseElement);  //We should have at least a BasicElement for the base
00284     return true;
00285 }
00286 
00287 void MultiscriptElement::writeMathMLContent( KoXmlWriter* writer ) const
00288 {
00289     m_baseElement->writeMathML( writer );        // Just save the children in
00290                                                  // the right order
00291     foreach( BasicElement* tmp, m_postScripts ) {
00292         if(tmp)
00293             tmp->writeMathML( writer );
00294         else {
00295             //We need to use a none element for missing elements in the super/sub scripts
00296             writer->startElement("none");
00297             writer->endElement();
00298         }
00299     }
00300     if( m_preScripts.isEmpty() ) return;
00301     writer->startElement("mprescripts");
00302     writer->endElement();
00303     foreach( BasicElement* tmp, m_preScripts ) {
00304         if(tmp)
00305             tmp->writeMathML( writer );
00306         else {
00307             //We need to use a none element for missing elements in the super/sub scripts
00308             writer->startElement("none");
00309             writer->endElement();
00310         }
00311     }
00312 }
00313 
00314 // int MultiscriptElement::length() const
00315 // {
00316 //     if (!m_postScripts.isEmpty() && m_postScripts.last()==0) {
00317 //         //the last element is empty, so there are no cursor positions around it
00318 //         return 2*(m_preScripts.count()+m_postScripts.count())-1;
00319 //     } else {
00320 //         return 2*(m_preScripts.count()+m_postScripts.count()+1)-1;
00321 //     }
00322 // }
00323 
00324 bool MultiscriptElement::moveCursor ( FormulaCursor& newcursor, FormulaCursor& oldcursor )
00325 {
00326     // grouppositions:  1 3   1 3
00327     //                  0 2 2 0 2
00328     //                  |     | |
00329     // pairs:           0 1   0 1
00330     //TODO: Fill this out
00331 
00332     int childposition=newcursor.position()/2;
00333     //this should be cached
00334     int prescriptCount=0;
00335     foreach (BasicElement* tmp, m_preScripts) {
00336         if (tmp) {
00337             prescriptCount++;
00338         }
00339     }
00340     if (childposition==prescriptCount) {
00341         //we are in BasePosition
00342         if (newcursor.direction()==MoveUp || newcursor.direction()==MoveDown) {
00343             return false;
00344         }
00345         if (m_postScripts.isEmpty() && m_preScripts.isEmpty()) {
00346             //this should not happen
00347             return moveSingleSituation(newcursor,oldcursor,
00348                                         childElements().indexOf(m_baseElement));
00349         }
00350         if (newcursor.direction()==MoveLeft) {
00351             if (!m_preScripts.isEmpty()) {
00352                 // we search for the first non NULL element to the left
00353                 int i;
00354                 for (i=0; i<m_preScripts.count(); i++) {
00355                     if (m_preScripts[i]) {
00356                         break;
00357                     }
00358                 }
00359                 if ((i<m_preScripts.count()) && m_preScripts[i]) {
00360                     return moveHorSituation(newcursor,oldcursor,
00361                                              childElements().indexOf(m_preScripts[i]),
00362                                              childElements().indexOf(m_baseElement));
00363                 }
00364             }
00365             return moveSingleSituation(newcursor,oldcursor,0);
00366         } else if (newcursor.direction()==MoveRight) {
00367             if (!m_postScripts.isEmpty()) {
00368                 // we search for the first non NULL element to the left
00369                 int i;
00370                 for (i=0; i<m_postScripts.count(); i++) {
00371                     if (m_postScripts[i]) {
00372                         break;
00373                     }
00374                 }
00375                 if (m_postScripts[i]) {
00376                     return moveHorSituation(newcursor,oldcursor,
00377                                              childElements().indexOf(m_baseElement),
00378                                              childElements().indexOf(m_postScripts[i]));
00379                 }
00380             }
00381             return moveSingleSituation(newcursor,oldcursor,
00382                                         childElements().indexOf(m_baseElement));
00383         }
00384     } else {
00385         int groupposition;
00386         bool prescript=true;
00387         if (childposition<prescriptCount) {
00388                 //determine the position in the pre-/postscripts we are in
00389                 groupposition=m_preScripts.indexOf(childElements()[childposition]);
00390         } else {
00391                 groupposition=m_postScripts.indexOf(childElements()[childposition]);
00392                 prescript=false;
00393         }
00394         int pair=groupposition/2;
00395         if (newcursor.direction()==MoveUp || newcursor.direction()==MoveDown) {
00396 //             kDebug()<<groupposition<<" - "<<prescriptCount<< "-" <<pair;
00397             if (prescript) {
00398                 if (m_preScripts[pair*2] && m_preScripts[pair*2+1]) {
00399                     return moveVertSituation(newcursor,oldcursor,
00400                                               childElements().indexOf(m_preScripts[pair*2+1]),
00401                                               childElements().indexOf(m_preScripts[pair*2]));
00402                 } else {
00403                     return false;
00404                 }
00405             } else {
00406                 if (m_postScripts[pair*2] && m_postScripts[pair*2+1]) {
00407                     return moveVertSituation(newcursor,oldcursor,
00408                                               childElements().indexOf(m_postScripts[pair*2+1]),
00409                                               childElements().indexOf(m_postScripts[pair*2]));
00410                 } else {
00411                     return false;
00412                 }
00413             }
00414         } else if (newcursor.direction()==MoveLeft) {
00415             if (prescript) {
00416                 //we are in the prescripts
00417                 int i=groupposition+2;
00418                 if (!((i<m_preScripts.count()) && m_preScripts[i])) {
00419                     for (i=groupposition+1; i<m_preScripts.count(); i++) {
00420                         if (m_preScripts[i]) {
00421                             break;
00422                         }
00423                     }
00424                 }
00425                 if ((i<m_preScripts.count()) && m_preScripts[i]) {
00426                     return moveHorSituation(newcursor,oldcursor,
00427                                              childElements().indexOf(m_preScripts[i]),
00428                                              childElements().indexOf(m_preScripts[groupposition]));
00429                 } else {
00430                     return moveSingleSituation(newcursor,oldcursor,
00431                                                 childElements().indexOf(m_preScripts[groupposition]));
00432                 }
00433             } else {
00434                 //we are in the postscripts
00435                 int i=groupposition-1;
00436                 if (!(i>=0) && m_postScripts[i]) {
00437                     for (i=groupposition-2; i>=0; i--) {
00438                         if (m_postScripts[i]) {
00439                             break;
00440                         }
00441                     }
00442                 }
00443                 if ((i>=0) && m_postScripts[i]) {
00444                     return moveHorSituation(newcursor,oldcursor,
00445                                              childElements().indexOf(m_postScripts[i]),
00446                                              childElements().indexOf(m_postScripts[groupposition]));
00447                 } else {
00448                     return moveHorSituation(newcursor,oldcursor,
00449                                              childElements().indexOf(m_baseElement),
00450                                              childElements().indexOf(elementNext(newcursor.position())));
00451                 }
00452             }
00453         } else if (newcursor.direction()==MoveRight) {
00454             if (prescript) {
00455                 //we are in the prescripts
00456                 int i=groupposition-2;
00457                 if (!((i>=0) && m_preScripts[i])) {
00458                     for (i=groupposition-1; i>=0; i--) {
00459                         if (m_preScripts[i]) {
00460                             break;
00461                         }
00462                     }
00463                 }
00464                 if ((i>=0) && m_preScripts[i]) {
00465 //                    kDebug()<<"Going from "<< groupposition <<" to " <<i;
00466                     return moveHorSituation(newcursor,oldcursor,
00467                                              childElements().indexOf(m_preScripts[groupposition]),
00468                                              childElements().indexOf(m_preScripts[i]));
00469                 } else {
00470                     return moveHorSituation(newcursor,oldcursor,
00471                                              childElements().indexOf(elementNext(newcursor.position())),
00472                                              childElements().indexOf(m_baseElement));
00473                 }
00474             } else {
00475                 //we are in the postscripts
00476                 int i=groupposition+2;
00477                 if (!((i<m_postScripts.count()) && m_postScripts[i])) {
00478                     for (i=groupposition+1; i<m_postScripts.count(); i++) {
00479                         if (m_postScripts[i]) {
00480                             break;
00481                         }
00482                     }
00483                 }
00484                 if ((i<m_postScripts.count()) && m_postScripts[i]) {
00485                     return moveHorSituation(newcursor,oldcursor,
00486                                              childElements().indexOf(m_postScripts[groupposition]),
00487                                              childElements().indexOf(m_postScripts[i]));
00488                 } else {
00489                     return moveSingleSituation(newcursor,oldcursor,
00490                                                 childElements().indexOf(m_preScripts[groupposition]));
00491                 }
00492             }
00493         }
00494     }
00495     return false;
00496 }
00497 
00498 bool MultiscriptElement::setCursorTo ( FormulaCursor& cursor, QPointF point )
00499 {
00500     if (cursor.isSelecting()) {
00501         return false;
00502     }
00503     foreach (BasicElement* tmp, childElements()) {
00504         if (tmp->boundingRect().contains(point)) {
00505             return tmp->setCursorTo(cursor,point-tmp->origin());
00506         }
00507     }
00508     return m_baseElement->setCursorTo(cursor,point-m_baseElement->origin());
00509 }