File indexing completed on 2024-05-12 16:34:03

0001 /* This file is part of the KDE project
0002    Copyright (C) 2006 Martin Pfeiffer <hubipete@gmx.net>
0003                  2009 Jeremias Epperlein <jeeree@web.de>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This library is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this library; see the file COPYING.LIB.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018    Boston, MA 02110-1301, USA.
0019  */
0020 
0021 #include "KoFormulaTool.h"
0022 
0023 #include "KoFormulaShape.h"
0024 #include "FormulaToolWidget.h"
0025 #include "BasicElement.h"
0026 #include "FormulaEditor.h"
0027 #include "FormulaDebug.h"
0028 
0029 #include <KoCanvasBase.h>
0030 #include <KoPointerEvent.h>
0031 #include <KoSelection.h>
0032 #include <KoShapeController.h>
0033 #include <KoIcon.h>
0034 
0035 #include <klocalizedstring.h>
0036 
0037 #include <QKeyEvent>
0038 #include <QAction>
0039 #include <QPainter>
0040 #include <QFile>
0041 #include <QFileDialog>
0042 
0043 #include <KoShapeSavingContext.h>
0044 #include <KoShapeLoadingContext.h>
0045 #include <KoOdfLoadingContext.h>
0046 #include <KoOdfStylesReader.h>
0047 #include "FormulaCommand.h"
0048 #include "FormulaCommandUpdate.h"
0049 #include <KoXmlReader.h>
0050 #include <KoXmlWriter.h>
0051 #include <KoGenStyles.h>
0052 #include <KoEmbeddedDocumentSaver.h>
0053 #include "FormulaRenderer.h"
0054 #include <QClipboard>
0055 
0056 // this adds const to non-const objects (like std::as_const)
0057 template <typename T> Q_DECL_CONSTEXPR typename std::add_const<T>::type &koAsConst(T &t) noexcept { return t; }
0058 // prevent rvalue arguments:
0059 template <typename T> void koAsConst(const T &&) = delete;
0060 
0061 
0062 KoFormulaTool::KoFormulaTool( KoCanvasBase* canvas ) : KoToolBase( canvas ),
0063                                                        m_formulaShape( 0 ),
0064                                                        m_formulaEditor( 0 )
0065 {
0066     setupActions();
0067     setTextMode(true);
0068 }
0069 
0070 KoFormulaTool::~KoFormulaTool()
0071 {
0072     if( m_formulaEditor ) {
0073         m_cursorList.removeAll(m_formulaEditor);
0074         delete m_formulaEditor;
0075     }
0076     foreach (FormulaEditor* tmp, m_cursorList) {
0077         delete tmp;
0078     }
0079 }
0080 
0081 void KoFormulaTool::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
0082 {
0083     Q_UNUSED(toolActivation);
0084     foreach (KoShape *shape, shapes) {
0085         m_formulaShape = dynamic_cast<KoFormulaShape*>( shape );
0086         if( m_formulaShape )
0087             break;
0088     }
0089     
0090     if( m_formulaShape == 0 )  // none found
0091     {
0092         emit done();
0093         return;
0094     }
0095     useCursor(Qt::IBeamCursor);
0096     m_formulaEditor=0;
0097     for (int i = 0; i < m_cursorList.count(); i++) {
0098         FormulaEditor* editor = m_cursorList[i];
0099         FormulaData* formulaData=m_formulaShape->formulaData();
0100         if (editor->formulaData() == formulaData) {
0101             //we have to check if the cursors current element is actually a 
0102             //child of the m_formulaShape->formulaData()
0103             m_cursorList.removeAll(editor);
0104             if (formulaData->formulaElement()->hasDescendant(editor->cursor().currentElement())) {
0105                 if (editor->cursor().isAccepted()) {
0106                     debugFormula << "Found old cursor";
0107                     m_formulaEditor=editor;
0108                     break;
0109                 }
0110             }
0111             delete editor;
0112         }
0113     }
0114     if (m_formulaEditor==0) {
0115         //TODO: there should be a extra constructor for this
0116         m_formulaEditor = new FormulaEditor( m_formulaShape->formulaData());
0117     }
0118     connect(m_formulaShape->formulaData(), SIGNAL(dataChanged(FormulaCommand*,bool)), this, SLOT(updateCursor(FormulaCommand*,bool)));
0119     for (const TemplateAction &templateAction : koAsConst(m_templateActions)) {
0120         connect(templateAction.action, &QAction::triggered, this, [this, templateAction] { insert(templateAction.data); });
0121     }
0122     //Only for debugging:
0123     connect(action("write_elementTree"),SIGNAL(triggered(bool)), m_formulaShape->formulaData(), SLOT(writeElementTree()));
0124 }
0125 
0126 void KoFormulaTool::deactivate()
0127 {
0128     for (const TemplateAction &templateAction : koAsConst(m_templateActions)) {
0129         disconnect(templateAction.action, &QAction::triggered, this, nullptr);
0130     }
0131     if (!m_formulaShape) {
0132         // activated without shape
0133         return;
0134     }
0135     disconnect(m_formulaShape->formulaData(),0,this,0);
0136     if (canvas()) {
0137         m_cursorList.append(m_formulaEditor);
0138         debugFormula << "Appending cursor";
0139     }
0140     if (m_cursorList.count() > 20) { // don't let it grow indefinitely
0141         delete m_cursorList.takeAt(0);
0142     }
0143     m_formulaShape = 0;
0144 }
0145 
0146 
0147 void KoFormulaTool::updateCursor(FormulaCommand* command, bool undo)
0148 {
0149     if (command!=0) {
0150         debugFormula << "Going to change cursor";
0151         command->changeCursor(m_formulaEditor->cursor(),undo);
0152     } else {
0153         debugFormula << "Going to reset cursor";
0154         resetFormulaEditor();
0155     }
0156     repaintCursor();
0157 }
0158 
0159 
0160 void KoFormulaTool::paint( QPainter &painter, const KoViewConverter &converter )
0161 {
0162     painter.save();
0163     // transform painter from view coordinate system to document coordinate system
0164     // remember that matrix multiplication is not commutative so painter.matrix
0165     // has to come last
0166     painter.setTransform(m_formulaShape->absoluteTransformation(&converter) * painter.transform());
0167     KoShape::applyConversion(painter,converter);
0168     m_formulaShape->formulaRenderer()->paintElement(painter,m_formulaShape->formulaData()->formulaElement(),true);
0169     m_formulaEditor->paint( painter );
0170     painter.restore();
0171 }
0172 
0173 void KoFormulaTool::repaintCursor()
0174 {
0175     canvas()->updateCanvas( m_formulaShape->boundingRect() );
0176 }
0177 
0178 void KoFormulaTool::mousePressEvent( KoPointerEvent *event )
0179 {
0180     // Check if the event is valid means inside the shape 
0181     if(!m_formulaShape->boundingRect().contains( event->point )) {
0182         return;
0183     }
0184     // transform the global coordinates into shape coordinates
0185     QPointF p = m_formulaShape->absoluteTransformation(0).inverted().map( event->point );
0186     if (event->modifiers() & Qt::ShiftModifier) {
0187         m_formulaEditor->cursor().setSelecting(true);
0188     } else {
0189         m_formulaEditor->cursor().setSelecting(false);
0190     }
0191     // set the cursor to the element the user clicked on
0192     m_formulaEditor->cursor().setCursorTo( p );
0193 
0194     repaintCursor();
0195     event->accept();
0196 }
0197 
0198 void KoFormulaTool::mouseDoubleClickEvent( KoPointerEvent *event )
0199 {
0200     if( !m_formulaShape->boundingRect().contains( event->point ) ) {
0201         return;
0202     }
0203     // transform the global coordinates into shape coordinates
0204     QPointF p = m_formulaShape->absoluteTransformation(0).inverted().map( event->point );
0205     
0206     //clear the current selection
0207     m_formulaEditor->cursor().setSelecting(false);
0208     //place the cursor
0209     m_formulaEditor->cursor().setCursorTo(p);
0210     m_formulaEditor->cursor().selectElement(m_formulaEditor->cursor().currentElement());
0211     repaintCursor();
0212     event->accept();
0213 }
0214 
0215 void KoFormulaTool::mouseMoveEvent( KoPointerEvent *event )
0216 {
0217 //     Q_UNUSED( event )
0218     if (!(event->buttons() & Qt::LeftButton)) {
0219     return;
0220     }
0221     // Check if the event is valid means inside the shape
0222     if( !m_formulaShape->boundingRect().contains( event->point ) )
0223         debugFormula << "Getting most probably invalid mouseMoveEvent";
0224     
0225     // transform the global coordinates into shape coordinates
0226     QPointF p = m_formulaShape->absoluteTransformation(0).inverted().map( event->point );
0227     //TODO Implement drag and drop of elements
0228     m_formulaEditor->cursor().setSelecting(true);
0229     m_formulaEditor->cursor().setCursorTo( p );
0230     repaintCursor();
0231     event->accept();
0232 }
0233 
0234 void KoFormulaTool::mouseReleaseEvent( KoPointerEvent *event )
0235 {
0236     Q_UNUSED( event )
0237 
0238     // TODO Implement drag and drop
0239 }
0240 
0241 void KoFormulaTool::keyPressEvent( QKeyEvent *event )
0242 {
0243     FormulaCommand *command=0;
0244     if( !m_formulaEditor )
0245         return;
0246 
0247     if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right || 
0248         event->key() == Qt::Key_Up || event->key() == Qt::Key_Down ||
0249         event->key() == Qt::Key_Home || event->key() == Qt::Key_End ) {
0250         if (event->modifiers() & Qt::ShiftModifier) {
0251             m_formulaEditor->cursor().setSelecting(true);
0252         } else {
0253             m_formulaEditor->cursor().setSelecting(false);
0254         }
0255     }
0256     switch( event->key() )                           // map key to movement or action
0257     {
0258         case Qt::Key_Backspace:
0259             m_formulaShape->update();
0260             command=m_formulaEditor->remove( true );
0261             m_formulaShape->updateLayout();
0262             m_formulaShape->update();
0263             break;
0264         case Qt::Key_Delete:
0265             m_formulaShape->update();
0266             command=m_formulaEditor->remove( false );
0267             m_formulaShape->updateLayout();
0268             m_formulaShape->update();
0269             break;
0270         case Qt::Key_Left:
0271             m_formulaEditor->cursor().move( MoveLeft );
0272             break;
0273         case Qt::Key_Up:
0274             m_formulaEditor->cursor().move( MoveUp );
0275             break;
0276         case Qt::Key_Right:
0277             m_formulaEditor->cursor().move( MoveRight );
0278             break;
0279         case Qt::Key_Down:
0280             m_formulaEditor->cursor().move( MoveDown );
0281             break;
0282         case Qt::Key_End:
0283             m_formulaEditor->cursor().moveEnd();
0284             break;
0285         case Qt::Key_Home:
0286             m_formulaEditor->cursor().moveHome();
0287             break;
0288         default:
0289             if( event->text().length() != 0 ) {
0290                 command=m_formulaEditor->insertText( event->text() );
0291             }
0292     }
0293     if (command!=0) {
0294         canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape,command));
0295     }
0296     repaintCursor();
0297     event->accept();
0298 }
0299 
0300 void KoFormulaTool::keyReleaseEvent( QKeyEvent *event )
0301 {
0302     event->accept();
0303 }
0304 
0305 void KoFormulaTool::remove( bool backSpace )
0306 {
0307     m_formulaShape->update();
0308     m_formulaEditor->remove( backSpace );
0309     m_formulaShape->updateLayout();
0310     m_formulaShape->update();
0311 }
0312 
0313 void KoFormulaTool::insert( const QString& action )
0314 {
0315     FormulaCommand *command;
0316     m_formulaShape->update();
0317     command=m_formulaEditor->insertMathML( action );
0318     if (command!=0) {
0319         canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape, command));
0320     }
0321 }
0322 
0323 void KoFormulaTool::changeTable ( QAction* action )
0324 {
0325     FormulaCommand *command;
0326     m_formulaShape->update();
0327     bool row=action->data().toList()[0].toBool();
0328     bool insert=action->data().toList()[1].toBool();
0329     command=m_formulaEditor->changeTable(insert,row);
0330     if (command!=0) {
0331         canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape, command));
0332     }
0333 }
0334 
0335 void KoFormulaTool::insertSymbol ( const QString& symbol )
0336 {
0337     FormulaCommand *command;
0338     m_formulaShape->update();
0339     command=m_formulaEditor->insertText( symbol );
0340     if (command!=0) {
0341         canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape, command));
0342     }
0343 }
0344 
0345 
0346 QWidget* KoFormulaTool::createOptionWidget()
0347 {
0348     FormulaToolWidget* options = new FormulaToolWidget( this );
0349     options->setFormulaTool( this );
0350     return options;
0351 }
0352 
0353 KoFormulaShape* KoFormulaTool::shape()
0354 {
0355     return m_formulaShape;
0356 }
0357 
0358 
0359 FormulaEditor* KoFormulaTool::formulaEditor()
0360 {
0361     return m_formulaEditor;
0362 }
0363 
0364 
0365 void KoFormulaTool::resetFormulaEditor() {
0366     m_formulaEditor->setData(m_formulaShape->formulaData());
0367     FormulaCursor cursor(FormulaCursor(m_formulaShape->formulaData()->formulaElement(),false,0,0));
0368     m_formulaEditor->setCursor(cursor);
0369     //if the cursor is not allowed at the beginning of the formula, move it right
0370     //TODO: check, if this can ever happen
0371     if ( !m_formulaEditor->cursor().isAccepted() ) {
0372         m_formulaEditor->cursor().move(MoveRight);
0373     }
0374 }
0375 
0376 void KoFormulaTool::loadFormula()
0377 {
0378     // get an filepath
0379     const QString fileName = QFileDialog::getOpenFileName();
0380     if( fileName.isEmpty() || !shape() )
0381         return;
0382 
0383     // open the file the filepath points to
0384     QFile file( fileName );
0385     if( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
0386         return;
0387 
0388     KoOdfStylesReader stylesReader;
0389     KoOdfLoadingContext odfContext( stylesReader, 0 );
0390     KoShapeLoadingContext shapeContext(odfContext, canvas()->shapeController()->resourceManager());
0391 
0392     // setup a DOM structure and start the actual loading process
0393     KoXmlDocument tmpDocument;
0394     tmpDocument.setContent( &file, false, 0, 0, 0 );
0395     FormulaElement* formulaElement = new FormulaElement();     // create a new root element
0396     formulaElement->readMathML( tmpDocument.documentElement() );     // and load the new formula
0397     FormulaCommand* command=new FormulaCommandLoad(m_formulaShape->formulaData(),formulaElement);
0398     canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape, command));
0399 }
0400 
0401 void KoFormulaTool::saveFormula()
0402 {
0403     const QString filePath = QFileDialog::getSaveFileName();
0404     if( filePath.isEmpty() || !shape() )
0405         return;
0406 
0407     QFile file( filePath );
0408     KoXmlWriter writer( &file );
0409     KoGenStyles styles;
0410     KoEmbeddedDocumentSaver embeddedSaver;
0411     KoShapeSavingContext shapeSavingContext( writer, styles, embeddedSaver );
0412 
0413     m_formulaShape->formulaData()->saveMathML( shapeSavingContext );
0414 }
0415 
0416 void KoFormulaTool::setupActions()
0417 {
0418     //notice that only empty mrows hows parent is a inferred mrow are treated as placeholders
0419     //this causes the <mrow><mrow/></mrow> constructs
0420     addTemplateAction(i18n("Insert fenced element"), "insert_fence","<mfenced><mrow/></mfenced>", koIconNameCStr("brackets"));
0421     addTemplateAction(i18n("Insert enclosed element"), "insert_enclosed",
0422                       "<menclosed><mrow/></menclosed>", koIconNameCStr("enclosed"));
0423 
0424     addTemplateAction(i18n("Insert root"), "insert_root","<mroot><mrow><mrow/></mrow></mroot>", koIconNameCStr("root"));
0425     addTemplateAction(i18n("Insert square root"), "insert_sqrt","<msqrt><mrow/></msqrt>", koIconNameCStr("sqrt"));
0426 
0427     addTemplateAction(i18n("Insert fraction"), "insert_fraction",
0428                       "<mfrac><mrow><mrow/></mrow><mrow/></mfrac>", koIconNameCStr("frac"));
0429     addTemplateAction(i18n("Insert bevelled fraction"), "insert_bevelled_fraction",
0430                       "<mfrac bevelled=\"true\"><mrow><mrow/></mrow><mrow/></mfrac>", koIconNameCStr("bevelled"));
0431 
0432     addTemplateAction(i18n("Insert 3x3 table"), "insert_33table",
0433                       "<mtable><mtr><mtd><mrow /></mtd><mtd></mtd><mtd></mtd></mtr>" \
0434                       "<mtr><mtd></mtd><mtd></mtd><mtd></mtd></mtr>" \
0435                       "<mtr><mtd></mtd><mtd></mtd><mtd></mtd></mtr></mtable>", koIconNameCStr("matrix"));
0436     addTemplateAction(i18n("Insert 2 dimensional vector"), "insert_21table",
0437                       "<mtable><mtr><mtd><mrow/></mtd></mtr><mtr><mtd></mtd></mtr></mtable>", koIconNameCStr("vector"));
0438 
0439     addTemplateAction(i18n("Insert subscript"), "insert_subscript",
0440                       "<msub><mrow><mrow/></mrow><mrow/></msubsup>", koIconNameCStr("rsub"));
0441     addTemplateAction(i18n("Insert superscript"), "insert_supscript",
0442                       "<msup><mrow><mrow/></mrow><mrow/></msup>", koIconNameCStr("rsup"));
0443     addTemplateAction(i18n("Insert sub- and superscript"), "insert_subsupscript",
0444                       "<msubsup><mrow><mrow/></mrow><mrow/><mrow/></msubsup>", koIconNameCStr("rsubup"));
0445     addTemplateAction(i18n("Insert overscript"), "insert_overscript",
0446                       "<mover><mrow><mrow/></mrow><mrow/></mover>", koIconNameCStr("gsup"));
0447     addTemplateAction(i18n("Insert underscript"), "insert_underscript",
0448                       "<munder><mrow><mrow/></mrow><mrow/></munder>", koIconNameCStr("gsub"));
0449     addTemplateAction(i18n("Insert under- and overscript"), "insert_underoverscript",
0450                       "<munderover><mrow><mrow/></mrow><mrow/><mrow/></munderover>", koIconNameCStr("gsubup"));
0451 
0452     //only for debugging
0453     QAction * action;
0454     action = new QAction( "Debug - writeElementTree" , this );
0455     addAction( "write_elementTree", action );
0456 
0457     QList<QVariant> list;
0458     action = new QAction( i18n( "Insert row" ), this );
0459     list<<true<<true;
0460     action->setData( list);
0461     list.clear();
0462     addAction( "insert_row", action );
0463     action->setIcon(koIcon("insrow"));
0464 
0465     action = new QAction( i18n( "Insert column" ), this );
0466     list<<false<<true;
0467     action->setData( list);
0468     list.clear();
0469     addAction( "insert_column", action );
0470     action->setIcon(koIcon("inscol"));
0471 
0472     action = new QAction( i18n( "Remove row" ), this );
0473     list<<true<<false;
0474     action->setData( list);
0475     list.clear();
0476     addAction( "remove_row", action );
0477     action->setIcon(koIcon("remrow"));
0478 
0479     action = new QAction( i18n( "Remove column" ), this );
0480     list<<false<<false;
0481     action->setData( list);
0482     list.clear();
0483     addAction( "remove_column", action );
0484     action->setIcon(koIcon("remcol"));
0485 
0486 }
0487 
0488 
0489 void KoFormulaTool::addTemplateAction(const QString &caption, const QString &name, const QString &data,
0490                                       const char *iconName)
0491 {
0492     QAction *action = new QAction( caption, this );
0493     addAction(name , action);
0494     action->setIcon(QIcon::fromTheme(QLatin1String(iconName)));
0495     m_templateActions.push_back(TemplateAction { action, data });
0496     // the connection takes place when this KoToolBase is activated
0497 }
0498 
0499 
0500 void KoFormulaTool::copy() const
0501 {
0502     QApplication::clipboard()->setText("test");
0503 }
0504 
0505 void KoFormulaTool::deleteSelection()
0506 {
0507     KoToolBase::deleteSelection();
0508 }
0509 
0510 bool KoFormulaTool::paste()
0511 {
0512     const QMimeData* data=QApplication::clipboard()->mimeData();
0513     if (data->hasFormat("text/plain")) {
0514         debugFormula << data->text();
0515         FormulaCommand* command=m_formulaEditor->insertText(data->text());
0516         if (command!=0) {
0517             canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape,command));
0518         }
0519         repaintCursor();
0520         return true;
0521     }
0522     return false;
0523 }
0524 
0525 QStringList KoFormulaTool::supportedPasteMimeTypes() const
0526 {
0527     QStringList tmp;
0528     tmp << "text/plain";
0529     tmp << "application/xml";
0530     return tmp;
0531 }