2
* wbitems.cpp - the item classes for the SVG WB
3
* Copyright (C) 2006 Joonas Govenius
5
* This program is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU General Public License
7
* as published by the Free Software Foundation; either version 2
8
* of the License, or (at your option) any later version.
10
* This program 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
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this library; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25
#define M_PI 3.14159265358979323846
32
Edit::Edit(Type _type, const QDomElement &_xml) {
34
xml = _xml.cloneNode().toElement();
37
Edit::Edit(Type _type, const QString &_target, const QDomElement &edit, const QString &_oldValue) {
40
xml = edit.cloneNode().toElement();
41
if(_type == AttributeEdit)
43
else if(_type == AttributeEdit)
44
oldParent = _oldValue;
47
Edit::Edit(const QString &_target, const QDomElement &edit, QDomNodeList _oldContent) {
48
type = Edit::ContentEdit;
50
xml = edit.cloneNode().toElement();
51
oldContent = _oldContent;
58
EditUndo::EditUndo(const int &_version, const Edit &edit) {
61
if(type == Edit::AttributeEdit) {
62
attribute = edit.xml.attribute("name");
63
oldValue = edit.oldValue;
64
} else if(type == Edit::ParentEdit) {
65
oldParent = edit.oldParent;
66
} else if(type == Edit::ContentEdit) {
67
oldContent = edit.oldContent;
71
EditUndo::EditUndo(const int &_version, const QString &_attribute, const QString &_oldValue) {
72
type = Edit::AttributeEdit;
74
attribute = _attribute;
78
EditUndo::EditUndo(const int &_version, const QString &_oldParent) {
79
type = Edit::ParentEdit;
81
oldParent = _oldParent;
84
EditUndo::EditUndo(const int &_version, QDomNodeList _oldContent) {
85
type = Edit::ContentEdit;
87
oldContent = _oldContent;
94
WbItemMenu::WbItemMenu(QWidget* parent) : QMenu(parent) {
95
connect(this, SIGNAL(aboutToHide()), SLOT(destructSelf()));
98
WbItemMenu::~WbItemMenu() {
99
foreach(QActionGroup* g, groups_) {
100
foreach(QAction* a, g->actions()) {
107
void WbItemMenu::addActionGroup(QActionGroup* g) {
109
addActions(g->actions());
112
void WbItemMenu::destructSelf() {
121
WbItem::WbItem(const QString &id) {
127
QString WbItem::parentWbItem() {
131
void WbItem::setParentWbItem(const QString &newParent, bool emitChanges) {
132
if(newParent != parent_) {
133
if(newParent.isEmpty()) {
134
if(parent_ != "root") {
135
graphicsItem()->setParentItem(0);
137
emit parentChanged(id(), "root", parent_);
142
WbScene* wbscene = qobject_cast<WbScene*>(graphicsItem()->scene());
144
WbItem* p = wbscene->findWbItem(newParent);
145
if(p && p->graphicsItem())
146
graphicsItem()->setParentItem(p->graphicsItem());
150
emit parentChanged(id(), newParent, parent_);
156
QString WbItem::id() {
160
qreal WbItem::index() {
164
void WbItem::setIndex(qreal index, bool emitChanges) {
165
if(index_ != index) {
167
emit indexChanged(id(), index - index_);
171
if(index_ - static_cast<int>(index_))
172
z = QString("%1").arg(index_);
174
z = QString("%1.").arg(index_);
176
for(int i=0; i < id_.length(); i++) {
177
c = id_.at(i).unicode();
178
//TODO: what should be the minumum width? at least 3 but can a jid contain characters that have a value beyond 999
179
z.append(QString("%1").arg(QString("%1").arg(c), 4, QLatin1Char('0')));
181
// qDebug() << "z: " << z;
182
graphicsItem()->setZValue(z.toDouble());
187
WbItemMenu* WbItem::constructContextMenu() {
188
WbItemMenu* menu = new WbItemMenu(0);
189
WbScene* wbscene = qobject_cast<WbScene*>(graphicsItem()->scene());
191
// Add the default actions
192
QActionGroup* group = new QActionGroup(this);
193
QPixmap pixmap(2, 2);
194
pixmap.fill(QColor(Qt::black));
195
QAction* qaction = new QAction(QIcon(pixmap), tr("Thin stroke"), group);
196
qaction->setData(QVariant(1));
197
pixmap = QPixmap(6, 6);
198
pixmap.fill(QColor(Qt::black));
199
qaction = new QAction(QIcon(pixmap), tr("Medium stroke"), group);
200
qaction->setData(QVariant(3));
201
pixmap = QPixmap(12, 12);
202
pixmap.fill(QColor(Qt::black));
203
qaction = new QAction(QIcon(pixmap), tr("Thick stroke"), group);
204
qaction->setData(QVariant(6));
205
connect(group, SIGNAL(triggered(QAction*)), wbscene, SLOT(setStrokeWidth(QAction*)));
206
menu->addActionGroup(group);
208
menu->addSeparator();
209
group = new QActionGroup(this);
210
pixmap = QPixmap(16, 16);
211
pixmap.fill(QColor(Qt::darkCyan));
212
qaction = new QAction(QIcon(pixmap), tr("Change stroke color"), group);
213
connect(qaction, SIGNAL(triggered()), wbscene, SLOT(setStrokeColor()));
214
pixmap.fill(QColor(Qt::darkYellow));
215
qaction = new QAction(QIcon(pixmap), tr("Change fill color"), group);
216
connect(qaction, SIGNAL(triggered()), wbscene, SLOT(setFillColor()));
217
menu->addActionGroup(group);
219
menu->addSeparator();
220
group = new QActionGroup(this);
221
IconAction* action = new IconAction(tr("Bring forward"), "psi/bringForwards", tr("Bring forward"), 0, group);
222
connect(action, SIGNAL(triggered()), wbscene, SLOT(bringForward()));
223
action = new IconAction(tr("Bring to front"), "psi/bringToFront", tr("Bring to front"), 0, group);
224
connect(action, SIGNAL(triggered()), wbscene, SLOT(bringToFront()));
225
action = new IconAction(tr("Send backwards"), "psi/sendBackwards", tr("Send backwards"), 0, group);
226
connect(action, SIGNAL(triggered()), wbscene, SLOT(sendBackwards()));
227
action = new IconAction(tr("Send to back"), "psi/sendToBack", tr("Send to back"), 0, group);
228
connect(action, SIGNAL(triggered()), wbscene, SLOT(sendToBack()));
229
menu->addActionGroup(group);
231
menu->addSeparator();
232
group = new QActionGroup(this);
233
action = new IconAction(tr("Group"), "psi/group", tr("Group"), 0, group);
234
connect(action, SIGNAL(triggered()), wbscene, SLOT(group()));
235
action = new IconAction(tr("Ungroup"), "psi/ungroup", tr("Ungroup"), 0, group);
236
connect(action, SIGNAL(triggered()), wbscene, SLOT(ungroup()));
237
menu->addActionGroup(group);
242
void WbItem::setStrokeColor(QColor color) {
244
QDomElement _svg = svg();
245
_svg.setAttribute("stroke", QString("rgb(%1,%2,%3)").arg(color.red()).arg(color.green()).arg(color.blue()));
246
if(color.alphaF() != 1 || _svg.hasAttribute("stroke-opacity"))
247
_svg.setAttribute("stroke-opacity", QString("%1").arg(color.alphaF()));
248
parseSvg(_svg, true);
252
void WbItem::setFillColor(QColor color) {
254
QDomElement _svg = svg();
255
if(color == Qt::transparent) {
256
_svg.removeAttribute("fill");
257
_svg.removeAttribute("fill-opacity");
259
_svg.setAttribute("fill", QString("rgb(%1,%2,%3)").arg(color.red()).arg(color.green()).arg(color.blue()));
260
if(color.alphaF() != 1 || _svg.hasAttribute("fill-opacity"))
261
_svg.setAttribute("fill-opacity", QString("%1").arg(color.alphaF()));
263
parseSvg(_svg, true);
267
void WbItem::setStrokeWidth(int width) {
269
QDomElement _svg = svg();
270
_svg.setAttribute("stroke-width", QString("%1").arg(width));
271
parseSvg(_svg, true);
275
QDomElement WbItem::svg() {
277
QDomElement _svg = doc.createElement("item");
278
foreach(QString a, attributes.keys()) {
279
if(!attributes[a].isNull())
280
_svg.setAttribute(a, attributes[a]);
282
attributes.remove(a);
286
_svg.setTagName("root");
289
_svg.setTagName("path");
292
_svg.setTagName("ellipse");
295
_svg.setTagName("circle");
298
_svg.setTagName("rect");
301
_svg.setTagName("line");
304
_svg.setTagName("polyline");
307
_svg.setTagName("polygon");
310
_svg.setTagName("text");
313
_svg.setTagName("image");
316
_svg.setTagName("g");
322
QList<QString> WbItem::parseSvg(QDomElement &_svg, bool emitChanges) {
323
QList<QString> changed;
324
// First process changes to existing elements
325
foreach(QString a, attributes.keys()) {
326
if(attributes[a] != _svg.attribute(a)) {
327
if(a.startsWith("stroke") || a == "opacity")
328
changed.append("pen");
332
if(_svg.hasAttribute(a)) {
333
if(attributes[a] != _svg.attribute(a) && emitChanges)
334
emit attributeChanged(id(), a, _svg.attribute(a), attributes[a]);
335
attributes[a] = _svg.attribute(a);
336
_svg.removeAttribute(a);
339
emit attributeChanged(id(), a, QString(), attributes[a]);
340
attributes.remove(a);
344
for(uint i = 0; i < _svg.attributes().length(); i++) {
345
a = _svg.attributes().item(i).cloneNode();
347
emit attributeChanged(id(), a.nodeName(), a.nodeValue(), QString());
348
attributes[a.nodeName()] = a.nodeValue();
349
if(a.nodeName().startsWith("stroke") || a.nodeName() == "opacity")
350
changed.append("pen");
352
changed.append(a.nodeName());
356
// 'x' & 'y' & 'cx' & 'cy'
357
if(changed.contains("x") || changed.contains("y") || changed.contains("cx") || changed.contains("cy")) {
360
if(changed.contains("x") || changed.contains("y")) {
361
xPos = attributes["x"].toDouble(&xOk);
362
yPos = attributes["y"].toDouble(&yOk);
364
xPos = attributes["cx"].toDouble(&xOk);
365
yPos = attributes["cy"].toDouble(&yOk);
368
graphicsItem()->setPos(QPointF(xPos, yPos));
369
// graphicsItem()->setPos(graphicsItem()->mapToParent(graphicsItem()->mapFromScene(QPointF(xPos, yPos))));
373
if(changed.contains("fill")) {
375
if(!attributes["fill"].isEmpty())
376
brush = QBrush(constructColor(attributes["fill"], attributes["fill-opacity"]));
377
QAbstractGraphicsShapeItem* s = qgraphicsitem_cast<QAbstractGraphicsShapeItem*>(graphicsItem());
379
// Only set pens for items that implement it
384
// 'stroke' & stroke-dasharray & stroke-dashoffset & stroke-dashoffset & stroke-linecap & stroke-linejoin & stroke-miterlimit & stroke-opacity &
385
if(changed.contains("pen")) {
387
pen = parseQPen(attributes["stroke"],
388
attributes["stroke-dasharray"],
389
attributes["stroke-dashoffset"],
390
attributes["stroke-linecap"],
391
attributes["stroke-linejoin"],
392
attributes["stroke-miterlimit"],
393
attributes["stroke-opacity"],
394
attributes["opacity"],
395
attributes["stroke-width"]);
396
QAbstractGraphicsShapeItem* s = qgraphicsitem_cast<QAbstractGraphicsShapeItem*>(graphicsItem());
398
// Only set pens for items that implement it
400
}/* else if(typerange == 101) {
401
QGraphicsTextItem* s = dynamic_cast<QGraphicsTextItem*>(graphicsItem());
406
if(changed.contains("transform"))
407
graphicsItem()->setMatrix(parseTransformationMatrix(attributes["transform"]));
409
graphicsItem()->update();
414
void WbItem::regenerateTransform() {
417
QMatrix m = graphicsItem()->matrix();
418
QString old = attributes["transform"];
420
attributes["transform"] = QString();
422
attributes["transform"] = QString("matrix(%1 %2 %3 %4 %5 %6)").arg(m.m11()).arg(m.m12()).arg(m.m21()).arg(m.m22()).arg(m.dx()).arg(m.dy());
423
if(attributes["transform"] != old)
424
emit attributeChanged(id(), "transform", attributes["transform"], old);
427
QPointF WbItem::center() {
429
// Determine the center of the item in item coordinates before transformation
430
QRectF r = graphicsItem()->boundingRect();
431
QPointF c(r.x() + r.width()/2, r.y() + r.height()/2);
432
// qDebug() << QString("center: %1 + %2 %3 + %4").arg(r.x()).arg(r.width()/2).arg(r.y()).arg(r.height()/2);
433
// return the center with transformation applied
434
return graphicsItem()->matrix().map(c);
439
void WbItem::handleMouseMoveEvent(QGraphicsSceneMouseEvent * event) {
441
if(graphicsItem() && graphicsItem()->isSelected()) {
442
if(event->modifiers() == Qt::KeyboardModifiers(Qt::ControlModifier)) {
444
// Translate each selected item
445
WbScene* wbscene = qobject_cast<WbScene*>(graphicsItem()->scene());
447
foreach(QGraphicsItem* graphicsitem, graphicsItem()->scene()->selectedItems()) {
448
if (!graphicsitem->parentItem() || !graphicsitem->parentItem()->isSelected()) {
449
QPointF d = graphicsitem->mapFromScene(event->scenePos()) - graphicsitem->mapFromScene(event->lastScenePos());
450
graphicsitem->translate(d.x(), d.y());
451
// Regenerate the SVG transformation matrix later
452
wbscene->queueTransformationChange(wbscene->findWbItem(graphicsitem));
456
} else if(event->modifiers() == Qt::KeyboardModifiers(Qt::ControlModifier + Qt::AltModifier)) {
457
// Ctrl + Alt: Rotate
458
// get center coordinates in item coordinates
459
QPointF c = center();
460
QMatrix translation, delta;
461
// translates the the item's center to the origin of the item coordinates
462
translation.translate(-c.x(), -c.y());
463
// Rotate. Determine the direction relative to the center
464
// Divide the sum by two to reduce the "speed" of rotation
465
QPointF difference = event->scenePos() - event->lastScenePos();
466
// qDebug() << QString("d: %1 %2 = %3 %4 + %5 %6").arg(difference.x()).arg(difference.y()).arg(event->scenePos().x()).arg(event->scenePos().y()).arg(event->lastScenePos().x()).arg(event->lastScenePos().y());
467
QPointF p = event->scenePos();
468
QPointF sceneCenter = graphicsItem()->mapToScene(c);
469
if(p.x() >= sceneCenter.x() && p.y() >= sceneCenter.y()) {
470
delta.rotate((-difference.x() + difference.y()) / 2);
471
} else if(p.x() < sceneCenter.x() && p.y() >= sceneCenter.y()) {
472
delta.rotate((-difference.x() - difference.y()) / 2);
473
} else if(p.x() < sceneCenter.x() && p.y() < sceneCenter.y()) {
474
delta.rotate((difference.x() - difference.y()) / 2);
475
} else if(p.x() >= sceneCenter.x() && p.y() < sceneCenter.y()) {
476
delta.rotate((difference.x() + difference.y()) / 2);
479
graphicsItem()->setMatrix(graphicsItem()->matrix() * translation * delta * translation.inverted());
480
// Regenerate the SVG transformation matrix later
481
WbScene* wbscene = qobject_cast<WbScene*>(graphicsItem()->scene());
483
wbscene->queueTransformationChange(this);
484
} else if(event->modifiers() == Qt::KeyboardModifiers(Qt::ControlModifier + Qt::ShiftModifier)) {
485
// Ctrl + Shift: Scale
486
// get center coordinates in item coordinates
487
QPointF c = center();
488
QMatrix translation, delta;
489
// translates the the item's center to the origin of the item coordinates
490
translation.translate(-c.x(), -c.y());
492
// Moving mouse up enlarges, down shrinks, y axis.
493
// Moving mouse right enlarges, left shrinks, x axis.
494
QPointF difference = event->scenePos() - event->lastScenePos();
495
// Note: the y axis points downwards in scene coordinates
496
delta.scale(1 + difference.x()/50, 1 - difference.y()/50);
498
graphicsItem()->setMatrix(graphicsItem()->matrix() * translation * delta * translation.inverted());
499
// Regenerate the SVG transformation matrix later
500
WbScene* wbscene = qobject_cast<WbScene*>(graphicsItem()->scene());
502
wbscene->queueTransformationChange(this);
507
void WbItem::handleMouseReleaseEvent(QGraphicsSceneMouseEvent * event) {
508
WbScene * wbscene = qobject_cast<WbScene*>(graphicsItem()->scene());
509
wbscene->regenerateTransformations();
517
WbUnknown::WbUnknown(QDomElement &_svg, const QString &id, const qreal &index, const QString &parent, QGraphicsScene *) : WbItem(id) {
518
setParentWbItem(parent);
523
QDomElement WbUnknown::svg() {
524
return svgElement_.cloneNode().toElement();
527
QList<QString> WbUnknown::parseSvg(QDomElement &_svg, bool) {
528
svgElement_ = _svg.cloneNode().toElement();
529
return QList<QString>();
532
WbItem* WbUnknown::clone() {
533
QDomElement _svg = svg();
534
WbItem* cloned = new WbUnknown(_svg, id(), index(), parentWbItem(), 0);
535
cloned->undos = undos;
543
WbRoot::WbRoot(QGraphicsScene* scene) : WbItem("root") {
547
QList<QString> WbRoot::parseSvg(QDomElement &_svg, bool emitChanges) {
548
QList<QString> changed = WbItem::parseSvg(_svg, emitChanges);
549
if(changed.contains("viewBox")) {
550
QString::const_iterator itr = attributes["viewBox"].constBegin();
551
QList<qreal> points = WbItem::parseNumbersList(itr);
552
if(points.size() == 4)
553
scene_->setSceneRect(points.at(0), points.at(1), points.at(2), points.at(3));
558
WbItem* WbRoot::clone() {
559
WbItem* cloned = new WbRoot(scene_);
560
QDomElement _svg = svg();
561
cloned->parseSvg(_svg, false);
562
cloned->undos = undos;
570
WbPath::WbPath(QDomElement &_svg, const QString &id, const qreal &index, const QString &parent, QGraphicsScene * scene) : QGraphicsPathItem(0, scene), WbItem(id) {
571
setParentWbItem(parent);
574
setFlag(QGraphicsItem::ItemIsSelectable);
575
setFlag(QGraphicsItem::ItemIsMovable);
576
setAcceptDrops(true);
579
QPainterPath WbPath::shape() const {
580
QPainterPath qpath = path();
581
qpath.lineTo(qpath.currentPosition() + QPointF(2, 0));
582
qpath.lineTo(qpath.currentPosition() + QPointF(-2, 2));
583
qpath.closeSubpath();
584
qpath.lineTo(qpath.currentPosition() + QPointF(2, 0));
585
qpath.lineTo(qpath.currentPosition() + QPointF(-2, 2));
589
QList<QString> WbPath::parseSvg(QDomElement &_svg, bool emitChanges) {
590
QList<QString> changed = WbItem::parseSvg(_svg, emitChanges);
592
// 'd' && 'fill-rule'
593
if(changed.contains("d")) {
594
parsePathDataFast(attributes["d"], qpath);
596
qpath = this->path();
597
if(changed.contains("fill-rule")) {
598
if (attributes["fill-rule"] == QLatin1String("nonzero"))
599
qpath.setFillRule(Qt::WindingFill);
601
qpath.setFillRule(Qt::OddEvenFill);
603
qpath.setFillRule(this->path().fillRule());
608
WbItem* WbPath::clone() {
609
QDomElement _svg = svg();
610
WbItem* cloned = new WbPath(_svg, id(), index(), parentWbItem(), 0);
611
cloned->undos = undos;
615
void WbPath::lineTo(const QPointF &newPoint) {
616
QPainterPath qpath = path();
617
qpath.lineTo(newPoint);
619
QString old = attributes["d"];
620
attributes["d"] += QString("L%1,%2").arg(newPoint.x()).arg(newPoint.y());
621
emit attributeChanged(id(), "d", attributes["d"], old);
624
void WbPath::quadTo(const QPointF &controlPoint, const QPointF &newPoint) {
625
QPainterPath qpath = path();
626
qpath.quadTo(controlPoint, newPoint);
628
QString old = attributes["d"];
629
attributes["d"] += QString("Q%1,%2 %3,%4").arg(controlPoint.x()).arg(controlPoint.y()).arg(newPoint.x()).arg(newPoint.y());
630
emit attributeChanged(id(), "d", attributes["d"], old);
633
void WbPath::contextMenuEvent (QGraphicsSceneContextMenuEvent * event) {
634
constructContextMenu()->exec(event->screenPos());
635
regenerateTransform();
638
void WbPath::mouseMoveEvent(QGraphicsSceneMouseEvent * event) {
639
handleMouseMoveEvent(event);
640
if(!event->isAccepted())
641
QGraphicsPathItem::mouseMoveEvent(event);
644
void WbPath::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) {
645
handleMouseReleaseEvent(event);
646
if(!event->isAccepted())
647
QGraphicsPathItem::mouseReleaseEvent(event);
654
WbEllipse::WbEllipse(QDomElement &_svg, const QString &id, const qreal &index, const QString &parent, QGraphicsScene * scene) : QGraphicsEllipseItem(0, scene), WbItem(id) {
655
setParentWbItem(parent);
658
setFlag(QGraphicsItem::ItemIsSelectable);
659
setFlag(QGraphicsItem::ItemIsMovable);
660
setAcceptDrops(true);
663
QList<QString> WbEllipse::parseSvg(QDomElement &_svg, bool emitChanges) {
664
QList<QString> changed = WbItem::parseSvg(_svg, emitChanges);
666
if(changed.contains("rx") || changed.contains("ry")) {
668
qreal rx = attributes["rx"].toDouble(&okX);
669
qreal ry = attributes["ry"].toDouble(&okY);
670
// FIXME: I think it's an issue with Qt, but with very small value I sometime get when setRect()
671
// QPainterPath::lineTo: Adding point where x or y is NaN, results are undefined
672
// QPainterPath::cubicTo: Adding point where x or y is NaN, results are undefined
673
// That's why i'm putting those minumum values in
679
setRect(-rx, -ry, 2 * rx, 2 * ry);
686
WbItem* WbEllipse::clone() {
687
QDomElement _svg = svg();
688
WbItem* cloned = new WbEllipse(_svg, id(), index(), parentWbItem(), 0);
689
cloned->undos = undos;
693
void WbEllipse::contextMenuEvent (QGraphicsSceneContextMenuEvent * event) {
694
WbItemMenu* menu = constructContextMenu();
695
menu->exec(event->screenPos());
699
void WbEllipse::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
700
handleMouseMoveEvent(event);
701
if(!event->isAccepted())
702
QGraphicsEllipseItem::mouseMoveEvent(event);
705
void WbEllipse::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
706
handleMouseReleaseEvent(event);
707
if(!event->isAccepted())
708
QGraphicsEllipseItem::mouseReleaseEvent(event);
715
WbCircle::WbCircle(QDomElement &_svg, const QString &id, const qreal &index, const QString &parent, QGraphicsScene * scene) : WbEllipse(_svg, id, index, parent, scene) {
716
QDomElement _svg_ = svg();
717
attributes.remove("r");
721
QList<QString> WbCircle::parseSvg(QDomElement &_svg, bool emitChanges) {
722
QList<QString> changed = WbItem::parseSvg(_svg, emitChanges);
724
if(changed.contains("r")) {
726
qreal r = attributes["r"].toDouble(&ok);
728
setRect(-r, -r, 2 * r, 2 * r);
735
WbItem* WbCircle::clone() {
736
QDomElement _svg = svg();
737
WbItem* cloned = new WbCircle(_svg, id(), index(), parentWbItem(), 0);
738
cloned->undos = undos;
746
WbRectangle::WbRectangle(QDomElement &_svg, const QString &id, const qreal &index, const QString &parent, QGraphicsScene * scene) : QGraphicsRectItem(0, scene), WbItem(id) {
747
setParentWbItem(parent);
750
setFlag(QGraphicsItem::ItemIsSelectable);
751
setFlag(QGraphicsItem::ItemIsMovable);
752
setAcceptDrops(true);
755
QList<QString> WbRectangle::parseSvg(QDomElement &_svg, bool emitChanges) {
756
QList<QString> changed = WbItem::parseSvg(_svg, emitChanges);
757
// TODO: support rx and ry
758
// 'width' & 'height'
759
if(changed.contains("width") || changed.contains("height")) {
761
qreal width = attributes["width"].toDouble(&okW);
762
qreal height = attributes["height"].toDouble(&okH);
763
if(okW && okH && width > 0 && height > 0)
764
setRect(-width/2, -height/2, width, height);
771
WbItem* WbRectangle::clone() {
772
QDomElement _svg = svg();
773
WbItem* cloned = new WbRectangle(_svg, id(), index(), parentWbItem(), 0);
774
cloned->undos = undos;
778
void WbRectangle::contextMenuEvent (QGraphicsSceneContextMenuEvent * event) {
779
WbItemMenu* menu = constructContextMenu();
780
menu->exec(event->screenPos());
784
void WbRectangle::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
785
handleMouseMoveEvent(event);
786
if(!event->isAccepted())
787
QGraphicsRectItem::mouseMoveEvent(event);
790
void WbRectangle::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
791
handleMouseReleaseEvent(event);
792
if(!event->isAccepted())
793
QGraphicsRectItem::mouseReleaseEvent(event);
800
WbLine::WbLine(QDomElement &_svg, const QString &id, const qreal &index, const QString &parent, QGraphicsScene * scene) : QGraphicsPathItem(0, scene), WbItem(id) {
801
setParentWbItem(parent);
804
setFlag(QGraphicsItem::ItemIsSelectable);
805
setFlag(QGraphicsItem::ItemIsMovable);
806
setAcceptDrops(true);
809
QPainterPath WbLine::shape() const {
810
QPainterPath qpath = path();
811
qpath.lineTo(qpath.currentPosition() + QPointF(2, 0));
812
qpath.lineTo(qpath.currentPosition() + QPointF(-2, 2));
813
qpath.closeSubpath();
814
qpath.lineTo(qpath.currentPosition() + QPointF(2, 0));
815
qpath.lineTo(qpath.currentPosition() + QPointF(-2, 2));
819
QList<QString> WbLine::parseSvg(QDomElement &_svg, bool emitChanges) {
820
QList<QString> changed= WbItem::parseSvg(_svg, emitChanges);
821
// 'x1' & 'y1' & 'x2' & 'y2'
822
if(changed.contains("x1") || changed.contains("y1") || changed.contains("x2") || changed.contains("y2")) {
823
bool okX1, okY1, okX2, okY2;
824
qreal x1 = attributes["x1"].toDouble(&okX1);
825
qreal y1 = attributes["y1"].toDouble(&okY1);
826
qreal x2 = attributes["x2"].toDouble(&okX2);
827
qreal y2 = attributes["y2"].toDouble(&okY2);
828
if(okX1 && okY1 && okX2 && okY2) {
829
QPainterPath qpath(QPointF(x1, y1));
830
qpath.lineTo(QPointF(x2, y2));
833
setPath(QPainterPath());
838
WbItem* WbLine::clone() {
839
QDomElement _svg = svg();
840
WbItem* cloned = new WbLine(_svg, id(), index(), parentWbItem(), 0);
841
cloned->undos = undos;
845
void WbLine::contextMenuEvent (QGraphicsSceneContextMenuEvent * event) {
846
constructContextMenu()->exec(event->screenPos());
847
regenerateTransform();
850
void WbLine::mouseMoveEvent(QGraphicsSceneMouseEvent * event) {
851
handleMouseMoveEvent(event);
852
if(!event->isAccepted())
853
QGraphicsPathItem::mouseMoveEvent(event);
856
void WbLine::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) {
857
handleMouseReleaseEvent(event);
858
if(!event->isAccepted())
859
QGraphicsPathItem::mouseReleaseEvent(event);
866
WbPolyline::WbPolyline(QDomElement &_svg, const QString &id, const qreal &index, const QString &parent, QGraphicsScene * scene) : QGraphicsPathItem(0, scene), WbItem(id) {
867
setParentWbItem(parent);
870
setFlag(QGraphicsItem::ItemIsSelectable);
871
setFlag(QGraphicsItem::ItemIsMovable);
872
setAcceptDrops(true);
875
QPainterPath WbPolyline::shape() const {
876
QPainterPath qpath = path();
877
qpath.lineTo(qpath.currentPosition() + QPointF(2, 0));
878
qpath.lineTo(qpath.currentPosition() + QPointF(-2, 2));
879
qpath.closeSubpath();
880
qpath.lineTo(qpath.currentPosition() + QPointF(2, 0));
881
qpath.lineTo(qpath.currentPosition() + QPointF(-2, 2));
885
QList<QString> WbPolyline::parseSvg(QDomElement &_svg, bool emitChanges) {
886
QList<QString> changed= WbItem::parseSvg(_svg, emitChanges);
888
if(changed.contains("points")) {
889
QString::const_iterator itr = attributes["points"].constBegin();
890
QList<qreal> points = WbItem::parseNumbersList(itr);
891
if(points.size() > 1) {
892
QPainterPath qpath(QPointF(points.takeFirst(), points.takeFirst()));
893
while(points.size() > 1) {
894
qpath.lineTo(QPointF(points.takeFirst(), points.takeFirst()));
898
setPath(QPainterPath());
903
WbItem* WbPolyline::clone() {
904
QDomElement _svg = svg();
905
WbItem* cloned = new WbPolyline(_svg, id(), index(), parentWbItem(), 0);
906
cloned->undos = undos;
910
void WbPolyline::contextMenuEvent (QGraphicsSceneContextMenuEvent * event) {
911
constructContextMenu()->exec(event->screenPos());
912
regenerateTransform();
915
void WbPolyline::mouseMoveEvent(QGraphicsSceneMouseEvent * event) {
916
handleMouseMoveEvent(event);
917
if(!event->isAccepted())
918
QGraphicsPathItem::mouseMoveEvent(event);
921
void WbPolyline::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) {
922
handleMouseReleaseEvent(event);
923
if(!event->isAccepted())
924
QGraphicsPathItem::mouseReleaseEvent(event);
931
WbPolygon::WbPolygon(QDomElement &_svg, const QString &id, const qreal &index, const QString &parent, QGraphicsScene * scene) : WbPolyline(_svg, id, index, parent, scene) {
932
QDomElement _svg_ = svg();
933
attributes.remove("points");
937
QList<QString> WbPolygon::parseSvg(QDomElement &_svg, bool emitChanges) {
938
QList<QString> changed = WbPolyline::parseSvg(_svg, emitChanges);
940
if(changed.contains("points")) {
941
QPainterPath qpath = path();
942
qpath.closeSubpath();
948
WbItem* WbPolygon::clone() {
949
QDomElement _svg = svg();
950
WbItem* cloned = new WbCircle(_svg, id(), index(), parentWbItem(), 0);
951
cloned->undos = undos;
959
WbGraphicsTextItem::WbGraphicsTextItem(WbText* wbtext, QGraphicsScene * scene) : QGraphicsTextItem(0, scene) {
961
setFlag(QGraphicsItem::ItemIsSelectable);
962
setFlag(QGraphicsItem::ItemIsMovable);
963
setTextInteractionFlags(Qt::TextInteractionFlags(Qt::NoTextInteraction));
964
setAcceptDrops(true);
967
void WbGraphicsTextItem::contextMenuEvent (QGraphicsSceneContextMenuEvent * event) {
969
QGraphicsTextItem::contextMenuEvent(event);
971
WbItemMenu* menu = wbText_->constructContextMenu();
972
// Add the default actions
973
menu->addSeparator();
974
QActionGroup* group = new QActionGroup(menu);
975
IconAction* action = new IconAction(QObject::tr("Font..."), "psi/compact", QObject::tr("Font..."), 0, group);
976
QObject::connect(action, SIGNAL(triggered()), wbText_, SLOT(setFont()));
977
menu->addActionGroup(group);
979
menu->exec(event->screenPos());
984
void WbGraphicsTextItem::focusOutEvent (QFocusEvent *event) {
985
QGraphicsTextItem::focusOutEvent(event);
986
wbText_->checkTextChanges();
987
setTextInteractionFlags(Qt::TextInteractionFlags(Qt::NoTextInteraction));
988
// setFlag(QGraphicsItem::ItemIsFocusable, false);
991
void WbGraphicsTextItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) {
992
if(event->modifiers() == Qt::KeyboardModifiers(Qt::NoModifier)) {
993
setTextInteractionFlags(Qt::TextInteractionFlags(Qt::TextEditorInteraction));
994
setFlag(QGraphicsItem::ItemIsFocusable);
996
QGraphicsTextItem::mouseDoubleClickEvent(event);
999
void WbGraphicsTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
1000
wbText_->handleMouseMoveEvent(event);
1001
if(!event->isAccepted())
1002
QGraphicsTextItem::mouseMoveEvent(event);
1005
void WbGraphicsTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
1006
wbText_->handleMouseReleaseEvent(event);
1007
if(!event->isAccepted())
1008
QGraphicsTextItem::mouseReleaseEvent(event);
1015
WbText::WbText(QDomElement &_svg, const QString &id, const qreal &index, const QString &parent, QGraphicsScene * scene) : WbItem(id) {
1016
graphicsitem_ = new WbGraphicsTextItem(this, scene);
1017
setParentWbItem(parent);
1023
graphicsitem_->deleteLater();
1026
QDomElement WbText::svg() {
1027
QDomElement _svg = WbItem::svg();
1029
_svg.appendChild(d.createTextNode(text_));
1033
QList<QString> WbText::parseSvg(QDomElement &_svg, bool emitChanges) {
1034
QList<QString> changed = WbItem::parseSvg(_svg, emitChanges);
1035
QFont _font = graphicsitem_->font();
1037
if(changed.contains("font-family"))
1038
_font.setFamily(attributes["font-family"]);
1040
if(changed.contains("font-size"))
1041
_font.setPointSize(attributes["font-size"].toInt());
1043
if(changed.contains("font-style")) {
1044
if(attributes["font-style"] == "normal")
1045
_font.setStyle(QFont::StyleNormal);
1046
else if(attributes["font-style"] == "italic")
1047
_font.setStyle(QFont::StyleItalic);
1048
else if(attributes["font-style"] == "oblique")
1049
_font.setStyle(QFont::StyleOblique);
1052
if(changed.contains("font-weight"))
1053
_font.setWeight(((attributes["font-size"].toInt() - 100) / 800) * 99);
1054
if(graphicsitem_->font() != _font)
1055
graphicsitem_->setFont(_font);
1057
QString t = _svg.text();
1061
QDomElement oldSvg = d.createElement("e");
1062
oldSvg.appendChild(d.createTextNode(text_));
1063
emit contentChanged(id(), _svg.cloneNode().childNodes(), oldSvg.childNodes());
1064
graphicsitem_->adjustSize();
1067
graphicsitem_->setPlainText(t);
1073
WbItem* WbText::clone() {
1074
QDomElement _svg = svg();
1075
WbItem* cloned = new WbText(_svg, id(), index(), parentWbItem(), 0);
1076
cloned->undos = undos;
1080
void WbText::checkTextChanges() {
1081
if(graphicsitem_->toPlainText() != text_) {
1083
QDomElement _svg = d.createElement("e");
1084
_svg.appendChild(d.createTextNode(graphicsitem_->toPlainText()));
1085
QDomElement oldSvg = d.createElement("e");
1086
oldSvg.appendChild(d.createTextNode(text_));
1087
emit contentChanged(id(), _svg.cloneNode().childNodes(), oldSvg.childNodes());
1088
text_ = graphicsitem_->toPlainText();
1089
graphicsitem_->adjustSize();
1093
void WbText::setFont() {
1095
QFont _font = QFontDialog::getFont(&ok, graphicsitem_->font());
1097
// Not supported features
1098
_font.setUnderline(false);
1099
_font.setOverline(false);
1100
_font.setStrikeOut(false);
1101
_font.setFixedPitch(false);
1102
// end: Not supported features
1103
if(_font != graphicsitem_->font()) {
1104
if(_font.family() != graphicsitem_->font().family())
1105
emit attributeChanged(id(), "font-family", _font.family(), graphicsitem_->font().family());
1106
if(_font.pointSize() != graphicsitem_->font().pointSize())
1107
emit attributeChanged(id(), "font-size", QString("%1").arg(_font.pointSize()), QString("%1").arg(graphicsitem_->font().pointSize()));
1108
if(_font.style() != graphicsitem_->font().style()) {
1109
QString oldStyle, newStyle;
1110
switch(graphicsitem_->font().style()) {
1111
case QFont::StyleNormal:
1112
oldStyle = "normal";
1114
case QFont::StyleItalic:
1115
oldStyle = "italic";
1117
case QFont::StyleOblique:
1118
oldStyle = "oblique";
1121
switch(_font.style()) {
1122
case QFont::StyleNormal:
1123
newStyle = "normal";
1125
case QFont::StyleItalic:
1126
newStyle = "italic";
1128
case QFont::StyleOblique:
1129
newStyle = "oblique";
1132
emit attributeChanged(id(), "font-style", newStyle, oldStyle);
1134
if(_font.weight() != graphicsitem_->font().weight()) {
1135
int oldWeight = (graphicsitem_->font().weight() - (graphicsitem_->font().weight() % 10)) * 10;
1136
int newWeight = (_font.weight() - (_font.weight() % 10)) * 10;
1141
emit attributeChanged(id(), "font-weight", QString("%1").arg(newWeight), QString("%1").arg(oldWeight));
1142
_font.setWeight(((newWeight - 100) / 800) * 99);
1144
graphicsitem_->setFont(_font);
1153
// WbImage::WbImage(const QString &id, qreal index, const QPointF &startPoint, const QString &parent, QGraphicsScene * scene) : QGraphicsPathItem(0, scene), WbItem(id) {
1155
// attributes["d"] = QString("M %1 %2").arg(startPoint.x()).arg(startPoint.y());
1156
// setPath(QPainterPath(startPoint));
1157
// setFlag(QGraphicsItem::ItemIsSelectable);
1158
// setFlag(QGraphicsItem::ItemIsMovable);
1159
// setAcceptDrops(true);
1162
WbImage::WbImage(QDomElement &_svg, const QString &id, const qreal &index, const QString &parent, QGraphicsScene * scene) : QGraphicsPixmapItem(0, scene), WbItem(id) {
1163
setParentWbItem(parent);
1166
setFlag(QGraphicsItem::ItemIsSelectable);
1167
setFlag(QGraphicsItem::ItemIsMovable);
1168
setAcceptDrops(true);
1171
QList<QString> WbImage::parseSvg(QDomElement &_svg, bool emitChanges) {
1172
QList<QString> changed = WbItem::parseSvg(_svg, emitChanges);
1173
if(changed.contains("xlink:href") || changed.contains("width") || changed.contains("height")) {
1174
QString header = attributes["xlink:href"].left(23);
1175
// we're looking for something like this "data:;image/png;base64,"
1176
if(header.startsWith("data:")) {
1177
if(header.left(12).contains("image/")) {
1178
if(header.left(15).contains("png") || header.left(15).contains("jpg") || header.left(16).contains("jpeg") || header.left(15).contains("bmp") || header.left(15).contains("gif") || header.left(15).contains("pbm") || header.left(15).contains("pgm") || header.left(15).contains("ppm") || header.left(15).contains("xbm") || header.left(15).contains("xpm")) {
1179
if(header.contains("base64,")) {
1180
// qDebug() << "data: " << attributes["xlink:href"].mid(header.indexOf("base64,") + 7);
1181
QCA::Base64 decoder(QCA::Decode);
1182
decoder.setLineBreaksEnabled(true);
1183
QImage image = QImage::fromData(decoder.stringToArray(attributes["xlink:href"].mid(header.indexOf("base64,") + 7)).toByteArray());
1185
int width = static_cast<int>(attributes["width"].toDouble(&okW));
1186
int height = static_cast<int>(attributes["height"].toDouble(&okH));
1187
// TODO: aspect ratio
1189
image = image.scaled(QSize(width, height), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
1190
setPixmap(QPixmap::fromImage(image));
1199
WbItem* WbImage::clone() {
1200
QDomElement _svg = svg();
1201
WbItem* cloned = new WbImage(_svg, id(), index(), parentWbItem(), 0);
1202
cloned->undos = undos;
1206
void WbImage::contextMenuEvent (QGraphicsSceneContextMenuEvent * event) {
1207
constructContextMenu()->exec(event->screenPos());
1208
regenerateTransform();
1211
void WbImage::mouseMoveEvent(QGraphicsSceneMouseEvent * event) {
1212
handleMouseMoveEvent(event);
1213
if(!event->isAccepted())
1214
QGraphicsPixmapItem::mouseMoveEvent(event);
1217
void WbImage::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) {
1218
handleMouseReleaseEvent(event);
1219
if(!event->isAccepted())
1220
QGraphicsPixmapItem::mouseReleaseEvent(event);
1227
WbGroup::WbGroup(QDomElement &_svg, const QString &id, const qreal &index, const QString &parent, QGraphicsScene * scene) : QGraphicsItem(0, scene), WbItem(id) {
1228
setParentWbItem(parent);
1231
setFlag(QGraphicsItem::ItemIsSelectable);
1232
setFlag(QGraphicsItem::ItemIsMovable);
1233
setAcceptDrops(true);
1234
setHandlesChildEvents(true);
1237
WbItem* WbGroup::clone() {
1238
QDomElement _svg = svg();
1239
WbItem* cloned = new WbGroup(_svg, id(), index(), parentWbItem(), 0);
1240
cloned->undos = undos;
1244
void WbGroup::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) {
1245
if (option->state & QStyle::State_Selected) {
1246
painter->setBrush(Qt::NoBrush);
1247
painter->drawRect(boundingRect_);
1251
QRectF WbGroup::boundingRect() const {
1252
return boundingRect_;
1255
QVariant WbGroup::itemChange(GraphicsItemChange change, const QVariant &value) {
1256
if(change == QGraphicsItem::ItemChildAddedChange) {
1257
QGraphicsItem* child = qVariantValue<QGraphicsItem *>(value);
1259
if(QGraphicsItem::children().size() == 1)
1260
boundingRect_ = mapFromItem(child, child->boundingRect()).boundingRect();
1262
addToBoundingRect(mapFromItem(child, child->boundingRect()).boundingRect());
1264
} else if(change == QGraphicsItem::ItemChildRemovedChange) {
1265
QGraphicsItem* child = qVariantValue<QGraphicsItem *>(value);
1267
QRectF itemrect = mapFromItem(child, child->boundingRect()).boundingRect();
1268
if(itemrect.x() == boundingRect_.x() || itemrect.y() == boundingRect_.y() || itemrect.right() == boundingRect_.right() || itemrect.bottom() == boundingRect_.bottom()) {
1269
// If the item was on one of the borders of the boundingRect_ recalculate it
1270
if(!QGraphicsItem::children().isEmpty()) {
1271
boundingRect_ = QGraphicsItem::children().first()->boundingRect();
1272
for(int i = 1; i < QGraphicsItem::children().size(); i++)
1273
addToBoundingRect(QGraphicsItem::children().at(i)->boundingRect());
1275
boundingRect_ = QRectF();
1282
void WbGroup::contextMenuEvent (QGraphicsSceneContextMenuEvent * event) {
1283
constructContextMenu()->exec(event->screenPos());
1284
regenerateTransform();
1287
void WbGroup::mouseMoveEvent(QGraphicsSceneMouseEvent * event) {
1288
handleMouseMoveEvent(event);
1289
if(!event->isAccepted())
1290
QGraphicsItem::mouseMoveEvent(event);
1293
void WbGroup::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) {
1294
handleMouseReleaseEvent(event);
1295
if(!event->isAccepted())
1296
QGraphicsItem::mouseReleaseEvent(event);
1299
void WbGroup::addToBoundingRect(const QRectF &itemRect) {
1300
if(itemRect.left() < boundingRect_.left())
1301
boundingRect_.adjust(itemRect.left() - boundingRect_.left(), 0, 0, 0);
1302
if(itemRect.top() < boundingRect_.top())
1303
boundingRect_.adjust(0, itemRect.top() - boundingRect_.top(), 0, 0);
1304
if(itemRect.right() > boundingRect_.right())
1305
boundingRect_.adjust(0, 0, itemRect.right() - boundingRect_.right(), 0);
1306
if(itemRect.bottom() > boundingRect_.bottom())
1307
boundingRect_.adjust(0, 0, 0, itemRect.bottom() - boundingRect_.bottom());
1315
* Many of he following methods are copied and/or adapted from
1316
* the Qt Svg module's file called qsvghandler.cpp.
1317
* The original header is shown below:
1320
/****************************************************************************
1322
** Copyright (C) 1992-2006 Trolltech AS. All rights reserved.
1324
** This file is part of the QtSVG module of the Qt Toolkit.
1326
** This file may be used under the terms of the GNU General Public
1327
** License version 2.0 as published by the Free Software Foundation
1328
** and appearing in the file LICENSE.GPL included in the packaging of
1329
** this file. Please review the following information to ensure GNU
1330
** General Public Licensing requirements will be met:
1331
** http://www.trolltech.com/products/qt/opensource.html
1333
** If you are unsure which license is appropriate for your use, please
1334
** review the following information:
1335
** http://www.trolltech.com/products/qt/licensing.html or contact the
1336
** sales department at sales@trolltech.com.
1338
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
1339
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
1341
****************************************************************************/
1343
// WbItem::WbItem(const QDomElement &svg, QObject * parent, QGraphicsScene * scene) : QGraphicsItem(parent, scene) {
1351
// QString WbItem::xmlSimplify(const QString &str)
1353
// QString dummy = str;
1354
// dummy.remove('\n');
1355
// if (dummy.trimmed().isEmpty())
1356
// return QString();
1358
// QString::const_iterator itr = dummy.constBegin();
1359
// bool wasSpace = false;
1360
// for (;itr != dummy.constEnd(); ++itr) {
1361
// if ((*itr).isSpace()) {
1362
// if (wasSpace || !(*itr).isPrint()) {
1369
// wasSpace = false;
1375
QList<qreal> WbItem::parseNumbersList(QString::const_iterator &itr)
1377
QList<qreal> points;
1379
while ((*itr).isSpace())
1381
while ((*itr).isNumber() ||
1382
(*itr) == '-' || (*itr) == '+' || (*itr) == '.') {
1387
else if ((*itr) == '+')
1389
while ((*itr).isDigit())
1393
while ((*itr).isDigit())
1395
if (( *itr) == 'e') {
1397
if ((*itr) == '-' ||
1401
while ((*itr).isDigit())
1403
while ((*itr).isSpace())
1407
points.append(temp.toDouble());
1408
//eat the rest of space
1409
while ((*itr).isSpace())
1416
QList<qreal> WbItem::parsePercentageList(QString::const_iterator &itr)
1418
QList<qreal> points;
1420
while ((*itr).isSpace())
1422
while ((*itr).isNumber() ||
1423
(*itr) == '-' || (*itr) == '+') {
1428
else if ((*itr) == '+')
1430
while ((*itr).isDigit())
1434
while ((*itr).isDigit())
1436
if (( *itr) == '%') {
1439
while ((*itr).isSpace())
1443
points.append(temp.toDouble());
1444
//eat the rest of space
1445
while ((*itr).isSpace())
1453
bool WbItem::isUnreserved(const QChar &c, bool first_char)
1455
return (c.isLetterOrNumber() || c == QLatin1Char('_') || c == QLatin1Char(':')
1456
|| (!first_char && (c == QLatin1Char('.') || c == QLatin1Char('-'))));
1460
QString WbItem::idFromUrl(const QString &url)
1462
QString::const_iterator itr = url.constBegin();
1463
while ((*itr).isSpace())
1467
while ((*itr).isSpace())
1473
if (isUnreserved(*itr, true)) {
1474
while (isUnreserved(*itr)) {
1483
QMatrix WbItem::parseTransformationMatrix(const QString &value)
1488
QString::const_iterator itr = value.constBegin();
1490
while (itr != value.constEnd()) {
1491
if ((*itr) == 'm') { //matrix
1498
while ((*itr).isSpace())
1501
QList<qreal> points = WbItem::parseNumbersList(itr);
1504
Q_ASSERT(points.count() == 6);
1505
matrix = matrix * QMatrix(points[0], points[1],
1506
points[2], points[3],
1507
points[4], points[5]);
1509
//qDebug()<<"matrix is "<<temp;
1510
} else if ((*itr) == 't') { //translate
1516
while ((*itr).isSpace())
1519
QList<qreal> points = WbItem::parseNumbersList(itr);
1522
Q_ASSERT(points.count() == 2 ||
1523
points.count() == 1);
1524
if (points.count() == 2)
1525
matrix.translate(points[0], points[1]);
1527
matrix.translate(points[0], 0);
1529
//qDebug()<<"trans is "<<points;
1530
} else if ((*itr) == 'r') { //rotate
1536
while ((*itr).isSpace())
1540
QList<qreal> points = WbItem::parseNumbersList(itr);
1542
Q_ASSERT(points.count() == 3 ||
1543
points.count() == 1);
1544
if (points.count() == 3) {
1545
matrix.translate(points[1], points[2]);
1546
matrix.rotate(points[0]);
1547
matrix.translate(-points[1], -points[2]);
1550
matrix.rotate(points[0]);
1552
//qDebug()<<"rot is "<<points;
1553
} else if ((*itr) == 's') { //scale | skewX | skewY
1559
while ((*itr).isSpace())
1563
QList<qreal> points = WbItem::parseNumbersList(itr);
1565
Q_ASSERT(points.count() == 2 ||
1566
points.count() == 1);
1567
if (temp == QLatin1String("scale")) {
1568
if (points.count() == 2) {
1569
matrix.scale(points[0], points[1]);
1572
matrix.scale(points[0], points[0]);
1573
} else if (temp == QLatin1String("skewX")) {
1574
const qreal deg2rad = qreal(0.017453292519943295769);
1575
matrix.shear(tan(points[0]*deg2rad), 0);
1576
} else if (temp == QLatin1String("skewY")) {
1577
const qreal deg2rad = qreal(0.017453292519943295769);
1578
matrix.shear(0, tan(points[0]*deg2rad));
1580
} else if ((*itr) == ' ' ||
1585
if (itr != value.constEnd())
1591
QPen WbItem::parseQPen(QString stroke, const QString &dashArray, const QString &dashOffset, const QString &linecap, const QString &linejoin, const QString &miterlimit, QString strokeopacity, QString opacity, const QString &width)
1593
// TODO: do something with 'dashOffset'
1594
Q_UNUSED(dashOffset);
1598
if (!strokeopacity.isEmpty())
1599
opacity = strokeopacity;
1601
if (!stroke.isEmpty() || !width.isEmpty()) {
1602
if (stroke != QLatin1String("none")) {
1603
if (!stroke.isEmpty()) {
1604
if (stroke.startsWith("url")) {
1605
// TODO: support stroke='url...'
1606
// stroke = stroke.remove(0, 3);
1607
// QString id = idFromUrl(stroke);
1608
// QSvgStructureNode *group = 0;
1609
// QSvgNode *dummy = node;
1610
// while (dummy && (dummy->type() != QSvgNode::DOC &&
1611
// dummy->type() != QSvgNode::G &&
1612
// dummy->type() != QSvgNode::DEFS &&
1613
// dummy->type() != QSvgNode::SWITCH)) {
1614
// dummy = dummy->parent();
1617
// group = static_cast<QSvgStructureNode*>(dummy);
1619
// QSvgStyleProperty *style =
1620
// group->scopeStyle(id);
1621
// if (style->type() == QSvgStyleProperty::GRADIENT) {
1622
// QBrush b(*((QSvgGradientStyle*)style)->qgradient());
1624
// } else if (style->type() == QSvgStyleProperty::SOLID_COLOR) {
1626
// ((QSvgSolidColorStyle*)style)->qcolor());
1629
// qDebug()<<"QSvgHandler::parsePen no parent group?";
1632
pen.setColor(constructColor(stroke, opacity));
1634
//since we could inherit stroke="none"
1635
//we need to reset the style of our stroke to something
1636
pen.setStyle(Qt::SolidLine);
1638
if (!width.isEmpty()) {
1639
// // QSvgHandler::LengthType lt;
1640
qreal widthF = parseLength(width);
1641
// TODO: support percentage widths
1646
pen.setWidthF(widthF);
1648
qreal penw = pen.widthF();
1650
if (!linejoin.isEmpty()) {
1651
if (linejoin == "miter")
1652
pen.setJoinStyle(Qt::MiterJoin);
1653
else if (linejoin == "round")
1654
pen.setJoinStyle(Qt::RoundJoin);
1655
else if (linejoin == "bevel")
1656
pen.setJoinStyle(Qt::BevelJoin);
1658
if (!miterlimit.isEmpty()) {
1659
pen.setMiterLimit(miterlimit.toDouble()/2);
1662
if (!linecap.isEmpty()) {
1663
if (linecap == "butt")
1664
pen.setCapStyle(Qt::FlatCap);
1665
else if (linecap == "round")
1666
pen.setCapStyle(Qt::RoundCap);
1667
else if (linecap == "square")
1668
pen.setCapStyle(Qt::SquareCap);
1671
if (!dashArray.isEmpty()) {
1672
QString::const_iterator itr = dashArray.constBegin();
1673
QList<qreal> dashes = parseNumbersList(itr);
1674
QVector<qreal> vec(dashes.size());
1677
foreach(qreal dash, dashes) {
1678
vec[i++] = dash/penw;
1680
pen.setDashPattern(vec);
1684
pen.setStyle(Qt::NoPen);
1687
// TODO: check what the actual defaults are
1688
pen = QPen(QColor(Qt::black), 1, Qt::NoPen, Qt::FlatCap, Qt::MiterJoin);
1693
QColor WbItem::constructColor(const QString &colorStr, const QString &opacity)
1695
QColor c = resolveColor(colorStr);
1696
if (!opacity.isEmpty()) {
1697
qreal op = opacity.toDouble();
1700
c.setAlpha(int(op));
1705
QColor WbItem::resolveColor(const QString &colorStr)
1708
static QHash<QString, QColor> colors;
1709
QString colorStrTr = colorStr.trimmed();
1710
if (colors.isEmpty()) {
1711
colors.insert("black", QColor( 0, 0, 0));
1712
colors.insert("green", QColor( 0, 128, 0));
1713
colors.insert("silver", QColor(192, 192, 192));
1714
colors.insert("lime", QColor( 0, 255, 0));
1715
colors.insert("gray", QColor(128, 128, 128));
1716
colors.insert("olive", QColor(128, 128, 0));
1717
colors.insert("white", QColor(255, 255, 255));
1718
colors.insert("yellow", QColor(255, 255, 0));
1719
colors.insert("maroon", QColor(128, 0, 0));
1720
colors.insert("navy", QColor( 0, 0, 128));
1721
colors.insert("red", QColor(255, 0, 0));
1722
colors.insert("blue", QColor( 0, 0, 255));
1723
colors.insert("purple", QColor(128, 0, 128));
1724
colors.insert("teal", QColor( 0, 128, 128));
1725
colors.insert("fuchsia", QColor(255, 0, 255));
1726
colors.insert("aqua", QColor( 0, 255, 255));
1728
if (colors.contains(colorStrTr)) {
1729
color = colors[colorStrTr];
1730
} else if (colorStr.startsWith("rgb(")) {
1731
QString::const_iterator itr = colorStr.constBegin();
1732
++itr; ++itr; ++itr; ++itr;
1733
QString::const_iterator itr_back = itr;
1734
QList<qreal> compo = parseNumbersList(itr);
1735
//1 means that it failed after reaching non-parsable
1736
//character which is going to be "%"
1737
if (compo.size() == 1) {
1739
compo = parsePercentageList(itr);
1745
color = QColor(int(compo[0]),
1748
} else if (colorStr == QLatin1String("inherited") ||
1749
colorStr == QLatin1String("inherit")) {
1750
// TODO: handle inherited color
1751
color = QColor(Qt::black);
1752
} else if (colorStr == QLatin1String("currentColor")) {
1753
//TODO: handle current Color
1754
color = QColor(Qt::black);
1759
qreal WbItem::parseLength(const QString &str)
1761
QString numStr = str.trimmed();
1764
if (numStr.endsWith("%")) {
1766
len = numStr.toDouble();
1767
// type = QSvgHandler::PERCENT;
1768
} else if (numStr.endsWith("px")) {
1770
len = numStr.toDouble();
1771
// type = QSvgHandler::PX;
1772
} else if (numStr.endsWith("pc")) {
1774
len = numStr.toDouble();
1775
// type = QSvgHandler::PC;
1776
} else if (numStr.endsWith("pt")) {
1778
len = numStr.toDouble();
1779
// type = QSvgHandler::PT;
1780
} else if (numStr.endsWith("mm")) {
1782
len = numStr.toDouble();
1783
// type = QSvgHandler::MM;
1784
} else if (numStr.endsWith("cm")) {
1786
len = numStr.toDouble();
1787
// type = QSvgHandler::CM;
1788
} else if (numStr.endsWith("in")) {
1790
len = numStr.toDouble();
1791
// type = QSvgHandler::IN;
1793
len = numStr.toDouble();
1794
// type = handler->defaultCoordinateSystem();
1795
//type = QSvgHandler::OTHER;
1797
//qDebug()<<"len is "<<len<<", from "<<numStr;
1805
bool WbPath::parsePathDataFast(const QString &data, QPainterPath &path)
1807
QString::const_iterator itr = data.constBegin();
1808
qreal x0 = 0, y0 = 0; // starting point
1809
qreal x = 0, y = 0; // current point
1814
while (itr != data.constEnd()) {
1815
while ((*itr).isSpace())
1819
QList<qreal> arg = WbItem::parseNumbersList(itr);
1820
if (pathElem == 'z' || pathElem == 'Z')
1821
arg.append(0);//dummy
1822
while (!arg.isEmpty()) {
1823
qreal offsetX = x; // correction offsets
1824
qreal offsetY = y; // for relative commands
1825
switch (pathElem.toAscii()) {
1827
x = x0 = arg[0] + offsetX;
1828
y = y0 = arg[1] + offsetY;
1829
path.moveTo(x0, y0);
1830
arg.pop_front(); arg.pop_front();
1836
path.moveTo(x0, y0);
1837
arg.pop_front(); arg.pop_front();
1844
path.closeSubpath();
1845
arg.pop_front();//pop dummy
1849
x = arg.front() + offsetX;
1851
y = arg.front() + offsetY;
1858
x = arg.front(); arg.pop_front();
1859
y = arg.front(); arg.pop_front();
1864
x = arg.front() + offsetX; arg.pop_front();
1875
y = arg[0] + offsetY;
1887
QPointF c1(arg[0]+offsetX, arg[1]+offsetY);
1888
QPointF c2(arg[2]+offsetX, arg[3]+offsetY);
1889
QPointF e(arg[4]+offsetX, arg[5]+offsetY);
1890
path.cubicTo(c1, c2, e);
1894
arg.pop_front(); arg.pop_front();
1895
arg.pop_front(); arg.pop_front();
1896
arg.pop_front(); arg.pop_front();
1900
QPointF c1(arg[0], arg[1]);
1901
QPointF c2(arg[2], arg[3]);
1902
QPointF e(arg[4], arg[5]);
1903
path.cubicTo(c1, c2, e);
1907
arg.pop_front(); arg.pop_front();
1908
arg.pop_front(); arg.pop_front();
1909
arg.pop_front(); arg.pop_front();
1914
if (lastMode == 'c' || lastMode == 'C' ||
1915
lastMode == 's' || lastMode == 'S')
1916
c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1919
QPointF c2(arg[0]+offsetX, arg[1]+offsetY);
1920
QPointF e(arg[2]+offsetX, arg[3]+offsetY);
1921
path.cubicTo(c1, c2, e);
1925
arg.pop_front(); arg.pop_front();
1926
arg.pop_front(); arg.pop_front();
1931
if (lastMode == 'c' || lastMode == 'C' ||
1932
lastMode == 's' || lastMode == 'S')
1933
c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1936
QPointF c2(arg[0], arg[1]);
1937
QPointF e(arg[2], arg[3]);
1938
path.cubicTo(c1, c2, e);
1942
arg.pop_front(); arg.pop_front();
1943
arg.pop_front(); arg.pop_front();
1947
QPointF c(arg[0]+offsetX, arg[1]+offsetY);
1948
QPointF e(arg[2]+offsetX, arg[3]+offsetY);
1953
arg.pop_front(); arg.pop_front();
1954
arg.pop_front(); arg.pop_front();
1958
QPointF c(arg[0], arg[1]);
1959
QPointF e(arg[2], arg[3]);
1964
arg.pop_front(); arg.pop_front();
1965
arg.pop_front(); arg.pop_front();
1969
QPointF e(arg[0]+offsetX, arg[1]+offsetY);
1971
if (lastMode == 'q' || lastMode == 'Q' ||
1972
lastMode == 't' || lastMode == 'T')
1973
c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1980
arg.pop_front(); arg.pop_front();
1984
QPointF e(arg[0], arg[1]);
1986
if (lastMode == 'q' || lastMode == 'Q' ||
1987
lastMode == 't' || lastMode == 'T')
1988
c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
1995
arg.pop_front(); arg.pop_front();
2001
qreal xAxisRotation = arg[2];
2002
qreal largeArcFlag = arg[3];
2003
qreal sweepFlag = arg[4];
2004
qreal ex = arg[5] + offsetX;
2005
qreal ey = arg[6] + offsetY;
2008
pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
2009
int(sweepFlag), ex, ey, curx, cury);
2014
arg.pop_front(); arg.pop_front();
2015
arg.pop_front(); arg.pop_front();
2016
arg.pop_front(); arg.pop_front();
2023
qreal xAxisRotation = arg[2];
2024
qreal largeArcFlag = arg[3];
2025
qreal sweepFlag = arg[4];
2030
pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
2031
int(sweepFlag), ex, ey, curx, cury);
2034
arg.pop_front(); arg.pop_front();
2035
arg.pop_front(); arg.pop_front();
2036
arg.pop_front(); arg.pop_front();
2041
qDebug() << QString("path data is ") << pathElem;
2042
Q_ASSERT(!"invalid path data");
2045
lastMode = pathElem.toAscii();
2051
void WbPath::pathArcSegment(QPainterPath &path, qreal xc, qreal yc, qreal th0, qreal th1, qreal rx, qreal ry, qreal xAxisRotation)
2054
qreal a00, a01, a10, a11;
2055
qreal x1, y1, x2, y2, x3, y3;
2059
sinTh = sin(xAxisRotation * (M_PI / 180.0));
2060
cosTh = cos(xAxisRotation * (M_PI / 180.0));
2067
thHalf = 0.5 * (th1 - th0);
2068
t = (8.0 / 3.0) * sin(thHalf * 0.5) * sin(thHalf * 0.5) / sin(thHalf);
2069
x1 = xc + cos(th0) - t * sin(th0);
2070
y1 = yc + sin(th0) + t * cos(th0);
2073
x2 = x3 + t * sin(th1);
2074
y2 = y3 - t * cos(th1);
2076
path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
2077
a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
2078
a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);
2081
// the arc handling code underneath is from XSVG (BSD license)
2083
* Copyright 2002 USC/Information Sciences Institute
2085
* Permission to use, copy, modify, distribute, and sell this software
2086
* and its documentation for any purpose is hereby granted without
2087
* fee, provided that the above copyright notice appear in all copies
2088
* and that both that copyright notice and this permission notice
2089
* appear in supporting documentation, and that the name of
2090
* Information Sciences Institute not be used in advertising or
2091
* publicity pertaining to distribution of the software without
2092
* specific, written prior permission. Information Sciences Institute
2093
* makes no representations about the suitability of this software for
2094
* any purpose. It is provided "as is" without express or implied
2097
* INFORMATION SCIENCES INSTITUTE DISCLAIMS ALL WARRANTIES WITH REGARD
2098
* TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
2099
* MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL INFORMATION SCIENCES
2100
* INSTITUTE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
2101
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
2102
* OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
2103
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
2104
* PERFORMANCE OF THIS SOFTWARE.
2107
void WbPath::pathArc(QPainterPath &path,
2110
qreal x_axis_rotation,
2115
qreal curx, qreal cury)
2117
qreal sin_th, cos_th;
2118
qreal a00, a01, a10, a11;
2119
qreal x0, y0, x1, y1, xc, yc;
2120
qreal d, sfactor, sfactor_sq;
2121
qreal th0, th1, th_arc;
2123
qreal dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check;
2128
sin_th = sin(x_axis_rotation * (M_PI / 180.0));
2129
cos_th = cos(x_axis_rotation * (M_PI / 180.0));
2131
dx = (curx - x) / 2.0;
2132
dy = (cury - y) / 2.0;
2133
dx1 = cos_th * dx + sin_th * dy;
2134
dy1 = -sin_th * dx + cos_th * dy;
2139
/* Spec : check if radii are large enough */
2140
check = Px / Pr1 + Py / Pr2;
2142
rx = rx * sqrt(check);
2143
ry = ry * sqrt(check);
2150
x0 = a00 * curx + a01 * cury;
2151
y0 = a10 * curx + a11 * cury;
2152
x1 = a00 * x + a01 * y;
2153
y1 = a10 * x + a11 * y;
2154
/* (x0, y0) is current point in transformed coordinate space.
2155
(x1, y1) is new point in transformed coordinate space.
2157
The arc fits a unit-radius circle in this space.
2159
d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
2160
sfactor_sq = 1.0 / d - 0.25;
2161
if (sfactor_sq < 0) sfactor_sq = 0;
2162
sfactor = sqrt(sfactor_sq);
2163
if (sweep_flag == large_arc_flag) sfactor = -sfactor;
2164
xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
2165
yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
2166
/* (xc, yc) is center of the circle. */
2168
th0 = atan2(y0 - yc, x0 - xc);
2169
th1 = atan2(y1 - yc, x1 - xc);
2172
if (th_arc < 0 && sweep_flag)
2174
else if (th_arc > 0 && !sweep_flag)
2177
n_segs = int(ceil(qAbs(th_arc / (M_PI * 0.5 + 0.001))));
2179
for (i = 0; i < n_segs; i++) {
2180
pathArcSegment(path, xc, yc,
2181
th0 + i * th_arc / n_segs,
2182
th0 + (i + 1) * th_arc / n_segs,
2183
rx, ry, x_axis_rotation);