# Regedit - a Registry Editor for Python
import win32api, win32ui, win32con, commctrl
from pywin.mfc import window, docview, dialog
import hierlist
import regutil
import string

def SafeApply( fn, args, err_desc = "" ):
	try:
		fn(*args)
		return 1
	except win32api.error, exc:
		msg = "Error " + err_desc + "\r\n\r\n" + exc.strerror
		win32ui.MessageBox(msg)
		return 0

class SplitterFrame(window.MDIChildWnd):
	def __init__(self):
		# call base CreateFrame
		self.images = None
		window.MDIChildWnd.__init__(self)

	def OnCreateClient(self, cp, context):
		splitter = win32ui.CreateSplitter()
		doc = context.doc
		frame_rect = self.GetWindowRect()
		size = ((frame_rect[2] - frame_rect[0]),
		        (frame_rect[3] - frame_rect[1])//2)
		sub_size = (size[0]//3, size[1])
		splitter.CreateStatic (self, 1, 2)
		# CTreeControl view
		self.keysview = RegistryTreeView(doc)
		# CListControl view
		self.valuesview = RegistryValueView(doc)

		splitter.CreatePane (self.keysview, 0, 0, (sub_size))
		splitter.CreatePane (self.valuesview, 0, 1, (0,0)) # size ignored.
		splitter.SetRowInfo(0, size[1] ,0)
		# Setup items in the imagelist

		return 1

	def OnItemDoubleClick(self, info, extra):
		(hwndFrom, idFrom, code) = info
		if idFrom==win32ui.AFX_IDW_PANE_FIRST:
			# Tree control
			return None
		elif idFrom==win32ui.AFX_IDW_PANE_FIRST + 1:
			item = self.keysview.SelectedItem()
			self.valuesview.EditValue(item)
			return 0
			# List control
		else:
			return None # Pass it on

	def PerformItemSelected(self,item):
		return self.valuesview.UpdateForRegItem(item)

	def OnDestroy(self, msg):
		window.MDIChildWnd.OnDestroy(self, msg)
		if self.images:
			self.images.DeleteImageList()
			self.images = None

class RegistryTreeView(docview.TreeView):
	def OnInitialUpdate(self):
		rc = self._obj_.OnInitialUpdate()
		self.frame = self.GetParent().GetParent()
		self.hierList = hierlist.HierListWithItems( self.GetHLIRoot(), win32ui.IDB_HIERFOLDERS, win32ui.AFX_IDW_PANE_FIRST)
		self.hierList.HierInit(self.frame, self.GetTreeCtrl())
		self.hierList.SetStyle(commctrl.TVS_HASLINES | commctrl.TVS_LINESATROOT | commctrl.TVS_HASBUTTONS)
		self.hierList.PerformItemSelected = self.PerformItemSelected

		self.frame.HookNotify(self.frame.OnItemDoubleClick, commctrl.NM_DBLCLK)
		self.frame.HookNotify(self.OnItemRightClick, commctrl.NM_RCLICK)
#		self.HookMessage(self.OnItemRightClick, win32con.WM_RBUTTONUP)

	def GetHLIRoot(self):
		doc = self.GetDocument()
		regroot = doc.root
		subkey = doc.subkey
		return HLIRegistryKey(regroot, subkey, "Root")

	def OnItemRightClick(self, notify_data, extra):
		# First select the item we right-clicked on.
		pt = self.ScreenToClient(win32api.GetCursorPos())
		flags, hItem = self.HitTest(pt)
		if hItem==0 or commctrl.TVHT_ONITEM & flags==0:
			return None
		self.Select(hItem, commctrl.TVGN_CARET)

		menu = win32ui.CreatePopupMenu()
		menu.AppendMenu(win32con.MF_STRING|win32con.MF_ENABLED,1000, "Add Key")
		menu.AppendMenu(win32con.MF_STRING|win32con.MF_ENABLED,1001, "Add Value")
		menu.AppendMenu(win32con.MF_STRING|win32con.MF_ENABLED,1002, "Delete Key")
		self.HookCommand(self.OnAddKey, 1000)
		self.HookCommand(self.OnAddValue, 1001)
		self.HookCommand(self.OnDeleteKey, 1002)
		menu.TrackPopupMenu(win32api.GetCursorPos()) # track at mouse position.
		return None

	def OnDeleteKey(self,command, code):
		hitem = self.hierList.GetSelectedItem()
		item = self.hierList.ItemFromHandle(hitem)
		msg = "Are you sure you wish to delete the key '%s'?" % (item.keyName,)
		id = win32ui.MessageBox(msg, None, win32con.MB_YESNO)
		if id != win32con.IDYES:
			return
		if SafeApply(win32api.RegDeleteKey, (item.keyRoot, item.keyName), "deleting registry key" ):
			# Get the items parent.
			try:
				hparent = self.GetParentItem(hitem)
			except win32ui.error:
				hparent = None
			self.hierList.Refresh(hparent)

	def OnAddKey(self,command, code):
		from pywin.mfc import dialog
		val = dialog.GetSimpleInput("New key name", '', "Add new key")
		if val is None: return # cancelled.
		hitem = self.hierList.GetSelectedItem()
		item = self.hierList.ItemFromHandle(hitem)
		if SafeApply(win32api.RegCreateKey, (item.keyRoot, item.keyName + "\\" + val)):
			self.hierList.Refresh(hitem)

	def OnAddValue(self,command, code):
		from pywin.mfc import dialog
		val = dialog.GetSimpleInput("New value", "", "Add new value")
		if val is None: return # cancelled.
		hitem = self.hierList.GetSelectedItem()
		item = self.hierList.ItemFromHandle(hitem)
		if SafeApply(win32api.RegSetValue, (item.keyRoot, item.keyName, win32con.REG_SZ, val)):
			# Simply re-select the current item to refresh the right spitter.
			self.PerformItemSelected(item)
#			self.Select(hitem, commctrl.TVGN_CARET)

	def PerformItemSelected(self, item):
		return self.frame.PerformItemSelected(item)

	def SelectedItem(self):
		return self.hierList.ItemFromHandle(self.hierList.GetSelectedItem())

	def SearchSelectedItem(self):
		handle = self.hierList.GetChildItem(0)
		while 1:
#			print "State is", self.hierList.GetItemState(handle, -1)
			if self.hierList.GetItemState(handle, commctrl.TVIS_SELECTED):
#				print "Item is ", self.hierList.ItemFromHandle(handle)
				return self.hierList.ItemFromHandle(handle)
			handle = self.hierList.GetNextSiblingItem(handle)

class RegistryValueView(docview.ListView):
	def OnInitialUpdate(self):
		hwnd = self._obj_.GetSafeHwnd()
		style = win32api.GetWindowLong(hwnd, win32con.GWL_STYLE);
		win32api.SetWindowLong(hwnd, win32con.GWL_STYLE, (style & ~commctrl.LVS_TYPEMASK) | commctrl.LVS_REPORT); 

		itemDetails = (commctrl.LVCFMT_LEFT, 100, "Name", 0)
		self.InsertColumn(0, itemDetails)
		itemDetails = (commctrl.LVCFMT_LEFT, 500, "Data", 0)
		self.InsertColumn(1, itemDetails)

	def UpdateForRegItem(self, item):
		self.DeleteAllItems()
		hkey = win32api.RegOpenKey(item.keyRoot, item.keyName)
		try:
			valNum = 0
			ret = []
			while 1:
				try:
					res = win32api.RegEnumValue(hkey, valNum)
				except win32api.error:
					break
				name = res[0]
				if not name: name = "(Default)"
				self.InsertItem(valNum, name)
				self.SetItemText(valNum, 1, str(res[1]))
				valNum = valNum + 1
		finally:
			win32api.RegCloseKey(hkey)
	def EditValue(self, item):
		# Edit the current value
		class EditDialog(dialog.Dialog):
			def __init__(self, item):
				self.item = item
				dialog.Dialog.__init__(self, win32ui.IDD_LARGE_EDIT)
			def OnInitDialog(self):
				self.SetWindowText("Enter new value")
				self.GetDlgItem(win32con.IDCANCEL).ShowWindow(win32con.SW_SHOW)
				self.edit = self.GetDlgItem(win32ui.IDC_EDIT1)
				# Modify the edit windows style
				style = win32api.GetWindowLong(self.edit.GetSafeHwnd(), win32con.GWL_STYLE)
				style = style & (~win32con.ES_WANTRETURN)
				win32api.SetWindowLong(self.edit.GetSafeHwnd(), win32con.GWL_STYLE, style)
				self.edit.SetWindowText(str(self.item))
				self.edit.SetSel(-1)
				return dialog.Dialog.OnInitDialog(self)
			def OnDestroy(self,msg):
				self.newvalue = self.edit.GetWindowText()
		
		try:
			index = self.GetNextItem(-1, commctrl.LVNI_SELECTED)
		except win32ui.error:
			return # No item selected.

		if index==0:
			keyVal = ""
		else:
			keyVal = self.GetItemText(index,0)
		# Query for a new value.
		try:
			newVal = self.GetItemsCurrentValue(item, keyVal)
		except TypeError, details:
			win32ui.MessageBox(details)
			return
		
		d = EditDialog(newVal)
		if d.DoModal()==win32con.IDOK:
			try:
				self.SetItemsCurrentValue(item, keyVal, d.newvalue)
			except win32api.error, exc:
				win32ui.MessageBox("Error setting value\r\n\n%s" % exc.strerror)
			self.UpdateForRegItem(item)

	def GetItemsCurrentValue(self, item, valueName):
		hkey = win32api.RegOpenKey(item.keyRoot, item.keyName)
		try:
			val, type = win32api.RegQueryValueEx(hkey, valueName)
			if type != win32con.REG_SZ:
				raise TypeError("Only strings can be edited")
			return val
		finally:
			win32api.RegCloseKey(hkey)
	
	def SetItemsCurrentValue(self, item, valueName, value):
		# ** Assumes already checked is a string.
		hkey = win32api.RegOpenKey(item.keyRoot, item.keyName , 0, win32con.KEY_SET_VALUE)
		try:
			win32api.RegSetValueEx(hkey, valueName, 0, win32con.REG_SZ, value)
		finally:
			win32api.RegCloseKey(hkey)


class RegTemplate(docview.DocTemplate):
	def __init__(self):
		docview.DocTemplate.__init__(self, win32ui.IDR_PYTHONTYPE, None, SplitterFrame, None)

#	def InitialUpdateFrame(self, frame, doc, makeVisible=1):
#		self._obj_.InitialUpdateFrame(frame, doc, makeVisible) # call default handler.
#		frame.InitialUpdateFrame(doc, makeVisible)

	def OpenRegistryKey(self, root = None, subkey = None): # Use this instead of OpenDocumentFile.
		# Look for existing open document
		if root is None: root = regutil.GetRootKey()
		if subkey is None: subkey = regutil.BuildDefaultPythonKey()
		for doc in self.GetDocumentList():
			if doc.root==root and doc.subkey==subkey:
				doc.GetFirstView().ActivateFrame()
				return doc
		# not found - new one.
		doc = RegDocument(self, root, subkey)
		frame = self.CreateNewFrame(doc)
		doc.OnNewDocument()
		self.InitialUpdateFrame(frame, doc, 1)
		return doc

class RegDocument (docview.Document):
	def __init__(self, template, root, subkey):
		docview.Document.__init__(self, template)
		self.root = root
		self.subkey = subkey
		self.SetTitle("Registry Editor: " + subkey)

	def OnOpenDocument (self, name):
		raise TypeError("This template can not open files")
		return 0
		

class HLIRegistryKey(hierlist.HierListItem):
	def __init__( self, keyRoot, keyName, userName ):
		self.keyRoot = keyRoot
		self.keyName = keyName
		self.userName = userName
		hierlist.HierListItem.__init__(self)
	def __lt__(self, other):
		return self.name < other.name
	def __eq__(self, other):
		return self.keyRoot==other.keyRoot and \
		       self.keyName == other.keyName and \
		       self.userName == other.userName
	def __repr__(self):
		return "<%s with root=%s, key=%s>" % (self.__class__.__name__, self.keyRoot, self.keyName)
	def GetText(self):
		return self.userName
	def IsExpandable(self):
		# All keys are expandable, even if they currently have zero children.
		return 1
##		hkey = win32api.RegOpenKey(self.keyRoot, self.keyName)
##		try:
##			keys, vals, dt = win32api.RegQueryInfoKey(hkey)
##			return (keys>0)
##		finally:
##			win32api.RegCloseKey(hkey)

	def GetSubList(self):
		hkey = win32api.RegOpenKey(self.keyRoot, self.keyName)
		win32ui.DoWaitCursor(1)
		try:
			keyNum = 0
			ret = []
			while 1:
				try:
					key = win32api.RegEnumKey(hkey, keyNum)
				except win32api.error:
					break
				ret.append(HLIRegistryKey(self.keyRoot, self.keyName + "\\" + key, key))
				keyNum = keyNum + 1
		finally:
			win32api.RegCloseKey(hkey)
			win32ui.DoWaitCursor(0)
		return ret

template = RegTemplate()

def EditRegistry(root = None, key = None):
	doc=template.OpenRegistryKey(root, key)

if __name__=='__main__':
	EditRegistry()
