Skip to content

Instantly share code, notes, and snippets.

@blakejohnson
Created November 21, 2016 18:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save blakejohnson/b60fea03e6db96d359d39e1757e6c67b to your computer and use it in GitHub Desktop.
Save blakejohnson/b60fea03e6db96d359d39e1757e6c67b to your computer and use it in GitHub Desktop.
atom/enaml exception issue
from atom.api import (Atom, List, ContainerList, Dict, observe, Callable, Typed, Unicode)
import enaml
class DictManager(Atom):
"""
Control - Presenter for a dictionary of items.
i.e. give the ability to add/delete rename items
"""
itemDict = Typed(dict)
displayFilter = Callable() # filter which items to display later
possibleItems = List() # a list of classes that can possibly be added to this list
displayList = ContainerList()
onChangeDelegate = Callable()
def __init__(self, itemDict={}, displayFilter=lambda x: True, **kwargs):
self.displayFilter = displayFilter
super(DictManager, self).__init__(itemDict=itemDict, displayFilter=displayFilter, **kwargs)
def add_item(self, parent):
"""
Create a new item dialog window and handle the result
"""
pass
# with enaml.imports():
# from widgets.dialogs import AddItemDialog
# dialogBox = AddItemDialog(parent, modelNames=[i.__name__ for i in self.possibleItems], objText='')
# dialogBox.exec_()
# if dialogBox.result:
# if dialogBox.newLabel not in self.itemDict.keys():
# self.itemDict[dialogBox.newLabel] = self.possibleItems[dialogBox.newModelNum](label=dialogBox.newLabel)
# self.displayList.append(dialogBox.newLabel)
# else:
# print("WARNING: Can't use duplicate label %s"%dialogBox.newLabel)
def remove_item(self, itemLabel):
#check that the item exists before removing from the list
if itemLabel in self.itemDict.keys():
self.itemDict.pop(itemLabel)
# TODO: once ContainerDicts land see if we still need this
self.displayList.remove(itemLabel)
def name_changed(self, oldLabel, newLabel):
# Add copy of changing item
self.itemDict[newLabel] = self.itemDict[oldLabel]
# update display list
idx = self.displayList.index(oldLabel)
self.displayList[idx] = newLabel
# remove old label from itemDict list
if oldLabel in self.itemDict.keys():
self.itemDict.pop(oldLabel)
else:
print("WARNING: %s is not in the list"%oldLabel)
# update label to new label list
self.itemDict[newLabel].label = newLabel
if self.onChangeDelegate:
self.onChangeDelegate(oldLabel, newLabel)
def update_enable(self, itemLabel, checkState):
self.itemDict[itemLabel].enabled = checkState
@observe('itemDict')
def update_display_list(self, change):
"""
Eventualy itemDict will be a ContainerDict and this will fire on all events.
Will have to be more careful about whether it is a "create" event or "update"
"""
self.displayList = sorted([v.label for v in self.itemDict.values() if self.displayFilter(v)])
"""
Reusable widgets for handling lists and dictionaries
"""
from qt_list_str_widget import QtListStrWidget
from enaml.widgets.api import Window, Container, PushButton, Form, Label, Field, ComboBox
from enaml.stdlib.mapped_view import MappedView
from enaml.core.api import Looper
from enaml.layout.api import hbox, vbox, spacer
enamldef DictManagerView(Container): dictView:
"""
Display a list of items on the left; a view of the selected item on the right and some add/delete buttons.
"""
attr dictManager
attr viewMap # dictionary mapping classes to views
attr viewkwargs # extra parameters to pass to the model view
attr modelName # attr name for the model in the view
attr labelValidator # callable validator for QtListStrWidget labels
constraints = [
hbox(
vbox(
itemList,
hbox(addButton, removeButton, spacer),
spacer
),
selectedItemView,
spacer
)
]
QtListStrWidget: itemList:
items << [(item, dictManager.itemDict[item].enabled) for item in dictManager.displayList]
validator = labelValidator
initialized ::
itemList.item_changed.connect(dictManager.name_changed)
itemList.enable_changed.connect(dictManager.update_enable)
PushButton: addButton:
text = "Add"
clicked :: dictManager.add_item(self)
PushButton: removeButton:
text = "Remove"
clicked :: dictManager.remove_item(itemList.selected_item)
Container: selectedItemView:
MappedView:
model << dictManager.itemDict[itemList.selected_item] if itemList.selected_item else None
typemap = dictView.viewMap
modelkey = dictView.modelName
kwargs = dictView.viewkwargs if dictView.viewkwargs else {}
# from atom.api import (Bool, List, Dict, observe, set_default, Unicode, Enum, Int, Callable, Typed)
from enaml.widgets.api import ComboBox
#A combo box connected to a Enum trait
enamldef EnumComboBox(ComboBox):
attr obj
attr enumName
attr itemList = list(obj.get_member(enumName).items)
items = itemList
index << itemList.index(getattr(obj, enumName))
index :: setattr(obj, enumName, itemList[index])
tool_tip = obj.get_member(enumName).metadata["desc"]
enamldef EnumFloatComboBox(ComboBox):
attr obj
attr floatName
attr itemList = [str(item) for item in list(obj.get_member(floatName).items)]
items = itemList
index << itemList.index(str(getattr(obj, floatName)))
index :: setattr(obj, floatName, float(itemList[index]))
tool_tip = obj.get_member(floatName).metadata["desc"]
enamldef EnumIntComboBox(ComboBox):
attr obj
attr intName
attr itemList = [str(item) for item in list(obj.get_member(intName).items)]
items = itemList
index << itemList.index(str(getattr(obj, intName)))
index :: setattr(obj, intName, int(itemList[index]))
tool_tip = obj.get_member(intName).metadata["desc"]
"""
Measurement filters
"""
from atom.api import Atom, Int, Float, Str, Bool, Enum
class MeasFilter(Atom):
label = Str()
enabled = Bool(True)
data_source = Str().tag(desc="Where the measurement data is pushed from.")
class Plotter(MeasFilter):
plot_mode = Enum('amp/phase', 'real/imag', 'quad').tag(desc='Filtered data scope mode.')
class WriteToHDF5(MeasFilter):
filename = Str('').tag(desc='Path to file where records will be saved.')
compression = Bool(True)
class Averager(MeasFilter):
axis = Str('').tag(desc='Name of the axis to average along.')
class Channelizer(MeasFilter):
if_freq = Float(10e6).tag(desc='The I.F. frequency for digital demodulation.')
bandwidth = Float(5e6).tag(desc='Low-pass filter bandwidth')
sampling_rate = Float(250e6).tag(desc='The sampling rate of the digitizer.')
phase = Float(0.0).tag(desc='Phase rotation to apply in rad.')
decim_factor_1 = Int(1).tag(desc="First stage polyphase decimation (before multiplication with reference).")
decim_factor_2 = Int(1).tag(desc="Second stage polyphase decimation (before IIR filter).")
decim_factor_3 = Int(1).tag(desc="Third stage polyphase decimation (after IIR filter).")
class KernelIntegrator(MeasFilter):
kernel = Str('').tag(desc="Integration kernel vector.")
bias = Float(0.0).tag(desc="Bias after integration.")
simple_kernel = Bool(True)
box_car_start = Int(1)
box_car_stop = Int(1)
if_freq = Float(10e6)
sampling_rate = Float(250e6)
measFilterList = [Averager,
Channelizer,
KernelIntegrator,
Plotter,
WriteToHDF5]
if __name__ == "__main__":
import enaml
from enaml.qt.qt_application import QtApplication
from DictManager import DictManager
# Work around annoying problem with multiple class definitions
from MeasFilters import Channelizer, Averager, KernelIntegrator, Plotter, WriteToHDF5
M1 = Channelizer(label='M1')
M2 = Averager(label='M2')
M3 = KernelIntegrator(label='M3')
M4 = Plotter(label='M4')
M5 = WriteToHDF5(label='M5')
filters = {'M1': M1,
'M2': M2,
'M3': M3,
'M4': M4,
'M5': M5}
manager = DictManager(
itemDict=filters,
possibleItems=measFilterList)
with enaml.imports():
from MeasFiltersViews import MeasFilterManagerWindow
app = QtApplication()
view = MeasFilterManagerWindow(filterLib=manager)
view.show()
app.start()
from enaml.widgets.api import Window, Label, Field, Form, Container, GroupBox, ComboBox, \
CheckBox, PushButton, SpinBox, RadioButton
from enaml.stdlib.fields import FloatField, IntField
from enaml.core.api import Conditional, Looper
from enaml.layout.api import hbox, vbox, spacer
from enum_combos import EnumComboBox
from DictManagerView import DictManagerView
import MeasFilters
from numpy import pi
enamldef ChannelizerForm(GroupBox):
attr myFilter
title := '{} ({})'.format(myFilter.label, myFilter.__class__.__name__)
hug_width = 'medium'
Form:
Label:
text = 'I.F. Freq (MHz)'
FloatField:
value << myFilter.if_freq/1e6
value :: myFilter.if_freq = value*1e6
tool_tip = myFilter.get_member('if_freq').metadata['desc']
Label:
text = 'Bandwidth (MHz)'
FloatField:
value << myFilter.bandwidth/1e6
value :: myFilter.bandwidth = value*1e6
tool_tip = myFilter.get_member('bandwidth').metadata['desc']
Label:
text = 'Sampling Rate (MS/s)'
FloatField:
value << myFilter.sampling_rate/1e6
value :: myFilter.sampling_rate = value*1e6
tool_tip = myFilter.get_member('sampling_rate').metadata['desc']
Label:
text = "First Stage Decimation"
IntField:
value := myFilter.decim_factor_1
tool_tip = myFilter.get_member('decim_factor_1').metadata['desc']
Label:
text = "Second Stage Decimation"
IntField:
value := myFilter.decim_factor_2
tool_tip = myFilter.get_member('decim_factor_2').metadata['desc']
Label:
text = "Third Stage Decimation"
IntField:
value := myFilter.decim_factor_3
tool_tip = myFilter.get_member('decim_factor_3').metadata['desc']
Label:
text = "Phase (deg.)"
FloatField:
value << (180/pi)*myFilter.phase
value :: myFilter.phase = (pi/180)*value
Label:
text = "Data source"
Field:
text := myFilter.data_source
enamldef KernelIntegratorForm(GroupBox):
attr myFilter
title := '{} ({})'.format(myFilter.label, myFilter.__class__.__name__)
Form:
Label:
text = "Data source"
Field:
text := myFilter.data_source
Label:
text = 'Arbitrary kernel'
Field:
text := myFilter.kernel
tool_tip = myFilter.get_member('kernel').metadata["desc"]
Label:
text = 'Simple kernel'
GroupBox:
Form:
Label:
text = 'Box car start'
IntField:
value := myFilter.box_car_start
Label:
text = 'Box car stop'
IntField:
value := myFilter.box_car_stop
Label:
text = 'IF frequency (MHz)'
FloatField:
value << myFilter.if_freq / 1e6
value :: myFilter.if_freq = value * 1e6
Label:
text = 'Sampling rate (MS/s)'
FloatField:
value << myFilter.sampling_rate / 1e6
value :: myFilter.sampling_rate = value * 1e6
Label:
text = ''
Container:
constraints = [hbox(rb1, rb2, spacer)]
RadioButton: rb1:
text = 'arbitrary'
checked << not myFilter.simple_kernel
RadioButton: rb2:
text = 'simple'
checked := myFilter.simple_kernel
Label:
text = 'Bias'
FloatField:
value := myFilter.bias
tool_tip = myFilter.get_member('bias').metadata["desc"]
enamldef PlotterView(GroupBox):
attr myFilter
title := '{} ({})'.format(myFilter.label, myFilter.__class__.__name__)
Form:
Label:
text = "Plot mode"
EnumComboBox:
obj := myFilter
enumName = 'plot_mode'
Label:
text = "Data source"
Field:
text := myFilter.data_source
enamldef WriteToHDF5View(GroupBox):
attr myFilter
title := '{} ({})'.format(myFilter.label, myFilter.__class__.__name__)
Form:
Label:
text = "Filename"
Field:
text := myFilter.filename
Label:
text = "Compression"
CheckBox:
checked := myFilter.compression
Label:
text = "Data source"
Field:
text := myFilter.data_source
enamldef AveragerView(GroupBox):
attr myFilter
title := '{} ({})'.format(myFilter.label, myFilter.__class__.__name__)
Form:
Label:
text = "Averaging Axis"
Field:
text := myFilter.axis
Label:
text = "Data source"
Field:
text := myFilter.data_source
#Dummy empty view
enamldef EmptyMeasFilterForm(Container):
attr myFilter
#Map filters to views
filterViewMap = {type(None): EmptyMeasFilterForm,
MeasFilters.Channelizer: ChannelizerForm,
MeasFilters.KernelIntegrator: KernelIntegratorForm,
MeasFilters.WriteToHDF5: WriteToHDF5View,
MeasFilters.Averager: AveragerView,
MeasFilters.Plotter: PlotterView}
enamldef MeasFilterManager(Container): measFilterManager:
attr filterLib
DictManagerView:
dictManager = filterLib
modelName = 'myFilter'
viewMap = filterViewMap
enamldef MeasFilterManagerWindow(Window): measFilterManagerTest:
attr filterLib
title = 'Filter Manager'
MeasFilterManager:
filterLib := measFilterManagerTest.filterLib
""" Enaml widget for editing a list of string
"""
#-------------------------------------------------------------------------------
# Imports:
#-------------------------------------------------------------------------------
from atom.api import (Bool, List, Tuple, ContainerList, observe, set_default, Unicode, Enum, Int, Signal, Callable)
from enaml.widgets.api import RawWidget
from enaml.core.declarative import d_
from enaml.qt.QtGui import QListWidget, QListWidgetItem, QAbstractItemView, QColor
from enaml.qt.QtCore import Qt
class QtListStrWidget(RawWidget):
""" A Qt4 implementation of an Enaml ProxyListStrView.
"""
__slots__ = '__weakref__'
#: The list of (str, enabled) tuples being viewed
items = d_(List(Tuple() ) )
#: The index of the currently selected str
selected_index = d_(Int(-1))
#: The currently selected str
selected_item = d_(Unicode())
#: Whether or not the items should be checkable
checkable = d_(Bool(True))
#: Whether or not the items should be editable
editable = d_(Bool(True))
validator = d_(Callable())
hug_width = set_default('weak')
item_changed = Signal()
enable_changed = Signal()
#--------------------------------------------------------------------------
# Initialization API
#--------------------------------------------------------------------------
def create_widget(self, parent):
"""
Create the QListWidget widget.
"""
# Create the list model and accompanying controls:
widget = QListWidget(parent)
for item, checked in self.items:
self.add_item(widget, item, checked)
# set selected_item here so that first change fires an 'update' rather than 'create' event
self.selected_item = u''
if self.items:
self.selected_index = 0
self.selected_item = self.items[0][0]
widget.setCurrentRow(0)
widget.itemSelectionChanged.connect(self.on_selection)
widget.itemChanged.connect(self.on_edit)
return widget
def add_item(self, widget, item, checked=True):
itemWidget = QListWidgetItem(item)
if self.checkable:
itemWidget.setCheckState(Qt.Checked if checked else Qt.Unchecked)
if self.editable:
_set_item_flag(itemWidget, Qt.ItemIsEditable, True)
widget.addItem(itemWidget)
self.apply_validator(itemWidget, itemWidget.text())
#--------------------------------------------------------------------------
# Signal Handlers
#--------------------------------------------------------------------------
def on_selection(self):
"""
The signal handler for the index changed signal.
"""
widget = self.get_widget()
self.selected_index = widget.currentRow()
self.selected_item = self.items[widget.currentRow()][0] if self.selected_index >= 0 else u''
def on_edit(self, item):
"""
The signal handler for the item changed signal.
"""
widget = self.get_widget()
itemRow = widget.indexFromItem(item).row()
oldLabel = self.items[itemRow][0]
newLabel = item.text()
# only signal the enable change when the labels are the same and is in
# the item list, also only signal a name change when the labels are not
# the same and the newlabel is not in the item list
item_labels = [_[0] for _ in self.items]
if newLabel == oldLabel and newLabel in item_labels:
self.items[itemRow] = (newLabel, item.checkState() == Qt.Checked)
self.enable_changed(item.text(), self.items[itemRow][1])
elif oldLabel != newLabel and newLabel not in item_labels:
self.item_changed(oldLabel, newLabel)
self.selected_item = newLabel
self.items[itemRow] = (newLabel, item.checkState() == Qt.Checked)
self.apply_validator(item, newLabel)
#--------------------------------------------------------------------------
# ProxyListStrView API
#--------------------------------------------------------------------------
def set_items(self, items, widget=None):
widget = self.get_widget()
count = widget.count()
nitems = len(items)
for idx, item in enumerate(items[:count]):
itemWidget = widget.item(idx)
# Update checked state before the text so that we can distinguish a checked state change from a label change
itemWidget.setCheckState(Qt.Checked if self.items[idx][1] else Qt.Unchecked)
itemWidget.setText(item[0])
self.apply_validator(itemWidget, item[0])
if nitems > count:
for item in items[count:]:
self.add_item(widget, item[0])
elif nitems < count:
for idx in reversed(xrange(nitems, count)):
widget.takeItem(idx)
#--------------------------------------------------------------------------
# Utility methods
#--------------------------------------------------------------------------
def apply_validator(self, item, label):
if self.validator and not self.validator(label):
item.setTextColor(QColor(255,0,0))
else:
item.setTextColor(QColor(0,0,0))
#--------------------------------------------------------------------------
# Observers
#--------------------------------------------------------------------------
@observe('items')
def _update_items(self, change):
""" An observer which sends state change to the proxy. """
#this callback may be called before the widget is initialized
widget = self.get_widget()
if widget == None:
return
self.set_items(self.items)
# update the selected item because the current row has changed
self.selected_item = self.items[widget.currentRow()][0] if self.selected_index >= 0 else u''
# Helper methods
def _set_item_flag(item, flag, enabled):
"""
Set or unset the given item flag for the item.
"""
flags = item.flags()
if enabled:
flags |= flag
else:
flags &= ~flag
item.setFlags(flags)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment