I have 2 QTreeViews both with a QFileSystemModel attached. One is a local directory and one is pointing at a server.
I am looking to change the colors of text on files based on its relation to the opposite location. For example:
If both server and local have the same modified time - set to green
If the server is more up to date - set local to red, server to orange. Etc etc.
I have had multiple working examples of this but I keep running into the same roadblock, sluggy UI response. I am using the data() function in both QFileSystemModels to achieve this, I tried threading the operation outside of data() to return the color back but it just makes all my files turn in to a light show. Files will randomly go green/red/orange based on me moving the mouse around.
I currently have the date modified column visible as a workaround but it doesn't help the user know if they have a file from the server locally already.
Has anyone got anything like this working without having the UI chug? Or even found a method to return the result from a thread and have it work for the correct entry?
I appreciate this may look a little messy but here is where I init the roots (some of these globals are used elsewhere)
def initRoots(self, loc):
'''
This function sets the starting directories for both server and local
Does not handle anything except top level
'''
global localMask
global serverMask
global serverRoot
global userRoot
global GserverModel
global GlocalModel
if loc == "Server":
self.serverRoot = self.cb_serverWorkspace.currentText()
serverRoot = self.serverRoot
self.serverModel = ServerFileSystemModel()
GserverModel = self.serverModel
self.tv_serverFiles.setModel(self.serverModel)
#self.serverModel.setRootPath(QtCore.QDir.rootPath())
self.serverModel.setRootPath("")
self.tv_serverFiles.setRootIndex(self.serverModel.index(self.serverRoot))
self.tv_serverFiles.setUniformRowHeights(True)
self.tv_serverFiles.setExpandsOnDoubleClick(True)
self.tv_serverFiles.hideColumn(1)
self.tv_serverFiles.hideColumn(2)
self.tv_serverFiles.setColumnWidth(0,400)
if loc == "Local":
self.userRoot = self.cb_localWorkspace.currentText()
userRoot = self.userRoot
self.localModel = LocalFileSystemModel()
GlocalModel = self.localModel
self.tv_localFiles.setModel(self.localModel)
#self.localModel.setRootPath(QtCore.QDir.rootPath())
self.localModel.setRootPath("")
self.tv_localFiles.setRootIndex(self.localModel.index(self.userRoot))
self.tv_serverFiles.setUniformRowHeights(True)
self.tv_localFiles.setExpandsOnDoubleClick(True)
self.tv_localFiles.hideColumn(1)
self.tv_localFiles.hideColumn(2)
self.tv_localFiles.setColumnWidth(0,400)
Then my two QFileSystemModels are pretty much this (This is my non-threaded example)
class ServerFileSystemModel(QtWidgets.QFileSystemModel):
def __init__(self, *args, **kwargs):
super(ServerFileSystemModel, self).__init__(*args, **kwargs)
def data(self, index, role=QtCore.Qt.DisplayRole):
try:
global GlocalModel
global GserverModel
serverPath = self.filePath(index)
localMask = userRoot + "/"
serverMask = serverRoot + "/"
newPath = serverPath.replace(serverMask, localMask)
serverIndex = GserverModel.index(serverPath)
localIndex = GlocalModel.index(newPath)
if role == QtCore.Qt.TextColorRole and serverPath.endswith(".fbx") and os.path.exists(newPath) and downloading == False:
if GlocalModel.lastModified(localIndex) == GserverModel.lastModified(serverIndex):
return QtGui.QColor("#58cd1c")
if GlocalModel.lastModified(localIndex) < GserverModel.lastModified(serverIndex):
return QtGui.QColor("#ed7011")
if GlocalModel.lastModified(localIndex) > GserverModel.lastModified(serverIndex):
return QtGui.QColor("#ed1111")
return super(ServerFileSystemModel, self).data(index, role)
except:
return super(ServerFileSystemModel, self).data(index, role)
Anything else just let me know.
EDIT******
I was asked to put minimal replicable code so here we go
MainWindow
class WorkspacerUI(QMainWindow, Ui_MainWindow):
def __init__(self, parent = None):
super(WorkspacerUI, self).__init__(parent)
self.setupUi(self)
self.userRoot = "D:/OCooke_Workspace"
self.serverRoot = "D:/OCooke_Server"
self.initRoots("Local")
self.initRoots("Server")
def initRoots(self, loc):
'''
This function sets the starting directories for both server and local
Does not handle anything except top level
'''
if loc == "Server":
self.serverModel = ServerFileSystemModel()
self.tv_serverFiles.setModel(self.serverModel)
#self.serverModel.setRootPath(QtCore.QDir.rootPath())
self.serverModel.setRootPath("")
self.tv_serverFiles.setRootIndex(self.serverModel.index(self.serverRoot))
self.tv_serverFiles.setUniformRowHeights(True)
self.tv_serverFiles.setExpandsOnDoubleClick(True)
self.tv_serverFiles.hideColumn(1)
self.tv_serverFiles.hideColumn(2)
self.tv_serverFiles.setColumnWidth(0,400)
if loc == "Local":
self.localModel = LocalFileSystemModel()
self.tv_localFiles.setModel(self.localModel)
#self.localModel.setRootPath(QtCore.QDir.rootPath())
self.localModel.setRootPath("")
self.tv_localFiles.setRootIndex(self.localModel.index(self.userRoot))
self.tv_serverFiles.setUniformRowHeights(True)
self.tv_localFiles.setExpandsOnDoubleClick(True)
self.tv_localFiles.hideColumn(1)
self.tv_localFiles.hideColumn(2)
self.tv_localFiles.setColumnWidth(0,400)
QRunnable with Signals
class WorkerSignals(QObject):
finished = Signal()
result = Signal(object)
progress = Signal(int)
class ColourWorker(QRunnable):
'''
Worker thread
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
:param callback: The function callback to run on this worker thread. Supplied args and
kwargs will be passed through to the runner.
:type callback: function
:param args: Arguments to pass to the callback function
:param kwargs: Keywords to pass to the callback function
'''
def __init__(self, fn, *args, **kwargs):
super(ColourWorker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
# Add the callback to our kwargs
self.kwargs['progress_callback'] = self.signals.progress
self.kwargs['indexy']
#Slot()
def run(self):
'''
Initialise the runner function with passed args, kwargs.
'''
# Retrieve args/kwargs here; and fire processing using them
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result) # Return the result of the processing
finally:
self.signals.finished.emit() # Done
Finally the 2 QSystemFileModels
class ServerFileSystemModel(QFileSystemModel):
def __init__(self, *args, **kwargs):
super(ServerFileSystemModel, self).__init__(*args, **kwargs)
self.threadpool = QThreadPool()
self.colour = None
def data(self, index, role=Qt.DisplayRole):
if role == Qt.TextColorRole:
worker = ColourWorker(self.execute_this_fn, indexy = index) # Any other args, kwargs are passed to the run function
worker.signals.result.connect(self.print_output)
worker.signals.finished.connect(self.thread_complete)
# Execute
self.threadpool.start(worker)
return self.colour
return super(ServerFileSystemModel, self).data(index, role)
#
def execute_this_fn(self, progress_callback, indexy):
serverPath = self.filePath(indexy)
localMask = "D:/OCooke_Workspace/"
serverMask = "D:/OCooke_Server/"
#create an expected path to the local using the server address
# D:/OCooke_Server/2020/file1 turns into D:/OCooke_Workspace/2020/file1
newPath = serverPath.replace(serverMask, localMask)
#Check the stat time between the two locations
if os.stat(newPath).st_mtime == os.stat(serverPath).st_mtime:
return QColor("#58cd1c")
if os.stat(newPath).st_mtime < os.stat(serverPath).st_mtime:
return QColor("#ed7011")
if os.stat(newPath).st_mtime > os.stat(serverPath).st_mtime:
return QColor("#ed1111")
else:
return None
def print_output(self, s):
#return the value of color
self.colour = s
def thread_complete(self):
#ive tried passing the color once the thread completes, no joy
pass
class LocalFileSystemModel(QFileSystemModel):
def __init__(self, *args, **kwargs):
super(LocalFileSystemModel, self).__init__(*args, **kwargs)
def data(self, index, role=Qt.DisplayRole):
return super(LocalFileSystemModel, self).data(index, role)
MAIN
def main():
app = QApplication(sys.argv)
MainWindow = WorkspacerUI()
MainWindow.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Related
I'm trying to port this IronPython WPF example to CPython with python.NET.
I'm able to get the window running after fixing some known issues (adding __namespace__ to the ViewModel class, using Single Thread Apartment and updating the syntax of pyevent.py to python3), but bindings are ignored: I can write in the textbox, but no events gets triggered, and the button doesn't fire the OnClick method.
Here's the full code:
import clr
clr.AddReference(r'wpf\PresentationFramework')
clr.AddReference('PresentationCore')
from System.Windows.Markup import XamlReader
from System.Windows import Application, Window
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
import pyevent
from System.Threading import Thread, ThreadStart, ApartmentState
XAML_str = """<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Sync Paramanager" Height="180" Width="400">
<StackPanel x:Name="DataPanel" Orientation="Horizontal">
<Label Content="Size"/>
<Label Content="{Binding size}"/>
<TextBox x:Name="tbSize" Text="{Binding size, UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="Button" Content="Set Initial Value"></Button>
</StackPanel>
</Window>"""
class notify_property(property):
def __init__(self, getter):
def newgetter(slf):
#return None when the property does not exist yet
try:
return getter(slf)
except AttributeError:
return None
super().__init__(newgetter)
def setter(self, setter):
def newsetter(slf, newvalue):
# do not change value if the new value is the same
# trigger PropertyChanged event when value changes
oldvalue = self.fget(slf)
if oldvalue != newvalue:
setter(slf, newvalue)
slf.OnPropertyChanged(setter.__name__)
return property(
fget=self.fget,
fset=newsetter,
fdel=self.fdel,
doc=self.__doc__)
class NotifyPropertyChangedBase(INotifyPropertyChanged):
__namespace__ = "NotifyPropertyChangedBase"
PropertyChanged = None
def __init__(self):
self.PropertyChanged, self._propertyChangedCaller = pyevent.make_event()
def add_PropertyChanged(self, value):
self.PropertyChanged += value
def remove_PropertyChanged(self, value):
self.PropertyChanged -= value
def OnPropertyChanged(self, propertyName):
if self.PropertyChanged is not None:
self._propertyChangedCaller(self, PropertyChangedEventArgs(propertyName))
class ViewModel(NotifyPropertyChangedBase):
__namespace__ = "WpfViewModel"
def __init__(self):
super(ViewModel, self).__init__()
# must be string to two-way binding work correctly
self.size = '10'
#notify_property
def size(self):
return self._size
#size.setter
def size(self, value):
self._size = value
print(f'Size changed to {self.size}')
class TestWPF(object):
def __init__(self):
self._vm = ViewModel()
self.root = XamlReader.Parse(XAML_str)
self.DataPanel.DataContext = self._vm
self.Button.Click += self.OnClick
def OnClick(self, sender, event):
# must be string to two-way binding work correctly
self._vm.size = '10'
def __getattr__(self, name):
# provides easy access to XAML elements (e.g. self.Button)
return self.root.FindName(name)
def main():
tw = TestWPF()
app = Application()
app.Run(tw.root)
if __name__ == '__main__':
thread = Thread(ThreadStart(main))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()
It seems that assigning the ModelView to DataPanel's DataContext doesn't trigger any binding registration, but I have no clue on how to fix that.
Am I missing something obvious?
Unfortunately pythonnet does not support defining binding in the xaml file.
That's what I'm trying to achieve: the only instance of publisher in Elasticsearch, which is running on the master and, which, with a certain number of records in a certain queue, collected from workers with a multiple number of users, will send them directly to Elasticsearch, and at the end of the test, all the records that remain in the queue will be sent with bulk.
At the moment, this is implemented by using event hooks report_to_master and worker_report, but some records are lost, as I suspect after debugging, sometimes the variables of response_data and another_data overwrite the new value before sending them through on_to_master.
import gevent
gevent.monkey.patch_all()
from datetime import datetime
from functools import wraps
from time import perf_counter
from uuid import uuid4
from gevent.queue import Queue
from elasticsearch import Elasticsearch
from elasticsearch.helpers import bulk
from locust import SequentialTaskSet, HttpUser, task, between, events
from locust.runners import WorkerRunner
def on_worker_report(es):
def inner(data, **kw):
if (rd := data.get('response_data')):
es.response_queue.put_nowait(rd)
if (ad := data.get('another_data')):
es.another_queue.put_nowait(ad)
return inner
#events.init.add_listener
def on_init(environment, **_kw):
if not isinstance(environment.runner, WorkerRunner):
es = Elastic(environment)
environment.runner.greenlet.spawn(es.run)
on_worker_report_listener = on_worker_report(es)
environment.events.worker_report.add_listener(on_worker_report_listener)
class Elastic:
def __init__(self, env):
self.es = Elasticsearch('localhost:9200')
self.env = env
self.response_queue = Queue()
self.response_index = 'response'
self.another_queue = Queue()
self.another_index = 'another'
self.bulk_size = 10
self.env.events.quitting.add_listener(self.on_quit)
def to_es(self, data, index):
bulk(self.es, ({'_index': index, '_source': doc} for doc in data))
def run(self):
while True:
if self.response_queue.qsize() >= self.bulk_size:
data = (self.response_queue.get_nowait() for _ in range(self.bulk_size))
self.to_es(data, self.response_index)
if self.another_queue.qsize() >= self.bulk_size:
data = (self.another_queue.get_nowait() for _ in range(self.bulk_size))
self.to_es(data, self.another_index)
gevent.sleep(1.0)
def on_quit(self, **kw):
response_data = (self.response_queue.get_nowait() for _ in range(self.response_queue.qsize()))
self.to_es(response_data, self.response_index)
another_data = (self.another_queue.get_nowait() for _ in range(self.another_queue.qsize()))
self.to_es(another_data, self.another_index)
class MyBaseTask(SequentialTaskSet):
def __init__(self, parent):
super().__init__(parent)
self.root_path = '/'
self.events = self.user.environment.events
self.events.report_to_master.add_listener(self.on_to_master)
self.response_data = None
self.another_data = None
def validate(self, response):
print('Validating', response)
self.prepare_response_data(response)
def on_to_master(self, data, **kw):
data['response_data'] = self.response_data
data['another_data'] = self.another_data
self.response_data = None
self.another_data = None
def prepare_response_data(self, response):
data = {
'timestamp': datetime.now().isoformat(),
'url': response.request.url,
'uuid': str(uuid4())
}
self.response_data = data
def prepare_another_data(self, name, time):
data = {
'timestamp': datetime.now().isoformat(),
'name': name,
'time': time,
'uuid': str(uuid4())
}
self.another_data = data
def timer(name):
def decorator(func):
#wraps(func)
def inner(self, *args, **kwargs):
s_perf = perf_counter()
ret = func(self, *args, **kwargs)
d_perf = perf_counter() - s_perf
self.prepare_another_data(name, d_perf)
return ret
return inner
return decorator
class MyMainTask(MyBaseTask):
def __init__(self, parent):
super().__init__(parent)
self.task_path = '/api'
def action_1(self):
with self.client.get(self.root_path) as response:
self.validate(response)
def action_2(self):
with self.client.get(self.root_path) as response_1:
self.validate(response_1)
with self.client.get(self.task_path) as response_2:
self.validate(response_2)
class MyTest(MyMainTask):
def __init__(self, parent):
super().__init__(parent)
def on_start(self):
print(f'Start {self}')
#MyBaseTask.timer(name='Step 1')
#task
def step_1(self):
self.action_1()
#MyBaseTask.timer(name='Step 2')
#task
def step_2(self):
self.action_2()
#task
def stop(self):
self.interrupt()
class MyUser(HttpUser):
tasks = [MyTest]
wait_time = between(1.0, 5.0)
I also tried to use variables in the user space, not test, but it also did not give the result (a dot means the same code since the past example):
...
class MyBaseTask(SequentialTaskSet):
...
def on_to_master(self, data, **kw):
data['response_data'] = self.user.response_data
data['another_data'] = self.user.another_data
self.user.response_data = None
self.user.another_data = None
def prepare_response_data(self, response):
data = ...
self.user.response_data = data
def prepare_another_data(self, name, time):
data = ...
self.user.another_data = data
...
...
class MyUser(HttpUser):
tasks = [MyTest]
wait_time = between(1.0, 5.0)
def __init__(self, environment):
super().__init__(environment)
self.response_data = None
self.another_data = None
Another option was to use a list to combine the values, which should be in the queue, and check the already existing entries in it, but it led to the effect that part of the records do not fall into the queue (the problem that was before) and part of the records duplicated despite checks. This I followed using prints and comparison of uuids what were pushed into ES.
...
def on_worker_report(es):
def inner(data, **kw):
if (rd := data.get('response_data')):
for e in rd:
es.response_queue.put_nowait(e)
if (ad := data.get('another_data')):
for e in ad:
es.another_queue.put_nowait(e)
return inner
...
class MyBaseTask(SequentialTaskSet):
def __init__(self, parent):
...
self.user.response_data.clear()
self.user.another_data.clear()
def on_to_master(self, data, **kw):
data['response_data'] = self.user.response_data
data['another_data'] = self.user.another_data
def prepare_response_data(self, response):
data = ...
if data not in self.user.response_data:
self.user.response_data.append(data)
def prepare_another_data(self, name, time):
data = ...
if data not in self.user.another_data:
self.user.another_data.append(data)
...
...
class MyUser(HttpUser):
tasks = [MyTest]
wait_time = between(1.0, 5.0)
def __init__(self, environment):
super().__init__(environment)
self.response_data = []
self.another_data = []
I even tried to collect the data dict for report_to_master and call the fire method manually, but then even statistics breaks.
class MyBaseTask(SequentialTaskSet):
...
def prepare_response_data(self, response):
data = ...
stats = self.user.environment.stats
data_report = {}
data_report['stats'] = stats.serialize_stats()
data_report['stats_total'] = stats.total.get_stripped_report()
data_report['errors'] = stats.serialize_errors()
stats.errors = {}
data_report['response_data'] = data
self.events.report_to_master.fire(client_id=self.user.environment.runner.client_id, data=data_report)
Also, a number of problems that I encountered:
Event hooks request, request_failure/request_success are not suitable, since the transfer of additional data is required, which is not provided for by these hooks, as well as they work in the same place where they're registered, so no master-worker communication is presented. From here it turns out that I also can't use own custom hook, as there will be no communications that in the locust is organized as a client-server via zmq. Also, the data that I transmit in this mode must be serializable, so I can't pass, e.g, a full Response object to handle it already on the master, but must convert to some kind on worker.
Perhaps I implement something absolutely wrong, so I should return to the very beginning, for example, it was now thinking about the creation of instance of Elastic precisely on workers and then the communications with the master may not be needed, but on the other hand, I wanted workers to send requests, while the master would be loaded by creating and processing logs.
I have a question regarding a Python class I use in Blender. Basically, I wonder how the class works because some attributes are recorded without me specifically writing self.value = something. Here's the code:
class DialogOperator(bpy.types.Operator):
bl_idname = "object.dialog_operator"
bl_label = "Save/Load animation"
saving = bpy.props.BoolProperty(name="Save ? Else load.")
path_to_anim = bpy.props.StringProperty(name="Path to folder")
anim_name = bpy.props.StringProperty(name="Animation name:")
# path_to_anim += "/home/mehdi/Blender/Scripts/"
def execute(self, context):
# print('This is execute with: Saving: {} Name:{}'.format(self.saving, self.path_to_anim))
if self.saving:
self.launch_save()
message = 'Animation {} saved at {}'.format(self.anim_name, self.path_to_anim)
else:
self.launch_load()
message = 'Animation {} loaded'.format(self.anim_name)
self.report({'INFO'}, message)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def launch_load(self):
full_path = self.path_to_anim + self.anim_name
target_armature = Humanoid(bpy.data.objects['Armature'])
load_all(full_path, target_armature, 'LastLoaded')
def launch_save(self):
full_path = self.path_to_anim + self.anim_name
source_armature = Humanoid(bpy.data.objects['Armature'])
curves = source_armature.get_curves()
save_all(curves, source_armature,full_path)
Now, how come saving, path_to_anim and anim_name are considered as attributes (I'm able to call them in execute() and launch()) even though I did not write self.saving = saving
Thanks !
This is because saving,path_to_anim and anim_name are class attributes. They are defined for the class and not for a particular instance. They are shared among the instances. Here is a link for further explanation class-instance-attributes-python
A QPushButton is set 'asCheckable'. Whence toggled, a class bool is changed.
This altered bool allows a method in a different class to proceed, and upon completion of this outside method I need to return the button to its initial state, 'setChecked(False)'.
While I am able to return the class housed bool to its default state at the end of this external method, I am unable to externally access a method which un-clicks the button.
I assume its due to the arguments in the classes init, but these are necessary - and I'm wondering if there is another means to achieve the described workflow.
Related code snips below:
(command in question is distinguished at bottom of 'Class 2')
Class 1:
class shapeCSVeditor(QtGui.QDialog, QtGui.QWidget):
valueShare = []
rowOverride = False# <<=== equivalent to 'override' in 'Class 2'
def __init__(self, iface, fileName, editorType, parent=None):
super(shapeCSVeditor, self).__init__(parent)
self.iface = iface
self.editorType = editorType
self.fileName = filename
self.pushButtonSetBase = QtGui.QPushButton(self)
self.pushButtonSetBase.setText("Set Base Shape")
self.pushButtonSetBase.setCheckable(True)
self.pushButtonSetBase.toggled.connect(self.on_pushButtonSetBase_toggled)
self.layoutHorizontal.addWidget(self.pushButtonSetBase)
#some other things here...
#QtCore.pyqtSlot()
def on_pushButtonSetBase_toggled(self):
shapeCSVeditor.rowOverride = True
pass
def on_BaseRow_Changed(self):
self.pushButtonSetBase.setChecked(False)
return
Class 2:
class CSVModel(QtCore.QAbstractTableModel):
# Establish inital settings and branch processes
def __init__(self, iface, fileName, editorType, parent=None):
super(CSVModel,self).__init__()
self.propertiesFile = r'some file'
self.areaStressFile = r'some other file'
self.iface = iface
self.rows = []
self.editorType = editorType
self.loadCSV()
self.iface.mapCanvas().selectionChanged.connect(self.addRow)
# add rows to the TableView based on object selection(s) in Qgis.mapCanvas
def addRow(self):
override = shapeCSVeditor.rowOverride
selectedFeatures = selectedLayer.selectedFeatures()
if override:
for feature in selectedFeatures:
self.rows.pop(0)
feat_Attributes = []
feat_Attributes.extend([self.iface.activeLayer().name()+'_'+str(feature.id())])
feat_Attributes.extend(['',]*(len(self.header)-1))
self.beginResetModel()
self.rows.insert(0,feat_Attributes)
shapeCSVeditor.rowOverride = False
self.endResetModel()
shapeCSVeditor.on_BaseRow_Changed# <<<=== wrong-diddily!
break
PS - if parentheticals are added to the 'shapeCSVeditor()' 3 arguments are requisite as referenced in the Button class, and if parentheticals are added to 'on_BaseRow_Changed', the return is;
TypeError: unbound method on_BaseRow_Changed() must be called with
shapeCSVeditor instance as first argument (got nothing instead)
What you are doing is strange.
In python, the first argument of a class method is always the object itself.
So, in your:
def on_BaseRow_Changed(self):
self.pushButtonSetBase.setChecked(False)
# return => This return is useless
if you don't provide an object then you can't access the pushbutton.
You didn't gave us all the code but I think you should provide your addRow with the shapeCSVeditor object that you want to update:
def addRow(self, shapeCSVObj):
override = shapeCSVObj.rowOverride
if override:
for feature in selectedFeatures:
self.rows.pop(0)
feat_Attributes = []
feat_Attributes.extend([self.iface.activeLayer().name()+'_'+str(feature.id())])
feat_Attributes.extend(['',]*(len(self.header)-1))
self.beginResetModel()
self.rows.insert(0,feat_Attributes)
shapeCSVObj.rowOverride = False
self.endResetModel()
shapeCSVObj.on_BaseRow_Changed()
break
Somewhere you must have a shapeCSVeditor that is created. You should provide it to you outside class.
Hope this helps.
class shapeCSVeditor(QtGui.QDialog, QtGui.QWidget):
valueShare = []
rowOverride = False
def __init__(self, iface, fileName, editorType, parent=None):
super(shapeCSVeditor, self).__init__(parent)
self.iface = iface
self.editorType = editorType
self.fileName = fileName
self.tableView = QtGui.QTableView(self)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.tableData = CSVModel(self,iface,fileName,editorType)
^^==not implementing 'self' (shapeCSVeditor object) was the problem!
self.tableView.setModel(self.tableData)
...
self.pushButtonSetBase = QtGui.QPushButton(self)
self.pushButtonSetBase.setText("Set Base Shape")
self.pushButtonSetBase.setCheckable(True)
self.pushButtonSetBase.clicked.connect(self.on_pushButtonSetBase_toggled)
...
#QtCore.pyqtSlot()
def on_pushButtonSetBase_toggled(self):
self.rowOverride = True
#QtCore.pyqtSlot()
def on_BaseRow_Changed(self):
self.rowOverride = False
self.pushButtonSetBase.setChecked(False)
///////////////////////////////////////////////////////////////////////////////////////
class CSVModel(QtCore.QAbstractTableModel):
def __init__(self, shapeCSVeditor, iface, fileName, editorType):
super(CSVModel,self).__init__()
self.propertiesFile = r'foo'
self.areaStressFile = r'bar'
self.tableView = shapeCSVeditor <<== proper passing of shapeCSVeditor object! (?)
self.iface = iface
self.rows = []
self.editorType = editorType
self.loadCSV()
self.iface.mapCanvas().selectionChanged.connect(self.addRow)
...
def addRow(self):
selectedFeatures = selectedLayer.selectedFeatures()
if self.tableView.rowOverride:
for feature in selectedFeatures:
self.rows.pop(0)
feat_Attributes = []
feat_Attributes.extend([self.iface.activeLayer().name()+'_'+str(feature.id())])
feat_Attributes.extend(['',]*(len(self.header)-1))
self.beginResetModel()
self.rows.insert(0,feat_Attributes)
self.endResetModel()
self.tableView.rowOverride = False
self.tableView.on_BaseRow_Changed()
Radical. Works for the current needs.
Now the question is if its proper to python 'standards'.
Quite new to writing, so its possible more needs fixed.
High thanks to Plouff for the clues.
I'm trying to find a way to use libavg's event handlers from an embedded serial output. My understanding is that I need to create my own Publisher that I will call when I process serial commands. All I need is a way to create 10 different triggers given different serial inputs. An analogy of what I am trying to do would be to use libavg's keyboard handling to process different keyboard inputs.
I want the custom publisher to take the 10 serial outputs and pass a event.serialid parameter to various subscribers similarly to what event.keystring does.
Here is some nonfunctional code that I have that I think has the basics of what needs to be done.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from libavg import avg, statemachine, player
class Test():
PRESSED = avg.Publisher.genMessageID()
RELEASED = avg.Publisher.genMessageID()
def __init__(self, parent=None, **kwargs):
self.registerInstance(self, parent)
self.publish(self.PRESSED)
self.publish(self.RELEASED)
def isActive(self):
self.notifySubscribers(Test.PRESSED, [])
def isInactive(self):
self.notifySubscribers(Test.RELEASED, [])
def onKeyDown(event):
global node
if event.serialid == '1':
#serialid isn't implemented anywhere but this is what ideally I would like to have happen
node.color = "FF8000"
def onKeyUp(event):
global node
node.color = "FFFFFF"
player = avg.Player.get()
canvas = player.createMainCanvas(size=(640,480))
rootNode = player.getRootNode()
node = avg.WordsNode(pos=(10,10), font="arial", text="Hello World", parent=rootNode)
vbutton=Test()
node.subscribe(vbutton.PRESSED, onKeyDown)
node.subscribe(vbutton.RELEASED, onKeyUp)
player.play()
examples of custom publishers from here:
class _ButtonBase(avg.DivNode):
PRESSED = avg.Publisher.genMessageID()
RELEASED = avg.Publisher.genMessageID()
def __init__(self, parent=None, **kwargs):
super(_ButtonBase, self).__init__(**kwargs)
self.registerInstance(self, parent)
self.publish(self.PRESSED)
self.publish(self.RELEASED)
def _setActiveArea(self, upNode, activeAreaNode, fatFingerEnlarge):
self.__activeAreaNode = activeAreaNode
if fatFingerEnlarge:
if self.__activeAreaNode != None:
raise(RuntimeError(
"Button: Can't specify both fatFingerEnlarge and activeAreaNode"))
size = upNode.size
minSize = 20*player.getPixelsPerMM()
size = avg.Point2D(max(minSize, size.x), max(minSize, size.y))
self.__activeAreaNode = avg.RectNode(size=size, opacity=0, parent=self)
else:
if self.__activeAreaNode == None:
self.__activeAreaNode = self
else:
self.appendChild(self.__activeAreaNode)
self._tapRecognizer = gesture.TapRecognizer(self.__activeAreaNode,
possibleHandler=self._onDown,
detectedHandler=self._onTap,
failHandler=self._onTapFail)
You can pass arbitrary parameters through the publish-subscribe interface. The parameter(s) are passed as a list:
self.notifySubscribers(Test.PRESSED, [serialID])
And in the handler:
def onKeyDown(serialID):
global node
if serialid == '1':
node.color = "FF8000"