Source code for menus.radio

# ../menus/radio.py

"""Provides ShowMenu based functionality."""

# =============================================================================
# >> IMPORTS
# =============================================================================
# Source.Python Imports
#   Core
from core import SOURCE_ENGINE
#   Menus
from menus.base import Text
from menus.base import _BaseMenu
from menus.base import _PagedMenuBase
from menus.base import _BaseOption
from menus.base import _translate_text
from menus.queue import _radio_queues
#   Messages
from messages import ShowMenu


# =============================================================================
# >> ALL DECLARATION
# =============================================================================
__all__ = ('ListRadioMenu',
           'ListRadioOption',
           'PagedRadioMenu',
           'PagedRadioOption',
           'SimpleRadioMenu',
           'SimpleRadioOption',
           )


# =============================================================================
# >> CONSTANTS
# =============================================================================
if SOURCE_ENGINE in ('csgo', 'bms'):
    BUTTON_BACK = 7
    BUTTON_NEXT = 8
    BUTTON_CLOSE = 9
    BUTTON_CLOSE_SLOT = BUTTON_CLOSE
    MAX_ITEM_COUNT = 6
    VALID_CHOICES = range(1, 10)
else:
    BUTTON_BACK = 8
    BUTTON_NEXT = 9
    BUTTON_CLOSE = 10
    BUTTON_CLOSE_SLOT = 0
    MAX_ITEM_COUNT = 7
    VALID_CHOICES = range(1, 11)


# =============================================================================
# >> CLASSES
# =============================================================================
[docs]class SimpleRadioMenu(_BaseMenu): """This class creates a basic radio menu.""" def _get_menu_data(self, player_index): """Return the menu string, enabled slots, and time to show the menu. :param int player_index: See :meth:`menus.base._BaseMenu._get_menu_data`. """ # Always enable BUTTON_CLOSE_SLOT slots = {BUTTON_CLOSE_SLOT} buffer = '' page = self._player_pages[player_index] page.options = {} for raw_data in self: # Handle Text objects if isinstance(raw_data, Text): buffer += raw_data._render(player_index) # Handle _BaseOption objects elif isinstance(raw_data, SimpleRadioOption): buffer += raw_data._render(player_index) if raw_data.selectable: slots.add(raw_data.choice_index) page.options[raw_data.choice_index] = raw_data # Handle every other object type as a text else: buffer += Text(raw_data)._render(player_index) # Return the menu data return (buffer[:-1] if buffer else '', self._slots_to_bin(slots), 1) @staticmethod def _slots_to_bin(slots): """Convert an iterable of slots to the binary slot representation. :param iterable slots: Slots that should be enabled. :raise ValueError: Raised if a slot is out of range. """ # Keys are enabled in that order: 0987654321 buffer = list('0000000000') for slot in slots: if 0 <= slot <= 9: buffer[~(slot - 1)] = '1' else: raise ValueError('Slot out of range: {}'.format(slot)) return int(''.join(buffer), 2) def _select(self, player_index, choice_index): """See :meth:`menus.base._BaseMenu._select`.""" if choice_index == BUTTON_CLOSE: return self._select_close(player_index) return super()._select( player_index, self._player_pages[player_index].options[choice_index]) def _send(self, player_index): """Build and sends the menu to the given player via ShowMenu. :param int player_index: See :meth:`menus.base._BaseMenu._send`. """ ShowMenu(*self._build(player_index)).send(player_index) @staticmethod def _close(player_index): """See :meth:`menus.base._BaseMenu._close`.""" # Send an empty menu ShowMenu('', 0b0000000000, 0).send(player_index) @staticmethod def _get_queue_holder(): """Return the queue for radio menus.""" return _radio_queues
[docs]class PagedRadioMenu(SimpleRadioMenu, _PagedMenuBase): """Create menus with an unlimited number of options. Navigation options will be added automatically. """
[docs] def __init__( self, data=None, select_callback=None, build_callback=None, close_callback=None, description=None, title=None, top_separator='-' * 30, bottom_separator='-' * 30, fill=True, parent_menu=None): """Initialize the object. :param iterable|None data: See :meth:`menus.base._BaseMenu.__init__`. :param callable|None select_callback: See :meth:`menus.base._BaseMenu.__init__`. :param callable|None build_callback: See :meth:`menus.base._BaseMenu.__init__`. :param callable|None close_callback: See :meth:`menus.base._BaseMenu.__init__`. :param str|None description: A description that is displayed under the title. :param str|None title: A title that is displayed at the top of the menu. :param str top_separator: A separator that is displayed right after the title/description. :param str bottom_separator: A separator that is displayed right after the body. :param bool fill: If True the menu will be filled so that it will always have the same size. :param _BaseMenu parent_menu: A menu that will be displayed when hitting 'Back' on the first page. """ super().__init__(data, select_callback, build_callback, close_callback) self.title = title self.description = description self.top_separator = top_separator self.bottom_separator = bottom_separator self.fill = fill self.parent_menu = parent_menu
@staticmethod def _get_max_item_count(): """Return the maximum possible item count per page.""" return MAX_ITEM_COUNT def _format_header(self, player_index, page, slots): """Prepare the header for the menu. :param int player_index: A player index. :param _PlayerPage page: The player's current page. :param slots: A set to which slots can be added. :type slots: :class:`set` """ # Create the page info string info = '[{0}/{1}]\n'.format(page.index + 1, self.page_count) buffer = '{0} {1}'.format(_translate_text( self.title, player_index), info) if self.title else info # Set description if present if self.description is not None: buffer += _translate_text(self.description, player_index) + '\n' # Set the top separator if present if self.top_separator is not None: buffer += self.top_separator + '\n' return buffer def _format_body(self, player_index, page, slots): """Prepare the body for the menu. :param int player_index: A player index. :param _PlayerPage page: The player's current page. :param slots: A set to which slots can be added. :type slots: :class:`set` """ buffer = '' # Get all options for the current page options = tuple(enumerate(self._get_options(page.index), 1)) page.options = dict(options) # Loop through all options of the current page for choice_index, option in options: if isinstance(option, PagedRadioOption): buffer += option._render(player_index, choice_index) if option.selectable: slots.add(choice_index) elif isinstance(option, Text): buffer += option._render(player_index, choice_index) else: buffer += Text(option)._render(player_index, choice_index) # Fill the rest of the menu if self.fill: buffer += ' \n' * (self._get_max_item_count() - len(options)) return buffer def _format_footer(self, player_index, page, slots): """Prepare the footer for the menu. :param int player_index: A player index. :param _PlayerPage page: The player's current page. :param slots: A set to which slots can be added. :type slots: :class:`set` """ buffer = '' # Set the bottom separator if present if self.bottom_separator is not None: buffer += self.bottom_separator + '\n' # TODO: Add translations # Add "Back" option back_selectable = page.index > 0 or self.parent_menu is not None buffer += PagedRadioOption( 'Back', highlight=back_selectable)._render( player_index, BUTTON_BACK) if back_selectable: slots.add(BUTTON_BACK) # Add "Next" option next_selectable = page.index < self.last_page_index buffer += PagedRadioOption( 'Next', highlight=next_selectable)._render( player_index, BUTTON_NEXT) if next_selectable: slots.add(BUTTON_NEXT) # Add "Close" option buffer += PagedRadioOption( 'Close', highlight=False)._render(player_index, BUTTON_CLOSE_SLOT) # Return the buffer return buffer def _get_menu_data(self, player_index): """Return all relevant menu data as a dictionary. :param int player_index: A player index. """ # Get the player's current page page = self._player_pages[player_index] # Always enable BUTTON_CLOSE_SLOT slots = {BUTTON_CLOSE_SLOT} # Format the menu buffer = self._format_header(player_index, page, slots) buffer += self._format_body(player_index, page, slots) buffer += self._format_footer(player_index, page, slots) # Return the menu data return (buffer[:-1] if buffer else '', self._slots_to_bin(slots), 1) def _select(self, player_index, choice_index): """See :meth:`menus.base._BaseMenu._select`.""" if choice_index == BUTTON_CLOSE: del self._player_pages[player_index] return self._select_close(player_index) # Get the player's current page page = self._player_pages[player_index] # Display previous page? if choice_index == BUTTON_BACK: # Is the player on the first page, and do we have a parent menu? if not page.index and self.parent_menu is not None: return self.parent_menu self.set_player_page(player_index, page.index - 1) return self # Display next page? if choice_index == BUTTON_NEXT: self.set_player_page(player_index, page.index + 1) return self return super()._select(player_index, choice_index)
[docs]class ListRadioMenu(PagedRadioMenu): """Creates a list-like radio menu. Navigation options are added automatically."""
[docs] def __init__( self, data=None, select_callback=None, build_callback=None, close_callback=None, description=None, title=None, top_separator='-' * 30, bottom_separator='-' * 30, fill=True, parent_menu=None, items_per_page=10): """Initialize the object. :param iterable|None data: See :meth:`menus.base._BaseMenu.__init__`. :param callable|None select_callback: See :meth:`menus.base._BaseMenu.__init__`. :param callable|None build_callback: See :meth:`menus.base._BaseMenu.__init__`. :param callable|None close_callback: See :meth:`menus.base._BaseMenu.__init__`. :param str|None description: See :meth:`PagedRadioMenu.__init__`. :param str|None title: See :meth:`PagedRadioMenu.__init__`. :param str top_separator: See :meth:`PagedRadioMenu.__init__`. :param str bottom_separator: See :meth:`PagedRadioMenu.__init__`. :param bool fill: See :meth:`PagedRadioMenu.__init__`. :param _BaseMenu parent_menu: See :meth:`PagedRadioMenu.__init__`. :param int items_per_page: Number of options that should be displayed on a single page. """ super().__init__(data, select_callback, build_callback, close_callback, description, title, top_separator, bottom_separator, fill, parent_menu) self.items_per_page = items_per_page
def _get_max_item_count(self): return self.items_per_page
class _BaseRadioOption(_BaseOption): """Base class for radio options.""" def _get_highlight_prefix(self): """Return the hightlight prefix if ``highlight`` was set. Else it will return an empty string. """ return '->' if self.highlight else ''
[docs]class SimpleRadioOption(_BaseRadioOption): """Provides options for :class:`SimpleRadioMenu` objects."""
[docs] def __init__( self, choice_index, text, value=None, highlight=True, selectable=True): """Initialize the option. :param int choice_index: The number that is required to select the option. :param str text: See :meth:`menus.base._BaseOption.__init__`. :param value: See :meth:`menus.base._BaseOption.__init__`. :param bool hightlight: If True the text will be highlighted. :param bool selectable: If True the option will be selectable. """ super().__init__(text, value, highlight, selectable) self.choice_index = choice_index
def _render(self, player_index, choice_index=None): """See :meth:`menus.base._MenuData._render`.""" return '{0}{1}. {2}\n'.format( self._get_highlight_prefix(), self.choice_index, _translate_text(self.text, player_index) )
[docs]class PagedRadioOption(_BaseRadioOption): """Provides options for :class:`PagedRadioMenu` objects.""" def _render(self, player_index, choice_index): """See :meth:`menus.base._MenuData._render`.""" return '{0}{1}. {2}\n'.format( self._get_highlight_prefix(), choice_index, _translate_text(self.text, player_index) )
[docs]class ListRadioOption(PagedRadioOption): """Provides options for :class:`ListRadioMenu` objects."""
[docs] def __init__(self, text, highlight=True, enumerated=True): """Initialize the option. :param str text: See :meth:`menus.base._BaseOption.__init__`. :param bool hightlight: If True the text will be highlighted. :param bool enumerated: If True the number of the option will be added in front of the text. .. note:: ``highlight`` only works if ``enumerated`` is set to True. """ super().__init__(text, None, highlight, False) self.enumerated = enumerated
def _render(self, player_index, choice_index): """See :meth:`menus.base._MenuData._render`.""" if self.enumerated: return super()._render(player_index, choice_index) return '{}{}\n'.format(self._get_highlight_prefix(), _translate_text(self.text, player_index))