#!/usr/bin/env python

# Having disabled TAB_TRAVERSAL everywhere and used style=WANTS_CHARS
# for all widgets (and TE_PROCESS_TAB as well for TextCtrl and ComboBox
# widgets), here are the differences I see between different platforms
# with regards to tabbing and shift-tabbing between widgets.
#
# On MacOSX, ComboBox doesn't receive EVT_CHAR events for tab characters.
# However, tabbing does work through ComboBox widgets (somehow).
# However, shift-tabbing when a ComboBox has focus is
# the same as tab i.e. Focus moves to the next widget,
# not the previous one. So, tabbing forward cycles fine,
# but shift-tabbing backwards gets stuck on the first
# ComboBox encountered. The other widgets (TextCtrl,
# CheckBox and Button) were fine with shift-tabbing.
# ComboBox does receive EVT_KEY_DOWN events for tab characters
# and by handling tab and shift-tab here on MacOSX,
# shift-tabbing backwards across ComboBox widgets works.
#
# On Linux, using only EVT_CHAR events, tabbing forwards and
# shift-tabbing backwards works fine with all widgets that
# I used (TextCtrl, ComboBox, CheckBox and Button).
#
# On WinNT, ComboBox receives two EVT_SET_FOCUS events and doesn't
# receive EVT_CHAR or EVT_KEY_DOWN events for tab characters.
# So, tabbing forwards stops at the first ComboBox.
# Shift-tabbing in a ComboBox sends focus to the
# only Button widget (last in tab order but that might not be relevant).
# Tabbing from the button makes focus disappear but shift-tabbing
# makes it return to the Button. Shift-tabbing when the button has
# focus does nothing.
#
# MAC: tab combo: EES.keydown(handletab), TE.childfocus
# MAC: tab other: EES.keydown(handletab), TE.childfocus
# LIN: tab combo: EES.keydown, CE.keydown, EES.char(handletab), TE.setfocus
# LIN: tab other: EES.keydown, EES.char(handletab), TE.setfocus
# WIN: tab combo: nothing at all...
# WIN: tab other: EES.keydown, EES.char(handletab), TE.setfocus, TE.setfocus
#
# Where:
# EES = class EditEmployeeScreen
# TE = class TextEntry(TextCtrl)
# CE = class CodeEntry(ComboBox)
# --------------------------------------------------------------------
# Other platform inconsistencies:
#
# On MacOSX, ComboBox doesn't receive EVT_SET_FOCUS but it
# does receive EVT_CHILD_FOCUS. This isn't related to
# tabbing but rather to automatically selecting all text in a
# widget when it receives focus (as the following code does).
#
# On Linux/GTK, in a ComboBox, when some text is selected and
# the user types a character, there is one EVT_TEXT event where
# the selected text is deleted, then there is a second EVT_TEXT
# event with the new character. This is playing havoc with my
# auto-completing ComboBox. More on that later.
#

from wx import *
from wx.grid import *
from wx.aui import *
from wx.lib.mixins.listctrl import *
from wx.lib.scrolledpanel import *

def app():
	return _app

class TestApp(App):
	def GetAppName(self):
		return 'Payroll'
	def GetAppVersion(self):
		return '0.1'
	def GetUser(self):
		try:
			return self.GetUserPosix()
		except:
			try: return self.GetUserMSWin()
			except: return ''
	def GetUserPosix(self):
		import pwd
		return pwd.getpwuid(os.getuid())[0]
	def GetUserMSWin(self):
		import win32api, win32net, win32netcon
		dc = win32net.NetServerEnum(None, 100, win32netcon.SV_TYPE_DOMAIN_CTRL)
		name = r'\\' + dc[0][0]['name'] if dc[0] and type(dc[0][0]) == type({}) else None
		user = win32api.GetUserName()
		return str(win32net.NetUserGetInfo(name, user, 1)['name'])
	def warn(self, msg=''):
		self.mainframe.SetStatusText(msg)
	def debug(self, msg):
		'''Emit a debug message to standard output if in debug mode.'''
		if self.debug_mode:
			print msg
	def OnInit(self):
		global _app
		_app = self
		self.debug_mode = True
		self.debug("TestApp.OnInit()")
		self.mainframe = MainFrame(self)
		self.SetTopWindow(self.mainframe)
		return True

def debug_event(label, event):
	if isinstance(event, KeyEvent):
		app().debug('%s(%s key=%d%s)' % (label, event.GetEventObject().GetName(), event.GetKeyCode(), ((' [' if event.HasModifiers() else '') + ('s' if event.ShiftDown() else '') + ('c' if event.ControlDown() else '') + ('C' if event.CmdDown() else '') + ('a' if event.AltDown() else '') + ('m' if event.MetaDown() else '') + (']' if event.HasModifiers() else ''))))
	else:
		app().debug('%s(%s)' % (label, event.GetEventObject().GetName()))

class TextEntry(TextCtrl):
	def __init__(self, parent, id=-1, value='', pos=DefaultPosition, size=DefaultSize, style=WANTS_CHARS|TE_PROCESS_TAB, validator=DefaultValidator, name=TextCtrlNameStr, width=15):
		TextCtrl.__init__(self, parent, id, value, pos, size, style, validator, name)
		SetTextWidth(self, width)
		# Note: On MacOSX, ComboBox doesn't receive EVT_SET_FOCUS
		# but it does receive EVT_CHILD_FOCUS (wxpython-2.8.8.1).
		if Platform == '__WXMAC__':
			self.Bind(EVT_CHILD_FOCUS, self.OnChildFocus)
		else:
			self.Bind(EVT_SET_FOCUS, self.OnSetFocus)
	def OnChildFocus(self, event):
		debug_event('TextEntry.OnChildFocus', event); print
		txt = event.GetWindow()
		if isinstance(txt, TextCtrl):
			self.OnSetFocus()
		event.Skip()
	def OnSetFocus(self, event=None):
		'''When receiving focus, select all of the text.'''
		if event: debug_event('TextEntry.OnSetFocus', event); print
		CallAfter(self.SetSelection, 0, len(self.GetValue()))
		if event: event.Skip()
	def SetValue(self, val):
		if val is None: val = ''
		TextCtrl.SetValue(self, val)

class PhoneEntry(TextEntry): # XXX
	def __init__(self, parent, id=-1, value='', pos=DefaultPosition, size=DefaultSize, style=WANTS_CHARS, validator=DefaultValidator, name=TextCtrlNameStr, width=15):
		TextEntry.__init__(self, parent, id, value, pos, size, style, validator, name, width)

class IntEntry(TextEntry): # XXX
	def __init__(self, parent, id=-1, value='', pos=DefaultPosition, size=DefaultSize, style=WANTS_CHARS, validator=DefaultValidator, name=TextCtrlNameStr, width=15):
		TextEntry.__init__(self, parent, id, value, pos, size, style, validator, name, width)

class DateEntry(TextEntry): # XXX
	def __init__(self, parent, id=-1, value='', pos=DefaultPosition, size=DefaultSize, style=WANTS_CHARS, validator=DefaultValidator, name=TextCtrlNameStr, width=10):
		TextEntry.__init__(self, parent, id, value, pos, size, style, validator, name, width)

class FlagEntry(CheckBox): # XXX
	def __init__(self, parent, id=-1, label=EmptyString, pos=DefaultPosition, size=DefaultSize, style=WANTS_CHARS, validator=DefaultValidator, name=CheckBoxNameStr):
		CheckBox.__init__(self, parent, id, label, pos, size, style, validator, name)

class Table:
	@classmethod
	def load(cls, data):
		cls._loaded = [cls(r) for r in data]
		cls._by_name = dict([(r.name, r) for r in cls._loaded])
		cls._by_code = dict([(r.code, r) for r in cls._loaded])
		cls._by_id = dict([(r.id, r) for r in cls._loaded])
	@classmethod
	def get(cls, key):
		if hasattr(key, 'upper') and cls._by_code.has_key(key.upper()):
			return cls._by_code[key.upper()]
		if cls._by_name.has_key(key):
			return cls._by_name[key]
		if cls._by_id.has_key(key):
			return cls._by_id[key]
		return None
	@classmethod
	def get_all(cls):
		return cls._loaded

class T_title(Table):
	def __init__(self, r): self.id, self.code, self.name = r
class T_gender(Table):
	def __init__(self, r): self.id, self.code, self.name = r
class T_marital_status(Table):
	def __init__(self, r): self.id, self.code, self.name = r
class T_state(Table):
	def __init__(self, r): self.id, self.code, self.name = r
class T_country(Table):
	def __init__(self, r): self.id, self.code, self.name = r
class T_language(Table):
	def __init__(self, r): self.id, self.code, self.name = r
class T_period(Table):
	def __init__(self, r): self.id, self.code, self.name = r
class T_leaving_reason(Table):
	def __init__(self, r): self.id, self.code, self.name = r

T_title.load([(1, 'M', 'Mr'), (2, 'S', 'MS'), (3, 'I', 'Miss'), (4, 'R', 'Mrs')])
T_gender.load([(1, 'M', 'Male'), (2, 'F', 'Female'), (3, 'O', 'Other')])
T_marital_status.load([(1, 'M', 'Married'), (2, 'S', 'Single'), (3, 'O', 'Other')])
T_state.load([(1, 'NSW', 'NSW'), (2, 'VIC', 'VIC'), (3, 'TAS', 'TAS')])
T_country.load([(1, 'AU', 'Australia'), (2, 'NZ', 'New Zealand')])
T_language.load([(1, 'EN', 'English'), (2, 'ZH', 'Mandarin')])
T_period.load([(1, 'D', 'Days'), (2, 'W', 'Weeks'), (3, 'M', 'Months')])
T_leaving_reason.load([(1, 'R', 'Resigned'), (2, 'T', 'Retired'), (3, 'D', 'Dismissed')])

class CodeEntry(ComboBox):
	def __init__(self, parent, tableclass, id=-1, value='', pos=DefaultPosition, size=DefaultSize, choices=[], style=CB_DROPDOWN|WANTS_CHARS|TE_PROCESS_TAB, validator=DefaultValidator, name=TextCtrlNameStr, width=5):
		if choices == []:
			self.tableclass = tableclass
			#self.tableclass.load()
			self.items = self.tableclass.get_all()
			choices = [i.name for i in self.items]
			self.choices = choices
		ComboBox.__init__(self, parent, id, value, pos, size, choices, style, validator, name)
		SetTextWidth(self, width + 3) # The popup button takes up some room
		self.lastkey = None
		self.Bind(EVT_KEY_DOWN, self.OnKeyDown)
		self.Bind(EVT_CHAR, self.OnChar)
		self.Bind(EVT_TEXT, self.OnText)
		# Note: On MacOSX, ComboBox doesn't receive EVT_SET_FOCUS
		# but it does receive EVT_CHILD_FOCUS (wxpython-2.8.8.1).
		if Platform == '__WXMAC__':
			self.Bind(EVT_CHILD_FOCUS, self.OnChildFocus)
		else:
			self.Bind(EVT_SET_FOCUS, self.OnSetFocus)
	def SetID(self, id):
		'''Set the text to be the name corresponding to the given id.'''
		if id is None: self.SetValue(''); return
		item = self.tableclass.get(id)
		if item: self.SetValue(item.name)
	def GetID(self):
		'''Return id corresponding to the current selection.'''
		txt = self.GetValue().lower()
		if len(txt) == 0: return None
		for item in self.items:
			if txt == item.name.lower(): return item.id
		raise Exception('No such id: ' + self.GetValue())
	def _complete(self):
		if self.lastkey == WXK_BACK: return
		txt = self.GetValue().lower()
		l = len(txt)
		if l == 0:
			return
		for item in self.items:
			if l == len(item.name) and txt == item.name[0:l].lower():
				return
			if l < len(item.name) and txt == item.name[0:l].lower():
				self.SetValue(item.name)
				CallAfter(self.SetMark, l, len(item.name))
				return
		for item in self.items:
			if l <= len(item.code) and txt == item.code[0:l].lower():
				self.SetValue(item.name)
				CallAfter(self.SetMark, l, len(item.name))
				return
	def _nav(self, init, dir):
		txt = self.GetValue()
		if len(txt) == 0:
			self.SetValue(self.items[init].name)
		nitems = len(self.items)
		for i in range(nitems):
			if txt == self.items[i].name:
				self.SetValue(self.items[dir(i)].name)
				break
	def OnChildFocus(self, event):
		debug_event('CodeEntry.OnChildFocus', event); print
		txt = event.GetWindow()
		if isinstance(txt, TextCtrl):
			self.OnSetFocus()
		event.Skip()
	def OnSetFocus(self, event=None):
		'''When receiving focus, select all of the text.'''
		if event: debug_event('CodeEntry.OnSetFocus', event); print
		CallAfter(self.SetMark, 0, len(self.GetValue()))
		if event: event.Skip()
	def OnKeyDown(self, event):
		debug_event('CodeEntry.OnKeyDown', event)
		event.Skip()
	def OnChar(self, event):
		debug_event('CodeEntry.OnChar', event)
		key = event.GetKeyCode()
		self.lastkey = key
		if key == WXK_UP:
			self._nav(-1, lambda i: i - 1)
		elif key == WXK_DOWN:
			self._nav(0, lambda i: i + 1 if i < len(self.items) - 1 else 0)
		else:
			event.Skip()
	def OnText(self, event):
		app().debug('CodeEntry.OnText(%s)' % self.GetValue())
		self._complete()
		event.Skip()

def handle_tab(event, widgets):
	if event.GetKeyCode() != WXK_TAB:
		return False
	shift = event.ShiftDown()
	src = event.GetEventObject()
	nwidgets = len(widgets)
	for i in range(nwidgets):
		if src == widgets[i]:
			app().debug('handle_tab')
			widgets[i - 1 if shift else (i + 1) % nwidgets].SetFocus()
			return True
	return False

def SetTextWidth(textctrl, length):
	'''Set a TextCtrl's width in terms of the number of characters rather than pixels.'''
	size = textctrl.GetSize()
	size.width = (length + 1) * textctrl.GetCharWidth()
	textctrl.SetInitialSize(size)

class AutoWidthListCtrl(ListCtrl, ListCtrlAutoWidthMixin):
	'''Required by ListCtrlAutoWidthMixin. Seems to do nothing on Mac.'''
	def __init__(self, parent, id=-1, pos=DefaultPosition, size=DefaultSize, style=0):
		ListCtrl.__init__(self, parent, id, pos, size, style)
		ListCtrlAutoWidthMixin.__init__(self)

class EditEmployeeScreen(ScrolledPanel):
	def get_caption(self): return 'EditEmployee'
	def __init__(self, parent, id=-1, pos=DefaultPosition, size=DefaultSize, style=0, name='scrolledpanel'):
		ScrolledPanel.__init__(self, parent, id, pos, size, style, name)
		self.employee = None

		# Create widgets
		self.employee_search_label = StaticText(self, label='Employee:')
		self.employee_search_ctrl = TextEntry(self, width=10, name='employee_search')
		self.employee_search_result_label = StaticText(self, label='')
		self.name_label = StaticText(self, label='Name:')
		self.title_ctrl = CodeEntry(self, T_title, width=4, name='title')
		self.first_names_ctrl = TextEntry(self, name='first_names')
		self.family_name_ctrl = TextEntry(self, name='family_name')
		self.birth_date_label = StaticText(self, label='Birth:')
		self.birth_date_ctrl = DateEntry(self, width=10, name='birth_date')
		self.gender_label = StaticText(self, label='Gender:')
		self.gender_ctrl = CodeEntry(self, T_gender, width=6, name='gender')
		self.marital_status_label = StaticText(self, label='Marital:')
		self.marital_status_ctrl = CodeEntry(self, T_marital_status, width=9, name='marital_status')
		self.preferred_name_label = StaticText(self, label='Alias:')
		self.preferred_name_ctrl = TextEntry(self, name='preferred_name')
		self.maiden_name_label = StaticText(self, label='Maiden:')
		self.maiden_name_ctrl = TextEntry(self, name='maiden_name')
		self.address_label = StaticText(self, label='Address:')
		self.address_1_ctrl = TextEntry(self, name='address_1')
		self.address_2_ctrl = TextEntry(self, name='address_2')
		self.address_3_ctrl = TextEntry(self, name='address_3')
		self.post_code_label = StaticText(self, label='Postcode:')
		self.post_code_ctrl = TextEntry(self, width=4, name='post_code')
		self.state_label = StaticText(self, label='State:')
		self.state_ctrl = CodeEntry(self, T_state, width=3, name='state')
		self.country_label = StaticText(self, label='Country:')
		self.country_ctrl = CodeEntry(self, T_country, width=10, name='country')
		self.home_telephone_label = StaticText(self, label='Phone:')
		self.home_telephone_ctrl = PhoneEntry(self, width=10, name='home_telephone')
		self.office_telephone_label = StaticText(self, label='W:')
		self.office_telephone_ctrl = PhoneEntry(self, width=10, name='office_telephone')
		self.mobile_telephone_label = StaticText(self, label='M:')
		self.mobile_telephone_ctrl = PhoneEntry(self, width=10, name='mobile_telephone')
		self.fax_telephone_label = StaticText(self, label='F:')
		self.fax_telephone_ctrl = PhoneEntry(self, width=10, name='fax_telephone')
		self.email_address_label = StaticText(self, label='Email:')
		self.email_address_ctrl = TextEntry(self, width=30, name='email_address')
		self.emergency_contact_label = StaticText(self, label='Emergency:')
		self.emergency_contact_ctrl = TextEntry(self, name='emergency_contact')
		self.emergency_contact_telephone_label = StaticText(self, label='Phone:')
		self.emergency_contact_telephone_ctrl = PhoneEntry(self, width=15, name='emergency_contact_telephone')
		self.start_date_label = StaticText(self, label='Joined:')
		self.start_date_ctrl = DateEntry(self, width=10, name='start_date')
		self.leaving_date_label = StaticText(self, label='Left:')
		self.leaving_date_ctrl = DateEntry(self, width=10, name='leaving_date')
		self.nationality_country_label = StaticText(self, label='Nationality:')
		self.nationality_country_ctrl = CodeEntry(self, T_country, width=10, name='nationality_country')
		self.birth_country_label = StaticText(self, label='Birth:')
		self.birth_country_ctrl = CodeEntry(self, T_country, width=10, name='birth_country')
		self.birth_place_label = StaticText(self, label='Place:')
		self.birth_place_ctrl = TextEntry(self, name='birth_place')
		self.first_language_label = StaticText(self, label='Language:')
		self.first_language_ctrl = CodeEntry(self, T_language, width=10, name='first_language')
		self.health_general_label = StaticText(self, label='Health')
		self.health_general_ctrl = TextEntry(self, name='health_general')
		self.health_specific_label = StaticText(self, label='(Specific)')
		self.health_specific_ctrl = TextEntry(self, name='health_specific')
		self.disabled_flag_ctrl = FlagEntry(self, label='Disabled', name='disabled_flag')
		self.retirement_age_label = StaticText(self, label='Ret. Age:')
		self.retirement_age_ctrl = IntEntry(self, width=3, name='retirement_age')
		self.probation_label = StaticText(self, label='Probation:')
		self.probation_ctrl = IntEntry(self, width=3, name='probation')
		self.probation_period_ctrl = CodeEntry(self, T_period, width=6, name='probation_period')
		self.notice_label = StaticText(self, label='Notice:')
		self.notice_ctrl = IntEntry(self, width=3, name='notice')
		self.notice_period_ctrl = CodeEntry(self, T_period, width=6, name='notice_period')
		self.notice_given_label = StaticText(self, label='Notice:')
		self.notice_given_ctrl = IntEntry(self, width=3, name='notice_given')
		self.notice_given_period_ctrl = CodeEntry(self, T_period, width=6, name='notice_given_period')
		self.rehire_flag_ctrl = FlagEntry(self, label='Rehire', name='rehire_flag')
		self.continuous_service_flag_ctrl = FlagEntry(self, label='Continuous', name='continuous_service_flag')
		self.type_of_licence_label = StaticText(self, label='Licence:')
		self.type_of_licence_ctrl = TextEntry(self, width=3, name='type_of_licence')
		self.endorsements_label = StaticText(self, label='Endorsement:')
		self.endorsements_ctrl = TextEntry(self, name='endorsements')
		self.hobbies_label = StaticText(self, label='Hobbies:')
		self.hobbies_ctrl = TextEntry(self, name='hobbies')
		self.previous_start_date_label = StaticText(self, label='Prior Start:')
		self.previous_start_date_ctrl = DateEntry(self, width=10, name='previous_start_date')
		self.previous_leaving_date_label = StaticText(self, label='Left:')
		self.previous_leaving_date_ctrl = DateEntry(self, width=10, name='previous_leaving_date')
		self.previous_employee_number_label = StaticText(self, label='Employee:')
		self.previous_employee_number_ctrl = TextEntry(self, width=6, name='previous_employee_number')
		self.leaving_reason_label = StaticText(self, label='Reason:')
		self.leaving_reason_ctrl = CodeEntry(self, T_leaving_reason, width=25, name='leaving_reason')
		self.save_button = Button(self, label='Save', name='save_button')

		# Set up tab order
		self.tab_order = [
			self.employee_search_ctrl,
			self.title_ctrl,
			self.first_names_ctrl,
			self.family_name_ctrl,
			self.birth_date_ctrl,
			self.gender_ctrl,
			self.marital_status_ctrl,
			self.preferred_name_ctrl,
			self.maiden_name_ctrl,
			self.address_1_ctrl,
			self.address_2_ctrl,
			self.address_3_ctrl,
			self.post_code_ctrl,
			self.state_ctrl,
			self.country_ctrl,
			self.home_telephone_ctrl,
			self.office_telephone_ctrl,
			self.mobile_telephone_ctrl,
			self.fax_telephone_ctrl,
			self.email_address_ctrl,
			self.emergency_contact_ctrl,
			self.emergency_contact_telephone_ctrl,
			self.start_date_ctrl,
			self.leaving_date_ctrl,
			self.nationality_country_ctrl,
			self.birth_country_ctrl,
			self.birth_place_ctrl,
			self.first_language_ctrl,
			self.health_general_ctrl,
			self.health_specific_ctrl,
			self.disabled_flag_ctrl,
			self.retirement_age_ctrl,
			self.probation_ctrl,
			self.probation_period_ctrl,
			self.notice_ctrl,
			self.notice_period_ctrl,
			self.notice_given_ctrl,
			self.notice_given_period_ctrl,
			self.rehire_flag_ctrl,
			self.continuous_service_flag_ctrl,
			self.type_of_licence_ctrl,
			self.endorsements_ctrl,
			self.hobbies_ctrl,
			self.previous_start_date_ctrl,
			self.previous_leaving_date_ctrl,
			self.previous_employee_number_ctrl,
			self.leaving_reason_ctrl,
			self.save_button
		]

		# Bind event handlers
		for w in self.tab_order:
			w.Bind(EVT_CHAR, self.OnChar)
			w.Bind(EVT_KEY_DOWN, self.OnKeyDown)
		self.save_button.Bind(EVT_BUTTON, self.OnSave)

		# Layout the widgets
		vsz = BoxSizer(VERTICAL)

		# Put the employee fields in a grid
		gsz = FlexGridSizer(cols=2, vgap=3, hgap=3)

		def row(lhs, rhs):
			gsz.Add(lhs, 0, ALL, 2)
			hsz = BoxSizer(HORIZONTAL)
			# On MacOSX, there is extra border around compound widgets like
			# ComboBox and CheckBox. To compensate, add a border around
			# TextCtrl widgets. It isn't needed on Linux or Windows.
			needsit = Platform == '__WXMAC__'
			for w in rhs:
				hsz.Add(w, 0, ALL, 3 if needsit and isinstance(w, TextEntry) else 0)
				hsz.AddSpacer(2)
			gsz.Add(hsz, 0, 0, 0)

		row(self.employee_search_label,
			[self.employee_search_ctrl, self.employee_search_result_label])

		# Name: title_ctrl, first_names_ctrl, family_name_ctrl
		row(self.name_label, [self.title_ctrl, self.first_names_ctrl, self.family_name_ctrl])

		# Birth: birth_date_ctrl, Gender: gender_ctrl, Marital: marital_status_ctrl
		row(self.birth_date_label, [self.birth_date_ctrl, self.gender_label, self.gender_ctrl,
			self.marital_status_label, self.marital_status_ctrl])

		# Alias: preferred_name_ctrl, Maiden: maiden_name_ctrl
		row(self.preferred_name_label, [self.preferred_name_ctrl,
			self.maiden_name_label, self.maiden_name_ctrl])

		# Address: address_[123]_ctrl
		row(self.address_label, [self.address_1_ctrl, self.address_2_ctrl, self.address_3_ctrl])

		# Postcode: post_code_ctrl, state_ctrl, country_ctrl
		row(self.post_code_label, [self.post_code_ctrl, self.state_label, self.state_ctrl,
			self.country_label, self.country_ctrl])

		# Phone: home_telephone_ctrl, office_telephone_ctrl, mobile_telephone_ctrl, fax_telephone_ctrl
		row(self.home_telephone_label, [self.home_telephone_ctrl,
			self.office_telephone_label, self.office_telephone_ctrl,
			self.mobile_telephone_label, self.mobile_telephone_ctrl,
			self.fax_telephone_label, self.fax_telephone_ctrl])

		# Email: email_address_ctrl
		row(self.email_address_label, [self.email_address_ctrl])

		# Next of kin: emergency_contact_ctrl, emergency_contact_telephone_ctrl
		row(self.emergency_contact_label, [self.emergency_contact_ctrl,
			self.emergency_contact_telephone_label, self.emergency_contact_telephone_ctrl])

		# Start: start_date_ctrl, leaving_date_ctrl
		row(self.start_date_label, [self.start_date_ctrl,
			self.leaving_date_label, self.leaving_date_ctrl])

		# Nationality: nationality_country_ctrl, birth_country_ctrl, birth_place_ctrl
		row(self.nationality_country_label, [self.nationality_country_ctrl,
			self.birth_country_label, self.birth_country_ctrl,
			self.birth_place_label, self.birth_place_ctrl])

		# Language: first_language_ctrl
		row(self.first_language_label, [self.first_language_ctrl])

		# Health: health_general_ctrl, health_specific_ctrl, disabled_flag_ctrl
		row(self.health_general_label, [self.health_general_ctrl,
			self.health_specific_label, self.health_specific_ctrl, self.disabled_flag_ctrl])

		# Retirement/Probation: retirement_age_ctrl, probation_ctrl, probation_period_ctrl
		row(self.retirement_age_label, [self.retirement_age_ctrl,
			self.probation_label, self.probation_ctrl, self.probation_period_ctrl])

		# Notice: notice_ctrl, notice_period_ctrl, notice_given_ctrl,
		# notice_given_period_ctrl, rehire_flag_ctrl,
		# continuous_service_flag_ctrl
		row(self.notice_label, [self.notice_ctrl, self.notice_period_ctrl,
			self.notice_given_label, self.notice_given_ctrl, self.notice_given_period_ctrl,
			self.rehire_flag_ctrl, self.continuous_service_flag_ctrl])

		# Licence: type_of_licence_ctrl, endorsements_ctrl, hobbies_ctrl
		row(self.type_of_licence_label, [self.type_of_licence_ctrl,
			self.endorsements_label, self.endorsements_ctrl,
			self.hobbies_label, self.hobbies_ctrl])

		# Previous: previous_start_date_ctrl, previous_leaving_date_ctrl,
		# previous_employee_number_ctrl, leaving_reason_ctrl
		row(self.previous_start_date_label, [self.previous_start_date_ctrl,
			self.previous_leaving_date_label, self.previous_leaving_date_ctrl,
			self.previous_employee_number_label, self.previous_employee_number_ctrl])

		row(self.leaving_reason_label, [self.leaving_reason_ctrl])

		vsz.Add(gsz, 1, EXPAND | ALL, 2)
		vsz.Add(self.save_button, 0, ALL, 5)

		self.SetSizer(vsz)
		self.SetupScrolling()

	def OnSave(self, event=None):
		app().debug('EditEmployeeScreen.OnSave()')
		app().warn('Not relevant to gui test')

	def OnKeyDown(self, event):
		debug_event('EditEmployeeScreen.OnKeyDown', event)
		# On MacOSX, ComboBox doesn't get EVT_CHAR events for
		# TAB characters but it does get EVT_KEY_DOWN events.
		if Platform == '__WXMAC__' and event.GetKeyCode() == WXK_TAB:
			if not handle_tab(event, self.tab_order):
				event.Skip()
		else:
			event.Skip()

	def OnChar(self, event):
		debug_event('EditEmployeeScreen.OnChar', event)
		key = event.GetKeyCode()
		src = event.GetEventObject()
		if key == WXK_TAB:
			if not handle_tab(event, self.tab_order):
				event.Skip()
		elif key == WXK_RETURN:
			if src == self.employee_search_ctrl:
				self.do_employee_search(self.employee_search_ctrl.GetValue())
			else:
				self.OnSave()
		else:
			event.Skip()

	def do_employee_search(self, term):
		app().debug('EditEmployeeScreen.do_employee_search(%s)' % term)
		app().warn('Not releveant to gui test')

class MainFrame(Frame):
	def __init__(self, app):
		self.app = app
		self.app.debug('mainframe.__init__')
		Frame.__init__(self, None, title=app.GetAppName(), size=(1000, 700))
		self.build_menus()
		self.build_tools()
		self.build_panel()
		self.CreateStatusBar()
		self.Show()

	def build_menus(self):
		'''Build the menu bar.'''
		self.file_menu = Menu()
		self.exit_menu_item = self.file_menu.Append(-1, 'E&xit')
		self.menu_bar = MenuBar()
		self.menu_bar.Append(self.file_menu, '&File')
		self.Bind(EVT_MENU, self.OnQuitCmd, self.exit_menu_item)
		self.SetMenuBar(self.menu_bar)

	def build_tools(self):
		'''Build the tool bar.'''
		self.tool_bar = self.CreateToolBar()
		tsize = (16, 16)
		self.blah_tool_bmp = ArtProvider.GetBitmap(ART_NEW, ART_TOOLBAR, tsize)
		self.tool_bar.SetToolBitmapSize(tsize)
		self.blah_tool_id = NewId()
		self.tool_bar.AddLabelTool(self.blah_tool_id, 'Blah', self.blah_tool_bmp, shortHelp='Blah', longHelp='Long help for blah')
		self.Bind(EVT_TOOL, self.OnTool, id=self.blah_tool_id)
		self.tool_bar.Realize()

	def build_panel(self):
		'''Build the main window's contents.'''
		splitter_flags = SP_LIVE_UPDATE | SP_3D
		# Split nav+info to the left, notebook to the right
		self.vsplit = SplitterWindow(self, style=splitter_flags)
		# Disallow complete removal of a pane or user won't be able to retrieve it
		self.vsplit.SetMinimumPaneSize(1)
		# When growing, only grow the right (main) pane
		self.vsplit.SetSashGravity(0.0)

		# Split nav to the top and info to the bottom
		self.hsplit = SplitterWindow(self.vsplit, style=splitter_flags)
		# Disallow complete removal of a pane or user won't be able to retrieve it
		self.hsplit.SetMinimumPaneSize(1)
		# When growing, only grow the top (tree) pane
		self.hsplit.SetSashGravity(1.0)

		p1 = Panel(self.hsplit, style=BORDER_SUNKEN)
		sz = BoxSizer(VERTICAL)
		sz.Add(self.create_tree(p1), 1, EXPAND | ALL, 3)
		p1.SetSizer(sz)

		p2 = Panel(self.hsplit, style=BORDER_SUNKEN)
		sz = BoxSizer(VERTICAL)
		sz.Add(self.create_info(p2), 1, EXPAND | ALL, 3)
		p2.SetSizer(sz)

		p3 = Panel(self.vsplit, style=BORDER_SUNKEN)
		sz = BoxSizer(VERTICAL)
		sz.Add(self.create_book(p3), 1, EXPAND | ALL, 3)
		p3.SetSizer(sz)

		self.hsplit.SplitHorizontally(p1, p2, 400)
		# Initial sash position for a nested SplitterWindow doesn't work so...
		CallAfter(self.hsplit.SetSashPosition, 400)

		self.vsplit.SplitVertically(self.hsplit, p3, 300)

	def create_tree(self, panel):
		'''Create the tree control (top left) for navigating around data entry screens.'''
		tree_style = TR_DEFAULT_STYLE | TR_HIDE_ROOT
		self.tree = TreeCtrl(panel, style=tree_style)
		self.build_tree_items()
		self.Bind(EVT_TREE_ITEM_ACTIVATED, self.OnTreeItemActivated, self.tree)
		self.tree.ExpandAll()
		return self.tree

	def build_tree_items(self):
		'''Build the contents of the navigation tree.'''
		self.screen_data = [
			['Employee', [
				('Edit', EditEmployeeScreen)
			]]
		]

		self.tree_root = self.tree.AddRoot(self.app.GetAppName())
		first = None
		for data in self.screen_data:
			node, children = data
			parent = self.tree.AppendItem(self.tree_root, node)
			for child in children:
				label, paneclass = child
				id = node + '/' + label
				# Add the item and save a pane class with each item
				item = self.tree.AppendItem(parent, label)
				self.tree.SetPyData(item, (paneclass, id))
				# Select the first top-level child in the tree (by default)
				if first == None:
					first = item
					self.tree.SelectItem(first)

	def create_info(self, panel):
		'''Create the informational panel (bottom left).'''
		self.info_panel = Panel(panel, style=0)
		return self.info_panel

	def create_book(self, panel):
		'''Create the 'notebook' panel (right) that will contain data entry screens activated via the navigation tree.'''
		self.book = AuiNotebook(panel, style=AUI_NB_DEFAULT_STYLE | AUI_NB_CLOSE_ON_ALL_TABS)
		return self.book

	def OnTool(self, event):
		'''Handler for the EVT_TOOL event.'''
		self.app.debug('MainFrame.OnTool()')
		tool_id = event.GetId()
		if tool_id == self.blah_tool_id:
			self.OnQuitCmd(None)

	def OnTreeItemActivated(self, event):
		'''Handler for the EVT_TREE_ITEM_ACTIVATED event.'''
		self.app.debug('MainFrame.OnTreeItemActivated()')
		item = event.GetItem()
		if item:
			paneclass, label = self.tree.GetPyData(item)
			if paneclass:
				pane = paneclass(self.book)
				self.book.AddPage(pane, pane.get_caption(), True)
		event.Skip()

	def OnQuitCmd(self, event):
		'''Quit the application.'''
		self.app.debug('MainFrame.OnQuitCmd()')
		self.Close()

def main(redirect=False):
	'''Call this function from another module to simulate running this module directly.'''
	TestApp(redirect=redirect).MainLoop()

if __name__ == '__main__':
	main()

# vi:set ts=4 sw=4:
