# 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("%s %s" % (sndcs_common.NAME, sndcs_common.VERSION))
label_splash_description_shadow.set_markup("%s %s" % (sndcs_common.NAME, sndcs_common.VERSION))
label_splash_action.set_markup("Connecting to server...")
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("Loading available activity data...")
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("Loading available setup data...")
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("Loading available gang data...")
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("Loading indirect activities...")
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("Loading active employee data...")
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("%s %s" % (sndcs_common.NAME, sndcs_common.VERSION))
label_dialog_about_description = self.xml.get_widget("label_dialog_about_description")
label_dialog_about_description.set_markup("%s" % (sndcs_common.DESCRIPTION))
label_dialog_about_copyright = self.xml.get_widget("label_dialog_about_copyright")
label_dialog_about_copyright.set_markup("%s" % (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("Starting heartbeat monitor...")
while gtk.events_pending():
gtk.main_iteration()
self.startHeartbeatMonitor()
# Set up the event subscriber
label_splash_action.set_markup("Starting event subscriber...")
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 = ["Activity", "%", "Curr Comp", "Qty Comp" ]
self.job_entry = [ 0, [] ]
if self.true(self.config.get("gtk","scrap_codes", False)):
for x in scrap_codes.getScrapCodes():
column_headings.extend(["" + x["description"] + ""])
else:
column_headings.extend(["Scrap"])
if self.true(self.config.get("gtk","show_notes", False)):
column_headings.extend(["Notes"])
column_headings.extend(["Comp?"])
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("""Job Description:\n %s \nJob Note:\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