code:
#ifndef TRANSCEIVER_HPP__
#define TRANSCEIVER_HPP__

#include <QObject>

#include "qt_helpers.hpp"
#include "Radio.hpp"

class QString;

//
// Abstract Transceiver Interface
//
//  This is  the minimal  generic interface  to a  rig as  required by
//  wsjtx.
//
// Responsibilities
//
//  Provides a  Qt slot  to set  the frequency, mode  and PTT  of some
//  transceiver. This is a Qt slot so  that it may be invoked across a
//  thread boundary.
//
//  Provides a synchronisation Qt slot  which should be implemented in
//  sub-classes in such a way that  normal operation of the rig is not
//  disturbed.  This  is  intended  to   be  use  to  poll  rig  state
//  periodically and changing  VFO to read the other  VFO frequency or
//  mode for  example should  not be  done since  the operator  may be
//  tuning the VFO at the time and would be surprised by an unprompted
//  VFO change.
//
//  Provides a control interface using Qt  slots to start and stop the
//  rig control and PTT connections.
//
//  These  are Qt  slots rather  than the  constructor and  destructor
//  because   it   is   expected   that   the   concrete   Transceiver
//  implementations will run in a  separate thread from where they are
//  constructed.
//
//  Qt signals are defined to notify clients of asynchronous rig state
//  changes and failures.  These can and are expected  to cross thread
//  boundaries.
//
//  A  signal   finished()  is   defined  that   concrete  Transceiver
//  implementations must emit when they are ripe for destruction. This
//  is  intended to  be  used  by clients  that  move the  Transceiver
//  instance to  a thread  and need  to use  QObject::deleteLater() to
//  safely dispose of the Transceiver instance. Implementations should
//  expect Qt  slot calls  after emitting  finished, it  is up  to the
//  implementation whether these slot invocations are ignored.
//
class Transceiver
  : public QObject
{
  Q_OBJECT
  Q_ENUMS (MODE)

public:
  using Frequency = Radio::Frequency;

protected:
  Transceiver (QObject * parent) : QObject {parent} {}

public:
  virtual ~Transceiver () {}

  enum MODE {UNK, CW, CW_R, USB, LSB, FSK, FSK_R, DIG_U, DIG_L, AM, FM, DIG_FM};
  Q_ENUM (MODE)

  //
  // Aggregation of all of the rig and PTT state accessible via this
  // interface.
  //
  class TransceiverState
  {
  public:
    TransceiverState ()
      : online_ {false}
      , rx_frequency_ {0}
      , tx_frequency_ {0}
      , mode_ {UNK}
      , split_ {Split::unknown}
      , ptt_ {false}
    {
    }

    bool online () const {return online_;}
    Frequency frequency () const {return rx_frequency_;}
    Frequency tx_frequency () const {return tx_frequency_;}
    bool split () const {return Split::on == split_;}
    MODE mode () const {return mode_;}
    bool ptt () const {return ptt_;}

    void online (bool state) {online_ = state;}
    void frequency (Frequency f) {rx_frequency_ = f;}
    void tx_frequency (Frequency f) {tx_frequency_ = f;}
    void split (bool state) {split_ = state ? Split::on : Split::off;}
    void mode (MODE m) {mode_ = m;}
    void ptt (bool state) {ptt_ = state;}

  private:
    bool online_;
    Frequency rx_frequency_;
    Frequency tx_frequency_;    // 0 means use Rx
    MODE mode_;
    enum class Split {unknown, off, on} split_;
    bool ptt_;
    // Don't forget to update the debug print and != operator if you
    // add more members here

    friend QDebug operator << (QDebug, TransceiverState const&);
    friend bool operator != (TransceiverState const&, TransceiverState const&);
  };

  //
  // The following  slots and signals are  expected to all run  in the
  // same thread which  is not necessarily the main GUI  thread. It is
  // up  to  the client  of  the  Transceiver  class to  organise  the
  // allocation to a thread and the lifetime of the object instances.
  //

  // Apply  state changes  to the  rig. The  sequence_number parameter
  // will  be included  in  any status  updates  generated after  this
  // transaction  is processed.  The sequence  number may  be used  to
  // ignore any status  updates until the results  of this transaction
  // have been processed thus avoiding any unwanted "ping-pong" due to
  // signals crossing in transit.
  Q_SLOT virtual void set (Transceiver::TransceiverState const&,
                           unsigned sequence_number) noexcept = 0;

  // Connect and disconnect.
  Q_SLOT virtual void start (unsigned sequence_number) noexcept = 0;
  Q_SLOT virtual void stop () noexcept = 0;

  //
  // asynchronous status updates
  //

  // 0 - 1Hz
  // 1 - 10Hz rounded
  // -1 - 10Hz truncated
  // 2 - 100Hz rounded
  // -2 - 100Hz truncated
  Q_SIGNAL void resolution (int);

  // rig state changed
  Q_SIGNAL void update (Transceiver::TransceiverState const&,
                        unsigned sequence_number) const;

  // something went wrong - not recoverable, start new instance
  Q_SIGNAL void failure (QString const& reason) const;

  // Ready to be destroyed.
  Q_SIGNAL void finished () const;
};

Q_DECLARE_METATYPE (Transceiver::TransceiverState);
#if QT_VERSION < 0x050500
Q_DECLARE_METATYPE (Transceiver::MODE);
#endif

#if !defined (QT_NO_DEBUG_STREAM)
ENUM_QDEBUG_OPS_DECL (Transceiver, MODE);

QDebug operator << (QDebug, Transceiver::TransceiverState const&);
#endif

ENUM_QDATASTREAM_OPS_DECL (Transceiver, MODE);
ENUM_CONVERSION_OPS_DECL (Transceiver, MODE);

bool operator != (Transceiver::TransceiverState const&, Transceiver::TransceiverState const&);
bool operator == (Transceiver::TransceiverState const&, Transceiver::TransceiverState const&);

#endif


code:
#include "TransceiverBase.hpp"

#include <exception>

#include <QString>
#include <QTimer>
#include <QThread>
#include <QDebug>

#include "moc_TransceiverBase.cpp"

namespace
{
  auto const unexpected = TransceiverBase::tr ("Unexpected rig error");
}

void TransceiverBase::start (unsigned sequence_number) noexcept
{
  QString message;
  try
    {
      may_update u {this, true};
      shutdown ();
      startup ();
      last_sequence_number_ = sequence_number;
    }
  catch (std::exception const& e)
    {
      message = e.what ();
    }
  catch (...)
    {
      message = unexpected;
    }
  if (!message.isEmpty ())
    {
      offline (message);
    }
}

void TransceiverBase::set (TransceiverState const& s,
                           unsigned sequence_number) noexcept
{
  TRACE_CAT ("TransceiverBase", "#:" << sequence_number << s);

  QString message;
  try
    {
      may_update u {this, true};
      bool was_online {requested_.online ()};
      if (!s.online () && was_online)
        {
          shutdown ();
        }
      else if (s.online () && !was_online)
        {
          shutdown ();
          startup ();
        }
      if (requested_.online ())
        {
          bool ptt_on {false};
          bool ptt_off {false};
          if (s.ptt () != requested_.ptt ())
            {
              ptt_on = s.ptt ();
              ptt_off = !s.ptt ();
            }
          if (ptt_off)
            {
              do_ptt (false);
              do_post_ptt (false);
              QThread::msleep (100); // some rigs cannot process CAT
                                     // commands while switching from
                                     // Tx to Rx
            }
          if (s.frequency ()    // ignore bogus zero frequencies
              && ((s.frequency () != requested_.frequency () // and QSY
                   || (s.mode () != UNK && s.mode () != requested_.mode ())) // or mode change
                  || ptt_off))       // or just returned to rx
            {
              do_frequency (s.frequency (), s.mode (), ptt_off);
              do_post_frequency (s.frequency (), s.mode ());

              // record what actually changed
              requested_.frequency (actual_.frequency ());
              requested_.mode (actual_.mode ());
            }
          if (!s.tx_frequency ()
              || (s.tx_frequency () > 10000 // ignore bogus startup values
                  && s.tx_frequency () < std::numeric_limits<Frequency>::max () - 10000))
            {
              if ((s.tx_frequency () != requested_.tx_frequency () // and QSY
                   || (s.mode () != UNK && s.mode () != requested_.mode ())) // or mode change
                  // || s.split () != requested_.split ())) // or split change
                  || (s.tx_frequency () && ptt_on)) // or about to tx split
                {
                  do_tx_frequency (s.tx_frequency (), s.mode (), ptt_on);
                  do_post_tx_frequency (s.tx_frequency (), s.mode ());

                  // record what actually changed
                  requested_.tx_frequency (actual_.tx_frequency ());
                  requested_.split (actual_.split ());
                }
            }
          if (ptt_on)
            {
              do_ptt (true);
              do_post_ptt (true);
              QThread::msleep (100); // some rigs cannot process CAT
                                     // commands while switching from
                                     // Rx to Tx
            }

          // record what actually changed
          requested_.ptt (actual_.ptt ());
        }
      last_sequence_number_ = sequence_number;
    }
  catch (std::exception const& e)
    {
      message = e.what ();
    }
  catch (...)
    {
      message = unexpected;
    }
  if (!message.isEmpty ())
    {
      offline (message);
    }
}

void TransceiverBase::startup ()
{
  Q_EMIT resolution (do_start ());
  do_post_start ();
  actual_.online (true);
  requested_.online (true);
}

void TransceiverBase::shutdown ()
{
  may_update u {this};
  if (requested_.online ())
    {
      try
        {
          // try and ensure PTT isn't left set
          do_ptt (false);
          do_post_ptt (false);
          if (requested_.split ())
            {
              // try and reset split mode
              do_tx_frequency (0, UNK, true);
              do_post_tx_frequency (0, UNK);
            }
        }
      catch (...)
        {
          // don't care about exceptions
        }
    }
  do_stop ();
  do_post_stop ();
  actual_.online (false);
  requested_.online (false);
}

void TransceiverBase::stop () noexcept
{
  QString message;
  try
    {
      shutdown ();
    }
  catch (std::exception const& e)
    {
      message = e.what ();
    }
  catch (...)
    {
      message = unexpected;
    }
  if (!message.isEmpty ())
    {
      offline (message);
    }
  else
    {
      Q_EMIT finished ();
    }
}

void TransceiverBase::update_rx_frequency (Frequency rx)
{
  actual_.frequency (rx);
  requested_.frequency (rx);    // track rig changes
}

void TransceiverBase::update_other_frequency (Frequency tx)
{
  actual_.tx_frequency (tx);
}

void TransceiverBase::update_split (bool state)
{
  actual_.split (state);
}

void TransceiverBase::update_mode (MODE m)
{
  actual_.mode (m);
  requested_.mode (m);    // track rig changes
}

void TransceiverBase::update_PTT (bool state)
{
  actual_.ptt (state);
}

void TransceiverBase::update_complete (bool force_signal)
{
  if ((do_pre_update () && actual_ != last_) || force_signal)
    {
      Q_EMIT update (actual_, last_sequence_number_);
      last_ = actual_;
    }
}

void TransceiverBase::offline (QString const& reason)
{
  Q_EMIT failure (reason);
  try
    {
      shutdown ();
    }
  catch (...)
    {
      // don't care
    }
}

license: mit


code:
#ifndef TRANSCEIVER_BASE_HPP__
#define TRANSCEIVER_BASE_HPP__

#include <stdexcept>

#include <QString>

#include "Transceiver.hpp"

//
// Base Transceiver Implementation
//
//  Behaviour common to all Transceiver implementations.
//
// Collaborations
//
//  Implements the Transceiver abstract  interface as template methods
//  and provides  a new abstract interface  with similar functionality
//  (do_XXXXX operations). Provides and  calls abstract interface that
//  gets  called post  the above  operations (do_post_XXXXX)  to allow
//  caching implementation etc.
//
//  A  key factor  is  to  catch all  exceptions  thrown by  sub-class
//  implementations where  the template method  is a Qt slot  which is
//  therefore  likely  to  be  called   by  Qt  which  doesn't  handle
//  exceptions. Any exceptions are converted to Transceiver::failure()
//  signals.
//
//  Sub-classes update the stored state via a protected interface.
//
// Responsibilities:
//
//  Wrap incoming  Transceiver messages catching all  exceptions in Qt
//  slot driven  messages and converting  them to Qt signals.  This is
//  done because exceptions  make concrete Transceiver implementations
//  simpler  to   write,  but  exceptions  cannot   cross  signal/slot
//  boundaries  (especially across  threads).  This  also removes  any
//  requirement for the client code to handle exceptions.
//
//  Maintain the  state of the  concrete Transceiver instance  that is
//  passed back via  the Transceiver::update(TransceiverState) signal,
//  it   is  still   the   responsibility   of  concrete   Transceiver
//  implementations to emit  the state_change signal when  they have a
//  status update.
//
//  Maintain    a   go/no-go    status   for    concrete   Transceiver
//  implementations  ensuring only  a valid  sequence of  messages are
//  passed. A concrete Transceiver instance  must be started before it
//  can receive  messages, any exception thrown  takes the Transceiver
//  offline.
//
//  Implements methods  that concrete Transceiver  implementations use
//  to update the Transceiver state.  These do not signal state change
//  to  clients  as  this  is   the  responsibility  of  the  concrete
//  Transceiver implementation, thus allowing multiple state component
//  updates to be signalled together if required.
//
class TransceiverBase
  : public Transceiver
{
  Q_OBJECT;

protected:
  TransceiverBase (QObject * parent)
    : Transceiver {parent}
    , last_sequence_number_ {0}
  {}

public:
  //
  // Implement the Transceiver abstract interface.
  //
  void start (unsigned sequence_number) noexcept override final;
  void set (TransceiverState const&,
            unsigned sequence_number) noexcept override final;
  void stop () noexcept override final;

  //
  // Query operations
  //
  TransceiverState const& state () const {return actual_;}

protected:
  //
  // Error exception which is thrown to signal unexpected errors.
  //
  struct error
    : public std::runtime_error
  {
    explicit error (char const * const msg) : std::runtime_error (msg) {}
    explicit error (QString const& msg) : std::runtime_error (msg.toStdString ()) {}
  };

  // Template methods that sub classes implement to do what they need to do.
  //
  // These methods may throw exceptions to signal errors.
  virtual int do_start () = 0;  // returns resolution, See Transceiver::resolution
  virtual void do_post_start () {}

  virtual void do_stop () = 0;
  virtual void do_post_stop () {}

  virtual void do_frequency (Frequency, MODE, bool no_ignore) = 0;
  virtual void do_post_frequency (Frequency, MODE) {}

  virtual void do_tx_frequency (Frequency, MODE, bool no_ignore) = 0;
  virtual void do_post_tx_frequency (Frequency, MODE) {}

  virtual void do_mode (MODE) = 0;
  virtual void do_post_mode (MODE) {}

  virtual void do_ptt (bool = true) = 0;
  virtual void do_post_ptt (bool = true) {}

  virtual void do_sync (bool force_signal = false, bool no_poll = false) = 0;

  virtual bool do_pre_update () {return true;}

  // sub classes report rig state changes with these methods
  void update_rx_frequency (Frequency);
  void update_other_frequency (Frequency = 0);
  void update_split (bool);
  void update_mode (MODE);
  void update_PTT (bool = true);

  // Calling this eventually triggers the Transceiver::update(State) signal.
  void update_complete (bool force_signal = false);

  // sub class may asynchronously take the rig offline by calling this
  void offline (QString const& reason);

private:
  void startup ();
  void shutdown ();
  bool maybe_low_resolution (Frequency low_res, Frequency high_res);

  // use this convenience class to notify in update methods
  class may_update
  {
  public:
    explicit may_update (TransceiverBase * self, bool force_signal = false)
      : self_ {self}
      , force_signal_ {force_signal}
    {}
    ~may_update () {self_->update_complete (force_signal_);}
  private:
    TransceiverBase * self_;
    bool force_signal_;
  };

  TransceiverState requested_;
  TransceiverState actual_;
  TransceiverState last_;
  unsigned last_sequence_number_;    // from set state operation
};

// some trace macros
#if WSJT_TRACE_CAT
#define TRACE_CAT(FAC, MSG) qDebug () << QString {"%1::%2:"}.arg ((FAC)).arg (__func__) << MSG
#else
#define TRACE_CAT(FAC, MSG)
#endif

#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS
#define TRACE_CAT_POLL(FAC, MSG) qDebug () << QString {"%1::%2:"}.arg ((FAC)).arg (__func__) << MSG
#else
#define TRACE_CAT_POLL(FAC, MSG)
#endif

#endif


code:
#include "TransceiverFactory.hpp"

#include <QMetaType>

#include "HamlibTransceiver.hpp"
#include "DXLabSuiteCommanderTransceiver.hpp"
#include "HRDTransceiver.hpp"
#include "EmulateSplitTransceiver.hpp"

#if defined (WIN32)
#include "OmniRigTransceiver.hpp"
#endif

#include "moc_TransceiverFactory.cpp"

// we use the hamlib "Hamlib Dummy" transceiver for non-CAT radios,
// this allows us to still use the hamlib PTT control features for a
// unified PTT control solution

char const * const TransceiverFactory::basic_transceiver_name_ = "None";

namespace
{
  enum				// supported non-hamlib radio interfaces
    {
      NonHamlibBaseId = 9899
      , CommanderId
      , HRDId
      , OmniRigOneId
      , OmniRigTwoId
    };
}

TransceiverFactory::TransceiverFactory ()
{
  HamlibTransceiver::register_transceivers (&transceivers_);
  DXLabSuiteCommanderTransceiver::register_transceivers (&transceivers_, CommanderId);
  HRDTransceiver::register_transceivers (&transceivers_, HRDId);

#if defined (WIN32)
  // OmniRig is ActiveX/COM server so only on Windows
  OmniRigTransceiver::register_transceivers (&transceivers_, OmniRigOneId, OmniRigTwoId);
#endif
}

TransceiverFactory::~TransceiverFactory ()
{
  HamlibTransceiver::unregister_transceivers ();
}

auto TransceiverFactory::supported_transceivers () const -> Transceivers const&
{
  return transceivers_;
}

auto TransceiverFactory::CAT_port_type (QString const& name) const -> Capabilities::PortType
{
  return supported_transceivers ()[name].port_type_;
}

bool TransceiverFactory::has_CAT_PTT (QString const& name) const
{
  return
    supported_transceivers ()[name].has_CAT_PTT_
    || supported_transceivers ()[name].model_number_ > NonHamlibBaseId;
}

bool TransceiverFactory::has_CAT_PTT_mic_data (QString const& name) const
{
  return supported_transceivers ()[name].has_CAT_PTT_mic_data_;
}

bool TransceiverFactory::has_CAT_indirect_serial_PTT (QString const& name) const
{
  return supported_transceivers ()[name].has_CAT_indirect_serial_PTT_;
}

bool TransceiverFactory::has_asynchronous_CAT (QString const& name) const
{
  return supported_transceivers ()[name].asynchronous_;
}

std::unique_ptr<Transceiver> TransceiverFactory::create (ParameterPack const& params, QThread * target_thread)
{
  std::unique_ptr<Transceiver> result;
  switch (supported_transceivers ()[params.rig_name].model_number_)
    {
    case CommanderId:
      {
        std::unique_ptr<TransceiverBase> basic_transceiver;
        if (PTT_method_CAT != params.ptt_type)
          {
            // we start with a dummy HamlibTransceiver object instance that can support direct PTT
            basic_transceiver.reset (new HamlibTransceiver {params.ptt_type, params.ptt_port});
            if (target_thread)
              {
                basic_transceiver.get ()->moveToThread (target_thread);
              }
          }

        // wrap the basic Transceiver object instance with a decorator object that talks to DX Lab Suite Commander
        result.reset (new DXLabSuiteCommanderTransceiver {std::move (basic_transceiver), params.network_port, PTT_method_CAT == params.ptt_type, params.poll_interval});
        if (target_thread)
          {
            result->moveToThread (target_thread);
          }
      }
      break;

    case HRDId:
      {
        std::unique_ptr<TransceiverBase> basic_transceiver;
        if (PTT_method_CAT != params.ptt_type)
          {
            // we start with a dummy HamlibTransceiver object instance that can support direct PTT
            basic_transceiver.reset (new HamlibTransceiver {params.ptt_type, params.ptt_port});
            if (target_thread)
              {
                basic_transceiver.get ()->moveToThread (target_thread);
              }
          }

        // wrap the basic Transceiver object instance with a decorator object that talks to ham Radio Deluxe
        result.reset (new HRDTransceiver {std::move (basic_transceiver), params.network_port, PTT_method_CAT == params.ptt_type, params.audio_source, params.poll_interval});
        if (target_thread)
          {
            result->moveToThread (target_thread);
          }
      }
      break;

#if defined (WIN32)
    case OmniRigOneId:
      {
        std::unique_ptr<TransceiverBase> basic_transceiver;
        if (PTT_method_CAT != params.ptt_type && "CAT" != params.ptt_port)
          {
            // we start with a dummy HamlibTransceiver object instance that can support direct PTT
            basic_transceiver.reset (new HamlibTransceiver {params.ptt_type, params.ptt_port});
            if (target_thread)
              {
                basic_transceiver.get ()->moveToThread (target_thread);
              }
          }

        // wrap the basic Transceiver object instance with a decorator object that talks to OmniRig rig one
        result.reset (new OmniRigTransceiver {std::move (basic_transceiver), OmniRigTransceiver::One, params.ptt_type, params.ptt_port});
        if (target_thread)
          {
            result->moveToThread (target_thread);
          }
      }
      break;

    case OmniRigTwoId:
      {
        std::unique_ptr<TransceiverBase> basic_transceiver;
        if (PTT_method_CAT != params.ptt_type && "CAT" != params.ptt_port)
          {
            // we start with a dummy HamlibTransceiver object instance that can support direct PTT
            basic_transceiver.reset (new HamlibTransceiver {params.ptt_type, params.ptt_port});
            if (target_thread)
              {
                basic_transceiver.get ()->moveToThread (target_thread);
              }
          }

        // wrap the basic Transceiver object instance with a decorator object that talks to OmniRig rig two
        result.reset (new OmniRigTransceiver {std::move (basic_transceiver), OmniRigTransceiver::Two, params.ptt_type, params.ptt_port});
        if (target_thread)
          {
            result->moveToThread (target_thread);
          }
      }
      break;
#endif

    default:
      result.reset (new HamlibTransceiver {supported_transceivers ()[params.rig_name].model_number_, params});
      if (target_thread)
        {
          result->moveToThread (target_thread);
        }
      break;
    }

  if (split_mode_emulate == params.split_mode)
    {
      // wrap the Transceiver object instance with a decorator that emulates split mode
      result.reset (new EmulateSplitTransceiver {std::move (result)});
      if (target_thread)
        {
          result->moveToThread (target_thread);
        }
    }

  return result;
}

#if !defined (QT_NO_DEBUG_STREAM)
ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, DataBits);
ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, StopBits);
ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, Handshake);
ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, PTTMethod);
ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, TXAudioSource);
ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, SplitMode);
#endif

ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, DataBits);
ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, StopBits);
ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, Handshake);
ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, PTTMethod);
ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, TXAudioSource);
ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, SplitMode);

ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, DataBits);
ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, StopBits);
ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, Handshake);
ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, PTTMethod);
ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, TXAudioSource);
ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, SplitMode);


code:
#ifndef TRANSCEIVER_FACTORY_HPP__
#define TRANSCEIVER_FACTORY_HPP__

#include <memory>

#include <QObject>
#include <QMap>

#include "Transceiver.hpp"

#include "qt_helpers.hpp"

class QString;
class QThread;
class QDir;

//
// Transceiver Factory
//
class TransceiverFactory
  : public QObject
{
  Q_OBJECT
  Q_ENUMS (DataBits StopBits Handshake PTTMethod TXAudioSource SplitMode)

public:
  //
  // Capabilities of a Transceiver that can be determined without
  // actually instantiating one, these are for use in Configuration
  // GUI behaviour determination
  //
  struct Capabilities
  {
    enum PortType {none, serial, network, usb};

    explicit Capabilities (int model_number = 0
                           , PortType port_type = none
                           , bool has_CAT_PTT = false
                           , bool has_CAT_PTT_mic_data = false
                           , bool has_CAT_indirect_serial_PTT = false
                           , bool asynchronous = false)
      : model_number_ {model_number}
      , port_type_ {port_type}
      , has_CAT_PTT_ {has_CAT_PTT}
      , has_CAT_PTT_mic_data_ {has_CAT_PTT_mic_data}
      , has_CAT_indirect_serial_PTT_ {has_CAT_indirect_serial_PTT}
      , asynchronous_ {asynchronous}
    {
    }

    int model_number_;
    PortType port_type_;
    bool has_CAT_PTT_;
    bool has_CAT_PTT_mic_data_;
    bool has_CAT_indirect_serial_PTT_; // OmniRig controls RTS/DTR via COM interface
    bool asynchronous_;
  };

  //
  // Dictionary of Transceiver types Capabilities
  //
  typedef QMap<QString, Capabilities> Transceivers;

  //
  // various Transceiver parameters
  //
  enum DataBits {seven_data_bits = 7, eight_data_bits, default_data_bits};
  Q_ENUM (DataBits)
  enum StopBits {one_stop_bit = 1, two_stop_bits, default_stop_bits};
  Q_ENUM (StopBits)
  enum Handshake {handshake_default, handshake_none, handshake_XonXoff, handshake_hardware};
  Q_ENUM (Handshake)
  enum PTTMethod {PTT_method_VOX, PTT_method_CAT, PTT_method_DTR, PTT_method_RTS};
  Q_ENUM (PTTMethod)
  enum TXAudioSource {TX_audio_source_front, TX_audio_source_rear};
  Q_ENUM (TXAudioSource)
  enum SplitMode {split_mode_none, split_mode_rig, split_mode_emulate};
  Q_ENUM (SplitMode)

  TransceiverFactory ();
  ~TransceiverFactory ();

  static char const * const basic_transceiver_name_; // dummy transceiver is basic model

  //
  // fetch all supported rigs as a list of name and model id
  //
  Transceivers const& supported_transceivers () const;

  // supported model queries
  Capabilities::PortType CAT_port_type (QString const& name) const; // how to talk to CAT
  bool has_CAT_PTT (QString const& name) const;	// can be keyed via CAT
  bool has_CAT_PTT_mic_data (QString const& name) const; // Tx audio port is switchable via CAT
  bool has_CAT_indirect_serial_PTT (QString const& name) const; // Can PTT via CAT port use DTR or RTS (OmniRig for example)
  bool has_asynchronous_CAT (QString const& name) const; // CAT asynchronous rather than polled

  struct ParameterPack
  {
    QString rig_name;           // from supported_transceivers () key
    QString serial_port;        // serial port device name or empty
    QString network_port;       // hostname:port or empty
    QString usb_port;           // [vid[:pid[:vendor[:product]]]]
    int baud;
    DataBits data_bits;
    StopBits stop_bits;
    Handshake handshake;
    bool force_dtr;
    bool dtr_high;              // to power interface
    bool force_rts;
    bool rts_high;              // to power interface
    PTTMethod ptt_type;         // "CAT" | "DTR" | "RTS" | "VOX"
    TXAudioSource audio_source; // some rigs allow audio routing
                                // to Mic/Data connector
    SplitMode split_mode;       // how to support split TX mode
    QString ptt_port;           // serial port device name or special
                                // value "CAT"
    int poll_interval;          // in seconds for interfaces that
                                // require polling for state changes

    bool operator == (ParameterPack const& rhs) const
    {
      return rhs.rig_name == rig_name
        && rhs.serial_port == serial_port
        && rhs.network_port == network_port
        && rhs.usb_port == usb_port
        && rhs.baud == baud
        && rhs.data_bits == data_bits
        && rhs.stop_bits == stop_bits
        && rhs.handshake == handshake
        && rhs.force_dtr == force_dtr
        && rhs.dtr_high == dtr_high
        && rhs.force_rts == force_rts
        && rhs.rts_high == rts_high
        && rhs.ptt_type == ptt_type
        && rhs.audio_source == audio_source
        && rhs.split_mode == split_mode
        && rhs.ptt_port == ptt_port
        && rhs.poll_interval == poll_interval
        ;
    }
  };

  // make a new Transceiver instance
  //
  // cat_port, cat_baud, cat_data_bits, cat_stop_bits, cat_handshake,
  // cat_dtr_control, cat_rts_control are only relevant to interfaces
  // that are served by Hamlib
  //
  // PTT port and to some extent ptt_type are independent of interface
  // type
  //
  std::unique_ptr<Transceiver> create (ParameterPack const&, QThread * target_thread = nullptr);

private:
  Transceivers transceivers_;
};

inline
bool operator != (TransceiverFactory::ParameterPack const& lhs, TransceiverFactory::ParameterPack const& rhs)
{
  return !(lhs == rhs);
}

//
// boilerplate routines to make enum types useable and debuggable in
// Qt
//
#if QT_VERSION < 0x050500
Q_DECLARE_METATYPE (TransceiverFactory::DataBits);
Q_DECLARE_METATYPE (TransceiverFactory::StopBits);
Q_DECLARE_METATYPE (TransceiverFactory::Handshake);
Q_DECLARE_METATYPE (TransceiverFactory::PTTMethod);
Q_DECLARE_METATYPE (TransceiverFactory::TXAudioSource);
Q_DECLARE_METATYPE (TransceiverFactory::SplitMode);
#endif

#if !defined (QT_NO_DEBUG_STREAM)
ENUM_QDEBUG_OPS_DECL (TransceiverFactory, DataBits);
ENUM_QDEBUG_OPS_DECL (TransceiverFactory, StopBits);
ENUM_QDEBUG_OPS_DECL (TransceiverFactory, Handshake);
ENUM_QDEBUG_OPS_DECL (TransceiverFactory, PTTMethod);
ENUM_QDEBUG_OPS_DECL (TransceiverFactory, TXAudioSource);
ENUM_QDEBUG_OPS_DECL (TransceiverFactory, SplitMode);
#endif

ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, DataBits);
ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, StopBits);
ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, Handshake);
ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, PTTMethod);
ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, TXAudioSource);
ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, SplitMode);

ENUM_CONVERSION_OPS_DECL (TransceiverFactory, DataBits);
ENUM_CONVERSION_OPS_DECL (TransceiverFactory, StopBits);
ENUM_CONVERSION_OPS_DECL (TransceiverFactory, Handshake);
ENUM_CONVERSION_OPS_DECL (TransceiverFactory, PTTMethod);
ENUM_CONVERSION_OPS_DECL (TransceiverFactory, TXAudioSource);
ENUM_CONVERSION_OPS_DECL (TransceiverFactory, SplitMode);

#endif


code:
# Version number components
set (WSJTX_VERSION_MAJOR 0)
set (WSJTX_VERSION_MINOR 7)
set (WSJTX_VERSION_PATCH 2)
set (WSJTX_RC 0)		 # release candidate number, comment out or zero for development versions
set (WSJTX_VERSION_IS_RELEASE 0) # set to 1 for final release build


code:
#include "WFPalette.hpp"

#include <stdexcept>
#include <memory>
#include <algorithm>

#include <QMetaType>
#include <QObject>
#include <QFile>
#include <QTextStream>
#include <QString>
#include <QDialog>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QColorDialog>
#include <QColor>
#include <QBrush>
#include <QPoint>
#include <QMenu>
#include <QAction>
#include <QPushButton>
#include <QStandardPaths>
#include <QFileDialog>
#include <QFile>
#include <QTextStream>
#include <QDebug>

#include "qt_helpers.hpp"

#include "ui_wf_palette_design_dialog.h"

namespace
{
  int constexpr points {256};

  using Colours = WFPalette::Colours;

  // ensure that palette colours are useable for interpolation
  Colours make_valid (Colours colours)
  {
    if (colours.size () < 2)
      {
        // allow single element by starting at black
        colours.prepend (QColor {0, 0, 0});
      }

    if (1 == colours.size ())
      {
        // allow empty list by using black to white
        colours.append (QColor {255,255,255});
      }

    if (colours.size () > points)
      {
        throw_qstring (QObject::tr ("Too many colours in palette."));
      }

    return colours;
  }

  // load palette colours from a file
  Colours load_palette (QString const& file_name)
  {
    Colours colours;
    QFile file {file_name};
    if (file.open (QIODevice::ReadOnly))
      {
        unsigned count {0};
        QTextStream in (&file);
        int line_counter {0};
        while (!in.atEnd ())
          {
            auto line = in.readLine();
            ++line_counter;

            if (++count >= points)
              {
                throw_qstring (QObject::tr ("Error reading waterfall palette file \"%1:%2\" too many colors.")
                               .arg (file.fileName ()).arg (line_counter));
              }
            auto items = line.split (';');
            if (items.size () != 3)
              {
                throw_qstring (QObject::tr ("Error reading waterfall palette file \"%1:%2\" invalid triplet.")
                               .arg (file.fileName ()).arg (line_counter));
              }
            bool r_ok, g_ok, b_ok;
            auto r = items[0].toInt (&r_ok);
            auto g = items[1].toInt (&g_ok);
            auto b = items[2].toInt (&b_ok);
            if (!r_ok || !g_ok || !b_ok
                || r < 0 || r > 255
                || g < 0 || g > 255
                || b < 0 || b > 255)
              {
                throw_qstring (QObject::tr ("Error reading waterfall palette file \"%1:%2\" invalid color.")
                               .arg (file.fileName ()).arg (line_counter));
              }
            colours.append (QColor {r, g, b});
          }
      }
    else
      {
        throw_qstring (QObject::tr ("Error opening waterfall palette file \"%1\": %2.").arg (file.fileName ()).arg (file.errorString ()));
      }

    return colours;
  }

  // GUI to design and manage waterfall palettes
  class Designer
    : public QDialog
  {
    Q_OBJECT;

  public:
    explicit Designer (Colours const& current, QWidget * parent = nullptr)
      : QDialog {parent}
      , colours_ {current}
    {
      ui_.setupUi (this);

      // context menu actions
      auto import_button = ui_.button_box->addButton ("&Import...", QDialogButtonBox::ActionRole);
      connect (import_button, &QPushButton::clicked, this, &Designer::import_palette);

      auto export_button = ui_.button_box->addButton ("&Export...", QDialogButtonBox::ActionRole);
      connect (export_button, &QPushButton::clicked, this, &Designer::export_palette);

      // hookup the context menu handler
      connect (ui_.colour_table_widget, &QWidget::customContextMenuRequested, this, &Designer::context_menu);

      load_table ();
    }

    void load_table ()
    {
      // load the table items
      ui_.colour_table_widget->clear ();
      ui_.colour_table_widget->setRowCount (colours_.size ());
      for (int i {0}; i < colours_.size (); ++i)
        {
          insert_item (i);
        }
    }

    Colours colours () const
    {
      return colours_;
    }

    // invoke the colour editor
    Q_SLOT void on_colour_table_widget_itemDoubleClicked (QTableWidgetItem * item)
    {
      auto new_colour = QColorDialog::getColor (item->background ().color (), this);
      if (new_colour.isValid ())
        {
          item->setBackground (QBrush {new_colour});
          colours_[item->row ()] = new_colour;
        }
    }

  private:
    void insert_item (int row)
    {
      std::unique_ptr<QTableWidgetItem> item {new QTableWidgetItem {""}};
      item->setBackground (QBrush {colours_[row]});
      item->setFlags (Qt::ItemIsEnabled);
      ui_.colour_table_widget->setItem (row, 0, item.release ());
    }

    void insert_new_item (int row, QColor const& default_colour)
    {
      // use the prior row colour as default if available
      auto new_colour = QColorDialog::getColor (row > 0 ? colours_[row - 1] : default_colour, this);
      if (new_colour.isValid ())
        {
          ui_.colour_table_widget->insertRow (row);
          colours_.insert (row, new_colour);
          insert_item (row);
        }
    }

    void context_menu (QPoint const& p)
    {
      context_menu_.clear ();
      if (ui_.colour_table_widget->itemAt (p))
        {
          auto delete_action = context_menu_.addAction (tr ("&Delete"));
          connect (delete_action, &QAction::triggered, [this] ()
                   {
                     auto row = ui_.colour_table_widget->currentRow ();
                     ui_.colour_table_widget->removeRow (row);
                     colours_.removeAt (row);
                   });
        }

      auto insert_action = context_menu_.addAction (tr ("&Insert ..."));
      connect (insert_action, &QAction::triggered, [this] ()
               {
                 auto item = ui_.colour_table_widget->itemAt (menu_pos_);
                 int row = item ? item->row () : colours_.size ();
                 insert_new_item (row, QColor {0, 0, 0});
               });

      auto insert_after_action = context_menu_.addAction (tr ("Insert &after ..."));
      connect (insert_after_action, &QAction::triggered, [this] ()
               {
                 auto item = ui_.colour_table_widget->itemAt (menu_pos_);
                 int row = item ? item->row () + 1 : colours_.size ();
                 insert_new_item( row, QColor {255, 255, 255});
               });

      menu_pos_ = p;            // save for context menu action handlers
      context_menu_.popup (ui_.colour_table_widget->mapToGlobal (p));
    }

    void import_palette ()
    {
      auto docs = QStandardPaths::writableLocation (QStandardPaths::DocumentsLocation);
      auto file_name = QFileDialog::getOpenFileName (this, tr ("Import Palette"), docs, tr ("Palettes (*.pal)"));
      if (!file_name.isEmpty ())
        {
          colours_ = load_palette (file_name);
          load_table ();
        }
    }

    void export_palette ()
    {
      auto docs = QStandardPaths::writableLocation (QStandardPaths::DocumentsLocation);
      auto file_name = QFileDialog::getSaveFileName (this, tr ("Export Palette"), docs, tr ("Palettes (*.pal)"));
      if (!file_name.isEmpty ())
        {
          if (!QFile::exists (file_name) && !file_name.contains ('.'))
            {
              file_name += ".pal";
            }
          QFile file {file_name};
          if (file.open (QFile::WriteOnly | QFile::Truncate | QFile::Text))
            {
              QTextStream stream {&file};
              Q_FOREACH (auto colour, colours_)
                {
                  stream << colour.red () << ';' << colour.green () << ';' << colour.blue () << endl;
                }
            }
          else
            {
              throw_qstring (QObject::tr ("Error writing waterfall palette file \"%1\": %2.").arg (file.fileName ()).arg (file.errorString ()));
            }
        }
    }

    Ui::wf_palette_design_dialog ui_;
    Colours colours_;
    QMenu context_menu_;
    QPoint menu_pos_;
  };
}

#include "WFPalette.moc"

WFPalette::WFPalette (QString const& file_path)
  : colours_ {load_palette (file_path)}
{
}

WFPalette::WFPalette (QList<QColor> const& colour_list)
  : colours_ {colour_list}
{
}

  // generate an array of colours suitable for the waterfall plotter
QVector<QColor> WFPalette::interpolate () const
{
  Colours colours {make_valid (colours_)};
  QVector<QColor> result;
  result.reserve (points);

  // do a linear-ish gradient between each supplied colour point
  auto interval = qreal (points) / (colours.size () - 1);

  for (int i {0}; i < points; ++i)
    {
      int prior = i / interval;

      if (prior >= (colours.size () - 1))
        {
          --prior;
        }
      auto next = prior + 1;
      if (next >= colours.size ())
        {
          --next;
        }

      // qDebug () << "WFPalette::interpolate: prior:" << prior << "total:" << colours.size ();

      auto increment = i - qreal (interval) * prior;
      qreal r {colours[prior].redF () + (increment * (colours[next].redF () - colours[prior].redF ()))/interval};
      qreal g {colours[prior].greenF () + (increment * (colours[next].greenF () - colours[prior].greenF ()))/interval};
      qreal b {colours[prior].blueF () + (increment * (colours[next].blueF () - colours[prior].blueF ()))/interval};
      result.append (QColor::fromRgbF (r, g, b));

      // qDebug () << "Palette colour[" << (result.size () - 1) << "] =" << result[result.size () - 1] << "from: r:" << r << "g:" << g << "b:" << b;
    }

  return result;
}

  // invoke the palette designer
bool WFPalette::design ()
{
  Designer designer {colours_};
  if (QDialog::Accepted == designer.exec ())
    {
      colours_ = designer.colours ();
      return true;
    }
  return false;
}


code:
#ifndef W_F_PALETTE_HPP__
#define W_F_PALETTE_HPP__

#include <QMetaType>
#include <QList>
#include <QVector>
#include <QColor>

class QString;

//
// Class WFPalette
//
//	Encapulates  a waterfall palette  description.  A  colour gradient
//	over 256 intervals is described  by a list of RGB colour triplets.
//	The list of  colours are use to interpolate  the full 256 interval
//	waterfall colour gradient.
//
// Responsibilities
//
//	Construction from  a string which is  a path to  a file containing
//	colour  descriptions  in  the   form  rrr;ggg;bbb  on  up  to  256
//	consecutive lines, where rrr, ggg and, bbb are integral numbers in
//	the range 0<=n<256.
//
//	Construction from a list of QColor instances.  Up to the first 256
//	list elements are used.
//
//	Includes a design GUI to create or adjust a WFPalette.
//
class WFPalette
{
public:
  using Colours = QList<QColor>;

  WFPalette () = default;
  explicit WFPalette (Colours const&);
  explicit WFPalette (QString const& file_path);
  WFPalette (WFPalette const&) = default;
  WFPalette& operator = (WFPalette const&) = default;

  Colours colours () const {return colours_;}

  // interpolate a gradient over 256 steps
  QVector<QColor> interpolate () const;

  // returns true if colours have been modified
  bool design ();

private:
  Colours colours_;
};

Q_DECLARE_METATYPE (WFPalette::Colours);

#endif


code:
#include "WSPRBandHopping.hpp"

#include <random>

#include <QPointer>
#include <QSettings>
#include <QBitArray>
#include <QList>
#include <QSet>
#include <QtWidgets>

#include "SettingsGroup.hpp"
#include "Configuration.hpp"
#include "Bands.hpp"
#include "FrequencyList.hpp"
#include "WsprTxScheduler.h"
#include "DriftingDateTime.h"

#include "pimpl_impl.hpp"
#include "moc_WSPRBandHopping.cpp"

extern "C"
{
#ifndef CMAKE_BUILD
#define FC_grayline grayline_
#else
#include "FC.h"
  void FC_grayline (int const * year, int const * month, int const * nday, float const * uth, char const * my_grid
                   , int const * nduration, int * isun
                   , int my_grid_len);
#endif
};

namespace
{
  char const * const title = "WSPR Band Hopping";
  char const * const periods[] = {"Sunrise grayline", "Day", "Sunset grayline", "Night", "Tune", "Rx only"};
  size_t constexpr num_periods {sizeof (periods) / sizeof (periods[0])};
  // These 10 bands are globally coordinated
  QList<QString> const coordinated_bands = {"160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"};
}

//
// Dialog - maintenance of band hopping options
//
class Dialog
  : public QDialog
{
public:
  using BandList = QList<QString>;

  Dialog (QSettings *, Configuration const *, BandList const * WSPT_bands, QBitArray * bands
          , int * gray_line_duration, QWidget * parent = nullptr);
  ~Dialog ();

  Q_SLOT void frequencies_changed ();
  void resize_to_maximum ();

private:
  void closeEvent (QCloseEvent *) override;
  void save_window_state ();

  QSettings * settings_;
  Configuration const * configuration_;
  BandList const * WSPR_bands_;
  QBitArray * bands_;
  int * gray_line_duration_;
  QPointer<QTableWidget> bands_table_;
  QBrush coord_background_brush_;
  QPointer<QSpinBox> gray_line_width_spin_box_;
  static int constexpr band_index_role {Qt::UserRole};
};

Dialog::Dialog (QSettings * settings, Configuration const * configuration, BandList const * WSPR_bands
                , QBitArray * bands, int * gray_line_duration, QWidget * parent)
  : QDialog {parent, Qt::Window | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint}
  , settings_ {settings}
  , configuration_ {configuration}
  , WSPR_bands_ {WSPR_bands}
  , bands_ {bands}
  , gray_line_duration_ {gray_line_duration}
  , bands_table_ {new QTableWidget {this}}
  , coord_background_brush_ {Qt::yellow}
  , gray_line_width_spin_box_ {new QSpinBox {this}}
{
  setWindowTitle (windowTitle () + ' ' + tr (title));
  {
    SettingsGroup g {settings_, title};
    restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ());
  }

  QVBoxLayout * main_layout {new QVBoxLayout};

  bands_table_->setRowCount (num_periods);
  bands_table_->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
  bands_table_->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
  bands_table_->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding);
  frequencies_changed ();
  main_layout->addWidget (bands_table_);
  // recalculate table when frequencies change
  connect (configuration_->frequencies (), &QAbstractItemModel::layoutChanged
           , this, &Dialog::frequencies_changed);
  // handle changes by updating the underlying flags
  connect (bands_table_.data (), &QTableWidget::itemChanged, [this] (QTableWidgetItem * item) {
      auto band_number = item->data (band_index_role).toInt ();
      bands_[item->row ()].setBit (band_number, Qt::Checked == item->checkState ());
    });

  // set up the gray line duration spin box
  gray_line_width_spin_box_->setRange (1, 60 * 2);
  gray_line_width_spin_box_->setSuffix ("min");
  gray_line_width_spin_box_->setValue (*gray_line_duration_);
  QFormLayout * form_layout = new QFormLayout;
  form_layout->addRow (tr ("Gray time:"), gray_line_width_spin_box_);
  connect (gray_line_width_spin_box_.data ()
           , static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged)
           , [this] (int new_value) {*gray_line_duration_ = new_value;});

  QHBoxLayout * bottom_layout = new QHBoxLayout;
  bottom_layout->addStretch ();
  bottom_layout->addLayout (form_layout);
  main_layout->addLayout (bottom_layout);

  setLayout (main_layout);
}

Dialog::~Dialog ()
{
  // do this here too because ESC or parent shutdown closing this
  // window doesn't queue a close event
  save_window_state ();
}

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

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

void Dialog::frequencies_changed ()
{
  bands_table_->setColumnCount (WSPR_bands_->size ());
  // set up and load the table of check boxes
  for (auto row = 0u; row < num_periods; ++row)
    {
      auto vertical_header = new QTableWidgetItem {periods[row]};
      vertical_header->setTextAlignment (Qt::AlignRight | Qt::AlignVCenter);
      bands_table_->setVerticalHeaderItem (row, vertical_header);
      int column {0};
      int band_number {0};
      for (auto const& band : *configuration_->bands ())
        {
          if (WSPR_bands_->contains (band))
            {
              if (0 == row)
                {
                  auto horizontal_header = new QTableWidgetItem {band};
                  bands_table_->setHorizontalHeaderItem (column, horizontal_header);
                }
              auto item = new QTableWidgetItem;
              item->setFlags (Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
              item->setCheckState (bands_[row].testBit (band_number) ? Qt::Checked : Qt::Unchecked);
              item->setData (band_index_role, band_number);
              if (coordinated_bands.contains (band))
                {
                  item->setBackground (coord_background_brush_);
                }
              bands_table_->setItem (row, column, item);
              ++column;
            }
          ++band_number;
        }
    }
  bands_table_->resizeColumnsToContents ();
  auto is_visible = isVisible ();
  show ();
  resize_to_maximum ();
  adjustSize ();   // fix the size
  if (!is_visible)
    {
      hide ();
    }
}

// to get the dialog window exactly the right size to contain the
// widgets without needing scroll bars we need to measure the size of
// the table widget and set its minimum size to the measured size
void Dialog::resize_to_maximum ()
{
  bands_table_->setMinimumSize ({
      bands_table_->horizontalHeader ()->length ()
        + bands_table_->verticalHeader ()->width ()
        + 2 * bands_table_->frameWidth ()
      , bands_table_->verticalHeader ()->length ()
        + bands_table_->horizontalHeader ()->height ()
        + 2 * bands_table_->frameWidth ()
    });
  bands_table_->setMaximumSize (bands_table_->minimumSize ());
}

class WSPRBandHopping::impl
{
public:
  using BandList = Dialog::BandList;

  impl (QSettings * settings, Configuration const * configuration, QWidget * parent_widget)
    : settings_ {settings}
    , configuration_ {configuration}
    , tx_percent_ {0}
    , parent_widget_ {parent_widget}
    , last_was_tx_ {false}
    , carry_ {false}
    , seed_ {{rand (), rand (), rand (), rand (), rand (), rand (), rand (), rand ()}}
    , gen_ {seed_}
    , dist_ {1, 100}
  {
    auto num_bands = configuration_->bands ()->rowCount ();
    for (auto& flags : bands_)
      {
        flags.resize (num_bands);
      }
  }

  bool simple_scheduler ();

  QSettings * settings_;
  Configuration const * configuration_;
  int tx_percent_;
  BandList WSPR_bands_;
  BandList rx_permutation_;
  BandList tx_permutation_;
  QWidget * parent_widget_;

  // 5 x 10 bit flags representing each hopping band in each period
  // and tune
  QBitArray bands_[num_periods];

  int gray_line_duration_;
  QPointer<Dialog> dialog_;
  bool last_was_tx_;
  bool carry_;
  std::seed_seq seed_;
  std::mt19937 gen_;
  std::uniform_int_distribution<int> dist_;
};

bool WSPRBandHopping::impl::simple_scheduler ()
{
  auto tx = carry_ || tx_percent_ > dist_ (gen_);
  if (carry_)
    {
      carry_ = false;
    }
  else if (tx_percent_ < 40 && last_was_tx_ && tx)
    {
      // if percentage is less than 40 then avoid consecutive tx but
      // always catch up on the next round
      tx = false;
      carry_ = true;
    }
  last_was_tx_ = tx;
  return tx;
}

WSPRBandHopping::WSPRBandHopping (QSettings * settings, Configuration const * configuration, QWidget * parent_widget)
  : m_ {settings, configuration, parent_widget}
{
#if INCLUDE_ALL_MODES
  // detect changes to the working frequencies model
  m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (m_->configuration_->region (), Modes::WSPR).toList ();
  connect (m_->configuration_->frequencies (), &QAbstractItemModel::layoutChanged
           , [this] () {
             m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (m_->configuration_->region (), Modes::WSPR).toList ();
           });
#endif

  // load settings
  SettingsGroup g {m_->settings_, title};
  size_t size = m_->settings_->beginReadArray ("phases");
  for (auto i = 0u; i < size; ++i)
    {
      if (i < num_periods)
        {
          m_->settings_->setArrayIndex (i);
          m_->bands_[i] = m_->settings_->value ("bands").toBitArray ();
        }
    }
  m_->settings_->endArray ();
  m_->gray_line_duration_ = m_->settings_->value ("GrayLineDuration", 60).toUInt ();
}

WSPRBandHopping::~WSPRBandHopping ()
{
  // save settings
  SettingsGroup g {m_->settings_, title};
  m_->settings_->beginWriteArray ("phases");
  for (auto i = 0u; i < num_periods; ++i)
    {
      m_->settings_->setArrayIndex (i);
      m_->settings_->setValue ("bands", m_->bands_[i]);
    }
  m_->settings_->endArray ();
  m_->settings_->setValue ("GrayLineDuration", m_->gray_line_duration_);
}

// pop up the maintenance dialog window
void WSPRBandHopping::show_dialog (bool /* checked */)
{
  if (!m_->dialog_)
    {
      m_->dialog_ = new Dialog {m_->settings_, m_->configuration_, &m_->WSPR_bands_, m_->bands_
                                , &m_->gray_line_duration_, m_->parent_widget_};
    }
  m_->dialog_->show ();
  m_->dialog_->raise ();
  m_->dialog_->activateWindow ();
}

int WSPRBandHopping::tx_percent () const
{
  return m_->tx_percent_;
}

void WSPRBandHopping::set_tx_percent (int new_value)
{
  m_->tx_percent_ = new_value;
}

// determine the parameters of the hop, if any
auto WSPRBandHopping::next_hop (bool tx_enabled) -> Hop
{
  auto const& now = DriftingDateTime::currentDateTimeUtc ();
  auto const& date = now.date ();
  auto year = date.year ();
  auto month = date.month ();
  auto day = date.day ();
  auto const& time = now.time ();
  float uth = time.hour () + time.minute () / 60.
    + (time.second () + .001 * time.msec ()) / 3600.;
  auto my_grid = m_->configuration_->my_grid ();
  int period_index;
  int band_index;
  int tx_next;

  my_grid = (my_grid + "      ").left (6); // hopping doesn't like
                                           // short grids

  // look up the period for this time
  FC_grayline (&year, &month, &day, &uth, my_grid.toLatin1 ().constData ()
               , &m_->gray_line_duration_, &period_index
               , my_grid.size ());

  band_index = next_hopping_band();

  tx_next = next_is_tx () && tx_enabled;

  int frequencies_index {-1};
  auto const& frequencies = m_->configuration_->frequencies ();
  auto const& bands = m_->configuration_->bands ();
  auto band_name = bands->data (bands->index (band_index + 3, 0)).toString ();
  if (m_->bands_[period_index].testBit (band_index + 3) // +3 for
                                                        // coordinated bands
      && m_->WSPR_bands_.contains (band_name))
    {
      // here we have a band that has been enabled in the hopping
      // matrix so check it it has a configured working frequency
      frequencies_index = frequencies->best_working_frequency (band_name);
    }

  // if we do not have a configured working frequency on the selected
  // coordinated hopping band we next pick from a random permutation
  // of the other enabled bands in the hopping bands matrix
  if (frequencies_index < 0)
    {
      // build sets of available rx and tx bands
      auto target_rx_bands = m_->WSPR_bands_.toSet ();
      auto target_tx_bands = target_rx_bands;
      for (auto i = 0; i < m_->bands_[period_index].size (); ++i)
        {
          auto const& band = bands->data (bands->index (i, 0)).toString ();
          // remove bands that are not enabled for hopping in this phase
          if (!m_->bands_[period_index].testBit (i))
            {
              target_rx_bands.remove (band);
              target_tx_bands.remove (band);
            }
          // remove rx only bands from transmit list and vice versa
          if (m_->bands_[5].testBit (i))
            {
              target_tx_bands.remove (band);
            }
          else
            {
              target_rx_bands.remove (band);
            }
        }
      // if we have some bands to permute
      if (target_rx_bands.size () + target_tx_bands.size ())
        {
          if (!(m_->rx_permutation_.size () + m_->tx_permutation_.size ()) // all used up
              // or rx list contains a band no longer scheduled
              || !target_rx_bands.contains (m_->rx_permutation_.toSet ())
              // or tx list contains a band no longer scheduled for tx
              || !target_tx_bands.contains (m_->tx_permutation_.toSet ()))
            {
              // build new random permutations
              m_->rx_permutation_ = target_rx_bands.toList ();
              std::random_shuffle (std::begin (m_->rx_permutation_), std::end (m_->rx_permutation_));
              m_->tx_permutation_ = target_tx_bands.toList ();
              std::random_shuffle (std::begin (m_->tx_permutation_), std::end (m_->tx_permutation_));
              // qDebug () << "New random Rx permutation:" << m_->rx_permutation_
              //           << "random Tx permutation:" << m_->tx_permutation_;
            }
          if ((tx_next && m_->tx_permutation_.size ()) || !m_->rx_permutation_.size ())
            {
              Q_ASSERT (m_->tx_permutation_.size ());
              // use one from the current random tx permutation
              band_name = m_->tx_permutation_.takeFirst ();
            }
          else
            {
              Q_ASSERT (m_->rx_permutation_.size ());
              // use one from the current random rx permutation
              band_name = m_->rx_permutation_.takeFirst ();
            }
          // find the first WSPR working frequency for the chosen band
          frequencies_index = frequencies->best_working_frequency (band_name);
          if (frequencies_index >= 0) // should be a redundant check,
                                      // but to be safe
            {
              // we can use the random choice
              // qDebug () << "random:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList_v2::frequency_column)).toString ();
              band_index = bands->find (band_name);
              if (band_index < 0) // this shouldn't happen
                {
                  Q_ASSERT (band_index >= 0);
                  frequencies_index = -1;
                }
            }
        }
     }
  else
    {
      band_index += 3;
      // qDebug () << "scheduled:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList_v2::frequency_column)).toString ();
      // remove from random permutations to stop the coordinated bands
      // getting too high a weighting - not perfect but surely helps
      m_->rx_permutation_.removeOne (band_name);
      m_->tx_permutation_.removeOne (band_name);
    }

  return {
    periods[period_index]

      , frequencies_index

      , frequencies_index >= 0               // new band
      && tx_enabled                          // transmit is allowed
      && !tx_next                            // not going to Tx anyway
      && m_->bands_[4].testBit (band_index)  // tune up required
      && !m_->bands_[5].testBit (band_index) // not an Rx only band

      , frequencies_index >= 0               // new band
      && tx_next                             // Tx scheduled
      && !m_->bands_[5].testBit (band_index) // not an Rx only band
   };
}

bool WSPRBandHopping::next_is_tx (bool simple_schedule)
{
  if (simple_schedule)
    {
      return m_->simple_scheduler ();
    }
  if (100 == m_->tx_percent_)
    {
      return true;
    }
  else
    {
      // consult scheduler to determine if next period should be a tx interval
      return next_tx_state(m_->tx_percent_);
    }
}


code:
#ifndef WSPR_BAND_HOPPING_HPP__
#define WSPR_BAND_HOPPING_HPP__

#include <QObject>

#include "pimpl_h.hpp"

class QSettings;
class Configuration;
class QWidget;

//
// WSPR Band Hopping Control
//
//  WSPR specifies  a globally  coordinated band hopping  schedule and
//  this class implements that.
//
// Responsibilities
//
//  Provides a  maintenance dialog allowing  the user to  define which
//  bands are allowed from the band hopping schedule as defined here:
//
//    http://physics.princeton.edu/pulsar/K1JT/doc/wspr/wspr-main.html
//
//  Along with selecting bands a flag  indicating that a short tune up
//  signal is  required for specified  bands before they are  used for
//  receive.
//
//  Provides a Qt property that holds  the Tx percentage which is used
//  to generate a semi-randomized schedule of period to transmit. This
//  schedule is random but adjusted to limit the number of consecutive
//  transmission periods, it also adjusts  the schedule to ensure that
//  the overall number of transmission periods in any two hour hopping
//  schedule reflects the percentage provided.
//
// Collaborations
//
//  Settings including  the selected bands  with periods, the  tune up
//  flags  and the  gray line  duration are  maintained in  persistent
//  storage using the provided QSettings object instance.
//
//  A passed  in Configuration  object instance is  used to  query the
//  FrequencyList_v2 model  to determine working frequencies  for each
//  band. The  row index  of this  model is  returned by  this classes
//  hopping scheduling method so it may be conveniently used to select
//  a new working frequency by a client.
//
class WSPRBandHopping
  : public QObject
{
  Q_OBJECT;
  Q_PROPERTY (int tx_percent READ tx_percent WRITE set_tx_percent);

public:
  WSPRBandHopping (QSettings *, Configuration const *, QWidget * parent = nullptr);
  ~WSPRBandHopping ();

  // display the band hopping maintenance dialog
  Q_SLOT void show_dialog (bool);

  // Property tx_percent implementation
  int tx_percent () const;
  Q_SLOT void set_tx_percent (int);

  // structure that defines the results of the next_hop() method
  struct Hop
  {
    QString period_name_;
    int frequencies_index_;     // may be -1 indicating no change
    bool tune_required_;
    bool tx_next_;
  };
  // return the next band parameters
  Hop next_hop (bool tx_enabled);
  // determine if the next period should be a transmit period
  bool next_is_tx (bool simple_schedule = false);

private:
  // implementation hidden from public interface
  class impl;
  pimpl<impl> m_;
};

#endif


code:
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cstring>
#include <iostream>

namespace
{
  char tx[6][10];
  int tx_table_2hr_slot=-1, tx_table_pctx=0;
};

int tx_band_sum(char bsum[10])
{
    int i,j;

    for (j=0; j<10; j++) {
        bsum[j]=0;
        for (i=0; i<6; i++) {
            bsum[j]=bsum[j]+tx[i][j];
        }
    }
    return 1;
}

int tx_add_to_band(int band)
{
    // add tx cycle to a band without regard to ntxlim
    int i,islot;
    for ( i=0; i<10; i++) {
        islot=rand()%6;
        if( tx[islot][band] != 1 ) {
            tx[islot][band]=1;
            return 1;
        }
    }
    return 0;
}

int tx_sum()
{
    int i,j,sum=0;
    for (i=0; i<6; i++) {
        for (j=0; j<10; j++) {
            sum=sum+tx[i][j];
        }
    }
    return sum;
}

int tx_add_one(char* tx)
{
    int i, j, txflag, ngap;
    // adds one tx slot to an existing array without
    // creating successive tx slots.
    // try to fill largest gaps first
    // try gap sizes of 13, 11, 9, 7, 5, and finally 3
    for (ngap=13; ngap>=3; ngap=ngap-2) {
        for (i=0; i< 60-ngap; i++) {
            txflag=0;
            for (j=0; j<ngap; j++) {
              if( tx[i+j]==1 )
                  txflag=1;
            }
            if( txflag == 0 ) { // found a gap of size ngap
                tx[i+ngap/2]=1;
                return 1;
            }
        }
    }
    // last resort - see if we can set the last slot to 1
    if( tx[58]==0 && tx[59]==0 ) {
      tx[59]=1;
      return 1;
    }
    return 0;
}

int tx_trim(char* tx, int ntxlim)
{
    /* ntxlim is max number of successive transmissions
    * trim array so that ntxlim is not exceeded
    * also make sure that first slot is never a tx slot
    * this enures that we won't get a double tx because of the
    * last slot of one table and the first slot of the next table
    * both being tx slots.
    */
    int i,nrun,sum;

    if( tx[0] == 1 ) tx[0] = 0;
    nrun=0;
    for (i=0; i<60; i++) {
        if( tx[i]==1 ) {
            nrun++;
            if( nrun > ntxlim ) {
                tx[i]=0;
                nrun=0;
            }
        } else {
            nrun=0;
        }
    }
    sum=0;
    for (i=0; i<60; i++) {
        sum=sum+tx[i];
    }
    return sum;
}

void tx_print()
{
    int i,j;
    for (i=0; i<6; i++) {
        for (j=0; j<10; j++) {
            if( (i*10+j)%10 == 0 && i>=0 ) printf("\n");
            printf("%d ",tx[i][j]);
        }
    }
    printf("\n");
    fflush(stdout);
}

int create_tx_schedule(int pctx)
{
    char bsum[10];
    int i, j, k, sum, ntxlim, ntxbandmin, needed;
    int iflag, nrx;
    float rxavg,x;

    needed=60*(pctx/100.0)+0.5;

    memset(tx,0,sizeof(char)*60);

    if( pctx == 0 ) return 0;

    if( pctx <= 25 ) { // Use K1JT's algorithm in this regime
        rxavg=100.0/pctx-1.0;
        i=0;
        while(1) {
            x=(rand()%100)/100.0;
            nrx=(rxavg+3.0*x-1.0); //2-5 for 25%
            i=i+nrx+1;
            if( i < 60 ) {
                tx[i/10][i%10]=1;
            } else {
                break;
            }
        }
        return 0;
    } else if( pctx > 25 && pctx < 33 ) {
        ntxlim=1;
        ntxbandmin=1;
    } else if( pctx >= 33 && pctx < 50 ) {
        ntxlim=1;
        ntxbandmin=2;
    } else if( pctx >= 50 && pctx < 60 ) {
        ntxlim=2;
        ntxbandmin=3;
    } else {
        ntxlim=3;
        ntxbandmin=4;
    }

    // when txpct>25% create a table that guarantees that all
    // bands will be visited 1, 2, or 3 times, as appropriate.
    //
    // start by filling each band slot with ntxbandmin tx's
    for (i=0; i<ntxbandmin; i++) {
        for (j=0; j<10; j++) {
            tx_add_to_band(j);
        }
    }
    // trim so that no more than ntxlim successive transmissions
    sum=tx_trim(*tx,ntxlim);
    j=0;
    iflag=0;
    while (j<100 && iflag==0) {
        // now backfill columns that got trimmed
        tx_band_sum(bsum);

        iflag=1;
        for (i=0; i<10; i++) {
            if( bsum[i] < ntxbandmin ) {
                iflag=0;
                for (k=0; k<ntxbandmin-bsum[i]; k++) {
                    tx_add_to_band(i);
                }
            }
        }
        sum=tx_trim(*tx,ntxlim);
        j++;
    }

    for(j=0; j < (needed-sum); j++ ) {
        tx_add_one(*tx);
    }
    return 0;
}

int next_tx_state(int pctx)
{
  time_t now=time(0)+30;
  tm *ltm = gmtime(&now);
  int hour  = ltm->tm_hour;
  int minute = ltm->tm_min;

  int tx_2hr_slot = hour/2;
  int tx_20min_slot = (hour-tx_2hr_slot*2)*3 + minute/20;
  int tx_2min_slot = (minute%20)/2;

  if( (tx_2hr_slot != tx_table_2hr_slot) || (tx_table_pctx != pctx) ) {
    create_tx_schedule(pctx);
    tx_table_2hr_slot = tx_2hr_slot;
    tx_table_pctx = pctx;
  }

//  tx_print();
  return tx[tx_20min_slot][tx_2min_slot];
}

int next_hopping_band()
{
  time_t now=time(0)+30;
  tm *ltm = gmtime(&now);
  int minute = ltm->tm_min;
  int tx_2min_slot = (minute%20)/2;
  return tx_2min_slot;
}


code:
#ifndef WSPR_TX_SCHEDULER_H_
#define WSPR_TX_SCHEDULER_H_

int next_tx_state (int pctx);
int next_hopping_band();

#endif


code:
#include "about.h"

#include <QCoreApplication>
#include <QString>

#include "revision_utils.hpp"

#include "ui_about.h"

CAboutDlg::CAboutDlg(QWidget *parent) :
  QDialog(parent),
  ui(new Ui::CAboutDlg)
{
  ui->setupUi(this);

  ui->labelTxt->setText ("
" + QString {"JS8Call v"
                             + QCoreApplication::applicationVersion ()
                             + " " + revision ()}.simplified () + "

"

                         "JS8Call is a derivative of the WSJT-X application, "
                         "restructured and redesigned for message passing.
"
                         "It is not supported by nor endorsed by the WSJT-X "
                         "development group.
JS8Call is "
                         "licensed under and in accordance with the terms "
                         "of the GPLv3 license.
"
                         "The source code modifications are public and can be found in this repository.

"

                         "JS8Call is heavily inspired by WSJT-X, Fldigi, "
                         "and FSQCall
and would not exist without the hard work and "
                         "dedication of the many
developers in the amateur radio "
                         "community.

"
                         "JS8Call stands on the shoulder of giants...the takeoff angle "
                         "is better up there.

"
                         "A special thanks goes out to the JS8Call development team:

"
                         "KC9QNE, "
                         "KI6SSI, "
                         "K0OG, "
                         "LB9YH, "
                         "M0IAX, "
                         "N0JDS, "
                         "OH8STN, "
                         "VA3OSO, "
                         "VK1MIC, "
                         "W0FW,

and the many other amateur radio operators who have given "
                         "JS8Call a chance.");
}

CAboutDlg::~CAboutDlg()
{
}


code:
// -*- Mode: C++ -*-
#ifndef ABOUTDLG_H
#define ABOUTDLG_H

#include <QDialog>
#include <QScopedPointer>

namespace Ui {
  class CAboutDlg;
}

class CAboutDlg
  : public QDialog
{
public:
	explicit CAboutDlg(QWidget *parent = nullptr);
  ~CAboutDlg ();

private:
	QScopedPointer<Ui::CAboutDlg> ui;
};

#endif // ABOUTDLG_H


code:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>CAboutDlg</class>
 <widget class="QDialog" name="CAboutDlg">
  <property name="windowModality">
   <enum>Qt::NonModal</enum>
  </property>
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>192</width>
    <height>116</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>About JS8Call</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout_2">
     <item>
      <widget class="QLabel" name="labelTxt">
       <property name="text">
        <string/>
       </property>
       <property name="alignment">
        <set>Qt::AlignCenter</set>
       </property>
       <property name="openExternalLinks">
        <bool>true</bool>
       </property>
      </widget>
     </item>
    </layout>
   </item>
   <item>
    <spacer name="verticalSpacer">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
   <item>
    <layout class="QHBoxLayout" name="horizontalLayout">
     <item>
      <spacer name="horizontalSpacer">
       <property name="orientation">
        <enum>Qt::Horizontal</enum>
       </property>
       <property name="sizeHint" stdset="0">
        <size>
         <width>40</width>
         <height>20</height>
        </size>
       </property>
      </spacer>
     </item>
     <item>
      <widget class="QPushButton" name="okButton">
       <property name="text">
        <string>OK</string>
       </property>
      </widget>
     </item>
     <item>
      <spacer name="horizontalSpacer_2">
       <property name="orientation">
        <enum>Qt::Horizontal</enum>
       </property>
       <property name="sizeHint" stdset="0">
        <size>
         <width>40</width>
         <height>20</height>
        </size>
       </property>
      </spacer>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>okButton</sender>
   <signal>clicked()</signal>
   <receiver>CAboutDlg</receiver>
   <slot>accept()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>321</x>
     <y>120</y>
    </hint>
    <hint type="destinationlabel">
     <x>186</x>
     <y>71</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>


code:
This directory contains original artwork used to generate the graphics
used in various parts of the WSJT-X ecosystem. The CMake build scripts
do not  generate the  final image bitmaps  because of the  extra tools
required to  complete this  step. Instead there  is shell  script here
(make_graphics.sh) that does the generation. If you want to modify the
source graphics  or add new  ones then you  need an SVG editor  (I use
inkscape)  and  a   tool  to  do  various  conversion   steps  (I  use
ImageMagick), the shell script explicitly uses these tools.

The files here are:

installer_logo.svg - A 150x57 pixel  image (the size is important with
    a  whte background  that is  used  at the  top right  of the  NSIS
    Windows installer.

wsjt_globe_1024x1024.svg - A 1024x1024 pixel generic globe image which
    is used in various places, mainly for high resolution icons.

wsjtx_globe_1024x1024.svg -  A 1024x1024  pixel WSJT-X  specific image
    which is used in various places, mainly for high resolution icons.

wsjt_globe_128x128.svg - A  128x128 pixel image which is  used for low
    resolution icons.

make_graphics.sh  -  Run  this  script  (on  Linux)  to  generate  the
    intermediate  bitmap image  files used  in the  wsjtx  build. This
    script  generates all  but the  final  Mac iconset  file which  is
    generated by a build on Mac since it requires a Mac developer tool
    (iconutil). This script requires that inkscape and ImageMagick are
    installed.

path: artwork/README


code:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="1024"
   height="1024"
   viewBox="0 0 270.93333 270.93334"
   version="1.1"
   id="svg16"
   inkscape:export-filename="/home/jordan/bitmap.png"
   inkscape:export-xdpi="300.06662"
   inkscape:export-ydpi="300.06662"
   inkscape:version="0.92.3 (2405546, 2018-03-11)"
   sodipodi:docname="icon_1024.svg">
  <defs
     id="defs10">
    <linearGradient
       id="linearGradient6001"
       osb:paint="solid">
      <stop
         style="stop-color:#000000;stop-opacity:1;"
         offset="0"
         id="stop5999" />
    </linearGradient>
  </defs>
  <sodipodi:namedview
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.48333333"
     inkscape:cx="1350"
     inkscape:cy="512"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     showgrid="false"
     inkscape:window-width="1920"
     inkscape:window-height="1053"
     inkscape:window-x="0"
     inkscape:window-y="27"
     inkscape:window-maximized="1"
     units="px"
     inkscape:pagecheckerboard="true" />
  <metadata
     id="metadata13">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(0,-26.06665)">
    <g
       id="g6030"
       transform="matrix(1.5635065,0,0,1.5635065,-28.822499,6.8392367)">
      <circle
         inkscape:export-ydpi="300.06662"
         inkscape:export-xdpi="300.06662"
         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         r="81.642853"
         cy="98.940475"
         cx="105.07738"
         id="path18" />
      <text
         id="text24"
         y="152.33945"
         x="52.539673"
         style="font-style:normal;font-weight:normal;font-size:7.09937859px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.17748445"
         xml:space="preserve"><tspan
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:176.3888855px;font-family:wasy10;-inkscape-font-specification:wasy10;fill:#000000;stroke-width:0.17748445"
           y="152.33945"
           x="52.539673"
           id="tspan22"
           sodipodi:role="line">⌁</tspan></text>
    </g>
  </g>
</svg>


code:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="128"
   height="128"
   viewBox="0 0 33.866666 33.866667"
   version="1.1"
   id="svg16"
   inkscape:export-filename="/home/jordan/bitmap.png"
   inkscape:export-xdpi="300.06662"
   inkscape:export-ydpi="300.06662"
   inkscape:version="0.92.3 (2405546, 2018-03-11)"
   sodipodi:docname="icon_1024.svg">
  <defs
     id="defs10">
    <linearGradient
       id="linearGradient6001"
       osb:paint="solid">
      <stop
         style="stop-color:#000000;stop-opacity:1;"
         offset="0"
         id="stop5999" />
    </linearGradient>
  </defs>
  <sodipodi:namedview
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.48333333"
     inkscape:cx="1350"
     inkscape:cy="512"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     showgrid="false"
     inkscape:window-width="1920"
     inkscape:window-height="1053"
     inkscape:window-x="0"
     inkscape:window-y="27"
     inkscape:window-maximized="1"
     units="px"
     inkscape:pagecheckerboard="true" />
  <metadata
     id="metadata13">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(0,-263.13332)">
    <g
       id="g6030"
       transform="matrix(0.19543832,0,0,0.19543832,-3.6028125,260.72988)">
      <circle
         inkscape:export-ydpi="300.06662"
         inkscape:export-xdpi="300.06662"
         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         r="81.642853"
         cy="98.940475"
         cx="105.07738"
         id="path18" />
      <text
         id="text24"
         y="152.33945"
         x="52.539673"
         style="font-style:normal;font-weight:normal;font-size:7.09937859px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.17748445"
         xml:space="preserve"><tspan
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:176.3888855px;font-family:wasy10;-inkscape-font-specification:wasy10;fill:#000000;stroke-width:0.17748445"
           y="152.33945"
           x="52.539673"
           id="tspan22"
           sodipodi:role="line">⌁</tspan></text>
    </g>
  </g>
</svg>


code:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="2700"
   height="1024"
   viewBox="0 0 714.37498 270.93334"
   version="1.1"
   id="svg16"
   inkscape:version="0.92.3 (2405546, 2018-03-11)"
   sodipodi:docname="icon_1024.svg">
  <defs
     id="defs10">
    <linearGradient
       id="linearGradient6001"
       osb:paint="solid">
      <stop
         style="stop-color:#000000;stop-opacity:1;"
         offset="0"
         id="stop5999" />
    </linearGradient>
  </defs>
  <sodipodi:namedview
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.48333333"
     inkscape:cx="1350"
     inkscape:cy="512"
     inkscape:document-units="mm"
     inkscape:current-layer="layer1"
     showgrid="false"
     inkscape:window-width="1920"
     inkscape:window-height="1053"
     inkscape:window-x="0"
     inkscape:window-y="27"
     inkscape:window-maximized="1"
     units="px"
     inkscape:pagecheckerboard="true" />
  <metadata
     id="metadata13">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1"
     transform="translate(0,-26.06665)">
    <g
       id="g6030"
       transform="matrix(1.4906384,0,0,1.4906384,-20.910237,15.983062)">
      <circle
         inkscape:export-ydpi="300.06662"
         inkscape:export-xdpi="300.06662"
         style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
         r="81.642853"
         cy="98.940475"
         cx="105.07738"
         id="path18" />
      <text
         id="text24"
         y="152.33945"
         x="52.539673"
         style="font-style:normal;font-weight:normal;font-size:7.09937859px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.17748445"
         xml:space="preserve"><tspan
           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:176.3888855px;font-family:wasy10;-inkscape-font-specification:wasy10;fill:#000000;stroke-width:0.17748445"
           y="152.33945"
           x="52.539673"
           id="tspan22"
           sodipodi:role="line">⌁</tspan></text>
    </g>
  </g>
</svg>


code:
#!/bin/sh

#
# Windows
#
inkscape -z -e /tmp/image-0.png icon_128.svg
inkscape -z -e /tmp/image-1.png icon_1024.svg
convert '/tmp/image-%d.png[0-1]' -background transparent	\
    \( -clone 0 -resize 16 -colors 256 -compress none \)	\
    \( -clone 0 -resize 20 -colors 256 -compress none \)	\
    \( -clone 0 -resize 24 -colors 256 -compress none \)	\
    \( -clone 0 -resize 32 -colors 256 -compress none \)	\
    \( -clone 0 -resize 40 -colors 256 -compress none \)	\
    \( -clone 1 -resize 48 -colors 256 -compress none \)	\
    \( -clone 1 -resize 96 -colors 256 -compress none \)	\
    \( -clone 1 -resize 128 -colors 256 -compress none \)	\
    \( -clone 0 -resize 16 -compress none \)	\
    \( -clone 0 -resize 20 -compress none \)	\
    \( -clone 0 -resize 24 -compress none \)	\
    \( -clone 0 -resize 32 -compress none \)	\
    \( -clone 0 -resize 40 -compress none \)	\
    \( -clone 1 -resize 48 -compress none \)	\
    \( -clone 1 -resize 64 -compress none \)	\
    \( -clone 1 -resize 96 -compress none \)	\
    \( -clone 1 -resize 128 -compress none \)	\
    \( -clone 1 -resize 256 -compress Zip \)	\
    -delete 1 -delete 0	\
    -alpha remove ../icons/windows-icons/js8call.ico
rm /tmp/image-0.png /tmp/image-1.png
identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' ../icons/windows-icons/js8call.ico
#
#inkscape -z -e /dev/stdout -w 150 -h 57 -b white installer_logo.svg | tail -n +4 |	\
#    convert png:- -resize 150x57 +matte BMP3:../icons/windows-icons/installer_logo.bmp
convert png:installer_logo.svg.png -resize 150x57 +matte BMP3:../icons/windows-icons/installer_logo.bmp
identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' ../icons/windows-icons/installer_logo.bmp

#
# Mac
#
mkdir -p ../icons/Darwin/JS8Call.iconset
inkscape -z -e ../icons/Darwin/JS8Call.iconset/icon_16x16.png -w 16 -h 16 icon_128.svg
inkscape -z -e ../icons/Darwin/JS8Call.iconset/icon_16x16@2x.png -w 32 -h 32 icon_128.svg
inkscape -z -e ../icons/Darwin/JS8Call.iconset/icon_32x32.png -w 32 -h 32 icon_128.svg
inkscape -z -e ../icons/Darwin/JS8Call.iconset/icon_32x32@2x.png -w 64 -h 64 icon_128.svg
inkscape -z -e ../icons/Darwin/JS8Call.iconset/icon_128x128.png -w 128 -h 128 icon_1024.svg
inkscape -z -e ../icons/Darwin/JS8Call.iconset/icon_128x128@2x.png -w 256 -h 256 icon_1024.svg
inkscape -z -e ../icons/Darwin/JS8Call.iconset/icon_256x256.png -w 256 -h 256 icon_1024.svg
inkscape -z -e ../icons/Darwin/JS8Call.iconset/icon_256x256@2x.png -w 512 -h 512 icon_1024.svg
inkscape -z -e ../icons/Darwin/JS8Call.iconset/icon_512x512.png -w 512 -h 512 icon_1024.svg
inkscape -z -e ../icons/Darwin/JS8Call.iconset/icon_512x512@2x.png -w 1024 -h 1024 icon_1024.svg
identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' ../icons/Darwin/JS8Call.iconset/*
# generic globe iconset for utilities
mkdir -p ../icons/Darwin/wsjt.iconset
inkscape -z -e ../icons/Darwin/wsjt.iconset/icon_16x16.png -w 16 -h 16 icon_128.svg
inkscape -z -e ../icons/Darwin/wsjt.iconset/icon_16x16@2x.png -w 32 -h 32 icon_128.svg
inkscape -z -e ../icons/Darwin/wsjt.iconset/icon_32x32.png -w 32 -h 32 icon_128.svg
inkscape -z -e ../icons/Darwin/wsjt.iconset/icon_32x32@2x.png -w 64 -h 64 icon_128.svg
inkscape -z -e ../icons/Darwin/wsjt.iconset/icon_128x128.png -w 128 -h 128 icon_1024.svg
inkscape -z -e ../icons/Darwin/wsjt.iconset/icon_128x128@2x.png -w 256 -h 256 icon_1024.svg
inkscape -z -e ../icons/Darwin/wsjt.iconset/icon_256x256.png -w 256 -h 256 icon_1024.svg
inkscape -z -e ../icons/Darwin/wsjt.iconset/icon_256x256@2x.png -w 512 -h 512 icon_1024.svg
inkscape -z -e ../icons/Darwin/wsjt.iconset/icon_512x512.png -w 512 -h 512 icon_1024.svg
inkscape -z -e ../icons/Darwin/wsjt.iconset/icon_512x512@2x.png -w 1024 -h 1024 icon_1024.svg
identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' ../icons/Darwin/wsjt.iconset/*
#
inkscape -z -e "../icons/Darwin/DragNDrop Background.png" -w 640 -h 480 -b white "DragNDrop Background.svg"
identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' "../icons/Darwin/DragNDrop Background.png"

#
# KDE & Gnome
#
inkscape -z -e ../icons/Unix/js8call_icon.png -w 128 -h 128 icon_1024.svg
identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' ../icons/Unix/js8call_icon.png


code:
#include "astro.h"

#include <stdio.h>

#include <QApplication>
#include <QFile>
#include <QTextStream>
#include <QSettings>
#include <QDateTime>
#include <QDir>
#include <QCloseEvent>
#include <QDebug>

#include "commons.h"
#include "MessageBox.hpp"
#include "Configuration.hpp"
#include "SettingsGroup.hpp"
#include "qt_helpers.hpp"

#include "ui_astro.h"
#include "moc_astro.cpp"

extern "C" {
  void astrosub_(int* nyear, int* month, int* nday, double* uth, double* freqMoon,
                 const char* mygrid, const char* hisgrid, double* azsun,
                 double* elsun, double* azmoon, double* elmoon, double* azmoondx,
                 double* elmoondx, int* ntsky, int* ndop, int* ndop00,
                 double* ramoon, double* decmoon, double* dgrd, double* poloffset,
                 double* xnr, double* techo, double* width1, double* width2,
                 bool* bTx, const char* AzElFileName, const char* jpleph,
                 fortran_charlen_t, fortran_charlen_t, fortran_charlen_t, fortran_charlen_t);
}

Astro::Astro(QSettings * settings, Configuration const * configuration, QWidget * parent)
  : QDialog {parent, Qt::WindowTitleHint}
  , settings_ {settings}
  , configuration_ {configuration}
  , ui_ {new Ui::Astro}
  , m_DopplerMethod {0}
  , m_dop {0}
  , m_dop00 {0}
  , m_dx_two_way_dop {0}
{
  ui_->setupUi (this);
  setWindowTitle (QApplication::applicationName () + " - " + tr ("Astronomical Data"));
  setStyleSheet ("QWidget {background: white;}");
  connect (ui_->cbDopplerTracking, &QAbstractButton::toggled, ui_->doppler_widget, &QWidget::setVisible);
  read_settings ();
  ui_->text_label->clear ();
}

Astro::~Astro ()
{
  ui_->cbDopplerTracking->setChecked (false);
  Q_EMIT tracking_update ();
  if (isVisible ()) write_settings ();
}

void Astro::closeEvent (QCloseEvent * e)
{
  write_settings ();
  e->ignore ();                 // do not allow closure by the window system
}

void Astro::read_settings ()
{
  SettingsGroup g (settings_, "Astro");
  ui_->doppler_widget->setVisible (ui_->cbDopplerTracking->isChecked ());
  m_DopplerMethod=settings_->value("DopplerMethod",0).toInt();
  switch (m_DopplerMethod)
    {
    case 0: ui_->rbNoDoppler->setChecked (true); break;
    case 1: ui_->rbFullTrack->setChecked (true); break;
    case 2: ui_->rbConstFreqOnMoon->setChecked (true); break;
    case 3: ui_->rbOwnEcho->setChecked (true); break;
	case 4: ui_->rbOnDxEcho->setChecked (true); break;
	case 5: ui_->rbCallDx->setChecked (true); break;
    }
  move (settings_->value ("window/pos", pos ()).toPoint ());
}

void Astro::write_settings ()
{
  SettingsGroup g (settings_, "Astro");
  //settings_->setValue ("DopplerTracking", ui_->cbDopplerTracking->isChecked ());
  settings_->setValue ("DopplerMethod",m_DopplerMethod);
  settings_->setValue ("window/pos", pos ());
}

auto Astro::astroUpdate(QDateTime const& t, QString const& mygrid, QString const& hisgrid, Frequency freq,
                        bool dx_is_self, bool bTx, bool no_tx_QSY, int TR_period) -> Correction
{
  Frequency freq_moon {freq};
  double azsun,elsun,azmoon,elmoon,azmoondx,elmoondx;
  double ramoon,decmoon,dgrd,poloffset,xnr,techo,width1,width2;
  int ntsky;
  QString date {t.date().toString("yyyy MMM dd").trimmed ()};
  QString utc {t.time().toString().trimmed ()};
  int nyear {t.date().year()};
  int month {t.date().month()};
  int nday {t.date().day()};
  int nhr {t.time().hour()};
  int nmin {t.time().minute()};
  double sec {t.time().second() + 0.001*t.time().msec()};
  double uth {nhr + nmin/60.0 + sec/3600.0};
  if(freq_moon < 1) freq_moon = 144000000;
  int nfreq {static_cast<int> (freq_moon / 1000000u)};
  double freq8 {static_cast<double> (freq_moon)};
  auto const& AzElFileName = QDir::toNativeSeparators (configuration_->azel_directory ().absoluteFilePath ("azel.dat"));
  auto const& jpleph = configuration_->data_dir ().absoluteFilePath ("JPLEPH");

  QString mygrid_padded {(mygrid + "      ").left (6)};
  QString hisgrid_padded {(hisgrid + "      ").left (6)};
  astrosub_(&nyear, &month, &nday, &uth, &freq8, mygrid_padded.toLatin1().constData(),
	    hisgrid_padded.toLatin1().constData(), &azsun, &elsun, &azmoon, &elmoon,
      &azmoondx, &elmoondx, &ntsky, &m_dop, &m_dop00, &ramoon, &decmoon,
	    &dgrd, &poloffset, &xnr, &techo, &width1, &width2, &bTx,
	    AzElFileName.toLatin1().constData(), jpleph.toLatin1().constData(), 6, 6,
	    AzElFileName.length(), jpleph.length());

  if(hisgrid_padded=="      ") {
    azmoondx=0.0;
    elmoondx=0.0;
    m_dop=0;
    width2=0.0;
  }
  QString message;
  {
    QTextStream out {&message};
    out << " " << date << "\n"
      "UTC:  " << utc << "\n"
      << fixed
      << qSetFieldWidth (6)
      << qSetRealNumberPrecision (1)
      << "Az:     " << azmoon << "\n"
      "El:     " << elmoon << "\n"
      "SelfDop:" << m_dop00 << "\n"
      "Width:  " << int(width1) << "\n"
      << qSetRealNumberPrecision (2)
      << "Delay:  " << techo << "\n"
      << qSetRealNumberPrecision (1)
      << "DxAz:   " << azmoondx << "\n"
      "DxEl:   " << elmoondx << "\n"
      "DxDop:  " << m_dop << "\n"
      "DxWid:  " << int(width2) << "\n"
      "Dec:    " << decmoon << "\n"
      "SunAz:  " << azsun << "\n"
      "SunEl:  " << elsun << "\n"
      "Freq:   " << nfreq << "\n";
    if(nfreq>=50) {                     //Suppress data not relevant below VHF
      out << "Tsky:   " << ntsky << "\n"
        "Dpol:   " << poloffset << "\n"
        "MNR:    " << xnr << "\n"
        "Dgrd:   " << dgrd;
    }
  }
  ui_->text_label->setText(message);

  Correction correction;
  if (ui_->cbDopplerTracking->isChecked ()) {
    switch (m_DopplerMethod)
      {
      case 1: // All Doppler correction done here; DX station stays at nominal dial frequency.
	  correction.rx =  m_dop;
	  break;
	  case 4: // All Doppler correction done here; DX station stays at nominal dial frequency. (Trial for OnDxEcho)
	  correction.rx =  m_dop;
	  break;
	  //case 5: // All Doppler correction done here; DX station stays at nominal dial frequency.

      case 3: // Both stations do full correction on Rx and none on Tx
        //correction.rx = dx_is_self ? m_dop00 : m_dop;
		correction.rx =  m_dop00; // Now always sets RX to *own* echo freq
        break;
	  case 2:
        // Doppler correction to constant frequency on Moon
        correction.rx = m_dop00 / 2;
        break;

      }
    switch (m_DopplerMethod)
      {
	  case 1: correction.tx = -correction.rx;
	  break;
	  case 2: correction.tx = -correction.rx;
	  break;
	  case 3: correction.tx = 0;
	  break;
	  case 4: // correction.tx = m_dop - m_dop00;

			  correction.tx = m_dx_two_way_dop - m_dop;
			  qDebug () << "correction.tx:" << correction.tx;
	  break;
	  case 5: correction.tx = - m_dop00;
	  break;
      }
    //if (3 != m_DopplerMethod || 4 != m_DopplerMethod) correction.tx = -correction.rx;

    if(dx_is_self && m_DopplerMethod == 1) correction.rx = 0;

    if (no_tx_QSY && 3 != m_DopplerMethod && 0 != m_DopplerMethod)
      {
       // calculate a single correction for transmit half way through
       // the period as a compromise for rigs that can't CAT QSY
       // while transmitting
        //
        // use a base time of (secs-since-epoch + 2) so as to be sure
        // we do the next period if we calculate just before it starts
        auto sec_since_epoch = t.toMSecsSinceEpoch () / 1000 + 2;
        auto target_sec = sec_since_epoch - sec_since_epoch % TR_period + TR_period / 2;
        auto target_date_time = QDateTime::fromMSecsSinceEpoch (target_sec * 1000);
        QString date {target_date_time.date().toString("yyyy MMM dd").trimmed ()};
        QString utc {target_date_time.time().toString().trimmed ()};
        int nyear {target_date_time.date().year()};
        int month {target_date_time.date().month()};
        int nday {target_date_time.date().day()};
        int nhr {target_date_time.time().hour()};
        int nmin {target_date_time.time().minute()};
        double sec {target_date_time.time().second() + 0.001*target_date_time.time().msec()};
        double uth {nhr + nmin/60.0 + sec/3600.0};
        astrosub_(&nyear, &month, &nday, &uth, &freq8, mygrid_padded.toLatin1().constData(),
                  hisgrid_padded.toLatin1().constData(), &azsun, &elsun, &azmoon, &elmoon,
                  &azmoondx, &elmoondx, &ntsky, &m_dop, &m_dop00, &ramoon, &decmoon,
                  &dgrd, &poloffset, &xnr, &techo, &width1, &width2, &bTx,
                  "", jpleph.toLatin1().constData(), 6, 6,
                  0, jpleph.length());
        FrequencyDelta offset {0};
        switch (m_DopplerMethod)
          {
          case 1:
            // All Doppler correction done here; DX station stays at nominal dial frequency.
            offset = dx_is_self ? m_dop00 : m_dop;
            break;

          case 2:
            // Doppler correction to constant frequency on Moon
           offset = m_dop00 / 2;
		    break;

		case 4:
            // Doppler correction for OnDxEcho
            offset = m_dop - m_dx_two_way_dop;
            break;

		//case 5: correction.tx = - m_dop00;
		case 5: offset = m_dop00;// version for _7
			break;

          }
        correction.tx = -offset;
		qDebug () << "correction.tx (no tx qsy):" << correction.tx;
      }
  }
  return correction;
}

void Astro::check_split ()
{
  if (doppler_tracking () && !configuration_->split_mode ())
    {
      MessageBox::warning_message (this, tr ("Doppler Tracking Error"),
                                   tr ("Split operating is required for Doppler tracking"),
                                   tr ("Go to \"Menu->File->Settings->Radio\" to enable split operation"));
      ui_->rbNoDoppler->click ();
    }
}

void Astro::on_rbFullTrack_clicked()
{
  m_DopplerMethod = 1;
  check_split ();
  Q_EMIT tracking_update ();
}

void Astro::on_rbOnDxEcho_clicked(bool checked)
{
  m_DopplerMethod = 4;
  check_split ();
  if (checked) {
	  m_dx_two_way_dop = 2 * (m_dop - (m_dop00/2));
	  qDebug () << "Starting Doppler:" << m_dx_two_way_dop;
  }
  Q_EMIT tracking_update ();
}

void Astro::on_rbOwnEcho_clicked()
{
  m_DopplerMethod = 3;
  check_split ();
  Q_EMIT tracking_update ();
}

void Astro::on_rbCallDx_clicked()
{
  m_DopplerMethod = 5;
  check_split ();
  Q_EMIT tracking_update ();
}

void Astro::on_rbConstFreqOnMoon_clicked()
{
  m_DopplerMethod = 2;
  check_split ();
  Q_EMIT tracking_update ();
}

void Astro::on_rbNoDoppler_clicked()
{
  m_DopplerMethod = 0;
  Q_EMIT tracking_update ();
}

bool Astro::doppler_tracking () const
{
  return ui_->cbDopplerTracking->isChecked () && m_DopplerMethod;
}

void Astro::on_cbDopplerTracking_toggled(bool)
{
  check_split ();
  Q_EMIT tracking_update ();
}

void Astro::nominal_frequency (Frequency rx, Frequency tx)
{
  ui_->sked_frequency_label->setText (Radio::pretty_frequency_MHz_string (rx));
  ui_->sked_tx_frequency_label->setText (Radio::pretty_frequency_MHz_string (tx));
}

void Astro::hideEvent (QHideEvent * e)
{
  Q_EMIT tracking_update ();
  QWidget::hideEvent (e);
}