How to get visible items bounding-box? - qt

I have a scene that contains many QGraphicsItem items(about 25000 items) , When I hide useless items, How Can I get all visible items bounding-box, so that I can use fitInView ensure visible item just in the view center.
My Scene
Wanted get visible items bounding-box

I do not think there is any function for bounding getting rect of only visible items. I would use brute force, iterating over all visible items and calculating the total bounding rect. For example have a look at the implementation of QGraphicsScene::itemsBoundingRect() here https://code.woboq.org/qt5/qtbase/src/widgets/graphicsview/qgraphicsscene.cpp.html#_ZNK14QGraphicsScene17itemsBoundingRectEv and add visibility check to it.
QRectF visibleItemsBoundingRect() const
{
QRectF boundingRect;
const auto items_ = items();
for (QGraphicsItem *item : items_)
{
if (item->isVisible()) // Note: this line was added to original implementation
boundingRect |= item->sceneBoundingRect();
}
return boundingRect;
}
If this brute force performance is not satisfactory due to large number of invisible items, then you should probably keep some list of only visible items somewhere but you need to take the care to maintain it and update it everytime you show or hide or add or remove anything to the scene. Then you could iterate over such smaller list of items much faster. But I would not go this way unless the brute force method performance is really bad.

Related

In Graphic scene how to make overlapped object transparent?

In graphics view m setting scene in that adding objects (by dropping ), i can move these items by mouse, when i moved one object on another object moved object should be transparent. how can i make it?
I don't believe you actually want full transparency since it will make it impossible to visually recognize the transparent object later on. Reduced opacity - yes.
As for your question: each item inside your scene has a bounding rectangle (or other type of bounding area). You can easily get it by calling boundingRect() of your item. The returned QRectF has (just like QRect) has the bool QRect::intersects(const QRect &rectangle) const function, which takes another rectangle and checks if a collision is present.
Whenever you move your mouse while dragging an item you need to iterate either through all or just a subset of all items in your scene (by subset I mean just the items in a specific region to increase the performance) and check for collision. If a collision is detected, you can alter either the item you are dragging or the item underneath it.
Of course to make sure that one item covers another one you also need to check the Z value. The easiest way to do that is if you keep all currently not being dragged items at the same Z level and then, whenever you drag one, increase it's Z level by one so that it is "above" the others.

how to specify the height of scrollbar in QTreeView

just like the title described, when i redraw the scrollbar in QTreeView which has a header(QHeaderView), but the scrollbar's height is the entire height of QTreeView, and i want to let the scrollbar's height equals the QTreeView's height minus the header's height.just like the pic below:
A solution for your problem might be setting the location of the vertical scrollbar to a constant_offset value acquired from the QHeaderView (on the y axis).
This could be done by subclassing the QTreeView like so:
class MyTreeView : public QTreeView
{
public:
MyTreeView(QWidget* parent = nullptr): QTreeView(parent){}
void updateVertScrollBar()
{
auto* ptr = verticalScrollBar();
QRect rect = ptr->geometry();
rect.setTop(header()->height());
ptr->setGeometry(rect);
}
void resizeEvent(QResizeEvent* ev) override
{
QTreeView::resizeEvent(ev);
updateVertScrollBar();
}
};
Depending on the sizePolicy the updateVertScrollBar method could be called just after data is filled or as presented in the sample implementation the update can occur for each resizeEvent - which should cover various resizing performed to the widget.
EDIT
Additionally removing the blank space left from the shrunk scrollbar would be tricky. First denote that the QTreeView is built from a viewport widget and scrollbars (among others). The issue you now see comes from the fact that viewport plus vertical scrollbar widths (if visible) should match and this is calculated internally.
I stated that it's tricky since there is a load of stuff happening when you try to force the size of these components. Some updates are called in-place some are called through the event loop. You can check this to get more detaile info about the concept. Similar approach is probably applied to QTreeView.
Basically what you would need to do is to stretch the viewport width. This should be probably done during the resizeEvent but calling from there methods like viewport()->setGeometry() might not end well - you might get caught into a loop. You might try blockSignals but I'm not sure this would help. In general if you want to mess with the internals of a given Qt widget you should go through it's implementation at least briefly.

How to shift the pixels of part of a QGraphicsItem?

I would like to know whether it is possible to shift part of a drawing by copying its pixels rather than redrawing it.
I work in an embedded environment, where performance is a key factor. We use Qt 4.8.
I have a set of real-time data points that I want to draw. I define the following class:
class SetOfDataPoints : public QGraphicsItem
{
public:
<constructor>
QRectF boundingRect() const { ... }
void paint(QPainter* painter,
const QStyleOptionGraphicsItem* option,
QWidget* widget = NULL) { ... }
<other methods>
};
At regular intervals, I read a new data point, add it to the instance of SetOfDataPoints, and shift the SetOfDataPoints to the left (by calling QGraphicsItem::moveBy() on the SetOfDataPoints), so the new data point becomes visible. As a result, SetOfDataPoints::paint() gets called, and in that method I have to draw the entire set of data points. The drawing currently consists only of line segments that connect the data points, but will become more elaborate in the future.
Now, it feels inefficient to redraw the whole set of data points, when most of the graph is actually just shifted to the left. I would like to shift the pixels of the unchanged part of the graph to the left, and draw only the one line segment that connects the last two points. At least I would like to try, and measure how much that improves performance.
Is there a way to do that in Qt 4.8?
This won't work in general:
Your item doesn't exist as any pixels until it's rendered. You don't know how many pixels it is drawn on, or even if there are any, since there are 0 or more views your scene is shown on, and the item might be visible to various extent on these views.
There are transformations applied to your item. It doesn't have to be rectangular.
Your item is composited with the items below it, unless it is completely opaque.
Your item, shown on a view, is composited with the backing store of the widget the view is on.
You can optimize for special cases. If your item is not cached, then it's always painted on a view, and the widget argument of paint will point to that widget. You then have direct access to the backing store, and the painter gives you the transformation used to go from item coordinates to the backing store's device coordinates. You can then inspect the path on the widget tree from the view to the window for opacity. If all intervening widgets paint opaque, and your item has an orientation-preserving transformation, you can certainly do a blit on the image, and redraw only a small part of the item.
If your item is cached, it should then be cached in device coordinates. You can do the blitting too, as you're painting on a pixmap. That pixmap is then composited onto the backing store of the window the view is on. There's a separate cache pixmap for each view.
When blitting, you must always recognize how much of the previous pixels are correct. For each view or cache pixmap, you should keep a region that is valid. That region initally should be empty.

QScrollArea: auto-scroll to newly added widget

it's not the first time that I want a scroll area which behaves like the following (imagine a log or chat window, but too complex to use a simple QTextBrowser):
I want to add multiple widgets, which appear one below the other (like when placed in a QVBoxLayout)
Each widget within this layout should have a fixed size or a height-for-width (like a simple label)
The scroll area should auto-scroll to the most recently added one
(optional) When there is space left (scroll bar not yet enabled), the contents should be aligned to bottom
Using QScrollArea:
My attempt in the past was using a QScrollArea using a QVBoxLayout inside. But this seems to be not as simple as I thought: Whenever I add a widget to the layout, the layout doesn't resize the scroll area content widget immediately, resulting in a delayed adjustment of the contents. For one short moment, the widgets contained in the layout are resized so that the total size equals the total size before the add operation, resulting in a too small size per widget. Also, scrolling to the newly added widget is thus not possible until the layout corrected its size to the new total size of widgets, so even a QTimer::singleShot(0, ...) doesn't help here. Even with a timeout of 20 or so, there are situations in which the layout needs more time to resize. It's not deterministic, and thus far away from a nice solution.
In order to get the bottom alignment behaviour, I initially place a spacer item in the layout. It won't require any space as soon as there is no space left and scrolling gets enabled.
Using QListView:
As my items are too complex, they need to be QWidgets. They can't have the focus, aren't selectable, so an item-based solution seems to be just "the wrong way". Also, this solution sounds too "heavy".
I just can't believe that there is no easy way, so I think I just haven't seen it yet!
QListView should be fine. You claim that your items are static, there's no interaction with them: no focus, no selection. It'd seem that a QWidget is an overkill for such items. You only need something that has a fixed size and can draw itself. That is precisely what delegates in the Qt's model-view system are for. Just implement one or more QAbstractItemDelegates for your items, and provide an implementation of a model for the data they will be rendering. The QAbstractItemView is is already a QAbstractScrollArea!
If you want to paint HTML within a delegate, it's easy to do -- again, QWidget is an overkill for a static display! There is a very food reason why it's "hard" to use QWidget for this -- the API guides you to the correct solution. Assuming your model contains html for each item, here's how you can paint it. You can go fancy with the sizeHint, of course, and should be caching the text document, ideally storing it in the model I'd think.
void MyDelegate::paint(QPainter* p, const QStyleOptionViewItem & opt, const QModelIndex & index) const
{
QTextDocument doc;
doc.setHtml(index.data().toString());
doc.drawContents(p, QRect(QPoint(0,0), sizeHint(opt, index)));
}
QSize MyDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const
{
return QSize(100, 200);
}

Resizing QT's QTextEdit to Match Text Height: maximumViewportSize()

I am trying to use a QTextEdit widget inside of a form containing several QT widgets. The form itself sits inside a QScrollArea that is the central widget for a window. My intent is that any necessary scrolling will take place in the main QScrollArea (rather than inside any widgets), and any widgets inside will automatically resize their height to hold their contents.
I have tried to implement the automatic resizing of height with a QTextEdit, but have run into an odd issue. I created a sub-class of QTextEdit and reimplemented sizeHint() like this:
QSize OperationEditor::sizeHint() const {
QSize sizehint = QTextBrowser::sizeHint();
sizehint.setHeight(this->fitted_height);
return sizehint;
}
this->fitted_height is kept up-to-date via this slot that is wired to the QTextEdit's "contentsChanged()" signal:
void OperationEditor::fitHeightToDocument() {
this->document()->setTextWidth(this->viewport()->width());
QSize document_size(this->document()->size().toSize());
this->fitted_height = document_size.height();
this->updateGeometry();
}
The size policy of the QTextEdit sub-class is:
this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
I took this approach after reading this post.
Here is my problem:
As the QTextEdit gradually resizes to fill the window, it stops getting larger and starts scrolling within the QTextEdit, no matter what height is returned from sizeHint(). If I initially have sizeHint() return some large constant number, then the QTextEdit is very big and is contained nicely within the outer QScrollArea, as one would expect. However, if sizeHint gradually adjusts the size of the QTextEdit rather than just making it really big to start, then it tops out when it fills the current window and starts scrolling instead of growing.
I have traced this problem to be that, no matter what my sizeHint() returns, it will never resize the QTextEdit larger than the value returned from maximumViewportSize(), which is inherited from QAbstractScrollArea. Note that this is not the same number as viewport()->maximumSize(). I am unable to figure out how to set that value.
Looking at QT's source code, maximumViewportSize() is returning "the size of the viewport as if the scroll bars had no valid scrolling range." This value is basically computed as the current size of the widget minus (2 * frameWidth + margins) plus any scrollbar widths/heights. This does not make a lot of sense to me, and it's not clear to me why that number would be used anywhere in a way that supercede's the sub-class's sizeHint() implementation. Also, it does seem odd that the single "frameWidth" integer is used in computing both the width and the height.
Can anyone please shed some light on this? I suspect that my poor understanding of QT's layout engine is to blame here.
Edit: after initially posting this, I had the idea to reimplement maximumViewportSize() to return the same thing as sizeHint(). Unfortunately, this did not work as I still have the same problem.
I have solved this issue. There were 2 things that I had to do to get it to work:
Walk up the widget hierarchy and make sure all the size policies made sense to ensure that if any child widget wanted to be big/small, then the parent widget would want to be the same thing.
This is the main source of the fix. It turns out that since the QTextEdit is inside a QFrame that is the main widget in a QScrollArea, the QScrollArea has a constraint that it will not resize the internal widget unless the "widgetResizable" property is true. The documentation for that is here: http://doc.qt.io/qt-4.8/qscrollarea.html#widgetResizable-prop. The documentation was not clear to me until I played around with this setting and got it to work. From the docs, it seems that this property only deals with times where the main scroll area wants to resize a widget (i.e. from parent to child). It actually means that if the main widget in the scroll area wants to ever resize (i.e. child to parent), then this setting has to be set to true.
So, the moral of the story is that the QTextEdit code was correct in overriding sizeHint, but the QScrollArea was ignoring the value returned from the main frame's sizeHint.
Yay! It Works!
You may try setting minimumSize property of the QTextEdit to see if that force the layout to grow.
I don't understand most of Qt's layout scheme but setting minimum and maximum size pretty much does what I want it to do. Well, most of the time anyways.

Resources