new time and bar display methods
parent
5358639307
commit
1b67057527
|
@ -1,6 +1,7 @@
|
||||||
# -*- fill-column: 72 -*-
|
# -*- fill-column: 72 -*-
|
||||||
|
|
||||||
from time import time, sleep, localtime
|
from time import time, sleep, localtime
|
||||||
|
from datetime import datetime
|
||||||
from network import BBJ, URLError
|
from network import BBJ, URLError
|
||||||
from string import punctuation
|
from string import punctuation
|
||||||
from subprocess import run
|
from subprocess import run
|
||||||
|
@ -49,17 +50,19 @@ editors = ["nano", "emacs", "vim", "micro", "ed", "joe"]
|
||||||
default_prefs = {
|
default_prefs = {
|
||||||
# well, it WILL default to the internal editor when I write it =)
|
# well, it WILL default to the internal editor when I write it =)
|
||||||
"editor": "vim",
|
"editor": "vim",
|
||||||
"dramatic_exit": True
|
"dramatic_exit": True,
|
||||||
|
"date": "%Y/%m/%d",
|
||||||
|
"time": "%H:%M"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class App(object):
|
class App(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.mode = None
|
self.bars = {
|
||||||
self.thread = None
|
"index": "[C]ompose [R]efresh [/]Search [O]ptions [?]Help/More [Q]uit",
|
||||||
self.usermap = {}
|
"thread": "[C]ompose [Q]Back [R]efresh [E]dit [/]Search [B/T]End [?]Help/More"
|
||||||
self.prefs = bbjrc("load")
|
}
|
||||||
self.window_split = False
|
|
||||||
colors = [
|
colors = [
|
||||||
("bar", "light magenta", "default"),
|
("bar", "light magenta", "default"),
|
||||||
("button", "light red", "default"),
|
("button", "light red", "default"),
|
||||||
|
@ -74,16 +77,22 @@ class App(object):
|
||||||
("5", "light cyan", "default"),
|
("5", "light cyan", "default"),
|
||||||
("6", "light magenta", "default")
|
("6", "light magenta", "default")
|
||||||
]
|
]
|
||||||
self.loop = urwid.MainLoop(urwid.Frame(
|
|
||||||
urwid.LineBox(ActionBox(urwid.SimpleFocusListWalker([])),
|
|
||||||
title="> > T I L D E T O W N < <",
|
|
||||||
tlcorner="@", tline="=", lline="|", rline="|",
|
|
||||||
bline="=", trcorner="@", brcorner="@", blcorner="@"
|
|
||||||
|
|
||||||
)), colors)
|
self.mode = None
|
||||||
self.walker = self.loop.widget.body.base_widget.body
|
self.thread = None
|
||||||
|
self.usermap = {}
|
||||||
|
self.prefs = bbjrc("load")
|
||||||
|
self.window_split = False
|
||||||
self.last_pos = 0
|
self.last_pos = 0
|
||||||
self.date_format = "{1}/{2}/{0}"
|
|
||||||
|
self.walker = urwid.SimpleFocusListWalker([])
|
||||||
|
self.loop = urwid.MainLoop(urwid.Frame(
|
||||||
|
urwid.LineBox(ActionBox(self.walker),
|
||||||
|
title="> > T I L D E T O W N < <",
|
||||||
|
tlcorner="@", trcorner="@", blcorner="@", brcorner="@",
|
||||||
|
tline="=", bline="=", lline="|", rline="|"
|
||||||
|
)), colors)
|
||||||
|
|
||||||
self.index()
|
self.index()
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,42 +108,48 @@ class App(object):
|
||||||
), "bar")
|
), "bar")
|
||||||
|
|
||||||
|
|
||||||
def set_footer(self, *controls, static_string=""):
|
def set_footer(self, string):
|
||||||
"""
|
"""
|
||||||
Sets the footer, emphasizing the first character of each string
|
Sets the footer to display `string`, applying bar formatting.
|
||||||
argument passed to it. Used to show controls to the user. Applies
|
Other than setting the color, `string` is shown verbatim.
|
||||||
bar formatting.
|
|
||||||
"""
|
"""
|
||||||
text = str()
|
self.loop.widget.footer = \
|
||||||
for control in controls:
|
urwid.AttrMap(urwid.Text(string), "bar")
|
||||||
text += "[{}]{} ".format(control[0], control[1:])
|
|
||||||
text += static_string
|
|
||||||
self.loop.widget.footer = urwid.AttrMap(urwid.Text(text), "bar")
|
|
||||||
|
|
||||||
|
|
||||||
def close_editor(self):
|
def close_editor(self):
|
||||||
|
"""
|
||||||
|
Close whatever editing widget is open and restore proper
|
||||||
|
state back the walker.
|
||||||
|
"""
|
||||||
if self.window_split:
|
if self.window_split:
|
||||||
self.window_split = False
|
self.window_split = False
|
||||||
self.loop.widget.focus_position = "body"
|
self.loop.widget.focus_position = "body"
|
||||||
self.set_footer("lmao")
|
self.set_footer(self.bars["thread"])
|
||||||
|
name = self.usermap[self.thread["author"]]["user_name"]
|
||||||
|
self.set_header("~{}: {}", name, self.thread["title"])
|
||||||
else:
|
else:
|
||||||
self.loop.widget = self.loop.widget[0]
|
self.loop.widget = self.loop.widget[0]
|
||||||
|
|
||||||
|
|
||||||
def switch_editor(self):
|
def switch_editor(self):
|
||||||
|
"""
|
||||||
|
Switch focus between the thread viewer and the open editor
|
||||||
|
"""
|
||||||
if not self.window_split:
|
if not self.window_split:
|
||||||
return
|
return
|
||||||
|
|
||||||
elif self.loop.widget.focus_position == "body":
|
elif self.loop.widget.focus_position == "body":
|
||||||
self.loop.widget.focus_position = "footer"
|
self.loop.widget.focus_position = "footer"
|
||||||
focus = "[focused on editor]"
|
focus = "[focused on editor]"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.loop.widget.focus_position = "body"
|
self.loop.widget.focus_position = "body"
|
||||||
focus = "[focused on thread]"
|
focus = "[focused on thread]"
|
||||||
if self.window_split:
|
|
||||||
control = ("" if self.prefs["editor"] else " [F3]Send")
|
control = "[save/quit to send]" if self.prefs["editor"] else "[F3]Send"
|
||||||
self.loop.widget.footer[0].set_text(
|
self.loop.widget.footer[0].set_text(
|
||||||
"[F1]Abort [F2]Swap%s %s" % (control, focus)
|
"[F1]Abort [F2]Swap %s %s" % (control, focus))
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def readable_delta(self, modified):
|
def readable_delta(self, modified):
|
||||||
|
@ -145,7 +160,7 @@ class App(object):
|
||||||
delta = time() - modified
|
delta = time() - modified
|
||||||
hours, remainder = divmod(delta, 3600)
|
hours, remainder = divmod(delta, 3600)
|
||||||
if hours > 48:
|
if hours > 48:
|
||||||
return self.date_format.format(*localtime(modified))
|
return self.timestring(modified)
|
||||||
elif hours > 1:
|
elif hours > 1:
|
||||||
return "%d hours ago" % hours
|
return "%d hours ago" % hours
|
||||||
elif hours == 1:
|
elif hours == 1:
|
||||||
|
@ -158,7 +173,7 @@ class App(object):
|
||||||
|
|
||||||
def make_message_body(self, message):
|
def make_message_body(self, message):
|
||||||
name = urwid.Text("~{}".format(self.usermap[message["author"]]["user_name"]))
|
name = urwid.Text("~{}".format(self.usermap[message["author"]]["user_name"]))
|
||||||
info = "@ " + self.date_format.format(*localtime(message["created"]))
|
info = "@ " + self.timestring(message["created"])
|
||||||
if message["edited"]:
|
if message["edited"]:
|
||||||
info += " [edited]"
|
info += " [edited]"
|
||||||
|
|
||||||
|
@ -180,17 +195,34 @@ class App(object):
|
||||||
return pile
|
return pile
|
||||||
|
|
||||||
|
|
||||||
|
def timestring(self, epoch, mode="both"):
|
||||||
|
if mode == "delta":
|
||||||
|
return self.readable_delta(epoch)
|
||||||
|
|
||||||
|
date = datetime.fromtimestamp(epoch)
|
||||||
|
if mode == "time":
|
||||||
|
directive = self.prefs["time"]
|
||||||
|
elif mode == "date":
|
||||||
|
directive = self.prefs["date"]
|
||||||
|
else:
|
||||||
|
directive = "%s %s" % (self.prefs["date"], self.prefs["time"])
|
||||||
|
return date.strftime(directive)
|
||||||
|
|
||||||
|
|
||||||
def make_thread_body(self, thread):
|
def make_thread_body(self, thread):
|
||||||
button = cute_button(">>", self.thread_load, thread["thread_id"])
|
button = cute_button(">>", self.thread_load, thread["thread_id"])
|
||||||
title = urwid.Text(thread["title"])
|
title = urwid.Text(thread["title"])
|
||||||
infoline = "by ~{} @ {} | last active {}".format(
|
dateline = "by ~{} @ {}".format(
|
||||||
self.usermap[thread["author"]]["user_name"],
|
self.usermap[thread["author"]]["user_name"],
|
||||||
self.date_format.format(*localtime(thread["created"])),
|
self.timestring(thread["created"])
|
||||||
self.readable_delta(thread["last_mod"])
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
infoline = "%d replies; active %s" % (
|
||||||
|
thread["reply_count"], self.timestring(thread["last_mod"], "delta"))
|
||||||
|
|
||||||
pile = urwid.Pile([
|
pile = urwid.Pile([
|
||||||
urwid.Columns([(3, urwid.AttrMap(button, "button")), title]),
|
urwid.Columns([(3, urwid.AttrMap(button, "button")), title]),
|
||||||
|
urwid.AttrMap(urwid.Text(dateline), "dim"),
|
||||||
urwid.AttrMap(urwid.Text(infoline), "dim"),
|
urwid.AttrMap(urwid.Text(infoline), "dim"),
|
||||||
urwid.AttrMap(urwid.Divider("-"), "dim")
|
urwid.AttrMap(urwid.Divider("-"), "dim")
|
||||||
])
|
])
|
||||||
|
@ -209,11 +241,13 @@ class App(object):
|
||||||
threads, usermap = network.thread_index()
|
threads, usermap = network.thread_index()
|
||||||
self.usermap.update(usermap)
|
self.usermap.update(usermap)
|
||||||
self.set_header("{} threads", len(threads))
|
self.set_header("{} threads", len(threads))
|
||||||
self.set_footer("Refresh", "Compose", "Quit", "/Search", "?Help")
|
self.set_footer(self.bars["index"])
|
||||||
self.walker.clear()
|
self.walker.clear()
|
||||||
for thread in threads:
|
for thread in threads:
|
||||||
self.walker.append(self.make_thread_body(thread))
|
self.walker.append(self.make_thread_body(thread))
|
||||||
self.loop.widget.body.base_widget.set_focus(self.last_pos)
|
try: self.loop.widget.body.base_widget.set_focus(self.last_pos)
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def thread_load(self, button, thread_id):
|
def thread_load(self, button, thread_id):
|
||||||
|
@ -229,11 +263,7 @@ class App(object):
|
||||||
self.walker.clear()
|
self.walker.clear()
|
||||||
self.set_header("~{}: {}",
|
self.set_header("~{}: {}",
|
||||||
usermap[thread["author"]]["user_name"], thread["title"])
|
usermap[thread["author"]]["user_name"], thread["title"])
|
||||||
self.set_footer(
|
self.set_footer(self.bars["thread"])
|
||||||
"Compose", "Refresh",
|
|
||||||
"\"Quote", "/Search",
|
|
||||||
"Top", "Bottom", "QBack"
|
|
||||||
)
|
|
||||||
for message in thread["messages"]:
|
for message in thread["messages"]:
|
||||||
self.walker.append(self.make_message_body(message))
|
self.walker.append(self.make_message_body(message))
|
||||||
|
|
||||||
|
@ -241,7 +271,9 @@ class App(object):
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
if self.mode == "index":
|
if self.mode == "index":
|
||||||
self.index()
|
return self.index()
|
||||||
|
self.thread_load(None, self.thread["thread_id"])
|
||||||
|
self.loop.widget.body.base_widget.set_focus(len(self.walker) - 1)
|
||||||
|
|
||||||
|
|
||||||
def back(self):
|
def back(self):
|
||||||
|
@ -278,8 +310,7 @@ class App(object):
|
||||||
"Title", self.compose, extra_text=e.description)
|
"Title", self.compose, extra_text=e.description)
|
||||||
|
|
||||||
self.set_header('Composing "{}"', title)
|
self.set_header('Composing "{}"', title)
|
||||||
self.set_footer(static_string=
|
self.set_footer("[F1]Abort [Save and quit to submit your thread]")
|
||||||
"[F1]Abort [Save and quit to submit your thread]")
|
|
||||||
|
|
||||||
self.loop.widget = urwid.Overlay(
|
self.loop.widget = urwid.Overlay(
|
||||||
urwid.LineBox(
|
urwid.LineBox(
|
||||||
|
@ -294,14 +325,14 @@ class App(object):
|
||||||
self.set_header('Replying to "{}"', self.thread["title"])
|
self.set_header('Replying to "{}"', self.thread["title"])
|
||||||
self.loop.widget.footer = urwid.Pile([
|
self.loop.widget.footer = urwid.Pile([
|
||||||
urwid.AttrMap(urwid.Text(""), "bar"),
|
urwid.AttrMap(urwid.Text(""), "bar"),
|
||||||
urwid.BoxAdapter(urwid.LineBox(editor("thread_reply", thread_id=self.thread["thread_id"])), 15),
|
urwid.BoxAdapter(urwid.LineBox(editor("thread_reply",
|
||||||
|
thread_id=self.thread["thread_id"])),
|
||||||
|
self.loop.screen_size[1] // 2),
|
||||||
])
|
])
|
||||||
self.switch_editor()
|
self.switch_editor()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MessageBody(urwid.Text):
|
class MessageBody(urwid.Text):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -317,7 +348,7 @@ class FootPrompt(urwid.Edit):
|
||||||
if key != "enter":
|
if key != "enter":
|
||||||
return super(FootPrompt, self).keypress(size, key)
|
return super(FootPrompt, self).keypress(size, key)
|
||||||
app.loop.widget.focus_position = "body"
|
app.loop.widget.focus_position = "body"
|
||||||
app.set_footer()
|
app.set_footer(app.bars[app.mode])
|
||||||
self.callback(self.get_edit_text(), *self.args)
|
self.callback(self.get_edit_text(), *self.args)
|
||||||
|
|
||||||
|
|
||||||
|
@ -580,20 +611,24 @@ def bbjrc(mode, **params):
|
||||||
values depending on `mode`.
|
values depending on `mode`.
|
||||||
"""
|
"""
|
||||||
path = os.path.join(os.getenv("HOME"), ".bbjrc")
|
path = os.path.join(os.getenv("HOME"), ".bbjrc")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# load it up
|
||||||
with open(path, "r") as _in:
|
with open(path, "r") as _in:
|
||||||
values = json.load(_in)
|
values = json.load(_in)
|
||||||
|
# update it with new keys if necessary
|
||||||
|
for key, default_value in default_prefs.items():
|
||||||
|
if key not in values:
|
||||||
|
values[key] = default_value
|
||||||
|
# else make one
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
values = default_prefs
|
values = default_prefs
|
||||||
|
|
||||||
|
if mode == "update":
|
||||||
|
values.update(params)
|
||||||
|
|
||||||
with open(path, "w") as _out:
|
with open(path, "w") as _out:
|
||||||
json.dump(values, _out)
|
json.dump(values, _out)
|
||||||
|
|
||||||
if mode == "load":
|
|
||||||
return values
|
|
||||||
values.update(params)
|
|
||||||
with open(path, "w") as _out:
|
|
||||||
json.dump(values, _out)
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
@ -603,7 +638,7 @@ def main():
|
||||||
motherfucking_rainbows(obnoxious_logo)
|
motherfucking_rainbows(obnoxious_logo)
|
||||||
print(welcome)
|
print(welcome)
|
||||||
log_in()
|
log_in()
|
||||||
sleep(0.6) # let that confirmation message shine
|
sleep(0.8) # let that confirmation message shine
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
global app
|
global app
|
||||||
|
|
Loading…
Reference in New Issue