[Commits] (stearns) A lot of, but not all, attribute editor refactoring:

commits at osafoundation.org commits at osafoundation.org
Mon Apr 25 13:49:24 PDT 2005


Commit by: stearns
Modified files:
chandler/parcels/osaf/examples/zaobao/blocks/parcel.xml 1.4 1.5
chandler/parcels/osaf/framework/attributeEditors/AttributeEditors.py 1.30 1.31
chandler/parcels/osaf/framework/blocks/ControlBlocks.py 1.196 1.197
chandler/parcels/osaf/framework/blocks/DrawingUtilities.py 1.2 1.3
chandler/parcels/osaf/framework/blocks/Trunk.py 1.12 1.13
chandler/parcels/osaf/framework/blocks/parcel.xml 1.162 1.163
chandler/parcels/osaf/framework/blocks/detail/Detail.py 1.113 1.114
chandler/parcels/osaf/framework/blocks/detail/parcel.xml 1.74 1.75

Log message:
A lot of, but not all, attribute editor refactoring:
- removed support for label-on-left; labels are independent blocks now.
- rewrote support for label-in-place (aka "sample text")

and...
- fixed several bugs:
2814: Title truncates when you stamp an mail message to a task
2856: Detail view fields not aligned in the same line
2343: Title needs to be label-in-place
- several small debugging-support changes

Not there yet:
- Only the textboxes are AEs still; I've written the checkbox
 and popup-menu AEs, but they're not hooked in yet.
- Font sizing to look better between platforms
- More precise vertical sizing (hopefully dynamic based on font size)
- Automatic horizontal sizing of labels (they're too wide now, but at least they line up!)

Known problems hopefully fixed by the next wx version:
- stamping or clicking all-day (and similar operations that result
 in the detail view tree being rebuilt while the focus is on a detail
 view control) crash Chandler on Mac. (Bug 2857)

Bugzilla links:
http://bugzilla.osafoundation.org/show_bug.cgi?id=2814
http://bugzilla.osafoundation.org/show_bug.cgi?id=2856
http://bugzilla.osafoundation.org/show_bug.cgi?id=2343
http://bugzilla.osafoundation.org/show_bug.cgi?id=2857

ViewCVS links:
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/examples/zaobao/blocks/parcel.xml.diff?r1=text&tr1=1.4&r2=text&tr2=1.5
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/attributeEditors/AttributeEditors.py.diff?r1=text&tr1=1.30&r2=text&tr2=1.31
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/blocks/ControlBlocks.py.diff?r1=text&tr1=1.196&r2=text&tr2=1.197
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/blocks/DrawingUtilities.py.diff?r1=text&tr1=1.2&r2=text&tr2=1.3
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/blocks/Trunk.py.diff?r1=text&tr1=1.12&r2=text&tr2=1.13
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/blocks/parcel.xml.diff?r1=text&tr1=1.162&r2=text&tr2=1.163
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/blocks/detail/Detail.py.diff?r1=text&tr1=1.113&r2=text&tr2=1.114
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/blocks/detail/parcel.xml.diff?r1=text&tr1=1.74&r2=text&tr2=1.75

Index: chandler/parcels/osaf/framework/blocks/DrawingUtilities.py
diff -u chandler/parcels/osaf/framework/blocks/DrawingUtilities.py:1.2 chandler/parcels/osaf/framework/blocks/DrawingUtilities.py:1.3
--- chandler/parcels/osaf/framework/blocks/DrawingUtilities.py:1.2	Wed Apr 20 18:16:22 2005
+++ chandler/parcels/osaf/framework/blocks/DrawingUtilities.py	Mon Apr 25 13:49:21 2005
@@ -3,7 +3,7 @@
 __copyright__ = "Copyright (c) 2003-2005 Open Source Applications Foundation"
 __license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
 
-import wx, os
+import wx, os, random
 
 def SetTextColorsAndFont (grid, attr, dc, isSelected):
     """
@@ -31,6 +31,9 @@
     x = rect.x + 1
     y = rect.y + 1
     for line in str (string).split (os.linesep):
+        # test for flicker by drawing a random character first each time we draw
+        # line = chr(ord('a') + random.randint(0,25)) + line
+        
         dc.DrawText (line, x, y)
         lineWidth, lineHeight = dc.GetTextExtent (line)
         # If the text doesn't fit within the box we want to clip it and

Index: chandler/parcels/osaf/examples/zaobao/blocks/parcel.xml
diff -u chandler/parcels/osaf/examples/zaobao/blocks/parcel.xml:1.4 chandler/parcels/osaf/examples/zaobao/blocks/parcel.xml:1.5
--- chandler/parcels/osaf/examples/zaobao/blocks/parcel.xml:1.4	Thu Apr 21 21:08:55 2005
+++ chandler/parcels/osaf/examples/zaobao/blocks/parcel.xml	Mon Apr 25 13:49:20 2005
@@ -63,6 +63,7 @@
     <childrenBlocks itemref="doc:LinkLabel"/>
     <childrenBlocks itemref="doc:LinkAttribute"/>
     <stretchFactor>0.0</stretchFactor>
+    <border>5, 0, 0, 5</border>
   </ContentItemDetail>
 
   <CharacterStyle itsName="LinkStyle">
@@ -81,8 +82,8 @@
     <characterStyle itemref="detail:LabelStyle"/>
     <stretchFactor>0.0</stretchFactor>
     <textAlignmentEnum>Right</textAlignmentEnum>    
-    <minimumSize>70, 24</minimumSize>
-    <border>0.0, 0.0, 0.0, 5.0</border>
+    <minimumSize>80, 24</minimumSize>
+    <border>0, 0, 0, 5</border>
   </StaticText>
   
   <StaticText itsName="LinkAttribute" itemClass="osaf.examples.zaobao.blocks.LinkDetail">
@@ -101,6 +102,7 @@
     <childrenBlocks itemref="doc:CategoryLabel"/>
     <childrenBlocks itemref="doc:CategoryAttribute"/>
     <stretchFactor>0.0</stretchFactor>
+    <border>5, 0, 0, 5</border>
   </ContentItemDetail>
 
   <StaticText itsName="CategoryLabel" itemClass="osaf.framework.blocks.detail.Detail.StaticRedirectAttributeLabel">
@@ -108,8 +110,8 @@
     <characterStyle itemref="detail:LabelStyle"/>
     <stretchFactor>0.0</stretchFactor>
     <textAlignmentEnum>Right</textAlignmentEnum>    
-    <minimumSize>70, 24</minimumSize>
-    <border>0.0, 0.0, 0.0, 5.0</border>
+    <minimumSize>80, 24</minimumSize>
+    <border>0, 0, 0, 5</border>
   </StaticText>
   
   <StaticText itsName="CategoryAttribute" itemClass="osaf.framework.blocks.detail.Detail.StaticRedirectAttribute">
@@ -121,11 +123,12 @@
   
   <!-- Author area -->
   <ContentItemDetail itsName="AuthorArea" itemClass="osaf.framework.blocks.detail.Detail.DetailSynchronizedLabeledTextAttributeBlock">
-    <position>0.0</position>
+    <position>0.19</position>
     <selectedItemsAttribute>author</selectedItemsAttribute>
     <childrenBlocks itemref="doc:AuthorLabel"/>
     <childrenBlocks itemref="doc:AuthorAttribute"/>
     <stretchFactor>0.0</stretchFactor>
+    <border>5, 0, 0, 5</border>
   </ContentItemDetail>
   
   <StaticText itsName="AuthorLabel" itemClass="osaf.framework.blocks.detail.Detail.StaticRedirectAttributeLabel">
@@ -133,8 +136,8 @@
     <characterStyle itemref="detail:LabelStyle"/>
     <stretchFactor>0.0</stretchFactor>
     <textAlignmentEnum>Right</textAlignmentEnum>    
-    <minimumSize>70, 24</minimumSize>
-    <border>0.0, 0.0, 0.0, 5.0</border>
+    <minimumSize>80, 24</minimumSize>
+    <border>0, 0, 0, 5</border>
   </StaticText>
   
   <StaticText itsName="AuthorAttribute" itemClass="osaf.framework.blocks.detail.Detail.StaticRedirectAttribute">
@@ -151,22 +154,23 @@
     <childrenBlocks itemref="doc:DateLabel"/>
     <childrenBlocks itemref="doc:DateAttribute"/>
     <stretchFactor>0.0</stretchFactor>
+    <border>5, 0, 0, 5</border>
   </ContentItemDetail>
 
-  <StaticText itsName="DateAttribute" itemClass="osaf.framework.blocks.detail.Detail.StaticRedirectAttribute">
+  <StaticText itsName="DateLabel" itemClass="osaf.framework.blocks.detail.Detail.StaticRedirectAttributeLabel">
     <title>date</title>
     <characterStyle itemref="detail:LabelStyle"/>
     <stretchFactor>0.0</stretchFactor>
-    <textAlignmentEnum>Left</textAlignmentEnum>    
+    <textAlignmentEnum>Right</textAlignmentEnum>    
+    <minimumSize>80, 24</minimumSize>
+    <border>0, 0, 0, 5</border>
   </StaticText>
   
-  <StaticText itsName="DateLabel" itemClass="osaf.framework.blocks.detail.Detail.StaticRedirectAttributeLabel">
+  <StaticText itsName="DateAttribute" itemClass="osaf.framework.blocks.detail.Detail.StaticRedirectAttribute">
     <title>date</title>
     <characterStyle itemref="detail:LabelStyle"/>
     <stretchFactor>0.0</stretchFactor>
-    <textAlignmentEnum>Right</textAlignmentEnum>    
-    <minimumSize>70, 24</minimumSize>
-    <border>0.0, 0.0, 0.0, 5.0</border>
+    <textAlignmentEnum>Left</textAlignmentEnum>    
   </StaticText>
   
 </core:Parcel>

Index: chandler/parcels/osaf/framework/blocks/parcel.xml
diff -u chandler/parcels/osaf/framework/blocks/parcel.xml:1.162 chandler/parcels/osaf/framework/blocks/parcel.xml:1.163
--- chandler/parcels/osaf/framework/blocks/parcel.xml:1.162	Mon Apr 25 11:57:34 2005
+++ chandler/parcels/osaf/framework/blocks/parcel.xml	Mon Apr 25 13:49:21 2005
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="iso-8859-1"?>
 
-<!-- $Revision: 1.162 $ -->
-<!-- $Date: 2005/04/25 18:57:34 $ -->
+<!-- $Revision: 1.163 $ -->
+<!-- $Date: 2005/04/25 20:49:21 $ -->
 <!-- Copyright (c) 2003-2005 Open Source Applications Foundation -->
 <!-- License: http://osafoundation.org/Chandler_0.1_license_terms.htm -->
 
@@ -96,6 +96,11 @@
     <initialValue type="docSchema:orientationEnumType">Horizontal</initialValue>
   </Attribute>
 
+  <Attribute itsName="readOnly">
+    <type itemref="Boolean"/>
+    <initialValue type="Boolean">False</initialValue>
+  </Attribute>
+
   <Attribute itsName="columnHeadings">
     <type itemref="String"/>
     <cardinality>list</cardinality>
@@ -961,10 +966,7 @@
       <initialValue type="docSchema:EditText/textStyleEnumType" value="PlainText"/>
     </Attribute>
 
-    <Attribute itsName="readOnly">
-      <type itemref="Boolean"/>
-    </Attribute>
-
+    <attributes itemref="docSchema:readOnly"/>
     <attributes itemref="docSchema:textAlignmentEnum"/>
     <attributes itemref="docSchema:characterStyle"/>
 
@@ -1583,76 +1585,48 @@
   <Kind itsName="PresentationStyle">
     <displayName itsValue="Presentation Style"/>
 
-    <Enumeration itsName="LabelEnum">
-      <values>OnLeft</values>
-      <values>InPlace</values>
-    </Enumeration>
-
-    <Enumeration itsName="FormEnum">
-      <values>Text</values>
-      <values>Graphic</values>
-    </Enumeration>
-
-    <Attribute itsName="label">
-      <description>If you want the block to show a label, tell the AE where to put it</description>
-      <type itemref="docSchema:PresentationStyle/LabelEnum"/>
-    </Attribute>
-    <Attribute itsName="labelWidth">
-      <description>OnLeft label needs a width to use for the label portion</description>
-      <type itemref="Integer"/>
-      <initialValue type="Integer" value="0"/>
-    </Attribute>
-    <Attribute itsName="labelBorder">
-      <description>Border to use between the label and left or right edge</description>
-      <type itemref="Integer"/>
-      <initialValue type="Integer" value="0"/>
-    </Attribute>
-    <Attribute itsName="labelTextAlignmentEnum">
-      <description>Text alignment enum for the label</description>
-      <type itemref="docSchema:textAlignmentEnumType"/>
-      <initialValue type="docSchema:textAlignmentEnumType" value="Right"/>
+    <Attribute itsName="sampleText">
+      <description>Localized in-place sample text (optional); if "", will use the attr's displayName.</description>
+      <type itemref="String"/>
     </Attribute>
-    <Attribute itsName="labelDisplayName">
-      <description>Localized display name to use for the label</description>
+
+    <Attribute itsName="format">
+      <description>customization of presentation format</description>
       <type itemref="String"/>
     </Attribute>
-    <Attribute itsName="form">
-      <description>Overall selector for text form versus graphic form</description>
-      <issues>Currently unused</issues>
-      <type itemref="docSchema:PresentationStyle/FormEnum"/>
+    
+    <Attribute itsName="choices">
+      <description>options for multiple-choice values</description>
+      <type itemref="String"/>
+      <cardinality>list</cardinality>
     </Attribute>
     
+    <Attribute itsName="useControl">
+      <description>True if we should always present the control (instead of waiting for a click to look editable)</description>
+      <type itemref="Boolean"/>
+    </Attribute>
+
     <Cloud itsName="DefaultCloud">
-      <Endpoint itsName="label">
-          <attribute value="label"/>
-          <includePolicy value="byValue"/>
-      </Endpoint>
-      <endpoints itemref="docSchema:PresentationStyle/DefaultCloud/label"/>
-      <Endpoint itsName="labelWidth">
-          <attribute value="labelWidth"/>
-          <includePolicy value="byValue"/>
-      </Endpoint>
-      <endpoints itemref="docSchema:PresentationStyle/DefaultCloud/labelWidth"/>
-      <Endpoint itsName="labelBorder">
-          <attribute value="labelBorder"/>
+      <Endpoint itsName="sampleText">
+          <attribute value="sampleText"/>
           <includePolicy value="byValue"/>
       </Endpoint>
-      <endpoints itemref="docSchema:PresentationStyle/DefaultCloud/labelBorder"/>
-      <Endpoint itsName="labelTextAlignmentEnum">
-          <attribute value="labelTextAlignmentEnum"/>
+      <endpoints itemref="docSchema:PresentationStyle/DefaultCloud/sampleText"/>
+      <Endpoint itsName="format">
+          <attribute value="format"/>
           <includePolicy value="byValue"/>
       </Endpoint>
-      <endpoints itemref="docSchema:PresentationStyle/DefaultCloud/labelTextAlignmentEnum"/>
-      <Endpoint itsName="labelDisplayName">
-          <attribute value="labelDisplayName"/>
+      <endpoints itemref="docSchema:PresentationStyle/DefaultCloud/format"/>
+      <Endpoint itsName="choices">
+          <attribute value="choices"/>
           <includePolicy value="byValue"/>
       </Endpoint>
-      <endpoints itemref="docSchema:PresentationStyle/DefaultCloud/labelDisplayName"/>
-      <Endpoint itsName="form">
-          <attribute value="form"/>
+      <endpoints itemref="docSchema:PresentationStyle/DefaultCloud/choices"/>
+      <Endpoint itsName="useControl">
+          <attribute value="useControl"/>
           <includePolicy value="byValue"/>
       </Endpoint>
-      <endpoints itemref="docSchema:PresentationStyle/DefaultCloud/form"/>
+      <endpoints itemref="docSchema:PresentationStyle/DefaultCloud/useControl"/>
     </Cloud>
     <clouds alias="default" itemref="docSchema:PresentationStyle/DefaultCloud"/>
 
@@ -1670,7 +1644,7 @@
     </Attribute>
 
     <attributes itemref="docSchema:characterStyle"/>
-    <attributes itemref="docSchema:EditText/readOnly"/>
+    <attributes itemref="docSchema:readOnly"/>
 
     <Cloud itsName="DefaultCloud">
         <Endpoint itsName="characterStyle">

Index: chandler/parcels/osaf/framework/blocks/ControlBlocks.py
diff -u chandler/parcels/osaf/framework/blocks/ControlBlocks.py:1.196 chandler/parcels/osaf/framework/blocks/ControlBlocks.py:1.197
--- chandler/parcels/osaf/framework/blocks/ControlBlocks.py:1.196	Wed Apr 20 18:16:22 2005
+++ chandler/parcels/osaf/framework/blocks/ControlBlocks.py	Mon Apr 25 13:49:21 2005
@@ -1,5 +1,5 @@
-__version__ = "$Revision: 1.196 $"
-__date__ = "$Date: 2005/04/21 01:16:22 $"
+__version__ = "$Revision: 1.197 $"
+__date__ = "$Date: 2005/04/25 20:49:21 $"
 __copyright__ = "Copyright (c) 2003-2005 Open Source Applications Foundation"
 __license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
 
@@ -15,11 +15,12 @@
 import wx.gizmos
 import wx.grid
 import webbrowser # for opening external links
-from osaf.framework.attributeEditors.AttributeEditors import IAttributeEditor
+import osaf.framework.attributeEditors.AttributeEditors as AttributeEditors
+import osaf.framework.blocks.DrawingUtilities as DrawingUtilities
+
 from repository.schema.Types import DateTime
 from repository.schema.Types import RelativeDateTime
 import mx.DateTime
-import osaf.framework.blocks.DrawingUtilities as DrawingUtilities
 
 class Button(RectangularChild):
     def instantiateWidget(self):
@@ -399,7 +400,7 @@
         self.widget.GoToItem (self.selection)
 
 
-class wxTableData (wx.grid.PyGridTableBase):
+class wxTableData(wx.grid.PyGridTableBase):
     def __init__(self, *arguments, **keywords):
         super (wxTableData, self).__init__ (*arguments, **keywords)
         self.defaultRWAttribute = wx.grid.GridCellAttr()
@@ -450,7 +451,7 @@
         attribute = self.base_GetAttr (row, column, kind)
         if attribute is None:
             type = self.GetTypeName (row, column)
-            delegate = IAttributeEditor.GetAttributeEditorSingleton (type)
+            delegate = AttributeEditors.getSingleton (type)
             attribute = self.defaultROAttribute
             """
               An apparent bug in table asks for an attribute even when
@@ -502,17 +503,15 @@
         background = wx.SystemSettings.GetColour (wx.SYS_COLOUR_HIGHLIGHT)
         self.SetLightSelectionBackground()
 
+        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+        self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus)
+        self.Bind(wx.EVT_SET_FOCUS, self.OnGainFocus)
         self.Bind(wx.EVT_SIZE, self.OnSize)
-        self.Bind(wx.grid.EVT_GRID_COL_SIZE, self.OnColumnDrag)
-        self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self.OnRangeSelect)
-        self.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, self.OnRightClick)
         self.Bind(wx.grid.EVT_GRID_CELL_BEGIN_DRAG, self.OnItemDrag)
-        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
         self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnLeftClick)
-        self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnLeftClick)
-        self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.OnLeftClick)
-        self.Bind(wx.EVT_SET_FOCUS, self.OnGainFocus)
-        self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus)
+        self.Bind(wx.grid.EVT_GRID_CELL_RIGHT_CLICK, self.OnRightClick)
+        self.Bind(wx.grid.EVT_GRID_COL_SIZE, self.OnColumnDrag)
+        self.Bind(wx.grid.EVT_GRID_RANGE_SELECT, self.OnRangeSelect)
 
     def OnGainFocus (self, event):
         self.SetSelectionBackground (wx.SystemSettings.GetColour (wx.SYS_COLOUR_HIGHLIGHT))
@@ -800,28 +799,26 @@
 class GridCellAttributeRenderer (wx.grid.PyGridCellRenderer):
     def __init__(self, type):
         super (GridCellAttributeRenderer, self).__init__ ()
-        self.delegate = IAttributeEditor.GetAttributeEditorSingleton (type)
-
+        self.delegate = AttributeEditors.getSingleton (type)
 
-    def Draw (self, grid, attr, dc, rect, row, column, isSelected):
+    def Draw (self, grid, attr, dc, rect, row, column, isInSelection):
         """
           Currently only handles left justified multiline text
         """
-        DrawingUtilities.SetTextColorsAndFont (grid, attr, dc, isSelected)
+        DrawingUtilities.SetTextColorsAndFont (grid, attr, dc, isInSelection)
         item, attributeName = grid.GetElementValue (row, column)
-        self.delegate.Draw (dc, rect, item, attributeName, isSelected)
-
+        self.delegate.Draw (dc, rect, item, attributeName, isInSelection)
 
 class GridCellAttributeEditor (wx.grid.PyGridCellEditor):
     def __init__(self, type):
         super (GridCellAttributeEditor, self).__init__ ()
-        self.delegate = IAttributeEditor.GetAttributeEditorSingleton (type)
+        self.delegate = AttributeEditors.getSingleton (type)
 
     def Create (self, parent, id, evtHandler):
         """
           Create an edit control to edit the text
         """
-        self.control = self.delegate.Create (parent, id) # create Attribute Editor control
+        self.control = self.delegate.CreateControl(parent, id)
         self.SetControl (self.control)
         if evtHandler:
             self.control.PushEventHandler (evtHandler)
@@ -833,11 +830,18 @@
         pass
 
     def BeginEdit (self, row,  column, grid):
+        assert getattr(self, 'editingCell', None) is None
+        self.editingCell = (row, column)
+        
         item, attributeName = grid.GetElementValue (row, column)
         self.initialValue = self.delegate.GetAttributeValue (item, attributeName)
-        self.delegate.BeginControlEdit (self.control, self.initialValue)
+        self.delegate.BeginControlEdit (item, attributeName, self.control)
+        self.control.SetFocus()
 
     def EndEdit (self, row, column, grid):
+        assert self.editingCell == (row, column)
+        self.editingCell = None
+
         value = self.delegate.GetControlValue (self.control)
         item, attributeName = grid.GetElementValue (row, column)
         if value == self.initialValue:
@@ -862,9 +866,9 @@
         self.delegate.SetControlValue (self.control, self.initialValue)
 
     def GetValue (self):
+        assert False # who needs this?
         return self.delegate.GetControlValue (self.control)
 
-
 class Table (RectangularChild):
     def __init__(self, *arguments, **keywords):
         super (Table, self).__init__ (*arguments, **keywords)
@@ -875,13 +879,12 @@
         widget.SetDefaultRenderer (GridCellAttributeRenderer (defaultName))
         map = wx.GetApp().UIRepositoryView.findPath('//parcels/osaf/framework/attributeEditors/AttributeEditors')
         for key in map.editorString.keys():
-            if key != defaultName:
+            if key != defaultName and not '+' in key:
                 widget.RegisterDataType (key,
                                          GridCellAttributeRenderer (key),
                                          GridCellAttributeEditor (key))
         return widget
 
-
     def onSetContentsEvent (self, event):
         item = event.arguments ['item']
         if isinstance (item, ItemCollection):
@@ -948,6 +951,9 @@
             style = wx.ALIGN_CENTRE
         elif self.textAlignmentEnum == "Right":
             style = wx.ALIGN_RIGHT
+            
+        # @@@BJS For my own debugging
+        # style |= wx.SIMPLE_BORDER
 
         staticText = wxStaticText (self.parentBlock.widget,
                                    -1,
@@ -1383,122 +1389,153 @@
         # superclass sync will handle shown-ness
         super(wxAEBlock, self).wxSynchronizeWidget()
 
+        # Make sure we've got the appropriate editor. (We could do the lookup 
+        # at init time, but then value-based lookups won't be right.)
         block = self.blockItem
-        if not block.isShown:
-            self.destroyControl()
-            return
-
-        # lookup the appropriate editor
-        # could do the lookup at init time, but then value-based lookups won't be right.
-        editor = self.blockItem.lookupEditor(self.editor)
-        if editor is not self.editor:
+        newEditor = block.isShown and self.blockItem.lookupEditor(self.editor) or None
+        if self.editor is not newEditor:
             self.destroyControl()
-            self.editor = editor
+            self.editor = newEditor
+            
+            if newEditor is None:
+                return
+            
+            # Give the editor a chance to create its control early
+            if self.editor.UsePermanentControl():
+                self.createControl()
+                self.editor.BeginControlEdit(self.blockItem.getItem(), 
+                                             self.blockItem.getAttributeName(),
+                                             self.control)
 
         # redraw
-        self.redrawAEBlock()
+        self.Refresh()
 
     def onClick(self, event):
         """
           A click has occured.  Prepare to edit the value, if editable.
         """
-        # if already created a control, then return
-        if self.control is not None:
-            return
-
-        # consume the event
-        event.Skip()
-
-        # return if editing is not allowed
-        block = self.blockItem
-        if block.readOnly:  # the block could be readOnly
-            return
         editor = self.editor
+        assert editor is not None
+        
+        block = self.blockItem
         item = block.getItem()
-        attribute = block.getAttributeName()
-        if editor.ReadOnly ((item, attribute)):  # The editor might not allow editing
-            return
+        attributeName = block.getAttributeName()
 
-        # create the control to use for editing
-        self.createControl()
+        if self.control is None:
+            # Create the control
+            # return if editing is not allowed
+            if block.readOnly:  # the block could be readOnly
+                logger.debug("wxAEBlock.onClick: ignoring: block is readonly.")
+                return
+            if editor.ReadOnly ((item, attributeName)):  # The editor might not allow editing
+                logger.debug("wxAEBlock.onClick: ignoring: editor is readonly.")
+                return
+    
+            # create the control to use for editing
+            logger.debug("wxAEBlock.onClick: creating control.")
+            self.createControl()
+        else:
+            # Show the control we've already got
+            assert not self.control.IsShown()
+            self.control.Show()
+            logger.debug("wxAEBlock.onClick: showing existing control.")
+
+        # Begin editing
+        editor.BeginControlEdit(item, attributeName, self.control)
+        self.control.SetFocus()
 
-        # begin editing
-        curValue = editor.GetAttributeValue(item, attribute)
-        editor.BeginControlEdit(self.control, curValue)
+        # consume the event
+        # @@@BJS: might not want to do this, if that allows first-clicks to 
+        # go into the textbox and place the insertion point...
+        # event.Skip()
 
         # redraw
-        self.redrawAEBlock()
+        # @@@BJS: needed? was: self.drawAEBlock()
+        # if not, refactor drawAEBlock into OnPaint
 
     def OnPaint(self, paintEvent):
         """
           Need to update a portion of ourself.  Ask the control to draw in the update region.
         """
-        paintDC = wx.PaintDC(self)
-        paintDC.SetBrush (wx.TRANSPARENT_BRUSH)
-        self.drawAEBlock(paintDC)
+        if self.editor is not None: # Ignore paints until we've been sync'd.
+            self.drawAEBlock(wx.PaintDC(self))
 
-    def redrawAEBlock(self):
+    def drawAEBlock(self, dc=None):
         """
-          Redraw the control.  
-
-        If editable, a control exists, and it will redraw itself.  
-        Otherwise we call the Attribute Editor to do the drawing.
+        Draw ourself.
         """
-        clientDC = wx.ClientDC(self)
-        clientDC.SetBrush (wx.TRANSPARENT_BRUSH)
-        self.drawAEBlock(clientDC)
-
-    def drawAEBlock(self, dc):
+        assert self.editor is not None
+        
         block = self.blockItem
-        if block.isShown: 
+        if block.isShown:
             item = block.getItem ()
             if item is not None:
                 blockRect = self.GetRect() # use the rect of the AE Block
                 rect = wx.Rect(0, 0, blockRect.width, blockRect.height)
                 attributeName = block.getAttributeName()
-                isSelected = self.control is not None
-                self.ensureEditor()
+                
+                if dc is None:
+                    dc = wx.ClientDC(self)
+                    
                 font = self.GetFont()
                 dc.SetFont(font)
-                self.editor.Draw(dc, rect, item, attributeName, isSelected)
-
-    def ensureEditor(self):
-        if self.editor is None:
-            self.editor = self.blockItem.lookupEditor()
+                dc.SetBrush(wx.TRANSPARENT_BRUSH)
+                self.editor.Draw(dc, rect, item, attributeName)
 
     def createControl(self):
         # create the control to use for editing
-        control = self.editor.Create(self.blockItem.widget, -1)
-        control.Bind(wx.EVT_KILL_FOCUS, self.onLoseFocusFromControl)
-        control.Bind(wx.EVT_KEY_UP, self.OnKeyPressedFromControl)
-        self.control = control # remember the widget created (aka the control)
+        assert self.control is None
+        self.control = self.editor.CreateControl(self, -1)
+        
+        # @@@BJS: Todo: get the editor's control to handle both these events, and notify us
+        self.control.Bind(wx.EVT_SET_FOCUS, self.onGainFocusFromControl)
+        self.control.Bind(wx.EVT_KILL_FOCUS, self.onLoseFocusFromControl)
+        self.control.Bind(wx.EVT_KEY_UP, self.OnKeyUpFromControl)
 
     def destroyControl(self):
+        if self.editor is None:
+            assert self.control is None
+            return
+        
         if self.control is None:
             return
-
-        wx.CallAfter(self.control.Destroy) # destroy this control next idle.
+        
+        wx.CallAfter(self.control.Destroy)
         self.control = None
 
+    def onGainFocusFromControl(self, event):
+        """
+          The control got the focus
+        """
+        logger.debug("wxAEBlock: control gained focus")
+
     def onLoseFocusFromControl(self, event):
         """
           The control lost focus - we're finishing editing in the control.
         """
-        # return if there's no control
+        logger.debug("wxAEBlock: control lost focus")
+        
+        # @@@BJS: needed? return if there's no control
+        assert self.control
         if self.control is None:
             return
 
         item = self.blockItem.getItem()
         attributeName = self.blockItem.getAttributeName()
         self.editor.EndControlEdit(item, attributeName, self.control)
-        self.destroyControl()
+        if not self.editor.UsePermanentControl():
+            self.control.Hide()
 
-        self.wxSynchronizeWidget() #resync, so we'll draw without the control.
         event.Skip()
 
-    def OnKeyPressedFromControl(self, event):
+    def OnKeyUpFromControl(self, event):
         if event.m_keyCode == wx.WXK_RETURN:
+            # @@@ On PC, Hide causes loss of focus. On Mac, it doesn't
+            # Do the extra EndControlEdit here.
             self.editor.EndControlEdit(self.blockItem.getItem(), self.blockItem.getAttributeName(), self.control)
+            if not self.editor.UsePermanentControl():
+                self.control.Hide()
+            # @@@ Should do the tab thing
         else:
             event.Skip()
 
@@ -1519,7 +1556,12 @@
                             -1,
                             wx.DefaultPosition,
                             (self.minimumSize.width, self.minimumSize.height))
-        widget.SetFont(Font (self.characterStyle))
+        try:
+            charStyle = self.characterStyle
+        except AttributeError:
+            pass
+        else:
+            widget.SetFont(Font (charStyle))
         return widget
 
     def lookupEditor(self, oldEditor):
@@ -1527,31 +1569,26 @@
         typeName = self.getItemAttributeTypeName()
         item = self.getItem()
         attributeName = self.getAttributeName()
-        try:
-            presentationStyle = self.presentationStyle
-        except AttributeError:
-            presentationStyle = None
-            
+        
+        presentationStyle = getattr(self, 'presentationStyle', None)            
         if (oldEditor is not None) and (oldEditor.typeName == typeName) and \
            (oldEditor.attributeName == attributeName) and \
            (oldEditor.presentationStyle is presentationStyle):
+            assert oldEditor.item is item # this shouldn't've changed.
             return oldEditor
         
-        selectedEditor = IAttributeEditor.GetAttributeEditorInstance (typeName, 
-                                        item, attributeName, presentationStyle)
+        selectedEditor = AttributeEditors.getInstance\
+                       (typeName, item, attributeName, presentationStyle)
 
         return selectedEditor
 
-    def getItem(self):
-        # Get the Item connected to this block
-        try:
-            item = self.contents
-        except AttributeError:
-            try:
-                item = self.detailRoot().selectedItem() # @@@DLD fix Detail-View specific code
-            except AttributeError:
-                item = None
-        return item
+    def onSetContentsEvent (self, event):
+        newContents = event.arguments['item']
+        if getattr(self, 'contents', None) is not newContents:
+            self.contents = newContents
+
+    def getItem(self):        
+        return getattr(self, 'contents', None)
 
     def getAttributeName(self):
         attributeName = self.viewAttribute
@@ -1563,19 +1600,21 @@
         if item is None:
             return None
 
-        # if the attribute has a value, use it's type's name
+        # Ask the schema for the attribute's type first
         attributeName = self.getAttributeName()
         try:
-            attrValue = getattr(item, attributeName)
-            typeName = type(attrValue).__name__
+            theType = item.getAttributeAspect(attributeName, "type")
         except:
-
-            # if the attribute has no value, we must use the schema to determine type
+            # If the repository doesn't know about it (it might be a property),
+            # get its value and use its type
             try:
-                theType = item.getAttributeAspect(attributeName, "type")
+                attrValue = getattr(item, attributeName)
             except:
                 typeName = "_default"
             else:
-                typeName = theType.itsName
+                typeName = type(attrValue).__name__
+        else:
+            typeName = theType.itsName
+        
         return typeName
 

Index: chandler/parcels/osaf/framework/attributeEditors/AttributeEditors.py
diff -u chandler/parcels/osaf/framework/attributeEditors/AttributeEditors.py:1.30 chandler/parcels/osaf/framework/attributeEditors/AttributeEditors.py:1.31
--- chandler/parcels/osaf/framework/attributeEditors/AttributeEditors.py:1.30	Fri Apr 22 13:54:13 2005
+++ chandler/parcels/osaf/framework/attributeEditors/AttributeEditors.py	Mon Apr 25 13:49:20 2005
@@ -1,5 +1,5 @@
-__version__ = "$Revision: 1.30 $"
-__date__ = "$Date: 2005/04/22 20:54:13 $"
+__version__ = "$Revision: 1.31 $"
+__date__ = "$Date: 2005/04/25 20:49:20 $"
 __copyright__ = "Copyright (c) 2003-2005 Open Source Applications Foundation"
 __license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
 
@@ -9,15 +9,62 @@
 import osaf.contentmodel.tasks.Task as Task
 import osaf.contentmodel.calendar.Calendar as Calendar
 import repository.item.ItemHandler as ItemHandler
-import osaf.framework.blocks.Styles as Styles
+import repository.item.Query as ItemQuery
 import repository.query.Query as Query
 import osaf.framework.blocks.DrawingUtilities as DrawingUtilities
+import osaf.framework.blocks.Styles as Styles
+import logging
+from operator import itemgetter
 
-class IAttributeEditor (object):
-    """ CPIA Attribute Editor base class """
-    _TypeToEditorInstances = {}
+logger = logging.getLogger('ae')
+logger.setLevel(logging.INFO)
 
-    def __init__(self, isShared, presentationStyle=None):
+_TypeToEditorInstances = {}
+
+def getSingleton (typeName):
+    """ Get (and cache) a single shared Attribute Editor for this type. """
+    try:
+        instance = _TypeToEditorInstances [typeName]
+    except KeyError:
+        aeClass = _getAEClass (typeName)
+        instance = aeClass (True, typeName, item=None, attributeName=None, presentationStyle=None)
+        _TypeToEditorInstances [typeName] = instance
+    return instance
+
+def getInstance (typeName, item, attributeName, presentationStyle):
+    """ Get a new unshared instance of the Attribute Editor for this type (and optionally, format). """
+    try:
+        format = presentationStyle.format
+    except AttributeError:
+        format = None
+    aeClass = _getAEClass(typeName, format)
+    logger.debug("getAEClass(%s [%s, %s]) --> %s" % (attributeName, typeName, format, aeClass))    
+    instance = aeClass (False, typeName, item=item, attributeName=attributeName, presentationStyle=presentationStyle)        
+    return instance
+
+def _getAEClass (type, format=None):
+    """ Return the attribute editor class for this type """
+    map = wx.GetApp().UIRepositoryView.findPath('//parcels/osaf/framework/attributeEditors/AttributeEditors')
+    # If we have a format specified, try to find a specific 
+    # editor for type+form. If we don't, just use the type, 
+    # and if we don't have a type-specific one, use the "_default".
+    classPath = ((format is not None) and map.editorString.get("%s+%s" % (type, format), None)) \
+              or map.editorString.get(type, None)
+    if classPath is None: # do this separately for now so I can set a breakpoint
+        classPath = map.editorString.get("_default", None)
+    assert classPath is not None, "Default attribute editor doesn't exist ('_default')"
+
+    parts = classPath.split (".")
+    assert len(parts) >= 2, " %s isn't a module and class" % classPath
+    className = parts.pop ()
+    module = __import__ ('.'.join(parts), globals(), locals(), className)
+    assert module.__dict__[className], "Class %s doesn't exist" % classPath
+    aeClass = module.__dict__[className]
+    return aeClass
+
+class BaseAttributeEditor (object):
+    """ Base class for Attribute Editors. """
+    def __init__(self, isShared, typeName, item=None, attributeName=None, presentationStyle=None):
         """ 
         Create a shared, or unshared instance of an Attribute Editor. 
         
@@ -25,165 +72,316 @@
                   several values (e.g. Grid uses a single AE for a whole
                   column).
         @type isShared: boolean
+        @param typeName: the string name of this type
+        @type typeName: str
         @param presentationStyle: gives style information to the AE
         @type presentationStyle: reference to PresentationStyle item, or
                   None when isShared is True (default presentation).
         Unshared instances may store data in attributes of self, but
         that may cause trouble for shared Attribute Editors instances.
         """
+        self.isShared = isShared
+        
+        # Note the characteristics that made us pick this editor
+        self.typeName = typeName
+        self.attributeName = attributeName
+        self.presentationStyle = presentationStyle
+        
+        # And the item we're editing, if we have it
+        self.item = item
 
     def ReadOnly (self, (item, attribute)):
         """ Return True if this Attribute Editor refuses to edit """
-        
-    def Draw (self, dc, rect, item, attributeName, isSelected):
-        """ Draw the value of the attribute in the specified rect of the dc """
-        
-    def Create (self, parent, id):
-        """ Create and return a control to use for editing the attribute value. """
+        # By default, everything's editable.
+        return False
 
-    def BeginControlEdit (self, control, value):
-        """ Begin editing the value """
-        
-    def EndControlEdit (self, item, attributeName, control):
+    def Draw (self, dc, rect, item, attributeName, isInSelection=False):
+        """ Draw the value of the attribute in the specified rect of the dc """
+        raise NotImplementedError
+    
+    def UsePermanentControl(self):
         """ 
-        End editing the value.  
+        Does this attribute editor use a permanent control (or
+        will the control be created when the user clicks)? 
+        """
+        return False
 
-        Called before destroying the control created in Create(). 
+    def CreateControl (self, parent, id):
+        """ 
+        Create and return a control to use for editing the attribute value. 
         """
-        
+        raise NotImplementedError
+    
+    def DestroyControl (self, control, losingFocus=False):
+        """ 
+        Destroy the control at next idle, by default.
+        Return True if we did, or False if we did nothing because we were just
+        losing focus.
+        """
+        wx.CallAfter(control.Destroy)
+        return True
+ 
+    def BeginControlEdit (self, item, attributeName, control):
+        """ 
+        Load this attribute into the editing control. 
+        """
+        pass # do nothing by default
+
+    def EndControlEdit (self, item, attributeName, control):
+        """ Save the control's value into this attribute. """
+        # Do nothing by default.
+        pass        
+    
     def GetControlValue (self, control):
         """ Get the value from the control. """
+        value = control.GetValue()
+        # @@@ BJS For now, make sure the strings we return are Unicode.
+        # This'll go away when we build wx with the "unicode" flag.
+        if isinstance(value, str): value = unicode(value)
+        assert not isinstance(value, str)
+        return value
 
     def SetControlValue (self, control, value):
         """ Set the value in the control. """
+        # @@@BJS For now, make sure the strings we put in the controls 
+        # are Unicode.
+        assert not isinstance(value, str)
+        control.SetValue (value)
 
     def GetAttributeValue (self, item, attributeName):
         """ Get the value from the specified attribute of the item. """
-        
+        value = getattr(item, attributeName, None)
+        # @@@BJS For now, make sure the strings we put in the content model 
+        # are Unicode. This'll go away when we build wx with the unicode flag.
+        assert not isinstance(value, str)
+        return value
+
     def SetAttributeValue (self, item, attributeName, value):
         """ Set the value of the attribute given by the value. """
+        if not self.ReadOnly((item, attributeName)):
+            # @@@BJS For now, make sure the strings we put in the content model 
+            # are Unicode. This'll go away when we build wx with the 
+            # "unicode" flag.
+            # if isinstance(value, str): value = unicode(value)
+            assert not isinstance(value, str)
+            setattr(item, attributeName, value)
 
-    """ Informal conventions """
-    def onKeyPressed(self, event):
-        """ Handle a Key pressed in the control. """
-
-    """ Class Methods """
-    def GetAttributeEditorSingleton (theClass, type):
-        """ Get (and cache) a single shared Attribute Editor for this type. """
-        try:
-            instance = theClass._TypeToEditorInstances [type]
-        except KeyError:
-            aeClass = theClass._GetAttributeEditorClass (type)
-            # init the attribute editor, letting it know it's shared
-            instance = aeClass (isShared=True)
-            # remember it in our cache
-            theClass._TypeToEditorInstances [type] = instance
-        return instance
-
-    GetAttributeEditorSingleton = classmethod (GetAttributeEditorSingleton)
-
-    def GetAttributeEditorInstance (theClass, type, item, attributeName, presentationStyle):
-        """ Get a new unshared instance of the Attribute Editor for this type. """
-        aeClass = theClass._GetAttributeEditorClass (type)
-        # init the attribute editor, letting it know it's not shared (can use instance data)
-        instance = aeClass (isShared=False, presentationStyle=presentationStyle)
-        
-        # Note the characteristics that made us pick this editor
-        instance.typeName = type
-        instance.attributeName = attributeName
-        instance.presentationStyle = presentationStyle
-        
-        return instance
-    GetAttributeEditorInstance = classmethod (GetAttributeEditorInstance)
-
-    def _GetAttributeEditorClass (theClass, type):
-        """ Return the attribute editor class for this type """
-        map = wx.GetApp().UIRepositoryView.findPath('//parcels/osaf/framework/attributeEditors/AttributeEditors')
-        try:
-            classPath = map.editorString [type]
-        except KeyError:
-            assert map.editorString ["_default"], "Default attribute editor doesn't exist ('_default')"
-            classPath = map.editorString ["_default"]
-        parts = classPath.split (".")
-        assert len(parts) >= 2, " %s isn't a module and class" % classPath
-        className = parts.pop ()
-        module = __import__ ('.'.join(parts), globals(), locals(), className)
-        assert module.__dict__[className], "Class %s doesn't exist" % classPath
-        aeClass = module.__dict__[className]
-        return aeClass
-    _GetAttributeEditorClass = classmethod (_GetAttributeEditorClass)
-
-class BaseAttributeEditor (IAttributeEditor):
-    """ Base class for many Attribute Editors. """
-    def __init__(self, isShared, *args, **keys):
-        self.isShared = isShared
-
-    def ReadOnly (self, (item, attribute)):
-        return False # @@@BJS for now, don't. Was: not str(item.itsPath).startswith('//userdata')
-
-    def Draw (self, dc, rect, item, attributeName, isSelected):
-        """ You must override Draw. """
-        raise NotImplementedError
-    
-    def Create (self, parent, id):
-        """ You must override Create. """
-        raise NotImplementedError
-
-    def BeginControlEdit (self, control, value):
-        """ Do nothing by default. """
-
-    def GetControlValue (self, control):
-        return control.GetValue()
-
-    def SetControlValue (self, control, value):
-        control.SetValue (value)
-
-    def GetAttributeValue (self, item, attributeName):
-        """ You must override GetAttributeValue. """
-        raise NotImplementedError
+class myTextCtrl(wx.TextCtrl):
+    def Destroy(self):
+        # @@@BJS Hack until we switch to wx 2.5.4: don't destroy if we're already destroyed
+        # (in which case we're a PyDeadObject)
+        if isinstance(self, wx.TextCtrl):
+            super(myTextCtrl, self).Destroy()
+        else:
+            pass # (give me a place to set a breakpoint)
 
 class StringAttributeEditor (BaseAttributeEditor):
-    """ Uses a Text Control to edit attributes in string form. """
+    """ 
+    Uses a Text Control to edit attributes in string form. 
+    Supports sample text.
+    """
     
-    def Draw (self, dc, rect, item, attributeName, isSelected):
+    def UsePermanentControl(self):
+        try:
+            uc = self.presentationStyle.useControl
+        except AttributeError:
+            uc = False
+        return uc
+
+    def Draw (self, dc, rect, item, attributeName, isInSelection=False):
         """
           Currently only handles left justified single line text.
         """
+        
+        # If we have a control, it'll do the drawing.
+        if self.UsePermanentControl():
+            return
+        
+        if False:
+            logger.debug("StringAE.Draw: %s, %s of %s; %s in selection",
+                         self.isShared and "shared" or "dv",
+                         attributeName, item,
+                         isInSelection and "is" or "not")
+
         # Erase the bounding box
         dc.SetBackgroundMode (wx.SOLID)
         dc.SetPen (wx.TRANSPARENT_PEN)
 
         dc.DrawRectangleRect (rect)
 
-        """
-          Draw the text in the box
-        """
-
-        dc.SetBackgroundMode (wx.TRANSPARENT)
-        rect.Inflate (-1, -1)
-        dc.SetClippingRect (rect)
-
-        DrawingUtilities.DrawWrappedText (dc,
-                                          self.GetAttributeValue (item, attributeName),
-                                          rect)
-        dc.DestroyClippingRegion()
+        # Get the text we'll display, and note whether it's the sample text.
+        theText = None # assume that we won't use the sample.
+        if not self.HasValue(item, attributeName):
+            # Consider using the sample text
+            theText = self.GetSampleText(item, attributeName)
+        if theText is None:
+            # No sample text, or we have a value. Use the value.
+            theText = self.GetAttributeValue(item, attributeName)
+            # style = wx.NORMAL
+            # textColor = wx.SystemSettings.GetColour (wx.SYS_COLOUR_BTNTEXT)
+        elif len(theText) > 0:
+            # theText is the sample text - switch to gray
+            # (not italic; was...) style = wx.ITALIC
+            textColor = wx.Colour(153, 153, 153)
+            dc.SetTextForeground (textColor)
+            font = dc.GetFont ()
+            #font.SetStyle (style)
+            dc.SetFont (font)
 
-    def Create (self, parent, id):
+        if len(theText) > 0:
+            # Draw inside the lines.
+            dc.SetBackgroundMode (wx.TRANSPARENT)
+            rect.Inflate (-1, -1)
+            dc.SetClippingRect (rect)
+            
+            DrawingUtilities.DrawWrappedText (dc, theText, rect)
+                
+            dc.DestroyClippingRegion()
+        
+    def CreateControl (self, parent, id):
         # create a text control for editing the string value
-        return wx.TextCtrl (parent, id)
+        logger.debug("StringAE.CreateControl")
+        
+        control = myTextCtrl(parent, id, '', wx.DefaultPosition, 
+                             wx.DefaultSize,
+                             wx.TE_PROCESS_TAB | wx.TE_AUTO_SCROLL)
+        control.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
+        control.Bind(wx.EVT_TEXT, self.onTextChanged)
+        control.Bind(wx.EVT_LEFT_DOWN, self.onClick)
+        
+        if not self.isShared:
+            # Inflate us to our parent's size
+            parentRect = parent.GetRect()
+            control.SetSizeHints(minW=parentRect.width, minH=parentRect.height)
+            controlSize = wx.Rect(wx.DefaultPosition[0], wx.DefaultPosition[0], parentRect.width, parentRect.height)
+            logger.debug("StringAE.CreateControl: created; control size is %s", controlSize)    
+            control.SetRect(controlSize)
 
-    def BeginControlEdit (self, control, value):
-        # set up the value and move the selection to the end
-        control.SetValue (value)
-        control.SetInsertionPointEnd ()
+        return control
+
+    def BeginControlEdit (self, item, attributeName, control):
+        self.sampleText = self.GetSampleText(item, attributeName)
+        self.item = item
+        self.attributeName = attributeName
+        logger.debug("BeginControlEdit: context for %s.%s is '%s'", item, attributeName, self.sampleText)
+
+        # set up the value (which may be the sample!) and select all the text
+        value = self.GetAttributeValue(item, attributeName)
+        if self.sampleText is not None and len(value) == 0:
+            self.__setSampleText(control, self.sampleText)
+        else:
+            self.showingSample = False
+            self.__changeTextQuietly(control, value)
+            control.SetSelection (-1,-1)
+            # @@@BJS is this necessary?: control.SetInsertionPointEnd ()
+
+        logger.debug("BeginControlEdit: %s (%s) on %s", attributeName, self.showingSample, item)
+
+    def EndControlEdit (self, item, attributeName, control):
+        # update the item attribute value, from the latest control value.
+        if item is not None:
+            logger.debug("EndControlEdit: %s is '%s' on %s", attributeName, self.GetControlValue(control), item)
+            self.SetAttributeValue (item, attributeName, self.GetControlValue (control))
+
+    def GetControlValue (self, control):
+        # return the empty string, if we're showing the sample value.
+        if self.showingSample:
+            value = u""
+        else:
+            value = super(StringAttributeEditor, self).GetControlValue(control)
+        return value
+    
+    def SetControlValue(self, control, value):
+        if len(value) != 0 or self.sampleText is None:
+            self.showingSample = False
+            self.__changeTextQuietly(control, value)
+        else:
+            self.__setSampleText(control, self.sampleText)
+
+    def onTextChanged(self, event):
+        if not getattr(self, "ignoreTextChanged", False):
+            control = event.GetEventObject()
+            if self.sampleText is not None:
+                logger.debug("StringAE.onTextChanged: not ignoring.")                    
+                currentText = control.GetValue()
+                if self.showingSample:
+                    logger.debug("onTextChanged: removing sample")
+                    if currentText != self.sampleText:
+                        self.showingSample = False
+                elif len(currentText) == 0:
+                    self.__setSampleText(control, self.sampleText)
+            else:
+                logger.debug("StringAE.onTextChanged: ignoring (no sample text)")
+        else:
+            logger.debug("StringAE.onTextChanged: ignoring (self-changed)")
+
+    def __changeTextQuietly(self, control, text):
+        self.ignoreTextChanged = True
+        logger.debug("__changeTextQuietly: to '%s'", text)
+        control.SetValue(text)
+        del self.ignoreTextChanged
+        
+    def __setSampleText(self, control, sampleText):
+        logger.debug("__setSampleText: installing sampletext")
+        self.showingSample = True
+        self.__changeTextQuietly(control, sampleText)
         control.SetSelection (-1,-1)
-        control.SetFocus()
+        control.SetStyle(0, len(sampleText), wx.TextAttr(wx.Colour(153, 153, 153)))
 
-    def GetAttributeValue (self, item, attributeName):
+    def onKeyDown(self, event):
+        """ Note whether the sample's been replaced. """
+        # If we're showing sample text and this key would only change the 
+        # selection, ignore it.
+        if self.showingSample and event.GetKeyCode() in \
+            (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_BACK):
+             logger.debug("onKeyDown: Ignoring selection-changer %s (%s) while showing the sample text", event.GetKeyCode(), wx.WXK_LEFT)
+             return # skip out without calling event.Skip()
+
+        logger.debug("onKeyDown: processing %s (%s)", event.GetKeyCode(), wx.WXK_LEFT)
+        event.Skip()
+        
+    def onClick(self, event):
+        """ Ignore clicks if we're showing the sample """
+        control = event.GetEventObject()
+        if self.showingSample and control == wx.Control.FindFocus():
+            logger.debug("onClick: ignoring click because we're showing the sample.")
+        else:
+            event.Skip()
+        if self.showingSample:
+            control.SetSelection(-1, -1) # Make sure the whole thing's still selected
+            
+    def GetSampleText(self, item, attributeName):
+        """ Return this attribute's sample text, or None if there isn't any. """
         try:
-            value = getattr (item, attributeName) # getattr will work with properties
+            sampleText = self.presentationStyle.sampleText
         except AttributeError:
-            value = ""
+            return None
+
+        # Yep, there's supposed to be sample text.
+        if len(sampleText) == 0:
+            # Empty sample text was specified: this means use the attribute's displayName,
+            # or the attribute name itself if no displayName is present. Redirect if 
+            # necessary first.
+            sampleText = item.getAttributeAspect(attributeName, 'redirectTo');
+            if sampleText is None:
+                sampleText = attributeName
+            if item.hasAttributeAspect (sampleText, 'displayName'):
+                sampleText = item.getAttributeAspect (sampleText, 'displayName')                  
+        return sampleText
+    
+    def HasValue(self, item, attributeName):
+        """ 
+        Return True if a non-default value has been set for this attribute, 
+        or False if this value is the default and deserves the sample text 
+        (if any) instead. (Can be overridden.) """
+        return len(unicode(getattr(item, attributeName, u""))) > 0
+
+    def GetAttributeValue(self, item, attributeName):
+        """ Get the attribute's current value, converted to a (unicode) string """
+        try:
+            valueString = unicode(getattr(item, attributeName))
+        except AttributeError:
+            valueString = u""
         else:
             try:
                 cardinality = item.getAttributeAspect (attributeName, "cardinality")
@@ -191,16 +389,16 @@
                 pass
             else:
                 if  cardinality == "list":
-                    value = ', '.join([part.getItemDisplayName() for part in value])
-        return value
+                    valueString = u", ".join([part.getItemDisplayName() for part in value])
+        return valueString
 
-    def SetAttributeValue (self, item, attributeName, valueString):
+    def SetAttributeValue(self, item, attributeName, valueString):            
         try:
             cardinality = item.getAttributeAspect (attributeName, "cardinality")
         except AttributeError:
             pass
         else:
-            if  cardinality == "single":
+            if cardinality == "single":
                 setattr (item, attributeName, valueString)
 
 class DateTimeAttributeEditor (StringAttributeEditor):
@@ -274,250 +472,65 @@
         value = attrType.makeValue (valueString)
         setattr (item, attributeName, value)
 
-class myTextCtrl(wx.TextCtrl):
-    def Destroy(self):
-        # @@@ Hack until we switch to wx 2.5.4: don't destroy if we're already destroyed
-        # (in which case we're a PyDeadObject)
-        if isinstance(self, wx.TextCtrl):
-            super(myTextCtrl, self).Destroy()
-
-class LabeledAttributeEditor (StringAttributeEditor):
-    """ Attribute Editor that shows a Label for the attribute in addition to the value. """
-    def __init__(self, isShared, presentationStyle=None):
-        super (LabeledAttributeEditor, self).__init__(isShared,
-                                                         presentationStyle)
-
-        """ set up internal state for this item/attribute combination """
-        try:
-            labelStyle = presentationStyle.label
-            self.presentationStyle = presentationStyle
-        except AttributeError:
-            labelStyle = 'None'
-        # attributes that share an Attribute Editor will all use the same label style
-        self.labelStyle = labelStyle
-
-    def _IsAttributeLabel (self, item, attributeName):
-        """ return True if this value is the "default" value
-        for the attribute """
-        return not hasattr (item, attributeName)
-
-    def _GetAttributeLabel (self, item, attributeName):
-        try:
-            return item.getAttributeAspect (attributeName, 'displayName')
-        except:
-            try:
-                return self.presentationStyle.labelDisplayName
-            except AttributeError:
-                pass
-            
-        raise NotImplementedError, "Can't display name of property '%s' for item of type %s" \
-                           %  (attributeName, item.itsKind.displayName)
-
-    def _SetLabelStyle (self, item, attributeName, dc):
-        if self.labelStyle == 'OnLeft':
-            """ 
-              For Label-On-Left collect label layout information:
-            self.editOffset - offset of the edit area
-            self.labelOffset - offset of the label area
-            """
-            assert not self.isShared, "Label on Left presentationStyle not allowed for shared Attribute Editors"
-            try:
-                editOffset = self.editOffset # cached value?
-            except AttributeError:
-                label = self._GetAttributeLabel (item, attributeName)
-                lineWidth, lineHeight = dc.GetTextExtent (label)
-                # editOffset - where to put the edit text
-                try:
-                    editOffset = self.presentationStyle.labelWidth
-                except AttributeError:
-                    # not supplied: use the width of the label for the offset
-                    editOffset = lineWidth
-                try:
-                    # border is extra space next to label
-                    border = self.presentationStyle.labelBorder
-                except AttributeError:
-                    border = 0
-                try:
-                    # label alignment: Left, Center, or Right
-                    alignment = self.presentationStyle.labelTextAlignmentEnum
-                except AttributeError:
-                    alignment = "Right"
-                self.editOffset = editOffset
-                self.border = border
-                self.alignment = alignment
-                self.label = label
-
-                # figure out the label offset
-                if self.alignment == "Left":
-                    labelOffset = self.border
-                else:
-                    if self.alignment == "Right":
-                        scaleFactor = 1
-                    elif self.alignment == "Center":
-                        scaleFactor = 2
-                    else:
-                        assert False, "invalid labelTextAlignmentEnum detected"
-                    labelOffset = editOffset - (lineWidth + self.border) / scaleFactor
-                self.labelOffset = labelOffset
-
-        elif self.labelStyle == 'InPlace':
-            drawItalic = self._IsAttributeLabel (item, attributeName)
-            if drawItalic:
-                style = wx.ITALIC
-                textColor = wx.Colour(64, 64, 64)
-            else:
-                style = wx.NORMAL
-                textColor = wx.SystemSettings.GetColour (wx.SYS_COLOUR_BTNTEXT)
-            dc.SetTextForeground (textColor)
-            font = dc.GetFont ()
-            font.SetStyle (style)
-            dc.SetFont (font)
-
-    def Draw (self, dc, rect, item, attributeName, isSelected):
-        # always setup for drawing
-        dc.SetBackgroundMode (wx.SOLID)
-        dc.SetPen (wx.TRANSPARENT_PEN)
-
-        dc.DrawRectangleRect (rect)
-
-        """
-          Draw the text in the box
-        """
-
-        dc.SetBackgroundMode (wx.TRANSPARENT)
-        rect.Inflate (-1, -1)
-        dc.SetClippingRect (rect)
-
-        x = rect.x + 1
-        y = rect.y + 1
-
-        # set up our label style information
-        self._SetLabelStyle (item, attributeName, dc)
-
-        # Label OnLeft?  
-        if self.labelStyle == 'OnLeft':
-            # Draw label first, then move to the right.
-            label = self._GetAttributeLabel (item, attributeName)
-            dc.DrawText (label, x + self.labelOffset, y)
-            x += self.editOffset
-            rect.width -= self.editOffset
-            
-            # draw an area that looks editable on the right
-            dc.SetBackgroundMode (wx.SOLID)
-            oldBrush = dc.GetBrush()
-            dc.SetBrush (wx.WHITE_BRUSH)
-            dc.DrawRectangle (x-1, y-1, rect.width, rect.height)
-            dc.SetPen (wx.LIGHT_GREY_PEN)
-            dc.DrawLine (x-1, y-1, x-1, y+rect.height)
-            dc.DrawLine (x-1, y-1, x+rect.width, y-1)
-            dc.SetBrush (oldBrush)
-            
-        # if not selected there's no edit control, so we need to draw the value text.
-        if not isSelected:
-            textRectangle = wx.Rect (x, y, rect.GetRight() - x, rect.GetBottom() - y)
-            DrawingUtilities.DrawWrappedText (dc,
-                                              self.GetAttributeValue (item, attributeName),
-                                              textRectangle)
-        dc.DestroyClippingRegion()
-    
-    def Create (self, parent, id):
-        parentRect = parent.GetRect()
-        controlPosition = wx.DefaultPosition
-        controlSize = [parentRect.width, parentRect.height]
-        
-        # if the label belongs on the left, the control needs to be on the right.
-        if self.labelStyle == "OnLeft":
-            controlPosition = (self.editOffset, -1)
-            
-        # create the edit control
-        control = myTextCtrl (parent, id, '', controlPosition)
-        
-        # get size hints based on the parent
-        control.SetSizeHints(minW=controlSize[0], minH=controlSize[1])
-        controlRect = wx.Rect(controlPosition[0], controlPosition[1], controlSize[0], controlSize[1])
-        control.SetRect(controlRect)
-        
-        # bind to a key handler, if it exits, to process keystrokes e.g. completion
-        try:
-            keyHandler = self.onKeyPressed
-        except AttributeError:
-            pass
-        else:
-            control.Bind (wx.EVT_KEY_UP, keyHandler)
-        return control
-        
-    def EndControlEdit (self, item, attributeName, control):
-        # update the item attribute value, from the latest control value.
-        controlValue = self.GetControlValue (control)
-        if item is not None:
-            self.SetAttributeValue (item, attributeName, controlValue)
-
-class LocationAttributeEditor (LabeledAttributeEditor):
+class LocationAttributeEditor (StringAttributeEditor):
     """ Knows that the data Type is a Location. """
-    def GetAttributeValue (self, item, attributeName):
-        # get the value, and if it doesn't exist, use the label
-        try:
-            value = getattr (item, attributeName)
-        except:
-            valueString = "" # @@@BJS: for now, don't hint. was: self._GetAttributeLabel (item, attributeName)
-            # self.isLabelValue = True
-        else:
-            valueString = str (value)
-        self.showingTheLabel = False # @@@ BJS: was self._IsAttributeLabel (item, attributeName) # remember if we're showing the label value
-        return valueString
-
-    import osaf.contentmodel.calendar.Calendar as Calendar
-
     def SetAttributeValue (self, item, attributeName, valueString):
-        # if the value has changed, create a location for it.
-        if not valueString or self.showingTheLabel: # no value, or still showing the label
+        if not valueString:
+            # @@@BJS There's a repository bug that makes this hasattr necessary;
+            # once it's fixed, replace this with try: delattr(item, attributeName) except AttributeError: pass
+            #if hasattr(item, attributeName):
+            #    delattr (item, attributeName)
             try:
-                delattr (item, attributeName)
+                delattr(item, attributeName)
             except AttributeError:
                 pass
         else:
             # lookup an existing item by name, if we can find it, 
             value = Calendar.Location.getLocation (item.itsView, valueString)
-            setattr (item, attributeName, value)
+            if getattr(item, attributeName, None) is not value:
+                setattr (item, attributeName, value)
+
+    def CreateControl (self, parent, id):
+        control = super(LocationAttributeEditor, self).CreateControl(parent, id)
+        control.Bind(wx.EVT_KEY_UP, self.onKeyUp)
+        return control
 
-    def onKeyPressed(self, event):
+    def onKeyUp(self, event):
         """
           Handle a Key pressed in the control.
         """
-        event.Skip()
-        self.showingTheLabel = False # remember we've edited the value
+        logger.debug("LocationAttrEditor: onKeyUp")
+        
         control = event.GetEventObject()
         controlValue = self.GetControlValue (control)
         keysTyped = len(controlValue)
         isDelete = event.m_keyCode == wx.WXK_DELETE or event.m_keyCode == wx.WXK_BACK
         if keysTyped > 1 and not isDelete:
-            # get all Location objects whose displayName contains the current string
-            # @@@DLD is there a way to get values whose displayName *starts* with the string?
-            queryString = u'for i in "//parcels/osaf/contentmodel/calendar/Location" \
-                          where contains(i.displayName, $0)'
+            # See if there's exactly one existing Location object whose 
+            # displayName starts with the current string; if so, we'll complete
+            # on it.
             view = wx.GetApp().UIRepositoryView
-            queryName = 'locationAttributeEditorQuery'
-            locQuery = view.findPath('//Queries/'+queryName)
-            if locQuery is None:
-                p = view.findPath('//Queries')
-                k = view.findPath('//Schema/Core/Query')
-                locQuery = Query.Query (queryName, p, k, queryString)
-                locQuery.args["$0"] = ( controlValue, )
-    
-            # build a list of matches here
-            candidates = []
-            for aLoc in locQuery:
+            locationKind = view.findPath(Calendar.Location.myKindPath)
+            allLocations = ItemQuery.KindQuery().run([locationKind])
+            existingLocation = None
+            for aLoc in allLocations:
                 if aLoc.displayName[0:keysTyped] == controlValue:
-                    candidates.append (aLoc)
-
-            # for now, we perform competion only when exactly one match was found.
-            if len (candidates) == 1:
-                completion = candidates[0].displayName
+                    if existingLocation is None:
+                        existingLocation = aLoc
+                        logger.debug("LocationAE.onKeyUp: '%s' completes!", aLoc.displayName)
+                    else:
+                        # We found a second candidate - we won't complete
+                        logger.debug("LocationAE.onKeyUp: ... but so does '%s'", aLoc.displayName)
+                        existingLocation = None
+                        break
+                
+            if existingLocation is not None:
+                completion = existingLocation.displayName
                 self.SetControlValue (control, completion)
+                logger.debug("LocationAE.onKeyUp: completing with '%s'", completion[keysTyped:])
                 control.SetSelection (keysTyped, len (completion))
 
-
-class DateTimeDeltaAttributeEditor (LabeledAttributeEditor):
+class DateTimeDeltaAttributeEditor (StringAttributeEditor):
     """ Knows that the data Type is DateTimeDelta. """
     def GetAttributeValue (self, item, attributeName):
         # attempt to access as a plain Python attribute
@@ -601,6 +614,150 @@
                 value = addresses.emailAddress
         return value
 
+class BasePermanentAttributeEditor (BaseAttributeEditor):
+    """ Base class for editors that always need controls """
+    def UsePermanentControl(self):
+        return True
+    
+class CheckboxAttributeEditor (BasePermanentAttributeEditor):
+    """ A checkbox control. """
+    def __init__(self, isShared, typeName, item=None, attributeName=None, presentationStyle=None):
+        super(CheckboxAttributeEditor, self).__init__(isShared, typeName, item=item, attributeName=attributeName, presentationStyle=presentationStyle)
+        
+    def Draw (self, dc, rect, item, attributeName, isInSelection=False):
+        # We have to implement Draw, but we don't need to do anything
+        # because we've always got a control to do it for us.
+        pass
+
+    def CreateControl (self, parent, id):
+        control = wx.CheckBox(parent, id)
+        control.Bind(wx.EVT_CHECKBOX, self.onChecked)
+        return control
+        
+    def DestroyControl (self, control, losingFocus=False):
+        # Only destroy the control if we're not just losing focus
+        if losingFocus:
+            return False # we didn't destroy the control
+        
+        wx.CallAfter(control.Destroy)
+        return True
+    
+    def onChecked(self, event):
+        logger.debug("CheckboxAE.onChecked: new choice is %s",
+                     self.GetControlValue(event.GetEventObject()))
+        control = event.GetEventObject()
+        self.SetAttributeValue(self.item, self.attributeName, \
+                               self.GetControlValue(control))
+
+    def GetControlValue (self, control):
+        """ Are we checked? """
+        return control.IsChecked()
+
+    def SetControlValue (self, control, value):
+        """ Set our state """
+        control.SetValue(value)
+
+class ChoiceAttributeEditor (BasePermanentAttributeEditor):
+    """ A pop-up control. The list of choices comes from presentationStyle.choices """
+    def __init__(self, isShared, typeName, item=None, attributeName=None, presentationStyle=None):
+        super(ChoiceAttributeEditor, self).__init__(isShared, typeName, item=item, attributeName=attributeName, presentationStyle=presentationStyle)
+        
+    def Draw (self, dc, rect, item, attributeName, isInSelection=False):
+        # We have to implement Draw, but we don't need to do anything
+        # because we've always got a control to do it for us.
+        pass
+
+    def CreateControl (self, parent, id):
+        control = wx.Choice(parent, id)
+        control.Bind(wx.EVT_CHOICE, self.onChoice)
+        return control
+        
+    def DestroyControl (self, control, losingFocus=False):
+        # Only destroy the control if we're not just losing focus
+        if losingFocus:
+            return False # we didn't destroy the control
+        
+        wx.CallAfter(control.Destroy)
+        return True
+    
+    def onChoice(self, event):
+        logger.debug("ChoiceAE.onChoice: new choice is %s",
+                     self.GetControlValue(event.GetEventObject()))
+        control = event.GetEventObject()
+        self.SetAttributeValue(self.item, self.attributeName, \
+                               self.GetControlValue(control))
+
+    def GetChoices(self):
+        """ Get the choices we're presenting """
+        return self.presentationStyle.choices
+
+    def GetControlValue (self, control):
+        """ Get the selected choice's text """
+        choiceIndex = control.GetSelection()
+        value = self.item.getAttributeAspect(self.attributeName, 'type').values[choiceIndex]
+        if isinstance(value, str): value = unicode(value) # @@@BJS Make sure we return unicode!
+        return value
+
+    def SetControlValue (self, control, value):
+        """ Select the choice with the given text """
+        # We also take this opportunity to populate the menu
+        existingValue = self.GetControlValue(control)
+        if existingValue != value:            
+            # rebuild the list of choices
+            choices = self.GetChoices()
+            control.Clear()
+            control.AppendItems(choices)
+        
+            try:
+                choiceIndex = self.item.getAttributeAspect(self.attributeName, 'type').values.index(value)
+            except AttributeError:
+                choiceIndex = 0
+            control.Select(choiceIndex)
+
+class ReminderDeltaAttributeEditor(ChoiceAttributeEditor):
+    def GetControlValue (self, control):
+        """ Get the reminder delta value for the current selection """
+        
+        # @@@ For now, assumes that the menu will be a number of minutes, 
+        # followed by a space (eg, "1 minute", "15 minutes", etc), or something
+        # that doesn't match this (eg, "None") for no-alarm.
+        value = control.GetStringSelection()
+        try:
+            minuteCount = int(value.split(u" ")[0])
+        except ValueError:
+            # "None"
+            value = Calendar.CalendarEventMixin.NoReminderDelta
+        else:
+            value = DateTime.DateTimeDeltaFrom(minutes=-minuteCount)
+        return value
+
+    def SetControlValue (self, control, value):
+        """ Select the choice that matches this delta value"""
+        # We also take this opportunity to populate the menu
+        existingValue = self.GetControlValue(control)
+        if existingValue != value or control.GetCount() == 0:            
+            # rebuild the list of choices
+            choices = self.GetChoices()
+            control.Clear()
+            control.AppendItems(choices)
+
+            if value == Calendar.CalendarEventMixin.NoReminderDelta:
+                choiceIndex = 0 # the "None" choice
+            else:
+                reminderChoice = (value.minutes == 1) and _("1 minute") or (_("%i minutes") % value.minutes)
+                choiceIndex = control.FindString(reminderChoice)
+                # If we can't find the choice, just show "None" - this'll happen if this event's reminder has been "snoozed"
+                if choiceIndex == -1:
+                    choiceIndex = 0 # the "None" choice
+            control.Select(choiceIndex)
+        
+    def SetAttributeValue (self, item, attributeName, value):
+        if value is None and hasattr(item, attributeName):
+            delattr(item, attributeName)
+        else:
+            super(ReminderDeltaAttributeEditor, \
+                  self).SetAttributeValue(item, attributeName, value)
+
 class IconAttributeEditor (BaseAttributeEditor):
     def ReadOnly (self, (item, attribute)):
         return True # The Icon editor doesn't support editing.
@@ -613,7 +770,7 @@
             value = ""
         return value
     
-    def Draw (self, dc, rect, item, attributeName, isSelected):
+    def Draw (self, dc, rect, item, attributeName, isInSelection=False):
         dc.DrawRectangleRect(rect) # always draw the background
         imageName = self.GetAttributeValue(item, attributeName) + ".png"
         image = wx.GetApp().GetImage(imageName)
@@ -622,7 +779,6 @@
             y = rect.GetTop() + (rect.GetHeight() - image.GetHeight()) / 2
             dc.DrawBitmap (image, x, y, True)
 
-
 class EnumAttributeEditor (IconAttributeEditor):
     """
     An attribute editor for enumerated types to be represented as icons. 

Index: chandler/parcels/osaf/framework/blocks/Trunk.py
diff -u chandler/parcels/osaf/framework/blocks/Trunk.py:1.12 chandler/parcels/osaf/framework/blocks/Trunk.py:1.13
--- chandler/parcels/osaf/framework/blocks/Trunk.py:1.12	Thu Mar 17 15:01:28 2005
+++ chandler/parcels/osaf/framework/blocks/Trunk.py	Mon Apr 25 13:49:21 2005
@@ -1,5 +1,5 @@
-__version__ = "$Revision: 1.12 $"
-__date__ = "$Date: 2005/03/17 23:01:28 $"
+__version__ = "$Revision: 1.13 $"
+__date__ = "$Date: 2005/04/25 20:49:21 $"
 __copyright__ = "Copyright (c) 2005 Open Source Applications Foundation"
 __license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
 
@@ -8,6 +8,7 @@
 import osaf.framework.blocks.ContainerBlocks as ContainerBlocks
 from repository.item.Item import Item
 import osaf.contentmodel.ContentModel as ContentModel
+import logging
 
 """
 Trunk.py - Classes for dynamically substituting child trees-of-blocks.
@@ -19,6 +20,9 @@
 to customize its behavior.
 """
 
+logger = logging.getLogger('trunk')
+logger.setLevel(logging.INFO)
+
 class wxTrunkParentBlock(ContainerBlocks.wxBoxContainer):
     """ 
     A widget block that gives its TrunkParentBlock a chance to change 
@@ -72,8 +76,14 @@
                 newView = self.trunkDelegate.getTrunkForKeyItem(keyItem)
             
         oldView = self.childrenBlocks.first()
-        if (newView is not  oldView) or ('rerenderHint' in keywords):
+        if (newView is not oldView) or ('rerenderHint' in keywords):
+            logger.debug("changing tree to display %s", detailItem)
             if not oldView is None:
+                """
+                oldFocus = self.widget.FindFocus()
+                if oldFocus is not None:
+                    logger.debug("unrendering the focused block!")
+                """
                 oldView.unRender()
 
             self.childrenBlocks = []
@@ -86,6 +96,9 @@
                 assert newView.eventBoundary
                 self.trunkDelegate._setContentsOnTrunk (newView, detailItem, keyItem)
                 newView.render()
+        else:
+            logger.debug("NOT changing tree to display %s", detailItem)
+
 
     def synchronizeColor (self):
         # if there's a color style defined, synchronize the color

Index: chandler/parcels/osaf/framework/blocks/detail/Detail.py
diff -u chandler/parcels/osaf/framework/blocks/detail/Detail.py:1.113 chandler/parcels/osaf/framework/blocks/detail/Detail.py:1.114
--- chandler/parcels/osaf/framework/blocks/detail/Detail.py:1.113	Tue Apr 19 11:26:17 2005
+++ chandler/parcels/osaf/framework/blocks/detail/Detail.py	Mon Apr 25 13:49:22 2005
@@ -1,5 +1,5 @@
-__version__ = "$Revision: 1.113 $"
-__date__ = "$Date: 2005/04/19 18:26:17 $"
+__version__ = "$Revision: 1.114 $"
+__date__ = "$Date: 2005/04/25 20:49:22 $"
 __copyright__ = "Copyright (c) 2004-2005 Open Source Applications Foundation"
 __license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
 
@@ -46,6 +46,7 @@
     # code related to selection down the block tree - we'll revisit it all in 0.6
 
     def onSetContentsEvent (self, event):
+        logger.debug("DetailRoot.onSetContentsEvent: %s", event.arguments['item'])
         self.__changeSelection(event.arguments['item'])
 
     def onSelectItemEvent (self, event):
@@ -53,6 +54,8 @@
           A DetailTrunk is an event boundary; this keeps all the events 
         sent between blocks of the Detail View to ourselves.
         """
+        logger.debug("DetailRoot.onSelectItemEvent: %s", event.arguments['item'])
+        
         # Finish changes to previous selected item 
         self.finishSelectionChanges () 
         
@@ -276,7 +279,7 @@
         inconsistent state.)
         """
         if not getattr(self, "ignoreCollectionChangedWhileStamping", False):
-            # Block.logger.debug("DetailRoot: onCollectionChanged")
+            # logger.debug("DetailRoot: onCollectionChanged")
             self.synchronizeWidget()
     
 class DetailTrunkDelegate (Trunk.TrunkDelegate):
@@ -524,6 +527,16 @@
         if self.isShown:
             self.synchronizeWidget()
 
+    def saveTextValue (self, validate=False):
+        # Tell the AE to save itself
+        item = self.selectedItem()
+        try:
+            widget = self.widget
+        except AttributeError:
+            widget = None
+        if item is not None and widget is not None:
+            widget.onLoseFocusFromControl(None)
+
 def ItemCollectionOrMailMessageMixin (item):
     # if the item is a MailMessageMixin, or an ItemCollection,
     # then return True
@@ -728,8 +741,9 @@
                 #     and will not address issues related to internationalization
                 if not isinstance(widgetText, unicode):
                     widgetText = unicode(widgetText, 'utf-8', 'ignore')
-                text = widgetText.encode('ascii', 'ignore')
-                item.body = textType.makeValue(text, encoding='ascii', indexed=True)
+                asciiText = widgetText.encode('ascii', 'ignore')
+                if item.ItemBodyString() != asciiText: # only put back if it's changed.
+                    item.body = textType.makeValue(asciiText, encoding='ascii', indexed=True)
         
     def loadAttributeIntoWidget (self, item, widget):  
         attributeName = GetRedirectAttribute(item, 'body');
@@ -979,7 +993,7 @@
                 pass
             else:
                 showIt = True
-        # logger.debug("AcceptShareButton.shouldShow = %s" % showIt)
+        # logger.debug("AcceptShareButton.shouldShow = %s", showIt)
         return showIt
     
     def onAcceptShareEvent(self, event):
@@ -1102,6 +1116,10 @@
             value = dateTime.strftime (format)
         widget.SetValue (value)
 
+class CalendarDurationArea (CalendarEventBlock):
+    def shouldShow (self, item):
+        return not item.allDay
+
 class EditDurationAttribute (DetailSynchronizedAttributeEditorBlock):
     def shouldShow (self, item):
         return not item.allDay

Index: chandler/parcels/osaf/framework/blocks/detail/parcel.xml
diff -u chandler/parcels/osaf/framework/blocks/detail/parcel.xml:1.74 chandler/parcels/osaf/framework/blocks/detail/parcel.xml:1.75
--- chandler/parcels/osaf/framework/blocks/detail/parcel.xml:1.74	Mon Apr 25 11:57:35 2005
+++ chandler/parcels/osaf/framework/blocks/detail/parcel.xml	Mon Apr 25 13:49:22 2005
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="iso-8859-1"?>
 
-<!-- $Revision: 1.74 $ -->
-<!-- $Date: 2005/04/25 18:57:35 $ -->
+<!-- $Revision: 1.75 $ -->
+<!-- $Date: 2005/04/25 20:49:22 $ -->
 <!-- Copyright (c) 2003-2005 Open Source Applications Foundation -->
 <!-- License: http://osafoundation.org/Chandler_0.1_license_terms.htm -->
 
@@ -27,7 +27,7 @@
 
   <CharacterStyle itsName="BigTextStyle">
     <fontFamily>DefaultUIFont</fontFamily>
-    <fontSize>10</fontSize>
+    <fontSize>12</fontSize>
     <fontStyle>bold</fontStyle>
   </CharacterStyle>
 
@@ -117,7 +117,7 @@
   <doc:DetailTrunkSubtree itsName="NoteSubtree">
     <key itemref="content:Note"/>
     <rootBlocks itemref="doc:MarkupBar"/>
-    <rootBlocks itemref="doc:HeadlineArea"/>
+    <rootBlocks itemref="doc:HeadlineBlock"/>
     <rootBlocks itemref="doc:NotesArea"/>
   </doc:DetailTrunkSubtree>
 
@@ -141,7 +141,7 @@
     <key itemref="content:ItemCollection"/>
     <rootBlocks itemref="doc:ParticipantsArea"/>
     <rootBlocks itemref="doc:InviteArea"/>
-    <rootBlocks itemref="doc:HeadlineArea"/>
+    <rootBlocks itemref="doc:HeadlineBlock"/>
     <rootBlocks itemref="doc:SharingActiveArea"/>
     <rootBlocks itemref="doc:NotesArea"/>
   </doc:DetailTrunkSubtree>
@@ -231,7 +231,7 @@
     <position>0.1</position>
     <selectedItemsAttribute>whoFrom</selectedItemsAttribute>
     <stretchFactor>0.0</stretchFactor>
-    <border>5, 5, 0, 5</border>
+    <border>5, 0, 0, 5</border>
   </ContentItemDetail>
 
   <StaticText itsName="FromString">
@@ -276,7 +276,7 @@
     <!-- Attributes -->
     <position>0.85</position>
     <stretchFactor>0.0</stretchFactor>
-    <border>5, 5, 0, 5</border>
+    <border>5, 0, 0, 5</border>
   </ContentItemDetail>
  
   <StaticText itsName="AttachmentString">
@@ -308,7 +308,7 @@
     <position>0.2</position>
     <selectedItemsAttribute>who</selectedItemsAttribute>
     <stretchFactor>0.0</stretchFactor>
-    <border>5, 5, 0, 5</border>
+    <border>5, 0, 0, 5</border>
   </ContentItemDetail>
  
   <StaticText itsName="ToString">
@@ -340,7 +340,7 @@
     <position>0.3</position>
     <selectedItemsAttribute>who</selectedItemsAttribute>
     <stretchFactor>0.0</stretchFactor>
-    <border>5, 5, 0, 5</border>
+    <border>5, 0, 0, 5</border>
   </ContentItemDetail>
  
   <StaticText itsName="ParticipantsString">
@@ -372,7 +372,7 @@
     <position>0.3</position>
     <selectedItemsAttribute>whoFrom</selectedItemsAttribute>
     <stretchFactor>0.0</stretchFactor>
-    <border>5, 5, 0, 5</border>
+    <border>5, 0, 0, 5</border>
   </ContentItemDetail>
 
   <StaticText itsName="InviteString">
@@ -464,19 +464,18 @@
   </EditText>
 
   <PresentationStyle itsName="HeadlinePresentation">
-    <label>InPlace</label>
+    <sampleText></sampleText><!-- empty sample means "use displayname" -->
   </PresentationStyle>
 
   <!-- Headline area done as an AE Block -->
   <AEBlock itsName="HeadlineBlock"
            itemClass="osaf.framework.blocks.detail.Detail.DetailSynchronizedAttributeEditorBlock">
     <blockName value="HeadlineBlock"/>
-    <characterStyle itemref="doc:LabelStyle"/>
+    <characterStyle itemref="doc:BigTextStyle"/>
     <position>0.5</position>
-    <readOnly>False</readOnly>
     <stretchFactor>0.0</stretchFactor>
     <minimumSize>300, 24</minimumSize>
-    <border>0, 5, 0, 0</border>
+    <border>5, 5, 0, 5</border>
     <presentationStyle itemref="doc:HeadlinePresentation"/>
     <viewAttribute value="about"/>
   </AEBlock>
@@ -508,20 +507,18 @@
     <blockName value="EditCalendarStartTime"/>
     <characterStyle itemref="doc:TextStyle"/>
     <lineStyleEnum>SingleLine</lineStyleEnum>
-    <readOnly>False</readOnly>
     <textAlignmentEnum>Left</textAlignmentEnum>
     <minimumSize>300, 24</minimumSize>
   </EditText>
 
   <ContentItemDetail itsName="CalendarDurationArea"
-                       itemClass="osaf.framework.blocks.detail.Detail.CalendarEventBlock">
+                     itemClass="osaf.framework.blocks.detail.Detail.CalendarDurationArea">
     <blockName value="CalendarDurationArea"/>
     <childrenBlocks itemref="doc:CalendarDurationLabel"/>
-    <childrenBlocks itemref="doc:CalendarDuration"/>
-    <!-- Attributes -->
+    <childrenBlocks itemref="doc:EditCalendarDuration"/>
     <stretchFactor>0.0</stretchFactor>
     <minimumSize>300, 24</minimumSize>
-    <border>5, 5, 0, 5</border>
+    <border>5, 0, 0, 5</border>
   </ContentItemDetail>
 
   <StaticText itsName="CalendarDurationLabel">
@@ -533,12 +530,17 @@
     <minimumSize>80, 24</minimumSize>
     <border>0, 0, 0, 5</border>
   </StaticText>
+  
+  <PresentationStyle itsName="DurationPresentation">
+    <sampleText>HH:MM</sampleText>
+    <useControl>True</useControl>
+  </PresentationStyle>
 
-  <AEBlock itsName="CalendarDuration"
+  <AEBlock itsName="EditCalendarDuration"
            itemClass="osaf.framework.blocks.detail.Detail.EditDurationAttribute">
-    <blockName value="AECalendarDuration"/>
+    <blockName value="EditCalendarDuration"/>
     <characterStyle itemref="doc:LabelStyle"/>
-    <readOnly>False</readOnly>
+    <presentationStyle itemref="doc:DurationPresentation"/>
     <stretchFactor>1</stretchFactor>
     <minimumSize>300, 24</minimumSize>
     <viewAttribute value="duration"/>
@@ -546,7 +548,7 @@
 
   <ContentItemDetail itsName="CalendarDetails">
     <blockName value="CalendarDetails"/>
-    <childrenBlocks itemref="doc:CalendarLocationArea"/>
+    <childrenBlocks itemref="doc:CalendarLocation"/>
     <childrenBlocks itemref="doc:CalendarStartTime"/>
     <childrenBlocks itemref="doc:CalendarDurationArea"/>
     <childrenBlocks itemref="doc:CalendarAllDayArea"/>
@@ -558,15 +560,15 @@
     <position>0.8</position>
   </ContentItemDetail>
   
+  <!--
   <ContentItemDetail itsName="CalendarLocationArea"
                        itemClass="osaf.framework.blocks.detail.Detail.CalendarEventBlock">
     <blockName value="CalendarLocationArea"/>
     <childrenBlocks itemref="doc:CalendarLocationLabel"/>
     <childrenBlocks itemref="doc:CalendarLocation"/>
-    <!-- Attributes -->
     <stretchFactor>0.0</stretchFactor>
     <minimumSize>300, 24</minimumSize>
-    <border>5, 5, 0, 5</border>
+    <border>5, 0, 0, 5</border>
   </ContentItemDetail>
 
   <StaticText itsName="CalendarLocationLabel">
@@ -578,14 +580,20 @@
     <minimumSize>80, 24</minimumSize>
     <border>0, 0, 0, 5</border>
   </StaticText>
-  
+  -->
+
+  <PresentationStyle itsName="LocationPresentation">
+    <sampleText></sampleText>
+  </PresentationStyle>
+
   <AEBlock itsName="CalendarLocation"
            itemClass="osaf.framework.blocks.detail.Detail.DetailSynchronizedAttributeEditorBlock">
     <blockName value="AECalendarLocation"/>
     <characterStyle itemref="doc:LabelStyle"/>
-    <readOnly>False</readOnly>
     <stretchFactor>0.0</stretchFactor>
     <minimumSize>300, 24</minimumSize>
+    <border>0, 5, 0, 5</border>
+    <presentationStyle itemref="doc:LocationPresentation"/>
     <viewAttribute value="location"/>
   </AEBlock>
 
@@ -613,11 +621,9 @@
   <CheckBox itsName="EditAllDay"
             itemClass="osaf.framework.blocks.detail.Detail.AllDayCheckBox">
     <blockName value="EditAllDay"/>
-    <!-- Attributes -->
     <title></title>
     <size>30,24</size>
     <minimumSize>30,24</minimumSize>
-    <!--<border>0.0, 10.0, 0.0, 0.0</border>-->
     <event itemref="doc:ToggleAllDay"/>
     <alignmentEnum>alignMiddleLeft</alignmentEnum>
     <stretchFactor>0.0</stretchFactor>
@@ -661,7 +667,7 @@
 
   <ContentItemDetail itsName="CalendarTransparencyArea"
                        itemClass="osaf.framework.blocks.detail.Detail.CalendarEventBlock">
-    <blockName value="CalendarAllDayAndReminder"/>
+    <blockName value="CalendarTransparencyArea"/>
     <childrenBlocks itemref="doc:CalDetailsTransparencyLabel"/>
     <childrenBlocks itemref="doc:EditTransparency"/>
     <!-- Attributes -->
@@ -682,7 +688,7 @@
 
   <Choice itsName="EditTransparency"
             itemClass="osaf.framework.blocks.detail.Detail.EditTransparency">
-    <blockName value="EditReminder"/>
+    <blockName value="EditTransparency"/>
     <issues>It'd be nice to not maintain the transparency choices separately from the enum values; currently, the choices must match the enum's items and ordering.</issues>
     <choices>Confirmed</choices>
     <choices>Tentative</choices>
@@ -700,7 +706,6 @@
     <characterStyle itemref="doc:TextStyle"/>
     <lineStyleEnum>MultiLine</lineStyleEnum>
     <textStyleEnum>RichText</textStyleEnum>
-    <readOnly>False</readOnly>
     <textAlignmentEnum>Left</textAlignmentEnum>
     <stretchFactor>3</stretchFactor>
     <minimumSize>300, 80</minimumSize>



More information about the Commits mailing list