forked from iarv/contact
Compare commits
13 Commits
fix-settin
...
refactor-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74d19c5ca7 | ||
|
|
33f7db4cb9 | ||
|
|
06d151e991 | ||
|
|
50efa50923 | ||
|
|
cef86ab185 | ||
|
|
3d305a22b9 | ||
|
|
6d45788a5b | ||
|
|
e0e09aeccf | ||
|
|
bfb7ed0278 | ||
|
|
b7a275f725 | ||
|
|
587d79cb93 | ||
|
|
eb79675be0 | ||
|
|
61e1799199 |
@@ -12,8 +12,7 @@ from contact.utilities.db_handler import get_name_from_database, update_node_inf
|
||||
from contact.utilities.input_handlers import get_list_input
|
||||
import contact.ui.default_config as config
|
||||
import contact.ui.dialog
|
||||
|
||||
|
||||
from contact.ui.nav_utils import move_main_highlight, draw_main_arrows, get_msg_window_lines
|
||||
from contact.utilities.singleton import ui_state, interface_state
|
||||
|
||||
|
||||
@@ -86,6 +85,7 @@ def handle_resize(stdscr: curses.window, firstrun: bool) -> None:
|
||||
draw_channel_list()
|
||||
draw_messages_window(True)
|
||||
draw_node_list()
|
||||
|
||||
except:
|
||||
# Resize events can come faster than we can re-draw, which can cause a curses error.
|
||||
# In this case we'll see another curses.KEY_RESIZE in our key handler and draw again later.
|
||||
@@ -137,7 +137,7 @@ def main_ui(stdscr: curses.window) -> None:
|
||||
select_channel(len(ui_state.channel_list) - 1)
|
||||
elif ui_state.current_window == 1:
|
||||
msg_line_count = messages_pad.getmaxyx()[0]
|
||||
ui_state.selected_message = max(msg_line_count - get_msg_window_lines(), 0)
|
||||
ui_state.selected_message = max(msg_line_count - get_msg_window_lines(messages_win, packetlog_win), 0)
|
||||
refresh_pad(1)
|
||||
elif ui_state.current_window == 2:
|
||||
select_node(len(ui_state.node_list) - 1)
|
||||
@@ -148,7 +148,9 @@ def main_ui(stdscr: curses.window) -> None:
|
||||
ui_state.selected_channel - (channel_win.getmaxyx()[0] - 2)
|
||||
) # select_channel will bounds check for us
|
||||
elif ui_state.current_window == 1:
|
||||
ui_state.selected_message = max(ui_state.selected_message - get_msg_window_lines(), 0)
|
||||
ui_state.selected_message = max(
|
||||
ui_state.selected_message - get_msg_window_lines(messages_win, packetlog_win), 0
|
||||
)
|
||||
refresh_pad(1)
|
||||
elif ui_state.current_window == 2:
|
||||
select_node(
|
||||
@@ -163,7 +165,8 @@ def main_ui(stdscr: curses.window) -> None:
|
||||
elif ui_state.current_window == 1:
|
||||
msg_line_count = messages_pad.getmaxyx()[0]
|
||||
ui_state.selected_message = min(
|
||||
ui_state.selected_message + get_msg_window_lines(), msg_line_count - get_msg_window_lines()
|
||||
ui_state.selected_message + get_msg_window_lines(messages_win, packetlog_win),
|
||||
msg_line_count - get_msg_window_lines(messages_win, packetlog_win),
|
||||
)
|
||||
refresh_pad(1)
|
||||
elif ui_state.current_window == 2:
|
||||
@@ -181,7 +184,6 @@ def main_ui(stdscr: curses.window) -> None:
|
||||
channel_win.attrset(get_color("window_frame"))
|
||||
channel_win.box()
|
||||
channel_win.refresh()
|
||||
highlight_line(False, 0, ui_state.selected_channel)
|
||||
refresh_pad(0)
|
||||
if old_window == 1:
|
||||
messages_win.attrset(get_color("window_frame"))
|
||||
@@ -193,7 +195,6 @@ def main_ui(stdscr: curses.window) -> None:
|
||||
nodes_win.attrset(get_color("window_frame"))
|
||||
nodes_win.box()
|
||||
nodes_win.refresh()
|
||||
highlight_line(False, 2, ui_state.selected_node)
|
||||
refresh_pad(2)
|
||||
|
||||
if ui_state.current_window == 0:
|
||||
@@ -201,7 +202,6 @@ def main_ui(stdscr: curses.window) -> None:
|
||||
channel_win.box()
|
||||
channel_win.attrset(get_color("window_frame"))
|
||||
channel_win.refresh()
|
||||
highlight_line(True, 0, ui_state.selected_channel)
|
||||
refresh_pad(0)
|
||||
elif ui_state.current_window == 1:
|
||||
messages_win.attrset(get_color("window_frame_selected"))
|
||||
@@ -215,7 +215,6 @@ def main_ui(stdscr: curses.window) -> None:
|
||||
nodes_win.box()
|
||||
nodes_win.attrset(get_color("window_frame"))
|
||||
nodes_win.refresh()
|
||||
highlight_line(True, 2, ui_state.selected_node)
|
||||
refresh_pad(2)
|
||||
|
||||
# Check for Esc
|
||||
@@ -421,8 +420,7 @@ def main_ui(stdscr: curses.window) -> None:
|
||||
|
||||
def draw_channel_list() -> None:
|
||||
channel_pad.erase()
|
||||
win_height, win_width = channel_win.getmaxyx()
|
||||
start_index = max(0, ui_state.selected_channel - (win_height - 3)) # Leave room for borders
|
||||
win_width = channel_win.getmaxyx()[1]
|
||||
|
||||
channel_pad.resize(len(ui_state.all_messages), channel_win.getmaxyx()[1])
|
||||
|
||||
@@ -460,6 +458,8 @@ def draw_channel_list() -> None:
|
||||
)
|
||||
channel_win.box()
|
||||
channel_win.attrset((get_color("window_frame")))
|
||||
|
||||
draw_main_arrows(channel_win, len(ui_state.channel_list), window=0)
|
||||
channel_win.refresh()
|
||||
|
||||
refresh_pad(0)
|
||||
@@ -501,10 +501,22 @@ def draw_messages_window(scroll_to_bottom: bool = False) -> None:
|
||||
messages_win.attrset(get_color("window_frame"))
|
||||
messages_win.refresh()
|
||||
|
||||
visible_lines = get_msg_window_lines(messages_win, packetlog_win)
|
||||
|
||||
if scroll_to_bottom:
|
||||
ui_state.selected_message = max(msg_line_count - get_msg_window_lines(), 0)
|
||||
ui_state.selected_message = max(msg_line_count - visible_lines, 0)
|
||||
ui_state.start_index[1] = max(msg_line_count - visible_lines, 0)
|
||||
pass
|
||||
else:
|
||||
ui_state.selected_message = max(min(ui_state.selected_message, msg_line_count - get_msg_window_lines()), 0)
|
||||
ui_state.selected_message = max(min(ui_state.selected_message, msg_line_count - visible_lines), 0)
|
||||
|
||||
draw_main_arrows(
|
||||
messages_win,
|
||||
msg_line_count,
|
||||
window=1,
|
||||
log_height=packetlog_win.getmaxyx()[0],
|
||||
)
|
||||
messages_win.refresh()
|
||||
|
||||
refresh_pad(1)
|
||||
|
||||
@@ -547,6 +559,8 @@ def draw_node_list() -> None:
|
||||
)
|
||||
nodes_win.box()
|
||||
nodes_win.attrset(get_color("window_frame"))
|
||||
|
||||
draw_main_arrows(nodes_win, len(ui_state.node_list), window=2)
|
||||
nodes_win.refresh()
|
||||
|
||||
refresh_pad(2)
|
||||
@@ -567,9 +581,15 @@ def select_channel(idx: int) -> None:
|
||||
remove_notification(ui_state.selected_channel)
|
||||
draw_channel_list()
|
||||
return
|
||||
highlight_line(False, 0, old_selected_channel)
|
||||
highlight_line(True, 0, ui_state.selected_channel)
|
||||
refresh_pad(0)
|
||||
|
||||
move_main_highlight(
|
||||
old_idx=old_selected_channel,
|
||||
new_idx=ui_state.selected_channel,
|
||||
options=ui_state.channel_list,
|
||||
menu_win=channel_win,
|
||||
menu_pad=channel_pad,
|
||||
ui_state=ui_state,
|
||||
)
|
||||
|
||||
|
||||
def scroll_channels(direction: int) -> None:
|
||||
@@ -587,7 +607,30 @@ def scroll_messages(direction: int) -> None:
|
||||
ui_state.selected_message += direction
|
||||
|
||||
msg_line_count = messages_pad.getmaxyx()[0]
|
||||
ui_state.selected_message = max(0, min(ui_state.selected_message, msg_line_count - get_msg_window_lines()))
|
||||
ui_state.selected_message = max(
|
||||
0, min(ui_state.selected_message, msg_line_count - get_msg_window_lines(messages_win, packetlog_win))
|
||||
)
|
||||
|
||||
max_index = msg_line_count - 1
|
||||
visible_height = get_msg_window_lines(messages_win, packetlog_win)
|
||||
|
||||
if ui_state.selected_message < ui_state.start_index[ui_state.current_window]: # Moving above the visible area
|
||||
ui_state.start_index[ui_state.current_window] = ui_state.selected_message
|
||||
elif ui_state.selected_message >= ui_state.start_index[ui_state.current_window]: # Moving below the visible area
|
||||
ui_state.start_index[ui_state.current_window] = ui_state.selected_message
|
||||
|
||||
# Ensure start_index is within bounds
|
||||
ui_state.start_index[ui_state.current_window] = max(
|
||||
0, min(ui_state.start_index[ui_state.current_window], max_index - visible_height + 1)
|
||||
)
|
||||
|
||||
draw_main_arrows(
|
||||
messages_win,
|
||||
msg_line_count,
|
||||
ui_state.current_window,
|
||||
log_height=packetlog_win.getmaxyx()[0],
|
||||
)
|
||||
messages_win.refresh()
|
||||
|
||||
refresh_pad(1)
|
||||
|
||||
@@ -596,9 +639,14 @@ def select_node(idx: int) -> None:
|
||||
old_selected_node = ui_state.selected_node
|
||||
ui_state.selected_node = max(0, min(idx, len(ui_state.node_list) - 1))
|
||||
|
||||
highlight_line(False, 2, old_selected_node)
|
||||
highlight_line(True, 2, ui_state.selected_node)
|
||||
refresh_pad(2)
|
||||
move_main_highlight(
|
||||
old_idx=old_selected_node,
|
||||
new_idx=ui_state.selected_node,
|
||||
options=ui_state.node_list,
|
||||
menu_win=nodes_win,
|
||||
menu_pad=nodes_pad,
|
||||
ui_state=ui_state,
|
||||
)
|
||||
|
||||
draw_function_win()
|
||||
|
||||
@@ -805,11 +853,6 @@ def draw_function_win() -> None:
|
||||
draw_help()
|
||||
|
||||
|
||||
def get_msg_window_lines() -> None:
|
||||
packetlog_height = packetlog_win.getmaxyx()[0] - 1 if ui_state.display_log else 0
|
||||
return messages_win.getmaxyx()[0] - 2 - packetlog_height
|
||||
|
||||
|
||||
def refresh_pad(window: int) -> None:
|
||||
|
||||
win_height = channel_win.getmaxyx()[0]
|
||||
@@ -817,7 +860,7 @@ def refresh_pad(window: int) -> None:
|
||||
if window == 1:
|
||||
pad = messages_pad
|
||||
box = messages_win
|
||||
lines = get_msg_window_lines()
|
||||
lines = get_msg_window_lines(messages_win, packetlog_win)
|
||||
selected_item = ui_state.selected_message
|
||||
start_index = ui_state.selected_message
|
||||
|
||||
@@ -845,34 +888,10 @@ def refresh_pad(window: int) -> None:
|
||||
box.getbegyx()[0] + 1,
|
||||
box.getbegyx()[1] + 1,
|
||||
box.getbegyx()[0] + lines,
|
||||
box.getbegyx()[1] + box.getmaxyx()[1] - 2,
|
||||
box.getbegyx()[1] + box.getmaxyx()[1] - 3,
|
||||
)
|
||||
|
||||
|
||||
def highlight_line(highlight: bool, window: int, line: int) -> None:
|
||||
pad = nodes_pad
|
||||
|
||||
color = get_color("node_list")
|
||||
select_len = nodes_win.getmaxyx()[1] - 2
|
||||
|
||||
if window == 2:
|
||||
node_num = ui_state.node_list[line]
|
||||
node = interface_state.interface.nodesByNum[node_num]
|
||||
if "isFavorite" in node and node["isFavorite"]:
|
||||
color = get_color("node_favorite")
|
||||
if "isIgnored" in node and node["isIgnored"]:
|
||||
color = get_color("node_ignored")
|
||||
|
||||
if window == 0:
|
||||
pad = channel_pad
|
||||
color = get_color(
|
||||
"channel_selected" if (line == ui_state.selected_channel and highlight == False) else "channel_list"
|
||||
)
|
||||
select_len = channel_win.getmaxyx()[1] - 2
|
||||
|
||||
pad.chgat(line, 1, select_len, color | curses.A_REVERSE if highlight else color)
|
||||
|
||||
|
||||
def add_notification(channel_number: int) -> None:
|
||||
if channel_number not in ui_state.notifications:
|
||||
ui_state.notifications.append(channel_number)
|
||||
|
||||
@@ -3,6 +3,18 @@ import re
|
||||
from contact.ui.colors import get_color
|
||||
from contact.utilities.control_utils import transform_menu_path
|
||||
from typing import Any, Optional, List, Dict
|
||||
from contact.utilities.singleton import interface_state, ui_state
|
||||
|
||||
|
||||
def get_node_color(node_index: int, reverse: bool = False):
|
||||
node_num = ui_state.node_list[node_index]
|
||||
node = interface_state.interface.nodesByNum.get(node_num, {})
|
||||
if node.get("isFavorite"):
|
||||
return get_color("node_favorite", reverse=reverse)
|
||||
elif node.get("isIgnored"):
|
||||
return get_color("node_ignored", reverse=reverse)
|
||||
return get_color("settings_default", reverse=reverse)
|
||||
|
||||
|
||||
# Aliases
|
||||
Segment = tuple[str, str, bool, bool]
|
||||
@@ -128,7 +140,6 @@ def draw_arrows(
|
||||
win: object, visible_height: int, max_index: int, start_index: List[int], show_save_option: bool
|
||||
) -> None:
|
||||
|
||||
# vh = visible_height + (1 if show_save_option else 0)
|
||||
mi = max_index - (2 if show_save_option else 0)
|
||||
|
||||
if visible_height < mi:
|
||||
@@ -316,3 +327,91 @@ def wrap_text(text: str, wrap_width: int) -> List[str]:
|
||||
wrapped_lines.append(line_buffer)
|
||||
|
||||
return wrapped_lines
|
||||
|
||||
|
||||
def move_main_highlight(
|
||||
old_idx: int, new_idx, options: List[str], menu_win: curses.window, menu_pad: curses.window, ui_state: object
|
||||
) -> None:
|
||||
|
||||
if old_idx == new_idx: # No-op
|
||||
return
|
||||
|
||||
max_index = len(options) - 1
|
||||
visible_height = menu_win.getmaxyx()[0] - 2
|
||||
|
||||
if new_idx < ui_state.start_index[ui_state.current_window]: # Moving above the visible area
|
||||
ui_state.start_index[ui_state.current_window] = new_idx
|
||||
elif new_idx >= ui_state.start_index[ui_state.current_window] + visible_height: # Moving below the visible area
|
||||
ui_state.start_index[ui_state.current_window] = new_idx - visible_height + 1
|
||||
|
||||
# Ensure start_index is within bounds
|
||||
ui_state.start_index[ui_state.current_window] = max(
|
||||
0, min(ui_state.start_index[ui_state.current_window], max_index - visible_height + 1)
|
||||
)
|
||||
|
||||
highlight_line(menu_win, menu_pad, old_idx, new_idx, visible_height)
|
||||
|
||||
if ui_state.current_window == 0: # hack to fix max_index
|
||||
max_index += 1
|
||||
|
||||
draw_main_arrows(menu_win, max_index, window=ui_state.current_window)
|
||||
menu_win.refresh()
|
||||
|
||||
|
||||
def highlight_line(
|
||||
menu_win: curses.window, menu_pad: curses.window, old_idx: int, new_idx: int, visible_height: int
|
||||
) -> None:
|
||||
|
||||
if ui_state.current_window == 0:
|
||||
color_old = (
|
||||
get_color("channel_selected") if old_idx == ui_state.selected_channel else get_color("channel_list")
|
||||
)
|
||||
color_new = get_color("channel_list", reverse=True) if True else get_color("channel_list", reverse=True)
|
||||
menu_pad.chgat(old_idx, 1, menu_pad.getmaxyx()[1] - 4, color_old)
|
||||
menu_pad.chgat(new_idx, 1, menu_pad.getmaxyx()[1] - 4, color_new)
|
||||
|
||||
elif ui_state.current_window == 2:
|
||||
menu_pad.chgat(old_idx, 1, menu_pad.getmaxyx()[1] - 4, get_node_color(old_idx))
|
||||
menu_pad.chgat(new_idx, 1, menu_pad.getmaxyx()[1] - 4, get_node_color(new_idx, reverse=True))
|
||||
|
||||
menu_win.refresh()
|
||||
|
||||
# Refresh pad only if scrolling is needed
|
||||
menu_pad.refresh(
|
||||
ui_state.start_index[ui_state.current_window],
|
||||
0,
|
||||
menu_win.getbegyx()[0] + 1,
|
||||
menu_win.getbegyx()[1] + 1,
|
||||
menu_win.getbegyx()[0] + visible_height,
|
||||
menu_win.getbegyx()[1] + menu_win.getmaxyx()[1] - 3,
|
||||
)
|
||||
|
||||
|
||||
def draw_main_arrows(win: object, max_index: int, window: int, **kwargs) -> None:
|
||||
|
||||
height, width = win.getmaxyx()
|
||||
usable_height = height - 2
|
||||
usable_width = width - 2
|
||||
|
||||
if window == 1 and ui_state.display_log:
|
||||
if log_height := kwargs.get("log_height"):
|
||||
usable_height -= log_height - 1
|
||||
|
||||
if usable_height < max_index:
|
||||
if ui_state.start_index[window] > 0:
|
||||
win.addstr(1, usable_width, "▲", get_color("settings_default"))
|
||||
else:
|
||||
win.addstr(1, usable_width, " ", get_color("settings_default"))
|
||||
|
||||
if max_index - ui_state.start_index[window] - 1 >= usable_height:
|
||||
win.addstr(usable_height, usable_width, "▼", get_color("settings_default"))
|
||||
else:
|
||||
win.addstr(usable_height, usable_width, " ", get_color("settings_default"))
|
||||
else:
|
||||
win.addstr(1, usable_width, " ", get_color("settings_default"))
|
||||
win.addstr(usable_height, usable_width, " ", get_color("settings_default"))
|
||||
|
||||
|
||||
def get_msg_window_lines(messages_win, packetlog_win) -> None:
|
||||
packetlog_height = packetlog_win.getmaxyx()[0] - 1 if ui_state.display_log else 0
|
||||
return messages_win.getmaxyx()[0] - 2 - packetlog_height
|
||||
|
||||
@@ -25,6 +25,11 @@ class ChatUIState:
|
||||
selected_node: int = 0
|
||||
current_window: int = 0
|
||||
|
||||
selected_index: int = 0
|
||||
start_index: List[int] = field(default_factory=lambda: [0, 0, 0])
|
||||
show_save_option: bool = False
|
||||
menu_path: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class InterfaceState:
|
||||
|
||||
Reference in New Issue
Block a user