blob: 3c16beb6285fef32057fdf9af061ac366bb3d1ce [file] [log] [blame]
# Copyright 2021 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Window pane toolbar base class."""
import logging
from typing import Any, Callable, List, Optional
import functools
from prompt_toolkit.filters import Condition, has_focus
from prompt_toolkit.layout import (
ConditionalContainer,
FormattedTextControl,
VSplit,
Window,
WindowAlign,
)
from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
from pw_console.get_pw_console_app import get_pw_console_app
import pw_console.style
from pw_console.widgets import (
ToolbarButton,
to_checkbox_with_keybind_indicator,
to_keybind_indicator,
)
import pw_console.widgets.mouse_handlers
_LOG = logging.getLogger(__package__)
class WindowPaneResizeHandle(FormattedTextControl):
"""Button to initiate window pane resize drag events."""
def __init__(self, parent_window_pane: Any, *args, **kwargs) -> None:
self.parent_window_pane = parent_window_pane
super().__init__(*args, **kwargs)
def mouse_handler(self, mouse_event: MouseEvent):
"""Mouse handler for this control."""
# Start resize mouse drag event
if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
get_pw_console_app().window_manager.start_resize_pane(
self.parent_window_pane)
# Mouse event handled, return None.
return None
# Mouse event not handled, return NotImplemented.
return NotImplemented
class WindowPaneToolbar:
"""One line toolbar for display at the bottom of of a window pane."""
# pylint: disable=too-many-instance-attributes
TOOLBAR_HEIGHT = 1
def get_left_text_tokens(self):
"""Return toolbar indicator and title."""
title = self.title
if not title and self.parent_window_pane:
# No title was set, fetch the parent window pane title if available.
parent_pane_title = self.parent_window_pane.pane_title()
title = parent_pane_title if parent_pane_title else title
return pw_console.style.get_pane_indicator(self.focus_check_container,
f' {title} ',
self.focus_mouse_handler)
def get_center_text_tokens(self):
"""Return formatted text tokens for display in the center part of the
toolbar."""
button_style = pw_console.style.get_button_style(
self.focus_check_container)
# FormattedTextTuple contents: (Style, Text, Mouse handler)
separator_text = [('', ' ')
] # 2 spaces of separaton between keybinds.
if self.focus_mouse_handler:
separator_text = [('', ' ', self.focus_mouse_handler)]
fragments = []
fragments.extend(separator_text)
for button in self.buttons:
on_click_handler = None
if button.mouse_handler:
on_click_handler = functools.partial(
pw_console.widgets.mouse_handlers.on_click,
button.mouse_handler)
if button.is_checkbox:
fragments.extend(
to_checkbox_with_keybind_indicator(
button.checked(),
button.key,
button.description,
on_click_handler,
base_style=button_style))
else:
fragments.extend(
to_keybind_indicator(button.key,
button.description,
on_click_handler,
base_style=button_style))
fragments.extend(separator_text)
# Remaining whitespace should focus on click.
fragments.extend(separator_text)
return fragments
def get_right_text_tokens(self):
"""Return formatted text tokens for display."""
fragments = []
if not has_focus(self.focus_check_container.__pt_container__())():
fragments.append((
'class:toolbar-button-inactive class:toolbar-button-decoration',
' ', self.focus_mouse_handler))
fragments.append(('class:toolbar-button-inactive class:keyhelp',
'click to focus', self.focus_mouse_handler))
fragments.append((
'class:toolbar-button-inactive class:toolbar-button-decoration',
' ', self.focus_mouse_handler))
fragments.append(
('', ' {} '.format(self.subtitle()), self.focus_mouse_handler))
return fragments
def get_resize_handle(self):
return pw_console.style.get_pane_indicator(self.focus_check_container,
'─══─',
hide_indicator=True)
def add_button(self, button: ToolbarButton):
self.buttons.append(button)
def __init__(
self,
parent_window_pane: Optional[Any] = None,
title: Optional[str] = None,
subtitle: Optional[Callable[[], str]] = None,
focus_check_container: Optional[Any] = None,
focus_action_callable: Optional[Callable] = None,
center_section_align: WindowAlign = WindowAlign.LEFT,
include_resize_handle: bool = True,
):
self.parent_window_pane = parent_window_pane
self.title = title
self.subtitle = subtitle
# Assume check this container for focus
self.focus_check_container = self
self.focus_action_callable = None
# Set parent_window_pane related options
if self.parent_window_pane:
if not subtitle:
self.subtitle = self.parent_window_pane.pane_subtitle
self.focus_check_container = self.parent_window_pane
self.focus_action_callable = self.parent_window_pane.focus_self
# Set title overrides
if self.subtitle is None:
def empty_subtitle() -> str:
return ''
self.subtitle = empty_subtitle
if focus_check_container:
self.focus_check_container = focus_check_container
if focus_action_callable:
self.focus_action_callable = focus_action_callable
self.focus_mouse_handler = None
if self.focus_action_callable:
self.focus_mouse_handler = functools.partial(
pw_console.widgets.mouse_handlers.on_click,
self.focus_action_callable)
self.buttons: List[ToolbarButton] = []
self.show_toolbar = True
self.left_section_window = Window(
content=FormattedTextControl(self.get_left_text_tokens),
align=WindowAlign.LEFT,
dont_extend_width=True,
)
self.center_section_window = Window(
content=FormattedTextControl(self.get_center_text_tokens),
align=center_section_align,
dont_extend_width=False,
)
self.right_section_window = Window(
content=FormattedTextControl(self.get_right_text_tokens),
# Right side text should appear at the far right of the toolbar
align=WindowAlign.RIGHT,
dont_extend_width=True,
)
get_toolbar_style = functools.partial(
pw_console.style.get_toolbar_style, self.focus_check_container)
sections = [
self.left_section_window,
self.center_section_window,
self.right_section_window,
]
if self.parent_window_pane and include_resize_handle:
resize_handle = Window(
content=WindowPaneResizeHandle(
self.parent_window_pane,
self.get_resize_handle,
),
# Right side text should appear at the far right of the toolbar
align=WindowAlign.RIGHT,
dont_extend_width=True,
)
sections.append(resize_handle)
self.toolbar_vsplit = VSplit(
sections,
height=WindowPaneToolbar.TOOLBAR_HEIGHT,
style=get_toolbar_style,
)
self.container = self._create_toolbar_container(self.toolbar_vsplit)
def _create_toolbar_container(self, content):
return ConditionalContainer(
content, filter=Condition(lambda: self.show_toolbar))
def __pt_container__(self):
"""Return the prompt_toolkit root container for this log pane.
This allows self to be used wherever prompt_toolkit expects a container
object."""
return self.container # pylint: disable=no-member