class RulesView(Widget): """The view to edit a rule Presents three tabs, one each for trigger, prereq, and action. Each has a deckbuilder in it with a column of used functions and a column of unused actions. """ engine = ObjectProperty() rulebook = ObjectProperty() entity = ObjectProperty() rule = ObjectProperty(allownone=True) def on_rule(self, *args): """Make sure to update when the rule changes""" if self.rule is None: return self.rule.connect(self._listen_to_rule) def _listen_to_rule(self, rule, **kwargs): if rule is not self.rule: rule.disconnect(self._listen_to_rule) return if 'triggers' in kwargs: self.pull_triggers() if 'prereqs' in kwargs: self.pull_prereqs() if 'actions' in kwargs: self.pull_actions() def __init__(self, **kwargs): super().__init__(**kwargs) self.finalize() def finalize(self, *args): """Add my tabs""" if not self.canvas: Clock.schedule_once(self.finalize, 0) return deck_builder_kwargs = { 'pos_hint': { 'x': 0, 'y': 0 }, 'starting_pos_hint': { 'x': 0.05, 'top': 0.95 }, 'card_size_hint': (0.3, 0.4), 'card_hint_step': (0, -0.1), 'deck_x_hint_step': 0.4 } self._tabs = TabbedPanel(size=self.size, pos=self.pos, do_default_tab=False) self.bind(size=self._tabs.setter('size'), pos=self._tabs.setter('pos')) self.add_widget(self._tabs) for functyp in 'trigger', 'prereq', 'action': tab = TabbedPanelItem(text=functyp.capitalize()) setattr(self, '_{}_tab'.format(functyp), tab) self._tabs.add_widget(getattr(self, '_{}_tab'.format(functyp))) builder = DeckBuilderView(**deck_builder_kwargs) setattr(self, '_{}_builder'.format(functyp), builder) builder.bind( decks=getattr(self, '_trigger_push_{}s'.format(functyp))) scroll_left = DeckBuilderScrollBar(size_hint_x=0.01, pos_hint={ 'x': 0, 'y': 0 }, deckbuilder=builder, deckidx=0, scroll_min=0) setattr(self, '_scroll_left_' + functyp, scroll_left) scroll_right = DeckBuilderScrollBar(size_hint_x=0.01, pos_hint={ 'right': 1, 'y': 0 }, deckbuilder=builder, deckidx=1, scroll_min=0) setattr(self, '_scroll_right_' + functyp, scroll_right) layout = FloatLayout() setattr(self, '_{}_layout'.format(functyp), layout) tab.add_widget(layout) layout.add_widget(builder) layout.add_widget(scroll_left) layout.add_widget(scroll_right) layout.add_widget( Label(text='Used', pos_hint={ 'center_x': 0.1, 'center_y': 0.98 }, size_hint=(None, None))) layout.add_widget( Label(text='Unused', pos_hint={ 'center_x': 0.5, 'center_y': 0.98 }, size_hint=(None, None))) self.bind(rule=getattr(self, '_trigger_pull_{}s'.format(functyp))) def get_functions_cards(self, what, allfuncs): """Return a pair of lists of Card widgets for used and unused functions. :param what: a string: 'trigger', 'prereq', or 'action' :param allfuncs: a sequence of functions' (name, sourcecode, signature) """ if not self.rule: return [], [] rulefuncnames = getattr(self.rule, what + 's') unused = [ Card(ud={ 'type': what, 'funcname': name, 'signature': sig }, headline_text=name, show_art=False, midline_text=what.capitalize(), text=source) for (name, source, sig) in allfuncs if name not in rulefuncnames ] used = [ Card(ud={ 'type': what, 'funcname': name, }, headline_text=name, show_art=False, midline_text=what.capitalize(), text=str(getattr(getattr(self.engine, what), name))) for name in rulefuncnames ] return used, unused def set_functions(self, what, allfuncs): """Set the cards in the ``what`` builder to ``allfuncs`` :param what: a string, 'trigger', 'prereq', or 'action' :param allfuncs: a sequence of triples of (name, sourcecode, signature) as taken by my ``get_function_cards`` method. """ setattr(getattr(self, '_{}_builder'.format(what)), 'decks', self.get_functions_cards(what, allfuncs)) def _pull_functions(self, what): return self.get_functions_cards( what, list( map(self.inspect_func, getattr(self.engine, what)._cache.items()))) def pull_triggers(self, *args): """Refresh the cards in the trigger builder""" self._trigger_builder.decks = self._pull_functions('trigger') _trigger_pull_triggers = trigger(pull_triggers) def pull_prereqs(self, *args): """Refresh the cards in the prereq builder""" self._prereq_builder.decks = self._pull_functions('prereq') _trigger_pull_prereqs = trigger(pull_prereqs) def pull_actions(self, *args): """Refresh the cards in the action builder""" self._action_builder.decks = self._pull_functions('action') _trigger_pull_actions = trigger(pull_actions) def inspect_func(self, namesrc): """Take a function's (name, sourcecode) and return a triple of (name, sourcecode, signature)""" (name, src) = namesrc glbls = {} lcls = {} exec(src, glbls, lcls) assert name in lcls func = lcls[name] return name, src, signature(func) def update_builders(self, *args): for attrn in '_trigger_builder', '_prereq_builder', '_action_builder': if not hasattr(self, attrn): dbg('RulesView: no {}'.format(attrn)) Clock.schedule_once(self.update_builders, 0) return self._trigger_builder.clear_widgets() self._prereq_builder.clear_widgets() self._action_builder.clear_widgets() if self.rule is None: dbg('RulesView: no rule') return if hasattr(self, '_list'): self._list.redata() self.pull_triggers() self.pull_prereqs() self.pull_actions() _trigger_update_builders = trigger(update_builders) def _upd_unused(self, what): """Make sure to have exactly one copy of every valid function in the "unused" pile on the right. Doesn't read from the database. :param what: a string, 'trigger', 'prereq', or 'action' """ builder = getattr(self, '_{}_builder'.format(what)) updtrig = getattr(self, '_trigger_upd_unused_{}s'.format(what)) builder.unbind(decks=updtrig) funcs = OrderedDict() cards = list(self._action_builder.decks[1]) cards.reverse() for card in cards: funcs[card.ud['funcname']] = card for card in self._action_builder.decks[0]: if card.ud['funcname'] not in funcs: funcs[card.ud['funcname']] = card.copy() unused = list(funcs.values()) unused.reverse() builder.decks[1] = unused builder.bind(decks=updtrig) def upd_unused_actions(self, *args): self._upd_unused('action') _trigger_upd_unused_actions = trigger(upd_unused_actions) def upd_unused_triggers(self, *args): self._upd_unused('trigger') _trigger_upd_unused_triggers = trigger(upd_unused_triggers) def upd_unused_prereqs(self, *args): self._upd_unused('prereq') _trigger_upd_unused_prereqs = trigger(upd_unused_prereqs) def _push_funcs(self, what): if not self.rule: Logger.debug( "RulesView: not pushing {} for lack of rule".format(what)) return funcs = [ card.ud['funcname'] for card in getattr(self, '_{}_builder'.format(what)).decks[0] ] funlist = getattr(self.rule, what + 's') if funlist != funcs: setattr(self.rule, what + 's', funcs) def push_actions(self, *args): self._push_funcs('action') _trigger_push_actions = trigger(push_actions) def push_prereqs(self, *args): self._push_funcs('prereq') _trigger_push_prereqs = trigger(push_prereqs) def push_triggers(self, att, *args): self._push_funcs('trigger') _trigger_push_triggers = trigger(push_triggers)
class RulesView(Widget): """The view to edit a rule Presents three tabs, one each for trigger, prereq, and action. Each has a deckbuilder in it with a column of used functions and a column of unused actions. """ engine = ObjectProperty() rulebook = ObjectProperty() entity = ObjectProperty() rule = ObjectProperty(allownone=True) def on_rule(self, *args): """Make sure to update when the rule changes""" if self.rule is None: return self.rule.connect(self._listen_to_rule) def _listen_to_rule(self, rule, **kwargs): if rule is not self.rule: rule.disconnect(self._listen_to_rule) return if 'triggers' in kwargs: self.pull_triggers() if 'prereqs' in kwargs: self.pull_prereqs() if 'actions' in kwargs: self.pull_actions() def __init__(self, **kwargs): super().__init__(**kwargs) self.finalize() def finalize(self, *args): """Add my tabs""" if not self.canvas: Clock.schedule_once(self.finalize, 0) return deck_builder_kwargs = { 'pos_hint': {'x': 0, 'y': 0}, 'starting_pos_hint': {'x': 0.05, 'top': 0.95}, 'card_size_hint': (0.3, 0.4), 'card_hint_step': (0, -0.1), 'deck_x_hint_step': 0.4 } self._tabs = TabbedPanel( size=self.size, pos=self.pos, do_default_tab=False ) self.bind( size=self._tabs.setter('size'), pos=self._tabs.setter('pos') ) self.add_widget(self._tabs) for functyp in 'trigger', 'prereq', 'action': tab = TabbedPanelItem(text=functyp.capitalize()) setattr(self, '_{}_tab'.format(functyp), tab) self._tabs.add_widget(getattr(self, '_{}_tab'.format(functyp))) builder = DeckBuilderView(**deck_builder_kwargs) setattr(self, '_{}_builder'.format(functyp), builder) builder.bind(decks=getattr(self, '_trigger_push_{}s'.format(functyp))) scroll_left = DeckBuilderScrollBar( size_hint_x=0.01, pos_hint={'x': 0, 'y': 0}, deckbuilder=builder, deckidx=0, scroll_min=0 ) setattr(self, '_scroll_left_' + functyp, scroll_left) scroll_right = DeckBuilderScrollBar( size_hint_x=0.01, pos_hint={'right': 1, 'y': 0}, deckbuilder=builder, deckidx=1, scroll_min=0 ) setattr(self, '_scroll_right_' + functyp, scroll_right) layout = FloatLayout() setattr(self, '_{}_layout'.format(functyp), layout) tab.add_widget(layout) layout.add_widget(builder) layout.add_widget(scroll_left) layout.add_widget(scroll_right) layout.add_widget( Label( text='Used', pos_hint={'center_x': 0.1, 'center_y': 0.98}, size_hint=(None, None) ) ) layout.add_widget( Label( text='Unused', pos_hint={'center_x': 0.5, 'center_y': 0.98}, size_hint=(None, None) ) ) self.bind(rule=getattr(self, '_trigger_pull_{}s'.format(functyp))) def get_functions_cards(self, what, allfuncs): """Return a pair of lists of Card widgets for used and unused functions. :param what: a string: 'trigger', 'prereq', or 'action' :param allfuncs: a sequence of functions' (name, sourcecode, signature) """ if not self.rule: return [], [] rulefuncnames = getattr(self.rule, what+'s') unused = [ Card( ud={ 'type': what, 'funcname': name, 'signature': sig }, headline_text=name, show_art=False, midline_text=what.capitalize(), text=source ) for (name, source, sig) in allfuncs if name not in rulefuncnames ] used = [ Card( ud={ 'type': what, 'funcname': name, }, headline_text=name, show_art=False, midline_text=what.capitalize(), text=str(getattr(getattr(self.engine, what), name)) ) for name in rulefuncnames ] return used, unused def set_functions(self, what, allfuncs): """Set the cards in the ``what`` builder to ``allfuncs`` :param what: a string, 'trigger', 'prereq', or 'action' :param allfuncs: a sequence of triples of (name, sourcecode, signature) as taken by my ``get_function_cards`` method. """ setattr(getattr(self, '_{}_builder'.format(what)), 'decks', self.get_functions_cards(what, allfuncs)) def _pull_functions(self, what): return self.get_functions_cards(what, list(map(self.inspect_func, getattr(self.engine, what)._cache.items()))) def pull_triggers(self, *args): """Refresh the cards in the trigger builder""" self._trigger_builder.decks = self._pull_functions('trigger') _trigger_pull_triggers = trigger(pull_triggers) def pull_prereqs(self, *args): """Refresh the cards in the prereq builder""" self._prereq_builder.decks = self._pull_functions('prereq') _trigger_pull_prereqs = trigger(pull_prereqs) def pull_actions(self, *args): """Refresh the cards in the action builder""" self._action_builder.decks = self._pull_functions('action') _trigger_pull_actions = trigger(pull_actions) def inspect_func(self, namesrc): """Take a function's (name, sourcecode) and return a triple of (name, sourcecode, signature)""" (name, src) = namesrc glbls = {} lcls = {} exec(src, glbls, lcls) assert name in lcls func = lcls[name] return name, src, signature(func) def update_builders(self, *args): for attrn in '_trigger_builder', '_prereq_builder', '_action_builder': if not hasattr(self, attrn): dbg('RulesView: no {}'.format(attrn)) Clock.schedule_once(self.update_builders, 0) return self._trigger_builder.clear_widgets() self._prereq_builder.clear_widgets() self._action_builder.clear_widgets() if self.rule is None: dbg('RulesView: no rule') return if hasattr(self, '_list'): self._list.redata() self.pull_triggers() self.pull_prereqs() self.pull_actions() _trigger_update_builders = trigger(update_builders) def _upd_unused(self, what): """Make sure to have exactly one copy of every valid function in the "unused" pile on the right. Doesn't read from the database. :param what: a string, 'trigger', 'prereq', or 'action' """ builder = getattr(self, '_{}_builder'.format(what)) updtrig = getattr(self, '_trigger_upd_unused_{}s'.format(what)) builder.unbind(decks=updtrig) funcs = OrderedDict() cards = list(self._action_builder.decks[1]) cards.reverse() for card in cards: funcs[card.ud['funcname']] = card for card in self._action_builder.decks[0]: if card.ud['funcname'] not in funcs: funcs[card.ud['funcname']] = card.copy() unused = list(funcs.values()) unused.reverse() builder.decks[1] = unused builder.bind(decks=updtrig) def upd_unused_actions(self, *args): self._upd_unused('action') _trigger_upd_unused_actions = trigger(upd_unused_actions) def upd_unused_triggers(self, *args): self._upd_unused('trigger') _trigger_upd_unused_triggers = trigger(upd_unused_triggers) def upd_unused_prereqs(self, *args): self._upd_unused('prereq') _trigger_upd_unused_prereqs = trigger(upd_unused_prereqs) def _push_funcs(self, what): if not self.rule: Logger.debug("RulesView: not pushing {} for lack of rule".format(what)) return funcs = [ card.ud['funcname'] for card in getattr(self, '_{}_builder'.format(what)).decks[0] ] funlist = getattr(self.rule, what+'s') if funlist != funcs: setattr(self.rule, what+'s', funcs) def push_actions(self, *args): self._push_funcs('action') _trigger_push_actions = trigger(push_actions) def push_prereqs(self, *args): self._push_funcs('prereq') _trigger_push_prereqs = trigger(push_prereqs) def push_triggers(self, att, *args): self._push_funcs('trigger') _trigger_push_triggers = trigger(push_triggers)