mschieder@482: #!/usr/bin/env python3 ingo_weinzierl@23: # -*- coding: utf-8 -*- ingo_weinzierl@23: # ingo_weinzierl@23: # (c) 2010 by Ingo Weinzierl bjoern@385: # (c) 2011, 2012, 2014 by Björn Ricks mschieder@488: # (c) 2017, 2018 Intevation GmbH mschieder@488: # Authors: mschieder@488: # * Bernhard Reiter mschieder@488: # * Magnus Schieder ingo_weinzierl@23: # ingo_weinzierl@23: # This is Free Software licensed under the terms of GPLv3 or later. ingo_weinzierl@23: # For details see LICENSE coming with the source of 'getan'. ingo_weinzierl@23: ingo_weinzierl@23: import logging ingo_weinzierl@23: import signal bjoern@195: bjoern@195: from datetime import datetime, timedelta ingo_weinzierl@23: ingo_weinzierl@36: from getan.resources import gettext as _ bjoern@195: from getan.utils import human_time, safe_int ingo_weinzierl@23: ingo_weinzierl@23: logger = logging.getLogger() ingo_weinzierl@23: bjoern@296: ingo_weinzierl@23: class State(object): bjoern@197: """ Represents a State of Getan bjoern@197: bernhard@410: A State can be used to handle user input. The user input handling is done sascha@563: in two phases. sascha@563: First it is possible to redirect a key to a specific widget in keypress. sascha@563: In the second phase it is possible bernhard@410: to act on user input which isn't handled by a widget yet. The corresponing bernhard@410: method is handle_input. bjoern@197: bjoern@197: Normally handle_input should be used to act on user input and change a bjoern@197: state. bjoern@197: """ bjoern@196: ingo_weinzierl@23: messages = { ingo_weinzierl@23: } ingo_weinzierl@23: ingo_weinzierl@23: def __init__(self, controller, view): ingo_weinzierl@23: self.controller = controller bjoern@196: self.view = view bjoern@151: self.config = controller.get_config() bjoern@276: self.set_focus() ingo_weinzierl@23: bjoern@197: def keypress(self, size, key): bjoern@197: """Redirects user input to the current view""" bjoern@197: self.view.keypress(size, key) bjoern@197: bjoern@197: def handle_input(self, input): bjoern@197: """A derived State must implement handle_input""" bjoern@197: raise NotImplementedError() bjoern@197: bjoern@197: def set_next_state(self, state): bjoern@197: """Sets the next state""" bjoern@197: self.controller.set_state(state) bjoern@197: ingo_weinzierl@23: def msg(self, key): ingo_weinzierl@23: return self.messages[key] ingo_weinzierl@23: bjoern@276: def set_focus(self): bjoern@276: """ Override this method to set the focus when the state is created """ bjoern@276: pass bjoern@276: ingo_weinzierl@23: ingo_weinzierl@23: class ProjectState(State): bjoern@205: bjoern@205: def handle_input(self, input): bjoern@151: keys = self.config.get_keybinding() bjoern@205: logger.debug("ProjectState: handle input '%r'" % input) bjoern@205: if keys.get_switch_time_mode() in input: ingo_weinzierl@23: self.view.switch_time_mode() bjoern@205: return True ingo_weinzierl@23: bjoern@205: if keys.get_switch_project_order() in input: bjoern@142: self.view.switch_project_order() bjoern@205: return True bjoern@142: mschieder@479: if 'ctrl l' in input: mschieder@479: self.controller.redraw() mschieder@479: return True mschieder@479: bjoern@205: if keys.get_switch_lists() in input: bjoern@72: if not self.controller.entries_view.rows: bjoern@205: return True mschieder@469: self.view.highlight_open_project() bjoern@205: new_state = DefaultEntryListState(self, self.controller, bjoern@296: self.controller.entries_view) bjoern@205: self.set_next_state(new_state) bjoern@205: return True ingo_weinzierl@23: bjoern@277: def set_focus(self): bjoern@277: self.controller.view.set_focus("projects") bjoern@303: self.view.frame.set_focus("body") bjoern@277: bjoern@170: ingo_weinzierl@23: class PausedProjectsState(ProjectState): bjoern@205: ingo_weinzierl@23: messages = { ingo_weinzierl@36: 'choose_proj': _('Choose a project: '), ingo_weinzierl@23: } ingo_weinzierl@23: bjoern@205: def handle_input(self, key): ingo_weinzierl@23: logger.debug("PausedProjectsState: handle key '%r'" % key) bjoern@151: keys = self.config.get_keybinding() bjoern@205: ret = super(PausedProjectsState, self).handle_input(key) ingo_weinzierl@23: if ret: bjoern@205: return True ingo_weinzierl@23: bjoern@151: if keys.get_enter() in key: ingo_weinzierl@23: return self.select() ingo_weinzierl@23: bjoern@151: if keys.get_insert() in key: bjoern@205: state = AddProjectKeyState(self.controller, self.view) bjoern@205: self.set_next_state(state) bjoern@205: return True bjoern@54: bjoern@151: if keys.get_escape() in key: bjoern@205: state = ExitState(self.controller, self.view) bjoern@205: self.set_next_state(state) bjoern@205: return True ingo_weinzierl@23: bjoern@179: if keys.get_project_edit() in key: bjoern@179: proj = self.view.item_in_focus() bjoern@179: if not proj: bjoern@205: return True bjoern@205: state = ProjectEditKeyState(self.controller, self.view, proj) bjoern@205: self.set_next_state(state) bjoern@205: return True bjoern@179: ingo_weinzierl@23: else: bjoern@98: if len(key) > 0 and len(key[0]) == 1: bjoern@205: state = SelectProjectState(self.controller, self.view) bjoern@205: self.set_next_state(state) bjoern@205: return state.handle_input(key) bjoern@205: return False ingo_weinzierl@23: ingo_weinzierl@23: def select(self): bjoern@89: proj = self.view.item_in_focus() bjoern@89: self.controller.start_project(proj) bjoern@205: state = RunningProjectsState(self.controller, self.view, proj) bjoern@205: self.set_next_state(state) bjoern@205: return True ingo_weinzierl@23: bjoern@170: bjoern@94: class SelectProjectState(State): bjoern@94: bjoern@167: def __init__(self, controller, view): bjoern@94: super(SelectProjectState, self).__init__(controller, view) bjoern@167: self.proj_keys = "" bjoern@94: self.set_footer_text() bjoern@94: bjoern@94: def reset(self): bjoern@231: self.view.reset_footer() bjoern@94: bjoern@94: def set_footer_text(self): bjoern@296: self.view.set_footer_text("Selecting project from key: %s" % bjoern@296: self.proj_keys, "running") bjoern@94: bjoern@155: def check_key(self, key): bjoern@167: return len(self.controller.find_projects_by_key(key)) bjoern@155: bjoern@155: def select_project(self): bjoern@95: proj = self.controller.project_by_key(self.proj_keys) bjoern@95: if proj: bjoern@95: self.reset() bjoern@95: self.view.select_project(proj) bjoern@307: self.controller.start_project(proj) bjoern@307: self.controller.update_entries(proj) bjoern@296: self.set_next_state( bjoern@296: RunningProjectsState(self.controller, self.view, bjoern@296: proj)) bjoern@205: return True bjoern@95: bjoern@205: def handle_input(self, key): bjoern@151: keys = self.config.get_keybinding() bjoern@151: if keys.get_escape() in key: bjoern@94: self.reset() bjoern@296: self.set_next_state( bjoern@296: PausedProjectsState(self.controller, self.view)) bjoern@205: return True bjoern@139: mschieder@479: if 'ctrl l' in key: mschieder@479: self.controller.redraw() mschieder@479: return True mschieder@479: bjoern@139: if 'backspace' in key: bjoern@139: if len(self.proj_keys) > 0: bjoern@139: self.proj_keys = self.proj_keys[:-1] bjoern@139: self.set_footer_text() bjoern@205: return True bjoern@167: bjoern@155: if keys.get_enter() in key: bjoern@155: return self.select_project() bjoern@167: bjoern@94: else: bjoern@98: if len(key) > 0 and len(key[0]) == 1: bjoern@155: proj_key = self.proj_keys + key[0] bjoern@167: num = self.check_key(proj_key) bjoern@167: if num > 0: bjoern@155: self.proj_keys += key[0] bjoern@155: self.set_footer_text() bjoern@167: if num == 1: bjoern@167: # run project directly bjoern@167: return self.select_project() bjoern@205: return False bjoern@94: bjoern@170: ingo_weinzierl@23: class ExitState(ProjectState): bjoern@205: ingo_weinzierl@23: messages = { bjoern@296: 'quit': _(" Really quit? (y/n)"), ingo_weinzierl@36: 'choose': _(" Choose a project:") ingo_weinzierl@23: } ingo_weinzierl@23: ingo_weinzierl@23: def __init__(self, controller, view): ingo_weinzierl@23: super(ExitState, self).__init__(controller, view) ingo_weinzierl@23: self.controller.view.set_footer_text(self.msg('quit'), 'question') ingo_weinzierl@23: bjoern@205: def handle_input(self, key): ingo_weinzierl@23: logger.debug("ExitState: handle key '%r'" % key) bjoern@205: ret = super(ExitState, self).handle_input(key) ingo_weinzierl@23: if ret: ingo_weinzierl@23: return ret ingo_weinzierl@23: ingo_weinzierl@23: if 'y' in key or 'Y' in key: ingo_weinzierl@23: self.controller.exit() bjoern@205: return True ingo_weinzierl@23: ingo_weinzierl@23: if 'n' in key or 'N' in key: bjoern@296: self.controller.view.set_footer_text( bjoern@296: self.msg('choose'), 'question') bjoern@296: self.set_next_state( bjoern@296: PausedProjectsState(self.controller, self.view)) bjoern@205: return True ingo_weinzierl@23: bjoern@205: return False ingo_weinzierl@23: ingo_weinzierl@23: ingo_weinzierl@23: class RunningProjectsState(ProjectState): bjoern@205: ingo_weinzierl@23: messages = { ingo_weinzierl@36: 'description': _("Enter a description: "), bjoern@296: 'add_time': _("Enter time to add [min]: "), bjoern@296: 'min_time': _("Enter time to subtract [min]: "), bjoern@296: 'continue': _("Press '%s' to continue."), bjoern@296: 'running': _("Running ( %s ) on '%s'."), bjoern@296: 'paused': _(" Break ( %s ) %s."), ingo_weinzierl@23: } ingo_weinzierl@23: bjoern@296: sec = 0 ingo_weinzierl@23: break_start = None ingo_weinzierl@23: bjoern@89: def __init__(self, controller, view, project): ingo_weinzierl@23: super(RunningProjectsState, self).__init__(controller, view) bjoern@89: self.project = project bjoern@309: self.view.deactivate_selection() ingo_weinzierl@23: signal.signal(signal.SIGALRM, self.handle_signal) ingo_weinzierl@23: signal.alarm(1) ingo_weinzierl@23: ingo_weinzierl@23: def handle_signal(self, signum, frame): bjoern@89: proj = self.project bjoern@151: keys = self.config.get_keybinding() bjoern@205: bjoern@135: if not proj: bjoern@135: return bjoern@205: ingo_weinzierl@23: if not self.break_start: ingo_weinzierl@36: self.controller.view.set_footer_text(self.msg('running') % ingo_weinzierl@23: (human_time(self.sec), ingo_weinzierl@23: proj.desc), ingo_weinzierl@23: 'running') bjoern@194: self.controller.loop.draw_screen() ingo_weinzierl@23: self.sec = self.sec + 1 mschieder@501: # The time is stored every minute to be able to restore them in mschieder@501: # case of a crash. mschieder@499: if self.sec % 60 == 0: mschieder@499: self.controller.save_recovery_data() ingo_weinzierl@23: else: bjoern@296: self.view.set_footer_text( bjoern@296: self.msg('paused') % bjoern@206: (human_time((datetime.now() - self.break_start).seconds), bjoern@296: self.msg( bjoern@296: 'continue') % keys.get_project_pause()), ingo_weinzierl@23: 'paused_running') bjoern@194: self.controller.loop.draw_screen() ingo_weinzierl@23: ingo_weinzierl@23: signal.signal(signal.SIGALRM, self.handle_signal) ingo_weinzierl@23: signal.alarm(1) ingo_weinzierl@23: bjoern@205: def handle_input(self, key): ingo_weinzierl@23: logger.debug("RunningProjectsState: handle key '%r'" % key) bjoern@151: keys = self.config.get_keybinding() bjoern@205: ret = super(RunningProjectsState, self).handle_input(key) ingo_weinzierl@23: if ret: ingo_weinzierl@23: return ret ingo_weinzierl@23: bjoern@151: if keys.get_enter() in key: ingo_weinzierl@23: return self.stop() bjoern@206: bjoern@151: if keys.get_add_time() in key: ingo_weinzierl@23: self.view.set_footer_text(self.msg('add_time'), bjoern@384: 'question', edit=True) bjoern@205: self.set_next_state(AddTimeState(self.controller, self.view, self)) bjoern@205: return True bjoern@205: bjoern@151: if keys.get_subtract_time() in key: ingo_weinzierl@23: self.view.set_footer_text(self.msg('min_time'), bjoern@384: 'question', edit=True) bjoern@205: self.set_next_state(SubtractTimeState(self.controller, self.view, bjoern@296: self)) bjoern@205: return True bjoern@151: bjoern@171: if keys.get_project_pause() in key: bjoern@151: if not self.break_start: bjoern@151: self.break_start = datetime.now() bjoern@151: else: bjoern@278: self.view.show_total_time() bjoern@151: proj = self.project bjoern@151: if proj: bjoern@151: proj.start += datetime.now() - self.break_start bjoern@151: self.break_start = None bjoern@151: signal.signal(signal.SIGALRM, self.handle_signal) bjoern@151: signal.alarm(1) bjoern@205: return True bjoern@205: return False ingo_weinzierl@23: ingo_weinzierl@23: def stop(self): ingo_weinzierl@23: signal.alarm(0) ingo_weinzierl@23: if self.break_start: bjoern@89: proj = self.project bjoern@206: if proj: bjoern@206: proj.start += datetime.now() - self.break_start bjoern@277: self.controller.view.set_footer_text(self.msg('description'), bjoern@296: 'question', edit=True) bjoern@309: self.view.enable_selection() bjoern@296: self.set_next_state( bjoern@296: DescriptionProjectsState( bjoern@296: self.controller, self.view, bjoern@308: self, self.controller.view.get_frame().get_footer(), bjoern@308: self.project)) bjoern@205: return True ingo_weinzierl@23: ingo_weinzierl@23: ingo_weinzierl@23: class HandleUserInputState(State): bjoern@206: ingo_weinzierl@23: def __init__(self, controller, view, state, footer): bjoern@150: super(HandleUserInputState, self).__init__(controller, view) bjoern@206: self.state = state bjoern@206: self.footer = footer ingo_weinzierl@23: bjoern@205: def handle_input(self, key): ingo_weinzierl@23: logger.debug("HandleUserInputState: handle key '%r'" % key) bjoern@151: keys = self.config.get_keybinding() ingo_weinzierl@23: bjoern@151: if keys.get_escape() in key: ingo_weinzierl@23: return self.exit() bjoern@151: elif keys.get_enter() in key: ingo_weinzierl@23: return self.enter() bjoern@205: return False teichmann@39: teichmann@39: def enter(self): teichmann@39: raise Exception("Not implemented") ingo_weinzierl@23: ingo_weinzierl@23: def exit(self): bjoern@303: # restore old focus bjoern@303: self.state.set_focus() bjoern@205: self.set_next_state(self.state) bjoern@205: return True ingo_weinzierl@23: ingo_weinzierl@23: ingo_weinzierl@23: class BaseTimeState(HandleUserInputState): bjoern@206: ingo_weinzierl@23: def __init__(self, controller, view, running_state): teichmann@44: super(BaseTimeState, self).__init__(controller, view, running_state, bjoern@296: view.frame.get_footer()) bjoern@89: self.project = running_state.project ingo_weinzierl@23: ingo_weinzierl@23: def exit(self): bjoern@278: self.view.show_total_time() bjoern@205: return super(BaseTimeState, self).exit() ingo_weinzierl@23: ingo_weinzierl@23: def insert(self, key): bjoern@296: if key[0] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']: ingo_weinzierl@23: self.footer.insert_text(key[0]) ingo_weinzierl@23: else: bjoern@296: logger.debug("BaseTimeState: invalid character for " ingo_weinzierl@23: "adding/subtracting time: '%r'" % key) bjoern@205: return True ingo_weinzierl@23: bjoern@277: def set_focus(self): bjoern@277: self.controller.view.set_focus("projects") bjoern@277: self.view.frame.set_focus("footer") bjoern@277: ingo_weinzierl@23: ingo_weinzierl@23: class AddTimeState(BaseTimeState): bjoern@206: ingo_weinzierl@23: def enter(self): bjoern@206: minutes = safe_int(self.view.frame.get_footer().get_edit_text()) bjoern@206: project = self.project bjoern@206: project.start -= timedelta(minutes=minutes) ingo_weinzierl@23: self.state.sec += minutes * 60 ingo_weinzierl@23: logger.info("AddTimeState: add %d minutes to project '%s'" ingo_weinzierl@23: % (minutes, project.desc)) bjoern@278: self.view.show_total_time() bjoern@303: # set focus to the original element bjoern@303: self.state.set_focus() bjoern@205: self.set_next_state(self.state) bjoern@205: return True ingo_weinzierl@23: ingo_weinzierl@23: ingo_weinzierl@23: class SubtractTimeState(BaseTimeState): bjoern@206: ingo_weinzierl@23: def enter(self): bjoern@206: minutes = safe_int(self.view.frame.get_footer().get_edit_text()) bjoern@76: sec = minutes * 60 bjoern@76: if sec > self.state.sec: bjoern@278: self.view.show_total_time() bjoern@303: # set focus to the original element bjoern@303: self.state.set_focus() bjoern@294: self.set_next_state(self.state) bjoern@294: return False bjoern@206: project = self.project bjoern@206: project.start += timedelta(minutes=minutes) bjoern@76: self.state.sec -= sec ingo_weinzierl@23: logger.info("SubtractTimeState: subtract %d minutes from project '%s'" bjoern@296: % (minutes, project.desc)) bjoern@278: self.view.show_total_time() bjoern@303: # set focus to the original element bjoern@303: self.state.set_focus() bjoern@205: self.set_next_state(self.state) bjoern@205: return True ingo_weinzierl@23: ingo_weinzierl@23: ingo_weinzierl@23: class DescriptionProjectsState(HandleUserInputState): bjoern@296: bjoern@279: """ Adds a description to a stopped running project """ bjoern@279: ingo_weinzierl@23: messages = { ingo_weinzierl@36: 'choose_proj': _(" Choose a project."), ingo_weinzierl@23: } ingo_weinzierl@23: bjoern@308: def __init__(self, controller, view, state, footer, project): bjoern@308: super(DescriptionProjectsState, self).__init__(controller, view, state, bjoern@308: footer) bjoern@308: self.project = project mschieder@528: self.history_position = - 1 bjoern@308: mschieder@530: bjoern@246: def keypress(self, size, key): bjoern@246: """ Direct key to frame of GetanView """ mschieder@528: bjoern@246: self.controller.view.frame.keypress(size, key) bjoern@246: mschieder@530: entries = self.project.entries mschieder@528: if key == 'up': mschieder@528: if self.history_position < len(entries) - 1: mschieder@528: self.history_position = self.history_position + 1 mschieder@528: self.controller.view.frame.footer.set_edit_text( mschieder@528: entries[self.history_position].desc) mschieder@528: self.controller.view.frame.footer.set_edit_pos( mschieder@528: len(entries[self.history_position].desc)) mschieder@528: mschieder@528: if key == 'down': mschieder@528: if self.history_position >= 0: mschieder@528: self.history_position = self.history_position - 1 mschieder@528: if self.history_position == -1: mschieder@528: self.controller.view.frame.footer.set_edit_text("") mschieder@528: self.controller.view.frame.footer.set_edit_pos(0) mschieder@528: else: mschieder@528: self.controller.view.frame.footer.set_edit_text( mschieder@528: entries[self.history_position].desc) mschieder@528: self.controller.view.frame.footer.set_edit_pos( mschieder@528: len(entries[self.history_position].desc)) mschieder@528: mschieder@528: ingo_weinzierl@23: def enter(self): ingo_weinzierl@23: text = self.footer.get_edit_text() ingo_weinzierl@30: self.controller.stop_project(text) bjoern@296: self.controller.view.set_footer_text( bjoern@296: self.msg('choose_proj'), 'question') bjoern@205: self.set_next_state(PausedProjectsState(self.controller, self.view)) bjoern@286: self.view.update_rows() bjoern@280: self.view.show_total_time() bjoern@205: return True ingo_weinzierl@23: ingo_weinzierl@23: def exit(self): bjoern@308: if self.project: bjoern@308: time = (datetime.now() - self.project.start).seconds teichmann@43: self.state.sec = time teichmann@43: signal.signal(signal.SIGALRM, self.state.handle_signal) teichmann@43: signal.alarm(1) bjoern@205: return super(DescriptionProjectsState, self).exit() bjoern@106: bjoern@277: def set_focus(self): bjoern@277: self.controller.view.set_focus("footer") bjoern@277: ingo_weinzierl@23: ingo_weinzierl@23: class EntryListState(State): bjoern@206: ingo_weinzierl@23: def __init__(self, state, controller, view): bjoern@150: super(EntryListState, self).__init__(controller, view) ingo_weinzierl@23: self.projectlist_state = state ingo_weinzierl@23: bjoern@205: def handle_input(self, key): ingo_weinzierl@23: logger.debug("EntryListState: pressed key '%r'" % key) bjoern@151: keys = self.config.get_keybinding() bjoern@206: bjoern@151: if keys.get_switch_lists() in key: ingo_weinzierl@23: self.view.clear() bjoern@205: self.set_next_state(self.projectlist_state) bjoern@205: self.controller.view.set_focus(0) bjoern@205: return True bjoern@205: mschieder@479: if 'ctrl l' in key: mschieder@479: self.controller.redraw() mschieder@479: return True mschieder@479: bjoern@151: if keys.get_enter() in key: ingo_weinzierl@23: return self.select() bjoern@205: return False ingo_weinzierl@23: ingo_weinzierl@23: def select(self): ingo_weinzierl@23: self.view.select() bjoern@205: return True ingo_weinzierl@23: ingo_weinzierl@23: def renew_focus(self): ingo_weinzierl@23: e_len = self.view.row_count() mschieder@468: if e_len == 0: mschieder@473: return False bjoern@235: f = self.view.get_focus_pos() bjoern@135: if f >= e_len: bjoern@135: f = e_len - 1 bjoern@207: self.view.set_focus(f) mschieder@473: return True ingo_weinzierl@23: bjoern@277: def set_focus(self): bjoern@277: self.controller.view.set_focus("entries") bjoern@277: self.controller.entries_view.set_focus(0) bjoern@277: ingo_weinzierl@23: ingo_weinzierl@23: class DefaultEntryListState(EntryListState): bjoern@205: bjoern@205: def handle_input(self, key): bjoern@205: logger.info("Handling DefaultEntryListState input") bjoern@205: ret = super(DefaultEntryListState, self).handle_input(key) ingo_weinzierl@23: if ret: ingo_weinzierl@23: return ret ingo_weinzierl@23: bjoern@151: keys = self.config.get_keybinding() bjoern@151: if keys.get_escape() in key: bjoern@105: self.view.clear() bjoern@205: self.set_next_state(self.projectlist_state) bjoern@236: self.controller.view.set_focus(0) bjoern@205: return True bjoern@105: bjoern@151: if keys.get_entry_delete() in key: ingo_weinzierl@25: if self.view.selection: bjoern@205: self.set_next_state(DeleteEntryState(self.projectlist_state, bjoern@312: self, bjoern@296: self.controller, bjoern@296: self.view)) bjoern@102: else: bjoern@102: entry = self.view.item_in_focus() bjoern@205: self.set_next_state(DeleteEntryState(self.projectlist_state, bernhard@410: self, self.controller, bernhard@410: self.view, [entry])) bjoern@205: return True bjoern@205: bjoern@151: if keys.get_entry_move() in key: ingo_weinzierl@25: if self.view.selection: bjoern@205: self.set_next_state(MoveEntryState(self.projectlist_state, bjoern@296: self.controller, self.view)) bernhard@439: else: bernhard@439: entry = self.view.item_in_focus() bernhard@439: self.set_next_state(MoveEntryState(self.projectlist_state, bernhard@439: self.controller, bernhard@439: self.view, [entry])) bjoern@205: return True bjoern@205: bjoern@151: if keys.get_entry_edit() in key: bjoern@74: entry = self.view.item_in_focus() bjoern@74: if entry: bjoern@205: self.set_next_state(EditEntryState(self.projectlist_state, bjoern@296: self.controller, self.view, bjoern@296: entry)) bjoern@205: return True bjoern@205: bernhard@400: if keys.get_entry_adjust() in key: bernhard@400: entry = self.view.item_in_focus() bernhard@400: if entry: bernhard@400: self.set_next_state(AdjustEntryState(self.projectlist_state, bernhard@410: self.controller, bernhard@410: self.view, entry)) bernhard@405: return True bernhard@405: bernhard@405: if keys.get_entry_length() in key: bernhard@405: entry = self.view.item_in_focus() bernhard@405: if entry: bernhard@405: self.set_next_state(LengthEntryState(self.projectlist_state, bernhard@410: self.controller, bernhard@410: self.view, entry)) bernhard@400: return True bernhard@400: bjoern@205: return False ingo_weinzierl@23: ingo_weinzierl@23: ingo_weinzierl@23: class DeleteEntryState(EntryListState): bjoern@206: ingo_weinzierl@23: messages = { bjoern@296: 'delete': _("Really delete this entry? (y/n)"), ingo_weinzierl@23: } ingo_weinzierl@23: bjoern@312: def __init__(self, state, old_state, controller, view, entries=None): ingo_weinzierl@23: super(DeleteEntryState, self).__init__(state, controller, view) ingo_weinzierl@23: self.view.set_footer_text(self.msg('delete'), 'question') bjoern@100: self.entries = entries bjoern@312: self.old_state = old_state bjoern@100: if not self.entries: bjoern@100: self.entries = [x.item for x in self.view.selection] ingo_weinzierl@23: bjoern@205: def handle_input(self, key): bjoern@310: keys = self.config.get_keybinding() ingo_weinzierl@23: if 'y' in key: bjoern@100: if self.entries: bjoern@100: self.controller.delete_entries(self.entries) mschieder@473: new_focus = self.renew_focus() mschieder@470: self.projectlist_state.view.update_rows() mschieder@474: self.view.clear() ingo_weinzierl@25: self.view.set_footer_text("", 'entry_footer') bjoern@312: # avoid creating new DefaultEntryListState and setting focus mschieder@473: if new_focus: mschieder@473: self.set_next_state(self.old_state) mschieder@473: else: mschieder@473: self.set_next_state(self.projectlist_state) mschieder@473: self.controller.view.set_focus(0) mschieder@473: bjoern@280: self.controller.project_view.show_total_time() bjoern@205: return True ingo_weinzierl@23: bjoern@310: if 'n' in key or keys.get_escape() in key: ingo_weinzierl@23: self.view.set_footer_text("", 'entry_footer') bjoern@312: # avoid creating new DefaultEntryListState and setting focus bjoern@312: self.set_next_state(self.old_state) bjoern@205: return True ingo_weinzierl@23: bjoern@205: return False ingo_weinzierl@23: bjoern@313: def set_focus(self): bjoern@313: self.controller.view.set_focus("entries") bjoern@313: ingo_weinzierl@23: ingo_weinzierl@23: class MoveEntryState(EntryListState): ingo_weinzierl@23: messages = { ingo_weinzierl@36: 'project': _(" Into which project do you want to move these entries?"), bjoern@103: 'really': _(" Are you sure? (y/n)"), ingo_weinzierl@23: } ingo_weinzierl@23: ingo_weinzierl@23: proj = None ingo_weinzierl@23: bernhard@439: def __init__(self, state, controller, view, entries=None): ingo_weinzierl@23: super(MoveEntryState, self).__init__(state, controller, view) ingo_weinzierl@23: self.view.set_footer_text(self.msg('project'), 'question') bernhard@439: self.entries = entries bjoern@104: self.proj_keys = "" bjoern@133: self.project_view = controller.project_view bernhard@439: if not self.entries: bernhard@439: self.entries = [x.item for x in self.view.selection] bjoern@104: bjoern@104: def set_project_footer(self): bjoern@296: self.project_view.set_footer_text("Selecting project from " bernhard@410: "key: %s" % self.proj_keys, bernhard@410: "running") bjoern@104: bjoern@104: def reset_project_footer(self): bjoern@133: self.project_view.reset_footer() ingo_weinzierl@23: bjoern@169: def check_key(self, key): bjoern@169: return len(self.controller.find_projects_by_key(key)) bjoern@169: bjoern@169: def select_project(self): bjoern@169: proj = self.controller.project_by_key(self.proj_keys) bjoern@169: if proj: bjoern@169: self.proj = proj bjoern@169: self.reset_project_footer() bjoern@296: logger.debug("MoveEntryState: prepared entries to be " bjoern@296: "moved to project '%s'" % self.proj.desc) bjoern@169: self.view.set_footer_text(self.msg('really'), 'question') bjoern@169: bjoern@205: def handle_input(self, key): bjoern@151: keys = self.config.get_keybinding() ingo_weinzierl@23: if 'y' in key and self.proj: ingo_weinzierl@23: logger.debug("MoveEntryState: move selected entries.") bernhard@439: self.controller.move_entries(self.entries, self.proj) mschieder@473: new_focus = self.renew_focus() mschieder@474: self.view.clear() ingo_weinzierl@23: self.view.set_footer_text('', 'entry_footer') ingo_weinzierl@23: self.proj = None mschieder@473: if new_focus: mschieder@473: self.set_next_state(DefaultEntryListState(self.projectlist_state, mschieder@473: self.controller, mschieder@473: self.view)) mschieder@473: else: mschieder@473: self.set_next_state(self.projectlist_state) mschieder@473: self.controller.view.set_focus(0) mschieder@473: bjoern@205: return True ingo_weinzierl@23: ingo_weinzierl@25: if 'n' in key and self.proj: ingo_weinzierl@23: self.view.set_footer_text('', 'entry_footer') bjoern@104: self.reset_project_footer() bjoern@205: self.set_next_state(DefaultEntryListState(self.projectlist_state, bjoern@296: self.controller, bjoern@296: self.view)) bjoern@205: return True ingo_weinzierl@23: bjoern@151: if keys.get_escape() in key: ingo_weinzierl@23: self.view.set_footer_text('', 'entry_footer') bjoern@104: self.reset_project_footer() bjoern@205: self.set_next_state(DefaultEntryListState(self.projectlist_state, bjoern@296: self.controller, bjoern@296: self.view)) bjoern@205: return True bjoern@243: bjoern@134: if 'backspace' in key: bjoern@134: if len(self.proj_keys) > 0: bjoern@134: self.proj_keys = self.proj_keys[:-1] bjoern@134: self.set_project_footer() bjoern@205: return True ingo_weinzierl@23: bjoern@169: if keys.get_enter() in key and self.proj is None: bjoern@169: self.select_project() bjoern@205: return True bjoern@169: bjoern@104: if len(key) > 0 and len(key[0]) == 1 and self.proj is None: bjoern@169: proj_key = self.proj_keys + key[0] bjoern@169: num = self.check_key(proj_key) bjoern@169: if num > 0: bjoern@169: self.proj_keys = proj_key bjoern@169: self.set_project_footer() bjoern@169: if num == 1: bjoern@169: self.select_project() bjoern@205: return True bjoern@170: bjoern@205: return False bjoern@54: bernhard@439: def set_focus(self): bernhard@439: self.controller.view.set_focus("entries") bernhard@439: bjoern@170: bjoern@173: class AlterProjectState(HandleUserInputState): bjoern@54: bjoern@54: messages = { bjoern@296: 'choose_proj': _(' Choose a project.'), bjoern@296: } bjoern@54: bjoern@54: def __init__(self, controller, view): bjoern@296: super(AlterProjectState, self).__init__( bjoern@296: controller, view, None, bjoern@296: controller.view.get_frame().get_footer()) bjoern@54: bjoern@54: def exit(self): bjoern@268: self.controller.view.set_footer_text(self.msg('choose_proj'), bjoern@296: 'question') bjoern@205: self.set_next_state(PausedProjectsState(self.controller, self.view)) bjoern@205: return True bjoern@54: bjoern@267: def keypress(self, size, key): bjoern@267: """ Direct key to frame of GetanView """ bjoern@267: self.controller.view.frame.keypress(size, key) bjoern@267: bjoern@277: def set_focus(self): bjoern@277: self.controller.view.set_focus("footer") bjoern@277: bjoern@170: bjoern@173: class AddProjectKeyState(AlterProjectState): bjoern@172: bjoern@54: messages = { bjoern@54: 'choose_proj': _(' Choose a project.'), bjoern@172: 'proj_key': _('Insert key for new project: '), bjoern@54: } bjoern@54: bjoern@54: def __init__(self, controller, view): bjoern@268: controller.view.set_footer_text(self.msg('proj_key'), 'question', bjoern@296: edit=True) bjoern@54: super(AddProjectKeyState, self).__init__(controller, view) bjoern@54: bjoern@54: def enter(self): bjoern@54: key = self.footer.get_edit_text() bjoern@54: if key == '': bjoern@205: return True bjoern@205: self.set_next_state(AddProjectDescriptionState(self.controller, bjoern@296: self.view, key)) bjoern@205: return True bjoern@54: bjoern@170: bjoern@173: class AddProjectDescriptionState(AlterProjectState): bjoern@206: bjoern@54: messages = { bjoern@54: 'proj_description': _('Insert a description for project: '), bjoern@54: 'choose_proj': _(" Choose a project.") bjoern@296: } bjoern@54: bjoern@172: def __init__(self, controller, view, key): bjoern@54: controller.view.set_footer_text(self.msg('proj_description'), bjoern@296: 'question', edit=True) bjoern@54: super(AddProjectDescriptionState, self).__init__(controller, view) bjoern@54: self.key = key bjoern@54: bjoern@54: def enter(self): bjoern@54: description = self.footer.get_edit_text() bjoern@54: if description == '': bjoern@54: return self bjoern@54: self.controller.add_project(self.key, description) bjoern@271: self.exit() bjoern@205: return True bjoern@73: bjoern@73: bjoern@73: class EditEntryState(HandleUserInputState): bjoern@73: messages = { bjoern@73: 'edit_entry': _('Edit entry text: '), bjoern@73: } bjoern@73: bjoern@73: def __init__(self, state, controller, view, entry): bjoern@87: view.set_footer_text(self.msg('edit_entry'), bjoern@87: 'question', True) bjoern@73: super(EditEntryState, self).__init__(controller, view, bjoern@296: None, view.footer) bjoern@73: self.footer.set_edit_text(entry.desc) bjoern@73: self.footer.set_edit_pos(len(self.footer.edit_text)) bjoern@73: self.entry = entry bjoern@73: self.state = state bjoern@73: logger.debug("EditEntryState: Entry %s" % entry) bjoern@73: bjoern@73: def enter(self): bjoern@73: entry_desc = self.footer.get_edit_text() bjoern@73: if entry_desc == '': bjoern@73: return self bjoern@73: entry = self.entry bjoern@73: entry.desc = entry_desc bjoern@73: self.controller.update_entry(entry) bjoern@87: self.view.node_in_focus().update() bjoern@73: return self.exit() bjoern@73: bjoern@73: def exit(self): bjoern@87: self.view.set_footer_text("", 'entry_footer', False) bjoern@205: self.set_next_state(DefaultEntryListState(self.state, self.controller, bjoern@296: self.view)) bjoern@205: return True bjoern@177: bjoern@277: def set_focus(self): bjoern@277: self.controller.view.set_focus("entries") bjoern@277: self.view.frame.set_focus("footer") bjoern@277: bernhard@410: bernhard@400: class AdjustEntryState(HandleUserInputState): bernhard@400: messages = { bernhard@400: 'adjust_entry': _('Adjust datetime of entry: '), bernhard@400: } bernhard@400: bernhard@400: def __init__(self, state, controller, view, entry): bernhard@400: view.set_footer_text(self.msg('adjust_entry'), bernhard@400: 'question', True) bernhard@400: super(AdjustEntryState, self).__init__(controller, view, bernhard@410: None, view.footer) bernhard@400: bernhard@400: # we only care up to seconds (which is 19 characters). bernhard@400: # for usability the default value has to match the strptime fmt below. bernhard@400: self.footer.set_edit_text(str(entry.start)[:19]) bernhard@400: self.footer.set_edit_pos(len(self.footer.edit_text)) bernhard@400: self.entry = entry bernhard@400: self.state = state bernhard@400: logger.debug("AdjustEntryState: Entry %s" % entry) bernhard@400: bernhard@400: def enter(self): bernhard@410: entry_datetime = self.footer.get_edit_text() bernhard@400: bernhard@400: entry = self.entry bernhard@400: duration = entry.get_duration() bernhard@400: bernhard@400: try: bernhard@410: entry.start = datetime.strptime(entry_datetime, bernhard@410: "%Y-%m-%d %H:%M:%S") bernhard@400: except: bernhard@400: return self bernhard@400: bernhard@400: entry.end = entry.start + duration bernhard@400: bernhard@400: self.controller.update_entry(entry) bernhard@400: self.view.node_in_focus().update() bernhard@400: return self.exit() bernhard@400: bernhard@400: def exit(self): bernhard@400: self.view.set_footer_text("", 'entry_footer', False) bernhard@400: self.set_next_state(DefaultEntryListState(self.state, self.controller, bernhard@400: self.view)) bernhard@400: return True bernhard@400: bernhard@400: def set_focus(self): bernhard@400: self.controller.view.set_focus("entries") bernhard@400: self.view.frame.set_focus("footer") bernhard@400: bernhard@410: bernhard@405: class LengthEntryState(HandleUserInputState): bernhard@405: messages = { bernhard@405: 'adjust_length_entry': _('Adjust length of entry: '), bernhard@405: } bernhard@405: bernhard@405: def __init__(self, state, controller, view, entry): bernhard@405: view.set_footer_text(self.msg('adjust_length_entry'), bernhard@405: 'question', True) bernhard@405: super(LengthEntryState, self).__init__(controller, view, bernhard@410: None, view.footer) bernhard@405: bernhard@405: # format current duration as string that is also accepted by enter() bernhard@405: total_minutes = int(entry.get_duration().total_seconds()/60) bernhard@405: hours = int(total_minutes // 60) bernhard@405: bernhard@405: if hours > 0: bernhard@405: self.footer.set_edit_text( bernhard@409: "{:d}:{:02d}".format(hours, int(total_minutes % 60))) bernhard@405: else: bernhard@407: self.footer.set_edit_text("{:d}".format(total_minutes)) bernhard@405: bernhard@405: self.footer.set_edit_pos(len(self.footer.edit_text)) bernhard@405: self.entry = entry bernhard@405: self.state = state bernhard@405: logger.debug("LengthEntryState: Entry %s" % entry) bernhard@405: bernhard@405: def enter(self): bernhard@405: """Changed the length of an entry. bernhard@405: bernhard@405: Works for total minutes or HH:MM. bernhard@405: """ bernhard@405: entry_duration = self.footer.get_edit_text() bernhard@405: bernhard@405: # avoid unexpected behavior if minus signs are given in the new length bernhard@405: if '-' in entry_duration: bernhard@405: return self bernhard@405: bernhard@405: if ':' in entry_duration: bernhard@405: hours, minutes = entry_duration.split(':') bernhard@405: else: bernhard@405: hours = 0 bernhard@405: minutes = entry_duration bernhard@405: bernhard@405: try: bernhard@405: duration = timedelta(minutes=int(minutes), hours=int(hours)) bernhard@405: except: bernhard@405: return self bernhard@405: bernhard@405: entry = self.entry bernhard@405: entry.end = entry.start + duration bernhard@405: bernhard@405: self.controller.update_entry(entry) bernhard@405: self.view.node_in_focus().update() mschieder@476: self.controller.view.proj_list.update_rows() mschieder@478: self.controller.project_view.show_total_time() bernhard@405: return self.exit() bernhard@405: bernhard@405: def exit(self): bernhard@405: self.view.set_footer_text("", 'entry_footer', False) bernhard@405: self.set_next_state(DefaultEntryListState(self.state, self.controller, bernhard@405: self.view)) bernhard@405: return True bernhard@405: bernhard@405: def set_focus(self): bernhard@405: self.controller.view.set_focus("entries") bernhard@405: self.view.frame.set_focus("footer") bernhard@400: bernhard@400: bjoern@177: class ProjectEditKeyState(AlterProjectState): bjoern@177: bjoern@177: messages = { bjoern@177: 'proj_key': _('Insert key for project: '), bjoern@177: 'proj_description': _('Insert description for project: '), bjoern@177: 'choose_proj': _(" Choose a project.") bjoern@177: } bjoern@177: bjoern@177: def __init__(self, controller, view, project): bjoern@177: controller.view.set_footer_text(self.msg('proj_key'), bjoern@296: 'question', 1) bjoern@177: super(ProjectEditKeyState, self).__init__(controller, view) bjoern@177: self.project = project bjoern@177: self.footer.set_edit_text(project.key) bjoern@177: self.footer.set_edit_pos(len(self.footer.edit_text)) bjoern@177: bjoern@177: def enter(self): bjoern@177: key = self.footer.get_edit_text() bjoern@177: if key == '': bjoern@205: return True bjoern@177: self.project.key = key bjoern@205: self.set_next_state(ProjectEditDescriptionState(self.controller, bjoern@296: self.view, bjoern@296: self.project)) bjoern@205: return True bjoern@177: bjoern@177: bjoern@177: class ProjectEditDescriptionState(AlterProjectState): bjoern@177: bjoern@177: messages = { bjoern@296: "proj_description": _("Insert description for project: "), bjoern@296: "choose_proj": _(" Choose a project.") bjoern@177: } bjoern@177: bjoern@177: def __init__(self, controller, view, project): bjoern@177: controller.view.set_footer_text(self.msg("proj_description"), bjoern@296: "question", 1) bjoern@177: super(ProjectEditDescriptionState, self).__init__(controller, view) bjoern@177: self.project = project bjoern@177: self.footer.set_edit_text(project.desc) bjoern@177: self.footer.set_edit_pos(len(self.footer.edit_text)) bjoern@177: bjoern@177: def enter(self): bjoern@177: description = self.footer.get_edit_text() bjoern@177: if description == '': bjoern@177: return self bjoern@177: self.project.desc = description bjoern@177: self.controller.update_project(self.project) bjoern@296: self.controller.view.set_footer_text( bjoern@296: self.msg('choose_proj'), 'question') bjoern@205: self.set_next_state(PausedProjectsState(self.controller, self.view)) bjoern@205: return True