[hamster-applet] fixed bug 607108. due to all kinds of little things in treeview (no col and rowspan) had to go for "



commit 137bb8e79078ab2a317d1dc8db1adb943086b936
Author: Toms Bauģis <toms baugis gmail com>
Date:   Sat Jan 16 04:39:45 2010 +0000

    fixed bug 607108. due to all kinds of little things in treeview (no col and rowspan) had to go for "tile style single cell - do your own layout and drawing" for the fact tree
    
    Signed-off-by: Toms Bauģis <toms baugis gmail com>

 data/applet.ui              |    1 +
 data/overview.ui            |    1 +
 hamster/applet.py           |    5 +
 hamster/overview.py         |    7 +-
 hamster/widgets/facttree.py |  381 ++++++++++++++++++++++++++++++++++--------
 hamster/widgets/tags.py     |  136 +---------------
 6 files changed, 326 insertions(+), 205 deletions(-)
---
diff --git a/data/applet.ui b/data/applet.ui
index 0f69ac0..4ff525b 100644
--- a/data/applet.ui
+++ b/data/applet.ui
@@ -14,6 +14,7 @@
     <property name="skip_pager_hint">True</property>
     <property name="decorated">False</property>
     <signal name="key_press_event" handler="on_windows_keys"/>
+    <signal name="configure_event" handler="on_window_configure_event"/>
     <child>
       <object class="GtkEventBox" id="hamster-box">
         <property name="visible">True</property>
diff --git a/data/overview.ui b/data/overview.ui
index 8d995f6..22c8b3f 100644
--- a/data/overview.ui
+++ b/data/overview.ui
@@ -7,6 +7,7 @@
     <property name="title" translatable="yes">Overview - Hamster</property>
     <property name="default_width">800</property>
     <property name="default_height">600</property>
+    <signal name="configure_event" handler="on_tabs_window_configure_event"/>
     <signal name="delete_event" handler="on_tabs_window_deleted"/>
     <child>
       <object class="GtkVBox" id="vbox1">
diff --git a/hamster/applet.py b/hamster/applet.py
index 7f694a1..5e19047 100755
--- a/hamster/applet.py
+++ b/hamster/applet.py
@@ -617,6 +617,11 @@ class HamsterApplet(object):
         self.last_activity = None
         runtime.dispatcher.dispatch('panel_visible', False)
 
+    def on_window_configure_event(self, window, event):
+        # this is required so that the rows would grow on resize
+        self.treeview.fix_row_heights()
+
+
     def show(self):
         self.window.show_all()
 
diff --git a/hamster/overview.py b/hamster/overview.py
index d8e72f2..4091d88 100644
--- a/hamster/overview.py
+++ b/hamster/overview.py
@@ -251,6 +251,11 @@ class Overview(object):
 
             self.search()
 
+    def on_tabs_window_configure_event(self, window, event):
+        # this is required so that the rows would grow on resize
+        self.fact_tree.fix_row_heights()
+
+
     def on_start_date_entered(self, input):
         self.start_date = input.get_date().date()
         self.view_date = self.start_date
@@ -332,7 +337,7 @@ class Overview(object):
 
     def on_edit_clicked(self, button):
         fact = self.fact_tree.get_selected_fact()
-        if not fact or isinstance(fact, date):
+        if not fact or isinstance(fact, dt.date):
             return
         dialogs.edit.show(fact_id = fact["id"])
 
diff --git a/hamster/widgets/facttree.py b/hamster/widgets/facttree.py
index 516dd1f..20510d8 100644
--- a/hamster/widgets/facttree.py
+++ b/hamster/widgets/facttree.py
@@ -22,54 +22,23 @@ import datetime as dt
 
 from .hamster import stuff
 from .hamster.stuff import format_duration, format_activity
-from tags import TagCellRenderer
+from tags import Tag
 
 import pango
 
 def parent_painter(column, cell, model, iter):
     fact = model.get_value(iter, 0)
-    parent_info = model.get_value(iter, 2)
+    parent_info = model.get_value(iter, 1)
 
     if fact is None:
-        if model.get_path(iter) == (0,):  # first row
-            text = '<span weight="heavy">%s</span>' % parent_info["label"]
-        else:
-            text = '<span weight="heavy" rise="-20000">%s</span>' % parent_info["label"]
-
-        cell.set_property('markup', text)
-
+        parent_info["first"] = model.get_path(iter) == (0,) # first row
+        cell.set_property('data', parent_info)
     else:
-        if fact["end_time"]:
-            fact_time = "%s - %s " % (fact["start_time"].strftime("%H:%M"),
-                                   fact["end_time"].strftime("%H:%M"))
-        else:
-            fact_time = fact["start_time"].strftime("%H:%M ")
-        
-
-        activity_name = stuff.escape_pango("%s %s" % (fact_time, fact["name"]))
-        description = stuff.escape_pango(fact["description"])
-        category = stuff.escape_pango(fact["category"])
-
-        markup = stuff.format_activity(activity_name,
-                                       category,
-                                       description,
-                                       pad_description = True)
-        cell.set_property('markup', markup)
-
-def duration_painter(column, cell, model, iter):
-    cell.set_property('xalign', 1)
-
-
-    text = model.get_value(iter, 1)
-    if model.get_value(iter, 0) is None:
-        if model.get_path(iter) == (0,):
-            text = '<span weight="heavy">%s</span>' % text
-        else:
-            text = '<span weight="heavy" rise="-20000">%s</span>' % text
-    cell.set_property('markup', text)
+        cell.set_property('data', fact)
 
 def action_painter(column, cell, model, iter):
     cell.set_property('xalign', 1)
+    cell.set_property('yalign', 0)
 
     if model.get_value(iter, 0) is None:
         cell.set_property("stock_id", "")
@@ -90,32 +59,15 @@ class FactTree(gtk.TreeView):
         self.set_show_expanders(False)
 
         # fact (None for parent), duration, parent data (if any)
-        self.store_model = gtk.TreeStore(gobject.TYPE_PYOBJECT, str, gobject.TYPE_PYOBJECT)
+        self.store_model = gtk.TreeStore(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)
         self.set_model(self.store_model)
 
 
-        # start time - end time, name, category
-        nameColumn = gtk.TreeViewColumn()
-        nameCell = gtk.CellRendererText()
-        #nameCell.set_property("ellipsize", pango.ELLIPSIZE_END)
-        nameColumn.pack_start(nameCell, True)
-        nameColumn.set_cell_data_func(nameCell, parent_painter)
-        self.append_column(nameColumn)
-
-        # tags
-        tag_cell = TagCellRenderer()
-        tag_cell.set_font_size(8);
-        tagColumn = gtk.TreeViewColumn("", tag_cell, data=0)
-        tagColumn.set_expand(True)
-        self.append_column(tagColumn)
-
-
-        # duration
-        timeColumn = gtk.TreeViewColumn()
-        timeCell = gtk.CellRendererText()
-        timeColumn.pack_end(timeCell, True)
-        timeColumn.set_cell_data_func(timeCell, duration_painter)
-        self.append_column(timeColumn)
+        fact_cell = FactCellRenderer()
+        fact_column = gtk.TreeViewColumn("", fact_cell, data=0)
+        fact_column.set_cell_data_func(fact_cell, parent_painter)
+        fact_column.set_expand(True)
+        self.append_column(fact_column)
 
         edit_cell = gtk.CellRendererPixbuf()
         edit_cell.set_property("mode", gtk.CELL_RENDERER_MODE_ACTIVATABLE)
@@ -126,35 +78,43 @@ class FactTree(gtk.TreeView):
         self.connect("row-activated", self._on_row_activated)
         self.connect("button-release-event", self._on_button_release_event)
         self.connect("key-press-event", self._on_key_pressed)
+        self.connect("configure-event", lambda *args: self.columns_autosize())
+
 
         self.show()
         
+        self.longest_activity_category = 0 # we will need this for the cell renderer
+        
+        
         self.stored_selection = None
 
+        self.box = None
+        
+        
+    def fix_row_heights(self):
+        alloc = self.get_allocation()
+        if alloc != self.box:
+            self.box = alloc
+            self.columns_autosize()
+        
     def clear(self):
         self.store_model.clear()
+        self.longest_activity_category = 0
 
     def add_fact(self, fact, parent = None):
-        duration = stuff.duration_minutes(fact["delta"]) / 60
+        self.longest_activity_category = max(self.longest_activity_category,
+                                             len(fact["name"]) + len(fact["category"]))
 
-        if fact["end_time"]:
-            fact_time = "%s - %s " % (fact["start_time"].strftime("%H:%M"),
-                                   fact["end_time"].strftime("%H:%M"))
-        else:
-            fact_time = fact["start_time"].strftime("%H:%M ")
-
-        self.store_model.append(parent, [fact,
-                                         stuff.format_duration(fact["delta"]),
-                                         None])
+        self.store_model.append(parent, [fact, None])
 
     def add_group(self, group_label, group_date, facts):
         total = sum([stuff.duration_minutes(fact["delta"]) for fact in facts])
 
         # adds group of facts with the given label
         group_row = self.store_model.append(None, [None,
-                                                   stuff.format_duration(total),
                                                    dict(date = group_date,
-                                                        label = group_label)])
+                                                        label = group_label,
+                                                        duration = total)])
 
         for fact in facts:
             self.add_fact(fact, group_row)
@@ -181,7 +141,7 @@ class FactTree(gtk.TreeView):
         selection = self.get_selection()
         (model, iter) = selection.get_selected()
         if iter:
-            return model[iter][0] or model[iter][2]["date"]
+            return model[iter][0] or model[iter][1]["date"]
         else:
             return None
 
@@ -212,3 +172,278 @@ class FactTree(gtk.TreeView):
             return True
 
         return False
+
+
+
+class FactCellRenderer(gtk.GenericCellRenderer):
+    """ We need all kinds of wrapping and spanning and the treeview just does
+        not cut it"""
+    
+    __gproperties__ = {
+        "data": (gobject.TYPE_PYOBJECT, "Data", "Data", gobject.PARAM_READWRITE),
+    }
+
+    def __init__(self):
+        gtk.GenericCellRenderer.__init__(self)
+        self.height = 0
+        self.data = None
+
+        default_font = gtk.Style().font_desc.to_string()
+        self.label_font = pango.FontDescription(default_font)
+        self.label_font_size = 10
+        
+        self.selected_color = gtk.Style().text[gtk.STATE_SELECTED]
+        self.normal_color = gtk.Style().text[gtk.STATE_NORMAL]
+
+        self.tag_font = pango.FontDescription(default_font)
+        self.tag_font.set_size(pango.SCALE * 8)
+
+        self.layout = None
+        
+        self.col_padding = 10
+        self.row_padding = 4
+        
+    def do_set_property (self, pspec, value):
+        setattr(self, pspec.name, value)
+
+    def do_get_property(self, pspec):
+        return getattr (self, pspec.name)
+
+
+    def set_text(self, text):
+        # sets text and returns width and height of the layout
+        self.layout.set_text(text)
+        w, h = self.layout.get_pixel_size()
+        return w, h
+
+    def on_render (self, window, widget, background_area, cell_area, expose_area, flags):
+        if not self.data:
+            return
+
+        """
+          ASCII Art
+          --------------+--------------------------------------------+-------+---+    
+          13:12 - 17:18 | Some activity - category, tag, tag, tag,   | 14:44 | E |
+                        | tag, tag, some description in grey italics |       |   |
+          --------------+--------------------------------------------+-------+---+    
+        """
+
+
+        context = window.cairo_create()
+        if not self.layout:
+            self.layout = context.create_layout()
+            self.layout.set_font_description(self.label_font)
+
+
+        if "id" in self.data:
+            fact, parent = self.data, None
+        else:
+            parent, fact = self.data, None
+
+
+
+
+        x, y, width, height = cell_area
+        context.translate(x, y)
+        
+        current_fact = widget.get_selected_fact()
+
+        if parent:
+            text_color = self.normal_color
+            # if we are selected, change font color appropriately
+            if current_fact and isinstance(current_fact, dt.date) \
+               and current_fact == parent["date"]:
+                text_color = self.selected_color
+                
+            self.set_color(context, text_color)
+
+            self.layout.set_markup("<b>%s</b>" % parent["label"])
+            if self.data["first"]:
+                y = 5
+            else:
+                y = 20
+
+            context.move_to(5, y)
+            context.show_layout(self.layout)
+
+            self.layout.set_markup("<b>%s</b>" % stuff.format_duration(parent["duration"]))
+            label_w, label_h = self.layout.get_pixel_size()
+
+            context.move_to(width - label_w, y)
+            context.show_layout(self.layout)
+            
+        else:
+            text_color = self.normal_color
+            selected = False
+            # if we are selected, change font color appropriately
+            if current_fact and isinstance(current_fact, dt.date) == False \
+               and current_fact["id"] == fact["id"]:
+                text_color = self.selected_color
+                selected = True
+               
+            
+            """ start time and end time at beginning of column """
+            interval = fact["start_time"].strftime("%H:%M")
+            if fact["end_time"]:
+                interval = "%s - %s" % (interval, fact["end_time"].strftime("%H:%M"))
+            
+            self.set_color(context, text_color)
+
+            self.layout.set_markup(interval)
+            context.move_to(self.col_padding, 2)
+            context.show_layout(self.layout)
+
+            """ duration at the end """
+            self.layout.set_markup(stuff.format_duration(fact["delta"]))
+            duration_w, duration_h = self.layout.get_pixel_size()
+            context.move_to(width - duration_w, 2)
+            context.show_layout(self.layout)
+            
+            """ activity, category, tags, description in middle """
+            # we want our columns look aligned, so we will do fixed offset from
+            # both sides, in letter length
+            self.layout.set_markup("8")
+            letter_w, letter_h = self.layout.get_pixel_size()
+            
+            box_x = letter_w * 14
+            box_w = width - letter_w * 15 - max(letter_w * 10, duration_w)
+
+            context.translate(box_x, 2)
+
+            context.move_to(0,0)
+            self.layout.set_markup(fact["name"])
+            label_w, label_h = self.layout.get_pixel_size()
+            self.set_color(context, text_color)
+            context.show_layout(self.layout)
+
+            context.move_to(label_w, 0)
+
+            if not selected:
+                self.set_color(context, widget.get_style().text[gtk.STATE_INSENSITIVE])
+            self.layout.set_markup(" - %s" % fact["category"])
+            label_w, label_h = self.layout.get_pixel_size()
+            context.show_layout(self.layout)
+            
+            act_cat_offset = (widget.longest_activity_category + 4) * letter_w
+            context.move_to(act_cat_offset, 0)
+            context.set_source_rgb(0,0,0)
+            
+            self.layout.set_font_description(self.tag_font)
+            cur_x, cur_y = act_cat_offset, 0
+            for i, tag in enumerate(fact["tags"]):
+                tag_w, tag_h = Tag.tag_size(tag, self.layout)
+                
+                if i > 0 and cur_x + tag_w >= box_w:
+                    cur_x = act_cat_offset
+                    cur_y += tag_h + 4
+                
+                Tag(context, self.layout, True, tag, None,
+                    gtk.gdk.Rectangle(cur_x, cur_y, box_w - cur_x, height - cur_y))
+                
+                cur_x += tag_w + 4
+
+            self.layout.set_font_description(self.label_font)
+
+            if fact["description"]:
+                self.layout.set_markup("<small><i>%s</i></small>" % fact["description"])
+                label_w, label_h = self.layout.get_pixel_size()
+                
+                x, y = cur_x, cur_y + 4
+                if cur_x + label_w > box_w:
+                    x = 0
+                    y = label_h + 4
+                
+                context.move_to(x, y)
+                self.layout.set_width((box_w - x) * pango.SCALE)
+                context.show_layout(self.layout)
+                self.layout.set_width(-1)
+                
+            self.layout.set_font_description(self.label_font)
+
+
+    def set_color(self, context, color):
+        context.set_source_rgba(*self.color_to_cairo_rgba(color))
+        
+    def color_to_cairo_rgba(self, c, a=1):
+        return c.red/65535.0, c.green/65535.0, c.blue/65535.0, a
+
+
+    def get_fact_size(self, widget):
+        #all we care for, is height
+        if not self.data or "id" not in self.data:
+            return None
+        fact = self.data
+
+        pixmap = gtk.gdk.Pixmap(None, 10, 10, 24)
+        context = pixmap.cairo_create()
+
+        layout = context.create_layout()
+        layout.set_font_description(self.label_font)
+
+
+        x, y, width, height = widget.get_allocation()
+
+
+        """ duration at the end """
+        layout.set_markup(stuff.format_duration(fact["delta"]))
+        duration_w, duration_h = layout.get_pixel_size()
+        
+        """ activity, category, tags, description in middle """
+        # we want our columns look aligned, so we will do fixed offset from
+        # both sides, in letter length
+        layout.set_markup("8")
+        letter_w, letter_h = layout.get_pixel_size()
+        
+        box_x = letter_w * 14
+        box_w = width - letter_w * 15 - max(letter_w * 14, duration_w)
+
+        act_cat_offset = (widget.longest_activity_category + 4) * letter_w
+
+        required_height = letter_h
+
+        if fact["tags"]:
+            layout.set_font_description(self.tag_font)
+            cur_x, cur_y = act_cat_offset, 0
+            for i, tag in enumerate(fact["tags"]):
+                tag_w, tag_h = Tag.tag_size(tag, layout)
+                
+                if i > 0 and cur_x + tag_w >= box_w:
+                    cur_x = act_cat_offset
+                    cur_y += tag_h + 4
+                
+                cur_x += tag_w + 4
+            
+            required_height = cur_y + tag_h + 4
+
+        layout.set_font_description(self.label_font)
+
+        if fact["description"]:
+            layout.set_markup("<small><i>%s</i></small>" % fact["description"])
+            label_w, label_h = layout.get_pixel_size()
+            
+            x, y = cur_x, cur_y + 4
+            if cur_x + label_w > box_w:
+                x = 0
+                y = label_h + 4
+            
+            layout.set_width((box_w - x) * pango.SCALE)
+            label_w, label_h = layout.get_pixel_size()
+
+
+            required_height = y + label_h
+
+        required_height = required_height + 6
+        return (0, 0, 0, required_height)
+
+
+    def on_get_size (self, widget, cell_area = None):
+        if "id" in self.data: # fact
+            return self.get_fact_size(widget)
+        else:
+            if self.data["first"]:
+                return (0, 0, 0, 25)
+            else:
+                return (0, 0, 0, 40)
+            
+
+
diff --git a/hamster/widgets/tags.py b/hamster/widgets/tags.py
index 945b393..b404999 100644
--- a/hamster/widgets/tags.py
+++ b/hamster/widgets/tags.py
@@ -307,133 +307,8 @@ class TagBox(graphics.Area):
             cur_x += w + 6 #some padding too, please
 
 
-# snatch from winedoors. Carl Lattimer is my hero as always
-# wish he could write tutorials
-class TagCellRenderer(gtk.GenericCellRenderer):
-    __gproperties__ = {
-        "data": (gobject.TYPE_PYOBJECT, "Data", "Data", gobject.PARAM_READWRITE),
-    }
-
-    def __init__(self):
-        gtk.GenericCellRenderer.__init__(self)
-        self.height = 0
-        self.width = None
-        self.data = None
-
-        self._font = pango.FontDescription(gtk.Style().font_desc.to_string())
-        self._font_size = 10
-        self.layout = None
-
-    def font_size(self):
-        return self._font_size
-
-    def set_font_size(self, val):
-        self._font_size = val
-        self._font.set_size(pango.SCALE * self._font_size)
-
-
-    def do_set_property (self, pspec, value):
-        setattr (self, pspec.name, value)
-
-    def do_get_property (self, pspec):
-        return getattr (self, pspec.name)
-
-
-    def tag_size(self, label):
-        text_w, text_h = self.set_text(label)
-        w = text_w + 16 # padding (we have some diagonals to draw)
-        h = text_h + 2
-        return w, h
-
-    def set_text(self, text):
-        # sets text and returns width and height of the layout
-        self.layout.set_text(text)
-        w, h = self.layout.get_pixel_size()
-        return w, h
-
-    def on_render (self, window, widget, background_area, cell_area, expose_area, flags):
-        if not self.data: return
-
-        self.width = cell_area.width
-
-        context = window.cairo_create()
-
-        if isinstance(self.data, dict):
-            tags = self.data["tags"]
-        else:
-            tags = self.data
-
-        x, y, width, h = cell_area
-
-        context.translate(x, y)
-
-        if not self.layout:
-            self.layout = context.create_layout()
-        self.layout.set_font_description(self._font)
-
-        cur_x, cur_y = 4, 2
-        for tag in tags:
-            w, h = self.tag_size(tag)
-            if cur_x + w >= self.width - 5:  #if we do not fit, we wrap
-                cur_x = 5
-                cur_y += h + 6
-
-            Tag(context,
-                self.layout,
-                True,
-                tag,
-                None,
-                gtk.gdk.Rectangle(cur_x, cur_y, self.width - cur_x, self.height - cur_y))
-
-
-            cur_x += w + 6 #some padding too, please
-
-            self.height = cur_y
-
-    def on_get_size (self, widget, cell_area = None):
-        # TODO - we are replicating rendering code - should reuse it instead
-        if isinstance(self.data, dict):
-            tags = self.data["tags"]
-        else:
-            tags = self.data
-
-        if not self.width or not tags:
-            height = 0
-            min_width = 80
-        else:
-            pixmap = gtk.gdk.Pixmap(None, self.width, 500, 24)
-            context = pixmap.cairo_create()
-            self.layout = context.create_layout()
-            default_font = pango.FontDescription(gtk.Style().font_desc.to_string())
-            default_font.set_size(pango.SCALE * self.font_size())
-            self.layout.set_font_description(default_font)
-
-            #make sure we fit in
-            min_width = 0
-            for tag in tags:
-                min_width = max(min_width, self.tag_size(tag)[0]) + 6
-
-
-            cur_x, cur_y = 4, 2
-            for tag in tags:
-                w, h = self.tag_size(tag)
-                if cur_x + w >= self.width - 5:  #if we do not fit, we wrap
-                    cur_x = 5
-                    cur_y += h + 6
-
-                cur_x += w + 6 #some padding too, please
-
-            cur_y += h + 3
-
-            self.height = cur_y # TODO - this should actually trigger whole tree redraw if heights do not match
-
-        return (0, 0, min_width, self.height)
-
-
 class Tag(object):
     def __init__(self, context, layout, render_now = False, label = None, color = None, rect = None):
-        self.font_size = 10
-
         if not context:
             render_now = False
         else:
@@ -469,22 +344,21 @@ class Tag(object):
         w, h = self.layout.get_pixel_size()
         return w, h
 
-    def tag_size(self, label):
-        text_w, text_h = self.set_text(label)
+    @staticmethod
+    def tag_size(label, layout):
+        layout.set_text(label)
+        text_w, text_h = layout.get_pixel_size()
         w = text_w + 16 # padding (we have some diagonals to draw)
         h = text_h + 2
         return w, h
 
     def draw_tag(self):
         self.context.set_line_width(1)
-        self.context.set_antialias(cairo.ANTIALIAS_NONE)
-
-        self.context.set_antialias(cairo.ANTIALIAS_DEFAULT)
         label, x, y, color = self.label, self.x, self.y, self.color
         if x - round(x) != 0.5: x += 0.5
         if y - round(y) != 0.5: y += 0.5
 
-        w, h = self.tag_size(label)
+        w, h = self.tag_size(label, self.layout)
         corner = h / 3
 
         self.context.move_to(x, y + corner)



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]