[Chandler-dev] Re: [commits] (alecf) [10551] refactor the somewhat monolit

Alec Flett alecf at osafoundation.org
Tue May 2 14:22:58 PDT 2006


Ooops, this should have been:

refactor the rather monolithic scripting module into three sub-modules:
- general scripting and the Script Item
- detail view blocks for viewing scripts in chandler
- app-level proxies like AppProxy, BlockProxy, and so forth
- User-level emulation like emulate_typing

The API remains identical but the code is less tangled together now...

Alec

commits at osafoundation.org wrote:
>
> Revision
>     10551 <http://viewcvs.osafoundation.org/chandler?view=rev&rev=10551>
> Author
>     alecf
> Date
>     2006-05-02 14:21:02 -0700 (Tue, 02 May 2006)
>
>
>       Log Message
>
> refactor the somewhat monolit
>
>
>       Added Paths
>
>     * trunk/chandler/parcels/osaf/framework/scripting/
>     * trunk/chandler/parcels/osaf/framework/scripting/User.py
>       <#trunkchandlerparcelsosafframeworkscriptingUserpy>
>     * trunk/chandler/parcels/osaf/framework/scripting/__init__.py
>       <#trunkchandlerparcelsosafframeworkscripting__init__py>
>     * trunk/chandler/parcels/osaf/framework/scripting/blocks.py
>       <#trunkchandlerparcelsosafframeworkscriptingblockspy>
>     * trunk/chandler/parcels/osaf/framework/scripting/proxy.py
>       <#trunkchandlerparcelsosafframeworkscriptingproxypy>
>     * trunk/chandler/parcels/osaf/framework/scripting/script.py
>       <#trunkchandlerparcelsosafframeworkscriptingscriptpy>
>
>
>       Removed Paths
>
>     * trunk/chandler/parcels/osaf/framework/scripting.py
>       <#trunkchandlerparcelsosafframeworkscriptingpy>
>
>
>       Diff
>
>
>         Added: trunk/chandler/parcels/osaf/framework/scripting/User.py
>         (10550 => 10551)
>
> --- trunk/chandler/parcels/osaf/framework/scripting/User.py	2006-05-02 21:11:44 UTC (rev 10550)
> +++ trunk/chandler/parcels/osaf/framework/scripting/User.py	2006-05-02 21:21:02 UTC (rev 10551)
> @@ -0,0 +1,170 @@
> +import wx
> +from application import schema, Globals
> +
> +"""
> +Emulating User-level Actions
> +"""
> +def emulate_typing(string, ctrlFlag = False, altFlag = False, shiftFlag = False):
> +    """ emulate_typing the string into the current focused widget """
> +    success = True
> +    def set_event_info(event):
> +        # setup event info for a keypress event
> +        event.m_keyCode = keyCode
> +        event.m_rawCode = keyCode
> +        event.m_shiftDown = char.isupper() or shiftFlag
> +        event.m_controlDown = event.m_metaDown = ctrlFlag
> +        event.m_altDown = altFlag
> +        event.SetEventObject(widget)
> +    # for each key, check for specials, then try several approaches
> +    app = wx.GetApp()
> +    for char in string:
> +        keyCode = ord(char)
> +        if keyCode == wx.WXK_RETURN:
> +            emulate_return()
> +        elif keyCode == wx.WXK_TAB:
> +            emulate_tab(shiftFlag=shiftFlag)
> +        else:
> +            # in case the focus has changed, get the new focused widget
> +            widget = wx.Window_FindFocus()
> +            if widget is None:
> +                success = False
> +            else:
> +                # try calling any bound key handler
> +                keyPress = wx.KeyEvent(wx.wxEVT_KEY_DOWN)
> +                set_event_info(keyPress)
> +                downWorked = widget.ProcessEvent(keyPress)
> +                keyUp = wx.KeyEvent(wx.wxEVT_KEY_UP)
> +                set_event_info(keyUp)
> +                upWorked = widget.ProcessEvent(keyUp)
> +                if not (downWorked or upWorked): # key handler worked?
> +                    # try calling EmulateKeyPress
> +
> +                    emulateMethod = getattr(widget, 'EmulateKeyPress',
> +                                            lambda k: False)
> +
> +                    if ('__WXMSW__' in wx.PlatformInfo or
> +                        not emulateMethod(keyPress)): # emulate worked?
> +                        # try calling WriteText
> +                        writeMethod = getattr(widget, 'WriteText', None)
> +                        if writeMethod:
> +                            writeMethod(char)
> +                        else:
> +                            success = False # remember we had a failure
> +                if success:
> +                    app.Yield()
> +    return success
> +
> +def emulate_tab(shiftFlag=False):
> +    if shiftFlag:
> +        flags = wx.NavigationKeyEvent.IsBackward
> +    else:
> +        flags = wx.NavigationKeyEvent.IsForward
> +    wx.Window_FindFocus().Navigate(flags)
> +
> +def emulate_click(block, x=None, y=None, double=False):
> +    """ Simulates left mouse click on the given block or widget """
> +    try:
> +        widget =  block.widget
> +    except AttributeError:
> +        widget = block
> +    # event settings
> +    mouseEnter = wx.MouseEvent(wx.wxEVT_ENTER_WINDOW)
> +    if double:
> +        mouseDown = wx.MouseEvent(wx.wxEVT_LEFT_DCLICK)
> +    else:
> +        mouseDown = wx.MouseEvent(wx.wxEVT_LEFT_DOWN)
> +    mouseUp = wx.MouseEvent(wx.wxEVT_LEFT_UP)
> +    mouseLeave = wx.MouseEvent(wx.wxEVT_LEAVE_WINDOW)
> +    if x:
> +        mouseEnter.m_x = mouseDown.m_x = mouseUp.m_x = x
> +    if y:
> +        mouseEnter.m_y = mouseDown.m_y = mouseUp.m_y = y
> +
> +    for event in (mouseEnter, mouseDown, mouseUp, mouseLeave):
> +        event.SetEventObject(widget)
> +
> +    # events processing
> +    widget.ProcessEvent(mouseEnter)
> +    widget.ProcessEvent(mouseDown)
> +    if not double:
> +        widget.ProcessEvent(mouseUp)
> +    widget.ProcessEvent(mouseLeave)
> +    # Give Yield to the App
> +    wx.GetApp().Yield()
> +
> +def emulate_return(block=None):
> +    """ Simulates a return-key event in the given block """
> +    try:
> +        if block :
> +            widget = block.widget
> +        else :
> +            widget = wx.Window_FindFocus()
> +    except AttributeError:
> +        return False
> +    else:
> +        # return-key down
> +        ret_d = wx.KeyEvent(wx.wxEVT_KEY_DOWN)
> +        ret_d.m_keyCode = wx.WXK_RETURN
> +        ret_d.SetEventObject(widget)
> +        # return-key up
> +        ret_up = wx.KeyEvent(wx.wxEVT_KEY_UP)
> +        ret_up.m_keyCode = wx.WXK_RETURN
> +        ret_up.SetEventObject(widget)
> +        # text updated event
> +        tu = wx.CommandEvent(wx.wxEVT_COMMAND_TEXT_UPDATED)
> +        tu.SetEventObject(widget)
> +        # kill focus event
> +        kf = wx.FocusEvent(wx.wxEVT_KILL_FOCUS)
> +        kf.SetEventObject(widget)
> +        # Text enter
> +        ent = wx.CommandEvent(wx.wxEVT_COMMAND_TEXT_ENTER)
> +        ent.SetEventObject(widget)
> +
> +        #work around for mac bug
> +        widget.ProcessEvent(tu) #for start/end time and location field
> +        #work around for canvasItem
> +        widget.ProcessEvent(kf) #for canvasItem title
> +        # events processing
> +        widget.ProcessEvent(ret_d)
> +        widget.ProcessEvent(ret_up)
> +        # Give Yield & Idle to the App
> +        idle()
> +        return True
> +
> +def emulate_sidebarClick(sidebar, cellName, double=False, overlay=False):
> +    ''' Process a left click on the given cell in the given sidebar
> +        if overlay is true the overlay disk next to the collection name is checked
> +        otherwise the collection is selected'''
> +    #determine x coordinate offset based on overlay value
> +    xOffset = 24
> +    if overlay:
> +        xOffset=3 
> +
> +    # find special collections by item because their names may
> +    # change (i.e. "All" becomes "My Items" or "My Calendar
> +    # Events" etc...
> +    pim_ns = schema.ns('osaf.pim', Globals.mainViewRoot)
> +    chandler_collections = {"All":pim_ns.allCollection}
> +    if cellName in chandler_collections.keys():
> +        cellName = chandler_collections[cellName]
> +
> +    cellRect = None
> +    for i in range(sidebar.widget.GetNumberRows()):
> +        item = sidebar.widget.GetTable().GetValue(i,0)[0]
> +        if item.displayName == cellName or item is cellName:
> +            cellRect = sidebar.widget.CalculateCellRect(i)
> +            break
> +    if cellRect:
> +        # events processing
> +        gw = sidebar.widget.GetGridWindow()
> +        # +3 work around for the sidebar bug
> +        emulate_click(gw, x=cellRect.GetX()+xOffset, y=cellRect.GetY()+3, double=double)
> +        return True
> +    else:
> +        return False
> +
> +def idle():
> +    app = wx.GetApp()
> +    app.Yield()
> +    app.ProcessEvent(wx.IdleEvent())
> +    app.Yield()
> Property changes on: trunk/chandler/parcels/osaf/framework/scripting/User.py
> ___________________________________________________________________
> Name: svn:eol-style
>    + native
>
>
>         Copied:
>         trunk/chandler/parcels/osaf/framework/scripting/__init__.py
>         (from rev 10543,
>         trunk/chandler/parcels/osaf/framework/scripting.py) (10543 =>
>         10551)
>
> --- trunk/chandler/parcels/osaf/framework/scripting.py	2006-05-02 18:07:48 UTC (rev 10543)
> +++ trunk/chandler/parcels/osaf/framework/scripting/__init__.py	2006-05-02 21:21:02 UTC (rev 10551)
> @@ -0,0 +1,75 @@
> +__copyright__ = "Copyright (c) 2005-2006 Open Source Applications Foundation"
> +__license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
> +
> +import logging
> +import wx
> +import os, sys
> +
> +from application import schema, Globals
> +
> +from osaf import pim
> +from osaf.framework.blocks import Block
> +from osaf.usercollections import UserCollection
> +
> +from i18n import OSAFMessageFactory as _
> +
> +from script import cats_profiler, hotkey_script, \
> +     run_script_with_symbols, run_startup_script_with_symbols, \
> +     Script, script_file
> +import User
> +from proxy import app_ns
> +
> +"""
> +This is the main module for scripting, and ties in multiple scripting components:
> +- basic loading and running of scripts (script.py)
> +- detail view blocks (blocks.py)
> +- User-level emulation (typing, and such) (User.py)
> +- app-level scripting (App and Block proxies - this file)
> +
> +"""
> +
> +
> +__all__ = [
> +    'app_ns', 'cats_profiler', 'hotkey_script', 
> +    'run_script', 'run_startup_script', 'Script', 'script_file',
> +    'User'
> +]
> +
> +logger = logging.getLogger(__name__)
> +
> +def installParcel(parcel, oldVersion=None):
> +
> +    scripts = pim.KindCollection.update(parcel, 'scripts',
> +        kind=Script.getKind(parcel.itsView)
> +    )
> +
> +    scriptsCollection = \
> +        pim.SmartCollection.update(parcel, 'scriptsCollection',
> +            displayName = _(u"Scripts"),
> +            renameable = False,
> +            private = False,
> +            source=scripts
> +            )
> +    userScripts = UserCollection(scriptsCollection)
> +    userScripts.dontDisplayAsCalendar = True
> +    
> +
> +    from blocks import installBlocks
> +    installBlocks(parcel, oldVersion)
> +
> +
> +# these are essentially wrappers around run_script_* that add app_ns and User
> +script_builtins = {'app_ns':app_ns, 'User':User }
> +
> +def run_script(*args, **kwds):
> +    builtIns = kwds.pop('builtIns', {})
> +    builtIns.update(script_builtins)
> +    run_script_with_symbols(builtIns=builtIns, *args, **kwds)
> +
> +
> +def run_startup_script(*args, **kwds):
> +    builtIns = kwds.pop('builtIns', {})
> +    builtIns.update(script_builtins)
> +    run_startup_script_with_symbols(builtIns=builtIns, *args, **kwds)
> +    
> +
>
>
>         Added:
>         trunk/chandler/parcels/osaf/framework/scripting/blocks.py
>         (10550 => 10551)
>
> --- trunk/chandler/parcels/osaf/framework/scripting/blocks.py	2006-05-02 21:11:44 UTC (rev 10550)
> +++ trunk/chandler/parcels/osaf/framework/scripting/blocks.py	2006-05-02 21:21:02 UTC (rev 10551)
> @@ -0,0 +1,227 @@
> +__copyright__ = "Copyright (c) 2005-2006 Open Source Applications Foundation"
> +__license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
> +
> +"""
> +This file provides block instances for the detail view
> +"""
> +
> +import wx
> +from i18n import OSAFMessageFactory as _
> +
> +from application import schema
> +from osaf.framework.blocks.detail import Detail
> +from osaf.framework.blocks import ControlBlocks
> +import osaf.framework.blocks.detail.Detail as Detail
> +import osaf.framework.attributeEditors.AttributeEditors as AttributeEditors
> +
> +def installBlocks(parcel, oldVersion=None):
> +    from osaf.pim import structs
> +    from osaf.framework.blocks import Block
> +    from script import Script
> +
> +    detail = schema.ns('osaf.framework.blocks.detail', parcel)
> +    blocks = schema.ns("osaf.framework.blocks", parcel.itsView)
> +
> +    # UI Elements:
> +    # -----------
> +    hotkeyArea = detail.makeArea(parcel, 'HotkeyArea',
> +                            position=0.6,
> +                            childrenBlocks=[
> +                                detail.makeLabel(parcel, _(u'Hotkey'), borderTop=4),
> +                                detail.makeSpacer(parcel, width=6),
> +                                detail.makeEditor(parcel, 'EditFKey',
> +                                           viewAttribute=u'fkey',
> +                                           stretchFactor=0.0,
> +                                           size=structs.SizeType(75, -1)),
> +                                detail.makeSpacer(parcel, width=60),
> +                                ]).install(parcel)
> +
> +    saveFileEvent = Block.BlockEvent.template('SaveFile',
> +                                              dispatchEnum='SendToSender',
> +                                              ).install(parcel)
> +
> +    openFileButton = OpenFileButton.template('openFileButton',
> +                                             title=_(u'Open'),
> +                                             buttonKind='Text',
> +                                             characterStyle=blocks.LabelStyle,
> +                                             stretchFactor=0.0,
> +                                             size=structs.SizeType(100, -1),
> +                                             event=saveFileEvent
> +                                             ).install(parcel)
> +
> +    saveFileButton = SaveFileButton.template('saveFileButton',
> +                                             title=_(u'Save'), 
> +                                             buttonKind='Text',
> +                                             characterStyle=blocks.LabelStyle,
> +                                             stretchFactor=0.0,
> +                                             size=structs.SizeType(100, -1),
> +                                             event=saveFileEvent
> +                                             ).install(parcel)
> +
> +    saveAsFileButton = SaveAsFileButton.template('saveAsFileButton',
> +                                             title=_(u'Save As'), # fill in title later
> +                                             buttonKind='Text',
> +                                             characterStyle=blocks.LabelStyle,
> +                                             stretchFactor=0.0,
> +                                             size=structs.SizeType(100, -1),
> +                                             event=saveFileEvent
> +                                             ).install(parcel)
> +
> +    testCheckboxArea = detail.makeArea(parcel, 'TestCheckboxArea',
> +                            position=0.7,
> +                            childrenBlocks=[
> +                                detail.makeLabel(parcel, _(u'test'), borderTop=4),
> +                                detail.makeSpacer(parcel, width=6),
> +                                detail.makeEditor(parcel, 'EditTest',
> +                                           viewAttribute=u'test',
> +                                           stretchFactor=0.0,
> +                                           minimumSize=structs.SizeType(16,-1)),
> +                                detail.makeSpacer(parcel, width=6),
> +                                openFileButton, saveFileButton, saveAsFileButton,
> +                                ]).install(parcel)
> +
> +    scriptTextArea = detail.makeEditor(parcel, 'NotesBlock',
> +                                       viewAttribute=u'body',
> +                                       presentationStyle={'lineStyleEnum': 'MultiLine',
> +                                                          'format': 'fileSynchronized'},
> +                                       position=0.9).install(parcel)
> +
> +    filePathArea = detail.makeArea(parcel, 'FilePathArea',
> +                                   baseClass=FilePathAreaBlock,
> +                                   position=0.8,
> +                                   childrenBlocks=[
> +                                       detail.makeLabel(parcel, _(u'path'), borderTop=4),
> +                                       detail.makeSpacer(parcel, width=6),
> +                                       detail.makeEditor(parcel, 'EditFilePath',
> +                                                         viewAttribute=u'filePath',
> +                                                         presentationStyle={'format': 'static'}
> +                                                         )
> +                                       ]).install(parcel)
> +
> +
> +    # Block Subtree for the Detail View of a Script
> +    # ------------
> +    detail.makeSubtree(parcel, Script, [
> +        detail.makeSpacer(parcel, height=6, position=0.01).install(parcel),
> +        detail.HeadlineArea,
> +        hotkeyArea,
> +        testCheckboxArea,
> +        filePathArea,
> +        detail.makeSpacer(parcel, height=7, position=0.8).install(parcel),
> +        scriptTextArea
> +    ])
> +
> +    AttributeEditors.AttributeEditorMapping.update(parcel, 
> +                                                   'Text+fileSynchronized',
> +                                                   className=__name__ + '.' +
> +                                                   'FileSynchronizedAttributeEditor')
> +
> +"""
> +Support classes
> +"""
> +class FileSynchronizedAttributeEditor(AttributeEditors.StringAttributeEditor):
> +    """
> +    Delegate for an Attribute Editor that synchronizes the attribute with
> +    a file's contents.
> +    """
> +    def BeginControlEdit (self, item, attributeName, control):
> +        """ 
> +        Prepare to edit this value. 
> +        """
> +        # sync first, then we'll have the right value
> +        item.sync_file_with_model()
> +        return super(FileSynchronizedAttributeEditor, self).BeginControlEdit(item, attributeName, control)
> +
> +    def SetAttributeValue (self, item, attributeName, value):
> +        """ Set the value of the attribute given by the value. """
> +        # has the data really changed?
> +        if value != self.GetAttributeValue(item, attributeName):
> +            # update the model data
> +            super(FileSynchronizedAttributeEditor, 
> +                         self).SetAttributeValue(item, attributeName, value)
> +            # update the file data too
> +            item.sync_file_with_model()
> +
> +class FilePathAreaBlock(Detail.DetailSynchronizedContentItemDetail):
> +    """
> +    Block to show (or hide) the File Path area of the script.
> +    """
> +    def shouldShow(self, item):
> +        return len(item.filePath) > 0
> +
> +class OpenFileButton(Detail.DetailSynchronizer, ControlBlocks.Button):
> +    """
> +    Block to show (or hide) the "Open File" button, and
> +    to handle that event.
> +    """
> +    def shouldShow(self, item):
> +        self._item = item
> +        return len(item.body) == 0
> +
> +    def onSaveFileEvent(self, event):
> +        """
> +        Open a file to associate with this script, or save an existing
> +        script to a file.
> +        """
> +        if not self._item.body:
> +            # no script body, open and overwrite existing model data
> +            title = _(u"Open a script file")
> +            message = _(u"Open an existing script file, or choose a name\n"
> +                        "for a new file for this script.")
> +            flags = wx.OPEN
> +        else:
> +            # model data exists, we need a place to write it
> +            title =_(u"Save this script file as")
> +            message = _(u"Choose an existing script file, or enter a name\n"
> +                        "for a new file for this script.")
> +            flags = wx.SAVE | wx.OVERWRITE_PROMPT
> +
> +        if self._item.filePath:
> +            # already have a file, default to that name and path
> +            # dirname returns unicode here since the filePath variable is unicode
> +            path = os.path.dirname(self._item.filePath)
> +            name = self._item.filePath.split(os.sep)[-1]
> +        else:
> +            # no file yet, point to scripts directory, use display name
> +            name = self._item.displayName+u".py"
> +            # dirname returns an str of bytes since the __file__ variable is bytes
> +            path = os.path.dirname(schema.ns('osaf.app', self).scripts.__file__)
> +            # convert the path bytes to unicode
> +            path = unicode(path, sys.getfilesystemencoding())
> +
> +        # present the Open/Save dialog
> +        result = Util.showFileDialog(wx.GetApp().mainFrame, 
> +                                     title,
> +                                     path,
> +                                     name,
> +                                     _(u"Python files|*.py|") + _(u"All files (*.*)|*.*"),
> +                                     flags)
> +        cmd, dir, fileName = result
> +
> +        if cmd == wx.ID_OK:
> +            preferFile = len(self._item.body) == 0
> +            writeFile = not preferFile
> +            self._item.filePath = os.path.join(dir, fileName)
> +            self._item.sync_file_with_model(preferFile=preferFile)
> +            resyncEvent = schema.ns('osaf.framework.blocks.detail', self).Resynchronize
> +            self.post(resyncEvent, {})
> +            self.postEventByName('ResyncDetailParent', {})
> +
> +class SaveFileButton(OpenFileButton):
> +    """
> +    Block to show (or hide) the "Save" button, and
> +    to handle that event.
> +    """
> +    def shouldShow(self, item):
> +        self._item = item
> +        return len(item.body) > 0 and len(item.filePath) == 0
> +
> +class SaveAsFileButton(OpenFileButton):
> +    """
> +    Block to show (or hide) the "Save As" button, and
> +    to handle that event.
> +    """
> +    def shouldShow(self, item):
> +        self._item = item
> +        return len(item.body) > 0 and len(item.filePath) > 0
> +    
> Property changes on: trunk/chandler/parcels/osaf/framework/scripting/blocks.py
> ___________________________________________________________________
> Name: svn:eol-style
>    + native
>
>
>         Added:
>         trunk/chandler/parcels/osaf/framework/scripting/proxy.py
>         (10550 => 10551)
>
> --- trunk/chandler/parcels/osaf/framework/scripting/proxy.py	2006-05-02 21:11:44 UTC (rev 10550)
> +++ trunk/chandler/parcels/osaf/framework/scripting/proxy.py	2006-05-02 21:21:02 UTC (rev 10551)
> @@ -0,0 +1,185 @@
> +__copyright__ = "Copyright (c) 2005-2006 Open Source Applications Foundation"
> +__license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
> +
> +
> +import wx
> +from datetime import datetime
> +from application import schema, Globals
> +from osaf.framework.blocks import Block
> +
> +def app_ns(view=None):
> +    if view is None:
> +        view = wx.GetApp().UIRepositoryView
> +    return AppProxy(view)
> +
> +class EventTiming(dict):
> +    """
> +    dictionary of event timings for events that have
> +    been sent by CPIA Script since this dictionary was reset.
> +    Use clear() to reset timings.
> +    """
> +    def start_timer(self):
> +        return datetime.now()
> +
> +    def end_timer(self, startTime, eventName):
> +        self.setdefault(eventName, []).append(datetime.now()-startTime)
> +
> +    def get_strings(self):
> +        strTimings = {}
> +        for eName, timingList in self.iteritems():
> +            strList = []
> +            for timing in timingList:
> +                strList.append(str(timing))
> +            strTimings[eName] = strList
> +        return strTimings
> +
> +    strings = property(get_strings)
> +
> +class BlockProxy(object):
> +    """
> +    Proxy for a Block which is dynamically located by name.
> +    Since Blocks come and go in CPIA, this proxy helps
> +    provide a solid reference point for locating the
> +    currently rendered Block by name.
> +    """
> +    def __init__(self, blockName, app_ns):
> +        # create an attribute that looks up this block by name
> +        self.proxy = blockName
> +        self.app_ns = app_ns
> +
> +    def __getattr__(self, attr):
> +        # if it's a block that's our child, return it
> +        child = getattr(self.app_ns, attr, None)
> +        if child is not None:
> +            return child
> +        
> +        # delegate the lookup to our View
> +        block = getattr(self.app_ns, self.proxy)
> +        return getattr(block, attr)
> +
> +    def focus(self):
> +        block = getattr(self.app_ns, self.proxy)
> +        block.widget.SetFocus()
> +
> +class RootProxy(BlockProxy):
> +    """ 
> +    Proxy to the Main View Root block. 
> +    Handles BlockEvents as methods.
> +    """
> +
> +    def __init__(self, *args, **keys):
> +        super(RootProxy, self).__init__(*args, **keys)
> +        # our timing object
> +        self.timing = EventTiming()
> +
> +    """
> +    We need to find the best BlockEvent at runtime on each invokation,
> +    because the BlockEvents come and go from the soup as UI portions
> +    are rendered and unrendered.  The best BlockEvent is the one copied
> +    into the soup and attached to rendered blocks that were also copied.
> +    """
> +    def post_script_event(self, eventName, event, argDict=None, timing=None, **keys):
> +        # Post the supplied event, keeping track of the timing if requested.
> +        # Also, call Yield() on the application, so it gets some time during
> +        #   script execution.
> +        if argDict is None:
> +            argDict = {}
> +        try:
> +            argDict.update(keys)
> +        except AttributeError:
> +            # make sure the first parameter was a dictionary, or give a friendly error
> +            message = "BlockEvents may only have one positional parameter - a dict"
> +            raise AttributeError, message
> +        # remember timing information
> +        if timing is not None:
> +            startTime = timing.start_timer()
> +        # post the event
> +        result = Globals.mainViewRoot.post(event, argDict)
> +        # finish timing
> +        if timing is not None:
> +            timing.end_timer(startTime, eventName)
> +        # let the Application get some time
> +        wx.GetApp().Yield()
> +        return result
> +
> +    # Attributes that are BlockEvents get converted to functions
> +    # that invoke that event.
> +    # All other attributes are redirected to the root view.
> +    def __getattr__(self, attr):
> +        def scripted_blockEvent(argDict=None, **keys):
> +            # merge the named parameters, into the dictionary positional arg
> +            return self.post_script_event(attr, best, argDict, timing=self.timing, **keys)
> +        best = Block.Block.findBlockEventByName(attr)
> +        if best is not None:
> +            return scripted_blockEvent
> +        else:
> +            # delegate the lookup to our View
> +            return getattr(Globals.mainViewRoot, attr)
> +
> +
> +"""
> +Children to use for the Detail View.
> +This is a mapping of the form:
> +attribute_name: block_name
> +"""
> +class AppProxy(object):
> +    """
> +    Proxy for the app namespace, and the items you'd expect
> +    in that namespace.
> +    Provides easy access to useful attributes, like "view".
> +    Has attributes for its major children blocks, like
> +    "root", "sidebar", "calendar", etc.
> +    All BlockEvents are mapped onto methods in this class, 
> +    so you can say AppProxy.NewTask() to post the "NewTask"
> +    event.
> +    """
> +    def __init__(self, view):
> +        # our view attribute
> +        self.itsView = view
> +        # we proxy to the app name space
> +        self.app_ns = schema.ns('osaf.app', view)
> +        # view proxies
> +        self.root = RootProxy('MainViewRoot', self)
> +        self.appbar = BlockProxy('ApplicationBar', self)
> +        self.markupbar = BlockProxy('MarkupBar', self)
> +        self.sidebar = BlockProxy('Sidebar', self)
> +        self.calendar = BlockProxy('CalendarSummaryView', self)
> +        self.summary = BlockProxy('TableSummaryView', self)
> +        self.detail = BlockProxy('DetailView', self)
> +
> +    def item_named(self, itemClass, itemName):
> +        for item in itemClass.iterItems(self.itsView):
> +            if self._name_of(item) == itemName:
> +                return item
> +        return None
> +
> +    @staticmethod
> +    def _name_of(item):
> +        try:
> +            return item.about
> +        except AttributeError:
> +            pass
> +        try:
> +            return item.blockName
> +        except AttributeError:
> +            pass
> +        try:
> +            return item.displayName
> +        except AttributeError:
> +            pass
> +        try:
> +            return item.itsName
> +        except AttributeError:
> +            pass
> +        return None
> +
> +    # Attributes that are named blocks are found by name.
> +    # All other attributes are redirected to the app name space.
> +    def __getattr__(self, attr):
> +        block = Block.Block.findBlockByName(attr)
> +        if block is not None:
> +            return block
> +        else:
> +            return getattr(self.app_ns, attr)
> +
> +
> Property changes on: trunk/chandler/parcels/osaf/framework/scripting/proxy.py
> ___________________________________________________________________
> Name: svn:eol-style
>    + native
>
>
>         Added:
>         trunk/chandler/parcels/osaf/framework/scripting/script.py
>         (10550 => 10551)
>
> --- trunk/chandler/parcels/osaf/framework/scripting/script.py	2006-05-02 21:11:44 UTC (rev 10550)
> +++ trunk/chandler/parcels/osaf/framework/scripting/script.py	2006-05-02 21:21:02 UTC (rev 10551)
> @@ -0,0 +1,277 @@
> +__copyright__ = "Copyright (c) 2005-2006 Open Source Applications Foundation"
> +__license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
> +
> +import wx
> +
> +import hotshot
> +from hotshot import stats
> +
> +from application import schema, Globals
> +
> +from osaf import pim
> +from osaf import messages
> +from i18n import OSAFMessageFactory as _
> +from datetime import datetime
> +import os, sys
> +
> +"""
> +This file provides the most basic scripting support. It defines what a
> +script is, and provides some basic functions for running them.
> +"""
> +
> +__all__ = [
> +    'cats_profiler', 'hotkey_script',
> +    'Script', 'script_file'
> +]
> +
> +class Script(pim.ContentItem):
> +    """ Persistent Script Item, to be executed. """
> +    schema.kindInfo(displayName=_(u"Script"), displayAttribute="displayName")
> +    lastRan = schema.One(schema.DateTime, displayName = _(u"last ran"))
> +    fkey = schema.One(schema.Text, initialValue = u'')
> +    test = schema.One(schema.Boolean, initialValue = False)
> +
> +    filePath = schema.One(schema.Text, initialValue = u'')
> +    lastSync = schema.One(schema.DateTime)
> +
> +    # redirections
> +
> +    about = schema.One(redirectTo = 'displayName')
> +    who = schema.One(redirectTo = 'creator')
> +    date = schema.One(redirectTo = 'lastRan')
> +
> +    def __init__(self, itsName=None, itsParent=None, itsKind=None, itsView=None,
> +                 body=None, *args, **keys):
> +        defaultName = messages.UNTITLED
> +        if itsName is not None:
> +            defaultName = unicode(itsName)
> +        keys.setdefault('displayName', defaultName)
> +        super(Script, self).__init__(
> +            itsName, itsParent, itsKind, itsView, *args, **keys
> +        )
> +        self.lastRan = datetime.now()
> +        self.lastSync = self.lastRan
> +        if body is not None:
> +            self.body = body # property for the body LOB
> +        self.private = False # can share scripts
> +
> +    def execute(self):
> +        self.sync_file_with_model()
> +        self.lastRan = datetime.now()
> +
> +        # this is a nasty hack to import from proxy.py.
> +        # This is the only way that scripts know about app_ns
> +        # and other chandler-specific proxies
> +        from proxy import app_ns
> +        run_script_with_symbols(self.body, fileName=self.filePath,
> +                                builtIns=dict(app_ns=app_ns))
> +
> +    def set_body_quietly(self, newValue):
> +        if newValue != self.body:
> +            oldQuiet = getattr(self, '_change_quietly', False)
> +            self._change_quietly = True
> +            self.body = newValue
> +            self._change_quietly = oldQuiet
> +
> +    def onValueChanged(self, name):
> +        if name == 'body':
> +            self.model_data_changed()
> +
> +    def model_data_changed(self):
> +        if self.filePath and not getattr(self, '_change_quietly', False):
> +            self.modelModTime = datetime.now()
> +
> +    def sync_file_with_model(self, preferFile=False):
> +        """
> +        Synchronize file and model - which ever is latest wins, with
> +        conflict dialogs possible if both have changed since the last sync.
> +        """
> +        if self.filePath and not getattr(self, '_change_quietly', False):
> +            writeFile = False
> +            # make sure we have a model modification time
> +            if not hasattr(self, 'modelModTime'):
> +                self.modelModTime = self.lastSync
> +            # get modification date for the file
> +            try:
> +                fileModTime = datetime.fromtimestamp(os.stat(self.filePath)[8])
> +            except OSError:
> +                fileModTime = self.lastSync
> +                writeFile = True
> +            if preferFile or fileModTime > self.modelModTime:
> +                self.set_body_quietly(self.file_contents(self.filePath))
> +            elif writeFile or fileModTime < self.modelModTime:
> +                # model is newer
> +                latest = self.modelModTime
> +                if fileModTime > self.lastSync:
> +                    caption = _(u"Overwrite script file?")
> +                    msg = _(u"The file associated with this script has been changed,"
> +                            "\nbut those changes are older than your recent edits.\n\n"
> +                            "Do you want to overwrite those file changes with your\n"
> +                            "recent edits of this script?")
> +                    if not Util.yesNo(wx.GetApp().mainFrame, caption, msg):
> +                        return
> +                self.write_file(self.body)
> +                fileModTime = datetime.fromtimestamp(os.stat(self.filePath)[8])
> +            # now the file and model match
> +            self.lastSync = self.modelModTime = fileModTime
> +
> +    def file_contents(self, filePath):
> +        """
> +        return the contents of our script file
> +        """
> +        scriptFile = open(filePath, 'rt')
> +        try:
> +            scriptText = scriptFile.read(-1)
> +        finally:
> +            scriptFile.close()
> +        return scriptText
> +
> +    def write_file(self, scriptText):
> +        """
> +        write the contents of our script file into the file
> +        """
> +        scriptFile = open(self.filePath, 'wt')
> +        try:
> +            scriptText = scriptFile.write(scriptText)
> +        finally:
> +            scriptFile.close()
> +
> +    def set_file(self, fileName, siblingPath):
> +        #Convert fileName with the system charset encoding
> +        #to bytes to prevent the join function from trying to downcast
> +        #the unicode fileName to ascii
> +        fileName = fileName.encode(sys.getfilesystemencoding())
> +        #Convert the filePath bytes to unicode for storage
> +        filePath = unicode(os.path.join(os.path.dirname(siblingPath), fileName), sys.getfilesystemencoding())
> +        self.body = self.file_contents(filePath)
> +        self.filePath = filePath
> +
> +
> +"""
> +Handle running a Script.
> +"""
> +def run_script_with_symbols(scriptText, fileName=u"", profiler=None, builtIns=None):
> +    """
> +    exec the supplied script providing everything from __all__ plus whatever
> +    globals got passed in.
> +    """
> +    assert len(scriptText) > 0, "Empty script"
> +    assert fileName is not None
> +
> +    if profiler:
> +        profiler.stop() # start with the profile turned off
> +
> +    scriptText = scriptText.encode(sys.getfilesystemencoding())
> +    fileName = fileName.encode(sys.getfilesystemencoding())
> +
> +    # compile the code
> +    scriptCode = compile(scriptText, fileName, 'exec')
> +
> +    # next, build a dictionary of names that are predefined
> +    if builtIns is None:
> +        builtIns = {}
> +
> +    for attr in __all__:
> +        builtIns[attr] = globals()[attr]
> +
> +    # Protect against scripts that don't stop, needed especially by 
> +    # automated tests.
> +    scriptTimeout = int(getattr(Globals.options, 'scriptTimeout', 0))
> +    if scriptTimeout > 0:
> +        try:
> +            from signal import signal, alarm, SIGALRM
> +        except ImportError:
> +            pass    # no alarm on Windows  :(
> +        else:
> +            def timeout(*args):
> +                sys.exit('Timeout error: Script did not finish within %d seconds.' % scriptTimeout)
> +            signal(SIGALRM, timeout)
> +            alarm(scriptTimeout)
> +
> +    # now run that script in our predefined scope
> +    exec scriptCode in builtIns
> +
> +def hotkey_script(event, view):
> +    """
> +    Check if the event is a hot key to run a script.
> +    Returns True if it does trigger a script to run, False otherwise.
> +    """
> +    keycode = event.GetKeyCode()
> +    # for now, we just allow function keys to be hot keys.
> +    if (wx.WXK_F1 <= keycode <= wx.WXK_F24
> +            and not event.AltDown()
> +            and not event.CmdDown()
> +            and not event.ControlDown()
> +            and not event.MetaDown()
> +            and not event.ShiftDown()):
> +        # try to find the corresponding Script
> +        targetFKey = _(u"F%(FunctionKeyNumber)s") % {'FunctionKeyNumber':unicode(keycode-wx.WXK_F1+1)}
> +
> +        # maybe we have an existing script?
> +        script = _findHotKeyScript(targetFKey, view)
> +        if script:
> +            wx.CallAfter(script.execute)
> +            return True
> +
> +    # not a hot key
> +    return False
> +
> +def _findHotKeyScript(targetFKey, view):
> +    # find a script that starts with the given name
> +    for aScript in Script.iterItems(view):
> +        if aScript.fkey == targetFKey:
> +            return aScript
> +    return None
> +
> +def run_startup_script_with_symbols(view, builtIns):
> +    global global_cats_profiler
> +    if Globals.options.testScripts:
> +        try:
> +            for aScript in Script.iterItems(view):
> +                if aScript.test:
> +                    aScript.execute()
> +        finally:
> +            # run the cleanup script
> +            schema.ns('osaf.app', view).CleanupAfterTests.execute()
> +
> +    fileName = Globals.options.scriptFile
> +    if fileName:
> +        scriptFileText = script_file(fileName)
> +        if scriptFileText:
> +            # check if we should turn on the profiler for this script
> +            if Globals.options.catsProfile:
> +                profiler = hotshot.Profile(Globals.options.catsProfile)
> +                global_cats_profiler = profiler
> +                profiler.runcall(run_script_with_symbols,
> +                                 scriptFileText,
> +                                 fileName=fileName, 
> +                                 profiler=profiler,
> +                                 builtIns=builtIns)
> +                profiler.close()
> +                global_cats_profiler = None
> +            else:
> +                run_script_with_symbols(scriptFileText, fileName = fileName,
> +                                        builtIns=builtIns)
> +                
> +global_cats_profiler = None # remember the CATS profiler here
> +
> +def cats_profiler():
> +    return global_cats_profiler
> +
> +def script_file(fileName, siblingPath=None):
> +    """
> +    Return the script from the file, given a file name
> +    and a path to a sibling file.
> +    """
> +    #Encode the unicode filename to the system character set encoding
> +    fileName = fileName.encode(sys.getfilesystemencoding())
> +
> +    if siblingPath is not None:
> +        fileName = os.path.join(os.path.dirname(siblingPath), fileName)
> +    # read the script from a file, and return it.
> +    scriptFile = open(fileName, 'rt')
> +    try:
> +        scriptText = scriptFile.read(-1)
> +    finally:
> +        scriptFile.close()
> +    return scriptText
> Property changes on: trunk/chandler/parcels/osaf/framework/scripting/script.py
> ___________________________________________________________________
> Name: svn:eol-style
>    + native
>
>
>         Deleted: trunk/chandler/parcels/osaf/framework/scripting.py
>         (10550 => 10551)
>
> --- trunk/chandler/parcels/osaf/framework/scripting.py	2006-05-02 21:11:44 UTC (rev 10550)
> +++ trunk/chandler/parcels/osaf/framework/scripting.py	2006-05-02 21:21:02 UTC (rev 10551)
> @@ -1,844 +0,0 @@
> -__copyright__ = "Copyright (c) 2005-2006 Open Source Applications Foundation"
> -__license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
> -
> -import application.Globals as Globals
> -import application.schema as schema
> -import osaf.pim as pim
> -import osaf.framework.blocks.Block as Block
> -import osaf.framework.blocks.ControlBlocks as ControlBlocks
> -from osaf.pim import structs
> -import osaf.framework.attributeEditors.AttributeEditors as AttributeEditors
> -import osaf.framework.blocks.detail.Detail as Detail
> -import application.dialogs.Util as Util
> -from repository.item.Item import Item
> -from datetime import datetime
> -import logging
> -import wx
> -import os, sys
> -import hotshot
> -import hotshot.stats as stats
> -from i18n import OSAFMessageFactory as _
> -from osaf import messages
> -from osaf.usercollections import UserCollection
> -
> -__all__ = [
> -    'app_ns', 'cats_profiler', 'hotkey_script', 
> -    'run_script', 'run_startup_script', 'Script', 'script_file',
> -    'User'
> -]
> -
> -logger = logging.getLogger(__name__)
> -
> -def installParcel(parcel, oldVersion=None):
> -
> -    scripts = pim.KindCollection.update(parcel, 'scripts',
> -        kind=Script.getKind(parcel.itsView)
> -    )
> -
> -    scriptsCollection = \
> -        pim.SmartCollection.update(parcel, 'scriptsCollection',
> -            displayName = _(u"Scripts"),
> -            renameable = False,
> -            private = False,
> -            source=scripts
> -            )
> -    userScripts = UserCollection(scriptsCollection)
> -    userScripts.dontDisplayAsCalendar = True
> -    
> -
> -    detail = schema.ns('osaf.framework.blocks.detail', parcel)
> -    blocks = schema.ns("osaf.framework.blocks", parcel.itsView)
> -
> -    # UI Elements:
> -    # -----------
> -    hotkeyArea = detail.makeArea(parcel, 'HotkeyArea',
> -                            position=0.6,
> -                            childrenBlocks=[
> -                                detail.makeLabel(parcel, _(u'Hotkey'), borderTop=4),
> -                                detail.makeSpacer(parcel, width=6),
> -                                detail.makeEditor(parcel, 'EditFKey',
> -                                           viewAttribute=u'fkey',
> -                                           stretchFactor=0.0,
> -                                           size=structs.SizeType(75, -1)),
> -                                detail.makeSpacer(parcel, width=60),
> -                                ]).install(parcel)
> -
> -    saveFileEvent = Block.BlockEvent.template('SaveFile',
> -                                              dispatchEnum='SendToSender',
> -                                              ).install(parcel)
> -
> -    openFileButton = OpenFileButton.template('openFileButton',
> -                                             title=_(u'Open'),
> -                                             buttonKind='Text',
> -                                             characterStyle=blocks.LabelStyle,
> -                                             stretchFactor=0.0,
> -                                             size=structs.SizeType(100, -1),
> -                                             event=saveFileEvent
> -                                             ).install(parcel)
> -
> -    saveFileButton = SaveFileButton.template('saveFileButton',
> -                                             title=_(u'Save'), 
> -                                             buttonKind='Text',
> -                                             characterStyle=blocks.LabelStyle,
> -                                             stretchFactor=0.0,
> -                                             size=structs.SizeType(100, -1),
> -                                             event=saveFileEvent
> -                                             ).install(parcel)
> -
> -    saveAsFileButton = SaveAsFileButton.template('saveAsFileButton',
> -                                             title=_(u'Save As'), # fill in title later
> -                                             buttonKind='Text',
> -                                             characterStyle=blocks.LabelStyle,
> -                                             stretchFactor=0.0,
> -                                             size=structs.SizeType(100, -1),
> -                                             event=saveFileEvent
> -                                             ).install(parcel)
> -
> -    testCheckboxArea = detail.makeArea(parcel, 'TestCheckboxArea',
> -                            position=0.7,
> -                            childrenBlocks=[
> -                                detail.makeLabel(parcel, _(u'test'), borderTop=4),
> -                                detail.makeSpacer(parcel, width=6),
> -                                detail.makeEditor(parcel, 'EditTest',
> -                                           viewAttribute=u'test',
> -                                           stretchFactor=0.0,
> -                                           minimumSize=structs.SizeType(16,-1)),
> -                                detail.makeSpacer(parcel, width=6),
> -                                openFileButton, saveFileButton, saveAsFileButton,
> -                                ]).install(parcel)
> -
> -    scriptTextArea = detail.makeEditor(parcel, 'NotesBlock',
> -                                       viewAttribute=u'body',
> -                                       presentationStyle={'lineStyleEnum': 'MultiLine',
> -                                                          'format': 'fileSynchronized'},
> -                                       position=0.9).install(parcel)
> -
> -    filePathArea = detail.makeArea(parcel, 'FilePathArea',
> -                                   baseClass=FilePathAreaBlock,
> -                                   position=0.8,
> -                                   childrenBlocks=[
> -                                       detail.makeLabel(parcel, _(u'path'), borderTop=4),
> -                                       detail.makeSpacer(parcel, width=6),
> -                                       detail.makeEditor(parcel, 'EditFilePath',
> -                                                         viewAttribute=u'filePath',
> -                                                         presentationStyle={'format': 'static'}
> -                                                         )
> -                                       ]).install(parcel)
> -
> -
> -    # Block Subtree for the Detail View of a Script
> -    # ------------
> -    detail.makeSubtree(parcel, Script, [
> -        detail.makeSpacer(parcel, height=6, position=0.01).install(parcel),
> -        detail.HeadlineArea,
> -        hotkeyArea,
> -        testCheckboxArea,
> -        filePathArea,
> -        detail.makeSpacer(parcel, height=7, position=0.8).install(parcel),
> -        scriptTextArea
> -    ])
> -
> -    AttributeEditors.AttributeEditorMapping.update(parcel, 
> -                                                   'Text+fileSynchronized',
> -                                                   className=__name__ + '.' + 'FileSynchronizedAttributeEditor')
> -
> -"""
> -Handle running a Script.
> -"""
> -def run_script(scriptText, fileName=u"", profiler=None):
> -    """
> -    exec the supplied script, in an environment equivalent to
> -    what you get when you say:
> -    from scripting.Helpers import *
> -    """
> -    assert len(scriptText) > 0, "Empty script"
> -    assert fileName is not None
> -
> -    if profiler:
> -        profiler.stop() # start with the profile turned off
> -
> -    scriptText = scriptText.encode(sys.getfilesystemencoding())
> -    fileName = fileName.encode(sys.getfilesystemencoding())
> -
> -    # compile the code
> -    scriptCode = compile(scriptText, fileName, 'exec')
> -
> -    # next, build a dictionary of names that are predefined
> -    builtIns = {}
> -
> -    for attr in __all__:
> -        builtIns[attr] = globals()[attr]
> -
> -    # Protect against scripts that don't stop, needed especially by 
> -    # automated tests.
> -    scriptTimeout = int(getattr(Globals.options, 'scriptTimeout', 0))
> -    if scriptTimeout > 0:
> -        try:
> -            from signal import signal, alarm, SIGALRM
> -        except ImportError:
> -            pass    # no alarm on Windows  :(
> -        else:
> -            def timeout(*args):
> -                sys.exit('Timeout error: Script did not finish within %d seconds.' % scriptTimeout)
> -            signal(SIGALRM, timeout)
> -            alarm(scriptTimeout)
> -
> -    # now run that script in our predefined scope
> -    exec scriptCode in builtIns
> -
> -class Script(pim.ContentItem):
> -    """ Persistent Script Item, to be executed. """
> -    schema.kindInfo(displayName=_(u"Script"), displayAttribute="displayName")
> -    lastRan = schema.One(schema.DateTime, displayName = _(u"last ran"))
> -    fkey = schema.One(schema.Text, initialValue = u'')
> -    test = schema.One(schema.Boolean, initialValue = False)
> -
> -    filePath = schema.One(schema.Text, initialValue = u'')
> -    lastSync = schema.One(schema.DateTime)
> -
> -    # redirections
> -
> -    about = schema.One(redirectTo = 'displayName')
> -    who = schema.One(redirectTo = 'creator')
> -    date = schema.One(redirectTo = 'lastRan')
> -
> -    def __init__(self, itsName=None, itsParent=None, itsKind=None, itsView=None,
> -                 body=None, *args, **keys):
> -        defaultName = messages.UNTITLED
> -        if itsName is not None:
> -            defaultName = unicode(itsName)
> -        keys.setdefault('displayName', defaultName)
> -        super(Script, self).__init__(
> -            itsName, itsParent, itsKind, itsView, *args, **keys
> -        )
> -        self.lastRan = datetime.now()
> -        self.lastSync = self.lastRan
> -        if body is not None:
> -            self.body = body # property for the body LOB
> -        self.private = False # can share scripts
> -
> -    def execute(self):
> -        self.sync_file_with_model()
> -        self.lastRan = datetime.now()
> -        run_script(self.body, fileName=self.filePath)
> -
> -    def set_body_quietly(self, newValue):
> -        if newValue != self.body:
> -            oldQuiet = getattr(self, '_change_quietly', False)
> -            self._change_quietly = True
> -            self.body = newValue
> -            self._change_quietly = oldQuiet
> -
> -    def onValueChanged(self, name):
> -        if name == 'body':
> -            self.model_data_changed()
> -
> -    def model_data_changed(self):
> -        if self.filePath and not getattr(self, '_change_quietly', False):
> -            self.modelModTime = datetime.now()
> -
> -    def sync_file_with_model(self, preferFile=False):
> -        """
> -        Synchronize file and model - which ever is latest wins, with
> -        conflict dialogs possible if both have changed since the last sync.
> -        """
> -        if self.filePath and not getattr(self, '_change_quietly', False):
> -            writeFile = False
> -            # make sure we have a model modification time
> -            if not hasattr(self, 'modelModTime'):
> -                self.modelModTime = self.lastSync
> -            # get modification date for the file
> -            try:
> -                fileModTime = datetime.fromtimestamp(os.stat(self.filePath)[8])
> -            except OSError:
> -                fileModTime = self.lastSync
> -                writeFile = True
> -            if preferFile or fileModTime > self.modelModTime:
> -                self.set_body_quietly(self.file_contents(self.filePath))
> -            elif writeFile or fileModTime < self.modelModTime:
> -                # model is newer
> -                latest = self.modelModTime
> -                if fileModTime > self.lastSync:
> -                    caption = _(u"Overwrite script file?")
> -                    msg = _(u"The file associated with this script has been changed,"
> -                            "\nbut those changes are older than your recent edits.\n\n"
> -                            "Do you want to overwrite those file changes with your\n"
> -                            "recent edits of this script?")
> -                    if not Util.yesNo(wx.GetApp().mainFrame, caption, msg):
> -                        return
> -                self.write_file(self.body)
> -                fileModTime = datetime.fromtimestamp(os.stat(self.filePath)[8])
> -            # now the file and model match
> -            self.lastSync = self.modelModTime = fileModTime
> -
> -    def file_contents(self, filePath):
> -        """
> -        return the contents of our script file
> -        """
> -        scriptFile = open(filePath, 'rt')
> -        try:
> -            scriptText = scriptFile.read(-1)
> -        finally:
> -            scriptFile.close()
> -        return scriptText
> -
> -    def write_file(self, scriptText):
> -        """
> -        write the contents of our script file into the file
> -        """
> -        scriptFile = open(self.filePath, 'wt')
> -        try:
> -            scriptText = scriptFile.write(scriptText)
> -        finally:
> -            scriptFile.close()
> -
> -    def set_file(self, fileName, siblingPath):
> -        #Convert fileName with the system charset encoding
> -        #to bytes to prevent the join function from trying to downcast
> -        #the unicode fileName to ascii
> -        fileName = fileName.encode(sys.getfilesystemencoding())
> -        #Convert the filePath bytes to unicode for storage
> -        filePath = unicode(os.path.join(os.path.dirname(siblingPath), fileName), sys.getfilesystemencoding())
> -        self.body = self.file_contents(filePath)
> -        self.filePath = filePath
> -
> -class FileSynchronizedAttributeEditor(AttributeEditors.StringAttributeEditor):
> -    """
> -    Delegate for an Attribute Editor that synchronizes the attribute with
> -    a file's contents.
> -    """
> -    def BeginControlEdit (self, item, attributeName, control):
> -        """ 
> -        Prepare to edit this value. 
> -        """
> -        # sync first, then we'll have the right value
> -        item.sync_file_with_model()
> -        return super(FileSynchronizedAttributeEditor, self).BeginControlEdit(item, attributeName, control)
> -
> -    def SetAttributeValue (self, item, attributeName, value):
> -        """ Set the value of the attribute given by the value. """
> -        # has the data really changed?
> -        if value != self.GetAttributeValue(item, attributeName):
> -            # update the model data
> -            super(FileSynchronizedAttributeEditor, 
> -                         self).SetAttributeValue(item, attributeName, value)
> -            # update the file data too
> -            item.sync_file_with_model()
> -
> -class FilePathAreaBlock(Detail.DetailSynchronizedContentItemDetail):
> -    """
> -    Block to show (or hide) the File Path area of the script.
> -    """
> -    def shouldShow(self, item):
> -        return len(item.filePath) > 0
> -
> -class OpenFileButton(Detail.DetailSynchronizer, ControlBlocks.Button):
> -    """
> -    Block to show (or hide) the "Open File" button, and
> -    to handle that event.
> -    """
> -    def shouldShow(self, item):
> -        self._item = item
> -        return len(item.body) == 0
> -
> -    def onSaveFileEvent(self, event):
> -        """
> -        Open a file to associate with this script, or save an existing
> -        script to a file.
> -        """
> -        if not self._item.body:
> -            # no script body, open and overwrite existing model data
> -            title = _(u"Open a script file")
> -            message = _(u"Open an existing script file, or choose a name\n"
> -                        "for a new file for this script.")
> -            flags = wx.OPEN
> -        else:
> -            # model data exists, we need a place to write it
> -            title =_(u"Save this script file as")
> -            message = _(u"Choose an existing script file, or enter a name\n"
> -                        "for a new file for this script.")
> -            flags = wx.SAVE | wx.OVERWRITE_PROMPT
> -
> -        if self._item.filePath:
> -            # already have a file, default to that name and path
> -            # dirname returns unicode here since the filePath variable is unicode
> -            path = os.path.dirname(self._item.filePath)
> -            name = self._item.filePath.split(os.sep)[-1]
> -        else:
> -            # no file yet, point to scripts directory, use display name
> -            name = self._item.displayName+u".py"
> -            # dirname returns an str of bytes since the __file__ variable is bytes
> -            path = os.path.dirname(schema.ns('osaf.app', self).scripts.__file__)
> -            # convert the path bytes to unicode
> -            path = unicode(path, sys.getfilesystemencoding())
> -
> -        # present the Open/Save dialog
> -        result = Util.showFileDialog(wx.GetApp().mainFrame, 
> -                                     title,
> -                                     path,
> -                                     name,
> -                                     _(u"Python files|*.py|") + _(u"All files (*.*)|*.*"),
> -                                     flags)
> -        cmd, dir, fileName = result
> -
> -        if cmd == wx.ID_OK:
> -            preferFile = len(self._item.body) == 0
> -            writeFile = not preferFile
> -            self._item.filePath = os.path.join(dir, fileName)
> -            self._item.sync_file_with_model(preferFile=preferFile)
> -            resyncEvent = schema.ns('osaf.framework.blocks.detail', self).Resynchronize
> -            self.post(resyncEvent, {})
> -            self.postEventByName('ResyncDetailParent', {})
> -
> -class SaveFileButton(OpenFileButton):
> -    """
> -    Block to show (or hide) the "Save" button, and
> -    to handle that event.
> -    """
> -    def shouldShow(self, item):
> -        self._item = item
> -        return len(item.body) > 0 and len(item.filePath) == 0
> -
> -class SaveAsFileButton(OpenFileButton):
> -    """
> -    Block to show (or hide) the "Save As" button, and
> -    to handle that event.
> -    """
> -    def shouldShow(self, item):
> -        self._item = item
> -        return len(item.body) > 0 and len(item.filePath) > 0
> -
> -
> -def hotkey_script(event, view):
> -    """
> -    Check if the event is a hot key to run a script.
> -    Returns True if it does trigger a script to run, False otherwise.
> -    """
> -    keycode = event.GetKeyCode()
> -    # for now, we just allow function keys to be hot keys.
> -    if (keycode >= wx.WXK_F1 and keycode <= wx.WXK_F24
> -            and not event.AltDown()
> -            and not event.CmdDown()
> -            and not event.ControlDown()
> -            and not event.MetaDown()
> -            and not event.ShiftDown()):
> -        # try to find the corresponding Script
> -        targetFKey = _(u"F%(FunctionKeyNumber)s") % {'FunctionKeyNumber':unicode(keycode-wx.WXK_F1+1)}
> -
> -        # maybe we have an existing script?
> -        script = _findHotKeyScript(targetFKey, view)
> -        if script:
> -            wx.CallAfter(script.execute)
> -            return True
> -
> -    # not a hot key
> -    return False
> -
> -def _findHotKeyScript(targetFKey, view):
> -    # find a script that starts with the given name
> -    for aScript in Script.iterItems(view):
> -        if aScript.fkey == targetFKey:
> -            return aScript
> -    return None
> -
> -global_cats_profiler = None # remember the CATS profiler here
> -
> -def run_startup_script(view):
> -    global global_cats_profiler
> -    if Globals.options.testScripts:
> -        try:
> -            for aScript in Script.iterItems(view):
> -                if aScript.test:
> -                    aScript.execute()
> -        finally:
> -            # run the cleanup script
> -            schema.ns('osaf.app', view).CleanupAfterTests.execute()
> -
> -    fileName = Globals.options.scriptFile
> -    if fileName:
> -        scriptFileText = script_file(fileName)
> -        if scriptFileText:
> -            # check if we should turn on the profiler for this script
> -            if Globals.options.catsProfile:
> -                profiler = hotshot.Profile(Globals.options.catsProfile)
> -                global_cats_profiler = profiler
> -                profiler.runcall(run_script, scriptFileText, fileName=fileName, 
> -                                               profiler=profiler)
> -                profiler.close()
> -                global_cats_profiler = None
> -            else:
> -                run_script(scriptFileText, fileName = fileName)
> -
> -def cats_profiler():
> -    return global_cats_profiler
> -
> -def script_file(fileName, siblingPath=None):
> -    """
> -    Return the script from the file, given a file name
> -    and a path to a sibling file.
> -    """
> -    #Encode the unicode filename to the system character set encoding
> -    fileName = fileName.encode(sys.getfilesystemencoding())
> -
> -    if siblingPath is not None:
> -        fileName = os.path.join(os.path.dirname(siblingPath), fileName)
> -    # read the script from a file, and return it.
> -    scriptFile = open(fileName, 'rt')
> -    try:
> -        scriptText = scriptFile.read(-1)
> -    finally:
> -        scriptFile.close()
> -    return scriptText
> -
> -class EventTiming(dict):
> -    """
> -    dictionary of event timings for events that have
> -    been sent by CPIA Script since this dictionary was reset.
> -    Use clear() to reset timings.
> -    """
> -    def start_timer(self):
> -        return datetime.now()
> -
> -    def end_timer(self, startTime, eventName):
> -        self.setdefault(eventName, []).append(datetime.now()-startTime)
> -
> -    def get_strings(self):
> -        strTimings = {}
> -        for eName, timingList in self.iteritems():
> -            strList = []
> -            for timing in timingList:
> -                strList.append(str(timing))
> -            strTimings[eName] = strList
> -        return strTimings
> -
> -    strings = property(get_strings)
> -
> -class BlockProxy(object):
> -    """
> -    Proxy for a Block which is dynamically located by name.
> -    Since Blocks come and go in CPIA, this proxy helps
> -    provide a solid reference point for locating the
> -    currently rendered Block by name.
> -    """
> -    def __init__(self, blockName, app_ns):
> -        # create an attribute that looks up this block by name
> -        self.proxy = blockName
> -        self.app_ns = app_ns
> -
> -    def __getattr__(self, attr):
> -        # if it's a block that's our child, return it
> -        child = getattr(self.app_ns, attr, None)
> -        if child is not None:
> -            return child
> -        
> -        # delegate the lookup to our View
> -        block = getattr(self.app_ns, self.proxy)
> -        return getattr(block, attr)
> -
> -    def focus(self):
> -        block = getattr(self.app_ns, self.proxy)
> -        block.widget.SetFocus()
> -
> -class RootProxy(BlockProxy):
> -    """ 
> -    Proxy to the Main View Root block. 
> -    Handles BlockEvents as methods.
> -    """
> -
> -    def __init__(self, *args, **keys):
> -        super(RootProxy, self).__init__(*args, **keys)
> -        # our timing object
> -        self.timing = EventTiming()
> -
> -    """
> -    We need to find the best BlockEvent at runtime on each invokation,
> -    because the BlockEvents come and go from the soup as UI portions
> -    are rendered and unrendered.  The best BlockEvent is the one copied
> -    into the soup and attached to rendered blocks that were also copied.
> -    """
> -    def post_script_event(self, eventName, event, argDict=None, timing=None, **keys):
> -        # Post the supplied event, keeping track of the timing if requested.
> -        # Also, call Yield() on the application, so it gets some time during
> -        #   script execution.
> -        if argDict is None:
> -            argDict = {}
> -        try:
> -            argDict.update(keys)
> -        except AttributeError:
> -            # make sure the first parameter was a dictionary, or give a friendly error
> -            message = "BlockEvents may only have one positional parameter - a dict"
> -            raise AttributeError, message
> -        # remember timing information
> -        if timing is not None:
> -            startTime = timing.start_timer()
> -        # post the event
> -        result = Globals.mainViewRoot.post(event, argDict)
> -        # finish timing
> -        if timing is not None:
> -            timing.end_timer(startTime, eventName)
> -        # let the Application get some time
> -        wx.GetApp().Yield()
> -        return result
> -
> -    # Attributes that are BlockEvents get converted to functions
> -    # that invoke that event.
> -    # All other attributes are redirected to the root view.
> -    def __getattr__(self, attr):
> -        def scripted_blockEvent(argDict=None, **keys):
> -            # merge the named parameters, into the dictionary positional arg
> -            return self.post_script_event(attr, best, argDict, timing=self.timing, **keys)
> -        best = Block.Block.findBlockEventByName(attr)
> -        if best is not None:
> -            return scripted_blockEvent
> -        else:
> -            # delegate the lookup to our View
> -            return getattr(Globals.mainViewRoot, attr)
> -
> -
> -"""
> -Children to use for the Detail View.
> -This is a mapping of the form:
> -attribute_name: block_name
> -"""
> -class AppProxy(object):
> -    """
> -    Proxy for the app namespace, and the items you'd expect
> -    in that namespace.
> -    Provides easy access to useful attributes, like "view".
> -    Has attributes for its major children blocks, like
> -    "root", "sidebar", "calendar", etc.
> -    All BlockEvents are mapped onto methods in this class, 
> -    so you can say AppProxy.NewTask() to post the "NewTask"
> -    event.
> -    """
> -    def __init__(self, view):
> -        # our view attribute
> -        self.itsView = view
> -        # we proxy to the app name space
> -        self.app_ns = schema.ns('osaf.app', view)
> -        # view proxies
> -        self.root = RootProxy('MainViewRoot', self)
> -        self.appbar = BlockProxy('ApplicationBar', self)
> -        self.markupbar = BlockProxy('MarkupBar', self)
> -        self.sidebar = BlockProxy('Sidebar', self)
> -        self.calendar = BlockProxy('CalendarSummaryView', self)
> -        self.summary = BlockProxy('TableSummaryView', self)
> -        self.detail = BlockProxy('DetailView', self)
> -
> -    def item_named(self, itemClass, itemName):
> -        for item in itemClass.iterItems(self.itsView):
> -            if self._name_of(item) == itemName:
> -                return item
> -        return None
> -
> -    @staticmethod
> -    def _name_of(item):
> -        try:
> -            return item.about
> -        except AttributeError:
> -            pass
> -        try:
> -            return item.blockName
> -        except AttributeError:
> -            pass
> -        try:
> -            return item.displayName
> -        except AttributeError:
> -            pass
> -        try:
> -            return item.itsName
> -        except AttributeError:
> -            pass
> -        return None
> -
> -    # Attributes that are named blocks are found by name.
> -    # All other attributes are redirected to the app name space.
> -    def __getattr__(self, attr):
> -        block = Block.Block.findBlockByName(attr)
> -        if block is not None:
> -            return block
> -        else:
> -            return getattr(self.app_ns, attr)
> -
> -    """
> -    Emulating User-level Actions
> -    """
> -class User(object):
> -    """
> -    Emulate User Actions
> -    """
> -    @classmethod
> -    def emulate_typing(cls, string, ctrlFlag = False, altFlag = False, shiftFlag = False):
> -        """ emulate_typing the string into the current focused widget """
> -        success = True
> -        def set_event_info(event):
> -            # setup event info for a keypress event
> -            event.m_keyCode = keyCode
> -            event.m_rawCode = keyCode
> -            event.m_shiftDown = char.isupper() or shiftFlag
> -            event.m_controlDown = event.m_metaDown = ctrlFlag
> -            event.m_altDown = altFlag
> -            event.SetEventObject(widget)
> -        # for each key, check for specials, then try several approaches
> -        app = wx.GetApp()
> -        for char in string:
> -            keyCode = ord(char)
> -            if keyCode == wx.WXK_RETURN:
> -                cls.emulate_return()
> -            elif keyCode == wx.WXK_TAB:
> -                cls.emulate_tab(shiftFlag=shiftFlag)
> -            else:
> -                # in case the focus has changed, get the new focused widget
> -                widget = wx.Window_FindFocus()
> -                if widget is None:
> -                    success = False
> -                else:
> -                    # try calling any bound key handler
> -                    keyPress = wx.KeyEvent(wx.wxEVT_KEY_DOWN)
> -                    set_event_info(keyPress)
> -                    downWorked = widget.ProcessEvent(keyPress)
> -                    keyUp = wx.KeyEvent(wx.wxEVT_KEY_UP)
> -                    set_event_info(keyUp)
> -                    upWorked = widget.ProcessEvent(keyUp)
> -                    if not (downWorked or upWorked): # key handler worked?
> -                        # try calling EmulateKeyPress
> -                        
> -                        emulateMethod = getattr(widget, 'EmulateKeyPress',
> -                                                lambda k: False)
> -                            
> -                        if ('__WXMSW__' in wx.PlatformInfo or
> -                            not emulateMethod(keyPress)): # emulate worked?
> -                            # try calling WriteText
> -                            writeMethod = getattr(widget, 'WriteText', None)
> -                            if writeMethod:
> -                                writeMethod(char)
> -                            else:
> -                                success = False # remember we had a failure
> -                    if success:
> -                        app.Yield()
> -        return success
> -
> -    @classmethod 
> -    def emulate_tab(cls, shiftFlag=False):
> -        if shiftFlag:
> -            flags = wx.NavigationKeyEvent.IsBackward
> -        else:
> -            flags = wx.NavigationKeyEvent.IsForward
> -        wx.Window_FindFocus().Navigate(flags)
> -
> -    @classmethod
> -    def emulate_click(cls, block, x=None, y=None, double=False):
> -        """ Simulates left mouse click on the given block or widget """
> -        try:
> -            widget =  block.widget
> -        except AttributeError:
> -            widget = block
> -        # event settings
> -        mouseEnter = wx.MouseEvent(wx.wxEVT_ENTER_WINDOW)
> -        if double:
> -            mouseDown = wx.MouseEvent(wx.wxEVT_LEFT_DCLICK)
> -        else:
> -            mouseDown = wx.MouseEvent(wx.wxEVT_LEFT_DOWN)
> -        mouseUp = wx.MouseEvent(wx.wxEVT_LEFT_UP)
> -        mouseLeave = wx.MouseEvent(wx.wxEVT_LEAVE_WINDOW)
> -        if x:
> -            mouseEnter.m_x = mouseDown.m_x = mouseUp.m_x = x
> -        if y:
> -            mouseEnter.m_y = mouseDown.m_y = mouseUp.m_y = y
> -
> -        for event in (mouseEnter, mouseDown, mouseUp, mouseLeave):
> -            event.SetEventObject(widget)
> -            
> -        # events processing
> -        widget.ProcessEvent(mouseEnter)
> -        widget.ProcessEvent(mouseDown)
> -        if not double:
> -            widget.ProcessEvent(mouseUp)
> -        widget.ProcessEvent(mouseLeave)
> -        # Give Yield to the App
> -        wx.GetApp().Yield()
> -
> -    @classmethod
> -    def emulate_return(cls, block=None):
> -        """ Simulates a return-key event in the given block """
> -        try:
> -            if block :
> -                widget = block.widget
> -            else :
> -                widget = wx.Window_FindFocus()
> -        except AttributeError:
> -            return False
> -        else:
> -            # return-key down
> -            ret_d = wx.KeyEvent(wx.wxEVT_KEY_DOWN)
> -            ret_d.m_keyCode = wx.WXK_RETURN
> -            ret_d.SetEventObject(widget)
> -            # return-key up
> -            ret_up = wx.KeyEvent(wx.wxEVT_KEY_UP)
> -            ret_up.m_keyCode = wx.WXK_RETURN
> -            ret_up.SetEventObject(widget)
> -            # text updated event
> -            tu = wx.CommandEvent(wx.wxEVT_COMMAND_TEXT_UPDATED)
> -            tu.SetEventObject(widget)
> -            # kill focus event
> -            kf = wx.FocusEvent(wx.wxEVT_KILL_FOCUS)
> -            kf.SetEventObject(widget)
> -            # Text enter
> -            ent = wx.CommandEvent(wx.wxEVT_COMMAND_TEXT_ENTER)
> -            ent.SetEventObject(widget)
> -
> -            #work around for mac bug
> -            widget.ProcessEvent(tu) #for start/end time and location field
> -            #work around for canvasItem
> -            widget.ProcessEvent(kf) #for canvasItem title
> -            # events processing
> -            widget.ProcessEvent(ret_d)
> -            widget.ProcessEvent(ret_up)
> -            # Give Yield & Idle to the App
> -            cls.idle()
> -            return True
> -
> -    @classmethod
> -    def emulate_sidebarClick(cls, sidebar, cellName, double=False, overlay=False):
> -        ''' Process a left click on the given cell in the given sidebar
> -            if overlay is true the overlay disk next to the collection name is checked
> -            otherwise the collection is selected'''
> -        #determine x coordinate offset based on overlay value
> -        xOffset = 24
> -        if overlay:
> -            xOffset=3 
> -
> -        # find special collections by item because their names may
> -        # change (i.e. "All" becomes "My Items" or "My Calendar
> -        # Events" etc...
> -        pim_ns = schema.ns('osaf.pim', Globals.mainViewRoot)
> -        chandler_collections = {"All":pim_ns.allCollection}
> -        if cellName in chandler_collections.keys():
> -            cellName = chandler_collections[cellName]
> -
> -        cellRect = None
> -        for i in range(sidebar.widget.GetNumberRows()):
> -            item = sidebar.widget.GetTable().GetValue(i,0)[0]
> -            if item.displayName == cellName or item is cellName:
> -                cellRect = sidebar.widget.CalculateCellRect(i)
> -                break
> -        if cellRect:
> -            # events processing
> -            gw = sidebar.widget.GetGridWindow()
> -            # +3 work around for the sidebar bug
> -            cls.emulate_click(gw, x=cellRect.GetX()+xOffset, y=cellRect.GetY()+3, double=double)
> -            return True
> -        else:
> -            return False
> -
> -    @classmethod
> -    def idle(cls):
> -        app = wx.GetApp()
> -        app.Yield()
> -        app.ProcessEvent(wx.IdleEvent())
> -        app.Yield()
> -
> -def app_ns(view=None):
> -    if view is None:
> -        view = wx.GetApp().UIRepositoryView
> -    return AppProxy(view)
>
>   
> ------------------------------------------------------------------------
>
> _______________________________________________
> Commits mailing list
> Commits at osafoundation.org
> http://lists.osafoundation.org/mailman/listinfo/commits
>   

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.osafoundation.org/pipermail/chandler-dev/attachments/20060502/9e56fd70/attachment.html


More information about the chandler-dev mailing list