Mercurial > getan > getan
view getan/states.py @ 151:1e35c24708dd
Allow to configure the action keys
Use new config and keybinding classes to allow to configure the action keys in
the states. This change decouples the actual action keys from the state
transtitions.
author | Björn Ricks <bjoern.ricks@intevation.de> |
---|---|
date | Thu, 06 Dec 2012 12:30:27 +0100 |
parents | 7ab5b887a7c5 |
children | 7ffcd2ea92e3 |
line wrap: on
line source
#!/usr/bin/env python # -*- coding: utf-8 -*- # # (c) 2010 by Ingo Weinzierl <ingo.weinzierl@intevation.de> # (c) 2011, 2012 by Björn Ricks <bjoern.ricks@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 locale import logging import signal from datetime import datetime, timedelta from getan.resources import gettext as _ from getan.utils import human_time, safe_int logger = logging.getLogger() class State(object): messages = { } def __init__(self, controller, view): self.controller = controller self.view = view self.config = controller.get_config() def msg(self, key): return self.messages[key] class ProjectState(State): def keypress(self, key): keys = self.config.get_keybinding() logger.debug("ProjectState: handle key '%r'" % key) if keys.get_switch_time_mode() in key: self.view.switch_time_mode() return self if keys.get_switch_project_order() in key: self.view.switch_project_order() return self if keys.get_switch_lists() in key: if not self.controller.entries_view.rows: return self self.controller.entries_view.focused = 0 self.controller.entries_view.update_focus(0) return DefaultEntryListState(self, self.controller, self.controller.entries_view) if keys.get_entry_up() in key: return self.up() if keys.get_entry_down() in key: return self.down() 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 class PausedProjectsState(ProjectState): messages = { 'choose_proj': _('Choose a project: '), } def keypress(self, key): logger.debug("PausedProjectsState: handle key '%r'" % key) keys = self.config.get_keybinding() ret = super(PausedProjectsState, self).keypress(key) if ret: return ret if keys.get_enter() in key: return self.select() if keys.get_insert() in key: return AddProjectNameState(self.controller, self.view) if keys.get_escape() in key: return ExitState(self.controller, self.view) else: if len(key) > 0 and len(key[0]) == 1: select_proj = SelectProjectState(self.controller, self.view, key[0]) return select_proj.check_key() return self def select(self): proj = self.view.item_in_focus() self.controller.start_project(proj) return RunningProjectsState(self.controller, self.view, proj) class SelectProjectState(State): def __init__(self, controller, view, key): super(SelectProjectState, self).__init__(controller, view) self.proj_keys = key self.set_footer_text() def reset(self): self.view.set_footer_text("", "entry_footer") def set_footer_text(self): self.view.set_footer_text("Selecting project from key: %s" % self.proj_keys, "running") def check_key(self): proj = self.controller.project_by_key(self.proj_keys) if proj: self.reset() self.view.select_project(proj) self.controller.start_project(self.view.item_in_focus()) self.controller.update_entries( self.view.item_in_focus()) return RunningProjectsState(self.controller, self.view, proj) return self def keypress(self, key): keys = self.config.get_keybinding() if keys.get_escape() in key: self.reset() return PausedProjectsState(self.controller, self.view) if 'backspace' in key: if len(self.proj_keys) > 0: self.proj_keys = self.proj_keys[:-1] self.set_footer_text() return self else: if len(key) > 0 and len(key[0]) == 1: self.proj_keys += key[0] self.set_footer_text() return self.check_key() return self class ExitState(ProjectState): messages = { 'quit' : _(" Really quit? (y/n)"), 'choose': _(" Choose a project:") } 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': _("Enter a description: "), 'add_time' : _("Enter time to add [min]: "), 'min_time' : _("Enter time to subtract [min]: "), 'continue' : _("Press 'Space' to continue."), 'running' : _("Running ( %s ) on '%s'."), 'paused' : _(" Break ( %s )%s."), } sec = 0 break_start = None def __init__(self, controller, view, project): super(RunningProjectsState, self).__init__(controller, view) self.project = project signal.signal(signal.SIGALRM, self.handle_signal) signal.alarm(1) def handle_signal(self, signum, frame): proj = self.project keys = self.config.get_keybinding() if not proj: return if not self.break_start: self.controller.view.set_footer_text(self.msg('running') % (human_time(self.sec), proj.desc), 'running') self.controller.view.loop.draw_screen() self.sec = self.sec + 1 else: self.view.set_footer_text( self.msg('paused') % (human_time((datetime.now()-self.break_start).seconds), self.msg('continue')), '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) keys = self.config.get_keybinding() ret = super(RunningProjectsState, self).keypress(key) if ret: return ret if keys.get_enter() in key: return self.stop() if keys.get_add_time() 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 keys.get_subtract_time() 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 keys.get_pause_project() in key: if not self.break_start: self.break_start = datetime.now() return self else: self.view._total_time() proj = self.project if proj: 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.project if proj: 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): super(HandleUserInputState, self).__init__(controller, view) self.state = state self.footer = footer def keypress(self, key): logger.debug("HandleUserInputState: handle key '%r'" % key) pos = self.footer.edit_pos keys = self.config.get_keybinding() if keys.get_escape() in key: return self.exit() elif keys.get_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 enter(self): raise Exception("Not implemented") def exit(self): return self.state def insert(self, key): logger.debug("Enter key: %r" % key) text = "".join(key) # check for unicode here # urwid (at least up to version 1.0.2) will crash if a non-unicode # string with a char > 128 is passed here if not isinstance(text, unicode): text = unicode(text, locale.getpreferredencoding()) self.footer.insert_text(text) return self class BaseTimeState(HandleUserInputState): def __init__(self, controller, view, running_state): super(BaseTimeState, self).__init__(controller, view, running_state, view.frame.get_footer()) self.project = running_state.project def exit(self): self.view._total_time() return self.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 = safe_int(self.view.frame.get_footer().get_edit_text()) project = self.project 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 = safe_int(self.view.frame.get_footer().get_edit_text()) sec = minutes * 60 if sec > self.state.sec: self.view._total_time() return self.state project = self.project project.start += timedelta(minutes=minutes) self.state.sec -= sec 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': _(" Choose a project."), } def enter(self): text = self.footer.get_edit_text() if text == '': return self self.controller.stop_project(text) 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() if project: 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): super(EntryListState, self).__init__(controller, view) self.projectlist_state = state def keypress(self, key): logger.debug("EntryListState: pressed key '%r'" % key) keys = self.config.get_keybinding() if keys.get_switch_lists() in key: self.view.clear() return self.projectlist_state if keys.get_entry_up() in key: return self.up() if keys.get_entry_down() in key: return self.down() if keys.get_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 keys = self.config.get_keybinding() if keys.get_escape() in key: self.view.clear() return self.projectlist_state if keys.get_entry_delete() in key: if self.view.selection: return DeleteEntryState(self.projectlist_state, self.controller, self.view) else: entry = self.view.item_in_focus() return DeleteEntryState(self.projectlist_state, self.controller, self.view, [entry]) if keys.get_entry_move() in key: if self.view.selection: return MoveEntryState(self.projectlist_state, self.controller, self.view) if keys.get_entry_edit() in key: entry = self.view.item_in_focus() if entry: return EditEntryState(self.projectlist_state, self.controller, self.view, entry) return self class DeleteEntryState(EntryListState): messages = { 'delete' : _("Really delete this entry? (y/n)"), } def __init__(self, state, controller, view, entries=None): super(DeleteEntryState, self).__init__(state, controller, view) self.view.set_footer_text(self.msg('delete'), 'question') self.entries = entries if not self.entries: self.entries = [x.item for x in self.view.selection] def keypress(self, key): if 'y' in key: if self.entries: self.controller.delete_entries(self.entries) self.renew_focus() self.view.set_footer_text("", 'entry_footer') 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': _(" Into which project do you want to move these entries?"), 'really': _(" Are you sure? (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') self.proj_keys = "" self.project_view = controller.project_view def set_project_footer(self): self.project_view.set_footer_text("Selecting project from " \ "key: %s" % self.proj_keys, "running") def reset_project_footer(self): self.project_view.reset_footer() def keypress(self, key): keys = self.config.get_keybinding() if 'y' in key and self.proj: logger.debug("MoveEntryState: move selected entries.") self.controller.move_selected_entries(self.proj) self.renew_focus() self.view.set_footer_text('', 'entry_footer') self.proj = None return DefaultEntryListState(self.projectlist_state, self.controller, self.view) if 'n' in key and self.proj: self.view.set_footer_text('', 'entry_footer') self.reset_project_footer() return DefaultEntryListState(self.projectlist_state, self.controller, self.view) if keys.get_escape() in key: self.view.set_footer_text('', 'entry_footer') self.reset_project_footer() return DefaultEntryListState(self.projectlist_state, self.controller, self.view) if 'backspace' in key: if len(self.proj_keys) > 0: self.proj_keys = self.proj_keys[:-1] self.set_project_footer() return self if len(key) > 0 and len(key[0]) == 1 and self.proj is None: self.proj_keys += key[0] self.set_project_footer() self.proj = self.controller.project_by_key(self.proj_keys) if self.proj: self.reset_project_footer() 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 class CreateProjectState(HandleUserInputState): messages = { 'choose_proj': _(' Choose a project.'), } def __init__(self, controller, view): super(CreateProjectState, self).__init__(controller, view, None, controller.view.view.get_footer()) def exit(self): self.controller.view.set_footer_text(self.msg('choose_proj'), 'question') return PausedProjectsState(self.controller, self.view) class AddProjectNameState(CreateProjectState): messages = { 'choose_proj': _(' Choose a project.'), 'proj_name': _('Insert new project name: '), } def __init__(self, controller, view): controller.view.set_footer_text(self.msg('proj_name'), 'question', 1) super(AddProjectNameState, self).__init__(controller, view) self.view.frame.set_focus('footer') def enter(self): project = self.footer.get_edit_text() if project == '': return self return AddProjectKeyState(self.controller, self.view, project) class AddProjectKeyState(CreateProjectState): messages = { 'proj_name': _('Insert new project name: '), 'proj_key': _('Insert key for new project: '), } def __init__(self, controller, view, project): controller.view.set_footer_text(self.msg('proj_key'), 'question', 1) super(AddProjectKeyState, self).__init__(controller, view) self.view.frame.set_focus('footer') self.project = project def enter(self): key = self.footer.get_edit_text() if key == '': return self return AddProjectDescriptionState(self.controller, self.view, self.project, key) class AddProjectDescriptionState(CreateProjectState): messages = { 'proj_description': _('Insert a description for project: '), 'choose_proj': _(" Choose a project.") } def __init__(self, controller, view, project, key): controller.view.set_footer_text(self.msg('proj_description'), 'question', 1) super(AddProjectDescriptionState, self).__init__(controller, view) self.view.frame.set_focus('footer') self.project = project self.key = key def enter(self): description = self.footer.get_edit_text() if description == '': return self self.controller.add_project(self.key, description) self.controller.view.set_footer_text(self.msg('choose_proj'), 'question') return PausedProjectsState(self.controller, self.view) class EditEntryState(HandleUserInputState): messages = { 'edit_entry': _('Edit entry text: '), } def __init__(self, state, controller, view, entry): view.set_footer_text(self.msg('edit_entry'), 'question', True) super(EditEntryState, self).__init__(controller, view, None, view.footer) self.footer.set_edit_text(entry.desc) self.footer.set_edit_pos(len(self.footer.edit_text)) self.view.frame.set_focus('footer') self.entry = entry self.state = state logger.debug("EditEntryState: Entry %s" % entry) def enter(self): entry_desc = self.footer.get_edit_text() if entry_desc == '': return self entry = self.entry entry.desc = entry_desc self.controller.update_entry(entry) self.view.node_in_focus().update() return self.exit() def exit(self): self.view.set_footer_text("", 'entry_footer', False) return DefaultEntryListState(self.state, self.controller, self.view)