[hamster-applet] more extracted widgets



commit f59581d5a1289c006bd7e356465d5eb46e7f37ff
Author: Toms Bauģis <toms baugis gmail com>
Date:   Thu Nov 19 16:56:29 2009 +0000

    more extracted widgets

 hamster/edit_activity.py    |  319 +------------------------------------------
 hamster/stats.py            |  297 +---------------------------------------
 hamster/widgets/Makefile.am |    5 +-
 hamster/widgets/__init__.py |    5 +
 4 files changed, 12 insertions(+), 614 deletions(-)
---
diff --git a/hamster/edit_activity.py b/hamster/edit_activity.py
index d651307..cc475c0 100644
--- a/hamster/edit_activity.py
+++ b/hamster/edit_activity.py
@@ -43,323 +43,6 @@ import cairo, pango
      * hook into notifications and refresh our days if some evil neighbour edit
        fact window has dared to edit facts
 """
-class Dayline(graphics.Area):
-    def __init__(self):
-        graphics.Area.__init__(self)
-
-        self.set_events(gtk.gdk.EXPOSURE_MASK
-                                 | gtk.gdk.LEAVE_NOTIFY_MASK
-                                 | gtk.gdk.BUTTON_PRESS_MASK
-                                 | gtk.gdk.BUTTON_RELEASE_MASK
-                                 | gtk.gdk.POINTER_MOTION_MASK
-                                 | gtk.gdk.POINTER_MOTION_HINT_MASK)
-        self.connect("button_release_event", self.on_button_release)
-        self.connect("motion_notify_event", self.draw_cursor)
-        self.highlight_start, self.highlight_end = None, None
-        self.drag_start = None
-        self.move_type = ""
-        self.on_time_changed = None #override this with your func to get notified when user changes date
-        self.on_more_data = None #supplement with more data func that accepts single date
-        self.in_progress = False
-
-        self.range_start = None
-        self.in_motion = False
-        self.days = []
-        
-
-    def draw(self, day_facts, highlight = None):
-        """Draw chart with given data"""
-        self.facts = day_facts
-        if self.facts:
-            self.days.append(self.facts[0]["start_time"].date())
-        
-        start_time = highlight[0] - dt.timedelta(minutes = highlight[0].minute) - dt.timedelta(hours = 10)
-        
-        if self.range_start:
-            self.range_start.target(start_time)
-            self.scroll_to_range_start()
-        else:
-            self.range_start = graphics.Integrator(start_time, damping = 0.35, attraction = 0.5)
-
-        self.highlight = highlight
-        
-        self.show()
-        
-        self.redraw_canvas()
-
-
-    def on_button_release(self, area, event):
-        if not self.drag_start:
-            return
-        
-        self.drag_start, self.move_type = None, None
-
-        if event.state & gtk.gdk.BUTTON1_MASK:
-            self.__call_parent_time_changed()
-
-    def set_in_progress(self, in_progress):
-        self.in_progress = in_progress
-
-    def __call_parent_time_changed(self):
-        #now calculate back from pixels into minutes
-        start_time = self.highlight[0]
-        end_time = self.highlight[1]
-
-        if self.on_time_changed:
-            self.on_time_changed(start_time, end_time)
-    
-    def get_time(self, pixels):
-        minutes = self.get_value_at_pos(x = pixels)
-        return self.range_start.value + dt.timedelta(minutes = minutes) 
-    
-    def scroll_to_range_start(self):
-        if not self.in_motion:
-            self.in_motion = True
-            gobject.timeout_add(1000 / 30, self.animate_scale)
-        
-        
-    def animate_scale(self):
-        moving = self.range_start.update() > 5
-        
-        
-        # check if maybe we are approaching day boundaries and should ask for
-        # more data!
-        if self.on_more_data:
-            now = self.range_start.value
-            date_plus = (now + dt.timedelta(hours = 12 + 2*4 + 1)).date()
-            date_minus = (now - dt.timedelta(hours=1)).date()
-
-            if date_minus != now.date() and date_minus not in self.days:
-                self.facts += self.on_more_data(date_minus)
-                self.days.append(date_minus)
-            elif date_plus != now.date() and date_plus not in self.days:
-                self.facts += self.on_more_data(date_plus)
-                self.days.append(date_plus)
-        
-        
-        self.redraw_canvas()
-        if moving:
-            return True
-        else:
-            self.in_motion = False
-            return False
-
-
-        
-    def draw_cursor(self, area, event):
-        if event.is_hint:
-            x, y, state = event.window.get_pointer()
-        else:
-            x = event.x
-            y = event.y
-            state = event.state
-
-        mouse_down = state & gtk.gdk.BUTTON1_MASK
-            
-        #print x, self.highlight_start, self.highlight_end
-        if self.highlight_start != None:
-            start_drag = 10 > (self.highlight_start - x) > -1
-
-            end_drag = 10 > (x - self.highlight_end) > -1
-
-            if start_drag and end_drag:
-                start_drag = abs(x - self.highlight_start) < abs(x - self.highlight_end)
-
-            in_between = self.highlight_start <= x <= self.highlight_end
-            scale = True
-
-            if self.in_progress:
-                end_drag = False
-                in_between = False
-
-            if mouse_down and not self.drag_start:
-                self.drag_start = x
-                if start_drag:
-                    self.move_type = "start"
-                elif end_drag:
-                    self.move_type = "end"
-                elif in_between:
-                    self.move_type = "move"
-                    self.drag_start = x - self.highlight_start
-                elif scale:
-                    self.move_type = "scale_drag"
-                    self.drag_start_time = self.range_start.value
-
-            
-            if mouse_down and self.drag_start:
-                start, end = 0, 0
-                if self.move_type and self.move_type != "scale_drag":
-                    if self.move_type == "start":
-                        if 0 <= x <= self.width:
-                            start = x
-                            end = self.highlight_end
-                    elif self.move_type == "end":
-                        if 0 <= x <= self.width:
-                            start = self.highlight_start
-                            end = x
-                    elif self.move_type == "move":
-                        width = self.highlight_end - self.highlight_start
-                        start = x - self.drag_start
-                        start = max(0, min(start, self.width))
-                        
-                        end = start + width
-                        if end > self.width:
-                            end = self.width
-                            start = end - width
-    
-                    if end - start > 1:
-                        self.highlight = (self.get_time(start), self.get_time(end))
-                        self.redraw_canvas()
-
-                    self.__call_parent_time_changed()
-                else:
-                    self.range_start.target(self.drag_start_time +
-                                            dt.timedelta(minutes = self.get_value_at_pos(x = self.drag_start) - self.get_value_at_pos(x = x)))
-                    self.scroll_to_range_start()
-
-
-
-            if start_drag:
-                area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_SIDE))
-            elif end_drag:
-                area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.RIGHT_SIDE))
-            elif in_between:
-                area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
-            else:
-                area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
-                
-        
-    def _minutes_from_start(self, date):
-            delta = (date - self.range_start.value)
-            return delta.days * 24 * 60 + delta.seconds / 60
-            
-    def _render(self):
-        context = self.context
-        #TODO - use system colors and fonts
- 
-        context.set_line_width(1)
-
-        #we will buffer 4 hours to both sides so partial labels also appear
-        range_end = self.range_start.value + dt.timedelta(hours = 12 + 2 * 4)        
-        self.graph_x = -self.width / 3 #so x moves one third out of screen
-        self.set_value_range(x_min = 0, x_max = 12 * 60)
-
-        minutes = self._minutes_from_start(range_end)
-
-
-
-        graph_y = 4
-        graph_height = self.height - 10
-        graph_y2 = graph_y + graph_height
-
-        
-        # graph area
-        self.fill_area(0, graph_y - 1, self.width, graph_height, (1,1,1))
-    
-        #bars
-        for fact in self.facts:
-            start_minutes = self._minutes_from_start(fact["start_time"])
-            
-            if fact["end_time"]:
-                end_minutes = self._minutes_from_start(fact["end_time"])
-            else:
-                if fact["start_time"].date() > dt.date.today() - dt.timedelta(days=1):
-                    end_minutes = self._minutes_from_start(dt.datetime.now())
-                else:
-                    end_minutes = start_minutes
-            
-            if self.get_pixel(end_minutes) > 0 and \
-                self.get_pixel(start_minutes) < self.width:
-                    context.set_source_rgba(0.86, 0.86, 0.86, 0.5)
-
-                    context.rectangle(round(self.get_pixel(start_minutes)),
-                                      graph_y,
-                                      round(self.get_pixel(end_minutes) - self.get_pixel(start_minutes)),
-                                      graph_height - 1)
-                    context.fill()
-                    context.stroke()
-
-                    context.set_source_rgba(0.86, 0.86, 0.86, 1)
-                    self.move_to(start_minutes, graph_y)
-                    self.line_to(start_minutes, graph_y2)
-                    self.move_to(end_minutes, graph_y)
-                    self.line_to(end_minutes, graph_y2)
-                    context.stroke()
-
-        
-        
-        #time scale
-        context.set_source_rgb(0, 0, 0)
-        self.layout.set_width(-1)
-        for i in range(minutes):
-            label_time = (self.range_start.value + dt.timedelta(minutes=i))
-            
-            if label_time.minute == 0:
-                context.set_source_rgb(0.8, 0.8, 0.8)
-                self.move_to(i, graph_y2 - 15)
-                self.line_to(i, graph_y2)
-                context.stroke()
-            elif label_time.minute % 15 == 0:
-                context.set_source_rgb(0.8, 0.8, 0.8)
-                self.move_to(i, graph_y2 - 5)
-                self.line_to(i, graph_y2)
-                context.stroke()
-                
-                
-                
-            if label_time.minute == 0 and label_time.hour % 2 == 0:
-                if label_time.hour == 0:
-                    context.set_source_rgb(0.8, 0.8, 0.8)
-                    self.move_to(i, graph_y)
-                    self.line_to(i, graph_y2)
-                    label_minutes = label_time.strftime("%b %d")
-                else:
-                    label_minutes = label_time.strftime("%H<small><sup>%M</sup></small>")
-
-                context.set_source_rgb(0.4, 0.4, 0.4)
-                self.layout.set_markup(label_minutes)
-                label_w, label_h = self.layout.get_pixel_size()
-                
-                context.move_to(self.get_pixel(i) + 2, graph_y2 - label_h - 8)                
-
-                context.show_layout(self.layout)
-        context.stroke()
-        
-        #highlight rectangle
-        if self.highlight:
-            self.highlight_start = self.get_pixel(self._minutes_from_start(self.highlight[0]))
-            self.highlight_end = self.get_pixel(self._minutes_from_start(self.highlight[1]))
-
-        #TODO - make a proper range check here
-        if self.highlight_end > 0 and self.highlight_start < self.width:
-            rgb = colorsys.hls_to_rgb(.6, .7, .5)
-
-
-            self.fill_area(self.highlight_start, graph_y,
-                           self.highlight_end - self.highlight_start, graph_height,
-                           (rgb[0], rgb[1], rgb[2], 0.5))
-            context.stroke()
-
-            context.set_source_rgb(*rgb)
-            self.context.move_to(self.highlight_start, graph_y)
-            self.context.line_to(self.highlight_start, graph_y + graph_height)
-            self.context.move_to(self.highlight_end, graph_y)
-            self.context.line_to(self.highlight_end, graph_y + graph_height)
-            context.stroke()
-
-        #and now put a frame around the whole thing
-        context.set_source_rgb(0.7, 0.7, 0.7)
-        context.rectangle(0, graph_y-1, self.width - 1, graph_height)
-        context.stroke()
-        
-        if self.move_type == "move" and (self.highlight_start == 0 or self.highlight_end == self.width):
-            if self.highlight_start == 0:
-                self.range_start.target(self.range_start.value - dt.timedelta(minutes=30))
-            if self.highlight_end == self.width:
-                self.range_start.target(self.range_start.value + dt.timedelta(minutes=30))
-            self.scroll_to_range_start()
-
-
 
 class CustomFactController:
     def __init__(self,  parent = None, fact_date = None, fact_id = None):
@@ -434,7 +117,7 @@ class CustomFactController:
         self.set_dropdown()
         self.refresh_menu()
 
-        self.dayline = Dayline()
+        self.dayline = widgets.DayLine()
         self.dayline.on_time_changed = self.update_time
         self.dayline.on_more_data = runtime.storage.get_facts
         self._gui.get_object("day_preview").add(self.dayline)
diff --git a/hamster/stats.py b/hamster/stats.py
index dfc449e..b4216cc 100644
--- a/hamster/stats.py
+++ b/hamster/stats.py
@@ -44,299 +44,6 @@ import calendar
 import time
 from hamster.i18n import C_
 
-class ReportChooserDialog(gtk.Dialog):
-    __gsignals__ = {
-        # format, path, start_date, end_date
-        'report-chosen': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
-                          (gobject.TYPE_STRING, gobject.TYPE_STRING,
-                           gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
-                           gobject.TYPE_PYOBJECT)),
-        'report-chooser-closed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
-    }
-    def __init__(self):
-        gtk.Dialog.__init__(self)
-        ui = stuff.load_ui_file("stats.ui")
-        self.dialog = ui.get_object('save_report_dialog')
-
-        self.dialog.set_action(gtk.FILE_CHOOSER_ACTION_SAVE)
-        self.dialog.set_current_folder(os.path.expanduser("~"))
-
-        self.filters = {}
-
-        filter = gtk.FileFilter()
-        filter.set_name(_("HTML Report"))
-        filter.add_mime_type("text/html")
-        filter.add_pattern("*.html")
-        filter.add_pattern("*.htm")
-        self.filters[filter] = "html"
-        self.dialog.add_filter(filter)
-
-        filter = gtk.FileFilter()
-        filter.set_name(_("Tab-Separated Values (TSV)"))
-        filter.add_mime_type("text/plain")
-        filter.add_pattern("*.tsv")
-        filter.add_pattern("*.txt")
-        self.filters[filter] = "tsv"
-        self.dialog.add_filter(filter)
-
-        filter = gtk.FileFilter()
-        filter.set_name(_("XML"))
-        filter.add_mime_type("text/xml")
-        filter.add_pattern("*.xml")
-        self.filters[filter] = "xml"
-        self.dialog.add_filter(filter)
-
-        filter = gtk.FileFilter()
-        filter.set_name(_("iCal"))
-        filter.add_mime_type("text/calendar")
-        filter.add_pattern("*.ics")
-        self.filters[filter] = "ical"
-        self.dialog.add_filter(filter)
-
-        filter = gtk.FileFilter()
-        filter.set_name("All files")
-        filter.add_pattern("*")
-        self.dialog.add_filter(filter)
-        
-        self.start_date = widgets.DateInput()
-        ui.get_object('from_date_box').add(self.start_date)
-        self.end_date = widgets.DateInput()
-        ui.get_object('to_date_box').add(self.end_date)
-
-        self.category_box = ui.get_object('category_box')
-
-        ui.get_object('save_button').connect("clicked", self.on_save_button_clicked)
-        ui.get_object('cancel_button').connect("clicked", self.on_cancel_button_clicked)
-        
-
-    def show(self, start_date, end_date):
-        #set suggested name to something readable, replace backslashes with dots
-        #so the name is valid in linux
-        filename = "Time track %s - %s." % (start_date.strftime("%x").replace("/", "."),
-                                           end_date.strftime("%x").replace("/", "."))
-        self.dialog.set_current_name(filename)
-        
-        self.start_date.set_date(start_date)
-        self.end_date.set_date(end_date)
-        
-        #add unsorted category
-        button_all = gtk.CheckButton(C_("categories", "All").encode("utf-8"))
-        button_all.value = None
-        button_all.set_active(True)
-        
-        def on_category_all_clicked(checkbox):
-            active = checkbox.get_active()
-            for checkbox in self.category_box.get_children():
-                checkbox.set_active(active)
-        
-        button_all.connect("clicked", on_category_all_clicked)
-        self.category_box.attach(button_all, 0, 1, 0, 1)
-
-        categories = runtime.storage.get_category_list()
-        col, row = 0, 0
-        for category in categories:
-            col +=1
-            if col % 4 == 0:
-                col = 0
-                row +=1
-
-            button = gtk.CheckButton(category['name'].encode("utf-8"))
-            button.value = category['id']
-            button.set_active(True)
-            self.category_box.attach(button, col, col+1, row, row+1)
-
-        
-
-        response = self.dialog.show_all()
-
-    def present(self):
-        self.dialog.present()
-
-    def on_save_button_clicked(self, widget):
-        path, format = None,  None
-
-        format = "html"
-        if self.dialog.get_filter() in self.filters:
-            format = self.filters[self.dialog.get_filter()]
-        path = self.dialog.get_filename()
-        
-        # append correct extension if it is missing
-        # TODO - proper way would be to change extension on filter change
-        # only pointer in web is http://www.mail-archive.com/pygtk daa com au/msg08740.html
-        if path.endswith(".%s" % format) == False:
-            path = "%s.%s" % (path.rstrip("."), format)
-        
-        categories = []
-        for button in self.category_box.get_children():
-            if button.get_active():
-                categories.append(button.value)
-        
-        if None in categories:
-            categories = None # nothing is everything
-        
-        # format, path, start_date, end_date
-        self.emit("report-chosen", format, path,
-                           self.start_date.get_date().date(),
-                           self.end_date.get_date().date(),
-                           categories)
-        self.dialog.destroy()
-        
-
-    def on_cancel_button_clicked(self, widget):
-        self.emit("report-chooser-closed")
-        self.dialog.destroy()
-
-class TimeLine(graphics.Area):
-    MODE_YEAR = 0
-    MODE_MONTH = 1
-    MODE_WEEK = 1
-    MODE_DAY = 3
-    def __init__(self):
-        graphics.Area.__init__(self)
-        self.start_date, self.end_date = None, None
-        self.draw_mode = None
-        self.max_hours = None
-        
-    
-    def draw(self, facts):
-        import itertools
-        self.facts = {}
-        for date, date_facts in itertools.groupby(facts, lambda x: x["start_time"].date()):
-            date_facts = list(date_facts)
-            self.facts[date] = date_facts
-            self.max_hours = max(self.max_hours,
-                                 sum([fact["delta"].seconds / 60 / float(60) +
-                               fact["delta"].days * 24 for fact in date_facts]))
-        
-        start_date = facts[0]["start_time"].date()
-        end_date = facts[-1]["start_time"].date()
-
-        self.draw_mode = self.MODE_YEAR
-        self.start_date = start_date.replace(month=1, day=1)
-        self.end_date = end_date.replace(month=12, day=31)
-        
-
-        """
-        #TODO - for now we have only the year mode        
-        if start_date.year != end_date.year or start_date.month != end_date.month:
-            self.draw_mode = self.MODE_YEAR
-            self.start_date = start_date.replace(month=1, day=1)
-            self.end_date = end_date.replace(month=12, day=31)
-        elif start_date.strftime("%W") != end_date.strftime("%W"):
-            self.draw_mode = self.MODE_MONTH
-            self.start_date = start_date.replace(day=1)
-            self.end_date = end_date.replace(date =
-                                    calendar.monthrange(self.end_date.year,
-                                                        self.end_date.month)[1])
-        elif start_date != end_date:
-            self.draw_mode = self.MODE_WEEK
-        else:
-            self.draw_mode = self.MODE_DAY
-        """
-        
-        self.redraw_canvas()
-        
-        
-    def _render(self):
-        import calendar
-        
-        if self.draw_mode != self.MODE_YEAR:
-            return
-
-        self.fill_area(0, 0, self.width, self.height, (0.975,0.975,0.975))
-        self.set_color((100,100,100))
-
-        self.set_value_range(x_min = 1, x_max = (self.end_date - self.start_date).days)        
-        month_label_fits = True
-        for month in range(1, 13):
-            self.layout.set_text(calendar.month_abbr[month])
-            label_w, label_h = self.layout.get_pixel_size()
-            if label_w * 2 > self.x_factor * 30:
-                month_label_fits = False
-                break
-        
-        
-        ticker_date = self.start_date
-        
-        year_pos = 0
-        
-        for year in range(self.start_date.year, self.end_date.year + 1):
-            #due to how things lay over, we are putting labels on backwards, so that they don't overlap
-            
-            self.context.set_line_width(1)
-            for month in range(1, 13):
-                for day in range(1, calendar.monthrange(year, month)[1] + 1):
-                    ticker_pos = year_pos + ticker_date.timetuple().tm_yday
-                    
-                    #if ticker_date.weekday() in [0, 6]:
-                    #    self.fill_area(ticker_pos * self.x_factor + 1, 20, self.x_factor, self.height - 20, (240, 240, 240))
-                    #    self.context.stroke()
-                        
-    
-                    if self.x_factor > 5:
-                        self.move_to(ticker_pos, self.height - 20)
-                        self.line_to(ticker_pos, self.height)
-                   
-                        self.layout.set_text(ticker_date.strftime("%d"))
-                        label_w, label_h = self.layout.get_pixel_size()
-                        
-                        if label_w < self.x_factor / 1.2: #if label fits
-                            self.context.move_to(self.get_pixel(ticker_pos) + 2,
-                                                 self.height - 20)
-                            self.context.show_layout(self.layout)
-                    
-                        self.context.stroke()
-                        
-                    #now facts
-                    facts_today = self.facts.get(ticker_date, [])
-                    if facts_today:
-                        total_length = dt.timedelta()
-                        for fact in facts_today:
-                            total_length += fact["delta"]
-                        total_length = total_length.seconds / 60 / 60.0 + total_length.days * 24
-                        total_length = total_length / float(self.max_hours) * self.height - 16
-
-                        self.fill_area(round(ticker_pos * self.x_factor),
-                                       round(self.height - total_length),
-                                       round(self.x_factor),
-                                       round(total_length),
-                                       (190,190,190))
-
-
-                        
-
-                    ticker_date += dt.timedelta(1)
-                
-            
-                
-                if month_label_fits:
-                    #roll back a little
-                    month_pos = ticker_pos - calendar.monthrange(year, month)[1] + 1
-
-                    self.move_to(month_pos, 0)
-                    #self.line_to(month_pos, 20)
-                    
-                    self.layout.set_text(dt.date(year, month, 1).strftime("%b"))
-    
-                    self.move_to(month_pos, 0)
-                    self.context.show_layout(self.layout)
-
-
-    
-            
-    
-            self.layout.set_text("%d" % year)
-            label_w, label_h = self.layout.get_pixel_size()
-                        
-            self.move_to(year_pos + 2 / self.x_factor, month_label_fits * label_h * 1.2)
-    
-            self.context.show_layout(self.layout)
-            
-            self.context.stroke()
-
-            year_pos = ticker_pos #save current state for next year
-
-
 
 class StatsViewer(object):
     def __init__(self, parent = None):
@@ -423,7 +130,7 @@ class StatsViewer(object):
         self._gui.connect_signals(self)
         self.fact_tree.grab_focus()
 
-        self.timeline = TimeLine()
+        self.timeline = widgets.TimeLine()
         self.get_widget("explore_everything").add(self.timeline)
         self.get_widget("explore_everything").show_all()
 
@@ -1335,7 +1042,7 @@ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent))
         
     def on_report_button_clicked(self, widget):
         if not self.report_chooser:
-            self.report_chooser = ReportChooserDialog()
+            self.report_chooser = widgets.ReportChooserDialog()
             self.report_chooser.connect("report-chosen", self.on_report_chosen)
             self.report_chooser.connect("report-chooser-closed",
                                         self.on_report_chooser_closed)
diff --git a/hamster/widgets/Makefile.am b/hamster/widgets/Makefile.am
index d422ee0..11a663e 100644
--- a/hamster/widgets/Makefile.am
+++ b/hamster/widgets/Makefile.am
@@ -3,7 +3,10 @@ hamster_PYTHON = \
 	__init__.py \
 	activityentry.py \
 	timeinput.py \
-	dateinput.py
+	dateinput.py \
+	dayline.py \
+	timeline.py \
+	reportchooserdialog.py \
 
 clean-local:
 	rm -rf *.pyc *.pyo
diff --git a/hamster/widgets/__init__.py b/hamster/widgets/__init__.py
index b927f2d..644633c 100644
--- a/hamster/widgets/__init__.py
+++ b/hamster/widgets/__init__.py
@@ -25,6 +25,11 @@ from activityentry import ActivityEntry
 from dateinput import DateInput
 from timeinput import TimeInput
 
+from timeline import TimeLine
+from dayline import DayLine
+
+from reportchooserdialog import ReportChooserDialog
+
 # handy wrappers
 def add_hint(entry, hint):
     entry.hint = hint        



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