I'm building a PyGTK application with several widgets that when changed, need to notify other widgets about the change. I would like to avoid code like this:
def on_entry_color_updated(self, widget):
self.paint_tools_panel.current_color_pane.update_color()
self.main_window.status_bar.update_color()
self.current_tool.get_brush().update_color()
And do something like this instead:
def on_entry_color_updated(self, widget):
self.update_notify('color-changed')
The status bar, current color pane and current tool would subscribe to that notification event and act accordingly. From what I can tell, the GObject signaling mechanism only allows me to register a callback on a particular widget, so each object that wants to receive a notification has to be aware of that widget.
Does GTK provide such a system or should I build it myself? Developers of Shotwell, a photo organization application for GNOME, had to build their own signaling mechanism, if I understand their design doc correctly. Searching here on SO didn't turn out any definitive answers.
Edit:
Clarification why I think GObject signaling is not what I need (or just a part of what I need). With GObject, I need to explicitly connect an object to another object, like so:
emitter.connect('custom-event', receiver.event_handler)
So in my application, I would have to do this:
class ColorPane(gtk.Something):
def __init__(self, application):
# init stuff goes here...
application.color_pallette.connect('color-changed', self.update_color)
def update_color(self, widget):
"""Show the new color."""
pass
class StatusBar(gtk.Something):
def __init__(self, application):
# init stuff goes here...
application.color_pallette.connect('color-changed', self.update_color)
def update_color(self, widget):
"""Show the new color name."""
pass
class Brush(gtk.Something):
def __init__(self, application):
# init stuff goes here...
application.color_pallette.connect('color-changed', self.update_color)
def update_color(self, widget):
"""Draw with new color."""
pass
In other words, I have to pass the application object or some other object that knows about the color_pallete to other objects in my application so that they connect to color_pallette signals. This is the kind of coupling that I want to avoid.
For one, you could create a custom subclass of GObject, which provides some custom signals. The following example is a slightly adapted version of the one given in the linked article:
import pygtk
pygtk.require('2.0')
import gobject
class Car(gobject.GObject):
__gsignals__ = {
'engine-started': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
'engine-stopped': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
}
def __init__(self):
gobject.GObject.__init__(self)
self._state = 0
def start(self):
if not self._state:
self._state = 1
self.emit('engine-started')
def stop(self):
if self._state:
self._state = 0
self.emit('engine-stopped')
gobject.type_register(Car)
def kill_switch(c):
def callback(*unused, **ignored):
c.stop()
return callback
def on_start(*unused, **ignored):
print "Started..."
def on_stop(*unused, **ignored):
print "Stopped..."
some_car = Car()
some_car.connect('engine-started', on_start)
some_car.connect('engine-started', kill_switch(some_car))
some_car.connect('engine-stopped', on_stop)
some_car.start()
Another approach would be to take advantage of one of the many event/signalling packages already on PyPI, for example:
Zope Event
Louie
PyDispatcher
Darts Events
Trellis
GObjects don't have to be widgets. For example, your application class can be a GObject which defines signals that other widgets connect to.
Also, I don't think you understood the Shotwell design document correctly. It looks to me like their signalling system is 100% GObject signalling system, just with particular guarantees about the order in which signals are handled. As they say in their design document, such things are possible in plain GObject, but Vala makes it easier to code it their way.
Related
I am building a simple state machine, which manipulates the __class__ attribute to identify which state we are in. This follows recipe 8.19 in "Python Cookbook, D. Beazley". The problem is when I use this recipe with PyQt signals, the state machine is not able to track in which __class__ it belongs.
Here is a simplification of the state machine Interface I am writing:
from PyQt5 import QtCore, QtWidgets
class Interface:
def __init__(self):
self.new_state(DisabledState)
def enable(self):
raise NotImplementedError
def disable(self):
raise NotImplementedError
def new_state(self, newstate):
self.__class__ = newstate
class EnabledState(Interface):
def enable(self):
print('enable/enabled state')
def disable(self):
print('disable/enabled state')
self.new_state(DisabledState)
class DisabledState(Interface):
def enable(self):
print('enable/disabled state')
self.new_state(EnabledState)
def disable(self):
print('disable/disabled state')
When I enable then disable this state machine as so:
class Thread(QtCore.QObject):
enable = QtCore.pyqtSignal()
disable = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
self.interface = Interface()
self.enable.connect(self.interface.enable)
self.disable.connect(self.interface.disable)
myThread = Thread()
app = QtWidgets.QApplication([])
myThread.enable.emit()
myThread.disable.emit()
app.exec_()
In this code above, I first enable the disabled interface and move to enabled state. Then from enabled state I disable the interface, so the expected output is:
# enable/disabled state
# disable/enabled state
However, I get the following where it looks like interface never went to enabled state.
# enable/disabled state
# disable/disabled state
Can anyone explain to me what is happening here?
P.S.
The book describes another recipe which does not require modification of the __class__ attribute and no inheritance. That recipe does indeed give the correct output...
When you pass self.interface.enable to .connect(), it references DisabledState.enable, so it will always behave like it is disabled. To fix this, you could use lambda: self.interface.enable(), and similar for disable.
New to SO, please forgive any etiquette errors (point out if there are!).
I'm working on a script that is running on the programs main UI thread. That being said, I need to avoid all blocking calls to ensure user can still interact. I do not have access to the UI event loop so any busy loop solutions aren't possible in my situation.
I have a simple background thread that is communicating to another app and gathering data, and storing in a simple array for consumption. Each time this data is updated I need to use the data to modify the UI (this must run in main thread). Ideally the background thread would emit a signal each time the data is updated then in the main thread a listener would handle this and modify the UI. A busy loop is not an option everything must be asyncronous/event based.
I have the data gathering continuossly running in the background using threading.timer(..). However since this runs in a seperate thread, the UI operations need to be called externally to this.
def _pollLoop(self):
if (self._isCubeControl):
self._getNewData()
#in a perfect world, updateUI() would be here
self._pollThread = threading.Timer(0.1,self._pollLoop)
self._pollThread.start()
I need a way for this pollLoop to callback to main thread so I can update the UI. I've tried direct callbacks within the pollLoop but the callback are ran within the seperate thread causing errors.
Looking for a way to attach listener to the data object so that on change updateUI() can be ran IN MAIN THREAD.
Thanks for any help you can offer! If this is at all vague please let me know
Update
Based off of #CAB's answer I'm now trying to implement an Observer Pattern. The difficulty is that the Observable is to be ran in a spawned thread while the Observer update function must run in the main thread. I've implemented the example chad lung (http://www.giantflyingsaucer.com/blog/?p=5117).
import threading
class Observable(object):
def __init__(self):
self.observers = []
def register(self, observer):
if not observer in self.observers:
self.observers.append(observer)
def unregister(self, observer):
if observer in self.observers:
self.observers.remove(observer)
def unregister_all(self):
if self.observers:
del self.observers[:]
def update_observers(self, *args, **kwargs):
for observer in self.observers:
observer.update(*args, **kwargs)
thread = threading.Timer(4,self.update_observers).start()
from abc import ABCMeta, abstractmethod
class Observer(object):
__metaclass__ = ABCMeta
#abstractmethod
def update(self, *args, **kwargs):
pass
class myObserver(Observer):
def update(self, *args, **kwargs):
'''update is called in the source thread context'''
print(str(threading.current_thread()))
observable = Observable()
observer = myObserver()
observable.register(observer)
observable.update_observers('Market Rally', something='Hello World')
What I get in response is:
<_MainThread(MainThread, started 140735179829248)>
<_Timer(Thread-1, started 123145306509312)>
<_Timer(Thread-2, started 123145310715904)>
So clearly the Observer is running in the spawned thread and not main. Anyone have another method for me? :) Once again I cannot have a busy loop to periodically check for value change (i wish.. :( ) This script is running overtop a UI and I do not have access to the GUI event loop, so everything needs to be asynchronous and non-blocking.
Let's build on that example from http://www.giantflyingsaucer.com/blog/?p=5117.
from abc import ABCMeta, abstractmethod
class Observer(object):
__metaclass__ = ABCMeta
#abstractmethod
def update(self, *args, **kwargs):
pass
The onus is then on the Observer implementation to disconnect the threads. Let's say we do this using a simplistic thread. (syntax might be off, I'm cramming this in and need to catch a bus).
from observer import Observer
from threading import Thread
class myObserver(Observer):
def update(self, *args, **kwargs):
'''update is called in the source thread context'''
Thread(target=self.handler, args=(self,*args), kwargs=**kwargs).start()
def handler(self, *args, **kwargs):
'''handler runs in an independent thread context'''
pass # do something useful with the args
I'm working on a GUI application, developed in Python and its UI library : PySide2 (Qt wrapper for Python)
I have a heavy computation function I want to put on another thread in order to not freeze my UI. The Ui should show "Loading" and when the function is over, receive from it it's results and update the UI with it.
I've tried a lot of different codes, a lot of examples are working for others but not me, is it PySide2 fault ? (For example this is almost what I want to do : Updating GUI elements in MultiThreaded PyQT)
My code is :
class OtherThread(QThread):
def __init__(self):
QThread.__init__(self)
def run(self):
print 'Running......'
self.emit(SIGNAL("over(object)"), [(1,2,3), (2,3,4)])
#Slot(object)
def printHey( obj):
print 'Hey, I\'ve got an object ',
print obj
thr = OtherThread()
self.connect(thr,SIGNAL("over(object)"),printHey)
thr.start()
My code is working if I use primitives such as bool or int but not with object. I see 'Running....' but never the rest.
Hope someone can enlighten me
You can't define signals dynamically on a class instance. They have to be defined as class attributes. You should be using the new-style signals and slot syntax.
class OtherThread(QThread):
over = QtCore.Signal(object)
def run(self):
...
self.over.emit([(1,2,3), (2,3,4)])
class MyApp(QtCore.QObject)
def __init__(self):
super(MyApp, self).__init__()
self.thread = OtherThread(self)
self.thread.over.connect(self.on_over)
self.thread.start()
#QtCore.Slot(object)
def on_over(self, value):
print 'Thread Value', value
I am trying to create an "auto refresh" tool for ArcMap, to refresh the DataFrame. I believe version 10 had an add-on you could download for this purpose.. however we are running 10.1 at work and there is no such tool.
EDIT wxPython's timer should work, however using wx in arc is tricky. Here's what the code looks like currently:
import arcpy
import pythonaddins
import os
import sys
sMyPath = os.path.dirname(__file__)
sys.path.insert(0, sMyPath)
WATCHER = None
class WxExtensionClass(object):
"""Implementation for Refresher_addin.extension (Extension)"""
_wxApp = None
def __init__(self):
# For performance considerations, please remove all unused methods in this class.
self.enabled = True
def startup(self):
from wx import PySimpleApp
self._wxApp = PySimpleApp()
self._wxApp.MainLoop()
global WATCHER
WATCHER = watcherDialog()
class RefreshButton(object):
"""Implementation for Refresher_addin.button (Button)"""
def __init__(self):
self.enabled = True
self.checked = False
def onClick(self):
if not WATCHER.timer.IsRunning():
WATCHER.timer.Start(5000)
else:
WATCHER.timer.Stop()
class watcherDialog(wx.Frame):
'''Frame subclass, just used as a timer event.'''
def __init__(self):
wx.Frame.__init__(self, None, -1, "timer_event")
#set up timer
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onTimer, self.timer)
def onTimer(self, event):
localtime = time.asctime( time.localtime(time.time()) )
print "Refresh at :", localtime
arcpy.RefreshActiveView()
app = wx.App(False)
You will notice the PySimpleApp stuff in there. I got that from the Cederholm's presentation. I am wondering if I am misunderstanding something though. Should I create an entirely separate addin for the extension? THEN, create my toolbar/bar addin with the code I need? I ask this because I don't see the PySimpleApp referenced in your code below, or any importing from wx in the startup override method either... which I thought was required/the point of all this. I do appreciate your help. Please let me know what you see in my code.
You can't do this the way you are trying, because time.sleep will block and lock up the entire application. Python addins in ArcGIS is pretty new stuff, and there's a lot of functionality that hasn't been implemented yet. One of these is some kind of update or timer event like you get in .NET and ArcObjects. You might think of using threading.Thread and threading.Event in a case like this, but nothing to do with threads will work in the Python addin environment. At least I can't get it to work. So what I've done in situations like this is use wxPython and the Timer class. The code below will work if the addin is set up correctly.
import time
import os, sys
import wx
import arcpy
mp = os.path.dirname(__file__)
sys.path.append(mp)
WATCHER = None
class LibLoader1(object):
"""Extension Implementation"""
def __init__(self):
self.enabled = True
def startup(self):
global WATCHER
WATCHER = watcherDialog()
class ButtonClass5(object):
"""Button Implementation"""
def __init__(self):
self.enabled = True
self.checked = False
def onClick(self):
if not WATCHER.timer.IsRunning():
WATCHER.timer.Start(5000)
else:
WATCHER.timer.Stop()
class watcherDialog(wx.Frame):
'''Frame subclass, just used as a timer event.'''
def __init__(self):
wx.Frame.__init__(self, None, -1, "timer_event")
#set up timer
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.onTimer, self.timer)
def onTimer(self, event):
localtime = time.asctime( time.localtime(time.time()) )
print "Refresh at :", localtime
arcpy.RefreshActiveView()
app = wx.App(False)
Make an extension addin, with a toolbar and a button class. Override the startup method of the extension as shown above. That will create an instance of a Frame subclass with a timer. Then, whenever you click the button on the toolbar, the timer will toggle on or off. The Timer argument is in milliseconds, so the code as shown will refresh every 5 seconds.
You can read more about using wxPython in addins here. Pay particular attention to MCederholm's posts, like about the print statement not working.
EDIT
The code uses a startup method override of the addin extension class. This method is supposed to run when Arcmap starts, but it seems from your comments that this startup method is failing to run on startup. That's possible if you don't create your addin just right, but it works fine for me in my tests. If you continue to get "AttributeError: 'NoneType' object has no attribute 'timer'", then change the onClick method of your button class like so:
def onClick(self):
if WATCHER is None:
global WATCHER
WATCHER = watcherDialog()
if not WATCHER.timer.IsRunning():
WATCHER.timer.Start(5000)
else:
WATCHER.timer.Stop()
The first 3 lines check to make sure that the WATCHER variable has been set to an instance of watcherDialog and is not still set to None. Don't know why your startup method is not running, but hopefully this will fix things for you.
You can use either the RefreshTOC or RefreshActiveView Method. Just add a timer
I'm working on a project in Tornado that relies heavily on the asynchronous features of the library. By following the chat demo, I've managed to get long-polling working with my application, however I seem to have run into a problem with the way it all works.
Basically what I want to do is be able to call a function on the UpdateManager class and have it finish the asynchronous request for any callbacks in the waiting list. Here's some code to explain what I mean:
update.py:
class UpdateManager(object):
waiters = []
attrs = []
other_attrs = []
def set_attr(self, attr):
self.attrs.append(attr)
def set_other_attr(self, attr):
self.other_attrs.append(attr)
def add_callback(self, cb):
self.waiters.append(cb)
def send(self):
for cb in self.waiters:
cb(self.attrs, self.other_attrs)
class LongPoll(tornado.web.RequestHandler, UpdateManager):
#tornado.web.asynchronous
def get(self):
self.add_callback(self.finish_request)
def finish_request(self, attrs, other_attrs):
# Render some JSON to give the client, etc...
class SetSomething(tornado.web.RequestHandler):
def post(self):
# Handle the stuff...
self.add_attr(some_attr)
(There's more code implementing the URL handlers/server and such, however I don't believe that's necessary for this question)
So what I want to do is make it so I can call UpdateManager.send from another place in my application and still have it send the data to the waiting clients. The problem is that when you try to do this:
from update import UpdateManager
UpdateManager.send()
it only gets the UpdateManager class, not the instance of it that is holding user callbacks. So my question is: is there any way to create a persistent object with Tornado that will allow me to share a single instance of UpdateManager throughout my application?
Don't use instance methods - use class methods (after all, you're already using class attributes, you just might not realize it). That way, you don't have to instantiate the object, and can instead just call the methods of the class itself, which acts as a singleton:
class UpdateManager(object):
waiters = []
attrs = []
other_attrs = []
#classmethod
def set_attr(cls, attr):
cls.attrs.append(attr)
#classmethod
def set_other_attr(cls, attr):
cls.other_attrs.append(attr)
#classmethod
def add_callback(cls, cb):
cls.waiters.append(cb)
#classmethod
def send(cls):
for cb in cls.waiters:
cb(cls.attrs, cls.other_attrs)
This will make...
from update import UpdateManager
UpdateManager.send()
work as you desire it to.