~ubuntu-branches/ubuntu/karmic/psi/karmic

« back to all changes in this revision

Viewing changes to src/whiteboarding/wbwidget.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jan Niehusmann
  • Date: 2008-08-28 18:46:52 UTC
  • mfrom: (1.2.4 upstream)
  • Revision ID: james.westby@ubuntu.com-20080828184652-iiik12dl91nq7cdi
Tags: 0.12-2
Uploading to unstable (Closes: Bug#494352)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * wbwidget.cpp - a widget for processing and showing whiteboard
 
3
 *                messages.
 
4
 * Copyright (C) 2006  Joonas Govenius
 
5
 *
 
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.
 
10
 *
 
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.
 
15
 *
 
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
 
19
 *
 
20
 */
 
21
 
 
22
#include "wbwidget.h"
 
23
#include "wbnewpath.h"
 
24
#include "wbnewimage.h"
 
25
 
 
26
#include <QMouseEvent>
 
27
#include <QApplication>
 
28
 
 
29
WbWidget::WbWidget(SxeSession* session, QWidget *parent) : QGraphicsView(parent) {
 
30
        newWbItem_ = 0;
 
31
        adding_ = 0;
 
32
        addVertex_ = false;
 
33
        strokeColor_ = Qt::black;
 
34
        fillColor_ = Qt::transparent;
 
35
        strokeWidth_ = 1;
 
36
    session_ = session;
 
37
 
 
38
//      setCacheMode(CacheBackground);
 
39
        setRenderHint(QPainter::Antialiasing);
 
40
        setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
 
41
 
 
42
    // create the scene
 
43
        scene_ = new WbScene(session_, this);
 
44
        scene_->setItemIndexMethod(QGraphicsScene::NoIndex);
 
45
    setRenderHint(QPainter::Antialiasing);
 
46
    setTransformationAnchor(AnchorUnderMouse);
 
47
    setResizeAnchor(AnchorViewCenter);
 
48
    setScene(scene_);
 
49
 
 
50
    // render the initial document
 
51
    rerender();
 
52
    // rerender on update
 
53
    connect(session_, SIGNAL(documentUpdated(bool)), SLOT(handleDocumentUpdated(bool)));
 
54
    
 
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());
 
61
        }
 
62
    }
 
63
    inspectNodes();
 
64
 
 
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)));
 
76
 
 
77
    // set the default mode to select
 
78
        setMode(Select);
 
79
 
 
80
    // set the initial size
 
81
    if(session_->document().documentElement().hasAttribute("viewbox"))
 
82
            checkForViewBoxChange(session_->document().documentElement().attributeNode("viewbox"));
 
83
        else {
 
84
        QSize size;
 
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)
 
89
            setSize(size);
 
90
        else
 
91
            setSize(QSize(400, 600));
 
92
        }
 
93
}
 
94
 
 
95
SxeSession* WbWidget::session() {
 
96
        return session_;
 
97
}
 
98
 
 
99
WbWidget::Mode WbWidget::mode() {
 
100
        return mode_;
 
101
}
 
102
 
 
103
void WbWidget::setMode(Mode mode) {
 
104
     mode_ = mode;
 
105
 
 
106
     if(mode_ < DrawPath) {
 
107
             if(newWbItem_) {
 
108
                     delete newWbItem_;
 
109
                     newWbItem_ = 0;
 
110
             }
 
111
     }
 
112
 
 
113
     if(mode_ >= Erase) {
 
114
             setDragMode(QGraphicsView::NoDrag);
 
115
             setInteractive(false);
 
116
             setCursor(Qt::CrossCursor);
 
117
     } else {
 
118
             setInteractive(true);
 
119
     }
 
120
 
 
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);
 
129
             unsetCursor();
 
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);
 
136
     }
 
137
}
 
138
 
 
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()));
 
141
    session_->flush();
 
142
}
 
143
 
 
144
/*! \brief Generates a QRectF based on \a string provided in the SVG viewbox format. */
 
145
static QRectF parseSvgViewBox(QString string) {
 
146
    QString strings[4];
 
147
    qreal numbers[4];
 
148
    for(int i = 0, j = 0; i < 4; i++) {
 
149
        // skip spaces before number
 
150
        while(string[j].isSpace() && j < string.length())
 
151
            j++;
 
152
 
 
153
        while(!string[j].isSpace() && j < string.length()) {
 
154
            if(string[j].isNumber())
 
155
                strings[i] += string[j];
 
156
            j++;
 
157
        }
 
158
        
 
159
        numbers[i] = strings[i].toDouble();
 
160
    }
 
161
 
 
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]);
 
164
}
 
165
 
 
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);
 
171
        }
 
172
    }
 
173
}
 
174
 
 
175
void WbWidget::setStrokeColor(const QColor &color) {
 
176
        strokeColor_ = color;
 
177
}
 
178
 
 
179
void WbWidget::setFillColor(const QColor &color) {
 
180
        fillColor_ = color;
 
181
}
 
182
 
 
183
void WbWidget::setStrokeWidth(int width) {
 
184
        strokeWidth_ = width;
 
185
}
 
186
 
 
187
void WbWidget::clear() {
 
188
        foreach(QGraphicsItem* graphicsitem, scene_->items()) {
 
189
        WbItem* wbitem = dynamic_cast<WbItem*>(graphicsitem);
 
190
        if(wbitem)
 
191
            session_->removeNode(wbitem->node());
 
192
        }
 
193
    session_->flush();
 
194
}
 
195
 
 
196
QSize WbWidget::sizeHint() const {
 
197
        if(scene_)
 
198
                return scene_->sceneRect().size().toSize();
 
199
        else
 
200
                return QSize();
 
201
}
 
202
 
 
203
void WbWidget::resizeEvent(QResizeEvent * event) {
 
204
        // Never show areas outside the sceneRect
 
205
        // Doesn't consider rotated views.
 
206
        QMatrix t;
 
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)
 
211
                sx *= 1.05;
 
212
        // Never shrink the view. Only enlarge if necessary.
 
213
        if(sx > 1 || sy > 1) {
 
214
                if(sx > sy)
 
215
                        t.scale(sx, sx);
 
216
                else
 
217
                        t.scale(sy, sy);
 
218
        }
 
219
        setMatrix(t);
 
220
        QGraphicsView::resizeEvent(event);
 
221
}
 
222
 
 
223
void WbWidget::mousePressEvent(QMouseEvent * event) {
 
224
    // ignore non-leftclicks when not in Select mode
 
225
        if(event->button() != Qt::LeftButton && mode_ != Select)
 
226
                return;
 
227
 
 
228
    // delete any temporary item being drawn
 
229
    if(newWbItem_) {
 
230
        delete newWbItem_;
 
231
        newWbItem_ = 0;
 
232
    }
 
233
 
 
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_);
 
240
        return;
 
241
        } else if(mode_ == DrawText) {
 
242
 
 
243
        } else if(mode_ == DrawRectangle) {
 
244
 
 
245
        } else if(mode_ == DrawEllipse) {
 
246
 
 
247
        } else if(mode_ == DrawCircle) {
 
248
 
 
249
        } else if(mode_ == DrawLine) {
 
250
 
 
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());
 
256
            session_->flush();
 
257
            delete newWbItem_;
 
258
            newWbItem_ = 0;
 
259
        }
 
260
    }
 
261
 
 
262
    QGraphicsView::mousePressEvent(event);
 
263
}
 
264
 
 
265
void WbWidget::mouseMoveEvent(QMouseEvent * event) {
 
266
    if(mode_ < Erase) {
 
267
        QGraphicsView::mouseMoveEvent(event);
 
268
        return;
 
269
    }
 
270
 
 
271
    if(QApplication::mouseButtons() != Qt::MouseButtons(Qt::LeftButton)) {
 
272
        if(newWbItem_) {
 
273
             delete newWbItem_;
 
274
             newWbItem_ = 0;
 
275
        }
 
276
        return;
 
277
        }
 
278
 
 
279
    if(mode_ == Erase) {
 
280
         if(event->buttons() != Qt::MouseButtons(Qt::LeftButton))
 
281
             return;
 
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);
 
287
            if(wbitem)
 
288
                session_->removeNode(wbitem->node());
 
289
         }
 
290
         delete eraseRect;
 
291
         eraseRect = 0;
 
292
         
 
293
         event->ignore();
 
294
         return;
 
295
    } else if(mode_ >= DrawPath && newWbItem_) {
 
296
        newWbItem_->parseCursorMove(mapToScene(mapFromGlobal(event->globalPos())));
 
297
        }
 
298
}
 
299
 
 
300
void WbWidget::mouseReleaseEvent(QMouseEvent * event) {
 
301
 
 
302
        if(event->button() != Qt::LeftButton && mode_ >= Erase)
 
303
                return;
 
304
 
 
305
        if (newWbItem_ && mode_ >= DrawPath && mode_ != DrawImage) {
 
306
        session_->insertNodeAfter(newWbItem_->serializeToSvg(), session_->document().documentElement());
 
307
        session_->flush();
 
308
        delete newWbItem_;
 
309
        newWbItem_ = 0;
 
310
        return;
 
311
        }
 
312
 
 
313
    QGraphicsView::mouseReleaseEvent(event);
 
314
}
 
315
 
 
316
WbItem* WbWidget::wbItem(const QDomNode &node) {
 
317
    foreach(WbItem* wbitem, items_) {
 
318
        if(wbitem->node() == node)
 
319
            return wbitem;
 
320
    }
 
321
    return 0;
 
322
}
 
323
 
 
324
void WbWidget::handleDocumentUpdated(bool remote) {
 
325
    Q_UNUSED(remote);
 
326
    inspectNodes();
 
327
    rerender();
 
328
}
 
329
 
 
330
void WbWidget::inspectNodes() {
 
331
    while(!recentlyRelocatedNodes_.isEmpty()) {
 
332
        QDomNode node = recentlyRelocatedNodes_.takeFirst();
 
333
        
 
334
        if(!node.isElement())
 
335
            continue;
 
336
 
 
337
        // check if we already have a WbItem for the node
 
338
        WbItem* item = wbItem(node);
 
339
 
 
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()))
 
342
            continue;
 
343
 
 
344
        // Otherwise, either item exists and needs to be removed
 
345
        //              or it doesn't exist and needs to be added
 
346
        if(item)
 
347
            removeWbItem(item);
 
348
        else
 
349
            items_.append(new WbItem(session_, &renderer_, node.toElement(), scene_, this));
 
350
    }
 
351
}
 
352
 
 
353
void WbWidget::queueNodeInspection(const QDomNode &node) {
 
354
    if(node.isElement())
 
355
        recentlyRelocatedNodes_.append(node);
 
356
}
 
357
 
 
358
void WbWidget::removeWbItem(const QDomNode &node) {
 
359
    removeWbItem(wbItem(node));
 
360
}
 
361
 
 
362
void WbWidget::removeWbItem(WbItem *wbitem) {
 
363
    if(wbitem) {
 
364
        // Remove from the lookup table to avoid infinite loop of deletes
 
365
        items_.removeAll(wbitem);
 
366
        // items_.takeAt(items_.indexOf(wbitem));
 
367
 
 
368
        idlessItems_.removeAll(wbitem);
 
369
 
 
370
        delete wbitem;
 
371
    }
 
372
}
 
373
 
 
374
void WbWidget::addIds() {
 
375
    while(!idlessItems_.isEmpty())
 
376
        idlessItems_.takeFirst()->addToScene();
 
377
 
 
378
    session_->flush();
 
379
}
 
380
 
 
381
void WbWidget::addToIdLess(const QDomElement &element) {
 
382
    if(element.parentNode() != session_->document().documentElement())
 
383
        return;
 
384
 
 
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();
 
389
 
 
390
        idlessItems_.append(item);
 
391
 
 
392
        // Try adding the 'id' attribute after a random delay of 0 to 2s
 
393
        QTimer::singleShot(2000 * qrand() / RAND_MAX, this, SLOT(addIds()));
 
394
    }
 
395
}
 
396
 
 
397
void WbWidget::checkForRemovalOfId(QDomNode node) {
 
398
    if(node.isAttr()) {
 
399
        if(node.nodeName() == "id") {
 
400
            while(!(node.isElement() || node.isNull()))
 
401
                node = node.parentNode();
 
402
 
 
403
            if(!node.isNull())
 
404
                addToIdLess(node.toElement());
 
405
        }
 
406
    }
 
407
}
 
408
 
 
409
void WbWidget::rerender() {
 
410
    QString xmldump;
 
411
    QTextStream stream(&xmldump);
 
412
    session_->document().save(stream, 1);
 
413
 
 
414
    // qDebug("Document in WbWidget:");
 
415
    // qDebug(xmldump.toAscii());
 
416
 
 
417
    renderer_.load(xmldump.toAscii());
 
418
 
 
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());
 
423
 
 
424
        // qDebug(QString("Rerendering %1").arg((unsigned int) wbitem).toAscii());
 
425
        wbitem->resetPos();
 
426
    }
 
427
}