###############################################################################
# Name: ed_theme.py                                                           #
# Purpose: Icon theme management for Editra                                   #
# Author: Cody Precord <cprecord@editra.org>                                  #
# Copyright: (c) 2007 Cody Precord <staff@editra.org>                         #
# License: wxWindows License                                                  #
###############################################################################

"""
Provide an interface for creating icon themes for Editra. This will allow for
themes to be created, installed, and managed as plugins, which means that they
can be installed as single file instead of dozens of individual image files.

@summary: Editra's theme interface and implementation

"""

__author__ = "Cody Precord <cprecord@editra.org>"
__svnid__ = "$Id: ed_theme.py 61142 2009-06-20 05:28:36Z CJP $"
__revision__ = "$Revision: 61142 $"

#--------------------------------------------------------------------------#
# Imports
import os
import wx

# Local Imports
import ed_glob
import util
import plugin
from profiler import Profile_Get, Profile_Set
import syntax.synglob as synglob
from syntax.syntax import SYNTAX_IDS

#--------------------------------------------------------------------------#

class ThemeI(plugin.Interface):
    """Interface for defining an icon theme in Editra
    When a icon theme is active Editra's ArtProvider will ask the active
    theme that implements this interface to give it a bitmap. The requests
    for bitmaps will be numerical ID values that come from ed_glob. These
    ID's are associated with different objects in the interface. The names
    are descriptive of the object they represent, for reference however
    see the implementation of the two main themes (Tango and Nuovo).

    @see: L{ed_glob}
    @see: L{syntax.synglob}

    """
    def GetName(self):
        """Return the name of this theme. This is used to identify the
        theme when the provider looks for resources based on user preferences

        @return: name string

        """

    def GetMenuBitmap(self, bmp_id):
        """Get the menu bitmap associated with the object id
        If this theme does not have a resource to provide for this
        object return a wx.NullBitmap.

        @return: 16x16 pixel bitmap

        """
        return wx.NullBitmap

    def GetFileBitmap(self, bmp_id):
        """Get the filetype bitmap associated with the object id, the
        valid object ids are defined in the module syntax.synglob and
        are used to request images for menu's and page tabs. The theme
        implimenting this interface should at least be able to
        provide an image for plain text files and return that for any
        unmapped types.

        If this theme does not have a resource to provide for this
        object return a wx.NullBitmap.

        @return: 16x16 pixel bitmap

        """
        return wx.NullBitmap

    def GetOtherBitmap(self, bmp_id):
        """Get the bitmap from the 'other' icon resources. Valid id's are
        identified by a mapping in the ART dictionary.
        
        If this theme does not have a resource to provide for this
        object return a wx.NullBitmap.

        @return: wx.Bitmap

        """
        return wx.NullBitmap

    def GetToolbarBitmap(self, bmp_id):
        """Get the toolbar bitmap associated with the object id. The
        toolbar icons must be returned as a 32x32 pixel bitmap any
        scaling that is needed will be handled by the art provider that
        requests the resource.

        If this theme does not have a resource to provide for this
        object return a wx.NullBitmap.

        @return: 32x32 pixel bitmap

        """
        return wx.NullBitmap

#-----------------------------------------------------------------------------#

class BitmapProvider(plugin.Plugin):
    """Plugin that fetches requested icons from the current active theme.

    """
    observers = plugin.ExtensionPoint(ThemeI)

    def __GetCurrentProvider(self):
        """Gets the provider of the current theme resources
        @return: ThemeI object

        """
        theme = Profile_Get('ICONS', 'str', u'')
        for prov in self.observers:
            if theme == prov.GetName():
                return prov

        # Case if a theme was deleted while it was the active theme
        if theme.lower() != u'default':
            Profile_Set('ICONS', u'Default')

        return None

    def _GetTango(self, bmp_id, client):
        """Try to get the icon from the default tango theme"""
        theme = None
        bmp = wx.NullBitmap
        for prov in self.observers:
            if prov.GetName() == TangoTheme.name:
                theme = prov
                break
        else:
            return bmp

        if client == wx.ART_TOOLBAR:
            bmp = theme.GetToolbarBitmap(bmp_id)
        elif client == wx.ART_MENU:
            bmp = theme.GetMenuBitmap(bmp_id)
        elif client == wx.ART_OTHER:
            bmp = theme.GetOtherBitmap(bmp_id)
        else:
            pass

        return bmp

    def GetThemes(self):
        """Gets a list of the installed and activated themes
        @return: list of strings

        """
        return [ name.GetName() for name in self.observers ]

    def GetBitmap(self, bmp_id, client):
        """Gets a 16x16 or 32x32 pixel bitmap depending on client value.
        May return a NullBitmap if no suitable bitmap can be
        found.

        @param bmp_id: id of bitmap to lookup
        @param client: wxART_MENU, wxART_TOOLBAR
        @see: L{ed_glob}

        """
        prov = self.__GetCurrentProvider()
        if prov is not None:
            if client == wx.ART_MENU:
                bmp = prov.GetMenuBitmap(bmp_id)
            elif client == wx.ART_OTHER:
                # Backwards compatibility for older interface
                if hasattr(prov, 'GetOtherBitmap'):
                    bmp = prov.GetOtherBitmap(bmp_id)
                else:
                    bmp = wx.NullBitmap
            else:
                bmp = prov.GetToolbarBitmap(bmp_id)

            if bmp.IsOk():
                return bmp

        # Try to fallback to tango theme when icon lookup fails
        bmp = self._GetTango(bmp_id, client)
        if bmp.IsOk():
            return bmp

        return wx.NullBitmap

#-----------------------------------------------------------------------------#
# Default theme data maps
ART = { ed_glob.ID_ABOUT  : u'about.png',
        ed_glob.ID_ADD_BM : u'bmark_add.png',
        ed_glob.ID_ADVANCED : u'advanced.png',
        ed_glob.ID_BACKWARD : u'backward.png',
        ed_glob.ID_BIN_FILE : u'bin_file.png',
        ed_glob.ID_CDROM  : u'cdrom.png',
        ed_glob.ID_CONTACT : u'mail.png',
        ed_glob.ID_COPY   : u'copy.png',
        ed_glob.ID_COMPUTER : u'computer.png',
        ed_glob.ID_CUT    : u'cut.png',
        ed_glob.ID_DELETE : u'delete.png',
        ed_glob.ID_DOCPROP : u'doc_props.png',
        ed_glob.ID_DOCUMENTATION : u'docs.png',
        ed_glob.ID_DOWN   : u'down.png',
        ed_glob.ID_EXIT   : u'quit.png',
        ed_glob.ID_FILE   : u'file.png',
        ed_glob.ID_FIND   : u'find.png',
        ed_glob.ID_FIND_REPLACE : u'findr.png',
        ed_glob.ID_FIND_RESULTS : u'find.png',
        ed_glob.ID_FLOPPY : u'floppy.png',
        ed_glob.ID_FOLDER : u'folder.png',
        ed_glob.ID_FONT   : u'font.png',
        ed_glob.ID_FORWARD : u'forward.png',
        ed_glob.ID_HARDDISK : u'harddisk.png',
        ed_glob.ID_HOMEPAGE : u'web.png',
        ed_glob.ID_HTML_GEN : u'html_gen.png',
        ed_glob.ID_INDENT : u'indent.png',
        ed_glob.ID_LOGGER : u'log.png',
        ed_glob.ID_NEW    : u'new.png',
        ed_glob.ID_NEW_WINDOW: u'newwin.png',
        ed_glob.ID_NEXT_MARK : u'bmark_next.png',
        ed_glob.ID_NEXT_POS : u'forward.png',
        ed_glob.ID_OPEN    : u'open.png',
        ed_glob.ID_PACKAGE : u'package.png',
        ed_glob.ID_PASTE   : u'paste.png',
        ed_glob.ID_PLUGMGR : u'plugin.png',
        ed_glob.ID_PRE_MARK : u'bmark_pre.png',
        ed_glob.ID_PRE_POS : u'backward.png',
        ed_glob.ID_PREF    : u'pref.png',
        ed_glob.ID_PRINT   : u'print.png',
        ed_glob.ID_PRINT_PRE : u'printpre.png',
        ed_glob.ID_PYSHELL : u'pyshell.png',
        ed_glob.ID_REDO    : u'redo.png',
        ed_glob.ID_REFRESH : u'refresh.png',
        ed_glob.ID_RTF_GEN : u'rtf_gen.png',
        ed_glob.ID_SAVE    : u'save.png',
        ed_glob.ID_SAVEALL : u'saveall.png',
        ed_glob.ID_SAVEAS  : u'saveas.png',
        ed_glob.ID_SELECTALL : u'selectall.png',
        ed_glob.ID_STOP    : u'stop.png',
        ed_glob.ID_STYLE_EDIT : u'style_edit.png',
        ed_glob.ID_TEX_GEN : u'tex_gen.png',
        ed_glob.ID_THEME  : u'theme.png',
        ed_glob.ID_UNDO   : u'undo.png',
        ed_glob.ID_UNINDENT : u'outdent.png',
        ed_glob.ID_UP     : u'up.png',
        ed_glob.ID_USB    : u'usb.png',
        ed_glob.ID_WEB    : u'web.png',
        ed_glob.ID_ZOOM_IN : u'zoomi.png',
        ed_glob.ID_ZOOM_OUT : u'zoomo.png',
        ed_glob.ID_ZOOM_NORMAL : u'zoomd.png',
        ed_glob.ID_READONLY : u'readonly.png',

        # code elements
        ed_glob.ID_CLASS_TYPE : u'class.png',
        ed_glob.ID_FUNCT_TYPE : u'function.png',
        ed_glob.ID_ELEM_TYPE : u'element.png',
        ed_glob.ID_VARIABLE_TYPE : u'variable.png',
        ed_glob.ID_ATTR_TYPE : u'attribute.png',
        ed_glob.ID_PROPERTY_TYPE : u'property.png',
        ed_glob.ID_METHOD_TYPE : u'method.png'
}

# File Type Art
MIME_ART = { synglob.ID_LANG_ADA : u'ada.png',
             synglob.ID_LANG_BASH : u'shell.png',
             synglob.ID_LANG_BOO : u'boo.png',
             synglob.ID_LANG_BOURNE : u'shell.png',
             synglob.ID_LANG_C : u'c.png',
             synglob.ID_LANG_CPP : u'cpp.png',
             synglob.ID_LANG_CSH : u'shell.png',
             synglob.ID_LANG_CSS : u'css.png',
             synglob.ID_LANG_DIFF : u'diff.png',
             synglob.ID_LANG_HTML : u'html.png',
             synglob.ID_LANG_JAVA : u'java.png',
             synglob.ID_LANG_KSH : u'shell.png',
             synglob.ID_LANG_LATEX : u'tex.png',
             synglob.ID_LANG_MAKE : u'makefile.png',
             synglob.ID_LANG_PASCAL : u'pascal.png',
             synglob.ID_LANG_PERL : u'perl.png',
             synglob.ID_LANG_PHP : u'php.png',
             synglob.ID_LANG_PS : u'postscript.png',
             synglob.ID_LANG_PYTHON : u'python.png',
             synglob.ID_LANG_RUBY : u'ruby.png',
             synglob.ID_LANG_TCL : u'tcl.png',
             synglob.ID_LANG_TEX : u'tex.png',
             synglob.ID_LANG_TXT : u'text.png',
             synglob.ID_LANG_XML : u'xml.png'
 }

#-----------------------------------------------------------------------------#

class TangoTheme(plugin.Plugin):
    """Represents the Tango Icon theme for Editra"""
    plugin.Implements(ThemeI)

    name = u'Tango'

    def __GetArtPath(self, client, mime=False):
        """Gets the path of the resource directory to get
        the bitmaps from.
        @param client: wx.ART_MENU/wx.ART_TOOLBAR
        @keyword mime: is this a filetype icon lookup
        @return: path of art resource
        @rtype: string

        """
        clients = { wx.ART_MENU : u"menu",
                    wx.ART_TOOLBAR : u"toolbar",
                    wx.ART_OTHER : u"other" }

        # Get the path
        if ed_glob.CONFIG['THEME_DIR'] == u'':
            theme = util.ResolvConfigDir(os.path.join(u"pixmaps", u"theme"))
            ed_glob.CONFIG['THEME_DIR'] = theme

        if mime:
            path = os.path.join(ed_glob.CONFIG['THEME_DIR'],
                                Profile_Get('ICONS'), u'mime')
        else:
            path = os.path.join(ed_glob.CONFIG['THEME_DIR'],
                                self.GetName(),
                                clients.get(client, u"menu"))

        path += os.sep
        if os.path.exists(path):
            return path
        else:
            return None

    def GetName(self):
        """Get the name of this theme
        @return: string

        """
        return TangoTheme.name

    def GetMenuBitmap(self, bmp_id):
        """Get a menu bitmap
        @param bmp_id: Id of bitmap to look for

        """
        if bmp_id in ART:
            path = self.__GetArtPath(wx.ART_MENU, mime=False)
            if path is not None:
                path = path + ART[bmp_id]
                if os.path.exists(path):
                    return wx.Bitmap(path, wx.BITMAP_TYPE_PNG)
        else:
            return self.GetFileBitmap(bmp_id)

        return wx.NullBitmap

    def GetFileBitmap(self, bmp_id):
        """Get a mime type bitmap from the theme
        @param bmp_id: Id of filetype bitmap to look up
        @see: L{syntax.synglob}

        """
        path = self.__GetArtPath(wx.ART_MENU, mime=True)
        if path is not None and bmp_id in SYNTAX_IDS:
            if bmp_id in MIME_ART:
                req = path + MIME_ART[bmp_id]
                if os.path.exists(req):
                    return wx.Bitmap(req, wx.BITMAP_TYPE_PNG)

            # Try to fall back to bmp for plain text when above is not found
            bkup = path + MIME_ART[synglob.ID_LANG_TXT]
            if os.path.exists(bkup):
                return wx.Bitmap(bkup, wx.BITMAP_TYPE_PNG)

        return wx.NullBitmap

    def GetOtherBitmap(self, bmp_id):
        """Get a other catagory bitmap.
        @param bmp_id: Id of art resource

        """
        if bmp_id in ART:
            path = self.__GetArtPath(wx.ART_OTHER, mime=False)
            if path is not None:
                path = path + ART[bmp_id]
                if os.path.exists(path):
                    return wx.Bitmap(path, wx.BITMAP_TYPE_PNG)

        return wx.NullBitmap

    def GetToolbarBitmap(self, bmp_id):
        """Get a toolbar bitmap
        @param bmp_id: Id of bitmap to look for
        @return: wx.NullBitmap or a 32x32 bitmap

        """
        if bmp_id in ART:
#            size = Profile_Get('ICON_SZ', default=(24, 24))
            path = self.__GetArtPath(wx.ART_TOOLBAR, mime=False)
            if path is not None:
#                tpath = os.path.join(path, '24', ART[bmp_id])
#                if size[0] == 24 and os.path.exists(tpath):
#                    path = tpath
#                else:
                path = path + ART[bmp_id]

                if os.path.exists(path):
                    return wx.Bitmap(path, wx.BITMAP_TYPE_PNG)

        return wx.NullBitmap
