code:
/* This is a dummy file required to make the qmake build compatible
   with the CMake build. */

#define WSJTX_STRINGIZE(X)

license: mit


code:
#ifndef ORDEREDMAP_H
#define ORDEREDMAP_H

#include <QtGlobal>
#include <QHash>
#include <QLinkedList>
#include <QList>
#include <QPair>

template <typename Key> inline bool oMHashEqualToKey(const Key &key1, const Key &key2)
{
    // Key type must provide '==' operator
    return key1 == key2;
}

template <typename Ptr> inline bool oMHashEqualToKey(Ptr *key1, Ptr *key2)
{
    Q_ASSERT(sizeof(quintptr) == sizeof(Ptr *));
    return quintptr(key1) == quintptr(key2);
}

template <typename Ptr> inline bool oMHashEqualToKey(const Ptr *key1, const Ptr *key2)
{
    Q_ASSERT(sizeof(quintptr) == sizeof(const Ptr *));
    return quintptr(key1) == quintptr(key2);
}

template <typename Key, typename Value>
class OrderedMap
{
    class OMHash;

    typedef typename QLinkedList<Key>::iterator QllIterator;
    typedef typename QLinkedList<Key>::const_iterator QllConstIterator;
    typedef QPair<Value, QllIterator> OMHashValue;

    typedef typename OMHash::iterator OMHashIterator;
    typedef typename OMHash::const_iterator OMHashConstIterator;

public:

    class iterator;
    class const_iterator;

    typedef typename OrderedMap<Key, Value>::iterator Iterator;
    typedef typename OrderedMap<Key, Value>::const_iterator ConstIterator;

    explicit OrderedMap();

    OrderedMap(const OrderedMap<Key, Value>& other);

#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0))
    OrderedMap(OrderedMap<Key, Value>&& other);
#endif

    void clear();

    bool contains(const Key &key) const;

    int count() const;

    bool empty() const;

    iterator insert(const Key &key, const Value &value);

    bool isEmpty() const;

    QList<Key> keys() const;

    int remove(const Key &key);

    int size() const;

    Value take(const Key &key);

    Value value(const Key &key) const;

    Value value(const Key &key, const Value &defaultValue) const;

    QList<Value> values() const;

    OrderedMap<Key, Value> & operator=(const OrderedMap<Key, Value>& other);

#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0))
    OrderedMap<Key, Value> & operator=(OrderedMap<Key, Value>&& other);
#endif

    bool operator==(const OrderedMap<Key, Value> &other) const;

    bool operator!=(const OrderedMap<Key, Value> &other) const;

    Value& operator[](const Key &key);

    const Value operator[](const Key &key) const;

    iterator begin();

    const_iterator begin() const;

    iterator end();

    const_iterator end() const;

    iterator erase(iterator pos);

    iterator find(const Key& key);

    const_iterator find(const Key& key) const;

    class const_iterator;

    class iterator
    {
        QllIterator qllIter;
        OMHash *data;
        friend class const_iterator;
        friend class OrderedMap;

    public:
        iterator() : data(NULL) {}

        iterator(const QllIterator &qllIter, OMHash *data) :
            qllIter(qllIter), data(data) {}

        const Key & key() const
        {
            return *qllIter;
        }

        Value & value() const
        {
            OMHashIterator hit = data->find(*qllIter);
            OMHashValue &pair = hit.value();
            return pair.first;
        }

        Value & operator*() const
        {
            return value();
        }

        iterator operator+(int i) const
        {
            QllIterator q = qllIter;
            q += i;

            return iterator(q, data);
        }

        iterator operator-(int i) const
        {
            return operator +(- i);
        }

        iterator& operator+=(int i)
        {
            qllIter += i;
            return *this;
        }

        iterator& operator-=(int i)
        {
            qllIter -= i;
            return *this;
        }

        iterator& operator++()
        {
            ++qllIter;
            return *this;
        }

        iterator operator++(int)
        {
            iterator it = *this;
            qllIter++;
            return it;
        }

        iterator operator--()
        {
            --qllIter;
            return *this;
        }

        iterator operator--(int)
        {
            iterator it = *this;
            qllIter--;
            return it;
        }

        bool operator ==(const iterator &other) const
        {
            return (qllIter == other.qllIter);
        }

        bool operator !=(const iterator &other) const
        {
            return (qllIter != other.qllIter);
        }
    };

    class const_iterator
    {

        QllConstIterator qllConstIter;
        const OMHash *data;

    public:
        const_iterator() : data(NULL) {}

        const_iterator(const iterator &i) :
            qllConstIter(i.qllIter), data(i.data) {}

        const_iterator(const QllConstIterator &qllConstIter, const OMHash* data) :
            qllConstIter(qllConstIter), data(data) {}

        const Key & key() const
        {
            return *qllConstIter;
        }

        const Value & value() const
        {
            OMHashConstIterator hit = data->find(*qllConstIter);
            const OMHashValue &pair = hit.value();
            return pair.first;
        }

        const Value & operator*() const
        {
            return value();
        }

        const_iterator operator+(int i) const
        {
            QllConstIterator q = qllConstIter;
            q += i;

            return const_iterator(q, data);
        }

        const_iterator operator-(int i) const
        {
            return operator +(- i);
        }

        const_iterator& operator+=(int i)
        {
            qllConstIter += i;
            return *this;
        }

        const_iterator& operator-=(int i)
        {
            qllConstIter -= i;
            return *this;
        }

        const_iterator& operator++()
        {
            ++qllConstIter;
            return *this;
        }

        const_iterator operator++(int)
        {
            const_iterator it = *this;
            qllConstIter++;
            return it;
        }

        const_iterator operator--()
        {
            --qllConstIter;
            return *this;
        }

        const_iterator operator--(int)
        {
            const_iterator it = *this;
            qllConstIter--;
            return it;
        }

        bool operator ==(const const_iterator &other) const
        {
            return (qllConstIter == other.qllConstIter);
        }

        bool operator !=(const const_iterator &other) const
        {
            return (qllConstIter != other.qllConstIter);
        }
    };

private:

    class OMHash : public QHash<Key, OMHashValue >
    {
    public:
        bool operator == (const OMHash &other) const
        {
            if (size() != other.size()) {
                return false;
            }

            if (QHash<Key, OMHashValue >::operator ==(other)) {
                return true;
            }

            typename QHash<Key, OMHashValue >::const_iterator it1 = this->constBegin();
            typename QHash<Key, OMHashValue >::const_iterator it2 = other.constBegin();

            while(it1 != this->end()) {
                OMHashValue v1 = it1.value();
                OMHashValue v2 = it2.value();

                if ((v1.first != v2.first) || !oMHashEqualToKey<Key>(it1.key(), it2.key())) {
                    return false;
                }
                ++it1;
                ++it2;
            }
            return true;
        }
    };

private:
    void copy(const OrderedMap<Key, Value> &other);

    OMHash data;
    QLinkedList<Key> insertOrder;
};

template <typename Key, typename Value>
OrderedMap<Key, Value>::OrderedMap() {}

template <typename Key, typename Value>
OrderedMap<Key, Value>::OrderedMap(const OrderedMap<Key, Value>& other)
{
    copy(other);
}

#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0))
template <typename Key, typename Value>
OrderedMap<Key, Value>::OrderedMap(OrderedMap<Key, Value>&& other)
{
    data = std::move(other.data);
    insertOrder = std::move(other.insertOrder);
}
#endif

template <typename Key, typename Value>
void OrderedMap<Key, Value>::clear()
{
    data.clear();
    insertOrder.clear();
}

template <typename Key, typename Value>
bool OrderedMap<Key, Value>::contains(const Key &key) const
{
    return data.contains(key);
}

template <typename Key, typename Value>
int OrderedMap<Key, Value>::count() const
{
    return data.count();
}

template <typename Key, typename Value>
bool OrderedMap<Key, Value>::empty() const
{
    return data.empty();
}

template <typename Key, typename Value>
typename OrderedMap<Key, Value>::iterator OrderedMap<Key, Value>::insert(const Key &key, const Value &value)
{
    OMHashIterator it = data.find(key);

    if (it == data.end()) {
        // New key
        QllIterator ioIter = insertOrder.insert(insertOrder.end(), key);
        OMHashValue pair(value, ioIter);
        data.insert(key, pair);
        return iterator(ioIter, &data);
    }

    OMHashValue pair = it.value();
    // remove old reference
    insertOrder.erase(pair.second);
    // Add new reference
    QllIterator ioIter = insertOrder.insert(insertOrder.end(), key);
    pair.first = value;
    pair.second = ioIter;
    return iterator(ioIter, &data);
}

template <typename Key, typename Value>
bool OrderedMap<Key, Value>::isEmpty() const
{
    return data.isEmpty();
}

template<typename Key, typename Value>
QList<Key> OrderedMap<Key, Value>::keys() const
{
    return QList<Key>::fromStdList(insertOrder.toStdList());
}

template<typename Key, typename Value>
int OrderedMap<Key, Value>::remove(const Key &key)
{
    OMHashIterator it = data.find(key);
    if (it == data.end()) {
        return 0;
    }
    OMHashValue pair = it.value();
    insertOrder.erase(pair.second);
    data.erase(it);
    return 1;
}

template<typename Key, typename Value>
int OrderedMap<Key, Value>::size() const
{
    return data.size();
}

template<typename Key, typename Value>
void OrderedMap<Key, Value>::copy(const OrderedMap<Key, Value> &other)
{
    /* Since I'm storing iterators of QLinkedList, I simply cannot make
     * a trivial copy of the linked list. This is a limitation due to implicit
     * sharing used in Qt containers, due to which iterator active on one
     * QLL can change the data of another QLL even after creating a copy.
     *
     * Because of this, the old iterators have to be invalidated and new ones
     * have to be generated.
     */
    insertOrder.clear();
    // Copy hash
    data = other.data;

    QllConstIterator cit = other.insertOrder.begin();
    for (; cit != other.insertOrder.end(); ++cit) {
        Key key = *cit;
        QllIterator ioIter = insertOrder.insert(insertOrder.end(), key);
        OMHashIterator it = data.find(key);
        (*it).second = ioIter;
    }
}

template<typename Key, typename Value>
Value OrderedMap<Key, Value>::take(const Key &key)
{
    OMHashIterator it = data.find(key);
    if (it == data.end()) {
        return Value();
    }
    OMHashValue pair = it.value();
    insertOrder.erase(pair.second);
    data.erase(it);
    return pair.first;
}

template <typename Key, typename Value>
Value OrderedMap<Key, Value>::value(const Key &key) const
{
    return data.value(key).first;
}

template <typename Key, typename Value>
Value OrderedMap<Key, Value>::value(const Key &key, const Value &defaultValue) const
{
    OMHashConstIterator it = data.constFind(key);
    if (it == data.end()) {
        return defaultValue;
    }
    OMHashValue pair = it.value();
    return pair.first;
}

template <typename Key, typename Value>
QList<Value> OrderedMap<Key, Value>::values() const
{
    QList<Value> values;
    foreach (const Key &key, insertOrder.toStdList()) {
        OMHashValue v = data.value(key);
        values.append(v.first);
    }
    return values;
}

template <typename Key, typename Value>
OrderedMap<Key, Value> & OrderedMap<Key, Value>::operator=(const OrderedMap<Key, Value>& other)
{
    if (this != &other) {
        copy(other);
    }
    return *this;
}

#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0))
template <typename Key, typename Value>
OrderedMap<Key, Value> & OrderedMap<Key, Value>::operator=(OrderedMap<Key, Value>&& other)
{
    if (this != &other) {
        data = other.data;
        insertOrder = other.insertOrder;
    }
    return *this;
}
#endif

template <typename Key, typename Value>
bool OrderedMap<Key, Value>::operator==(const OrderedMap<Key, Value> &other) const
{
    // 2 Ordered maps are equal if they have the same contents in the same order
    return ((data == other.data) && (insertOrder == other.insertOrder));
}

template <typename Key, typename Value>
bool OrderedMap<Key, Value>::operator!=(const OrderedMap<Key, Value> &other) const
{
    return ((data != other.data) || (insertOrder != other.insertOrder));
}

template <typename Key, typename Value>
Value& OrderedMap<Key, Value>::operator[](const Key &key)
{
    OMHashIterator it = data.find(key);
    if (it == data.end()) {
        insert(key, Value());
        it = data.find(key);
    }
    OMHashValue &pair = it.value();
    return pair.first;
}

template <typename Key, typename Value>
const Value OrderedMap<Key, Value>::operator[](const Key &key) const
{
    return value(key);
}

template <typename Key, typename Value>
typename OrderedMap<Key, Value>::iterator OrderedMap<Key, Value>::begin()
{
    return iterator(insertOrder.begin(), &data);
}

template <typename Key, typename Value>
typename OrderedMap<Key, Value>::const_iterator OrderedMap<Key, Value>::begin() const
{
    return const_iterator(insertOrder.begin(), &data);
}

template <typename Key, typename Value>
typename OrderedMap<Key, Value>::iterator OrderedMap<Key, Value>::end()
{
    return iterator(insertOrder.end(), &data);
}

template <typename Key, typename Value>
typename OrderedMap<Key, Value>::const_iterator OrderedMap<Key, Value>::end() const
{
    return const_iterator(insertOrder.end(), &data);
}

template <typename Key, typename Value>
typename OrderedMap<Key, Value>::iterator OrderedMap<Key, Value>::erase(iterator pos)
{
    OMHashIterator hit = data.find(*(pos.qllIter));
    if (hit == data.end()) {
        return pos;
    }
    data.erase(hit);
    QllIterator ioIter = insertOrder.erase(pos.qllIter);

    return iterator(ioIter, &data);
}

template <typename Key, typename Value>
typename OrderedMap<Key, Value>::iterator OrderedMap<Key, Value>::find(const Key& key)
{
    OMHashIterator hit = data.find(key);
    if (hit == data.end()) {
        return end();
    }

    return iterator(hit.value().second, &data);
}

template <typename Key, typename Value>
typename OrderedMap<Key, Value>::const_iterator OrderedMap<Key, Value>::find(const Key& key) const
{
    OMHashConstIterator hit = data.find(key);
    if (hit == data.end()) {
        return end();
    }

    return const_iterator(hit.value().second, &data);
}

#endif // ORDEREDMAP_H


code:
/****************************************************************************
**
** Copyright (C) 2015 Corentin Chary <corentin.chary@gmail.cm>
**
** This file could be part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef QPRIORITY_QUEUE_H
#define QPRIORITY_QUEUE_H

#include <QtCore/qalgorithms.h>

QT_BEGIN_HEADER

QT_BEGIN_NAMESPACE

QT_MODULE(Core)

template <class T> class QList;

#ifndef QT_NO_STL
#include <queue>
#include <vector>
#else
/* Fallback class used when stl is not available */
template <class T, typename LessThan = qLess < T > >
class QPriorityQueuePrivate {
public:
    inline QPriorityQueuePrivate(LessThan l) : lessThan(l), d() {}
    inline ~QPriorityQueuePrivate() {}

    inline void clear() { return d.clear(); }
    inline int size() const { return d.size(); }
    inline bool empty() const { return d.empty(); }

    inline T &top() { return d.front(); }

    void pop();
    void push(const T &value);
private:
    inline int parent(int i) {
    return (i - 1) / 2;
    }
    inline int leftChild(int i) {
    return 2 * i + 1;
    }
    inline int rightChild(int i) {
    return 2 * i + 2;
    }

    LessThan lessThan;
    QList < T > d;
};
#endif

template <class T, typename LessThan = qLess < T > >
class Q_CORE_EXPORT QPriorityQueue
{
public:
    inline QPriorityQueue(LessThan l = qLess < T >())
      : lessThan(l), d(lessThan) { }
    inline QPriorityQueue(const QPriorityQueue<T> &q)
      : lessThan(q.lessThan), d(q.d) { }
    inline ~QPriorityQueue() { }

    QPriorityQueue<T> &operator=(const QPriorityQueue<T> &q) {
      d = q.d; return *this;
    }

    inline int size() const { return d.size(); }
    inline bool isEmpty() const { return empty(); }

    // like qlist
    void clear();
    void append(const T &t) { return enqueue(t); }
    void append(const QList<T> &t);
    T takeFirst() { return dequeue(); }
    inline int length() const { return size(); } // Same as count()
    inline T& first() { return head(); }
    inline const T& first() const { return head(); }
    inline void removeFirst() { pop(); }
    inline bool startsWith(const T &t) const
    { return !isEmpty() && first() == t; }

    // like qqueue
    inline void enqueue(const T &t) { push(t); }
    inline T dequeue() { T t = d.top();	d.pop(); return t; }
    inline T &head() { return const_cast<T&>(top()); }
    inline const T &head() const { return top(); }

    // stl compatibility
    typedef int size_type;
    typedef T value_type;

    inline bool empty() const { return d.empty(); }
    inline const value_type& top() { Q_ASSERT(!isEmpty()); return d.top(); }
    inline void push(const value_type& x) { return d.push(x); }
    inline void pop() { Q_ASSERT(!isEmpty()); d.pop(); }

    // comfort
    inline QPriorityQueue<T> &operator+=(const T &t) {
      enqueue(t); return *this;
    }
    inline QPriorityQueue<T> &operator<< (const T &t) {
      enqueue(t); return *this;
    }
    inline QPriorityQueue<T> &operator>> (T &t) {
      t = d.top(); d.pop(); return *this;
    }

#ifndef QT_NO_STL
    static inline QPriorityQueue<T>fromStdPriorityQueue(
        const std::priority_queue<T> &q) {
      QPriorityQueue<T> tmp; tmp.d = q; return tmp;
    }
    inline std::priority_queue<T> toStdPriorityQueue() const {
      return d;
    }
#endif

private:
    LessThan lessThan;
#ifndef QT_NO_STL
    std::priority_queue <T, std::vector < T >, LessThan> d;
#else
    QPriorityQueuePrivate <T> d;
#endif
};

#ifndef QT_NO_STL

template <typename T, typename LessThan>
Q_INLINE_TEMPLATE void QPriorityQueue<T, LessThan>::clear()
{
    d = std::priority_queue <T, std::vector < T >, LessThan>(lessThan);
    //d = std::priority_queue<T>(lessThan);
}

#else

template <typename T, typename LessThan>
Q_INLINE_TEMPLATE void QPriorityQueue<T, LessThan>::clear()
{
    d.clear();
}

#endif

template <typename T, typename LessThan>
Q_OUTOFLINE_TEMPLATE void QPriorityQueue<T, LessThan>::append(const QList<T> &t)
{
    foreach (T & e, t)
    push(e);
}

// Re-implement std::priority_queue if STL not available, probably
// less efficient.
#ifdef QT_NO_STL
/*!
 *  Pop an element from the queue and reorder it using an
 *  inlined binary heap.
 *
 *  \internal
 */
template <typename T, typename LessThan>
Q_OUTOFLINE_TEMPLATE void QPriorityQueuePrivate<T, LessThan>::pop()
{
    int i = 0;
    ssize_t size = d.size();;

    if(!size)
    return;
    if(size == 1)
    return d.clear();

    d[0] = d.takeLast();

    while(i < size - 1) {
    int left = leftChild(i);
    int right = rightChild(i);
    bool validLeft = left < size;
    bool validRight = right < size;

    if(validLeft && lessThan(d.at(i), d.at(left)))
        if(validRight && !lessThan(d.at(right), d.at(left))) {
        d.swap(i, right);
        i = right;
        } else {
        d.swap(i, left);
        i = left;
        }
    else if(validRight && lessThan(d.at(i), d.at(right))) {
        d.swap(i, right);
        i = right;
    }
    else
        break;
    }
}

/*!
 *  Push an element with a given priority to the right place.
 *
 *  \internal
 */
template <typename T, typename LessThan>
Q_OUTOFLINE_TEMPLATE void QPriorityQueuePrivate<T, LessThan>::push(const T &value)
{
    int i = d.size();
    d.append(value);
    while(i != 0 && !lessThan(d.at(i), d.at(parent(i)))) {
    d.swap(i, parent(i));
    i = parent(i);
    }
}
#endif

QT_END_NAMESPACE

QT_END_HEADER

#endif // QPRIORITY_QUEUE_H


code:
#include "qt_helpers.hpp"

#include <QString>
#include <QFont>
#include <QWidget>
#include <QStyle>
#include <QVariant>

QString font_as_stylesheet (QFont const& font)
{
  QString font_weight;
  switch (font.weight ())
    {
    case QFont::Light: font_weight = "light"; break;
    case QFont::Normal: font_weight = "normal"; break;
    case QFont::DemiBold: font_weight = "demibold"; break;
    case QFont::Bold: font_weight = "bold"; break;
    case QFont::Black: font_weight = "black"; break;
    }
  return QString {
      " font-family: %1;\n"
      " font-size: %2pt;\n"
      " font-style: %3;\n"
      " font-weight: %4;\n"}
  .arg (font.family ())
     .arg (font.pointSize ())
     .arg (font.styleName ())
     .arg (font_weight);
}

void update_dynamic_property (QWidget * widget, char const * property, QVariant const& value)
{
  widget->setProperty (property, value);
  widget->style ()->unpolish (widget);
  widget->style ()->polish (widget);
  widget->update ();
}


code:
#ifndef QT_HELPERS_HPP_
#define QT_HELPERS_HPP_

#include <stdexcept>

#include <QString>
#include <QMetaObject>
#include <QHostAddress>
#include <QDataStream>
#include <QMetaType>
#include <QMetaEnum>

#define ENUM_QDATASTREAM_OPS_DECL(CLASS, ENUM)				\
  QDataStream& operator << (QDataStream&, CLASS::ENUM const&);			\
  QDataStream& operator >> (QDataStream&, CLASS::ENUM&);

#define ENUM_QDATASTREAM_OPS_IMPL(CLASS, ENUM)				\
  QDataStream& operator << (QDataStream& os, CLASS::ENUM const& v)		\
  {									\
    auto const& mo = CLASS::staticMetaObject;				\
    return os << mo.enumerator (mo.indexOfEnumerator (#ENUM)).valueToKey (v); \
  }									\
									\
  QDataStream& operator >> (QDataStream& is, CLASS::ENUM& v)		\
  {									\
    char * buffer;							\
    is >> buffer;							\
    bool ok {false};							\
    auto const& mo = CLASS::staticMetaObject;				\
    auto const& me = mo.enumerator (mo.indexOfEnumerator (#ENUM)); \
    if (buffer)								\
      {									\
	v = static_cast<CLASS::ENUM> (me.keyToValue (buffer, &ok));	\
	delete [] buffer;						\
      }									\
    if (!ok)								\
      {									\
	v = static_cast<CLASS::ENUM> (me.value (0));			\
      }									\
    return is;								\
  }

#define ENUM_CONVERSION_OPS_DECL(CLASS, ENUM)	\
  QString enum_to_qstring (CLASS::ENUM const&);

#define ENUM_CONVERSION_OPS_IMPL(CLASS, ENUM)				\
  QString enum_to_qstring (CLASS::ENUM const& m)				\
  {									\
    auto const& mo = CLASS::staticMetaObject;				\
    return QString {mo.enumerator (mo.indexOfEnumerator (#ENUM)).valueToKey (m)}; \
  }

#if QT_VERSION >= 0x050500

// Qt 5.5 now has Q_ENUM which registers enumns better
#define ENUM_QDEBUG_OPS_DECL(CLASS, ENUM)
#define ENUM_QDEBUG_OPS_IMPL(CLASS, ENUM)

#else

#define Q_ENUM(E)

#include <QDebug>

class QVariant;

#define ENUM_QDEBUG_OPS_DECL(CLASS, ENUM)				\
  QDebug operator << (QDebug, CLASS::ENUM const&);

#define ENUM_QDEBUG_OPS_IMPL(CLASS, ENUM)				\
  QDebug operator << (QDebug d, CLASS::ENUM const& m)				\
  {									\
    auto const& mo = CLASS::staticMetaObject;				\
    return d << mo.enumerator (mo.indexOfEnumerator (#ENUM)).valueToKey (m); \
  }

#endif

inline
void throw_qstring (QString const& qs)
{
  throw std::runtime_error {qs.toLocal8Bit ().constData ()};
}

QString font_as_stylesheet (QFont const&);

// do what is necessary to change a dynamic property and trigger any
// conditional style sheet updates
void update_dynamic_property (QWidget *, char const * property, QVariant const& value);

template <class T>
class VPtr
{
public:
  static T * asPtr (QVariant v)
  {
    return reinterpret_cast<T *> (v.value<void *> ());
  }

  static QVariant asQVariant(T * ptr)
  {
    return qVariantFromValue (reinterpret_cast<void *> (ptr));
  }
};

// Register some useful Qt types with QMetaType
Q_DECLARE_METATYPE (QHostAddress);

#endif


code:
#include "revision_utils.hpp"

#include <cstring>

#include <QCoreApplication>
#include <QRegularExpression>

QString revision (QString const& svn_rev_string)
{
  return "";
}

QString version (bool include_patch)
{
#if defined (CMAKE_BUILD)
  QString v {WSJTX_STRINGIZE (WSJTX_VERSION_MAJOR) "." WSJTX_STRINGIZE (WSJTX_VERSION_MINOR)};
  if (include_patch)
    {
      v += "." WSJTX_STRINGIZE (WSJTX_VERSION_PATCH)
#if 0
# if defined (WSJTX_RC)
        + "-rc" WSJTX_STRINGIZE (WSJTX_RC)
# endif
#endif
        ;
    }
#else
  QString v {"Not for Release"};
#endif

  return v;
}

QString program_title (QString const& revision)
{
  QString id {"JS8Call de KN4CRD (v%1)"};
  return id.arg(QCoreApplication::applicationVersion ());
}


code:
#ifndef REVISION_UTILS_HPP__
#define REVISION_UTILS_HPP__

#include <QString>

QString revision (QString const& svn_rev_string = QString {});
QString version (bool include_patch = true);
QString program_title (QString const& revision = QString {});

#endif


code:
// Simple bargraph dB meter
// Implemented by Edson Pereira PY2SDR
//

#include "signalmeter.h"

#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QFontMetrics>
#include <QDebug>

#include <meterwidget.h>

#include "moc_signalmeter.cpp"

#define MAXDB 90

class Scale final
  : public QWidget
{
public:
  explicit Scale (QWidget * parent = 0)
    : QWidget {parent}
  {
    setSizePolicy (QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
  }

  QSize sizeHint () const override
  {
    return minimumSizeHint ();
  }

  QSize minimumSizeHint () const override
  {
    QFontMetrics font_metrics {font (), nullptr};
    return {tick_length + text_indent + font_metrics.width ("00+"), (font_metrics.height () + line_spacing) * range};
  }

protected:
  void paintEvent (QPaintEvent * event) override
  {
    QWidget::paintEvent (event);

    QPainter p {this};
    p.setPen(Qt::white);
    auto const& target = contentsRect ();
    QFontMetrics font_metrics {p.font (), this};
    auto font_offset = font_metrics.ascent () / 2;
    p.drawLine (target.left (), target.top () + font_offset, target.left (), target.bottom () - font_offset - font_metrics.descent ());
    for (int i = 0; i <= range; ++i)
      {
        p.save ();
        p.translate (target.left ()
                     , target.top () + font_offset + i * (target.height () - font_metrics.ascent () - font_metrics.descent ()) / range);
        p.drawLine (0, 0, tick_length, 0);
	if((i%2==1)) {
	  auto text = QString::number ((range - i) * scale);
	  p.drawText (tick_length + text_indent, font_offset, text);
	}
        p.restore ();
      }
  }

private:
  static int constexpr tick_length {4};
  static int constexpr text_indent {2};
  static int constexpr line_spacing {0};
  static int constexpr range {MAXDB/10};
  static int constexpr scale {10};
};

SignalMeter::SignalMeter (QWidget * parent)
  : QFrame {parent}
{
  auto outer_layout = new QVBoxLayout;
  outer_layout->setSpacing (0);

  auto inner_layout = new QHBoxLayout;
  inner_layout->setContentsMargins (9, 0, 9, 0);
  inner_layout->setSpacing (0);

  m_meter = new MeterWidget;
  m_meter->setSizePolicy (QSizePolicy::Minimum, QSizePolicy::Minimum);
  //inner_layout->addWidget (m_meter);

  m_scale = new Scale;
  inner_layout->addWidget (m_scale);

  // add this second...
  inner_layout->addWidget (m_meter);

  m_reading = new QLabel(this);
  auto p = m_reading->palette();
  p.setColor(m_reading->foregroundRole(), Qt::white);
  m_reading->setPalette(p);

  outer_layout->addLayout (inner_layout);
  outer_layout->addWidget (m_reading);
  setLayout (outer_layout);
}

void SignalMeter::setValue(float value, float valueMax)
{
  if(value<0) value=0;
  QFontMetrics font_metrics {m_scale->font (), nullptr};
  m_meter->setContentsMargins (0, font_metrics.ascent () / 2, 0, font_metrics.ascent () / 2 + font_metrics.descent ());
  m_meter->setValue(int(value));
  m_meter->set_sigPeak(valueMax);
  QString t;
  t.sprintf("%d dB",int(value+0.5));
  m_reading->setText(t);
}


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

#include <QFrame>

class QLabel;
class MeterWidget;

class SignalMeter final
  : public QFrame
{
  Q_OBJECT

public:
  explicit SignalMeter (QWidget * parent = nullptr);

public slots:
  void setValue (float value, float valueMax);

private:
  MeterWidget * m_meter;
  QWidget * m_scale;
  QLabel * m_reading;
};

#endif // SIGNALMETER_H


code:
// -*- Mode: C++ -*-
#ifndef SLEEP_H
#define SLEEP_H
#include <qthread.h>

class Sleep : public QThread
{
public:
  static void msleep(int ms) {
    QThread::msleep(ms);
  }
  static int idealThreadCount() {
    return QThread::idealThreadCount();
  }
};

#endif // SLEEP_H


code:
#include "soundin.h"

#include <QAudioDeviceInfo>
#include <QAudioFormat>
#include <QAudioInput>
#include <QSysInfo>
#include <QDebug>

#include "DriftingDateTime.h"

#include "moc_soundin.cpp"

bool SoundInput::audioError () const
{
  bool result (true);

  Q_ASSERT_X (m_stream, "SoundInput", "programming error");
  if (m_stream)
    {
      switch (m_stream->error ())
        {
        case QAudio::OpenError:
          Q_EMIT error (tr ("An error opening the audio input device has occurred."));
          break;

        case QAudio::IOError:
          Q_EMIT error (tr ("An error occurred during read from the audio input device."));
          break;

        case QAudio::UnderrunError:
          Q_EMIT error (tr ("Audio data not being fed to the audio input device fast enough."));
          break;

        case QAudio::FatalError:
          Q_EMIT error (tr ("Non-recoverable error, audio input device not usable at this time."));
          break;

        case QAudio::NoError:
          result = false;
          break;
        }
    }
  return result;
}

void SoundInput::start(QAudioDeviceInfo const& device, int framesPerBuffer, AudioDevice * sink, unsigned downSampleFactor, AudioDevice::Channel channel)
{
  Q_ASSERT (sink);

  stop ();

  m_sink = sink;

  QAudioFormat format (device.preferredFormat());
//  qDebug () << "Preferred audio input format:" << format;
  format.setChannelCount (AudioDevice::Mono == channel ? 1 : 2);
  format.setCodec ("audio/pcm");
  format.setSampleRate (12000 * downSampleFactor);
  format.setSampleType (QAudioFormat::SignedInt);
  format.setSampleSize (16);
  format.setByteOrder (QAudioFormat::Endian (QSysInfo::ByteOrder));
  if (!format.isValid ())
    {
      Q_EMIT error (tr ("Requested input audio format is not valid."));
      return;
    }

  if (!device.isFormatSupported (format))
    {
//      qDebug () << "Nearest supported audio format:" << device.nearestFormat (format);
      Q_EMIT error (tr ("Requested input audio format is not supported on device."));
      return;
    }
//  qDebug () << "Selected audio input format:" << format;

  m_stream.reset (new QAudioInput {device, format});
  if (audioError ())
    {
      return;
    }

  connect (m_stream.data(), &QAudioInput::stateChanged, this, &SoundInput::handleStateChanged);

  m_stream->setBufferSize (m_stream->format ().bytesForFrames (framesPerBuffer));
  if (sink->initialize (QIODevice::WriteOnly, channel))
    {
      m_stream->start (sink);
      audioError ();
    }
  else
    {
      Q_EMIT error (tr ("Failed to initialize audio sink device"));
    }
}

void SoundInput::suspend ()
{
  if (m_stream)
    {
      m_stream->suspend ();
      audioError ();
    }
}

void SoundInput::resume ()
{
//  qDebug() << "Resume" << fmod(0.001*DriftingDateTime::currentMSecsSinceEpoch(),6.0);
  if (m_sink)
    {
      m_sink->reset ();
    }

  if (m_stream)
    {
      m_stream->resume ();
      audioError ();
    }
}

void SoundInput::handleStateChanged (QAudio::State newState) const
{
  // qDebug () << "SoundInput::handleStateChanged: newState:" << newState;

  switch (newState)
    {
    case QAudio::IdleState:
      Q_EMIT status (tr ("Idle"));
      break;

    case QAudio::ActiveState:
      Q_EMIT status (tr ("Receiving"));
      break;

    case QAudio::SuspendedState:
      Q_EMIT status (tr ("Suspended"));
      break;

    case QAudio::StoppedState:
      if (audioError ())
        {
          Q_EMIT status (tr ("Error"));
        }
      else
        {
          Q_EMIT status (tr ("Stopped"));
        }
      break;
    }
}

void SoundInput::stop()
{
  if (m_stream)
    {
      m_stream->stop ();
    }
  m_stream.reset ();

  if (m_sink)
    {
      m_sink->close ();
    }
}

SoundInput::~SoundInput ()
{
  stop ();
}


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

#include <QObject>
#include <QString>
#include <QDateTime>
#include <QScopedPointer>
#include <QPointer>
#include <QAudioInput>

#include "AudioDevice.hpp"

class QAudioDeviceInfo;
class QAudioInput;

// Gets audio data from sound sample source and passes it to a sink device
class SoundInput
  : public QObject
{
  Q_OBJECT;

public:
  SoundInput (QObject * parent = nullptr)
    : QObject {parent}
    , m_sink {nullptr}
  {
  }

  ~SoundInput ();

  // sink must exist from the start call until the next start call or
  // stop call
  Q_SLOT void start(QAudioDeviceInfo const&, int framesPerBuffer, AudioDevice * sink, unsigned downSampleFactor, AudioDevice::Channel = AudioDevice::Mono);
  Q_SLOT void suspend ();
  Q_SLOT void resume ();
  Q_SLOT void stop ();

  Q_SIGNAL void error (QString message) const;
  Q_SIGNAL void status (QString message) const;

private:
  // used internally
  Q_SLOT void handleStateChanged (QAudio::State) const;

  bool audioError () const;

  QScopedPointer<QAudioInput> m_stream;
  QPointer<AudioDevice> m_sink;
};

#endif


code:
#include "soundout.h"

#include <QDateTime>
#include <QAudioDeviceInfo>
#include <QAudioOutput>
#include <QSysInfo>
#include <qmath.h>
#include <QDebug>

#include "moc_soundout.cpp"

#if defined (WIN32)
# define MS_BUFFERED 1000u
#else
# define MS_BUFFERED 2000u
#endif

bool SoundOutput::audioError () const
{
  bool result (true);

  Q_ASSERT_X (m_stream, "SoundOutput", "programming error");
  if (m_stream) {
    switch (m_stream->error ())
      {
      case QAudio::OpenError:
        Q_EMIT error (tr ("An error opening the audio output device has occurred."));
        break;

      case QAudio::IOError:
        Q_EMIT error (tr ("An error occurred during write to the audio output device."));
        break;

      case QAudio::UnderrunError:
        Q_EMIT error (tr ("Audio data not being fed to the audio output device fast enough."));
        break;

      case QAudio::FatalError:
        Q_EMIT error (tr ("Non-recoverable error, audio output device not usable at this time."));
        break;

      case QAudio::NoError:
        result = false;
        break;
      }
  }
  return result;
}

void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, unsigned msBuffered)
{
  Q_ASSERT (0 < channels && channels < 3);

  m_msBuffered = msBuffered;

  QAudioFormat format (device.preferredFormat ());
//  qDebug () << "Preferred audio output format:" << format;
  format.setChannelCount (channels);
  format.setCodec ("audio/pcm");
  format.setSampleRate (48000);
  format.setSampleType (QAudioFormat::SignedInt);
  format.setSampleSize (16);
  format.setByteOrder (QAudioFormat::Endian (QSysInfo::ByteOrder));
  if (!format.isValid ())
    {
      Q_EMIT error (tr ("Requested output audio format is not valid."));
    }
  if (!device.isFormatSupported (format))
    {
      Q_EMIT error (tr ("Requested output audio format is not supported on device."));
    }
//  qDebug () << "Selected audio output format:" << format;

  m_stream.reset (new QAudioOutput (device, format));
  audioError ();
  m_stream->setVolume (m_volume);
  m_stream->setNotifyInterval(100);

  connect (m_stream.data(), &QAudioOutput::stateChanged, this, &SoundOutput::handleStateChanged);

  //      qDebug() << "A" << m_volume << m_stream->notifyInterval();
}

void SoundOutput::restart (QIODevice * source)
{
  Q_ASSERT (m_stream);

  //
  // This buffer size is critical since for proper sound streaming. If
  // it is too short; high activity levels on the machine can starve
  // the audio buffer. On the other hand the Windows implementation
  // seems to take the length of the buffer in time to stop the audio
  // stream even if reset() is used.
  //
  // 2 seconds seems a reasonable compromise except for Windows
  // where things are probably broken.
  //
  // we have to set this before every start on the stream because the
  // Windows implementation seems to forget the buffer size after a
  // stop.
  m_stream->setBufferSize (m_stream->format().bytesForDuration((m_msBuffered ? m_msBuffered : MS_BUFFERED) * 1000));
  //  qDebug() << "B" << m_stream->bufferSize() <<
  //  m_stream->periodSize() << m_stream->notifyInterval();

  m_stream->start (source);
}

void SoundOutput::suspend ()
{
  if (m_stream && QAudio::ActiveState == m_stream->state ())
    {
      m_stream->suspend ();
      audioError ();
    }
}

void SoundOutput::resume ()
{
  if (m_stream && QAudio::SuspendedState == m_stream->state ())
    {
      m_stream->resume ();
      audioError ();
    }
}

void SoundOutput::reset ()
{
  if (m_stream)
    {
      m_stream->reset ();
      audioError ();
    }
}

void SoundOutput::stop ()
{
  if (m_stream)
    {
      m_stream->stop ();
      audioError ();
    }
}

qreal SoundOutput::attenuation () const
{
  return -(20. * qLn (m_volume) / qLn (10.));
}

void SoundOutput::setAttenuation (qreal a)
{
  Q_ASSERT (0. <= a && a <= 999.);
  m_volume = qPow(10.0, -a/20.0);
  //  qDebug () << "SoundOut: attn = " << a << ", vol = " << m_volume;
  if (m_stream)
    {
      m_stream->setVolume (m_volume);
    }
}

void SoundOutput::resetAttenuation ()
{
  m_volume = 1.;
  if (m_stream)
    {
      m_stream->setVolume (m_volume);
    }
}

void SoundOutput::handleStateChanged (QAudio::State newState)
{
  // qDebug () << "SoundOutput::handleStateChanged: newState:" << newState;

  switch (newState)
    {
    case QAudio::IdleState:
      Q_EMIT status (tr ("Idle"));
      break;

    case QAudio::ActiveState:
      Q_EMIT status (tr ("Sending"));
      break;

    case QAudio::SuspendedState:
      Q_EMIT status (tr ("Suspended"));
      break;

    case QAudio::StoppedState:
      if (audioError ())
        {
          Q_EMIT status (tr ("Error"));
        }
      else
        {
          Q_EMIT status (tr ("Stopped"));
        }
      break;
    }
}


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

#include <QObject>
#include <QString>
#include <QAudioOutput>
#include <QAudioDeviceInfo>

class QAudioDeviceInfo;

// An instance of this sends audio data to a specified soundcard.

class SoundOutput
  : public QObject
{
  Q_OBJECT;

public:
  SoundOutput ()
    : m_msBuffered {0u}
    , m_volume {1.0}
  {
  }

  qreal attenuation () const;

public Q_SLOTS:
  void setFormat (QAudioDeviceInfo const& device, unsigned channels, unsigned msBuffered = 0u);
  void restart (QIODevice *);
  void suspend ();
  void resume ();
  void reset ();
  void stop ();
  void setAttenuation (qreal);	/* unsigned */
  void resetAttenuation ();	/* to zero */

Q_SIGNALS:
  void error (QString message) const;
  void status (QString message) const;

private:
  bool audioError () const;

private Q_SLOTS:
  void handleStateChanged (QAudio::State);

private:
  QScopedPointer<QAudioOutput> m_stream;
  unsigned m_msBuffered;
  qreal m_volume;
};

#endif


code:
from __future__ import print_function

import json
from socket import socket, AF_INET, SOCK_DGRAM
import time

listen = ('127.0.0.1', 2237)

def from_message(content):
    try:
        return json.loads(content)
    except ValueError:
        return {}

def to_message(typ, value='', params=None):
    if params is None:
        params = {}
    return json.dumps({'type': typ, 'value': value, 'params': params})

class Server(object):
    first = True
    def process(self, message):
        typ = message.get('type', '')
        value = message.get('value', '')
        params = message.get('params', {})
        if not typ:
            return

        print('->', typ)

        if value:
            print('-> value', value)

        if params:
            print('-> params: ', params)

        #### if typ == 'PING':
        ####     if self.first:
        ####         self.send('RX.GET_BAND_ACTIVITY')
        ####         self.send('TX.SET_TEXT', 'HERE WE GO')
        ####         time.sleep(1)
        ####         self.send('WINDOW.RAISE')
        ####         self.first = False

        #### if typ == 'PING':
        ####     self.send('STATION.GET_GRID')
        ####     self.send('RIG.GET_FREQ')
        ####     self.send('STATION.GET_CALLSIGN')
        ####     self.send('RX.GET_CALL_ACTIVITY')

        #### elif typ == 'STATION.GRID':
        ####     if value != 'EM73TU49TQ':
        ####         self.send('STATION.SET_GRID', 'EM73TU49TQ')

        #### elif typ == 'RIG.FREQ':
        ####     if params.get('DIAL', 0) != 14064000:
        ####         self.send('RIG.SET_FREQ', '', {"DIAL": 14064000, "OFFSET": 977})
        ####         self.send('TX.SEND_MESSAGE', 'HELLO WORLD')

        elif typ == 'CLOSE':
            self.close()

    def send(self, *args, **kwargs):
        message = to_message(*args, **kwargs)
        print('outgoing message:', message)
        self.sock.sendto(message, self.reply_to)

    def listen(self):
        print('listening on', ':'.join(map(str, listen)))
        self.sock = socket(AF_INET, SOCK_DGRAM)
        self.sock.bind(listen)
        self.listening = True
        try:
            while self.listening:
                content, addr = self.sock.recvfrom(65500)
                print('incoming message:', ':'.join(map(str, addr)))

                try:
                    message = json.loads(content)
                except ValueError:
                    message = {}

                if not message:
                    continue

                self.reply_to = addr
                self.process(message)

        finally:
            self.sock.close()

    def close(self):
        self.listening = False

def main():
    s = Server()
    s.listen()

if __name__ == '__main__':
    main()


code:
/**
 * This file is part of JS8Call.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * (C) 2018 Jordan Sherer <kn4crd@gmail.com> - All Rights Reserved
 *
 **/

#include <QDebug>
#include <QMap>
#include <QSet>

#define CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS
#include "crc.h"

#include "varicode.h"
#include "jsc.h"

const int nalphabet = 41;
QString alphabet = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"}; // alphabet to encode _into_ for FT8 freetext transmission
QString alphabet72 = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-+/?."};
QString grid_pattern = {R"((?<grid>[A-X]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*)+)"};
QString orig_compound_callsign_pattern = {R"((?<callsign>(\d|[A-Z])+\/?((\d|[A-Z]){2,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?))"};
QString compound_callsign_pattern = {R"((?<callsign>\b(?<prefix>[A-Z0-9]{1,4}\/)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix>\/[A-Z0-9]{1,4})?)\b)"};
QString pack_callsign_pattern = {R"(([0-9A-Z ])([0-9A-Z])([0-9])([A-Z ])([A-Z ])([A-Z ]))"};
QString alphanumeric = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ "}; // callsign and grid alphabet

QMap<QString, int> directed_cmds = {
    // any changes here need to be made also in the directed regular xpression for parsing

    {"?",     0  }, // query snr
    {"@",     1  }, // query qth
    {"&",     2  }, // query station message
    {"$",     3  }, // query station(s) heard
    {"^",     4  }, // query grid
    {">",     5  }, // relay message
    //{"|",     6  }, // retransmit message
    //{"!",     7  }, // alert message
    {"#",     8  }, // all or nothing message

    // {"=",      9  }, // unused
    // {"/",     10  }, // unused

    {" BEACON",     -1 }, // this is my beacon (unused except for faux processing of beacons as directed commands)
    {" BEACON ACK", 12 }, // i received your beacon at this snr
    {" BEACON REQ", 13 }, // can you transmit a beacon to callsign?

    {" APRS:",   14  }, // send an aprs packet

    {" GRID",    15  }, // this is my current grid locator
    {" QTC",     16  }, // this is my qtc message
    {" QTH",     17  }, // this is my qth message

    {" FB",      18  }, // fine business
    {" HW CPY?", 19  }, // how do you copy?
    {" HEARING", 20  }, // i am hearing the following stations
    {" RR",      21  }, // roger roger
    {" QSL?",    22  }, // do you copy?
    {" QSL",     23  }, // i copy
    {" QRZ?",    24  }, // who is calling me
    {" SNR",     25  }, // seen a station at the provided snr
    {" NO",      26  }, // negative confirm
    {" YES",     27  }, // confirm
    {" 73",      28  }, // best regards, end of contact
    {" ACK",     29  }, // acknowledge
    {" AGN?",    30  }, // repeat message
    {" ",        31 },  // send freetext
};

QSet<int> allowed_cmds = {-1, 0, 1, 2, 3, 4, 5, /*6,*/ /*7,*/ 8, /*...*/ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};

QSet<int> buffered_cmds = {3, 5, /*6,*/ /*7,*/ 8, 13, 14, 15};

QSet<int> snr_cmds = {12, 25};

QMap<int, int> checksum_cmds = {
    {  5, 16 },
    {  8, 32 },
    { 13, 16 },
    { 14, 16 },
    { 15,  0 }
};

QString callsign_pattern = QString("(?<callsign>[A-Z0-9/]+)");
QString optional_cmd_pattern = QString("(?<cmd>\\s?(?:AGN[?]|ACK|73|YES|NO|SNR|QSL[?]?|RR|HEARING|HW CPY[?]|FB|QTH|QTC|GRID|APRS[:]|BEACON (ACK|REQ)|QRZ[?]|[?@&$%#^> ]))?");
QString optional_grid_pattern = QString("(?<grid>\\s?[A-R]{2}[0-9]{2})?");
QString optional_extended_grid_pattern = QString("^(?<grid>\\s?(?:[A-R]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*))?");
QString optional_num_pattern = QString("(?<num>(?<=SNR|HEARING|BEACON ACK)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");

QRegularExpression directed_re("^"                    +
                               callsign_pattern       +
                               optional_cmd_pattern   +
                               optional_num_pattern);

QRegularExpression beacon_re(R"(^\s*(?<type>CQCQCQ|CQ QRPP?|CQ DX|CQ TEST|CQ( CQ){0,2}|BEACON)(?:\s(?<grid>[A-R]{2}[0-9]{2}))?\b)");

QRegularExpression compound_re("^\\s*[<]"                  +
                               callsign_pattern        +
                               "(?<extra>"             +
                                 optional_grid_pattern +
                                 optional_cmd_pattern  +
                                 optional_num_pattern  +
                               ")[>]");

QMap<QString, QString> hufftable = {
    // char   code                 weight
    { " " , "01" }, // 1.0
    { "E" , "100" }, // 0.5
    { "T" , "1101" }, // 0.333333333333
    { "A" , "0011" }, // 0.25
    { "O" , "11111" }, // 0.2
    { "I" , "11100" }, // 0.166666666667
    { "N" , "10111" }, // 0.142857142857
    { "S" , "10100" }, // 0.125
    { "H" , "00011" }, // 0.111111111111
    { "R" , "00000" }, // 0.1
    { "D" , "111011" }, // 0.0909090909091
    { "L" , "110011" }, // 0.0833333333333
    { "C" , "110001" }, // 0.0769230769231
    { "U" , "101101" }, // 0.0714285714286
    { "M" , "101011" }, // 0.0666666666667
    { "W" , "001011" }, // 0.0625
    { "F" , "001001" }, // 0.0588235294118
    { "G" , "000101" }, // 0.0555555555556
    { "Y" , "000011" }, // 0.0526315789474
    { "P" , "1111011" }, // 0.05
    { "B" , "1111001" }, // 0.047619047619
    { "." , "1110100" }, // 0.0434782608696
    { "V" , "1100101" }, // 0.0416666666667
    { "K" , "1100100" }, // 0.04
    { "-" , "1100001" }, // 0.0384615384615
    { "+" , "1100000" }, // 0.037037037037
    { "?" , "1011001" }, // 0.0344827586207
    { "!" , "1011000" }, // 0.0333333333333
    {"\"" , "1010101" }, // 0.0322580645161
    { "X" , "1010100" }, // 0.03125
    { "0" , "0010101" }, // 0.030303030303
    { "J" , "0010100" }, // 0.0294117647059
    { "1" , "0010001" }, // 0.0285714285714
    { "Q" , "0010000" }, // 0.0277777777778
    { "2" , "0001001" }, // 0.027027027027
    { "Z" , "0001000" }, // 0.0263157894737
    { "3" , "0000101" }, // 0.025641025641
    { "5" , "0000100" }, // 0.025
    { "4" , "11110101" }, // 0.0243902439024
    { "9" , "11110100" }, // 0.0238095238095
    { "8" , "11110001" }, // 0.0232558139535
    { "6" , "11110000" }, // 0.0227272727273
    { "7" , "11101011" }, // 0.0222222222222
    { "/" , "11101010" }, // 0.0217391304348
};

QChar ESC = '\\';   // Escape char
QChar EOT = '\x04'; // EOT char

quint32 nbasecall = 37 * 36 * 10 * 27 * 27 * 27;
quint16 nbasegrid = 180 * 180;
quint16 nusergrid = nbasegrid + 10;
quint16 nmaxgrid  = (1<<15)-1;

QMap<QString, quint32> basecalls = {
    { "<....>",    nbasecall + 1 }, // incomplete callsign
    { "ALLCALL",   nbasecall + 2 },
    { "GROUPCALL", nbasecall + 3 },
};

QMap<quint32, QString> cqs = {
    { 0, "CQCQCQ" },
    { 1, "CQ DX"  },
    { 2, "CQ QRP" },
    { 3, "CQ TEST" },
    { 4, "CQ QRPP" },
    { 5, "CQ"},
    { 6, "CQ CQ"},
    { 7, "CQ CQ CQ"},
};

QMap<int, int> dbm2mw = {
    {0  , 1},       //   1mW
    {3  , 2},       //   2mW
    {7  , 5},       //   5mW
    {10 , 10},      //  10mW
    {13 , 20},      //  20mW
    {17 , 50},      //  50mW
    {20 , 100},     // 100mW
    {23 , 200},     // 200mW
    {27 , 500},     // 500mW
    {30 , 1000},    //    1W
    {33 , 2000},    //    2W
    {37 , 5000},    //    5W
    {40 , 10000},   //   10W
    {43 , 20000},   //   20W
    {47 , 50000},   //   50W
    {50 , 100000},  //  100W
    {53 , 200000},  //  200W
    {57 , 500000},  //  500W
    {60 , 1000000}, // 1000W
};

/*
 * UTILITIES
 */

int mwattsToDbm(int mwatts){
    int dbm = 0;
    auto values = dbm2mw.values();
    qSort(values);
    foreach(auto mw, values){
        if(mw < mwatts){ continue; }
        dbm = dbm2mw.key(mw);
        break;
    }

    return dbm;
}

int dbmTomwatts(int dbm){
    if(dbm2mw.contains(dbm)){
        return dbm2mw[dbm];
    }
    auto iter = dbm2mw.lowerBound(dbm);
    if(iter == dbm2mw.end()){
        return dbm2mw.last();
    }
    return iter.value();
}

QString Varicode::rstrip(const QString& str) {
  int n = str.size() - 1;
  for (; n >= 0; --n) {
    if (str.at(n).isSpace()) {
        continue;
    }
    return str.left(n + 1);
  }
  return "";
}

QString Varicode::lstrip(const QString& str) {
  int len = str.size();
  for (int n = 0; n < len; n++) {
      if(str.at(n).isSpace()){
          continue;
      }
      return str.mid(n);
  }
  return "";
}

/*
 * VARICODE
 */
QMap<QString, QString> Varicode::defaultHuffTable(){
    return hufftable;
}

QString Varicode::cqString(int number){
    if(!cqs.contains(number)){
        return QString{};
    }
    return cqs[number];
}

bool Varicode::startsWithCQ(QString text){
    foreach(auto cq, cqs.values()){
        if(text.startsWith(cq)){
            return true;
        }
    }
    return false;
}

QString Varicode::formatSNR(int snr){
    if(snr < -60 || snr > 60){
        return QString();
    }

    return QString("%1%2").arg(snr >= 0 ? "+" : "").arg(snr, snr < 0 ? 3 : 2, 10, QChar('0'));
}

QString Varicode::checksum16(QString const &input){
    auto fromBytes = input.toLocal8Bit();
    auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_16_KERMIT());
    auto checksum = Varicode::pack16bits(crc);
    if(checksum.length() < 3){
        checksum += QString(" ").repeated(3-checksum.length());
    }
    return checksum;
}

bool Varicode::checksum16Valid(QString const &checksum, QString const &input){
    auto fromBytes = input.toLocal8Bit();
    auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_16_KERMIT());
    return Varicode::pack16bits(crc) == checksum;
}

QString Varicode::checksum32(QString const &input){
    auto fromBytes = input.toLocal8Bit();
    auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_32_BZIP2());
    auto checksum = Varicode::pack32bits(crc);
    if(checksum.length() < 6){
        checksum += QString(" ").repeated(6-checksum.length());
    }
    return checksum;
}

bool Varicode::checksum32Valid(QString const &checksum, QString const &input){
    auto fromBytes = input.toLocal8Bit();
    auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_32_BZIP2());
    return Varicode::pack32bits(crc) == checksum;
}

QStringList Varicode::parseCallsigns(QString const &input){
    QStringList callsigns;
    QRegularExpression re(compound_callsign_pattern);
    QRegularExpressionMatchIterator iter = re.globalMatch(input);
    while(iter.hasNext()){
        QRegularExpressionMatch match = iter.next();
        if(!match.hasMatch()){
            continue;
        }
        QString callsign = match.captured("callsign");
        QRegularExpression m(grid_pattern);
        if(m.match(callsign).hasMatch()){
            continue;
        }
        callsigns.append(callsign);
    }
    return callsigns;
}

QStringList Varicode::parseGrids(const QString &input){
    QStringList grids;
    QRegularExpression re(grid_pattern);
    QRegularExpressionMatchIterator iter = re.globalMatch(input);
    while(iter.hasNext()){
        QRegularExpressionMatch match = iter.next();
        if(!match.hasMatch()){
            continue;
        }
        auto grid = match.captured("grid");
        if(grid == "RR73"){
            continue;
        }
        grids.append(grid);
    }
    return grids;
}

QList<QPair<int, QVector<bool>>> Varicode::huffEncode(const QMap<QString, QString> &huff, QString const& text){
    QList<QPair<int, QVector<bool>>> out;

    int i = 0;

    auto keys = huff.keys();
    qSort(keys.begin(), keys.end(), [](QString const &a, QString const &b){
        auto alen = a.length();
        auto blen = b.length();
        if(blen < alen){
            return true;
        }
        if(alen < blen){
            return false;
        }

        return b < a;
    });

    while(i < text.length()){
        bool found = false;
        foreach(auto ch, keys){
            if(text.midRef(i).startsWith(ch)){
                out.append({ ch.length(), Varicode::strToBits(huff[ch])});
                i += ch.length();
                found = true;
                break;
            }
        }

        if(!found){
            i++;
        }
    }

    return out;
}

QString Varicode::huffDecode(QMap<QString, QString> const &huff, QVector<bool> const& bitvec){
    QString text;

    QString bits = Varicode::bitsToStr(bitvec); //.mid(0, bitvec.length()-pad);

    // TODO: jsherer - this is naive...
    while(bits.length() > 0){
        bool found = false;
        foreach(auto key, huff.keys()){
            if(bits.startsWith(huff[key])){
                if(key == EOT){
                    text.append(" ");
                    found = false;
                    break;
                }
                text.append(key);
                bits = bits.mid(huff[key].length());
                found = true;
            }
        }
        if(!found){
            break;
        }
    }

    return text;
}

QSet<QString> Varicode::huffValidChars(const QMap<QString, QString> &huff){
    return QSet<QString>::fromList(huff.keys());
}

// convert char* array of 0 bytes and 1 bytes to bool vector
QVector<bool> Varicode::bytesToBits(char *bitvec, int n){
    QVector<bool> bits;
    for(int i = 0; i < n; i++){
        bits.append(bitvec[i] == 0x01);
    }
    return bits;
}

// convert string of 0s and 1s to bool vector
QVector<bool> Varicode::strToBits(QString const& bitvec){
    QVector<bool> bits;
    foreach(auto ch, bitvec){
        bits.append(ch == '1');
    }
    return bits;
}

QString Varicode::bitsToStr(QVector<bool> const& bitvec){
    QString bits;
    foreach(auto bit, bitvec){
        bits.append(bit ? "1" : "0");
    }
    return bits;
}

QVector<bool> Varicode::intToBits(quint64 value, int expected){
    QVector<bool> bits;

    while(value){
        bits.prepend((bool)(value & 1));
        value = value >> 1;
    }

    if(expected){
        while(bits.count() < expected){
            bits.prepend((bool) 0);
        }
    }

    return bits;
}

quint64 Varicode::bitsToInt(QVector<bool> const value){
    quint64 v = 0;
    foreach(bool bit, value){
        v = (v << 1) + (int)(bit);
    }
    return v;
}

quint64 Varicode::bitsToInt(QVector<bool>::ConstIterator start, int n){
    quint64 v = 0;
    for(int i = 0; i < n; i++){
        int bit = (int)(*start);
        v = (v << 1) + (int)(bit);
        start++;
    }
    return v;
}

QVector<bool> Varicode::bitsListToBits(QList<QVector<bool>> &list){
    QVector<bool> out;
    foreach(auto vec, list){
        out += vec;
    }
    return out;
}

quint8 Varicode::unpack5bits(QString const& value){
    return alphabet.indexOf(value.at(0));
}

// pack a 5-bit value from 0 to 31 into a single character
QString Varicode::pack5bits(quint8 packed){
    return alphabet.at(packed % 32);
}

quint8 Varicode::unpack6bits(QString const& value){
    return alphabet.indexOf(value.at(0));
}

// pack a 6-bit value from 0 to 40 into a single character
QString Varicode::pack6bits(quint8 packed){
    return alphabet.at(packed % 41);
}

quint16 Varicode::unpack16bits(QString const& value){
    int a = alphabet.indexOf(value.at(0));
    int b = alphabet.indexOf(value.at(1));
    int c = alphabet.indexOf(value.at(2));

    int unpacked = (nalphabet * nalphabet) * a + nalphabet * b + c;
    if(unpacked > (1<<16)-1){
        // BASE-41 can produce a value larger than 16 bits... ala "???" == 70643
        return 0;
    }

    return unpacked & ((1<<16)-1);
}

// pack a 16-bit value into a three character sequence
QString Varicode::pack16bits(quint16 packed){
    QString out;
    quint16 tmp = packed / (nalphabet * nalphabet);

    out.append(alphabet.at(tmp));

    tmp = (packed - (tmp * (nalphabet * nalphabet))) / nalphabet;
    out.append(alphabet.at(tmp));

    tmp = packed % nalphabet;
    out.append(alphabet.at(tmp));

    return out;
}

quint32 Varicode::unpack32bits(QString const& value){
    return (quint32)(unpack16bits(value.left(3))) << 16 | unpack16bits(value.right(3));
}

QString Varicode::pack32bits(quint32 packed){
    quint16 a = (packed & 0xFFFF0000) >> 16;
    quint16 b = packed & 0xFFFF;
    return pack16bits(a) + pack16bits(b);
}

quint64 Varicode::unpack64bits(QString const& value){
    return (quint64)(unpack32bits(value.left(6))) << 32 | unpack32bits(value.right(6));
}

QString Varicode::pack64bits(quint64 packed){
    quint32 a = (packed & 0xFFFFFFFF00000000) >> 32;
    quint32 b = packed & 0xFFFFFFFF;
    return pack32bits(a) + pack32bits(b);
}

// returns the first 64 bits and sets the last 8 bits in pRem
quint64 Varicode::unpack72bits(QString const& text, quint8 *pRem){
    quint64 value = 0;
    quint8 rem = 0;
    quint8 mask2 = ((1<<2)-1);

    for(int i = 0; i < 10; i++){
        value |= (quint64)(alphabet72.indexOf(text.at(i))) << (58-6*i);
    }

    quint8 remHigh = alphabet72.indexOf(text.at(10));
    value |= remHigh >> 2;

    quint8 remLow = alphabet72.indexOf(text.at(11));
    rem = ((remHigh & mask2) << 6) | remLow;

    if(pRem) *pRem = rem;
    return value;
}

QString Varicode::pack72bits(quint64 value, quint8 rem){
    QChar packed[12]; // 12 x 6bit characters

    quint8 mask4 = ((1<<4)-1);
    quint8 mask6 = ((1<<6)-1);

    quint8 remHigh = ((value & mask4) << 2) | (rem >> 6);
    quint8 remLow = rem & mask6;
    value = value >> 4;

    packed[11] = alphabet72.at(remLow);
    packed[10] = alphabet72.at(remHigh);

    for(int i = 0; i < 10; i++){
        packed[9-i] = alphabet72.at(value & mask6);
        value = value >> 6;
    }

    return QString(packed, 12);
}

//     //
// --- //
//     //

// pack a 4-digit alpha-numeric + space into a 22 bit value
// 21 bits for the data + 1 bit for a flag indicator
// giving us a total of 5.5 bits per character
quint32 Varicode::packAlphaNumeric22(QString const& value, bool isFlag){
    QString word = QString(value).replace(QRegExp("[^A-Z0-9]"), "");
    if(word.length() < 4){
        word = word + QString(" ").repeated(4-word.length());
    }

    quint32 a = 37 * 37 * 37 * alphanumeric.indexOf(word.at(0));
    quint32 b = 37 * 37 * alphanumeric.indexOf(word.at(1));
    quint32 c = 37 * alphanumeric.indexOf(word.at(2));
    quint32 d = alphanumeric.indexOf(word.at(3));

    quint32 packed = a + b + c + d;
    packed = (packed << 1) + (int)isFlag;

    return packed;
}

QString Varicode::unpackAlphaNumeric22(quint32 packed, bool *isFlag){
    QChar word[4];

    if(isFlag) *isFlag = packed & 1;
    packed = packed >> 1;

    quint32 tmp = packed % 37;
    word[3] = alphanumeric.at(tmp);
    packed = packed / 37;

    tmp = packed % 37;
    word[2] = alphanumeric.at(tmp);
    packed = packed / 37;

    tmp = packed % 37;
    word[1] = alphanumeric.at(tmp);
    packed = packed / 37;

    tmp = packed % 37;
    word[0] = alphanumeric.at(tmp);
    packed = packed / 37;

    return QString(word, 4);
}

// pack a callsign into a 28-bit value
quint32 Varicode::packCallsign(QString const& value){
    quint32 packed = 0;

    QString callsign = value.toUpper().trimmed();

    if(basecalls.contains(callsign)){
        return basecalls[callsign];
    }

    // workaround for swaziland
    if(callsign.startsWith("3DA0")){
        callsign = "3D0" + callsign.mid(4);
    }

    // workaround for guinea
    if(callsign.startsWith("3X") && 'A' <= callsign.at(2) && callsign.at(2) <= 'Z'){
        callsign = "Q" + callsign.mid(2);
    }

    int slen = callsign.length();
    if(slen < 2){
        return packed;
    }

    if(slen > 6){
        return packed;
    }

    QStringList permutations = { callsign };
    if(slen == 2){
        permutations.append(" " + callsign + "   ");
    }
    if(slen == 3){
        permutations.append(" " + callsign + "  ");
        permutations.append(callsign + "   ");
    }
    if(slen == 4){
        permutations.append(" " + callsign + " ");
        permutations.append(callsign + "  ");
    }
    if(slen == 5){
        permutations.append(" " + callsign);
        permutations.append(callsign + " ");
    }

    QString matched;
    QRegularExpression m(pack_callsign_pattern);
    foreach(auto permutation, permutations){
        auto match = m.match(permutation);
        if(match.hasMatch()){
            matched = match.captured(0);
        }
    }
    if(matched.isEmpty()){
        return packed;
    }
    if(matched.length() < 6){
        return packed;
    }

    packed = alphanumeric.indexOf(matched.at(0));
    packed = 36*packed + alphanumeric.indexOf(matched.at(1));
    packed = 10*packed + alphanumeric.indexOf(matched.at(2));
    packed = 27*packed + alphanumeric.indexOf(matched.at(3)) - 10;
    packed = 27*packed + alphanumeric.indexOf(matched.at(4)) - 10;
    packed = 27*packed + alphanumeric.indexOf(matched.at(5)) - 10;

    return packed;
}

QString Varicode::unpackCallsign(quint32 value){
    foreach(auto key, basecalls.keys()){
        if(basecalls[key] == value){
            return key;
        }
    }

    QChar word[6];
    quint32 tmp = value % 27 + 10;
    word[5] = alphanumeric.at(tmp);
    value = value/27;

    tmp = value % 27 + 10;
    word[4] = alphanumeric.at(tmp);
    value = value/27;

    tmp = value % 27 + 10;
    word[3] = alphanumeric.at(tmp);
    value = value/27;

    tmp = value % 10;
    word[2] = alphanumeric.at(tmp);
    value = value/10;

    tmp = value % 36;
    word[1] = alphanumeric.at(tmp);
    value = value/36;

    tmp = value;
    word[0] = alphanumeric.at(tmp);

    QString callsign(word, 6);
    if(callsign.startsWith("3D0")){
        callsign = "3DA0" + callsign.mid(3);
    }

    if(callsign.startsWith("Q") and 'A' <= callsign.at(1) && callsign.at(1) <= 'Z'){
        callsign = "3X" + callsign.mid(1);
    }

    return callsign;
}

QString Varicode::deg2grid(float dlong, float dlat){
    QChar grid[6];

    if(dlong < -180){
        dlong += 360;
    }
    if(dlong > 180){
        dlong -= 360;
    }

    int nlong = int(60.0*(180.0-dlong)/5);

    int n1 = nlong/240;
    int n2 = (nlong-240*n1)/24;
    int n3 = (nlong-240*n1-24*n2);

    grid[0] = QChar('A' + n1);
    grid[2] = QChar('0' + n2);
    grid[4] = QChar('a' + n3);

    int nlat=int(60.0*(dlat+90)/2.5);

    n1 = nlat/240;
    n2 = (nlat-240*n1)/24;
    n3 = (nlat-240*n1-24*n2);

    grid[1] = QChar('A' + n1);
    grid[3] = QChar('0' + n2);
    grid[5] = QChar('a' + n3);

    return QString(grid, 6);
}

QPair<float, float> Varicode::grid2deg(QString const &grid){
    QPair<float, float> longLat;

    QString g = grid;
    if(g.length() < 6){
        g = grid.left(4) + "mm";
    }

    g = g.left(4).toUpper() + g.right(2).toLower();

    int nlong = 180 - 20 * (g.at(0).toLatin1() - 'A');
    int n20d  = 2 * (g.at(2).toLatin1() - '0');
    float xminlong  = 5 * (g.at(4).toLatin1() - 'a' + 0.5);
    float dlong = nlong - n20d - xminlong/60.0;

    int nlat = -90 + 10*(g.at(1).toLatin1() - 'A') + g.at(3).toLatin1() - '0';
    float xminlat = 2.5 * (g.at(5).toLatin1() - 'a' + 0.5);
    float dlat = nlat + xminlat/60.0;

    longLat.first = dlong;
    longLat.second = dlat;

    return longLat;
}

// pack a 4-digit maidenhead grid locator into a 15-bit value
quint16 Varicode::packGrid(QString const& value){
    QString grid = QString(value).trimmed();
    if(grid.length() < 4){
        return (1<<15)-1;
    }

    auto pair = Varicode::grid2deg(grid.left(4));
    int ilong = pair.first;
    int ilat = pair.second + 90;

    return ((ilong + 180)/2) * 180 + ilat;
}

QString Varicode::unpackGrid(quint16 value){
    if(value > nbasegrid){
        return "";
    }

    float dlat = value % 180 - 90;
    float dlong = value / 180 * 2 - 180 + 2;

    return Varicode::deg2grid(dlong, dlat).left(4);
}

// pack a number or snr into an integer between 0 & 62
quint8 Varicode::packNum(QString const &num, bool *ok){
    int inum = 0;
    if(num.isEmpty()){
        if(ok) *ok = false;
        return inum;
    }

    inum = qMax(-30, qMin(num.toInt(ok, 10), 31));
    return inum + 30 + 1;
}

// pack a reduced fidelity command and a number into the extra bits provided between nbasegrid and nmaxgrid
quint8 Varicode::packCmd(quint8 cmd, quint8 num, bool *pPackedNum){
    //quint8 allowed = nmaxgrid - nbasegrid - 1;

    // if cmd == snr
    quint8 value = 0;
    auto cmdStr = directed_cmds.key(cmd);
    if(Varicode::isSNRCommand(cmdStr)){
        // 8 bits - 1 bit flag + 1 bit type + 6 bit number
        // [1][X][6]
        // X = 0 == SNR
        // X = 1 == BEACON ACK
        value = ((1 << 1) | (int)(cmdStr == " BEACON ACK")) << 6;
        value = value + (num & ((1<<6)-1));
        if(pPackedNum) *pPackedNum = true;
    } else {
        value = cmd & ((1<<7)-1);
        if(pPackedNum) *pPackedNum = false;
    }

    return value;
}

quint8 Varicode::unpackCmd(quint8 value, quint8 *pNum){
    // if the first bit is 1, this is an SNR with a number encoded in the lower 6 bits
    if(value & (1<<7)){
        if(pNum) *pNum = value & ((1<<6)-1);

        auto cmd = directed_cmds[" SNR"];
        if(value & (1<<6)){
            cmd = directed_cmds[" BEACON ACK"];
        }
        return cmd;
    } else {
        if(pNum) *pNum = 0;
        return value & ((1<<7)-1);
    }
}

bool Varicode::isSNRCommand(const QString &cmd){
    return directed_cmds.contains(cmd) && snr_cmds.contains(directed_cmds[cmd]);
}

bool Varicode::isCommandAllowed(const QString &cmd){
    return directed_cmds.contains(cmd) && allowed_cmds.contains(directed_cmds[cmd]);
}

bool Varicode::isCommandBuffered(const QString &cmd){
    return directed_cmds.contains(cmd) && (cmd.contains(" ") || buffered_cmds.contains(directed_cmds[cmd]));
}

int Varicode::isCommandChecksumed(const QString &cmd){
    if(!directed_cmds.contains(cmd) || !checksum_cmds.contains(directed_cmds[cmd])){
        return 0;
    }

    return checksum_cmds[directed_cmds[cmd]];
}

// CQCQCQ EM73
// CQ DX EM73
// CQ QRP EM73
// BEACON EM73
QString Varicode::packBeaconMessage(QString const &text, const QString &callsign, int *n){
    QString frame;

    auto parsedText = beacon_re.match(text);
    if(!parsedText.hasMatch()){
        if(n) *n = 0;
        return frame;
    }

    auto extra = parsedText.captured("grid");

    // Beacon Alt Type
    // ---------------
    // 1      0   BEACON
    // 1      1   CQCQCQ

    auto type = parsedText.captured("type");
    auto isAlt = type.startsWith("CQ");

    auto parsedCall = QRegularExpression(compound_callsign_pattern).match(callsign);
    if(!parsedCall.hasMatch()){
        if(n) *n = 0;
        return frame;
    }

    QString base = parsedCall.captured("base");

    bool isPrefix = false;
    QString fix = parsedCall.captured("prefix");
    if(!fix.isEmpty()){
        isPrefix = true;
    }

    if(!isPrefix){
        fix = parsedCall.captured("suffix");
    }

    quint16 packed_extra = nmaxgrid; // which will display an empty string
    if(extra.length() == 4 && QRegularExpression(grid_pattern).match(extra).hasMatch()){
        packed_extra = Varicode::packGrid(extra);
    }

    if(isAlt){
        packed_extra |= (1<<15);
    }

    quint8 cqNumber = cqs.key(type, 0);

    frame = packCompoundFrame(base, fix, isPrefix, FrameBeacon, packed_extra, cqNumber);
    if(frame.isEmpty()){
        if(n) *n = 0;
        return frame;
    }

    if(n) *n = parsedText.captured(0).length();
    return frame;
}

QStringList Varicode::unpackBeaconMessage(const QString &text, quint8 *pType, bool * isAlt, quint8 * pBits3){
    quint8 type = FrameBeacon;
    quint16 num = nmaxgrid;
    quint8 bits3 = 0;

    QStringList unpacked = unpackCompoundFrame(text, &type, &num, &bits3);
    if(unpacked.isEmpty() || type != FrameBeacon){
        return QStringList{};
    }

    unpacked.append(Varicode::unpackGrid(num & ((1<<15)-1)));

    if(isAlt) *isAlt = (num & (1<<15));
    if(pType) *pType = type;
    if(pBits3) *pBits3 = bits3;

    return unpacked;
}

// KN4CRD/XXXX EM73
// XXXX/KN4CRD EM73
// KN4CRD/P
QString Varicode::packCompoundMessage(QString const &text, int *n){
    QString frame;

    qDebug() << "trying to pack compound message" << text;
    auto parsedText = compound_re.match(text);
    if(!parsedText.hasMatch()){
        if(n) *n = 0;
        return frame;
    }

    qDebug() << parsedText.capturedTexts();

    QString callsign = parsedText.captured("callsign");
    QString grid = parsedText.captured("grid");
    QString cmd = parsedText.captured("cmd");
    QString num = parsedText.captured("num").trimmed();

    QString base;
    QString fix;
    bool isPrefix = false;

    if(basecalls.contains(callsign)){
        // if it's a basecall, use it verbatim with no prefix/suffix
        base = callsign;
        fix = "";
    } else {
        // otherwise, parse the callsign for prefix/suffix
        auto parsedCall = QRegularExpression(compound_callsign_pattern).match(callsign);
        if(!parsedCall.hasMatch()){
            if(n) *n = 0;
            return frame;
        }

        base = parsedCall.captured("base");
        fix = parsedCall.captured("prefix");
        if(!fix.isEmpty()){
            isPrefix = true;
        }
        if(!isPrefix){
            fix = parsedCall.captured("suffix");
        }
    }

    quint8 type = FrameCompound;
    quint16 extra = nmaxgrid;

    if (!cmd.isEmpty() && directed_cmds.contains(cmd) && Varicode::isCommandAllowed(cmd)){
        bool packedNum = false;
        qint8 inum = Varicode::packNum(num, nullptr);
        extra = nusergrid + Varicode::packCmd(directed_cmds[cmd], inum, &packedNum);

        type = FrameCompoundDirected;
    } else if(!grid.isEmpty()){
        extra = Varicode::packGrid(grid);
    }

    frame = Varicode::packCompoundFrame(base, fix, isPrefix, type, extra, 0);

    if(n) *n = parsedText.captured(0).length();
    return frame;
}

QStringList Varicode::unpackCompoundMessage(const QString &text, quint8 *pType, quint8 *pBits3){
    quint8 type = FrameCompound;
    quint16 extra = nmaxgrid;
    quint8 bits3 = 0;

    QStringList unpacked = unpackCompoundFrame(text, &type, &extra, &bits3);
    if(unpacked.isEmpty() || (type != FrameCompound && type != FrameCompoundDirected)){
        return QStringList {};
    }

    if(extra <= nbasegrid){
        unpacked.append(" " + Varicode::unpackGrid(extra));
    } else if (nusergrid <= extra && extra < nmaxgrid) {
        quint8 num = 0;
        auto cmd = Varicode::unpackCmd(extra - nusergrid, &num);
        auto cmdStr = directed_cmds.key(cmd);

        unpacked.append(cmdStr);

        if(Varicode::isSNRCommand(cmdStr)){
            unpacked.append(Varicode::formatSNR(num - 31));
        }
    }

    if(pType) *pType = type;
    if(pBits3) *pBits3 = bits3;

    return unpacked;
}

QString Varicode::packCompoundFrame(const QString &baseCallsign, const QString &fix, bool isPrefix, quint8 type, quint16 num, quint8 bits3){
    QString frame;

    // needs to be a compound type...
    if(type == FrameDataCompressed || type == FrameDataUncompressed || type == FrameDirected){
        return frame;
    }

    quint8 packed_flag = type;
    quint32 packed_base = Varicode::packCallsign(baseCallsign);
    quint32 packed_fix = Varicode::packAlphaNumeric22(fix, isPrefix);

    if(packed_base == 0 || packed_fix == 0){
        return frame;
    }

    quint16 mask11 = ((1<<11)-1)<<5;
    quint8 mask5 = (1<<5)-1;

    quint16 packed_11 = (num & mask11) >> 5;
    quint8 packed_5 = num & mask5;
    quint8 packed_8 = (packed_5 << 3) | bits3;

    // [3][28][22][11],[5][3] = 72
    auto bits = (
        Varicode::intToBits(packed_flag,  3) +
        Varicode::intToBits(packed_base, 28) +
        Varicode::intToBits(packed_fix,  22) +
        Varicode::intToBits(packed_11,   11)
    );

    return Varicode::pack72bits(Varicode::bitsToInt(bits), packed_8);
}

QStringList Varicode::unpackCompoundFrame(const QString &text, quint8 *pType, quint16 *pNum, quint8 *pBits3){
    QStringList unpacked;

    if(text.length() < 12 || text.contains(" ")){
        return unpacked;
    }

    // [3][28][22][11],[5][3] = 72
    quint8 packed_8 = 0;
    auto bits = Varicode::intToBits(Varicode::unpack72bits(text, &packed_8), 64);

    quint8 packed_5 = packed_8 >> 3;
    quint8 packed_3 = packed_8 & ((1<<3)-1);

    quint8 packed_flag = Varicode::bitsToInt(bits.mid(0, 3));

    // needs to be a beacon type...
    if(packed_flag == FrameDataCompressed || packed_flag == FrameDataUncompressed || packed_flag == FrameDirected){
        return unpacked;
    }

    quint32 packed_base = Varicode::bitsToInt(bits.mid(3, 28));
    quint32 packed_fix = Varicode::bitsToInt(bits.mid(31, 22));
    quint16 packed_11 = Varicode::bitsToInt(bits.mid(53, 11));

    QString base = Varicode::unpackCallsign(packed_base).trimmed();

    bool isPrefix = false;
    QString fix = Varicode::unpackAlphaNumeric22(packed_fix, &isPrefix).trimmed();

    quint16 num = (packed_11 << 5) | packed_5;

    if(pType) *pType = packed_flag;
    if(pNum) *pNum = num;
    if(pBits3) *pBits3 = packed_3;

    if(isPrefix){
        unpacked.append(fix);
    }

    unpacked.append(base);

    if(!isPrefix){
        unpacked.append(fix);
    }

    return unpacked;
}

// J1Y ACK
// J1Y?
// KN4CRD: J1Y$
// KN4CRD: J1Y! HELLO WORLD
QString Varicode::packDirectedMessage(const QString &text, const QString &callsign, QString *pTo, QString * pCmd, QString *pNum, int *n){
    QString frame;

    auto match = directed_re.match(text);
    if(!match.hasMatch()){
        if(n) *n = 0;
        return frame;
    }

    QString from = callsign;
    QString to = match.captured("callsign");
    QString cmd = match.captured("cmd");
    QString num = match.captured("num").trimmed();

    // ensure we have a directed command
    if(cmd.isEmpty()){
        if(n) *n = 0;
        return frame;
    }

    // validate "to" callsign
    auto parsedTo = QRegularExpression(compound_callsign_pattern).match(to);
    bool validToCallsign = (to != callsign) && (basecalls.contains(to) || parsedTo.hasMatch());
    if(!validToCallsign){
        if(n) *n = 0;
        return frame;
    }

    if(basecalls.contains(to)){
        if(pTo) *pTo = to;
    } else if(parsedTo.hasMatch()){
        if(pTo) *pTo = parsedTo.captured(0);

        auto parsedBase = parsedTo.captured("base");
        if(parsedBase.length() != to.length()){
            to = "<....>"; // parsedBase;
        }
    }

    // validate command
    if(!Varicode::isCommandAllowed(cmd) && !Varicode::isCommandAllowed(cmd.trimmed())){
        if(n) *n = 0;
        return frame;
    }

    // packing general number...
    quint8 inum = Varicode::packNum(num, nullptr);
    if(pNum) *pNum = num;

    quint32 packed_from = Varicode::packCallsign(from);
    quint32 packed_to = Varicode::packCallsign(to);

    if(packed_from == 0 || packed_to == 0){
        if(n) *n = 0;
        return frame;
    }

    quint8 packed_cmd = 0;
    if(directed_cmds.contains(cmd)){
        packed_cmd = directed_cmds[cmd];
    }
    if(directed_cmds.contains(cmd.trimmed())){
        packed_cmd = directed_cmds[cmd.trimmed()];
    }
    quint8 packed_flag = FrameDirected;
    quint8 packed_extra = inum;

    // [3][28][28][5],[2][6] = 72
    auto bits = (
        Varicode::intToBits(packed_flag,      3) +
        Varicode::intToBits(packed_from,     28) +
        Varicode::intToBits(packed_to,       28) +
        Varicode::intToBits(packed_cmd % 32,  5)
    );

    if(pCmd) *pCmd = cmd;
    if(n) *n = match.captured(0).length();
    return Varicode::pack72bits(Varicode::bitsToInt(bits), packed_extra % 64);
}

QStringList Varicode::unpackDirectedMessage(const QString &text, quint8 *pType){
    QStringList unpacked;

    if(text.length() < 12 || text.contains(" ")){
        return unpacked;
    }

    // [3][28][22][11],[2][6] = 72
    quint8 extra = 0;
    auto bits = Varicode::intToBits(Varicode::unpack72bits(text, &extra), 64);

    quint8 packed_flag = Varicode::bitsToInt(bits.mid(0, 3));
    if(packed_flag != FrameDirected){
        return unpacked;
    }

    quint32 packed_from = Varicode::bitsToInt(bits.mid(3, 28));
    quint32 packed_to = Varicode::bitsToInt(bits.mid(31, 28));
    quint8 packed_cmd = Varicode::bitsToInt(bits.mid(59, 5));

    QString from = Varicode::unpackCallsign(packed_from).trimmed();
    QString to = Varicode::unpackCallsign(packed_to).trimmed();
    QString cmd = directed_cmds.key(packed_cmd % 32);

    unpacked.append(from);
    unpacked.append(to);
    unpacked.append(cmd);

    if(extra != 0){
        if(Varicode::isSNRCommand(cmd)){
            unpacked.append(Varicode::formatSNR((int)extra-31));
        } else {
            unpacked.append(QString("%1").arg(extra-31));
        }
    }

    if(pType) *pType = packed_flag;
    return unpacked;
}

QString packHuffMessage(const QString &input, int *n){
    static const int frameSize = 72;

    QString frame;

    // [3][69] = 72
    QVector<bool> frameDataBits;

    QVector<bool> frameHeaderBits = Varicode::intToBits(Varicode::FrameDataUncompressed, 3);

    int i = 0;

    // only pack huff messages that only contain valid chars
    QString::const_iterator it;
    QSet<QString> validChars = Varicode::huffValidChars(Varicode::defaultHuffTable());
    for(it = input.constBegin(); it != input.constEnd(); it++){
        auto ch = (*it).toUpper();
        if(!validChars.contains(ch)){
            return frame;
        }
    }

    // pack using the default huff table
    foreach(auto pair, Varicode::huffEncode(Varicode::defaultHuffTable(), input)){
        auto charN = pair.first;
        auto charBits = pair.second;
        if(frameHeaderBits.length() + frameDataBits.length() + charBits.length() < frameSize){
            frameDataBits += charBits;
            i += charN;
            continue;
        }
        break;
    }

    QVector<bool> framePadBits;

    int pad = frameSize - frameHeaderBits.length() - frameDataBits.length();
    if(pad){
        // the way we will pad is this...
        // set the bit after the frame to 0 and every bit after that a 1
        // to unpad, seek from the end of the bits until you hit a zero... the rest is the actual frame.
        for(int i = 0; i < pad; i++){
            framePadBits.append(i == 0 ? (bool)0 : (bool)1);
        }
    }

    qDebug() << "Huff bits" << frameDataBits.length() << "chars" << i;

    QVector<bool> allBits = frameHeaderBits + frameDataBits + framePadBits;

    quint64 value = Varicode::bitsToInt(allBits.constBegin(), 64);
    quint8 rem = (quint8)Varicode::bitsToInt(allBits.constBegin() + 64, 8);
    frame = Varicode::pack72bits(value, rem);

    if(n) *n = i;

    return frame;
}

QString packCompressedMessage(const QString &input, int *n){
    static const int frameSize = 72;

    QString frame;

    QVector<bool> frameBits;

    frameBits.append(Varicode::intToBits(Varicode::FrameDataCompressed, 3));

    int i = 0;
    foreach(auto pair, JSC::compress(input)){
        auto bits = pair.first;
        auto chars = pair.second;

        if(frameBits.length() + bits.length() < frameSize){
            frameBits.append(bits);
            i += chars;
            continue;
        }

        break;
    }

    qDebug() << "Compressed bits" << frameBits.length() - 3 << "chars" << i;

    int pad = frameSize - frameBits.length();
    if(pad){
        // the way we will pad is this...
        // set the bit after the frame to 0 and every bit after that a 1
        // to unpad, seek from the end of the bits until you hit a zero... the rest is the actual frame.
        for(int i = 0; i < pad; i++){
            frameBits.append(i == 0 ? (bool)0 : (bool)1);
        }
    }

    quint64 value = Varicode::bitsToInt(frameBits.constBegin(), 64);
    quint8 rem = (quint8)Varicode::bitsToInt(frameBits.constBegin() + 64, 8);
    frame = Varicode::pack72bits(value, rem);

    if(n) *n = i;

    return frame;
}

QString Varicode::packDataMessage(const QString &input, int *n){
    QString huffFrame;
    int huffChars = 0;
    huffFrame = packHuffMessage(input, &huffChars);

    QString compressedFrame;
    int compressedChars = 0;
    compressedFrame = packCompressedMessage(input, &compressedChars);

    if(huffChars > compressedChars){
        if(n) *n = huffChars;
        return huffFrame;
    } else {
        if(n) *n = compressedChars;
        return compressedFrame;
    }
}

QString Varicode::unpackDataMessage(const QString &text, quint8 *pType){
    QString unpacked;

    if(text.length() < 12 || text.contains(" ")){
        return unpacked;
    }

    quint8 rem = 0;
    quint64 value = Varicode::unpack72bits(text, &rem);
    auto bits = Varicode::intToBits(value, 64) + Varicode::intToBits(rem, 8);

    quint8 type = Varicode::bitsToInt(bits.mid(0, 3));

    int n = bits.lastIndexOf(0);
    bits = bits.mid(3, n-3);

    if(type == FrameDataUncompressed){
        // huff decode the bits (without escapes)
        unpacked = Varicode::huffDecode(Varicode::defaultHuffTable(), bits);
        if(pType) *pType = type;
    } else if(type == FrameDataCompressed) {
        unpacked = JSC::decompress(bits);
        if(pType) *pType = type;
    }

    return unpacked;
}

// TODO: remove the dependence on providing all this data?
QStringList Varicode::buildMessageFrames(
    QString const& mycall,
    QString const& basecall,
    QString const& mygrid,
    bool compound,
    QString const& selectedCall,
    QString const& text
){
    #define ALLOW_SEND_COMPOUND 1
    #define AUTO_PREPEND_DIRECTED 1

    QStringList frames;

    foreach(QString line, text.split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts)){
        // once we find a directed call, data encode the rest of the line.
        bool hasDirected = false;

        // do the same for when we have sent data...
        bool hasData = false;

        // remove our callsign from the start of the line...
        if(line.startsWith(mycall + ":") || line.startsWith(mycall + " ")){
            line = lstrip(line.mid(mycall.length() + 1));
        }
        if(line.startsWith(basecall + ":") || line.startsWith(basecall + " ")){
            line = lstrip(line.mid(basecall.length() + 1));
        }

        // remove trailing whitespace as long as there are characters left afterwards
        auto rline = rstrip(line);
        if(!rline.isEmpty()){
            line = rline;
        }

#if AUTO_PREPEND_DIRECTED
        // see if we need to prepend the directed call to the line...
        // if we have a selected call and the text doesn't start with that call...
        // and if this isn't a raw message (starting with "<")... then...
        if(!selectedCall.isEmpty() && !line.startsWith(selectedCall) && !line.startsWith("<")){
            auto calls = Varicode::parseCallsigns(line);

            bool lineStartsWithBaseCall = (
                line.startsWith("ALLCALL") ||
                line.startsWith("GROUPCALL") ||
                line.startsWith("BEACON")  ||
                Varicode::startsWithCQ(line)
            );

            bool lineStartsWithStandardCall = !calls.isEmpty() && line.startsWith(calls.first());

            if(lineStartsWithBaseCall || lineStartsWithStandardCall){
                // pass
            } else {
                // if the message doesn't start with a base call
                // and if there are no other callsigns in this message
                // or if the first callsign in the message isn't at the beginning...
                // then we should be auto-prefixing this line with the selected call

                line = QString("%1 %2").arg(selectedCall).arg(line);
            }
        }
#endif

        while(line.size() > 0){
          QString frame;

          bool useBcn = false;
#if ALLOW_SEND_COMPOUND
          bool useCmp = false;
#endif
          bool useDir = false;
          bool useDat = false;

          int l = 0;
          QString bcnFrame = Varicode::packBeaconMessage(line, mycall, &l);

#if ALLOW_SEND_COMPOUND
          int o = 0;
          QString cmpFrame = Varicode::packCompoundMessage(line, &o);
#endif

          int n = 0;
          QString dirCmd;
          QString dirTo;
          QString dirNum;
          QString dirFrame = Varicode::packDirectedMessage(line, basecall, &dirTo, &dirCmd, &dirNum, &n);
          bool dirToCompound = dirTo.contains("/");
          if(dirToCompound){
              qDebug() << "directed message to field is compound" << dirTo;
          }

          int m = 0;
          QString datFrame = Varicode::packDataMessage(line, &m);

          // if this parses to a standard FT8 free text message
          // but it can be parsed as a directed message, then we
          // should send the directed version. if we've already sent
          // a directed message or a data frame, we will only follow it
          // with more data frames.

          if(!hasDirected && !hasData && l > 0){
              useBcn = true;
              hasDirected = false;
              frame = bcnFrame;
          }
#if ALLOW_SEND_COMPOUND
          else if(!hasDirected && !hasData && o > 0){
              useCmp = true;
              hasDirected = false;
              frame = cmpFrame;
          }
#endif
          else if(!hasDirected && !hasData && n > 0){
              useDir = true;
              hasDirected = true;
              frame = dirFrame;
          }
          else if (m > 0) {
              useDat = true;
              hasData = true;
              frame = datFrame;
          }

          if(useBcn){
              frames.append(frame);
              line = line.mid(l);
          }

#if ALLOW_SEND_COMPOUND
          if(useCmp){
              frames.append(frame);
              line = line.mid(o);
          }
#endif

          if(useDir){
              /**
               * We have a few special cases when we are sending to a compound call, or our call is a compound call, or both.
               * CASE 0: Non-compound:       KN4CRD: J1Y ACK
               * -> One standard directed message frame
               *
               * CASE 1: Compound From:      KN4CRD/P: J1Y ACK
               * -> One standard compound frame, followed by a standard directed message frame with placeholder
               * -> The second standard directed frame _could_ be replaced with a compound directed frame
               * -> <KN4CRD/P EM73> then <....>: J1Y ACK
               * -> <KN4CRD/P EM73> then <J1Y ACK>
               *
               * CASE 2: Compound To:        KN4CRD: J1Y/P ACK
               * -> One standard compound frame, followed by a compound directed frame
               * -> <KN4CRD EM73> then <J1Y/P ACK>
               *
               * CASE 3: Compound From & To: KN4CRD/P: J1Y/P ACK
               * -> One standard compound frame, followed by a compound directed frame
               * -> <KN4CRD/P EM73> then <J1Y/P ACK>
               **/
              bool shouldUseStandardFrame = true;
              if(compound || dirToCompound){
                  // Cases 1, 2, 3 all send a standard compound frame first...
                  QString deCompoundMessage = QString("<%1 %2>").arg(mycall).arg(mygrid);
                  QString deCompoundFrame = Varicode::packCompoundMessage(deCompoundMessage, nullptr);
                  if(!deCompoundFrame.isEmpty()){
                      frames.append(deCompoundFrame);
                  }

                  // Followed, by a standard OR compound directed message...
                  QString dirCompoundMessage = QString("<%1%2%3>").arg(dirTo).arg(dirCmd).arg(dirNum);
                  QString dirCompoundFrame = Varicode::packCompoundMessage(dirCompoundMessage, nullptr);
                  if(!dirCompoundFrame.isEmpty()){
                      frames.append(dirCompoundFrame);
                  }
                  shouldUseStandardFrame = false;
              }

              if(shouldUseStandardFrame) {
                  // otherwise, just send the standard directed frame
                  frames.append(frame);
              }

              line = line.mid(n);

              // generate a checksum for buffered commands with line data
              if(Varicode::isCommandBuffered(dirCmd) && !line.isEmpty()){
                  qDebug() << "generating checksum for line" << line << line.mid(1);

                  // strip leading whitespace after a buffered directed command
                  line = lstrip(line);

                  qDebug() << "before:" << line;
                  int checksumSize = Varicode::isCommandChecksumed(dirCmd);

                  if(checksumSize == 32){
                      line = line + " " + Varicode::checksum32(line);
                  } else if (checksumSize == 16) {
                      line = line + " " + Varicode::checksum16(line);
                  } else if (checksumSize == 0) {
                      // pass
                      qDebug() << "no checksum required for cmd" << dirCmd;
                  }
                  qDebug() << "after:" << line;
              }

#if 0
              // APRS:
              if(dirCmd.trimmed() == "APRS:" && !m_aprsClient->isPasscodeValid()){
                  MessageBox::warning_message(this, tr ("Please enter a valid APRS passcode in the settings to send an APRS packet."));
              }
#endif
          }

          if(useDat){
              frames.append(frame);
              line = line.mid(m);
          }
        }
    }

    return frames;
}

BuildMessageFramesThread::BuildMessageFramesThread(const QString &mycall, const QString &basecall, const QString &mygrid, bool compound, const QString &selectedCall, const QString &text, QObject *parent):
    QThread(parent),
    m_mycall{mycall},
    m_basecall{basecall},
    m_mygrid{mygrid},
    m_compound{compound},
    m_selectedCall{selectedCall},
    m_text{text}
{
}

void BuildMessageFramesThread::run(){
    auto results = Varicode::buildMessageFrames(
        m_mycall,
        m_basecall,
        m_mygrid,
        m_compound,
        m_selectedCall,
        m_text
    );

    emit resultReady(results);
}