diff --git a/clients/urwid/main.py b/clients/urwid/main.py index face685..b77280d 100644 --- a/clients/urwid/main.py +++ b/clients/urwid/main.py @@ -68,6 +68,7 @@ class App(object): } colors = [ + ("default", "default", "default"), ("bar", "light magenta", "default"), ("button", "light red", "default"), ("dim", "dark gray", "default"), @@ -91,10 +92,10 @@ class App(object): self.walker = urwid.SimpleFocusListWalker([]) self.loop = urwid.MainLoop(urwid.Frame( - urwid.LineBox(ActionBox(self.walker), + urwid.LineBox( + ActionBox(self.walker), title=self.prefs["frame_title"], - tlcorner="@", trcorner="@", blcorner="@", brcorner="@", - tline="=", bline="=", lline="|", rline="|" + **frame_theme() )), colors) self.index() @@ -286,18 +287,88 @@ class App(object): self.walker += self.make_message_body(message) - def refresh(self): + def refresh(self, bottom=False): if self.mode == "index": return self.index() self.thread_load(None, self.thread["thread_id"]) - self.loop.widget.body.base_widget.set_focus(len(self.walker) - 1) + if bottom: + self.loop.widget.body.base_widget.set_focus(len(self.walker) - 5) def back(self): - if self.mode == "thread": + if app.mode == "index": + frilly_exit() + elif app.mode == "thread": self.index() + def set_new_editor(self, button, value, key): + if value == False: + return + elif key == "internal": + key = None + self.prefs.update({"editor": key}) + bbjrc("update", **self.prefs) + + + def set_editor_mode(self, button, value): + self.prefs["integrate_external_editor"] = value + bbjrc("update", **self.prefs) + + + def options_menu(self): + editor_buttons = [] + edit_mode = [] + + urwid.RadioButton( + editor_buttons, "Internal", + state=not self.prefs["editor"], + on_state_change=self.set_new_editor, + user_data="internal") + + for editor in editors: + urwid.RadioButton( + editor_buttons, editor.title(), + state=self.prefs["editor"] == editor, + on_state_change=self.set_new_editor, + user_data=editor) + + urwid.RadioButton( + edit_mode, "Integrate", + state=self.prefs["integrate_external_editor"], + on_state_change=self.set_editor_mode) + + urwid.RadioButton( + edit_mode, "Overthrow", + state=not self.prefs["integrate_external_editor"]) + + widget = OptionsMenu( + urwid.Filler( + urwid.Pile([ + urwid.AttrMap(urwid.Text("Text editor:"), "button"), + urwid.Divider(), + *editor_buttons, + urwid.Divider("-"), + urwid.AttrMap(urwid.Text("External text editor mode:"), "button"), + urwid.Text("If you have problems using an external text editor, " + "set this to Overthrow."), + urwid.Divider(), + *edit_mode, + urwid.Divider("-"), + ]), "top"), + title="BBJ Options", + **frame_theme() + ) + + self.loop.widget = urwid.Overlay( + widget, self.loop.widget, + align="center", + valign="middle", + width=30, + height=(self.loop.screen_size[1] - 10) + ) + + def footer_prompt(self, text, callback, *callback_args, extra_text=None): text = "(%s)> " % text widget = urwid.Columns([ @@ -312,12 +383,20 @@ class App(object): ]) self.loop.widget.footer = widget - app.loop.widget.focus_position = "footer" + self.loop.widget.focus_position = "footer" + + + def reset_footer(self, *_, **__): + try: + self.set_footer(self.bars[self.mode]) + self.loop.widget.focus_position = "body" + except: + # just keep trying until the widget can handle it + self.loop.set_alarm_in(0.5, self.reset_footer) def temp_footer_message(self, string, duration=3): - self.loop.set_alarm_in( - duration, lambda x,y: self.set_footer(self.bars[self.mode])) + self.loop.set_alarm_in(duration, self.reset_footer) self.set_footer(string) @@ -353,46 +432,52 @@ class App(object): return self.temp_footer_message("EMPTY POST DISCARDED") params = {"body": body} - thread = False if self.mode == "thread": - thread = True + endpoint = "reply" params.update({"thread_id": self.thread["thread_id"]}) else: + endpoint = "create" params.update({"title": title}) - network.request("thread_" + ("reply" if thread else "create"), **params) - return self.refresh() + network.request("thread_" + endpoint, **params) + return self.refresh(True) - editor = ExternalEditor if self.prefs["editor"] else InternalEditor if self.mode == "index": self.set_header('Composing "{}"', title) - self.set_footer("[F1]Abort [Save and quit to submit your thread]") + if self.prefs["editor"]: + editor = ExternalEditor("thread_create", title=title) + self.set_footer("[F1]Abort [Save and quit to submit your thread]") + else: + editor = urwid.Filler( + InternalEditor("thread_create", title=title), + valign=urwid.TOP) + self.set_footer("[F1]Abort [F3]Send") self.loop.widget = urwid.Overlay( urwid.LineBox( - editor("thread_create", title=title), - title=self.prefs["editor"], - tlcorner="@", trcorner="@", blcorner="@", brcorner="@", - tline="-", bline="-", lline="|", rline="|"), - self.loop.widget, align="center", valign="middle", + editor, + title=self.prefs["editor"] or "", + **frame_theme()), + self.loop.widget, + align="center", + valign="middle", width=self.loop.screen_size[0] - 2, height=(self.loop.screen_size[1] - 4)) elif self.mode == "thread": self.window_split=True self.set_header('Replying to "{}"', self.thread["title"]) + if self.prefs["editor"]: + editor = ExternalEditor("thread_reply", thread_id=self.thread["thread_id"]) + else: + editor = urwid.AttrMap(urwid.Filler( + InternalEditor("thread_reply", thread_id=self.thread["thread_id"]), + valign=urwid.TOP), "default") self.loop.widget.footer = urwid.Pile([ urwid.AttrMap(urwid.Text(""), "bar"), - urwid.BoxAdapter( - urwid.AttrMap( - urwid.LineBox( - editor( - "thread_reply", - thread_id=self.thread["thread_id"]), - tlcorner="@", trcorner="@", blcorner="@", brcorner="@", - tline="-", bline="-", lline="|", rline="|" - ), "bar"), - self.loop.screen_size[1] // 2),]) + urwid.BoxAdapter(urwid.AttrMap(urwid.LineBox( + editor, **frame_theme() + ), "bar"), self.loop.screen_size[1] // 2),]) self.switch_editor() @@ -417,18 +502,28 @@ class FootPrompt(urwid.Edit): class InternalEditor(urwid.Edit): def __init__(self, endpoint, **params): - super(InternalEditor, self).__init__() + super(InternalEditor, self).__init__(multiline=True) self.endpoint = endpoint self.params = params def keypress(self, size, key): - if key not in ["f1", "f2"]: + if key not in ["f1", "f2", "f3"]: return super(InternalEditor, self).keypress(size, key) elif key == "f1": - return app.close_editor() + app.close_editor() + return app.refresh() elif key == "f2": return app.switch_editor() + body = self.get_edit_text().strip() + app.close_editor() + if body: + self.params.update({"body": body}) + network.request(self.endpoint, **self.params) + app.refresh(True) + else: + app.temp_footer_message("EMPTY POST DISCARDED") + class ExternalEditor(urwid.Terminal): def __init__(self, endpoint, **params): @@ -450,7 +545,7 @@ class ExternalEditor(urwid.Terminal): os.remove(self.path) if self.params["body"]: network.request(self.endpoint, **self.params) - return app.refresh() + return app.refresh(True) else: return app.temp_footer_message("EMPTY POST DISCARDED") @@ -465,9 +560,21 @@ class ExternalEditor(urwid.Terminal): app.switch_editor() +class OptionsMenu(urwid.LineBox): + def keypress(self, size, key): + super(OptionsMenu, self).keypress(size, key) + if key.lower() == "q": + app.loop.widget = app.loop.widget[0] + elif key in ["ctrl n", "j", "n"]: + return self.keypress(size, "down") + elif key in ["ctrl p", "k", "p"]: + return self.keypress(size, "up") + + class ActionBox(urwid.ListBox): """ - The listwalker used by all the browsing pages. Handles keys. + The listwalker used by all the browsing pages. Most of the application + takes place in an instance of this box. Handles many keybinds. """ def keypress(self, size, key): super(ActionBox, self).keypress(size, key) @@ -495,26 +602,23 @@ class ActionBox(urwid.ListBox): elif key in ["l", "right"]: self.keypress(size, "enter") - elif key.lower() == "b": + elif key == "b": self.change_focus(size, len(app.walker) - 1) - elif key.lower() == "t": + elif key == "t": self.change_focus(size, 0) - elif key == "c": + elif key in ["c", "R", "+"]: app.compose() - elif key == "6": - app.set_footer(app.overthrow_ext_edit()) - elif key == "r": app.refresh() + elif key == "o": + app.options_menu() + elif key.lower() == "q": - if app.mode == "index": - frilly_exit() - else: - app.back() + app.back() def frilly_exit(): @@ -675,6 +779,18 @@ def log_in(): motherfucking_rainbows("~~welcome to the party, %s!~~" % network.user_name) +def frame_theme(mode="default"): + """ + Return the kwargs for a frame theme. + """ + if mode == "default": + return dict( + tlcorner="@", trcorner="@", blcorner="@", brcorner="@", + tline="=", bline="=", lline="|", rline="|" + ) + + + def bbjrc(mode, **params): """ Maintains a user a preferences file, setting or returning