Qt moving an Object in a specific Region - c++

I'm new to Qt programming. So far I made a QGraphicsScene draw some rectangles (looks like a Chess Board) and then added an own QGraphicsItem to the scene. For this item I set the ItemIsMovable flag. Now I can move it but I would like to restrict the movement to the area where the Chess Board is.
Would I have to unset the flag and realize the movement manually or is there like an option or flag where I can specify the area it can be moved in ?
renderableObject::renderableObject(QObject */*parent*/)
{
pressed = false;
setFlag(ItemIsMovable);
}

You can reimplement the QGraphicsItem's itemChange() if you want to restrict movable area. Repositioning the item will cause itemChange to get called. You should note that ItemSendsGeometryChanges flag is needed to capture the change in position of QGraphicsItem.
class MyItem : public QGraphicsRectItem
{
public:
MyItem(const QRectF & rect, QGraphicsItem * parent = 0);
protected:
virtual QVariant itemChange ( GraphicsItemChange change, const QVariant & value );
};
MyItem::MyItem(const QRectF & rect, QGraphicsItem * parent )
:QGraphicsRectItem(rect,parent)
{
setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges);
}
QVariant MyItem::itemChange ( GraphicsItemChange change, const QVariant & value )
{
if (change == ItemPositionChange && scene()) {
// value is the new position.
QPointF newPos = value.toPointF();
QRectF rect = scene()->sceneRect();
if (!rect.contains(newPos)) {
// Keep the item inside the scene rect.
newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
return newPos;
}
}
return QGraphicsItem::itemChange(change, value);
}
This will keep item moving limited to scene rect. You can define any arbitrary rect if you like.

Related

Will paint(), redraw everything from scene or will redraw only updated items in Qt?

I have QGraphicsView which contains many QGraphicsItem such as Rectangle, polylines. I have overriden paint() method. I am drawing QGraphicsItem using boost-graph. While drawing items, I am storing boost-graph pointer on every QGraphicsItem.
Now I am right clicking on some rectangle from scene and trying to hide it. While hiding it, I am trying to hide lines connected to it also. For that, I am taking boost-graph pointer stored at every item and iterating through it.
In boost graph, there is a flag isVisible, through setting-resetting it, I am hidding-unhidding that item.
myView.cpp
while(true)
{
// iterating thorugh boost-graph and finding co-ordinates
myRect* _rect = new myRect(rect co-ordinates);
_rect->setBrush(Qt::yellow);
_rect->setPtr(boost-graph pointer);
_scene->addItem(static_cast<QGraphicsRectItem*>(_rect));
}
void myView::HideSelectedRectangle() // after choosing hide from right mouse click, control comes here
{
foreach(QGraphicsItem* currentItem, _scene->selectedItems())
{
myRect* rItem = qgraphicsitem_cast<myRect*>(currentItem);
if(rItem)
{
VertexDescriptor vPtr = rItem->getBoostPtr(); // getting boost-graph ptr
// logic for making it hide
// Question is how paint() will know about this QGraphicsItem that it is hidden?
};
}
}
myRect.h
class myRect: public QGraphicsRectItem
{
public:
explicit myRect();
explicit myRect(QRectF &rectPoints,QGraphicsItem *parent = nullptr)
: QGraphicsRectItem(rectPoints,parent){}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
void setPtr(VertexDescriptor);
VertexDescriptor getPtr();
VertexDescriptor boostPtr;
}
myRect.cpp
void myRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
auto copied_option = *option;
copied_option.state &= ~QStyle::State_Selected;
auto selected = option->state & QStyle::State_Selected;
QGraphicsRectItem::paint(painter, &copied_option, widget);
if(selected)
{
painter->save();
painter->setBrush(Qt::NoBrush);
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
painter->drawPath(shape());
painter->restore();
}
}
void myRect::setPtr(VertexDescriptor vIter)
{
this->boostPtr = vIter;
}
VertexDescriptor myRect::getPtr()
{
return boostPtr;
}
Now assume I have made _isVisible = false (which is in boost-graph) for rectangle and some connected lines.
And now I want to redraw view using paint(). And expecting, paint() should not draw those
rectangle and lines which are marked as not visible.
While doing this :
Is paint() redraw every QGraphicsItem from view or it will redraw only
those were updated ?
How paint() will know, which shape should it draw and its co-ordinates
?
Is it possible in my paint(), by checking QGraphicsItem's flag (isVisible) I can guide paint() which items to redraw and which not to
redraw ?

How to paint object by overriding paintEvent in Qt?

In my QGraphicsView, I have many QGraphicsItem. I want to search specific QGraphicsItem out of all the items present in view and highlight the matched item. For highlighting item, I am trying to use paintEvent.
So I am calling paintEvent, but not understanding how to heighlight border of the matched object ?
Should I need co-ordinates of that matched object ?
I tried like this:
foreach(QGraphicsItem* currentItem, _scene->items())
{
pEvent = false;
QGraphicsRectItem* rItem = qgraphicsitem_cast<QGraphicsRectItem*>(currentItem);
if(rItem)
{
// some logic to get i->Name()
QString name1 = i->Name();
QString name2 = "reN"; // I want to find reN named item in view
if(name1 == name2)
{
pEvent = true;
qDebug()<<"Object Found ";
this->repaint();
break;
}
}
}
void myClass::paintEvent(QPaintEvent *event) {
Q_UNUSED(event);
qDebug()<<"In paint event ";
if(pEvent)
{
QPainter qp(this);
drawBody(&qp);
}
}
void myClass::drawBody(QPainter *qp) {
Q_UNUSED(qp);
// want logic for heighlighting border of the item
}
I understand from your problem that you are overridng QGraphicsView in MyClass since paintEnent is defined QWidgets classes.
To solve your problem, if I understand correctly. It's necessary to save the QGraphicsRectItem coordinates:
QRectF rectF = item.boundingRect();
then use the following in the function drawBody:
qp.save();
const float width = 5;
QPen pen;
pen.setWidth(width );
pen.setColor("green");
painter->drawRect(rectF.x(), rectF().y(),
rectF().width(), rectF().height());
qp.restore();

How to get mouse click and release position using override virtual paint()?

I am having QGraphicsView which contains different QGraphicsItem's like rectangle,polyline,text etc. I have installed event filter through which I am managing mouse click and release event and creating rubber band rectangle. Whichever area comes under rubber band rectangle, it gets zoom in.
I am storing rubber band rectangle points in local variables like rubberBandOrigin ( mouse click position ) and rubberBandEnd ( mouse release position)
When I click on any item's, its paint() method gets called. I want to use rubberBandOrigin and rubberBandEnd variables in paint() method.
The way I was told that, you will get these variables ( means mouse click position and release position ) through paint() itself.
paint() has 3rd parameter QWidget *widget. Through this, you will get mouse click and release position (i.e. rubber band rectangle position)
Here is my code:
bool guiSchematicViewUtil::eventFilter(QObject *watched, QEvent *event)
{
bool filterEvent = false;
switch(event->type())
{
case QEvent::MouseButtonPress:
{
QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
rubberBandOrigin = mouseEvent->pos();
rubberBandActive = true;
break;
}
case QEvent::MouseButtonRelease:
{
if (rubberBandActive)
{
QMouseEvent * mouseEvent = static_cast<QMouseEvent *>(event);
rubberBandEnd = mouseEvent->pos();
QGraphicsView * view = static_cast<QGraphicsView *>(watched->parent());
QRectF zoomRectInScene = QRectF(view->mapToScene(rubberBandOrigin),
view->mapToScene(rubberBandEnd));
view->fitInView(zoomRectInScene, Qt::KeepAspectRatio);
rubberBandActive = false;
}
break;
}
default:
break;
}
return filterEvent;
}
paint method:
void guiSchematicRect::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
// Here I want rubberBandOrigin and rubberBandEnd co-ordinates using QWidget* widget parameter
}
I tried with api like parent(), parentWidget() of QWidget but did not get it.
Can any one help me ?

How to interactively resize a QGraphicsObject?

I'm having trouble figuring out how to scale a custom sub classed QGraphicsObject in Qt.
I have the code working so that the mouse cursor changes when hovering over the edges object's boundingRect(Left,Right,Top,Bottom).
I have the overloaded mouse events working properly.
And I have custom methods that are trying to change the boundingRect.
But the object refuses to change size.
As a quick and simple test.
I tried to change the size of the object in the paint() method using hard coded values. And what happens is that the object does change size. But the collisions are still using the old boundingRect size.
What seems to be tripping me up is that QGraphicsObject uses a 'const' boundingRect value. And I can't figure out how to deal with that.
ResizableObject::ResizableObject( QGraphicsItem *parent ): QGraphicsObject( parent ), mResizeLeft( false ), mResizeRight( false ), mResizeBottom( false ), mResizeTop( false )
{
setAcceptHoverEvents( true );
setFlags( ItemIsMovable | ItemIsSelectable );
}
QRectF ResizableObject::boundingRect() const
{
return QRectF(0 , 0 , 100 , 100);
}
void ResizableObject::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED( option )
Q_UNUSED( widget )
painter->setRenderHint(QPainter::Antialiasing);
//Determine whether to draw a solid, or dashed, outline border
if (isSelected() )
{
//Draw a dashed outlined rectangle
painter->setPen( Qt::DashLine );
painter->drawRect( boundingRect() );
}
else painter->drawRect(0,0,20,20); //Draw a normal solid outlined rectangle
//No collisions are happening
if(scene()->collidingItems(this).isEmpty())
{
//qDebug()<< "empty";
}
//Collisions are happening
else
{
foreach(QGraphicsItem *item, collidingItems())
{
qDebug()<< item->pos();
}
}
}
The only examples I can find use Qwidget or QGraphicsRectItem. But those are slightly different and the constructors are not constants.
Can anyone tell me how to change the size of these QGraphicsObjects with the mouse?
The paint method's only job is to paint. It can't be doing anything else - that includes attempting to change the object's size.
Instead of making the item itself resizable, let's see if we can have a more generic way of resizing any item, of any class. First, we may not wish all objects to be resizable. We need a way to figure which of them are. We also need a way to tell an item to resize - a QGraphicsItem doesn't offer a way to do that. In generic programming, one pattern used to provide such functionality is a traits class. This class implements the application-specific traits that will customize the generic resize behavior to our needs.
For this simple example, we allow any QGraphicsEllipseItems to be resizable. For your application, you can implement any logic you desire - without any subclassing. You can certainly create a base class for resizable items, and expose it via the traits, if you have many custom, resizable items. Even then, the traits class can still support items of other classes.
In all cases, the items to be resized must be selectable, and we exploit the selection mechanism to hook into the scene. This could be also done without using selections.
// https://github.com/KubaO/stackoverflown/tree/master/questions/graphics-resizable-32416527
#include <QtWidgets>
class SimpleTraits {
public:
/// Determines whether an item is manually resizeable.
static bool isGraphicsItemResizeable(QGraphicsItem * item) {
return dynamic_cast<QGraphicsEllipseItem*>(item);
}
/// Gives the rectangle one can base the resize operations on for an item
static QRectF rectFor(QGraphicsItem * item) {
auto ellipse = dynamic_cast<QGraphicsEllipseItem*>(item);
if (ellipse) return ellipse->rect();
return QRectF();
}
/// Sets a new rectangle on the item
static void setRectOn(QGraphicsItem * item, const QRectF & rect) {
auto ellipse = dynamic_cast<QGraphicsEllipseItem*>(item);
if (ellipse) return ellipse->setRect(rect);
}
};
To implement a "wide rubber band" controller, we need a helper to tell what rectangle edges a given point intersects:
/// The set of edges intersecting a rectangle of given pen width
Qt::Edges edgesAt(const QPointF & p, const QRectF & r, qreal w) {
Qt::Edges edges;
auto hw = w / 2.0;
if (QRectF(r.x()-hw, r.y()-hw, w, r.height()+w).contains(p)) edges |= Qt::LeftEdge;
if (QRectF(r.x()+r.width()-hw, r.y()-hw, w, r.height()+w).contains(p)) edges |= Qt::RightEdge;
if (QRectF(r.x()-hw, r.y()-hw, r.width()+w, w).contains(p)) edges |= Qt::TopEdge;
if (QRectF(r.x()-hw, r.y()+r.height()-hw, r.width()+w, w).contains(p)) edges |= Qt::BottomEdge;
return edges;
}
Then, we have a "rubberband" resize helper item that tracks the selection on its scene. Whenever the selection contains one item, and the item is resizable, the helper makes itself a child of the item to be resized, and becomes visible. It tracks the mouse drags on the active edges of the rubber band, and resizes the parent items accordingly.
template <typename Tr>
class ResizeHelperItem : public QGraphicsObject {
QRectF m_rect;
QPen m_pen;
Qt::Edges m_edges;
void newGeometry() {
prepareGeometryChange();
auto parentRect = Tr::rectFor(parentItem());
m_rect.setTopLeft(mapFromParent(parentRect.topLeft()));
m_rect.setBottomRight(mapFromParent(parentRect.bottomRight()));
m_pen.setWidthF(std::min(m_rect.width(), m_rect.height()) * 0.1);
m_pen.setJoinStyle(Qt::MiterJoin);
}
public:
ResizeHelperItem() {
setAcceptedMouseButtons(Qt::LeftButton);
m_pen.setColor(QColor(255, 0, 0, 128));
m_pen.setStyle(Qt::SolidLine);
}
QRectF boundingRect() const Q_DECL_OVERRIDE {
auto hWidth = m_pen.widthF()/2.0;
return m_rect.adjusted(-hWidth, -hWidth, hWidth, hWidth);
}
void selectionChanged() {
if (!scene()) { setVisible(false); return; }
auto sel = scene()->selectedItems();
if (sel.isEmpty() || sel.size() > 1) { setVisible(false); return; }
auto item = sel.at(0);
if (! Tr::isGraphicsItemResizeable(item)) { setVisible(false); return; }
setParentItem(item);
newGeometry();
setVisible(true);
}
void paint(QPainter * p, const QStyleOptionGraphicsItem *, QWidget *) Q_DECL_OVERRIDE {
p->setPen(m_pen);
p->drawRect(m_rect);
}
void mousePressEvent(QGraphicsSceneMouseEvent * ev) Q_DECL_OVERRIDE {
m_edges = edgesAt(ev->pos(), m_rect, m_pen.widthF());
if (!m_edges) return;
ev->accept();
}
void mouseMoveEvent(QGraphicsSceneMouseEvent * ev) Q_DECL_OVERRIDE {
auto pos = mapToItem(parentItem(), ev->pos());
auto rect = Tr::rectFor(parentItem());
if (m_edges & Qt::LeftEdge) rect.setLeft(pos.x());
if (m_edges & Qt::TopEdge) rect.setTop(pos.y());
if (m_edges & Qt::RightEdge) rect.setRight(pos.x());
if (m_edges & Qt::BottomEdge) rect.setBottom(pos.y());
if (!!m_edges) {
Tr::setRectOn(parentItem(), rect);
newGeometry();
}
}
};
Finally, we create a simple scenario to try it all out:
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QGraphicsScene scene;
QGraphicsView view { &scene };
typedef ResizeHelperItem<SimpleTraits> HelperItem;
HelperItem helper;
QObject::connect(&scene, &QGraphicsScene::selectionChanged, &helper, &HelperItem::selectionChanged);
scene.addItem(&helper);
auto item = scene.addEllipse(0, 0, 100, 100);
item->setFlag(QGraphicsItem::ItemIsSelectable);
view.setMinimumSize(400, 400);
view.show();
return app.exec();
}
I think you need to declare QRectF variable in somewhere and modify boundingRect() to simply return that one. And override mouse event handlers to modify that QRectF variable where you need (like mouseReleaseEvent() maybe,,)
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event)
virtual void mousePressEvent(QGraphicsSceneMouseEvent * event)
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
And don't forget to call prepareGeometryChange() before changing that value to invalidate item's boundingRect() cache.

Adjusting the selection behaviour of QStandardItem with QStyledItemDelegate

I am using QStyledItemDelegate to style the items in my QTreeView.
The roots of my treeview are not decorated. It's just a simple tree with relation similar to the one below:
ColorBook1
Color1
Color2
ColorBook2
Color3
The parent and child are styled differently and selection on parent is disabled.
I want to customise the selection behaviour in the child nodes so that the selection rectangle around the child would cover the entire row and not the text child alone.
Current Behaviour:
Desired Behaviour:
Is there any way to extend the selection rectangle like this using QStyledItemDelegate? I tried adjusting the rect in QStyleOptionViewItem parameter of QStyledItemDelegate::paint. But that moved the child node text to the left. I want to keep the text node at the same place but only the selection rectangle has to be adjusted to the left. So just like drawing text and pixmaps in the paint method is there a way to draw the selection rectangle as well(, using the default selection rect color)?
The paint method of my StyledItemDelegate is as follows:
I am using the following code in the QStyledItemDelegate::paint method:
void paint( QPainter * inPainter, const QStyleOptionViewItem & inOption, const QModelIndex & inIndex ) const
{
if( inIndex.data( Qt::UserRole ) == ColorInfoType::kColorBook )
{
QFont font = inPainter->font();
font.setWeight( QFont::Bold );
font.setPointSize( 8 );
inPainter->setFont( font );
inPainter->drawText
(
inOption.rect.adjusted( 5,0,0,0 ),
inIndex.data( Qt::DisplayRole ).toString(),
QTextOption( Qt::AlignVCenter | Qt::AlignLeft )
);
}
else
{
//To Do: draw the selection rect after adjusting the size.
// Draw the Color Name text
QStyledItemDelegate::paint( inPainter, inOption, inIndex );
}
}
You can paint it yourself. Use option.palette.brush(QPalette::Highlight) to get the highlight color.
In this snippet I just paint the blank area manually. I also changed the color, but you don't have to do that.
void StyleDel::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if(option.state.testFlag(QStyle::State_Selected))
{
QStyleOptionViewItem newOption = option;
newOption.state = option.state & (~QStyle::State_HasFocus);
QBrush brush = option.palette.brush(QPalette::Highlight);
brush.setColor(QColor(150,0,0,100));
newOption.palette.setBrush(QPalette::Highlight, brush);
QRect s_rect = newOption.rect; //we use this rect to define the blank area
s_rect.setLeft(0); // starts from 0
s_rect.setRight(newOption.rect.left()); //ends where the default rect starts
painter->fillRect(s_rect, newOption.palette.brush(QPalette::Highlight));
QStyledItemDelegate::paint(painter, newOption, index);
return;
}
QStyledItemDelegate::paint(painter, option, index);
}