Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

some protocol changes and preliminary layer support

  • Loading branch information...
commit a1a21123f9391f29e869c13801eca07c4d280e3c 1 parent 341a6a6
Calle Laakkonen authored
Showing with 1,308 additions and 169 deletions.
  1. +4 −0 ChangeLog
  2. +2 −2 doc/dox/protocol.dox
  3. +8 −5 src/client/CMakeLists.txt
  4. +2 −2 src/client/annotationitem.cpp
  5. +65 −11 src/client/board.cpp
  6. +19 −0 src/client/board.h
  7. +51 −3 src/client/boardeditor.cpp
  8. +16 −1 src/client/boardeditor.h
  9. +11 −6 src/client/boarditem.cpp
  10. +10 −9 src/client/boarditem.h
  11. +13 −9 src/client/brushpreview.cpp
  12. +2 −2 src/client/brushpreview.h
  13. +17 −0 src/client/controller.cpp
  14. +8 −0 src/client/controller.h
  15. +15 −15 src/client/core/brush.cpp
  16. +12 −12 src/client/core/brush.h
  17. +62 −38 src/client/core/layer.cpp
  18. +28 −8 src/client/core/layer.h
  19. +251 −0 src/client/core/layerstack.cpp
  20. +110 −0 src/client/core/layerstack.h
  21. +5 −5 src/client/core/rasterop.cpp
  22. +6 −14 src/client/core/tile.cpp
  23. +7 −4 src/client/core/tile.h
  24. +97 −0 src/client/layerlistdelegate.cpp
  25. +49 −0 src/client/layerlistdelegate.h
  26. +115 −0 src/client/layerlistwidget.cpp
  27. +71 −0 src/client/layerlistwidget.h
  28. +24 −2 src/client/mainwindow.cpp
  29. +4 −0 src/client/mainwindow.h
  30. +22 −0 src/client/sessionstate.cpp
  31. +10 −0 src/client/sessionstate.h
  32. +68 −0 src/client/ui/layerbox.ui
  33. +22 −6 src/client/user.cpp
  34. +20 −5 src/client/user.h
  35. +1 −1  src/client/version.h
  36. +1 −1  src/shared/net/constants.h
  37. +10 −6 src/shared/net/protocol.cpp
  38. +2 −1  src/shared/net/protocol.h
  39. +12 −0 src/shared/net/toolselect.cpp
  40. +44 −0 src/shared/net/toolselect.h
  41. +5 −1 src/shared/server/client.cpp
  42. +4 −0 src/shared/server/client.h
  43. +3 −0  src/shared/server/server.cpp
4 ChangeLog
View
@@ -1,3 +1,7 @@
+2008-??-?? Version 0.7.0
+ * Protocol change: packet length is now comes first in the header
+ * Layer support
+
2008-10-20 Version 0.6.0
* Subpixel drawing
* Added pen tool (draw without antialiasing)
4 doc/dox/protocol.dox
View
@@ -14,8 +14,8 @@ for passing the packets, and the packet format is described below.
\section msg_structure Message structure
-All packets are prefixed with an 8 bit packet identifier and 16 bit
-packet payload length value.
+All packets are prefixed with 16 bit packet length value and an 8 bit packet
+identifier. The length is the length of the payload + 1 for the type field.
At minimum, the packet consists only of the packet type and nothing else.
The rest depends on the packet itself.
13 src/client/CMakeLists.txt
View
@@ -15,7 +15,8 @@ set (
localserver.h popupmessage.h userlistwidget.h userlistmodel.h chatwidget.h
mandatoryfields.h palettewidget.h palettebox.h
colorbox.h settingsdialog.h
- navigator.h toolsettings.h brushslider.h viewstatus.h
+ navigator.h toolsettings.h brushslider.h viewstatus.h layerlistdelegate.h
+ layerlistwidget.h core/layerstack.h
)
# unused headers because they do not create moc_* files ()
@@ -24,7 +25,8 @@ set (
version.h user.h interfaces.h preview.h boardeditor.h boarditem.h
localpalette.h palette.h icons.h
tools.h recentfiles.h sessioninfo.h annotationitem.h
- core/tile.h core/layer.h core/brush.h core/point.h core/rasterop.h
+ core/tile.h core/layer.h core/brush.h core/point.h
+ core/rasterop.h
)
set (
@@ -40,15 +42,16 @@ set (
sessioninfo.cpp icons.cpp mandatoryfields.cpp
localpalette.cpp palettewidget.cpp palettebox.cpp colorbox.cpp
settingsdialog.cpp palette.cpp navigator.cpp annotationitem.cpp
- core/tile.cpp core/layer.cpp core/brush.cpp core/rasterop.cpp
- brushslider.cpp viewstatus.cpp
+ core/tile.cpp core/layer.cpp core/layerstack.cpp core/brush.cpp
+ core/rasterop.cpp brushslider.cpp viewstatus.cpp layerlistdelegate.cpp
+ layerlistwidget.cpp
)
set ( UIs ui/brushsettings.ui ui/simplesettings.ui ui/colordialog.ui
ui/newdialog.ui ui/hostdialog.ui ui/joindialog.ui ui/logindialog.ui
ui/chatbox.ui ui/palettebox.ui ui/colorbox.ui
ui/settings.ui ui/navibox.ui ui/textsettings.ui ui/pensettings.ui
- ui/erasersettings.ui )
+ ui/erasersettings.ui ui/layerbox.ui )
# .UI
4 src/client/annotationitem.cpp
View
@@ -206,7 +206,7 @@ dpcore::Layer *AnnotationItem::toLayer(int *x, int *y)
QPoint offset(int(pos().x() - xi*Tile::SIZE),
int(pos().y() - yi*Tile::SIZE));
QImage img(offset.x() + int(size_.width()),
- int(offset.y() + size_.height()), QImage::Format_ARGB32);
+ int(offset.y() + size_.height()), QImage::Format_ARGB32_Premultiplied);
img.fill(0);
QPainter painter(&img);
render(&painter, QRectF(offset, size_));
@@ -214,7 +214,7 @@ dpcore::Layer *AnnotationItem::toLayer(int *x, int *y)
*x = xi;
if(y)
*y = yi;
- return new Layer(img);
+ return new Layer(0, -1, "", img);
}
}
76 src/client/board.cpp
View
@@ -26,14 +26,14 @@
#include "boardeditor.h"
#include "preview.h"
#include "interfaces.h"
-#include "core/layer.h"
+#include "core/layerstack.h"
#include "../shared/net/annotation.h"
#include "../shared/net/message.h"
namespace drawingboard {
Board::Board(QObject *parent, interface::BrushSource *brush, interface::ColorSource *color)
- : QGraphicsScene(parent), image_(0),localuser_(-1), toolpreview_(0), brushsrc_(brush), colorsrc_(color), hla_(false)
+ : QGraphicsScene(parent), image_(0),localuser_(-1), toolpreview_(0), brushsrc_(brush), colorsrc_(color), hla_(false), layerwidget_(0)
{
setItemIndexMethod(NoIndex);
}
@@ -69,7 +69,7 @@ void Board::initBoard(QImage image)
image_ = new BoardItem(image.convertToFormat(QImage::Format_RGB32));
addItem(image_);
foreach(User *u, users_)
- u->setLayer(image_);
+ u->setBoard(image_);
QList<QRectF> regions;
regions.append(sceneRect());
emit changed(regions);
@@ -156,8 +156,7 @@ void Board::addUser(int id)
qDebug() << "Reusing board user" << id;
delete users_.take(id);
}
- User *user = new User(id);
- user->setLayer(image_);
+ User *user = new User(image_, id);
users_[id] = user;
}
@@ -170,6 +169,20 @@ void Board::setLocalUser(int id)
{
Q_ASSERT(users_.contains(id));
localuser_ = id;
+ // Set the layer list if we know it already
+ if(layerwidget_)
+ users_[localuser_]->setLayerList(layerwidget_);
+}
+
+/**
+ * @param llist layer list widget
+ */
+void Board::setLayerList(widgets::LayerList *llist)
+{
+ layerwidget_ = llist;
+ // Update local user if it exists
+ if(localuser_ != -1)
+ users_[localuser_]->setLayerList(layerwidget_);
}
/**
@@ -178,7 +191,7 @@ void Board::setLocalUser(int id)
QImage Board::image() const
{
if(image_)
- return image_->image()->toImage();
+ return image_->image()->toFlatImage();
else
return QImage();
}
@@ -220,6 +233,16 @@ int Board::height() const {
}
/**
+ * @return layer stack
+ */
+dpcore::LayerStack *Board::layers()
+{
+ if(image_)
+ return image_->image();
+ return 0;
+}
+
+/**
* Returns a BoardEditor for modifying the drawing board either
* directly or over the network.
* @param session which network session the editor works over. If 0, a local editor is returned
@@ -248,7 +271,7 @@ void Board::addPreview(const dpcore::Point& point)
Preview *pre;
if(previewcache_.isEmpty()) {
- pre = new StrokePreview(user->layer());
+ pre = new StrokePreview(user->board());
} else
pre = previewcache_.dequeue();
if(previewstarted_) {
@@ -278,10 +301,10 @@ void Board::commitPreviews()
while(previews_.isEmpty()==false) {
qreal distance;
Preview *p = previews_.dequeue();
- if(p->from() != lastpoint)
- image_->drawPoint(p->from(), p->brush());
- else
- image_->drawLine(p->from(), p->to(), p->brush(), distance);
+ if(p->from() != lastpoint) // TODO
+ image_->drawPoint(0, p->from(), p->brush());
+ else // TODO
+ image_->drawLine(0, p->from(), p->to(), p->brush(), distance);
lastpoint = p->to();
delete p;
}
@@ -389,6 +412,37 @@ void Board::unannotate(int id)
}
}
+void Board::addLayer(const QString& name)
+{
+ layers()->addLayer(name, layers()->size());
+}
+
+/**
+ * The layer is removed and all users active layers are changed to point
+ * to something else.
+ * @param layer id
+ */
+void Board::deleteLayer(int id)
+{
+ const int index = layers()->id2index(id);
+ if(index<0) {
+ qWarning() << "Tried to delete nonexistent layer";
+ }
+
+ // Fix user layers
+ foreach(User *u, users_) {
+ if(u->layer() == index) {
+ if(index==0)
+ u->setLayerId(1);
+ else
+ u->setLayerId(index-1);
+ }
+ }
+
+ // Delete the layer
+ layers()->deleteLayer(id);
+ update();
+}
}
19 src/client/board.h
View
@@ -41,6 +41,11 @@ namespace interface {
namespace dpcore {
class Brush;
+ class LayerStack;
+}
+
+namespace widgets {
+ class LayerList;
}
//! Drawing board related classes
@@ -73,12 +78,18 @@ class Board : public QGraphicsScene
//! Initialize the board using an existing image as base
void initBoard(QImage image);
+ //! Set the layer list widget to update when local user's layer changes
+ void setLayerList(widgets::LayerList *llist);
+
//! Get board width
int width() const;
//! Get board height
int height() const;
+ //! Get the layers
+ dpcore::LayerStack *layers();
+
//! Get board contents as an image
QImage image() const;
@@ -137,6 +148,12 @@ class Board : public QGraphicsScene
//! Remove an annotation
void unannotate(int id);
+ //! Create a new layer
+ void addLayer(const QString& name);
+
+ //! Delete a layer
+ void deleteLayer(int id);
+
signals:
//! The local user just created a new annotation
void newLocalAnnotation(drawingboard::AnnotationItem *item);
@@ -180,6 +197,8 @@ class Board : public QGraphicsScene
interface::ColorSource *colorsrc_;
bool hla_;
+
+ widgets::LayerList *layerwidget_;
};
}
54 src/client/boardeditor.cpp
View
@@ -30,6 +30,7 @@
#include "preview.h"
#include "annotationitem.h"
#include "core/layer.h"
+#include "core/layerstack.h"
#include "../shared/net/annotation.h"
namespace drawingboard {
@@ -111,9 +112,9 @@ void BoardEditor::startPreview(tools::Type tool, const dpcore::Point& point, con
Q_ASSERT(board_->toolpreview_ == 0);
Q_ASSERT(tool == tools::LINE || tool == tools::RECTANGLE || tool == tools::ANNOTATION);
if(tool == tools::LINE)
- board_->toolpreview_ = new StrokePreview(user_->layer());
+ board_->toolpreview_ = new StrokePreview(user_->board());
else
- board_->toolpreview_ = new RectanglePreview(user_->layer());
+ board_->toolpreview_ = new RectanglePreview(user_->board());
board_->toolpreview_->preview(point,point, brush);
}
@@ -138,7 +139,7 @@ void BoardEditor::endPreview()
*/
void BoardEditor::mergeLayer(int x, int y, const dpcore::Layer *layer)
{
- board_->image_->image()->merge(x, y, layer);
+ board_->image_->image()->getLayer(user_->layer())->merge(x, y, layer);
}
/**
@@ -159,6 +160,31 @@ void LocalBoardEditor::setTool(const dpcore::Brush& brush)
}
/**
+ * @param id layer id
+ */
+void LocalBoardEditor::setLayer(int id)
+{
+ user_->setLayerId(id);
+}
+
+/**
+ * A new empty layer is created on top of the layer stack
+ * @param name layer name
+ */
+void LocalBoardEditor::createLayer(const QString& name)
+{
+ board_->addLayer(name);
+}
+
+/**
+ * @param id layer ID
+ */
+void LocalBoardEditor::deleteLayer(int id)
+{
+ board_->deleteLayer(id);
+}
+
+/**
* @param point point to add
*/
void LocalBoardEditor::addStroke(const dpcore::Point& point)
@@ -224,6 +250,28 @@ void RemoteBoardEditor::setTool(const dpcore::Brush& brush)
}
/**
+ * @param id layer id
+ */
+void RemoteBoardEditor::setLayer(int id)
+{
+ session_->sendLayerSelect(id);
+}
+
+void RemoteBoardEditor::createLayer(const QString& name)
+{
+ // TODO
+}
+
+/**
+ * Layer deletion is not supported while in a network session.
+ * @param id layer ID
+ */
+void RemoteBoardEditor::deleteLayer(int id)
+{
+ qWarning() << "BUG: Tried to delete layer ID" << id << "while in a network session!";
+}
+
+/**
* In atomic mode, all strokes until strokeEnd are sent in a single long
* message. This is slightly more efficient and ensures no brush strokes
* from other users can be interleaved with the components of this stroke.
17 src/client/boardeditor.h
View
@@ -95,12 +95,21 @@ class BoardEditor {
//! Remove the preview
void endPreview();
- //! Merge a layer
+ //! Create an empty layer
+ virtual void createLayer(const QString& name) = 0;
+
+ //! Delete a layer
+ virtual void deleteLayer(int id) = 0;
+
+ //! Merge a layer with the currently selected layer
void mergeLayer(int x, int y, const dpcore::Layer *layer);
//! Set the tool used for drawing
virtual void setTool(const dpcore::Brush& brush) = 0;
+ //! Select the layer to draw on
+ virtual void setLayer(int id) = 0;
+
//! Make strokes until endStroke atomic
virtual void startAtomic() = 0;
@@ -148,6 +157,9 @@ class LocalBoardEditor : public BoardEditor {
: BoardEditor(board,user, brush, color) {}
void setTool(const dpcore::Brush& brush);
+ void setLayer(int id);
+ void createLayer(const QString& name);
+ void deleteLayer(int id);
// Atomic strokes are meaningles in local mode
void startAtomic() { }
void addStroke(const dpcore::Point& point);
@@ -169,6 +181,9 @@ class RemoteBoardEditor : public BoardEditor {
bool isCurrentBrush(const dpcore::Brush& brush) const;
void setTool(const dpcore::Brush& brush);
+ void createLayer(const QString& name);
+ void deleteLayer(int id);
+ void setLayer(int id);
void startAtomic();
void addStroke(const dpcore::Point& point);
void endStroke();
17 src/client/boarditem.cpp
View
@@ -26,6 +26,7 @@
#include "boarditem.h"
#include "core/point.h"
#include "core/brush.h"
+#include "core/layerstack.h"
#include "core/layer.h"
namespace drawingboard {
@@ -62,7 +63,10 @@ void BoardItem::setImage(const QImage& image)
Q_ASSERT(image.format() == QImage::Format_RGB32 || image.format() == QImage::Format_ARGB32);
prepareGeometryChange();
delete image_;
- image_ = new dpcore::Layer(image);
+ image_ = new dpcore::LayerStack();
+ image_->addLayer(dpcore::LayerStack::tr("Background"), image);
+ // TODO testing...
+ image_->addLayer("Testing...", image.size());
}
/**
@@ -71,7 +75,7 @@ void BoardItem::setImage(const QImage& image)
* First pixel is not drawn. This is done on purpose, as drawLine is usually
* used to draw multiple joined lines.
*
- * If distance is not null, it is used to add spacing between dabs.
+ * @param layer ID of the layer on which to draw
* @param point1 start coordinates
* @param point2 end coordinates
* @param brush brush to draw with
@@ -79,22 +83,23 @@ void BoardItem::setImage(const QImage& image)
*
* @todo delta pressure(?)
*/
-void BoardItem::drawLine(const dpcore::Point& point1, const dpcore::Point& point2, const dpcore::Brush& brush,qreal &distance)
+void BoardItem::drawLine(int layer, const dpcore::Point& point1, const dpcore::Point& point2, const dpcore::Brush& brush,qreal &distance)
{
- image_->drawLine(brush, point1, point2, distance);
+ image_->getLayer(layer)->drawLine(brush, point1, point2, distance);
// Update screen
int rad = brush.radius(point1.pressure());
update(QRect(point1, point2).normalized().adjusted(-rad-2,-rad-2,rad+2,rad+2));
}
/**
+ * @param layer ID of the layer on which to draw
* @param point coordinates
* @param brush brush to use
*/
-void BoardItem::drawPoint(const dpcore::Point& point, const dpcore::Brush& brush)
+void BoardItem::drawPoint(int layer, const dpcore::Point& point, const dpcore::Brush& brush)
{
int r = brush.radius(point.pressure());
- image_->dab(brush, point);
+ image_->getLayer(layer)->dab(brush, point);
update(point.x()-r-2,point.y()-r-2,r*2+4,r*2+4);
}
19 src/client/boarditem.h
View
@@ -24,17 +24,17 @@
namespace dpcore {
class Layer;
+ class LayerStack;
class Brush;
class Point;
}
namespace drawingboard {
-//! A drawing layer item item for QGraphicsScene
+//! A drawing board item item for QGraphicsScene
/**
- * The layer item provides a modifiable image item for QGraphicsScene.
+ * The board item provides an interface to a LayerStack for QGraphicsScene.
* Methods are provided for drawing lines and points with a Brush object.
- *
*/
class BoardItem : public QGraphicsItem
{
@@ -51,14 +51,16 @@ class BoardItem : public QGraphicsItem
void setImage(const QImage& image);
//! Get the image
- dpcore::Layer *image() const { return image_; }
+ dpcore::LayerStack *image() const { return image_; }
//! Draw a line between two points with interpolated pressure values
- void drawLine(const dpcore::Point& point1, const dpcore::Point& point2,
- const dpcore::Brush& brush, qreal &distance);
+ void drawLine(int layer,const dpcore::Point& point1,
+ const dpcore::Point& point2, const dpcore::Brush& brush,
+ qreal &distance);
//! Draw a single point
- void drawPoint(const dpcore::Point& point, const dpcore::Brush& brush);
+ void drawPoint(int layer, const dpcore::Point& point,
+ const dpcore::Brush& brush);
/** reimplematation */
QRectF boundingRect() const;
@@ -70,8 +72,7 @@ class BoardItem : public QGraphicsItem
private:
- dpcore::Layer *image_;
-
+ dpcore::LayerStack *image_;
int plastx_, plasty_;
};
22 src/client/brushpreview.cpp
View
@@ -23,6 +23,7 @@
#include <cmath>
#include "core/point.h"
+#include "core/layerstack.h"
#include "core/layer.h"
#include "brushpreview.h"
@@ -90,14 +91,17 @@ void BrushPreview::paintEvent(QPaintEvent *event)
void BrushPreview::updatePreview()
{
if(preview_==0) {
- preview_ = new dpcore::Layer(QColor(0,0,0), contentsRect().size());
+ preview_ = new dpcore::LayerStack();
+ preview_->addLayer("", QColor(0,0,0), contentsRect().size());
} else if(preview_->width() != contentsRect().width() || preview_->height() != contentsRect().height()) {
// TODO resize more nicely
delete preview_;
- preview_ = new dpcore::Layer(QColor(0,0,0), contentsRect().size());
+ preview_ = new dpcore::LayerStack();
+ preview_->addLayer("", QColor(0,0,0), contentsRect().size());
}
+ dpcore::Layer *layer = preview_->getLayerByIndex(0);
- preview_->fillChecker(palette().light().color(), palette().mid().color());
+ layer->fillChecker(palette().light().color(), palette().mid().color());
const int strokew = preview_->width() - preview_->width()/4;
const int strokeh = preview_->height() / 4;
@@ -114,7 +118,7 @@ void BrushPreview::updatePreview()
const qreal fx = x/qreal(strokew);
const qreal pressure = qBound(0.0, ((fx*fx) - (fx*fx*fx))*6.756, 1.0);
const int y = qRound(sin(phase) * strokeh);
- preview_->drawLine(brush_,
+ layer->drawLine(brush_,
dpcore::Point(offx+lastx,offy+lasty, lastp),
dpcore::Point(offx+x, offy+y, pressure), distance);
lastx = x;
@@ -122,25 +126,25 @@ void BrushPreview::updatePreview()
lastp = pressure;
}
} else if(shape_ == Line) {
- preview_->drawLine(brush_,
+ layer->drawLine(brush_,
dpcore::Point(offx, offy, 1),
dpcore::Point(offx+strokew, offy, 1),
distance
);
} else {
- preview_->drawLine(brush_,
+ layer->drawLine(brush_,
dpcore::Point(offx, offy-strokeh, 1),
dpcore::Point(offx+strokew, offy-strokeh, 1),
distance);
- preview_->drawLine(brush_,
+ layer->drawLine(brush_,
dpcore::Point(offx+strokew, offy-strokeh, 1),
dpcore::Point(offx+strokew, offy+strokeh, 1),
distance);
- preview_->drawLine(brush_,
+ layer->drawLine(brush_,
dpcore::Point(offx+strokew, offy+strokeh, 1),
dpcore::Point(offx, offy+strokeh, 1),
distance);
- preview_->drawLine(brush_,
+ layer->drawLine(brush_,
dpcore::Point(offx, offy+strokeh, 1),
dpcore::Point(offx, offy-strokeh, 1),
distance);
4 src/client/brushpreview.h
View
@@ -25,7 +25,7 @@
#include "core/brush.h"
namespace dpcore {
- class Layer;
+ class LayerStack;
}
#ifndef DESIGNER_PLUGIN
@@ -116,7 +116,7 @@ class PLUGIN_EXPORT BrushPreview : public QFrame {
dpcore::Brush brush_;
//QImage preview_;
- dpcore::Layer *preview_;
+ dpcore::LayerStack *preview_;
QPixmap bg_;
bool sizepressure_;
bool opacitypressure_;
17 src/client/controller.cpp
View
@@ -70,6 +70,8 @@ void Controller::setModel(drawingboard::Board *board)
toolbox_.aeditor(), SLOT(setSelection(drawingboard::AnnotationItem*)));
connect(board, SIGNAL(annotationDeleted(drawingboard::AnnotationItem*)),
toolbox_.aeditor(), SLOT(unselect(drawingboard::AnnotationItem*)));
+ // TODO testing...
+ //toolbox_.editor()->setLayer(1);
}
/**
@@ -445,6 +447,21 @@ void Controller::setTool(tools::Type tool)
tool_ = toolbox_.get(tool);
}
+void Controller::selectLayer(int id)
+{
+ toolbox_.editor()->setLayer(id);
+}
+
+void Controller::newLayer(const QString& name)
+{
+ toolbox_.editor()->createLayer(name);
+}
+
+void Controller::deleteLayer(int id, bool mergedown)
+{
+ toolbox_.editor()->deleteLayer(id);
+}
+
void Controller::penDown(const dpcore::Point& point)
{
if(lock_ == false || (lock_ && tool_->readonly())) {
8 src/client/controller.h
View
@@ -107,6 +107,14 @@ class Controller : public QObject
//! Send chat message
void sendChat(const QString& msg);
+ //! Select a layer
+ void selectLayer(int id);
+
+ //! Add a new blank layer
+ void newLayer(const QString& name);
+ //! Delete or merge a layer
+ void deleteLayer(int id, bool mergedown);
+
void penDown(const dpcore::Point& point);
void penMove(const dpcore::Point& point);
void penUp();
30 src/client/core/brush.cpp
View
@@ -69,7 +69,7 @@ void Brush::setRadius(int radius)
Q_ASSERT(radius>=0);
radius1_ = radius;
checkSensitivity();
- cache_ = RenderedBrush();
+ cache_ = BrushMask();
}
/**
@@ -82,7 +82,7 @@ void Brush::setHardness(qreal hardness)
Q_ASSERT(hardness>=0 && hardness<=1);
hardness1_ = hardness;
checkSensitivity();
- cache_ = RenderedBrush();
+ cache_ = BrushMask();
}
/**
@@ -95,7 +95,7 @@ void Brush::setOpacity(qreal opacity)
Q_ASSERT(opacity>=0 && opacity<=1);
opacity1_ = opacity;
checkSensitivity();
- cache_ = RenderedBrush();
+ cache_ = BrushMask();
}
/**
@@ -118,7 +118,7 @@ void Brush::setRadius2(int radius)
Q_ASSERT(radius>=0);
radius2_ = radius;
checkSensitivity();
- cache_ = RenderedBrush();
+ cache_ = BrushMask();
}
/**
@@ -131,7 +131,7 @@ void Brush::setHardness2(qreal hardness)
Q_ASSERT(hardness>=0 && hardness<=1);
hardness2_ = hardness;
checkSensitivity();
- cache_ = RenderedBrush();
+ cache_ = BrushMask();
}
/**
@@ -144,7 +144,7 @@ void Brush::setOpacity2(qreal opacity)
Q_ASSERT(opacity>=0 && opacity<=1);
opacity2_ = opacity;
checkSensitivity();
- cache_ = RenderedBrush();
+ cache_ = BrushMask();
}
/**
@@ -286,7 +286,7 @@ int Brush::spacing() const
* @param pressure brush pressue [0..1]
* @return diameter^2 pixel values
*/
-RenderedBrush Brush::render(qreal pressure) const {
+BrushMask Brush::render(qreal pressure) const {
const int dia = diameter(pressure)+1;
const qreal o = opacity(pressure);
@@ -305,7 +305,7 @@ RenderedBrush Brush::render(qreal pressure) const {
memset(lookup+i, int(255*o), int(ceil(rr)-i));
// Render the brush
- cache_ = RenderedBrush(dia, pressure);
+ cache_ = BrushMask(dia, pressure);
uchar *ptr = cache_.data();
for(int y=0;y<dia;++y) {
const qreal yy = (R-y)*(R-y);
@@ -328,15 +328,15 @@ RenderedBrush Brush::render(qreal pressure) const {
* @param pressure brush pressure
* @return resampled brush mask
*/
-RenderedBrush Brush::render_subsampled(qreal x, qreal y, qreal pressure) const
+BrushMask Brush::render_subsampled(qreal x, qreal y, qreal pressure) const
{
Q_ASSERT(x>= 0 && x<= 1);
Q_ASSERT(y>= 0 && y<= 1);
- const RenderedBrush rb = render(pressure);
+ const BrushMask rb = render(pressure);
if(x==0 && y==0)
return rb;
const int dia = rb.diameter();
- RenderedBrush b(dia, pressure);
+ BrushMask b(dia, pressure);
qreal kernel[] = {x*y, (1.0-x)*y, x*(1.0-y), (1.0-x)*(1.0-y)};
Q_ASSERT(fabs(kernel[0]+kernel[1]+kernel[2]+kernel[3]-1.0)<0.001);
@@ -395,7 +395,7 @@ bool Brush::operator!=(const Brush& brush) const
* Copy the data from an existing brush. A brush data is guaranteed
* to contain a pixel buffer.
*/
-RenderedBrushData::RenderedBrushData(const RenderedBrushData& other)
+BrushMaskData::BrushMaskData(const BrushMaskData& other)
: dia(other.dia), pressure(other.pressure)
{
data = new uchar[dia*dia];
@@ -409,8 +409,8 @@ RenderedBrushData::RenderedBrushData(const RenderedBrushData& other)
* @param dia diameter of the new brush
* @param pressure the pressure value at which the brush was rendered
*/
-RenderedBrush::RenderedBrush(int dia, qreal pressure)
- : d(new RenderedBrushData)
+BrushMask::BrushMask(int dia, qreal pressure)
+ : d(new BrushMaskData)
{
Q_ASSERT(dia>0);
d->data = new uchar[dia*dia];
@@ -424,7 +424,7 @@ RenderedBrush::RenderedBrush(int dia, qreal pressure)
* 1. it contains data
* 2. it's pressure value is the same as @parma pressure iff sensitive is true.
*/
-bool RenderedBrush::isFresh(qreal pressure, bool sensitive) const {
+bool BrushMask::isFresh(qreal pressure, bool sensitive) const {
if(sensitive)
return d.constData() && int(pressure*PRESSURE_LEVELS)==d->pressure;
else
24 src/client/core/brush.h
View
@@ -27,23 +27,23 @@ namespace dpcore {
class Point;
-struct RenderedBrushData : public QSharedData
+struct BrushMaskData : public QSharedData
{
- RenderedBrushData() : data(0), dia(0), pressure(-1) { }
- RenderedBrushData(const RenderedBrushData& other);
- ~RenderedBrushData() { delete [] data; }
+ BrushMaskData() : data(0), dia(0), pressure(-1) { }
+ BrushMaskData(const BrushMaskData& other);
+ ~BrushMaskData() { delete [] data; }
uchar *data;
int dia;
int pressure;
};
-//! A rendered brush
+//! A brush mask
/**
* This is an implicitly shared class that holds the alpha map of the
* brush shape.
*/
-class RenderedBrush
+class BrushMask
{
public:
//! Number of pressure levels supported.
@@ -54,10 +54,10 @@ class RenderedBrush
static const int PRESSURE_LEVELS = 256;
//! Create an empty brush
- RenderedBrush() : d(0) { }
+ BrushMask() : d(0) { }
//! Create a new rendered brush
- RenderedBrush(int dia, qreal pressure);
+ BrushMask(int dia, qreal pressure);
//! Is this brush still valid
bool isFresh(qreal pressure, bool sensitive) const;
@@ -72,7 +72,7 @@ class RenderedBrush
int diameter() const { return d->dia; }
private:
- QSharedDataPointer<RenderedBrushData> d;
+ QSharedDataPointer<BrushMaskData> d;
};
//! A brush for drawing onto a layer
@@ -135,10 +135,10 @@ class Brush
int blendingMode() const { return blend_; }
//! Render the brush
- RenderedBrush render(qreal pressure) const;
+ BrushMask render(qreal pressure) const;
//! Render the brush with an offset
- RenderedBrush render_subsampled(qreal x, qreal y, qreal pressure) const;
+ BrushMask render_subsampled(qreal x, qreal y, qreal pressure) const;
//! Equality test
bool operator==(const Brush& brush) const;
@@ -159,7 +159,7 @@ class Brush
bool subpixel_;
int blend_;
- mutable RenderedBrush cache_;
+ mutable BrushMask cache_;
};
}
100 src/client/core/layer.cpp
View
@@ -17,10 +17,12 @@
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
+#include <QDebug>
#include <QPainter>
#include <QImage>
#include <cmath>
+#include "layerstack.h"
#include "layer.h"
#include "tile.h"
#include "brush.h"
@@ -28,27 +30,57 @@
namespace dpcore {
-Layer::Layer(const QImage& image) {
- width_ = image.width();
- height_ = image.height();
+void Layer::init(LayerStack *owner, int id, const QString& name, const QSize& size)
+{
+ owner_ = owner;
+ id_ = id;
+ name_ = name;
+ width_ = size.width();
+ height_ = size.height();
xtiles_ = width_ / Tile::SIZE + ((width_ % Tile::SIZE)>0);
ytiles_ = height_ / Tile::SIZE + ((height_ % Tile::SIZE)>0);
tiles_ = new Tile*[xtiles_ * ytiles_];
+}
+
+/**
+ * Construct a layer whose content is loaded from the provided image.
+ * @param owner the stack to which this layer belongs to
+ * @param id layer ID
+ * @param image image on which the layer is based
+ */
+Layer::Layer(LayerStack *owner, int id, const QString& name, const QImage& image) {
+ init(owner, id, name, image.size());
for(int y=0;y<ytiles_;++y)
for(int x=0;x<xtiles_;++x)
tiles_[y*xtiles_+x] = new Tile(image, x, y);
}
-Layer::Layer(const QColor& color, const QSize& size) {
- width_ = size.width();
- height_ = size.height();
- xtiles_ = width_ / Tile::SIZE + ((width_ % Tile::SIZE)>0);
- ytiles_ = height_ / Tile::SIZE + ((height_ % Tile::SIZE)>0);
- tiles_ = new Tile*[xtiles_ * ytiles_];
+/**
+ * Construct a layer initialized to a solid color
+ * @param owner the stack to which this layer belongs to
+ * @param id layer ID
+ * @param color layer color
+ * @parma size layer size
+ */
+Layer::Layer(LayerStack *owner, int id, const QString& name, const QColor& color, const QSize& size) {
+ init(owner, id, name, size);
for(int y=0;y<ytiles_;++y)
for(int x=0;x<xtiles_;++x)
tiles_[y*xtiles_+x] = new Tile(color, x, y);
+}
+/**
+ * Construct an empty (transparent) layer.
+ * Memory for the layer tiles will be allocated on demand.
+ * @param owner the stack to which this layer belongs to
+ * @param id layer ID
+ * @param size layer size
+ */
+Layer::Layer(LayerStack *owner, int id, const QString& name, const QSize& size)
+{
+ init(owner, id, name, size);
+ for(int i=0;i<xtiles_*ytiles_;++i)
+ tiles_[i] = 0;
}
Layer::~Layer() {
@@ -58,30 +90,12 @@ Layer::~Layer() {
}
QImage Layer::toImage() const {
- QImage image(width_, height_, QImage::Format_RGB32);
- for(int i=0;i<xtiles_*ytiles_;++i)
- tiles_[i]->copyToImage(image);
-
- return image;
-}
-
-void Layer::paint(const QRectF& rect, QPainter *painter) {
- const int top = qMax(int(rect.top()), 0);
- const int left = qMax(int(rect.left()), 0);
- const int right = Tile::roundTo(qMin(int(rect.right()), width_));
- const int bottom = Tile::roundTo(qMin(int(rect.bottom()), height_));
-
- painter->save();
- painter->setClipRect(rect);
- for(int y=top;y<bottom;y+=Tile::SIZE) {
- const int yindex = (y/Tile::SIZE);
- for(int x=left;x<right;x+=Tile::SIZE) {
- const int xindex = x/Tile::SIZE;
- tiles_[xtiles_*yindex + xindex]->paint(painter,
- QPoint(xindex*Tile::SIZE, yindex*Tile::SIZE));
- }
+ QImage image(width_, height_, QImage::Format_ARGB32_Premultiplied);
+ for(int i=0;i<xtiles_*ytiles_;++i) {
+ if(tiles_[i])
+ tiles_[i]->copyToImage(image);
}
- painter->restore();
+ return image;
}
/**
@@ -89,7 +103,7 @@ void Layer::paint(const QRectF& rect, QPainter *painter) {
* @param y
* @return invalid color if x or y is outside image boundaries
*/
-QColor Layer::colorAt(int x, int y)
+QColor Layer::colorAt(int x, int y) const
{
if(x<0 || y<0 || x>=width_ || y>=height_)
return QColor();
@@ -118,9 +132,9 @@ void Layer::dab(const Brush& brush, const Point& point)
return;
// Render the brush
- RenderedBrush rb = brush.subpixel()?brush.render_subsampled(point.xFrac(), point.yFrac(), point.pressure()):brush.render(point.pressure());
- const int realdia = rb.diameter();
- const uchar *values = rb.data();
+ BrushMask bm = brush.subpixel()?brush.render_subsampled(point.xFrac(), point.yFrac(), point.pressure()):brush.render(point.pressure());
+ const int realdia = bm.diameter();
+ const uchar *values = bm.data();
QColor color = brush.color(point.pressure());
// A single dab can (and often does) span multiple tiles.
@@ -138,8 +152,10 @@ void Layer::dab(const Brush& brush, const Point& point)
const int xindex = x / Tile::SIZE;
const int xt = x - xindex * Tile::SIZE;
const int wb = xt+realdia-xb < Tile::SIZE ? realdia-xb : Tile::SIZE-xt;
-
- tiles_[xtiles_ * yindex + xindex]->composite(
+ const int i = xtiles_ * yindex + xindex;
+ if(tiles_[i]==0)
+ tiles_[i] = new Tile(xindex, yindex);
+ tiles_[i]->composite(
brush.blendingMode(),
values + yb * realdia + xb,
color,
@@ -148,6 +164,9 @@ void Layer::dab(const Brush& brush, const Point& point)
realdia-wb
);
+ if(owner_)
+ owner_->markDirty(xindex, yindex);
+
x = (xindex+1) * Tile::SIZE;
xb = xb + wb;
}
@@ -281,7 +300,12 @@ void Layer::merge(int x, int y, const Layer *layer)
int myy = y;
for(int i=0;i<layer->ytiles_&&myy<ytiles_;++i,myy++) {
for(int j=0;j<layer->xtiles_;++j&&myx<xtiles_,myx++) {
+ const int index = xtiles_*myy + myx;
+ if(tiles_[index]==0)
+ tiles_[index] = new Tile(myx, myy);
tiles_[xtiles_*myy+myx]->merge(layer->tiles_[layer->xtiles_*i+j]);
+ if(owner_)
+ owner_->markDirty(myx, myy);
}
myx = x;
}
36 src/client/core/layer.h
View
@@ -20,16 +20,19 @@
#ifndef LAYER_H
#define LAYER_H
+#include <QColor>
+
class QImage;
-class QColor;
class QPainter;
class QRectF;
+class QSize;
namespace dpcore {
class Brush;
class Point;
class Tile;
+class LayerStack;
//! A drawing layer/tile manager
/**
@@ -40,10 +43,13 @@ class Tile;
class Layer {
public:
//! Construct a layer from an image
- Layer(const QImage& image);
+ Layer(LayerStack *owner, int id, const QString& name, const QImage& image);
+
+ //! Construct a blank layer
+ Layer(LayerStack *owner, int id, const QString& name, const QColor& color, const QSize& size);
- //! Construct an empty layer
- Layer(const QColor& color, const QSize& size);
+ //! Construct a blank layer
+ Layer(LayerStack *owner, int id, const QString& name, const QSize& size);
~Layer();
@@ -53,17 +59,20 @@ class Layer {
//! Get the layer height in pixels
int height() const { return height_; }
+ //! Get the layer ID
+ int id() const { return id_; }
+
+ //! Get the layer name
+ const QString& name() const { return name_; }
+
//! Get the layer as an image
QImage toImage() const;
//! Resize this layer
//void resize(const QSize& newsize);
- //! Paint an area of this layer
- void paint(const QRectF& rect, QPainter *painter);
-
//! Get the color at the specified coordinate
- QColor colorAt(int x, int y);
+ QColor colorAt(int x, int y) const;
//! Dab the layer with a brush
void dab(const Brush& brush, const Point& point);
@@ -83,11 +92,22 @@ class Layer {
//! Fill the layer with a checker pattern
void fillChecker(const QColor& dark, const QColor& light);
+ //! Get a tile
+ const Tile *tile(int x, int y) const { return tiles_[y*xtiles_+x]; }
+
+ //! Get a tile
+ const Tile *tile(int index) const { return tiles_[index]; }
+
private:
+ LayerStack *owner_;
+ void init(LayerStack *owner, int id, const QString& name, const QSize& size);
+
int width_;
int height_;
int xtiles_;
int ytiles_;
+ int id_;
+ QString name_;
Tile **tiles_;
};
251 src/client/core/layerstack.cpp
View
@@ -0,0 +1,251 @@
+/*
+ DrawPile - a collaborative drawing program.
+
+ Copyright (C) 2008 Calle Laakkonen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include <QPixmap>
+#include <QPainter>
+
+#include "layer.h"
+#include "layerstack.h"
+#include "tile.h"
+#include "rasterop.h"
+
+namespace dpcore {
+
+LayerStack::LayerStack(QObject *parent) :
+ QAbstractListModel(parent), width_(-1), height_(-1), lastid_(0)
+{
+}
+
+LayerStack::~LayerStack()
+{
+ foreach(Layer *l, layers_)
+ delete l;
+}
+
+void LayerStack::init(const QSize& size)
+{
+ width_ = size.width();
+ height_ = size.height();
+ xtiles_ = width_ / Tile::SIZE + ((width_ % Tile::SIZE)>0);
+ ytiles_ = height_ / Tile::SIZE + ((height_ % Tile::SIZE)>0);
+ cache_ = new QPixmap[xtiles_*ytiles_];
+}
+
+Layer *LayerStack::addLayer(const QString& name, const QImage& image)
+{
+ if(width_ != -1) {
+ // TODO, check that image dimensions match
+ } else {
+ init(image.size());
+ }
+ Layer *nl = new Layer(this, lastid_++, name, image);
+ beginInsertRows(QModelIndex(),1,1);
+ layers_.append(nl);
+ endInsertRows();
+ return nl;
+}
+
+Layer *LayerStack::addLayer(const QString& name, const QColor& color, const QSize& size)
+{
+ if(width_ != -1) {
+ // TODO, check that image dimensions match
+ } else {
+ init(size);
+ }
+ Layer *nl = new Layer(this, lastid_++, name, color, size);
+ beginInsertRows(QModelIndex(),1,1);
+ layers_.append(nl);
+ endInsertRows();
+ return nl;
+}
+
+Layer *LayerStack::addLayer(const QString& name, const QSize& size)
+{
+ if(width_ != -1) {
+ // TODO, check that image dimensions match
+ } else {
+ init(size);
+ }
+ Layer *nl = new Layer(this, lastid_++, name, size);
+ beginInsertRows(QModelIndex(),1,1);
+ layers_.append(nl);
+ endInsertRows();
+ return nl;
+}
+
+/**
+ * @param id layer ID
+ * @return true if layer was found and deleted
+ */
+bool LayerStack::deleteLayer(int id)
+{
+ for(int i=0;i<layers_.size();++i) {
+ if(layers_.at(i)->id() == id) {
+ // Invalidate cache
+ Layer *l = layers_.at(i);
+ for(int j=0;j<xtiles_*ytiles_;++j) {
+ if(l->tile(j))
+ cache_[j] = QPixmap();
+ }
+ // Remove the layer
+ int row = layers() - i;
+ beginRemoveRows(QModelIndex(), row, row);
+ layers_.removeAt(i);
+ endRemoveRows();
+ return true;
+ }
+ }
+ return false;
+}
+
+Layer *LayerStack::getLayerByIndex(int index)
+{
+ return layers_.at(index);
+}
+
+Layer *LayerStack::getLayer(int id)
+{
+ // Since the expected number of layers is always fairly low,
+ // we can get away with a simple linear search. (Note that IDs
+ // may appear in random order due to layers being moved around.)
+ foreach(Layer *l, layers_)
+ if(l->id() == id)
+ return l;
+ return 0;
+}
+
+/**
+ * @param id layer id
+ * @return layer index. Returns a negative index if layer is not found
+ */
+int LayerStack::id2index(int id) const
+{
+ for(int i=0;i<layers_.size();++i)
+ if(layers_.at(i)->id() == id)
+ return i;
+ return -1;
+}
+
+/**
+ * Paint a view of the layer stack. The layers are composited
+ * together according to their options.
+ * @param rect area of image to paint
+ * @param painter painter to use
+ */
+void LayerStack::paint(const QRectF& rect, QPainter *painter)
+{
+ const int top = qMax(int(rect.top()), 0);
+ const int left = qMax(int(rect.left()), 0);
+ const int right = Tile::roundTo(qMin(int(rect.right()), width_));
+ const int bottom = Tile::roundTo(qMin(int(rect.bottom()), height_));
+
+ painter->save();
+ painter->setClipRect(rect);
+ for(int y=top;y<bottom;y+=Tile::SIZE) {
+ const int yindex = (y/Tile::SIZE);
+ for(int x=left;x<right;x+=Tile::SIZE) {
+ const int xindex = x/Tile::SIZE;
+ int i = xtiles_*yindex + xindex;
+ if(cache_[i].isNull())
+ updateCache(xindex, yindex);
+ painter->drawPixmap(QPoint(xindex*Tile::SIZE, yindex*Tile::SIZE),
+ cache_[i]);
+ }
+ }
+ painter->restore();
+
+}
+
+QColor LayerStack::colorAt(int x, int y) const
+{
+ // TODO merge
+ return layers_.at(0)->colorAt(x, y);
+}
+
+QImage LayerStack::toFlatImage() const
+{
+ // TODO
+ return layers_.at(0)->toImage();
+}
+
+// Flatten a single tile
+void LayerStack::flattenTile(quint32 *data, int xindex, int yindex)
+{
+ // Start out with the lowermost visible layer
+ memcpy(data, layers_.at(0)->tile(xindex, yindex)->data(), Tile::BYTES);
+
+ // Composite remaining layers
+ for(int i=1;i<layers();++i) {
+ const Tile *tile = layers_.at(i)->tile(xindex, yindex);
+ if(tile) {
+ compositePixels(0, data, tile->data(), Tile::SIZE*Tile::SIZE);
+ }
+ }
+}
+
+// Update the paint cache. The layers are composited together
+// according to their blend mode and opacity options.
+void LayerStack::updateCache(int xindex, int yindex)
+{
+ quint32 data[Tile::SIZE*Tile::SIZE];
+ flattenTile(data, xindex, yindex);
+ cache_[yindex*xtiles_+xindex] = QPixmap::fromImage(
+ QImage(reinterpret_cast<const uchar*>(data), Tile::SIZE, Tile::SIZE,
+ QImage::Format_RGB32)
+ );
+
+}
+
+void LayerStack::markDirty(int tilex, int tiley)
+{
+ cache_[tiley*xtiles_ + tilex] = QPixmap();
+}
+
+// Model related functions
+QVariant LayerStack::data(const QModelIndex& index, int role) const
+{
+ // Always display one extra layer (for adding new layers)
+ if(index.row() >= 0 && index.row() <= layers() && role == Qt::DisplayRole) {
+ if(index.row()==0)
+ return "New";
+ // Display the layers in reverse order (topmost layer first)
+ int row = layers() - index.row();
+ return QVariant::fromValue(layers_.at(row));
+ }
+ return QVariant();
+
+}
+
+Qt::ItemFlags LayerStack::flags(const QModelIndex& index) const
+{
+ if(index.row()==0)
+ return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled;
+ else
+ return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+}
+
+int LayerStack::rowCount(const QModelIndex& parent) const
+{
+ if(parent.isValid())
+ return 0;
+ return layers() + 1;
+}
+
+}
110 src/client/core/layerstack.h
View
@@ -0,0 +1,110 @@
+/*
+ DrawPile - a collaborative drawing program.
+
+ Copyright (C) 2008 Calle Laakkonen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#ifndef LAYERSTACK_H
+#define LAYERSTACK_H
+
+#include <QList>
+#include <QImage>
+#include <QAbstractListModel>
+
+namespace dpcore {
+
+class Layer;
+
+/**
+ * A stack of layers. The stack also provides a list model so it can be
+ * easily manipulated using Qt's MVC framework.
+ * The model always includes one null layer which can be used as an
+ * anchor point for new layers.
+ */
+class LayerStack : public QAbstractListModel {
+ Q_OBJECT
+ public:
+ LayerStack(QObject *parent=0);
+ ~LayerStack();
+
+ //! Add a new image as a layer to the top of the stack
+ Layer *addLayer(const QString& name, const QImage& image);
+
+ //! Add a new layer of solid color to the top of the stack
+ Layer *addLayer(const QString& name, const QColor& color, const QSize& size);
+
+ //! Add a new empty layre to the top of the stack
+ Layer *addLayer(const QString& name, const QSize& size);
+
+ //! Delete a layer
+ bool deleteLayer(int id);
+
+ //! Get the number of layers in the stack
+ int layers() const { return layers_.count(); }
+
+ //! Get a layer by its index
+ Layer *getLayerByIndex(int index);
+
+ //! Get a layer by its ID
+ Layer *getLayer(int id);
+
+ //! Get the index of the specified layer
+ int id2index(int id) const;
+
+ //! Get the width of the layer stack
+ int width() const { return width_; }
+
+ //! Get the height of the layer stack
+ int height() const { return height_; }
+
+ //! Get the width and height of the layer stack
+ QSize size() const { return QSize(width_, height_); }
+
+ //! Paint an area of this layer stack
+ void paint(const QRectF& rect, QPainter *painter);
+
+ //! Get the merged color value at the point
+ QColor colorAt(int x, int y) const;
+
+ //! Return a flattened image of the layer stack
+ QImage toFlatImage() const;
+
+ //! Mark a tile whose content has changed
+ void markDirty(int tilex, int tiley);
+
+ // List model functions
+ int rowCount(const QModelIndex& parent) const;
+ QVariant data(const QModelIndex& index, int role) const;
+ Qt::ItemFlags flags(const QModelIndex& index) const;
+
+ private:
+ void init(const QSize& size);
+ void flattenTile(quint32 *data, int xindex, int yindex);
+ void updateCache(int xindex, int yindex);
+
+ int width_, height_;
+ int xtiles_, ytiles_;
+ QList<Layer*> layers_;
+ QPixmap *cache_;
+ int lastid_;
+};
+
+}
+
+Q_DECLARE_METATYPE(dpcore::Layer*)
+
+#endif
+
10 src/client/core/rasterop.cpp
View
@@ -121,7 +121,7 @@ void doComposite(quint32 *base, quint32 color, const uchar *mask,
*dest = UINT8_BLEND(BO(*dest, src[0]), *dest, *mask); ++dest;
*dest = UINT8_BLEND(BO(*dest, src[1]), *dest, *mask); ++dest;
*dest = UINT8_BLEND(BO(*dest, src[2]), *dest, *mask); ++dest;
- ++dest;
+ *dest = *dest + UINT8_MULT(255-*dest, *mask); ++dest;
++mask;
}
dest += baseskip*4;
@@ -169,10 +169,10 @@ void compositePixels(int mode, quint32 *base, const quint32 *over, int len)
uchar *dest = reinterpret_cast<uchar*>(base);
const uchar *src = reinterpret_cast<const uchar*>(over);
while(len--) {
- *dest = UINT8_BLEND(src[0], *dest, src[3]); ++dest;
- *dest = UINT8_BLEND(src[1], *dest, src[3]); ++dest;
- *dest = UINT8_BLEND(src[2], *dest, src[3]); ++dest;
- ++dest;
+ *dest = src[0] + UINT8_MULT(255-src[3], *dest); ++dest;
+ *dest = src[1] + UINT8_MULT(255-src[3], *dest); ++dest;
+ *dest = src[2] + UINT8_MULT(255-src[3], *dest); ++dest;
+ *dest = *dest + UINT8_MULT(255-*dest, src[3]); ++dest;
src+=4;
}
20 src/client/core/tile.cpp
View
@@ -36,6 +36,12 @@ Tile::Tile(const QColor& color, int x, int y)
*(ptr++) = col;
}
+Tile::Tile(int x, int y)
+ : x_(x), y_(y), data_(new quint32[SIZE*SIZE])
+{
+ memset(data_, 0, SIZE*SIZE*sizeof(quint32));
+}
+
/**
* Copy all pixel data from (x*SIZE, y*SIZE, (x+1)*SIZE, (y+1)*SIZE).
* Pixels outside the source image are set to zero
@@ -98,18 +104,6 @@ quint32 Tile::pixel(int x,int y) const {
}
/**
- * @param painter painter to paint the tile with
- * @param target where to paint the tile
- */
-void Tile::paint(QPainter *painter, const QPoint& target) const {
- if(cache_.isNull()) {
- cache_ = QPixmap::fromImage(QImage(reinterpret_cast<uchar*>(data_),
- SIZE, SIZE, QImage::Format_RGB32));
- }
- painter->drawPixmap(target, cache_);
-}
-
-/**
* @param values array of alpha values
* @param color composite color
* @param x offset in the tile
@@ -124,13 +118,11 @@ void Tile::composite(int mode, const uchar *values, const QColor& color, int x,
Q_ASSERT((x+w)<=SIZE && (y+h)<=SIZE);
compositeMask(mode, data_ + y * SIZE + x,
color.rgba(), values, w, h, skip, SIZE-w);
- cache_ = QPixmap();
}
void Tile::merge(const Tile *tile)
{
compositePixels(0, data_, tile->data_, SIZE*SIZE);
- cache_ = QPixmap();
}
}
11 src/client/core/tile.h
View
@@ -36,6 +36,7 @@ namespace dpcore {
class Tile {
public:
static const int SIZE = 64;
+ static const int BYTES = SIZE * SIZE * sizeof(quint32);
//! Round i upwards to SIZE boundary
static int roundTo(int i) {
@@ -48,6 +49,9 @@ class Tile {
//! Construct a tile from an image
Tile(const QImage& image, int x, int y);
+ //! Construct an empty tile
+ Tile(int x, int y);
+
~Tile();
//! Get tile X index
@@ -65,19 +69,18 @@ class Tile {
//! Composite another tile with this tile
void merge(const Tile *tile);
- //! Paint this tile
//! Copy the contents of this tile onto the appropriate spot on an image
void copyToImage(QImage& image) const;
- void paint(QPainter *painter, const QPoint& target) const;
-
//! Fill this tile with a checker pattern
void fillChecker(const QColor& dark, const QColor& light);
+ //! Get read access to the raw pixel data
+ const quint32 *data() const { return data_; }
+
private:
int x_, y_;
quint32 *data_;
- mutable QPixmap cache_;
};
}
97 src/client/layerlistdelegate.cpp
View
@@ -0,0 +1,97 @@
+/*
+ DrawPile - a collaborative drawing program.
+
+ Copyright (C) 2008 Calle Laakkonen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include <QDebug>
+#include <QMouseEvent>
+#include <QPainter>
+#include "layerlistdelegate.h"
+#include "core/layerstack.h"
+#include "core/layer.h"
+#include "icons.h"
+
+LayerListDelegate::LayerListDelegate(QObject *parent)
+ : QItemDelegate(parent)
+{
+}
+
+LayerListDelegate::~LayerListDelegate()
+{
+}
+
+void LayerListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QStyleOptionViewItemV2 opt = setOptions(index, option);
+ const QStyleOptionViewItemV2 *v2 = qstyleoption_cast<const QStyleOptionViewItemV2 *>(&option);
+ opt.features = v2 ? v2->features : QStyleOptionViewItemV2::ViewItemFeatures(QStyleOptionViewItemV2::None);
+
+ // Background
+ drawBackground(painter, opt, index);
+
+ QRect textrect = opt.rect;
+
+ if(index.row()==0) {
+ // Draw add new layer button
+ opt.font.setStyle(QFont::StyleItalic);
+ opt.displayAlignment = Qt::AlignHCenter;
+ drawDisplay(painter, opt, textrect, "New layer...");
+ } else {
+ const dpcore::Layer *layer = index.data().value<dpcore::Layer*>();
+ QString name = QString("%1 %2 %3%").arg(layer->name()).arg("Normal").arg(100);
+
+ const int delwidth = icon::kick().actualSize(QSize(16,16)).width();
+ // Draw layer name
+ textrect.setWidth(textrect.width() - delwidth);
+ drawDisplay(painter, opt, textrect, name);
+
+ // Draw delete button (except when in a network session)
+ // TODO correct icon
+ painter->drawPixmap(opt.rect.topRight()-QPoint(delwidth,0),
+ icon::kick().pixmap(16));
+ }
+}
+
+#if 0
+QSize LayerListDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index ) const
+{
+ QSize size = QItemDelegate::sizeHint(option, index);
+ const QSize iconsize = icon::lock().actualSize(QSize(16,16));
+ if(size.height() < iconsize.height())
+ size.setHeight(iconsize.height());
+ return size;
+}
+#endif
+
+bool LayerListDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
+ const QStyleOptionViewItem &option, const QModelIndex &index)
+{
+ const int btnwidth = icon::lock().actualSize(QSize(16,16)).width();
+ if(event->type() == QEvent::MouseButtonRelease) {
+ const QMouseEvent *me = static_cast<QMouseEvent*>(event);
+ if(index.row()==0) {
+ emit newLayer();
+ } else {
+ const dpcore::Layer *layer = index.data().value<dpcore::Layer*>();
+ if(me->x() >= option.rect.width() - btnwidth)
+ emit deleteLayer(layer);
+ }
+ }
+ return QItemDelegate::editorEvent(event, model, option, index);
+}
+
49 src/client/layerlistdelegate.h
View
@@ -0,0 +1,49 @@
+/*
+ DrawPile - a collaborative drawing program.
+
+ Copyright (C) 2008 Calle Laakkonen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#ifndef LAYERLISTMODEL_H
+#define LAYERLISTMODEL_H
+
+#include <QAbstractListModel>
+#include <QItemDelegate>
+
+namespace dpcore {
+ class Layer;
+}
+
+class LayerListDelegate : public QItemDelegate {
+ Q_OBJECT
+ public:
+ LayerListDelegate(QObject *parent=0);
+ ~LayerListDelegate();
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+ //QSize sizeHint(const QStyleOptionViewItem & option,
+ //const QModelIndex & index ) const;
+ bool editorEvent(QEvent *event, QAbstractItemModel *model,
+ const QStyleOptionViewItem &option, const QModelIndex &index);
+
+ signals:
+ void newLayer();
+ void deleteLayer(const dpcore::Layer *layer);
+};
+
+#endif
+
115 src/client/layerlistwidget.cpp
View
@@ -0,0 +1,115 @@
+/*
+ DrawPile - a collaborative drawing program.
+
+ Copyright (C) 2008 Calle Laakkonen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include <QDebug>
+#include <QItemSelection>
+#include <QListView>
+#include <QInputDialog>
+#include <QMessageBox>
+#include <QPushButton>
+
+#include "layerlistwidget.h"
+#include "layerlistdelegate.h"
+#include "board.h"
+#include "ui_layerbox.h"
+#include "core/layerstack.h"
+#include "core/layer.h"
+
+namespace widgets {
+
+LayerList::LayerList(QWidget *parent)
+ : QDockWidget(tr("Layers"), parent)
+{
+ ui_ = new Ui_LayerBox;
+ QWidget *w = new QWidget(this);
+ setWidget(w);
+ ui_->setupUi(w);
+
+ //model_ = new LayerListModel(this);
+ //ui_->layers->setModel(model_);
+ LayerListDelegate *del = new LayerListDelegate(this);
+ ui_->layers->setItemDelegate(del);
+ connect(del, SIGNAL(newLayer()), this, SLOT(newLayer()));
+ connect(del, SIGNAL(deleteLayer(const dpcore::Layer*)), this,
+ SLOT(deleteLayer(const dpcore::Layer*)));
+}
+
+LayerList::~LayerList()
+{
+}
+
+void LayerList::setBoard(drawingboard::Board *board)
+{
+ ui_->layers->setModel(board->layers());
+ connect(ui_->layers->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(selected(const QItemSelection&, const QItemSelection&)));
+}
+
+void LayerList::selectLayer(int id)
+{
+ dpcore::LayerStack *layers = static_cast<dpcore::LayerStack*>(ui_->layers->model());
+ int row = layers->layers() - layers->id2index(id);
+ QModelIndexList sel = ui_->layers->selectionModel()->selectedIndexes();
+ if(sel.isEmpty() || sel.first().row() != row)
+ ui_->layers->selectionModel()->select(layers->index(row,0),
+ QItemSelectionModel::Select);
+}
+
+void LayerList::selected(const QItemSelection& selection, const QItemSelection& prev)
+{
+ if(selection.indexes().isEmpty()) {
+ // A layer must always be selected
+ ui_->layers->selectionModel()->select(prev.indexes().first(),
+ QItemSelectionModel::Select);
+ } else {
+ dpcore::LayerStack *layers = static_cast<dpcore::LayerStack*>(ui_->layers->model());
+ emit selected(layers->layers() - selection.indexes().first().row());
+ }
+}
+
+
+void LayerList::newLayer()
+{
+ bool ok;
+ QString name = QInputDialog::getText(this, tr("Add a new layer"),
+ tr("Layer name:"), QLineEdit::Normal, "", &ok);
+ if(ok) {
+ if(name.isEmpty())
+ name = tr("Unnamed layer");
+ emit newLayer(name);
+ }
+}
+
+void LayerList::deleteLayer(const dpcore::Layer *layer)
+{
+ QMessageBox box(QMessageBox::Question, tr("Delete layer"),
+ tr("Really delete %1?").arg(layer->id()),
+ QMessageBox::NoButton, this);
+ box.setInformativeText(tr("Press merge down to merge the layer with the first visible layer below it instead of deleting."));
+ box.addButton(tr("Delete"), QMessageBox::DestructiveRole);
+ QPushButton *merge = box.addButton(tr("Merge down"), QMessageBox::DestructiveRole);
+ QPushButton *cancel = box.addButton(tr("Cancel"), QMessageBox::RejectRole);
+ box.setDefaultButton(cancel);
+ box.exec();
+ QAbstractButton *choice = box.clickedButton();
+ if(choice != cancel)
+ emit deleteLayer(layer->id(), choice==merge);
+}
+
+}
+
71 src/client/layerlistwidget.h
View
@@ -0,0 +1,71 @@
+/*
+ DrawPile - a collaborative drawing program.
+
+ Copyright (C) 2008 Calle Laakkonen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#ifndef LAYERLISTWIDGET_H
+#define LAYERLISTWIDGET_H
+
+#include <QDockWidget>
+
+class LayerListModel;
+class QListView;
+
+namespace drawingboard {
+ class Board;
+}
+
+namespace dpcore {
+ class Layer;
+}
+
+class Ui_LayerBox;
+class QItemSelection;
+
+namespace widgets {
+
+class LayerList : public QDockWidget
+{
+ Q_OBJECT
+ public:
+ LayerList(QWidget *parent=0);
+ ~LayerList();
+
+ void setBoard(drawingboard::Board *board);
+
+ public slots:
+ void selectLayer(int id);
+
+ signals:
+ void newLayer(const QString& name);
+ void deleteLayer(int id, bool mergedown);
+ void selected(int id);
+
+ private slots:
+ void newLayer();
+ void deleteLayer(const dpcore::Layer* layer);
+ void selected(const QItemSelection& selection, const QItemSelection& prev);
+
+ private:
+ Ui_LayerBox *ui_;
+ LayerListModel *model_;
+};
+
+}
+
+#endif
+
26 src/client/mainwindow.cpp
View
@@ -47,6 +47,7 @@
#include "controller.h"
#include "toolsettingswidget.h"
#include "userlistwidget.h"
+#include "layerlistwidget.h"
#include "chatwidget.h"
#include "dualcolorbutton.h"
#include "localserver.h"
@@ -129,8 +130,8 @@ MainWindow::MainWindow(const MainWindow *source)
board_->setBackgroundBrush(
palette().brush(QPalette::Active,QPalette::Window));
view_->setBoard(board_);
-
navigator_->setScene(board_);
+
// Navigator <-> View
connect(navigator_, SIGNAL(focusMoved(const QPoint&)),
view_, SLOT(scrollTo(const QPoint&)));
@@ -219,6 +220,14 @@ MainWindow::MainWindow(const MainWindow *source)
connect(netstatus_, SIGNAL(statusMessage(QString)),
chatbox_, SLOT(systemMessage(QString)));
+ // Layer box -> controller
+ connect(layerlist_, SIGNAL(newLayer(const QString&)),
+ controller_, SLOT(newLayer(const QString&)));
+ connect(layerlist_, SIGNAL(deleteLayer(int, bool)),
+ controller_, SLOT(deleteLayer(int, bool)));
+ connect(layerlist_, SIGNAL(selected(int)),
+ controller_, SLOT(selectLayer(int)));
+
if(source)
cloneSettings(source);
else
@@ -297,6 +306,8 @@ void MainWindow::postInitBoard(const QString& filename)
setTitle();
save_->setEnabled(true);
saveas_->setEnabled(true);
+ layerlist_->setBoard(board_);
+ board_->setLayerList(layerlist_);
}
/**
@@ -1353,7 +1364,7 @@ void MainWindow::initActions()
zoomin_ = makeAction("zoomin", "zoom-in.png",tr("Zoom &in"), QString(), QKeySequence::ZoomIn);
zoomout_ = makeAction("zoomout", "zoom-out.png",tr("Zoom &out"), QString(), QKeySequence::ZoomOut);
zoomorig_ = makeAction("zoomone", "zoom-original.png",tr("&Normal size"), QString(), QKeySequence(Qt::CTRL + Qt::Key_0));
- rotateorig_ = makeAction("rotatezero", "view-refresh.png",tr("&Reset rotation"), QString(), QKeySequence(Qt::CTRL + Qt::Key_R));
+ rotateorig_ = makeAction("rotatezero", "view-refresh.png",tr("&Reset rotation"), tr("Drag the view while holding ctrl-space to rotate"), QKeySequence(Qt::CTRL + Qt::Key_R));
fullscreen_ = makeAction("fullscreen", 0, tr("&Full screen"), QString(), QKeySequence("F11"));
fullscreen_->setCheckable(true);
@@ -1510,9 +1521,11 @@ void MainWindow::createDocks()
createColorBoxes(toggles);
createPalette(toggles);
createUserList(toggles);
+ createLayerList(toggles);
createNavigator(toggles);
tabifyDockWidget(hsv_, rgb_);
tabifyDockWidget(hsv_, palette_);
+ tabifyDockWidget(userlist_, layerlist_);
docktoggles_->setMenu(toggles);
}
@@ -1547,6 +1560,15 @@ void MainWindow::createUserList(QMenu *toggles)
addDockWidget(Qt::RightDockWidgetArea, userlist_);
}
+void MainWindow::createLayerList(QMenu *toggles)
+{
+ layerlist_ = new widgets::LayerList(this);
+ layerlist_->setObjectName("layerlistdock");
+ layerlist_->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
+ toggles->addAction(layerlist_->toggleViewAction());
+ addDockWidget(Qt::RightDockWidgetArea, layerlist_);
+}
+
void MainWindow::createPalette(QMenu *toggles)
{
palette_ = new widgets::PaletteBox(tr("Palette"), this);
4 src/client/mainwindow.h
View
@@ -39,6 +39,7 @@ namespace widgets {
class DualColorButton;
class ToolSettings;
class UserList;
+ class LayerList;
class ChatBox;
class PaletteBox;
class ColorBox;
@@ -229,6 +230,8 @@ class MainWindow : public QMainWindow {
void createToolSettings(QMenu *menu);
//! Create user list dock
void createUserList(QMenu *menu);
+ //! Create layer list dock
+ void createLayerList(QMenu *menu);
//! Create palette dock
void createPalette(QMenu *menu);
//! Create color docks
@@ -247,6 +250,7 @@ class MainWindow : public QMainWindow {
QSplitter *splitter_;
widgets::ToolSettings *toolsettings_;
widgets::UserList *userlist_;
+ widgets::LayerList *layerlist_;
widgets::ChatBox *chatbox_;
widgets::DualColorButton *fgbgcolor_;
22 src/client/sessionstate.cpp
View
@@ -226,6 +226,18 @@ void SessionState::sendToolSelect(const dpcore::Brush& brush)
}
/**
+ * @param layer ID of the layer to select
+ */
+void SessionState::sendLayerSelect(int layer)
+{
+ host_->sendPacket( protocol::LayerSelect(
+ host_->localuser_,
+ layer
+ )
+ );
+}
+
+/**
* @param point stroke coordinates to send
*/
void SessionState::sendStrokePoint(const dpcore::Point& point)
@@ -431,6 +443,16 @@ bool SessionState::handleToolSelect(protocol::ToolSelect *ts)
return false;
}
+bool SessionState::handleLayerSelect(protocol::LayerSelect *ts)
+{
+ if(bufferdrawing_) {
+ drawbuffer_.enqueue(ts);
+ return true;
+ }
+ emit layerSelectReceived(ts->user(), ts->layer());
+ return false;
+}
+
/**
* @param msg StrokeInfo message
* @retval true message was buffered, don't delete
10 src/client/sessionstate.h
View
@@ -38,6 +38,7 @@ namespace dpcore {
namespace protocol {
class Packet;
class ToolSelect;
+ class LayerSelect;
class StrokePoint;
class StrokeEnd;
class BinaryChunk;
@@ -105,6 +106,9 @@ class SessionState : public QObject {
//! Send a tool select message
void sendToolSelect(const dpcore::Brush& brush);
+ //! Send a layer select message
+ void sendLayerSelect(int layerId);
+
//! Send a stroke info message
void sendStrokePoint(const dpcore::Point& point);
@@ -169,6 +173,9 @@ class SessionState : public QObject {
//! Results of a ToolInfo message
void toolReceived(int user, const dpcore::Brush& brush);
+ //! A layer selection
+ void layerSelectReceived(int user, int id);
+
//! Results of a StrokeInfo message
void strokeReceived(int user, const dpcore::Point& point);
@@ -195,6 +202,9 @@ class SessionState : public QObject {
//! Handle a tool select
bool handleToolSelect(protocol::ToolSelect *ts);
+ //! Handle layer select
+ bool handleLayerSelect(protocol::LayerSelect *ls);
+
//! Handle a stroke
bool handleStroke(protocol::StrokePoint *s);
68 src/client/ui/layerbox.ui
View
@@ -0,0 +1,68 @@
+<ui version="4.0" >
+ <class>LayerBox</class>
+ <widget class="QWidget" name="LayerBox" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>166</width>
+ <height>131</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" >
+ <property name="spacing" >
+ <number>1</number>
+ </property>
+ <property name="leftMargin" >
+ <number>3</number>
+ </property>
+ <property name="topMargin" >
+ <number>3</number>
+ </property>
+ <property name="rightMargin" >
+ <number>3</number>
+ </property>
+ <property name="bottomMargin" >
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QListView" name="layers" />
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="spacing" >
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QComboBox" name="blendmode" >
+ <property name="toolTip" >
+ <string>Layer blending mode</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="opacity" >
+ <property name="toolTip" >
+ <string>Opacity</string>
+ </property>
+ <property name="minimum" >
+ <number>0</number>
+ </property>
+ <property name="maximum" >
+ <number>100</number>
+ </property>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
28 src/client/user.cpp
View
@@ -1,7 +1,7 @@
/*
DrawPile - a collaborative drawing program.
- Copyright (C) 2006 Calle Laakkonen
+ Copyright (C) 2006-2008 Calle Laakkonen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,14 +20,29 @@
#include "user.h"
#include "boarditem.h"
+#include "layerlistwidget.h"
namespace drawingboard {
-User::User(int id)
- : id_(id), layer_(0), strokestarted_(false)
+User::User(BoardItem *board, int id)
+ : id_(id), board_(board), layerlist_(0), layer_(0), strokestarted_(false)
{
}
+void User::setLayerList(widgets::LayerList *ll)
+{
+ layerlist_ = ll;
+ // Synchronize widget with current layer selection
+ layerlist_->selectLayer(layer_);
+}
+
+void User::setLayerId(int id)
+{
+ layer_ = id;
+ if(layerlist_)
+ layerlist_->selectLayer(id);
+}
+
/**
* Starts or continues a stroke. If continuing, a line is drawn from the
* previous coordinates to \a point.
@@ -35,10 +50,11 @@ User::User(int id)
*/
void User::addStroke(const dpcore::Point& point)
{
- if(layer_) {
+ if(board_) {
if(strokestarted_) {
// Continuing stroke
- layer_->drawLine(
+ board_->drawLine(
+ layer_,
lastpoint_,