(转)Qt图形视图框架(二)

// demo: 城市地图
// 主要特点:
// 滚动和缩放: 鼠标滚动,放大或者缩小地图
// 键盘操作: 上下左右,PageUp, PageDown
// 自定义视图, 自定义图元
// 显示注解

// cityscape.h
#ifndef CITYSCAPE_H
#define CITYSCAPE_H

#include <QMainWindow>

class QGraphicsScene;
class CityView;

class Cityscape : public QMainWindow
{
Q_OBJECT

public:
Cityscape();

private:
void generateCityBlocks();

QGraphicsScene *scene;
CityView *view;
};

#endif

// cityscape.cpp
#include <QtGui>

#include "annotation.h"
#include "cityblock.h"
#include "cityscape.h"
#include "cityview.h"

Cityscape::Cityscape()
{
scene = new QGraphicsScene(-22.25, -22.25, 1980, 1980);
scene->setBackgroundBrush(QColor(255, 255, 238));
generateCityBlocks();

view = new CityView;
view->setScene(scene);
setCentralWidget(view);

setWindowTitle(tr("Cityscape"));
}

void Cityscape::generateCityBlocks()
{
QSet<QString> names;
names << "Adams" << "Agnew" << "Arthur" << "Breckinridge"
<< "Buchanan" << "Burr" << "Bush" << "Calhoun" << "Carter"
<< "Cheney" << "Cleveland" << "Clinton" << "Colfax"
<< "Coolidge" << "Curtis" << "Dallas" << "Dawes"
<< "Eisenhower" << "Fairbanks" << "Fillmore" << "Ford"
<< "Garfield" << "Garner" << "Gerry" << "Gore" << "Grant"
<< "Hamlin" << "Harding" << "Harrison" << "Hayes"
<< "Hendricks" << "Hobart" << "Hoover" << "Humphrey"
<< "Jackson" << "Jefferson" << "Johnson" << "Kennedy"
<< "King" << "Lincoln" << "Madison" << "Marshall"
<< "McKinley" << "Mondale" << "Monroe" << "Morton"
<< "Nixon" << "Pierce" << "Polk" << "Quayle" << "Reagan"
<< "Rockefeller" << "Roosevelt" << "Sherman" << "Stevenson"
<< "Taft" << "Taylor" << "Tompkins" << "Truman" << "Tyler"
<< "Van Buren" << "Wallace" << "Washington" << "Wheeler"
<< "Wilson";

QSetIterator<QString> i(names);
for (int y = 0; y < 44; ++y) {
for (int x = 0; x < 44; ++x) {
int percentile;
if (x > 20 && x < 24 && y > 20 && y < 24) {
percentile = std::rand() % (std::rand() % 2 != 0
? 10 : 100);
} else if (x > 18 && x < 26 && y > 18 && y < 26) {
percentile = std::rand() % (rand() % 3 != 0
? 10 : 100);
} else if (x > 15 && x < 29 && y > 15 && y < 29) {
percentile = std::rand() % (std::rand() % 5 != 0
? 10 : 100);
} else {
percentile = std::rand() % 100;
}

CityBlock::Kind kind;
QString name;

if (percentile == 0) {
kind = CityBlock::Park;
name = tr("%1 Park");
} else if (percentile <= 2) {
kind = CityBlock::SmallBuilding;
} else if (percentile <= 4) {
kind = CityBlock::Hospital;
name = tr("%1 Hospital");
} else if (percentile == 5) {
kind = CityBlock::Hall;
name = tr("%1 Hall");
} else if (percentile <= 7) {
kind = CityBlock::Building;
name = tr("%1 Bldg");
} else if (percentile <= 9) {
kind = CityBlock::Tower;
name = tr("%1 Tower");
} else if (percentile <= 15) {
kind = CityBlock::LShapedBlock;
} else if (percentile <= 30) {
kind = CityBlock::LShapedBlockPlusSmallBlock;
} else if (percentile <= 70) {
kind = CityBlock::TwoBlocks;
} else {
kind = CityBlock::BlockPlusTwoSmallBlocks;
}

CityBlock *block = new CityBlock(kind);
block->setPos(QPointF(x * 44.5, y * 44.5));
scene->addItem(block);

if (!name.isEmpty()) {
if (!i.hasNext())
i.toFront();

bool major = (std::rand() % 10 == 0);
Annotation *annotation =
new Annotation(name.arg(i.next()), major);
annotation->setPos(block->pos());
scene->addItem(annotation);
}
}
}
}

// cityblock.h 自定义图元
#define CITYBLOCK_H

#include <QColor>
#include <QGraphicsItem>
#include <QPainterPath>

class QGradient;

class CityBlock : public QGraphicsItem
{
public:
enum Kind { Park, SmallBuilding, Hospital, Hall, Building, Tower,
LShapedBlock, LShapedBlockPlusSmallBlock, TwoBlocks,
BlockPlusTwoSmallBlocks };

CityBlock(Kind kind);

QRectF boundingRect() const;
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option, QWidget *widget);

private:
int kind;
QColor color;
QPainterPath shape;
};

#endif

// cityblock.cpp
#include <QtGui>
#include <cmath>

#include "cityblock.h"

CityBlock::CityBlock(Kind kind)
{
this->kind = kind;

int green = 96 + (std::rand() % 64);
int red = 16 + green + (std::rand() % 64);
int blue = 16 + (std::rand() % green);
color = QColor(red, green, blue);

if (kind == Park) {
color = QColor(192 + (std::rand() % 32), 255,
192 + (std::rand() % 16));
shape.addRect(boundingRect());
} else if (kind == SmallBuilding) {
QRectF block(-7.5, -7.5, 15, 15);
block.moveBottomLeft(QPointF((std::rand() % 6) - 3,
(std::rand() % 6) - 3));
shape.addRect(block);
} else if (kind == Hospital) {
int a = (std::rand() % 6) + 10;
int b = (std::rand() % 6) + 10;
QPolygonF block;
block << QPointF(-5, -a) << QPointF(-5, -5) << QPointF(-10, -5)
<< QPointF(-10, 5) << QPointF(-5, 5)  << QPointF(-5, 10)
<< QPointF(5, 10)  << QPointF(5, 5)   << QPointF(b, 5)
<< QPointF(b, -5)  << QPointF(5, -5)  << QPointF(5, -a);
shape.addPolygon(block);
} else if (kind == Hall) {
int padding1 = (std::rand() % 8) + 2;
int padding2 = (std::rand() % 8) + 2;
shape.addEllipse(boundingRect().adjusted(+padding1, +padding1,
-padding2, -padding2));
} else if (kind == Building) {
shape.addRect(boundingRect());
} else if (kind == Tower) {
int padding1 = (std::rand() % 8) + 2;
int padding2 = (std::rand() % 8) + 2;
shape.addRect(boundingRect().adjusted(+padding1, +padding1,
-padding2, -padding2));
} else if (kind == LShapedBlock
|| kind == LShapedBlockPlusSmallBlock) {
int a = (std::rand() % 6) + 10;
int b = (std::rand() % 6) + 10;
int s = qMin(a, b) / 2;
QPolygonF block;
block << QPointF(-a, -a) << QPointF(-a, +a) << QPointF(-s, +a)
<< QPointF(-s, -s) << QPointF(+b, -s) << QPointF(+b, -a);
shape.addPolygon(block);
if (kind == LShapedBlockPlusSmallBlock) {
int inset = (std::rand() % 4) + 4;
shape.addRect(QRectF(-s + inset, -s + inset, a, b));
}
} else if (kind == TwoBlocks) {
int w1 = (std::rand() % 10) + 8;
int h1 = (std::rand() % 28) + 8;
int w2 = (std::rand() % 10) + 8;
int h2 = (std::rand() % 24) + 8;
shape.addRect(QRectF(-16, -16, w1, h1));
shape.addRect(QRectF(-16 + w1 + 4, -16 + (std::rand() % 4),
w2, h2));
} else if (kind == BlockPlusTwoSmallBlocks) {
int w1 = (std::rand() % 10) + 8;
int h1 = (std::rand() % 28) + 8;
int w2 = (std::rand() % 10) + 8;
int h2 = (std::rand() % 10) + 8;
int w3 = (std::rand() % 6) + 8;
int h3 = (std::rand() % 6) + 8;
int y = (std::rand() % 4) - 16;
shape.addRect(QRectF(-16, -16, w1, h1));
shape.addRect(QRectF(-16 + w1 + 4, y, w2, h2));
shape.addRect(QRectF(-16 + w1 + 4,
y + h2 + 4 + (std::rand() % 4), w3, h3));
}
}

QRectF CityBlock::boundingRect() const
{
return QRectF(-20, -20, 40, 40); // 返回一个矩形,40x40, 中心点在(0,0)
}

void CityBlock::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget * /* widget */)
{
if (option->levelOfDetail < 4.0) {    // 缩放倍数
painter->fillPath(shape, color);
} else {
QLinearGradient gradient(QPoint(-20, -20), QPoint(+20, +20));
int coeff = 105 + int(std::log(option->levelOfDetail - 4.0));
gradient.setColorAt(0.0, color.lighter(coeff));
gradient.setColorAt(1.0, color.darker(coeff));
painter->fillPath(shape, gradient);
}
}

// annotation.h 注解类图元,不缩放
#ifndef ANNOTATION_H
#define ANNOTATION_H

#include <QGraphicsItem>

class Annotation : public QGraphicsItem
{
public:
Annotation(const QString &text, bool major = false);

void setText(const QString &text);
QString text() const;

QRectF boundingRect() const;
void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option, QWidget *widget);

private:
QFont font;
QString str;
bool major;
double threshold;
int y;
};

#endif

//annotation.cpp
#include <QtGui>
#include <cmath>

#include "annotation.h"

Annotation::Annotation(const QString &text, bool major)
{
font = qApp->font();
font.setBold(true);
if (major) {
font.setPointSize(font.pointSize() + 2);
font.setStretch(QFont::SemiExpanded);
}

if (major) {
threshold = 0.01 * (40 + (std::rand() % 40));
} else {
threshold = 0.01 * (100 + (std::rand() % 100));
}

str = text;
this->major = major;
y = 20 - (std::rand() % 40);

setZValue(1000);  // 位于最上面
setFlag(ItemIgnoresTransformations, true); // 忽略缩放
}

void Annotation::setText(const QString &text)
{
prepareGeometryChange();
str = text;
update();
}

QString Annotation::text() const
{
return str;
}

QRectF Annotation::boundingRect() const
{
QFontMetricsF metrics(font);
QRectF rect = metrics.boundingRect(str);
rect.moveCenter(QPointF(0, y));
rect.adjust(-4, 0, +4, 0);
return rect;
}

void Annotation::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget * /* widget */)
{
if (option->levelOfDetail <= threshold)
return;

painter->setFont(font);

QRectF rect = boundingRect();

int alpha = int(30 * std::log(option->levelOfDetail));
if (alpha >= 32)
painter->fillRect(rect, QColor(255, 255, 255, qMin(alpha, 63)));

painter->setPen(Qt::white);
painter->drawText(rect.translated(+1, +1), str,
QTextOption(Qt::AlignCenter));
painter->setPen(Qt::blue);
painter->drawText(rect, str, QTextOption(Qt::AlignCenter));  // 画2次文本
}

// cityview.h 自定义视图
#ifndef CITYVIEW_H
#define CITYVIEW_H

#include <QGraphicsView>

class CityView : public QGraphicsView
{
Q_OBJECT

public:
CityView(QWidget *parent = 0);

protected:
void wheelEvent(QWheelEvent *event);  // 滚轮事件
};

#endif

// cityview.cpp
#include <QtGui>
#include <cmath>

#include "cityview.h"

CityView::CityView(QWidget *parent)
: QGraphicsView(parent)
{
setDragMode(ScrollHandDrag); // 支持鼠标拖动
}

void CityView::wheelEvent(QWheelEvent *event)
{
double numDegrees = -event->delta() / 8.0;
double numSteps = numDegrees / 15.0;
double factor = std::pow(1.125, numSteps);
scale(factor, factor);
}


// 使用QGraphicsItemAnimation和QTimeLine实现动画效果

#include <QtGui>
#include <cmath>

using namespace std;

const qreal PI = 3.14159265;

int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QGraphicsEllipseItem *sun = new QGraphicsEllipseItem(0, 0, 20, 20); // 图元
sun->setBrush(Qt::red);
sun->setPen(QPen(Qt::red));

QTimeLine *timeline = new QTimeLine(10000);  // 10秒的动画时间
timeline->setCurveShape(QTimeLine::LinearCurve);
//timeline->setFrameRange(0, 100);

QGraphicsItemAnimation *animation = new QGraphicsItemAnimation; // 动画
animation->setItem(sun);
animation->setTimeLine(timeline);

qreal x, y;
qreal angle = PI;
for (int i = 0; i <= 180; ++i)
{
x = 200.0 * cos(angle);
y = 200.0 * sin(angle);
qDebug() << x << y;
animation->setPosAt(i/180.0, QPointF(x, y));
angle += PI/180.0;
}

QGraphicsScene *scene = new QGraphicsScene();
scene->addItem(sun);

QGraphicsView *view = new QGraphicsView(scene);
view->resize(640,480);
view->show();

timeline->start(); // 开始运动
return app.exec();
}

// 图元的右键处理
// target.h
#ifndef TARGET_H
#define TARGET_H

#include <QGraphicsItem>
#include <QObject>

class Target : public QObject, public QGraphicsItem
{
Q_OBJECT

public:
Target();

QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget);

public:
qreal course;
qreal speed;
short type;         // surface, underwater, air
short attribute;     // friend, foe, unknown
QColor color;

protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); // 右键菜单
};

#endif

// target.cpp
#include "target.h"

#include <QGraphicsScene>
#include <QPainter>
#include <QStyleOption>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>
#include <QAction>

#include <math.h>

static const double Pi = 3.14159265358979323846264338327950288419717;

Target::Target()
: speed(qrand()%10+1), type(qrand()%3), attribute(qrand()%3)
{
course = (qrand() % 360);
switch(attribute) {
case 0: // 敌方
color.setRgb(255, 0, 0);
break;
case 1: // 我方
color.setRgb(0, 255, 255);
break;
default: // 不明
color.setRgb(255, 255, 0);
break;
}       
}

QRectF Target::boundingRect() const
{
qreal adjust = 0.5;
return QRectF(-20 - adjust, -22 - adjust,
40 + adjust, 83 + adjust);
}

void Target::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
QPen pen(color);
pen.setWidth(2);
painter->setPen(pen);
switch(type) {
case 0:     //水面目标
painter->drawEllipse(-15, -15, 30, 30);
break;
case 1:      // 水下目标
painter->drawArc(-15, -15, 30, 30, 180*16, 180*16);
break;
default:        // 空中目标
painter->drawLine(-15, 0, 0, -15);
painter->drawLine(0, -15, 15, 0);
break;
}
painter->drawLine(0, 0, int(speed*5*cos(course)), int(speed*5*sin(course)));
}

void Target::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)  // 右键处理
{
QMenu menu;
menu.setWindowOpacity(0.8);
QAction *removeAction = menu.addAction(tr("武器发射"));
QAction *markAction = menu.addAction(tr("电子干扰"));
QAction *selectedAction = menu.exec(event->screenPos());
}

你可能感兴趣的:((转)Qt图形视图框架(二))