# basic module browser.

# usage:
# >>> import browser
# >>> browser.Browse()
# or
# >>> browser.Browse(your_module)
import sys
import types
import __main__
import win32ui
from pywin.mfc import dialog

import hierlist

special_names = [ '__doc__', '__name__', '__self__' ]

#
# HierList items
class HLIPythonObject(hierlist.HierListItem):
	def __init__(self, myobject=None, name=None ):
		hierlist.HierListItem.__init__(self)
		self.myobject = myobject
		self.knownExpandable = None
		if name:
			self.name=name
		else:
			try:
				self.name=myobject.__name__
			except (AttributeError, TypeError):
				try:
					r = repr(myobject)
					if len(r)>20:
						r = r[:20] + "..."
					self.name=r
				except (AttributeError, TypeError):
					self.name="???"
	def __lt__(self, other):
		return self.name < other.name
	def __eq__(self, other):
		return self.name == other.name
	def __repr__(self):
		try:
			type = self.GetHLIType()
		except:
			type = "Generic"
		return "HLIPythonObject("+type+") - name: "+ self.name + " object: " + repr(self.myobject)
	def GetText(self):
		try:
			return str(self.name) + ' (' + self.GetHLIType() + ')'
		except AttributeError:
			return str(self.name) + ' = ' + repr(self.myobject)
	def InsertDocString(self, lst):
		ob = None
		try:
			ob = self.myobject.__doc__
		except (AttributeError, TypeError):
			pass
		# I don't quite grok descriptors enough to know how to
		# best hook them up. Eg:
		# >>> object.__getattribute__.__class__.__doc__
		# <attribute '__doc__' of 'wrapper_descriptor' objects>
		if ob and isinstance(ob, str):
			lst.insert(0, HLIDocString( ob, "Doc" ))

	def GetSubList(self):
		ret = []
		try:
			for (key, ob) in self.myobject.__dict__.iteritems():
				if key not in special_names:
					ret.append(MakeHLI( ob, key ) )
		except (AttributeError, TypeError):
			pass
		try:
			for name in self.myobject.__methods__:
				ret.append(HLIMethod( name ))	# no MakeHLI, as cant auto detect
		except (AttributeError, TypeError):
			pass
		try:
			for member in self.myobject.__members__:
				if not member in special_names:
					ret.append(MakeHLI(getattr(self.myobject, member), member))
		except (AttributeError, TypeError):
			pass
		ret.sort()
		self.InsertDocString(ret)
		return ret
	# if the has a dict, it is expandable.
	def IsExpandable(self):
		if self.knownExpandable is None:
			self.knownExpandable = self.CalculateIsExpandable()
		return self.knownExpandable

	def CalculateIsExpandable(self):
		if hasattr(self.myobject, '__doc__'):
			return 1
		try:
			for key in self.myobject.__dict__.iterkeys():
				if key not in special_names:
					return 1
		except (AttributeError, TypeError):
			pass
		try:
			self.myobject.__methods__
			return 1
		except (AttributeError, TypeError):
			pass
		try:
			for item in self.myobject.__members__:
				if item not in special_names:
					return 1
		except (AttributeError, TypeError):
			pass
		return 0
	def GetBitmapColumn(self):
		if self.IsExpandable():
			return 0
		else:
			return 4
	def TakeDefaultAction(self):
		ShowObject(self.myobject, self.name)


class HLIDocString(HLIPythonObject):
	def GetHLIType(self):
		return "DocString"
	def GetText(self):
		return self.myobject.strip()
	def IsExpandable(self):
		return 0
	def GetBitmapColumn(self):
		return 6

class HLIModule(HLIPythonObject):
	def GetHLIType(self):
		return "Module"

class HLIFrame(HLIPythonObject):
	def GetHLIType(self):
		return "Stack Frame"

class HLITraceback(HLIPythonObject):
	def GetHLIType(self):
		return "Traceback"

class HLIClass(HLIPythonObject):
	def GetHLIType(self):
		return "Class"
	def GetSubList(self):
		ret = []
		for base in self.myobject.__bases__:
			ret.append( MakeHLI(base, 'Base class: ' + base.__name__ ) )
		ret = ret + HLIPythonObject.GetSubList(self)
		return ret

class HLIMethod(HLIPythonObject):
	# myobject is just a string for methods.
	def GetHLIType(self):
		return "Method"
	def GetText(self):
		return "Method: " + self.myobject + '()'

class HLICode(HLIPythonObject):
	def GetHLIType(self):
		return "Code"
	def IsExpandable(self):
		return self.myobject
	def GetSubList(self):
		ret = []
		ret.append( MakeHLI( self.myobject.co_consts, "Constants (co_consts)" ))
		ret.append( MakeHLI( self.myobject.co_names, "Names (co_names)" ))
		ret.append( MakeHLI( self.myobject.co_filename, "Filename (co_filename)" ))
		ret.append( MakeHLI( self.myobject.co_argcount, "Number of args (co_argcount)"))
		ret.append( MakeHLI( self.myobject.co_varnames, "Param names (co_varnames)"))
		
		return ret

class HLIInstance(HLIPythonObject):
	def GetHLIType(self):
		return "Instance"
	def GetText(self):
		return str(self.name) + ' (Instance of class ' + str(self.myobject.__class__.__name__) + ')'
	def IsExpandable(self):
		return 1
	def GetSubList(self):
		ret = []
		ret.append( MakeHLI( self.myobject.__class__) )
		ret = ret + HLIPythonObject.GetSubList(self)
		return ret


class HLIBuiltinFunction(HLIPythonObject):
	def GetHLIType(self):
		return "Builtin Function"

class HLIFunction(HLIPythonObject):
	def GetHLIType(self):
		return "Function"
	def IsExpandable(self):
		return 1
	def GetSubList(self):
		ret = []
#		ret.append( MakeHLI( self.myobject.func_argcount, "Arg Count" ))
		try:
			ret.append( MakeHLI( self.myobject.func_argdefs, "Arg Defs" ))
		except AttributeError:
			pass
		try:
			code = self.myobject.__code__
			globs = self.myobject.__globals__
		except AttributeError:
			# must be py2.5 or earlier...
			code = self.myobject.func_code
			globs = self.myobject.func_globals
		ret.append(MakeHLI(code, "Code" ))
		ret.append(MakeHLI(globs, "Globals" ))
		self.InsertDocString(ret)
		return ret

class HLISeq(HLIPythonObject):
	def GetHLIType(self):
		return "Sequence (abstract!)"
	def IsExpandable(self):
		return len(self.myobject)>0
	def GetSubList(self):
		ret = []
		pos=0
		for item in self.myobject:
			ret.append(MakeHLI( item, '['+str(pos)+']' ) )
			pos=pos+1
		self.InsertDocString(ret)
		return ret

class HLIList(HLISeq):
	def GetHLIType(self):
		return "List"

class HLITuple(HLISeq):
	def GetHLIType(self):
		return "Tuple"

class HLIDict(HLIPythonObject):
	def GetHLIType(self):
		return "Dict"
	def IsExpandable(self):
		try:
			self.myobject.__doc__
			return 1
		except (AttributeError, TypeError):
			return len(self.myobject) > 0
	def GetSubList(self):
		ret = []
		keys = list(self.myobject.keys())
		keys.sort()
		for key in keys:
			ob = self.myobject[key]
			ret.append(MakeHLI( ob, str(key) ) )
		self.InsertDocString(ret)
		return ret

# In Python 1.6, strings and Unicode have builtin methods, but we dont really want to see these
class HLIString(HLIPythonObject):
    def IsExpandable(self):
        return 0

TypeMap = { type : HLIClass, 
            types.FunctionType: HLIFunction,
            tuple: HLITuple,
            dict: HLIDict,
            list: HLIList,
            types.ModuleType: HLIModule,
            types.CodeType : HLICode,
            types.BuiltinFunctionType : HLIBuiltinFunction,
            types.FrameType : HLIFrame,
            types.TracebackType : HLITraceback,
            str : HLIString,
            unicode : HLIString,
            int: HLIPythonObject,
            long: HLIPythonObject,
            bool: HLIPythonObject,
            float: HLIPythonObject,
           }

def MakeHLI( ob, name=None ):
	try:
		cls = TypeMap[type(ob)]
	except KeyError:
		# hrmph - this check gets more and more bogus as Python
		# improves.  Its possible we should just *always* use
		# HLIInstance?
		if hasattr(ob, '__class__'): # 'new style' class
			cls = HLIInstance
		else:
			cls = HLIPythonObject
	return cls( ob, name )

#########################################
#
# Dialog related.


class DialogShowObject(dialog.Dialog):
	def __init__(self, object, title):
		self.object = object
		self.title = title
		dialog.Dialog.__init__(self, win32ui.IDD_LARGE_EDIT)
	def OnInitDialog(self):
		import re
		self.SetWindowText(self.title)
		self.edit = self.GetDlgItem(win32ui.IDC_EDIT1)
		try:
			strval = str(self.object)
		except:
			t, v, tb = sys.exc_info()
			strval = "Exception getting object value\n\n%s:%s" % (t, v)
			tb = None
		strval = re.sub('\n','\r\n', strval)
		self.edit.ReplaceSel(strval)
		
def ShowObject(object, title):
	dlg = DialogShowObject(object, title)
	dlg.DoModal()

# And some mods for a sizable dialog from Sam Rushing!
import win32con
import win32api
import commctrl

class dynamic_browser (dialog.Dialog):
    style = win32con.WS_OVERLAPPEDWINDOW | win32con.WS_VISIBLE
    cs = (
        win32con.WS_CHILD           |
        win32con.WS_VISIBLE         |
        commctrl.TVS_HASLINES       |
        commctrl.TVS_LINESATROOT    |
        commctrl.TVS_HASBUTTONS
        )

    dt = [
        ["Python Object Browser", (0, 0, 200, 200), style, None, (8, "MS Sans Serif")],
        ["SysTreeView32", None, win32ui.IDC_LIST1, (0, 0, 200, 200), cs]
        ]

    def __init__ (self, hli_root):
        dialog.Dialog.__init__ (self, self.dt)
        self.hier_list = hierlist.HierListWithItems (
            hli_root,
            win32ui.IDB_BROWSER_HIER
            )
        self.HookMessage (self.on_size, win32con.WM_SIZE)

    def OnInitDialog (self):
        self.hier_list.HierInit (self)
        return dialog.Dialog.OnInitDialog (self)

    def OnOK(self):
        self.hier_list.HierTerm()
        self.hier_list = None
        return self._obj_.OnOK()
    def OnCancel(self):
        self.hier_list.HierTerm()
        self.hier_list = None
        return self._obj_.OnCancel()

    def on_size (self, params):
        lparam = params[3]
        w = win32api.LOWORD(lparam)
        h = win32api.HIWORD(lparam)
        self.GetDlgItem (win32ui.IDC_LIST1).MoveWindow((0,0,w,h))

def Browse (ob=__main__):
    " Browse the argument, or the main dictionary "
    root = MakeHLI (ob, 'root')
    if not root.IsExpandable():
        raise TypeError("Browse() argument must have __dict__ attribute, or be a Browser supported type")
        
    dlg = dynamic_browser (root)
    dlg.CreateWindow()

#
#
# Classes for using the browser in an MDI window, rather than a dialog
#
from pywin.mfc import docview
class BrowserTemplate(docview.DocTemplate):
	def __init__(self):
		docview.DocTemplate.__init__(self, win32ui.IDR_PYTHONTYPE, BrowserDocument, None, BrowserView)

	def OpenObject(self, root): # Use this instead of OpenDocumentFile.
		# Look for existing open document
		for doc in self.GetDocumentList():
			if doc.root==root:
				doc.GetFirstView().ActivateFrame()
				return doc
		# not found - new one.
		doc = BrowserDocument(self, root)
		frame = self.CreateNewFrame(doc)
		doc.OnNewDocument()
		self.InitialUpdateFrame(frame, doc, 1)
		return doc

class BrowserDocument (docview.Document):
	def __init__(self, template, root):
		docview.Document.__init__(self, template)
		self.root = root
		self.SetTitle("Browser: " + root.name)
	def OnOpenDocument (self, name):
		raise TypeError("This template can not open files")
		return 0

class BrowserView(docview.TreeView):
	def OnInitialUpdate(self):
		import commctrl
		rc = self._obj_.OnInitialUpdate()
		list=hierlist.HierListWithItems( self.GetDocument().root, win32ui.IDB_BROWSER_HIER, win32ui.AFX_IDW_PANE_FIRST)
		list.HierInit(self.GetParent())
		list.SetStyle(commctrl.TVS_HASLINES | commctrl.TVS_LINESATROOT | commctrl.TVS_HASBUTTONS)
		return rc

template = None
def MakeTemplate():
	global template
	if template is None:
		template = BrowserTemplate() #win32ui.IDR_PYTHONTYPE, BrowserDocument, None, BrowserView)

def BrowseMDI(ob=__main__):
	"""Browse an object using an MDI window.
	"""

	MakeTemplate()
	root = MakeHLI(ob, repr(ob))
	if not root.IsExpandable():
		raise TypeError("Browse() argument must have __dict__ attribute, or be a Browser supported type")
		
	template.OpenObject(root)

