Figure 5-2 the IconEditor Widget
#define ICONEDITOR_H
#include <QColor>
#include <QImage>
#include <QWidget>
class IconEditor : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor penColor READ penColor WRITE setPenColor)
Q_PROPERTY(QImage iconImage READ iconImage WRITE setIconImage)
Q_PROPERTY(int zoomFactor READ zoomFactor WRITE setZoomFactor)
public:
IconEditor(QWidget *parent = 0);
void setPenColor(const QColor &newColor);
QColor penColor() const { return curColor; }
void setZoomFactor(int newZoom);
int zoomFactor() const { return zoom; }
void setIconImage(const QImage &newImage);
QImage iconImage() const { return image; }
QSize sizeHint() const;
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
private:
void setImagePixel(const QPoint &pos, bool opaque);
QRect pixelRect(int i, int j) const;
QColor curColor;
QImage image;
int zoom;
};
#endif
类IconEditor使用宏Q_PROPERTY()声明了三个自定义属性:penColor,iconImage,zoomFactor。每一个属性都有一个数据类型,一个读函数和一个写函数。例如,属性penColor类型为QColor,读写函数分别为penColor()和setPenColor()。
#include "iconeditor.h"
IconEditor::IconEditor(QWidget *parent)
: QWidget(parent)
{
setAttribute(Qt::WA_StaticContents);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
curColor = Qt::black;
zoom = 8;
image = QImage(16, 16, QImage::Format_ARGB32);
image.fill(qRgba(0, 0, 0, 0));
}
QSize IconEditor::sizeHint() const
{
QSize size = zoom * image.size();
if (zoom >= 3)
size += QSize(1, 1);
return size;
}
void IconEditor::setPenColor(const QColor &newColor)
{
curColor = newColor;
}
void IconEditor::setIconImage(const QImage &newImage)
{
if (newImage != image) {
image = newImage.convertToFormat(QImage::Format_ARGB32);
update();
updateGeometry();
}
}
void IconEditor::setZoomFactor(int newZoom)
{
if (newZoom < 1)
newZoom = 1;
if (newZoom != zoom) {
zoom = newZoom;
update();
updateGeometry();
}
}
void IconEditor::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
if (zoom >= 3) {
painter.setPen(palette().foreground().color());
for (int i = 0; i <= image.width(); ++i)
painter.drawLine(zoom * i, 0,
zoom * i, zoom * image.height());
for (int j = 0; j <= image.height(); ++j)
painter.drawLine(0, zoom * j,
zoom * image.width(), zoom * j);
}
for (int i = 0; i < image.width(); ++i) {
for (int j = 0; j < image.height(); ++j) {
QRect rect = pixelRect(i, j);
if (!event->region().intersect(rect).isEmpty()) {
QColor color = QColor::fromRgba(image.pixel(i, j));
painter.fillRect(rect, color);
}
}
}
}
QRect IconEditor::pixelRect(int i, int j) const
{
if (zoom >= 3) {
return QRect(zoom * i + 1, zoom * j + 1, zoom - 1, zoom - 1);
} else {
return QRect(zoom * i, zoom * j, zoom, zoom);
}
}
void IconEditor::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
setImagePixel(event->pos(), true);
} else if (event->button() == Qt::RightButton) {
setImagePixel(event->pos(), false);
}
}
void IconEditor::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
setImagePixel(event->pos(), true);
} else if (event->buttons() & Qt::RightButton) {
setImagePixel(event->pos(), false);
}
}
void IconEditor::setImagePixel(const QPoint &pos, bool opaque)
{
int i = pos.x() / zoom;
int j = pos.y() / zoom;
if (image.rect().contains(i, j)) {
if (opaque) {
image.setPixel(i, j, penColor().rgba());
} else {
image.setPixel(i, j, qRgba(0, 0, 0, 0));
}
update(pixelRect(i, j));
}
}
painter.drawLine(x1, y1, x2, y2);
QRect IconEditor::pixelRect(int i, int j) const
{
if (zoom >= 3) {
return QRect(zoom * i + 1, zoom * j + 1, zoom - 1, zoom - 1);
} else {
return QRect(zoom * i, zoom * j, zoom, zoom);
}
}
函数pixelRect()返回一个QRect,传递给QPainter::fillRect()。参数i和j是QImage对象中象素的坐标,而不是控件的坐标。只有放大倍数为1时,这两者的坐标系才是一致的。
QRect的构造函数语法为QRect(x, y, width, height),(x,y)是矩形左上角的坐标,width和height是矩形的长和宽。如果放大倍数大于等于3,为了不覆盖住网格线,我们水平和垂直方向大小都减少一个象素。
void IconEditor::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { setImagePixel(event->pos(), true); } else if (event->button() == Qt::RightButton) { setImagePixel(event->pos(), false); } }
当用户点击鼠标时,系统产生鼠标点击事件。重载QWidget::mousePressEvent(),我们可以按照我们的意愿响应这个事件,在鼠标位置设置或者清除图像象素。
如果用户点击鼠标左键,调用私有函数setImagePixel( ,true)设置当前象素为当前画笔的颜色。如果用户点击鼠标右键,也调用setImagePixel(,false)清除当前位置的象素。
void IconEditor::mouseMoveEvent(QMouseEvent *event) { if (event->buttons() & Qt::LeftButton) { setImagePixel(event->pos(), true); } else if (event->buttons() & Qt::RightButton) { setImagePixel(event->pos(), false); } }
鼠标移动事件由函数mouseMoveEvent()处理。缺省情况下,这些事件在用户保持鼠标按下时产生。调用QWidget::setMouseTracking()改变这个行为,但是这个例子中我们不需要。
点击鼠标左键设置象素,点击右键清除象素。同样一直按住鼠标或者鼠标焦点在象素位置时也进行设置和清除象素。由于可以同时按下多个鼠标键,QMouseEvent::buttons()返回的值是鼠标键按位或运算得到的。使用&运算可以确定点击的鼠标键,如果是这样,就调用setImagePixel()。
void IconEditor::setImagePixel(const QPoint &pos, bool opaque) { int i = pos.x() / zoom; int j = pos.y() / zoom; if (image.rect().contains(i, j)) { if (opaque) { image.setPixel(i, j, penColor().rgba()); } else { image.setPixel(i, j, qRgba(0, 0, 0, 0)); } update(pixelRect(i, j)); } }
函数setImagePixel()由mousePressEvent()和mouseMoveEvent()调用进行设置或清除象素。参数pos是控件上鼠标的位置。
首先鼠标坐标值x()和y()除以放大倍数是把控件坐标系的鼠标位置转换为图像坐标系里的位置。然后我们检查当前的点是否在有效区域内,使用的函数是QImage::rect()和QRect::contains(),判断i是否在0和iamge.width()-1之间,和j是否在0和image.height()-1之间。
根据opaque参数,我们或者设置或者清除图像象素。将象素值设为透明就可以清除象素。QImage::setPixel()函数需要把画笔的QColor转换为32位的ARGB值。最后,我们调用update()重新绘制需要绘制的QRect区域。
成员函数我们已经介绍完了,现在让我们来回到构造函数中的Qt::WA_StaticContents属性。这个属性的含义是当控件大小改变时,控件的内容不会跟着变化。从左上角开始保持不变。这样当控件尺寸改变时,不需要重新绘制已经绘制的区域。
通常情况下,控件尺寸改变时,Qt会产生一个控件全部可见区域的绘制事件。如果控件的属性设置为Qt::WA_StaticContents属性,绘制事件的区域就会限制在以前没有显示的部分。如果控件变小,那么没有绘制事件产生。