text: import linuxcnc
  from qtpy.QtWidgets import QComboBox
  
  from qtpyvcp.utilities.settings import setting
  
  # Set up logging
  from qtpyvcp.utilities import logger
  LOG = logger.getLogger(__name__)
  
  from qtpyvcp.actions.base_actions import setTaskMode
  from qtpyvcp.plugins import getPlugin
  
  STATUS = getPlugin('status')
  STAT = STATUS.stat
  
  from qtpyvcp.utilities.info import Info
  INFO = Info()
  CMD = linuxcnc.command()
  
  
  # -------------------------------------------------------------------------
  # E-STOP action
  # -------------------------------------------------------------------------
  class estop:
      """E-Stop action group"""
      @staticmethod
      def activate():
          """Set E-Stop active
  
          ActionButton syntax::
  
              machine.estop.activate
          """
          LOG.debug("Setting state red<ESTOP>")
          CMD.state(linuxcnc.STATE_ESTOP)
  
      @staticmethod
      def reset():
          """Resets E-Stop
  
          ActionButton syntax::
  
              machine.estop.reset
          """
          LOG.debug("Setting state green<ESTOP_RESET>")
          CMD.state(linuxcnc.STATE_ESTOP_RESET)
  
      @staticmethod
      def toggle():
          """Toggles E-Stop state
  
          ActionButton syntax::
  
              machine.estop.toggle
          """
  
          if estop.is_activated():
              estop.reset()
          else:
              estop.activate()
  
      @staticmethod
      def is_activated():
          """Checks if E_Stop is activated.
  
          Returns:
              bool : True if E-Stop is active, else False.
          """
          return bool(STAT.estop)
  
  def _estop_ok(widget=None):
      # E-Stop is ALWAYS ok, but provide this method for consistency
      _estop_ok.msg = ""
      return True
  
  def _estop_bindOk(widget):
      widget.setChecked(STAT.estop != linuxcnc.STATE_ESTOP)
      STATUS.estop.onValueChanged(lambda v: widget.setChecked(not v))
  
  estop.activate.ok = estop.reset.ok = estop.toggle.ok = _estop_ok
  estop.activate.bindOk = estop.reset.bindOk = estop.toggle.bindOk = _estop_bindOk
  
  # -------------------------------------------------------------------------
  # POWER action
  # -------------------------------------------------------------------------
  class power:
      """Power action group"""
      @staticmethod
      def on():
          """Turns machine power On
  
          ActionButton syntax::
  
              machine.power.on
          """
          LOG.debug("Setting state green<ON>")
          CMD.state(linuxcnc.STATE_ON)
  
      @staticmethod
      def off():
          """Turns machine power Off
  
          ActionButton syntax::
  
              machine.power.off
          """
          LOG.debug("Setting state red<OFF>")
          CMD.state(linuxcnc.STATE_OFF)
  
      @staticmethod
      def toggle():
          """Toggles machine power On/Off
  
          ActionButton syntax::
  
              machine.power.toggle
          """
          if power.is_on():
              power.off()
          else:
              power.on()
  
      @staticmethod
      def is_on():
          """Checks if power is on.
  
          Returns:
              bool : True if power is on, else False.
          """
          return STAT.task_state == linuxcnc.STATE_ON
  
  def _power_ok(widget=None):
      if STAT.task_state == linuxcnc.STATE_ESTOP_RESET:
          ok = True
          msg = ""
      else:
          ok = False
          msg = "Can't turn machine ON until out of E-Stop"
  
      _power_ok.msg = msg
  
      if widget is not None:
          widget.setEnabled(ok)
          widget.setStatusTip(msg)
          widget.setToolTip(msg)
  
      return ok
  
  def _power_bindOk(widget):
      _power_ok(widget)
      widget.setChecked(STAT.task_state == linuxcnc.STATE_ON)
      STATUS.estop.onValueChanged(lambda: _power_ok(widget))
      STATUS.on.notify(lambda v: widget.setChecked(v))
  
  power.on.ok = power.off.ok = power.toggle.ok = _power_ok
  power.on.bindOk = power.off.bindOk = power.toggle.bindOk = _power_bindOk
  
  # -------------------------------------------------------------------------
  # MDI action
  # -------------------------------------------------------------------------
  
  PREVIOUS_MODE = None
  
  def _resetMode(interp_state):
      global PREVIOUS_MODE
      if  PREVIOUS_MODE is not None and interp_state == linuxcnc.INTERP_IDLE:
          if setTaskMode(PREVIOUS_MODE):
              LOG.debug("Successfully reset task_mode after MDI")
          PREVIOUS_MODE = None
  
  STATUS.interp_state.onValueChanged(_resetMode)
  
  def issue_mdi(command, reset=True):
      """Issue an MDI command.
  
          An MDI command can be issued any time the machine is homed (if not
          NO_FORCE_HOMING in the INI) and the interpreter is IDLE.  The task
          mode will automatically be switched to MDI prior to issuing the command
          and will be returned to the previous mode when the interpreter becomes IDLE.
  
          ActionButton syntax to issue G0 X5:
          ::
  
              machine.issue_mdi:G0X5
  
          It is simpler to use the MDIButton, examples in the Widgets section.
  
      Args:
          command (str) : A valid RS274 gcode command string. Multiple MDI commands
              can be separated with a ``;`` and will be issued sequentially.
          reset (bool, optional): Whether to reset the Task Mode to the state
              the machine was in prior to issuing the MDI command.
      """
      if reset:
          # save the previous mode
          global PREVIOUS_MODE
          PREVIOUS_MODE = STAT.task_mode
          # Force `interp_state` update on next status cycle. This is needed because
          # some commands might take less than `cycle_time` (50ms) to complete,
          # so status would not even notice that the interp_state had changed and the
          # reset mode method would not be called.
          STATUS.old['interp_state'] = -1
  
      if setTaskMode(linuxcnc.MODE_MDI):
          # issue multiple MDI commands separated by ';'
          for cmd in command.strip().split(';'):
              LOG.info("Issuing MDI command: %s", cmd)
              CMD.mdi(cmd)
      else:
          LOG.error("Failed to issue MDI command: {}".format(command))
  
  def _issue_mdi_ok(mdi_cmd='', widget=None):
      if STAT.task_state == linuxcnc.STATE_ON \
                            and STATUS.allHomed() \
                            and STAT.interp_state == linuxcnc.INTERP_IDLE:
  
          ok = True
          msg = ""
  
      else:
          ok = False
          msg = "Can't issue MDI unless machine is ON, HOMED and IDLE"
  
      _issue_mdi_ok.msg = msg
  
      if widget is not None:
          widget.setEnabled(ok)
          widget.setStatusTip(msg)
          widget.setToolTip(msg)
  
      return ok
  
  def _issue_mdi_bindOk(mdi_cmd='', widget=None):
      _issue_mdi_ok(mdi_cmd=mdi_cmd, widget=widget)
      STATUS.task_state.onValueChanged(lambda: _issue_mdi_ok(mdi_cmd=mdi_cmd, widget=widget))
      STATUS.interp_state.onValueChanged(lambda: _issue_mdi_ok(mdi_cmd=mdi_cmd, widget=widget))
      STATUS.homed.onValueChanged(lambda: _issue_mdi_ok(mdi_cmd=mdi_cmd, widget=widget))
  
  issue_mdi.ok = _issue_mdi_ok
  issue_mdi.bindOk = _issue_mdi_bindOk
  
  # -------------------------------------------------------------------------
  # WORK COORDINATES action
  # -------------------------------------------------------------------------
  
  def set_work_coord(coord):
      issue_mdi(coord)
  
  def _set_work_coord_bindOk(coord='', widget=None):
      coord = coord.upper()
      _issue_mdi_bindOk(coord, widget=widget)
      if isinstance(widget, QComboBox):
          widget.setCurrentText(coord)
          widget.setCurrentText(STATUS.g5x_index.getString())
          STATUS.g5x_index.notify(lambda g5x: widget.setCurrentText(g5x), 'string')
      else:
          widget.setCheckable(True)
          widget.setChecked(STATUS.g5x_index.getString() == coord)
          STATUS.g5x_index.notify(lambda g5x: widget.setChecked(g5x == coord), 'string')
  
  set_work_coord.ok = _issue_mdi_ok
  set_work_coord.bindOk = _set_work_coord_bindOk
  
  # -------------------------------------------------------------------------
  # FEED HOLD action
  # -------------------------------------------------------------------------
  class feedhold:
      """Feed Hold action Group"""
  
      # FIXME: Not sure what feedhold does, or how to turn it ON/OFF, if it even can be.
  
      @staticmethod
      def enable():
          """Enables Feed Hold"""
          LOG.info("Setting feedhold ENABLED")
          CMD.set_feed_hold(1)
  
      @staticmethod
      def disable():
          """Disables Feed Hold"""
          LOG.info("Setting feedhold DISABLED")
          CMD.set_feed_hold(0)
  
      @staticmethod
      def toggle():
          """Toggles Feed Hold state"""
          if STAT.feed_hold_enabled:
              feedhold.disable()
          else:
              feedhold.enable()
  
  def _feed_hold_ok(widget=None):
      return STAT.task_state == linuxcnc.STATE_ON and STAT.interp_state == linuxcnc.INTERP_IDLE
  
  def _feed_hold_bindOk(widget):
      widget.setEnabled(STAT.task_state == linuxcnc.STATE_ON)
      widget.setChecked(STAT.feed_hold_enabled)
      STATUS.task_state.notify(lambda s: widget.setEnabled(s == linuxcnc.STATE_ON))
      STATUS.feed_hold_enabled.notify(widget.setChecked)
  
  feedhold.enable.ok = feedhold.disable.ok = feedhold.toggle.ok = _feed_hold_ok
  feedhold.enable.bindOk = feedhold.disable.bindOk = feedhold.toggle.bindOk = _feed_hold_bindOk
  
  # -------------------------------------------------------------------------
  # FEED OVERRIDE actions
  # -------------------------------------------------------------------------
  
  class feed_override:
      """Feed Override Group"""
      @staticmethod
      def enable():
          """Feed Override Enable"""
          CMD.set_feed_override(True)
  
      @staticmethod
      def disable():
          """Feed Override Disable"""
          CMD.set_feed_override(False)
  
      @staticmethod
      def toggle_enable():
          """Feed Override Enable Toggle"""
          if STAT.feed_override_enabled:
              feed_override.disable()
          else:
              feed_override.enable()
  
      @staticmethod
      def set(value):
          """Feed Override Set Value"""
          CMD.feedrate(float(value) / 100)
  
      @staticmethod
      def reset():
          """Feed Override Reset"""
          CMD.feedrate(1.0)
  
  def _feed_override_enable_ok(widget=None):
      if STAT.task_state == linuxcnc.STATE_ON \
          and STAT.interp_state == linuxcnc.INTERP_IDLE:
          ok = True
          msg = ""
      else:
          ok = False
          msg = "Machine must be ON and IDLE to enable/disable feed override"
  
      _feed_override_enable_ok.msg = msg
  
      if widget is not None:
          widget.setEnabled(ok)
          widget.setStatusTip(msg)
          widget.setToolTip(msg)
  
      return ok
  
  def _feed_override_enable_bindOk(widget):
      STATUS.task_state.onValueChanged(lambda: _feed_override_enable_ok(widget))
      STATUS.interp_state.onValueChanged(lambda: _feed_override_enable_ok(widget))
      STATUS.feed_override_enabled.onValueChanged(widget.setChecked)
  
  def _feed_override_ok(value=100, widget=None):
      if STAT.task_state == linuxcnc.STATE_ON and STAT.feed_override_enabled == 1:
          ok = True
          msg = ""
      elif STAT.task_state != linuxcnc.STATE_ON:
          ok = False
          msg = "Machine must be ON to set feed override"
      elif STAT.feed_override_enabled == 0:
          ok = False
          msg = "Feed override is not enabled"
      else:
          ok = False
          msg = "Feed override can not be changed"
  
      _feed_override_ok.msg = msg
  
      if widget is not None:
          widget.setEnabled(ok)
          widget.setStatusTip(msg)
          widget.setToolTip(msg)
  
      return ok
  
  def _feed_override_bindOk(value=100, widget=None):
  
      # This will work for any widget
      STATUS.task_state.onValueChanged(lambda: _feed_override_ok(widget=widget))
      STATUS.feed_override_enabled.onValueChanged(lambda: _feed_override_ok(widget=widget))
  
      try:
          # these will only work for QSlider or QSpinBox
          widget.setMinimum(0)
          widget.setMaximum(INFO.maxFeedOverride() * 100)
  
          try:
              widget.setSliderPosition(100)
              STATUS.feedrate.onValueChanged(
                  lambda v: widget.setSliderPosition(v * 100))
  
          except AttributeError:
              widget.setValue(100)
              STATUS.feedrate.onValueChanged(
                  lambda v: widget.setValue(v * 100))
  
          feed_override.set(100)
  
      except AttributeError:
          pass
      except:
          LOG.exception('Error in feed_override bindOk')
  
  feed_override.set.ok = feed_override.reset.ok = _feed_override_ok
  feed_override.set.bindOk = feed_override.reset.bindOk = _feed_override_bindOk
  feed_override.enable.ok = feed_override.disable.ok = feed_override.toggle_enable.ok = _feed_override_enable_ok
  feed_override.enable.bindOk = feed_override.disable.bindOk = feed_override.toggle_enable.bindOk = _feed_override_enable_bindOk
  
  # -------------------------------------------------------------------------
  # RAPID OVERRIDE actions
  # -------------------------------------------------------------------------
  
  class rapid_override:
      """Rapid Override Group"""
      @staticmethod
      def set(value):
          CMD.rapidrate(float(value) / 100)
  
      @staticmethod
      def reset():
          CMD.rapidrate(1.0)
  
  def _rapid_override_ok(value=100, widget=None):
      if STAT.task_state == linuxcnc.STATE_ON:
          ok = True
          msg = ""
      else:
          ok = False
          msg = "Machine must be ON to set rapid override"
  
      _rapid_override_ok.msg = msg
  
      if widget is not None:
          widget.setEnabled(ok)
          widget.setStatusTip(msg)
          widget.setToolTip(msg)
  
      return ok
  
  def _rapid_override_bindOk(value=100, widget=None):
  
      # This will work for any widget
      STATUS.task_state.onValueChanged(lambda: _rapid_override_ok(widget=widget))
  
      try:
          # these will only work for QSlider or QSpinBox
          widget.setMinimum(0)
          widget.setMaximum(100)
  
          try:
              widget.setSliderPosition(100)
              STATUS.rapidrate.onValueChanged(
                  lambda v: widget.setSliderPosition(v * 100))
  
          except AttributeError:
              STATUS.rapidrate.onValueChanged(
                  lambda v: widget.setValue(v * 100))
              widget.setValue(100)
  
          rapid_override.set(100)
  
      except AttributeError:
          pass
      except:
          LOG.exception('Error in rapid_override bindOk')
  
  rapid_override.set.ok = rapid_override.reset.ok = _rapid_override_ok
  rapid_override.set.bindOk = rapid_override.reset.bindOk = _rapid_override_bindOk
  
  # -------------------------------------------------------------------------
  # MAX VEL OVERRIDE actions
  # -------------------------------------------------------------------------
  
  class max_velocity:
      """Max Velocity Group"""
      @staticmethod
      def set(value):
          """Max Velocity Override Set Value"""
          CMD.maxvel(float(value) / 60)
  
      @staticmethod
      def reset():
          """Max Velocity Override Reset Value"""
          CMD.maxvel(INFO.maxVelocity() / 60)
  
  def _max_velocity_ok(value=100, widget=None):
      if STAT.task_state == linuxcnc.STATE_ON:
          ok = True
          msg = ""
      else:
          ok = False
          msg = "Machine must be ON to set max velocity"
  
      _max_velocity_ok.msg = msg
  
      if widget is not None:
          widget.setEnabled(ok)
          widget.setStatusTip(msg)
          widget.setToolTip(msg)
  
      return ok
  
  def _max_velocity_bindOk(value=100, widget=None):
  
      # This will work for any widget
      STATUS.task_state.onValueChanged(lambda: _max_velocity_ok(widget=widget))
  
      try:
          # these will only work for QSlider or QSpinBox
          widget.setMinimum(0)
          widget.setMaximum(INFO.maxVelocity())
  
          try:
              widget.setSliderPosition(INFO.maxVelocity())
              STATUS.max_velocity.onValueChanged(
                  lambda v: widget.setSliderPosition(v * 60))
  
          except AttributeError:
              widget.setValue(INFO.maxVelocity())
              STATUS.max_velocity.onValueChanged(
                  lambda v: widget.setValue(v * 60))
  
      except AttributeError:
          pass
      except:
          LOG.exception('Error in max_velocity bindOk')
  
  max_velocity.set.ok = max_velocity.reset.ok = _max_velocity_ok
  max_velocity.set.bindOk = max_velocity.reset.bindOk = _max_velocity_bindOk
  
  
  # -------------------------------------------------------------------------
  # set MODE actions
  # -------------------------------------------------------------------------
  class mode:
      """Mode action group"""
      @staticmethod
      def manual():
          """Change mode to Manual
  
          ActionButton syntax:
          ::
  
              machine.mode.manual
  
          """
          setTaskMode(linuxcnc.MODE_MANUAL)
  
      @staticmethod
      def auto():
          """Change mode to Auto
  
          ActionButton syntax:
          ::
  
              machine.mode.auto
  
          """
          setTaskMode(linuxcnc.MODE_AUTO)
  
      @staticmethod
      def mdi():
          """Change mode to MDI
  
          ActionButton syntax:
          ::
  
              machine.mode.mdi
  
          """
          setTaskMode(linuxcnc.MODE_MDI)
  
  def _mode_ok(widget=None):
      if STAT.task_state == linuxcnc.STATE_ON and STAT.interp_state == linuxcnc.INTERP_IDLE:
          ok = True
          msg = ""
  
      else:
          ok = False
          msg = "Can't set mode when not ON and IDLE"
  
      _mode_ok.msg = msg
  
      if widget is not None:
          widget.setEnabled(ok)
          widget.setStatusTip(msg)
          widget.setToolTip(msg)
  
      return ok
  
  def _manual_bindOk(widget):
      widget.setChecked(STAT.task_mode == linuxcnc.MODE_MANUAL)
      STATUS.task_state.onValueChanged(lambda: _mode_ok(widget))
      STATUS.interp_state.onValueChanged(lambda: _mode_ok(widget))
      STATUS.task_mode.onValueChanged(lambda m: widget.setChecked(m == linuxcnc.MODE_MANUAL))
  
  mode.manual.ok = _mode_ok
  mode.manual.bindOk = _manual_bindOk
  
  def _auto_bindOk(widget):
      widget.setChecked(STAT.task_mode == linuxcnc.MODE_AUTO)
      STATUS.task_state.onValueChanged(lambda: _mode_ok(widget))
      STATUS.interp_state.onValueChanged(lambda: _mode_ok(widget))
      STATUS.task_mode.onValueChanged(lambda m: widget.setChecked(m == linuxcnc.MODE_AUTO))
  
  mode.auto.ok = _mode_ok
  mode.auto.bindOk = _auto_bindOk
  
  def _mdi_bindOk(widget):
      widget.setChecked(STAT.task_mode == linuxcnc.MODE_MDI)
      STATUS.task_state.onValueChanged(lambda: _mode_ok(widget))
      STATUS.interp_state.onValueChanged(lambda: _mode_ok(widget))
      STATUS.task_mode.onValueChanged(lambda m: widget.setChecked(m == linuxcnc.MODE_MDI))
  
  mode.mdi.ok = _mode_ok
  mode.mdi.bindOk = _mdi_bindOk
  
  # -------------------------------------------------------------------------
  # HOME actions
  # -------------------------------------------------------------------------
  class home:
      """Homing actions group"""
      @staticmethod
      def all():
          """Homes all axes
  
          ActionButton syntax::
  
              machine.home.all
          """
          LOG.info("Homing all axes")
          _home_joint(-1)
  
      @staticmethod
      def axis(axis):
          """Home a specific axis
  
          Args:
              axis (int | str) : one of (xyzabcuvw or 012345678)
  
          ActionButton syntax to home the X axis::
  
              machine.home.axis:x
          """
          axis = getAxisLetter(axis)
          if axis.lower() == 'all':
              home.all()
              return
          jnum = INFO.COORDINATES.index(axis)
          LOG.info('Homing Axis: {}'.format(axis.upper()))
          _home_joint(jnum)
  
      @staticmethod
      def joint(jnum):
          """Home a specific joint
  
          Args:
              jnum (int) : one of (012345678)
  
          ActionButton syntax to home joint 0::
  
              machine.home.joint:0
          """
          LOG.info("Homing joint: {}".format(jnum))
          _home_joint(jnum)
  
  def _home_ok(jnum=-1, widget=None):
      # TODO: Check if homing a specific joint is OK
      if power.is_on(): # and not STAT.homed[jnum]:
          ok = True
          msg = ""
      else:
          ok = False
          msg = "Machine must be on to home"
  
      _home_ok.msg = msg
  
      if widget is not None:
          widget.setEnabled(ok)
          widget.setStatusTip(msg)
          widget.setToolTip(msg)
  
      return ok
  
  def _home_all_bindOk(widget):
      STATUS.on.notify(lambda: _home_ok(widget=widget))
      STATUS.homed.notify(lambda: _home_ok(widget=widget))
  
  home.all.ok = _home_ok
  home.all.bindOk = _home_all_bindOk
  
  def _home_joint_bindOk(jnum, widget):
      STATUS.on.notify(lambda: _home_ok(jnum, widget=widget))
      STATUS.homed.notify(lambda: _home_ok(jnum, widget=widget))
  
  home.joint.ok = _home_ok
  home.joint.bindOk = _home_joint_bindOk
  
  def _home_axis_bindOk(axis, widget):
      aletter = getAxisLetter(axis)
      if aletter not in INFO.AXIS_LETTER_LIST:
          msg = 'Machine has no {} axis'.format(aletter.upper())
          widget.setEnabled(False)
          widget.setToolTip(msg)
          widget.setStatusTip(msg)
          return
  
      jnum = INFO.AXIS_LETTER_LIST.index(axis)
      STATUS.on.notify(lambda: _home_ok(jnum, widget=widget))
  
  home.axis.ok = _home_ok
  home.axis.bindOk = _home_axis_bindOk
  
  
  class unhome:
      """Unhoming actions group"""
      @staticmethod
      def all():
          """Unhome all the axes
  
          ActionButton syntax::
  
              machine.unhome.all
          """
          LOG.info("Unhoming all Axes")
          _unhome_joint(-1)
  
      @staticmethod
      def axis(axis):
          """Unhome a specific axis
  
          Args:
              axis (int | str) : one of (xyzabcuvw or 012345678)
  
          ActionButton syntax to unhome the X axis::
  
              machine.unhome.axis:x
          """
          axis = getAxisLetter(axis)
          if axis.lower() == 'all': # not sure what this is copied from home
              unhome.all()
              return
          jnum = INFO.COORDINATES.index(axis)
          LOG.info('Unhoming Axis: {}'.format(axis.upper()))
          _unhome_joint(jnum)
  
      @staticmethod
      def joint(jnum):
          """Unhome a specific joint
  
          Args:
              jnum (int) : The number of the joint to home.
  
          ActionButton syntax to unhome the joint 0::
  
              machine.unhome.joint:0
          """
          LOG.info("Unhoming joint: {}".format(jnum))
          _unhome_joint(jnum)
  
  def _home_joint(jnum):
      setTaskMode(linuxcnc.MODE_MANUAL)
      CMD.teleop_enable(False)
      CMD.home(jnum)
  
  def _unhome_joint(jnum):
      setTaskMode(linuxcnc.MODE_MANUAL)
      CMD.teleop_enable(False)
      CMD.unhome(jnum)
  
  # Homing helper functions
  
  def getAxisLetter(axis):
      """Takes an axis letter or number and returns the axis letter.
  
      Args:
          axis (int | str) : Either a axis letter or an axis number.
  
      Returns:
          str : The axis letter, `all` for an input of -1.
      """
      if isinstance(axis, int):
          return ['x', 'y', 'z', 'a', 'b', 'c', 'u', 'v', 'w', 'all'][axis]
      return axis.lower()
  
  def getAxisNumber(axis):
      """Takes an axis letter or number and returns the axis number.
  
      Args:
          axis (int | str) : Either a axis letter or an axis number.
  
      Returns:
          int : The axis number, -1 for an input of `all`.
      """
      if isinstance(axis, str):
          return ['x', 'y', 'z', 'a', 'b', 'c', 'u', 'v', 'w', 'all'].index(axis.lower())
      return axis
  
  
  # -------------------------------------------------------------------------
  # OVERRIDE LIMITS action
  # -------------------------------------------------------------------------
  def override_limits():
      LOG.info("Setting override limits")
      CMD.override_limits()
  
  def _override_limits_ok(widget=None):
      ok = False
      mgs = None
      for anum in INFO.AXIS_NUMBER_LIST:
          if STAT.limit[anum] != 0:
              aaxis = getAxisLetter(anum)
              aletter = INFO.COORDINATES.index(aaxis)
              ok = True
              msg = "Axis {} on limit".format(aletter)
  
      if not ok:
          msg = "Axis must be on limit to override"
  
      _override_limits_ok.msg = msg
  
      if widget is not None:
          widget.setEnabled(ok)
          widget.setStatusTip(msg)
          widget.setToolTip(msg)
  
      return ok
  
  def _override_limits_bindOk(widget):
      STATUS.limit.onValueChanged(lambda: _override_limits_ok(widget))
  
  override_limits.ok = _override_limits_ok
  override_limits.bindOk = _override_limits_bindOk
  
  # -------------------------------------------------------------------------
  # JOG actions
  # -------------------------------------------------------------------------
  
  DEFAULT_JOG_SPEED = INFO.getJogVelocity()
  MAX_JOG_SPEED = INFO.getMaxJogVelocity()
  
  @setting('machine.jog.linear-speed',
           default_value=DEFAULT_JOG_SPEED,
           min_value=0,
           max_value=MAX_JOG_SPEED,
           persistent=True)
  def jog_linear_speed(obj):
      return obj.value
  
  @jog_linear_speed.setter
  def jog_linear_speed(obj, value):
  
      obj.value = obj.clampValue(value)
      jog_linear_speed.signal.emit(obj.value)
      LOG.debug("Setting jog linear speed: %4.1f", obj.value)
  
      percentage = int(obj.value * 100 / MAX_JOG_SPEED)
      jog_linear_speed_percentage.value = percentage
      jog_linear_speed_percentage.signal.emit(percentage)
  
  
  @setting('machine.jog.linear-speed-percentage',
           default_value=int(DEFAULT_JOG_SPEED * 100 / MAX_JOG_SPEED),
           min_value=0,
           max_value=100,
           persistent=False)
  def jog_linear_speed_percentage(obj):
      return obj.value
  
  @jog_linear_speed_percentage.setter
  def jog_linear_speed_percentage(obj, percentage):
      LOG.debug("Setting jog linear speed percentage: %d", percentage)
      jog_linear_speed.setValue(float(MAX_JOG_SPEED * percentage / 100))
  
  
  @setting('machine.jog.angular-speed', default_value=100.0, persistent=False)
  def jog_angular_speed(obj):
      return obj.value
  
  @jog_angular_speed.setter
  def jog_angular_speed(obj, value):
      LOG.debug("Setting Jog Angular Speed: %d", value)
      obj.value = value
  
  
  @setting('machine.jog.mode-incremental', default_value=True)
  def jog_mode_incremental(obj):
      return obj.value
  
  @jog_mode_incremental.setter
  def jog_mode_incremental(obj, value):
      LOG.debug("Setting jog mode incremental: %s", value)
      obj.value = value
  
  
  def fromInternalLinearUnits(v, unit=None):
      if unit is None:
          unit = STAT.linear_units
      lu = (unit or 1) * 25.4
      return v * lu
  
  def parseJogIncrement(jogincr):
      scale = 1
      if isinstance(jogincr, basestring):
          jogincr = jogincr.lower()
          if jogincr.endswith("mm"):
              scale = fromInternalLinearUnits(1 / 25.4)
          elif jogincr.endswith("cm"):
              scale = fromInternalLinearUnits(10 / 25.4)
          elif jogincr.endswith("um"):
              scale = fromInternalLinearUnits(.001 / 25.4)
          elif jogincr.endswith("in") or jogincr.endswith("inch") or jogincr.endswith('"'):
              scale = fromInternalLinearUnits(1.)
          elif jogincr.endswith("mil"):
              scale = fromInternalLinearUnits(.001)
          else:
              scale = 1
          jogincr = jogincr.rstrip(" inchmuil")
          try:
              if "/" in jogincr:
                  p, q = jogincr.split("/")
                  jogincr = float(p) / float(q)
              else:
                  jogincr = float(jogincr)
          except ValueError:
              jogincr = 0
      return jogincr * scale
  
  @setting('machine.jog.increment', parseJogIncrement(INFO.getIncrements()[0]))
  def jog_increment(obj):
      """Linear jog increment.
  
      Args:
          jogincr (str, int, float) : The desired jog increment. Can be passed
              as a string including an optional units specifier. Valid unit
              specifiers include ``mm``, ``cm``, ``um``, ``in``, ``inch``, ``"``,
              and ``mil``.
      """
      return obj.value
  
  @jog_increment.setter
  def jog_increment(obj, jogincr):
      value = parseJogIncrement(jogincr)
      LOG.debug('Setting jog increment: %s (%2.4f)', jogincr, value)
      obj.value = value
      obj.signal.emit(value)
  
  
  class jog:
      """Jog Actions Group"""
  
      max_linear_speed = INFO.getMaxJogVelocity()
      angular_speed = INFO.getJogVelocity()
      continuous = False
      increment = INFO.getIncrements()[0]
  
      @staticmethod
      def axis(axis, direction=0, speed=None, distance=None):
          """Jog an axis.
  
          Action Button Syntax to jog the X axis in the positive direction::
  
              machine.jog.axis:x,pos
  
          Args:
              axis (str | int) : Either the letter or number of the axis to jog.
              direction (str | int) : pos or +1 for positive, neg or -1 for negative.
              speed (float, optional) : Desired jog vel in machine_units/s.
              distance (float, optional) : Desired jog distance, continuous if 0.00.
          """
  
          # check if it even makes sense to try to jog
          if STAT.task_state != linuxcnc.STATE_ON or STAT.task_mode != linuxcnc.MODE_MANUAL:
              return
  
          if isinstance(direction, str):
              direction = {'neg': -1, 'pos': 1}.get(direction.lower(), 0)
  
          axis = getAxisNumber(axis)
  
          # must be in teleoperating mode to jog.
          if STAT.motion_mode != linuxcnc.TRAJ_MODE_TELEOP:
              CMD.teleop_enable(1)
  
          if speed == 0 or direction == 0:
              CMD.jog(linuxcnc.JOG_STOP, 0, axis)
  
          else:
  
              if speed is None:
                  if axis in (3, 4, 5):
                      speed = jog_angular_speed.value / 60.0
                  else:
                      speed = jog_linear_speed.value / 60.0
  
              if distance is None:
                  if jog_mode_incremental.value:
                      distance = jog_increment.value
                  else:
                      distance = 0
  
              velocity = float(speed) * direction
  
              if distance == 0:
                  CMD.jog(linuxcnc.JOG_CONTINUOUS, 0, axis, velocity)
              else:
                  CMD.jog(linuxcnc.JOG_INCREMENT, 0, axis, velocity, distance)
  
      @staticmethod
      def set_jog_continuous(continuous):
          """Set Jog Continuous"""
          if continuous:
              LOG.debug("Setting jog mode to continuous")
          else:
              LOG.debug("Setting jog mode to incremental")
          jog.jog_mode_incremental = continuous
  
      @staticmethod
      def set_increment(raw_increment):
          """Set Jog Increment"""
          jog_increment.setValue(raw_increment)
  
      @staticmethod
      def set_linear_speed(speed):
          """Set Jog Linear Speed
  
          ActionSlider syntax::
  
              machine.jog.set-linear-speed
          """
          jog_linear_speed.setValue(float(speed))
  
      @staticmethod
      def set_angular_speed(speed):
          """Set Jog Angular Speed"""
          jog_angular_speed.setValue(float(speed))
  
      @staticmethod
      def set_linear_speed_percentage(percentage):
          """Set Jog Linear Speed Percentage"""
          jog_linear_speed_percentage.setValue(percentage)
  
  
  def _jog_speed_slider_bindOk(widget):
  
      try:
          # these will only work for QSlider or QSpinBox
          widget.setMinimum(0)
          widget.setMaximum(100)
          widget.setValue((jog.linear_speed.getValue() / jog.max_linear_speed) * 100)
  
          # jog.linear_speed.connect(lambda v: widget.setValue(v * 100))
      except AttributeError:
          pass
  
  jog.set_linear_speed.ok = jog.set_angular_speed.ok = lambda *a, **k: True
  jog.set_linear_speed.bindOk = jog.set_angular_speed.bindOk = lambda *a, **k: True
  
  jog.set_linear_speed_percentage.ok = lambda *a, **k: True
  jog.set_linear_speed_percentage.bindOk = _jog_speed_slider_bindOk
  
  
  def _jog_axis_ok(axis, direction=0, widget=None):
      axisnum = getAxisNumber(axis)
      jnum = INFO.COORDINATES.index(axis)
      if STAT.task_state == linuxcnc.STATE_ON \
              and STAT.interp_state == linuxcnc.INTERP_IDLE \
              and (STAT.limit[axisnum] == 0 or STAT.joint[jnum]['override_limits']):
          # and STAT.homed[axisnum] == 1 \
          ok = True
          msg = ""
      else:
          ok = False
          msg = "Machine must be ON and in IDLE to jog"
  
      _jog_axis_ok.msg = msg
  
      if widget is not None:
          widget.setEnabled(ok)
          widget.setStatusTip(msg)
          widget.setToolTip(msg)
  
      return ok
  
  
  def _jog_axis_bindOk(axis, direction, widget):
      aletter = getAxisLetter(axis)
      if aletter not in INFO.AXIS_LETTER_LIST:
          msg = 'Machine has no {} axis'.format(aletter.upper())
          widget.setEnabled(False)
          widget.setToolTip(msg)
          widget.setStatusTip(msg)
          return
  
      STATUS.limit.onValueChanged(lambda: _jog_axis_ok(aletter, direction, widget))
      STATUS.homed.onValueChanged(lambda: _jog_axis_ok(aletter, direction, widget))
      STATUS.task_state.onValueChanged(lambda: _jog_axis_ok(aletter, direction, widget))
      STATUS.interp_state.onValueChanged(lambda: _jog_axis_ok(aletter, direction, widget))
  
  jog.axis.ok = _jog_axis_ok
  jog.axis.bindOk = _jog_axis_bindOk
  
  
  class jog_mode:
      """Jog Mode Group"""
  
      @staticmethod
      def continuous():
          """Set Jog Continuous
  
          ActionButton syntax::
  
              machine.jog-mode.continuous
          """
          jog_mode_incremental.setValue(False)
  
  
      @staticmethod
      def incremental():
          """Set Jog Incremental
  
          ActionButton syntax::
  
              machine.jog-mode.incremental
          """
          jog_mode_incremental.setValue(True)
  
      @staticmethod
      def toggle():
          """Jog Mode Toggle
  
          ActionButton syntax::
  
              machine.jog-mode.toggle
          """
          jog_mode_incremental.setValue(not jog_mode_incremental.value)
  
  jog_mode.incremental.ok = jog_mode.continuous.ok = lambda *a, **kw: True
  jog_mode.incremental.bindOk = jog_mode.continuous.bindOk = lambda *a, **kw: True
  