How to know a QGraphicsItem is moving - qt

I have a child class of QGraphicsItem with Selectable, Movable and SendsScenePositionChanges flags. When I select many items and moving them, all recieves itemChange event. Is there any way to detect in itemChanged if the item is moving? (or other way but in item level not scene level)
I tried to check if the left button was held down, but this way seems not working.
QVariant BaseItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemScenePositionHasChanged)
{
Qt::MouseButtons btns = QApplication::mouseButtons();
if (btns & Qt::LeftButton) {
// The left button is pressed.
mIsMoving = true;
}
else
{
mIsMoving = false; // this line is never triggered
}
}
return QGraphicsItem::itemChange(change, value);
}
Edit:
Let me describe my original needs, there are item1 and item2, an arrow connect between them, let's call it arrowItem, if I move item1, the arrow also needs to automatically adjust the shape to apply the new position, let's call this shape autoShape. User can also adjust the shape of the arrow, let's call it customShape.
Now, If item1 and item2 are selected at the same time and moved together, the arrow (has customShape) just need to move with them, no need to adjust the shape automatically . My current code flow is as follows: the movement of item1 triggers the update of the arrow, then the arrow needs to check whether item2 is also moving. If item2 is not moving, then automatically adjust the shape of the arrow. If item2 is also moving and the movement is same as item1, then Just move the arrowItem directly, no need to adjust the shape. Both item1 and item2 may be children of some other items.

Seems like you have to enable ItemSendsGeometryChanges flag to receive this event. Also, there are two similar but different events: QGraphicsItem::ItemPositionChange and QGraphicsItem::ItemPositionHasChanged. The first one is triggered when the item is moving, and the second one is after it finishes moving. The difference is that .pos() returns different values if called in the event callback. For the first event, it returns the original item position, and in the case of the second event, it returns a new position. Both events have new positions in their argument.
More about those events: https://doc.qt.io/qt-6/qgraphicsitem.html#GraphicsItemChange-enum
EDIT:
Based on the comments from the author of the question. After testing, it seems that QGraphicsItem::ItemPositionHasChanged and QGraphicsItem::ItemScenePositionHasChanged (which is useful to the author) are regularly and very often emitted after QGraphicsItem::ItemPositionChange.
In my case, when grabbing the item and shaking the mouse, the QGraphicsItem::ItemPositionChange was emitted exactly 2 times between each QGraphicsItem::ItemScenePositionHasChanged.
The only experimental difference I see between them is that in QGraphicsItem::ItemPositionChange callback, by calling .pos(), you can still access old item position, while in QGraphicsItem::ItemPositionHasChanged and QGraphicsItem::ItemScenePositionHasChanged calling .pos() results in a new item position.
So if for some reason the old item position is needed, then it is sufficient to additionally store it as a member of the derived class and update it in the QGraphicsItem::ItemScenePositionHasChanged callback when you're done.
EDIT 2:
With more information on the problem from the author. My approach would be to implement a "movement check" in the following way. If all of the selected items move together, then when item1 moves, it checks whether item2 is also currently selected (thereby, it also moves) with item1->scene()->selectedItems()->indexOf(item2).
If the result is -1, it means that item2 is not selected, hence, it is not moving and the arrow shape needs to be updated; otherwise, move the arrow along with both items.

Related

How can I detect whether a tooltip is visible at a given moment?

I'm looking for a way to detect whether a Qt widget's ToolTip is visible at the moment when a particular key combination is pressed. If it is, I want to copy the ToolTip's text to the clipboard.
Specifically, I have a QListView containing abbreviated strings, which is set up (via the Qt::ToolTipRole of the associated model) to show the full string of the appropriate list item when the mouse is hovered over it. The behaviour I'm looking for is that if the user presses CTRL-C (as detected by a QShortcut) while the tooltip is visible, then the tooltip text is copied to the clipboard.
My original idea was to use the children() method of the QListView widget to see if there was a tooltip preset among them:
// Inisde the slot connected to QShortcut::activated...
auto children = _ui -> myListView -> children();
QString selectionText;
for (const auto & child : children)
{
if (qobject_cast<QToolTip *>(child))
{
selectionText = qobject_cast<QToolTip *>(child) -> text();
break;
}
}
...but this failed because it turns out that QToolTip does not inherit from QObject.
I've also thought of screening for QEvent::QToolTip events in the ListView's main event handler, and while I could probably get this to work it seems excessively low-level; I'd need to use screen co-ordinates to determine which item in the list was being hovered over and look for the widget's timeout to check that the tooltip hadn't disappeared again by the time that the QShortcut was fired. I'd be disappointed if there weren't a simpler way.
Is there an obvious way forward that I've failed to see?
There are probably several possible solutions, but I am afraid none of them is simple. What I would do is to use the implementation detail that the tooltip actual widget is called QTipLabel. See https://code.woboq.org/qt5/qtbase/src/widgets/kernel/qtooltip.cpp.html#QTipLabel and it inherits from QLabel so you can easily get the text from it.
I am afraid the following solution is just a savage hack. I have not tested it, but it should work.
I would override the data model for your view, specifically override method data() which would call the data() method of the original model class but cache the last value which was returned when this method is called with role == Qt::ToolTipRole.
Then you need to catch the shortcut you are interested in. After it is caught, you get all qApp->topLevelWidgets() https://doc.qt.io/qt-5/qapplication.html#topLevelWidgets` and go through them and check if any of them has class name equal to QTipLabel (use QMetaObject::className()) and is visible, i.e. isVisible() == true.
If you get this visible QTipLabel widget (you hold it via QWidget*), qobject_cast it to QLabel* (you cannot cast it to QTipLabel beause you do not have access to the definition of QTipLabel class because it is in private Qt source file) and get the text with QLabel::text(). If the text is the same as the text which you stored in step 1, then yes, this is the text you are looking for and you can copy it to clipboard or do whatever yo want with it.
Nasty, isn't it? But it is the simplest what I can think of.
PS: I believe that step 1 can be implemented also by catching QEvent::QToolTip for your view and then do some magic to get the text, but I think that overriding data() for model can be a bit easier.
PPS: One obvious drawback is that Qt can rename QTipLabel class in the future. But I would not be worry about it. That won't happen becaus ethey do not change QtWidgets module any more. And if it happens, then you just rename the class in your code. No problem.
PPPS: Another potential corner-case is that some other widget (whose tooltip you do NOT want to capture with that shortcut) actually has the same tooltip text as any of the items in your list view (which you DO want to capture). Then if you display tooltip for your list item, then you move your mouse over to that other widget and hover so that its tooltip gets shown (but you do NOT want to capture it) and then you press that shortcut... But I guess that in reality this will not be your case. I doubt there will be this unlikely clash of tooltips.
With thanks to #V.K., this is what worked:
auto candidates = qApp->topLevelWidgets();
QString selectionText;
for (const auto & candidate : candidates)
{
if (strcmp(candidate->metaObject()->className(), "QTipLabel") == 0)
{
QLabel * label = qobject_cast<QLabel *>(candidate);
if (label->isVisible())
{
selectionText = label -> text();
break;
}
}
}
if (!selectionText.isEmpty())
QGuiApplication::clipboard() -> setText(selectionText);

Aframe “come here” component. Such that the 2nd entity can find the 1rst entity and animate to it no matter where the user puts the 1st entity

How can I create a “come here” component. Such that the 2nd entity can find the 1rst entity and animate to it no matter where the user puts the 1st entity.
I have a button that travels with the user, it’s there when called, it doesn’t move with the camera, it vanishes before teleport and the idea of what I want to add to this is a function where when the user clicks this button (entity ID:One) it will say “come here” to another entity (Entity ID:Two) entity two is listening for entity one to be clicked, and then entity receives entity one’s coordinates, and then entity two travels to entity one.
A longer description of what I am trying to do: Previously I wrote: “How would I write a "come to me" function where one entity calls another to its new location. Lets say that I have a draggable entity, super hands is enabled and the user drags a cube to a new location. Okay so then the user clicks on the cube and a sphere flies to the cube's position but its z (position coordinate z), relative the cube is -1. Then the user drags the cube to another new location, clicks the cube with their laser, and the same sphere from before flies to the cube's new location but its still z -1 relative to the cube. How should I write that? what are the possible approaches? whats the efficient way to do it in aframe? would I create an invisible
child entity on the cube that has a -1 z position? is there a way to look up the global position of a child entity and then tell the sphere entity to update its position to match the child entities position? I would summarize this as a "come to me" function where one entity calls another to its new location.
What I tried: It was suggested that I look at two things 1. The pathfinding component in aframe extras that allows an npc to navigate a mesh during it’s animation, but for this example a simple animation is fine. 2. The follow component (which is 2 years old). The follow component is too abstract for my current level of understanding of aframe code. I don’t understand what it’s following or how to edit it to make it do what I want.
What else I tried: Then I stumbled upon “Interactive image grid in WebVR with A-frame” https://ottifox.com/prototype/2017/09/25/interactive-image-grid-in-webvr.html this makes a camera look up the location of an image, and then move to that image when clicked. Well I thought maybe I can replace the camera with a box and make the box move to another box instead of an image. Well I tried that, and so far it’s not doing anything.
My code is here at https://comehere.glitch.me https://glitch.com/edit/#!/comehere Can you help me solve how to have an entity look up the position of another entity and then animate to it the new location, so that the user can keep updating the position of the new entity, and when clicked the second entity can always move to anywhere the user wants it to move based on where the user puts the first entity.
You can manually move an entity towards a target, like i did here (press the sphere).
The idea is simple:
1) get the target position in a local space
targetPos = someEl.object3D.worldToLocal(target.object3D.position.clone())
2) move one entity towards another
someEl.object3D.translateOnAxis(targetPos, someDistance)
3) repeat until the object is close enough
var distanceFromTarget = currentPosition.distanceTo(target.object3D.position);
if (distanceFromTarget > 1) {
someEl.object3D.translateOnAxis(targetPos, distance);
}
You can throw it into an aframe component:
AFRAME.registerComponent('foo', {
init: function() {
this.target = document.querySelector("#target")
},
tick: function(t, dt) {
var currentPosition = this.el.object3D.position;
var distanceToTarget = currentPosition.distanceTo(target.object3D.position);
if (distanceToTarget < 1) return;
var targetPos = this.el.object3D.worldToLocal(target.object3D.position.clone())
var distance = dt*this.data.speed / 4000;
this.el.object3D.translateOnAxis(targetPos, distance);
}
}
and use it like this:
<a-box foo></a-box>
<a-box id="target"></a-box>
You can use the animation component like you did, by re-setting the to attribute:
// on some event:
this.el.setAttribute("animation", "to", newTargetPosition)
this.el.emit("startAnimation")
But it gets complicated if the target is moving since not only you would need to get the new position, but also the starting (from) position as well.

fullcalendar removeEventSource is not working

I'm using FullCalendar v2.3.0,
When I click a date, background color of the cell is changed as 'its booked', Its working perfectly with addEventSource,(stored selected date in an array)
when im clicking once agian to the selected date, need to change the background color of the cell as white (available for booking),
How to remove the cell color as original?
How to remove the event using .fullCalendar( 'removeEventSource', source ) for a particular date...
select: function (start, end, jsEvent, view, cell, calEvent) {
if (jQuery.inArray(moment(start).format('YYYY-MM-DD'), sel_dates) == -1) {
sel_dates.push(moment(start).format('YYYY-MM-DD'));
$("#calendar").fullCalendar('addEventSource', [{
start: start,
end: end,
rendering: 'background'
}]);
}
else {
sel_dates.pop(moment(start).format('YYYY-MM-DD'));
// Here i need to change the cell background color
}
}
The correct method signature for select is function( start, end, jsEvent, view, [ resource ] ). See https://fullcalendar.io/docs/selection/select_callback. I don't know where you got the idea that it contained either "cell" or "calEvent"?
A selected area could be across multiple cells, so you can't change the background colour directly like this. Anyway, even if you could, this would make no difference, because you'd have to remove the event you previously added that is on top of the cells. An event is not merely some background colour - even a "background" event is actually an object that needs to be managed, although it may have the appearance of just a colour change.
Some suggestions:
1) I wouldn't add a whole event source just to create one event. An event source is intended to split large groups of events into logical blocks to make it easier to manage them, or fetch them from different servers.
Instead use renderEvent to create a new single event. https://fullcalendar.io/docs/event_rendering/renderEvent/. If you have any existing event sources you can optionally specify the ID of one to include it into.
2) To remove the event, as I've explained you can't just revert the background colour, you need to remove the actual event itself. You also can't use the "select" callback for this, because you can't be sure that the user will select the same sized area which actually matches any particular event.
One obvious way to allow deletion is to handle the "eventClick" callback (https://fullcalendar.io/docs/mouse/eventClick/) and ask the user to confirm whether they want to delete. If they agree, you can call the "removeEvents" method (https://fullcalendar.io/docs/event_data/removeEvents/) passing the ID of the event that the user clicked on.

Qt: change QGraphicsItem receiver during mouse move

I am currently trying to implement a Bezier pen tool. The course of events looks like this:
click on point (QGraphicsItem), start moving while clicked
in QGraphicsScene mouseMoveEvent, prevent moves of point (with a boolean flag) until when distance from point.pos() to event.scenePos() reaches a threshold. When this happens unselect and mouseRelease point, add a node (QGraphicsItem) – select it and give it mousePress state (plus unset the boolean flag)
the user can move node after that, then release mouse.
(The node is a child item of the point.)
I tried to do this inside the scene’s mouseMoveEvent (I have a conditional branch to know when to do this):
point.setSelected(False)
point.ungrabMouse()
node.setPos(event.scenePos()-point.pos()) # positioning relative to point since it’s a childItem()
node.grabMouse()
event.accept()
But after doing this it occured that the node was only getting mouseMoveEvent’s after I release the mouse… (I print them in the console, the node itself did not move.)
So I figured, maybe the scene needs to eat a mouseReleaseEvent before sort of "releasing focus". I found an article that is tangent to the subject here.
So then instead of using ungrabMouse()/grabMouse(), I tried this:
mouseRelease = QEvent(QEvent.MouseButtonRelease)
self.sendEvent(point, mouseRelease)
node.setPos(event.scenePos()-point.pos()) # positioning relative to point since it’s a childItem()
mousePress = QEvent(QEvent.MouseButtonPress)
self.sendEvent(node, mousePress)
Now when I reach the distance threshold, I can see that only point gets selected (good) however as I move further both point and node are selected and moving… I would expect that since I have unselected and released (parent) point, it would not keep moving.
The article I linked to does do something different but it says "It turns out, we have to simulate a mouse release event to clear Qt’s internal state." which might be relevant to the current situation however I do not know what extra steps might need to be taken in order to “clear Qt’s internal state”… so I’m hoping a QGraphics aficionado can weigh in and help me out figuring this.
Thanks for having a look here.
A combination of sending mouse events and grabbing mouse manually works… has to be ungrabbed manually on mouseRelease though.

PyQt (or just QT). How to get QComboBox to fire a signal whenever it is set to a value (even if unchanged)

I am using PyQt4, but this is general enough that it could just apply to QT.
I have a series of QComboBoxes that I fill from left to right (i.e. selecting an item in the leftmost will populate the next one. Selecting an item in that one will populate the next, and so on)
I am having difficulty getting my signals to fire under all situations (i.e. regardless of whether the current index changes or not AND regardless of whether the item is set by the user or set programatically).
More detail:
I rely on the signals of the first QCombox to fire whenever an item is selected so that I can populate the next QCombobox in the gui. I then rely on THAT QCombobox to emit a signal so that I can populate the next one. And so on.
I want to pre-select an item in each QCombobox based on the user's last interaction with the gui.
I have a unique function per QCombobox that is responsible for populating and pre-selecting just that QCombobox. The code looks something like this:
comboBox1.blockSignals(True)
comboBox1.clear()
comboBox1.addItems(sorted(itemList))
comboBox1.blockSignals(False)
comboBox1.setCurrentIndex(intLastSavedState1)
where intLastSavedState1 is an integer that is derived from the text that was last selected by the user the last time they had used the app. I had hoped that the last line of this function would fire a signal that would cause the next combo box's function to load and pre-select an item (comboBox2). And that action would then cause the next comboBox's function to activate and it would cascade to the next and the next. But it is not working across all cases.
I have tried two versions of the signals:
self.connect(comboBox1, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.load_comboBox2)
and
self.connect(comboBox1, QtCore.SIGNAL("activated(const QString&)"), self.load_comboBox2)
In the first case, the signal will fire only if the intLastSavedState1 is different than whatever is currently selected in the combo box. This causes an issue if the user had last selected item 0 from that list. In this case QT does not recognize my script setting the the current index to 0 as being a change (since after loading the box it appears to think it is already on index 0), and so the signal does not fire.
In the second case, the signal will fire regardless of what is currently selected in the combo box... but only if activated by the user. It will not fire when my script tries to set the current index programatically.
These appear to be my only two options regarding the signals. So... is there another way of pre-selecting items in a QCombobox that will trigger a signal each and every time?
Well... sometimes just the act of asking a question can lead you to a (partial) answer.
I have a work-around but I am still interested in hearing if someone has a better idea.
I am now programatically setting the index of the QCombobox to -1 immediately after loading it up. Then, when I programatically set the actual index based on the user's history, it will always be considered a change (i.e. it will never be -1) and the signal will fire
using: currentIndexChanged(const QString&)
So my code looks like this now:
comboBox1.blockSignals(True)
comboBox1.clear()
comboBox1.addItems(sorted(itemList))
comboBox1.setCurrentIndex(-1)
comboBox1.blockSignals(False)
comboBox1.setCurrentIndex(intLastSavedState1)
and my signal looks like this:
self.connect(comboBox1, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.load_comboBox2)
This functions... does anyone have a better idea?
Thanks agian.
You can check current Index of your ComboBox and then either call your slot or do call setCurrentIndex().
Example:
if comboBox1.currentIndex() == index:
self.load_comboBox2(index)
else
comboBox1.setCurrentIndex(index)
This way you will not end up calling slot twice.

Resources