Dragging multiple QGraphicsItems in different QGraphicsScenes for the same amount - qt

I have my own classes (MyGraphicsScene, MyGraphicsView, MyGraphicsItem) derived from QGraphicsScene, QGraphicsView and QGraphicsItem.
In my main window, I then create nine (9) instances of MyGraphicsScene, shown through nine instances of MyGraphicsView. All nine MyGraphicsScenes contain pointers to each other.
How can I drag an instance of MyGraphicsItem in one MyGraphicsScene, and then automatically drag certain MyGraphicsItem instances (in the eight remaining MyGraphicsScenes) for the same amount/distance/vector?
My first idea was to reimplement MyGraphicsItem::itemChange (with change == QGraphicsItem::ItemPositionChange) and then call setPos() for remaining instances of MyGraphicsItem (contained within other MyGraphicsScenes). However this won't work because I will get infinite recursion (setPos() would trigger itemChange() as well, including for the originating MyGraphicsItem).
Any other ideas from experienced Qt-ers?

I do know for a fact you can use the same QGraphicsScene in multiple views. So if you're just trying to have multiple views of the same thing, call MyGraphicsView all with the same scene.

If you define derived class for QGraphicsItem (I think you already have, right?) and reimplement the mouseMoveEvent so it calls the move method to all 9 objects.
Remember that you have at your disposal all the attributes of QMouseEvent, e.g. buttons(), pos().
void MyGraphicsItem::mouseMoveEvent( QMouseEvent *event )
{
if (event->buttons() & Qt::LeftButton) {
QPoint pos = event->pos();
// ...
}
}
Hope that helped!

Related

QGraphicsView level of detail handling

I am working on a visualization SW which uses QGraphicsView and QGraphicsScene (Qt4.8, PyQt). I need LOD (level of detail) handling depending on zoom level. For this I create more QGraphicsScenes and I render the whole scene to each with different LOD level. (There is a QGraphicsScene for each LOD level).
I switch between these QGraphicsScenes depending on the zoom level. (QGraphicsView.setScene()).
The big problem is that the QGraphicsView's ScrollBars resets their positions when setScene() is called.
Here is a code snippet which tries to restore the ScrollBars values, but it doesn't work:
hsb = self.horizontalScrollBar()
vsb = self.verticalScrollBar()
hv, vv = hsb.value(), vsb.value()
self.lod = i
self.setScene(self.scenes[self.lod])
hsb.setValue(hv) # this simply doesn't work !!!!
vsb.setValue(vv)
Any idea to retain the positions of the ScrollBars? Or any idea for efficient LOD handling?
I could try to connect the ScrollBar's value changed signals into some custom slots which would disconnect from its own signal than re-set the ScrollBar's value from a saved one. That would be a very lame and ugly solution.
I suggest to use a single scene to manage different level of details.
You can get the current level of detail directly inside the QGraphicsItem::paint method and draw the item accordingly.
Example C++:
void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
qreal levelOfDetail = QStyleOptionGraphicsItem::levelOfDetailFromTransform(painter->worldTransform());
//draw the item depending on the level of detail
}
Python
def paint(self, painter, option, widget):
levelOfDetail = QStyleOptionGraphicsItem.optionlevelOfDetailFromTransform(painter.worldTransform())
#draw the item depending on the level of detail
See QStyleOptionGraphicsItem::levelOfDetailFromTransform (for pyqt4 QStyleOptionGraphicsItem.levelOfDetailFromTransform )
For large scene, with a lot of items, consider to group items. You can check the level of details when the scene transform (or the view transform) changes and show/hide the group of items. See QGraphicsItemGroup.

Using QPainter with QPaintDevice multiple times

we all know this Warning from
bool QPainter::​begin(QPaintDevice * device)
Warning: A paint device can only be painted by one painter at a time.
http://doc.qt.io/qt-5/qpainter.html#begin
But what if I have two object sharing one pixmap, and one object Bar contains other object Foo.
class Foo
{
public:
QPixmap* barPixmap;
void draw()
{
QPainter painter(barPixmap);
painter.drawText(0,0,"FooText");
}
}
class Bar
{
public:
QPixmap* barPixmap;
Foo* fooObject;
}
and I got something like this
Bar::paintEvent(QPaintEvent* )
{
QPainter painter(barPixmap);
painter.drawText(50,50,"BarText");
fooObject->draw();
}
Is it multiple drawing? Compiler throws nothing and code seems working.
The warning tells you about creating multiple QPainters at one time. Since all paint events are processed in the main thread, they are processed consequently. As long as QPainter object is destroyed at the end of your event handler, the warning will not appear. Multiple consequent paintings on one device is fine.
However the architecture is questionable. For instance, if multiple widgets are painted this way, one of widgets will display old version of pixmap while second widget will display updated version. This inconsistency can be a problem. Putting any logic in paint event handlers is generaly pointless (and sometimes harmful). You should change pixmap when available data changes and just paint it in the paint event.

How do I keep two parallel Qsplitters equal sizes?

I have a Qt widget with four partitions, separated with splitters.
The top level is a vertical splitter, changing the heights of two horizontal splitters, topSplitter and bottomSplitter.
How can I keep both horizontal splitters positions equal, as if it was just one horizontal splitter?
I looked at linking signal for splitterMoved, and connecting it to a slot on the other splitter but there are no equivalent slots in the splitter class.
This would obviously have to avoid the issue of an infinite loop where one splitter's position updates the second, which updates the first.
It's pretty simple. Initialization (splitter1 and splitter2 are the splitters that need to be syncronized):
connect(ui->splitter1, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved()));
connect(ui->splitter2, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved()));
The slot:
void MainWindow::splitterMoved() {
QSplitter* senderSplitter = static_cast<QSplitter*>(sender());
QSplitter* receiverSplitter = senderSplitter == ui->splitter1 ?
ui->splitter2 : ui->splitter1;
receiverSplitter->blockSignals(true);
receiverSplitter->setSizes(senderSplitter->sizes());
receiverSplitter->blockSignals(false);
}
blockSignals ensures that calls will not go to infinite recursion. Actually, setSizes doesn't cause emitting splitterMoved, so you can remove both blockSigals calls and the code will still work. However, there is no note about this in the documentation, so I wouldn't rely on that.
In the containing widget you can create slots to connect to the splitterMoved signal.
There needs to be one slot for each splitter, where it needs to check if the splitter is already the correct size (to avoid the infinite loop) then update the size if necessary.
I am only including one of the example slots and connections, but one will be needed for each splitter that needs to be linked.
Putting the following content into the new slots for updating the splitter positions.
QList<int> OtherSize,Current;
OtherSize=topSplitter->sizes();
Current=bottomSplitter->sizes();
if(OtherSize!=Current)
{
bottomSplitter->setSizes(OtherSize);
}
This will create two lists ready to hold the sizes for the splitters.
It gets the current sizes of both splitters, and compares them.
The comparison is necessary to avoid the infinite loop.
Then, if they are different, it sets the sizes to match that of the other splitter.
Connecting that slot to the appropriate splitterMoved signal should work.
This connection is used in the constructor of the containing widget (where you created the new slots).
connect(topSplitter,SIGNAL(splitterMoved(int,int)),this,SLOT(updateBottomSplitter()));
It is safe to ignore the position and index supplied by the signal, because we check the sizes in a different way in this slot.
This will set all sizes, so all splitter bars will match position.
If there are only two, it doesn't matter but if there are more than two, when any bar is moved, all will be updated to match.

What is an appropriate place to initialize QWidgets?

What is the best way (place) to initialize QWidgets, when you add them programmatically?
I can think of ways to do it:
1) Create instances in the definition file and then initialize them in the initializer list. This seems to be the right way, but it becomes kind of sloppy the more QWidgets you need to add to the Window.
class SomeWindow : QDialog{
...
private:
QLabel label1;
QLabel label2;
...
}
SomeWindow::SomeWindow(...) : QDialog(...),
label('label1 text'), label('label2 text')
{
...
layout.addWidget(&label1);
layout.addWidget(&label2);
...
}
2) Coming from C# I tend to like this, but it seem to generate memory leak...
SomeWindow::SomeWindow(...) : QDialog(...)
{
QLabel* label1 = new QLabel('label1 text');
QLabel* label2 = new QLabel('label2 text');
...
layout.addWidget(label1);
layout.addWidget(label2);
...
}
Is there a better way to do this that I'm missing?
I appologize for the newbie question.
Qt use his own system to delete parent-children QObject derived classes. When you delete object, all children are deleted too.
With the first code, you have 2 destructions (in destructor of SomeWindow and with QObject system), then this code is illegal (only with Qt ; with C++ standard code, it's good)
With the second code, labels are delete by QObject system, you don't have memory leak. No need to keep pointer on objects.
#Jairo
Setting parent in constructor is not the only way to add children to objects. In particular, here, QLayout::addWidget reparent objets (if layout is correctly child of object)
#msgmaxim
Be carefull, layout must not be local variable in constructor
An advantage to having pointers to widgets in the headers, rather than the actual objects, is that you don't need to include all the headers for the widgets, but just forward declare them. This increases compilation time, which can be considerably noticeable in large projects.
In addition, if you have a dialog and are just adding a lot of widgets, such as a QLabel, you can make the code neater by doing this in the implementation: -
layout.addWidget(new QLabel('label1 text'));
layout.addWidget(new QLabel('label2 text'));
layout.addWidget(new QLabel('label3 text'));
layout.addWidget(new QLabel('label4 text'));
As has been mentioned, Qt's parent system will take care of cleaning up the widgets when their parent is deleted.
Of-course, if you want to change a property, such as the text, you'd then need to find the widget from the layout's children, but often QLabel, as its name implies, just labels an item and its properties aren't changed.
Two ways are good in order to initialize a new widget.
First case you have labels as objects. So when SomeWindow is destroyed, they will be destroyed automatically too. Remember, if you have pointer to widgets instead objects, you will need (and can) to delete the labels into destructor of dialog.
SomeWindow::~SomeWindow()
{
delete label1;
label2.deleteLater(); // A safer way to delete a widget. Thread-safe.
}
And second case there will be a memory leak because you have no way to delete the widget into destructor. But if you define a parent for labels, they will be delete when parent is delete too. Take a look to QWidget documentation.
If parent is 0, the new widget becomes a window. If parent is another widget, this widget becomes a child window inside parent. The new widget is deleted when its parent is deleted.
Moreover, anytime a object constructor ask you for a parent QWidget or QObject, you can think that Qt will delete the object when the parent is deleted.
I am a newbey as well, but I hope this help you.

Best way to create a long line (or cross line) cursor in Qt GraphicsView

The easy way to create the long cross line cursor (as long as viewport) is create a cross line graphicsItem, when mouse moved, set the item's pos property.
But this way will be very slow when the scene is complex, because it should update the whole viewport to update the cursor's pos.
The another easy way is setCursor(QCursor(..)),use a QPixmap to define the long cross line, this way will very fast , but the cursor will exceed the viewport rect.
Is there another way to show a long cross line cursor fastly?
Thanks very much!
If I understand correctly, you want to draw an horizontal line and a vertical line, crossing at the cursor position, and being as large as the viewport is.
A poosible solution would be to reimplement QGraphicsScene::drawForeground() to draw the two lines with the painter.
The problem is that the scene doesn't know about the mouse position. This means the view will have to track it and inform the scene when the mouse position has changed.
To do that, you'll have to create your own GraphicsScene (inheriting QGraphicsScene) and your own GraphicsView (inheriting QGraphicsView).
On your GraphicsView constructor, you'll have to start mouse tracking. This will make your you receive a mouseMoveEvent each time the mouse moves inside the view :
GraphicsViewTrack::GraphicsViewTrack(QWidget* parent) : QGraphicsView(parent) {
setMouseTracking(true);
}
void GraphicsViewTrack::mouseMoveEvent(QMouseEvent* pEvent) {
QPointF MousePos = this->mapToScene(pEvent->pos());
emit mousePosChanged(MousePos.toPoint());
}
As you can see in the code snippet above, the view is emitting a signal (mousePosChanged) to which the scene will be connected. This signal contains the mouse position, converted to the scene's coordinates.
Now, on the scene side, you have to add a slot which will be called when the mouse position changed, store the new mouse position in a member variable and reimplement QGraphicsScene::drawForeground() :
void GraphicsSceneCross::drawForeground(QPainter* painter, const QRectF& rect) {
QRectF SceneRect = this->sceneRect();
painter->setPen(QPen(Qt::black, 1));
painter->drawLine(SceneRect.left(), m_MousePos.y(), SceneRect.right(), m_MousePos.y());
painter->drawLine(m_MousePos.x(), SceneRect.top(), m_MousePos.x(), SceneRect.bottom());
}
void GraphicsSceneCross::onMouseChanged(QPoint NewMousePos) {
m_MousePos = NewMousePos; // Store the mouse position in a member variable
invalidate(); // Tells the scene it should be redrawn
}
The last thing to do is connect the GraphicsView's signal to the GraphicsScene slot.
I'll let you check if this solution is acceptable performance wise.
Based on Jerome answer and using python I created this code in my QGraphicsScene subclass:
def drawForeground(self, painter, rect):
if self.guidesEnabled:
painter.setClipRect(rect)
painter.setPen(self.guidePen)
painter.drawLine(self.coords.x(), rect.top(), self.coords.x(), rect.bottom())
painter.drawLine(rect.left(), self.coords.y(), rect.right(), self.coords.y())
def mouseMoveEvent(self, event):
self.coords = event.scenePos()
self.invalidate()
It should be straighforward for you to write its appropiate C++ code. Note that I take advantage of the rect argument passed by Qt Api framework and I clip the painter to that
area, since it's the visible area to be drawn.
I also cache the pen object, since I realized in other experiments that creating objects while painting will penalty performace and doing it so you also give the user the chance to set a custom pen in your program options.
I have found a way to do this!
I develops under Windows system, so can use lower GDI api jumping out of Qt's painting system.
The detail is get the HDC of the QGraphicsView's viewPort. Then in the QMouseEvent of QGraphicsView use "MoveToEx" and "LineTo" drawing two lines on the viewport,then I should do is erase the "old" cursor, It's easy to do this using "setROP2(HDC dc,R2_NOT)",then draw the old Cursor stored again.
This method doesn't enter the QPainter system, so the GraphicsItems under the cursor will not be repaint.
To solve the filker problem when the mouse moves fast, I don't use "double buffer". I used QTimer to update the cursor at CPU's idle. The detail is in QMouseEvent ,don't update the cursor at time ,but store the position to a list, When CPU idle, draw the cursor at the list of positions
I wish this will help others who meets the same problem with me.
Thanks Jérôme, who gave me useful tip of QGraphicsScene.

Resources