##############################################################################
#
# Copyright (c) 2003 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
""" Map CMF actions to icons, for ease of building icon-centric toolbars.
 
$Id: ActionIconsTool.py 110650 2010-04-08 15:30:52Z tseaver $
"""
 
import os
 
from AccessControl.SecurityInfo import ClassSecurityInfo
from App.class_init import InitializeClass
from App.Common import package_home
from OFS.SimpleItem import SimpleItem
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from zope.interface import implements
 
from Products.CMFActionIcons.interfaces import IActionIconsTool
from Products.CMFActionIcons.permissions import ManagePortal
from Products.CMFActionIcons.permissions import View
from Products.CMFCore.Expression import Expression
from Products.CMFCore.utils import registerToolInterface
from Products.CMFCore.utils import UniqueObject
 
_wwwdir = os.path.join( package_home( globals() ), 'www' )
 
 
class ActionIcon( SimpleItem ):
 
    security = ClassSecurityInfo()
 
    _title = None           # Use the one supplied by the provider
    _priority = 0           # All animals are equal....
    _category = 'object'
    _action_id = 'view'
    _icon_expr_text = 'document_icon'
 
    def __init__( self
                , category
                , action_id
                , icon_expr_text=''
                , title=None
                , priority=0
                ):
 
        self._category = category
        self._action_id = action_id
        self.updateIconExpression( icon_expr_text )
        self._title = title
        self._priority = priority
 
    security.declareProtected( View, 'getTitle' )
    def getTitle( self ):
 
        """ Simple accessor """
        return self._title
 
    security.declareProtected( View, 'getPriority' )
    def getPriority( self ):
 
        """ Simple accessor """
        return self._priority
 
    security.declareProtected( View, 'getCategory' )
    def getCategory( self ):
 
        """ Simple accessor """
        return self._category
 
    security.declareProtected( View, 'getActionId' )
    def getActionId( self ):
 
        """ Simple accessor """
        return self._action_id
 
    security.declareProtected( View, 'getExpression' )
    def getExpression( self ):
 
        """ Simple accessor """
        return self._icon_expr_text
 
    security.declareProtected( View, 'getIconURL' )
    def getIconURL( self, context=None ):
 
        """ Simple accessor """
        if context is None:
            return self._icon_expr_text
 
        return self._icon_expr( context )
 
    security.declareProtected( ManagePortal, 'updateIconExpression' )
    def updateIconExpression( self, icon_expr_text ):
 
        """ Mutate icon expression. """
        self._icon_expr_text = icon_expr_text
 
        if not ':' in icon_expr_text: # default to 'string:' type
            icon_expr_text = 'string:%s' % icon_expr_text
 
        self._icon_expr = Expression( icon_expr_text )
 
InitializeClass( ActionIcon )
 
 
class ActionIconsTool( UniqueObject, SimpleItem ):
 
    """ Map actions only icons.
    """
 
    implements(IActionIconsTool)
 
    meta_type = 'Action Icons Tool'
    id = 'portal_actionicons'
 
    security = ClassSecurityInfo()
    security.declareObjectProtected( View )
 
    def __init__( self ):
 
        self.clearActionIcons()
 
    #
    #   Accessors
    #
    security.declareProtected( ManagePortal, 'listActionIcons' )
    def listActionIcons( self ):
 
        """ Return a sequence of mappings for action icons
 
        o Mappings are in the form: ( category, action ) -> icon,
          where category and action are strings and icon is an ActionIcon
          instance.
        """
        return [ x.__of__( self ) for x in self._icons ]
 
    security.declareProtected( View, 'getActionInfo' )
    def getActionInfo( self
                     , category
                     , action_id
                     , context=None
                     ):
 
        """ Return a tuple, '(title, priority, icon ID), for the given action.
 
        o Raise a KeyError if no icon has been defined for the action.
        """
        ai = self._lookup[ ( category, action_id ) ]
        return ( ai.getTitle()
               , ai.getPriority()
               , ai.getIconURL( context )
               )
 
    security.declareProtected( View, 'queryActionInfo' )
    def queryActionInfo( self
                       , category
                       , action_id
                       , default=None
                       , context=None
                       ):
 
        """ Return a tuple, '(title, priority, icon ID), for the given action.
 
        o Return 'default' if no icon has been defined for the action.
        """
        ai = self._lookup.get( ( category, action_id ) )
        return ai and ( ai.getTitle()
                      , ai.getPriority()
                      , ai.getIconURL( context )
                      ) or default
 
    security.declareProtected( View, 'getActionIcon' )
    def getActionIcon( self, category, action_id, context=None ):
 
        """ Return an icon ID for the given action.
 
        o Raise a KeyError if no icon has been defined for the action.
 
        o Context is an Expression context object, used to evaluate
          TALES expressions.
        """
        return self._lookup[ ( category, action_id ) ].getIconURL( context )
 
    security.declareProtected( View, 'queryActionIcon' )
    def queryActionIcon( self, category, action_id
                       , default=None, context=None ):
 
        """ Return an icon ID for the given action.
 
        o Return 'default' if no icon has been defined for the action.
 
        o Context is an Expression context object, used to evaluate
          TALES expressions.
        """
        ai = self._lookup.get( ( category, action_id ) )
        return ai and ai.getIconURL( context ) or default
 
    security.declareProtected( View, 'updateActionDicts' )
    def updateActionDicts( self, categorized_actions, context=None ):
 
        """ Update a set of dictionaries, adding 'title, 'priority', and
            'icon' keys.
 
        o S.b. passed a data structure like that returned from ActionsTool's
          'listFilteredActionsFor':
 
          - Dict mapping category -> seq. of dicts, where each of the
            leaf dicts must have 'category' and 'id' keys.
 
        o *Will* overwrite the 'title' key, if title is defined on the tool.
 
        o *Will* overwrite the 'priority' key.
 
        o *Will* overwrite the 'icon' key, if icon is defined on the tool
 
        o XXX:  Don't have a way to pass Expression context yet.
        """
        result = {}
 
        for category, actions in categorized_actions.items():
 
            new_actions = []
 
            for action in actions:
 
                action = action.copy()
 
                action_id = action.get( 'id' )
 
                #  Hack around DCWorkflow's ID-less worklist actions.
                if action_id is None and action.get( 'category' ) == 'workflow':
                    action[ 'id' ] = action_id = action.get( 'name' )
 
                if action_id:
 
                    info = self.queryActionInfo( category
                                               , action_id
                                               , context=context
                                               )
                    if info is not None:
 
                        title, priority, icon = info
 
                        if title is not None:
                            action[ 'title' ] = title
 
                        if priority is not None:
                            action[ 'priority' ] = priority
 
                        if icon is not None:
                            action[ 'icon' ] = icon
 
                new_actions.append( action )
 
            new_actions.sort( lambda x, y: cmp( x.get( 'priority', 0 )
                                              , y.get( 'priority', 0 )
                                              ) )
            result[ category ] = new_actions
 
        return result
 
    __call__ = updateActionDicts
 
    #
    #   Mutators
    #
    security.declareProtected( ManagePortal, 'addActionIcon' )
    def addActionIcon( self
                     , category
                     , action_id
                     , icon_expr
                     , title=None
                     , priority=0
                     ):
 
        """ Add an icon for the given action.
 
        o Raise KeyError if an icon has already been defined.
        """
        if self.queryActionInfo( category, action_id ) is not None:
            raise KeyError, 'Duplicate definition!'
 
        icons = list( self._icons )
        icons.append( ActionIcon( category
                                , action_id
                                , icon_expr
                                , title
                                , priority
                                ) )
        self._lookup[ ( category, action_id ) ] = icons[-1]
        self._icons = tuple( icons )
 
    security.declareProtected( ManagePortal, 'updateActionIcon' )
    def updateActionIcon( self
                        , category
                        , action_id
                        , icon_expr
                        , title=None
                        , priority=0
                        ):
 
        """ Update the icon for the given action.
 
        o Raise KeyError if an icon has not already been defined.
        """
        if self._lookup.get( ( category, action_id ) ) is None:
            raise KeyError, 'No such definition!'
 
        icons = list( self._icons )
        for ai in icons:
            if ( ai.getCategory() == category
             and ai.getActionId() == action_id
               ):
                ai.updateIconExpression( icon_expr )
                ai._title = title
                ai._priority = priority
                break
        else:
            raise KeyError, ( category, action_id )
        self._icons = tuple( icons )
 
    security.declareProtected( ManagePortal, 'removeActionIcon' )
    def removeActionIcon( self, category, action_id ):
 
        """ Remove the icon for the given action.
 
        o Raise KeyError if an icon has not already been defined.
        """
        if self.queryActionInfo( category, action_id ) is None:
            raise KeyError, 'No such definition (%s, %s)!' % (
                category, action_id)
 
        icons = list( self._icons )
        icon = self._lookup[ ( category, action_id ) ]
        icons.remove( icon )
        del self._lookup[ ( category, action_id ) ]
        self._icons = tuple( icons )
 
    security.declareProtected( ManagePortal, 'clearActionIcons' )
    def clearActionIcons( self ):
 
        """ Remove all mappings from the tool.
        """
        self._icons = ()
        self._lookup = {}
 
    #
    #   ZMI
    #
    manage_options =  ( { 'label' : 'Icons'
                        , 'action' : 'manage_editActionIcons'
                        }
                      ,
                      ) + SimpleItem.manage_options
 
    security.declareProtected( ManagePortal, 'manage_editActionIcons' )
    manage_editActionIcons = PageTemplateFile( 'aitEdit', _wwwdir )
 
    security.declareProtected( ManagePortal, 'manage_addActionIcon' )
    def manage_addActionIcon( self
                            , category
                            , action_id
                            , icon_expr
                            , title
                            , priority
                            , REQUEST
                            ):
 
        """ Add an icon for the given action via the ZMI.
        """
        self.addActionIcon( category
                          , action_id
                          , icon_expr
                          , title
                          , priority
                          )
 
        REQUEST['RESPONSE'].redirect( '%s/manage_editActionIcons'
                                      '?manage_tabs_message=Action+added.'
                                    % self.absolute_url()
                                    )
 
    security.declareProtected( ManagePortal, 'manage_updateActionIcon' )
    def manage_updateActionIcon( self
                               , category
                               , action_id
                               , icon_expr
                               , title
                               , priority
                               , REQUEST
                               ):
 
        """ Update an icon for the given action via the ZMI.
        """
        self.updateActionIcon( category
                             , action_id
                             , icon_expr
                             , title
                             , priority
                             )
 
        REQUEST['RESPONSE'].redirect( '%s/manage_editActionIcons'
                                      '?manage_tabs_message=Action+updated.'
                                    % self.absolute_url()
                                    )
 
    security.declareProtected( ManagePortal, 'manage_removeActionIcon' )
    def manage_removeActionIcon( self, category, action_id, REQUEST ):
 
        """ Remove the icon for the given action via the ZMI.
        """
        self.removeActionIcon( category, action_id )
 
        REQUEST['RESPONSE'].redirect( '%s/manage_editActionIcons'
                                      '?manage_tabs_message=Action+removed.'
                                    % self.absolute_url()
                                    )
 
InitializeClass( ActionIconsTool )
registerToolInterface('portal_actionicons', IActionIconsTool)