code:
#ifndef CONFIGURATION_HPP_
#define CONFIGURATION_HPP_

#include <QObject>
#include <QFont>

#include "Radio.hpp"
#include "IARURegions.hpp"
#include "AudioDevice.hpp"
#include "Transceiver.hpp"

#include "pimpl_h.hpp"

class QSettings;
class QWidget;
class QAudioDeviceInfo;
class QString;
class QDir;
class Bands;
class FrequencyList_v2;
class StationList;
class QStringListModel;
class QHostAddress;

//
// Class Configuration
//
//  Encapsulates the control, access  and, persistence of user defined
//  settings for the wsjtx GUI.  Setting values are accessed through a
//  QDialog window containing concept orientated tab windows.
//
// Responsibilities
//
//  Provides management of  the CAT and PTT  rig interfaces, providing
//  control access  via a minimal generic  set of Qt slots  and status
//  updates via Qt signals.  Internally  the rig control capability is
//  farmed out  to a  separate thread  since many  of the  rig control
//  functions are blocking.
//
//  All user  settings required by  the wsjtx GUI are  exposed through
//  query methods.  Settings only become  visible once they  have been
//  accepted by the user which is  done by clicking the "OK" button on
//  the settings dialog.
//
//  The QSettings instance  passed to the constructor is  used to read
//  and write user settings.
//
//  Pointers to three QAbstractItemModel  objects are provided to give
//  access to amateur band  information, user working frequencies and,
//  user operating  band information.  These porovide  consistent data
//  models that can  be used in GUI lists or  tables or simply queried
//  for user defined bands, default operating frequencies and, station
//  descriptions.
//
class Configuration final
  : public QObject
{
  Q_OBJECT
  Q_ENUMS (DataMode Type2MsgGen)

public:
  using MODE = Transceiver::MODE;
  using TransceiverState = Transceiver::TransceiverState;
  using Frequency = Radio::Frequency;
  using port_type = quint16;

  enum DataMode {data_mode_none, data_mode_USB, data_mode_data};
  Q_ENUM (DataMode)
  enum Type2MsgGen {type_2_msg_1_full, type_2_msg_3_full, type_2_msg_5_only};
  Q_ENUM (Type2MsgGen)

  explicit Configuration (QDir const& temp_directory, QSettings * settings,
                          QWidget * parent = nullptr);
  ~Configuration ();

  void select_tab (int);
  int exec ();
  bool is_active () const;

  QDir temp_dir () const;
  QDir doc_dir () const;
  QDir data_dir () const;
  QDir writeable_data_dir () const;

  QAudioDeviceInfo const& audio_input_device () const;
  AudioDevice::Channel audio_input_channel () const;
  QAudioDeviceInfo const& audio_output_device () const;
  AudioDevice::Channel audio_output_channel () const;

  // These query methods should be used after a call to exec() to
  // determine if either the audio input or audio output stream
  // parameters have changed. The respective streams should be
  // re-opened if they return true.
  bool restart_audio_input () const;
  bool restart_audio_output () const;

  bool use_dynamic_grid() const;
  QString my_callsign () const;
  QString my_grid () const;
  QString my_station () const;
  int activity_aging() const;
  int callsign_aging() const;
  QString my_qth () const;
  QString cq_message () const;
  QString reply_message () const;
  QFont table_font() const;
  QFont text_font () const;
  QFont rx_text_font () const;
  QFont tx_text_font () const;
  QFont compose_text_font () const;
  qint32 id_interval () const;
  qint32 ntrials() const;
  qint32 aggressive() const;
  qint32 RxBandwidth() const;
  double degrade() const;
  double txDelay() const;
  bool id_after_73 () const;
  bool tx_qsy_allowed () const;
  bool spot_to_reporting_networks () const;
  void set_spot_to_reporting_networks (bool);
  bool transmit_directed() const;
  bool autoreply_off_at_startup () const;
  bool beacon_anywhere() const;
  bool relay_off() const;
  bool monitor_off_at_startup () const;
  bool monitor_last_used () const;
  bool log_as_DATA () const;
  bool report_in_comments () const;
  bool prompt_to_log () const;
  bool insert_blank () const;
  bool DXCC () const;
  bool ppfx() const;
  bool clear_DX () const;
  bool miles () const;
  bool quick_call () const;
  bool disable_TX_on_73 () const;
  int beacon () const;
  int watchdog () const;
  bool TX_messages () const;
  bool split_mode () const;
  bool enable_VHF_features () const;
  bool decode_at_52s () const;
  bool single_decode () const;
  bool twoPass() const;
  bool bFox() const;
  bool bHound() const;
  bool x2ToneSpacing() const;
  bool x4ToneSpacing() const;
  bool contestMode() const;
  bool MyDx() const;
  bool CQMyN() const;
  bool NDxG() const;
  bool NN() const;
  bool EMEonly() const;
  bool post_decodes () const;
  QString opCall() const;
  QString aprs_server_name () const;
  port_type aprs_server_port () const;
  QString aprs_passcode () const;
  QString udp_server_name () const;
  port_type udp_server_port () const;
  QString n1mm_server_name () const;
  port_type n1mm_server_port () const;
  bool valid_n1mm_info () const;
  bool broadcast_to_n1mm() const;
  bool accept_udp_requests () const;
  bool udpWindowToFront () const;
  bool udpWindowRestore () const;
  bool udpEnabled () const;
  Bands * bands ();
  Bands const * bands () const;
  IARURegions::Region region () const;
  FrequencyList_v2 * frequencies ();
  FrequencyList_v2 const * frequencies () const;
  StationList * stations ();
  StationList const * stations () const;
  bool auto_switch_bands() const;
  QStringListModel * macros ();
  QStringListModel const * macros () const;
  QDir save_directory () const;
  QDir azel_directory () const;
  QString sound_dm_path() const;
  QString sound_am_path() const;
  QString rig_name () const;
  Type2MsgGen type_2_msg_gen () const;
  QColor color_table_background() const;
  QColor color_table_highlight() const;
  QColor color_table_foreground() const;
  QColor color_CQ () const;
  QColor color_MyCall () const;
  QColor color_rx_background () const;
  QColor color_rx_foreground () const;
  QColor color_tx_foreground () const;
  QColor color_compose_background () const;
  QColor color_compose_foreground () const;
  QColor color_DXCC () const;
  QColor color_NewCall () const;
  bool pwrBandTxMemory () const;
  bool pwrBandTuneMemory () const;

  struct CalibrationParams
  {
    CalibrationParams ()
      : intercept {0.}
      , slope_ppm {0.}
    {
    }

    CalibrationParams (double the_intercept, double the_slope_ppm)
      : intercept {the_intercept}
      , slope_ppm {the_slope_ppm}
    {
    }

    double intercept;           // Hertz
    double slope_ppm;           // Hertz
  };

  // Temporarily enable or disable calibration adjustments.
  void enable_calibration (bool = true);

  // Set the calibration parameters and enable calibration corrections.
  void set_calibration (CalibrationParams);

  // Set the dynamic grid which is only used if configuration setting is enabled.
  void set_dynamic_location (QString const&);

  // Set the dynamic statios message which is only used if configuration setting is enabled.
  void set_dynamic_station_message(QString const& qtc);

  // This method queries if a CAT and PTT connection is operational.
  bool is_transceiver_online () const;

  // Start the rig connection, safe and normal to call when rig is
  // already open.
  bool transceiver_online ();

  // check if a real rig is configured
  bool is_dummy_rig () const;

  // Frequency resolution of the rig
  //
  //  0 - 1Hz
  //  1 - 10Hz rounded
  // -1 - 10Hz truncated
  //  2 - 100Hz rounded
  // -2 - 100Hz truncated
  int transceiver_resolution () const;

  // Close down connection to rig.
  void transceiver_offline ();

  // Set transceiver frequency in Hertz.
  Q_SLOT void transceiver_frequency (Frequency);

  // Setting a non zero TX frequency means split operation
  // rationalise_mode means ensure TX uses same mode as RX.
  Q_SLOT void transceiver_tx_frequency (Frequency = 0u);

  // Set transceiver mode.
  //
  // Rationalise means ensure TX uses same mode as RX.
  Q_SLOT void transceiver_mode (MODE);

  // Set/unset PTT.
  //
  // Note that this must be called even if VOX PTT is selected since
  // the "Emulate Split" mode requires PTT information to coordinate
  // frequency changes.
  Q_SLOT void transceiver_ptt (bool = true);

  // Attempt to (re-)synchronise transceiver state.
  //
  // Force signal guarantees either a transceiver_update or a
  // transceiver_failure signal.
  //
  // The enforce_mode_and_split parameter ensures that future
  // transceiver updates have the correct mode and split setting
  // i.e. the transceiver is ready for use.
  Q_SLOT void sync_transceiver (bool force_signal = false, bool enforce_mode_and_split = false);

  //
  // These signals indicate a font has been selected and accepted for
  // the application text and decoded text respectively.
  //
  Q_SIGNAL void gui_text_font_changed (QFont);
  Q_SIGNAL void tx_text_font_changed (QFont);
  Q_SIGNAL void rx_text_font_changed (QFont);
  Q_SIGNAL void compose_text_font_changed (QFont);
  Q_SIGNAL void table_font_changed (QFont);
  Q_SIGNAL void colors_changed ();

  //
  // This signal is emitted when the UDP server changes
  //
  Q_SIGNAL void udp_server_changed (QString const& udp_server);
  Q_SIGNAL void udp_server_port_changed (port_type server_port);

  // This signal is emitted when the band schedule changes
  Q_SIGNAL void band_schedule_changed (StationList &stations);

  //
  // These signals are emitted and reflect transceiver state changes
  //

  // signals a change in one of the TransceiverState members
  Q_SIGNAL void transceiver_update (Transceiver::TransceiverState const&) const;

  // Signals a failure of a control rig CAT or PTT connection.
  //
  // A failed rig CAT or PTT connection is fatal and the underlying
  // connections are closed automatically. The connections can be
  // re-established with a call to transceiver_online(true) assuming
  // the fault condition has been rectified or is transient.
  Q_SIGNAL void transceiver_failure (QString const& reason) const;

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

#if QT_VERSION < 0x050500
Q_DECLARE_METATYPE (Configuration::DataMode);
Q_DECLARE_METATYPE (Configuration::Type2MsgGen);
#endif

#if !defined (QT_NO_DEBUG_STREAM)
ENUM_QDEBUG_OPS_DECL (Configuration, DataMode);
ENUM_QDEBUG_OPS_DECL (Configuration, Type2MsgGen);
#endif

ENUM_QDATASTREAM_OPS_DECL (Configuration, DataMode);
ENUM_QDATASTREAM_OPS_DECL (Configuration, Type2MsgGen);

ENUM_CONVERSION_OPS_DECL (Configuration, DataMode);
ENUM_CONVERSION_OPS_DECL (Configuration, Type2MsgGen);

#endif

license: mit


code:
#include "DXLabSuiteCommanderTransceiver.hpp"

#include <QTcpSocket>
#include <QRegularExpression>
#include <QLocale>
#include <QThread>
#include <QDateTime>

#include "DriftingDateTime.h"

#include "NetworkServerLookup.hpp"

#include "moc_DXLabSuiteCommanderTransceiver.cpp"

namespace
{
  char const * const commander_transceiver_name {"DX Lab Suite Commander"};

  QString map_mode (Transceiver::MODE mode)
  {
    switch (mode)
      {
      case Transceiver::AM: return "AM";
      case Transceiver::CW: return "CW";
      case Transceiver::CW_R: return "CW-R";
      case Transceiver::USB: return "USB";
      case Transceiver::LSB: return "LSB";
      case Transceiver::FSK: return "RTTY";
      case Transceiver::FSK_R: return "RTTY-R";
      case Transceiver::DIG_L: return "DATA-L";
      case Transceiver::DIG_U: return "DATA-U";
      case Transceiver::FM:
      case Transceiver::DIG_FM:
        return "FM";
      default: break;
      }
    return "USB";
  }
}

void DXLabSuiteCommanderTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id)
{
  (*registry)[commander_transceiver_name] = TransceiverFactory::Capabilities {id, TransceiverFactory::Capabilities::network, true};
}

DXLabSuiteCommanderTransceiver::DXLabSuiteCommanderTransceiver (std::unique_ptr<TransceiverBase> wrapped,
                                                                QString const& address, bool use_for_ptt,
                                                                int poll_interval, QObject * parent)
  : PollingTransceiver {poll_interval, parent}
  , wrapped_ {std::move (wrapped)}
  , use_for_ptt_ {use_for_ptt}
  , server_ {address}
  , commander_ {nullptr}
{
}

int DXLabSuiteCommanderTransceiver::do_start ()
{
  TRACE_CAT ("DXLabSuiteCommanderTransceiver", "starting");
  if (wrapped_) wrapped_->start (0);

  auto server_details = network_server_lookup (server_, 52002u, QHostAddress::LocalHost, QAbstractSocket::IPv4Protocol);

  if (!commander_)
    {
      commander_ = new QTcpSocket {this}; // QObject takes ownership
    }

  commander_->connectToHost (std::get<0> (server_details), std::get<1> (server_details));
  if (!commander_->waitForConnected ())
    {
      TRACE_CAT ("DXLabSuiteCommanderTransceiver", "failed to connect" << commander_->errorString ());
      throw error {tr ("Failed to connect to DX Lab Suite Commander\n") + commander_->errorString ()};
    }

  // sleeps here are to ensure Commander has actually queried the rig
  // rather than returning cached data which maybe stale or simply
  // read backs of not yet committed values, the 2s delays are
  // arbitrary but hopefully enough as the actual time required is rig
  // and Commander setting dependent
  int resolution {0};
  QThread::msleep (2000);
  auto reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>");
  if (0 == reply.indexOf ("<CmdFreq:"))
    {
      auto f = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
      if (f && !(f % 10))
        {
          auto test_frequency = f - f % 100 + 55;
          auto f_string = frequency_to_string (test_frequency);
          auto params =  ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
          simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
          QThread::msleep (2000);
          reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>");
          if (0 == reply.indexOf ("<CmdFreq:"))
            {
              auto new_frequency = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
              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 = -2; break; // 100Hz truncated
                case 45: resolution = 2; break;   // 100Hz rounded
                }
              if (1 == resolution)      // may be 20Hz rounded
                {
                  test_frequency = f - f % 100 + 51;
                  f_string = frequency_to_string (test_frequency);
                  params =  ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
                  simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
                  QThread::msleep (2000);
                  reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>");
                  new_frequency = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
                  if (9 == static_cast<Radio::FrequencyDelta> (new_frequency - test_frequency))
                    {
                      resolution = 2;   // 20Hz rounded
                    }
                }
              f_string = frequency_to_string (f);
              params =  ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
              simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
            }
        }
    }
  else
    {
      TRACE_CAT ("DXLabSuiteCommanderTransceiver", "get frequency unexpected response" << reply);
      throw error {tr ("DX Lab Suite Commander didn't respond correctly reading frequency: ") + reply};
    }

  poll ();
  return resolution;
}

void DXLabSuiteCommanderTransceiver::do_stop ()
{
  if (commander_)
    {
      commander_->close ();
      delete commander_, commander_ = nullptr;
    }

  if (wrapped_) wrapped_->stop ();
  TRACE_CAT ("DXLabSuiteCommanderTransceiver", "stopped");
}

void DXLabSuiteCommanderTransceiver::do_ptt (bool on)
{
  TRACE_CAT ("DXLabSuiteCommanderTransceiver", on << state ());
  if (use_for_ptt_)
    {
      simple_command (on  ? "<command:5>CmdTX<parameters:0>" : "<command:5>CmdRX<parameters:0>");

      bool tx {!on};
      auto start = DriftingDateTime::currentMSecsSinceEpoch ();
      // we must now wait for Tx on the rig, we will wait a short while
      // before bailing out
      while (tx != on && DriftingDateTime::currentMSecsSinceEpoch () - start < 1000)
        {
          auto reply = command_with_reply ("<command:9>CmdSendTx<parameters:0>");
          if (0 == reply.indexOf ("<CmdTX:"))
            {
              auto state = reply.mid (reply.indexOf ('>') + 1);
              if ("ON" == state)
                {
                  tx = true;
                }
              else if ("OFF" == state)
                {
                  tx = false;
                }
              else
                {
                  TRACE_CAT ("DXLabSuiteCommanderTransceiver", "unexpected TX state" << state);
                  throw error {tr ("DX Lab Suite Commander sent an unrecognised TX state: ") + state};
                }
            }
          else
            {
              TRACE_CAT ("DXLabSuiteCommanderTransceiver", "get TX unexpected response" << reply);
              throw error {tr ("DX Lab Suite Commander didn't respond correctly polling TX status: ") + reply};
            }
          if (tx != on) QThread::msleep (10); // don't thrash Commander
        }
      update_PTT (tx);
      if (tx != on)
        {
          TRACE_CAT ("DXLabSuiteCommanderTransceiver", "rig failed to respond to PTT: " << on);
          throw error {tr ("DX Lab Suite Commander rig did not respond to PTT: ") + (on ? "ON" : "OFF")};
        }
    }
  else
    {
      Q_ASSERT (wrapped_);
      TransceiverState new_state {wrapped_->state ()};
      new_state.ptt (on);
      wrapped_->set (new_state, 0);
      update_PTT (on);
    }
}

void DXLabSuiteCommanderTransceiver::do_frequency (Frequency f, MODE m, bool /*no_ignore*/)
{
  TRACE_CAT ("DXLabSuiteCommanderTransceiver", f << state ());
  auto f_string = frequency_to_string (f);
  if (UNK != m && m != get_mode ())
    {
      auto m_string = map_mode (m);
      auto params =  ("<xcvrfreq:%1>" + f_string + "<xcvrmode:%2>" + m_string + "<preservesplitanddual:1>Y").arg (f_string.size ()).arg (m_string.size ());
      simple_command (("<command:14>CmdSetFreqMode<parameters:%1>" + params).arg (params.size ()));
      update_mode (m);
    }
  else
    {
      auto params =  ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
      simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
    }
  update_rx_frequency (f);
}

void DXLabSuiteCommanderTransceiver::do_tx_frequency (Frequency tx, MODE mode, bool /*no_ignore*/)
{
  TRACE_CAT ("DXLabSuiteCommanderTransceiver", tx << state ());
  if (tx)
    {
      auto f_string = frequency_to_string (tx);
      auto params = ("<xcvrfreq:%1>" + f_string + "<SuppressDual:1>Y").arg (f_string.size ());
      if (UNK == mode)
        {
          params += "<SuppressModeChange:1>Y";
        }
      simple_command (("<command:11>CmdQSXSplit<parameters:%1>" + params).arg (params.size ()));
    }
  else
    {
      simple_command ("<command:8>CmdSplit<parameters:8><1:3>off");
    }
  update_split (tx);
  update_other_frequency (tx);
}

void DXLabSuiteCommanderTransceiver::do_mode (MODE m)
{
  TRACE_CAT ("DXLabSuiteCommanderTransceiver", m << state ());
  auto m_string = map_mode (m);
  auto params =  ("<1:%1>" + m_string).arg (m_string.size ());
  simple_command (("<command:10>CmdSetMode<parameters:%1>" + params).arg (params.size ()));
  update_mode (m);
}

void DXLabSuiteCommanderTransceiver::poll ()
{
#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS
  bool quiet {false};
#else
  bool quiet {true};
#endif

  auto reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>", quiet);
  if (0 == reply.indexOf ("<CmdFreq:"))
    {
      auto f = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
      if (f)
        {
          if (!state ().ptt ()) // Commander is not reliable on frequency
                                // polls while transmitting
            {
              update_rx_frequency (f);
            }
        }
    }
  else
    {
      TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "get frequency unexpected response" << reply);
      throw error {tr ("DX Lab Suite Commander didn't respond correctly polling frequency: ") + reply};
    }

  if (state ().split ())
    {
      reply = command_with_reply ("<command:12>CmdGetTXFreq<parameters:0>", quiet);
      if (0 == reply.indexOf ("<CmdTXFreq:"))
        {
          auto f = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
          if (f)
            {
              if (!state ().ptt ()) // Commander is not reliable on frequency
                                // polls while transmitting
                {
                  update_other_frequency (f);
                }
            }
        }
      else
        {
          TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "get tx frequency unexpected response" << reply);
          throw error {tr ("DX Lab Suite Commander didn't respond correctly polling TX frequency: ") + reply};
        }
    }

  reply = command_with_reply ("<command:12>CmdSendSplit<parameters:0>", quiet);
  if (0 == reply.indexOf ("<CmdSplit:"))
    {
      auto split = reply.mid (reply.indexOf ('>') + 1);
      if ("ON" == split)
        {
          update_split (true);
        }
      else if ("OFF" == split)
        {
          update_split (false);
        }
      else
        {
          TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "unexpected split state" << split);
          throw error {tr ("DX Lab Suite Commander sent an unrecognised split state: ") + split};
        }
    }
  else
    {
      TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "get split mode unexpected response" << reply);
      throw error {tr ("DX Lab Suite Commander didn't respond correctly polling split status: ") + reply};
    }

  get_mode (quiet);
}

auto DXLabSuiteCommanderTransceiver::get_mode (bool no_debug) -> MODE
{
  MODE m {UNK};
  auto reply = command_with_reply ("<command:11>CmdSendMode<parameters:0>", no_debug);
  if (0 == reply.indexOf ("<CmdMode:"))
    {
      auto mode = reply.mid (reply.indexOf ('>') + 1);
      if ("AM" == mode)
        {
          m = AM;
        }
      else if ("CW" == mode)
        {
          m = CW;
        }
      else if ("CW-R" == mode)
        {
          m = CW_R;
        }
      else if ("FM" == mode || "WBFM" == mode)
        {
          m = FM;
        }
      else if ("LSB" == mode)
        {
          m = LSB;
        }
      else if ("USB" == mode)
        {
          m = USB;
        }
      else if ("RTTY" == mode)
        {
          m = FSK;
        }
      else if ("RTTY-R" == mode)
        {
          m = FSK_R;
        }
      else if ("PKT" == mode || "DATA-L" == mode || "Data-L" == mode || "DIGL" == mode)
        {
          m = DIG_L;
        }
      else if ("PKT-R" == mode || "DATA-U" == mode || "Data-U" == mode || "DIGU" == mode)
        {
          m = DIG_U;
        }
      else
        {
          TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "unexpected mode name" << mode);
          throw error {tr ("DX Lab Suite Commander sent an unrecognised mode: \"") + mode + '"'};
        }
      update_mode (m);
    }
  else
    {
      TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "unexpected response" << reply);
      throw error {tr ("DX Lab Suite Commander didn't respond correctly polling mode: ") + reply};
    }
  return m;
}

void DXLabSuiteCommanderTransceiver::simple_command (QString const& cmd, bool no_debug)
{
  Q_ASSERT (commander_);

  if (!no_debug)
    {
      TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd);
    }

  if (!write_to_port (cmd))
    {
      TRACE_CAT ("DXLabSuiteCommanderTransceiver", "failed:" << commander_->errorString ());
      throw error {tr ("DX Lab Suite Commander send command failed\n") + commander_->errorString ()};
    }
}

QString DXLabSuiteCommanderTransceiver::command_with_reply (QString const& cmd, bool no_debug)
{
  Q_ASSERT (commander_);

  if (!write_to_port (cmd))
    {
      TRACE_CAT ("DXLabSuiteCommanderTransceiver", "failed to send command:" << commander_->errorString ());
      throw error {
        tr ("DX Lab Suite Commander failed to send command \"%1\": %2\n")
          .arg (cmd)
          .arg (commander_->errorString ())
          };
    }

  // waitForReadReady appears to be unreliable on Windows timing out
  // when data is waiting so retry a few times
  unsigned retries {5};
  bool replied {false};
  while (!replied && --retries)
    {
      replied = commander_->waitForReadyRead ();
      if (!replied && commander_->error () != commander_->SocketTimeoutError)
        {
          TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd << "failed to read reply:" << commander_->errorString ());
          throw error {
            tr ("DX Lab Suite Commander send command \"%1\" read reply failed: %2\n")
              .arg (cmd)
              .arg (commander_->errorString ())
              };
        }
    }

  if (!replied)
    {
      TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd << "retries exhausted");
      throw error {
        tr ("DX Lab Suite Commander retries exhausted sending command \"%1\"")
          .arg (cmd)
          };
    }

  auto result = commander_->readAll ();
  // qDebug () << "result: " << result;
  // for (int i = 0; i < result.size (); ++i)
  //   {
  //     qDebug () << i << ":" << hex << int (result[i]);
  //   }

  if (!no_debug)
    {
      TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd << "->" << result);
    }

  return result;                // converting raw UTF-8 bytes to QString
}

bool DXLabSuiteCommanderTransceiver::write_to_port (QString const& s)
{
  auto data = s.toLocal8Bit ();
  auto to_send = data.constData ();
  auto length = data.size ();

  qint64 total_bytes_sent {0};
  while (total_bytes_sent < length)
    {
      auto bytes_sent = commander_->write (to_send + total_bytes_sent, length - total_bytes_sent);
      if (bytes_sent < 0 || !commander_->waitForBytesWritten ())
        {
          return false;
        }

      total_bytes_sent += bytes_sent;
    }
  return true;
}

QString DXLabSuiteCommanderTransceiver::frequency_to_string (Frequency f) const
{
  // number is localized and in kHz, avoid floating point translation
  // errors by adding a small number (0.1Hz)
  return QString {"%L1"}.arg (f / 1e3 + 1e-4, 10, 'f', 3);
}

auto DXLabSuiteCommanderTransceiver::string_to_frequency (QString s) const -> Frequency
{
  // temporary hack because Commander is returning invalid UTF-8 bytes
  s.replace (QChar {QChar::ReplacementCharacter}, locale_.groupSeparator ());

  // remove DP - relies on n.nnn kHz format so we can do ulonglong
  // conversion to Hz
  bool ok;

  // auto f = locale_.toDouble (s, &ok); // use when CmdSendFreq and
                                      // CmdSendTxFreq reinstated

  auto f = QLocale::c ().toDouble (s, &ok); // temporary fix

  if (!ok)
    {
      throw error {tr ("DX Lab Suite Commander sent an unrecognized frequency")};
    }
  return (f + 1e-4) * 1e3;
}


code:
#ifndef DX_LAB_SUITE_COMMANDER_TRANSCEIVER_HPP__
#define DX_LAB_SUITE_COMMANDER_TRANSCEIVER_HPP__

#include <memory>

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

class QTcpSocket;
class QByteArray;
class QString;

//
// DX Lab Suite Commander Interface
//
// Implemented as a Transceiver decorator  because we may want the PTT
// services of another Transceiver  type such as the HamlibTransceiver
// which can  be enabled by wrapping  a HamlibTransceiver instantiated
// as a "Hamlib Dummy" transceiver in the Transceiver factory method.
//
class DXLabSuiteCommanderTransceiver final
  : public PollingTransceiver
{
  Q_OBJECT;                     // for translation context

public:
  static void register_transceivers (TransceiverFactory::Transceivers *, int id);

  // takes ownership of wrapped Transceiver
  explicit DXLabSuiteCommanderTransceiver (std::unique_ptr<TransceiverBase> wrapped,
                                           QString const& address, bool use_for_ptt,
                                           int poll_interval, QObject * parent = nullptr);

protected:
  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 on) override;

  void poll () override;

private:
  MODE get_mode (bool no_debug = false);
  void simple_command (QString const&, bool no_debug = false);
  QString command_with_reply (QString const&, bool no_debug = false);
  bool write_to_port (QString const&);
  QString frequency_to_string (Frequency) const;
  Frequency string_to_frequency (QString) const;

  std::unique_ptr<TransceiverBase> wrapped_; // may be null
  bool use_for_ptt_;
  QString server_;
  QTcpSocket * commander_;
  QLocale locale_;
};

#endif


code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>English</string>
    <key>CFBundleExecutable</key>
    <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
    <key>CFBundleGetInfoString</key>
    <string>${MACOSX_BUNDLE_INFO_STRING}</string>
    <key>CFBundleIconFile</key>
    <string>${MACOSX_BUNDLE_ICON_FILE}</string>
    <key>CFBundleIdentifier</key>
    <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleLongVersionString</key>
    <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
    <key>CFBundleName</key>
    <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
    <key>CSResourcesFileMapped</key>
    <true/>
    <key>LSRequiresCarbon</key>
    <true/>
    <key>NSHumanReadableCopyright</key>
    <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
    <key>NSPrincipalClass</key>
    <string>NSApplication</string>
    <key>NSHighResolutionCapable</key>
    <string>True</string>
</dict>
</plist>


code:
#!/bin/sh
WSJTX_BUNDLE="`echo "$0" | sed -e 's/\/Contents\/MacOS\/.*//'`"
WSJTX_RESOURCES="$WSJTX_BUNDLE/Contents/Resources"
WSJTX_TEMP="/tmp/js8call/$UID"

echo "running $0"
echo "WSJTX_BUNDLE: $WSJTX_BUNDLE"

# Setup temporary runtime files
rm -rf "$WSJTX_TEMP"

export "DYLD_LIBRARY_PATH=$WSJTX_RESOURCES/lib"
export "PATH=$WSJTX_RESOURCES/bin:$PATH"

#export
exec "$WSJTX_RESOURCES/bin/js8call"


code:
                    Notes on JS8Call Installation for Mac OS X
                  (replace all instances of WSJT-X with JS8Call)

                    Notes on WSJT-X Installation for Mac OS X
                    -----------------------------------------

                               Updated 16 July 2017
                               --------------------

If you have already downloaded a previous version of WSJT-X then I suggest
you change the name in the Applications folder from WSJT-X to WSJT-X_previous
before proceeding.

If you have installed a previous version of WSJT-X before then there is no
need to change anything on your system so proceed to NEXT.

BEGIN:

There are some system matters you must deal with first.  Open a Terminal window
by going to Applications->Utilities and clicking on Terminal.

Along with this ReadMe file there is a file:   sysctl.conf.   Drag this file to your Desktop.

WSJT-X makes use of a block of memory which is shared between different parts of
the code.  The normal allocation of shared memory on a Mac is insufficient and this
has to be increased.   You should use a Mac editor to examine sysctl.conf.

There are two important parameters that you need to consider.  shmmax determines the
amount of shared memory that must be allocated for WSJT-X to operate.  This is 14680064 (14MB)
and this is defined in the sysctl.conf file and should not be changed.

It is possible to run more than one instance of WSJT-X simultaneously.  See
"Section 14. Platform Dependencies" in the User Guide.  The second important parameter
shmall=17920 determines how many instances are permitted.  This is calculated as:
  (shmall x 4096/14680064) = 5.
The sysctl.conf file is configured to permit up to 5 instances of wsjtx to run simultaneously.
If this limitation is acceptable then you can continue to install the sysctl.conf file without making any
alterations.  Otherwise you must edit the file to increase shmall according to this calculation.

Now move this file into place for the system to use by typing: (Note this assumes that
you really did drag this file to your Desktop as required earlier.)

  sudo cp $HOME/Desktop/sysctl.conf /etc/
  sudo chmod 664 /etc/sysctl.conf
  sudo chown  root:wheel  /etc/sysctl.conf

and then reboot your Mac.  This is necessary to install the changes.  After the
reboot you should re-open the Terminal window as before and you can check that the
change has been made by typing:

  sysctl -a | grep sysv.shm

If shmmax is not shown as 14680064 then contact me since WSJT-X will fail to load with
an error message: "Unable to create shared memory segment".

You are now finished with system changes.  You should make certain that NO error messages
have been produced during these steps.   You can now close the Terminal window.  It will
not be necessary to repeat this procedure again, even when you download an updated
version of WSJT-X.

NEXT:

Drag the WSJT-X app to your preferred location, such as Applications.

You need to configure your sound card.   Visit Applications > Utilities > Audio MIDI
Setup and select your sound card and then set Format to be "48000Hz 2ch-16bit" for
input and output.

Now double-click on the WSJT-X app and two windows will appear.  Select Preferences
under the WSJT-X Menu and fill in various station details on the General panel.
I recommend checking the 4 boxes under the Display heading and the first 4 boxes under
the Behaviour heading.

Next visit the Audio panel and select the Audio Codec you use to communicate between
WSJT-X and your rig.   There are so many audio interfaces available that it is not
possible to give detailed advice on selection.  If you have difficulties contact me.
Note the location of the Save Directory.  Decoded wave forms are located here.

Look at the Reporting panel.  If you check the "Prompt me" box, a logging panel will appear
at the end of the QSO.  Two log files are provided in Library/Application Support/WSJT-X.
These are a simple wsjtx.log file and wsjtx_log.adi which is formatted for use with
logging databases.    The "File" menu bar items include a button "Open log directory"
to open the log directory in Finder for you, ready for processing by any logging
application you use.

Finally, visit the Radio panel.  WSJT-X is most effective when operated with CAT
control.  You will need to install the relevant Mac driver for your rig.   This must
be located in the device driver directory  /dev. You should install your driver
and then re-launch WSJT-X. Return to the the Radio panel in Preferences and in
the "Serial port" panel select your driver from the list that is presented.   If
for some reason your driver is not shown, then insert the full name
of your driver in the Serial Port panel.   Such as:  /dev/tty.PL2303-00002226 or
whatever driver you have.  The /dev/ prefix is mandatory.  Set the relevant
communication parameters as required by your transceiver and click "Test CAT" to
check.

WSJT-X needs the Mac clock to be accurate.  Visit System Preferences > Date & Time
and make sure that date and time are set automatically.  The drop-down menu will
normally offer you several time servers to choose from.

On the Help menu, have a look at the new Online User's Guide for operational hints
and tips.

Please email me if you have problems.

--- John G4KLA     (g4kla@rmnjmn.co.uk)


code:
Changing the content of the DragNDrop DMG root folder.
======================================================

The files and links in this folder are populated by the WSJT-X CMake build script. There are install commands which are only run on Apple hosts, this is important because they will get installed at the install root on other platforms, which would be very bad on Linux for example since that is /usr normally!

The symlink to /Applications, the background image (derived from ".../artwork/DragNDrop Background.svg") and, the custom .DS_Store file (".../Darwin/wsjtx_DMG.DS_Store") are all handled specifically by the CPack DragNDrop packager so you don't need to install those.

Modifying the .DS_Store folder options for the DragNDrop DMG root folder.
=========================================================================

The DragNDrop installer is a generated a DMG file that has a custom .DS_Store file that defines the layout, background image and, folder view options of the DMG root folder.

To modify this file, first you need to make a DragNDrop package then mount the DMG file, then modify the root .DS_Store file using Finder. Once you are happy with the results, you check into source control the modified .DS_Store file and then future package builds will use that file.

The installer DMG is read only and shrunk to exactly the size of the contents, also the .DS_Store file is read only and the background image PNG file is hidden. You need to undo all of these things before changing the .DS_Store file. Don't forget to redo these things before checking in a new version of the custom .DS_Store file.

The following recipe shows how to amend the content and layout of the DMG root folder:

# convert the DMG to a R/W copy (substitute the DMG you have built)
hdiutil convert wsjtx-1.7.0-rc1-Darwin.dmg -format UDRW -o rw.dmg

# expand the R/W copy to make room for changes
# first find the current number of sectors
hdiutil resize -limits rw.dmg
# the output looks like:
#
#  min 	 cur 	 max
#109696	109696	33037872
#
# you need to increase the sector count to something a bit bigger than current
#  e.g. in this case use 110000
hdiutil resize -sectors 110000 rw.dmg

# now you can mount the R/W DMG
hdiutil attach rw.dmg

# make the .DS_Store file writeable
chmod 644 /Volumes/WSJT-X/.DS_Store

# now you can change Finder view options, rearrange icons etc. Remember that you are
# only changing the folder options, not the folder content as that is controlled by
# the install steps in the project CMakeLists.txt if you are adding or removing a file
# to the DMG root folder, you need to have changed the install steps before doing this
# procedure so the content changes are reflected in the installer DMG you start with.

# when you are happy with the layout etc. move the Finder window by any amount, this
# ensures that the .DS_Store file is updated from Finder's cache.

# make the .DS_Store file read only
chmod 444 /Volumes/WSJT-X/.DS_Store

# update the custom .DS_Store file in the source repository (NOTE the file name)
cp /Volumes/WSJT-X/.DS_Store .../wsjtx/wsjtx_DMG.DS_Store

# dismount and eject the R/W DMG and discard it
hdiutil detach /Volumes/WSJT-X
rm rw.dmg

# build a new package and try out the new installer to test your changes
# if all is well commit the changes

# That's all Folks!

path: Darwin/developer read me.txt


code:
#!/bin/bash
"$2@CMAKE_INSTALL_SUBDIR@/@WSJTX_BUNDLE_NAME@.app/Contents/MacOS/@WSJTX_BUNDLE_NAME@" --mac-install
exit 0


code:
kern.sysv.shmmax=14680064
kern.sysv.shmmin=1
kern.sysv.shmmni=128
kern.sysv.shmseg=32
kern.sysv.shmall=17920


code:
#include "Detector.hpp"
#include <QDateTime>
#include <QtAlgorithms>
#include <QDebug>
#include "commons.h"

#include "DriftingDateTime.h"

#include "moc_Detector.cpp"

extern "C" {
  void   fil4_(qint16*, qint32*, qint16*, qint32*);
}

Detector::Detector (unsigned frameRate, unsigned periodLengthInSeconds,
                    unsigned downSampleFactor, QObject * parent)
  : AudioDevice (parent)
  , m_frameRate (frameRate)
  , m_period (periodLengthInSeconds)
  , m_downSampleFactor (downSampleFactor)
  , m_samplesPerFFT {max_buffer_size}
  , m_ns (999)
  , m_buffer ((downSampleFactor > 1) ?
              new short [max_buffer_size * downSampleFactor] : nullptr)
  , m_bufferPos (0)
{
  (void)m_frameRate;            // quell compiler warning
  clear ();
}

void Detector::setBlockSize (unsigned n)
{
  m_samplesPerFFT = n;
}

bool Detector::reset ()
{
  clear ();
  // don't call base call reset because it calls seek(0) which causes
  // a warning
  return isOpen ();
}

void Detector::clear ()
{
  // set index to roughly where we are in time (1ms resolution)
  // qint64 now (DriftingDateTime::currentMSecsSinceEpoch ());
  // unsigned msInPeriod ((now % 86400000LL) % (m_period * 1000));
  // dec_data.params.kin = qMin ((msInPeriod * m_frameRate) / 1000, static_cast<unsigned> (sizeof (dec_data.d2) / sizeof (dec_data.d2[0])));
  dec_data.params.kin = 0;
  m_bufferPos = 0;

  // fill buffer with zeros (G4WJS commented out because it might cause decoder hangs)
  // qFill (dec_data.d2, dec_data.d2 + sizeof (dec_data.d2) / sizeof (dec_data.d2[0]), 0);
}

qint64 Detector::writeData (char const * data, qint64 maxSize)
{
  int ns=secondInPeriod();
  if(ns < m_ns) {                      // When ns has wrapped around to zero, restart the buffers
    dec_data.params.kin = 0;
    m_bufferPos = 0;
  }
  m_ns=ns;

  // no torn frames
  Q_ASSERT (!(maxSize % static_cast<qint64> (bytesPerFrame ())));
  // these are in terms of input frames (not down sampled)
  size_t framesAcceptable ((sizeof (dec_data.d2) /
                            sizeof (dec_data.d2[0]) - dec_data.params.kin) * m_downSampleFactor);
  size_t framesAccepted (qMin (static_cast<size_t> (maxSize /
                                                    bytesPerFrame ()), framesAcceptable));

  if (framesAccepted < static_cast<size_t> (maxSize / bytesPerFrame ())) {
    qDebug () << "dropped " << maxSize / bytesPerFrame () - framesAccepted
                << " frames of data on the floor!"
                << dec_data.params.kin << ns;
    }

    for (unsigned remaining = framesAccepted; remaining; ) {
      size_t numFramesProcessed (qMin (m_samplesPerFFT *
                                       m_downSampleFactor - m_bufferPos, remaining));

      if(m_downSampleFactor > 1) {
        store (&data[(framesAccepted - remaining) * bytesPerFrame ()],
               numFramesProcessed, &m_buffer[m_bufferPos]);
        m_bufferPos += numFramesProcessed;

        if(m_bufferPos==m_samplesPerFFT*m_downSampleFactor) {
          qint32 framesToProcess (m_samplesPerFFT * m_downSampleFactor);
          qint32 framesAfterDownSample (m_samplesPerFFT);
          if(m_downSampleFactor > 1 && dec_data.params.kin>=0 &&
             dec_data.params.kin < (NTMAX*12000 - framesAfterDownSample)) {
            fil4_(&m_buffer[0], &framesToProcess, &dec_data.d2[dec_data.params.kin],
                  &framesAfterDownSample);
            dec_data.params.kin += framesAfterDownSample;
          } else {
            // qDebug() << "framesToProcess     = " << framesToProcess;
            // qDebug() << "dec_data.params.kin = " << dec_data.params.kin;
            // qDebug() << "secondInPeriod      = " << secondInPeriod();
            // qDebug() << "framesAfterDownSample" << framesAfterDownSample;
          }
          Q_EMIT framesWritten (dec_data.params.kin);
          m_bufferPos = 0;
        }

      } else {
        store (&data[(framesAccepted - remaining) * bytesPerFrame ()],
               numFramesProcessed, &dec_data.d2[dec_data.params.kin]);
        m_bufferPos += numFramesProcessed;
        dec_data.params.kin += numFramesProcessed;
        if (m_bufferPos == static_cast<unsigned> (m_samplesPerFFT)) {
          Q_EMIT framesWritten (dec_data.params.kin);
          m_bufferPos = 0;
        }
      }
      remaining -= numFramesProcessed;
    }

  return maxSize;    // we drop any data past the end of the buffer on
  // the floor until the next period starts
}

unsigned Detector::secondInPeriod () const
{
  // we take the time of the data as the following assuming no latency
  // delivering it to us (not true but close enough for us)
  qint64 now (DriftingDateTime::currentMSecsSinceEpoch ());

  unsigned secondInToday ((now % 86400000LL) / 1000);
  return secondInToday % m_period;
}


code:
#ifndef DETECTOR_HPP__
#define DETECTOR_HPP__
#include "AudioDevice.hpp"
#include <QScopedArrayPointer>

//
// output device that distributes data in predefined chunks via a signal
//
// the underlying device for this abstraction is just the buffer that
// stores samples throughout a receiving period
//
class Detector : public AudioDevice
{
  Q_OBJECT;

public:
  //
  // if the data buffer were not global storage and fixed size then we
  // might want maximum size passed as constructor arguments
  //
  // we down sample by a factor of 4
  //
  // the samplesPerFFT argument is the number after down sampling
  //
  Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned downSampleFactor = 4u, QObject * parent = 0);

  void setTRPeriod(unsigned p) {m_period=p;}
  bool reset () override;

  Q_SIGNAL void framesWritten (qint64) const;
  Q_SLOT void setBlockSize (unsigned);

protected:
  qint64 readData (char * /* data */, qint64 /* maxSize */) override
  {
    return -1;			// we don't produce data
  }

  qint64 writeData (char const * data, qint64 maxSize) override;

private:
  void clear ();		// discard buffer contents
  unsigned secondInPeriod () const;

  unsigned m_frameRate;
  unsigned m_period;
  unsigned m_downSampleFactor;
  qint32 m_samplesPerFFT;	// after any down sampling
  qint32 m_ns;
  static size_t const max_buffer_size {7 * 512};
  QScopedArrayPointer<short> m_buffer; // de-interleaved sample buffer
  // big enough for all the
  // samples for one increment of
  // data (a signals worth) at
  // the input sample rate
  unsigned m_bufferPos;
};

#endif


code:
#include "DisplayManual.hpp"

#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QString>
#include <QDir>
#include <QFileInfo>
#include <QDesktopServices>
#include <QLocale>

#include "revision_utils.hpp"

#include "pimpl_impl.hpp"

namespace
{
  class token
    : public QObject
  {
    Q_OBJECT
  public:
    token (QUrl const& url, QString const& lang, QString const& name_we, QObject * parent = nullptr)
      : QObject {parent}
      , url_ {url}
      , lang_ {lang}
      , name_we_ {name_we}
    {
    }

    QUrl url_;
    QString lang_;
    QString name_we_;
  };
}

class DisplayManual::impl final
  : public QObject
{
  Q_OBJECT
public:
  impl (QNetworkAccessManager * qnam)
    : qnam_ {qnam}
  {
    connect (qnam_, &QNetworkAccessManager::finished, this, &DisplayManual::impl::reply_finished);
  }

  void display (QUrl const& url, QString const& name_we)
  {
    if (QNetworkAccessManager::Accessible != qnam_->networkAccessible ()) {
      // try and recover network access for QNAM
      qnam_->setNetworkAccessible (QNetworkAccessManager::Accessible);
    }

    // try and find a localized manual
    auto lang = QLocale::system ().name ();
    // try for language and country first
    auto file = name_we + '_' + lang + '-' + version () + ".html";
    auto target = url.resolved (file);
    QNetworkRequest request {target};
    request.setRawHeader ("User-Agent", "WSJT-X Manual Checker");
    request.setOriginatingObject (new token {url, lang, name_we, this});
    auto * reply = qnam_->head (request);
    outstanding_requests_ << reply;
  }

  void reply_finished (QNetworkReply * reply)
  {
    if (outstanding_requests_.contains (reply))
      {
        QUrl target;
        if (reply->error ())
          {
            if (auto * tok = qobject_cast<token *> (reply->request ().originatingObject ()))
              {
                auto pos = tok->lang_.lastIndexOf ('_');
                QString file;
                if (pos >= 0)
                  {
                    tok->lang_.truncate (pos);
                    file = tok->name_we_ + '_' + tok->lang_ + '-' + version () + ".html";
                    target = tok->url_.resolved (file);
                    QNetworkRequest request {target};
                    request.setRawHeader ("User-Agent", "WSJT-X Manual Checker");
                    request.setOriginatingObject (tok);
                    auto * reply = qnam_->head (request);
                    outstanding_requests_ << reply;
                  }
                else
                  {
                    // give up looking and request the default
                    file = tok->name_we_ + '-' + version () + ".html";
                    target = tok->url_.resolved (file);
                    QDesktopServices::openUrl (target);
                    delete tok;
                  }
              }
          }
        else
          {
            // found it
            if (auto * tok = qobject_cast<token *> (reply->request ().originatingObject ()))
              {
                delete tok;
              }
            QDesktopServices::openUrl (reply->request ().url ());
          }

        outstanding_requests_.removeOne (reply);
        reply->deleteLater ();
      }
  }

  QNetworkAccessManager * qnam_;
  QList<QNetworkReply *> outstanding_requests_;
};

#include "DisplayManual.moc"

DisplayManual::DisplayManual (QNetworkAccessManager * qnam, QObject * parent)
  : QObject {parent}
  , m_ {qnam}
{
}

DisplayManual::~DisplayManual ()
{
}

void DisplayManual::display_html_url (QUrl const& url, QString const& name_we)
{
  m_->display (url, name_we);
}

void DisplayManual::display_html_file (QDir const& dir, QString const& name_we)
{
  // try and find a localized manual
  auto lang = QLocale::system ().name ();
  // try for language and country first
  auto file = dir.absoluteFilePath (name_we + '_' + lang + '-' + version () + ".html");
  if (!QFileInfo::exists (file))
    {
      // try for language
      lang.truncate (lang.lastIndexOf ('_'));
      file = dir.absoluteFilePath (name_we + '_' + lang + '-' + version () + ".html");
      if (!QFileInfo::exists (file))
        {
          // use default
          file = dir.absoluteFilePath (name_we + '-' + version () + ".html");
        }
    }
  // may fail but browser 404 error is a good as anything
  QDesktopServices::openUrl (QUrl {"file:///" + file});
}


code:
#ifndef DISPLAY_MANUAL_HPP__
#define DISPLAY_MANUAL_HPP__

#include <QObject>

#include "pimpl_h.hpp"

class QNetworkAccessManager;
class QDir;
class QUrl;
class QString;

class DisplayManual
  : public QObject
{
public:
  DisplayManual (QNetworkAccessManager *, QObject * = nullptr);
  ~DisplayManual ();
  void display_html_url (QUrl const& url, QString const& name_we);
  void display_html_file (QDir const& dir, QString const& name_we);

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

#endif


code:
#include "DoubleClickablePushButton.hpp"

#include "moc_DoubleClickablePushButton.cpp"

DoubleClickablePushButton::DoubleClickablePushButton (QWidget * parent)
  : QPushButton {parent}
{
}

void DoubleClickablePushButton::mouseDoubleClickEvent (QMouseEvent * event)
{
  Q_EMIT doubleClicked ();
  QPushButton::mouseDoubleClickEvent (event);
}


code:
#ifndef DOUBLE_CLICKABLE_PUSH_BUTTON_HPP_
#define DOUBLE_CLICKABLE_PUSH_BUTTON_HPP_

#include <QPushButton>

//
// DoubleClickablePushButton - QPushButton that emits a mouse double
//                 click signal
//
//  Clients  should be  aware of  the QWidget::mouseDoubleClickEvent()
//  notes about receipt of mouse press and mouse release events.
//
class DoubleClickablePushButton
  : public QPushButton
{
  Q_OBJECT

public:
  DoubleClickablePushButton (QWidget * = nullptr);

  Q_SIGNAL void doubleClicked ();

protected:
  void mouseDoubleClickEvent (QMouseEvent *) override;
};

#endif


code:
#include "DoubleClickableRadioButton.hpp"

#include "moc_DoubleClickableRadioButton.cpp"

DoubleClickableRadioButton::DoubleClickableRadioButton (QWidget * parent)
  : QRadioButton {parent}
{
}

void DoubleClickableRadioButton::mouseDoubleClickEvent (QMouseEvent * event)
{
  Q_EMIT doubleClicked ();
  QRadioButton::mouseDoubleClickEvent (event);
}


code:
#ifndef DOUBLE_CLICKABLE_RADIO_BUTTON_HPP_
#define DOUBLE_CLICKABLE_RADIO_BUTTON_HPP_

#include <QRadioButton>

//
// DoubleClickableRadioButton - QRadioButton that emits a mouse double
//                 click signal
//
//  Clients  should be  aware of  the QWidget::mouseDoubleClickEvent()
//  notes about receipt of mouse press and mouse release events.
//
class DoubleClickableRadioButton
  : public QRadioButton
{
  Q_OBJECT

public:
  DoubleClickableRadioButton (QWidget * = nullptr);

  Q_SIGNAL void doubleClicked ();

protected:
  void mouseDoubleClickEvent (QMouseEvent *) override;
};

#endif


code:
#include "DriftingDateTime.h"

qint64 driftms = 0;

QDateTime DriftingDateTime::currentDateTime(){
    return QDateTime::currentDateTime().addMSecs(driftms);
}

QDateTime DriftingDateTime::currentDateTimeUtc(){
    return QDateTime::currentDateTimeUtc().addMSecs(driftms);
}

qint64 DriftingDateTime::currentMSecsSinceEpoch(){
    return QDateTime::currentMSecsSinceEpoch() + driftms;
}

qint64 DriftingDateTime::drift(){
    return driftms;
}

void DriftingDateTime::setDrift(qint64 ms){
    driftms = ms;
}

qint64 DriftingDateTime::incrementDrift(qint64 msdelta){
    driftms += msdelta;
    return driftms;
}


code:
#ifndef DRIFTINGDATETIME_H
#define DRIFTINGDATETIME_H

#include <QDateTime>

class DriftingDateTime /*: QDateTime*/
{
public:
    static QDateTime currentDateTime();
    static QDateTime currentDateTimeUtc();
    static qint64 currentMSecsSinceEpoch();

    static qint64 drift();
    static void setDrift(qint64 ms);
    static qint64 incrementDrift(qint64 msdelta);

private:

};

#endif // DRIFTINGDATETIME_H


code:
#include "EmulateSplitTransceiver.hpp"

EmulateSplitTransceiver::EmulateSplitTransceiver (std::unique_ptr<Transceiver> wrapped, QObject * parent)
  : Transceiver {parent}
  , wrapped_ {std::move (wrapped)}
  , rx_frequency_ {0}
  , tx_frequency_ {0}
  , split_ {false}
{
  // Connect update signal of wrapped Transceiver object instance to ours.
  connect (wrapped_.get (), &Transceiver::update, this, &EmulateSplitTransceiver::handle_update);

  // Connect other signals of wrapped Transceiver object to our
  // parent matching signals.
  connect (wrapped_.get (), &Transceiver::resolution, this, &Transceiver::resolution);
  connect (wrapped_.get (), &Transceiver::finished, this, &Transceiver::finished);
  connect (wrapped_.get (), &Transceiver::failure, this, &Transceiver::failure);
}

void EmulateSplitTransceiver::set (TransceiverState const& s, unsigned sequence_number) noexcept
{
#if WSJT_TRACE_CAT
  qDebug () << "EmulateSplitTransceiver::set: state:" << s << "#:" << sequence_number;
#endif
  // save for use in updates
  rx_frequency_ = s.frequency ();
  tx_frequency_ = s.tx_frequency ();
  split_ = s.split ();

  TransceiverState emulated_state {s};
  if (s.ptt () && split_) emulated_state.frequency (s.tx_frequency ());
  emulated_state.split (false);
  emulated_state.tx_frequency (0);
  wrapped_->set (emulated_state, sequence_number);
}

void EmulateSplitTransceiver::handle_update (TransceiverState const& state,
                                             unsigned sequence_number)
{
#if WSJT_TRACE_CAT
  qDebug () << "EmulateSplitTransceiver::handle_update: from wrapped:" << state;
#endif

  if (state.split ())
    {
      Q_EMIT failure (tr ("Emulated split mode requires rig to be in simplex mode"));
    }
  else
    {
      TransceiverState new_state {state};
      // Follow the rig if in RX mode.
      if (state.ptt ()) new_state.frequency (rx_frequency_);

      // These are always what was requested in prior set state operation
      new_state.tx_frequency (tx_frequency_);
      new_state.split (split_);

#if WSJT_TRACE_CAT
      qDebug () << "EmulateSplitTransceiver::handle_update: signalling:" << state;
#endif

      // signal emulated state
      Q_EMIT update (new_state, sequence_number);
    }
}


code:
#ifndef EMULATE_SPLIT_TRANSCEIVER_HPP__
#define EMULATE_SPLIT_TRANSCEIVER_HPP__

#include <memory>

#include "Transceiver.hpp"

//
// Emulate Split Transceiver
//
// Helper decorator class that encapsulates  the emulation of split TX
// operation.
//
// Responsibilities
//
//  Delegates  all  but setting  of  other  (split) frequency  to  the
//  wrapped Transceiver instance. Also routes failure signals from the
//  wrapped Transceiver instance to this instances failure signal.
//
//  Intercepts status  updates from  the wrapped  Transceiver instance
//  and re-signals it with the emulated status.
//
//  Generates a status update signal if the other (split) frequency is
//  changed, this is necessary  since the wrapped transceiver instance
//  never receives other frequency changes.
//
class EmulateSplitTransceiver final
  : public Transceiver
{
public:
  // takes ownership of wrapped Transceiver
  explicit EmulateSplitTransceiver (std::unique_ptr<Transceiver> wrapped,
                                    QObject * parent = nullptr);

  void set (TransceiverState const&,
            unsigned sequence_number) noexcept override;

  // forward everything else to wrapped Transceiver
  void start (unsigned sequence_number) noexcept override {wrapped_->start (sequence_number);}
  void stop () noexcept override {wrapped_->stop ();}

private:
  void handle_update (TransceiverState const&, unsigned seqeunce_number);

  std::unique_ptr<Transceiver> wrapped_;
  Frequency rx_frequency_;        // requested Rx frequency
  Frequency tx_frequency_;        // requested Tx frequency
  bool split_; // requested split state
};

#endif


code:
#include "EqualizationToolsDialog.hpp"

#include <iterator>
#include <algorithm>
#include <fstream>
#include <limits>
#include <cmath>

#include <QDir>
#include <QVector>
#include <QHBoxLayout>
#include <QDialog>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QFileDialog>
#include <QSettings>

#include "SettingsGroup.hpp"
#include "qcustomplot.h"
#include "pimpl_impl.hpp"

namespace
{
  float constexpr PI = 3.1415927f;
  char const * const title = "Equalization Tools";
  size_t constexpr intervals = 144;

  // plot data loaders - wraps a plot providing value_type and
  // push_back so that a std::back_inserter output iterator can be
  // used to load plot data
  template<typename T, typename A>
  struct plot_data_loader
  {
  public:
    typedef T value_type;

    // the adjust argument is a function that is passed the plot
    // pointer, the graph index and a data point, it returns a
    // possibly adjusted data point and can modify the graph including
    // adding extra points or gaps (quiet_NaN)
    plot_data_loader (QCustomPlot * plot, int graph_index, A adjust)
      : plot_ {plot}
      , index_ {graph_index}
      , adjust_ (adjust)
    {
    }

    // load point into graph
    void push_back (value_type const& d)
    {
      plot_->graph (index_)->data ()->add (adjust_ (plot_, index_, d));
    }

  private:
    QCustomPlot * plot_;
    int index_;
    A adjust_;
  };
  // helper function template to make a plot_data_loader instance
  template<typename A>
  auto make_plot_data_loader (QCustomPlot * plot, int index, A adjust)
    -> plot_data_loader<QCPGraphData, decltype (adjust)>
  {
    return plot_data_loader<QCPGraphData, decltype (adjust)> {plot, index, adjust};
  }
  // identity adjust function when no adjustment is needed with the
  // above instantiation helper
  QCPGraphData adjust_identity (QCustomPlot *, int, QCPGraphData const& v) {return v;}

  // a plot_data_loader adjustment function that wraps Y values of
  // (-1..+1) plotting discontinuities as gaps in the graph data
  auto wrap_pi = [] (QCustomPlot * plot, int index, QCPGraphData d)
  {
    double constexpr limit {1};
    static unsigned wrap_count {0};
    static double last_x {std::numeric_limits<double>::lowest ()};

    d.value += 2 * limit * wrap_count;
    if (d.value > limit)
      {
        // insert a gap in the graph
        plot->graph (index)->data ()->add ({last_x + (d.key - last_x) / 2
              , std::numeric_limits<double>::quiet_NaN ()});
        while (d.value > limit)
          {
            --wrap_count;
            d.value -= 2 * limit;
          }
      }
    else if (d.value < -limit)
      {
        // insert a gap into the graph
        plot->graph (index)->data ()->add ({last_x + (d.key - last_x) / 2
              , std::numeric_limits<double>::quiet_NaN ()});
        while (d.value < -limit)
          {
            ++wrap_count;
            d.value += 2 * limit;
          }
      }
    last_x = d.key;
    return d;
  };

  // generate points of type R from a function of type F for X in
  // (-1..+1) with N intervals and function of type SX to scale X and
  // of type SY to scale Y
  //
  // it is up to the user to call the generator sufficient times which
  // is interval+1 times to reach +1
  template<typename R, typename F, typename SX, typename SY>
  struct graph_generator
  {
  public:
    graph_generator (F f, size_t intervals, SX x_scaling, SY y_scaling)
      : x_ {0}
      , f_ (f)
      , intervals_ {intervals}
      , x_scaling_ (x_scaling)
      , y_scaling_ (y_scaling)
    {
    }

    R operator () ()
    {
      typename F::value_type x {x_++ * 2.f / intervals_ - 1.f};
      return {x_scaling_ (x), y_scaling_ (f_ (x))};
    }

  private:
    int x_;
    F f_;
    size_t intervals_;
    SX x_scaling_;
    SY y_scaling_;
  };
  // helper function template to make a graph_generator instance for
  // QCPGraphData type points with intervals intervals
  template<typename F, typename SX, typename SY>
  auto make_graph_generator (F function, SX x_scaling, SY y_scaling)
    -> graph_generator<QCPGraphData, F, decltype (x_scaling), decltype (y_scaling)>
  {
    return graph_generator<QCPGraphData, F, decltype (x_scaling), decltype (y_scaling)>
      {function, intervals, x_scaling, y_scaling};
  }

  // template function object for a polynomial with coefficients
  template<typename C>
  class polynomial
  {
  public:
    typedef typename C::value_type value_type;

    explicit polynomial (C const& coefficients)
      : c_ {coefficients}
    {
    }

    value_type operator () (value_type const& x)
    {
      value_type y {};
      for (typename C::size_type i = c_.size (); i > 0; --i)
        {
          y = c_[i - 1] + x * y;
        }
      return y;
    }

  private:
    C c_;
  };
  // helper function template to instantiate a polynomial instance
  template<typename C>
  auto make_polynomial (C const& coefficients) -> polynomial<C>
  {
    return polynomial<C> (coefficients);
  }

  // template function object for a group delay with coefficients
  template<typename C>
  class group_delay
  {
  public:
    typedef typename C::value_type value_type;

    explicit group_delay (C const& coefficients)
      : c_ {coefficients}
    {
    }

    value_type operator () (value_type const& x)
    {
      value_type tau {};
      for (typename C::size_type i = 2; i < c_.size (); ++i)
        {
          tau += i * c_[i] * std::pow (x, i - 1);
        }
      return -1 / (2 * PI) * tau;
    }

  private:
    C c_;
  };
  // helper function template to instantiate a group_delay function
  // object
  template<typename C>
  auto make_group_delay (C const& coefficients) -> group_delay<C>
  {
    return group_delay<C> (coefficients);
  }

  // handy identity function
  template<typename T> T identity (T const& v) {return v;}

  // a lambda that scales the X axis from normalized to (500..2500)Hz
  auto freq_scaling = [] (float v) -> float {return 1500.f + 1000.f * v;};

  // a lambda that scales the phase Y axis from radians to units of Pi
  auto pi_scaling = [] (float v) -> float {return v / PI;};
}

class EqualizationToolsDialog::impl final
  : public QDialog
{
  Q_OBJECT

public:
  explicit impl (EqualizationToolsDialog * self, QSettings * settings
                 , QDir const& data_directory, QVector<double> const& coefficients
                 , QWidget * parent);
  ~impl () {save_window_state ();}

protected:
  void closeEvent (QCloseEvent * e) override
  {
    save_window_state ();
    QDialog::closeEvent (e);
  }

private:
  void save_window_state ()
  {
    SettingsGroup g (settings_, title);
    settings_->setValue ("geometry", saveGeometry ());
  }

  void plot_current ();
  void plot_phase ();
  void plot_amplitude ();

  EqualizationToolsDialog * self_;
  QSettings * settings_;
  QDir data_directory_;
  QHBoxLayout layout_;
  QVector<double> current_coefficients_;
  QVector<double> new_coefficients_;
  unsigned amp_poly_low_;
  unsigned amp_poly_high_;
  QVector<double> amp_coefficients_;
  QCustomPlot plot_;
  QDialogButtonBox button_box_;
};

#include "EqualizationToolsDialog.moc"

EqualizationToolsDialog::EqualizationToolsDialog (QSettings * settings
                                                  , QDir const& data_directory
                                                  , QVector<double> const& coefficients
                                                  , QWidget * parent)
  : m_ {this, settings, data_directory, coefficients, parent}
{
}

void EqualizationToolsDialog::show ()
{
  m_->show ();
}

EqualizationToolsDialog::impl::impl (EqualizationToolsDialog * self
                                     , QSettings * settings
                                     , QDir const& data_directory
                                     , QVector<double> const& coefficients
                                     , QWidget * parent)
  : QDialog {parent}
  , self_ {self}
  , settings_ {settings}
  , data_directory_ {data_directory}
  , current_coefficients_ {coefficients}
  , amp_poly_low_ {0}
  , amp_poly_high_ {6000}
  , button_box_ {QDialogButtonBox::Discard | QDialogButtonBox::Apply
        | QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Close
        , Qt::Vertical}
{
  setWindowTitle (windowTitle () + ' ' + tr (title));
  resize (500, 600);
  {
    SettingsGroup g {settings_, title};
    restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ());
  }

  auto legend_title = new QCPTextElement {&plot_, tr ("Phase"), QFont {"sans", 9, QFont::Bold}};
  legend_title->setLayer (plot_.legend->layer ());
  plot_.legend->addElement (0, 0, legend_title);
  plot_.legend->setVisible (true);

  plot_.xAxis->setLabel (tr ("Freq (Hz)"));
  plot_.xAxis->setRange (500, 2500);
  plot_.yAxis->setLabel (tr ("Phase (Π)"));
  plot_.yAxis->setRange (-1, +1);
  plot_.yAxis2->setLabel (tr ("Delay (ms)"));
  plot_.axisRect ()->setRangeDrag (Qt::Vertical);
  plot_.axisRect ()->setRangeZoom (Qt::Vertical);
  plot_.yAxis2->setVisible (true);
  plot_.axisRect ()->setRangeDragAxes (nullptr, plot_.yAxis2);
  plot_.axisRect ()->setRangeZoomAxes (nullptr, plot_.yAxis2);
  plot_.axisRect ()->insetLayout ()->setInsetAlignment (0, Qt::AlignBottom|Qt::AlignRight);
  plot_.setInteractions (QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);

  plot_.addGraph ()->setName (tr ("Measured"));
  plot_.graph ()->setPen (QPen {Qt::blue});
  plot_.graph ()->setVisible (false);
  plot_.graph ()->removeFromLegend ();

  plot_.addGraph ()->setName (tr ("Proposed"));
  plot_.graph ()->setPen (QPen {Qt::red});
  plot_.graph ()->setVisible (false);
  plot_.graph ()->removeFromLegend ();

  plot_.addGraph ()->setName (tr ("Current"));
  plot_.graph ()->setPen (QPen {Qt::green});

  plot_.addGraph (plot_.xAxis, plot_.yAxis2)->setName (tr ("Group Delay"));
  plot_.graph ()->setPen (QPen {Qt::darkGreen});

  plot_.plotLayout ()->addElement (new QCPAxisRect {&plot_});
  plot_.plotLayout ()->setRowStretchFactor (1, 0.5);

  auto amp_legend = new QCPLegend;
  plot_.axisRect (1)->insetLayout ()->addElement (amp_legend, Qt::AlignTop | Qt::AlignRight);
  plot_.axisRect (1)->insetLayout ()->setMargins (QMargins {12, 12, 12, 12});
  amp_legend->setVisible (true);
  amp_legend->setLayer (QLatin1String {"legend"});
  legend_title = new QCPTextElement {&plot_, tr ("Amplitude"), QFont {"sans", 9, QFont::Bold}};
  legend_title->setLayer (amp_legend->layer ());
  amp_legend->addElement (0, 0, legend_title);

  plot_.axisRect (1)->axis (QCPAxis::atBottom)->setLabel (tr ("Freq (Hz)"));
  plot_.axisRect (1)->axis (QCPAxis::atBottom)->setRange (0, 6000);
  plot_.axisRect (1)->axis (QCPAxis::atLeft)->setLabel (tr ("Relative Power (dB)"));
  plot_.axisRect (1)->axis (QCPAxis::atLeft)->setRangeLower (0);
  plot_.axisRect (1)->setRangeDragAxes (nullptr, nullptr);
  plot_.axisRect (1)->setRangeZoomAxes (nullptr, nullptr);

  plot_.addGraph (plot_.axisRect (1)->axis (QCPAxis::atBottom)
                  , plot_.axisRect (1)->axis (QCPAxis::atLeft))->setName (tr ("Reference"));
  plot_.graph ()->setPen (QPen {Qt::blue});
  plot_.graph ()->removeFromLegend ();
  plot_.graph ()->addToLegend (amp_legend);

  layout_.addWidget (&plot_);

  auto load_phase_button = button_box_.addButton (tr ("Phase ..."), QDialogButtonBox::ActionRole);
  auto refresh_button = button_box_.addButton (tr ("Refresh"), QDialogButtonBox::ActionRole);
  layout_.addWidget (&button_box_);
  setLayout (&layout_);

  connect (&button_box_, &QDialogButtonBox::rejected, this, &QDialog::reject);
  connect (&button_box_, &QDialogButtonBox::clicked, [=] (QAbstractButton * button) {
      if (button == load_phase_button)
        {
          plot_phase ();
        }
      else if (button == refresh_button)
        {
          plot_current ();
        }
      else if (button == button_box_.button (QDialogButtonBox::Apply))
        {
          if (plot_.graph (0)->dataCount ()) // something loaded
            {
              current_coefficients_ = new_coefficients_;
              Q_EMIT self_->phase_equalization_changed (current_coefficients_);
              plot_current ();
            }
        }
      else if (button == button_box_.button (QDialogButtonBox::RestoreDefaults))
        {
          current_coefficients_ = QVector<double> {0., 0., 0., 0., 0.};
          Q_EMIT self_->phase_equalization_changed (current_coefficients_);
          plot_current ();
        }
      else if (button == button_box_.button (QDialogButtonBox::Discard))
        {
          new_coefficients_ = QVector<double> {0., 0., 0., 0., 0.};

          plot_.graph (0)->data ()->clear ();
          plot_.graph (0)->setVisible (false);
          plot_.graph (0)->removeFromLegend ();

          plot_.graph (1)->data ()->clear ();
          plot_.graph (1)->setVisible (false);
          plot_.graph (1)->removeFromLegend ();

          plot_.replot ();
        }
    });

  plot_current ();
}

struct PowerSpectrumPoint
{
  operator QCPGraphData () const
  {
    return QCPGraphData {freq_, power_};
  }

  float freq_;
  float power_;
};

// read an amplitude point line from a stream (refspec.dat)
std::istream& operator >> (std::istream& is, PowerSpectrumPoint& r)
{
  float y1, y3, y4;             // discard these
  is >> r.freq_ >> y1 >> r.power_ >> y3 >> y4;
  return is;
}

void EqualizationToolsDialog::impl::plot_current ()
{
  auto phase_graph = make_plot_data_loader (&plot_, 2, wrap_pi);
  plot_.graph (2)->data ()->clear ();
  std::generate_n (std::back_inserter (phase_graph), intervals + 1
                   , make_graph_generator (make_polynomial (current_coefficients_), freq_scaling, pi_scaling));

  auto group_delay_graph = make_plot_data_loader (&plot_, 3, adjust_identity);
  plot_.graph (3)->data ()->clear ();
  std::generate_n (std::back_inserter (group_delay_graph), intervals + 1
                   , make_graph_generator (make_group_delay (current_coefficients_), freq_scaling, identity<double>));
  plot_.graph (3)->rescaleValueAxis ();

  QFileInfo refspec_file_info {data_directory_.absoluteFilePath ("refspec.dat")};
  std::ifstream refspec_file (refspec_file_info.absoluteFilePath ().toLatin1 ().constData (), std::ifstream::in);
  unsigned n;
  if (refspec_file >> amp_poly_low_ >> amp_poly_high_ >> n)
    {
      std::istream_iterator<double> isi {refspec_file};
      amp_coefficients_.clear ();
      std::copy_n (isi, n, std::back_inserter (amp_coefficients_));
    }
  else
    {
      // may be old format refspec.dat with no header so rewind
      refspec_file.clear ();
      refspec_file.seekg (0);
    }

  auto reference_spectrum_graph = make_plot_data_loader (&plot_, 4, adjust_identity);
  plot_.graph (4)->data ()->clear ();
  std::copy (std::istream_iterator<PowerSpectrumPoint> {refspec_file},
             std::istream_iterator<PowerSpectrumPoint> {},
             std::back_inserter (reference_spectrum_graph));
  plot_.graph (4)->rescaleValueAxis (true);

  plot_.replot ();
}

struct PhasePoint
{
  operator QCPGraphData () const
  {
    return QCPGraphData {freq_, phase_};
  }

  double freq_;
  double phase_;
};

// read a phase point line from a stream (pcoeff file)
std::istream& operator >> (std::istream& is, PhasePoint& c)
{
  double pp, sigmay;            // discard these
  if (is >> c.freq_ >> pp >> c.phase_ >> sigmay)
    {
      c.freq_ = 1500. + 1000. * c.freq_; // scale frequency to Hz
      c.phase_ /= PI;                    // scale to units of Pi
    }
  return is;
}

void EqualizationToolsDialog::impl::plot_phase ()
{
  auto const& phase_file_name = QFileDialog::getOpenFileName (this
                                                              , "Select Phase Response Coefficients"
                                                              , data_directory_.absolutePath ()
                                                              , "Phase Coefficient Files (*.pcoeff)");
  if (!phase_file_name.size ()) return;

  std::ifstream phase_file (phase_file_name.toLatin1 ().constData (), std::ifstream::in);
  int n;
  float chi;
  float rmsdiff;
  unsigned freq_low;
  unsigned freq_high;
  unsigned terms;
  // read header information
  if (phase_file >> n >> chi >> rmsdiff >> freq_low >> freq_high >> terms)
    {
      std::istream_iterator<double> isi {phase_file};
      new_coefficients_.clear ();
      std::copy_n (isi, terms, std::back_inserter (new_coefficients_));

      if (phase_file)
        {
          plot_.graph (0)->data ()->clear ();
          plot_.graph (1)->data ()->clear ();

          // read the phase data and plot as graph 0
          auto graph = make_plot_data_loader (&plot_, 0, adjust_identity);
          std::copy_n (std::istream_iterator<PhasePoint> {phase_file},
                       intervals + 1, std::back_inserter (graph));

          if (phase_file)
            {
              plot_.graph(0)->setLineStyle(QCPGraph::lsNone);
              plot_.graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 4));
              plot_.graph (0)->setVisible (true);
              plot_.graph (0)->addToLegend ();

              // generate the proposed polynomial plot as graph 1
              auto graph = make_plot_data_loader (&plot_, 1, wrap_pi);
              std::generate_n (std::back_inserter (graph), intervals + 1
                               , make_graph_generator (make_polynomial (new_coefficients_)
                                                       , freq_scaling, pi_scaling));
              plot_.graph (1)->setVisible (true);
              plot_.graph (1)->addToLegend ();
            }

          plot_.replot ();
        }
    }
}

#include "moc_EqualizationToolsDialog.cpp"


code:
#ifndef EQUALIZATION_TOOLS_DIALOG_HPP__
#define EQUALIZATION_TOOLS_DIALOG_HPP__

#include <QObject>

#include "pimpl_h.hpp"

class QWidget;
class QSettings;
class QDir;

class EqualizationToolsDialog
  : public QObject
{
  Q_OBJECT

public:
  explicit EqualizationToolsDialog (QSettings *
                                    , QDir const& data_directory
                                    , QVector<double> const& coefficients
                                    , QWidget * = nullptr);
  Q_SLOT void show ();

  Q_SIGNAL void phase_equalization_changed (QVector<double> const&);

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

#endif


code:
#include "ForeignKeyDelegate.hpp"

#include <QComboBox>

#include "CandidateKeyFilter.hpp"

ForeignKeyDelegate::ForeignKeyDelegate (QAbstractItemModel * referenced_model
                                        , int referenced_key_column
                                        , QObject * parent
                                        , int referenced_key_role)
  : QStyledItemDelegate {parent}
  , candidate_key_filter_ {new CandidateKeyFilter {referenced_model, referenced_key_column, nullptr, referenced_key_role}}
{
}

ForeignKeyDelegate::ForeignKeyDelegate (QAbstractItemModel * referenced_model
                                        , QAbstractItemModel const * referencing_model
                                        , int referenced_key_column
                                        , int referencing_key_column
                                        , QObject * parent
                                        , int referenced_key_role
                                        , int referencing_key_role)
  : QStyledItemDelegate {parent}
  , candidate_key_filter_ {new CandidateKeyFilter {referenced_model, referencing_model, referenced_key_column, referencing_key_column, nullptr, referenced_key_role, referencing_key_role}}
{
}

ForeignKeyDelegate::~ForeignKeyDelegate ()
{
}

QWidget * ForeignKeyDelegate::createEditor (QWidget * parent
                                            , QStyleOptionViewItem const& /* option */
                                            , QModelIndex const& index) const
{
  auto editor = new QComboBox {parent};
  editor->setFrame (false);
  candidate_key_filter_->set_active_key (index);
  editor->setModel (candidate_key_filter_.data ());
  return editor;
}


code:
#ifndef FOREIGN_KEY_DELEGATE_HPP_
#define FOREIGN_KEY_DELEGATE_HPP_

#include <QStyledItemDelegate>
#include <QScopedPointer>

class CandidateKeyFilter;

//
// Class ForeignKeyDelegate
//
//	Item delegate for editing a foreign key item in a one or many
//	to one relationship. A QComboBox is used as an item delegate
//	for the edit role.
//
class ForeignKeyDelegate final
  : public QStyledItemDelegate
{
public:
  // many to many relationship
  explicit ForeignKeyDelegate (QAbstractItemModel * referenced_model
                               , int referenced_key_column
                               , QObject * parent = nullptr
                               , int referenced_key_role = Qt::EditRole);

  // one to many (referenced to referencing) relationship
  explicit ForeignKeyDelegate (QAbstractItemModel * referenced_model
                               , QAbstractItemModel const * referencing_model
                               , int referenced_key_column
                               , int referencing_key_column
                               , QObject * parent = nullptr
                               , int referenced_key_role = Qt::EditRole
                               , int referencing_key_role = Qt::EditRole);
  ~ForeignKeyDelegate ();

  QWidget * createEditor (QWidget * parent, QStyleOptionViewItem const&, QModelIndex const&) const override;

private:
  QScopedPointer<CandidateKeyFilter> candidate_key_filter_;
};

#endif


code:
#include "FrequencyLineEdit.hpp"

#include <limits>

#include <QDoubleValidator>
#include <QString>
#include <QLocale>

#include "moc_FrequencyLineEdit.cpp"

namespace
{
  class MHzValidator
    : public QDoubleValidator
  {
  public:
    MHzValidator (double bottom, double top, QObject * parent = nullptr)
      : QDoubleValidator {bottom, top, 6, parent}
    {
    }

    State validate (QString& input, int& pos) const override
    {
      State result = QDoubleValidator::validate (input, pos);
      if (Acceptable == result)
        {
          bool ok;
          (void)QLocale {}.toDouble (input, &ok);
          if (!ok)
            {
              result = Intermediate;
            }
        }
      return result;
    }
  };
}

FrequencyLineEdit::FrequencyLineEdit (QWidget * parent)
  : QLineEdit (parent)
{
  setValidator (new MHzValidator {0., std::numeric_limits<Radio::Frequency>::max () / 10.e6, this});
}

auto FrequencyLineEdit::frequency () const -> Frequency
{
  return Radio::frequency (text (), 6);
}

void FrequencyLineEdit::frequency (Frequency f)
{
  setText (Radio::frequency_MHz_string (f));
}

FrequencyDeltaLineEdit::FrequencyDeltaLineEdit (QWidget * parent)
  : QLineEdit (parent)
{
  setValidator (new MHzValidator {-std::numeric_limits<FrequencyDelta>::max () / 10.e6,
        std::numeric_limits<FrequencyDelta>::max () / 10.e6, this});
}

auto FrequencyDeltaLineEdit::frequency_delta () const -> FrequencyDelta
{
  return Radio::frequency_delta (text (), 6);
}

void FrequencyDeltaLineEdit::frequency_delta (FrequencyDelta d)
{
  setText (Radio::frequency_MHz_string (d));
}


code:
#ifndef FREQUENCY_LINE_EDIT_HPP_
#define FREQUENCY_LINE_EDIT_HPP_

#include <QLineEdit>

#include "Radio.hpp"

class QWidget;

//
// MHz frequency line edits with validation
//
class FrequencyLineEdit final
  : public QLineEdit
{
  Q_OBJECT;
  Q_PROPERTY (Frequency frequency READ frequency WRITE frequency USER true);

public:
  using Frequency = Radio::Frequency;

  explicit FrequencyLineEdit (QWidget * parent = nullptr);

  // Property frequency implementation
  Frequency frequency () const;
  void frequency (Frequency);
};

class FrequencyDeltaLineEdit final
  : public QLineEdit
{
  Q_OBJECT;
  Q_PROPERTY (FrequencyDelta frequency_delta READ frequency_delta WRITE frequency_delta USER true);

public:
  using FrequencyDelta = Radio::FrequencyDelta;

  explicit FrequencyDeltaLineEdit (QWidget * parent = nullptr);

  // Property frequency_delta implementation
  FrequencyDelta frequency_delta () const;
  void frequency_delta (FrequencyDelta);
};

#endif