Draw a scale ruler in QGraphicsScene? - c++

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;
};

Related

QGraphicsItemGroup and childs resize

I made an algorithm for resizing a picture(inherited from QGraphicsItem) using vector mathematics (I added points to the corners and using the mouse, the picture is resized while maintaining the aspect ratio QGraphicsItem resize with mouse position and keeping aspect ratio , gif: https://gph.is/g/Zr0WdxJ).
Next, I created a group(inherited from QGraphicsItemGroup) add border dots(inherited from QGraphicsRectItem) and added pictures to the group (via addToGroup).
Is it possible to generalize this algorithm to a group? So that all pictures in the group are resized with border dot position.
this what I want: https://gph.is/g/EJxpeVQ (PureRef app)
and this is what I got: https://gph.is/g/aQnpq5x
here the project if anybody wants to run application: https://github.com/try-hard-factory/familiar/tree/feature/itemgroup-resize
can't resize childs... here the code of some classes:
borderdot.h :
#ifndef BORDERDOT_H
#define BORDERDOT_H
#include <QObject>
#include <QGraphicsRectItem>
class QGraphicsSceneHoverEventPrivate;
class QGraphicsSceneMouseEvent;
class DotSignal : public QObject, public QGraphicsRectItem
{
Q_OBJECT
Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged)
public:
explicit DotSignal(QGraphicsItem *parentItem = 0, QObject *parent = 0);
explicit DotSignal(QPointF pos, QGraphicsItem *parentItem = 0, QObject *parent = 0);
~DotSignal();
enum Flags {
Movable = 0x01
};
enum { Type = UserType + 1 };
int type() const override
{
return Type;
}
QPointF previousPosition() const;
void setPreviousPosition(const QPointF previousPosition);
void setDotFlags(unsigned int flags);
signals:
void previousPositionChanged();
void signalMouseRelease();
void signalMove(QGraphicsItem *signalOwner, qreal dx, qreal dy);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
public slots:
private:
unsigned int m_flags;
QPointF m_previousPosition;
};
borderdot.cpp:
#include "borderdot.h"
#include <QBrush>
#include <QColor>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneMouseEvent>
DotSignal::DotSignal(QGraphicsItem *parentItem, QObject *parent) :
QObject(parent)
{
setZValue(999999999);
// setFlags(ItemIsMovable);
setParentItem(parentItem);
setAcceptHoverEvents(true);
setBrush(QBrush(Qt::black));
setRect(-4,-4,8,8);
setDotFlags(0);
}
DotSignal::DotSignal(QPointF pos, QGraphicsItem *parentItem, QObject *parent) :
QObject(parent)
{
setZValue(999999999);
// setFlags(ItemIsMovable);
setParentItem(parentItem);
setAcceptHoverEvents(true);
setBrush(QBrush(Qt::black));
setRect(-4,-4,8,8);
setPos(pos);
setPreviousPosition(pos);
setDotFlags(0);
}
DotSignal::~DotSignal()
{
}
QPointF DotSignal::previousPosition() const
{
return m_previousPosition;
}
void DotSignal::setPreviousPosition(const QPointF previousPosition)
{
if (m_previousPosition == previousPosition)
return;
m_previousPosition = previousPosition;
emit previousPositionChanged();
}
void DotSignal::setDotFlags(unsigned int flags)
{
m_flags = flags;
}
void DotSignal::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(m_flags & Movable) {
qDebug()<<"DotSignal::mouseMoveEvent";
auto dx = event->scenePos().x() - m_previousPosition.x();
auto dy = event->scenePos().y() - m_previousPosition.y();
moveBy(dx,dy);
setPreviousPosition(event->scenePos());
emit signalMove(this, dx, dy);
} else {
qDebug()<<"else DotSignal::mouseMoveEvent";
QGraphicsItem::mouseMoveEvent(event);
}
}
void DotSignal::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(m_flags & Movable){
setPreviousPosition(event->scenePos());
} else {
QGraphicsItem::mousePressEvent(event);
}
}
void DotSignal::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
emit signalMouseRelease();
QGraphicsItem::mouseReleaseEvent(event);
}
void DotSignal::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
qDebug()<<"DotSignal::hoverEnterEvent";
Q_UNUSED(event)
setBrush(QBrush(Qt::red));
}
void DotSignal::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
qDebug()<<"DotSignal::hoverLeaveEvent";
Q_UNUSED(event)
setBrush(QBrush(Qt::black));
}
itemgroup.h:
#ifndef ITEMGROUP_H
#define ITEMGROUP_H
#include <QObject>
#include <QGraphicsItemGroup>
class DotSignal;
class QGraphicsSceneMouseEvent;
class ItemGroup : public QObject, public QGraphicsItemGroup
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged)
public:
enum EItemsType {
eBorderDot = QGraphicsItem::UserType + 1,
};
ItemGroup(uint64_t& zc, QGraphicsItemGroup *parent = nullptr);
~ItemGroup();
enum ActionStates {
ResizeState = 0x01,
RotationState = 0x02
};
enum CornerFlags {
Top = 0x01,
Bottom = 0x02,
Left = 0x04,
Right = 0x08,
TopLeft = Top|Left,
TopRight = Top|Right,
BottomLeft = Bottom|Left,
BottomRight = Bottom|Right
};
enum CornerGrabbers {
GrabberTop = 0,
GrabberBottom,
GrabberLeft,
GrabberRight,
GrabberTopLeft,
GrabberTopRight,
GrabberBottomLeft,
GrabberBottomRight
};
public:
void addItem(QGraphicsItem* item);
void printChilds();
QPointF previousPosition() const;
void setPreviousPosition(const QPointF previousPosition);
signals:
void rectChanged(ItemGroup *rect);
void previousPositionChanged();
void clicked(ItemGroup *rect);
void signalMove(QGraphicsItemGroup *item, qreal dx, qreal dy);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
public:
void clearItemGroup();
bool isContain(const QGraphicsItem* item) const;
bool isEmpty() const;
void incZ();
protected:
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
private:
QPointF shiftMouseCoords_;
uint64_t& zCounter_;
QRectF m_tmpRect;
private:
unsigned int m_cornerFlags;
unsigned int m_actionFlags;
QPointF m_previousPosition;
bool m_leftMouseButtonPressed;
DotSignal *cornerGrabber[8];
void resizeLeft( const QPointF &pt);
void resizeRight( const QPointF &pt);
void resizeBottom(const QPointF &pt);
void resizeTop(const QPointF &pt);
void rotateItem(const QPointF &pt);
void setPositionGrabbers();
void setVisibilityGrabbers();
void hideGrabbers();
};
#endif // ITEMGROUP_H
itemgroup.cpp:
#include <QPainter>
#include <QDebug>
#include <QCursor>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsRectItem>
#include <math.h>
#include "borderdot.h"
static const double Pi = 3.14159265358979323846264338327950288419717;
static double TwoPi = 2.0 * Pi;
ItemGroup::~ItemGroup()
{
for(int i = 0; i < 8; i++){
delete cornerGrabber[i];
}
}
QPointF ItemGroup::previousPosition() const
{
return m_previousPosition;
}
void ItemGroup::setPreviousPosition(const QPointF previousPosition)
{
if (m_previousPosition == previousPosition)
return;
m_previousPosition = previousPosition;
emit previousPositionChanged();
}
ItemGroup::ItemGroup(uint64_t& zc, QGraphicsItemGroup *parent) :
QGraphicsItemGroup(parent),
zCounter_(zc),
m_cornerFlags(0),
m_actionFlags(ResizeState)
{
setAcceptHoverEvents(true);
setFlags(ItemIsSelectable|ItemSendsGeometryChanges);
for(int i = 0; i < 8; i++){
cornerGrabber[i] = new DotSignal(this);
}
setPositionGrabbers();
}
void ItemGroup::addItem(QGraphicsItem* item)
{
addToGroup(item);
auto childs = childItems();
auto tmp = childs.first()->sceneBoundingRect();
for (auto& it : childs) {
if (it->type() == eBorderDot) continue;
tmp = tmp.united(it->sceneBoundingRect());
}
m_tmpRect = tmp;
}
void ItemGroup::printChilds()
{
auto childs = childItems();
for (auto& it : childs) {
LOG_DEBUG(logger, "CHILDREN: ", it);
}
}
QRectF ItemGroup::boundingRect() const
{
return m_tmpRect;
}
void ItemGroup::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
QPointF pt = event->pos();
if(m_actionFlags == ResizeState){
switch (m_cornerFlags) {
case Top:
resizeTop(pt);
break;
case Bottom:
resizeBottom(pt);
break;
case Left:
resizeLeft(pt);
break;
case Right:
resizeRight(pt);
break;
case TopLeft:
resizeTop(pt);
resizeLeft(pt);
break;
case TopRight:
resizeTop(pt);
resizeRight(pt);
break;
case BottomLeft:
resizeBottom(pt);
resizeLeft(pt);
break;
case BottomRight:
resizeBottom(pt);
resizeRight(pt);
break;
default:
if (m_leftMouseButtonPressed) {
setCursor(Qt::ClosedHandCursor);
auto dx = event->scenePos().x() - m_previousPosition.x();
auto dy = event->scenePos().y() - m_previousPosition.y();
moveBy(dx,dy);
setPreviousPosition(event->scenePos());
emit signalMove(this, dx, dy);
}
break;
}
} else {
if (m_leftMouseButtonPressed) {
setCursor(Qt::ClosedHandCursor);
auto dx = event->scenePos().x() - m_previousPosition.x();
auto dy = event->scenePos().y() - m_previousPosition.y();
moveBy(dx,dy);
setPreviousPosition(event->scenePos());
emit signalMove(this, dx, dy);
}
}
QGraphicsItemGroup::mouseMoveEvent(event);
}
void ItemGroup::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
setZValue(++zCounter_);
shiftMouseCoords_ = (this->pos() - mapToScene(event->pos()))/scale();
if (event->button() & Qt::LeftButton) {
m_leftMouseButtonPressed = true;
setPreviousPosition(event->scenePos());
emit clicked(this);
}
QGraphicsItemGroup::mousePressEvent(event);
LOG_DEBUG(logger, "EventPos: (", event->pos().x(),";",event->pos().y(), "), Pos: (", pos().x(),";",pos().y(),")");
}
void ItemGroup::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() & Qt::LeftButton) {
m_leftMouseButtonPressed = false;
}
QGraphicsItemGroup::mouseReleaseEvent(event);
}
void ItemGroup::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
qDebug()<<"ItemGroup::hoverEnterEvent";
setPositionGrabbers();
setVisibilityGrabbers();
QGraphicsItem::hoverEnterEvent(event);
}
void ItemGroup::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
qDebug()<<"ItemGroup::hoverLeaveEvent";
m_cornerFlags = 0;
hideGrabbers();
setCursor(Qt::CrossCursor);
QGraphicsItem::hoverLeaveEvent( event );
}
void ItemGroup::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
QPointF pt = event->pos(); // The current position of the mouse
qreal drx = pt.x() - boundingRect().right(); // Distance between the mouse and the right
qreal dlx = pt.x() - boundingRect().left(); // Distance between the mouse and the left
qreal dby = pt.y() - boundingRect().top(); // Distance between the mouse and the top
qreal dty = pt.y() - boundingRect().bottom(); // Distance between the mouse and the bottom
m_cornerFlags = 0;
if( dby < 10 && dby > -10 ) m_cornerFlags |= Top; // Top side
if( dty < 10 && dty > -10 ) m_cornerFlags |= Bottom; // Bottom side
if( drx < 10 && drx > -10 ) m_cornerFlags |= Right; // Right side
if( dlx < 10 && dlx > -10 ) m_cornerFlags |= Left; // Left side
switch (m_cornerFlags) {
case TopLeft:
case TopRight:
case BottomLeft:
case BottomRight: {
setCursor(Qt::BusyCursor);
break;
}
default:
setCursor(Qt::CrossCursor);
break;
}
}
QVariant ItemGroup::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
switch (change) {
case QGraphicsItemGroup::ItemSelectedChange:
m_actionFlags = ResizeState;
break;
default:
break;
}
return QGraphicsItemGroup::itemChange(change, value);
}
void ItemGroup::resizeRight(const QPointF &pt)
{
QRectF tmpRect = boundingRect();
if( pt.x() < tmpRect.left() )
return;
qreal widthOffset = ( pt.x() - tmpRect.left() );
if( widthOffset < 10 ) /// limit
return;
if( widthOffset < 10)
tmpRect.setWidth( -widthOffset );
else
tmpRect.setWidth( widthOffset );
prepareGeometryChange();
m_tmpRect = tmpRect;
update();
setPositionGrabbers();
}
void ItemGroup::resizeTop(const QPointF &pt)
{
QRectF tmpRect = boundingRect();
if( pt.y() > tmpRect.bottom() )
return;
qreal heightOffset = ( pt.y() - tmpRect.bottom() );
if( heightOffset > -11 ) /// limit
return;
if( heightOffset < 0)
tmpRect.setHeight( -heightOffset );
else
tmpRect.setHeight( heightOffset );
tmpRect.translate( 0 , boundingRect().height() - tmpRect.height() );
prepareGeometryChange();
m_tmpRect = tmpRect;
update();
setPositionGrabbers();
}
void ItemGroup::setPositionGrabbers()
{
QRectF tmpRect = boundingRect();
cornerGrabber[GrabberTop]->setPos(tmpRect.left() + tmpRect.width()/2, tmpRect.top());
cornerGrabber[GrabberBottom]->setPos(tmpRect.left() + tmpRect.width()/2, tmpRect.bottom());
cornerGrabber[GrabberLeft]->setPos(tmpRect.left(), tmpRect.top() + tmpRect.height()/2);
cornerGrabber[GrabberRight]->setPos(tmpRect.right(), tmpRect.top() + tmpRect.height()/2);
cornerGrabber[GrabberTopLeft]->setPos(tmpRect.topLeft().x(), tmpRect.topLeft().y());
cornerGrabber[GrabberTopRight]->setPos(tmpRect.topRight().x(), tmpRect.topRight().y());
cornerGrabber[GrabberBottomLeft]->setPos(tmpRect.bottomLeft().x(), tmpRect.bottomLeft().y());
cornerGrabber[GrabberBottomRight]->setPos(tmpRect.bottomRight().x(), tmpRect.bottomRight().y());
}
void ItemGroup::setVisibilityGrabbers()
{
cornerGrabber[GrabberTopLeft]->setVisible(true);
cornerGrabber[GrabberTopRight]->setVisible(true);
cornerGrabber[GrabberBottomLeft]->setVisible(true);
cornerGrabber[GrabberBottomRight]->setVisible(true);
cornerGrabber[GrabberTop]->setVisible(true);
cornerGrabber[GrabberBottom]->setVisible(true);
cornerGrabber[GrabberLeft]->setVisible(true);
cornerGrabber[GrabberRight]->setVisible(true);
}
void ItemGroup::hideGrabbers()
{
for(int i = 0; i < 8; i++){
cornerGrabber[i]->setVisible(false);
}
}
I didn't dig through your code, but yes, it can absolutely be done. The product I work in has a "group" concept, and I had to support this exact thing. I can't spell out code for you, but here's the general concept. The same concept works for resizing a related set of selected items that you want to resize all together.
I created a Resizer class that manages the handles and responds to those movements. The Resizer is instantiated when one or more objects is selected. All of the graphics item classes are my own, derived from QGraphicsItem; if you're using the built-in classes, you may need to subclass them and add your own functions.
Whenever the user moves a handle, the Resizer determines the new geometry of the selected items and updates their sizes. For rectangles and ellipses, this involves calls to setRect; for paths, you'll have to write a scaling algorithm that moves the points in the path to their new locations.
For a grouped object, the concept is identical. You look at the proportions of the member objects' areas to the group's encompassing rectangle, recalculate them, and then update them.
What I found to be the key is to save the starting encompassing rectangle, and then create a resize method that accepts the original encompassing rectangle and the new encompassing rectangle (defined by the handle movement), and then scale from the original to the new rectangle. You'll also need the original rectangle of the individual objects.
Off the top of my head, here's the general idea, and this is assuming you want the items to redraw themselves as you move the handles:
When user initiates first handle movement, grab the current geometry of all of the objects being resized, and the encompassing geometry for the overall group.
As the handle moves, call a new "resize" method with the original encompassing rectangle, and the new one defined by the handle positions, and then have each object resize itself by mapping its original area onto the new area, using the group's before/after encompassing rectangles to define the scaling.
When the movement stops, notify each object being resized that the operation is finished, and this is their new size. (This may not be necessary for you, but it was for me because the final object attributes have to be reflected back to a database.)
This takes some effort, but it's absolutely doable. The main things to think about is to abstract the operations and create your own QGraphicsitem-derived classes with methods to support it. If you're using the built-in classes, you can extend them with an interface class that ensures they all have the required methods your resizing code needs.

Mouse resizable, draggable widgets

I am trying to allow users to add new "widgets" (images, text, perhaps other custom data too. Image is good enough for now) to a kind of design area. And then I would like them to be able to resize/move those conveniently. The best way for the moving part seems to be to use QGraphicsView. Can be done nicely with 4 lines of code:
auto const scene = new QGraphicsScene{this};
auto const item = scene->addPixmap(QPixmap{":/img/example.png"});
item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
ui->graphicsView->setScene(scene);
This results in something like this:
It's nice, but cannot be resized with the mouse. I've seen (on this site) multiple ways to make this, sort of, resizable with a mouse, but they are all kind of hacky.
I've seen this example on Qt website which takes a different approach of creating a custom container for a moveable-and-resizeable container. It seems like it can be made work with a bit more tweaking, but it's again, not a nice solution. The widget doesn't really look like it's resizable. When selected, the borders don't have the nice the clue that it's a dynamically placed/sized thing.
Having ms-paint like moveable widgets must be a common use case, so I reckon there has got to be a nice way to get this happen. Is this the case? QGraphicsScene seems like a good candidate honestly. Perhaps I am missing something?
Ok, so I had this problem and my solution was to link the creation of the handlers with the selection of the item:
mainwindow.h
#pragma once
#include <QMainWindow>
#include <QGraphicsItem>
#include <QPainter>
class Handler: public QGraphicsItem
{
public:
enum Mode
{
Top = 0x1,
Bottom = 0x2,
Left = 0x4,
TopLeft = Top | Left,
BottomLeft = Bottom | Left,
Right = 0x8,
TopRight = Top | Right,
BottomRight = Bottom | Right,
Rotate = 0x10
};
Handler(QGraphicsItem *parent, Mode mode);
~Handler(){}
void updatePosition();
QRectF boundingRect() const override;
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
QPointF iniPos;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
private:
Mode mode;
bool isMoving = false;
};
class ObjectResizerGrip: public QGraphicsItem
{
public:
ObjectResizerGrip(QGraphicsItem *parent): QGraphicsItem(parent)
{
setFlag(QGraphicsItem::ItemHasNoContents, true);
setFlag(QGraphicsItem::ItemIsSelectable, false);
setFlag(QGraphicsItem::ItemIsFocusable, false);
}
void updateHandlerPositions();
virtual QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override{Q_UNUSED(painter) Q_UNUSED(option) Q_UNUSED(widget)}
protected:
QList<Handler*> handlers;
};
class Object4SidesResizerGrip: public ObjectResizerGrip
{
public:
Object4SidesResizerGrip(QGraphicsItem *parent);
};
class Item:public QGraphicsItem
{
public:
Item(QGraphicsItem *parent=nullptr): QGraphicsItem(parent)
{
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
setAcceptHoverEvents(true);
}
QRectF boundingRect() const override
{
return boundingBox;
}
void setWidth(qreal value)
{
auto width = boundingBox.width();
if(width == value) return;
width = qMax(value, 100.0);
setDimensions(width, boundingBox.height());
}
void setHeight(qreal value)
{
auto height = boundingBox.height();
if(height == value) return;
height = qMax(value, 100.0);
setDimensions(boundingBox.width(), height);
}
void setDimensions(qreal w, qreal h)
{
prepareGeometryChange();
boundingBox = QRectF(-w/2.0, -h/2.0, w, h);
if(resizerGrip) resizerGrip->updateHandlerPositions();
update();
}
private:
ObjectResizerGrip* resizerGrip = nullptr;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
{
if(change == ItemSelectedHasChanged && scene())
{
if(value.toBool())
{
if(!resizerGrip)
resizerGrip = newSelectionGrip();
}
else
{
if(resizerGrip)
{
delete resizerGrip;
resizerGrip = nullptr;
}
}
}
return QGraphicsItem::itemChange(change, value);
}
QRectF boundingBox;
virtual ObjectResizerGrip *newSelectionGrip() =0;
};
class CrossItem:public Item
{
public:
CrossItem(QGraphicsItem *parent=nullptr): Item(parent){};
private:
virtual ObjectResizerGrip *newSelectionGrip() override
{
return new Object4SidesResizerGrip(this);
}
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
painter->drawLine(boundingRect().topLeft(), boundingRect().bottomRight());
painter->drawLine(boundingRect().topRight(), boundingRect().bottomLeft());
}
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
};
mainwindow.cpp
#include "mainwindow.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QApplication>
#include <QGraphicsSceneMouseEvent>
#include <QHBoxLayout>
// Return nearest point along the line to a given point
// http://stackoverflow.com/questions/1459368/snap-point-to-a-line
QPointF getClosestPoint(const QPointF &vertexA, const QPointF &vertexB, const QPointF &point, const bool segmentClamp)
{
QPointF AP = point - vertexA;
QPointF AB = vertexB - vertexA;
qreal ab2 = AB.x()*AB.x() + AB.y()*AB.y();
if(ab2 == 0) // Line lenth == 0
return vertexA;
qreal ap_ab = AP.x()*AB.x() + AP.y()*AB.y();
qreal t = ap_ab / ab2;
if (segmentClamp)
{
if (t < 0.0f) t = 0.0f;
else if (t > 1.0f) t = 1.0f;
}
return vertexA + AB * t;
}
Object4SidesResizerGrip::Object4SidesResizerGrip(QGraphicsItem* parent) : ObjectResizerGrip(parent)
{
handlers.append(new Handler(this, Handler::Left));
handlers.append(new Handler(this, Handler::BottomLeft));
handlers.append(new Handler(this, Handler::Bottom));
handlers.append(new Handler(this, Handler::BottomRight));
handlers.append(new Handler(this, Handler::Right));
handlers.append(new Handler(this, Handler::TopRight));
handlers.append(new Handler(this, Handler::Top));
handlers.append(new Handler(this, Handler::TopLeft));
handlers.append(new Handler(this, Handler::Rotate));
updateHandlerPositions();
}
QRectF ObjectResizerGrip::boundingRect() const
{
return QRectF();
}
void ObjectResizerGrip::updateHandlerPositions()
{
foreach (Handler* item, handlers)
item->updatePosition();
}
Handler::Handler(QGraphicsItem *parent, Mode mode): QGraphicsItem(parent), mode(mode)
{
QPen pen(Qt::white);
pen.setWidth(0);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, false);
setAcceptHoverEvents(true);
setZValue(100);
setCursor(Qt::UpArrowCursor);
updatePosition();
}
void Handler::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen pen(isMoving ? QColor(250,214,36) : QColor(100,100,100));
pen.setWidth(0);
pen.setBrush(pen.color());
painter->setPen(pen);
painter->setBrush(QColor(100,100,100,150));
if(mode & Rotate)
{
auto rect_ = ((Item*) parentItem()->parentItem())->boundingRect();
auto topPos = QPointF(rect_.left() + rect_.width() / 2 - 1, rect_.top());
painter->drawLine(mapFromParent(topPos), mapFromParent(topPos - QPointF(0, 175)));
painter->drawEllipse(boundingRect());
}
else
painter->drawRect(boundingRect());
}
QRectF Handler::boundingRect() const
{
return QRectF(-25, -25, 50, 50);
}
void Handler::updatePosition()
{
auto rect_ = ((Item*) parentItem()->parentItem())->boundingRect();
switch (mode)
{
case TopLeft:
setPos(rect_.topLeft());
break;
case Top:
setPos(rect_.left() + rect_.width() / 2 - 1,rect_.top());
break;
case TopRight:
setPos(rect_.topRight());
break;
case Right:
setPos(rect_.right(),rect_.top() + rect_.height() / 2 - 1);
break;
case BottomRight:
setPos(rect_.bottomRight());
break;
case Bottom:
setPos(rect_.left() + rect_.width() / 2 - 1,rect_.bottom());
break;
case BottomLeft:
setPos(rect_.bottomLeft());
break;
case Left:
setPos(rect_.left(), rect_.top() + rect_.height() / 2 - 1);
break;
case Rotate:
setPos(0, rect_.top() - 200);
break;
}
}
void Handler::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(mode & Rotate)
{
Item* item = (Item*) parentItem()->parentItem();
auto angle = QLineF(item->mapToScene(QPoint()), event->scenePos()).angle();
if(!(QApplication::keyboardModifiers() & Qt::AltModifier)) // snap to 45deg
{
auto modAngle = fmod(angle+180, 45);
if(modAngle < 10 || modAngle > 35)
angle = round(angle/45)*45;
}
item->setRotation(0);
angle = QLineF(item->mapFromScene(QPoint()), item->mapFromScene(QLineF::fromPolar(10, angle).p2())).angle();
item->setRotation(90 - angle);
item->update();
}
else
{
Item* item = (Item*) parentItem()->parentItem();
auto diff = mapToItem(item, event->pos()) - mapToItem(item, event->lastPos());
auto bRect = item->boundingRect();
if(mode == TopLeft || mode == BottomRight)
diff = getClosestPoint(bRect.topLeft(), QPoint(0,0), diff, false);
else if(mode == TopRight || mode == BottomLeft)
diff = getClosestPoint(bRect.bottomLeft(), QPoint(0,0), diff, false);
if(mode & Left || mode & Right)
{
item->setPos(item->mapToScene(QPointF(diff.x()/2.0, 0)));
if(mode & Left)
item->setWidth(item->boundingRect().width() - diff.x());
else
item->setWidth(item->boundingRect().width() + diff.x());
}
if(mode & Top || mode & Bottom)
{
item->setPos(item->mapToScene(QPointF(0, diff.y()/2.0)));
if(mode & Top)
item->setHeight(item->boundingRect().height() - diff.y());
else
item->setHeight(item->boundingRect().height() + diff.y());
}
item->update();
}
((ObjectResizerGrip*) parentItem())->updateHandlerPositions();
}
void Handler::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
isMoving = true;
}
void Handler::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
isMoving = false;
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
auto const graphicsView = new QGraphicsView(this);
graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
auto const scene = new QGraphicsScene(this);
auto const item = new CrossItem();
item->setWidth(100);
item->setHeight(100);
scene->addItem(item);
item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
graphicsView->setScene(scene);
graphicsView-> fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
setCentralWidget(graphicsView);
}
MainWindow::~MainWindow()
{
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
This solution is far from perfect but it works and can be a good start for improvements.
Known issues:
rotation grip requires FullViewportUpdate because I was too lazy to implement it in a separate child item and it is drawing outside the bounding box.
there are probably better architectures like using proxies or signals/event.
When it comes to using mouse, keyboard and in general capturing operating system events, you have to rely on the event system. The base class is QEvent, which in your specific case allows you to "QResizeEvent, QMouseEvent, QScrollEvent, ..." and many more fun things.

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 create qt label hover effect?

I must use hover event on Qt label, but I can't found no information about that.
I try use something like ui->label->setText("<a>ads</a>") and onLinkHovered but it's not work correct.
I must change text on hover.
The most flexible solution would be to create your own widget which inherits from QLabel. This way, you could override the enterEvent and leaveEvent #Jeremy and #Moe are writing about which are protected. As a part of these methods implementation you could change the text or decoration accordingly. For example:
class CustomLabel : public QLabel
{
Q_OBJECT
public:
CustomLabel(QWidget* parent = nullptr) : QLabel(parent){ }
protected:
void enterEvent(QEvent *ev) override
{
setStyleSheet("QLabel { background-color : blue; }");
}
void leaveEvent(QEvent *ev) override
{
setStyleSheet("QLabel { background-color : green; }");
}
};
Another approach, but a lot less flexible would be to set the href attribute for the link tag you have specified in label text. This way text would be treated as actual link and you could use the linkHovered signal to connect to. For example:
ui->label->setText("<a href='www.google.com'>link</a>");
connect(ui->label, &QLabel::linkHovered, this, [this](const QString&)
{
// do smth with the widget/text
});
However, please denote that this way you could only make a modification on the hover event.
So if you need to bring the label back to its original state, the first option is the way to go.
Make use of enterEvent and leaveEvent of QLabel.
Create a subclass of QLabel like this for example:
class MyLabel : public QLabel
{
public:
MyLabel();
MyLabel(char* text, MainWindow* w) : QLabel(text, w) { }
void enterEvent(QEvent *event);
void leaveEvent(QEvent *event);
};
and override enterEvent and leaveEvent like this:
void MyLabel::enterEvent(QEvent *event) {
qDebug() << "Entered!";
// Change your text here
}
void MyLabel::leaveEvent(QEvent *event) {
qDebug() << "Left!";
}
You can create instances of this class now like this:
MainWindow w;
MyLabel myLabel("A Test Label", &w);
If you want to determine whether mouse hovers over text inside the QLabel you can use
void mouseMoveEvent(QMouseEvent* event);
and check inside it whether mouse cursor is in bounding rectangle of text.
void PressLabel::mouseMoveEvent(QMouseEvent* event) {
QRect bRect = getTextComponentRectangle();
m_mouseCoord = event->pos();
if(bRect.contains(event->pos())) {
// Mouse pointer over text.
} else {
// Mouse pointer outside text.
}
QLabel::mouseMoveEvent(event);
}
Turn on mouse tracking for your widget to make mouseMoveEvent occur also when mouse button is not pressed.
setMouseTracking(true);
And finally the function that does the calculation of text bounding rectangle. Code below take into account some unexpected cases, too. I tried it with Qt 5.9.1.
For calculation of effective indent in negative case refer to http://doc.qt.io/archives/qt-4.8/qlabel.html#indent-prop .
QRect PressLabel::getTextComponentRectangle() const {
if(frameWidth() < 0) {
throw std::runtime_error("Negative frame width.");
}
int effectiveIndent = indent();
int trueMargin = margin();
if(effectiveIndent < 0) {
if(frameWidth() == 0 || margin() > 0) { // (1)
effectiveIndent = 0;
} else if(frameWidth() > 0) {
QFontMetrics fm(font());
effectiveIndent = fm.width(QChar('x')) / 2;
}
if(frameWidth() > 0 && margin() < 0) { // (2)
trueMargin = 0;
}
}
QFontMetrics fm(font());
QRect bRect = fm.boundingRect(text());
bRect.setWidth(fm.width(text()));
int indentOffset = effectiveIndent + trueMargin + frameWidth();
int offsetX = 0;
int offsetY = 0;
if(alignment() & Qt::AlignHCenter) {
offsetX = rect().width() / 2 - bRect.width() / 2;
} else if(alignment() & Qt::AlignRight) {
offsetX = rect().width() - bRect.width() - indentOffset;
} else if(alignment() & Qt::AlignJustify) {
offsetX = trueMargin + frameWidth();
} else if(alignment() & Qt::AlignLeft) {
offsetX = indentOffset;
}
if(alignment() & Qt::AlignVCenter) {
offsetY = rect().height() / 2 - bRect.height() / 2;
} else if(alignment() & Qt::AlignBottom) {
offsetY = rect().height() - bRect.height() - indentOffset;
} else if(alignment() & Qt::AlignTop) {
offsetY = indentOffset;
}
bRect.moveTopLeft(rect().topLeft());
bRect.setX(bRect.x() + offsetX);
bRect.setWidth(bRect.width() + offsetX);
bRect.setY(bRect.y() + offsetY);
bRect.setHeight(bRect.height() + offsetY);
return bRect;
}
Unexpected cases:
(1) For indent < 0 and margin > 0 effective indent is 0, not width('x')/2 as it is intended to be for negative indent.
(2) For indent < 0 and margin < 0 true margin is 0 and isn't summed to make offset.
I have found that you can also achieve this through the style sheet.
self.label = QtWidgets.QLabel("Toast")
self.label.setStyleSheet("QLabel{color: white;} QLabel:hover {color: blue;}")

How QTableView or QListView is scrolling with hand drag?

in QGraphicview,
if we set it with : ui->graphicsView->setDragMode(QGraphicsView::ScrollHandDrag);
this code make graphicsview can scroll items with mouse pressed and drag.
How can we make QListView or QTableView as the QGraphicsView?
You will need to subclass these widgets and reimplement QWidget::mousePressEvent, QWidget::mousMoveEvent and QWidget::mouseReleaseEvent. However you will have to be careful because you may be interfering with actions that are mapped to these by default implementations (e.g. selecting) so it would need to be tweaked a bit. For example (assumed subclass of QListView):
void MyListView::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::RightButton) //lets map scrolling to right button
m_ScrollStart = event->pos(); //QPoint member, indicates the start of the scroll
else
QListView::mousePressEvent(event);
}
and then
void MyListView::mouseMoveEvent(QMouseEvent *event)
{
if(!m_ScrollStart.isNull()) //if the scroll was started
{
bool direction = (m_ScrollStart.y() < event->pos().y()); //determine direction, true is up (start is below current), false is down (start is above current)
int singleStep = (direction ? 10 : -10); //fill in the desired value
verticalScrollBar()->setValue(verticalScrollBar()->value() + singleStep);
//scroll by the certain amount in determined direction,
//you decide how much will be a single step... test and see what you like
}
QListView::mouseMoveEvent(event);
}
and finally
void MyListView::mouseReleaseEvent(QMouseEvent *event)
{
m_ScrollStart = QPoint(); //resets the scroll drag
QListView::mouseReleaseEvent(event);
}
like Resurrection did mention
You will need to subclass these widgets and reimplement QWidget::mousePressEvent, QWidget::mousMoveEvent and QWidget::mouseReleaseEvent
but below code is more preferred by us:
class MyListView : public QListView
{
typedef QListView super;
public:
explicit MyListView(QWidget *parent = 0);
protected:
// QWidget interface
void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE;
private:
enum DragState {
DragStopped,
DragStarted,
Dragged
};
quint8 m_dragState;
int m_dragStartPos;
};
MyListView::MyListView(QWidget *parent)
: super(parent)
, m_dragState(DragStopped)
, m_dragStartPos(-1)
{
}
void MyListView::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) {
m_dragState = DragStarted;
m_dragStartPos = event->pos().y();
} else
super::mousePressEvent(event);
}
void MyListView::mouseReleaseEvent(QMouseEvent *event)
{
if(m_dragState) {
m_dragState = DragStopped;
m_dragStartPos = -1;
return;
}
super::mouseReleaseEvent(event);
}
void MyListView::mouseMoveEvent(QMouseEvent *event)
{
if(m_dragState != DragStopped) {
const int itemSize = sizeHintForRow(0) / 2;
const int distance = qAbs(m_dragStartPos - event->pos().y());
if(distance > 10)
m_dragState = Dragged;
if(distance > itemSize) {
QScrollBar *scrollBar = this->verticalScrollBar();
int stepCount = (distance/itemSize);
if(m_dragStartPos < event->pos().y())
stepCount = -stepCount; //scrolling up
scrollBar->setValue(scrollBar->value() + (stepCount * scrollBar->singleStep()));
m_dragStartPos = event->y();
}
return;
}
super::mouseMoveEvent(event);
}