changeset 45:780bfda9c583 project-tree

Initialized a branch to work on a tree-like project view.
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Tue, 21 Dec 2010 19:34:03 +0100
parents f10126519797
children 0a0c77b8606a
files ChangeLog getan.py getan/project.py getan/states.py getan/view.py
diffstat 5 files changed, 130 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Dec 15 14:47:40 2010 +0100
+++ b/ChangeLog	Tue Dec 21 19:34:03 2010 +0100
@@ -1,3 +1,16 @@
+2010-12-21  Ingo Weinzierl <ingo@intevation.de>
+
+	* getan.py: Added a method to sort the projects based on its key.
+
+	* getan/project.py: A project can now have children. Therefore, a new
+	  method add_child() has been implemented. Important to build up a tree is
+	  the project's key. If we try to append a new child to a project, but
+	  there is already a child with the key that starts just like the new
+	  child's, we will try to append the new child to the existing one.
+
+	* getan/view.py: The project view looks now like a tree. Projects can have
+	  children, that are indented relative to its parent.
+
 2010-12-15	Sascha L. Teichmann <sascha.teichmann@intevation.de>
 
 	Fix for #1638
--- a/getan.py	Wed Dec 15 14:47:40 2010 +0100
+++ b/getan.py	Tue Dec 21 19:34:03 2010 +0100
@@ -20,6 +20,18 @@
 
 logger = logging.getLogger()
 
+def order_projects(projects):
+    projects = sorted(projects, key=lambda proj: proj.key)
+    ordered  = {}
+    for p in projects:
+        c = p.key[0]
+        if c in ordered:
+            ordered[c].add_child(p.key[1:], p)
+        else:
+            ordered[c] = p
+    return ordered
+
+
 class GetanController:
     def __init__(self, backend, pv_class, ev_class):
         self.ev_class = ev_class
@@ -33,7 +45,7 @@
         self.running  = []
 
         self.backend      = backend
-        self.project_view = pv_class(self, self.projects)
+        self.project_view = pv_class(self, order_projects(self.projects))
         self.entries_view = ev_class(entries)
 
         self.view  = GetanView(self, self.project_view, self.entries_view)
--- a/getan/project.py	Wed Dec 15 14:47:40 2010 +0100
+++ b/getan/project.py	Tue Dec 21 19:34:03 2010 +0100
@@ -8,18 +8,37 @@
 # This is Free Software licensed unter the terms of GPLv3 or later.
 # For details see LICENSE coming with the source of 'getan'.
 
+import logging
+
 from datetime import datetime, timedelta
 
+logger = logging.getLogger()
+
 class Project:
 
     def __init__(self, id, key, desc, total):
-        self.id      = id
-        self.key     = key
-        self.desc    = desc
-        self.entries = []
-        self.total   = total
-        self.start   = None
-        self.stop    = None
+        self.id       = id
+        self.key      = key
+        self.desc     = desc
+        self.entries  = []
+        self.children = {}
+        self.total    = total
+        self.start    = None
+        self.stop     = None
+
+    def add_child(self, key, project):
+        if not key or not project:
+            return
+        logger.debug("Parent: %s\tAdd child: %s" % (self.key, project.key))
+        if key in self.children:
+            self.children[key].add_child(key[1:], project)
+        else:
+            self.children[key] = project
+
+    def dump(self):
+        logger.debug(self.key)
+        for k, p in self.children.items():
+            p.dump()
 
     def year(self):
         total = 0
--- a/getan/states.py	Wed Dec 15 14:47:40 2010 +0100
+++ b/getan/states.py	Tue Dec 21 19:34:03 2010 +0100
@@ -37,6 +37,7 @@
             return None
 
         if 'tab' in key:
+            if self.view.item_in_focus().children: return None
             self.controller.entries_view.focused = 0
             self.controller.entries_view.update_focus(0)
             return DefaultEntryListState(self, self.controller,
--- a/getan/view.py	Wed Dec 15 14:47:40 2010 +0100
+++ b/getan/view.py	Tue Dec 21 19:34:03 2010 +0100
@@ -19,24 +19,53 @@
 
 logger = logging.getLogger()
 
+
+def create_project_tree(projects):
+    nodes = []
+    for k in sorted(projects.keys()):
+        nodes.extend(create_project_node(projects[k], 0))
+    return nodes
+
+def create_project_node(project, indent=0):
+    nodes = [ProjectNode(project, indent)]
+    for k, p in project.children.items():
+        nodes.extend(create_project_node(p, indent+3))
+    return nodes
+
 class ListWidget(urwid.BoxWidget):
     def _update_view(self):
         logger.debug("ListWidget: update view now.")
+        if self.size and len(self.rows) > self.size[1]:
+            start_idx = self.top
+            end_idx   = start_idx + self.size[1] - 6
+        else:
+            start_idx = self.top
+            end_idx   = start_idx + len(self.rows)
+        listbox = urwid.LineBox(urwid.Padding(
+            urwid.ListBox(urwid.SimpleListWalker(self.rows[start_idx:
+                                                           end_idx])),
+            ('fixed left', 1),
+            ('fixed right', 1)))
+
+        self.body = urwid.AttrWrap(listbox, 'body')
+
         self.frame.set_body(self.body)
         self.frame.set_footer(self.footer)
         self.frame.set_header(self.header)
 
     def update_focus(self, focus, unfocus=-1):
-        logger.debug("ListWidget: focus row (index = %d)" % focus)
-        logger.debug("ListWidget: unfocus row (index = %d)" % unfocus)
+        self.focused = focus
         if focus >= 0 and focus <= len(self.rows)-1:
             self.rows[focus].focus = True
             self.rows[focus].update_w()
         if unfocus >= 0:
             self.rows[unfocus].focus = False
             self.rows[unfocus].update_w()
+        if self.size:
+            self.validate_view()
 
     def render(self, size, focus=False):
+        self.size = size
         maxcol, maxrow = size
         return self.frame.render((maxcol, maxrow), focus)
 
@@ -44,11 +73,10 @@
         if edit:
             logger.debug("ListWidget: set footer text (edit) = '%s'" % text)
             self.footer = urwid.AttrWrap(urwid.Edit(text),attr)
-            self._update_view()
         else:
             logger.debug("ListWidget: set footer text = '%s'" % text)
             self.footer = urwid.AttrWrap(urwid.Text(text),attr)
-            self._update_view()
+        self._update_view()
 
     def row_count(self):
         if not self.rows: return 0
@@ -60,22 +88,31 @@
         return None
 
     def up(self):
-        logger.debug("ListWidget: navigate to upper row.")
         if self.focused > 0:
-            self.focused = self.focused - 1
-            self.update_focus(self.focused, self.focused+1)
+            logger.debug("ListWidget: navigate to upper row.")
+            self.update_focus(self.focused-1, self.focused)
 
     def down(self):
-        logger.debug("ListWidget: navigate to lower row.")
         if self.focused < len(self.rows) - 1:
-            self.focused = self.focused + 1
-            self.update_focus(self.focused, self.focused-1)
+            logger.debug("ListWidget: navigate to lower row.")
+            self.update_focus(self.focused+1, self.focused)
+
+    def validate_view(self):
+        if self.focused < self.top:
+            self.top = self.focused
+            self._update_view()
+        if self.focused + 8 >= self.top + self.size[1]:
+            self.top = self.focused - (self.size[1]) + 9
+        if self.top < 0:
+            self.top = 0
+        self._update_view()
 
     def select(self):
         if not self.rows: return None
         node = self.rows[self.focused]
         logger.debug("ListWidget: select row '%s'" % self.focused)
-        node.select()
+        selected = node.select()
+        if not selected: return None
         if node.selected:
             self.selection.append(node)
         else:
@@ -101,9 +138,10 @@
         (4, _('Day'))
     ]
 
-    def __init__(self, proj, mode=3):
+    def __init__(self, proj, indent=0, mode=3):
         self.selected   = False
         self.focus      = False
+        self.indent     = indent
         self.mode       = self.MODES[mode]
         self.item       = proj
         w               = self.update()
@@ -113,8 +151,14 @@
     def update(self):
         logger.debug("Update ProjectNode '%s'" % self.item.desc)
         time_str    = self._get_formatted_time()
-        description = urwid.Text('%s %s' % (self.item.key, self.item.desc))
-        time        = urwid.Text('%s (%s)' % (self.mode[1], time_str))
+        description = urwid.Text([' ' * self.indent,
+                                  ('project_key',self.item.key),
+                                  (' '),
+                                  (self.item.desc)])
+        if self.selectable():
+            time = urwid.Text('%s (%s)' % (self.mode[1], time_str))
+        else:
+            time = urwid.Text('')
         self.widget = urwid.AttrWrap(urwid.Columns([description, time]),None)
         self._w     = self.widget
         self.update_w()
@@ -161,22 +205,30 @@
                 self._w.focus_attr = 'body'
                 self._w.attr       = 'body'
 
+    def selectable(self):
+        return not self.item.children
+
     def select(self):
-        self.selected = not self.selected
-        self.update_w()
+        if self.selectable():
+            self.selected = not self.selected
+            self.update_w()
+            return True
+        return False
 
 
 class ProjectList(ListWidget):
     def __init__(self, controller, rows):
         self.selection    = []
         self.focused      = 0
+        self.size         = ()
+        self.top          = 0
         self.controller   = controller
-        self.raw_projects = rows
+        self.raw_rows     = rows
 
         self.header    = urwid.LineBox(urwid.AttrWrap(urwid.Text("\n%s\n" %
             _('List of registered projects')),'project_header'))
         self.footer    = urwid.Edit()
-        self.rows      = [ProjectNode(x) for x in rows]
+        self.rows      = create_project_tree(rows)
         self.listbox   = urwid.ListBox(urwid.SimpleListWalker(self.rows))
         self.body      = urwid.LineBox(urwid.Padding(urwid.AttrWrap(
             self.listbox, 'entries'),('fixed left',1),('fixed right',1)))
@@ -193,9 +245,9 @@
             tmp = proj._get_time()
             if tmp and type(tmp) == int:
                 total += tmp
-        self.frame.set_footer(urwid.AttrWrap(
-            urwid.Text(_(' All projects: %s %s')
-                       % (proj.mode[1],human_time(total))), 'project_footer'))
+        self.set_footer_text(_(' All projects: %s %s')
+                             % (proj.mode[1],human_time(total)),
+                             'project_footer')
 
     def update(self):
         logger.debug("ProjectList: update focused project row now.")
@@ -240,7 +292,6 @@
         self.update_w()
 
     def update(self):
-        logger.debug("EntryNode: update entry '%s'." % self.item.desc)
         row = urwid.Text(' %s [%s] %s' \
                          % (format_datetime(self.item.start), 
                             short_time(self.item.duration().seconds),
@@ -271,11 +322,14 @@
                      % self.item.desc)
         self.selected = not self.selected
         self.update_w()
+        return True
 
 
 class EntryList(ListWidget):
     def __init__(self, rows):
         self.selection = []
+        self.top       = 0
+        self.size      = ()
         self.focused   = 0
         self.rows      = [EntryNode(x) for x in rows]
         listbox        = urwid.ListBox(urwid.SimpleListWalker(self.rows))
@@ -290,11 +344,7 @@
                                      footer=self.footer)
 
     def set_rows(self, rows):
-        logger.debug("EntryList: set new entries.")
         self.rows = [EntryNode(x) for x in rows]
-        listbox      = urwid.LineBox(urwid.ListBox(urwid.SimpleListWalker(
-            self.rows)))
-        self.body    = urwid.AttrWrap(listbox, 'entry_body')
         self._update_view()
 
 
@@ -306,6 +356,7 @@
         ('footer',               'yellow', 'dark blue'),
         ('entry_footer',         'white',  'dark blue'),
         ('project_footer',       'white',  'dark blue'),
+        ('project_key',          'black',  'dark cyan'),
         ('body',                 'white',  'black'),
         ('entry body',           'white',  'dark blue'),
         ('entries',              'white',  'black'),
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)