code:
#include "FrequencyList.hpp"

#include <cstdlib>
#include <utility>
#include <limits>

#include <QMetaType>
#include <QAbstractTableModel>
#include <QString>
#include <QList>
#include <QListIterator>
#include <QVector>
#include <QStringList>
#include <QMimeData>
#include <QDataStream>
#include <QByteArray>
#include <QDebugStateSaver>

#include "Radio.hpp"
#include "Bands.hpp"
#include "pimpl_impl.hpp"

#include "moc_FrequencyList.cpp"

namespace
{
  FrequencyList_v2::FrequencyItems const default_frequency_list =
    {
      { 1842000, Modes::JS8, IARURegions::ALL}, // 2 above
      { 3578000, Modes::JS8, IARURegions::ALL}, // 5 above
      { 7078000, Modes::JS8, IARURegions::ALL}, // 4 above
      {10130000, Modes::JS8, IARURegions::ALL}, // 6 below
      {14078000, Modes::JS8, IARURegions::ALL}, // 4 above
      {18104000, Modes::JS8, IARURegions::ALL}, // 4 above
      {21078000, Modes::JS8, IARURegions::ALL}, // 4 above
      {24922000, Modes::JS8, IARURegions::ALL}, // 9 above
      {28078000, Modes::JS8, IARURegions::ALL}, // 4 above
      {50318000, Modes::JS8, IARURegions::ALL}, // 5 above
    };
}

#if !defined (QT_NO_DEBUG_STREAM)
QDebug operator << (QDebug debug, FrequencyList_v2::Item const& item)
{
  QDebugStateSaver saver {debug};
  debug.nospace () << "FrequencyItem("
                   << item.frequency_ << ", "
                   << item.region_ << ", "
                   << item.mode_ << ')';
  return debug;
}
#endif

QDataStream& operator << (QDataStream& os, FrequencyList_v2::Item const& item)
{
  return os << item.frequency_
            << item.mode_
            << item.region_;
}

QDataStream& operator >> (QDataStream& is, FrequencyList_v2::Item& item)
{
  return is >> item.frequency_
            >> item.mode_
            >> item.region_;
}

class FrequencyList_v2::impl final
  : public QAbstractTableModel
{
public:
  impl (Bands const * bands, QObject * parent)
    : QAbstractTableModel {parent}
    , bands_ {bands}
    , region_filter_ {IARURegions::ALL}
    , mode_filter_ {Modes::ALL}
  {
  }

  FrequencyItems frequency_list (FrequencyItems);
  QModelIndex add (Item);
  void add (FrequencyItems);

  // Implement the QAbstractTableModel interface
  int rowCount (QModelIndex const& parent = QModelIndex {}) const override;
  int columnCount (QModelIndex const& parent = QModelIndex {}) const override;
  Qt::ItemFlags flags (QModelIndex const& = QModelIndex {}) const override;
  QVariant data (QModelIndex const&, int role = Qt::DisplayRole) const override;
  bool setData (QModelIndex const&, QVariant const& value, int role = Qt::EditRole) override;
  QVariant headerData (int section, Qt::Orientation, int = Qt::DisplayRole) const override;
  bool removeRows (int row, int count, QModelIndex const& parent = QModelIndex {}) override;
  bool insertRows (int row, int count, QModelIndex const& parent = QModelIndex {}) override;
  QStringList mimeTypes () const override;
  QMimeData * mimeData (QModelIndexList const&) const override;

  static int constexpr num_cols {SENTINAL};
  static auto constexpr mime_type = "application/wsjt.Frequencies";

  Bands const * bands_;
  FrequencyItems frequency_list_;
  Region region_filter_;
  Mode mode_filter_;
};

FrequencyList_v2::FrequencyList_v2 (Bands const * bands, QObject * parent)
  : QSortFilterProxyModel {parent}
  , m_ {bands, parent}
{
  setSourceModel (&*m_);
  setSortRole (SortRole);
}

FrequencyList_v2::~FrequencyList_v2 ()
{
}

auto FrequencyList_v2::frequency_list (FrequencyItems frequency_list) -> FrequencyItems
{
  return m_->frequency_list (frequency_list);
}

auto FrequencyList_v2::frequency_list () const -> FrequencyItems const&
{
  return m_->frequency_list_;
}

auto FrequencyList_v2::frequency_list (QModelIndexList const& model_index_list) const -> FrequencyItems
{
  FrequencyItems list;
  Q_FOREACH (auto const& index, model_index_list)
    {
      list << m_->frequency_list_[mapToSource (index).row ()];
    }
  return list;
}

void FrequencyList_v2::frequency_list_merge (FrequencyItems const& items)
{
  m_->add (items);
}

int FrequencyList_v2::best_working_frequency (Frequency f) const
{
  int result {-1};
  auto const& target_band = m_->bands_->find (f);
  if (!target_band.isEmpty ())
    {
      Radio::FrequencyDelta delta {std::numeric_limits<Radio::FrequencyDelta>::max ()};
      // find a frequency in the same band that is allowed
      for (int row = 0; row < rowCount (); ++row)
        {
          auto const& source_row = mapToSource (index (row, 0)).row ();
          auto const& candidate_frequency = m_->frequency_list_[source_row].frequency_;
          auto const& band = m_->bands_->find (candidate_frequency);
          if (band == target_band)
            {
              // take closest band match
              Radio::FrequencyDelta new_delta = f - candidate_frequency;
              if (std::abs (new_delta) < std::abs (delta))
                {
                  delta = new_delta;
                  result = row;
                }
            }
        }
    }
  return result;
}

int FrequencyList_v2::best_working_frequency (QString const& target_band) const
{
  int result {-1};
  if (!target_band.isEmpty ())
    {
      // find a frequency in the same band that is allowed
      for (int row = 0; row < rowCount (); ++row)
        {
          auto const& source_row = mapToSource (index (row, 0)).row ();
          auto const& band = m_->bands_->find (m_->frequency_list_[source_row].frequency_);
          if (band == target_band)
            {
              return row;
            }
        }
    }
  return result;
}

void FrequencyList_v2::reset_to_defaults ()
{
  m_->frequency_list (default_frequency_list);
}

QModelIndex FrequencyList_v2::add (Item f)
{
  return mapFromSource (m_->add (f));
}

bool FrequencyList_v2::remove (Item f)
{
  auto row = m_->frequency_list_.indexOf (f);

  if (0 > row)
    {
      return false;
    }

  return m_->removeRow (row);
}

namespace
{
  bool row_is_higher (QModelIndex const& lhs, QModelIndex const& rhs)
  {
    return lhs.row () > rhs.row ();
  }
}

bool FrequencyList_v2::removeDisjointRows (QModelIndexList rows)
{
  bool result {true};

  // We must work with source model indexes because we don't want row
  // removes to invalidate model indexes we haven't yet processed. We
  // achieve that by processing them in decending row order.
  for (int r = 0; r < rows.size (); ++r)
    {
      rows[r] = mapToSource (rows[r]);
    }

  // reverse sort by row
  qSort (rows.begin (), rows.end (), row_is_higher);
  Q_FOREACH (auto index, rows)
    {
      if (result && !m_->removeRow (index.row ()))
        {
          result = false;
        }
    }
  return result;
}

void FrequencyList_v2::filter (Region region, Mode mode)
{
  m_->region_filter_ = region;
  m_->mode_filter_ = mode;
  invalidateFilter ();
}

bool FrequencyList_v2::filterAcceptsRow (int source_row, QModelIndex const& /* parent */) const
{
  bool result {true};
  auto const& item = m_->frequency_list_[source_row];
  if (m_->region_filter_ != IARURegions::ALL)
    {
      result = IARURegions::ALL == item.region_ || m_->region_filter_ == item.region_;
    }
  if (result && m_->mode_filter_ != Modes::ALL)
    {
      result = Modes::ALL == item.mode_ || m_->mode_filter_ == item.mode_;
    }
  return result;
}

auto FrequencyList_v2::impl::frequency_list (FrequencyItems frequency_list) -> FrequencyItems
{
  beginResetModel ();
  std::swap (frequency_list_, frequency_list);
  endResetModel ();
  return frequency_list;
}

QModelIndex FrequencyList_v2::impl::add (Item f)
{
  // Any Frequency that isn't in the list may be added
  if (!frequency_list_.contains (f))
    {
      auto row = frequency_list_.size ();

      beginInsertRows (QModelIndex {}, row, row);
      frequency_list_.append (f);
      endInsertRows ();

      return index (row, 0);
    }
  return QModelIndex {};
}

void FrequencyList_v2::impl::add (FrequencyItems items)
{
  // Any Frequency that isn't in the list may be added
  for (auto p = items.begin (); p != items.end ();)
    {
      if (frequency_list_.contains (*p))
        {
          p = items.erase (p);
        }
      else
        {
          ++p;
        }
    }

  if (items.size ())
    {
      auto row = frequency_list_.size ();

      beginInsertRows (QModelIndex {}, row, row + items.size () - 1);
      frequency_list_.append (items);
      endInsertRows ();
    }
}

int FrequencyList_v2::impl::rowCount (QModelIndex const& parent) const
{
  return parent.isValid () ? 0 : frequency_list_.size ();
}

int FrequencyList_v2::impl::columnCount (QModelIndex const& parent) const
{
  return parent.isValid () ? 0 : num_cols;
}

Qt::ItemFlags FrequencyList_v2::impl::flags (QModelIndex const& index) const
{
  auto result = QAbstractTableModel::flags (index) | Qt::ItemIsDropEnabled;
  auto row = index.row ();
  auto column = index.column ();
  if (index.isValid ()
      && row < frequency_list_.size ()
      && column < num_cols)
    {
      if (frequency_mhz_column != column)
        {
          result |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
        }
    }
  return result;
}

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

  auto const& row = index.row ();
  auto const& column = index.column ();

  if (index.isValid ()
      && row < frequency_list_.size ()
      && column < num_cols)
    {
      auto const& frequency_item = frequency_list_.at (row);
      switch (column)
        {
        case region_column:
          switch (role)
            {
            case SortRole:
            case Qt::DisplayRole:
            case Qt::EditRole:
            case Qt::AccessibleTextRole:
              item = IARURegions::name (frequency_item.region_);
              break;

            case Qt::ToolTipRole:
            case Qt::AccessibleDescriptionRole:
              item = tr ("IARU Region");
              break;

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

        case mode_column:
          switch (role)
            {
            case SortRole:
            case Qt::DisplayRole:
            case Qt::EditRole:
            case Qt::AccessibleTextRole:
              item = Modes::name (frequency_item.mode_);
              break;

            case Qt::ToolTipRole:
            case Qt::AccessibleDescriptionRole:
              item = tr ("Mode");
              break;

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

        case frequency_column:
          switch (role)
            {
            case SortRole:
            case Qt::EditRole:
            case Qt::AccessibleTextRole:
              item = frequency_item.frequency_;
              break;

            case Qt::DisplayRole:
              {
                auto const& band = bands_->find (frequency_item.frequency_);
                item = Radio::pretty_frequency_MHz_string (frequency_item.frequency_)
                  + " MHz (" + (band.isEmpty () ? "OOB" : band) + ')';
              }
              break;

            case Qt::ToolTipRole:
            case Qt::AccessibleDescriptionRole:
              item = tr ("Frequency");
              break;

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

        case frequency_mhz_column:
          switch (role)
            {
            case Qt::EditRole:
            case Qt::AccessibleTextRole:
              item = Radio::frequency_MHz_string (frequency_item.frequency_);
              break;

            case Qt::DisplayRole:
              {
                auto const& band = bands_->find (frequency_item.frequency_);
                item = Radio::pretty_frequency_MHz_string (frequency_item.frequency_)
                  + " MHz (" + (band.isEmpty () ? "OOB" : band) + ')';
              }
              break;

            case Qt::ToolTipRole:
            case Qt::AccessibleDescriptionRole:
              item = tr ("Frequency (MHz)");
              break;

            case Qt::TextAlignmentRole:
              item = Qt::AlignRight + Qt::AlignVCenter;
              break;
            }
          break;
        }
    }
  return item;
}

bool FrequencyList_v2::impl::setData (QModelIndex const& model_index, QVariant const& value, int role)
{
  bool changed {false};

  auto const& row = model_index.row ();
  if (model_index.isValid ()
      && Qt::EditRole == role
      && row < frequency_list_.size ())
    {
      QVector<int> roles;
      roles << role;

      auto& item = frequency_list_[row];
      switch (model_index.column ())
        {
        case region_column:
          {
            auto region = IARURegions::value (value.toString ());
            if (region != item.region_)
              {
                item.region_ = region;
                Q_EMIT dataChanged (model_index, model_index, roles);
                changed = true;
              }
            }
          break;

        case mode_column:
          {
            auto mode = Modes::value (value.toString ());
            if (mode != item.mode_)
              {
                item.mode_ = mode;
                Q_EMIT dataChanged (model_index, model_index, roles);
                changed = true;
              }
          }
          break;

        case frequency_column:
          if (value.canConvert<Frequency> ())
            {
              Radio::Frequency frequency {qvariant_cast <Radio::Frequency> (value)};
              if (frequency != item.frequency_)
                {
                  item.frequency_ = frequency;
                  // mark derived column (1) changed as well
                  Q_EMIT dataChanged (index (model_index.row (), 1), model_index, roles);
                  changed = true;
                }
            }
          break;
        }
    }

  return changed;
}

QVariant FrequencyList_v2::impl::headerData (int section, Qt::Orientation orientation, int role) const
{
  QVariant header;
  if (Qt::DisplayRole == role
      && Qt::Horizontal == orientation
      && section < num_cols)
    {
      switch (section)
        {
        case region_column: header = tr ("IARU Region"); break;
        case mode_column: header = tr ("Mode"); break;
        case frequency_column: header = tr ("Frequency"); break;
        case frequency_mhz_column: header = tr ("Frequency (MHz)"); break;
        }
    }
  else
    {
      header = QAbstractTableModel::headerData (section, orientation, role);
    }
  return header;
}

bool FrequencyList_v2::impl::removeRows (int row, int count, QModelIndex const& parent)
{
  if (0 < count && (row + count) <= rowCount (parent))
    {
      beginRemoveRows (parent, row, row + count - 1);
      for (auto r = 0; r < count; ++r)
        {
          frequency_list_.removeAt (row);
        }
      endRemoveRows ();
      return true;
    }
  return false;
}

bool FrequencyList_v2::impl::insertRows (int row, int count, QModelIndex const& parent)
{
  if (0 < count)
    {
      beginInsertRows (parent, row, row + count - 1);
      for (auto r = 0; r < count; ++r)
        {
          frequency_list_.insert (row, Item {0, Mode::ALL, IARURegions::ALL});
        }
      endInsertRows ();
      return true;
    }
  return false;
}

QStringList FrequencyList_v2::impl::mimeTypes () const
{
  QStringList types;
  types << mime_type;
  return types;
}

QMimeData * FrequencyList_v2::impl::mimeData (QModelIndexList const& items) const
{
  QMimeData * mime_data = new QMimeData {};
  QByteArray encoded_data;
  QDataStream stream {&encoded_data, QIODevice::WriteOnly};

  Q_FOREACH (auto const& item, items)
    {
      if (item.isValid () && frequency_column == item.column ())
        {
          stream << frequency_list_.at (item.row ());
        }
    }

  mime_data->setData (mime_type, encoded_data);
  return mime_data;
}

auto FrequencyList_v2::const_iterator::operator * () const -> Item const&
{
  return parent_->frequency_list ().at(parent_->mapToSource (parent_->index (row_, 0)).row ());
}

auto FrequencyList_v2::const_iterator::operator -> () const -> Item const *
{
  return &parent_->frequency_list ().at(parent_->mapToSource (parent_->index (row_, 0)).row ());
}

bool FrequencyList_v2::const_iterator::operator != (const_iterator const& rhs) const
{
  return parent_ != rhs.parent_ || row_ != rhs.row_;
}

bool FrequencyList_v2::const_iterator::operator == (const_iterator const& rhs) const
{
  return parent_ == rhs.parent_ && row_ == rhs.row_;
}

auto FrequencyList_v2::const_iterator::operator ++ () -> const_iterator&
{
  ++row_;
  return *this;
}

auto FrequencyList_v2::begin () const -> const_iterator
{
  return const_iterator (this, 0);
}

auto FrequencyList_v2::end () const -> const_iterator
{
  return const_iterator (this, rowCount ());
}

auto FrequencyList_v2::find (Frequency f) const -> const_iterator
{
  int row {0};
  for (; row < rowCount (); ++row)
    {
      if (m_->frequency_list_[mapToSource (index (row, 0)).row ()].frequency_ == f)
        {
          break;
        }
    }
  return const_iterator (this, row);
}

auto FrequencyList_v2::filtered_bands () const -> BandSet
{
  BandSet result;
  for (auto const& item : *this)
    {
      result << m_->bands_->find (item.frequency_);
    }
  return result;
}

auto FrequencyList_v2::all_bands (Region region, Mode mode) const -> BandSet
{
  BandSet result;
  for (auto const& item : m_->frequency_list_)
    {
      if (region == IARURegions::ALL || item.region_ == region
          || mode == Modes::ALL || item.mode_ == mode)
        {
          result << m_->bands_->find (item.frequency_);
        }
    }
  return result;
}

//
// Obsolete version of FrequencyList no longer used but needed to
// allow loading and saving of old settings contents without damage
//
QDataStream& operator << (QDataStream& os, FrequencyList::Item const& item)
{
  return os << item.frequency_
            << item.mode_;
}

QDataStream& operator >> (QDataStream& is, FrequencyList::Item& item)
{
  return is >> item.frequency_
            >> item.mode_;
}


code:
#ifndef FREQUENCY_LIST_HPP__
#define FREQUENCY_LIST_HPP__

#include "pimpl_h.hpp"

#include <QList>
#include <QSortFilterProxyModel>

#include "Radio.hpp"
#include "IARURegions.hpp"
#include "Modes.hpp"

class Bands;

//
// Class FrequencyList_v2
//
//  Encapsulates a  collection of  frequencies with  associated modes.
//  The implementation is a table  containing the list of IARU region,
//  Frequency and  mode tuples which  are editable. A third  column is
//  modeled in the  model which is an  immutable double representation
//  of the corresponding Frequency item scaled to mega-Hertz.
//
//  The  list  is ordered.   A  filter  on  IARU  region and  mode  is
//  available  and is  set by  the filter(Region,  Mode) method.   The
//  Region value IARURegions::ALL and the Mode value Modes::ALL may be
//  optionally given which passes all rows in the filtered column.
//
// Responsibilities
//
//  Stores internally a list of  unique region, frequency mode tuples.
//  Provides methods to  add and delete list  elements. Provides range
//  iterators for a filtered view of the underlying table.
//
// Collaborations
//
//  Implements the QSortFilterProxyModel interface  for a list of spot
//  frequencies.
//
class FrequencyList_v2 final
  : public QSortFilterProxyModel
{
  Q_OBJECT;

public:
  using Region = IARURegions::Region;
  using Frequency = Radio::Frequency;
  using Mode = Modes::Mode;

  struct Item
  {
    Frequency frequency_;
    Mode mode_;
    Region region_;
  };
  using FrequencyItems = QList<Item>;
  using BandSet = QSet<QString>;

  enum Column {region_column, mode_column, frequency_column, frequency_mhz_column, SENTINAL};

  // an iterator that meets the requirements of the C++ for range statement
  class const_iterator
  {
  public:
    const_iterator (FrequencyList_v2 const * parent, int row)
      : parent_ {parent}
      , row_ {row}
    {
    }

    Item const& operator * () const;
    Item const * operator -> () const;
    bool operator != (const_iterator const&) const;
    bool operator == (const_iterator const&) const;
    const_iterator& operator ++ ();

  private:
    FrequencyList_v2 const * parent_;
    int row_;
  };

  explicit FrequencyList_v2 (Bands const *, QObject * parent = nullptr);
  ~FrequencyList_v2 ();

  // Load and store underlying items
  FrequencyItems frequency_list (FrequencyItems);
  FrequencyItems const& frequency_list () const;
  FrequencyItems frequency_list (QModelIndexList const&) const;
  void frequency_list_merge (FrequencyItems const&);

  // Iterators for the sorted and filtered items
  //
  // Note that these iterators are on the final sorted and filtered
  // rows, if you need to access the underlying unfiltered and
  // unsorted frequencies then use the frequency_list() member to
  // access the underlying list of rows.
  const_iterator begin () const;
  const_iterator end () const;

  // Find a row with a given frequency
  const_iterator find (Frequency) const;

  // Bands of the frequencies
  BandSet all_bands (Region = IARURegions::ALL, Mode = Modes::ALL) const;
  BandSet filtered_bands () const;

  // Find the row of the nearest best working frequency given a
  // frequency. Returns -1 if no suitable working frequency is found
  // in the list.
  int best_working_frequency (Frequency) const;

  // Find the row  of the nearest best working frequency  given a band
  // name. Returns -1 if no suitable working frequency is found in the
  // list.
  int best_working_frequency (QString const& band) const;

  // Set filter
  Q_SLOT void filter (Region, Mode);

  // Reset
  Q_SLOT void reset_to_defaults ();

  // Model API
  QModelIndex add (Item);
  bool remove (Item);
  bool removeDisjointRows (QModelIndexList);

  // Proxy API
  bool filterAcceptsRow (int source_row, QModelIndex const& parent) const override;

  // Custom roles.
  static int constexpr SortRole = Qt::UserRole;

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

inline
bool operator == (FrequencyList_v2::Item const& lhs, FrequencyList_v2::Item const& rhs)
{
  return
    lhs.frequency_ == rhs.frequency_
    && lhs.region_ == rhs.region_
    && lhs.mode_ == rhs.mode_;
}

QDataStream& operator << (QDataStream&, FrequencyList_v2::Item const&);
QDataStream& operator >> (QDataStream&, FrequencyList_v2::Item&);

#if !defined (QT_NO_DEBUG_STREAM)
QDebug operator << (QDebug, FrequencyList_v2::Item const&);
#endif

Q_DECLARE_METATYPE (FrequencyList_v2::Item);
Q_DECLARE_METATYPE (FrequencyList_v2::FrequencyItems);

//
// Obsolete version of FrequencyList no longer used but needed to
// allow loading and saving of old settings contents without damage
//
class FrequencyList final
{
public:
  using Frequency = Radio::Frequency;
  using Mode = Modes::Mode;

  struct Item
  {
    Frequency frequency_;
    Mode mode_;
  };
  using FrequencyItems = QList<Item>;

private:
  FrequencyItems frequency_list_;
};

QDataStream& operator << (QDataStream&, FrequencyList::Item const&);
QDataStream& operator >> (QDataStream&, FrequencyList::Item&);

Q_DECLARE_METATYPE (FrequencyList::Item);
Q_DECLARE_METATYPE (FrequencyList::FrequencyItems);

#endif

license: mit


code:
#include "GetUserId.hpp"

#include <stdexcept>

#include <QApplication>
#include <QString>
#include <QDialog>
#include <QLineEdit>
#include <QRegExpValidator>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QVBoxLayout>

//
// Dialog to get callsign
//
class CallsignDialog final
  : public QDialog
{
  Q_OBJECT;

private:
  Q_DISABLE_COPY (CallsignDialog);

public:
  explicit CallsignDialog (QWidget * parent = nullptr)
    : QDialog {parent}
  {
    setWindowTitle (QApplication::applicationName () + " - " + tr ("Callsign"));

    callsign_.setValidator (new QRegExpValidator {QRegExp {"[A-Za-z0-9]+"}, this});

    auto form_layout = new QFormLayout ();
    form_layout->addRow ("&Callsign:", &callsign_);

    auto main_layout = new QVBoxLayout (this);
    main_layout->addLayout (form_layout);

    auto button_box = new QDialogButtonBox {QDialogButtonBox::Ok | QDialogButtonBox::Cancel};
    main_layout->addWidget (button_box);

    connect (button_box, &QDialogButtonBox::accepted, this, &CallsignDialog::accept);
    connect (button_box, &QDialogButtonBox::rejected, this, &CallsignDialog::reject);
  }

  QString callsign () const {return callsign_.text ();}

private:
  QLineEdit callsign_;
};

#include "GetUserId.moc"

QString get_user_id ()
{
  // get the users callsign so we can use it to persist the
  // settings and log file against a unique tag
  QString id;
  {
    CallsignDialog dialog;
    while (id.isEmpty ())
      {
        if (QDialog::Accepted == dialog.exec ())
          {
            id = dialog.callsign ().toUpper ();
          }
        else
          {
            throw std::runtime_error ("Callsign required");
          }
      }
  }

  return id;
}


code:
#ifndef GETUSERID_HPP_
#define GETUSERID_HPP_

#include <QString>

QString get_user_id ();

#endif


code:
#include "HRDTransceiver.hpp"

#include <QHostAddress>
#include <QByteArray>
#include <QRegExp>
#include <QTcpSocket>
#include <QThread>
#include <QStandardPaths>
#include <QDir>

#include "NetworkServerLookup.hpp"

namespace
{
  char const * const HRD_transceiver_name = "Ham Radio Deluxe";

  // some commands require a settling time, particularly "RX A" and
  // "RX B" on the Yaesu FTdx3000.
  int constexpr yaesu_delay {250};
}

void HRDTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id)
{
  (*registry)[HRD_transceiver_name] = TransceiverFactory::Capabilities (id, TransceiverFactory::Capabilities::network, true, true /* maybe */);
}

struct HRDMessage
{
  // Placement style new overload for outgoing messages that does the
  // construction too.
  static void * operator new (size_t size, QString const& payload)
  {
    size += sizeof (QChar) * (payload.size () + 1); // space for terminator too
    HRDMessage * storage (reinterpret_cast<HRDMessage *> (new char[size]));
    storage->size_ = size ;
    ushort const * pl (payload.utf16 ());
    qCopy (pl, pl + payload.size () + 1, storage->payload_); // copy terminator too
    storage->magic_1_ = magic_1_value_;
    storage->magic_2_ = magic_2_value_;
    storage->checksum_ = 0;
    return storage;
  }

  // Placement style new overload for incoming messages that does the
  // construction too.
  //
  // No memory allocation here.
  static void * operator new (size_t /* size */, QByteArray const& message)
  {
    // Nasty const_cast here to avoid copying the message buffer.
    return const_cast<HRDMessage *> (reinterpret_cast<HRDMessage const *> (message.data ()));
  }

  void operator delete (void * p, size_t)
  {
    delete [] reinterpret_cast<char *> (p); // Mirror allocation in operator new above.
  }

  quint32 size_;
  qint32 magic_1_;
  qint32 magic_2_;
  qint32 checksum_;            // Apparently not used.
  QChar payload_[0];           // UTF-16 (which is wchar_t on Windows)

  static qint32 constexpr magic_1_value_ = 0x1234ABCD;
  static qint32 constexpr magic_2_value_ = 0xABCD1234;
};

HRDTransceiver::HRDTransceiver (std::unique_ptr<TransceiverBase> wrapped
                                , QString const& server
                                , bool use_for_ptt
                                , TransceiverFactory::TXAudioSource audio_source
                                , int poll_interval
                                , QObject * parent)
  : PollingTransceiver {poll_interval, parent}
  , wrapped_ {std::move (wrapped)}
  , use_for_ptt_ {use_for_ptt}
  , audio_source_ {audio_source}
  , server_ {server}
  , hrd_ {0}
  , protocol_ {none}
  , current_radio_ {0}
  , vfo_count_ {0}
  , vfo_A_button_ {-1}
  , vfo_B_button_ {-1}
  , vfo_toggle_button_ {-1}
  , mode_A_dropdown_ {-1}
  , mode_B_dropdown_ {-1}
  , data_mode_toggle_button_ {-1}
  , data_mode_on_button_ {-1}
  , data_mode_off_button_ {-1}
  , data_mode_dropdown_ {-1}
  , split_mode_button_ {-1}
  , split_mode_dropdown_ {-1}
  , split_mode_dropdown_write_only_ {false}
  , split_off_button_ {-1}
  , tx_A_button_ {-1}
  , tx_B_button_ {-1}
  , rx_A_button_ {-1}
  , rx_B_button_ {-1}
  , receiver_dropdown_ {-1}
  , ptt_button_ {-1}
  , alt_ptt_button_ {-1}
  , reversed_ {false}
{
}

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

  auto server_details = network_server_lookup (server_, 7809u);
  if (!hrd_)
    {
      hrd_ = new QTcpSocket {this}; // QObject takes ownership
    }
  hrd_->connectToHost (std::get<0> (server_details), std::get<1> (server_details));
  if (!hrd_->waitForConnected ())
    {
      TRACE_CAT ("HRDTransceiver", "failed to connect:" <<  hrd_->errorString ());
      throw error {tr ("Failed to connect to Ham Radio Deluxe\n") + hrd_->errorString ()};
    }

  if (none == protocol_)
    {
      try
        {
          protocol_ = v5;	// try this first (works for v6 too)
          send_command ("get context", false, false);
        }
      catch (error const&)
        {
          protocol_ = none;
        }
    }

  if (none == protocol_)
    {
      hrd_->close ();

      protocol_ = v4;		// try again with older protocol
      hrd_->connectToHost (std::get<0> (server_details), std::get<1> (server_details));
      if (!hrd_->waitForConnected ())
        {
          TRACE_CAT ("HRDTransceiver", "failed to connect:" <<  hrd_->errorString ());
          throw error {tr ("Failed to connect to Ham Radio Deluxe\n") + hrd_->errorString ()};
        }

      send_command ("get context", false, false);
    }

  QFile HRD_info_file {QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}.absoluteFilePath ("HRD Interface Information.txt")};
  if (!HRD_info_file.open (QFile::WriteOnly | QFile::Text | QFile::Truncate))
    {
      throw error {tr ("Failed to open file \"%1\": %2.").arg (HRD_info_file.fileName ()).arg (HRD_info_file.errorString ())};
    }
  QTextStream HRD_info {&HRD_info_file};

  auto id = send_command ("get id", false, false);
  auto version = send_command ("get version", false, false);

  TRACE_CAT ("HRDTransceiver", "Id:" << id << "Version:" << version);
  HRD_info << "Id: " << id << "\n";
  HRD_info << "Version: " << version << "\n";

  auto radios = send_command ("get radios", false, false).trimmed ().split (',', QString::SkipEmptyParts);
  if (radios.isEmpty ())
    {
      TRACE_CAT ("HRDTransceiver", "no rig found");
      throw error {tr ("Ham Radio Deluxe: no rig found")};
    }

  HRD_info << "Radios:\n";
  Q_FOREACH (auto const& radio, radios)
    {
      HRD_info << "\t" << radio << "\n";
      auto entries = radio.trimmed ().split (':', QString::SkipEmptyParts);
      radios_.push_back (std::forward_as_tuple (entries[0].toUInt (), entries[1]));
    }

#if WSJT_TRACE_CAT
  TRACE_CAT ("HRDTransceiver", "radios:-");
  Q_FOREACH (auto const& radio, radios_)
    {
      TRACE_CAT ("HRDTransceiver", "\t[" << std::get<0> (radio) << "] " << std::get<1> (radio));
    }
#endif

  auto current_radio_name = send_command ("get radio", false, false, true);
  HRD_info << "Current radio: " << current_radio_name << "\n";
  if (current_radio_name.isEmpty ())
    {
      TRACE_CAT ("HRDTransceiver", "no rig found");
      throw error {tr ("Ham Radio Deluxe: no rig found")};
    }

  vfo_count_ = send_command ("get vfo-count").toUInt ();
  HRD_info << "VFO count: " << vfo_count_ << "\n";
  TRACE_CAT ("HRDTransceiver", "vfo count:" << vfo_count_);

  buttons_ = send_command ("get buttons").trimmed ().split (',', QString::SkipEmptyParts).replaceInStrings (" ", "~");
  TRACE_CAT ("HRDTransceiver", "HRD Buttons: " << buttons_);
  HRD_info << "Buttons: {" << buttons_.join (", ") << "}\n";

  dropdown_names_ = send_command ("get dropdowns").trimmed ().split (',', QString::SkipEmptyParts);
  TRACE_CAT ("HRDTransceiver", "Dropdowns:");
  HRD_info << "Dropdowns:\n";
  Q_FOREACH (auto const& dd, dropdown_names_)
    {
      auto selections = send_command ("get dropdown-list {" + dd + "}").trimmed ().split (',');
      TRACE_CAT ("HRDTransceiver", "\t" << dd << ": {" << selections.join (", ") << "}");
      HRD_info << "\t" << dd << ": {" << selections.join (", ") << "}\n";
      dropdowns_[dd] = selections;
    }

  slider_names_ = send_command ("get sliders").trimmed ().split (',', QString::SkipEmptyParts).replaceInStrings (" ", "~");
  TRACE_CAT ("HRDTransceiver", "Sliders:-");
  HRD_info << "Sliders:\n";
  Q_FOREACH (auto const& s, slider_names_)
    {
      auto range = send_command ("get slider-range " + current_radio_name + " " + s).trimmed ().split (',', QString::SkipEmptyParts);
      TRACE_CAT ("HRDTransceiver", "\t" << s << ": {" << range.join (", ") << "}");
      HRD_info << "\t" << s << ": {" << range.join (", ") << "}\n";
      sliders_[s] = range;
    }

  // set RX VFO
  rx_A_button_ = find_button (QRegExp ("^(RX~A)$"));
  rx_B_button_ = find_button (QRegExp ("^(RX~B)$"));

  // select VFO (sometime set as well)
  vfo_A_button_ = find_button (QRegExp ("^(VFO~A|Main)$"));
  vfo_B_button_ = find_button (QRegExp ("^(VFO~B|Sub)$"));

  vfo_toggle_button_ = find_button (QRegExp ("^(A~/~B)$"));

  split_mode_button_ = find_button (QRegExp ("^(Spl~On|Spl_On|Split|Split~On)$"));
  split_off_button_ = find_button (QRegExp ("^(Spl~Off|Spl_Off|Split~Off)$"));

  if ((split_mode_dropdown_ = find_dropdown (QRegExp ("^(Split)$"))) >= 0)
    {
      split_mode_dropdown_selection_on_ = find_dropdown_selection (split_mode_dropdown_, QRegExp ("^(On)$"));
      split_mode_dropdown_selection_off_ = find_dropdown_selection (split_mode_dropdown_, QRegExp ("^(Off)$"));
    }
  else if ((receiver_dropdown_ = find_dropdown (QRegExp ("^Receiver$"))) >= 0)
    {
      rx_A_selection_ = find_dropdown_selection (receiver_dropdown_, QRegExp ("^(RX / Off)$"));
      rx_B_selection_ = find_dropdown_selection (receiver_dropdown_, QRegExp ("^(Mute / RX)$"));
    }

  tx_A_button_ = find_button (QRegExp ("^(TX~main|TX~-~A|TX~A)$"));
  tx_B_button_ = find_button (QRegExp ("^(TX~sub|TX~-~B|TX~B)$"));

  if ((mode_A_dropdown_ = find_dropdown (QRegExp ("^(Main Mode|Mode|Mode A)$"))) >= 0)
    {
      map_modes (mode_A_dropdown_, &mode_A_map_);
    }
  else
    {
      Q_ASSERT (mode_A_dropdown_ <= 0);
    }

  if ((mode_B_dropdown_ = find_dropdown (QRegExp ("^(Sub Mode|Mode B)$"))) >= 0)
    {
      map_modes (mode_B_dropdown_, &mode_B_map_);
    }

  // Can't do this with ^Data$ as the button name because some Kenwood
  // rigs have a "Data" button which is for turning the DSP on and off
  // so we must filter by rig name as well
  if (current_radio_name.startsWith ("IC")) // works with Icom transceivers
    {
      data_mode_toggle_button_ = find_button (QRegExp ("^(Data)$"));
    }

  data_mode_on_button_ = find_button (QRegExp ("^(DATA-ON\\(mid\\))$"));
  data_mode_off_button_ = find_button (QRegExp ("^(DATA-OFF)$"));

  // Some newer Icoms have a Data drop down with (Off,On,D1,D2,D3)
  // Some newer Icoms have a Data drop down with (Off,D1,D2,D3)
  // Some newer Icoms (IC-7300) have a Data drop down with
  // (Off,,D1-FIL1,D1-FIL2,D1-FIL3) the missing value counts as an
  // index value - I think it is a drop down separator line - this
  // appears to be an HRD defect and we cannot work around it
  if ((data_mode_dropdown_ = find_dropdown (QRegExp ("^(Data)$"))) >= 0)
    {
      data_mode_dropdown_selection_on_ = find_dropdown_selection (data_mode_dropdown_, QRegExp ("^(On|Data1|D1|D1-FIL1)$"));
      data_mode_dropdown_selection_off_ = find_dropdown_selection (data_mode_dropdown_, QRegExp ("^(Off)$"));
    }

  ptt_button_ = find_button (QRegExp ("^(TX)$"));
  alt_ptt_button_ = find_button (QRegExp ("^(TX~Alt|TX~Data)$"));

  if (vfo_count_ == 1 && ((vfo_B_button_ >= 0 && vfo_A_button_ >= 0) || vfo_toggle_button_ >= 0))
    {
      // put the rig into a known state for tricky cases like Icoms

      auto f = send_command ("get frequency").toUInt ();
      auto m = get_data_mode (lookup_mode (get_dropdown (mode_A_dropdown_), mode_A_map_));
      set_button (vfo_B_button_ >= 0 ? vfo_B_button_ : vfo_toggle_button_);
      auto fo = send_command ("get frequency").toUInt ();
      update_other_frequency (fo);
      auto mo = get_data_mode (lookup_mode (get_dropdown (mode_A_dropdown_), mode_A_map_));
      set_button (vfo_A_button_ >= 0 ? vfo_A_button_ : vfo_toggle_button_);
      if (f != fo || m != mo)
        {
          // we must have started with A/MAIN
          update_rx_frequency (f);
          update_mode (m);
        }
      else
        {
          update_rx_frequency (send_command ("get frequency").toUInt ());
          update_mode (get_data_mode (lookup_mode (get_dropdown (mode_A_dropdown_), mode_A_map_)));
        }
    }

  int resolution {0};
  auto f = send_command ("get frequency").toUInt ();
  if (f && !(f % 10))
    {
      auto test_frequency = f - f % 100 + 55;
      send_simple_command ("set frequency-hz " + QString::number (test_frequency));
      auto new_frequency = send_command ("get frequency").toUInt ();
      switch (static_cast<Radio::FrequencyDelta> (new_frequency - test_frequency))
        {
        case -5: resolution = -1; break;  // 10Hz truncated
        case 5: resolution = 1; break;    // 10Hz rounded
        case -15: resolution = -2; break; // 20Hz truncated
        case -55: resolution = -2; break; // 100Hz truncated
        case 45: resolution = 2; break;   // 100Hz rounded
        }
      if (1 == resolution)      // may be 20Hz rounded
        {
          test_frequency = f - f % 100 + 51;
          send_simple_command ("set frequency-hz " + QString::number (test_frequency));
          new_frequency = send_command ("get frequency").toUInt ();
          if (9 == static_cast<Radio::FrequencyDelta> (new_frequency - test_frequency))
            {
              resolution = 2;   // 20Hz rounded
            }
        }
      send_simple_command ("set frequency-hz " + QString::number (f));
    }
  return resolution;
}

void HRDTransceiver::do_stop ()
{
  if (hrd_)
    {
      hrd_->close ();
    }

  if (wrapped_) wrapped_->stop ();
  TRACE_CAT ("HRDTransceiver", "stopped" << state () << "reversed" << reversed_);
}

int HRDTransceiver::find_button (QRegExp const& re) const
{
  return buttons_.indexOf (re);
}

int HRDTransceiver::find_dropdown (QRegExp const& re) const
{
  return dropdown_names_.indexOf (re);
}

std::vector<int> HRDTransceiver::find_dropdown_selection (int dropdown, QRegExp const& re) const
{
  std::vector<int> indices;     // this will always contain at least a
                                // -1
  auto list = dropdowns_.value (dropdown_names_.value (dropdown));
  int index {0};
  while (-1 != (index = list.lastIndexOf (re, index - 1)))
    {
      // search backwards because more specialized modes tend to be
      // later in list
      indices.push_back (index);
      if (!index)
        {
          break;
        }
    }
  return indices;
}

void HRDTransceiver::map_modes (int dropdown, ModeMap *map)
{
  // order matters here (both in the map and in the regexps)
  map->push_back (std::forward_as_tuple (CW, find_dropdown_selection (dropdown, QRegExp ("^(CW|CW\\(N\\))|CWL$"))));
  map->push_back (std::forward_as_tuple (CW_R, find_dropdown_selection (dropdown, QRegExp ("^(CW-R|CW-R\\(N\\)|CW|CWU)$"))));
  map->push_back (std::forward_as_tuple (LSB, find_dropdown_selection (dropdown, QRegExp ("^(LSB\\(N\\)|LSB)$"))));
  map->push_back (std::forward_as_tuple (USB, find_dropdown_selection (dropdown, QRegExp ("^(USB\\(N\\)|USB)$"))));
  map->push_back (std::forward_as_tuple (DIG_U, find_dropdown_selection (dropdown, QRegExp ("^(DIG|DIGU|DATA-U|PKT-U|DATA|USER-U|USB)$"))));
  map->push_back (std::forward_as_tuple (DIG_L, find_dropdown_selection (dropdown, QRegExp ("^(DIG|DIGL|DATA-L|PKT-L|DATA-R|USER-L|LSB)$"))));
  map->push_back (std::forward_as_tuple (FSK, find_dropdown_selection (dropdown, QRegExp ("^(DIG|FSK|RTTY|RTTY-LSB)$"))));
  map->push_back (std::forward_as_tuple (FSK_R, find_dropdown_selection (dropdown, QRegExp ("^(DIG|FSK-R|RTTY-R|RTTY|RTTY-USB)$"))));
  map->push_back (std::forward_as_tuple (AM, find_dropdown_selection (dropdown, QRegExp ("^(AM|DSB|SAM|DRM)$"))));
  map->push_back (std::forward_as_tuple (FM, find_dropdown_selection (dropdown, QRegExp ("^(FM|FM\\(N\\)|FM-N|WFM)$"))));
  map->push_back (std::forward_as_tuple (DIG_FM, find_dropdown_selection (dropdown, QRegExp ("^(PKT-FM|PKT|FM)$"))));

#if WSJT_TRACE_CAT
  TRACE_CAT ("HRDTransceiver", "for dropdown" << dropdown_names_[dropdown]);
  std::for_each (map->begin (), map->end (), [this, dropdown] (ModeMap::value_type const& item)
                 {
                   auto const& rhs = std::get<1> (item);
                   TRACE_CAT ("HRDTransceiver", '\t' << std::get<0> (item) << "<->" << (rhs.size () ? dropdowns_[dropdown_names_[dropdown]][rhs.front ()] : "None"));
                 });
#endif
}

int HRDTransceiver::lookup_mode (MODE mode, ModeMap const& map) const
{
  auto it = std::find_if (map.begin (), map.end (), [mode] (ModeMap::value_type const& item) {return std::get<0> (item) == mode;});
  if (map.end () == it)
    {
      throw error {tr ("Ham Radio Deluxe: rig doesn't support mode")};
    }
  return std::get<1> (*it).front ();
}

auto HRDTransceiver::lookup_mode (int mode, ModeMap const& map) const -> MODE
{
  if (mode < 0)
    {
      return UNK;               // no mode dropdown
    }

  auto it = std::find_if (map.begin (), map.end (), [mode] (ModeMap::value_type const& item)
                          {
                            auto const& indices = std::get<1> (item);
                            return indices.cend () != std::find (indices.cbegin (), indices.cend (), mode);
                          });
  if (map.end () == it)
    {
      throw error {tr ("Ham Radio Deluxe: sent an unrecognised mode")};
    }
  return std::get<0> (*it);
}

int HRDTransceiver::get_dropdown (int dd, bool no_debug)
{
  if (dd < 0)
    {
      return -1;                // no dropdown to interrogate
    }

  auto dd_name = dropdown_names_.value (dd);
  auto reply = send_command ("get dropdown-text {" + dd_name + "}", no_debug);
  auto colon_index = reply.indexOf (':');

  if (colon_index < 0)
    {
      return -1;
    }

  Q_ASSERT (reply.left (colon_index).trimmed () == dd_name);
  return dropdowns_.value (dropdown_names_.value (dd)).indexOf (reply.mid (colon_index + 1).trimmed ());
}

void HRDTransceiver::set_dropdown (int dd, int value)
{
  auto dd_name = dropdown_names_.value (dd);
  if (value >= 0)
    {
      send_simple_command ("set dropdown " + dd_name.replace (' ', '~') + ' ' + dropdowns_.value (dd_name).value (value).replace (' ', '~') + ' ' + QString::number (value));
    }
  else
    {
      TRACE_CAT ("HRDTransceiver", "item" << value << "not found in" << dd_name);
      throw error {tr ("Ham Radio Deluxe: item not found in %1 dropdown list").arg (dd_name)};
    }
}

void HRDTransceiver::do_ptt (bool on)
{
  TRACE_CAT ("HRDTransceiver", on);
  if (use_for_ptt_)
    {
      if (alt_ptt_button_ >= 0 && TransceiverFactory::TX_audio_source_rear == audio_source_)
        {
          set_button (alt_ptt_button_, on);
        }
      else if (ptt_button_ >= 0)
        {
          set_button (ptt_button_, on);
        }
      // else
      // allow for pathological HRD rig interfaces that don't do
      // PTT by simply not even trying
    }
  else
    {
      Q_ASSERT (wrapped_);
      TransceiverState new_state {wrapped_->state ()};
      new_state.ptt (on);
      wrapped_->set (new_state, 0);
    }
  update_PTT (on);
}

void HRDTransceiver::set_button (int button_index, bool checked)
{
  if (button_index >= 0)
    {
      send_simple_command ("set button-select " + buttons_.value (button_index) + (checked ? " 1" : " 0"));
      if (button_index == rx_A_button_ || button_index == rx_B_button_)
        {
          QThread::msleep (yaesu_delay);
        }
    }
  else
    {
      TRACE_CAT ("HRDTransceiver", "invalid button");
      throw error {tr ("Ham Radio Deluxe: button not available")};
    }
}

void HRDTransceiver::set_data_mode (MODE m)
{
  if (data_mode_toggle_button_ >= 0)
    {
      switch (m)
        {
        case DIG_U:
        case DIG_L:
        case DIG_FM:
          set_button (data_mode_toggle_button_, true);
          break;
        default:
          set_button (data_mode_toggle_button_, false);
          break;
        }
    }
  else if (data_mode_on_button_ >= 0 && data_mode_off_button_ >= 0)
    {
      switch (m)
        {
        case DIG_U:
        case DIG_L:
        case DIG_FM:
          set_button (data_mode_on_button_, true);
          break;
        default:
          set_button (data_mode_off_button_, true);
          break;
        }
    }
  else if (data_mode_dropdown_ >= 0
      && data_mode_dropdown_selection_off_.size ()
      && data_mode_dropdown_selection_on_.size ())
    {
      switch (m)
        {
        case DIG_U:
        case DIG_L:
        case DIG_FM:
          set_dropdown (data_mode_dropdown_, data_mode_dropdown_selection_on_.front ());
          break;
        default:
          set_dropdown (data_mode_dropdown_, data_mode_dropdown_selection_off_.front ());
          break;
        }
    }
}

auto HRDTransceiver::get_data_mode (MODE m, bool quiet) -> MODE
{
  if (data_mode_dropdown_ >= 0
      && data_mode_dropdown_selection_off_.size ())
    {
      auto selection = get_dropdown (data_mode_dropdown_, quiet);
      // can't check for on here as there may be multiple on values so
      // we must rely on the initial parse finding valid on values
      if (selection >= 0 && selection != data_mode_dropdown_selection_off_.front ())
        {
          switch (m)
            {
            case USB: m = DIG_U; break;
            case LSB: m = DIG_L; break;
            case FM: m = DIG_FM; break;
            default: break;
            }
        }
    }
  return m;
}

void HRDTransceiver::do_frequency (Frequency f, MODE m, bool /*no_ignore*/)
{
  TRACE_CAT ("HRDTransceiver", f << "reversed" << reversed_);
  if (UNK != m)
    {
      do_mode (m);
    }
  auto fo_string = QString::number (f);
  if (vfo_count_ > 1 && reversed_)
    {
      auto frequencies = send_command ("get frequencies").trimmed ().split ('-', QString::SkipEmptyParts);
      send_simple_command ("set frequencies-hz " + QString::number (frequencies[0].toUInt ()) + ' ' + fo_string);
    }
  else
    {
      send_simple_command ("set frequency-hz " + QString::number (f));
    }
  update_rx_frequency (f);
}

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

  // re-check if reversed VFOs
  bool rx_A {true};
  bool rx_B {false};
  if (receiver_dropdown_ >= 0 && rx_A_selection_.size ())
    {
      auto selection = get_dropdown (receiver_dropdown_);
      rx_A = selection == rx_A_selection_.front ();
      if (!rx_A && rx_B_selection_.size ())
        {
          rx_B = selection == rx_B_selection_.front ();
        }
    }
  else if (vfo_B_button_ >= 0 || rx_B_button_ >= 0)
    {
      rx_A = is_button_checked (rx_A_button_ >= 0 ? rx_A_button_ : vfo_A_button_);
      if (!rx_A)
        {
          rx_B = is_button_checked (rx_B_button_ >= 0 ? rx_B_button_ : vfo_B_button_);
        }
    }
  reversed_ = rx_B;

  bool split {tx != 0};
  if (split)
    {
      if (mode != UNK)
        {
          if (!reversed_ && mode_B_dropdown_ >= 0)
            {
              set_dropdown (mode_B_dropdown_, lookup_mode (mode, mode_B_map_));
            }
          else if (reversed_ && mode_B_dropdown_ >= 0)
            {
              set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
            }
          else
            {
              Q_ASSERT (mode_A_dropdown_ >= 0 && ((vfo_A_button_ >=0 && vfo_B_button_ >=0) || vfo_toggle_button_ >= 0));

              if (rx_B_button_ >= 0)
                {
                  set_button (reversed_ ? rx_A_button_ : rx_B_button_);
                  set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
                  set_data_mode (mode);
                  set_button (reversed_ ? rx_B_button_ : rx_A_button_);
                }
              else if (receiver_dropdown_ >= 0
                       && rx_A_selection_.size () && rx_B_selection_.size ())
                {
                  set_dropdown (receiver_dropdown_, (reversed_ ? rx_A_selection_ : rx_B_selection_).front ());
                  set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
                  set_data_mode (mode);
                  set_dropdown (receiver_dropdown_, (reversed_ ? rx_B_selection_ : rx_A_selection_).front ());
                }
              else if (vfo_count_ > 1 && ((vfo_A_button_ >=0 && vfo_B_button_ >=0) || vfo_toggle_button_ >= 0))
                {
                  set_button (vfo_A_button_ >= 0 ? (reversed_ ? vfo_A_button_ : vfo_B_button_) : vfo_toggle_button_);
                  set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
                  set_data_mode (mode);
                  set_button (vfo_A_button_ >= 0 ? (reversed_ ? vfo_B_button_ : vfo_A_button_) : vfo_toggle_button_);
                }
              // else Tx VFO mode gets set with frequency below or we
              // don't have a way of setting it so we assume it is
              // always the same as the Rx VFO mode
            }
        }

      auto fo_string = QString::number (tx);
      if (reversed_)
        {
          Q_ASSERT (vfo_count_ > 1);

          auto frequencies = send_command ("get frequencies").trimmed ().split ('-', QString::SkipEmptyParts);
          send_simple_command ("set frequencies-hz " + fo_string + ' ' + QString::number (frequencies[1].toUInt ()));
        }
      else
        {
          if (vfo_count_ > 1)
            {
              auto frequencies = send_command ("get frequencies").trimmed ().split ('-', QString::SkipEmptyParts);
              send_simple_command ("set frequencies-hz " + QString::number (frequencies[0].toUInt ()) + ' ' + fo_string);
            }
          else if ((vfo_B_button_ >= 0 && vfo_A_button_ >= 0) || vfo_toggle_button_ >= 0)
            {
              // we rationalise the modes here as well as the frequencies
              set_button (vfo_B_button_ >= 0 ? vfo_B_button_ : vfo_toggle_button_);
              send_simple_command ("set frequency-hz " + fo_string);
              if (mode != UNK && mode_B_dropdown_ < 0)
                {
                  // do this here rather than later so we only
                  // toggle/switch VFOs once
                  set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
                  set_data_mode (mode);
                }
              set_button (vfo_A_button_ >= 0 ? vfo_A_button_ : vfo_toggle_button_);
            }
        }
    }
  update_other_frequency (tx);

  if (split_mode_button_ >= 0)
    {
      if (split_off_button_ >= 0 && !split)
        {
          set_button (split_off_button_);
        }
      else
        {
          set_button (split_mode_button_, split);
        }
    }
  else if (split_mode_dropdown_ >= 0
           && split_mode_dropdown_selection_off_.size ()
           && split_mode_dropdown_selection_on_.size ())
    {
      set_dropdown (split_mode_dropdown_, split ? split_mode_dropdown_selection_on_.front () : split_mode_dropdown_selection_off_.front ());
    }
  else if (vfo_A_button_ >= 0 && vfo_B_button_ >= 0 && tx_A_button_ >= 0 && tx_B_button_ >= 0)
    {
      if (split)
        {
          if (reversed_ != is_button_checked (tx_A_button_))
            {
              if (rx_A_button_ >= 0 && rx_B_button_ >= 0)
                {
                  set_button (reversed_ ? rx_B_button_ : rx_A_button_);
                }
              else if (receiver_dropdown_ >= 0
                       && rx_A_selection_.size () && rx_B_selection_.size ())
                {
                  set_dropdown (receiver_dropdown_, (reversed_ ? rx_B_selection_ : rx_A_selection_).front ());
                }
              else
                {
                  set_button (reversed_ ? vfo_B_button_ : vfo_A_button_);
                }
              set_button (reversed_ ? tx_A_button_ : tx_B_button_);
            }
        }
      else
        {
          if (reversed_ != is_button_checked (tx_B_button_))
            {
              if (rx_A_button_ >= 0 && rx_B_button_ >= 0)
                {
                  set_button (reversed_ ? rx_B_button_ : rx_A_button_);
                }
              else if (receiver_dropdown_ >= 0
                       && rx_A_selection_.size () && rx_B_selection_.size ())
                {
                  set_dropdown (receiver_dropdown_, (reversed_ ? rx_B_selection_ : rx_A_selection_).front ());
                }
              else
                {
                  set_button (reversed_ ? vfo_B_button_ : vfo_A_button_);
                }
              set_button (reversed_ ? tx_B_button_ : tx_A_button_);
            }
        }
    }
  update_split (split);
}

void HRDTransceiver::do_mode (MODE mode)
{
  TRACE_CAT ("HRDTransceiver", mode);
  if (reversed_ && mode_B_dropdown_ >= 0)
    {
      set_dropdown (mode_B_dropdown_, lookup_mode (mode, mode_B_map_));
    }
  else
    {
      set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
    }
  if (mode != UNK && state ().split ()) // rationalise mode if split
    {
      if (reversed_)
        {
          if (mode_B_dropdown_ >= 0)
            {
              set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
            }
          else
            {
              Q_ASSERT ((vfo_B_button_ >= 0 && vfo_A_button_ >= 0) || vfo_toggle_button_ >= 0);

              if (rx_B_button_ >= 0)
                {
                  set_button (rx_A_button_);
                  set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
                  set_button (rx_B_button_);
                }
              else if (receiver_dropdown_ >= 0
                       && rx_A_selection_.size () && rx_B_selection_.size ())
                {
                  set_dropdown (receiver_dropdown_, rx_A_selection_.front ());
                  set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
                  set_dropdown (receiver_dropdown_, rx_B_selection_.front ());
                }
              else if (vfo_count_ > 1 && ((vfo_A_button_ >=0 && vfo_B_button_ >=0) || vfo_toggle_button_ >= 0))
                {
                  set_button (vfo_A_button_ >= 0 ? vfo_A_button_ : vfo_toggle_button_);
                  set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
                  set_button (vfo_B_button_ >= 0 ? vfo_B_button_ : vfo_toggle_button_);
                }
              // else Tx VFO mode gets set when Tx VFO frequency is
              // set

              if ( tx_A_button_ >= 0)
                {
                  set_button (tx_A_button_);
                }
            }
        }
      else
        {
          if (mode_B_dropdown_ >= 0)
            {
              set_dropdown (mode_B_dropdown_, lookup_mode (mode, mode_B_map_));
            }
          else
            {
              Q_ASSERT ((vfo_B_button_ >= 0 && vfo_A_button_ >= 0) || vfo_toggle_button_ >= 0);

              if (rx_B_button_ >= 0)
                {
                  set_button (rx_B_button_);
                  set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
                  set_button (rx_A_button_);
                }
              else if (receiver_dropdown_ >= 0
                       && rx_A_selection_.size () && rx_B_selection_.size ())
                {
                  set_dropdown (receiver_dropdown_, rx_B_selection_.front ());
                  set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
                  set_dropdown (receiver_dropdown_, rx_A_selection_.front ());
                }
              else if (vfo_count_ > 1 && ((vfo_A_button_ >=0 && vfo_B_button_ >=0) || vfo_toggle_button_ >= 0))
                {
                  set_button (vfo_B_button_ >= 0 ? vfo_B_button_ : vfo_toggle_button_);
                  set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_));
                  set_button (vfo_A_button_ >= 0 ? vfo_A_button_ : vfo_toggle_button_);
                }
              // else Tx VFO mode gets set when Tx VFO frequency is
              // set

              if ( tx_B_button_ >= 0)
                {
                  set_button (tx_B_button_);
                }
            }
        }
    }
  set_data_mode (mode);
  update_mode (mode);
}

bool HRDTransceiver::is_button_checked (int button_index, bool no_debug)
{
  if (button_index < 0)
    {
      return false;
    }

  auto reply = send_command ("get button-select " + buttons_.value (button_index), no_debug);
  if ("1" != reply && "0" != reply)
    {
      TRACE_CAT ("HRDTransceiver", "bad response");
      throw error {tr ("Ham Radio Deluxe didn't respond as expected")};
    }
  return "1" == reply;
}

void HRDTransceiver::poll ()
{
#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS
  bool quiet {false};
  qDebug () << "+++++++ poll dump +++++++";
  qDebug () << "reversed:" << reversed_;
  is_button_checked (vfo_A_button_);
  is_button_checked (vfo_B_button_);
  is_button_checked (vfo_toggle_button_);
  is_button_checked (split_mode_button_);
  is_button_checked (split_off_button_);
  is_button_checked (rx_A_button_);
  is_button_checked (rx_B_button_);
  get_dropdown (receiver_dropdown_);
  is_button_checked (tx_A_button_);
  is_button_checked (tx_B_button_);
  is_button_checked (ptt_button_);
  is_button_checked (alt_ptt_button_);
  get_dropdown (mode_A_dropdown_);
  get_dropdown (mode_B_dropdown_);
  is_button_checked (data_mode_toggle_button_);
  is_button_checked (data_mode_on_button_);
  is_button_checked (data_mode_off_button_);
  if (data_mode_dropdown_ >=0
      && data_mode_dropdown_selection_off_.size ()
      && data_mode_dropdown_selection_on_.size ())
    {
      get_dropdown (data_mode_dropdown_);
    }
  if (!split_mode_dropdown_write_only_)
    {
      get_dropdown (split_mode_dropdown_);
    }
  qDebug () << "------- poll dump -------";
#else
  bool quiet {true};
#endif

  if (split_off_button_ >= 0)
    {
      // we are probably dealing with an Icom and have to guess SPLIT mode :(
    }
  else if (split_mode_button_ >= 0 && !(tx_A_button_ >= 0 && tx_B_button_ >= 0))
    {
      update_split (is_button_checked (split_mode_button_, quiet));
    }
  else if (split_mode_dropdown_ >= 0)
    {
      if (!split_mode_dropdown_write_only_)
        {
          auto selection = get_dropdown (split_mode_dropdown_, quiet);
          if (selection >= 0
              && split_mode_dropdown_selection_off_.size ())
            {
              update_split (selection == split_mode_dropdown_selection_on_.front ());
            }
          else
            {
              // leave split alone as we can't query it - it should be
              // correct so long as rig or HRD haven't been changed
              split_mode_dropdown_write_only_ = true;
            }
        }
    }
  else if (vfo_A_button_ >= 0 && vfo_B_button_ >= 0 && tx_A_button_ >= 0 && tx_B_button_ >= 0)
    {
      bool rx_A {true};         // no Rx taken as not reversed
      bool rx_B {false};

      auto tx_A = is_button_checked (tx_A_button_, quiet);

      // some rigs have dual Rx, we take VFO A/MAIN receiving as
      // normal and only say reversed when only VFO B/SUB is active
      // i.e. VFO A/MAIN muted VFO B/SUB active
      if (receiver_dropdown_ >= 0 && rx_A_selection_.size ())
        {
          auto selection = get_dropdown (receiver_dropdown_);
          rx_A = selection == rx_A_selection_.front ();
          if (!rx_A && rx_B_selection_.size ())
            {
              rx_B = selection == rx_B_selection_.front ();
            }
        }
      else if (vfo_B_button_ >= 0 || rx_B_button_ >= 0)
        {
          rx_A = is_button_checked (rx_A_button_ >= 0 ? rx_A_button_ : vfo_A_button_, quiet);
          if (!rx_A)
            {
              rx_B = is_button_checked (rx_B_button_ >= 0 ? rx_B_button_ : vfo_B_button_, quiet);
            }
        }

      update_split (rx_B == tx_A);
      reversed_ = rx_B;
    }

  if (vfo_count_ > 1)
    {
      auto frequencies = send_command ("get frequencies", quiet).trimmed ().split ('-', QString::SkipEmptyParts);
      update_rx_frequency (frequencies[reversed_ ? 1 : 0].toUInt ());
      update_other_frequency (frequencies[reversed_ ? 0 : 1].toUInt ());
    }
  else
    {
      // read frequency is unreliable on single VFO addressing rigs
      // while transmitting
      if (!state ().ptt ())
        {
          update_rx_frequency (send_command ("get frequency", quiet).toUInt ());
        }
    }

  // read mode is unreliable on single VFO addressing rigs while
  // transmitting
  if (vfo_count_ > 1 || !state ().ptt ())
    {
      update_mode (get_data_mode (lookup_mode (get_dropdown (mode_A_dropdown_, quiet), mode_A_map_), quiet));
    }
}

QString HRDTransceiver::send_command (QString const& cmd, bool no_debug, bool prepend_context, bool recurse)
{
  Q_ASSERT (hrd_);

  QString result;

  if (current_radio_ && prepend_context && vfo_count_ < 2)
    {
      // required on some radios because commands don't get executed
      // correctly otherwise (ICOM for example)
      QThread::msleep (50);
   }

  if (!recurse && prepend_context)
    {
      auto radio_name = send_command ("get radio", true, current_radio_, true);
      auto radio_iter = std::find_if (radios_.begin (), radios_.end (), [this, &radio_name] (RadioMap::value_type const& radio)
                                      {
                                        return std::get<1> (radio) == radio_name;
                                      });
      if (radio_iter == radios_.end ())
        {
          TRACE_CAT ("HRDTransceiver", "rig disappeared or changed");
          throw error {tr ("Ham Radio Deluxe: rig has disappeared or changed")};
        }

      if (0u == current_radio_ || std::get<0> (*radio_iter) != current_radio_)
        {
          current_radio_ = std::get<0> (*radio_iter);
        }
    }

  auto context = '[' + QString::number (current_radio_) + "] ";

  if (QTcpSocket::ConnectedState != hrd_->state ())
    {
      TRACE_CAT ("HRDTransceiver", cmd << "failed" << hrd_->errorString ());
      throw error {
        tr ("Ham Radio Deluxe send command \"%1\" failed %2\n")
          .arg (cmd)
          .arg (hrd_->errorString ())
          };
    }

  if (v4 == protocol_)
    {
      auto message = ((prepend_context ? context + cmd : cmd) + "\r").toLocal8Bit ();
      if (!write_to_port (message.constData (), message.size ()))
        {
          TRACE_CAT ("HRDTransceiver", "failed to write command" << cmd << "to HRD");
          throw error {
            tr ("Ham Radio Deluxe: failed to write command \"%1\"")
              .arg (cmd)
              };
        }
    }
  else
    {
      auto string = prepend_context ? context + cmd : cmd;
      QScopedPointer<HRDMessage> message {new (string) HRDMessage};
      if (!write_to_port (reinterpret_cast<char const *> (message.data ()), message->size_))
        {
          TRACE_CAT ("HRDTransceiver", "failed to write command" << cmd << "to HRD");
          throw error {
            tr ("Ham Radio Deluxe: failed to write command \"%1\"")
              .arg (cmd)
              };
        }
    }
  auto buffer = read_reply (cmd);
  if (v4 == protocol_)
    {
      result = QString {buffer}.trimmed ();
    }
  else
    {
      HRDMessage const * reply {new (buffer) HRDMessage};
      if (reply->magic_1_value_ != reply->magic_1_ && reply->magic_2_value_ != reply->magic_2_)
        {
          TRACE_CAT ("HRDTransceiver", cmd << "invalid reply");
          throw error {
            tr ("Ham Radio Deluxe sent an invalid reply to our command \"%1\"")
              .arg (cmd)
              };
        }

      // keep reading until expected size arrives
      while (buffer.size () - offsetof (HRDMessage, size_) < reply->size_)
        {
          if (!no_debug)
            {
              TRACE_CAT ("HRDTransceiver", cmd << "reading more reply data");
            }
          buffer += read_reply (cmd);
          reply = new (buffer) HRDMessage;
        }

      result = QString {reply->payload_}; // this is not a memory leak (honest!)
    }
  if (!no_debug)
    {
      TRACE_CAT ("HRDTransceiver", cmd << " ->" << result);
    }
  return result;
}

bool HRDTransceiver::write_to_port (char const * data, qint64 length)
{
  qint64 total_bytes_sent {0};
  while (total_bytes_sent < length)
    {
      auto bytes_sent = hrd_->write (data + total_bytes_sent, length - total_bytes_sent);
      if (bytes_sent < 0 || !hrd_->waitForBytesWritten ())
        {
          return false;
        }

      total_bytes_sent += bytes_sent;
    }
  return true;
}

QByteArray HRDTransceiver::read_reply (QString const& cmd)
{
  // waitForReadReady appears to be occasionally unreliable on Windows
  // timing out when data is waiting so retry a few times
  unsigned retries {3};
  bool replied {false};
  while (!replied && retries--)
    {
      replied = hrd_->waitForReadyRead ();
      if (!replied && hrd_->error () != hrd_->SocketTimeoutError)
        {
          TRACE_CAT ("HRDTransceiver", cmd << "failed to reply" << hrd_->errorString ());
          throw error {
            tr ("Ham Radio Deluxe failed to reply to command \"%1\" %2\n")
              .arg (cmd)
              .arg (hrd_->errorString ())
              };
        }
    }
  if (!replied)
    {
      TRACE_CAT ("HRDTransceiver", cmd << "retries exhausted");
      throw error {
        tr ("Ham Radio Deluxe retries exhausted sending command \"%1\"")
          .arg (cmd)
          };
    }
  return hrd_->readAll ();
}

void HRDTransceiver::send_simple_command (QString const& command, bool no_debug)
{
  if ("OK" != send_command (command, no_debug))
    {
      TRACE_CAT ("HRDTransceiver", command << "unexpected response");
      throw error {
        tr ("Ham Radio Deluxe didn't respond to command \"%1\" as expected")
          .arg (command)
          };
    }
}


code:
#ifndef HRD_TRANSCEIVER_HPP__
#define HRD_TRANSCEIVER_HPP__

#include <vector>
#include <tuple>
#include <memory>

#include <QScopedPointer>
#include <QString>
#include <QStringList>

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

class QRegExp;
class QTcpSocket;
class QByteArray;

//
// Ham Radio Deluxe Transceiver Interface
//
// Implemented as a Transceiver decorator  because we may want the PTT
// services of another Transceiver  type such as the HamlibTransceiver
// which can  be enabled by wrapping  a HamlibTransceiver instantiated
// as a "Hamlib Dummy" transceiver in the Transceiver factory method.
//
class HRDTransceiver final
  : public PollingTransceiver
{
public:
  static void register_transceivers (TransceiverFactory::Transceivers *, int id);

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

protected:
  // Implement the TransceiverBase interface.
  int do_start () override;
  void do_stop () override;
  void do_frequency (Frequency, MODE, bool no_ignore) override;
  void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
  void do_mode (MODE) override;
  void do_ptt (bool on) override;

  // Implement the PollingTransceiver interface.
  void poll () override;

private:
  QString send_command (QString const&, bool no_debug = false, bool prepend_context = true, bool recurse = false);
  QByteArray read_reply (QString const& command);
  void send_simple_command (QString const&, bool no_debug = false);
  bool write_to_port (char const *, qint64 length);
  int find_button (QRegExp const&) const;
  int find_dropdown (QRegExp const&) const;
  std::vector<int> find_dropdown_selection (int dropdown, QRegExp const&) const;
  int get_dropdown (int, bool no_debug = false);
  void set_dropdown (int, int);
  void set_button (int button_index, bool checked = true);
  bool is_button_checked (int button_index, bool no_debug = false);

  // This dictionary type maps Transceiver::MODE to a list of mode
  // drop down selection indexes that equate to that mode.  It is used
  // to map internal MODE values to HRD drop down selections and vice
  // versa.
  using ModeMap = std::vector<std::tuple<MODE, std::vector<int> > >;

  void map_modes (int dropdown, ModeMap *);
  int lookup_mode (MODE, ModeMap const&) const;
  MODE lookup_mode (int, ModeMap const&) const;
  void set_data_mode (MODE);
  MODE get_data_mode (MODE, bool no_debug = false);

  // An alternate TransceiverBase instance that can be used to drive
  // PTT if required.
  std::unique_ptr<TransceiverBase> wrapped_; // may be null

  bool use_for_ptt_;            // Use HRD for PTT.
  TransceiverFactory::TXAudioSource audio_source_; // Select rear/data
                                                   // audio if available

  QString server_;              // The TCP/IP addrress and port for
                                // the HRD server.

  QTcpSocket * hrd_;            // The TCP/IP client that links to the
                                // HRD server.

  enum {none, v4, v5} protocol_; // The HRD protocol that has been
                                 // detected.

  using RadioMap = std::vector<std::tuple<unsigned, QString> >;

  RadioMap radios_;             // Dictionary of available radios.

  unsigned current_radio_;      // The current addressed radio.

  unsigned vfo_count_;          // How many VFOs are supported.

  QStringList buttons_;         // The buttons available to click.

  QStringList dropdown_names_;  // The names of drop down selectors
                                // available.

  QMap<QString, QStringList> dropdowns_; // Dictionary of available
                                         // drop down selections.

  QStringList slider_names_;    // The name of available sliders.

  QMap<QString, QStringList> sliders_; // Dictionary of available
                                // slider ranges.

  int vfo_A_button_;            // The button we use to select VFO
                                // A. May be -1 if none available.

  int vfo_B_button_;            // Index of button we use to select
                                // VFO B. May be -1 if none available.

  int vfo_toggle_button_;       // Index of button we use to toggle
                                // the VFOs. Use this if VFO A and VFO
                                // B selection are not available.

  int mode_A_dropdown_;         // Index of the mode drop down for VFO
                                // A.

  ModeMap mode_A_map_;          // The map of modes available for VFO
                                // A.

  int mode_B_dropdown_;         // The drop down index for VFO B mode
                                // setting. May be -1 if independent
                                // VFO mode setting not available.

  ModeMap mode_B_map_;          // The map of modes for VFO B.

  int data_mode_toggle_button_; // Button to toggle DATA mode
  int data_mode_on_button_;     // Button to enable DATA mode
  int data_mode_off_button_;    // Button to disable DATA mode
  int data_mode_dropdown_;      // Index of data mode drop down, may
                                // be -1 if no such drop down exists
  std::vector<int> data_mode_dropdown_selection_on_; // The drop down
                                // selection to turn on data mode.

  std::vector<int> data_mode_dropdown_selection_off_; // The drop
                                // down selection to disable data mode.

  int split_mode_button_;       // Button to use to select split
                                // operation. May be -1 if no button
                                // is available.

  int split_mode_dropdown_;     // The drop down index that allows
                                // split mode to be turned on and
                                // off. May be -1 if no such drop down
                                // exists.

  bool split_mode_dropdown_write_only_; // Some rigs cannot report
                                        // split status.

  std::vector<int> split_mode_dropdown_selection_on_; // The drop down
                                                      // selection to
                                                      // turn on
                                                      // split.

  std::vector<int> split_mode_dropdown_selection_off_; // The drop
                                                       // down
                                                       // selection to
                                                       // disable
                                                       // split.

  int split_off_button_;        // The button to turn off split mode.

  int tx_A_button_;             // The button to transmit on VFO A.

  int tx_B_button_;             // The button to transmit on VFO B.

  int rx_A_button_;             // The button to receive on VFO A
                                // A. May be -1 if none available.

  int rx_B_button_;             // The button to receive on VFO B
                                // May be -1 if none available.

  int receiver_dropdown_;       // Select receiver

  std::vector<int> rx_A_selection_;

  std::vector<int> rx_B_selection_;

  int ptt_button_;              // The button to toggle PTT.
  int alt_ptt_button_;          // The alternative button to toggle
                                // PTT - used to select rear audio.

  bool reversed_;               // True if VFOs are reversed.
};

#endif