
# The options of a widget are described by the following attributes
# of the Pack and Widget dialogs:
#
# Dialog.current: {name: value}
# -- changes during Widget's lifetime
#
# Dialog.options: {name: (default, klass)}
# -- depends on widget class only
#
# Dialog.classes: {klass: (v0, v1, v2, ...) | 'boolean' | 'other'}
# -- totally static, though different between PackDialog and WidgetDialog
#    (but even that could be unified)

from Tkinter import *

class Option:

	varclass = StringVar		# May be overridden

	def __init__(self, dialog, option):
		self.dialog = dialog
		self.option = option
		self.master = dialog.top
		self.default, self.klass = dialog.options[option]
		self.var = self.varclass(self.master)
		self.frame = Frame(self.master)
		self.frame.pack(fill=X)
		self.label = Label(self.frame, text=(option + ":"))
		self.label.pack(side=LEFT)
		self.update()
		self.addoption()

	def refresh(self):
		self.dialog.refresh()
		self.update()

	def update(self):
		try:
			self.current = self.dialog.current[self.option]
		except KeyError:
			self.current = self.default
		self.var.set(self.current)

	def set(self, e=None):		# Should be overridden
		pass

class BooleanOption(Option):

	varclass = BooleanVar

	def addoption(self):
		self.button = Checkbutton(self.frame,
					 text='on/off',
					 onvalue=1,
					 offvalue=0,
					 variable=self.var,
					 relief=RAISED,
					 borderwidth=2,
					 command=self.set)
		self.button.pack(side=RIGHT)

class EnumOption(Option):

	def addoption(self):
		self.button = Menubutton(self.frame,
					 textvariable=self.var,
					 relief=RAISED, borderwidth=2)
		self.button.pack(side=RIGHT)
		self.menu = Menu(self.button)
		self.button['menu'] = self.menu
		for v in self.dialog.classes[self.klass]:
			self.menu.add_radiobutton(
			    label=v,
			    variable=self.var,
			    value=v,
			    command=self.set)

class StringOption(Option):

	def addoption(self):
		self.entry = Entry(self.frame,
				   textvariable=self.var,
				   width=10,
				   relief=SUNKEN,
				   borderwidth=2)
		self.entry.pack(side=RIGHT, fill=X, expand=1)
		self.entry.bind('<Return>', self.set)

class ReadonlyOption(Option):

	def addoption(self):
		self.label = Label(self.frame, textvariable=self.var,
				   anchor=E)
		self.label.pack(side=RIGHT)

class Dialog:

	def __init__(self, master):
		self.master = master
		self.fixclasses()
		self.refresh()
		self.top = Toplevel(self.master)
		self.top.title(self.__class__.__name__)
		self.top.minsize(1, 1)
		self.addchoices()

	def refresh(self): pass		# Must override

	def fixclasses(self): pass	# May override

	def addchoices(self):
		self.choices = {}
		list = []
		for k, dc in self.options.items():
			list.append(k, dc)
		list.sort()
		for k, (d, c) in list:
			try:
				cl = self.classes[c]
			except KeyError:
				cl = 'unknown'
			if type(cl) == TupleType:
				cl = self.enumoption
			elif cl == 'boolean':
				cl = self.booleanoption
			elif cl == 'readonly':
				cl = self.readonlyoption
			else:
				cl = self.stringoption
			self.choices[k] = cl(self, k)

	# Must override:
	options = {}
	classes = {}

	# May override:
	booleanoption = BooleanOption
	stringoption = StringOption
	enumoption = EnumOption
	readonlyoption = ReadonlyOption

class PackDialog(Dialog):

	def __init__(self, widget):
		self.widget = widget
		Dialog.__init__(self, widget)

	def refresh(self):
		self.current = self.widget.info()
		self.current['.class'] = self.widget.winfo_class()
		self.current['.name'] = self.widget._w

	class packoption: # Mix-in class
		def set(self, e=None):
			self.current = self.var.get()
			try:
				apply(self.dialog.widget.pack, (),
				      {self.option: self.current})
			except TclError, msg:
				print msg
				self.refresh()

	class booleanoption(packoption, BooleanOption): pass
	class enumoption(packoption, EnumOption): pass
	class stringoption(packoption, StringOption): pass
	class readonlyoption(packoption, ReadonlyOption): pass

	options = {
		'.class': (None, 'Class'),
		'.name': (None, 'Name'),
		'after': (None, 'Widget'),
		'anchor': ('center', 'Anchor'),
		'before': (None, 'Widget'),
		'expand': ('no', 'Boolean'),
		'fill': ('none', 'Fill'),
		'in': (None, 'Widget'),
		'ipadx': (0, 'Pad'),
		'ipady': (0, 'Pad'),
		'padx': (0, 'Pad'),
		'pady': (0, 'Pad'),
		'side': ('top', 'Side'),
		}

	classes = {
		'Anchor': (N, NE, E, SE, S, SW, W, NW, CENTER),
		'Boolean': 'boolean',
		'Class': 'readonly',
		'Expand': 'boolean',
		'Fill': (NONE, X, Y, BOTH),
		'Name': 'readonly',
		'Pad': 'pixel',
		'Side': (TOP, RIGHT, BOTTOM, LEFT),
		'Widget': 'readonly',
		}

class RemotePackDialog(PackDialog):

	def __init__(self, master, app, widget):
		self.master = master
		self.app = app
		self.widget = widget
		self.refresh()
		self.top = Toplevel(self.master)
		self.top.title(self.app + ' PackDialog')
		self.top.minsize(1, 1)
		self.addchoices()

	def refresh(self):
		try:
			words = self.master.tk.splitlist(
				self.master.send(self.app,
						 'pack',
						 'info',
						 self.widget))
		except TclError, msg:
			print msg
			return
		dict = {}
		for i in range(0, len(words), 2):
			key = words[i][1:]
			value = words[i+1]
			dict[key] = value
		dict['.class'] = self.master.send(self.app,
						  'winfo',
						  'class',
						  self.widget)
		dict['.name'] = self.widget
		self.current = dict

	class remotepackoption: # Mix-in class
		def set(self, e=None):
			self.current = self.var.get()
			try:
				self.dialog.master.send(
					self.dialog.app,
					'pack',
					'config',
					self.dialog.widget,
					'-'+self.option,
					self.dialog.master.tk.merge(
						self.current))
			except TclError, msg:
				print msg
				self.refresh()

	class booleanoption(remotepackoption, BooleanOption): pass
	class enumoption(remotepackoption, EnumOption): pass
	class stringoption(remotepackoption, StringOption): pass
	class readonlyoption(remotepackoption, ReadonlyOption): pass

class WidgetDialog(Dialog):

	def __init__(self, widget):
		self.widget = widget
		self.klass = widget.winfo_class()
		Dialog.__init__(self, widget)

	def fixclasses(self):
		if self.addclasses.has_key(self.klass):
			classes = {}
			for c in (self.classes,
				  self.addclasses[self.klass]):
				for k in c.keys():
					classes[k] = c[k]
			self.classes = classes

	def refresh(self):
		self.configuration = self.widget.config()
		self.update()
		self.current['.class'] = self.widget.winfo_class()
		self.current['.name'] = self.widget._w

	def update(self):
		self.current = {}
		self.options = {}
		for k, v in self.configuration.items():
			if len(v) > 4:
				self.current[k] = v[4]
				self.options[k] = v[3], v[2] # default, klass
		self.options['.class'] = (None, 'Class')
		self.options['.name'] = (None, 'Name')

	class widgetoption: # Mix-in class
		def set(self, e=None):
			self.current = self.var.get()
			try:
				self.dialog.widget[self.option] = self.current
			except TclError, msg:
				print msg
				self.refresh()

	class booleanoption(widgetoption, BooleanOption): pass
	class enumoption(widgetoption, EnumOption): pass
	class stringoption(widgetoption, StringOption): pass
	class readonlyoption(widgetoption, ReadonlyOption): pass

	# Universal classes
	classes = {
		'Anchor': (N, NE, E, SE, S, SW, W, NW, CENTER),
		'Aspect': 'integer',
		'Background': 'color',
		'Bitmap': 'bitmap',
		'BorderWidth': 'pixel',
		'Class': 'readonly',
		'CloseEnough': 'double',
		'Command': 'command',
		'Confine': 'boolean',
		'Cursor': 'cursor',
		'CursorWidth': 'pixel',
		'DisabledForeground': 'color',
		'ExportSelection': 'boolean',
		'Font': 'font',
		'Foreground': 'color',
		'From': 'integer',
		'Geometry': 'geometry',
		'Height': 'pixel',
		'InsertWidth': 'time',
		'Justify': (LEFT, CENTER, RIGHT),
		'Label': 'string',
		'Length': 'pixel',
		'MenuName': 'widget',
		'Name': 'readonly',
		'OffTime': 'time',
		'OnTime': 'time',
		'Orient': (HORIZONTAL, VERTICAL),
		'Pad': 'pixel',
		'Relief': (RAISED, SUNKEN, FLAT, RIDGE, GROOVE),
		'RepeatDelay': 'time',
		'RepeatInterval': 'time',
		'ScrollCommand': 'command',
		'ScrollIncrement': 'pixel',
		'ScrollRegion': 'rectangle',
		'ShowValue': 'boolean',
		'SetGrid': 'boolean',
		'Sliderforeground': 'color',
		'SliderLength': 'pixel',
		'Text': 'string',
		'TickInterval': 'integer',
		'To': 'integer',
		'Underline': 'index',
		'Variable': 'variable',
		'Value': 'string',
		'Width': 'pixel',
		'Wrap': (NONE, CHAR, WORD),
		}

	# Classes that (may) differ per widget type
	_tristate = {'State': (NORMAL, ACTIVE, DISABLED)}
	_bistate = {'State': (NORMAL, DISABLED)}
	addclasses = {
		'Button': _tristate,
		'Radiobutton': _tristate,
		'Checkbutton': _tristate,
		'Entry': _bistate,
		'Text': _bistate,
		'Menubutton': _tristate,
		'Slider': _bistate,
		}

class RemoteWidgetDialog(WidgetDialog):

	def __init__(self, master, app, widget):
		self.app = app
		self.widget = widget
		self.klass = master.send(self.app,
					 'winfo',
					 'class',
					 self.widget)
		Dialog.__init__(self, master)

	def refresh(self):
		try:
			items = self.master.tk.splitlist(
				self.master.send(self.app,
						 self.widget,
						 'config'))
		except TclError, msg:
			print msg
			return
		dict = {}
		for item in items:
			words = self.master.tk.splitlist(item)
			key = words[0][1:]
			value = (key,) + words[1:]
			dict[key] = value
		self.configuration = dict
		self.update()
		self.current['.class'] = self.klass
		self.current['.name'] = self.widget

	class remotewidgetoption: # Mix-in class
		def set(self, e=None):
			self.current = self.var.get()
			try:
				self.dialog.master.send(
					self.dialog.app,
					self.dialog.widget,
					'config',
					'-'+self.option,
					self.current)
			except TclError, msg:
				print msg
				self.refresh()

	class booleanoption(remotewidgetoption, BooleanOption): pass
	class enumoption(remotewidgetoption, EnumOption): pass
	class stringoption(remotewidgetoption, StringOption): pass
	class readonlyoption(remotewidgetoption, ReadonlyOption): pass

def test():
	import sys
	root = Tk()
	root.minsize(1, 1)
	if sys.argv[1:]:
		remotetest(root, sys.argv[1])
	else:
		frame = Frame(root, name='frame')
		frame.pack(expand=1, fill=BOTH)
		button = Button(frame, name='button', text='button')
		button.pack(expand=1)
		canvas = Canvas(frame, name='canvas')
		canvas.pack()
		fpd = PackDialog(frame)
		fwd = WidgetDialog(frame)
		bpd = PackDialog(button)
		bwd = WidgetDialog(button)
		cpd = PackDialog(canvas)
		cwd = WidgetDialog(canvas)
	root.mainloop()

def remotetest(root, app):
	from listtree import listtree
	list = listtree(root, app)
	list.bind('<Any-Double-1>', opendialogs)
	list.app = app			# Pass it on to handler

def opendialogs(e):
	import string
	list = e.widget
	sel = list.curselection()
	for i in sel:
		item = list.get(i)
		widget = string.split(item)[0]
		RemoteWidgetDialog(list, list.app, widget)
		if widget == '.': continue
		try:
			RemotePackDialog(list, list.app, widget)
		except TclError, msg:
			print msg

test()
