1
/* This file is part of the KDE project
2
Copyright (C) 2006-2009 Jan Hambrecht <jaham@gmx.net>
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.
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.
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.
20
#include "KoStarShape.h"
22
#include <KoPathPoint.h>
23
#include <KoShapeLoadingContext.h>
24
#include <KoShapeSavingContext.h>
25
#include <KoXmlReader.h>
27
#include <KoXmlWriter.h>
31
KoStarShape::KoStarShape()
37
m_radius[base] = 25.0;
39
m_angles[base] = m_angles[tip] = defaultAngleRadian();
40
m_roundness[base] = m_roundness[tip] = 0.0f;
42
m_center = QPointF(50,50);
43
updatePath( QSize(100,100) );
46
KoStarShape::~KoStarShape()
50
void KoStarShape::setCornerCount( uint cornerCount )
52
if( cornerCount >= 3 )
54
double oldDefaultAngle = defaultAngleRadian();
55
m_cornerCount = cornerCount;
56
double newDefaultAngle = defaultAngleRadian();
57
m_angles[base] += newDefaultAngle-oldDefaultAngle;
58
m_angles[tip] += newDefaultAngle-oldDefaultAngle;
60
updatePath( QSize() );
64
uint KoStarShape::cornerCount() const
69
void KoStarShape::setBaseRadius( qreal baseRadius )
71
m_radius[base] = fabs( baseRadius );
72
updatePath( QSize() );
75
qreal KoStarShape::baseRadius() const
77
return m_radius[base];
80
void KoStarShape::setTipRadius( qreal tipRadius )
82
m_radius[tip] = fabs( tipRadius );
83
updatePath( QSize() );
86
qreal KoStarShape::tipRadius() const
91
void KoStarShape::setBaseRoundness( qreal baseRoundness )
93
m_roundness[base] = baseRoundness;
94
updatePath( QSize() );
97
void KoStarShape::setTipRoundness( qreal tipRoundness )
99
m_roundness[tip] = tipRoundness;
100
updatePath( QSize() );
103
void KoStarShape::setConvex( bool convex )
106
updatePath( QSize() );
109
bool KoStarShape::convex() const
114
QPointF KoStarShape::starCenter() const
119
void KoStarShape::moveHandleAction( int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers )
121
if( modifiers & Qt::ShiftModifier )
123
QPointF tangentVector = point - m_handles[handleId];
124
qreal distance = sqrt( tangentVector.x()*tangentVector.x() + tangentVector.y()*tangentVector.y() );
125
QPointF radialVector = m_handles[handleId] - m_center;
126
// cross product to determine in which direction the user is dragging
127
qreal moveDirection = radialVector.x()*tangentVector.y() - radialVector.y()*tangentVector.x();
128
// make the roundness stick to zero if distance is under a certain value
129
float snapDistance = 3.0;
130
if( distance >= 0.0 )
131
distance = distance < snapDistance ? 0.0 : distance-snapDistance;
133
distance = distance > -snapDistance ? 0.0 : distance+snapDistance;
134
// control changes roundness on both handles, else only the actual handle roundness is changed
135
if( modifiers & Qt::ControlModifier )
136
m_roundness[handleId] = moveDirection < 0.0f ? distance : -distance;
138
m_roundness[base] = m_roundness[tip] = moveDirection < 0.0f ? distance : -distance;
142
QPointF distVector = point - m_center;
144
distVector.rx() /= m_zoomX;
145
distVector.ry() /= m_zoomY;
146
m_radius[handleId] = sqrt( distVector.x()*distVector.x() + distVector.y()*distVector.y() );
148
qreal angle = atan2( distVector.y(), distVector.x() );
151
qreal diffAngle = angle-m_angles[handleId];
152
qreal radianStep = M_PI / static_cast<qreal>(m_cornerCount);
153
if( handleId == tip )
155
m_angles[tip] += diffAngle-radianStep;
156
m_angles[base] += diffAngle-radianStep;
160
// control make the base point move freely
161
if( modifiers & Qt::ControlModifier )
162
m_angles[base] += diffAngle-2*radianStep;
164
m_angles[base] = m_angles[tip];
169
void KoStarShape::updatePath( const QSizeF &size )
172
qreal radianStep = M_PI / static_cast<qreal>(m_cornerCount);
174
createPoints( m_convex ? m_cornerCount : 2*m_cornerCount );
176
KoSubpath &points = *m_subpaths[0];
179
for( uint i = 0; i < 2*m_cornerCount; ++i )
181
uint cornerType = i % 2;
182
if( cornerType == base && m_convex )
184
qreal radian = static_cast<qreal>( (i+1)*radianStep ) + m_angles[cornerType];
185
QPointF cornerPoint = QPointF( m_zoomX * m_radius[cornerType] * cos( radian ), m_zoomY * m_radius[cornerType] * sin( radian ) );
187
points[index]->setPoint( m_center + cornerPoint );
188
points[index]->unsetProperty( KoPathPoint::StopSubpath );
189
points[index]->unsetProperty( KoPathPoint::CloseSubpath );
190
if( m_roundness[cornerType] > 1e-10 || m_roundness[cornerType] < -1e-10 )
192
// normalized cross product to compute tangential vector for handle point
193
QPointF tangentVector( cornerPoint.y()/m_radius[cornerType], -cornerPoint.x()/m_radius[cornerType] );
194
points[index]->setControlPoint2( points[index]->point() - m_roundness[cornerType] * tangentVector );
195
points[index]->setControlPoint1( points[index]->point() + m_roundness[cornerType] * tangentVector );
199
points[index]->removeControlPoint1();
200
points[index]->removeControlPoint2();
205
// first path starts and closes path
206
points[0]->setProperty( KoPathPoint::StartSubpath );
207
points[0]->setProperty( KoPathPoint::CloseSubpath );
208
// last point stops and closes path
209
points.last()->setProperty( KoPathPoint::StopSubpath );
210
points.last()->setProperty( KoPathPoint::CloseSubpath );
215
m_handles.push_back( points.at(tip)->point() );
217
m_handles.push_back( points.at(base)->point() );
219
m_center = computeCenter();
222
void KoStarShape::createPoints( int requiredPointCount )
224
if ( m_subpaths.count() != 1 ) {
226
m_subpaths.append( new KoSubpath() );
228
int currentPointCount = m_subpaths[0]->count();
229
if (currentPointCount > requiredPointCount) {
230
for( int i = 0; i < currentPointCount-requiredPointCount; ++i ) {
231
delete m_subpaths[0]->front();
232
m_subpaths[0]->pop_front();
235
else if (requiredPointCount > currentPointCount) {
236
for( int i = 0; i < requiredPointCount-currentPointCount; ++i ) {
237
m_subpaths[0]->append( new KoPathPoint( this, QPointF() ) );
242
void KoStarShape::setSize( const QSizeF &newSize )
244
QMatrix matrix(resizeMatrix(newSize));
245
m_zoomX *= matrix.m11();
246
m_zoomY *= matrix.m22();
248
// this transforms the handles
249
KoParameterShape::setSize( newSize );
251
m_center = computeCenter();
254
QPointF KoStarShape::computeCenter() const
256
KoSubpath &points = *m_subpaths[0];
258
QPointF center( 0, 0 );
259
for( uint i = 0; i < m_cornerCount; ++i )
262
center += points[i]->point();
264
center += points[2*i]->point();
266
return center / static_cast<qreal>( m_cornerCount );
269
bool KoStarShape::loadOdf( const KoXmlElement & element, KoShapeLoadingContext & context )
271
bool loadAsCustomShape = false;
273
if( element.localName() == "custom-shape" )
275
QString drawEngine = element.attributeNS( KoXmlNS::draw, "engine", "" );
276
if( drawEngine != "koffice:star" )
278
loadAsCustomShape = true;
280
else if( element.localName() != "regular-polygon" )
285
QPointF loadedPosition = position();
288
m_center = QPointF(50,50);
290
if( ! loadAsCustomShape )
292
QString corners = element.attributeNS( KoXmlNS::draw, "corners", "" );
293
if( ! corners.isEmpty() ) {
294
m_cornerCount = corners.toUInt();
295
// initialize default angles of tip and base
296
m_angles[base] = m_angles[tip] = defaultAngleRadian();
299
m_convex = (element.attributeNS( KoXmlNS::draw, "concave", "false" ) == "false" );
303
m_radius[base] = m_radius[tip];
307
// sharpness is radius of ellipse on which inner polygon points are located
308
// 0% means all polygon points are on a single ellipse
309
// 100% means inner points are located at polygon center point
310
QString sharpness = element.attributeNS( KoXmlNS::draw, "sharpness", "" );
311
if( ! sharpness.isEmpty() && sharpness.right( 1 ) == "%" )
313
float percent = sharpness.left( sharpness.length()-1 ).toFloat();
314
m_radius[base] = m_radius[tip] * (100-percent)/100;
320
QString drawData = element.attributeNS( KoXmlNS::draw, "data" );
321
if( drawData.isEmpty() )
324
QStringList properties = drawData.split( ';' );
325
if( properties.count() == 0 )
328
foreach( const QString &property, properties )
330
QStringList pair = property.split( ':' );
331
if( pair.count() != 2 )
333
if( pair[0] == "corners" )
335
m_cornerCount = pair[1].toInt();
337
else if( pair[0] == "concave" )
339
m_convex = (pair[1] == "false");
341
else if( pair[0] == "baseRoundness" )
343
m_roundness[base] = pair[1].toDouble();
345
else if( pair[0] == "tipRoundness" )
347
m_roundness[tip] = pair[1].toDouble();
349
else if( pair[0] == "baseAngle" )
351
m_angles[base] = pair[1].toDouble();
353
else if( pair[0] == "tipAngle" )
355
m_angles[tip] = pair[1].toDouble();
357
else if( pair[0] == "sharpness" )
359
float percent = pair[1].left( pair[1].length()-1 ).toFloat();
360
m_radius[base] = m_radius[tip] * (100-percent)/100;
366
m_radius[base] = m_radius[tip];
370
updatePath( QSizeF() );
372
// reset transformation
373
setTransformation( QMatrix() );
375
loadOdfAttributes( element, context, OdfAllAttributes );
380
void KoStarShape::saveOdf( KoShapeSavingContext & context ) const
382
if( isParametricShape() )
384
double defaultAngle = defaultAngleRadian();
385
bool hasRoundness = m_roundness[tip] != 0.0f || m_roundness[base] != 0.0f;
386
bool hasAngleOffset = m_angles[base] != defaultAngle || m_angles[tip] != defaultAngle;
387
if( hasRoundness || hasAngleOffset )
389
// draw:regular-polygon has no means of saving roundness
390
// so we save as a custom shape with a specific draw:engine
391
context.xmlWriter().startElement("draw:custom-shape");
392
saveOdfAttributes( context, OdfAllAttributes );
394
// now write the special shape data
395
context.xmlWriter().addAttribute( "draw:engine", "koffice:star" );
396
// create the data attribute
397
QString drawData = QString("corners:%1;").arg( m_cornerCount );
398
drawData += m_convex ? "concave:false;" : "concave:true;";
401
// sharpness is radius of ellipse on which inner polygon points are located
402
// 0% means all polygon points are on a single ellipse
403
// 100% means inner points are located at polygon center point
404
qreal percent = (m_radius[tip]-m_radius[base]) / m_radius[tip] * 100.0;
405
drawData += QString("sharpness:%1%;").arg( percent );
407
if( m_roundness[base] != 0.0f )
409
drawData += QString("baseRoundness:%1;").arg( m_roundness[base] );
411
if( m_roundness[tip] != 0.0f )
413
drawData += QString("tipRoundness:%1;").arg( m_roundness[tip] );
415
drawData += QString("baseAngle:%1;").arg( m_angles[base] );
416
drawData += QString("tipAngle:%1;").arg( m_angles[tip] );
418
context.xmlWriter().addAttribute( "draw:data", drawData );
420
// write a enhanced geometry element for compatibility with other applications
421
context.xmlWriter().startElement("draw:enhanced-geometry");
422
context.xmlWriter().addAttribute("draw:enhanced-path", toString( transformation() ) );
423
context.xmlWriter().endElement(); // draw:enhanced-geometry
425
saveOdfCommonChildElements( context );
426
context.xmlWriter().endElement(); // draw:custom-shape
430
context.xmlWriter().startElement("draw:regular-polygon");
431
saveOdfAttributes( context, OdfAllAttributes );
432
context.xmlWriter().addAttribute( "draw:corners", m_cornerCount );
433
context.xmlWriter().addAttribute( "draw:concave", m_convex ? "false" : "true" );
436
// sharpness is radius of ellipse on which inner polygon points are located
437
// 0% means all polygon points are on a single ellipse
438
// 100% means inner points are located at polygon center point
439
qreal percent = (m_radius[tip]-m_radius[base]) / m_radius[tip] * 100.0;
440
context.xmlWriter().addAttribute( "draw:sharpness", QString("%1%" ).arg( percent ) );
442
saveOdfCommonChildElements( context );
443
context.xmlWriter().endElement();
448
KoPathShape::saveOdf( context );
452
QString KoStarShape::pathShapeId() const
454
return KoStarShapeId;
457
double KoStarShape::defaultAngleRadian() const
459
qreal radianStep = M_PI / static_cast<qreal>(m_cornerCount);
461
return M_PI_2-2*radianStep;