Qt Study Note(5-4)

Double Buffering(雙緩衝)

雙緩衝是一種GUI編程技術,它包括把一個窗口部件渲染到一個脫屏像素映射off-screen pixmap)中以及把這個像素映射覆制到顯示器上

  1. eliminate screen flash(消除屏幕閃爍)
  2. provide a good User-Interface(提供漂亮的界面)

Plotter Widget(繪圖窗口部件)

/* plotter.h */
#ifndef PLOTTER_H_
#define PLOTTER_H_

#include <QMap>
#include <QPixmap>
#include <QVector>
#include <QWidget>

class QToolButton;
class PlotSettings;

class Plotter : public QWidget
{
	Q_OBJECT
public:
	Plotter(QWidget *parent = 0);
	
	void setPlotSettings(const PlotSettings &settings);
	void setCurveData(int id, const QVector<QPointF> &data); // floating-point
	void clearCurve(int id);
	QSize minimumSizeHint() const;
	QSize sizeHint() const;
public slots:
	void zoomIn();
	void zoomOut();
protected:
	void paintEvent(QPainEvent *event);
	void resizeEvent(QResizeEvent *event);
	void mousePressEvent(QMouseEvent *event);
	void mouseMoveEvent(QMouseEvent *event);
	void mouseReleaseEvent(QMouseEvent *event);
	void keyPressEvent(QKeyEvent *event);
	void wheelEvent(QWheelEvent *event);
private:
	void updateRubberBanRegion();
	void refreshPixmap();
	void drawGrid(QPainer *painter);
	void drawCurves(QPainter *painter);
	
	enum { Margin = 50 };

	QToolButton *zoomInButton;
	QToolButton *zoomOutButton;
	QMap<int, QVector<QPointF> > curveMap;
	/*
		QMap<Key, T> is one of Qt's generic container classes. 
		It stores (key, value) pairs(鍵值對) and provides fast 
		lookup of the value associated with a key
	*/
	QVector<PlotSettings> zoomStack; // save zoom factors
	int curZoom; // save current index
	bool rubberBandShown; // rubber band(橡皮圈)
	QRect rubberBandRect;
	QPixmap pixmap; // store plotting data
	/*
		繪圖區總是先在脫屏像素映射上繪製圖形,然後才把這一像素映射覆制
		到窗口部件中,相當於脫屏像素映射是第二個緩衝區(窗體本身是第一個)
	*/
};


class PlotSettings
{
public:
	PlotSettings();

	void scroll(int dx, int dy);
	void adjust();
	double spanX() const {return maxX - minX;}
	double spanY() const {return maxY - minY;}
	
	double minX;
	double maxX;
	int numXTicks;
	double minY;
	double maxY;
	int numYTicks;
private:
	static void adjustAxis(double &min, double &max, int &numTicks);
};

#endif
/* plotter.cpp */
Plotter::Plotter(QWidget *parent) : QWidget(parent)
{
	setBackgroundRole(QPalette::Dark); // set dark portion(暗分量) as repaint color
	setAutoFillBackground(true);
	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
	setFocusPolicy(Qt::StrongFocus); // set keyboard focus
	rubberBandIsShown = false;

	zoomInButton = new QToolButton(this);
	zoomInButton->setIcon(QIcon(":/images/zoomin.png"));
	zoomInButton->adjustSize();
	connect(zoonInButton, SIGNAL(clicked()), this, SLOT(zoomIn()));

	zoomOutButton = new QToolButton(this);
	zoomOutButton->setIcon(QIcon(":/images/zoomout.png"));
	zoomOutButton->adjustSize();
	connect(zoonOutButton, SIGNAL(clicked()), this, SLOT(zoomOut()));

	setPlotSettings(PlotSettings());
}

void Plotter::setPlotSettings(const PlotSettings &settings)
{
	zoomStack.clear();
	zoomStack.append(settings);
	curZoom = 0;
	zoomInButton->hide();
	zoomOutButton->hide();
	refreshPixmap();
}

void Plotter::zoomOut() // 縮小
{
	if (curZoom > 0)
	{
		--curZoom;
		zoomOutButton->setEnabled(curZoom > 0);
		zoomInButton->setEnabled(true);
		zoomInButton->show();
		refreshPixmap();
	}
}

void Plotter::zoomIn() // 放大
{
	if (curZoom < zoomStack.count() - 1)
	{
		++curZoom;
		zoomInButton->setEnabled(curZoom < zoomStack.count() - 1);
		zoomOutButton->setEnabled(true);
		zoomOutButton->show();
		refreshPixmap();
	}
}

void Plotter::setCurveData(int id, const QVector<QPointF> &data)
{
	curveMap[id] = data;
	refreshPixmap();
}

void Plotter::clearCurve(int id)
{
	curveMap.remove(id);
	refreshPixmap();
}

QSize Plotter::minimumSizeHint() const
{
	return QSize(6 * Margin, 4 * Margin);
}

QSize Plotter::sizeHint() const
{
	return QSize(12 * Margin, 8 * Margin);
}

void Plotter::paintEvent(QPainEvent *event)
{
	QStylePainter painter(this);
	painter.drawPixmap(0, 0, pixmap);

	if (rubberBandIsShown)
	{
		painter.setPen(palette().light().color());
		painter.drawRect(rubberBandRect.normalized().adjusted(0, 0, -1, -1);
	}

	if (hasFocus())
	{	
		QStyleOptionFocusRect option;
		option.initFrom(this);
		option->backgroundColor = palette().dark().color();
		painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
	}
}

void Plotter::resizeEvent(QResizeEvent *event)
{
	int x = width() - (zoomInButton->width() + zoomOutButton->width() + 10);
	zoomInButton->move(x, 5);
	zoomOutButton->move(x + zoomInButton->width() + 5, 5);
	refreshPixmap();
}

void Plotter::mousePressEvent(QMouseEvent *event)
{
	QRect rect(Margin, Margin, width() - 2 * Margin, height() - 2 * Margin);
	if (event->button() == Qt::LeftButton)
	{
		if (rect.contains(event->pos()))
		{
			rubberBandShown = true;
			rubberBandRect.setTopLeft(event->pos());
			rubberBandRect.setBottomRight(event->pos());
			updateRubberBandRegion();
			setCursor(Qt::CrossCursor);
		}
	}
}

void Plotter::mouseMoveEvent(QMouseEvent *event)
{
	if (rubberBandIsShown)
	{
		updateRubberBandRegion();
		rubberBandRect.setBottomRight(event->pos());
		updateRubberBandRegion();
	}
}

void Plotter::mouseReleaseEvent(QMouseEvent *event)
{
    if ((event->button() == Qt::LeftButton) && rubberBandIsShown) 
    {
        rubberBandIsShown = false;
        updateRubberBandRegion();
        unsetCursor();

        QRect rect = rubberBandRect.normalized();
        if (rect.width() < 4 || rect.height() < 4)
            return;
        rect.translate(-Margin, -Margin);
// 把rubberBandRect的座標從窗口部件座標系轉換成繪圖區座標系
        PlotSettings prevSettings = zoomStack[curZoom];
        PlotSettings settings;
        double dx = prevSettings.spanX() / (width() - 2 * Margin);
        double dy = prevSettings.spanY() / (height() - 2 * Margin);
        settings.minX = prevSettings.minX + dx * rect.left();
        settings.maxX = prevSettings.minX + dx * rect.right();
        settings.minY = prevSettings.maxY - dy * rect.bottom();
        settings.maxY = prevSettings.maxY - dy * rect.top();
        settings.adjust();

        zoomStack.resize(curZoom + 1);
        zoomStack.append(settings);
        zoomIn();
    }
}

void Plotter::keyPressEvent(QKeyEvent *event)
{
    switch (event->key()) {
    case Qt::Key_Plus:
        zoomIn();
        break;
    case Qt::Key_Minus:
        zoomOut();
        break;
    case Qt::Key_Left:
        zoomStack[curZoom].scroll(-1, 0);
        refreshPixmap();
        break;
    case Qt::Key_Right:
        zoomStack[curZoom].scroll(+1, 0);
        refreshPixmap();
        break;
    case Qt::Key_Down:
        zoomStack[curZoom].scroll(0, -1);
        refreshPixmap();
        break;
    case Qt::Key_Up:
        zoomStack[curZoom].scroll(0, +1);
        refreshPixmap();
        break;
    default:
        QWidget::keyPressEvent(event);
    }
}

void Plotter::wheelEvent(QWheelEvent *event)
{
    int numDegrees = event->delta() / 8;
    int numTicks = numDegrees / 15; // 鼠標通常以15°爲一步長

    if (event->orientation() == Qt::Horizontal) {
        zoomStack[curZoom].scroll(numTicks, 0);
    } else {
        zoomStack[curZoom].scroll(0, numTicks);
    }
    refreshPixmap();
}

void Plotter::updateRubberBandRegion()
{
    QRect rect = rubberBandRect.normalized();
    update(rect.left(), rect.top(), rect.width(), 1);
    update(rect.left(), rect.top(), 1, rect.height());
    update(rect.left(), rect.bottom(), rect.width(), 1);
    update(rect.right(), rect.top(), 1, rect.height());
}

void Plotter::refreshPixmap()
{
    pixmap = QPixmap(size());
    pixmap.fill(this, 0, 0);

    QPainter painter(&pixmap);
    painter.initFrom(this);
    drawGrid(&painter);
    drawCurves(&painter);
    update();
}

void Plotter::drawGrid(QPainter *painter)
{
    QRect rect(Margin, Margin, width() - 2 * Margin, height() - 2 * Margin);
    if (!rect.isValid())
        return;

    PlotSettings settings = zoomStack[curZoom];
    QPen quiteDark = palette().dark().color().light();
    QPen light = palette().light().color();
	/* 繪製網格的垂直線和沿x軸方向上的標記符 */
    for (int i = 0; i <= settings.numXTicks; ++i) 
    {
        int x = rect.left() + (i * (rect.width() - 1) / settings.numXTicks);
        double label = settings.minX + (i * settings.spanX() / settings.numXTicks);
        painter->setPen(quiteDark);
        painter->drawLine(x, rect.top(), x, rect.bottom());
        painter->setPen(light);
        painter->drawLine(x, rect.bottom(), x, rect.bottom() + 5);
        painter->drawText(x - 50, rect.bottom() + 5, 100, 20,
                          Qt::AlignHCenter | Qt::AlignTop,
                          QString::number(label)); 
                          // drawText(x, y, width, height, alignment, text);
    }
    /* 繪製網格的水平線和沿y軸方向上的標記符 */
    for (int j = 0; j <= settings.numYTicks; ++j) 
    {
        int y = rect.bottom() - (j * (rect.height() - 1) / settings.numYTicks);
        double label = settings.minY + (j * settings.spanY() / settings.numYTicks);
        painter->setPen(quiteDark);
        painter->drawLine(rect.left(), y, rect.right(), y);
        painter->setPen(light);
        painter->drawLine(rect.left() - 5, y, rect.left(), y);
        painter->drawText(rect.left() - Margin, y - 10, Margin - 5, 20,
                          Qt::AlignRight | Qt::AlignVCenter,
                          QString::number(label));
    }
    painter->drawRect(rect.adjusted(0, 0, -1, -1));
}

void Plotter::drawCurves(QPainter *painter)
{
    static const QColor colorForIds[6] = {
        Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow
    };
    PlotSettings settings = zoomStack[curZoom];
    QRect rect(Margin, Margin, width() - 2 * Margin, height() - 2 * Margin);
    if (!rect.isValid())
        return;

    painter->setClipRect(rect.adjusted(+1, +1, -1, -1)); // 設置剪輯區

    QMapIterator<int, QVector<QPointF> > i(curveMap); // java-style iterator
    /*
    	迭代器:GOF給出的定義爲:提供一種方法訪問一個容器(container)對象中
    	各個元素,而又不需暴露該對象的內部細節
    */
    while (i.hasNext()) 
    {
        i.next();

        int id = i.key();
        QVector<QPointF> data = i.value();
        QPolygonF polyline(data.count());

        for (int j = 0; j < data.count(); ++j) {
            double dx = data[j].x() - settings.minX;
            double dy = data[j].y() - settings.minY;
            double x = rect.left() + (dx * (rect.width() - 1)
                                         / settings.spanX());
            double y = rect.bottom() - (dy * (rect.height() - 1)
                                           / settings.spanY());
            polyline[j] = QPointF(x, y); // 繪製折線
        }
        painter->setPen(colorForIds[uint(id) % 6]);
        painter->drawPolyline(polyline);
    }
}
/* plotsettings.cpp */
PlotSettings::PlotSettings()
{
    minX = 0.0;
    maxX = 10.0;
    numXTicks = 5;

    minY = 0.0;
    maxY = 10.0;
    numYTicks = 5;
}

void PlotSettings::scroll(int dx, int dy)
{
    double stepX = spanX() / numXTicks;
    minX += dx * stepX;
    maxX += dx * stepX;

    double stepY = spanY() / numYTicks;
    minY += dy * stepY;
    maxY += dy * stepY;
}

void PlotSettings::adjust()
{
    adjustAxis(minX, maxX, numXTicks);
    adjustAxis(minY, maxY, numYTicks);
}

void PlotSettings::adjustAxis(double &min, double &max, int &numTicks)
{
    const int MinTicks = 4;
    double grossStep = (max - min) / MinTicks;
    double step = std::pow(10.0, std::floor(std::log10(grossStep)));
    // floor(x),其功能是“向下取整”

    if (5 * step < grossStep) {
        step *= 5;
    } else if (2 * step < grossStep) {
        step *= 2;
    }

    numTicks = int(std::ceil(max / step) - std::floor(min / step));
    // ceil(x),其功能是“向上取整”
    if (numTicks < MinTicks) // whether smaller than 4
        numTicks = MinTicks;
    min = std::floor(min / step) * step;
    max = std::ceil(max / step) * step;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章