Commit 667082b7 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Start of a new user interface.

Started on the UI that I envisioned since the very beginning of this
project, 364 days ago.

The UI is inspired by TweetDeck, the column-like Twitter interface.
Every column is associated with a View object (which has a filter and a
sort order on the active todo.txt file). So each column can show a
particular part of the todo.txt files. E.g, a column for today's items,
a column for overdue items or a column for a particular project
(filter: GrepFilter("+SomeProject")).

The way topydo is designed was always with this UI in mind. Commands
('add', 'do', etc.) should be independent of the UI that invoked them,
therefore they never print to stdout but output to a function such that
the current UI will show it properly. Also the View class, which hasn't
been incredibly useful sofar, can finally fulfil its role better.

This is still a very immature version of the UI. It shows a single
column with all the todos. It can execute commands, but it will do so
silently, no output or errors are shown yet.

The navigation will be Vim-like. Press : to toggle the command-line at
the bottom of the screen, where you can enter any command that you're
used to. Press Escape entering a command to set the focus back on the
columns.

The next few items to address:
* show output, errors and handle input
* ability to dynamically add/edit/delete columns
* add navigation keys to 'walk' through the items in the columns and act
  on them with shortcuts. E.g. highlight a todo, press 'x' to complete
  it.

I chose urwid as the widget library for the console, since using curses
directly gave me a headache.
parent 9d33afef
...@@ -14,6 +14,7 @@ setup( ...@@ -14,6 +14,7 @@ setup(
extras_require = { extras_require = {
'ical': ['icalendar'], 'ical': ['icalendar'],
'prompt-toolkit': ['prompt-toolkit >= 0.39'], 'prompt-toolkit': ['prompt-toolkit >= 0.39'],
'urwid': ['urwid >= 1.3.0'],
'edit-cmd-tests': ['mock'], 'edit-cmd-tests': ['mock'],
}, },
entry_points= { entry_points= {
......
# 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 CommandLineWidget(urwid.Edit):
signals = [
'blur',
'execute_command',
]
def clear(self):
self.set_edit_text(u"")
def _blur(self):
self.clear()
self._emit('blur')
def _emit_command(self):
self._emit('execute_command', self.edit_text)
self.clear()
def keypress(self, p_size, p_key):
dispatch = {
'enter': self._emit_command,
'esc': self._blur,
}
try:
dispatch[p_key]()
except KeyError:
super(CommandLineWidget, self).keypress(p_size, p_key)
# 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
from topydo.cli.CLIApplicationBase import CLIApplicationBase
from topydo.Commands import get_subcommand
from topydo.ui.CommandLineWidget import CommandLineWidget
from topydo.ui.TodoListWidget import TodoListWidget
from topydo.lib.Config import config
from topydo.lib.Sorter import Sorter
from topydo.lib import TodoFile
from topydo.lib import TodoList
COLUMN_WIDTH = 40
class UIApplication(CLIApplicationBase):
def __init__(self):
super(UIApplication, self).__init__()
self.columns = urwid.Columns([], dividechars=0, min_width=COLUMN_WIDTH)
self.commandline = CommandLineWidget('topydo> ')
urwid.connect_signal(self.commandline, 'blur',
lambda _: self._blur_commandline())
urwid.connect_signal(self.commandline, 'execute_command',
self._execute_input)
self.mainwindow = urwid.Pile([
('weight', 1, self.columns),
(1, urwid.Filler(self.commandline)),
])
# the columns should have keyboard focus
self._blur_commandline()
self.mainloop = urwid.MainLoop(
self.mainwindow,
unhandled_input=self._handle_input,
pop_ups=True
)
def _execute_input(self, _, p_command):
"""
Callback for executing a command that was entered on the command line box.
"""
(subcommand, args) = get_subcommand(p_command.split())
try:
command = subcommand(
args,
self.todolist,
lambda _: None, # TODO output
lambda _: None, # TODO error
lambda _: None, # TODO input
)
if command.execute() != False:
self._post_execute()
except TypeError:
# TODO: show error message
pass
def _focus_commandline(self):
self.mainwindow.focus_item = 1
def _blur_commandline(self):
self.mainwindow.focus_item = 0
def _handle_input(self, p_input):
dispatch = {
':': self._focus_commandline,
}
try:
dispatch[p_input]()
except KeyError:
# the key is unknown, ignore
pass
def _add_column(self, p_view, p_title):
todolist = TodoListWidget(p_view, p_title)
options = self.columns.options(
width_type='given',
width_amount=COLUMN_WIDTH,
box_widget=True
)
item = (todolist, options)
self.columns.contents.append(item)
def run(self):
self.todofile = TodoFile.TodoFile(config().todotxt())
self.todolist = TodoList.TodoList(self.todofile.read())
view1 = self.todolist.view(Sorter(), [])
self._add_column(view1, "View 1")
self.mainloop.run()
if __name__ == '__main__':
UIApplication().run()
# 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 TodoListWidget(urwid.WidgetWrap):
def __init__(self, p_view, p_title):
self.view = p_view
title_widget = urwid.Filler(urwid.Text(p_title, align='center'))
todos = []
for todo in self.view.todos:
todos.append(('pack', urwid.Text(todo.source())))
todos.append(urwid.Divider(u'-'))
todo_pile = urwid.Pile(todos)
pile = urwid.Pile([
(1, title_widget),
(1, urwid.Filler(urwid.Divider(u'\u2500'))),
('weight', 1, urwid.Filler(todo_pile, valign='top')),
])
widget = urwid.LineBox(pile)
super(TodoListWidget, self).__init__(widget)
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