Commit 981d1af2 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge pull request #101 from mruwek/column-ui-keymap

Introduce keymap for column_mode
parents a2a77716 b7a99d42
[column_keymap]
up = up
<Left> = prev_column
<Esc>d = delete_column
...@@ -140,5 +140,31 @@ class ConfigTest(TopydoTest): ...@@ -140,5 +140,31 @@ class ConfigTest(TopydoTest):
self.assertEqual(config("test/data/ConfigTest5.conf").link_color(), self.assertEqual(config("test/data/ConfigTest5.conf").link_color(),
config().defaults["colorscheme"]["link_color"]) config().defaults["colorscheme"]["link_color"])
def test_config24(self):
""" column_keymap test. """
keymap, keystates = config("test/data/ConfigTest6.conf").column_keymap()
self.assertEqual(keymap['pp'], 'postpone')
self.assertEqual(keymap['ps'], 'postpone_s')
self.assertEqual(keymap['pr'], 'pri')
self.assertEqual(keymap['pra'], 'cmd pri {} a')
self.assertIn('p', keystates)
self.assertIn('g', keystates)
self.assertIn('pp', keystates)
self.assertIn('ps', keystates)
self.assertIn('pr', keystates)
self.assertEqual(keymap['up'], 'up')
self.assertIn('u', keystates)
self.assertEqual(keymap['<Left>'], 'prev_column')
self.assertNotIn('<Lef', keystates)
self.assertEqual(keymap['<Esc>d'], 'delete_column')
self.assertNotIn('<Esc', keystates)
self.assertIn('<Esc>', keystates)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
from test.topydo_testcase import TopydoTest
from topydo.lib.Utils import translate_key_to_config
class UtilsTest(TopydoTest):
def test_key_to_cfg(self):
ctrl_s = translate_key_to_config('ctrl s')
meta_d = translate_key_to_config('meta d')
esc = translate_key_to_config('esc')
f4 = translate_key_to_config('f4')
self.assertEqual(ctrl_s, '<C-s>')
self.assertEqual(meta_d, '<M-d>')
self.assertEqual(esc, '<Esc>')
self.assertEqual(f4, '<F4>')
if __name__ == '__main__':
unittest.main()
...@@ -63,3 +63,30 @@ append_parent_contexts = 0 ...@@ -63,3 +63,30 @@ append_parent_contexts = 0
;listcon = lscon ;listcon = lscon
;listcontext = lscon ;listcontext = lscon
;listcontexts = lscon ;listcontexts = lscon
[column_keymap]
; Keymap configuration for column-mode
gg = home
G = end
j = down
k = up
d = cmd del {}
e = cmd edit {}
u = cmd revert
x = cmd do {}
pp = postpone
ps = postpone_s
pr = pri
0 = first_column
$ = last_column
h = prev_column
l = next_column
A = append_column
I = insert_column
E = edit_column
D = delete_column
Y = copy_column
L = swap_left
R = swap_right
<Left> = prev_column
<Right> = next_column
...@@ -16,8 +16,12 @@ ...@@ -16,8 +16,12 @@
import configparser import configparser
import os import os
import re
import shlex import shlex
from itertools import accumulate
from string import ascii_lowercase
def home_config_path(p_filename): def home_config_path(p_filename):
return os.path.join(os.path.expanduser('~'), p_filename) return os.path.join(os.path.expanduser('~'), p_filename)
...@@ -109,11 +113,40 @@ class _Config: ...@@ -109,11 +113,40 @@ class _Config:
'listcontext': 'lscon', 'listcontext': 'lscon',
'listcontexts': 'lscon', 'listcontexts': 'lscon',
}, },
'column_keymap': {
'gg': 'home',
'G': 'end',
'j': 'down',
'k': 'up',
'd': 'cmd del {}',
'e': 'cmd edit {}',
'u': 'cmd revert',
'x': 'cmd do {}',
'pp': 'postpone',
'ps': 'postpone_s',
'pr': 'pri',
'0': 'first_column',
'$': 'last_column',
'h': 'prev_column',
'l': 'next_column',
'A': 'append_column',
'I': 'insert_column',
'E': 'edit_column',
'D': 'delete_column',
'Y': 'copy_column',
'L': 'swap_left',
'R': 'swap_right',
'<Left>': 'prev_column',
'<Right>': 'next_column',
},
} }
self.config = {} self.config = {}
self.cp = configparser.RawConfigParser() self.cp = configparser.RawConfigParser()
# allow uppercase config keys
self.cp.optionxform = lambda option: option
for section in self.defaults: for section in self.defaults:
self.cp.add_section(section) self.cp.add_section(section)
...@@ -311,6 +344,29 @@ class _Config: ...@@ -311,6 +344,29 @@ class _Config:
""" Returns the list format used by `ls` """ """ Returns the list format used by `ls` """
return self.cp.get('ls', 'list_format') return self.cp.get('ls', 'list_format')
def column_keymap(self):
""" Returns keymap and keystates used in column mode """
keystates = set()
shortcuts = self.cp.items('column_keymap')
keymap_dict = dict(shortcuts)
for combo, action in shortcuts:
# add all possible prefixes to keystates
combo_as_list = re.split('(<[A-Z].+?>|.)', combo)[1::2]
if len(combo_as_list) > 1:
keystates |= set(accumulate(combo_as_list[:-1]))
if action in ['pri', 'postpone', 'postpone_s']:
keystates.add(combo)
if action == 'pri':
for c in ascii_lowercase:
keymap_dict[combo + c] = 'cmd pri {} ' + c
return (keymap_dict, keystates)
def config(p_path=None, p_overrides=None): def config(p_path=None, p_overrides=None):
""" """
Retrieve the config instance. Retrieve the config instance.
......
...@@ -94,3 +94,18 @@ def get_terminal_size(p_getter=None): ...@@ -94,3 +94,18 @@ def get_terminal_size(p_getter=None):
get_terminal_size.getter = inner get_terminal_size.getter = inner
return get_terminal_size.getter() return get_terminal_size.getter()
def translate_key_to_config(p_key):
"""
Translates urwid key event to form understandable by topydo config parser.
"""
if len(p_key) > 1:
key = p_key.capitalize()
if key.startswith('Ctrl') or key.startswith('Meta'):
key = key[0] + '-' + key[5:]
key = '<' + key + '>'
else:
key = p_key
return key
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
import urwid import urwid
class ConsoleWidget(urwid.LineBox): class ConsoleWidget(urwid.LineBox):
def __init__(self, p_text=""): def __init__(self, p_text=""):
urwid.register_signal(ConsoleWidget, ['close']) urwid.register_signal(ConsoleWidget, ['close'])
...@@ -29,7 +30,10 @@ class ConsoleWidget(urwid.LineBox): ...@@ -29,7 +30,10 @@ class ConsoleWidget(urwid.LineBox):
if p_key == 'enter' or p_key == 'q' or p_key == 'esc': if p_key == 'enter' or p_key == 'q' or p_key == 'esc':
urwid.emit_signal(self, 'close') urwid.emit_signal(self, 'close')
# don't return the key, 'enter', 'escape' or 'q' are your only escape. # don't return the key, 'enter', 'escape', 'q' or ':' are your only
# escape. ':' will reenter to the cmdline.
elif p_key == ':':
urwid.emit_signal(self, 'close', True)
def render(self, p_size, focus): def render(self, p_size, focus):
""" """
......
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2015 Bram Schoenmakers <me@bramschoenmakers.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urwid
class KeystateWidget(urwid.Text):
def __init__(self):
super().__init__('', align='right')
def selectable(self):
return False
...@@ -24,6 +24,7 @@ from topydo.cli.CLIApplicationBase import CLIApplicationBase ...@@ -24,6 +24,7 @@ from topydo.cli.CLIApplicationBase import CLIApplicationBase
from topydo.Commands import get_subcommand from topydo.Commands import get_subcommand
from topydo.ui.CommandLineWidget import CommandLineWidget from topydo.ui.CommandLineWidget import CommandLineWidget
from topydo.ui.ConsoleWidget import ConsoleWidget from topydo.ui.ConsoleWidget import ConsoleWidget
from topydo.ui.KeystateWidget import KeystateWidget
from topydo.ui.TodoListWidget import TodoListWidget from topydo.ui.TodoListWidget import TodoListWidget
from topydo.ui.ViewWidget import ViewWidget from topydo.ui.ViewWidget import ViewWidget
from topydo.ui.ColumnLayout import columns from topydo.ui.ColumnLayout import columns
...@@ -37,6 +38,7 @@ from topydo.lib import TodoList ...@@ -37,6 +38,7 @@ from topydo.lib import TodoList
COLUMN_WIDTH = 40 COLUMN_WIDTH = 40
class UIView(View): class UIView(View):
""" """
A subclass of view holding user input data that constructed the view (i.e. A subclass of view holding user input data that constructed the view (i.e.
...@@ -51,6 +53,7 @@ _EDIT_COLUMN = 2 ...@@ -51,6 +53,7 @@ _EDIT_COLUMN = 2
_COPY_COLUMN = 3 _COPY_COLUMN = 3
_INSERT_COLUMN = 4 _INSERT_COLUMN = 4
class MainPile(urwid.Pile): class MainPile(urwid.Pile):
""" """
This subclass of Pile doesn't change focus on cursor up/down / mouse press This subclass of Pile doesn't change focus on cursor up/down / mouse press
...@@ -82,6 +85,7 @@ class MainPile(urwid.Pile): ...@@ -82,6 +85,7 @@ class MainPile(urwid.Pile):
if self._command_map[key] not in ('cursor up', 'cursor down'): if self._command_map[key] not in ('cursor up', 'cursor down'):
return key return key
class UIApplication(CLIApplicationBase): class UIApplication(CLIApplicationBase):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
...@@ -93,6 +97,13 @@ class UIApplication(CLIApplicationBase): ...@@ -93,6 +97,13 @@ class UIApplication(CLIApplicationBase):
self.columns = urwid.Columns([], dividechars=0, min_width=COLUMN_WIDTH) self.columns = urwid.Columns([], dividechars=0, min_width=COLUMN_WIDTH)
self.commandline = CommandLineWidget('topydo> ') self.commandline = CommandLineWidget('topydo> ')
self.keystate_widget = KeystateWidget()
self.status_line = urwid.Columns([
('weight', 1, urwid.Filler(self.commandline)),
])
self.keymap = config().column_keymap()
self._alarm = None
# console widget # console widget
self.console = ConsoleWidget() self.console = ConsoleWidget()
...@@ -102,8 +113,10 @@ class UIApplication(CLIApplicationBase): ...@@ -102,8 +113,10 @@ class UIApplication(CLIApplicationBase):
urwid.connect_signal(self.commandline, 'execute_command', urwid.connect_signal(self.commandline, 'execute_command',
self._execute_handler) self._execute_handler)
def hide_console(): def hide_console(p_focus_commandline=False):
self._console_visible = False self._console_visible = False
if p_focus_commandline:
self._focus_commandline()
urwid.connect_signal(self.console, 'close', hide_console) urwid.connect_signal(self.console, 'close', hide_console)
# view widget # view widget
...@@ -120,7 +133,7 @@ class UIApplication(CLIApplicationBase): ...@@ -120,7 +133,7 @@ class UIApplication(CLIApplicationBase):
self.mainwindow = MainPile([ self.mainwindow = MainPile([
('weight', 1, self.columns), ('weight', 1, self.columns),
(1, urwid.Filler(self.commandline)), (1, self.status_line),
]) ])
urwid.connect_signal(self.mainwindow, 'blur_console', hide_console) urwid.connect_signal(self.mainwindow, 'blur_console', hide_console)
...@@ -244,22 +257,25 @@ class UIApplication(CLIApplicationBase): ...@@ -244,22 +257,25 @@ class UIApplication(CLIApplicationBase):
self.column_mode = _COPY_COLUMN self.column_mode = _COPY_COLUMN
self._viewwidget_visible = True self._viewwidget_visible = True
def _column_action_handler(self, p_action):
dispatch = {
'first_column': self._focus_first_column,
'last_column': self._focus_last_column,
'prev_column': self._focus_previous_column,
'next_column': self._focus_next_column,
'append_column': self._append_column,
'insert_column': self._insert_column,
'edit_column': self._edit_column,
'delete_column': self._delete_column,
'copy_column': self._copy_column,
'swap_left': self._swap_column_left,
'swap_right': self._swap_column_right,
}
dispatch[p_action]()
def _handle_input(self, p_input): def _handle_input(self, p_input):
dispatch = { dispatch = {
':': self._focus_commandline, ':': self._focus_commandline,
'0': self._focus_first_column,
'$': self._focus_last_column,
'left': self._focus_previous_column,
'h': self._focus_previous_column,
'right': self._focus_next_column,
'l': self._focus_next_column,
'A': self._append_column,
'I': self._insert_column,
'E': self._edit_column,
'D': self._delete_column,
'Y': self._copy_column,
'L': self._swap_column_left,
'R': self._swap_column_right,
} }
try: try:
...@@ -307,11 +323,16 @@ class UIApplication(CLIApplicationBase): ...@@ -307,11 +323,16 @@ class UIApplication(CLIApplicationBase):
before that position. before that position.
""" """
todolist = TodoListWidget(p_view, p_view.data['title']) todolist = TodoListWidget(p_view, p_view.data['title'], self.keymap)
no_output = lambda _: None no_output = lambda _: None
urwid.connect_signal(todolist, 'execute_command', urwid.connect_signal(todolist, 'execute_command_silent',
lambda cmd: self._execute_handler(cmd, no_output)) lambda cmd: self._execute_handler(cmd, no_output))
urwid.connect_signal(todolist, 'execute_command', self._execute_handler)
urwid.connect_signal(todolist, 'refresh', self.mainloop.screen.clear) 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)
options = self.columns.options( options = self.columns.options(
width_type='given', width_type='given',
...@@ -329,6 +350,19 @@ class UIApplication(CLIApplicationBase): ...@@ -329,6 +350,19 @@ class UIApplication(CLIApplicationBase):
self.columns.focus_position = p_pos self.columns.focus_position = p_pos
self._blur_commandline() self._blur_commandline()
def _print_keystate(self, p_keystate):
self.keystate_widget.set_text(p_keystate)
self._keystate_visible = len(p_keystate) > 0
def _set_alarm(self, p_callback):
""" Sets alarm to execute p_action specified in 0.5 sec. """
self._alarm = self.mainloop.set_alarm_in(0.5, p_callback)
def _remove_alarm(self):
""" Removes pending action alarm stored in _alarm attribute. """
self.mainloop.remove_alarm(self._alarm)
self._alarm = None
def _swap_column_left(self): def _swap_column_left(self):
pos = self.columns.focus_position pos = self.columns.focus_position
if pos > 0: if pos > 0:
...@@ -358,6 +392,23 @@ class UIApplication(CLIApplicationBase): ...@@ -358,6 +392,23 @@ class UIApplication(CLIApplicationBase):
elif p_enabled == False and self._console_visible: elif p_enabled == False and self._console_visible:
self.console.clear() self.console.clear()
del contents[2] del contents[2]
self.mainwindow.focus_position = 0
@property
def _keystate_visible(self):
contents = self.status_line.contents
return len(contents) == 2 and isinstance(contents[1][0].original_widget,
KeystateWidget)
@_keystate_visible.setter
def _keystate_visible(self, p_enabled):
contents = self.status_line.contents
if p_enabled and len(contents) == 1:
contents.append((urwid.Filler(self.keystate_widget),
('weight', 1, True)))
elif not p_enabled and self._keystate_visible:
del contents[1]
@property @property
def _viewwidget_visible(self): def _viewwidget_visible(self):
......
...@@ -17,15 +17,27 @@ ...@@ -17,15 +17,27 @@
import urwid import urwid
from topydo.ui.TodoWidget import TodoWidget from topydo.ui.TodoWidget import TodoWidget
from topydo.lib.Utils import translate_key_to_config
def get_execute_signal(p_prefix):
if p_prefix == 'cmdv':
signal = 'execute_command'
else:
signal = 'execute_command_silent'
return signal
class TodoListWidget(urwid.LineBox): class TodoListWidget(urwid.LineBox):
def __init__(self, p_view, p_title): def __init__(self, p_view, p_title, p_keymap):
self._view = None self._view = None
self.keymap = p_keymap
# store a state for multi-key shortcuts (e.g. 'gg') # store a state for multi-key shortcuts (e.g. 'gg')
self.keystate = None self.keystate = None
# store offset length for postpone command (e.g. '3' for 'p3w') # store offset length for postpone command (e.g. '3' for 'p3w')
self._pp_offset = '' self._pp_offset = None
self._title_widget = urwid.Text(p_title, align='center') self._title_widget = urwid.Text(p_title, align='center')
...@@ -43,7 +55,14 @@ class TodoListWidget(urwid.LineBox): ...@@ -43,7 +55,14 @@ class TodoListWidget(urwid.LineBox):
super().__init__(pile) super().__init__(pile)
urwid.register_signal(TodoListWidget, ['execute_command', 'refresh']) urwid.register_signal(TodoListWidget, ['execute_command_silent',
'execute_command',
'refresh',
'add_pending_action',
'remove_pending_action',
'column_action',
'show_keystate',
])
@property @property
def view(self): def view(self):
...@@ -100,133 +119,212 @@ class TodoListWidget(urwid.LineBox): ...@@ -100,133 +119,212 @@ class TodoListWidget(urwid.LineBox):
# deals with pending focus changes. # deals with pending focus changes.
self.listbox.calculate_visible(p_size) self.listbox.calculate_visible(p_size)
@property
def keystate(self):
return self._keystate
@keystate.setter
def keystate(self, p_keystate):
self._keystate = p_keystate
keystate_to_show = p_keystate if p_keystate else ''
urwid.emit_signal(self, 'show_keystate', keystate_to_show)
def keypress(self, p_size, p_key): def keypress(self, p_size, p_key):
# first check whether 'g' was pressed previously urwid.emit_signal(self, 'remove_pending_action')
if self.keystate == 'g': requires_further_input = ['postpone', 'postpone_s', 'pri']
if p_key == 'g':
self._scroll_to_top(p_size)
# make sure to accept normal shortcuts again keymap, keystates = self.keymap
shortcut = self.keystate or ''
shortcut += translate_key_to_config(p_key)
try:
action = keymap[shortcut]
except KeyError:
action = None
if action:
if shortcut in keystates:
# Supplied key-shortcut matches keystate and action. Save the
# keystate in case user will hit another key and add an action
# waiting for execution if user won't type anything further.
self.keystate = shortcut
if action not in requires_further_input:
self._add_pending_action(action, p_size)
else:
# Only action is matched. Handle it and reset keystate.
self.resolve_action(action, p_size)
self.keystate = None self.keystate = None
return return
elif self.keystate in ['p', 'ps']:
if p_key not in ['d', 'w', 'm', 'y']:
if p_key.isdigit():
self._pp_offset += p_key
elif self.keystate == 'p' and p_key == 's':
self.keystate = 'ps'
else: else:
self._pp_offset = '' if shortcut in keystates:
self.keystate = shortcut
else:
try:
# Check whether current keystate matches built-in 'postpone'
# action.
mode = keymap[self.keystate]
if mode in ['postpone', 'postpone_s']:
if self._postpone_selected(p_key, mode) is not None:
self.keystate = None self.keystate = None
else: else:
self._postpone_selected_item(p_key) urwid.emit_signal(self, 'show_keystate',
self._pp_offset = '' self.keystate + self._pp_offset)
else:
self.keystate = None self.keystate = None
return return
elif self.keystate == 'r': except KeyError:
if p_key.isalpha(): if not self.keystate:
self._pri_selected_item(p_key) # Single key that is not described in keymap config.
return self.listbox.keypress(p_size, p_key)
self.keystate = None self.keystate = None
return return
if p_key == 'x':
self._complete_selected_item()
elif p_key == 'p':
self.keystate = 'p'
elif p_key == 'd':
self._remove_selected_item()
elif p_key == 'e':
self._edit_selected_item()
# force screen redraw after editing
urwid.emit_signal(self, 'refresh')
elif p_key == 'r':
self.keystate = 'r'
elif p_key == 'u':
urwid.emit_signal(self, 'execute_command', "revert")
elif p_key == 'j':
self.listbox.keypress(p_size, 'down')
elif p_key == 'k':
self.listbox.keypress(p_size, 'up')
elif p_key == 'home':
self._scroll_to_top(p_size)
elif p_key == 'G' or p_key == 'end':
self._scroll_to_bottom(p_size)
elif p_key == 'g':
self.keystate = 'g'
else:
return self.listbox.keypress(p_size, p_key)
def mouse_event(self, p_size, p_event, p_button, p_column, p_row, p_focus): def mouse_event(self, p_size, p_event, p_button, p_column, p_row, p_focus):
if p_event == 'mouse press': if p_event == 'mouse press':
if p_button == 4: # up if p_button == 4: # up
self.listbox.keypress(p_size, 'up') self.listbox.keypress(p_size, 'up')
return return
elif p_button == 5: #down: elif p_button == 5: # down:
self.listbox.keypress(p_size, 'down') self.listbox.keypress(p_size, 'down')
return return
return super().mouse_event(p_size, p_event, p_button, p_column, p_row, p_focus) # pylint: disable=E1102 return super().mouse_event(p_size, # pylint: disable=E1102
p_event,
p_button,
p_column,
p_row,
p_focus)
def selectable(self): def selectable(self):
return True return True
def _command_on_selected(self, p_cmd_str): def _execute_on_selected(self, p_cmd_str, p_execute_signal):
""" """
Executes command specified by p_cmd_str on selected todo item. Executes command specified by p_cmd_str on selected todo item.
p_cmd_str should be string with one replacement field ('{}') which will p_cmd_str should be a string with one replacement field ('{}') which
be substituted by id of selected todo item. will be substituted by id of the selected todo item.
p_execute_signal is the signal name passed to the main loop. It should
be one of 'execute_command' or 'execute_command_silent'.
""" """
try: try:
todo = self.listbox.focus.todo todo = self.listbox.focus.todo
todo_id = str(self.view.todolist.number(todo)) todo_id = str(self.view.todolist.number(todo))
urwid.emit_signal(self, 'execute_command', p_cmd_str.format(todo_id)) urwid.emit_signal(self, p_execute_signal, p_cmd_str.format(todo_id))
# force screen redraw after editing
if p_cmd_str.startswith('edit'):
urwid.emit_signal(self, 'refresh')
except AttributeError: except AttributeError:
# No todo item selected # No todo item selected
pass pass
def _complete_selected_item(self): def resolve_action(self, p_action_str, p_size=None):
""" """
Marks the highlighted todo item as complete. Checks whether action specified in p_action_str is "built-in" or
""" contains topydo command (i.e. starts with 'cmd') and forwards it to
self._command_on_selected('do {}') proper executing methods.
def _postpone_selected_item(self, p_pattern): p_size should be specified for some of the builtin actions like 'up' or
""" 'home' as they can interact with urwid.ListBox.keypress or
Postpones highlighted todo item by p_pattern with optional offset from urwid.ListBox.calculate_visible.
_pp_offset attribute.
""" """
if self._pp_offset == '': if p_action_str.startswith(('cmd ', 'cmdv ')):
self._pp_offset = '1' prefix, cmd = p_action_str.split(' ', 1)
execute_signal = get_execute_signal(prefix)
pattern = self._pp_offset + p_pattern if '{}' in cmd:
self._execute_on_selected(cmd, execute_signal)
if self.keystate == 'ps':
cmd_str = 'postpone -s {t_id} {pattern}'.format(t_id='{}', pattern=pattern)
else: else:
cmd_str = 'postpone {t_id} {pattern}'.format(t_id='{}', pattern=pattern) urwid.emit_signal(self, execute_signal, cmd)
else:
self._command_on_selected(cmd_str) self.execute_builtin_action(p_action_str, p_size)
def _remove_selected_item(self): def execute_builtin_action(self, p_action_str, p_size=None):
""" """
Removes the highlighted todo item. Executes built-in action specified in p_action_str.
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'.
""" """
self._command_on_selected('del {}') column_actions = ['first_column',
'last_column',
'prev_column',
'next_column',
'append_column',
'insert_column',
'edit_column',
'delete_column',
'copy_column',
'swap_left',
'swap_right',
]
if p_action_str in column_actions:
urwid.emit_signal(self, 'column_action', p_action_str)
elif p_action_str in ['up', 'down']:
self.listbox.keypress(p_size, p_action_str)
elif p_action_str == 'home':
self._scroll_to_top(p_size)
elif p_action_str == 'end':
self._scroll_to_bottom(p_size)
elif p_action_str in ['postpone', 'postpone_s']:
pass
elif p_action_str == 'pri':
pass
def _edit_selected_item(self): def _add_pending_action(self, p_action, p_size):
""" """
Opens the highlighted todo item in $EDITOR for editing. Creates action waiting for execution and forwards it to the mainloop.
""" """
self._command_on_selected('edit {}') def generate_callback():
def callback(*args):
self.resolve_action(p_action, p_size)
self.keystate = None
def _pri_selected_item(self, p_priority): return callback
"""
Sets the priority of the highlighted todo item with value from urwid.emit_signal(self, 'add_pending_action', generate_callback())
p_priority.
def _postpone_selected(self, p_pattern, p_mode):
""" """
cmd_str = 'pri {t_id} {priority}'.format(t_id='{}', priority=p_priority) Postpones selected todo item by <COUNT><PERIOD>.
self._command_on_selected(cmd_str) Returns True after 'postpone' command is called (i.e. p_pattern is valid
<PERIOD>), False when p_pattern is invalid and None if p_pattern is
digit (i.e. part of <COUNT>).
p_pattern accepts digit (<COUNT>) or one of the <PERIOD> letters:
'd'(ay), 'w'(eek), 'm'(onth), 'y'(ear). If digit is specified, it is
appended to _pp_offset attribute. If p_pattern contains one of the
<PERIOD> letters, 'postpone' command is forwarded to execution with
value of _pp_offset attribute used as <COUNT>. If _pp_offset is None,
<COUNT> is set to 1.
p_mode should be one of 'postpone_s' or 'postpone'. It decides whether
'postpone' command should be called with or without '-s' flag.
"""
if p_pattern.isdigit():
if not self._pp_offset:
self._pp_offset = ''
self._pp_offset += p_pattern
result = None
else:
if p_pattern in ['d', 'w', 'm', 'y']:
offset = self._pp_offset or '1'
if p_mode == 'postpone':
pp_cmd = 'cmd postpone {} '
else:
pp_cmd = 'cmd postpone -s {} '
pp_cmd += offset + p_pattern
self.resolve_action(pp_cmd)
result = True
self._pp_offset = None
result = False
return result
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