1 /* This file is part of the KDE project
2 Copyright (C) 2006 Martin Pfeiffer <hubipete@gmx.net>
3 2009 Jeremias Epperlein <jeeree@web.de>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19 */
20
21 #include "KoFormulaTool.h"
22
23 #include "KoFormulaShape.h"
24 #include "FormulaToolWidget.h"
25 #include "BasicElement.h"
26 #include "FormulaEditor.h"
27 #include "FormulaDebug.h"
28
29 #include <KoCanvasBase.h>
30 #include <KoPointerEvent.h>
31 #include <KoSelection.h>
32 #include <KoShapeController.h>
33 #include <KoIcon.h>
34
35 #include <klocalizedstring.h>
36
37 #include <QKeyEvent>
38 #include <QAction>
39 #include <QPainter>
40 #include <QFile>
41 #include <QFileDialog>
42
43 #include <KoShapeSavingContext.h>
44 #include <KoShapeLoadingContext.h>
45 #include <KoOdfLoadingContext.h>
46 #include <KoOdfStylesReader.h>
47 #include "FormulaCommand.h"
48 #include "FormulaCommandUpdate.h"
49 #include <KoXmlReader.h>
50 #include <KoXmlWriter.h>
51 #include <KoGenStyles.h>
52 #include <KoEmbeddedDocumentSaver.h>
53 #include "FormulaRenderer.h"
54 #include <QClipboard>
55
56 // this adds const to non-const objects (like std::as_const)
koAsConst(T & t)57 template <typename T> Q_DECL_CONSTEXPR typename std::add_const<T>::type &koAsConst(T &t) noexcept { return t; }
58 // prevent rvalue arguments:
59 template <typename T> void koAsConst(const T &&) = delete;
60
61
KoFormulaTool(KoCanvasBase * canvas)62 KoFormulaTool::KoFormulaTool( KoCanvasBase* canvas ) : KoToolBase( canvas ),
63 m_formulaShape( 0 ),
64 m_formulaEditor( 0 )
65 {
66 setupActions();
67 setTextMode(true);
68 }
69
~KoFormulaTool()70 KoFormulaTool::~KoFormulaTool()
71 {
72 if( m_formulaEditor ) {
73 m_cursorList.removeAll(m_formulaEditor);
74 delete m_formulaEditor;
75 }
76 foreach (FormulaEditor* tmp, m_cursorList) {
77 delete tmp;
78 }
79 }
80
activate(ToolActivation toolActivation,const QSet<KoShape * > & shapes)81 void KoFormulaTool::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
82 {
83 Q_UNUSED(toolActivation);
84 foreach (KoShape *shape, shapes) {
85 m_formulaShape = dynamic_cast<KoFormulaShape*>( shape );
86 if( m_formulaShape )
87 break;
88 }
89
90 if( m_formulaShape == 0 ) // none found
91 {
92 emit done();
93 return;
94 }
95 useCursor(Qt::IBeamCursor);
96 m_formulaEditor=0;
97 for (int i = 0; i < m_cursorList.count(); i++) {
98 FormulaEditor* editor = m_cursorList[i];
99 FormulaData* formulaData=m_formulaShape->formulaData();
100 if (editor->formulaData() == formulaData) {
101 //we have to check if the cursors current element is actually a
102 //child of the m_formulaShape->formulaData()
103 m_cursorList.removeAll(editor);
104 if (formulaData->formulaElement()->hasDescendant(editor->cursor().currentElement())) {
105 if (editor->cursor().isAccepted()) {
106 debugFormula << "Found old cursor";
107 m_formulaEditor=editor;
108 break;
109 }
110 }
111 delete editor;
112 }
113 }
114 if (m_formulaEditor==0) {
115 //TODO: there should be a extra constructor for this
116 m_formulaEditor = new FormulaEditor( m_formulaShape->formulaData());
117 }
118 connect(m_formulaShape->formulaData(), SIGNAL(dataChanged(FormulaCommand*,bool)), this, SLOT(updateCursor(FormulaCommand*,bool)));
119 for (const TemplateAction &templateAction : koAsConst(m_templateActions)) {
120 connect(templateAction.action, &QAction::triggered, this, [this, templateAction] { insert(templateAction.data); });
121 }
122 //Only for debugging:
123 connect(action("write_elementTree"),SIGNAL(triggered(bool)), m_formulaShape->formulaData(), SLOT(writeElementTree()));
124 }
125
deactivate()126 void KoFormulaTool::deactivate()
127 {
128 for (const TemplateAction &templateAction : koAsConst(m_templateActions)) {
129 disconnect(templateAction.action, &QAction::triggered, this, nullptr);
130 }
131 disconnect(m_formulaShape->formulaData(),0,this,0);
132 if (canvas()) {
133 m_cursorList.append(m_formulaEditor);
134 debugFormula << "Appending cursor";
135 }
136 if (m_cursorList.count() > 20) { // don't let it grow indefinitely
137 delete m_cursorList.takeAt(0);
138 }
139 m_formulaShape = 0;
140 }
141
142
updateCursor(FormulaCommand * command,bool undo)143 void KoFormulaTool::updateCursor(FormulaCommand* command, bool undo)
144 {
145 if (command!=0) {
146 debugFormula << "Going to change cursor";
147 command->changeCursor(m_formulaEditor->cursor(),undo);
148 } else {
149 debugFormula << "Going to reset cursor";
150 resetFormulaEditor();
151 }
152 repaintCursor();
153 }
154
155
paint(QPainter & painter,const KoViewConverter & converter)156 void KoFormulaTool::paint( QPainter &painter, const KoViewConverter &converter )
157 {
158 painter.save();
159 // transform painter from view coordinate system to document coordinate system
160 // remember that matrix multiplication is not commutative so painter.matrix
161 // has to come last
162 painter.setTransform(m_formulaShape->absoluteTransformation(&converter) * painter.transform());
163 KoShape::applyConversion(painter,converter);
164 m_formulaShape->formulaRenderer()->paintElement(painter,m_formulaShape->formulaData()->formulaElement(),true);
165 m_formulaEditor->paint( painter );
166 painter.restore();
167 }
168
repaintCursor()169 void KoFormulaTool::repaintCursor()
170 {
171 canvas()->updateCanvas( m_formulaShape->boundingRect() );
172 }
173
mousePressEvent(KoPointerEvent * event)174 void KoFormulaTool::mousePressEvent( KoPointerEvent *event )
175 {
176 // Check if the event is valid means inside the shape
177 if(!m_formulaShape->boundingRect().contains( event->point )) {
178 return;
179 }
180 // transform the global coordinates into shape coordinates
181 QPointF p = m_formulaShape->absoluteTransformation(0).inverted().map( event->point );
182 if (event->modifiers() & Qt::ShiftModifier) {
183 m_formulaEditor->cursor().setSelecting(true);
184 } else {
185 m_formulaEditor->cursor().setSelecting(false);
186 }
187 // set the cursor to the element the user clicked on
188 m_formulaEditor->cursor().setCursorTo( p );
189
190 repaintCursor();
191 event->accept();
192 }
193
mouseDoubleClickEvent(KoPointerEvent * event)194 void KoFormulaTool::mouseDoubleClickEvent( KoPointerEvent *event )
195 {
196 if( !m_formulaShape->boundingRect().contains( event->point ) ) {
197 return;
198 }
199 // transform the global coordinates into shape coordinates
200 QPointF p = m_formulaShape->absoluteTransformation(0).inverted().map( event->point );
201
202 //clear the current selection
203 m_formulaEditor->cursor().setSelecting(false);
204 //place the cursor
205 m_formulaEditor->cursor().setCursorTo(p);
206 m_formulaEditor->cursor().selectElement(m_formulaEditor->cursor().currentElement());
207 repaintCursor();
208 event->accept();
209 }
210
mouseMoveEvent(KoPointerEvent * event)211 void KoFormulaTool::mouseMoveEvent( KoPointerEvent *event )
212 {
213 // Q_UNUSED( event )
214 if (!(event->buttons() & Qt::LeftButton)) {
215 return;
216 }
217 // Check if the event is valid means inside the shape
218 if( !m_formulaShape->boundingRect().contains( event->point ) )
219 debugFormula << "Getting most probably invalid mouseMoveEvent";
220
221 // transform the global coordinates into shape coordinates
222 QPointF p = m_formulaShape->absoluteTransformation(0).inverted().map( event->point );
223 //TODO Implement drag and drop of elements
224 m_formulaEditor->cursor().setSelecting(true);
225 m_formulaEditor->cursor().setCursorTo( p );
226 repaintCursor();
227 event->accept();
228 }
229
mouseReleaseEvent(KoPointerEvent * event)230 void KoFormulaTool::mouseReleaseEvent( KoPointerEvent *event )
231 {
232 Q_UNUSED( event )
233
234 // TODO Implement drag and drop
235 }
236
keyPressEvent(QKeyEvent * event)237 void KoFormulaTool::keyPressEvent( QKeyEvent *event )
238 {
239 FormulaCommand *command=0;
240 if( !m_formulaEditor )
241 return;
242
243 if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right ||
244 event->key() == Qt::Key_Up || event->key() == Qt::Key_Down ||
245 event->key() == Qt::Key_Home || event->key() == Qt::Key_End ) {
246 if (event->modifiers() & Qt::ShiftModifier) {
247 m_formulaEditor->cursor().setSelecting(true);
248 } else {
249 m_formulaEditor->cursor().setSelecting(false);
250 }
251 }
252 switch( event->key() ) // map key to movement or action
253 {
254 case Qt::Key_Backspace:
255 m_formulaShape->update();
256 command=m_formulaEditor->remove( true );
257 m_formulaShape->updateLayout();
258 m_formulaShape->update();
259 break;
260 case Qt::Key_Delete:
261 m_formulaShape->update();
262 command=m_formulaEditor->remove( false );
263 m_formulaShape->updateLayout();
264 m_formulaShape->update();
265 break;
266 case Qt::Key_Left:
267 m_formulaEditor->cursor().move( MoveLeft );
268 break;
269 case Qt::Key_Up:
270 m_formulaEditor->cursor().move( MoveUp );
271 break;
272 case Qt::Key_Right:
273 m_formulaEditor->cursor().move( MoveRight );
274 break;
275 case Qt::Key_Down:
276 m_formulaEditor->cursor().move( MoveDown );
277 break;
278 case Qt::Key_End:
279 m_formulaEditor->cursor().moveEnd();
280 break;
281 case Qt::Key_Home:
282 m_formulaEditor->cursor().moveHome();
283 break;
284 default:
285 if( event->text().length() != 0 ) {
286 command=m_formulaEditor->insertText( event->text() );
287 }
288 }
289 if (command!=0) {
290 canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape,command));
291 }
292 repaintCursor();
293 event->accept();
294 }
295
keyReleaseEvent(QKeyEvent * event)296 void KoFormulaTool::keyReleaseEvent( QKeyEvent *event )
297 {
298 event->accept();
299 }
300
remove(bool backSpace)301 void KoFormulaTool::remove( bool backSpace )
302 {
303 m_formulaShape->update();
304 m_formulaEditor->remove( backSpace );
305 m_formulaShape->updateLayout();
306 m_formulaShape->update();
307 }
308
insert(const QString & action)309 void KoFormulaTool::insert( const QString& action )
310 {
311 FormulaCommand *command;
312 m_formulaShape->update();
313 command=m_formulaEditor->insertMathML( action );
314 if (command!=0) {
315 canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape, command));
316 }
317 }
318
changeTable(QAction * action)319 void KoFormulaTool::changeTable ( QAction* action )
320 {
321 FormulaCommand *command;
322 m_formulaShape->update();
323 bool row=action->data().toList()[0].toBool();
324 bool insert=action->data().toList()[1].toBool();
325 command=m_formulaEditor->changeTable(insert,row);
326 if (command!=0) {
327 canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape, command));
328 }
329 }
330
insertSymbol(const QString & symbol)331 void KoFormulaTool::insertSymbol ( const QString& symbol )
332 {
333 FormulaCommand *command;
334 m_formulaShape->update();
335 command=m_formulaEditor->insertText( symbol );
336 if (command!=0) {
337 canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape, command));
338 }
339 }
340
341
createOptionWidget()342 QWidget* KoFormulaTool::createOptionWidget()
343 {
344 FormulaToolWidget* options = new FormulaToolWidget( this );
345 options->setFormulaTool( this );
346 return options;
347 }
348
shape()349 KoFormulaShape* KoFormulaTool::shape()
350 {
351 return m_formulaShape;
352 }
353
354
formulaEditor()355 FormulaEditor* KoFormulaTool::formulaEditor()
356 {
357 return m_formulaEditor;
358 }
359
360
resetFormulaEditor()361 void KoFormulaTool::resetFormulaEditor() {
362 m_formulaEditor->setData(m_formulaShape->formulaData());
363 FormulaCursor cursor(FormulaCursor(m_formulaShape->formulaData()->formulaElement(),false,0,0));
364 m_formulaEditor->setCursor(cursor);
365 //if the cursor is not allowed at the beginning of the formula, move it right
366 //TODO: check, if this can ever happen
367 if ( !m_formulaEditor->cursor().isAccepted() ) {
368 m_formulaEditor->cursor().move(MoveRight);
369 }
370 }
371
loadFormula()372 void KoFormulaTool::loadFormula()
373 {
374 // get an filepath
375 const QString fileName = QFileDialog::getOpenFileName();
376 if( fileName.isEmpty() || !shape() )
377 return;
378
379 // open the file the filepath points to
380 QFile file( fileName );
381 if( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
382 return;
383
384 KoOdfStylesReader stylesReader;
385 KoOdfLoadingContext odfContext( stylesReader, 0 );
386 KoShapeLoadingContext shapeContext(odfContext, canvas()->shapeController()->resourceManager());
387
388 // setup a DOM structure and start the actual loading process
389 KoXmlDocument tmpDocument;
390 tmpDocument.setContent( &file, false, 0, 0, 0 );
391 FormulaElement* formulaElement = new FormulaElement(); // create a new root element
392 formulaElement->readMathML( tmpDocument.documentElement() ); // and load the new formula
393 FormulaCommand* command=new FormulaCommandLoad(m_formulaShape->formulaData(),formulaElement);
394 canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape, command));
395 }
396
saveFormula()397 void KoFormulaTool::saveFormula()
398 {
399 const QString filePath = QFileDialog::getSaveFileName();
400 if( filePath.isEmpty() || !shape() )
401 return;
402
403 QFile file( filePath );
404 KoXmlWriter writer( &file );
405 KoGenStyles styles;
406 KoEmbeddedDocumentSaver embeddedSaver;
407 KoShapeSavingContext shapeSavingContext( writer, styles, embeddedSaver );
408
409 m_formulaShape->formulaData()->saveMathML( shapeSavingContext );
410 }
411
setupActions()412 void KoFormulaTool::setupActions()
413 {
414 //notice that only empty mrows hows parent is a inferred mrow are treated as placeholders
415 //this causes the <mrow><mrow/></mrow> constructs
416 addTemplateAction(i18n("Insert fenced element"), "insert_fence","<mfenced><mrow/></mfenced>", koIconNameCStr("brackets"));
417 addTemplateAction(i18n("Insert enclosed element"), "insert_enclosed",
418 "<menclosed><mrow/></menclosed>", koIconNameCStr("enclosed"));
419
420 addTemplateAction(i18n("Insert root"), "insert_root","<mroot><mrow><mrow/></mrow></mroot>", koIconNameCStr("root"));
421 addTemplateAction(i18n("Insert square root"), "insert_sqrt","<msqrt><mrow/></msqrt>", koIconNameCStr("sqrt"));
422
423 addTemplateAction(i18n("Insert fraction"), "insert_fraction",
424 "<mfrac><mrow><mrow/></mrow><mrow/></mfrac>", koIconNameCStr("frac"));
425 addTemplateAction(i18n("Insert bevelled fraction"), "insert_bevelled_fraction",
426 "<mfrac bevelled=\"true\"><mrow><mrow/></mrow><mrow/></mfrac>", koIconNameCStr("bevelled"));
427
428 addTemplateAction(i18n("Insert 3x3 table"), "insert_33table",
429 "<mtable><mtr><mtd><mrow /></mtd><mtd></mtd><mtd></mtd></mtr>" \
430 "<mtr><mtd></mtd><mtd></mtd><mtd></mtd></mtr>" \
431 "<mtr><mtd></mtd><mtd></mtd><mtd></mtd></mtr></mtable>", koIconNameCStr("matrix"));
432 addTemplateAction(i18n("Insert 2 dimensional vector"), "insert_21table",
433 "<mtable><mtr><mtd><mrow/></mtd></mtr><mtr><mtd></mtd></mtr></mtable>", koIconNameCStr("vector"));
434
435 addTemplateAction(i18n("Insert subscript"), "insert_subscript",
436 "<msub><mrow><mrow/></mrow><mrow/></msubsup>", koIconNameCStr("rsub"));
437 addTemplateAction(i18n("Insert superscript"), "insert_supscript",
438 "<msup><mrow><mrow/></mrow><mrow/></msup>", koIconNameCStr("rsup"));
439 addTemplateAction(i18n("Insert sub- and superscript"), "insert_subsupscript",
440 "<msubsup><mrow><mrow/></mrow><mrow/><mrow/></msubsup>", koIconNameCStr("rsubup"));
441 addTemplateAction(i18n("Insert overscript"), "insert_overscript",
442 "<mover><mrow><mrow/></mrow><mrow/></mover>", koIconNameCStr("gsup"));
443 addTemplateAction(i18n("Insert underscript"), "insert_underscript",
444 "<munder><mrow><mrow/></mrow><mrow/></munder>", koIconNameCStr("gsub"));
445 addTemplateAction(i18n("Insert under- and overscript"), "insert_underoverscript",
446 "<munderover><mrow><mrow/></mrow><mrow/><mrow/></munderover>", koIconNameCStr("gsubup"));
447
448 //only for debugging
449 QAction * action;
450 action = new QAction( "Debug - writeElementTree" , this );
451 addAction( "write_elementTree", action );
452
453 QList<QVariant> list;
454 action = new QAction( i18n( "Insert row" ), this );
455 list<<true<<true;
456 action->setData( list);
457 list.clear();
458 addAction( "insert_row", action );
459 action->setIcon(koIcon("insrow"));
460
461 action = new QAction( i18n( "Insert column" ), this );
462 list<<false<<true;
463 action->setData( list);
464 list.clear();
465 addAction( "insert_column", action );
466 action->setIcon(koIcon("inscol"));
467
468 action = new QAction( i18n( "Remove row" ), this );
469 list<<true<<false;
470 action->setData( list);
471 list.clear();
472 addAction( "remove_row", action );
473 action->setIcon(koIcon("remrow"));
474
475 action = new QAction( i18n( "Remove column" ), this );
476 list<<false<<false;
477 action->setData( list);
478 list.clear();
479 addAction( "remove_column", action );
480 action->setIcon(koIcon("remcol"));
481
482 }
483
484
addTemplateAction(const QString & caption,const QString & name,const QString & data,const char * iconName)485 void KoFormulaTool::addTemplateAction(const QString &caption, const QString &name, const QString &data,
486 const char *iconName)
487 {
488 QAction *action = new QAction( caption, this );
489 addAction(name , action);
490 action->setIcon(QIcon::fromTheme(QLatin1String(iconName)));
491 m_templateActions.push_back(TemplateAction { action, data });
492 // the connection takes place when this KoToolBase is activated
493 }
494
495
copy() const496 void KoFormulaTool::copy() const
497 {
498 QApplication::clipboard()->setText("test");
499 }
500
deleteSelection()501 void KoFormulaTool::deleteSelection()
502 {
503 KoToolBase::deleteSelection();
504 }
505
paste()506 bool KoFormulaTool::paste()
507 {
508 const QMimeData* data=QApplication::clipboard()->mimeData();
509 if (data->hasFormat("text/plain")) {
510 debugFormula << data->text();
511 FormulaCommand* command=m_formulaEditor->insertText(data->text());
512 if (command!=0) {
513 canvas()->addCommand(new FormulaCommandUpdate(m_formulaShape,command));
514 }
515 repaintCursor();
516 return true;
517 }
518 return false;
519 }
520
supportedPasteMimeTypes() const521 QStringList KoFormulaTool::supportedPasteMimeTypes() const
522 {
523 QStringList tmp;
524 tmp << "text/plain";
525 tmp << "application/xml";
526 return tmp;
527 }
528