QGraphicsItem and tab order - c++

For some GUI application I use QMainWindow with different controls on it: QGraphicsScene + QGraphicsView , QPushButtons, QWidget.
Inside QGraphicsScene located a lot of different items type:
QGraphicsPolygonItem
QGraphicsTextItem
QGraphicsRectItem
But for me is most important is Polygon item, this item has those flags:
setFlag(QGraphicsItem::ItemIsFocusable, true);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
I can select every item by mouse, but I need to change focus for those items by Tab.
How is it possible ?
SetTabOrder worked only with QGraphicsObjects.
I tried to solve this problem with QGraphicsView::focusNextPrevChild redefining
bool MyGraphicsView::focusNextPrevChild( bool next )
{
QGraphicsPolygonItem *target;
QGraphicsPolygonItem *current;
if( scene()->focusItem() )
{
target = qgraphicsitem_cast<QGraphicsPolygonItem*>( scene()->focusItem() );
bool is_focus_next=false;
foreach( QGraphicsItem *item, scene()->items() )
{
current = qgraphicsitem_cast<QGraphicsPolygonItem*>( item );
// set focus for next before selected
if( current && is_focus_next )
{
item->setFocus( Qt::MouseFocusReason );
// item->setSelected( true );
is_focus_next = false;
break;
}
// searching for selected item
if( current && current == target )
{
is_focus_next = true;
}
}
}
}
But only first Tab worked, When I pressed Tab again focus has moved to other QWidget outside the QGraphicsView control.
Please, could you help me with Tab order focus for QGraphicsItem.
Thank you
EDIT: The final version, thanks to Steffen.
bool MyGraphicsView::focusNextPrevChild( bool next )
{
QGraphicsPolygonItem *target;
QGraphicsPolygonItem *current;
if( scene()->focusItem() )
{
target = qgraphicsitem_cast<QGraphicsPolygonItem*>( scene()->focusItem() );
bool is_focus_next=false;
foreach( QGraphicsItem *item, scene()->items() )
{
current = qgraphicsitem_cast<QGraphicsPolygonItem*>( item );
// set focus for next before selected
if( current && is_focus_next )
{
item->setFocus( Qt::MouseFocusReason );
return true;
}
// searching for selected item
if( current && current == target )
{
is_focus_next = true;
}
}
}
return QGraphicsView::focusNextPrevChild(next);
}

You should return true to indicate that you actually found a widget. As you have no return statement at all at the moment, the behaviour is undefined. You can also add some logic to return false on the last element to allow the user to leave your QGraphicsView again.
See also the documentation for QWidget::focusNextPrevChild().

Related

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 know the QTreeView item decoration is clicked

I'm trying to know when the user selects the decoration of an item, because I was trying to implement a single click expand/collapse QTreeview and the decoration now does nothing. It doesn't expand or collapse the item where as if I click on the item it works properly.
void MyTreeView::mousePressEvent(QMouseEvent *event)
{
QTreeView::mousePressEvent(event);
if (event->button() == Qt::LeftButton)
{
QModelIndex index = indexAt(event->pos());
isExpanded(index) ? collapse(index) : expand(index);
}
}
The problem is that when the decoration is selected, it enters the if condition. If it was not, everything works fine.
I don't know if I have to block the decoration action or have a condition in the if statement.
How do I know the decoration is selected and not the item itself or how do I block decoration action ?
Try this:
void MyTreeView::mousePressEvent( QMouseEvent* aEvent )
{
QModelIndex index = indexAt( aEvent->pos() );
if ( index.isValid() )
{
const bool wasExpanded = isExpanded( index );
QTreeView::mousePressEvent( aEvent );
if ( aEvent->button() == Qt::LeftButton )
{
const bool expanded = isExpanded( index );
// QTreeView did not change the item's state ... but you want.
if ( wasExpanded == expanded )
{
expanded ? collapse( index ) : expand( index );
}
}
}
else
{
QTreeView::mousePressEvent( aEvent );
}
}

Qt moving an Object in a specific Region

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.

Cannot draw checkbox in QStyledItemDelegate

I have a QStyledItemDelegate derived object for a QTableView derived view. I further delegate the painting and editor creation depending on the model index data type. For bools I wanted to represent the state via a checkbox - but the check box never appears.
Here is the base delegate paint function:
void Sy_QtPropertyDelegate::paint( QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index ) const
{
painter->save();
if ( index.column() == 0 ) {
...
} else {
QVariant var = index.data();
bool modified = index.data( Sy_QtPropertyModel::ModifiedRole ).toBool();
// If the data type is one of our delegates, then push the work onto
// that.
auto it = delegateMap_.find( var.type() );
if ( it != delegateMap_.end() ) {
( *it )->paint( painter, option, index );
} else if ( var.type() != QVariant::UserType ) {
...
} else {
...
}
}
painter->restore();
}
And the bool sub delegate paint function:
void Sy_boolPD::paint( QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index ) const
{
painter->save();
bool checked = index.data().toBool();
bool modified = index.data( Sy_QtPropertyModel::ModifiedRole ).toBool();
QStyle* style = Sy_application::style();
if ( modified ) {
QStyleOptionViewItemV4 bgOpt( option );
bgOpt.backgroundBrush = QBrush( Sy_QtPropertyDelegate::ModifiedColour );
style->drawControl( QStyle::CE_ItemViewItem, &bgOpt, painter );
}
QStyleOption butOpt( option );
butOpt.state = QStyle::State_Enabled;
butOpt.state |= checked ? QStyle::State_On : QStyle::State_Off;
style->drawControl( QStyle::CE_CheckBox, &butOpt, painter );
painter->restore();
}
If I force modified to be true, the background is colour of the table is appropriately changed, and couting butOpt's rect and state members show that they are correct - but no check box is shown! Setting QStyle::CE_CheckBox to any other type also causes nothing to render.
I've worked with Qt's MVC framework a lot, but I cannot see where I have gone wrong here.
All I had to do was look in the source code...
case CE_CheckBox:
if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
...
}
The QStyleOption I pass to the method is cast to the type specific for drawing the control, CE_CheckBox requires a QStyleOptionButton, and if the cast fails the drawing operation silently skips.

Getting position of mouse click in a QLabel

What is the best (as in simplest) way to obtain the pos of a mousePressedEvent in a QLabel? (Or basically just obtain the location of a mouse click relative to a QLabel widget)
EDIT
I tried what Frank suggested in this way:
bool MainWindow::eventFilter(QObject *someOb, QEvent *ev)
{
if(someOb == ui->label && ev->type() == QEvent::MouseButtonPress)
{
QMouseEvent *me = static_cast<QMouseEvent *>(ev);
QPoint coordinates = me->pos();
//do stuff
return true;
}
else return false;
}
However, I receive the compile error invalid static_cast from type 'QEvent*' to type 'const QMouseEvent*' on the line where I try to declare me. Any ideas what I'm doing wrong here?
You could subclass QLabel and reimplement mousePressEvent(QMouseEvent*). Or use an event filter:
bool OneOfMyClasses::eventFilter( QObject* watched, QEvent* event ) {
if ( watched != label )
return false;
if ( event->type() != QEvent::MouseButtonPress )
return false;
const QMouseEvent* const me = static_cast<const QMouseEvent*>( event );
//might want to check the buttons here
const QPoint p = me->pos(); //...or ->globalPos();
...
return false;
}
label->installEventFilter( watcher ); // watcher is the OneOfMyClasses instance supposed to do the filtering.
The advantage of event filtering is that is more flexible and doesn't require subclassing. But if you need custom behavior as a result of the received event anyway or already have a subclass, its more straightforward to just reimplement fooEvent().
I had the same problem
invalid static_cast...
I just forgot to include the header: #include "qevent.h"
Now everything is working well.