LogixNG Tutorial - Chapter 21

Adding a new action or expression

See the package jmri.jmrix.loconet.logixng for the classes mentioned in this part.

How do we add a new action or expression type to LogixNG? Lets assume we want to have an expression that looks at the LocoNet slot manager and returns true if more that 75% of the slots are not free, false otherwise.

The first thing to notice is that is expression is LocoNet specific. Which means that it should be in a sub package to jmri.jmrix.loconet and that the expression should not be available if there is no LocoNet connection. This is easy to do. LogixNG uses @ServiceProvider to get a list of the available actions and expressions. For example, the class jmri.jmrit.logixng.expressions.DigitalFactory provides a list of digital expressions. We can add our own factory class for our LocoNet expression and we can make the method getExpressionClasses() conditional so that it only returns our class if there is a LocoNet connection active.

Second, it may be helpful for the user if the LocoNet actions/expressions have their own category. So lets create that. LogixNG has an "enum" Category, but since Java enums cannot be extended, the LogixNG Category class simulates an enum without being a true enum class. We add the LocoNet category to the Factory class and let a static initializer in the Factory class register the LocoNet category.

Now it's time to create the expression itself. For that, we need three classes. ExpressionSlotUsage that has the expression itself, ExpressionSlotUsageXml that stores the expression to the panel xml file and ExpressionSlotUsageSwing that has the swing editor for this expression.

For the expression itself, ExpressionSlotUsage, there is two important things to do. First, the expression needs to do something useful, in this case check the slots in the slot manager. The interface jmri.jmrit.logixng.DigitalExpression defines a digital expression and what it needs to do. It has a method evaluate() that returns a boolean. We need to implement this method and it's here that we check the slots in the slot manager.

The second important thing we need to do in ExpressionSlotUsage is to register and unregister any listeners. In this case, we want to listen for changed slots so we use the methods registerListenersForThisClass() and unregisterListenersForThisClass() to register/unregister the listeners. Please check that listeners are not registered twice. We do that by the varable _listenersAreRegistered to check if listeners has already been registered.

The swing editor ExpressionSlotUsageSwing implements the SwingConfiguratorInterface that is used by the ConditionalNG editor to create and edit actions and expressions. It does that by calling the method getConfigPanel() in the SwingConfiguratorInterface that creates a JPanel with the components that are needed to configure the action/expression. After the user has clicked "OK" to create a new expression or to save the changes, the method validate() is called. If it returns true, a new object is created by a call to createNewObject() or the changes are saved to the expresion by a call to updateObject().

In the case of ExpressionSlotUsageSwing, we have a sentense like "The number of slots which 'has not' state 'COMMON or IDLE' is 'less than' '32', there the parts within '' are JComponents that let the user select different things. But this sentence needs to be translated to different languages and the order of words may be different in another language. The SwingConfiguratorInterface has a method parseMessage() that takes the message that we get from the bundle, together with the array of JComponents, and then returns a new list of JComponents there the message has been replaced by JLabel components and there the JComponents has been ordered according to the language.