Mercurial > getan > getan
diff getan/states.py @ 23:9c4e8ba3c4fa
Added a new implementation of 'getan' based on urwid, a python console user interface library.
author | Ingo Weinzierl <ingo_weinzierl@web.de> |
---|---|
date | Sat, 28 Aug 2010 20:16:58 +0200 |
parents | |
children | 155b23da504b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/getan/states.py Sat Aug 28 20:16:58 2010 +0200 @@ -0,0 +1,427 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# (c) 2010 by Ingo Weinzierl <ingo.weinzierl@intevation.de> +# +# This is Free Software licensed under the terms of GPLv3 or later. +# For details see LICENSE coming with the source of 'getan'. +# + + +import logging +import signal +from datetime import datetime, timedelta + +from getan.utils import human_time + +logger = logging.getLogger() + +class State(object): + messages = { + } + + def __init__(self, controller, view): + self.controller = controller + self.view = view + + def msg(self, key): + return self.messages[key] + + +class ProjectState(State): + def keypress(self, key): + logger.debug("ProjectState: handle key '%r'" % key) + if 'f1' in key: + self.view.switch_time_mode() + return None + + if 'tab' in key: + self.controller.entries_view.focused = 0 + self.controller.entries_view.update_focus(0) + return DefaultEntryListState(self, self.controller, + self.controller.entries_view) + + +class PausedProjectsState(ProjectState): + messages = { + 'choose_proj': u" Wählen Sie ein Projekt:" + } + + def keypress(self, key): + logger.debug("PausedProjectsState: handle key '%r'" % key) + ret = super(PausedProjectsState, self).keypress(key) + if ret: + return ret + + if 'up' in key: + return self.up() + + if 'down' in key: + return self.down() + + if 'enter' in key: + return self.select() + + if 'esc' in key: + return ExitState(self.controller, self.view) + + else: + if len(key) > 0: + proj = self.controller.project_by_key(key[0]) + if proj: + self.view.select_project(proj) + self.controller.start_project(self.view.item_in_focus()) + self.controller.update_entries( + self.view.item.in_focus().project) + return RunningProjectsState(self.controller, self.view) + return self + + def up(self): + self.view.up() + self.controller.update_entries(self.view.item_in_focus()) + return self + + def down(self): + self.view.down() + self.controller.update_entries(self.view.item_in_focus()) + return self + + def select(self): + proj_node = self.view.select() + self.controller.start_project(self.view.item_in_focus()) + return RunningProjectsState(self.controller, self.view) + + +class ExitState(ProjectState): + messages = { + 'quit' : u" Wirklich beenden? (y/n)", + 'choose': u" Wählen Sie ein Projekt:" + } + + def __init__(self, controller, view): + super(ExitState, self).__init__(controller, view) + self.controller.view.set_footer_text(self.msg('quit'), 'question') + + def keypress(self, key): + logger.debug("ExitState: handle key '%r'" % key) + ret = super(ExitState, self).keypress(key) + if ret: + return ret + + if 'y' in key or 'Y' in key: + self.controller.exit() + + if 'n' in key or 'N' in key: + self.controller.view.set_footer_text(self.msg('choose'), 'question') + return PausedProjectsState(self.controller, self.view) + + return self + + +class RunningProjectsState(ProjectState): + messages = { + 'description': u" Geben Sie eine Beschreibung ein: ", + 'add_time' : u" Geben Sie die zu addierende Zeit ein [min]: ", + 'min_time' : u" Geben Sie die zu abzuziehende Zeit ein [min]: ", + 'paused' : u" 'Space' zum Fortzusetzen", + } + + sec = 0 + break_start = None + + def __init__(self, controller, view): + super(RunningProjectsState, self).__init__(controller, view) + signal.signal(signal.SIGALRM, self.handle_signal) + signal.alarm(1) + + def handle_signal(self, signum, frame): + proj = self.view.item_in_focus() + if not self.break_start: + self.controller.view.set_footer_text(" Running ( %s ) on '%s'" % + (human_time(self.sec), + proj.desc), + 'running') + self.controller.view.loop.draw_screen() + self.sec = self.sec + 1 + else: + self.view.set_footer_text( + ' Break ( %s )%s' % + (human_time((datetime.now()-self.break_start).seconds), + self.msg('paused')), + 'paused_running') + self.controller.view.loop.draw_screen() + + signal.signal(signal.SIGALRM, self.handle_signal) + signal.alarm(1) + + def keypress(self, key): + logger.debug("RunningProjectsState: handle key '%r'" % key) + ret = super(RunningProjectsState, self).keypress(key) + if ret: + return ret + + if 'enter' in key: + return self.stop() + if '+' in key: + self.view.set_footer_text(self.msg('add_time'), + 'question', 1) + self.view.frame.set_focus('footer') + return AddTimeState(self.controller, self.view, self) + if '-' in key: + self.view.set_footer_text(self.msg('min_time'), + 'question', 1) + self.view.frame.set_focus('footer') + return SubtractTimeState(self.controller, self.view, self) + if ' ' in key and not self.break_start: + self.break_start = datetime.now() + return self + if ' ' in key and self.break_start: + self.view._total_time() + proj = self.view.item_in_focus() + proj.start += datetime.now() - self.break_start + self.break_start = None + signal.signal(signal.SIGALRM, self.handle_signal) + signal.alarm(1) + return self + + def stop(self): + signal.alarm(0) + self.view.select() + if self.break_start: + proj = self.view.item_in_focus() + proj.start += datetime.now() - self.break_start + self.controller.view.set_footer_text(self.msg('description'),'question',1) + self.controller.view.get_frame().set_focus('footer') + return DescriptionProjectsState( + self.controller, self.view, self, + self.controller.view.get_frame().get_footer()) + + +class HandleUserInputState(State): + def __init__(self, controller, view, state, footer): + self.controller = controller + self.view = view + self.state = state + self.footer = footer + + def keypress(self, key): + logger.debug("HandleUserInputState: handle key '%r'" % key) + pos = self.footer.edit_pos + + if 'esc' in key: + return self.exit() + elif 'enter' in key: + return self.enter() + elif 'left' in key: + self.footer.set_edit_pos(pos-1) + elif 'right' in key: + self.footer.set_edit_pos(pos+1) + elif 'backspace' in key: + text = self.footer.edit_text + self.footer.set_edit_text( + '%s%s' % (text[0:pos-1], text[pos:len(text)])) + self.footer.set_edit_pos(pos-1) + elif 'delete' in key: + text = self.footer.edit_text + self.footer.set_edit_text( + '%s%s' % (text[0:pos], text[pos+1:len(text)])) + self.footer.set_edit_pos(pos) + elif len(key) >= 1 and len(key[0]) == 1: + return self.insert(key) + return self + + def exit(self): + return self.state + + def insert(self, key): + logger.debug("Enter key: %r" % key) + self.footer.insert_text(key[0]) + return self + + +class BaseTimeState(HandleUserInputState): + def __init__(self, controller, view, running_state): + super(BaseTimeState, self).__init__(controller, view, running_state, + view.frame.get_footer()) + + def exit(self): + self.view._total_time() + return self.running_state + + def insert(self, key): + if key[0] in ['0','1','2','3','4','5','6','7','8','9']: + self.footer.insert_text(key[0]) + else: + logger.debug("BaseTimeState: invalid character for "\ + "adding/subtracting time: '%r'" % key) + return self + + +class AddTimeState(BaseTimeState): + def enter(self): + minutes = int(self.view.frame.get_footer().get_edit_text()) + project = self.view.item_in_focus() + project.start -= timedelta(minutes=minutes) + self.state.sec += minutes * 60 + logger.info("AddTimeState: add %d minutes to project '%s'" + % (minutes, project.desc)) + self.view._total_time() + return self.state + + +class SubtractTimeState(BaseTimeState): + def enter(self): + minutes = int(self.view.frame.get_footer().get_edit_text()) + project = self.view.item_in_focus() + project.start += timedelta(minutes=minutes) + self.state.sec -= minutes * 60 + logger.info("SubtractTimeState: subtract %d minutes from project '%s'" + % (minutes, project.desc)) + self.view._total_time() + return self.state + + +class DescriptionProjectsState(HandleUserInputState): + messages = { + 'choose_proj': u" Wählen Sie ein Projekt." + } + + def enter(self): + text = self.footer.get_edit_text() + if text == '': + return self + self.controller.stop_project() + self.controller.view.set_footer_text(self.msg('choose_proj'), 'question') + return PausedProjectsState(self.controller, self.view) + + def exit(self): + project = self.view.item_in_focus() + time = (datetime.now() - project.start).seconds + self.state.sec = time + signal.signal(signal.SIGALRM, self.state.handle_signal) + signal.alarm(1) + return self.state + + +class EntryListState(State): + def __init__(self, state, controller, view): + self.projectlist_state = state + self.controller = controller + self.view = view + + def keypress(self, key): + logger.debug("EntryListState: pressed key '%r'" % key) + if 'tab' in key: + self.view.clear() + return self.projectlist_state + if 'up' in key: + return self.up() + if 'down' in key: + return self.down() + if 'enter' in key: + return self.select() + return None + + def up(self): + self.view.up() + return self + + def down(self): + self.view.down() + return self + + def select(self): + self.view.select() + return self + + def renew_focus(self): + e_len = self.view.row_count() + f = self.view.focused + if f >= e_len: f = e_len - 1 + self.view.focused = f + self.view.update_focus(f) + + +class DefaultEntryListState(EntryListState): + def keypress(self, key): + ret = super(DefaultEntryListState, self).keypress(key) + if ret: + return ret + + if 'd' in key: + return DeleteEntryState(self.projectlist_state, + self.controller, self.view) + if 'm' in key: + return MoveEntryState(self.projectlist_state, + self.controller, self.view) + return self + + +class DeleteEntryState(EntryListState): + messages = { + 'delete' : u" Wirklich löschen? (y/n)", + } + + def __init__(self, state, controller, view): + super(DeleteEntryState, self).__init__(state, controller, view) + self.view.set_footer_text(self.msg('delete'), 'question') + + def keypress(self, key): + ret = super(DeleteEntryState, self).keypress(key) + if ret: + return ret + + if 'y' in key: + self.controller.delete_entries(self.view.selection) + self.renew_focus() + return DefaultEntryListState(self.projectlist_state, + self.controller, self.view) + + if 'n' in key: + self.view.set_footer_text("", 'entry_footer') + return DefaultEntryListState(self.projectlist_state, + self.controller, self.view) + + return self + + +class MoveEntryState(EntryListState): + messages = { + 'project': u" In welches Projekt möchten Sie die Einträge verschieben?", + 'really': u" Sind sie sich sicher? (y/n)", + } + + proj = None + + def __init__(self, state, controller, view): + super(MoveEntryState, self).__init__(state, controller, view) + self.view.set_footer_text(self.msg('project'), 'question') + + def keypress(self, key): + if 'y' in key and self.proj: + logger.debug("MoveEntryState: move selected entries.") + self.controller.move_selected_entries(self.proj) + self.view.set_footer_text('', 'entry_footer') + self.proj = None + self.renew_focus() + return DefaultEntryListState(self.projectlist_state, + self.controller, self.view) + + if 'n' in key: + self.view.set_footer_text('', 'entry_footer') + return DefaultEntryListState(self.projectlist_state, + self.controller, self.view) + + if 'esc' in key: + self.view.set_footer_text('', 'entry_footer') + return DefaultEntryListState(self.projectlist_state, + self.controller, self.view) + + if len(key) > 0 and self.proj is None: + self.proj = self.controller.project_by_key(key[0]) + if self.proj: + logger.debug("MoveEntryState: prepared entries to be moved to "\ + "project '%s'" % self.proj.desc) + self.view.set_footer_text(self.msg('really'), 'question') + + return self