2
* wbwidget.cpp - a widget for processing and showing whiteboard
4
* Copyright (C) 2006 Joonas Govenius
6
* This program is free software; you can redistribute it and/or
7
* modify it under the terms of the GNU General Public License
8
* as published by the Free Software Foundation; either version 2
9
* of the License, or (at your option) any later version.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this library; if not, write to the Free Software
18
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
#include "wbnewpath.h"
24
#include "wbnewimage.h"
26
#include <QMouseEvent>
27
#include <QApplication>
29
WbWidget::WbWidget(SxeSession* session, QWidget *parent) : QGraphicsView(parent) {
33
strokeColor_ = Qt::black;
34
fillColor_ = Qt::transparent;
38
// setCacheMode(CacheBackground);
39
setRenderHint(QPainter::Antialiasing);
40
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
43
scene_ = new WbScene(session_, this);
44
scene_->setItemIndexMethod(QGraphicsScene::NoIndex);
45
setRenderHint(QPainter::Antialiasing);
46
setTransformationAnchor(AnchorUnderMouse);
47
setResizeAnchor(AnchorViewCenter);
50
// render the initial document
53
connect(session_, SIGNAL(documentUpdated(bool)), SLOT(handleDocumentUpdated(bool)));
55
// add the initial items
56
const QDomNodeList children = session_->document().documentElement().childNodes();
57
for(uint i = 0; i < children.length(); i++) {
58
const QDomNode node = children.at(i);
59
if(node.isElement()) {
60
queueNodeInspection(node.toElement());
65
// add new items as nodes are added
66
// remove/add items if corresponding nodes are moved
67
connect(session_, SIGNAL(nodeAdded(QDomNode, bool)), SLOT(queueNodeInspection(QDomNode)));
68
connect(session_, SIGNAL(nodeMoved(QDomNode, bool)), SLOT(queueNodeInspection(QDomNode)));
69
// remove items if corresponding nodes are deleted
70
connect(session_, SIGNAL(nodeToBeRemoved(QDomNode, bool)), SLOT(removeWbItem(QDomNode)));
71
connect(session_, SIGNAL(nodeToBeRemoved(QDomNode, bool)), SLOT(checkForRemovalOfId(QDomNode)));
72
// adjust the viewbox as necessary
73
connect(session_, SIGNAL(nodeAdded(QDomNode, bool)), SLOT(checkForViewBoxChange(QDomNode)));
74
connect(session_, SIGNAL(nodeMoved(QDomNode, bool)), SLOT(checkForViewBoxChange(QDomNode)));
75
connect(session_, SIGNAL(chdataChanged(QDomNode, bool)), SLOT(checkForViewBoxChange(QDomNode)));
77
// set the default mode to select
80
// set the initial size
81
if(session_->document().documentElement().hasAttribute("viewbox"))
82
checkForViewBoxChange(session_->document().documentElement().attributeNode("viewbox"));
85
QRectF rect = scene_->sceneRect();
86
size.setWidth(rect.x() + rect.width());
87
size.setHeight(rect.y() + rect.height());
88
if(size.width() > 0 && size.height() > 0)
91
setSize(QSize(400, 600));
95
SxeSession* WbWidget::session() {
99
WbWidget::Mode WbWidget::mode() {
103
void WbWidget::setMode(Mode mode) {
106
if(mode_ < DrawPath) {
114
setDragMode(QGraphicsView::NoDrag);
115
setInteractive(false);
116
setCursor(Qt::CrossCursor);
118
setInteractive(true);
121
if(mode_ == Select) {
122
setDragMode(QGraphicsView::RubberBandDrag);
123
setCursor(Qt::ArrowCursor);
124
} else if(mode_ == Translate) {
125
setDragMode(QGraphicsView::RubberBandDrag);
126
setCursor(Qt::SizeAllCursor);
127
} else if(mode_ == Rotate) {
128
setDragMode(QGraphicsView::RubberBandDrag);
130
// TODO: load cursor from image
131
} else if(mode_ == Scale) {
132
setDragMode(QGraphicsView::RubberBandDrag);
133
setCursor(Qt::SizeBDiagCursor);
134
} else if(mode_ == Scroll) {
135
setDragMode(QGraphicsView::ScrollHandDrag);
139
void WbWidget::setSize(const QSize &size) {
140
session_->setAttribute(session_->document().documentElement(), "viewbox", QString("0 0 %1 %2").arg(size.width()).arg(size.height()));
144
/*! \brief Generates a QRectF based on \a string provided in the SVG viewbox format. */
145
static QRectF parseSvgViewBox(QString string) {
148
for(int i = 0, j = 0; i < 4; i++) {
149
// skip spaces before number
150
while(string[j].isSpace() && j < string.length())
153
while(!string[j].isSpace() && j < string.length()) {
154
if(string[j].isNumber())
155
strings[i] += string[j];
159
numbers[i] = strings[i].toDouble();
162
// qDebug(QString("QRectF(%1 %2 %3 %4)").arg(numbers[0]).arg(numbers[1]).arg(numbers[2]).arg(numbers[3]).toAscii());
163
return QRect(numbers[0], numbers[1], numbers[2], numbers[3]);
166
void WbWidget::checkForViewBoxChange(const QDomNode &node) {
167
if(node.isAttr() && node.nodeName().toLower() == "viewbox" && node.parentNode() == session_->document().documentElement()) {
168
QRectF box = parseSvgViewBox(node.nodeValue());
169
if(box.width() > 0 && box.height() > 0) {
170
scene_->setSceneRect(box);
175
void WbWidget::setStrokeColor(const QColor &color) {
176
strokeColor_ = color;
179
void WbWidget::setFillColor(const QColor &color) {
183
void WbWidget::setStrokeWidth(int width) {
184
strokeWidth_ = width;
187
void WbWidget::clear() {
188
foreach(QGraphicsItem* graphicsitem, scene_->items()) {
189
WbItem* wbitem = dynamic_cast<WbItem*>(graphicsitem);
191
session_->removeNode(wbitem->node());
196
QSize WbWidget::sizeHint() const {
198
return scene_->sceneRect().size().toSize();
203
void WbWidget::resizeEvent(QResizeEvent * event) {
204
// Never show areas outside the sceneRect
205
// Doesn't consider rotated views.
207
qreal sx = event->size().width() / scene_->sceneRect().width();
208
qreal sy = event->size().height() / scene_->sceneRect().height();
209
// Skip through the boundary region where the scroll bars change to avoid a nasty loop
210
if((sx-sy)/sx < 0.03 && (sy-sx)/sx < 0.03)
212
// Never shrink the view. Only enlarge if necessary.
213
if(sx > 1 || sy > 1) {
220
QGraphicsView::resizeEvent(event);
223
void WbWidget::mousePressEvent(QMouseEvent * event) {
224
// ignore non-leftclicks when not in Select mode
225
if(event->button() != Qt::LeftButton && mode_ != Select)
228
// delete any temporary item being drawn
234
QPointF startPoint = mapToScene(mapFromGlobal(event->globalPos()));
235
if(mode_ == DrawPath) {
236
// // Create the element with starting position
237
// QPointF sp = mapToScene(mapFromGlobal(event->globalPos()));
238
// qDebug(QString("1: (%1, %2)").arg(sp.x()).arg(sp.y()));
239
newWbItem_ = new WbNewPath(scene_, startPoint, strokeWidth_, strokeColor_, fillColor_);
241
} else if(mode_ == DrawText) {
243
} else if(mode_ == DrawRectangle) {
245
} else if(mode_ == DrawEllipse) {
247
} else if(mode_ == DrawCircle) {
249
} else if(mode_ == DrawLine) {
251
} else if(mode_ == DrawImage) {
252
QString filename = QFileDialog::getOpenFileName(this, "Choose an image", QString(), "Images (*.png *.jpg)");
253
if(!filename.isEmpty()) {
254
newWbItem_ = new WbNewImage(scene_, startPoint, filename);
255
session_->insertNodeAfter(newWbItem_->serializeToSvg(), session_->document().documentElement());
262
QGraphicsView::mousePressEvent(event);
265
void WbWidget::mouseMoveEvent(QMouseEvent * event) {
267
QGraphicsView::mouseMoveEvent(event);
271
if(QApplication::mouseButtons() != Qt::MouseButtons(Qt::LeftButton)) {
280
if(event->buttons() != Qt::MouseButtons(Qt::LeftButton))
282
// Erase all items that appear in a 2*strokeWidth_ square with center at the event position
283
QPointF p = mapToScene(mapFromGlobal(event->globalPos()));
284
QGraphicsRectItem* eraseRect = scene_->addRect(QRectF(p.x() - strokeWidth_, p.y() - strokeWidth_, 2 * strokeWidth_, 2 * strokeWidth_));
285
foreach(QGraphicsItem * item, eraseRect->collidingItems()) {
286
WbItem* wbitem = dynamic_cast<WbItem*>(item);
288
session_->removeNode(wbitem->node());
295
} else if(mode_ >= DrawPath && newWbItem_) {
296
newWbItem_->parseCursorMove(mapToScene(mapFromGlobal(event->globalPos())));
300
void WbWidget::mouseReleaseEvent(QMouseEvent * event) {
302
if(event->button() != Qt::LeftButton && mode_ >= Erase)
305
if (newWbItem_ && mode_ >= DrawPath && mode_ != DrawImage) {
306
session_->insertNodeAfter(newWbItem_->serializeToSvg(), session_->document().documentElement());
313
QGraphicsView::mouseReleaseEvent(event);
316
WbItem* WbWidget::wbItem(const QDomNode &node) {
317
foreach(WbItem* wbitem, items_) {
318
if(wbitem->node() == node)
324
void WbWidget::handleDocumentUpdated(bool remote) {
330
void WbWidget::inspectNodes() {
331
while(!recentlyRelocatedNodes_.isEmpty()) {
332
QDomNode node = recentlyRelocatedNodes_.takeFirst();
334
if(!node.isElement())
337
// check if we already have a WbItem for the node
338
WbItem* item = wbItem(node);
340
// We don't need to do anything iff node is child of <svg/> and item exists or vice versa
341
if((item != NULL) == (node.parentNode() == session_->document().documentElement()))
344
// Otherwise, either item exists and needs to be removed
345
// or it doesn't exist and needs to be added
349
items_.append(new WbItem(session_, &renderer_, node.toElement(), scene_, this));
353
void WbWidget::queueNodeInspection(const QDomNode &node) {
355
recentlyRelocatedNodes_.append(node);
358
void WbWidget::removeWbItem(const QDomNode &node) {
359
removeWbItem(wbItem(node));
362
void WbWidget::removeWbItem(WbItem *wbitem) {
364
// Remove from the lookup table to avoid infinite loop of deletes
365
items_.removeAll(wbitem);
366
// items_.takeAt(items_.indexOf(wbitem));
368
idlessItems_.removeAll(wbitem);
374
void WbWidget::addIds() {
375
while(!idlessItems_.isEmpty())
376
idlessItems_.takeFirst()->addToScene();
381
void WbWidget::addToIdLess(const QDomElement &element) {
382
if(element.parentNode() != session_->document().documentElement())
385
WbItem* item = wbItem(element);
386
if(item && !idlessItems_.contains(item)) {
387
// Remove from the scene until a new 'id' attribute is added
388
item->removeFromScene();
390
idlessItems_.append(item);
392
// Try adding the 'id' attribute after a random delay of 0 to 2s
393
QTimer::singleShot(2000 * qrand() / RAND_MAX, this, SLOT(addIds()));
397
void WbWidget::checkForRemovalOfId(QDomNode node) {
399
if(node.nodeName() == "id") {
400
while(!(node.isElement() || node.isNull()))
401
node = node.parentNode();
404
addToIdLess(node.toElement());
409
void WbWidget::rerender() {
411
QTextStream stream(&xmldump);
412
session_->document().save(stream, 1);
414
// qDebug("Document in WbWidget:");
415
// qDebug(xmldump.toAscii());
417
renderer_.load(xmldump.toAscii());
419
// Update all positions if changed
420
foreach(WbItem* wbitem, items_) {
421
// resetting elementId is necessary for rendering some updates to the element (e.g. adding child elements to <g/>)
422
wbitem->setElementId(wbitem->id());
424
// qDebug(QString("Rerendering %1").arg((unsigned int) wbitem).toAscii());