QBasicTimer can be used with threads started with QThread when selecting widget.setFocus(True) - pyqt5

I always get this error message.
QBasicTimer can be used with threads started with QThread when widget.setFocus()
Basically, I have a class Rotary which is listening to GPIO.
it has myForm.dial_on_value_changed() registered as a callback to Rotary which will call dial_on_value_changed() when rotator is rotated.
If myForm.dial_on_value_changed() only prints a text or string then it is fine. But if it selected one of myForm element and change its state such as setFocus(True), it will throw the above error, smth related with QThread.
My Application is a single thread and Rotari itself works based on listening to an interrupt in a GPIO pin which will call the registered callback function.
can someone tell me or guide me what I have done wrong? Or maybe a better way to solve this problem which is "A knob which will rotate and set all GUI element to be focused"
thanks in advance.
This is my snippet code
class MyForm(QDialog):
### creating a circular list
self._vt_dial_cycle = cycle([
# tuple UI element with a function
(self.vt_textedit, self.dial_to_vt),
(self.rr_textedit, self.dial_to_rr),
(self.stop_button, None),
(self.start_button, None),
(self.ie_textedit, self.dial_to_ie)
])
def dial_to_vt(self):
print("Dial on vt")
def dial_to_rr(self):
print("Dial on rr")
def dial_to_ie(self):
print("Dial on ie")
def dial_on_value_changed(self, direction):
# get next element from circular list.
(active_widget, action) = next(self._vt_dial_cycle)
self._current_widget = (active_widget, action)
#execute action manually registered with widget
action()
if isinstance(active_widget, QTextEdit):
self.move_cursor_to_end(active_widget)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MyForm()
w.show()
# ... a rotari with a knop
ky040 = KY040(CLOCKPIN, DATAPIN, SWITCHPIN, w.dial_on_value_changed, w.dial_on_pressed)
ky040.start()

Related

Understanding Qt5 / PyQt5 focus behavior

I have a very simple test application:
from PyQt5.QtWidgets import *
import sys
import time
class Example(QWidget):
def __init__(self):
super().__init__()
self.pbar = QProgressBar(self)
self.pbar.setGeometry(30, 40, 200, 25)
self.btn = QPushButton('Start', self)
self.btn.move(40, 80)
self.btn.clicked.connect(self.do_action)
self.txt = QLineEdit('Some info goes here', self)
self.txt.setReadOnly(True)
self.txt.move(40, 120)
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle("Python")
self.show()
def do_action(self):
# setting for loop to set value of progress bar
self.btn.setDisabled(True)
for i in range(101):
time.sleep(0.05)
self.pbar.setValue(i)
self.btn.setEnabled(True)
if __name__ == '__main__':
App = QApplication(sys.argv)
window = Example()
sys.exit(App.exec())
I have two, possibly related, problems with this code:
If I click on btn it dutifully starts QProgressBar updating and disables itself, but if I click on it twice I will see two iterations of the pbar; shouldn't clicks on a disabled widget be ignored?
As soon as btn is disabled txt contents are selected, in spite of it being ReadOnly; is there some way to prevent this "focusing next" behavior? Leaving everything unfocused till next click would be best.
Is this behavior considered "normal"?
The first issue is due to the fact that you used a blocking function (which should never happen) in event-driven systems like ui frameworks. What happens is that the for loop with the sleep function prevents Qt to correctly process events, including input events: your second click gets "queued" and can only be processed as soon as the control is returned to the event manager (when the function finally returns), and since, at that point, you've re-enabled the button, only then the previously queud event gets finally processed (and the function is called again).
The solution is simple: avoid any situation like this, if you need a time-based iteration, use a QTimer, if you need parallel and non blocking processing, use a QThread.

QLayout.replace not replacing

I have the following code to replace a widget (self.lbl) each time I click on a button (self.btn):
import sys
from PySide2.QtCore import Slot
from PySide2.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget, \
QPushButton
class Workshop(QWidget):
def __init__(self):
super().__init__()
self.n = 0
self.btn = QPushButton('Push me')
self.lbl = QLabel(str(self.n))
self.main_layout = QVBoxLayout()
self.sub_layout = QVBoxLayout()
self.sub_layout.addWidget(self.lbl)
self.sub_layout.addWidget(self.btn)
self.main_layout.addLayout(self.sub_layout)
self.btn.clicked.connect(self.change_label)
self.setLayout(self.main_layout)
self.show()
#Slot()
def change_label(self):
new_label = QLabel(str(self.n + 1))
self.main_layout.replaceWidget(self.lbl, new_label)
self.n += 1
self.lbl = new_label
if __name__ == '__main__':
app = QApplication()
w = Workshop()
sys.exit(app.exec_())
Right after its initialization, the object w looks like this:
When I click on the "Push me" button (self.btn), the number is incremented as wanted, but the initial "0" remains in the background:
But the other numbers do not however remain in the background ; only "0" does. Fore example, here is "22" (result after I clicked 22 times on "Push me"):
Note: I know that I could achieve the resultant I want with the setText method, but this code is just a snippet that I will adapt for a class in which I will not have a method like setText.
Thank you!
When you replace the widget in the layout, the previous one still remains there.
From replaceWidget():
The parent of widget from is left unchanged.
The problem is that when a widget is removed from a layout, it still keeps its parent (in your case, the Workshop instance), so you can still view it. This is more clear if you set the alignment to AlignCenter for each new QLabel you create: you'll see that if you add a new label and resize the window, the previous one will keep its previous position:
class Workshop(QWidget):
def __init__(self):
# ...
self.lbl = QLabel(str(self.n), alignment=QtCore.Qt.AlignCenter)
# ...
def change_label(self):
new_label = QLabel(str(self.n + 1), alignment=QtCore.Qt.AlignCenter)
# ...
You have two possibilities, which are actually very similar:
set the parent of the "removed" widget to None: the garbage collector will remove the widget as soon as you overwrite self.lbl:
self.lbl.setParent(None)
remove the widget by calling deleteLater() which is what happens when reparenting a widget to None and, if it has no other persisting references, gets garbage collected:
self.lbl.deleteLater()
For your pourposes, I'd suggest you to go with deleteLater(), as calling setParent() (which is a reimplementation of QObject's setParent) actually does lots of other things (most importantly, checks the focus chain and resets the widget's window flags), and since the widget is going to be deleted anyway, all those things are actually unnecessary, and QObject's implementation of setParent(None) would be called anyway.
The graphic "glitch" you are facing might depend on the underlying low-level painting function, which has some (known) unexpected behaviors on MacOS in certain cases.

Google Colab session timeout

In the FAQs it is mentioned that
Virtual machines are recycled when idle for a while, and have a maximum lifetime enforced by the system.
Are the maximum lifetime and idle times fixed or variable? Is there any way to predict them?
PROBLEM:
I have to training my model for hours but the google colab keeps disconnecting after 30 mins automatically if I do not click frequently, leading to loss of all data.
SOLUTION:
Steps:
Open the inspector view by typing Ctrl+ Shift + i and then clicking on console tab at top.
Paste the below code snippet at bottom of console and hit enter
function ClickConnect(){
console.log("Working");
document.querySelector("#top-toolbar > colab-connect-button").shadowRoot.querySelector("#connect").click();
}
setInterval(ClickConnect,60000)
Believe me, that's all folks.
Above code would keep on clicking the page and prevent it from disconnecting.
Below is the image showing console view of above steps:-
Alternatively you can also try below snippet:
interval = setInterval(function() {
console.log("working")
var selector = "#top-toolbar > colab-connect-button"
document.querySelector(selector).shadowRoot.querySelector("#connect").click()
setTimeout(function() {
document.querySelector(selector).shadowRoot.querySelector("#connect").click()
}, 1000)
}, 60*1000)
It's 90 minutes if you close the browser. 12 hours if you keep the browser open. Additionally, if you close your browser with a code cell is running, if that same cell has not finished, when you reopen the browser it will still be running (the current executing cell keeps running even after browser is closed)
Improving to #Ashish Anand's answer
Use this code when you want to start:
function ClickConnect(){
console.log("Working");
document.querySelector("#top-toolbar > colab-connect-button").shadowRoot.querySelector("#connect").click();
}
var clicker = setInterval(ClickConnect,60000);
And the following code when you need to stop:
clearInterval(clicker);
Another way to overcome the session timeout is to run an autoclick script in python (especially if you intend not to use your computer while running your code in colab)
Here is the code : (Be sure to pip install pynput before)
import threading
from pynput.mouse import Button, Controller
from pynput.keyboard import Listener, KeyCode
delay = 20 #this is the delay of the autoclick (20 seconds here)
button = Button.left
start_stop_key = KeyCode(char='s')
exit_key = KeyCode(char='e')
class ClickMouse(threading.Thread):
def __init__(self, delay, button):
super(ClickMouse, self).__init__()
self.delay = delay
self.button = button
self.running = False
self.program_running = True
def start_clicking(self):
self.running = True
def stop_clicking(self):
self.running = False
def exit(self):
self.stop_clicking()
self.program_running = False
def run(self):
while self.program_running:
while self.running:
mouse.click(self.button)
time.sleep(self.delay)
time.sleep(0.1)
mouse = Controller()
click_thread = ClickMouse(delay, button)
click_thread.start()
def on_press(key):
if key == start_stop_key:
if click_thread.running:
click_thread.stop_clicking()
else:
click_thread.start_clicking()
elif key == exit_key:
click_thread.exit()
listener.stop()
with Listener(on_press=on_press) as listener:
listener.join()
Run this script on a commandline window, and then press the key "s" to start autoclicking and "e" for exit, than leave the mouse pointer on a code cell (normally it will click after a certain delay).

Moving a QGraphicsProxyWidget with ItemIgnoresTransformations after changing QGraphicsView scale

I have a QGraphicsScene that contains multiple custom QGraphicsItems. Each item contains a QGraphicsProxyWidget which itself contains whatever widgets are needed by the business logic. The proxy has a Qt::Window flag applied to it, so that it has a title bar to move it around. This is all working well, except when moving a proxy widget when the view has been scaled.
The user can move around the scene à la google maps, ie by zooming out then zooming in back a little farther away. This is done with calls to QGraphicsView::scale. Items should always be visible no matter the zoom value, so they have the QGraphicsItem::ItemIgnoresTransformations flag set.
What happens when moving a proxyWidget while the view has been scaled is that on the first move event the widget will jump to some location before properly being dragged.
I had this issue with Qt5.7.1, and could reproduce it with PyQt5 as it is simpler to reproduce and hack around, please see the snippet below.
Steps to reproduce:
move the widget around, notice nothing unusual
use the mouse wheel to zoom in or out. The higher the absolute scale, the higher the effect on the issue.
click on the widget, and notice how it jumps on the first moving of the mouse.
Snippet:
import sys
import PyQt5
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsProxyWidget, QGraphicsWidget, QGraphicsObject
global view
global scaleLabel
def scaleScene(event):
delta = 1.0015**event.angleDelta().y()
view.scale(delta, delta)
scaleLabel.setPlainText("scale: %.2f"%view.transform().m11())
view.update()
if __name__ == '__main__':
app = QApplication(sys.argv)
# create main widget
w = QWidget()
w.resize(800, 600)
layout = QVBoxLayout()
w.setLayout(layout)
w.setWindowTitle('Example')
w.show()
# rescale view on mouse wheel, notice how when view.transform().m11() is not 1,
# dragging the subwindow is not smooth on the first mouse move event
w.wheelEvent = scaleScene
# create scene and view
scene = QGraphicsScene()
scaleLabel = scene.addText("scale: 1")
view = QGraphicsView(scene)
layout.addWidget(view)
view.show();
# create item in which the proxy lives
item = QGraphicsWidget()
scene.addItem(item)
item.setFlag(PyQt5.QtWidgets.QGraphicsItem.ItemIgnoresTransformations)
item.setAcceptHoverEvents(True)
# create proxy with window and dummy content
proxy = QGraphicsProxyWidget(item, Qt.Window)
button = QPushButton('dummy')
proxy.setWidget(button)
# start app
sys.exit(app.exec_())
The jump distance is:
proportional to the scaling of the view , and to the distance of the mouse from the scene origin
goes from scene position (0,0) towards the mouse position (I think)
might be caused by the proxy widget not reporting the mouse press/move properly. I'm hinted at this diagnostic after looking at QGraphicsProxyWidgetPrivate::mapToReceiver in qgraphicsproxywidget.cpp (sample source), which does not seem to take scene scaling into account.
I am looking for either
confirmation that this is an issue with Qt and I did not misconfigured the proxy.
an explanation on how fix the mouse location given by the proxy to its children widgets (after installing a eventFilter)
any other workaround
Thanks
Almost 2 years later I got back to this issue again, and finally found a solution. Or rather a workaround, but a simple one at least. It turns out I can easily avoid getting into the issue with local/scene/ignored transforms in the first place.
Instead of parenting the QGraphicsProxyWidget to a QGraphicsWidget, and explicitly setting the QWidget as proxy target, I get the proxy directly from the QGraphicsScene, letting it set the window flag on the wrapper, and set the ItemIgnoresTransformations flag on the proxy. Then (and here's the workaround) I install an event filter on the proxy, intercept the GraphicsSceneMouseMove event where I force the proxy position to currentPos+mouseDelta (both in scene coordinates).
Here's the code sample from above, patched with that solution:
import sys
import PyQt5
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import *
global view
global scaleLabel
def scaleScene(event):
delta = 1.0015**event.angleDelta().y()
view.scale(delta, delta)
scaleLabel.setPlainText("scale: %.2f"%view.transform().m11())
view.update()
class ItemFilter(PyQt5.QtWidgets.QGraphicsItem):
def __init__(self, target):
super(ItemFilter, self).__init__()
self.target = target
def boundingRect(self):
return self.target.boundingRect()
def paint(self, *args, **kwargs):
pass
def sceneEventFilter(self, watched, event):
if watched != self.target:
return False
if event.type() == PyQt5.QtCore.QEvent.GraphicsSceneMouseMove:
self.target.setPos(self.target.pos()+event.scenePos()-event.lastScenePos())
event.setAccepted(True)
return True
return super(ItemFilter, self).sceneEventFilter(watched, event)
if __name__ == '__main__':
app = QApplication(sys.argv)
# create main widget
w = QWidget()
w.resize(800, 600)
layout = QVBoxLayout()
w.setLayout(layout)
w.setWindowTitle('Example')
w.show()
# rescale view on mouse wheel, notice how when view.transform().m11() is not 1,
# dragging the subwindow is not smooth on the first mouse move event
w.wheelEvent = scaleScene
# create scene and view
scene = QGraphicsScene()
scaleLabel = scene.addText("scale: 1")
view = QGraphicsView(scene)
layout.addWidget(view)
view.show();
button = QPushButton('dummy')
proxy = scene.addWidget(button, Qt.Window)
proxy.setFlag(PyQt5.QtWidgets.QGraphicsItem.ItemIgnoresTransformations)
itemFilter = ItemFilter(proxy)
scene.addItem(itemFilter)
proxy.installSceneEventFilter(itemFilter)
# start app
sys.exit(app.exec_())
Hoping this may help someone who's ended up in the same dead end I was :)

react on events generated by chaco tools: how to get values out of a chaco tool when an event is fired ?

actually this should be a pretty simple question, but I am experiencing the quite steep learning curve of chaco and traits...
I am currently writing an application to plot a medical image using chaco and traits and I simply want to pick a pixel location from the image and use this pixel location to do evaluations on an image stack. So I started to write my own Chaco Tool that reacts on mouse clicks on an imageplot.
This works fine so far. When I click on the imageplot I can see the mouse coordinates WITHIN the Tool (a custom made PixelPickerTool). However, as I want to use this coordinate value outside the tool my question would be: How can I hand the coordinates over to another object or variable OUTSIDE the Tool when an event is fired.
To illustrate what I want to do I attached the main structure of the two classes I am Writing:
class PixelPickerTool(BaseTool):
'''Pick a Pixel coordinate from an image'''
ImageCoordinates = [0,0]
def normal_left_down(self, event):
print "Mouse:", event.x, event.y,
click_x, click_y = self.component.map_data((event.x, event.y))
img_x = int(click_x)
img_y = int(click_y)
coord = [img_x, img_y]
if ( (img_x > self.ImageSizeX) or (img_x < 0) ):
coord = [0,0]
if ( (img_y > self.ImageSizeY) or (img_y < 0) ):
coord = [0,0]
print coord
# this print gives out the coordinates of the pixel that was clicked - this works fine...
# so inside the picker too I can get the coordinates
# but how can I use the coordinates outside this tool ?
class ImagePlot(HasTraits):
# create simple chaco plot of 2D numpy image array, with a simple interactor (PixelPickerTool)
plot = Instance(Plot)
string = String("hallo")
picker = Instance(PixelPickerTool)
traits_view = View(
Item('plot', editor=ComponentEditor(), show_label=False,width=500, height=500, resizable=False),
Item('string', show_label=False, springy=True, width=300, height=20, resizable=False),
title="")
def __init__(self, numpyImage):
super(ImagePlot, self).__init__()
npImage = np.flipud(np.transpose(numpyImage))
plotdata = ArrayPlotData(imagedata = npImage)
plot = Plot(plotdata)
plot.img_plot("imagedata", colormap=gray)
self.plot = plot
# Bild Nullpunkt ist oben links!
self.plot.default_origin = 'top left'
pixelPicker = PixelPickerTool(plot)
self.picker = pixelPicker
plot.tools.append(pixelPicker)
I want to use the coordinates that are measured by the PixelPickerTool somewhere in this ImagePlot class. E.g. by handing them over to another Object like MyImageSeries.setCoordinate(xy_coordinateFromPickerTool)
So how can I hand over the pixel coordinates from PickerTool to some member variable in this class when an event is fired ?
Maybe something like this: self.PixelCoordinates = picker.getPixelCoordinates() could work ?
But how do I know then, when the on_normal_left_down function was executed in the picker ?
In the end I want to hand the coordinates over to another class which hold more images to process the images and do a fit at the pixel position determined in the ImagePlot.
I tried to use something like "_picker_changed" in my imagePlot class to detect if an event has been fired in the PickerTool, but this didn't detect event firing. So maybe I am doing something wrong...
Can anybody tell me how to get events and associated variables out of this picker tool ?
Cheers,
Andre
"But how do I know then, when the on_normal_left_down function was executed in the picker?"
There are several ways you could probably do this, but one way would be to simply do exactly what you are asking and fire an event that you define explicitly.
for instance:
from traits.api import Event
class PickerTool(BaseTool):
last_coords = SomeTrait
i_fired = Event
def normal_left_down(self,event):
# do whatever necessary processing
self.last_coords = do_some_stuff(event.some_attribute)
# now notify your parent
self.i_fired = True
and then listen to plot.picker.i_fired from wherever you want to display, and look in plot.picker.last_coords for the saved state.
Another thing you can do that may be simpler if what you want to do with these coordinates is very straightforward, is just pass on intialization the data structures the picker needs to interact with (or get them with a chain of calls to self.parent) and do your work directly inside the picker.