from typing import Any, Callable, Dict, List, Optional, Tuple, Union import urwid from zulipterminal.config.keys import is_command_key, keys_for_command from zulipterminal.urwid_types import urwid_Size class MenuButton(urwid.Button): def __init__(self, caption: Any, email: str='') -> None: self.caption = caption # str self.email = email super().__init__("") self._w = urwid.AttrMap(urwid.SelectableIcon( [self.caption], 0), None, 'selected') class TopButton(urwid.Button): def __init__(self, controller: Any, caption: str, show_function: Callable[..., Any], width: int, prefix_character: Union[str, Tuple[Any, str]]='\N{BULLET}', text_color: Optional[str]=None, count: int=0) -> None: if isinstance(prefix_character, tuple): prefix = prefix_character[1] else: prefix = prefix_character assert len(prefix) in (0, 1) self._caption = caption self.prefix_character = prefix_character self.post_prefix_spacing = ' ' if prefix else '' self.count = count prefix_length = 0 if prefix == '' else 2 # Space either side, at least one space between self.width_for_text_and_count = width - 3 - prefix_length self.text_color = text_color self.show_function = show_function super().__init__("") self.update_count(count) self.controller = controller urwid.connect_signal(self, 'click', self.activate) def update_count(self, count: int) -> None: self.count = count if count == 0: count_text = '' else: count_text = str(count) self.update_widget(count_text) def update_widget(self, count_text: str) -> Any: # Note that we don't modify self._caption max_caption_length = (self.width_for_text_and_count - len(count_text)) if len(self._caption) > max_caption_length: caption = (self._caption[:max_caption_length - 1] + '\N{HORIZONTAL ELLIPSIS}') else: caption = self._caption num_extra_spaces = ( self.width_for_text_and_count - len(count_text) - len(caption) ) # NOTE: Generated text does not include space at end self._w = urwid.AttrMap(urwid.SelectableIcon( [' ', self.prefix_character, self.post_prefix_spacing, '{}{}'.format(caption, num_extra_spaces * ' '), ' ', ('unread_count', count_text)], self.width_for_text_and_count + 5), # cursor location self.text_color, 'selected') def activate(self, key: Any) -> None: self.controller.view.show_left_panel(visible=False) self.controller.view.show_right_panel(visible=False) self.controller.view.body.focus_col = 1 self.show_function(self) def keypress(self, size: urwid_Size, key: str) -> Optional[str]: if is_command_key('ENTER', key): self.activate(key) return None else: # This is in the else clause, to avoid multiple activation return super().keypress(size, key) class HomeButton(TopButton): def __init__(self, controller: Any, width: int, count: int=0) -> None: button_text = ("All messages [" + keys_for_command("GO_BACK").pop() # FIXME + "]") super().__init__(controller, button_text, controller.show_all_messages, count=count, prefix_character='', width=width) class PMButton(TopButton): def __init__(self, controller: Any, width: int, count: int=0) -> None: button_text = ("Private messages [" + keys_for_command("ALL_PM").pop() + "]") super().__init__(controller, button_text, controller.show_all_pm, count=count, prefix_character='', width=width) class MentionedButton(TopButton): def __init__(self, controller: Any, width: int, count: int=0) -> None: button_text = ("Mentions [" + keys_for_command("ALL_MENTIONS").pop() + "]") super().__init__(controller, button_text, controller.show_all_mentions, width=width, count=count, prefix_character='') class StarredButton(TopButton): def __init__(self, controller: Any, width: int) -> None: button_text = ("Starred messages [" + keys_for_command("ALL_STARRED").pop() + "]") super().__init__(controller, button_text, controller.show_all_starred, width=width, prefix_character='', count=0) # Starred messages are already marked read class StreamButton(TopButton): def __init__(self, properties: List[Any], controller: Any, view: Any, width: int, count: int=0) -> None: # FIXME Is having self.stream_id the best way to do this? # (self.stream_id is used elsewhere) (self.stream_name, self.stream_id, self.color, is_private, self.description) = properties self.model = controller.model self.count = count self.view = view for entry in view.palette: if entry[0] is None: background = entry[5] if len(entry) > 4 else entry[2] inverse_text = background if background else 'black' break view.palette.append(( self.color, '', '', 'bold', self.color + ', bold', background)) view.palette.append(( 's' + self.color, '', '', 'standout', inverse_text, self.color)) super().__init__(controller, caption=self.stream_name, show_function=controller.narrow_to_stream, prefix_character=( self.color, 'P' if is_private else '#'), width=width, count=count) # Mark muted streams 'M' during button creation. if self.model.is_muted_stream(self.stream_id): self.mark_muted() def mark_muted(self) -> None: self.update_widget('M') self.model.unread_counts['all_msg'] -= self.count self.view.home_button.update_count( self.model.unread_counts['all_msg']) def mark_unmuted(self) -> None: if self.stream_id in self.model.unread_counts['streams']: unmuted_count = self.model.unread_counts['streams'][self.stream_id] self.update_count(unmuted_count) self.model.unread_counts['all_msg'] += self.count self.view.home_button.update_count( self.model.unread_counts['all_msg']) else: # All messages in this stream are read. self.update_count(0) def keypress(self, size: urwid_Size, key: str) -> Optional[str]: if is_command_key('TOGGLE_TOPIC', key): topic_view = self.view.left_panel.topics_view(self) self.view.left_panel.is_in_topic_view = True self.view.left_panel.contents[1] = ( topic_view, self.view.left_panel.options(height_type="weight") ) elif is_command_key('TOGGLE_MUTE_STREAM', key): self.controller.stream_muting_confirmation_popup(self) elif is_command_key('STREAM_DESC', key): self.model.controller.show_stream_info( self.color, self.stream_name, self.description) return super().keypress(size, key) class UserButton(TopButton): def __init__(self, user: Dict[str, Any], controller: Any, view: Any, width: int, color: Optional[str]=None, count: int=0) -> None: # Properties accessed externally self.email = user['email'] self.user_id = user['user_id'] self._view = view # Used in _narrow_with_compose # FIXME Is this still needed? self.recipients = frozenset({self.user_id, view.model.user_id}) super().__init__(controller, caption=user['full_name'], show_function=self._narrow_with_compose, prefix_character=(color, '\N{BULLET}'), text_color=color, width=width, count=count) def _narrow_with_compose(self, button: Any) -> None: # Switches directly to composing with user # FIXME should we just narrow? self.controller.narrow_to_user(self) self._view.body.focus.original_widget.set_focus('footer') self._view.write_box.private_box_view(self) class TopicButton(TopButton): def __init__(self, stream_id: int, topic: str, controller: Any, width: int=0, count: int=0) -> None: self.stream_name = controller.model.stream_dict[stream_id]['name'] self.topic_name = topic self.stream_id = stream_id self.model = controller.model super().__init__(controller=controller, caption=self.topic_name, prefix_character='', show_function=controller.narrow_to_topic, width=width, count=count) topic_pair = [self.stream_name, self.topic_name] if topic_pair in controller.model.muted_topics: self.mark_muted() def mark_muted(self) -> None: self.update_widget('M') # TODO: Handle event-based approach for topic-muting. class UnreadPMButton(urwid.Button): def __init__(self, user_id: int, email: str) -> None: self.user_id = user_id self.email = email