[billreminder: 2/10] New timeline widget
- From: Og B. Maciel <ogmaciel src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [billreminder: 2/10] New timeline widget
- Date: Fri, 17 Jun 2011 04:10:43 +0000 (UTC)
commit c8b5cb5ffb65db346a9389b594e036e1cf75a4ab
Author: Luiz Armesto <luiz armesto gmail com>
Date: Thu Jun 16 20:15:44 2011 -0300
New timeline widget
src/gui/maindialog.py | 61 ++-
src/gui/widgets/timeline.py | 1603 ++++++++++++++++++++-----------------------
src/lib/Settings.py | 1 +
3 files changed, 804 insertions(+), 861 deletions(-)
---
diff --git a/src/gui/maindialog.py b/src/gui/maindialog.py
index dc71051..bce26bf 100644
--- a/src/gui/maindialog.py
+++ b/src/gui/maindialog.py
@@ -16,7 +16,7 @@ from gui.widgets.statusbar import Statusbar
from gui.widgets.viewbill import ViewBill as ViewBill
from gui.widgets.trayicon import NotifyIcon
from gui.widgets.chartwidget import ChartWidget
-from gui.widgets.timeline import Timeline, Bullet
+from gui.widgets.timeline import Timeline, Event
# Import data model modules
from lib.actions import Actions
@@ -79,9 +79,14 @@ class MainDialog:
self.statusbar = Statusbar()
self.ui.get_object("statusbar_box").add(self.statusbar)
+ # Restore timeline zoom
+ timeline_count = self.gconf_client.get('timeline_count')
+
# Timeline
- self.timeline = Timeline(callback=self.on_timeline_cb)
+ self.timeline = Timeline(count=timeline_count,
+ callback=self.on_timeline_cb)
self.timeline.connect("value-changed", self._on_timeline_changed)
+ self.timeline.connect("cleared", self._on_timeline_cleared)
self.ui.get_object("timeline_box").add(self.timeline)
# Chart
@@ -346,6 +351,7 @@ class MainDialog:
def _quit_application(self):
self.save_position()
self.save_size()
+ self.save_timeline_zoom()
gtk.main_quit()
return False
@@ -359,6 +365,10 @@ class MainDialog:
self.gconf_client.set('window_width', width)
self.gconf_client.set('window_height', height)
+ def save_timeline_zoom(self):
+ count = self.timeline.count
+ self.gconf_client.set('timeline_count', count)
+
def toggle_buttons(self, paid=None):
""" Toggles all buttons conform number of records present and
their state """
@@ -456,6 +466,8 @@ class MainDialog:
def _on_timeline_changed(self, widget, args):
self.reloadTreeView()
+ def _on_timeline_cleared(self, widget, args):
+ self._bullet_cache = {}
def switch_view(self, view_number):
self.gconf_client.set('show_paid_bills', view_number)
@@ -487,42 +499,55 @@ class MainDialog:
self._bullet_cache = {}
self.timeline.refresh()
- def on_timeline_cb(self, date, display_type):
+ def on_timeline_cb(self, date, display_type, force=False):
# TODO: Improve tooltip
# TODO: Improve cache
- if not date in self._bullet_cache.keys():
- self._bullet_cache[date] = self.actions.get_bills(dueDate=date)
+ if date in self._bullet_cache.keys() and not force:
+ return None
+ self._bullet_cache[date] = self.actions.get_bills(dueDate=date)
if self._bullet_cache[date]:
status = self.gconf_client.get('show_paid_bills')
amount = 0
+ amount_not_paid = 0
tooltip = ''
- bullet = Bullet()
+ bullet = Event()
bullet.date = date
for bill in self._bullet_cache[date]:
amount += bill.amount
if tooltip:
tooltip += '\n'
- tooltip += bill.payee + '\n' + str(float_to_currency(bill.amount))
- if bill.notes:
- tooltip += '\n' + bill.notes
+ tooltip += bill.payee + ' (' + str(float_to_currency(bill.amount)) + ')'
if bill.paid:
- if status == 0: return False
+ tooltip += ' [%s]' % _('Paid')
+
+ if status == 0:
+ return False
bullet.status = bullet.status | bullet.PAID
- elif date <= datetime.date.today():
- if status == 1: return False
- bullet.status = bullet.status | bullet.OVERDUE
else:
- if status == 1: return False
- bullet.status = bullet.status | bullet.TO_BE_PAID
+ amount_not_paid += bill.amount
+ if date <= datetime.date.today():
+ if status == 1:
+ return False
+ bullet.status = bullet.status | bullet.OVERDUE
+ else:
+ if status == 1:
+ return False
+ bullet.status = bullet.status | bullet.TO_BE_PAID
+
+ if bill.notes:
+ tooltip += ' - ' + bill.notes
- bullet.amountDue = amount
+ bullet.amountdue = amount_not_paid if amount_not_paid else amount
+ bullet.payee = bill.payee
- if len(self._bullet_cache[date]) > 1:
- bullet.multi = True
+ bills = len(self._bullet_cache[date])
+ if bills > 1:
+ bullet.multi = bills
+ bullet.payee = '%d bills' % bills
bullet.tooltip = tooltip
return bullet
diff --git a/src/gui/widgets/timeline.py b/src/gui/widgets/timeline.py
index 347ab77..3c9af1b 100644
--- a/src/gui/widgets/timeline.py
+++ b/src/gui/widgets/timeline.py
@@ -1,919 +1,836 @@
#!/usr/bin/env python
#-*- coding:utf-8 -*-
+import datetime
+import locale
+from math import pi, floor
+
import pygtk
-pygtk.require('2.0')
+pygtk.require("2.0")
import gtk
import gobject
import pango
-import datetime
-from math import pi, floor
-import warnings
+import cairo
-debug = False
+import graphics
+from pytweener import Easing
-class Bullet(object):
+class Event(graphics.Sprite):
OVERDUE = int('001', 2)
TO_BE_PAID = int('010', 2)
PAID = int('100', 2)
- debug = False
+ def __init__(self, date=None, radius=16, fill='#f00', stroke='#fff',
+ line_width=1, amountdue=0, payee='', estimated=False,
+ overthreshold=False, status=0, multi=0, tooltip='',
+ **kwargs):
+ graphics.Sprite.__init__(self, **kwargs)
- def __init__(self, date=None, amountDue=None, estimated=False, status=0,
- overthreshold=False, multi=False, tooltip=''):
- self.date = date
- self.amountDue = amountDue
+ if date:
+ self.date = date
+ else:
+ self.date = datetime.date.today()
+
+ self.radius = radius
+ self.fill = fill
+ self.stroke = stroke
+ self.line_width = line_width
+
+ self.amountdue = amountdue
+ self.payee = payee
self.estimated = estimated
- self.status = status
self.overthreshold = overthreshold
+ self.status = status
self.multi = multi
self.tooltip = tooltip
- if self.debug:
- print "Bullet created: ", self.date
-
-class Timeline(gtk.DrawingArea):
- """ A widget that displays a timeline and allows the user to select a
- date interval
- """
- DAY, WEEK, MONTH, YEAR = range(4)
- DIVS = {
- DAY: 16,
- WEEK: 16,
- MONTH: 24,
- YEAR: 10
- }
- debug = False
+ self.bullet_color = None
+
+ self.rectangle = graphics.Rectangle(1, 1, 5,
+ stroke='#fff',
+ interactive=True)
+ self.rectangle.connect("on-mouse-over", self.on_mouse_over)
+ self.rectangle.connect("on-mouse-out", self.on_mouse_out)
+ self.rectangle.connect("on-click", self.on_click)
+ self.rectangle.mouse_cursor = gtk.gdk.ARROW
+ self.rectangle.opacity = 0
+ self.add_child(self.rectangle)
+
+ self.payee_label = graphics.Label(self.payee, color='#fff')
+ self.payee_label.x = 14
+ self.add_child(self.payee_label)
+
+ self.amount_label = graphics.Label(self.float_to_currency(
+ self.amountdue),
+ color='#fff')
+ self.amount_label.x = 4
+ self.amount_label.y = 14
+ self.add_child(self.amount_label)
+
+ self.connect("on-render", self.on_render)
+
+ def on_mouse_over(self, sprite):
+ self.parent.set_tooltip_text(self.tooltip)
+ self.emit('on-mouse-over')
+
+ def on_mouse_out(self, sprite):
+ self.parent.set_tooltip_text(None)
+ self.emit('on-mouse-out')
+
+ def on_click(self, event, sprite):
+ self.emit('on-click', event)
+
+ def move_to_default_position(self):
+ x, y = self.parent.get_date_coords(self.date)
+ self.x = x + 4
+ self.y = y + self.parent._middle - 7
+
+ def on_render(self, sprite):
+ #TODO make colors configurable
+ if self.status == self.TO_BE_PAID:
+ self.fill = '#008000'
+ elif self.status == self.OVERDUE:
+ self.fill = '#ff0000'
+ elif self.status == self.PAID:
+ self.fill = '#b3b3b3'
+
+ self.move_to_default_position()
+
+ if self.multi:
+ self.graphics.rectangle(2.5, 2.5, self.parent.item_width - 5,
+ int(self.parent._font_height * 1.8) + 2,
+ corner_radius=7)
+ self.graphics.fill_stroke(self.fill, '#fff', self.line_width)
+
+ self.graphics.rectangle(0.5, 0.5, self.parent.item_width - 6,
+ int(self.parent._font_height * 1.8),
+ corner_radius=5)
+ self.graphics.fill_stroke(self.fill, '#fff', self.line_width)
+ self.graphics.circle(7.5, 7.5, 3)
+ self.graphics.fill_stroke((self.bullet_color if self.bullet_color else
+ self.parent.style.text[self.parent.state]),
+ self.parent.style.base[self.parent.state],
+ self.line_width)
+
+ self.rectangle.width = self.parent.item_width - 1
+ self.rectangle.height = int(self.parent._font_height * 1.8) + 5
+
+ if self.parent.item_width >= 52:
+ self.style = gtk.Style()
+ size = self.style.font_desc.get_size() / pango.SCALE
+ family = self.style.font_desc.get_family()
+ fontstyle = self.style.font_desc.get_style()
+ weight = self.style.font_desc.get_weight()
+
+ self.payee_label.font_desc.set_size((size - 1) * pango.SCALE)
+ self.payee_label.font_desc.set_family(family)
+ self.payee_label.font_desc.set_style(fontstyle)
+ self.payee_label.font_desc.set_weight(weight)
+ self.payee_label.max_width = self.parent.item_width - 22
+ self.payee_label.text = self.payee
+
+ self.amount_label.font_desc.set_size((size - 2) * pango.SCALE)
+ self.amount_label.font_desc.set_family(family)
+ self.amount_label.font_desc.set_style(fontstyle)
+ self.amount_label.font_desc.set_weight(weight)
+ self.amount_label.y = self.parent._font_height * 0.9
+ self.amount_label.max_width = self.parent.item_width - 12
+ self.amount_label.text = self.float_to_currency(self.amountdue)
+ else:
+ if self.multi and self.parent.item_width >= 25:
+ self.amount_label.text = '%d' % self.multi
+ else:
+ self.amount_label.text = ''
+ self.payee_label.text = ''
+
+ @staticmethod
+ def float_to_currency(number):
+ format_ = "%%.%df" % locale.localeconv()['int_frac_digits']
+ return locale.format(format_, number)
+
+class TimelineBox(graphics.Scene):
__gsignals__ = {
- 'realize': 'override',
- 'unrealize': 'override',
- 'expose-event': 'override',
- 'button-press-event': 'override',
- 'button-release-event': 'override',
- 'motion-notify-event': 'override',
- 'key-press-event': 'override'
+ 'value-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,)),
+ 'event-added': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,)),
+ 'cleared': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_BOOLEAN,))
}
- def __init__(self, date=None, callback=None):
- """ Timeline widget constructor
+ ZOOM_IN = 1
+ ZOOM_OUT = -1
- Parameter:
- - date is a datetime.date object or a tuple (year, month, day).
- By default it is datetime.date.today()
- - callback is a functions that receive a datatetime.date object
- and return a Bullet object
- """
+ DEBUG = False
- # Treat the parameter
- if date and not isinstance(date, datetime.date) and \
- isinstance(date, tuple) and len(date) == 3:
- date = datetime.date(date[0], date[1], date[2])
- else:
- date = datetime.date.today()
+ def __init__(self, start_date=None, count=7, callback=None):
+ graphics.Scene.__init__(self, scale=False, keep_aspect=False,
+ framerate=60)
- if callback:
- self._bullet_func = callback
+ if start_date:
+ self._start_date = start_date
else:
- self._bullet_func = lambda date_, type_: None
-
- # Set defaults
- self._mindex = 0
- self._type = self.DAY
- self._display_days = self.DIVS[self._type]
- self._dates = {}
- self.start_date = None
- self.end_date = None
- self._bullets = {}
- self._box_rect = gtk.gdk.Rectangle()
- self._timer = None
- self._scroll_delay = 1800 / self._display_days
+ self._start_date = datetime.date.today() - datetime.timedelta(3)
+
+ self._event_function = callback
+
+ self.value = self.start_date
+
+ self.item_width = self.default_item_width = 62
+
+ self.width = 400
+ self.height = 50
+ self.set_size_request(self.width, self.height)
+
+ self.count = count
+
+ self._stop = False
+ self._scrolling = False
self._pressed = False
- self._dragged = False
- self._clicked_position = -1
- self.value = date
- self.orientation = gtk.ORIENTATION_HORIZONTAL
- self._position = round((self._display_days - 1) / 2)
- self.auto_select_display_days = True
- self.hide_day_labels = False
-
- # Widget initialization
- self.drag = False
- super(gtk.DrawingArea, self).__init__()
- self.add_events(
- gtk.gdk.BUTTON_MOTION_MASK |
- gtk.gdk.BUTTON_PRESS_MASK |
- gtk.gdk.BUTTON_RELEASE_MASK |
- gtk.gdk.SCROLL_MASK
- )
- gobject.signal_new(
- 'value-changed',
- Timeline,
- gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- (gobject.TYPE_PYOBJECT,)
- )
- gobject.signal_new(
- 'scroll',
- Timeline,
- gobject.SIGNAL_RUN_LAST,
- gobject.TYPE_NONE,
- (gobject.TYPE_PYOBJECT,)
- )
- self.connect('size-allocate', self.on_size_allocate)
-
- def do_realize(self):
- self.set_flags(self.flags() | gtk.REALIZED)
- events = (
- gtk.gdk.EXPOSURE_MASK |
- gtk.gdk.BUTTON_PRESS_MASK |
- gtk.gdk.POINTER_MOTION_MASK |
- gtk.gdk.KEY_PRESS_MASK |
- gtk.gdk.KEY_RELEASE_MASK
- )
- self.window = gtk.gdk.Window(
- self.get_parent_window(),
- x=self.allocation.x,
- y=self.allocation.y,
- width=self.allocation.width,
- height=self.allocation.height,
- window_type=gtk.gdk.WINDOW_CHILD,
- wclass=gtk.gdk.INPUT_OUTPUT,
- visual=self.get_visual(),
- colormap=self.get_colormap(),
- event_mask=self.get_events() | events
- )
- self.window.set_user_data(self)
- self.style.attach(self.window)
- self.style.set_background(self.window, gtk.STATE_NORMAL)
- self.set_property('can-focus', True)
+ self._middle = 0
+
+ self._scroll_awaiting = 0
+ self._font_height = 17
- self.select_date(self.value.day, self.value.month, self.value.year)
+ self._layout = self.create_pango_layout('')
+ self._2_times_pi = 2 * pi
- # Define widget minimum size
- self.set_size_request(540, 81)
+ # Sprite used as reference to draw and animete date boxes
+ if self.DEBUG:
+ self.reference = graphics.Circle(10, 10, '#0ff', x=0, y=10)
+ else:
+ self.reference = graphics.Circle(0, 0, '#fff', x=0, y=0)
+ self.add_child(self.reference)
- def do_unrealize(self):
- self.window.destroy()
+ # Define the scroll speed
+ self._scroll_duration_factor = 300.0
+ self._scroll_duration = self.item_width / self._scroll_duration_factor
- def do_expose_event(self, event):
- self.draw()
- return False
+ self.set_property('can-focus', True)
- def refresh(self):
- self._bullets = {}
- self._dist_dates()
- self.queue_draw_area(
- 0, 0, self.allocation.width, self.allocation.height
- )
+ self.connect('size-allocate', self._on_size_allocate)
+ self.connect('on-enter-frame', self._on_enter_frame)
+ # Prevent infinite auto scroll
+ self.connect('focus-out-event', lambda *args: self.stop_auto_scroll())
- def draw(self, redraw=False):
- if self.orientation == gtk.ORIENTATION_HORIZONTAL:
- self._hdraw(redraw)
- elif self.orientation == gtk.ORIENTATION_VERTICAL:
- raise NotImplementedError, \
- 'self.orientation == gtk.ORIENTATION_VERTICAL'
+ self.refresh()
+
+ def clear(self):
+ graphics.Scene.clear(self)
+ self.emit('cleared', True)
+
+ def refresh(self):
+ if self._event_function:
+ self.clear()
+
+ total = self.count + 2
+ for i in range(-1, total):
+ date = self.start_date + datetime.timedelta(i)
+ event = self._event_function(date, 0, True)
+ if event:
+ self.add_event(event)
else:
- raise ValueError, 'self.orientation'
+ self.move_all_sprites_to_default_position()
- def _hdraw(self, redraw=False):
- """ Draw a horizontal timeline """
- self._layout = self.create_pango_layout('')
- cr = self.window.cairo_create()
-
- # Draw the base box
- self.window.draw_rectangle(
- self.style.base_gc[gtk.STATE_NORMAL], True,
- self._box_rect.x,
- self._box_rect.y,
- self._box_rect.width,
- self._box_rect.height
- )
+ self.redraw()
- self.style.paint_shadow(
- self.window, self.state,
- gtk.SHADOW_IN, None, self, '',
- self._box_rect.x, self._box_rect.y,
- self._box_rect.width + 1,
- self._box_rect.height
- )
+ def get_start_date(self):
+ return self._start_date
+ def set_start_date(self, date):
+ self._start_date = date
+ self.value_changed()
+ start_date = property(get_start_date, set_start_date)
- y_ = self._box_rect.y + self._box_rect.height / 2
-
- line_cg = self.style.text_gc
- fg_color = {
- 'red': self.parent.style.text[gtk.STATE_NORMAL].red / 65535.0,
- 'green': self.parent.style.text[gtk.STATE_NORMAL].green / 65535.0,
- 'blue': self.parent.style.text[gtk.STATE_NORMAL].blue / 65535.0
- }
- radius = self._bullet_radius
- radius_by_two = radius / 2
- radius_by_three = radius / 3
- radius_by_four = radius / 4
- radius_by_five = radius / 5
- radius_by_seven = radius / 7
- radius_by_eight = radius / 8
- two_pi = 2 * pi;
- arc = (two_pi) / 40
- y = int(y_)
-
- # Go through all visible positions
- for i in range(self._display_days):
- line_h = 3
- x = self._box_rect.x + self._div_width * i + self._div_width / 2
-
- # Draw bullets
- if self._bullets[i] and i < self._display_days:
- bullet_ = self._bullets[i]
-
- # Draw the behind bullet when is multiple
- if bullet_.multi:
- if bullet_.status & Bullet.TO_BE_PAID:
- cr.set_source_rgba(0.13, 0.4, 0.48)
- if bullet_.status & Bullet.OVERDUE:
- cr.set_source_rgba(0.47, 0, 0)
- if bullet_.status & Bullet.PAID:
- cr.set_source_rgba(0.19, 0.51, 0)
- ## Move to the position of behind bullet
- x += radius_by_four
- y += radius_by_three
- ## Draw it
- cr.arc(x, y, radius, 0, two_pi)
- cr.fill_preserve()
- cr.set_line_width(radius_by_eight)
- cr.stroke()
- ## Move back to the main bullet position
- x -= radius_by_four
- y -= radius_by_three
-
- # Draw the main bullet body
- ## Set the color
- if bullet_.status & Bullet.PAID:
- cr.set_source_rgb(0.27, 0.81, 0.44)
- if bullet_.status & Bullet.OVERDUE:
- cr.set_source_rgb(1, 0.16, 0.16)
- if bullet_.status & Bullet.TO_BE_PAID:
- cr.set_source_rgb(0.52, 0.81, 0.87)
- ## Draw it
- cr.arc(x, y, radius, 0, two_pi)
- cr.fill()
-
- # Draw the main bullet border
- ## Set the color
- if bullet_.status & Bullet.PAID:
- cr.set_source_rgb(0.19, 0.51, 0)
- if bullet_.status & Bullet.OVERDUE:
- cr.set_source_rgb(0.47, 0, 0)
- if bullet_.status & Bullet.TO_BE_PAID:
- cr.set_source_rgb(0.13, 0.4, 0.48)
-
- # Set different border size if is overthreshold
- if bullet_.overthreshold:
- cr.set_line_width(radius_by_three)
- else:
- cr.set_line_width(radius_by_five)
-
- # Draw dotted border if is estimeded
- if bullet_.estimated:
- for j in range(0, 40, 2):
- if bullet_.overthreshold:
- cr.arc(x, y, radius, arc * j, arc * (j + 1))
- else:
- cr.arc(x, y, radius, arc * j, arc * (j + 0.5))
- cr.stroke()
- # Else draw the solid border
- else:
- cr.arc(x, y, radius, 0, two_pi)
- cr.stroke()
-
- # Draw vertical dashed line if the position is for today
- if self._dates[i] == datetime.date.today():
- cr.set_source_rgba(
- fg_color['red'], fg_color['green'], fg_color['blue'], 0.75
- )
- cr.set_line_width(max(radius_by_eight, 0.8))
- cr.set_source_rgb(0.4, 0.4, 0.4)
- h_ = (self._box_rect.height + self._box_rect.y) / 10
- for j in range(0, 10, 2):
- cr.move_to(x, self._box_rect.y + h_ * j + 1)
- cr.line_to(x, self._box_rect.y + h_ * (j + 1))
- cr.stroke()
-
- # Draw dots
- cr.set_source_rgba(
- fg_color['red'], fg_color['green'], fg_color['blue'], 0.75
- )
- if self._dates[i].weekday() == 6:
- cr.arc(x, y, radius_by_five, 0, two_pi)
- else:
- cr.arc(x, y, radius_by_seven, 0, two_pi)
- cr.fill()
-
- #Draw label
- ## Draw the year label
- if (self._dates[i].day, self._dates[i].month) == (1, 1) or \
- (self._dates[i].month == 1 and self._dates[i].day <= 7 and
- self._type == self.WEEK):
- if i < self._display_days:
- self._layout.set_markup(
- '<small>' + str(self._dates[i].year) + '</small>'
- )
- size_ = self._layout.get_pixel_size()
- self.style.paint_layout(
- self.window, self.state, False,
- None, self, '',
- int(self._box_rect.x + \
- self._div_width * i + \
- self._div_width / 2 - size_[0] / 2),
- self._box_rect.y + \
- self._box_rect.height + 12,
- self._layout
- )
-
- line_h = 6
-
- ## Draw the month label
- elif ((self._dates[i].day == 1 and self._type == self.DAY) or \
- (self._dates[i].day <= 7 and self._type == self.WEEK) or \
- (i == 0 and self.start_date.month == self.end_date.month)):
- if i < self._display_days:
- self._layout.set_markup(
- '<small>' + self._dates[i].strftime('%b') + '</small>'
- )
- size_ = self._layout.get_pixel_size()
- self.style.paint_layout(
- self.window, self.state, False,
- None, self, '',
- int(self._box_rect.x + \
- self._div_width * i + \
- self._div_width / 2 - size_[0] / 2),
- self._box_rect.y + \
- self._box_rect.height + 12,
- self._layout
- )
-
- line_h = 6
-
- self.window.draw_rectangle(
- line_cg[self.state],
- True,
- int(self._box_rect.x + self._div_width * i +
- self._div_width / 2),
- self._box_rect.height + \
- self._box_rect.y - 2,
- 1, line_h
- )
-
- ## Draw day label
- if i < self._display_days and \
- (self.display_days < 20 or self._dates[i].weekday() == 0 or \
- self._dates[i] == self.value or not self.hide_day_labels):
- # Draw today with bold font
- if self._dates[i] == datetime.date.today():
- self._layout.set_markup(
- '<b><small>' + str(self._dates[i].day) + '</small></b>'
- )
- else:
- self._layout.set_markup(
- '<small>' + str(self._dates[i].day) + '</small>'
- )
- state_ = self.state
- size_ = self._layout.get_pixel_size()
- if self._dates[i] == self.value:
- state_ = gtk.STATE_SELECTED
- self.window.draw_rectangle(
- self.style.base_gc[gtk.STATE_SELECTED],
- True,
- int(self._box_rect.x + self._div_width * i),
- int(self._box_rect.y + self._box_rect.height),
- int(self._div_width + 1),
- size_[1]
- )
- self.style.paint_layout(
- self.window, state_, False,
- None, self, '',
- int(self._box_rect.x + \
- self._div_width * i + \
- self._div_width / 2 - size_[0] / 2),
- self._box_rect.y + \
- self._box_rect.height,
- self._layout
- )
-
- # Draw the central line
- cr.set_line_width(max(radius_by_eight, 0.5))
- cr.set_source_rgba(
- fg_color['red'], fg_color['green'], fg_color['blue'], 0.5
- )
- cr.move_to(self._box_rect.x + 1, y)
- cr.line_to(self._box_rect.width + self._box_rect.x, y)
- cr.stroke()
-
- mx, my = self.get_pointer()
-
- # Draw the Arrows
- arrow_y = self._box_rect.y + self._box_rect.height / 2 - 5
- ## Draw the left arrow
- self.style.paint_arrow(
- self.window, self.state, gtk.SHADOW_IN,
- None, self, '', gtk.ARROW_LEFT, True,
- self._box_rect.x - 15, arrow_y,
- 8, 10
+ def get_end_date(self):
+ return self.start_date + datetime.timedelta((self.width /
+ self.item_width) - 1)
+ def set_end_date(self, date):
+ raise TypeError("property 'end_date' is not writable")
+ end_date = property(get_end_date, set_end_date)
+
+ def value_changed(self):
+ self.value = self.start_date
+ if (self._stop or self._scroll_awaiting == 1 or
+ self._scroll_awaiting % 3 == 0):
+ self.emit('value-changed', self.value)
+
+ if self._event_function:
+ event = self._event_function(self.start_date - datetime.timedelta(1), 0)
+ if event:
+ self.add_event(event)
+
+ event = self._event_function(self.end_date + datetime.timedelta(1), 0)
+ if event:
+ self.add_event(event)
+
+ #self.move_all_sprites_to_default_position()
+
+ def scroll_left(self, sprite=None, auto=False, duration=None, days=1,
+ easing=Easing.Linear.ease_out):
+ if not sprite:
+ sprite = self.reference
+
+ if not duration:
+ duration = self._scroll_duration
+
+ self._scrolling = True
+ self._scroll_awaiting = days
+ self.reference.old_x = self.reference.x
+
+ def _scroll_left_next(sprite=None):
+ if self.reference.x <= self.item_width:
+ self.reference.x = 0
+ self.reference.old_x = self.reference.x
+ self.start_date -= datetime.timedelta(1)
+ self._scroll_next(sprite, gtk.gdk.SCROLL_LEFT)
+
+ def _scroll_left_stopped(sprite=None):
+ self.start_date -= datetime.timedelta(1)
+ self._scroll_stopped(sprite)
+
+ self.animate(
+ sprite, x=self.item_width,
+ easing=easing,
+ on_complete=_scroll_left_next if auto else _scroll_left_stopped,
+ on_update=self._scroll_all_sprites,
+ duration=duration
)
- ## Draw the right arrow
- self.style.paint_arrow(
- self.window, self.state, gtk.SHADOW_IN,
- None, self, '', gtk.ARROW_RIGHT, True,
- self._box_rect.x + self._box_rect.width + 9, arrow_y,
- 8, 10
- )
+ def scroll_right(self, sprite=None, auto=False, duration=None, days=1,
+ easing=Easing.Linear.ease_out):
+ if not sprite:
+ sprite = self.reference
+
+ if not duration:
+ duration = self._scroll_duration
+
+ self._scrolling = True
+ self._scroll_awaiting = days
+ self.reference.old_x = self.reference.x
+
+ def _scroll_right_next(sprite=None):
+ if self.reference.x >= self.item_width * (-1):
+ self.reference.x = 0
+ self.reference.old_x = self.reference.x
+ self.start_date += datetime.timedelta(1)
+ self._scroll_next(sprite, gtk.gdk.SCROLL_RIGHT)
+
+ def _scroll_right_stopped(sprite=None):
+ self.start_date += datetime.timedelta(1)
+ self._scroll_stopped(sprite)
+
+ self.animate(sprite, x=self.item_width * (-1), easing=easing,
+ on_complete=(_scroll_right_next if auto else
+ _scroll_right_stopped),
+ on_update=self._scroll_all_sprites, duration=duration)
+
+ def _scroll_next(self, sprite=None, direction=None):
+ if self._stop:
+ self._scroll_stopped()
+ return
- # Draw the focus rect
- if self.get_property('has-focus'):
- self.style.paint_focus(
- self.window, self.state, None, self, '',
- 1, 1,
- self.width - 2, self.height -2
- )
+ easing = Easing.Linear.ease_out
+ duration = self._scroll_duration * 0.4
+
+ if self._scroll_awaiting > 1:
+ self._scroll_awaiting -= 1
+ if self._scroll_awaiting > 2:
+ duration = self._scroll_duration * 0.4
+ self.stop_auto_scroll()
+
+ if direction is gtk.gdk.SCROLL_LEFT:
+ self.scroll_left(sprite, True, duration, self._scroll_awaiting,
+ easing)
+ elif direction is gtk.gdk.SCROLL_RIGHT:
+ self.scroll_right(sprite, True, duration, self._scroll_awaiting,
+ easing)
+
+ def _scroll_stopped(self, sprite=None):
+ self._stop = False
+ self._scrolling = False
+ self.reference.x = 0
+ self.move_all_sprites_to_default_position()
+
+ def _scroll_all_sprites(self, sprite=None):
+ diff = self.reference.x - self.reference.old_x
+ for s in self.sprites:
+ if s is not self.reference:
+ s.x += diff
+ self.reference.old_x = self.reference.x
+
+ def move_all_sprites_to_default_position(self):
+ for s in self.sprites:
+ if s is not self.reference:
+ s.move_to_default_position()
+ s.emit('on-render')
+
+ def stop_auto_scroll(self):
+ if self._scroll_awaiting <= 1 and self._scrolling:
+ self._stop = True
+
+ def get_date_coords(self, date):
+ pos = (date - self.start_date).days
+ return [pos * self.item_width + self.reference.x, 0]
+
+ def add_event(self, sprite):
+ self.add_child(sprite)
+ self.emit('event-added', sprite)
+
+ def zoom(self, zoom):
+ if zoom is self.ZOOM_OUT:
+ self.count += 1
+ elif zoom is self.ZOOM_IN:
+ self.count -= 1
+
+ old_item_width = self.item_width
+ self.item_width = self.default_item_width = self.width / self.count
+ self._scroll_duration = self.item_width / self._scroll_duration_factor
+
+ self.move_all_sprites_to_default_position()
+
+ self.redraw()
+
+ if self.width / old_item_width != self.width / self.item_width:
+ self.value_changed()
+
+ return True
+ def can_zoom_in(self):
+ return self.width / self.count < 150 and self.count > 1
+
+ def can_zoom_out(self):
+ return self.width / self.count > 20
+
+ def zoom_in(self):
+ if self.can_zoom_in():
+ return self.zoom(self.ZOOM_IN)
+ else:
+ return False
+
+ def zoom_out(self):
+ if self.can_zoom_out():
+ return self.zoom(self.ZOOM_OUT)
+ else:
+ return False
+
+ def do_key_release_event(self, event):
+ self.stop_auto_scroll()
def do_key_press_event(self, event):
- if gtk.gdk.keyval_name(event.keyval) == 'Right':
- # Control+right - go to next month
- if event.state & gtk.gdk.CONTROL_MASK:
- month = (self.value.month % 12) + 1
- year = self.value.year + (self.value.month) / 12
- self.select_month(month=month, year=year)
- # Right - scroll right
- else:
- self.scroll(gtk.gdk.SCROLL_RIGHT)
-
- if gtk.gdk.keyval_name(event.keyval) == 'Left':
- # Control+left - go to prev month
- if event.state & gtk.gdk.CONTROL_MASK:
- year = self.value.year - int(not self.value.month - 1)
- month = self.value.month - 1 + (self.value.year - year) * 12
- self.select_month(month=month, year=year)
- # Left - scroll left
- else:
- self.scroll(gtk.gdk.SCROLL_LEFT)
-
+ if self._scrolling:
+ return
+
# "+" - zoom in
- if gtk.gdk.keyval_name(event.keyval) == 'plus' \
- or gtk.gdk.keyval_name(event.keyval) == 'KP_Add':
- self.display_days -= 2
+ if (gtk.gdk.keyval_name(event.keyval) == 'plus' or
+ gtk.gdk.keyval_name(event.keyval) == 'KP_Add'):
+ self.zoom_in()
+
# "-" - zoom out
- if gtk.gdk.keyval_name(event.keyval) == 'minus' or \
- gtk.gdk.keyval_name(event.keyval) == 'KP_Subtract':
- self.display_days += 2
- # Home - go to Today
- if gtk.gdk.keyval_name(event.keyval) == 'Home':
- self.value = datetime.date.today()
- self._value_changed()
- # PageUp
- if gtk.gdk.keyval_name(event.keyval) == 'Page_Up':
- self.set_position(self._position - self.display_days)
- self.move(self._box_rect.width / 2)
- # PageDow
- if gtk.gdk.keyval_name(event.keyval) == 'Page_Down':
- self.set_position(self._position + self.display_days)
- self.move(self._box_rect.width / 2)
- self.queue_draw_area(
- 0, 0, self.allocation.width, self.allocation.height
- )
-
- if self.debug:
- print gtk.gdk.keyval_name(event.keyval)
+ elif (gtk.gdk.keyval_name(event.keyval) == 'minus' or
+ gtk.gdk.keyval_name(event.keyval) == 'KP_Subtract'):
+ self.zoom_out()
+
+ elif event.state & gtk.gdk.CONTROL_MASK:
+ month = self.start_date.month
+ year = self.start_date.year
+ if gtk.gdk.keyval_name(event.keyval) == 'Left':
+ month -= 1
+ if month == 0:
+ month = 12
+ if month == 2:
+ if year % 4:
+ days = 28
+ else:
+ days = 29
+ elif (month <= 7 and month % 2) or (month >= 7 and not month % 2):
+ days = 31
+ else:
+ days = 30
+
+ if gtk.gdk.keyval_name(event.keyval) == 'Left':
+ self.start_date -= datetime.timedelta(days - 1)
+ self.refresh()
+ self.scroll_left(None, False, self._scroll_duration * 0.6)
+
+ elif gtk.gdk.keyval_name(event.keyval) == 'Right':
+ self.start_date += datetime.timedelta(days - 1)
+ self.refresh()
+ self.scroll_right(None, False, self._scroll_duration * 0.6)
+
+ elif gtk.gdk.keyval_name(event.keyval) == 'Left':
+ self.scroll_left(auto=True)
+
+ elif gtk.gdk.keyval_name(event.keyval) == 'Right':
+ self.scroll_right(auto=True)
+
+ elif gtk.gdk.keyval_name(event.keyval) == 'Page_Up':
+ self.start_date -= datetime.timedelta(self.count - 1)
+ self.refresh()
+ self.scroll_left(None, False, self._scroll_duration * 0.6)
+
+ elif gtk.gdk.keyval_name(event.keyval) == 'Page_Down':
+ self.start_date += datetime.timedelta(self.count - 1)
+ self.refresh()
+ self.scroll_right(None, False, self._scroll_duration * 0.6)
+
def do_button_release_event(self, event):
- # Get the mouse position and set focus to the wiget
- mx, my = self.get_pointer()
- self.drag = False
-
- # Released the click with the mouse left buttom
- if event.button == 1:
- self._pressed = False
- # Stop the autoscroll trigger timer
- if self._timer:
- gobject.source_remove(self._timer)
- self._timer = None
- # Released from the main box area
- if mx > self._box_rect.x and \
- mx < self._box_rect.width + self._box_rect.x:
- # The user didn't dragged the timeline
- if not self._dragged:
- self.move(mx - self._div_width / 2)
- gobject.timeout_add(
- self._scroll_delay, self._center_selection
- )
- if mx < self._box_rect.x or \
- mx > self._box_rect.x + self._box_rect.width or \
- my > self._box_rect.y + self._box_rect.height:
- self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
- else:
- self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND1))
- self.queue_draw_area(0, 0, self.allocation.width,
- self.allocation.height)
- if self._dragged:
- self.move(self._box_rect.width / 2)
- self._dragged = False
- self._pressed = False
- return False
+ self._pressed = False
+ self.mouse_cursor = None
+ if self.reference.x == 0:
+ return
+
+ self.reference.old_x = self.reference.x
+
+ if self.reference.x < 0:
+ new_x = self.item_width * (-1)
+ elif self.reference.x > 0:
+ new_x = self.item_width
+
+ def _on_complete(sprite=None):
+ sprite.x = 0
+ if self.reference.old_x > 0:
+ self.start_date -= datetime.timedelta(1)
+ else:
+ self.start_date += datetime.timedelta(1)
+ self.value_changed()
+
+ if self.item_width == abs(self.reference.x):
+ _on_complete(self.reference)
+ else:
+ self.animate(self.reference, x=new_x,
+ easing=Easing.Linear.ease_out,
+ on_complete=_on_complete,
+ on_update=self._scroll_all_sprites,
+ duration=(self._scroll_duration *
+ (self.item_width - abs(self.reference.x)) /
+ self.item_width))
def do_button_press_event(self, event):
- # Get the mouse position and set focus to the wiget
- mx, my = self.get_pointer()
- self.grab_focus()
-
- # Stop the autoscroll trigger timer
- if self._timer:
- gobject.source_remove(self._timer)
- self._timer = None
-
- # Clicked with the mouse left buttom
- if event.button == 1:
- # Clicked on the left arrow
- if mx < self._box_rect.x:
- self.scroll(gtk.gdk.SCROLL_LEFT)
- # Start the autoscroll trigger timer
- self._timer = gobject.timeout_add(
- 500, self.auto_scroll, gtk.gdk.SCROLL_LEFT
- )
- # Clicked on the right arrow
- elif mx > self._box_rect.width + self._box_rect.x:
- self.scroll(gtk.gdk.SCROLL_RIGHT)
- # Start the autoscroll trigger timer
- self._timer = gobject.timeout_add(
- 500, self.auto_scroll, gtk.gdk.SCROLL_RIGHT
- )
- # Clicked on the main box area
- elif mx > self._box_rect.x and \
- mx < self._box_rect.width + self._box_rect.x:
- self._pressed = True
- self._clicked_position = self._get_mouse_position()
- self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
- return False
+ if event.button == 1: # Clicked with the mouse left buttom
+ self._pressed = True
+ self._clicked_position = self.get_pointer()
+ self.mouse_cursor = gtk.gdk.Cursor(gtk.gdk.FLEUR)
def do_motion_notify_event(self, event):
- mx, my = self.get_pointer()
if self._pressed:
- self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
- pos_ = self._get_mouse_position()
- if pos_ != self._clicked_position or self._dragged:
- self._dragged = True
- self.set_position(
- (self.display_days / 2) + (pos_ - self._clicked_position),
- True
- )
- else:
- self._dragged = False
- else:
- # TODO Improve tooltip
- if mx < self._box_rect.x or \
- mx > self._box_rect.x + self._box_rect.width or \
- my > self._box_rect.y + self._box_rect.height:
- self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
- elif my > self._box_rect.y - self._bullet_radius / 3 + \
- (self._box_rect.height - self._bullet_radius) / 2 and \
- my < self._box_rect.y + self._bullet_radius / 3 + \
- (self._box_rect.height / 2) + self._bullet_radius:
- if self._mindex != int(
- (mx - self._box_rect.x) / self._div_width
- ):
- self._mindex = int(
- (mx - self._box_rect.x) / self._div_width
- )
- if self._mindex > -1 and \
- self._mindex < self.DIVS[self._type] and \
- self._bullets[self._mindex]:
- gobject.timeout_add(
- 100,
- self.set_tooltip,
- self._bullets[self._mindex].tooltip
- )
- else:
- self.set_tooltip_text(None)
- self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND1))
+ mx, my = self.get_pointer()
+
+ if self.reference.x <= self.item_width * (-1):
+ self._clicked_position = (mx, my)
+ self.reference.x += self.item_width
+ self.start_date += datetime.timedelta(1)
+ elif self.reference.x >= self.item_width:
+ self._clicked_position = (mx, my)
+ self.reference.x -= self.item_width
+ self.start_date -= datetime.timedelta(1)
+
+ self.reference.old_x = self.reference.x
+ self.reference.x = mx - self._clicked_position[0]
+
+ if self.sprites:
+ self._scroll_all_sprites()
else:
- self._mindex = -1
- self.set_tooltip_text(None)
- self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND1))
- #gobject.timeout_add(100, self.set_tooltip, str(mx))
- return False
-
- def do_scroll_event(self, event):
- # SCROLL_DOWN is used to be possible scroll using simple mousewheel
- if event.direction in (gtk.gdk.SCROLL_DOWN, gtk.gdk.SCROLL_RIGHT):
- self.scroll(gtk.gdk.SCROLL_RIGHT)
+ self.redraw()
+
+ def do_on_scroll(self, event):
+ if self._scrolling or self._stop:
+ return
+
+ if event.state & gtk.gdk.CONTROL_MASK:
+ # Zoom in-ou if ctrl key is pressed
+ old_item_width = self.item_width
+
+ if ((event.direction is gtk.gdk.SCROLL_LEFT or
+ event.direction is gtk.gdk.SCROLL_UP) and
+ self.item_width < 150 and self.count > 1):
+ self.zoom_in()
+ elif ((event.direction is gtk.gdk.SCROLL_RIGHT or
+ event.direction is gtk.gdk.SCROLL_DOWN) and
+ self.item_width > 20):
+ self.zoom_out()
else:
- self.scroll(gtk.gdk.SCROLL_LEFT)
-
- def set_tooltip(self, text=None):
- self.set_tooltip_text(text)
- return False
-
- def _get_mouse_position(self):
- mx, my = self.get_pointer()
- pos_ = (mx - self._div_width / 2 - self._box_rect.x) / self._div_width
- x = pos_ * self._div_width + self._box_rect.x
- if mx - self._div_width / 2 > x + self._div_width / 2:
- pos_ += 1
- return round(pos_)
-
- def _value_changed(self):
- self.day = self.value.day
- self.month = self.value.month
- self.year = self.value.year
- self._dist_dates()
- self.start_date = self._dates[0]
- self.end_date = self._dates[self._display_days - 1]
- self.emit('value-changed', self.value)
- if self.debug:
- print "Timeline.value: ", str(self.value)
- print "Timeline.start_date: ", self.start_date
- print "Timeline.end_date: ", self.end_date
-
- def on_size_allocate(self, widget, allocation):
+ # Scroll if ctrl key is not pressed
+ if (event.direction is gtk.gdk.SCROLL_LEFT or
+ event.direction is gtk.gdk.SCROLL_UP):
+ self.scroll_left(duration=self._scroll_duration * 0.4)
+ elif (event.direction is gtk.gdk.SCROLL_RIGHT or
+ event.direction is gtk.gdk.SCROLL_DOWN):
+ self.scroll_right(duration=self._scroll_duration * 0.4)
+
+ def _on_size_allocate(self, widget, allocation):
self.width = allocation.width
self.height = allocation.height
-
- if self.auto_select_display_days:
- self.display_days = self.width / 32
- self._position = self.display_days / 2
- self._dist_dates()
- self._value_changed()
-
- self.calculate_subdivisions_size(self, allocation)
-
- return False
-
- def calculate_subdivisions_size(self, widget, allocation):
- # Set timeline subdivisions size
- self._div_width = float(allocation.width - self._box_rect.x * 2) / \
- self._display_days
- # Set Timeline box size
- self._box_rect.x = 21
- self._box_rect.y = 6
- self._box_rect.width = allocation.width - self._box_rect.x * 2
- self._box_rect.height = allocation.height - 33
- if self._box_rect.height < 0:
- self._box_rect.height = 0
- # Set Bullet radius
- if self._div_width - self._div_width / 4 > self._box_rect.height / 2:
- self._bullet_radius = (self._box_rect.height / 2) / 2
- else:
- self._bullet_radius = (self._div_width - self._div_width / 4) / 2
-
-
- def _dist_dates(self, first=None):
- """ Calculate dates for visible positions """
- if not first:
- # Define the first day if not provided
- selected = self.value
- if self._type == self.WEEK and selected.weekday() != 0:
- selected -= datetime.timedelta(days=selected.weekday())
- if self._type == self.DAY:
- first = selected - datetime.timedelta(days=self._position)
- elif self._type == self.WEEK:
- first = selected - datetime.timedelta(days=self._position * 7)
- elif self._type == self.MONTH:
- month = (selected.month - self._position) % 12
- year = selected.year + (selected.month - self._position) // 12
- if not month:
- month = 12
- year -= 1
- first = selected.replace(month=month, year=year)
- elif self._type == self.YEAR:
- first = selected.replace(year=selected.year - self._position)
-
- # Define the day and create the bullet object for each visible position
- if self._type == self.DAY:
- for i in range(self._display_days + 1):
- self._dates[i] = first + datetime.timedelta(days=i)
- self._bullets[i] = self._bullet_func(self._dates[i], self._type)
- elif self._type == self.WEEK:
- for i in range(self._display_days + 1):
- self._dates[i] = first + datetime.timedelta(days=i * 7)
- self._bullets[i] = self._bullet_func(self._dates[i], self._type)
- elif self._type == self.MONTH:
- for i in range(self._display_days + 1):
- month = (first.month + i) % 12
- year = first.year + (first.month + i) // 12
- if not month:
- month = 12
- year -= 1
- self._dates[i] = first.replace(day=1, month=month, year=year)
- self._bullets[i] = self._bullet_func(self._dates[i], self._type)
- elif self._type == self.YEAR:
- for i in range(self._display_days + 1):
- self._dates[i] = first.replace(
- day=1, month=1, year=first.year + i
- )
- self._bullets[i] = self._bullet_func(self._dates[i], self._type)
-
- def scroll(self, direction, redraw=True):
- """ Scroll the timeline widget
-
- Parameters:
- - direction: gtk.gdk.SCROLL_LEFT or gtk.gdk.SCROLL_RIGHT
- - redraw: use False when scroll by dragging slider.
- By default it is True.
-
- Return:
- Return True to be used in gobject.timeout_add()
- """
- if direction == gtk.gdk.SCROLL_LEFT:
- self.set_position(self._position + 1, redraw)
- self.emit('scroll', direction)
- elif direction == gtk.gdk.SCROLL_RIGHT:
- self.set_position(self._position - 1, redraw)
- self.emit('scroll', direction)
- else:
- raise ValueError, direction
- self.move(self._box_rect.width / 2)
- return True
- def auto_scroll(self, direction, redraw=True):
- """ Auto scroll the widget
+ self._middle = int(self.height / 2) + .5
+
+ try:
+ if self.width != self.old_width:
+ self.count = int(self.width / self.default_item_width)
+ except AttributeError:
+ self.default_item_width = self.width / self.count
+
+ self.item_width = self.width / self.count
+ self.move_all_sprites_to_default_position()
+
+ self.old_width = self.width
+
+ def _on_enter_frame(self, scene, context):
+ # Define the widget background using the system color
+ if self.background_color != self.style.base[self.state]:
+ self.background_color = self.style.base[self.state]
+ self.redraw() # Fix the background color when theme is changed
+ self.move_all_sprites_to_default_position() # Fix all sprites
+
+ # Get the system foreground and background colors to use on some elements
+ self._fg_color = self.colors.parse(self.style.text[self.state])
+ self._bg_color = self.colors.parse(self.style.bg[self.state])
+ self._bs_color = self.colors.parse(self.style.base[self.state])
+ self._fg_color_selected = self.colors.parse(self.style.text[gtk.STATE_SELECTED])
+ self._bg_color_selected = self.colors.parse(self.style.bg[gtk.STATE_SELECTED])
+
+ self._layout = self.create_pango_layout('')
+
+ self._layout.set_markup('AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqWwXxYyZz')
+ size_ = self._layout.get_pixel_size()
+ self._font_height = size_[1]
+
+ self.set_size_request(400, self._font_height * 4)
+
+ # Draw main line
+ context.set_line_width(1)
+ context.set_source_rgb(*self._fg_color)
+ context.move_to(0, self._middle)
+ context.line_to(self.width, self._middle)
+ context.stroke()
+
+ # Draw year label
+ if self.end_date.year != self.start_date.year:
+ self._layout.set_markup('<small>%s</small>' %
+ self.start_date.strftime('%Y'))
+
+ self.style.paint_layout(self.window, self.state, False, None,
+ self, '', 1, 2, self._layout)
+
+ self._layout.set_markup('<small>%s</small>' %
+ self.end_date.strftime('%Y'))
+
+ size_ = self._layout.get_pixel_size()
+ year_label_width = size_[0]
+ self.style.paint_layout(self.window, self.state, False, None,
+ self, '', int(self.width -
+ year_label_width - 2),
+ 2, self._layout)
+
+ # Draw date boxes
+ today = datetime.date.today()
+ total = int(self.width / self.item_width) + 2
+ for i in range(-1, total):
+ date = self.start_date + datetime.timedelta(i)
+
+ context.move_to(self.reference.x + self.item_width * i + 15.5,
+ self._middle)
+ context.arc(self.reference.x + self.item_width * i + 11.5,
+ self._middle, 3, 0, self._2_times_pi)
+
+ context.set_line_width(1.5)
+
+ if date != today:
+ context.set_source_rgb(*self._fg_color)
+ context.fill_preserve()
+ context.set_source_rgb(*self._bs_color)
+ context.stroke()
+ else:
+ context.set_source_rgb(*self._bs_color)
+ context.fill_preserve()
+ context.set_source_rgb(*self._fg_color)
+ context.stroke()
+
+ # Draw date label
+ self._layout.set_markup('<small>%s</small>' %
+ date.strftime('%b %d'))
+ size_ = self._layout.get_pixel_size()
+
+ if size_[0] > self.item_width - 4 or self.item_width < 45:
+ if (date.day == 1 or
+ (i == 0 and self.end_date.year == self.start_date.year and
+ not (date + datetime.timedelta(1)).day == 1)):
+ x_ = int(self.reference.x + self.item_width * i + 4)
+ self._layout.set_markup('<small>%s</small>' %
+ date.strftime('%b'))
+ size_ = self._layout.get_pixel_size()
+ if x_ < self.width - year_label_width - size_[0] - 2:
+ self.style.paint_layout(self.window, self.state,
+ False, None, self, '',
+ x_ if (x_ >= 4 and
+ (i > 0 or
+ (i == 0 and
+ date.day == 1))
+ ) else 4,
+ 1, self._layout)
+
+ self._layout.set_markup('<small>%s</small>' %
+ date.strftime('%d'))
+
+ size_ = self._layout.get_pixel_size()
- Parameters:
- - direction: gtk.gdk.SCROLL_LEFT or gtk.gdk.SCROLL_RIGHT
- - redraw: use False when scroll by dragging slider.
- By default it is True.
+ self.style.paint_layout(self.window, self.state, False, None,
+ self, '', int(self.reference.x +
+ self.item_width * i + 4),
+ int(self._middle - size_[1] - 6),
+ self._layout)
- Return:
- Return False to run only one time when used in
- gobject.timeout_add()
- """
- self._timer = gobject.timeout_add(
- self._scroll_delay, self.scroll, direction, redraw
- )
- return False
-
- def _center_selection(self):
- if self._position > (self._display_days - 1) / 2:
- self.set_position(self._position - 1, True)
- elif self._position < (self._display_days - 1) / 2:
- self.set_position(self._position + 1, True)
-
- if self._position == (self._display_days - 1) / 2:
- self.value = self._dates[self._position]
- self._dist_dates()
- self._value_changed()
-
- return self._position != (self._display_days - 1) / 2
-
- def move(self, pos, update=True, redraw=True):
- position_old = self._position
- self._position = round((pos - self._box_rect.x) / self._div_width)
- x = self._position * self._div_width + self._box_rect.x
- if pos > x + self._div_width / 2:
- self._position += 1
- x += self._div_width
- self.queue_draw_area(
- 0, 0, self.allocation.width, self.allocation.height
- )
- if self.debug :
- if not self._dragged:
- print "Timeline.position: ", self._position
-
- if update:
- # Update self.value
- if self._position < 0 or self._position > self._display_days - 1:
- return
- self.value = self._dates[self._position]
- self._dist_dates()
- self._value_changed()
- return position_old, self._position
-
- def get_position(self):
- return int(self._position)
-
- def set_position(self, pos, redraw=True):
- self._position = round(pos)
- x = pos * self._div_width + self._box_rect.x
- self.move(x, False, redraw)
- self._dist_dates()
- return int(self._position)
-
- position = property(get_position, set_position)
+ if self.DEBUG:
+ # Draw box position number on debug mode
+ context.set_source_rgb(*self._fg_color)
+ context.move_to(self.reference.x + self.item_width * i + 6.5,
+ 12)
+ context.show_text('pos: %d/%d' % (i, total))
+
+ # Draw the reference sprite on debug mode
+ self.reference.width = 5
+ self.reference.height = 5
+
+ # Draw timeline box shadow
+ self.style.paint_shadow(self.window, self.state, gtk.SHADOW_IN,
+ None, self, '', 0, 0, self.width, self.height)
+
+ # Draw focus rect
+ if self.get_property('has-focus'):
+ self.style.paint_focus(self.window, self.state, None, self, '',
+ 2, 2, self.width - 4, self.height - 4)
+
+ context.rectangle(1, 1, self.width - 2, self.height - 2)
+ context.clip()
+
+class Timeline(gtk.HBox):
+ __gsignals__ = {
+ 'value-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,)),
+ 'event-added': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,)),
+ 'cleared': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_BOOLEAN,))
+ }
+
+ def __init__(self, date=None, count=7, callback=None, debug=False):
+ super(gtk.HBox, self).__init__()
+
+ # Treat the parameter
+ if date and not isinstance(date, datetime.date) and \
+ isinstance(date, tuple) and len(date) == 3:
+ date = datetime.date(date[0], date[1], date[2])
+ else:
+ date = datetime.date.today()
+
+ self.set_border_width(3)
+
+ self.connect('size-allocate', self._on_size_allocate)
+
+ # Create Timeline box
+ self.timeline_box = TimelineBox(count=count, callback=callback)
+ self.timeline_box.connect('value-changed',
+ lambda widget, *arg:
+ self.emit('value-changed', *arg))
+ self.timeline_box.connect('event-added',
+ lambda widget, *arg:
+ self.emit('event-added', *arg))
+ self.timeline_box.connect('cleared',
+ lambda widget, *arg:
+ self.emit('cleared', *arg))
+
+ self.refresh = self.timeline_box.refresh
+ self.can_zoom_in = self.timeline_box.can_zoom_in
+ self.can_zoom_out = self.timeline_box.can_zoom_out
+ self.zoom_in = self.timeline_box.zoom_in
+ self.zoom_out = self.timeline_box.zoom_out
+ self.add_event = self.timeline_box.add_event
+
+ # Create scroll buttons
+ left_arrow = gtk.Arrow(gtk.ARROW_LEFT, gtk.SHADOW_IN)
+ self.left_bttn = gtk.Button();
+ self.left_bttn.add(left_arrow)
+ self.left_bttn.set_relief(gtk.RELIEF_NONE)
+ self.left_bttn.set_property('can-focus', False)
+ self.left_bttn.connect('released', self._on_left_bttn_released)
+ self.left_bttn.connect('pressed', self._on_left_bttn_pressed)
+
+ right_arrow = gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_IN)
+ self.right_bttn = gtk.Button();
+ self.right_bttn.add(right_arrow)
+ self.right_bttn.set_relief(gtk.RELIEF_NONE)
+ self.right_bttn.set_property('can-focus', False)
+ self.right_bttn.connect('released', self._on_right_bttn_released)
+ self.right_bttn.connect('pressed', self._on_right_bttn_pressed)
+
+ self.add(self.left_bttn)
+ self.add(self.timeline_box)
+ self.add(self.right_bttn)
+
+ self.set_child_packing(self.left_bttn, False, False, 0, gtk.PACK_START)
+ self.set_child_packing(self.timeline_box, True, True, 0, gtk.PACK_START)
+ self.set_child_packing(self.right_bttn, False, False, 0, gtk.PACK_START)
def get_start_date(self):
- return self.start_date
+ return self.timeline_box.start_date
+ def set_start_date(self, date):
+ self.timeline_box.start_date = date
+ start_date = property(get_start_date, set_start_date)
def get_end_date(self):
- return self.end_date
-
- def get_display_days(self):
- return self._display_days
-
- def set_display_days(self, days):
- days_old = self._display_days
- if days < 7:
- days = 7
- warnings.warn('Using the minimum allowed value: 7', RuntimeWarning)
- elif days > 61:
- days = 61
- warnings.warn('Using the maximum allowed value: 61', RuntimeWarning)
- if days == days_old:
- return
- self._display_days = days
- self._dist_dates()
- # Set timeline subdivisions size
- self.calculate_subdivisions_size(self, self.allocation)
- self._center_selection()
- self.queue_draw_area(
- 0, 0, self.allocation.width, self.allocation.height
- )
- display_days = property(get_display_days, set_display_days)
-
- def get_type(self):
- """ Return timeline type
- (Timeline.DAY, Timeline.WEEK, Timeline.MONTH or Timeline.YEAR)
- """
- return self._type
-
- def set_type(self, display_type):
- if display_type not in (DAY, WEEK, MONTH, YEAR):
- raise ValueError
- self._type = display_type
- self._display_days = self.DIVS[self._type]
- self._dist_dates()
- self.queue_draw_area(
- 0, 0, self.allocation.width, self.allocation.height
- )
+ return self.timeline_box.end_date
+ def set_end_date(self, date):
+ self.timeline_box.end_date = date
+ end_date = property(get_end_date, set_end_date)
+
+ def get_value(self):
+ return self.timeline_box.value
+ def set_value(self, date):
+ self.timeline_box.value = date
+ value = property(get_value, set_value)
+
+ def get_count(self):
+ return self.timeline_box.count
+ def set_count(self, count):
+ self.timeline_box.count = count
+ count = property(get_count, set_count)
+
+ def _on_size_allocate(self, widget, allocation):
+ self.width = allocation.width
+ self.height = allocation.height
+ self.timeline_box.reference.x = 0
+
+ def _on_left_bttn_released(self, widget):
+ self.timeline_box.stop_auto_scroll()
- def get_interval(self):
- return self._interval
-
- def select_month(self, month=None, year=None):
- if month and not year and not self._type == self.YEAR:
- self.value = self.value.replace(month=month)
- self.set_position((self._display_days - 1) / 2)
- self._value_changed()
- if not month and year:
- self.value = self.value.replace(year=year)
- self.set_position((self._display_days - 1) / 2)
- self._value_changed()
- if month and year:
- self.value = self.value.replace(month=month, year=year)
- self.set_position((self._display_days - 1) / 2)
- self._value_changed()
-
- def select_day(self, day):
- if self._type in (self.DAY, self.WEEK):
- self.value = self.value.replace(day=day)
- self.set_position((self._display_days - 1) / 2)
- self._value_changed()
-
- def select_date(self, day, month, year):
- self.value = self.value.replace(day=day, month=month, year=year)
- self.queue_draw_area(0, 0, self.width, self.height)
- self._value_changed()
-
- def set_bullet_function(self, func):
- """
- Set the function to be used to create the bullet object for
- a given date.
- """
- self._bullet_func = func
-
-def bullet_cb(date, display_type):
- #print date, display_type
-
- bullets = []
-
- today = datetime.date.today()
- yesterday = today - datetime.timedelta(days=1)
- tomorrow = today + datetime.timedelta(days=1)
-
- # date=None, amountDue=None, estimated=False, status=0,
- # overthreshold=False, multi=False, tooltip=''
- if date == today - datetime.timedelta(days=3):
- return Bullet(date, 200, False, Bullet.PAID, False, True, 'PAID')
- elif date == today - datetime.timedelta(days=2):
- return Bullet(date, 200, False, Bullet.OVERDUE, False, True, 'OVERDUE')
- elif date == today - datetime.timedelta(days=1):
- return Bullet(date, 200, False, Bullet.OVERDUE | Bullet.PAID, False, True, 'OVERDUE and PAID')
- elif date == today:
- return Bullet(date, 50, False, Bullet.TO_BE_PAID, False, True, 'TO_BE_PAID')
- elif date == today + datetime.timedelta(days=1):
- return Bullet(date, 20, False, Bullet.TO_BE_PAID | Bullet.PAID, False, True, 'TO_BE_PAID and PAID')
-
-
- return None
+ def _on_right_bttn_released(self, widget):
+ self.timeline_box.stop_auto_scroll()
+ def _on_left_bttn_pressed(self, widget):
+ self.timeline_box.scroll_left(auto=True)
+
+ def _on_right_bttn_pressed(self, widget):
+ self.timeline_box.scroll_right(auto=True)
if __name__ == '__main__':
window = gtk.Window()
window.set_title('Timeline')
window.set_default_size(500, 70)
- timeline = Timeline(None, bullet_cb)
- timeline.debug = True
- timeline.display_days = 15
+
+ timeline = Timeline(debug=True)
+
+ b1 = Event(datetime.date.today(), fill='#bbb')
+ b1.payee = 'Payee 1'
+ timeline.add_event(b1)
+
+ b2 = Event(datetime.date.today() + datetime.timedelta(3), fill='#f22')
+ b2.payee = 'Payee 2'
+ timeline.add_event(b2)
+
window.add(timeline)
window.connect('delete-event', gtk.main_quit)
+
window.show_all()
gtk.main()
diff --git a/src/lib/Settings.py b/src/lib/Settings.py
index cfad7a5..7260c13 100644
--- a/src/lib/Settings.py
+++ b/src/lib/Settings.py
@@ -80,6 +80,7 @@ class Settings(gobject.GObject):
'show_menubar' : False,
'show_paid_bills' : 2,
'due_date' : 0,
+ 'timeline_count' : 15,
}
def __init__(self, **kwargs):
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]