view getan/states.py @ 100:8f433e3c2f21

Allow to pass entries to the DeleteEntryState By default DeleteEntryState uses the selected entries if no entry is passes to the constructor. This allows to reuse the state class for single entry deletion
author Björn Ricks <bjoern.ricks@intevation.de>
date Mon, 12 Sep 2011 14:05:34 +0200
parents 7c3f43bfc0a8
children df98b84d22f9
line wrap: on
line source
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# (c) 2010 by Ingo Weinzierl <ingo.weinzierl@intevation.de>
# (c) 2011 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 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

    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 self

        if 'tab' 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 'up' in key:
            return self.up()

        if '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)
        ret = super(PausedProjectsState, self).keypress(key)
        if ret:
            return ret

        if 'enter' in key:
            return self.select()

        if 'insert' in key:
            return AddProjectNameState(self.controller, self.view)

        if 'esc' 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):
        if 'esc' in key:
            self.reset()
            return PausedProjectsState(self.controller, self.view)
        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
        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)
        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.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):
        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 enter(self):
        raise Exception("Not implemented")

    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())
        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):
        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:
            if self.view.selection:
                return DeleteEntryState(self.projectlist_state,
                                        self.controller, self.view)
        if 'm' in key:
            if self.view.selection:
                return MoveEntryState(self.projectlist_state,
                                      self.controller, self.view)
        if 'e' 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):
        ret = super(DeleteEntryState, self).keypress(key)
        if ret:
            return ret

        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? (> %s) (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.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')
            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

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)

This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)