mirror of
https://github.com/markqvist/NomadNet.git
synced 2024-10-01 01:26:07 -04:00
Work on Conversations UI
This commit is contained in:
parent
0c1108f00e
commit
1969b49819
@ -17,9 +17,9 @@ class Conversation:
|
|||||||
def conversation_list(app):
|
def conversation_list(app):
|
||||||
conversations = []
|
conversations = []
|
||||||
for entry in os.listdir(app.conversationpath):
|
for entry in os.listdir(app.conversationpath):
|
||||||
if os.path.isdir(app.conversationpath + "/" + entry):
|
if len(entry) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2 and os.path.isdir(app.conversationpath + "/" + entry):
|
||||||
try:
|
try:
|
||||||
conversations.append(Conversation(entry, app))
|
conversations.append(entry)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while loading conversation "+str(entry)+", skipping it. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while loading conversation "+str(entry)+", skipping it. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
@ -29,12 +29,71 @@ class Conversation:
|
|||||||
|
|
||||||
def __init__(self, source_hash, app):
|
def __init__(self, source_hash, app):
|
||||||
self.source_hash = source_hash
|
self.source_hash = source_hash
|
||||||
self.message_path = app.conversationpath + "/" + source_hash
|
self.messages_path = app.conversationpath + "/" + source_hash
|
||||||
self.messages_load_time = None
|
self.messages_load_time = None
|
||||||
self.messages = []
|
self.messages = []
|
||||||
self.source_known = False
|
self.source_known = False
|
||||||
self.source_trusted = False
|
self.source_trusted = False
|
||||||
self.source_blocked = False
|
self.source_blocked = False
|
||||||
|
|
||||||
|
for filename in os.listdir(self.messages_path):
|
||||||
|
if len(filename) == RNS.Identity.HASHLENGTH//8*2:
|
||||||
|
message_path = self.messages_path + "/" + filename
|
||||||
|
self.messages.append(ConversationMessage(message_path))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.source_hash
|
return self.source_hash
|
||||||
|
|
||||||
|
class ConversationMessage:
|
||||||
|
def __init__(self, file_path):
|
||||||
|
self.file_path = file_path
|
||||||
|
self.loaded = False
|
||||||
|
self.timestamp = None
|
||||||
|
self.lxm = None
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
try:
|
||||||
|
self.lxm = LXMF.LXMessage.unpack_from_file(open(self.file_path, "rb"))
|
||||||
|
self.loaded = True
|
||||||
|
self.timestamp = self.lxm.timestamp
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log("Error while loading LXMF message "+str(self.file_path)+" from disk. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
def unload(self):
|
||||||
|
self.loaded = False
|
||||||
|
self.lxm = None
|
||||||
|
|
||||||
|
def get_title(self):
|
||||||
|
if not self.loaded:
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
return self.lxm.title_as_string()
|
||||||
|
|
||||||
|
def get_content(self):
|
||||||
|
if not self.loaded:
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
return self.lxm.content_as_string()
|
||||||
|
|
||||||
|
def get_hash(self):
|
||||||
|
if not self.loaded:
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
return self.lxm.hash
|
||||||
|
|
||||||
|
def signature_validated(self):
|
||||||
|
if not self.loaded:
|
||||||
|
self.load()
|
||||||
|
|
||||||
|
return self.lxm.signature_validated
|
||||||
|
|
||||||
|
def get_signature_description(self):
|
||||||
|
if self.signature_validated():
|
||||||
|
return "Signature Verified"
|
||||||
|
else:
|
||||||
|
if self.lxm.unverified_reason == LXMF.LXMessage.SOURCE_UNKNOWN:
|
||||||
|
return "Unknown Origin"
|
||||||
|
elif self.lxm.unverified_reason == LXMF.LXMessage.SIGNATURE_INVALID:
|
||||||
|
return "Invalid Signature"
|
||||||
|
else:
|
||||||
|
return "Unknown signature validation failure"
|
@ -10,6 +10,7 @@ from ._version import __version__
|
|||||||
from .vendor.configobj import ConfigObj
|
from .vendor.configobj import ConfigObj
|
||||||
|
|
||||||
class NomadNetworkApp:
|
class NomadNetworkApp:
|
||||||
|
time_format = "%Y-%m-%d %H:%M:%S"
|
||||||
_shared_instance = None
|
_shared_instance = None
|
||||||
|
|
||||||
configdir = os.path.expanduser("~")+"/.nomadnetwork"
|
configdir = os.path.expanduser("~")+"/.nomadnetwork"
|
||||||
@ -193,25 +194,25 @@ class NomadNetworkApp:
|
|||||||
self.config["textui"]["colormode"] = nomadnet.ui.COLORMODE_16
|
self.config["textui"]["colormode"] = nomadnet.ui.COLORMODE_16
|
||||||
else:
|
else:
|
||||||
if self.config["textui"]["colormode"].lower() == "monochrome":
|
if self.config["textui"]["colormode"].lower() == "monochrome":
|
||||||
self.config["textui"]["colormode"] = nomadnet.ui.COLORMODE_MONO
|
self.config["textui"]["colormode"] = nomadnet.ui.TextUI.COLORMODE_MONO
|
||||||
elif self.config["textui"]["colormode"].lower() == "16":
|
elif self.config["textui"]["colormode"].lower() == "16":
|
||||||
self.config["textui"]["colormode"] = nomadnet.ui.COLORMODE_16
|
self.config["textui"]["colormode"] = nomadnet.ui.TextUI.COLORMODE_16
|
||||||
elif self.config["textui"]["colormode"].lower() == "88":
|
elif self.config["textui"]["colormode"].lower() == "88":
|
||||||
self.config["textui"]["colormode"] = nomadnet.ui.COLORMODE_88
|
self.config["textui"]["colormode"] = nomadnet.ui.TextUI.COLORMODE_88
|
||||||
elif self.config["textui"]["colormode"].lower() == "256":
|
elif self.config["textui"]["colormode"].lower() == "256":
|
||||||
self.config["textui"]["colormode"] = nomadnet.ui.COLORMODE_256
|
self.config["textui"]["colormode"] = nomadnet.ui.TextUI.COLORMODE_256
|
||||||
elif self.config["textui"]["colormode"].lower() == "24bit":
|
elif self.config["textui"]["colormode"].lower() == "24bit":
|
||||||
self.config["textui"]["colormode"] = nomadnet.ui.COLORMODE_TRUE
|
self.config["textui"]["colormode"] = nomadnet.ui.TextUI.COLORMODE_TRUE
|
||||||
else:
|
else:
|
||||||
raise ValueError("The selected Text UI color mode is invalid")
|
raise ValueError("The selected Text UI color mode is invalid")
|
||||||
|
|
||||||
if not "theme" in self.config["textui"]:
|
if not "theme" in self.config["textui"]:
|
||||||
self.config["textui"]["theme"] = nomadnet.ui.THEME_DARK
|
self.config["textui"]["theme"] = nomadnet.ui.TextUI.THEME_DARK
|
||||||
else:
|
else:
|
||||||
if self.config["textui"]["theme"].lower() == "dark":
|
if self.config["textui"]["theme"].lower() == "dark":
|
||||||
self.config["textui"]["theme"] = nomadnet.ui.THEME_DARK
|
self.config["textui"]["theme"] = nomadnet.ui.TextUI.THEME_DARK
|
||||||
elif self.config["textui"]["theme"].lower() == "light":
|
elif self.config["textui"]["theme"].lower() == "light":
|
||||||
self.config["textui"]["theme"] = nomadnet.ui.THEME_LIGHT
|
self.config["textui"]["theme"] = nomadnet.ui.TextUI.THEME_LIGHT
|
||||||
else:
|
else:
|
||||||
raise ValueError("The selected Text UI theme is invalid")
|
raise ValueError("The selected Text UI theme is invalid")
|
||||||
else:
|
else:
|
||||||
|
@ -2,9 +2,32 @@ import RNS
|
|||||||
import importlib
|
import importlib
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from nomadnet import NomadNetworkApp
|
import nomadnet
|
||||||
from nomadnet.ui import *
|
|
||||||
from nomadnet.ui.textui import *
|
from nomadnet.ui.textui import *
|
||||||
|
from nomadnet import NomadNetworkApp
|
||||||
|
|
||||||
|
COLORMODE_MONO = 1
|
||||||
|
COLORMODE_16 = 16
|
||||||
|
COLORMODE_88 = 88
|
||||||
|
COLORMODE_256 = 256
|
||||||
|
COLORMODE_TRUE = 2**24
|
||||||
|
THEME_DARK = 0x01
|
||||||
|
THEME_LIGHT = 0x02
|
||||||
|
|
||||||
|
THEMES = {
|
||||||
|
THEME_DARK: [
|
||||||
|
# Style name # 16-color style # Monochrome style # 88, 256 and true-color style
|
||||||
|
('heading', 'light gray,underline', 'default', 'underline', 'g93,underline', 'default'),
|
||||||
|
('menubar', 'black', 'light gray', 'standout', '#111', '#bbb'),
|
||||||
|
('shortcutbar', 'black', 'light gray', 'standout', '#111', '#bbb'),
|
||||||
|
('body_text', 'white', 'default', 'default', '#0a0', 'default'),
|
||||||
|
('buttons', 'light green,bold', 'default', 'default', '#00a533', 'default'),
|
||||||
|
('msg_editor', 'black', 'light cyan', 'standout', '#111', '#0bb'),
|
||||||
|
("msg_header_ok", 'black', 'light green', 'standout', 'black', '#6b2',),
|
||||||
|
("msg_header_caution", 'black', 'yellow', 'standout', 'black', '#fd3',),
|
||||||
|
("list_focus", "black", "light cyan", "standout", "#111", "#0bb"),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
class TextUI:
|
class TextUI:
|
||||||
|
|
||||||
@ -25,16 +48,15 @@ class TextUI:
|
|||||||
colormode = self.app.config["textui"]["colormode"]
|
colormode = self.app.config["textui"]["colormode"]
|
||||||
theme = self.app.config["textui"]["theme"]
|
theme = self.app.config["textui"]["theme"]
|
||||||
|
|
||||||
palette = nomadnet.ui.THEMES[theme]
|
palette = THEMES[theme]
|
||||||
|
|
||||||
self.screen = urwid.raw_display.Screen()
|
self.screen = urwid.raw_display.Screen()
|
||||||
self.screen.register_palette(palette)
|
self.screen.register_palette(palette)
|
||||||
|
|
||||||
#self.main_display = nomadnet.ui.textui.Extras.DemoDisplay(self, self.app)
|
self.main_display = Main.MainDisplay(self, self.app)
|
||||||
self.main_display = nomadnet.ui.textui.Main.MainDisplay(self, self.app)
|
|
||||||
|
|
||||||
if intro_timeout > 0:
|
if intro_timeout > 0:
|
||||||
self.intro_display = nomadnet.ui.textui.Extras.IntroDisplay(self.app)
|
self.intro_display = Extras.IntroDisplay(self.app)
|
||||||
initial_widget = self.intro_display.widget
|
initial_widget = self.intro_display.widget
|
||||||
else:
|
else:
|
||||||
initial_widget = self.main_display.widget
|
initial_widget = self.main_display.widget
|
||||||
|
@ -3,11 +3,6 @@ import glob
|
|||||||
import RNS
|
import RNS
|
||||||
import nomadnet
|
import nomadnet
|
||||||
|
|
||||||
from .MenuUI import MenuUI
|
|
||||||
from .TextUI import TextUI
|
|
||||||
from .GraphicalUI import GraphicalUI
|
|
||||||
from .WebUI import WebUI
|
|
||||||
|
|
||||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||||
|
|
||||||
@ -19,37 +14,20 @@ UI_GRAPHICAL = 0x03
|
|||||||
UI_WEB = 0x04
|
UI_WEB = 0x04
|
||||||
UI_MODES = [UI_MENU, UI_TEXT, UI_GRAPHICAL, UI_WEB]
|
UI_MODES = [UI_MENU, UI_TEXT, UI_GRAPHICAL, UI_WEB]
|
||||||
|
|
||||||
COLORMODE_MONO = 1
|
|
||||||
COLORMODE_16 = 16
|
|
||||||
COLORMODE_88 = 88
|
|
||||||
COLORMODE_256 = 256
|
|
||||||
COLORMODE_TRUE = 2**24
|
|
||||||
THEME_DARK = 0x01
|
|
||||||
THEME_LIGHT = 0x02
|
|
||||||
|
|
||||||
THEMES = {
|
|
||||||
THEME_DARK: [
|
|
||||||
# Style name # 16-color style # Monochrome style # 88, 256 and true-color style
|
|
||||||
('heading', 'light gray,underline', 'default', 'underline', 'g93,underline', 'default'),
|
|
||||||
('menubar', 'black', 'light gray', 'standout', '#111', '#bbb'),
|
|
||||||
('shortcutbar', 'black', 'light gray', 'standout', '#111', '#bbb'),
|
|
||||||
('body_text', 'white', 'default', 'default', '#0a0', 'default'),
|
|
||||||
('buttons', 'light green,bold', 'default', 'default', '#00a533', 'default'),
|
|
||||||
('msg_editor', 'black', 'light cyan', 'standout', '#111', '#0bb'),
|
|
||||||
("list_focus", "black", "light cyan", "standout", "#111", "#0bb"),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def spawn(uimode):
|
def spawn(uimode):
|
||||||
if uimode in UI_MODES:
|
if uimode in UI_MODES:
|
||||||
RNS.log("Starting user interface...", RNS.LOG_INFO)
|
RNS.log("Starting user interface...", RNS.LOG_INFO)
|
||||||
if uimode == UI_MENU:
|
if uimode == UI_MENU:
|
||||||
|
from .MenuUI import MenuUI
|
||||||
return MenuUI()
|
return MenuUI()
|
||||||
elif uimode == UI_TEXT:
|
elif uimode == UI_TEXT:
|
||||||
|
from .TextUI import TextUI
|
||||||
return TextUI()
|
return TextUI()
|
||||||
elif uimode == UI_GRAPHICAL:
|
elif uimode == UI_GRAPHICAL:
|
||||||
|
from .GraphicalUI import GraphicalUI
|
||||||
return GraphicalUI()
|
return GraphicalUI()
|
||||||
elif uimode == UI_WEB:
|
elif uimode == UI_WEB:
|
||||||
|
from .WebUI import WebUI
|
||||||
return WebUI()
|
return WebUI()
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import RNS
|
||||||
|
import time
|
||||||
|
import nomadnet
|
||||||
|
|
||||||
class ConversationsDisplayShortcuts():
|
class ConversationsDisplayShortcuts():
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
import urwid
|
import urwid
|
||||||
@ -6,6 +10,9 @@ class ConversationsDisplayShortcuts():
|
|||||||
self.widget = urwid.AttrMap(urwid.Text("Conversations Display Shortcuts"), "shortcutbar")
|
self.widget = urwid.AttrMap(urwid.Text("Conversations Display Shortcuts"), "shortcutbar")
|
||||||
|
|
||||||
class ConversationsDisplay():
|
class ConversationsDisplay():
|
||||||
|
list_width = 0.33
|
||||||
|
cached_conversation_widgets = {}
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
import urwid
|
import urwid
|
||||||
from nomadnet.vendor.additional_urwid_widgets import IndicativeListBox
|
from nomadnet.vendor.additional_urwid_widgets import IndicativeListBox
|
||||||
@ -14,26 +21,104 @@ class ConversationsDisplay():
|
|||||||
|
|
||||||
conversation_list_widgets = []
|
conversation_list_widgets = []
|
||||||
for conversation in app.conversations():
|
for conversation in app.conversations():
|
||||||
widget = urwid.SelectableIcon(str(conversation), cursor_position=-1)
|
conversation_list_widgets.append(self.conversation_list_widget(conversation))
|
||||||
widget.conversation = conversation
|
|
||||||
conversation_list_widgets.append(urwid.AttrMap(widget, None, "list_focus"))
|
|
||||||
|
|
||||||
walker = urwid.SimpleFocusListWalker(conversation_list_widgets)
|
walker = urwid.SimpleFocusListWalker(conversation_list_widgets)
|
||||||
listbox = urwid.LineBox(urwid.Filler(IndicativeListBox(conversation_list_widgets), height=("relative", 100)))
|
listbox = urwid.LineBox(urwid.Filler(IndicativeListBox(conversation_list_widgets), height=("relative", 100)))
|
||||||
|
|
||||||
placeholder = urwid.Text("Conversation Display Area", "left")
|
columns_widget = urwid.Columns([("weight", ConversationsDisplay.list_width, listbox), ("weight", 1-ConversationsDisplay.list_width, self.make_conversation_widget(None))], dividechars=0, focus_column=0, box_columns=[0])
|
||||||
|
|
||||||
conversation_area = urwid.LineBox(
|
|
||||||
urwid.Frame(
|
|
||||||
urwid.Filler(placeholder,"top"),
|
|
||||||
footer=urwid.AttrMap(urwid.Edit(caption="\u270E", edit_text="Message input"), "msg_editor")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
columns_widget = urwid.Columns([("weight", 0.33, listbox), ("weight", 0.67, conversation_area)], dividechars=0, focus_column=0, box_columns=[0])
|
|
||||||
|
|
||||||
self.shortcuts_display = ConversationsDisplayShortcuts(self.app)
|
self.shortcuts_display = ConversationsDisplayShortcuts(self.app)
|
||||||
self.widget = columns_widget
|
self.widget = columns_widget
|
||||||
|
|
||||||
|
def display_conversation(self, sender=None, source_hash=None):
|
||||||
|
options = self.widget.options("weight", 1-ConversationsDisplay.list_width)
|
||||||
|
self.widget.contents[1] = (self.make_conversation_widget(source_hash), options)
|
||||||
|
|
||||||
|
|
||||||
|
def make_conversation_widget(self, source_hash):
|
||||||
|
time_format = self.app.time_format
|
||||||
|
import urwid
|
||||||
|
class LXMessageWidget(urwid.WidgetWrap):
|
||||||
|
def __init__(self, message):
|
||||||
|
title_string = time.strftime(time_format)
|
||||||
|
if message.get_title() != "":
|
||||||
|
title_string += " | " + message.get_title()
|
||||||
|
if message.signature_validated():
|
||||||
|
header_style = "msg_header_ok"
|
||||||
|
else:
|
||||||
|
header_style = "msg_header_caution"
|
||||||
|
title_string = "\u26A0 "+message.get_signature_description() + "\n" + title_string
|
||||||
|
|
||||||
|
title = urwid.AttrMap(urwid.Text(title_string), header_style)
|
||||||
|
|
||||||
|
display_widget = urwid.Pile([
|
||||||
|
title,
|
||||||
|
urwid.Text(message.get_content()),
|
||||||
|
urwid.Text("")
|
||||||
|
])
|
||||||
|
|
||||||
|
urwid.WidgetWrap.__init__(self, display_widget)
|
||||||
|
|
||||||
|
if source_hash == None:
|
||||||
|
return urwid.LineBox(urwid.Filler(urwid.Text("No conversation selected"), "top"))
|
||||||
|
else:
|
||||||
|
if source_hash in ConversationsDisplay.cached_conversation_widgets:
|
||||||
|
return ConversationsDisplay.cached_conversation_widgets[source_hash]
|
||||||
|
else:
|
||||||
|
conversation = nomadnet.Conversation(source_hash, self.app)
|
||||||
|
message_widgets = []
|
||||||
|
|
||||||
|
for message in conversation.messages:
|
||||||
|
message_widget = LXMessageWidget(message)
|
||||||
|
message_widgets.append(message_widget)
|
||||||
|
|
||||||
|
|
||||||
|
from nomadnet.vendor.additional_urwid_widgets import IndicativeListBox
|
||||||
|
messagelist = IndicativeListBox(message_widgets)
|
||||||
|
widget = urwid.LineBox(
|
||||||
|
urwid.Frame(
|
||||||
|
messagelist,
|
||||||
|
footer=urwid.AttrMap(urwid.Edit(caption="\u270E", edit_text=""), "msg_editor")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
ConversationsDisplay.cached_conversation_widgets[source_hash] = widget
|
||||||
|
return widget
|
||||||
|
|
||||||
|
|
||||||
|
def conversation_list_widget(self, conversation):
|
||||||
|
import urwid
|
||||||
|
|
||||||
|
class ListEntry(urwid.Text):
|
||||||
|
_selectable = True
|
||||||
|
|
||||||
|
signals = ["click"]
|
||||||
|
|
||||||
|
def keypress(self, size, key):
|
||||||
|
"""
|
||||||
|
Send 'click' signal on 'activate' command.
|
||||||
|
"""
|
||||||
|
if self._command_map[key] != urwid.ACTIVATE:
|
||||||
|
return key
|
||||||
|
|
||||||
|
self._emit('click')
|
||||||
|
|
||||||
|
def mouse_event(self, size, event, button, x, y, focus):
|
||||||
|
"""
|
||||||
|
Send 'click' signal on button 1 press.
|
||||||
|
"""
|
||||||
|
if button != 1 or not urwid.util.is_mouse_press(event):
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._emit('click')
|
||||||
|
return True
|
||||||
|
|
||||||
|
#widget = urwid.SelectableIcon(str(conversation), cursor_position=-1)
|
||||||
|
widget = ListEntry(str(conversation))
|
||||||
|
urwid.connect_signal(widget, "click", self.display_conversation, conversation)
|
||||||
|
return urwid.AttrMap(widget, None, "list_focus")
|
||||||
|
|
||||||
|
|
||||||
def shortcuts(self):
|
def shortcuts(self):
|
||||||
return self.shortcuts_display
|
return self.shortcuts_display
|
@ -1,5 +1,3 @@
|
|||||||
from nomadnet.ui import *
|
|
||||||
|
|
||||||
class IntroDisplay():
|
class IntroDisplay():
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
import urwid
|
import urwid
|
||||||
|
Loading…
Reference in New Issue
Block a user