operatorelement.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2006 Alfredo Beaumont Sainz <alfredo.beaumont@gmail.com>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18*/
19
20#include <algorithm>
21
22#include <tqpainter.h>
23
24#include <tdelocale.h>
25
26#include "elementtype.h"
27#include "sequenceelement.h"
28#include "textelement.h"
29#include "fontstyle.h"
30#include "operatordictionary.h"
31#include "operatorelement.h"
32#include "identifierelement.h"
33#include "numberelement.h"
34#include "kformulacommand.h"
35#include "kformulacontainer.h"
36#include "kformuladocument.h"
37#include "formulaelement.h"
38#include "creationstrategy.h"
39
40KFORMULA_NAMESPACE_BEGIN
41
42OperatorElement::OperatorElement( BasicElement* parent ) : TokenElement( parent ),
43 m_form( NoForm ),
44 m_lspaceType( ThickMathSpace ),
45 m_rspaceType( ThickMathSpace ),
46 m_maxSizeType( InfinitySize ),
47 m_minSizeType( RelativeSize ),
48 m_minSize( 1 ),
49 m_fence( false ),
50 m_separator( false ),
51 m_stretchy( false ),
52 m_symmetric( true ),
53 m_largeOp( false ),
54 m_movableLimits( false ),
55 m_accent( false ),
56 m_customForm( false ),
57 m_customFence( false ),
58 m_customSeparator( false ),
59 m_customLSpace( false ),
60 m_customRSpace( false ),
61 m_customStretchy( false ),
62 m_customSymmetric( false ),
63 m_customMaxSize( false ),
64 m_customMinSize( false ),
65 m_customLargeOp( false ),
66 m_customMovableLimits( false ),
67 m_customAccent( false )
68{
69}
70
71void OperatorElement::setForm( FormType type )
72{
73 if ( ! m_customForm ) { // Set by an attribute has higher priority
74 m_form = type;
75 }
76
77 if ( ! isTextOnly() ) { // Only text content can be dictionary keys
78 return;
79 }
80 TQString text;
81 for ( uint i = 0; i < countChildren(); i++ ) {
82 text.append( getChild( i )->getCharacter() );
83 }
84 TQString form;
85 switch ( m_form ) {
86 case PrefixForm:
87 form = "prefix";
88 break;
89 case InfixForm:
90 form = "infix";
91 break;
92 case PostfixForm:
93 form = "postfix";
94 break;
95 default:
96 // Should not happen
97 kdWarning( DEBUGID ) << "Invalid `form' attribute value\n";
98 return;
99 }
100 DictionaryKey key = { text.utf8(), form.ascii() };
101 const OperatorDictionary* begin = operators;
102 const OperatorDictionary* end = operators + OperatorDictionary::size();
103 const OperatorDictionary* pos = std::lower_bound( begin, end, key );
104 if ( pos != end && pos->key == key ) { // Entry found !
105 if ( ! m_customFence ) {
106 m_fence = pos->fence;
107 }
108 if ( ! m_customSeparator ) {
109 m_separator = pos->separator;
110 }
111 if ( ! m_customLSpace ) {
112 m_lspace = getSize( pos->lspace, &m_lspaceType );
113 if ( m_lspaceType == NoSize ) {
114 m_lspaceType = getSpace( pos->lspace );
115 }
116 }
117 if ( ! m_customRSpace ) {
118 m_rspace = getSize( pos->rspace, &m_rspaceType );
119 if ( m_rspaceType == NoSize ) {
120 m_rspaceType = getSpace( pos->rspace );
121 }
122 }
123 if ( ! m_customStretchy ) {
124 m_stretchy = pos->stretchy;
125 }
126 if ( ! m_customSymmetric ) {
127 m_symmetric = pos->symmetric;
128 }
129 if ( ! m_customMaxSize ) {
130 if ( qstrcmp( pos->maxsize, "infinity" ) == 0 ) {
131 m_maxSizeType = InfinitySize;
132 }
133 else {
134 m_maxSize = getSize( pos->maxsize, &m_maxSizeType );
135 if ( m_maxSizeType == NoSize ) {
136 m_maxSizeType = getSpace( pos->maxsize );
137 }
138 }
139 }
140 if ( ! m_customMinSize ) {
141 m_minSize = getSize( pos->minsize, &m_minSizeType );
142 if ( m_minSizeType == NoSize ) {
143 m_minSizeType = getSpace( pos->minsize );
144 }
145 }
146 if ( ! m_customLargeOp ) {
147 m_largeOp = pos->largeop;
148 }
149 if ( ! m_customMovableLimits ) {
150 m_movableLimits = pos->movablelimits;
151 }
152 if ( ! m_customAccent ) {
153 m_accent = pos->accent;
154 }
155 }
156}
157
158/*
159 * Token elements' content has to be of homogeneous type. Every token element
160 * must (TODO: check this) appear inside a non-token sequence, and thus, if
161 * the command asks for a different content, a new element has to be created in
162 * parent sequence.
163 */
164KCommand* OperatorElement::buildCommand( Container* container, Request* request )
165{
166 FormulaCursor* cursor = container->activeCursor();
167 if ( cursor->isReadOnly() ) {
168 formula()->tell( i18n( "write protection" ) );
169 return 0;
170 }
171
172 if ( *request == req_addOperator ) {
173 KFCReplace* command = new KFCReplace( i18n("Add Operator"), container );
174 OperatorRequest* opr = static_cast<OperatorRequest*>( request );
175 TextElement* element = creationStrategy->createTextElement( opr->ch(), true );
176 command->addElement( element );
177 return command;
178 }
179
180 if ( countChildren() == 0 || cursor->getPos() == countChildren() ) {
181 // We are in the last position, so it's easy, call the parent to
182 // create a new child
183 SequenceElement* parent = static_cast<SequenceElement*>( getParent() );
184 if ( parent ) {
185 uint pos = parent->childPos( this );
186 cursor->setTo( parent, pos + 1);
187 return parent->buildCommand( container, request );
188 }
189 }
190 if ( cursor->getPos() == 0 ) {
191 SequenceElement* parent = static_cast<SequenceElement*>( getParent() );
192 if ( parent ) {
193 uint pos = parent->childPos( this );
194 cursor->setTo( parent, pos );
195 return parent->buildCommand( container, request );
196 }
197 }
198
199 // We are in the middle of a token, so:
200 // a) Cut from mark to the end
201 // b) Create a new token and add an element from key pressed
202 // c) Create a new token and add elements cut previously
203 // d) Move cursor to parent so that it command execution works fine
204
205 switch( *request ) {
206 case req_addTextChar: {
207 KFCSplitToken* command = new KFCSplitToken( i18n("Add Text"), container );
208 TextCharRequest* tr = static_cast<TextCharRequest*>( request );
209 IdentifierElement* id = creationStrategy->createIdentifierElement();
210 TextElement* text = creationStrategy->createTextElement( tr->ch() );
211 command->addCursor( cursor );
212 command->addToken( id );
213 command->addContent( id, text );
214 SequenceElement* parent = static_cast< SequenceElement* >( getParent() );
215 if ( parent ) {
216 cursor->setTo( parent, parent->childPos( this ) + 1 );
217 }
218 return command;
219 }
220
221 case req_addText: {
222 KFCSplitToken* command = new KFCSplitToken( i18n("Add Text"), container );
223 TextRequest* tr = static_cast<TextRequest*>( request );
224 IdentifierElement* id = creationStrategy->createIdentifierElement();
225 command->addCursor( cursor );
226 command->addToken( id );
227 for ( uint i = 0; i < tr->text().length(); i++ ) {
228 TextElement* text = creationStrategy->createTextElement( tr->text()[i] );
229 command->addContent( id, text );
230 }
231 SequenceElement* parent = static_cast< SequenceElement* >( getParent() );
232 if ( parent ) {
233 cursor->setTo( parent, parent->childPos( this ) + 1 );
234 }
235 return command;
236 }
237
238 case req_addNumber: {
239 KFCSplitToken* command = new KFCSplitToken( i18n("Add Number"), container );
240 NumberRequest* nr = static_cast<NumberRequest*>( request );
241 NumberElement* num = creationStrategy->createNumberElement();
242 TextElement* text = creationStrategy->createTextElement( nr->ch() );
243 command->addCursor( cursor );
244 command->addToken( num );
245 command->addContent( num, text );
246 SequenceElement* parent = static_cast< SequenceElement* >( getParent() );
247 if ( parent ) {
248 cursor->setTo( parent, parent->childPos( this ) + 1 );
249 }
250 return command;
251 }
252 case req_addEmptyBox:
253 case req_addNameSequence:
254 case req_addBracket:
255 case req_addSpace:
256 case req_addFraction:
257 case req_addRoot:
258 case req_addSymbol:
259 case req_addOneByTwoMatrix:
260 case req_addMatrix: {
261 uint pos = static_cast<SequenceElement*>(getParent())->childPos( this );
262 cursor->setTo( getParent(), pos + 1);
263 return getParent()->buildCommand( container, request );
264 }
265 default:
266 return SequenceElement::buildCommand( container, request );
267 }
268 return 0;
269}
270
271
272bool OperatorElement::readAttributesFromMathMLDom( const TQDomElement &element )
273{
275 return false;
276 }
277
278 TQString formStr = element.attribute( "form" ).stripWhiteSpace().lower();
279 if ( ! formStr.isNull() ) {
280 m_customForm = true;
281 if ( formStr == "prefix" ) {
282 m_form = PrefixForm;
283 }
284 else if ( formStr == "infix" ) {
285 m_form = InfixForm;
286 }
287 else if ( formStr == "postfix" ) {
288 m_form = PostfixForm;
289 }
290 else {
291 kdWarning( DEBUGID ) << "Invalid value for attribute `form': " << formStr << endl;
292 m_customForm = false;
293 }
294 }
295 TQString fenceStr = element.attribute( "fence" ).stripWhiteSpace().lower();
296 if ( ! fenceStr.isNull() ) {
297 m_customFence = true;
298 if ( fenceStr == "true" ) {
299 m_fence = true;
300 }
301 else if ( fenceStr == "false" ) {
302 m_fence = false;
303 }
304 else {
305 kdWarning( DEBUGID ) << "Invalid value for attribute `fence': " << fenceStr << endl;
306 m_customFence = false;
307 }
308 }
309 TQString separatorStr = element.attribute( "separator" ).stripWhiteSpace().lower();
310 if ( ! separatorStr.isNull() ) {
311 m_customSeparator = true;
312 if ( separatorStr == "true" ) {
313 m_separator = true;
314 }
315 else if ( separatorStr == "false" ) {
316 m_separator = false;
317 }
318 else {
319 kdWarning( DEBUGID ) << "Invalid value for attribute `separator': " << separatorStr << endl;
320 m_customSeparator = false;
321 }
322 }
323 TQString lspaceStr = element.attribute( "lspace" ).stripWhiteSpace().lower();
324 if ( ! lspaceStr.isNull() ) {
325 m_customLSpace = true;
326 m_lspace = getSize( lspaceStr, &m_lspaceType );
327 if ( m_lspaceType == NoSize ) {
328 m_lspaceType = getSpace( lspaceStr );
329 }
330 }
331 TQString rspaceStr = element.attribute( "rspace" ).stripWhiteSpace().lower();
332 if ( ! rspaceStr.isNull() ) {
333 m_customRSpace = true;
334 m_rspace = getSize( rspaceStr, &m_rspaceType );
335 if ( m_rspaceType == NoSize ) {
336 m_rspaceType = getSpace( rspaceStr );
337 }
338 }
339 TQString stretchyStr = element.attribute( "stretchy" ).stripWhiteSpace().lower();
340 if ( ! stretchyStr.isNull() ) {
341 m_customStretchy = true;
342 if ( stretchyStr == "true" ) {
343 m_stretchy = true;
344 }
345 else if ( stretchyStr == "false" ) {
346 m_stretchy = false;
347 }
348 else {
349 kdWarning( DEBUGID ) << "Invalid value for attribute `stretchy': " << stretchyStr << endl;
350 m_customStretchy = false;
351 }
352 }
353 TQString symmetricStr = element.attribute( "symmetric" ).stripWhiteSpace().lower();
354 if ( ! symmetricStr.isNull() ) {
355 m_customSymmetric = true;
356 if ( symmetricStr == "true" ) {
357 m_symmetric = true;
358 }
359 else if ( symmetricStr == "false" ) {
360 m_symmetric = false;
361 }
362 else {
363 kdWarning( DEBUGID ) << "Invalid value for attribute `symmetric': " << symmetricStr << endl;
364 m_customSymmetric = false;
365 }
366 }
367 TQString maxsizeStr = element.attribute( "maxsize" ).stripWhiteSpace().lower();
368 if ( ! maxsizeStr.isNull() ) {
369 m_customMaxSize = true;
370 if ( maxsizeStr == "infinity" ) {
371 m_maxSizeType = InfinitySize;
372 }
373 else {
374 m_maxSize = getSize( maxsizeStr, &m_maxSizeType );
375 if ( m_maxSizeType == NoSize ) {
376 m_maxSizeType = getSpace( maxsizeStr );
377 }
378 }
379 }
380 TQString minsizeStr = element.attribute( "minsize" ).stripWhiteSpace().lower();
381 if ( ! minsizeStr.isNull() ) {
382 m_customMinSize = true;
383 m_minSize = getSize( minsizeStr, &m_minSizeType );
384 if ( m_minSizeType == NoSize ) {
385 m_minSizeType = getSpace( minsizeStr );
386 }
387 }
388 TQString largeopStr = element.attribute( "largeop" ).stripWhiteSpace().lower();
389 if ( ! largeopStr.isNull() ) {
390 m_customLargeOp = true;
391 if ( largeopStr == "true" ) {
392 m_largeOp = true;
393 }
394 else if ( largeopStr == "false" ) {
395 m_largeOp = false;
396 }
397 else {
398 kdWarning( DEBUGID ) << "Invalid value for attribute `largeop': " << largeopStr << endl;
399 m_customLargeOp = false;
400 }
401 }
402 TQString movablelimitsStr = element.attribute( "movablelimits" ).stripWhiteSpace().lower();
403 if ( ! movablelimitsStr.isNull() ) {
404 m_customMovableLimits = true;
405 if ( movablelimitsStr == "true" ) {
406 m_movableLimits = true;
407 }
408 else if ( movablelimitsStr == "false" ) {
409 m_movableLimits = false;
410 }
411 else {
412 kdWarning( DEBUGID ) << "Invalid value for attribute `movablelimits': " << movablelimitsStr << endl;
413 m_customMovableLimits = false;
414 }
415 }
416 TQString accentStr = element.attribute( "accent" ).stripWhiteSpace().lower();
417 if ( ! accentStr.isNull() ) {
418 m_customAccent = true;
419 if ( accentStr == "true" ) {
420 m_accent = true;
421 }
422 else if ( accentStr == "false" ) {
423 m_accent = false;
424 }
425 else {
426 kdWarning( DEBUGID ) << "Invalid value for attribute `accent': " << accentStr << endl;
427 m_customAccent = false;
428 }
429 }
430 return true;
431}
432
433void OperatorElement::writeMathMLAttributes( TQDomElement& element ) const
434{
435 if ( m_customForm ) {
436 switch ( m_form ) {
437 case PrefixForm:
438 element.setAttribute( "form", "prefix" );
439 break;
440 case InfixForm:
441 element.setAttribute( "form", "infix" );
442 break;
443 case PostfixForm:
444 element.setAttribute( "form", "postfix" );
445 default:
446 break;
447 }
448 }
449 if ( m_customFence ) {
450 element.setAttribute( "fence", m_fence ? "true" : "false" );
451 }
452 if ( m_customSeparator ) {
453 element.setAttribute( "separator", m_separator ? "true" : "false" );
454 }
455 if ( m_customLSpace ) {
456 writeSizeAttribute( element, "lspace", m_lspaceType, m_lspace );
457 }
458 if ( m_customRSpace ) {
459 writeSizeAttribute( element, "rspace", m_rspaceType, m_rspace );
460 }
461 if ( m_customStretchy ) {
462 element.setAttribute( "stretchy", m_stretchy ? "true" : "false" );
463 }
464 if ( m_customSymmetric ) {
465 element.setAttribute( "symmetric", m_symmetric ? "true" : "false" );
466 }
467 if ( m_customMaxSize ) {
468 writeSizeAttribute( element, "maxsize", m_maxSizeType, m_maxSize );
469 }
470 if ( m_customMinSize ) {
471 writeSizeAttribute( element, "minsize", m_minSizeType, m_minSize );
472 }
473 if ( m_customLargeOp ) {
474 element.setAttribute( "largeop", m_largeOp ? "true" : "false" );
475 }
476 if ( m_customMovableLimits ) {
477 element.setAttribute( "movablelimits", m_movableLimits ? "true" : "false" );
478 }
479 if ( m_customAccent ) {
480 element.setAttribute( "accent", m_accent ? "true" : "false" );
481 }
482}
483
484void OperatorElement::writeSizeAttribute( TQDomElement& element, const TQString &attr, SizeType type, double length ) const
485{
486 switch ( type ) {
487 case InfinitySize:
488 element.setAttribute( attr, "infinity" );
489 break;
490 case AbsoluteSize:
491 element.setAttribute( attr, TQString( "%1pt" ).arg( length ) );
492 break;
493 case RelativeSize:
494 element.setAttribute( attr, TQString( "%1% " ).arg( length * 100.0 ) );
495 break;
496 case PixelSize:
497 element.setAttribute( attr, TQString( "%1px " ).arg( length ) );
498 break;
499 case NegativeVeryVeryThinMathSpace:
500 element.setAttribute( attr, "negativeveryverythinmathspace" );
501 break;
502 case NegativeVeryThinMathSpace:
503 element.setAttribute( attr, "negativeverythinmathspace" );
504 break;
505 case NegativeThinMathSpace:
506 element.setAttribute( attr, "negativethinmathspace" );
507 break;
508 case NegativeMediumMathSpace:
509 element.setAttribute( attr, "negativemediummathspace" );
510 break;
511 case NegativeThickMathSpace:
512 element.setAttribute( attr, "negativethickmathspace" );
513 break;
514 case NegativeVeryThickMathSpace:
515 element.setAttribute( attr, "negativeverythickmathspace" );
516 break;
517 case NegativeVeryVeryThickMathSpace:
518 element.setAttribute( attr, "negativeveryverythickmathspace" );
519 break;
520 case VeryVeryThinMathSpace:
521 element.setAttribute( attr, "veryverythinmathspace" );
522 break;
523 case VeryThinMathSpace:
524 element.setAttribute( attr, "verythinmathspace" );
525 break;
526 case ThinMathSpace:
527 element.setAttribute( attr, "thinmathspace" );
528 break;
529 case MediumMathSpace:
530 element.setAttribute( attr, "mediummathspace" );
531 break;
532 case ThickMathSpace:
533 element.setAttribute( attr, "thickmathspace" );
534 break;
535 case VeryThickMathSpace:
536 element.setAttribute( attr, "verythickmathspace" );
537 break;
538 case VeryVeryThickMathSpace:
539 element.setAttribute( attr, "veryverythickmathspace" );
540 break;
541 default:
542 break;
543 }
544}
545
546
547KFORMULA_NAMESPACE_END
Basis of every formula element.
virtual bool readAttributesFromMathMLDom(const TQDomElement &element)
Reads our attributes from the MathML element.
The document.
FormulaCursor * activeCursor()
The selection.
void setTo(BasicElement *element, uint cursor, int mark=-1)
Sets the cursor to a new position.
bool isReadOnly() const
void addContent(BasicElement *element, BasicElement *content)
Collect content for each token.
void addToken(BasicElement *element)
Collect all tokens that are to be added.
void addElement(BasicElement *element)
Collects all elements that are to be added.
Removes the current selection and adds any new elements afterwards.
Add and remove arbitrary token elements and their contents.
void addCursor(FormulaCursor *cursor)
Collect cursor data that will be needed to do the stuff properly.
The element that contains a number of children.
virtual KCommand * buildCommand(Container *, Request *)
This is called by the container to get a command depending on the current cursor position (this is ho...
An element that represents one char.
Definition textelement.h:37