code:
#include "HamlibTransceiver.hpp"

#include <cstring>
#include <cmath>

#include <QByteArray>
#include <QString>
#include <QStandardPaths>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QDebug>

#include "moc_HamlibTransceiver.cpp"

namespace
{
  // Unfortunately bandwidth is conflated  with mode, this is probably
  // because Icom do  the same. So we have to  care about bandwidth if
  // we want  to set  mode otherwise we  will end up  setting unwanted
  // bandwidths every time we change mode.  The best we can do via the
  // Hamlib API is to request the  normal option for the mode and hope
  // that an appropriate filter is selected.  Also ensure that mode is
  // only set is absolutely necessary.  On Icoms (and probably others)
  // the filter is  selected by number without checking  the actual BW
  // so unless the  "normal" defaults are set on the  rig we won't get
  // desirable results.
  //
  // As  an ultimate  workaround make  sure  the user  always has  the
  // option to skip mode setting altogether.

  // reroute Hamlib diagnostic messages to Qt
  int debug_callback (enum rig_debug_level_e level, rig_ptr_t /* arg */, char const * format, va_list ap)
  {
    QString message;
    static char constexpr fmt[] = "Hamlib: %s";
    message = message.vsprintf (format, ap).trimmed ();

    switch (level)
      {
      case RIG_DEBUG_BUG:
        qFatal (fmt, message.toLocal8Bit ().data ());
        break;

      case RIG_DEBUG_ERR:
        qCritical (fmt, message.toLocal8Bit ().data ());
        break;

      case RIG_DEBUG_WARN:
        qWarning (fmt, message.toLocal8Bit ().data ());
        break;

      default:
        qDebug (fmt, message.toLocal8Bit ().data ());
        break;
      }

    return 0;
  }

  // callback function that receives transceiver capabilities from the
  // hamlib libraries
  int register_callback (rig_caps const * caps, void * callback_data)
  {
    TransceiverFactory::Transceivers * rigs = reinterpret_cast<TransceiverFactory::Transceivers *> (callback_data);

    QString key;
    if (RIG_MODEL_DUMMY == caps->rig_model)
      {
        key = TransceiverFactory::basic_transceiver_name_;
      }
    else
      {
        key = QString::fromLatin1 (caps->mfg_name).trimmed ()
          + ' '+ QString::fromLatin1 (caps->model_name).trimmed ()
          // + ' '+ QString::fromLatin1 (caps->version).trimmed ()
          // + " (" + QString::fromLatin1 (rig_strstatus (caps->status)).trimmed () + ')'
          ;
      }

    auto port_type = TransceiverFactory::Capabilities::none;
    switch (caps->port_type)
      {
      case RIG_PORT_SERIAL:
        port_type = TransceiverFactory::Capabilities::serial;
        break;

      case RIG_PORT_NETWORK:
        port_type = TransceiverFactory::Capabilities::network;
        break;

      case RIG_PORT_USB:
        port_type = TransceiverFactory::Capabilities::usb;
        break;

      default: break;
      }
    (*rigs)[key] = TransceiverFactory::Capabilities (caps->rig_model
                                                     , port_type
                                                     , RIG_MODEL_DUMMY != caps->rig_model
                                                     && (RIG_PTT_RIG == caps->ptt_type
                                                         || RIG_PTT_RIG_MICDATA == caps->ptt_type)
                                                     , RIG_PTT_RIG_MICDATA == caps->ptt_type);

    return 1;			// keep them coming
  }

  int unregister_callback (rig_caps const * caps, void *)
  {
    rig_unregister (caps->rig_model);
    return 1;			// keep them coming
  }

  // int frequency_change_callback (RIG * /* rig */, vfo_t vfo, freq_t f, rig_ptr_t arg)
  // {
  //   (void)vfo;			// unused in release build

  //   Q_ASSERT (vfo == RIG_VFO_CURR); // G4WJS: at the time of writing only current VFO is signalled by hamlib

  //   HamlibTransceiver * transceiver (reinterpret_cast<HamlibTransceiver *> (arg));
  //   Q_EMIT transceiver->frequency_change (f, Transceiver::A);
  //   return RIG_OK;
  // }

  class hamlib_tx_vfo_fixup final
  {
  public:
    hamlib_tx_vfo_fixup (RIG * rig, vfo_t tx_vfo)
      : rig_ {rig}
    {
      original_vfo_ = rig_->state.tx_vfo;
      rig_->state.tx_vfo = tx_vfo;
    }

    ~hamlib_tx_vfo_fixup ()
    {
      rig_->state.tx_vfo = original_vfo_;
    }

  private:
    RIG * rig_;
    vfo_t original_vfo_;
  };
}

freq_t HamlibTransceiver::dummy_frequency_;
rmode_t HamlibTransceiver::dummy_mode_ {RIG_MODE_NONE};

void HamlibTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry)
{
  rig_set_debug_callback (debug_callback, nullptr);

#if WSJT_HAMLIB_TRACE
#if WSJT_HAMLIB_VERBOSE_TRACE
  rig_set_debug (RIG_DEBUG_TRACE);
#else
  rig_set_debug (RIG_DEBUG_VERBOSE);
#endif
#elif defined (NDEBUG)
  rig_set_debug (RIG_DEBUG_ERR);
#else
  rig_set_debug (RIG_DEBUG_WARN);
#endif

  rig_load_all_backends ();
  rig_list_foreach (register_callback, registry);
}

void HamlibTransceiver::unregister_transceivers ()
{
  rig_list_foreach (unregister_callback, nullptr);
}

void HamlibTransceiver::RIGDeleter::cleanup (RIG * rig)
{
  if (rig)
    {
      // rig->state.obj = 0;
      rig_cleanup (rig);
    }
}

HamlibTransceiver::HamlibTransceiver (TransceiverFactory::PTTMethod ptt_type, QString const& ptt_port,
                                      QObject * parent)
  : PollingTransceiver {0, parent}
  , rig_ {rig_init (RIG_MODEL_DUMMY)}
  , back_ptt_port_ {false}
  , one_VFO_ {false}
  , is_dummy_ {true}
  , reversed_ {false}
  , freq_query_works_ {true}
  , mode_query_works_ {true}
  , split_query_works_ {true}
  , tickle_hamlib_ {false}
  , get_vfo_works_ {true}
  , set_vfo_works_ {true}
{
  if (!rig_)
    {
      throw error {tr ("Hamlib initialisation error")};
    }

  switch (ptt_type)
    {
    case TransceiverFactory::PTT_method_VOX:
      set_conf ("ptt_type", "None");
      break;

    case TransceiverFactory::PTT_method_CAT:
      // Use the default PTT_TYPE for the rig (defined in the Hamlib
      // rig back-end capabilities).
      break;

    case TransceiverFactory::PTT_method_DTR:
    case TransceiverFactory::PTT_method_RTS:
      if (!ptt_port.isEmpty ())
        {
#if defined (WIN32)
          set_conf ("ptt_pathname", ("\\\\.\\" + ptt_port).toLatin1 ().data ());
#else
          set_conf ("ptt_pathname", ptt_port.toLatin1 ().data ());
#endif
        }

      if (TransceiverFactory::PTT_method_DTR == ptt_type)
        {
          set_conf ("ptt_type", "DTR");
        }
      else
        {
          set_conf ("ptt_type", "RTS");
        }
    }
}

HamlibTransceiver::HamlibTransceiver (int model_number, TransceiverFactory::ParameterPack const& params,
                                      QObject * parent)
  : PollingTransceiver {params.poll_interval, parent}
  , rig_ {rig_init (model_number)}
  , back_ptt_port_ {TransceiverFactory::TX_audio_source_rear == params.audio_source}
  , one_VFO_ {false}
  , is_dummy_ {RIG_MODEL_DUMMY == model_number}
  , reversed_ {false}
  , freq_query_works_ {rig_ && rig_->caps->get_freq}
  , mode_query_works_ {rig_ && rig_->caps->get_mode}
  , split_query_works_ {rig_ && rig_->caps->get_split_vfo}
  , tickle_hamlib_ {false}
  , get_vfo_works_ {true}
  , set_vfo_works_ {true}
{
  if (!rig_)
    {
      throw error {tr ("Hamlib initialisation error")};
    }

  // rig_->state.obj = this;

  if (!is_dummy_)
    {
      //
      // user defined Hamlib settings
      //
      auto settings_file_name = QStandardPaths::locate (
#if QT_VERSION >= 0x050500
                                                        QStandardPaths::AppConfigLocation
#else
                                                        QStandardPaths::ConfigLocation
#endif
                                                        , "hamlib_settings.json");
      if (!settings_file_name.isEmpty ())
        {
          QFile settings_file {settings_file_name};
          qDebug () << "Using Hamlib settings file:" << settings_file_name;
          if (settings_file.open (QFile::ReadOnly))
            {
              QJsonParseError status;
              auto settings_doc = QJsonDocument::fromJson (settings_file.readAll (), &status);
              if (status.error)
                {
                  throw error {tr ("Hamlib settings file error: %1 at character offset %2")
                      .arg (status.errorString ()).arg (status.offset)};
                }
              qDebug () << "Hamlib settings JSON:" << settings_doc.toJson ();
              if (!settings_doc.isObject ())
                {
                  throw error {tr ("Hamlib settings file error: top level must be a JSON object")};
                }
              auto const& settings = settings_doc.object ();

              //
              // configuration settings
              //
              auto const& config = settings["config"];
              if (!config.isUndefined ())
                {
                  if (!config.isObject ())
                    {
                      throw error {tr ("Hamlib settings file error: config must be a JSON object")};
                    }
                  auto const& config_list = config.toObject ();
                  for (auto item = config_list.constBegin (); item != config_list.constEnd (); ++item)
                    {
                      set_conf (item.key ().toLocal8Bit ().constData ()
                                , (*item).toVariant ().toString ().toLocal8Bit ().constData ());
                    }
                }
            }
        }

      switch (rig_->caps->port_type)
        {
        case RIG_PORT_SERIAL:
          if (!params.serial_port.isEmpty ())
            {
              set_conf ("rig_pathname", params.serial_port.toLatin1 ().data ());
            }
          set_conf ("serial_speed", QByteArray::number (params.baud).data ());
          if (params.data_bits != TransceiverFactory::default_data_bits)
            {
              set_conf ("data_bits", TransceiverFactory::seven_data_bits == params.data_bits ? "7" : "8");
            }
          if (params.stop_bits != TransceiverFactory::default_stop_bits)
            {
              set_conf ("stop_bits", TransceiverFactory::one_stop_bit == params.stop_bits ? "1" : "2");
            }

          switch (params.handshake)
            {
            case TransceiverFactory::handshake_none: set_conf ("serial_handshake", "None"); break;
            case TransceiverFactory::handshake_XonXoff: set_conf ("serial_handshake", "XONXOFF"); break;
            case TransceiverFactory::handshake_hardware: set_conf ("serial_handshake", "Hardware"); break;
            default: break;
            }

          if (params.force_dtr)
            {
              set_conf ("dtr_state", params.dtr_high ? "ON" : "OFF");
            }
          if (params.force_rts)
            {
              if (TransceiverFactory::handshake_hardware != params.handshake)
                {
                  set_conf ("rts_state", params.rts_high ? "ON" : "OFF");
                }
            }
          break;

        case RIG_PORT_NETWORK:
          if (!params.network_port.isEmpty ())
            {
              set_conf ("rig_pathname", params.network_port.toLatin1 ().data ());
            }
          break;

        case RIG_PORT_USB:
          if (!params.usb_port.isEmpty ())
            {
              set_conf ("rig_pathname", params.usb_port.toLatin1 ().data ());
            }
          break;

        default:
          throw error {tr ("Unsupported CAT type")};
          break;
        }
    }

  switch (params.ptt_type)
    {
    case TransceiverFactory::PTT_method_VOX:
      set_conf ("ptt_type", "None");
      break;

    case TransceiverFactory::PTT_method_CAT:
      // Use the default PTT_TYPE for the rig (defined in the Hamlib
      // rig back-end capabilities).
      break;

    case TransceiverFactory::PTT_method_DTR:
    case TransceiverFactory::PTT_method_RTS:
      if (!params.ptt_port.isEmpty ()
          && params.ptt_port != "None"
          && (is_dummy_ || params.ptt_port != params.serial_port))
        {
#if defined (WIN32)
          set_conf ("ptt_pathname", ("\\\\.\\" + params.ptt_port).toLatin1 ().data ());
#else
          set_conf ("ptt_pathname", params.ptt_port.toLatin1 ().data ());
#endif
        }

      if (TransceiverFactory::PTT_method_DTR == params.ptt_type)
        {
          set_conf ("ptt_type", "DTR");
        }
      else
        {
          set_conf ("ptt_type", "RTS");
        }
    }

  // Make Icom CAT split commands less glitchy
  set_conf ("no_xchg", "1");

  // would be nice to get events but not supported on Windows and also not on a lot of rigs
  // rig_set_freq_callback (rig_.data (), &frequency_change_callback, this);
}

void HamlibTransceiver::error_check (int ret_code, QString const& doing) const
{
  if (RIG_OK != ret_code)
    {
      TRACE_CAT_POLL ("HamlibTransceiver", "error:" << rigerror (ret_code));
      throw error {tr ("Hamlib error: %1 while %2").arg (rigerror (ret_code)).arg (doing)};
    }
}

int HamlibTransceiver::do_start ()
{
  TRACE_CAT ("HamlibTransceiver",
             QString::fromLatin1 (rig_->caps->mfg_name).trimmed ()
             << QString::fromLatin1 (rig_->caps->model_name).trimmed ());

  error_check (rig_open (rig_.data ()), tr ("opening connection to rig"));

  // reset dynamic state
  one_VFO_ = false;
  reversed_ = false;
  freq_query_works_ = rig_->caps->get_freq;
  mode_query_works_ = rig_->caps->get_mode;
  split_query_works_ = rig_->caps->get_split_vfo;
  tickle_hamlib_ = false;
  get_vfo_works_ = true;
  set_vfo_works_ = true;

  // the Net rigctl back end promises all functions work but we must
  // test get_vfo as it determines our strategy for Icom rigs
  vfo_t vfo;
  int rc = rig_get_vfo (rig_.data (), &vfo);
  if (-RIG_ENAVAIL == rc || -RIG_ENIMPL == rc)
    {
      get_vfo_works_ = false;
      // determine if the rig uses single VFO addressing i.e. A/B and
      // no get_vfo function
      if (rig_->state.vfo_list & RIG_VFO_B)
        {
          one_VFO_ = true;
        }
    }
  else
    {
      error_check (rc, "testing getting current VFO");
    }

  if ((WSJT_RIG_NONE_CAN_SPLIT || !is_dummy_)
      && rig_->caps->set_split_vfo) // if split is possible do some extra setup
    {
      freq_t f1;
      freq_t f2;
      rmode_t m {RIG_MODE_USB};
      rmode_t mb;
      pbwidth_t w {RIG_PASSBAND_NORMAL};
      pbwidth_t wb;
      if (freq_query_works_
          && (!get_vfo_works_ || !rig_->caps->get_vfo))
        {
          // Icom have deficient CAT protocol with no way of reading which
          // VFO is selected or if SPLIT is selected so we have to simply
          // assume it is as when we started by setting at open time right
          // here. We also gather/set other initial state.
          error_check (rig_get_freq (rig_.data (), RIG_VFO_CURR, &f1), tr ("getting current frequency"));
          f1 = std::round (f1);
          TRACE_CAT ("HamlibTransceiver", "current frequency =" << f1);

          error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &m, &w), tr ("getting current mode"));
          TRACE_CAT ("HamlibTransceiver", "current mode =" << rig_strrmode (m) << "bw =" << w);

          if (!rig_->caps->set_vfo)
            {
              TRACE_CAT ("HamlibTransceiver", "rig_vfo_op TOGGLE");
              rc = rig_vfo_op (rig_.data (), RIG_VFO_CURR, RIG_OP_TOGGLE);
            }
          else
            {
              TRACE_CAT ("HamlibTransceiver", "rig_set_vfo to other VFO");
              rc = rig_set_vfo (rig_.data (), rig_->state.vfo_list & RIG_VFO_B ? RIG_VFO_B : RIG_VFO_SUB);
              if (-RIG_ENAVAIL == rc || -RIG_ENIMPL == rc)
                {
                  // if we are talking to netrigctl then toggle VFO op
                  // may still work
                  TRACE_CAT ("HamlibTransceiver", "rig_vfo_op TOGGLE");
                  rc = rig_vfo_op (rig_.data (), RIG_VFO_CURR, RIG_OP_TOGGLE);
                }
            }
          if (-RIG_ENAVAIL == rc || -RIG_ENIMPL == rc)
            {
              // we are probably dealing with rigctld so we do not
              // have completely accurate rig capabilities
              set_vfo_works_ = false;
              one_VFO_ = false; // we do not need single VFO addressing
            }
          else
            {
              error_check (rc, tr ("exchanging VFOs"));
            }

          if (set_vfo_works_)
            {
              // without the above we cannot proceed but we know we
              // are on VFO A and that will not change so there's no
              // need to execute this block
              error_check (rig_get_freq (rig_.data (), RIG_VFO_CURR, &f2), tr ("getting other VFO frequency"));
              f2 = std::round (f2);
              TRACE_CAT ("HamlibTransceiver", "rig_get_freq other frequency =" << f2);

              error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &mb, &wb), tr ("getting other VFO mode"));
              TRACE_CAT ("HamlibTransceiver", "rig_get_mode other mode =" << rig_strrmode (mb) << "bw =" << wb);

              update_other_frequency (f2);

              if (!rig_->caps->set_vfo)
                {
                  TRACE_CAT ("HamlibTransceiver", "rig_vfo_op TOGGLE");
                  error_check (rig_vfo_op (rig_.data (), RIG_VFO_CURR, RIG_OP_TOGGLE), tr ("exchanging VFOs"));
                }
              else
                {
                  TRACE_CAT ("HamlibTransceiver", "rig_set_vfo A/MAIN");
                  error_check (rig_set_vfo (rig_.data (), rig_->state.vfo_list & RIG_VFO_A ? RIG_VFO_A : RIG_VFO_MAIN), tr ("setting current VFO"));
                }

              if (f1 != f2 || m != mb || w != wb)	// we must have started with MAIN/A
                {
                  update_rx_frequency (f1);
                }
              else
                {
                  error_check (rig_get_freq (rig_.data (), RIG_VFO_CURR, &f1), tr ("getting frequency"));
                  f1 = std::round (f1);
                  TRACE_CAT ("HamlibTransceiver", "rig_get_freq frequency =" << f1);

                  error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &m, &w), tr ("getting mode"));
                  TRACE_CAT ("HamlibTransceiver", "rig_get_mode mode =" << rig_strrmode (m) << "bw =" << w);

                  update_rx_frequency (f1);
                }
            }

          // TRACE_CAT ("HamlibTransceiver", "rig_set_split_vfo split off");
          // error_check (rig_set_split_vfo (rig_.data (), RIG_VFO_CURR, RIG_SPLIT_OFF, RIG_VFO_CURR), tr ("setting split off"));
          // update_split (false);
        }
      else
        {
          vfo_t v {RIG_VFO_A};  // assume RX always on VFO A/MAIN

          if (get_vfo_works_ && rig_->caps->get_vfo)
            {
              error_check (rig_get_vfo (rig_.data (), &v), tr ("getting current VFO")); // has side effect of establishing current VFO inside hamlib
              TRACE_CAT ("HamlibTransceiver", "rig_get_vfo current VFO = " << rig_strvfo (v));
            }

          reversed_ = RIG_VFO_B == v;

          if (mode_query_works_ && !(rig_->caps->targetable_vfo & (RIG_TARGETABLE_MODE | RIG_TARGETABLE_PURE)))
            {
              if (RIG_OK == rig_get_mode (rig_.data (), RIG_VFO_CURR, &m, &w))
                {
                  TRACE_CAT ("HamlibTransceiver", "rig_get_mode current mode =" << rig_strrmode (m) << "bw =" << w);
                }
              else
                {
                  mode_query_works_ = false;
                  // Some rigs (HDSDR) don't have a working way of
                  // reporting MODE so we give up on mode queries -
                  // sets will still cause an error
                  TRACE_CAT ("HamlibTransceiver", "rig_get_mode can't do on this rig");
                }
            }
        }
      update_mode (map_mode (m));
    }

  tickle_hamlib_ = true;

  if (is_dummy_ && dummy_frequency_)
    {
      // return to where last dummy instance was
      // TODO: this is going to break down if multiple dummy rigs are used
      rig_set_freq (rig_.data (), RIG_VFO_CURR, dummy_frequency_);
      update_rx_frequency (dummy_frequency_);
      if (RIG_MODE_NONE != dummy_mode_)
        {
          rig_set_mode (rig_.data (), RIG_VFO_CURR, dummy_mode_, RIG_PASSBAND_NOCHANGE);
          update_mode (map_mode (dummy_mode_));
        }
    }

  int resolution {0};
  if (freq_query_works_)
    {
      freq_t current_frequency;
      error_check (rig_get_freq (rig_.data (), RIG_VFO_CURR, &current_frequency), tr ("getting current VFO frequency"));
      current_frequency = std::round (current_frequency);
      Frequency f = current_frequency;
      if (f && !(f % 10))
        {
          auto test_frequency = f - f % 100 + 55;
          error_check (rig_set_freq (rig_.data (), RIG_VFO_CURR, test_frequency), tr ("setting frequency"));
          freq_t new_frequency;
          error_check (rig_get_freq (rig_.data (), RIG_VFO_CURR, &new_frequency), tr ("getting current VFO frequency"));
          new_frequency = std::round (new_frequency);
          switch (static_cast<Radio::FrequencyDelta> (new_frequency - test_frequency))
            {
            case -5: resolution = -1; break;  // 10Hz truncated
            case 5: resolution = 1; break;    // 10Hz rounded
            case -15: resolution = -2; break; // 20Hz truncated
            case -55: resolution = -3; break; // 100Hz truncated
            case 45: resolution = 3; break;   // 100Hz rounded
            }
          if (1 == resolution)      // may be 20Hz rounded
            {
              test_frequency = f - f % 100 + 51;
              error_check (rig_set_freq (rig_.data (), RIG_VFO_CURR, test_frequency), tr ("setting frequency"));
              error_check (rig_get_freq (rig_.data (), RIG_VFO_CURR, &new_frequency), tr ("getting current VFO frequency"));
              if (9 == static_cast<Radio::FrequencyDelta> (new_frequency - test_frequency))
                {
                  resolution = 2;   // 20Hz rounded
                }
            }
          error_check (rig_set_freq (rig_.data (), RIG_VFO_CURR, current_frequency), tr ("setting frequency"));
        }
    }
  else
    {
      resolution = -1;          // best guess
    }

  poll ();

  TRACE_CAT ("HamlibTransceiver", "exit" << state () << "reversed =" << reversed_ << "resolution = " << resolution);
  return resolution;
}

void HamlibTransceiver::do_stop ()
{
  if (is_dummy_)
    {
      rig_get_freq (rig_.data (), RIG_VFO_CURR, &dummy_frequency_);
      dummy_frequency_ = std::round (dummy_frequency_);
      if (mode_query_works_)
        {
          pbwidth_t width;
          rig_get_mode (rig_.data (), RIG_VFO_CURR, &dummy_mode_, &width);
        }
    }
  if (rig_)
    {
      rig_close (rig_.data ());
    }

  TRACE_CAT ("HamlibTransceiver", "state:" << state () << "reversed =" << reversed_);
}

auto HamlibTransceiver::get_vfos (bool for_split) const -> std::tuple<vfo_t, vfo_t>
{
  if (get_vfo_works_ && rig_->caps->get_vfo)
    {
      vfo_t v;
      error_check (rig_get_vfo (rig_.data (), &v), tr ("getting current VFO")); // has side effect of establishing current VFO inside hamlib
      TRACE_CAT ("HamlibTransceiver", "rig_get_vfo VFO = " << rig_strvfo (v));

      reversed_ = RIG_VFO_B == v;
    }
  else if (!for_split && set_vfo_works_ && rig_->caps->set_vfo && rig_->caps->set_split_vfo)
    {
      // use VFO A/MAIN for main frequency and B/SUB for Tx
      // frequency if split since these type of radios can only
      // support this way around

      TRACE_CAT ("HamlibTransceiver", "rig_set_vfo VFO = A/MAIN");
      error_check (rig_set_vfo (rig_.data (), rig_->state.vfo_list & RIG_VFO_A ? RIG_VFO_A : RIG_VFO_MAIN), tr ("setting current VFO"));
    }
  // else only toggle available but VFOs should be substitutable

  auto rx_vfo = rig_->state.vfo_list & RIG_VFO_A ? RIG_VFO_A : RIG_VFO_MAIN;
  auto tx_vfo = (WSJT_RIG_NONE_CAN_SPLIT || !is_dummy_) && for_split
    ? (rig_->state.vfo_list & RIG_VFO_B ? RIG_VFO_B : RIG_VFO_SUB)
    : rx_vfo;
  if (reversed_)
    {
      TRACE_CAT ("HamlibTransceiver", "reversing VFOs");
      std::swap (rx_vfo, tx_vfo);
    }

  TRACE_CAT ("HamlibTransceiver", "RX VFO = " << rig_strvfo (rx_vfo) << " TX VFO = " << rig_strvfo (tx_vfo));
  return std::make_tuple (rx_vfo, tx_vfo);
}

void HamlibTransceiver::do_frequency (Frequency f, MODE m, bool no_ignore)
{
  TRACE_CAT ("HamlibTransceiver", f << "mode:" << m << "reversed:" << reversed_);

  // only change when receiving or simplex or direct VFO addressing
  // unavailable or forced
  if (!state ().ptt () || !state ().split () || !one_VFO_ || no_ignore)
    {
      // for the 1st time as a band change may cause a recalled mode to be
      // set
      error_check (rig_set_freq (rig_.data (), RIG_VFO_CURR, f), tr ("setting frequency"));
      update_rx_frequency (f);

      if (mode_query_works_ && UNK != m)
        {
          rmode_t current_mode;
          pbwidth_t current_width;
          auto new_mode = map_mode (m);
          error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &current_mode, &current_width), tr ("getting current VFO mode"));
          TRACE_CAT ("HamlibTransceiver", "rig_get_mode mode = " << rig_strrmode (current_mode) << "bw =" << current_width);

          if (new_mode != current_mode)
            {
              TRACE_CAT ("HamlibTransceiver", "rig_set_mode mode = " << rig_strrmode (new_mode));
              error_check (rig_set_mode (rig_.data (), RIG_VFO_CURR, new_mode, RIG_PASSBAND_NOCHANGE), tr ("setting current VFO mode"));

              // for the 2nd time because a mode change may have caused a
              // frequency change
              error_check (rig_set_freq (rig_.data (), RIG_VFO_CURR, f), tr ("setting frequency"));

              // for the second time because some rigs change mode according
              // to frequency such as the TS-2000 auto mode setting
              TRACE_CAT ("HamlibTransceiver", "rig_set_mode mode = " << rig_strrmode (new_mode));
              error_check (rig_set_mode (rig_.data (), RIG_VFO_CURR, new_mode, RIG_PASSBAND_NOCHANGE), tr ("setting current VFO mode"));
            }
          update_mode (m);
        }
    }
}

void HamlibTransceiver::do_tx_frequency (Frequency tx, MODE mode, bool no_ignore)
{
  TRACE_CAT ("HamlibTransceiver", tx << "reversed:" << reversed_);

  if (WSJT_RIG_NONE_CAN_SPLIT || !is_dummy_) // split is meaningless if you can't see it
    {
      auto split = tx ? RIG_SPLIT_ON : RIG_SPLIT_OFF;
      auto vfos = get_vfos (tx);
      // auto rx_vfo = std::get<0> (vfos); // or use RIG_VFO_CURR
      auto tx_vfo = std::get<1> (vfos);

      if (tx)
        {
          // Doing set split for the 1st of two times, this one
          // ensures that the internal Hamlib state is correct
          // otherwise rig_set_split_freq() will target the wrong VFO
          // on some rigs

          if (tickle_hamlib_)
            {
              // This potentially causes issues with the Elecraft K3
              // which will block setting split mode when it deems
              // cross mode split operation not possible. There's not
              // much we can do since the Hamlib Library needs this
              // call at least once to establish the Tx VFO. Best we
              // can do is only do this once per session.
              TRACE_CAT ("HamlibTransceiver", "rig_set_split_vfo split =" << split);
              auto rc = rig_set_split_vfo (rig_.data (), RIG_VFO_CURR, split, tx_vfo);
              if (tx || (-RIG_ENAVAIL != rc && -RIG_ENIMPL != rc))
                {
                  // On rigs that can't have split controlled only throw an
                  // exception when an error other than command not accepted
                  // is returned when trying to leave split mode. This allows
                  // fake split mode and non-split mode to work without error
                  // on such rigs without having to know anything about the
                  // specific rig.
                  error_check (rc, tr ("setting/unsetting split mode"));
                }
              tickle_hamlib_ = false;
              update_split (tx);
            }

          // just change current when transmitting with single VFO
          // addressing
          if (state ().ptt () && one_VFO_)
            {
              TRACE_CAT ("HamlibTransceiver", "rig_set_split_vfo split =" << split);
              error_check (rig_set_split_vfo (rig_.data (), RIG_VFO_CURR, split, tx_vfo), tr ("setting split mode"));

              error_check (rig_set_freq (rig_.data (), RIG_VFO_CURR, tx), tr ("setting frequency"));

              if (UNK != mode && mode_query_works_)
                {
                  rmode_t current_mode;
                  pbwidth_t current_width;
                  auto new_mode = map_mode (mode);
                  error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &current_mode, &current_width), tr ("getting current VFO mode"));
                  TRACE_CAT ("HamlibTransceiver", "rig_get_mode mode = " << rig_strrmode (current_mode) << "bw =" << current_width);

                  if (new_mode != current_mode)
                    {
                      TRACE_CAT ("HamlibTransceiver", "rig_set_mode mode = " << rig_strrmode (new_mode));
                      error_check (rig_set_mode (rig_.data (), RIG_VFO_CURR, new_mode, RIG_PASSBAND_NOCHANGE), tr ("setting current VFO mode"));
                    }
                }
              update_other_frequency (tx);
            }
          else if (!one_VFO_ || no_ignore)   // if not single VFO addressing and not forced
            {
              hamlib_tx_vfo_fixup fixup (rig_.data (), tx_vfo);
              if (UNK != mode)
                {
                  auto new_mode = map_mode (mode);
                  TRACE_CAT ("HamlibTransceiver", "rig_set_split_freq_mode freq = " << tx
                             << " mode = " << rig_strrmode (new_mode));
                  error_check (rig_set_split_freq_mode (rig_.data (), RIG_VFO_CURR, tx, new_mode, RIG_PASSBAND_NOCHANGE), tr ("setting split TX frequency and mode"));
                }
              else
                {
                  TRACE_CAT ("HamlibTransceiver", "rig_set_split_freq freq = " << tx);
                  error_check (rig_set_split_freq (rig_.data (), RIG_VFO_CURR, tx), tr ("setting split TX frequency"));
                }
              // Enable split last since some rigs (Kenwood for one) come out
              // of split when you switch RX VFO (to set split mode above for
              // example). Also the Elecraft K3 will refuse to go to split
              // with certain VFO A/B mode combinations.
              TRACE_CAT ("HamlibTransceiver", "rig_set_split_vfo split =" << split);
              error_check (rig_set_split_vfo (rig_.data (), RIG_VFO_CURR, split, tx_vfo), tr ("setting split mode"));
              update_other_frequency (tx);
              update_split (tx);
            }
        }
      else
        {
          // Disable split
          TRACE_CAT ("HamlibTransceiver", "rig_set_split_vfo split =" << split);
          auto rc = rig_set_split_vfo (rig_.data (), RIG_VFO_CURR, split, tx_vfo);
          if (tx || (-RIG_ENAVAIL != rc && -RIG_ENIMPL != rc))
            {
              // On rigs that can't have split controlled only throw an
              // exception when an error other than command not accepted
              // is returned when trying to leave split mode. This allows
              // fake split mode and non-split mode to work without error
              // on such rigs without having to know anything about the
              // specific rig.
              error_check (rc, tr ("setting/unsetting split mode"));
            }
          update_other_frequency (tx);
          update_split (tx);
        }
    }
}

void HamlibTransceiver::do_mode (MODE mode)
{
  TRACE_CAT ("HamlibTransceiver", mode);

  auto vfos = get_vfos (state ().split ());
  // auto rx_vfo = std::get<0> (vfos);
  auto tx_vfo = std::get<1> (vfos);

  rmode_t current_mode;
  pbwidth_t current_width;
  auto new_mode = map_mode (mode);

  // only change when receiving or simplex if direct VFO addressing unavailable
  if (!(state ().ptt () && state ().split () && one_VFO_))
    {
      error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &current_mode, &current_width), tr ("getting current VFO mode"));
      TRACE_CAT ("HamlibTransceiver", "rig_get_mode mode = " << rig_strrmode (current_mode) << "bw =" << current_width);

      if (new_mode != current_mode)
        {
          TRACE_CAT ("HamlibTransceiver", "rig_set_mode mode = " << rig_strrmode (new_mode));
          error_check (rig_set_mode (rig_.data (), RIG_VFO_CURR, new_mode, RIG_PASSBAND_NOCHANGE), tr ("setting current VFO mode"));
        }
    }

  // just change current when transmitting split with one VFO mode
  if (state ().ptt () && state ().split () && one_VFO_)
    {
      error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &current_mode, &current_width), tr ("getting current VFO mode"));
      TRACE_CAT ("HamlibTransceiver", "rig_get_mode mode = " << rig_strrmode (current_mode) << "bw =" << current_width);

      if (new_mode != current_mode)
        {
          TRACE_CAT ("HamlibTransceiver", "rig_set_mode mode = " << rig_strrmode (new_mode));
          error_check (rig_set_mode (rig_.data (), RIG_VFO_CURR, new_mode, RIG_PASSBAND_NOCHANGE), tr ("setting current VFO mode"));
        }
    }
  else if (state ().split () && !one_VFO_)
    {
      error_check (rig_get_split_mode (rig_.data (), RIG_VFO_CURR, &current_mode, &current_width), tr ("getting split TX VFO mode"));
      TRACE_CAT ("HamlibTransceiver", "rig_get_split_mode mode = " << rig_strrmode (current_mode) << "bw =" << current_width);

      if (new_mode != current_mode)
        {
          TRACE_CAT ("HamlibTransceiver", "rig_set_split_mode mode = " << rig_strrmode (new_mode));
          hamlib_tx_vfo_fixup fixup (rig_.data (), tx_vfo);
          error_check (rig_set_split_mode (rig_.data (), RIG_VFO_CURR, new_mode, RIG_PASSBAND_NOCHANGE), tr ("setting split TX VFO mode"));
        }
    }
  update_mode (mode);
}

void HamlibTransceiver::poll ()
{
#if !WSJT_TRACE_CAT_POLLS
#if defined (NDEBUG)
  rig_set_debug (RIG_DEBUG_ERR);
#else
  rig_set_debug (RIG_DEBUG_WARN);
#endif
#endif

  freq_t f;
  rmode_t m;
  pbwidth_t w;
  split_t s;

  if (get_vfo_works_ && rig_->caps->get_vfo)
    {
      vfo_t v;
      error_check (rig_get_vfo (rig_.data (), &v), tr ("getting current VFO")); // has side effect of establishing current VFO inside hamlib
      TRACE_CAT_POLL ("HamlibTransceiver", "VFO =" << rig_strvfo (v));
      reversed_ = RIG_VFO_B == v;
    }

  if ((WSJT_RIG_NONE_CAN_SPLIT || !is_dummy_)
      && rig_->caps->get_split_vfo && split_query_works_)
    {
      vfo_t v {RIG_VFO_NONE};		// so we can tell if it doesn't get updated :(
      auto rc = rig_get_split_vfo (rig_.data (), RIG_VFO_CURR, &s, &v);
      if (-RIG_OK == rc && RIG_SPLIT_ON == s)
        {
          TRACE_CAT_POLL ("HamlibTransceiver", "rig_get_split_vfo split = " << s << " VFO = " << rig_strvfo (v));
          update_split (true);
          // if (RIG_VFO_A == v)
          // 	{
          // 	  reversed_ = true;	// not sure if this helps us here
          // 	}
        }
      else if (-RIG_OK == rc)	// not split
        {
          TRACE_CAT_POLL ("HamlibTransceiver", "rig_get_split_vfo split = " << s << " VFO = " << rig_strvfo (v));
          update_split (false);
        }
      else
        {
          // Some rigs (Icom) don't have a way of reporting SPLIT
          // mode
          TRACE_CAT_POLL ("HamlibTransceiver", "rig_get_split_vfo can't do on this rig");
          // just report how we see it based on prior commands
          split_query_works_ = false;
        }
    }

  if (freq_query_works_)
    {
      // only read if possible and when receiving or simplex
      if (!state ().ptt () || !state ().split ())
        {
          error_check (rig_get_freq (rig_.data (), RIG_VFO_CURR, &f), tr ("getting current VFO frequency"));
          f = std::round (f);
          TRACE_CAT_POLL ("HamlibTransceiver", "rig_get_freq frequency =" << f);
          update_rx_frequency (f);
        }

      if ((WSJT_RIG_NONE_CAN_SPLIT || !is_dummy_)
          && state ().split ()
          && (rig_->caps->targetable_vfo & (RIG_TARGETABLE_FREQ | RIG_TARGETABLE_PURE))
          && !one_VFO_)
        {
          // only read "other" VFO if in split, this allows rigs like
          // FlexRadio to work in Kenwood TS-2000 mode despite them
          // not having a FB; command

          // we can only probe current VFO unless rig supports reading
          // the other one directly because we can't glitch the Rx
          error_check (rig_get_freq (rig_.data ()
                                     , reversed_
                                     ? (rig_->state.vfo_list & RIG_VFO_A ? RIG_VFO_A : RIG_VFO_MAIN)
                                     : (rig_->state.vfo_list & RIG_VFO_B ? RIG_VFO_B : RIG_VFO_SUB)
                                     , &f), tr ("getting other VFO frequency"));
          f = std::round (f);
          TRACE_CAT_POLL ("HamlibTransceiver", "rig_get_freq other VFO =" << f);
          update_other_frequency (f);
        }
    }

  // only read when receiving or simplex if direct VFO addressing unavailable
  if ((!state ().ptt () || !state ().split ())
      && mode_query_works_)
    {
      // We have to ignore errors here because Yaesu FTdx... rigs can
      // report the wrong mode when transmitting split with different
      // modes per VFO. This is unfortunate because that is exactly
      // what you need to do to get 4kHz Rx b.w and modulation into
      // the rig through the data socket or USB. I.e.  USB for Rx and
      // DATA-USB for Tx.
      auto rc = rig_get_mode (rig_.data (), RIG_VFO_CURR, &m, &w);
      if (RIG_OK == rc)
        {
          TRACE_CAT_POLL ("HamlibTransceiver", "rig_get_mode mode =" << rig_strrmode (m) << "bw =" << w);
          update_mode (map_mode (m));
        }
      else
        {
          TRACE_CAT_POLL ("HamlibTransceiver", "rig_get_mode mode failed with rc:" << rc << "ignoring");
        }
    }

  if (RIG_PTT_NONE != rig_->state.pttport.type.ptt && rig_->caps->get_ptt)
    {
      ptt_t p;
      auto rc = rig_get_ptt (rig_.data (), RIG_VFO_CURR, &p);
      if (-RIG_ENAVAIL != rc && -RIG_ENIMPL != rc) // may fail if
        // Net rig ctl and target doesn't
        // support command
        {
          error_check (rc, tr ("getting PTT state"));
          TRACE_CAT_POLL ("HamlibTransceiver", "rig_get_ptt PTT =" << p);
          update_PTT (!(RIG_PTT_OFF == p));
        }
    }

#if !WSJT_TRACE_CAT_POLLS
#if WSJT_HAMLIB_TRACE
#if WSJT_HAMLIB_VERBOSE_TRACE
  rig_set_debug (RIG_DEBUG_TRACE);
#else
  rig_set_debug (RIG_DEBUG_VERBOSE);
#endif
#elif defined (NDEBUG)
  rig_set_debug (RIG_DEBUG_ERR);
#else
  rig_set_debug (RIG_DEBUG_WARN);
#endif
#endif
}

void HamlibTransceiver::do_ptt (bool on)
{
  TRACE_CAT ("HamlibTransceiver", on << state () << "reversed =" << reversed_);
  if (on)
    {
      if (RIG_PTT_NONE != rig_->state.pttport.type.ptt)
        {
          TRACE_CAT ("HamlibTransceiver", "rig_set_ptt PTT = true");
          error_check (rig_set_ptt (rig_.data (), RIG_VFO_CURR
                                    , RIG_PTT_RIG_MICDATA == rig_->caps->ptt_type && back_ptt_port_
                                    ? RIG_PTT_ON_DATA : RIG_PTT_ON), tr ("setting PTT on"));
        }
    }
  else
    {
      if (RIG_PTT_NONE != rig_->state.pttport.type.ptt)
        {
          TRACE_CAT ("HamlibTransceiver", "rig_set_ptt PTT = false");
          error_check (rig_set_ptt (rig_.data (), RIG_VFO_CURR, RIG_PTT_OFF), tr ("setting PTT off"));
        }
    }

  update_PTT (on);
}

void HamlibTransceiver::set_conf (char const * item, char const * value)
{
  token_t token = rig_token_lookup (rig_.data (), item);
  if (RIG_CONF_END != token)	// only set if valid for rig model
    {
      error_check (rig_set_conf (rig_.data (), token, value), tr ("setting a configuration item"));
    }
}

QByteArray HamlibTransceiver::get_conf (char const * item)
{
  token_t token = rig_token_lookup (rig_.data (), item);
  QByteArray value {128, '\0'};
  if (RIG_CONF_END != token)	// only get if valid for rig model
    {
      error_check (rig_get_conf (rig_.data (), token, value.data ()), tr ("getting a configuration item"));
    }
  return value;
}

auto HamlibTransceiver::map_mode (rmode_t m) const -> MODE
{
  switch (m)
    {
    case RIG_MODE_AM:
    case RIG_MODE_SAM:
    case RIG_MODE_AMS:
    case RIG_MODE_DSB:
      return AM;

    case RIG_MODE_CW:
      return CW;

    case RIG_MODE_CWR:
      return CW_R;

    case RIG_MODE_USB:
    case RIG_MODE_ECSSUSB:
    case RIG_MODE_SAH:
    case RIG_MODE_FAX:
      return USB;

    case RIG_MODE_LSB:
    case RIG_MODE_ECSSLSB:
    case RIG_MODE_SAL:
      return LSB;

    case RIG_MODE_RTTY:
      return FSK;

    case RIG_MODE_RTTYR:
      return FSK_R;

    case RIG_MODE_PKTLSB:
      return DIG_L;

    case RIG_MODE_PKTUSB:
      return DIG_U;

    case RIG_MODE_FM:
    case RIG_MODE_WFM:
      return FM;

    case RIG_MODE_PKTFM:
      return DIG_FM;

    default:
      return UNK;
    }
}

rmode_t HamlibTransceiver::map_mode (MODE mode) const
{
  switch (mode)
    {
    case AM: return RIG_MODE_AM;
    case CW: return RIG_MODE_CW;
    case CW_R: return RIG_MODE_CWR;
    case USB: return RIG_MODE_USB;
    case LSB: return RIG_MODE_LSB;
    case FSK: return RIG_MODE_RTTY;
    case FSK_R: return RIG_MODE_RTTYR;
    case DIG_L: return RIG_MODE_PKTLSB;
    case DIG_U: return RIG_MODE_PKTUSB;
    case FM: return RIG_MODE_FM;
    case DIG_FM: return RIG_MODE_PKTFM;
    default: break;
    }
  return RIG_MODE_USB;	// quieten compiler grumble
}


code:
#ifndef HAMLIB_TRANSCEIVER_HPP_
#define HAMLIB_TRANSCEIVER_HPP_

#include <tuple>

#include <QString>

#include <hamlib/rig.h>

#include "TransceiverFactory.hpp"
#include "PollingTransceiver.hpp"

extern "C"
{
  typedef struct rig RIG;
  struct rig_caps;
  typedef int vfo_t;
}

// hamlib transceiver and PTT mostly delegated directly to hamlib Rig class
class HamlibTransceiver final
  : public PollingTransceiver
{
  Q_OBJECT;                     // for translation context

 public:
  static void register_transceivers (TransceiverFactory::Transceivers *);
  static void unregister_transceivers ();

  explicit HamlibTransceiver (int model_number, TransceiverFactory::ParameterPack const&,
                              QObject * parent = nullptr);
  explicit HamlibTransceiver (TransceiverFactory::PTTMethod ptt_type, QString const& ptt_port,
                              QObject * parent = nullptr);

 private:
  int do_start () override;
  void do_stop () override;
  void do_frequency (Frequency, MODE, bool no_ignore) override;
  void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
  void do_mode (MODE) override;
  void do_ptt (bool) override;

  void poll () override;

  void error_check (int ret_code, QString const& doing) const;
  void set_conf (char const * item, char const * value);
  QByteArray get_conf (char const * item);
  Transceiver::MODE map_mode (rmode_t) const;
  rmode_t map_mode (Transceiver::MODE mode) const;
  std::tuple<vfo_t, vfo_t> get_vfos (bool for_split) const;

  struct RIGDeleter {static void cleanup (RIG *);};
  QScopedPointer<RIG, RIGDeleter> rig_;

  bool back_ptt_port_;
  bool one_VFO_;
  bool is_dummy_;

  // these are saved on destruction so we can start new instances
  // where the last one left off
  static freq_t dummy_frequency_;
  static rmode_t dummy_mode_;

  bool mutable reversed_;

  bool freq_query_works_;
  bool mode_query_works_;
  bool split_query_works_;
  bool tickle_hamlib_;          // Hamlib requires a
                                // rig_set_split_vfo() call to
                                // establish the Tx VFO
  bool get_vfo_works_;          // Net rigctl promises what it can't deliver
  bool set_vfo_works_;          // More rigctl promises which it can't deliver
};

#endif

license: mit


code:
#include "HelpTextWindow.hpp"

#include <QApplication>
#include <QString>
#include <QPalette>
#include <QFile>
#include <QTextStream>

#include "qt_helpers.hpp"
#include <MessageBox.hpp>

HelpTextWindow::HelpTextWindow (QString const& title, QString const& file_name, QFont const& font, QWidget * parent)
  : QLabel {parent, Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint}
{
  QFile source {file_name};
  if (!source.open (QIODevice::ReadOnly | QIODevice::Text))
    {
      MessageBox::warning_message (this, tr ("Help file error")
                                   , tr ("Cannot open \"%1\" for reading").arg (source.fileName ())
                                   , tr ("Error: %1").arg (source.errorString ()));
      return;
    }
  setText (QTextStream {&source}.readAll ());
  setWindowTitle(QApplication::applicationName () + " - " + title);
  setMargin (10);
  setBackgroundRole (QPalette::Base);
  setAutoFillBackground (true);
  setStyleSheet (font_as_stylesheet (font));
}


code:
#ifndef HELP_TEXT_WINDOW_HPP__
#define HELP_TEXT_WINDOW_HPP__

#include <QLabel>
#include <QFont>

class QString;

class HelpTextWindow final
  : public QLabel
{
public:
  HelpTextWindow (QString const& title, QString const& file_name, QFont const& = QFont {}, QWidget * parent = nullptr);
};

#endif


code:
#include "HintedSpinBox.hpp"

#include <algorithm>

auto HintedSpinBox::values (values_type values, bool set_min_max) -> values_type
{
  // replace any prior values
  std::swap (values, values_);

  // sort values and make unique
  std::sort (values_.begin (), values_.end ());
  auto last = std::unique (values_.begin (), values_.end ());
  values_.erase (last, values_.end ());
  if (set_min_max)
    {
      setMinimum (values_.front ());
      setMaximum (values_.back ());
    }
  return values;                // return old values
}

void HintedSpinBox::stepBy (int steps)
{
  if (values_.size ())
    {
      if (steps > 0)
        {
          auto p = std::upper_bound (values_.begin (), values_.end (), value ());
          if (p != values_.end () && *p <= maximum ())
            {
              setValue (*p);
            }
          else if (wrapping ())
            {
              setValue (values_.front ());
            }
        }
      else if (steps < 0)
        {
          auto p = std::lower_bound (values_.begin (), values_.end (), value ());
          if (p != values_.begin () && *(p - 1) >= minimum ())
            {
              setValue (*(p - 1));
            }
          else if (wrapping ())
            {
              setValue (values_.back ());
            }
        }
    }
  else
    {
      QSpinBox::stepBy (steps); // no values so revert to QSpinBox behaviour
    }
}


code:
#ifndef HINTED_SPIN_BOX_HPP_
#define HINTED_SPIN_BOX_HPP_

#include <vector>

#include <QSpinBox>

//
// HintedSpinBox - QSpinBox refinement with an optional sequence of
//                 discrete hints
//
//  The  hint  sequence  values  are  used  as  step  values  and  can
//  optionally  define the  minimum and  maximum value.   Intermediate
//  values are  allowed but  the step  up/down arrows  and accelerator
//  keys will only use the hints.
//
//  Ensure that the default minimum  and maximum values are sufficient
//  to allow initial initialization if done before the hints are set.
//
class HintedSpinBox
  : public QSpinBox
{
public:
  typedef std::vector<int> values_type;

  HintedSpinBox (QWidget * parent = nullptr)
    : QSpinBox {parent}
  {
  }

  // Initialize sequence of values allowed.
  //
  // This  spin box  behaves exactly  as  a standard  QSpinBox if  the
  // sequence of values is empty.
  //
  // The minimum and  maximum are automatically set to  the lowest and
  // highest value provided if required.
  //
  // Returns the previous sequence.
  values_type values (values_type values, bool set_min_max = true);

  // Read access to the values.
  values_type const& values () const {return values_;}

protected:
  // override the QSpinBox stepping
  void stepBy (int steps) override;

private:
  std::vector<int> values_;
};

#endif


code:
#include "IARURegions.hpp"

#include <algorithm>

#include <QString>
#include <QVariant>
#include <QModelIndex>
#include <QMetaType>

#include "moc_IARURegions.cpp"

namespace
{
  // human readable strings for each Region enumeration value
  char const * const region_names[] =
  {
    "All",
    "Region 1",
    "Region 2",
    "Region 3",
  };
  std::size_t constexpr region_names_size = sizeof (region_names) / sizeof (region_names[0]);
}

IARURegions::IARURegions (QObject * parent)
  : QAbstractListModel {parent}
{
  static_assert (region_names_size == SENTINAL,
                 "region_names array must match Region enumeration");
}

char const * IARURegions::name (Region r)
{
  return region_names[static_cast<int> (r)];
}

auto IARURegions::value (QString const& s) -> Region
{
  auto end = region_names + region_names_size;
  auto p = std::find_if (region_names, end
                         , [&s] (char const * const name) {
                           return name == s;
                         });
  return p != end ? static_cast<Region> (p - region_names) : ALL;
}

QVariant IARURegions::data (QModelIndex const& index, int role) const
{
  QVariant item;

  if (index.isValid ())
    {
      auto const& row = index.row ();
      switch (role)
        {
        case Qt::ToolTipRole:
        case Qt::AccessibleDescriptionRole:
          item = tr ("IARU Region");
          break;

        case Qt::EditRole:
          item = static_cast<Region> (row);
          break;

        case Qt::DisplayRole:
        case Qt::AccessibleTextRole:
          item = region_names[row];
          break;

        case Qt::TextAlignmentRole:
          item = Qt::AlignHCenter + Qt::AlignVCenter;
          break;
        }
    }

  return item;
}

QVariant IARURegions::headerData (int section, Qt::Orientation orientation, int role) const
{
  QVariant result;

  if (Qt::DisplayRole == role && Qt::Horizontal == orientation)
    {
      result = tr ("IARU Region");
    }
  else
    {
      result = QAbstractListModel::headerData (section, orientation, role);
    }

  return result;
}

#if !defined (QT_NO_DEBUG_STREAM)
ENUM_QDEBUG_OPS_IMPL (IARURegions, Region);
#endif

ENUM_QDATASTREAM_OPS_IMPL (IARURegions, Region);
ENUM_CONVERSION_OPS_IMPL (IARURegions, Region);


code:
#ifndef IARU_REGIONS_HPP__
#define IARU_REGIONS_HPP__

#include <QAbstractListModel>

#include "qt_helpers.hpp"

class QString;
class QVariant;
class QModelIndex;

//
// Class IARURegions - Qt model that implements the list of IARU regions
//
//
// Responsibilities
//
// 	Provides  a  single column  list  model  that contains  the  human
// 	readable  string  version  of  the  data  region  in  the  display
// 	role. Also  provided is  a translatable  column header  string and
// 	tool tip string.
//
//
// Collaborations
//
// 	Implements a concrete sub-class of the QAbstractListModel class.
//
class IARURegions final
  : public QAbstractListModel
{
  Q_OBJECT
  Q_ENUMS (Region)

public:
  //
  // This enumeration  contains the  supported regions,  to complement
  // this an  array of  human readable  strings in  the implementation
  // (IARURegions.cpp) must be maintained in parallel.
  //
  enum Region
  {
    ALL,                        // matches with all regions
    R1,
    R2,
    R3,
    SENTINAL                    // this must be last
  };
  Q_ENUM (Region)

  explicit IARURegions (QObject * parent = nullptr);

  // translate between enumeration and human readable strings
  static char const * name (Region);
  static Region value (QString const&);

  // Implement the QAbstractListModel interface
  int rowCount (QModelIndex const& parent = QModelIndex {}) const override
  {
    return parent.isValid () ? 0 : SENTINAL; // Number of regionss in Region enumeration class
  }
  QVariant data (QModelIndex const&, int role = Qt::DisplayRole) const override;
  QVariant headerData (int section, Qt::Orientation, int = Qt::DisplayRole) const override;
};

// Qt boilerplate  to make the IARURegions::region  enumeration a type
// that can  be streamed and  queued as a  signal argument as  well as
// showing the human readable string when output to debug streams.
#if QT_VERSION < 0x050500
// Qt 5.5 introduces the Q_ENUM macro which automatically registers
// the meta-type
Q_DECLARE_METATYPE (IARURegions::Region);
#endif

#if !defined (QT_NO_DEBUG_STREAM)
ENUM_QDEBUG_OPS_DECL (IARURegions, Region);
#endif

ENUM_QDATASTREAM_OPS_DECL (IARURegions, Region);
ENUM_CONVERSION_OPS_DECL (IARURegions, Region);

#endif


code: Detailed instructions coming soon. In the meantime, follow INSTALL-WSJTX instructions, substituting the WSJT-X repository with this one.

path: INSTALL


code:
 __       __   ______      _____  ________      __    __
|  \  _  |  \ /      \    |     \|        \    |  \  |  \
| $$ / \ | $$|  $$$$$$\    \$$$$$ \$$$$$$$$    | $$  | $$
| $$/  $\| $$| $$___\$$      | $$   | $$ ______ \$$\/  $$
| $$  $$$\ $$ \$$    \  __   | $$   | $$|      \ >$$  $$
| $$ $$\$$\$$ _\$$$$$$\|  \  | $$   | $$ \$$$$$$/  $$$$\
| $$$$  \$$$$|  \__| $$| $$__| $$   | $$       |  $$ \$$\
| $$$    \$$$ \$$    $$ \$$    $$   | $$       | $$  | $$
 \$$      \$$  \$$$$$$   \$$$$$$     \$$        \$$   \$$

Installing WSJT-X
=================

Binary packages of WSJT-X are available from the project web site:

       http://www.physics.princeton.edu/pulsar/K1JT/wsjtx.html

Building from Source
====================

On Linux systems  some of the prerequisite libraries  are available in
the mainstream distribution repositories. They  are Qt v5 and FFTW v3.
For MS  Windows see the section  "Building from Source on  MS Windows"
below. For  Apple Mac see the  section "Building from Source  on Apple
Mac".

Qt v5, preferably v5.1 or later is required to build WSJT-X.

Qt v5 multimedia  support and serial port is necessary  as well as the
core  Qt   v5  components,  normally  installing   the  Qt  multimedia
development  package   and  Qt  serialport  development   package  are
sufficient to pull in all the required Qt components and dependants as
a  single  transaction.  On  some systems  the  Qt  multimedia  plugin
component is  separate in the  distribution repository an it  may also
need installing.

The single precision FFTW v3  library libfftw3f is required along with
the  libfftw library  development  package.   Normally installing  the
library  development  package  pulls  in all  the  FFTW  v3  libraries
including the single precision variant.

The Hamlib library optionally requires  the libusb-1.0 library, if the
development   version  (libusb-1.0-dev)   is  available   Hamlib  will
configure its  custom USB device  back end  drivers. Most rigs  do not
require this so normally you  can choose not to install libusb-1.0-dev
but if you have  a SoftRock USB or similar SDR that  uses a custom USB
interface then it is required.

The Hamlib  library is  required. Currently WSJT-X  needs to  be built
using a  forked version of the  Hamlib git master. This  fork contains
patches  not yet  accepted by  the Hamlib  development team  which are
essential for  correct operation of  WSJT-X. To build the  Hamlib fork
from sources something like the following recipe should suffice:

$ mkdir ~/hamlib-prefix
$ cd ~/hamlib-prefix
$ git clone git://git.code.sf.net/u/bsomervi/hamlib src
$ cd src
$ git checkout integration
$ ./bootstrap
$ mkdir ../build
$ cd ../build
$ ../src/configure --prefix=$HOME/hamlib-prefix \
   --disable-shared --enable-static \
   --without-cxx-binding --disable-winradio \
   CFLAGS="-g -O2 -fdata-sections -ffunction-sections" \
   LDFLAGS="-Wl,--gc-sections"
$ make
$ make install-strip

This will build a binary  hamlib package located at ~/hamlib-prefix so
you will need  to add that to your CMAKE_PREFIX_PATH  variable in your
WSJT-X build.  On Linux  that is  probably the only  path you  have on
CMAKE_PREFIX_PATH  unless  you  are   using  a  locally  installed  Qt
installation.

To get the  sources either download and extract a  source tarball from
the project web site or preferably fetch the sources directly from the
project's  subversion repository.  The  project svn  repository has  a
non-standard layout  in that the WSJT-X  project is not on  the trunk,
instead the main code line is in a branch at ^/branches/wsjtx

$ mkdir -p ~/wsjtx-prefix/build
$ cd ~/wsjtx-prefix
$ svn checkout svn://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx src

To build WSJT-X you will need CMake and asciidoc installed.

$ cd ~/wsjtx-prefix/build
$ cmake -D CMAKE_PREFIX_PATH=~/hamlib-prefix ../src
$ cmake --build .
$ cmake --build . --target install

The recipe  above will install  into /usr by  default, if you  wish to
install  in  you own  directory  you  can  add  a prefix-path  to  the
configure step like:

$ cd ~/wsjtx-prefix/build
$ cmake -D CMAKE_PREFIX_PATH=~/hamlib-prefix \
   -D CMAKE_INSTALL_PREFIX=~/wsjtx-prefix ../src
$ cmake --build .
$ cmake --build . --target install

this will install WSJT-X at ~/wsjtx-prefix.

Building from Source on MS Windows
==================================

Because  building on  MS  Windows  is quite  complicated  there is  an
Software Development Kit available  that provides all the prerequisite
libraries and tools for building  WSJT-X. This SDK is called JT-SDK-QT
which is documented here:

http://physics.princeton.edu/pulsar/K1JT/wsjtx-doc/dev-guide-main.html

If you need to build Hamlib rather than use the Hamlib kit included in
the  JT-SDK the  following recipe  should help.  Reasons for  building
Hamlib from source might include picking up the very latest patches or
building a different branch that you wish to contribute to.

Hamlib optionally depends upon  libusb-1.0, see "Building from Source"
above  for more  details.   If you  wish to  include  support for  the
optional  custom  USB  Hamlib  rig   drivers  then  you  must  install
libusb-1.0 before  building Hamlib. The  package may be  obtained from
http://libusb.info/,  install   it  in  a  convenient   location  like
C:\Tools.

On Windows there is a complication in that the compilers used to build
Qt  and WSJT-X  are the  MinGW ones  bundled with  the Qt  package but
Hamlib needs to be build from an MSYS shell with the tools required to
build  an autotools  project. This  means that  you need  to tell  the
Hamlib configuration  to use  the Qt bundled  MinGW compilers  (if you
don't  then  the  thread  support   library  use  by  Hamlib  will  be
incompatible  with that  used by  Qt and  WSJT-X). So  on Windows  the
Hamlib build recipe is something like:

In an MSYS shell:-

$ mkdir ~/hamib-prefix
$ cd ~/hamlib-prefix
$ git clone git://git.code.sf.net/u/bsomervi/hamlib src
$ cd src
$ git checkout integration
$ ./bootstrap
$ mkdir ../build
$ cd ../build
   ../src/configure --prefix=$HOME/hamlib-prefix \
   --disable-shared --enable-static \
   --without-cxx-binding --disable-winradio \
   CC=<path-to-Qt-MinGW-tools>/gcc \
   CXX=<path-to-Qt-MinGW-tools>/g++ \
   CFLAGS="-g -O2 -fdata-sections -ffunction-sections -I<path-to-libusb-1.0>/include" \
   LDFLAGS="-Wl,--gc-sections" \
   LIBUSB_LIBS="-L<path-to-libusb-1.0>/MinGW32/dll -lusb-1.0"
$ make
$ make install

NOTE: <path-to-Qt-MinGQ-tools>  should be substituted with  the actual
path   to  your   Qt   bundled  tools   e.g  on   my   system  it   is
C:\Tools\Qt\Tools\mingw530_32\bin

NOTE: <path-to-libusb-1.0> should be  substituted with the actual path
to  your libusb-1.0  installation directory  e.g. on  my system  it is
C:\Tools\libusb-1.0.20

This   will    leave   a   Hamlib   binary    package   installed   at
c:/Users/<user-name>/hamlib-prefix which  is what needs to  be on your
CMAKE_PREFIX_PATH. On  Windows you  almost certainly  will be  using a
CMake tool chain  file and this is  where you will need  to specify the
Hamlib binary location as one of the paths in CMAKE_PREFIX_PATH.

Building from Source on Apple Mac
=================================

These instructions are adapted from my Evernote page at:

    https://www.evernote.com/pub/bsomervi/wsjt-xmacbuilds

There are several  ways to get the required GNU  and other open source
tools and libraries installed, my preference is MacPorts because it is
easy to use and does everything we need.

You  will need  Xcode, MacPorts,  CMake  and, Qt.   The Xcode  install
instructions are included in the MacPorts documentation.

MacPorts
--------
Install MacPorts from instructions here:

    http://www.macports.org/install.php

More detailed instructions are available in the documentation:

    https://guide.macports.org

The ports that need to be installed are:

autoconf
automake
libtool
pkgconfig
texinfo
gcc5
fftw-3-single +gcc5
asciidoc
libusb-devel

These are installed by typing:

$ sudo port install autoconf automake \
   libtool pkgconfig texinfo gcc5 asciidoc \
   fftw-3-single +gcc5 libusb-devel

Once complete you should have all the tools required to build WSJT-X.

Uninstalling MacPorts
---------------------
If at some point  you wish to remove the ports  from your machine. The
instructions are here:

    https://guide.macports.org/#installing.macports.uninstalling .

Hamlib
------
First fetch hamlib from the repository, in this case my fork of Hamlib
3 until the official repository has all the fixes we need:

$ mkdir -p ~/hamlib-prefix/build
$ cd ~/hamlib-prefix
$ git clone git://git.code.sf.net/u/bsomervi/hamlib src
$ cd src
$ git checkout integration
$ ./bootstrap

The integration  branch is my system  testing branch which has  all my
latest published changes.

To build:

$ cd ~/hamlib-prefix/build
$ ../src/configure \
   --enable-static \
   --disable-shared \
   --disable-winradio \
   --prefix=$HOME/hamlib-prefix \
   CFLAGS="-g -O2 -mmacosx-version-min=10.7 -I/opt/local/include" \
   LIBUSB_LIBS="-L/opt/local/lib -lusb-1.0"
$ make
$ make install-strip

The   above  commands   will  build   hamlib  and   install  it   into
~/hamlib-prefix. If `make install-strip` fails, try `make install`.

Qt
--

NOTE: As of Qt  v5.4 building Qt from source on Mac OS  X is no longer
necessary since the  Qt team have switched to using  the modern libc++
Standard    C++   Library    for    all    distributable   run    time
components. Instead you may simply  download a binary installer for OS
X 64-bit. The binary installer is here:

    http://www.qt.io/download

The    binary   Qt    distributions    prior   to    Qt   v5.4    from
http://www.qt.io/download unfortunately are built to use the libstdc++
C++ support  library, WSJT-X uses  a less geriatric C++  dialect which
uses the  libc++ C++  support library.   This means  that you  need to
build Qt from sources. This is not difficult but does take some time.

Download        the        Qt        source        tarball        from
http://www.qt.io/download-open-source/,  the link  is  about half  way
down the page, you want the full  sources tar ball shown as a 'tar.gz'
link.

Unpack the sources and cd into the top level directory then type:

$ ./configure -prefix ~/local/qt-macx-clang -opensource \
    -confirm-license -platform macx-clang -silent -nomake tests \
    -nomake examples -sdk macosx10.10 -skip qtwebkit \
    -skip qtwebkit-examples -skip qtquick1 -skip qtconnectivity \
    -skip qtlocation -skip qtsensors -skip qtscript \
    -skip qtwebsockets -skip qtwebengine -skip qtwebchannel \
    -skip qtwayland -skip qtquickcontrols -skip qtdeclarative \
    -skip qtxmlpatterns -skip qtenginio
$ make -j4
$ make install

If you are building on 10.8 or  don't have the 10.10 Mac SDK (Xcode 6)
available, you can substitute '-sdk macosx10.9' above.

The build above will take a few hours to complete.

CMake
-----
Although CMake  is available via MacPorts  I prefer to use  the binary
installer  from cake.org  as  the MacPorts  port  doesn't include  the
graphical CMake tool cmake-gui which I find quite useful.

Fetch    the    latest    CMake     universal    64-bit    DMG    from
http://www.cmake.org/download/  open the  DMG then  drag and  drop the
application bundle onto the supplied /Applications link.

To  complete  the  install  process  you need  to  run  the  CMake-gui
application as root from a terminal shell as follows:

$ sudo "/Applications/CMake.app/Contents/MacOS/cmake" --install

that installs  the CMake command  line tools  which you can  verify by
typing into a terminal window:

$ cmake --version

If the install command above fails  with a "No such file or directory"
error, that probably means that /usr/local/bin does not exist. You can
create it correctly with the following commands:

$ sudo mkdir -p /usr/local/bin
$ sudo chmod 755 /usr/local/bin
$ sudo chgrp wheel /usr/local/bin

and then retry the install command.

WSJT-X
------
First fetch the source from the repository:

$ mkdir -p ~/wsjtx-prefix/build
$ cd ~/wsjtx-prefix
$ svn checkout svn://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx src

this links to the Subversion repository in a read-only fashion, if you
intend to  contribute to the project  then you probably want  to get a
developer login  and use a read-write  checkout. Even if you  don't it
can be upgraded at a later date.

The checkout  is of  the latest  code on the  project trunk,  i.e. the
development  branch. You  can easily  switch the  checkout to  another
branch  or  even  a  tag  if  you want  to  build  a  prior  published
generation. For now  we will build the latest  development sources. To
configure:

$ cd ~/wsjtx-prefix/build
$ FC=gfortran-mp-5 \
   cmake \
   -D CMAKE_PREFIX_PATH="~/Qt/5.7/clang_64;~/hamlib-prefix;/opt/local" \
   -D CMAKE_INSTALL_PREFIX=~/wsjtx-prefix \
   -D CMAKE_OSX_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk \
   ~/wsjtx-prefix/src

Substitute the  Mac OS X SDK  version you have installed  in the above
command if you have a different version from 10.11.

The CMAKE_PREFIX_PATH variable specifies where CMake should look first
for other packages, the two  elements may be different depending where
you have installed Qt and what version you have (~/local/qt-macx-clang
if  you have  built  Qt from  sources  as described  above  in the  Qt
section) and where you installed  Hamlib (i.e.  the --prefix configure
option above in the Hamlib section).

If you already have the fftw3-dev  package installed on your system it
may well get selected in preference to  the one you built above in the
MacPorts installation.  It is  unlikely that  a prior  installation of
libfftw3f is  correctly configured  for use in  a WSJT-X  package, the
CMAKE_PREFIX_PATH above  is augmented  with the  MacPorts installation
location  (/opt/local)  to  ensure  the  correct  libfftw3f.dylib  and
headers are located.

To build:

$ cmake --build .
$ cmake --build . --target install

which installs the WSJT-X application bundle into ~/wsjtx-prefix

Updating and Rebuilding Hamlib
==============================

From  time  to time  new  fixes  will be  pushed  to  the Hamlib  fork
repository integration branch. To pick them up type:

$ cd ~/hamlib-prefix/src
$ git pull

To rebuild hamlib with the changed sources:

$ cd ~/hamlib-prefix/build
$ make
$ make install-strip

Updating and Rebuilding WSJT-X
==============================

To update to the latest sources type:

$ cd ~/wsjtx-prefix/src
$ svn update
$ cd ~/wsjtx-prefix/build
$ cmake --build .
$ cmake --build . --target install

73
Bill
G4WJS.

path: INSTALL-WSJTX


code:
#include "LettersSpinBox.hpp"

#include <QString>
#include "moc_LettersSpinBox.cpp"

QString LettersSpinBox::textFromValue (int value) const
{
  QString text;
  do {
    auto digit = value % 26;
    value /= 26;
    text = QChar {lowercase_ ? 'a' + digit : 'A' + digit} + text;
  } while (value);
  return text;
}

int LettersSpinBox::valueFromText (QString const& text) const
{
  int value {0};
  for (int index = text.size (); index > 0; --index)
    {
      value = value * 26 + text[index - 1].toLatin1 () - (lowercase_ ? 'a' : 'A');
    }
  return value;
}


code:
#ifndef LETTERS_SPIN_BOX_HPP_
#define LETTERS_SPIN_BOX_HPP_

#include <QSpinBox>

class QString;

//
// LettersSpinBox - select from consecutive letters
//
class LettersSpinBox final
  : public QSpinBox
{
  Q_OBJECT
  Q_PROPERTY (bool lowercase MEMBER lowercase_)

public:
  LettersSpinBox (QWidget * parent = nullptr)
    : QSpinBox {parent}
    , lowercase_ {false}
  {
  }

  QString textFromValue (int) const override;
  int valueFromText (QString const&) const override;

private:
  bool lowercase_;
};

#endif


code:
#include "LiveFrequencyValidator.hpp"

#include <QLocale>
#include <QString>
#include <QComboBox>
#include <QLineEdit>

#include "Bands.hpp"
#include "FrequencyList.hpp"

#include "moc_LiveFrequencyValidator.cpp"

LiveFrequencyValidator::LiveFrequencyValidator (QComboBox * combo_box
                                                , Bands const * bands
                                                , FrequencyList_v2 const * frequencies
                                                , Frequency const * nominal_frequency
                                                , QWidget * parent)
  : QRegExpValidator {
      QRegExp {       // frequency in MHz or band
        bands->data (QModelIndex {}).toString () // out of band string
          + QString {R"(|((\d{0,6}(\)"}    // or up to 6 digits
          + QLocale {}.decimalPoint () // (followed by decimal separator
          + R"(\d{0,2})?)([Mm]{1,2}|([Cc][Mm])))|(\d{0,6}(\)" // followed by up to 2 digits and either 'm' or 'cm' or 'mm' (case insensitive))
          + QLocale {}.decimalPoint () // or a decimal separator
          + R"(\d{0,6})?)|(\d{0,3}(\)"        //  followed by up to 6
                                              //  digits or a decimal number
          + QLocale {}.decimalPoint () // or a decimal separator
          + R"(\d{0,6})?[Kk]))"        // followed by a 'k' or 'K'
      }
      , parent
    }
  , bands_ {bands}
  , frequencies_ {frequencies}
  , nominal_frequency_ {nominal_frequency}
  , combo_box_ {combo_box}
{
}

auto LiveFrequencyValidator::validate (QString& input, int& pos) const -> State
{
  auto state = QRegExpValidator::validate (input, pos);
  // by never being Acceptable we force fixup calls on ENTER or
  // losing focus
  return Acceptable == state ? Intermediate : state;
}

void LiveFrequencyValidator::fixup (QString& input) const
{
  QRegExpValidator::fixup (input);
  if (!bands_->oob ().startsWith (input))
    {
      if (input.contains ('m', Qt::CaseInsensitive))
        {
          input = input.toLower ();

          QVector<QVariant> frequencies;
          for (auto const& item : frequencies_->frequency_list ())
            {
              if (bands_->find (item.frequency_) == input)
                {
                  frequencies << item.frequency_;
                }
            }
          if (!frequencies.isEmpty ())
            {
              Q_EMIT valid (frequencies.first ().value<Frequency> ());
            }
          else
            {
              input = QString {};
            }
        }
      else if (input.contains (QChar {'k'}, Qt::CaseInsensitive))
        {
          // kHz in current MHz input
          auto f = Radio::frequency (input.remove (QChar {'k'}, Qt::CaseInsensitive), 3);
          f += *nominal_frequency_ / 1000000u * 1000000u;
          input = bands_->find (f);
          Q_EMIT valid (f);
        }
      else
        {
          // frequency input
          auto f = Radio::frequency (input, 6);
          input = bands_->find (f);
          Q_EMIT valid (f);
        }

      if (bands_->oob () == input)
        {
          combo_box_->lineEdit ()->setStyleSheet ("QLineEdit {color: yellow; background-color : red;}");
        }
      else
        {
          combo_box_->lineEdit ()->setStyleSheet ({});
        }
      combo_box_->setCurrentText (input);
    }
}


code:
#ifndef LIVE_FREQUENCY_VALIDATOR_HPP__
#define LIVE_FREQUENCY_VALIDATOR_HPP__

#include <QObject>
#include <QRegExpValidator>

#include "Radio.hpp"

class Bands;
class FrequencyList_v2;
class QComboBox;
class QWidget;

//
// Class LiveFrequencyValidator
//
//	QLineEdit validator that controls input to an editable
//	QComboBox where the user can enter a valid band or a valid
//	frequency in megahetz.
//
// Collabrations
//
//	Implements the QRegExpValidator interface. Validates input
//	from the supplied QComboBox as either a valid frequency in
//	megahertz or a valid band as defined by the supplied column of
//	the supplied QAbstractItemModel.
//
class LiveFrequencyValidator final
  : public QRegExpValidator
{
  Q_OBJECT;

public:
  using Frequency = Radio::Frequency;

  LiveFrequencyValidator (QComboBox * combo_box // associated combo box
                          , Bands const * bands // bands model
                          , FrequencyList_v2 const * frequencies // working frequencies model
                          , Frequency const * nominal_frequency
                          , QWidget * parent = nullptr);

  State validate (QString& input, int& pos) const override;
  void fixup (QString& input) const override;

  Q_SIGNAL void valid (Frequency) const;

private:
  Bands const * bands_;
  FrequencyList_v2 const * frequencies_;
  Frequency const * nominal_frequency_;
  QComboBox * combo_box_;
};

#endif


code:
#include "MaidenheadLocatorValidator.hpp"

MaidenheadLocatorValidator::MaidenheadLocatorValidator (QObject * parent, Length length, Length required)
  : QValidator {parent}
{
  switch (length)
    {
    case Length::field:
      re_.setPattern ({R"(^(?<field>[A-Ra-r]{2})$)"});
      break;
    case Length::square:
      if (Length::field == required)
        {
          re_.setPattern ({R"(^(?<field>[A-Ra-r]{2})([0-9]{2}){0,1}$)"});
        }
      else
        {
          re_.setPattern ({R"(^(?<field>[A-Ra-r]{2})[0-9]{2}$)"});
        }
      break;
    case Length::subsquare:
      if (Length::field == required)
        {
          re_.setPattern ({R"(^(?<field>[A-Ra-r]{2})([0-9]{2}((?<subsquare>[A-Xa-x]{2}){0,1})){0,1}$)"});
        }
      else if (Length::square == required)
        {
          re_.setPattern ({R"(^(?<field>[A-Ra-r]{2})[0-9]{2}(?<subsquare>[A-Xa-x]{2}){0,1}$)"});
        }
      else
        {
          re_.setPattern ({R"(^(?<field>[A-Ra-r]{2})[0-9]{2}(?<subsquare>[A-Xa-x]{2})$)"});
        }
      break;
    case Length::extended:
      if (Length::field == required)
        {
          re_.setPattern ({R"(^(?<field>[A-Ra-r]{2})([0-9]{2}((?<subsquare>[A-Xa-x]{2}){0,1}([0-9]{2}){0,1})){0,1}$)"});
        }
      else if (Length::square == required)
        {
          re_.setPattern ({R"(^(?<field>[A-Ra-r]{2})[0-9]{2}((?<subsquare>[A-Xa-x]{2})([0-9]{2}){0,1}){0,1}$)"});
        }
      else if (Length::subsquare == required)
        {
          re_.setPattern ({R"(^(?<field>[A-Ra-r]{2})[0-9]{2}(?<subsquare>[A-Xa-x]{2})([0-9]{2}){0,1}$)"});
        }
      else
        {
          re_.setPattern ({R"(^(?<field>[A-Ra-r]{2})[0-9]{2}(?<subsquare>[A-Xa-x]{2})[0-9]{2}$)"});
        }
      break;
    }
}

auto MaidenheadLocatorValidator::validate (QString& input, int& pos) const -> State
{
  auto match = re_.match (input, 0, QRegularExpression::PartialPreferCompleteMatch);
  auto field = match.captured ("field");
  if (field.size ())
    {
      input.replace (match.capturedStart ("field"), match.capturedLength ("field"), field.toUpper ());
    }
  auto subsquare = match.captured ("subsquare");
  if (subsquare.size ())
    {
      input.replace (match.capturedStart ("subsquare"), match.capturedLength ("subsquare"), subsquare.toLower ());
    }
  if (match.hasMatch ()) return Acceptable;
  if (!input.size () || match.hasPartialMatch ()) return Intermediate;
  pos = input.size ();
  return Invalid;
}


code:
#ifndef MAIDENHEAD_LOCATOR_VALIDATOR_HPP__
#define MAIDENHEAD_LOCATOR_VALIDATOR_HPP__

#include <QValidator>
#include <QRegularExpression>

//
// MaidenheadLocatorValidator - QValidator implementation for grid locators
//
class MaidenheadLocatorValidator final
  : public QValidator
{
public:
  enum class Length {field = 2, square = 4, subsquare = 6, extended = 8, doubleextended = 16};
  MaidenheadLocatorValidator (QObject * parent = nullptr
                              , Length length = Length::subsquare
                              , Length required = Length::square);

  // QValidator implementation
  State validate (QString& input, int& pos) const override;

private:
  QRegularExpression re_;
};

#endif


code:
#include "MessageBox.hpp"

#include <QDialogButtonBox>
#include <QPushButton>
#include <QCoreApplication>

#include "revision_utils.hpp"

MessageBox::MessageBox (QWidget * parent)
  : QMessageBox {parent}
{
  setWindowTitle (program_title ());
}

MessageBox::MessageBox (Icon icon, QString const& text, StandardButtons buttons
                        , QWidget * parent, Qt::WindowFlags flags)
  : QMessageBox {icon, QCoreApplication::applicationName (), text, buttons, parent, flags}
{
}

void MessageBox::about_message (QWidget * parent, QString const& text)
{
  QMessageBox::about (parent, program_title (), text);
}

void MessageBox::about_Qt_message (QWidget * parent)
{
  QMessageBox::aboutQt (parent, program_title ());
}

namespace
{
  QMessageBox::StandardButton show_it (QWidget * parent, MessageBox::Icon icon
                                       , QString const& text
                                       , QString const& informative
                                       , QString const& detail
                                       , MessageBox::StandardButtons buttons
                                       , MessageBox::StandardButton default_button)
  {
    MessageBox mb {icon, text, MessageBox::NoButton, parent};
    QDialogButtonBox * button_box = mb.findChild<QDialogButtonBox *> ();
    Q_ASSERT (button_box);

    uint mask = MessageBox::FirstButton;
    while (mask <= MessageBox::LastButton) {
      uint sb = buttons & mask;
      mask <<= 1;
      if (!sb)
        continue;
      QPushButton * button = mb.addButton (static_cast<MessageBox::StandardButton> (sb));
      // Choose the first accept role as the default
      if (mb.defaultButton ())
        continue;
      if ((default_button == MessageBox::NoButton
           && button_box->buttonRole (button) == QDialogButtonBox::AcceptRole)
          || (default_button != MessageBox::NoButton
              && sb == static_cast<uint> (default_button)))
        mb.setDefaultButton (button);
    }
    mb.setInformativeText (informative);
    mb.setDetailedText (detail);
    if (mb.exec() == -1)
      return MessageBox::Cancel;
    return mb.standardButton (mb.clickedButton ());
  }
}

auto MessageBox::information_message (QWidget * parent, QString const& text
                                      , QString const& informative
                                      , QString const& detail
                                      , StandardButtons buttons
                                      , StandardButton default_button) -> StandardButton
{
  return show_it (parent, Information, text, informative, detail, buttons, default_button);
}

auto MessageBox::query_message (QWidget * parent, QString const& text
                                , QString const& informative
                                , QString const& detail
                                , StandardButtons buttons
                                , StandardButton default_button) -> StandardButton
{
  return show_it (parent, Question, text, informative, detail, buttons, default_button);
}

auto MessageBox::warning_message (QWidget * parent, QString const& text
                                  , QString const& informative
                                  , QString const& detail
                                  , StandardButtons buttons
                                  , StandardButton default_button) -> StandardButton
{
  return show_it (parent, Warning, text, informative, detail, buttons, default_button);
}

auto MessageBox::critical_message (QWidget * parent, QString const& text
                                   , QString const& informative
                                   , QString const& detail
                                   , StandardButtons buttons
                                   , StandardButton default_button) -> StandardButton
{
  return show_it (parent, Critical, text, informative, detail, buttons, default_button);
}


code:
#ifndef MESSAGE_BOX_HPP__
#define MESSAGE_BOX_HPP__

#include <QMessageBox>

// get rid of the nasty MS define
#ifdef MessageBox
#undef MessageBox
#endif

//
// MessageBox - wrap the Qt QMessageBox class to give a more platform
// 							neutral and functional interface
//
class MessageBox
  : public QMessageBox
{
public:
  explicit MessageBox (QWidget * parent = nullptr);
  explicit MessageBox (Icon, QString const& text, StandardButtons = NoButton
                       , QWidget * parent = nullptr
                       , Qt::WindowFlags = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint);

  static void about_message (QWidget * parent, QString const& text);
  static void about_Qt_message (QWidget * parent);
  static StandardButton information_message (QWidget * parent, QString const& text
                                             , QString const& informative = QString {}
                                             , QString const& detail = QString {}
                                             , StandardButtons buttons = Ok
                                             , StandardButton default_button = NoButton);
  static StandardButton query_message (QWidget * parent, QString const& text
                                       , QString const& informative = QString {}
                                       , QString const& detail = QString {}
                                       , StandardButtons buttons = Yes | No
                                       , StandardButton default_button = NoButton);
  static StandardButton warning_message (QWidget * parent, QString const& text
                                         , QString const& informative = QString {}
                                         , QString const& detail = QString {}
                                         , StandardButtons buttons = Ok
                                         , StandardButton default_button = NoButton);
  static StandardButton critical_message (QWidget * parent, QString const& text
                                          , QString const& informative = QString {}
                                          , QString const& detail = QString {}
                                          , StandardButtons buttons = Ok
                                          , StandardButton default_button = NoButton);
private:
  // hide the parent static functions so that users use our versions
  // above that are correctly branded and have better platform
  // independence
  using QMessageBox::about;
  using QMessageBox::aboutQt;
  using QMessageBox::information;
  using QMessageBox::question;
  using QMessageBox::warning;
  using QMessageBox::critical;
};

#endif


code:
#include "MessageClient.hpp"

#include <stdexcept>
#include <vector>
#include <algorithm>

#include <QApplication>
#include <QUdpSocket>
#include <QHostInfo>
#include <QTimer>
#include <QQueue>
#include <QByteArray>
#include <QHostAddress>
#include <QColor>

#include "DriftingDateTime.h"

#include "pimpl_impl.hpp"

#include "moc_MessageClient.cpp"

Message::Message()
{
}

Message::Message(QString const &type, QString const &value):
    type_{ type },
    value_{ value }
{
}

Message::Message(QString const &type, QString const &value,  QMap<QString, QVariant> const &params):
    type_{ type },
    value_{ value },
    params_{ params }
{
}

void Message::read(const QJsonObject &json){
    if(json.contains("type") && json["type"].isString()){
        type_ = json["type"].toString();
    }

    if(json.contains("value") && json["value"].isString()){
        value_ = json["value"].toString();
    }

    if(json.contains("params") && json["params"].isObject()){
        params_.clear();

        QJsonObject params = json["params"].toObject();
        foreach(auto key, params.keys()){
            params_[key] = params[key].toVariant();
        }
    }
}

void Message::write(QJsonObject &json) const{
    json["type"] = type_;
    json["value"] = value_;

    QJsonObject params;
    foreach(auto key, params_.keys()){
        params.insert(key, QJsonValue::fromVariant(params_[key]));
    }
    json["params"] = params;
}

QByteArray Message::toJson() const {
    QJsonObject o;
    write(o);

    QJsonDocument d(o);
    return d.toJson();
}

class MessageClient::impl
  : public QUdpSocket
{
  Q_OBJECT

public:
  impl (QString const& id, QString const& version, QString const& revision,
        port_type server_port, MessageClient * self)
    : self_ {self}
    , id_ {id}
    , version_ {version}
    , revision_ {revision}
    , server_port_ {server_port}
    , schema_ {2}  // use 2 prior to negotiation not 1 which is broken
    , heartbeat_timer_ {new QTimer {this}}
  {
    connect (heartbeat_timer_, &QTimer::timeout, this, &impl::heartbeat);
    connect (this, &QIODevice::readyRead, this, &impl::pending_datagrams);

    heartbeat_timer_->start (15 * 1000);

    // bind to an ephemeral port
    bind ();
  }

  ~impl ()
  {
    closedown ();
  }

  enum StreamStatus {Fail, Short, OK};

  void parse_message (QByteArray const& msg);
  void pending_datagrams ();
  void heartbeat ();
  void closedown ();
  StreamStatus check_status (QDataStream const&) const;
  void send_message (QByteArray const&);
  void send_message (QDataStream const& out, QByteArray const& message)
  {
      if (OK == check_status (out))
        {
          send_message (message);
        }
      else
        {
          Q_EMIT self_->error ("Error creating UDP message");
        }
  }

  Q_SLOT void host_info_results (QHostInfo);

  MessageClient * self_;
  QString id_;
  QString version_;
  QString revision_;
  QString server_string_;
  port_type server_port_;
  QHostAddress server_;
  quint32 schema_;
  QTimer * heartbeat_timer_;
  std::vector<QHostAddress> blocked_addresses_;

  // hold messages sent before host lookup completes asynchronously
  QQueue<QByteArray> pending_messages_;
  QByteArray last_message_;
};

#include "MessageClient.moc"

void MessageClient::impl::host_info_results (QHostInfo host_info)
{
  if (QHostInfo::NoError != host_info.error ())
    {
      Q_EMIT self_->error ("UDP server lookup failed:\n" + host_info.errorString ());
      pending_messages_.clear (); // discard
    }
  else if (host_info.addresses ().size ())
    {
      auto server = host_info.addresses ()[0];
      if (blocked_addresses_.end () == std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server))
        {
          server_ = server;

          // send initial heartbeat which allows schema negotiation
          heartbeat ();

          // clear any backlog
          while (pending_messages_.size ())
            {
              send_message (pending_messages_.dequeue ());
            }
        }
      else
        {
          Q_EMIT self_->error ("UDP server blocked, please try another");
          pending_messages_.clear (); // discard
        }
    }
}

void MessageClient::impl::pending_datagrams ()
{
  while (hasPendingDatagrams ())
    {
      QByteArray datagram;
      datagram.resize (pendingDatagramSize ());
      QHostAddress sender_address;
      port_type sender_port;
      if (0 <= readDatagram (datagram.data (), datagram.size (), &sender_address, &sender_port))
        {
          parse_message (datagram);
        }
    }
}

void MessageClient::impl::parse_message (QByteArray const& msg)
{
  try
    {
        if(msg.isEmpty()){
            return;
        }

        QJsonParseError e;
        QJsonDocument d = QJsonDocument::fromJson(msg, &e);
        if(e.error != QJsonParseError::NoError){
            Q_EMIT self_->error(QString {"MessageClient json parse error:  %1"}.arg(e.errorString()));
            return;
        }

        if(!d.isObject()){
            Q_EMIT self_->error(QString {"MessageClient json parse error: json is not an object"});
            return;
        }

        Message m;
        m.read(d.object());
        Q_EMIT self_->message(m);

    }
  catch (std::exception const& e)
    {
      Q_EMIT self_->error (QString {"MessageClient exception: %1"}.arg (e.what ()));
    }
  catch (...)
    {
      Q_EMIT self_->error ("Unexpected exception in MessageClient");
    }
}

void MessageClient::impl::heartbeat ()
{
   if (server_port_ && !server_.isNull ())
    {
      Message m("PING", "", QMap<QString, QVariant>{
        {"NAME", QVariant(QApplication::applicationName())},
        {"VERSION", QVariant(QApplication::applicationVersion())},
        {"UTC", QVariant(DriftingDateTime::currentDateTimeUtc().toMSecsSinceEpoch())}
      });
      writeDatagram (m.toJson(), server_, server_port_);
    }
}

void MessageClient::impl::closedown ()
{
   if (server_port_ && !server_.isNull ())
    {
       Message m("CLOSE");
       writeDatagram (m.toJson(), server_, server_port_);
    }
}

void MessageClient::impl::send_message (QByteArray const& message)
{
  if (server_port_)
    {
      if (!server_.isNull ())
        {
          if (message != last_message_) // avoid duplicates
            {
              writeDatagram (message, server_, server_port_);
              last_message_ = message;
            }
        }
      else
        {
          pending_messages_.enqueue (message);
        }
    }
}

auto MessageClient::impl::check_status (QDataStream const& stream) const -> StreamStatus
{
  auto stat = stream.status ();
  StreamStatus result {Fail};
  switch (stat)
    {
    case QDataStream::ReadPastEnd:
      result = Short;
      break;

    case QDataStream::ReadCorruptData:
      Q_EMIT self_->error ("Message serialization error: read corrupt data");
      break;

    case QDataStream::WriteFailed:
      Q_EMIT self_->error ("Message serialization error: write error");
      break;

    default:
      result = OK;
      break;
    }
  return result;
}

MessageClient::MessageClient (QString const& id, QString const& version, QString const& revision,
                              QString const& server, port_type server_port, QObject * self)
  : QObject {self}
  , m_ {id, version, revision, server_port, this}
{
  connect (&*m_, static_cast<void (impl::*) (impl::SocketError)> (&impl::error)
           , [this] (impl::SocketError e)
           {
#if defined (Q_OS_WIN) && QT_VERSION >= 0x050500
             if (e != impl::NetworkError // take this out when Qt 5.5
                                         // stops doing this
                                         // spuriously
                 && e != impl::ConnectionRefusedError) // not
                                                       // interested
                                                       // in this with
                                                       // UDP socket
#else
             Q_UNUSED (e);
#endif
               {
                 Q_EMIT error (m_->errorString ());
               }
           });
  set_server (server);
}

QHostAddress MessageClient::server_address () const
{
  return m_->server_;
}

auto MessageClient::server_port () const -> port_type
{
  return m_->server_port_;
}

void MessageClient::set_server (QString const& server)
{
    qDebug() << "server changed to" << server;
  m_->server_.clear ();
  m_->server_string_ = server;
  if (!server.isEmpty ())
    {
      // queue a host address lookup
      QHostInfo::lookupHost (server, &*m_, SLOT (host_info_results (QHostInfo)));
    }
}

void MessageClient::set_server_port (port_type server_port)
{
  m_->server_port_ = server_port;
}

void MessageClient::send(Message const &message){
  m_->send_message(message.toJson());
}

void MessageClient::send_raw_datagram (QByteArray const& message, QHostAddress const& dest_address
                                       , port_type dest_port)
{
  if (dest_port && !dest_address.isNull ())
    {
      m_->writeDatagram (message, dest_address, dest_port);
    }
}

void MessageClient::add_blocked_destination (QHostAddress const& a)
{
  m_->blocked_addresses_.push_back (a);
  if (a == m_->server_)
    {
      m_->server_.clear ();
      Q_EMIT error ("UDP server blocked, please try another");
      m_->pending_messages_.clear (); // discard
    }
}


code:
#ifndef MESSAGE_CLIENT_HPP__
#define MESSAGE_CLIENT_HPP__

#include <QObject>
#include <QTime>
#include <QDataStream>
#include <QDateTime>
#include <QString>
#include <QJsonDocument>
#include <QJsonObject>

#include "Radio.hpp"
#include "pimpl_h.hpp"

class QByteArray;
class QHostAddress;
class QColor;

class Message {
public:
    Message();
    Message(QString const &type, QString const &value="");
    Message(QString const &type, QString const &value, QMap<QString, QVariant> const &params);

    void read(const QJsonObject &json);
    void write(QJsonObject &json) const;

    QByteArray toJson() const;

    QString type() const { return type_; }
    QString value() const { return value_; }
    QMap<QString, QVariant> params() const { return params_; }

private:
    QString type_;
    QString value_;
    QMap<QString, QVariant> params_;
};

//
// MessageClient - Manage messages sent and replies received from a
//                 matching server (MessageServer) at the other end of
//                 the wire
//
//
// Each outgoing message type is a Qt slot
//
class MessageClient
  : public QObject
{
  Q_OBJECT

public:
  using Frequency = Radio::Frequency;
  using port_type = quint16;

  // instantiate and initiate a host lookup on the server
  //
  // messages will be silently dropped until a server host lookup is complete
  MessageClient (QString const& id, QString const& version, QString const& revision,
                 QString const& server, port_type server_port, QObject * parent = nullptr);

  // query server details
  QHostAddress server_address () const;
  port_type server_port () const;

  // initiate a new server host lookup or is the server name is empty
  // the sending of messages is disabled
  Q_SLOT void set_server (QString const& server = QString {});

  // change the server port messages are sent to
  Q_SLOT void set_server_port (port_type server_port = 0u);

  // this slot is used to send an arbitrary message
  Q_SLOT void send(Message const &message);

  // this slot may be used to send arbitrary UDP datagrams to and
  // destination allowing the underlying socket to be used for general
  // UDP messaging if desired
  Q_SLOT void send_raw_datagram (QByteArray const&, QHostAddress const& dest_address, port_type dest_port);

  // disallowed message destination (does not block datagrams sent
  // with send_raw_datagram() above)
  Q_SLOT void add_blocked_destination (QHostAddress const&);

  Q_SIGNAL void message(Message const &message);

  // this signal is emitted when the a reply a message is received
  Q_SIGNAL void message_received(QString const &type, QString const &message);

  // this signal is emitted when network errors occur or if a host
  // lookup fails
  Q_SIGNAL void error (QString const&) const;

private:
  class impl;
  pimpl<impl> m_;
};

#endif