[Commits] (alecf) fix for bug 2855 - add gradients to the events by
creating colored brushes on demand. Cache the brushes on a
per-color basis in the calendar view,
and blow away the brush cache when the window is resized
commits at osafoundation.org
commits at osafoundation.org
Tue Apr 26 11:25:39 PDT 2005
Commit by: alecf
Modified files:
chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py 1.81 1.82
chandler/parcels/osaf/framework/blocks/calendar/parcel.xml 1.15 1.16
Log message:
fix for bug 2855 - add gradients to the events by creating colored brushes on demand. Cache the brushes on a per-color basis in the calendar view, and blow away the brush cache when the window is resized
fix for bug 2886 - auto-assign colors as each calendar is visited
fix for bug 2871 - FYI should have one-pixel wide status bars
fix for bug 2860 - make colors persist - problem is that you can't set red/green/blue directly, instead you have to make a copy of the color, and then set red/green/blue
Bugzilla links:
http://bugzilla.osafoundation.org/show_bug.cgi?id=2855
http://bugzilla.osafoundation.org/show_bug.cgi?id=2886
http://bugzilla.osafoundation.org/show_bug.cgi?id=2871
http://bugzilla.osafoundation.org/show_bug.cgi?id=2860
ViewCVS links:
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py.diff?r1=text&tr1=1.81&r2=text&tr2=1.82
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/blocks/calendar/parcel.xml.diff?r1=text&tr1=1.15&r2=text&tr2=1.16
Index: chandler/parcels/osaf/framework/blocks/calendar/parcel.xml
diff -u chandler/parcels/osaf/framework/blocks/calendar/parcel.xml:1.15 chandler/parcels/osaf/framework/blocks/calendar/parcel.xml:1.16
--- chandler/parcels/osaf/framework/blocks/calendar/parcel.xml:1.15 Wed Apr 20 13:15:58 2005
+++ chandler/parcels/osaf/framework/blocks/calendar/parcel.xml Tue Apr 26 11:25:38 2005
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="iso-8859-1"?>
-<!-- $Revision: 1.15 $ -->
-<!-- $Date: 2005/04/20 20:15:58 $-->
+<!-- $Revision: 1.16 $ -->
+<!-- $Date: 2005/04/26 18:25:38 $-->
<!-- Copyright (c) 2003-2004 Open Source Applications Foundation -->
<!-- License: http://osafoundation.org/Chandler_0.1_license_terms.htm -->
@@ -9,7 +9,7 @@
xmlns="http://osafoundation.org/parcels/core"
xmlns:blocks="http://osafoundation.org/parcels/osaf/framework/blocks"
xmlns:cm="http://osafoundation.org/parcels/osaf/contentmodel"
- xmlns:calBlocks="http://osafoundation.org/parcels/osaf/framework/blocks/calendar">
+ xmlns:doc="http://osafoundation.org/parcels/osaf/framework/blocks/calendar">
<description>calendar blocks</description>
<version>0.3</version>
@@ -22,7 +22,7 @@
<Kind itsName="CanvasWeek">
<classes key="python">osaf.framework.blocks.calendar.CalendarCanvas.WeekBlock</classes>
- <superKinds itemref="calBlocks:CollectionCanvas"/>
+ <superKinds itemref="doc:CollectionCanvas"/>
<Attribute itsName="rangeStart">
<type itemref="DateTime"/>
@@ -39,7 +39,11 @@
<Attribute itsName="selectedDate">
<type itemref="DateTime"/>
</Attribute>
- <attributes itemref="calBlocks:CanvasWeek/daysPerView"/>
+
+ <Attribute itsName="lastHue">
+ <type itemref="Float"/>
+ <initialValue value="-1.0"/>
+ </Attribute>
</Kind>
<Kind itsName="MiniCalendar">
Index: chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py
diff -u chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py:1.81 chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py:1.82
--- chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py:1.81 Sun Apr 24 20:26:05 2005
+++ chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py Tue Apr 26 11:25:38 2005
@@ -1,8 +1,8 @@
""" Canvas for calendaring blocks
"""
-__version__ = "$Revision: 1.81 $"
-__date__ = "$Date: 2005/04/25 03:26:05 $"
+__version__ = "$Revision: 1.82 $"
+__date__ = "$Date: 2005/04/26 18:25:38 $"
__copyright__ = "Copyright (c) 2004 Open Source Applications Foundation"
__license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
@@ -19,26 +19,121 @@
import osaf.framework.blocks.Styles as Styles
import osaf.framework.blocks.calendar.CollectionCanvas as CollectionCanvas
+from colorsys import *
import copy
+# from ASPN, this is frange4
+def frange(end,start=0,inc=0,precision=1):
+ """A range function that accepts float increments."""
+ import math
+
+ if not start:
+ start = end + 0.0
+ end = 0.0
+ else: end += 0.0
+
+ if not inc:
+ inc = 1.0
+ count = int(math.ceil((start - end) / inc))
+
+ L = [None] * count
+
+ L[0] = end
+ for i in (xrange(1,count)):
+ L[i] = L[i-1] + inc
+ return L
+
+# 'color' is 0..255 based
+# 'rgb' is 0..1.0 based
def color2rgb(r,g,b):
return (r*1.0)/255, (g*1.0)/255, (b*1.0)/255
def rgb2color(r,g,b):
return r*255,g*255,b*255
-def get_outline_color(r,g,b):
- from colorsys import *
- hsv = rgb_to_hsv(r,g,b)
- newhsv = (hsv[0], min(hsv[1]+.1,1), max(hsv[2] - .2,0))
- return hsv_to_rgb(*newhsv)
+# from ASPN/Python Cookbook
+class CachedAttribute(object):
+ def __init__(self, method):
+ self.method = method
+ self.name = method.__name__
+
+ def __get__(self, inst, cls):
+ if inst is None:
+ return self
+ result = self.method(inst)
+ setattr(inst, self.name, result)
+ return result
class CalendarData(ContentModel.ContentItem):
myKindPath = "//parcels/osaf/framework/blocks/calendar/CalendarData"
myKindID = None
def __init__(self, *args, **keywords):
super(CalendarData, self).__init__(*args, **keywords)
- self.calendarColor = Styles.ColorStyle(self.itsView)
+
+ # need to convert hues from 0..360 to 0..1.0 range
+ hueList = [k/360.0 for k in [210, 120, 60, 0, 240, 90, 330, 30, 180, 270]]
+
+ @classmethod
+ def getNextHue(cls, oldhue):
+ """
+ returns the next hue following the one passed in
+ For example,
+ f.hue = nextHue(f.hue)
+ """
+ found = False
+ for hue in cls.hueList:
+ if found: return hue
+ if hue == oldhue:
+ found = True
+ return cls.hueList[0]
+
+ def _setEventColor(self, color):
+ self.calendarColor.backgroundColor = color
+
+ # clear cached values
+ try:
+ del self.eventHue
+ except AttributeError:
+ pass
+
+ def _getEventColor(self):
+ return self.calendarColor.backgroundColor
+
+ # this is the actual RGB value for eventColor
+ eventColor = property(_getEventColor, _setEventColor)
+
+ @CachedAttribute
+ def eventHue(self):
+ c = self.eventColor
+ rgbvalues = (c.red, c.green, c.blue)
+ hsv = rgb_to_hsv(*color2rgb(*rgbvalues))
+ return hsv[0]
+
+ # to be used like a property, i.e. prop = tintedColor(0.5, 1.0)
+ # takes HSV 'S' and 'V' and returns an color based tuple property
+ def tintedColor(saturation, value = 1.0):
+ def getSaturatedColor(self):
+ hsv = (self.eventHue, saturation, value)
+ return rgb2color(*hsv_to_rgb(*hsv))
+ return property(getSaturatedColor)
+
+ # these are all for when this calendar is the 'current' one
+ gradientLeft = tintedColor(0.4)
+ gradientRight = tintedColor(0.2)
+ outlineColor = tintedColor(0.5)
+ textColor = tintedColor(0.67, 0.6)
+
+ # when a user selects a calendar event, use these
+ selectedGradientLeft = tintedColor(0.15)
+ selectedGradientRight = tintedColor(0.05)
+ selectedOutlineColor = tintedColor(0.5)
+ selectedTextColor = tintedColor(0.67, 0.6)
+
+ # 'visible' means that its not the 'current' calendar, but is still visible
+ visibleGradientLeft = tintedColor(0.4)
+ visibleGradientRight = tintedColor(0.4)
+ visibleOutlineColor = tintedColor(0.3)
+ visibleTextColor = tintedColor(0.5)
class CalendarCanvasItem(CollectionCanvas.CanvasItem):
"""
@@ -80,11 +175,12 @@
def GetStatusPen(self, styles):
# probably should use styles to determine a good pen color
item = self.GetItem()
- color = styles.blockItem.getEventOutlineColor(item)
+ eventColors = styles.blockItem.getEventColors(item)
+ color = eventColors.outlineColor
if (item.transparency == "confirmed"):
pen = wx.Pen(color, 4)
elif (item.transparency == "fyi"):
- pen = wx.Pen(color, 4)
+ pen = wx.Pen(color, 1)
elif (item.transparency == "tentative"):
pen = wx.Pen(color, 4, wx.DOT)
return pen
@@ -607,7 +703,10 @@
startDay = self.rangeStart
endDay = startDay + self.rangeIncrement
return (startDay, endDay)
-
+
+ #
+ # Color stuff
+ #
def getCalendarData(self):
"""
Lazily stamp the data
@@ -616,6 +715,8 @@
if not isinstance(caldata, CalendarData):
caldata.StampKind('add', CalendarData.getKind(view=caldata.itsView))
+ # XXX really, the object should be lazily creating this.
+
colorstyle = Styles.ColorStyle(view=self.itsView)
# make copies, because initialValue ends up being shared, because
# it is isn't immutable
@@ -623,11 +724,24 @@
colorstyle.backgroundColor = copy.copy(colorstyle.backgroundColor)
caldata.calendarColor = colorstyle
+
+ self.setupNextHue()
+
return caldata
calendarData = property(getCalendarData)
-
- def getEventColorStyle(self, event):
+
+ def setupNextHue(self):
+ c = self.contents.source.calendarColor.backgroundColor
+ self.lastHue = CalendarData.getNextHue(self.lastHue)
+ (c.red, c.green, c.blue) = rgb2color(*hsv_to_rgb(self.lastHue, 1.0, 1.0))
+
+ def getEventColors(self, event):
+ """
+ Get the eventColors object which contains all the right color tints
+ for the given event. If the given event doesn't have color data,
+ then we return the default one associated with the view
+ """
containingCollections = event.itemCollectionInclusions
calDataKind = CalendarData.getKind(view=self.itsView)
for coll in containingCollections:
@@ -636,45 +750,18 @@
# we'll rely on that to make sure we don't get 'All's color
if (not hasattr(coll, 'renameable') or coll.renameable) and \
coll.isItemOf(calDataKind):
- return coll.calendarColor
- return None
-
- def getEventBackgroundColor(self, event):
- colorStyle = self.getEventColorStyle(event)
- if colorStyle:
- return colorStyle.backgroundColor.wxColor()
- return self.getEventsBackgroundColor()
-
- def getEventOutlineColor(self, event):
- colorStyle = self.getEventColorStyle(event)
- if colorStyle:
- return colorStyle.foregroundColor.wxColor()
- return self.getEventsOutlineColor()
-
- def getEventsBackgroundColor(self):
- # avoid copy-on-write if possible, until I make self.calendarData copy-on-write
- if not isinstance(self.contents.source, CalendarData):
- return wx.WHITE
- return self.calendarData.calendarColor.backgroundColor.wxColor()
- return wx.Color(bc.red, bc.green, bc.blue)
-
- def getEventsOutlineColor(self):
- if not isinstance(self.contents.source, CalendarData):
- return wx.Color(102, 102, 102)
- fc = self.calendarData.calendarColor.foregroundColor
- return wx.Color(fc.red, fc.green, fc.blue)
-
- def setEventsColor(self, color):
- # just need to set attributes on these two locals
- bc = self.calendarData.calendarColor.backgroundColor
- fc = self.calendarData.calendarColor.foregroundColor
+ return coll
+ return self.calendarData
- c = color.Get()
- (bc.red, bc.green, bc.blue) = c
-
- fc_rgb = get_outline_color(*color2rgb(*c))
- (fc.red, fc.green, fc.blue) = rgb2color(*fc_rgb)
-
+ def setCalendarColor(self, color):
+ """
+ Set the base color from which all tints are determined. Note that
+ this will lazily stamp the selected collection
+ """
+ ec = copy.copy(self.calendarData.eventColor)
+ (ec.red, ec.green, ec.blue) = color
+ self.calendarData.eventColor = ec
+
class wxCalendarCanvas(CollectionCanvas.wxCollectionCanvas):
"""
Base class for all calendar canvases - handles basic item selection,
@@ -765,11 +852,26 @@
self.Bind(wx.EVT_SIZE, self.OnSize)
+ # gradient cache
+ self._gradientCache = {}
+
def _doDrawingCalculations(self):
self.size = self.GetSize()
self.xOffset = (self.size.width - self.scrollbarWidth) / 8
+
+ try:
+ oldDayWidth = self.dayWidth
+ except AttributeError:
+ oldDayWidth = -1
+
self.dayWidth = (self.size.width - self.scrollbarWidth - self.xOffset) / self.blockItem.daysPerView
+
+ # the gradient brushes are based on dayWidth, so blow it away
+ # when dayWidth changes
+ if oldDayWidth != self.dayWidth:
+ self._gradientCache = {}
+
if self.blockItem.dayMode:
self.columns = 1
else:
@@ -821,10 +923,62 @@
self.Layout()
def OnSelectColor(self, event):
- self.blockItem.setEventsColor(event.GetValue())
+ c = event.GetValue().Get()
+ self.blockItem.setCalendarColor(c)
# just cause a repaint - hopefully this cascades to child windows?
self.Refresh()
+
+ def MakeGradientBrush(self, leftColor, rightColor):
+ """
+ Creates a gradient brush from leftColor to rightColor, specified
+ as color tuples (r,g,b)
+ The brush is a bitmap, width of self.dayWidth, height 1. The color
+ gradient is made by varying the color saturation from leftColor to
+ rightColor. This means that the Hue and Value should be the same,
+ or the resulting color on the right won't match rightColor
+ """
+
+ # There is probably a nicer way to do this, without:
+ # - going through wxImage
+ # - individually setting each RGB pixel
+ image = wx.EmptyImage(self.dayWidth, 1)
+ leftHSV = rgb_to_hsv(*color2rgb(*leftColor))
+ rightHSV = rgb_to_hsv(*color2rgb(*rightColor))
+
+ # make sure they are the same hue
+ # this doesn't quite work, because sometimes division issues
+ # cause numbers to be very close, but not quite the same
+ #assert leftHSV[0] == rightHSV[0]
+ #assert leftHSV[2] == rightHSV[2]
+
+ hue = leftHSV[0]
+ value = leftHSV[2]
+ satRange = rightHSV[1] - leftHSV[1]
+
+ # assign a sliding scale of floating point values from left to right
+ # in the bitmap
+ for x, sat in enumerate(frange(leftHSV[1], rightHSV[1], satRange/self.dayWidth)):
+ newColor = rgb2color(*hsv_to_rgb(hue, sat, value))
+ image.SetRGB(x,0,*newColor)
+
+ # and now we have to go from Image -> Bitmap. Yuck.
+ bitmap = wx.BitmapFromImage(image)
+ brush = wx.Brush(leftColor)
+ brush.SetStipple(bitmap)
+ return brush
+
+ def GetGradientBrush(self, leftColor, rightColor):
+ """
+ Gets an appropriately sized gradient brush from the cache,
+ or creates one if necessary
+ """
+ brush = self._gradientCache.get((leftColor, rightColor), None)
+ if not brush:
+ brush = self.MakeGradientBrush(leftColor, rightColor)
+ self._gradientCache[(leftColor, rightColor)] = brush
+ return brush
+
class wxWeekHeaderWidgets(wx.Panel):
@@ -930,7 +1084,7 @@
return
# update the calendar with the calender's color
- self.colorSelect.SetColour(self.parent.blockItem.getEventsBackgroundColor())
+ self.colorSelect.SetColour(self.parent.blockItem.calendarData.eventColor.wxColor())
# Update the month button given the selected date
lastDate = startDate + DateTime.RelativeDateTime(days=6)
@@ -1052,25 +1206,10 @@
def DrawCells(self, dc):
- """
- import traceback
- print "\nDrawCells!"
-
- def PrintStackEntry(entry):
- print " " + entry[2] + " @ " + entry[0][30:] + ":" + str(entry[1])
-
- stack = traceback.extract_stack()
- #PrintStackEntry(stack[-1])
- #PrintStackEntry(stack[-2])
- PrintStackEntry(stack[-3])
- PrintStackEntry(stack[-4])
- PrintStackEntry(stack[-5])
- PrintStackEntry(stack[-6])
- """
styles = self.parent
- dc.SetTextForeground(styles.eventLabelColor)
+ #dc.SetTextForeground(styles.eventLabelColor)
dc.SetFont(styles.eventLabelFont)
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(wx.WHITE_BRUSH)
@@ -1084,11 +1223,19 @@
if self.parent.blockItem.selection is item:
selectedBox = canvasItem
else:
+ eventColors = styles.blockItem.getEventColors(item)
+ #dc.SetPen(wx.Pen(eventColors.outlineColor))
+ #dc.SetBrush(wx.Brush(eventColors.gradientLeft))
+ dc.SetTextForeground(eventColors.textColor)
canvasItem.Draw(dc, styles)
if selectedBox:
- dc.SetBrush(styles.selectionBrush)
- dc.SetPen(wx.Pen(styles.blockItem.getEventOutlineColor(selectedBox.GetItem())))
+ eventColors = styles.blockItem.getEventColors(selectedBox.GetItem())
+ dc.SetPen(wx.Pen(eventColors.selectedOutlineColor))
+ brush = styles.GetGradientBrush(eventColors.selectedGradientLeft,
+ eventColors.selectedGradientRight)
+ dc.SetBrush(brush)
+ dc.SetTextForeground(eventColors.selectedTextColor)
selectedBox.Draw(dc, styles)
@@ -1414,22 +1561,28 @@
for canvasItem in self.canvasItemList:
item = canvasItem.GetItem()
- drawingPen = wx.Pen(styles.blockItem.getEventOutlineColor(item))
- drawingBrush = wx.Brush(styles.blockItem.getEventBackgroundColor(item))
- dc.SetPen(drawingPen)
- dc.SetBrush(drawingBrush)
# save the selected box to be drawn last
if self.parent.blockItem.selection is item:
selectedBox = canvasItem
else:
+ eventColors = styles.blockItem.getEventColors(item)
+ dc.SetPen(wx.Pen(eventColors.outlineColor))
+ dc.SetBrush(styles.GetGradientBrush(eventColors.gradientLeft,
+ eventColors.gradientRight))
+ dc.SetTextForeground(eventColors.textColor)
+
canvasItem.Draw(dc, boundingRect, styles)
# now draw the current item on top of everything else
if selectedBox:
item = selectedBox.GetItem()
- dc.SetPen(wx.Pen(styles.blockItem.getEventOutlineColor(item)))
- dc.SetBrush(styles.selectionBrush)
+ eventColors = styles.blockItem.getEventColors(item)
+ dc.SetPen(wx.Pen(eventColors.selectedOutlineColor))
+ brush = styles.GetGradientBrush(eventColors.selectedGradientLeft,
+ eventColors.selectedGradientRight)
+ dc.SetBrush(brush)
+ dc.SetTextForeground(eventColors.selectedTextColor)
selectedBox.Draw(dc, boundingRect, styles)
More information about the Commits
mailing list