Simplify and expand building menus with proper callables and at-a-glance descriptions

This commit is contained in:
Vincent Le Goff 2018-03-28 14:06:51 +02:00
parent bfe9dde655
commit ec359503ac

View file

@ -11,7 +11,7 @@ Building menus are similar to `EvMenu`, except that they have been specifically-
[T]itle: the limbo room [T]itle: the limbo room
[D]escription [D]escription
This is the limbo room. You can easily change this default description, This is the limbo room. You can easily change this default description,
either by using the |y@desc/edit|n command, or simply by selecting this either by using the |y@desc/edit|n command, or simply by entering this
menu (enter |yd|n). menu (enter |yd|n).
[E]xits: [E]xits:
north to A parking(#4) north to A parking(#4)
@ -124,7 +124,7 @@ class Choice(object):
"""A choice object, created by `add_choice`.""" """A choice object, created by `add_choice`."""
def __init__(self, title, key=None, aliases=None, attr=None, on_select=None, on_nomatch=None, text=None, brief=None, def __init__(self, title, key=None, aliases=None, attr=None, text=None, glance=None, on_enter=None, on_nomatch=None, on_leave=None,
menu=None, caller=None, obj=None): menu=None, caller=None, obj=None):
"""Constructor. """Constructor.
@ -134,15 +134,16 @@ class Choice(object):
the sub-neu. If not set, try to guess it based on the title. the sub-neu. If not set, try to guess it based on the title.
aliases (list of str, optional): the allowed aliases for this choice. aliases (list of str, optional): the allowed aliases for this choice.
attr (str, optional): the name of the attribute of 'obj' to set. attr (str, optional): the name of the attribute of 'obj' to set.
on_select (callable, optional): a callable to call when the choice is selected.
on_nomatch (callable, optional): a callable to call when no match is entered in the choice.
text (str or callable, optional): a text to be displayed when text (str or callable, optional): a text to be displayed when
the menu is opened It can be a callable. the menu is opened It can be a callable.
brief (str or callable, optional): a brief summary of the glance (str or callable, optional): an at-a-glance summary of the
sub-menu shown in the main menu. It can be set to sub-menu shown in the main menu. It can be set to
display the current value of the attribute in the display the current value of the attribute in the
main menu itself. main menu itself.
menu (BuildingMenu, optional): the parent building menu. menu (BuildingMenu, optional): the parent building menu.
on_enter (callable, optional): a callable to call when the choice is entered.
on_nomatch (callable, optional): a callable to call when no match is entered in the choice.
on_leave (callable, optional): a callable to call when the caller leaves the choice.
caller (Account or Object, optional): the caller. caller (Account or Object, optional): the caller.
obj (Object, optional): the object to edit. obj (Object, optional): the object to edit.
@ -151,10 +152,11 @@ class Choice(object):
self.key = key self.key = key
self.aliases = aliases self.aliases = aliases
self.attr = attr self.attr = attr
self.on_select = on_select
self.on_nomatch = on_nomatch
self.text = text self.text = text
self.brief = brief self.glance = glance
self.on_enter = on_enter
self.on_nomatch = on_nomatch
self.on_leave = on_leave
self.menu = menu self.menu = menu
self.caller = caller self.caller = caller
self.obj = obj self.obj = obj
@ -162,15 +164,13 @@ class Choice(object):
def __repr__(self): def __repr__(self):
return "<Choice (title={}, key={})>".format(self.title, self.key) return "<Choice (title={}, key={})>".format(self.title, self.key)
def select(self, string): def enter(self, string):
"""Called when the user opens the choice.""" """Called when the user opens the choice."""
if self.on_select: if self.on_enter:
_call_or_get(self.on_select, menu=self.menu, choice=self, string=string, caller=self.caller, obj=self.obj) _call_or_get(self.on_enter, menu=self.menu, choice=self, string=string, caller=self.caller, obj=self.obj)
# Display the text if there is some # Display the text if there is some
if self.text: self.display_text()
self.caller.msg(_call_or_get(self.text, menu=self.menu, choice=self, string=string, caller=self.caller, obj=self.obj))
def nomatch(self, string): def nomatch(self, string):
"""Called when the user entered something that wasn't a command in a given choice. """Called when the user entered something that wasn't a command in a given choice.
@ -184,8 +184,9 @@ class Choice(object):
def display_text(self): def display_text(self):
"""Display the choice text to the caller.""" """Display the choice text to the caller."""
text = _call_or_get(self.text, menu=self.menu, choice=self, string="", caller=self.caller, obj=self.obj) if self.text:
return text.format(obj=self.obj, caller=self.caller) text = _call_or_get(self.text, menu=self.menu, choice=self, string="", caller=self.caller, obj=self.obj)
self.caller.msg(text.format(obj=self.obj, caller=self.caller))
class BuildingMenu(object): class BuildingMenu(object):
@ -206,6 +207,9 @@ class BuildingMenu(object):
""" """
keys_go_back = ["@"]
min_shortcut = 1
def __init__(self, caller=None, obj=None, title="Building menu: {obj}", key=None): def __init__(self, caller=None, obj=None, title="Building menu: {obj}", key=None):
"""Constructor, you shouldn't override. See `init` instead. """Constructor, you shouldn't override. See `init` instead.
@ -220,9 +224,6 @@ class BuildingMenu(object):
self.key = key self.key = key
self.cmds = {} self.cmds = {}
# Options (can be overridden in init)
self.min_shortcut = 1
if obj: if obj:
self.init(obj) self.init(obj)
@ -263,7 +264,8 @@ class BuildingMenu(object):
""" """
pass pass
def add_choice(self, title, key=None, aliases=None, attr=None, on_select=None, on_nomatch=None, text=None, brief=None): def add_choice(self, title, key=None, aliases=None, attr=None, text=None, glance=None,
on_enter=None, on_nomatch=None, on_leave=None):
"""Add a choice, a valid sub-menu, in the current builder menu. """Add a choice, a valid sub-menu, in the current builder menu.
Args: Args:
@ -272,16 +274,17 @@ class BuildingMenu(object):
the sub-neu. If not set, try to guess it based on the title. the sub-neu. If not set, try to guess it based on the title.
aliases (list of str, optional): the allowed aliases for this choice. aliases (list of str, optional): the allowed aliases for this choice.
attr (str, optional): the name of the attribute of 'obj' to set. attr (str, optional): the name of the attribute of 'obj' to set.
on_select (callable, optional): a callable to call when the choice is selected.
on_nomatch (callable, optional): a callable to call when no match is entered in the choice.
is set in `attr`. If `attr` is not set, you should
specify a function that both callback and set the value in `obj`.
text (str or callable, optional): a text to be displayed when text (str or callable, optional): a text to be displayed when
the menu is opened It can be a callable. the menu is opened It can be a callable.
brief (str or callable, optional): a brief summary of the glance (str or callable, optional): an at-a-glance summary of the
sub-menu shown in the main menu. It can be set to sub-menu shown in the main menu. It can be set to
display the current value of the attribute in the display the current value of the attribute in the
main menu itself. main menu itself.
on_enter (callable, optional): a callable to call when the choice is entered.
on_nomatch (callable, optional): a callable to call when no match is entered in the choice.
is set in `attr`. If `attr` is not set, you should
specify a function that both callback and set the value in `obj`.
on_leave (callable, optional): a callable to call when the caller leaves the choice.
Note: Note:
All arguments can be a callable, like a function. This has the All arguments can be a callable, like a function. This has the
@ -298,7 +301,7 @@ class BuildingMenu(object):
key = key.lower() key = key.lower()
aliases = aliases or [] aliases = aliases or []
aliases = [a.lower() for a in aliases] aliases = [a.lower() for a in aliases]
if on_select is None and on_nomatch is None: if on_enter is None and on_nomatch is None:
if attr is None: if attr is None:
raise ValueError("The choice {} has neither attr nor callback, specify one of these as arguments".format(title)) raise ValueError("The choice {} has neither attr nor callback, specify one of these as arguments".format(title))
@ -311,8 +314,8 @@ class BuildingMenu(object):
if key and key in self.cmds: if key and key in self.cmds:
raise ValueError("A conflict exists between {} and {}, both use key or alias {}".format(self.cmds[key], title, repr(key))) raise ValueError("A conflict exists between {} and {}, both use key or alias {}".format(self.cmds[key], title, repr(key)))
choice = Choice(title, key=key, aliases=aliases, attr=attr, on_select=on_select, on_nomatch=on_nomatch, text=text, choice = Choice(title, key=key, aliases=aliases, attr=attr, text=text, glance=glance, on_enter=on_enter, on_nomatch=on_nomatch, on_leave=on_leave,
brief=brief, menu=self, caller=self.caller, obj=self.obj) menu=self, caller=self.caller, obj=self.obj)
self.choices.append(choice) self.choices.append(choice)
if key: if key:
self.cmds[key] = choice self.cmds[key] = choice
@ -320,7 +323,7 @@ class BuildingMenu(object):
for alias in aliases: for alias in aliases:
self.cmds[alias] = choice self.cmds[alias] = choice
def add_choice_quit(self, title="quit the menu", key="q", aliases=None): def add_choice_quit(self, title="quit the menu", key="q", aliases=None, on_enter=None):
""" """
Add a simple choice just to quit the building menu. Add a simple choice just to quit the building menu.
@ -328,12 +331,18 @@ class BuildingMenu(object):
title (str, optional): the choice title. title (str, optional): the choice title.
key (str, optional): the choice key. key (str, optional): the choice key.
aliases (list of str, optional): the choice aliases. aliases (list of str, optional): the choice aliases.
on_enter (callable, optional): a different callable to quit the building menu.
Note: Note:
This is just a shortcut method, calling `add_choice`. This is just a shortcut method, calling `add_choice`.
If `on_enter` is not set, use `menu_quit` which simply
closes the menu and displays a message. It also
removes the CmdSet from the caller. If you supply
another callable instead, make sure to do the same.
""" """
return self.add_choice(title, key=key, aliases=aliases, on_select=menu_quit) on_enter = on_enter or menu_quit
return self.add_choice(title, key=key, aliases=aliases, on_enter=on_enter)
def _generate_commands(self, cmdset): def _generate_commands(self, cmdset):
""" """
@ -363,13 +372,7 @@ class BuildingMenu(object):
caller = self.caller caller = self.caller
self._save() self._save()
self.caller.cmdset.add(BuildingMenuCmdSet, permanent=True) self.caller.cmdset.add(BuildingMenuCmdSet, permanent=True)
self.display()
# Try to find the newly added cmdset (a shortcut would be nice)
for cmdset in self.caller.cmdset.get():
if isinstance(cmdset, BuildingMenuCmdSet):
self._generate_commands(cmdset)
self.display()
return
# Display methods. Override for customization # Display methods. Override for customization
def display_title(self): def display_title(self):
@ -391,6 +394,10 @@ class BuildingMenu(object):
ret += title[:pos] + "[|y" + choice.key.title() + "|n]" + title[pos + len(choice.key):] ret += title[:pos] + "[|y" + choice.key.title() + "|n]" + title[pos + len(choice.key):]
else: else:
ret += "[|y" + choice.key.title() + "|n] " + title ret += "[|y" + choice.key.title() + "|n] " + title
if choice.glance:
glance = _call_or_get(choice.glance, menu=self, choice=choice, caller=self.caller, string="", obj=self.obj)
glance = glance.format(obj=self.obj, caller=self.caller)
ret += ": " + glance
return ret return ret
@ -415,7 +422,7 @@ class BuildingMenu(object):
saved in the caller, but the object itself cannot be found. saved in the caller, but the object itself cannot be found.
""" """
menu = caller.db._buildingmenu menu = caller.db._building_menu
if menu: if menu:
class_name = menu.get("class") class_name = menu.get("class")
if not class_name: if not class_name:
@ -426,10 +433,11 @@ class BuildingMenu(object):
menu_class = class_from_module(class_name) menu_class = class_from_module(class_name)
except Exception: except Exception:
log_trace("BuildingMenu: attempting to load class {} failed".format(repr(class_name))) log_trace("BuildingMenu: attempting to load class {} failed".format(repr(class_name)))
return False return
# Create the menu # Create the menu
obj = menu.get("obj") obj = menu.get("obj")
key = menu.get("key")
try: try:
building_menu = menu_class(caller, obj) building_menu = menu_class(caller, obj)
except Exception: except Exception:
@ -437,6 +445,7 @@ class BuildingMenu(object):
return False return False
# If there's no saved key, add the menu commands # If there's no saved key, add the menu commands
building_menu.key = key
building_menu._generate_commands(cmdset) building_menu._generate_commands(cmdset)
return building_menu return building_menu
@ -463,12 +472,9 @@ class MenuCommand(Command):
self.menu.key = self.choice.key self.menu.key = self.choice.key
self.menu._save() self.menu._save()
for cmdset in self.caller.cmdset.get(): self.caller.cmdset.delete(BuildingMenuCmdSet)
if isinstance(cmdset, BuildingMenuCmdSet): self.caller.cmdset.add(BuildingMenuCmdSet, permanent=True)
for command in cmdset: self.choice.enter(self.raw_string)
cmdset.remove(command)
break
self.choice.select(self.raw_string)
class CmdNoInput(MenuCommand): class CmdNoInput(MenuCommand):
@ -512,10 +518,11 @@ class CmdNoMatch(MenuCommand):
log_err("When CMDNOMATCH was called, the building menu couldn't be found") log_err("When CMDNOMATCH was called, the building menu couldn't be found")
self.caller.msg("|rThe building menu couldn't be found, remove the CmdSet.|n") self.caller.msg("|rThe building menu couldn't be found, remove the CmdSet.|n")
self.caller.cmdset.delete(BuildingMenuCmdSet) self.caller.cmdset.delete(BuildingMenuCmdSet)
elif self.args == "/" and self.menu.key: elif raw_string in self.menu.keys_go_back and self.menu.key:
self.menu.key = None self.menu.key = None
self.menu._save() self.menu._save()
self.menu._generate_commands(cmdset) self.caller.cmdset.delete(BuildingMenuCmdSet)
self.caller.cmdset.add(BuildingMenuCmdSet, permanent=True)
self.menu.display() self.menu.display()
elif self.menu.key: elif self.menu.key:
choice.nomatch(raw_string) choice.nomatch(raw_string)