Commit 94ce313e authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge pull request #98 from mruwek/column-ui-multi

Execute commands on multiple items + repeat command
parents 8b1ff41a 06563a35
......@@ -79,6 +79,7 @@ x = cmd do {}
pp = postpone
ps = postpone_s
pr = pri
m = mark
0 = first_column
$ = last_column
h = prev_column
......@@ -92,3 +93,4 @@ L = swap_left
R = swap_right
<Left> = prev_column
<Right> = next_column
<Esc> = reset
......@@ -125,6 +125,8 @@ class _Config:
'e': 'cmd edit {}',
'u': 'cmd revert',
'x': 'cmd do {}',
'm': 'mark',
'.': 'repeat',
'pp': 'postpone',
'ps': 'postpone_s',
'pr': 'pri',
......@@ -141,6 +143,7 @@ class _Config:
'R': 'swap_right',
'<Left>': 'prev_column',
'<Right>': 'next_column',
'<Esc>': 'reset',
},
}
......
......@@ -96,6 +96,8 @@ class UIApplication(CLIApplicationBase):
self.todofile = TodoFile.TodoFile(config().todotxt())
self.todolist = TodoList.TodoList(self.todofile.read())
self.marked_todos = []
self.columns = urwid.Columns([], dividechars=0, min_width=COLUMN_WIDTH)
self.commandline = CommandLineWidget('topydo> ')
self.keystate_widget = KeystateWidget()
......@@ -106,6 +108,8 @@ class UIApplication(CLIApplicationBase):
self.keymap = config().column_keymap()
self._alarm = None
self._last_cmd = None
# console widget
self.console = ConsoleWidget()
get_terminal_size(self._console_width)
......@@ -165,11 +169,19 @@ class UIApplication(CLIApplicationBase):
def _output(self, p_text):
self._print_to_console(p_text + "\n")
def _execute_handler(self, p_command, p_output=None):
def _execute_handler(self, p_command, p_todo_id=None, p_output=None):
"""
Executes a command, given as a string.
"""
p_output = p_output or self._output
self._last_cmd = (p_command, p_output == self._output)
if '{}' in p_command:
if self._has_marked_todos():
p_todo_id = ' '.join(self.marked_todos)
p_command = p_command.format(p_todo_id)
p_command = shlex.split(p_command)
try:
(subcommand, args) = get_subcommand(p_command)
......@@ -199,14 +211,28 @@ class UIApplication(CLIApplicationBase):
def _update_all_columns(self):
for column, _ in self.columns.contents:
column.update()
column.keystate = None
def _post_execute(self):
# store dirty flag because base _post_execute will reset it after flush
dirty = self.todolist.dirty
super()._post_execute()
if dirty:
self._update_all_columns()
if dirty or self.marked_todos:
self._reset_state()
def _repeat_last_cmd(self, p_todo_id=None):
try:
cmd, verbosity = self._last_cmd
except TypeError:
return
self._execute_handler(cmd, p_todo_id,
self._output if verbosity else lambda _: None)
def _reset_state(self):
self.marked_todos = []
self._update_all_columns()
def _blur_commandline(self):
self.mainwindow.focus_item = 0
......@@ -276,6 +302,7 @@ class UIApplication(CLIApplicationBase):
'copy_column': self._copy_column,
'swap_left': self._swap_column_left,
'swap_right': self._swap_column_right,
'reset': self._reset_state,
}
dispatch[p_action]()
......@@ -328,17 +355,21 @@ class UIApplication(CLIApplicationBase):
When no position is given, it is added to the end, otherwise inserted
before that position.
"""
def execute_silent(p_cmd, p_todo_id=None):
self._execute_handler(p_cmd, p_todo_id, lambda _: None)
todolist = TodoListWidget(p_view, p_view.data['title'], self.keymap)
no_output = lambda _: None
urwid.connect_signal(todolist, 'execute_command_silent',
lambda cmd: self._execute_handler(cmd, no_output))
execute_silent)
urwid.connect_signal(todolist, 'execute_command', self._execute_handler)
urwid.connect_signal(todolist, 'repeat_cmd', self._repeat_last_cmd)
urwid.connect_signal(todolist, 'refresh', self.mainloop.screen.clear)
urwid.connect_signal(todolist, 'add_pending_action', self._set_alarm)
urwid.connect_signal(todolist, 'remove_pending_action', self._remove_alarm)
urwid.connect_signal(todolist, 'column_action', self._column_action_handler)
urwid.connect_signal(todolist, 'show_keystate', self._print_keystate)
urwid.connect_signal(todolist, 'toggle_mark',
self._process_mark_toggle)
options = self.columns.options(
width_type='given',
......@@ -455,6 +486,22 @@ class UIApplication(CLIApplicationBase):
return sz
def _has_marked_todos(self):
return len(self.marked_todos) > 0
def _process_mark_toggle(self, p_todo_id):
"""
Adds p_todo_id to marked_todos attribute and returns True if p_todo_id
is not already present. Removes p_todo_id from marked_todos and returns
False otherwise.
"""
if p_todo_id not in self.marked_todos:
self.marked_todos.append(p_todo_id)
return True
else:
self.marked_todos.remove(p_todo_id)
return False
def run(self):
layout = columns()
if len(layout) > 0:
......
......@@ -60,8 +60,10 @@ class TodoListWidget(urwid.LineBox):
'refresh',
'add_pending_action',
'remove_pending_action',
'repeat_cmd',
'column_action',
'show_keystate',
'toggle_mark',
])
@property
......@@ -199,6 +201,18 @@ class TodoListWidget(urwid.LineBox):
def selectable(self):
return True
def _toggle_marked_status(self):
try:
todo = self.listbox.focus.todo
todo_id = str(self.view.todolist.number(todo))
if urwid.emit_signal(self, 'toggle_mark', todo_id):
self.listbox.focus.mark()
else:
self.listbox.focus.unmark()
except AttributeError:
# No todo item selected
pass
def _execute_on_selected(self, p_cmd_str, p_execute_signal):
"""
Executes command specified by p_cmd_str on selected todo item.
......@@ -213,7 +227,7 @@ class TodoListWidget(urwid.LineBox):
todo = self.listbox.focus.todo
todo_id = str(self.view.todolist.number(todo))
urwid.emit_signal(self, p_execute_signal, p_cmd_str.format(todo_id))
urwid.emit_signal(self, p_execute_signal, p_cmd_str, todo_id)
# force screen redraw after editing
if p_cmd_str.startswith('edit'):
......@@ -250,8 +264,8 @@ class TodoListWidget(urwid.LineBox):
Currently supported actions are: 'up', 'down', 'home', 'end',
'first_column', 'last_column', 'prev_column', 'next_column',
'append_column', 'insert_column', 'edit_column', 'delete_column',
'copy_column', swap_right', 'swap_left', 'postpone', 'postpone_s' and
'pri'.
'copy_column', swap_right', 'swap_left', 'postpone', 'postpone_s',
'pri', 'mark', 'reset' and 'repeat'.
"""
column_actions = ['first_column',
'last_column',
......@@ -264,6 +278,7 @@ class TodoListWidget(urwid.LineBox):
'copy_column',
'swap_left',
'swap_right',
'reset',
]
if p_action_str in column_actions:
......@@ -278,6 +293,10 @@ class TodoListWidget(urwid.LineBox):
pass
elif p_action_str == 'pri':
pass
elif p_action_str == 'mark':
self._toggle_marked_status()
elif p_action_str == 'repeat':
self._repeat_cmd()
def _add_pending_action(self, p_action, p_size):
"""
......@@ -328,3 +347,12 @@ class TodoListWidget(urwid.LineBox):
self._pp_offset = None
result = False
return result
def _repeat_cmd(self):
try:
todo = self.listbox.focus.todo
todo_id = str(self.view.todolist.number(todo))
except AttributeError:
todo_id = None
urwid.emit_signal(self, 'repeat_cmd', todo_id)
......@@ -35,6 +35,7 @@ def _to_urwid_color(p_color):
else:
return 'h{}'.format(p_color.color)
def _markup(p_todo, p_focus):
"""
Returns an attribute spec for the colors that correspond to the given todo
......@@ -83,8 +84,8 @@ class TodoWidget(urwid.WidgetWrap):
self.widget = urwid.AttrMap(
self.columns,
_markup(p_todo, False), # no focus
_markup(p_todo, True) # focus
_markup(p_todo, False), # no focus
_markup(p_todo, True) # focus
)
super().__init__(self.widget)
......@@ -99,3 +100,9 @@ class TodoWidget(urwid.WidgetWrap):
def selectable(self):
# make sure that ListBox will highlight this widget
return True
def mark(self):
self.widget.set_attr_map({None: _markup(self.todo, True)})
def unmark(self):
self.widget.set_attr_map({None: _markup(self.todo, False)})
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment