/**
   * Given a new arrival date, re-calculate the speed
   *
   * @param arrivalDate the new arrival date
   */
  private void recalculateSpeeds(Date arrivalDate) {
    // Stop widget listeners
    boolean wasQuiescent = quiescent;
    quiescent = true;

    // Special case if the arrival date is before the start time
    if (route.getStarttime().after(arrivalDate)) {
      // Reset arrival to a valid time
      arrivalPicker.setDate(route.getEta());
      arrivalSpinner.setValue(route.getEta());

      quiescent = wasQuiescent;
      return;
    }

    // Total distance
    Dist distanceToTravel = new Dist(DistType.NAUTICAL_MILES, route.getRouteDtg());
    // And we want to get there in milliseconds:
    Time timeToTravel =
        new Time(TimeType.MILLISECONDS, arrivalDate.getTime() - route.getStarttime().getTime());

    // Subtract the distance and time from the locked way points
    for (int i = 0; i < route.getWaypoints().size() - 1; i++) {
      if (locked[i]) {
        distanceToTravel =
            distanceToTravel.subtract(new Dist(DistType.NAUTICAL_MILES, route.getWpRng(i)));
        timeToTravel =
            timeToTravel.subtract(new Time(TimeType.MILLISECONDS, route.getWpTtg(i + 1)));
      }
    }

    // Ensure the remaining time is actually positive (say, more than a minute)
    if (timeToTravel.in(TimeType.MINUTES).doubleValue() < 1.0) {
      // Reset arrival to a valid time
      arrivalPicker.setDate(route.getEta());
      arrivalSpinner.setValue(route.getEta());

      quiescent = wasQuiescent;
      return;
    }

    // So we need to travel how fast?
    double speed = distanceToTravel.inTime(timeToTravel).in(SpeedType.KNOTS).doubleValue();

    for (int i = 0; i < route.getWaypoints().size(); i++) {
      if (!locked[i]) {
        route.getWaypoints().get(i).setSpeed(speed);
      }
    }

    // Update fields
    updateFields();

    // Restore the quiescent state
    quiescent = wasQuiescent;
  }