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 }