2
* wbscene.cpp - an SVG whiteboard scene class
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
23
WbScene::WbScene(const QString &session, const QString &ownJid, QObject * parent) : QGraphicsScene(parent) {
30
WbRoot* w = new WbRoot(this);
31
QDomElement svg = w->svg();
33
svg.setAttribute("viewBox", "0 0 600 600");
34
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
35
svg.setAttribute("version", "1.1");
36
svg.setAttribute("baseProfile", "tiny");
37
w->parseSvg(svg, false);
38
elements_.insert("root", w);
41
QString WbScene::session() {
45
const QString & WbScene::ownJid() const {
49
bool WbScene::processWb(const QDomElement &wb) {
50
QDomNodeList children = wb.childNodes();
51
for(uint i=0; i < children.length(); i++) {
52
if(children.item(i).nodeName() == "new")
53
processNew(children.item(i).toElement());
54
else if(children.item(i).nodeName() == "configure")
55
processConfigure(children.item(i).toElement());
56
else if(children.item(i).nodeName() == "move")
57
processMove(children.item(i).toElement());
58
else if(children.item(i).nodeName() == "remove")
59
processRemove(children.item(i).toElement());
64
WbItem* WbScene::findWbItem(const QString &id) const {
65
if(elements_.contains(id))
66
return elements_.value(id);
71
WbItem* WbScene::findWbItem(QGraphicsItem* item) const {
72
QHashIterator<QString, WbItem*> i(elements_);
75
if(i.value()->graphicsItem() == item)
81
void WbScene::addWbItem(WbItem *item) {
82
connect(item, SIGNAL(attributeChanged(QString, QString, QString, QString)), SLOT(queueAttributeEdit(QString, QString, QString, QString)));
83
connect(item, SIGNAL(parentChanged(QString, QString, QString)), SLOT(queueParentEdit(QString, QString, QString)));
84
connect(item, SIGNAL(contentChanged(QString, QDomNodeList, QDomNodeList)), SLOT(queueContentEdit(QString, QDomNodeList, QDomNodeList)));
85
connect(item, SIGNAL(indexChanged(QString, qreal)), SLOT(queueMove(QString, qreal)));
87
if(item->graphicsItem() && item->graphicsItem()->scene() != this)
88
addItem(item->graphicsItem());
90
elements_.insert(item->id(), item);
93
bool WbScene::removeElement(const QString &id, bool emitChanges)
95
WbItem* wbitem = findWbItem(id);
97
// Reparent children to root
98
if(wbitem->graphicsItem()) {
99
QGraphicsLineItem lineitem(0, 0);
100
foreach(QGraphicsItem* c, wbitem->graphicsItem()->children()) {
101
// FIXME: should just be c->setParentItem(0);, this is a workaround for a qt bug
102
c->setParentItem(&lineitem);
105
if(wbitem->graphicsItem()->scene())
106
wbitem->graphicsItem()->scene()->removeItem(wbitem->graphicsItem());
108
elements_.remove(wbitem->id());
110
queueRemove(wbitem->id());
111
wbitem->deleteLater();
117
void WbScene::queueTransformationChange(WbItem* item) {
118
if(item && !pendingTranformations_.contains(item))
119
pendingTranformations_.append(QPointer<WbItem>(item));
122
void WbScene::regenerateTransformations() {
123
foreach(QPointer<WbItem> item, pendingTranformations_) {
125
item->regenerateTransform();
127
pendingTranformations_.clear();
130
QDomElement WbScene::element(const QString &id) const {
131
WbItem* i = findWbItem(id);
135
return QDomElement();
138
QList<WbItem*> WbScene::elements() const {
139
QList<QString> elementKeys = elements_.keys();
140
QList<QString> returnKeys;
141
// Append nodes so that parents are listed before children
142
int elementsAdded = 1;
143
while(elementsAdded != 0 && !elementKeys.isEmpty()) {
145
foreach(QString key, elementKeys) {
146
QString parent = elements_[key]->parentWbItem();
147
if(parent == "root" || returnKeys.contains(parent) || !elements_.contains(parent)) {
148
returnKeys.append(key);
151
elementKeys.removeAll(key);
155
QList<WbItem*> returnedElements;
156
foreach(QString key, returnKeys) {
157
WbItem* element = elements_[key]->clone();
158
returnedElements.append(element);
160
return returnedElements;
163
void WbScene::setStrokeColor() {
164
QColor newColor = QColorDialog::getColor();
165
if(newColor.isValid()) {
166
foreach(QGraphicsItem* selecteditem, selectedItems()) {
167
WbItem* selectedwbitem = findWbItem(selecteditem);
169
selectedwbitem->setStrokeColor(newColor);
174
void WbScene::setFillColor() {
175
QColor newColor = QColorDialog::getColor();
176
if(newColor.isValid()) {
177
foreach(QGraphicsItem* selecteditem, selectedItems()) {
178
WbItem* selectedwbitem = findWbItem(selecteditem);
180
selectedwbitem->setFillColor(newColor);
185
void WbScene::setStrokeWidth(QAction* a) {
186
foreach(QGraphicsItem* selecteditem, selectedItems()) {
187
WbItem* selectedwbitem = findWbItem(selecteditem);
189
selectedwbitem->setStrokeWidth(a->data().toInt());
193
void WbScene::bringForward(int n) {
196
// bring each selected item
197
foreach(QGraphicsItem* selecteditem, selectedItems()) {
198
if (!selecteditem->parentItem() || !selecteditem->parentItem()->isSelected()) {
199
WbItem* selectedwbitem = findWbItem(selecteditem);
201
QList<qreal> collidingindeces;
202
qreal selecteditemindex = selectedwbitem->index();
203
foreach(QGraphicsItem* collidingitem, selecteditem->collidingItems()) {
204
if(!selectedItems().contains(collidingitem)) {
205
WbItem* collidingwbitem =findWbItem(collidingitem);
206
if(collidingwbitem) {
207
qreal collidingitemindex = collidingwbitem->index();
208
if(collidingitemindex > selecteditemindex) {
210
while(i < collidingindeces.size() && collidingindeces.at(i) < collidingitemindex)
212
collidingindeces.insert(i, collidingitemindex);
217
if(!collidingindeces.isEmpty()) {
218
if(n < 0 || n >= collidingindeces.size())
219
selectedwbitem->setIndex(collidingindeces.last() + 1.1, true);
221
selectedwbitem->setIndex(collidingindeces.at(n-1) + ((collidingindeces.at(n) - collidingindeces.at(n-1)) / 2), true);
228
void WbScene::bringToFront() {
232
void WbScene::sendBackwards(int n) {
235
// bring each selected item
236
foreach(QGraphicsItem* selecteditem, selectedItems()) {
237
if (!selecteditem->parentItem() || !selecteditem->parentItem()->isSelected()) {
238
WbItem* selectedwbitem = findWbItem(selecteditem);
240
QList<qreal> collidingindeces;
241
qreal selecteditemindex = selectedwbitem->index();
242
foreach(QGraphicsItem* collidingitem, selecteditem->collidingItems()) {
243
if(!selectedItems().contains(collidingitem)) {
244
WbItem* collidingwbitem = findWbItem(collidingitem);
245
if(collidingwbitem) {
246
qreal collidingitemindex = collidingwbitem->index();
247
if(collidingitemindex < selecteditemindex) {
249
while(i < collidingindeces.size() && collidingindeces.at(i) < collidingitemindex)
251
collidingindeces.insert(i, collidingitemindex);
256
if(!collidingindeces.isEmpty()) {
257
if(n < 0 || n >= collidingindeces.size())
258
selectedwbitem->setIndex(collidingindeces.first() - 1.1, true);
260
selectedwbitem->setIndex(collidingindeces.at(collidingindeces.size() - n) - ((collidingindeces.at(collidingindeces.size() - n) - collidingindeces.at(collidingindeces.size() - n - 1)) / 2), true);
267
void WbScene::sendToBack() {
271
void WbScene::group() {
272
if(selectedItems().size() > 1) {
274
QDomElement _svg = QDomDocument().createElement("g");
275
WbGroup* group = new WbGroup(_svg, newId(), newIndex(), "root", this);
277
// Send out the group item
278
queueNew(group->id(), group->index(), group->svg());
279
// Reparent each selected item
280
foreach(QGraphicsItem* selecteditem, selectedItems()) {
281
WbItem* selectedwbitem = findWbItem(selecteditem);
283
selectedwbitem->setParentWbItem(group->id(), true);
286
group->setSelected(true);
290
void WbScene::ungroup() {
291
foreach(QGraphicsItem* selecteditem, selectedItems()) {
292
// 87654201 == WbGroup::type()
293
if(selecteditem->type() == 87654201) {
294
WbItem* selectedwbitem = findWbItem(selecteditem);
296
// If the group has some transformation, the children should be accordingly transformed as the group is removed
297
if(!selecteditem->sceneMatrix().isIdentity() || selecteditem->pos() != QPointF()) {
298
foreach(QGraphicsItem* child, selecteditem->children()) {
299
child->setMatrix(child->sceneMatrix());
300
WbItem* childwbitem = findWbItem(child);
302
childwbitem->regenerateTransform();
305
queueRemove(selectedwbitem->id());
306
removeElement(selectedwbitem->id());
313
void WbScene::sendWb() {
314
if(queue_.size() < 1)
317
QDomElement wb = d.createElementNS("http://jabber.org/protocol/svgwb", "wb");
318
wb.setAttribute("session", session_);
320
while(queue_.size() > 0) {
321
// only append the version just before sending the element
322
// and not when being queued to avoid unnecessary rejections
323
if(queue_.first().type == Edit::AttributeEdit || queue_.first().type == Edit::ParentEdit || queue_.first().type == Edit::ContentEdit) {
324
i = findWbItem(queue_.first().target);
325
QDomElement configure = d.createElement("configure");
326
configure.setAttribute("target", queue_.first().target);
327
configure.setAttribute("version", ++i->version);
328
// Lump all consequtive configure edits witht the same target into one <configure/>.
329
while(queue_.first().type == Edit::AttributeEdit || queue_.first().type == Edit::ParentEdit || queue_.first().type == Edit::ContentEdit && queue_.first().target == configure.attribute("target")) {
330
// Store the EditUndo's for each edit
331
Edit edit = queue_.takeFirst();
332
i->undos.append(EditUndo(i->version, edit));
333
configure.appendChild(edit.xml);
337
wb.appendChild(configure);
339
wb.appendChild(queue_.takeFirst().xml);
344
void WbScene::queueNew(const QString &id, const qreal &index, const QDomElement &svg) {
346
QDomElement n = d.createElement("new");
347
n.setAttribute("id", id);
348
n.setAttribute("index", index);
349
n.appendChild(svg.cloneNode());
350
queue_.append(Edit(Edit::NewEdit, n));
352
if(index > highestIndex_)
353
highestIndex_ = static_cast<int>(index);
355
//TODO: detect if more edits coming
359
void WbScene::queueAttributeEdit(const QString &target, const QString &attribute, const QString &value, QString oldValue, int from, int to) {
360
// Remove queued edits that have the same target and attribute if not a partial string replace
361
if(from < 0 || to < from)
362
removeFromQueue(target, attribute, oldValue);
364
Edit e = Edit(Edit::AttributeEdit, target, d.createElement("attribute"), oldValue);
365
e.xml.setAttribute("name", attribute);
366
if(from > -1 && to >= from) {
367
e.xml.setAttribute("from", from);
368
e.xml.setAttribute("to", to);
370
e.xml.appendChild(d.createTextNode(value));
373
//TODO: detect if more edits coming
377
void WbScene::queueParentEdit(const QString &target, const QString &value, QString oldValue) {
378
// Remove queued edits that have the same target and attribute
379
removeFromQueue(target, oldValue);
381
Edit e = Edit(Edit::ParentEdit, target, d.createElement("parent"), oldValue);
382
e.xml.appendChild(d.createTextNode(value));
385
//TODO: detect if more edits coming
389
void WbScene::queueContentEdit(const QString &target, const QDomNodeList &value, QDomNodeList oldValue) {
390
// Remove queued edits that have the same target and attribute
391
removeFromQueue(target, oldValue);
393
Edit e = Edit(target, d.createElement("content"), oldValue);
394
for(uint j=0; j < value.length(); j++)
395
e.xml.appendChild(value.at(j));
398
//TODO: detect if more edits coming
402
void WbScene::queueMove(const QString &target, qreal di) {
404
QDomElement n = d.createElement("move");
405
n.setAttribute("target", target);
406
n.setAttribute("di", di);
407
queue_.append(Edit(Edit::MoveEdit, n));
409
//TODO: detect if more edits coming
413
void WbScene::queueRemove(const QString &target) {
415
QDomElement n = d.createElement("remove");
416
n.setAttribute("target", target);
417
queue_.append(Edit(Edit::RemoveEdit, n));
419
//TODO: detect if more edits coming
423
bool WbScene::setElement(QDomElement &element, const QString &parent, const QString &id, const qreal &index)
427
WbItem* target = findWbItem(id);
429
// Get the QGraphicsItem* type of pointer to the parent WbItem
431
target->parseSvg(element, false);
432
if(parent != target->parentWbItem())
433
target->setParentWbItem(parent);
434
if(index != target->index())
435
target->setIndex(index);
438
if(element.tagName() == "path")
439
target = new WbPath(element, id, index, parent, this);
440
else if(element.tagName() == "ellipse")
441
target = new WbEllipse(element, id, index, parent, this);
442
else if(element.tagName() == "circle")
443
target = new WbCircle(element, id, index, parent, this);
444
else if(element.tagName() == "rect")
445
target = new WbRectangle(element, id, index, parent, this);
446
else if(element.tagName() == "line")
447
target = new WbLine(element, id, index, parent, this);
448
else if(element.tagName() == "polyline")
449
target = new WbPolyline(element, id, index, parent, this);
450
else if(element.tagName() == "polygon")
451
target = new WbPolygon(element, id, index, parent, this);
452
else if(element.tagName() == "text")
453
target = new WbText(element, id, index, parent, this);
454
else if(element.tagName() == "image")
455
target = new WbImage(element, id, index, parent, this);
456
else if(element.tagName() == "g")
457
target = new WbGroup(element, id, index, parent, this);
459
target = new WbUnknown(element, id, index, parent, this);
460
// Add the element pointer to the hash of elements_
469
bool WbScene::processNew(const QDomElement &New) {
470
QDomElement element = New.firstChildElement();
471
if(New.hasAttribute("id") && !element.isNull()) {
472
QString id = New.attribute("id");
473
QString parent = New.attribute("parent");
477
// FIXME: Actually, 'index' is REQUIRED in the JEP, leaving this as a fallback for now
478
if(New.hasAttribute("index"))
479
index = New.attribute("index").toDouble();
481
index = id.left(id.indexOf("/")).toDouble();
482
// Check that element with 'id' doesn't exist yet.
483
if(!findWbItem(id)) {
484
// qDebug() << QString("Adding - id: %1, index: %2").arg(id).arg(index).toAscii();
485
if(setElement(element, parent, id, index)) {
486
// save the highest index and id if appropriate
487
if(index > highestIndex_)
488
highestIndex_ = static_cast<int>(index);
489
if(id.left(id.indexOf("/")).toInt() > highestId_)
490
highestId_ = id.left(id.indexOf("/")).toInt();
498
bool WbScene::processConfigure(const QDomElement &configure) {
499
WbItem* wbitem = findWbItem(configure.attribute("target"));
501
// Note that _svg->svg() returns a deep copy
502
QDomElement _svg = wbitem->svg();
504
wbitem->version = configure.attribute("version").toInt();
507
if(configure.attribute("version").toInt() == wbitem->version) {
508
// qDebug) << (QString("Applying <configure/> - configure version: %1, current version: %2 target: %3 attribute: %4 value: %5").arg(configure.attribute("version")).arg(wbitem->version-1).arg(configure.attribute("target")).arg(configure.attribute("attribute")).arg(configure.attribute("value")).toAscii());
509
QDomNodeList configureChildren = configure.childNodes();
510
QString newParent = wbitem->parentWbItem();
511
for(uint i = 0; i < configureChildren.length(); i++) {
512
if(configureChildren.at(i).isElement()) {
513
QDomElement edit = configureChildren.at(i).toElement();
514
if(edit.nodeName() == "attribute" && !edit.attribute("name").isEmpty()) {
515
QString attributeName = edit.attribute("name");
516
QString oldValue = _svg.attribute(edit.attribute("name"));
517
// Remove queued <configure>s that had the same target and attribute and retrieve the correct oldvalue
518
removeFromQueue(configure.attribute("target"), attributeName, oldValue);
519
wbitem->undos.append(EditUndo(configure.attribute("version").toInt(), attributeName, oldValue));
521
if(!edit.attribute("from").isEmpty() && !edit.attribute("to").isEmpty()) {
523
int from = edit.attribute("from").toInt(&okF);
524
int to = edit.attribute("to").toInt(&okT);
525
if(okF && okT && from <= to) {
527
newValue.replace(from, to - from, edit.text());
529
newValue = edit.text();
531
newValue = edit.text();
532
_svg.setAttribute(attributeName, newValue);
533
} else if(edit.nodeName() == "content") {
534
QDomNodeList oldContent = _svg.cloneNode().childNodes();
535
// Remove queued <configure>s that had the same target and attribute and retrieve the correct oldvalue
536
removeFromQueue(configure.attribute("target"), oldContent);
537
wbitem->undos.append(EditUndo(configure.attribute("version").toInt(), oldContent));
538
while(_svg.hasChildNodes())
539
_svg.removeChild(_svg.firstChild());
540
for(uint j=0; j < edit.childNodes().length(); j++)
541
_svg.appendChild(edit.childNodes().at(j));
542
} else if(edit.nodeName() == "parent") {
543
QString oldParent = newParent;
544
// Remove queued <configure>s that had the same target and attribute and retrieve the correct oldvalue
545
removeFromQueue(configure.attribute("target"), oldParent);
546
wbitem->undos.append(EditUndo(configure.attribute("version").toInt(), oldParent));
547
newParent = edit.text();
551
if(setElement(_svg, newParent, wbitem->id(), wbitem->index())) {
554
wbitem->undos.removeLast();
555
// wbitem->version--;
558
} else if (configure.attribute("version").toInt() < wbitem->version) {
559
// qDebug() << (QString("Reverting <configure/>'s - configure version: %1, current version: %2 target: %3 attribute: %4 value: %5").arg(configure.attribute("version")).arg(wbitem->version-1).arg(configure.attribute("target")).arg(configure.attribute("attribute")).arg(configure.attribute("value")).toAscii());
560
// Revert the changes down to version of the configure - 1.
561
if(!wbitem->undos.isEmpty()) {
562
QString oldParent = wbitem->parentWbItem();
563
while(configure.attribute("version").toInt() <= wbitem->undos.last().version) {
564
EditUndo u = wbitem->undos.takeLast();
565
// qDebug() << (QString("setting %1 to %2").arg(u.attribute).arg(u.oldValue).toAscii());
566
if(u.type == Edit::AttributeEdit) {
567
if(u.oldValue.isNull())
568
_svg.removeAttribute(u.attribute);
570
_svg.setAttribute(u.attribute, u.oldValue);
571
} else if(u.type == Edit::ContentEdit) {
572
while(_svg.hasChildNodes())
573
_svg.removeChild(_svg.firstChild());
574
for(uint j=0; j < u.oldContent.length(); j++)
575
_svg.appendChild(u.oldContent.at(j));
576
} else if(u.type == Edit::ParentEdit) {
577
oldParent = u.oldParent;
579
if(wbitem->undos.isEmpty())
582
if(setElement(_svg, oldParent, wbitem->id(), wbitem->index()))
585
// Freak out! Should never happen.
586
// wbitem->version--;
587
qCritical(QString("Couldn't revert wbitem with id '%1' back to version '%2'").arg(configure.attribute("target")).arg(configure.attribute("version")).toAscii());
591
// The configure was processed succesfully even though no action was taken.
592
// This may happen if the wbitem was already reverted by another configure.
594
} else if (configure.attribute("version").toInt() > wbitem->version) {
595
// This should never happen given the seriality condition.
596
// Reason to worry about misfunction of infrastructure but not much to do from here.
597
qWarning(QString("Configure to wbitem '%1' version '%1' arrived when the wbitem had version '%3'.").arg(configure.attribute("target")).arg(configure.attribute("version")).arg(wbitem->version-1).toAscii());
605
bool WbScene::processMove(const QDomElement &move) {
606
WbItem* wbitem = findWbItem(move.attribute("target"));
608
// qDebug() << (QString("Moving - target: %1 index: %2").arg(move.attribute("target")).arg(move.attribute("di")).toAscii());
609
wbitem->setIndex(wbitem->index() + move.attribute("di").toDouble());
610
if(wbitem->index() > highestIndex_)
611
highestIndex_ = static_cast<int>(wbitem->index());
617
bool WbScene::processRemove(const QDomElement &remove) {
618
if(findWbItem(remove.attribute("target"))) {
619
// qDebug() << (QString("Removing - target: %1").arg(remove.attribute("target")).toAscii());
620
if(removeElement(remove.attribute("target"))) {
627
void WbScene::removeFromQueue(const QString &target, const QString &attribute, QString &oldValue) {
628
// remove queued edits that have the same target and attribute
629
for(int i = queue_.size() - 1; i >= 0; i--) {
630
if(queue_.at(i).type == Edit::AttributeEdit && queue_.at(i).target == target && queue_.at(i).xml.attribute("name") == attribute) {
631
// Set the "older" old value since that's the correct one to undo if necessary;
632
oldValue = queue_.at(i).oldValue;
634
// We can return because there can never be two that match
640
void WbScene::removeFromQueue(const QString &target, QString &oldValue) {
641
// remove queued edits that have the same target and set the parent
642
for(int i = queue_.size() - 1; i >= 0; i--) {
643
if(queue_.at(i).type == Edit::ParentEdit && queue_.at(i).target == target) {
644
// Set the "older" old parent since that's the correct one to undo if necessary;
645
oldValue = queue_.at(i).oldParent;
647
// We can return because there can never be two that match
653
void WbScene::removeFromQueue(const QString &target, QDomNodeList &oldValue) {
654
// remove queued edits that have the same target and set the content
655
for(int i = queue_.size() - 1; i >= 0; i--) {
656
if(queue_.at(i).type == Edit::ContentEdit && queue_.at(i).target == target) {
657
// Set the "older" old content since that's the correct one to undo if necessary;
658
oldValue = queue_.at(i).oldContent;
660
// We can return because there can never be two that match
666
QString WbScene::newId() {
667
return QString("%1/%2").arg(++highestId_).arg(ownJid_);
670
qreal WbScene::newIndex() {
671
return ++highestIndex_;