#!/usr/bin/env python

# --- UCSF Chimera Copyright ---
# Copyright (c) 2000 Regents of the University of California.
# All rights reserved.  This software provided pursuant to a
# license agreement containing restrictions on its disclosure,
# duplication and use.  This notice must be embedded in or
# attached to all copies, including partial copies, of the
# software or any revisions or derivations thereof.
# --- UCSF Chimera Copyright ---
#
# $Id: Sash.py 34686 2011-10-14 22:13:21Z conrad $


import Tkinter
Tk = Tkinter
from Tkinter import _cnfmerge
from SimpleDialog import SimpleDialog
import sys

IPS = 4		# Inter-pane spacing.  Must be a multiple
		# of 2 because when we add panes, we will
		# pad them with IPS/2 pixels to make sure
		# that we get exactly the right initial
		# window size

class CollapsiblePane(Tk.Frame):
	"""
	CollapsiblePane is a frame that has a Checkbutton at the
	top which controls whether the rest of the frame is displayed.
	CollapsiblePane instances should be children of Sash (below).
	"""
	def __init__(self, master=None, title='Untitled',
			titleFont=None, collapsed=0, **kw):
		Tk.Frame.__init__(self, master, kw)
		self.controlFrame = Tk.Frame(self)
		self.controlFrame.pack(side=Tk.TOP, fill=Tk.X)
		self.button = Tk.Label(self.controlFrame)
		from chimera import chimage
		self.rArrow = chimage.get("rightarrow.png", self.button)
		self.dArrow = chimage.get("downarrow.png", self.button)
		self.button.pack(side=Tk.LEFT, ipadx=2, ipady=0)
		self.button.bind("<Button-1>", self._buttonClick)
		self.label = Tk.Label(self.controlFrame, text=title)
		if titleFont:
			label.configure(font=titleFont)
		self.label.pack(side=Tk.LEFT)
		self.label.bind("<Button-1>", self._buttonClick)
		self.frame = Tk.Frame(self, bd=2, relief=Tk.SUNKEN)
		if collapsed:
			self.hide()
		else:
			self.show()
		self.paneMinHeight = self.controlFrame.winfo_reqheight() + \
					int(str(self.controlFrame['bd'])) * 2
		self.paneMinWidth = self.controlFrame.winfo_reqwidth()

	def _buttonClick(self, event=None):
		self.buttonCommand()

	def hide(self):
		self.paneCollapsed = Tk.TRUE
		self.button.configure(image=self.rArrow)
		self.buttonCommand = self.show
#					command=self.show)
		if not hasattr(self, 'paneHeight'):
			# Must be during setup (before being shown on screen)
			if self.paneCollapsed:
				self.frame.forget()
			return
		self.savePaneHeight = self.paneHeight
		self.paneHeight = self.winfo_height() - \
					self.frame.winfo_height()
		self.frame.forget()
		top = self.winfo_toplevel()
		ht = top.winfo_height() - self.savePaneHeight + \
			self.paneHeight
		wd = top.winfo_width()
		top.geometry('%dx%d' % (wd, ht))

	def show(self):
		self.paneCollapsed = Tk.FALSE
		self.button.configure(image=self.dArrow)
		self.buttonCommand = self.hide
#					command=self.hide)
		if not hasattr(self, 'paneHeight'):
			# Must be during setup (before being shown on screen)
			if not self.paneCollapsed:
				self.frame.pack(side=Tk.TOP, fill=Tk.BOTH,
						expand=Tk.TRUE)
			return
		if not hasattr(self, 'savePaneHeight'):
			# Must be because we started hidden
			self.savePaneHeight = self.paneHeight + \
						self.frame.winfo_reqheight()
		self.frame.pack(side=Tk.TOP, fill=Tk.BOTH, expand=Tk.TRUE,
				after=self.controlFrame)
		top = self.winfo_toplevel()
		ht = top.winfo_height() - self.paneHeight + \
			self.savePaneHeight
		self.paneHeight = self.savePaneHeight
		wd = top.winfo_width()
		top.geometry('%dx%d' % (wd, ht))

def _isCollapsed(pane):
	if not _isVisible(pane):
		return True
	if not hasattr(pane, 'paneCollapsed'):
		return False
	return pane.paneCollapsed

def _isVisible(pane):
	if not hasattr(pane, 'paneVisible'):
		return True
	return pane.paneVisible

class Sash(Tk.Frame):
	"""
	Sash is a Frame that contains several horizontally spanning Pane's
	(see above).  When any Pane changes in size, the Sash will handle
	the reconfiguration of the entire set of Panes.
	"""
	def __init__(self, master=None, orient=Tk.VERTICAL, cnf={}, **kw):
		if orient == Tk.VERTICAL:
			self.orientation = Tk.VERTICAL
		elif orient == Tk.HORIZONTAL:
			self.orientation = Tk.HORIZONTAL
		else:
			raise ValueError, 'unknown orientation'
		if self.orientation is Tk.VERTICAL:
			self._buttonDrag = self._buttonDragVertical
			cursor = 'sb_v_double_arrow'
		else:
			self._buttonDrag = self._buttonDragHorizontal
			cursor = 'sb_h_double_arrow'
		cnf = _cnfmerge((cnf, kw))
		Tk.Frame.__init__(self, master, cnf, cursor=cursor)
		self.needResize = self.master is self.winfo_toplevel()
		self.paneList = []
		self.bind('<Configure>', self._computeSize)
		self.bind('<ButtonPress-1>', self._buttonPress)
		self.bind('<ButtonRelease-1>', self._buttonRelease)

	def destroy(self):
		del self._buttonDrag
		del self.paneList
		self.reconfigure = None
		Tk.Frame.destroy(self)

	def addPane(self, pane, where=-1):
		pane.paneHeight = pane.winfo_reqheight()
		pane.paneWidth = pane.winfo_reqwidth()
		pane['cursor'] = 'arrow'
		if self.orientation is Tk.VERTICAL:
			side = Tk.TOP
			px = 0
			py = self.paneList and IPS / 2 or 0
		else:
			side = Tk.LEFT
			px = self.paneList and IPS / 2 or 0
			py = 0
		if where < 0:
			pane.pack(side=side, expand=Tk.TRUE, fill=Tk.BOTH,
					padx=px, pady=py)
			self.paneList.append(pane)
		else:
			pane.pack(side=side, expand=Tk.TRUE, fill=Tk.BOTH,
					padx=px, pady=py,
					after=self.paneList[where])
			self.paneList.insert(where, pane)

	def setVisible(self, pane, flag):
		if pane not in self.paneList:
			return
		pane.paneVisible = flag
		if pane.winfo_ismapped():
			self.reconfigure()

	def _computeSize(self, *args):
		if self.orientation is Tk.VERTICAL:
			height = IPS * len(self.paneList) - IPS
			width = 0
			self.reconfigure = self._reconfigureVertical
		else:
			height = 0
			width = IPS * len(self.paneList) - IPS
			self.reconfigure = self._reconfigureHorizontal
		for p in self.paneList:
			ht = p.winfo_reqheight()
			wd = p.winfo_reqwidth()
			if self.orientation is Tk.VERTICAL:
				height = height + ht
				if wd > width:
					width = wd
			else:
				if ht > height:
					height = ht
				width = width + wd
			p.paneHeight = ht
			p.paneWidth = wd
			if not hasattr(p, 'paneMinHeight'):
				p.paneMinHeight = 1
			if not hasattr(p, 'paneMinWidth'):
				p.paneMinWidth = 1
		for p in self.paneList:
			p.pack_forget()
		self.bind('<Configure>', self.reconfigure)
		self.reconfigure()

	def _reconfigureVertical(self, *args):
		totalHeight = 0.0
		numPanes = 0
		for p in self.paneList:
			if _isVisible(p):
				totalHeight = totalHeight + p.paneHeight
				numPanes = numPanes + 1
		if numPanes == 0:
			return
		myHeight = float(self.winfo_height())
		availableHeight = myHeight - IPS * numPanes + IPS
		ratio = availableHeight / totalHeight
		self.gapList = []
		y = 0.0
		for p in self.paneList:
			if not _isVisible(p):
				ht = 0
				p.paneHeight = p.paneHeight * ratio
			else:
				ht = p.paneHeight * ratio
				p.paneHeight = ht
			p.place(relx=0, relwidth=1, y=y, height=ht)
			y = y + ht + IPS
			self.gapList.append(y)

	def _reconfigureHorizontal(self, *args):
		totalWidth = 0.0
		numPanes = 0
		for p in self.paneList:
			if _isVisible(p):
				totalWidth = totalWidth + p.paneWidth
				numPanes = numPanes + 1
		if numPanes == 0:
			return
		myWidth = float(self.winfo_width())
		availableWidth = myWidth - IPS * numPanes + IPS
		ratio = availableWidth / totalWidth
		self.gapList = []
		x = 0.0
		for p in self.paneList:
			if not _isVisible(p):
				wd = 0
				p.paneWidth = p.paneWidth * ratio
			else:
				wd = p.paneWidth * ratio
				p.paneWidth = wd
			p.place(rely=0, relheight=1, x=x, width=wd)
			x = x + wd + IPS
			self.gapList.append(x)

	def _buttonPress(self, event):
		if self.orientation is Tk.VERTICAL:
			crd = event.y
		else:
			crd = event.x
		gapIndex = -1
		for i in range(len(self.gapList)):
			if crd < self.gapList[i]:
				gapIndex = i
				break
		if gapIndex == -1:
			raise SystemError, 'cannot identify sash gap'
		paneAbove = None
		for above in range(gapIndex, -1, -1):
			pane = self.paneList[above]
			if not _isCollapsed(pane):
				paneAbove = pane
				break
		if paneAbove is None:
			d = SimpleDialog(self.winfo_toplevel(),
				text='There is no adjustable pane above',
				buttons=['Okay'],
				title='User Error')
			d.go()
			return
		paneBelow = None
		for below in range(gapIndex + 1, len(self.paneList)):
			pane = self.paneList[below]
			if not _isCollapsed(pane):
				paneBelow = pane
				break
		if paneBelow is None:
			d = SimpleDialog(self.winfo_toplevel(),
				text='There is no adjustable pane below',
				buttons=['Okay'],
				title='User Error')
			d.go()
			return
		self.adjustAbove = paneAbove
		self.adjustBelow = paneBelow
		self.drag = crd
		self.bind('<Motion>', self._buttonDrag)

	def _buttonRelease(self, event):
		self.gapIndex = -1
		self.unbind('<Motion>')

	def _buttonDragVertical(self, event):
		delta = event.y - self.drag
		if delta < 2 and delta > -2:
			return
		aboveHt = self.adjustAbove.paneHeight + delta
		belowHt = self.adjustBelow.paneHeight - delta
		if aboveHt < self.adjustAbove.paneMinHeight \
		or belowHt < self.adjustBelow.paneMinHeight:
			return
		self.adjustAbove.paneHeight = aboveHt
		self.adjustBelow.paneHeight = belowHt
		self.reconfigure()
		self.drag = event.y

	def _buttonDragHorizontal(self, event):
		delta = event.x - self.drag
		if delta < 2 and delta > -2:
			return
		aboveWd = self.adjustAbove.paneWidth + delta
		belowWd = self.adjustBelow.paneWidth - delta
		if aboveWd < self.adjustAbove.paneMinWidth \
		or belowWd < self.adjustBelow.paneMinWidth:
			return
		self.adjustAbove.paneWidth = aboveWd
		self.adjustBelow.paneWidth = belowWd
		self.reconfigure()
		self.drag = event.x

if __name__ == '__main__':
	f = Sash()
	f.pack(expand=Tk.TRUE, fill=Tk.BOTH)

	p = CollapsiblePane(f, collapsed=1)
	f.addPane(p)
	e = Tk.Entry(p.frame, width=20)
	e.pack(side=Tk.TOP, expand=Tk.TRUE, fill=Tk.BOTH)

	b = Tk.Button(f, text='Quit', command=f.quit)
	f.addPane(b)

	e = Tk.Text(f, width=20, height=5)
	f.addPane(e, where=0)

	f.mainloop()