Lay out new client structure

This commit is contained in:
Joscha 2019-05-25 13:12:28 +00:00
parent 6c4bfe2752
commit 7da4bf36d5
21 changed files with 174 additions and 1137 deletions

View file

@ -1,8 +0,0 @@
from typing import List
from .single_room_application import *
from .util import *
__all__: List[str] = []
__all__ += single_room_application.__all__
__all__ += util.__all__

View file

@ -1,82 +0,0 @@
import asyncio
from typing import Any, List, Optional
import urwid
import yaboli
from ..config import Config
from ..markup import AT
from ..widgets import ATWidget
class CenteredTextWidget(urwid.WidgetWrap):
def __init__(self, lines: List[AT]):
max_width = max(map(len, lines))
text = AT("\n").join(lines)
filler = urwid.Filler(ATWidget(text, align="center"))
super().__init__(filler)
class RoomWidget(urwid.WidgetWrap):
"""
The RoomWidget connects to and displays a single yaboli room.
Its life cycle looks like this:
1. Create widget
2. Call connect() (while the event loop is running)
3. Keep widget around and occasionally display it
4. Call disconnect() (while the event loop is runnning)
5. When the room should be destroyed/forgotten about, it sends a "close"
event
"""
def __init__(self, config: Config, roomname: str) -> None:
self.c = config
self._room = yaboli.Room(roomname)
super().__init__(self._connecting_widget())
self._room_view = self._connected_widget()
def _connecting_widget(self) -> Any:
lines = [AT("Connecting to ")
+ AT("&" + self.room.name, style=self.c.v.element.room)
+ AT("...")]
return CenteredTextWidget(lines)
def _connected_widget(self) -> Any:
lines = [AT("Connected to ")
+ AT("&" + self.room.name, style=self.c.v.element.room)
+ AT(".")]
return CenteredTextWidget(lines)
def _connection_failed_widget(self) -> Any:
lines = [AT("Could not connect to ")
+ AT("&" + self.room.name, style=self.c.v.element.room)
+ AT(".")]
return CenteredTextWidget(lines)
@property
def room(self) -> yaboli.Room:
return self._room
# Start up the connection and room
async def _connect(self) -> None:
success = await self._room.connect()
if success:
self._w = self._room_view
else:
self._w = self._connection_failed_widget()
urwid.emit_signal(self, "close")
def connect(self) -> None:
asyncio.create_task(self._connect())
# Handle input
#def selectable(self) -> bool:
# return True
#def keypress(self, size: Any, key: str) -> Optional[str]:
# pass
urwid.register_signal(RoomWidget, ["close"])

View file

@ -1,120 +0,0 @@
from typing import Any, Optional
import urwid
from ..config import Config
from .room_widget import RoomWidget
__all__ = ["SingleRoomApplication"]
class ChooseRoomWidget(urwid.WidgetWrap):
def __init__(self, config: Config) -> None:
self.c = config
self.error = None
self.text = urwid.Text("Choose a room:", align=urwid.CENTER)
self.edit = urwid.Edit("&", align=urwid.CENTER)
self.pile = urwid.Pile([
self.text,
urwid.AttrMap(self.edit, self.c.v.element.room),
])
self.filler = urwid.Filler(self.pile)
super().__init__(self.filler)
def render(self, size: Any, focus: Any) -> Any:
if self.error:
width, _ = size
rows = self.error.rows((width,), focus)
self.filler.bottom = rows
return super().render(size, focus)
def set_error(self, text: Any) -> None:
self.error = urwid.Text(text, align=urwid.CENTER)
self.pile = urwid.Pile([
self.error,
self.text,
urwid.AttrMap(self.edit, self.c.v.element.room),
])
self.filler = urwid.Filler(self.pile)
self._w = self.filler
def unset_error(self) -> None:
self.error = None
self.pile = urwid.Pile([
self.text,
urwid.AttrMap(self.edit, self.c.v.element.room),
])
self.filler = urwid.Filler(self.pile)
self._w = self.filler
def could_not_connect(self, roomname: str) -> None:
text = [
"Could not connect to ",
(self.c.v.element.room, "&" + roomname),
".\n",
]
self.set_error(text)
def invalid_room_name(self, reason: str) -> None:
text = [f"Invalid room name: {reason}\n"]
self.set_error(text)
class SingleRoomApplication(urwid.WidgetWrap):
# The characters in the ALPHABET make up the characters that are allowed in
# room names.
ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789"
# These are other characters or character combinations necessary for the
# editor to function well.
ALLOWED_EDITOR_KEYS = {
"backspace", "delete",
"left", "right",
"home", "end",
}
def __init__(self, config: Config) -> None:
self.c = config
self.choose_room = ChooseRoomWidget(self.c)
super().__init__(self.choose_room)
def selectable(self) -> bool:
return True
def switch_to_choose(self) -> None:
self.choose_room.could_not_connect(self.choose_room.edit.edit_text)
self._w = self.choose_room
def keypress(self, size: Any, key: str) -> Optional[str]:
if self._w == self.choose_room:
if key == "esc":
raise urwid.ExitMainLoop()
self.choose_room.unset_error()
if key == "enter":
roomname = self.choose_room.edit.edit_text
if roomname:
room = RoomWidget(self.c, roomname)
urwid.connect_signal(room, "close", self.switch_to_choose)
room.connect()
self._w = room
else:
self.choose_room.invalid_room_name("too short")
elif not super().selectable():
return key
# Make sure we only enter valid room names
elif key.lower() in self.ALPHABET:
return super().keypress(size, key.lower())
elif key in self.ALLOWED_EDITOR_KEYS:
return super().keypress(size, key)
return None
elif super().selectable():
return super().keypress(size, key)
return key

View file

@ -1,56 +0,0 @@
from typing import List, Tuple, Union
from ..config import Config
__all__ = ["UtilException","Palette", "palette_from_config", "DEFAULT_CONFIG"]
class UtilException(Exception):
pass
Palette = List[Union[Tuple[str, str], Tuple[str, str, str],
Tuple[str, str, str, str]]]
def palette_from_config(conf: Config) -> Palette:
palette: Palette = []
styles = conf.tree["style"]
for style, info in styles.items():
# First, do the alias stuff
alias = info.get("alias")
if isinstance(alias, str):
if alias in styles:
palette.append((style, alias))
continue
else:
raise UtilException((f"style.{style}.alias must be the name of"
" another style"))
elif alias is not None:
raise UtilException(f"style.{style}.alias must be a string")
# Foreground/background
fg = info.get("fg")
bg = info.get("bg")
if not isinstance(fg, str) and fg is not None:
raise TypeError(f"style.{style}.fg must be a string")
if not isinstance(bg, str) and bg is not None:
raise TypeError(f"style.{style}.bg must be a string")
fg = fg or ""
bg = bg or ""
palette.append((style, fg, bg))
return palette
DEFAULT_CONFIG = {
"element": {
"room": "room",
},
"style": {
"room": {
"fg": "light blue, bold",
},
},
}