# vim: ts=4 et sts=4 sw=4 autoindent

import gtk.glade, gtk
import gobject
import sys, os.path
import math
import Pyro.core
import Pyro.errors
import Pyro.constants
from optparse import OptionParser
import time

import sndcs_common
#from sndcs.EmployeeFactory import EmployeeFactory
#from sndcs.Employee import Employee
#from sndcs.JobHedFactory import JobHedFactory
#from sndcs.OperationFactory import OperationFactory
#from sndcs.IndirectFactory import IndirectFactory
#from sndcs.LaborDtlFactory import LaborDtlFactory
from sndcs_common.TrueAndFalseMixin import TrueAndFalseMixin
from sndcs_common.MathUtilMixin import MathUtilMixin
from sndcs_common.PyroProxyMixin import PyroProxyMixin
from sndcs_common.Win32HelperMixin import Win32HelperMixin
from sndcs_client.Config import config
import sndcs_client.gtk # For required server version

#from MiddleKit.Run.ObjectStore import UnknownObjectError
from sndcs_common.SndcsExceptions import *

from sndcs_common.Logger import logger
log = logger.getLogger("sndcs_gtk")

gobject.threads_init()

def treeview_visible_function(model,iter,widget):
    #udata is a list of [ column_number, "search for" ]
    column_number=widget.column_number
    search_for=widget.search_criteria.lower()


    # Admin Employee throw off iterator errors, fix this.
    try:
        value=str(model.get_value(iter,column_number))
    except ValueError:
        return False
    
    if not hasattr(widget,"boolean_only") or not widget.boolean_only:
        if value.lower().find(search_for) > -1: # Partial query results
                return True
        else:
                return False
    else:
        if value.lower()==str(search_for) or search_for=="":
                return True
        else:
                return False

def getColumnNumberForName(widget,name):
        y = 0
        numberFound=False
        for x in widget.get_columns():
                if x.get_title() == name:
                        #print "Number Found",y,"!"
                        numberFound = True
                        break
                y += 1
        
        # If columns aren't defined the glade interface file sometimes the names don't get set in time for this function
        # but since the rlist defaults to 1, we can return 1 to prevent recursion errors.
        if numberFound:
                return y
        else:
                return 1


class Application(TrueAndFalseMixin, MathUtilMixin, PyroProxyMixin, Win32HelperMixin):

    def __init__(self):

        print
        print sndcs_common.NAME, sndcs_common.VERSION
        print sndcs_common.COPYRIGHT
        print
        print sndcs_common.LICENSE
        print

        # Check if we have correct Pyro version
        REQUIRED_PYRO_VERSION = '3.5'
        if Pyro.constants.VERSION < REQUIRED_PYRO_VERSION:
            log.error("Pyro version must be %s or greater. You have version %s installed.", REQUIRED_PYRO_VERSION, Pyro.constants.VERSION)
            sys.exit(1)
        
        self.config=config #Make Config() instance to all class methods

        # Parse comand line options
        self.parser = OptionParser(usage="%prog [options]", version="%prog " + sndcs_common.VERSION)
        self.parser.add_option("-c", "--config", dest="config_file", help="use FILE as additional config file", metavar="FILE")
        self.parser.add_option("-H", "--host", dest="ns_hostname", help="use HOST as name server host name", metavar="HOST")
        self.parser.add_option("-n", "--namespace", dest="namespace", help="use NAMESPACE as namespace", metavar="NAMESPACE")
        (self.options, args) = self.parser.parse_args()
        if self.options.config_file: # User specified an additional config file
            additional_config = config.read(os.path.expanduser(self.options.config_file))

        log.info("Using config file(s): %s", self.config.getParsedFilenames())

        # Set the Pyro config
        if self.options.ns_hostname:
            pyro_ns_hostname = self.options.ns_hostname
        else:
            pyro_ns_hostname = self.config.get("pyro", "ns_hostname", None)
        if pyro_ns_hostname:
            Pyro.config.PYRO_NS_HOSTNAME = pyro_ns_hostname
            log.info("Using Pyro Name Server at: %s", pyro_ns_hostname)

        # Get the Pyro namespace from the config
        if self.options.namespace:
            self.namespace = self.options.namespace
        else:
            self.namespace = self.config.get("pyro", "namespace", "sndcs")
        log.info("Using Pyro namespace '%s'", self.namespace)
        
        self.callbacks = {
            "on_window_pininterface_delete_event": self.on_window_pininterface_delete_event,
            "on_window_pininterface_key_press_event": self.addToPinPad,
            "on_enter_pinpad_pressed": self.enterPinPad,
            "on_cancel_pinpad_pressed": self.clearPinPad,
            "on_backspace_pinpad_pressed": self.backspacePinPad,
            "on_window_main_delete_event": gtk.main_quit,
            "on_window_main_key_press_event": self.on_window_main_key_press_event,
            "on_treeview_employee_list_button_press_event": self.on_treeview_employee_list_button_press_event,
            "on_window_admin_key_press_event": self.on_window_admin_key_press_event,
            "on_bttn_resume_tasks_clicked": self.resumeTasks,
            "on_bttn_clock_out_clicked": self.on_bttn_clock_out_clicked,
            "on_notebook_main_switch_page": self.on_notebook_main_switch_page,
            "on_notebook_selected_employee_switch_page": self.on_notebook_selected_employee_switch_page,
            "on_bttn_return_to_employee_list_clicked": self.on_bttn_return_to_employee_list_clicked,
            "on_bttn_start_production_activity_clicked": self.on_bttn_start_production_activity_clicked,
            "on_bttn_start_job_gang_clicked": self.on_bttn_start_job_gang_clicked,
            "on_bttn_start_setup_activity_clicked": self.on_bttn_start_setup_activity_clicked,
            "on_bttn_start_indirect_activity_clicked": self.on_bttn_start_indirect_activity_clicked,
            "on_bttn_end_activity_clicked": self.on_bttn_end_activity_clicked,
            "on_bttn_lunch_in_out_clicked": self.on_bttn_lunch_in_out_clicked,
            "on_bttn_break_in_out_clicked": self.on_bttn_break_in_out_clicked,
            "on_button_start_setup_gang_clicked": self.on_button_start_setup_gang_clicked,
            "on_button_indirect_cancel_clicked": self.on_button_indirect_cancel_clicked,
            "on_button_indirect_ok_clicked": self.on_button_indirect_ok_clicked,
            "on_rlistLookForIn_changed": self.on_rlistLookForIn_changed,
            "on_searchCriteria_changed": self.on_searchCriteria_changed,
            "on_rlistLookForIn_admin_changed": self.on_rlistLookForIn_admin_changed,
            "on_searchCriteria_admin_changed": self.on_searchCriteria_admin_changed,
            "on_recent_activities_treeview_row_activated": self.on_recent_activities_treeview_row_activated,
            "on_treeview_indirect_activity_codes_button_press_event": self.on_treeview_indirect_activity_codes_button_press_event,
            "on_button_available_production_cancel_clicked": self.on_button_available_production_cancel_clicked,
            "on_button_available_production_ok_clicked": self.on_button_available_production_ok_clicked,
            "on_treeview_available_activities_button_press_event": self.on_treeview_available_activities_button_press_event,
            "on_treeview_start_setup_button_press_event": self.on_treeview_start_setup_button_press_event,
            "on_button_setup_cancel_clicked": self.on_button_setup_cancel_clicked,
            "on_button_setup_ok_clicked": self.on_button_setup_ok_clicked,
            "on_button_start_gang_add_clicked": self.on_button_start_gang_add_clicked,
            "on_button_start_gang_remove_clicked": self.on_button_start_gang_remove_clicked,
            "on_treeview_start_gang_available_button_press_event": self.on_treeview_start_gang_available_button_press_event,
            "on_treeview_start_gang_available_drag_data_get": self.on_treeview_start_gang_available_drag_data_get,
            "on_treeview_start_gang_available_drag_data_received": self.on_treeview_start_gang_available_drag_data_received,
            "on_treeview_start_gang_current_button_press_event": self.on_treeview_start_gang_current_button_press_event,
            "on_treeview_start_gang_current_drag_data_received": self.on_treeview_start_gang_current_drag_data_received,
            "on_treeview_start_gang_current_drag_data_get": self.on_treeview_start_gang_current_drag_data_get,
            "on_button_start_gang_ok_clicked": self.on_button_start_gang_ok_clicked,
            "on_button_start_gang_cancel_clicked": self.on_button_start_gang_cancel_clicked,
            "on_button_end_activity_dynamic_cancel_clicked": self.on_button_end_activity_dynamic_cancel_clicked,
            "on_button_end_activity_dynamic_ok_clicked": self.on_button_end_activity_dynamic_ok_clicked,
            "on_menu_main_file_quit_activate": gtk.main_quit,
            "on_menu_main_help_about_activate": self.on_menu_main_help_about_activate,
            "on_menu_main_view_admin_activate": self.on_menu_main_view_admin_activate,
            
            "on_dialog_about_delete_event": self.on_dialog_about_delete_event,
            "on_button_dialog_about_ok_clicked": self.on_button_dialog_about_ok_clicked,

            "on_window_admin_delete_event": self.on_window_admin_delete_event,
            "on_notebook_admin_switch_page": self.on_notebook_admin_switch_page,
            "on_menu_admin_file_new_activate": self.on_menu_admin_file_new_activate,

            "on_button_additional_information_clicked": self.on_button_additional_information_clicked,
            "on_bttn_additional_information_ok_clicked": self.on_bttn_additional_information_ok_clicked,
        }
        if self.main_is_frozen():
            filename = os.path.join(self.get_main_dir(), "sndcs2.glade")
        else:
            filename = os.path.join(sys.prefix, "share", "sndcs", "sndcs2.glade")
        assert os.path.exists(filename)
        self.xml = gtk.glade.XML(filename)
        self.xml.signal_autoconnect(self.callbacks)

        # Display the splash screen
        fixed = self.xml.get_widget("fixed_splash_screen")
        label_splash_description = gtk.Label()
        label_splash_description_shadow = gtk.Label()
        label_splash_action = gtk.Label()
        label_splash_description.set_justify(gtk.JUSTIFY_LEFT)
        label_splash_description_shadow.set_justify(gtk.JUSTIFY_LEFT)
        label_splash_action.set_justify(gtk.JUSTIFY_LEFT)
        label_splash_description.set_markup("<span size=\"medium\" weight=\"bold\" foreground=\"white\">%s %s</span>" % (sndcs_common.NAME, sndcs_common.VERSION))
        label_splash_description_shadow.set_markup("<span size=\"medium\" weight=\"bold\" foreground=\"grey\">%s %s</span>" % (sndcs_common.NAME, sndcs_common.VERSION))
        label_splash_action.set_markup("<span size=\"x-small\" foreground=\"white\">Connecting to server...</span>")
        fixed.put(label_splash_description_shadow, 225, 161)
        fixed.put(label_splash_description, 225, 160)
        fixed.put(label_splash_action, 15, 165)
        label_splash_description_shadow.show()
        label_splash_description.show()
        label_splash_action.show()
        self.xml.get_widget("splash_screen").show()
        while gtk.events_pending():
            gtk.main_iteration()
        
        # Get server information
        # This need to be after the glade code so that if this fails the flatline can display the gtk error box properly
        try:
            self.server = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["DCSServer"]))
        except Exception, e:
            self.flatline(e)
        major_version, minor_version, macro_version, custom_version, version = self.server.getServerVersion()
        log.info("Connected to SaberNet DCS Server version: %s", version)
        log.info("SaberNet DCS Server uptime: %s", str(self.server.getUptime()) )

        # Does that meet the required version number?
        if ".".join((str(major_version), str(minor_version), str(macro_version))) < sndcs_client.gtk.REQUIRED_SERVER_VERSION:
            log.error("Server version does not meet requirement. Client required version: %s  Server version: %s", sndcs_client.gtk.REQUIRED_SERVER_VERSION, version)
            sys.exit(1)


        for x in [ "one_pinpad", "two_pinpad", "three_pinpad", "four_pinpad", "five_pinpad", "six_pinpad", "seven_pinpad", "eight_pinpad", "nine_pinpad", "zero_pinpad" ]:
                self.xml.get_widget(x).connect("pressed",self.addToPinPad)


        # For searching feature
        self.name,self.admin_name = [ "Employee Name","Number" ]

        self.vbox_information = self.xml.get_widget("vbox_information_display")
        window_main = self.xml.get_widget("window_main")
        window_main.set_title(" ".join((sndcs_common.NAME, sndcs_common.VERSION)))
        if self.true(self.config.get("gtk", "fullscreen")):
            window_main.fullscreen()

        # If search dialog is disabled then hide it
        if not self.true(self.config.get("gtk", "search_dialog")):
            if not self.true(self.config.get("gtk", "osd_keyboard")):
                widget = self.xml.get_widget("keyboard_toolbar")
                widget.hide()
            else:
                widget = self.xml.get_widget("searchdialog")
                widget.hide()
        # ...otherwise connect the proper callbacks
        else:
            self.searchCriteria = self.xml.get_widget("searchCriteria")
            self.searchCriteriaAdmin = self.xml.get_widget("searchCriteria_admin")
            self.searchCriteria.connect("changed", self.on_searchCriteria_changed)
            self.xml.get_widget("rlistLookForIn").connect("changed", self.on_rlistLookForIn_changed)

            self.attachHighlightToWidget(self.searchCriteria,True)
            self.attachHighlightToWidget(self.searchCriteriaAdmin,True)

        if self.true(self.config.get("gtk", "osd_keyboard")):
                self.capslock,self.shiftkey = [ False, False ]
                for x in [ "vbox_keyboard", "vbox_keyboard2", "osdbutton", "osdbutton2" ]:
                        self.xml.get_widget(x).show()

                self.xml.get_widget("osdbutton").connect("toggled",self.toggleKeyboard) 
                self.xml.get_widget("osdbutton2").connect("toggled",self.toggleKeyboard)

                # Touchscreen buttons callbacks here
                
                allTheKeys = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v" , "w", "x", "y" , "z" ]
                allTheKeys.extend([ "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "zero" ])
                allTheKeys.extend(["enter", "escape", "bs", "spacebar", "colon", "pipe", "comma", "period1", "period2", "tab", "hypen", "fslash1", "fslash2"])
                # "" is for main page, _admin is for Admin page   keyboards
                for prefix in [ "" ,"_admin" ]:
                    if prefix=="":
                            emitto="window_main"
                    elif prefix=="_admin":
                            emitto="window_admin"

                    for x in [ "capslock", "shiftkey" ]:
                            self.xml.get_widget("%s%s" % (x,prefix) ).connect("pressed",self.caseShift,x)
                            self.xml.get_widget("%s%s" % (x,prefix) ).set_focus_on_click(False)

                    for x in allTheKeys:
                                if x=="bs":
                                    #Backspace
                                    y = gtk.keysyms.BackSpace
                                elif x=="spacebar":
                                    #Spacebar
                                    y = gtk.keysyms.space
                                elif x=="colon":
                                     # Shift defines this   If shift is on then then [0] is selected
                                    y = [ gtk.keysyms.colon, gtk.keysyms.semicolon]
                                elif x=="pipe":
                                    # Watch the forward slash going into the eval()
                                    y= [ gtk.keysyms.bar, gtk.keysyms.backslash ]
                                elif x.find("fslash")>-1:
                                    y = gtk.keysyms.slash
                                elif x=="hypen":
                                    # hopefully the same everywhere
                                    y = 0x2d
                                elif x=="comma":
                                    y = gtk.keysyms.comma
                                elif x.find("period")>-1:
                                    y = gtk.keysyms.period
                                elif x=="tab":
                                    y = gtk.keysyms.Tab
                                elif x=="escape":
                                    y = gtk.keysyms.Escape
                                elif x=="enter":
                                    y = gtk.keysyms.Return
                                else:
                                    y=self.convertAlphaToNum(x)
                                try:
                                    self.xml.get_widget("%skey%s" % (x,prefix)).connect("pressed",self.keystrokes,(y,emitto))
                                    self.xml.get_widget("%skey%s" % (x,prefix)).connect("focus",self.focusCapture)
                                    self.xml.get_widget("%skey%s" % (x,prefix)).set_focus_on_click(False)
                                except: log.debug("Keybinding failed for: %s at %s" % (x,prefix) )

        # Check if the menu should be visible
        if self.true(self.config.get("gtk", "menu_bar", True)):
            menu_main = self.xml.get_widget("menu_main")
            menu_main.show()

        # Create window_main tree views
        from sndcs_client.gtk.AvailableActivitiesTreeView import AvailableActivitiesTreeView
        from sndcs_client.gtk.IndirectActivitiesTreeView import IndirectActivitiesTreeView
        from sndcs_client.gtk.CurrentActivitiesTreeView import CurrentActivitiesTreeView
        from sndcs_client.gtk.ActiveEmployeesTreeView import ActiveEmployeesTreeView

        label_splash_action.set_markup("<span size=\"x-small\" foreground=\"white\">Loading available activity data...</span>")
        while gtk.events_pending():
            gtk.main_iteration()
        tree_view = self.xml.get_widget("treeview_available_activities")
        tree_view.set_enable_search(False)
        self.treeview_available_activities = AvailableActivitiesTreeView(tree_view, namespace=self.namespace)
        activity_model = self.populateTreeViewAvailableActivities()

        label_splash_action.set_markup("<span size=\"x-small\" foreground=\"white\">Loading available setup data...</span>")
        while gtk.events_pending():
            gtk.main_iteration()
        tree_view = self.xml.get_widget("treeview_start_setup")
        tree_view.set_enable_search(False)
        self.treeview_start_setup = AvailableActivitiesTreeView(tree_view, multiple=False, namespace=self.namespace)
        self.populateTreeViewStartSetup(model=activity_model)

        label_splash_action.set_markup("<span size=\"x-small\" foreground=\"white\">Loading available gang data...</span>")
        while gtk.events_pending():
            gtk.main_iteration()
        tree_view = self.xml.get_widget("treeview_start_gang_available")
        tree_view.set_enable_search(False)
        self.treeview_start_gang_available = AvailableActivitiesTreeView(tree_view, namespace=self.namespace)
        self.populateTreeViewStartGangAvailable(model=activity_model)

        tree_view = self.xml.get_widget("treeview_start_gang_current")
        tree_view.set_enable_search(False)
        self.treeview_start_gang_current = AvailableActivitiesTreeView(tree_view, namespace=self.namespace)

        label_splash_action.set_markup("<span size=\"x-small\" foreground=\"white\">Loading indirect activities...</span>")
        while gtk.events_pending():
            gtk.main_iteration()
        tree_view = self.xml.get_widget("treeview_indirect_activity_codes")
        tree_view.set_enable_search(False)
        self.treeview_indirect_activity_codes = IndirectActivitiesTreeView(tree_view, namespace=self.namespace)
        self.populateTreeViewIndirectActivityCodes()
        
        label_splash_action.set_markup("<span size=\"x-small\" foreground=\"white\">Loading active employee data...</span>")
        while gtk.events_pending():
            gtk.main_iteration()
        tree_view = self.xml.get_widget("treeview_employee_list")
        tree_view.set_enable_search(False)
        self.treeview_active_employees = ActiveEmployeesTreeView(tree_view, namespace=self.namespace)
        self.populateTreeViewActiveEmployees()

        tree_view = self.xml.get_widget("treeview_current_activities")
        tree_view.set_enable_search(False)
        self.treeview_current_activities = CurrentActivitiesTreeView(tree_view, self.treeview_active_employees.model)

        # Set up drag and drop
        self.treeview_start_gang_available.view.drag_source_set(gtk.gdk.BUTTON1_MASK, [("text/plain", gtk.TARGET_SAME_APP, 0)], gtk.gdk.ACTION_MOVE)
        self.treeview_start_gang_current.view.drag_dest_set(gtk.DEST_DEFAULT_ALL, [("text/plain", gtk.TARGET_SAME_APP, 0)], gtk.gdk.ACTION_MOVE)
        self.treeview_start_gang_current.view.drag_source_set(gtk.gdk.BUTTON1_MASK, [("text/plain", gtk.TARGET_SAME_APP, 0)], gtk.gdk.ACTION_MOVE)
        self.treeview_start_gang_available.view.drag_dest_set(gtk.DEST_DEFAULT_ALL, [("text/plain", gtk.TARGET_SAME_APP, 0)], gtk.gdk.ACTION_MOVE)



        # Create window_admin tree views
        # TODO: see if user has access to admin stuff, if not then skip all this
        #from sndcs_client.gtk.JobAdminTreeView import JobAdminTreeView
        #tree_view = self.xml.get_widget("treeview_job_admin") 
        #tree_view.set_enable_search(False)
        #self.treeview_job_admin = JobAdminTreeView(tree_view)

        #from sndcs_client.gtk.EmployeeAdminTreeView import EmployeeAdminTreeView
        #tree_view = self.xml.get_widget("treeview_employee_admin") 
        #tree_view.set_enable_search(False)
        #self.employee_admin_tree_view = EmployeeAdminTreeView(tree_view)

        dialog_about = self.xml.get_widget("dialog_about")
        label_dialog_about_name = self.xml.get_widget("label_dialog_about_name")
        label_dialog_about_name.set_markup("<span size=\"x-large\" weight=\"bold\">%s %s</span>" % (sndcs_common.NAME, sndcs_common.VERSION))
        label_dialog_about_description = self.xml.get_widget("label_dialog_about_description")
        label_dialog_about_description.set_markup("<span size=\"medium\">%s</span>" % (sndcs_common.DESCRIPTION))
        label_dialog_about_copyright = self.xml.get_widget("label_dialog_about_copyright")
        label_dialog_about_copyright.set_markup("<span size=\"x-small\">%s</span>" % (sndcs_common.COPYRIGHT))
        dialog_about.set_title("%s %s" % (sndcs_common.NAME, sndcs_common.VERSION))

        window_admin = self.xml.get_widget("window_admin")
        window_admin.set_title(" ".join((sndcs_common.NAME, sndcs_common.VERSION, "Admin")))
        
        # Set up the recent activities list
        self.cached_jobs = gtk.ListStore(int,str)

        self.recent_activities_treeview = self.xml.get_widget("recent_activities_treeview")
        if self.false(self.config.get("gtk","recent_activity",True)):
                self.xml.get_widget("recent_activity_frame").hide()
        tree_column_1 = gtk.TreeViewColumn("Activities")
        self.recent_activities_treeview.append_column(tree_column_1)

        cell = gtk.CellRendererText()
        tree_column_1.pack_start(cell, True)
        tree_column_1.add_attribute(cell, "text", 1)

        self.recent_activities_treeview.set_model(self.cached_jobs)

        # Globals (if add a new one make sure to set to None in clearAndResetGui)
        self.currently_selected_employee = None
        self.barcode_command = ""
        
        # Globals for end activity screen
        # End and continue should be a list of LaborDtl objects, start should be a list of Operation objects
        self.end_activities_dynamic = []
        self.start_activities_dynamic = []
        self.continue_activities_dynamic = []
        self.start_indirect_dynamic = None
        self.frame_ending = None
        self.frame_starting = None
        # The rows of widgets in the end activity dynamic screen
        self.end_activities_dynamic_widgets = []
        self.start_activities_dynamic_widgets = []
        self.continue_activities_dynamic_widgets = []

        
        # TODO: lookup the barcode signifiers from the config file
        self.main_selection_signifier = '$'
        self.main_action_signifier = '*'
        self.main_command_signifier = '/'
        self.separator_signifier = '|'
        
        # Selection command signifiers
        self.clockin_and_select_selection_signifier = 's'
        self.lunch_selection_signifier = 'l'
        self.break_selection_signifier = 'b'
        self.resume_selection_signifier = 'r' # Resume Selection
        self.clockout_selection_signifier = 'o'

        # Action command signifiers
        self.indirect_action_signifier = 'i'
        self.production_action_signifier = 'p'
        self.end_production_action_signifier = 'pc'
        self.setup_action_signifier = 's'
        self.end_setup_action_signifier = 'sc'

        # Setup data for the statusbar clock
        self.clock_offset = 0.0
        gobject.idle_add(self.updateStatusBarClock)
        gobject.timeout_add(1000, gobject.idle_add, self.updateStatusBarClock)

        # Start the heartbeat monitor
        label_splash_action.set_markup("<span size=\"x-small\" foreground=\"white\">Starting heartbeat monitor...</span>")
        while gtk.events_pending():
            gtk.main_iteration()
        self.startHeartbeatMonitor()

        # Set up the event subscriber
        label_splash_action.set_markup("<span size=\"x-small\" foreground=\"white\">Starting event subscriber...</span>")
        while gtk.events_pending():
            gtk.main_iteration()
        self.startEventSubsciber()

        # Employee Image
        self.employee_image = self.xml.get_widget("img_employee")
        # Setup evironment to have search feature
        self.clear_search_criteria()
        # Show the "Employee Name" option when app first starts up
        obj=self.xml.get_widget("rlistLookForIn")
        obj.set_active(0)

        # Let's make the treeview_employee_list have the focus when the app starts up
        treeview_employee_list = self.xml.get_widget("treeview_employee_list")
        treeview_employee_list.grab_focus()

        window_main.show()
        self.xml.get_widget("splash_screen").hide()
 
    def set_busy_status(self):
        """ Set cursor to "busy" status """
        self.xml.get_widget("window_main").window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
        gtk.gdk.display_get_default().flush()

    def clear_busy_status(self):
        self.xml.get_widget("window_main").window.set_cursor(None)

    # TODO: this currently doesn't work very well. The EventSubscriber will
    # randomly leave it's threaded self running and even though the thread says
    # it's stopped and isAlive() returns False it will still process events.
    # This means a single event will get consumend multiple times
    def refresh(self):
        self.stopHeartbeatMonitor()
        self.stopEventSubscriber()
        self.startHeartbeatMonitor()
        self.startEventSubsciber()
        self.populateTreeViews()

    def addCachedJob(self,operation_id,jobString):
        for x in self.cached_jobs:
            if int(x[0]) == int(operation_id):
                return # Already on "Recent List"
        
        if len(self.cached_jobs) > 20: # Last 20 jobs displayed
            del self.cached_jobs[0]

        log.info("Added Operation %s to recent activity list" % operation_id)
        self.cached_jobs.append([operation_id,jobString])

    def addListToCachedJobs(self,job_list):
        log.debug("addListToCached() called: %s" % job_list )
        if not job_list is None:
            operation_object = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["Operation"]))
            for operation in job_list:
                    if type(operation) == dict:
                        if not operation["job_type"] is "Production": return
                        operation_id = int( operation["operation"] )
                    else:
                        operation_id = int ( operation )

                    operation = operation_object.setOperationBySerialNum( operation_id )
                    self.addCachedJob(operation_id,operation.formattedDescription() )
                    operation.disconnect()


    def deleteCachedJob(self,operation_id):
        p = None # (p)arent
        i = 0

        for x in self.cached_jobs:
            if int(x[0]) == int(operation_id):
                p = i
                break
            i += 1

        if p is not None:
            iter = self.cached_jobs.get_iter(p)
            self.cached_jobs.remove(iter)
            log.info("Removed Operation %s from recent activity list" % operation_id)

    def delete_operation(self,event):
        self.deleteCachedJob(event.msg["serialNum"])
        self.treeview_available_activities.update(event) # Pass along event to AvailableActivitiesView.py

    def startEventSubsciber(self):
        from sndcs_client.gtk.EventSubscriber import EventSubscriber
        callbacks = {self.namespace + "_heartbeat":[self.heartbeat_monitor.update],
            self.namespace + "_clock_in":[self.treeview_active_employees.update],
            self.namespace + "_clock_out":[self.treeview_active_employees.update],
            self.namespace + "_lunch_in":[self.treeview_active_employees.update],
            self.namespace + "_lunch_out":[self.treeview_active_employees.update],
            self.namespace + "_break_in":[self.treeview_active_employees.update],
            self.namespace + "_break_out":[self.treeview_active_employees.update],
            self.namespace + "_indirect_add":[self.treeview_indirect_activity_codes.update],
            self.namespace + "_indirect_delete":[self.treeview_indirect_activity_codes.update],
            self.namespace + "_indirect_start":[self.treeview_active_employees.update],
            self.namespace + "_indirect_stop":[self.treeview_active_employees.update],
            self.namespace + "_indirect_suspend":[self.treeview_active_employees.update],
            self.namespace + "_indirect_resume":[self.treeview_active_employees.update],
            self.namespace + "_production_start":[self.treeview_active_employees.update],
            self.namespace + "_production_stop":[self.treeview_active_employees.update],
            self.namespace + "_production_suspend":[self.treeview_active_employees.update],
            self.namespace + "_production_resume":[self.treeview_active_employees.update],
            self.namespace + "_employee_add":[self.treeview_active_employees.update],
            self.namespace + "_employee_edit":[self.treeview_active_employees.update],
            self.namespace + "_employee_delete":[self.treeview_active_employees.update],
            self.namespace + "_setup_start":[self.treeview_active_employees.update],
            self.namespace + "_setup_stop":[self.treeview_active_employees.update],
            self.namespace + "_setup_suspend":[self.treeview_active_employees.update],
            self.namespace + "_setup_resume":[self.treeview_active_employees.update],
            self.namespace + "_heartbeat":[self.heartbeat_monitor.update, self.calculateClockOffset],
            self.namespace + "_scrapcode_add":[self.add_scrapcode],
            self.namespace + "_scrapcode_delete":[self.delete_scrapcode],
            self.namespace + "_department_add":[self.add_department],
            self.namespace + "_department_delete":[self.delete_department],
            self.namespace + "_clear_settings": [self.clear_settings],
            self.namespace + "_operation_add":[self.treeview_available_activities.update],
            #self.namespace + "_operation_delete":[self.treeview_available_activities.update],
            self.namespace + "_operation_delete":[self.delete_operation],
            #self.namespace + "_operation_complete":[self.treeview_available_activities.update],
            self.namespace + "_operation_complete":[self.delete_operation],
            self.namespace + "_ping":[self.pong]
        }
        self.event_subscriber= EventSubscriber(callbacks, namespace=self.namespace)
        self.event_subscriber.start()

    def pong(self,event):
        log.info("Reporting info to namespace...")
        terminal_id = self.config.get("gtk", "terminal_id", "")
        self.server.pong({"ip_address": self.server.clientIPAddress() ,"terminal_id": terminal_id,"version": " ".join((sndcs_common.NAME, sndcs_common.VERSION))})

    def calculateClockOffset(self, event):
        """
        Calculates the difference between the time on the server and the time
        on the client. Event will contain the timestamp from the server.
        """
        self.clock_offset = event.time - time.time()
        
    def updateStatusBarClock(self):
        clock_format = self.config.get("gtk", "clock_format", "%I:%M %p")
        if clock_format:
            statusbar_main = self.xml.get_widget("statusbar_main")
            terminal_id = self.config.get("gtk", "terminal_id", "")
            context_id = statusbar_main.get_context_id("statusbar_main_clock")
            local_time = time.localtime(time.time() + self.clock_offset)
            formatted_local_time = time.strftime(clock_format, local_time)
            # Pop off the previous time
            statusbar_main.pop(context_id)
            # Push the new time onto the statusbar along with the terminal_id (if exists)
            if terminal_id:
                formatted_local_time = " ".join((formatted_local_time, "[%s]" % (terminal_id)))
            statusbar_main.push(context_id, formatted_local_time)

    def stopEventSubscriber(self):
        print "Stopping the Event Subscriber..."
        self.event_subscriber.abort()

    def flatline(self, exception = None, restart = False):
        self.xml.get_widget("splash_screen").hide() # Just in case the splash sceen is visible and covering up an error message
        if restart:
            log.error("Flatlined...restart event service and updating treeviews....")
            try:
                self.refresh()
            except Exception,exception:
                message = "An error has occured.\nThe application will now terminate."
                if exception:
                    message = message + "\n\nError: " + str(exception)
                self.alertBox("ERROR", "OK", message)
                gtk.main_quit() # TODO: This will occasionally throw an "RuntimeError: called outside of a mainloop" exception
        else:
            message = "An error has occured.\nThe application will now terminate."
            if exception:
                message = message + "\n\nError: " + str(exception)
            self.alertBox("ERROR", "OK", message)
            gtk.main_quit() # TODO: This will occasionally throw an "RuntimeError: called outside of a mainloop" exception

    def handle_flatline(self, exception = None):
        gobject.idle_add(self.flatline, exception)

    def startHeartbeatMonitor(self):
        from sndcs_client.HeartbeatMonitor import HeartbeatMonitor
        self.heartbeat_monitor = HeartbeatMonitor(self.handle_flatline)
        self.heartbeat_monitor.start()

    def stopHeartbeatMonitor(self):
        print "Stopping Heartbeat Monitor..."
        self.heartbeat_monitor.quit = True

    def clear_search_criteria(self):
        for x in [  self.treeview_available_activities,  self.treeview_start_setup, self.treeview_start_gang_available,  self.treeview_start_gang_current, self.treeview_start_gang_current,   self.treeview_indirect_activity_codes,  self.treeview_active_employees ]:
            x.column_number = 0
            x.search_criteria = ""
            x.boolean_only = False

    # Search as you type idea from Gourmet
        
    def on_rlistLookForIn_changed(self,widget):
        self.name=widget.get_model()[widget.get_active()][0]

        # Emit on_searchCriteria_change to update to new results
        self.xml.get_widget("searchCriteria").emit("changed")

    def on_searchCriteria_changed(self,widget): 
        notebook=self.xml.get_widget("notebook_main")
        if notebook.get_current_page()==0:
                obj=self.treeview_active_employees 
                obj.column_number=getColumnNumberForName(obj.view,self.name) + 2
                obj.search_criteria=widget.get_text()
                obj.update()

        elif notebook.get_current_page()==1:
                notebook=self.xml.get_widget("notebook_selected_employee")
                page_num=notebook.get_current_page()
                
                if page_num==1:
                    obj=self.treeview_available_activities
                    obj.column_number=getColumnNumberForName(obj.view,self.name) + 1
                    if self.name == "Priority":
                        obj.boolean_only = True
                    else:
                        obj.boolean_only = False
                    obj.search_criteria=widget.get_text()
                    obj.update(event=False)
                elif page_num==2: 
                    obj=self.treeview_start_gang_available
                    obj.column_number=getColumnNumberForName(obj.view,self.name) + 1
                    if self.name == "Priority":
                        obj.boolean_only = True
                    else:
                        obj.boolean_only = False
                    obj.search_criteria=widget.get_text()
                    obj.update(event=False)
                elif page_num==3:
                    obj=self.treeview_start_setup
                    obj.column_number=getColumnNumberForName(obj.view,self.name) + 1
                    obj.search_criteria=widget.get_text()
                    obj.update(event=False)
                elif page_num==4:
                    obj=self.treeview_indirect_activity_codes
                    obj.column_number=getColumnNumberForName(obj.view,self.name) + 1
                    obj.search_criteria=widget.get_text()
                    obj.update(event=False)

    def on_rlistLookForIn_admin_changed(self,widget):
        self.admin_name=widget.get_model()[widget.get_active()][0]
        
        # Emit on_serarchCiteria_admin_change to update to new results
        self.xml.get_widget("searchCriteria_admin").emit("changed")

    def on_searchCriteria_admin_changed(self,widget): 
        notebook=self.xml.get_widget("notebook_admin")
        page_num=notebook.get_current_page()
        if page_num==0:
                obj=self.treeview_job_admin
                obj.column_number=getColumnNumberForName(obj.view,self.admin_name) + 1
                obj.search_criteria=widget.get_text()
        elif page_num==2:
                obj=self.employee_admin_tree_view
                obj.column_number=getColumnNumberForName(obj.view,self.admin_name) + 1
                obj.search_criteria=widget.get_text()
        else:
                return
        obj.update()

    # If OSD keyboard is enable then the searchdialog is hide/show, but if not the whole toolbar is hidden/shown
    def showSearchDialog(self,window=""):
            if self.true(self.config.get("gtk", "search_dialog", True)):
                if self.true(self.config.get("gtk", "osd_keyboard", False)):
                        self.xml.get_widget("searchdialog%s" % window).show()
                else:
                        self.xml.get_widget("keyboard_toolbar%s" % window).show()


    # window can be  "" or "_admin"
    def hideSearchDialog(self,window=""):
            if self.true(self.config.get("gtk", "search_dialog", True)):
                if self.true(self.config.get("gtk", "osd_keyboard")):
                        self.xml.get_widget("searchdialog%s" % window).hide()
                else:
                        self.xml.get_widget("keyboard_toolbar%s" % window).hide()
    
    def convertAlphaToNum(self,input,alphaToKeysyms=False):
            numbers = { "one" : 0x31, "two" : 0x32, "three" : 0x33, "four" : 0x34, "five" : 0x35, "six" : 0x36, "seven" : 0x37, "eight" : 0x38, "nine" : 0x39, "zero" : 0x30 }

            reverse_numbers = { 0x31: 1, 0x32: 2, 0x33: 3, 0x34: 4 , 0x35 : 5,  0x36 : 6, 0x37 : 7, 0x38 : 8, 0x39 : 9, 0x30 : 0 }
            try:
                if not alphaToKeysyms:
                    return numbers[input]
                else:
                    for key,value in reverse_numbers.items():
                        if key == input: return value
            except KeyError:
                return input


    def caseShift(self,widget,whichkey):
            if str(widget.name).count("capslock"):
                self.capslock = widget.get_active() == False
                for x in [ "capslock", "capslock_admin" ]:
                    if not x == widget.name: self.xml.get_widget(x).set_active( widget.get_active() == False)

            if str(widget.name).count("shiftkey"):
                self.shiftkey = widget.get_active() == False
                for x in [ "shiftkey", "shiftkey_admin" ]:
                    if not x == widget.name: self.xml.get_widget(x).set_active( widget.get_active() == False)
            
    def toggleKeyboard(self,widget):
            for obj in [ self.xml.get_widget("vbox_keyboard"), self.xml.get_widget("vbox_keyboard2") ]:
                if widget.get_active():
                       self.xml.get_widget("osdbutton").set_active(True)
                       self.xml.get_widget("osdbutton2").set_active(True)
                       obj.show()
                else:
                       self.xml.get_widget("osdbutton").set_active(False)
                       self.xml.get_widget("osdbutton2").set_active(False)
                       obj.hide()

    def keystrokes(self,widget,currentkey):
        emitto = currentkey[1]
        currentkey = currentkey[0]
        
        if type(currentkey) == list:
            if self.shiftkey or self.capslock:
                currentkey = currentkey[0]
            else:
                currentkey = currentkey[1]
        
        if self.shiftkey or self.capslock:
            if self.shiftkey: 
                shiftkey = self.xml.get_widget("shiftkey")
                self.caseShift(shiftkey,"shiftkey")
                shiftkey.set_active(False)
            if isinstance(currentkey, str):
                keyval = gtk.gdk.keyval_from_name(currentkey)
            else:
                keyval = currentkey
            currentkey = gtk.gdk.keyval_to_upper(keyval)
            state = gtk.gdk.SHIFT_MASK
        else:
            state = 0
       
        if gtk.keysyms.__dict__.has_key(currentkey) :
            self.emitKey(eval("gtk.keysyms.%s" % currentkey), emitto, state) #Test is this a real letter/number or keysym
        else:
            self.emitKey(currentkey, emitto, state)
    
    def emitKey(self, emitthis, emitto="window_main", state=0):
        widget = self.xml.get_widget(emitto)

        try:
            string = chr(emitthis)
        except:
            string = ""
            
        event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
        event.keyval = int(emitthis)
        event.window = widget.window
        event.time = 0
        event.state = state

        # Probably a better way to do this but Tab and BackSpace don't seem to work unless we set the hardware_keycode
        if event.keyval == gtk.keysyms.Tab:
            event.hardware_keycode = 23
        elif event.keyval == gtk.keysyms.BackSpace:
            event.hardware_keycode = 22
        elif event.keyval == gtk.keysyms.space: # OSD keyboard space bar won't select complete check box on end activity screen w/out this
            event.hardware_keycode = 65
        
        widget.emit("key_press_event",event)

    def populateTreeViewAvailableActivities(self, model=None):
        print "Loading Available Activity Data..."
        try:
            model = self.treeview_available_activities.populate(model)
        except (Pyro.errors.ConnectionClosedError, Pyro.errors.ProtocolError, Pyro.errors.PyroError, Pyro.errors.NamingError), e:
            self.flatline(e)
        return model

    def populateTreeViewStartSetup(self, model=None):
        print "Loading Available Setup Data..."
        try:
            model = self.treeview_start_setup.populate(model)
        except (Pyro.errors.ConnectionClosedError, Pyro.errors.ProtocolError, Pyro.errors.PyroError, Pyro.errors.NamingError), e:
            self.flatline(e)
        return model

    def populateTreeViewStartGangAvailable(self, model=None):
        print "Loading Available Gang Data..."
        return self.treeview_start_gang_available.populate(model)

    def populateTreeViewIndirectActivityCodes(self, model=None):
        print "Loading Indirect Activities..."
        return self.treeview_indirect_activity_codes.populate(model)

    def populateTreeViewActiveEmployees(self, model=None):
        print "Loading Active Employee Data..."
        return self.treeview_active_employees.populate(model)

    def populateTreeViews(self):
        activity_model = self.populateTreeViewAvailableActivities()
        self.populateTreeViewStartSetup(model=activity_model)
        self.populateTreeViewStartGangAvailable(model=activity_model)
        self.populateTreeViewIndirectActivityCodes()
        self.populateTreeViewActiveEmployees()

    def populateCurrentActivitiesList(self, employee):
        self.treeview_current_activities.populate(employee)
   
    def switchPageByName(self, page_name):
        page_numbers = {"EmployeeList":(0,0), "SelectedEmployee":(1,0), "StartProductionActivity":(1,1), "StartJobGang":(1,2), "StartSetupActivity":(1,3), "StartIndirectActivity":(1,4), "AdditionalInformation":(1,5), "EndActivity":(1,6), "EndActivityDynamic":(1,7)}
        assert page_numbers.has_key(page_name)
        notebook_main = self.xml.get_widget("notebook_main")
        notebook_selected_employee = self.xml.get_widget("notebook_selected_employee")
        if page_name == "EmployeeList" and self.getCurrentPage() == "EmployeeList":
            pass
        else:
            notebook_selected_employee.set_current_page(page_numbers[page_name][1])
            notebook_main.set_current_page(page_numbers[page_name][0])
        # Place focus in first widget on end activity screen
        # Doing this here because setting the focus after building the end
        # activity screen, then calling switchPageByName to display would
        # change the focus to one of the buttons
        if page_name == "EndActivityDynamic":
            focus_me = None
            if self.end_activities_dynamic_widgets:
                focus_me = self.end_activities_dynamic_widgets[0]["activity_percentage"]
            elif self.continue_activities_dynamic_widgets:
                focus_me = self.continue_activities_dynamic_widgets[0]["activity_percentage"]
            if focus_me:
                focus_me.grab_focus()

    def getCurrentPage(self):
        page_numbers = {(0,0):"EmployeeList", (1,0):"SelectedEmployee", (1,1):"StartProductionActivity", (1,2):"StartJobGang", (1,3):"StartSetupActivity", (1,4):"StartIndirectActivity", (1,5):"AdditionalInformation", (1,6):"EndActivity", (1,7):"EndActivityDynamic"}
        notebook_main = self.xml.get_widget("notebook_main")
        notebook_selected_employee = self.xml.get_widget("notebook_selected_employee")
        return page_numbers[(notebook_main.get_current_page()), (notebook_selected_employee.get_current_page())]

    def getEmployeeProxy(self, employee_number = None, employee_serial_num = None):
        assert (employee_number or employee_serial_num) and not (employee_number and employee_serial_num) # One or the other.... not both
        try:
            proxy_factory = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["Employee"]))
        except Exception, e:
            self.flatline(e)
        
        try:
            if employee_serial_num:
                proxy = proxy_factory.setEmployeeBySerialNum(employee_serial_num)
            else:
                proxy = proxy_factory.setEmployeeByEmpId(employee_number)
        except SndcsEmployeeError:
            if employee_serial_num:
                self.alertBox("WARNING", "OK", "'%s' is an invalid employee serial number. Please try again." % (employee_serial_num))
            else:
                self.alertBox("WARNING", "OK", "'%s' is an invalid employee number. Please try again." % (employee_number))
            return
        except Pyro.errors.ConnectionClosedError, e:
            self.flatline(e)

        return proxy

    def selectEmployee(self, employee_id = None, employee_proxy = None):
        """
        If employee_id is passed it will get the proxy object. Otherwise pass in an existing proxy object.
        Returns True if employee clocked in or False if they just selected themselves (or if PIN pad displayed).
        """

        assert (employee_id or employee_proxy) and not (employee_id and employee_proxy) # One or the other.... not both

        if employee_proxy:
            employee = employee_proxy
            employee_id = int(employee.serialNum())
        else:
            employee = self.getEmployeeProxy(employee_serial_num = employee_id)
    
        if not employee.isClockedIn():
            if employee.pinNumber():
                self.employee = employee
                self.showPinPad()
                return False
        
            terminal_id = self.config.get("gtk", "terminal_id", "")
            employee.clockIn(terminal_id = terminal_id)
            self.clearAndResetGui()
            return True
        else:
            lbl_emp_name = self.xml.get_widget("lbl_selected_employee_name")
            lbl_emp_name.set_text("Employee Name: %s" % employee.properName())
            lbl_emp_id = self.xml.get_widget("lbl_selected_employee_id")
            lbl_emp_id.set_text("Employee ID: %s" % employee.empId())
            self.populateCurrentActivitiesList(employee_id)
            self.switchPageByName("SelectedEmployee")
            if self.currently_selected_employee:
                self.currently_selected_employee.disconnect()
            self.currently_selected_employee = employee

            pictureDataMD5=employee.picture_md5sum()
            
            if os.name == "posix":
                tmppath = "/tmp/"
            else: #Windows boxen
                tmppath = sys.prefix + "\\tmp\\"
                if not os.path.exists(tmppath):
                    os.mkdir(tmppath)
        
            def checkCachedVersion(file,md5sum):
                import md5
                f=open(file,"rb")
                file_md5sum=md5.new(f.read()).hexdigest() 
                log.debug("Found employee picture at %s with MD5 of [%s]" % (file, file_md5sum) )
                return file_md5sum == md5sum

            if not pictureDataMD5 is None:
                log.debug("Got MD5 of employee picture: [%s]" % pictureDataMD5)
                if not os.path.exists("%spicture%s.jpg" % ( tmppath,employee.empId() ) ) or not checkCachedVersion( ( "%spicture%s.jpg" % ( tmppath,employee.empId() ) ), pictureDataMD5 ):
                    
                    pictureData=employee.picture()
                    f=open("%spicture%s.jpg" % ( tmppath,employee.empId() ),"wb")
                    f.write(pictureData)
                    f.close()

                    log.debug("Saving employee picture to [%s]" % ("%spicture%s.jpg" % ( tmppath,employee.empId() ) ) )
   
                pixbuf = gtk.gdk.PixbufAnimation("%spicture%s.jpg" % (tmppath,employee.empId()) )
                if pixbuf.is_static_image(): # Non-animated image then we can scale.
                    pixbuf = gtk.gdk.pixbuf_new_from_file("%spicture%s.jpg" % (tmppath,employee.empId()) )
                    pixbuf = pixbuf.scale_simple(150,150,gtk.gdk.INTERP_BILINEAR)
                    self.employee_image.set_from_pixbuf(pixbuf)
                else:
                    self.employee_image.set_from_animation(pixbuf) # Set animation.
            else:
                self.employee_image.set_from_image(None,None)

            if self.true(self.config.get("gtk", "department_jobs", False)):
                self.treeview_available_activities.update( departments=self.currently_selected_employee.departments() )
                self.treeview_start_gang_available.update( departments=self.currently_selected_employee.departments() )
                self.treeview_start_setup.update( departments=self.currently_selected_employee.departments() )
                
            if employee.isClockedIn() and employee.isSuspended():
                # Display resume button and hide everything else
                self.hideButtons(False)
            else: self.hideButtons()

            return False

    
    def hideButtons(self,hide=True):
        if hide:
            self.xml.get_widget("bttn_resume_tasks").set_sensitive(False)
        else:
            self.xml.get_widget("bttn_resume_tasks").set_sensitive(True)

        widgets = [
            "recent_activities_treeview",
            "bttn_clock_out",
            "button_additional_information",
            "bttn_start_production_activity",
            "bttn_start_job_gang",
            "bttn_start_setup_gang",
            "bttn_start_setup_activity",
            "bttn_start_setup_gang",
            "bttn_start_indirect_activity",
            "bttn_end_activity",
            "bttn_lunch_in_out",
            "bttn_break_in_out" ]

        for x in widgets:
            self.xml.get_widget(x).set_sensitive(hide)

    def resumeTasks(self,widget=False):
        terminal_id = self.config.get("gtk", "terminal_id", "")
        self.currently_selected_employee.stopAllActiveLaborDtls( terminal_id ) #Prevents "Indirect" ganging
        self.currently_selected_employee.resumeAllLaborDtls( terminal_id )
        self.clearAndResetGui()
            

    def startIndirect(self, indirect_code = None, indirect_serial_num = None):
        assert self.currently_selected_employee
        assert (indirect_code or indirect_serial_num) and not (indirect_code and indirect_serial_num)

        # If code is passed we need to get the serialNum
        if indirect_code:
            try:
                indirect_factory = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["Indirect"]))
            except Exception, e:
                self.flatline(e)

            try:
                indirect = indirect_factory.setIndirectByCode(indirect_code)
            except SndcsIndirectError:
                self.alertBox("WARNING", "OK", "'%s' is an invalid Indirect code." % (indirect_code))
                self.clearAndResetGui()
                return
            except Pyro.errors.ConnectionClosedError, e:
                self.flatline(e)

            indirect_id = indirect.serialNum()
            indirect.disconnect()
        else:
            indirect_id = indirect_serial_num


        # Check if this is LUNCH or BREAK
        if indirect_code == "LUNCH":
            terminal_id = self.config.get("gtk", "terminal_id", "")
            try:
                self.currently_selected_employee.log_on_off_lunch_break(type=1, terminal_id = terminal_id)
            except Pyro.errors.ConnectionClosedError, e:
                self.flatline(e)
            self.clearAndResetGui()
        elif indirect_code == "BREAK":
            terminal_id = self.config.get("gtk", "terminal_id", "")
            try:
                self.currently_selected_employee.log_on_off_lunch_break(type=2, terminal_id = terminal_id)
            except Pyro.errors.ConnectionClosedError, e:
                self.flatline(e)
            self.clearAndResetGui()
        else:
            # Check if the user is ganged. If so, we need to run the end_activity_dynamic screen and then start the indirect
            try:
                is_ganged = self.currently_selected_employee.isGanged()
            except Pyro.errors.ConnectionClosedError, e:
                self.flatline(e)
            if is_ganged:
                self.createEndActivityDynamic(indirect_id = indirect_id)
            else:
                terminal_id = self.config.get("gtk", "terminal_id", False)
                try:
                    self.currently_selected_employee.startIndirect(indirect_id, terminal_id = terminal_id)
                except SndcsIndirectError:
                    self.alertBox("INFO", "OK", "Invalid indirect activity. Please try again.")
                except Pyro.errors.ConnectionClosedError, e:
                    self.flatline(e)
                self.clearAndResetGui()

    def alertBox(self, type, buttons, message):
        if type == "INFO":
            message_type = gtk.MESSAGE_INFO
        elif type == "WARNING":
            message_type = gtk.MESSAGE_WARNING
        elif type == "QUESTION":
            message_type = gtk.MESSAGE_QUESTION
        elif type in ["NON_FATAL_ERROR", "ERROR"]:
            message_type = gtk.MESSAGE_ERROR
        else:
            raise ValueError("Invalid message type '%s'." % (type))

        if buttons == "NONE":
            button_type = gtk.BUTTONS_NONE
        elif buttons == "OK":
            button_type = gtk.BUTTONS_OK
        elif buttons == "CLOSE":
            button_type = gtk.BUTTONS_CLOSE
        elif buttons == "CANCEL":
            button_type = gtk.BUTTONS_CANCEL
        elif buttons == "YES_NO":
            button_type = gtk.BUTTONS_YES_NO
        elif buttons == "OK_CANCEL":
            button_type = gtk.BUTTONS_OK_CANCEL
        else:
            raise ValueError("Invalid button type '%s'." % (buttons))

        #TODO: log to file

        window_main = self.xml.get_widget("window_main")
        dialog = gtk.MessageDialog(window_main, gtk.DIALOG_DESTROY_WITH_PARENT, message_type, button_type, message)
        resp = dialog.run()
        dialog.destroy()

        return resp

    def toggleAvailableToCurrentTreeViewRows(self, source_treeview, dest_treeview, paths_to_move):
        source_model = source_treeview.model
        dest_model = dest_treeview.model
        rowrefs = []
        for path in paths_to_move:
            rowrefs.append(gtk.TreeRowReference(source_model, path))
        for ref in rowrefs:
            path = ref.get_path()
            iter = source_model.get_iter(path)
            row = source_model[iter]

            dest_treeview.model.append(row)
            
            source_treeview.model.remove(iter)
            
        
    def autoToggleAvailableToCurrentTreeViewRows(self, treeview1, treeview2, data_to_move):
        """ Just like toggleAvailableToCurrentTreeViewRows but it will automatically figure out which treeview to move from <--> to """
        from_list = None
        to_list = None
        iter = self.find_liststore_iter_from_data(treeview1.model, data_to_move)
        if iter:
            from_list = treeview1
            to_list = treeview2
        else:
            iter = self.find_liststore_iter_from_data(treeview2.model, data_to_move)
            if iter:
                from_list = treeview2
                to_list = treeview1
        if not iter:
            self.alertBox("WARNING", "OK", "Could not toggle the job. The operation may be completed.")
            return
        if from_list and to_list:
            path = from_list.model.get_path(iter)
            self.toggleAvailableToCurrentTreeViewRows(from_list, to_list, (path,))
        
    def clearAndResetGui(self):
        if self.currently_selected_employee:
            try:
                self.currently_selected_employee.disconnect()
            except Pyro.errors.ProtocolError: pass #Already Disconnected
        self.currently_selected_employee = None
        self.currently_selected_start_gang_current_activity = None
        self.barcode_command = ""
        self.job_gang_type = ""
        self.continue_activities_dynamic = []
        self.end_activities_dynamic = []
        self.start_activities_dynamic = []
        self.start_indirect_dynamic = None
        self.continue_activities_dynamic_widgets = []
        self.end_activities_dynamic_widgets = []
        self.start_activities_dynamic_widgets = []
        self.switchPageByName("EmployeeList")
        self.employee_image.set_from_image(None,None)
        self.hideButtons(True) #Set the buttons to be "sensitive" again 
        if self.true(self.config.get("gtk", "department_jobs", False)):
            self.treeview_available_activities.update() #In case the available activities were changed.
            self.treeview_start_gang_available.update()
            self.treeview_start_setup.update()


    def createEndActivityDynamic(self, start = None, end = None, indirect_id = None):
        """
         This function will handle everything needed to run the 
         dynamic end activity screen. You can pass it either an array
         of operation id's to start or end (but not both).
         If both are None all current jobs will just be closed. This
         would be used in situations like clock out. Here's the logic...
         
         We can always get the employees current activities from the
         database so all we need to know is what jobs we are stopping
         or what jobs we are continuing/starting. If we know what jobs
         we are 'stopping' we can safely assuming that we are continuing
         everything else (if there is anything else). If we know what 
         jobs we are 'continuing/starting' then anything we are currently
         logged into that *is not* in that list we are stopping. Anything
         that we are logged into that *is* in that list we are continuing.
         And anything in that list that we are not currently logged onto
         is a new item.
         
         The reason that we need the option to only pass items to end
         is that we may only know the jobnum/asm/op of somthing that we
         want to end and may not necessarily easily know every other
         job that we are working on and want to continue on.
         I'll shut up now.
        """
        assert self.currently_selected_employee
        assert not (start and end)

        # Clean up everything from last time
        self.destroyEndActivityScreen()

        if start is None and end is None: # stop everything and start the indirect activity
            end_activities = []

            try:
                activities = self.currently_selected_employee.getActivities()
            except Pyro.errors.ConnectionClosedError, e:
                self.flatline(e)

            try:
                dtl_factory = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["LaborDtl"]))
            except Exception, e:
                self.flatline(e)

            for x in activities:
                if x["labordtl_id"]:
                    try:
                        dtl = dtl_factory.setLaborDtlBySerialNum(x["labordtl_id"])
                    except SndcsLaborDtlError:
                        self.alertBox("WARNING", "OK", "'%s' is an invalid LaborDtl ID." % (x["labordtl_id"]))
                        raise
                    end_activities.append(dtl.getDataDict())
                    dtl.disconnect()

            try:
                indirect_factory = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["Indirect"]))
            except Exception, e:
                self.flatline(e)

            try:
                indirect = indirect_factory.setIndirectBySerialNum(indirect_id)
            except SndcsIndirectError:
                self.alertBox("WARNING", "OK", "'%s' is an invalid Indirect ID." % (indirect_id))
                raise
            except Pyro.errors.ConnectionClosedError, e:
                self.flatline(e)
                
            self.end_activities_dynamic = end_activities

            try:
                self.start_indirect_dynamic = indirect.getDataDict()
                indirect.disconnect()
            except Pyro.errors.ConnectionClosedError, e:
                self.flatline(e)

        #current_activities = self.currently_selected_employee.getActivities()

        terminal_id = self.config.get("gtk", "terminal_id", False)

        # Find out if we are on any production activities currently
        #prod_activities = [x for x in current_activities if x["type"] == "production"]
        try:
            prod_activities = self.currently_selected_employee.getActiveProductionAndSetupLaborDetails()
        except Pyro.errors.ConnectionClosedError, e:
            self.flatline(e)

        if start is not None:
            # If not working on any production activities, just end everything 
            # (in case we were on an indirect) and start everything passed
            if not prod_activities:
                to_start = []
                for x in start:
                    to_start.append({"operation":x, "indirect":None, "job_type":self.job_gang_type})
                try:
                    self.currently_selected_employee.endAllLaborDtl(terminal_id = terminal_id)
                except Pyro.errors.ConnectionClosedError, e:
                    self.flatline(e)
                try:
                    self.currently_selected_employee.startStopContinueLaborDtls(starts = to_start, terminal_id = terminal_id, jobType = self.job_gang_type)
                except Pyro.errors.ConnectionClosedError, e:
                    self.flatline(e)
                self.clearAndResetGui()
                return

            # First we will see if we are stopping or continuing
            for x in prod_activities:
                found_match = 0
                for y in start:
                    if x["operation_id"] == y:
                        found_match = 1
                        break
                if found_match:
                    # We are continuing
                    self.continue_activities_dynamic.append(x)
                else:
                    # We are stopping
                    self.end_activities_dynamic.append(x)

            # Now we will see what we are starting fresh
            for x in start:
                found_match = 0
                for y in prod_activities:
                    if x == y["operation_id"]:
                        found_match = 1
                        break
                if found_match:
                    # We are continuing... we already took care of this... skip
                    continue
                else:
                    # We are starting new
                    self.start_activities_dynamic.append(x)

        elif end is not None:
            # See if we are stopping or continuing
            for x in prod_activities:
                found_match = 0
                for y in end:
                    if x["operation_id"] == y:
                        found_match = 1
                        break
                if found_match:
                    # We are ending
                    self.end_activities_dynamic.append(x)
                else:
                    # We are continuing
                    self.continue_activities_dynamic.append(x)


        #If we are only continuing a single activity and not starting or stopping others than it means that the user selected the same job they were already on... and that really means they want to end it. (Probably only got here if force_end_activity=True)
        if len(self.continue_activities_dynamic) == 1 and not self.end_activities_dynamic and not self.start_activities_dynamic:
            if self.true(self.config.get("gtk", "force_end_activity", False)):
                self.end_activities_dynamic = self.continue_activities_dynamic
                self.continue_activities_dynamic = []
                self.createEndActivityScreen()
                self.switchPageByName("EndActivityDynamic")
                return
            else:
                terminal_id = self.config.get("gtk", "terminal_id", "")
                operation_id = self.continue_activities_dynamic[0]
                description = self.continue_activities_dynamic[1]
                try:
                    self.currently_selected_employee.startProduction(operation_id, terminal_id = terminal_id)
                    self.addCachedJob(operation_id,description)
                except Pyro.errors.ConnectionClosedError, e:
                    self.flatline(e)
                self.clearAndResetGui()
                return

        #If we are only continuing a single activity (and starting others) then there is no need to display the end_activity screen because there is nothing the user could change about it (has to be 100%)
        if not self.end_activities_dynamic and len(self.continue_activities_dynamic) == 1:
            to_continue = []
            to_start = []
            to_continue.append({"labordtl":self.continue_activities_dynamic[0], "activity_percentage":100.0})
            for x in self.start_activities_dynamic:
                to_start.append({"operation":x, "indirect":None, "job_type":self.job_gang_type})
            try:
                self.addListToCachedJobs( to_start )
                self.currently_selected_employee.startStopContinueLaborDtls(starts = to_start, continues = to_continue, terminal_id = terminal_id, jobType = self.job_gang_type)
            except Pyro.errors.ConnectionClosedError, e:
                self.flatline(e)
            self.clearAndResetGui()
            return
        else:
            self.createEndActivityScreen()
            self.switchPageByName("EndActivityDynamic")

    def endActivityDynamicFocusIn(self, widget, event, vadj, hadj):
        # TODO: for some reason this doesn't work when first taken to the end
        # activity screen. The alloc.x and alloc.y come through as -1 for the
        # activity percentage on the very first focus in. Can switch to a
        # window and switch back and it will scroll (if the activty percentage
        # widget is off screen) or you can tab the first time and it will
        # scroll but not the very first time the end activity screen is
        # displayed. The focus event must occur before all ofthe layout is done
        # or something and I'm not sure how to fix it.
        alloc = widget.get_allocation()
        if alloc.y < vadj.value or alloc.y > vadj.value + vadj.page_size:
            vadj.set_value(min(alloc.y, vadj.upper-vadj.page_size))
        if alloc.x < hadj.value or alloc.x > hadj.value + hadj.page_size:
            hadj.set_value(min(alloc.x, hadj.upper-hadj.page_size))

    def createEndActivityScreen(self):
        scrap_codes = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["ScrapCode"]))

        hbox_main = self.xml.get_widget("hbox_end_activity_dynamic")
        self.frame_ending = gtk.Frame("Ending / Continuing")
        self.frame_starting = gtk.Frame("Starting")
        options_x = gtk.EXPAND | gtk.SHRINK
        options_y = gtk.SHRINK | gtk.FILL
        hbox_main.pack_start(self.frame_ending, True, True, 0)
        hbox_main.pack_start(self.frame_starting, True, True, 0)

        table = gtk.Table(1, 6, False)
        table.set_col_spacings(6)
        self.frame_ending.add(table)
        table.show()

        column_headings = ["<b><u>Activity</u></b>", "<b><u>%</u></b>", "<b><u>Curr Comp</u></b>", "<b><u>Qty Comp</u></b>" ]
        self.job_entry = [ 0, [] ]
        
        if self.true(self.config.get("gtk","scrap_codes", False)):
            for x in scrap_codes.getScrapCodes():
                column_headings.extend(["<b><u>" + x["description"] + "</u></b>"])
        else: 
            column_headings.extend(["<b><u>Scrap</u></b>"]) 


        if self.true(self.config.get("gtk","show_notes", False)):
            column_headings.extend(["<b><u>Notes</u></b>"])

        column_headings.extend(["<b><u>Comp?</u></b>"])
        
        i = 0
        for heading in column_headings:
            label = gtk.Label()
            label.set_markup(heading)
            table.attach(label, i, i+1, 0, 1, options_x, options_y, 0, 5)
            label.show()
            i += 1

        scrolled_window = self.xml.get_widget("scrolledwindow_end_activity_dynamic")
        vadj = scrolled_window.get_vadjustment()
        hadj = scrolled_window.get_hadjustment()
        
        # Add the rows for all ending activities
        j = 1

        for x in self.end_activities_dynamic:
            row_of_widgets = {}

            activity_desc = gtk.Label(x["formattedDescription"])
            self.attachHighlightToWidget(activity_desc)

            table.attach(activity_desc, 0, 1, j, j+1, gtk.FILL, options_y, 3, 0)
            activity_desc.set_alignment(0, 0.5)
            activity_desc.show()

            row_of_widgets["activity_desc"] = activity_desc

            activity_percentage_adjustment = gtk.Adjustment(0.0, 0.0, 100.0, 0.1, 0.1, 100.0)
            activity_percentage = gtk.SpinButton(activity_percentage_adjustment, 0.1, 1)
            activity_percentage.connect('focus_in_event', self.endActivityDynamicFocusIn, vadj, hadj)

            table.attach(activity_percentage, 1, 2, j, j+1, options_x, options_y, 0, 0)
            activity_percentage.show()
            self.attachHighlightToWidget(activity_percentage)
            row_of_widgets["activity_percentage"] = activity_percentage

            curr_complete = gtk.Label(x["currentComplete"])
            table.attach(curr_complete, 2, 3, j, j+1, gtk.FILL, options_y, 3, 0)
            curr_complete.set_alignment(0.5, 0.5)
            curr_complete.show()

            qty_complete = gtk.Entry()
            qty_complete.connect('focus_in_event', self.endActivityDynamicFocusIn, vadj, hadj)
            self.attachHighlightToWidget(qty_complete)
            qty_complete.set_size_request(50, -1)
            desc = x["unitOfMeasure"]
            if desc is None:
                desc = ""
            uofm_desc = gtk.Label(desc)
            hbox = gtk.HBox(spacing=3)
            hbox.pack_start(qty_complete)
            hbox.pack_start(uofm_desc)
            table.attach(hbox, 3, 4, j, j+1, options_x, options_y, 5, 0)
            qty_complete.show()
            uofm_desc.show()
            hbox.show()
            row_of_widgets["qty_complete"] = qty_complete

            y = 4
            if not self.true(self.config.get("gtk","scrap_codes", False)):

                scrap = gtk.Entry()
                scrap.connect('focus_in_event', self.endActivityDynamicFocusIn, vadj, hadj)
                self.attachHighlightToWidget(scrap)
                scrap.set_size_request(50, -1)
                table.attach(scrap, y, y+1, j, j+1, options_x, options_y, 5, 0)
                scrap.show()
                row_of_widgets["scrap"] = scrap
                y += 1
            else:
                for x in  scrap_codes.getScrapCodes():
                    scrap = gtk.Entry()
                    scrap.connect('focus_in_event', self.endActivityDynamicFocusIn, vadj, hadj)
                    scrap.set_size_request(50,-1)
                    self.attachHighlightToWidget(scrap)
                    table.attach(scrap, y, y+1, j, j+1, options_x, options_y, 5, 0)
                    scrap.show()
                    row_of_widgets["scrap_code%s" % x["code"]] = scrap
                    y += 1

            if self.true(self.config.get("gtk","show_notes", True)):
                notes = gtk.Entry()
                notes.connect('focus_in_event', self.endActivityDynamicFocusIn, vadj, hadj)
                self.attachHighlightToWidget(notes)
                table.attach(notes, y, y+1, j, j+1, options_x, options_y, 5, 0)
                notes.show()
                row_of_widgets["notes"] = notes
                y+=1 

            operation_complete = gtk.CheckButton()
            operation_complete.connect('focus_in_event', self.endActivityDynamicFocusIn, vadj, hadj)
            if x["operationComplete"]:
                operation_complete.set_active(True)
            self.attachHighlightToWidget(operation_complete)
            table.attach(operation_complete, y, y+1, j, j+1, options_x, options_y, 0, 0)
            operation_complete.show()
            row_of_widgets["operation_complete"] = operation_complete

            self.end_activities_dynamic_widgets.append(row_of_widgets)

            j += 1

        # Add the rows for all continuing activities
        for x in self.continue_activities_dynamic:
            row_of_widgets = {}

            activity_desc = gtk.Label(x["formattedDescription"])

            table.attach(activity_desc, 0, 1, j, j+1, gtk.FILL, options_y, 3, 0)
            activity_desc.set_alignment(0, 0)
            activity_desc.show()
            row_of_widgets["activity_desc"] = activity_desc

            activity_percentage_adjustment = gtk.Adjustment(0.0, 0.0, 100.0, 0.1, 0.1, 100.0)
            activity_percentage = gtk.SpinButton(activity_percentage_adjustment, 0.1, 1)
            activity_percentage.connect('focus_in_event', self.endActivityDynamicFocusIn, vadj, hadj)
            self.attachHighlightToWidget(activity_percentage)
            table.attach(activity_percentage, 1, 2, j, j+1, options_x, options_y, 0, 0)
            activity_percentage.show()
            row_of_widgets["activity_percentage"] = activity_percentage

            self.continue_activities_dynamic_widgets.append(row_of_widgets)

            j += 1

        # Calculate the average percentage and set the spin buttons
        end_and_continue_widgets = (self.end_activities_dynamic_widgets + self.continue_activities_dynamic_widgets)
        num = len(end_and_continue_widgets)
        if num:
            j = 0
            # Use the calculated percentage for every one except the last one
            # For the last one figure out what's left over
            #percentage = (math.floor(((100.0/num)*10)+0.5)) / 10
            #last_percentage = 100.0 -(percentage * (num - 1))
            percentage, last_percentage = self.calculateDefaultActivityPercentages(len(end_and_continue_widgets))
            for x in end_and_continue_widgets:
                if j == num - 1:
                    x["activity_percentage"].set_value(last_percentage)
                    self.attachHighlightToWidget(x["activity_percentage"])
                    break
                x["activity_percentage"].set_value(percentage)
                j += 1

        # Add the rows for all starting activities
        j = 1
        table_start = gtk.Table()
        self.frame_starting.add(table_start)
        table_start.show()

        if self.start_activities_dynamic:
            for x in self.start_activities_dynamic:
                row_of_widgets = {}

                try:
                    operation_factory = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["Operation"]))
                except Exception, e:
                    self.flatline(e)
                try:
                    operation = operation_factory.setOperationBySerialNum(x)
                except SndcsOperationError:
                    self.alertBox("WARNING", "OK", "'%s' is an invalid Operation ID." % (x))
                    raise


                activity_desc = gtk.Label(operation.formattedDescription())
                operation.disconnect()
                table_start.attach(activity_desc, 0, 1, j, j+1, gtk.FILL, options_y, 3, 3)
                activity_desc.set_alignment(0, 0)
                activity_desc.show()
                row_of_widgets["activity_desc"] = activity_desc

                self.start_activities_dynamic_widgets.append(row_of_widgets)

                j += 1
        elif self.start_indirect_dynamic:
                row_of_widgets = {}

                activity_desc = gtk.Label(self.start_indirect_dynamic["formattedDescription"])
                table_start.attach(activity_desc, 0, 1, j, j+1, gtk.FILL, options_y, 3, 0)
                activity_desc.set_alignment(0, 0)
                activity_desc.show()
                row_of_widgets["activity_desc"] = activity_desc

                self.start_activities_dynamic_widgets.append(row_of_widgets)
            
                j += 1
            
        self.frame_ending.show()
        self.frame_starting.show()
        hbox_main.show()


    def destroyEndActivityScreen(self):
        if self.frame_ending:
            self.frame_ending.destroy()
            self.frame_ending = None
        if self.frame_starting:
            self.frame_starting.destroy()
            self.frame_starting = None
        self.end_activities_dynamic = []
        self.start_activities_dynamic = []
        self.continue_activities_dynamic = []
        self.start_indirect_dynamic = None
        self.end_activities_dynamic_widgets = []
        self.start_activities_dynamic_widgets = []
        self.continue_activities_dynamic_widgets = []

    def validateEndActivityDynamic(self):
        #  TODO: Make sure the qty_complete and qty_scrap are valid numbers
        for x in self.end_activities_dynamic_widgets:
            qty_complete = x["qty_complete"].get_text().strip()
            try:
                qty_scrap = x["qty_scrap"].get_text().strip()
            except: qty_scrap = False
            if qty_complete:
                try:
                    float(qty_complete)
                except ValueError, e:
                    raise EndActivityQtyCompleteError(qty_complete)
            if qty_scrap:
                try:
                    float(qty_scrap)
                except ValueError, e:
                    raise EndActivityQtyScrapError(qty_scrap)

        # Make sure the sum of all percentages total 100%
        spin_total = 0.0
        for x in self.end_activities_dynamic_widgets:
            spin_total += x["activity_percentage"].get_value()
        for x in self.continue_activities_dynamic_widgets:
            spin_total += x["activity_percentage"].get_value()

        spin_total = int(math.floor((spin_total*10) + 0.5))
        if spin_total <> 1000:
            raise EndActivityPercentageError("Percentages total %.1f%%\nPlease adjust percentages until they total 100%% and try again." % (spin_total/10.0))

    def processEndActivityDynamic(self, terminal_id = None):
        stops = []
        scrap_codes = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["ScrapCode"]))

        for x in zip(self.end_activities_dynamic, self.end_activities_dynamic_widgets):
            scrap_dict = {}
            if self.true(self.config.get("gtk","scrap_codes", False)):
                scrap_count=0
                for z in scrap_codes.getScrapCodes():
                    try:
                        scrap_dict.update({"%s" % z["code"]: int( x[1]["scrap_code%s" % z["code"]].get_text() )}) 
                        scrap_count += int( x[1]["scrap_code%s" % z["code"]].get_text().strip() )
                    except ValueError: pass
            else:
                try:
                    scrap_count = int( x[1]["scrap"].get_text().strip() )
                except ValueError:
                    scrap_count = 0


            if self.true(self.config.get("gtk","show_notes", True)):
                notes = x[1]["notes"].get_text()
            else:
                notes = None
            
            if self.true(self.config.get("gtk","qty_complete_warning",False)):
                labor_factory = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["LaborDtl"]))
                labor = labor_factory.setLaborDtlBySerialNum(x[0]["labordtl_id"])
                labor_data = labor.getDataDict()
                labor.disconnect()

                try:
                    qty_complete = float(x[1]["qty_complete"].get_text().strip())
                except: qty_complete = 0

                if qty_complete and ( ( labor_data["jobhed_qty_required"] - labor_data["currentComplete"] ) < qty_complete ):
                    if self.alertBox(type = "QUESTION", buttons = "YES_NO", message = "You have entered more completed than required for '%s' is this correct?" % labor_data["formattedDescription"] ) == gtk.RESPONSE_NO:
                        return True
            
            stops.append({"labordtl":x[0], "activity_percentage":x[1]["activity_percentage"].get_value(), "qty_complete":x[1]["qty_complete"].get_text().strip(), "qty_scrap": scrap_count, "notes":notes, "operation_complete":x[1]["operation_complete"].get_active(), "completed_by":self.currently_selected_employee.serialNum(), "scrap_codes": scrap_dict })
        
        continues = []
        for x in zip(self.continue_activities_dynamic, self.continue_activities_dynamic_widgets):
            continues.append({"labordtl":x[0], "activity_percentage":x[1]["activity_percentage"].get_value()})
        starts = []
        if self.start_activities_dynamic:
            for x in self.start_activities_dynamic:
                starts.append({"operation":x, "indirect":None, "job_type":self.job_gang_type})
        elif self.start_indirect_dynamic:
            starts.append({"operation":None, "indirect":self.start_indirect_dynamic, "job_type":"Indirect"})
        try:
            self.currently_selected_employee.startStopContinueLaborDtls(starts = starts, stops = stops, continues = continues, terminal_id = terminal_id)
            self.addListToCachedJobs( starts )
        except Pyro.errors.ConnectionClosedError, e:
            self.flatline(e)

    def processBarcode(self):
        if not self.barcode_command:
            log.debug("Empty barcode command.")
            return

        log.debug("Processing barcode data: %s", self.barcode_command)

        self.set_busy_status()

        # Parse out the command and the data from the barcode
        separator_position = self.barcode_command.find(self.separator_signifier)
        if separator_position == -1:
            start_signifier = self.barcode_command[0]
            data = self.barcode_command[1:]
            command = None
            end_signifier = None
        else:
            start_signifier = self.barcode_command[0]
            data = self.barcode_command[1:separator_position]
            command = self.barcode_command[separator_position+1:]
            end_signifier = None
            if command:
                if command[-1] == start_signifier:
                    end_signifier = command[-1]
                    command = command[:-1]
            
        # Selection
        if start_signifier == self.main_selection_signifier:
            clock_in = False
            try:
                employee_id = int(data)
            except: # probably not an integer
                self.clear_busy_status()
                log.warning("'%s' is an invalid employee number.", data)
                self.alertBox("WARNING", "OK", "'%s' is an invalid employee number. Please try again." % (data))
                self.barcode_command = ""
                return

            employee = self.getEmployeeProxy(employee_number = employee_id)
            if not employee:
                self.barcode_command = ""
                self.clear_busy_status()
                return
            if command == self.clockin_and_select_selection_signifier:
                clock_in = self.selectEmployee(employee_proxy = employee)

            #if employee.isSuspended() and not command == self.resume_selection_signifier:
            #    self.clear_busy_status()
            #    self.barcode_command == ""
            #    self.alertBox("WARNING", "OK", "You must 'Resume' before any other action")
            #    self.clearAndResetGui()
            #    return

            elif command == self.lunch_selection_signifier:
                terminal_id = self.config.get("gtk", "terminal_id", "")
                try:
                    employee.log_on_off_lunch_break(1, terminal_id = terminal_id)
                except SndcsEmployeeError, e:
                    self.alertBox("WARNING", "OK", str(e))
                else:
                    self.clearAndResetGui()
            elif command == self.break_selection_signifier:
                terminal_id = self.config.get("gtk", "terminal_id", "")
                try:
                    employee.log_on_off_lunch_break(2, terminal_id = terminal_id)
                except SndcsEmployeeError, e:
                    self.alertBox("WARNING", "OK", str(e))
                else:
                    self.clearAndResetGui()
            elif command == self.resume_selection_signifier:
                try:
                    employee.resumeAllLaborDtls() # Resume LaborDtl's
                except SndcsEmployeeError, e:
                    self.alertBox("WARNING", "OK", str(e))
                else:
                    self.clearAndResetGui()

            elif command == self.clockout_selection_signifier:
                # Check if we are forcing the user to see the end activity screen
                if self.true(self.config.get("gtk", "force_end_activity", False)):
                    # If so, check if they are on any production activities and force them to end them if so
                    try:
                        num_production =  employee.getNumberActiveProductionLaborDetails()
                    except Pyro.errors.ConnectionClosedError, e:
                        self.clear_busy_status()
                        self.flatline(e)
                    if num_production:
                        self.alertBox("INFO", "OK", "You must end all production activities before clocking out.")
                        try:
                            stops = [x["operation_id"] for x in employee.getActiveProductionLaborDetails()] # Get list of activities to end
                        except Pyro.errors.ConnectionClosedError, e:
                            self.clear_busy_status()
                            self.flatline(e)
                        self.currently_selected_employee = employee # Set them as the currently selected employee
                        self.createEndActivityDynamic(end = stops) # Run the end activity screen
                        self.barcode_command = ""
                        self.clear_busy_status()
                        return # so we don't disconnect() the employee (or clock out) if we were to drop off the end of this function

                terminal_id = self.config.get("gtk", "terminal_id", "")
                try:
                    if not employee.pinNumber():
                        employee.clockOut(terminal_id = terminal_id)
                        self.clearAndResetGui()
                    else:
                        self.employee=employee
                        self.showPinPad()
                except SndcsEmployeeError, e:
                    self.alertBox("WARNING", "OK", str(e))

            employee_id = employee.serialNum()
            found_them = self.treeview_active_employees.scroll_to_employee(employee_id) # Make sure the employee is visible
            if found_them is None and command == self.clockin_and_select_selection_signifier: # Employee clocked in but they are filtered out of the model... try to add them by setting isClockedIn column of model to 1
                self.treeview_active_employees.clock_in_employee(employee_id)
                self.treeview_active_employees.scroll_to_employee(employee_id) # Try again
            if clock_in:
                employee.disconnect()
                

        # Action
        if start_signifier == self.main_action_signifier:
            # Make sure the employee has already selected themselves
            if not self.currently_selected_employee:
                self.clear_busy_status()
                self.alertBox("WARNING", "OK", "There is no selected employee. Please select yourself first and try again.")
                self.barcode_command = ""
                return

            if command == self.indirect_action_signifier:
                try:
                    indirect_factory = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["Indirect"]))
                except Exception, e:
                    self.clear_busy_status()
                    self.flatline(e)

                try:
                    indirect = indirect_factory.setIndirectByCode(data)
                except SndcsIndirectError:
                    self.clear_busy_status()
                    self.alertBox("WARNING", "OK", "'%s' is an invalid Indirect code." % (data))
                    self.clearAndResetGui()
                    return
                except Pyro.errors.ConnectionClosedError, e:
                    self.clear_busy_status()
                    self.flatline(e)

                indirect_id = indirect.serialNum()
                indirect.disconnect()
                # Check if we are forcing the user to see the end activity screen
                if self.true(self.config.get("gtk", "force_end_activity", False)):
                    # If so, check if they are on any production activities and force them to end them if so
                    try:
                        num_production =  self.currently_selected_employee.getNumberActiveProductionLaborDetails()
                    except Pyro.errors.ConnectionClosedError, e:
                        self.clear_busy_status()
                        self.flatline(e)
                    if num_production:
                        self.createEndActivityDynamic(indirect_id = indirect_id)
                        self.clear_busy_status()
                        return
                self.startIndirect(indirect_serial_num = indirect_id)
                self.clearAndResetGui()

                
            if command == self.production_action_signifier:
                # Get the operation
                try:
                    operation_factory = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["Operation"]))
                except Exception, e:
                    self.clear_busy_status()
                    self.flatline(e)
                try:
                    operation = operation_factory.setOperationByJobAssemblyOperation(data)
                except SndcsOperationError:
                    self.clear_busy_status()
                    self.alertBox("WARNING", "OK", "Could not find the job '%s'. Make sure the job exists and try again." % (data))
                    self.barcode_command = ""
                    return
                operation_id = operation.serialNum()
                operation.disconnect()

                # TODO: Check if the operation is completed and if it is, check from the configuration if we are allowed to clock onto completed jobs
                
                # If ganging, go to gang screen
                try:
                    if self.currently_selected_employee.isGanged():
                        self.job_gang_type = "Production"
                        self.switchPageByName("StartJobGang")
                except Pyro.errors.ConnectionClosedError, e:
                    self.clear_busy_status()
                    self.flatline(e)
                # If we are on the job ganging screen we do not want to simply start the production activity,
                # we want to move values from available to current or vice versa
                if self.getCurrentPage() == "StartJobGang":
                    self.autoToggleAvailableToCurrentTreeViewRows(self.treeview_start_gang_current, self.treeview_start_gang_available, operation_id)
                else:
                    # Check if we are forcing the user to see the end activity screen
                    if self.true(self.config.get("gtk", "force_end_activity", False)):
                        # If so, check if they are on any production activities and force them to end them if so
                        try:
                            num_production =  self.currently_selected_employee.getNumberActiveProductionLaborDetails()
                        except Pyro.errors.ConnectionClosedError, e:
                            self.clear_busy_status()
                            self.flatline(e)
                        if num_production:
                            self.createEndActivityDynamic(start = [operation_id])
                            self.clear_busy_status()
                            return
                    terminal_id = self.config.get("gtk", "terminal_id", "")
                    try:
                        self.currently_selected_employee.startProduction(operation_id, terminal_id = terminal_id)
                        self.addListToCachedJobs( [ operation_id ] ) 
                    except Pyro.errors.ConnectionClosedError, e:
                        self.clear_busy_status()
                        self.flatline(e)
                    self.clearAndResetGui()
                    
            if command == self.end_production_action_signifier:
                #TODO
                pass

            if command == self.setup_action_signifier:
                # Get the operation
                try:
                    operation_factory = Pyro.core.getProxyForURI(self.formatPyronameString(self.namespace, ["Operation"]))
                except Exception, e:
                    self.clear_busy_status()
                    self.flatline(e)
                try:
                    operation = operation_factory.setOperationByJobAssemblyOperation(data)
                except SndcsOperationError:
                    self.clear_busy_status()
                    self.alertBox("WARNING", "OK", "Could not find the job '%s'. Make sure the job exists and try again." % (data))
                    self.barcode_command = ""
                    return

                # TODO: Check if the operation is completed and if it is, check from the configuration if we are allowed to clock onto completed jobs
                
                # TODO: If ganging, go to gang screen

                operation_id = operation.serialNum()
                operation.disconnect()

                terminal_id = self.config.get("gtk", "terminal_id", "")
                try:
                    self.currently_selected_employee.startProduction(operation_id, jobType="Setup", terminal_id = terminal_id)
                except Pyro.errors.ConnectionClosedError, e:
                    self.clear_busy_status()
                    self.flatline(e)
                self.clearAndResetGui()
            
            if command == self.end_setup_action_signifier:
                #TODO
                pass
                

        # Command
        if start_signifier == self.main_command_signifier:
            if data.lower() in ["exit", "quit"]:
                gtk.main_quit()
            elif data.lower() in ["about", "version"]:
                self.showDialogAbout()
            elif data.lower() in ["admin",]:
                self.showWindowAdmin()
            #elif data.lower() in ["refresh", "reload"]:
            #    self.refresh()
        
        self.clear_busy_status()
        self.barcode_command = ""
        
    def find_liststore_iter_from_data(self, liststore, data, column_to_match=0):
        iter = liststore.get_iter_first()
        while iter:
            if liststore.get_value(iter, column_to_match) == data:
                return iter
            iter = liststore.iter_next(iter)
        return None















        


    def on_window_pininterface_delete_event(self, widget, event):
        self.hidePinPad()
        return True

    def on_window_admin_key_press_event(self, widget, event):
        pass

    def on_window_main_key_press_event(self, widget, event):
        # We want to have hot keys that will respond no matter what the user is doing. 
        if event.type == gtk.gdk.KEY_PRESS:
            if event.keyval == gtk.keysyms.Escape:
                self.set_busy_status()
                self.clearAndResetGui()
                self.clear_busy_status()
            elif event.keyval == gtk.keysyms.Return:
                self.processBarcode()
                return True
            elif event.keyval == gtk.keysyms.BackSpace:
                self.barcode_command = self.barcode_command[:-1]
            else:
                try:
                    string = chr(event.keyval)
                except:
                    string = ""
                self.barcode_command += string
        return False

    def on_treeview_employee_list_button_press_event(self, widget, event):
        # Check from the config file if "Easy Select Employee" is enabled
        if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1:
            if self.true(self.config.get("gtk", "easy_employee_selection", True)):
                self.set_busy_status()
                x = int(event.x)
                y = int(event.y)
                path = widget.get_path_at_pos(x, y)
                if path is None:
                    self.clear_busy_status()
                    return False
                path, col, cellx, celly = path
                model = widget.get_model()
                iter = model.get_iter(path)
                employee_id = model.get_value(iter, 0)
                self.selectEmployee(employee_id = employee_id)
                self.clear_busy_status()

    def on_bttn_clock_out_clicked(self, widget):
        assert self.currently_selected_employee

        self.set_busy_status()

        # Check if we are forcing the user to see the end activity screen
        if self.true(self.config.get("gtk", "force_end_activity", False)):
            # If so, check if they are on any production activities and force them to end them if so
            try:
                num_production =  self.currently_selected_employee.getNumberActiveProductionLaborDetails()
            except Pyro.errors.ConnectionClosedError, e:
                self.clear_busy_status()
                self.flatline(e)
            if num_production:
                self.clear_busy_status()
                self.alertBox("INFO", "OK", "You must end all production activities before clocking out.")
                self.set_busy_status()
                try:
                    stops = [x["operation_id"] for x in self.currently_selected_employee.getActiveProductionLaborDetails()] # Get list of activities to end
                except Pyro.errors.ConnectionClosedError, e:
                    self.clear_busy_status()
                    self.flatline(e)
                self.createEndActivityDynamic(end = stops) # Run the end activity screen
                self.clear_busy_status()
                return

        terminal_id = self.config.get("gtk", "terminal_id", "")
        try:
            if self.currently_selected_employee.pinNumber():
                self.employee = self.currently_selected_employee
                self.showPinPad()
                self.clear_busy_status()
            else:
                self.currently_selected_employee.clockOut(terminal_id = terminal_id)
                self.clearAndResetGui()
                self.clear_busy_status()
        except Pyro.errors.ConnectionClosedError, e:
            self.clear_busy_status()
            self.flatline(e)


    def hideShowKeybd(self,yesorno = True):
        if self.true(self.config.get("gtk", "osd_keyboard", False)):
            obj = self.xml.get_widget("osdbutton")
            obj.set_active(yesorno)

    def reset_gang_current_to_available(self):
        """ Move previously selected activities back to available """
        model = self.treeview_start_gang_current.view.get_model()
        paths = []
        iter = model.get_iter_first()
        while iter:
            paths.append(model.get_path(iter))
            iter = model.iter_next(iter)
        if paths:
            self.toggleAvailableToCurrentTreeViewRows(self.treeview_start_gang_current, self.treeview_start_gang_available, paths)

    def on_button_start_setup_gang_clicked(self,widget):
        # NOTE: You cannot setup gang if there are productions jobs active...
        if [ x for x in self.currently_selected_employee.getActiveLaborDetails() if x["jobType"] == "Production" ]:
            self.alertBox("INFO", "OK", "Please end all production jobs before setup ganging.")
            return True

        self.job_gang_type = "Setup"
        self.switchPageByName("StartJobGang")


    def on_notebook_main_switch_page(self, widget, page, page_num):
        self.xml.get_widget("searchCriteria").set_text("")

        if page_num == 0:
            das_modell = gtk.ListStore(str)
            das_modell.append([ "Employee Name" ])
            das_modell.append([ "Activity" ])

            self.name = "Employee Name"
            obj = self.xml.get_widget("rlistLookForIn")
            obj.set_model(das_modell)
            obj.set_active(0)

            self.showSearchDialog()
            self.hideShowKeybd(True)

        elif page_num == 1:
            self.hideSearchDialog()
            self.hideShowKeybd(False)

    def on_notebook_selected_employee_switch_page(self, widget, page, page_num):
        self.xml.get_widget("searchCriteria").set_text("")

        # Move all previously selected current activities back to available
        # This is probably overkill to put this here but it should catch all occurances
        # Since we are sharing the same model with multiple treeviews we need to make
        # sure and always reset so the models are pristine
        self.reset_gang_current_to_available()

        if page_num == 0:
            #TODO: determine from the config file what buttons should be displayed
            self.hideSearchDialog()
            self.hideShowKeybd(False)

        elif page_num == 1:
            #self.treeview_available_activities.populate()
            self.showSearchDialog()
            das_modell = gtk.ListStore(str)
            das_modell.append([ "Job" ]) 
            das_modell.append([ "Description" ])
            das_modell.append([ "Priority" ])
            das_modell.append([ "Department" ])

            self.name = "Job"
            obj =self.xml.get_widget("rlistLookForIn")
            obj.set_model(das_modell) 
            
            obj.set_active(1)
            
            search_widget = self.xml.get_widget("searchCriteria")
            if self.true(self.config.get("gtk","persistent_search", False)):
                self.treeview_available_activities.search_criteria = self.config.get("gtk","search_criteria", "")
                self.treeview_available_activities.column_number = getColumnNumberForName(self.treeview_available_activities.view,"Description") + 1
                search_widget.set_text(self.config.get("gtk","search_criteria", ""))
                self.treeview_available_activities.update()

            self.hideShowKeybd(True)

        elif page_num == 2:
            assert self.currently_selected_employee
            #self.treeview_start_gang_current.clear()
            das_modell = gtk.ListStore(str)
            das_modell.append([ "Job" ])
            das_modell.append([ "Description" ]) 
            das_modell.append([ "Priority" ])
            das_modell.append([ "Department" ])
            self.name = "Description"
            search_widget = self.xml.get_widget("searchCriteria")
            obj = self.xml.get_widget("rlistLookForIn")
            obj.set_model(das_modell)

            obj.set_active(1)

            if self.true(self.config.get("gtk","persistent_search", False)):
                self.treeview_start_gang_available.column_number = getColumnNumberForName(self.treeview_start_gang_available.view,"Description") + 1
                self.treeview_start_gang_available.search_criteria = self.config.get("gtk","search_criteria", "")
                search_widget.set_text(self.config.get("gtk","search_criteria", ""))
                self.treeview_start_gang_available.update()

            # If employee is on production activities, move them to the current activities list
            try:
                num_labordtls = [ x for x in self.currently_selected_employee.getActiveLaborDetails() if not x["jobType"] == "Indirect" ]
            except Pyro.errors.ConnectionClosedError, e:
                self.flatline(e)
            if num_labordtls:
                try:
                    current_production = [x["operation_id"] for x in self.currently_selected_employee.getActiveLaborDetails()]
                except Pyro.errors.ConnectionClosedError, e:
                    self.flatline(e)
                model = self.treeview_start_gang_available.model
                paths = []
                for x in current_production:
                    iter = self.find_liststore_iter_from_data(model, x)
                    if iter:
                        paths.append(model.get_path(iter))
                if paths:
                    self.toggleAvailableToCurrentTreeViewRows(self.treeview_start_gang_available, self.treeview_start_gang_current, paths)
            
            self.showSearchDialog()
            self.hideShowKeybd(True)


        elif page_num == 3:
            #self.treeview_start_setup.populate()
            self.showSearchDialog()
            das_modell = gtk.ListStore(str)
            das_modell.append([ "Job" ]) 
            das_modell.append([ "Description" ])

            self.name = "Job"
            obj = self.xml.get_widget("rlistLookForIn")
            obj.set_model(das_modell)
            obj.set_active(0)

            self.hideShowKeybd(True)
        elif page_num == 4:
            self.showSearchDialog() 
            das_modell = gtk.ListStore(str)
            das_modell.append([ "Code" ])
            das_modell.append([ "Description" ])

            self.name = "Description"
            obj = self.xml.get_widget("rlistLookForIn")
            obj.set_model(das_modell)
           
            obj.set_active(1)
           
            self.hideShowKeybd(True)

        elif page_num == 7:
            self.hideSearchDialog()
            self.hideShowKeybd(True)

    def on_bttn_return_to_employee_list_clicked(self, widget):
        self.clearAndResetGui()

    def on_bttn_start_production_activity_clicked(self, widget):
        self.switchPageByName("StartProductionActivity")

    def on_bttn_start_job_gang_clicked(self, widget):
        if [ x for x in self.currently_selected_employee.getActiveLaborDetails() if x["jobType"] == "Setup" ]:
            self.alertBox("INFO", "OK", "Please end all setup jobs before job ganging.")
            return True
        
        self.job_gang_type = "Production"
        self.switchPageByName("StartJobGang")

    def on_bttn_start_setup_activity_clicked(self, widget):
        self.switchPageByName("StartSetupActivity")

    def on_bttn_start_indirect_activity_clicked(self, widget):
        self.switchPageByName("StartIndirectActivity")

    def on_bttn_end_activity_clicked(self, widget):
        assert self.currently_selected_employee

        self.set_busy_status()

        try:
            num_activities = self.currently_selected_employee.getNumberActiveLaborDetails()
        except Pyro.errors.ConnectionClosedError, e:
            self.clear_busy_status()
            self.flatline(e)

        # If we are ganging go to the gang screen
        if num_activities > 1:
            self.job_gang_type = "Production"
            self.switchPageByName("StartJobGang")
            self.clear_busy_status()
            return False

        #TODO: If we are not logged onto anything the end activty button should be inactive and don't do anything
        if num_activities == 0:
            self.clear_busy_status()
            return False

        try:
            active_labordtl = self.currently_selected_employee.getActiveLaborDetails()[0] # Should only be one
        except Pyro.errors.ConnectionClosedError, e:
            self.clear_busy_status()
            self.flatline(e)

        # If indirect or setup activity return to idle
        if active_labordtl["jobType"] in [ "Indirect"]:
            terminal_id = self.config.get("gtk", "terminal_id", "")
            try:
                self.currently_selected_employee.endAllLaborDtl(terminal_id = terminal_id)
            except Pyro.errors.ConnectionClosedError, e:
                self.clear_busy_status()
                self.flatline(e)
            self.clearAndResetGui()
            self.clear_busy_status()
            return False

        # If single production go to end_activity_dynamic
        self.createEndActivityDynamic(end = [active_labordtl["operation_id"],])
        self.switchPageByName("EndActivityDynamic")
        self.clear_busy_status()

    def on_bttn_lunch_in_out_clicked(self, widget):
        assert self.currently_selected_employee
        terminal_id = self.config.get("gtk", "terminal_id", "")
        self.set_busy_status()
        try:
            self.currently_selected_employee.log_on_off_lunch_break(1, terminal_id = terminal_id)
        except Pyro.errors.ConnectionClosedError, e:
            self.clear_busy_status()
            self.flatline(e)
        self.clearAndResetGui()
        self.clear_busy_status()

    def on_bttn_break_in_out_clicked(self, widget):
        assert self.currently_selected_employee
        terminal_id = self.config.get("gtk", "terminal_id", "")
        self.set_busy_status()
        try:
            self.currently_selected_employee.log_on_off_lunch_break(2, terminal_id = terminal_id)
        except Pyro.errors.ConnectionClosedError, e:
            self.clear_busy_status()
            self.flatline(e)
        self.clearAndResetGui()
        self.clear_busy_status()

    def on_button_indirect_cancel_clicked(self, widget):
        self.switchPageByName("SelectedEmployee")
    
    def on_button_indirect_ok_clicked(self, widget):
        assert self.currently_selected_employee
        model, selected = self.treeview_indirect_activity_codes.view.get_selection().get_selected_rows()
        if not selected:
            self.alertBox("INFO", "OK", "Please select an indirect activity and try again.")
            return
        iter = model.get_iter(selected[0])
        indirect_id = model.get_value(iter, 0)

        self.set_busy_status()

        # Check if we are forcing the user to see the end activity screen
        if self.true(self.config.get("gtk", "force_end_activity", False)):
            # If so, check if they are on any production activities and force them to end them if so
            try:
                num_production =  self.currently_selected_employee.getNumberActiveProductionLaborDetails()
            except Pyro.errors.ConnectionClosedError, e:
                self.clear_busy_status()
                self.flatline(e)
            if num_production:
                self.createEndActivityDynamic(indirect_id = indirect_id)
                self.clear_busy_status()
                return
        self.startIndirect(indirect_serial_num = indirect_id)
        self.clear_busy_status()

    def on_treeview_indirect_activity_codes_button_press_event(self, widget, event):
        assert self.currently_selected_employee
        if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1:
            x = int(event.x)
            y = int(event.y)
            path = widget.get_path_at_pos(x, y)
            if path is None:
                return False
            path, col, cellx, celly = path
            model = widget.get_model()
            iter = model.get_iter(path)
            indirect_id = model.get_value(iter, 0)

            self.set_busy_status()

            # Check if we are forcing the user to see the end activity screen
            if self.true(self.config.get("gtk", "force_end_activity", False)):
                # If so, check if they are on any production activities and force them to end them if so
                try:
                    num_production =  self.currently_selected_employee.getNumberActiveProductionLaborDetails()
                except Pyro.errors.ConnectionClosedError, e:
                    self.clear_busy_status()
                    self.flatline(e)
                if num_production:
                    self.createEndActivityDynamic(indirect_id = indirect_id)
                    self.clear_busy_status()
                    return
            self.startIndirect(indirect_serial_num = indirect_id)
            self.clear_busy_status()


    def on_button_available_production_cancel_clicked(self, widget):
        self.switchPageByName("SelectedEmployee")

    def on_button_available_production_ok_clicked(self, widget):
        assert self.currently_selected_employee
        # Make sure a row has been selected
        model, selected = self.treeview_available_activities.view.get_selection().get_selected_rows()
        if not selected:
            self.alertBox("INFO", "OK", "Please select a production activity from the list and try again.")
            return
        
        operations = []
        for x in selected:
            iter = model.get_iter(x)
            operation_id = model.get_value(iter, 0)
            operations.append(operation_id)
            
        self.set_busy_status()
        
        # Check if the user is ganged. If so, we need to run the end_activity_dynamic screen and then start the single job.
        # Or is several operations were selected, run end_activity_dynamic
        try:
            is_ganged = self.currently_selected_employee.isGanged()
        except Pyro.errors.ConnectionClosedError, e:
            self.clear_busy_status()
            self.flatline(e)
        if is_ganged or len(operations) > 1:
            self.createEndActivityDynamic(start = operations)
            self.clear_busy_status()
        else:
            # Check if we are forcing the user to see the end activity screen
            if self.true(self.config.get("gtk", "force_end_activity", False)):
                # If so, check if they are on any production activities and force them to end them if so
                try:
                    num_production =  self.currently_selected_employee.getNumberActiveProductionLaborDetails()
                except Pyro.errors.ConnectionClosedError, e:
                    self.clear_busy_status()
                    self.flatline(e)
                if num_production:
                    self.createEndActivityDynamic(start = operations)
                    self.clear_busy_status()
                    return
            terminal_id = self.config.get("gtk", "terminal_id", "")
            try:
                self.currently_selected_employee.startProduction(operations[0], terminal_id = terminal_id)
                self.addListToCachedJobs([ operations[0] ] )
            except Pyro.errors.ConnectionClosedError, e:
                self.clear_busy_status()
                self.flatline(e)
            self.clearAndResetGui()
            self.clear_busy_status()
        
    def on_treeview_available_activities_button_press_event(self, widget, event):
        # TODO: see if SNDCS_EZ_START_PRODUCTION_ACTIVITY is set in config file
        assert self.currently_selected_employee
        if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1:
            x = int(event.x)
            y = int(event.y)
            path = widget.get_path_at_pos(x, y)
            if path is None:
                return False
            path, col, cellx, celly = path
            model = widget.get_model()
            iter = model.get_iter(path)
            operation_id = model.get_value(iter, 0)
            job_num = model.get_value(iter, 1)
            assembly_num = model.get_value(iter, 2)
            operation_num = model.get_value(iter, 3)
            description = model.get_value(iter, 4)

            self.set_busy_status()

            # Check if the user is ganged. If so, we need to run the end_activity_dynamic screen and then start the single job.
            try:
                is_ganged = self.currently_selected_employee.isGanged()
            except Pyro.errors.ConnectionClosedError, e:
                self.clear_busy_status()
                self.flatline(e)
            if is_ganged:
                self.createEndActivityDynamic(start = [operation_id])
                self.clear_busy_status()
            else:
                # Check if we are forcing the user to see the end activity screen
                if self.true(self.config.get("gtk", "force_end_activity", False)) and self.currently_selected_employee.getNumberActiveProductionLaborDetails():
                    # If so, check if they are on any production activities and force them to end them if so
                    try:
                        num_production =  self.currently_selected_employee.getNumberActiveProductionLaborDetails()
                    except Pyro.errors.ConnectionClosedError, e:
                        self.clear_busy_status()
                        self.flatline(e)
                    if num_production:
                        self.createEndActivityDynamic(start = [operation_id])
                        self.clear_busy_status()
                        return
                terminal_id = self.config.get("gtk", "terminal_id", "")
                try:
                    self.currently_selected_employee.startProduction(operation_id, terminal_id = terminal_id)
                    self.addCachedJob(operation_id,description)
                except Pyro.errors.ConnectionClosedError, e:
                    self.clear_busy_status()
                    self.flatline(e)
                self.clearAndResetGui()
                self.clear_busy_status()

    def on_treeview_start_setup_button_press_event(self, widget, event):
        # TODO: see if SNDCS_EZ_START_SETUP_ACTIVITY is set in config file
        assert self.currently_selected_employee
        if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1:
            x = int(event.x)
            y = int(event.y)
            path = widget.get_path_at_pos(x, y)
            if path is None:
                return False
            path, col, cellx, celly = path
            model = widget.get_model()
            iter = model.get_iter(path)
            operation_id = model.get_value(iter, 0)

            self.set_busy_status()

            # TODO: Check if the user is ganged. If so, we need to run the end_activity_dynamic screen and then start the single setup (look at on_treeview_available_activities_button_press_event())
            # TODO: Check if we are forcing the user to see the end activity screen
            terminal_id = self.config.get("gtk", "terminal_id", "")
            try:
                self.currently_selected_employee.startProduction(operation_id, jobType = "Setup", terminal_id = terminal_id)
            except Pyro.errors.ConnectionClosedError, e:
                self.clear_busy_status()
                self.flatline(e)
            self.clearAndResetGui()
            self.clear_busy_status()

    def on_button_setup_cancel_clicked(self, widget):
        self.switchPageByName("SelectedEmployee")

    def on_button_setup_ok_clicked(self, widget):
        assert self.currently_selected_employee
        # Make sure a row has been selected
        model, selected = self.treeview_start_setup.view.get_selection().get_selected_rows()
        if not selected:
            self.alertBox("INFO", "OK", "Please select a setup activity and try again.")
            return

        iter = model.get_iter(selected[0])
        setup_id = model.get_value(iter, 0)
            
        self.set_busy_status()

        # Check if the user is ganged. If so, we need to run the end_activity_dynamic screen and then start the single setup (look at on_treeview_available_activities_button_press_event())
        # TODO: Check if we are forcing the user to see the end activity screen
        terminal_id = self.config.get("gtk", "terminal_id", False)
        try:
            self.currently_selected_employee.startProduction(setup_id, jobType = "Setup", terminal_id = terminal_id)
        except Pyro.errors.ConnectionClosedError, e:
            self.clear_busy_status()
            self.flatline(e)
        self.clearAndResetGui()
        self.clear_busy_status()

    def on_button_start_gang_add_clicked(self, widget):
        model, selected = self.treeview_start_gang_available.view.get_selection().get_selected_rows()

        if not selected:
            self.alertBox("INFO", "OK", "Please select a available activity from the list and try again.")
            return
        
        self.set_busy_status()

        paths_to_move = []
        for x in selected:
            item_number = model[x][0]
            for y in self.treeview_start_gang_current.model:
                if y[0] == item_number:
                    # To prevent ganging on the same job.
                    continue
            # model will be the TreeModelSort... we need to convert paths back to the parent model
            # We are two (possibly three depending on 'department_jobs') models deep parent --> filter --> sort
            tmpmodel = model
            path = tmpmodel.convert_path_to_child_path(x)
            while hasattr(tmpmodel.get_model(), "get_model"):
                tmpmodel = tmpmodel.get_model()
                path = tmpmodel.convert_path_to_child_path(path)
            paths_to_move.append(path)
        
        self.toggleAvailableToCurrentTreeViewRows(self.treeview_start_gang_available, self.treeview_start_gang_current, paths_to_move = paths_to_move)
        self.clear_busy_status()

    def on_treeview_start_gang_available_button_press_event(self, widget, event):
        # TODO: see if SNDCS_EZ_START_GANG_ACTIVITY is set in config file
        assert self.currently_selected_employee
        if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1:
            x = int(event.x)
            y = int(event.y)
            path = widget.get_path_at_pos(x, y)
            if path is None:
                return False
            path, col, cellx, celly = path
            model = widget.get_model()
            iter = model.get_iter(path)
        
            self.set_busy_status()

            operation_id = model.get_value(iter,0)
            for y in self.treeview_start_gang_current.model:
                if y[0] == operation_id:
                    # To prevent ganging on the same job.
                    self.clear_busy_status()
                    return

            # model will be the TreeModelSort... we need to convert paths back to the parent model
            # We are two (possibly three depending on 'department_jobs') models deep parent --> filter --> sort
            path = model.convert_path_to_child_path(path)
            while hasattr(model.get_model(), "get_model"):
                model = model.get_model()
                path = model.convert_path_to_child_path(path)

            self.toggleAvailableToCurrentTreeViewRows(self.treeview_start_gang_available, self.treeview_start_gang_current, (path,))
            self.clear_busy_status()

    def on_treeview_start_gang_available_drag_data_get(self, widget, context, selection, targetType, eventTime):
        model, selected = widget.get_selection().get_selected_rows()
        operations = []
        for x in selected:
            iter = model.get_iter(x)
            operation_id = model.get_value(iter, 0)
            operations.append(str(operation_id))
        selection.set(selection.target, 8, ",".join(operations))
    
    def on_treeview_start_gang_available_drag_data_received(self, widget, context, x, y, selection, targetType, time):
        selections = selection.data.split(",")
        model = self.treeview_start_gang_current.model
        paths = []
        for x in selections:
            iter = self.find_liststore_iter_from_data(model, long(x))
            if iter:
                paths.append(model.get_path(iter))
        if paths:
            self.toggleAvailableToCurrentTreeViewRows(self.treeview_start_gang_current, self.treeview_start_gang_available, paths)

    def on_button_start_gang_remove_clicked(self, widget):
            model, selected = self.treeview_start_gang_current.view.get_selection().get_selected_rows()
            if not selected:
                self.alertBox("INFO", "OK", "Please select a current activity from the list and try again.")
                return
            self.set_busy_status()
            self.toggleAvailableToCurrentTreeViewRows(self.treeview_start_gang_current, self.treeview_start_gang_available, paths_to_move = selected)
            self.clear_busy_status()

    def on_treeview_start_gang_current_button_press_event(self, widget, event):
        # TODO: see if SNDCS_EZ_START_GANG_ACTIVITY is set in config file
        assert self.currently_selected_employee
        if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1:
            x = int(event.x)
            y = int(event.y)
            path = widget.get_path_at_pos(x, y)
            if path is None:
                return False
            path, col, cellx, celly = path
            #model = widget.get_model()
            #iter = model.get_iter(path)
            # TODO: check if it is a valid operation
            #operation_id = model.get_value(iter, 0)
            #try:
            #    operation = OperationFactory().getOperationBySerialNum(int(operation_id))
            #except UnknownObjectError:
            #    self.alertBox("INFO", "OK", "Invalid operation. Please try again.")
            self.set_busy_status()
            self.toggleAvailableToCurrentTreeViewRows(self.treeview_start_gang_current, self.treeview_start_gang_available, (path,))
            self.clear_busy_status()

    def on_treeview_start_gang_current_drag_data_received(self, widget, context, x, y, selection, targetType, time):
        selections = selection.data.split(",")
        model = self.treeview_start_gang_available.model
        paths = []
        for x in selections:
            check = False
            for y in self.treeview_start_gang_current.model:
                check = ( y[0] == long(x) ) == True

            if not check:
                iter = self.find_liststore_iter_from_data(model, long(x))
                if iter:
                    paths.append(model.get_path(iter))


        if paths:
            self.toggleAvailableToCurrentTreeViewRows(self.treeview_start_gang_available, self.treeview_start_gang_current, paths)

    def on_treeview_start_gang_current_drag_data_get(self, widget, context, selection, targetType, eventTime):
        model, selected = widget.get_selection().get_selected_rows()
        operations = []
        for x in selected:
            iter = model.get_iter(x)
            operation_id = model.get_value(iter, 0)
            operations.append(str(operation_id))
        selection.set(selection.target, 8, ",".join(operations))

    def on_button_start_gang_ok_clicked(self, widget):
        model = self.treeview_start_gang_current.view.get_model()
        #factory = OperationFactory()
        start = []
        iter = model.get_iter_first()
        while iter:
            #try:
            #    operation = factory.getOperationBySerialNum(model.get_value(iter, 0))
            #except UnknownObjectError:
            #    self.alertBox("INFO", "OK", "Invalid operation. Please try again.")
            operation_id = model.get_value(iter, 0)
            start.append(operation_id)
            iter = model.iter_next(iter)
        self.set_busy_status()
        self.createEndActivityDynamic(start)
        self.clear_busy_status()

    def on_button_start_gang_cancel_clicked(self, widget):
        self.switchPageByName("SelectedEmployee")

    def on_button_end_activity_dynamic_cancel_clicked(self, widget):
        self.destroyEndActivityScreen()
        self.switchPageByName("SelectedEmployee")

    def on_button_end_activity_dynamic_ok_clicked(self, widget):
        self.set_busy_status()
        try:
            self.validateEndActivityDynamic()
        except EndActivityPercentageError, e:
            self.clear_busy_status()
            self.alertBox("ERROR", "OK", str(e))
        except EndActivityQtyCompleteError, e:
            self.clear_busy_status()
            self.alertBox("ERROR", "OK", "'%s' is not a valid entry for quantity complete. Please try again." % str(e))
        except EndActivityQtyScrapError, e:
            self.clear_busy_status()
            self.alertBox("ERROR", "OK", "'%s' is not a valid entry for quantity scrap. Please try again." % str(e))
        else:
            terminal_id = self.config.get("gtk", "terminal_id", "")
            if self.processEndActivityDynamic(terminal_id = terminal_id):
                self.clear_busy_status() # Probably only get here if qty_complete_warning is True the user wanted to cancel the completion
                return

            self.clearAndResetGui()
            self.clear_busy_status()

    def on_menu_main_help_about_activate(self, menu_item):
        self.showDialogAbout()

    def on_menu_main_view_admin_activate(self, menu_item):
        pass
        #NOTE: Comment out because win32client would complain on exit...

        #if menu_item.get_active():
        #    self.treeview_job_admin.populate()
        #    self.showWindowAdmin()
        #else:
        #    self.hideWindowAdmin()
            
    ### PIN NUMBER FUNCTIONS ###
    
    def backspacePinPad(self,widget = ""):
        entry_pinpad = self.xml.get_widget("entry_pinpad")
        pin_number = entry_pinpad.get_text()
        if pin_number:
            entry_pinpad.set_text(pin_number[:-1])

    def clearPinPad(self,widget = False):
        entry_pinpad = self.xml.get_widget("entry_pinpad")
            
        if entry_pinpad.get_text() == "":
            window_pinpad = self.xml.get_widget("window_pininterface")
            window_pinpad.hide()
            self.employee = None
            self.clearAndResetGui()
        else:
            entry_pinpad.set_text("")


    def addToPinPad(self,widget,event = False):
        if not event:
            charToAdd = widget.get_label()
        else:
                if event.type == gtk.gdk.KEY_PRESS:
                        if event.keyval == gtk.keysyms.Return:
                                self.enterPinPad()
                                return
                        elif event.keyval == gtk.keysyms.BackSpace:
                                self.backspacePinPad()
                                return
                        elif event.keyval == gtk.keysyms.Escape:
                                self.clearPinPad()
                                return
                        else:
                                value = self.convertAlphaToNum(event.keyval,True)
                                if value: charToAdd = str(value)
                                else: return

        entry_pinpad = self.xml.get_widget("entry_pinpad")
        entry_pinpad.set_text(entry_pinpad.get_text() + charToAdd)

    def enterPinPad(self,widget = ""):
        entry_pinpad = self.xml.get_widget("entry_pinpad")
        if not entry_pinpad.get_text() == self.employee.pinNumber():
            self.hidePinPad()
            self.alertBox("WARNING", "OK", "Invalid PIN number!")
            self.employee = None
            self.clearAndResetGui()
            return

        terminal_id = self.config.get("gtk", "terminal_id", "")
        if self.employee.isClockedIn():
            self.employee.clockOut(terminal_id = terminal_id)
        else:
            self.employee.clockIn(terminal_id = terminal_id)

        self.employee = None
        self.hidePinPad()
        self.clearAndResetGui()
    
    ### DIALOG ABOUT ###
    
    def showDialogAbout(self):
        dialog_about = self.xml.get_widget("dialog_about")
        dialog_about.show()

    def hideDialogAbout(self):
        dialog_about = self.xml.get_widget("dialog_about")
        dialog_about.hide()

    def on_dialog_about_delete_event(self, widget, event):
        widget.hide()
        return True

    def on_button_dialog_about_ok_clicked(self, widget):
        self.hideDialogAbout()
        return True
    
    

    ### DIALOG PIN PAD ###

    def showPinPad(self):
        window_pinpad = self.xml.get_widget("window_pininterface")
        
        window_main = self.xml.get_widget("window_main")
        window_pinpad.set_transient_for(window_main) # For win32, to keep pin pad on top

        window_pinpad.activate_focus()
        window_pinpad.set_keep_above(True)
        window_pinpad.set_position(gtk.WIN_POS_CENTER)
        window_pinpad.show()
    
    def hidePinPad(self):
        window_pinpad = self.xml.get_widget("window_pininterface")
        entry_pinpad = self.xml.get_widget("entry_pinpad")
        window_pinpad.hide()
        entry_pinpad.set_text("")






    ### WINDOW ADMIN ###
    def showWindowAdmin(self):
        window_admin = self.xml.get_widget("window_admin")
        window_admin.show()

    def hideWindowAdmin(self):
        window_admin = self.xml.get_widget("window_admin")
        window_admin.hide()

    def on_window_admin_delete_event(self, widget, event):
        menu_main_view_admin = self.xml.get_widget("menu_main_view_admin")
        menu_main_view_admin.set_active(False)
        widget.hide()
        return True

    def on_notebook_admin_switch_page(self, widget, page, page_num): 
        self.xml.get_widget("searchCriteria_admin").set_text("")
        if page_num == 0:
            das_modell = gtk.ListStore(str)
            das_modell.append([ "Number" ])
            das_modell.append([ "Item" ])
            das_modell.append([ "Description" ])
            
            self.admin_name = "Number"
            obj = self.xml.get_widget("rlistLookForIn_admin")
            obj.set_model(das_modell)

            obj.set_active(0)
            self.treeview_job_admin.restore()

        elif page_num == 1:
            das_modell = gtk.ListStore(str)
            das_modell.append(["(NONE)"])

            self.admin_name = False #If self.name is False then don't search at all
            obj = self.xml.get_widget("rlistLookForIn_admin")
            obj.set_model(das_modell)

            obj.set_active(0)
        
        elif page_num == 2: 
            das_modell = gtk.ListStore(str)
            das_modell.append([ "Name" ])
            das_modell.append([ "Status" ])

            self.admin_name = "Name"
            obj = self.xml.get_widget("rlistLookForIn_admin")
            obj.set_model(das_modell)

            obj.set_active(0)

    def on_menu_admin_file_new_activate(self, menu_item):
        # Find out which notebook page we are on
        notebook_admin = self.xml.get_widget("notebook_admin")
        page = notebook_admin.get_current_page()
        if page == 0: # Jobs
            # Create new job
            pass


    def on_recent_activities_treeview_row_activated(self,widget,iter,path):
        model = widget.get_model() 
        operation_id = model[iter[0]][0]

        self.set_busy_status()

        # Check if we are forcing the user to see the end activity screen
        if self.true(self.config.get("gtk", "force_end_activity", False)):
            # If so, check if they are on any production activities and force them to end them if so
            try:
                num_production =  self.currently_selected_employee.getNumberActiveProductionLaborDetails()
            except Pyro.errors.ConnectionClosedError, e:
                self.clear_busy_status()
                self.flatline(e)
            if num_production:
                self.createEndActivityDynamic(start = [operation_id])
                self.clear_busy_status()
                return
        terminal_id = self.config.get("gtk", "terminal_id", "")
        try:
            self.currently_selected_employee.startProduction(operation_id, terminal_id = terminal_id)
            self.addListToCachedJobs( [ operation_id ] )
        except Pyro.errors.ConnectionClosedError, e:
            self.clear_busy_status()
            self.flatline(e)
        self.clearAndResetGui()
        self.clear_busy_status()



    ### SCRAPCODE FUNCTIONS ### Might not need these

    def delete_scrapcode(self,event):
        log.info("%s [%s] scrap_code deleted/inactive", event.msg["code"], event.msg["description"] )

    def add_scrapcode(self,event):
        log.info("%s [%s] scrap_code added", event.msg["code"], event.msg["description"] )

    ### DEPARTMENT FUNCTIONS ###

    def delete_department(self,event):
        if self.config.get("gtk", "terminal_id", "")==event.msg["terminal_name"] and isinstance(self.treeview_active_employees.display_departments,list):
            self.treeview_active_employees.display_departments.remove( event.msg["department"] )
        log.info("%s [%s] department deleted", event.msg["terminal_name"], event.msg["department"] )
        self.treeview_active_employees.populate()

    def add_department(self,event):
        if self.treeview_active_employees.display_departments is None:
            self.treeview_active_employees.display_departments = []

        if self.config.get("gtk", "terminal_id", "")==event.msg["terminal_name"] and isinstance(self.treeview_active_employees.display_departments,list):
            self.treeview_active_employees.display_departments.append( event.msg["department"] )
        log.info("%s [%s] department added", event.msg["terminal_name"], event.msg["department"] )
        self.treeview_active_employees.populate()
    
    def clear_settings(self,event):
        log.info("Clearing terminal settings: %s..." % event.msg["terminal_name"])
        if self.config.get("gtk", "terminal_id", "")==event.msg["terminal_name"]:
            self.treeview_active_employees.display_departments  = None
        self.treeview_active_employees.populate()

    ### HIGHLIGHT FUNCTIONS ###

    def highlight_on(self,widget,event):
        widget.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("yellow"))
        try:
            self.job_entry[0] = self.job_entry[1].index(widget)
        except AttributeError: pass
        except: pass
        return

    def highlight_off(self,widget,event):
        widget.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
        return
        
    def attachHighlightToWidget(self,widget,searchbox = False):
        widget.connect("focus-in-event",self.highlight_on)
        widget.connect("focus-out-event",self.highlight_off)
        widget.set_property("can-default",True)
        
        if not searchbox:
            self.job_entry[1].extend([widget])

    ### FOCUS FUNCTIONS ###
    
    def focusCapture(self,widget,event):
        #print "Focus Caught",widget.get_name()
        return True

    def on_button_additional_information_clicked(self,widget):
        activities = self.currently_selected_employee.getActiveProductionLaborDetails()
        
        if activities:
            for child in self.vbox_information.get_children():
                child.destroy()
            
            for x in activities:
                label = gtk.Label()
                label.set_markup("""<u><b>Job Description:</b></u>\n %s \n<u><b>Job Note:</b></u>\n %s \n\n\n""" % (x["formattedDescription"],x["jobhed_note"]) )
                label.set_alignment(0.0,0.0)
                label.set_padding(5,3)
                label.set_selectable(True)
                label.set_line_wrap(True)
                label.show()

                self.vbox_information.pack_start(label,expand = False)

            self.switchPageByName("AdditionalInformation")

    def on_bttn_additional_information_ok_clicked(self,widget):
        self.switchPageByName("SelectedEmployee")

class EndActivityPercentageError(Exception):
    """Exceptions raised when activity percentage totals do not total 100%"""
    pass

class EndActivityQtyCompleteError(Exception):
    """Exception raised when qty complete is not a valid numerical entry"""
    pass

class EndActivityQtyScrapError(Exception):
    """Exception raised when qty scrap is not a valid numerical entry"""
    pass
