class CuteWindow(AcceleratorSavvyWindow, BindSavvyEvtHandler, wx.Window): ''' An improved `wx.Window`. The advantages of this class over `wx.Window`: - A `.freezer` property for freezing the window. - A `.create_cursor_changer` method which creates a `CursorChanger` context manager for temporarily changing the cursor. - A `set_good_background_color` for setting a good background color. - A few more features. This class doesn't require calling its `__init__` when subclassing. (i.e., you *may* call its `__init__` if you want, but it will do the same as calling `wx.Window.__init__`.) ''' freezer = freezing.FreezerProperty( freezer_type=wx_tools.window_tools.WindowFreezer, doc='''Freezer for freezing the window while the suite executes.''' ) def create_cursor_changer(self, cursor): ''' Create a `CursorChanger` context manager for ...blocktotodoc `cursor` may be either a `wx.Cursor` object or a constant like `wx.CURSOR_BULLSEYE`. ''' return wx_tools.cursors.CursorChanger(self, cursor) def set_good_background_color(self): '''Set a good background color to the window.''' self.SetBackgroundColour(wx_tools.colors.get_background_color()) def has_focus(self): return wx.Window.FindFocus() == self def set_tool_tip_and_help_text(self, tool_tip=None, help_text=None): if tool_tip is not None: self.SetToolTipString(tool_tip) if help_text is not None: self.SetHelpText(help_text)
class EmitterSystem(object): ''' A system of emitters, representing a set of possible events in a program. `EmitterSystem` offers a few advantages over using plain emitters. There are the `bottom_emitter` and `top_emitter`, which allow, respectively, to keep track of each `emit`ting that goes on, and to generate an `emit`ting that affects all emitters in the system. The `EmitterSystem` also offers a context manager, `.freeze_cache_rebuilding`. When you do actions using this context manager, the emitters will not rebuild their cache when changing their inputs/outputs. When the outermost context manager has exited, all the caches for these emitters will get rebuilt. ''' # possible future idea: there is the idea of optimizing by cutting # redundant links between boxes. I'm a bit suspicious of it. The next # logical step is to make inputs and outputs abstract. def __init__(self): self.emitters = set() self.bottom_emitter = Emitter(self, name='bottom') self.emitters.add(self.bottom_emitter) self.top_emitter = Emitter( self, outputs=(self.bottom_emitter,), name='top', ) self.emitters.add(self.top_emitter) cache_rebuilding_freezer = freezing.FreezerProperty() ''' Context manager for freezing the cache rebuilding in an emitter system. When you do actions using this context manager, the emitters will not rebuild their cache when changing their inputs/outputs. When the outermost context manager has exited, all the caches for these emitters will get rebuilt. ''' @cache_rebuilding_freezer.on_thaw def _recalculate_all_cache(self): '''Recalculate the cache for all the emitters.''' self.bottom_emitter._recalculate_total_callable_outputs_recursively() def make_emitter(self, inputs=(), outputs=(), name=None): '''Create an emitter in this emitter system. Returns the emitter.''' # todo: allow one value in inputs and outputs. do in all emitter # constructors. inputs = set(inputs) inputs.add(self.top_emitter) outputs = set(outputs) outputs.add(self.bottom_emitter) emitter = Emitter(self, inputs, outputs, name) self.emitters.add(emitter) return emitter def remove_emitter(self, emitter): ''' Remove an emitter from this system, disconnecting it from everything. ''' with self.cache_rebuilding_freezer: emitter.disconnect_from_all() self.emitters.remove(emitter)
class EmittingOrderedSet(OrderedSet): '''An ordered set that emits to `.emitter` every time it's modified.''' def __init__(self, iterable=(), *, emitter=None): if emitter: from python_toolbox.emitting import Emitter assert isinstance(emitter, Emitter) self.emitter = emitter OrderedSet.__init__(self, iterable) def add(self, key, last=True): ''' Add an element to a set. This has no effect if the element is already present. ''' if key not in self._map: super().add(key, last=last) self._emit() def discard(self, key): ''' Remove an element from a set if it is a member. If the element is not a member, do nothing. ''' if key in self._map: super().discard(key) self._emit() def clear(self): '''Clear the ordered set, removing all items.''' if self: super().clear() self._emit() def set_emitter(self, emitter): '''Set `emitter` to be emitted with on every modification.''' self.emitter = emitter def _emit(self): if (self.emitter is not None) and not self._emitter_freezer.frozen: self.emitter.emit() def move_to_end(self, key, last=True): ''' Move an existing element to the end (or start if `last=False`.) ''' # Inefficient implementation until someone cares. with self._emitter_freezer: self.remove(key) self.add(key, last=last) _emitter_freezer = freezing.FreezerProperty() def __eq__(self, other): return ((type(self) is type(other)) and (len(self) == len(other)) and (self.emitter is other.emitter) and all(itertools.starmap(operator.eq, zip(self, other)))) def get_without_emitter(self): '''Get a version of this ordered set without an emitter attached.''' return OrderedSet(self)
class Textual(CutePanel): '''Display (and allow modifying) the hue as a number 0-359.''' def __init__(self, hue_selection_dialog): wx.Panel.__init__(self, parent=hue_selection_dialog, size=(75, 100)) self.set_good_background_color() self.SetHelpText('Set the hue in angles (0°-359°).') self.hue_selection_dialog = hue_selection_dialog self.hue = hue_selection_dialog.hue self.main_v_sizer = wx.BoxSizer(wx.VERTICAL) self.hue_static_text = wx.StaticText(self, label='&Hue:') self.main_v_sizer.Add(self.hue_static_text, 0, wx.ALIGN_LEFT | wx.BOTTOM, border=5) self.h_sizer = wx.BoxSizer(wx.HORIZONTAL) self.main_v_sizer.Add(self.h_sizer, 0) self.spin_ctrl = wx.SpinCtrl(self, min=0, max=359, initial=ratio_to_round_degrees(self.hue), size=(70, -1), style=wx.SP_WRAP) if wx_tools.is_mac: self.spin_ctrl.SetValue(ratio_to_round_degrees(self.hue)) self.h_sizer.Add(self.spin_ctrl, 0) self.degree_static_text = wx.StaticText(self, label=unichr(176)) self.h_sizer.Add(self.degree_static_text, 0) self.SetSizerAndFit(self.main_v_sizer) self.Bind(wx.EVT_SPINCTRL, self._on_spin, source=self.spin_ctrl) self.Bind(wx.EVT_TEXT, self._on_text, source=self.spin_ctrl) value_freezer = freezing.FreezerProperty() def update(self): '''Update to show the new hue.''' if not self.value_freezer.frozen and \ self.hue != self.hue_selection_dialog.hue: self.hue = self.hue_selection_dialog.hue self.spin_ctrl.SetValue(ratio_to_round_degrees(self.hue)) def _on_spin(self, event): self.hue_selection_dialog.setter(degrees_to_ratio( self.spin_ctrl.Value)) def _on_text(self, event): with self.value_freezer: self.hue_selection_dialog.setter( degrees_to_ratio(self.spin_ctrl.Value)) def set_focus_on_spin_ctrl_and_select_all(self): ''' The "select all" part works only on Windows and generic `wx.SpinCtrl` implementations. ''' self.spin_ctrl.SetFocus() self.spin_ctrl.SetSelection(-1, -1)