class TagView(Group): nsViewClass = FGTagView tag = delegateProperty("_nsObject") state = delegateProperty("_nsObject") _callback = weakrefCallbackProperty() def __init__(self, posSize, tag, state, callback=None): super().__init__(posSize) self.tag = tag self.state = state self._callback = callback def _callCallback(self): callback = self._callback if callback is not None: callback(self)
class FeatureTagGroup(Group): _callback = weakrefCallbackProperty() def __init__(self, width, tagGroups, callback=None): posSize = (0, 0, width, 50) # dummy height super().__init__(posSize) self._callback = callback self._state = {} self.setTags(tagGroups, {}) def _breakCycles(self): del self._callback super()._breakCycles() def setTags(self, tagGroups, stylisticSetNames): # clear all subviews for attr, value in list(self.__dict__.items()): if isinstance(value, VanillaBaseObject): delattr(self, attr) self._titles = list(tagGroups) self._tagIdentifiers = defaultdict(list) margin = 10 tagWidth = 60 y = margin tagCounter = 0 for title, tags in tagGroups.items(): titleLabel = TextBox((margin, y, -margin, 20), title) setattr(self, f"label_{title}", titleLabel) y += 24 for tag in sorted(tags): tagView = TagView((margin, y, tagWidth, 20), tag, None, callback=self._tagStateChanged) names = stylisticSetNames.get(tag) if names: if len(names) == 1: description = next(iter(names)) else: description = "<multiple names>" else: description = features.get(tag, ["<unknown>"])[0] friendlyName = TextBox( (margin + tagWidth + 6, y + 1, -margin, 20), description) friendlyName._nsObject.cell().setLineBreakMode_( AppKit.NSLineBreakByTruncatingTail) tagIdentifier = f"tag_{title}_{tag}" self._tagIdentifiers[tag].append(tagIdentifier) setattr(self, tagIdentifier, tagView) setattr(self, f"friendlyName_{title}_{tag}", friendlyName) tagCounter += 1 y += 26 y += 6 posSize = (0, 0, self.getPosSize()[2], y) self.setPosSize(posSize) self._updateState() def _tagStateChanged(self, tagView): tag = tagView.tag state = tagView.state # if a tag occurs in more than one group, reflect the new state for tagIdentifier in self._tagIdentifiers[tag]: otherTagView = getattr(self, tagIdentifier) if otherTagView is not tagView: otherTagView.state = state if state is None: self._state.pop(tag, None) else: self._state[tag] = state callback = self._callback if callback is not None: callback(self) def get(self): return dict(self._state) def _updateState(self): for tag, value in self._state.items(): for tagIdentifier in self._tagIdentifiers.get(tag, ()): tagView = getattr(self, tagIdentifier) tagView.state = value def set(self, state): for tag, tagIdentifiers in self._tagIdentifiers.items(): for tagIdentifier in tagIdentifiers: tagView = getattr(self, tagIdentifier) tagView.state = state.get(tag) self._state = dict(state)
class SliderPlus(Group): _callback = weakrefCallbackProperty() def __init__(self, posSize, label, minValue, value, maxValue, continuous=True, callback=None): super().__init__(posSize) self._callback = callback self.label = TextBox((0, 0, 0, 20), label) self.slider = Slider((0, 18, -60, 20), value=minValue, minValue=minValue, maxValue=maxValue, continuous=continuous, callback=self._sliderCallback) self.editField = EditText((-50, 16, 0, 24), "", continuous=False, callback=self._editFieldCallback) self.editField._nsObject.setAlignment_(AppKit.NSRightTextAlignment) self._setSliderFromValue(value) self._setEditFieldFromValue(value) def _breakCycles(self): self._callback = None super()._breakCycles() def _sliderCallback(self, sender): value = sender.get() self._setEditFieldFromValue(value) callCallback(self._callback, self) def _editFieldCallback(self, sender): value = sender.get() if not value: # self._setSliderFromValue(None) callCallback(self._callback, self) return value = value.replace(",", ".") try: f = float(value) except ValueError: pass else: self.slider.set(f) sliderValue = self.slider.get() if sliderValue != f: self._setEditFieldFromValue(sliderValue) callCallback(self._callback, self) def _setSliderFromValue(self, value): if isinstance(value, set): value = sum(value) / len(value) elif value is None: minValue = self.slider._nsObject.minValue() maxValue = self.slider._nsObject.maxValue() value = (minValue + maxValue) / 2 self.slider.set(value) def _setEditFieldFromValue(self, value): if isinstance(value, set): if len(value) == 1: value = next(iter(value)) else: value = None if value is None: s = "" else: if int(value) == value: s = str(int(value)) else: s = f"{value:.1f}" self.editField.set(s) def get(self): if not self.editField.get(): return None else: return self.slider.get() def set(self, value): self._setSliderFromValue(value) self._setEditFieldFromValue(value)
class SliderGroup(Group): _callback = weakrefCallbackProperty() def __init__(self, width, sliderInfo, continuous=True, callback=None): super().__init__((0, 0, width, 0)) self._callback = callback self._continuous = continuous self._tags = [] self.setSliderInfo(sliderInfo) def _breakCycles(self): self._callback = None super()._breakCycles() def setSliderInfo(self, sliderInfo): savedState = self.get() # clear all subviews for attr, value in list(self.__dict__.items()): if isinstance(value, VanillaBaseObject): delattr(self, attr) margin = 10 y = margin self._tags = [] self._defaultValues = {} for tag, (label, minValue, defaultValue, maxValue) in sliderInfo.items(): self._tags.append(tag) self._defaultValues[tag] = defaultValue attrName = f"slider_{tag}" slider = SliderPlus((margin, y, -margin, 40), label, minValue, defaultValue, maxValue, continuous=self._continuous, callback=self._sliderChanged) setattr(self, attrName, slider) y += 50 self.resetAllButton = Button((10, y, 120, 25), "Reset all axes", self._resetAllButtonCallback) self.resetAllButton.enable(False) y += 35 posSize = (0, 0, self.getPosSize()[2], y) self.setPosSize(posSize) self._updateState(savedState) def _sliderChanged(self, sender): self.resetAllButton.enable(True) callCallback(self._callback, self) def _resetAllButtonCallback(self, sender): self.resetAllButton.enable(False) for tag in self._tags: attrName = f"slider_{tag}" slider = getattr(self, attrName) slider.set(self._defaultValues[tag]) callCallback(self._callback, self) def get(self): state = {} for tag in self._tags: attrName = f"slider_{tag}" slider = getattr(self, attrName) value = slider.get() if value is not None: if len(self._defaultValues[tag] ) != 1 or value not in self._defaultValues[tag]: state[tag] = value return state def _updateState(self, state): for tag, value in state.items(): attrName = f"slider_{tag}" slider = getattr(self, attrName, None) if slider is not None: slider.set(value) def set(self, state): if state: self.resetAllButton.enable(True) for tag in self._tags: attrName = f"slider_{tag}" slider = getattr(self, attrName) value = state.get(tag) if value is None: value = self._defaultValues[tag] slider.set(value)