#   Programmer: Daniel Pozmanter
#   E-mail:     drpython@bluebottle.com
#   Note:       You must reply to the verification e-mail to get through.
#
#   Copyright 2003-2010 Daniel Pozmanter
#
#   Distributed under the terms of the GPL (GNU Public License)
#
#    DrPython is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#Source Browser

#   Icons taken from "Noia Kde 100" by Carles Carbonell Bernado from the KDE-LOOK site (some edited a bit).
#   An excellent artist.

import wx
import drScrolledMessageDialog
from drProperty import *
import wx.stc
import os
import re

recolour = re.compile('#\w+')

def GetCount(line, compchar):
    l = len(line)
    x = 0
    y = 0
    while x < l:
        if line[x] == compchar:
            y = y + 1
        elif not line[x].isspace():
            x = l
        x = x + 1
    return y

class drTree(wx.TreeCtrl):
    def __init__(self, parent, id, point, size, style, ancestor):
        wx.TreeCtrl.__init__(self, parent, id, point, size, style)

        self.grandparent = ancestor
        self.parent = parent

        style = self.grandparent.prefs.sourcebrowserstyle
        yarrr = convertStyleStringToWXFontArray(style)

        if self.grandparent.prefs.sourcebrowseruseimages==1:
            imagesize = (16,16)

            self.imagelist = wx.ImageList(imagesize[0], imagesize[1])
            self.images = [wx.BitmapFromImage(wx.Image(os.path.join(self.grandparent.bitmapdirectory, "16/class.png"), wx.BITMAP_TYPE_PNG)),
            wx.BitmapFromImage(wx.Image(os.path.join(self.grandparent.bitmapdirectory, "16/def.png"), wx.BITMAP_TYPE_PNG)),
            wx.BitmapFromImage(wx.Image(os.path.join(self.grandparent.bitmapdirectory, "16/import.png"), wx.BITMAP_TYPE_PNG)),
            wx.BitmapFromImage(wx.Image(os.path.join(self.grandparent.bitmapdirectory, "16/transparent.png"), wx.BITMAP_TYPE_PNG))]

            map(self.imagelist.Add, self.images)

            self.AssignImageList(self.imagelist)

        w = wx.Font(yarrr[1], wx.NORMAL, wx.NORMAL, wx.NORMAL, yarrr[2])

        w.SetFaceName(yarrr[0])

        if yarrr[3]:
            w.SetWeight(wx.BOLD)
        else:
            w.SetWeight(wx.NORMAL)
        if yarrr[4]:
            w.SetStyle(wx.ITALIC)
        else:
            w.SetStyle(wx.NORMAL)

        self.SetFont(w)

        f = convertColorPropertyToColorArray(getStyleProperty("fore", style))
        b = convertColorPropertyToColorArray(getStyleProperty("back", style))

        self.TextColor = wx.Colour(f[0], f[1], f[2])

        self.SetForegroundColour(self.TextColor)

        self.SetBackgroundColour(wx.Colour(b[0], b[1], b[2]))

        self.Bind(wx.EVT_TREE_ITEM_ACTIVATED,  self.OnItemActivated, id=id)
        self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK,  self.OnItemActivated, id=id)

        self.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnExpandedCollapse, id=id)
        self.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnExpandedCollapse, id=id)

    def OnExpandedCollapse(self, event):
        event.Skip()
        #AB: it is necesserary to do something to refresh scroll bars

    def OnCompareItems(self, item1, item2):
        #Overriding Base, Return -1 for <, 0 for ==, +1 for >
        t1 = self.GetItemText(item1).lower()
        t2 = self.GetItemText(item2).lower()

        x = 0
        l = len(t1)
        if l > len(t2):
            l = len(t2)
        while x < l:
            if t1[x] < t2[x]:
                return -1
            elif t1[x] > t2[x]:
                return 1
            x = x + 1

        if l == len(t2):
            return -1

        return 0

    def OnItemActivated(self, event):
        sel = self.GetSelection()
        if not sel.IsOk():
            return
        t = self.GetItemText(sel)
        try:
            i = self.parent.ItemsIndex.index(sel)
            pos = self.parent.ItemsPos[i]
            line = self.grandparent.txtDocument.LineFromPosition(pos)
            if self.grandparent.prefs.docfolding:
                #Make sure the line is visible.
                self.grandparent.txtDocument.EnsureVisible(line)
            self.grandparent.txtDocument.ScrollToLine(line)
            self.grandparent.txtDocument.GotoLine(line)
            self.grandparent.txtDocument.GotoPos(pos)
            self.grandparent.Raise()
            self.grandparent.SetFocus()
            if self.grandparent.prefs.sourcebrowsercloseonactivate:
                self.parent.OnbtnClose(event)
            else:
                self.grandparent.txtDocument.SetFocus()
        except:
            drScrolledMessageDialog.ShowMessage(self.parent, "Error Activating Item", "Source Browser Error")

class drSourceBrowserPanel(wx.Panel):
    def __init__(self, parent, id, Position, Index):
        wx.Panel.__init__(self, parent, id)

        self.theSizer = wx.BoxSizer(wx.VERTICAL)

        self.mixed = 0

        self.renext = re.compile(r"^[ \t]*[^#^\s]", re.M)

        self.reinspect = re.compile(r'(^[ \t]*?class\s.*[(:])|(^[ \t]*?def\s.*[(:])|(^[ \t]*?import\s.*$)|(^[ \t]*?from\s.*$)|(^\s*#---.+)', re.MULTILINE)

        self.panelparent = parent.GetGrandParent().GetParent()

        self.parent = parent.GetGrandParent().GetGrandParent()

        self.classtree = drTree(self, -1, wx.Point(0, 0), (400, 200), wx.TR_DEFAULT_STYLE|wx.TR_HIDE_ROOT, self.parent)

        self.btnClose = wx.Button(self, 101, "&Close")
        self.btnRefresh = wx.Button(self, 102, "&Refresh")

        self.theSizer.Add(self.classtree, 9, wx.EXPAND)
        self.bSizer = wx.BoxSizer(wx.HORIZONTAL)
        self.bSizer.Add(self.btnRefresh, 0,  wx.SHAPED | wx.ALIGN_LEFT)
        self.bSizer.Add(self.btnClose, 0,  wx.SHAPED | wx.ALIGN_LEFT)
        self.edSearch = wx.TextCtrl(self, -1, "", size=(-1,-1)) #edit for search in tree
        self.bSizer.Add(self.edSearch, 1,   wx.ALIGN_RIGHT)

        self.theSizer.Add(self.bSizer, 0, wx.EXPAND)

        self.position = Position
        self.Index = Index

        self.Bind(wx.EVT_BUTTON, self.OnbtnClose, id=101)
        self.Bind(wx.EVT_BUTTON, self.OnbtnRefresh, id=102)
        self.parent.PBind(self.parent.EVT_DRPY_DOCUMENT_CHANGED, self.OnbtnRefresh, None)
        self.edSearch.Bind(wx.EVT_KEY_UP, self.OnedSearch)
        self.edSearch.SetToolTipString("Search in the class-tree")
        self.eol = self.parent.txtDocument.GetEndOfLineCharacter()

        if not self.Browse():
            self.mixed = 1
            msg = "This document is mixed.  It uses tabs and spaces for indentation.\nDrPython may not be able to correctly display the class browser.  Please use 'Edit:Whitespace:Clean Up Indentation' to fix this."
            drScrolledMessageDialog.ShowMessage(self, msg, "Check Indentation Results")

        self.SetAutoLayout(True)
        self.SetSizer(self.theSizer)

        #AB:
        self.Bind(wx.EVT_WINDOW_DESTROY, self.OnClose)

    def OnClose(self, event):
        """Cleaning old references.
        It is necessary when the notebook panel is closed on his tab menu. Otherwise we get:
        wx._core.PyDeadObjectError: The C++ part of the drSourceBrowserPanel object has
        been deleted, attribute access no longer allowed."""
        self.parent.SourceBrowser = None
        self.parent.PUnbind(self.parent.EVT_DRPY_DOCUMENT_CHANGED, self.OnbtnRefresh)
        #AB end

    def Browse(self):

        #Submitted Patch:  Christian Daven
        self.classtree.Freeze()
        #/Submitted Patch:  Christian Daven

        self.classtree.DeleteAllItems()

        if self.parent.txtDocument.filetype != 0: # no python file
            self.classtree.Thaw()
            return 1 #without sense; destroys class tree font format?

        self.root = self.classtree.AddRoot("")

        self.ItemsIndex = []

        self.ItemsPos = []


        self.targetText = self.parent.txtDocument.GetText()

        if self.mixed:
            return 1
        RootArray = [self.root]
        Roots = [self.root]
        currentRoot = 0
        Indents = [0]
        currentIndent = 0

        #What is this document using?
        result = self.parent.txtDocument.CheckIndentation()
        wasnotmixed = 1
        if result == 0:
            wasnotmixed = 0
            if self.parent.prefs.docusetabs[self.parent.txtDocument.filetype]:
                result = 1
            else:
                result = -1
        if result == 1:
            compchar = '\t'
            dec = 1
        else:
            compchar = " "
            dec = self.parent.prefs.doctabwidth[0]

        #Handle Triple Quoted Strings:
        self.targetText = self.RemoveTripleQuotedString(self.targetText)

        matcher = self.reinspect.finditer(self.targetText)

        #Get On With It!
        try:
            match = matcher.next()
        except:
            match = None
        while match is not None:
            matchedtext = match.group().strip()
            if matchedtext[0] == '#':
                nextmatch = self.renext.search(self.targetText[match.end():])

                indent = 0

                if nextmatch is not None:
                    indent = GetCount(nextmatch.group(), compchar)

                cR = currentRoot
                cI = currentIndent

                while indent < Indents[cI]:
                        cR = cR - 1
                        cI = cI - 1

                i = matchedtext[4:].find("---")
                if i > -1:
                    a = matchedtext[4:i+4]
                else:
                    a = matchedtext[4:]

                m = match.group().find('#')

                currentitem = self.classtree.AppendItem(Roots[cI], a)
                #applied patch from bug report [ 1215144 ], 11.04.2007:
                Roots.append(currentitem)
                self.classtree.SetPyData(Roots[-1], None)
                currentRoot += 1
                RootArray.append(Roots[currentRoot])
                self.ItemsIndex.append(currentitem)
                self.ItemsPos.append(match.start() + m)

                colours = recolour.findall(matchedtext)

                if len(colours) > 1:
                    try:
                        self.classtree.SetItemTextColour(currentitem, convertColorPropertyToColorArray(colours[0]))
                        self.classtree.SetItemBackgroundColour(currentitem, convertColorPropertyToColorArray(colours[1]))
                    except Exception, e:
                        print "Error Setting Label Colour:", e
                self.classtree.SetItemImage(currentitem, 3, wx.TreeItemIcon_Normal)
                self.classtree.SetItemImage(currentitem, 3, wx.TreeItemIcon_Expanded)
            else:
                indent = GetCount(match.group(), compchar)

                while indent < Indents[currentIndent]:
                        Roots.pop()
                        currentRoot = currentRoot - 1
                        Indents.pop()
                        currentIndent = currentIndent - 1

                Indents.append(indent + dec)
                currentIndent = currentIndent + 1
                currentitem = self.classtree.AppendItem(Roots[currentRoot], matchedtext)
                Roots.append(currentitem)
                #Submitted bugfix, Franz Steinhausler
                self.classtree.SetPyData(Roots[-1], None)
                currentRoot += 1
                RootArray.append(Roots[currentRoot])
                self.ItemsIndex.append(Roots[currentRoot])
                self.ItemsPos.append(match.start())
                if matchedtext[0] == 'c':
                    try:
                        fg, bg = convertStyleToColorArray(self.parent.prefs.PythonStyleDictionary[5])
                        self.classtree.SetItemTextColour(Roots[currentRoot], fg)
                        self.classtree.SetItemBackgroundColour(Roots[currentRoot], bg)
                    except Exception, e:
                        print "Error Setting Class Colour:", e
                    self.classtree.SetItemImage(Roots[currentRoot], 0, wx.TreeItemIcon_Normal)
                    self.classtree.SetItemImage(Roots[currentRoot], 0, wx.TreeItemIcon_Expanded)
                elif matchedtext[0] == 'd':
                    try:
                        fg, bg = convertStyleToColorArray(self.parent.prefs.PythonStyleDictionary[8])
                        self.classtree.SetItemTextColour(Roots[currentRoot], fg)
                        self.classtree.SetItemBackgroundColour(Roots[currentRoot], bg)
                    except Exception, e:
                        print "Error Setting Def Colour:", e
                    self.classtree.SetItemImage(Roots[currentRoot], 1, wx.TreeItemIcon_Normal)
                    self.classtree.SetItemImage(Roots[currentRoot], 1, wx.TreeItemIcon_Expanded)
                else:
                    self.classtree.SetItemImage(Roots[currentRoot], 2, wx.TreeItemIcon_Normal)
                    self.classtree.SetItemImage(Roots[currentRoot], 2, wx.TreeItemIcon_Expanded)
            try:
                match = matcher.next()
            except:
                match = None

        if self.parent.prefs.sourcebrowserissorted:
            self.classtree.SortChildren(self.root)
            x = 0
            l = len(RootArray)
            while x < l:
                self.classtree.SortChildren(RootArray[x])
                x = x + 1

        #Submitted Patch:  Christian Daven
        self.classtree.Thaw()
        #/Submitted Patch:  Christian Daven

        return wasnotmixed

    def OnedSearch(self, event): #search on tree
        o = event.GetEventObject()
        s = o.GetValue().lower()
        if len(s) < 2:
            return
        #s=self.classtree.GetItemText(self.classtree.GetSelection())
        sel = self.classtree.GetSelection()
        found = False
        start = False
        #First try after selected item:
        for item in self.ItemsIndex:
            if start:
                z = self.classtree.GetItemText(item).lower()
                if z.find(s) > 0:
                    self.classtree.SelectItem(item, True)
                    self.classtree.OnItemActivated(None)
                    o.SetFocus()
                    o.SetInsertionPointEnd()
                    found = True
                    break
            if item == sel:
                start = True
        #Second try from start
        if not found:
            for item in self.ItemsIndex:
                z = self.classtree.GetItemText(item).lower()
                if z.find(s) > 0:
                    self.classtree.SelectItem(item, True)
                    self.classtree.OnItemActivated(None)
                    o.SetFocus()
                    o.SetInsertionPointEnd()
                    break
        event.Skip()

    def OnbtnClose(self, event):
        self.parent.PUnbind(self.parent.EVT_DRPY_DOCUMENT_CHANGED, self.OnbtnRefresh)
        self.parent.SourceBrowser = None
        self.parent.txtDocument.SetFocus()
        self.panelparent.ClosePanel(self.position, self.Index)

    def OnbtnRefresh(self, event):
        self.mixed = 0
        if not self.Browse():
            self.mixed = 1
            msg = "This document is mixed.  It uses tabs and spaces for indentation.\nDrPython may not be able to correctly display the class browser.  Please use 'Edit:Whitespace:Clean Up Indentation' to fix this."
            drScrolledMessageDialog.ShowMessage(self, msg, "Check Indentation Results")
        if event is not None:
            event.Skip()

    def RemoveTripleQuotedString(self, text):
        text = self.removeStringTripleQuotedWith(text, "'''")
        text = self.removeStringTripleQuotedWith(text, '"""')
        return text

    def removeStringTripleQuotedWith(self, text, target):
        start = text.find(target)
        while start > -1:
            end = text[start+3:].find(target)
            if end == -1:
                text = text[:start]
            else:
                end = start + 3 + end
                text = text[:start] + "".zfill((end - start) + 3) + text[end+3:]
            start = text.find(target)
        return text
