# AutoDispatcher 2
#
#    This script provides full layout automation, using connectivity info
#    provided by Layout Editor panels.
#
# This file is part of JMRI.
#
# JMRI is free software; you can redistribute it and/or modify it under
# the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation. See the "COPYING" file for a copy
# of this license.
#
# JMRI is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# Author:  Giorgio Terdina copyright (c) 2009, 2010, 2011
#
# 2.01 beta - Fixed problem wih signalheads without UserName
# 2.02 beta - Added test for empty sections
# 2.03 beta - Added test for no valid section found
# 2.04 beta - Corrected removal of SignalMasts
# 2.05 beta - Reverted signals to red aspect as soon as train enters next block
# 2.06 beta - Added new methods (getScale, getDeceleration, etc.) for custom AE
# 2.07 beta - Corrected bug when minimum speed is selected in SignalType
# 2.08 beta - Removed default speed from Speeds window, since it was confusing
# 2.09 beta - Corrected problem when manually changing train's section
# 2.10 beta - Corrected initialization of allocationReady at train departure
# 2.11 beta - Removed bell chime for warnings printed before loading preferences
# 2.12 beta - Modified Signal Edit window to make it more understandable
# 2.13 beta - Compensated difference for emergency stop between Multimaus and other CS
# 2.14 beta - Fixed bug for unbalanced brackets in schedule
# 2.15 beta - Added detailed error messages for wrong schedule
# 2.16 beta - Fixed problem of $P stopping trains controlled by "Braker" engineer 
# 2.17 beta - Added possibility of separating schedule tokens with comas ","
# 2.18 beta - Added error message when destination is a transit-only section
# 2.19 beta - Set default engineer to Auto, when custom script is not found
# 2.20 beta - Added blinking of new flashingLunar signal aspect (since JMRI 2.7.8)
# 2.21 beta - Corrected problem with OFF:Fx block action
# 2.22 beta - Added test for wrong input in Locomotives window
# 2.23 beta - Added possibility of stopping trains at the beginning of sections
# 2.24 beta - Fixed hang-up when schedule alternative has only one section and its name is wrong
# 2.25 beta - Added error message for nested "[" in schedule
# 2.26 beta - Fixed problem with static variable in ADTrain addressed as self.variable
# 2.27 beta - Fixed direction in transit-only sections when dealing with reversing tracks
# 2.28 beta - Closed "Train Detail" window when "Apply" is clicked
# 2.29 beta - Corrected loop in match method caused when an unknown section name was found
# 2.30 beta - Modified "import" statements to reflect package structure of JMRI 2.9.x
# 2.31 beta - Unified versions for JMRI 2.8 and 2.9.x and initialized block tracking at startup
# 2.32 beta - Implementd "train start actions" and "AutoStart trains" option.
# 2.33 beta - Enabled pause/resume buttons while script being stopped.
# 2.34 beta - Updated blinkSignals as previous methods were deprecated (Greg).
# 2.35 - Added $IFH (if held) command in schedule.
# 2.35 - Added $TC and $TT (set turnout or accessory) commands in schedule.
# 2.35 - Added $ST (Start at fast clock time) command in schedule.
# 2.35 - Released throttle when train is in manual section.
# 2.35 - Added custom section RGB colors.
# 2.36 - Avoided duplicate operation of turnouts due to changes in Layout Editor.
# 2.37 - Added section tracking using JMRI memory variables.
# 2.38 - Modified to ignored Power OFF when using XpressNet Simulator (since it's unreliable).
# 2.39 - Removed Operations interface (nobody ever used it!)
# 2.40 - Fixed thread race condition when stopping trains
# 2.41 - Set default minimum interval between speed commands (maxIdle) to 60 seconds
# 2.42 - Adapted to refactoring occurred in JMRI 3.32 (different access to default directory)
# 2.43 - timeout and retry on acquisition failure, handle new syntax of LayoutEditor class

# JAVA imports

import java
import java.awt
import java.awt.event
import java.beans
import java.io
import java.util

import jmri

from java.beans import PropertyChangeListener

from java.lang import System

from java.awt import BorderLayout
from java.awt import Color
from java.awt import GridLayout
from java.awt import Toolkit

from java.awt.event import ActionListener
from java.awt.event import WindowAdapter

from java.io import FileInputStream
from java.io import FileOutputStream
from java.io import IOException
from java.io import ObjectInputStream
from java.io import ObjectOutputStream

from java.util import Locale
from java.util import Random

from javax.swing import BorderFactory
from javax.swing import BoxLayout
from javax.swing import ButtonGroup
from javax.swing import JButton
from javax.swing import JCheckBox
from javax.swing import JComboBox
from javax.swing import JFileChooser
from javax.swing import JLabel
from javax.swing import JOptionPane
from javax.swing import JPanel
from javax.swing import JRadioButton
from javax.swing import JScrollPane
from javax.swing import JTextArea
from javax.swing import JTextField
from javax.swing import JToggleButton
from javax.swing import JSpinner
from javax.swing import SpinnerNumberModel

from javax.swing.event import ChangeListener

from javax.swing.filechooser import FileFilter

from math import sqrt

from time import sleep

from thread import start_new_thread

# JMRI imports

from jmri import Block
from jmri import DccThrottle
from jmri import InstanceManager
from jmri import PowerManager
from jmri import Section
from jmri import SectionManager
from jmri import Sensor
from jmri import SignalHead
from jmri import Turnout

from jmri.jmrit import Sound
from jmri.jmrit import XmlFile

from jmri.jmrit.consisttool import ConsistToolFrame

# is this next import necessary?
from jmri.implementation import AbstractShutDownTask

# from jmri.jmrit.operations.locations import LocationManager
#from jmri.jmrit.operations.rollingstock.cars import CarManager
#from jmri.jmrit.operations.trains import TrainManager

from jmri.jmrit.roster import Roster

from jmri.util import JmriJFrame

# Utility class for static methods ==============
# Must be defined before being used!

class ADstaticMethod :
    def __init__(self, anycallable):
        self.__call__ = anycallable

# Fast Clock Listener
class FastListener(java.beans.PropertyChangeListener):
  fastTime = 0

  def propertyChange(self, event):
    time = InstanceManager.getDefault(jmri.Timebase).getTime()
    FastListener.fastTime = time.getHours() * 60 + time.getMinutes()
    return

# MAIN CLASS ==============

class AutoDispatcher(jmri.jmrit.automat.AbstractAutomaton) :

# CONSTANTS ==========================

    version = "2.43"
    
    
    # Retrieve DOUBLE_XOVER constant, depending on JMRI Version
    # (LayoutTurnout class was moved to a new package, starting with JMRI 2.9.3)
    try :
        DOUBLE_XOVER = jmri.jmrit.display.layoutEditor.LayoutTurnout.TurnoutType.DOUBLE_XOVER
    except :
        DOUBLE_XOVER = jmri.jmrit.display.LayoutTurnout.DOUBLE_XOVER


    
    # Maximum number of lines kept in the status scroll area
    MAX_LINES = 200
    
    # Names of Signalhead aspects
    headsAspects = {
        "Dark": SignalHead.DARK,
        "Red": SignalHead.RED,
        "Yellow": SignalHead.YELLOW,
        "Green": SignalHead.GREEN,
        "Lunar": SignalHead.LUNAR,
        "Flash-Red": SignalHead.FLASHRED,
        "Flash-Yellow": SignalHead.FLASHYELLOW,
        "Flash-Green": SignalHead.FLASHGREEN,
        "Flash-Lunar": SignalHead.FLASHLUNAR
    }

    # Signalhead aspects inverse dictionary
    inverseAspects = {}
    for key in headsAspects.keys() :
        inverseAspects[headsAspects[key]] = key
    
# STATIC VARIABLES ==========================
    
    # Our unique instance
    instance = None
    # Power monitor instance
    powerMonitor = None
    
    # Fast Clock
    fastBase = InstanceManager.getDefault(jmri.Timebase)
    fastListener = FastListener()

    # Status variables
    error = False
    loop = False
    stopped = True
    exiting = False
    paused = False
    repaint = False
    simulation = False
    lenzSimulation = False
    trainsDirty = False
    preferencesDirty = False
    debug = False

    # Our random numbers generator
    random = Random()

    # Window frames
    mainFrame = None
    directionFrame = None
    panelFrame = None
    speedsFrame = None
    indicationsFrame = None
    signalTypesFrame = None
    signalEditFrame = None
    signalMastsFrame = None
    sectionsFrame = None
    blocksFrame = None
    locationsFrame = None
    preferencesFrame = None
    soundListFrame = None
    soundDefaultFrame = None
    locosFrame = None
    trainsFrame = None
    trainDetailFrame = None
    importFrame = None
    
    # Status area at the bottom of the main window
    statusScroll = JTextArea("Getting layout description (be patient!)", 4, 22)
    statusScroll.setEditable(False)
    statusScroll.setLineWrap(True)
    statusScroll.setWrapStyleWord(True)

    # Info gathered from JAVA
    screenSize = Toolkit.getDefaultToolkit().getScreenSize()
    
    # Info gathered from JMRI
    # List of signalHeads defined in JMRI
    signalHeadNames = []
    # List of signalHeadIcons displayed on the panel
    signalIcons = []
    # Original click mode of signalHeadIcons (used to restore them on exit)
    signalClick = []
    # maximum number of blocks encountered in any section
    maxBlocksPerSection = 0

    # Number of trains running
    runningTrains = 0
    # List of available engineer scripts
    engineers = {}
    
    # Lists of last DCC commands sent
    # Used when resuming operations after "Power off"
    turnoutCommands = {}
    signalCommands = {}
    
    # Table used for test purposes if file AutoDispatcher_Loc.bin is not found
    locomotives =  [
        ['1017', 1017, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0],
        ['1019', 1019, [0.15, 0.5, 0.55, 0.6, 0.65], 12, 7, 0, 90.0],
        ['1633', 1633, [0.2, 0.65, 0.7, 0.75, 0.8], 10, 6, 0, 0.0],
        ['1641', 1641, [0.2, 0.5, 0.7, 0.8, 0.8], 15, 10, 0, 0.0],
        ['3023', 3023, [0.2, 0.6, 0.7, 0.8, 0.85], 8, 7, 0, 0.0],
        ['3029', 3029, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0],
        ['4802', 4802, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0],
        ['4805', 4805, [0.2, 0.7, 0.8, 0.9, 0.95], 10, 8, 0, 0.0],
        ['5010', 5010, [0.2, 0.6, 0.75, 0.85, 0.95], 10, 8, 0, 0.0],
        ['5151', 5151, [0.3, 0.6, 0.7, 0.8, 0.8], 12, 12, 0, 0.0],
        ['5160', 5160, [0.2, 0.6, 0.7, 0.8, 0.8], 15, 6, 0, 0.0],
        ['5162', 5162, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0],
        ['5166', 5166, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0],
        ['5294', 5294, [0.01, 0.26, 0.51, 0.75, 1.0], 0, 0, 0, 0.0],
        ['5307', 5307, [0.2, 0.8, 0.8, 0.85, 0.9], 5, 10, 0, 0.0]]

    # Variable used by "printTime" debug utility
    lastTime = 0
    
# STATIC METHODS ==========================
    
    def getVersion() :
        return AutoDispatcher.version
    getVersion = ADstaticMethod(getVersion)

    def setDebug(on) :
        AutoDispatcher.debug = on
    setDebug = ADstaticMethod(setDebug)

    def message(text) :
        # Output a message only in verbose mode
        if ADsettings.verbose :
            AutoDispatcher.log(text)
    message = ADstaticMethod(message)
        
    def log(text) :
        # Output a message, both to script main window and to log file
        AutoDispatcher.statusScroll.append("\n" + text)
        while (AutoDispatcher.statusScroll.getLineCount() >
           AutoDispatcher.MAX_LINES) :
            AutoDispatcher.statusScroll.replaceRange(None, 0,
              AutoDispatcher.statusScroll.getLineEndOffset(0))
        AutoDispatcher.statusScroll.setCaretPosition(
          AutoDispatcher.statusScroll.getDocument().getLength())
        print text
    log = ADstaticMethod(log)

    def chimeLog(sound, text) :
        # As log, but also plays a sound
        AutoDispatcher.log(text)
        ind = ADsettings.defaultSounds[sound]
        if ADsettings.ringBell and ind > 0 and ind >= len(ADsettings.soundList) :
            ADsettings.soundList[ind-1].play()
    chimeLog = ADstaticMethod(chimeLog)

    def addEngineer(engineerName, engineerClass) :
        # Add an Engineer script to our list
        if (engineerName != "Auto" or not
           AutoDispatcher.engineers.has_key(engineerName)) :
            AutoDispatcher.engineers[engineerName] = engineerClass
            if AutoDispatcher.trainsFrame != None :
                AutoDispatcher.trainsFrame.reDisplay()
            if engineerName != "Auto" :
                AutoDispatcher.log("Added engineer: " + engineerName)
            return True
        AutoDispatcher.log("Warning: Engineer " + engineerName 
          + "already registered!")
        return False
    addEngineer = ADstaticMethod(addEngineer)
    
    def removeEngineer(engineerName) :
        # Remove an Engineer script from our list
        if (engineerName != "Auto" and
           AutoDispatcher.engineers.has_key(engineerName)) :
            del AutoDispatcher.engineers[engineerName]
            for train in ADtrain.getList() :
                if train.engineerName == engineerName :
                    train.setEngineer("Auto")
            AutoDispatcher.log("Removed engineer: " + engineerName)
            return True
        AutoDispatcher.log("Warning: Engineer " + engineerName + 
          " not found. Cannot be removed!")
        return False
    removeEngineer = ADstaticMethod(removeEngineer)

    def setTrainsDirty() :
        # Take note that trains info changed
        if AutoDispatcher.trainsDirty :
            return
        AutoDispatcher.trainsDirty = True
        if not AutoDispatcher.loop :
            ADmainMenu.saveTrainsButton.enabled = True
    setTrainsDirty = ADstaticMethod(setTrainsDirty)
            
    def setPreferencesDirty() :
        # Take note that preferences were changed
        if AutoDispatcher.preferencesDirty :
            return
        AutoDispatcher.preferencesDirty = True
        if not AutoDispatcher.loop :
            ADmainMenu.saveSettingsButton.enabled = True
    setPreferencesDirty = ADstaticMethod(setPreferencesDirty)

    def centerLabel(string) :
        # Create a centered JLabel
        tempLabel = JLabel(string)
        tempLabel.setHorizontalAlignment(JLabel.CENTER)
        return tempLabel
    centerLabel = ADstaticMethod(centerLabel)

    def printTime(text) :
        # Debug utility.  Can be used to measure time lapsed between events
        newTime = System.currentTimeMillis()
        if AutoDispatcher.lastTime != 0 :
            print (newTime - AutoDispatcher.lastTime), text
        AutoDispatcher.lastTime = newTime
    printTime = ADstaticMethod(printTime)
    
    def cleanName(name) :
        # Replace spaces, brackest and $ contained in section or signal names
        # to avoid errors in schedules
        name  = name.replace(" ", "_")
        name  = name.replace("$", "#")
        name  = name.replace("(", "{")
        name  = name.replace("[", "{")
        name  = name.replace(")", "}")
        name  = name.replace("]", "}")
        name  = name.replace(",", ".")
        return name
    cleanName = ADstaticMethod(cleanName)

# INSTANCE METHODS ==========================

# INITIAL SETUP ==============

    def setup(self):

    # Done once
        
        # Save the (unique) instance in a static variable, to allow access 
        # from different classes
        AutoDispatcher.instance = self
        # Create settings
        ADsettings()
        # Register our own engineer
        AutoDispatcher.addEngineer("Auto", ADengineer)
                                    
    # Span a separate thread to setup script without blocking JMRI
        start_new_thread(self.__setup__, ())

    def __setup__(self):
        # The real setup.
        
        # Now perform initialization
        # Display the main window
        AutoDispatcher.mainFrame = ADmainMenu()

         # Get layout  connectivity
        self.getLayoutData()
        if AutoDispatcher.error :
            return

        # Workout default settings
        self.defaultSettings()
         
        # Keep copy of automatic settings, to compare them with
        # user defined settings at saving time
        self.autoSections = ADsection.getSectionsTable()
        self.autoBlocks = ADsection.getBlocksTable()

        # If running in simulation mode, clear all sections before 
        # placing trains on tracks
        if AutoDispatcher.simulation :
            AutoDispatcher.log("Clearing sensors for simulation")           
            for block in ADblock.getList() :
                sensor = block.getOccupancySensor()
                if sensor != None :
                    sensor.setKnownState(Sensor.INACTIVE)
            # Wait to allow JMRI processing of sensor change events
            self.waitMsec(1000)
            # Set power ON, otherwise user will wonder why trains
            # don't start :-)
            AutoDispatcher.powerMonitor.setPower(PowerManager.ON)

        # Set demo preferences (will be used if preferences files are not found)
        AutoDispatcher.log("Restoring settings")
        # Choose preferences on the basis of Layout Editor panel title       
        title = self.layoutEditor.getTitle()
        if ADexamples1.examples.has_key(title) :
            ADsettings.load(ADexamples1.examples[title])
            self.trains = ADexamples1.exampleTrains[title]
        elif ADexamples2.examples.has_key(title) :
            ADsettings.load(ADexamples2.examples[title])
            self.trains = ADexamples2.exampleTrains[title]
        else :
            self.trains = []

        # Get user settings from disk (if any)
        # User settings files are named on the basis of the first word
        # in Layout Editor panel title.
        # Get first word of panel title
        firstWord = title.split()
        if len(firstWord) == 0 :
            firstWord = ""
        else :
            firstWord = firstWord[0]
        baseName = ""
        # Strip out all characters but letters and numbers
        for c in firstWord :
            if c.isalnum() :
                baseName += c
        # If we didn't get a valid name, use "AutoDispatcher"
        if baseName == "" :
            baseName = "AutoDispatcher"
        self.settingsFile = (Roster.getDefault().getFileLocation() + 
          baseName + "_AdP.bin")
        self.trainFile = (Roster.getDefault().getFileLocation() + 
          baseName + "_AdT.bin")
        # Locomotive settings are not based on panel title, since there is
        # only one JMRI roster
        self.locoFile = (Roster.getDefault().getFileLocation() + 
          "AutoDispatcher_Loc.bin")
        # Now try loading files
        self.loadSettings()
        self.loadLocomotives()
        self.loadTrains()
 
        # Apply user (or default) settings
        self.userSettings() 
        
        # Get data from Operations module
#        ADlocation.getOpLocations()

        # Setup completed
        # Enable buttons, unless some error occurred
        if not AutoDispatcher.error :
            AutoDispatcher.mainFrame.enableButtons(True)
            AutoDispatcher.chimeLog(ADsettings.START_STOP_SOUND, "Layout ready!")                   
        
# LAYOUT CONNECTIVITY ==============

    def getLayoutData(self) :
    
        # Retrieve power monitor (will be used to check whether 
        # the layout is powered)
        AutoDispatcher.powerMonitor = ADpowerMonitor()

        # Retrieve LayoutEditor instance by searching the relevant window
        # If more than one panel are open, we will get the first one we find
        windowsList = JmriJFrame.getFrameList()
        self.layoutEditor = None
        for i in range(windowsList.size()) :
            window = windowsList.get(i)
            windowClass = str(window.getClass())
            if(windowClass.find(".LayoutEditor") > 0) :
                self.layoutEditor =  window
                break
        if self.layoutEditor == None :
            AutoDispatcher.log("No Layout Editor window found," + 
              " script cannot continue!")
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, 
              "Load your Layout Editor panel" + 
              " before running AutoDispatcher!")
            AutoDispatcher.error = True
            return
            
        # Retrieve SignalHead names from JMRI (will be used to populate menus)
        signalHeads = InstanceManager.getDefault(SignalHeadManager).getNamedBeanSet()
        AutoDispatcher.signalHeadNames = [""]
        for s in signalHeads :
            # Use UserName (if available), otherwise SystemName
            signalName = s.getUserName()
            if signalName == None or signalName.strip() == "" :
                signalName = s.getSystemName()
            AutoDispatcher.signalHeadNames.append(signalName)
        AutoDispatcher.signalHeadNames.sort()

        # Retrieve List of SignalHeads that have an icon on the panel
        nIcons = self.layoutEditor.signalHeadImage.size()
        for i in range(nIcons) :
            signalIcon = self.layoutEditor.signalHeadImage.get(i)
            signalHead = signalIcon.getSignalHead()
            if signalHead != None :
                signalName = signalHead.getUserName()
                if signalName == None :
                    signalName = signalHead.getSystemName()
                AutoDispatcher.signalIcons.append(signalName)
            # Keep note of original signal icon click mode
            AutoDispatcher.signalClick.append(
              [signalIcon, signalIcon.getClickMode()])

        # Retrieve sections from JMRI
        # (it's unfortunate that this uses "sections" differently than the jmri_defaults style)
        sections = InstanceManager.getDefault(jmri.SectionManager).getNamedBeanSet()
        if sections.size() < 1 :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, 
              "Layout contains no sections, script" +
              " cannot continue!")
            
        # Create section and block instances
        for section in sections :
            ADsection(section.getSystemName())
            
        if len(ADsection.getList()) == 0 :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, 
              "No valid section found, script cannot continue!")
            AutoDispatcher.error = True
            return
            

        # Create entries (connections between sections)
        for section in ADsection.getList() :
            section.setEntries()

        # Retrieve paths (connections between blocks)
        for block in ADblock.getList() :
            block.setPaths()
            
        # Retrieve Crossings and establish inter-dependecy between 
        # crossed sections
        nXings = self.layoutEditor.xingList.size()
        for i in range(nXings) :
            xing = self.layoutEditor.xingList.get(i)
            # Ignore crossing if crossed blocks are not included in sections
            blockAC = ADblock.getByName(xing.blockNameAC)
            if blockAC != None :
                blockBD = ADblock.getByName(xing.blockNameBD)
                if blockBD != None :
                    sectionAC = blockAC.getSection()
                    sectionBD = blockBD.getSection()
                    # Ignore crossing if both crossed bocks belong to
                    # the same section
                    # (results can, however, be unpredictable!)
                    if sectionAC != sectionBD :
                        sectionAC.addXing(sectionBD)
                        sectionBD.addXing(sectionAC)

        # Retrieve double crossovers by scanning all tunouts
        # contained in LayoutEditor
        turnoutsNumber = self.layoutEditor.turnoutList.size()
        for i in range(turnoutsNumber) :
            layoutTurnout = self.layoutEditor.turnoutList.get(i)
            # If the turnout is a double crossover, process it
            if layoutTurnout.getTurnoutType() == AutoDispatcher.DOUBLE_XOVER :
                ADxover(layoutTurnout)

        # Retrieve Layout Editor tracks and save their original width
        nTracks = self.layoutEditor.trackList.size()
        if nTracks < 1 :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, 
              "Warning: layout contains no tracks!")
        else :
            for i in range(nTracks) :
                track = self.layoutEditor.trackList.get(i)
                blockName = track.getBlockName()
                block = ADblock.getByName(blockName)
                # Process track only if block contained in some section
                if block != None :
                    block.addTrack(ADtrack(track))
        # Reorganize sections' directions
        # Blocks within each section are listed in arbitrary order.
        # Make sure that the order is consistent.
        # Starting from the first section, mark sections with inconsistent 
        # order as "reversed"
        self.processedSections = []
        # If all sections are connected, the following for-loop
        # is actually performed ony once
        for section in ADsection.getList() :
            self.__alignSection__(section, False)
        # Find and mark reversing tracks (if any)
        self.findTransitPoints()
        
# INTERNAL METHODS OF getLayoutData ==============

    def __alignSection__(self, section, changeOrientation) :
        # Internal method. Recursively aligns all sections
        # If section already processed, exit
        if section in self.processedSections :
            return
        # Take note that this section was processed (in order to avoid a loop!)
        self.processedSections.append(section)
        # Change orientation, if needed
        if changeOrientation :
            section.setReversed(not section.isReversed())
        # Scan both entries and exits
        for direction in [False, True] :
            for entry in section.getEntries(direction) :
                nextSection = entry.getExternalSection()
                # Adjust orientation of each entry/exit
                self.__alignSection__(nextSection, 
                  self.__hasOrientationChanged__(entry, direction))

    def findTransitPoints(self) :
        # Find transit points on possible reversing tracks
        # Train color will change when transiting these points
        for section in ADsection.getList() :
            # Scan both entries and exits
            for direction in [False, True] :
                for entry in section.getEntries(direction) :
                    change = self.__hasOrientationChanged__(entry, direction)
                    entry.setDirectionChange(change)

    def __hasOrientationChanged__(self, startEntry, direction) :
        # Internal method. 
        # Checks if two sections are aligned, by verifying
        # if the start section is included in the entries of 
        # destination section (keeping into account direction)
        startSection = startEntry.getInternalSection()
        startBlock = startEntry.getInternalBlock()
        endSection = startEntry.getExternalSection()
        endBlock = startEntry.getExternalBlock()
        for endEntry in endSection.getEntries(not direction) :
            if (endEntry.getExternalSection() == startSection and
               endEntry.getExternalBlock() == startBlock and
               endEntry.getInternalBlock() == endBlock) :
                # section found, directions are consistent
                return False
        # section not found, directions are inconsistent
        return True                    
    
# DEFAULT SETTINGS ==============

    def defaultSettings(self) :
        # Set sections' settings to default value
        # They may then be overriden by user (or by settings stored on disk)           
        # Mark transit-only sections
        # (Sections where trains cannot stop without blocking traffic)
        for section in ADsection.getList() :
            section.setDefault()

# USER SETTINGS ==============
 
    def userSettings(self):

    # Override default settings with user defined settings loaded from disk
    # (or embedded settings for demo panels)
    
        # First of all define direction names, based on user choices
        if ADsettings.ccwStart.strip() != "" :
            self.setDirections()
        self.setSignals()

        # Set user defined one-way and transit-only sections
        ADsection.putSectionsTable()

        # Adjust entry points (in case some sections were manually flipped)
        self.findTransitPoints()
        
        # Mark sections where gridlock situations can occur
        ADgridGroup.create()

        # Set manual indicator sensors to INACTIVE, otherwise they can be
        # confused with signals (owing to the red aspect)
        for section in ADsection.getList() :
            if not section.isManual() :
                section.setManual(False)

        # Set user defined brake, stop, assignment and safe-point blocks
        ADsection.putBlocksTable()

        # Retrieve info from JMRI roster and create locomotives with 
        # standard speeds
        AutoDispatcher.log("Getting JMRI locomotives roster")           
        jmriRoster = Roster.getDefault().matchingList(None, None, None, None,
          None, None, None)
        for i in range(jmriRoster.size()) :
            name = jmriRoster.get(i).getId()
            address = int(jmriRoster.get(i).getDccAddress())
            ADlocomotive(name, address, None, True)
            # Roster contains also long address indicator
            # We should likely use it but, getThrottle method
            # of AbstractAutomaton don't cares!

        # Get consists from JMRI
        # Any consist must be defined before starting AutoDispatcher
        consistMan = InstanceManager.getDefault(jmri.ConsistManager)
        consists = consistMan.getConsistList()
        # Any consist?
        if len(consists) == 0 :
            # No, make sure consist file was loaded
            consistFrame = ConsistToolFrame()
            # and retry
            consists = consistMan.getConsistList()
            consistFrame.dispose()
        for a in consists :
            c = consistMan.getConsist(a)
            # Get consist address
            a = a.getNumber()
            # Get consist ID
            i = c.getConsistID()
            # Create locomotive
            l = ADlocomotive(i, a, None, True)
            # Get address of first locomotive in consist
            ll = c.getConsistList()
            if len(ll) > 0 :
                f = ll[0].getNumber()
                if f != a :
                    # Record address of first locomotive
                    # Function commands will be sent to it.
                    # May need revising. 
                    l.leadLoco = f

        # Match jmri roster with user defined locomotives (and consists) and set 
        # relevant speeds
        for l in AutoDispatcher.locomotives :
            ll = ADlocomotive.getByName(l[0])
            if ll == None :
                ll = ADlocomotive(l[0], l[1], l[2], False)
            else :
                ll.setSpeedTable(l[2])
            ll.setMomentum(l[3], l[4])
            ll.runningTime = l[5]
            ll.mileage = l[6]
        # Now build trains roster, based on user settings
        if len(self.trains) > 0 :
            AutoDispatcher.log("Placing trains on tracks :-)")
            ADtrain.buildRoster(self.trains)
 
    def setDirections(self) :
        # Find mapping between internal direction and user defined direction
        # To this purpose, two sections are specified, the shorter route 
        # between them is assumed to be CCW direction (or whatever name user 
        # chose). Return True if the selection was successful.
        
        # Check correctness of section names
        result = True
        startSection = ADsection.getByName(ADsettings.ccwStart)
        if startSection == None :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Unknown start section " + ADsettings.ccwStart 
              + " in direction definition")
            ADsettings.ccwStart = ""
            result =  False
        endSection = ADsection.getByName(ADsettings.ccwEnd)
        if endSection == None :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Unknown end section " + ADsettings.ccwEnd
              + " in direction definition")
            ADsettings.ccwEnd = ""
            result =  False
        if result :
            # Find route in direction 0
            route0 = ADautoRoute(startSection, endSection, 0, False)
            # Find route in direction 1
            route1 = ADautoRoute(startSection, endSection, 1, False)
            # Which one is shorter?
            len0 = len(route0.step)
            len1 = len(route1.step)
            if len0 == 0 or (len1 != 0 and len1 < len0) :
                ADsettings.ccw = 1
            else :
                ADsettings.ccw = 0
        return result

    def setSignals(self) :
        # Set signals at section exits, using direction names to identify 
        # signal heads.
        # Assume that the name of signal head is "H" + name of the 
        # section + (lower case) direction
        # Example HS01east is the East exit signal of section S01
        # User can override the choice by manually selecting a signal head
        # or signal mast in the "Sections" window
        extension = [ADsettings.directionNames[0].lower(), 
          ADsettings.directionNames[1].lower()]
        if ADsettings.ccw != 0:
            extension.reverse()
        # Scan all sections
        for s in ADsection.getList() :
            # In both directions
            for i in range(2) :
                newName = "H" + s.getName() + extension[i]
                # Check if a SignalMast or a SignalHead with such a name exists
                newSignal = ADsignalMast.getByName(newName)
                newHead = InstanceManager.getDefault(jmrix.SignalHeadManager).getSignalHead(newName)
                # Assign a new SignalMast only if it was not yet assigned
                # or it was automatically created in a previous call
                # (provided we found a replacement!) Replacing signals
                # is needed since user could change direction names.
                if (s.signal[i] == None or (s.signal[i].getName() != newName and
                  not s.signal[i].hasHead() and s.signal[i].inUse < 2 and 
                  s.signal[i].signalType == ADsettings.signalTypes[0] and
                  (newSignal != None or newHead != None))) :
                    # If a SignalMast was cretaing using the old direction name
                    # delete it
                    if s.signal[i] != None :
                        s.signal[i].changeUse(-2)
                    # Now assign/create the new signal
                    if newSignal == None :
                        s.signal[i] = ADsignalMast.provideSignal(newName)
                    else :
                        s.signal[i] = newSignal
                    # Take note that the signal is being used
                    s.signal[i].changeUse(1)
            # Try and find the (optional) sensor to set the section under 
            # manual control
            if s.manualSensor == None :
                sensorName = s.getName() + "man"
                if sensorName in ADsection.sensorNames :
                    s.manualSensor = InstanceManager.sensorManagerInstance().getSensor(sensorName)

# OPERATIONS ==============

    def init(self):
    # Start - Performed each time "Start" button is clicked
        # Clear number of running trains
        AutoDispatcher.runningTrains = 0
        # Start fast clock listener
        AutoDispatcher.fastBase.addMinuteChangeListener(AutoDispatcher.fastListener)
        # Set block occupancy listeners
        ADblock.setListeners()
        # Set sections manual control sensors listeners
        ADsection.setListeners()
        # Let other methods and classes know that we started
        AutoDispatcher.loop = True
        AutoDispatcher.stopped = False
        # Check the initial occupancy state of sections
        for section in ADsection.getList() :
            section.occupied = True
            section.empty = False
            section.checkOccupancy()
            section.occupied = False
            if not section.empty :
                section.setOccupied()
        # Force occupation of sections assigned to trains
        for train in ADtrain.getList() :
            for section in train.previousSections :
                section.empty = not section.occupied
                section.occupied = True
                section.allocate(train, train.direction)
            if (train.destination != train.lastRouteSection and
               train.lastRouteSection != None) :
                train.lastRouteSection.allocate(train, train.direction)
        # Set initial width of tracks
        for block in ADblock.getList() :
            block.adjustWidth()
        # Place/remove train names in jmri Blocks
        # (only if user enabled this option)
        if ADsettings.blockTracking :
            for section in ADsection.getList() :
                section.changeTrainName()
        # Set click-mode of panel SignalHeadIcons to "alternate held"
        # to avoid that user may change signal aspects
        for s in AutoDispatcher.signalClick :
            s[0].setClickMode(2)        
        # Set variables for the handle method
        self.train = 0
         # Force updating of LayoutEditor panel
        AutoDispatcher.repaint = True
        # Enable user interface buttons (unless errors occurred)
        if not AutoDispatcher.error :
            AutoDispatcher.mainFrame.enableButtons(False)
            if AutoDispatcher.simulation :
                # Separate thread, advancing trains in simulation mode
                start_new_thread(self.autoStep, ())
            # Separate thread, controlling trains speed
            start_new_thread(self.speedControl, ())
            # Separate thread, controlling locomotives maintenance time
            start_new_thread(self.maintenanceControl, ())
            # Separate thread, blinking signal head icons on panle
            start_new_thread(self.blinkSignals, ())
            # If power is off we start in "paused" mode
            if ADpowerMonitor.powerOn :
                AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Have fun!")
            else :
                AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Power Off!")
                ADpowerMonitor.savePause = True
                self.stopAll()

# BACKGROUND TASK ==============

    def handle(self):
        # Handle thread.  Runs in background and takes care of trains departure
        # Exit if there was an error or user clicked "STOP" button
        if AutoDispatcher.error or not AutoDispatcher.loop :
            # Cleanup before exiting
            # Wait for all trains to stop
            self.waitForStop()
            # Release engineers and throttles (if any)
            for t in ADtrain.getList() :
                if t.engineer != None :
                    t.releaseEngineer()
                locomotive = t.locomotive
                if locomotive != None :
                    locomotive.releaseThrottle()
            # Inform other threads that program is exiting
            AutoDispatcher.stopped = True
            # Remove listeners
            ADblock.removeListeners()
            ADsection.removeListeners()
            AutoDispatcher.fastBase.removeMinuteChangeListener(AutoDispatcher.fastListener)
            # Restore original block colors and track width
            for block in ADblock.getList() :
                block.restore()
            self.layoutEditor.redrawPanel()
            # Restore original click mode of signalHeadIcons on panel
            for s in AutoDispatcher.signalClick :
                s[0].setClickMode(s[1])
            # Are we quitting?
            if AutoDispatcher.exiting :
                # Yes, give user the possibility of saving settings and 
                # trains position
                self.saveBeforeExit()
            else :
                # Script not quitting yet
                # Allow user to change settings
                AutoDispatcher.mainFrame.enableButtons(True)
                # Allow user to restart operations, unless errors occurred
            if not AutoDispatcher.error :
                ADmainMenu.startButton.enabled = True
                AutoDispatcher.chimeLog(ADsettings.START_STOP_SOUND, "Goodbye!")
            return 0
        # Normal loop
        # Redraw Layout Editor panel, if needed
        if AutoDispatcher.repaint :
            AutoDispatcher.repaint = False
            self.layoutEditor.redrawPanel()
        # Start trains that are eligible (unless script is paused)
        if not AutoDispatcher.paused :
            # At each iteration we try and start one train
            if len(ADtrain.trains) > 0 :
                if self.train >= len(ADtrain.trains) :
                    self.train = 0
                ADtrain.trains[self.train].startIfReady()
                self.train += 1
        # Wait for a while, letting other threads do their work
        self.waitMsec(100)
        return 1

    def waitForStop(self) :
        # Wait until all trains halt (unless script stopped)
        if AutoDispatcher.stopped :
            return
        # Make sure script is not paused, otherwise we risk to wait forever
        wasPaused = AutoDispatcher.paused
        self.resume()
        # If we are in simulation mode, make sure layout is powered
        if AutoDispatcher.simulation :
            AutoDispatcher.powerMonitor.setPower(PowerManager.ON)
        # Wait only if layout is powered
        # This can actually result in inconsistent situations, but
        # we can neither switch power on (causing shorts or collisions)
        # neither wait here forever!
        if ADpowerMonitor.powerOn :
            for t in ADtrain.trains :
                # Lets wait if locomotive's speed is > 0, or
                # running indicator is set (if the train is being run
                # manually or by another script, we may not know
                # which locomotive is used), or
                # train is being started right now
                while (t.running or (t.locomotive != None and
                t.locomotive.getThrottleSpeed() > 0) or ADtrain.turnoutsBusy) :
                    self.waitMsec(100)
                    # Since train is running, occupation state of blocks and
                    # sections may change and the panel may need redrawing
                    if AutoDispatcher.repaint :
                        AutoDispatcher.repaint = False
                        self.layoutEditor.redrawPanel()
        # Set script into "pause mode", if it was so when this method was called
        if wasPaused :
            self.stopAll()

    def stopAll(self) :
        # Stop trains (or remove power) when paused or an error occurs
        # Ignore, if user disabled "Pause" option
        if(ADsettings.pauseMode == ADsettings.IGNORE or
          AutoDispatcher.exiting) :
            return
        # Make sure we are not called more than once 
        # (owing to Jython lack of synchronization)
        if not ADmainMenu.resumeButton.enabled :
            ADmainMenu.resumeButton.enabled = True
            ADmainMenu.pauseButton.enabled = False
            ADmainMenu.stopButton.enabled = False
            # Did user chose to power-off layout?
            if(ADsettings.pauseMode == ADsettings.POWER_OFF) :
                # Stop layout
                AutoDispatcher.paused = True
                AutoDispatcher.powerMonitor.setPower(PowerManager.OFF)
            else :
                # STOP_TRAINS case
                for train in ADtrain.getList() :
                    train.pause()
        AutoDispatcher.paused = True

    def resume(self) :
        # Resume script after a pause
        # Ignore if we were not paused
        if not AutoDispatcher.paused :
            return
        AutoDispatcher.paused = False
        if ADsettings.pauseMode == ADsettings.POWER_OFF :
           # Switch power ON
            AutoDispatcher.powerMonitor.setPower(PowerManager.ON)
        else :
            # or restart trains
            for train in ADtrain.getList() :
                train.resume()
        ADmainMenu.resumeButton.enabled = False
        ADmainMenu.pauseButton.enabled = True
        ADmainMenu.stopButton.enabled = AutoDispatcher.loop
            
    def saveBeforeExit(self) :
        # Give user the possibility of saving settings and trains position
        # before quitting
        # Check if trains settings or position were changed
        if AutoDispatcher.trainsDirty :
            # Yes, allow user to save them
            msg = "Save trains settings and positions before quitting? "
            if not ADpowerMonitor.powerOn :
                msg += ("(Warning, power is off and trains position could"
                  + " be inconsistent!)")
            if (JOptionPane.showConfirmDialog(None, msg, "Confirmation",
              JOptionPane.YES_NO_OPTION) == 0) :
                self.saveTrains()
                self.saveSettings()
                return
        # Check if settings were changed
        if AutoDispatcher.preferencesDirty :
            # Yes, allow user to save them
            if (JOptionPane.showConfirmDialog(None, 
              "Save preferences before quitting? ", "Confirmation", 
              JOptionPane.YES_NO_OPTION) == 0) :
                self.saveSettings()

# SPEED CONTROL TASK ==============

    def speedControl(self) :
        # Handle to control train speeds
        # Executed in a separate thread
        # Loops until the background handle exits
        while not AutoDispatcher.stopped :
            # Take current time - Will be used when testing
            # for stalled trains and to avoid throttle timeout
            # (on some command stations)
            currentTime = System.currentTimeMillis()
            # Check all trains
#            if not AutoDispatcher.paused :
            for train in ADtrain.getList() :
                # Check for train stalled
                if (train.running and ADsettings.stalledDetection !=
                  ADsettings.DETECTION_DISABLED and train.lastMove != -1L
                  and not AutoDispatcher.paused
                  and not AutoDispatcher.simulation) :
                    if (currentTime - train.lastMove >
                      ADsettings.stalledTime) :
                        train.lastMove = -1L
                        if (ADsettings.stalledDetection ==
                           ADsettings.DETECTION_PAUSE) :
                            AutoDispatcher.instance.stopAll()
                        AutoDispatcher.chimeLog(ADsettings.STALLED_SOUND,
                          "Train " + train.getName() + " stalled in section \""
                          + train.section.getName() + "\"")
                # Take care of speed changes only for trains with locomotive 
                # and throttle
                locomotive = train.locomotive
                if locomotive != None and locomotive.throttle != None :
                    # Do nothing if train already running at target speed.
                    # Compare speeds rounding them, to cope with different 
                    # precisions in Jython (double) and Java (float)
                    targetSpeed = round(locomotive.targetSpeed, 4)
                    presentSpeed = round(locomotive.currentSpeed, 4)
                    if presentSpeed < 0 :
                        presentSpeed = 0
                    if presentSpeed != targetSpeed :
                        # Train not running at target speed
                        # Should we stop train ?
                        if targetSpeed < 0 :
                            # Yes - Stop it immediately!
                            if presentSpeed > 0 :
                                locomotive.throttle.setSpeedSetting(targetSpeed)
                                locomotive.rampingSpeed = locomotive.currentSpeed = 0
                                locomotive.currentSpeedSwing.setText("0")
                                locomotive.updateMeter()
                        else :
                            # Should we increase or decrease speed?
                            if presentSpeed < targetSpeed :
                                # Acceleration
                                delta = locomotive.accStep
                            else :
                                # Deceleration (adjusted for self-learning)
                                delta = (-locomotive.decStep -
                                  locomotive.decAdjustment)
                            # Should we progressively vary speed?
                            if delta == 0 :
                                # No - Apply target speed
                                locomotive.throttle.setSpeedSetting(targetSpeed)
                                locomotive.currentSpeed = targetSpeed
                                locomotive.lastSent = currentTime
                                locomotive.rampingSpeed = targetSpeed
                                if targetSpeed <= 0 :
                                    locomotive.updateMeter()
                                locomotive.currentSpeedSwing.setText(
                                  str(targetSpeed))
                            else :
                                # Progressive speed change
                                delta *= ADsettings.speedRamp
                                locomotive.rampingSpeed += delta
                                # Make sure speed is within target 
                                if ((locomotive.rampingSpeed > targetSpeed 
                                   and delta > 0) or (locomotive.rampingSpeed <
                                   targetSpeed and delta < 0)) :
                                    locomotive.rampingSpeed = targetSpeed
                                # Were we braking in self-learning mode?
                                if locomotive.brakeAdjusting :
                                    #Yes, did we reach target (i.e. minimum) 
                                    # speed?
                                    if locomotive.rampingSpeed == targetSpeed :
                                        # Inform self-learning method
                                        locomotive.learningEnd()
                                # Apply new speed only if change is meaningful
                                locomotive.rampingSpeed = round(
                                  locomotive.rampingSpeed,4)
                                # Round values based on number of speed steps
                                # used by the locomotive
                                if (round(float(locomotive.rampingSpeed) *
                                  locomotive.stepsNumber) !=
                                  round(float(presentSpeed) *
                                  locomotive.stepsNumber)) :
                                      # Meaningful change - apply new speed
                                    locomotive.throttle.setSpeedSetting(
                                      locomotive.rampingSpeed)
                                    locomotive.currentSpeed = locomotive.rampingSpeed
                                    locomotive.lastSent = currentTime
                                       # Update locomotives window (if open)
                                    locomotive.currentSpeedSwing.setText(
                                      str(locomotive.rampingSpeed))
                                    # If train stopped, update mileage and
                                    # operation hours
                                    if targetSpeed <= 0 :
                                        locomotive.updateMeter()
                    # If requested, periodically change speed (even if not 
                    # needed), in order to avoid time-out.
                    # Some command stations stop a locomotive if it did not
                    # receive commands for a while.
                    if (locomotive.rampingSpeed > 0 and ADsettings.maxIdle 
                       > 0 and currentTime - locomotive.lastSent >
                       ADsettings.maxIdle) :
                        locomotive.throttle.setSpeedSetting(
                          locomotive.rampingSpeed)
                        locomotive.currentSpeed = locomotive.rampingSpeed
                        locomotive.lastSent = currentTime
            # Wait n/10 of second before repeating
            # This thread is no time critical, since it only varies speeds 
            # of running trains .
            # Trains stopping is dealt with by block's ChangeListener
            sleep(float(ADsettings.speedRamp)*0.1)

# LOCOMOTIVES MAINTENANCE TIME CONTROL TASK ==============

    def maintenanceControl(self) :
        # Periodically check if any locomotive exceedes the maximum
        # mileage or operation time 
        while not AutoDispatcher.stopped :
            # Convert hours to milliseconds
            intTime = int(ADsettings.maintenanceTime * 3600000.)
            for l in ADlocomotive.getList() :
                newWarnedTime = newWarnedMiles = False
                if (ADsettings.maintenanceTime > 0. 
                  and l.runningTime > intTime) :
                    newWarnedTime = True
                if (ADsettings.maintenanceMiles > 0. 
                  and l.mileage > ADsettings.maintenanceMiles) :
                    newWarnedMiles = True
                # make sure the warning is given only once
                if (not l.warnedTime and not l.warnedMiles and
                  (newWarnedTime or newWarnedMiles)) :
                    AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, 
                      "Locomotive " + l.getName()  + " needs maintenance")
                l.warnedTime = newWarnedTime
                l.warnedMiles = newWarnedMiles
                # If mileage and opertions time are being displayed,
                # update swing fields
                if AutoDispatcher.locosFrame != None :
                    l.outputMileage()
            # Not time critical: wait for one minute
            sleep(60.)

    def blinkSignals(self) :
        # Separate thread to blink flashing signals on LayoutEditor panel
        # (does not affect blinking of model signals on layout)
        # Quit if there are no Signal Heads on the panel
        if len(self.layoutEditor.signalHeadImage) == 0 :
            return
        
        # Retrieve signalHeads
        signals = []
        aspectNames = ["SignalHeadStateRed", "SignalHeadStateGreen",
            "SignalHeadStateYellow", "SignalHeadStateLunar", 
            "SignalHeadStateFlashingRed", "SignalHeadStateFlashingGreen",
            "SignalHeadStateFlashingYellow", "SignalHeadStateFlashingLunar",
            "SignalHeadStateDark"]
        hasLunar = True
       # Are we running a JMRI version released after 24/9/2010?
        newIcons = not callable(getattr(self.layoutEditor.signalHeadImage[0],
              "getRedIcon", None))
        if newIcons :
            # New JMRI version (use getIcon, setIcon)
            # Get aspect names
            rbean = java.util.ResourceBundle.getBundle("jmri.NamedBeanBundle")
            for i in range(len(aspectNames)) :
                aspectNames[i] = rbean.getString(aspectNames[i])
            for s in self.layoutEditor.signalHeadImage :
                a = []
                for i in range(7) :
                    a.append(s.getIcon(aspectNames[i]))
                signals.append([s, s.getIcon(aspectNames[8]), a])
        else:
            # Old JMRI version (use getRedIcon... setRedIcon...)
            # Is Lunar aspect (introduced since JMRI 2.7.8) supported?
            hasLunar = callable(getattr(self.layoutEditor.signalHeadImage[0],
              "getFlashLunarIcon", None))
            for s in self.layoutEditor.signalHeadImage :
                a = []
                a.append([s.getRedIcon(), s.getFlashRedIcon()])
                a.append([s.getGreenIcon(), s.getFlashGreenIcon()])
                a.append([s.getYellowIcon(), s.getFlashYellowIcon()])
                if hasLunar :
                    a.append([s.getLunarIcon(), s.getFlashLunarIcon()])
                signals.append([s, s.getDarkIcon(), a])
        on = True
        cycling = False
        # Loop until handle exits
        while not AutoDispatcher.stopped :
            # Did user enable flashing?
            if ADsettings.flashingCycle > 0 :
                cycling = True
                for s in signals :
                    a = s[2]
                    # Light-on half cycle?
                    if on :
                        # Yes - switch on signal head
                        if newIcons :
                            for i in range(3) :
                                if a[i+4] != None :
                                    s[0].setIcon(aspectNames[i+4], a[i])  
                        else :
                            s[0].setFlashRedIcon(a[0][0])
                            s[0].setFlashGreenIcon(a[1][0])
                            s[0].setFlashYellowIcon(a[2][0])
                            if hasLunar :
                                s[0].setFlashLunarIcon(a[3][0])
                    else :
                        # No - set signal head to dark aspect
                        if newIcons :
                             for i in range(3) :
                                 if a[i+4] != None :
                                    s[0].setIcon(aspectNames[i+4], s[1])  
                        else:
                            s[0].setFlashRedIcon(s[1])
                            s[0].setFlashGreenIcon(s[1])
                            s[0].setFlashYellowIcon(s[1])
                            if hasLunar :
                                s[0].setFlashLunarIcon(s[1])
                # Force panel redrawing
                AutoDispatcher.repaint = True
                # Invert cycle
                on = not on
                # Wait for half cycle
                sleep(ADsettings.flashingCycle * 0.5)
                continue
            # User has disabled flashing
            # Was flashing enabled before?
            elif cycling > 0 :
                # Yes - take note we disable it
                cycling = False
                # restore flashing icon in signal heads
                for s in signals :
                    a = s[2]
                    if newIcons :
                        for i in range(3) :
                            if a[i+4] != None :
                                s[0].setIcon(aspectNames[i+4], a[i+4]) 
                    else:
                        s[0].setFlashRedIcon(a[0][1])
                        s[0].setFlashGreenIcon(a[1][1])
                        s[0].setFlashYellowIcon(a[2][1])
                        if hasLunar :
                            s[0].setFlashLunarIcon(a[3][1])
                # Force panel redrawing
                AutoDispatcher.repaint = True
            # Wait a second
            sleep(1.0)
        # Exiting thread
        # Restore original aspects
        for s in signals :
            a = s[2]
            if newIcons :
                for i in range(3) :
                    if a[i+4] != None :
                        s[0].setIcon(aspectNames[i+4], a[i+4]) 
            else:
                s[0].setFlashRedIcon(a[0][1])
                s[0].setFlashGreenIcon(a[1][1])
                s[0].setFlashYellowIcon(a[2][1])
                if hasLunar :
                    s[0].setFlashLunarIcon(a[3][1])
        # Force panel redrawing
        AutoDispatcher.repaint = True

# I/O =================

    # Data are stored as Java objects.
    # Saving them as XML file would be nicer, but would
    # require adding new DTD files in JMRI
    
    def saveSettings(self) :
        # Save settings to disk
        # Get current settings
        newSections = ADsection.getSectionsTable()
        newBlocks = ADsection.getBlocksTable()
        # Compare current settings with those
        # automatically computed by the program at startup
        for i in range(len(newSections)) :
            newS = newSections[i]
            autoS = self.autoSections[i]
            for j in range(1, 3) :
                # Is user selection different from settings computed by program?
                if newS[j] != autoS[j] :
                    # Yes - if new settings are "No selection",
                    # set value to "NO"
                    if newS[j] == "" :
                        newS[j] = "NO"
        for i in range(len(newBlocks)) :
            newS = newBlocks[i]
            autoS = self.autoBlocks[i]
            for j in range(1, len(autoS)) :
                if j == 6 or j == 12 :
                    continue
                # Is user selection different from settings computed by program?
                if newS[j] != autoS[j] :
                    # Yes - if new settings are "No selection", 
                    # set value to "NO"
                    if newS[j] == "" :
                        newS[j] = "NO"
        # Write to file
        try :
            outs=ObjectOutputStream(FileOutputStream(self.settingsFile))
            try :
                ADsettings.save(outs, newSections, newBlocks)
                ADmainMenu.saveSettingsButton.enabled = False
                AutoDispatcher.preferencesDirty = False
                AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
                  "Preferences saved to disk")
            finally :
                outs.close()        
        except IOException, ioe :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Error writing to file " + self.settingsFile)

    def loadSettings(self) :
        # Get settings from disk
        ins = None
        try :
            ins=ObjectInputStream(FileInputStream(self.settingsFile))
            try :
                ADsettings.load(ins.readObject())
            except IOException, ioe :
                AutoDispatcher.log("Warning, could not read file " +
                  self.settingsFile + ". Default layout settings will be used.")
            ins.close()
            ins = None
        except IOException, ioe :
            if ins != None :
                ins.close()

    def saveLocomotives(self) :
        locomotives = []
        locos = ADlocomotive.getNames()
        locos.sort()
        # Extract persistent info
        for l in locos :
            ll = ADlocomotive.getByName(l)
            locomotives.append([ll.name, ll.address, ll.speed,
              ll.acceleration, ll.deceleration, ll.runningTime, ll.mileage])
        # Write to file
        try :
            outs=ObjectOutputStream(FileOutputStream(self.locoFile))
            try :
                outs.writeObject(locomotives)
                AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
                  "Locomotives changes saved to disk")
            finally :
                outs.close()        
        except IOException, ioe :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Error writing file " + self.locoFile)

    def loadLocomotives(self) :
        try :
            ins=ObjectInputStream(FileInputStream(self.locoFile))
            try :
                newLocomotives=ins.readObject()
                AutoDispatcher.locomotives = newLocomotives
            except IOException, ioe :
                AutoDispatcher.log("Warning, could not read file " +
                  self.locoFile + " Default settings will be used.")
            ins.close()
        except IOException, ioe :
            return
            
    def saveTrains(self) :
        # First of all, save locomotives
        self.saveLocomotives()
        trains = []
        # Extract persistent info
        for t in ADtrain.getList() :
            if t.direction == ADsettings.ccw :
                direction = ADsettings.directionNames[0]
            else :
                direction = ADsettings.directionNames[1]
            # Force saving of schedule status in the stack
            # (This is why trains must be stopped before saving!)
            t.schedule.push()
            # Copy stack contents
            stack = []
            stack.extend(t.schedule.stack)
            # Restore original stack contents
            t.schedule.pop()
            # Get current section name
            if t.section != None :
                sectionName = t.section.getName()
            else :
                sectionName = ""
            # Get pending commands (if any)
            pendingCommands = []
            for i in range(len(t.itemSections)) :
                section = t.itemSections[i].getName()
                action = t.items[i].action
                value = t.items[i].value
                message = t.items[i].message
                # Convert instances (sections or signals) to names
                if (action == ADschedule.WAIT_FOR or
                   action == ADschedule.MANUAL_OTHER or
                   action == ADschedule.HELD or
                   action == ADschedule.RELEASE or
                   action == ADschedule.IFH) :
                    value = value.getName()
                pendingCommands.append([section, action, value, message])
            # Get name of last section on the route
            if t.lastRouteSection == None :
                lastSection = ""
            else :
                lastSection = t.lastRouteSection.getName()
            # Add a record
            trains.append([t.name, sectionName, direction, 
                t.resistiveWheels, t.schedule.text, t.trainAllocation, 
                t.trainLength, stack, t.locoName, t.reversed,
                pendingCommands, t.engineerName, lastSection, t.trainSpeed,
                t.brakingHistory, t.canStopAtBeginning, t.startAction])
        # Write everything to file
        try :
            outs=ObjectOutputStream(FileOutputStream(self.trainFile))
            try :
                outs.writeObject(trains)
                AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
                  "Trains status saved to disk")
            finally :
                outs.close()        
        except IOException, ioe :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Error writing to file " + self.trainFile)
        ADmainMenu.saveTrainsButton.enabled = AutoDispatcher.trainsDirty = False
 
    def loadTrains(self) :
        # Read train data into memory
        # Appropriate data conversions will be performed later by
        # ADtrain.buildRoster method
        try :
            ins=ObjectInputStream(FileInputStream(self.trainFile))
            try :
                newTrains=ins.readObject()
                self.trains = newTrains
            except IOException, ioe :
                AutoDispatcher.log("Warning, could not read file " +
                  self.trainFile + ", Default train settings will be used.")
            ins.close()
        except IOException, ioe :
            return
                
    # SIMULATION =================

    # The following methods are used only in simulation mode

    def oneStep(self) :
        # Advance every train of one block, unless it
        # reached the stop block in front of a red signal.
        # Ignore calls if stopped, paused or without power
        if (AutoDispatcher.stopped or AutoDispatcher.paused or not
           ADpowerMonitor.powerOn) :
            return
        # Move running trains one block ahead
        for t in ADtrain.getList() :
            # Did train advance during previous call?
            if t.simSensor != None :
                # Yes, remove it from previous block
                t.simSensor.setKnownState(INACTIVE)
                t.simSensor = None
                continue
            # No - Is train running?
            if (not t.running or (t.locomotive != None and
              t.engineerSetLocomotive != None and
              t.locomotive.getThrottleSpeed() <= 0)) :
                continue
            # Is the train anywhere?
            section = t.section
            direction = t.direction
            if section != None :
                # Yes, find present block
                blocks = section.getBlocks(direction)
                ind = -1
                for i in range(len(blocks)) :
                    if blocks[i].getOccupancy() == Block.OCCUPIED :
                        ind = i
                        break
                if ind < 0 :
                    continue
                # Did train reach a stop block in front of a red signal?
                if (blocks[ind] == section.stopBlock[direction] and
                   section.getSignal(direction).getIndication() == 0) :
                    continue
                # No - get sensor of current block
                currentSensor = blocks[ind].getOccupancySensor()
                # Is this block section's exit block?
                block = None
                for e in t.entriesAhead :
                    if e.getInternalBlock() == blocks[ind] :
                        # Yes - Get entry block in the next section
                        block = e.getExternalBlock()
                        while e in t.entriesAhead :
                            t.entriesAhead.pop(0)
                        break
                if block == None :
                    # Train did not reach the exit block
                    # Get next block in the same section
                    ind += 1
                    # Should not occur, but it's better to check
                    if ind >= len(blocks) :
                        continue
                    block = blocks[ind]
               # Get next sensor
                nextSensor = block.getOccupancySensor()
                # Move train from current block to next one,
                # making sure the block has a sensor!
                if nextSensor != currentSensor and nextSensor != None :
                    nextSensor.setKnownState(ACTIVE)
                    # Take note that next time we must release the old block
                    t.simSensor = currentSensor

    def autoStep(self) :
        # Step trains forward every two seconds
        while not AutoDispatcher.stopped :
            # Call "oneStep" once per second
            # actual advancement will occur once every two seconds,
            # since during one call next block is occupied
            # and during next call previous block is released
            sleep(1.0)
            if ADmainMenu.autoButton.isSelected() :
                self.oneStep()
                
# CHILDREN CLASSES ==============

# SECTION ==============

class ADsection (PropertyChangeListener) :
    # Encapsulates JMRI Section, adding fields and methods used 
    # by AutoDispatcher

    # Section colors enumeration
    EMPTY_SECTION_COLOR = 0
    MANUAL_SECTION_COLOR = 1
    OCCUPIED_SECTION_COLOR = 2
    CCW_ALLOCATED_COLOR = 3
    CW_ALLOCATED_COLOR = 4
    CCW_TRAIN_COLOR = 5
    CW_TRAIN_COLOR = 6
    
    # Static variables
    userNames ={}       # dictionary of sections by userName
    systemNames ={}     # dictionary of sections by systemName
    sensorNames = None  # list of sensors that may be used for manual control
                        # (i.e. those not used for block occupancy)

    # STATIC METHODS

    def getList() :
        return ADsection.systemNames.values()
    getList = ADstaticMethod(getList)

    def getNames() :
        return ADsection.userNames.keys()
    getNames = ADstaticMethod(getNames)

    def getByName(name) :
        if ADsection.userNames.has_key(name) :
            return ADsection.userNames[name]
        return ADsection.systemNames.get(name, None)
    getByName = ADstaticMethod(getByName)

    def getSectionsTable() :
        # Creates a table containing sections' attributes as strings.
        # Uses current settings in order to translate direction names.
        # The output table, sorted by section name, contains:
        #   Section Name
        #   One-Way Indicator
        #   Transit-Only (and burst) Indicator
        #   Signal names
        #   Manual flipping Indicator
        #   Name of manual control sensor
        #   Signal held indicators
        out = []
        for s in ADsection.systemNames.values() :
            if s.direction == 3 :
                direction = ""
            elif s.direction == ADsettings.ccw + 1 :
                direction = ADsettings.directionNames[0]
            else :
                direction = ADsettings.directionNames[1]
            if s.burst :
                burst = "+"
            else :
                burst = ""
            if s.transitOnly[ADsettings.ccw] :
                if s.transitOnly[1-ADsettings.ccw] :
                    transitOnly = (ADsettings.directionNames[0] + "-" 
                      + ADsettings.directionNames[1] + burst)
                else :
                    transitOnly = ADsettings.directionNames[0] + burst
            elif s.transitOnly[1-ADsettings.ccw] :
                transitOnly = ADsettings.directionNames[1] + burst
            else :
                transitOnly = ""
            signals =["", ""]
            heldIndicators =["", ""]
            for i in range(2) :
                if s.signal[i] != None :
                    signals[i] = s.signal[i].name
                    if s.signal[i].isHeld() :
                        heldIndicators[i] = "Held"
            if s.manuallyFlipped :
                inverted = "INVERTED"
            else :
                inverted = ""
            if s.isManual() :
                manualSection = "Manual"
            else :
                manualSection = ""
            if s.manualSensor == None :
                manualSensor = ""
            else :
                manualSensor = s.manualSensor.getUserName()
                if manualSensor == None or manualSensor == "" :
                    manualSensor = s.manualSensor.getSystemName()
            out.append([s.name, direction, transitOnly, signals[0],
              signals[1], inverted, manualSensor, s.stopAtBeginning,
              heldIndicators, manualSection])
        out.sort()
        return out
    getSectionsTable = ADstaticMethod(getSectionsTable)

    def putSectionsTable() :
        # Updates sections, based on a table of attributes in string format.
        # Uses as input the table in the format provided by the 
        # getSectionsTable method and contained in current settings
        for i in ADsettings.sections :
            section = ADsection.getByName(i[0])
            if section != None :
                if i[1] == "NO" :
                    section.direction = 3
                elif (i[1] == "CCW" or i[1] == "EAST" or i[1] == "NORTH"
                   or i[1] == "LEFT" or i[1] == "UP") :
                    section.direction = ADsettings.ccw + 1
                    section.transitOnly[ADsettings.ccw] = False
                elif (i[1] == "CW" or i[1] == "WEST" or i[1] == "SOUTH"
                   or i[1] == "RIGHT" or i[1] == "DOWN") :
                    section.direction = 2 - ADsettings.ccw
                    section.transitOnly[1-ADsettings.ccw] = False
                i2 = i[2]
                if i2.endswith("+") :
                    i2 = i2[:len(i2)-1]
                    burst = True
                else :
                    burst = False
                if i2 == "NO" :
                    section.transitOnly[0] = section.transitOnly[1] = False
                    section.burst = False
                elif (i2 == "CCW-CW" or i2 == "EAST-WEST" or i2 == "NORTH-SOUTH"
                   or i2 == "LEFT-RIGHT" or i2 == "UP-DOWN") :
                    section.transitOnly[0] = section.transitOnly[1] = True
                    section.burst = burst
                elif (i2 == "CCW" or i2 == "EAST" or i2 == "NORTH"
                   or i2 == "LEFT" or i2 == "UP") :
                    section.transitOnly[ADsettings.ccw] = True
                    section.transitOnly[1-ADsettings.ccw] = False
                    section.burst = burst
                elif (i2 == "CW" or i2 == "WEST" or i2 == "SOUTH"
                   or i2 == "RIGHT" or i2 == "DOWN") :
                    section.transitOnly[1-ADsettings.ccw] = True
                    section.transitOnly[ADsettings.ccw] = False
                    section.burst = burst
                for j in range(2) :
                    if (i[j+3] != "" and (section.signal[j] == None or 
                       i[j+3] != section.signal[j].name)) :
                        if section.signal[j] != None :
                            section.signal[j].changeUse(-2)
                        section.signal[j] = ADsignalMast.provideSignal(i[j+3])
                        section.signal[j].changeUse(1)
                if i[5] == "INVERTED" :
                    section.manuallyFlip()
                sensorName = i[6]
                if sensorName == "NO" :
                    section.manualSensor = None
                elif sensorName.strip() != "" :
                    section.manualSensor = (
                    InstanceManager.sensorManagerInstance().getSensor(sensorName))
                if len(i) > 7 :
                    section.stopAtBeginning = i[7]
                    if len(i) > 9 and ADsettings.autoRestart :
                        for j in range(2) :
                            if (i[8][j] == "Held" and section.signal[j] != None
                             and section.signal[j].hasIcon()) :
                                section.signal[j].setHeld(True)
                        if i[9] == "Manual":
                            section.setManual(True)
                        
    putSectionsTable = ADstaticMethod(putSectionsTable)

    def getBlocksTable() :
        # Creates a table containing blocks' attributes as strings.
        # The output table, sorted by section name, contains:
        #   Block Name
        #   Stop-Block Indicator
        #   Allocation-Block Indicator
        #   Safe-Point Indicator
        #   Maximum speed name
        #   Brake-Block Indicator
        #   List of block actions for each direction
        # Within the section, blocks are sorted in accordance to 
        # internal direction
        out = []
        sections = ADsection.getNames()
        sections.sort()
        for sectionName in sections :
            section = ADsection.getByName(sectionName)
            for block in section.getBlocks(True) :
                outData = [block.getName()]
                for j in range (2) :
                    if block == section.stopBlock[j]:
                        outData.append("STOP")
                    else :
                        outData.append("")
                    if block == section.allocationPoint[j]:
                        outData.append("ALLOCATE")
                    else :
                        outData.append("")
                    if block == section.safePoint[j]:
                        outData.append("SAFE")
                    else :
                        outData.append("")
                    speedIndex = block.getSpeed(j)
                    if speedIndex == 0 :
                        outData.append("")
                    else :
                        outData.append(
                          ADsettings.speedsList[speedIndex-1])
                    if block == section.brakeBlock[j]:
                        outData.append("BRAKE")
                    else :
                        outData.append("")
                    outData.append(block.action[j])
                out.append(outData)
        return out
    getBlocksTable = ADstaticMethod(getBlocksTable)

    def putBlocksTable() :
        # Updates blocks, based on a table of attributes in string format.
        # Uses as input the table in the format provided by the 
        # getBlocksTable method and contained in current settings
        for i in ADsettings.blocks :
            block = ADblock.getByName(i[0])
            if block != None :
                section = block.getSection()
                jj = 1
                for j in range(2) :
                    if i[jj] == "STOP" :
                        section.stopBlock[j] = block
                    jj +=1
                    if i[jj] == "ALLOCATE" :
                        section.allocationPoint[j] = block
                    jj +=1
                    if i[jj] == "SAFE" :
                        section.safePoint[j] = block
                    elif i[jj] == "NO"  and section.safePoint[j] == block :
                        section.safePoint[j] = None
                    jj +=1
                    speedName = i[jj]
                    for speedIndex in range(len(ADsettings.speedsList)) :
                        if (speedName == 
                           ADsettings.speedsList[speedIndex]) :
                            block.speed[j] = speedIndex + 1
                            break
                    jj +=1
                    if i[jj] == "BRAKE" :
                        section.brakeBlock[j] = block
                    jj +=1
                    block.action[j] = i[jj]
                    jj +=1
    putBlocksTable = ADstaticMethod(putBlocksTable)

    def setListeners() :
        # Add a property change listener for each section that has a sensor 
        # for manual control
        for section in ADsection.systemNames.values() :
            if(section.manualSensor != None) :
                # add the listener
                section.manualSensor.addPropertyChangeListener(section)
    setListeners = ADstaticMethod(setListeners)

    def removeListeners() :
        # Remove the manual control sensor listener of each section
        for section in ADsection.systemNames.values() :
            if(section.manualSensor != None) :
                # remove the listener
                section.manualSensor.removePropertyChangeListener(section)
    removeListeners = ADstaticMethod(removeListeners)

    # INSTANCE METHODS

    def __init__(self, systemName):
        # Retrieve Section.java instance from JMRI
        self.jmriSection = (
          InstanceManager.getDefault(jmri.SectionManager).getBySystemName(systemName))
        # Get section name
        self.name = self.jmriSection.getUserName()
        if self.name == None or self.name.strip() == "" :
            self.name = systemName
        self.name = AutoDispatcher.cleanName(self.name)
        # Swing variables used in Blocks window
        self.stopGroup = [ButtonGroup(), ButtonGroup()]
        self.brakeGroup = [ButtonGroup(), ButtonGroup()]
        self.brakeNoneSwing = [JRadioButton(""), JRadioButton("")]
        self.brakeNoneSwing[0].setHorizontalAlignment(JLabel.CENTER)
        self.brakeNoneSwing[1].setHorizontalAlignment(JLabel.CENTER)
        self.brakeGroup[0].add(self.brakeNoneSwing[0])
        self.brakeGroup[1].add(self.brakeNoneSwing[1])
        self.safeGroup = [ButtonGroup(), ButtonGroup()]
        self.safeNoneSwing = [JRadioButton(""), JRadioButton("")]
        self.safeNoneSwing[0].setHorizontalAlignment(JLabel.CENTER)
        self.safeNoneSwing[1].setHorizontalAlignment(JLabel.CENTER)
        self.safeGroup[0].add(self.safeNoneSwing[0])
        self.safeGroup[1].add(self.safeNoneSwing[1])
        self.allocationGroup = [ButtonGroup(), ButtonGroup()]
        # Find out all blocks contained in the section
        # Make sure JMRI section direction is forward!
        # Otherwise we will get the list in reversed order.
        self.blockList = []         # Blocks contained in the section
        self.jmriSection.setState(Section.FREE)
        newBlock = self.jmriSection.getEntryBlock()
        while newBlock != None :
            # Get the corresponding LayoutBlock
            # Without LayoutBlock we cannot setup connectivity
            layoutBlock = None
            blockName = newBlock.getUserName()
            if blockName != None and blockName.strip() != "" :
                layoutBlock = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager).getLayoutBlock(blockName)
            if layoutBlock == None :
                blockName = newBlock.getSystemName()
                layoutBlock = InstanceManager.getDefault(jmri.jmrit.display.layoutEditor.LayoutBlockManager).getLayoutBlock(blockName)
            if layoutBlock == None :
                AutoDispatcher.log("No LayoutBlock for Block " + blockName
                  + " found: block skipped!")
            else :
                # make sure that block is included in one section only
                block = ADblock.getByName(blockName)
                if block != None :
                    AutoDispatcher.log("Block " + blockName + " included in both " 
                        + block.getSection().getName() +
                        " and " + self.name + 
                        " sections. AutoDispatcher " + 
                        "is unable to handle this case.")
                    # Continue even if results are unpredictable
                # Add the layoutBlock as sub-block
                self.blockList.append(ADblock(self, layoutBlock, blockName))
            newBlock = self.jmriSection.getNextBlock()
        # Make sure section contains at least one block
        blocksNumber = len(self.blockList)
        if blocksNumber < 1 :
            AutoDispatcher.log("Section " + self.name
              + "contains no valid  block. Section skipped!")
            return
        # Update the maximum number of blocks per section (will be used to 
        # determine default settings)
        if AutoDispatcher.maxBlocksPerSection < blocksNumber :
            AutoDispatcher.maxBlocksPerSection = blocksNumber
        # Include names in dictionaries
        ADsection.systemNames[AutoDispatcher.cleanName(systemName)] = self
        ADsection.userNames[self.name] = self
        self.forwardEntries=[]      # Entries
        self.reverseEntries=[]      # Exits
        self.xings = []             # Sections crossed by this section
        self.stopBlock = [None, None]
        self.brakeBlock = [None, None]
        self.allocationPoint = [None, None]
        self.safePoint = [None, None]
        self.manualSensor = None
        self.memoryVariable = jmri.InstanceManager.memoryManagerInstance().getMemory(self.name)
        self.stopAtBeginning = [-1.0, -1.0]
        # Assume that order of blocks and entries is not reversed
        self.reversed = False
        # Assume that user did not flip yet the order of blocks
        self.manuallyFlipped = False
        # Signals at the two exits of the section
        self.signal = [None, None]
        # Permitted train direction
        self.direction = 3  # 3 = both
        # Trains can stop
        self.transitOnly = [False, False]
        # "Burst" mode on transit-ony sections disabled
        self.burst = False
        # Section does not contain yet the head of a train
        self.trainHead = False
        # Section is not occupied yet
        self.occupied = False 
        # Section is not allocated yet
        self.allocated = None
        # Section length.  The value si recomputed each time a train travels
        # through the section, since actual length depends upon the entry and
        # exit points the train comes thru
        self.sectionLength = 0.0
        # Allocated train direction in this block
        self.trainDirection = 0
        # Indicator of exit turnouts position
        # True if at least one turnout is thrown
        self.turnoutsThrown = False
 
        # Retrieve the list of sensor names from JMRI, unless already done.
        # It will be used to populate sensor combo boxes
        if ADsection.sensorNames == None :
            ADsection.sensorNames = [""]
            # Remove from the list sensors associated with blocks
            # (this makes user selection easier and faster)
            # Get all blocks
            blocks = InstanceManager.getDefault(jmri.BlockManager).getNamedBeanSet()
            blockSensors = []
            for b in blocks :
                sensor = b.getSensor()
                if sensor != None :
                    blockSensors.append(sensor)
            sensors = InstanceManager.sensorManagerInstance().getNamedBeanSet()
            for s in sensors:
                if not s in blockSensors :
                    sensorName = s.getUserName()
                    if sensorName == None or sensorName.strip() == "" :
                        sensorName = s
                    if sensorName != "ISCLOCKRUNNING" :
                        ADsection.sensorNames.append(sensorName)
            ADsection.sensorNames.sort()
            
        # Swing variables used in Sections window
        # We should probably move them to the GUI
        self.oneWaySwing = JComboBox()
        self.transitOnlySwing = JComboBox()
        self.signalSwing = [JComboBox(), JComboBox()]
        self.manualSensorSwing = JComboBox()
        self.stopAtBeginningSwing = [JCheckBox("", False), JCheckBox("", False)]
        self.stopAtBeginningDelay = [JTextField("0.0", 4), JTextField("0.0", 4)]
                
    def setEntries(self):
        # Find section's entry points
        self.forwardEntries = self.__setEntries__(
          self.jmriSection.getForwardEntryPointList())
        self.reverseEntries = self.__setEntries__(
          self.jmriSection.getReverseEntryPointList())
        # Set default stop blocks
        self.brakeBlock[0] = self.stopBlock[0] = self.__setStopBlocks__(
          self.getBlocks(False), self.forwardEntries)
        self.brakeBlock[1] = self.stopBlock[1] = self.__setStopBlocks__(
          self.getBlocks(True), self.reverseEntries)
        # Set default allocation blocks and safe points
        self.safePoint[0] = self.allocationPoint[1] = self.stopBlock[0]
        self.safePoint[1] = self.allocationPoint[0] = self.stopBlock[1]
        if len(self.blockList) > 1 :
            # Set default brake block to block preceding stop block
            for i in range(2) :
                found = False
                for block in self.getBlocks(1-i) :
                    if found :
                        self.brakeBlock[i] = block
                        break
                    found =  block == self.stopBlock[i]
        else :
            # Section contains only one block. 
            # Brake and safe points are meaningless
            self.brakeBlock[0] = self.brakeBlock[1] = None
            self.safePoint[0] = self.safePoint[1] = None
            
    def __setEntries__(self, entries) :
        # Internal method used to retrieve entry points from JMRI
        entryPoints=[]
        for i in range(entries.size()) :
            # Get a JMRI EntryPoint
            entry = entries.get(i)
            # Create our own entry instance
            entryPoint = ADentry(self, entry)
            # If the creation failed, discard the instance
            if not entryPoint.inError() :
                entryPoints.append(entryPoint)
                entryPoint.getInternalBlock().entryBlock = True
        return entryPoints

    def __setStopBlocks__(self, blocks, entries) :
        # Stop point is set to the first exit block encountered
        block = None
        for b in blocks :
            for e in entries :
                if b == e.getInternalBlock() :
                    return b
            block = b
        return block

    def addXing(self, otherSection):
        # Add an item to the list of sections crossed by this section
        self.xings.append(otherSection)

    def setDefault(self):
        # Set transit-only indicators to default values
        # Any section containing a Xing or only one block
        # (unless all sections are one block long) is 
        # initially considered as transit-only in both directions
        # (user can change settings later in "Blocks" window)
        if (len(self.xings) > 0 or (len(self.blockList) < 2 and
           AutoDispatcher.maxBlocksPerSection > 1)) :
            self.transitOnly[0] = self.transitOnly[1] = True

    def getName(self):
        return self.name
        
    def getJmriSection(self):
        return self.jmriSection
        
    def getEntries(self, backwards):
        # Return entries (backwards == False)
        # or exits (backwards == True)
        # swapping them if order of blocks is reversed
        if backwards == self.reversed :
            return self.forwardEntries
        else :
            return self.reverseEntries

    def isReversed (self) :
        # Is the order of blocks reversed?
        return self.reversed

    def setReversed (self, reversed) :
        # Set the indicator of reversed order of blocks
        if reversed == self.reversed :
            return
        self.reversed = reversed
        # Flip contents of tables
        self.stopBlock.reverse()
        self.brakeBlock.reverse()
        self.allocationPoint.reverse()
        self.safePoint.reverse()
        # Do the same for blocks
        for block in self.blockList :
            block.speed.reverse()
            block.action.reverse()
        
    def getSignal(self, direction):
        if direction < 0 :
            direction = 0
        elif direction > 1 :
            direction = 1
        return self.signal[direction]

    def getStopAtBeginning(self, direction):
        if direction < 0 :
            direction = 0
        elif direction > 1 :
            direction = 1
        return self.stopAtBeginning[direction]

    def manuallyFlip(self) :
        # Reverse section's direction and take note that user requested flipping
        # (Can occur only for reversing-sections)
        self.setReversed(not self.reversed)
        self.manuallyFlipped = not self.manuallyFlipped
        
    def setOccupied(self) :
        # Set section to occupied (unless already set)
        if not self.occupied :
            self.empty = False
            self.occupied = True
            # Update section color
            self.setColor()

    def checkOccupancy(self) :
        # If already empty, ignore (shouldn't happen)
        if not self.occupied and self.allocated == None :
            return
        # Clear occupancy status only if all sub-blocks are empty
        for b in self.blockList :
            if b.getOccupancy() == Block.OCCUPIED :
                return
        # report error if the train "disappeared"
        if self.trainHead and self.allocated != None :
            # Avoid the warning when user is allowed to manually perform
            # switching operations
            if (not AutoDispatcher.stopped and not AutoDispatcher.paused and 
              not self.isManual() and
              ADsettings.derailDetection != ADsettings.DETECTION_DISABLED) :
                if (ADsettings.derailDetection == 
                   ADsettings.DETECTION_PAUSE) :
                    AutoDispatcher.instance.stopAll()
                AutoDispatcher.chimeLog(ADsettings.DERAILED_SOUND,
                  "Train \"" + self.allocated.getName() + 
                  "\" derailed in section \"" + self.name + "\"")
                self.allocated.lastMove = -1L
            return
        # Now this section is empty
        self.empty = True
        # Release it if train reached the "safe point" in next section
        self.freeSectionIfTrainSafe()

    def freeSectionIfTrainSafe(self) :
        # Relase the section if :
        #   section is empty;
        # and
        #   train is equipped with resistive wheels
        # or
        #   the distance of train head from section boundary
        #   is greater than train length
        # or
        #   train reached the safe-point of next section
        
        train = self.allocated
        if not self.occupied and train == None :
            # Already released (can occur if sensor is re-triggered)
            return
        # Is section allocated to a train?
        if train != None :
            # Is train's tail in this section?
            if (len(train.previousSections) > 0 and
               train.previousSections[0] != self) :
                # No - Try releasing previous sections (if empty)
                previousCount = 0
                while True :
                    # Recursivelly call "freeSectionIfTrainSafe" while 
                    # sections continue being released
                    currentCount = len(train.previousSections)
                    if currentCount ==  0 or currentCount ==  previousCount :
                        break
                    previousCount = currentCount
                    train.previousSections[0].freeSectionIfTrainSafe()
                return
            # Yes, section contains train's tail - Is train using 
            # resistive wheels?
            if not train.resistiveWheels :
                # No - Did train reach the safe point?
                if not train.safe :
                    # No - Can we release the section based on train's length?
                    if (not ADsettings.useLength or
                       train.trainLength <= 0.) :
                        # No - We can thus not release the section
                        return
                    # Yes - See if the head of the train is far enough
                    if train.trainLength > train.distance :
                        # Not yet
                        return
        if not self.empty :
            # At least one block sensor still active
            # Test for lost cars
            if (ADsettings.lostCarsDetection ==
               ADsettings.DETECTION_DISABLED or AutoDispatcher.paused or
               AutoDispatcher.stopped or train == None) :
                return
            # Can we base detection of lost cars on train length?
            if (ADblock.blocksWithoutLength == 0 and
               train.trainLength > 0.) :
                # Yes, is the head of the train not too far from the tail?
                # (Let's introduce some tollerance to compensate 
                # for sensors debouncing)
                if (train.distance - train.trainLength
                   < ADsettings.lostCarsTollerance) :
                    # Not too far
                    return
            else :
                # No length available
                # Let's base detection on the number of sections 
                # not yet released
                if (len(train.previousSections) <=
                   ADsettings.lostCarsSections) :
                    # Not too far
                    return
            if (ADsettings.lostCarsDetection ==
               ADsettings.DETECTION_PAUSE) :
                AutoDispatcher.instance.stopAll()
            AutoDispatcher.chimeLog(ADsettings.LOST_CARS_SOUND,
              "Train \"" + train.getName() + 
              "\" lost cars in section \"" + self.name + "\"")
            return
        # One way or another we found that we can release the section
        self.occupied = False
        if train != None :
            # Update distance of train head from section boundary
            if len(train.previousSections) < 3 :
                # If train is in the next section, clear distance
                # We cannot release next section while the train is in it!
                train.distance = 0.
                train.blockLength = 0.
            else :
                # If train head is two or more sections ahead
                # decrease total distance by present section length
                train.distance -= self.sectionLength
                if train.distance < 0. :
                    train.distance = 0.
                train.blockLength = train.block.getLength()
            # Remove section from the list of train's passed sections (if any)
            while self in train.previousSections :
                train.previousSections.pop(0)
            # Take note that the section is not allocated any more
            self.allocate(None, 0)
        # Reset signals
        if self.signal[0] != None :
            self.signal[0].setIndication(0)
        if self.signal[1] != None :
            self.signal[1].setIndication(0)
        # Update section color
        self.setColor()
        # Should train stop at the start of next section?
        if train == None or not train.shouldStopAtBeginning() :
            return
        if(len(train.previousSections) > 0 and
          train.previousSections[0] != train.section) :
            return
        # Yes, stop train
        start_new_thread(self.stopTrain,
          (train, train.section.stopAtBeginning[train.direction],))
        
    def stopTrain(self, train, delay) :
        # Stop a train after an optional delay
        if delay > 0 :
            sleep(delay)
        # Make sure train is still braking
        if train.speedLevel != 1 :
            return
        train.arrived()
        if (train.locomotive == None or train.engineerSetLocomotive == None
          or AutoDispatcher.simulation) :
            train.stop()
        if train.locomotive != None :
            train.locomotive.learningStop()
        train.changeSpeed(0)
        
    def isOccupied (self) :
        return self.occupied

    def allocate(self, train, direction) :
        # Called when:
        #   section is allocated to a new train
        #   section is de-allocated
        #   train direction changed 
        # Update JMRI section state, if requested
        if ADsettings.sectionTracking :
            if self.trainDirection != direction or self.allocated != train :
                if train == None :
                    if self.allocated != None :
                        self.jmriSection.setState(Section.FREE)
                else :
                    if direction == self.reversed :
                        self.jmriSection.setState(Section.REVERSE)
                    else :
                        self.jmriSection.setState(Section.FORWARD)
        self.trainDirection = direction
        # If only direction changed, return
        if self.allocated == train :
            return
        self.trainHead = train != None and self.occupied
        self.allocated = train
        # Change train name in blocks (if required)
        self.changeTrainName()
        # Clear length.  Will be recomputed as the train advances
        self.sectionLength = 0.
        self.setColor()
                            
    def changeTrainName(self) :
        # Place/remove train name in jmri Memory Variable (if defined)
        # and in jmri Blocks (only if user enabled this option)
        if self.memoryVariable != None :
            if self.allocated == None :
                self.memoryVariable.setValue("")
            else :
                self.memoryVariable.setValue(self.allocated.getName())
        if not ADsettings.blockTracking :
            return
        if self.allocated == None :
            if self.occupied :
                for block in self.blockList :
                    block.setValue("")
        else :
            if self.occupied :
                trainName = self.allocated.getName()
                for block in self.blockList :
                    if block.getOccupancy() == Block.OCCUPIED :
                        block.setValue(trainName)
       
    def getAllocated (self) :
        return self.allocated

    def isAvailable(self) :
        # Check if section is available
        if self.occupied or self.allocated != None :
            # Section occupied or allocated
            return False
        # If the section has crossings, check the status of crossed sections
        for x in self.xings :
            if x.isOccupied() or x.getAllocated() != None :
                return False
        # Check if section is under manual control
        return not self.isManual()

    def isManual(self) :
        if self.manualSensor == None :
            return False
        return self.manualSensor.getKnownState() == Sensor.ACTIVE

    def setManual(self, on) :
        # DO it only if user defined a manual control sensor
        if self.manualSensor != None :
            if on :
                self.manualSensor.setKnownState(Sensor.ACTIVE)
            else :
                self.manualSensor.setKnownState(Sensor.INACTIVE)

    def setColor(self) :
        # Set section color depending on section status
        # (only if user enabled this option)
        if AutoDispatcher.stopped or not ADsettings.useCustomColors :
            return
        if self.allocated != None :
            if self.occupied :
                if not self.allocated.running and self.isManual() :
                    color = ADsettings.sectionColor[
                      ADsection.MANUAL_SECTION_COLOR]
                elif self.allocated.getDirection() == ADsettings.ccw :
                    color = ADsettings.sectionColor[ADsection.CCW_TRAIN_COLOR]
                else :
                    color = ADsettings.sectionColor[ADsection.CW_TRAIN_COLOR]
            elif self.allocated.getDirection() == ADsettings.ccw :
                color = ADsettings.sectionColor[ADsection.CCW_ALLOCATED_COLOR]
            else :
                color = ADsettings.sectionColor[ADsection.CW_ALLOCATED_COLOR]
        elif self.isManual() :
            color = ADsettings.sectionColor[ADsection.MANUAL_SECTION_COLOR]
        elif self.occupied :
            color = ADsettings.sectionColor[ADsection.OCCUPIED_SECTION_COLOR]
        else :
            color = ADsettings.sectionColor[ADsection.EMPTY_SECTION_COLOR]
        for block in self.blockList :
            block.setColor(color)

    def getBlocks(self, direction) :
        # Returns the list of blocks contained in the section
        # sorted in accordance to train direction
        if direction != self.reversed :
            return self.blockList
        l = []
        l.extend(self.blockList)
        l.reverse()
        return l

    def updateFromSwing(self) :
        # Update section settings in accordance to input swing fields
        direction = self.oneWaySwing.getSelectedItem()
        transitOnly = self.transitOnlySwing.getSelectedItem()
        if transitOnly.endswith("+") :
            burst = True
            transitOnly = transitOnly[:len(transitOnly)-1]
        else :
            burst = False
        self.direction = 3
        if direction == ADsettings.directionNames[0] :
            self.direction = ADsettings.ccw + 1
        elif direction == ADsettings.directionNames[1] :
            self.direction = 2 - ADsettings.ccw
        self.transitOnly[0] = self.transitOnly[1] = False
        self.burst = False
        if (transitOnly == ADsettings.directionNames[0] or 
           transitOnly == ADsettings.directionNames[0] + 
           "-" + ADsettings.directionNames[1]) :
            self.transitOnly[ADsettings.ccw] = True
            self.burst = burst
        if (transitOnly == ADsettings.directionNames[1] or 
           transitOnly == ADsettings.directionNames[0] + 
           "-" + ADsettings.directionNames[1]) :
            self.transitOnly[1-ADsettings.ccw] = True
            self.burst = burst
        for j in range(2) :
            newSignalName = self.signalSwing[j].getSelectedItem()[3:]
            if newSignalName != self.signal[j].name and newSignalName.strip() != "" :
                if self.signal[j] != None :
                    self.signal[j].changeUse(-1)
                self.signal[j] = ADsignalMast.provideSignal(newSignalName)
                self.signal[j].changeUse(1)
            if self.stopAtBeginningSwing[j].isSelected() :
                try :
                    self.stopAtBeginning[j] = float(self.stopAtBeginningDelay[j].text)
                except :
                    self.stopAtBeginning[j] = 0.0
                    self.stopAtBeginningDelay[j].text = "0.0"
            else :
                self.stopAtBeginning[j] = -1.0
                self.stopAtBeginningDelay[j].text = "0.0"
        sensorName = self.manualSensorSwing.getSelectedItem()
        if sensorName == None :
            self.manualSensor = None
        else :
            self.manualSensor = InstanceManager.sensorManagerInstance(
              ).getSensor(sensorName)


    def propertyChange(self, event) :
        # Listener, invoked when the Manual sensor status changes
        if (event.getPropertyName() != "KnownState" or 
           event.newValue == event.oldValue) :
            return
        if (event.newValue == Sensor.ACTIVE 
          or event.newValue == Sensor.INACTIVE) :
            # Status changed
            self.setColor()
                    
# ENTRY POINT ==============

class ADentry :
    # Replaces JMRI class EntryPoint
    def __init__(self, section, entry):
        self.internalBlock = None
        self.externalBlock = None
        # Should transiting trains invert direction (i.e. color)?
        self.directionChange = False
        # Does entry include double crossovers?
        self.xOver = []
        # Error during creation
        self.error = True
        blockName = entry.getBlock().getUserName()
        if blockName == None :
            blockName = entry.getBlock().getSystemName()
        self.internalBlock = ADblock.getByName(blockName)
        # Skip entry if entry block not included in any section
        if self.internalBlock == None :
            return
        # Skip entry if entry block not included in this section
        # (should not occur!)
        if self.internalBlock.getSection() != section :
            return
        blockName = entry.getFromBlock().getUserName()
        if blockName == None :
            blockName = entry.getFromBlock().getSystemName()
        # Skip entry if external block not included in any section
        # (it may occur)
        self.externalBlock = ADblock.getByName(blockName)
        if self.externalBlock == None :
            return
        # Creation successful: clear error flag
        self.error = False   
        
    def inError(self):
        # Did any error occur during instantation?
        return self.error
        
    def getInternalBlock(self):
        # return the block internal to the section
        return self.internalBlock
    
    def getExternalBlock(self):
        # return the block external to the section
        return self.externalBlock
    
    def getInternalSection(self):
        # return the section of this entry
        return self.internalBlock.getSection()
    
    def getExternalSection(self):
        # return the section connected by this entry
        return self.externalBlock.getSection()
    
    def getDirectionChange(self):
        # Does train direction change when transiting over this entry?
        # (e.g. Eastbound trains become Westbound)
        return self.directionChange
    
    def setDirectionChange(self, change):
        self.directionChange = change
        
    def addXover(self,xOver):
        self.xOver.append(xOver)

    def areXoversAvailable(self):
        # Check if all Xovers of the route (if any) are available
        internalSection = self.internalBlock.getSection()
        for xOver in self.xOver :
            if not xOver.isAvailable(internalSection) :
                return False
        return True

# BLOCK ==============
    
class ADblock (PropertyChangeListener) :
    # Encapsulates JMRI Block and LayoutBlock, adding fields and methods 
    # used by AutoDispatcher

    # STATIC VARIABLES
    
    userNames ={}       # dictionary of blocks by userName
    systemNames ={}     # dictionary of blocks by systemName
    blocksList = {}     # dictionary of blocks by layoutBlock
    # Keep a count of how many blocks have a defined length and how many 
    # do not have it
    blocksWithLength = 0
    blocksWithoutLength = 0

    # STATIC METHODS

    def getList() :
        return ADblock.systemNames.values()
    getList = ADstaticMethod(getList)

    def getByName(name) :
        if ADblock.userNames.has_key(name) :
            return ADblock.userNames[name]
        return ADblock.systemNames.get(name, None)
    getByName = ADstaticMethod(getByName)

    def getByLayoutBlock(layoutBlock) :
        return ADblock.blocksList.get(layoutBlock, None)
    getByLayoutBlock = ADstaticMethod(getByLayoutBlock)

    def setListeners() :
        # Add a property change listener for each block
        # In order to avoid race conditions, listeners are added to
        # JMRI Block.java, not to sensors!
        for block in ADblock.systemNames.values() :
            if(block.jmriBlock != None) :
                # add the listener
                block.jmriBlock.addPropertyChangeListener(block)
    setListeners = ADstaticMethod(setListeners)

    def removeListeners() :
        # Remove the listener of each block
        for block in ADblock.systemNames.values() :
            if(block.jmriBlock != None) :
                # remove the listener
                block.jmriBlock.removePropertyChangeListener(block)
    removeListeners = ADstaticMethod(removeListeners)

    # INSTANCE METHODS

    def __init__(self, section, layoutBlock, userName) :
        # Take note of section (each block must be contained in 
        # one section only!)
        self.section = section
        self.layoutBlock = layoutBlock
        self.jmriBlock = self.layoutBlock.getBlock()
        self.name = self.layoutBlock.getUserName()
        if self.name == None or self.name.strip() == "" :
            self.name = self.layoutBlock.getSystemName()
        ADblock.userNames[userName] = self
        ADblock.systemNames[layoutBlock.getSystemName()] = self
        ADblock.blocksList[layoutBlock] = self
        # Get block length and take note of how many blocks have it
        # The later info will be used in the Preferences window
        self.length = float(self.jmriBlock.getLengthMm())
        if self.length <= 0.0 :
            self.length = 0.0
            ADblock.blocksWithoutLength += 1
        else :
            ADblock.blocksWithLength += 1
        # We do not know yet if this block is an entry point
        self.entryBlock = False
        # Set maximum speeds to undefined
        self.speed = [0, 0]
        # Block actions
        self.action = ["", ""]
        # Save original block colors in order to restore them before exiting
        self.trackColor = layoutBlock.getBlockTrackColor()
        self.occupiedColor = layoutBlock.getBlockOccupiedColor()
        # Path to other blocks     
        self.paths = {}
        # Tracks contained in the Block
        self.tracks = []
        # Warn user if block has no sensor
        if self.getOccupancySensor() == None :
            AutoDispatcher.log("No sensor for block \"" + self.name +
             "\" found")
        # Swing variables. (To be moved to GUI?)
        self.stopSwing = [JRadioButton(""), JRadioButton("")]
        self.stopSwing[0].setHorizontalAlignment(JLabel.CENTER)
        self.stopSwing[1].setHorizontalAlignment(JLabel.CENTER)
        self.section.stopGroup[0].add(self.stopSwing[0])
        self.section.stopGroup[1].add(self.stopSwing[1])
        self.brakeSwing = [JRadioButton(""), JRadioButton("")]
        self.brakeSwing[0].setHorizontalAlignment(JLabel.CENTER)
        self.brakeSwing[1].setHorizontalAlignment(JLabel.CENTER)
        self.section.brakeGroup[0].add(self.brakeSwing[0])
        self.section.brakeGroup[1].add(self.brakeSwing[1])
        self.allocationSwing = [JRadioButton(""), JRadioButton("")]
        self.allocationSwing[0].setHorizontalAlignment(JLabel.CENTER)
        self.allocationSwing[1].setHorizontalAlignment(JLabel.CENTER)
        self.section.allocationGroup[0].add(self.allocationSwing[0])
        self.section.allocationGroup[1].add(self.allocationSwing[1])
        self.safeSwing = [JRadioButton(""), JRadioButton("")]
        self.safeSwing[0].setHorizontalAlignment(JLabel.CENTER)
        self.safeSwing[1].setHorizontalAlignment(JLabel.CENTER)
        self.section.safeGroup[0].add(self.safeSwing[0])
        self.section.safeGroup[1].add(self.safeSwing[1])
        self.speedSwing = [JComboBox(), JComboBox()]
        self.actionSwing = [JTextField(self.action[0], 5),
          JTextField(self.action[1], 5)]
        
    def setPaths(self) :
        # Create our paths, based on JMRI paths
        # Our paths are stored in a Jython dictionary, for fast retrieval
        # (destination block is the key)
        self.paths = {}
        jmriPaths = self.getJmriBlock().getPaths()
        for i in range(jmriPaths.size()) :
            jmriPath = jmriPaths.get(i)
            fromName = jmriPath.getBlock().getUserName()
            if fromName == None :
                fromName = jmriPath.getBlock().getSystemName()
            destinationBlock = ADblock.getByName(fromName)
            if destinationBlock != None :
                self.paths[destinationBlock] = jmriPath.getSettings()

    def addTrack(self, track) :
        # Add a track to the block
        self.tracks.append(track)
        
    def getSection(self) :
        # return the section containing this block
        return self.section
        
    def getName(self) :
        return self.name
        
    def getLength(self) :
        return self.length

    def getJmriBlock(self) :
        return self.jmriBlock

    def setValue(self, value) :
        # Set the value (e.g. train name) of the corresponding JMRI block
        self.jmriBlock.setValue(value)

    def getOccupancy(self) :
        return self.jmriBlock.getState()
        
    def getOccupancySensor(self) :
        return self.layoutBlock.getOccupancySensor()

    def getSpeed(self, direction) :
        foundSpeed = self.speed[direction]
        if foundSpeed > len(ADsettings.speedsList) :
            foundSpeed = len(ADsettings.speedsList)
        return foundSpeed
        
    def setTurnouts(self, connectingBlock, turnoutList) :
        # Set turnouts between present block and connecting block
        # Return:
        #  True if any turnout is "Thrown"
        #  False if all turnouts are "Closed" (or no turnout was found)
        thrown = False
        # Check that connection exists
        if self.paths.has_key(connectingBlock) :
            # Connection found
            beanSettings = self.paths[connectingBlock]
            # Set all turnouts contained in the path
            for i in range(beanSettings.size()) :
                beanSetting = beanSettings.get(i)
                turnout = beanSetting.getBean()
                # Make sure turnout is not included twice in paths
                # Seems to happen starting with JMRI 2.11.4
                if not turnout in turnoutList :
                    turnoutList.append(turnout)
                    position = beanSetting.getSetting()
                    # Take note if turnout must be throw
                    if position == Turnout.THROWN :
                        thrown = True
                    # Operate turnout only if not yet set in the proper position
                    # or if user disabled "Trust turnouts KnownState" indicator
                    if (not ADsettings.trustTurnouts or 
                        turnout.getState() != position) :
                        AutoDispatcher.turnoutCommands[turnout] = [
                          position, System.currentTimeMillis()]
                        turnout.setState(position)
                        # No need of repainting, since LayoutEditor will do it
                        AutoDispatcher.repaint = False
                        # Wait if user specified a delay between turnouts operation
                        if ADsettings.turnoutDelay > 0 :
                            AutoDispatcher.instance.waitMsec(
                              ADsettings.turnoutDelay)
        return thrown
                    
    def setColor(self, color):
        # Set block color
        self.layoutBlock.setBlockTrackColor(color)
        self.layoutBlock.setBlockOccupiedColor(color)

    def adjustWidth (self) :
        # Set the width of tracks, depending on block occupancy
        if not ADsettings.useCustomWidth :
            return
        occupied = self.layoutBlock.getOccupancy() == Block.OCCUPIED
        for track in self.tracks :
            track.setWidth(occupied)

    def restore(self) :
        # Restore both block colors and tracks' width (invoked before exiting)
        # Restore original block colors
        self.layoutBlock.setBlockTrackColor(self.trackColor)
        self.layoutBlock.setBlockOccupiedColor(self.occupiedColor)
        # Restore original tracks' width
        for track in self.tracks :
            track.restoreWidth()

    def propertyChange(self, event) :
        # Listener, invoked when a train enters/leaves the block
        if (event.getPropertyName() != "state" or 
           event.newValue == event.oldValue) :
            return
        ind = 0
        if event.newValue == Block.OCCUPIED :
            # Train entering the block
            train = self.section.getAllocated()
            if train == None :
                # No train expected
                self.section.setOccupied()
                # Section is not allocated - Wrong route?
                if self.entryBlock and not self.section.isManual() :
                    if ((not AutoDispatcher.stopped) and (not 
                      AutoDispatcher.paused) and 
                       ADsettings.wrongRouteDetection !=
                       ADsettings.DETECTION_DISABLED) :
                        # Check if section has crossings 
                        # (this could be a false detection due to a short on 
                        # the crossing)
                        wrongRoute = True
                        for x in self.section.xings :
                            if x.getAllocated() != None :
                                wrongRoute = False
                                break
                        if wrongRoute :      
                            if (ADsettings.wrongRouteDetection ==
                              ADsettings.DETECTION_PAUSE) :
                                AutoDispatcher.instance.stopAll()
                            AutoDispatcher.chimeLog(ADsettings.WRONG_ROUTE_SOUND,
                              "Train entering wrong route in section \""
                              + self.section.getName() + "\", block \""
                              + self.name + "\"")
            else :
                # Train expected
                train.changeBlock(self)
                if ADsettings.blockTracking :
                    self.setValue(train.getName())
        elif event.newValue == Block.UNOCCUPIED :
            # Train leaving the block
            # Check if the section is still occupied
            if ADpowerMonitor.powerOn :
                 self.section.checkOccupancy()
        # Adjust track width in accordance to occupancy status
        self.adjustWidth()

# TRACK ==============

class ADtrack :
    # Encapsulates JMRI Track class, recording the original width
    def __init__(self, track):
        self.track = track
        # Save original track width
        self.width = track.getMainline()
        
    def setWidth(self, occupied):
        # Set width, depending on block's occupancy
        self.track.setMainline(occupied)

    def restoreWidth(self):
        # Restore original track width
        self.track.setMainline(self.width)

# LOCATION ==============

class ADlocation :
    # List of sections coresponding to each Operations' location
    
    # STATIC VARIABLES
    
#    locationManager = LocationManager.instance()
    locations ={}   # dictionary of location instances

    # STATIC METHODS

    def getList() :
        return ADlocation.locations.values()
    getList = ADstaticMethod(getList)

    def getNames() :
        return ADlocation.locations.keys()
    getNames = ADstaticMethod(getNames)

    def getByName(name) :
        return ADlocation.locations.get(name, None)
    getByName = ADstaticMethod(getByName)
    
#    def getOpLocations() :
        # Get location IDs from Operations
#        opIds = ADlocation.locationManager.getLocationsByNameList()
#        for opId in opIds :
#            # get Operation's location instance
#            opLocation = ADlocation.locationManager.getLocationById(opId)
#            name = opLocation.getName()
#            # get our corresponding location or create it
#            if ADlocation.locations.has_key(name) :
#                location = ADlocation.locations[name]
#           else :
#                location = ADlocation(name)
#            # Link our location with Operation's instance
#            location.opLocation = opLocation
#    getOpLocations = ADstaticMethod(getOpLocations)

    # INSTANCE METHODS

    def __init__(self, name):
        self.name = name
        ADlocation.locations[name] = self
        self.text = ""
        self.list = []
        self.opLocation = None
        
    def setSections(self, text) :
        textSpace  = text.replace(",", " ")
        tokens = textSpace.split()
        list = []
        for t in tokens :
            s = ADsection.getByName(t)
            if s == None :
                AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
                  "Location \"" + self.name +
                   "\": wrong format or unknown section")
                return
            list.append(s)
        self.list = list
        self.text = text

    def getSections(self) :
        return self.list

# DOUBLE CROSSOVER ==============

class ADxover :
    # Keeps information about double crossovers (for single crossovers 
    # there is no need, since they are simply two turnouts)
    def __init__(self, jmriXover):
        # Get the 4 layout blocks connected by the crossover
        block = []
        self.section = []
        block.append(jmriXover.getLayoutBlock())
        block.append(jmriXover.getLayoutBlockB())
        block.append(jmriXover.getLayoutBlockC())
        block.append(jmriXover.getLayoutBlockD())
        # Pick the corresponding ADblocks and ADsections
        for i in range(4) :
            # If block not specified, assume it to be block A
            if block[i] == None :
                block[i] = block[0]
            else :
                block[i] = ADblock.getByLayoutBlock(block[i])
            if block[i] == None :
                # If one of the blocks is not included in any section
                # the Xover can be treated as a turnout and 
                # this instance can be be purged
                return
            self.section.append(block[i].getSection())
            # If any connected section is null (i.e. section not controlled 
            # by AutoDispatcher), we can ignore the Xover (it will be treated 
            # as a simple turnout)
            if self.section[i] == None :
                # (this instance will be purged)
                return
        # Check if the crossover is the intersection of two sections
        if (self.section[0] == self.section[2] and 
           self.section[1] == self.section[3]) :
            # Intersection - is the crossover fully contained 
            # in a single section?
            if self.section[0] == self.section[1] :
                # Yes - Ignore it, since there is little we can do!
                # (this instance will be purged)
                return
            # Intersection between two different sections
            # Treat it as a simple crossing
            self.section[0].addXing(self.section[1])
            self.section[1].addXing(self.section[0])
            # (this instance will be purged)
            return
        # Sections are not intersecting         
        # Link the Xover with the relevant section entries
        self.__link__(self.section[0], block[0], self.section[2], block[2])
        self.__link__(self.section[0], block[0], self.section[3], block[3])
        self.__link__(self.section[1], block[1], self.section[2], block[2])
        self.__link__(self.section[1], block[1], self.section[3], block[3])
        self.__link__(self.section[2], block[2], self.section[0], block[0])
        self.__link__(self.section[2], block[2], self.section[1], block[1])
        self.__link__(self.section[3], block[3], self.section[0], block[0])
        self.__link__(self.section[3], block[3], self.section[1], block[1])

    def __link__(self, sectionFrom, blockFrom, sectionTo, blockTo):
        # Internal method - links the Xover to an entry point
        # Skip trivial case (both blocks in the same section)
        if sectionFrom == sectionTo :
            return
        # Find entry point
        for direction in [False, True] :
            for entry in sectionTo.getEntries(direction) :
                if (entry.getExternalSection() == sectionFrom and
                   entry.getExternalBlock() == blockFrom and
                   entry.getInternalBlock() == blockTo) :
                    entry.addXover(self)
                    return
                
    def isAvailable(self, comingFrom):
        # Checks if a route through the Xover can be used
        # Check only allocation of crossing routes
        # (other cases will anyway be solved when checking sections allocation)
        busyRoute = -1
        for i in range(2) :
            train = self.section[i].getAllocated()
            if train != None :
                if self.section[i+2].getAllocated() == train :
                    busyRoute = i
                    break
        # If no route allocated, Xover is available
        if busyRoute < 0 :
            return True
        # Allocated route found
        # Xover not available, unless route allocated to the same train
        return (self.section[busyRoute] == comingFrom or
          self.section[busyRoute+2] == comingFrom)
        

# GRID-LOCK PROTECTION GROUP ==============

class ADgridGroup :
    # Each  ADgridGroup instance contains the list of sections converging
    # into a transit-only section.  These are point where gridlock may occur.
    
    # STATIC VARIABLES
        
    list = {}

    # STATIC METHODS

    def create() :
        # Create all gridLock groups
           ADgridGroup.list = {}
           # Scan all sections
           for section in ADsection.getList() :
               # Create groups for transit-only sections that accept
               # trains in both directions
               # (one-way sections do not create gridlock situations)
            if ((section.transitOnly[0] or section.transitOnly[1])
              and section.direction == 3) :
                ADgridGroup(section)
    create = ADstaticMethod(create)

    def lockRisk(train, fromSection, toSection) :
        # Find if occupying a section can result in
        # a gridLock situation
        # Is the section included in a gridLock group
        group = ADgridGroup.list.get(fromSection.getName() + "$" +
          toSection.getName(), None)
        if group == None :
            # No
            return False
        # Yes - invoke the recursive inernal method
        ADgridGroup.debug = fromSection.getName() == "Lv1"
        return group.__lockRisk__(train, toSection, [])
    lockRisk = ADstaticMethod(lockRisk)
            
    # INSTANCE METHODS

    def __init__(self, transit) :
        # Keep note of the transit-only section, which is the
        # focus of the group
        self.transit = transit
        self.sections = []
        self.bumpers = []
        # Scan connected sections in both directions
        for direction in range(2) :
            dirSections = []
            dirBumpers = []
            for entry in transit.getEntries(not direction) :
                # Get the connected section
                section = entry.getExternalSection()
                dirSections.append(section)
                # Take note if the section is a siding
                dirBumpers.append(len(section.getEntries(direction)) == 0)
                # Create a dictionary entries for this section,
                # provided it's not one-way and gets accessed from a transit-only section
                # (focal section could not be transit-only in this direction)
                if self.transit.transitOnly[direction] and section.direction == 3 :
                    # Get the list of sections from which this section can be entered
                    for e in section.getEntries(not direction) :
                        fromSection = e.getExternalSection()
                        # Omit dictionary entries for one-way sections
                        if (fromSection.direction & (direction + 1)) != 0 :
                            # Create the entry, as follows:
                            # startSection$endSection
                            ADgridGroup.list[fromSection.getName() + "$" +
                                section.getName()] = self
            self.sections.append(dirSections)
            self.bumpers.append(dirBumpers)

    def __lockRisk__(self, train, toSection, processed) :
        # Internal recursive method
        # Make sure section was not processed yet during this call
        # to avoid an endless loop!
        if toSection in processed :
            return False
        processed.append(toSection)
        # Compute direction with respect to sections storing order
        direction = toSection in self.sections[1]
        if not direction and not toSection in self.sections[0] :
            # If section is not included in this group (shouldn't happen)
            # ignore call
            return False
        # Examine sections parallel to the requested section
        # If one of them is free or occupied by a train running in opposite
        # direction, there is no gridLock risk
        for section in self.sections[direction] :
           if (section != toSection and (section.getAllocated() == None or
              section.trainDirection != direction)) :
                return False
        ind = 0
        # Now look at sections on the other side of the transit only track
        for section in self.sections[not direction] :
            otherTrain = section.getAllocated()
            # Skip trivial case (oval with only one station)
            if train == otherTrain :
                return False
            # If section is free, or occupied by a train running in the same direction
            # of our train (and has no end-bumper), we need to make sure that we are not
            # creating a gridLock situation ahead
            if (otherTrain == None or (section.trainDirection == direction and 
              not self.bumpers[not ind])) :
                next = ADgridGroup.list.get(self.transit.getName() + "$" +
                  section.getName(), None)
                if next == None or next == self : 
                    return False
                if not next.__lockRisk__(train, section, processed) :
                    return False
            ind += 1
        return True

# TRAIN ==============

class ADtrain :
    # Our train class

    # CONSTANTS

    # Train status
    IDLE = 0
    ERROR = 1
    END_OF_SCHEDULE = 2
    STOPPED = 3
    ARRIVED = 4
    STARTING = 5
    STARTED = 6
    
    # STATIC VARIABLES
        
    trains = []       # list of trains
    # Busy indicator (used to avoid that different threads may
    # operate turnouts at once)
    turnoutsBusy = False
    # List of sections used for Swing Interface
    sectionsList = None

    # STATIC METHODS

    def getList() :
        return ADtrain.trains
    getList = ADstaticMethod(getList)
    
    def remove(train) :
        ADtrain.trains.remove(train)
    remove = ADstaticMethod(remove)

    def buildRoster(trains) :
    # Build trains roster, based on user settings
    # (retrieved from disk or from defaults for example panels)
        for t in trains :
            tt = ADtrain(t[0])
            tt.setDirection(t[2])
            section = ADsection.getByName(t[1])
            if section != None :
                tt.setSection(section, not ADsettings.autoRestart)
            tt.resistiveWheels = t[3]
            tt.setSchedule(t[4])
            tt.trainAllocation = t[5]
            tt.trainLength = t[6]
            # Set schedule info
            if tt.section != None :
                tt.schedule.stack = t[7]
                tt.schedule.pointer = tt.schedule.stack[
                  len(tt.schedule.stack)-1]
                tt.schedule.pop()
                if len(t) > 12 :
                    if t[12] != "" :
                        tt.lastRouteSection = ADsection.getByName(t[12])
            tt.changeLocomotive(t[8])
            tt.setReversed(t[9])
            # Rebuild pending commands, replacing names with instances
            for item in t[10] :
                section = ADsection.getByName(item[0])
                if section != None :
                    scheduleItem = ADscheduleItem()
                    scheduleItem.action = item[1]
                    scheduleItem.value = item[2]
                    if len(item) > 3 :
                        scheduleItem.message = item[3]
                    if (scheduleItem.action == ADschedule.WAIT_FOR or 
                       scheduleItem.action == ADschedule.MANUAL_OTHER) :
                        scheduleItem.value = ADsection.getByName(
                          scheduleItem.value)
                        if scheduleItem.value == None :
                            continue
                    elif (scheduleItem.action == ADschedule.HELD or
                       scheduleItem.action == ADschedule.RELEASE or
                       scheduleItem.action == ADschedule.IFH) :
                        scheduleItem.value = ADsignalMast.getByName(
                          scheduleItem.value)
                        if scheduleItem.value == None :
                            continue
                    tt.itemSections.append(section)
                    tt.items.append(scheduleItem)
            tt.engineerName = t[11]
            tt.trainSpeed = t[13]
            tt.brakingHistory = t[14]
            if len(t) > 15 :
                tt.canStopAtBeginning = t[15]
                if len(t) > 16 :
                    tt.startAction = t[16]
            tt.updateSwing()
    buildRoster = ADstaticMethod(buildRoster)

    # INSTANCE METHODS

    def __init__(self, trainName) :
        # record this instance in the list of trains
        ADtrain.trains.append(self)
        # Initailize instance variables
        # Train name
        self.name = trainName
        # Corresponding train of Operations module (if any)
        self.opTrain = None
        # Locomotive name
        self.locoName = ""
        # Locomotive instance
        self.locomotive = None
        # Indicator of locomotive running backwards
        self.reversed = False
        # Indicator that train stops at start of sections that provide this option
        self.canStopAtBeginning = False
        # Indicator that train is presently running
        self.running = False
        # Train status
        self.status = ADtrain.IDLE
        # Present train speed level
        self.speedLevel = 0
        # Speed conversion table 
        self.trainSpeed = []
        # Train departure time (-1L = immediate)
        self.departureTime = -1L
        # Departure time based on fast clock
        self.fastClock = False
        # Indicator that train is waiting for a section to become free
        # (None = no wait)
        self.waitFor = None
        # Indicator that train is operating in switching mode and can thus
        # enter any section
        self.switching = False
        # Protection flag. Suspends train processing while input data 
        # are being updated
        self.updating = False
        # Protection flag. Suspends train processing while another thread 
        # is already taking care of it
        self.checking = False
        # Direction of the train (referred to the internal conventional
        # direction)
        self.direction = 0
        # Section where the head of the train is located
        self.section = None
        # Block where the head of the train is located
        self.block = None
        # Current destination section along current route
        self.destination = None
        # Direction at destination
        self.finalDirection = 0
        # Exit signal of destination section
        self.destinationSignal = None
        # Last section of the route (can be different from present destination)
        self.lastRouteSection = None
        # Sections of current route to be still encountered
        self.sectionsAhead = []
        # Blocks of current route to be still encountered
        self.blocksAhead = []
        # Other sections still occupied by the train
        self.previousSections = []
        # Sections ahead where commands need to be executed
        self.itemSections = []
        # Commands to be executed (there is an item for each itemSection)
        self.items = []
        # Train schedule
        self.schedule = ADschedule(" ")
        # Indicator that train is fully recovered in the current section
        self.safe = True
        # Indicator that train already reached the allocation point
        self.allocationReady = True
        # Number of sections along the route allocated
        self.allocatedSections = 0
        # Indicator that cars are equipped with resistive wheels
        self.resistiveWheels = ADsettings.resistiveDefault
        # Number of sections ahead to be allocated for the train (0 = default)
        self.trainAllocation = 0
        # Train length in mm.
        self.trainLength = 0.0
        # Train length when leaving present section, provided by Operations module
        # (if used)
        self.opLength = 0.0
        # Train distance from first section contained in previousSections
        # (used to release previous sections)
        self.distance = 0.
        # Length of current block
        self.blockLength = 0.
        # Last time train entered a block (used to detect stalled trains)
        self.lastMove = -1L
        # Maximum allowed train speed (may vary from block to block)
        self.maxSpeed = 0
        # Actions to be played before train departure
        self.startAction = ""
        # Our engineer name
        self.engineerName = "Auto"
        # Our engineer class
        self.engineer = None
        # Available methods of the engineer
        # (Engineer classes may implement only some methods)
        self.engineerAssigned = False
        self.engineerSetTrain = None
        self.engineerSetLocomotive = None
        self.engineerSetLocoName = None
        self.engineerSetOrientation = None
        self.engineerSetFunction = None
        self.engineerChangeSpeed = None
        self.engineerPause = None
        self.engineerResume = None
        self.engineerRelease = None
        # Fields used for simulation mode
        # Current train route
        self.entriesAhead = []
        # Sensor of previous block
        self.simSensor = None
        # Fields for self-learning braking
        # Previous block
        self.previousBlock = None
        # Collected braking data
        self.brakingHistory = {}
        # Swing fields, used in Trains window
        self.directionSwing = JComboBox([ADsettings.directionNames[0],
          ADsettings.directionNames[1]])
        # Initialize sections' list if not yet done
        # The list will be used to populate combo boxes
        if ADtrain.sectionsList == None :
            ADtrain.sectionsList = [""]
            for section in ADsection.getList() :
                ADtrain.sectionsList.append(section.getName())
            ADtrain.sectionsList.sort()
        self.sectionSwing = JComboBox(ADtrain.sectionsList)
        self.nameSwing = JTextField(self.name, 4)
        self.locoRoster = JComboBox()
        self.reversedSwing = JCheckBox("", self.reversed)
        self.reversedSwing.setHorizontalAlignment(JLabel.CENTER)
        self.scheduleSwing = JTextField("", 8)
        self.speedLevelSwing = AutoDispatcher.centerLabel("Idle")
        self.resistiveSwing = JCheckBox("", self.resistiveWheels)
        self.canStopAtBeginningSwing = JCheckBox("", self.canStopAtBeginning)        
        self.trainLengthSwing = JTextField("0", 4)
        self.trainAllocationSwing = JComboBox(["Default", "1", "2", "3", "4", "5"])
        self.trainAllocationSwing.setSelectedIndex(self.trainAllocation)
        self.engineerSwing = JComboBox()
        self.deleteButton = JButton("Delete")
        self.deleteButton.actionPerformed = self.whenDeleteTrainClicked
        self.changeButton = JButton("Apply")
        self.changeButton.actionPerformed = self.whenChangeClicked
        self.setButton = JButton("Apply")
        self.setButton.actionPerformed = self.whenSetClicked
        self.detailButton = JButton("Detail")
        self.destinationSwing = AutoDispatcher.centerLabel("None")
        self.startActionSwing = JTextField("", 5)
        self.enableSwing()

    def getName(self) :
        return self.name
        
    def getCanStopAtBeginning(self) :
        return self.canStopAtBeginning
        
    def getLength(self) :
        return self.trainLength

    def whenDeleteTrainClicked(self,event) :
        # Ask confirmation before deleting the train!
        if (JOptionPane.showConfirmDialog(None, "Remove train \""
          + self.name + "\"?", "Confirmation",
          JOptionPane.YES_NO_OPTION) == 1) :
            return
        self.updating = True
        if self.running :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Train " + self.name + " running: cannot be deleted")
            self.updating = False
            return
        if self.opTrain != None and self.status == ADtrain.END_OF_SCHEDULE :
            try:
                self.opTrain.move()
            finally :
                i = 0 # Useless, just to complete the try construct
        AutoDispatcher.trainsFrame.deleteTrain(self)
        self.updating = True
              
    # define what Apply button in Trains Window does when clicked
    def whenChangeClicked(self,event) :
        self.trainChange()

    # define what Set button in Train Detail Window does when clicked
    def whenSetClicked(self,event) :
        self.scheduleSwing.text = AutoDispatcher.trainDetailFrame.scheduleSwing.text
        self.trainChange()
        
    def trainChange(self) :
        # Get input values from "Trains" and/or "Train Detail" windows
        # Check if this train is shown in the "Train Detail" window
        detail = (AutoDispatcher.trainDetailFrame != None and 
          AutoDispatcher.trainDetailFrame.train == self)
        # Take note that instance is being updated, preventing other 
        # threads from using it
        self.updating = True
        # Make sure another thread is not starting the train
        # (Let's cope with Jython's lack of synchronization)
        if self.running :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Train "
              + self.name + " running: changes cannot be applied")
            self.updating = False
            return
        # Change train's name
        newName = self.nameSwing.text
        if newName.strip() == "" :
            self.nameSwing.setText(oldName)
        elif newName != self.name :
            self.name = newName
            AutoDispatcher.setTrainsDirty()
            # Update train name in jmri Blocks (if needed)
            if self.section != None :
                if not self.section in self.previousSections :
                    self.section.changeTrainName()
                for section in self.previousSections :
                    section.changeTrainName()
        # Change train's direction
        oldDirection = self.direction
        self.setDirection(self.directionSwing.getSelectedItem())
        # If direction changed, take note that we need to restart schedule
        reSchedule = oldDirection != self.direction
        # Change train's section
        sectionName = self.sectionSwing.getSelectedItem()
        if sectionName.strip() == "" :
            newSection = None
        else :
            newSection = ADsection.getByName(sectionName)
        if newSection != self.section :
            # Train was manually moved to another section
            # Take note that we need to restart schedule
            reSchedule = True
            self.setSection(newSection, True)
            self.schedule = None
        # Change train's locomotive
        loco = self.locoRoster.getSelectedItem()
        if loco != "" and loco != self.locoName :
            self.changeLocomotive(loco)
            AutoDispatcher.setTrainsDirty()
        # Change locomotive orientation
        newReversed = self.reversedSwing.isSelected()
        if self.reversed != newReversed :
            self.setReversed(newReversed)
            AutoDispatcher.setTrainsDirty()
        # Change train's schedule
        newSchedule = self.scheduleSwing.text
        if (self.schedule == None or self.schedule.pointer >= len(self.schedule.source)
          or self.schedule.text != newSchedule or reSchedule) :
            self.setSchedule(newSchedule)
            AutoDispatcher.setTrainsDirty()
        if detail :
            AutoDispatcher.trainDetailFrame.scheduleSwing.text = (
              self.scheduleSwing.text)
            # Change train's resistive wheels indicator
            newResistiveWheels = self.resistiveSwing.isSelected()
            if self.resistiveWheels != newResistiveWheels :
                self.resistiveWheels = newResistiveWheels
                AutoDispatcher.setTrainsDirty()                
            canStopAtBeginningSwing = self.canStopAtBeginningSwing.isSelected()
            if self.canStopAtBeginning != canStopAtBeginningSwing :
                self.canStopAtBeginning = canStopAtBeginningSwing
                AutoDispatcher.setTrainsDirty()
            # Change train length
            oldLength = self.trainLength
            try :
                self.trainLength = (float(self.trainLengthSwing.text) *
                  ADsettings.units)
            except :
                self.trainLength = 0
                self.trainLengthSwing.text = "0"
            if oldLength != self.trainLength :
                AutoDispatcher.setTrainsDirty()
            # Change number of sections ahead to be allocated for this train
            newAllocation = self.trainAllocationSwing.getSelectedIndex()
            if newAllocation != self.trainAllocation :
                self.trainAllocation = newAllocation
                AutoDispatcher.setTrainsDirty()
            # Change engineer
            newEngineerName = self.engineerSwing.getSelectedItem()
            if newEngineerName != self.engineerName :
                self.setEngineer(newEngineerName)
                AutoDispatcher.setTrainsDirty()
            # Change train speeds table
            ind = 0
            for s in AutoDispatcher.trainDetailFrame.trainSpeedSwing :
                newSpeed = s.getSelectedIndex()+1
                if newSpeed != self.trainSpeed[ind] :
                    self.trainSpeed[ind] = newSpeed
                    AutoDispatcher.setTrainsDirty()
                ind += 1
            if self.startAction != self.startActionSwing.text :
                self.startAction = self.startActionSwing.text
                AutoDispatcher.setTrainsDirty()
        # Update swing fields
        self.updateSwing()
        if AutoDispatcher.trainsFrame != None :
            AutoDispatcher.trainsFrame.reDisplay()
        # Updating complete, let other threads use this instance
        self.updating = False
        if detail :
            AutoDispatcher.trainDetailFrame.dispose()
            AutoDispatcher.trainDetailFrame = None
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Changes for train \""
          + self.name + "\" applied")

    def setDirection(self, direction) :
        # Set train direction
        if (direction == "CCW" or direction == "EAST" or direction == "NORTH"
           or direction == "LEFT" or direction == "UP") :
            newDirection = ADsettings.ccw
        else :
            newDirection = 1 - ADsettings.ccw
        if newDirection == self.direction :
            return
        self.direction = newDirection
        if self.section != None :
            self.section.trainDirection = self.direction
    def getDirection(self) :
        return self.direction

    def setSection(self, newSection, held) :
        # Set initial position of train
        if newSection != self.section :
            # Train placed in a new section
            if self.section != None :
                # Remove train from the old sections
                self.section.trainHead = False
                self.releaseSections()
#                self.section.allocate(None, 0)
                self.section = None
            self.safe = False
            self.simSensor = None
            if newSection != None :
                # Check that section is not allocated to another train
                otherTrain = newSection.getAllocated()
                if otherTrain != None and otherTrain != self :
                    AutoDispatcher.message("Section " + newSection.getName() +
                      " already allocated to train " + otherTrain.getName())
                    newSection = None
                else :
                    # Check that section is actually occupied
                    newSection.occupied = True
                    newSection.checkOccupancy()
                    if not newSection.isOccupied() :
                        # Train is being placed in an empty section
                        # Are we running in simulation mode?
                        if AutoDispatcher.simulation :
                            # Simulation mode - Force section occupancy
                            # Find out block to be used
                            sensor = newSection.stopBlock[
                              self.direction].getOccupancySensor()
                            if sensor != None :
                                # Avoid "wrong route" error
                                wrongRoute = ADsettings.wrongRouteDetection
                                ADsettings.wrongRouteDetection = (
                                  ADsettings.DETECTION_DISABLED)
                                newSection.occupied = True
                                newSection.empty = False
                                # Force occupancy
                                sensor.setKnownState(Sensor.ACTIVE)
                                ADsettings.wrongRouteDetection = wrongRoute
                            else :
                                # Block has no sensor! :-O
                                newSection = None
                        else :
                            # We are not running in simulation mode.
                            # User should place train on tracks before 
                            # defining it (unless derailment detection is disabled)!
                            if (ADsettings.derailDetection != ADsettings.DETECTION_DISABLED) :
                                AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
                                    "Section " + newSection.getName() +
                                     " is empty. Place train "
                                    + self.name + " on tracks!")
                                # Attempt to assign train to an empty section, 
                                if (ADsettings.derailDetection == ADsettings.DETECTION_PAUSE) :
                                      # set section to undefined
                                    newSection = None
                # Was assignment successful?
                if newSection != None :
                    self.section = newSection
                    # Yes - Update allocation
                    self.section.allocate(self, self.direction)
                    self.destination = self.lastRouteSection = self.section
                    self.finalDirection = self.direction
                    self.destinationSignal = self.section.getSignal(
                      self.direction)
                    self.block = self.section.stopBlock[self.direction]
                    self.previousBlock = None
                    # Clear distance from previous sections
                    self.distance = 0.
                    self.blocklength = 0.
                    # Set signal in front of train to held
                    # (only if the signal icon is displayed on the panel,
                    # otherwise user will not be able to release it!
                    s = self.section.getSignal(self.direction)
                    if held and s.hasIcon() :
                        s.setHeld(True)
                else :
                    # Unsuccessful assignment - clear section name
                    self.sectionSwing.setSelectedItem("")

    def releaseSections(self) :
        # Routine to release sections occupied by the train,
        # when manually moving it to another section or deleting it
        for section in ADsection.getList() :
            if section.getAllocated() == self :
                section.allocate(None, 0)
                if AutoDispatcher.simulation :
                    section.occupied = False
                    section.empty = True
                    for block in section.getBlocks(True) :
                        sensor = block.getOccupancySensor()
                        if sensor != None :
                            sensor.setKnownState(Sensor.INACTIVE)
                            block.adjustWidth()
                else :
                    section.checkOccupancy()
                section.setColor()
                AutoDispatcher.repaint = True

    def changeSection(self, newSection) :
        # Change section containing train's head while train is running
        # Knowing where the head of the train is located is
        # necessary for proper action of signals and 
        # to detect derailments (or other malfunctions)
        # If a section containing the head of the train becomes empty,
        # something went wrong!
        # Avoid re-triggering a previous section
        if newSection in self.previousSections or newSection == self.section :
            return
        # Train moved to a new section
        if self.section != None :
            # Remove the head of the train from the old section (if any)
            self.section.trainHead = False
            # Reset signals
            if self.section.signal[0] != None :
                self.section.signal[0].setIndication(0)
            if self.section.signal[1] != None :
                self.section.signal[1].setIndication(0)
        # Since train is just entering a new section it cannot be safely
        # recovered in it
        self.safe = False
        self.section = newSection
        if self.section != None :
            # Must previous sections be still released?
            if len(self.previousSections) == 0 :
                # No, clear distance from previous sections
                 self.distance = 0.
                 self.blocklength = 0.
            # Is train moving to a section along the route? (it should)
            if self.section in self.sectionsAhead :
                # Yes
                # Remove it and all previous sections from the route,
                # adjusting train direction (if changed)
                oldDirection = self.direction
                while self.section in self.sectionsAhead :
                    # Adjust direction
                    passedSection = self.sectionsAhead.pop(0)
                    self.direction = passedSection.trainDirection
                    # Mark section as occupied
                    passedSection.setOccupied()
                    passedSection.checkOccupancy()
                    # Update number of allocated section
                    if (self.allocatedSections > 0 and 
                       (not passedSection.transitOnly[self.direction] or
                       passedSection.getSignal(self.direction).hasHead())) :
                        self.allocatedSections -= 1
                    # Keep note that this section was passed and must still 
                    # be released
                    self.previousSections.append(passedSection)
                self.section.trainHead = True
                # Update Trains window if direction changed
                if self.direction != oldDirection :
                    self.updateSwing()
                    # Update sections color if direction changed
                    for section in self.previousSections :
                        section.setColor()
                    for section in self.sectionsAhead :
                        section.setColor()
                # Update Trains window (if open)
                self.sectionSwing.setSelectedItem(self.section.getName())
                # Move ahead train position in Operations module (if any)
                if self.opTrain != None :
                    if self.opLength > 0. :
                        self.trainLength = self.opLength
                        self.opLength = 0.
                    try :
                        nextLocation = ADlocation.getByName(
                          self.opTrain.getNextLocationName())
                        if nextLocation != None :
                            if self.section in nextLocation.getSections() :
                                self.opTrain.move()
                                opCurrent = self.opTrain.getCurrentLocation()
                                if opCurrent != None :
                                    self.opLength = round(float(
                                    opCurrent.getTrainLength(
                                    )) * 304.8 / ADsettings.scale)
                    finally :
                        i = 0
            else :
                # Train ended nowhere :-O
                self.sectionSwing.setSelectedItem("")
            # Should train stop at the start of this section?
            if self.shouldStopAtBeginning() :
                if self.locomotive.learningBrake() :
                    if self.speedLevel > self.maxSpeed :
                        self.changeSpeed(self.maxSpeed)
                else :
                    # Yes, brake
                    self.changeSpeed(1)

    def shouldStopAtBeginning(self) :
        if not self.canStopAtBeginning :
            return False
        if self.section != self.destination :
            return False
        if self.section.stopAtBeginning[self.direction] < 0 :
            return False
        return True

    def changeBlock(self, newBlock) :
        # Check if we need to compute distance from previous sections
        if len(self.previousSections) == 0 :
            firstPassedSection = self.section
        else :
            firstPassedSection = self.previousSections[0]
        speed = self.speedLevel
        # Remove block from list of expected blocks in order to
        # avoid re-triggering. Also blocks that were skipped
        # i.e. did not trigger any event (malfunctioning sensor?)
        # are processed
        while newBlock in self.blocksAhead :
            self.previousBlock = self.block
            self.block = self.blocksAhead.pop(0)
            self.lastMove = System.currentTimeMillis()
            section = self.block.getSection()
            # Update maximum speed
            newSpeed = self.block.getSpeed(self.direction)
            if newSpeed > 0 :
                self.maxSpeed = newSpeed
                # Apply new speed if train is not braking or stopping
                if speed > 1 :
                    speed = self.maxSpeed
            self.changeSection(section)
            # Update distance from previous sections (unless we are still 
            # in the first section of the route)
            if firstPassedSection != section :
                self.distance += self.blockLength
                self.blockLength = self.block.getLength()
                section.sectionLength += self.blockLength
            # Update locomotive mileage
            if self.locomotive != None :
                # We actually do it in advance, but for our purposes it's fine
                self.locomotive.mileage += self.block.getLength()
            # allocation point?
            if section.allocationPoint[self.direction] == self.block :
                self.allocationReady = True
            if section == self.section :
                # safe point?
                if self.section.safePoint[self.direction] == self.block :
                    # Take note that the train is safely contained within
                    # section boundaries
                    self.safe = True
                # Is train still starting ?
                if self.status != ADtrain.STARTING :
                    # No, get speed indicated by signal
                    restrictedSpeed = self.section.getSignal(
                      self.direction).getSpeed()
                    # Should train stop at start of section ?
                    shouldStop = self.shouldStopAtBeginning()
                    # stop block?
                    if self.section.stopBlock[self.direction] == self.block :
                        # Apply restricted speed
#                        if restrictedSpeed > 0 :
                        speed = restrictedSpeed
                        if speed == 0 :
                            if not shouldStop :
                                self.arrived()
                            # If we have a locomotive, inform it that train
                            # reached stop block (in case it's implementing
                            # self-learning braking)
                            if self.locomotive != None :
                                self.locomotive.learningStop()
                            # If we don't have a locomotive or are running in
                            # simulation, assume that train stops
                            if (self.locomotive == None 
                              or self.engineerSetLocomotive == None
                              or AutoDispatcher.simulation) :
                                self.stop()
                    elif shouldStop :
                        speed = self.speedLevel
                    # Brake block?
                    elif self.section.brakeBlock[self.direction] == self.block :
                        # Set speed to minimum if exit signal is red
                        if restrictedSpeed == 0 :
                            # If we have a locomotive, inform it that train
                            # starts braking (in case it's implementing
                            # self-learning braking)
                            if self.locomotive != None :
                                if not self.locomotive.learningBrake() :
                                    # self-learning not active
                                    # Brake immediately
                                    speed = 1
                            else :
                                # Self-learning braking not implemented
                                # Brake immediately
                                speed = 1
                        # If signal is not red, set speed to value indicated 
                        # by signal (if lower than present speed)
                        else :
                            speed = restrictedSpeed
            # Almost done.
            # Call change speed even if speed did not change, in order to inform
            # the Engineer that some change occurred
            self.changeSpeed(speed)
            # Start a separate thread to take care of block actions (if any)
            if self.block.action[self.direction].strip() != "" :
                start_new_thread(self.doAction,
                  (self.block.action[self.direction],))
        # Release previously occupied sections (if empty)
        self.section.freeSectionIfTrainSafe()  
        
    def arrived(self) :
        # Assume train is stopping
        self.lastMove = -1L
        if AutoDispatcher.runningTrains > 0 :
            AutoDispatcher.runningTrains -= 1
        if self.section.isManual() :
            self.section.setColor()

    def stop(self) :
        # Train stops
        self.running = False
        self.enableSwing()

    def doAction(self, actionList) :
        # Run in a separate thread to perform block actions
        textSpace  = actionList.replace(",", " ")
        # Break down input text into tokens
        splitted = textSpace.split()
        for sl in splitted :
            if sl.startswith("$") and len(sl) > 1 :
                sl = sl[1:]
            action = ADschedule.ERROR
            s = sl.upper()
            # DCC function ON/OFF
            if s.startswith("ON:F") :
                action = ADschedule.SET_F_ON
                value = s[4:]
            elif s.startswith("OFF:F") :
                action = ADschedule.SET_F_OFF
                value = s[5:]
            if action != ADschedule.ERROR :
                # Retrieve ON OFF argument (function number)
                try :
                    value = int(value)
                    if value < 0 or value > 28 :
                        continue
                except :
                    continue
                if action == ADschedule.SET_F_ON :
                    # Set decoder function ON
                    self.setFunction(value, True)
                else :
                    # Set decoder function OFF
                    self.setFunction(value, False)
            # Delay n seconds or m fastclock minutes
            elif s.startswith("D") :
                try :
                    s = s[1:]
                except :
                    continue
                if s.startswith("M") :
                    try :
                        s = s[1:]
                    except :
                        continue
                    useFastClock = True        
                else :
                    useFastClock = False        
                try :
                    value = float(s)
                except :
                    continue
                if useFastClock :
                    value = (value * 60.
                  / AutoDispatcher.fastBase.getRate())
                sleep(value)
            # Play AudioClip
            elif s.startswith("S:") :
                try :
                    value = ADsettings.soundDic.get(sl[2:], None)
                    if value != None :
                        value.play()
                except :
                    continue
            # Set turnout
            elif s.startswith("TC:") or s.startswith("TT:") :
                try :
                    value = sl[3:]
                    t = InstanceManager.turnoutManagerInstance().getTurnout(value)
                    if s.startswith("TC:") :
                        t.setState(Turnout.CLOSED)
                        AutoDispatcher.message("Closed turnout " + value)
                    else :
                        t.setState(Turnout.THROWN)
                        AutoDispatcher.message("Thrown turnout " + value)                   
                except :
                    continue

    def setSchedule(self, schedule) :
        # Set train schedule
        self.schedule = ADschedule(schedule)
        self.status = ADtrain.IDLE
        self.speedLevelSwing.text == "Idle"
        self.waitFor = None
        self.departureTime = -1L
        self.destination = lastRouteSection = self.section
        self.finalDirection = self.direction
        self.sectionsAhead = []
        self.entriesAhead = []
        self.blocksAhead = []
        self.itemSections = []
        self.items = []
        # Update sections' colors and status (releasing possible sections
        # allocated to the train)
        for section in ADsection.getList() :
            if section.getAllocated() == self and not section.isOccupied() :
                section.allocate(None, 0)
            else :
                section.setColor()
        if self.section != None :
            # Set destination signal to exit signal of current section
            self.destinationSignal = self.section.signal[self.direction]
            # Try to find current section in new schedule
            self.schedule.match(self.section, self.direction)
            self.queueCommands()
        # Force panel repainting
        AutoDispatcher.repaint = True

    def changeLocomotive(self, locoName) :
        # Set/replace locomotive
        # If same locomotive as before ignore call
        if self.locoName == locoName :
            return
        self.locoName = locoName
        # Release previous locomotive
        locomotive = self.locomotive
        if locomotive != None :
            self.locomotive = None
            locomotive.usedBy = None
            locomotive.releaseThrottle()
        # If no new locomotive was selected, return
        if self.locoName.strip() == "" :
            return
        # Retrieve locomotive instance
        self.locomotive = ADlocomotive.getByName(self.locoName)
        if self.locomotive == None :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Locomotive " + locoName + " not found!")
        else :
            # Locomotive found
            self.locomotive.usedBy = self
            # Force assignment of engineer
            self.engineerAssigned = False
        self.status = ADtrain.IDLE
        
    def setReversed(self, reversed) :
        # Set locomotive orientation
        self.reversed = reversed

    def clearBrakingHistory(self, locomotive) :
        if locomotive == None :
            # No locomotive specified. Clear history of all locomotives
            self.brakingHistory = {}
        else :
            # Rebuild history dictionary skipping entries 
            # for specified locomotive
            keyStart = locomotive.getName() + "$"
            newHistory = {}
            for key in self.brakingHistory.keys() :
                if not key.startswith(keyStart) :
                    newHistory[key] = self.brakingHistory[key]
            self.brakingHistory = newHistory

    def setEngineer(self, engineerName) :
        self.engineerName = engineerName
        self.engineerAssigned = False
        
    def assignEngineer(self) :
        # If engineer already assigned, ignore call
        if self.engineerAssigned :
            return
        # Release previous Engineer, if any
        self.releaseEngineer()
        self.engineerAssigned = True
        # If user chose manual control, no other action is needed
        if self.engineerName == "Manual" :
            return
        # Retrieve engineer class
        if not AutoDispatcher.engineers.has_key(self.engineerName) :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Engineer script " + self.engineerName + " not found!")
            self.engineerName = "Auto"
        # Create engineer instance
        self.engineer = AutoDispatcher.engineers[self.engineerName]()
        # Check what methods are implemented by this engineer class
        self.engineerSetTrain  = self.getEngineerMethod("setTrain")
        self.engineerSetLocomotive = self.getEngineerMethod("setLocomotive")
        self.engineerSetLocoName = self.getEngineerMethod("engineerSetLocoName")
        self.engineerSetOrientation = self.getEngineerMethod("setOrientation")
        self.engineerSetFunction = self.getEngineerMethod("setFunction")
        self.engineerChangeSpeed = self.getEngineerMethod("changeSpeed")
        self.engineerPause = self.getEngineerMethod("pause")
        self.engineerResume = self.getEngineerMethod("resume")
        self.engineerRelease = self.getEngineerMethod("release")
        # Inform engineer that he is assigned to this train :-)
        self.callEngineer(self.engineerSetTrain, self)
        
    def getEngineerMethod (self, methodName) :
        # Check if a method exists
        method = getattr(self.engineer, methodName, None)
        if not callable(method) :
            return None
        return method            

    def setOrientation(self, forward) :
        # Set locomotive orientation
        # Does the engineer provide the relevant method?
        if self.engineerSetOrientation == None :
            # No, if we have a locomotive pass the call directly 
            # to the locomotive
            locomotive = self.locomotive
            if locomotive != None :
                locomotive.setOrientation(forward)
        else :
            # Engineer provides the appropriate method, invoke it
            self.callEngineer(self.engineerSetOrientation, forward)
                    
    def setFunction(self, functionNumber, on) :
        # Set/reset a locomotive function
        # Does the engineer provide the relevant method?
        if self.engineerSetFunction == None :
            # No, if we have a locomotive pass the call directly 
            # to the locomotive
            locomotive = self.locomotive
            if locomotive != None :
                locomotive.setFunction(functionNumber, on)
        else :
            # Engineer provides the appropriate method, invoke it
            self.callEngineer(self.engineerSetFunction, functionNumber, on)

    def changeSpeed(self, suggestedSpeed) :
        # Change train speed
        # Make sure that speed does not exceed block's maximum speed
        if suggestedSpeed > self.maxSpeed  and self.maxSpeed != 0 :
            suggestedSpeed = self.maxSpeed
        # If speed changed, update Trains window (if open)
        if self.speedLevel != suggestedSpeed :
            self.outputSpeed(suggestedSpeed)
            self.speedLevel = suggestedSpeed
        # Convert speed level, using train's correspendence table
        suggestedSpeed = self.convertSpeed(suggestedSpeed)
        # Does the engineer provide the relevant method?
        if self.engineerChangeSpeed == None :
            # No, if we have a locomotive pass the call directly
            # to the locomotive
            locomotive = self.locomotive
            if locomotive != None :
                locomotive.changeSpeed(suggestedSpeed)
        else :
            # Engineer provides the appropriate method, invoke it
            # Engineer is called even if speed value did not change, in order to
            # inform about other changes (e.g. section or block)
            # The Engineer class will decide if any action is required
            max = self.maxSpeed
            if max == 0 :
                max = len(ADsettings.speedsList)
            max = self.convertSpeed(max)
            self.callEngineer(self.engineerChangeSpeed, self.section,
              self.block, self.section.getSignal(self.direction),
              suggestedSpeed, max)
        # If user chose to switch off lights when train stops, do it
        if self.speedLevel == 0 and ADsettings.lightMode == 1 :
            self.setFunction(0, False)
            
    def outputSpeed(self, speed) :
        # Output speed level to Trains window
        if speed == 0 :        
            if self.departureTime > -1L or self.waitFor != None :
                return
            self.speedLevelSwing.text = "Stop"
        else :
            self.speedLevelSwing.text = ADsettings.speedsList[speed-1]

    def convertSpeed(self, speed) :
        # Convert speed level, using train's correspondence table
        # (User can decide that this train should run at 10mph when 
        # the prescribed speed is 20mph)
        if speed > 0 and len(self.trainSpeed) >= speed :
            speed =  self.trainSpeed[speed-1]
        if speed > len(ADsettings.speedsList) :
            speed = len(ADsettings.speedsList)
        return speed

    def pause(self) :
        # Script is pausing, train must be halted
        # Does the engineer provide the relevant method?
        if self.engineerPause == None :
            # No, if we have a locomotive pass the call directly
            # to the locomotive
            locomotive = self.locomotive
            if locomotive != None :
                locomotive.pause()
        else :
            # Engineer provides the appropriate method, invoke it
            self.callEngineer(self.engineerPause)
        # Take note that train stopped, to avoid "Stalled train" error
        self.lastMove = -1L

    def resume(self) :
        # Script is resuming after a pause, train must be restarted
        # Set correct locomotive direction, in case it was manually changed
        # during the pause
        self.setOrientation(not self.reversed)
        # Does the engineer provide the relevant method?
        if self.engineerResume == None :
            # No, if we have a locomotive pass the call directly
            # to the locomotive
            locomotive = self.locomotive
            if locomotive != None :
                locomotive.resume()
        else :
            # Engineer provides the appropriate method, invoke it
            self.callEngineer(self.engineerResume)
        if self.running :
            self.lastMove = System.currentTimeMillis()

    def releaseEngineer(self) :
        # Release the present engineer
        # Does the engineer provide a release method?
        if self.engineer != None and self.engineerRelease != None :
            # Yes, invoke it
            self.callEngineer(self.engineerRelease)
        # Clear all engineer related variables
        self.engineer = None
        self.engineerSetTrain = None
        self.engineerSetLocomotive = None
        self.engineerSetLocoName = None
        self.engineerSetOrientation = None
        self.engineerSetFunction = None
        self.engineerChangeSpeed = None
        self.engineerPause = None
        self.engineerResume = None
        self.engineerRelease = None
        self.engineerAssigned = False

    def callEngineer(self, method, *args) :
        # Invoke a method of the engineer class
        # Number of arguments may vary
        if method != None :
            start_new_thread(method, args)

    def startIfReady(self) :
        # Start the train, if it can reach a section towards its destination
        # If train reached the end of the schedule or is in error simply exit
        # Avoid any action if train is being updated or started by
        # another thread
        if (self.updating or self.checking or self.status == ADtrain.ERROR or
           self.status == ADtrain.END_OF_SCHEDULE) :
            return
        # If the train is nowhere, exit!
        if self.section == None :
            return
        loco = self.locomotive
        # Is train in a manually controlled section?
        if self.section.isManual() :
            # Manual section, release throttle if train stopped
            if (not self.running and 
               self.locomotive != None and self.locomotive.throttle != None and
               self.locomotive.getThrottleSpeed() <= 0) :
                self.locomotive.releaseThrottle()
        else:
            # Not manual section - Assign engineer, if not yet done
            if not self.engineerAssigned :
                self.assignEngineer()
                if loco != None :
                    if self.engineerSetLocomotive != None :
                        self.callEngineer(self.engineerSetLocomotive, loco)
                    else :
                        self.callEngineer(self.engineerSetLocoName, loco.getName())
            # Does train have a locomotive?
            if loco != None :
                # Assign throttle, if not yet done
                # (only if engineer implements setLocomotive method, otherwise
                # having a locomotive is useless)
                if (not loco.throttleAssigned and
                   self.engineerSetLocomotive != None) :
                    loco.assignThrottle()
                    return
        # If train is paused, check if time has expired
        if self.departureTime > -1L :
            if self.fastClock :
                if self.departureTime > FastListener.fastTime :
                    return
            elif self.departureTime > System.currentTimeMillis() :
                return
            self.departureTime = -1L
            self.outputSpeed(self.speedLevel)
        # If train is waiting for a section, see if the section is available
        # (or already occupied by the train)
        if self.waitFor != None :
            if (self.waitFor.isAvailable() or (self.waitFor == self.section and
               not self.section.isManual())) :
                self.waitFor = None
                if self.speedLevelSwing.text.startswith("WAITING") :
                    self.outputSpeed(self.speedLevel)
            else :
                return
        # Process commands prefixed by $ that can be executed without
        # stopping the train
        # Such commands were transferred to "items" array before train departure
        if self.section in self.itemSections :
            self.itemSections.pop(0)
            self.processCommands(self.items.pop(0))
            return
        # If running, see if destination was reached
        if ((self.status == ADtrain.STARTED or self.status == ADtrain.IDLE) and
           self.section == self.destination) :
            self.status = ADtrain.ARRIVED
        # Process commands prefixed by $ that can be executed only when
        # train is not running
        # (such commands are still contained in the schedule)
        scheduleItem = self.schedule.getFirstAlternative()
        if not self.running and self.status == ADtrain.ARRIVED :
            if scheduleItem.action != ADschedule.GOTO :
                # Take care of possible $IF commands
                scheduleItem = self.schedule.testCondition(scheduleItem,
                  self.destination, self.direction)
                # Now execute this command
                self.processCommands(scheduleItem)
                # and pick up the next one
                self.schedule.next()
                return
            self.status = ADtrain.STOPPED
        # Whether running or not, GOTO command can be implemented without
        # waiting for train to stop
        if (scheduleItem.action != ADschedule.GOTO or
           self.destination.isManual()) :
            return
        # If signal held, exit
        if self.destinationSignal.isHeld() :
            if not self.running and self.destinationSwing.text != "Held":
                self.destinationSwing.text = "Held"
            return
        # Is train eligible for starting?
        if self.trainAllocation == 0 :
            allocationAhead = ADsettings.allocationAhead
        else :
            allocationAhead = self.trainAllocation
        if (not self.allocationReady  or 
           self.allocatedSections >=  allocationAhead) :
            return
        # Limit number of running trains
        if (not self.running and (ADsettings.max_trains > 0 and
           AutoDispatcher.runningTrains >= ADsettings.max_trains)) :
            if (not self.running and
               self.destinationSwing.text != "Waiting turn"):
                self.destinationSwing.text = "Waiting turn"
            return
        # Make sure anther thread is not starting this train or 
        # operating turnouts
        if ADtrain.turnoutsBusy or self.checking :
            return
        ADtrain.turnoutsBusy = self.checking = True
        # Span a separate thread in order to try and start the train
        # In this way commands for other trains can be processed while
        # starting this train (and operating turnouts)
        start_new_thread(self.checkAndStart, ())

    def checkAndStart(self) :
        destinationText = ""
        # Compute number of sections ahead to be allocated
        if self.trainAllocation == 0 :
            allocationAhead = ADsettings.allocationAhead
        else :
            allocationAhead = self.trainAllocation
        if (self.lastRouteSection != None and 
           self.destination != self.lastRouteSection) :
            # Train running in burst mode - try reaching final section
            # of previous route
            route = ADautoRoute(self.destination, self.lastRouteSection,
              self.finalDirection, self.switching)
            route.reduce(self, allocationAhead)
            foundLength = len(route.step)
            atLeastOne = True
        else :
            # Final section of previous schedule step reached
            # Find a possible route to one of alternate destinations included
            # in the new schedule step
            route = None
            atLeastOne = False
            foundLength = 0
            scheduleItem = self.schedule.getFirstAlternative()
            while scheduleItem.action == ADschedule.GOTO :
                if destinationText != "" :
                    if not destinationText.startswith("[") :
                        destinationText = "[" + destinationText
                    destinationText += " "
                destinationText += scheduleItem.value.getName()
                newRoute = ADautoRoute(self.destination, scheduleItem.value,
                  self.finalDirection, self.switching)
                # Any route to this section found?
                if len(newRoute.step) > 0 : 
                    # Yes
                    atLeastOne = True
                    # See if we can advance along it.
                    newRoute.reduce(self,
                      allocationAhead - self.allocatedSections)
                    newLength = len(newRoute.step)
                    # Is this the longest reduced route?
                    if newLength > foundLength :
                        # Yes, take note
                        foundLength = newLength
                        route = newRoute
                scheduleItem = self.schedule.getNextAlternative()
            if scheduleItem.action == ADschedule.ERROR :
                # Wrong schedule
                if not self.running :
                    AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
                    "Error in schedule of train \"" + self.name + "\": "
                    + scheduleItem.message)
                    self.status = ADtrain.ERROR
                    self.destinationSwing.text = "ERROR"
                self.checking = ADtrain.turnoutsBusy = False
                return
            if destinationText.startswith("[") :
                destinationText += "]"
        # Check if any error was encounterd (i.e. transit-only destination)
        if route != None and route.error != None :
            # Error - Schedule contains transit-only destination
            if not self.running :
                # Output message only when train stops, otherwise we will
                #  flood user with messages
                AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
                  "Schedule error: Train " + self.name + " headed to transit-only section "
                  + route.error.getName())
                self.status = ADtrain.ERROR
                destinationText = "ERROR"
            foundLength = 0
            atLeastOne = True
        if foundLength == 0 :
            # A reduced route was not found, check if at least one valid 
            # route was found
            if not atLeastOne :
                # No valid route was found, clearly an error
                # in schedule definition
                if not self.running :
                    # Output message only when train stops, otherwise we will
                    #  flood user with messages
                    AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
                      "Train " + self.name + ": no valid route from section "
                      + self.section.getName())
                    self.status = ADtrain.ERROR
                    destinationText = "ERROR"
            # Even if a valid route was found, next section is occupied or
            # allocated: exit
            if destinationText != "" :
                self.destinationSwing.text = destinationText
            self.checking = ADtrain.turnoutsBusy = False
            return
        # Minimum route found, try allocating sections
        newAllocatedSections = route.allocate(self)
        if len(route.step) == 0 :
            # First section along the route is occupied by another train or
            # new route is no longer valid: exit
            if destinationText != "" :
                self.destinationSwing.text = destinationText
            self.checking = ADtrain.turnoutsBusy = False
            return
        # Successful allocation, train can start (or continue running)
        self.allocatedSections += newAllocatedSections
        self.destinationSwing.text = self.lastRouteSection.getName()
        # If present block has no maximum speed, use default speed
        startSpeed = self.maxSpeed
        if startSpeed == 0 :
            startSpeed = len(ADsettings.speedsList)
        self.status = ADtrain.STARTING
        # Increase count of running trains
        if not self.running :
            self.running = True
            AutoDispatcher.runningTrains += 1
        # Disable input in Trains window
        self.enableSwing()
        # Make sure user is not updating train data
        # (Let's cope with Jython's lack of synchronization)
        while self.updating :
            AutoDispatcher.instance.waitMsec(100)
        if AutoDispatcher.simulation :
            self.entriesAhead.extend(route.step)
        # Create a list of sections that the train will use
        # (the list will be employed to track train movement)
        if not self.section in self.previousSections :
            self.previousSections.append(self.section)
        # Force panel re-drawing
        AutoDispatcher.repaint = True
        # Set turnouts
        route.setTurnouts()
        # Update list of blocks ahead
        for block in route.blocksList :
            if not block in self.blocksAhead :
                self.blocksAhead.append(block)
        # Train must reach next allocation point before being re-considered
        # for scheduling, unless allocated sections was lower than maximum
        self.allocationReady = self.allocatedSections < allocationAhead
        # Wait if user specified a delay before clearing signals
        if ADsettings.clearDelay > 0 :
            AutoDispatcher.instance.waitMsec(ADsettings.clearDelay)
        # Set exit signals
        route.clearSignals(self)
        # Is our current destination the target of the present schedule item?
        scheduleItem = self.schedule.getFirstAlternative()
        while scheduleItem.action == ADschedule.GOTO :
            if self.destination == scheduleItem.value :
                # Yes - Take note of commands to be executed while running
                self.schedule.next()
                self.queueCommands()
                break
            scheduleItem = self.schedule.getNextAlternative()
        # Since train is (about) moving, user must be given the posibility
        # of saving its new position to disk before quitting the program.
        AutoDispatcher.setTrainsDirty()
        # Is train starting from still (or was it already running)?
        if self.speedLevel <= 0 :
            # Starting from still. Set locomotive direction (could have changed)
            self.setOrientation(not self.reversed)
            # Delay departure, if user chose this option
            if (ADsettings.startDelayMin > 0 or
               ADsettings.startDelayMax > 0) :
                delay = ADsettings.startDelayMin + int(
                   float(ADsettings.startDelayMax -
                     ADsettings.startDelayMin) * 
                   AutoDispatcher.random.nextDouble())
                AutoDispatcher.instance.waitMsec(delay)
            # Switch front light on, if user chose this option
            if ADsettings.lightMode != 0 :
                self.setFunction(0, True)
            # Play start actions, if any
            if self.startAction != "" :
                self.doAction(self.startAction)
            elif ADsettings.defaultStartAction != "" :
                self.doAction(ADsettings.defaultStartAction)
        # Now start train!
        if (self.section.stopBlock[self.direction] == self.block or
           self.section.brakeBlock[self.direction] == self.block) :
            restrictedSpeed = self.section.getSignal(self.direction).getSpeed()
            if restrictedSpeed <= self.maxSpeed or self.maxSpeed == 0 :
                startSpeed = restrictedSpeed
        if self.locomotive != None :
            self.locomotive.brakeAdjusting = False
        self.changeSpeed(startSpeed)
        # Take note that train was started
        self.lastMove = System.currentTimeMillis()
        self.status = ADtrain.STARTED
        self.checking = ADtrain.turnoutsBusy = False
        return

    def queueCommands(self) :
        # Transfer commands from schedule to pending comands queue (items)
        # Unless they were already transferrred (in the later case,
        # commands are discarded)
        toBeAdded = not self.destination in self.itemSections
        scheduleItem = self.schedule.getFirstAlternative()
        # Transfer only commands that can be executed while train is running
        while scheduleItem.action > ADschedule.GOTO :
            if toBeAdded :
                self.itemSections.append(self.destination)
                self.items.append(scheduleItem)
            self.schedule.next()
            scheduleItem = self.schedule.getFirstAlternative()
            
    def processCommands(self, scheduleItem) :
        # Process schedule commands prefixed by $
        if scheduleItem.action == ADschedule.SWON :
            # Set train into "switching mode"
            self.switching = True
            AutoDispatcher.message("Train " + self.name +
              " entering switching mode")
            return
        if scheduleItem.action == ADschedule.SWOFF :
            # Clear "switching mode"
            self.switching = False
            AutoDispatcher.message("Train " + self.name +
              " exiting switching mode")
            return
        if scheduleItem.action == ADschedule.HELD :
            # Set signal to "held" state
            scheduleItem.value.setHeld(True)
            AutoDispatcher.message("Signal " + scheduleItem.value.getName()
              + " held")
            return
        if scheduleItem.action == ADschedule.RELEASE :
            # Clear "held" state of signal
            scheduleItem.value.setHeld(False)
            AutoDispatcher.message("Signal " + scheduleItem.value.getName()
              + " released")
            return
        if scheduleItem.action == ADschedule.SET_F_ON :
            # Set decoder function ON
            self.setFunction(scheduleItem.value, True)
            return
        if scheduleItem.action == ADschedule.SET_F_OFF :
            # Set decoder function OFF
            self.setFunction(scheduleItem.value, False)
            return
        if scheduleItem.action == ADschedule.WAIT_FOR :
            # Wait for section empty
            self.waitFor = scheduleItem.value
            AutoDispatcher.message("Train " + self.name
              + " waiting for section" + self.waitFor.getName())
            self.destinationSwing.setText("$WF:"+self.waitFor.getName())
            return
        if scheduleItem.action == ADschedule.DELAY :
            # Delay next commands
            delay = int(scheduleItem.value * 1000.)
            if delay > 0 :
                self.fastClock = False
                self.departureTime = (System.currentTimeMillis() + delay)
            return
        if scheduleItem.action == ADschedule.MANUAL_PRESENT :
            self.switchToManual(self.section)
            return
        if scheduleItem.action == ADschedule.MANUAL_OTHER :
            self.switchToManual(scheduleItem.value)
            return
        if scheduleItem.action == ADschedule.ERROR :
            # Wrong schedule
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Error in schedule of train \"" + self.name + "\": "
              + scheduleItem.message)
            self.status = ADtrain.ERROR
            return
        if scheduleItem.action == ADschedule.STOP :
            # End of schedule reached
            self.status = ADtrain.END_OF_SCHEDULE
            if ADsettings.lightMode == 2 :
                self.setFunction(0, False)
            AutoDispatcher.message("End of schedule for train "
              + self.name)
            self.destinationSwing.setText("End")
            if self.opTrain != None :
                try:
                    self.opTrain.move()
                finally :
                    i = 0 # Useless, just to complete the try construct
            return
        # Test for change of direction command
        newDirection = -1
        if scheduleItem.action == ADschedule.CCW :
            newDirection = ADsettings.ccw
        elif scheduleItem.action == ADschedule.CW :
            newDirection = 1-ADsettings.ccw
        if newDirection > -1 :
            # Change of direction
            if newDirection != self.direction :
                # Allow enough time for simulation step to complete
                if AutoDispatcher.simulation :
                    AutoDispatcher.instance.waitMsec(1000)
                self.direction = self.finalDirection = newDirection
                # We are now facing the opposite signal
                self.destinationSignal = self.section.signal[self.direction]
                # Reverse locomotive direction
                self.reversed = not self.reversed
                # Change direction in section
                self.section.allocate(self, self.direction)
                # Change color of all sections occupied by the train
                if not self.section in self.previousSections :
                    self.section.setColor()
                for section in self.previousSections :
                    section.setColor()
                for section in self.sectionsAhead :
                    section.setColor()
                AutoDispatcher.repaint = True
            AutoDispatcher.message("Train " + self.name + " headed "
              + ADsettings.directionNames[1-newDirection])
            self.updateSwing()
            return
        if scheduleItem.action == ADschedule.PAUSE :
            AutoDispatcher.message("Train " + self.name + " pausing for "
              + str(scheduleItem.value) + " sec.")
            delay = int(scheduleItem.value * 1000.)
            if delay > 0 :
                # Pause - Compute departure time
                self.fastClock = False
                self.departureTime = (System.currentTimeMillis() + delay)
                self.destinationSwing.setText("$P"+str(scheduleItem.value))
                self.updateSwing()
            return
        if scheduleItem.action == ADschedule.START_AT :
            if scheduleItem.value > FastListener.fastTime :
                self.fastClock = True
                self.departureTime = scheduleItem.value
                hours = int(scheduleItem.value/60)
                minutes = scheduleItem.value - hours * 60
                if minutes > 9 :
                    hours = str(hours) + ":" + str(minutes)
                else :
                    hours = str(hours) + ":0" + str(minutes)
                AutoDispatcher.message("Train " + self.name + " waiting until " + hours)
                self.destinationSwing.setText("$ST "+ hours)
                self.updateSwing()
            return
        if scheduleItem.action == ADschedule.SOUND :
            # Play sound
            scheduleItem.value.play()
            return
        if (scheduleItem.action == ADschedule.TC or
           scheduleItem.action == ADschedule.TT) :
            # Set turnout (or other accessory)
            try :
                t = InstanceManager.turnoutManagerInstance().getTurnout(scheduleItem.value)
            except :
                t = None
            if t == None :
                AutoDispatcher.log("Error in schedule of train \"" + self.name + "\":")
                AutoDispatcher.chimeLog("  Unknown turnout \"" + scheduleItem.value + "\"")
                self.status = ADtrain.ERROR
                return
            if scheduleItem.action == ADschedule.TC :
                t.setState(Turnout.CLOSED)
                AutoDispatcher.message("Train " + self.name + " : closed turnout "
                 + scheduleItem.value)
            else :
                t.setState(Turnout.THROWN)
                AutoDispatcher.message("Train " + self.name + " : thrown turnout "
                 + scheduleItem.value)
            return            
             
    def switchToManual(self, section) :
        # Try switching the section to Manual control
        section.setManual(True)
        # Check result
        if section.isManual() :
            # Fine
            AutoDispatcher.message("Section \"" + section.getName()
              + "\" switched to manual control")
        else :
            # Failed. Section probably does not have a "Manual control" sensor
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Cannot switch section \"" +
              section.getName() + "\" to manual control")

    def updateSwing(self) :
        # Update SWING I/O fields for train
        self.directionSwing.removeAllItems()
        self.directionSwing.addItem(ADsettings.directionNames[0])
        self.directionSwing.addItem(ADsettings.directionNames[1])
        if ADsettings.ccw == 0 :
            self.directionSwing.setSelectedIndex(self.direction)
        else :
            self.directionSwing.setSelectedIndex(1- self.direction)
        if self.section != None :
            self.sectionSwing.setSelectedItem(self.section.getName())
        else :
            self.sectionSwing.setSelectedItem("")
        self.resistiveSwing.setSelected(self.resistiveWheels)
        self.canStopAtBeginningSwing.setSelected(self.canStopAtBeginning)
        self.locoRoster.setSelectedItem(self.locoName)
        self.reversedSwing.setSelected(self.reversed)
        # Display schedule
        self.scheduleSwing.setText(self.schedule.text)

    def enableSwing(self) :
        # Enable-disable SWING input fields, depending on train status
        # Since self.running can change while processing,
        # use a local copy of it in order to obtain consistent output
        stopped = not self.running
        self.nameSwing.setEnabled(stopped)
        self.directionSwing.setEnabled(stopped)
        self.sectionSwing.setEnabled(stopped)
        self.resistiveSwing.setEnabled(stopped)
        self.canStopAtBeginningSwing.setEnabled(stopped)
        self.trainLengthSwing.setEnabled(stopped)
        self.trainAllocationSwing.setEnabled(stopped)
        self.engineerSwing.setEnabled(stopped)
        if self.engineerName == "Manual" :
            self.locoRoster.setEnabled(False)
            self.reversedSwing.setEnabled(False)
        else :
            self.locoRoster.setEnabled(stopped)
            self.reversedSwing.setEnabled(stopped)
        self.scheduleSwing.setEnabled(stopped)
        self.deleteButton.setEnabled(stopped)
        self.changeButton.setEnabled(stopped)
        self.setButton.setEnabled(stopped)
        self.startActionSwing.setEnabled(stopped)

    def getCurrentBlock(self) :
        return self.block
        
    def getPreviousBlock(self) :
        return self.previousBlock
        
    def getNextBlock(self) :
        if len(self.blocksAhead) > 0 :
            return self.blocksAhead[0]
        return None
        
class ADlocomotive :
    # Our locomotives. Contain speed tables (i.e. correspondence between 
    # speed levels and throttle settings). A locomotive is created for each
    # entry in JMRI roster. Additional locomotives are created for demo
    # layouts
    
    # STATIC VARIABLES

    locoIndex = {}
    
    # STATIC METHODS

    def getNames() :
        # Return the list of locomotive names
        return ADlocomotive.locoIndex.keys()
    getNames = ADstaticMethod(getNames)

    def getList() :
        # Return the list of locomotives
       return ADlocomotive.locoIndex.values()
    getList = ADstaticMethod(getList)

    def getByName(name) :
        # Find a locomotive by name
        return ADlocomotive.locoIndex.get(name, None)
    getByName = ADstaticMethod(getByName)

    # INSTANCE METHODS

    def __init__(self, name, address, speed, inJmri) :
        self.name = name
        ADlocomotive.locoIndex[name] = self
        self.throttle = None
        self.leadThrottle = None
        # Is this locomotive contained in JMRI roster?
        self.inJmriRoster = inJmri
        # Throttle not assigned yet
        self.throttleAssigned = False
        # Default 128 speed steps
        self.stepsNumber = 126.
        self.locoPaused = False
        self.currentSpeed = 0
        self.targetSpeed = 0
        self.rampingSpeed = 0
        self.savedSpeed = 0
        self.usedBy = None
        self.lastSent = -1L
        self.leadLoco = 0
        # Allow user to define/change address only if locomotive
        # is not in JMRI roster
        if inJmri :
            self.addressSwing = AutoDispatcher.centerLabel("")
        else :
            self.addressSwing = JTextField("", 4)
        self.setAddress(address)
        # Table of speeds corresponding to each speed level
        self.speedSwing = []
        for ind in range(len(ADsettings.speedsList)) :
            self.speedSwing.append(JTextField("", 4))
        self.setSpeedTable(speed)
        self.currentSpeedSwing = AutoDispatcher.centerLabel("0")
        self.accSwing = JTextField("0", 4)
        self.decSwing = JTextField("0", 4)
        self.runningTime = 0
        self.hoursSwing = JLabel("")
        self.hoursSwing.setHorizontalAlignment(JLabel.RIGHT)
        self.warnedTime = False
        self.mileage = 0.0
        self.milesSwing = JLabel("")
        self.milesSwing.setHorizontalAlignment(JLabel.RIGHT)
        self.warnedMiles = False
        self.runningStart = -1L
        self.setMomentum(0, 0)
        self.brakeKey = ""
        self.learningClear()

    def setSpeedTable(self, speed) :
        # Change speeds table and display its contents
        levelsNumber = len(ADsettings.speedsList)
        if speed == None :
            # Speed table not defined
            self.speed = []
            # Assign even spaced speeds from 0.01 to 1.0
            if levelsNumber > 1 :
                step = 0.99 / float(levelsNumber - 1)
            else :
                step = 0.
            level = 0.01
            for i in range(levelsNumber) :
                self.speed.append(round(level,2))
                level += step
        else :
            # Speed table defined
            # Use it
            self.speed = speed
            # Extend table, if needed
            while len(self.speed) < levelsNumber :
                self.speed.append(1.)
        # Update swing fields
        for i in range(levelsNumber) :
            self.speedSwing[i].setText(str(self.speed[i]))
            
    def getCloserLevel(self, throttleValue) :
        # Return level corresponding to a given throttle setting.
        # If no exact match is found, the higher closer level is returned.
        # Returned value is in range 1-levelsNumber.
        levelsNumber = len(ADsettings.speedsList)
        closerLevel = levelsNumber
        ind = 1
        for s in self.speed :
            if s >= throttleValue :
                closerLevel = ind
                break
            ind += 1
        if closerLevel > levelsNumber :
            return levelsNumber
        return closerLevel
        
    def setMomentum(self, acceleration, deceleration) :
        if acceleration < 0 :
            acceleration = 0
        if acceleration > 255 :
            acceleration = 255
        if deceleration < 0 :
            deceleration = 0
        if deceleration > 255 :
            deceleration = 255
        self.acceleration = acceleration
        self.deceleration = deceleration
        # Compute acceleration/deceleration rate
        # Same as NMRA DCC CV3 and CV4
        if acceleration == 0 :
            self.accStep = 0
        else :
            try :
                self.accStep = 1./(float(acceleration) * 8.96)
            except :
                self.accStep = self.acceleration = 0
        if deceleration == 0 :
            self.decStep = 0
        else :
            try :
                self.decStep = 1./(float(deceleration) * 8.96)
            except :
                self.decStep = self.deceleration = 0
        self.accSwing.setText(str(self.acceleration))
        self.decSwing.setText(str(self.deceleration))

    def getAcceleration(self) :
        return self.accStep * 10

    def getDeceleration(self) :
        return self.decStep * 10

    def setAddress(self, address) :
        # Change dcc address
        if self.throttle != None :
            self.throttle.release(None)
        if address <1 :
                address = 1
        self.address = address
        self.addressSwing.setText(str(address))
        self.throttleAssigned = False
        
    def assignThrottle(self) :
        # Ignore call if throttle already assigned
        if self.throttleAssigned :
            return
        self.throttleAssigned = True
        # Assign the throttle
        AutoDispatcher.message("Acquiring throttle for locomotive " + self.name
          + " (" + str(self.address) + ")")
        if (self.address > 100) :
            long = True
        else :
            long = False
        # request address, timeout set to 5 seconds
        self.throttle = AutoDispatcher.instance.getThrottle(self.address, long, 5)
        if (self.throttle == None) :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Couldn't assign throttle " + str(self.address) + "!")
            self.throttleAssigned = False
        else :
            if self.leadLoco != 0 :
                AutoDispatcher.message("Acquired throttle for consist "
                  + self.name + " (" + str(self.address) + ")")
                self.leadThrottle = AutoDispatcher.instance.getThrottle(
                  self.leadLoco, long, 5)
                if (self.leadThrottle != None) :
                    AutoDispatcher.message("Acquired throttle for consist "
                      + self.name + " (" + str(self.leadLoco) + ")")
                else :
                    AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
                     "Couldn't assign throttle" + 
                    str(self.leadLoco)+ " for consist " + self.name + "!")
                    self.throttleAssigned = False
            else :
                AutoDispatcher.message("Acquired throttle for locomotive "
                  + self.name + " (" + str(self.address) + ")")
                self.leadThrottle = self.throttle
            speedStepMode = self.throttle.getSpeedStepMode()
            if speedStepMode == SpeedStepMode.NMRA_DCC_128 :
                self.stepsNumber = 126.
            elif speedStepMode == SpeedStepMode.NMRA_DCC_28 :
                self.stepsNumber = 28.
            elif speedStepMode == SpeedStepMode.NMRA_DCC_27 :
                self.stepsNumber = 27.
            else :
                self.stepsNumber = 14.
            self.currentSpeed = self.throttle.getSpeedSetting()

            
    def releaseThrottle(self) :
        if self.throttle != None :
            if ADsettings.lightMode != 0 :
                self.setFunction(0, False)
            self.throttle.release(None)
            self.throttle = None
            self.throttleAssigned = False
            if self.leadLoco != 0 :
                self.leadThrottle.release(None)
                AutoDispatcher.message("Released throttles of consist "
                  + self.name + " (" + str(self.address) + ", " + 
                  str(self.leadLoco) + ")")
            else :
                AutoDispatcher.message("Released throttle of locomotive "
                + self.name + " (" + str(self.address) + ")")
            self.leadThrottle = None

    def outputMileage(self) :
        # Output values of mileage and operation hours
        minutes = int(self.runningTime/60000)
        hours = int(minutes/60)
        minutes -= hours * 60
        if minutes < 10 :
            minutes = "0" + str(minutes)
        else :
            minutes = str(minutes)
        self.hoursSwing.text = str(hours) + ":" + minutes
        if self.warnedTime :
            self.hoursSwing.setForeground(Color.red)
        else :
            self.hoursSwing.setForeground(Color.black)
        if ADsettings.units == 25.4 :
            multiplier = ADsettings.scale / 1609344.
        else :
            multiplier = ADsettings.scale / 1000000.
        self.milesSwing.text = str(round(self.mileage * multiplier,1))
        if self.warnedMiles :
            self.milesSwing.setForeground(Color.red)
        else :
            self.milesSwing.setForeground(Color.black)

    def setOrientation(self, forward) :
        # Set locomotive direction
        if self.throttle != None :
            self.throttle.setIsForward(forward)
            if ADsettings.dccDelay > 0 :
                sleep(float(ADsettings.dccDelay)/1000.)

    def setFunction(self, functionNumber, on) :
        if self.leadThrottle != None :
            if functionNumber < 0 or functionNumber > 28 :
                return
            command = "self.leadThrottle.setF" + str(functionNumber)
            if on :
                command += "(True)"
            else :
                command += "(False)"
            exec(command)
            if ADsettings.dccDelay > 0 :
                sleep(float(ADsettings.dccDelay)/1000.)
            
    def changeSpeed(self, speedLevel) :
        # Stop ?
        if speedLevel <= 0 :
            self.changeThrottleSpeed(speedLevel)
            if (self.usedBy != None and self.usedBy.running
              and self.usedBy.engineerSetLocomotive != None
              and not AutoDispatcher.simulation) :
                self.usedBy.stop()
            return
        # Speed increase/decrease - speedControl thread will take care of it
        if speedLevel > len(self.speed) :
            speedLevel = len(self.speed)
        self.changeThrottleSpeed(self.speed[speedLevel-1])

    def changeThrottleSpeed(self, speed) :
        if self.locoPaused :
            self.savedSpeed = speed
            return
        if self.targetSpeed == speed :
            return
        self.targetSpeed = speed
        if speed <= 0 :
            # STOP
           if (ADsettings.stopMode != ADsettings.PROGRESSIVE_STOP
             or speed < 0) :
                speed = -1
                if self.throttle != None :
                    self.throttle.setSpeedSetting(speed)
#                self.targetSpeed = self.rampingSpeed = self.currentSpeed = 0
#                self.targetSpeed = 0
#                self.currentSpeedSwing.setText("0")
#                self.updateMeter()
        elif self.runningStart == -1L :
                self.runningStart = System.currentTimeMillis()
        if not AutoDispatcher.paused and not AutoDispatcher.stopped :
            AutoDispatcher.message("Locomotive " + self.name + " at speed "
              + str(self.targetSpeed))
    
    def updateMeter(self) :
        # Updates locomotive's operation time
        # Called when the locomotive is stopped
        if self.runningStart != -1L :
            self.runningTime += System.currentTimeMillis() - self.runningStart
            self.runningStart = -1L

    def getName(self) :
        return self.name

    def getThrottleSpeed(self) :
        return self.rampingSpeed
        
    def pause(self) :
        self.savedSpeed = self.targetSpeed
        if ADsettings.pauseMode == ADsettings.STOP_TRAINS :
            self.changeThrottleSpeed(0)
        else :
            self.changeThrottleSpeed(-1)
        self.locoPaused = True
        # Clear learning data, since they are now meaningless
        self.brakeAdjusting = False

    def resume(self) :
        self.locoPaused = False
        self.changeThrottleSpeed(self.savedSpeed)
        
# Self-learning methods of the locomotive ==============

    def learningClear(self) :
        # Variables for self-learning braking method
        self.brakeAdjusting = False
        self.delay = 0
        self.decAdjustment = 0.
        self.initialTime = -1L
        self.initialSpeed = 0.
        self.brakingStartTime = -1L
        self.brakingSpeed = 0
        self.brakingEndTime = -1L
        self.stoppingTime = -1L

    def learningBrake(self) :
        # Train entering a braking block
        self.length = 0
        if (not ADsettings.selfLearning or self.decStep == 0
           or self.usedBy == None or self.throttle == None or
           self.brakeAdjusting or AutoDispatcher.simulation) :
            # Self-learning not supported or already active
            return False
        # Make sure we have all information needed
        block = self.usedBy.block
        previousBlock = self.usedBy.previousBlock
        train = self.usedBy
        if block == None or previousBlock == None or train == None :
            return False
        self.learningClear()
        # Take note of time and speed
        self.initialTime = System.currentTimeMillis()
        self.initialSpeed = self.rampingSpeed
        # Since we must brake, make sure that speed is not being increased
        if self.targetSpeed > self.rampingSpeed :
            self.targetSpeed = self.rampingSpeed
        # Build identification key
        self.brakeKey = (self.name + "$" + block.getName() + "$"
          + previousBlock.getName())
        # Did we already stop in this block?
        if train.brakingHistory.has_key(self.brakeKey) :
            # Yes retrieve previous data
            data = train.brakingHistory[self.brakeKey]
            self.recordedValues = data[0]
            self.squareSum = data[1]
        else :
            # First time we are braking in this block
            # Initialize data
            self.recordedValues = 0
            self.squareSum = 0
        # Compute braking data 
        # (if we have needed info and we are not running yet at minimimum speed)
        if self.recordedValues > 0 and self.targetSpeed > self.speed[0] :
            self.length = sqrt(self.squareSum/float(self.recordedValues))
            # Reduce length, in order to reach minimum speed 2 seconds before
            # reaching the stop block
            length = self.length - self.speed[0] * 2.
            if length > 0. :
                # How much time is required to reach minimum speed?
                brakeTime = ((self.initialSpeed - self.speed[0]) / 
                  self.decStep * 100.)
                # How much space is required to reach minimum speed?
                brakeLength = ((self.initialSpeed + self.speed[0])
                  * brakeTime / 2000.)
                # Is block length sufficient?
                delaySpace = length - brakeLength
                if delaySpace < 0 :
                    # No, we must reduce momentum
                    # Compute time required to break
                    # try
                    brakeTime = (length * 2000. / (self.initialSpeed +
                      self.speed[0]))
                    # Compute acceleration
                    self.decAdjustment = ((self.initialSpeed - self.speed[0]) *
                     100. / brakeTime)  - self.decStep
                    self.brakeAdjusting =True
                else :
                    # Enough space
                    # How much time is required to reach the target speed?
                    targetTime = ((self.initialSpeed - self.targetSpeed) / 
                      self.decStep *100.)
                    # Let's compute braking delay
                    self.delay = int(delaySpace * 1000. / self.targetSpeed + 
                      targetTime)
                    if self.delay > 0 :
                        start_new_thread(self.learningDelayedBrake,
                          (self.brakeKey,))
                        self.brakeAdjusting = True
                        return True
        else :
            self.brakeAdjusting = True
        # We don't have previous data or something went wrong
        # Let's locomotive start braking immediately
        self.brakingStartTime = self.initialTime
        self.brakingSpeed = self.initialSpeed
        return False


    def learningDelayedBrake(self, key) :
        # Wait before braking
        sleep(float(self.delay)/1000.)
        # Make sure that braking is still needed
        if (self.delay > 0 and self.stoppingTime == -1L and
           self.brakingStartTime == -1L and key == self.brakeKey) :
            self.brakingStartTime = System.currentTimeMillis()
            self.brakingSpeed = self.rampingSpeed
            if self.usedBy == None :
                self.changeSpeed(1)
            else :
                self.usedBy.changeSpeed(1)

    def learningEnd(self) :
        # Train completed braking
        if self.brakeAdjusting and self.brakingEndTime == -1L :
            self.brakingEndTime = System.currentTimeMillis()

    def learningStop(self) :
        # Train entering a stop block
        # Were we controlling braking times?
        if not self.brakeAdjusting :
            # No, ignore call
            return
        # Yes, take note of stop time
        self.stoppingTime = System.currentTimeMillis()
        # Recompute braking data
        # (Computation are made assuming a constant speed/throttle-setting
        # ratio. This is seldom true, but each iteration will improve results)
        # Evaluate block length, based on speeds and timing
        # Computed length is expressed as throttle-setting * time and does
        # thus not refer to the actual length in inches or mm.
        brakingTime = creepingTime = 0
        # Did train start braking (at least!)
        if self.brakingStartTime == -1L :
            # No - braking delay was excessive!
            # Set length to half the length of block computed
            # in previous iteration (at least for the time being. A more
            # accurate computation can be implemented later)
            if AutoDispatcher.debug :
                print (self.brakeKey + " Delay " + str(self.delay)
                  + " Adjustment " + str(self.decAdjustment)
                  + " length " + str(self.length) + " braking not started")
            length = self.length * 0.5
        else :
            # Yes, train started braking
            # Compute distance at full speed
            length  = (float(self.brakingStartTime - self.initialTime) *
              (self.brakingSpeed + self.initialSpeed) / 2000.)
            # Did train attain minimum speed?
            if self.brakingEndTime == -1L :
                # No, set end of braking time to stopping time
                if AutoDispatcher.debug :
                    print (self.brakeKey + " Delay " + str(self.delay)
                      + " Adjustment " + str(self.decAdjustment)
                      + " length " + str(length) + " minimum not reached")
                self.brakingEndTime = self.stoppingTime
                if length > self.length :
                    length = self.length
                length = length * 0.5
            else :
                # Yes, minimum speed attained
                # Compute distance run at minimum speed
                creepingTime = self.stoppingTime - self.brakingEndTime
                length += (self.rampingSpeed * float(creepingTime) / 1000.)
                if AutoDispatcher.debug :
                    print (self.brakeKey + " Delay " + str(self.delay)
                      + " Adjustment " + str(self.decAdjustment)
                      + " length " + str(length) + " creepingTime " + str(creepingTime))
            # Now add the length of the braking ramp (braking distance)
            brakingTime = self.brakingEndTime - self.brakingStartTime
            length += ((self.brakingSpeed + self.rampingSpeed) * 
              float(brakingTime) / 2000.)
        # Update braking data
        if brakingTime >=0 and creepingTime >= 0 and length > 0 :
            self.recordedValues += 1
            self.squareSum += length * length
            train = self.usedBy
            if train != None :
                train.brakingHistory[self.brakeKey] = [self.recordedValues,
                  self.squareSum]
        # Clear data
        self.brakeKey = ""
        self.brakeAdjusting = False
        
# ENGINEER ==============

class ADengineer :
    # Our engineer is rather simple. It only lets know AutoDispatcher that
    # the ADlocomotive class should be used. ADlocomotive takes care of all
    # the rest.
    def setLocomotive(self, locomotive) :
        return
        
# ROUTE ==============

class ADautoRoute :
    # Defines a route, i.e. the list of entries connecting two sections
    def __init__(self, startSection, endSection, direction, switching) :
        # Train direction
        self.direction = direction
        # Is train running in switching mode?
        self.switching = switching
        # Error indicator. Set if destination is "transit-only"
        self.error = None
        # List of blocks contained in the route (filled by setTurnouts method)
        self.blocksList = []
        # Create an array to keep note of which sections were "visited"
        # in order to avoid an endless loop
        self.searchedSections = []
        # And now search - step will contain the list of entries found
        self.step = self.__fromTo__(startSection, endSection, direction)
        if self.found :
            self.error = None

    def __fromTo__(self, startSection, endSection, direction):
        # Internal method. Recursively explores all possibe routes.
        # If more than one route is found the shorter one (i.e. that with
        # less steps) is returned.
        self.found = False
        # Exit if this section was already "visited" (to avoid an endless loop!)
        if startSection in self.searchedSections :
            return []
        # Check if direction is acceptable for this section or if train is
        # in "switching mode" (switching trains can enter any track)
        if (((direction + 1) & startSection.direction) == 0 and
           not self.switching) :
            return []
        # Did we reach destination?
        if startSection == endSection :
            # Check if destination is transit-only
            if endSection.transitOnly[direction] and not self.switching :
                # Transit only destination
                self.error = endSection
            else :
                self.found = True
            return []
        # Mark this section as "visited" (to avoid an endless loop!)
        self.searchedSections.append(startSection)
        route = []
        routeLen = 0
        ## Get next sections in the desired direction
        for entry in startSection.getEntries(direction) :
            nextSection = entry.getExternalSection()
            # Compute direction along next section
            if entry.getDirectionChange() :
                newDirection = not direction
            else :
                newDirection = direction
            # Go ahead exploring
            newRoute = self.__fromTo__(nextSection, endSection, newDirection)
            if self.found :
                # A possible route found
                # Is it the first one found, or is it shorter than
                # previous routes?
                newLen = len(newRoute) + 1
                if routeLen == 0 or newLen < routeLen :
                    # Yes, keep it
                    route = [entry]
                    route.extend(newRoute)
                    routeLen = newLen
        # Allow this section to be considered in alternative routes
        self.searchedSections.pop()
        self.found = routeLen > 0
        return route

    def reduce(self, train, allocationAhead):
        # Reduces the length of the route (i.e. number of sections 
        # contained in it) to the minimum between:
        #   its total length;
        #   allocationAhead value.
        # Unless the train runs in "switching mode", the reduced
        # route is always terminated with a non transit-only section
        # (making the route longer or shorter, as needed) in order to
        # avoid (as far as possible) "grid lock" situations.
        # The route is anyway terminated before the first occupied section
        # encountered, unless:
        #   The occupied section is transit-only and
        #   "burst mode" is enabled
        #   (i.e. its direction name is suffixed by "+) and
        #   the section is occupied by a train running in the same 
        #   direction of the present train and the first non
        #   transit-only  section encountered after it is free.
        keep = lastFreeSection = 0
        firstTime = True
        # Number of sections with signal (or not transit-only) encountered
        nSections = 0
        previousBurst = False
        direction = self.direction
        ind = 1
        # Process entries contained in the route
        for entry in self.step :
            # Check if possible Xovers are available
            if not entry.areXoversAvailable() :
                # An Xover is allocated to another train. Terminate the route
                break
            # Get the corresponding section
            section = entry.getExternalSection()
            # Adjust train direction, if needed
            if entry.getDirectionChange() :
                direction = not direction
            # Is use of this section allowed ?
            if (section.isManual() or (not section.transitOnly[direction] and
              ADgridGroup.lockRisk(train,
              entry.getInternalSection(), section))) :
                # No
                break
            # get the train (if any) to which the section is allocated
            otherTrain = section.getAllocated()
            # Check if the section can be used by present train
            if (not section.isAvailable() and (otherTrain != train
               or section.isOccupied())) :
                # The section is occupied or allocated to another train
                # Is this the first section along the route or is
                # "burst" mode not allowed?
                if (firstTime or (not section.burst and not previousBurst)) :
                    # Present train cannot use it
                    break
                # Is a train using this section?
                if otherTrain == None :
                    # No train, section contains only rolling stock.
                    # Present train cannot use it
                    break
                # We have another train: is it running in the same direction?
                if otherTrain.getDirection() != direction :
                # Other train in opposite direction, present train cannot start
                    break
                # If this is not a Transit-Only section, train can run up to
                # previous  empty section
                if not section.transitOnly[direction] :
                    keep = lastFreeSection
                    break
            else :
                # Take note that section is free (or allocated to this train
                # but not occupied yet)
                lastFreeSection = ind
                # Count free sections encountered and equipped with exit signal
                if (not section.transitOnly[direction] or
                   section.getSignal(direction).hasHead()) :
                    nSections += 1
                # Could the reduced route end in this section?
                if not section.transitOnly[direction] or self.switching :
                    # The section is not transit-only (or train
                    # in switching mode).
                    # Terminate the route here if the requested number
                    # of sections ahead was found
                    keep = lastFreeSection
                    if nSections >= allocationAhead :
                        break
            # Take note that we already encountered a section
            firstTime = False
            # PreviousBurst temporarily suppressed (could be implemented
            # as an option)
#            previousBurst = section.burst
            # Make sure signal is not held
            if section.getSignal(direction).isHeld() :
                break
            ind += 1
        # Job almost done.  "keep" contains the number of entries to be kept
        # Remove possible trailing entries from the route
        while len(self.step) > keep :
            self.step.pop()
        return nSections

    def allocate(self, train):
        # Allocates all sections contained in the (reduced) route,
        # further reducing the route if part of it is occupied by 
        # another train (even if running in the same direction)
        keep = 0
        nSections = 0
        freeRoute = True
        direction = self.direction
        destination = None
        ind = 1
        # Process all entries in the route
        for entry in self.step :
            # Adjust direction, if needed
            if entry.getDirectionChange() :
                direction = not direction
            # Retrieve relevant section
            section = entry.getExternalSection()
            # Make sure section is not occupied or allocated
            if section.isAvailable() :
                # Allocate sections in front of other trains (if any)
                # only if they are not transit-only
                if freeRoute or not section.transitOnly[direction] :
                    section.allocate(train, direction)
            elif section.getAllocated() != train :
                # Section occupied or allocated to another train
                freeRoute = False
            if section.getAllocated() == train :
                train.lastRouteSection = section
            # Take note of last not occupied section
            if freeRoute :
                keep = ind
                if (not section.transitOnly[direction] or
                   section.getSignal(direction).hasHead()) :
                    nSections += 1
                destination = section
                if not section in train.sectionsAhead :
                    train.sectionsAhead.append(section)
            # Make sure signal is not held
            if section.getSignal(direction).isHeld() :
                break
            ind += 1
        # Remove trailing elements from the route (if needed)
        while len(self.step) > keep :
            self.step.pop()
        if destination != None :
            train.destination = destination
            direction = destination.trainDirection
            train.finalDirection = direction
            train.destinationSignal = destination.signal[direction]
        return nSections

    def setTurnouts(self):
        # Set all turnouts contained in the (reduced) route
        # Record also blocks of the route in blocksList
        self.blocksList = []
        self.turnoutList = []
        # Before starting, make sure we have a valid route :-)
        if len(self.step) == 0 :
            return
        direction = self.direction
        # Start setting turnouts from stop block of starting section
        startBlock = self.step[0].getInternalSection().stopBlock[direction]
        for entry in self.step :
            endBlock = entry.getInternalBlock()
            # Set turnouts up to exit block included
            thrown = self.__setTurnouts__(startBlock, endBlock, direction)
            # Set turnouts from present section to next section
            startBlock = entry.getExternalBlock()
            if startBlock.setTurnouts(endBlock, self.turnoutList) :
                thrown = True
            # and vice-versa
            if endBlock.setTurnouts(startBlock, self.turnoutList) :
                thrown = True
            # Keep note if some turnouts were thrown
            # (info will be used when clearing signals)
            entry.getInternalSection().turnoutsThrown = thrown
            # Adjust train direction, if needed
            if entry.getDirectionChange() :
                direction = not direction
        # Set turnouts from entry of last section to its stop block
        endBlock = startBlock.getSection().stopBlock[direction]
        if self.__setTurnouts__(startBlock, endBlock, direction) :
            # Keep note that some turnouts were thrown
            startBlock.getSection().turnoutsThrown = True
        
    def __setTurnouts__(self, startBlock, endBlock, direction) :
        # Internal method
        # Sets turnouts between two blocks of the same section
        # Returns True if any turnout was thrown.
        # Ignore call, if start and end block are the same one
        if startBlock == endBlock :
            self.blocksList.append(startBlock)
            return False
        thrown = False
        previousBlock = None
        for block in startBlock.getSection().getBlocks(direction) :
            # Find starting block
            if previousBlock == None :
                if block == startBlock :
                    previousBlock = block
                    self.blocksList.append(block)
            else:
                # Starting block found, set turnouts
                # Note that we need to set turnouts included in both paths:
                # From previousBlock to block; and 
                # from block to previousBlock.
                if block.setTurnouts(previousBlock, self.turnoutList) :
                    thrown = True
                if previousBlock.setTurnouts(block, self.turnoutList) :
                    thrown = True
                self.blocksList.append(block)
                # Look for ending block
                if block == endBlock :
                    break
                previousBlock = block
        return thrown

    def clearSignals(self, train):
        # Clears all signals along the route
        # Cannot be run before setTurnouts method!
        # First of all, make a copy of sectionsAhead
        sections = []
        sections.extend(train.previousSections)
        sections.append(train.section)
        sections.extend(train.sectionsAhead)
        # Remove last section (its signal will stay red)
        sections.pop()
        # Reverse sections order
        # (set the farer signal first)
        sections.reverse()
        # Next signal is red
        oldIndication = 0
        anyThrown = False
        processedSections = []
        setRed = False
        for section in sections :
            if section in processedSections :
                continue
            processedSections.append(section)
            signal = section.getSignal(section.trainDirection)
            if setRed :
                signal.setIndication(0)
                continue
            if section.turnoutsThrown :
                anyThrown = True
            indication = ADindication.getIndication(oldIndication, anyThrown)
            signal.setIndication(indication)
            # No need of repainting panel, Layout Editor will do it anyway
            AutoDispatcher.repaint = False
            if signal.hasHead() :
                anyThrown = False
                oldIndication = indication
            # Make sure train did not advance in the meantime
            if section == train.section :
                setRed = True
                           
# SIGNAL HEAD ==============

class ADsignalHead :
    # Our signal head class 
    # Created also if there is no signal head in JMRI
    def __init__(self, signalName) :
        self.name = signalName
        self.signalHead = None
        self.iconOnLayout = False
        if signalName.strip() != "" :
            self.signalHead = InstanceManager.getDefault(SignalHeadManager).getSignalHead(signalName)
            if self.signalHead != None :
                self.setHeld(False)
                self.iconOnLayout = signalName in AutoDispatcher.signalIcons

    def setAppearance(self, appearance) :
        # Record new signal appearance and modify also that of the signal head
        # (if available)
        self.appearance = appearance
        if self.signalHead != None :
            if (not ADsettings.trustSignals or
               self.signalHead.getAppearance() != self.appearance) :
                AutoDispatcher.signalCommands[self.signalHead] = [
                  self.appearance, System.currentTimeMillis()]
                self.signalHead.setAppearance(self.appearance)
                # Wait if user specified a delay between signal operation
                if ADsettings.signalDelay > 0 :
                    AutoDispatcher.instance.waitMsec(
                      ADsettings.signalDelay)

    def getAppearance(self) :
        return self.appearance

    def isHeld(self) :
        # Checks if the SignalHead (if any) is "HELD"
        if self.signalHead == None :
                return False
        else :
                return self.signalHead.getHeld()

    def setHeld(self, newHeld) :
        # Set the SignalHead (if any) to "HELD"
        if self.signalHead != None :
            self.signalHead.setHeld(newHeld)
            
    def hasHead(self) :
        return self.signalHead != None
            
    def hasIcon(self) :
        return self.iconOnLayout

# SIGNAL MAST ==============

class ADsignalMast :
    # Our multi-head signal class 
    # Created also if there is no signal head in JMRI
    
    # STATIC VARIABLES
    
    signalsList = {}
    
    # STATIC METHODS
    
    def getByName(name) :
        return ADsignalMast.signalsList.get(name, None)
    getByName = ADstaticMethod(getByName)

    def provideSignal(name) :
        signal = ADsignalMast.getByName(name)
        if signal == None :
            signal = ADsignalMast(name, ADsettings.signalTypes[0], [name])
        return signal
    provideSignal = ADstaticMethod(provideSignal)

    def getNames() :
        return ADsignalMast.signalsList.keys()
    getNames = ADstaticMethod(getNames)

    def getList() :
        return ADsignalMast.signalsList.values()
    getList = ADstaticMethod(getList)

    def getTable() :
        outBuffer = []
        for signal in ADsignalMast.signalsList.values() :
            outLine = [signal.name]
            outLine.append(signal.signalType.name)
            outHeads = []
            for h in signal.signalHeads :
                if h == None :
                    outHeads.append("")
                else :
                    outHeads.append(h.name)
            outLine.append(outHeads)
            outBuffer.append(outLine)
        return outBuffer
    getTable = ADstaticMethod(getTable)

    def putTable(inBuffer) :
        for inLine in inBuffer :
            type = ADsettings.signalTypes[0]
            for newType in ADsettings.signalTypes :
                if newType.name == inLine[1] :
                    type = newType
                    break
            ADsignalMast(inLine[0], type, inLine[2])            
    putTable = ADstaticMethod(putTable)

    def __init__(self, signalName, signalType, signalHeads) :
        self.name = signalName
        self.signalType = signalType
        self.signalType.changeUse(1)
        self.inUse = 0
        self.headsNumber = self.signalType.headsNumber
        self.signalHeads = [None] * self.headsNumber
        ind = 0
        for headName in signalHeads :
            if ind >= self.headsNumber :
                break
            self.signalHeads[ind] = ADsignalHead(headName)
            ind += 1
        self.indication = -1
        self.setIndication(0)
        ADsignalMast.signalsList[self.name] = self

    def setIndication(self, indication) :
        if self.indication == indication :
            return
        self.indication = indication
        if self.indication >= len(self.signalType.aspects) :
            return
        aspects = self.signalType.aspects[indication]
        for ind in range(self.headsNumber) :
            if self.signalHeads[ind] != None :
                self.signalHeads[ind].setAppearance(aspects[ind])

    def changeUse(self, increment) :
        self.inUse += increment
        if self.inUse < 0 :
            self.inUse = 0
            self.signalType.changeUse(-1)
            newDic = {}
            for s in ADsignalMast.getList() :
                if s != self :
                    newDic[s.name] = s
            ADsignalMast.signalsList = newDic           

    def getName(self) :
        return self.name

    def getIndication(self) :
        return self.indication

    def getSpeed(self) :
        if self.indication >= len(self.signalType.speeds) :
            return 0
        speed = self.signalType.speeds[self.indication]
        if speed < 0 :
            if self.indication >= len(ADsettings.indicationsList) :
                return 0
            return ADsettings.indicationsList[self.indication].speed
        return speed + 1

    def setHeld(self, newHeld) :
        if self.signalHeads > 0 :
            self.signalHeads[0].setHeld(newHeld)
    
    def isHeld(self) :
        # Checks if any SignalHead is "HELD"
        for head in self.signalHeads :
            if head != None :
                if head.isHeld() :
                    return True
        return False

    def hasIcon(self) :
        # Checks if any SignalHead has an icon
        for head in self.signalHeads :
            if head != None :
                if head.hasIcon() :
                    return True
        return False
            
    def hasHead(self) :
        if self.headsNumber < 1 or self.signalHeads[0] == None :
            return False
        return self.signalHeads[0].hasHead()

# SIGNAL INDICATIONS ==============

class ADindication :
           
    # STATIC METHODS

    def getIndication(nextIndication, nextTurnout) :
        next = nextIndication
        for i in range(3) :
            for j in range(2, len(ADsettings.indicationsList)) :
                a = ADsettings.indicationsList[j]
                if ((a.nextIndication == next
                or (a.nextIndication >=0 and next >= 0 and 
                ADsettings.indicationsList[a.nextIndication].name ==
                ADsettings.indicationsList[next].name))
                and a.nextTurnout == nextTurnout) :
                    return j
            if (i & 1) == 0 :
                next = -1
            else :
                next = nextIndication
            if i == 1 :
                nextTurnout = -1
        return 1
    getIndication = ADstaticMethod(getIndication)

    def __init__(self, name, nextIndication, nextTurnout, speed) :
        self.name = name
        self.nextIndication = nextIndication
        self.nextTurnout = nextTurnout
        if self.nextTurnout < -1 :
            self.nextTurnout = -1
        elif self.nextTurnout > 1 :
            self.nextTurnout = 1
        self.speed = speed
        self.nameSwing = JTextField(self.name, 20)
        self.nextIndicationSwing = JComboBox()
        self.nextTurnoutSwing = JComboBox(["-", "Closed", "Thrown"])
        self.nextTurnoutSwing.setSelectedIndex(self.nextTurnout+1)
        self.speedSwing = JComboBox()

# SIGNAL TYPE ==============

class ADsignalType :

    # STATIC METHODS

    def adjust() :
        for s in ADsettings.signalTypes :
            s.adjustIndications()
    adjust = ADstaticMethod(adjust)
           
    # INSTANCE METHODS

    def __init__(self, name, aspects, speeds) :
        # aspects = [[aspect0, aspect1...],...]
        self.name = name
        self.inUse = 0
        # Compute number of signal heads
        self.headsNumber = 1
        for a in aspects :
            if len(a) > self.headsNumber :
                self.headsNumber = len(a)
        # Compute number of aspects
        if ADsettings == None :
            self.aspectsNumber = 2
        else :
            self.aspectsNumber = len(ADsettings.indicationsList)
        if len(aspects) > self.aspectsNumber :
            self.aspectsNumber = len(aspects)
        # Initialize aspects for each head
        # Default setting for stop = all heads RED
        self.aspects = [[SignalHead.RED] * self.headsNumber]
        # Default setting for other indications = all heads GREEN
        for i in range(self.aspectsNumber-1) :
            self.aspects.append([SignalHead.GREEN] * self.headsNumber)
        # Set actual aspects
        i = 0
        for a in aspects :
            j = 0
            for aa in a :
                self.aspects[i][j] = aa
                j += 1
            i += 1
        self.speeds = [-1] * self.aspectsNumber
        i = 0
        for s in speeds :
            if i >= self.aspectsNumber :
                break
            self.speeds[i] = s
            i += 1
        self.nameSwing = JTextField(self.name, 20)

    def adjustIndications(self) :
        diff = len(ADsettings.indicationsList) - self.aspectsNumber
        if diff > 0 :
            for i in range(diff) :
                self.aspects.append([SignalHead.GREEN] * self.headsNumber)
                self.speeds.append(-1)
        self.aspectsNumber = len(ADsettings.indicationsList)

    def changeUse(self, increment) :
        self.inUse += increment
        if self.inUse < 0 :
            self.inUse = 0

# SETTINGS ==============

class ADsettings :
    # Contains script settings, in a format suitable to save-retrieve them from preferences file

    # CONSTANTS
    
    # Pause modes
    IGNORE = 0
    STOP_TRAINS = 1
    EMERGENCY_STOP_TRAINS = 2
    POWER_OFF = 3 
    ATTENTION_SOUND = 0
    START_STOP_SOUND = 1
    DERAILED_SOUND = 2
    STALLED_SOUND = 3
    LOST_CARS_SOUND = 4
    WRONG_ROUTE_SOUND = 5
    # Color names dictionary
    colors = {"BLACK": Color.BLACK,
              "BLUE": Color.BLUE,
              "CYAN": Color.CYAN,
              "DARK_GRAY": Color.DARK_GRAY,
              "GRAY": Color.GRAY,
              "GREEN": Color.GREEN,
              "LIGHT_GRAY": Color.LIGHT_GRAY,
              "MAGENTA": Color.MAGENTA,
              "ORANGE": Color.ORANGE,
              "PINK": Color.PINK,
              "RED": Color.RED,
              "WHITE": Color.WHITE,
              "YELLOW": Color.YELLOW}
    # Default sounds (None = 0)
    # Default sound names
    soundLabel = ["Attention",
                  "Script start/stop",
                  "Derailed train",
                  "Stalled train",
                  "Lost cars",
                  "Wrong route"]
    # Detection action
    DETECTION_DISABLED = 0
    DETECTION_WARNING = 1
    DETECTION_PAUSE = 2

    # Stop Mode
    PROGRESSIVE_STOP = 0
    IMMEDIATE_STOP = 1

    # STATIC VARIABLE - Current settings              

    # Default directions
    directionNames =("CCW", "CW")
    ccwStart = ""
    ccwEnd = ""
    ccw = 0
    # Measurement units 1.0=mm. 10.0=cm. 25.4=inches
    if Locale.getDefault().getCountry() == "US" :
        units = 25.4
    else :
        units = 1.0
    useLength = False
    max_trains = 0
    allocationAhead = 1
    blockTracking = False
    verbose = False
    ringBell = True
    pauseMode = STOP_TRAINS
    derailDetection = DETECTION_PAUSE
    wrongRouteDetection = DETECTION_PAUSE
    # Maximum idle time between speed commands
    maxIdle = 60000
    useCustomColors = True
    # Default section colors (as strings)
    colorTable = ["BLACK", "BLUE", "RED", "YELLOW", "ORANGE",
      "MAGENTA", "CYAN"]
    sectionColor = None
    useCustomWidth = True
    trustTurnouts = True
    # Delay between turnouts operations
    turnoutDelay = 1000
    trustSignals = True
    # Delay between signals operations
    signalDelay = 0
    # Delay before clearing signals
    clearDelay = 0
    # table of sections' settings in human readable format
    sections = []
    # table of blocks' settings in human readable format
    blocks = []
    # Speeds
    speedsList = ["Min.", "Low", "Med.", "High", "Max."]
    dccDelay = 10
    startDelayMin = 0
    # Speed change frequency (in 1/10h of second)
    speedRamp = 2
    # Ligths Mode: 
    #   0 = No lights;
    #   1 = ON/OFF when train starts/stops;
    #   2 = ON/OFF when schedule starts/ends
    lightMode = 1
    indicationsList = []
    signalTypes = []
    startDelayMax = 0
    separateTurnouts = False
    separateSignals = False
    stalledDetection = DETECTION_WARNING
    stalledTime = 60000.
    selfLearning = False
    stopMode = IMMEDIATE_STOP
    lostCarsDetection = DETECTION_WARNING
    if units == 25.4 :
        lostCarsTollerance = 2032.0
    else :
        lostCarsTollerance = 2000.0
    lostCarsSections = 3
    sectionTracking = False
    soundList = []
    defaultSounds = [1] * len(soundLabel)
    try :
        soundRoot = jmri.util.FileUtil.getUserFilesPath()
    except :
        try:
            soundRoot = XmlFile.userFileLocationDefault()
        except :
            AutoDispatcher.log("Unable to find user sound's directory")
        soundRoot = ""
    soundDic = {}
    maintenanceTime = 0.
    maintenanceMiles = 0.
    scale = 87
    flashingCycle = 1.0
    resistiveDefault = False
    defaultStartAction = ""
    autoRestart = False

    # STATIC METHODS
    
    def getSpeedName(speedLevel) :
        if speedLevel == 0 :
            return "Stop"
        if speedLevel > len(ADsettings.speedsList) :
            return "Unknown: " + str(speedLevel)
        return ADsettings.speedsList[speedLevel-1] 
    getSpeedName = ADstaticMethod(getSpeedName)

    def getScale() :
        return ADsettings.scale 
    getScale = ADstaticMethod(getScale)

    def getUnits() :
        return ADsettings.units 
    getUnits = ADstaticMethod(getUnits)

    def stringToColor(c) :
        # Convert a string into a color
        if c.startswith("R:") :
            # Custom RGB color
            r = int(c[2:5])
            g = int(c[7:10])
            b = int(c[12:])
            return Color(r, g, b) 
        # Standard Java color
        return ADsettings.colors[c]
    stringToColor = ADstaticMethod(stringToColor)

    def rgbToString(rgb) :
        # Build a color string "R:rrrG:gggB:bbb"
        lab = ["R:", "G:", "B:"]
        out = ""
        for j in range(3) :
            out += lab[j]
            c = rgb[j]
            if c < 100 :
                out += "0"
                if c < 10 :
                    out += "0"
            out += str(c)
        return out
    rgbToString = ADstaticMethod(rgbToString)
 
    def initColors() :
        # Convert colors from strings to JAVA constants
        ADsettings.sectionColor = []
        for c in ADsettings.colorTable :
            ADsettings.sectionColor.append(ADsettings.stringToColor(c))
    initColors = ADstaticMethod(initColors)

    def save(file, sections, blocks) :
        outIndications = []
        for indication in ADsettings.indicationsList :
            outIndications.append([indication.name, indication.nextIndication,
              indication.nextTurnout, indication.speed])
        outSignalTypes = []
        for signal in ADsettings.signalTypes :
            signalLine = [signal.name]
            indicationLines = []
            for i in range(len(ADsettings.indicationsList)) :
                indicationLine = []
                for aa in signal.aspects[i] :
                    indicationLine.append(AutoDispatcher.inverseAspects[aa])
                indicationLines.append(indicationLine)
            signalLine.append(indicationLines)
            signalLine.append(signal.speeds)
            outSignalTypes.append(signalLine)
        sounds = []
        for s in ADsettings.soundList :
            sounds.append([s.name, s.path])
        locations = []
        for l in ADlocation.getList() :
            locations.append([l.name, l.text])
        outData = (AutoDispatcher.version, ADsettings.directionNames,
         ADsettings.ccwStart, ADsettings.ccwEnd, ADsettings.turnoutDelay, 
         ADsettings.max_trains, ADsettings.allocationAhead, ADsettings.verbose, 
         ADsettings.pauseMode, ADsettings.derailDetection, 
         ADsettings.wrongRouteDetection, ADsettings.maxIdle, 
         ADsettings.useCustomColors, ADsettings.colorTable, ADsettings.useCustomWidth,
         sections, blocks, ADsettings.trustTurnouts,
         ADsettings.trustSignals, ADsettings.signalDelay, ADsettings.units,
         ADsettings.clearDelay, ADsettings.useLength, ADsettings.blockTracking,
         ADsettings.speedsList, ADsettings.resistiveDefault, ADsettings.dccDelay,
         ADsettings.startDelayMin, ADsettings.speedRamp, ADsettings.lightMode,
         outIndications, ADsettings.ringBell, outSignalTypes, ADsignalMast.getTable(),
         ADsettings.startDelayMax, ADsettings.separateTurnouts,
         ADsettings.separateSignals, ADsettings.stalledDetection, 
         ADsettings.stalledTime, ADsettings.selfLearning, ADsettings.stopMode, 
         ADsettings.lostCarsDetection, ADsettings.lostCarsTollerance,
         ADsettings.lostCarsSections, ADsettings.sectionTracking, sounds,
         ADsettings.defaultSounds, ADsettings.soundRoot, ADsettings.maintenanceTime,
         ADsettings.maintenanceMiles, ADsettings.scale, locations,
         ADsettings.flashingCycle, ADsettings.defaultStartAction,
         ADsettings.autoRestart)

        file.writeObject(outData)
    save = ADstaticMethod(save)

    def load(inData) :
        creationVersion = inData[0]
        ADsettings.directionNames = inData[1]
        ADsettings.ccwStart = inData[2]
        ADsettings.ccwEnd = inData[3]
        ADsettings.turnoutDelay = inData[4]
        ADsettings.max_trains = inData[5]
        ADsettings.allocationAhead = inData[6]
        ADsettings.verbose = inData[7]
        ADsettings.pauseMode = inData[8]
        ADsettings.derailDetection = inData[9]
        ADsettings.wrongRouteDetection = inData[10]
        ADsettings.maxIdle = inData[11]
        ADsettings.useCustomColors = inData[12]
        ADsettings.colorTable = inData[13]
        ADsettings.useCustomWidth = inData[14]
        ADsettings.sections = inData[15]
        ADsettings.blocks = inData[16]
        ADsettings.trustTurnouts = inData[17]
        ADsettings.trustSignals = inData[18]
        ADsettings.signalDelay = inData[19]
        ADsettings.units = inData[20]
        ADsettings.clearDelay = inData[21]
        if ADblock.blocksWithLength > 0 :
            ADsettings.useLength = inData[22]
        ADsettings.blockTracking = inData[23]
        ADsettings.speedsList = inData[24]
        ADsettings.resistiveDefault = inData[25]
        ADsettings.dccDelay = inData[26]
        ADsettings.startDelayMin = inData[27]
        ADsettings.speedRamp = inData[28]
        ADsettings.lightMode = inData[29]
        ADsettings.indicationsList = []
        for a in inData[30] :
            ADsettings.indicationsList.append(ADindication(a[0], a[1], a[2], a[3]))
        ADsignalType.adjust()
        ADsettings.ringBell = inData[31]
        ADsettings.signalTypes = []
        for s in inData[32] :
            indicationLines = []
            for a in s[1] :
                indicationLine = []
                for aa in a :
                    indicationLine.append(AutoDispatcher.headsAspects[aa])
                indicationLines.append(indicationLine)
            ADsettings.signalTypes.append(ADsignalType(s[0], indicationLines, s[2]))
        ADsignalMast.putTable(inData[33])
        ADsettings.startDelayMax = inData[34]
        ADsettings.separateTurnouts = inData[35]
        ADsettings.separateSignals = inData[36]
        ADsettings.stalledDetection = inData[37]
        ADsettings.stalledTime = inData[38]
        ADsettings.selfLearning = inData[39]
        ADsettings.stopMode = inData[40]
        if ADsettings.stopMode > 1 :
            ADsettings.stopMode = 1
        ADsettings.lostCarsDetection = inData[41]
        ADsettings.lostCarsTollerance = inData[42]
        ADsettings.lostCarsSections = inData[43]
        ADsettings.sectionTracking = inData[44]
        ADsettings.soundList = []
        for i in inData[45] :
            s = ADsound(i[0])
            s.setPath(i[1])
            ADsettings.soundList.append(s)
        ADsettings.newSoundDic()
        ADsettings.defaultSounds = inData[46]
        while len(ADsettings.defaultSounds) < len(ADsettings.soundLabel) :
            ADsettings.defaultSounds.append(1)
        if inData[47] != "" :
            ADsettings.soundRoot = inData[47]
        ADsettings.maintenanceTime = inData[48]
        ADsettings.maintenanceMiles = inData[49]
        ADsettings.scale = inData[50]
        for l in inData[51] :
            location = ADlocation(l[0])
            location.setSections(l[1])
        ADsettings.flashingCycle = inData[52]
        if len(inData) > 53 :
            ADsettings.defaultStartAction = inData[53]
            if len(inData) > 54 :
                ADsettings.autoRestart = inData[54]
        ADsettings.initColors()
        
    load = ADstaticMethod(load)
      
    def newSoundDic() :
        newDic = {}
        for s in ADsettings.soundList :
            newDic[s.name] = s
        ADsettings.soundDic = newDic
    newSoundDic = ADstaticMethod(newSoundDic)

    # INSTANCE METHODS!

    def __init__(self) :
        ADsettings.initColors()
        ADsettings.indicationsList = [ADindication("Stop", -1, -1, 0),
          ADindication("Clear", -1, -1, len(ADsettings.speedsList))]        
        ADsettings.signalTypes = [ADsignalType("Single Head", [[SignalHead.RED],
          [SignalHead.GREEN]], [])]
        alarmSound = ADsound("Bell")
        alarmSound.setPath("resources/sounds/bell.wav")
        ADsettings.soundList = [alarmSound]
        ADsettings.newSoundDic()
        


# SCHEDULE ==============

class ADschedule :
    # Encapsulates all info relevant to a schedule

    # CONSTANTS
    
    # Action types
    # Actions that require train stopping
    ERROR = -2
    END_ALTERNATIVE = -1
    STOP = 0
    PAUSE = 1
    START_AT = 2
    CCW = 3
    CW = 4
    WAIT_FOR = 5
    IFE = 6
    IFAT = 7
    IFH = 8
    MANUAL_PRESENT = 9
    # Actions that can be executed while train is running
    GOTO = 10
    SWON = 11
    SWOFF = 12
    HELD = 13
    RELEASE = 14
    SET_F_ON = 15
    SET_F_OFF = 16
    DELAY = 17
    MANUAL_OTHER = 18
    SOUND = 19
    TC = 20
    TT = 21
    
    def __init__(self, text) :
        # Save original text
        self.text = text
        self.source = []
        textSpace  = text.replace(",", " ")
        # Break down input text into tokens
        splitted = textSpace.split()
        # Break down tokens containing open brackets "("
        charList = ")[]"
        for s in splitted :
            i=s.find("(")
            while i >= 0 and i < len(s)-1 :
                self.__split(s[:i+1], charList)
                s = s[i+1:]
                i=s.find("(")
            self.__split(s, charList)
        self.__clearFields()

    def __split(self, s, charList) :
        # Internal method
        # Recursively breaks down tokens containing special characters 
        # (open/closed brackets)
        if charList == "" :
            self.source.append(s)
        else :
            term = charList[0]
            if len(charList) > 1 :
                charList = charList[1:]
            else :
                charList = ""
            i=s.find(term)
            while i >= 0 and len(s) > 1 :
                if i > 0 :
                    self.__split(s[:i], charList)
                    s = s[i:]
                if len(s) > 1 :
                    self.source.append(term)
                    s = s[1:]
                i=s.find(term)
            self.__split(s, charList)

    def __clearFields(self) :
        # Set initial status of fields, in order to start schedule scanning
        self.pointer = 0
        self.iteration = 0
        self.iterations = 0
        self.stack = []
        self.error = False
        self.alternative = False
        self.repeating = False
        self.test = False
        self.condition = True
        self.ifStart = False
        self.endAlternative = 0
        self.currentItem = self.__getNextItem()
        self.looping = False

    def __getNextItem(self) :
        # Get next item in the schedule (internal method)
        self.firstCall = True
        newItem = ADscheduleItem()
        # Are we at the beginning of a $IF?
        if self.ifStart :
            # Skip commands until condition becomes True
            count = 0
            while not self.condition :
                if self.pointer >= len(self.source) :
                    self.error = True
                    newItem.action = ADschedule.ERROR
                    newItem.message = "$IF not closed by $END"
                    return newItem
                sl = self.source[self.pointer]
                s = sl.upper()
                self.pointer += 1
                if s.startswith("$IF") :
                    # Nested IF
                    count += 1
                elif s == "$END" :
                    # Decrease number of nested IFs
                    count -= 1
                    if count < 0 :
                        # End of main IF reached
                        self.pop()
                        self.condition = True
                elif s == "$ELSE" :
                    if count == 0 :
                        # ELSE of main IF reached
                        self.condition = True
            self.ifStart = False
        # End of schedule?
        if self.pointer >= len(self.source) :
            return newItem
        # No, get next token?
        sl = self.source[self.pointer]
        s = sl.upper()
        self.pointer += 1
        if s.endswith("(") :
            # Start of repetition
            self.push()
            self.test = False
            self.alternative = False
            self.repeating = True
            self.iteration = 0
            if len(s) > 1 :
                s = s[:len(s)-1]
                try :
                    self.iterations = int(s)
                except :
                    self.error = True
                    newItem.action = ADschedule.ERROR
                    newItem.message = "Wrong value \"" + sl +"\""
                    return newItem
            return self.__getNextItem()
        if s == ")" :
            # End of repetition
            if not self.repeating :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Unbalanced close bracket \")\""
                self.looping = True
                return newItem
            # Should we iterate?
            self.iteration += 1
            # Is this an endless loop?
            self.looping = self.iterations == 0
            # Should we repeat?
            if self.looping or self.iteration < self.iterations :
                # Repeat again
                self.pointer = self.stack[len(self.stack)-1]
                if self.looping :
                    self.iteration = 0
                return self.__getNextItem()
            # Repetition completed
            self.pop()
            return self.__getNextItem()
        if s == "[" :
            # Start of alternative
            if self.alternative :
                newItem.action = ADschedule.ERROR
                newItem.message = "Nested square brackets \"[\" are not supported!"
                self.looping = True
                return newItem
            self.push()
            self.alternative = True
            self.test = False
            self.repeating = False
            return self.__getNextItem()
        if s == "]" :
            # End of alternative
            if not self.alternative :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Unbalanced close bracket \"]\""
                return newItem
            self.endAlternative = self.pointer
            self.pointer = self.stack[len(self.stack)-1]
            newItem.action = ADschedule.END_ALTERNATIVE
            return newItem
        # Test for $ prefixed commands
        if s.startswith("$IF") :
           # Start of test
            self.push()
            self.test = True
            self.alternative = False
            self.repeating = False
            i=s.find(":")
            if s.startswith("$IFH") :
                newItem.action = ADschedule.IFH
                if s == "$IFH" :
                    newItem.value = None
                    self.condition = True
                    self.ifStart = True
                    return newItem
                if i < 0 :
                    self.error = True
                    newItem.action = ADschedule.ERROR
                    newItem.message = "Wrong format \"" + sl +"\""
                    return newItem
                self.__getSignal(sl, newItem)
                if newItem.action == ADschedule.ERROR :
                    self.error = True
                else :
                    self.condition = True
                    self.ifStart = True
                return newItem
            if i < 0 :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Wrong format \"" + sl +"\""
                return newItem
            newItem.value = self.__getArgs(sl[i+1:])
            if len(newItem.value) == 0 :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Wrong/missing section name \"" + sl +"\""
                return newItem
            if s.startswith("$IFAT:") :
                newItem.action = ADschedule.IFAT
                self.condition = True
                self.ifStart = True
                return newItem
            elif s.startswith("$IFE:") :
                newItem.action = ADschedule.IFE
                self.condition = True
                self.ifStart = True
                return newItem
            else :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Unknown command \"" + sl +"\""
                return newItem
        if s == "$ELSE" :
            # Third term of test
            if not self.test :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "$ELSE not preceded by $IF"
                return newItem
            self.condition = False
            # Skip tokens untile $END is found
            count = 0
            while not self.condition :
                if self.pointer >= len(self.source) :
                    self.error = True
                    newItem.action = ADschedule.ERROR
                    newItem.message = "$IF not closed by $END"
                    return newItem
                sl = self.source[self.pointer]
                s = sl.upper()
                self.pointer += 1
                if s.startswith("$IF") :
                    # Nested IF
                    count += 1
                elif s == "$END" :
                    # Decrease number of nested IFs
                    count -= 1
                    if count < 0 :
                        # End of main IF reached
                        self.pop()
                        self.condition = True
                elif s == "$ELSE" :
                    if count == 0 :
                        # Too many ELSE
                        self.error = True
                        newItem.action = ADschedule.ERROR
                        newItem.message = "$ELSE not preceded by $IF"
                        return newItem
            return self.__getNextItem()
        if s == "$END" :
            # End of test
            if not self.test :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "$END not preceded by $IF"
                return newItem
            self.pop()
            return self.__getNextItem()
        # $Pn - pause n seconds.  $Dn delay n seconds
        if s.startswith("$P") or s.startswith("$D") :
            st = s
            try :
                st = s[2:]
            except :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Missing value \"" + sl + "\""
                return newItem
            if st.startswith("M") :
                try :
                    st = st[1:]
                except :
                    self.error = True
                    newItem.action = ADschedule.ERROR
                    newItem.message = "Missing value \"" + sl + "\""
                    return newItem
                useFastClock = True        
            else :
                useFastClock = False        
            try :
                newItem.value = float(st)
            except :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Wrong value \"" + sl + "\""
                return newItem
            if useFastClock :
                newItem.value = (newItem.value * 60.
                  / AutoDispatcher.fastBase.getRate())
            if s.startswith("$P") :         
                newItem.action = ADschedule.PAUSE
            else :
                newItem.action = ADschedule.DELAY
            return newItem
        # Direction change
        if (s == "$CCW" or s == "$EAST" or s == "$NORTH" or s == "$LEFT"
           or s == "$UP") :
            newItem.action = ADschedule.CCW
            return newItem
        if (s == "$CW" or s == "$WEST" or s == "$SOUTH" or s == "RIGHT"
           or s == "DOWN") :
            newItem.action = ADschedule.CW
            return newItem
        # Switching mode
        # Allows train to enter restricted tracks
        # i.e. ONE-WAY and TRANSIT-ONLY
        if s == "$SWON" :
            newItem.action = ADschedule.SWON
            return newItem
        if s == "$SWOFF" :
            newItem.action = ADschedule.SWOFF
            return newItem
        # Signals control
        newItem.action = ADschedule.ERROR
        if s.startswith("$H:") :
            # $H:signalName sets a signal to "Held" state
            newItem.action = ADschedule.HELD
            self.__getSignal(sl, newItem)
            self.error = newItem.action == ADschedule.ERROR
            return newItem
        elif s.startswith("$R:") :
            # $R:signalName removes the "Held" state
            newItem.action = ADschedule.RELEASE
            self.__getSignal(sl, newItem)
            self.error = newItem.action == ADschedule.ERROR
            return newItem
        # Decoder functions (F0-F28)
        if s.startswith("$ON:F") :
            newItem.action = ADschedule.SET_F_ON
            newItem.value = s[5:]
        elif s.startswith("$OFF:F") :
            newItem.action = ADschedule.SET_F_OFF
            newItem.value = s[6:]
        if newItem.action != ADschedule.ERROR :
            # Retrieve $ON $OFF argument (function number)
            try :
                newItem.value = int(newItem.value)
                if newItem.value < 0 or newItem.value > 28 :
                    self.error = True
                    newItem.action = ADschedule.ERROR
                    newItem.message = "Function number out of range \"" + sl + "\""
            except :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Wrong/missing function number \"" + sl + "\""
            return newItem
        # Wait for empty section
        if s.startswith("$WF:") :
            st = sl
            try :
                st = sl[4:]
            except :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Missing section name \"" + sl + "\""
                return newItem
            newItem.value = ADsection.getByName(st)
            if newItem.value == None :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Unknown section \"" + sl + "\""
                return newItem
            newItem.action = ADschedule.WAIT_FOR
            return newItem
        # Set present section to manual mode
        if s == "$M" :
            newItem.action = ADschedule.MANUAL_PRESENT
            return newItem
        # Set another section to manual mode
        if s.startswith("$M:") :
            try :
                newItem.value = ADsection.getByName(sl[3:])
            except :
                newItem.value = None
            if newItem.value == None :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Unknown/missing section \"" + sl + "\""
                self.looping = True
                return newItem
            newItem.action = ADschedule.MANUAL_OTHER
            return newItem
        if s.startswith("$S:") :
        # Play sound
            try :
                newItem.value = ADsettings.soundDic.get(sl[3:], None)
            except :
                newItem.value = None
            if newItem.value == None :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Unknown/missing sound \"" + sl + "\""
                return newItem
            newItem.action = ADschedule.SOUND
            return newItem
        # Set turnout
        if s.startswith("$TC:") or s.startswith("$TT:") :
            try :
                newItem.value = sl[4:]
            except :
                newItem.value = None
            if newItem.value == None or newItem.value == "" :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Missing turnout name \"" + sl + "\""
                return newItem
            if s.startswith("$TC:") :
                newItem.action = ADschedule.TC
            else :
                newItem.action = ADschedule.TT
            return newItem
        # Start time (using Fast Clock)
        if s.startswith("$ST:") :
            try:
                minutes = hours = 0
                time = sl[4:]
                i=time.find(":")
                if i < 0 :
                    hours = int(time)
                else :
                    hours = time[0:i]
                    hours = int (hours)
                    minutes = time[i+1:]
                    minutes = int (minutes)
                newItem.value = hours * 60 + minutes
            except :
                self.error = True
                newItem.action = ADschedule.ERROR
                newItem.message = "Wrong time value \"" + sl + "\""
                return newItem
            newItem.action = ADschedule.START_AT
            return newItem
        #If no command prefixed by $ was found
        # argument should be a section name
        newItem.value = ADsection.getByName(sl)
        if newItem.value == None :
            self.error = True
            newItem.action = ADschedule.ERROR
            if sl.startswith("$") :
                newItem.message = "Unknown command \"" + sl + "\""
            else :
                newItem.message = "Unknown section \"" + sl + "\""
            return newItem
        newItem.action = ADschedule.GOTO
        return newItem

    def __getArgs(self, arg) :
        # Get arguments for commands expecting a section name or
        # a list of section names
        argList = []
        if arg != "" :
            # Argument is a single section name
            arg = ADsection.getByName(arg)
            if arg != None :
                argList.append(arg)
        else :
            # Argument is a list of section names
            newItem = self.__getNextItem()
            while newItem.action == ADschedule.GOTO :
                argList.append(newItem.value)
                newItem = self.__getNextItem()
            self.next()
            self.pointer -=1
            self.alternative = False
        return argList

    def __getSignal(self, arg, newItem) :
        # Get argument for commands expecting a signal name
            i=arg.find(":")
            try :
                signalName = arg[i+1:]
            except :
                newItem.action = ADschedule.ERROR
                newItem.message = "Missing signal name \"" + arg + "\""
                return
            # retrieve signal
            newItem.value = ADsignalMast.getByName(signalName)
            if newItem.value == None :
                newItem.action = ADschedule.ERROR
                newItem.message = "Unknown signal \"" + arg + "\""
            return

    def getNextAlternative(self) :
        # Loop among alternative destinations
        # i.e. list of destinations enclosed in square brackets "[...]"
        outItem = self.currentItem
        if self.error :
            return outItem
        if self.alternative :
            self.currentItem = self.__getNextItem()
            if self.error :
                return self.currentItem
        elif (not self.firstCall) and self.currentItem.action == ADschedule.GOTO :
            outItem = ADscheduleItem()
            outItem.action = ADschedule.END_ALTERNATIVE
            outItem.value = 0
        self.firstCall = not self.firstCall
        return outItem
    
    def next(self) :
        # step to next item (or next list of alternatives)
        if self.error :
            self.currentItem.action = ADschedule.ERROR
            return      
        if self.alternative :
            self.pointer = self.endAlternative
            self.pop()
        self.currentItem = self.__getNextItem()
        
    def getFirstAlternative(self) :
        # Get the first alternative destination
        # i.e. first destination enclosed in square brackets "[x...]"
        if self.error:
            return self.currentItem
        if self.alternative :
            while self.currentItem.action == ADschedule.GOTO :
                self.currentItem = self.__getNextItem()
                if self.error:
                    return self.currentItem
            self.currentItem = self.__getNextItem()
        else :
            self.firstCall = True
        return self.getNextAlternative()
        
    def testCondition(self, item, section, direction) :
        # Set test results for $IFAT and $IFE
        condition = False
        if item.action == ADschedule.IFAT :
            # Test for current section
            for arg in item.value :
                if arg == section :
                    condition = True
                    break
        elif item.action == ADschedule.IFE :
            # Test for empty section
            for arg in item.value :
                if arg.isAvailable() :
                    condition = True
                    break
        # Set test results for $IFH
        elif item.action == ADschedule.IFH :
            signal = item.value
            if signal == None :
                signal = section.getSignal(direction)
            condition = signal.isHeld()
        else :
            # No $IF command - return present item
            return item
        # Make sure that we are at the beginning of a test
        if self.ifStart :
            # Apply test results
            self.condition = condition

        # Return next schedule item
        item.action = ADschedule.END_ALTERNATIVE
#        return self.getFirstAlternative()
        return item

    def push(self) :
        # Internal method - pushes status into the internal stack
        self.stack.append(self.ifStart)
        self.stack.append(self.condition)
        self.stack.append(self.test)
        self.stack.append(self.endAlternative)
        self.stack.append(self.alternative)
        self.stack.append(self.repeating)
        self.stack.append(self.iterations)
        self.stack.append(self.iteration)
        self.stack.append(self.pointer)

    def pop(self) :
        # Internal method - pops status from the internal stack
        # skip pointer (must be restored manually, if needed)
        self.stack.pop()    
        self.iteration = self.stack.pop()
        self.iterations = self.stack.pop()
        self.repeating = self.stack.pop()
        self.alternative = self.stack.pop()
        self.endAlternative = self.stack.pop()
        self.test = self.stack.pop()
        self.condition = self.stack.pop()
        self.ifStart = self.stack.pop()
        
    def match(self, section, direction) :
        # Try to find the first occurence of a section in the schedule
        self.__clearFields()
        minDistance = 0
        while True :
            if self.looping :
                break
            item = self.getFirstAlternative()
            item = self.testCondition(item, section, direction)
            while item.action == ADschedule.GOTO :
                if item.value == section :
                    # Move to next destination
                    self.next()
                    return
                # Try and find a route to present destination
                route = ADautoRoute(section, item.value, direction, False)
                # Take note of route length
                routeLength = len(route.step)
                if (routeLength > 0 and (minDistance == 0 or 
                   routeLength < minDistance)) :
                    minDistance = routeLength
                item = self.getNextAlternative()
            if item.action == ADschedule.ERROR :
                self.__clearFields()
                self.currentItem.action = ADschedule.ERROR
                self.currentItem.message = item.message
                self.error = True
                return
            if item.action == ADschedule.STOP :
                break
            self.next()
        # No explicit reference to section found
        self.__clearFields()
        # Did we find at least one route?
        if minDistance == 0 :
            return
        # At least one route found - synchronize with it
        while True :
            if self.looping :
                # Should not occur
                break
            item = self.getFirstAlternative()
            while item.action  == ADschedule.GOTO :
                # Get the route to present destination
                route = ADautoRoute(section, item.value, direction, False)
                # Has it the required length?
                if len(route.step) == minDistance :
                    return
                item = self.getNextAlternative()
            if item.action == ADschedule.ERROR :
                # Should not occur
                self.__clearFields()
                self.currentItem.action = ADschedule.ERROR
                self.error = True
                return
            if item.action == ADschedule.STOP :
                # Should not occur
                break
            self.next()
        # Just in case (we should never get here)
        self.__clearFields()
        

class ADscheduleItem :
    # Encapsulates info relevant to a schedule item
    def __init__(self) :
        self.action = ADschedule.STOP
        self.value = 0
        self.message = ""
        
class ADsound :
    # Encapsulates info relevant to a JMRI Sound
    def __init__(self, name) :
        self.name = name
        self.path = ""
        self.sound = None
        
    def setPath(self, path) :
        self.path = path
        if self.path.strip() != "" :
            try :
                self.sound = Sound(path)
            except :
                self.sound = None
        
    def play(self) :
        if self.sound != None :
            self.sound.play()
    

class ADpowerMonitor (PropertyChangeListener) :
    # Monitors power on layout
    # Or simulates it, if the simulator used (e.g. XpressNet) is not 
    # supporting powerManager
    
    # STATIC VARIABLES

    powerOn = True
    savePause = False
    powerOffTime = -1L
    
    def __init__(self):
        self.powerManager = InstanceManager.getDefault(jmri.PowerManager)
        if self.powerManager != None and not AutoDispatcher.lenzSimulation :
            ADpowerMonitor.powerOn = (self.powerManager.getPower()
              == PowerManager.ON)
            self.powerManager.addPropertyChangeListener(self)
    
    def propertyChange(self, ev) :
        value = self.powerManager.getPower()
        newStatus = ADpowerMonitor.powerOn
        if value == PowerManager.ON :
            newStatus = True
        elif value == PowerManager.OFF :
            newStatus = False
        if newStatus == ADpowerMonitor.powerOn :
            return
        ADpowerMonitor.powerOn = newStatus
        if ADpowerMonitor.powerOn :
            self.resetAccessories()
            if ADpowerMonitor.savePause and ADmainMenu.resumeButton.enabled :
                AutoDispatcher.instance.resume()
                AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Power On")
        else :
            ADpowerMonitor.powerOffTime = System.currentTimeMillis()
            ADpowerMonitor.savePause = ADmainMenu.pauseButton.enabled
            if ADpowerMonitor.savePause :
                AutoDispatcher.instance.stopAll()
                AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Power Off")

    def __del__(self) :
        self.dispose()

    def dispose(self) :
        if self.powerManager != None and not AutoDispatcher.lenzSimulation :
            self.powerManager.removePropertyChangeListener(self)
            
    def setPower(self, power) :
        ADpowerMonitor.powerOn = power == PowerManager.ON
        if self.powerManager != None :
            self.powerManager.setPower(power)
        if not ADsettings.separateTurnouts or not ADsettings.separateSignals :
            sleep(1.0)
            self.resetAccessories()

    def resetAccessories(self) :
        if ADpowerMonitor.powerOffTime == -1L :
            return
        if not ADsettings.separateTurnouts :
            checkTime = ADsettings.turnoutDelay * 2
            if checkTime < 3000 :
                checkTime = 3000
            checkTime =  ADpowerMonitor.powerOffTime - checkTime
            for turnout in AutoDispatcher.turnoutCommands.keys() :
                packet = AutoDispatcher.turnoutCommands[turnout]
                if packet[1] > checkTime :
                    AutoDispatcher.turnoutCommands[turnout] = [packet[0],
                      System.currentTimeMillis()]
                    turnout.setState(packet[0])
                    # Wait if user specified a delay between turnout operation
                    if ADsettings.turnoutDelay > 0 :
                        sleep(float(ADsettings.turnoutDelay)/1000.)
        if not ADsettings.separateSignals :
            checkTime = ADsettings.signalDelay * 2
            if checkTime < 3000 :
                checkTime = 3000
            checkTime =  ADpowerMonitor.powerOffTime - checkTime
            for signal in AutoDispatcher.signalCommands.keys() :
                packet = AutoDispatcher.signalCommands[signal]
                if packet[1] > checkTime :
                    AutoDispatcher.signalCommands[signal] = [packet[0],
                      System.currentTimeMillis()]
                    signal.setAppearance(packet[0])
                    # Wait if user specified a delay between signal operation
                    if ADsettings.signalDelay > 0 :
                        sleep(float(ADsettings.signalDelay)/1000.)

# USER INTERFACE CLASSES ============== Long and boring :-)

    # Main window =================
    
class ADmainMenu (JmriJFrame) :

    # create frame borders
    blackline = BorderFactory.createLineBorder(Color.black)
    spacing = BorderFactory.createEmptyBorder(5, 5, 5, 5)
        
    saveSettingsButton = JButton("Save Settings")
    saveTrainsButton = JButton("Save Trains")
    startButton = JButton("Start")
    stopButton = JButton("Stop")
    pauseButton = JButton("Pause")
    resumeButton = JButton("Resume")
    autoButton = JCheckBox("Auto", True)

    def __init__(self) :
        # super.init
        JmriJFrame.__init__(self, "AutoDispatcher " + AutoDispatcher.version)
        self.addWindowListener(ADcloseWindow()) # Handle window closure
        # (see last class at the bottom of file)
        self.contentPane.setLayout(BoxLayout(self.contentPane,
          BoxLayout.Y_AXIS))

        # create frame borders
        self.contentPane.setBorder(ADmainMenu.spacing)
        
        # Set a warning at the top of page
        
        temppane = JPanel()
        temppane.setLayout(GridLayout(2, 1))
        l = AutoDispatcher.centerLabel(" WARNING: DO NOT SAVE YOUR PANEL ")
        l.setForeground(Color.red)
        temppane.add(l)
        l = AutoDispatcher.centerLabel(" AFTER RUNNING THIS SCRIPT!!! ")
        l.setForeground(Color.red)
        temppane.add(l)
        self.contentPane.add(temppane)

        # Settings panel
        temppane = JPanel()
        temppane.setBorder(BorderFactory.createTitledBorder(ADmainMenu.blackline,
          "Layout settings"))
        temppane.setLayout(GridLayout(3, 2))
        
        # create the Preferences button
        self.preferencesButton = JButton("Preferences")
        self.preferencesButton.enabled = False
        self.preferencesButton.actionPerformed = self.whenPreferencesClicked
        temppane.add(self.preferencesButton)
        
        # create the Panel button
        self.panelButton = JButton("Panel")
        self.panelButton.actionPerformed = self.whenPanelClicked
        self.panelButton.enabled = False
        temppane.add(self.panelButton)

        # create the Direction button
        self.directionButton = JButton("Direction")
        self.directionButton.actionPerformed = self.whenDirectionClicked
        self.directionButton.enabled = False
        temppane.add(self.directionButton)

        # create the Sections button
        self.sectionsButton = JButton("Sections")
        self.sectionsButton.actionPerformed = self.whenSectionsClicked
        self.sectionsButton.enabled = False
        temppane.add(self.sectionsButton)
        
        # create the Blocks button
        self.blocksButton = JButton("Blocks")
        self.blocksButton.actionPerformed = self.whenBlocksClicked
        self.blocksButton.enabled = False
        temppane.add(self.blocksButton)
        
        # create the Locations button
        self.locationsButton = JButton("Locations")
        self.locationsButton.enabled = False
        self.locationsButton.actionPerformed = self.whenLocationsClicked
        temppane.add(self.locationsButton)

        self.contentPane.add(temppane)

        temppane = JPanel()
        temppane.setBorder(BorderFactory.createTitledBorder(ADmainMenu.blackline,
          "Signals"))
        temppane.setLayout(GridLayout(2, 2))

        # create the Speeds button
        self.speedsButton = JButton("Speeds")
        self.speedsButton.actionPerformed = self.whenSpeedsClicked
        self.speedsButton.enabled = False
        temppane.add(self.speedsButton)

        # create the Signal Indications button
        self.indicationsButton = JButton("Indications")
        self.indicationsButton.actionPerformed = self.whenIndicationsClicked
        self.indicationsButton.enabled = False
        temppane.add(self.indicationsButton)

        # create the Signal Types button
        self.signalTypesButton = JButton("Signal Types")
        self.signalTypesButton.actionPerformed = self.whenSignalTypesClicked
        self.signalTypesButton.enabled = False
        temppane.add(self.signalTypesButton)

        # create the Signal Masts button
        self.signalMastsButton = JButton("Signal Masts")
        self.signalMastsButton.actionPerformed = self.whenSignalMastsClicked
        self.signalMastsButton.enabled = False
        temppane.add(self.signalMastsButton)

        self.contentPane.add(temppane)

        # Sounds panel
        temppane = JPanel()
        temppane.setBorder(BorderFactory.createTitledBorder(ADmainMenu.blackline,
          "Sounds"))
        temppane.setLayout(GridLayout(1, 2))

        # create the List button
        self.soundListButton = JButton("List")
        self.soundListButton.actionPerformed = self.whenSoundListClicked
        self.soundListButton.enabled = False
        temppane.add(self.soundListButton)

        # create the Default button
        self.soundDefaultButton = JButton("Default")
        self.soundDefaultButton.actionPerformed = (
          self.whenSoundDefaultClicked)
        self.soundDefaultButton.enabled = False
        temppane.add(self.soundDefaultButton)

        self.contentPane.add(temppane)
        temppane = JPanel()
        temppane.setBorder(ADmainMenu.spacing)
        temppane.setLayout(GridLayout(1, 1))

        # create the Save Settings button
        ADmainMenu.saveSettingsButton.enabled = False
        ADmainMenu.saveSettingsButton.actionPerformed = (
          self.whenSaveSettingsClicked)
        temppane.add(ADmainMenu.saveSettingsButton)

        self.contentPane.add(temppane)

#        temppane.add(JLabel(""))
        
        # Operations panel
        temppane = JPanel()
        temppane.setBorder(BorderFactory.createTitledBorder(ADmainMenu.blackline,
          "Operations"))
        temppane.setLayout(GridLayout(3, 2))

        # create the Start button
        ADmainMenu.startButton.actionPerformed = self.whenStartClicked
        ADmainMenu.startButton.enabled = False
        temppane.add(ADmainMenu.startButton)

        # create the Stop button
        ADmainMenu.stopButton.enabled = False
        ADmainMenu.stopButton.actionPerformed = self.whenStopClicked
        temppane.add(ADmainMenu.stopButton)

        # create the Locomotives button
        self.locoButton = JButton("Locomotives")
        self.locoButton.actionPerformed = self.whenLocosClicked
        self.locoButton.enabled = False
        temppane.add(self.locoButton)

        # create the Trains button
        self.trainsButton = JButton("Trains")
        self.trainsButton.actionPerformed = self.whenTrainsClicked
        self.trainsButton.enabled = False
        temppane.add(self.trainsButton)

        # create the Save Trains button
        ADmainMenu.saveTrainsButton.actionPerformed = self.whenSaveTrainsClicked
        ADmainMenu.saveTrainsButton.enabled = False
        temppane.add(ADmainMenu.saveTrainsButton)

        temppane.add(JLabel(""))

        self.contentPane.add(temppane)

        # Emergency panel
        temppane = JPanel()
        temppane.setBorder(BorderFactory.createTitledBorder(ADmainMenu.blackline,
          "Emergency"))
        temppane.setLayout(GridLayout(1, 2))

        # create the Pause button
        ADmainMenu.pauseButton.enabled = False
        ADmainMenu.pauseButton.actionPerformed = self.whenPauseClicked
        temppane.add(ADmainMenu.pauseButton)

        # create the Resume button
        ADmainMenu.resumeButton.enabled = False
        ADmainMenu.resumeButton.actionPerformed = self.whenResumeClicked
        temppane.add(ADmainMenu.resumeButton)

        self.contentPane.add(temppane)

        if AutoDispatcher.simulation :

            # Simulation panel
            temppane = JPanel()
            temppane.setBorder(BorderFactory.createTitledBorder(
              ADmainMenu.blackline, "Simulation"))
            temppane.setLayout(GridLayout(1, 2))
            
            # create the Step button
            self.stepButton = JButton("Step")
            self.stepButton.actionPerformed = self.whenStepClicked
            self.stepButton.enabled = False
            temppane.add(self.stepButton)
            
            # create the Auto checkbox
            ADmainMenu.autoButton.enabled = False
            temppane.add(ADmainMenu.autoButton)
            self.contentPane.add(temppane)

        # Status message
        temppane = JPanel()
        scrollPane = JScrollPane(AutoDispatcher.statusScroll)
        scrollPane.setCorner(JScrollPane.LOWER_RIGHT_CORNER, JPanel())
        temppane.add(scrollPane)
        self.contentPane.add(temppane)
        
        # Display frame
        self.setLocation(0, 0)
        self.pack()
        self.show()
        
    # Main window buttons =================

    # Settings buttons

    # define what Direction button does when clicked
    def whenDirectionClicked(self,event) :
        if AutoDispatcher.directionFrame == None :
            AutoDispatcher.directionFrame = ADdirectionFrame()
        else :
            AutoDispatcher.directionFrame.show()

    # define what Panel button does when clicked
    def whenPanelClicked(self,event) :
        if AutoDispatcher.panelFrame == None :
            AutoDispatcher.panelFrame = ADpanelFrame()
        else :
            AutoDispatcher.panelFrame.show()
        
    # define what Speeds button does when clicked
    def whenSpeedsClicked(self,event) :
        if AutoDispatcher.speedsFrame == None :
            AutoDispatcher.speedsFrame = ADspeedsFrame()
        else :
            AutoDispatcher.speedsFrame.show()
        
    # define what Indications button does when clicked
    def whenIndicationsClicked(self,event) :
        if AutoDispatcher.indicationsFrame == None :
            AutoDispatcher.indicationsFrame = ADindicationsFrame()
        else :
            AutoDispatcher.indicationsFrame.show()
        
    # define what Signal Types button does when clicked
    def whenSignalTypesClicked(self,event) :
        if AutoDispatcher.signalTypesFrame == None :
            AutoDispatcher.signalTypesFrame = ADsignalTypesFrame()
        else :
            AutoDispatcher.signalTypesFrame.show()
        return

    # define what Signal Masts button does when clicked
    def whenSignalMastsClicked(self,event) :
        if AutoDispatcher.signalMastsFrame == None :
            AutoDispatcher.signalMastsFrame = ADsignalMastsFrame()
        else :
            AutoDispatcher.signalMastsFrame.show()
        return

    # define what Sections button does when clicked
    def whenSectionsClicked(self,event) :
        if AutoDispatcher.sectionsFrame == None :
            AutoDispatcher.sectionsFrame = ADsectionsFrame()
        else :
            AutoDispatcher.sectionsFrame.show()
 
    # define what Blocks button does when clicked
    def whenBlocksClicked(self,event) :
        if AutoDispatcher.blocksFrame == None :
            AutoDispatcher.blocksFrame = ADblocksFrame()
        else :
            AutoDispatcher.blocksFrame.show()

    # define what Locations button does when clicked
    def whenLocationsClicked(self,event) :
        if AutoDispatcher.locationsFrame == None :
            AutoDispatcher.locationsFrame = ADlocationsFrame()
        else :
            AutoDispatcher.locationsFrame.show()

    # define what Preferences button does when clicked
    def whenPreferencesClicked(self,event) :
        if AutoDispatcher.preferencesFrame == None :
            AutoDispatcher.preferencesFrame = ADpreferencesFrame()
        else :
            AutoDispatcher.preferencesFrame.show()

    # define what Save Settings button does when clicked
    def whenSaveSettingsClicked(self,event) :
        # Save to disk
        AutoDispatcher.instance.saveSettings()
        return

    # define what Sound List button does when clicked
    def whenSoundListClicked(self,event) :
        if AutoDispatcher.soundListFrame == None :
            AutoDispatcher.soundListFrame = ADsoundListFrame()

    # define what Sound Default button does when clicked
    def whenSoundDefaultClicked(self,event) :
        if AutoDispatcher.soundDefaultFrame == None :
            AutoDispatcher.soundDefaultFrame = ADsoundDefaultFrame()

    # Operations buttons

    # define what Start button does when clicked
    def whenStartClicked(self,event) :
        # leave the button off
        ADmainMenu.startButton.enabled = False
        AutoDispatcher.instance.start()

    # define what Stop button does when clicked
    def whenStopClicked(self,event) :
        AutoDispatcher.loop = False
        # leave the button off
        ADmainMenu.stopButton.enabled = False
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Stopping trains. Please wait!")

    # define what Locomotives button does when clicked
    def whenLocosClicked(self,event) :
        if AutoDispatcher.locosFrame == None :
            AutoDispatcher.locosFrame = ADlocosFrame()
        else :
            AutoDispatcher.locosFrame.show()

    # define what Trains button does when clicked
    def whenTrainsClicked(self,event) :
        if AutoDispatcher.trainsFrame == None :
            AutoDispatcher.trainsFrame = ADtrainsFrame()
        else :
            AutoDispatcher.trainsFrame.show()

    # define what Save Trains button does when clicked
    def whenSaveTrainsClicked(self,event) :
       AutoDispatcher.instance.saveTrains()
       return

    # Emergency buttons

    # define what Pause button does when clicked
    def whenPauseClicked(self,event) :
        if ADsettings.pauseMode != ADsettings.IGNORE :
            AutoDispatcher.instance.stopAll()
            AutoDispatcher.log("Script paused!")

    # define what Resume button does when clicked
    def whenResumeClicked(self,event) :
        if (ADsettings.pauseMode == ADsettings.IGNORE or
           not AutoDispatcher.paused) :
            return
        AutoDispatcher.instance.resume()
        AutoDispatcher.log("Script resumed!")

    # Simulation buttons

    # define what Step button does when clicked
    def whenStepClicked(self,event) :
        AutoDispatcher.instance.oneStep()

    def enableButtons(self, on) :
        if not AutoDispatcher.error :
            ADmainMenu.startButton.enabled = on
            ADmainMenu.stopButton.enabled = not on
            if ADsettings.pauseMode != ADsettings.IGNORE :
                ADmainMenu.pauseButton.enabled = not on
            # Enable/disable simulation buttons
            if AutoDispatcher.simulation :
                self.stepButton.enabled = not on
                ADmainMenu.autoButton.enabled = not on
            self.directionButton.enabled = True
            self.panelButton.enabled = True
            self.speedsButton.enabled = True
            self.indicationsButton.enabled = True
            self.signalTypesButton.enabled = True
            self.signalMastsButton.enabled = True
            self.sectionsButton.enabled = True
            self.blocksButton.enabled = True
            self.locationsButton.enabled = True
            self.preferencesButton.enabled = True
            self.soundListButton.enabled = True
            self.soundDefaultButton.enabled = True
            ADmainMenu.saveSettingsButton.enabled = ( 
              AutoDispatcher.preferencesDirty and on)
            ADmainMenu.saveTrainsButton.enabled = (AutoDispatcher.trainsDirty
              and on)
            self.locoButton.enabled = True
            self.trainsButton.enabled = True
            AdFrame.enableApply(on)

    # Main window closure handler =================

class ADcloseWindow(WindowAdapter) :

    # define what window closure does
    # (overrides empty method of WindowAdapter)
    def windowClosing(self,event) :
        # Close window
        event.getWindow().dispose()
        # Then stop everything,
        # otherwise the script will continue running
        # until JMRI exits and user will be unable to
        # stop it!
        # Close all other windows
        AdFrame.disposeAll()
        # Make sure ADpowerMonitor listener is removed
        if AutoDispatcher.powerMonitor != None :
            AutoDispatcher.powerMonitor.dispose()
        # Stop background handler (it will take care of stopping other
        # threads, etc.)
        if AutoDispatcher.loop :
            AutoDispatcher.exiting = True
            AutoDispatcher.loop = False
        else :
        # If background handler si not running, allow user to save data
            AutoDispatcher.instance.saveBeforeExit()


    # Our Abstract frame class =================

class AdFrame (JmriJFrame) :
    
    framesList = []
    applyEnabled = True

    def enableApply(on) :
        AdFrame.applyEnabled = on
        for f in AdFrame.framesList :
            f.applyButton.enabled = on
    enableApply = ADstaticMethod(enableApply)
    
    def disposeAll() :
        while len(AdFrame.framesList) > 0 :
            AdFrame.framesList[0].dispose()
    disposeAll = ADstaticMethod(disposeAll)
    
    def __init__(self, title) :
        # super.init
        JmriJFrame.__init__(self, title)
        self.setDefaultCloseOperation(JmriJFrame.HIDE_ON_CLOSE)
        self.contentPane.setLayout(BoxLayout(self.contentPane,
          BoxLayout.Y_AXIS))
        self.contentPane.setBorder(ADmainMenu.spacing)
        AdFrame.framesList.append(self)
        self.cancelButton = JButton("Cancel")
        self.applyButton = JButton("Apply")
        self.applyButton.enabled = AdFrame.applyEnabled
    
    def dispose(self) :
        JmriJFrame.dispose(self)
        AdFrame.framesList.remove(self)
        
    # Direction window =================
    
class ADdirectionFrame (AdFrame) :

    directionsSwing = JComboBox(["CCW-CW", "EAST-WEST", "NORTH-SOUTH",
      "LEFT-RIGHT", "UP-DOWN"])
    selectedDirectionNames = ""
    
    def __init__(self) :
        # Create and display Direction window
        # super.init
        AdFrame.__init__(self, "Direction")
        
        temppane = JPanel()
        temppane.setLayout(BoxLayout(temppane, BoxLayout.Y_AXIS))
        temppane1 = JPanel()
        temppane1.setLayout(GridLayout(5, 1))
        temppane1.add(AutoDispatcher.centerLabel(
          " The script runs trains in two directions. "))
        temppane1.add(AutoDispatcher.centerLabel(
          " Directions can be assigned a pair of names of your choice "))
        temppane1.add(AutoDispatcher.centerLabel(
          " i.e. CCW and CW, or EAST and WEST or NORTH and SOUTH, etc. "))
        temppane1.add(JLabel(""))
        temppane.add(temppane1)
        
        temppane1 = JPanel()
        temppane1.setLayout(GridLayout(1, 2))
        temppane1.add(JLabel(" Choose direction names:"))
        ADdirectionFrame.directionsSwing.setSelectedItem(
          ADsettings.directionNames[0] + "-"
          + ADsettings.directionNames[1])
        self.comboListener = ADcomboListener()
        ADdirectionFrame.directionsSwing.addActionListener(self.comboListener)
        temppane1.add(ADdirectionFrame.directionsSwing)
        temppane.add(temppane1)
        
        self.directionQuestion = AutoDispatcher.centerLabel("")
        self.displayDirectionQuestion()
        temppane1 = JPanel()
        temppane1.setLayout(GridLayout(4, 1))
        temppane1.add(AutoDispatcher.centerLabel(
          " In order to allow the correct identification of "))
        temppane1.add(AutoDispatcher.centerLabel(
          " directions, choose the start and end sections "))
        temppane1.add(self.directionQuestion)
        temppane1.add(JLabel(""))
        temppane.add(temppane1)
        
        temppane1 = JPanel()
        temppane1.setLayout(GridLayout(1, 2))
        temppane1.add(JLabel("  Start section:"))
        self.ccwStartSwing = JTextField(ADsettings.ccwStart, 6)
        temppane1.add(self.ccwStartSwing)
        temppane1.add(JLabel("  End section:"))
        self.ccwEndSwing = JTextField(ADsettings.ccwEnd, 6)
        temppane1.add(self.ccwEndSwing)
        temppane.add(temppane1)
        self.contentPane.add(temppane)
        
        # Buttons*
        temppane = JPanel()
        temppane.setLayout(BoxLayout(temppane, BoxLayout.X_AXIS))
        
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        temppane.add(self.cancelButton)

        # Apply button
        self.applyButton.actionPerformed = self.whenApplyClicked
        temppane.add(self.applyButton)
        self.contentPane.add(temppane)
        
        # Display frame
        self.pack()
        self.show()

    # routine to display direction question
    def displayDirectionQuestion(self) :
        ADdirectionFrame.selectedDirectionNames = ( 
          ADdirectionFrame.directionsSwing.getSelectedItem())
        selected = ADdirectionFrame.selectedDirectionNames[
          :ADdirectionFrame.selectedDirectionNames.find("-")]
        self.directionQuestion.setText(" of a short \"" + selected
          + "\" bound route:")

    # Buttons of Direction window =================
    
    # define what Cancel button in Direction Window does when clicked
    def whenCancelClicked(self,event) :
        AdFrame.dispose(self)
        AutoDispatcher.directionFrame = None

    # define what Apply button in Direction Window does when clicked
    def whenApplyClicked(self,event) :
        selected = ADdirectionFrame.selectedDirectionNames[
          :ADdirectionFrame.selectedDirectionNames.find("-")]
        if selected != ADsettings.directionNames[0] :
            ADsettings.directionNames = [selected,
             ADdirectionFrame.selectedDirectionNames[
             ADdirectionFrame.selectedDirectionNames.find("-")+1:]]
        ADsettings.ccwStart = self.ccwStartSwing.text
        ADsettings.ccwEnd = self.ccwEndSwing.text
        # Recompute direction along sections
        completion = AutoDispatcher.instance.setDirections()
        self.ccwStartSwing.text = ADsettings.ccwStart
        self.ccwEndSwing.text = ADsettings.ccwEnd
        # Update train directions
        for t in ADtrain.getList() :
            t.updateSwing()
        # Force redisplay of other windows with appropriate direction names
        if AutoDispatcher.panelFrame != None :
            AutoDispatcher.panelFrame.setColorLabels()
        if AutoDispatcher.sectionsFrame != None :
            AutoDispatcher.sectionsFrame.reDisplay()
        if AutoDispatcher.blocksFrame != None :
            AutoDispatcher.blocksFrame.reDisplay()
        if AutoDispatcher.trainsFrame != None :
            AutoDispatcher.trainsFrame.reDisplay()
        AutoDispatcher.setPreferencesDirty()
        if completion :
            AutoDispatcher.instance.setSignals()
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Direction changes applied")

    # Combo listener for the Direction frame =================
       
class ADcomboListener(ActionListener) :
    # Updates direction names in Directions frame
    def actionPerformed(self, event) :
        if (ADdirectionFrame.directionsSwing.getSelectedItem()
           == ADdirectionFrame.selectedDirectionNames) :
            return
        AutoDispatcher.directionFrame.displayDirectionQuestion()

    # Panel settings window =================
    
class ADpanelFrame (AdFrame) :
    def __init__(self) :
        # Create and display Panel window
        # super.init
        AdFrame.__init__(self, "Panel")

        temppane = JPanel()
        temppane.setLayout(BorderLayout())
        temppane.setBorder(ADmainMenu.spacing)
        
        temppane1 = JPanel()
        temppane1.setLayout(BoxLayout(temppane1, BoxLayout.Y_AXIS))
        self.colorGroup = ButtonGroup()
        self.standardColorButton = JRadioButton(
          "Keep block colors defined in Layout Editor")
        self.standardColorButton.selected = not ADsettings.useCustomColors
        self.standardColorButton.actionPerformed = (
          self.whenStandardColorsClicked)
        self.colorGroup.add(self.standardColorButton);
        temppane1.add(self.standardColorButton)
        self.customColorButton = JRadioButton(
          "Use colors to show sections status")
        self.customColorButton.selected = ADsettings.useCustomColors
        self.customColorButton.actionPerformed = self.whenCustomColorsClicked
        self.colorGroup.add(self.customColorButton);
        temppane1.add(self.customColorButton)
        temppane.add(temppane1, BorderLayout.NORTH);
        
        temppane1 = JPanel()
        temppane1.setBorder(ADmainMenu.spacing)

        temppane2 = JPanel()
        temppane2.setBorder(ADmainMenu.blackline)
        temppane2.setLayout(GridLayout(len(ADsettings.colorTable)+1, 3))
        temppane2.add(JLabel(" Section"))
        temppane2.add(AutoDispatcher.centerLabel("Color"))

        temppane3 = JPanel()
        temppane3.setLayout(GridLayout(1, 4))
        temppane3.add(AutoDispatcher.centerLabel("R"))
        temppane3.add(AutoDispatcher.centerLabel("G"))
        temppane3.add(AutoDispatcher.centerLabel("B"))
        temppane3.add(JLabel(""))
        temppane2.add(temppane3)

        self.colorLabels = []
        self.colorSwing = []
        self.colorPanes = []
        self.rgb = []
        colorList = ADsettings.colors.keys()
        colorList.append("CUSTOM")
 
        self.colorListener = ADcolorListener()

        for i in range(len(ADsettings.colorTable)) :
            self.colorLabels.append(JLabel(""))
            temppane2.add(self.colorLabels[i])
            self.colorSwing.append(JComboBox(colorList))
            c = ADsettings.colorTable[i]
            isCustom = c.startswith("R:")
            if isCustom :
                self.colorSwing[i].setSelectedItem("CUSTOM")
                rgbValues = [int(c[2:5]), int(c[7:10]), int(c[12:])]
            else :
                self.colorSwing[i].setSelectedItem(c)
                rgbValues = [0,0,0]
            self.colorSwing[i].setActionCommand(str(i))
            self.colorSwing[i].addActionListener(self.colorListener)
            self.colorSwing[i].enabled = ADsettings.useCustomColors
            temppane2.add(self.colorSwing[i])
            colorPane = JPanel()
            colorPane.setBorder(ADmainMenu.blackline)
            colorPane.setBackground(ADsettings.sectionColor[i])
            self.colorPanes.append(colorPane)
            rgbItem = []
            rgbPane = JPanel()
            rgbPane.setLayout(GridLayout(1, 4))
            rgbListener = ADrgbListener(i)
            for j in range(3) :
                rgbItem.append(JSpinner(SpinnerNumberModel(rgbValues[j],0,255,1)))
                tf = rgbItem[j].getEditor().getTextField()
                tf.setColumns(2)
                rgbItem[j].setEnabled(isCustom)
                rgbItem[j].addChangeListener(rgbListener)
                rgbPane.add(rgbItem[j])
            self.rgb.append(rgbItem)
            rgbPane.add(self.colorPanes[i])
            temppane2.add(rgbPane)        
        self.setColorLabels()
        temppane1.add(temppane2)
        temppane.add(temppane1, BorderLayout.CENTER);

        temppane1 = JPanel()
        temppane1.setLayout(BoxLayout(temppane1, BoxLayout.Y_AXIS))
        self.widthGroup = ButtonGroup()
        self.standardWidthButton = JRadioButton(
          "Keep track width defined in Layout Editor")
        self.standardWidthButton.selected = not ADsettings.useCustomWidth
        self.widthGroup.add(self.standardWidthButton);
        temppane1.add(self.standardWidthButton)
        self.customWidthButton = JRadioButton(
          "Use track width to show blocks occupancy")
        self.customWidthButton.selected = ADsettings.useCustomWidth
        self.widthGroup.add(self.customWidthButton);
        temppane1.add(self.customWidthButton)
        temppane.add(temppane1, BorderLayout.SOUTH);

        self.contentPane.add(temppane)
        
        # Buttons*
        temppane = JPanel()
        temppane.setLayout(BoxLayout(temppane, BoxLayout.X_AXIS))

        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        temppane.add(self.cancelButton)

        # Apply button
        self.applyButton.actionPerformed = self.whenApplyClicked
        temppane.add(self.applyButton)
        self.contentPane.add(temppane)
        
        # Display frame
        self.pack()
        self.show()

    # routine to display color labels
    def setColorLabels(self) :
        sectionType = ("  empty", 
                       "  in manual mode", 
                       "  occupied by rolling stock ", 
                       "  allocated to " 
                         + ADsettings.directionNames[0] + " train",
                       "  allocated to " 
                         + ADsettings.directionNames[1] + " train",
                       "  occupied by "
                         + ADsettings.directionNames[0] + " train",
                       "  occupied by "
                         + ADsettings.directionNames[1] + " train")
        for i in range(len(self.colorLabels)) :
            self.colorLabels[i].setText(sectionType[i])

    # Get input RGB values and convert them to string
    def getRGBinput(self,i) :
        c = []
        for j in range(3) :
            c.append(self.rgb[i][j].getValue())
        return ADsettings.rgbToString(c)
       
    
    # Buttons of Panel window =================

    # define what Standard Colors button in Panel Window does when clicked
    def whenStandardColorsClicked(self,event) :
        for i in range(len(self.colorLabels)) :
            self.colorSwing[i].enabled = False
        AutoDispatcher.setPreferencesDirty()

    # define what Custom Colors button in Panel Window does when clicked
    def whenCustomColorsClicked(self,event) :
        for i in range(len(self.colorLabels)) :
            self.colorSwing[i].enabled = True
        AutoDispatcher.setPreferencesDirty()
    
    # define what Cancel button in Direction Window does when clicked
    def whenCancelClicked(self,event) :
        AdFrame.dispose(self)
        AutoDispatcher.panelFrame = None

    # define what Apply button in Panel Window does when clicked
    def whenApplyClicked(self,event) :
        for i in range(len(self.colorLabels)) :
            c = self.colorSwing[i].getSelectedItem()
            if c == "CUSTOM" :
                c = AutoDispatcher.panelFrame.getRGBinput(i)
            ADsettings.colorTable[i] = c
        ADsettings.initColors()
        ADsettings.useCustomColors = self.customColorButton.selected
        ADsettings.useCustomWidth = self.customWidthButton.selected
        AutoDispatcher.setPreferencesDirty()
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Panel changes applied")

    # Listener for the color ComboBox =================       
class ADcolorListener(ActionListener) :
    def actionPerformed(self, event) :
        i = int(event.getActionCommand())
        c = AutoDispatcher.panelFrame.colorSwing[i].getSelectedItem()
        isCustom = c == "CUSTOM"
        for j in range(3) :
            AutoDispatcher.panelFrame.rgb[i][j].setEnabled(isCustom)
        if isCustom :
            c = AutoDispatcher.panelFrame.getRGBinput(i)
        AutoDispatcher.panelFrame.colorPanes[i].setBackground(ADsettings.stringToColor(c))

    # Listener for the rgb text fields =================       
class ADrgbListener(ChangeListener) :
    def __init__(self, ind) :
        self.i = ind
        
    def stateChanged(self, event) :
        c = AutoDispatcher.panelFrame.colorSwing[self.i].getSelectedItem()
        if c != "CUSTOM" :
            return
        c = AutoDispatcher.panelFrame.getRGBinput(self.i)
        p = AutoDispatcher.panelFrame.colorPanes[self.i]
        p.setBackground(ADsettings.stringToColor(c))

    # Our Abstract frame with a scroll pane =================

class AdScrollFrame (AdFrame) :
    # Subclasses must define methods:
    #   self.createHeader() (output returned in self.header JPanel)
    #   self.createDetail() (output returned in self.detail JPanel)
    #   self.createButtons()  (output returned in self.buttons JPanel)
    def __init__(self, title, firstLine) :
        # super.init
        AdFrame.__init__(self, title)
        
        # Create first Line
        if firstLine != None :
            temppane = JPanel()
            temppane.setLayout(BoxLayout(temppane, BoxLayout.X_AXIS))
            temppane.add(firstLine)
            self.contentPane.add(temppane)

        # Create scroll pane
        self.scrollPane = JScrollPane()
        self.firstTime = True
        self.createScroll()
        self.contentPane.add(self.scrollPane)
        
        # Create buttons at bottom
        self.buttons = JPanel()
        self.buttons.setLayout(BoxLayout(self.buttons, BoxLayout.X_AXIS))
        self.createButtons()
        self.contentPane.add(self.buttons)
        
        # Adjust size
        self.adjustSize()

        # Display frame
        self.show()
        
    def createScroll(self) :
        # Create header and scroll pane contents
        self.header = JPanel()
        self.createHeader()
        self.detail = JPanel()
        self.createDetail()

        self.scrollSize = self.detail.getPreferredSize()
        if self.header != None :
            #Get panel size
            headerSize = self.header.getPreferredSize()
            # Make sure header and scrollarea have the same width
            if headerSize.width < self.scrollSize.width :
                headerSize.width = self.scrollSize.width
                self.header.setPreferredSize(headerSize)
            elif self.scrollSize.width < headerSize.width :
                self.scrollSize.width = headerSize.width
                self.detail.setPreferredSize(self.scrollSize)
            self.scrollPane.setColumnHeaderView(self.header)
            self.scrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER, JPanel())

        self.scrollPane.setViewportView(self.detail)
        
        # Adjust size (when updating window - adding or removing speeds)
        frameSize = self.getPreferredSize()
        if self.header != None :
            frameSize.height = self.scrollSize.height + 110
        else :
            frameSize.height = self.scrollSize.height + 95
        self.setSize(frameSize)
        self.firstTime = False

    def reDisplay(self) :
        self.createScroll()
        self.adjustSize()

    def adjustSize(self) :
        self.pack()
        frameSize = self.getPreferredSize()
        frameSize.width = self.scrollSize.width + 30
        if frameSize.width > AutoDispatcher.screenSize.width :
            frameSize.width = AutoDispatcher.screenSize.width
        self.setSize(frameSize)

    # Speeds window =================
    
class ADspeedsFrame (AdScrollFrame) :
    def __init__(self) :
        # Create and display Speeds window
        # super.init
        AdScrollFrame.__init__(self, "Speed Levels",
          AutoDispatcher.centerLabel(
          "List of supported speed levels (minimum speed listed first)"))

    def createHeader(self):
        # Fill contents of Header
        self.header.setLayout(GridLayout(1, 2))
        self.header.add(AutoDispatcher.centerLabel("Name"))
        self.header.add(JLabel(""))

    def createDetail(self):
        # Fill contents of scroll area
        self.detail.setLayout(GridLayout(len(ADsettings.speedsList), 2))
        if self.firstTime :
            self.speedNamesSwing = []
            self.speedDefaultSwing = []
            self.speedGroup = ButtonGroup()
        ind = 0
        for s in ADsettings.speedsList :
            if self.firstTime :
                self.speedNamesSwing.append(JTextField(s, 20))
            self.detail.add(self.speedNamesSwing[ind])
            if ind == 0 :
                self.detail.add(JLabel(""))
            else :
                deleteButton = JButton("Delete")
                deleteButton.setActionCommand(str(ind))
                deleteButton.actionPerformed = self.whenDeleteClicked
                self.detail.add(deleteButton)
            ind += 1

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Add button
        self.addButton = JButton("Add")
        self.addButton.actionPerformed = self.whenAddClicked
        self.buttons.add(self.addButton)

        # Apply button
        self.applyButton.actionPerformed = self.whenApplyClicked
        self.buttons.add(self.applyButton)

    # Buttons of Speeds window =================

    # define what Cancel button in Speeds Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.speedsFrame = None

    # define what Add button in Speeds Window does when clicked
    def whenAddClicked(self,event) :
        ADsettings.speedsList.append("")
        self.speedNamesSwing.append(JTextField("", 20))
        radioButton = JRadioButton("Default speed")
        self.speedDefaultSwing.append(radioButton)
        self.speedGroup.add(radioButton)
        self.speedsChanged()

    # define what Delete button in Speeds Window does when clicked
    def whenDeleteClicked(self,event) :
        ind = int(event.getActionCommand())
        if (JOptionPane.showConfirmDialog(None, "Remove speed level \""
           + ADsettings.speedsList[ind] + "\"?", "Confirmation",
           JOptionPane.YES_NO_OPTION) == 1) :
            return
        ADsettings.speedsList.pop(ind)
        self.speedsChanged()

    # define what Apply button in Speeds Window does when clicked
    def whenApplyClicked(self,event) :
        ind = 0
        for s in ADsettings.speedsList :
            ADsettings.speedsList[ind] = self.speedNamesSwing[ind].text
            ind += 1 
        self.speedsChanged()
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, "Speed names changed")

    def speedsChanged(self) :
        if AutoDispatcher.blocksFrame != None :
            AutoDispatcher.blocksFrame.reDisplay()
        if AutoDispatcher.indicationsFrame != None :
            AutoDispatcher.indicationsFrame.reDisplay()
        if AutoDispatcher.signalEditFrame != None :
            AutoDispatcher.signalEditFrame.reDisplay()
        if AutoDispatcher.locosFrame != None :
            AutoDispatcher.locosFrame.reDisplay()
        if AutoDispatcher.trainDetailFrame != None :
            AutoDispatcher.trainDetailFrame.reDisplay()
        AutoDispatcher.setPreferencesDirty()
        self.reDisplay()
    
    # Indications window =================
    
class ADindicationsFrame (AdScrollFrame) :
    def __init__(self) :
        # Create and display Speeds window
        # super.init
        AdScrollFrame.__init__(self, "Signal Indications",
          AutoDispatcher.centerLabel("List of supported signal indications"))

    def createHeader(self):
        # Fill contents of Header
        self.header.setLayout(GridLayout(1, 5))
        header1 = JPanel()
        header1.setLayout(GridLayout(1, 2))
        self.header.add(AutoDispatcher.centerLabel("Indication"))
        header1.add(AutoDispatcher.centerLabel("Next section"))
        header1.add(AutoDispatcher.centerLabel("Turnouts ahead"))
        self.header.add(header1)
        self.header.add(AutoDispatcher.centerLabel("Next signal indication"))
        self.header.add(AutoDispatcher.centerLabel("Speed"))
        self.header.add(JLabel(""))

    def createDetail(self):
        # Fill contents of scroll area
        indicationNames = ["-"]
        for a in ADsettings.indicationsList :
            indicationNames.append(a.name)
        speedNames = []
        for s in ADsettings.speedsList :
            speedNames.append(s)
        maxSpeeds = len(speedNames)
        self.detail.setLayout(GridLayout(len(ADsettings.indicationsList), 5))
        ind = 0
        for a in ADsettings.indicationsList :
            self.detail.add(a.nameSwing)
            temppane1 = JPanel()
            temppane1.setLayout(GridLayout(1, 2))
            if ind == 0 :
                temppane1.add(AutoDispatcher.centerLabel("Occupied"))
                temppane1.add(AutoDispatcher.centerLabel("-"))
                self.detail.add(temppane1)
                self.detail.add(AutoDispatcher.centerLabel("-"))
                self.detail.add(AutoDispatcher.centerLabel("Stop"))
                self.detail.add(JLabel(""))
            elif ind == 1 :
                temppane1.add(AutoDispatcher.centerLabel("Available"))
                temppane1.add(AutoDispatcher.centerLabel("-"))
                self.detail.add(temppane1)
                self.detail.add(AutoDispatcher.centerLabel("-"))
                a.speedSwing = JComboBox(speedNames)
                if a.speed > maxSpeeds :
                    a.speedSwing.setSelectedIndex(maxSpeeds-1)
                else :
                    a.speedSwing.setSelectedIndex(a.speed-1)
                self.detail.add(a.speedSwing)
                self.detail.add(JLabel(""))
            else :
                temppane1.add(AutoDispatcher.centerLabel("Available"))
                temppane1.add(a.nextTurnoutSwing)
                self.detail.add(temppane1)
                a.nextIndicationSwing = JComboBox(indicationNames)
                a.nextIndicationSwing.setSelectedIndex(a.nextIndication+1)
                self.detail.add(a.nextIndicationSwing)
                a.speedSwing = JComboBox(speedNames)
                if a.speed > maxSpeeds :
                    a.speedSwing.setSelectedIndex(maxSpeeds-1)
                else :
                    a.speedSwing.setSelectedIndex(a.speed-1)
                self.detail.add(a.speedSwing)
                deleteButton = JButton("Delete")
                deleteButton.setActionCommand(str(ind))
                deleteButton.actionPerformed = self.whenDeleteIndicationClicked
                self.detail.add(deleteButton)
            ind += 1

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Add button
        self.addButton = JButton("Add")
        self.addButton.actionPerformed = self.whenAddClicked
        self.buttons.add(self.addButton)

        # Apply button
        self.applyButton.actionPerformed = self.whenApplyClicked
        self.buttons.add(self.applyButton)

    # Buttons of Indications window =================

    # define what Cancel button in Indications Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.indicationsFrame = None

    # define what Add button in Indications Window does when clicked
    def whenAddClicked(self,event) :
        ADsettings.indicationsList.append(ADindication("New indication", -1, -1,
          len(ADsettings.speedsList)))
        ADsignalType.adjust()
        self.indicationsChanged()

    # define what Delete button in Indications Window does when clicked
    def whenDeleteIndicationClicked(self,event) :
        ind = int(event.getActionCommand())
        if (JOptionPane.showConfirmDialog(None, "Remove signal indication \""
          + ADsettings.indicationsList[ind].name + "\"?", "Confirmation",
          JOptionPane.YES_NO_OPTION) == 1) :
            return
        ADsettings.indicationsList.pop(ind)
        for a in ADsettings.indicationsList :
            if a.nextIndication == ind :
                a.nextIndication = -1
            elif a.nextIndication > ind :
                a.nextIndication -=1
        AutoDispatcher.setPreferencesDirty()
        self.indicationsChanged()

    # define what Apply button in Indications Window does when clicked
    def whenApplyClicked(self,event) :
        ind = 0
        for a in ADsettings.indicationsList :
            a.name = a.nameSwing.text
            if ind > 0 :
                a.speed = a.speedSwing.getSelectedIndex() + 1
                if ind > 1 :
                    a.nextIndication = a.nextIndicationSwing.getSelectedIndex()-1
                    a.nextTurnout = a.nextTurnoutSwing.getSelectedIndex()-1
            ind += 1 
        self.indicationsChanged()
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Signal indications changed")

    def indicationsChanged(self) :
        if AutoDispatcher.signalEditFrame != None :
            AutoDispatcher.signalEditFrame.reDisplay()
        AutoDispatcher.setPreferencesDirty()
        self.reDisplay()

    # Signal Types window =================
    
class ADsignalTypesFrame (AdScrollFrame) :
    def __init__(self) :
        # Create and display Speeds window
        # super.init
        AdScrollFrame.__init__(self, "Signal Types",
          AutoDispatcher.centerLabel("List of supported signal types"))

    def createHeader(self):
        # Fill contents of Header
        self.header = None
    
    def createDetail(self):
        # Fill contents of scroll area
        self.detail.setLayout(GridLayout(len(ADsettings.signalTypes), 2))
        ind = 0
        for s in ADsettings.signalTypes :
            self.detail.add(JLabel(s.name))
            temppane1 = JPanel()
            temppane1.setLayout(GridLayout(1, 4))
            if s.headsNumber == 1 :
                temppane1.add(AutoDispatcher.centerLabel("1 Head"))
            else :
                temppane1.add(AutoDispatcher.centerLabel(str(s.headsNumber)
                  + " Heads"))
            editButton = JButton("Edit")
            editButton.setActionCommand(str(ind))
            editButton.actionPerformed = self.whenEditClicked
            temppane1.add(editButton)
            duplicateButton = JButton("Duplicate")
            duplicateButton.setActionCommand(str(ind))
            duplicateButton.actionPerformed = self.whenDuplicateClicked
            temppane1.add(duplicateButton)
            if ind == 0 :
                temppane1.add(JLabel(""))
            else :
                if s.inUse > 0 :
                    temppane1.add(AutoDispatcher.centerLabel("In use"))
                else :
                    deleteButton = JButton("Delete")
                    deleteButton.setActionCommand(str(ind))
                    deleteButton.actionPerformed = self.whenDeleteClicked
                    temppane1.add(deleteButton)
            self.detail.add(temppane1)
            ind += 1

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Add button
        self.addButton = JButton("Add")
        self.addButton.actionPerformed = self.whenAddClicked
        self.buttons.add(self.addButton)

    # Buttons of Signal Types window =================

    # define what Cancel button in Signal Types Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.signalTypesFrame = None
            if AutoDispatcher.signalEditFrame != None :
                AutoDispatcher.signalEditFrame.dispose()
                AutoDispatcher.signalEditFrame = None
                        
    # define what Edit button in Signal Types Window does when clicked
    def whenEditClicked(self,event) :
        ind = int(event.getActionCommand())
        if AutoDispatcher.signalEditFrame != None :
            AutoDispatcher.signalEditFrame.show()
            if (ADsettings.signalTypes[ind]
               == AutoDispatcher.signalEditFrame.editSignal) :
                return
            if (JOptionPane.showConfirmDialog(None,
               "Save changes to signal type \""
               + AutoDispatcher.signalEditFrame.editSignal.name
               + "\" before editing signal type \""
               + ADsettings.signalTypes[ind].name
               + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) != 1) :
                    AutoDispatcher.signalEditFrame.whenApplyClicked(None)
            AutoDispatcher.signalEditFrame.dispose()
        AutoDispatcher.signalEditFrame = (
          ADsignalEditFrame(ADsettings.signalTypes[ind]))

    # define what Add button in Signal Types Window does when clicked
    def whenAddClicked(self,event) :
        ADsettings.signalTypes.append(ADsignalType(
          "New signal type", [], []))
        self.signalTypesChanged()

    # define what Delete button in Signal Types Window does when clicked
    def whenDeleteClicked(self,event) :
        ind = int(event.getActionCommand())
        if (JOptionPane.showConfirmDialog(None, "Remove signal type \""
           + ADsettings.signalTypes[ind].name
           + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) == 1) :
            return
        ADsettings.signalTypes.pop(ind)
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Signal type \"" + ADsettings.signalTypes[ind].name + " removed")
        self.signalTypesChanged()

    # define what Duplicate button in Signal Types Window does when clicked
    def whenDuplicateClicked(self,event) :
        ind = int(event.getActionCommand())
        ADsettings.signalTypes.append(
          ADsignalType(ADsettings.signalTypes[ind].name + 
          " copy", ADsettings.signalTypes[ind].aspects,
          ADsettings.signalTypes[ind].speeds))
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Signal type \"" + ADsettings.signalTypes[ind].name + " duplicated")
        self.signalTypesChanged()

    def signalTypesChanged(self) :
        if AutoDispatcher.signalMastsFrame != None :
            AutoDispatcher.signalMastsFrame.reDisplay()
        AutoDispatcher.setPreferencesDirty()
        self.reDisplay()

    # Signal Edit Window =================

class ADsignalEditFrame (AdScrollFrame) :
    def __init__(self, signal) :
        # Create and display Speeds window
        self.editSignal = signal
        first = JPanel()
        first.setLayout(BoxLayout(first, BoxLayout.X_AXIS))
        first.add(AutoDispatcher.centerLabel("Name: "))
        first.add(self.editSignal.nameSwing)
        first.add(AutoDispatcher.centerLabel("Heads: "))
        self.headsSwing = JComboBox(["1", "2", "3", "4", "5"])
        self.headsSwing.setSelectedIndex(self.editSignal.headsNumber -1)
        if self.editSignal == ADsettings.signalTypes[0] :
            self.headsSwing.enabled = False
        first.add(self.headsSwing)
        # super.init
        AdScrollFrame.__init__(self, "Edit signal type", first)

    def createHeader(self):
        # Fill contents of Header
        self.header.setLayout(GridLayout(1, 3 + self.editSignal.headsNumber))
        self.header.add(AutoDispatcher.centerLabel("Signal indication"))
        for i in range(self.editSignal.headsNumber) :
            self.header.add(AutoDispatcher.centerLabel("Head" + str(i+1)))
        self.header.add(AutoDispatcher.centerLabel("Speed"))
        self.header.add(AutoDispatcher.centerLabel("Override speed"))
    
    def createDetail(self):
        # Fill contents of scroll area
        self.detail.setLayout(GridLayout(self.editSignal.aspectsNumber, 3
           + self.editSignal.headsNumber))
        speedNames = ["No"]
        for s in ADsettings.speedsList :
            speedNames.append(s)
        self.signalSpeedSwing = []
        self.signalAspectsSwing = []
        ind = 0
        headsAspects = AutoDispatcher.headsAspects.keys()
        headsAspects.sort()
        for a in self.editSignal.aspects :
            if ind >= len(ADsettings.indicationsList) :
 #               self.detail.add(JLabel("Not defined"))
                break
 #           else :
            self.detail.add(JLabel(ADsettings.indicationsList[ind].name))
            aspectLine = []
            for aa in a :
                aspectSwing = JComboBox(headsAspects)
                aspectSwing.setSelectedItem(AutoDispatcher.inverseAspects[aa])
                aspectLine.append(aspectSwing)
                self.detail.add(aspectSwing)
            self.signalAspectsSwing.append(aspectLine)
            self.detail.add(AutoDispatcher.centerLabel(
              ADsettings.getSpeedName(ADsettings.indicationsList[ind].speed)))
            speedSwing = JComboBox(speedNames)
            speedIndex = self.editSignal.speeds[ind]+1
            if speedIndex >= len(speedNames) :
                speedIndex = len(speedNames) -1
            speedSwing.setSelectedIndex(speedIndex)
            if ind == 0 :
                speedSwing.enabled = False
            self.signalSpeedSwing.append(speedSwing)
            self.detail.add(speedSwing)
            ind += 1

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Apply button
        self.applyButton.actionPerformed = self.whenApplyClicked
        self.buttons.add(self.applyButton)

    # Buttons of Signal Edit window =================

    # define what Cancel button in Signal Edit Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.signalEditFrame = None

    # define what Apply button in Signal Edit Window does when clicked
    def whenApplyClicked(self,event) :
        if self.editSignal != None :
            self.editSignal.name = self.editSignal.nameSwing.text
            for i in range(self.editSignal.aspectsNumber) :
                for j in range(self.editSignal.headsNumber) :
                    self.editSignal.aspects[i][j] = AutoDispatcher.headsAspects[
                      self.signalAspectsSwing[i][j].getSelectedItem()]
                self.editSignal.speeds[i] = self.signalSpeedSwing[
                      i].getSelectedIndex()-1
            newHeads = self.headsSwing.getSelectedIndex() + 1
            if newHeads != self.editSignal.headsNumber :
                diff = newHeads - self.editSignal.headsNumber
                if diff > 0 :
                    for i in range(diff) :
                        self.editSignal.aspects[0].append(SignalHead.RED)
                    for i in range(len(self.editSignal.aspects) -1) :
                        for j in range(diff) :
                            self.editSignal.aspects[i+1].append(
                              SignalHead.GREEN)
                else :
                    for a in self.editSignal.aspects :
                        for i in range(-diff) :
                            a.pop()
                self.editSignal.headsNumber = (
                  self.headsSwing.getSelectedIndex() + 1)
            if AutoDispatcher.signalTypesFrame != None :
                AutoDispatcher.signalTypesFrame.reDisplay()
            if AutoDispatcher.signalMastsFrame != None :
                AutoDispatcher.signalMastsFrame.reDisplay()
            AutoDispatcher.setPreferencesDirty()
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Signal type \"" + self.editSignal.name + " modified")
            self.reDisplay()

    # Signal Types window =================
    
class ADsignalMastsFrame (AdScrollFrame) :
    def __init__(self) :
        # Create and display Speeds window
        # super.init
        AdScrollFrame.__init__(self, "Signal masts", None)

    def createHeader(self):
        # Fill contents of Header
        if self.firstTime :
            names = ADsignalMast.getNames()
            names.sort()
            self.signals = []
            self.maxHeads = 1
            for name in names :
                s = ADsignalMast.getByName(name)
                # Shouldn't happen
                if s != None :
                    if s.headsNumber > self.maxHeads :
                        self.maxHeads = s.headsNumber
                    self.signals.append(s)
        self.header.setLayout(GridLayout(1, 3))
        self.header.add(AutoDispatcher.centerLabel("Signal name"))
        self.header.add(AutoDispatcher.centerLabel("Signal type"))
        header1 = JPanel()
        header1.setLayout(GridLayout(1, self.maxHeads + 1))
        for i in range(self.maxHeads) :
            header1.add(AutoDispatcher.centerLabel("Head" + str(i+1)))
        header1.add(JLabel(""))
        self.header.add(header1)
    
    def createDetail(self):
        # Fill contents of scroll area
        self.detail.setLayout(GridLayout(len(self.signals), 3))
        self.namesSwing = []
        self.typesSwing = []
        self.headsSwing = []
        types = []
        for t in ADsettings.signalTypes :
            types.append(t.name)
        ind = 0
        for s in self.signals :
            nameSwing = JTextField(s.name, 20)
            self.detail.add(nameSwing)
            self.namesSwing.append(nameSwing)
            typeSwing = JComboBox(types)
            typeSwing.setSelectedItem(s.signalType.name)
            self.detail.add(typeSwing)
            self.typesSwing.append(typeSwing)
            temppane1 = JPanel()
            temppane1.setLayout(GridLayout(1, self.maxHeads))
            i = 0
            headsLineSwing = []
            for h in s.signalHeads :
                headSwing = JComboBox(AutoDispatcher.signalHeadNames)
                if i < self.maxHeads :
                    if h != None :
                        headSwing.setSelectedItem(h.name)
                    temppane1.add(headSwing)
                    headsLineSwing.append(headSwing)
                    i += 1
            self.headsSwing.append(headsLineSwing)
            while i < self.maxHeads :
                temppane1.add(JLabel(""))
                i += 1
            if s.inUse > 0 :
                temppane1.add(AutoDispatcher.centerLabel("In use"))
            else :
                deleteButton = JButton("Delete")
                deleteButton.setActionCommand(str(ind))
                deleteButton.actionPerformed = self.whenDeleteClicked
                temppane1.add(deleteButton)
            self.detail.add(temppane1)
            ind += 1

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Add button
        self.addButton = JButton("Add")
        self.addButton.actionPerformed = self.whenAddClicked
        self.buttons.add(self.addButton)

        # Apply button
        self.applyButton.actionPerformed = self.whenApplyClicked
        self.buttons.add(self.applyButton)

    # Buttons of Signal Types window =================

    # define what Cancel button in Signal Masts Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.signalMastsFrame = None
                        
    # define what Add button in Signal Masts Window does when clicked
    def whenAddClicked(self,event) :
        newName = "New signal"
        i = 1
        while ADsignalMast.signalsList.has_key(newName) :
            i += 1
            newName = "New signal " + str(i)
        self.signals.append(ADsignalMast.provideSignal(newName))
        self.reDisplay()

    # define what Apply button in Signal Masts Window does when clicked
    def whenApplyClicked(self,event) :
        ind = 0
        for s in self.signals :
            newName = self.namesSwing[ind].text
            if newName != s.name :
                if ADsignalMast.signalsList.has_key(newName) :
                    AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
                      "Duplicate signal masts name \""
                      + newName + "\" - ignored")
                    self.namesSwing[ind].text = s.name
                else :
                    s.name = newName
            typeName = self.typesSwing[ind].getSelectedItem()
            newType = s.signalType
            for t in ADsettings.signalTypes :
                if t.name == typeName :
                    newType = t
                    break
            if newType != s.signalType :
                s.signalType.changeUse(-1)
                while newType.headsNumber > s.headsNumber :
                    s.signalHeads.append(None)
                    s.headsNumber += 1
                s.signalType = newType
                s.signalType.changeUse(1)
                if newType.headsNumber > self.maxHeads :
                    self.maxHeads = newType.headsNumber
            i = 0
            for h in self.headsSwing[ind] :
                headName = h.getSelectedItem()
                if headName == "" :
                    s.signalHeads[i] = None
                else :
                    s.signalHeads[i] = ADsignalHead(headName)
                i += 1
            ind += 1
        newDic = {}
        for s in ADsignalMast.getList() :
            newDic[s.name] = s
        ADsignalMast.signalsList = newDic           
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Signal masts updated")
        self.signalMastsChanged()

    # define what Delete button in Signal Masts Window does when clicked
    def whenDeleteClicked(self,event) :
        ind = int(event.getActionCommand())
        name = self.signals[ind].name
        if (JOptionPane.showConfirmDialog(None, "Remove signal mast \"" + name
           + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) == 1) :
            return
        removed = self.signals.pop(ind)
        removed.changeUse(-1)
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Signal mast \"" + name + " removed")
        self.signalMastsChanged()
        
    def signalMastsChanged(self) :
        if AutoDispatcher.signalTypesFrame != None :
            AutoDispatcher.signalTypesFrame.reDisplay()
        if AutoDispatcher.sectionsFrame != None :
            AutoDispatcher.sectionsFrame.reDisplay()
        AutoDispatcher.setPreferencesDirty()
        self.reDisplay()

    # Sections Window =================
    
class ADsectionsFrame (AdScrollFrame) :
    def __init__(self) :
        # Create Sections window
        # super.init
        AdScrollFrame.__init__(self, "Sections", None)

    def createHeader(self):
        # Fill contents of Header
        self.header.setLayout(GridLayout(1, 8))
        self.header.add(AutoDispatcher.centerLabel("Section"))
        self.header.add(AutoDispatcher.centerLabel("One-Way"))
        self.header.add(AutoDispatcher.centerLabel("Transit-Only"))
        self.header.add(AutoDispatcher.centerLabel(
          ADsettings.directionNames[0] + " signal"))
        self.header.add(AutoDispatcher.centerLabel(
          ADsettings.directionNames[0] + " stop at beginning"))
        self.header.add(AutoDispatcher.centerLabel(
          ADsettings.directionNames[1] + " signal"))
        self.header.add(AutoDispatcher.centerLabel(
          ADsettings.directionNames[1] + " stop at beginning"))
        self.header.add(AutoDispatcher.centerLabel("Man. sensor"))
        
    def createDetail(self):
        # Fill contents of scroll area
        sections = ADsection.getSectionsTable()
        self.detail.setLayout(GridLayout(len(sections), 6))
        both = (ADsettings.directionNames[0] + "-"
          + ADsettings.directionNames[1])
        signalMastList = ADsignalMast.getNames()
        signalMastList.sort()
        signalPopUp = [""]
        for s in signalMastList :
            signalPopUp.append("s: " + s)
        for s in AutoDispatcher.signalHeadNames :
            if not s in signalMastList and s.strip() != "" :
                signalPopUp.append("h: " + s)        
        for s in sections :
            self.detail.add(AutoDispatcher.centerLabel(s[0]))
            ss = ADsection.getByName(s[0])
            ss.oneWaySwing.removeAllItems()
            ss.oneWaySwing.addItem("")
            ss.oneWaySwing.addItem(ADsettings.directionNames[0])
            ss.oneWaySwing.addItem(ADsettings.directionNames[1])
            self.detail.add(ss.oneWaySwing)
            ss.oneWaySwing.setSelectedItem(s[1])
            ss.transitOnlySwing.removeAllItems()
            ss.transitOnlySwing.addItem("")
            ss.transitOnlySwing.addItem(both)
            ss.transitOnlySwing.addItem(both + "+")
            ss.transitOnlySwing.addItem(ADsettings.directionNames[0])
            ss.transitOnlySwing.addItem(ADsettings.directionNames[0]
              + "+")
            ss.transitOnlySwing.addItem(ADsettings.directionNames[1])
            ss.transitOnlySwing.addItem(ADsettings.directionNames[1]
              + "+")
            self.detail.add(ss.transitOnlySwing)
            ss.transitOnlySwing.setSelectedItem(s[2])
            j = ADsettings.ccw
            for k in range(2) :
                ss.signalSwing[j] = JComboBox(signalPopUp)
                self.detail.add(ss.signalSwing[j])
                if ss.signal[j] != None :
                    ss.signalSwing[j].setSelectedItem("s: " + ss.signal[j].name)
                temppane = JPanel()
                temppane.setLayout(GridLayout(1, 2))
                if ss.stopAtBeginning[j] >= 0 :
                    ss.stopAtBeginningSwing[j].selected = True
                    ss.stopAtBeginningDelay[j].text = str(ss.stopAtBeginning[j])
                else :
                    ss.stopAtBeginningSwing[j].selected = False
                    ss.stopAtBeginningDelay[j].text = "0.0"
                temppane.add(ss.stopAtBeginningSwing[j])
                temppane.add(ss.stopAtBeginningDelay[j])
                self.detail.add(temppane)
                j = 1-j
            ss.manualSensorSwing = JComboBox(ADsection.sensorNames)
            if s[6] in ADsection.sensorNames :
                ss.manualSensorSwing.setSelectedItem(s[6])
            self.detail.add(ss.manualSensorSwing)

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Apply button
        self.applyButton.actionPerformed = self.whenApplyClicked
        self.buttons.add(self.applyButton)

    # Buttons of Sections window =================
    
    # define what Cancel button in Sections Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.sectionsFrame = None

    # define what Apply button in Sections Window does when clicked
    def whenApplyClicked(self,event) :
        for s in ADsection.getList() :
            s.updateFromSwing()
        if AutoDispatcher.signalMastsFrame != None :
            AutoDispatcher.signalMastsFrame.reDisplay()
        ADgridGroup.create()
        AutoDispatcher.setPreferencesDirty()
        self.reDisplay()
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Sections changes applied")

    # Blocks Window =================
    
class ADblocksFrame (AdScrollFrame) :
    def __init__(self) :
        # Create Blocks window
        # super.init
        AdScrollFrame.__init__(self, "Blocks", None)

    def createHeader(self):
        # Fill contents of Header
        # Retrieve section names
        self.sectionNames = ADsection.getNames()
        self.sectionNames.sort()
            
        # Check if the direction of any section can be reversed
        self.columns = 10
        self.canBeReversed = []
        self.inversionPoints = []
        for sectionName in self.sectionNames :
            section = ADsection.getByName(sectionName)
            canBe = False
            # Direction can be reversed only if all entry points
            # in one direction connect with sections in opposite direction
            # (i.e. the section is at the end of a reversing loop)
            invPoints = ["Inv. Points"]
            for direction in [False, True] :
                entries = section.getEntries(direction)
                if len(entries) > 0 :
                    canBe1 = True
                    invPoints1 = []
                    for entry in entries :
                        if not entry.getDirectionChange() :
                            canBe1 = False
                            break
                        invPoints1.append(entry.getExternalSection().getName())
                    if canBe1 :
                        canBe = True
                        invPoints.extend(invPoints1)
            # Keep track of the condition
            self.canBeReversed.append(canBe)
            if canBe :
                self.columns = 11
            else :
                invPoints = []
            self.inversionPoints.append(invPoints)
                
        self.header.setLayout(GridLayout(2, self.columns))
        self.header.add(AutoDispatcher.centerLabel("Section"))
        self.header.add(AutoDispatcher.centerLabel("Block"))
        for i in range(2) :
            for j in range(2) :
                header1 = JPanel()
                header1.setLayout(GridLayout(1, 2))
                for k in range(2) :
                    header1.add(AutoDispatcher.centerLabel(
                      ADsettings.directionNames[i]))
                self.header.add(header1)
            self.header.add(AutoDispatcher.centerLabel(
              ADsettings.directionNames[i]))
            self.header.add(AutoDispatcher.centerLabel(
              ADsettings.directionNames[i]))
        if self.columns > 10 :
            self.header.add(JLabel(""))
        self.header.add(JLabel(""))
        self.header.add(JLabel(""))
        for i in range(2) :
            header1 = JPanel()
            header1.setLayout(GridLayout(1, 2))
            header1.add(AutoDispatcher.centerLabel("alloc."))
            header1.add(AutoDispatcher.centerLabel("safe"))
            self.header.add(header1)
            header1 = JPanel()
            header1.setLayout(GridLayout(1, 2))
            header1.add(AutoDispatcher.centerLabel("stop"))
            header1.add(AutoDispatcher.centerLabel("brake"))
            self.header.add(header1)
            self.header.add(AutoDispatcher.centerLabel("speed"))
            self.header.add(AutoDispatcher.centerLabel("action"))

        if self.columns > 10 :
            self.header.add(AutoDispatcher.centerLabel("Reverse"))

    def createDetail(self):
        # Fill contents of scroll area
        # Compute total number of lines
        linesNumber = len(self.sectionNames) * 2
        ind = 0
        for sectionName in self.sectionNames :
            s = ADsection.getByName(sectionName)
            lines = len(s.getBlocks(True))
            if lines < len(self.inversionPoints[ind]) :
                lines = len(self.inversionPoints[ind])
            linesNumber += lines
            ind +=1
        self.detail.setLayout(GridLayout(linesNumber, self.columns))
        # Prepare list for speeds ComboBox
        speeds = [""]
        speeds.extend(ADsettings.speedsList)
        for sectionName in self.sectionNames :
            canBe = self.canBeReversed.pop(0)
            invPoints = self.inversionPoints.pop(0)
            section = ADsection.getByName(sectionName)
            for block in section.getBlocks(ADsettings.ccw) :
                self.detail.add(AutoDispatcher.centerLabel(sectionName))
                self.detail.add(AutoDispatcher.centerLabel(block.getName()))
                j = ADsettings.ccw
                for k in range(2) :
                    temppane = JPanel()
                    temppane.setLayout(GridLayout(1, 2))
                    block.allocationSwing[j].setSelected(block == (
                      section.allocationPoint[j]))
                    temppane.add(block.allocationSwing[j])
 
                    block.safeSwing[j].setSelected(block == (
                      section.safePoint[j]))
                    temppane.add(block.safeSwing[j])
                    self.detail.add(temppane)
                    
                    temppane = JPanel()
                    temppane.setLayout(GridLayout(1, 2))
                    block.stopSwing[j].setSelected(block == (
                      section.stopBlock[j]))
                    temppane.add(block.stopSwing[j])

                    block.brakeSwing[j].setSelected(block == (
                      section.brakeBlock[j]))
                    temppane.add(block.brakeSwing[j])
                    self.detail.add(temppane)

                    block.speedSwing[j] = JComboBox(speeds)
                    block.speedSwing[j].setSelectedIndex(block.getSpeed(j))
                    self.detail.add(block.speedSwing[j])
                    block.actionSwing[j].text = block.action[j]
                    self.detail.add(block.actionSwing[j])
                    
                    j = 1 - j
                if self.columns > 10 :
                    if canBe and sectionName != "" :
                        reverseButton = JButton("Reverse")
                        reverseButton.setActionCommand(sectionName)
                        reverseButton.actionPerformed = self.whenReverseClicked
                        self.detail.add(reverseButton)
                    else :
                        if len(invPoints) > 0 :
                            self.detail.add(AutoDispatcher.centerLabel(invPoints.pop(0)))
                        else :
                            self.detail.add(JLabel(""))
                sectionName = ""
            # Add a "None" line
            self.detail.add(JLabel(""))
            self.detail.add(AutoDispatcher.centerLabel("None"))
            j = ADsettings.ccw
            for k in range(2) :
                temppane = JPanel()
                temppane.setLayout(GridLayout(1, 2))
                temppane.add(JLabel(""))
                section.safeNoneSwing[j].setSelected(
                  section.safePoint[j] == None)
                temppane.add(section.safeNoneSwing[j])
                self.detail.add(temppane)
                temppane = JPanel()
                temppane.setLayout(GridLayout(1, 2))
                temppane.add(JLabel(""))
                section.brakeNoneSwing[j].setSelected(
                  section.brakeBlock[j] == None)
                temppane.add(section.brakeNoneSwing[j])
                self.detail.add(temppane)
                self.detail.add(JLabel(""))
                self.detail.add(JLabel(""))
                j = 1 - j
            if self.columns > 10 :
                if len(invPoints) > 0 :
                    self.detail.add(AutoDispatcher.centerLabel(invPoints.pop(0)))
                    while len(invPoints) > 0 :
                        for i in range(self.columns - 1) :
                            self.detail.add(JLabel(""))
                        self.detail.add(AutoDispatcher.centerLabel(invPoints.pop(0)))
                else :
                    self.detail.add(JLabel(""))
             # Add an empty line between sections
            for i in range(self.columns) :
                self.detail.add(JLabel(""))

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Apply button
        self.applyButton.actionPerformed = self.whenApplyClicked
        self.buttons.add(self.applyButton)

    # Buttons of Blocks window =================
    
    # define what Cancel button in Blocks Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.blocksFrame = None

    # define what Reverse button in Blocks Window does when clicked
    def whenReverseClicked(self,event) :
        sectionName = event.getActionCommand()
        section = ADsection.getByName(sectionName)
        # Reverse blocks order
        section.manuallyFlip()
        # Adjust entry points
        AutoDispatcher.instance.findTransitPoints()
        self.reDisplay()
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Direction of section " + sectionName + " reversed")
        return

    # define what Apply button in Blocks Window does when clicked
    def whenApplyClicked(self,event) :
        for section in ADsection.getList() :
            section.safePoint[0] = section.safePoint[1] = None
            for block in section.getBlocks(True) :
                for j in range(2) :
                    if block.allocationSwing[j].isSelected() :
                        section.allocationPoint[j] = block
                    if block.safeSwing[j].isSelected() :
                        section.safePoint[j] = block
                    if block.stopSwing[j].isSelected() :
                        section.stopBlock[j] = block
                    if block.brakeSwing[j].isSelected() :
                        section.brakeBlock[j] = block
                    block.speed[j] = block.speedSwing[j].getSelectedIndex()
                    block.action[j] = block.actionSwing[j].text
            for j in range(2) :
                if section.safeNoneSwing[j].isSelected() :
                    section.safePoint[j] = None
                if section.brakeNoneSwing[j].isSelected() :
                    section.brakeBlock[j] = None
        self.reDisplay()
        AutoDispatcher.setPreferencesDirty()
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Blocks changes applied")
          
    # Locations Window =================
    
class ADlocationsFrame (AdScrollFrame) :
    def __init__(self) :
        # Create Locations window
        # Retrieve location names
        # super.init
        AdScrollFrame.__init__(self, "Locations", JLabel("Define the list"
        + " of sections corresponding to each Operations' location"))
        frameSize = self.getMinimumSize()
        if frameSize.width < 800 :
            frameSize.width = 800
            self.setMinimumSize(frameSize)
            self.pack()
    def createHeader(self):
        # Fill contents of Header
        self.header.setLayout(GridLayout(1, 2))
        self.header.add(AutoDispatcher.centerLabel("Location"))
        self.header.add(AutoDispatcher.centerLabel("Sections"))

    def createDetail(self):
        # Fill contents of scroll area
        # Compute total number of lines
        self.locationNames = ADlocation.getNames()
        self.locationNames.sort()
        self.detail.setLayout(GridLayout(len(self.locationNames), 2))
        self.valuesSwing = []
        for locationName in self.locationNames :
            location = ADlocation.getByName(locationName)
            if location.opLocation == None :
                locationName += " (Unknown)"
            self.detail.add(JLabel(locationName))
            value = location.text
            value =  JTextField(value, 20)
            self.detail.add(value)
            self.valuesSwing.append(value)

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Apply button
        self.applyButton.actionPerformed = self.whenApplyClicked
        self.buttons.add(self.applyButton)

    # Buttons of Locations window =================
    
    # define what Cancel button in Locations Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.locationsFrame = None

    # define what Apply button in Locations Window does when clicked
    def whenApplyClicked(self,event) :
        ind = 0 
        for locationName in self.locationNames :
            location = ADlocation.getByName(locationName)
            location.setSections(self.valuesSwing[ind].text)
            ind += 1
        AutoDispatcher.setPreferencesDirty()
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Locations changes applied")

    # Preferences Window =================   
   
class ADpreferencesFrame (AdScrollFrame) :
    def __init__(self) :
        # Create Preferences window
        # super.init
        AdScrollFrame.__init__(self, "Preferences", None)

    def createHeader(self):
        self.header = None

    def createDetail(self):
        self.detail.setLayout(GridLayout(43, 2))
        self.detail.add(JLabel("COMMON SETTINGS"))
        self.detail.add(JLabel(""))
        self.detail.add(JLabel("Verbose output:"))
        self.verboseSwing = JCheckBox("", ADsettings.verbose)
        self.detail.add(self.verboseSwing)
        self.detail.add(JLabel("Ring bell for main events:"))
        self.ringBellSwing = JCheckBox("", ADsettings.ringBell)
        self.detail.add(self.ringBellSwing)
        self.detail.add(JLabel("Pause mode:"))
        self.pauseSwing = JComboBox(["Disabled", "Stop trains",
          "Emergency stop trains", "Turn power off"])
        self.pauseSwing.setSelectedIndex(ADsettings.pauseMode)
        self.detail.add(self.pauseSwing)
        self.detail.add(JLabel("Turnouts controlled by separate system: "))
        self.separateTurnoutsSwing = JCheckBox("",
          ADsettings.separateTurnouts)
        self.detail.add(self.separateTurnoutsSwing)
        self.detail.add(JLabel("Signals controlled by separate system: "))
        self.separateSignalsSwing = JCheckBox("",
          ADsettings.separateSignals)
        self.detail.add(self.separateSignalsSwing)
        self.detail.add(JLabel("Scale: "))
        self.scaleSwing = JTextField(str(ADsettings.scale), 5)
        temppane = JPanel()
        temppane.setLayout(BoxLayout(temppane, BoxLayout.X_AXIS))
        temppane.add(JLabel("1:"))
        temppane.add(self.scaleSwing)
        self.detail.add(temppane)
        self.detail.add(JLabel("Flashing cycle of signal icons (seconds):"))
        self.flashingSwing = JTextField(str(ADsettings.flashingCycle), 5)        
        self.detail.add(self.flashingSwing)
        self.detail.add(JLabel(""))
        self.detail.add(JLabel(""))
        self.detail.add(JLabel("DISPATCHER SETTINGS"))
        self.detail.add(JLabel(""))
        self.detail.add(JLabel("Derailed trains detection:"))
        self.derailedSwing = JComboBox(["Disabled", "Enabled: only warning",
          "Enabled: pause script"])
        self.derailedSwing.setSelectedIndex(ADsettings.derailDetection)
        self.detail.add(self.derailedSwing)
        self.detail.add(JLabel("Trains entering wrong route detection: "))
        self.wrongRouteSwing = JComboBox(["Disabled", "Enabled: only warning",
          "Enabled: pause script"])
        self.wrongRouteSwing.setSelectedIndex(
          ADsettings.wrongRouteDetection)
        self.detail.add(self.wrongRouteSwing)
        self.detail.add(JLabel("Stalled trains detection: "))
        self.stalledSwing = JComboBox(["Disabled", "Enabled: only warning",
          "Enabled: pause script"])
        self.stalledSwing.setSelectedIndex(ADsettings.stalledDetection)
        self.detail.add(self.stalledSwing)
        self.detail.add(JLabel(
          "Maximum time required to travel a block (seconds): "))
        self.stalledTimeSwing = JTextField(
          str(float(ADsettings.stalledTime)/1000.), 5)
        self.detail.add(self.stalledTimeSwing)
        self.detail.add(JLabel("Lost cars detection:"))
        self.lostCarsSwing = JComboBox(["Disabled", "Enabled: only warning",
          "Enabled: pause script"])
        self.lostCarsSwing.setSelectedIndex(ADsettings.lostCarsDetection)
        self.detail.add(self.lostCarsSwing)
        if ADsettings.units == 1.0 :
            self.detail.add(JLabel("Tollerance for lost cars detection (mm.):"))
        elif ADsettings.units == 10.0 :
            self.detail.add(JLabel("Tollerance for lost cars detection (cm.):"))
        else :
            self.detail.add(JLabel(
              "Tolerance for lost cars detection (inches):"))
        self.lostLengthSwing = JTextField(
          str(ADsettings.lostCarsTollerance / ADsettings.units), 4)
        self.detail.add(self.lostLengthSwing)
        self.detail.add(JLabel(
          "Maximum number of sections occupied by a train:"))
        self.lostMaxSwing = JTextField(
          str(ADsettings.lostCarsSections), 4)        
        self.detail.add(self.lostMaxSwing)        
        self.detail.add(JLabel(
          "(Most) cars are equipped with resistive wheel-sets:"))        
        self.useResistiveSwing = JCheckBox("", ADsettings.resistiveDefault)
        self.detail.add(self.useResistiveSwing)
        self.detail.add(JLabel("Train length expressed in: "))
        self.unitsSwing = JComboBox(["mm.", "cm.", "inches"])
        if ADsettings.units == 1.0 :
            self.unitsSwing.setSelectedIndex(0)
        elif ADsettings.units == 10.0 :
            self.unitsSwing.setSelectedIndex(1)
        else :
            self.unitsSwing.setSelectedIndex(2)
        self.detail.add(self.unitsSwing)
        self.detail.add(JLabel("Release sections based on train lenght: "))
        self.useLengthSwing = JCheckBox("", ADsettings.useLength)
        self.detail.add(self.useLengthSwing)
        if ADblock.blocksWithLength == 0 :
            self.useLengthSwing.setEnabled(False)
            self.detail.add(JLabel("  (Block lengths not defined!) "))
        elif ADblock.blocksWithoutLength == 0 :
            self.detail.add(JLabel("  (Length defined for all blocks)"))
        else :            
            self.detail.add(JLabel("  (Length not defined for "
              + str(ADblock.blocksWithoutLength) + " blocks!)"))
        self.detail.add(JLabel(""))
        self.detail.add(JLabel("Number of sections ahead to be allocated: "))
        self.aheadSwing = JComboBox(["1", "2", "3", "4", "5"])
        self.aheadSwing.setSelectedIndex(ADsettings.allocationAhead -1)
        self.detail.add(self.aheadSwing)
        self.detail.add(JLabel("Locomotives maintenance interval (hours): "))
        self.maintenanceSwing = JTextField(str(ADsettings.maintenanceTime), 5)
        self.detail.add(self.maintenanceSwing)
        if ADsettings.units == 25.4 :
            self.detail.add(JLabel(
              "Mileage maintenance interval (scale miles): "))
            multiplier = ADsettings.scale / 1609344.
        else :
            self.detail.add(JLabel(
              "Mileage maintenance interval (scale Km.): "))
            multiplier = ADsettings.scale / 1000000.
        self.milesSwing = JTextField(str(round(ADsettings.maintenanceMiles *
          multiplier,1)), 5)
        self.detail.add(self.milesSwing)

        self.detail.add(JLabel("Override JMRI block tracking:"))
        self.blockTrackingSwing = JCheckBox("", ADsettings.blockTracking)
        self.detail.add(self.blockTrackingSwing)
        self.detail.add(JLabel("Update JMRI sections state:"))
        self.sectionTrackingSwing = JCheckBox("",
          ADsettings.sectionTracking)
        self.detail.add(self.sectionTrackingSwing)
        self.detail.add(JLabel("Trust turnouts KnownState:"))
        self.trustTurnoutsSwing = JCheckBox("", ADsettings.trustTurnouts)
        self.detail.add(self.trustTurnoutsSwing)
        self.detail.add(JLabel("Delay between turnout commands (seconds): "))
        self.turnoutDelaySwing = JTextField(
          str(float(ADsettings.turnoutDelay)/1000.), 5)
        self.detail.add(self.turnoutDelaySwing)
        self.detail.add(JLabel("Delay before clearing signals (seconds): "))
        self.clearDelaySwing = JTextField(
          str(float(ADsettings.clearDelay)/1000.), 5)
        self.detail.add(self.clearDelaySwing)
        self.detail.add(JLabel("Trust signals KnownState:"))
        self.trustSignalsSwing = JCheckBox("", ADsettings.trustSignals)
        self.detail.add(self.trustSignalsSwing)
        self.detail.add(JLabel("Delay between signal commands (seconds): "))
        self.signalDelaySwing = JTextField(
          str(float(ADsettings.signalDelay)/1000.), 5)
        self.detail.add(self.signalDelaySwing)
        self.detail.add(JLabel("Automatically restart trains at script startup:"))
        self.autoRestartSwing = JCheckBox("",
          ADsettings.autoRestart)
        self.detail.add(self.autoRestartSwing)
        self.detail.add(JLabel(""))
        self.detail.add(JLabel(""))
        self.detail.add(JLabel("ENGINEER SETTINGS"))
        self.detail.add(JLabel(""))
        self.detail.add(JLabel("In front of red signals:"))
        self.stopModeSwing = JComboBox(["Progressively stop train", 
          "Immediately stop train"])
        self.stopModeSwing.setSelectedIndex(ADsettings.stopMode)
        self.detail.add(self.stopModeSwing)
        self.detail.add(JLabel(
          "Delay between clear signal and train departure (seconds): "))
        self.detail1 = JPanel()
        self.startDelayMinSwing = JTextField(
          str(float(ADsettings.startDelayMin)/1000.), 5)
        self.startDelayMaxSwing = JTextField(
          str(float(ADsettings.startDelayMax)/1000.), 5)
        self.detail1.add(JLabel("Between "))
        self.detail1.add(self.startDelayMinSwing)
        self.detail1.add(JLabel(" and "))
        self.detail1.add(self.startDelayMaxSwing)
        self.detail.add(self.detail1)
        self.detail.add(JLabel("Default actions before train departure:"))
        self.defaultStartSwing = JTextField(ADsettings.defaultStartAction, 5)
        self.detail.add(self.defaultStartSwing)
        self.detail.add(JLabel("Switch headlights ON/OFF:"))
        self.lightsSwing = JComboBox(["Never", "When train starts/stops",
          "When schedule starts/ends"])
        self.lightsSwing.setSelectedIndex(ADsettings.lightMode)
        self.detail.add(self.lightsSwing)
        self.detail.add(JLabel("Delay between throttle commands (seconds):"))
        self.dccDelaySwing = JTextField(
          str(float(ADsettings.dccDelay)/1000.), 5)
        self.detail.add(self.dccDelaySwing)
        self.detail.add(JLabel(
          "Maximum interval between throttle commands (seconds): "))
        self.maxIdleSwing = JTextField(
          str(float(ADsettings.maxIdle)/1000.), 5)
        self.detail.add(self.maxIdleSwing)
        self.detail.add(JLabel("Acceleration/deceleration interval: "))
        self.speedRampSwing = JComboBox(["1/10 sec.", "2/10 sec.", "3/10 sec.",
          "4/10 sec.", "5/10 sec."])
        self.speedRampSwing.setSelectedIndex(ADsettings.speedRamp -1)
        self.detail.add(self.speedRampSwing)
        self.detail.add(JLabel("Enable self-adjustment of braking distance: "))
        self.selfLearningSwing = JCheckBox("",
          ADsettings.selfLearning)
        if AutoDispatcher.simulation :
            self.selfLearningSwing.enabled = False
        self.detail.add(self.selfLearningSwing)

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Apply button (don't use the default one, since we wish 
        # the button always on)
        self.setButton = JButton("Apply")
        self.setButton.actionPerformed = self.whenApplyClicked
        self.buttons.add(self.setButton)
        
    # Buttons of Preferences window =================

    # define what Cancel button in Preferences Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.preferencesFrame = None

    # define what Apply button in Preferences Window does when clicked
    def whenApplyClicked(self,event) :
        ADsettings.verbose = self.verboseSwing.isSelected()
        self.ringBellSwing.isSelected()
        ADsettings.ringBell = self.ringBellSwing.isSelected()
        ADsettings.pauseMode = self.pauseSwing.getSelectedIndex()
        ADsettings.separateTurnouts = (
          self.separateTurnoutsSwing.isSelected())
        ADsettings.separateSignals = (
          self.separateSignalsSwing.isSelected())
        try :
            ADsettings.scale = float(self.scaleSwing.text)
        except :
            ADsettings.scale = 1
            self.scaleSwing.text = "1"
        try :
            ADsettings.flashingCycle = float(self.flashingSwing.text)
        except :
            ADsettings.flashingCycle = 1.0
            self.flashingSwing.text = "1"
        ADsettings.derailDetection = self.derailedSwing.getSelectedIndex()
        ADsettings.stalledDetection = self.stalledSwing.getSelectedIndex()
        try :
            ADsettings.stalledTime = int(
              float(self.stalledTimeSwing.text)*1000.)
        except :
            ADsettings.stalledTime = 1000
            self.stalledTimeSwing.text = "1"
        ADsettings.lostCarsDetection = (
          self.lostCarsSwing.getSelectedIndex())
        oldValue = ADsettings.lostCarsTollerance
        try :
            ADsettings.lostCarsTollerance = int(
              float(self.lostLengthSwing.text) * ADsettings.units)
        except :
            ADsettings.lostCarsTollerance = oldValue
            self.lostLengthSwing.text = str(oldValue
              / ADsettings.units)
        oldValue = ADsettings.lostCarsTollerance
        try :
            ADsettings.lostCarsSections = int(
              self.lostMaxSwing.text)
        except :
            ADsettings.lostCarsSections = oldValue
            self.lostMaxSwing.text = str(oldValue)
        ADsettings.wrongRouteDetection = (
          self.wrongRouteSwing.getSelectedIndex())
        i = self.unitsSwing.getSelectedIndex()
        if i == 0 :
            newUnits = 1.0
        elif i == 1 :
            newUnits = 10.0
        else :
            newUnits = 25.4
        if newUnits != ADsettings.units :
            ADsettings.units = newUnits
            if AutoDispatcher.trainsFrame != None :
                AutoDispatcher.trainsFrame.reDisplay()
                AutoDispatcher.preferencesFrame.show()
        ADsettings.useLength = self.useLengthSwing.isSelected()
        ADsettings.resistiveDefault = self.useResistiveSwing.isSelected()
        ADsettings.allocationAhead = (self.aheadSwing.getSelectedIndex() + 1)
        ADsettings.blockTracking = self.blockTrackingSwing.isSelected()
        ADsettings.sectionTracking = (
          self.sectionTrackingSwing.isSelected())
        ADsettings.trustTurnouts = self.trustTurnoutsSwing.isSelected()
        try :
            ADsettings.turnoutDelay = int(
              float(self.turnoutDelaySwing.text)*1000.)
        except :
            ADsettings.turnoutDelay = 1000
            self.turnoutDelaySwing.text = "1"
        try :
            ADsettings.clearDelay = int(
              float(self.clearDelaySwing.text)*1000.)
        except :
            ADsettings.clearDelay = 0
            self.clearDelaySwing.text = "0"
        ADsettings.trustSignals = self.trustSignalsSwing.isSelected()
        try :
            ADsettings.signalDelay = int(
              float(self.signalDelaySwing.text)*1000.)
        except :
            ADsettings.signalDelay = 0
            self.signalDelaySwing.text = "0"
        ADsettings.autoRestart = self.autoRestartSwing.isSelected()
        try :
            ADsettings.maintenanceTime = float(self.maintenanceSwing.text)
        except :
            ADsettings.maintenanceTime = 0
            self.maintenanceSwing.text = "0"
        try :
            if ADsettings.units == 25.4 :
                multiplier = 1609344. / ADsettings.scale
            else :
                multiplier = 1000000. / ADsettings.scale
            ADsettings.maintenanceMiles = (float(self.milesSwing.text) *
              multiplier)
        except :
            ADsettings.maintenanceMiles = 0
            self.milesSwing.text = "0"
        try :
            ADsettings.dccDelay = int(
              float(self.dccDelaySwing.text)*1000.)
        except :
            ADsettings.dccDelay = 10
            self.dccDelaySwing.text = "0.01"
        ADsettings.stopMode = self.stopModeSwing.getSelectedIndex()
        try :
            ADsettings.startDelayMin = int(
              float(self.startDelayMinSwing.text)*1000.)
        except :
            ADsettings.startDelayMin = 0
            self.startDelayMinSwing.text = "0"
        try :
            ADsettings.startDelayMax = int(
              float(self.startDelayMaxSwing.text)*1000.)
        except :
            ADsettings.startDelayMax = 0
            self.startDelayMaxSwing.text = "0"
        ADsettings.defaultStartAction = self.defaultStartSwing.text
        ADsettings.lightMode = self.lightsSwing.getSelectedIndex()
        ADsettings.speedRamp = self.speedRampSwing.getSelectedIndex() + 1
 
        try :
            ADsettings.maxIdle = int(float(self.maxIdleSwing.text)*1000.)
        except :
            ADsettings.maxIdle = 0
            self.maxIdleSwing.text = "0"
        ADsettings.selfLearning = self.selfLearningSwing.isSelected()
            
        AutoDispatcher.setPreferencesDirty()
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
         "Preferences changes applied")

    # Sound List Window =================

class ADsoundListFrame (AdScrollFrame) :
    def __init__(self) :
        # Create Sound List window
        # super.init
        AdScrollFrame.__init__(self, "List of Sounds", None)

    def createHeader(self):
        header1 = JPanel()
        header1.setLayout(GridLayout(1, 4))
        header1.add(AutoDispatcher.centerLabel("Name"))
        header1.add(JLabel(""))
        header1.add(JLabel(""))
        header1.add(JLabel(""))

        self.header.setLayout(GridLayout(1, 2))
        self.header.add(header1)
        self.header.add(AutoDispatcher.centerLabel("Path"))

    def createDetail(self):
        self.detail.setLayout(GridLayout(len(ADsettings.soundList), 2))
        self.namesSwing = []
        ind = 0
        for s in ADsettings.soundList :
            temppane = JPanel()
            temppane.setLayout(GridLayout(1, 4))
            nameSwing = JTextField(s.name, 5)
            self.namesSwing.append(nameSwing)
            temppane.add(nameSwing)
            browseButton = JButton("Browse")
            browseButton.setActionCommand(str(ind))
            browseButton.actionPerformed = self.whenBrowseClicked
            temppane.add(browseButton)
            if ind == 0 :
                temppane.add(JLabel(""))
            else :
                deleteButton = JButton("Delete")
                deleteButton.setActionCommand(str(ind))
                deleteButton.actionPerformed = self.whenDeleteClicked
                temppane.add(deleteButton)
            playButton = JButton("Play")
            playButton.setActionCommand(str(ind))
            playButton.actionPerformed = self.whenPlayClicked
            temppane.add(playButton)
            self.detail.add(temppane)
            self.detail.add(JLabel(s.path))
            ind += 1
            
    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Add button
        self.addButton = JButton("Add")
        self.addButton.actionPerformed = self.whenAddClicked
        self.buttons.add(self.addButton)

        # Apply button
        self.setButton = JButton("Apply")
        self.setButton.actionPerformed = self.whenApplyClicked
        self.buttons.add(self.setButton)

    # Buttons of Sound List window =================
    
    # define what Cancel button in Sound List Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.soundListFrame = None

    # define what Add button in Sound List Window does when clicked
    def whenAddClicked(self,event) :
        ADsettings.soundList.append(ADsound("New sound"))
        self.soundListChanged()

    # define what Apply button in Sound List Window does when clicked
    def whenApplyClicked(self,event) :
        ind = 0
        for s in ADsettings.soundList :
            s.name = self.namesSwing[ind].text
            ind += 1
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Sound list updated")
        self.soundListChanged()

    # define what Browse button in Sound List Window does when clicked
    def whenBrowseClicked(self,event) :
        ind = int(event.getActionCommand())
        fc = JFileChooser(ADsettings.soundRoot)
        fc.addChoosableFileFilter(ADsoundFilter())
        retVal = fc.showOpenDialog(None)
        if retVal != JFileChooser.APPROVE_OPTION :
            return
        file = fc.getSelectedFile()
        if file == None :
            return
        ADsettings.soundRoot = file.getParent()
        ADsettings.soundList[ind].setPath(file.getPath())
        if self.namesSwing[ind].text != "New sound" :
            ADsettings.soundList[ind].name = self.namesSwing[ind].text
        if ADsettings.soundList[ind].name == "New sound" :
            fileName = file.getName()
            upperName = fileName.upper()
            if upperName.endswith(".WAV") :
                fileName = fileName[0:len(fileName)-4]
            if upperName.endswith(".AU") :
                fileName = fileName[0:len(fileName)-3]
            ADsettings.soundList[ind].name = fileName
        self.soundListChanged()

    # define what Delete button in Sound List Window does when clicked
    def whenDeleteClicked(self,event) :
        ind = int(event.getActionCommand())
        name = ADsettings.soundList[ind].name
        if (JOptionPane.showConfirmDialog(None, "Remove sound \""
           + name
           + "\"?", "Confirmation", JOptionPane.YES_NO_OPTION) == 1) :
            return
        ADsettings.soundList.pop(ind)
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Sound \"" + name + "\" removed")
        self.soundListChanged()

    # define what Play button in Sound List Window does when clicked
    def whenPlayClicked(self,event) :
        ind = int(event.getActionCommand())
        ADsettings.soundList[ind].play()
        
    def soundListChanged(self) :
        if AutoDispatcher.soundDefaultFrame != None :
            AutoDispatcher.soundDefaultFrame.reDisplay()
        ADsettings.newSoundDic()
        AutoDispatcher.setPreferencesDirty()
        self.reDisplay()

class ADsoundFilter (FileFilter) :
    def __init__(self) :
        FileFilter.__init__(self)
    def accept(self, f) :
        if f.isDirectory() :
            return True
        name = str(f.getName()).upper()
        if (name.endswith(".WAV") or 
          name.endswith(".AU")) :
            return True;
        return False;
        
    def getDescription(self) :
        return "Sound Clip (*.wav, *.au)"

    # Sound Default Window =================

class ADsoundDefaultFrame (AdScrollFrame) :
    def __init__(self) :
        # Create Sound Default window
        # super.init
        AdScrollFrame.__init__(self, "Default Sounds", None)

    def createHeader(self):
        self.header.setLayout(GridLayout(1, 3))
        self.header.add(AutoDispatcher.centerLabel("Event"))
        self.header.add(AutoDispatcher.centerLabel("Sound"))
        self.header.add(JLabel(""))

    def createDetail(self):
        self.detail.setLayout(GridLayout(len(ADsettings.soundLabel), 3))
        self.soundsSwing = []
        sounds = ["None"]
        for s in ADsettings.soundList :
            sounds.append(s.name)
        ind = 0
        for s in ADsettings.soundLabel :
            self.detail.add(JLabel(s))
            soundSwing = JComboBox(sounds)
            soundSwing.setSelectedIndex(ADsettings.defaultSounds[ind])
            self.soundsSwing.append(soundSwing)
            self.detail.add(soundSwing)
            playButton = JButton("Play")
            playButton.setActionCommand(str(ind))
            playButton.actionPerformed = self.whenPlayClicked
            if ADsettings.defaultSounds[ind] < 1 :
                playButton.enabled = False
            self.detail.add(playButton)
            ind += 1
            
    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Apply button
        self.setButton = JButton("Apply")
        self.setButton.actionPerformed = self.whenApplyClicked
        self.buttons.add(self.setButton)

    # Buttons of Sound Default window =================
    
    # define what Cancel button in Sound List Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.soundDefaultFrame = None

    # define what Apply button in Sound Default Window does when clicked
    def whenApplyClicked(self,event) :
        ind = 0
        for s in self.soundsSwing :
            ADsettings.defaultSounds[ind] = s.getSelectedIndex()
            ind += 1
        AutoDispatcher.setPreferencesDirty()
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Default sounds changed")
        self.reDisplay()

    # define what Play button in Sound List Window does when clicked
    def whenPlayClicked(self,event) :
        ind = int(event.getActionCommand())
        ind = self.soundsSwing[ind].getSelectedIndex()-1
        if ind < 0 :
            return
        ADsettings.soundList[ind].play()

    # Locomotives Window =================
    
class ADlocosFrame (AdScrollFrame) :
    def __init__(self) :
        # Create Locomotives window
        # super.init
        AdScrollFrame.__init__(self, "Locomotives", None)

    def createHeader(self):
        self.columns = len(ADsettings.speedsList) + 8
        self.header.setLayout(GridLayout(1, self.columns))
        self.header.add(AutoDispatcher.centerLabel("Loco"))
        self.header.add(AutoDispatcher.centerLabel("Addr."))
        for s in ADsettings.speedsList :
            self.header.add(AutoDispatcher.centerLabel(s))
        self.header.add(AutoDispatcher.centerLabel("Acc."))
        self.header.add(AutoDispatcher.centerLabel("Dec."))
        self.header.add(AutoDispatcher.centerLabel("Throttle"))
        runTime = JLabel("RunTime")
        runTime.setHorizontalAlignment(JLabel.RIGHT)
        self.header.add(runTime)
        if ADsettings.units == 25.4 :
            miles = JLabel("Miles")
        else :
            miles = JLabel("Km.")
        miles.setHorizontalAlignment(JLabel.RIGHT)
        self.header.add(miles)
        self.header.add(JLabel(""))

    def createDetail(self):
        # Fill contents of scroll area
        self.locos = ADlocomotive.getNames()
        self.locos.sort()
        self.detail.setLayout(GridLayout(len(self.locos), self.columns))
        ind = 0 
        for ll in self.locos :
            l = ADlocomotive.getByName(ll)
            self.detail.add(AutoDispatcher.centerLabel(str(l.name)))
            self.detail.add(l.addressSwing)
            for s in l.speedSwing :
                self.detail.add(s)
            self.detail.add(l.accSwing)
            self.detail.add(l.decSwing)
            self.detail.add(l.currentSpeedSwing)
            l.outputMileage()
            self.detail.add(l.hoursSwing)
            self.detail.add(l.milesSwing)
            clearButton = JButton("Clear")
            clearButton.setActionCommand(str(ind))
            clearButton.actionPerformed = self.whenClearClicked
            self.detail.add(clearButton)
            ind += 1

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Apply button
        self.setButton = JButton("Apply")
        self.setButton.actionPerformed = self.whenApplyClicked
        self.buttons.add(self.setButton)

        # Remove button
        self.removeButton = JButton("Remove locos not in JMRI roster")
        self.removeButton.actionPerformed = self.whenRemoveClicked
        self.buttons.add(self.removeButton)

    # Buttons of Locomotives window =================
    
    # define what Cancel button in Locomotives Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.locosFrame = None

    # define what Remove button in Locomotives Window does when clicked
    def whenRemoveClicked(self,event) :
        locos = ADlocomotive.getList()
        newDic = {}
        removed = 0
        for l in locos :
            if l.inJmriRoster or l.usedBy != None :
                newDic[l.name] = l
            else :
                removed += 1
        if removed > 0 :
            # Ask confirmation!
            if (JOptionPane.showConfirmDialog(None, "Remove " + str(removed)
             + " locomotives not included in JMRI roster?",
             "Confirmation", JOptionPane.YES_NO_OPTION) == 1) :
                removed = 0
        if removed > 0 :
            ADlocomotive.locoIndex = newDic
            self.reDisplay()
            if AutoDispatcher.trainsFrame != None :
                AutoDispatcher.trainsFrame.reDisplay()
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND, str(removed)
              + " locomotives removed")
            AutoDispatcher.instance.saveLocomotives()
        else :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              " No locomotive removed")
        
    # define what Apply button in Locomotives Window does when clicked
    def whenApplyClicked(self,event) :
        for l in ADlocomotive.getList() :
            if not l.inJmriRoster :
                l.setAddress(int(l.addressSwing.text))
            ss = []
            for i in range(len(l.speedSwing)) :
                try :
                    value = float(l.speedSwing[i].text)
                except :
                    value = 0.5
                    l.speedSwing[i].text = "0.5"
                ss.append(value)
            l.setSpeedTable(ss)
            try :
                acc = int(l.accSwing.text)
            except :
                acc = 0
                l.accSwing.text = "0"
            try :
                dec = int(l.decSwing.text)
            except :
                dec = 0
                l.decSwing.text = "0"
            l.setMomentum(acc, dec)
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Locomotives changes applied")
        AutoDispatcher.instance.saveLocomotives()

    # define what Clear button in Locomotives Window does when clicked
    def whenClearClicked(self,event) :
        ind = int(event.getActionCommand())
        locoName = self.locos[ind]
        loco = ADlocomotive.getByName(locoName)
        loco.runningTime = 0
        loco.mileage = 0
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Mileage and timer of locomotive \"" + locoName + "\" cleared")
        AutoDispatcher.instance.saveLocomotives()
        self.reDisplay()

    # Trains Window =================
    
class ADtrainsFrame (AdScrollFrame) :
    def __init__(self) :
        # Create Trains window
        temppane = JPanel()
        temppane1 = JPanel()
        temppane1.setLayout(BoxLayout(temppane1, BoxLayout.X_AXIS))
        temppane1.add(JLabel(
          " Maximum number of trains running at once (0 = no limit) : "))
        self.maxTrainsSwing = JTextField(str(ADsettings.max_trains), 4)
        temppane1.add(self.maxTrainsSwing)
        self.applyButton = JButton("Set")
        self.applyButton.actionPerformed = self.whenApplyClicked
        temppane1.add(self.applyButton)
        temppane.add(temppane1)
        # super.init
        AdScrollFrame.__init__(self, "Trains", temppane)

    def createHeader(self):
        header1 = JPanel()
        header1.setLayout(GridLayout(1, 2))
        header1.add(AutoDispatcher.centerLabel("Train"))
        header1.add(AutoDispatcher.centerLabel("Direction"))
        header2 = JPanel()
        header2.setLayout(GridLayout(1, 2))
        header2.add(AutoDispatcher.centerLabel("Section"))
        header2.add(AutoDispatcher.centerLabel("Locomotive"))
        header3 = JPanel()
        header3.setLayout(GridLayout(1, 2))
        header3.add(AutoDispatcher.centerLabel("Reversed"))
        header3.add(AutoDispatcher.centerLabel("Dest./State"))
        header4 = JPanel()
        header4.setLayout(GridLayout(1, 2))
        header4.add(AutoDispatcher.centerLabel("Speed"))
        header4.add(JLabel(""))
        header5 = JPanel()
        header5.setLayout(GridLayout(1, 2))
        header5.add(JLabel(""))
        header5.add(JLabel(""))

        header6 = JPanel()
        header6.setLayout(GridLayout(1, 1))
        header6.add(AutoDispatcher.centerLabel("Schedule"))

        self.header.setLayout(GridLayout(1, 6))
        self.header.add(header1)
        self.header.add(header2)
        self.header.add(header3)
        self.header.add(header4)
        self.header.add(header5)
        self.header.add(header6)

    def createDetail(self):
        # Fill contents of scroll area
        locos = []
        for l in ADlocomotive.getList() :
            if l.usedBy == None :
                locos.append(l.name)
                
        trains = ADtrain.getList()
        nTrains = len(trains)

        temppane1 = JPanel()
        temppane1.setLayout(GridLayout(nTrains, 2))
        temppane2 = JPanel()
        temppane2.setLayout(GridLayout(nTrains, 2))       
        temppane3 = JPanel()
        temppane3.setLayout(GridLayout(nTrains, 2))
        temppane4 = JPanel()
        temppane4.setLayout(GridLayout(nTrains, 2))
        temppane5 = JPanel()
        temppane5.setLayout(GridLayout(nTrains, 2))
        temppane6 = JPanel()
        temppane6.setLayout(GridLayout(nTrains, 1))

        ind = 0
        for t in trains :
        
            locosList = [t.locoName]
            locosList.extend(locos)
            locosList.sort()
            t.locoRoster = JComboBox(locosList)
            t.locoRoster.setSelectedItem(t.locoName)
            enableLoco = (t.engineerSwing.getSelectedItem() != "Manual" and
              not t.running )
            t.locoRoster.setEnabled(enableLoco)
            t.reversedSwing.setEnabled(enableLoco)
        
            temppane1.add(t.nameSwing)
            temppane1.add(t.directionSwing)

            temppane2.add(t.sectionSwing)
            temppane2.add(t.locoRoster)
            
            temppane3.add(t.reversedSwing)
            temppane3.add(t.destinationSwing)

            temppane4.add(t.speedLevelSwing)
            t.detailButton.setActionCommand(str(ind))
            t.detailButton.actionPerformed = self.whenDetailClicked
            temppane4.add(t.detailButton)
            
            temppane5.add(t.changeButton)
            temppane5.add(t.deleteButton)

            temppane6.add(t.scheduleSwing)
            ind += 1
            
        self.detail.setLayout(GridLayout(1, 4))
        self.detail.add(temppane1)
        self.detail.add(temppane2)
        self.detail.add(temppane3)
        self.detail.add(temppane4)
        self.detail.add(temppane5)
        self.detail.add(temppane6)

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Add button
        self.addButton = JButton("Add")
        self.addButton.actionPerformed = self.whenAddClicked
        self.buttons.add(self.addButton)

        # Import button
        self.importButton = JButton("Import")
        self.importButton.actionPerformed = self.whenImportClicked
        self.buttons.add(self.importButton)

    # Buttons of Trains window =================
    
    # define what Cancel button in Locomotives Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.trainsFrame = None
            if AutoDispatcher.trainDetailFrame != None :
                AutoDispatcher.trainDetailFrame.dispose()
                AutoDispatcher.trainDetailFrame = None

    # define what Add button in Trains Window does when clicked
    def whenAddClicked(self,event) :
        ADtrain("New train")
        AutoDispatcher.setTrainsDirty()
        self.reDisplay()
        
    # define what Import button in Trains Window does when clicked
    def whenImportClicked(self,event) :
        if AutoDispatcher.importFrame == None :
            AutoDispatcher.importFrame = ADImportTrainFrame()
        else :
            AutoDispatcher.importFrame.reDisplay()

    # define what Detail button in Trains Window does when clicked
    def whenDetailClicked(self,event) :
        ind = int(event.getActionCommand())
        if AutoDispatcher.trainDetailFrame != None :
            AutoDispatcher.trainDetailFrame.show()
            if ADtrain.trains[ind] == AutoDispatcher.trainDetailFrame.train :
                return
            if (JOptionPane.showConfirmDialog(None, "Save details of train \""
               + AutoDispatcher.trainDetailFrame.train.getName()
               + "\" before editing details of train \""
               + ADtrain.trains[ind].getName() + "\"?", "Confirmation",
               JOptionPane.YES_NO_OPTION) != 1) :
                    AutoDispatcher.trainDetailFrame.train.whenSetClicked(None)
            AutoDispatcher.trainDetailFrame.dispose()
        AutoDispatcher.trainDetailFrame = ADtrainDetailFrame(ADtrain.trains[ind])

    # define what Apply button in Trains Window does when clicked
    def whenApplyClicked(self,event) :
        try :
            ADsettings.max_trains = int(self.maxTrainsSwing.text)
        except :
            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
              "Wrong maximum number of trains: "
              + self.maxTrainsSwing.text + " ignored")
            self.maxTrainsSwing.text = str(ADsettings.max_trains)
            return
        if ADsettings.max_trains < 0 :
            ADsettings.max_trains = 0
            self.maxTrainsSwing.text = "0"
        AutoDispatcher.setPreferencesDirty()
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Maximum number of trains set to "
          + str(ADsettings.max_trains))

    def deleteTrain(self, train):
    # Routine to delete a train, called when the DEL button on train's
    # row is clicked
        # Release sections allocated to removed train
        train.releaseSections()
        # If the train has a locomotive, release it
        if train.locomotive != None :
            train.locomotive.usedBy = None
        # remove train from table
        ADtrain.remove(train)
        AutoDispatcher.setTrainsDirty()
        # force redisplay of Trains Window contents
        self.reDisplay()
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Train \"" + train.name + "\" removed")

    # Trains Window =================
    
class ADtrainDetailFrame (AdScrollFrame) :
    def __init__(self, train) :
        # Create Train Detail window
        self.train = train
        # super.init
        AdScrollFrame.__init__(self, "Train " + train.getName()
          + " detail", None)

    def createHeader(self):
        self.header = None

    def createDetail(self):
        # Fill contents of scroll area
        engineerList = AutoDispatcher.engineers.keys()
        engineerList.sort()
        engineerList.append("Manual")

        if self.train.locomotive == None :
            rows = 7
        else :
            rows = 8

        self.detail.setLayout(BoxLayout(self.detail, BoxLayout.Y_AXIS))

        detail1 = JPanel()
        detail1.setLayout(GridLayout(rows, 2))
        
        detail1.add(JLabel("Cars are equipped with resistive wheels: "))
        detail1.add(self.train.resistiveSwing)
        detail1.add(JLabel("Actions before train departure: "))
        self.train.startActionSwing.text = self.train.startAction
        detail1.add(self.train.startActionSwing)        
        detail1.add(JLabel("Stop at beginning of sections that support this option: "))
        detail1.add(self.train.canStopAtBeginningSwing)
        if ADsettings.units == 1.0 :
            detail1.add(JLabel("Train length (mm.), including"
              " locomotive: "))
        elif ADsettings.units == 10.0 :
            detail1.add(JLabel("Train length (cm.), including"
              " locomotive: "))
        else :
            detail1.add(JLabel("Train length (inches), including"
              " locomotive: "))
        self.train.trainLengthSwing.text = str(round(self.train.trainLength
          / ADsettings.units,2))
        detail1.add(self.train.trainLengthSwing)

        detail1.add(JLabel("Sections ahead to be allocated: "))
        detail1.add(self.train.trainAllocationSwing)

        detail1.add(JLabel("Engineer: "))
        self.train.engineerSwing.removeAllItems()
        for i in engineerList:
            self.train.engineerSwing.addItem(i)
        if self.train.engineerName in engineerList :
            self.train.engineerSwing.setSelectedItem(self.train.engineerName)
        else :
            self.train.engineerSwing.setSelectedItem("Auto")
            self.train.engineerAssigned = False
        detail1.add(self.train.engineerSwing)
        if self.train.locomotive != None :
            detail1.add(JLabel(
              "Clear braking history of this train for locomotive \""
              + self.train.locoName + "\": "))
            clearLoco = JButton("Clear history for current locomotive")
            clearLoco.actionPerformed = self.whenClearLocoClicked
            detail1.add(clearLoco)
        detail1.add(JLabel(
          "Clear braking history of this train for all locomotives: "))
        clearAll = JButton("Clear history for all locomotives")
        clearAll.actionPerformed = self.whenClearAllClicked
        detail1.add(clearAll)
        self.detail.add(detail1)
        
        detail1 = JPanel()
        detail1.add(JLabel("Schedule"))
        self.detail.add(detail1)

        detail1 = JPanel()
        self.scheduleSwing = JTextArea(self.train.scheduleSwing.text, 4, 60)
        self.scheduleSwing.setLineWrap(True)
        self.scheduleSwing.setWrapStyleWord(True)
        detail1.add(JScrollPane(self.scheduleSwing))
        self.detail.add(detail1)

        detail1 = JPanel()
        detail1.add(JLabel("Train speeds correspondence"))
        self.detail.add(detail1)

        detail1 = JPanel()
        detail1.setLayout(GridLayout(len(ADsettings.speedsList)+1, 2))
        detail1.add(JLabel("Instead of"))
        detail1.add(JLabel("Use"))
        self.trainSpeedSwing = []
        ind = 1
        max = len(ADsettings.speedsList)-1
        for s in ADsettings.speedsList :
            if ind > len(self.train.trainSpeed) :
                self.train.trainSpeed.append(ind)
            detail1.add(JLabel(s))
            speedSwing = JComboBox(ADsettings.speedsList)
            i = self.train.trainSpeed[ind-1]-1
            if i > max :
                i = max
            speedSwing.setSelectedIndex(i)
            self.trainSpeedSwing.append(speedSwing)
            detail1.add(speedSwing)
            ind += 1
        self.detail.add(detail1)

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

        # Apply button
        self.buttons.add(self.train.setButton)

    # Buttons of Train Detail window =================
    
    # define what Cancel button in Train Detail Window does when clicked
    def whenCancelClicked(self,event) :
            AdScrollFrame.dispose(self)
            AutoDispatcher.trainDetailFrame = None

    # define what Clear Loco button in Train Detail Window does when clicked
    def whenClearLocoClicked(self,event) :
        self.train.clearBrakingHistory(self.train.locomotive)
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Braking history for locomotive cleared")

    # define what Clear All button in Train Detail Window does when clicked
    def whenClearAllClicked(self,event) :
        self.train.clearBrakingHistory(None)
        AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
          "Braking history for train cleared")

    # Import Trains Window =================
    
class ADImportTrainFrame (AdScrollFrame) :
    def __init__(self) :
        # Create Operations Interface window
        # Allows user to import an Operations train in AutoDispatcher
        # super.init
        AdScrollFrame.__init__(self, "Import train", JLabel("Choose train to be imported"))

    def createHeader(self):
        self.header.setLayout(GridLayout(1, 3))
        self.header.add(AutoDispatcher.centerLabel("Train name"))
        self.header.add(AutoDispatcher.centerLabel("Status"))
        self.header.add(JLabel(""))

    def createDetail(self):
        trainIds = []
        # Fill contents of scroll area
#        trainManager = TrainManager.instance()
#        trainIds = trainManager.getTrainsByNameList()
#        nTrains = len(trainIds)
#        self.detail.setLayout(GridLayout(nTrains, 3))
#        self.opTrains = []
#        for i in range(nTrains) :
#            opTrain = trainManager.getTrainById(trainIds[i])
#            self.opTrains.append(opTrain)
#            self.detail.add(JLabel(opTrain.getName()))
#            if opTrain.getBuilt() :
#                self.detail.add(AutoDispatcher.centerLabel("Built"))
#                importButton = JButton("Import")
#                importButton.setActionCommand(str(i))
#                importButton.actionPerformed = self.whenImportClicked
#                self.detail.add(importButton)
#            else :
#                self.detail.add(JLabel(""))
#                self.detail.add(JLabel(""))

    def createButtons(self) : 
        # Cancel button
        self.cancelButton.actionPerformed = self.whenCancelClicked
        self.buttons.add(self.cancelButton)

    # Buttons of Import Trains window =================
    
    # define what Cancel button in Import Trains Window does when clicked
    def whenCancelClicked(self,event) :
        AdScrollFrame.dispose(self)
        AutoDispatcher.importFrame = None
            
    # define what Import button in Import Trains Window does when clicked
    def whenImportClicked(self,event) :
        ind = 0
#        ind = int(event.getActionCommand())
#        # Get Operations train
#        opTrain = self.opTrains[ind]
#        name = opTrain.getIconName()
#        engine = opTrain.getLeadEngine()
#        if engine == None :
#            engine = ""
#        else :
#            engine = engine.getNumber()
#        # Get the list of cars to be hauled by the train
#        carManager = CarManager.instance()
#        carIds = carManager.getCarsByTrainList(opTrain)
#        cars = []
#        for carId in carIds :
#            cars.append(carManager.getCarById(carId))
#        # Get train route
#        route = opTrain.getRoute()
#        routeIds = route.getLocationsBySequenceList()
#        routeLocations = []
#        for id in routeIds :
#            routeLocations.append(route.getLocationById(id))
#        # Now build our schedule
#        departStation = True
#        schedule = ""
#        previousDirection = startDirection = ""
#        startLocation = None
#        hauled = []
#        while len(routeLocations) > 0 :
#            routeLocation = routeLocations.pop(0)
#            direction = routeLocation.getTrainDirectionString().upper()
#            location = ADlocation.getByName(routeLocation.getName())
#            manualSwitching = ""
#            # Check if any car must be picked up or dropped here
#            for car in cars :
#                source = car.getRouteLocation()
#                destination = car.getRouteDestination()
#                if source == destination :
#                    continue
#                if source == routeLocation and not car in hauled :
#                    pickUp = True
#                    # does train pass twice in this location ?
#                    if routeLocation in routeLocations :
#                        # Yes. See if car must be picked up now or next time
#                        for nextLocation in routeLocations :
#                            if nextLocation == routeLocation :
#                                pickUp = False
#                                break
#                            if nextLocation == destination :
#                                break
#                    if pickUp :
#                        hauled.append(car)
#                        manualSwitching = " $M"
#                elif destination == routeLocation and car in hauled :
#                    manualSwitching = " $M"
#                    hauled.remove(car)
#            if departStation :
#                startDirection = direction
#                manualSwitching = ""
#                startLocation = location
#                routeStart = routeLocation
#                departStation = False
#            if location != None :
#                if len(schedule) > 0 :
#                    schedule += " "
#                if previousDirection != direction :
#                    schedule += "$" + direction + " "
#                    previousDirection = direction
#                schedule += "[" + location.text + "]" + manualSwitching
#        train = ADtrain(name)
#        train.opTrain = opTrain
#        if startDirection.strip() != "" :
#            train.setDirection(startDirection)
#        if engine != None and ADlocomotive.getByName(engine) != None :
#            train.changeLocomotive(engine)
#        else :
#            AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
#              "Locomotive \"" + engine +
#              "\" not found. Manual running assumed!")
#            train.setEngineer("Manual")
#        # Find start section
#        if startLocation != None :
#            for section in startLocation.getSections() :
#                section.setManual(True)
#                if section.getAllocated() == None :
#                    train.setSection(section, True)
#                    if section.getAllocated() == train :
#                        break
#                section.setManual(False)
#            if section.getAllocated() != train :
#                AutoDispatcher.chimeLog(ADsettings.ATTENTION_SOUND,
#                "Canot place train \"" + name +
#                "\" in location " + startLocation.name
#                + " (all sections occupied)")
#            train.trainLength = round(float(routeStart.getTrainLength())
#              * 304.8 / ADsettings.scale)
#        if schedule.strip() != "" :
#            train.setSchedule(schedule)
#        train.updateSwing()
#        
#        AutoDispatcher.setTrainsDirty()
#        if AutoDispatcher.trainsFrame != None :
#            AutoDispatcher.trainsFrame.reDisplay()
#        self.whenCancelClicked(None)
            
class ADexamples1 :

# DEFAULT DATA FOR EXAMPLE PANELS ==========================
    # Used if no setting file is found
    # Settings are chosen on the basis of layout Title
    examples = {}
    exampleTrains =  {}

    examples["JavaOne remake"] = (
        '2.0 Beta', ('CCW', 'CW'), 'N1', 'W', 1000, 0, 1, 0, 1, 2, 2, 30000
        , 1, ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA', 'CYAN']
        , 1, [['E', '', 'CCW-CW', 'HEccw', 'HEcw', '', ''], ['N1', '', ''
        , 'HN1ccw', 'HN1cw', '', ''], ['N2', '', '', 'HN2ccw', 'HN2cw', ''
        , ''], ['S1', '', '', 'HS1ccw', 'HS1cw', '', ''], ['S2', '', ''
        , 'HS2ccw', 'HS2cw', '', ''], ['W', '', 'CCW-CW', 'HWccw', 'HWcw', ''
        , '']], [['Ea', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', ''
        , '', '', ''], ['Eb', '', '', '', '', 'BRAKE', '', '', '', '', '', ''
        , ''], ['Ec', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'Ed', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', '']
        , ['N1a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['N1b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['N1c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'N1d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''
        ], ['N2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['N2b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['N2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'N2d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''
        ], ['S1a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['S1b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['S1c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'S1d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''
        ], ['S2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['S2b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['S2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'S2d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''
        ], ['Wa', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['Wb', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], [
        'Wc', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Wd', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', '']], 1, 1
        , 0, 25.4, 0, 1, 0, ['Min.', 'Low', 'Med.', 'High', 'Max.'], 2, 10, 0
        , 2, 1, [['Stop', -1, -1, 0], ['Clear', -1, -1, 3]], 1, [['Single '
        'Head', [['Red'], ['Green']], [-1, -1]]], [['HXbccw', 'Single Head'
        , ['HXbccw']], ['HWcw', 'Single Head', ['HWcw']], ['HEcw', 'Single '
        'Head', ['HEcw']], ['HS1cw', 'Single Head', ['HS1cw']], ['HSWcw'
        , 'Single Head', ['HSWcw']], ['HSEcw', 'Single Head', ['HSEcw']], [
        'HNWccw', 'Single Head', ['HNWccw']], ['HSWccw', 'Single Head', [
        'HSWccw']], ['HWccw', 'Single Head', ['HWccw']], ['HN1cw', 'Single '
        'Head', ['HN1cw']], ['HNWcw', 'Single Head', ['HNWcw']], ['HXacw'
        , 'Single Head', ['HXacw']], ['HNEcw', 'Single Head', ['HNEcw']], [
        'HNEccw', 'Single Head', ['HNEccw']], ['HN1ccw', 'Single Head', [
        'HN1ccw']], ['HSEccw', 'Single Head', ['HSEccw']], ['HS2cw', 'Single '
        'Head', ['HS2cw']], ['HS1ccw', 'Single Head', ['HS1ccw']], ['HEccw'
        , 'Single Head', ['HEccw']], ['HN2ccw', 'Single Head', ['HN2ccw']], [
        'HS2ccw', 'Single Head', ['HS2ccw']], ['HXaccw', 'Single Head', [
        'HXaccw']], ['HN2cw', 'Single Head', ['HN2cw']], ['HXbcw', 'Single '
        'Head', ['HXbcw']]], 0, 0, 0, 1, 20000, 0, 1, 1, 1270, 3, 0, [['Bell'
        , 'resources/sounds/bell.wav']], [1, 1, 1, 1, 1, 1]
        , '', 12.0, 0.0, 87.0, [], 1.0)

    exampleTrains["JavaOne remake"] = [
        ['T1017', 'N1', 'CCW', 0, '(3([N1 N2] [S1 S2]) $P25)', 0, 889.0, [0, 1
        , 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1, 3
        , 1, 7, 0, 1, 0, 10, 1, 0, 3, 1, 9], '1017', 0, [], 'Auto', 'N1', [1
        , 2, 3, 4, 5], {}], ['T1019', 'S1', 'CW', 0, '(2([S1 S2] [N1 N2]) '
        '$P15)', 0, 508.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0
        , 2, 0, 1, 0, 0, 0, 1, 2, 1, 7, 0, 1, 0, 10, 1, 0, 2, 1, 9], '1019'
        , 0, [], 'Auto', 'S1', [1, 2, 3, 4, 5], {}]]

    examples["Operations Example"] = (
        '2.0 Beta', ['EAST', 'WEST'], 'Sv1', 'SvFr', 1000, 0, 2, 0, 1, 2, 2
        , 0, 1, ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA'
        , 'CYAN'], 1, [['Bf1', '', '', 'HBf1west', 'HBf1east', '', 'BFman']
        , ['Bf2', '', '', 'HBf2west', 'HBf2east', '', 'BFman'], ['BfPa', ''
        , 'EAST-WEST', 'HBfPawest', 'HBfPaeast', '', ''], ['Dv1', '', ''
        , 'HDv1west', 'HDv1east', '', 'DVman'], ['Dv2', '', '', 'HDv2west'
        , 'HDv2east', '', 'DVman'], ['DvHb', '', 'EAST-WEST', 'HDvHbwest'
        , 'HDvHbeast', '', ''], ['Fa1', '', '', 'HFa1west', 'HFa1east', ''
        , 'FAman'], ['Fa2', '', '', 'HFa2west', 'HFa2east', '', 'FAman'], [
        'FaLv1', '', 'EAST-WEST+', 'HFaLv1west', 'HFaLv1east', '', ''], [
        'FaLv2', '', 'EAST-WEST+', 'HFaLv2west', 'HFaLv2east', '', ''], [
        'FaLv3', '', 'EAST-WEST+', 'HFaLv3west', 'HFaLv3east', '', ''], [
        'Fr1', '', '', 'HFr1west', 'HFr1east', '', 'FRman'], ['Fr2', '', ''
        , 'HFr2west', 'HFr2east', '', 'FRman'], ['FrBf', '', 'EAST-WEST'
        , 'HFrBfwest', 'HFrBfeast', '', ''], ['Hb1', '', '', 'HHb1west'
        , 'HHb1east', '', 'HBman'], ['Hb2', '', '', 'HHb2west', 'HHb2east'
        , '', 'HBman'], ['HbFa', '', 'EAST-WEST', 'HHbFawest', 'HHbFaeast'
        , '', ''], ['Lv1', '', '', 'HLv1west', 'HLv1cw', '', 'LVman'], ['Lv2'
        , '', '', 'HLv2west', 'HLv2cw', '', 'LVman'], ['Pa1', '', ''
        , 'HPa1west', 'HPa1east', '', 'PAman'], ['Pa2', '', '', 'HPa2west'
        , 'HPa2east', '', 'PAman'], ['PaDv', '', 'EAST-WEST', 'HPaDvwest'
        , 'HPaDveast', '', ''], ['Sv1', '', '', 'HSv1ccw', 'HSv1east', ''
        , 'SVman'], ['Sv2', '', '', 'HSv2ccw', 'HSv2east', '', 'SVman'], [
        'SvFr', '', 'EAST-WEST', 'HSvFrwest', 'HSvFreast', '', '']], [['Bf1a'
        , 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], [
        'Bf1b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['Bf1c'
        , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Bf1d', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [
        'Bf2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['Bf2b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['Bf2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'Bf2d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', ''
        , ''], ['BfPa1', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', ''
        , '', '', ''], ['BfPa2', '', '', '', '', 'BRAKE', '', '', '', '', ''
        , '', ''], ['BfPa3', '', '', '', '', '', '', '', '', '', '', '', '']
        , ['BfPa4', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'BfPa5', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', ''
        , ''], ['Dv1a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', ''
        , '', '', ''], ['Dv1b', '', '', '', '', 'BRAKE', '', '', '', '', ''
        , '', ''], ['Dv1c', '', '', '', '', '', '', '', '', '', '', 'BRAKE'
        , ''], ['Dv1d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE'
        , '', '', ''], ['Dv2a', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['Dv2b', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', '', ''], ['Dv2c', '', '', '', '', '', '', '', ''
        , '', '', 'BRAKE', ''], ['Dv2d', '', 'ALLOCATE', '', '', '', ''
        , 'STOP', '', 'SAFE', '', '', ''], ['DvHb1', 'STOP', '', 'SAFE', ''
        , '', '', '', 'ALLOCATE', '', '', '', ''], ['DvHb2', '', '', '', ''
        , 'BRAKE', '', '', '', '', '', '', ''], ['DvHb3', '', '', '', '', ''
        , '', '', '', '', '', '', ''], ['DvHb4', '', '', '', '', '', '', ''
        , '', '', '', 'BRAKE', ''], ['DvHb5', '', 'ALLOCATE', '', '', '', ''
        , 'STOP', '', 'SAFE', '', '', ''], ['Fa1a', 'STOP', '', 'SAFE', ''
        , '', '', '', 'ALLOCATE', '', '', '', ''], ['Fa1b', '', '', '', ''
        , 'BRAKE', '', '', '', '', '', '', ''], ['Fa1c', '', '', '', '', ''
        , '', '', '', '', '', 'BRAKE', ''], ['Fa1d', '', 'ALLOCATE', '', ''
        , '', '', 'STOP', '', 'SAFE', '', '', ''], ['Fa2a', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['Fa2b', ''
        , '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['Fa2c', '', ''
        , '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Fa2d', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [
        'FaLv1', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['FaLv2', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['FaLv3', '', '', '', '', '', '', '', '', '', '', '', ''], ['FaLv4'
        , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['FaLv5', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [
        'LaFv6', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['FaLv7', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['FaLv8', '', '', '', '', '', '', '', '', '', '', '', ''], ['FaLv9'
        , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['FaLv10', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [
        'FaLv11', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['FaLv12', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''
        ], ['FaLv13', '', '', '', '', '', '', '', '', '', '', '', ''], [
        'FaLv14', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'FaLv15', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', ''
        , ''], ['Fr1a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', ''
        , '', '', ''], ['Fr1b', '', '', '', '', 'BRAKE', '', '', '', '', ''
        , '', ''], ['Fr1c', '', '', '', '', '', '', '', '', '', '', 'BRAKE'
        , ''], ['Fr1d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE'
        , '', '', ''], ['Fr2a', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['Fr2b', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', '', ''], ['Fr2c', '', '', '', '', '', '', '', ''
        , '', '', 'BRAKE', ''], ['Fr2d', '', 'ALLOCATE', '', '', '', ''
        , 'STOP', '', 'SAFE', '', '', ''], ['FrBf1', 'STOP', '', 'SAFE', ''
        , '', '', '', 'ALLOCATE', '', '', '', ''], ['FrBf2', '', '', '', ''
        , 'BRAKE', '', '', '', '', '', '', ''], ['FrBf3', '', '', '', '', ''
        , '', '', '', '', '', '', ''], ['FrBf4', '', '', '', '', '', '', ''
        , '', '', '', 'BRAKE', ''], ['FrBf5', '', 'ALLOCATE', '', '', '', ''
        , 'STOP', '', 'SAFE', '', '', ''], ['Hb1a', 'STOP', '', 'SAFE', ''
        , '', '', '', 'ALLOCATE', '', '', '', ''], ['Hb1b', '', '', '', ''
        , 'BRAKE', '', '', '', '', '', '', ''], ['Hb1c', '', '', '', '', ''
        , '', '', '', '', '', 'BRAKE', ''], ['Hb1d', '', 'ALLOCATE', '', ''
        , '', '', 'STOP', '', 'SAFE', '', '', ''], ['Hb2a', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['Hb2b', ''
        , '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['Hb2c', '', ''
        , '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Hb2d', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [
        'HbFa1', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['HbFa2', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['HbFa3', '', '', '', '', '', '', '', '', '', '', '', ''], ['HbFa4'
        , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['HbFa5', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [
        'Lv1a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['Lv1b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['Lv1c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'Lv1d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', ''
        , ''], ['Lv2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', ''
        , '', '', ''], ['Lv2b', '', '', '', '', 'BRAKE', '', '', '', '', ''
        , '', ''], ['Lv2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE'
        , ''], ['Lv2d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE'
        , '', '', ''], ['Pa1a', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['Pa1b', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', '', ''], ['Pa1c', '', '', '', '', '', '', '', ''
        , '', '', 'BRAKE', ''], ['Pa1d', '', 'ALLOCATE', '', '', '', ''
        , 'STOP', '', 'SAFE', '', '', ''], ['Pa2a', 'STOP', '', 'SAFE', ''
        , '', '', '', 'ALLOCATE', '', '', '', ''], ['Pa2b', '', '', '', ''
        , 'BRAKE', '', '', '', '', '', '', ''], ['Pa2c', '', '', '', '', ''
        , '', '', '', '', '', 'BRAKE', ''], ['Pa2d', '', 'ALLOCATE', '', ''
        , '', '', 'STOP', '', 'SAFE', '', '', ''], ['PaDv1', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['PaDv2', ''
        , '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['PaDv3', '', ''
        , '', '', '', '', '', '', '', '', '', ''], ['PaDv4', '', '', '', ''
        , '', '', '', '', '', '', 'BRAKE', ''], ['PaDv5', '', 'ALLOCATE', ''
        , '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['Sv1a', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['Sv1b', ''
        , '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['Sv1c', '', ''
        , '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Sv1d', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [
        'Sv2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['Sv2b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['Sv2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'Sv2d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', ''
        , ''], ['SvFr1', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', ''
        , '', '', ''], ['SvFr2', '', '', '', '', 'BRAKE', '', '', '', '', ''
        , '', ''], ['SvFr3', '', '', '', '', '', '', '', '', '', '', '', '']
        , ['SvFr4', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'SvFr5', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', ''
        , '']], 1, 1, 0, 25.4, 0, 0, 0, ['Min.', 'Low', 'Med.', 'High'
        , 'Max.'], 3, 10, 0, 2, 1, [['Stop', -1, -1, 0], ['Clear', -1, -1, 5]
        , ['Approach', 0, -1, 3]], 1, [['Single Head', [['Red'], ['Green'], [
        'Yellow']], [-1, -1, -1]]], [['HFaLv3west', 'Single Head', [
        'HFaLv3west']], ['HDvHbccw', 'Single Head', ['HDvHbccw']], [
        'HFr1east', 'Single Head', ['HFr1east']], ['HFaLveast', 'Single Head'
        , ['HFaLveast']], ['HHbFacw', 'Single Head', ['HHbFacw']], [
        'HFaLv1east', 'Single Head', ['HFaLv1east']], ['HSv2east', 'Single '
        'Head', ['HSv2east']], ['HDv2west', 'Single Head', ['HDv2west']], [
        'HBf1west', 'Single Head', ['HBf1west']], ['HHb1east', 'Single Head'
        , ['HHb1east']], ['HHbFawest', 'Single Head', ['HHbFawest']], [
        'HSvFrccw', 'Single Head', ['HSvFrccw']], ['HFa2east', 'Single Head'
        , ['HFa2east']], ['HFr2west', 'Single Head', ['HFr2west']], [
        'HBfPawest', 'Single Head', ['HBfPawest']], ['HSvFreast', 'Single '
        'Head', ['HSvFreast']], ['HFaLv2west', 'Single Head', ['HFaLv2west']]
        , ['HFaLvccw', 'Single Head', ['HFaLvccw']], ['HSv1east', 'Single '
        'Head', ['HSv1east']], ['HDv1west', 'Single Head', ['HDv1west']], [
        'HHb2west', 'Single Head', ['HHb2west']], ['HLv2east', 'Single Head'
        , ['HLv2east']], ['HBfPaccw', 'Single Head', ['HBfPaccw']], [
        'HFa1east', 'Single Head', ['HFa1east']], ['HFrBfccw', 'Single Head'
        , ['HFrBfccw']], ['HFr1west', 'Single Head', ['HFr1west']], [
        'HFaLvwest', 'Single Head', ['HFaLvwest']], ['HSv1ccw', 'Single Head'
        , ['HSv1ccw']], ['HPa2east', 'Single Head', ['HPa2east']], [
        'HFaLv1west', 'Single Head', ['HFaLv1west']], ['HFaLvcw', 'Single '
        'Head', ['HFaLvcw']], ['HSv2west', 'Single Head', ['HSv2west']], [
        'HSv2ccw', 'Single Head', ['HSv2ccw']], ['HFrBfeast', 'Single Head'
        , ['HFrBfeast']], ['HFrBfcw', 'Single Head', ['HFrBfcw']], [
        'HPaDveast', 'Single Head', ['HPaDveast']], ['HSvFrcw', 'Single Head'
        , ['HSvFrcw']], ['HHb1west', 'Single Head', ['HHb1west']], ['HSv2cw'
        , 'Single Head', ['HSv2cw']], ['HDvHbeast', 'Single Head', [
        'HDvHbeast']], ['HFa2west', 'Single Head', ['HFa2west']], ['HSv1cw'
        , 'Single Head', ['HSv1cw']], ['HLv1east', 'Single Head', ['HLv1east'
        ]], ['HSvFrwest', 'Single Head', ['HSvFrwest']], ['HPa1east', 'Single'
        ' Head', ['HPa1east']], ['HBf2east', 'Single Head', ['HBf2east']], [
        'HHbFaccw', 'Single Head', ['HHbFaccw']], ['HSv1west', 'Single Head'
        , ['HSv1west']], ['HBfPacw', 'Single Head', ['HBfPacw']], ['HDvHbcw'
        , 'Single Head', ['HDvHbcw']], ['HFaLv3east', 'Single Head', [
        'HFaLv3east']], ['HLv2west', 'Single Head', ['HLv2west']], [
        'HFa1west', 'Single Head', ['HFa1west']], ['HDv2east', 'Single Head'
        , ['HDv2east']], ['HPa2west', 'Single Head', ['HPa2west']], [
        'HPaDvcw', 'Single Head', ['HPaDvcw']], ['HBf1east', 'Single Head', [
        'HBf1east']], ['HFrBfwest', 'Single Head', ['HFrBfwest']], ['HLv2cw'
        , 'Single Head', ['HLv2cw']], ['HPaDvwest', 'Single Head', [
        'HPaDvwest']], ['HHbFaeast', 'Single Head', ['HHbFaeast']], ['HLv1cw'
        , 'Single Head', ['HLv1cw']], ['HFr2east', 'Single Head', ['HFr2east'
        ]], ['HBfPaeast', 'Single Head', ['HBfPaeast']], ['HDvHbwest'
        , 'Single Head', ['HDvHbwest']], ['HFaLv2east', 'Single Head', [
        'HFaLv2east']], ['HLv1west', 'Single Head', ['HLv1west']], [
        'HDv1east', 'Single Head', ['HDv1east']], ['HPa1west', 'Single Head'
        , ['HPa1west']], ['HBf2west', 'Single Head', ['HBf2west']], [
        'HHb2east', 'Single Head', ['HHb2east']], ['HPaDvccw', 'Single Head'
        , ['HPaDvccw']]], 0, 0, 0, 1, 60000, 0, 1, 1, 2032, 3, 0, [['Bell'
        , 'resources/sounds/bell.wav']], [1, 1, 1, 1, 1, 1]
        , '', 0.0, 0.0, 87.0, [['Lakeview', 'Lv1 Lv2'], ['Hillsboro',
        'Hb1 Hb2'], ['Fremont', 'Fr1 Fr2'], ['Port Arthur', 'Pa1 Pa2'],
        ['Danville', 'Dv1 Dv2'], ['Farmington', 'Fa1 Fa2'], ['Bakersfield',
        'Bf1 Bf2'], ['Susanville', 'Sv1 Sv2']], 1.0)

    exampleTrains["Operations Example"] = []

    examples["Reversing Track Example"] = (
        '2.0 Beta', ('CCW', 'CW'), 'B1', 'B4', 1000, 0, 1, 0, 1, 2, 2, 0, 1
        , ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA', 'CYAN'], 1
        , [['B1', '', '', 'HB1cw', 'HB1ccw', '', 'B1man'], ['B2', '', ''
        , 'HB2cw', 'HB2ccw', '', 'B2man'], ['B3', '', 'CCW-CW', 'HB3cw'
        , 'HB3ccw', '', ''], ['B4', '', 'CCW-CW', 'HB4cw', 'HB4ccw', '', '']
        , ['B5', '', '', 'HB5cw', 'HB5ccw', 'INVERTED', 'B5man'], ['B6', ''
        , '', 'HB6cw', 'HB6ccw', '', ''], ['B7', '', '', 'HB7cw', 'HB7ccw'
        , '', 'B7man']], [['B1a', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['B1b', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', '', ''], ['B1c', '', '', '', '', '', '', '', '', ''
        , '', 'BRAKE', ''], ['B1d', '', 'ALLOCATE', '', '', '', '', 'STOP'
        , '', 'SAFE', '', '', ''], ['B2a', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['B2b', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', '', ''], ['B2c', '', '', '', '', '', '', '', '', ''
        , '', 'BRAKE', ''], ['B2d', '', 'ALLOCATE', '', '', '', '', 'STOP'
        , '', 'SAFE', '', '', ''], ['B3a', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['B3b', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', '', ''], ['B3c', '', '', '', '', '', '', '', '', ''
        , '', '', ''], ['B3d', '', '', '', '', '', '', '', '', '', ''
        , 'BRAKE', ''], ['B3e', '', 'ALLOCATE', '', '', '', '', 'STOP', ''
        , 'SAFE', '', '', ''], ['B4a', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['B4b', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', 'BRAKE', ''], ['B4c', '', 'ALLOCATE', '', '', ''
        , '', 'STOP', '', 'SAFE', '', '', ''], ['B5a', 'STOP', '', 'SAFE', ''
        , '', '', '', 'ALLOCATE', '', '', '', ''], ['B5b', '', '', '', ''
        , 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['B5c', '', 'ALLOCATE'
        , '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B6a', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['B6b', '', ''
        , '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['B6c', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B7e'
        , 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], [
        'B7d', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B7c'
        , '', '', '', '', '', '', '', '', '', '', '', ''], ['B7b', '', '', ''
        , '', '', '', '', '', '', '', 'BRAKE', ''], ['B7a', '', 'ALLOCATE'
        , '', '', '', '', 'STOP', '', 'SAFE', '', '', '']], 1, 1, 0, 25.4, 0
        , 1, 0, ['Min.', 'Low', 'Med.', 'High', 'Max.'], 3, 10, 0, 2, 1, [[
        'Stop', -1, -1, 0], ['Clear', -1, -1, 5]], 1, [['Single Head', [[
        'Red'], ['Green']], [-1, -1]]], [['HB2ccw', 'Single Head', ['HB2ccw']
        ], ['HB4cw', 'Single Head', ['']], ['HB5ccw', 'Single Head', [
        'HB5ccw']], ['HB1ccw', 'Single Head', ['HB1ccw']], ['HB5cw', 'Single '
        'Head', ['HB5cw']], ['HB4ccw', 'Single Head', ['']], ['HB1cw'
        , 'Single Head', ['HB1cw']], ['HB6cw', 'Single Head', ['HB6cw']], [
        'HB7ccw', 'Single Head', ['HB7ccw']], ['HB2cw', 'Single Head', [
        'HB2cw']], ['HB3ccw', 'Single Head', ['']], ['HB7cw', 'Single Head'
        , ['HB7cw']], ['HB3cw', 'Single Head', ['']], ['HB6ccw', 'Single '
        'Head', ['HB6ccw']]], 0, 0, 0, 1, 60000, 0, 1, 1, 2032, 3, 0, [[
        'Bell', 'resources/sounds/bell.wav']], [1, 1, 1, 1, 1, 1]
        , '', 0.0, 0.0, 87.0, [], 1.0)

    exampleTrains["Reversing Track Example"] = [
        ['T1017', 'B1', 'CW', 0, '(2(B6 [B1 B2]) $P10)', 0, 508.0, [0, 1, 0
        , 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1, 2, 1
        , 3], '1017', 0, [], 'Auto', 'B1', [1, 2, 3, 4, 5], {}], ['T1633'
        , 'B2', 'CCW', 0, '(2(B6 [B1 B2]) $P15)', 0, 304.79999999999995, [0
        , 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1
        , 2, 0, 3], '1633', 0, [], 'Auto', 'B2', [1, 2, 3, 4, 5], {}], [
        'T3023', 'B5', 'CW', 0, '(2( B7 $P10 [B1 B2] B5) $P20)', 0, 762.0, [0
        , 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1
        , 2, 0, 3], '3023', 0, [], 'Auto', 'B5', [1, 2, 3, 4, 5], {}]]
        
class ADexamples2 :

# DEFAULT DATA FOR EXAMPLE PANELS ==========================
# Examples are divided into two different classes to avoid exceeding Jython 64K limit
    examples = {}
    exampleTrains =  {}

    examples["SignalMasts Example"] = (
        '2.0 Beta', ('CCW', 'CW'), 'B30', 'B29', 1500, 0, 2, 0, 2, 2, 2
        , 30000, 1, ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA'
        , 'CYAN'], 1, [['B01', '', '', 'HB01cw', 'HB01ccw', '', 'B01man'], [
        'B02', '', '', 'HB02cw', 'HB02ccw', '', 'B02man'], ['B03', '', ''
        , 'HB03cw', 'HB03ccw', '', 'B03man'], ['B04', '', '', 'HB04cw'
        , 'HB04ccw', '', 'B04man'], ['B05', '', '', 'HB05cw', 'HB05ccw', ''
        , 'B05man'], ['B06', '', '', 'HB06cw', 'HB06ccw', '', 'B11man'], [
        'B07', '', '', 'HB07cw', 'HB07ccw', '', 'B11man'], ['B08', '', ''
        , 'HB08cw', 'HB08ccw', '', 'B11man'], ['B09', '', '', 'HB09cw'
        , 'HB09ccw', '', 'B09man'], ['B10', '', '', 'HB10cw', 'HB10ccw', ''
        , 'B10man'], ['B11', '', 'CCW-CW', 'HB11cw', 'HB11ccw', '', 'B11man']
        , ['B12', '', 'CCW-CW', 'HB12cw', 'HB12ccw', '', 'B12man'], ['B13'
        , '', '', 'HB13cw', 'HB13ccw', '', 'B13man'], ['B14', '', ''
        , 'HB14cw', 'HB14ccw', '', 'B13man'], ['B15', '', '', 'HB15cw'
        , 'HB15ccw', '', 'B13man'], ['B16', '', '', 'HB16cw', 'HB16ccw', ''
        , 'B13man'], ['B17', '', 'CCW-CW', 'HB17cw', 'HB17ccw', '', 'B26man']
        , ['B18', '', 'CCW-CW', 'HB18cw', 'HB18ccw', '', 'B28man'], ['B19'
        , 'CCW', '', 'HB19cw', 'HB19ccw', '', 'B19man'], ['B20', 'CW', ''
        , 'HB20cw', 'HB20ccw', '', 'B20man'], ['B21', 'CW', '', 'HB21cw'
        , 'HB21ccw', '', ''], ['B22', 'CCW', '', 'HB22cw', 'HB22ccw', '', '']
        , ['B23', 'CW', '', 'HB23cw', 'HB23ccw', '', ''], ['B24', '', ''
        , 'HB24cw', 'HB24ccw', '', ''], ['B25', '', 'CW+', 'HB25cw'
        , 'HB25ccw', '', ''], ['B26', 'CCW', '', 'HB26cw', 'HB26ccw', ''
        , 'B26man'], ['B27', '', 'CCW', 'HB27cw', 'HB27ccw', '', 'B27man'], [
        'B28', '', 'CCW+', 'HB28cw', 'HB28ccw', '', 'B28man'], ['B29', ''
        , 'CCW-CW+', 'HB29cw', 'HB29ccw', '', 'B29man'], ['B30', '', ''
        , 'HB30cw', 'HB30ccw', '', 'B30man'], ['B31', '', '', 'HB31cw'
        , 'HB31ccw', '', 'B31man'], ['B32', '', '', 'HB32cw', 'HB32ccw', ''
        , 'B32man'], ['B33', 'CCW', 'CCW', 'HB33cw', 'HB33ccw', '', ''], [
        'B34', 'CW', 'CW', 'HB34cw', 'HB34ccw', '', '']], [['B1a', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '30 MPH', '', ''], ['B1b'
        , '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B1c', ''
        , '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['B1d', ''
        , 'ALLOCATE', '', '30 MPH', '', '', 'STOP', '', 'SAFE', '', '', '']
        , ['B2a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '30 '
        'MPH', '', ''], ['B2b', '', '', '', '', 'BRAKE', '', '', '', '', ''
        , '', ''], ['B2c', '', '', '', '', '', '', '', '', '', '', 'BRAKE'
        , ''], ['B2d', '', 'ALLOCATE', '', '30 MPH', '', '', 'STOP', ''
        , 'SAFE', '', '', ''], ['B3a', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '30 MPH', '', ''], ['B3b', '', '', '', '', 'BRAKE'
        , '', '', '', '', '', '', ''], ['B3c', '', '', '', '', '', '', '', ''
        , '', '', 'BRAKE', ''], ['B3d', '', 'ALLOCATE', '', '30 MPH', '', ''
        , 'STOP', '', 'SAFE', '', '', ''], ['B4a', 'STOP', '', 'SAFE', '', ''
        , '', '', 'ALLOCATE', '', '30 MPH', '', ''], ['B4b', '', '', '', ''
        , 'BRAKE', '', '', '', '', '', '', ''], ['B4c', '', '', '', '', ''
        , '', '', '', '', '', 'BRAKE', ''], ['B4d', '', 'ALLOCATE', '', '30 '
        'MPH', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B5a', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '30 MPH', '', ''], ['B5b'
        , '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B5c', ''
        , '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['B5d', ''
        , 'ALLOCATE', '', '30 MPH', '', '', 'STOP', '', 'SAFE', '', '', '']
        , ['B6a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', ''
        , 'BRAKE', ''], ['B6b', '', '', '', '', 'NO', '', '', '', '', ''
        , 'NO', ''], ['B6c', '', 'ALLOCATE', '', '', 'BRAKE', '', 'STOP', ''
        , 'SAFE', '', '', ''], ['B7a', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', 'BRAKE', ''], ['B7b', '', '', '', '', 'NO', ''
        , '', '', '', '', 'NO', ''], ['B7c', '', 'ALLOCATE', '', '', 'BRAKE'
        , '', 'STOP', '', 'SAFE', '', '', ''], ['B8a', 'STOP', '', 'SAFE', ''
        , '', '', '', 'ALLOCATE', '', '', 'BRAKE', ''], ['B8b', '', '', ''
        , '', 'NO', '', '', '', '', '', 'NO', ''], ['B8c', '', 'ALLOCATE', ''
        , '', 'BRAKE', '', 'STOP', '', 'SAFE', '', '', ''], ['B9b', 'STOP'
        , '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', 'BRAKE', ''], [
        'B9a', '', 'ALLOCATE', '', '', 'BRAKE', '', 'STOP', '', 'SAFE', ''
        , '', ''], ['B10a', 'NO', '', 'NO', '', '', '', '', 'NO', '', '', ''
        , ''], ['B10b', 'STOP', '', 'SAFE', '', 'NO', '', '', 'ALLOCATE', ''
        , '', 'BRAKE', ''], ['B10c', '', 'ALLOCATE', '', '', 'BRAKE', ''
        , 'STOP', '', 'SAFE', '', '', ''], ['B11a', 'STOP', '', 'NO', '', ''
        , '', '', 'ALLOCATE', '', '', 'BRAKE', ''], ['B11b', '', 'ALLOCATE'
        , '', '', 'BRAKE', '', 'STOP', '', 'NO', '', '', ''], ['B11c', '', ''
        , '', '', '', '', '', '', '', '', '', ''], ['B12a', '', '', '', ''
        , '', '', '', '', '', '', '', ''], ['B12b', 'STOP', '', 'NO', '', ''
        , '', '', 'ALLOCATE', '', '', 'BRAKE', ''], ['B12c', '', 'ALLOCATE'
        , '', '', 'BRAKE', '', 'STOP', '', 'NO', '', '', ''], ['B13a', 'STOP'
        , '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', 'BRAKE', ''], [
        'B13b', '', 'ALLOCATE', '', '', 'BRAKE', '', 'STOP', '', 'SAFE', ''
        , '', ''], ['B14a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE'
        , '', '', 'BRAKE', ''], ['B14b', '', 'ALLOCATE', '', '', 'BRAKE', ''
        , 'STOP', '', 'SAFE', '', '', ''], ['B15a', 'STOP', '', 'SAFE', ''
        , '', '', '', 'ALLOCATE', '', '', 'BRAKE', ''], ['B15b', ''
        , 'ALLOCATE', '', '', 'BRAKE', '', 'STOP', '', 'SAFE', '', '', ''], [
        'B16a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', ''
        , 'BRAKE', ''], ['B16b', '', 'ALLOCATE', '', '', 'BRAKE', '', 'STOP'
        , '', 'SAFE', '', '', ''], ['B17a', 'STOP', 'ALLOCATE', '', '30 MPH'
        , '', '', 'STOP', 'ALLOCATE', '', '30 MPH', '', ''], ['B18a', 'STOP'
        , 'ALLOCATE', '', '30 MPH', 'BRAKE', '', 'STOP', 'ALLOCATE', '', '30 '
        'MPH', 'BRAKE', ''], ['B19a', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['B19b', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', 'BRAKE', ''], ['B19c', '', 'ALLOCATE', '', '', ''
        , '', 'STOP', '', 'SAFE', '40 MPH', '', ''], ['B20a', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['B20b', ''
        , '', '', '', 'BRAKE', '', '', '', '', '40 MPH', '', ''], ['B20c', ''
        , '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['B20d', ''
        , 'ALLOCATE', '', '40 MPH', '', '', 'STOP', '', 'SAFE', '', '', '']
        , ['B21a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['B21b', '', '', '', '', 'BRAKE', '', '', '', '', '', 'BRAKE'
        , ''], ['B21c', '', 'ALLOCATE', '', '50 MPH', '', '', 'STOP', ''
        , 'SAFE', '', '', ''], ['B22a', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['B22b', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', 'BRAKE', ''], ['B22c', '', 'ALLOCATE', '', '', ''
        , '', 'STOP', '', 'SAFE', '50 MPH', '', ''], ['B23a', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['B23b', ''
        , '', '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['B23c', ''
        , 'ALLOCATE', '', '50 MPH', '', '', 'STOP', '', 'SAFE', '', '', '']
        , ['B24a', 'STOP', '', 'NO', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['B24b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['B24c', '', '', 'SAFE', '', '', '', '', '', 'SAFE', '40 MPH', ''
        , ''], ['B24d', '', '', '', '40 MPH', '', '', '', '', '', '', 'BRAKE'
        , ''], ['B24e', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'NO', ''
        , '', ''], ['B25a', 'STOP', '', 'NO', '', '', '', '', 'NO', '', ''
        , '', ''], ['B25b', '', '', '', '', 'BRAKE', '', '', '', '', '', ''
        , ''], ['B25c', '', 'ALLOCATE', 'SAFE', 'Max.', '', '', ''
        , 'ALLOCATE', 'SAFE', '40 MPH', '', ''], ['B25d', '', '', '', '', ''
        , '', '', '', '', '', 'BRAKE', ''], ['B25e', '', 'NO', '', '40 MPH'
        , '', '', 'STOP', '', 'NO', '', '', ''], ['B26d', 'STOP', '', 'SAFE'
        , '', '', '', '', 'ALLOCATE', '', '30 MPH', '', ''], ['B26c', '', ''
        , '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B26b', '', '', ''
        , '', '', '', '', '', '', '', 'BRAKE', ''], ['B26a', '', 'ALLOCATE'
        , '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B27d', 'STOP'
        , '', 'SAFE', '', '', '', '', 'ALLOCATE', '', 'Max.', '', ''], [
        'B27c', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B27b'
        , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['B27a', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], [
        'B28d', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['B28c', '', '', '', '', 'BRAKE', '', '', '', '', '', '', '']
        , ['B28b', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], [
        'B28a', '', 'ALLOCATE', '', 'Max.', '', '', 'STOP', '', 'SAFE', ''
        , '', ''], ['B29a', 'STOP', '', 'NO', '40 MPH', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['B29b', '', '', 'SAFE', '', 'BRAKE'
        , '', '', '', '', '', '', ''], ['B29c', '', '', '', '', '', '', ''
        , '', 'SAFE', '', 'BRAKE', ''], ['B29d', '', 'ALLOCATE', '', '', ''
        , '', 'STOP', '', 'NO', '', '', ''], ['B30a', 'STOP', '', 'SAFE', ''
        , '', '', '', 'ALLOCATE', '', '30 MPH', '', ''], ['B30b', '', '', ''
        , '', 'BRAKE', '', '', '', '', '', '', ''], ['B30c', '', '', '', ''
        , '', '', '', '', '', '', 'BRAKE', ''], ['B30d', '', 'ALLOCATE', ''
        , '30 MPH', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B31a', 'STOP'
        , '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '30 MPH', '', ''], [
        'B31b', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], ['B31c'
        , '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['B31d', ''
        , 'ALLOCATE', '', '30 MPH', '', '', 'STOP', '', 'SAFE', '', '', '']
        , ['B32a', 'NO', '', 'NO', '', '', '', '', 'NO', '', '', '', ''], [
        'B32b', 'STOP', '', 'SAFE', '', 'NO', '', '', 'ALLOCATE', '', ''
        , 'BRAKE', ''], ['B32c', '', 'ALLOCATE', '', '', 'BRAKE', '', 'STOP'
        , '', 'SAFE', '', '', ''], ['B33a', 'STOP', 'ALLOCATE', '', '', ''
        , '', 'STOP', 'ALLOCATE', '', '50 MPH', '', ''], ['B34a', 'STOP'
        , 'ALLOCATE', '', '40 MPH', '', '', 'STOP', 'ALLOCATE', '', '', ''
        , '']], 1, 1, 0, 1.0, 0, 1, 0, ['Min.', '30 MPH', '40 MPH', '50 MPH'
        , 'Max.'], 2, 10, 0, 2, 2, [['Stop', -1, -1, 0], ['Clear', -1, -1, 5]
        , ['Diverging Clear Limited', -1, 1, 3], ['Approach', 0, 0, 2], [
        'Diverging Approach', 0, 1, 2], ['Approach Limited', 2, 0, 3], [
        'Diverging Approach Limited', 2, 1, 4]], 1, [['Single Head', [['Red']
        , ['Green'], ['Yellow'], ['Flash-Green'], ['Flash-Yellow'], ['Green']
        , ['Green']], [-1, -1, -1, -1, -1, -1, -1]], ['Double head', [['Red'
        , 'Red'], ['Green', 'Red'], ['Red', 'Flash-Green'], ['Yellow', 'Red']
        , ['Red', 'Yellow'], ['Flash-Yellow', 'Red'], ['Red', 'Flash-Yellow']
        ], [-1, -1, -1, -1, -1, -1, -1]]], [['HB03ccw', 'Double head', [
        'HB03ccw', 'HB03ccwL']], ['HB24cw', 'Double head', ['HB24cw'
        , 'HB24cwL']], ['HB19cw', 'Single Head', ['']], ['HB22ccw', 'Double '
        'head', ['HB22ccw', 'HB22ccwL']], ['HB08ccw', 'Single Head', ['']], [
        'HB10cw', 'Single Head', ['']], ['HB05cw', 'Double head', ['HB05cw'
        , 'HB05cwL']], ['HB27ccw', 'Double head', ['HB27ccw', 'HB27ccwL']], [
        'HB23cw', 'Double head', ['HB23cw', 'HB23cwL']], ['HB14ccw', 'Double '
        'head', ['HB14ccw', 'HB14ccwL']], ['HB18cw', 'Single Head', ['']], [
        'HB33ccw', 'Single Head', ['']], ['HB01ccw', 'Double head', [
        'HB01ccw', 'HB01ccwL']], ['HB04cw', 'Double head', ['HB04cw'
        , 'HB04cwL']], ['HB19ccw', 'Double head', ['HB19ccw', 'HB19ccwL']], [
        'HB20ccw', 'Single Head', ['']], ['HB06ccw', 'Single Head', ['']], [
        'HB22cw', 'Single Head', ['']], ['HB17cw', 'Single Head', ['']], [
        'HB25ccw', 'Double head', ['HB25ccw', 'HB25ccwL']], ['HB03cw'
        , 'Double head', ['HB03cw', 'HB03cwL']], ['HB12ccw', 'Single Head', [
        '']], ['HB21cw', 'Double head', ['HB21cw', 'HB21cwL']], ['HB31ccw'
        , 'Double head', ['HB31ccw', 'HB31ccwL']], ['HB16cw', 'Single Head'
        , ['']], ['HB17ccw', 'Single Head', ['']], ['HB04ccw', 'Double head'
        , ['HB04ccw', 'HB04ccwL']], ['HB02cw', 'Single Head', ['']], [
        'HB34cw', 'Single Head', ['']], ['HB29cw', 'Double head', ['HB29cw'
        , 'HB29cwL']], ['HB23ccw', 'Single Head', ['']], ['HB09ccw', 'Single '
        'Head', ['']], ['HB20cw', 'Double head', ['HB20cw', 'HB20cwL']], [
        'HB15cw', 'Single Head', ['']], ['HB10ccw', 'Double head', ['HB10ccw'
        , 'HB10ccwL']], ['HB28ccw', 'Single Head', ['']], ['HB33cw', 'Single '
        'Head', ['']], ['HB01cw', 'Single Head', ['']], ['HB28cw', 'Double '
        'head', ['HB28cw', 'HB28cwL']], ['HB15ccw', 'Double head', ['HB15ccw'
        , 'HB15ccwL']], ['HB02ccw', 'Double head', ['HB02ccw', 'HB02ccwL']]
        , ['HB34ccw', 'Single Head', ['']], ['HB14cw', 'Single Head', ['']]
        , ['HB09cw', 'Double head', ['HB09cw', 'HB09cwL']], ['HB21ccw'
        , 'Single Head', ['']], ['HB07ccw', 'Single Head', ['']], ['HB32cw'
        , 'Single Head', ['']], ['HB27cw', 'Double head', ['HB27cw'
        , 'HB27cwL']], ['HB26ccw', 'Double head', ['HB26ccw', 'HB26ccwL']], [
        'HB13cw', 'Single Head', ['']], ['HB08cw', 'Double head', ['HB08cw'
        , 'HB08cwL']], ['HB13ccw', 'Double head', ['HB13ccw', 'HB13ccwL']], [
        'HB32ccw', 'Double head', ['HB32ccw', 'HB32ccwL']], ['HB31cw'
        , 'Double head', ['HB31cw', 'HB31cwL']], ['HB26cw', 'Single Head', [
        '']], ['HB18ccw', 'Single Head', ['']], ['HB05ccw', 'Double head', [
        'HB05ccw', 'HB05ccwL']], ['HB12cw', 'Single Head', ['']], ['HB07cw'
        , 'Double head', ['HB07cw', 'HB07cwL']], ['HB24ccw', 'Double head', [
        'HB24ccw', 'HB24ccwL']], ['HB30cw', 'Double head', ['HB30cw'
        , 'HB30cwL']], ['HB25cw', 'Double head', ['HB25cw', 'HB25cwL']], [
        'HB11ccw', 'Single Head', ['']], ['HB29ccw', 'Double head', [
        'HB29ccw', 'HB29ccwL']], ['HB30ccw', 'Double head', ['HB30ccw'
        , 'HB30ccwL']], ['HB11cw', 'Single Head', ['']], ['HB16ccw', 'Double '
        'head', ['HB16ccw', 'HB16ccwL']], ['HB06cw', 'Double head', ['HB06cw'
        , 'HB06cwL']]], 0, 1, 1, 1, 60000, 1, 1, 1, 2000, 3, 0, [['Bell'
        , 'resources/sounds/bell.wav']], [1, 1, 1, 1, 1, 1]
        , '', 0.0, 0.0, 87.0, [], 1.0)

    exampleTrains["SignalMasts Example"] = [
        ['T1017', 'B01', 'CCW', 0, '(3(B24 [B01 B02]) $P30 [B30 B31] [B01 '
        'B02])', 0, 650.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0
        , 10, 0, 1, 0, 13, 1, 0, 0, 0, 12], '1017', 0, [], 'Auto', 'B01', [1
        , 2, 2, 3, 4], {}], ['T1019', 'B02', 'CCW', 0, '(3(B24 [B02 B01]) '
        '$P30) ', 0, 680.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0
        , 0, 2, 0, 1, 0, 0, 0, 1, 3, 0, 3], '1019', 0, [], 'Auto', 'B02', [1
        , 2, 3, 4, 5], {}], ['T1633', 'B05', 'CW', 0, '(2(B20 $H:HB26ccw B24 '
        '$R:HB26ccw [B05 B04 B03]) $P15 [B31 B30] $P10 [B05 B04 B03])', 0
        , 650.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 14, 0, 1
        , 0, 17, 1, 0, 0, 0, 16], '1633', 0, [], 'Auto', 'B05', [1, 2, 3, 4
        , 5], {}], ['T1641', 'B30', 'CCW', 0, '([B02 B03 B01] B24 2([B03 B02 '
        'B01] B30) $P10)', 0, 410.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0
        , 0, 1, 0, 0, 2, 0, 1, 0, 6, 1, 0, 0, 0, 4], '1641', 0, [], 'Auto'
        , 'B30', [1, 2, 3, 4, 5], {}], ['T3023', 'B31', 'CW', 0, '(2([B05 B04'
        ' B03] B31) $P25 [B05 B04 B03] B24)', 0, 490.0, [0, 1, 0, 0, 0, 0, 0
        , 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1, 2, 1, 3, 0, 1, 0
        , 7, 1, 0, 2, 1, 5], '3023', 0, [], 'Auto', 'B31', [1, 2, 3, 4, 5], {
        }], ['T3029', 'B06', 'CW', 0, '($SWOFF $CW 2(B24 [B04 B03]) [B31 B30]'
        ' [B04 B03] B20 $SWON $H:HB06cw $R:HB07cw $CCW B06 $D3 $OFF:F0)', 0
        , 810.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 4, 0, 1
        , 0, 0, 0, 1, 2, 0, 6, 0, 1, 0, 9, 1, 0, 2, 0, 8], '3029', 0, []
        , 'Auto', 'B06', [1, 2, 3, 4, 5], {}], ['T4802', 'B07', 'CW', 0
        , '($SWOFF $CW 3(B24 [B05 B04 B03]) B20 $SWON $H:HB07cw $R:HB06cw '
        '$CCW B07 $D3 $OFF:F0)', 0, 250.0, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1
        , 0, 0, 0, 1, 0, 0, 4, 0, 1, 0, 0, 0, 1, 3, 0, 6, 0, 1, 0, 10, 1, 0
        , 3, 0, 8], '4802', 1, [], 'Auto', 'B07', [1, 2, 3, 4, 5], {}], [
        'T4805', 'B32', 'CCW', 0, '($SWOFF $CCW [B02 B03 B01] B24 [B02 B03 '
        'B01] [B31 B30] $SWON $H:HB32ccw B29 $CW B32 $D3 $OFF:F0)', 0, 380.0
        , [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 4, 0, 1, 0, 8
        , 1, 0, 0, 1, 6], '4805', 0, [], 'Auto', 'B32', [1, 2, 3, 4, 5], {}]]

    examples["Xing Example"] = (
        '2.0 Beta', ('CCW', 'CW'), 'N1', 'W', 1000, 0, 1, 0, 1, 2, 2, 30000
        , 1, ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA', 'CYAN']
        , 1, [['E', '', 'CCW-CW', 'HEcw', 'HEccw', '', ''], ['N1', '', ''
        , 'HN1cw', 'HN1ccw', '', ''], ['N2', '', '', 'HN2cw', 'HN2ccw', ''
        , ''], ['NE', '', '', 'HNEcw', 'HNEccw', '', ''], ['NW', '', ''
        , 'HNWcw', 'HNWccw', '', ''], ['S1', '', '', 'HS1cw', 'HS1ccw', ''
        , ''], ['S2', '', '', 'HS2cw', 'HS2ccw', '', ''], ['SE', '', ''
        , 'HSEcw', 'HSEccw', '', ''], ['SW', '', '', 'HSWcw', 'HSWccw'
        , 'INVERTED', ''], ['W', '', 'CCW-CW', 'HWcw', 'HWccw', '', ''], [
        'Xa', '', 'CCW-CW', 'HXacw', 'HXaccw', '', ''], ['Xb', '', 'CCW-CW'
        , 'HXbcw', 'HXbccw', '', '']], [['Ed', 'STOP', '', 'SAFE', '', '', ''
        , '', 'ALLOCATE', '', '', '', ''], ['Ec', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', '', ''], ['Eb', '', '', '', '', '', '', '', '', ''
        , '', 'BRAKE', ''], ['Ea', '', 'ALLOCATE', '', '', '', '', 'STOP', ''
        , 'SAFE', '', '', ''], ['N1d', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['N1c', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', '', ''], ['N1b', '', '', '', '', '', '', '', '', ''
        , '', 'BRAKE', ''], ['N1a', '', 'ALLOCATE', '', '', '', '', 'STOP'
        , '', 'SAFE', '', '', ''], ['N2d', 'STOP', '', 'SAFE', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['N2c', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', '', ''], ['N2b', '', '', '', '', '', '', '', '', ''
        , '', 'BRAKE', ''], ['N2a', '', 'ALLOCATE', '', '', '', '', 'STOP'
        , '', 'SAFE', '', '', ''], ['NEc', 'STOP', '', 'NO', '', '', '', ''
        , 'ALLOCATE', '', '', '', ''], ['NEb', '', '', '', '', 'BRAKE', ''
        , '', '', '', '', 'BRAKE', ''], ['NEa', '', 'ALLOCATE', '', '', ''
        , '', 'STOP', '', 'NO', '', '', ''], ['NWc', 'STOP', '', 'NO', '', ''
        , '', '', 'ALLOCATE', '', '', '', ''], ['NWb', '', '', '', ''
        , 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['NWa', '', 'ALLOCATE'
        , '', '', '', '', 'STOP', '', 'NO', '', '', ''], ['S1d', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['S1c', '', ''
        , '', '', 'BRAKE', '', '', '', '', '', '', ''], ['S1b', '', '', ''
        , '', '', '', '', '', '', '', 'BRAKE', ''], ['S1a', '', 'ALLOCATE'
        , '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['S2d', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['S2c', '', ''
        , '', '', 'BRAKE', '', '', '', '', '', '', ''], ['S2b', '', '', ''
        , '', '', '', '', '', '', '', 'BRAKE', ''], ['S2a', '', 'ALLOCATE'
        , '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['SEa', 'STOP', ''
        , 'NO', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['SEb', '', ''
        , '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['SEc', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'NO', '', '', ''], ['SWa'
        , 'STOP', '', 'NO', '', '', '', '', 'ALLOCATE', '', '', '', ''], [
        'SWb', '', '', '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], [
        'SWc', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'NO', '', '', '']
        , ['Wd', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['Wc', '', '', '', '', 'BRAKE', '', '', '', '', '', '', ''], [
        'Wb', '', '', '', '', '', '', '', '', '', '', 'BRAKE', ''], ['Wa', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['Xa'
        , 'STOP', 'ALLOCATE', '', '', '', '', 'STOP', 'ALLOCATE', '', '', ''
        , ''], ['Xb', 'STOP', 'ALLOCATE', '', '', '', '', 'STOP', 'ALLOCATE'
        , '', '', '', '']], 1, 1, 0, 25.4, 0, 1, 0, ['Min.', 'Low', 'Med.'
        , 'High', 'Max.'], 3, 10, 0, 2, 1, [['Stop', -1, -1, 0], ['Clear', -1
        , -1, 5]], 1, [['Single Head', [['Red'], ['Green']], [-1, -1]]], [[
        'HXbccw', 'Single Head', ['HXbccw']], ['HWcw', 'Single Head', ['HWcw'
        ]], ['HEcw', 'Single Head', ['HEcw']], ['HS1cw', 'Single Head', [
        'HS1cw']], ['HSWcw', 'Single Head', ['HSWcw']], ['HSEcw', 'Single '
        'Head', ['HSEcw']], ['HNWccw', 'Single Head', ['HNWccw']], ['HSWccw'
        , 'Single Head', ['HSWccw']], ['HWccw', 'Single Head', ['HWccw']], [
        'HN1cw', 'Single Head', ['HN1cw']], ['HNWcw', 'Single Head', ['HNWcw'
        ]], ['HXacw', 'Single Head', ['HXacw']], ['HNEcw', 'Single Head', [
        'HNEcw']], ['HNEccw', 'Single Head', ['HNEccw']], ['HN1ccw', 'Single '
        'Head', ['HN1ccw']], ['HSEccw', 'Single Head', ['HSEccw']], ['HS2cw'
        , 'Single Head', ['HS2cw']], ['HEccw', 'Single Head', ['HEccw']], [
        'HS1ccw', 'Single Head', ['HS1ccw']], ['HN2ccw', 'Single Head', [
        'HN2ccw']], ['HS2ccw', 'Single Head', ['HS2ccw']], ['HN2cw', 'Single '
        'Head', ['HN2cw']], ['HXaccw', 'Single Head', ['HXaccw']], ['HXbcw'
        , 'Single Head', ['HXbcw']]], 0, 0, 0, 1, 20000, 0, 1, 1, 1778, 3, 0
        , [['Bell', 'resources/sounds/bell.wav']], [1, 1, 1, 1, 1, 1]
        , '', 12.0, 0.0, 87.0, [])

    exampleTrains["Xing Example"] = [
        ['T1017', 'S1', 'CCW', 0, '([N1 N2] [S1 S2] $P10)', 0, 889.0, [0, 1, 0
        , 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 5, 1, 0, 0, 0
        , 4], '1017', 0, [], 'Auto', 'S1', [1, 2, 3, 4, 5], {}], ['T1019', 'N1'
        , 'CW', 0, '([S1 S2] $P15 [N1 N2])', 0, 1143.0, [0, 1, 0, 0, 0, 0, 0
        , 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 5, 1, 0, 0, 0, 4], '1019'
        , 0, [], 'Auto', 'N1', [1, 2, 3, 4, 5], {}], ['T1633', 'NE', 'CW', 0
        , '(SE NW [S1 S2] [N1 N2] SW NE [S1 S2] [N1 N2] )', 0
        , 355.6, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0
        , 0, 14, 0, 1, 0, 17, 1, 0, 0, 0, 16], '1633', 0, [], 'Auto', 'NE', [
        1, 2, 3, 4, 5], {}], ['T1641', 'SW', 'CW', 0, '(SW SE)', 0, 203.2, [0
        , 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 3], '1641', 0, []
        , 'Auto', 'SW', [1, 2, 3, 4, 5], {}]]

    examples["Xover Example"] = (
        '2.0 Beta', ('CCW', 'CW'), 'B1', 'B6', 1000, 0, 1, 1, 1, 2, 2, 30000
        , 1, ['BLACK', 'BLUE', 'RED', 'YELLOW', 'ORANGE', 'MAGENTA', 'CYAN']
        , 1, [['B1', '', '', 'HB1ccw', 'HB1cw', '', ''], ['B2', '', ''
        , 'HB2ccw', 'HB2cw', '', ''], ['B3', '', '', 'HB3ccw', 'HB3cw', ''
        , ''], ['B4', '', '', 'HB4ccw', 'HB4cw', '', ''], ['B5', ''
        , 'CCW-CW+', 'HB5ccw', 'HB5cw', '', ''], ['B6', '', 'CCW-CW+'
        , 'HB6ccw', 'HB6cw', '', '']], [['B1a', 'STOP', '', 'SAFE', '', ''
        , '', '', 'ALLOCATE', '', '', '', ''], ['B1b', '', '', '', ''
        , 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['B1c', '', 'ALLOCATE'
        , '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B2a', 'STOP', ''
        , 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], ['B2b', '', ''
        , '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], ['B2c', ''
        , 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''], ['B3a'
        , 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', '', ''], [
        'B3b', '', '', '', '', 'BRAKE', '', '', '', '', '', 'BRAKE', ''], [
        'B3c', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', '', '', ''
        ], ['B4a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', '', '', ''
        , ''], ['B4b', '', '', '', '', 'BRAKE', '', '', '', '', '', 'BRAKE'
        , ''], ['B4c', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', ''
        , '', ''], ['B5a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', ''
        , '', '', ''], ['B5b', '', '', '', '', 'BRAKE', '', '', '', '', ''
        , '', ''], ['B5c', '', '', '', '', '', '', '', '', '', '', 'BRAKE'
        , ''], ['B5d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', ''
        , '', ''], ['B6a', 'STOP', '', 'SAFE', '', '', '', '', 'ALLOCATE', ''
        , '', '', ''], ['B6b', '', '', '', '', 'BRAKE', '', '', '', '', ''
        , '', ''], ['B6c', '', '', '', '', '', '', '', '', '', '', 'BRAKE'
        , ''], ['B6d', '', 'ALLOCATE', '', '', '', '', 'STOP', '', 'SAFE', ''
        , '', '']], 1, 1, 0, 25.4, 0, 1, 0, ['Min.', 'Low', 'Med.', 'High'
        , 'Max.'], 3, 10, 0, 2, 1, [['Stop', -1, -1, 0], ['Clear', -1, -1, 5]
        ], 1, [['Single Head', [['Red'], ['Green']], [-1, -1]]], [['HB2ccw'
        , 'Single Head', ['HB2ccw']], ['HB4cw', 'Single Head', ['HB4cw']], [
        'HB5ccw', 'Single Head', ['HB5ccw']], ['HB1ccw', 'Single Head', [
        'HB1ccw']], ['HB5cw', 'Single Head', ['HB5cw']], ['HB4ccw', 'Single '
        'Head', ['HB4ccw']], ['HB1cw', 'Single Head', ['HB1cw']], ['HB6cw'
        , 'Single Head', ['HB6cw']], ['HB3ccw', 'Single Head', ['HB3ccw']], [
        'HB2cw', 'Single Head', ['HB2cw']], ['HB3cw', 'Single Head', ['HB3cw'
        ]], ['HB6ccw', 'Single Head', ['HB6ccw']]], 0, 0, 0, 1, 60000, 0, 1
        , 1, 2032, 3, 0, [['Bell', 'resources/sounds/bell.wav']], [1, 1, 1, 1
        , 1, 1], '', 0.0, 0.0, 87.0, [], 1.0)

    exampleTrains["Xover Example"] = [
        ['T1017', 'B1', 'CCW', 0, '(2([B3 B4] [B1 B2]) $P15)', 0
        , 304.79999999999995, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0
        , 0, 2, 0, 1, 0, 0, 0, 1, 2, 1, 3, 0, 1, 0, 6, 1, 0, 2, 1, 5], '1017'
        , 0, [], 'Auto', 'B1', [1, 2, 3, 4, 5], {}], ['T1019', 'B2', 'CW', 0
        , '(2([B3 B4] [B1 B2]) $P20)', 0, 609.5999999999999, [0, 1, 0, 0, 0
        , 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 0, 0, 0, 1, 2, 1, 3, 0
        , 1, 0, 6, 1, 0, 2, 1, 5], '1019', 0, [], 'Auto', 'B2', [1, 2, 3, 4
        , 5], {}]]
                        
# MAIN PROGRAMM ==============
    
a = AutoDispatcher()
 
a.setup()
