Skip to content

Commit

Permalink
Support for kb ordering mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
joce committed Dec 23, 2023
1 parent c653ef9 commit 96851e6
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 26 deletions.
105 changes: 90 additions & 15 deletions src/appui/_quote_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from rich.style import Style
from rich.text import Text
from textual import events
from textual.binding import _Bindings # type: ignore
from textual.coordinate import Coordinate
from textual.message import Message
Expand Down Expand Up @@ -38,16 +39,11 @@ def __init__(self, state: QuoteTableState) -> None:
self._state: QuoteTableState = state
self._version: int
self._column_key_map: dict[str, Any] = {}
self._current_hover_column: int = -1

self.cursor_type = "row"
self.zebra_stripes = True
self.cursor_foreground_priority = "renderable"

# Bindings
self._bindings_modes: dict[QuoteTable.BM, _Bindings] = {
QuoteTable.BM.DEFAULT: self._bindings.copy(),
QuoteTable.BM.IN_ORDERING: self._bindings.copy(),
QuoteTable.BM.IN_ORDERING: _Bindings(),
}

self._bindings_modes[QuoteTable.BM.DEFAULT].bind(
Expand All @@ -56,20 +52,40 @@ def __init__(self, state: QuoteTableState) -> None:
self._bindings_modes[QuoteTable.BM.DEFAULT].bind(
"insert", "add_quote", "Add quote", key_display="Ins"
)
self._bindings_modes[QuoteTable.BM.IN_ORDERING].bind(
"escape", "exit_ordering", "Done", key_display="Esc"
)

# For Delete, we want the same bindings as default, plus delete
self._bindings_modes[QuoteTable.BM.WITH_DELETE] = self._bindings_modes[
QuoteTable.BM.DEFAULT
].copy()
self._bindings_modes[QuoteTable.BM.WITH_DELETE].bind(
"delete", "remove_quote", "Remove quote", key_display="Del"
)

# For Ordering, we want to drop all default binding. No add / delete, or cursor
# movement.
self._bindings_modes[QuoteTable.BM.IN_ORDERING].bind(
"escape", "exit_ordering", "Done", key_display="Esc"
)
self._bindings_modes[QuoteTable.BM.IN_ORDERING].bind(
"right", "order_move_right", show=False
)
self._bindings_modes[QuoteTable.BM.IN_ORDERING].bind(
"left", "order_move_left", show=False
)
self._bindings_modes[QuoteTable.BM.IN_ORDERING].bind(
"enter", "order_toggle", show=False
)

# TODO This maybe should be part of the state... hum...
self._current_bindings = QuoteTable.BM.DEFAULT
self._bindings = self._bindings_modes[self._current_bindings]

# The following (especially the cursor type) need to be set after the binding
# modes have been created
self.cursor_type = "row"
self.zebra_stripes = True
self.cursor_foreground_priority = "renderable"

def __del__(self) -> None:
# Make sure the query thread is stopped
self._state.query_thread_running = False
Expand Down Expand Up @@ -220,13 +236,30 @@ def _get_styled_cell(self, cell: QuoteCell) -> Text:

@override
def watch_hover_coordinate(self, old: Coordinate, value: Coordinate) -> None:
if self._current_bindings == QuoteTable.BM.IN_ORDERING:
return

if value.row == -1:
self._current_hover_column = value.column
self._state.hovered_column = value.column
else:
self._current_hover_column = -1
self._state.hovered_column = -1

super().watch_hover_coordinate(old, value)

@override
async def _on_click(self, event: events.Click) -> None:
# Prevent mouse interaction when in ordering (KB-only) mode
if self._current_bindings == QuoteTable.BM.IN_ORDERING:
event.prevent_default()
return

@override
def _on_mouse_move(self, event: events.MouseMove) -> None:
# Prevent mouse interaction when in ordering (KB-only) mode
if self._current_bindings == QuoteTable.BM.IN_ORDERING:
event.prevent_default()
return

@override
def _render_cell(
self,
Expand All @@ -237,12 +270,20 @@ def _render_cell(
cursor: bool = False,
hover: bool = False,
):
current_show_hover_cursor: bool = self._show_hover_cursor
if row_index == -1:
hover = self._current_hover_column == column_index
if self._current_bindings == QuoteTable.BM.IN_ORDERING:
self._show_hover_cursor = True
hover = self._state.hovered_column == column_index # Mouse mode

return super()._render_cell(
row_index, column_index, base_style, width, cursor, hover
)
try:
return super()._render_cell(
row_index, column_index, base_style, width, cursor, hover
)
finally:
if row_index == -1:
if self._current_bindings == QuoteTable.BM.IN_ORDERING:
self._show_hover_cursor = current_show_hover_cursor

@override
def watch_cursor_coordinate(
Expand Down Expand Up @@ -285,6 +326,10 @@ def action_order_quotes(self) -> None:
"""Order the quotes in the table."""

self._switch_bindings(QuoteTable.BM.IN_ORDERING)
self._set_hover_cursor(False)
if self._state.hovered_column == -1:
self._state.hovered_column = self._state.sort_column_idx
self._version -= 1 # Force refresh

def action_exit_ordering(self) -> None:
"""Exit the ordering mode."""
Expand All @@ -293,3 +338,33 @@ def action_exit_ordering(self) -> None:
self._switch_bindings(QuoteTable.BM.WITH_DELETE)
else:
self._switch_bindings(QuoteTable.BM.DEFAULT)

self._set_hover_cursor(True)

# TODO Might want to set to whatever the mouse hover is now
self._state.hovered_column = -1

def action_order_move_right(self) -> None:
"""Move the cursor right in order mode."""

self._state.hovered_column += 1

def action_order_move_left(self) -> None:
"""Move the cursor left in order mode."""

# We need the check here cause hovered_column can go to -1 (which signifies
# the hovered column is inactive)
if self._state.hovered_column > 0:
self._state.hovered_column -= 1

def action_order_toggle(self) -> None:
"""Toggle the order of the current column in order mode."""

if self._state.hovered_column != self._state.sort_column_idx:
self._state.sort_column_idx = self._state.hovered_column
else:
self._state.sort_direction = (
SortDirection.ASCENDING
if self._state.sort_direction == SortDirection.DESCENDING
else SortDirection.DESCENDING
)
49 changes: 38 additions & 11 deletions src/appui/quote_table_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,8 @@ class QuoteTableState:
_QUERY_FREQUENCY: Final[str] = "query_frequency"

def __init__(self, yfin: YFinance) -> None:
self._yfin: YFinance = yfin

# Persistent state
self._quotes_symbols: list[str] = QuoteTableState._DEFAULT_QUOTES[:]

self._sort_column_key: str = QuoteTableState._TICKER_COLUMN_KEY
self._sort_direction: SortDirection = QuoteTableState._DEFAULT_SORT_DIRECTION
self._query_frequency: int = QuoteTableState._DEFAULT_QUERY_FREQUENCY
Expand All @@ -61,18 +59,22 @@ def __init__(self, yfin: YFinance) -> None:
ALL_QUOTE_COLUMNS[column] for column in columns_keys
]

# Transient state
self._cursor_symbol: str = ""
self._hovered_column: int = -1
self._version: int = 0
self._quotes: list[YQuote] = []
self._sort_key_func: Callable[[YQuote], Any] = ALL_QUOTE_COLUMNS[
self._sort_column_key
].sort_key_func

self._cursor_symbol: str = ""

# Query thread
self._query_thread_running: bool = False
self._query_thread: Thread = Thread(target=self._retrieve_quotes)
self._last_query_time: float = monotonic()
self._version: int = 0

self._quotes: list[YQuote] = []
# Other
self._yfin: YFinance = yfin
self._quotes_lock = Lock()

def __del__(self) -> None:
Expand Down Expand Up @@ -120,6 +122,16 @@ def sort_column_key(self, value: str) -> None:
self._sort_quotes()
self._version += 1

@property
def sort_column_idx(self) -> int:
"""The index of the column to sort by."""

return self._columns.index(ALL_QUOTE_COLUMNS[self._sort_column_key])

@sort_column_idx.setter
def sort_column_idx(self, value: int) -> None:
self.sort_column_key = self._columns[value].key

@property
def sort_direction(self) -> SortDirection:
"""The direction of the sort."""
Expand All @@ -146,6 +158,21 @@ def query_frequency(self, value: int) -> None:
self._query_frequency = value
# Don't change the version. This is a setting for the backend, not the UI.

@property
def hovered_column(self) -> int:
"""
The index of the column currently hovered by the mouse, or highlighted in column
ordering mode.
"""
return self._hovered_column

@hovered_column.setter
def hovered_column(self, value: int) -> None:
if value == self._hovered_column or value < -1 or value >= len(self._columns):
return
self._hovered_column = value
self._version += 1

@property
def cursor_row(self) -> int:
"""The current row of the cursor."""
Expand Down Expand Up @@ -231,11 +258,11 @@ def quotes_rows(self) -> list[QuoteRow]:
# saved whenever we change the columns?
[
QuoteCell(
ALL_QUOTE_COLUMNS[column].format_func(q),
ALL_QUOTE_COLUMNS[column].sign_indicator_func(q),
ALL_QUOTE_COLUMNS[column].justification,
ALL_QUOTE_COLUMNS[column.key].format_func(q),
ALL_QUOTE_COLUMNS[column.key].sign_indicator_func(q),
ALL_QUOTE_COLUMNS[column.key].justification,
)
for column in self.column_keys
for column in self._columns
],
)
for q in self._quotes
Expand Down

0 comments on commit 96851e6

Please sign in to comment.