mirror of
https://github.com/markqvist/NomadNet.git
synced 2025-02-02 01:14:56 -05:00
Implemented node browser and micron parser link handling.
This commit is contained in:
parent
6a36786c4d
commit
6e4baf3731
@ -70,22 +70,24 @@ class Directory:
|
||||
RNS.log("Could not load directory from disk. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def lxmf_announce_received(self, source_hash, app_data):
|
||||
timestamp = time.time()
|
||||
self.announce_stream.insert(0, (timestamp, source_hash, app_data, False))
|
||||
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
||||
self.announce_stream.pop()
|
||||
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
||||
if app_data != None:
|
||||
timestamp = time.time()
|
||||
self.announce_stream.insert(0, (timestamp, source_hash, app_data, False))
|
||||
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
||||
self.announce_stream.pop()
|
||||
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
||||
|
||||
def node_announce_received(self, source_hash, app_data, associated_peer):
|
||||
timestamp = time.time()
|
||||
self.announce_stream.insert(0, (timestamp, source_hash, app_data, True))
|
||||
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
||||
self.announce_stream.pop()
|
||||
if app_data != None:
|
||||
timestamp = time.time()
|
||||
self.announce_stream.insert(0, (timestamp, source_hash, app_data, True))
|
||||
while len(self.announce_stream) > Directory.ANNOUNCE_STREAM_MAXLENGTH:
|
||||
self.announce_stream.pop()
|
||||
|
||||
if self.trust_level(associated_peer) == DirectoryEntry.TRUSTED:
|
||||
node_entry = DirectoryEntry(source_hash, display_name=app_data.decode("utf-8"), trust_level=DirectoryEntry.TRUSTED, hosts_node=True)
|
||||
self.remember(node_entry)
|
||||
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
||||
if self.trust_level(associated_peer) == DirectoryEntry.TRUSTED:
|
||||
node_entry = DirectoryEntry(source_hash, display_name=app_data.decode("utf-8"), trust_level=DirectoryEntry.TRUSTED, hosts_node=True)
|
||||
self.remember(node_entry)
|
||||
self.app.ui.main_display.sub_displays.network_display.directory_change_callback()
|
||||
|
||||
def remove_announce_with_timestamp(self, timestamp):
|
||||
selected_announce = None
|
||||
|
@ -116,7 +116,7 @@ class Node:
|
||||
|
||||
DEFAULT_INDEX = '''>Default Home Page
|
||||
|
||||
This node is serving pages, but no home page file (index.mu) was found in the page storage directory. This is an auto-generated placeholder.
|
||||
This node is serving pages, but the home page file (index.mu) was not found in the page storage directory. This is an auto-generated placeholder.
|
||||
|
||||
If you are the node operator, you can define your own home page by creating a file named `*index.mu`* in the page storage directory.
|
||||
'''
|
@ -133,6 +133,7 @@ class TextUI:
|
||||
self.loop.run()
|
||||
|
||||
def set_colormode(self, colormode):
|
||||
self.colormode = colormode
|
||||
self.screen.set_terminal_properties(colormode)
|
||||
self.screen.reset_default_terminal_palette()
|
||||
|
||||
|
377
nomadnet/ui/textui/Browser.py
Normal file
377
nomadnet/ui/textui/Browser.py
Normal file
@ -0,0 +1,377 @@
|
||||
import RNS
|
||||
import time
|
||||
import urwid
|
||||
import nomadnet
|
||||
import threading
|
||||
from .MicronParser import markup_to_attrmaps
|
||||
from nomadnet.vendor.Scrollable import *
|
||||
|
||||
# TODO: REMOVE
|
||||
import os
|
||||
|
||||
class BrowserFrame(urwid.Frame):
|
||||
def keypress(self, size, key):
|
||||
if key == "ctrl w":
|
||||
self.delegate.disconnect()
|
||||
elif self.get_focus() == "body":
|
||||
return super(BrowserFrame, self).keypress(size, key)
|
||||
# if key == "up" and self.delegate.messagelist.top_is_visible:
|
||||
# nomadnet.NomadNetworkApp.get_shared_instance().ui.main_display.frame.set_focus("header")
|
||||
# elif key == "down" and self.delegate.messagelist.bottom_is_visible:
|
||||
# self.set_focus("footer")
|
||||
# else:
|
||||
# return super(ConversationFrame, self).keypress(size, key)
|
||||
else:
|
||||
return super(BrowserFrame, self).keypress(size, key)
|
||||
|
||||
class Browser:
|
||||
DEFAULT_PATH = "/page/index.mu"
|
||||
DEFAULT_TIMEOUT = 5
|
||||
|
||||
NO_PATH = 0x00
|
||||
PATH_REQUESTED = 0x01
|
||||
ESTABLISHING_LINK = 0x02
|
||||
LINK_ESTABLISHED = 0x03
|
||||
REQUESTING = 0x04
|
||||
REQUEST_SENT = 0x05
|
||||
REQUEST_FAILED = 0x06
|
||||
REQUEST_TIMEOUT = 0x07
|
||||
RECEIVING_RESPONSE = 0x08
|
||||
DONE = 0xFF
|
||||
DISCONECTED = 0xFE
|
||||
|
||||
def __init__(self, app, app_name, aspects, destination_hash = None, path = None, auth_identity = None, delegate = None):
|
||||
self.app = app
|
||||
self.g = self.app.ui.glyphs
|
||||
self.delegate = delegate
|
||||
self.app_name = app_name
|
||||
self.aspects = aspects
|
||||
self.destination_hash = destination_hash
|
||||
self.path = path
|
||||
self.timeout = Browser.DEFAULT_TIMEOUT
|
||||
self.last_keypress = None
|
||||
|
||||
self.link = None
|
||||
self.status = Browser.DISCONECTED
|
||||
self.page_data = None
|
||||
self.displayed_page_data = None
|
||||
self.auth_identity = auth_identity
|
||||
self.display_widget = None
|
||||
self.frame = None
|
||||
self.attr_maps = []
|
||||
self.build_display()
|
||||
|
||||
if self.path == None:
|
||||
self.path = Browser.DEFAULT_PATH
|
||||
|
||||
if self.destination_hash != None:
|
||||
self.load_page()
|
||||
|
||||
def current_url(self):
|
||||
if self.destination_hash == None:
|
||||
return ""
|
||||
else:
|
||||
if self.path == None:
|
||||
path = ""
|
||||
else:
|
||||
path = self.path
|
||||
return RNS.hexrep(self.destination_hash, delimit=False)+":"+path
|
||||
|
||||
def handle_link(self, link_target):
|
||||
RNS.log("Browser handling link to: "+str(link_target))
|
||||
try:
|
||||
self.retrieve_url(link_target)
|
||||
except Exception as e:
|
||||
self.browser_footer = urwid.Text("Could not open link: "+str(e))
|
||||
self.frame.contents["footer"] = (self.browser_footer, self.frame.options())
|
||||
|
||||
|
||||
def micron_released_focus(self):
|
||||
if self.delegate != None:
|
||||
self.delegate.focus_lists()
|
||||
|
||||
def build_display(self):
|
||||
self.browser_header = urwid.Text("")
|
||||
self.browser_footer = urwid.Text("")
|
||||
|
||||
self.browser_body = urwid.Filler(urwid.Text("Disconnected\n"+self.g["arrow_l"]+" "+self.g["arrow_r"], align="center"), "middle")
|
||||
|
||||
self.frame = BrowserFrame(self.browser_body, header=self.browser_header, footer=self.browser_footer)
|
||||
self.frame.delegate = self
|
||||
self.display_widget = urwid.AttrMap(urwid.LineBox(self.frame, title="Remote Node"), "inactive_text")
|
||||
|
||||
def make_status_widget(self):
|
||||
return urwid.Pile([urwid.Divider(self.g["divider1"]), urwid.Text(self.status_text())])
|
||||
|
||||
def make_control_widget(self):
|
||||
return urwid.Pile([urwid.Text(self.g["node"]+" "+self.current_url()), urwid.Divider(self.g["divider1"])])
|
||||
|
||||
def make_request_failed_widget(self):
|
||||
def back_action(sender):
|
||||
self.status = Browser.DONE
|
||||
self.destination_hash = self.previous_destination_hash
|
||||
self.path = self.previous_path
|
||||
self.update_display()
|
||||
|
||||
columns = urwid.Columns([
|
||||
("weight", 0.5, urwid.Text(" ")),
|
||||
(8, urwid.Button("Back", on_press=back_action)),
|
||||
("weight", 0.5, urwid.Text(" "))
|
||||
])
|
||||
|
||||
pile = urwid.Pile([
|
||||
urwid.Text("!\n\n"+self.status_text()+"\n", align="center"),
|
||||
columns
|
||||
])
|
||||
|
||||
return urwid.Filler(pile, "middle")
|
||||
|
||||
def update_display(self):
|
||||
if self.status == Browser.DISCONECTED:
|
||||
self.display_widget.set_attr_map({None: "inactive_text"})
|
||||
self.browser_body = urwid.Filler(urwid.Text("Disconnected\n"+self.g["arrow_l"]+" "+self.g["arrow_r"], align="center"), "middle")
|
||||
self.browser_footer = urwid.Text("")
|
||||
self.browser_header = urwid.Text("")
|
||||
else:
|
||||
self.display_widget.set_attr_map({None: "body_text"})
|
||||
self.browser_header = self.make_control_widget()
|
||||
|
||||
if self.status == Browser.DONE:
|
||||
self.browser_footer = self.make_status_widget()
|
||||
self.update_page_display()
|
||||
elif self.status <= Browser.REQUEST_SENT:
|
||||
if len(self.attr_maps) == 0:
|
||||
self.browser_body = urwid.Filler(urwid.Text("Retrieving\n["+self.current_url()+"]", align="center"), "middle")
|
||||
self.browser_footer = self.make_status_widget()
|
||||
elif self.status == Browser.REQUEST_FAILED:
|
||||
self.browser_body = self.make_request_failed_widget()
|
||||
self.browser_footer = urwid.Text("")
|
||||
elif self.status == Browser.REQUEST_TIMEOUT:
|
||||
self.browser_body = self.make_request_failed_widget()
|
||||
self.browser_footer = urwid.Text("")
|
||||
else:
|
||||
pass
|
||||
|
||||
self.frame.contents["body"] = (self.browser_body, self.frame.options())
|
||||
self.frame.contents["header"] = (self.browser_header, self.frame.options())
|
||||
self.frame.contents["footer"] = (self.browser_footer, self.frame.options())
|
||||
|
||||
def update_page_display(self):
|
||||
pile = urwid.Pile(self.attr_maps)
|
||||
self.browser_body = urwid.AttrMap(ScrollBar(Scrollable(pile), thumb_char="\u2503", trough_char=" "), "scrollbar")
|
||||
|
||||
def identify(self):
|
||||
if self.link != None:
|
||||
if self.link.status == RNS.Link.ACTIVE:
|
||||
self.link.identify(self.auth_identity)
|
||||
|
||||
|
||||
def disconnect(self):
|
||||
if self.link != None:
|
||||
self.link.teardown()
|
||||
|
||||
self.attr_maps = []
|
||||
self.status = Browser.DISCONECTED
|
||||
self.update_display()
|
||||
|
||||
|
||||
def retrieve_url(self, url):
|
||||
self.previous_destination_hash = self.destination_hash
|
||||
self.previous_path = self.path
|
||||
|
||||
destination_hash = None
|
||||
path = None
|
||||
|
||||
components = url.split(":")
|
||||
if len(components) == 1:
|
||||
if len(components[0]) == 20:
|
||||
try:
|
||||
destination_hash = bytes.fromhex(components[0])
|
||||
except Exception as e:
|
||||
raise ValueError("Malformed URL")
|
||||
path = Browser.DEFAULT_PATH
|
||||
else:
|
||||
raise ValueError("Malformed URL")
|
||||
elif len(components) == 2:
|
||||
if len(components[0]) == 20:
|
||||
try:
|
||||
destination_hash = bytes.fromhex(components[0])
|
||||
except Exception as e:
|
||||
raise ValueError("Malformed URL")
|
||||
path = components[1]
|
||||
if len(path) == 0:
|
||||
path = Browser.DEFAULT_PATH
|
||||
else:
|
||||
if len(components[0]) == 0:
|
||||
if self.destination_hash != None:
|
||||
destination_hash = self.destination_hash
|
||||
path = components[1]
|
||||
if len(path) == 0:
|
||||
path = Browser.DEFAULT_PATH
|
||||
else:
|
||||
raise ValueError("Malformed URL")
|
||||
else:
|
||||
raise ValueError("Malformed URL")
|
||||
else:
|
||||
raise ValueError("Malformed URL")
|
||||
|
||||
if destination_hash != None and path != None:
|
||||
self.set_destination_hash(destination_hash)
|
||||
self.set_path(path)
|
||||
self.load_page()
|
||||
|
||||
def set_destination_hash(self, destination_hash):
|
||||
if len(destination_hash) == RNS.Identity.TRUNCATED_HASHLENGTH//8:
|
||||
self.destination_hash = destination_hash
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def set_path(self, path):
|
||||
self.path = path
|
||||
|
||||
|
||||
def set_timeout(self, timeout):
|
||||
self.timeout = timeout
|
||||
|
||||
|
||||
def load_page(self):
|
||||
load_thread = threading.Thread(target=self.__load)
|
||||
load_thread.setDaemon(True)
|
||||
load_thread.start()
|
||||
|
||||
|
||||
def __load(self):
|
||||
# If an established link exists, but it doesn't match the target
|
||||
# destination, we close and clear it.
|
||||
if self.link != None and self.link.destination.hash != self.destination_hash:
|
||||
self.link.close()
|
||||
self.link = None
|
||||
|
||||
# If no link to the destination exists, we create one.
|
||||
if self.link == None:
|
||||
if not RNS.Transport.has_path(self.destination_hash):
|
||||
self.status = Browser.NO_PATH
|
||||
self.update_display()
|
||||
|
||||
RNS.Transport.request_path(self.destination_hash)
|
||||
self.status = Browser.PATH_REQUESTED
|
||||
self.update_display()
|
||||
|
||||
pr_time = time.time()
|
||||
while not RNS.Transport.has_path(self.destination_hash):
|
||||
now = time.time()
|
||||
if now > pr_time+self.timeout:
|
||||
self.request_timeout()
|
||||
|
||||
time.sleep(0.25)
|
||||
|
||||
self.status = Browser.ESTABLISHING_LINK
|
||||
self.update_display()
|
||||
|
||||
identity = RNS.Identity.recall(self.destination_hash)
|
||||
destination = RNS.Destination(
|
||||
identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
self.app_name,
|
||||
self.aspects
|
||||
)
|
||||
|
||||
self.link = RNS.Link(destination, established_callback = self.link_established, closed_callback = self.link_closed)
|
||||
|
||||
l_time = time.time()
|
||||
while not self.status == Browser.LINK_ESTABLISHED:
|
||||
now = time.time()
|
||||
if now > l_time+self.timeout:
|
||||
self.request_timeout()
|
||||
|
||||
time.sleep(0.25)
|
||||
|
||||
self.update_display()
|
||||
|
||||
# Send the request
|
||||
self.status = Browser.REQUESTING
|
||||
self.update_display()
|
||||
receipt = self.link.request(
|
||||
self.path,
|
||||
data = None,
|
||||
response_callback = self.response_received,
|
||||
failed_callback = self.request_failed,
|
||||
timeout = self.timeout
|
||||
)
|
||||
|
||||
self.last_request_receipt = receipt
|
||||
self.last_request_id = receipt.request_id
|
||||
|
||||
self.status = Browser.REQUEST_SENT
|
||||
self.update_display()
|
||||
|
||||
|
||||
def link_established(self, link):
|
||||
self.status = Browser.LINK_ESTABLISHED
|
||||
|
||||
|
||||
def link_closed(self, link):
|
||||
if self.status == Browser.DISCONECTED or self.status == Browser.DONE:
|
||||
self.link = None
|
||||
else:
|
||||
self.link = None
|
||||
self.status = Browser.REQUEST_FAILED
|
||||
self.update_display()
|
||||
|
||||
|
||||
def response_received(self, request_receipt):
|
||||
try:
|
||||
self.status = Browser.DONE
|
||||
self.page_data = request_receipt.response
|
||||
self.markup = self.page_data.decode("utf-8")
|
||||
self.attr_maps = markup_to_attrmaps(self.markup, url_delegate=self)
|
||||
self.update_display()
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while handling response. The contained exception was: "+str(e))
|
||||
|
||||
|
||||
def request_failed(self, request_receipt=None):
|
||||
if request_receipt != None:
|
||||
if request_receipt.request_id == self.last_request_id:
|
||||
self.status = Browser.REQUEST_FAILED
|
||||
self.update_display()
|
||||
else:
|
||||
self.status = Browser.REQUEST_FAILED
|
||||
self.update_display()
|
||||
|
||||
|
||||
def request_timeout(self, request_receipt=None):
|
||||
self.status = Browser.REQUEST_TIMEOUT
|
||||
self.update_display()
|
||||
|
||||
|
||||
def status_text(self):
|
||||
if self.status == Browser.NO_PATH:
|
||||
return "No path to destination known"
|
||||
elif self.status == Browser.PATH_REQUESTED:
|
||||
return "Path requested, waiting for path..."
|
||||
elif self.status == Browser.ESTABLISHING_LINK:
|
||||
return "Establishing link..."
|
||||
elif self.status == Browser.LINK_ESTABLISHED:
|
||||
return "Link established"
|
||||
elif self.status == Browser.REQUESTING:
|
||||
return "Sending request..."
|
||||
elif self.status == Browser.REQUEST_SENT:
|
||||
return "Request sent, awaiting response..."
|
||||
elif self.status == Browser.REQUEST_FAILED:
|
||||
return "Request failed"
|
||||
elif self.status == Browser.REQUEST_TIMEOUT:
|
||||
return "Request timed out"
|
||||
elif self.status == Browser.RECEIVING_RESPONSE:
|
||||
return "Receiving response..."
|
||||
elif self.status == Browser.DONE:
|
||||
return "Done"
|
||||
elif self.status == Browser.DISCONECTED:
|
||||
return "Disconnected"
|
||||
else:
|
||||
return "Browser Status Unknown"
|
||||
|
||||
|
@ -64,6 +64,7 @@ class GuideEntry(urwid.WidgetWrap):
|
||||
def __init__(self, app, reader, topic_name):
|
||||
self.app = app
|
||||
self.reader = reader
|
||||
self.last_keypress = None
|
||||
g = self.app.ui.glyphs
|
||||
|
||||
widget = ListEntry(topic_name)
|
||||
@ -76,10 +77,13 @@ class GuideEntry(urwid.WidgetWrap):
|
||||
|
||||
def display_topic(self, event, topic):
|
||||
markup = TOPICS[topic]
|
||||
attrmaps = markup_to_attrmaps(markup)
|
||||
attrmaps = markup_to_attrmaps(markup, url_delegate=None)
|
||||
|
||||
self.reader.set_content_widgets(attrmaps)
|
||||
|
||||
def micron_released_focus(self):
|
||||
self.reader.focus_topics()
|
||||
|
||||
class TopicList(urwid.WidgetWrap):
|
||||
def __init__(self, app, guide_display):
|
||||
self.app = app
|
||||
@ -142,12 +146,21 @@ class GuideDisplay():
|
||||
def shortcuts(self):
|
||||
return self.shortcuts_display
|
||||
|
||||
def focus_topics(self):
|
||||
self.columns.focus_position = 0
|
||||
|
||||
|
||||
TOPIC_INTRODUCTION = '''>Nomad Network
|
||||
|
||||
`c`*Communicate Freely.`*
|
||||
`a
|
||||
|
||||
TODO: REMOVE
|
||||
This is a `F07flink `[With a label`344858860838a8d9f8ed:/page/test] to some resource`f.
|
||||
This is a link `*`[With a label`:/page/test]`* to some resource.
|
||||
This is a link `[With a label`:] to some resource.
|
||||
This is a link `*`[With a label`344858860838a8d9f8ed] to some`* resource.
|
||||
|
||||
The intention with this program is to provide a tool to that allows you to build private and resilient communications platforms that are in complete control and ownership of the people that use them.
|
||||
|
||||
Nomad Network is build on LXMF and Reticulum, which together provides the cryptographic mesh functionality and peer-to-peer message routing that Nomad Network relies on. This foundation also makes it possible to use the program over a very wide variety of communication mediums, from packet radio to gigabit fiber.
|
||||
|
@ -1,5 +1,8 @@
|
||||
import nomadnet
|
||||
import urwid
|
||||
import time
|
||||
from urwid.util import is_mouse_press
|
||||
from urwid.text_layout import calc_coords
|
||||
import re
|
||||
|
||||
STYLES = {
|
||||
@ -10,11 +13,12 @@ STYLES = {
|
||||
}
|
||||
|
||||
SYNTH_STYLES = []
|
||||
SYNTH_SPECS = {}
|
||||
|
||||
SECTION_INDENT = 2
|
||||
INDENT_RIGHT = 1
|
||||
|
||||
def markup_to_attrmaps(markup):
|
||||
def markup_to_attrmaps(markup, url_delegate = None):
|
||||
attrmaps = []
|
||||
|
||||
state = {
|
||||
@ -39,7 +43,7 @@ def markup_to_attrmaps(markup):
|
||||
|
||||
for line in lines:
|
||||
if len(line) > 0:
|
||||
display_widget = parse_line(line, state)
|
||||
display_widget = parse_line(line, state, url_delegate)
|
||||
else:
|
||||
display_widget = urwid.Text("")
|
||||
|
||||
@ -50,7 +54,7 @@ def markup_to_attrmaps(markup):
|
||||
return attrmaps
|
||||
|
||||
|
||||
def parse_line(line, state):
|
||||
def parse_line(line, state, url_delegate):
|
||||
if len(line) > 0:
|
||||
first_char = line[0]
|
||||
|
||||
@ -68,7 +72,7 @@ def parse_line(line, state):
|
||||
# Check for section heading reset
|
||||
elif first_char == "<":
|
||||
state["depth"] = 0
|
||||
return parse_line(line[1:], state)
|
||||
return parse_line(line[1:], state, url_delegate)
|
||||
|
||||
# Check for section headings
|
||||
elif first_char == ">":
|
||||
@ -88,7 +92,7 @@ def parse_line(line, state):
|
||||
style_to_state(style, state)
|
||||
|
||||
heading_style = make_style(state)
|
||||
output = make_output(state, line)
|
||||
output = make_output(state, line, url_delegate)
|
||||
|
||||
style_to_state(latched_style, state)
|
||||
|
||||
@ -114,13 +118,18 @@ def parse_line(line, state):
|
||||
else:
|
||||
return urwid.Padding(urwid.Divider(divider_char), left=left_indent(state), right=right_indent(state))
|
||||
|
||||
output = make_output(state, line)
|
||||
output = make_output(state, line, url_delegate)
|
||||
|
||||
if output != None:
|
||||
if state["depth"] == 0:
|
||||
return urwid.Text(output, align=state["align"])
|
||||
if url_delegate != None:
|
||||
text_widget = LinkableText(output, align=state["align"], delegate=url_delegate)
|
||||
else:
|
||||
return urwid.Padding(urwid.Text(output, align=state["align"]), left=left_indent(state), right=right_indent(state))
|
||||
text_widget = urwid.Text(output, align=state["align"])
|
||||
|
||||
if state["depth"] == 0:
|
||||
return text_widget
|
||||
else:
|
||||
return urwid.Padding(text_widget, left=left_indent(state), right=right_indent(state))
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
@ -180,11 +189,15 @@ def make_style(state):
|
||||
if not name in SYNTH_STYLES:
|
||||
screen = nomadnet.NomadNetworkApp.get_shared_instance().ui.screen
|
||||
screen.register_palette_entry(name, low_color(fg)+format_string,low_color(bg),mono_color(fg, bg)+format_string,high_color(fg)+format_string,high_color(bg))
|
||||
|
||||
synth_spec = screen._palette[name]
|
||||
SYNTH_STYLES.append(name)
|
||||
if not name in SYNTH_SPECS:
|
||||
SYNTH_SPECS[name] = synth_spec
|
||||
|
||||
return name
|
||||
|
||||
def make_output(state, line):
|
||||
def make_output(state, line, url_delegate):
|
||||
output = []
|
||||
if state["literal"]:
|
||||
if line == "\\`=":
|
||||
@ -246,6 +259,58 @@ def make_output(state, line):
|
||||
elif c == "a":
|
||||
state["align"] = state["default_align"]
|
||||
|
||||
elif c == "[":
|
||||
endpos = line[i:].find("]")
|
||||
if endpos == -1:
|
||||
pass
|
||||
else:
|
||||
link_data = line[i+1:i+endpos]
|
||||
skip = endpos
|
||||
|
||||
link_components = link_data.split("`")
|
||||
if len(link_components) == 1:
|
||||
link_label = ""
|
||||
link_url = link_data
|
||||
elif len(link_components) == 2:
|
||||
link_label = link_components[0]
|
||||
link_url = link_components[1]
|
||||
else:
|
||||
link_url = ""
|
||||
link_label = ""
|
||||
|
||||
if len(link_url) != 0:
|
||||
if link_label == "":
|
||||
link_label = link_url
|
||||
|
||||
# First generate output until now
|
||||
if len(part) > 0:
|
||||
output.append(make_part(state, part))
|
||||
|
||||
cm = nomadnet.NomadNetworkApp.get_shared_instance().ui.colormode
|
||||
|
||||
specname = make_style(state)
|
||||
speclist = SYNTH_SPECS[specname]
|
||||
|
||||
orig_spec = urwid.AttrSpec('underline', 'default', cm)
|
||||
if cm == 1:
|
||||
orig_spec = speclist[0]
|
||||
elif cm == 16:
|
||||
orig_spec = speclist[1]
|
||||
elif cm == 88:
|
||||
orig_spec = speclist[2]
|
||||
elif cm == 256:
|
||||
orig_spec = speclist[3]
|
||||
elif cm == 2**24:
|
||||
orig_spec = speclist[4]
|
||||
|
||||
if url_delegate != None:
|
||||
linkspec = LinkSpec(link_url, orig_spec)
|
||||
output.append((linkspec, link_label))
|
||||
else:
|
||||
output.append(make_part(state, link_label))
|
||||
|
||||
|
||||
|
||||
mode = "text"
|
||||
if len(part) > 0:
|
||||
output.append(make_part(state, part))
|
||||
@ -272,4 +337,141 @@ def make_output(state, line):
|
||||
if len(output) > 0:
|
||||
return output
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
class LinkSpec(urwid.AttrSpec):
|
||||
def __init__(self, link_target, orig_spec):
|
||||
self.link_target = link_target
|
||||
|
||||
urwid.AttrSpec.__init__(self, orig_spec.foreground, orig_spec.background)
|
||||
|
||||
|
||||
class LinkableText(urwid.Text):
|
||||
ignore_focus = False
|
||||
_selectable = True
|
||||
|
||||
signals = ["click", "change"]
|
||||
|
||||
def __init__(self, text, align=None, cursor_position=0, delegate=None):
|
||||
self.__super.__init__(text, align=align)
|
||||
self.delegate = delegate
|
||||
self._cursor_position = 0
|
||||
self.key_timeout = 3
|
||||
if self.delegate != None:
|
||||
self.delegate.last_keypress = 0
|
||||
|
||||
def handle_link(self, link_target):
|
||||
if self.delegate != None:
|
||||
self.delegate.handle_link(link_target)
|
||||
|
||||
|
||||
def find_next_part_pos(self, pos, part_positions):
|
||||
for position in part_positions:
|
||||
if position > pos:
|
||||
return position
|
||||
return pos
|
||||
|
||||
def find_prev_part_pos(self, pos, part_positions):
|
||||
nextpos = pos
|
||||
for position in part_positions:
|
||||
if position < pos:
|
||||
nextpos = position
|
||||
return nextpos
|
||||
|
||||
def find_item_at_pos(self, pos):
|
||||
total = 0
|
||||
text, parts = self.get_text()
|
||||
for i, info in enumerate(parts):
|
||||
style, length = info
|
||||
if total <= pos < length+total:
|
||||
return style
|
||||
|
||||
total += length
|
||||
|
||||
return None
|
||||
|
||||
def keypress(self, size, key):
|
||||
part_positions = [0]
|
||||
parts = []
|
||||
total = 0
|
||||
text, parts = self.get_text()
|
||||
for i, info in enumerate(parts):
|
||||
style_name, length = info
|
||||
part_positions.append(length+total)
|
||||
total += length
|
||||
|
||||
|
||||
if self.delegate != None:
|
||||
self.delegate.last_keypress = time.time()
|
||||
self._invalidate()
|
||||
nomadnet.NomadNetworkApp.get_shared_instance().ui.loop.set_alarm_in(self.key_timeout, self.kt_event)
|
||||
|
||||
if self._command_map[key] == urwid.ACTIVATE:
|
||||
item = self.find_item_at_pos(self._cursor_position)
|
||||
if item != None:
|
||||
if isinstance(item, LinkSpec):
|
||||
self.handle_link(item.link_target)
|
||||
|
||||
elif key == "up":
|
||||
self._cursor_position = 0
|
||||
return key
|
||||
|
||||
elif key == "down":
|
||||
self._cursor_position = 0
|
||||
return key
|
||||
|
||||
elif key == "right":
|
||||
self._cursor_position = self.find_next_part_pos(self._cursor_position, part_positions)
|
||||
self._invalidate()
|
||||
|
||||
elif key == "left":
|
||||
if self._cursor_position > 0:
|
||||
self._cursor_position = self.find_prev_part_pos(self._cursor_position, part_positions)
|
||||
self._invalidate()
|
||||
|
||||
else:
|
||||
if self.delegate != None:
|
||||
self.delegate.micron_released_focus()
|
||||
|
||||
else:
|
||||
return key
|
||||
|
||||
def kt_event(self, loop, user_data):
|
||||
self._invalidate()
|
||||
|
||||
def render(self, size, focus=False):
|
||||
now = time.time()
|
||||
c = self.__super.render(size, focus)
|
||||
|
||||
if focus and (self.delegate == None or now < self.delegate.last_keypress+self.key_timeout):
|
||||
c = urwid.CompositeCanvas(c)
|
||||
c.cursor = self.get_cursor_coords(size)
|
||||
return c
|
||||
|
||||
def get_cursor_coords(self, size):
|
||||
if self._cursor_position > len(self.text):
|
||||
return None
|
||||
|
||||
(maxcol,) = size
|
||||
trans = self.get_line_translation(maxcol)
|
||||
x, y = calc_coords(self.text, trans, self._cursor_position)
|
||||
if maxcol <= x:
|
||||
return None
|
||||
return x, y
|
||||
|
||||
def mouse_event(self, size, event, button, x, y, focus):
|
||||
if button != 1 or not is_mouse_press(event):
|
||||
return False
|
||||
else:
|
||||
pos = (y * size[0]) + x
|
||||
self._cursor_position = pos
|
||||
item = self.find_item_at_pos(self._cursor_position)
|
||||
if item != None:
|
||||
if isinstance(item, LinkSpec):
|
||||
self.handle_link(item.link_target)
|
||||
|
||||
self._invalidate()
|
||||
self._emit("change")
|
||||
|
||||
return True
|
@ -6,12 +6,14 @@ from datetime import datetime
|
||||
from nomadnet.Directory import DirectoryEntry
|
||||
from nomadnet.vendor.additional_urwid_widgets import IndicativeListBox, MODIFIER_KEY
|
||||
|
||||
from .Browser import Browser
|
||||
|
||||
class NetworkDisplayShortcuts():
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
g = app.ui.glyphs
|
||||
|
||||
self.widget = urwid.AttrMap(urwid.Text("[C-l] Toggle Nodes/Announces View [C-x] Remove entry"), "shortcutbar")
|
||||
self.widget = urwid.AttrMap(urwid.Text("[C-l] Toggle Nodes/Announces view [C-x] Remove entry [C-w] Disconnect remote"), "shortcutbar")
|
||||
# "[C-"+g["arrow_u"]+g["arrow_d"]+"] Navigate Lists"
|
||||
|
||||
|
||||
@ -392,6 +394,7 @@ class KnownNodes(urwid.WidgetWrap):
|
||||
self.delegate.close_list_dialogs()
|
||||
|
||||
def confirmed(sender):
|
||||
self.delegate.browser.retrieve_url(RNS.hexrep(source_hash, delimit=False))
|
||||
self.delegate.close_list_dialogs()
|
||||
|
||||
|
||||
@ -714,6 +717,8 @@ class NetworkLeftPile(urwid.Pile):
|
||||
def keypress(self, size, key):
|
||||
if key == "ctrl l":
|
||||
self.parent.toggle_list()
|
||||
elif key == "ctrl w":
|
||||
self.parent.browser.disconnect()
|
||||
else:
|
||||
return super(NetworkLeftPile, self).keypress(size, key)
|
||||
|
||||
@ -725,6 +730,8 @@ class NetworkDisplay():
|
||||
self.app = app
|
||||
g = self.app.ui.glyphs
|
||||
|
||||
self.browser = Browser(self.app, "nomadnetwork", "node", auth_identity = self.app.identity, delegate = self)
|
||||
|
||||
self.known_nodes_display = KnownNodes(self.app)
|
||||
self.network_stats_display = NetworkStats(self.app, self)
|
||||
self.announce_stream_display = AnnounceStream(self.app, self)
|
||||
@ -733,9 +740,9 @@ class NetworkDisplay():
|
||||
|
||||
self.known_nodes_display.delegate = self
|
||||
|
||||
self.list_display = 0
|
||||
self.list_display = 1
|
||||
self.left_pile = NetworkLeftPile([
|
||||
("weight", 1, self.announce_stream_display),
|
||||
("weight", 1, self.known_nodes_display),
|
||||
("pack", self.network_stats_display),
|
||||
("pack", self.local_peer_display),
|
||||
])
|
||||
@ -743,7 +750,7 @@ class NetworkDisplay():
|
||||
self.left_pile.parent = self
|
||||
|
||||
self.left_area = self.left_pile
|
||||
self.right_area = urwid.AttrMap(urwid.LineBox(urwid.Filler(urwid.Text("Disconnected\n"+g["arrow_l"]+" "+g["arrow_r"], align="center"), "middle"), title="Remote Node"), "inactive_text")
|
||||
self.right_area = self.browser.display_widget
|
||||
|
||||
self.columns = urwid.Columns(
|
||||
[
|
||||
@ -766,6 +773,9 @@ class NetworkDisplay():
|
||||
self.left_pile.contents[0] = (self.known_nodes_display, options)
|
||||
self.list_display = 1
|
||||
|
||||
def focus_lists(self):
|
||||
self.columns.focus_position = 0
|
||||
|
||||
def reinit_known_nodes(self):
|
||||
self.known_nodes_display = KnownNodes(self.app)
|
||||
self.known_nodes_display.delegate = self
|
||||
|
Loading…
x
Reference in New Issue
Block a user