Qt antialiasing sets different pen widths - c++

I have a qt app which has a big QGraphicsView where I draw a grid.
This grid reacts on mouse clicks. Basically, if you click on some box -> the box is made red. And if you double click on it -> it goes back to its original state (its white).
This is the implementation of my QGraphicsView:
#include "mapview.h"
// Constructors
mapview::mapview()
{
setUpGui();
}
mapview::mapview(QWidget *parent) : QGraphicsView(parent)
{
setUpGui();
}
// GUI setup
void mapview::setUpGui()
{
scene = new QGraphicsScene(this);
this->setScene(scene);
this->setRenderHint(QPainter::Antialiasing);
// this->setRenderHint(QPainter::HighQualityAntialiasing);
}
// Events
void mapview::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
lastPoint = event->pos();
scribbling = true;
}
}
void mapview::mouseMoveEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton) && scribbling)
{
drawWall(event->pos());
}
}
void mapview::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && scribbling)
{
drawWall(event->pos());
scribbling = false;
}
}
void mapview::mouseDoubleClickEvent(QMouseEvent *event)
{
removeWall(event->pos());
}
// Drawing methods
void mapview::drawGrid(const int box_count)
{
scene->clear();
auto x = 0.0;
auto y = 0.0;
this->margin = 20.0;
_width = this->width() - 2 * margin;
_height = this->height() - 2 * margin;
if (fabs(_width - _height) >= std::numeric_limits<double>::epsilon())
{
// qDebug() << "width (" << width << ") != height (" << height << ")";
return;
}
this->box_count = box_count;
this->box_size = _width / box_count;
// Horizontal
for (auto i = 0; i <= box_count; i++)
{
QGraphicsLineItem *line = new QGraphicsLineItem(x, y, x + _width, y);
QPen pen;
pen.setColor(Qt::black);
line->setPen(pen);
// scene->addLine(x, y, x + _width, y);
scene->addItem(line);
y += box_size;
}
y = 0.0;
// Vertical
for (auto i = 0; i <= box_count; i++)
{
scene->addLine(x, y, x, y + _height);
x += box_size;
}
}
void mapview::drawWall(const QPointF &endPoint)
{
auto x = endPoint.x() - margin;
auto y = endPoint.y() - margin;
x = static_cast<int>(x / box_size) * box_size;
y = static_cast<int>(y / box_size) * box_size;
QGraphicsRectItem* rect = new QGraphicsRectItem(x, y, this->box_size, this->box_size);
rect->setBrush(QBrush(Qt::red));
rect->setPen(QPen());
scene->addItem(rect);
}
void mapview::removeWall(const QPointF &point)
{
auto x = point.x() - margin;
auto y = point.y() - margin;
x = static_cast<int>(x / box_size) * box_size;
y = static_cast<int>(y / box_size) * box_size;
QGraphicsRectItem* rect = new QGraphicsRectItem(x, y, this->box_size, this->box_size);
rect->setBrush(QBrush(Qt::white));
rect->setPen(QPen());
scene->addItem(rect);
}
As you can see at the top, I set up antialiasing. The problem is, that when I set it up, it somehow changes the view of my drawing:
This is the view of an untouched grid:
Now for the box 1x1 (indexed from 0) I've clicked it (made it red) and then I double clicked it (so it got back to its original state). The problem is, as you can see, the border of this box is kinda thicker:
Funny thing is, that it's only thicker on this view. If I zoom it in, there's no difference between its border and the borders of other boxes. The other thing is, if I set Qt::HighQualityAntialiasing -> the border is the same, but then the zooming gets laggy, I guess it has to do more heavy computations or something like this.
So my question here would be: Is there a way, to make the antialiasing not change the thickness of the border? (I mean, I know it doesn't really change it, but you can definitely see a difference here)

Related

How can I modify my Qt PaintEvent code to scale a displayed Pixmap relative to the mouse pointer

I have a (so far) fairly simple QWidget (size of say 300 * 300) that I am using to display a large QPixmap (e.g 5184 * 3456). The key parts of the code read as follows:
void DSSImageWidget::resizeEvent(QResizeEvent* e)
{
QSize sz = e->size();
qreal hScale = (qreal)sz.width() / (m_pixmap.width() + 4);
qreal vScale = (qreal)sz.height() / (m_pixmap.height() + 4);
m_scale = std::min(hScale, vScale);
update();
Inherited::resizeEvent(e);
}
void DSSImageWidget::paintEvent(QPaintEvent* event)
{
QPainter painter;
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
//QPointF whereScaled = m_where / (m_zoom * m_scale);
//qDebug() << "m_where:" << m_where.x() << m_where.y();
//qDebug() << whereScaled.x() << " " << whereScaled.y();
//painter.translate(-m_where);
painter.scale(m_zoom*m_scale, m_zoom*m_scale);
//painter.translate(m_where);
painter.drawPixmap(QPointF(0.0, 0.0), m_pixmap);
painter.setPen(QPen(QColor(255, 0, 0, alpha), 0.25, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin));
painter.setBrush(Qt::NoBrush);
painter.drawRect(QRectF(0, 0, m_pixmap.width(), m_pixmap.height()).adjusted(-2, -2, 2, 2));
painter.end();
}
void DSSImageWidget::wheelEvent(QWheelEvent* e)
{
qreal degrees = -e->angleDelta().y() / 8.0;
//
// If zooming in and zoom factor is currently 1.0
// then remember mouse location
//
if ((degrees > 0) && (m_zoom == 1.0))
{
m_where = e->position();
}
qreal steps = degrees / 60.0;
qreal factor = m_zoom * std::pow(1.125, steps);
m_zoom = std::clamp(factor, 1.0, 5.0);
update();
Inherited::wheelEvent(e);
}
This scales the pixmap about the window origin which is a good start, but it's not what I want. I want it to scale the pixmap so that the part of the image under the mouse pointer remains where it is and the image expands around that point.
I've played all sorts of tunes with the painter.translate() calls and the location I use to actually draw the pixmap, but I've so far failed with flying colours :).
Please could someone who knows how this stuff actually works put me out of my misery and tell me how I achieve my objective here?
Thank you
After quite some grappling with the code I've finally solved this. Given that it wasn't dead simple, I'm posting the relevant portions of the code here in the hope it will assist someone else.
From the header:
typedef QWidget
Inherited;
private:
bool initialised;
qreal m_scale, m_zoom;
QPointF m_origin;
QPixmap & m_pixmap;
QPointF m_pointInPixmap;
inline bool mouseOverImage(QPointF loc)
{
qreal x = loc.x(), y = loc.y(), ox = m_origin.x(), oy = m_origin.y();
return (
(x >= ox) &&
(x <= ox + (m_pixmap.width() * m_scale)) &&
(y >= oy) &&
(y <= oy + (m_pixmap.height() * m_scale)));
};
And the source code:
DSSImageWidget::DSSImageWidget(QPixmap& p, QWidget* parent)
: QWidget(parent),
initialised(false),
m_scale(1.0),
m_zoom(1.0),
m_origin(0.0, 0.0),
m_pixmap(p),
m_pointInPixmap((m_pixmap.width() / 2), (m_pixmap.height() / 2))
{
setAttribute(Qt::WA_MouseTracking);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
void DSSImageWidget::resizeEvent(QResizeEvent* e)
{
QSize sz = e->size();
qreal pixWidth = m_pixmap.width();
qreal pixHeight = m_pixmap.height();
qreal hScale = (qreal)sz.width() / pixWidth;
qreal vScale = (qreal)sz.height() / pixHeight;
m_scale = std::min(hScale, vScale);
qreal xoffset = 0.0, yoffset = 0.0;
if ((pixWidth * m_scale) < sz.width())
{
xoffset = (sz.width() - (pixWidth * m_scale)) / 2.0;
}
if ((pixHeight * m_scale) < sz.height())
{
yoffset = (sz.height() - (pixHeight * m_scale)) / 2.0;
}
m_origin = QPointF(xoffset, yoffset);
update();
Inherited::resizeEvent(e);
}
void DSSImageWidget::paintEvent(QPaintEvent* event)
{
QPainter painter;
qDebug() << "pointInPixmap: " << m_pointInPixmap.x() << m_pointInPixmap.y();
//
// Now calcualate the rectangle we're interested in
//
qreal width = m_pixmap.width();
qreal height = m_pixmap.height();
qreal x = m_pointInPixmap.x();
qreal y = m_pointInPixmap.y();
QRectF sourceRect(
x - (x / m_zoom),
y - (y / m_zoom),
width / m_zoom,
height / m_zoom
);
qDebug() << "sourceRect: " << sourceRect.x() << sourceRect.y() << sourceRect.width() << sourceRect.height();
//
// Now calculate the rectangle that is the intersection of this rectangle and the pixmap's rectangle.
//
sourceRect &= m_pixmap.rect();
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.translate(m_origin);
painter.scale(m_zoom*m_scale, m_zoom*m_scale);
painter.translate(-m_origin);
//painter.drawPixmap(QPointF(0.0, 0.0), m_pixmap, sourceRect);
painter.drawPixmap(m_origin, m_pixmap, sourceRect);
painter.end();
}
#if QT_CONFIG(wheelevent)
void DSSImageWidget::wheelEvent(QWheelEvent* e)
{
qreal degrees = -e->angleDelta().y() / 8.0;
//
// If zooming in and zoom factor is currently 1.0
// then remember mouse location
//
if ((degrees > 0) && (m_zoom == 1.0))
{
QPointF mouseLocation = e->position();
if (mouseOverImage(mouseLocation))
{
m_pointInPixmap = QPointF((mouseLocation-m_origin) / m_scale);
}
else
{
m_pointInPixmap = QPointF((m_pixmap.width() / 2), (m_pixmap.height() / 2));
}
}
qreal steps = degrees / 60.0;
qreal factor = m_zoom * std::pow(1.125, steps);
m_zoom = std::clamp(factor, 1.0, 5.0);
if (degrees < 0 && m_zoom == 1.0)
{
m_pointInPixmap = QPointF((m_pixmap.width() / 2), (m_pixmap.height() / 2));
}
update();
Inherited::wheelEvent(e);
}
#endif
All the best, David

Refresh error when dragging a line in QSceneView

I need to draw a connector between two items in a QSceneView. I've subclssed QGraphicsObject for creating this line (it's not a QGraphicsLineItem because I need to update its appearance later). This is the class:
#ifndef TRANSITIONDRAGLINE_HPP_
#define TRANSITIONDRAGLINE_HPP_
#include "Style/CanvasTransitionDragLine.hpp"
#include <QGraphicsObject>
#include <QPointF>
#include <string>
class TransitionDragLine : public QGraphicsObject {
Q_OBJECT
public:
TransitionDragLine(const Style::CanvasTransitionDragLine& style, QGraphicsItem* parent = nullptr);
virtual ~TransitionDragLine();
void setStartPoint(const QPointF& startPoint);
void setEndPoint(const QPointF& endPoint);
public:
QRectF boundingRect() const override;
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
private:
Style::CanvasTransitionDragLine m_style;
QPointF m_startPoint;
QPointF m_endPoint;
};
#endif // !TRANSITIONDRAGLINE_HPP_
#include "TransitionDragLine.hpp"
#include <QPainter>
///////////////////////////////////////////////////////////////////////////////
// PUBLIC SECTION //
///////////////////////////////////////////////////////////////////////////////
TransitionDragLine::TransitionDragLine(const Style::CanvasTransitionDragLine& style, QGraphicsItem* parent) :
QGraphicsObject(parent),
m_style(style) {
}
TransitionDragLine::~TransitionDragLine() {
}
void TransitionDragLine::setStartPoint(const QPointF& startPoint) {
m_startPoint = startPoint;
}
void TransitionDragLine::setEndPoint(const QPointF& endPoint) {
m_endPoint = endPoint;
update();
}
///////////////////////////////////////////////////////////////////////////////
// VIRTUAL PUBLIC SECTION //
///////////////////////////////////////////////////////////////////////////////
QRectF TransitionDragLine::boundingRect() const {
qreal dx = m_endPoint.x() - m_startPoint.x();
qreal dy = m_endPoint.y() - m_startPoint.y();
qreal x{ 0.0 };
qreal y{ 0.0 };
qreal w{ 0.0 };
qreal h{ 0.0 };
qreal penHalfWidth{ m_style.getPen().widthF() * 0.5 };
if (dx >= 0.0 && dy >= 0.0) {
x = 0.0 - penHalfWidth;
y = 0.0 - penHalfWidth;
w = dx + penHalfWidth;
h = dy + penHalfWidth;
}
else if (dx >= 0.0 && dy < 0.0) {
x = 0.0 - penHalfWidth;
y = dy - penHalfWidth;
w = dx - penHalfWidth;
h = -dy - penHalfWidth;
}
else if (dx < 0.0 && dy >= 0.0) {
x = dx - penHalfWidth;
y = 0 - penHalfWidth;
w = -dx - penHalfWidth;
h = dy - penHalfWidth;
}
else {
x = dx - penHalfWidth;
y = dy - penHalfWidth;
w = -dx - penHalfWidth;
h = -dy - penHalfWidth;
}
return QRectF(x, y, w, h);
}
void TransitionDragLine::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
qreal px = m_endPoint.x() - m_startPoint.x();
qreal py = m_endPoint.y() - m_startPoint.y();
painter->setPen(m_style.getPen());
painter->drawLine(0.0, 0.0, px, py);
}
When I want to use it, I set a flag in my Canvas class, that inherits QGraphicsView, and I update it. When I press the mouse button I set the start point and then I set the endpoint (and call update()), every time that I move the mouse until left button is pressed. Basically I call startTransitionLineDrag in polling until the mouse is pressed, and when I release the mouse button I call endTransitionLineDrag in my Canvas (subclass of QGraphicsView):
void Canvas::mouseMoveEvent(QMouseEvent* event) {
switch (m_currentState) {
case CurrentState::PressedOnTransition: {
if (event->buttons() == Qt::LeftButton) {
startTransitionLineDrag(event);
}
} break;
}
QGraphicsView::mouseMoveEvent(event);
}
void Canvas::mouseReleaseEvent(QMouseEvent* event) {
if (m_currentState == CurrentState::PressedOnTransition) {
if (true /* business logic here not useful for the problem */) {
endTransitionLineDrag(event);
}
}
QGraphicsView::mouseReleaseEvent(event);
}
void Canvas::startTransitionLineDrag(QMouseEvent* event) {
if (m_transitionDragLine == nullptr) {
m_transitionDragLine = new TransitionDragLine(m_style.getTransitionDragLineStyle());
m_transitionDragLine->setStartPoint(mapToScene(event->pos()));
m_scene->addItem(m_transitionDragLine);
m_transitionDragLine->setPos(mapToScene(event->pos()));
}
m_transitionDragLine->setEndPoint(mapToScene(event->pos()));
//repaint();
}
void Canvas::endTransitionLineDrag(QMouseEvent* event) {
/* other business logic */
deleteDragTransitionLine();
}
void Canvas::deleteDragTransitionLine() {
if (m_transitionDragLine) {
m_scene->removeItem(m_transitionDragLine);
delete m_transitionDragLine;
m_transitionDragLine = nullptr;
}
}
The logic works: when I activate the dragging I can see the line and it's update until the mouse button is pressed. But you can see in the attached image that I've a rendering problem:
The rendering is not working properly for the line; I've a trail of past images of the line, like the QGraphicsView is not updated properly.
I sense a code smell when in TransitionDragLine::setEndPoint I need to call update() after setting the end point of the line (otherwise the line is not displayed) but I don't find a way to solve the issue.
What I'm doing wrong?
EDIT:
I've seen a solution here:
Artifacts showing when modifying a custom QGraphicsItem
I've tried to call prepareGeometryChange() every time that I update the end point but artifacts remain:
void TransitionDragLine::setEndPoint(const QPointF& endPoint) {
m_endPoint = endPoint;
prepareGeometryChange();
update();
}
It looks like your implementation of QRectF TransitionDragLine::boundingRect() const is incorrect.
It can be a bit tricky to get it right since multiple coordinate systems are involved.
The proper solution would be to get the bounding boxes right, but for small scenes, it's usually good enough to set the QGraphicsView::updateMode to QGraphicsView::FullViewportUpdate.
That is, QGraphicsView will redraw the whole viewport rather than just the bounding box area.

How to extract collision coordinates (x,y) between objects in QGraphicsScene and send the coordinates (x,y) to QLineEdit

I am trying to extract collision coordinate between two rectangles moving inside a QGraphicsScene and send these coordinates to a QLineEdit. Basically the position of the rectangle when they collide.
I was reading this source and this source.
They advice to use QGraphicsItem::collidesWithPath but no success.
This is the snipped of code that does the collision with my own implementation:
MyItem::MyItem()
{
angle = (qrand() % 360);
setRotation(angle);
speed = 5;
int startX = 0;
int startY = 0;
if((qrand() % 1)){
startX = (qrand() % 200);
startY = (qrand() % 200);
}else{
startX = (qrand() % -100);
startY = (qrand() % -100);
}
setPos(mapToParent(startX, startY));
}
void MyItem::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QRectF rec = boundingRect();
QBrush brush(Qt::gray);
if(scene()->collidingItems(this).isEmpty()){
brush.setColor(Qt::green); // no collision
}else{
brush.setColor(Qt::red); // yes collision
doCollision();
}
painter->fillRect(rec, brush);
painter->drawRect(rec);
}
void MyItem::advance(int phase)
{
if(!phase) return;
QPointF location = this->pos();
setPos(mapToParent(0, -(speed)));
}
void MyItem::doCollision()
{
if((qrand() %1)) {
setRotation(rotation() + (180+(qrand() % 10)));
}else{
setRotation(rotation() + (180+(qrand() % -10)));
}
// see if the new position is in bounds
QPointF newPoint = mapToParent(-(boundingRect().width()), -(boundingRect().width() + 2));
if(!scene()->sceneRect().contains((newPoint))){
newPoint = mapToParent(0, 0); // move it back in bounds
}else{
setPos(newPoint); // set new position
}
}
How to extract collision coordinates (x,y) between objects in QGraphicsScene and send the coordinates (x,y) to QLineEdit?
Thanks for shedding light on this issue.

Drawing Line in grid with snap Functionality

I want to draw a line in grid and implement snap functionality to it. I have drawn grid and line but snap functionality is not implemented. How can I implement that functionality in it. I have drawn grid as follows:
void CadGraphicsScene::drawBackground(QPainter *painter, const QRectF &rect)
{
if (isGridVisible)
{
const int gridSize = 50;
const int realLeft = static_cast<int>(std::floor(rect.left()));
const int realRight = static_cast<int>(std::ceil(rect.right()));
const int realTop = static_cast<int>(std::floor(rect.top()));
const int realBottom = static_cast<int>(std::ceil(rect.bottom()));
// Draw grid.
const int firstLeftGridLine = realLeft - (realLeft % gridSize);
const int firstTopGridLine = realTop - (realTop % gridSize);
QVarLengthArray<QLine, 100> lines;
for (qreal x = firstLeftGridLine; x <= realRight; x += gridSize)
lines.append(QLine(x, realTop, x, realBottom));
for (qreal y = firstTopGridLine; y <= realBottom; y += gridSize)
lines.append(QLine(realLeft, y, realRight, y));
painter->setPen(QPen(QColor(220, 220, 220), 0.0));
painter->drawLines(lines.data(), lines.size());
// Draw axes.
painter->setPen(QPen(Qt::lightGray, 0.0));
painter->drawLine(0, realTop, 0, realBottom);
painter->drawLine(realLeft, 0, realRight, 0);
}
}
My Line class is as follows:
#include "line.h"
Line::Line(int i, QPointF p1, QPointF p2)
{
// assigns id
id = i;
// set values of start point and end point of line
startP = p1;
endP = p2;
}
int Line::type() const
{
// Enable the use of qgraphicsitem_cast with line item.
return Type;
}
QRectF Line::boundingRect() const
{
qreal extra = 1.0;
// bounding rectangle for line
return QRectF(line().p1(), QSizeF(line().p2().x() - line().p1().x(),
line().p2().y() - line().p1().y()))
.normalized()
.adjusted(-extra, -extra, extra, extra);
}
void Line::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
// draws/paints the path of line
QPen paintpen;
painter->setRenderHint(QPainter::Antialiasing);
paintpen.setWidth(1);
if (isSelected())
{
// sets brush for end points
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::red);
painter->setPen(paintpen);
painter->drawEllipse(startP, 2, 2);
painter->drawEllipse(endP, 2, 2);
// sets pen for line path
paintpen.setStyle(Qt::DashLine);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawLine(startP, endP);
}
else
{
painter->setBrush(Qt::SolidPattern);
paintpen.setColor(Qt::black);
painter->setPen(paintpen);
painter->drawEllipse(startP, 2, 2);
painter->drawEllipse(endP, 2, 2);
painter->drawLine(startP, endP);
}
}
snap.cpp
#include "snap.h"
#include <QApplication>
Snap::Snap(const QRect& rect, QGraphicsItem* parent,
QGraphicsScene* scene):
QGraphicsRectItem(QRectF())
{
setFlags(QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemIsMovable |
QGraphicsItem::ItemSendsGeometryChanges);
}
void Snap::mousePressEvent(QGraphicsSceneMouseEvent *event){
offset = pos() - computeTopLeftGridPoint(pos());
QGraphicsRectItem::mousePressEvent(event);
}
QVariant Snap::itemChange(GraphicsItemChange change,
const QVariant &value)
{
if (change == ItemPositionChange && scene()) {
QPointF newPos = value.toPointF();
if(QApplication::mouseButtons() == Qt::LeftButton &&
qobject_cast<CadGraphicsScene*> (scene())){
QPointF closestPoint = computeTopLeftGridPoint(newPos);
return closestPoint+=offset;
}
else
return newPos;
}
else
return QGraphicsItem::itemChange(change, value);
}
QPointF Snap::computeTopLeftGridPoint(const QPointF& pointP){
CadGraphicsScene* customScene = qobject_cast<CadGraphicsScene*> (scene());
int gridSize = customScene->getGridSize();
qreal xV = floor(pointP.x()/gridSize)*gridSize;
qreal yV = floor(pointP.y()/gridSize)*gridSize;
return QPointF(xV, yV);
}
Can anyone please help me to solve this?
In the MouseMoveEvent of the Item you want to snap:
Check if the distance between the Item and the Next GridLine is below a certain distance (the distance from which the item would snap).
Then change the Position such that your Item moves to the next GridLine.
In Pseudo:
if( distance(Item.Position, Grid.NextLine) < SnapDistance)
line.Position = Grid.MextLine.Position
You have to seperate X and Y though to make this work properly.

Draw a scale ruler in QGraphicsScene?

I am building a map widget (something like the google map) using Qt, basically I used a QGraphicsScene to display the map tile.
Now I want to add a scale ruler to the widget just like the one in google map.
Any suggestions about how could I realize this?
Take a look at this example:
Structure your code base as following:
Write a class inheriting descendants class of QAbstractScrollArea (As example QGraphicsView, QMdiArea, QPlainTextEdit, QScrollArea, QTextEdit, QColumnView, QHeaderView, QListView, QTableView, QTreeView etc.)
In the constructor of your class call setViewportMargins and set the margins of left/top/right/bottom areas length.
Create a QGridLayout and adds your custom Ruler/Scale in the layout.
Set this layout calling setLayout
Example:
setViewportMargins(RULER_BREADTH,RULER_BREADTH,0,0);
QGridLayout* gridLayout = new QGridLayout();
gridLayout->setSpacing(0);
gridLayout->setMargin(0);
mHorzRuler = new QDRuler(QDRuler::Horizontal);
mVertRuler = new QDRuler(QDRuler::Vertical);
QWidget* fake = new QWidget();
fake->setBackgroundRole(QPalette::Window);
fake->setFixedSize(RULER_BREADTH,RULER_BREADTH);
gridLayout->addWidget(fake,0,0);
gridLayout->addWidget(mHorzRuler,0,1);
gridLayout->addWidget(mVertRuler,1,0);
gridLayout->addWidget(this->viewport(),1,1);
this->setLayout(gridLayout);
QDRuler: The ruler class
#define RULER_BREADTH 20
class QDRuler : public QWidget
{
Q_OBJECT
Q_ENUMS(RulerType)
Q_PROPERTY(qreal origin READ origin WRITE setOrigin)
Q_PROPERTY(qreal rulerUnit READ rulerUnit WRITE setRulerUnit)
Q_PROPERTY(qreal rulerZoom READ rulerZoom WRITE setRulerZoom)
public:
enum RulerType { Horizontal, Vertical };
QDRuler(QDRuler::RulerType rulerType, QWidget* parent)
: QWidget(parent), mRulerType(rulerType), mOrigin(0.), mRulerUnit(1.),
mRulerZoom(1.), mMouseTracking(false), mDrawText(false)
{
setMouseTracking(true);
QFont txtFont("Goudy Old Style", 5,20);
txtFont.setStyleHint(QFont::TypeWriter,QFont::PreferOutline);
setFont(txtFont);
}
QSize minimumSizeHint() const
{
return QSize(RULER_BREADTH,RULER_BREADTH);
}
QDRuler::RulerType rulerType() const
{
return mRulerType;
}
qreal origin() const
{
return mOrigin;
}
qreal rulerUnit() const
{
return mRulerUnit;
}
qreal rulerZoom() const
{
return mRulerZoom;
}
public slots:
void setOrigin(const qreal origin)
{
if (mOrigin != origin)
{
mOrigin = origin;
update();
}
}
void setRulerUnit(const qreal rulerUnit)
{
if (mRulerUnit != rulerUnit)
{
mRulerUnit = rulerUnit;
update();
}
}
void setRulerZoom(const qreal rulerZoom)
{
if (mRulerZoom != rulerZoom)
{
mRulerZoom = rulerZoom;
update();
}
}
void setCursorPos(const QPoint cursorPos)
{
mCursorPos = this->mapFromGlobal(cursorPos);
mCursorPos += QPoint(RULER_BREADTH,RULER_BREADTH);
update();
}
void setMouseTrack(const bool track)
{
if (mMouseTracking != track)
{
mMouseTracking = track;
update();
}
}
protected:
void mouseMoveEvent(QMouseEvent* event)
{
mCursorPos = event->pos();
update();
QWidget::mouseMoveEvent(event);
}
void paintEvent(QPaintEvent* event)
{
QPainter painter(this);
painter.setRenderHints(QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing);
QPen pen(Qt::black,0); // zero width pen is cosmetic pen
//pen.setCosmetic(true);
painter.setPen(pen);
// We want to work with floating point, so we are considering
// the rect as QRectF
QRectF rulerRect = this->rect();
// at first fill the rect
//painter.fillRect(rulerRect,QColor(220,200,180));
painter.fillRect(rulerRect,QColor(236,233,216));
// drawing a scale of 25
drawAScaleMeter(&painter,rulerRect,25,(Horizontal == mRulerType ? rulerRect.height()
: rulerRect.width())/2);
// drawing a scale of 50
drawAScaleMeter(&painter,rulerRect,50,(Horizontal == mRulerType ? rulerRect.height()
: rulerRect.width())/4);
// drawing a scale of 100
mDrawText = true;
drawAScaleMeter(&painter,rulerRect,100,0);
mDrawText = false;
// drawing the current mouse position indicator
painter.setOpacity(0.4);
drawMousePosTick(&painter);
painter.setOpacity(1.0);
// drawing no man's land between the ruler & view
QPointF starPt = Horizontal == mRulerType ? rulerRect.bottomLeft()
: rulerRect.topRight();
QPointF endPt = Horizontal == mRulerType ? rulerRect.bottomRight()
: rulerRect.bottomRight();
painter.setPen(QPen(Qt::black,2));
painter.drawLine(starPt,endPt);
}
private:
void drawAScaleMeter(QPainter* painter, QRectF rulerRect, qreal scaleMeter, qreal startPositoin)
{
// Flagging whether we are horizontal or vertical only to reduce
// to cheching many times
bool isHorzRuler = Horizontal == mRulerType;
scaleMeter = scaleMeter * mRulerUnit * mRulerZoom;
// Ruler rectangle starting mark
qreal rulerStartMark = isHorzRuler ? rulerRect.left() : rulerRect.top();
// Ruler rectangle ending mark
qreal rulerEndMark = isHorzRuler ? rulerRect.right() : rulerRect.bottom();
// Condition A # If origin point is between the start & end mard,
//we have to draw both from origin to left mark & origin to right mark.
// Condition B # If origin point is left of the start mark, we have to draw
// from origin to end mark.
// Condition C # If origin point is right of the end mark, we have to draw
// from origin to start mark.
if (mOrigin >= rulerStartMark && mOrigin <= rulerEndMark)
{
drawFromOriginTo(painter, rulerRect, mOrigin, rulerEndMark, 0, scaleMeter, startPositoin);
drawFromOriginTo(painter, rulerRect, mOrigin, rulerStartMark, 0, -scaleMeter, startPositoin);
}
else if (mOrigin < rulerStartMark)
{
int tickNo = int((rulerStartMark - mOrigin) / scaleMeter);
drawFromOriginTo(painter, rulerRect, mOrigin + scaleMeter * tickNo,
rulerEndMark, tickNo, scaleMeter, startPositoin);
}
else if (mOrigin > rulerEndMark)
{
int tickNo = int((mOrigin - rulerEndMark) / scaleMeter);
drawFromOriginTo(painter, rulerRect, mOrigin - scaleMeter * tickNo,
rulerStartMark, tickNo, -scaleMeter, startPositoin);
}
}
void drawFromOriginTo(QPainter* painter, QRectF rulerRect, qreal startMark, qreal endMark, int startTickNo, qreal step, qreal startPosition)
{
bool isHorzRuler = Horizontal == mRulerType;
int iterate = 0;
for (qreal current = startMark;
(step < 0 ? current >= endMark : current <= endMark); current += step)
{
qreal x1 = isHorzRuler ? current : rulerRect.left() + startPosition;
qreal y1 = isHorzRuler ? rulerRect.top() + startPosition : current;
qreal x2 = isHorzRuler ? current : rulerRect.right();
qreal y2 = isHorzRuler ? rulerRect.bottom() : current;
painter->drawLine(QLineF(x1,y1,x2,y2));
if (mDrawText)
{
QPainterPath txtPath;
txtPath.addText(x1 + 1,y1 + (isHorzRuler ? 7 : -2),this->font(),QString::number(qAbs(int(step) * startTickNo++)));
painter->drawPath(txtPath);
iterate++;
}
}
}
void drawMousePosTick(QPainter* painter)
{
if (mMouseTracking)
{
QPoint starPt = mCursorPos;
QPoint endPt;
if (Horizontal == mRulerType)
{
starPt.setY(this->rect().top());
endPt.setX(starPt.x());
endPt.setY(this->rect().bottom());
}
else
{
starPt.setX(this->rect().left());
endPt.setX(this->rect().right());
endPt.setY(starPt.y());
}
painter->drawLine(starPt,endPt);
}
}
private:
RulerType mRulerType;
qreal mOrigin;
qreal mRulerUnit;
qreal mRulerZoom;
QPoint mCursorPos;
bool mMouseTracking;
bool mDrawText;
};