diff --git a/AudioDevice.cpp b/AudioDevice.cpp new file mode 100644 index 000000000..871273d19 --- /dev/null +++ b/AudioDevice.cpp @@ -0,0 +1,23 @@ +#include "AudioDevice.hpp" + +#include + +namespace +{ + struct init + { + init () + { + qRegisterMetaType ("AudioDevice::Channel"); + } + } static_initializer; +} + +bool AudioDevice::open (OpenMode mode, Channel channel) +{ + m_channel = channel; + + // ensure we are unbuffered + return QIODevice::open (mode | QIODevice::Unbuffered); +} + diff --git a/AudioDevice.hpp b/AudioDevice.hpp index 5139c35e4..63f8b5999 100644 --- a/AudioDevice.hpp +++ b/AudioDevice.hpp @@ -24,13 +24,7 @@ public: return "both" == s ? Both : "right" == s ? Right : "left" == s ? Left : Mono; } - bool open (OpenMode mode, Channel channel) - { - m_channel = channel; - - // ensure we are unbuffered - return QIODevice::open (mode | QIODevice::Unbuffered); - } + bool open (OpenMode mode, Channel channel); bool isSequential () const {return true;} diff --git a/Bands.cpp b/Bands.cpp new file mode 100644 index 000000000..747eb7f41 --- /dev/null +++ b/Bands.cpp @@ -0,0 +1,196 @@ +#include "Bands.hpp" + +#include + +#include +#include + +namespace +{ + // Local structure to hold a single ADIF band definition. + struct ADIF_band + { + char const * const name_; + Radio::Frequency lower_bound_; + Radio::Frequency upper_bound_; + }; + + // Table of ADIF band definitions as defined in the ADIF + // specification. + ADIF_band constexpr ADIF_bands[] = { + {"2190m", 136000u, 137000u}, + {"630m", 472000u, 479000u}, + {"560m", 501000u, 504000u}, + {"160m", 1800000u, 2000000u}, + {"80m", 3500000u, 4000000u}, + {"60m", 5102000u, 5406500u}, + {"40m", 7000000u, 7300000u}, + {"30m", 10000000u, 10150000u}, + {"20m", 14000000u, 14350000u}, + {"17m", 18068000u, 18168000u}, + {"15m", 21000000u, 21450000u}, + {"12m", 24890000u, 24990000u}, + {"10m", 28000000u, 29700000u}, + {"6m", 50000000u, 54000000u}, + {"4m", 70000000u, 71000000u}, + {"2m", 144000000u, 148000000u}, + {"1.25m", 222000000u, 225000000u}, + {"70cm", 420000000u, 450000000u}, + {"33cm", 902000000u, 928000000u}, + {"23cm", 1240000000u, 1300000000u}, + {"13cm", 2300000000u, 2450000000u}, + {"9cm", 3300000000u, 3500000000u}, + {"6cm", 5650000000u, 5925000000u}, + {"3cm", 10000000000u, 10500000000u}, + {"1.25cm", 24000000000u, 24250000000u}, + {"6mm", 47000000000u, 47200000000u}, + {"4mm", 75500000000u, 81000000000u}, + {"2.5mm", 119980000000u, 120020000000u}, + {"2mm", 142000000000u, 149000000000u}, + {"1mm", 241000000000u, 250000000000u}, + }; + + auto constexpr out_of_band = "OOB"; + + int constexpr table_rows () + { + return sizeof (ADIF_bands) / sizeof (ADIF_bands[0]); + } +} + +Bands::Bands (QObject * parent) + : QAbstractTableModel {parent} +{ +} + +QModelIndex Bands::find (QVariant const& v) const +{ + auto f = v.value (); + auto end_iter = ADIF_bands + table_rows (); + auto row_iter = std::find_if (ADIF_bands, end_iter, [f] (ADIF_band const& band) { + return band.lower_bound_ <= f && f <= band.upper_bound_; + }); + if (row_iter != end_iter) + { + return index (row_iter - ADIF_bands, 0); // return the band row index + } + + return QModelIndex {}; +} + +int Bands::rowCount (QModelIndex const& parent) const +{ + return parent.isValid () ? 0 : table_rows (); +} + +int Bands::columnCount (QModelIndex const& parent) const +{ + return parent.isValid () ? 0 : 3; +} + +Qt::ItemFlags Bands::flags (QModelIndex const& index) const +{ + return QAbstractTableModel::flags (index) | Qt::ItemIsDropEnabled; +} + +QVariant Bands::data (QModelIndex const& index, int role) const +{ + QVariant item; + + if (!index.isValid ()) + { + // Hijack root for OOB string. + if (Qt::DisplayRole == role) + { + item = out_of_band; + } + } + else + { + auto row = index.row (); + auto column = index.column (); + + if (row < table_rows ()) + { + switch (role) + { + case Qt::ToolTipRole: + case Qt::AccessibleDescriptionRole: + switch (column) + { + case 0: item = tr ("Band name"); break; + case 1: item = tr ("Lower frequency limit"); break; + case 2: item = tr ("Upper frequency limit"); break; + } + break; + + case SortRole: + case Qt::DisplayRole: + case Qt::EditRole: + switch (column) + { + case 0: + if (SortRole == role) + { + // band name sorts by lower bound + item = ADIF_bands[row].lower_bound_; + } + else + { + item = ADIF_bands[row].name_; + } + break; + + case 1: item = ADIF_bands[row].lower_bound_; break; + case 2: item = ADIF_bands[row].upper_bound_; break; + } + break; + + case Qt::AccessibleTextRole: + switch (column) + { + case 0: item = ADIF_bands[row].name_; break; + case 1: item = ADIF_bands[row].lower_bound_; break; + case 2: item = ADIF_bands[row].upper_bound_; break; + } + break; + + case Qt::TextAlignmentRole: + switch (column) + { + case 0: + item = Qt::AlignHCenter + Qt::AlignVCenter; + break; + + case 1: + case 2: + item = Qt::AlignRight + Qt::AlignVCenter; + break; + } + break; + } + } + } + return item; +} + +QVariant Bands::headerData (int section, Qt::Orientation orientation, int role) const +{ + QVariant result; + + if (Qt::DisplayRole == role && Qt::Horizontal == orientation) + { + switch (section) + { + case 0: result = tr ("Band"); break; + case 1: result = tr ("Lower Limit"); break; + case 2: result = tr ("Upper Limit"); break; + } + } + else + { + result = QAbstractTableModel::headerData (section, orientation, role); + } + + return result; +} diff --git a/Bands.hpp b/Bands.hpp new file mode 100644 index 000000000..41d3810af --- /dev/null +++ b/Bands.hpp @@ -0,0 +1,58 @@ +#ifndef BANDS_HPP__ +#define BANDS_HPP__ + +#include + +#include "Radio.hpp" + +// +// Class Bands +// +// Encapsulates information about amateur radio bands as defined by +// the ADIF specification. The model is immutable. The rows are +// stored in asscending order of frequency. +// +// Responsibilities +// +// Provides a well known band name mapped to lower and upper +// frequency limits. Also provides a convenience operation to +// determine the band details for any given frequency, the result of +// which may be invalid if the given frequency doesn't lie within a +// recognised band. +// +// Collaborations +// +// Implements the QAbstractTableModel interface as an immutable table +// where rows are bands and columns are band name, lower frequency +// limit and, upper ferquency limit respectively. +// +class Bands final + : public QAbstractTableModel +{ +public: + explicit Bands (QObject * parent = nullptr); + + // + // Model API + // + QModelIndex find (QVariant const&) const; // find band Frequency is in + + // Custom role for sorting. + static int constexpr SortRole = Qt::UserRole; + + // Implement the QAbstractTableModel interface + int rowCount (QModelIndex const& parent = QModelIndex {}) const override; + int columnCount (QModelIndex const& parent = QModelIndex {}) const override; + Qt::ItemFlags flags (QModelIndex const& = QModelIndex {}) const override; + QVariant headerData (int section, Qt::Orientation, int = Qt::DisplayRole) const override; + + // The value return for the Qt::DisplayRole role for the root of the + // model (invalid index) is a special string representing out of + // band. + // + // All columns return a number for the custom role SortRole, this + // number defines a strict frequency order for the rows. + QVariant data (QModelIndex const&, int role = Qt::DisplayRole) const override; +}; + +#endif diff --git a/CALL3.TXT b/CALL3.TXT deleted file mode 100644 index e69de29bb..000000000 diff --git a/CMake/Modules/FindFFTW3.cmake b/CMake/Modules/FindFFTW3.cmake new file mode 100644 index 000000000..b242bc71b --- /dev/null +++ b/CMake/Modules/FindFFTW3.cmake @@ -0,0 +1,93 @@ +# - Try to find FFTW3. +# Usage: find_package(FFTW3 [COMPONENTS [single double long-double threads]]) +# +# Variables used by this module: +# FFTW3_ROOT_DIR - FFTW3 root directory +# Variables defined by this module: +# FFTW3_FOUND - system has FFTW3 +# FFTW3_INCLUDE_DIR - the FFTW3 include directory (cached) +# FFTW3_INCLUDE_DIRS - the FFTW3 include directories +# (identical to FFTW3_INCLUDE_DIR) +# FFTW3[FL]?_LIBRARY - the FFTW3 library - double, single(F), +# long-double(L) precision (cached) +# FFTW3[FL]?_THREADS_LIBRARY - the threaded FFTW3 library - double, single(F), +# long-double(L) precision (cached) +# FFTW3_LIBRARIES - list of all FFTW3 libraries found + +# Copyright (C) 2009-2010 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see . +# +# $Id: FindFFTW3.cmake 15918 2010-06-25 11:12:42Z loose $ + +# Use double precision by default. +if (FFTW3_FIND_COMPONENTS MATCHES "^$") + set (_components double) +else () + set (_components ${FFTW3_FIND_COMPONENTS}) +endif () + +# Loop over each component. +set (_libraries) +foreach (_comp ${_components}) + if (_comp STREQUAL "single") + list (APPEND _libraries fftw3f) + elseif (_comp STREQUAL "double") + list (APPEND _libraries fftw3) + elseif (_comp STREQUAL "long-double") + list (APPEND _libraries fftw3l) + elseif (_comp STREQUAL "threads") + set (_use_threads ON) + else (_comp STREQUAL "single") + message (FATAL_ERROR "FindFFTW3: unknown component `${_comp}' specified. " + "Valid components are `single', `double', `long-double', and `threads'.") + endif (_comp STREQUAL "single") +endforeach (_comp ${_components}) + +# If using threads, we need to link against threaded libraries as well. +if (_use_threads) + set (_thread_libs) + foreach (_lib ${_libraries}) + list (APPEND _thread_libs ${_lib}_threads) + endforeach (_lib ${_libraries}) + set (_libraries ${_thread_libs} ${_libraries}) +endif (_use_threads) + +# Keep a list of variable names that we need to pass on to +# find_package_handle_standard_args(). +set (_check_list) + +# Search for all requested libraries. +foreach (_lib ${_libraries}) + string (TOUPPER ${_lib} _LIB) + find_library (${_LIB}_LIBRARY NAMES ${_lib} ${_lib}-3 + HINTS ${FFTW3_ROOT_DIR} PATH_SUFFIXES lib) + mark_as_advanced (${_LIB}_LIBRARY) + list (APPEND FFTW3_LIBRARIES ${${_LIB}_LIBRARY}) + list (APPEND _check_list ${_LIB}_LIBRARY) +endforeach (_lib ${_libraries}) + +# Search for the header file. +find_path (FFTW3_INCLUDE_DIR fftw3.h + HINTS ${FFTW3_ROOT_DIR} PATH_SUFFIXES include) +mark_as_advanced (FFTW3_INCLUDE_DIR) +list(APPEND _check_list FFTW3_INCLUDE_DIR) + +# Handle the QUIETLY and REQUIRED arguments and set FFTW_FOUND to TRUE if +# all listed variables are TRUE +include (FindPackageHandleStandardArgs) +find_package_handle_standard_args (FFTW3 DEFAULT_MSG ${_check_list}) diff --git a/CMake/Modules/Findhamlib.cmake b/CMake/Modules/Findhamlib.cmake new file mode 100644 index 000000000..2a368188a --- /dev/null +++ b/CMake/Modules/Findhamlib.cmake @@ -0,0 +1,28 @@ +# - Try to find hamlib +# Once done, this will define: +# +# hamlib_FOUND - system has Hamlib-2 +# hamlib_INCLUDE_DIRS - the Hamlib-2 include directories +# hamlib_LIBRARIES - link these to use Hamlib-2 + +include (LibFindMacros) + +# Use pkg-config to get hints about paths +libfind_pkg_check_modules (hamlib_PKGCONF hamlib) + +# Include dirs +find_path (hamlib_INCLUDE_DIR + NAMES hamlib/rig.h + PATHS ${hamlib_PKGCONF_INCLUDE_DIRS} +) + +# The library +find_library (hamlib_LIBRARY + NAMES hamlib hamlib-2 + PATHS ${hamlib_PKGCONF_LIBRARY_DIRS} +) + +# Set the include dir variables and the libraries and let libfind_process do the rest +set (hamlib_PROCESS_INCLUDES hamlib_INCLUDE_DIR) +set (hamlib_PROCESS_LIBS hamlib_LIBRARY) +libfind_process (hamlib) diff --git a/CMake/Modules/LibFindMacros.cmake b/CMake/Modules/LibFindMacros.cmake new file mode 100644 index 000000000..aa2143c82 --- /dev/null +++ b/CMake/Modules/LibFindMacros.cmake @@ -0,0 +1,112 @@ +# Version 1.0 (2013-04-12) +# Public Domain, originally written by Lasse Kärkkäinen +# Published at http://www.cmake.org/Wiki/CMake:How_To_Find_Libraries + +# If you improve the script, please modify the forementioned wiki page because +# I no longer maintain my scripts (hosted as static files at zi.fi). Feel free +# to remove this entire header if you use real version control instead. + +# Changelog: +# 2013-04-12 Added version number (1.0) and this header, no other changes +# 2009-10-08 Originally published + + +# Works the same as find_package, but forwards the "REQUIRED" and "QUIET" arguments +# used for the current package. For this to work, the first parameter must be the +# prefix of the current package, then the prefix of the new package etc, which are +# passed to find_package. +macro (libfind_package PREFIX) + set (LIBFIND_PACKAGE_ARGS ${ARGN}) + if (${PREFIX}_FIND_QUIETLY) + set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} QUIET) + endif (${PREFIX}_FIND_QUIETLY) + if (${PREFIX}_FIND_REQUIRED) + set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} REQUIRED) + endif (${PREFIX}_FIND_REQUIRED) + find_package(${LIBFIND_PACKAGE_ARGS}) +endmacro (libfind_package) + +# CMake developers made the UsePkgConfig system deprecated in the same release (2.6) +# where they added pkg_check_modules. Consequently I need to support both in my scripts +# to avoid those deprecated warnings. Here's a helper that does just that. +# Works identically to pkg_check_modules, except that no checks are needed prior to use. +macro (libfind_pkg_check_modules PREFIX PKGNAME) + if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) + include(UsePkgConfig) + pkgconfig(${PKGNAME} ${PREFIX}_INCLUDE_DIRS ${PREFIX}_LIBRARY_DIRS ${PREFIX}_LDFLAGS ${PREFIX}_CFLAGS) + else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(${PREFIX} ${PKGNAME}) + endif (PKG_CONFIG_FOUND) + endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) +endmacro (libfind_pkg_check_modules) + +# Do the final processing once the paths have been detected. +# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain +# all the variables, each of which contain one include directory. +# Ditto for ${PREFIX}_PROCESS_LIBS and library files. +# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. +# Also handles errors in case library detection was required, etc. +macro (libfind_process PREFIX) + # Skip processing if already processed during this run + if (NOT ${PREFIX}_FOUND) + # Start with the assumption that the library was found + set (${PREFIX}_FOUND TRUE) + + # Process all includes and set _FOUND to false if any are missing + foreach (i ${${PREFIX}_PROCESS_INCLUDES}) + if (${i}) + set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${i}}) + mark_as_advanced(${i}) + else (${i}) + set (${PREFIX}_FOUND FALSE) + endif (${i}) + endforeach (i) + + # Process all libraries and set _FOUND to false if any are missing + foreach (i ${${PREFIX}_PROCESS_LIBS}) + if (${i}) + set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARIES} ${${i}}) + mark_as_advanced(${i}) + else (${i}) + set (${PREFIX}_FOUND FALSE) + endif (${i}) + endforeach (i) + + # Print message and/or exit on fatal error + if (${PREFIX}_FOUND) + if (NOT ${PREFIX}_FIND_QUIETLY) + message (STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") + endif (NOT ${PREFIX}_FIND_QUIETLY) + else (${PREFIX}_FOUND) + if (${PREFIX}_FIND_REQUIRED) + foreach (i ${${PREFIX}_PROCESS_INCLUDES} ${${PREFIX}_PROCESS_LIBS}) + message("${i}=${${i}}") + endforeach (i) + message (FATAL_ERROR "Required library ${PREFIX} NOT FOUND.\nInstall the library (dev version) and try again. If the library is already installed, use ccmake to set the missing variables manually.") + endif (${PREFIX}_FIND_REQUIRED) + endif (${PREFIX}_FOUND) + endif (NOT ${PREFIX}_FOUND) +endmacro (libfind_process) + +macro(libfind_library PREFIX basename) + set(TMP "") + if(MSVC80) + set(TMP -vc80) + endif(MSVC80) + if(MSVC90) + set(TMP -vc90) + endif(MSVC90) + set(${PREFIX}_LIBNAMES ${basename}${TMP}) + if(${ARGC} GREATER 2) + set(${PREFIX}_LIBNAMES ${basename}${TMP}-${ARGV2}) + string(REGEX REPLACE "\\." "_" TMP ${${PREFIX}_LIBNAMES}) + set(${PREFIX}_LIBNAMES ${${PREFIX}_LIBNAMES} ${TMP}) + endif(${ARGC} GREATER 2) + find_library(${PREFIX}_LIBRARY + NAMES ${${PREFIX}_LIBNAMES} + PATHS ${${PREFIX}_PKGCONF_LIBRARY_DIRS} + ) +endmacro(libfind_library) + diff --git a/CMake/Modules/QtAxMacros.cmake b/CMake/Modules/QtAxMacros.cmake new file mode 100644 index 000000000..6a4cd1442 --- /dev/null +++ b/CMake/Modules/QtAxMacros.cmake @@ -0,0 +1,36 @@ +# +# Macros for processing ActiveX and COM controls with ActiveQt +# + +if (WIN32) + include (CMakeParseArguments) + + find_program (DUMPCPP_Executable dumpcpp.exe) + + # wrap_ax_server (outfiles inputfile ...) + + function (WRAP_AX_SERVER outfiles) + set (options) + set (oneValueArgs) + set (multiValueArgs OPTIONS) + + cmake_parse_arguments (_WRAP_AX_SERVER "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set (ax_server_files ${_WRAP_AX_SERVER_UNPARSED_ARGUMENTS}) + set (ax_server_options ${_WRAP_AX_SERVER_OPTIONS}) + + foreach (it ${ax_server_files}) + get_filename_component (outfile ${it} NAME_WE) + get_filename_component (infile ${it} ABSOLUTE) + set (outfile ${CMAKE_CURRENT_BINARY_DIR}/${outfile}) + add_custom_command ( + OUTPUT ${outfile}.h ${outfile}.cpp + COMMAND ${DUMPCPP_Executable} + ARGS ${AX_SERVER_options} -o "${outfile}" "${infile}" + MAIN_DEPENDENCY ${infile} VERBATIM) + list (APPEND ${outfiles} ${outfile}.cpp) + endforeach() + set(${outfiles} ${${outfiles}} PARENT_SCOPE) + endfunction () + +endif (WIN32) \ No newline at end of file diff --git a/CMake/VersionCompute.cmake b/CMake/VersionCompute.cmake new file mode 100644 index 000000000..b726a9161 --- /dev/null +++ b/CMake/VersionCompute.cmake @@ -0,0 +1,8 @@ +# Load version number components. +include (${wsjtx_SOURCE_DIR}/Versions.cmake) + +# Compute the full version string. +set (wsjtx_VERSION ${WSJTX_VERSION_MAJOR}.${WSJTX_VERSION_MINOR}.${WSJTX_VERSION_PATCH}) +if (WSJTX_RC) + set (wsjtx_VERSION ${wsjtx_VERSION}-rc${WSJTX_RC}) +endif () diff --git a/CMake/cmake_uninstall.cmake.in b/CMake/cmake_uninstall.cmake.in new file mode 100644 index 000000000..23b3064a0 --- /dev/null +++ b/CMake/cmake_uninstall.cmake.in @@ -0,0 +1,22 @@ +if (NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message (FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +endif () + +file (READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string (REGEX REPLACE "\n" ";" files "${files}") +foreach (file ${files}) + message (STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + if (EXISTS "$ENV{DESTDIR}${file}") + exec_program ( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if ("${rm_retval}" STREQUAL 0) + else () + message (FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + endif () + else () + message (STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + endif () +endforeach () diff --git a/CMake/getsvn.cmake b/CMake/getsvn.cmake new file mode 100644 index 000000000..7d946d597 --- /dev/null +++ b/CMake/getsvn.cmake @@ -0,0 +1,15 @@ +find_package (Subversion) +if (Subversion_FOUND AND EXISTS ${PROJECT_SOURCE_DIR}/.svn) + # the FindSubversion.cmake module is part of the standard distribution + include (FindSubversion) + # extract working copy information for SOURCE_DIR into MY_XXX variables + Subversion_WC_INFO (${SOURCE_DIR} MY) + # write a file with the SVNVERSION define + file (WRITE svnversion.h.txt "#define SVNVERSION r${MY_WC_REVISION}\n") +else (Subversion_FOUND AND EXISTS ${PROJECT_SOURCE_DIR}/.svn) + file (WRITE svnversion.h.txt "#define SVNVERSION local\n") +endif (Subversion_FOUND AND EXISTS ${PROJECT_SOURCE_DIR}/.svn) + +# copy the file to the final header only if the version changes +# reduces needless rebuilds +execute_process (COMMAND ${CMAKE_COMMAND} -E copy_if_different svnversion.h.txt svnversion.h) diff --git a/CMakeCPackOptions.cmake.in b/CMakeCPackOptions.cmake.in new file mode 100644 index 000000000..43b566c0d --- /dev/null +++ b/CMakeCPackOptions.cmake.in @@ -0,0 +1,81 @@ +# This file is configured at cmake time, and loaded at cpack time. +# To pass variables to cpack from cmake, they must be configured +# in this file. + +set (CPACK_PACKAGE_VENDOR "Joe Taylor, K1JT") +set (CPACK_PACKAGE_CONTACT "k1jt@arrl.net") +set (CPACK_PACKAGE_DESCRIPTION_FILE "@PROJECT_SOURCE_DIR@/Copyright.txt") +set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "WSJT-X - JT9 and JT65 Modes for LF, MF and HF Amateur Radio") +set (CPACK_RESOURCE_FILE_LICENSE "@PROJECT_SOURCE_DIR@/LICENSE_WHEATLEY.txt") +set (CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) +set (CPACK_PACKAGE_EXECUTABLES wsjtx "WSJT-X") +set (CPACK_CREATE_DESKTOP_LINKS wsjtx) +set (CPACK_STRIP_FILES TRUE) + +# +# components +# +#set (CPACK_COMPONENT_APPLICATIONS_DISPLAY_NAME "WSJT-X Application") +#set (CPACK_COMPONENT_APPLICATIONS_DESCRIPTION "@WSJTX_DESCRIPTION_SUMMARY@") + +if (CPACK_GENERATOR MATCHES "NSIS") + set (CPACK_STRIP_FILES FALSE) # breaks Qt packaging on Windows + + set (CPACK_NSIS_INSTALL_ROOT "C:\\WSJT") + + # set the install/unistall icon used for the installer itself + # There is a bug in NSI that does not handle full unix paths properly. + set (CPACK_NSIS_MUI_ICON "@PROJECT_SOURCE_DIR@/icons/windows-icons\\wsjtx.ico") + set (CPACK_NSIS_MUI_UNIICON "@PROJECT_SOURCE_DIR@/icons/windows-icons\\wsjtx.ico") + # set the package header icon for MUI + set (CPACK_PACKAGE_ICON "@PROJECT_SOURCE_DIR@/icons/windows-icons\\installer_logo.bmp") + # tell cpack to create links to the doc files + set (CPACK_NSIS_MENU_LINKS + "@PROJECT_MANUAL@" "WSJT-X Documentation" + "@PROJECT_HOMEPAGE@" "WSJT-X Web Site" + ) + # Use the icon from wsjtx for add-remove programs + set (CPACK_NSIS_INSTALLED_ICON_NAME "@PROJECT_BINARY_DIR@\\wsjtx.exe") + + set (CPACK_NSIS_DISPLAY_NAME "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}") + set (CPACK_NSIS_HELP_LINK "@PROJECT_MANUAL@") + set (CPACK_NSIS_URL_INFO_ABOUT "@PROJECT_HOMEPAGE@") + set (CPACK_NSIS_CONTACT "${CPACK_PACKAGE_CONTACT}") + set (CPACK_NSIS_MUI_FINISHPAGE_RUN "wsjtx.exe") + set (CPACK_NSIS_MODIFY_PATH ON) +endif () + +if ("${CPACK_GENERATOR}" STREQUAL "PackageMaker") + set (CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}-pkg") + set (CPACK_PACKAGE_DEFAULT_LOCATION "/Applications") + set (CPACK_PACKAGING_INSTALL_PREFIX "/") +endif () + +if ("${CPACK_GENERATOR}" STREQUAL "DragNDrop") + set (CPACK_DMG_BACKGROUND_IMAGE "@PROJECT_SOURCE_DIR@/icons/Darwin/DragNDrop Background.png") + set (CPACK_DMG_DS_STORE "@PROJECT_SOURCE_DIR@/wsjtx_DMG.DS_Store") + set (CPACK_BUNDLE_NAME "@WSJTX_BUNDLE_NAME@") + set (CPACK_PACKAGE_ICON "@PROJECT_BINARY_DIR@/wsjtx.icns") + set (CPACK_BUNDLE_ICON "@PROJECT_BINARY_DIR@/wsjtx.icns") + set (CPACK_BUNDLE_STARTUP_COMMAND "@PROJECT_SOURCE_DIR@/Mac-wsjtx-startup.sh") +endif () + +if ("${CPACK_GENERATOR}" STREQUAL "WIX") + # Reset CPACK_PACKAGE_VERSION to deal with WiX restriction. + # But the file names still use the full CMake_VERSION value: + set (CPACK_PACKAGE_FILE_NAME + "${CPACK_PACKAGE_NAME}-@wsjtx_VERSION@-${CPACK_SYSTEM_NAME}") + set (CPACK_SOURCE_PACKAGE_FILE_NAME + "${CPACK_PACKAGE_NAME}-@wsjtx_VERSION@-Source") + + if (NOT CPACK_WIX_SIZEOF_VOID_P) + set (CPACK_WIX_SIZEOF_VOID_P "@CMAKE_SIZEOF_VOID_P@") + endif () +endif () + +if ("${CPACK_GENERATOR}" STREQUAL "DEB") + set (CPACK_PACKAGE_FILE_NAME "@DEBIAN_PACKAGE_FILE_NAME@") + set (CPACK_DEBIAN_PACKAGE_HOMEPAGE "@PROJECT_HOMEPAGE@") +endif ("${CPACK_GENERATOR}" STREQUAL "DEB") + +message (STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") diff --git a/CMakeLists.txt b/CMakeLists.txt index f0dc4ca71..fbb57a18e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,314 @@ -cmake_minimum_required (VERSION 2.8.9) +cmake_minimum_required (VERSION 2.8.9 FATAL_ERROR) project (wsjtx C CXX Fortran) -set (WSJTX_VERSION_MAJOR 1) -set (WSJTX_VERSION_MINOR 3) +set (WSJTX_COPYRIGHT "Copyright (C) 2001-2014 by Joe Taylor, K1JT") + +set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMake/Modules) + +include (${PROJECT_SOURCE_DIR}/CMake/VersionCompute.cmake) + + +# +# Options & features +# +# Some of these directly effect compilation by being defined in +# wsjtx_config.h.in which makes them available to the C/C++ +# pre-processor. +# +option (UPDATE_TRANSLATIONS "Update source translation translations/*.ts +files (WARNING: make clean will delete the source .ts files! Danger!)") +option (WSJT_SHARED_RUNTIME "Debugging option that allows running from a shared Cloud directory.") +option (WSJT_QDEBUG_IN_RELEASE "Leave qDebug statements in Release configuration.") +option (WSJT_TRACE_CAT "Debugging option that turns on CAT diagnostics.") +option (WSJT_TRACE_CAT_POLLS "Debugging option that turns on CAT diagnostics during polling.") +option (WSJT_HAMLIB_TRACE "Debugging option that turns on full Hamlib internal diagnostics.") +option (WSJT_STANDARD_FILE_LOCATIONS "All non-installation files located in \"Standard\" platfom specific locations." ON) + + +# +# install locations +# +set (WSJT_BIN_DESTINATION bin) +set (WSJT_LIB_DESTINATION lib) +set (WSJT_SHARE_DESTINATION share/wsjtx) +set (WSJT_DOC_DESTINATION share/wsjtx/doc) +set (WSJT_PLUGIN_DESTINATION lib/plugins) +set (WSJT_QT_CONF_DESTINATION bin) + + +# +# project information +# +set (PROJECT_HOMEPAGE "http://www.physics.princeton.edu/pulsar/K1JT/wsjtx.html") +set (PROJECT_MANUAL "http://www.physics.princeton.edu/pulsar/K1JT/wsjtx-doc/wsjtx-main-toc2.html") + + +# +# Project sources +# +set (wsjt_qt_CXXSRCS + NetworkServerLookup.cpp + Radio.cpp + Bands.cpp + FrequencyList.cpp + StationList.cpp + FrequencyLineEdit.cpp + FrequencyItemDelegate.cpp + ForeignKeyDelegate.cpp + LiveFrequencyValidator.cpp + GetUserId.cpp + TraceFile.cpp + AudioDevice.cpp + Transceiver.cpp + TransceiverBase.cpp + EmulateSplitTransceiver.cpp + TransceiverFactory.cpp + PollingTransceiver.cpp + HamlibTransceiver.cpp + HRDTransceiver.cpp + DXLabSuiteCommanderTransceiver.cpp + ) + +set (ConfigTest_CXXSRCS + Configuration.cpp + TestConfiguration.cpp + ConfigTest.cpp + ) + +set (jt9_CXXSRCS + lib/ipcomm.cpp + ) + +set (wsjtx_CXXSRCS + logbook/adif.cpp + logbook/countrydat.cpp + logbook/countriesworked.cpp + logbook/logbook.cpp + psk_reporter.cpp + Modulator.cpp + Detector.cpp + logqso.cpp + displaytext.cpp + decodedtext.cpp + getfile.cpp + soundout.cpp + soundin.cpp + meterwidget.cpp + signalmeter.cpp + plotter.cpp + widegraph.cpp + about.cpp + astro.cpp + mainwindow.cpp + Configuration.cpp + main.cpp + ) + +if (WIN32) + set (ConfigTest_CXXSRCS + ${ConfigTest_CXXSRCS} + OmniRigTransceiver.cpp + ) + + set (wsjt_CXXSRCS + ${wsjt_CXXSRCS} + killbyname.cpp + ) + + set (wsjt_qt_CXXSRCS + ${wsjt_qt_CXXSRCS} + OmniRigTransceiver.cpp + ) +endif (WIN32) + +set (wsjt_FSRCS + lib/afc65b.f90 + lib/afc9.f90 + lib/analytic.f90 + lib/astro.f90 + lib/astrosub.f90 + lib/astro0.f90 + lib/azdist.f90 + lib/baddata.f90 + lib/ccf2.f90 + lib/ccf65.f90 + lib/chkhist.f90 + lib/chkss2.f90 + lib/coord.f90 + lib/db.f90 + lib/dcoord.f90 + lib/decode65a.f90 + lib/decode65b.f90 + lib/decode9.f90 + lib/decoder.f90 + lib/deg2grid.f90 + lib/demod64a.f90 + lib/determ.f90 + lib/dot.f90 + lib/downsam9.f90 + lib/encode232.f90 + lib/entail.f90 + lib/extract.F90 + lib/geocentric.f90 + lib/f77_wisdom.f90 + lib/fano232.f90 + lib/fchisq.f90 + lib/fchisq65.f90 + lib/fil3.f90 + lib/fil4.f90 + lib/fil6521.f90 + lib/filbig.f90 + lib/fillcom.f90 + lib/flat1.f90 + lib/flat2.f90 + lib/flat3.f90 + lib/flat65.f90 + lib/four2a.f90 + lib/gen65.f90 + lib/genjt9.f90 + lib/geodist.f90 + lib/getlags.f90 + lib/getpfx1.f90 + lib/getpfx2.f90 + lib/graycode.f90 + lib/graycode65.f90 + lib/grid2deg.f90 + lib/grid2k.f90 + lib/grid2n.f90 + lib/indexx.f90 + lib/interleave63.f90 + lib/interleave9.f90 + lib/jt65a.f90 + lib/k2grid.f90 + lib/moon2.f90 + lib/moondop.f90 + lib/morse.f90 + lib/move.f90 + lib/n2grid.f90 + lib/nchar.f90 + lib/options.f90 + lib/packbits.f90 + lib/packcall.f90 + lib/packgrid.f90 + lib/packmsg.f90 + lib/packtext.f90 + lib/pctile.f90 + lib/peakdt9.f90 + lib/pfxdump.f90 + lib/polfit.f90 + lib/prog_args.f90 + lib/sec_midn.f90 + lib/setup65.f90 + lib/sleep_msec.f90 + lib/smo.f90 + lib/smo121.f90 + lib/softsym.f90 + lib/sort.f90 + lib/ssort.f90 + lib/stdmsg.f90 + lib/sun.f90 + lib/symspec.f90 + lib/symspec2.f90 + lib/symspec65.f90 + lib/sync9.f90 + lib/timer.f90 + lib/tm2.f90 + lib/toxyz.f90 + lib/twkfreq.f90 + lib/twkfreq65.f90 + lib/unpackbits.f90 + lib/unpackcall.f90 + lib/unpackgrid.f90 + lib/unpackmsg.f90 + lib/unpacktext.f90 + lib/zplot9.f90 + ) + +set (wsjt_CSRCS + lib/decode_rs.c + lib/encode_rs.c + lib/gran.c + lib/igray.c + lib/init_rs.c + lib/tmoonsub.c + lib/usleep.c + lib/wrapkarn.c + ) + +set (ConfigTest_UISRCS + TestConfiguration.ui + Configuration.ui + ) + +set (wsjtx_UISRCS + mainwindow.ui + about.ui + astro.ui + widegraph.ui + logqso.ui + Configuration.ui + ) + +set (all_C_and_CXXSRCS + ${wsjt_CSRCS} + ${wsjt_CXXSRCS} + ${wsjt_qt_CXXSRCS} + ${jt9_CXXSRCS} + ${ConfigTest_CXXSRCS} + ${wsjtx_CXXSRCS} + ) + +if (APPLE) + set (WSJTX_ICON_FILE ${CMAKE_PROJECT_NAME}.icns) + set (ICONSRCS + icons/Darwin/${CMAKE_PROJECT_NAME}.iconset/icon_16x16.png + icons/Darwin/${CMAKE_PROJECT_NAME}.iconset/icon_16x16@2x.png + icons/Darwin/${CMAKE_PROJECT_NAME}.iconset/icon_32x32.png + icons/Darwin/${CMAKE_PROJECT_NAME}.iconset/icon_32x32@2x.png + icons/Darwin/${CMAKE_PROJECT_NAME}.iconset/icon_128x128.png + icons/Darwin/${CMAKE_PROJECT_NAME}.iconset/icon_128x128@2x.png + icons/Darwin/${CMAKE_PROJECT_NAME}.iconset/icon_256x256.png + icons/Darwin/${CMAKE_PROJECT_NAME}.iconset/icon_256x256@2x.png + icons/Darwin/${CMAKE_PROJECT_NAME}.iconset/icon_512x512.png + icons/Darwin/${CMAKE_PROJECT_NAME}.iconset/icon_512x512@2x.png + ) + add_custom_command ( + OUTPUT ${WSJTX_ICON_FILE} + COMMAND iconutil -c icns --output "${CMAKE_BINARY_DIR}/${WSJTX_ICON_FILE}" "${CMAKE_SOURCE_DIR}/icons/Darwin/${CMAKE_PROJECT_NAME}.iconset" + DEPENDS ${ICONSRCS} + COMMENT "Building Icons" + ) +endif (APPLE) + +set_source_files_properties (${WSJTX_ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + +if (NOT WSJT_QDEBUG_IN_RELEASE) + set_directory_properties (PROPERTIES COMPILE_DEFINITIONS_RELEASE "QT_NO_DEBUG_OUTPUT;QT_NO_WARNING_OUTPUT") + set_directory_properties (PROPERTIES COMPILE_DEFINITIONS_MINSIZEREL "QT_NO_DEBUG_OUTPUT;QT_NO_WARNING_OUTPUT") +endif (NOT WSJT_QDEBUG_IN_RELEASE) + +set_property (SOURCE ${all_C_and_CXXSRCS} APPEND PROPERTY COMPILE_FLAGS "-include wsjtx_config.h") +set_property (SOURCE ${all_C_and_CXXSRCS} APPEND PROPERTY OBJECT_DEPENDS wsjtx_config.h) + +if (WIN32) + # generate the OmniRig COM interface source + find_program (DUMPCPP dumpcpp) + if (DUMPCPP-NOTFOUND) + message (FATAL_ERROR "dumpcpp tool not found") + endif (DUMPCPP-NOTFOUND) + execute_process ( + COMMAND ${DUMPCPP} -getfile {4FE359C5-A58F-459D-BE95-CA559FB4F270} + OUTPUT_VARIABLE AXSERVER + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + string (STRIP "${AXSERVER}" AXSERVER) + if (NOT AXSERVER) + message (FATAL_ERROR "You need to install OmniRig on this computer") + endif (NOT AXSERVER) + string (REPLACE "\"" "" AXSERVER ${AXSERVER}) + file (TO_CMAKE_PATH ${AXSERVER} AXSERVERSRCS) +endif (WIN32) if (POLICY CMP0020) cmake_policy (SET CMP0020 NEW) # link to Qt winmain on Windows @@ -18,79 +323,142 @@ endif (NOT CMAKE_BUILD_TYPE) # -# C++ setup +# decide on platform specifc packing and fixing up # +if (APPLE) + set (WSJTX_BUNDLE_VERSION ${wsjtx_VERSION}) -set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + # make sure CMAKE_INSTALL_PREFIX ends in / + string (LENGTH "${CMAKE_INSTALL_PREFIX}" LEN) + math (EXPR LEN "${LEN} -1" ) + string (SUBSTRING "${CMAKE_INSTALL_PREFIX}" ${LEN} 1 ENDCH) + if (NOT "${ENDCH}" STREQUAL "/") + set (CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/") + endif () + # install inside bundle + # set (CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}wsjtx.app") + + set (WSJT_PLUGIN_DESTINATION ${CMAKE_PROJECT_NAME}.app/Contents/PlugIns) + set (WSJT_QT_CONF_DESTINATION ${CMAKE_PROJECT_NAME}.app/Contents/Resources) + set (WSJT_SHARE_DESTINATION ${CMAKE_PROJECT_NAME}.app/Contents/Resources) + set (WSJT_BIN_DESTINATION ${CMAKE_PROJECT_NAME}.app/Contents/MacOS) +endif (APPLE) + +#set (QT_NEED_RPATH FALSE) +#if (NOT "${QT_LIBRARY_DIR}" STREQUAL "/lib" AND NOT "${QT_LIBRARY_DIR}" STREQUAL "/usr/lib" AND NOT "${QT_LIBRARY_DIR}" STREQUAL "/lib64" AND NOT "${QT_LIBRARY_DIR}" STREQUAL "/usr/lib64") +# set (QT_NEED_RPATH TRUE) +#endif () + + +# +# C & C++ setup +# +set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -std=c++11 -fexceptions -frtti") if (WIN32) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-keep-inline-dllexport") endif (WIN32) +if (APPLE) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") +else (APPLE) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") +endif (APPLE) -set (CXXSRCS - logbook/adif.cpp - logbook/countrydat.cpp - logbook/countriesworked.cpp - logbook/logbook.cpp - rigclass.cpp - psk_reporter.cpp - Modulator.cpp - Detector.cpp - logqso.cpp - displaytext.cpp - decodedtext.cpp - getfile.cpp - soundout.cpp - soundin.cpp - meterwidget.cpp - signalmeter.cpp - plotter.cpp - widegraph.cpp - devsetup.cpp - about.cpp - astro.cpp - mainwindow.cpp - main.cpp - ) +# +# Fortran setup +# +set (General_FFLAGS "-fbounds-check -Wall -Wno-conversion -fno-second-underscore") -if (WIN32) - set (CXXSRCS ${CXXSRCS} killbyname.cpp) -endif (WIN32) +# FFLAGS depend on the compiler +get_filename_component (Fortran_COMPILER_NAME ${CMAKE_Fortran_COMPILER} NAME) -set_property (SOURCE ${CXXSRCS} APPEND PROPERTY COMPILE_FLAGS "-include wsjtx_config.h") +if (Fortran_COMPILER_NAME MATCHES "gfortran.*") + # gfortran + set (CMAKE_Fortran_FLAGS_RELEASE "-funroll-all-loops -fno-f2c -O3 ${General_FFLAGS}") + set (CMAKE_Fortran_FLAGS_DEBUG "-fno-f2c -O0 -g ${General_FFLAGS}") +elseif (Fortran_COMPILER_NAME MATCHES "ifort.*") + # ifort (untested) + set (CMAKE_Fortran_FLAGS_RELEASE "-f77rtl -O3 ${General_FFLAGS}") + set (CMAKE_Fortran_FLAGS_DEBUG "-f77rtl -O0 -g ${General_FFLAGS}") +elseif (Fortran_COMPILER_NAME MATCHES "g77") + # g77 + set (CMAKE_Fortran_FLAGS_RELEASE "-funroll-all-loops -fno-f2c -O3 -m32 ${General_FFLAGS}") + set (CMAKE_Fortran_FLAGS_DEBUG "-fno-f2c -O0 -g -m32 ${General_FFLAGS}") +else (Fortran_COMPILER_NAME MATCHES "gfortran.*") + message ("CMAKE_Fortran_COMPILER full path: " ${CMAKE_Fortran_COMPILER}) + message ("Fortran compiler: " ${Fortran_COMPILER_NAME}) + message ("No optimized Fortran compiler flags are known, we just try -O2...") + set (CMAKE_Fortran_FLAGS_RELEASE "-O2 ${General_FFLAGS}") + set (CMAKE_Fortran_FLAGS_DEBUG "-O0 -g ${General_FFLAGS}") +endif (Fortran_COMPILER_NAME MATCHES "gfortran.*") + + +# +# setup and test Fortran C/C++ interaction +# + +include (FortranCInterface) +FortranCInterface_VERIFY (CXX QUIET) +FortranCInterface_HEADER (FC.h MACRO_NAMESPACE "FC_" SYMBOL_NAMESPACE "FC_" + SYMBOLS ) -set (UISRCS - mainwindow.ui - about.ui - astro.ui - devsetup.ui - widegraph.ui - logqso.ui -) # # sort out pre-requisites # # -# libfftw3 setup +# Setup RPATH so that built executable targets will run in both the +# build tree and the install location without having to set a +# (DYLD|LD)_LIBRARY_PATH override. # -find_path (fftw3f_INCLUDES fftw3.f) -find_library (fftw3f_LIBRARIES NAMES fftw3f fftw3f-3) -include_directories (${fftw3f_INCLUDES}) + +# use the full RPATH of the build tree +set (CMAKE_SKIP_BUILD_RPATH FALSE) + +# when building, don't use the install RPATH, it will still be used +# later on in the install phase +set (CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + +set (CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${WSJT_LIB_DESTINATION}") + +# add teh automaticaly determined parts of the RPATH which point to +# directories outside of the build tree to the install RPATH +set (CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +# the RPATH to be used when installing, but only if it's not a system +# directory +list (FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/${WSJT_LIB_DESTINATION}" isSystemDir) +if ("${isSystemDir}" STREQUAL "-1") + set (CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${WSJT_LIB_DESTINATION}") +endif ("${isSystemDir}" STREQUAL "-1") + + +# +# fftw3 single precsion library +# +find_package (FFTW3 COMPONENTS single REQUIRED) +if (NOT FFTW3_FOUND) + message (FATAL_ERROR "fftw3 single precsion library not found") +endif (NOT FFTW3_FOUND) +include_directories (${FFTW3_INCLUDE_DIRS}) # # libhamlib setup # -find_path (hamlib_INCLUDES hamlib/rig.h) -find_library (hamlib_LIBRARIES NAMES hamlib hamlib-2 PATH_SUFFIXES msvc gcc) -include_directories (${hamlib_INCLUDES}) +find_package (hamlib REQUIRED) +if (NOT hamlib_FOUND) + message (FATAL_ERROR "hamlib library not found") +endif (NOT hamlib_FOUND) +include_directories (${hamlib_INCLUDE_DIRS}) if (WIN32) - find_library (hamlib_RUNTIME NAMES hamlib hamlib-2 PATH_SUFFIXES bin) - get_filename_component (_hamlib_runtime_path "${hamlib_RUNTIME}" PATH) - file (GLOB hamlib_BACKENDS ${_hamlib_runtime_path}/hamlib*.dll) - find_library (usb_RUNTIME NAMES usb0 PATH_SUFFIXES bin) + find_library (hamlib_RUNTIME NAMES hamlib hamlib-2 PATH_SUFFIXES bin) + get_filename_component (_hamlib_runtime_path "${hamlib_RUNTIME}" PATH) + file (GLOB hamlib_BACKENDS ${_hamlib_runtime_path}/hamlib*.dll) + find_library (usb_RUNTIME NAMES usb0 PATH_SUFFIXES bin) endif (WIN32) @@ -102,117 +470,373 @@ endif (WIN32) find_package (Qt5Widgets REQUIRED) find_package (Qt5Multimedia REQUIRED) +if (WIN32) + add_definitions (-DQT_NEEDS_QTMAIN) + find_package (Qt5AxContainer REQUIRED) +endif (WIN32) + +# +# stuff only qmake can tell us +# +get_target_property (QMAKE_EXECUTABLE Qt5::qmake LOCATION) +function (QUERY_QMAKE VAR RESULT) + exec_program (${QMAKE_EXECUTABLE} ARGS "-query ${VAR}" RETURN_VALUE return_code OUTPUT_VARIABLE output) + if (NOT return_code) + file (TO_CMAKE_PATH "${output}" output) + set (${RESULT} ${output} PARENT_SCOPE) + endif (NOT return_code) +endfunction (QUERY_QMAKE) + +query_qmake (QT_INSTALL_PLUGINS QT_PLUGINS_DIR) +query_qmake (QT_INSTALL_IMPORTS QT_IMPORTS_DIR) +query_qmake (QT_HOST_DATA QT_DATA_DIR) +set (QT_MKSPECS_DIR ${QT_DATA_DIR}/mkspecs) + # Tell CMake to run moc when necessary set (CMAKE_AUTOMOC ON) # don't use Qt "keywords" signal, slot, emit in generated files to # avoid compatability issue with other libraries -#ADD_DEFINITIONS (-DQT_NO_KEYWORDS) +# ADD_DEFINITIONS (-DQT_NO_KEYWORDS) +# ADD_DEFINITIONS (-DUNICODE) #as per qmake # As moc files are generated in the binary dir, tell CMake to always # look for includes there: set (CMAKE_INCLUDE_CURRENT_DIR ON) # project definitions -add_definitions (-DQT5) +add_definitions (-DQT5 -DCMAKE_BUILD -DBIGSYM=1) if (CMAKE_HOST_UNIX) add_definitions (-DUNIX) elseif (CMAKE_HOST_WIN32) add_definitions (-DWIN32) endif () -# add_definitions (-DWSJT_SOFT_KEYING) -set_property (DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_RELEASE QT_NO_DEBUG_OUTPUT QT_NO_WARNING_OUTPUT) - - -# -# build the subdirectories -# -add_subdirectory (lib) - # # fetch kvasd # -if (APPLE) - set (kvasd_NAME http://svn.berlios.de/wsvn/wsjt/trunk/KVASD_gfortran_Mac${CMAKE_EXECUTABLE_SUFFIX}) -else () - set (kvasd_NAME http://www.physics.princeton.edu/pulsar/K1JT/kvasd${CMAKE_EXECUTABLE_SUFFIX}) -endif () +set (kvasd_NAME https://svn.code.sf.net/p/wsjt/wsjt/trunk/kvasd-binary/${CMAKE_SYSTEM_NAME}/kvasd${CMAKE_EXECUTABLE_SUFFIX}) file ( DOWNLOAD ${kvasd_NAME} contrib/kvasd${CMAKE_EXECUTABLE_SUFFIX} + TIMEOUT 10 STATUS kvasd_STATUS LOG kvasd_LOG SHOW_PROGRESS ) message (${kvasd_LOG}) list (GET kvasd_STATUS 0 kvasd_RC) -if (!kvasd_RC) - message (FATAL_ERROR "${kvasd_STATUS}") -endif (!kvasd_RC) +if (kvasd_RC) + message (WARNING "${kvasd_STATUS}") +endif (kvasd_RC) add_custom_target (kvasd DEPENDS contrib/kvasd${CMAKE_EXECUTABLE_SUFFIX}) # UI generation -qt5_wrap_ui (GENUISRCS ${UISRCS}) +qt5_wrap_ui (GENUISRCS ${ConfigTest_UISRCS} ${wsjtx_UISRCS}) -add_executable (wsjtx ${CXXSRCS} ${GENUISRCS} wsjtx.rc) -target_link_libraries (wsjtx jt9impl ${hamlib_LIBRARIES} ${fftw3f_LIBRARIES}) +# Resource generation +qt5_add_resources (wsjtx_RESOURCES_RCC wsjtx.qrc) + +# AX COM servers if (WIN32) - target_link_libraries (wsjtx ${CMAKE_CURRENT_SOURCE_DIR}/libHRDInterface001.a) - set_target_properties (wsjtx PROPERTIES LINK_FLAGS_RELEASE "${LINKER_FLAGS_RELEASE} -mwindows") + include (QtAxMacros) + wrap_ax_server (GENAXSRCS ${AXSERVERSRCS}) endif (WIN32) + + +# +# targets +# + +# build a library of package functionality +add_library (wsjt STATIC ${wsjt_CSRCS} ${wsjt_CXXSRCS} ${wsjt_FSRCS}) + +# build a library of package Qt functionality +add_library (wsjt_qt STATIC ${wsjt_qt_CXXSRCS} ${GENAXSRCS}) +qt5_use_modules (wsjt_qt Widgets OpenGL Network) +if (WIN32) + qt5_use_modules (wsjt_qt AxContainer AxBase) +endif (WIN32) + +add_executable (jt9sim lib/jt9sim.f90 wsjtx.rc) +target_link_libraries (jt9sim wsjt) + +add_executable (jt9code lib/jt9code.f90 wsjtx.rc) +target_link_libraries (jt9code wsjt) + +add_executable (jt9 lib/jt9.f90 lib/jt9a.f90 lib/jt9b.f90 lib/jt9c.f90 ${jt9_CXXSRCS} wsjtx.rc) +target_link_libraries (jt9 wsjt ${FFTW3F_LIBRARY}) +qt5_use_modules (jt9 Core) + +# build configuration dialog and transceiver interface test application +#add_executable (ConfigTest ${ConfigTest_CXXSRCS} ${ConfigTest_UISRCS} wsjtx.rc) +#target_link_libraries (ConfigTest wsjt wsjt_qt ${hamlib_LIBRARIES}) +#qt5_use_modules (ConfigTest Widgets OpenGL Network Multimedia) + +# build the main application +add_executable (wsjtx WIN32 MACOSX_BUNDLE ${wsjtx_CXXSRCS} ${wsjtx_UISRCS} wsjtx.rc ${WSJTX_ICON_FILE} ${wsjtx_RESOURCES_RCC}) +qt5_use_modules (wsjtx Widgets OpenGL Network Multimedia) + +set_target_properties (wsjtx PROPERTIES + MACOSX_BUNDLE_INFO_STRING "${WSJTX_DESCRIPTION_SUMMARY}" + MACOSX_BUNDLE_ICON_FILE "${WSJTX_ICON_FILE}" + MACOSX_BUNDLE_BUNDLE_VERSION ${wsjtx_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING "v${wsjtx_VERSION}" + MACOSX_BUNDLE_LONG_VERSION_STRING "Version ${wsjtx_VERSION}" + MACOSX_BUNDLE_BUNDLE_NAME "WSJT-X" + MACOSX_BUNDLE_COPYRIGHT "${WSJTX_COPYRIGHT}" + ) + +target_link_libraries (wsjtx wsjt wsjt_qt ${hamlib_LIBRARIES} ${FFTW3F_LIBRARY}) add_dependencies (wsjtx kvasd) -qt5_use_modules (wsjtx Widgets Multimedia OpenGL) - - -install ( - TARGETS wsjtx - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ) - -install (DIRECTORY Palettes DESTINATION bin PATTERN *.pal) -install (DIRECTORY samples DESTINATION bin/save) -install (FILES - shortcuts.txt - mouse_commands.txt - prefixes.txt - cty.dat - kvasd.dat - DESTINATION bin - ) - -install ( - PROGRAMS ${CMAKE_BINARY_DIR}/contrib/kvasd${CMAKE_EXECUTABLE_SUFFIX} - DESTINATION bin -) if (WIN32) - install ( - FILES ${hamlib_RUNTIME} ${hamlib_BACKENDS} ${fftw3f_LIBRARIES} ${usb_RUNTIME} contrib/HRDInterface001.dll - DESTINATION bin COMPONENT Runtime + set_target_properties ( + wsjtx + # ConfigTest + PROPERTIES LINK_FLAGS_RELEASE "${LINKER_FLAGS_RELEASE} -mwindows" ) endif (WIN32) -# a custom target that is always built -ADD_CUSTOM_TARGET (revisiontag ALL) +# +# installation +# +install (TARGETS wsjtx + RUNTIME DESTINATION ${WSJT_BIN_DESTINATION} + LIBRARY DESTINATION ${WSJT_LIB_DESTINATION} + BUNDLE DESTINATION . + # COMPONENT Runtime + ) -# creates svnversion.h using cmake script -ADD_CUSTOM_COMMAND (TARGET revisiontag COMMAND ${CMAKE_COMMAND} - -DSOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} - -P ${CMAKE_CURRENT_SOURCE_DIR}/getsvn.cmake) +install (TARGETS jt9 + RUNTIME DESTINATION ${WSJT_BIN_DESTINATION} + LIBRARY DESTINATION ${WSJT_LIB_DESTINATION} + BUNDLE DESTINATION ${WSJT_BIN_DESTINATION} + # COMPONENT Runtime + ) + +install (PROGRAMS + ${CMAKE_BINARY_DIR}/contrib/kvasd${CMAKE_EXECUTABLE_SUFFIX} + DESTINATION ${WSJT_BIN_DESTINATION} + # COMPONENT Runtime + ) -# explicitly say that the executable depends on custom target -add_dependencies(wsjtx revisiontag) # -# versioning +# uninstall support +# +configure_file ( + "${CMAKE_CURRENT_SOURCE_DIR}/CMake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + @ONLY) +add_custom_target (uninstall + "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") + + +# creates svnversion.h using cmake script +add_custom_target (revisiontag + COMMAND ${CMAKE_COMMAND} -D SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} -P ${CMAKE_CURRENT_SOURCE_DIR}/CMake/getsvn.cmake + COMMENT "Generating Subversion revision information" + VERBATIM + ) +# explicitly say that the executable depends on custom target, this is +# done indirectly so that the revisiontag target gets built exactly +# once per build +add_dependencies(wsjt revisiontag) +add_dependencies(jt9 revisiontag) +add_dependencies(wsjtx revisiontag) + + +# +# versioning and configuration # configure_file ( "${PROJECT_SOURCE_DIR}/wsjtx_config.h.in" "${PROJECT_BINARY_DIR}/wsjtx_config.h" ) - include_directories ("${PROJECT_BINARY_DIR}") + + +if (NOT WIN32 AND NOT APPLE) + # install a desktop file so wsjtx appears in the application start + # menu with an icon + install (FILES wsjtx.desktop DESTINATION share/applications) + install (FILES icons/Unix/wsjtx_icon.png DESTINATION share/pixmaps) +endif (NOT WIN32 AND NOT APPLE) + + +# +# bundle fixup only done in Release or MinSizeRel configurations +# +if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + + # get_target_property (QtCore_location Qt5::Core LOCATION) + # get_filename_component (QtCore_location ${QtCore_location} PATH) + # list (APPEND fixup_library_dirs ${QtCore_location}) + + if (APPLE) + set (CMAKE_POSTFLIGHT_SCRIPT + "${wsjtx_BINARY_DIR}/postflight.sh") + set (CMAKE_POSTUPGRADE_SCRIPT + "${wsjtx_BINARY_DIR}/postupgrade.sh") + configure_file ("${wsjtx_SOURCE_DIR}/postflight.sh.in" + "${wsjtx_BINARY_DIR}/postflight.sh") + configure_file ("${wsjtx_SOURCE_DIR}/postupgrade.sh.in" + "${wsjtx_BINARY_DIR}/postupgrade.sh") + endif () + + if (APPLE OR WIN32) + # install rules for including 3rd party libs such as Qt + + # install a qt.conf file + install (CODE " + file (WRITE \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${WSJT_QT_CONF_DESTINATION}/qt.conf\" +\"[Paths] +\") +" #COMPONENT Runtime + ) + + # if a system Qt is used (e.g. installed in /usr/lib/), it will not be included in the installation + set (fixup_exe "\${CMAKE_INSTALL_PREFIX}/${WSJT_BIN_DESTINATION}/${CMAKE_PROJECT_NAME}${CMAKE_EXECUTABLE_SUFFIX}") + get_filename_component (hamlib_lib_dir ${hamlib_LIBRARIES} PATH) + + if (APPLE) + # install required Qt plugins + install ( + DIRECTORY + ${QT_PLUGINS_DIR}/platforms + ${QT_PLUGINS_DIR}/audio + DESTINATION ${WSJT_PLUGIN_DESTINATION} + CONFIGURATIONS Release MinSizeRel + # COMPONENT Runtime + FILES_MATCHING PATTERN "*${CMAKE_SHARED_LIBRARY_SUFFIX}" + PATTERN "*minimal*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + PATTERN "*offscreen*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + PATTERN "*_debug${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + ) + # install ( + # DIRECTORY + # ${QT_PLUGINS_DIR}/platforms + # ${QT_PLUGINS_DIR}/audio + # DESTINATION ${WSJT_PLUGIN_DESTINATION} + # CONFIGURATIONS Debug + # # COMPONENT Runtime + # FILES_MATCHING PATTERN "*_debug${CMAKE_SHARED_LIBRARY_SUFFIX}" + # PATTERN "*minimal*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + # PATTERN "*offscreen*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + # ) + + # add plugins path for Mac Bundle + install (CODE " +file (APPEND \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${WSJT_QT_CONF_DESTINATION}/qt.conf\" +\"Plugins = Plugins +\") +" #COMPONENT Runtime + ) + endif (APPLE) + + if (WIN32) + # DLL directory + set (hamlib_lib_dir ${hamlib_lib_dir}/../bin) + + get_filename_component (fftw_lib_dir ${FFTW3F_LIBRARY} PATH) + list (APPEND fixup_library_dirs ${fftw_lib_dir}) + + # install required Qt plugins + install ( + DIRECTORY + ${QT_PLUGINS_DIR}/platforms + DESTINATION ${WSJT_PLUGIN_DESTINATION} + CONFIGURATIONS Release MinSizeRel + # COMPONENT Runtime + FILES_MATCHING PATTERN "*${CMAKE_SHARED_LIBRARY_SUFFIX}" + PATTERN "*minimal*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + PATTERN "*offscreen*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + PATTERN "*d${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + ) + # install ( + # DIRECTORY + # ${QT_PLUGINS_DIR}/platforms + # DESTINATION ${WSJT_PLUGIN_DESTINATION} + # CONFIGURATIONS Debug + # # COMPONENT Runtime + # FILES_MATCHING PATTERN "*d${CMAKE_SHARED_LIBRARY_SUFFIX}" + # PATTERN "*minimal*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + # PATTERN "*offscreen*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE + # ) + + # add plugins path for WIN32 + file (RELATIVE_PATH _plugins_path "${CMAKE_INSTALL_PREFIX}/${WSJT_QT_CONF_DESTINATION}" "${CMAKE_INSTALL_PREFIX}/${WSJT_PLUGIN_DESTINATION}") + install (CODE " +file (APPEND \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${WSJT_QT_CONF_DESTINATION}/qt.conf\" +\"Plugins = ${_plugins_path} +\") +" #COMPONENT Runtime + ) + + # set (gp_tool "objdump") # we want MinGW tool - not MSVC (See GetPrerequisites.cmake) + endif (WIN32) + + list (APPEND fixup_library_dirs ${hamlib_lib_dir}) + + install (CODE " + file (GLOB_RECURSE QTPLUGINS + \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${WSJT_PLUGIN_DESTINATION}/*${CMAKE_SHARED_LIBRARY_SUFFIX}\") + include (BundleUtilities) + set (BU_CHMOD_BUNDLE_ITEMS ON) + set (gp_tool ${gp_tool}) + message (STATUS \"fixup_exe: ${fixup_exe}\") + fixup_bundle (\"${fixup_exe}\" \"\${QTPLUGINS}\" \"${fixup_library_dirs}\") + " + #COMPONENT Runtime + ) + endif (APPLE OR WIN32) + +endif (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + + +# +# packaging +# +set (CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") +set (CPACK_PACKAGE_VERSION_MAJOR ${WSJTX_VERSION_MAJOR}) +set (CPACK_PACKAGE_VERSION_MINOR ${WSJTX_VERSION_MINOR}) +set (CPACK_PACKAGE_VERSION_PATCH ${WSJTX_VERSION_PATCH}) + +if (WIN32) + set (CPACK_GENERATOR "NSIS") +elseif (APPLE) + set (CPACK_GENERATOR "DragNDrop" "PackageMaker") +else () + # + # Derive the correct filename for a Debian package because the DEB + # generator doesn't do this correctly at present. + # + string (TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_PACKAGE_NAME_LOWERCASE) + find_program (DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") + if (DPKG_PROGRAM) + execute_process ( + COMMAND ${DPKG_PROGRAM} --print-architecture + OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + set (DEBIAN_PACKAGE_FILE_NAME + "${CPACK_PACKAGE_NAME_LOWERCASE}_${wsjtx_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") + + else (DPKG_PROGRAM) + set (DEBIAN_PACKAGE_FILE_NAME + "${CPACK_PACKAGE_NAME_LOWERCASE}_${wsjtx_VERSION}_${CMAKE_SYSTEM_NAME}") + endif (DPKG_PROGRAM) + + set (CPACK_DEBIAN_PACKAGE_PACKAGE_SHLIBDEPS ON) + + set (CPACK_GENERATOR "DEB" "RPM" "TGZ") +endif () + +configure_file ("${PROJECT_SOURCE_DIR}/CMakeCPackOptions.cmake.in" + "${PROJECT_BINARY_DIR}/CMakeCPackOptions.cmake" @ONLY) +set (CPACK_PROJECT_CONFIG_FILE "${PROJECT_BINARY_DIR}/CMakeCPackOptions.cmake") + +include (CPack) diff --git a/ConfigTest.cpp b/ConfigTest.cpp new file mode 100644 index 000000000..1d596c861 --- /dev/null +++ b/ConfigTest.cpp @@ -0,0 +1,63 @@ +#include +#include + +#include + +#include +#include +#include +#include + +#include "GetUserId.hpp" +#include "TraceFile.hpp" +#include "TestConfiguration.hpp" +#include "AudioDevice.hpp" +#include "TransceiverFactory.hpp" +#include "Configuration.hpp" + +int main (int argc, char *argv[]) +{ + try + { + QApplication application {argc, argv}; + setlocale (LC_NUMERIC, "C"); // ensure number forms are in + // consistent format, do this after + // instantiating QApplication so + // that GUI has correct l18n + + // get a unique id from the user + auto id = get_user_id (); + + // open a user specific trace file + TraceFile trace_file {QDir {QApplication::applicationDirPath () + "/logs"}.absoluteFilePath (id + "_config_test.log")}; + + // announce to log file + qDebug () << "Configuration Test v" WSJTX_STRINGIZE (CONFIG_TEST_VERSION_MAJOR) "." WSJTX_STRINGIZE (CONFIG_TEST_VERSION_MINOR) "." WSJTX_STRINGIZE (CONFIG_TEST_VERSION_PATCH) ", " WSJTX_STRINGIZE (SVNVERSION) " - Program startup"; + + // open user specific settings + QSettings settings {QDir {QApplication::applicationDirPath () + "/settings"}.absoluteFilePath (id + "_config_test.ini"), QSettings::IniFormat}; + + // the test GUI + TestConfiguration main_window ("ConfigTest", &settings); + + // hook up close down mechanism + QObject::connect (&application, SIGNAL (lastWindowClosed ()), &application, SLOT (quit ())); + + // start event loop + auto status = application.exec(); + qDebug () << "Normal exit with status: " << status; + return status; + } + catch (std::exception const& e) + { + qDebug () << "Error exit: " << e.what () << '\n'; + std::cerr << "Error: " << e.what () << '\n'; + } + catch (...) + { + qDebug () << "Unknown error exit\n"; + std::cerr << "Unexpected error\n"; + throw; // hoping the runtime might tell us more about the exception + } + return -1; +} diff --git a/Configuration.cpp b/Configuration.cpp new file mode 100644 index 000000000..15e50ec0b --- /dev/null +++ b/Configuration.cpp @@ -0,0 +1,2128 @@ +#include "Configuration.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ui_Configuration.h" + +#include "SettingsGroup.hpp" +#include "FrequencyLineEdit.hpp" +#include "FrequencyItemDelegate.hpp" +#include "ForeignKeyDelegate.hpp" +#include "TransceiverFactory.hpp" +#include "Transceiver.hpp" +#include "Bands.hpp" +#include "FrequencyList.hpp" +#include "StationList.hpp" + +#include "pimpl_impl.hpp" + +#include "moc_Configuration.cpp" + +namespace +{ + struct init + { + init () + { + qRegisterMetaType ("Configuration::DataMode"); + qRegisterMetaTypeStreamOperators ("Configuration::DataMode"); + } + } static_initializer; + + // these undocumented flag values when stored in (Qt::UserRole - 1) + // of a ComboBox item model index allow the item to be enabled or + // disabled + int const combo_box_item_enabled (32 | 1); + int const combo_box_item_disabled (0); +} + + +// +// Dialog to get a new Frequency item +// +class FrequencyDialog final + : public QDialog +{ +public: + using Frequency = Radio::Frequency; + + explicit FrequencyDialog (QWidget * parent = nullptr) + : QDialog {parent} + { + setWindowTitle (QApplication::applicationName () + " - " + tr ("Add Frequency")); + + auto form_layout = new QFormLayout (); + form_layout->addRow (tr ("&Frequency (MHz):"), &frequency_line_edit_); + + auto main_layout = new QVBoxLayout (this); + main_layout->addLayout (form_layout); + + auto button_box = new QDialogButtonBox {QDialogButtonBox::Ok | QDialogButtonBox::Cancel}; + main_layout->addWidget (button_box); + + connect (button_box, &QDialogButtonBox::accepted, this, &FrequencyDialog::accept); + connect (button_box, &QDialogButtonBox::rejected, this, &FrequencyDialog::reject); + } + + Frequency frequency () const + { + return frequency_line_edit_.frequency (); + } + +private: + FrequencyLineEdit frequency_line_edit_; +}; + + +// +// Dialog to get a new Station item +// +class StationDialog final + : public QDialog +{ +public: + explicit StationDialog (Bands * bands, QWidget * parent = nullptr) + : QDialog {parent} + , bands_ {bands} + { + setWindowTitle (QApplication::applicationName () + " - " + tr ("Add Station")); + + band_.setModel (bands_); + + auto form_layout = new QFormLayout (); + form_layout->addRow (tr ("&Band:"), &band_); + form_layout->addRow (tr ("&Offset (MHz):"), &delta_); + form_layout->addRow (tr ("&Antenna:"), &description_); + + auto main_layout = new QVBoxLayout (this); + main_layout->addLayout (form_layout); + + auto button_box = new QDialogButtonBox {QDialogButtonBox::Ok | QDialogButtonBox::Cancel}; + main_layout->addWidget (button_box); + + connect (button_box, &QDialogButtonBox::accepted, this, &StationDialog::accept); + connect (button_box, &QDialogButtonBox::rejected, this, &StationDialog::reject); + + if (delta_.text ().isEmpty ()) + { + delta_.setText ("0"); + } + } + + StationList::Station station () const + { + return {band_.currentText (), delta_.frequency_delta (), description_.text ()}; + } + +private: + Bands * bands_; + + QComboBox band_; + FrequencyDeltaLineEdit delta_; + QLineEdit description_; +}; + +// Fields that are transceiver related. +// +// These are aggregated in a structure to enable a non-equivalence to +// be provided. +// +// don't forget to update the != operator if any fields are added +// otherwise rig parameter changes will not trigger reconfiguration +struct RigParams +{ + QString CAT_serial_port_; + QString CAT_network_port_; + qint32 CAT_baudrate_; + TransceiverFactory::DataBits CAT_data_bits_; + TransceiverFactory::StopBits CAT_stop_bits_; + TransceiverFactory::Handshake CAT_handshake_; + bool CAT_DTR_always_on_; + bool CAT_RTS_always_on_; + qint32 CAT_poll_interval_; + TransceiverFactory::PTTMethod PTT_method_; + QString PTT_port_; + TransceiverFactory::TXAudioSource TX_audio_source_; + TransceiverFactory::SplitMode split_mode_; + QString rig_name_; +}; +bool operator != (RigParams const&, RigParams const&); + +inline +bool operator == (RigParams const& lhs, RigParams const& rhs) +{ + return !(lhs != rhs); +} + + +// Internal implementation of the Configuration class. +class Configuration::impl final + : public QDialog +{ + Q_OBJECT; + +public: + using FrequencyDelta = Radio::FrequencyDelta; + + explicit impl (Configuration * self, QString const& instance_key, QSettings * settings, QWidget * parent); + ~impl (); + + bool have_rig (bool open_if_closed = true); + + void transceiver_frequency (Frequency); + void transceiver_tx_frequency (Frequency); + void transceiver_mode (MODE); + void transceiver_ptt (bool); + void sync_transceiver (bool force_signal); + + Q_SLOT int exec () override; + Q_SLOT void accept () override; + Q_SLOT void reject () override; + Q_SLOT void done (int) override; + +private: + typedef QList AudioDevices; + + void read_settings (); + void write_settings (); + + bool load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *); + void update_audio_channels (QComboBox const *, int, QComboBox *, bool); + + void initialise_models (); + bool open_rig (); + bool set_mode (); + void close_rig (); + void enumerate_rigs (); + void set_rig_invariants (); + bool validate (); + void message_box (QString const& reason, QString const& detail = QString ()); + void fill_port_combo_box (QComboBox *); + + Q_SLOT void on_font_push_button_clicked (); + Q_SLOT void on_decoded_text_font_push_button_clicked (); + + Q_SLOT void on_PTT_port_combo_box_activated (int); + + Q_SLOT void on_CAT_port_combo_box_activated (int); + + Q_SLOT void on_CAT_serial_baud_combo_box_currentIndexChanged (int); + + Q_SLOT void on_CAT_data_bits_button_group_buttonClicked (int); + + Q_SLOT void on_CAT_stop_bits_button_group_buttonClicked (int); + + Q_SLOT void on_CAT_handshake_button_group_buttonClicked (int); + + Q_SLOT void on_CAT_poll_interval_spin_box_valueChanged (int); + + Q_SLOT void on_split_mode_button_group_buttonClicked (int); + + Q_SLOT void on_test_CAT_push_button_clicked (); + + Q_SLOT void on_test_PTT_push_button_clicked (); + + Q_SLOT void on_CAT_DTR_check_box_toggled (bool); + + Q_SLOT void on_CAT_RTS_check_box_toggled (bool); + + Q_SLOT void on_rig_combo_box_currentIndexChanged (int); + + Q_SLOT void on_sound_input_combo_box_currentTextChanged (QString const&); + Q_SLOT void on_sound_output_combo_box_currentTextChanged (QString const&); + + Q_SLOT void on_add_macro_push_button_clicked (bool = false); + Q_SLOT void on_delete_macro_push_button_clicked (bool = false); + + Q_SLOT void on_PTT_method_button_group_buttonClicked (int); + + Q_SLOT void on_callsign_line_edit_editingFinished (); + + Q_SLOT void on_grid_line_edit_editingFinished (); + + Q_SLOT void on_add_macro_line_edit_editingFinished (); + Q_SLOT void delete_macro (); + + Q_SLOT void on_save_path_select_push_button_clicked (bool); + + Q_SLOT void delete_frequencies (); + Q_SLOT void insert_frequency (); + + Q_SLOT void delete_stations (); + Q_SLOT void insert_station (); + + Q_SLOT void handle_transceiver_update (TransceiverState); + Q_SLOT void handle_transceiver_failure (QString reason); + + // typenames used as arguments must match registered type names :( + Q_SIGNAL void stop_transceiver () const; + Q_SIGNAL void frequency (Frequency rx) const; + Q_SIGNAL void tx_frequency (Frequency tx, bool rationalize_mode) const; + Q_SIGNAL void mode (Transceiver::MODE, bool rationalize) const; + Q_SIGNAL void ptt (bool) const; + Q_SIGNAL void sync (bool force_signal) const; + + Configuration * const self_; // back pointer to public interface + + QThread transceiver_thread_; + TransceiverFactory transceiver_factory_; + + Ui::configuration_dialog * ui_; + + QSettings * settings_; + + QDir temp_path_; + QDir data_path_; + QDir default_save_directory_; + QDir save_directory_; + + QFont font_; + bool font_changed_; + QFont next_font_; + + QFont decoded_text_font_; + bool decoded_text_font_changed_; + QFont next_decoded_text_font_; + + bool restart_sound_input_device_; + bool restart_sound_output_device_; + + unsigned jt9w_bw_mult_; + float jt9w_min_dt_; + float jt9w_max_dt_; + + QStringListModel macros_; + QStringListModel next_macros_; + QAction * macro_delete_action_; + + Bands bands_; + FrequencyList frequencies_; + FrequencyList next_frequencies_; + StationList stations_; + StationList next_stations_; + + QAction * frequency_delete_action_; + QAction * frequency_insert_action_; + FrequencyDialog * frequency_dialog_; + + QAction * station_delete_action_; + QAction * station_insert_action_; + StationDialog * station_dialog_; + + RigParams rig_params_; + RigParams saved_rig_params_; + bool rig_active_; + bool have_rig_; + bool rig_changed_; + TransceiverState cached_rig_state_; + bool ptt_state_; + bool setup_split_; + bool enforce_mode_and_split_; + FrequencyDelta transceiver_offset_; + + // configuration fields that we publish + QString my_callsign_; + QString my_grid_; + qint32 id_interval_; + bool id_after_73_; + bool spot_to_psk_reporter_; + bool monitor_off_at_startup_; + bool log_as_RTTY_; + bool report_in_comments_; + bool prompt_to_log_; + bool insert_blank_; + bool DXCC_; + bool clear_DX_; + bool miles_; + bool quick_call_; + bool disable_TX_on_73_; + bool watchdog_; + bool TX_messages_; + DataMode data_mode_; + + QAudioDeviceInfo audio_input_device_; + bool default_audio_input_device_selected_; + AudioDevice::Channel audio_input_channel_; + QAudioDeviceInfo audio_output_device_; + bool default_audio_output_device_selected_; + AudioDevice::Channel audio_output_channel_; + + friend class Configuration; +}; + +#include "Configuration.moc" + + +// delegate to implementation class +Configuration::Configuration (QString const& instance_key, QSettings * settings, QWidget * parent) + : m_ {this, instance_key, settings, parent} +{ +} + +Configuration::~Configuration () +{ +} + +QDir Configuration::data_path () const {return m_->data_path_;} + +int Configuration::exec () {return m_->exec ();} + +QAudioDeviceInfo const& Configuration::audio_input_device () const {return m_->audio_input_device_;} +AudioDevice::Channel Configuration::audio_input_channel () const {return m_->audio_input_channel_;} +QAudioDeviceInfo const& Configuration::audio_output_device () const {return m_->audio_output_device_;} +AudioDevice::Channel Configuration::audio_output_channel () const {return m_->audio_output_channel_;} +bool Configuration::restart_audio_input () const {return m_->restart_sound_input_device_;} +bool Configuration::restart_audio_output () const {return m_->restart_sound_output_device_;} +unsigned Configuration::jt9w_bw_mult () const {return m_->jt9w_bw_mult_;} +float Configuration::jt9w_min_dt () const {return m_->jt9w_min_dt_;} +float Configuration::jt9w_max_dt () const {return m_->jt9w_max_dt_;} +QString Configuration::my_callsign () const {return m_->my_callsign_;} +QString Configuration::my_grid () const {return m_->my_grid_;} +QFont Configuration::decoded_text_font () const {return m_->decoded_text_font_;} +qint32 Configuration::id_interval () const {return m_->id_interval_;} +bool Configuration::id_after_73 () const {return m_->id_after_73_;} +bool Configuration::spot_to_psk_reporter () const {return m_->spot_to_psk_reporter_;} +bool Configuration::monitor_off_at_startup () const {return m_->monitor_off_at_startup_;} +bool Configuration::log_as_RTTY () const {return m_->log_as_RTTY_;} +bool Configuration::report_in_comments () const {return m_->report_in_comments_;} +bool Configuration::prompt_to_log () const {return m_->prompt_to_log_;} +bool Configuration::insert_blank () const {return m_->insert_blank_;} +bool Configuration::DXCC () const {return m_->DXCC_;} +bool Configuration::clear_DX () const {return m_->clear_DX_;} +bool Configuration::miles () const {return m_->miles_;} +bool Configuration::quick_call () const {return m_->quick_call_;} +bool Configuration::disable_TX_on_73 () const {return m_->disable_TX_on_73_;} +bool Configuration::watchdog () const {return m_->watchdog_;} +bool Configuration::TX_messages () const {return m_->TX_messages_;} +bool Configuration::split_mode () const {return m_->rig_params_.split_mode_ != TransceiverFactory::split_mode_none;} +Bands * Configuration::bands () {return &m_->bands_;} +StationList * Configuration::stations () {return &m_->stations_;} +FrequencyList * Configuration::frequencies () {return &m_->frequencies_;} +QStringListModel * Configuration::macros () {return &m_->macros_;} +QDir Configuration::save_directory () const {return m_->save_directory_;} +QString Configuration::rig_name () const {return m_->rig_params_.rig_name_;} + +bool Configuration::transceiver_online (bool open_if_closed) +{ +#if WSJT_TRACE_CAT + qDebug () << "Configuration::transceiver_online: open_if_closed:" << open_if_closed << m_->cached_rig_state_; +#endif + + return m_->have_rig (open_if_closed); +} + +void Configuration::transceiver_offline () +{ +#if WSJT_TRACE_CAT + qDebug () << "Configuration::transceiver_offline:" << m_->cached_rig_state_; +#endif + + return m_->close_rig (); +} + +void Configuration::transceiver_frequency (Frequency f) +{ +#if WSJT_TRACE_CAT + qDebug () << "Configuration::transceiver_frequency:" << f << m_->cached_rig_state_; +#endif + + m_->transceiver_frequency (f); +} + +void Configuration::transceiver_tx_frequency (Frequency f) +{ +#if WSJT_TRACE_CAT + qDebug () << "Configuration::transceiver_tx_frequency:" << f << m_->cached_rig_state_; +#endif + + m_->setup_split_ = true; + m_->transceiver_tx_frequency (f); +} + +void Configuration::transceiver_mode (MODE mode) +{ +#if WSJT_TRACE_CAT + qDebug () << "Configuration::transceiver_mode:" << mode << m_->cached_rig_state_; +#endif + + m_->transceiver_mode (mode); +} + +void Configuration::transceiver_ptt (bool on) +{ +#if WSJT_TRACE_CAT + qDebug () << "Configuration::transceiver_ptt:" << on << m_->cached_rig_state_; +#endif + + m_->transceiver_ptt (on); +} + +void Configuration::sync_transceiver (bool force_signal, bool enforce_mode_and_split) +{ +#if WSJT_TRACE_CAT + qDebug () << "Configuration::sync_transceiver: force signal:" << force_signal << "enforce_mode_and_split:" << enforce_mode_and_split << m_->cached_rig_state_; +#endif + + m_->enforce_mode_and_split_ = enforce_mode_and_split; + m_->setup_split_ = enforce_mode_and_split; + m_->sync_transceiver (force_signal); +} + + +Configuration::impl::impl (Configuration * self, QString const& instance_key, QSettings * settings, QWidget * parent) + : QDialog {parent} + , self_ {self} + , ui_ {new Ui::configuration_dialog} + , settings_ {settings} + , temp_path_ {QApplication::applicationDirPath ()} + , data_path_ {QApplication::applicationDirPath ()} + , font_ {QApplication::font ()} + , font_changed_ {false} + , decoded_text_font_changed_ {false} + , frequencies_ { + { + 136130, + 474200, + 1838000, + 3576000, + 5357000, + 7076000, + 10138000, + 14076000, + 18102000, + 21076000, + 24917000, + 28076000, + 50276000, + 70091000, + 144489000, + } + } + , stations_ {&bands_} + , next_stations_ {&bands_} + , frequency_dialog_ {new FrequencyDialog {this}} + , station_dialog_ {new StationDialog {&bands_, this}} + , rig_active_ {false} + , have_rig_ {false} + , rig_changed_ {false} + , ptt_state_ {false} + , setup_split_ {false} + , enforce_mode_and_split_ {false} + , transceiver_offset_ {0} + , default_audio_input_device_selected_ {false} + , default_audio_output_device_selected_ {false} +{ + (void)instance_key; // quell compiler warning + + ui_->setupUi (this); + +#if WSJT_STANDARD_FILE_LOCATIONS + // the following needs to be done on all platforms but changes need + // coordination with JTAlert developers + { + // Create a temporary directory in a suitable location + QString temp_location {QStandardPaths::writableLocation (QStandardPaths::TempLocation)}; + if (!temp_location.isEmpty ()) + { + temp_path_.setPath (temp_location); + } + + QString unique_directory {instance_key}; + if (!temp_path_.mkpath (unique_directory) || !temp_path_.cd (unique_directory)) + { + QMessageBox::critical (this, "WSJT-X", tr ("Create temporary directory error: ") + temp_path_.absolutePath ()); + throw std::runtime_error {"Failed to create usable temporary directory"}; + } + } + + // kvasd writes files to $cwd so by changing to the temp directory + // we can keep these files out of the startup directory + QDir::setCurrent (temp_path_.absolutePath ()); + + // we run kvasd with a $cwd of our temp directory so we need to copy + // in a fresh kvasd.dat for it + // + // this requirement changes with kvasd v 1.12 since it has a + // parameter to set the data file path + QString kvasd_data_file {"kvasd.dat"}; + if (!temp_path_.exists (kvasd_data_file)) + { + auto dest_file = temp_path_.absoluteFilePath (kvasd_data_file); + if (!QFile::copy (":/" + kvasd_data_file, dest_file)) + { + QMessageBox::critical (this, "WSJT-X", tr ("Cannot copy: :/") + kvasd_data_file + tr (" to: ") + temp_path_.absolutePath ()); + throw std::runtime_error {"Failed to copy kvasd.dat to temporary directory"}; + } + else + { + QFile {dest_file}.setPermissions (QFile::ReadOwner | QFile::WriteOwner); + } + } + + + { + // Find a suitable data file location + QString data_location {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}; + if (!data_location.isEmpty ()) + { + data_path_.setPath (data_location); + } + + if (!data_path_.mkpath (".")) + { + QMessageBox::critical (this, "WSJT-X", tr ("Create data directory error: ") + data_path_.absolutePath ()); + throw std::runtime_error {"Failed to create data directory"}; + } + } + // qDebug () << "Data store path:" << data_path_.absolutePath (); + // auto paths = QStandardPaths::standardLocations (QStandardPaths::DataLocation); + // Q_FOREACH (auto const& path, paths) + // { + // qDebug () << "path:" << path; + // } +#endif + + { + // Make sure the default save directory exists + QString save_dir {"save"}; + default_save_directory_ = data_path_; + if (!default_save_directory_.mkpath (save_dir) || !default_save_directory_.cd (save_dir)) + { + QMessageBox::critical (this, "WSJT-X", tr ("Create Directory", "Cannot create directory \"") + default_save_directory_.absoluteFilePath (save_dir) + "\"."); + throw std::runtime_error {"Failed to create save directory"}; + } + + // we now have a deafult save path that exists + + // make sure samples directory exists + QString samples_dir {"samples"}; + if (!default_save_directory_.mkpath (samples_dir)) + { + QMessageBox::critical (this, "WSJT-X", tr ("Create Directory", "Cannot create directory \"") + default_save_directory_.absoluteFilePath (samples_dir) + "\"."); + throw std::runtime_error {"Failed to create save directory"}; + } + + // copy in any new sample files to the sample directory + QDir dest_dir {default_save_directory_}; + dest_dir.cd (samples_dir); + + QDir source_dir {":/" + samples_dir}; + source_dir.cd (save_dir); + source_dir.cd (samples_dir); + auto list = source_dir.entryInfoList (QStringList {{"*.wav"}}, QDir::Files | QDir::Readable); + Q_FOREACH (auto const& item, list) + { + if (!dest_dir.exists (item.fileName ())) + { + QFile file {item.absoluteFilePath ()}; + file.copy (dest_dir.absoluteFilePath (item.fileName ())); + } + } + } + + // this must be done after the default paths above are set + read_settings (); + + // + // validation + // + ui_->callsign_line_edit->setValidator (new QRegExpValidator {QRegExp {"[A-Za-z0-9/]+"}, this}); + ui_->grid_line_edit->setValidator (new QRegExpValidator {QRegExp {"[A-Ra-r]{2,2}[0-9]{2,2}[A-Xa-x]{0,2}"}, this}); + ui_->add_macro_line_edit->setValidator (new QRegExpValidator {QRegExp {"[ A-Za-z0-9/?]+"}, this}); + + // + // assign ids to radio buttons + // + ui_->CAT_data_bits_button_group->setId (ui_->CAT_7_bit_radio_button, TransceiverFactory::seven_data_bits); + ui_->CAT_data_bits_button_group->setId (ui_->CAT_8_bit_radio_button, TransceiverFactory::eight_data_bits); + + ui_->CAT_stop_bits_button_group->setId (ui_->CAT_one_stop_bit_radio_button, TransceiverFactory::one_stop_bit); + ui_->CAT_stop_bits_button_group->setId (ui_->CAT_two_stop_bit_radio_button, TransceiverFactory::two_stop_bits); + + ui_->CAT_handshake_button_group->setId (ui_->CAT_handshake_none_radio_button, TransceiverFactory::handshake_none); + ui_->CAT_handshake_button_group->setId (ui_->CAT_handshake_xon_radio_button, TransceiverFactory::handshake_XonXoff); + ui_->CAT_handshake_button_group->setId (ui_->CAT_handshake_hardware_radio_button, TransceiverFactory::handshake_hardware); + + ui_->PTT_method_button_group->setId (ui_->PTT_VOX_radio_button, TransceiverFactory::PTT_method_VOX); + ui_->PTT_method_button_group->setId (ui_->PTT_CAT_radio_button, TransceiverFactory::PTT_method_CAT); + ui_->PTT_method_button_group->setId (ui_->PTT_DTR_radio_button, TransceiverFactory::PTT_method_DTR); + ui_->PTT_method_button_group->setId (ui_->PTT_RTS_radio_button, TransceiverFactory::PTT_method_RTS); + + ui_->TX_audio_source_button_group->setId (ui_->TX_source_mic_radio_button, TransceiverFactory::TX_audio_source_front); + ui_->TX_audio_source_button_group->setId (ui_->TX_source_data_radio_button, TransceiverFactory::TX_audio_source_rear); + + ui_->TX_mode_button_group->setId (ui_->mode_none_radio_button, data_mode_none); + ui_->TX_mode_button_group->setId (ui_->mode_USB_radio_button, data_mode_USB); + ui_->TX_mode_button_group->setId (ui_->mode_data_radio_button, data_mode_data); + + ui_->split_mode_button_group->setId (ui_->split_none_radio_button, TransceiverFactory::split_mode_none); + ui_->split_mode_button_group->setId (ui_->split_rig_radio_button, TransceiverFactory::split_mode_rig); + ui_->split_mode_button_group->setId (ui_->split_emulate_radio_button, TransceiverFactory::split_mode_emulate); + + // + // setup PTT port combo box drop down content + // + fill_port_combo_box (ui_->PTT_port_combo_box); + ui_->PTT_port_combo_box->addItem ("CAT"); + + // + // setup hooks to keep audio channels aligned with devices + // + { + using namespace std; + using namespace std::placeholders; + + function cb (bind (&Configuration::impl::update_audio_channels, this, ui_->sound_input_combo_box, _1, ui_->sound_input_channel_combo_box, false)); + connect (ui_->sound_input_combo_box, static_cast (&QComboBox::currentIndexChanged), cb); + cb = bind (&Configuration::impl::update_audio_channels, this, ui_->sound_output_combo_box, _1, ui_->sound_output_channel_combo_box, true); + connect (ui_->sound_output_combo_box, static_cast (&QComboBox::currentIndexChanged), cb); + } + + // + // setup macros list view + // + ui_->macros_list_view->setModel (&next_macros_); + + macro_delete_action_ = new QAction {tr ("&Delete"), ui_->macros_list_view}; + ui_->macros_list_view->insertAction (nullptr, macro_delete_action_); + connect (macro_delete_action_, &QAction::triggered, this, &Configuration::impl::delete_macro); + + + // + // setup working frequencies table model & view + // + frequencies_.sort (0); + + ui_->frequencies_table_view->setModel (&next_frequencies_); + ui_->frequencies_table_view->sortByColumn (0, Qt::AscendingOrder); + ui_->frequencies_table_view->setItemDelegateForColumn (0, new FrequencyItemDelegate {&bands_, this}); + ui_->frequencies_table_view->setColumnHidden (1, true); + + frequency_delete_action_ = new QAction {tr ("&Delete"), ui_->frequencies_table_view}; + ui_->frequencies_table_view->insertAction (nullptr, frequency_delete_action_); + connect (frequency_delete_action_, &QAction::triggered, this, &Configuration::impl::delete_frequencies); + + frequency_insert_action_ = new QAction {tr ("&Insert ..."), ui_->frequencies_table_view}; + ui_->frequencies_table_view->insertAction (nullptr, frequency_insert_action_); + connect (frequency_insert_action_, &QAction::triggered, this, &Configuration::impl::insert_frequency); + + + // + // setup stations table model & view + // + stations_.sort (0); + + ui_->stations_table_view->setModel (&next_stations_); + ui_->stations_table_view->sortByColumn (0, Qt::AscendingOrder); + ui_->stations_table_view->setColumnWidth (1, 150); + ui_->stations_table_view->setItemDelegateForColumn (0, new ForeignKeyDelegate {&next_stations_, &bands_, 0, this}); + ui_->stations_table_view->setItemDelegateForColumn (1, new FrequencyDeltaItemDelegate {this}); + + station_delete_action_ = new QAction {tr ("&Delete"), ui_->stations_table_view}; + ui_->stations_table_view->insertAction (nullptr, station_delete_action_); + connect (station_delete_action_, &QAction::triggered, this, &Configuration::impl::delete_stations); + + station_insert_action_ = new QAction {tr ("&Insert ..."), ui_->stations_table_view}; + ui_->stations_table_view->insertAction (nullptr, station_insert_action_); + connect (station_insert_action_, &QAction::triggered, this, &Configuration::impl::insert_station); + + // + // load combo boxes with audio setup choices + // + default_audio_input_device_selected_ = load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &audio_input_device_); + default_audio_output_device_selected_ = load_audio_devices (QAudio::AudioOutput, ui_->sound_output_combo_box, &audio_output_device_); + + update_audio_channels (ui_->sound_input_combo_box, ui_->sound_input_combo_box->currentIndex (), ui_->sound_input_channel_combo_box, false); + update_audio_channels (ui_->sound_output_combo_box, ui_->sound_output_combo_box->currentIndex (), ui_->sound_output_channel_combo_box, true); + + ui_->sound_input_channel_combo_box->setCurrentIndex (audio_input_channel_); + ui_->sound_output_channel_combo_box->setCurrentIndex (audio_output_channel_); + + restart_sound_input_device_ = false; + restart_sound_output_device_ = false; + + enumerate_rigs (); + initialise_models (); + + transceiver_thread_.start (); +} + +Configuration::impl::~impl () +{ + write_settings (); + + close_rig (); + + transceiver_thread_.quit (); + transceiver_thread_.wait (); + + QDir::setCurrent (QApplication::applicationDirPath ()); +#if WSJT_STANDARD_FILE_LOCATIONS + temp_path_.removeRecursively (); // clean up temp files +#endif +} + +void Configuration::impl::initialise_models () +{ + auto pal = ui_->callsign_line_edit->palette (); + if (my_callsign_.isEmpty ()) + { + pal.setColor (QPalette::Base, "#ffccff"); + } + else + { + pal.setColor (QPalette::Base, Qt::white); + } + ui_->callsign_line_edit->setPalette (pal); + ui_->grid_line_edit->setPalette (pal); + ui_->callsign_line_edit->setText (my_callsign_); + ui_->grid_line_edit->setText (my_grid_); + font_changed_ = false; + decoded_text_font_changed_ = false; + ui_->CW_id_interval_spin_box->setValue (id_interval_); + ui_->PTT_method_button_group->button (rig_params_.PTT_method_)->setChecked (true); + ui_->save_path_display_label->setText (save_directory_.absolutePath ()); + ui_->CW_id_after_73_check_box->setChecked (id_after_73_); + ui_->psk_reporter_check_box->setChecked (spot_to_psk_reporter_); + ui_->monitor_off_check_box->setChecked (monitor_off_at_startup_); + ui_->log_as_RTTY_check_box->setChecked (log_as_RTTY_); + ui_->report_in_comments_check_box->setChecked (report_in_comments_); + ui_->prompt_to_log_check_box->setChecked (prompt_to_log_); + ui_->insert_blank_check_box->setChecked (insert_blank_); + ui_->DXCC_check_box->setChecked (DXCC_); + ui_->clear_DX_check_box->setChecked (clear_DX_); + ui_->miles_check_box->setChecked (miles_); + ui_->quick_call_check_box->setChecked (quick_call_); + ui_->disable_TX_on_73_check_box->setChecked (disable_TX_on_73_); + ui_->watchdog_check_box->setChecked (watchdog_); + ui_->TX_messages_check_box->setChecked (TX_messages_); + ui_->jt9w_bandwidth_mult_combo_box->setCurrentText (QString::number (jt9w_bw_mult_)); + ui_->jt9w_min_dt_double_spin_box->setValue (jt9w_min_dt_); + ui_->jt9w_max_dt_double_spin_box->setValue (jt9w_max_dt_); + ui_->rig_combo_box->setCurrentText (rig_params_.rig_name_); + ui_->TX_mode_button_group->button (data_mode_)->setChecked (true); + ui_->split_mode_button_group->button (rig_params_.split_mode_)->setChecked (true); + ui_->CAT_port_combo_box->setCurrentText (rig_params_.CAT_serial_port_); + ui_->CAT_serial_baud_combo_box->setCurrentText (QString::number (rig_params_.CAT_baudrate_)); + ui_->CAT_data_bits_button_group->button (rig_params_.CAT_data_bits_)->setChecked (true); + ui_->CAT_stop_bits_button_group->button (rig_params_.CAT_stop_bits_)->setChecked (true); + ui_->CAT_handshake_button_group->button (rig_params_.CAT_handshake_)->setChecked (true); + ui_->CAT_DTR_check_box->setChecked (rig_params_.CAT_DTR_always_on_); + ui_->CAT_RTS_check_box->setChecked (rig_params_.CAT_RTS_always_on_); + ui_->TX_audio_source_button_group->button (rig_params_.TX_audio_source_)->setChecked (true); + ui_->CAT_poll_interval_spin_box->setValue (rig_params_.CAT_poll_interval_); + + if (rig_params_.PTT_port_.isEmpty ()) + { + if (ui_->PTT_port_combo_box->count ()) + { + ui_->PTT_port_combo_box->setCurrentText (ui_->PTT_port_combo_box->itemText (0)); + } + } + else + { + ui_->PTT_port_combo_box->setCurrentText (rig_params_.PTT_port_); + } + + next_macros_.setStringList (macros_.stringList ()); + next_frequencies_ = frequencies_.frequencies (); + next_stations_ = stations_.stations (); + + set_rig_invariants (); +} + +void Configuration::impl::done (int r) +{ + // do this here since window is still on screen at this point + SettingsGroup g {settings_, "Configuration"}; + settings_->setValue ("window/size", size ()); + settings_->setValue ("window/pos", pos ()); + + QDialog::done (r); +} + +void Configuration::impl::read_settings () +{ + SettingsGroup g {settings_, "Configuration"}; + + resize (settings_->value ("window/size", size ()).toSize ()); + move (settings_->value ("window/pos", pos ()).toPoint ()); + + my_callsign_ = settings_->value ("MyCall", "").toString (); + my_grid_ = settings_->value ("MyGrid", "").toString (); + + if (next_font_.fromString (settings_->value ("Font", QGuiApplication::font ().toString ()).toString ()) + && next_font_ != QGuiApplication::font ()) + { + font_ = next_font_; + QApplication::setFont (font_); + } + + if (next_decoded_text_font_.fromString (settings_->value ("DecodedTextFont", "Courier, 10").toString ()) + && decoded_text_font_ != next_decoded_text_font_) + { + decoded_text_font_ = next_decoded_text_font_; + Q_EMIT self_->decoded_text_font_changed (decoded_text_font_); + } + + id_interval_ = settings_->value ("IDint", 0).toInt (); + + save_directory_ = settings_->value ("SaveDir", default_save_directory_.absolutePath ()).toString (); + + { + // + // retrieve audio input device + // + auto saved_name = settings_->value ("SoundInName").toString (); + + // deal with special Windows default audio devices + auto default_device = QAudioDeviceInfo::defaultInputDevice (); + if (saved_name == default_device.deviceName ()) + { + audio_input_device_ = default_device; + default_audio_input_device_selected_ = true; + } + else + { + default_audio_input_device_selected_ = false; + Q_FOREACH (auto const& p, QAudioDeviceInfo::availableDevices (QAudio::AudioInput)) // available audio input devices + { + if (p.deviceName () == saved_name) + { + audio_input_device_ = p; + } + } + } + } + + { + // + // retrieve audio output device + // + auto saved_name = settings_->value("SoundOutName").toString(); + + // deal with special Windows default audio devices + auto default_device = QAudioDeviceInfo::defaultOutputDevice (); + if (saved_name == default_device.deviceName ()) + { + audio_output_device_ = default_device; + default_audio_output_device_selected_ = true; + } + else + { + default_audio_output_device_selected_ = false; + Q_FOREACH (auto const& p, QAudioDeviceInfo::availableDevices (QAudio::AudioOutput)) // available audio output devices + { + if (p.deviceName () == saved_name) + { + audio_output_device_ = p; + } + } + } + } + + // retrieve audio channel info + audio_input_channel_ = AudioDevice::fromString (settings_->value ("AudioInputChannel", "Mono").toString ()); + audio_output_channel_ = AudioDevice::fromString (settings_->value ("AudioOutputChannel", "Mono").toString ()); + + jt9w_bw_mult_ = settings_->value ("ToneMult", 1).toUInt (); + jt9w_min_dt_ = settings_->value ("DTmin", -2.5).toFloat (); + jt9w_max_dt_ = settings_->value ("DTmax", 5.).toFloat (); + + monitor_off_at_startup_ = settings_->value ("MonitorOFF", false).toBool (); + spot_to_psk_reporter_ = settings_->value ("PSKReporter", false).toBool (); + id_after_73_ = settings_->value ("After73", false).toBool (); + + macros_.setStringList (settings_->value ("Macros", QStringList {"TNX 73 GL"}).toStringList ()); + + if (settings_->contains ("frequencies")) + { + frequencies_ = settings_->value ("frequencies").value (); + } + + stations_ = settings_->value ("stations").value (); + + log_as_RTTY_ = settings_->value ("toRTTY", false).toBool (); + report_in_comments_ = settings_->value("dBtoComments", false).toBool (); + rig_params_.rig_name_ = settings_->value ("Rig", TransceiverFactory::basic_transceiver_name_).toString (); + rig_params_.CAT_network_port_ = settings_->value ("CATNetworkPort").toString (); + rig_params_.CAT_serial_port_ = settings_->value ("CATSerialPort").toString (); + rig_params_.CAT_baudrate_ = settings_->value ("CATSerialRate", 4800).toInt (); + rig_params_.CAT_data_bits_ = settings_->value ("CATDataBits", QVariant::fromValue (TransceiverFactory::eight_data_bits)).value (); + rig_params_.CAT_stop_bits_ = settings_->value ("CATStopBits", QVariant::fromValue (TransceiverFactory::two_stop_bits)).value (); + rig_params_.CAT_handshake_ = settings_->value ("CATHandshake", QVariant::fromValue (TransceiverFactory::handshake_none)).value (); + rig_params_.CAT_DTR_always_on_ = settings_->value ("DTR", false).toBool (); + rig_params_.CAT_RTS_always_on_ = settings_->value ("RTS", false).toBool (); + rig_params_.PTT_method_ = settings_->value ("PTTMethod", QVariant::fromValue (TransceiverFactory::PTT_method_VOX)).value (); + rig_params_.TX_audio_source_ = settings_->value ("TXAudioSource", QVariant::fromValue (TransceiverFactory::TX_audio_source_front)).value (); + rig_params_.PTT_port_ = settings_->value ("PTTport").toString (); + data_mode_ = settings_->value ("DataMode", QVariant::fromValue (data_mode_none)).value (); + prompt_to_log_ = settings_->value ("PromptToLog", false).toBool (); + insert_blank_ = settings_->value ("InsertBlank", false).toBool (); + DXCC_ = settings_->value ("DXCCEntity", false).toBool (); + clear_DX_ = settings_->value ("ClearCallGrid", false).toBool (); + miles_ = settings_->value ("Miles", false).toBool (); + quick_call_ = settings_->value ("QuickCall", false).toBool (); + disable_TX_on_73_ = settings_->value ("73TxDisable", false).toBool (); + watchdog_ = settings_->value ("Runaway", false).toBool (); + TX_messages_ = settings_->value ("Tx2QSO", false).toBool (); + rig_params_.CAT_poll_interval_ = settings_->value ("Polling", 0).toInt (); + rig_params_.split_mode_ = settings_->value ("SplitMode", QVariant::fromValue (TransceiverFactory::split_mode_none)).value (); +} + +void Configuration::impl::write_settings () +{ + SettingsGroup g {settings_, "Configuration"}; + + settings_->setValue ("MyCall", my_callsign_); + settings_->setValue ("MyGrid", my_grid_); + + settings_->setValue ("Font", font_.toString ()); + settings_->setValue ("DecodedTextFont", decoded_text_font_.toString ()); + + settings_->setValue ("IDint", id_interval_); + + settings_->setValue ("PTTMethod", QVariant::fromValue (rig_params_.PTT_method_)); + settings_->setValue ("PTTport", rig_params_.PTT_port_); + settings_->setValue ("SaveDir", save_directory_.absolutePath ()); + + if (default_audio_input_device_selected_) + { + settings_->setValue ("SoundInName", QAudioDeviceInfo::defaultInputDevice ().deviceName ()); + } + else + { + settings_->setValue ("SoundInName", audio_input_device_.deviceName ()); + } + + if (default_audio_output_device_selected_) + { + settings_->setValue ("SoundOutName", QAudioDeviceInfo::defaultOutputDevice ().deviceName ()); + } + else + { + settings_->setValue ("SoundOutName", audio_output_device_.deviceName ()); + } + + settings_->setValue ("AudioInputChannel", AudioDevice::toString (audio_input_channel_)); + settings_->setValue ("AudioOutputChannel", AudioDevice::toString (audio_output_channel_)); + settings_->setValue ("ToneMult", jt9w_bw_mult_); + settings_->setValue ("DTmin", jt9w_min_dt_); + settings_->setValue ("DTmax", jt9w_max_dt_); + settings_->setValue ("MonitorOFF", monitor_off_at_startup_); + settings_->setValue ("PSKReporter", spot_to_psk_reporter_); + settings_->setValue ("After73", id_after_73_); + settings_->setValue ("Macros", macros_.stringList ()); + settings_->setValue ("frequencies", QVariant::fromValue (frequencies_.frequencies ())); + settings_->setValue ("stations", QVariant::fromValue (stations_.stations ())); + settings_->setValue ("toRTTY", log_as_RTTY_); + settings_->setValue ("dBtoComments", report_in_comments_); + settings_->setValue ("Rig", rig_params_.rig_name_); + settings_->setValue ("CATNetworkPort", rig_params_.CAT_network_port_); + settings_->setValue ("CATSerialPort", rig_params_.CAT_serial_port_); + settings_->setValue ("CATSerialRate", rig_params_.CAT_baudrate_); + settings_->setValue ("CATDataBits", QVariant::fromValue (rig_params_.CAT_data_bits_)); + settings_->setValue ("CATStopBits", QVariant::fromValue (rig_params_.CAT_stop_bits_)); + settings_->setValue ("CATHandshake", QVariant::fromValue (rig_params_.CAT_handshake_)); + settings_->setValue ("DataMode", QVariant::fromValue (data_mode_)); + settings_->setValue ("PromptToLog", prompt_to_log_); + settings_->setValue ("InsertBlank", insert_blank_); + settings_->setValue ("DXCCEntity", DXCC_); + settings_->setValue ("ClearCallGrid", clear_DX_); + settings_->setValue ("Miles", miles_); + settings_->setValue ("QuickCall", quick_call_); + settings_->setValue ("73TxDisable", disable_TX_on_73_); + settings_->setValue ("Runaway", watchdog_); + settings_->setValue ("Tx2QSO", TX_messages_); + settings_->setValue ("DTR", rig_params_.CAT_DTR_always_on_); + settings_->setValue ("RTS", rig_params_.CAT_RTS_always_on_); + settings_->setValue ("pttData", TransceiverFactory::TX_audio_source_rear == rig_params_.TX_audio_source_); + settings_->setValue ("Polling", rig_params_.CAT_poll_interval_); + settings_->setValue ("SplitMode", QVariant::fromValue (rig_params_.split_mode_)); +} + +void Configuration::impl::set_rig_invariants () +{ + auto const& rig = ui_->rig_combo_box->currentText (); + auto const& ptt_port = ui_->PTT_port_combo_box->currentText (); + auto ptt_method = static_cast (ui_->PTT_method_button_group->checkedId ()); + + auto CAT_PTT_enabled = transceiver_factory_.has_CAT_PTT (rig); + auto CAT_indirect_serial_PTT = transceiver_factory_.has_CAT_indirect_serial_PTT (rig); + auto asynchronous_CAT = transceiver_factory_.has_asynchronous_CAT (rig); + + ui_->test_CAT_push_button->setStyleSheet ({}); + + ui_->CAT_poll_interval_label->setEnabled (!asynchronous_CAT); + ui_->CAT_poll_interval_spin_box->setEnabled (!asynchronous_CAT); + + static TransceiverFactory::Capabilities::PortType last_port_type = TransceiverFactory::Capabilities::none; + auto port_type = transceiver_factory_.CAT_port_type (rig); + + bool is_serial_CAT (TransceiverFactory::Capabilities::serial == port_type); + + if (port_type != last_port_type) + { + last_port_type = port_type; + + switch (port_type) + { + case TransceiverFactory::Capabilities::serial: + fill_port_combo_box (ui_->CAT_port_combo_box); + if (ui_->CAT_port_combo_box->currentText ().isEmpty ()) + { + if (ui_->CAT_port_combo_box->count ()) + { + ui_->CAT_port_combo_box->setCurrentText (ui_->CAT_port_combo_box->itemText (0)); + } + } + else + { + ui_->CAT_port_combo_box->setCurrentText (rig_params_.CAT_serial_port_); + } + + ui_->CAT_control_group_box->setEnabled (true); + ui_->CAT_port_label->setText (tr ("Serial Port:")); + ui_->CAT_port_combo_box->setToolTip (tr ("Serial port used for CAT control")); + break; + + case TransceiverFactory::Capabilities::network: + ui_->CAT_port_combo_box->clear (); + if (!rig_params_.CAT_network_port_.isEmpty ()) + { + ui_->CAT_port_combo_box->setCurrentText (rig_params_.CAT_network_port_); + } + + ui_->CAT_control_group_box->setEnabled (true); + ui_->CAT_port_label->setText (tr ("Network Server:")); + ui_->CAT_port_combo_box->setToolTip (tr ("Optional hostname and port of network service\n" + "You can usually leave this blank\n" + "for a sensible default on this machine\n" + "formats:\n" + "\thostname:port\n" + "\tIPv4-address:port\n" + "\t[IPv6-address]:port")); + break; + + default: + ui_->CAT_port_combo_box->clear (); + ui_->CAT_control_group_box->setEnabled (false); + break; + } + } + + auto const& cat_port = ui_->CAT_port_combo_box->currentText (); + + ui_->CAT_serial_port_parameters_group_box->setEnabled (is_serial_CAT); + + auto is_hw_handshake = TransceiverFactory::handshake_hardware == static_cast (ui_->CAT_handshake_button_group->checkedId ()); + ui_->CAT_RTS_check_box->setEnabled (is_serial_CAT && !is_hw_handshake); + + ui_->TX_audio_source_group_box->setEnabled (transceiver_factory_.has_CAT_PTT_mic_data (rig) && TransceiverFactory::PTT_method_CAT == ptt_method); + + // if (ui_->test_PTT_push_button->isEnabled ()) // don't enable if disabled - "Test CAT" must succeed first + // { + // ui_->test_PTT_push_button->setEnabled ((TransceiverFactory::PTT_method_CAT == ptt_method && CAT_PTT_enabled) + // || TransceiverFactory::PTT_method_DTR == ptt_method + // || TransceiverFactory::PTT_method_RTS == ptt_method); + // } + ui_->test_PTT_push_button->setEnabled (false); + + // only enable CAT option if transceiver has CAT PTT + ui_->PTT_CAT_radio_button->setEnabled (CAT_PTT_enabled); + + auto enable_ptt_port = TransceiverFactory::PTT_method_CAT != ptt_method && TransceiverFactory::PTT_method_VOX != ptt_method; + ui_->PTT_port_combo_box->setEnabled (enable_ptt_port); + ui_->PTT_port_label->setEnabled (enable_ptt_port); + + ui_->PTT_port_combo_box->setItemData (ui_->PTT_port_combo_box->findText ("CAT") + , CAT_indirect_serial_PTT ? combo_box_item_enabled : combo_box_item_disabled + , Qt::UserRole - 1); + + ui_->PTT_DTR_radio_button->setEnabled (!(ui_->CAT_DTR_check_box->isChecked () + && ((is_serial_CAT && ptt_port == cat_port) + || ("CAT" == ptt_port && !CAT_indirect_serial_PTT)))); + + ui_->PTT_RTS_radio_button->setEnabled (!((ui_->CAT_RTS_check_box->isChecked () || is_hw_handshake) + && ((ptt_port == cat_port && is_serial_CAT) + || ("CAT" == ptt_port && !CAT_indirect_serial_PTT)))); +} + +bool Configuration::impl::validate () +{ + if (ui_->sound_input_combo_box->currentIndex () < 0 + && !QAudioDeviceInfo::availableDevices (QAudio::AudioInput).empty ()) + { + message_box (tr ("Invalid audio input device")); + return false; + } + + if (ui_->sound_output_combo_box->currentIndex () < 0 + && !QAudioDeviceInfo::availableDevices (QAudio::AudioOutput).empty ()) + { + message_box (tr ("Invalid audio output device")); + return false; + } + + if (!ui_->PTT_method_button_group->checkedButton ()->isEnabled ()) + { + message_box (tr ("Invalid PTT method")); + return false; + } + + // auto CAT_port = ui_->CAT_port_combo_box->currentText (); + // if (ui_->CAT_port_combo_box->isEnabled () && CAT_port.isEmpty ()) + // { + // message_box (tr ("Invalid CAT port")); + // return false; + // } + + auto ptt_method = static_cast (ui_->PTT_method_button_group->checkedId ()); + auto ptt_port = ui_->PTT_port_combo_box->currentText (); + if ((TransceiverFactory::PTT_method_DTR == ptt_method || TransceiverFactory::PTT_method_RTS == ptt_method) + && (ptt_port.isEmpty () + || combo_box_item_disabled == ui_->PTT_port_combo_box->itemData (ui_->PTT_port_combo_box->findText (ptt_port), Qt::UserRole - 1))) + { + message_box (tr ("Invalid PTT port")); + return false; + } + + return true; +} + +int Configuration::impl::exec () +{ + ptt_state_ = false; + have_rig_ = rig_active_; // record that we started with a rig open + + saved_rig_params_ = rig_params_; // used to detect changes that + // require the Transceiver to be + // re-opened + rig_changed_ = false; + + return QDialog::exec(); +} + +void Configuration::impl::accept () +{ + // Called when OK button is clicked. + + if (!validate ()) + { + return; // not accepting + } + + // extract all rig related configuration parameters into temporary + // structure for checking if the rig needs re-opening without + // actually updating our live state + RigParams temp_rig_params; + temp_rig_params.rig_name_ = ui_->rig_combo_box->currentText (); + + switch (transceiver_factory_.CAT_port_type (temp_rig_params.rig_name_)) + { + case TransceiverFactory::Capabilities::serial: + temp_rig_params.CAT_serial_port_ = ui_->CAT_port_combo_box->currentText (); + break; + + case TransceiverFactory::Capabilities::network: + temp_rig_params.CAT_network_port_ = ui_->CAT_port_combo_box->currentText (); + break; + + default: + break; + } + + temp_rig_params.CAT_baudrate_ = ui_->CAT_serial_baud_combo_box->currentText ().toInt (); + temp_rig_params.CAT_data_bits_ = static_cast (ui_->CAT_data_bits_button_group->checkedId ()); + temp_rig_params.CAT_stop_bits_ = static_cast (ui_->CAT_stop_bits_button_group->checkedId ()); + temp_rig_params.CAT_handshake_ = static_cast (ui_->CAT_handshake_button_group->checkedId ()); + temp_rig_params.CAT_DTR_always_on_ = ui_->CAT_DTR_check_box->isChecked (); + temp_rig_params.CAT_RTS_always_on_ = ui_->CAT_RTS_check_box->isChecked (); + temp_rig_params.CAT_poll_interval_ = ui_->CAT_poll_interval_spin_box->value (); + temp_rig_params.PTT_method_ = static_cast (ui_->PTT_method_button_group->checkedId ()); + temp_rig_params.PTT_port_ = ui_->PTT_port_combo_box->currentText (); + temp_rig_params.TX_audio_source_ = static_cast (ui_->TX_audio_source_button_group->checkedId ()); + temp_rig_params.split_mode_ = static_cast (ui_->split_mode_button_group->checkedId ()); + + // open_rig() uses values from models so we use it to validate the + // Transceiver settings before agreeing to accept the configuration + if (temp_rig_params != rig_params_ && !open_rig ()) + { + return; // not accepting + } + sync_transceiver (true); // force an update + + // + // from here on we are bound to accept the new configuration + // parameters so extract values from models and make them live + // + + if (font_changed_) + { + font_changed_ = false; + font_ = next_font_; + QApplication::setFont (font_); + } + + if (decoded_text_font_changed_) + { + decoded_text_font_changed_ = false; + decoded_text_font_ = next_decoded_text_font_; + Q_EMIT self_->decoded_text_font_changed (decoded_text_font_); + } + + rig_params_ = temp_rig_params; // now we can go live with the rig + // related configuration parameters + + // Check to see whether SoundInThread must be restarted, + // and save user parameters. + { + auto const& device_name = ui_->sound_input_combo_box->currentText (); + if (device_name != audio_input_device_.deviceName ()) + { + auto const& default_device = QAudioDeviceInfo::defaultInputDevice (); + if (device_name == default_device.deviceName ()) + { + audio_input_device_ = default_device; + } + else + { + bool found {false}; + Q_FOREACH (auto const& d, QAudioDeviceInfo::availableDevices (QAudio::AudioInput)) + { + if (device_name == d.deviceName ()) + { + audio_input_device_ = d; + found = true; + } + } + if (!found) + { + audio_input_device_ = default_device; + } + } + restart_sound_input_device_ = true; + } + } + + { + auto const& device_name = ui_->sound_output_combo_box->currentText (); + if (device_name != audio_output_device_.deviceName ()) + { + auto const& default_device = QAudioDeviceInfo::defaultOutputDevice (); + if (device_name == default_device.deviceName ()) + { + audio_output_device_ = default_device; + } + else + { + bool found {false}; + Q_FOREACH (auto const& d, QAudioDeviceInfo::availableDevices (QAudio::AudioOutput)) + { + if (device_name == d.deviceName ()) + { + audio_output_device_ = d; + found = true; + } + } + if (!found) + { + audio_output_device_ = default_device; + } + } + restart_sound_output_device_ = true; + } + } + + if (audio_input_channel_ != static_cast (ui_->sound_input_channel_combo_box->currentIndex ())) + { + audio_input_channel_ = static_cast (ui_->sound_input_channel_combo_box->currentIndex ()); + restart_sound_input_device_ = true; + } + Q_ASSERT (audio_input_channel_ <= AudioDevice::Right); + + if (audio_output_channel_ != static_cast (ui_->sound_output_channel_combo_box->currentIndex ())) + { + audio_output_channel_ = static_cast (ui_->sound_output_channel_combo_box->currentIndex ()); + restart_sound_output_device_ = true; + } + Q_ASSERT (audio_output_channel_ <= AudioDevice::Both); + + my_callsign_ = ui_->callsign_line_edit->text (); + my_grid_ = ui_->grid_line_edit->text (); + spot_to_psk_reporter_ = ui_->psk_reporter_check_box->isChecked (); + id_interval_ = ui_->CW_id_interval_spin_box->value (); + id_after_73_ = ui_->CW_id_after_73_check_box->isChecked (); + monitor_off_at_startup_ = ui_->monitor_off_check_box->isChecked (); + jt9w_bw_mult_ = ui_->jt9w_bandwidth_mult_combo_box->currentText ().toUInt (); + jt9w_min_dt_ = static_cast (ui_->jt9w_min_dt_double_spin_box->value ()); + jt9w_max_dt_ = static_cast (ui_->jt9w_max_dt_double_spin_box->value ()); + log_as_RTTY_ = ui_->log_as_RTTY_check_box->isChecked (); + report_in_comments_ = ui_->report_in_comments_check_box->isChecked (); + prompt_to_log_ = ui_->prompt_to_log_check_box->isChecked (); + insert_blank_ = ui_->insert_blank_check_box->isChecked (); + DXCC_ = ui_->DXCC_check_box->isChecked (); + clear_DX_ = ui_->clear_DX_check_box->isChecked (); + miles_ = ui_->miles_check_box->isChecked (); + quick_call_ = ui_->quick_call_check_box->isChecked (); + disable_TX_on_73_ = ui_->disable_TX_on_73_check_box->isChecked (); + watchdog_ = ui_->watchdog_check_box->isChecked (); + TX_messages_ = ui_->TX_messages_check_box->isChecked (); + data_mode_ = static_cast (ui_->TX_mode_button_group->checkedId ()); + save_directory_ = ui_->save_path_display_label->text (); + macros_.setStringList (next_macros_.stringList ()); + frequencies_ = next_frequencies_.frequencies (); + frequencies_.sort (0); + stations_ = next_stations_.stations (); + stations_.sort (0); + + write_settings (); // make visible to all + + QDialog::accept(); +} + +void Configuration::impl::reject () +{ + initialise_models (); // reverts to settings as at exec () + + // check if the Transceiver instance changed, in which case we need + // to re open any prior Transceiver type + if (rig_changed_) + { + if (have_rig_) + { + // we have to do this since the rig has been opened since we + // were exec'ed even though it might fail + open_rig (); + } + else + { + close_rig (); + } + } + + QDialog::reject (); +} + +void Configuration::impl::message_box (QString const& reason, QString const& detail) +{ + QMessageBox mb; + mb.setText (reason); + if (!detail.isEmpty ()) + { + mb.setDetailedText (detail); + } + mb.setStandardButtons (QMessageBox::Ok); + mb.setDefaultButton (QMessageBox::Ok); + mb.setIcon (QMessageBox::Critical); + mb.exec (); +} + +void Configuration::impl::on_font_push_button_clicked () +{ + next_font_ = QFontDialog::getFont (&font_changed_, this); +} + +void Configuration::impl::on_decoded_text_font_push_button_clicked () +{ + next_decoded_text_font_ = QFontDialog::getFont (&decoded_text_font_changed_ + , decoded_text_font_ + , this + , tr ("WSJT-X Decoded Text Font Chooser") +#if QT_VERSION >= 0x050201 + , QFontDialog::MonospacedFonts +#endif + ); +} + +void Configuration::impl::on_PTT_port_combo_box_activated (int /* index */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_port_combo_box_activated (int /* index */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_serial_baud_combo_box_currentIndexChanged (int /* index */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_handshake_button_group_buttonClicked (int /* id */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_rig_combo_box_currentIndexChanged (int /* index */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_data_bits_button_group_buttonClicked (int /* id */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_stop_bits_button_group_buttonClicked (int /* id */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_poll_interval_spin_box_valueChanged (int /* value */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_split_mode_button_group_buttonClicked (int /* id */) +{ + setup_split_ = true; +} + +void Configuration::impl::on_test_CAT_push_button_clicked () +{ + if (!validate ()) + { + return; + } + + ui_->test_CAT_push_button->setStyleSheet ({}); + if (open_rig ()) + { + Q_EMIT sync (true); + } + + set_rig_invariants (); +} + +void Configuration::impl::on_test_PTT_push_button_clicked () +{ + if (!validate ()) + { + return; + } + + Q_EMIT self_->transceiver_ptt ((ptt_state_ = !ptt_state_)); + + ui_->test_PTT_push_button->setStyleSheet (ptt_state_ ? "QPushButton{background-color: red;" + "border-style: outset; border-width: 1px; border-radius: 5px;" + "border-color: black; min-width: 5em; padding: 3px;}" + : ""); +} + +void Configuration::impl::on_CAT_DTR_check_box_toggled (bool /* checked */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_CAT_RTS_check_box_toggled (bool /* checked */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_PTT_method_button_group_buttonClicked (int /* id */) +{ + set_rig_invariants (); +} + +void Configuration::impl::on_callsign_line_edit_editingFinished () +{ + ui_->callsign_line_edit->setText (ui_->callsign_line_edit->text ().toUpper ()); +} + +void Configuration::impl::on_grid_line_edit_editingFinished () +{ + auto text = ui_->grid_line_edit->text (); + ui_->grid_line_edit->setText (text.left (4).toUpper () + text.mid (4).toLower ()); +} + +void Configuration::impl::on_sound_input_combo_box_currentTextChanged (QString const& text) +{ + default_audio_input_device_selected_ = QAudioDeviceInfo::defaultInputDevice ().deviceName () == text; +} + +void Configuration::impl::on_sound_output_combo_box_currentTextChanged (QString const& text) +{ + default_audio_output_device_selected_ = QAudioDeviceInfo::defaultOutputDevice ().deviceName () == text; +} + +void Configuration::impl::on_add_macro_line_edit_editingFinished () +{ + ui_->add_macro_line_edit->setText (ui_->add_macro_line_edit->text ().toUpper ()); +} + +void Configuration::impl::on_delete_macro_push_button_clicked (bool /* checked */) +{ + auto index = ui_->macros_list_view->selectionModel ()->currentIndex (); + if (index.isValid ()) + { + next_macros_.removeRow (index.row ()); + } +} + +void Configuration::impl::delete_macro () +{ + auto index = ui_->macros_list_view->currentIndex (); + if (index.isValid ()) + { + next_macros_.removeRow (index.row ()); + } +} + +void Configuration::impl::on_add_macro_push_button_clicked (bool /* checked */) +{ + if (next_macros_.insertRow (next_macros_.rowCount ())) + { + auto index = next_macros_.index (next_macros_.rowCount () - 1); + ui_->macros_list_view->setCurrentIndex (index); + next_macros_.setData (index, ui_->add_macro_line_edit->text ()); + ui_->add_macro_line_edit->clear (); + } +} + +void Configuration::impl::delete_frequencies () +{ + auto selection_model = ui_->frequencies_table_view->selectionModel (); + selection_model->select (selection_model->selection (), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + next_frequencies_.removeDisjointRows (selection_model->selectedRows ()); +} + +void Configuration::impl::insert_frequency () +{ + if (QDialog::Accepted == frequency_dialog_->exec ()) + { + ui_->frequencies_table_view->setCurrentIndex (next_frequencies_.add (frequency_dialog_->frequency ())); + } +} + +void Configuration::impl::delete_stations () +{ + auto selection_model = ui_->stations_table_view->selectionModel (); + selection_model->select (selection_model->selection (), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + next_stations_.removeDisjointRows (selection_model->selectedRows ()); +} + +void Configuration::impl::insert_station () +{ + if (QDialog::Accepted == station_dialog_->exec ()) + { + ui_->stations_table_view->setCurrentIndex (next_stations_.add (station_dialog_->station ())); + } +} + +void Configuration::impl::on_save_path_select_push_button_clicked (bool /* checked */) +{ + QFileDialog fd {this, tr ("Save Directory"), ui_->save_path_display_label->text ()}; + fd.setFileMode (QFileDialog::Directory); + fd.setOption (QFileDialog::ShowDirsOnly); + if (fd.exec ()) + { + if (fd.selectedFiles ().size ()) + { + ui_->save_path_display_label->setText (fd.selectedFiles ().at (0)); + } + } +} + +bool Configuration::impl::have_rig (bool open_if_closed) +{ + if (open_if_closed && !rig_active_ && !open_rig ()) + { + QMessageBox::critical (this, "WSJT-X", tr ("Failed to open connection to rig")); + } + return rig_active_; +} + +bool Configuration::impl::open_rig () +{ + auto result = false; + + try + { + close_rig (); + + // create a new Transceiver object + auto rig = transceiver_factory_.create (ui_->rig_combo_box->currentText () + , ui_->CAT_port_combo_box->currentText () + , ui_->CAT_serial_baud_combo_box->currentText ().toInt () + , static_cast (ui_->CAT_data_bits_button_group->checkedId ()) + , static_cast (ui_->CAT_stop_bits_button_group->checkedId ()) + , static_cast (ui_->CAT_handshake_button_group->checkedId ()) + , ui_->CAT_DTR_check_box->isChecked () + , ui_->CAT_RTS_check_box->isChecked () + , static_cast (ui_->PTT_method_button_group->checkedId ()) + , static_cast (ui_->TX_audio_source_button_group->checkedId ()) + , static_cast (ui_->split_mode_button_group->checkedId ()) + , ui_->PTT_port_combo_box->currentText () + , ui_->CAT_poll_interval_spin_box->value () * 1000 + , &transceiver_thread_); + + // hook up Configuration transceiver control signals to Transceiver slots + // + // these connections cross the thread boundary + connect (this, &Configuration::impl::frequency, rig.get (), &Transceiver::frequency); + connect (this, &Configuration::impl::tx_frequency, rig.get (), &Transceiver::tx_frequency); + connect (this, &Configuration::impl::mode, rig.get (), &Transceiver::mode); + connect (this, &Configuration::impl::ptt, rig.get (), &Transceiver::ptt); + connect (this, &Configuration::impl::sync, rig.get (), &Transceiver::sync); + + // hook up Transceiver signals to Configuration signals + // + // these connections cross the thread boundary + connect (rig.get (), &Transceiver::update, this, &Configuration::impl::handle_transceiver_update); + connect (rig.get (), &Transceiver::failure, this, &Configuration::impl::handle_transceiver_failure); + + // setup thread safe startup and close down semantics + connect (this, &Configuration::impl::stop_transceiver, rig.get (), &Transceiver::stop); + + auto p = rig.release (); // take ownership + // schedule eventual destruction + // + // must be queued connection to avoid premature self-immolation + // since finished signal is going to be emitted from the object + // that will get destroyed in its own stop slot i.e. a same + // thread signal to slot connection which by default will be + // reduced to a method function call. + connect (p, &Transceiver::finished, p, &Transceiver::deleteLater, Qt::QueuedConnection); + + ui_->test_CAT_push_button->setStyleSheet ({}); + rig_active_ = true; + QTimer::singleShot (0, p, SLOT (start ())); // start rig on its thread + result = true; + } + catch (std::exception const& e) + { + handle_transceiver_failure (e.what ()); + } + + rig_changed_ = true; + return result; +} + +void Configuration::impl::transceiver_frequency (Frequency f) +{ +#if WSJT_TRACE_CAT + qDebug () << "Configuration::transceiver_frequency:" << f; +#endif + + if (set_mode () || cached_rig_state_.frequency () != f) + { + cached_rig_state_.frequency (f); + + // lookup offset + transceiver_offset_ = stations_.offset (f); + Q_EMIT frequency (f + transceiver_offset_); + } +} + +bool Configuration::impl::set_mode () +{ + // Some rigs change frequency when switching between some modes so + // we need to check if we change mode and not elide the frequency + // setting in the same as the cached frequency. + bool mode_changed {false}; + + auto data_mode = static_cast (ui_->TX_mode_button_group->checkedId ()); + + // Set mode if we are responsible for it. + if (data_mode_USB == data_mode && cached_rig_state_.mode () != Transceiver::USB) + { + if (Transceiver::USB != cached_rig_state_.mode ()) + { + cached_rig_state_.mode (Transceiver::USB); + Q_EMIT mode (Transceiver::USB, cached_rig_state_.split () && data_mode_none != data_mode_); + mode_changed = true; + } + } + if (data_mode_data == data_mode && cached_rig_state_.mode () != Transceiver::DIG_U) + { + if (Transceiver::DIG_U != cached_rig_state_.mode ()) + { + cached_rig_state_.mode (Transceiver::DIG_U); + Q_EMIT mode (Transceiver::DIG_U, cached_rig_state_.split () && data_mode_none != data_mode_); + mode_changed = true; + } + } + + return mode_changed; +} + +void Configuration::impl::transceiver_tx_frequency (Frequency f) +{ + if (set_mode () || cached_rig_state_.tx_frequency () != f) + { + cached_rig_state_.tx_frequency (f); + cached_rig_state_.split (f); + + // lookup offset if we are in split mode + if (f) + { + transceiver_offset_ = stations_.offset (f); + } + + // Rationalise TX VFO mode if we ask for split and are + // responsible for mode. + Q_EMIT tx_frequency (f, cached_rig_state_.split () && data_mode_none != data_mode_); + } +} + +void Configuration::impl::transceiver_mode (MODE m) +{ + if (cached_rig_state_.mode () != m) + { + cached_rig_state_.mode (m); + + // Rationalise mode if we are responsible for it and in split mode. + Q_EMIT mode (m, cached_rig_state_.split () && data_mode_none != data_mode_); + } +} + +void Configuration::impl::transceiver_ptt (bool on) +{ + set_mode (); + + cached_rig_state_.ptt (on); + + // pass this on regardless of cache + Q_EMIT ptt (on); +} + +void Configuration::impl::sync_transceiver (bool force_signal) +{ + // pass this on as cache must be ignored + Q_EMIT sync (force_signal); +} + +void Configuration::impl::handle_transceiver_update (TransceiverState state) +{ +#if WSJT_TRACE_CAT + qDebug () << "Configuration::handle_transceiver_update: Transceiver State:" << state; +#endif + + if (state.online ()) + { + TransceiverFactory::SplitMode split_mode_selected; + if (isVisible ()) + { + ui_->test_CAT_push_button->setStyleSheet ("QPushButton {background-color: green;}"); + + auto const& rig = ui_->rig_combo_box->currentText (); + auto ptt_method = static_cast (ui_->PTT_method_button_group->checkedId ()); + auto CAT_PTT_enabled = transceiver_factory_.has_CAT_PTT (rig); + ui_->test_PTT_push_button->setEnabled ((TransceiverFactory::PTT_method_CAT == ptt_method && CAT_PTT_enabled) + || TransceiverFactory::PTT_method_DTR == ptt_method + || TransceiverFactory::PTT_method_RTS == ptt_method); + + set_mode (); + + // Follow the setup choice. + split_mode_selected = static_cast (ui_->split_mode_button_group->checkedId ()); + } + else + { + // Follow the rig unless configuration has been changed. + split_mode_selected = static_cast (rig_params_.split_mode_); + + if (enforce_mode_and_split_) + { + if ((TransceiverFactory::split_mode_none != split_mode_selected) != state.split ()) + { + if (!setup_split_) + { + // Rig split mode isn't consistent with settings so + // change settings. + // + // For rigs that can't report split mode changes + // (e.g.Icom) this is going to confuse operators, but + // what can we do if they change the rig? + // auto split_mode = state.split () ? TransceiverFactory::split_mode_rig : TransceiverFactory::split_mode_none; + // rig_params_.split_mode_ = split_mode; + // ui_->split_mode_button_group->button (split_mode)->setChecked (true); + // split_mode_selected = split_mode; + setup_split_ = true; + + // Q_EMIT self_->transceiver_failure (tr ("Rig split mode setting not consistent with WSJT-X settings. Changing WSJT-X settings for you.")); + Q_EMIT self_->transceiver_failure (tr ("Rig split mode setting not consistent with WSJT-X settings.")); + } + } + + set_mode (); + } + } + + // One time rig setup split + if (setup_split_ && cached_rig_state_.split () != state.split ()) + { + Q_EMIT tx_frequency (TransceiverFactory::split_mode_none != split_mode_selected ? state.tx_frequency () : 0, true); + } + setup_split_ = false; + + // if (TransceiverFactory::split_mode_emulate == split_mode_selected) + // { + // state.split (true); // complete the illusion + // } + } + else + { + close_rig (); + } + + cached_rig_state_ = state; + + // take off offset + cached_rig_state_.frequency (cached_rig_state_.frequency () - transceiver_offset_); + if (cached_rig_state_.tx_frequency ()) + { + cached_rig_state_.tx_frequency (cached_rig_state_.tx_frequency () - transceiver_offset_); + } + + // pass on to clients + Q_EMIT self_->transceiver_update (cached_rig_state_); +} + +void Configuration::impl::handle_transceiver_failure (QString reason) +{ +#if WSJT_TRACE_CAT + qDebug () << "Configuration::handle_transceiver_failure: reason:" << reason; +#endif + + close_rig (); + + if (isVisible ()) + { + message_box (tr ("Rig failure"), reason); + } + else + { + // pass on if our dialog isn't active + Q_EMIT self_->transceiver_failure (reason); + } +} + +void Configuration::impl::close_rig () +{ + ui_->test_PTT_push_button->setStyleSheet ({}); + ui_->test_PTT_push_button->setEnabled (false); + ptt_state_ = false; + + // revert to no rig configured + if (rig_active_) + { + ui_->test_CAT_push_button->setStyleSheet ("QPushButton {background-color: red;}"); + Q_EMIT stop_transceiver (); + rig_active_ = false; + } +} + +// load the available audio devices into the selection combo box and +// select the default device if the current device isn't set or isn't +// available +bool Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * combo_box, QAudioDeviceInfo * device) +{ + using std::copy; + using std::back_inserter; + + bool result {false}; + + combo_box->clear (); + + int current_index = -1; + int default_index = -1; + + int extra_items {0}; + + auto const& default_device = (mode == QAudio::AudioInput ? QAudioDeviceInfo::defaultInputDevice () : QAudioDeviceInfo::defaultOutputDevice ()); + + // deal with special default audio devices on Windows + if ("Default Input Device" == default_device.deviceName () + || "Default Output Device" == default_device.deviceName ()) + { + default_index = 0; + + QList channel_counts; + auto scc = default_device.supportedChannelCounts (); + copy (scc.cbegin (), scc.cend (), back_inserter (channel_counts)); + + combo_box->addItem (default_device.deviceName (), channel_counts); + ++extra_items; + if (default_device == *device) + { + current_index = 0; + result = true; + } + } + + Q_FOREACH (auto const& p, QAudioDeviceInfo::availableDevices (mode)) + { + // convert supported channel counts into something we can store in the item model + QList channel_counts; + auto scc = p.supportedChannelCounts (); + copy (scc.cbegin (), scc.cend (), back_inserter (channel_counts)); + + combo_box->addItem (p.deviceName (), channel_counts); + if (p == *device) + { + current_index = combo_box->count () - 1; + } + else if (p == default_device) + { + default_index = combo_box->count () - 1; + } + } + if (current_index < 0) // not found - use default + { + *device = default_device; + result = true; + current_index = default_index; + } + combo_box->setCurrentIndex (current_index); + + return result; +} + +// enable only the channels that are supported by the selected audio device +void Configuration::impl::update_audio_channels (QComboBox const * source_combo_box, int index, QComboBox * combo_box, bool allow_both) +{ + // disable all items + for (int i (0); i < combo_box->count (); ++i) + { + combo_box->setItemData (i, combo_box_item_disabled, Qt::UserRole - 1); + } + + Q_FOREACH (QVariant const& v, source_combo_box->itemData (index).toList ()) + { + // enable valid options + int n {v.toInt ()}; + if (2 == n) + { + combo_box->setItemData (AudioDevice::Left, combo_box_item_enabled, Qt::UserRole - 1); + combo_box->setItemData (AudioDevice::Right, combo_box_item_enabled, Qt::UserRole - 1); + if (allow_both) + { + combo_box->setItemData (AudioDevice::Both, combo_box_item_enabled, Qt::UserRole - 1); + } + } + else if (1 == n) + { + combo_box->setItemData (AudioDevice::Mono, combo_box_item_enabled, Qt::UserRole - 1); + } + } +} + +// load all the supported rig names into the selection combo box +void Configuration::impl::enumerate_rigs () +{ + ui_->rig_combo_box->clear (); + + auto rigs = transceiver_factory_.supported_transceivers (); + + for (auto r = rigs.cbegin (); r != rigs.cend (); ++r) + { + if ("None" == r.key ()) + { + // put None first + ui_->rig_combo_box->insertItem (0, r.key (), r.value ().model_number_); + } + else + { + ui_->rig_combo_box->addItem (r.key (), r.value ().model_number_); + } + } + + ui_->rig_combo_box->setCurrentText (rig_params_.rig_name_); +} + +void Configuration::impl::fill_port_combo_box (QComboBox * cb) +{ + auto current_text = cb->currentText (); + + cb->clear (); + +#ifdef WIN32 + + for (int i {1}; i < 100; ++i) + { + auto item = "COM" + QString::number (i); + cb->addItem (item); + } + cb->addItem("USB"); + +#else + + QStringList ports = { + "/dev/ttyS0" + , "/dev/ttyS1" + , "/dev/ttyS2" + , "/dev/ttyS3" + , "/dev/ttyS4" + , "/dev/ttyS5" + , "/dev/ttyS6" + , "/dev/ttyS7" + , "/dev/ttyUSB0" + , "/dev/ttyUSB1" + , "/dev/ttyUSB2" + , "/dev/ttyUSB3" + }; + cb->addItems (ports); + +#endif + + cb->setEditText (current_text); +} + + +inline +bool operator != (RigParams const& lhs, RigParams const& rhs) +{ + return + lhs.CAT_serial_port_ != rhs.CAT_serial_port_ + || lhs.CAT_network_port_ != rhs.CAT_network_port_ + || lhs.CAT_baudrate_ != rhs.CAT_baudrate_ + || lhs.CAT_data_bits_ != rhs.CAT_data_bits_ + || lhs.CAT_stop_bits_ != rhs.CAT_stop_bits_ + || lhs.CAT_handshake_ != rhs.CAT_handshake_ + || lhs.CAT_DTR_always_on_ != rhs.CAT_DTR_always_on_ + || lhs.CAT_RTS_always_on_ != rhs.CAT_RTS_always_on_ + || lhs.CAT_poll_interval_ != rhs.CAT_poll_interval_ + || lhs.PTT_method_ != rhs.PTT_method_ + || lhs.PTT_port_ != rhs.PTT_port_ + || lhs.TX_audio_source_ != rhs.TX_audio_source_ + || lhs.split_mode_ != rhs.split_mode_ + || lhs.rig_name_ != rhs.rig_name_; +} + + +#if !defined (QT_NO_DEBUG_STREAM) +ENUM_QDEBUG_OPS_IMPL (Configuration, DataMode); +#endif + +ENUM_QDATASTREAM_OPS_IMPL (Configuration, DataMode); + +ENUM_CONVERSION_OPS_IMPL (Configuration, DataMode); diff --git a/Configuration.hpp b/Configuration.hpp new file mode 100644 index 000000000..caff847d8 --- /dev/null +++ b/Configuration.hpp @@ -0,0 +1,188 @@ +#ifndef CONFIGURATION_HPP_ +#define CONFIGURATION_HPP_ + +#include + +#include "Radio.hpp" +#include "AudioDevice.hpp" +#include "Transceiver.hpp" + +#include "pimpl_h.hpp" + +class QSettings; +class QWidget; +class QAudioDeviceInfo; +class QString; +class QDir; +class QFont; +class Bands; +class FrequencyList; +class StationList; +class QStringListModel; + +// +// Class Configuration +// +// Encapsulates the control, access and, persistence of user defined +// settings for the wsjtx GUI. Setting values are accessed through a +// QDialog window containing concept orientated tab windows. +// +// Responsibilities +// +// Provides management of the CAT and PTT rig interfaces, providing +// control access via a minimal generic set of Qt slots and status +// updates via Qt signals. Internally the rig control capability is +// farmed out to a separate thread since many of the rig control +// functions are blocking. +// +// All user settings required by the wsjtx GUI are exposed through +// query methods. +// +// The QSettings instance passed to the constructor is used to read +// and write user settings. +// +// Pointers to three QAbstractItemModel objects are provided to give +// access to amateur band information, user working frequencies and, +// user operating band information. These porovide consistent data +// models that can be used in GUI lists or tables or simply queried +// for user defined bands, default operating frequencies and, station +// descriptions. +// +class Configuration final + : public QObject +{ + Q_OBJECT; + Q_ENUMS (DataMode); + +public: + using MODE = Transceiver::MODE; + using TransceiverState = Transceiver::TransceiverState; + using Frequency = Radio::Frequency; + + enum DataMode {data_mode_none, data_mode_USB, data_mode_data}; + + explicit Configuration (QString const& instance_key, QSettings * settings, QWidget * parent = nullptr); + ~Configuration (); + + int exec (); + + QDir data_path () const; + + QAudioDeviceInfo const& audio_input_device () const; + AudioDevice::Channel audio_input_channel () const; + QAudioDeviceInfo const& audio_output_device () const; + AudioDevice::Channel audio_output_channel () const; + + // These query methods should be used after a call to exec() to + // determine if either the audio input or audio output stream + // parameters have changed. The respective streams should be + // re-opened if they return true. + bool restart_audio_input () const; + bool restart_audio_output () const; + + QString my_callsign () const; + QString my_grid () const; + QFont decoded_text_font () const; + qint32 id_interval () const; + bool id_after_73 () const; + bool spot_to_psk_reporter () const; + bool monitor_off_at_startup () const; + bool log_as_RTTY () const; + bool report_in_comments () const; + bool prompt_to_log () const; + bool insert_blank () const; + bool DXCC () const; + bool clear_DX () const; + bool miles () const; + bool quick_call () const; + bool disable_TX_on_73 () const; + bool watchdog () const; + bool TX_messages () const; + bool split_mode () const; + Bands * bands (); + FrequencyList * frequencies (); + StationList * stations (); + QStringListModel * macros (); + QDir save_directory () const; + QString rig_name () const; + unsigned jt9w_bw_mult () const; + float jt9w_min_dt () const; + float jt9w_max_dt () const; + + // This method queries if a CAT and PTT connection is operational, + // + // It also doubles as an initialisation method when the + // open_if_closed parameter is passed as true. + bool transceiver_online (bool open_if_closed = false); + + // Close down connection to rig. + void transceiver_offline (); + + // Set transceiver frequency in Hertz. + Q_SLOT void transceiver_frequency (Frequency); + + // Setting a non zero TX frequency means split operation + // rationalise_mode means ensure TX uses same mode as RX. + Q_SLOT void transceiver_tx_frequency (Frequency = 0u); + + // Set transceiver mode. + // + // Rationalise means ensure TX uses same mode as RX. + Q_SLOT void transceiver_mode (MODE); + + // Set/unset PTT. + // + // Note that this must be called even if VOX PTT is selected since + // the "Emulate Split" mode requires PTT information to coordinate + // frequency changes. + Q_SLOT void transceiver_ptt (bool = true); + + // Attempt to (re-)synchronise transceiver state. + // + // Force signal guarantees either a transceiver_update or a + // transceiver_failure signal. + // + // The enforce_mode_and_split parameter ensures that future + // transceiver updates have the correct mode and split setting + // i.e. the transceiver is ready for use. + Q_SLOT void sync_transceiver (bool force_signal = false, bool enforce_mode_and_split = false); + + + // + // This signal indicates that a font has been selected and accepted + // for the decoded text. + // + Q_SIGNAL void decoded_text_font_changed (QFont); + + + // + // These signals are emitted and reflect transceiver state changes + // + + // signals a change in one of the TransceiverState members + Q_SIGNAL void transceiver_update (Transceiver::TransceiverState) const; + + // Signals a failure of a control rig CAT or PTT connection. + // + // A failed rig CAT or PTT connection is fatal and the underlying + // connections are closed automatically. The connections can be + // re-established with a call to transceiver_online(true) assuming + // the fault condition has been rectified or is transient. + Q_SIGNAL void transceiver_failure (QString reason) const; + +private: + class impl; + pimpl m_; +}; + +Q_DECLARE_METATYPE (Configuration::DataMode); + +#if !defined (QT_NO_DEBUG_STREAM) +ENUM_QDEBUG_OPS_DECL (Configuration, DataMode); +#endif + +ENUM_QDATASTREAM_OPS_DECL (Configuration, DataMode); + +ENUM_CONVERSION_OPS_DECL (Configuration, DataMode); + +#endif diff --git a/Configuration.ui b/Configuration.ui new file mode 100644 index 000000000..5c3883a13 --- /dev/null +++ b/Configuration.ui @@ -0,0 +1,1812 @@ + + + configuration_dialog + + + + 0 + 0 + 508 + 471 + + + + Configuration + + + + + + Select tab to change configuration parameters. + + + 0 + + + + Genera&l + + + General station details and settings. + + + + + + Station Details + + + + + + My C&all: + + + callsign_line_edit + + + + + + + Station callsign. + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + M&y Grid: + + + grid_line_edit + + + + + + + Maidenhead locator (only the first four characters are required). + + + + + + + + + + Qt::Horizontal + + + + + + + Display + + + + + + Show if decoded stations are new DXCC entities or worked before. + + + Show &DXCC entity and worked before status + + + false + + + + + + + Include a separator line between periods in the band activity window. + + + &Blank line between decoding periods + + + + + + + + + Set the font characteristics for the application. + + + Font ... + + + + + + + Set the font characteristics for the Band Activity and Rx Frequency areas. + + + Decoded text font ... + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Show distance to DX station in miles rather than kilometers. + + + Display dista&nce in miles + + + + + + + Show outgoing transmitted messages in the Rx frequency window. + + + &Tx messages to Rx frequency window + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Horizontal + + + + + + + Behaviour + + + + + + Don't start decoding until the monitor button is clicked. + + + Mon&itor off at startup + + + false + + + + + + + Automatic transmission mode. + + + Doubl&e-click on call sets Tx enable + + + + + + + Turns off automatic transmissions after sending a 73 or any other free +text message. + + + Di&sable Tx after sending 73 + + + + + + + Stop transmitting automatically after five periods. + + + Runaway Tx &watchdog + + + + + + + + + Send a CW ID after every 73 or free text message. + + + CW ID a&fter 73 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Periodic CW ID Inter&val: + + + CW_id_interval_spin_box + + + + + + + + 0 + 0 + + + + Send a CW ID periodically every few minutes. +This might be required under your countries licence regulations. +It will not interfere with other users as it is always sent in the +quiet period when decoding is done. + + + + + + + + + + + + + &Radio + + + Radio interface configuration settings. + + + + + + + 0 + 0 + + + + Settings that control your CAT interface. + + + CAT Control + + + + + + + + Port: + + + CAT_port_combo_box + + + + + + + Serial port used for CAT control. + + + true + + + + + + QComboBox::NoInsert + + + + + + + + + Serial Port Parameters + + + + + + + + Baud Rate: + + + CAT_serial_baud_combo_box + + + + + + + Serial port data rate which must match the setting of your radio. + + + 2 + + + + 1200 + + + + + 2400 + + + + + 4800 + + + + + 9600 + + + + + 19200 + + + + + 38400 + + + + + 57600 + + + + + + + + + + Number of data bits used to communicate with your radios CAT interface (nealy always 8). + + + Data Bits + + + + + + Se&ven + + + CAT_data_bits_button_group + + + + + + + E&ight + + + true + + + CAT_data_bits_button_group + + + + + + + + + + Number of stop bits used when communicating with your radios CAT interface +(consult you radio manual for details). + + + Stop Bits + + + + + + On&e + + + CAT_stop_bits_button_group + + + + + + + T&wo + + + true + + + CAT_stop_bits_button_group + + + + + + + + + + Flow control protocol used between this computer and your radios CAT interface (usually "None" but some require "Hardware"). + + + Handshake + + + + + + &None + + + true + + + CAT_handshake_button_group + + + + + + + Software flow control (very rare on CAT interfaces). + + + XON/XOFF + + + CAT_handshake_button_group + + + + + + + Flow control using the RTS and CTS RS-232 control lines +not often used but some radios have it as an option and +a few, particularly some Kenwood rigs, require it). + + + &Hardware + + + CAT_handshake_button_group + + + + + + + + + + Special control of CAT port control lines. + + + Force Control Lines + + + + + + Force the DTR control line high on the CAT serial port. +This is required for a few CAT interfaces that are powered +from the DTR control line. +Normally you should leave this unchecked. + + + DTR + + + + + + + Force the RTS control line high on the CAT serial port. +This is required for a few CAT interfaces that are powered +from the RTS control line. +Normally you should leave this unchecked. +This option is only available if Hardware handshaking is not +selected above. + + + RTS + + + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + + + + How this program activates the PTT on your radio + + + PTT Method + + + + + + No PTT activation, instead the radios automatic VOX is used to key the transmitter. +Use this if you have no radio interface hardware. + + + VO&X + + + PTT_method_button_group + + + + + + + Use the RS-232 DTR control line to toggle your radios PTT, +requires hardware to inteface the line. +Some commercial interface units also use this method. +The DTR control line of the CAT serial port may be used for this or +a DTR control line on a different serial port may be used. + + + &DTR + + + true + + + PTT_method_button_group + + + + + + + Some radios support PTT via CAT commands, +use this option if your radio supports it and you have no +other hardware interface for PTT. + + + C&AT + + + PTT_method_button_group + + + + + + + Use the RS-232 RTS control line to toggle your radios PTT, +requires hardware to inteface the line. +Some commercial interface units also use this method. +The RTS control line of the CAT serial port may be used for this or +a RTS control line on a different serial port may be used. +Note that this options is not available on the CAT serial port +when hardware flow control is used. + + + R&TS + + + PTT_method_button_group + + + + + + + + + Port: + + + PTT_port_combo_box + + + + + + + + 1 + 0 + + + + Select the RS-232 serial port utilised for PTT control, +this option is available when DTR or RTS is selected above +as a transmit method. +This port can be the same one as the one used for CAT control. +For some interface types the special value CAT may be choosen, +this is used for non-serial CAT interfaces that can control +serial port control lines remotely (OmniRig for example). + + + true + + + + + + -1 + + + QComboBox::NoInsert + + + + + + + + + + + + Modulation mode selected on radio. + + + Mode + + + + + + USB is the usually the correct modulation mode, +unless the radio has a special data or packet mode setting +for AFSK operation. + + + US&B + + + true + + + TX_mode_button_group + + + + + + + Don't allow the program to set the radio mode +(not recommended but use if the wrong mode +or bandwidth is selected). + + + None + + + TX_mode_button_group + + + + + + + If this is availabe then it is usually the correct mode for this program. + + + Data/P&kt + + + TX_mode_button_group + + + + + + + + + + Some radios can select the audio input using a CAT command, +this setting allows you to select which audio input will be used +(if it is available then generally the Rear/Data option is best). + + + Transmit Audio Source + + + + + + Rear&/Data + + + TX_audio_source_button_group + + + + + + + &Front/Mic + + + true + + + TX_audio_source_button_group + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Rig: + + + rig_combo_box + + + + + + + + 1 + 0 + + + + + + + + Poll Interval: + + + CAT_poll_interval_spin_box + + + + + + + 1 + + + + + + + + + Qt::Horizontal + + + + + + + + + Attempt to connect to the radio with these settings. +Another dialog will pop up where you can verify correct operation. + + + Test CAT + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 40 + 20 + + + + + + + + Attempt to activate the transmitter. +Click again to deactivate. Normally no power should be +output since there is no audio being generated at this time. +Check that any Tx indication on your radio and/or your +radio interface behave as expected. + + + Test PTT + + + + + + + + + Split Operation + + + + + + None + + + true + + + split_mode_button_group + + + + + + + Rig + + + split_mode_button_group + + + + + + + Fake It + + + split_mode_button_group + + + + + + + + + + + A&udio + + + Audio interface settings + + + + + + Save Directory + + + + + + Loc&ation: + + + save_path_select_push_button + + + + + + + + 1 + 0 + + + + Path to which .WAV files are saved. + + + false + + + background-color: rgb(255, 255, 255); + + + TextLabel + + + + + + + Click to select a different save directory for .WAV files. + + + S&elect + + + + + + + + + + Soundcard + + + + + + + 1 + 0 + + + + Select the audio CODEC to use for transmitting. +If this is your default device for system sounds then +ensure that all system sounds are disabled otherwise +you will broadcast any systems sounds generated during +transmitting periods. + + + + + + + + 1 + 0 + + + + Select the audio CODEC to use for receiving. + + + + + + + &Input: + + + sound_input_combo_box + + + + + + + Select the channel to use for receiving. + + + + Mono + + + + + Left + + + + + Right + + + + + Both + + + + + + + + Select the audio channel used for transmission. +Unless you have multiple radios connected on different +channels; then you will usually want to select mono or +both here. + + + + Mono + + + + + Left + + + + + Right + + + + + Both + + + + + + + + Ou&tput: + + + sound_output_combo_box + + + + + + + + + + JT9W Settings + + + + + + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + 16 + + + + + 32 + + + + + 64 + + + + + 128 + + + + + + + + Bandwidth Multiplier: + + + jt9w_bandwidth_mult_combo_box + + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + DT Max: + + + jt9w_max_dt_double_spin_box + + + + + + + DT Min: + + + jt9w_min_dt_double_spin_box + + + + + + + s + + + 1 + + + -2.500000000000000 + + + 5.000000000000000 + + + 0.100000000000000 + + + -2.500000000000000 + + + + + + + s + + + 1 + + + -2.500000000000000 + + + 5.000000000000000 + + + 0.100000000000000 + + + 5.000000000000000 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + + + + + Tx &Macros + + + Canned free text messages setup + + + + + + &Add + + + + + + + 13 + + + + + + + &Delete + + + + + + + Qt::ActionsContextMenu + + + QListView { + show-decoration-selected: 1; /* make the selection span the entire width of the view */ +} + +QListView::item:alternate { + background: #EEEEEE; +} + +QListView::item:selected { + border: 1px solid #6a6ea9; +} + +QListView::item:selected:!active { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #ABAFE5, stop: 1 #8588B2); +} + +QListView::item:selected:active { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #6a6ea9, stop: 1 #888dd9); +} + +QListView::item:hover { + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #FAFBFE, stop: 1 #DCDEF1); +} + + + QAbstractItemView::SingleSelection + + + true + + + + + + + + Reportin&g + + + Reporting and logging settings + + + + + + Logging + + + + + + The program will pop up a partially completed Log QSO dialog when you send a 73 or free text message. + + + Promp&t me to log QSO + + + + + + + Some logging programs will not accept JT-65 or JT9 as a recognized mode. + + + Con&vert mode to RTTY + + + + + + + Some logging programs will not accept the type of reports +saved by this program. +Check this option to save the sent and received reports in the +comments field. + + + d&B reports to comments + + + + + + + Check this option to force the clearing of the DX Call +and DX Grid fields when a 73 or free text message is sent. + + + Clear &DX call and grid after logging + + + + + + + + + + Qt::Horizontal + + + + + + + Network + + + + + + The program can send your station details and all +decoded signals as spots to the http://pskreporter.info web site. +This is used for reverse beacon analysis which is very useful +for assessing propagation and system performance. + + + Enable &PSK Reporter Spotting + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Frequencies + + + Default frequencies and band specific station details setup + + + + + + Working Frequencies + + + + + + Qt::ActionsContextMenu + + + Right click to add or delete frequencies. + + + QAbstractItemView::DragOnly + + + true + + + QAbstractItemView::SelectRows + + + true + + + true + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Station Information + + + + + + Qt::ActionsContextMenu + + + true + + + Items may be edited. +Right click for insert and delete options. + + + true + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + true + + + true + + + false + + + + + + + + + + + + + + Discard or apply configuration changes including +resetting the radio interface and applying any +soundcard changes + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + configuration_tabs + callsign_line_edit + grid_line_edit + insert_blank_check_box + miles_check_box + TX_messages_check_box + DXCC_check_box + font_push_button + decoded_text_font_push_button + monitor_off_check_box + quick_call_check_box + disable_TX_on_73_check_box + watchdog_check_box + CW_id_after_73_check_box + CW_id_interval_spin_box + rig_combo_box + CAT_poll_interval_spin_box + CAT_port_combo_box + CAT_serial_baud_combo_box + CAT_7_bit_radio_button + CAT_8_bit_radio_button + CAT_one_stop_bit_radio_button + CAT_two_stop_bit_radio_button + CAT_handshake_none_radio_button + CAT_handshake_xon_radio_button + CAT_handshake_hardware_radio_button + CAT_DTR_check_box + CAT_RTS_check_box + PTT_VOX_radio_button + PTT_DTR_radio_button + PTT_CAT_radio_button + PTT_RTS_radio_button + PTT_port_combo_box + TX_source_data_radio_button + TX_source_mic_radio_button + mode_none_radio_button + mode_USB_radio_button + mode_data_radio_button + split_none_radio_button + split_rig_radio_button + split_emulate_radio_button + test_CAT_push_button + test_PTT_push_button + sound_input_combo_box + sound_input_channel_combo_box + sound_output_combo_box + sound_output_channel_combo_box + save_path_select_push_button + jt9w_bandwidth_mult_combo_box + jt9w_min_dt_double_spin_box + jt9w_max_dt_double_spin_box + add_macro_line_edit + add_macro_push_button + delete_macro_push_button + macros_list_view + prompt_to_log_check_box + log_as_RTTY_check_box + report_in_comments_check_box + clear_DX_check_box + psk_reporter_check_box + frequencies_table_view + stations_table_view + configuration_dialog_button_box + + + + + configuration_dialog_button_box + accepted() + configuration_dialog + accept() + + + 236 + 540 + + + 157 + 274 + + + + + configuration_dialog_button_box + rejected() + configuration_dialog + reject() + + + 304 + 540 + + + 286 + 274 + + + + + add_macro_line_edit + returnPressed() + add_macro_push_button + setFocus() + + + 188 + 62 + + + 406 + 62 + + + + + add_macro_push_button + clicked() + add_macro_line_edit + setFocus() + + + 406 + 62 + + + 188 + 62 + + + + + + + + + + + + + + diff --git a/Copyright.txt b/Copyright.txt new file mode 100644 index 000000000..09cf8de7d --- /dev/null +++ b/Copyright.txt @@ -0,0 +1 @@ +Copyright (C) 2001-2014 by Joe Taylor, K1JT diff --git a/DXLabSuiteCommanderTransceiver.cpp b/DXLabSuiteCommanderTransceiver.cpp new file mode 100644 index 000000000..d118074fd --- /dev/null +++ b/DXLabSuiteCommanderTransceiver.cpp @@ -0,0 +1,377 @@ +#include "DXLabSuiteCommanderTransceiver.hpp" + +#include +#include + +#include "NetworkServerLookup.hpp" + +namespace +{ + char const * const commander_transceiver_name {"DX Lab Suite Commander"}; + int socket_wait_time {5000}; + + QString map_mode (Transceiver::MODE mode) + { + switch (mode) + { + case Transceiver::AM: return "AM"; + case Transceiver::CW: return "CW"; + case Transceiver::CW_R: return "CW-R"; + case Transceiver::USB: return "USB"; + case Transceiver::LSB: return "LSB"; + case Transceiver::FSK: return "RTTY"; + case Transceiver::FSK_R: return "RTTY-R"; + case Transceiver::DIG_L: return "DATA-L"; + case Transceiver::DIG_U: return "DATA-U"; + case Transceiver::FM: + case Transceiver::DIG_FM: + return "FM"; + default: break; + } + return "USB"; + } +} + +void DXLabSuiteCommanderTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id) +{ + (*registry)[commander_transceiver_name] = TransceiverFactory::Capabilities {id, TransceiverFactory::Capabilities::network, true}; +} + +DXLabSuiteCommanderTransceiver::DXLabSuiteCommanderTransceiver (std::unique_ptr wrapped, QString const& address, bool use_for_ptt, int poll_interval) + : PollingTransceiver {poll_interval} + , wrapped_ {std::move (wrapped)} + , use_for_ptt_ {use_for_ptt} + , server_ {address} + , commander_ {nullptr} +{ +} + +DXLabSuiteCommanderTransceiver::~DXLabSuiteCommanderTransceiver () +{ +} + +void DXLabSuiteCommanderTransceiver::do_start () +{ +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::start"; +#endif + + wrapped_->start (); + + auto server_details = network_server_lookup (server_, 52002u, QHostAddress::LocalHost, QAbstractSocket::IPv4Protocol); + + if (!commander_) + { + commander_ = new QTcpSocket {this}; // QObject takes ownership + } + + commander_->connectToHost (std::get<0> (server_details), std::get<1> (server_details)); + if (!commander_->waitForConnected (socket_wait_time)) + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::start failed to connect" << commander_->errorString (); +#endif + + throw error {"Failed to connect to DX Lab Suite Commander\n" + commander_->errorString ().toLocal8Bit ()}; + } + + poll (); +} + +void DXLabSuiteCommanderTransceiver::do_stop () +{ + if (commander_) + { + commander_->close (); + delete commander_, commander_ = nullptr; + } + + wrapped_->stop (); + +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::stop"; +#endif +} + +void DXLabSuiteCommanderTransceiver::do_ptt (bool on) +{ +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::do_ptt:" << on << state (); +#endif + + if (use_for_ptt_) + { + send_command (on ? "CmdTX" : "CmdRX"); + } + else + { + wrapped_->ptt (on); + } + + update_PTT (on); +} + +void DXLabSuiteCommanderTransceiver::do_frequency (Frequency f) +{ +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::do_frequency:" << f << state (); +#endif + + // number is localised + // avoid floating point translation errors by adding a small number (0.1Hz) + send_command ("CmdSetFreq" + QString ("%L1").arg (f / 1e3 + 1e-4, 10, 'f', 3).toLocal8Bit ()); + update_rx_frequency (f); +} + +void DXLabSuiteCommanderTransceiver::do_tx_frequency (Frequency tx, bool /* rationalise_mode */) +{ +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::do_tx_frequency:" << tx << state (); +#endif + + if (tx) + { + send_command ("CmdSplit<1:2>on"); + update_split (true); + + // number is localised + // avoid floating point translation errors by adding a small number (0.1Hz) + + // set TX frequency after going split because going split + // rationalises TX VFO mode and that can change the frequency on + // Yaesu rigs if CW is involved + send_command ("CmdSetTxFreq" + QString ("%L1").arg (tx / 1e3 + 1e-4, 10, 'f', 3).toLocal8Bit ()); + } + else + { + send_command ("CmdSplit<1:3>off"); + } + update_other_frequency (tx); +} + +void DXLabSuiteCommanderTransceiver::do_mode (MODE mode, bool /* rationalise */) +{ +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::do_mode:" << mode << state (); +#endif + + auto mapped = map_mode (mode); + send_command ((QString ("CmdSetMode<1:%2>").arg (5 + mapped.size ()).arg (mapped.size ()) + mapped).toLocal8Bit ()); + + if (state ().split ()) + { + // this toggle ensures that the TX VFO mode is the same as the RX VFO + send_command ("CmdSplit<1:3>off"); + send_command ("CmdSplit<1:2>on"); + } + + // setting TX frequency rationalises the mode on Icoms so get current and set + poll (); +} + +void DXLabSuiteCommanderTransceiver::poll () +{ +#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS + bool quiet {false}; +#else + bool quiet {true}; +#endif + + send_command ("CmdSendFreq", quiet); + auto reply = read_reply (quiet); + if (0 == reply.indexOf ("') + 1).replace (",", "").replace (".", ""); + update_rx_frequency (reply.toUInt ()); + } + else + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::poll: get frequency unexpected response"; +#endif + + throw error {"DX Lab Suite Commander didn't respond correctly polling frequency"}; + } + + send_command ("CmdSendTXFreq", quiet); + reply = read_reply (quiet); + if (0 == reply.indexOf ("') + 1).replace (",", "").replace (".", ""); + if ("000" != text) + { + update_other_frequency (text.toUInt ()); + } + } + else + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::poll: get tx frequency unexpected response"; +#endif + + throw error {"DX Lab Suite Commander didn't respond correctly polling TX frequency"}; + } + + send_command ("CmdSendSplit", quiet); + reply = read_reply (quiet); + if (0 == reply.indexOf ("') + 1); + if ("ON" == split) + { + update_split (true); + } + else if ("OFF" == split) + { + update_split (false); + } + else + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::poll: unexpected split state" << split; +#endif + + throw error {"DX Lab Suite Commander sent an unrecognised split state: " + split}; + } + } + else + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::poll: get split mode unexpected response"; +#endif + + throw error {"DX Lab Suite Commander didn't respond correctly polling split status"}; + } + + send_command ("CmdSendMode", quiet); + reply = read_reply (quiet); + if (0 == reply.indexOf ("') + 1); + MODE m {UNK}; + if ("AM" == mode) + { + m = AM; + } + else if ("CW" == mode) + { + m = CW; + } + else if ("CW-R" == mode) + { + m = CW_R; + } + else if ("FM" == mode || "WBFM" == mode) + { + m = FM; + } + else if ("LSB" == mode) + { + m = LSB; + } + else if ("USB" == mode) + { + m = USB; + } + else if ("RTTY" == mode) + { + m = FSK; + } + else if ("RTTY-R" == mode) + { + m = FSK_R; + } + else if ("PKT" == mode || "DATA-L" == mode || "Data-L" == mode) + { + m = DIG_L; + } + else if ("PKT-R" == mode || "DATA-U" == mode || "Data-U" == mode) + { + m = DIG_U; + } + else + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::poll: unexpected mode name" << mode; +#endif + + throw error {"DX Lab Suite Commander sent an unrecognised mode: " + mode}; + } + update_mode (m); + } + else + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::poll: unexpected response"; +#endif + + throw error {"DX Lab Suite Commander didn't respond correctly polling mode"}; + } +} + +void DXLabSuiteCommanderTransceiver::send_command (QByteArray const& cmd, bool no_debug) +{ + Q_ASSERT (commander_); + + if (!no_debug) + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver:send_command(" << cmd << ')'; +#endif + } + + if (QTcpSocket::ConnectedState != commander_->state ()) + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::send_command failed:" << commander_->errorString (); +#endif + + throw error {"DX Lab Suite Commander send command failed\n" + commander_->errorString ().toLocal8Bit ()}; + } + + commander_->write (cmd); + if (!commander_->waitForBytesWritten (socket_wait_time)) + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::send_command failed:" << commander_->errorString (); +#endif + + throw error {"DX Lab Suite Commander send command failed\n" + commander_->errorString ().toLocal8Bit ()}; + } +} + +QByteArray DXLabSuiteCommanderTransceiver::read_reply (bool no_debug) +{ + Q_ASSERT (commander_); + + if (QTcpSocket::ConnectedState != commander_->state ()) + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::read_reply failed:" << commander_->errorString (); +#endif + + throw error {"DX Lab Suite Commander read reply failed\n" + commander_->errorString ().toLocal8Bit ()}; + } + + if (!commander_->waitForReadyRead (socket_wait_time)) + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver::read_reply failed:" << commander_->errorString (); +#endif + + throw error {"DX Lab Suite Commander read reply failed\n" + commander_->errorString ().toLocal8Bit ()}; + } + + auto result = commander_->readAll (); + + if (!no_debug) + { +#if WSJT_TRACE_CAT + qDebug () << "DXLabSuiteCommanderTransceiver:read_reply() ->" << result; +#endif + } + + return result; +} diff --git a/DXLabSuiteCommanderTransceiver.hpp b/DXLabSuiteCommanderTransceiver.hpp new file mode 100644 index 000000000..05dfab74a --- /dev/null +++ b/DXLabSuiteCommanderTransceiver.hpp @@ -0,0 +1,50 @@ +#ifndef DX_LAB_SUITE_COMMANDER_TRANSCEIVER_HPP__ +#define DX_LAB_SUITE_COMMANDER_TRANSCEIVER_HPP__ + +#include + +#include + +#include "TransceiverFactory.hpp" +#include "PollingTransceiver.hpp" + +class QTcpSocket; + +// +// DX Lab Suite Commander Interface +// +// Implemented as a Transceiver decorator because we may want the PTT +// services of another Transceiver type such as the HamlibTransceiver +// which can be enabled by wrapping a HamlibTransceiver instantiated +// as a "Hamlib Dummy" transceiver in the Transceiver factory method. +// +class DXLabSuiteCommanderTransceiver final + : public PollingTransceiver +{ +public: + static void register_transceivers (TransceiverFactory::Transceivers *, int id); + + // takes ownership of wrapped Transceiver + explicit DXLabSuiteCommanderTransceiver (std::unique_ptr wrapped, QString const& address, bool use_for_ptt, int poll_interval); + ~DXLabSuiteCommanderTransceiver (); + +private: + void do_start () override; + void do_stop () override; + void do_frequency (Frequency) override; + void do_tx_frequency (Frequency, bool rationalise_mode) override; + void do_mode (MODE, bool rationalise) override; + void do_ptt (bool on) override; + + void poll () override; + + void send_command (QByteArray const&, bool no_debug = false); + QByteArray read_reply (bool no_debug = false); + + std::unique_ptr wrapped_; + bool use_for_ptt_; + QString server_; + QTcpSocket * commander_; +}; + +#endif diff --git a/Detector.cpp b/Detector.cpp index 7f99a6599..48a251b49 100644 --- a/Detector.cpp +++ b/Detector.cpp @@ -4,6 +4,8 @@ #include #include "commons.h" +#include "moc_Detector.cpp" + extern "C" { void fil4_(qint16*, qint32*, qint16*, qint32*); } diff --git a/Detector.hpp b/Detector.hpp index 6003b7fd8..3004408be 100644 --- a/Detector.hpp +++ b/Detector.hpp @@ -45,7 +45,7 @@ private: Q_SLOT bool reset (); Q_SLOT void close () {AudioDevice::close ();} - Q_SIGNAL void framesWritten (qint64); + Q_SIGNAL void framesWritten (qint64) const; void clear (); // discard buffer contents unsigned secondInPeriod () const; diff --git a/EmulateSplitTransceiver.cpp b/EmulateSplitTransceiver.cpp new file mode 100644 index 000000000..ba0dbc6ee --- /dev/null +++ b/EmulateSplitTransceiver.cpp @@ -0,0 +1,102 @@ +#include "EmulateSplitTransceiver.hpp" + +EmulateSplitTransceiver::EmulateSplitTransceiver (std::unique_ptr wrapped) + : wrapped_ {std::move (wrapped)} + , frequency_ {0, 0} + , tx_ {false} +{ + // Connect update signal of wrapped Transceiver object instance to ours. + connect (wrapped_.get (), &Transceiver::update, this, &EmulateSplitTransceiver::handle_update); + + // Connect failure signal of wrapped Transceiver object to our + // parent failure signal. + connect (wrapped_.get (), &Transceiver::failure, this, &Transceiver::failure); +} + +void EmulateSplitTransceiver::start () noexcept +{ + wrapped_->start (); + wrapped_->tx_frequency (0, false); +} + +void EmulateSplitTransceiver::frequency (Frequency rx) noexcept +{ +#if WSJT_TRACE_CAT + qDebug () << "EmulateSplitTransceiver::frequency:" << rx; +#endif + + // Save frequency parameters. + frequency_[0] = rx; + + // Set active frequency. + wrapped_->frequency (rx); +} + +void EmulateSplitTransceiver::tx_frequency (Frequency tx, bool /* rationalise_mode */) noexcept +{ +#if WSJT_TRACE_CAT + qDebug () << "EmulateSplitTransceiver::tx_frequency:" << tx; +#endif + + // Save frequency parameter. + frequency_[1] = tx; + + // Set active frequency. + wrapped_->frequency (frequency_[(tx_ && frequency_[1]) ? 1 : 0]); +} + +void EmulateSplitTransceiver::ptt (bool on) noexcept +{ +#if WSJT_TRACE_CAT + qDebug () << "EmulateSplitTransceiver::ptt:" << on; +#endif + + // Save TX state for future frequency change requests. + tx_ = on; + + // Switch to other frequency if we have one i.e. client wants split + // operation). + wrapped_->frequency (frequency_[(on && frequency_[1]) ? 1 : 0]); + + // Change TX state. + wrapped_->ptt (on); +} + +void EmulateSplitTransceiver::handle_update (TransceiverState state) +{ +#if WSJT_TRACE_CAT + qDebug () << "EmulateSplitTransceiver::handle_update: from wrapped:" << state; +#endif + + // Change to reflect emulated state, we don't want to report the + // shifted frequency when transmitting. + if (tx_) + { + state.frequency (frequency_[0]); + } + else + { + // Follow the rig if in RX mode. + frequency_[0] = state.frequency (); + } + + // Always report the other frequency as the Tx frequency we will use. + state.tx_frequency (frequency_[1]); + + if (state.split ()) + { + Q_EMIT failure (tr ("Emulated split mode requires rig to in simplex mode")); + } + else + { + // Always emit rigs split state so clients can detect abuse. + state.split (true); + +#if WSJT_TRACE_CAT + qDebug () << "EmulateSplitTransceiver::handle_update: signalling:" << state; +#endif + + // signal emulated state + Q_EMIT update (state); + } +} diff --git a/EmulateSplitTransceiver.hpp b/EmulateSplitTransceiver.hpp new file mode 100644 index 000000000..7f32247e0 --- /dev/null +++ b/EmulateSplitTransceiver.hpp @@ -0,0 +1,52 @@ +#ifndef EMULATE_SPLIT_TRANSCEIVER_HPP__ +#define EMULATE_SPLIT_TRANSCEIVER_HPP__ + +#include + +#include "Transceiver.hpp" + +// +// Emulate Split Transceiver +// +// Helper decorator class that encapsulates the emulation of split TX +// operation. +// +// Responsibilities +// +// Delegates all but setting of other (split) frequency to the +// wrapped Transceiver instance. Also routes failure signals from the +// wrapped Transceiver instance to this instances failure signal. +// +// Intercepts status updates from the wrapped Transceiver instance +// and re-signals it with the emulated status. +// +// Generates a status update signal if the other (split) frequency is +// changed, this is necessary since the wrapped transceiver instance +// never receives other frequency changes. +// +class EmulateSplitTransceiver final + : public Transceiver +{ +public: + // takes ownership of wrapped Transceiver + explicit EmulateSplitTransceiver (std::unique_ptr wrapped); + + void start () noexcept override; + void frequency (Frequency) noexcept override; + void tx_frequency (Frequency, bool rationalise_mode) noexcept override; + void ptt (bool on) noexcept override; + + // forward everything else to wrapped Transceiver + void stop () noexcept override {wrapped_->stop (); Q_EMIT finished ();} + void mode (MODE m, bool /* rationalise */) noexcept override {wrapped_->mode (m, false);} + void sync (bool force_signal) noexcept override {wrapped_->sync (force_signal);} + +private: + void handle_update (TransceiverState); + + std::unique_ptr wrapped_; + Frequency frequency_[2]; // [0] <- RX, [1] <- other + bool tx_; +}; + +#endif diff --git a/ForeignKeyDelegate.cpp b/ForeignKeyDelegate.cpp new file mode 100644 index 000000000..e3c717299 --- /dev/null +++ b/ForeignKeyDelegate.cpp @@ -0,0 +1,77 @@ +#include "ForeignKeyDelegate.hpp" + +#include +#include + +class CandidateKeyFilter final + : public QSortFilterProxyModel +{ +public: + explicit CandidateKeyFilter (QAbstractItemModel const * referencing_model + , QAbstractItemModel * referenced_model + , int referenced_key_column + , int referencing_key_role + , int referenced_key_role) + : QSortFilterProxyModel {nullptr} // ForeignKeyDelegate owns us + , referencing_ {referencing_model} + , referencing_key_role_ {referencing_key_role} + , referenced_key_column_ {referenced_key_column} + , referenced_key_role_ {referenced_key_role} + { + setSourceModel (referenced_model); + } + + void set_active_key (QModelIndex const& index) + { + active_key_ = index; + invalidateFilter (); + } + +protected: + bool filterAcceptsRow (int candidate_row, QModelIndex const& candidate_parent) const override + { + auto candidate_key = sourceModel ()->index (candidate_row, referenced_key_column_, candidate_parent).data (referenced_key_role_); + + // Include the current key. + if (candidate_key == active_key_.data (referencing_key_role_)) + { + return true; + } + + // Filter out any candidates already in the referencing key rows. + return referencing_->match (referencing_->index (0, active_key_.column ()), referencing_key_role_, candidate_key, 1, Qt::MatchExactly).isEmpty (); + } + +private: + QAbstractItemModel const * referencing_; + int referencing_key_role_; + int referenced_key_column_; + int referenced_key_role_; + QModelIndex active_key_; +}; + +ForeignKeyDelegate::ForeignKeyDelegate (QAbstractItemModel const * referencing_model + , QAbstractItemModel * referenced_model + , int referenced_key_column + , QObject * parent + , int referencing_key_role + , int referenced_key_role) + : QStyledItemDelegate {parent} + , candidate_key_filter_ {new CandidateKeyFilter {referencing_model, referenced_model, referenced_key_column, referencing_key_role, referenced_key_role}} +{ +} + +ForeignKeyDelegate::~ForeignKeyDelegate () +{ +} + +QWidget * ForeignKeyDelegate::createEditor (QWidget * parent + , QStyleOptionViewItem const& /* option */ + , QModelIndex const& index) const +{ + auto editor = new QComboBox {parent}; + editor->setFrame (false); + candidate_key_filter_->set_active_key (index); + editor->setModel (candidate_key_filter_.data ()); + return editor; +} diff --git a/ForeignKeyDelegate.hpp b/ForeignKeyDelegate.hpp new file mode 100644 index 000000000..6e6797079 --- /dev/null +++ b/ForeignKeyDelegate.hpp @@ -0,0 +1,34 @@ +#ifndef FOREIGN_KEY_DELEGATE_HPP_ +#define FOREIGN_KEY_DELEGATE_HPP_ + +#include +#include + +class CandidateKeyFilter; + +// +// Class ForeignKeyDelegate +// +// Item delegate for editing a foreign key item in a one or many +// to one relationship. A QComboBox is used as an item delegate +// for the edit role. +// +class ForeignKeyDelegate final + : public QStyledItemDelegate +{ +public: + explicit ForeignKeyDelegate (QAbstractItemModel const * referencing_model + , QAbstractItemModel * referenced_model + , int referenced_key_column = 0 + , QObject * parent = nullptr + , int referencing_key_role = Qt::EditRole + , int referenced_key_role = Qt::EditRole); + ~ForeignKeyDelegate (); + + QWidget * createEditor (QWidget * parent, QStyleOptionViewItem const&, QModelIndex const&) const override; + +private: + QScopedPointer candidate_key_filter_; +}; + +#endif diff --git a/FrequencyItemDelegate.cpp b/FrequencyItemDelegate.cpp new file mode 100644 index 000000000..8e9ebd5dc --- /dev/null +++ b/FrequencyItemDelegate.cpp @@ -0,0 +1,36 @@ +#include "FrequencyItemDelegate.hpp" + +#include "Radio.hpp" +#include "FrequencyLineEdit.hpp" +#include "Bands.hpp" + +QString FrequencyItemDelegate::displayText (QVariant const& value, QLocale const& locale) const +{ + auto frequency = value.value (); + auto band_name = bands_->data (bands_->find (frequency)); + return Radio::pretty_frequency_MHz_string (frequency, locale) + " MHz (" + band_name.toString () + ')'; +} + +QWidget * FrequencyItemDelegate::createEditor (QWidget * parent + , QStyleOptionViewItem const& /* option */ + , QModelIndex const& /* index */) const +{ + auto editor = new FrequencyLineEdit {parent}; + editor->setFrame (false); + return editor; +} + + +QString FrequencyDeltaItemDelegate::displayText (QVariant const& value, QLocale const& locale) const +{ + return Radio::pretty_frequency_MHz_string (value.value (), locale) + " MHz"; +} + +QWidget * FrequencyDeltaItemDelegate::createEditor (QWidget * parent + , QStyleOptionViewItem const& /* option */ + , QModelIndex const& /* index */) const +{ + auto editor = new FrequencyDeltaLineEdit {parent}; + editor->setFrame (false); + return editor; +} diff --git a/FrequencyItemDelegate.hpp b/FrequencyItemDelegate.hpp new file mode 100644 index 000000000..5d35a9a0a --- /dev/null +++ b/FrequencyItemDelegate.hpp @@ -0,0 +1,56 @@ +#ifndef FREQUENCY_ITEM_DELEGATE_HPP_ +#define FREQUENCY_ITEM_DELEGATE_HPP_ + +#include + +class QStyleOptionItemView; +class QWidget; +class QModelIndex; +class Bands; + +// +// Class FrequencyItemDelegate +// +// Item delegate for displaying and editing a Frequency item in a +// view that uses a FrequencyLineEdit as an item delegate for the +// edit role. +// +class FrequencyItemDelegate final + : public QStyledItemDelegate +{ +public: + explicit FrequencyItemDelegate (Bands const * bands, QObject * parent = nullptr) + : QStyledItemDelegate {parent} + , bands_ {bands} + { + } + + QString displayText (QVariant const& value, QLocale const&) const override; + QWidget * createEditor (QWidget * parent, QStyleOptionViewItem const&, QModelIndex const&) const override; + +private: + Bands const * bands_; +}; + + +// +// Class FrequencyDeltaItemDelegate +// +// Item delegate for displaying and editing a FrequencyDelta item +// in a view that uses a FrequencyDeltaLineEdit as an item +// delegate for the edit role. +// +class FrequencyDeltaItemDelegate final + : public QStyledItemDelegate +{ +public: + explicit FrequencyDeltaItemDelegate (QObject * parent = nullptr) + : QStyledItemDelegate {parent} + { + } + + QString displayText (QVariant const& value, QLocale const&) const override; + QWidget * createEditor (QWidget * parent, QStyleOptionViewItem const&, QModelIndex const&) const override; +}; + +#endif diff --git a/FrequencyLineEdit.cpp b/FrequencyLineEdit.cpp new file mode 100644 index 000000000..e37800f8d --- /dev/null +++ b/FrequencyLineEdit.cpp @@ -0,0 +1,40 @@ +#include "FrequencyLineEdit.hpp" + +#include +#include +#include + +#include "moc_FrequencyLineEdit.cpp" + +FrequencyLineEdit::FrequencyLineEdit (QWidget * parent) + : QLineEdit (parent) +{ + setValidator (new QRegExpValidator {QRegExp {R"(\d{0,6}(\.\d{0,6})?)"}, this}); +} + +auto FrequencyLineEdit::frequency () const -> Frequency +{ + return Radio::frequency (text (), 6); +} + +void FrequencyLineEdit::frequency (Frequency f) +{ + setText (Radio::frequency_MHz_string (f)); +} + + +FrequencyDeltaLineEdit::FrequencyDeltaLineEdit (QWidget * parent) + : QLineEdit (parent) +{ + setValidator (new QRegExpValidator {QRegExp {R"(-?\d{0,6}(\.\d{0,6})?)"}, this}); +} + +auto FrequencyDeltaLineEdit::frequency_delta () const -> FrequencyDelta +{ + return Radio::frequency_delta (text (), 6); +} + +void FrequencyDeltaLineEdit::frequency_delta (FrequencyDelta d) +{ + setText (Radio::frequency_MHz_string (d)); +} diff --git a/FrequencyLineEdit.hpp b/FrequencyLineEdit.hpp new file mode 100644 index 000000000..f59a37b07 --- /dev/null +++ b/FrequencyLineEdit.hpp @@ -0,0 +1,45 @@ +#ifndef FREQUENCY_LINE_EDIT_HPP_ +#define FREQUENCY_LINE_EDIT_HPP_ + +#include + +#include "Radio.hpp" + +class QWidget; + +// +// MHz frequency line edits with validation +// +class FrequencyLineEdit final + : public QLineEdit +{ + Q_OBJECT; + Q_PROPERTY (Frequency frequency READ frequency WRITE frequency USER true); + +public: + using Frequency = Radio::Frequency; + + explicit FrequencyLineEdit (QWidget * parent = nullptr); + + // Property frequency implementation + Frequency frequency () const; + void frequency (Frequency); +}; + +class FrequencyDeltaLineEdit final + : public QLineEdit +{ + Q_OBJECT; + Q_PROPERTY (FrequencyDelta frequency_delta READ frequency_delta WRITE frequency_delta USER true); + +public: + using FrequencyDelta = Radio::FrequencyDelta; + + explicit FrequencyDeltaLineEdit (QWidget * parent = nullptr); + + // Property frequency_delta implementation + FrequencyDelta frequency_delta () const; + void frequency_delta (FrequencyDelta); +}; + +#endif diff --git a/FrequencyList.cpp b/FrequencyList.cpp new file mode 100644 index 000000000..b2daf7549 --- /dev/null +++ b/FrequencyList.cpp @@ -0,0 +1,358 @@ +#include "FrequencyList.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pimpl_impl.hpp" + +class FrequencyList::impl final + : public QAbstractTableModel +{ +public: + impl (Frequencies frequencies, QObject * parent) + : QAbstractTableModel {parent} + , frequencies_ {frequencies} + { + } + + Frequencies const& frequencies () const {return frequencies_;} + void assign (Frequencies); + QModelIndex add (Frequency); + +protected: + // Implement the QAbstractTableModel interface + int rowCount (QModelIndex const& parent = QModelIndex {}) const override; + int columnCount (QModelIndex const& parent = QModelIndex {}) const override; + Qt::ItemFlags flags (QModelIndex const& = QModelIndex {}) const override; + QVariant data (QModelIndex const&, int role = Qt::DisplayRole) const override; + bool setData (QModelIndex const&, QVariant const& value, int role = Qt::EditRole) override; + QVariant headerData (int section, Qt::Orientation, int = Qt::DisplayRole) const override; + bool removeRows (int row, int count, QModelIndex const& parent = QModelIndex {}) override; + bool insertRows (int row, int count, QModelIndex const& parent = QModelIndex {}) override; + QStringList mimeTypes () const override; + QMimeData * mimeData (QModelIndexList const&) const override; + +private: + static int constexpr num_cols {2}; + static auto constexpr mime_type ="application/wsjt.Frequencies"; + + Frequencies frequencies_; +}; + +FrequencyList::FrequencyList (QObject * parent) + : FrequencyList {{}, parent} +{ +} + +FrequencyList::FrequencyList (Frequencies frequencies, QObject * parent) + : QSortFilterProxyModel {parent} + , m_ {frequencies, parent} +{ + // setDynamicSortFilter (true); + setSourceModel (&*m_); + setSortRole (SortRole); +} + +FrequencyList::~FrequencyList () +{ +} + +FrequencyList& FrequencyList::operator = (Frequencies frequencies) +{ + m_->assign (frequencies); + return *this; +} + +auto FrequencyList::frequencies () const -> Frequencies +{ + return m_->frequencies (); +} + +QModelIndex FrequencyList::add (Frequency f) +{ + return mapFromSource (m_->add (f)); +} + +bool FrequencyList::remove (Frequency f) +{ + auto row = m_->frequencies ().indexOf (f); + + if (0 > row) + { + return false; + } + + return m_->removeRow (row); +} + +namespace +{ + bool row_is_higher (QModelIndex const& lhs, QModelIndex const& rhs) + { + return lhs.row () > rhs.row (); + } +} + +bool FrequencyList::removeDisjointRows (QModelIndexList rows) +{ + bool result {true}; + + // We must work with source model indexes because we don't want row + // removes to invalidate model indexes we haven't yet processed. We + // achieve that by processing them in decending row order. + for (int r = 0; r < rows.size (); ++r) + { + rows[r] = mapToSource (rows[r]); + } + + // reverse sort by row + qSort (rows.begin (), rows.end (), row_is_higher); + Q_FOREACH (auto index, rows) + { + if (result && !m_->removeRow (index.row ())) + { + result = false; + } + } + + return result; +} + + +void FrequencyList::impl::assign (Frequencies frequencies) +{ + beginResetModel (); + std::swap (frequencies_, frequencies); + endResetModel (); +} + +QModelIndex FrequencyList::impl::add (Frequency f) +{ + // Any Frequency that isn't in the list may be added + if (!frequencies_.contains (f)) + { + auto row = frequencies_.size (); + + beginInsertRows (QModelIndex {}, row, row); + frequencies_.append (f); + endInsertRows (); + + return index (row, 0); + } + + return QModelIndex {}; +} + +int FrequencyList::impl::rowCount (QModelIndex const& parent) const +{ + return parent.isValid () ? 0 : frequencies_.size (); +} + +int FrequencyList::impl::columnCount (QModelIndex const& parent) const +{ + return parent.isValid () ? 0 : num_cols; +} + +Qt::ItemFlags FrequencyList::impl::flags (QModelIndex const& index) const +{ + auto result = QAbstractTableModel::flags (index) | Qt::ItemIsDropEnabled; + + auto row = index.row (); + auto column = index.column (); + + if (index.isValid () + && row < frequencies_.size () + && column < num_cols) + { + switch (column) + { + case 0: + result |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + break; + + case 1: + result |= Qt::ItemIsDragEnabled; + break; + } + } + + return result; +} + +QVariant FrequencyList::impl::data (QModelIndex const& index, int role) const +{ + QVariant item; + + auto row = index.row (); + auto column = index.column (); + + if (index.isValid () + && row < frequencies_.size () + && column < num_cols) + { + auto frequency = frequencies_.at (row); + + switch (column) + { + case 0: + switch (role) + { + case SortRole: + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::AccessibleTextRole: + item = frequency; + break; + + case Qt::ToolTipRole: + case Qt::AccessibleDescriptionRole: + item = tr ("Frequency"); + break; + + case Qt::TextAlignmentRole: + item = Qt::AlignRight + Qt::AlignVCenter; + break; + } + break; + + case 1: + switch (role) + { + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::AccessibleTextRole: + item = static_cast (frequency / 1.e6); + break; + + case SortRole: // use the underlying Frequency value + item = frequency; + break; + + case Qt::ToolTipRole: + case Qt::AccessibleDescriptionRole: + item = tr ("Frequency MHz"); + break; + + case Qt::TextAlignmentRole: + item = Qt::AlignRight + Qt::AlignVCenter; + break; + } + break; + } + } + + return item; +} + +bool FrequencyList::impl::setData (QModelIndex const& model_index, QVariant const& value, int role) +{ + bool changed {false}; + + auto row = model_index.row (); + if (model_index.isValid () + && Qt::EditRole == role + && row < frequencies_.size () + && 0 == model_index.column () + && value.canConvert ()) + { + auto frequency = value.value (); + auto original_frequency = frequencies_.at (row); + if (frequency != original_frequency) + { + frequencies_.replace (row, frequency); + Q_EMIT dataChanged (model_index, index (model_index.row (), 1), QVector {} << role); + } + changed = true; + } + + return changed; +} + +QVariant FrequencyList::impl::headerData (int section, Qt::Orientation orientation, int role) const +{ + QVariant header; + + if (Qt::DisplayRole == role + && Qt::Horizontal == orientation + && section < num_cols) + { + switch (section) + { + case 0: header = tr ("Frequency"); break; + case 1: header = tr ("Frequency (MHz)"); break; + } + } + else + { + header = QAbstractTableModel::headerData (section, orientation, role); + } + + return header; +} + +bool FrequencyList::impl::removeRows (int row, int count, QModelIndex const& parent) +{ + if (0 < count && (row + count) <= rowCount (parent)) + { + beginRemoveRows (parent, row, row + count - 1); + for (auto r = 0; r < count; ++r) + { + frequencies_.removeAt (row); + } + endRemoveRows (); + return true; + } + + return false; +} + +bool FrequencyList::impl::insertRows (int row, int count, QModelIndex const& parent) +{ + if (0 < count) + { + beginInsertRows (parent, row, row + count - 1); + for (auto r = 0; r < count; ++r) + { + frequencies_.insert (row, Frequency {}); + } + endInsertRows (); + return true; + } + + return false; +} + +QStringList FrequencyList::impl::mimeTypes () const +{ + QStringList types; + types << mime_type; + return types; +} + +QMimeData * FrequencyList::impl::mimeData (QModelIndexList const& items) const +{ + QMimeData * mime_data = new QMimeData {}; + QByteArray encoded_data; + QDataStream stream {&encoded_data, QIODevice::WriteOnly}; + + Q_FOREACH (auto const& item, items) + { + if (item.isValid ()) + { + stream << QString {data (item, Qt::DisplayRole).toString ()}; + } + } + + mime_data->setData (mime_type, encoded_data); + return mime_data; +} diff --git a/FrequencyList.hpp b/FrequencyList.hpp new file mode 100644 index 000000000..1ff7427d7 --- /dev/null +++ b/FrequencyList.hpp @@ -0,0 +1,59 @@ +#ifndef FREQUENCY_LIST_HPP__ +#define FREQUENCY_LIST_HPP__ + +#include "pimpl_h.hpp" + +#include + +#include "Radio.hpp" + +// +// Class FrequencyList +// +// Encapsulates a collection of frequencies. The implementation is a +// table containing the list of Frequency type elements which is +// editable and a second column which is an immutable double +// representation of the corresponding Frequency item scaled to +// mega-Hertz. +// +// The list is ordered. +// +// Responsibilities +// +// Stores internally a list of unique frequencies. Provides methods +// to add and delete list elements. +// +// Collaborations +// +// Implements the QSortFilterProxyModel interface for a list of spot +// frequencies. +// +class FrequencyList final + : public QSortFilterProxyModel +{ +public: + using Frequency = Radio::Frequency; + using Frequencies = Radio::Frequencies; + + explicit FrequencyList (QObject * parent = nullptr); + explicit FrequencyList (Frequencies, QObject * parent = nullptr); + ~FrequencyList (); + + // Load and store contents + FrequencyList& operator = (Frequencies); + Frequencies frequencies () const; + + // Model API + QModelIndex add (Frequency); + bool remove (Frequency); + bool removeDisjointRows (QModelIndexList); + + // Custom roles. + static int constexpr SortRole = Qt::UserRole; + +private: + class impl; + pimpl m_; +}; + +#endif diff --git a/GetUserId.cpp b/GetUserId.cpp new file mode 100644 index 000000000..238c3b5a9 --- /dev/null +++ b/GetUserId.cpp @@ -0,0 +1,75 @@ +#include "GetUserId.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// +// Dialog to get callsign +// +class CallsignDialog final + : public QDialog +{ + Q_OBJECT; + +private: + Q_DISABLE_COPY (CallsignDialog); + +public: + explicit CallsignDialog (QWidget * parent = nullptr) + : QDialog {parent} + { + setWindowTitle (QApplication::applicationName () + " - " + tr ("Callsign")); + + callsign_.setValidator (new QRegExpValidator {QRegExp {"[A-Za-z0-9]+"}, this}); + + auto form_layout = new QFormLayout (); + form_layout->addRow ("&Callsign:", &callsign_); + + auto main_layout = new QVBoxLayout (this); + main_layout->addLayout (form_layout); + + auto button_box = new QDialogButtonBox {QDialogButtonBox::Ok | QDialogButtonBox::Cancel}; + main_layout->addWidget (button_box); + + connect (button_box, &QDialogButtonBox::accepted, this, &CallsignDialog::accept); + connect (button_box, &QDialogButtonBox::rejected, this, &CallsignDialog::reject); + } + + QString callsign () const {return callsign_.text ();} + +private: + QLineEdit callsign_; +}; + +#include "GetUserId.moc" + +QString get_user_id () +{ + // get the users callsign so we can use it to persist the + // settings and log file against a unique tag + QString id; + { + CallsignDialog dialog; + while (id.isEmpty ()) + { + if (QDialog::Accepted == dialog.exec ()) + { + id = dialog.callsign ().toUpper (); + } + else + { + throw std::runtime_error ("Callsign required"); + } + } + } + + return id; +} diff --git a/GetUserId.hpp b/GetUserId.hpp new file mode 100644 index 000000000..205042f98 --- /dev/null +++ b/GetUserId.hpp @@ -0,0 +1,8 @@ +#ifndef GETUSERID_HPP_ +#define GETUSERID_HPP_ + +#include + +QString get_user_id (); + +#endif diff --git a/HRDTransceiver.cpp b/HRDTransceiver.cpp new file mode 100644 index 000000000..0ddf5aa15 --- /dev/null +++ b/HRDTransceiver.cpp @@ -0,0 +1,766 @@ +#include "HRDTransceiver.hpp" + +#include +#include +#include +#include +#include + +#include "NetworkServerLookup.hpp" + +namespace +{ + char const * const HRD_transceiver_name = "Ham Radio Deluxe"; + int socket_wait_time {5000}; +} + +void HRDTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id) +{ + (*registry)[HRD_transceiver_name] = TransceiverFactory::Capabilities (id, TransceiverFactory::Capabilities::network, "localhost:7809", true); +} + +struct HRDMessage +{ + // placement style new overload for outgoing messages that does the construction too + static void * operator new (size_t size, QString const& payload) + { + size += sizeof (QChar) * (payload.size () + 1); // space for terminator too + HRDMessage * storage (reinterpret_cast (new char[size])); + storage->size_ = size ; + ushort const * pl (payload.utf16 ()); + qCopy (pl, pl + payload.size () + 1, storage->payload_); // copy terminator too + storage->magic_1_ = magic_1_value_; + storage->magic_2_ = magic_2_value_; + storage->checksum_ = 0; + return storage; + } + + // placement style new overload for incoming messages that does the construction too + // + // no memory allocation here + static void * operator new (size_t /* size */, QByteArray const& message) + { + // nasty const_cast here to avoid copying the message buffer + return const_cast (reinterpret_cast (message.data ())); + } + + void operator delete (void * p, size_t) + { + delete [] reinterpret_cast (p); // mirror allocation in operator new above + } + + qint32 size_; + qint32 magic_1_; + qint32 magic_2_; + qint32 checksum_; // apparently not used + QChar payload_[0]; // UTF-16 (which is wchar_t on Windows) + + static qint32 const magic_1_value_; + static qint32 const magic_2_value_; +}; + +qint32 const HRDMessage::magic_1_value_ (0x1234ABCD); +qint32 const HRDMessage::magic_2_value_ (0xABCD1234); + +HRDTransceiver::HRDTransceiver (std::unique_ptr wrapped, QString const& server, bool use_for_ptt, int poll_interval) + : PollingTransceiver {poll_interval} + , wrapped_ {std::move (wrapped)} + , use_for_ptt_ {use_for_ptt} + , server_ {server} + , hrd_ {0} + , protocol_ {none} + , current_radio_ {0} + , vfo_count_ {0} + , vfo_A_button_ {-1} + , vfo_B_button_ {-1} + , vfo_toggle_button_ {-1} + , mode_A_dropdown_ {-1} + , mode_B_dropdown_ {-1} + , split_mode_button_ {-1} + , split_mode_dropdown_ {-1} + , split_mode_dropdown_write_only_ {false} + , split_mode_dropdown_selection_on_ {-1} + , split_mode_dropdown_selection_off_ {-1} + , split_off_button_ {-1} + , tx_A_button_ {-1} + , tx_B_button_ {-1} + , ptt_button_ {-1} + , reversed_ {false} +{ +} + +HRDTransceiver::~HRDTransceiver () +{ +} + +void HRDTransceiver::do_start () +{ +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::start"; +#endif + + wrapped_->start (); + + auto server_details = network_server_lookup (server_, 7809u); + + if (!hrd_) + { + hrd_ = new QTcpSocket {this}; // QObject takes ownership + } + + hrd_->connectToHost (std::get<0> (server_details), std::get<1> (server_details)); + if (!hrd_->waitForConnected (socket_wait_time)) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::start failed to connect:" << hrd_->errorString (); +#endif + + throw error {"Failed to connect to Ham Radio Deluxe\n" + hrd_->errorString ().toLocal8Bit ()}; + } + + init_radio (); +} + +void HRDTransceiver::do_stop () +{ + if (hrd_) + { + hrd_->close (); + } + + if (wrapped_) + { + wrapped_->stop (); + } + +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::stop: state:" << state () << "reversed =" << reversed_; +#endif +} + +void HRDTransceiver::init_radio () +{ + Q_ASSERT (hrd_); + + if (none == protocol_) + { + try + { + protocol_ = v5; // try this first (works for v6 too) + send_command ("get context", false, false); + } + catch (error const&) + { + protocol_ = none; + } + } + + if (none == protocol_) + { + hrd_->close (); + + protocol_ = v4; // try again with older protocol + hrd_->connectToHost (QHostAddress::LocalHost, 7809); + if (!hrd_->waitForConnected (socket_wait_time)) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::init_radio failed to connect:" << hrd_->errorString (); +#endif + + throw error {"Failed to connect to Ham Radio Deluxe\n" + hrd_->errorString ().toLocal8Bit ()}; + } + + send_command ("get context", false, false); + } + +#if WSJT_TRACE_CAT + qDebug () << send_command ("get id", false, false); + qDebug () << send_command ("get version", false, false); +#endif + + auto radios = send_command ("get radios", false, false).trimmed ().split (',', QString::SkipEmptyParts); + if (radios.isEmpty ()) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::init_radio no rig found"; +#endif + + throw error {"Ham Radio Deluxe: no rig found"}; + } + + Q_FOREACH (auto const& radio, radios) + { + auto entries = radio.trimmed ().split (':', QString::SkipEmptyParts); + radios_.push_back (std::forward_as_tuple (entries[0].toUInt (), entries[1])); + } + +#if WSJT_TRACE_CAT + qDebug () << "radios:"; + Q_FOREACH (auto const& radio, radios_) + { + qDebug () << "\t[" << std::get<0> (radio) << "] " << std::get<1> (radio); + } +#endif + + if (send_command ("get radio", false, false, true).isEmpty ()) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::init_radio no rig found"; +#endif + + throw error {"Ham Radio Deluxe: no rig found"}; + } + + vfo_count_ = send_command ("get vfo-count").toUInt (); + +#if WSJT_TRACE_CAT + qDebug () << "vfo count:" << vfo_count_; +#endif + + buttons_ = send_command ("get buttons").trimmed ().split (',', QString::SkipEmptyParts).replaceInStrings (" ", "~"); + +#if WSJT_TRACE_CAT + qDebug () << "HRD Buttons: " << buttons_; +#endif + + dropdown_names_ = send_command ("get dropdowns").trimmed ().split (',', QString::SkipEmptyParts); + Q_FOREACH (auto d, dropdown_names_) + { + dropdowns_[d] = send_command ("get dropdown-list {" + d + "}").trimmed ().split (',', QString::SkipEmptyParts); + } + +#if WSJT_TRACE_CAT + qDebug () << "HRD Dropdowns: " << dropdowns_; +#endif + + vfo_A_button_ = find_button (QRegExp ("^(VFO~A|Main)$")); + vfo_B_button_ = find_button (QRegExp ("^(VFO~B|Sub)$")); + vfo_toggle_button_ = find_button (QRegExp ("^(A~/~B)$")); + Q_ASSERT (vfo_toggle_button_ >= 0 || (vfo_A_button_ >= 0 && vfo_B_button_ >=0)); + + split_mode_button_ = find_button (QRegExp ("^(Spl~On|Spl_On|Split)$")); + split_off_button_ = find_button (QRegExp ("^(Spl~Off|Spl_Off)$")); + + if ((split_mode_dropdown_ = find_dropdown (QRegExp ("^(Split)$"))) >= 0) + { + split_mode_dropdown_selection_on_ = find_dropdown_selection (split_mode_dropdown_, QRegExp ("^(On)$")); + split_mode_dropdown_selection_off_ = find_dropdown_selection (split_mode_dropdown_, QRegExp ("^(Off)$")); + } + + tx_A_button_ = find_button (QRegExp ("^(TX~main|TX~-~A)$")); + tx_B_button_ = find_button (QRegExp ("^(TX~sub|TX~-~B)$")); + + Q_ASSERT (split_mode_button_ >= 0 || split_mode_dropdown_ >= 0 || (tx_A_button_ >= 0 && tx_B_button_ >= 0)); + + mode_A_dropdown_ = find_dropdown (QRegExp ("^(Main Mode|Mode)$")); + map_modes (mode_A_dropdown_, &mode_A_map_); + + if ((mode_B_dropdown_ = find_dropdown (QRegExp ("^(Sub Mode)$"))) >= 0) + { + map_modes (mode_B_dropdown_, &mode_B_map_); + } + + ptt_button_ = find_button (QRegExp ("^(TX)$")); + + sync_impl (); +} + +void HRDTransceiver::sync_impl () +{ + if (vfo_count_ == 1) + { + // put the rig into a known state + auto f = send_command ("get frequency").toUInt (); + auto m = lookup_mode (get_dropdown (mode_A_dropdown_), mode_A_map_); + set_button (vfo_B_button_ >= 0 ? vfo_B_button_ : vfo_toggle_button_); + auto fo = send_command ("get frequency").toUInt (); + update_other_frequency (fo); + auto mo = lookup_mode (get_dropdown (mode_A_dropdown_), mode_A_map_); + set_button (vfo_A_button_ >= 0 ? vfo_A_button_ : vfo_toggle_button_); + if (f != fo || m != mo) + { + // we must have started with A/MAIN + update_rx_frequency (f); + update_mode (m); + } + else + { + update_rx_frequency (send_command ("get frequency").toUInt ()); + update_mode (lookup_mode (get_dropdown (mode_A_dropdown_), mode_A_map_)); + } + } + + poll (); +} + +int HRDTransceiver::find_button (QRegExp const& re) const +{ + return buttons_.indexOf (re); +} + +int HRDTransceiver::find_dropdown (QRegExp const& re) const +{ + return dropdown_names_.indexOf (re); +} + +int HRDTransceiver::find_dropdown_selection (int dropdown, QRegExp const& re) const +{ + return dropdowns_.value (dropdown_names_.value (dropdown)).lastIndexOf (re); // backwards because more specialised modes tend to be later in list +} + +int HRDTransceiver::lookup_dropdown_selection (int dropdown, QString const& selection) const +{ + int index {dropdowns_.value (dropdown_names_.value (dropdown)).indexOf (selection)}; + Q_ASSERT (-1 != index); + return index; +} + +void HRDTransceiver::map_modes (int dropdown, ModeMap *map) +{ + // order matters here (both in the map and in the regexps) + map->push_back (std::forward_as_tuple (CW, find_dropdown_selection (dropdown, QRegExp ("^(CW|CW\\(N\\))$")))); + map->push_back (std::forward_as_tuple (CW_R, find_dropdown_selection (dropdown, QRegExp ("^(CW-R|CW)$")))); + map->push_back (std::forward_as_tuple (LSB, find_dropdown_selection (dropdown, QRegExp ("^(LSB)$")))); + map->push_back (std::forward_as_tuple (USB, find_dropdown_selection (dropdown, QRegExp ("^(USB)$")))); + map->push_back (std::forward_as_tuple (DIG_U, find_dropdown_selection (dropdown, QRegExp ("^(DIG|PKT-U|USB)$")))); + map->push_back (std::forward_as_tuple (DIG_L, find_dropdown_selection (dropdown, QRegExp ("^(DIG|PKT-L|LSB)$")))); + map->push_back (std::forward_as_tuple (FSK, find_dropdown_selection (dropdown, QRegExp ("^(DIG|FSK|RTTY)$")))); + map->push_back (std::forward_as_tuple (FSK_R, find_dropdown_selection (dropdown, QRegExp ("^(DIG|FSK-R|RTTY-R|RTTY)$")))); + map->push_back (std::forward_as_tuple (AM, find_dropdown_selection (dropdown, QRegExp ("^(AM)$")))); + map->push_back (std::forward_as_tuple (FM, find_dropdown_selection (dropdown, QRegExp ("^(FM|FM\\(N\\)|WFM)$")))); + map->push_back (std::forward_as_tuple (DIG_FM, find_dropdown_selection (dropdown, QRegExp ("^(PKT-FM|PKT|FM)$")))); + +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::map_modes: for dropdown" << dropdown_names_[dropdown]; + + std::for_each (map->begin (), map->end (), [this, dropdown] (ModeMap::value_type const& item) + { + auto rhs = std::get<1> (item); + qDebug () << '\t' << std::get<0> (item) << "<->" << (rhs >= 0 ? dropdowns_[dropdown_names_[dropdown]][rhs] : "None"); + }); +#endif +} + +int HRDTransceiver::lookup_mode (MODE mode, ModeMap const& map) const +{ + auto it = std::find_if (map.begin (), map.end (), [mode] (ModeMap::value_type const& item) {return std::get<0> (item) == mode;}); + if (map.end () == it) + { + throw error {"Ham Radio Deluxe: rig doesn't support mode"}; + } + return std::get<1> (*it); +} + +auto HRDTransceiver::lookup_mode (int mode, ModeMap const& map) const -> MODE +{ + auto it = std::find_if (map.begin (), map.end (), [mode] (ModeMap::value_type const& item) {return std::get<1> (item) == mode;}); + if (map.end () == it) + { + throw error {"Ham Radio Deluxe: sent an unrecognised mode"}; + } + return std::get<0> (*it); +} + +int HRDTransceiver::get_dropdown (int dd, bool no_debug) +{ + auto dd_name = dropdown_names_.value (dd); + auto reply = send_command ("get dropdown-text {" + dd_name + "}", no_debug); + auto colon_index = reply.indexOf (':'); + + if (colon_index < 0) + { + +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::get_dropdown bad response"; +#endif + + throw error {"Ham Radio Deluxe didn't respond as expected"}; + } + + Q_ASSERT (reply.left (colon_index).trimmed () == dd_name); + return lookup_dropdown_selection (dd, reply.mid (colon_index + 1).trimmed ()); +} + +void HRDTransceiver::set_dropdown (int dd, int value) +{ + auto dd_name = dropdown_names_.value (dd); + if (value >= 0) + { + send_simple_command ("set dropdown " + dd_name.replace (' ', '~') + ' ' + dropdowns_.value (dd_name).value (value) + ' ' + QString::number (value)); + } + else + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::set_dropdown item" << value << "not found in" << dd_name; +#endif + + throw error {("Ham Radio Deluxe: item not found in " + dd_name + " dropdown list").toLocal8Bit ()}; + } +} + +void HRDTransceiver::do_ptt (bool on) +{ +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::do_ptt:" << on; +#endif + + if (use_for_ptt_) + { + set_button (ptt_button_, on); + } + else + { + wrapped_->ptt (on); + } + update_PTT (on); +} + +void HRDTransceiver::set_button (int button_index, bool checked) +{ + if (button_index >= 0) + { + send_simple_command ("set button-select " + buttons_.value (button_index) + (checked ? " 1" : " 0")); + } + else + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::set_button invalid button"; +#endif + + throw error {"Ham Radio Deluxe: button not available"}; + } +} + +void HRDTransceiver::do_frequency (Frequency f) +{ +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::do_frequency:" << f << "reversed:" << reversed_; +#endif + + send_simple_command ("set frequency-hz " + QString::number (f)); + update_rx_frequency (f); +} + +void HRDTransceiver::do_tx_frequency (Frequency tx, bool rationalise_mode) +{ +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::do_tx_frequency:" << tx << "reversed:" << reversed_; +#endif + + bool split {tx != 0}; + + if (vfo_count_ > 1) + { + reversed_ = is_button_checked (vfo_B_button_); + } + + if (split) + { + auto fo_string = QString::number (tx); + if (reversed_) + { + Q_ASSERT (vfo_count_ > 1); + + auto frequencies = send_command ("get frequencies").trimmed ().split ('-', QString::SkipEmptyParts); + send_simple_command ("set frequencies-hz " + fo_string + ' ' + QString::number (frequencies[1].toUInt ())); + } + else + { + if (vfo_count_ > 1) + { + auto frequencies = send_command ("get frequencies").trimmed ().split ('-', QString::SkipEmptyParts); + send_simple_command ("set frequencies-hz " + QString::number (frequencies[0].toUInt ()) + ' ' + fo_string); + } + else + { + // we rationalise the modes and VFOs here as well as the frequencies + set_button (vfo_B_button_ >= 0 ? vfo_B_button_ : vfo_toggle_button_); + send_simple_command ("set frequency-hz " + fo_string); + if (rationalise_mode) + { + set_dropdown (mode_B_dropdown_ >= 0 ? mode_B_dropdown_ : mode_A_dropdown_, lookup_mode (state ().mode (), mode_B_dropdown_ >= 0 ? mode_B_map_ : mode_A_map_)); + } + set_button (vfo_A_button_ >= 0 ? vfo_A_button_ : vfo_toggle_button_); + } + } + if (rationalise_mode) + { + set_dropdown (mode_B_dropdown_ >= 0 ? mode_B_dropdown_ : mode_A_dropdown_, lookup_mode (state ().mode (), mode_B_dropdown_ >= 0 ? mode_B_map_ : mode_A_map_)); + } + } + update_other_frequency (tx); + + if (split_mode_button_ >= 0) + { + if (split_off_button_ >= 0 && !split) + { + set_button (split_off_button_); + } + else + { + set_button (split_mode_button_, split); + } + } + else if (split_mode_dropdown_ >= 0) + { + set_dropdown (split_mode_dropdown_, split ? split_mode_dropdown_selection_on_ : split_mode_dropdown_selection_off_); + } + else if (vfo_A_button_ >= 0 && vfo_B_button_ >= 0 && tx_A_button_ >= 0 && tx_B_button_ >= 0) + { + if (split) + { + if (reversed_ != is_button_checked (tx_A_button_)) + { + set_button (reversed_ ? tx_A_button_ : tx_B_button_); + } + } + else + { + if (reversed_ != is_button_checked (tx_B_button_)) + { + set_button (reversed_ ? tx_B_button_ : tx_A_button_); + } + } + } + update_split (split); +} + +void HRDTransceiver::do_mode (MODE mode, bool rationalise) +{ +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::do_mode:" << mode; +#endif + + set_dropdown (mode_A_dropdown_, lookup_mode (mode, mode_A_map_)); + + if (rationalise && state ().split ()) // rationalise mode if split + { + if (mode_B_dropdown_ >= 0) + { + set_dropdown (mode_B_dropdown_, lookup_mode (mode, mode_B_map_)); + } + else if (vfo_count_ < 2) + { + set_button (vfo_B_button_ >= 0 ? vfo_B_button_ : vfo_toggle_button_); + set_dropdown (mode_B_dropdown_ >= 0 ? mode_B_dropdown_ : mode_A_dropdown_, lookup_mode (mode, mode_B_dropdown_ >= 0 ? mode_B_map_ : mode_A_map_)); + set_button (vfo_A_button_ >= 0 ? vfo_A_button_ : vfo_toggle_button_); + } + } + + update_mode (mode); +} + +bool HRDTransceiver::is_button_checked (int button_index, bool no_debug) +{ + auto reply = send_command ("get button-select " + buttons_.value (button_index), no_debug); + if ("1" != reply && "0" != reply) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::is_button_checked bad response"; +#endif + + throw error {"Ham Radio Deluxe didn't respond as expected"}; + } + return "1" == reply; +} + +void HRDTransceiver::poll () +{ +#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS + bool quiet {false}; +#else + bool quiet {true}; +#endif + + if (split_off_button_ >= 0) + { + // we are probably dealing with an Icom and have to guess SPLIT mode :( + } + else if (split_mode_button_ >= 0) + { + update_split (is_button_checked (split_mode_button_, quiet)); + } + else if (split_mode_dropdown_ >= 0) + { + if (!split_mode_dropdown_write_only_) + { + try + { + update_split (get_dropdown (split_mode_dropdown_, quiet) == split_mode_dropdown_selection_on_); + } + catch (error const&) + { + // leave split alone as we can't query it - it should be + // correct so long as rig or HRD haven't been changed + split_mode_dropdown_write_only_ = true; + } + } + } + else if (vfo_A_button_ >= 0 && vfo_B_button_ >= 0 && tx_A_button_ >= 0 && tx_B_button_ >= 0) + { + auto vfo_A = is_button_checked (vfo_A_button_, quiet); + auto tx_A = is_button_checked (tx_A_button_, quiet); + + update_split (vfo_A != tx_A); + reversed_ = !vfo_A; + } + + if (vfo_count_ > 1) + { + auto frequencies = send_command ("get frequencies", quiet).trimmed ().split ('-', QString::SkipEmptyParts); + update_rx_frequency (frequencies[reversed_ ? 1 : 0].toUInt ()); + update_other_frequency (frequencies[reversed_ ? 0 : 1].toUInt ()); + } + else + { + update_rx_frequency (send_command ("get frequency", quiet).toUInt ()); + } + + update_mode (lookup_mode (get_dropdown (mode_A_dropdown_, quiet), mode_A_map_)); +} + +QString HRDTransceiver::send_command (QString const& cmd, bool no_debug, bool prepend_context, bool recurse) +{ + Q_ASSERT (hrd_); + + QString result; + + if (current_radio_ && prepend_context && vfo_count_ < 2) + { + // TODO G4WJS: get rid of this or move to a thread required on some + // radios because commands don't get executed correctly (ICOM for + // example) + QThread::msleep (50); + } + + if (QTcpSocket::ConnectedState != hrd_->state ()) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::send_command connection failed:" << hrd_->errorString (); +#endif + + throw error {"Ham Radio Deluxe connection failed\n" + hrd_->errorString ().toLocal8Bit ()}; + } + + if (!recurse && prepend_context) + { + auto radio_name = send_command ("get radio", no_debug, current_radio_, true); + auto radio_iter = std::find_if (radios_.begin (), radios_.end (), [this, &radio_name] (RadioMap::value_type const& radio) + { + return std::get<1> (radio) == radio_name; + }); + if (radio_iter == radios_.end ()) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::send_command rig disappeared or changed"; +#endif + + throw error {"Ham Radio Deluxe: rig has disappeared or changed"}; + } + + if (0u == current_radio_ || std::get<0> (*radio_iter) != current_radio_) + { + current_radio_ = std::get<0> (*radio_iter); + } + } + + auto context = '[' + QString::number (current_radio_) + "] "; + + int bytes_to_send; + int bytes_sent; + if (v4 == protocol_) + { + auto message = ((prepend_context ? context + cmd : cmd) + "\r").toLocal8Bit (); + bytes_to_send = message.size (); + bytes_sent = hrd_->write (message.data (), bytes_to_send); + + if (!no_debug) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::send_command:" << message; +#endif + } + } + else + { + auto string = prepend_context ? context + cmd : cmd; + if (!no_debug) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::send_command:" << string; +#endif + } + + QScopedPointer message (new (string) HRDMessage); + bytes_to_send = message->size_; + bytes_sent = hrd_->write (reinterpret_cast (message.data ()), bytes_to_send); + } + + if (bytes_sent < bytes_to_send + || !hrd_->waitForBytesWritten (socket_wait_time) + || QTcpSocket::ConnectedState != hrd_->state ()) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::send_command failed" << hrd_->errorString (); +#endif + + throw error {"Ham Radio Deluxe send command failed\n" + hrd_->errorString ().toLocal8Bit ()}; + } + + if (!hrd_->waitForReadyRead (socket_wait_time)) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::send_command failed to reply" << hrd_->errorString (); +#endif + + throw error {"Ham Radio Deluxe failed to reply to command\n" + hrd_->errorString ().toLocal8Bit ()}; + } + QByteArray buffer (hrd_->readAll ()); + if (!no_debug) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::send_command: reply byte count:" << buffer.size (); +#endif + } + + if (v4 == protocol_) + { + result = QString {buffer}.trimmed (); + } + else + { + HRDMessage const * reply (new (buffer) HRDMessage); + + if (reply->magic_1_value_ != reply->magic_1_ && reply->magic_2_value_ != reply->magic_2_) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::send_command invalid reply"; +#endif + + throw error {"Ham Radio Deluxe sent an invalid reply to our command"}; + } + + result = QString {reply->payload_}; // this is not a memory leak (honest!) + } + + if (!no_debug) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::send_command: reply: " << result; +#endif + } + + return result; +} + +void HRDTransceiver::send_simple_command (QString const& command, bool no_debug) +{ + if ("OK" != send_command (command, no_debug)) + { +#if WSJT_TRACE_CAT + qDebug () << "HRDTransceiver::send_simple_command unexpected response"; +#endif + + throw error {"Ham Radio Deluxe didn't respond as expected"}; + } +} diff --git a/HRDTransceiver.hpp b/HRDTransceiver.hpp new file mode 100644 index 000000000..7eae74eb1 --- /dev/null +++ b/HRDTransceiver.hpp @@ -0,0 +1,95 @@ +#ifndef HRD_TRANSCEIVER_HPP__ +#define HRD_TRANSCEIVER_HPP__ + +#include +#include +#include + +#include +#include +#include + +#include "TransceiverFactory.hpp" +#include "PollingTransceiver.hpp" + +class QRegExp; +class QTcpSocket; + +// +// Ham Radio Deluxe Transceiver Interface +// +// Implemented as a Transceiver decorator because we may want the PTT +// services of another Transceiver type such as the HamlibTransceiver +// which can be enabled by wrapping a HamlibTransceiver instantiated +// as a "Hamlib Dummy" transceiver in the Transceiver factory method. +// +class HRDTransceiver final + : public PollingTransceiver +{ +public: + static void register_transceivers (TransceiverFactory::Transceivers *, int id); + + // takes ownership of wrapped Transceiver + explicit HRDTransceiver (std::unique_ptr wrapped, QString const& server, bool use_for_ptt, int poll_interval); + ~HRDTransceiver (); + +private: + void do_start () override; + void do_stop () override; + void do_frequency (Frequency) override; + void do_tx_frequency (Frequency, bool rationalise_mode) override; + void do_mode (MODE, bool rationalise) override; + void do_ptt (bool on) override; + + void poll () override; + + void init_radio (); + QString send_command (QString const&, bool no_debug = false, bool prepend_context = true, bool recurse = false); + void send_simple_command (QString const&, bool no_debug = false); + void sync_impl (); + int find_button (QRegExp const&) const; + int find_dropdown (QRegExp const&) const; + int find_dropdown_selection (int dropdown, QRegExp const&) const; + int lookup_dropdown_selection (int dropdown, QString const&) const; + int get_dropdown (int, bool no_debug = false); + void set_dropdown (int, int); + void set_button (int button_index, bool checked = true); + bool is_button_checked (int button_index, bool no_debug = false); + + using ModeMap = std::vector >; + void map_modes (int dropdown, ModeMap *); + int lookup_mode (MODE, ModeMap const&) const; + MODE lookup_mode (int, ModeMap const&) const; + + std::unique_ptr wrapped_; + bool use_for_ptt_; + QString server_; + QTcpSocket * hrd_; + enum {none, v4, v5} protocol_; + using RadioMap = std::vector >; + RadioMap radios_; + unsigned current_radio_; + unsigned vfo_count_; + QStringList buttons_; + QStringList dropdown_names_; + QMap dropdowns_; + int vfo_A_button_; + int vfo_B_button_; + int vfo_toggle_button_; + int mode_A_dropdown_; + ModeMap mode_A_map_; + int mode_B_dropdown_; + ModeMap mode_B_map_; + int split_mode_button_; + int split_mode_dropdown_; + bool split_mode_dropdown_write_only_; + int split_mode_dropdown_selection_on_; + int split_mode_dropdown_selection_off_; + int split_off_button_; + int tx_A_button_; + int tx_B_button_; + int ptt_button_; + bool reversed_; +}; + +#endif diff --git a/HamlibTransceiver.cpp b/HamlibTransceiver.cpp new file mode 100644 index 000000000..ddfbd811a --- /dev/null +++ b/HamlibTransceiver.cpp @@ -0,0 +1,845 @@ +#include "HamlibTransceiver.hpp" + +#include + +#include +#include +#include + +namespace +{ + // Unfortunately bandwidth is conflated with mode, this is probably + // because Icom do the same. So we have to care about bandwidth if + // we want to set mode otherwise we will end up setting unwanted + // bandwidths every time we change mode. The best we can do via the + // Hamlib API is to request the normal option for the mode and hope + // that an appropriate filter is selected. Also ensure that mode is + // only set is absolutely necessary. On Icoms (and probably others) + // the filter is selected by number without checking the actual BW + // so unless the "normal" defaults are set on the rig we won't get + // desirable results. + // + // As an ultimate workaround make sure the user always has the + // option to skip mode setting altogether. + + // reroute Hamlib diagnostic messages to Qt + int debug_callback (enum rig_debug_level_e level, rig_ptr_t /* arg */, char const * format, va_list ap) + { + QString message; + message = message.vsprintf (format, ap).trimmed (); + + switch (level) + { + case RIG_DEBUG_BUG: + qFatal ("%s", message.toLocal8Bit ().data ()); + break; + + case RIG_DEBUG_ERR: + qCritical ("%s", message.toLocal8Bit ().data ()); + break; + + case RIG_DEBUG_WARN: + qWarning ("%s", message.toLocal8Bit ().data ()); + break; + + default: + qDebug ("%s", message.toLocal8Bit ().data ()); + break; + } + + return 0; + } + + // callback function that receives transceiver capabilities from the + // hamlib libraries + int rigCallback (rig_caps const * caps, void * callback_data) + { + TransceiverFactory::Transceivers * rigs = reinterpret_cast (callback_data); + + QString key; + if ("Hamlib" == QString::fromLatin1 (caps->mfg_name).trimmed () + && "Dummy" == QString::fromLatin1 (caps->model_name).trimmed ()) + { + key = TransceiverFactory::basic_transceiver_name_; + } + else + { + key = QString::fromLatin1 (caps->mfg_name).trimmed () + + ' '+ QString::fromLatin1 (caps->model_name).trimmed () + // + ' '+ QString::fromLatin1 (caps->version).trimmed () + // + " (" + QString::fromLatin1 (rig_strstatus (caps->status)).trimmed () + ')' + ; + } + + auto port_type = TransceiverFactory::Capabilities::none; + switch (caps->port_type) + { + case RIG_PORT_SERIAL: + port_type = TransceiverFactory::Capabilities::serial; + break; + + case RIG_PORT_NETWORK: + port_type = TransceiverFactory::Capabilities::network; + break; + + default: break; + } + (*rigs)[key] = TransceiverFactory::Capabilities (caps->rig_model + , port_type + , RIG_PTT_RIG == caps->ptt_type || RIG_PTT_RIG_MICDATA == caps->ptt_type + , RIG_PTT_RIG_MICDATA == caps->ptt_type); + + return 1; // keep them coming + } + + // int frequency_change_callback (RIG * /* rig */, vfo_t vfo, freq_t f, rig_ptr_t arg) + // { + // (void)vfo; // unused in release build + + // Q_ASSERT (vfo == RIG_VFO_CURR); // G4WJS: at the time of writing only current VFO is signalled by hamlib + + // HamlibTransceiver * transceiver (reinterpret_cast (arg)); + // Q_EMIT transceiver->frequency_change (f, Transceiver::A); + // return RIG_OK; + // } + + class hamlib_tx_vfo_fixup final + { + public: + hamlib_tx_vfo_fixup (RIG * rig, vfo_t tx_vfo) + : rig_ {rig} + { + original_vfo_ = rig_->state.tx_vfo; + rig_->state.tx_vfo = tx_vfo; + } + + ~hamlib_tx_vfo_fixup () + { + rig_->state.tx_vfo = original_vfo_; + } + + private: + RIG * rig_; + vfo_t original_vfo_; + }; +} + +void HamlibTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry) +{ + rig_set_debug_callback (debug_callback, nullptr); + +#if WSJT_HAMLIB_TRACE + rig_set_debug (RIG_DEBUG_TRACE); +#elif defined (NDEBUG) + rig_set_debug (RIG_DEBUG_ERR); +#else + rig_set_debug (RIG_DEBUG_VERBOSE); +#endif + + rig_load_all_backends (); + rig_list_foreach (rigCallback, registry); +} + +void HamlibTransceiver::RIGDeleter::cleanup (RIG * rig) +{ + if (rig) + { + // rig->state.obj = 0; + rig_cleanup (rig); + } +} + +HamlibTransceiver::HamlibTransceiver (int model_number + , QString const& cat_port + , int cat_baud + , TransceiverFactory::DataBits cat_data_bits + , TransceiverFactory::StopBits cat_stop_bits + , TransceiverFactory::Handshake cat_handshake + , bool cat_dtr_always_on + , bool cat_rts_always_on + , TransceiverFactory::PTTMethod ptt_type + , TransceiverFactory::TXAudioSource back_ptt_port + , QString const& ptt_port + , int poll_interval) +: PollingTransceiver {poll_interval} +, rig_ {rig_init (model_number)} +, back_ptt_port_ {TransceiverFactory::TX_audio_source_rear == back_ptt_port} +, is_dummy_ {RIG_MODEL_DUMMY == model_number} +, reversed_ {false} +{ + if (!rig_) + { + throw error {"Hamlib initialisation error"}; + } + + // rig_->state.obj = this; + + if (/*!is_dummy_ &&*/ !cat_port.isEmpty () /*&& cat_port != "None"*/) + { +// #if defined (WIN32) +// set_conf ("rig_pathname", ("\\\\.\\" + cat_port).toLatin1 ().data ()); +// #else + set_conf ("rig_pathname", cat_port.toLatin1 ().data ()); +// #endif + } + + set_conf ("serial_speed", QByteArray::number (cat_baud).data ()); + set_conf ("data_bits", TransceiverFactory::seven_data_bits == cat_data_bits ? "7" : "8"); + set_conf ("stop_bits", TransceiverFactory::one_stop_bit == cat_stop_bits ? "1" : "2"); + + switch (cat_handshake) + { + case TransceiverFactory::handshake_none: set_conf ("serial_handshake", "None"); break; + case TransceiverFactory::handshake_XonXoff: set_conf ("serial_handshake", "XONXOFF"); break; + case TransceiverFactory::handshake_hardware: set_conf ("serial_handshake", "Hardware"); break; + } + + if (cat_dtr_always_on) + { + set_conf ("dtr_state", "ON"); + } + if (TransceiverFactory::handshake_hardware != cat_handshake && cat_rts_always_on) + { + set_conf ("rts_state", "ON"); + } + + switch (ptt_type) + { + case TransceiverFactory::PTT_method_VOX: + set_conf ("ptt_type", "None"); + break; + + case TransceiverFactory::PTT_method_CAT: + set_conf ("ptt_type", "RIG"); + break; + + case TransceiverFactory::PTT_method_DTR: + case TransceiverFactory::PTT_method_RTS: + if (!ptt_port.isEmpty () && ptt_port != "None" && ptt_port != cat_port) + { +#if defined (WIN32) + set_conf ("ptt_pathname", ("\\\\.\\" + ptt_port).toLatin1 ().data ()); +#else + set_conf ("ptt_pathname", ptt_port.toLatin1 ().data ()); +#endif + } + + if (TransceiverFactory::PTT_method_DTR == ptt_type) + { + set_conf ("ptt_type", "DTR"); + } + else + { + set_conf ("ptt_type", "RTS"); + } + } + + // Make Icom CAT split commands less glitchy + set_conf ("no_xchg", "1"); + + // would be nice to get events but not supported on Windows and also not on a lot of rigs + // rig_set_freq_callback (rig_.data (), &frequency_change_callback, this); +} + +HamlibTransceiver::~HamlibTransceiver () +{ +} + +void HamlibTransceiver::do_start () +{ +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::do_start rig:" << QString::fromLatin1 (rig_->caps->mfg_name).trimmed () + ' ' + + QString::fromLatin1 (rig_->caps->model_name).trimmed (); +#endif + + error_check (rig_open (rig_.data ())); + + init_rig (); +} + +void HamlibTransceiver::do_stop () +{ + if (rig_) + { + rig_close (rig_.data ()); + } + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::do_stop: state:" << state () << "reversed =" << reversed_; +#endif +} + +void HamlibTransceiver::init_rig () +{ + if (!is_dummy_) + { + freq_t f1; + freq_t f2; + rmode_t m {RIG_MODE_USB}; + rmode_t mb; + pbwidth_t w {rig_passband_wide (rig_.data (), m)}; + pbwidth_t wb; + if (!rig_->caps->get_vfo) + { + // Icom have deficient CAT protocol with no way of reading which + // VFO is selected or if SPLIT is selected so we have to simply + // assume it is as when we started by setting at open time right + // here. We also gather/set other initial state. + error_check (rig_get_freq (rig_.data (), RIG_VFO_CURR, &f1)); + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_get_freq =" << f1; +#endif + + error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &m, &w)); + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_get_mode =" << m << "bw =" << w; +#endif + + if (!rig_->caps->set_vfo) + { + if (rig_has_vfo_op (rig_.data (), RIG_OP_TOGGLE)) + { + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_vfo_op TOGGLE"; +#endif + + error_check (rig_vfo_op (rig_.data (), RIG_VFO_CURR, RIG_OP_TOGGLE)); + } + else + { + throw error {"Hamlib: unable to initialise rig"}; + } + } + else + { + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_set_vfo"; +#endif + + error_check (rig_set_vfo (rig_.data (), rig_->state.vfo_list & RIG_VFO_B ? RIG_VFO_B : RIG_VFO_SUB)); + } + + error_check (rig_get_freq (rig_.data (), RIG_VFO_CURR, &f2)); + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_get_freq =" << f2; +#endif + + error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &mb, &wb)); + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_get_mode =" << mb << "bw =" << wb; +#endif + + update_other_frequency (f2); + + if (!rig_->caps->set_vfo) + { + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_vfo_op TOGGLE"; +#endif + + error_check (rig_vfo_op (rig_.data (), RIG_VFO_CURR, RIG_OP_TOGGLE)); + } + else + { +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_set_vfo"; +#endif + + error_check (rig_set_vfo (rig_.data (), rig_->state.vfo_list & RIG_VFO_A ? RIG_VFO_A : RIG_VFO_MAIN)); + } + + if (f1 != f2 || m != mb || w != wb) // we must have started with MAIN/A + { + update_rx_frequency (f1); + } + else + { + error_check (rig_get_freq (rig_.data (), RIG_VFO_CURR, &f1)); + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_get_freq =" << f1; +#endif + + error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &m, &w)); + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_get_mode =" << m << "bw =" << w; +#endif + + update_rx_frequency (f1); + } + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_set_split_vfo"; +#endif + + // error_check (rig_set_split_vfo (rig_.data (), RIG_VFO_CURR, RIG_SPLIT_OFF, RIG_VFO_CURR)); + // update_split (false); + } + else + { + vfo_t v; + error_check (rig_get_vfo (rig_.data (), &v)); // has side effect of establishing current VFO inside hamlib + +#if WSJT_TRACE_CAT + qDebug ().nospace () << "HamlibTransceiver::init_rig rig_get_vfo = 0x" << hex << v; +#endif + + reversed_ = RIG_VFO_B == v; + + if (!(rig_->caps->targetable_vfo & (RIG_TARGETABLE_MODE | RIG_TARGETABLE_PURE))) + { + error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &m, &w)); + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig rig_get_mode =" << m << "bw =" << w; +#endif + } + } + update_mode (map_mode (m)); + } + + poll (); + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::init_rig exit" << state () << "reversed =" << reversed_; +#endif +} + +auto HamlibTransceiver::get_vfos () const -> std::tuple +{ + if (rig_->caps->get_vfo) + { + vfo_t v; + error_check (rig_get_vfo (rig_.data (), &v)); // has side effect of establishing current VFO inside hamlib + +#if WSJT_TRACE_CAT + qDebug ().nospace () << "HamlibTransceiver::get_vfos rig_get_vfo = 0x" << hex << v; +#endif + + reversed_ = RIG_VFO_B == v; + } + else if (rig_->caps->set_vfo) + { + // use VFO A/MAIN for main frequency and B/SUB for Tx + // frequency if split since these type of radios can only + // support this way around + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::get_vfos rig_set_vfo"; +#endif + + error_check (rig_set_vfo (rig_.data (), rig_->state.vfo_list & RIG_VFO_A ? RIG_VFO_A : RIG_VFO_MAIN)); + } + // else only toggle available but both VFOs should be substitutable + + auto rx_vfo = rig_->state.vfo_list & RIG_VFO_A ? RIG_VFO_A : RIG_VFO_MAIN; + auto tx_vfo = state ().split () ? (rig_->state.vfo_list & RIG_VFO_B ? RIG_VFO_B : RIG_VFO_SUB) : rx_vfo; + if (reversed_) + { +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::get_vfos reversing VFOs"; +#endif + + std::swap (rx_vfo, tx_vfo); + } + +#if WSJT_TRACE_CAT + qDebug ().nospace () << "HamlibTransceiver::get_vfos RX VFO = 0x" << hex << rx_vfo << " TX VFO = 0x" << hex << tx_vfo; +#endif + + return std::make_tuple (rx_vfo, tx_vfo); +} + +void HamlibTransceiver::do_frequency (Frequency f) +{ +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::do_frequency:" << f << "reversed:" << reversed_; +#endif + + if (!is_dummy_) + { + error_check (rig_set_freq (rig_.data (), RIG_VFO_CURR, f)); + } + + update_rx_frequency (f); +} + +void HamlibTransceiver::do_tx_frequency (Frequency tx, bool rationalise_mode) +{ +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::do_tx_frequency:" << tx << "rationalise mode:" << rationalise_mode << "reversed:" << reversed_; +#endif + + if (!is_dummy_) + { + auto vfos = get_vfos (); + // auto rx_vfo = std::get<0> (vfos); + auto tx_vfo = std::get<1> (vfos); + + if (tx) + { +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::do_tx_frequency rig_set_split_freq"; +#endif + + hamlib_tx_vfo_fixup fixup (rig_.data (), tx_vfo); + error_check (rig_set_split_freq (rig_.data (), RIG_VFO_CURR, tx)); + + if (rationalise_mode) + { + rmode_t current_mode; + pbwidth_t current_width; + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::mode rig_get_split_mode"; +#endif + auto new_mode = map_mode (state ().mode ()); + error_check (rig_get_split_mode (rig_.data (), RIG_VFO_CURR, ¤t_mode, ¤t_width)); + + if (new_mode != current_mode) + { +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::do_tx_frequency rig_set_split_mode"; +#endif + + error_check (rig_set_split_mode (rig_.data (), RIG_VFO_CURR, new_mode, rig_passband_wide (rig_.data (), new_mode))); + } + } + } + + // enable split last since some rigs (Kenwood for one) come out + // of split when you switch RX VFO (to set split mode above for + // example) + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::do_tx_frequency rig_set_split_vfo"; +#endif + + error_check (rig_set_split_vfo (rig_.data (), RIG_VFO_CURR, tx ? RIG_SPLIT_ON : RIG_SPLIT_OFF, tx_vfo)); + } + + update_split (tx); + update_other_frequency (tx); +} + +void HamlibTransceiver::do_mode (MODE mode, bool rationalise) +{ +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::do_mode:" << mode << "rationalise:" << rationalise; +#endif + + if (!is_dummy_) + { + auto vfos = get_vfos (); + // auto rx_vfo = std::get<0> (vfos); + auto tx_vfo = std::get<1> (vfos); + + rmode_t current_mode; + pbwidth_t current_width; + +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::mode rig_get_mode"; +#endif + error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, ¤t_mode, ¤t_width)); + + auto new_mode = map_mode (mode); + if (new_mode != current_mode) + { +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::mode rig_set_mode"; +#endif + error_check (rig_set_mode (rig_.data (), RIG_VFO_CURR, new_mode, rig_passband_wide (rig_.data (), new_mode))); + } + + if (state ().split () && rationalise) + { +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::mode rig_get_split_mode"; +#endif + error_check (rig_get_split_mode (rig_.data (), RIG_VFO_CURR, ¤t_mode, ¤t_width)); + + if (new_mode != current_mode) + { +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::mode rig_set_split_mode"; +#endif + hamlib_tx_vfo_fixup fixup (rig_.data (), tx_vfo); + error_check (rig_set_split_mode (rig_.data (), RIG_VFO_CURR, new_mode, rig_passband_wide (rig_.data (), new_mode))); + } + } + } + + update_mode (mode); +} + +void HamlibTransceiver::poll () +{ + if (is_dummy_) + { + // split with dummy is never reported since there is no rig + if (state ().split ()) + { + update_split (false); + } + } + else + { +#if !WSJT_TRACE_CAT_POLLS +#if defined (NDEBUG) + rig_set_debug (RIG_DEBUG_ERR); +#else + rig_set_debug (RIG_DEBUG_VERBOSE); +#endif +#endif + + freq_t f; + rmode_t m; + pbwidth_t w; + split_t s; + + if (rig_->caps->get_vfo) + { + vfo_t v; + error_check (rig_get_vfo (rig_.data (), &v)); // has side effect of establishing current VFO inside hamlib + +#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS + qDebug ().nospace () << "HamlibTransceiver::state rig_get_vfo = 0x" << hex << v; +#endif + + reversed_ = RIG_VFO_B == v; + } + + error_check (rig_get_freq (rig_.data (), RIG_VFO_CURR, &f)); + +#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS + qDebug () << "HamlibTransceiver::state rig_get_freq =" << f; +#endif + + update_rx_frequency (f); + + if (rig_->caps->targetable_vfo & (RIG_TARGETABLE_FREQ | RIG_TARGETABLE_PURE)) + { + // we can only probe current VFO unless rig supports reading the other one directly + error_check (rig_get_freq (rig_.data () + , reversed_ + ? (rig_->state.vfo_list & RIG_VFO_A ? RIG_VFO_A : RIG_VFO_MAIN) + : (rig_->state.vfo_list & RIG_VFO_B ? RIG_VFO_B : RIG_VFO_SUB) + , &f)); + +#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS + qDebug () << "HamlibTransceiver::state rig_get_freq other =" << f; +#endif + + update_other_frequency (f); + } + + error_check (rig_get_mode (rig_.data (), RIG_VFO_CURR, &m, &w)); + +#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS + qDebug () << "HamlibTransceiver::state rig_get_mode =" << m << "bw =" << w; +#endif + + update_mode (map_mode (m)); + + vfo_t v {RIG_VFO_NONE}; // so we can tell if it doesn't get updated :( + auto rc = rig_get_split_vfo (rig_.data (), RIG_VFO_CURR, &s, &v); + if (RIG_OK == rc && RIG_SPLIT_ON == s) + { + +#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS + qDebug ().nospace () << "HamlibTransceiver::state rig_get_split_vfo split = " << s << " VFO = 0x" << hex << v; +#endif + + update_split (true); + // if (RIG_VFO_A == v) + // { + // reversed_ = true; // not sure if this helps us here + // } + } + else if (RIG_OK == rc) // not split + { +#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS + qDebug ().nospace () << "HamlibTransceiver::state rig_get_split_vfo split = " << s << " VFO = 0x" << hex << v; +#endif + + update_split (false); + } + else if (-RIG_ENAVAIL == rc) // Some rigs (Icom) don't have a way of reporting SPLIT mode + { +#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS + qDebug ().nospace () << "HamlibTransceiver::state rig_get_split_vfo can't do on this rig"; +#endif + + // just report how we see it based on prior commands + } + else + { + error_check (rc); + } + + if (RIG_PTT_NONE != rig_->state.pttport.type.ptt && rig_->caps->get_ptt) + { + ptt_t p; + error_check (rig_get_ptt (rig_.data (), RIG_VFO_CURR, &p)); + +#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS + qDebug () << "HamlibTransceiver::state rig_get_ptt =" << p; +#endif + + update_PTT (!(RIG_PTT_OFF == p)); + } + +#if !WSJT_TRACE_CAT_POLLS +#if WSJT_HAMLIB_TRACE + rig_set_debug (RIG_DEBUG_TRACE); +#elif defined (NDEBUG) + rig_set_debug (RIG_DEBUG_ERR); +#else + rig_set_debug (RIG_DEBUG_VERBOSE); +#endif +#endif + } +} + +void HamlibTransceiver::do_ptt (bool on) +{ +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::do_ptt:" << on << state () << "reversed =" << reversed_; +#endif + + if (on) + { + if (RIG_PTT_NONE != rig_->state.pttport.type.ptt) + { +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::ptt rig_set_ptt"; +#endif + + error_check (rig_set_ptt (rig_.data (), RIG_VFO_CURR, back_ptt_port_ ? RIG_PTT_ON_DATA : RIG_PTT_ON)); + } + } + else + { + if (RIG_PTT_NONE != rig_->state.pttport.type.ptt) + { +#if WSJT_TRACE_CAT + qDebug () << "HamlibTransceiver::ptt rig_set_ptt"; +#endif + + error_check (rig_set_ptt (rig_.data (), RIG_VFO_CURR, RIG_PTT_OFF)); + } + } + + update_PTT (on); +} + +void HamlibTransceiver::error_check (int ret_code) const +{ + if (RIG_OK != ret_code) + { +#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS + qDebug () << "HamlibTransceiver::error_check: error:" << rigerror (ret_code); +#endif + + throw error {QByteArray ("Hamlib error: ") + rigerror (ret_code)}; + } +} + +void HamlibTransceiver::set_conf (char const * item, char const * value) +{ + token_t token = rig_token_lookup (rig_.data (), item); + if (RIG_CONF_END != token) // only set if valid for rig model + { + error_check (rig_set_conf (rig_.data (), token, value)); + } +} + +QByteArray HamlibTransceiver::get_conf (char const * item) +{ + token_t token = rig_token_lookup (rig_.data (), item); + QByteArray value {128, '\0'}; + if (RIG_CONF_END != token) // only get if valid for rig model + { + error_check (rig_get_conf (rig_.data (), token, value.data ())); + } + return value; +} + +auto HamlibTransceiver::map_mode (rmode_t m) const -> MODE +{ + switch (m) + { + case RIG_MODE_AM: + case RIG_MODE_SAM: + case RIG_MODE_AMS: + case RIG_MODE_DSB: + return AM; + + case RIG_MODE_CW: + return CW; + + case RIG_MODE_CWR: + return CW_R; + + case RIG_MODE_USB: + case RIG_MODE_ECSSUSB: + case RIG_MODE_SAH: + case RIG_MODE_FAX: + return USB; + + case RIG_MODE_LSB: + case RIG_MODE_ECSSLSB: + case RIG_MODE_SAL: + return LSB; + + case RIG_MODE_RTTY: + return FSK; + + case RIG_MODE_RTTYR: + return FSK_R; + + case RIG_MODE_PKTLSB: + return DIG_L; + + case RIG_MODE_PKTUSB: + return DIG_U; + + case RIG_MODE_FM: + case RIG_MODE_WFM: + return FM; + + case RIG_MODE_PKTFM: + return DIG_FM; + + default: + return UNK; + } +} + +rmode_t HamlibTransceiver::map_mode (MODE mode) const +{ + switch (mode) + { + case AM: return RIG_MODE_AM; + case CW: return RIG_MODE_CW; + case CW_R: return RIG_MODE_CWR; + case USB: return RIG_MODE_USB; + case LSB: return RIG_MODE_LSB; + case FSK: return RIG_MODE_RTTY; + case FSK_R: return RIG_MODE_RTTYR; + case DIG_L: return RIG_MODE_PKTLSB; + case DIG_U: return RIG_MODE_PKTUSB; + case FM: return RIG_MODE_FM; + case DIG_FM: return RIG_MODE_PKTFM; + default: break; + } + return RIG_MODE_USB; // quieten compiler grumble +} diff --git a/HamlibTransceiver.hpp b/HamlibTransceiver.hpp new file mode 100644 index 000000000..57b3742c0 --- /dev/null +++ b/HamlibTransceiver.hpp @@ -0,0 +1,68 @@ +#ifndef HAMLIB_TRANSCEIVER_HPP_ +#define HAMLIB_TRANSCEIVER_HPP_ + +#include + +#include + +#include + +#include "TransceiverFactory.hpp" +#include "PollingTransceiver.hpp" + +extern "C" +{ + typedef struct rig RIG; + struct rig_caps; + typedef int vfo_t; +} + +// hamlib transceiver and PTT mostly delegated directly to hamlib Rig class +class HamlibTransceiver final + : public PollingTransceiver +{ + public: + static void register_transceivers (TransceiverFactory::Transceivers *); + + explicit HamlibTransceiver (int model_number + , QString const& cat_port + , int cat_baud + , TransceiverFactory::DataBits cat_data_bits + , TransceiverFactory::StopBits cat_stop_bits + , TransceiverFactory::Handshake cat_handshake + , bool cat_dtr_always_on + , bool cat_rts_always_on + , TransceiverFactory::PTTMethod ptt_type + , TransceiverFactory::TXAudioSource back_ptt_port + , QString const& ptt_port + , int poll_interval = 0); + ~HamlibTransceiver (); + + private: + void do_start () override; + void do_stop () override; + void do_frequency (Frequency) override; + void do_tx_frequency (Frequency, bool rationalise_mode) override; + void do_mode (MODE, bool rationalise) override; + void do_ptt (bool) override; + + void poll () override; + + void error_check (int ret_code) const; + void set_conf (char const * item, char const * value); + QByteArray get_conf (char const * item); + Transceiver::MODE map_mode (rmode_t) const; + rmode_t map_mode (Transceiver::MODE mode) const; + void init_rig (); + std::tuple get_vfos () const; + + struct RIGDeleter {static void cleanup (RIG *);}; + QScopedPointer rig_; + + bool back_ptt_port_; + bool is_dummy_; + + bool mutable reversed_; +}; + +#endif diff --git a/Info.plist.in b/Info.plist.in new file mode 100644 index 000000000..2ad1e0db0 --- /dev/null +++ b/Info.plist.in @@ -0,0 +1,10 @@ + + + + + CFBundleExecutable + @WSJTX_BUNDLE_NAME@ + CFBundleDisplayName + @WSJTX_BUNDLE_NAME@ + + diff --git a/LICENSE_WHEATLEY.TXT b/LICENSE_WHEATLEY.txt similarity index 98% rename from LICENSE_WHEATLEY.TXT rename to LICENSE_WHEATLEY.txt index 8b0ce2498..8adb5204b 100644 --- a/LICENSE_WHEATLEY.TXT +++ b/LICENSE_WHEATLEY.txt @@ -1,30 +1,30 @@ -+ + + This Software is released under the "Simplified BSD License" + + + -Copyright 2010 Moe Wheatley. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - -THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation -are those of the authors and should not be interpreted as representing -official policies, either expressed or implied, of Moe Wheatley. ++ + + This Software is released under the "Simplified BSD License" + + + +Copyright 2010 Moe Wheatley. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation +are those of the authors and should not be interpreted as representing +official policies, either expressed or implied, of Moe Wheatley. diff --git a/LiveFrequencyValidator.cpp b/LiveFrequencyValidator.cpp new file mode 100644 index 000000000..f234122c1 --- /dev/null +++ b/LiveFrequencyValidator.cpp @@ -0,0 +1,92 @@ +#include "LiveFrequencyValidator.hpp" + +#include +#include +#include +#include + +#include "Bands.hpp" +#include "FrequencyList.hpp" + +#include "moc_LiveFrequencyValidator.cpp" + +LiveFrequencyValidator::LiveFrequencyValidator (QComboBox * combo_box + , Bands const * bands + , FrequencyList const * frequencies + , QWidget * parent) + : QRegExpValidator { + QRegExp { // frequency in MHz or band + bands->data (QModelIndex {}).toString () // out of band string + + QString {R"(|((\d{0,6}(\)"} // up to 6 digits + + QLocale {}.decimalPoint () // (followed by decimal separator + + R"(\d{0,2})?)([Mm]{1,2}|([Cc][Mm])))|(\d{0,4}(\)" // followed by up to 2 digits and either 'm' or 'cm' or 'mm' (case insensitive)) + + QLocale {}.decimalPoint () // or a decimal separator + + R"(\d{0,6})?))" // followed by up to 6 digits + } + , parent + } + , bands_ {bands} + , frequencies_ {frequencies} + , combo_box_ {combo_box} +{ +} + +auto LiveFrequencyValidator::validate (QString& input, int& pos) const -> State +{ + auto state = QRegExpValidator::validate (input, pos); + + // by never being Acceptable we force fixup calls on ENTER or + // losing focus + return Acceptable == state ? Intermediate : state; +} + +void LiveFrequencyValidator::fixup (QString& input) const +{ + QRegExpValidator::fixup (input); + + auto out_of_band = bands_->data (QModelIndex {}).toString (); + + if (!out_of_band.startsWith (input)) + { + if (input.contains ('m', Qt::CaseInsensitive)) + { + input = input.toLower (); + + QVector frequencies; + for (int r = 0; r < frequencies_->rowCount (); ++r) + { + auto frequency = frequencies_->index (r, 0).data (); + auto band_index = bands_->find (frequency); + if (band_index.data ().toString () == input) + { + frequencies << frequency; + } + } + if (!frequencies.isEmpty ()) + { + Q_EMIT valid (frequencies.first ().value ()); + } + else + { + input = QString {}; + } + } + else + { + // frequency input + auto f = Radio::frequency (input, 6); + input = bands_->data (bands_->find (f)).toString (); + Q_EMIT valid (f); + } + + if (out_of_band == input) + { + combo_box_->lineEdit ()->setStyleSheet ("QLineEdit {color: yellow; background-color : red;}"); + } + else + { + combo_box_->lineEdit ()->setStyleSheet ({}); + } + combo_box_->setCurrentText (input); + } +} diff --git a/LiveFrequencyValidator.hpp b/LiveFrequencyValidator.hpp new file mode 100644 index 000000000..d4d6cc696 --- /dev/null +++ b/LiveFrequencyValidator.hpp @@ -0,0 +1,52 @@ +#ifndef LIVE_FREQUENCY_VALIDATOR_HPP__ +#define LIVE_FREQUENCY_VALIDATOR_HPP__ + +#include +#include + +#include "Radio.hpp" + +class Bands; +class FrequencyList; +class QComboBox; +class QWidget; + +// +// Class LiveFrequencyValidator +// +// QLineEdit validator that controls input to an editable +// QComboBox where the user can enter a valid band or a valid +// frequency in megahetz. +// +// Collabrations +// +// Implements the QRegExpValidator interface. Validates input +// from the supplied QComboBox as either a valid frequency in +// megahertz or a valid band as defined by the supplied column of +// the supplied QAbstractItemModel. +// +class LiveFrequencyValidator final + : public QRegExpValidator +{ + Q_OBJECT; + +public: + using Frequency = Radio::Frequency; + + LiveFrequencyValidator (QComboBox * combo_box // associated combo box + , Bands const * bands // bands model + , FrequencyList const * frequencies // working frequencies model + , QWidget * parent = nullptr); + + State validate (QString& input, int& pos) const override; + void fixup (QString& input) const override; + + Q_SIGNAL void valid (Frequency) const; + +private: + Bands const * bands_; + FrequencyList const * frequencies_; + QComboBox * combo_box_; +}; + +#endif diff --git a/Mac-wsjtx-startup.sh b/Mac-wsjtx-startup.sh new file mode 100644 index 000000000..b3939102a --- /dev/null +++ b/Mac-wsjtx-startup.sh @@ -0,0 +1,16 @@ +#!/bin/sh +WSJTX_BUNDLE="`echo "$0" | sed -e 's/\/Contents\/MacOS\/.*//'`" +WSJTX_RESOURCES="$WSJTX_BUNDLE/Contents/Resources" +WSJTX_TEMP="/tmp/wsjtx/$UID" + +echo "running $0" +echo "WSJTX_BUNDLE: $WSJTX_BUNDLE" + +# Setup temporary runtime files +rm -rf "$WSJTX_TEMP" + +export "DYLD_LIBRARY_PATH=$WSJTX_RESOURCES/lib" +export "PATH=$WSJTX_RESOURCES/bin:$PATH" + +#export +exec "$WSJTX_RESOURCES/bin/wsjtx" \ No newline at end of file diff --git a/Modulator.cpp b/Modulator.cpp index 0708d3e28..927882023 100644 --- a/Modulator.cpp +++ b/Modulator.cpp @@ -5,7 +5,10 @@ #include #include "mainwindow.h" -extern float gran(); // Noise generator (for tests only) +#include "moc_Modulator.cpp" + +extern float gran(); // Noise generator (for tests only) + #define RAMP_INCREMENT 64 // MUST be an integral factor of 2^16 #if defined (WSJT_SOFT_KEYING) diff --git a/Modulator.hpp b/Modulator.hpp index ba6b8edc2..d6ec3a5a9 100644 --- a/Modulator.hpp +++ b/Modulator.hpp @@ -57,7 +57,7 @@ private: Q_SLOT void tune (bool newState = true) {m_tuning = newState;} Q_SLOT void mute (bool newState = true) {m_muted = newState;} Q_SLOT void setFrequency (unsigned newFrequency) {m_frequency = newFrequency;} - Q_SIGNAL void stateChanged (ModulatorState); + Q_SIGNAL void stateChanged (ModulatorState) const; private: qint16 postProcessSample (qint16 sample) const; diff --git a/NetworkServerLookup.cpp b/NetworkServerLookup.cpp new file mode 100644 index 000000000..be872e715 --- /dev/null +++ b/NetworkServerLookup.cpp @@ -0,0 +1,83 @@ +#include "NetworkServerLookup.hpp" + +#include + +#include +#include + +std::tuple +network_server_lookup (QString const& query + , quint16 default_service_port + , QHostAddress default_host_address + , QAbstractSocket::NetworkLayerProtocol required_protocol) +{ + QHostAddress host_address {default_host_address}; + quint16 service_port {default_service_port}; + + QString host_name; + if (!query.isEmpty ()) + { + int port_colon_index {-1}; + + if ('[' == query[0]) + { + // assume IPv6 combined address/port syntax [
]: + auto close_bracket_index = query.lastIndexOf (']'); + host_name = query.mid (1, close_bracket_index - 1); + port_colon_index = query.indexOf (':', close_bracket_index); + } + else + { + port_colon_index = query.lastIndexOf (':'); + host_name = query.left (port_colon_index); + } + + if (port_colon_index >= 0) + { + bool ok; + service_port = query.mid (port_colon_index + 1).toUShort (&ok); + if (!ok) + { + throw std::runtime_error {"network server lookup error: invalid port"}; + } + } + } + + if (!host_name.isEmpty ()) + { + auto host_info = QHostInfo::fromName (host_name); + if (host_info.addresses ().isEmpty ()) + { + throw std::runtime_error {"network server lookup error: host name lookup failed"}; + } + + bool found {false}; + for (int i {0}; i < host_info.addresses ().size () && !found; ++i) + { + host_address = host_info.addresses ().at (i); + switch (required_protocol) + { + case QAbstractSocket::IPv4Protocol: + case QAbstractSocket::IPv6Protocol: + if (required_protocol != host_address.protocol ()) + { + break; + } + // drop through + + case QAbstractSocket::AnyIPProtocol: + found = true; + break; + + default: + throw std::runtime_error {"network server lookup error: invalid required protocol"}; + } + } + if (!found) + { + throw std::runtime_error {"network server lookup error: no suitable host address found"}; + } + } + + return std::make_tuple (host_address, service_port); +} diff --git a/NetworkServerLookup.hpp b/NetworkServerLookup.hpp new file mode 100644 index 000000000..1f21ad591 --- /dev/null +++ b/NetworkServerLookup.hpp @@ -0,0 +1,38 @@ +#ifndef NETWORK_SERVER_LOOKUP_HPP__ +#define NETWORK_SERVER_LOOKUP_HPP__ + +#include + +#include +#include + +class QString; + +// +// Do a blocking DNS lookup using query as a destination host address +// and port. +// +// query can be one of: +// +// 1) "" (empty string) - use defaults +// 2) ":nnnnn" - override default service port with port nnnnn +// 3) "" - override default host address with DNS lookup +// 4) "nnn.nnn.nnn.nnn" - override default host address with the IPv4 address given by nnn.nnn.nnn.nnn +// 5) "[:nnnnn" - use as per (3) & (2) +// 7) "nnn.nnn.nnn.nnn:nnnnn" - use as per (4) & (2) +// 8) "[ +network_server_lookup (QString const& query + , quint16 default_service_port + , QHostAddress default_host_address = QHostAddress::LocalHost + , QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::AnyIPProtocol); + +#endif diff --git a/OmniRigTransceiver.cpp b/OmniRigTransceiver.cpp new file mode 100644 index 000000000..5d81db23a --- /dev/null +++ b/OmniRigTransceiver.cpp @@ -0,0 +1,741 @@ +#include "OmniRigTransceiver.hpp" + +#include +#include + +#include + +#include "moc_OmniRigTransceiver.cpp" + +namespace +{ + auto constexpr OmniRig_transceiver_one_name = "OmniRig Rig 1"; + auto constexpr OmniRig_transceiver_two_name = "OmniRig Rig 2"; +} + +auto OmniRigTransceiver::map_mode (OmniRig::RigParamX param) -> MODE +{ + if (param & OmniRig::PM_CW_U) + { + return CW_R; + } + else if (param & OmniRig::PM_CW_L) + { + return CW; + } + else if (param & OmniRig::PM_SSB_U) + { + return USB; + } + else if (param & OmniRig::PM_SSB_L) + { + return LSB; + } + else if (param & OmniRig::PM_DIG_U) + { + return DIG_U; + } + else if (param & OmniRig::PM_DIG_L) + { + return DIG_L; + } + else if (param & OmniRig::PM_AM) + { + return AM; + } + else if (param & OmniRig::PM_FM) + { + return FM; + } + +#if WSJT_TRACE_CAT + qDebug () << "OmniRig map_mode unrecognized mode"; +#endif + + throw error {"OmniRig: unrecognized mode"}; +} + +OmniRig::RigParamX OmniRigTransceiver::map_mode (MODE mode) +{ + switch (mode) + { + case AM: return OmniRig::PM_AM; + case CW: return OmniRig::PM_CW_L; + case CW_R: return OmniRig::PM_CW_U; + case USB: return OmniRig::PM_SSB_U; + case LSB: return OmniRig::PM_SSB_L; + case FSK: return OmniRig::PM_DIG_L; + case FSK_R: return OmniRig::PM_DIG_U; + case DIG_L: return OmniRig::PM_DIG_L; + case DIG_U: return OmniRig::PM_DIG_U; + case FM: return OmniRig::PM_FM; + case DIG_FM: return OmniRig::PM_FM; + default: break; + } + return OmniRig::PM_SSB_U; // quieten compiler grumble +} + +void OmniRigTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id1, int id2) +{ + (*registry)[OmniRig_transceiver_one_name] = TransceiverFactory::Capabilities { + id1 + , TransceiverFactory::Capabilities::none // COM isn't serial or network + , true // does PTT + , false // doesn't select mic/data (use OmniRig config file) + , true // can remote control RTS nd DTR + , true // asynchronous interface + }; + (*registry)[OmniRig_transceiver_two_name] = TransceiverFactory::Capabilities { + id2 + , TransceiverFactory::Capabilities::none // COM isn't serial or network + , true // does PTT + , false // doesn't select mic/data (use OmniRig config file) + , true // can remote control RTS nd DTR + , true // asynchronous interface + }; +} + +OmniRigTransceiver::OmniRigTransceiver (std::unique_ptr wrapped, RigNumber n, TransceiverFactory::PTTMethod ptt_type, QString const& ptt_port) + : wrapped_ {std::move (wrapped)} + , use_for_ptt_ {TransceiverFactory::PTT_method_CAT == ptt_type || ("CAT" == ptt_port && (TransceiverFactory::PTT_method_RTS == ptt_type || TransceiverFactory::PTT_method_DTR == ptt_type))} + , ptt_type_ {ptt_type} + , startup_poll_countdown_ {2} + , rig_number_ {n} + , readable_params_ {0} + , writable_params_ {0} + , send_update_signal_ {false} + , reversed_ {false} + , starting_ {false} +{ +} + +OmniRigTransceiver::~OmniRigTransceiver () +{ +} + +void OmniRigTransceiver::do_start () +{ +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_start"; +#endif + + wrapped_->start (); + + CoInitializeEx (nullptr, 0 /*COINIT_APARTMENTTHREADED*/); // required because Qt only does this for GUI thread + + omni_rig_.reset (new OmniRig::OmniRigX {this}); + if (omni_rig_->isNull ()) + { +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_start: failed to start COM server"; +#endif + + throw error {"Failed to start OmniRig COM server"}; + } + + // COM/OLE exceptions get signalled + connect (&*omni_rig_, SIGNAL (exception (int, QString, QString, QString)), this, SLOT (handle_COM_exception (int, QString, QString, QString))); + + // IOmniRigXEvent interface signals + connect (&*omni_rig_, SIGNAL (VisibleChange ()), this, SLOT (handle_visible_change ())); + connect (&*omni_rig_, SIGNAL (RigTypeChange (int)), this, SLOT (handle_rig_type_change (int))); + connect (&*omni_rig_, SIGNAL (StatusChange (int)), this, SLOT (handle_status_change (int))); + connect (&*omni_rig_, SIGNAL (ParamsChange (int, int)), this, SLOT (handle_params_change (int, int))); + connect (&*omni_rig_ + , SIGNAL (CustomReply (int, QVariant const&, QVariant const&)) + , this, SLOT (handle_custom_reply (int, QVariant const&, QVariant const&))); + +#if WSJT_TRACE_CAT + qDebug () + << "OmniRig s/w version:" << QString::number (omni_rig_->SoftwareVersion ()).toLocal8Bit () + << "i/f version:" << QString::number (omni_rig_->InterfaceVersion ()).toLocal8Bit () + ; +#endif + + // fetch the interface of the RigX CoClass and instantiate a proxy object + switch (rig_number_) + { + case One: rig_.reset (new OmniRig::RigX (omni_rig_->Rig1 ())); break; + case Two: rig_.reset (new OmniRig::RigX (omni_rig_->Rig2 ())); break; + } + + Q_ASSERT (rig_); + Q_ASSERT (!rig_->isNull ()); + + if (use_for_ptt_ && (TransceiverFactory::PTT_method_DTR == ptt_type_ || TransceiverFactory::PTT_method_RTS == ptt_type_)) + { + // fetch the interface for the serial port if we need it for PTT + port_.reset (new OmniRig::PortBits (rig_->PortBits ())); + + Q_ASSERT (port_); + Q_ASSERT (!port_->isNull ()); + + // if (!port_->Lock ()) // try to take exclusive use of the OmniRig serial port for PTT + // { + // throw error {("Failed to get exclusive use of " + ptt_type + " from OmniRig").toLocal8Bit ()}; + // } + + // start off so we don't accidentally key the radio + if (TransceiverFactory::PTT_method_DTR == ptt_type_) + { + port_->SetDtr (false); + } + else // RTS + { + port_->SetRts (false); + } + } + + readable_params_ = rig_->ReadableParams (); + writable_params_ = rig_->WriteableParams (); + +#if WSJT_TRACE_CAT + qDebug () + << QString ("OmniRig initial rig type: %1 readable params = 0x%2 writable params = 0x%3 for rig %4") + .arg (rig_->RigType ()) + .arg (readable_params_, 8, 16, QChar ('0')) + .arg (writable_params_, 8, 16, QChar ('0')) + .arg (rig_number_).toLocal8Bit () + ; +#endif + + starting_ = true; + QTimer::singleShot (5000, this, SLOT (startup_check ())); +} + +void OmniRigTransceiver::do_stop () +{ + if (port_) + { + // port_->Unlock (); // release serial port + port_->clear (); + } + + rig_->clear (); + + omni_rig_->clear (); + + CoUninitialize (); + + wrapped_->stop (); + +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_stop"; +#endif +} + +void OmniRigTransceiver::startup_check () +{ + if (starting_) + { + if (--startup_poll_countdown_) + { + init_rig (); + QTimer::singleShot (5000, this, SLOT (startup_check ())); + } + else + { + startup_poll_countdown_ = 2; + + // signal that we haven't seen anything from OmniRig + offline ("OmniRig initialisation timeout"); + } + } +} + +void OmniRigTransceiver::init_rig () +{ + if (writable_params_ & OmniRig::PM_VFOA) + { +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::init_rig: set VFO A"; +#endif + + rig_->SetVfo (OmniRig::PM_VFOA); + if (writable_params_ & OmniRig::PM_SPLITOFF) + { +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::init_rig: set SPLIT off"; +#endif + + rig_->SetSplit (OmniRig::PM_SPLITOFF); + } + } + else if (writable_params_ & OmniRig::PM_VFOAA) + { +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::init_rig: set VFO A and SPLIT off"; +#endif + + rig_->SetVfo (OmniRig::PM_VFOAA); + } + + reversed_ = false; +} + +void OmniRigTransceiver::do_sync (bool force_signal) +{ + // nothing much we can do here, we just have to let OmniRig do its + // stuff and its first poll should send us and update that will + // trigger a update signal from us. Any attempt to query OmniRig + // leads to a whole mess of trouble since its internal state is + // garbage until it has done its first rig poll. + send_update_signal_ = force_signal; +} + +void OmniRigTransceiver::handle_COM_exception (int code, QString source, QString desc, QString help) +{ +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::handle_COM_exception:" << QString::number (code) + " at " + source + ": " + desc + " (" + help + ')'; +#endif + + throw error {("OmniRig COM/OLE error: " + QString::number (code) + " at " + source + ": " + desc + " (" + help + ')').toLocal8Bit ()}; +} + +void OmniRigTransceiver::handle_visible_change () +{ +#if WSJT_TRACE_CAT + qDebug () << "OmniRig visibility change: visibility =" << omni_rig_->DialogVisible (); +#endif +} + +void OmniRigTransceiver::handle_rig_type_change (int rig_number) +{ + if (rig_number_ == rig_number) + { + readable_params_ = rig_->ReadableParams (); + writable_params_ = rig_->WriteableParams (); + +#if WSJT_TRACE_CAT + qDebug () + << QString ("OmniRig rig type change to: %1 readable params = 0x%2 writable params = 0x%3 for rig %4") + .arg (rig_->RigType ()) + .arg (readable_params_, 8, 16, QChar ('0')) + .arg (writable_params_, 8, 16, QChar ('0')) + .arg (rig_number).toLocal8Bit () + ; +#endif + + offline ("OmniRig rig changed"); + } +} + +void OmniRigTransceiver::handle_status_change (int rig_number) +{ + if (rig_number_ == rig_number) + { +#if WSJT_TRACE_CAT + qDebug () + << QString ("OmniRig status change: new status for rig %1 =").arg (rig_number).toLocal8Bit () << rig_->StatusStr ().toLocal8Bit (); +#endif + + if (OmniRig::ST_ONLINE != rig_->Status ()) + { + offline ("OmniRig rig went offline"); + } + else + { + starting_ = false; + + TransceiverState old_state {state ()}; + init_rig (); + + if (old_state != state () || send_update_signal_) + { + update_complete (); + send_update_signal_ = false; + } + } + } +} + +void OmniRigTransceiver::handle_params_change (int rig_number, int params) +{ + if (rig_number_ == rig_number) + { +#if WSJT_TRACE_CAT + qDebug () + << QString ("OmniRig params change: params = 0x%1 for rig %2") + .arg (params, 8, 16, QChar ('0')) + .arg (rig_number).toLocal8Bit () + << "state before:" << state () + ; +#endif + + starting_ = false; + + TransceiverState old_state {state ()}; + auto need_frequency = false; + + // state_.online = true; // sometimes we don't get an initial + // // OmniRig::ST_ONLINE status change + // // event + + if (params & OmniRig::PM_VFOAA) + { + update_split (false); + reversed_ = false; + update_rx_frequency (rig_->FreqA ()); + update_other_frequency (rig_->FreqB ()); + } + if (params & OmniRig::PM_VFOAB) + { + update_split (true); + reversed_ = false; + update_rx_frequency (rig_->FreqA ()); + update_other_frequency (rig_->FreqB ()); + } + if (params & OmniRig::PM_VFOBA) + { + update_split (true); + reversed_ = true; + update_other_frequency (rig_->FreqA ()); + update_rx_frequency (rig_->FreqB ()); + } + if (params & OmniRig::PM_VFOBB) + { + update_split (false); + reversed_ = true; + update_other_frequency (rig_->FreqA ()); + update_rx_frequency (rig_->FreqB ()); + } + if (params & OmniRig::PM_VFOA) + { + reversed_ = false; + need_frequency = true; + } + if (params & OmniRig::PM_VFOB) + { + reversed_ = true; + need_frequency = true; + } + + if (params & OmniRig::PM_FREQ) + { + need_frequency = true; + } + if (params & OmniRig::PM_FREQA) + { + if (reversed_) + { + update_other_frequency (rig_->FreqA ()); + } + else + { + update_rx_frequency (rig_->FreqA ()); + } + } + if (params & OmniRig::PM_FREQB) + { + if (reversed_) + { + update_rx_frequency (rig_->FreqB ()); + } + else + { + update_other_frequency (rig_->FreqB ()); + } + } + + if (need_frequency) + { + if (readable_params_ & OmniRig::PM_FREQA) + { + if (reversed_) + { + update_other_frequency (rig_->FreqA ()); + } + else + { + update_rx_frequency (rig_->FreqA ()); + } + need_frequency = false; + } + if (readable_params_ & OmniRig::PM_FREQB) + { + if (reversed_) + { + update_rx_frequency (rig_->FreqB ()); + } + else + { + update_other_frequency (rig_->FreqB ()); + } + } + } + if (need_frequency && (readable_params_ & OmniRig::PM_FREQ)) + { + update_rx_frequency (rig_->Freq ()); + } + + if (params & OmniRig::PM_PITCH) + { + } + if (params & OmniRig::PM_RITOFFSET) + { + } + if (params & OmniRig::PM_RIT0) + { + } + if (params & OmniRig::PM_VFOEQUAL) + { + auto f = readable_params_ & OmniRig::PM_FREQA ? rig_->FreqA () : rig_->Freq (); + update_rx_frequency (f); + update_other_frequency (f); + update_mode (map_mode (rig_->Mode ())); + } + if (params & OmniRig::PM_VFOSWAP) + { + auto temp = state ().tx_frequency (); + update_other_frequency (state ().frequency ()); + update_rx_frequency (temp); + update_mode (map_mode (rig_->Mode ())); + } + if (params & OmniRig::PM_SPLITON) + { + update_split (true); + } + if (params & OmniRig::PM_SPLITOFF) + { + update_split (false); + } + if (params & OmniRig::PM_RITON) + { + } + if (params & OmniRig::PM_RITOFF) + { + } + if (params & OmniRig::PM_XITON) + { + } + if (params & OmniRig::PM_XITOFF) + { + } + if (params & OmniRig::PM_RX) + { + update_PTT (false); + } + if (params & OmniRig::PM_TX) + { + update_PTT (); + } + if (params & OmniRig::PM_CW_U) + { + update_mode (CW_R); + } + if (params & OmniRig::PM_CW_L) + { + update_mode (CW); + } + if (params & OmniRig::PM_SSB_U) + { + update_mode (USB); + } + if (params & OmniRig::PM_SSB_L) + { + update_mode (LSB); + } + if (params & OmniRig::PM_DIG_U) + { + update_mode (DIG_U); + } + if (params & OmniRig::PM_DIG_L) + { + update_mode (DIG_L); + } + if (params & OmniRig::PM_AM) + { + update_mode (AM); + } + if (params & OmniRig::PM_FM) + { + update_mode (FM); + } + + if (old_state != state () || send_update_signal_) + { + update_complete (); + send_update_signal_ = false; + } + +#if WSJT_TRACE_CAT + qDebug () + << "OmniRig params change: state after:" << state () + ; +#endif + } +} + +void OmniRigTransceiver::handle_custom_reply (int rig_number, QVariant const& command, QVariant const& reply) +{ + (void)command; + (void)reply; + + if (rig_number_ == rig_number) + { +#if WSJT_TRACE_CAT + qDebug () + << "OmniRig custom command" << command.toString ().toLocal8Bit () + << "with reply" << reply.toString ().toLocal8Bit () + << QString ("for rig %1").arg (rig_number).toLocal8Bit () + ; + + qDebug () << "OmniRig rig number:" << rig_number_ << ':' << state (); +#endif + } +} + +void OmniRigTransceiver::do_ptt (bool on) +{ +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_ptt:" << on << state (); +#endif + + if (use_for_ptt_ && TransceiverFactory::PTT_method_CAT == ptt_type_) + { +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_ptt: set PTT"; +#endif + + rig_->SetTx (on ? OmniRig::PM_TX : OmniRig::PM_RX); + } + else + { + if (port_) + { + if (TransceiverFactory::PTT_method_RTS == ptt_type_) + { +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_ptt: set RTS"; +#endif + port_->SetRts (on); + } + else // "DTR" + { +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_ptt: set DTR"; +#endif + + port_->SetDtr (on); + } + } + else + { +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_ptt: set PTT using basic transceiver"; +#endif + + wrapped_->ptt (on); + } + + if (state ().ptt () != on) + { + update_PTT (on); + update_complete (); + } + } +} + +void OmniRigTransceiver::do_frequency (Frequency f) +{ +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_frequency:" << f << state (); +#endif + + if (OmniRig::PM_FREQ & writable_params_) + { + rig_->SetFreq (f); + update_rx_frequency (f); + } + else if (reversed_ && (OmniRig::PM_FREQB & writable_params_)) + { + rig_->SetFreqB (f); + update_rx_frequency (f); + } + else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_)) + { + rig_->SetFreqA (f); + update_rx_frequency (f); + } + else + { + throw error {"OmniRig: don't know how to set rig frequency"}; + } +} + +void OmniRigTransceiver::do_tx_frequency (Frequency tx, bool /* rationalise_mode */) +{ +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_tx_frequency:" << tx << state (); +#endif + + bool split {tx != 0}; + + if (split) + { +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_tx_frequency: set SPLIT mode on"; +#endif + + rig_->SetSplitMode (state ().frequency (), tx); + update_other_frequency (tx); + update_split (true); + } + else + { +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_tx_frequency: set SPLIT mode off"; +#endif + + rig_->SetSimplexMode (state ().frequency ()); + update_split (false); + } + + bool notify {false}; + + if (readable_params_ & OmniRig::PM_FREQ || !(readable_params_ & (OmniRig::PM_FREQA | OmniRig::PM_FREQB))) + { + update_other_frequency (tx); // async updates won't return this + // so just store it and hope + // operator doesn't change the + // "back" VFO on rig + notify = true; + } + + if (!((OmniRig::PM_VFOAB | OmniRig::PM_VFOBA | OmniRig::PM_SPLITON) & readable_params_)) + { +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_tx_frequency: setting SPLIT manually"; +#endif + + update_split (split); // we can't read it so just set and + // hope op doesn't change it + notify = true; + } + + if (notify) + { + update_complete (); + } +} + +void OmniRigTransceiver::do_mode (MODE mode, bool /* rationalise */) +{ +#if WSJT_TRACE_CAT + qDebug () << "OmniRigTransceiver::do_mode:" << mode << state (); +#endif + + // TODO: G4WJS OmniRig doesn't seem to have any capability of tracking/setting VFO B mode + + auto mapped = map_mode (mode); + + if (mapped & writable_params_) + { + rig_->SetMode (mapped); + update_mode (mode); + } + else + { + offline ("OmniRig invalid mode"); + } +} diff --git a/OmniRigTransceiver.hpp b/OmniRigTransceiver.hpp new file mode 100644 index 000000000..b875165fa --- /dev/null +++ b/OmniRigTransceiver.hpp @@ -0,0 +1,73 @@ +#ifndef OMNI_RIG_TRANSCEIVER_HPP__ +#define OMNI_RIG_TRANSCEIVER_HPP__ + +#include + +#include +#include + +#include "TransceiverFactory.hpp" +#include "TransceiverBase.hpp" + +#include "OmniRig.h" + +// +// OmniRig Transceiver Interface +// +// Implemented as a Transceiver decorator because we may want the PTT +// services of another Transceiver type such as the HamlibTransceiver +// which can be enabled by wrapping a HamlibTransceiver instantiated +// as a "Hamlib Dummy" transceiver in the Transceiver factory method. +// +class OmniRigTransceiver final + : public TransceiverBase +{ + Q_OBJECT; + +public: + static void register_transceivers (TransceiverFactory::Transceivers *, int id1, int id2); + + enum RigNumber {One = 1, Two}; + + // takes ownership of wrapped Transceiver + explicit OmniRigTransceiver (std::unique_ptr wrapped, RigNumber, TransceiverFactory::PTTMethod ptt_type, QString const& ptt_port); + ~OmniRigTransceiver (); + + void do_start () override; + void do_stop () override; + void do_frequency (Frequency) override; + void do_tx_frequency (Frequency, bool rationalise_mode) override; + void do_mode (MODE, bool rationalise) override; + void do_ptt (bool on) override; + void do_sync (bool force_signal) override; + +private: + Q_SLOT void startup_check (); + Q_SLOT void handle_COM_exception (int, QString, QString, QString); + Q_SLOT void handle_visible_change (); + Q_SLOT void handle_rig_type_change (int rig_number); + Q_SLOT void handle_status_change (int rig_number); + Q_SLOT void handle_params_change (int rig_number, int params); + Q_SLOT void handle_custom_reply (int, QVariant const& command, QVariant const& reply); + + void init_rig (); + + static MODE map_mode (OmniRig::RigParamX param); + static OmniRig::RigParamX map_mode (MODE mode); + + std::unique_ptr wrapped_; + bool use_for_ptt_; + TransceiverFactory::PTTMethod ptt_type_; + unsigned startup_poll_countdown_; + QScopedPointer omni_rig_; + RigNumber rig_number_; + QScopedPointer rig_; + QScopedPointer port_; + int readable_params_; + int writable_params_; + bool send_update_signal_; + bool reversed_; // some rigs can reverse VFOs + bool starting_; +}; + +#endif diff --git a/PollingTransceiver.cpp b/PollingTransceiver.cpp new file mode 100644 index 000000000..51073704a --- /dev/null +++ b/PollingTransceiver.cpp @@ -0,0 +1,209 @@ +#include "PollingTransceiver.hpp" + +#include + +#include +#include +#include + +#include "pimpl_impl.hpp" + +namespace +{ + unsigned const polls_to_stabilize {3}; +} + +// Internal implementation of the PollingTransceiver class. +class PollingTransceiver::impl final + : public QObject +{ + Q_OBJECT; + +private: + Q_DISABLE_COPY (impl); + +public: + impl (PollingTransceiver * self, int poll_interval) + : QObject {self} + , self_ {self} + , interval_ {poll_interval} + , poll_timer_ {nullptr} + , retries_ {0} + { + } + +private: + void start_timer () + { + if (interval_) + { + if (!poll_timer_) + { + poll_timer_ = new QTimer {this}; // pass ownership to QObject which handles destruction for us + + connect (poll_timer_, &QTimer::timeout, this, &PollingTransceiver::impl::handle_timeout); + } + poll_timer_->start (interval_); + } + } + + void stop_timer () + { + if (poll_timer_) + { + poll_timer_->stop (); + } + } + + Q_SLOT void handle_timeout (); + + PollingTransceiver * self_; // our owner so we can call methods + int interval_; // polling interval in milliseconds + QTimer * poll_timer_; + + // keep a record of the last state signalled so we can elide + // duplicate updates + Transceiver::TransceiverState last_signalled_state_; + + // keep a record of expected state so we can compare with actual + // updates to determine when state changes have bubbled through + Transceiver::TransceiverState next_state_; + + unsigned retries_; // number of incorrect polls left + + friend class PollingTransceiver; +}; + +#include "PollingTransceiver.moc" + + +PollingTransceiver::PollingTransceiver (int poll_interval) + : m_ {this, poll_interval} +{ +} + +PollingTransceiver::~PollingTransceiver () +{ +} + +void PollingTransceiver::do_post_start () +{ + m_->start_timer (); + if (!m_->next_state_.online ()) + { + // remember that we are expecting to go online + m_->next_state_.online (true); + m_->retries_ = polls_to_stabilize; + } +} + +void PollingTransceiver::do_post_stop () +{ + // not much point waiting for rig to go offline since we are ceasing + // polls + m_->stop_timer (); +} + +void PollingTransceiver::do_post_frequency (Frequency f) +{ + if (m_->next_state_.frequency () != f) + { + // update expected state with new frequency and set poll count + m_->next_state_.frequency (f); + m_->retries_ = polls_to_stabilize; + } +} + +void PollingTransceiver::do_post_tx_frequency (Frequency f) +{ + if (m_->next_state_.tx_frequency () != f) + { + // update expected state with new TX frequency and set poll + // count + m_->next_state_.tx_frequency (f); + m_->next_state_.split (f); // setting non-zero TX frequency means split + m_->retries_ = polls_to_stabilize; + } +} + +void PollingTransceiver::do_post_mode (MODE m) +{ + if (m_->next_state_.mode () != m) + { + // update expected state with new mode and set poll count + m_->next_state_.mode (m); + m_->retries_ = polls_to_stabilize; + } +} + +bool PollingTransceiver::do_pre_update () +{ + // if we are holding off a change then withhold the signal + if (m_->retries_ && state () != m_->next_state_) + { + return false; + } + return true; +} + +void PollingTransceiver::do_sync (bool force_signal) +{ + poll (); // tell sub-classes to update our + // state + + // Signal new state if it is directly requested or, what we expected + // or, hasn't become what we expected after polls_to_stabilize + // polls. Unsolicited changes will be signalled immediately unless + // they intervene in a expected sequence where they will be delayed. + if (m_->retries_) + { + --m_->retries_; + if (force_signal || state () == m_->next_state_ || !m_->retries_) + { + // our client wants a signal regardless + // or the expected state has arrived + // or there are no more retries + force_signal = true; + } + } + else if (force_signal || state () != m_->last_signalled_state_) + { + // here is the normal passive polling path + // either our client has requested a state update regardless of change + // or sate has changed asynchronously + force_signal = true; + } + + if (force_signal) + { + // reset everything, record and signal the current state + m_->retries_ = 0; + m_->next_state_ = state (); + m_->last_signalled_state_ = state (); + update_complete (); + } +} + +void PollingTransceiver::impl::handle_timeout () +{ + QString message; + + // we must catch all exceptions here since we are called by Qt and + // inform our parent of the failure via the offline() message + try + { + self_->do_sync (false); + } + catch (std::exception const& e) + { + message = e.what (); + } + catch (...) + { + message = "Unexpected rig error"; + } + if (!message.isEmpty ()) + { + self_->offline (message); + } +} diff --git a/PollingTransceiver.hpp b/PollingTransceiver.hpp new file mode 100644 index 000000000..8353eb303 --- /dev/null +++ b/PollingTransceiver.hpp @@ -0,0 +1,60 @@ +#ifndef POLLING_TRANSCEIVER_HPP__ +#define POLLING_TRANSCEIVER_HPP__ + +#include + +#include "TransceiverBase.hpp" + +#include "pimpl_h.hpp" + +// +// Polling Transceiver +// +// Helper base class that encapsulates the emulation of continuous +// update and caching of a transceiver state. +// +// Collaborations +// +// Implements the TransceiverBase post action interface and provides +// the abstract poll() operation for sub-classes to implement. The +// pol operation is invoked every poll_interval milliseconds. +// +// Responsibilities +// +// Because some rig interfaces don't immediately update after a state +// change request; this class allows a rig a few polls to stabilise +// to the requested state before signalling the change. This means +// that clients don't see intermediate states that are sometimes +// inaccurate, e.g. changing the split TX frequency on Icom rigs +// requires a VFO switch and polls while switched will return the +// wrong current frequency. +// +class PollingTransceiver + : public TransceiverBase +{ +protected: + explicit PollingTransceiver (int poll_interval); // in milliseconds + +public: + ~PollingTransceiver (); + +protected: + void do_sync (bool /* force_signal */) override final; + + // Sub-classes implement this and fetch what they can from the rig + // in a non-intrusive manner. + virtual void poll () = 0; + + void do_post_start () override final; + void do_post_stop () override final; + void do_post_frequency (Frequency) override final; + void do_post_tx_frequency (Frequency) override final; + void do_post_mode (MODE) override final; + bool do_pre_update () override final; + +private: + class impl; + pimpl m_; +}; + +#endif diff --git a/Radio.cpp b/Radio.cpp new file mode 100644 index 000000000..d93df3983 --- /dev/null +++ b/Radio.cpp @@ -0,0 +1,72 @@ +#include "Radio.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +namespace Radio +{ + namespace + { + struct init + { + init () + { + qRegisterMetaType ("Frequency"); + + qRegisterMetaType ("Frequencies"); + qRegisterMetaTypeStreamOperators ("Frequencies"); + + qRegisterMetaType ("FrequencyDelta"); + } + } static_initaializer; + + double constexpr MHz_factor {1.e6}; + int constexpr frequency_precsion {6}; + } + + + Frequency frequency (QVariant const& v, int scale) + { + return std::llround (v.toDouble () * std::pow (10., scale)); + } + + FrequencyDelta frequency_delta (QVariant const& v, int scale) + { + return std::llround (v.toDouble () * std::pow (10., scale)); + } + + + QString frequency_MHz_string (Frequency f, QLocale const& locale) + { + return locale.toString (f / MHz_factor, 'f', frequency_precsion); + } + + QString frequency_MHz_string (FrequencyDelta d, QLocale const& locale) + { + return locale.toString (d / MHz_factor, 'f', frequency_precsion); + } + + QString pretty_frequency_MHz_string (Frequency f, QLocale const& locale) + { + auto f_string = locale.toString (f / MHz_factor, 'f', frequency_precsion); + return f_string.insert (f_string.size () - 3, QChar::Nbsp); + } + + QString pretty_frequency_MHz_string (double f, int scale, QLocale const& locale) + { + auto f_string = locale.toString (f / std::pow (10., scale - 6), 'f', frequency_precsion); + return f_string.insert (f_string.size () - 3, QChar::Nbsp); + } + + QString pretty_frequency_MHz_string (FrequencyDelta d, QLocale const& locale) + { + auto d_string = locale.toString (d / MHz_factor, 'f', frequency_precsion); + return d_string.insert (d_string.size () - 3, QChar::Nbsp); + } +} diff --git a/Radio.hpp b/Radio.hpp new file mode 100644 index 000000000..c9e279244 --- /dev/null +++ b/Radio.hpp @@ -0,0 +1,47 @@ +#ifndef RADIO_HPP_ +#define RADIO_HPP_ + +#include +#include +#include + +class QVariant; +class QString; + +// +// Declarations common to radio software. +// + +namespace Radio +{ + // + // Frequency types + // + using Frequency = quint64; + using Frequencies = QList; + using FrequencyDelta = qint64; + + // + // Frequency type conversion. + // + // QVariant argument is convertible to double and is assumed to + // be scaled by (10 ** -scale). + // + Frequency frequency (QVariant const&, int scale); + FrequencyDelta frequency_delta (QVariant const&, int scale); + + // + // Frequency type formatting + // + QString frequency_MHz_string (Frequency, QLocale const& = QLocale ()); + QString frequency_MHz_string (FrequencyDelta, QLocale const& = QLocale ()); + QString pretty_frequency_MHz_string (Frequency, QLocale const& = QLocale ()); + QString pretty_frequency_MHz_string (double, int scale, QLocale const& = QLocale ()); + QString pretty_frequency_MHz_string (FrequencyDelta, QLocale const& = QLocale ()); +} + +Q_DECLARE_METATYPE (Radio::Frequency); +Q_DECLARE_METATYPE (Radio::Frequencies); +Q_DECLARE_METATYPE (Radio::FrequencyDelta); + +#endif diff --git a/SettingsGroup.hpp b/SettingsGroup.hpp new file mode 100644 index 000000000..e728e6c30 --- /dev/null +++ b/SettingsGroup.hpp @@ -0,0 +1,34 @@ +#ifndef SETTINGS_GROUP_HPP_ +#define SETTINGS_GROUP_HPP_ + +#include +#include + +// +// Class SettingsGroup +// +// Simple RAII type class to apply a QSettings group witin a +// scope. +// +class SettingsGroup +{ +public: + SettingsGroup (QSettings * settings, QString const& group) + : settings_ {settings} + { + settings_->beginGroup (group); + } + + SettingsGroup (SettingsGroup const&) = delete; + SettingsGroup& operator = (SettingsGroup const&) = delete; + + ~SettingsGroup () + { + settings_->endGroup (); + } + +private: + QSettings * settings_; +}; + +#endif diff --git a/StationList.cpp b/StationList.cpp new file mode 100644 index 000000000..069dc0730 --- /dev/null +++ b/StationList.cpp @@ -0,0 +1,551 @@ +#include "StationList.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pimpl_impl.hpp" + +#include "Bands.hpp" + +namespace +{ + struct init + { + init () + { + qRegisterMetaType ("Station"); + qRegisterMetaTypeStreamOperators ("Station"); + qRegisterMetaType ("Stations"); + qRegisterMetaTypeStreamOperators ("Stations"); + } + } static_initializer; +} + +#if !defined (QT_NO_DEBUG_STREAM) +QDebug operator << (QDebug debug, StationList::Station const& station) +{ + debug.nospace () << "Station(" + << station.band_name_ << ", " + << station.offset_ << ", " + << station.antenna_description_ << ')'; + return debug.space (); +} +#endif + +QDataStream& operator << (QDataStream& os, StationList::Station const& station) +{ + return os << station.band_name_ + << station.offset_ + << station.antenna_description_; +} + +QDataStream& operator >> (QDataStream& is, StationList::Station& station) +{ + return is >> station.band_name_ + >> station.offset_ + >> station.antenna_description_; +} + + +class StationList::impl final + : public QAbstractTableModel +{ +public: + impl (Bands const * bands, Stations stations, QObject * parent) + : QAbstractTableModel {parent} + , bands_ {bands} + , stations_ {stations} + { + } + + Stations const& stations () const {return stations_;} + void assign (Stations); + QModelIndex add (Station); + FrequencyDelta offset (Frequency) const; + +protected: + // Implement the QAbstractTableModel interface. + int rowCount (QModelIndex const& parent = QModelIndex {}) const override; + int columnCount (QModelIndex const& parent = QModelIndex {}) const override; + Qt::ItemFlags flags (QModelIndex const& = QModelIndex {}) const override; + QVariant data (QModelIndex const&, int role) const override; + QVariant headerData (int section, Qt::Orientation, int = Qt::DisplayRole) const override; + bool setData (QModelIndex const&, QVariant const& value, int role = Qt::EditRole) override; + bool removeRows (int row, int count, QModelIndex const& parent = QModelIndex {}) override; + bool insertRows (int row, int count, QModelIndex const& parent = QModelIndex {}) override; + Qt::DropActions supportedDropActions () const override; + QStringList mimeTypes () const override; + QMimeData * mimeData (QModelIndexList const&) const override; + bool dropMimeData (QMimeData const *, Qt::DropAction, int row, int column, QModelIndex const& parent) override; + +private: + // Helper method for band validation. + QModelIndex first_matching_band (QString const& band_name) const + { + // find first exact match in bands + auto matches = bands_->match (bands_->index (0, 0) + , Qt::DisplayRole + , band_name + , 1 + , Qt::MatchExactly); + return matches.isEmpty () ? QModelIndex {} : matches.first (); + } + + static int constexpr num_columns {3}; + static auto constexpr mime_type = "application/wsjt.antenna-descriptions"; + + Bands const * bands_; + Stations stations_; +}; + +StationList::StationList (Bands const * bands, QObject * parent) + : StationList {bands, {}, parent} +{ +} + +StationList::StationList (Bands const * bands, Stations stations, QObject * parent) + : QSortFilterProxyModel {parent} + , m_ {bands, stations, parent} +{ + // setDynamicSortFilter (true); + setSourceModel (&*m_); + setSortRole (SortRole); +} + +StationList::~StationList () +{ +} + +StationList& StationList::operator = (Stations stations) +{ + m_->assign (stations); + return *this; +} + +auto StationList::stations () const -> Stations +{ + return m_->stations (); +} + +QModelIndex StationList::add (Station s) +{ + return mapFromSource (m_->add (s)); +} + +bool StationList::remove (Station s) +{ + auto row = m_->stations ().indexOf (s); + + if (0 > row) + { + return false; + } + + return removeRow (row); +} + +namespace +{ + bool row_is_higher (QModelIndex const& lhs, QModelIndex const& rhs) + { + return lhs.row () > rhs.row (); + } +} + +bool StationList::removeDisjointRows (QModelIndexList rows) +{ + bool result {true}; + + // We must work with source model indexes because we don't want row + // removes to invalidate model indexes we haven't yet processed. We + // achieve that by processing them in decending row order. + for (int r = 0; r < rows.size (); ++r) + { + rows[r] = mapToSource (rows[r]); + } + + // reverse sort by row + qSort (rows.begin (), rows.end (), row_is_higher); + Q_FOREACH (auto index, rows) + { + if (result && !m_->removeRow (index.row ())) + { + result = false; + } + } + + return result; +} + +auto StationList::offset (Frequency f) const -> FrequencyDelta +{ + return m_->offset (f); +} + + +void StationList::impl::assign (Stations stations) +{ + beginResetModel (); + std::swap (stations_, stations); + endResetModel (); +} + +QModelIndex StationList::impl::add (Station s) +{ + // Any band that isn't in the list may be added + if (!stations_.contains (s)) + { + auto row = stations_.size (); + + beginInsertRows (QModelIndex {}, row, row); + stations_.append (s); + endInsertRows (); + + return index (row, 0); + } + + return QModelIndex {}; +} + +auto StationList::impl::offset (Frequency f) const -> FrequencyDelta +{ + // Lookup band for frequency + auto band_index = bands_->find (f); + if (band_index.isValid ()) + { + auto band_name = band_index.data ().toString (); + + // Lookup station for band + for (int i = 0; i < stations ().size (); ++i) + { + if (stations_[i].band_name_ == band_name) + { + return stations_[i].offset_; + } + } + } + + return 0; // no offset +} + +int StationList::impl::rowCount (QModelIndex const& parent) const +{ + return parent.isValid () ? 0 : stations_.size (); +} + +int StationList::impl::columnCount (QModelIndex const& parent) const +{ + return parent.isValid () ? 0 : num_columns; +} + +Qt::ItemFlags StationList::impl::flags (QModelIndex const& index) const +{ + auto result = QAbstractTableModel::flags (index); + + auto row = index.row (); + auto column = index.column (); + + if (index.isValid () + && row < stations_.size () + && column < num_columns) + { + if (2 == column) + { + result |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + } + else + { + result |= Qt::ItemIsEditable | Qt::ItemIsDropEnabled; + } + } + else + { + result |= Qt::ItemIsDropEnabled; + } + + return result; +} + +QVariant StationList::impl::data (QModelIndex const& index, int role) const +{ + QVariant item; + + auto row = index.row (); + auto column = index.column (); + + if (index.isValid () + && row < stations_.size ()) + { + switch (column) + { + case 0: // band name + switch (role) + { + case SortRole: + { + // Lookup band. + auto band_index = first_matching_band (stations_.at (row).band_name_); + // Use the sort role value of the band. + item = band_index.data (Bands::SortRole); + } + break; + + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::AccessibleTextRole: + item = stations_.at (row).band_name_; + break; + + case Qt::ToolTipRole: + case Qt::AccessibleDescriptionRole: + item = tr ("Band name"); + break; + + case Qt::TextAlignmentRole: + item = Qt::AlignHCenter + Qt::AlignVCenter; + break; + } + break; + + case 1: // frequency offset + { + auto frequency_offset = stations_.at (row).offset_; + switch (role) + { + case Qt::AccessibleTextRole: + item = frequency_offset; + break; + + case SortRole: + case Qt::DisplayRole: + case Qt::EditRole: + item = frequency_offset; + break; + + case Qt::ToolTipRole: + case Qt::AccessibleDescriptionRole: + item = tr ("Frequency offset"); + break; + + case Qt::TextAlignmentRole: + item = Qt::AlignRight + Qt::AlignVCenter; + break; + } + } + break; + + case 2: // antenna description + switch (role) + { + case SortRole: + case Qt::EditRole: + case Qt::DisplayRole: + case Qt::AccessibleTextRole: + item = stations_.at (row).antenna_description_; + break; + + case Qt::ToolTipRole: + case Qt::AccessibleDescriptionRole: + item = tr ("Antenna description"); + break; + + case Qt::TextAlignmentRole: + item = Qt::AlignLeft + Qt::AlignVCenter; + break; + } + break; + } + } + + return item; +} + +QVariant StationList::impl::headerData (int section, Qt::Orientation orientation, int role) const +{ + QVariant header; + + if (Qt::DisplayRole == role && Qt::Horizontal == orientation) + { + switch (section) + { + case 0: header = tr ("Band"); break; + case 1: header = tr ("Offset"); break; + case 2: header = tr ("Antenna Description"); break; + } + } + else + { + header = QAbstractTableModel::headerData (section, orientation, role); + } + + return header; +} + +bool StationList::impl::setData (QModelIndex const& model_index, QVariant const& value, int role) +{ + bool changed {false}; + + auto row = model_index.row (); + auto size = stations_.size (); + if (model_index.isValid () + && Qt::EditRole == role + && row < size) + { + QVector roles; + roles << role; + + switch (model_index.column ()) + { + case 0: + { + // Check if band name is valid. + auto band_index = first_matching_band (value.toString ()); + if (band_index.isValid ()) + { + stations_[row].band_name_ = band_index.data ().toString (); + Q_EMIT dataChanged (model_index, model_index, roles); + changed = true; + } + } + break; + + case 1: + { + stations_[row].offset_ = value.value (); + Q_EMIT dataChanged (model_index, model_index, roles); + changed = true; + } + break; + + case 2: + stations_[row].antenna_description_ = value.toString (); + Q_EMIT dataChanged (model_index, model_index, roles); + changed = true; + break; + } + } + + return changed; +} + +bool StationList::impl::removeRows (int row, int count, QModelIndex const& parent) +{ + if (0 < count && (row + count) <= rowCount (parent)) + { + beginRemoveRows (parent, row, row + count - 1); + for (auto r = 0; r < count; ++r) + { + stations_.removeAt (row); + } + endRemoveRows (); + return true; + } + + return false; +} + +bool StationList::impl::insertRows (int row, int count, QModelIndex const& parent) +{ + if (0 < count) + { + beginInsertRows (parent, row, row + count - 1); + for (auto r = 0; r < count; ++r) + { + stations_.insert (row, Station ()); + } + endInsertRows (); + return true; + } + + return false; +} + +Qt::DropActions StationList::impl::supportedDropActions () const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +QStringList StationList::impl::mimeTypes () const +{ + QStringList types; + types << mime_type; + types << "application/wsjt.Frequencies"; + return types; +} + +QMimeData * StationList::impl::mimeData (QModelIndexList const& items) const +{ + QMimeData * mime_data = new QMimeData {}; + QByteArray encoded_data; + QDataStream stream {&encoded_data, QIODevice::WriteOnly}; + + Q_FOREACH (auto const& item, items) + { + if (item.isValid ()) + { + stream << QString {data (item, Qt::DisplayRole).toString ()}; + } + } + + mime_data->setData (mime_type, encoded_data); + return mime_data; +} + +bool StationList::impl::dropMimeData (QMimeData const * data, Qt::DropAction action, int /* row */, int /* column */, QModelIndex const& parent) +{ + if (Qt::IgnoreAction == action) + { + return true; + } + + if (parent.isValid () + && 2 == parent.column () + && data->hasFormat (mime_type)) + { + QByteArray encoded_data {data->data (mime_type)}; + QDataStream stream {&encoded_data, QIODevice::ReadOnly}; + auto dest_index = parent; + while (!stream.atEnd ()) + { + QString text; + stream >> text; + setData (dest_index, text); + dest_index = index (dest_index.row () + 1, dest_index.column (), QModelIndex {}); + } + return true; + } + else if (data->hasFormat ("application/wsjt.Frequencies")) + { + QByteArray encoded_data {data->data ("application/wsjt.Frequencies")}; + QDataStream stream {&encoded_data, QIODevice::ReadOnly}; + while (!stream.atEnd ()) + { + QString frequency_string; + stream >> frequency_string; + auto frequency = Radio::frequency (frequency_string, 0); + auto band_index = bands_->find (frequency); + if (stations_.cend () == std::find_if (stations_.cbegin () + , stations_.cend () + , [&band_index] (Station const& s) {return s.band_name_ == band_index.data ().toString ();})) + { + add (Station {band_index.data ().toString (), 0, QString {}}); + } + } + + return true; + } + + return false; +} diff --git a/StationList.hpp b/StationList.hpp new file mode 100644 index 000000000..389719629 --- /dev/null +++ b/StationList.hpp @@ -0,0 +1,96 @@ +#ifndef STATION_LIST_HPP__ +#define STATION_LIST_HPP__ + +#include +#include +#include + +#include "pimpl_h.hpp" + +#include "Radio.hpp" + +class Bands; + +// +// Class StationList +// +// Encapsulates information about a collection of unique operating +// stations per band. The implementation is a table model with the +// first column being the unique (within the table rows) band name +// and, the second the frequency offset for transverter usage and, +// the third the antenna description. All are editable. +// +// Responsibilities +// +// Stores internally an unordered table of bands. +// +// If an ordered representaion is required then wrapping with an +// appropriate proxy model is sufficient +// e.g. QSortFilterProxyModel. A custom SortRole role is provided for +// the band name column which returns a numeric value (Bands lower +// frequency limit) which gives a strict frequency ordering by band. +// +// Collaborations +// +// Implements the QAbstractTableModel interface for a grid of bands +// with offset frequencies and antenna descriptions. +// +// Uses the QAbstractItemModel interface of the bands model to lookup +// band information. +// +class StationList final + : public QSortFilterProxyModel +{ +public: + using Frequency = Radio::Frequency; + using FrequencyDelta = Radio::FrequencyDelta; + + // + // Struct Station + // + // Aggregation of fields that describe a radio station on a band. + // + struct Station + { + QString band_name_; + FrequencyDelta offset_; + QString antenna_description_; + }; + + using Stations = QList; + + explicit StationList (Bands const * bands, QObject * parent = nullptr); + explicit StationList (Bands const * bands, Stations, QObject * parent = nullptr); + ~StationList (); + + // Load and store contents. + StationList& operator = (Stations); + Stations stations () const; + + // + // Model API + // + QModelIndex add (Station); // Add a new Station + bool remove (Station); // Remove a Station + bool removeDisjointRows (QModelIndexList); // Remove one or more stations + FrequencyDelta offset (Frequency) const; // Return the offset to be used for a Frequency + + // Custom sort role. + static int constexpr SortRole = Qt::UserRole; + +private: + class impl; + pimpl m_; +}; + +// Station equivalence is based on band name alone. +inline +bool operator == (StationList::Station const& lhs, StationList::Station const& rhs) +{ + return lhs.band_name_ == rhs.band_name_; +} + +Q_DECLARE_METATYPE (StationList::Station); +Q_DECLARE_METATYPE (StationList::Stations); + +#endif diff --git a/TestConfiguration.cpp b/TestConfiguration.cpp new file mode 100644 index 000000000..da41cbd27 --- /dev/null +++ b/TestConfiguration.cpp @@ -0,0 +1,470 @@ +#include "TestConfiguration.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Bands.hpp" +#include "FrequencyList.hpp" +#include "Configuration.hpp" +#include "LiveFrequencyValidator.hpp" + +#include "pimpl_impl.hpp" + +#include "ui_TestConfiguration.h" + +namespace +{ + char const * const title = "Configuration Test v" WSJTX_STRINGIZE (CONFIG_TEST_VERSION_MAJOR) "." WSJTX_STRINGIZE (CONFIG_TEST_VERSION_MINOR) "." WSJTX_STRINGIZE (CONFIG_TEST_VERSION_PATCH) ", " WSJTX_STRINGIZE (SVNVERSION); + + // these undocumented flag values when stored in (Qt::UserRole - 1) + // of a ComboBox item model index allow the item to be enabled or + // disabled + int const combo_box_item_enabled (32 | 1); + int const combo_box_item_disabled (0); + + auto LCD_error = "-- Error --"; +} + +class status_bar_frequency final + : public QLabel +{ +public: + status_bar_frequency (QString const& prefix, QWidget * parent = 0) + : QLabel {parent} + , prefix_ {prefix} + { + } + + void setText (QString const& text) + { + QLabel::setText (prefix_ + " Frequency: " + text); + } + +private: + QString prefix_; +}; + +class TestConfiguration::impl final + : public QMainWindow +{ + Q_OBJECT; + +public: + using Frequency = Radio::Frequency; + + explicit impl (QString const& instance_key, QSettings *, QWidget * parent = nullptr); + + void closeEvent (QCloseEvent *) override; + + Q_SIGNAL void new_frequency (Frequency) const; + Q_SIGNAL void new_tx_frequency (Frequency = 0, bool rationalise_mode = true) const; + +private: + Q_SLOT void on_configuration_action_triggered (); + Q_SLOT void on_band_combo_box_activated (int); + Q_SLOT void on_mode_combo_box_activated (int); + Q_SLOT void on_PTT_push_button_clicked (bool); + Q_SLOT void on_split_push_button_clicked (bool); + Q_SLOT void on_TX_offset_spin_box_valueChanged (int); + Q_SLOT void on_sync_push_button_clicked (bool); + + Q_SLOT void handle_transceiver_update (Transceiver::TransceiverState); + Q_SLOT void handle_transceiver_failure (QString); + + void information_message_box (QString const& reason, QString const& detail); + void read_settings (); + void write_settings (); + void load_models (); + void sync_rig (); + void band_changed (Frequency); + void frequency_changed (Frequency); + + Ui::test_configuration_main_window * ui_; + QSettings * settings_; + QString callsign_; + Configuration configuration_dialog_; + bool updating_models_; // hold off UI reaction while adjusting models + bool band_edited_; + + Transceiver::TransceiverState rig_state_; + + Frequency desired_frequency_; + + status_bar_frequency RX_frequency_; + status_bar_frequency TX_frequency_; +}; + +#include "TestConfiguration.moc" + +TestConfiguration::TestConfiguration (QString const& instance_key, QSettings * settings, QWidget * parent) + : m_ {instance_key, settings, parent} +{ +} + +TestConfiguration::~TestConfiguration () +{ +} + +TestConfiguration::impl::impl (QString const& instance_key, QSettings * settings, QWidget * parent) + : QMainWindow {parent} + , ui_ {new Ui::test_configuration_main_window} + , settings_ {settings} + , configuration_dialog_ {instance_key, settings, this} + , updating_models_ {false} + , band_edited_ {false} + , desired_frequency_ {0u} + , RX_frequency_ {"RX"} + , TX_frequency_ {"TX"} +{ + ui_->setupUi (this); + + setWindowTitle (QApplication::applicationName () + " - " + title); + + // mode "Unknown" is display only + ui_->mode_combo_box->setItemData (ui_->mode_combo_box->findText ("Unknown"), combo_box_item_disabled, Qt::UserRole - 1); + + // setup status bar widgets + statusBar ()->insertPermanentWidget (0, &TX_frequency_); + statusBar ()->insertPermanentWidget (0, &RX_frequency_); + + // assign push button ids + ui_->TX_button_group->setId (ui_->vfo_0_TX_push_button, 0); + ui_->TX_button_group->setId (ui_->vfo_1_TX_push_button, 1); + + // enable live band combo box entry validation and action + auto band_validator = new LiveFrequencyValidator {ui_->band_combo_box + , configuration_dialog_.bands () + , configuration_dialog_.frequencies () + , this}; + ui_->band_combo_box->setValidator (band_validator); + connect (band_validator, &LiveFrequencyValidator::valid, this, &TestConfiguration::impl::band_changed); + connect (ui_->band_combo_box->lineEdit (), &QLineEdit::textEdited, [this] (QString const&) {band_edited_ = true;}); + + // hook up band data model + ui_->band_combo_box->setModel (configuration_dialog_.frequencies ()); + + // combo box drop downs are limited to the drop down selector width, + // this almost random increase improves the situation + ui_->band_combo_box->view ()->setMinimumWidth (ui_->band_combo_box->view ()->sizeHintForColumn (0) + 10); + + // hook up configuration signals + connect (&configuration_dialog_, &Configuration::transceiver_update, this, &TestConfiguration::impl::handle_transceiver_update); + connect (&configuration_dialog_, &Configuration::transceiver_failure, this, &TestConfiguration::impl::handle_transceiver_failure); + + // hook up configuration slots + connect (this, &TestConfiguration::impl::new_frequency, &configuration_dialog_, &Configuration::transceiver_frequency); + connect (this, &TestConfiguration::impl::new_tx_frequency, &configuration_dialog_, &Configuration::transceiver_tx_frequency); + + load_models (); + + read_settings (); + + show (); +} + +void TestConfiguration::impl::closeEvent (QCloseEvent * e) +{ + write_settings (); + + QMainWindow::closeEvent (e); +} + +void TestConfiguration::impl::on_configuration_action_triggered () +{ + qDebug () << "TestConfiguration::on_configuration_action_triggered"; + if (QDialog::Accepted == configuration_dialog_.exec ()) + { + qDebug () << "TestConfiguration::on_configuration_action_triggered: Configuration changed"; + qDebug () << "TestConfiguration::on_configuration_action_triggered: rig is" << configuration_dialog_.rig_name (); + + if (configuration_dialog_.restart_audio_input ()) + { + qDebug () << "Audio Device Changes - Configuration changes require an audio input device to be restarted"; + } + if (configuration_dialog_.restart_audio_output ()) + { + qDebug () << "Audio Device Changes - Configuration changes require an audio output device to be restarted"; + } + + load_models (); + } + else + { + qDebug () << "TestConfiguration::on_configuration_action_triggered: Confiugration changes cancelled"; + } +} + +void TestConfiguration::impl::on_band_combo_box_activated (int index) +{ + qDebug () << "TestConfiguration::on_band_combo_box_activated: " << ui_->band_combo_box->currentText (); + + auto model = configuration_dialog_.frequencies (); + auto value = model->data (model->index (index, 2), Qt::DisplayRole).toString (); + + if (configuration_dialog_.bands ()->data (QModelIndex {}).toString () == value) + { + ui_->band_combo_box->lineEdit ()->setStyleSheet ("QLineEdit {color: yellow; background-color : red;}"); + } + else + { + ui_->band_combo_box->lineEdit ()->setStyleSheet ({}); + } + + ui_->band_combo_box->setCurrentText (value); + + auto f = model->data (model->index (index, 0), Qt::UserRole + 1).value (); + + band_edited_ = true; + band_changed (f); +} + +void TestConfiguration::impl::band_changed (Frequency f) +{ + if (band_edited_) + { + band_edited_ = false; + frequency_changed (f); + sync_rig (); + } +} + +void TestConfiguration::impl::frequency_changed (Frequency f) +{ + desired_frequency_ = f; + + // lookup band + auto bands_model = configuration_dialog_.bands (); + ui_->band_combo_box->setCurrentText (bands_model->data (bands_model->find (f)).toString ()); +} + +void TestConfiguration::impl::on_sync_push_button_clicked (bool /* checked */) +{ + qDebug () << "TestConfiguration::on_sync_push_button_clicked"; + + auto model = configuration_dialog_.frequencies (); + auto model_index = model->index (ui_->band_combo_box->currentIndex (), 0); + desired_frequency_ = model->data (model_index, Qt::UserRole + 1).value (); + + sync_rig (); +} + +void TestConfiguration::impl::on_mode_combo_box_activated (int index) +{ + qDebug () << "TestConfiguration::on_vfo_A_mode_combo_box_activated: " << static_cast (index); + + // reset combo box back to current mode and let status update do the actual change + ui_->mode_combo_box->setCurrentIndex (rig_state_.mode ()); + + Q_EMIT configuration_dialog_.transceiver_mode (static_cast (index)); +} + +void TestConfiguration::impl::on_TX_offset_spin_box_valueChanged (int value) +{ + qDebug () << "TestConfiguration::on_TX_offset_spin_box_editingFinished: " << value; + + Q_EMIT new_tx_frequency (rig_state_.frequency () + value); +} + +void TestConfiguration::impl::on_PTT_push_button_clicked (bool checked) +{ + qDebug () << "TestConfiguration::on_PTT_push_button_clicked: " << (checked ? "true" : "false"); + + // reset button and let status update do the actual checking + ui_->PTT_push_button->setChecked (rig_state_.ptt ()); + + Q_EMIT configuration_dialog_.transceiver_ptt (checked); +} + +void TestConfiguration::impl::on_split_push_button_clicked (bool checked) +{ + qDebug () << "TestConfiguration::on_split_push_button_clicked: " << (checked ? "true" : "false"); + + // reset button and let status update do the actual checking + ui_->split_push_button->setChecked (rig_state_.split ()); + + if (checked) + { + Q_EMIT new_tx_frequency (rig_state_.frequency () + ui_->TX_offset_spin_box->value ()); + } + else + { + Q_EMIT new_tx_frequency (); + } +} + +void TestConfiguration::impl::sync_rig () +{ + if (!updating_models_) + { + if (configuration_dialog_.transceiver_online (true)) + { + if (configuration_dialog_.split_mode ()) + { + Q_EMIT new_frequency (desired_frequency_); + Q_EMIT new_tx_frequency (desired_frequency_ + ui_->TX_offset_spin_box->value ()); + } + else + { + Q_EMIT new_frequency (desired_frequency_); + Q_EMIT new_tx_frequency (); + } + } + } +} + +void TestConfiguration::impl::handle_transceiver_update (Transceiver::TransceiverState s) +{ + rig_state_ = s; + + auto model = configuration_dialog_.frequencies (); + bool valid {false}; + for (int row = 0; row < model->rowCount (); ++row) + { + auto working_frequency = model->data (model->index (row, 0), Qt::UserRole + 1).value (); + if (std::abs (static_cast (working_frequency - s.frequency ())) < 10000) + { + valid = true; + } + } + if (!valid) + { + ui_->vfo_0_lcd_number->setStyleSheet ("QLCDNumber {background-color: red;}"); + } + else + { + ui_->vfo_0_lcd_number->setStyleSheet (QString {}); + } + + ui_->vfo_0_lcd_number->display (Radio::pretty_frequency_MHz_string (s.frequency ())); + + if (s.split ()) + { + ui_->vfo_1_lcd_number->display (Radio::pretty_frequency_MHz_string (s.tx_frequency ())); + + valid = false; + for (int row = 0; row < model->rowCount (); ++row) + { + auto working_frequency = model->data (model->index (row, 0), Qt::UserRole + 1).value (); + if (std::abs (static_cast (working_frequency - s.tx_frequency ())) < 10000) + { + valid = true; + } + } + if (!valid) + { + ui_->vfo_1_lcd_number->setStyleSheet ("QLCDNumber {background-color: red;}"); + } + else + { + ui_->vfo_1_lcd_number->setStyleSheet (QString {}); + } + ui_->vfo_1_lcd_number->show (); + ui_->vfo_1_TX_push_button->show (); + } + else + { + ui_->vfo_1_lcd_number->hide (); + ui_->vfo_1_TX_push_button->hide (); + } + + frequency_changed (s.frequency ()); + + ui_->radio_widget->setEnabled (s.online ()); + + ui_->mode_combo_box->setCurrentIndex (s.mode ()); + + RX_frequency_.setText (Radio::pretty_frequency_MHz_string (s.frequency ())); + TX_frequency_.setText (Radio::pretty_frequency_MHz_string (s.split () ? s.tx_frequency () : s.frequency ())); + + ui_->TX_button_group->button (s.split ())->setChecked (true); + + ui_->PTT_push_button->setChecked (s.ptt ()); + + ui_->split_push_button->setChecked (s.split ()); + + ui_->radio_widget->setEnabled (s.online ()); +} + +void TestConfiguration::impl::handle_transceiver_failure (QString reason) +{ + ui_->radio_widget->setEnabled (false); + + ui_->vfo_0_lcd_number->display (LCD_error); + ui_->vfo_1_lcd_number->display (LCD_error); + information_message_box ("Rig failure", reason); +} + +void TestConfiguration::impl::read_settings () +{ + settings_->beginGroup ("TestConfiguration"); + resize (settings_->value ("window/size", size ()).toSize ()); + move (settings_->value ("window/pos", pos ()).toPoint ()); + restoreState (settings_->value ("window/state", saveState ()).toByteArray ()); + ui_->band_combo_box->setCurrentText (settings_->value ("Band").toString ()); + settings_->endGroup (); + + settings_->beginGroup ("Configuration"); + callsign_ = settings_->value ("MyCall").toString (); + settings_->endGroup (); +} + +void TestConfiguration::impl::write_settings () +{ + settings_->beginGroup ("TestConfiguration"); + settings_->setValue ("window/size", size ()); + settings_->setValue ("window/pos", pos ()); + settings_->setValue ("window/state", saveState ()); + settings_->setValue ("Band", ui_->band_combo_box->currentText ()); + settings_->endGroup (); +} + +void TestConfiguration::impl::load_models () +{ + updating_models_ = true; + + ui_->frequency_group_box->setTitle (configuration_dialog_.rig_name ()); + // if (auto rig = configuration_dialog_.rig (false)) // don't open radio + if (configuration_dialog_.transceiver_online (false)) // don't open radio + { + Q_EMIT configuration_dialog_.sync_transceiver (true); + } + else + { + ui_->radio_widget->setEnabled (false); + ui_->vfo_0_lcd_number->display (Radio::pretty_frequency_MHz_string (static_cast (0))); + ui_->vfo_1_lcd_number->display (Radio::pretty_frequency_MHz_string (static_cast (0))); + } + + if (!configuration_dialog_.split_mode ()) + { + ui_->vfo_1_lcd_number->hide (); + ui_->vfo_1_TX_push_button->hide (); + } + + updating_models_ = false; +} + +void TestConfiguration::impl::information_message_box (QString const& reason, QString const& detail) +{ + qDebug () << "TestConfiguration::information_message_box: reason =" << reason << "detail =" << detail; + QMessageBox mb; + mb.setWindowFlags (mb.windowFlags () | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + mb.setText (reason); + if (!detail.isEmpty ()) + { + mb.setDetailedText (detail); + } + mb.setStandardButtons (QMessageBox::Ok); + mb.setDefaultButton (QMessageBox::Ok); + mb.setIcon (QMessageBox::Information); + mb.exec (); +} diff --git a/TestConfiguration.hpp b/TestConfiguration.hpp new file mode 100644 index 000000000..428afef6f --- /dev/null +++ b/TestConfiguration.hpp @@ -0,0 +1,21 @@ +#ifndef TEST_CONFIGURATION_HPP_ +#define TEST_CONFIGURATION_HPP_ + +#include "pimpl_h.hpp" + +class QString; +class QSettings; +class QWidget; + +class TestConfiguration final +{ + public: + explicit TestConfiguration (QString const& instance_key, QSettings *, QWidget * parent = nullptr); + ~TestConfiguration (); + + private: + class impl; + pimpl m_; +}; + +#endif diff --git a/TestConfiguration.ui b/TestConfiguration.ui new file mode 100644 index 000000000..89c206f47 --- /dev/null +++ b/TestConfiguration.ui @@ -0,0 +1,627 @@ + + + test_configuration_main_window + + + + 0 + 0 + 550 + 227 + + + + + 0 + 0 + + + + + 550 + 227 + + + + Configuration Test + + + + + + + true + + + + 1 + 0 + + + + + + + true + + + + 1 + 0 + + + + Frequency + + + + + + + 1 + 0 + + + + + 11 + + + + 12 + + + + + + + + 1 + 0 + + + + + 11 + + + + 12 + + + + + + + false + + + + 0 + 0 + + + + + 20 + 20 + + + + + 20 + 20 + + + + QPushButton { + border: 2px solid #8f8f91; + border-radius: 6px; + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #f6f7fa, stop: 1 #dadbde); + min-width: 16px; +} + +QPushButton:checked { + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #ff2a2e, stop: 1 #ffc6ca); +} + +QPushButton:flat { + border: none; /* no border for a flat push button */ +} + +QPushButton:default { + border-color: navy; /* make the default button prominent */ +} + + + + + + true + + + true + + + true + + + TX_button_group + + + + + + + false + + + + 0 + 0 + + + + + 20 + 20 + + + + + 20 + 20 + + + + QPushButton { + border: 2px solid #8f8f91; + border-radius: 6px; + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #f6f7fa, stop: 1 #dadbde); + min-width: 16px; +} + +QPushButton:checked { + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #ff2a2e, stop: 1 #ffc6ca); +} + +QPushButton:flat { + border: none; /* no border for a flat push button */ +} + +QPushButton:default { + border-color: navy; /* make the default button prominent */ +} + + + + + + true + + + false + + + true + + + TX_button_group + + + + + + + + + + + 0 + 0 + + + + Mode + + + + + + + 0 + 0 + + + + 0 + + + + Unknown + + + + + CW + + + + + CW_R + + + + + USB + + + + + LSB + + + + + FSK + + + + + FSK_R + + + + + DIG_U + + + + + DIG_L + + + + + AM + + + + + FM + + + + + DIG_FM + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QPushButton { + border: 2px solid #8f8f91; + border-radius: 6px; + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #9aff9a, stop: 1 #2aff2e); + min-width: 80px; +} + +QPushButton:checked { + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #ff2b2e, stop: 1 #ff979a); +} + +QPushButton:flat { + border: none; /* no border for a flat push button */ +} + +QPushButton:default { + border-color: navy; /* make the default button prominent */ +} + + + PTT + + + true + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QPushButton { + border: 2px solid #8f8f91; + border-radius: 6px; + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #9aff9a, stop: 1 #2aff2e); + min-width: 80px; +} + +QPushButton:checked { + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #ff2b2e, stop: 1 #ff979a); +} + +QPushButton:flat { + border: none; /* no border for a flat push button */ +} + +QPushButton:default { + border-color: navy; /* make the default button prominent */ +} + + + Split + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + + + + Split Offset: + + + TX_offset_spin_box + + + + + + + + 0 + 0 + + + + Hz + + + -10000 + + + 10000 + + + 100 + + + + + + + + + + + + + + + + Band: + + + band_combo_box + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + true + + + QComboBox::NoInsert + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Sync + + + + + + + + + + + 0 + 0 + 550 + 20 + + + + + &File + + + + + + &Options + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + E&xit + + + + + &Configuration + + + + + + + exit_action + triggered() + test_configuration_main_window + close() + + + -1 + -1 + + + 261 + 214 + + + + + + + + diff --git a/TraceFile.cpp b/TraceFile.cpp new file mode 100644 index 000000000..022a020d7 --- /dev/null +++ b/TraceFile.cpp @@ -0,0 +1,118 @@ +#include "TraceFile.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pimpl_impl.hpp" + +namespace +{ + QMutex lock; +} + +class TraceFile::impl +{ +public: + impl (QString const& trace_file_path); + ~impl (); + + // no copying + impl (impl const&) = delete; + impl& operator = (impl const&) = delete; + +private: + // write Qt messages to the diagnostic log file + static void message_handler (QtMsgType type, QMessageLogContext const& context, QString const& msg); + + QFile file_; + QTextStream stream_; + QTextStream * original_stream_; + QtMessageHandler original_handler_; + static QTextStream * current_stream_; +}; + +QTextStream * TraceFile::impl::current_stream_; + + +// delegate to implementation class +TraceFile::TraceFile (QString const& trace_file_path) + : m_ {trace_file_path} +{ +} + +TraceFile::~TraceFile () +{ +} + + +TraceFile::impl::impl (QString const& trace_file_path) + : file_ {trace_file_path} + , original_stream_ {current_stream_} + , original_handler_ {nullptr} +{ + // if the log file is writeable; initialise diagnostic logging to it + // for append and hook up the Qt global message handler + if (file_.open (QFile::WriteOnly | QFile::Append | QFile::Text)) + { + stream_.setDevice (&file_); + current_stream_ = &stream_; + original_handler_ = qInstallMessageHandler (message_handler); + } +} + +TraceFile::impl::~impl () +{ + // unhook our message handler before the stream and file are destroyed + if (original_handler_) + { + qInstallMessageHandler (original_handler_); + } + current_stream_ = original_stream_; // revert to prior stream +} + +// write Qt messages to the diagnostic log file +void TraceFile::impl::message_handler (QtMsgType type, QMessageLogContext const& context, QString const& msg) +{ + char const * severity; + switch (type) + { + case QtDebugMsg: + severity = "Debug"; + break; + + case QtWarningMsg: + severity = "Warning"; + break; + + case QtFatalMsg: + severity = "Fatal"; + break; + + default: + severity = "Critical"; + break; + } + + { + // guard against multiple threads with overlapping messages + QMutexLocker guard (&lock); + Q_ASSERT_X (current_stream_, "TraceFile:message_handler", "no stream to write to"); + *current_stream_ + << QDateTime::currentDateTimeUtc ().toString () + << '(' << context.file << ':' << context.line /* << ", " << context.function */ << ')' + << severity << ": " << msg.trimmed () << endl; + } + + if (QtFatalMsg == type) + { + throw std::runtime_error {"Fatal Qt Error"}; + } +} diff --git a/TraceFile.hpp b/TraceFile.hpp new file mode 100644 index 000000000..6a27b842f --- /dev/null +++ b/TraceFile.hpp @@ -0,0 +1,23 @@ +#ifndef TRACE_FILE_HPP_ +#define TRACE_FILE_HPP_ + +#include "pimpl_h.hpp" + +class QString; + +class TraceFile final +{ +public: + explicit TraceFile (QString const& TraceFile_file_path); + ~TraceFile (); + + // copying not allowed + TraceFile (TraceFile const&) = delete; + TraceFile& operator = (TraceFile const&) = delete; + +private: + class impl; + pimpl m_; +}; + +#endif diff --git a/Transceiver.cpp b/Transceiver.cpp new file mode 100644 index 000000000..2665a2783 --- /dev/null +++ b/Transceiver.cpp @@ -0,0 +1,51 @@ +#include "Transceiver.hpp" + +#include "moc_Transceiver.cpp" + +namespace +{ + struct init + { + init () + { + qRegisterMetaType ("Transceiver::TransceiverState"); + qRegisterMetaType ("Transceiver::MODE"); + } + } static_initialization; +} + +#if !defined (QT_NO_DEBUG_STREAM) + +ENUM_QDEBUG_OPS_IMPL (Transceiver, MODE); + +QDebug operator << (QDebug d, Transceiver::TransceiverState const& s) +{ + d.nospace () + << "Transceiver::TransceiverState(online: " << (s.online_ ? "yes" : "no") + << " Frequency {" << s.frequency_[0] << "Hz, " << s.frequency_[1] << "Hz} " << s.mode_ + << "; SPLIT: " << (Transceiver::TransceiverState::on == s.split_ ? "on" : Transceiver::TransceiverState::off == s.split_ ? "off" : "unknown") + << "; PTT: " << (s.ptt_ ? "on" : "off") + << ')'; + return d.space (); +} + +#endif + +ENUM_QDATASTREAM_OPS_IMPL (Transceiver, MODE); + +ENUM_CONVERSION_OPS_IMPL (Transceiver, MODE); + +bool operator != (Transceiver::TransceiverState const& lhs, Transceiver::TransceiverState const& rhs) +{ + return lhs.online_ != rhs.online_ + || lhs.frequency_[0] != rhs.frequency_[0] + || lhs.frequency_[1] != rhs.frequency_[1] + || lhs.mode_ != rhs.mode_ + || lhs.split_ != rhs.split_ + || lhs.ptt_ != rhs.ptt_; +} + +bool operator == (Transceiver::TransceiverState const& lhs, Transceiver::TransceiverState const& rhs) +{ + return !(lhs != rhs); +} diff --git a/Transceiver.hpp b/Transceiver.hpp new file mode 100644 index 000000000..b30150552 --- /dev/null +++ b/Transceiver.hpp @@ -0,0 +1,169 @@ +#ifndef TRANSCEIVER_HPP__ +#define TRANSCEIVER_HPP__ + +#include + +#include "qt_helpers.hpp" +#include "Radio.hpp" + +class QString; + +// +// Abstract Transceiver Interface +// +// This is the minimal generic interface to a rig as required by +// wsjtx. +// +// Responsibilities +// +// Provides Qt slots to set the frequency, mode and PTT of some +// transceiver. They are Qt slots so that they may be invoked across +// a thread boundary. +// +// Provides a synchronisation Qt slot which should be implemented in +// sub-classes in such a way that normal operation of the rig is not +// disturbed. This is intended to be use to poll rig state +// periodically and changing VFO to read the other VFO frequency or +// mode for example should not be done since the operator may be +// tuning the VFO at the time and would be surprised by an unprompted +// VFO change. +// +// Provides a control interface using Qt slots to start and stop the +// rig control and PTT connections. +// +// These are Qt slots rather than the constructor and destructor +// because it is expected that the concrete Transceiver +// implementations will run in a separate thread from where they are +// constructed. +// +// Qt signals are defined to notify clients of asynchronous rig state +// changes and failures. These can and are expected to cross thread +// boundaries. +// +// A signal finished() is defined that concrete Transceiver +// implementations must emit when they are ripe for destruction. This +// is intended to be used by clients that move the Transceiver +// instance to a thread and need to use QObject::deleteLater() to +// safely dispose of the Transceiver instance. Implementations should +// expect Qt slot calls after emitting finished, it is up to the +// implementation whether these slot invocations are ignored. +// +class Transceiver + : public QObject +{ + Q_OBJECT; + Q_ENUMS (MODE); + +public: + using Frequency = Radio::Frequency; + +protected: + Transceiver () + { + } + +public: + virtual ~Transceiver () + { + } + + enum MODE {UNK, CW, CW_R, USB, LSB, FSK, FSK_R, DIG_U, DIG_L, AM, FM, DIG_FM}; + + // + // Aggregation of all of the rig and PTT state accessible via this + // interface. + class TransceiverState + { + public: + TransceiverState () + : online_ {false} + , frequency_ {0, 0} + , mode_ {UNK} + , split_ {unknown} + , ptt_ {false} + { + } + + bool online () const {return online_;} + Frequency frequency () const {return frequency_[0];} + Frequency tx_frequency () const {return frequency_[1];} + bool split () const {return on == split_;} + MODE mode () const {return mode_;} + bool ptt () const {return ptt_;} + + void online (bool state) {online_ = state;} + void frequency (Frequency f) {frequency_[0] = f;} + void tx_frequency (Frequency f) {frequency_[1] = f;} + void split (bool state) {split_ = state ? on : off;} + void mode (MODE m) {mode_ = m;} + void ptt (bool state) {ptt_ = state;} + + private: + bool online_; + Frequency frequency_[2]; // [0] -> Rx; [1] -> Other + MODE mode_; + enum {unknown, off, on} split_; + bool ptt_; + // Don't forget to update the debug print and != operator if you + // add more members here + + friend QDebug operator << (QDebug, TransceiverState const&); + friend bool operator != (TransceiverState const&, TransceiverState const&); + }; + + // + // The following slots and signals are expected to all run in the + // same thread which is not necessarily the main GUI thread. It is + // up to the client of the Transceiver class to organise the + // allocation to a thread and the lifetime of the object instances. + // + + // Connect and disconnect. + Q_SLOT virtual void start () noexcept = 0; + Q_SLOT virtual void stop () noexcept = 0; + + // Ready to be destroyed. + Q_SIGNAL void finished () const; + + // Set frequency in Hertz. + Q_SLOT virtual void frequency (Frequency) noexcept = 0; + + // Setting a non-zero TX frequency means split operation, the value + // zero means simplex operation. + // + // Rationalise_mode means ensure TX uses same mode as RX. + Q_SLOT virtual void tx_frequency (Frequency tx = 0, bool rationalise_mode = true) noexcept = 0; + + // Set mode. + // Rationalise means ensure TX uses same mode as RX. + Q_SLOT virtual void mode (MODE, bool rationalise = true) noexcept = 0; + + // Set/unset PTT. + Q_SLOT virtual void ptt (bool = true) noexcept = 0; + + // Attempt to re-synchronise or query state. + // Force_signal guarantees a update or failure signal. + Q_SLOT virtual void sync (bool force_signal = false) noexcept = 0; + + // asynchronous status updates + Q_SIGNAL void update (Transceiver::TransceiverState) const; + Q_SIGNAL void failure (QString reason) const; +}; + +Q_DECLARE_METATYPE (Transceiver::TransceiverState); +Q_DECLARE_METATYPE (Transceiver::MODE); + +#if !defined (QT_NO_DEBUG_STREAM) +ENUM_QDEBUG_OPS_DECL (Transceiver, MODE); + +QDebug operator << (QDebug, Transceiver::TransceiverState const&); +#endif + +ENUM_QDATASTREAM_OPS_DECL (Transceiver, MODE); + +ENUM_CONVERSION_OPS_DECL (Transceiver, MODE); + +bool operator != (Transceiver::TransceiverState const&, Transceiver::TransceiverState const&); +bool operator == (Transceiver::TransceiverState const&, Transceiver::TransceiverState const&); + +#endif diff --git a/TransceiverBase.cpp b/TransceiverBase.cpp new file mode 100644 index 000000000..6e664bb55 --- /dev/null +++ b/TransceiverBase.cpp @@ -0,0 +1,269 @@ +#include "TransceiverBase.hpp" + +#include + +#include + +#include "pimpl_impl.hpp" + +class TransceiverBase::impl final +{ +public: + impl () + { + } + + impl (impl const&) = delete; + impl& operator = (impl const&) = delete; + + TransceiverState state_; +}; + + +TransceiverBase::TransceiverBase () +{ +} + +TransceiverBase::~TransceiverBase () +{ +} + +void TransceiverBase::start () noexcept +{ + QString message; + try + { + if (m_->state_.online ()) + { + do_stop (); + do_post_stop (); + m_->state_.online (false); + } + do_start (); + do_post_start (); + m_->state_.online (true); + } + catch (std::exception const& e) + { + message = e.what (); + } + catch (...) + { + message = "Unexpected rig error"; + } + if (!message.isEmpty ()) + { + offline (message); + } +} + +void TransceiverBase::stop () noexcept +{ + QString message; + try + { + do_stop (); + do_post_stop (); + m_->state_.online (false); + } + catch (std::exception const& e) + { + message = e.what (); + } + catch (...) + { + message = "Unexpected rig error"; + } + if (!message.isEmpty ()) + { + offline (message); + } + else + { + Q_EMIT finished (); + } +} + +void TransceiverBase::frequency (Frequency f) noexcept +{ + QString message; + try + { + if (m_->state_.online ()) + { + do_frequency (f); + do_post_frequency (f); + } + } + catch (std::exception const& e) + { + message = e.what (); + } + catch (...) + { + message = "Unexpected rig error"; + } + if (!message.isEmpty ()) + { + offline (message); + } +} + +void TransceiverBase::tx_frequency (Frequency tx, bool rationalise_mode) noexcept +{ + QString message; + try + { + if (m_->state_.online ()) + { + do_tx_frequency (tx, rationalise_mode); + do_post_tx_frequency (tx); + } + } + catch (std::exception const& e) + { + message = e.what (); + } + catch (...) + { + message = "Unexpected rig error"; + } + if (!message.isEmpty ()) + { + offline (message); + } +} + +void TransceiverBase::mode (MODE m, bool rationalise) noexcept +{ + QString message; + try + { + if (m_->state_.online ()) + { + do_mode (m, rationalise); + do_post_mode (m); + } + } + catch (std::exception const& e) + { + message = e.what (); + } + catch (...) + { + message = "Unexpected rig error"; + } + if (!message.isEmpty ()) + { + offline (message); + } +} + +void TransceiverBase::ptt (bool on) noexcept +{ + QString message; + try + { + if (m_->state_.online ()) + { + do_ptt (on); + do_post_ptt (on); + } + } + catch (std::exception const& e) + { + message = e.what (); + } + catch (...) + { + message = "Unexpected rig error"; + } + if (!message.isEmpty ()) + { + offline (message); + } +} + +void TransceiverBase::sync (bool force_signal) noexcept +{ + QString message; + try + { + if (m_->state_.online ()) + { + do_sync (force_signal); + } + } + catch (std::exception const& e) + { + message = e.what (); + } + catch (...) + { + message = "Unexpected rig error"; + } + if (!message.isEmpty ()) + { + offline (message); + } +} + +void TransceiverBase::update_rx_frequency (Frequency rx) +{ + m_->state_.frequency (rx); +} + +void TransceiverBase::update_other_frequency (Frequency tx) +{ + m_->state_.tx_frequency (tx); +} + +void TransceiverBase::update_split (bool state) +{ + m_->state_.split (state); +} + +void TransceiverBase::update_mode (MODE m) +{ + m_->state_.mode (m); +} + +void TransceiverBase::update_PTT (bool state) +{ + m_->state_.ptt (state); +} + +void TransceiverBase::update_complete () +{ + if (do_pre_update ()) + { + Q_EMIT update (m_->state_); + } +} + +void TransceiverBase::offline (QString const& reason) +{ + QString message; + try + { + if (m_->state_.online ()) + { + m_->state_.online (false); + do_stop (); + } + } + catch (std::exception const& e) + { + message = e.what (); + } + catch (...) + { + message = "Unexpected rig error"; + } + Q_EMIT failure (reason + '\n' + message); +} + +auto TransceiverBase::state () const -> TransceiverState const& +{ + return m_->state_; +} diff --git a/TransceiverBase.hpp b/TransceiverBase.hpp new file mode 100644 index 000000000..62a0cfc68 --- /dev/null +++ b/TransceiverBase.hpp @@ -0,0 +1,136 @@ +#ifndef TRANSCEIVER_BASE_HPP__ +#define TRANSCEIVER_BASE_HPP__ + +#include + +#include "Transceiver.hpp" + +#include "pimpl_h.hpp" + +class QString; + +// +// Base Transceiver Implementation +// +// Behaviour common to all Transceiver implementations. +// +// Collaborations +// +// Implements the Transceiver abstract interface as template methods +// and provides a new abstract interface with similar functionality +// (do_XXXXX operations). Provides and calls abstract interface that +// gets called post the above operations (do_post_XXXXX) to allow +// caching implementation etc. +// +// A key factor is to catch all exceptions thrown by sub-class +// implementations where the template method is a Qt slot which is +// therefore likely to be called by Qt which doesn't handle +// exceptions. Any exceptions are converted to Transceiver::failure() +// signals. +// +// Sub-classes update the stored state via a protected interface. +// +// Responsibilities: +// +// Wrap incoming Transceiver messages catching all exceptions in Qt +// slot driven messages and converting them to Qt signals. This is +// done because exceptions make concrete Transceiver implementations +// simpler to write, but exceptions cannot cross signal/slot +// boundaries (especially across threads). This also removes any +// requirement for the client code to handle exceptions. +// +// Maintain the state of the concrete Transceiver instance that is +// passed back via the Transceiver::update(TransceiverState) signal, +// it is still the responsibility of concrete Transceiver +// implementations to emit the state_change signal when they have a +// status update. +// +// Maintain a go/no-go status for concrete Transceiver +// implementations ensuring only a valid sequence of messages are +// passed. A concrete Transceiver instance must be started before it +// can receive messages, any exception thrown takes the Transceiver +// offline. +// +// Implements methods that concrete Transceiver implementations use +// to update the Transceiver state. These do not signal state change +// to clients as this is the responsibility of the concrete +// Transceiver implementation, thus allowing multiple state component +// updates to be signalled together if required. +// +class TransceiverBase + : public Transceiver +{ +protected: + TransceiverBase (); + +public: + ~TransceiverBase (); + + // + // Implement the Transceiver abstract interface. + // + void start () noexcept override final; + void stop () noexcept override final; + void frequency (Frequency rx) noexcept override final; + void tx_frequency (Frequency tx, bool rationalise_mode) noexcept override final; + void mode (MODE, bool rationalise) noexcept override final; + void ptt (bool) noexcept override final; + void sync (bool force_signal) noexcept override final; + +protected: + // + // Error exception which is thrown to signal unexpected errors. + // + struct error + : public std::runtime_error + { + error (char const * msg) : std::runtime_error (msg) {} + }; + + // Template methods that sub classes implement to do what they need to do. + // + // These methods may throw exceptions to signal errors. + virtual void do_start () = 0; + virtual void do_post_start () {} + + virtual void do_stop () = 0; + virtual void do_post_stop () {} + + virtual void do_frequency (Frequency rx) = 0; + virtual void do_post_frequency (Frequency) {} + + virtual void do_tx_frequency (Frequency tx = 0, bool rationalise_mode = true) = 0; + virtual void do_post_tx_frequency (Frequency) {} + + virtual void do_mode (MODE, bool rationalise = true) = 0; + virtual void do_post_mode (MODE) {} + + virtual void do_ptt (bool = true) = 0; + virtual void do_post_ptt (bool) {} + + virtual void do_sync (bool force_signal = false) = 0; + + virtual bool do_pre_update () {return true;} + + // sub classes report rig state changes with these methods + void update_rx_frequency (Frequency); + void update_other_frequency (Frequency = 0); + void update_split (bool); + void update_mode (MODE); + void update_PTT (bool = true); + + // Calling this triggers the Transceiver::update(State) signal. + void update_complete (); + + // sub class may asynchronously take the rig offline by calling this + void offline (QString const& reason); + + // and query state with this one + TransceiverState const& state () const; + +private: + class impl; + pimpl m_; +}; + +#endif diff --git a/TransceiverFactory.cpp b/TransceiverFactory.cpp new file mode 100644 index 000000000..bb6c04145 --- /dev/null +++ b/TransceiverFactory.cpp @@ -0,0 +1,301 @@ +#include "TransceiverFactory.hpp" + +#include + +#include "HamlibTransceiver.hpp" +#include "DXLabSuiteCommanderTransceiver.hpp" +#include "HRDTransceiver.hpp" +#include "EmulateSplitTransceiver.hpp" + +#if defined (WIN32) +#include "OmniRigTransceiver.hpp" +#endif + +#include "moc_TransceiverFactory.cpp" + +// we use the hamlib "Hamlib Dummy" transceiver for non-CAT radios, +// this allows us to still use the hamlib PTT control features for a +// unified PTT control solution + +char const * const TransceiverFactory::basic_transceiver_name_ = "None"; + +namespace +{ + struct init + { + init () + { + qRegisterMetaType ("TransceiverFactory::DataBits"); + qRegisterMetaTypeStreamOperators ("TransceiverFactory::DataBits"); + qRegisterMetaType ("TransceiverFactory::StopBits"); + qRegisterMetaTypeStreamOperators ("TransceiverFactory::StopBits"); + qRegisterMetaType ("TransceiverFactory::Handshake"); + qRegisterMetaTypeStreamOperators ("TransceiverFactory::Handshake"); + qRegisterMetaType ("TransceiverFactory::PTTMethod"); + qRegisterMetaTypeStreamOperators ("TransceiverFactory::PTTMethod"); + qRegisterMetaType ("TransceiverFactory::TXAudioSource"); + qRegisterMetaTypeStreamOperators ("TransceiverFactory::TXAudioSource"); + qRegisterMetaType ("TransceiverFactory::SplitMode"); + qRegisterMetaTypeStreamOperators ("TransceiverFactory::SplitMode"); + } + } static_initializer; + + enum // supported non-hamlib radio interfaces + { + NonHamlibBaseId = 9899 + , CommanderId + , HRDId + , OmniRigOneId + , OmniRigTwoId + }; +} + +TransceiverFactory::TransceiverFactory () +{ + HamlibTransceiver::register_transceivers (&transceivers_); + DXLabSuiteCommanderTransceiver::register_transceivers (&transceivers_, CommanderId); + HRDTransceiver::register_transceivers (&transceivers_, HRDId); + +#if defined (WIN32) + // OmniRig is ActiveX/COM server so only on Windows + OmniRigTransceiver::register_transceivers (&transceivers_, OmniRigOneId, OmniRigTwoId); +#endif +} + +auto TransceiverFactory::supported_transceivers () const -> Transceivers const& +{ + return transceivers_; +} + +auto TransceiverFactory::CAT_port_type (QString const& name) const -> Capabilities::PortType +{ + return supported_transceivers ()[name].port_type_; +} + +bool TransceiverFactory::has_CAT_PTT (QString const& name) const +{ + return + supported_transceivers ()[name].has_CAT_PTT_ + || supported_transceivers ()[name].model_number_ > NonHamlibBaseId; +} + +bool TransceiverFactory::has_CAT_PTT_mic_data (QString const& name) const +{ + return supported_transceivers ()[name].has_CAT_PTT_mic_data_; +} + +bool TransceiverFactory::has_CAT_indirect_serial_PTT (QString const& name) const +{ + return supported_transceivers ()[name].has_CAT_indirect_serial_PTT_; +} + +bool TransceiverFactory::has_asynchronous_CAT (QString const& name) const +{ + return supported_transceivers ()[name].asynchronous_; +} + +std::unique_ptr TransceiverFactory::create (QString const& name + , QString const& cat_port + , int cat_baud + , DataBits cat_data_bits + , StopBits cat_stop_bits + , Handshake cat_handshake + , bool cat_dtr_always_on + , bool cat_rts_always_on + , PTTMethod ptt_type + , TXAudioSource ptt_use_data_ptt + , SplitMode split_mode + , QString const& ptt_port + , int poll_interval + , QThread * target_thread) +{ + std::unique_ptr result; + switch (supported_transceivers ()[name].model_number_) + { + case CommanderId: + { + // we start with a dummy HamlibTransceiver object instance that can support direct PTT + std::unique_ptr basic_transceiver { + new HamlibTransceiver { + supported_transceivers ()[basic_transceiver_name_].model_number_ + , cat_port + , cat_baud + , cat_data_bits + , cat_stop_bits + , cat_handshake + , cat_dtr_always_on + , cat_rts_always_on + , ptt_type + , ptt_use_data_ptt + , "CAT" == ptt_port ? "" : ptt_port + } + }; + if (target_thread) + { + basic_transceiver.get ()->moveToThread (target_thread); + } + + // wrap the basic Transceiver object instance with a decorator object that talks to DX Lab Suite Commander + result.reset (new DXLabSuiteCommanderTransceiver {std::move (basic_transceiver), cat_port, PTT_method_CAT == ptt_type, poll_interval}); + if (target_thread) + { + result.get ()->moveToThread (target_thread); + } + } + break; + + case HRDId: + { + // we start with a dummy HamlibTransceiver object instance that can support direct PTT + std::unique_ptr basic_transceiver { + new HamlibTransceiver { + supported_transceivers ()[basic_transceiver_name_].model_number_ + , cat_port + , cat_baud + , cat_data_bits + , cat_stop_bits + , cat_handshake + , cat_dtr_always_on + , cat_rts_always_on + , ptt_type + , ptt_use_data_ptt + , "CAT" == ptt_port ? "" : ptt_port + } + }; + if (target_thread) + { + basic_transceiver.get ()->moveToThread (target_thread); + } + + // wrap the basic Transceiver object instance with a decorator object that talks to ham Radio Deluxe + result.reset (new HRDTransceiver {std::move (basic_transceiver), cat_port, PTT_method_CAT == ptt_type, poll_interval}); + if (target_thread) + { + result.get ()->moveToThread (target_thread); + } + } + break; + +#if defined (WIN32) + case OmniRigOneId: + { + // we start with a dummy HamlibTransceiver object instance that can support direct PTT + std::unique_ptr basic_transceiver { + new HamlibTransceiver { + supported_transceivers ()[basic_transceiver_name_].model_number_ + , cat_port + , cat_baud + , cat_data_bits + , cat_stop_bits + , cat_handshake + , cat_dtr_always_on + , cat_rts_always_on + , ptt_type + , ptt_use_data_ptt + , "CAT" == ptt_port ? "" : ptt_port + } + }; + if (target_thread) + { + basic_transceiver.get ()->moveToThread (target_thread); + } + + // wrap the basic Transceiver object instance with a decorator object that talks to OmniRig rig one + result.reset (new OmniRigTransceiver {std::move (basic_transceiver), OmniRigTransceiver::One, ptt_type, ptt_port}); + if (target_thread) + { + result.get ()->moveToThread (target_thread); + } + } + break; + + case OmniRigTwoId: + { + // we start with a dummy HamlibTransceiver object instance that can support direct PTT + std::unique_ptr basic_transceiver { + new HamlibTransceiver { + supported_transceivers ()[basic_transceiver_name_].model_number_ + , cat_port + , cat_baud + , cat_data_bits + , cat_stop_bits + , cat_handshake + , cat_dtr_always_on + , cat_rts_always_on + , ptt_type + , ptt_use_data_ptt + , "CAT" == ptt_port ? "" : ptt_port + } + }; + if (target_thread) + { + basic_transceiver.get ()->moveToThread (target_thread); + } + + // wrap the basic Transceiver object instance with a decorator object that talks to OmniRig rig two + result.reset (new OmniRigTransceiver {std::move (basic_transceiver), OmniRigTransceiver::Two, ptt_type, ptt_port}); + if (target_thread) + { + result.get ()->moveToThread (target_thread); + } + } + break; +#endif + + default: + result.reset (new HamlibTransceiver { + supported_transceivers ()[name].model_number_ + , cat_port + , cat_baud + , cat_data_bits + , cat_stop_bits + , cat_handshake + , cat_dtr_always_on + , cat_rts_always_on + , ptt_type + , ptt_use_data_ptt + , "CAT" == ptt_port ? cat_port : ptt_port + , poll_interval + }); + if (target_thread) + { + result.get ()->moveToThread (target_thread); + } + break; + } + + if (split_mode_emulate == split_mode) + { + // wrap the Transceiver object instance with a decorator that emulates split mode + result.reset (new EmulateSplitTransceiver {std::move (result)}); + if (target_thread) + { + result.get ()->moveToThread (target_thread); + } + } + + return std::move (result); +} + +#if !defined (QT_NO_DEBUG_STREAM) +ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, DataBits); +ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, StopBits); +ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, Handshake); +ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, PTTMethod); +ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, TXAudioSource); +ENUM_QDEBUG_OPS_IMPL (TransceiverFactory, SplitMode); +#endif + +ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, DataBits); +ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, StopBits); +ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, Handshake); +ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, PTTMethod); +ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, TXAudioSource); +ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, SplitMode); + +ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, DataBits); +ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, StopBits); +ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, Handshake); +ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, PTTMethod); +ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, TXAudioSource); +ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, SplitMode); diff --git a/TransceiverFactory.hpp b/TransceiverFactory.hpp new file mode 100644 index 000000000..176c91612 --- /dev/null +++ b/TransceiverFactory.hpp @@ -0,0 +1,155 @@ +#ifndef TRANSCEIVER_FACTORY_HPP__ +#define TRANSCEIVER_FACTORY_HPP__ + +#include + +#include +#include + +#include "Transceiver.hpp" + +#include "qt_helpers.hpp" + +class QString; +class QThread; + +// +// Transceiver Factory +// +class TransceiverFactory + : public QObject +{ + Q_OBJECT; + Q_ENUMS (DataBits StopBits Handshake PTTMethod TXAudioSource SplitMode); + +private: + Q_DISABLE_COPY (TransceiverFactory); + +public: + // + // Capabilities of a Transceiver that can be determined without + // actually instantiating one, these are for use in Configuration + // GUI behaviour determination + // + struct Capabilities + { + enum PortType {none, serial, network}; + + explicit Capabilities (int model_number = 0 + , PortType port_type = none + , bool has_CAT_PTT = false + , bool has_CAT_PTT_mic_data = false + , bool has_CAT_indirect_serial_PTT = false + , bool asynchronous = false) + : model_number_ {model_number} + , port_type_ {port_type} + , has_CAT_PTT_ {has_CAT_PTT} + , has_CAT_PTT_mic_data_ {has_CAT_PTT_mic_data} + , has_CAT_indirect_serial_PTT_ {has_CAT_indirect_serial_PTT} + , asynchronous_ {asynchronous} + { + } + + int model_number_; + PortType port_type_; + bool has_CAT_PTT_; + bool has_CAT_PTT_mic_data_; + bool has_CAT_indirect_serial_PTT_; // OmniRig controls RTS/DTR via COM interface + bool asynchronous_; + }; + + // + // Dictionary of Transceiver types Capabilities + // + typedef QMap Transceivers; + + // + // various Transceiver parameters + // + enum DataBits {seven_data_bits = 7, eight_data_bits}; + enum StopBits {one_stop_bit = 1, two_stop_bits}; + enum Handshake {handshake_none, handshake_XonXoff, handshake_hardware}; + enum PTTMethod {PTT_method_VOX, PTT_method_CAT, PTT_method_DTR, PTT_method_RTS}; + enum TXAudioSource {TX_audio_source_front, TX_audio_source_rear}; + enum SplitMode {split_mode_none, split_mode_rig, split_mode_emulate}; + + TransceiverFactory (); + + static char const * const basic_transceiver_name_; // dummy transceiver is basic model + + // + // fetch all supported rigs as a list of name and model id + // + Transceivers const& supported_transceivers () const; + + // supported model queries + Capabilities::PortType CAT_port_type (QString const& name) const; // how to talk to CAT + bool has_CAT_PTT (QString const& name) const; // can be keyed via CAT + bool has_CAT_PTT_mic_data (QString const& name) const; // Tx audio port is switchable via CAT + bool has_CAT_indirect_serial_PTT (QString const& name) const; // Can PTT via CAT port use DTR or RTS (OmniRig for example) + bool has_asynchronous_CAT (QString const& name) const; // CAT asynchronous rather than polled + + // make a new Transceiver instance + // + // cat_port, cat_baud, cat_data_bits, cat_stop_bits, cat_handshake, + // cat_dtr_alway_on, cat_rts_always_on are only relevant to + // interfaces that are served by hamlib + // + // PTT port and to some extent ptt_type are independent of interface + // type + // + std::unique_ptr create (QString const& name // from supported_transceivers () key + , QString const& cat_port // serial port device name or empty + , int cat_baud + , DataBits cat_data_bits + , StopBits cat_stop_bits + , Handshake cat_handshake + , bool cat_dtr_always_on // to power interface + , bool cat_rts_always_on // to power inteface + , PTTMethod ptt_type // "CAT" | "DTR" | "RTS" | "VOX" + , TXAudioSource ptt_use_data_ptt // some rigs allow audio routing to Mic/Data connector + , SplitMode split_mode // how to support split TX mode + , QString const& ptt_port // serial port device name or special value "CAT" + , int poll_interval // in milliseconds for interfaces that require polling for parameter changes + , QThread * target_thread = nullptr + ); + +private: + Transceivers transceivers_; +}; + +// +// boilerplate routines to make enum types useable and debuggable in +// Qt +// +Q_DECLARE_METATYPE (TransceiverFactory::DataBits); +Q_DECLARE_METATYPE (TransceiverFactory::StopBits); +Q_DECLARE_METATYPE (TransceiverFactory::Handshake); +Q_DECLARE_METATYPE (TransceiverFactory::PTTMethod); +Q_DECLARE_METATYPE (TransceiverFactory::TXAudioSource); +Q_DECLARE_METATYPE (TransceiverFactory::SplitMode); + +#if !defined (QT_NO_DEBUG_STREAM) +ENUM_QDEBUG_OPS_DECL (TransceiverFactory, DataBits); +ENUM_QDEBUG_OPS_DECL (TransceiverFactory, StopBits); +ENUM_QDEBUG_OPS_DECL (TransceiverFactory, Handshake); +ENUM_QDEBUG_OPS_DECL (TransceiverFactory, PTTMethod); +ENUM_QDEBUG_OPS_DECL (TransceiverFactory, TXAudioSource); +ENUM_QDEBUG_OPS_DECL (TransceiverFactory, SplitMode); +#endif + +ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, DataBits); +ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, StopBits); +ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, Handshake); +ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, PTTMethod); +ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, TXAudioSource); +ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, SplitMode); + +ENUM_CONVERSION_OPS_DECL (TransceiverFactory, DataBits); +ENUM_CONVERSION_OPS_DECL (TransceiverFactory, StopBits); +ENUM_CONVERSION_OPS_DECL (TransceiverFactory, Handshake); +ENUM_CONVERSION_OPS_DECL (TransceiverFactory, PTTMethod); +ENUM_CONVERSION_OPS_DECL (TransceiverFactory, TXAudioSource); +ENUM_CONVERSION_OPS_DECL (TransceiverFactory, SplitMode); + +#endif diff --git a/Versions.cmake b/Versions.cmake new file mode 100644 index 000000000..2984af67d --- /dev/null +++ b/Versions.cmake @@ -0,0 +1,10 @@ +# Version number components +set (WSJTX_VERSION_MAJOR 1) +set (WSJTX_VERSION_MINOR 4) +set (WSJTX_VERSION_PATCH 0) +#set (WSJTX_RC 1) +set (WSJTX_VERSION_IS_RELEASE 0) + +set (CONFIG_TEST_VERSION_MAJOR 0) +set (CONFIG_TEST_VERSION_MINOR 2) +set (CONFIG_TEST_VERSION_PATCH 13) diff --git a/about.cpp b/about.cpp index f23486116..734503ecc 100644 --- a/about.cpp +++ b/about.cpp @@ -1,6 +1,8 @@ #include "about.h" #include "ui_about.h" +#include "moc_about.cpp" + CAboutDlg::CAboutDlg(QWidget *parent, QString Revision) : QDialog(parent), m_Revision(Revision), diff --git a/about.ui b/about.ui index 3a862599c..23d3eff21 100644 --- a/about.ui +++ b/about.ui @@ -10,7 +10,7 @@ 0 0 374 - 164 + 144 @@ -24,17 +24,75 @@ - - - + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + Qt::Vertical - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 20 + 40 + - + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + OK + + + + - + + + okButton + clicked() + CAboutDlg + accept() + + + 321 + 120 + + + 186 + 71 + + + + diff --git a/artwork/DragNDrop Background.svg b/artwork/DragNDrop Background.svg new file mode 100644 index 000000000..fa325a7f2 --- /dev/null +++ b/artwork/DragNDrop Background.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + Drag the icononto the linkto install WSJT-X + + + + + image/svg+xml + + + + + Openclipart + + + + 2010-03-28T09:25:56 + Drawing by Francesco 'Architetto' Rollandin. From OCAL 0.18 release. + http://openclipart.org/detail/34711/architetto----tasto-5-by-anonymous + + + Anonymous + + + + + arrow + clip art + clipart + green + icon + right + sign + symbol + + + + + + + + + + + diff --git a/artwork/README b/artwork/README new file mode 100644 index 000000000..3b5fd5826 --- /dev/null +++ b/artwork/README @@ -0,0 +1,27 @@ +This directory contains original artwork used to generate the graphics +used in various parts of the WSJT-X ecosystem. The CMake build scripts +do not generate the final image bitmaps becuase of teh extra tools +required to complete this step. Instead there is shell script here +(make_graphics.sh) that does the generation. If you want to modify the +sourec graphics or add new ones then you need an SVG editor (I use +inkscape) and a tool to do various conversion steps (I use +ImageMagick), the sheel script explicitly uses these tools. + +The files here are: + +installer_logo.svg - A 150x57 pixel image (the size is important with + a whte background that is used at the top right of the NSIS + Windows installer. + +wsjtx_globe_1024x1024.svg - A 1024x1024 pixel image which is used in + various places, mainly for high resolution icons. + +wsjtx_globe_128x128.svg - A 128x128 pixel image which is used for low + resolution icons. + +make_graphics.sh - Run this script (on Linux) to generate the + intermediate bitmap image files used in the wsjtx build. This + script generates all but the final Mac iconset file which is + generated by a build on Mac since it requires a Mac developer tool + (iconutil). This script requires that inkscape and ImageMagick are + installed. \ No newline at end of file diff --git a/artwork/installer_logo.svg b/artwork/installer_logo.svg new file mode 100644 index 000000000..4dcf15e6f --- /dev/null +++ b/artwork/installer_logo.svg @@ -0,0 +1,1126 @@ + + + + + + + + Simple globe centered on North America + + + earth globe northamerica + + + + + Open Clip Art Library + + + + + Dan Gerhrads + + + + + Dan Gerhrads + + + May 1, 2005 + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wmf2svg + + + + + + + + + + + + + + + WSJT-XJT65&JT9by K1JT + diff --git a/artwork/make_graphics.sh b/artwork/make_graphics.sh new file mode 100644 index 000000000..9a9275fb8 --- /dev/null +++ b/artwork/make_graphics.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +# +# Windows +# +inkscape -z -e /tmp/image-0.png wsjtx_globe_128x128.svg +inkscape -z -e /tmp/image-1.png wsjtx_globe_1024x1024.svg +convert '/tmp/image-%d.png[0-1]' -background transparent \ + \( -clone 0 -resize 16 -colors 256 -compress none \) \ + \( -clone 0 -resize 20 -colors 256 -compress none \) \ + \( -clone 0 -resize 24 -colors 256 -compress none \) \ + \( -clone 0 -resize 32 -colors 256 -compress none \) \ + \( -clone 0 -resize 40 -colors 256 -compress none \) \ + \( -clone 1 -resize 48 -colors 256 -compress none \) \ + \( -clone 1 -resize 96 -colors 256 -compress none \) \ + \( -clone 1 -resize 128 -colors 256 -compress none \) \ + \( -clone 0 -resize 16 -compress none \) \ + \( -clone 0 -resize 20 -compress none \) \ + \( -clone 0 -resize 24 -compress none \) \ + \( -clone 0 -resize 32 -compress none \) \ + \( -clone 0 -resize 40 -compress none \) \ + \( -clone 1 -resize 48 -compress none \) \ + \( -clone 1 -resize 64 -compress none \) \ + \( -clone 1 -resize 96 -compress none \) \ + \( -clone 1 -resize 128 -compress none \) \ + \( -clone 1 -resize 256 -compress Zip \) \ + -delete 1 -delete 0 \ + -alpha remove ../icons/windows-icons/wsjtx.ico +rm /tmp/image-0.png /tmp/image-1.png +identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' ../icons/windows-icons/wsjtx.ico +# +inkscape -z -e /dev/stdout -w 150 -h 57 -b white installer_logo.svg | tail -n +4 | \ + convert png:- -resize 150x57 +matte BMP3:../icons/windows-icons/installer_logo.bmp +identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' ../icons/windows-icons/installer_logo.bmp + +# +# Mac +# +inkscape -z -e ../icons/Darwin/wsjtx.iconset/icon_16x16.png -w 16 -h 16 wsjtx_globe_128x128.svg +inkscape -z -e ../icons/Darwin/wsjtx.iconset/icon_16x16@2x.png -w 32 -h 32 wsjtx_globe_128x128.svg +inkscape -z -e ../icons/Darwin/wsjtx.iconset/icon_32x32.png -w 32 -h 32 wsjtx_globe_128x128.svg +inkscape -z -e ../icons/Darwin/wsjtx.iconset/icon_32x32@2x.png -w 64 -h 64 wsjtx_globe_128x128.svg +inkscape -z -e ../icons/Darwin/wsjtx.iconset/icon_128x128.png -w 128 -h 128 wsjtx_globe_1024x1024.svg +inkscape -z -e ../icons/Darwin/wsjtx.iconset/icon_128x128@2x.png -w 256 -h 256 wsjtx_globe_1024x1024.svg +inkscape -z -e ../icons/Darwin/wsjtx.iconset/icon_256x256.png -w 256 -h 256 wsjtx_globe_1024x1024.svg +inkscape -z -e ../icons/Darwin/wsjtx.iconset/icon_256x256@2x.png -w 512 -h 512 wsjtx_globe_1024x1024.svg +inkscape -z -e ../icons/Darwin/wsjtx.iconset/icon_512x512.png -w 512 -h 512 wsjtx_globe_1024x1024.svg +inkscape -z -e ../icons/Darwin/wsjtx.iconset/icon_512x512@2x.png -w 1024 -h 1024 wsjtx_globe_1024x1024.svg +identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' ../icons/Darwin/wsjtx.iconset/* +# +inkscape -z -e "../icons/Darwin/DragNDrop Background.png" -w 640 -h 480 -b white "DragNDrop Background.svg" +identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' "../icons/Darwin/DragNDrop Background.png" + +# +# KDE & Gnome +# +inkscape -z -e ../icons/Unix/wsjtx_icon.png -w 128 -h 128 wsjtx_globe_1024x1024.svg +identify -format '%f %p/%n %m %C/%Q %r %G %A %z\n' ../icons/Unix/wsjtx_icon.png diff --git a/artwork/wsjtx_globe_1024x1024.svg b/artwork/wsjtx_globe_1024x1024.svg new file mode 100644 index 000000000..e294483f9 --- /dev/null +++ b/artwork/wsjtx_globe_1024x1024.svg @@ -0,0 +1,602 @@ + + + + + + + + Simple globe centered on North America + + + earth globe northamerica + + + + + Open Clip Art Library + + + + + Dan Gerhrads + + + + + Dan Gerhrads + + + May 1, 2005 + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wmf2svg + + + + + + + + + + + + + + WSJT-XJT65&JT9by K1JT + diff --git a/artwork/wsjtx_globe_128x128.svg b/artwork/wsjtx_globe_128x128.svg new file mode 100644 index 000000000..650324316 --- /dev/null +++ b/artwork/wsjtx_globe_128x128.svg @@ -0,0 +1,839 @@ + + + + + + + + Simple globe centered on North America + + + earth globe northamerica + + + + + Open Clip Art Library + + + + + Dan Gerhrads + + + + + Dan Gerhrads + + + May 1, 2005 + image/svg+xml + + + en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wmf2svg + + + + + + + + + + + + + + diff --git a/astro.cpp b/astro.cpp index 13bd9f17a..86416a777 100644 --- a/astro.cpp +++ b/astro.cpp @@ -1,37 +1,101 @@ #include "astro.h" -#include "ui_astro.h" -#include -#include -#include + #include + +#include +#include +#include +#include +#include +#include +#include +#include + #include "commons.h" -Astro::Astro(QWidget *parent) : - QWidget(parent), - ui(new Ui::Astro) +#include "ui_astro.h" + +#include "moc_astro.cpp" + +Astro::Astro(QSettings * settings, QDir const& dataPath, QWidget * parent) : + QWidget {parent}, + settings_ {settings}, + ui_ {new Ui::Astro}, + data_path_ {dataPath} { - ui->setupUi(this); - ui->astroTextBrowser->setStyleSheet( - "QTextBrowser { background-color : cyan; color : black; }"); - ui->astroTextBrowser->clear(); + ui_->setupUi(this); + + setWindowFlags (Qt::Dialog | Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint); + + setWindowTitle(QApplication::applicationName () + " - " + tr ("Astronomical Data")); + + read_settings (); + + ui_->text_label->clear(); } -Astro::~Astro() +Astro::~Astro () { - delete ui; + if (isVisible ()) + { + write_settings (); + } +} + +void Astro::closeEvent (QCloseEvent * e) +{ + write_settings (); + QWidget::closeEvent (e); +} + +void Astro::read_settings () +{ + settings_->beginGroup ("Astro"); + move (settings_->value ("window/pos", pos ()).toPoint ()); + QFont font; + if (font.fromString (settings_->value ("font", ui_->text_label->font ().toString ()).toString ())) + { + ui_->text_label->setFont (font); + adjustSize (); + } + settings_->endGroup (); +} + +void Astro::write_settings () +{ + settings_->beginGroup ("Astro"); + settings_->setValue ("window/pos", pos ()); + settings_->setValue ("font", ui_->text_label->font ().toString ()); + settings_->endGroup (); +} + +void Astro::on_font_push_button_clicked (bool /* checked */) +{ + bool changed; + ui_->text_label->setFont (QFontDialog::getFont (&changed + , ui_->text_label->font () + , this + , tr ("WSJT-X Astro Text Font Chooser") +#if QT_VERSION >= 0x050201 + , QFontDialog::MonospacedFonts +#endif + )); + if (changed) + { + adjustSize (); + } } void Astro::astroUpdate(QDateTime t, QString mygrid, QString hisgrid, - int fQSO, int nsetftx, int ntxFreq, QString azelDir) + int fQSO, int nsetftx, int ntxFreq) { static int ntxFreq0=-99; static bool astroBusy=false; - char cc[300]; double azsun,elsun,azmoon,elmoon,azmoondx,elmoondx; double ramoon,decmoon,dgrd,poloffset,xnr,techo; int ntsky,ndop,ndop00; - QString date = t.date().toString("yyyy MMM dd"); - QString utc = t.time().toString(); + QString date = t.date().toString("yyyy MMM dd").trimmed (); + QString utc = t.time().toString().trimmed (); int nyear=t.date().year(); int month=t.date().month(); int nday=t.date().day(); @@ -40,7 +104,7 @@ void Astro::astroUpdate(QDateTime t, QString mygrid, QString hisgrid, double sec=t.time().second() + 0.001*t.time().msec(); int isec=sec; double uth=nhr + nmin/60.0 + sec/3600.0; -// int nfreq=(int)datcom_.fcenter; + // int nfreq=(int)datcom_.fcenter; int nfreq=10368; if(nfreq<10 or nfreq > 50000) nfreq=144; @@ -48,60 +112,82 @@ void Astro::astroUpdate(QDateTime t, QString mygrid, QString hisgrid, astroBusy=true; astrosub_(&nyear, &month, &nday, &uth, &nfreq, mygrid.toLatin1(), - hisgrid.toLatin1(), &azsun, &elsun, &azmoon, &elmoon, - &azmoondx, &elmoondx, &ntsky, &ndop, &ndop00,&ramoon, &decmoon, - &dgrd, &poloffset, &xnr, &techo, 6, 6); + hisgrid.toLatin1(), &azsun, &elsun, &azmoon, &elmoon, + &azmoondx, &elmoondx, &ntsky, &ndop, &ndop00,&ramoon, &decmoon, + &dgrd, &poloffset, &xnr, &techo, 6, 6); astroBusy=false; } - sprintf(cc, - "Az: %6.1f\n" - "El: %6.1f\n" - "MyDop: %6d\n" - "Delay: %6.2f\n" - "DxAz: %6.1f\n" - "DxEl: %6.1f\n" - "DxDop: %6d\n" - "Dec: %6.1f\n" - "SunAz: %6.1f\n" - "SunEl: %6.1f\n" - "Freq: %6d\n" - "Tsky: %6d\n" - "MNR: %6.1f\n" - "Dgrd: %6.1f", - azmoon,elmoon,ndop00,techo,azmoondx,elmoondx,ndop,decmoon, - azsun,elsun,nfreq,ntsky,xnr,dgrd); - ui->astroTextBrowser->setText(" "+ date + "\nUTC: " + utc + "\n" + cc); + QString message; + { + QTextStream out {&message}; + out + << " " << date << "\n" + "UTC: " << utc << "\n" + << fixed + << qSetFieldWidth (6) + << qSetRealNumberPrecision (1) + << "Az: " << azmoon << "\n" + "El: " << elmoon << "\n" + "MyDop: " << ndop00 << "\n" + << qSetRealNumberPrecision (2) + << "Delay: " << techo << "\n" + << qSetRealNumberPrecision (1) + << "DxAz: " << azmoondx << "\n" + "DxEl: " << elmoondx << "\n" + "DxDop: " << ndop << "\n" + "Dec: " << decmoon << "\n" + "SunAz: " << azsun << "\n" + "SunEl: " << elsun << "\n" + "Freq: " << nfreq << "\n" + "Tsky: " << ntsky << "\n" + "MNR: " << xnr << "\n" + "Dgrd: " << dgrd; + } + ui_->text_label->setText(message); - QString fname=azelDir+"/azel.dat"; - QFile f(fname); + QString fname {"azel.dat"}; + QFile f(data_path_.absoluteFilePath (fname)); if(!f.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox mb; - mb.setText("Cannot open " + fname); + mb.setText("Cannot open \"" + f.fileName () + "\"."); mb.exec(); return; } int ndiff=0; if(ntxFreq != ntxFreq0) ndiff=1; ntxFreq0=ntxFreq; - QTextStream out(&f); - sprintf(cc,"%2.2d:%2.2d:%2.2d,%5.1f,%5.1f,Moon\n" - "%2.2d:%2.2d:%2.2d,%5.1f,%5.1f,Sun\n" - "%2.2d:%2.2d:%2.2d,%5.1f,%5.1f,Source\n" - "%4d,%6d,Doppler\n" - "%3d,%1d,fQSO\n" - "%3d,%1d,fQSO2\n", - nhr,nmin,isec,azmoon,elmoon, - nhr,nmin,isec,azsun,elsun, - nhr,nmin,isec,0.0,0.0, - nfreq,ndop, - fQSO,nsetftx, - ntxFreq,ndiff); - out << cc; + { + QTextStream out {&f}; + out << fixed + << qSetFieldWidth (2) + << qSetRealNumberPrecision (1) + << qSetPadChar ('0') + << right + << nhr << ':' << nmin << ':' << isec + << qSetFieldWidth (5) + << ',' << azmoon << ',' << elmoon << ",Moon\n" + << qSetFieldWidth (2) + << nhr << ':' << nmin << ':' << isec + << qSetFieldWidth (5) + << ',' << azsun << ',' << elsun << ",Sun\n" + << qSetFieldWidth (2) + << nhr << ':' << nmin << ':' << isec + << qSetFieldWidth (5) + << ',' << 0. << ',' << 0. << ",Sun\n" + << qSetPadChar (' ') + << qSetFieldWidth (4) + << nfreq << ',' + << qSetFieldWidth (6) + << ndop << ",Doppler\n" + << qSetFieldWidth (3) + << fQSO << ',' + << qSetFieldWidth (1) + << nsetftx << ",fQSO\n" + << qSetFieldWidth (3) + << ntxFreq << ',' + << qSetFieldWidth (1) + << ndiff << ",fQSO2"; + } f.close(); } - -void Astro::setFontSize(int n) -{ - ui->astroTextBrowser->setFontPointSize(n); -} diff --git a/astro.h b/astro.h index 4a0e45ce9..f0879b1b6 100644 --- a/astro.h +++ b/astro.h @@ -1,36 +1,52 @@ +// -*- Mode: C++ -*- #ifndef ASTRO_H #define ASTRO_H #include -#include +#include + +class QSettings; namespace Ui { class Astro; } -class Astro : public QWidget +class Astro final + : public QWidget { - Q_OBJECT - -public: - explicit Astro(QWidget *parent = 0); - void astroUpdate(QDateTime t, QString mygrid, QString hisgrid, - int fQSO, int nsetftx, int ntxFreq, QString azelDir); - void setFontSize(int n); -// ~Astro(); - virtual ~Astro(); + Q_OBJECT; private: - Ui::Astro *ui; + Q_DISABLE_COPY (Astro); + +public: + explicit Astro(QSettings * settings, QDir const& dataPath, QWidget * parent = nullptr); + ~Astro (); + + void astroUpdate(QDateTime t, QString mygrid, QString hisgrid, + int fQSO, int nsetftx, int ntxFreq); + + Q_SLOT void on_font_push_button_clicked (bool); + +protected: + void closeEvent (QCloseEvent *) override; + +private: + void read_settings (); + void write_settings (); + + QSettings * settings_; + QScopedPointer ui_; + QDir data_path_; }; extern "C" { void astrosub_(int* nyear, int* month, int* nday, double* uth, int* nfreq, - const char* mygrid, const char* hisgrid, double* azsun, - double* elsun, double* azmoon, double* elmoon, double* azmoondx, - double* elmoondx, int* ntsky, int* ndop, int* ndop00, - double* ramoon, double* decmoon, double* dgrd, double* poloffset, - double* xnr, double* techo, int len1, int len2); + const char* mygrid, const char* hisgrid, double* azsun, + double* elsun, double* azmoon, double* elmoon, double* azmoondx, + double* elmoondx, int* ntsky, int* ndop, int* ndop00, + double* ramoon, double* decmoon, double* dgrd, double* poloffset, + double* xnr, double* techo, int len1, int len2); } #endif // ASTRO_H diff --git a/astro.ui b/astro.ui index df1ce80c7..a8c8f044c 100644 --- a/astro.ui +++ b/astro.ui @@ -6,31 +6,100 @@ 0 0 - 262 - 483 + 169 + 79 - - Form + + + 0 + 0 + - - - - 0 - 10 - 256 - 451 - + + QWidget { + background: cyan; +} + + + + 0 - - - Courier New - 20 - 75 - true - + + 0 - + + 0 + + + 9 + + + + + + 0 + 0 + + + + + Courier + 18 + + + + QFrame::Sunken + + + Astro Data + + + Qt::AlignCenter + + + 6 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Font ... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + diff --git a/contrib/Commander TCPIP Mesages.pdf b/contrib/Commander TCPIP Mesages.pdf new file mode 100644 index 000000000..ca7b42e50 Binary files /dev/null and b/contrib/Commander TCPIP Mesages.pdf differ diff --git a/contrib/HRDInterface001.dll b/contrib/HRDInterface001.dll deleted file mode 100644 index 974d7fd29..000000000 Binary files a/contrib/HRDInterface001.dll and /dev/null differ diff --git a/devsetup.cpp b/devsetup.cpp deleted file mode 100644 index dfbf6c8cc..000000000 --- a/devsetup.cpp +++ /dev/null @@ -1,750 +0,0 @@ -#include "devsetup.h" - -#include "ui_devsetup.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -extern double dFreq[16]; -qint32 g2_iptt; -qint32 g2_COMportOpen; - -//----------------------------------------------------------- DevSetup() -DevSetup::DevSetup(QWidget *parent) - : QDialog(parent) - , ui (new Ui::DevSetup) - , m_audioInputDevices (QAudioDeviceInfo::availableDevices (QAudio::AudioInput)) - , m_audioOutputDevices (QAudioDeviceInfo::availableDevices (QAudio::AudioOutput)) -{ - ui->setupUi(this); //setup the dialog form - m_restartSoundIn=false; - m_restartSoundOut=false; - m_firstCall=true; - g2_iptt=0; - m_test=0; - m_bRigOpen=false; - g2_COMportOpen=0; -} - -DevSetup::~DevSetup() -{ -} - -void DevSetup::initDlg() -{ - QString m_appDir = QApplication::applicationDirPath(); - QString inifile = m_appDir + "/wsjtx.ini"; - QSettings settings(inifile, QSettings::IniFormat); - settings.beginGroup("Common"); - QString catPortDriver = settings.value("CATdriver","None").toString(); - settings.endGroup(); - - // - // load combo boxes with audio setup choices - // - loadAudioDevices (m_audioInputDevices, ui->comboBoxSndIn, m_audioInputDevice, QAudioDeviceInfo::defaultInputDevice ()); - loadAudioDevices (m_audioOutputDevices, ui->comboBoxSndOut, m_audioOutputDevice, QAudioDeviceInfo::defaultOutputDevice ()); - - { - using namespace std::tr1; - using namespace std::tr1::placeholders; - - function cb (bind (&DevSetup::updateAudioChannels, this, ui->comboBoxSndIn, _1, ui->audioInputChannel, false)); - connect (ui->comboBoxSndIn, static_cast (&QComboBox::currentIndexChanged), cb); - cb = bind (&DevSetup::updateAudioChannels, this, ui->comboBoxSndOut, _1, ui->audioOutputChannel, true); - connect (ui->comboBoxSndOut, static_cast (&QComboBox::currentIndexChanged), cb); - - updateAudioChannels (ui->comboBoxSndIn, ui->comboBoxSndIn->currentIndex (), ui->audioInputChannel, false); - updateAudioChannels (ui->comboBoxSndOut, ui->comboBoxSndOut->currentIndex (), ui->audioOutputChannel, true); - - ui->audioInputChannel->setCurrentIndex (m_audioInputChannel); - ui->audioOutputChannel->setCurrentIndex (m_audioOutputChannel); - } - - enumerateRigs (); - - QPalette pal(ui->myCallEntry->palette()); - if(m_myCall=="") { - pal.setColor(QPalette::Base,"#ffccff"); - } else { - pal.setColor(QPalette::Base,Qt::white); - } - ui->myCallEntry->setPalette(pal); - ui->myGridEntry->setPalette(pal); - ui->myCallEntry->setText(m_myCall); - ui->myGridEntry->setText(m_myGrid); - - ui->idIntSpinBox->setValue(m_idInt); - ui->pttMethodComboBox->setCurrentIndex(m_pttMethodIndex); - ui->saveDirEntry->setText(m_saveDir); - ui->cbID73->setChecked(m_After73); - ui->cbDisplayAstroData->setChecked(m_bAstroData); - ui->cbPSKReporter->setChecked(m_pskReporter); - ui->cbSplit->setChecked(m_bSplit and m_catEnabled); - ui->cbXIT->setChecked(m_bXIT); - ui->cbXIT->setVisible(false); - ui->dtMinSpinBox->setValue(m_DTmin); - ui->dtMaxSpinBox->setValue(m_DTmax); - - enableWidgets(); - - ui->catPortComboBox->setCurrentIndex(m_catPortIndex); - ui->serialRateComboBox->setCurrentIndex(m_serialRateIndex); - ui->dataBitsComboBox->setCurrentIndex(m_dataBitsIndex); - ui->stopBitsComboBox->setCurrentIndex(m_stopBitsIndex); - ui->handshakeComboBox->setCurrentIndex(m_handshakeIndex); - ui->rbData->setChecked(m_pttData); - ui->pollSpinBox->setValue(m_poll); - ui->cbEMEband->setCurrentIndex(m_EMEbandIndex); - ui->cbBWmult->setCurrentIndex(m_toneMultIndex); - ui->astroFontSpinBox->setValue(m_astroFont); - - // PY2SDR -- Per OS serial port names - m_tmp=m_pttPort; - ui->pttComboBox->clear(); - ui->catPortComboBox->clear(); - ui->pttComboBox->addItem("None"); - ui->catPortComboBox->addItem("None"); -#ifdef WIN32 - for ( int i = 1; i < 100; i++ ) { - ui->pttComboBox->addItem("COM" + QString::number(i)); - ui->catPortComboBox->addItem("COM" + QString::number(i)); - } - ui->pttComboBox->addItem("USB"); - ui->catPortComboBox->addItem("USB"); -#else - ui->catPortComboBox->addItem("/dev/ttyS0"); - ui->catPortComboBox->addItem("/dev/ttyS1"); - ui->catPortComboBox->addItem("/dev/ttyS2"); - ui->catPortComboBox->addItem("/dev/ttyS3"); - ui->catPortComboBox->addItem("/dev/ttyS4"); - ui->catPortComboBox->addItem("/dev/ttyS5"); - ui->catPortComboBox->addItem("/dev/ttyS6"); - ui->catPortComboBox->addItem("/dev/ttyS7"); - ui->catPortComboBox->addItem("/dev/ttyUSB0"); - ui->catPortComboBox->addItem("/dev/ttyUSB1"); - ui->catPortComboBox->addItem("/dev/ttyUSB2"); - ui->catPortComboBox->addItem("/dev/ttyUSB3"); - ui->catPortComboBox->addItem(catPortDriver); - - ui->pttComboBox->addItem("/dev/ttyS0"); - ui->pttComboBox->addItem("/dev/ttyS1"); - ui->pttComboBox->addItem("/dev/ttyS2"); - ui->pttComboBox->addItem("/dev/ttyS3"); - ui->pttComboBox->addItem("/dev/ttyS4"); - ui->pttComboBox->addItem("/dev/ttyS5"); - ui->pttComboBox->addItem("/dev/ttyS6"); - ui->pttComboBox->addItem("/dev/ttyS7"); - ui->pttComboBox->addItem("/dev/ttyUSB0"); - ui->pttComboBox->addItem("/dev/ttyUSB1"); - ui->pttComboBox->addItem("/dev/ttyUSB2"); - ui->pttComboBox->addItem("/dev/ttyUSB3"); -#endif - ui->pttComboBox->setCurrentIndex(m_tmp); - ui->catPortComboBox->setCurrentIndex(m_catPortIndex); - - int n=m_macro.length(); - if(n>=1) ui->macro1->setText(m_macro[0].toUpper()); - if(n>=2) ui->macro2->setText(m_macro[1].toUpper()); - if(n>=3) ui->macro3->setText(m_macro[2].toUpper()); - if(n>=4) ui->macro4->setText(m_macro[3].toUpper()); - if(n>=5) ui->macro5->setText(m_macro[4].toUpper()); - if(n>=6) ui->macro6->setText(m_macro[5].toUpper()); - if(n>=7) ui->macro7->setText(m_macro[6].toUpper()); - if(n>=8) ui->macro8->setText(m_macro[7].toUpper()); - if(n>=8) ui->macro9->setText(m_macro[8].toUpper()); - if(n>=10) ui->macro10->setText(m_macro[9].toUpper()); - - ui->f1->setText(m_dFreq[0]); - ui->f2->setText(m_dFreq[1]); - ui->f3->setText(m_dFreq[2]); - ui->f4->setText(m_dFreq[3]); - ui->f5->setText(m_dFreq[4]); - ui->f6->setText(m_dFreq[5]); - ui->f7->setText(m_dFreq[6]); - ui->f8->setText(m_dFreq[7]); - ui->f9->setText(m_dFreq[8]); - ui->f10->setText(m_dFreq[9]); - ui->f11->setText(m_dFreq[10]); - ui->f12->setText(m_dFreq[11]); - ui->f13->setText(m_dFreq[12]); - ui->f14->setText(m_dFreq[13]); - ui->f15->setText(m_dFreq[14]); - ui->f16->setText(m_dFreq[15]); - - ui->AntDescription1->setText(m_antDescription[0]); - ui->AntDescription2->setText(m_antDescription[1]); - ui->AntDescription3->setText(m_antDescription[2]); - ui->AntDescription4->setText(m_antDescription[3]); - ui->AntDescription5->setText(m_antDescription[4]); - ui->AntDescription6->setText(m_antDescription[5]); - ui->AntDescription7->setText(m_antDescription[6]); - ui->AntDescription8->setText(m_antDescription[7]); - ui->AntDescription9->setText(m_antDescription[8]); - ui->AntDescription10->setText(m_antDescription[9]); - ui->AntDescription11->setText(m_antDescription[10]); - ui->AntDescription12->setText(m_antDescription[11]); - ui->AntDescription13->setText(m_antDescription[12]); - ui->AntDescription14->setText(m_antDescription[13]); - ui->AntDescription15->setText(m_antDescription[14]); - ui->AntDescription16->setText(m_antDescription[15]); - - ui->Band1->setText(m_bandDescription[0]); - ui->Band2->setText(m_bandDescription[1]); - ui->Band3->setText(m_bandDescription[2]); - ui->Band4->setText(m_bandDescription[3]); - ui->Band5->setText(m_bandDescription[4]); - ui->Band6->setText(m_bandDescription[5]); - ui->Band7->setText(m_bandDescription[6]); - ui->Band8->setText(m_bandDescription[7]); - ui->Band9->setText(m_bandDescription[8]); - ui->Band10->setText(m_bandDescription[9]); - ui->Band11->setText(m_bandDescription[10]); - ui->Band12->setText(m_bandDescription[11]); - ui->Band13->setText(m_bandDescription[12]); - ui->Band14->setText(m_bandDescription[13]); - ui->Band15->setText(m_bandDescription[14]); - ui->Band16->setText(m_bandDescription[15]); - -} - -//------------------------------------------------------- accept() -void DevSetup::accept() -{ - // Called when OK button is clicked. - // Check to see whether SoundInThread must be restarted, - // and save user parameters. - - m_restartSoundIn = false; - m_restartSoundOut = false; - - if (m_audioInputDevice != m_audioInputDevices[ui->comboBoxSndIn->currentIndex ()]) - { - m_audioInputDevice = m_audioInputDevices[ui->comboBoxSndIn->currentIndex ()]; - m_restartSoundIn = true; - } - - if (m_audioOutputDevice != m_audioOutputDevices[ui->comboBoxSndOut->currentIndex ()]) - { - m_audioOutputDevice = m_audioOutputDevices[ui->comboBoxSndOut->currentIndex ()]; - m_restartSoundOut = true; - } - - if (m_audioInputChannel != static_cast (ui->audioInputChannel->currentIndex ())) - { - m_audioInputChannel = static_cast (ui->audioInputChannel->currentIndex ()); - m_restartSoundIn = true; - } - Q_ASSERT (m_audioInputChannel <= AudioDevice::Right); - - if (m_audioOutputChannel != static_cast (ui->audioOutputChannel->currentIndex ())) - { - m_audioOutputChannel = static_cast (ui->audioOutputChannel->currentIndex ()); - m_restartSoundOut = true; - } - Q_ASSERT (m_audioOutputChannel <= AudioDevice::Both); - - m_myCall=ui->myCallEntry->text(); - m_myGrid=ui->myGridEntry->text(); - m_idInt=ui->idIntSpinBox->value(); - m_pttMethodIndex=ui->pttMethodComboBox->currentIndex(); - m_pttPort=ui->pttComboBox->currentIndex(); - m_saveDir=ui->saveDirEntry->text(); - - m_macro.clear(); - m_macro.append(ui->macro1->text()); - m_macro.append(ui->macro2->text()); - m_macro.append(ui->macro3->text()); - m_macro.append(ui->macro4->text()); - m_macro.append(ui->macro5->text()); - m_macro.append(ui->macro6->text()); - m_macro.append(ui->macro7->text()); - m_macro.append(ui->macro8->text()); - m_macro.append(ui->macro9->text()); - m_macro.append(ui->macro10->text()); - - m_dFreq.clear(); - m_dFreq.append(ui->f1->text()); - m_dFreq.append(ui->f2->text()); - m_dFreq.append(ui->f3->text()); - m_dFreq.append(ui->f4->text()); - m_dFreq.append(ui->f5->text()); - m_dFreq.append(ui->f6->text()); - m_dFreq.append(ui->f7->text()); - m_dFreq.append(ui->f8->text()); - m_dFreq.append(ui->f9->text()); - m_dFreq.append(ui->f10->text()); - m_dFreq.append(ui->f11->text()); - m_dFreq.append(ui->f12->text()); - m_dFreq.append(ui->f13->text()); - m_dFreq.append(ui->f14->text()); - m_dFreq.append(ui->f15->text()); - m_dFreq.append(ui->f16->text()); - - m_antDescription.clear(); - m_antDescription.append(ui->AntDescription1->text()); - m_antDescription.append(ui->AntDescription2->text()); - m_antDescription.append(ui->AntDescription3->text()); - m_antDescription.append(ui->AntDescription4->text()); - m_antDescription.append(ui->AntDescription5->text()); - m_antDescription.append(ui->AntDescription6->text()); - m_antDescription.append(ui->AntDescription7->text()); - m_antDescription.append(ui->AntDescription8->text()); - m_antDescription.append(ui->AntDescription9->text()); - m_antDescription.append(ui->AntDescription10->text()); - m_antDescription.append(ui->AntDescription11->text()); - m_antDescription.append(ui->AntDescription12->text()); - m_antDescription.append(ui->AntDescription13->text()); - m_antDescription.append(ui->AntDescription14->text()); - m_antDescription.append(ui->AntDescription15->text()); - m_antDescription.append(ui->AntDescription16->text()); - - m_bandDescription.clear(); - m_bandDescription.append(ui->Band1->text()); - m_bandDescription.append(ui->Band2->text()); - m_bandDescription.append(ui->Band3->text()); - m_bandDescription.append(ui->Band4->text()); - m_bandDescription.append(ui->Band5->text()); - m_bandDescription.append(ui->Band6->text()); - m_bandDescription.append(ui->Band7->text()); - m_bandDescription.append(ui->Band8->text()); - m_bandDescription.append(ui->Band9->text()); - m_bandDescription.append(ui->Band10->text()); - m_bandDescription.append(ui->Band11->text()); - m_bandDescription.append(ui->Band12->text()); - m_bandDescription.append(ui->Band13->text()); - m_bandDescription.append(ui->Band14->text()); - m_bandDescription.append(ui->Band15->text()); - m_bandDescription.append(ui->Band16->text()); - - - if(m_bRigOpen) { - rig->close(); - if(m_rig<9900) delete rig; - m_bRigOpen=false; - } - - QDialog::accept(); -} - -//------------------------------------------------------- reject() -void DevSetup::reject() -{ - if(m_bRigOpen) rig->close(); - QDialog::reject(); -} - -void DevSetup::msgBox(QString t) //msgBox -{ - msgBox0.setText(t); - msgBox0.exec(); -} - -void DevSetup::on_myCallEntry_editingFinished() -{ - QString t=ui->myCallEntry->text(); - ui->myCallEntry->setText(t.toUpper()); -} - -void DevSetup::on_myGridEntry_editingFinished() -{ - QString t=ui->myGridEntry->text(); - t=t.mid(0,4).toUpper()+t.mid(4,2).toLower(); - ui->myGridEntry->setText(t); -} - -void DevSetup::setEnableAntennaDescriptions(bool enable) -{ - ui->AntDescription1->setEnabled(enable); - ui->AntDescription2->setEnabled(enable); - ui->AntDescription3->setEnabled(enable); - ui->AntDescription4->setEnabled(enable); - ui->AntDescription5->setEnabled(enable); - ui->AntDescription6->setEnabled(enable); - ui->AntDescription7->setEnabled(enable); - ui->AntDescription8->setEnabled(enable); - ui->AntDescription9->setEnabled(enable); - ui->AntDescription10->setEnabled(enable); - ui->AntDescription11->setEnabled(enable); - ui->AntDescription12->setEnabled(enable); - ui->AntDescription13->setEnabled(enable); - ui->AntDescription14->setEnabled(enable); - ui->AntDescription15->setEnabled(enable); - ui->AntDescription16->setEnabled(enable); - if (enable) - ui->AntDescriptionColumnLabel->setText("Antenna description"); - else - ui->AntDescriptionColumnLabel->setText("Antenna description (enable PSK Reporter)"); -} - -void DevSetup::on_cbPSKReporter_clicked(bool b) -{ - m_pskReporter=b; - setEnableAntennaDescriptions(m_pskReporter); -} - -void DevSetup::on_pttMethodComboBox_activated(int index) -{ - m_pttMethodIndex=index; - enableWidgets(); -} - -void DevSetup::on_catPortComboBox_activated(int index) -{ - m_catPortIndex=index; - m_catPort=ui->catPortComboBox->itemText(index); -} - -void DevSetup::on_cbEnableCAT_toggled(bool b) -{ - m_catEnabled=b; - enableWidgets(); - ui->cbSplit->setChecked(m_bSplit and m_catEnabled); -} - -void DevSetup::on_serialRateComboBox_activated(int index) -{ - m_serialRateIndex=index; - m_serialRate=ui->serialRateComboBox->itemText(index).toInt(); -} - -void DevSetup::on_handshakeComboBox_activated(int index) -{ - m_handshakeIndex=index; - m_handshake=ui->handshakeComboBox->itemText(index); -} - -void DevSetup::on_handshakeComboBox_currentIndexChanged(int index) -{ - ui->RTSCheckBox->setEnabled(index != 2); -} - -void DevSetup::on_dataBitsComboBox_activated(int index) -{ - m_dataBitsIndex=index; - m_dataBits=ui->dataBitsComboBox->itemText(index).toInt(); -} - -void DevSetup::on_stopBitsComboBox_activated(int index) -{ - m_stopBitsIndex=index; - m_stopBits=ui->stopBitsComboBox->itemText(index).toInt(); -} - -void DevSetup::on_rigComboBox_activated(int index) -{ - m_rig = ui->rigComboBox->itemData (index).toInt (); - enableWidgets(); -} - -void DevSetup::on_cbID73_toggled(bool checked) -{ - m_After73=checked; -} - -void DevSetup::on_testCATButton_clicked() -{ - openRig(); - if(!m_catEnabled) return; - QString t; - double fMHz=rig->getFreq(RIG_VFO_CURR)/1000000.0; - if(fMHz>0.0) { - t.sprintf("Rig control appears to be working.\nDial Frequency: %.6f MHz", - fMHz); - } else { - t.sprintf("Rig control error %d\nFailed to read frequency.", - int(1000000.0*fMHz)); - if(m_poll>0) { - m_catEnabled=false; - ui->cbEnableCAT->setChecked(false); - } - } - msgBox(t); -} - -void DevSetup::openRig() -{ - QString t; - int ret; - - if(!m_catEnabled) return; - if(m_bRigOpen) { - rig->close(); - if(m_rig<9900) delete rig; - m_bRigOpen=false; - } - - rig = new Rig(); - - if(m_rig<9900) { - if (!rig->init(m_rig)) { - msgBox("Rig init failure"); - m_catEnabled=false; - return; - } - QString sCATport=m_catPort; -#ifdef WIN32 - sCATport="\\\\.\\" + m_catPort; //Allow COM ports above 9 -#endif - rig->setConf("rig_pathname", sCATport.toLatin1().data()); - char buf[80]; - sprintf(buf,"%d",m_serialRate); - rig->setConf("serial_speed",buf); - sprintf(buf,"%d",m_dataBits); - rig->setConf("data_bits",buf); - sprintf(buf,"%d",m_stopBits); - rig->setConf("stop_bits",buf); - rig->setConf("serial_handshake",m_handshake.toLatin1().data()); - rig->setConf("dtr_state",m_bDTR ? "ON" : "OFF"); - if(ui->RTSCheckBox->isEnabled()) { - rig->setConf("rts_state",m_bRTS ? "ON" : "OFF"); - } - } - - ret=rig->open(m_rig); - if(ret==RIG_OK) { - m_bRigOpen=true; - } else { - t="Open rig failed"; - msgBox(t); - m_catEnabled=false; - ui->cbEnableCAT->setChecked(false); - return; - } -} - -void DevSetup::on_testPTTButton_clicked() -{ - m_test=1-m_test; - if(m_pttMethodIndex==1 or m_pttMethodIndex==2) { - ptt(m_pttPort,m_test,&g2_iptt,&g2_COMportOpen); - } - if(m_pttMethodIndex==0 and !m_bRigOpen) { -// on_testCATButton_clicked(); - openRig(); - } - if(m_pttMethodIndex==0 and m_bRigOpen) { - if(m_test==0) rig->setPTT(RIG_PTT_OFF, RIG_VFO_CURR); - if(m_test==1) { - if(m_pttData) rig->setPTT(RIG_PTT_ON_DATA, RIG_VFO_CURR); - if(!m_pttData) rig->setPTT(RIG_PTT_ON_MIC, RIG_VFO_CURR); - } - } -} - -void DevSetup::on_DTRCheckBox_toggled(bool checked) -{ - m_bDTR=checked; -} - -void DevSetup::on_RTSCheckBox_toggled(bool checked) -{ - m_bRTS=checked; -} - -void DevSetup::on_rbData_toggled(bool checked) -{ - m_pttData=checked; -} - -void DevSetup::on_pollSpinBox_valueChanged(int n) -{ - m_poll=n; -} - -void DevSetup::on_pttComboBox_currentIndexChanged(int index) -{ - m_pttPort=index; - enableWidgets(); -} - -void DevSetup::on_pttMethodComboBox_currentIndexChanged(int index) -{ - m_pttMethodIndex=index; - bool b=m_pttMethodIndex==1 or m_pttMethodIndex==2; - ui->pttComboBox->setEnabled(b); -} - -void DevSetup::enableWidgets() -{ - ui->cbEnableCAT->setChecked(m_catEnabled); - ui->rigComboBox->setEnabled(m_catEnabled); - ui->testCATButton->setEnabled(m_catEnabled); - ui->label_4->setEnabled(m_catEnabled); - ui->label_47->setEnabled(m_catEnabled); - ui->cbSplit->setEnabled(m_catEnabled); - if(m_rig==9999) { //No Split Tx with HRD - ui->cbSplit->setChecked(false); - ui->cbSplit->setEnabled(false); - } - ui->cbXIT->setEnabled(m_catEnabled); - - bool bSerial=m_catEnabled and (m_rig<9900); - ui->catPortComboBox->setEnabled(bSerial); - ui->serialRateComboBox->setEnabled(bSerial); - ui->dataBitsComboBox->setEnabled(bSerial); - ui->stopBitsComboBox->setEnabled(bSerial); - ui->handshakeComboBox->setEnabled(bSerial); - ui->DTRCheckBox->setEnabled(bSerial); - ui->DTRCheckBox->setChecked(m_bDTR); - ui->RTSCheckBox->setEnabled(bSerial && m_handshakeIndex != 2); - ui->RTSCheckBox->setChecked(m_bRTS); - ui->rbData->setEnabled(bSerial); - ui->rbMic->setEnabled(bSerial); - ui->label_21->setEnabled(bSerial); - ui->label_22->setEnabled(bSerial); - ui->label_23->setEnabled(bSerial); - ui->label_24->setEnabled(bSerial); - ui->label_25->setEnabled(bSerial); - - ui->pollSpinBox->setEnabled(m_catEnabled); - bool b1=(m_pttMethodIndex==1 or m_pttMethodIndex==2); - ui->pttComboBox->setEnabled(b1); - b1=b1 and (m_pttPort!=0); - bool b2 = (m_catEnabled and m_pttMethodIndex==1 and m_rig<9900) or - (m_catEnabled and m_pttMethodIndex==2 and m_rig<9900); - bool b3 = (m_catEnabled and m_pttMethodIndex==0); - ui->testPTTButton->setEnabled(b1 or b2 or b3); //Include PTT via HRD or Commander - setEnableAntennaDescriptions(m_pskReporter); -} - -void DevSetup::on_cbSplit_toggled(bool checked) -{ - m_bSplit=checked; - if(m_bSplit and m_bXIT) ui->cbXIT->setChecked(false); -} - -void DevSetup::on_cbXIT_toggled(bool checked) -{ - m_bXIT=checked; - if(m_bSplit and m_bXIT) ui->cbSplit->setChecked(false); -} - -void DevSetup::loadAudioDevices (AudioDevices const& d, QComboBox * cb, QAudioDeviceInfo const& device, QAudioDeviceInfo const& defaultDevice) -{ - using std::copy; - using std::back_inserter; - - int currentIndex = -1; - int defaultIndex = 0; - for (AudioDevices::const_iterator p = d.cbegin (); p != d.cend (); ++p) - { - // convert supported channel counts into something we can store in the item model - QList channelCounts; - QList scc (p->supportedChannelCounts ()); - copy (scc.cbegin (), scc.cend (), back_inserter (channelCounts)); - - cb->addItem (p->deviceName (), channelCounts); - if (*p == device) - { - currentIndex = p - d.cbegin (); - } - else if (*p == defaultDevice) - { - defaultIndex = p - d.cbegin (); - } - } - cb->setCurrentIndex (currentIndex != -1 ? currentIndex : defaultIndex); -} - -void DevSetup::updateAudioChannels (QComboBox const * srcCb, int index, QComboBox * cb, bool allowBoth) -{ - // disable all items - for (int i (0); i < cb->count (); ++i) - { - cb->setItemData (i, 0, Qt::UserRole - 1); // undocumented model internals allows disable - } - - Q_FOREACH (QVariant const& v, srcCb->itemData (index).toList ()) - { - // enable valid options - int n (v.toInt ()); - if (2 == n) - { - cb->setItemData (AudioDevice::Left, 32 | 1, Qt::UserRole - 1); // undocumented model internals allows enable - cb->setItemData (AudioDevice::Right, 32 | 1, Qt::UserRole - 1); - if (allowBoth) - { - cb->setItemData (AudioDevice::Both, 32 | 1, Qt::UserRole - 1); - } - } - else if (1 == n) - { - cb->setItemData (AudioDevice::Mono, 32 | 1, Qt::UserRole - 1); - } - } -} - -typedef QMap RigList; - -int rigCallback (rig_caps const * caps, void * cbData) -{ - RigList * rigs = reinterpret_cast (cbData); - - QString key (QString::fromLatin1 (caps->mfg_name).trimmed () - + ' '+ QString::fromLatin1 (caps->model_name).trimmed () - // + ' '+ QString::fromLatin1 (caps->version).trimmed () - // + " (" + QString::fromLatin1 (rig_strstatus (caps->status)).trimmed () + ')' - ); - - (*rigs)[key] = caps->rig_model; - - return 1; // keep them coming -} - -void DevSetup::enumerateRigs () -{ - RigList rigs; - rig_load_all_backends (); - rig_list_foreach (rigCallback, &rigs); - - for (RigList::const_iterator r = rigs.cbegin (); r != rigs.cend (); ++r) - { - ui->rigComboBox->addItem (r.key (), r.value ()); - } - - ui->rigComboBox->addItem ("DX Lab Suite Commander", 9998); - ui->rigComboBox->addItem ("Ham Radio Deluxe", 9999); - ui->rigComboBox->setCurrentIndex (ui->rigComboBox->findData (m_rig)); -} - -void DevSetup::on_cbEMEband_activated(int index) -{ - m_EMEbandIndex=index; - m_EMEband=ui->cbEMEband->itemText(index).toInt(); -} - -void DevSetup::on_cbBWmult_activated(int index) -{ - m_toneMultIndex=index; - m_toneMult=pow(2,index); -} - -void DevSetup::on_dtMinSpinBox_valueChanged(double arg1) -{ - m_DTmin=arg1; -} - -void DevSetup::on_dtMaxSpinBox_valueChanged(double arg1) -{ - m_DTmax=arg1; -} - -void DevSetup::on_astroFontSpinBox_valueChanged(int arg1) -{ - if(arg1==-999) m_astroFont=18; //silence compiler warning - m_astroFont=ui->astroFontSpinBox->value(); -} - -void DevSetup::on_cbDisplayAstroData_toggled(bool checked) -{ - m_bAstroData=checked; -} diff --git a/devsetup.h b/devsetup.h deleted file mode 100644 index 139628492..000000000 --- a/devsetup.h +++ /dev/null @@ -1,156 +0,0 @@ -#ifndef DEVSETUP_H -#define DEVSETUP_H - -#include -#include -#include -#include - -#include - -#include "rigclass.h" -#include "AudioDevice.hpp" - -int rigCallback (rig_caps const *, void *); - -namespace Ui { - class DevSetup; -} - -class QComboBox; - -class DevSetup : public QDialog -{ - Q_OBJECT; - - private: - Ui::DevSetup * ui; - -public: - DevSetup(QWidget *parent=0); - ~DevSetup(); - - void initDlg(); - - float m_DTmin; - float m_DTmax; - - qint32 m_idInt; - qint32 m_pttMethodIndex; - qint32 m_pttPort; - qint32 m_catPortIndex; - qint32 m_rig; - qint32 m_rigIndex; - qint32 m_serialRate; - qint32 m_serialRateIndex; - qint32 m_dataBits; - qint32 m_dataBitsIndex; - qint32 m_stopBits; - qint32 m_stopBitsIndex; - qint32 m_handshakeIndex; - qint32 m_test; - qint32 m_poll; - qint32 m_tmp; - qint32 m_EMEband; - qint32 m_EMEbandIndex; - qint32 m_toneMult; - qint32 m_toneMultIndex; - qint32 m_astroFont; - - typedef QList AudioDevices; - AudioDevices m_audioInputDevices; // available input devices - AudioDevices m_audioOutputDevices; // available output devices - QAudioDeviceInfo m_audioInputDevice; // selected input device - QAudioDeviceInfo m_audioOutputDevice; // selected output device - AudioDevice::Channel m_audioInputChannel; - AudioDevice::Channel m_audioOutputChannel; - - bool m_restartSoundIn; - bool m_restartSoundOut; - bool m_pskReporter; - bool m_firstCall; - bool m_catEnabled; - bool m_After73; - bool m_bRigOpen; - bool m_bDTR; - bool m_bRTS; - bool m_pttData; - bool m_bSplit; - bool m_bXIT; - bool m_bAstroData; - - QString m_myCall; - QString m_myGrid; - QString m_saveDir; - QString m_azelDir; - QString m_catPort; - QString m_handshake; - - QStringList m_macro; - QStringList m_dFreq; // per band frequency in MHz as a string - QStringList m_antDescription; // per band antenna description - QStringList m_bandDescription; // per band description - - QMessageBox msgBox0; - -public slots: - void accept(); - void reject(); - -private slots: - void on_myCallEntry_editingFinished(); - void on_myGridEntry_editingFinished(); - void on_cbPSKReporter_clicked(bool checked); - void on_pttMethodComboBox_activated(int index); - void on_catPortComboBox_activated(int index); - void on_cbEnableCAT_toggled(bool checked); - void on_serialRateComboBox_activated(int index); - void on_handshakeComboBox_activated(int index); - void on_handshakeComboBox_currentIndexChanged(int index); - void on_dataBitsComboBox_activated(int index); - void on_stopBitsComboBox_activated(int index); - void on_rigComboBox_activated(int index); - void on_cbID73_toggled(bool checked); - void on_testCATButton_clicked(); - void on_testPTTButton_clicked(); - void on_DTRCheckBox_toggled(bool checked); - void on_RTSCheckBox_toggled(bool checked); - void on_rbData_toggled(bool checked); - void on_pollSpinBox_valueChanged(int n); - void on_pttComboBox_currentIndexChanged(int index); - void on_pttMethodComboBox_currentIndexChanged(int index); - void on_cbSplit_toggled(bool checked); - void on_cbXIT_toggled(bool checked); - void on_cbEMEband_activated(int index); - void on_cbBWmult_activated(int index); - void on_dtMinSpinBox_valueChanged(double arg1); - void on_dtMaxSpinBox_valueChanged(double arg1); - void on_astroFontSpinBox_valueChanged(int arg1); - void on_cbDisplayAstroData_toggled(bool checked); - -private: - void loadAudioDevices (AudioDevices const&, QComboBox *, QAudioDeviceInfo const&, QAudioDeviceInfo const&); - void updateAudioChannels (QComboBox const *, int, QComboBox *, bool); - void enumerateRigs (); - Rig* rig; - void msgBox(QString t); - void setEnableAntennaDescriptions(bool enable); - void enableWidgets(); - void openRig(); - - friend int rigCallback (rig_caps const *, void *); -}; - -extern int ptt(int nport, int ntx, int* iptt, int* nopen); - -#ifdef WIN32 -extern "C" { - bool HRDInterfaceConnect(const wchar_t *host, const ushort); - void HRDInterfaceDisconnect(); - bool HRDInterfaceIsConnected(); - wchar_t* HRDInterfaceSendMessage(const wchar_t *msg); - void HRDInterfaceFreeString(const wchar_t *lstring); -} -#endif - -#endif // DEVSETUP_H diff --git a/devsetup.ui b/devsetup.ui deleted file mode 100644 index 9abc92652..000000000 --- a/devsetup.ui +++ /dev/null @@ -1,2835 +0,0 @@ - - - DevSetup - - - - 0 - 0 - 571 - 440 - - - - - 588 - 522 - - - - - 0 - 1 - - - - - 0 - 0 - - - - Configuration - - - - - - - 0 - 0 - - - - 0 - - - - Station - - - - - - - - - 0 - 0 - - - - - 80 - 20 - - - - - 80 - 16777215 - - - - My Call: - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 90 - 16777215 - - - - Station callsign - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 80 - 20 - - - - - 80 - 16777215 - - - - My Grid: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 60 - 16777215 - - - - Maidenhead locator - - - - - - - - - - - - - - - 0 - 0 - - - - - 80 - 0 - - - - - 80 - 16777215 - - - - PTT method: - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 90 - 16777215 - - - - Select method for T/R switching - - - 1 - - - - CAT - - - - - DTR - - - - - RTS - - - - - VOX - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 80 - 0 - - - - - 80 - 16777215 - - - - PTT Port: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 100 - 16777215 - - - - Select port for hardware T/R switching - - - - None - - - - - - - - - - - - - 0 - 0 - - - - - 160 - 0 - - - - - 160 - 16777215 - - - - Send spots to PSK Reporter web site - - - Enable PSK Reporter - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Send CW ID after a message with 73 or free text - - - CW ID after 73 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 20 - - - - CW ID Interval (min): - - - - - - - true - - - - 60 - 16777215 - - - - Select CW ID interval (0 for none) - - - 0 - - - 10 - - - - - - - - - Qt::Vertical - - - - 20 - 13 - - - - - - - - - - - 0 - 0 - - - - - 110 - 0 - - - - - 100 - 16777215 - - - - Enable computer control of transceiver - - - Enable CAT - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - Rig: - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - Select radio type - - - - - - - - - - - - 0 - 0 - - - - - 110 - 0 - - - - - 100 - 16777215 - - - - CAT port: - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 90 - 16777215 - - - - Select port for CAT control - - - 0 - - - - None - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 40 - 20 - - - - - - - - false - - - DTR - - - - - - - false - - - RTS - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 60 - 0 - - - - - 60 - 16777215 - - - - Data bits: - - - - - - - - 0 - 0 - - - - - 50 - 0 - - - - - 50 - 16777215 - - - - 1 - - - - 7 - - - - - 8 - - - - - - - - - - - - - 0 - 0 - - - - - 110 - 0 - - - - - 100 - 16777215 - - - - Serial rate: - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 90 - 16777215 - - - - 1 - - - - 1200 - - - - - 4800 - - - - - 9600 - - - - - 19200 - - - - - 38400 - - - - - 57600 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 40 - 20 - - - - - - - - Use special Tx command for "data" - - - Data - - - - - - - Use normal Tx command - - - Mic - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 60 - 0 - - - - - 60 - 16777215 - - - - Stop bits: - - - - - - - - 0 - 0 - - - - - 50 - 0 - - - - - 50 - 16777215 - - - - 1 - - - - 1 - - - - - 2 - - - - - - - - - - - - - 0 - 0 - - - - - 110 - 0 - - - - - 100 - 16777215 - - - - Handshake: - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 90 - 16777215 - - - - 2 - - - - None - - - - - XONXOFF - - - - - Hardware - - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 40 - 20 - - - - - - - - Split Tx - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 0 - - - - - 110 - 0 - - - - - 100 - 16777215 - - - - Check to see if CAT control is working - - - Test CAT Control - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 90 - 16777215 - - - - Test to see if T/R switching is working - - - Test PTT - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 40 - 20 - - - - - - - - XIT - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 60 - 0 - - - - Polling interval (s): - - - - - - - - 50 - 0 - - - - Select interval for reading dial frequency - - - 60 - - - - - - - - - Qt::Vertical - - - - 20 - 13 - - - - - - - - - - - - - 0 - 0 - - - - - 90 - 20 - - - - Save Directory: - - - - - - - Directory for saved audio files - - - C:\Users\joe\wsjt\map65\save - - - - - - - - - - - - 90 - 0 - - - - - 90 - 16777215 - - - - Audio In: - - - - - - - true - - - - 0 - 0 - - - - Select audio input device and driver API - - - - - - - - Mono - - - - - Left - - - - - Right - - - - - - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 90 - 16777215 - - - - Audio Out: - - - - - - - - 0 - 0 - - - - select audio output device and driver API - - - - - - - - 0 - 0 - - - - - Mono - - - - - Left - - - - - Right - - - - - Both - - - - - - - - - - - - - Tx Macros - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - 7 - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - 3 - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - 10W DPL 73 GL - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - 5 - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - 8 - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - 9 - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - 10 - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - 6 - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - 4 - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - 5W DPL 73 GL - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - 1 - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - - - 0 - 0 - - - - - 20 - 0 - - - - - 20 - 16777215 - - - - 2 - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Band Settings - - - - - - true - - - - - 0 - 0 - 510 - 449 - - - - - - - 0 - - - 6 - - - - - - 0 - 0 - - - - - 90 - 20 - - - - - 90 - 20 - - - - 5.2872 - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 90 - 20 - - - - 2200 m - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 90 - 20 - - - - - 90 - 20 - - - - 1.838 - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - - - Freq (MHz) - - - - - - - - 0 - 0 - - - - - 90 - 20 - - - - - 90 - 20 - - - - 14.076 - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 90 - 20 - - - - - 90 - 20 - - - - 3.578 - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 90 - 20 - - - - - 90 - 20 - - - - 10.130 - - - Qt::AlignCenter - - - - - - - - 0 - 20 - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 90 - 20 - - - - - 90 - 20 - - - - 0.4742 - - - Qt::AlignCenter - - - - - - - - - - - 0 - 0 - - - - - 90 - 20 - - - - - 90 - 20 - - - - 7.078 - - - Qt::AlignCenter - - - - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 90 - 20 - - - - - 90 - 20 - - - - 0.136 - - - Qt::AlignCenter - - - - - - - - - - - 90 - 20 - - - - 20 m - - - Qt::AlignCenter - - - - - - - Band - - - - - - - My Antenna - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - <html><head/><body><p>Antenna description</p></body></html> - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - 90 - 20 - - - - Qt::AlignCenter - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - JT9W - - - - - 140 - 101 - 202 - 161 - - - - - - - Band (MHz): - - - - - - - 9 - - - - 50 - - - - - 144 - - - - - 222 - - - - - 432 - - - - - 903 - - - - - 1296 - - - - - 2320 - - - - - 3400 - - - - - 5760 - - - - - 10368 - - - - - 24192 - - - - - - - - BW Mult: - - - - - - - 5 - - - - 1 - - - - - 2 - - - - - 4 - - - - - 8 - - - - - 16 - - - - - 32 - - - - - 64 - - - - - 128 - - - - - - - - DT min (s): - - - - - - - 1 - - - -2.500000000000000 - - - 5.000000000000000 - - - 0.100000000000000 - - - -2.500000000000000 - - - - - - - DT max (s): - - - - - - - 1 - - - -2.500000000000000 - - - 5.000000000000000 - - - 0.100000000000000 - - - 5.000000000000000 - - - - - - - Astro Font Size: - - - - - - - 14 - - - 18 - - - 2 - - - 18 - - - - - - - Display Astronomical Data on startup - - - - - - - - - - - - - 469 - 16777215 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - myCallEntry - myGridEntry - pttMethodComboBox - pttComboBox - cbPSKReporter - cbID73 - idIntSpinBox - cbEnableCAT - rigComboBox - catPortComboBox - dataBitsComboBox - serialRateComboBox - stopBitsComboBox - handshakeComboBox - testCATButton - testPTTButton - pollSpinBox - comboBoxSndIn - comboBoxSndOut - saveDirEntry - macro1 - macro2 - macro3 - macro4 - macro5 - macro6 - macro7 - macro8 - macro9 - macro10 - buttonBox - - - - - buttonBox - accepted() - DevSetup - accept() - - - 257 - 483 - - - 157 - 274 - - - - - buttonBox - rejected() - DevSetup - reject() - - - 325 - 483 - - - 286 - 274 - - - - - diff --git a/displaytext.cpp b/displaytext.cpp index 0120b1b3f..f91e1a6d0 100644 --- a/displaytext.cpp +++ b/displaytext.cpp @@ -3,6 +3,8 @@ #include #include +#include "moc_displaytext.cpp" + DisplayText::DisplayText(QWidget *parent) : QTextBrowser(parent) { @@ -19,7 +21,7 @@ void DisplayText::mouseDoubleClickEvent(QMouseEvent *e) } -void DisplayText::setFont(QFont font) +void DisplayText::setFont(QFont const& font) { QFontMetrics qfm(font); _fontWidth = qfm.averageCharWidth()+1; // the plus one is emperical diff --git a/displaytext.h b/displaytext.h index b4874557a..1307b37a9 100644 --- a/displaytext.h +++ b/displaytext.h @@ -12,7 +12,7 @@ class DisplayText : public QTextBrowser public: explicit DisplayText(QWidget *parent = 0); - void setFont(QFont font); + void setFont(QFont const& font); void insertLineSpacer(); void displayDecodedText(DecodedText decodedText, QString myCall, bool displayDXCCEntity, LogBook logBook); diff --git a/getfile.cpp b/getfile.cpp index ecc7bbb65..cf1dfa9ab 100644 --- a/getfile.cpp +++ b/getfile.cpp @@ -19,8 +19,10 @@ void getfile(QString fname, int ntrperiod) { - char name[80]; - strcpy(name,fname.toLatin1()); + char name[512]; + strncpy(name,fname.toLatin1(), sizeof (name) - 1); + name[sizeof (name) - 1] = '\0'; + FILE* fp=fopen(name,"rb"); int i0=fname.indexOf(".wav"); @@ -60,8 +62,9 @@ void savewav(QString fname, int ntrperiod) int npts=ntrperiod*12000; // qint16* buf=(qint16*)malloc(2*npts); - char name[80]; - strcpy(name,fname.toLatin1()); + char name[512]; + strncpy(name,fname.toLatin1(),sizeof (name) - 1); + name[sizeof (name) - 1] = '\0'; FILE* fp=fopen(name,"wb"); if(fp != NULL) { diff --git a/icons/Darwin/DragNDrop Background.png b/icons/Darwin/DragNDrop Background.png new file mode 100644 index 000000000..fc230f5cb Binary files /dev/null and b/icons/Darwin/DragNDrop Background.png differ diff --git a/icons/Darwin/wsjtx.iconset/icon_128x128.png b/icons/Darwin/wsjtx.iconset/icon_128x128.png new file mode 100644 index 000000000..9387de431 Binary files /dev/null and b/icons/Darwin/wsjtx.iconset/icon_128x128.png differ diff --git a/icons/Darwin/wsjtx.iconset/icon_128x128@2x.png b/icons/Darwin/wsjtx.iconset/icon_128x128@2x.png new file mode 100644 index 000000000..6b0df8fba Binary files /dev/null and b/icons/Darwin/wsjtx.iconset/icon_128x128@2x.png differ diff --git a/icons/Darwin/wsjtx.iconset/icon_16x16.png b/icons/Darwin/wsjtx.iconset/icon_16x16.png new file mode 100644 index 000000000..ecc5868e6 Binary files /dev/null and b/icons/Darwin/wsjtx.iconset/icon_16x16.png differ diff --git a/icons/Darwin/wsjtx.iconset/icon_16x16@2x.png b/icons/Darwin/wsjtx.iconset/icon_16x16@2x.png new file mode 100644 index 000000000..9a8ee28d8 Binary files /dev/null and b/icons/Darwin/wsjtx.iconset/icon_16x16@2x.png differ diff --git a/icons/Darwin/wsjtx.iconset/icon_256x256.png b/icons/Darwin/wsjtx.iconset/icon_256x256.png new file mode 100644 index 000000000..6b0df8fba Binary files /dev/null and b/icons/Darwin/wsjtx.iconset/icon_256x256.png differ diff --git a/icons/Darwin/wsjtx.iconset/icon_256x256@2x.png b/icons/Darwin/wsjtx.iconset/icon_256x256@2x.png new file mode 100644 index 000000000..f30a99d28 Binary files /dev/null and b/icons/Darwin/wsjtx.iconset/icon_256x256@2x.png differ diff --git a/icons/Darwin/wsjtx.iconset/icon_32x32.png b/icons/Darwin/wsjtx.iconset/icon_32x32.png new file mode 100644 index 000000000..9a8ee28d8 Binary files /dev/null and b/icons/Darwin/wsjtx.iconset/icon_32x32.png differ diff --git a/icons/Darwin/wsjtx.iconset/icon_32x32@2x.png b/icons/Darwin/wsjtx.iconset/icon_32x32@2x.png new file mode 100644 index 000000000..88b285eea Binary files /dev/null and b/icons/Darwin/wsjtx.iconset/icon_32x32@2x.png differ diff --git a/icons/Darwin/wsjtx.iconset/icon_512x512.png b/icons/Darwin/wsjtx.iconset/icon_512x512.png new file mode 100644 index 000000000..f30a99d28 Binary files /dev/null and b/icons/Darwin/wsjtx.iconset/icon_512x512.png differ diff --git a/icons/Darwin/wsjtx.iconset/icon_512x512@2x.png b/icons/Darwin/wsjtx.iconset/icon_512x512@2x.png new file mode 100644 index 000000000..b0b4be5d9 Binary files /dev/null and b/icons/Darwin/wsjtx.iconset/icon_512x512@2x.png differ diff --git a/icons/README b/icons/README new file mode 100644 index 000000000..dd9b1fcce --- /dev/null +++ b/icons/README @@ -0,0 +1,3 @@ +Files under this directory are generated. Even though they are checked +into source control they should not be modified directly. See +../artwork/README for details of generating these files. \ No newline at end of file diff --git a/icons/Unix/wsjtx_icon.png b/icons/Unix/wsjtx_icon.png new file mode 100644 index 000000000..9387de431 Binary files /dev/null and b/icons/Unix/wsjtx_icon.png differ diff --git a/icons/windows-icons/installer_logo.bmp b/icons/windows-icons/installer_logo.bmp new file mode 100644 index 000000000..ef0153143 Binary files /dev/null and b/icons/windows-icons/installer_logo.bmp differ diff --git a/icons/windows-icons/wsjtx.ico b/icons/windows-icons/wsjtx.ico new file mode 100644 index 000000000..142232ec7 Binary files /dev/null and b/icons/windows-icons/wsjtx.ico differ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt deleted file mode 100644 index e1eb94bfe..000000000 --- a/lib/CMakeLists.txt +++ /dev/null @@ -1,208 +0,0 @@ -cmake_minimum_required (VERSION 2.8.8) - -project (libjt9 C CXX Fortran) - -if (POLICY CMP0020) - cmake_policy (SET CMP0020 NEW) # link to Qt winmain on Windows -endif (POLICY CMP0020) - -# make sure that the default is a RELEASE -if (NOT CMAKE_BUILD_TYPE) - set (CMAKE_BUILD_TYPE RELEASE CACHE STRING - "Choose the type of build, options are: None Debug Release." - FORCE) -endif (NOT CMAKE_BUILD_TYPE) - -set (CMAKE_POSITION_INDEPENDENT_CODE ON) - - -# -# Fortran setup -# - -# FFLAGS depend on the compiler -get_filename_component (Fortran_COMPILER_NAME ${CMAKE_Fortran_COMPILER} NAME) - -if (Fortran_COMPILER_NAME MATCHES "gfortran.*") - # gfortran - set (CMAKE_Fortran_FLAGS_RELEASE "-funroll-all-loops -fno-f2c -O3") - set (CMAKE_Fortran_FLAGS_DEBUG "-fno-f2c -O0 -g") -elseif (Fortran_COMPILER_NAME MATCHES "ifort.*") - # ifort (untested) - set (CMAKE_Fortran_FLAGS_RELEASE "-f77rtl -O3") - set (CMAKE_Fortran_FLAGS_DEBUG "-f77rtl -O0 -g") -elseif (Fortran_COMPILER_NAME MATCHES "g77") - # g77 - set (CMAKE_Fortran_FLAGS_RELEASE "-funroll-all-loops -fno-f2c -O3 -m32") - set (CMAKE_Fortran_FLAGS_DEBUG "-fno-f2c -O0 -g -m32") -else (Fortran_COMPILER_NAME MATCHES "gfortran.*") - message ("CMAKE_Fortran_COMPILER full path: " ${CMAKE_Fortran_COMPILER}) - message ("Fortran compiler: " ${Fortran_COMPILER_NAME}) - message ("No optimized Fortran compiler flags are known, we just try -O2...") - set (CMAKE_Fortran_FLAGS_RELEASE "-O2") - set (CMAKE_Fortran_FLAGS_DEBUG "-O0 -g") -endif (Fortran_COMPILER_NAME MATCHES "gfortran.*") - - -# -# C++ setup -# - -if (UNIX) - SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") -endif (UNIX) - - -# -# setup and test Fortran C/C++ interaction -# - -include (FortranCInterface) -FortranCInterface_VERIFY (CXX QUIET) -FortranCInterface_HEADER (FC.h MACRO_NAMESPACE "FC_" SYMBOL_NAMESPACE "FC_" - SYMBOLS ) - - -set (FSRCS - afc65b.f90 - afc9.f90 - analytic.f90 - astro.f90 - astro0.f90 - astrosub.f90 - azdist.f90 - baddata.f90 - ccf2.f90 - ccf65.f90 - chkhist.f90 - chkss2.f90 - coord.f90 - db.f90 - dcoord.f90 - decode65a.f90 - decode65b.f90 - decode9.f90 - decoder.f90 - deg2grid.f90 - demod64a.f90 - determ.f90 - dot.f90 - downsam9.f90 - encode232.f90 - entail.f90 - extract.F90 - f77_wisdom.f90 - fano232.f90 - fchisq.f90 - fchisq65.f90 - fil3.f90 - fil4.f90 - fil6521.f90 - filbig.f90 - fillcom.f90 - flat1.f90 - flat2.f90 - flat3.f90 - flat65.f90 - four2a.f90 - gen65.f90 - genjt9.f90 - geocentric.f90 - geodist.f90 - getlags.f90 - getpfx1.f90 - getpfx2.f90 - graycode.f90 - graycode65.f90 - grid2deg.f90 - grid2k.f90 - grid2n.f90 - indexx.f90 - interleave63.f90 - interleave9.f90 - jt65a.f90 - k2grid.f90 - moon2.f90 - moondop.f90 - morse.f90 - move.f90 - n2grid.f90 - nchar.f90 - packbits.f90 - packcall.f90 - packgrid.f90 - packmsg.f90 - packtext.f90 - pctile.f90 - peakdt9.f90 - pfxdump.f90 - polfit.f90 - sec_midn.f90 - setup65.f90 - sleep_msec.f90 - smo.f90 - smo121.f90 - softsym.f90 - sort.f90 - ssort.f90 - stdmsg.f90 - sun.f90 - symspec.f90 - symspec2.f90 - symspec65.f90 - sync9.f90 - timer.f90 - tm2.f90 - toxyz.f90 - twkfreq.f90 - twkfreq65.f90 - unpackbits.f90 - unpackcall.f90 - unpackgrid.f90 - unpackmsg.f90 - unpacktext.f90 - zplot9.f90 - ) - -set (CSRCS - decode_rs.c - encode_rs.c - gran.c - igray.c - init_rs.c - tmoonsub.c - usleep.c - wrapkarn.c -) - -set (CXXSRCS - ipcomm.cpp - ) - - -add_definitions (-DBIGSYM=1) -set_source_files_properties (sec_midn.f90 PROPERTIES COMPILE_FLAGS -fno-second-underscore) - - -# -# build our targets -# -add_library (jt9impl STATIC ${FSRCS} ${CSRCS} ${CXXSRCS}) -qt5_use_modules (jt9impl Core) - -add_executable (jt9sim jt9sim.f90 ../wsjtx.rc) -target_link_libraries (jt9sim jt9impl) - -add_executable (jt9code jt9code.f90 ../wsjtx.rc) -target_link_libraries (jt9code jt9impl) - -add_executable (jt9 jt9.f90 jt9a.f90 jt9b.f90 jt9c.f90 ../wsjtx.rc) -target_link_libraries (jt9 jt9impl ${fftw3f_LIBRARIES}) -add_dependencies (jt9 fftw3f) -qt5_use_modules (jt9 Core) - -install ( - TARGETS jt9 - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ) diff --git a/lib/decoder.f90 b/lib/decoder.f90 index 6127559cb..746782ca6 100644 --- a/lib/decoder.f90 +++ b/lib/decoder.f90 @@ -2,6 +2,8 @@ subroutine decoder(ss,id2) ! Decoder for JT9. + use prog_args + include 'constants.f90' real ss(184,NSMAX) character*22 msg diff --git a/lib/extract.F90 b/lib/extract.F90 index 942ea1004..978bc6dca 100644 --- a/lib/extract.F90 +++ b/lib/extract.F90 @@ -1,5 +1,7 @@ subroutine extract(s3,nadd,ncount,nhist,decoded,ltext,nbmkv) + use prog_args + real s3(64,63) character decoded*22 integer era(51),dat4(12),indx(64) @@ -76,10 +78,15 @@ subroutine extract(s3,nadd,ncount,nhist,decoded,ltext,nbmkv) write(22,rec=2) -1,-1,dat4 call flush(22) call timer('kvasd ',0) -#ifdef UNIX - iret=system('./kvasd -q > dev_null') + +! TODO G4WJS: Take out '-q' argument once kvasd 1.12 is available for Mac and in the repo +! where CMake fetches it from. +#ifdef WIN32 + iret=system('""'//trim(exe_dir)//'/kvasd" -q >dev_null"') +! iret=system('""'//trim(exe_dir)//'/kvasd" kvasd.dat >dev_null"') #else - iret=system('kvasd -q > dev_null') + iret=system('"'//trim(exe_dir)//'/kvasd" -q >/dev/null') +! iret=system('"'//trim(exe_dir)//'/kvasd" kvasd.dat >/dev/null') #endif call timer('kvasd ',1) if(iret.ne.0) then diff --git a/lib/jt9.f90 b/lib/jt9.f90 index 2265f7576..893dfe71e 100644 --- a/lib/jt9.f90 +++ b/lib/jt9.f90 @@ -3,44 +3,73 @@ program jt9 ! Decoder for JT9. Can run stand-alone, reading data from *.wav files; ! or as the back end of wsjt-x, with data placed in a shared memory region. + use options + use prog_args + include 'constants.f90' integer*4 ihdr(11) real*4 s(NSMAX) integer*2 id2 - character*80 arg,ldir,infile + character c + character(len=500) optarg, infile + integer*4 arglen,stat,offset,remain + logical :: shmem = .false., read_files = .false., have_args = .false. common/jt9com/ss(184,NSMAX),savg(NSMAX),id2(NMAX),nutc,ndiskdat,ntr, & mousefqso,newdat,nfa,nfsplit,nfb,ntol,kin,nzhsym,nsynced,ndecoded common/tracer/limtrace,lu - nargs=iargc() - if(nargs.lt.1) then - print*,'Usage: jt9 TRperiod ndepth rxfreq file1 [file2 ...]' + do + call getopt('s:e:a:r:p:d:f:',(/type(option) ::/),c,optarg,arglen,stat,offset,remain) + if (stat .ne. 0) then + exit + end if + have_args = .true. + select case (c) + case ('s') + shmem = .true. + shm_key = optarg(:arglen) + + case ('e') + exe_dir = optarg(:arglen) + + case ('a') + data_dir = optarg(:arglen) + + case ('p') + read_files = .true. + read (optarg(:arglen), *) ntrperiod + + case ('d') + read_files = .true. + read (optarg(:arglen), *) ndepth + + case ('f') + read_files = .true. + read (optarg(:arglen), *) nrxfreq + end select + end do + + if (.not. have_args .or. (stat .lt. 0 .or. (shmem .and. remain .gt. 0) & + .or. (read_files .and. remain .eq. 0) .or. (shmem .and. read_files))) then + print*,'Usage: jt9 -p TRperiod -d ndepth -f rxfreq -e exe_dir file1 [file2 ...]' print*,' Reads data from *.wav files.' print*,'' - print*,' jt9 -s ' + print*,' jt9 -s -e exe_dir' print*,' Gets data from shared memory region with key==' go to 999 endif - call getarg(1,arg) - if(arg(1:2).eq.'-s') then -! Multiple instances: - call getarg(2,arg) - call getarg(3,ldir) - call jt9a(trim(arg),trim(ldir)) + + if (shmem) then + call jt9a() go to 999 endif - read(arg,*) ntrperiod - call getarg(2,arg) - read(arg,*) ndepth - call getarg(3,arg) - read(arg,*) nrxfreq - ifile1=4 limtrace=0 lu=12 - do ifile=ifile1,nargs - call getarg(ifile,infile) + do iarg = offset + 1, offset + remain + call get_command_argument (iarg, optarg, arglen) + infile = optarg(:arglen) open(10,file=infile,access='stream',status='old',err=998) read(10) ihdr nutc0=ihdr(1) !Silence compiler warning @@ -71,8 +100,8 @@ program jt9 k=0 nhsym0=-999 npts=(60*ntrperiod-6)*12000 - if(ifile.eq.ifile1) then - open(12,file='timer.out',status='unknown') + if(iarg .eq. offset + 1) then + open(12,file=trim(data_dir)//'/timer.out',status='unknown') call timer('jt9 ',0) endif diff --git a/lib/jt9a.f90 b/lib/jt9a.f90 index 34f1a46ff..eb9010893 100644 --- a/lib/jt9a.f90 +++ b/lib/jt9a.f90 @@ -1,7 +1,6 @@ -subroutine jt9a(thekey,ldir) +subroutine jt9a() - character(len=*), intent(in):: thekey - character(len=*), intent(in):: ldir + use prog_args ! These routines connect the shared memory region to the decoder. interface @@ -21,29 +20,29 @@ subroutine jt9a(thekey,ldir) common/tracer/limtrace,lu ! Multiple instances: - i0 = len(trim(thekey)) + i0 = len(trim(shm_key)) call getcwd(cwd) - open(12,file='timer.out',status='unknown') + open(12,file=trim(data_dir)//'/timer.out',status='unknown') limtrace=0 ! limtrace=-1 !Disable all calls to timer() lu=12 ! Multiple instances: set the shared memory key before attaching - mykey=trim(repeat(thekey,1)) + mykey=trim(repeat(shm_key,1)) i0 = len(mykey) i0=setkey_jt9(trim(mykey)) i1=attach_jt9() -10 inquire(file=trim(ldir)//'/.lock',exist=fileExists) +10 inquire(file='.lock',exist=fileExists) if(fileExists) then call sleep_msec(100) go to 10 endif - inquire(file=trim(ldir)//'/.quit',exist=fileExists) + inquire(file='.quit',exist=fileExists) if(fileExists) then ! call ftnquit i1=detach_jt9() @@ -62,7 +61,7 @@ subroutine jt9a(thekey,ldir) call jt9b(p_jt9,nbytes) call timer('jt9b ',1) -100 inquire(file=trim(ldir)//'/.lock',exist=fileExists) +100 inquire(file='.lock',exist=fileExists) if(fileExists) go to 10 call sleep_msec(100) go to 100 diff --git a/lib/jt9c.f90 b/lib/jt9c.f90 index 0fa3e76a6..f1c447128 100644 --- a/lib/jt9c.f90 +++ b/lib/jt9c.f90 @@ -14,7 +14,7 @@ subroutine jt9c(ss,savg,id2,nparams0) nparams=nparams0 !Copy parameters into common/npar/ call flush(6) -! if(sum(nparams).ne.0) call decoder(ss,id2) +! if(sum(nparams).ne.0) call decoder(ss,id2,ldir) call decoder(ss,id2) return diff --git a/lib/options.f90 b/lib/options.f90 new file mode 100644 index 000000000..7a04df16f --- /dev/null +++ b/lib/options.f90 @@ -0,0 +1,337 @@ +module options + ! + ! Source code copied from: + ! http://fortranwiki.org/fortran/show/Command-line+arguments + ! + implicit none + + type option + !> Long name. + character(len=100) :: name + !> Does the option require an argument? + logical :: has_arg + !> Corresponding short name. + character :: chr + !> Description. + character(len=500) :: descr + !> Argument name, if required. + character(len=20) :: argname + contains + procedure :: print => print_opt + end type option + +contains + + !> Parse command line options. Options and their arguments must come before + !> all non-option arguments. Short options have the form "-X", long options + !> have the form "--XXXX..." where "X" is any character. Parsing can be + !> stopped with the option '--'. + !> The following code snippet illustrates the intended use: + !> \code + !> do + !> call getopt (..., optchar=c, ...) + !> if (stat /= 0) then + !> ! optional error handling + !> exit + !> end if + !> select case (c) + !> ! process options + !> end select + !> end do + !> \endcode + subroutine getopt (options, longopts, optchar, optarg, arglen, stat, & + offset, remain, err) + use iso_fortran_env, only: error_unit + + !> String containing the characters that are valid short options. If + !> present, command line arguments are scanned for those options. + !> If a character is followed by a colon (:) its corresponding option + !> requires an argument. E.g. "vn:" defines two options -v and -n with -n + !> requiring an argument. + character(len=*), intent(in), optional :: options + + !> Array of long options. If present, options of the form '--XXXX...' are + !> recognised. Each option has an associated option character. This can be + !> any character of default kind, it is just an identifier. It can, but + !> doesn't have to, match any character in the options argument. In fact it + !> is possible to only pass long options and no short options at all. + !> Only name, has_arg and chr need to be set. + type(option), intent(in), optional :: longopts(:) + + !> If stat is not 1, optchar contains the option character that was parsed. + !> Otherwise its value is undefined. + character, intent(out), optional :: optchar + + !> If stat is 0 and the parsed option requires an argument, optarg contains + !> the first len(optarg) (but at most 500) characters of that argument. + !> Otherwise its value is undefined. If the arguments length exceeds 500 + !> characters and err is .true., a warning is issued. + character(len=*), intent(out), optional :: optarg + + !> If stat is 0 and the parsed option requires an argument, arglen contains + !> the actual length of that argument. Otherwise its value is undefined. + !> This can be used to make sure the argument was not truncated by the + !> limited length of optarg. + integer, intent(out), optional :: arglen + + !> Status indicator. Can have the following values: + !> - 0: An option was successfully parsed. + !> - 1: Parsing stopped successfully because a non-option or '--' was + !> encountered. + !> - -1: An unrecognised option was encountered. + !> - -2: A required argument was missing. + !> . + !> Its value is never undefined. + integer, intent(out), optional :: stat + + !> If stat is 1, offset contains the number of the argument before the + !> first non-option argument, i.e. offset+n is the nth non-option argument. + !> If stat is not 1, offset contains the number of the argument that would + !> be parsed in the next call to getopt. This number can be greater than + !> the actual number of arguments. + integer, intent(out), optional :: offset + + !> If stat is 1, remain contains the number of remaining non-option + !> arguments, i.e. the non-option arguments are in the range + !> (offset+1:offset+remain). If stat is not 1, remain is undefined. + integer, intent(out), optional :: remain + + !> If err is present and .true., getopt prints messages to the standard + !> error unit if an error is encountered (i.e. whenever stat would be set + !> to a negative value). + logical, intent(in), optional :: err + + integer, save :: pos = 1, cnt = 0 + character(len=500), save :: arg + + integer :: chrpos, length, st, id + character :: chr + logical :: long + + if (cnt == 0) cnt = command_argument_count() + long = .false. + + ! no more arguments left + if (pos > cnt) then + pos = pos - 1 + st = 1 + goto 10 + end if + + call get_command_argument (pos, arg, length) + + ! is argument an option? + if (arg(1:1) == '-') then + + chr = arg(2:2) + + ! too long ('-xxxx...') for one dash? + if (chr /= '-' .and. len_trim(arg) > 2) then + st = -1 + goto 10 + end if + + ! forced stop ('--') + if (chr == '-' .and. arg(3:3) == ' ') then + st = 1 + goto 10 + end if + + ! long option ('--xxx...') + if (chr == '-') then + + long = .true. + + ! check if valid + id = lookup(arg(3:)) + + ! option is invalid, stop + if (id == 0) then + st = -1 + goto 10 + end if + + chr = longopts(id)%chr + + ! check if option requires an argument + if (.not. longopts(id)%has_arg) then + st = 0 + goto 10 + end if + + ! check if there are still arguments left + if (pos == cnt) then + st = -2 + goto 10 + end if + + ! go to next position + pos = pos + 1 + + ! get argument + call get_command_argument (pos, arg, length) + + ! make sure it is not an option + if (arg(1:1) == '-') then + st = -2 + pos = pos - 1 + goto 10 + end if + + end if + + ! short option + ! check if valid + if (present(options)) then + chrpos = scan(options, chr) + else + chrpos = 0 + end if + + ! option is invalid, stop + if (chrpos == 0) then + st = -1 + goto 10 + end if + + ! look for argument requirement + if (chrpos < len_trim(options)) then + if (options(chrpos+1:chrpos+1) == ':') then + + ! check if there are still arguments left + if (pos == cnt) then + st = -2 + goto 10 + end if + + ! go to next position + pos = pos + 1 + + ! get argument + call get_command_argument (pos, arg, length) + + ! make sure it is not an option + if (arg(1:1) == '-') then + st = -2 + pos = pos - 1 + goto 10 + end if + + end if + end if + + ! if we get to this point, no error happened + ! return option and the argument (if there is one) + st = 0 + goto 10 + end if + + ! not an option, parsing stops + st = 1 + ! we are already at the first non-option argument + ! go one step back to the last option or option argument + pos = pos - 1 + + + ! error handling and setting of return values +10 continue + + if (present(err)) then + if (err) then + + select case (st) + case (-1) + write (error_unit, *) "error: unrecognised option: " // trim(arg) + case (-2) + if (.not. long) then + write (error_unit, *) "error: option -" // chr & + // " requires an argument" + else + write (error_unit, *) "error: option --" & + // trim(longopts(id)%name) // " requires an argument" + end if + end select + + end if + end if + + if (present(optchar)) optchar = chr + if (present(optarg)) optarg = arg + if (present(arglen)) arglen = length + if (present(stat)) stat = st + if (present(offset)) offset = pos + if (present(remain)) remain = cnt-pos + + ! setup pos for next call to getopt + pos = pos + 1 + + contains + + integer function lookup (name) + character(len=*), intent(in) :: name + integer :: i + + ! if there are no long options, skip the loop + if (.not. present(longopts)) goto 10 + + do i = 1, size(longopts) + if (name == longopts(i)%name) then + lookup = i + return + end if + end do + ! if we get to this point, the option was not found + +10 lookup = 0 + end function lookup + + end subroutine getopt + + !============================================================================ + + !> Print an option in the style of a man page. I.e. + !> \code + !> -o arg + !> --option arg + !> description................................................................. + !> ............................................................................ + !> \endcode + subroutine print_opt (opt, unit) + !> the option + class(option), intent(in) :: opt + !> logical unit number + integer, intent(in) :: unit + + integer :: l, c1, c2 + + if (opt%has_arg) then + write (unit, '(1x,"-",a,1x,a)') opt%chr, trim(opt%argname) + write (unit, '(1x,"--",a,1x,a)') trim(opt%name), trim(opt%argname) + else + write (unit, '(1x,"-",a)') opt%chr + write (unit, '(1x,"--",a)') trim(opt%name) + end if + l = len_trim(opt%descr) + + ! c1 is the first character of the line + ! c2 is one past the last character of the line + c1 = 1 + do + if (c1 > l) exit + ! print at maximum 4+76 = 80 characters + c2 = min(c1 + 76, 500) + ! if not at the end of the whole string + if (c2 /= 500) then + ! find the end of a word + do + if (opt%descr(c2:c2) == ' ') exit + c2 = c2-1 + end do + end if + write (unit, '(4x,a)') opt%descr(c1:c2-1) + c1 = c2+1 + end do + + end subroutine print_opt + +end module options diff --git a/lib/prog_args.f90 b/lib/prog_args.f90 new file mode 100644 index 000000000..163a76472 --- /dev/null +++ b/lib/prog_args.f90 @@ -0,0 +1,4 @@ +MODULE prog_args + CHARACTER(len=80) :: shm_key + CHARACTER(len=500) :: exe_dir = '.', data_dir = '.' +END MODULE prog_args diff --git a/lib/tmoonsub.c b/lib/tmoonsub.c index 3b171b540..429313348 100644 --- a/lib/tmoonsub.c +++ b/lib/tmoonsub.c @@ -358,8 +358,9 @@ void libration(double day, double lambda, double beta, double alpha, double *l, Returns: position angle of bright limb wrt NCP, percentage illumination of Sun */ -void illumination(double day, double lra, double ldec, double dr, double sra, double sdec, double *pabl, double *ill) { +void illumination(double day , double lra, double ldec, double dr, double sra, double sdec, double *pabl, double *ill) { double x, y, phi, i; + (void)day; y = cos(sdec) * sin(sra - lra); x = sin(sdec) * cos(ldec) - cos(sdec) * sin(ldec) * cos (sra - lra); *pabl = atan22(y, x); @@ -446,8 +447,9 @@ replaces ecliptic coordinates with equatorial coordinates note: call by reference destroys original values R is unchanged. */ -void equatorial(double d, double *lon, double *lat, double *r) { +void equatorial(double d, double *lon, double *lat, double * r) { double eps, ceps, seps, l, b; + (void)r; l = *lon; b = * lat; @@ -462,8 +464,10 @@ void equatorial(double d, double *lon, double *lat, double *r) { replaces equatorial coordinates with ecliptic ones. Inverse of above, but used to find topocentric ecliptic coords. */ -void ecliptic(double d, double *lon, double *lat, double *r) { +void ecliptic(double d, double *lon, double *lat, double * r) { double eps, ceps, seps, alp, dec; + (void)r; + alp = *lon; dec = *lat; eps = epsilon(d); diff --git a/lib/usleep.c b/lib/usleep.c index cdaa7f909..21d242a68 100644 --- a/lib/usleep.c +++ b/lib/usleep.c @@ -1,3 +1,5 @@ +#include + /* usleep(3) */ void usleep_(unsigned long *microsec) { diff --git a/libHRDInterface001.a b/libHRDInterface001.a deleted file mode 100644 index f3cbd2f01..000000000 Binary files a/libHRDInterface001.a and /dev/null differ diff --git a/logbook/logbook.cpp b/logbook/logbook.cpp index e0e6d50c6..70b1b42d4 100644 --- a/logbook/logbook.cpp +++ b/logbook/logbook.cpp @@ -1,34 +1,48 @@ #include "logbook.h" #include #include +#include - -void LogBook::init() +namespace { - const QString logFilename = "wsjtx_log.adi"; //TODO get from user - const QString countryDataFilename = "cty.dat"; //TODO get from user + auto logFileName = "wsjtx_log.adi"; + auto countryFileName = "cty.dat"; +} - _countries.init(countryDataFilename); - _countries.load(); +void LogBook::init(QDir const& dataPath) +{ + QString countryDataFilename; + if (dataPath.exists (countryFileName)) + { + // User override + countryDataFilename = dataPath.absoluteFilePath (countryFileName); + } + else + { + countryDataFilename = QString {":/"} + countryFileName; + } - _worked.init(_countries.getCountryNames()); + _countries.init(countryDataFilename); + _countries.load(); - _log.init(logFilename); - _log.load(); + _worked.init(_countries.getCountryNames()); - _setAlreadyWorkedFromLog(); + _log.init(dataPath.absoluteFilePath (logFileName)); + _log.load(); -/* + _setAlreadyWorkedFromLog(); + + /* int QSOcount = _log.getCount(); int count = _worked.getWorkedCount(); qDebug() << QSOcount << "QSOs and" << count << "countries worked in file" << logFilename; -*/ + */ -// QString call = "ok1ct"; -// QString countryName; -// bool callWorkedBefore,countryWorkedBefore; -// match(/*in*/call, /*out*/ countryName,callWorkedBefore,countryWorkedBefore); -// qDebug() << countryName; + // QString call = "ok1ct"; + // QString countryName; + // bool callWorkedBefore,countryWorkedBefore; + // match(/*in*/call, /*out*/ countryName,callWorkedBefore,countryWorkedBefore); + // qDebug() << countryName; } @@ -38,45 +52,45 @@ void LogBook::_setAlreadyWorkedFromLog() QList calls = _log.getCallList(); QString c; foreach(c,calls) - { + { QString countryName = _countries.find(c); if (countryName.length() > 0) - { + { _worked.setAsWorked(countryName); //qDebug() << countryName << " worked " << c; - } - } + } + } } void LogBook::match(/*in*/const QString call, - /*out*/ QString &countryName, - bool &callWorkedBefore, - bool &countryWorkedBefore) + /*out*/ QString &countryName, + bool &callWorkedBefore, + bool &countryWorkedBefore) { - if (call.length() > 0) + if (call.length() > 0) { - QString currentMode = "JT9"; // JT65 == JT9 in ADIF::match() - QString currentBand = ""; // match any band - callWorkedBefore = _log.match(call,currentBand,currentMode); - countryName = _countries.find(call); - if (countryName.length() > 0) // country was found - countryWorkedBefore = _worked.getHasWorked(countryName); - else + QString currentMode = "JT9"; // JT65 == JT9 in ADIF::match() + QString currentBand = ""; // match any band + callWorkedBefore = _log.match(call,currentBand,currentMode); + countryName = _countries.find(call); + if (countryName.length() > 0) // country was found + countryWorkedBefore = _worked.getHasWorked(countryName); + else { countryName = "where?"; //error: prefix not found countryWorkedBefore = false; } } - //qDebug() << "Logbook:" << call << ":" << countryName << "Cty B4:" << countryWorkedBefore << "call B4:" << callWorkedBefore; + //qDebug() << "Logbook:" << call << ":" << countryName << "Cty B4:" << countryWorkedBefore << "call B4:" << callWorkedBefore; } void LogBook::addAsWorked(const QString call, const QString band, const QString mode, const QString date) { - //qDebug() << "adding " << call << " as worked"; - _log.add(call,band,mode,date); - QString countryName = _countries.find(call); - if (countryName.length() > 0) - _worked.setAsWorked(countryName); + //qDebug() << "adding " << call << " as worked"; + _log.add(call,band,mode,date); + QString countryName = _countries.find(call); + if (countryName.length() > 0) + _worked.setAsWorked(countryName); } diff --git a/logbook/logbook.h b/logbook/logbook.h index 4e61305f4..a087c3e46 100644 --- a/logbook/logbook.h +++ b/logbook/logbook.h @@ -14,10 +14,12 @@ #include "countriesworked.h" #include "adif.h" +class QDir; + class LogBook { public: - void init(); + void init(QDir const& dataPath); void match(/*in*/ const QString call, /*out*/ QString &countryName, bool &callWorkedBefore, diff --git a/logqso.cpp b/logqso.cpp index 707466c9a..a675230a8 100644 --- a/logqso.cpp +++ b/logqso.cpp @@ -1,14 +1,22 @@ #include "logqso.h" -#include "ui_logqso.h" -#include "logbook/adif.h" + #include +#include +#include #include +#include "Configuration.hpp" +#include "logbook/adif.h" -LogQSO::LogQSO(QSettings * settings, QWidget *parent) : +#include "ui_logqso.h" + +#include "moc_logqso.cpp" + +LogQSO::LogQSO(QSettings * settings, Configuration const * configuration, QWidget *parent) : QDialog(parent), ui(new Ui::LogQSO), - m_settings (settings) + m_settings (settings), + m_configuration (configuration) { ui->setupUi(this); @@ -101,19 +109,20 @@ void LogQSO::accept() //Log this QSO to ADIF file "wsjtx_log.adi" QString filename = "wsjtx_log.adi"; // TODO allow user to set ADIF adifile; - adifile.init(filename); + auto adifilePath = m_configuration->data_path ().absoluteFilePath (filename); + adifile.init(adifilePath); if (!adifile.addQSOToFile(hisCall,hisGrid,mode,rptSent,rptRcvd,date,time,band,comments,name,strDialFreq,m_myCall,m_myGrid,m_txPower)) { QMessageBox m; - m.setText("Cannot open file \"wsjtx_log.adi\"."); + m.setText("Cannot open file \"" + adifilePath + "\"."); m.exec(); } //Log this QSO to file "wsjtx.log" - QFile f("wsjtx.log"); + QFile f(m_configuration->data_path ().absoluteFilePath ("wsjtx.log")); if(!f.open(QIODevice::Text | QIODevice::Append)) { QMessageBox m; - m.setText("Cannot open file \"wsjtx.log\"."); + m.setText("Cannot open file \"" + f.fileName () + "\"."); m.exec(); } else { QString logEntry=m_dateTime.date().toString("yyyy-MMM-dd,") + diff --git a/logqso.h b/logqso.h index a0c1a7b7c..98816a68f 100644 --- a/logqso.h +++ b/logqso.h @@ -14,13 +14,14 @@ namespace Ui { } class QSettings; +class Configuration; class LogQSO : public QDialog { Q_OBJECT public: - explicit LogQSO(QSettings *, QWidget *parent = 0); + explicit LogQSO(QSettings *, Configuration const *, QWidget *parent = 0); ~LogQSO(); void initLogQSO(QString hisCall, QString hisGrid, QString mode, QString rptSent, QString rptRcvd, QDateTime dateTime, @@ -43,6 +44,7 @@ private: QScopedPointer ui; QSettings * m_settings; + Configuration const * m_configuration; QString m_txPower; QString m_comments; double m_dialFreq; diff --git a/main.cpp b/main.cpp index ad348a014..5f8d552a0 100644 --- a/main.cpp +++ b/main.cpp @@ -1,3 +1,10 @@ +#include +#include +#include +#include + +#include + #ifdef QT5 #include #else @@ -6,72 +13,110 @@ #include #include #include +#include #include +#include +#include +#include "SettingsGroup.hpp" +#include "TraceFile.hpp" #include "mainwindow.h" - // Multiple instances: QSharedMemory mem_jt9; -QUuid my_uuid; QString my_key; int main(int argc, char *argv[]) { - QApplication a(argc, argv); + try + { + QApplication a(argc, argv); + setlocale (LC_NUMERIC, "C"); // ensure number forms are in + // consistent format, do this after + // instantiating QApplication so + // that GUI has correct l18n - qRegisterMetaType ("AudioDevice::Channel"); + bool multiple {false}; - QSettings settings(a.applicationDirPath() + "/wsjtx.ini", QSettings::IniFormat); - - QFile f("fonts.txt"); - qint32 fontSize,fontWeight,fontSize2,fontWeight2; // Defaults 8 50 10 50 - fontSize2=10; - fontWeight2=50; - if(f.open(QIODevice::ReadOnly)) { - QTextStream in(&f); - in >> fontSize >> fontWeight >> fontSize2 >> fontWeight2; - f.close(); - QFont font=a.font(); - if(fontSize!=8) font.setPointSize(fontSize); - font.setWeight(fontWeight); //Set the GUI fonts - a.setFont(font); - } - - // Create and initialize shared memory segment - // Multiple instances: generate shared memory keys with UUID - my_uuid = QUuid::createUuid(); - my_key = my_uuid.toString(); - mem_jt9.setKey(my_key); - - if(!mem_jt9.attach()) { - if (!mem_jt9.create(sizeof(jt9com_))) { - QMessageBox::critical( 0, "Error", "Unable to create shared memory segment."); - exit(1); - } - } - char *to = (char*)mem_jt9.data(); - int size=sizeof(jt9com_); - if(jt9com_.newdat==0) { - } - memset(to,0,size); //Zero all decoding params in shared memory - - settings.beginGroup ("Tune"); - - // deal with Windows Vista input audio rate converter problems - unsigned downSampleFactor = settings.value ("Audio/DisableInputResampling", -#if defined (Q_OS_WIN) - QSysInfo::WV_VISTA == QSysInfo::WindowsVersion ? true : false -#else - false +#if WSJT_STANDARD_FILE_LOCATIONS + // support for multiple instances running from a single installation + auto args = a.arguments (); + auto rig_arg_index = args.lastIndexOf (QRegularExpression {R"((-r)|(--r(i?(g?))))"}); + if (rig_arg_index > 0 // not interested if somehow the exe is called -r or --rig + && rig_arg_index + 1 < args.size ()) + { + auto temp_name = args.at (rig_arg_index + 1); + if ('-' != temp_name[0] + && !temp_name.isEmpty ()) + { + a.setApplicationName (a.applicationName () + " - " + temp_name); + } + multiple = true; + } #endif - ).toBool () ? 1u : 4u; - settings.endGroup (); -// Multiple instances: Call MainWindow() with the UUID key - MainWindow w(&settings, &mem_jt9, &my_key, fontSize2, fontWeight2, downSampleFactor); - w.show(); + auto config_directory = QStandardPaths::writableLocation (QStandardPaths::ConfigLocation); + QDir config_path {config_directory}; // will be "." if config_directory is empty + if (!config_path.mkpath (".")) + { + throw std::runtime_error {"Cannot find a usable configuration path \"" + config_path.path ().toStdString () + '"'}; + } + QSettings settings(config_path.absoluteFilePath (a.applicationName () + ".ini"), QSettings::IniFormat); - QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit())); - return a.exec(); + // // open a trace file + // TraceFile trace_file {QDir {QApplication::applicationDirPath ()}.absoluteFilePath ("wsjtx_trace.log")}; + + // // announce to log file + // qDebug () << "WSJT-X v" WSJTX_STRINGIZE (WSJTX_VERSION_MAJOR) "." WSJTX_STRINGIZE (WSJTX_VERSION_MINOR) "." WSJTX_STRINGIZE (WSJTX_VERSION_PATCH) ", " WSJTX_STRINGIZE (SVNVERSION) " - Program startup"; + + // Create and initialize shared memory segment + // Multiple instances: use rig_name as shared memory key + my_key = a.applicationName (); + mem_jt9.setKey(my_key); + + if(!mem_jt9.attach()) { + if (!mem_jt9.create(sizeof(jt9com_))) { + QMessageBox::critical( 0, "Error", "Unable to create shared memory segment."); + exit(1); + } + } + char *to = (char*)mem_jt9.data(); + int size=sizeof(jt9com_); + if(jt9com_.newdat==0) { + } + memset(to,0,size); //Zero all decoding params in shared memory + + unsigned downSampleFactor; + { + SettingsGroup {&settings, "Tune"}; + + // deal with Windows Vista and earlier input audio rate + // converter problems + downSampleFactor = settings.value ("Audio/DisableInputResampling", +#if defined (Q_OS_WIN) + // default to true for + // Windows Vista and older + QSysInfo::WV_VISTA >= QSysInfo::WindowsVersion ? true : false +#else + false +#endif + ).toBool () ? 1u : 4u; + } + + MainWindow w(multiple, &settings, &mem_jt9, my_key, downSampleFactor); + w.show(); + + QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit())); + return a.exec(); + } + catch (std::exception const& e) + { + std::cerr << "Error: " << e.what () << '\n'; + } + catch (...) + { + std::cerr << "Unexpected error\n"; + throw; // hoping the runtime might tell us more about the exception + } + return -1; } diff --git a/mainwindow.cpp b/mainwindow.cpp index 8f922c353..7cbdcf65a 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,13 +1,18 @@ //--------------------------------------------------------- MainWindow #include "mainwindow.h" -#include "ui_mainwindow.h" + +#include +#include #include -#include +#include +#include +#ifdef QT5 +#include +#endif #include "soundout.h" -#include "devsetup.h" #include "plotter.h" #include "about.h" #include "astro.h" @@ -15,10 +20,14 @@ #include "sleep.h" #include "getfile.h" #include "logqso.h" +#include "Bands.hpp" +#include "TransceiverFactory.hpp" +#include "FrequencyList.hpp" +#include "StationList.hpp" +#include "LiveFrequencyValidator.hpp" +#include "FrequencyItemDelegate.hpp" -#ifdef QT5 -#include -#endif +#include "ui_mainwindow.h" int itone[NUM_JT65_SYMBOLS]; //Audio tones for all Tx symbols int icw[NUM_CW_SYMBOLS]; //Dits for CW ID @@ -29,52 +38,98 @@ qint32 g_COMportOpen; qint32 g_iptt; static int nc1=1; wchar_t buffer[256]; -Astro* g_pAstro = NULL; -Rig* rig = NULL; QTextEdit* pShortcuts; QTextEdit* pPrefixes; -QTcpSocket* commanderSocket = new QTcpSocket(0); +#if defined (CMAKE_BUILD) +QString rev (" " WSJTX_STRINGIZE (SVNVERSION)); +QString Program_Title_Version ("WSJT-X v" WSJTX_STRINGIZE (WSJTX_VERSION_MAJOR) "." WSJTX_STRINGIZE (WSJTX_VERSION_MINOR) "." WSJTX_STRINGIZE (WSJTX_VERSION_PATCH) ", " WSJTX_STRINGIZE (SVNVERSION) " by K1JT"); +#else QString rev="$Rev$"; QString Program_Title_Version=" WSJT-X v1.4, r" + rev.mid(6,4) + - " by K1JT"; + " by K1JT"; +#endif + +namespace +{ + Radio::Frequency constexpr default_frequency {14076000}; +} + + +class BandAndFrequencyItemDelegate final + : public QStyledItemDelegate +{ +public: + explicit BandAndFrequencyItemDelegate (Bands const * bands, QObject * parent = nullptr) + : QStyledItemDelegate {parent} + , bands_ {bands} + { + } + + QString displayText (QVariant const& v, QLocale const&) const override + { + return Radio::pretty_frequency_MHz_string (Radio::frequency (v, 6)) + + QChar::Nbsp + + '(' + (bands_->data (bands_->find (Radio::frequency (v, 6)))).toString () + ')'; + } + +private: + Bands const * bands_; +}; //--------------------------------------------------- MainWindow constructor -// Multiple instances: new arg *thekey -MainWindow::MainWindow(QSettings * settings, QSharedMemory *shdmem, QString *thekey, - qint32 fontSize2, qint32 fontWeight2, unsigned downSampleFactor, - QWidget *parent) : +MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdmem, QString const& thekey, + unsigned downSampleFactor, QWidget *parent) : QMainWindow(parent), + m_multiple {multiple}, m_settings (settings), ui(new Ui::MainWindow), + m_config (thekey, settings, this), m_wideGraph (new WideGraph (settings)), - m_logDlg (new LogQSO (settings, this)), + m_logDlg (new LogQSO (settings, &m_config, this)), + m_dialFreq {0}, m_detector (RX_SAMPLE_RATE, NTMAX / 2, 6912 / 2, downSampleFactor), - m_audioInputDevice (QAudioDeviceInfo::defaultInputDevice ()), // start with default m_modulator (TX_SAMPLE_RATE, NTMAX / 2), - m_audioOutputDevice (QAudioDeviceInfo::defaultOutputDevice ()), // start with default m_soundOutput (&m_modulator), + m_audioThread {new QThread}, + m_appDir {QApplication::applicationDirPath ()}, + mem_jt9 {shdmem}, + mykey_jt9 {thekey}, psk_Reporter (new PSK_Reporter (this)), m_msAudioOutputBuffered (0u), m_framesAudioInputBuffered (RX_SAMPLE_RATE / 10), m_downSampleFactor (downSampleFactor), - m_audioThreadPriority (QThread::HighPriority) + m_audioThreadPriority (QThread::HighPriority), + m_bandEdited {false}, + m_splitMode {false}, + m_monitoring {false}, + m_transmitting {false}, + m_tune {false}, + m_lastMonitoredFrequency {default_frequency}, + m_toneSpacing {0.} { ui->setupUi(this); - connect (this, &MainWindow::finished, this, &MainWindow::close); + // Closedown. + connect (ui->actionExit, &QAction::triggered, this, &QMainWindow::close); + + // parts of the rig error message box that are fixed + m_rigErrorMessageBox.setInformativeText (tr ("Do you want to reconfigure the radio interface?")); + m_rigErrorMessageBox.setStandardButtons (QMessageBox::Cancel | QMessageBox::Ok | QMessageBox::Retry); + m_rigErrorMessageBox.setDefaultButton (QMessageBox::Ok); + m_rigErrorMessageBox.setIcon (QMessageBox::Critical); // start audio thread and hook up slots & signals for shutdown management // these objects need to be in the audio thread so that invoking // their slots is done in a thread safe way - m_soundOutput.moveToThread (&m_audioThread); - m_modulator.moveToThread (&m_audioThread); - m_soundInput.moveToThread (&m_audioThread); - m_detector.moveToThread (&m_audioThread); + m_soundOutput.moveToThread (m_audioThread); + m_modulator.moveToThread (m_audioThread); + m_soundInput.moveToThread (m_audioThread); + m_detector.moveToThread (m_audioThread); - connect (this, &MainWindow::finished, &m_audioThread, &QThread::quit); // quit thread event loop - connect (&m_audioThread, &QThread::finished, &m_audioThread, &QThread::deleteLater); // disposal + connect (this, &MainWindow::finished, m_audioThread, &QThread::quit); // quit thread event loop + connect (m_audioThread, &QThread::finished, m_audioThread, &QThread::deleteLater); // disposal // hook up sound output stream slots & signals connect (this, SIGNAL (startAudioOutputStream (QAudioDeviceInfo const&, unsigned, unsigned)), &m_soundOutput, SLOT (startStream (QAudioDeviceInfo const&, unsigned, unsigned))); @@ -89,13 +144,16 @@ MainWindow::MainWindow(QSettings * settings, QSharedMemory *shdmem, QString *the connect (this, SIGNAL (endTransmitMessage ()), &m_modulator, SLOT (close ())); connect (this, SIGNAL (tune (bool)), &m_modulator, SLOT (tune (bool))); connect (this, SIGNAL (sendMessage (unsigned, double, unsigned, AudioDevice::Channel, bool, double)) - , &m_modulator, SLOT (open (unsigned, double, unsigned, AudioDevice::Channel, bool, double))); + , &m_modulator, SLOT (open (unsigned, double, unsigned, AudioDevice::Channel, bool, double))); // hook up the audio input stream connect (this, SIGNAL (startAudioInputStream (QAudioDeviceInfo const&, unsigned, int, QIODevice *, unsigned)) - , &m_soundInput, SLOT (start (QAudioDeviceInfo const&, unsigned, int, QIODevice *, unsigned))); + , &m_soundInput, SLOT (start (QAudioDeviceInfo const&, unsigned, int, QIODevice *, unsigned))); connect (this, SIGNAL (stopAudioInputStream ()), &m_soundInput, SLOT (stop ())); + connect (this, &MainWindow::finished, &m_soundInput, &SoundInput::stop); + connect (this, &MainWindow::finished, this, &MainWindow::close); + connect(&m_soundInput, SIGNAL (error (QString)), this, SLOT (showSoundInError (QString))); // connect(&m_soundInput, SIGNAL(status(QString)), this, SLOT(showStatusMessage(QString))); @@ -108,18 +166,19 @@ MainWindow::MainWindow(QSettings * settings, QSharedMemory *shdmem, QString *the // setup the waterfall connect(m_wideGraph.data (), SIGNAL(freezeDecode2(int)),this, - SLOT(freezeDecode(int))); + SLOT(freezeDecode(int))); connect(m_wideGraph.data (), SIGNAL(f11f12(int)),this, - SLOT(bumpFqso(int))); + SLOT(bumpFqso(int))); connect(m_wideGraph.data (), SIGNAL(setXIT2(int)),this, - SLOT(setXIT(int))); + SLOT(setXIT(int))); // connect(m_wideGraph.data (), SIGNAL(dialFreqChanged(double)),this, // SLOT(dialFreqChanged2(double))); connect (this, &MainWindow::finished, m_wideGraph.data (), &WideGraph::close); // setup the log QSO dialog - connect (m_logDlg.data (), SIGNAL (acceptQSO (bool)), this, SLOT (acceptQSO2 (bool))); + connect (m_logDlg.data (), &LogQSO::acceptQSO, this, &MainWindow::acceptQSO2); + connect (this, &MainWindow::finished, m_logDlg.data (), &LogQSO::close); on_EraseButton_clicked(); @@ -154,12 +213,17 @@ MainWindow::MainWindow(QSettings * settings, QSharedMemory *shdmem, QString *the connect(ui->decodedTextBrowser,SIGNAL(selectCallsign(bool,bool)),this, SLOT(doubleClickOnCall2(bool,bool))); + // initialise decoded text font and hook up change signal + setDecodedTextFont (m_config.decoded_text_font ()); + connect (&m_config, &Configuration::decoded_text_font_changed, [this] (QFont const& font) { + setDecodedTextFont (font); + }); setWindowTitle(Program_Title_Version); createStatusBar(); connect(&proc_jt9, SIGNAL(readyReadStandardOutput()), - this, SLOT(readFromStdout())); + this, SLOT(readFromStdout())); connect(&proc_jt9, SIGNAL(error(QProcess::ProcessError)), this, SLOT(jt9_error(QProcess::ProcessError))); @@ -167,27 +231,44 @@ MainWindow::MainWindow(QSettings * settings, QSharedMemory *shdmem, QString *the connect(&proc_jt9, SIGNAL(readyReadStandardError()), this, SLOT(readFromStderr())); - ui->bandComboBox->setEditable(true); - ui->bandComboBox->lineEdit()->setReadOnly(true); - ui->bandComboBox->lineEdit()->setAlignment(Qt::AlignCenter); - for(int i = 0; i < ui->bandComboBox->count(); i++) - ui->bandComboBox->setItemData(i, Qt::AlignCenter, Qt::TextAlignmentRole); + // Hook up working frequencies. + ui->bandComboBox->setModel (m_config.frequencies ()); + ui->bandComboBox->setModelColumn (1); // MHz - ui->tx5->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->tx5, SIGNAL(customContextMenuRequested(const QPoint&)), - this, SLOT(showMacros(const QPoint&))); + // Add delegate to show bands alongside frequencies in combo box + // popup list. + ui->bandComboBox->view ()->setItemDelegateForColumn (1, new BandAndFrequencyItemDelegate {m_config.bands (), this}); - ui->freeTextMsg->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->freeTextMsg, SIGNAL(customContextMenuRequested(const QPoint&)), - this, SLOT(showMacros(const QPoint&))); + // combo box drop downs are limited to the drop down selector width, + // this almost random increase improves the situation + ui->bandComboBox->view ()->setMinimumWidth (ui->bandComboBox->view ()->sizeHintForColumn (1) + 40); - QFont font=ui->decodedTextBrowser->font(); - font.setPointSize(fontSize2); - font.setWeight(fontWeight2); - ui->decodedTextBrowser->setFont(font); - ui->decodedTextBrowser2->setFont(font); + // Enable live band combo box entry validation and action. + auto band_validator = new LiveFrequencyValidator {ui->bandComboBox + , m_config.bands () + , m_config.frequencies () + , this}; + ui->bandComboBox->setValidator (band_validator); - font=ui->readFreq->font(); + // Hook up signals. + connect (band_validator, &LiveFrequencyValidator::valid, this, &MainWindow::band_changed); + connect (ui->bandComboBox->lineEdit (), &QLineEdit::textEdited, [this] (QString const&) {m_bandEdited = true;}); + + // hook up configuration signals + connect (&m_config, &Configuration::transceiver_update, this, &MainWindow::handle_transceiver_update); + connect (&m_config, &Configuration::transceiver_failure, this, &MainWindow::handle_transceiver_failure); + + // Free text macros model to widget hook up. + ui->tx5->setModel (m_config.macros ()); + connect (ui->tx5->lineEdit () + , &QLineEdit::editingFinished + , [this] () {on_tx5_currentTextChanged (ui->tx5->lineEdit ()->text ());}); + ui->freeTextMsg->setModel (m_config.macros ()); + connect (ui->freeTextMsg->lineEdit () + , &QLineEdit::editingFinished + , [this] () {on_freeTextMsg_currentTextChanged (ui->freeTextMsg->lineEdit ()->text ());}); + + auto font = ui->readFreq->font(); font.setFamily("helvetica"); font.setPointSize(9); font.setWeight(75); @@ -225,14 +306,9 @@ MainWindow::MainWindow(QSettings * settings, QSharedMemory *shdmem, QString *the m_btxMute=false; m_btxok=false; m_restart=false; - m_transmitting=false; m_killAll=false; m_widebandDecode=false; m_ntx=1; - m_myCall=""; - m_myGrid="FN20qi"; - m_appDir = QApplication::applicationDirPath(); - m_saveDir="/users/joe/wsjtx/install/save"; m_rxFreq=1500; m_txFreq=1500; m_setftx=0; @@ -252,29 +328,18 @@ MainWindow::MainWindow(QSettings * settings, QSharedMemory *shdmem, QString *the g_iptt=0; g_COMportOpen=0; m_secID=0; - m_promptToLog=false; m_blankLine=false; - m_insertBlank=false; - m_displayDXCCEntity=false; - m_clearCallGrid=false; - m_bMiles=false; m_decodedText2=false; m_freeText=false; m_msErase=0; m_sent73=false; m_watchdogLimit=5; - m_tune=false; m_repeatMsg=0; - m_bRigOpen=false; m_secBandChanged=0; - m_bMultipleOK=false; - m_dontReadFreq=false; m_lockTxFreq=false; ui->readFreq->setEnabled(false); m_QSOText.clear(); - m_CATerror=false; decodeBusy(false); - m_toneSpacing=0; signalMeter = new SignalMeter(ui->meterFrame); signalMeter->resize(50, 160); @@ -282,108 +347,63 @@ MainWindow::MainWindow(QSettings * settings, QSharedMemory *shdmem, QString *the ui->labAz->setStyleSheet("border: 0px;"); ui->labDist->setStyleSheet("border: 0px;"); - mem_jt9 = shdmem; -// Multiple instances: - mykey_jt9 = thekey; - //Band Settings readSettings(); //Restore user's setup params // start the audio thread - m_audioThread.start (m_audioThreadPriority); + m_audioThread->start (m_audioThreadPriority); #ifdef WIN32 - if(!m_bMultipleOK) { - while(true) { - int iret=killbyname("jt9.exe"); - if(iret == 603) break; - if(iret != 0) msgBox("KillByName return code: " + - QString::number(iret)); + if (!m_multiple) + { + while(true) + { + int iret=killbyname("jt9.exe"); + if(iret == 603) break; + if(iret != 0) msgBox("KillByName return code: " + + QString::number(iret)); + } } - } #endif - if(m_dFreq.length()<=1) { //Use the startup default frequencies and band descriptions - // default bands and JT65 frequencies - const double dFreq[]={0.13613,0.4742,1.838,3.576,5.357,7.076,10.138,14.076, - 18.102,21.076,24.917,28.076,50.276,70.091,144.489,432.178}; - const QStringList dBandDescription = QStringList() << "2200 m" << "630 m" << "160 m" - << "80 m" << "60 m" << "40 m" - << "30 m" << "20 m" << "17 m" - << "15 m" << "12 m" << "10 m" - << "6 m" << "4 m" << "2 m" - << "other"; - m_dFreq.clear(); - m_antDescription.clear(); - m_bandDescription.clear(); - for(int i=0; i<16; i++) { - QString t; - t.sprintf("%f",dFreq[i]); - m_dFreq.append(t); - m_antDescription.append(""); - m_bandDescription.append(dBandDescription[i]); - } - } - - ui->bandComboBox->clear(); - ui->bandComboBox->addItems(m_bandDescription); - ui->bandComboBox->setCurrentIndex(m_band); + auto_tx_label->setText (m_config.quick_call () ? "Tx-Enable Armed" : "Tx-Enable Disarmed"); { //delete any .quit file that might have been left lying around //since its presence will cause jt9 to exit a soon as we start it //and decodes will hang - QString quitFileName (m_appDir + "/.quit"); - QFile quitFile (quitFileName); + QFile quitFile (".quit"); while (quitFile.exists ()) { - if (!quitFile.remove ()) - { - msgBox ("Error removing " + quitFileName + " - OK to retry."); - } + if (!quitFile.remove ()) + { + msgBox ("Error removing \"" + quitFile.fileName () + "\" - OK to retry."); + } } } - QFile lockFile(m_appDir + "/.lock"); //Create .lock so jt9 will wait + QFile lockFile(".lock"); //Create .lock so jt9 will wait lockFile.open(QIODevice::ReadWrite); -// Multiple instances: make the Qstring key into command line arg -// Multiple instances: start "jt9 -s " - QByteArray ba = mykey_jt9->toLocal8Bit(); - const char *bc = ba.data(); -// proc_jt9.start(QDir::toNativeSeparators('"' + m_appDir + '"' + "/jt9 -s " + bc)); - QByteArray lda = m_appDir.toLocal8Bit(); - const char *ldir = lda.data(); - proc_jt9.start(QDir::toNativeSeparators('"' + m_appDir + "\"/jt9 -s " + bc + " \"" + ldir + '"')); - - m_pbdecoding_style1="QPushButton{background-color: cyan; \ - border-style: outset; border-width: 1px; border-radius: 5px; \ - border-color: black; min-width: 5em; padding: 3px;}"; - m_pbmonitor_style="QPushButton{background-color: #00ff00; \ - border-style: outset; border-width: 1px; border-radius: 5px; \ - border-color: black; min-width: 5em; padding: 3px;}"; - m_pbAutoOn_style="QPushButton{background-color: red; \ - border-style: outset; border-width: 1px; border-radius: 5px; \ - border-color: black; min-width: 5em; padding: 3px;}"; - m_pbTune_style="QPushButton{background-color: red; \ - border-style: outset; border-width: 1px; border-radius: 5px; \ - border-color: black; min-width: 5em; padding: 3px;}"; + QStringList jt9_args { + "-s", mykey_jt9 + , "-e", QDir::toNativeSeparators (m_appDir) + , "-a", QDir::toNativeSeparators (m_config.data_path ().absolutePath ()) + }; + proc_jt9.start(QDir::toNativeSeparators (m_appDir) + QDir::separator () + "jt9", jt9_args, QIODevice::ReadWrite | QIODevice::Unbuffered); getpfx(); //Load the prefix/suffix dictionary genStdMsgs(m_rpt); m_ntx=6; ui->txrb6->setChecked(true); if(m_mode!="JT9" and m_mode!="JT9W-1" and m_mode!="JT65" and - m_mode!="JT9+JT65") m_mode="JT9"; + m_mode!="JT9+JT65") m_mode="JT9"; on_actionWide_Waterfall_triggered(); //### -// on_actionAstronomical_data_triggered(); - if(m_bAstroData) on_actionAstronomical_data_triggered(); m_wideGraph->setRxFreq(m_rxFreq); m_wideGraph->setTxFreq(m_txFreq); m_wideGraph->setLockTxFreq(m_lockTxFreq); m_wideGraph->setModeTx(m_mode); m_wideGraph->setModeTx(m_modeTx); - dialFreqChanged2(m_dialFreq); connect(m_wideGraph.data (), SIGNAL(setFreq3(int,int)),this, SLOT(setFreq4(int,int))); @@ -401,17 +421,14 @@ MainWindow::MainWindow(QSettings * settings, QSharedMemory *shdmem, QString *the watcher2 = new QFutureWatcher; connect(watcher2, SIGNAL(finished()),this,SLOT(diskWriteFinished())); - Q_EMIT startDetector (m_audioInputChannel); - Q_EMIT startAudioInputStream (m_audioInputDevice, AudioDevice::Mono == m_audioInputChannel ? 1 : 2, m_framesAudioInputBuffered, &m_detector, m_downSampleFactor); + Q_EMIT startDetector (m_config.audio_input_channel ()); + Q_EMIT startAudioInputStream (m_config.audio_input_device (), AudioDevice::Mono == m_config.audio_input_channel () ? 1 : 2, m_framesAudioInputBuffered, &m_detector, m_downSampleFactor); - Q_EMIT transmitFrequency (m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); + Q_EMIT transmitFrequency (m_txFreq - m_XIT); Q_EMIT muteAudioOutput (false); - m_monitoring=!m_monitorStartOFF; // Start with Monitoring ON/OFF - Q_EMIT detectorSetMonitoring (m_monitoring); - m_diskData=false; -// Create "m_worked", a dictionary of all calls in wsjtx.log - QFile f("wsjtx.log"); + // Create "m_worked", a dictionary of all calls in wsjtx.log + QFile f(m_config.data_path ().absoluteFilePath ("wsjtx.log")); f.open(QIODevice::ReadOnly | QIODevice::Text); QTextStream in(&f); QString line,t,callsign; @@ -423,39 +440,28 @@ MainWindow::MainWindow(QSettings * settings, QSharedMemory *shdmem, QString *the } f.close(); - ui->decodedTextLabel->setFont(ui->decodedTextBrowser->font()); - ui->decodedTextLabel2->setFont(ui->decodedTextBrowser2->font()); t="UTC dB DT Freq Message"; ui->decodedTextLabel->setText(t); ui->decodedTextLabel2->setText(t); - psk_Reporter->setLocalStation(m_myCall,m_myGrid, m_antDescription[m_band], "WSJT-X r" + rev.mid(6,4) ); - - on_actionEnable_DXCC_entity_triggered(m_displayDXCCEntity); // sets text window proportions and (re)inits the logbook + enable_DXCC_entity (m_config.DXCC ()); // sets text window proportions and (re)inits the logbook ui->label_9->setStyleSheet("QLabel{background-color: #aabec8}"); ui->label_10->setStyleSheet("QLabel{background-color: #aabec8}"); ui->labUTC->setStyleSheet( \ - "QLabel { background-color : black; color : yellow; }"); + "QLabel { background-color : black; color : yellow; }"); ui->labDialFreq->setStyleSheet( \ - "QLabel { background-color : black; color : yellow; }"); + "QLabel { background-color : black; color : yellow; }"); - QFile f2("ALL.TXT"); - f2.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append); - QTextStream out(&f2); - out << QDateTime::currentDateTimeUtc().toString("yyyy-MMM-dd hh:mm") - << " " << m_dialFreq << " MHz " << m_mode << endl; - f2.close(); -} // End of MainWindow constructor + m_config.transceiver_online (true); + qsy (m_lastMonitoredFrequency); + monitor (!m_config.monitor_off_at_startup ()); +} //--------------------------------------------------- MainWindow destructor MainWindow::~MainWindow() { - if(!m_decoderBusy) { - QFile lockFile(m_appDir + "/.lock"); - lockFile.remove(); - } - delete ui; + m_audioThread->wait (); } //-------------------------------------------------------- writeSettings() @@ -468,86 +474,26 @@ void MainWindow::writeSettings() m_settings->setValue("TxFirst",m_txFirst); m_settings->setValue("DXcall",ui->dxCallEntry->text()); m_settings->setValue("DXgrid",ui->dxGridEntry->text()); - if(g_pAstro!=NULL and g_pAstro->isVisible()) { - m_astroGeom = g_pAstro->geometry(); - m_settings->setValue("AstroGeom",m_astroGeom); - } + m_settings->setValue ("AstroDisplayed", m_astroWidget && m_astroWidget->isVisible()); m_settings->endGroup(); m_settings->beginGroup("Common"); - m_settings->setValue("MyCall",m_myCall); - m_settings->setValue("MyGrid",m_myGrid); - m_settings->setValue("IDint",m_idInt); - m_settings->setValue("PTTmethod",m_pttMethodIndex); - m_settings->setValue("PTTport",m_pttPort); - m_settings->setValue("AstroFont",m_astroFont); - m_settings->setValue("SaveDir",m_saveDir); - m_settings->setValue("SoundInName", m_audioInputDevice.deviceName ()); - m_settings->setValue("SoundOutName", m_audioOutputDevice.deviceName ()); - - m_settings->setValue ("AudioInputChannel", AudioDevice::toString (m_audioInputChannel)); - m_settings->setValue ("AudioOutputChannel", AudioDevice::toString (m_audioOutputChannel)); m_settings->setValue("Mode",m_mode); m_settings->setValue("ModeTx",m_modeTx); m_settings->setValue("SaveNone",ui->actionNone->isChecked()); m_settings->setValue("SaveDecoded",ui->actionSave_decoded->isChecked()); m_settings->setValue("SaveAll",ui->actionSave_all->isChecked()); m_settings->setValue("NDepth",m_ndepth); - m_settings->setValue("MonitorOFF",m_monitorStartOFF); - m_settings->setValue("DialFreq",m_dialFreq); m_settings->setValue("RxFreq",m_rxFreq); m_settings->setValue("TxFreq",m_txFreq); + m_settings->setValue ("DialFreq", QVariant::fromValue(m_lastMonitoredFrequency)); m_settings->setValue("InGain",m_inGain); m_settings->setValue("OutAttenuation", ui->outAttenuation->value ()); - m_settings->setValue("PSKReporter",m_pskReporter); - m_settings->setValue("After73",m_After73); - m_settings->setValue("DisplayAstro",m_bAstroData); - m_settings->setValue("Macros",m_macro); - //Band Settings - m_settings->setValue("BandFrequencies",m_dFreq); - m_settings->setValue("BandDescriptions",m_bandDescription); - m_settings->setValue("AntennaDescriptions",m_antDescription); - m_settings->setValue("toRTTY",m_toRTTY); m_settings->setValue("NoSuffix",m_noSuffix); - m_settings->setValue("dBtoComments",m_dBtoComments); - m_settings->setValue("catEnabled",m_catEnabled); - m_settings->setValue("Rig",m_rig); - m_settings->setValue("RigIndex",m_rigIndex); - m_settings->setValue("CATport",m_catPort); - m_settings->setValue("CATportIndex",m_catPortIndex); - m_settings->setValue("SerialRate",m_serialRate); - m_settings->setValue("SerialRateIndex",m_serialRateIndex); - m_settings->setValue("DataBits",m_dataBits); - m_settings->setValue("DataBitsIndex",m_dataBitsIndex); - m_settings->setValue("StopBits",m_stopBits); - m_settings->setValue("StopBitsIndex",m_stopBitsIndex); - m_settings->setValue("Handshake",m_handshake); - m_settings->setValue("HandshakeIndex",m_handshakeIndex); - m_settings->setValue("BandIndex",m_band); - m_settings->setValue("PromptToLog",m_promptToLog); - m_settings->setValue("InsertBlank",m_insertBlank); - m_settings->setValue("DXCCEntity",m_displayDXCCEntity); - m_settings->setValue("ClearCallGrid",m_clearCallGrid); - m_settings->setValue("Miles",m_bMiles); m_settings->setValue("GUItab",ui->tabWidget->currentIndex()); - m_settings->setValue("QuickCall",m_quickCall); - m_settings->setValue("73TxDisable",m_73TxDisable); - m_settings->setValue("Runaway",m_runaway); - m_settings->setValue("Tx2QSO",m_tx2QSO); - m_settings->setValue("MultipleOK",m_bMultipleOK); - m_settings->setValue("DTR",m_bDTR); - m_settings->setValue("RTS",m_bRTS); m_settings->setValue("pttData",m_pttData); - m_settings->setValue("Polling",m_poll); m_settings->setValue("OutBufSize",outBufSize); m_settings->setValue("LockTxFreq",m_lockTxFreq); - m_settings->setValue("TxSplit",m_bSplit); - m_settings->setValue("UseXIT",m_bXIT); - m_settings->setValue("XIT",m_XIT); m_settings->setValue("Plus2kHz",m_plus2kHz); - m_settings->setValue("EMEbandIndex",m_EMEbandIndex); - m_settings->setValue("ToneMultIndex",m_toneMultIndex); - m_settings->setValue("DTmin",m_DTmin); - m_settings->setValue("DTmax",m_DTmax); m_settings->endGroup(); } @@ -560,69 +506,34 @@ void MainWindow::readSettings() restoreState (m_settings->value ("state", saveState ()).toByteArray ()); ui->dxCallEntry->setText(m_settings->value("DXcall","").toString()); ui->dxGridEntry->setText(m_settings->value("DXgrid","").toString()); - m_astroGeom = m_settings->value("AstroGeom", QRect(71,390,227,403)).toRect(); - m_path = m_settings->value("MRUdir", m_appDir + "/save").toString(); + m_path = m_settings->value("MRUdir", m_config.save_directory ().absolutePath ()).toString (); m_txFirst = m_settings->value("TxFirst",false).toBool(); ui->txFirstCheckBox->setChecked(m_txFirst); + auto displayAstro = m_settings->value ("AstroDisplayed", false).toBool (); m_settings->endGroup(); - m_settings->beginGroup("Common"); - m_myCall=m_settings->value("MyCall","").toString(); - morse_(m_myCall.toLatin1().data(),icw,&m_ncw,m_myCall.length()); - m_myGrid=m_settings->value("MyGrid","").toString(); - m_idInt=m_settings->value("IDint",0).toInt(); - m_pttMethodIndex=m_settings->value("PTTmethod",1).toInt(); - m_pttPort=m_settings->value("PTTport",0).toInt(); - m_astroFont=m_settings->value("AstroFont",18).toInt(); - m_saveDir=m_settings->value("SaveDir",m_appDir + "/save").toString(); - - { - // - // retrieve audio input device - // - QString savedName = m_settings->value( "SoundInName").toString(); - QList audioInputDevices (QAudioDeviceInfo::availableDevices (QAudio::AudioInput)); // available audio input devices - for (QList::const_iterator p = audioInputDevices.begin (); p != audioInputDevices.end (); ++p) - { - if (p->deviceName () == savedName) - { - m_audioInputDevice = *p; - } - } - } - - { - // - // retrieve audio output device - // - QString savedName = m_settings->value("SoundOutName").toString(); - QList audioOutputDevices (QAudioDeviceInfo::availableDevices (QAudio::AudioOutput)); // available audio output devices - for (QList::const_iterator p = audioOutputDevices.begin (); p != audioOutputDevices.end (); ++p) + // do this outside of settings group because it uses groups internally + if (displayAstro) { - if (p->deviceName () == savedName) { - m_audioOutputDevice = *p; - } + on_actionAstronomical_data_triggered (); } - } - - // retrieve audio channel info - m_audioInputChannel = AudioDevice::fromString (m_settings->value ("AudioInputChannel", "Mono").toString ()); - m_audioOutputChannel = AudioDevice::fromString (m_settings->value ("AudioOutputChannel", "Mono").toString ()); + m_settings->beginGroup("Common"); + morse_(const_cast (m_config.my_callsign ().toLatin1().constData()),icw,&m_ncw,m_config.my_callsign ().length()); m_mode=m_settings->value("Mode","JT9").toString(); m_modeTx=m_settings->value("ModeTx","JT9").toString(); if(m_modeTx.mid(0,3)=="JT9") ui->pbTxMode->setText("Tx JT9 @"); if(m_modeTx=="JT65") ui->pbTxMode->setText("Tx JT65 #"); ui->actionNone->setChecked(m_settings->value("SaveNone",true).toBool()); ui->actionSave_decoded->setChecked(m_settings->value( - "SaveDecoded",false).toBool()); + "SaveDecoded",false).toBool()); ui->actionSave_all->setChecked(m_settings->value("SaveAll",false).toBool()); - m_dialFreq=m_settings->value("DialFreq",14.078).toDouble(); m_rxFreq=m_settings->value("RxFreq",1500).toInt(); ui->RxFreqSpinBox->setValue(m_rxFreq); m_txFreq=m_settings->value("TxFreq",1500).toInt(); + m_lastMonitoredFrequency = m_settings->value ("DialFreq", QVariant::fromValue (default_frequency)).value (); ui->TxFreqSpinBox->setValue(m_txFreq); - Q_EMIT transmitFrequency (m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); + Q_EMIT transmitFrequency (m_txFreq - m_XIT); m_saveDecoded=ui->actionSave_decoded->isChecked(); m_saveAll=ui->actionSave_all->isChecked(); m_ndepth=m_settings->value("NDepth",3).toInt(); @@ -633,77 +544,19 @@ void MainWindow::readSettings() ui->outAttenuation->setValue (m_settings->value ("OutAttenuation", 0).toInt ()); on_outAttenuation_valueChanged (ui->outAttenuation->value ()); - m_monitorStartOFF=m_settings->value("MonitorOFF",false).toBool(); - ui->actionMonitor_OFF_at_startup->setChecked(m_monitorStartOFF); - m_pskReporter=m_settings->value("PSKReporter",false).toBool(); - m_After73=m_settings->value("After73",false).toBool(); - m_bAstroData=m_settings->value("DisplayAstro",false).toBool(); - m_macro=m_settings->value("Macros","TNX 73 GL").toStringList(); - //Band Settings - m_dFreq=m_settings->value("BandFrequencies","").toStringList(); - m_bandDescription=m_settings->value("BandDescriptions","").toStringList(); - m_antDescription=m_settings->value("AntennaDescriptions","").toStringList(); - m_toRTTY=m_settings->value("toRTTY",false).toBool(); - ui->actionConvert_JT9_x_to_RTTY->setChecked(m_toRTTY); m_noSuffix=m_settings->value("NoSuffix",false).toBool(); - m_dBtoComments=m_settings->value("dBtoComments",false).toBool(); - ui->actionLog_dB_reports_to_Comments->setChecked(m_dBtoComments); - m_rig=m_settings->value("Rig",214).toInt(); - m_rigIndex=m_settings->value("RigIndex",100).toInt(); - m_catPort=m_settings->value("CATport","None").toString(); - m_catPortIndex=m_settings->value("CATportIndex",0).toInt(); - m_serialRate=m_settings->value("SerialRate",4800).toInt(); - m_serialRateIndex=m_settings->value("SerialRateIndex",1).toInt(); - m_dataBits=m_settings->value("DataBits",8).toInt(); - m_dataBitsIndex=m_settings->value("DataBitsIndex",1).toInt(); - m_stopBits=m_settings->value("StopBits",2).toInt(); - m_stopBitsIndex=m_settings->value("StopBitsIndex",1).toInt(); - m_handshake=m_settings->value("Handshake","None").toString(); - m_handshakeIndex=m_settings->value("HandshakeIndex",0).toInt(); - m_band=m_settings->value("BandIndex",7).toInt(); - ui->bandComboBox->setCurrentIndex(m_band); - dialFreqChanged2(m_dialFreq); - m_catEnabled=m_settings->value("catEnabled",false).toBool(); - m_promptToLog=m_settings->value("PromptToLog",false).toBool(); - ui->actionPrompt_to_log_QSO->setChecked(m_promptToLog); - m_insertBlank=m_settings->value("InsertBlank",false).toBool(); - ui->actionBlank_line_between_decoding_periods->setChecked(m_insertBlank); - m_displayDXCCEntity=m_settings->value("DXCCEntity",false).toBool(); - ui->actionEnable_DXCC_entity->setChecked(m_displayDXCCEntity); - m_clearCallGrid=m_settings->value("ClearCallGrid",false).toBool(); - ui->actionClear_DX_Call_and_Grid_after_logging->setChecked(m_clearCallGrid); - m_bMiles=m_settings->value("Miles",false).toBool(); - ui->actionDisplay_distance_in_miles->setChecked(m_bMiles); int n=m_settings->value("GUItab",0).toInt(); ui->tabWidget->setCurrentIndex(n); - m_quickCall=m_settings->value("QuickCall",false).toBool(); - ui->actionDouble_click_on_call_sets_Tx_Enable->setChecked(m_quickCall); - m_73TxDisable=m_settings->value("73TxDisable",false).toBool(); - ui->action_73TxDisable->setChecked(m_73TxDisable); - m_runaway=m_settings->value("Runaway",false).toBool(); - ui->actionRunaway_Tx_watchdog->setChecked(m_runaway); - m_tx2QSO=m_settings->value("Tx2QSO",false).toBool(); - ui->actionTx2QSO->setChecked(m_tx2QSO); - m_bMultipleOK=m_settings->value("MultipleOK",false).toBool(); - ui->actionAllow_multiple_instances->setChecked(m_bMultipleOK); - m_bDTR=m_settings->value("DTR",false).toBool(); - m_bRTS=m_settings->value("RTS",false).toBool(); m_pttData=m_settings->value("pttData",false).toBool(); - m_poll=m_settings->value("Polling",0).toInt(); outBufSize=m_settings->value("OutBufSize",4096).toInt(); m_lockTxFreq=m_settings->value("LockTxFreq",false).toBool(); ui->cbTxLock->setChecked(m_lockTxFreq); - m_bSplit=m_settings->value("TxSplit",false).toBool(); - m_bXIT=m_settings->value("UseXIT",false).toBool(); - m_XIT=m_settings->value("XIT",0).toInt(); - m_plus2kHz=m_settings->value("Plus2kHz",false).toBool(); - ui->cbPlus2kHz->setChecked(m_plus2kHz); + m_plus2kHz=m_settings->value("Plus2kHz",false).toBool(); + ui->cbPlus2kHz->setChecked(m_plus2kHz); m_EMEbandIndex=m_settings->value("EMEbandIndex",0).toInt(); m_toneMultIndex=m_settings->value("ToneMultIndex",0).toInt(); - m_DTmin=m_settings->value("DTmin",-2.5).toFloat(); - m_DTmax=m_settings->value("DTmax",5.0).toFloat(); m_settings->endGroup(); - // use these initialisation settings to tune the audio o/p bufefr + // use these initialisation settings to tune the audio o/p buffer // size and audio thread priority m_settings->beginGroup ("Tune"); m_msAudioOutputBuffered = m_settings->value ("Audio/OutputBufferMs").toInt (); @@ -718,6 +571,14 @@ void MainWindow::readSettings() statusChanged(); } +void MainWindow::setDecodedTextFont (QFont const& font) +{ + ui->decodedTextBrowser->setFont (font); + ui->decodedTextBrowser2->setFont (font); + ui->decodedTextLabel->setFont (font); + ui->decodedTextLabel2->setFont (font); +} + //-------------------------------------------------------------- dataSink() void MainWindow::dataSink(qint64 frames) { @@ -736,7 +597,7 @@ void MainWindow::dataSink(qint64 frames) jt9com_.ndiskdat=0; } -// Get power, spectrum, and ihsym + // Get power, spectrum, and ihsym trmin=m_TRperiod/60; int k (frames - 1); jt9com_.nfa=m_wideGraph->nStartFreq(); @@ -768,8 +629,7 @@ void MainWindow::dataSink(qint64 frames) imin=imin - (imin%(m_TRperiod/60)); QString t2; t2.sprintf("%2.2d%2.2d",ihr,imin); - m_fname=m_saveDir + "/" + t.date().toString("yyMMdd") + "_" + - t2 + ".wav"; + m_fname=m_config.save_directory ().absoluteFilePath (t.date().toString("yyMMdd") + "_" + t2 + ".wav"); *future2 = QtConcurrent::run(savewav, m_fname, m_TRperiod); watcher2->setFuture(*future2); } @@ -777,165 +637,90 @@ void MainWindow::dataSink(qint64 frames) } void MainWindow::showSoundInError(const QString& errorMsg) - {QMessageBox::critical(this, tr("Error in SoundInput"), errorMsg);} +{QMessageBox::critical(this, tr("Error in SoundInput"), errorMsg);} void MainWindow::showSoundOutError(const QString& errorMsg) - {QMessageBox::critical(this, tr("Error in SoundOutput"), errorMsg);} +{QMessageBox::critical(this, tr("Error in SoundOutput"), errorMsg);} void MainWindow::showStatusMessage(const QString& statusMsg) - {statusBar()->showMessage(statusMsg);} +{statusBar()->showMessage(statusMsg);} -void MainWindow::on_actionDeviceSetup_triggered() //Setup Dialog +void MainWindow::on_actionSettings_triggered() //Setup Dialog { - DevSetup dlg(this); - dlg.m_myCall=m_myCall; - dlg.m_myGrid=m_myGrid; - dlg.m_idInt=m_idInt; - dlg.m_pttMethodIndex=m_pttMethodIndex; - dlg.m_pttPort=m_pttPort; - dlg.m_astroFont=m_astroFont; - dlg.m_saveDir=m_saveDir; - dlg.m_audioInputDevice = m_audioInputDevice; - dlg.m_audioOutputDevice = m_audioOutputDevice; - dlg.m_audioInputChannel = m_audioInputChannel; - dlg.m_audioOutputChannel = m_audioOutputChannel; - dlg.m_pskReporter=m_pskReporter; - dlg.m_After73=m_After73; - dlg.m_bAstroData=m_bAstroData; - dlg.m_macro=m_macro; - dlg.m_dFreq=m_dFreq; - dlg.m_antDescription=m_antDescription; - dlg.m_bandDescription=m_bandDescription; - dlg.m_catEnabled=m_catEnabled; - dlg.m_rig=m_rig; - dlg.m_rigIndex=m_rigIndex; - dlg.m_catPort=m_catPort; - dlg.m_catPortIndex=m_catPortIndex; - dlg.m_serialRate=m_serialRate; - dlg.m_serialRateIndex=m_serialRateIndex; - dlg.m_dataBits=m_dataBits; - dlg.m_dataBitsIndex=m_dataBitsIndex; - dlg.m_stopBits=m_stopBits; - dlg.m_stopBitsIndex=m_stopBitsIndex; - dlg.m_handshake=m_handshake; - dlg.m_handshakeIndex=m_handshakeIndex; - dlg.m_bDTR=m_bDTR; - dlg.m_bRTS=m_bRTS; dlg.m_pttData=m_pttData; - dlg.m_poll=m_poll; - dlg.m_bSplit=m_bSplit; - dlg.m_bXIT=m_bXIT; - dlg.m_EMEbandIndex=m_EMEbandIndex; - dlg.m_toneMultIndex=m_toneMultIndex; - dlg.m_DTmin=m_DTmin; - dlg.m_DTmax=m_DTmax; + ui->readFreq->setStyleSheet(""); + ui->readFreq->setEnabled(false); - if(m_bRigOpen) { - rig->close(); - ui->readFreq->setStyleSheet(""); - ui->readFreq->setEnabled(false); - if(m_rig<9900) delete rig; - m_bRigOpen=false; - m_catEnabled=false; - m_CATerror=false; - } + if (QDialog::Accepted == m_config.exec ()) + { + on_dxGridEntry_textChanged (m_hisGrid); // recalculate distances in case of units change + enable_DXCC_entity (m_config.DXCC ()); // sets text window proportions and (re)inits the logbook - dlg.initDlg(); - if(dlg.exec() == QDialog::Accepted) { - m_myCall=dlg.m_myCall; - m_myGrid=dlg.m_myGrid; - m_idInt=dlg.m_idInt; - m_pttMethodIndex=dlg.m_pttMethodIndex; - m_pttPort=dlg.m_pttPort; - m_astroFont=dlg.m_astroFont; - if(g_pAstro!=NULL and g_pAstro->isVisible()) { - g_pAstro->setFontSize(m_astroFont); - } - m_saveDir=dlg.m_saveDir; - m_audioInputDevice = dlg.m_audioInputDevice; - m_audioOutputDevice = dlg.m_audioOutputDevice; - m_audioInputChannel = dlg.m_audioInputChannel; - m_audioOutputChannel = dlg.m_audioOutputChannel; - m_macro=dlg.m_macro; - m_dFreq=dlg.m_dFreq; - m_antDescription=dlg.m_antDescription; - m_bandDescription=dlg.m_bandDescription; - m_catEnabled=dlg.m_catEnabled; - m_rig=dlg.m_rig; - m_rigIndex=dlg.m_rigIndex; - m_catPort=dlg.m_catPort; - m_catPortIndex=dlg.m_catPortIndex; - m_serialRate=dlg.m_serialRate; - m_serialRateIndex=dlg.m_serialRateIndex; - m_dataBits=dlg.m_dataBits; - m_dataBitsIndex=dlg.m_dataBitsIndex; - m_stopBits=dlg.m_stopBits; - m_stopBitsIndex=dlg.m_stopBitsIndex; - m_handshake=dlg.m_handshake; - m_handshakeIndex=dlg.m_handshakeIndex; - m_bDTR=dlg.m_bDTR; - m_bRTS=dlg.m_bRTS; - m_pttData=dlg.m_pttData; - m_poll=dlg.m_poll; - m_EMEbandIndex=dlg.m_EMEbandIndex; - m_toneMultIndex=dlg.m_toneMultIndex; - if(m_mode=="JT9W-1") m_toneSpacing=pow(2,m_toneMultIndex)*12000.0/6912.0; - m_DTmin=dlg.m_DTmin; - m_DTmax=dlg.m_DTmax; + if(m_config.spot_to_psk_reporter ()) + { + pskSetLocal (); + } - //Band Settings - ui->bandComboBox->clear(); - ui->bandComboBox->addItems(dlg.m_bandDescription); - ui->bandComboBox->setCurrentIndex(m_band); - m_pskReporter=dlg.m_pskReporter; - if(m_pskReporter) { - psk_Reporter->setLocalStation(m_myCall, m_myGrid, m_antDescription[m_band], "WSJT-X r" + rev.mid(6,4) ); - } - m_After73=dlg.m_After73; - m_bAstroData=dlg.m_bAstroData; + if(m_mode=="JT9W-1") m_toneSpacing=pow(2,m_config.jt9w_bw_mult ())*12000.0/6912.0; - if(dlg.m_restartSoundIn) { - Q_EMIT stopAudioInputStream (); - Q_EMIT detectorClose (); - Q_EMIT startDetector (m_audioInputChannel); - Q_EMIT startAudioInputStream (m_audioInputDevice, AudioDevice::Mono == m_audioInputChannel ? 1 : 2, m_framesAudioInputBuffered, &m_detector, m_downSampleFactor); - } - - if(dlg.m_restartSoundOut) { - Q_EMIT stopAudioOutputStream (); - Q_EMIT startAudioOutputStream (m_audioOutputDevice, AudioDevice::Mono == m_audioOutputChannel ? 1 : 2, m_msAudioOutputBuffered); - } - } - m_catEnabled=dlg.m_catEnabled; - - if(m_catEnabled) { - rigOpen(); - } else { - ui->readFreq->setStyleSheet(""); - } - - if(dlg.m_bSplit!=m_bSplit or dlg.m_bXIT!=m_bXIT) { - m_bSplit=dlg.m_bSplit; - if(m_bSplit) ui->readFreq->setText("S"); - if(!m_bSplit) ui->readFreq->setText(""); - m_bXIT=dlg.m_bXIT; - if(m_bSplit or m_bXIT) setXIT(m_txFreq); - if(m_bRigOpen and !m_bSplit) { - int ret=rig->setSplitFreq(MHz(m_dialFreq),RIG_VFO_B); - if(ret!=RIG_OK) { - QString rt; - rt.sprintf("Setting VFO_B failed: %d",ret); - msgBox(rt); + if(m_config.restart_audio_input ()) { + Q_EMIT stopAudioInputStream (); + Q_EMIT detectorClose (); + Q_EMIT startDetector (m_config.audio_input_channel ()); + Q_EMIT startAudioInputStream (m_config.audio_input_device (), AudioDevice::Mono == m_config.audio_input_channel () ? 1 : 2, m_framesAudioInputBuffered, &m_detector, m_downSampleFactor); } + if(m_config.restart_audio_output ()) { + Q_EMIT stopAudioOutputStream (); + Q_EMIT startAudioOutputStream (m_config.audio_output_device (), AudioDevice::Mono == m_config.audio_output_channel () ? 1 : 2, m_msAudioOutputBuffered); + } + + auto_tx_label->setText (m_config.quick_call () ? "Tx-Enable Armed" : "Tx-Enable Disarmed"); + + displayDialFrequency (); + } + + setXIT (m_txFreq); + if (m_config.transceiver_online ()) + { + Q_EMIT m_config.transceiver_frequency (m_dialFreq); } - } } -void MainWindow::on_monitorButton_clicked() //Monitor +void MainWindow::on_monitorButton_clicked (bool checked) { - m_monitoring=true; - Q_EMIT detectorSetMonitoring (true); - // Q_EMIT startAudioInputStream (m_audioInputDevice, AudioDevice::Mono == m_audioInputChannel ? 1 : 2, m_framesAudioInputBuffered, &m_detector, m_downSampleFactor); - m_diskData=false; + // make sure we have the current rig state + // Q_EMIT m_config.sync_transceiver (true); + + if (!m_transmitting) + { + m_monitoring = checked; + + if (checked) + { + m_diskData = false; // no longer reading WAV files + + if (m_monitoring) + { + // put rig back where it was when last in control + Q_EMIT m_config.transceiver_frequency (m_lastMonitoredFrequency); + setXIT (m_txFreq); + } + } + + Q_EMIT detectorSetMonitoring (checked); + // Q_EMIT startAudioInputStream (m_params.audio_input_device, AudioDevice::Mono == m_params.audio_input_channel ? 1 : 2, m_framesAudioInputBuffered, &m_detector, m_downSampleFactor); + } + else + { + ui->monitorButton->setChecked (false); // disallow + } + + Q_EMIT m_config.sync_transceiver (true, checked); // gets Configuration in/out of strict split and mode checking +} + +void MainWindow::monitor (bool state) +{ + ui->monitorButton->setChecked (state); + on_monitorButton_clicked (state); } void MainWindow::on_actionAbout_triggered() //Display "About" @@ -944,120 +729,124 @@ void MainWindow::on_actionAbout_triggered() //Display "About" dlg.exec(); } -void MainWindow::on_autoButton_clicked() //Auto +void MainWindow::on_autoButton_clicked (bool checked) { - m_auto = !m_auto; - if(m_auto) { - ui->autoButton->setStyleSheet(m_pbAutoOn_style); - } else { - m_btxok=false; - Q_EMIT muteAudioOutput (); - ui->autoButton->setStyleSheet(""); - on_monitorButton_clicked(); - m_repeatMsg=0; - } + m_auto = checked; + if (!m_auto) + { + m_btxok = false; + Q_EMIT muteAudioOutput (); + monitor (true); + m_repeatMsg = 0; + } +} + +void MainWindow::auto_tx_mode (bool state) +{ + ui->autoButton->setChecked (state); + on_autoButton_clicked (state); } void MainWindow::keyPressEvent( QKeyEvent *e ) //keyPressEvent { int n; switch(e->key()) - { - case Qt::Key_1: - if(e->modifiers() & Qt::AltModifier) { - on_txb1_clicked(); + { + case Qt::Key_1: + if(e->modifiers() & Qt::AltModifier) { + on_txb1_clicked(); + break; + } + case Qt::Key_2: + if(e->modifiers() & Qt::AltModifier) { + on_txb2_clicked(); + break; + } + case Qt::Key_3: + if(e->modifiers() & Qt::AltModifier) { + on_txb3_clicked(); + break; + } + case Qt::Key_4: + if(e->modifiers() & Qt::AltModifier) { + on_txb4_clicked(); + break; + } + case Qt::Key_5: + if(e->modifiers() & Qt::AltModifier) { + on_txb5_clicked(); + break; + } + case Qt::Key_6: + if(e->modifiers() & Qt::AltModifier) { + on_txb6_clicked(); + break; + } + case Qt::Key_D: + if(e->modifiers() & Qt::ShiftModifier) { + if(!m_decoderBusy) { + jt9com_.newdat=0; + jt9com_.nagain=0; + decode(); + break; + } + } break; - } - case Qt::Key_2: - if(e->modifiers() & Qt::AltModifier) { - on_txb2_clicked(); + case Qt::Key_F4: + ui->dxCallEntry->setText(""); + ui->dxGridEntry->setText(""); + genStdMsgs(""); + m_ntx=6; + ui->txrb6->setChecked(true); break; - } - case Qt::Key_3: - if(e->modifiers() & Qt::AltModifier) { - on_txb3_clicked(); + case Qt::Key_F6: + if(e->modifiers() & Qt::ShiftModifier) { + on_actionDecode_remaining_files_in_directory_triggered(); + } break; - } - case Qt::Key_4: - if(e->modifiers() & Qt::AltModifier) { - on_txb4_clicked(); + case Qt::Key_F11: + n=11; + if(e->modifiers() & Qt::ControlModifier) n+=100; + bumpFqso(n); break; - } - case Qt::Key_5: - if(e->modifiers() & Qt::AltModifier) { - on_txb5_clicked(); + case Qt::Key_F12: + n=12; + if(e->modifiers() & Qt::ControlModifier) n+=100; + bumpFqso(n); break; - } - case Qt::Key_6: - if(e->modifiers() & Qt::AltModifier) { - on_txb6_clicked(); - break; - } - case Qt::Key_D: - if(e->modifiers() & Qt::ShiftModifier) { - if(!m_decoderBusy) { - jt9com_.newdat=0; - jt9com_.nagain=0; - decode(); + case Qt::Key_F: + if(e->modifiers() & Qt::ControlModifier) { + if(ui->tabWidget->currentIndex()==0) { + ui->tx5->clearEditText(); + ui->tx5->setFocus(); + } else { + ui->freeTextMsg->clearEditText(); + ui->freeTextMsg->setFocus(); + } + break; + } + case Qt::Key_G: + if(e->modifiers() & Qt::AltModifier) { + genStdMsgs(m_rpt); + break; + } + case Qt::Key_H: + if(e->modifiers() & Qt::AltModifier) { + on_stopTxButton_clicked(); + break; + } + case Qt::Key_L: + if(e->modifiers() & Qt::ControlModifier) { + lookup(); + genStdMsgs(m_rpt); + break; + } + case Qt::Key_V: + if(e->modifiers() & Qt::AltModifier) { + m_fileToSave=m_fname; break; } } - break; - case Qt::Key_F4: - ui->dxCallEntry->setText(""); - ui->dxGridEntry->setText(""); - genStdMsgs(""); - m_ntx=6; - ui->txrb6->setChecked(true); - break; - case Qt::Key_F6: - if(e->modifiers() & Qt::ShiftModifier) { - on_actionDecode_remaining_files_in_directory_triggered(); - } - break; - case Qt::Key_F11: - n=11; - if(e->modifiers() & Qt::ControlModifier) n+=100; - bumpFqso(n); - break; - case Qt::Key_F12: - n=12; - if(e->modifiers() & Qt::ControlModifier) n+=100; - bumpFqso(n); - break; - case Qt::Key_F: - if(e->modifiers() & Qt::ControlModifier) { - if(ui->tabWidget->currentIndex()==0) { - ui->tx5->clear(); - ui->tx5->setFocus(); - } else { - ui->freeTextMsg->clear(); - ui->freeTextMsg->setFocus(); - } - break; - } - case Qt::Key_G: - if(e->modifiers() & Qt::AltModifier) { - genStdMsgs(m_rpt); - break; - } - case Qt::Key_H: - if(e->modifiers() & Qt::AltModifier) { - on_stopTxButton_clicked(); - break; - } - case Qt::Key_L: - if(e->modifiers() & Qt::ControlModifier) { - lookup(); - genStdMsgs(m_rpt); - break; - } - case Qt::Key_V: - if(e->modifiers() & Qt::AltModifier) { - m_fileToSave=m_fname; - break; - } - } } void MainWindow::bumpFqso(int n) //bumpFqso() @@ -1075,26 +864,68 @@ void MainWindow::bumpFqso(int n) //bumpFqso() } } -void MainWindow::dialFreqChanged2(double f) +void MainWindow::qsy (Frequency f) { - m_dialFreq=f; - if(m_band<0 or m_band>15 or m_dFreq.length()<=1) return; - QString t; - t.sprintf("%.6f",m_dialFreq); - int n=t.length(); - t=t.mid(0,n-3) + " " + t.mid(n-3,3); - double fBand=m_dFreq[m_band].toDouble(); - if(qAbs(m_dialFreq-fBand)<0.01) { - ui->labDialFreq->setStyleSheet( \ - "QLabel { background-color : black; color : yellow; }"); - } else { - ui->labDialFreq->setStyleSheet( \ - "QLabel { background-color : red; color : yellow; }"); - ui->labDialFreq->setText(t); - } - ui->labDialFreq->setText(t); - statusChanged(); - m_wideGraph->setDialFreq(m_dialFreq); + if (m_monitoring || m_transmitting) + { + m_lastMonitoredFrequency = f; + } + + if (m_dialFreq != f) + { + m_dialFreq = f; + setXIT(m_txFreq); + + m_repeatMsg=0; + m_secBandChanged=QDateTime::currentMSecsSinceEpoch()/1000; + + bumpFqso(11); + bumpFqso(12); + + QFile f2(m_config.data_path ().absoluteFilePath ("ALL.TXT")); + f2.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append); + QTextStream out(&f2); + out << QDateTime::currentDateTimeUtc().toString("yyyy-MMM-dd hh:mm") + << " " << (m_dialFreq / 1.e6) << " MHz " << m_mode << endl; + f2.close(); + if (m_config.spot_to_psk_reporter ()) + { + pskSetLocal (); + } + + displayDialFrequency (); + statusChanged(); + m_wideGraph->setDialFreq(m_dialFreq / 1.e6); + } +} + +void MainWindow::displayDialFrequency () +{ + // lookup band + auto bands_model = m_config.bands (); + ui->bandComboBox->setCurrentText (bands_model->data (bands_model->find (m_dialFreq)).toString ()); + + // search working frequencies for one we are within 10kHz of + auto frequencies = m_config.frequencies (); + bool valid {false}; + for (int row = 0; row < frequencies->rowCount (); ++row) + { + auto working_frequency = frequencies->data (frequencies->index (row, 0)).value (); + if (std::llabs (working_frequency - m_dialFreq) < 10000) + { + valid = true; + } + } + if (valid) + { + ui->labDialFreq->setStyleSheet("QLabel { background-color : black; color : yellow; }"); + } + else + { + ui->labDialFreq->setStyleSheet("QLabel { background-color : red; color : yellow; }"); + } + + ui->labDialFreq->setText (Radio::pretty_frequency_MHz_string (m_dialFreq)); } void MainWindow::statusChanged() @@ -1102,11 +933,11 @@ void MainWindow::statusChanged() QFile f("wsjtx_status.txt"); if(f.open(QFile::WriteOnly | QIODevice::Text)) { QTextStream out(&f); - out << m_dialFreq << ";" << m_mode << ";" << m_hisCall << ";" + out << (m_dialFreq / 1.e6) << ";" << m_mode << ";" << m_hisCall << ";" << ui->rptSpinBox->value() << ";" << m_modeTx << endl; f.close(); } else { - msgBox("Cannot open file \"wsjtx_status.txt\"."); + msgBox("Cannot open file \"" + f.fileName () + "\"."); return; } } @@ -1123,62 +954,62 @@ bool MainWindow::eventFilter(QObject *object, QEvent *event) //eventFilter() void MainWindow::createStatusBar() //createStatusBar { - lab1 = new QLabel("Receiving"); - lab1->setAlignment(Qt::AlignHCenter); - lab1->setMinimumSize(QSize(80,18)); - lab1->setStyleSheet("QLabel{background-color: #00ff00}"); - lab1->setFrameStyle(QFrame::Panel | QFrame::Sunken); - statusBar()->addWidget(lab1); + tx_status_label = new QLabel("Receiving"); + tx_status_label->setAlignment(Qt::AlignHCenter); + tx_status_label->setMinimumSize(QSize(80,18)); + tx_status_label->setStyleSheet("QLabel{background-color: #00ff00}"); + tx_status_label->setFrameStyle(QFrame::Panel | QFrame::Sunken); + statusBar()->addWidget(tx_status_label); - lab2 = new QLabel(""); - lab2->setAlignment(Qt::AlignHCenter); - lab2->setMinimumSize(QSize(80,18)); - lab2->setFrameStyle(QFrame::Panel | QFrame::Sunken); - statusBar()->addWidget(lab2); + mode_label = new QLabel(""); + mode_label->setAlignment(Qt::AlignHCenter); + mode_label->setMinimumSize(QSize(80,18)); + mode_label->setFrameStyle(QFrame::Panel | QFrame::Sunken); + statusBar()->addWidget(mode_label); - lab3 = new QLabel(""); - lab3->setAlignment(Qt::AlignHCenter); - lab3->setMinimumSize(QSize(150,18)); - lab3->setFrameStyle(QFrame::Panel | QFrame::Sunken); - statusBar()->addWidget(lab3); -} + last_tx_label = new QLabel(""); + last_tx_label->setAlignment(Qt::AlignHCenter); + last_tx_label->setMinimumSize(QSize(150,18)); + last_tx_label->setFrameStyle(QFrame::Panel | QFrame::Sunken); + statusBar()->addWidget(last_tx_label); -void MainWindow::on_actionExit_triggered() //Exit() -{ - OnExit(); + auto_tx_label = new QLabel(""); + auto_tx_label->setAlignment(Qt::AlignHCenter); + auto_tx_label->setMinimumSize(QSize(150,18)); + auto_tx_label->setFrameStyle(QFrame::Panel | QFrame::Sunken); + statusBar()->addWidget(auto_tx_label); } void MainWindow::closeEvent(QCloseEvent * e) { - writeSettings (); - OnExit(); - delete g_pAstro; //Is there a better way ? - QMainWindow::closeEvent (e); -} + m_config.transceiver_offline (); + + writeSettings (); -void MainWindow::OnExit() -{ m_guiTimer.stop (); + if(m_fname != "") killFile(); m_killAll=true; mem_jt9->detach(); - QFile quitFile(m_appDir + "/.quit"); + QFile quitFile(".quit"); quitFile.open(QIODevice::ReadWrite); - QFile lockFile(m_appDir + "/.lock"); + QFile lockFile(".lock"); lockFile.remove(); // Allow jt9 to terminate bool b=proc_jt9.waitForFinished(1000); if(!b) proc_jt9.kill(); quitFile.remove(); Q_EMIT finished (); - m_audioThread.wait (); + + QMainWindow::closeEvent (e); } void MainWindow::on_stopButton_clicked() //stopButton { - m_monitoring=false; - Q_EMIT detectorSetMonitoring (m_monitoring); + // m_monitoring=false; + // Q_EMIT detectorSetMonitoring (m_monitoring); + monitor (false); m_loopall=false; } @@ -1191,8 +1022,8 @@ void MainWindow::msgBox(QString t) //msgBox void MainWindow::on_actionOnline_Users_Guide_triggered() //Display manual { QDesktopServices::openUrl(QUrl( - "http://www.physics.princeton.edu/pulsar/K1JT/wsjtx-doc/wsjtx-main-toc2.html", - QUrl::TolerantMode)); + "http://www.physics.princeton.edu/pulsar/K1JT/wsjtx-doc/wsjtx-main-toc2.html", + QUrl::TolerantMode)); } void MainWindow::on_actionWide_Waterfall_triggered() //Display Waterfalls @@ -1202,33 +1033,33 @@ void MainWindow::on_actionWide_Waterfall_triggered() //Display Waterfalls void MainWindow::on_actionAstronomical_data_triggered() { - if(g_pAstro==NULL) { - g_pAstro = new Astro(0); - g_pAstro->setWindowTitle("Astronomical Data"); - Qt::WindowFlags flags = Qt::Dialog | Qt::WindowCloseButtonHint | - Qt::WindowMinimizeButtonHint; - g_pAstro->setWindowFlags(flags); - g_pAstro->setGeometry(m_astroGeom); - } - g_pAstro->setFontSize(m_astroFont); - g_pAstro->show(); + if (!m_astroWidget) + { + m_astroWidget.reset (new Astro {m_settings, m_config.data_path ()}); + + // hook up termination signal + connect (this, &MainWindow::finished, m_astroWidget.data (), &Astro::close); + } + m_astroWidget->show(); } void MainWindow::on_actionOpen_triggered() //Open File { - m_monitoring=false; - Q_EMIT detectorSetMonitoring (m_monitoring); + // m_monitoring=false; + // Q_EMIT detectorSetMonitoring (m_monitoring); + monitor (false); + QString fname; fname=QFileDialog::getOpenFileName(this, "Open File", m_path, - "WSJT Files (*.wav)"); + "WSJT Files (*.wav)"); if(fname != "") { m_path=fname; int i; i=fname.indexOf(".wav") - 11; if(i>=0) { - lab1->setStyleSheet("QLabel{background-color: #66ff66}"); - lab1->setText(" " + fname.mid(i,15) + " "); -// lab1->setText(" " + fname + " "); + tx_status_label->setStyleSheet("QLabel{background-color: #66ff66}"); + tx_status_label->setText(" " + fname.mid(i,15) + " "); + // lab1->setText(" " + fname + " "); } on_stopButton_clicked(); m_diskData=true; @@ -1253,8 +1084,8 @@ void MainWindow::on_actionOpen_next_in_directory_triggered() //Open Next int i; i=fname.indexOf(".wav") - 11; if(i>=0) { - lab1->setStyleSheet("QLabel{background-color: #66ff66}"); - lab1->setText(" " + fname.mid(i,len) + " "); + tx_status_label->setStyleSheet("QLabel{background-color: #66ff66}"); + tx_status_label->setText(" " + fname.mid(i,len) + " "); } m_diskData=true; *future1 = QtConcurrent::run(getfile, fname, m_TRperiod); @@ -1263,7 +1094,7 @@ void MainWindow::on_actionOpen_next_in_directory_triggered() //Open Next } } } - //Open all remaining files +//Open all remaining files void MainWindow::on_actionDecode_remaining_files_in_directory_triggered() { m_loopall=true; @@ -1280,7 +1111,7 @@ void MainWindow::diskDat() //diskDat() jt9com_.npts8=k/8; dataSink(k * sizeof (jt9com_.d2[0])); if(n%10 == 1 or n == m_hsymStop) - qApp->processEvents(); //Keep GUI responsive + qApp->processEvents(); //Keep GUI responsive } } @@ -1294,11 +1125,11 @@ void MainWindow::on_actionDelete_all_wav_files_in_SaveDir_triggered() int i; QString fname; int ret = QMessageBox::warning(this, "Confirm Delete", - "Are you sure you want to delete all *.wav files in\n" + - QDir::toNativeSeparators(m_saveDir) + " ?", - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + "Are you sure you want to delete all *.wav files in\n" + + QDir::toNativeSeparators(m_config.save_directory ().absolutePath ()) + " ?", + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if(ret==QMessageBox::Yes) { - QDir dir(m_saveDir); + QDir dir(m_config.save_directory ()); QStringList files=dir.entryList(QDir::Files); QList::iterator f; for(f=files.begin(); f!=files.end(); ++f) { @@ -1335,15 +1166,14 @@ void MainWindow::on_actionKeyboard_shortcuts_triggered() pShortcuts = new QTextEdit(0); pShortcuts->setReadOnly(true); pShortcuts->setFontPointSize(10); - pShortcuts->setWindowTitle("Keyboard Shortcuts"); + pShortcuts->setWindowTitle(QApplication::applicationName () + " - " + tr ("Keyboard Shortcuts")); pShortcuts->setGeometry(QRect(45,50,430,460)); Qt::WindowFlags flags = Qt::WindowCloseButtonHint | - Qt::WindowMinimizeButtonHint; + Qt::WindowMinimizeButtonHint; pShortcuts->setWindowFlags(flags); - QString shortcuts = m_appDir + "/shortcuts.txt"; - QFile f(shortcuts); + QFile f(":/shortcuts.txt"); if(!f.open(QIODevice::ReadOnly | QIODevice::Text)) { - msgBox("Cannot open " + shortcuts); + msgBox("Cannot open \"" + f.fileName () + "\"."); return; } QTextStream s(&f); @@ -1362,15 +1192,14 @@ void MainWindow::on_actionSpecial_mouse_commands_triggered() pMouseCmnds = new QTextEdit(0); pMouseCmnds->setReadOnly(true); pMouseCmnds->setFontPointSize(10); - pMouseCmnds->setWindowTitle("Special Mouse Commands"); + pMouseCmnds->setWindowTitle(QApplication::applicationName () + " - " + tr ("Special Mouse Commands")); pMouseCmnds->setGeometry(QRect(45,50,440,300)); Qt::WindowFlags flags = Qt::WindowCloseButtonHint | - Qt::WindowMinimizeButtonHint; + Qt::WindowMinimizeButtonHint; pMouseCmnds->setWindowFlags(flags); - QString mouseCmnds = m_appDir + "/mouse_commands.txt"; - QFile f(mouseCmnds); + QFile f(":/mouse_commands.txt"); if(!f.open(QIODevice::ReadOnly | QIODevice::Text)) { - msgBox("Cannot open " + mouseCmnds); + msgBox("Cannot open \"" + f.fileName () + "\"."); return; } QTextStream s(&f); @@ -1383,7 +1212,7 @@ void MainWindow::on_actionSpecial_mouse_commands_triggered() pMouseCmnds->show(); } -void MainWindow::on_DecodeButton_clicked() //Decode request +void MainWindow::on_DecodeButton_clicked (bool /* checked */) //Decode request { if(!m_decoderBusy) { jt9com_.newdat=0; @@ -1401,13 +1230,13 @@ void MainWindow::freezeDecode(int n) //freezeDecode() ui->TxFreqSpinBox->setValue(i); m_wideGraph->setTxFreq(i); } - if((n%100)==2) on_DecodeButton_clicked(); + if((n%100)==2) on_DecodeButton_clicked (true); } void MainWindow::decode() //decode() { if(!m_dataAvailable) return; - ui->DecodeButton->setStyleSheet(m_pbdecoding_style1); + ui->DecodeButton->setChecked (true); if(jt9com_.newdat==1 && (!m_diskData)) { qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000; int imin=ms/60000; @@ -1453,12 +1282,12 @@ void MainWindow::decode() //decode() } memcpy(to, from, qMin(mem_jt9->size(), size)); - QFile lockFile(m_appDir + "/.lock"); // Allow jt9 to start + QFile lockFile(".lock"); // Allow jt9 to start lockFile.remove(); decodeBusy(true); } -void MainWindow::jt9_error(QProcess::ProcessError e) //jt9_error +void MainWindow::jt9_error (QProcess::ProcessError e) { if(!m_killAll) { msgBox("Error starting or running\n" + m_appDir + "/jt9 -s"); @@ -1476,85 +1305,88 @@ void MainWindow::readFromStderr() //readFromStderr void MainWindow::readFromStdout() //readFromStdout { while(proc_jt9.canReadLine()) - { - QByteArray t=proc_jt9.readLine(); - if(t.indexOf("") >= 0) { - m_bdecoded = (t.mid(23,1).toInt()==1); - bool keepFile=m_saveAll or (m_saveDecoded and m_bdecoded); - if(!keepFile and !m_diskData) killFileTimer->start(45*1000); //Kill in 45 s - jt9com_.nagain=0; - jt9com_.ndiskdat=0; - QFile lockFile(m_appDir + "/.lock"); - lockFile.open(QIODevice::ReadWrite); - ui->DecodeButton->setStyleSheet(""); - decodeBusy(false); - m_RxLog=0; - m_startAnother=m_loopall; - m_blankLine=true; - return; - } else { - QFile f("ALL.TXT"); - f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append); - QTextStream out(&f); - if(m_RxLog==1) { - out << QDateTime::currentDateTimeUtc().toString("yyyy-MMM-dd hh:mm") - << " " << m_dialFreq << " MHz " << m_mode << endl; - m_RxLog=0; - } - int n=t.length(); - out << t.mid(0,n-2) << endl; - f.close(); + QByteArray t=proc_jt9.readLine(); + if(t.indexOf("") >= 0) + { + m_bdecoded = (t.mid(23,1).toInt()==1); + bool keepFile=m_saveAll or (m_saveDecoded and m_bdecoded); + if(!keepFile and !m_diskData) killFileTimer->start(45*1000); //Kill in 45 s + jt9com_.nagain=0; + jt9com_.ndiskdat=0; + QFile lockFile(".lock"); + lockFile.open(QIODevice::ReadWrite); + ui->DecodeButton->setChecked (false); + decodeBusy(false); + m_RxLog=0; + m_startAnother=m_loopall; + m_blankLine=true; + return; + } else { + QFile f(m_config.data_path().absoluteFilePath ("ALL.TXT")); + f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append); + QTextStream out(&f); + if(m_RxLog==1) { + out << QDateTime::currentDateTimeUtc().toString("yyyy-MMM-dd hh:mm") + << " " << (m_dialFreq / 1.e6) << " MHz " << m_mode << endl; + m_RxLog=0; + } + int n=t.length(); + out << t.mid(0,n-2) << endl; + f.close(); - if(m_insertBlank and m_blankLine) - { - ui->decodedTextBrowser->insertLineSpacer(); - m_blankLine=false; - } + if(m_config.insert_blank () && m_blankLine) + { + ui->decodedTextBrowser->insertLineSpacer(); + m_blankLine=false; + } - DecodedText decodedtext; - decodedtext = t.replace("\n",""); //t.replace("\n","").mid(0,t.length()-4); + DecodedText decodedtext; + decodedtext = t.replace("\n",""); //t.replace("\n","").mid(0,t.length()-4); - // the left band display - ui->decodedTextBrowser->displayDecodedText(decodedtext,m_myCall,m_displayDXCCEntity,m_logBook); + // the left band display + ui->decodedTextBrowser->displayDecodedText (decodedtext + , m_config.my_callsign () + , m_config.DXCC () + , m_logBook); - if (abs(decodedtext.frequencyOffset() - m_wideGraph->rxFreq()) <= 10) // this msg is within 10 hertz of our tuned frequency - { - // the right QSO window - ui->decodedTextBrowser2->displayDecodedText(decodedtext,m_myCall,false,m_logBook); + if (abs(decodedtext.frequencyOffset() - m_wideGraph->rxFreq()) <= 10) // this msg is within 10 hertz of our tuned frequency + { + // the right QSO window + ui->decodedTextBrowser2->displayDecodedText(decodedtext,m_config.my_callsign (),false,m_logBook); - bool b65=decodedtext.isJT65(); - if(b65 and m_modeTx!="JT65") on_pbTxMode_clicked(); - if(!b65 and m_modeTx=="JT65") on_pbTxMode_clicked(); - m_QSOText=decodedtext; - } + bool b65=decodedtext.isJT65(); + if(b65 and m_modeTx!="JT65") on_pbTxMode_clicked(); + if(!b65 and m_modeTx=="JT65") on_pbTxMode_clicked(); + m_QSOText=decodedtext; + } - // find and extract any report for myCall - bool stdMsg = decodedtext.report(m_myCall,/*mod*/m_rptRcvd); + // find and extract any report for myCall + bool stdMsg = decodedtext.report(m_config.my_callsign (),/*mod*/m_rptRcvd); - // extract details and send to PSKreporter - int nsec=QDateTime::currentMSecsSinceEpoch()/1000-m_secBandChanged; - bool okToPost=(nsec>50); - if(m_pskReporter and stdMsg and !m_diskData and okToPost) - { - QString msgmode="JT9"; - if (decodedtext.isJT65()) + // extract details and send to PSKreporter + int nsec=QDateTime::currentMSecsSinceEpoch()/1000-m_secBandChanged; + bool okToPost=(nsec>50); + if(m_config.spot_to_psk_reporter () and stdMsg and !m_diskData and okToPost) + { + QString msgmode="JT9"; + if (decodedtext.isJT65()) msgmode="JT65"; - QString deCall; - QString grid; - decodedtext.deCallAndGrid(/*out*/deCall,grid); - int audioFrequency = decodedtext.frequencyOffset(); - int snr = decodedtext.snr(); - uint frequency = 1000000.0*m_dialFreq + audioFrequency + 0.5; + QString deCall; + QString grid; + decodedtext.deCallAndGrid(/*out*/deCall,grid); + int audioFrequency = decodedtext.frequencyOffset(); + int snr = decodedtext.snr(); + Frequency frequency = m_dialFreq + audioFrequency; - psk_Reporter->setLocalStation(m_myCall, m_myGrid, m_antDescription[m_band], "WSJT-X r" + rev.mid(6,4) ); - if(gridOK(grid)) + pskSetLocal (); + if(gridOK(grid)) psk_Reporter->addRemoteStation(deCall,grid,QString::number(frequency),msgmode,QString::number(snr), QString::number(QDateTime::currentDateTime().toTime_t())); + } } } - } } void MainWindow::killFile() @@ -1572,9 +1404,9 @@ void MainWindow::on_EraseButton_clicked() //Erase ui->decodedTextBrowser2->clear(); m_QSOText.clear(); if((ms-m_msErase)<500) { - ui->decodedTextBrowser->clear(); - QFile f(m_appDir + "/decoded.txt"); - if(f.exists()) f.remove(); + ui->decodedTextBrowser->clear(); + QFile f("decoded.txt"); + if(f.exists()) f.remove(); } m_msErase=ms; } @@ -1599,7 +1431,6 @@ void MainWindow::guiUpdate() static int giptt00=-1; static int gcomport00=-1; static double onAirFreq0=0.0; - int ret=0; QString rt; double tx1=0.0; @@ -1622,43 +1453,36 @@ void MainWindow::guiUpdate() bTxTime=true; } - double onAirFreq=m_dialFreq+1.e-6*m_txFreq; - if(onAirFreq>10.139900 and onAirFreq<10.140320) { - bTxTime=false; - if(m_tune) on_tuneButton_clicked(); - if(onAirFreq!=onAirFreq0) { - onAirFreq0=onAirFreq; - on_autoButton_clicked(); - QString t="Please choose another Tx frequency.\n"; - t+="WSJT-X will not knowingly transmit\n"; - t+="in the WSPR sub-band on 30 m."; - msgBox0.setText(t); - msgBox0.show(); + Frequency onAirFreq = m_dialFreq + m_txFreq; + if (onAirFreq > 10139900 && onAirFreq < 10140320) + { + bTxTime=false; + if (m_tune) + { + tuning (false); + } + + if (m_auto) + { + auto_tx_mode (false); + } + + if(onAirFreq!=onAirFreq0) + { + onAirFreq0=onAirFreq; + QString t="Please choose another Tx frequency.\n"; + t+="WSJT-X will not knowingly transmit\n"; + t+="in the WSPR sub-band on 30 m."; + msgBox0.setText(t); + msgBox0.show(); + } } - } float fTR=float((nsec%m_TRperiod))/m_TRperiod; if(g_iptt==0 and ((bTxTime and !m_btxMute and fTR<0.4) or m_tune )) { icw[0]=m_ncw; - -//Raise PTT - if(m_catEnabled and m_bRigOpen and m_pttMethodIndex==0) { - g_iptt=1; - if(m_pttData) ret=rig->setPTT(RIG_PTT_ON_DATA, RIG_VFO_CURR); - if(!m_pttData) ret=rig->setPTT(RIG_PTT_ON_MIC, RIG_VFO_CURR); - if(ret!=RIG_OK) { - rt.sprintf("CAT control PTT failed: %d",ret); - msgBox(rt); - } - - } - - if(m_pttMethodIndex==1 or m_pttMethodIndex==2) { //DTR or RTS - ptt(m_pttPort,1,&g_iptt,&g_COMportOpen); - } - if(m_pttMethodIndex==3) { //VOX - g_iptt=1; - } + g_iptt = 1; + Q_EMIT m_config.transceiver_ptt (true); ptt1Timer->start(200); //Sequencer delay } if(!bTxTime || m_btxMute) { @@ -1667,20 +1491,20 @@ void MainWindow::guiUpdate() } } -// Calculate Tx tones when needed + // Calculate Tx tones when needed if((g_iptt==1 && iptt0==0) || m_restart) { QByteArray ba; if(m_ntx == 1) ba=ui->tx1->text().toLocal8Bit(); if(m_ntx == 2) ba=ui->tx2->text().toLocal8Bit(); if(m_ntx == 3) ba=ui->tx3->text().toLocal8Bit(); if(m_ntx == 4) ba=ui->tx4->text().toLocal8Bit(); - if(m_ntx == 5) ba=ui->tx5->text().toLocal8Bit(); + if(m_ntx == 5) ba=ui->tx5->currentText().toLocal8Bit(); if(m_ntx == 6) ba=ui->tx6->text().toLocal8Bit(); if(m_ntx == 7) ba=ui->genMsg->text().toLocal8Bit(); - if(m_ntx == 8) ba=ui->freeTextMsg->text().toLocal8Bit(); + if(m_ntx == 8) ba=ui->freeTextMsg->currentText().toLocal8Bit(); ba2msg(ba,message); -// ba2msg(ba,msgsent); + // ba2msg(ba,msgsent); int len1=22; int ichk=0,itext=0; if(m_modeTx=="JT9") genjt9_(message,&ichk,msgsent,itone,&itext,len1,len1); @@ -1688,17 +1512,19 @@ void MainWindow::guiUpdate() msgsent[22]=0; QString t=QString::fromLatin1(msgsent); if(m_tune) t="TUNE"; - lab3->setText("Last Tx: " + t); + last_tx_label->setText("Last Tx: " + t); if(m_restart) { - QFile f("ALL.TXT"); + QFile f(m_config.data_path ().absoluteFilePath ("ALL.TXT")); f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append); QTextStream out(&f); out << QDateTime::currentDateTimeUtc().toString("hhmm") - << " Transmitting " << m_dialFreq << " MHz " << m_modeTx + << " Transmitting " << (m_dialFreq / 1.e6) << " MHz " << m_modeTx << ": " << t << endl; f.close(); - if(m_tx2QSO) + if (m_config.TX_messages ()) + { ui->decodedTextBrowser2->displayTransmittedText(t,m_modeTx,m_txFreq); + } } QStringList w=t.split(" ",QString::SkipEmptyParts); @@ -1707,20 +1533,20 @@ void MainWindow::guiUpdate() icw[0]=0; m_sent73=(t=="73" or itext!=0); if(m_sent73) { - if(m_After73) icw[0]=m_ncw; - if(m_promptToLog and !m_tune) logQSOTimer->start(200); + if(m_config.id_after_73 ()) icw[0]=m_ncw; + if(m_config.prompt_to_log () && !m_tune) logQSOTimer->start(200); } - if(m_idInt>0) { + if(m_config.id_interval () >0) { int nmin=(m_sec0-m_secID)/60; - if(nmin >= m_idInt) { + if(nmin >= m_config.id_interval ()) { icw[0]=m_ncw; m_secID=m_sec0; } } QString t2=QDateTime::currentDateTimeUtc().toString("hhmm"); - if(itext==0 and w.length()>=3 and w[1]==m_myCall) { + if(itext==0 and w.length()>=3 and w[1]==m_config.my_callsign ()) { int i1; bool ok; i1=t.toInt(&ok); @@ -1742,13 +1568,13 @@ void MainWindow::guiUpdate() } -// If PTT was just raised, start a countdown for raising TxOK: -// NB: could be better implemented with a timer + // If PTT was just raised, start a countdown for raising TxOK: + // NB: could be better implemented with a timer if(g_iptt == 1 && iptt0 == 0) { - nc1=-9; // TxDelay = 0.8 s + nc1=-9; // TxDelay = 0.8 s } if(nc1 <= 0) { - nc1++; + nc1++; } if(nc1 == 0) { QString t=QString::fromLatin1(msgsent); @@ -1760,99 +1586,85 @@ void MainWindow::guiUpdate() } signalMeter->setValue(0); - m_monitoring=false; - Q_EMIT detectorSetMonitoring (false); + + // m_monitoring=false; + // Q_EMIT detectorSetMonitoring (false); + if (m_monitoring) + { + monitor (false); + } + m_btxok=true; Q_EMIT muteAudioOutput (false); m_transmitting=true; ui->pbTxMode->setEnabled(false); if(!m_tune) { - QFile f("ALL.TXT"); + QFile f(m_config.data_path ().absoluteFilePath ("ALL.TXT")); f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append); QTextStream out(&f); out << QDateTime::currentDateTimeUtc().toString("hhmm") - << " Transmitting " << m_dialFreq << " MHz " << m_modeTx + << " Transmitting " << (m_dialFreq / 1.e6) << " MHz " << m_modeTx << ": " << t << endl; f.close(); } - if(m_tx2QSO and !m_tune) + if (m_config.TX_messages () && !m_tune) + { ui->decodedTextBrowser2->displayTransmittedText(t,m_modeTx,m_txFreq); + } } if(!m_btxok && btxok0 && g_iptt==1) stopTx(); -/* -// If m_btxok was just lowered, start a countdown for lowering PTT + /* + // If m_btxok was just lowered, start a countdown for lowering PTT if(!m_btxok && btxok0 && g_iptt==1) nc0=-11; //RxDelay = 1.0 s if(nc0 <= 0) { - nc0++; - } -*/ - - if(m_monitoring) { - ui->monitorButton->setStyleSheet(m_pbmonitor_style); - } else { - ui->monitorButton->setStyleSheet(""); + nc0++; } + */ if(m_startAnother) { m_startAnother=false; on_actionOpen_next_in_directory_triggered(); } - if(m_catEnabled and !m_bRigOpen) { - rigOpen(); - if(m_bSplit or m_bXIT) setXIT(m_txFreq); - if(m_bRigOpen and !m_bSplit) { - int ret=rig->setSplitFreq(MHz(m_dialFreq),RIG_VFO_B); - if(ret!=RIG_OK) { - QString rt; - rt.sprintf("Setting VFO_B failed: %d",ret); - msgBox(rt); - } - } - } - if(nsec != m_sec0) { //Once per second QDateTime t = QDateTime::currentDateTimeUtc(); int fQSO=125; - m_azelDir=m_appDir; - if(g_pAstro!=NULL) g_pAstro->astroUpdate(t, m_myGrid, m_hisGrid, fQSO, - m_setftx, m_txFreq, m_azelDir); + if(m_astroWidget) m_astroWidget->astroUpdate(t, m_config.my_grid (), m_hisGrid, fQSO, + m_setftx, m_txFreq); if(m_transmitting) { if(nsendingsh==1) { - lab1->setStyleSheet("QLabel{background-color: #66ffff}"); + tx_status_label->setStyleSheet("QLabel{background-color: #66ffff}"); } else if(nsendingsh==-1) { - lab1->setStyleSheet("QLabel{background-color: #ffccff}"); + tx_status_label->setStyleSheet("QLabel{background-color: #ffccff}"); } else { - lab1->setStyleSheet("QLabel{background-color: #ffff33}"); + tx_status_label->setStyleSheet("QLabel{background-color: #ffff33}"); } if(m_tune) { - lab1->setText("Tx: TUNE"); + tx_status_label->setText("Tx: TUNE"); } else { - char s[37]; - sprintf(s,"Tx: %s",msgsent); - lab1->setText(s); + char s[37]; + sprintf(s,"Tx: %s",msgsent); + tx_status_label->setText(s); } } else if(m_monitoring) { - lab1->setStyleSheet("QLabel{background-color: #00ff00}"); - lab1->setText("Receiving "); + tx_status_label->setStyleSheet("QLabel{background-color: #00ff00}"); + tx_status_label->setText("Receiving "); } else if (!m_diskData) { - lab1->setStyleSheet(""); - lab1->setText(""); + tx_status_label->setStyleSheet(""); + tx_status_label->setText(""); } m_setftx=0; QString utc = t.date().toString("yyyy MMM dd") + "\n " + - t.time().toString() + " "; + t.time().toString() + " "; ui->labUTC->setText(utc); if(!m_monitoring and !m_diskData) { signalMeter->setValue(0); } - if(m_catEnabled and m_poll>0 and (nsec%m_poll)==0 and - !m_decoderBusy) pollRigFreq(); m_sec0=nsec; } @@ -1869,18 +1681,19 @@ void MainWindow::guiUpdate() void MainWindow::startTx2() { if (!m_modulator.isActive ()) { - m_fSpread=0.0; + double fSpread=0.0; double snr=99.0; - QString t=ui->tx5->text(); - if(t.mid(0,1)=="#") m_fSpread=t.mid(1,5).toDouble(); - m_modulator.setWide9(m_toneSpacing, m_fSpread); + QString t=ui->tx5->currentText(); + if(t.mid(0,1)=="#") fSpread=t.mid(1,5).toDouble(); + m_modulator.setWide9(m_toneSpacing, fSpread); t=ui->tx6->text(); if(t.mid(0,1)=="#") snr=t.mid(1,5).toDouble(); if(snr>0.0 or snr < -50.0) snr=99.0; transmit (snr); signalMeter->setValue(0); - m_monitoring=false; - Q_EMIT detectorSetMonitoring (false); + + monitor (false); + m_btxok=true; Q_EMIT muteAudioOutput (false); m_transmitting=true; @@ -1893,39 +1706,34 @@ void MainWindow::stopTx() Q_EMIT endTransmitMessage (); Q_EMIT stopAudioOutputStream (); m_transmitting=false; - ui->pbTxMode->setEnabled(true); + if ("JT9+JT65" == m_mode) ui->pbTxMode->setEnabled(true); g_iptt=0; - lab1->setStyleSheet(""); - lab1->setText(""); + tx_status_label->setStyleSheet(""); + tx_status_label->setText(""); ptt0Timer->start(200); //Sequencer delay - m_monitoring=true; - Q_EMIT detectorSetMonitoring (true); + + monitor (true); } void MainWindow::stopTx2() { - int ret=0; QString rt; -//Lower PTT - if(m_catEnabled and m_bRigOpen and m_pttMethodIndex==0) { - ret=rig->setPTT(RIG_PTT_OFF, RIG_VFO_CURR); //CAT control for PTT=0 - if(ret!=RIG_OK) { - rt.sprintf("CAT control PTT failed: %d",ret); - msgBox(rt); - } - } - if(m_pttMethodIndex==1 or m_pttMethodIndex==2) { - ptt(m_pttPort,0,&g_iptt,&g_COMportOpen); - } - if(m_73TxDisable and m_sent73) on_stopTxButton_clicked(); + //Lower PTT + Q_EMIT m_config.transceiver_ptt (false); - if(m_runaway and m_repeatMsg>m_watchdogLimit) { - on_stopTxButton_clicked(); - msgBox0.setText("Runaway Tx watchdog"); - msgBox0.show(); - m_repeatMsg=0; - } + if (m_config.disable_TX_on_73 () && m_sent73) + { + on_stopTxButton_clicked(); + } + + if (m_config.watchdog () && m_repeatMsg>m_watchdogLimit) + { + on_stopTxButton_clicked(); + msgBox0.setText("Runaway Tx watchdog"); + msgBox0.show(); + m_repeatMsg=0; + } } void MainWindow::ba2msg(QByteArray ba, char message[]) //ba2msg() @@ -2019,17 +1827,17 @@ void MainWindow::doubleClickOnCall(bool shift, bool ctrl) decodedtext = t1.mid(i1,i2-i1); //selected line if (decodedtext.indexOf(" CQ ") > 0) - { + { // TODO this magic 36 characters is also referenced in DisplayText::_appendDXCCWorkedB4() int s3 = decodedtext.indexOf(" ",35); if (s3 < 35) - s3 = 35; // we always want at least the characters to position 35 + s3 = 35; // we always want at least the characters to position 35 s3 += 1; // convert the index into a character count decodedtext = decodedtext.left(s3); // remove DXCC entity and worked B4 status. TODO need a better way to do this - } + } -// if(decodedtext.indexOf("Tx")==6) return; //Ignore Tx line + // if(decodedtext.indexOf("Tx")==6) return; //Ignore Tx line int i4=t.mid(i1).length(); if(i4>55) i4=55; QString t3=t.mid(i1,i4); @@ -2040,51 +1848,51 @@ void MainWindow::doubleClickOnCall(bool shift, bool ctrl) int i9=m_QSOText.indexOf(decodedtext.string()); if (i9<0 and !decodedtext.isTX()) - { - ui->decodedTextBrowser2->displayDecodedText(decodedtext,m_myCall,false,m_logBook); - m_QSOText=decodedtext; - } + { + ui->decodedTextBrowser2->displayDecodedText(decodedtext,m_config.my_callsign (),false,m_logBook); + m_QSOText=decodedtext; + } int frequency = decodedtext.frequencyOffset(); m_wideGraph->setRxFreq(frequency); //Set Rx freq if (decodedtext.isTX()) - { - if (ctrl) + { + if (ctrl) ui->TxFreqSpinBox->setValue(frequency); //Set Tx freq - return; - } + return; + } QString firstcall = decodedtext.call(); // Don't change Tx freq if a station is calling me, unless m_lockTxFreq // is true or CTRL is held down - if ((firstcall!=m_myCall) or m_lockTxFreq or ctrl) + if ((firstcall!=m_config.my_callsign ()) or m_lockTxFreq or ctrl) ui->TxFreqSpinBox->setValue(frequency); if (decodedtext.isJT9()) - { - m_modeTx="JT9"; - ui->pbTxMode->setText("Tx JT9 @"); - m_wideGraph->setModeTx(m_modeTx); - } + { + m_modeTx="JT9"; + ui->pbTxMode->setText("Tx JT9 @"); + m_wideGraph->setModeTx(m_modeTx); + } else - if (decodedtext.isJT65()) + if (decodedtext.isJT65()) { - m_modeTx="JT65"; - ui->pbTxMode->setText("Tx JT65 #"); - m_wideGraph->setModeTx(m_modeTx); + m_modeTx="JT65"; + ui->pbTxMode->setText("Tx JT65 #"); + m_wideGraph->setModeTx(m_modeTx); } QString hiscall; QString hisgrid; decodedtext.deCallAndGrid(/*out*/hiscall,hisgrid); if (hiscall != ui->dxCallEntry->text()) - ui->dxGridEntry->setText(""); + ui->dxGridEntry->setText(""); ui->dxCallEntry->setText(hiscall); if (gridOK(hisgrid)) - ui->dxGridEntry->setText(hisgrid); + ui->dxGridEntry->setText(hisgrid); if (ui->dxGridEntry->text()=="") - lookup(); + lookup(); m_hisGrid = ui->dxGridEntry->text(); int n = decodedtext.timeInSeconds(); @@ -2097,70 +1905,70 @@ void MainWindow::doubleClickOnCall(bool shift, bool ctrl) genStdMsgs(rpt); // determine the appropriate response to the received msg - if(decodedtext.indexOf(m_myCall)>=0) - { - if (t4.length()>=7 // enough fields for a normal msg - and !gridOK(t4.at(7))) // but no grid on end of msg + if(decodedtext.indexOf(m_config.my_callsign ())>=0) { - QString r=t4.at(7); - if(r.mid(0,3)=="RRR") { - m_ntx=5; - ui->txrb5->setChecked(true); + if (t4.length()>=7 // enough fields for a normal msg + and !gridOK(t4.at(7))) // but no grid on end of msg + { + QString r=t4.at(7); + if(r.mid(0,3)=="RRR") { + m_ntx=5; + ui->txrb5->setChecked(true); + if(ui->tabWidget->currentIndex()==1) { + ui->genMsg->setText(ui->tx5->currentText()); + m_ntx=7; + ui->rbGenMsg->setChecked(true); + } + } else if(r.mid(0,1)=="R") { + m_ntx=4; + ui->txrb4->setChecked(true); + if(ui->tabWidget->currentIndex()==1) { + ui->genMsg->setText(ui->tx4->text()); + m_ntx=7; + ui->rbGenMsg->setChecked(true); + } + } else if(r.toInt()>=-50 and r.toInt()<=49) { + m_ntx=3; + ui->txrb3->setChecked(true); + if(ui->tabWidget->currentIndex()==1) { + ui->genMsg->setText(ui->tx3->text()); + m_ntx=7; + ui->rbGenMsg->setChecked(true); + } + } else if(r.toInt()==73) { + m_ntx=5; + ui->txrb5->setChecked(true); + if(ui->tabWidget->currentIndex()==1) { + ui->genMsg->setText(ui->tx5->currentText()); + m_ntx=7; + ui->rbGenMsg->setChecked(true); + } + } + } else { + m_ntx=2; + ui->txrb2->setChecked(true); if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx5->text()); - m_ntx=7; - ui->rbGenMsg->setChecked(true); - } - } else if(r.mid(0,1)=="R") { - m_ntx=4; - ui->txrb4->setChecked(true); - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx4->text()); - m_ntx=7; - ui->rbGenMsg->setChecked(true); - } - } else if(r.toInt()>=-50 and r.toInt()<=49) { - m_ntx=3; - ui->txrb3->setChecked(true); - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx3->text()); - m_ntx=7; - ui->rbGenMsg->setChecked(true); - } - } else if(r.toInt()==73) { - m_ntx=5; - ui->txrb5->setChecked(true); - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx5->text()); + ui->genMsg->setText(ui->tx2->text()); m_ntx=7; ui->rbGenMsg->setChecked(true); } } - } else { - m_ntx=2; - ui->txrb2->setChecked(true); + + } + else // myCall not in msg + { + m_ntx=1; + ui->txrb1->setChecked(true); if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx2->text()); + ui->genMsg->setText(ui->tx1->text()); m_ntx=7; ui->rbGenMsg->setChecked(true); } } - - } - else // myCall not in msg - { - m_ntx=1; - ui->txrb1->setChecked(true); - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx1->text()); - m_ntx=7; - ui->rbGenMsg->setChecked(true); + if(m_config.quick_call ()) + { + auto_tx_mode (true); } - } - if(m_quickCall) { - m_auto=true; - ui->autoButton->setStyleSheet(m_pbAutoOn_style); - } } void MainWindow::genStdMsgs(QString rpt) //genStdMsgs() @@ -2175,29 +1983,29 @@ void MainWindow::genStdMsgs(QString rpt) //genStdMsgs() ui->tx2->setText(""); ui->tx3->setText(""); ui->tx4->setText(""); - ui->tx5->setText(""); + ui->tx5->setCurrentText(""); ui->tx6->setText(""); - if(m_myCall!="" and m_myGrid!="") { - t="CQ " + m_myCall + " " + m_myGrid.mid(0,4); + if(m_config.my_callsign () !="" and m_config.my_grid () !="") { + t="CQ " + m_config.my_callsign () + " " + m_config.my_grid ().mid(0,4); msgtype(t, ui->tx6); } ui->genMsg->setText(""); - ui->freeTextMsg->setText(""); + ui->freeTextMsg->setCurrentText(""); return; } QString hisBase=baseCall(hisCall); - QString myBase=baseCall(m_myCall); + QString myBase=baseCall(m_config.my_callsign ()); QString t0=hisBase + " " + myBase + " "; - t=t0 + m_myGrid.mid(0,4); -// if(myBase!=m_myCall) t="DE " + m_myCall + " " + m_myGrid.mid(0,4); //### + t=t0 + m_config.my_grid ().mid(0,4); + // if(myBase!=m_config.my_callsign ()) t="DE " + m_config.my_callsign () + " " + m_config.my_grid ().mid(0,4); //### msgtype(t, ui->tx1); if(rpt == "") { t=t+" OOO"; msgtype(t, ui->tx2); msgtype("RO", ui->tx3); msgtype("RRR", ui->tx4); - msgtype("73", ui->tx5); + msgtype("73", ui->tx5->lineEdit ()); } else { t=t0 + rpt; msgtype(t, ui->tx2); @@ -2206,29 +2014,29 @@ void MainWindow::genStdMsgs(QString rpt) //genStdMsgs() t=t0 + "RRR"; msgtype(t, ui->tx4); t=t0 + "73"; -// if(myBase!=m_myCall) t="DE " + m_myCall + " 73"; //### - msgtype(t, ui->tx5); + // if(myBase!=m_config.my_callsign ()) t="DE " + m_config.my_callsign () + " 73"; //### + msgtype(t, ui->tx5->lineEdit ()); } - t="CQ " + m_myCall + " " + m_myGrid.mid(0,4); + t="CQ " + m_config.my_callsign () + " " + m_config.my_grid ().mid(0,4); msgtype(t, ui->tx6); - if(m_myCall!=myBase) { - if(shortList(m_myCall)) { - t="CQ " + m_myCall; + if(m_config.my_callsign ()!=myBase) { + if(shortList(m_config.my_callsign ())) { + t="CQ " + m_config.my_callsign (); msgtype(t, ui->tx6); } else { - t="DE " + m_myCall + " " + m_myGrid.mid(0,4); + t="DE " + m_config.my_callsign () + " " + m_config.my_grid ().mid(0,4); msgtype(t, ui->tx2); - t="DE " + m_myCall + " 73"; - msgtype(t, ui->tx5); - t="CQ " + m_myCall + " " + m_myGrid.mid(0,4); + t="DE " + m_config.my_callsign () + " 73"; + msgtype(t, ui->tx5->lineEdit ()); + t="CQ " + m_config.my_callsign () + " " + m_config.my_grid ().mid(0,4); msgtype(t, ui->tx6); } } else { if(hisCall!=hisBase) { if(shortList(hisCall)) { - t=hisCall + " " + m_myCall; + t=hisCall + " " + m_config.my_callsign (); msgtype(t, ui->tx2); } } @@ -2251,35 +2059,33 @@ void MainWindow::lookup() //lookup() { QString hisCall=ui->dxCallEntry->text().toUpper().trimmed(); ui->dxCallEntry->setText(hisCall); - QString call3File = m_appDir + "/CALL3.TXT"; - QFile f(call3File); - if(!f.open(QIODevice::ReadOnly | QIODevice::Text)) { - msgBox("Cannot open " + call3File); - return; - } - char c[132]; - qint64 n=0; - for(int i=0; i<999999; i++) { - n=f.readLine(c,sizeof(c)); - if(n <= 0) { - ui->dxGridEntry->setText(""); - break; - } - QString t=QString(c); - if(t.indexOf(hisCall)==0) { - int i1=t.indexOf(","); - QString hisgrid=t.mid(i1+1,6); - i1=hisgrid.indexOf(","); - if(i1>0) { - hisgrid=hisgrid.mid(0,4); - } else { - hisgrid=hisgrid.mid(0,4) + hisgrid.mid(4,2).toLower(); + QFile f(m_config.data_path ().absoluteFilePath ("CALL3.TXT")); + if (f.open (QIODevice::ReadOnly | QIODevice::Text)) + { + char c[132]; + qint64 n=0; + for(int i=0; i<999999; i++) { + n=f.readLine(c,sizeof(c)); + if(n <= 0) { + ui->dxGridEntry->setText(""); + break; + } + QString t=QString(c); + if(t.indexOf(hisCall)==0) { + int i1=t.indexOf(","); + QString hisgrid=t.mid(i1+1,6); + i1=hisgrid.indexOf(","); + if(i1>0) { + hisgrid=hisgrid.mid(0,4); + } else { + hisgrid=hisgrid.mid(0,4) + hisgrid.mid(4,2).toLower(); + } + ui->dxGridEntry->setText(hisgrid); + break; + } } - ui->dxGridEntry->setText(hisgrid); - break; + f.close(); } - } - f.close(); } void MainWindow::on_lookupButton_clicked() //Lookup button @@ -2298,33 +2104,29 @@ void MainWindow::on_addButton_clicked() //Add button QString hisgrid=ui->dxGridEntry->text().trimmed(); QString newEntry=hisCall + "," + hisgrid; -// int ret = QMessageBox::warning(this, "Add", -// newEntry + "\n" + "Is this station known to be active on EME?", -// QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); -// if(ret==QMessageBox::Yes) { -// newEntry += ",EME,,"; -// } else { - newEntry += ",,,"; -// } - - QString call3File = m_appDir + "/CALL3.TXT"; - QFile f1(call3File); - if(!f1.open(QIODevice::ReadOnly | QIODevice::Text)) { - msgBox("Cannot open " + call3File); + // int ret = QMessageBox::warning(this, "Add", + // newEntry + "\n" + "Is this station known to be active on EME?", + // QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + // if(ret==QMessageBox::Yes) { + // newEntry += ",EME,,"; + // } else { + newEntry += ",,,"; + // } + + QFile f1(m_config.data_path ().absoluteFilePath ("CALL3.TXT")); + if(!f1.open(QIODevice::ReadWrite | QIODevice::Text)) { + msgBox("Cannot open \"" + f1.fileName () + "\"."); return; } if(f1.size()==0) { - f1.close(); - f1.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&f1); out << "ZZZZZZ" << endl; f1.close(); f1.open(QIODevice::ReadOnly | QIODevice::Text); } - QString tmpFile = m_appDir + "/CALL3.TMP"; - QFile f2(tmpFile); + QFile f2(m_config.data_path ().absoluteFilePath ("CALL3.TMP")); if(!f2.open(QIODevice::WriteOnly | QIODevice::Text)) { - msgBox("Cannot open " + tmpFile); + msgBox("Cannot open \"" + f2.fileName () + "\"."); return; } QTextStream in(&f1); @@ -2345,14 +2147,14 @@ void MainWindow::on_addButton_clicked() //Add button out << newEntry + "\n"; if(s.mid(0,6)=="ZZZZZZ") { out << s + "\n"; -// exit; //Statement has no effect! + // exit; //Statement has no effect! } m_call3Modified=true; } else if(hc==hc2) { QString t=s + "\n\n is already in CALL3.TXT\n" + - "Do you wish to replace it?"; + "Do you wish to replace it?"; int ret = QMessageBox::warning(this, "Add",t, - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if(ret==QMessageBox::Yes) { out << newEntry + "\n"; m_call3Modified=true; @@ -2368,11 +2170,12 @@ void MainWindow::on_addButton_clicked() //Add button out << newEntry + "\n"; } if(m_call3Modified) { - QFile f0(m_appDir + "/CALL3.OLD"); + QDir data_path {m_config.data_path ()}; + QFile f0(data_path.absoluteFilePath ("CALL3.OLD")); if(f0.exists()) f0.remove(); - QFile f1(m_appDir + "/CALL3.TXT"); - f1.rename(m_appDir + "/CALL3.OLD"); - f2.rename(m_appDir + "/CALL3.TXT"); + QFile f1(data_path.absoluteFilePath ("CALL3.TXT")); + f1.rename(data_path.absoluteFilePath ("CALL3.OLD")); + f2.rename(data_path.absoluteFilePath ("CALL3.TXT")); f2.close(); } } @@ -2434,10 +2237,10 @@ void MainWindow::on_tx4_editingFinished() //tx4 edited msgtype(t, ui->tx4); } -void MainWindow::on_tx5_editingFinished() //tx5 edited +void MainWindow::on_tx5_currentTextChanged (QString const& text) //tx5 edited { - QString t=ui->tx5->text(); - msgtype(t, ui->tx5); + msgtype(text, ui->tx5->lineEdit ()); + msgtype(text, ui->freeTextMsg->lineEdit ()); } void MainWindow::on_tx6_editingFinished() //tx6 edited @@ -2472,20 +2275,26 @@ void MainWindow::on_dxGridEntry_textChanged(const QString &t) //dxGrid changed if(!t[2].isDigit() or !t[3].isDigit()) return; if(n==4) m_hisGrid=t.mid(0,2).toUpper() + t.mid(2,2); if(n==6) m_hisGrid=t.mid(0,2).toUpper() + t.mid(2,2) + - t.mid(4,2).toLower(); + t.mid(4,2).toLower(); ui->dxGridEntry->setText(m_hisGrid); if(gridOK(m_hisGrid)) { qint64 nsec = QDateTime::currentMSecsSinceEpoch() % 86400; double utch=nsec/3600.0; int nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter; - azdist_(m_myGrid.toLatin1().data(),m_hisGrid.toLatin1().data(),&utch, - &nAz,&nEl,&nDmiles,&nDkm,&nHotAz,&nHotABetter,6,6); + azdist_(const_cast (m_config.my_grid ().toLatin1().constData()),const_cast (m_hisGrid.toLatin1().constData()),&utch, + &nAz,&nEl,&nDmiles,&nDkm,&nHotAz,&nHotABetter,6,6); QString t; t.sprintf("Az: %d",nAz); ui->labAz->setText(t); - if(m_bMiles) t.sprintf("%d mi",int(0.621371*nDkm)); - if(!m_bMiles) t.sprintf("%d km",nDkm); + if (m_config.miles ()) + { + t.sprintf ("%d mi", int (0.621371 * nDkm)); + } + else + { + t.sprintf ("%d km", nDkm); + } ui->labDist->setText(t); } else { ui->labAz->setText(""); @@ -2503,21 +2312,31 @@ void MainWindow::on_logQSOButton_clicked() //Log QSO button if(m_hisCall=="") return; m_dateTimeQSO=QDateTime::currentDateTimeUtc(); - m_logDlg->initLogQSO(m_hisCall,m_hisGrid,m_modeTx,m_rptSent,m_rptRcvd, - m_dateTimeQSO,m_dialFreq+m_txFreq/1.0e6, - m_myCall,m_myGrid,m_noSuffix,m_toRTTY,m_dBtoComments); + m_logDlg->initLogQSO (m_hisCall + , m_hisGrid + , m_modeTx + , m_rptSent + , m_rptRcvd + , m_dateTimeQSO + , (m_dialFreq + m_txFreq) / 1.e6 + , m_config.my_callsign () + , m_config.my_grid () + , m_noSuffix + , m_config.log_as_RTTY () + , m_config.report_in_comments () + ); } void MainWindow::acceptQSO2(bool accepted) { - if(accepted) + if(accepted) { - QString band = ADIF::bandFromFrequency(m_dialFreq+m_txFreq/1.0e6); - QString date = m_dateTimeQSO.toString("yyyy-MM-dd"); - date=date.mid(0,4) + date.mid(5,2) + date.mid(8,2); - m_logBook.addAsWorked(m_hisCall,band,m_modeTx,date); + QString band = ADIF::bandFromFrequency ((m_dialFreq + m_txFreq) / 1.e6); + QString date = m_dateTimeQSO.toString("yyyy-MM-dd"); + date=date.mid(0,4) + date.mid(5,2) + date.mid(8,2); + m_logBook.addAsWorked(m_hisCall,band,m_modeTx,date); - if (m_clearCallGrid) + if (m_config.clear_DX ()) { m_hisCall=""; ui->dxCallEntry->setText(""); @@ -2539,9 +2358,9 @@ void MainWindow::on_actionJT9_1_triggered() m_TRperiod=60; m_nsps=6912; m_hsymStop=173; + mode_label->setStyleSheet("QLabel{background-color: #ff6ec7}"); + mode_label->setText(m_mode); m_toneSpacing=0.0; - lab2->setStyleSheet("QLabel{background-color: #ff6ec7}"); - lab2->setText(m_mode); ui->actionJT9_1->setChecked(true); m_wideGraph->setPeriod(m_TRperiod,m_nsps); m_wideGraph->setMode(m_mode); @@ -2557,9 +2376,9 @@ void MainWindow::on_actionJT9W_1_triggered() m_TRperiod=60; m_nsps=6912; m_hsymStop=173; - m_toneSpacing=pow(2,m_toneMultIndex)*12000.0/6912.0; - lab2->setStyleSheet("QLabel{background-color: #ff6ec7}"); - lab2->setText(m_mode); + m_toneSpacing=pow(2,m_config.jt9w_bw_mult ())*12000.0/6912.0; + mode_label->setStyleSheet("QLabel{background-color: #ff6ec7}"); + mode_label->setText(m_mode); ui->actionJT9W_1->setChecked(true); m_wideGraph->setPeriod(m_TRperiod,m_nsps); m_wideGraph->setMode(m_mode); @@ -2575,8 +2394,9 @@ void MainWindow::on_actionJT65_triggered() m_TRperiod=60; m_nsps=6912; //For symspec only m_hsymStop=173; - lab2->setStyleSheet("QLabel{background-color: #ffff00}"); - lab2->setText(m_mode); + m_toneSpacing=0.0; + mode_label->setStyleSheet("QLabel{background-color: #ffff00}"); + mode_label->setText(m_mode); ui->actionJT65->setChecked(true); m_wideGraph->setPeriod(m_TRperiod,m_nsps); m_wideGraph->setMode(m_mode); @@ -2587,13 +2407,14 @@ void MainWindow::on_actionJT65_triggered() void MainWindow::on_actionJT9_JT65_triggered() { m_mode="JT9+JT65"; -// if(m_modeTx!="JT9") on_pbTxMode_clicked(); + // if(m_modeTx!="JT9") on_pbTxMode_clicked(); statusChanged(); m_TRperiod=60; m_nsps=6912; m_hsymStop=173; - lab2->setStyleSheet("QLabel{background-color: #ffa500}"); - lab2->setText(m_mode); + m_toneSpacing=0.0; + mode_label->setStyleSheet("QLabel{background-color: #ffa500}"); + mode_label->setText(m_mode); ui->actionJT9_JT65->setChecked(true); m_wideGraph->setPeriod(m_TRperiod,m_nsps); m_wideGraph->setMode(m_mode); @@ -2606,7 +2427,7 @@ void MainWindow::on_TxFreqSpinBox_valueChanged(int n) m_txFreq=n; m_wideGraph->setTxFreq(n); if(m_lockTxFreq) ui->RxFreqSpinBox->setValue(n); - Q_EMIT transmitFrequency (m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); + Q_EMIT transmitFrequency (m_txFreq - m_XIT); } void MainWindow::on_RxFreqSpinBox_valueChanged(int n) @@ -2639,18 +2460,13 @@ void MainWindow::on_inGain_valueChanged(int n) m_inGain=n; } -void MainWindow::on_actionMonitor_OFF_at_startup_triggered() -{ - m_monitorStartOFF=!m_monitorStartOFF; -} - void MainWindow::on_actionErase_ALL_TXT_triggered() //Erase ALL.TXT { int ret = QMessageBox::warning(this, "Confirm Erase", - "Are you sure you want to erase file ALL.TXT ?", - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + "Are you sure you want to erase file ALL.TXT ?", + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if(ret==QMessageBox::Yes) { - QFile f("ALL.TXT"); + QFile f(m_config.data_path ().absoluteFilePath ("ALL.TXT")); f.remove(); m_RxLog=1; } @@ -2659,169 +2475,93 @@ void MainWindow::on_actionErase_ALL_TXT_triggered() //Erase ALL.TXT void MainWindow::on_actionErase_wsjtx_log_adi_triggered() { int ret = QMessageBox::warning(this, "Confirm Erase", - "Are you sure you want to erase file wsjtx_log.adi ?", - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + "Are you sure you want to erase file wsjtx_log.adi ?", + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if(ret==QMessageBox::Yes) { - QFile f("wsjtx_log.adi"); + QFile f(m_config.data_path ().absoluteFilePath ("wsjtx_log.adi")); f.remove(); } } -void MainWindow::showMacros(const QPoint &pos) -{ - if(m_macro.length()<10) return; - QPoint globalPos = ui->tx5->mapToGlobal(pos); - QMenu popupMenu; - QAction* popup1 = new QAction(m_macro[0],ui->tx5); - QAction* popup2 = new QAction(m_macro[1],ui->tx5); - QAction* popup3 = new QAction(m_macro[2],ui->tx5); - QAction* popup4 = new QAction(m_macro[3],ui->tx5); - QAction* popup5 = new QAction(m_macro[4],ui->tx5); - QAction* popup6 = new QAction(m_macro[5],ui->tx5); - QAction* popup7 = new QAction(m_macro[6],ui->tx5); - QAction* popup8 = new QAction(m_macro[7],ui->tx5); - QAction* popup9 = new QAction(m_macro[8],ui->tx5); - QAction* popup10 = new QAction(m_macro[9],ui->tx5); - - if(m_macro[0]!="") popupMenu.addAction(popup1); - if(m_macro[1]!="") popupMenu.addAction(popup2); - if(m_macro[2]!="") popupMenu.addAction(popup3); - if(m_macro[3]!="") popupMenu.addAction(popup4); - if(m_macro[4]!="") popupMenu.addAction(popup5); - if(m_macro[5]!="") popupMenu.addAction(popup6); - if(m_macro[6]!="") popupMenu.addAction(popup7); - if(m_macro[7]!="") popupMenu.addAction(popup8); - if(m_macro[8]!="") popupMenu.addAction(popup9); - if(m_macro[9]!="") popupMenu.addAction(popup10); - - connect(popup1,SIGNAL(triggered()), this, SLOT(onPopup1())); - connect(popup2,SIGNAL(triggered()), this, SLOT(onPopup2())); - connect(popup3,SIGNAL(triggered()), this, SLOT(onPopup3())); - connect(popup4,SIGNAL(triggered()), this, SLOT(onPopup4())); - connect(popup5,SIGNAL(triggered()), this, SLOT(onPopup5())); - connect(popup6,SIGNAL(triggered()), this, SLOT(onPopup6())); - connect(popup7,SIGNAL(triggered()), this, SLOT(onPopup7())); - connect(popup8,SIGNAL(triggered()), this, SLOT(onPopup8())); - connect(popup9,SIGNAL(triggered()), this, SLOT(onPopup9())); - connect(popup10,SIGNAL(triggered()), this, SLOT(onPopup10())); - popupMenu.exec(globalPos); -} - -void MainWindow::onPopup1() { ui->tx5->setText(m_macro[0]); freeText(); } -void MainWindow::onPopup2() { ui->tx5->setText(m_macro[1]); freeText(); } -void MainWindow::onPopup3() { ui->tx5->setText(m_macro[2]); freeText(); } -void MainWindow::onPopup4() { ui->tx5->setText(m_macro[3]); freeText(); } -void MainWindow::onPopup5() { ui->tx5->setText(m_macro[4]); freeText(); } -void MainWindow::onPopup6() { ui->tx5->setText(m_macro[5]); freeText(); } -void MainWindow::onPopup7() { ui->tx5->setText(m_macro[6]); freeText(); } -void MainWindow::onPopup8() { ui->tx5->setText(m_macro[7]); freeText(); } -void MainWindow::onPopup9() { ui->tx5->setText(m_macro[8]); freeText(); } -void MainWindow::onPopup10() { ui->tx5->setText(m_macro[9]); freeText(); } - -void MainWindow::freeText() { ui->freeTextMsg->setText(ui->tx5->text()); } - bool MainWindow::gridOK(QString g) { bool b=g.mid(0,1).compare("A")>=0 and - g.mid(0,1).compare("R")<=0 and - g.mid(1,1).compare("A")>=0 and - g.mid(1,1).compare("R")<=0 and - g.mid(2,1).compare("0")>=0 and - g.mid(2,1).compare("9")<=0 and - g.mid(3,1).compare("0")>=0 and - g.mid(3,1).compare("9")<=0; + g.mid(0,1).compare("R")<=0 and + g.mid(1,1).compare("A")>=0 and + g.mid(1,1).compare("R")<=0 and + g.mid(2,1).compare("0")>=0 and + g.mid(2,1).compare("9")<=0 and + g.mid(3,1).compare("0")>=0 and + g.mid(3,1).compare("9")<=0; return b; } -void MainWindow::on_actionConvert_JT9_x_to_RTTY_triggered(bool checked) +void MainWindow::on_bandComboBox_activated (int index) { - m_toRTTY=checked; -} + auto frequencies = m_config.frequencies (); + auto frequency = frequencies->data (frequencies->index (index, 0)); -void MainWindow::on_actionLog_dB_reports_to_Comments_triggered(bool checked) -{ - m_dBtoComments=checked; -} - -void MainWindow::on_bandComboBox_activated(int index) -{ - int ret=0; - QString rt; - - // Upload any queued spots before changing band - psk_Reporter->sendReport(); - - m_band=index; - QString t=m_dFreq[index]; - m_dialFreq=t.toDouble(); - if(m_plus2kHz) m_dialFreq+=0.002; - dialFreqChanged2(m_dialFreq); - m_repeatMsg=0; - m_secBandChanged=QDateTime::currentMSecsSinceEpoch()/1000; - if(m_catEnabled) { - if(!m_bRigOpen) { - rigOpen(); + // Lookup band + auto bands = m_config.bands (); + auto band_index = bands->find (frequency); + if (band_index.isValid ()) + { + ui->bandComboBox->lineEdit ()->setStyleSheet ({}); + ui->bandComboBox->setCurrentText (band_index.data ().toString ()); } - if(m_bRigOpen) { - m_dontReadFreq=true; - ret=rig->setFreq(MHz(m_dialFreq)); - if(m_bSplit or m_bXIT) setXIT(m_txFreq); - - bumpFqso(11); - bumpFqso(12); - - if(ret!=RIG_OK) { - rt.sprintf("Set rig frequency failed: %d",ret); - msgBox(rt); - } + else + { + ui->bandComboBox->lineEdit ()->setStyleSheet ("QLineEdit {color: yellow; background-color : red;}"); + ui->bandComboBox->setCurrentText (bands->data (QModelIndex {}).toString ()); } - } - QFile f2("ALL.TXT"); - f2.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append); - QTextStream out(&f2); - out << QDateTime::currentDateTimeUtc().toString("yyyy-MMM-dd hh:mm") - << " " << m_dialFreq << " MHz " << m_mode << endl; - f2.close(); - psk_Reporter->setLocalStation(m_myCall, m_myGrid, m_antDescription[m_band], "WSJT-X r" + rev.mid(6,4) ); + + auto f = frequency.value (); + if (m_plus2kHz) + { + f += 2000; + } + + m_bandEdited = true; + band_changed (f); } -void MainWindow::on_actionPrompt_to_log_QSO_triggered(bool checked) +void MainWindow::band_changed (Frequency f) { - m_promptToLog=checked; + if (m_bandEdited) + { + m_bandEdited = false; + + // Upload any queued spots before changing band + psk_Reporter->sendReport(); + + if (!m_transmitting) + { + monitor (true); + } + + Q_EMIT m_config.transceiver_frequency (f); + qsy (f); + } } -void MainWindow::on_actionBlank_line_between_decoding_periods_triggered(bool checked) +void MainWindow::enable_DXCC_entity (bool on) { - m_insertBlank=checked; -} + if (on) + { + // re-read the log and cty.dat files + m_logBook.init(m_config.data_path ()); + } -void MainWindow::on_actionEnable_DXCC_entity_triggered(bool checked) -{ - m_displayDXCCEntity=checked; - if (checked) - m_logBook.init(); // re-read the log and cty.dat files - - if (checked) // adjust the proportions between the two text displays - { + if (on) // adjust the proportions between the two text displays + { ui->gridLayout->setColumnStretch(0,55); ui->gridLayout->setColumnStretch(1,45); - } + } else - { + { ui->gridLayout->setColumnStretch(0,0); ui->gridLayout->setColumnStretch(1,0); - } -} - -void MainWindow::on_actionClear_DX_Call_and_Grid_after_logging_triggered(bool checked) -{ - m_clearCallGrid=checked; -} - -void MainWindow::on_actionDisplay_distance_in_miles_triggered(bool checked) -{ - m_bMiles=checked; - on_dxGridEntry_textChanged(m_hisGrid); + } } void MainWindow::on_pbCallCQ_clicked() @@ -2872,7 +2612,7 @@ void MainWindow::on_pbSendReport_clicked() void MainWindow::on_pbSend73_clicked() { genStdMsgs(m_rpt); - ui->genMsg->setText(ui->tx5->text()); + ui->genMsg->setText(ui->tx5->currentText()); m_ntx=7; ui->rbGenMsg->setChecked(true); if(m_transmitting) m_restart=true; @@ -2896,29 +2636,19 @@ void MainWindow::on_rbFreeText_toggled(bool checked) } } -void MainWindow::on_freeTextMsg_editingFinished() +void MainWindow::on_freeTextMsg_currentTextChanged (QString const& text) { - QString t=ui->freeTextMsg->text(); - msgtype(t, ui->freeTextMsg); -} - -void MainWindow::on_actionDouble_click_on_call_sets_Tx_Enable_triggered(bool checked) -{ - m_quickCall=checked; - if(checked) { - lab3->setText("Tx-Enable Armed"); - } else { - lab3->setText("Tx-Enable Disarmed"); - } + msgtype(text, ui->freeTextMsg->lineEdit ()); + msgtype(text, ui->tx5->lineEdit ()); } void MainWindow::on_rptSpinBox_valueChanged(int n) { m_rpt=QString::number(n); int ntx0=m_ntx; - QString t=ui->tx5->text(); + QString t=ui->tx5->currentText(); genStdMsgs(m_rpt); - ui->tx5->setText(t); + ui->tx5->setCurrentText(t); m_ntx=ntx0; if(m_ntx==1) ui->txrb1->setChecked(true); if(m_ntx==2) ui->txrb2->setChecked(true); @@ -2929,109 +2659,57 @@ void MainWindow::on_rptSpinBox_valueChanged(int n) statusChanged(); } -void MainWindow::on_action_73TxDisable_triggered(bool checked) +void MainWindow::on_tuneButton_clicked (bool checked) { - m_73TxDisable=checked; + if (m_tune) + { + nc1=1; //disable the countdown timer + tuneButtonTimer->start(250); + } + else + { + m_sent73=false; + m_repeatMsg=0; + } + m_tune = checked; + Q_EMIT tune (checked); } -void MainWindow::on_actionRunaway_Tx_watchdog_triggered(bool checked) +void MainWindow::tuning (bool state) { - m_runaway=checked; -} - -void MainWindow::on_tuneButton_clicked() -{ - if(m_tune) { - nc1=1; //disable the countdown timer - tuneButtonTimer->start(250); - } else { - m_tune=true; - m_sent73=false; - Q_EMIT tune (); - m_repeatMsg=0; - ui->tuneButton->setStyleSheet(m_pbTune_style); - } + ui->tuneButton->setChecked (state); + on_tuneButton_clicked (state); } void MainWindow::on_stopTxButton_clicked() //Stop Tx { - if(m_tune) { - m_tune=false; - Q_EMIT tune (m_tune); - } - if(m_auto) on_autoButton_clicked(); + // if(m_tune) { + // m_tune=false; + // Q_EMIT tune (m_tune); + // } + if (m_tune) + { + tuning (false); + } + + if (m_auto) + { + auto_tx_mode (false); + } + m_btxok=false; Q_EMIT muteAudioOutput (); m_repeatMsg=0; - ui->tuneButton->setStyleSheet(""); } -void MainWindow::rigOpen() +void MainWindow::rigOpen () { - QString t; - int ret; - rig = new Rig(); - - if(m_rig<9900) { - if (!rig->init(m_rig)) { - msgBox("Rig init failure"); - return; - } - QString sCATport=m_catPort; -#ifdef WIN32 - sCATport="\\\\.\\" + m_catPort; //Allow COM ports above 9 -#endif - rig->setConf("rig_pathname", sCATport.toLatin1().data()); - char buf[80]; - sprintf(buf,"%d",m_serialRate); - rig->setConf("serial_speed",buf); - sprintf(buf,"%d",m_dataBits); - rig->setConf("data_bits",buf); - sprintf(buf,"%d",m_stopBits); - rig->setConf("stop_bits",buf); - rig->setConf("serial_handshake",m_handshake.toLatin1().data()); - if(m_handshakeIndex != 2) { - rig->setConf("rts_state",m_bRTS ? "ON" : "OFF"); - rig->setConf("dtr_state",m_bDTR ? "ON" : "OFF"); - } - } - - ret=rig->open(m_rig); - if(ret==RIG_OK) { - m_bRigOpen=true; - m_bad=0; - if(m_poll==0) ui->readFreq->setEnabled(true); - m_CATerror=false; - } else { - t="Open rig failed"; - msgBox(t); - m_catEnabled=false; - m_bRigOpen=false; - m_CATerror=true; - } - - if(m_bRigOpen) { - if(m_poll>0) { - ui->readFreq->setStyleSheet("QPushButton{background-color: #00ff00; \ - border-width: 0px; border-radius: 5px;}"); - } else { - ui->readFreq->setStyleSheet("QPushButton{background-color: orange; \ - border-width: 0px; border-radius: 5px;}"); - } - - if(m_bSplit) ui->readFreq->setText("S"); - if(!m_bSplit) ui->readFreq->setText(""); - } else { - if(m_CATerror) ui->readFreq->setStyleSheet("QPushButton{background-color: red; \ - border-width: 0px; border-radius: 5px;}"); - if(!m_CATerror) ui->readFreq->setStyleSheet(""); - ui->readFreq->setText(""); - } -} - -void MainWindow::on_actionAllow_multiple_instances_triggered(bool checked) -{ - m_bMultipleOK=checked; + ui->readFreq->setStyleSheet (""); + ui->readFreq->setText (""); + m_config.transceiver_online (true); + Q_EMIT m_config.sync_transceiver (true); + ui->readFreq->setStyleSheet("QPushButton{background-color: orange;" + "border-width: 0px; border-radius: 5px;}"); } void MainWindow::on_pbR2T_clicked() @@ -3048,19 +2726,12 @@ void MainWindow::on_pbT2R_clicked() void MainWindow::on_readFreq_clicked() { - if(m_transmitting) return; - m_dontReadFreq=false; - double fMHz=rig->getFreq(RIG_VFO_CURR)/1000000.0; - if(fMHz<0.0) { - QString rt; - rt.sprintf("Rig control error %d\nFailed to read frequency.", - int(1000000.0*fMHz)); - msgBox(rt); - m_catEnabled=false; - } - if(fMHz<0.01 or fMHz>1300.0) fMHz=0; - int ndiff=1000000.0*(fMHz-m_dialFreq); - if(ndiff!=0) dialFreqChanged2(fMHz); + if (m_transmitting) return; + + if (m_config.transceiver_online (true)) + { + Q_EMIT m_config.sync_transceiver (true); + } } void MainWindow::on_pbTxMode_clicked() @@ -3078,23 +2749,24 @@ void MainWindow::on_pbTxMode_clicked() void MainWindow::setXIT(int n) { - int ret; m_XIT = 0; - if(m_bRigOpen) { - m_XIT=(n/500)*500 - 1500; - if(m_bXIT) { - ret=rig->setXit((shortfreq_t)m_XIT,RIG_VFO_TX); - if(ret!=RIG_OK) { - QString rt; - rt.sprintf("Setting RIG_VFO_TX failed: %d",ret); - msgBox(rt); - } + if (m_config.split_mode ()) + { + m_XIT=(n/500)*500 - 1500; } - if(m_bSplit) { - ret=rig->setSplitFreq(MHz(m_dialFreq)+m_XIT,RIG_VFO_B); + + if (m_monitoring || m_transmitting) + { + if (m_config.transceiver_online ()) + { + if (m_config.split_mode ()) + { + Q_EMIT m_config.transceiver_tx_frequency (m_dialFreq + m_XIT); + } + } } - } - Q_EMIT transmitFrequency (m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0)); + + Q_EMIT transmitFrequency (m_txFreq - m_XIT); } void MainWindow::setFreq4(int rxFreq, int txFreq) @@ -3112,58 +2784,86 @@ void MainWindow::on_cbTxLock_clicked(bool checked) if(m_lockTxFreq) on_pbR2T_clicked(); } -void MainWindow::on_actionTx2QSO_triggered(bool checked) -{ - m_tx2QSO=checked; -} - void MainWindow::on_cbPlus2kHz_toggled(bool checked) { - m_plus2kHz=checked; - on_bandComboBox_activated(m_band); + // Upload any queued spots before changing band + psk_Reporter->sendReport(); + + m_plus2kHz = checked; + + auto f = m_dialFreq; + + if (m_plus2kHz) + { + f += 2000; + } + else + { + f -= 2000; + } + + m_bandEdited = true; + band_changed (f); } -void MainWindow::pollRigFreq() +void MainWindow::handle_transceiver_update (Transceiver::TransceiverState s) { - double fMHz; - if(m_dontReadFreq) { - m_dontReadFreq=false; - } else if(!m_transmitting) { - fMHz=rig->getFreq(RIG_VFO_CURR)/1000000.0; - if(fMHz<0.0) { - m_bad++; - if(m_bad>=20) { - QString rt; - rt.sprintf("Rig control error %d\nFailed to read frequency.", - int(1000000.0*fMHz)); - msgBox(rt); - m_catEnabled=false; - ui->readFreq->setStyleSheet("QPushButton{background-color: red; \ - border-width: 0px; border-radius: 5px;}"); - } - } else { - int ndiff=1000000.0*(fMHz-m_dialFreq); - if(ndiff!=0) dialFreqChanged2(fMHz); + if ((s.frequency () - m_dialFreq) || s.split () != m_splitMode) + { + m_splitMode = s.split (); + qsy (s.frequency ()); + } + + ui->readFreq->setStyleSheet("QPushButton{background-color: #00ff00;" + "border-width: 0px; border-radius: 5px;}"); + ui->readFreq->setText (s.split () ? "S" : ""); +} + +void MainWindow::handle_transceiver_failure (QString reason) +{ + ui->readFreq->setStyleSheet("QPushButton{background-color: red;" + "border-width: 0px; border-radius: 5px;}"); + + m_btxok=false; + Q_EMIT muteAudioOutput (); + m_repeatMsg=0; + + rigFailure ("Rig Control Error", reason); +} + +void MainWindow::rigFailure (QString const& reason, QString const& detail) +{ + m_rigErrorMessageBox.setText (reason); + m_rigErrorMessageBox.setDetailedText (detail); + + // don't call slot functions directly to avoid recursion + switch (m_rigErrorMessageBox.exec ()) + { + case QMessageBox::Ok: + QTimer::singleShot (0, this, SLOT (on_actionSettings_triggered ())); + break; + + case QMessageBox::Retry: + QTimer::singleShot (0, this, SLOT (rigOpen ())); + break; + + case QMessageBox::Cancel: + QTimer::singleShot (0, this, SLOT (close ())); + break; } - } } void MainWindow::transmit (double snr) { if (m_modeTx == "JT65") { - Q_EMIT sendMessage (NUM_JT65_SYMBOLS, 4096.0 * 12000.0 / 11025.0, - m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0), - m_audioOutputChannel, true, snr); + Q_EMIT sendMessage (NUM_JT65_SYMBOLS, 4096.0 * 12000.0 / 11025.0, m_txFreq - m_XIT, m_config.audio_output_channel (), true, snr); } else { - Q_EMIT sendMessage (NUM_JT9_SYMBOLS, m_nsps, - m_txFreq - (m_bSplit || m_bXIT ? m_XIT : 0), - m_audioOutputChannel, true, snr); + Q_EMIT sendMessage (NUM_JT9_SYMBOLS, m_nsps, m_txFreq - m_XIT, m_config.audio_output_channel (), true, snr); } - Q_EMIT startAudioOutputStream (m_audioOutputDevice, - AudioDevice::Mono == m_audioOutputChannel ? 1 : 2, m_msAudioOutputBuffered); + Q_EMIT startAudioOutputStream (m_config.audio_output_device (), AudioDevice::Mono == m_config.audio_output_channel () ? 1 : 2, m_msAudioOutputBuffered); } void MainWindow::on_outAttenuation_valueChanged (int a) @@ -3178,15 +2878,14 @@ void MainWindow::on_actionShort_list_of_add_on_prefixes_and_suffixes_triggered() pPrefixes = new QTextEdit(0); pPrefixes->setReadOnly(true); pPrefixes->setFontPointSize(10); - pPrefixes->setWindowTitle("Prefixes"); + pPrefixes->setWindowTitle(QApplication::applicationName () + " - " + tr ("Prefixes")); pPrefixes->setGeometry(QRect(45,50,565,450)); Qt::WindowFlags flags = Qt::WindowCloseButtonHint | - Qt::WindowMinimizeButtonHint; + Qt::WindowMinimizeButtonHint; pPrefixes->setWindowFlags(flags); - QString prefixes = m_appDir + "/prefixes.txt"; - QFile f(prefixes); + QFile f(":/prefixes.txt"); if(!f.open(QIODevice::ReadOnly | QIODevice::Text)) { - msgBox("Cannot open " + prefixes); + msgBox("Cannot open \"" + f.fileName () + "\"."); return; } QTextStream s(&f); @@ -3202,51 +2901,51 @@ void MainWindow::on_actionShort_list_of_add_on_prefixes_and_suffixes_triggered() void MainWindow::getpfx() { m_prefix <<"1A" <<"1S" <<"3A" <<"3B6" <<"3B8" <<"3B9" <<"3C" <<"3C0" \ - <<"3D2" <<"3D2C" <<"3D2R" <<"3DA" <<"3V" <<"3W" <<"3X" <<"3Y" \ - <<"3YB" <<"3YP" <<"4J" <<"4L" <<"4S" <<"4U1I" <<"4U1U" <<"4W" \ - <<"4X" <<"5A" <<"5B" <<"5H" <<"5N" <<"5R" <<"5T" <<"5U" \ - <<"5V" <<"5W" <<"5X" <<"5Z" <<"6W" <<"6Y" <<"7O" <<"7P" \ - <<"7Q" <<"7X" <<"8P" <<"8Q" <<"8R" <<"9A" <<"9G" <<"9H" \ - <<"9J" <<"9K" <<"9L" <<"9M2" <<"9M6" <<"9N" <<"9Q" <<"9U" \ - <<"9V" <<"9X" <<"9Y" <<"A2" <<"A3" <<"A4" <<"A5" <<"A6" \ - <<"A7" <<"A9" <<"AP" <<"BS7" <<"BV" <<"BV9" <<"BY" <<"C2" \ - <<"C3" <<"C5" <<"C6" <<"C9" <<"CE" <<"CE0X" <<"CE0Y" <<"CE0Z" \ - <<"CE9" <<"CM" <<"CN" <<"CP" <<"CT" <<"CT3" <<"CU" <<"CX" \ - <<"CY0" <<"CY9" <<"D2" <<"D4" <<"D6" <<"DL" <<"DU" <<"E3" \ - <<"E4" <<"EA" <<"EA6" <<"EA8" <<"EA9" <<"EI" <<"EK" <<"EL" \ - <<"EP" <<"ER" <<"ES" <<"ET" <<"EU" <<"EX" <<"EY" <<"EZ" \ - <<"F" <<"FG" <<"FH" <<"FJ" <<"FK" <<"FKC" <<"FM" <<"FO" \ - <<"FOA" <<"FOC" <<"FOM" <<"FP" <<"FR" <<"FRG" <<"FRJ" <<"FRT" \ - <<"FT5W" <<"FT5X" <<"FT5Z" <<"FW" <<"FY" <<"M" <<"MD" <<"MI" \ - <<"MJ" <<"MM" <<"MU" <<"MW" <<"H4" <<"H40" <<"HA" \ - <<"HB" <<"HB0" <<"HC" <<"HC8" <<"HH" <<"HI" <<"HK" <<"HK0A" \ - <<"HK0M" <<"HL" <<"HM" <<"HP" <<"HR" <<"HS" <<"HV" <<"HZ" \ - <<"I" <<"IS" <<"IS0" <<"J2" <<"J3" <<"J5" <<"J6" \ - <<"J7" <<"J8" <<"JA" <<"JDM" <<"JDO" <<"JT" <<"JW" \ - <<"JX" <<"JY" <<"K" <<"KG4" <<"KH0" <<"KH1" <<"KH2" <<"KH3" \ - <<"KH4" <<"KH5" <<"KH5K" <<"KH6" <<"KH7" <<"KH8" <<"KH9" <<"KL" \ - <<"KP1" <<"KP2" <<"KP4" <<"KP5" <<"LA" <<"LU" <<"LX" <<"LY" \ - <<"LZ" <<"OA" <<"OD" <<"OE" <<"OH" <<"OH0" <<"OJ0" <<"OK" \ - <<"OM" <<"ON" <<"OX" <<"OY" <<"OZ" <<"P2" <<"P4" <<"PA" \ - <<"PJ2" <<"PJ7" <<"PY" <<"PY0F" <<"PT0S" <<"PY0T" <<"PZ" <<"R1F" \ - <<"R1M" <<"S0" <<"S2" <<"S5" <<"S7" <<"S9" <<"SM" <<"SP" \ - <<"ST" <<"SU" <<"SV" <<"SVA" <<"SV5" <<"SV9" <<"T2" <<"T30" \ - <<"T31" <<"T32" <<"T33" <<"T5" <<"T7" <<"T8" <<"T9" <<"TA" \ - <<"TF" <<"TG" <<"TI" <<"TI9" <<"TJ" <<"TK" <<"TL" \ - <<"TN" <<"TR" <<"TT" <<"TU" <<"TY" <<"TZ" <<"UA" <<"UA2" \ - <<"UA9" <<"UK" <<"UN" <<"UR" <<"V2" <<"V3" <<"V4" <<"V5" \ - <<"V6" <<"V7" <<"V8" <<"VE" <<"VK" <<"VK0H" <<"VK0M" <<"VK9C" \ - <<"VK9L" <<"VK9M" <<"VK9N" <<"VK9W" <<"VK9X" <<"VP2E" <<"VP2M" <<"VP2V" \ - <<"VP5" <<"VP6" <<"VP6D" <<"VP8" <<"VP8G" <<"VP8H" <<"VP8O" <<"VP8S" \ - <<"VP9" <<"VQ9" <<"VR" <<"VU" <<"VU4" <<"VU7" <<"XE" <<"XF4" \ - <<"XT" <<"XU" <<"XW" <<"XX9" <<"XZ" <<"YA" <<"YB" <<"YI" \ - <<"YJ" <<"YK" <<"YL" <<"YN" <<"YO" <<"YS" <<"YU" <<"YV" \ - <<"YV0" <<"Z2" <<"Z3" <<"ZA" <<"ZB" <<"ZC4" <<"ZD7" <<"ZD8" \ - <<"ZD9" <<"ZF" <<"ZK1N" <<"ZK1S" <<"ZK2" <<"ZK3" <<"ZL" <<"ZL7" \ - <<"ZL8" <<"ZL9" <<"ZP" <<"ZS" <<"ZS8" <<"KC4" <<"E5"; + <<"3D2" <<"3D2C" <<"3D2R" <<"3DA" <<"3V" <<"3W" <<"3X" <<"3Y" \ + <<"3YB" <<"3YP" <<"4J" <<"4L" <<"4S" <<"4U1I" <<"4U1U" <<"4W" \ + <<"4X" <<"5A" <<"5B" <<"5H" <<"5N" <<"5R" <<"5T" <<"5U" \ + <<"5V" <<"5W" <<"5X" <<"5Z" <<"6W" <<"6Y" <<"7O" <<"7P" \ + <<"7Q" <<"7X" <<"8P" <<"8Q" <<"8R" <<"9A" <<"9G" <<"9H" \ + <<"9J" <<"9K" <<"9L" <<"9M2" <<"9M6" <<"9N" <<"9Q" <<"9U" \ + <<"9V" <<"9X" <<"9Y" <<"A2" <<"A3" <<"A4" <<"A5" <<"A6" \ + <<"A7" <<"A9" <<"AP" <<"BS7" <<"BV" <<"BV9" <<"BY" <<"C2" \ + <<"C3" <<"C5" <<"C6" <<"C9" <<"CE" <<"CE0X" <<"CE0Y" <<"CE0Z" \ + <<"CE9" <<"CM" <<"CN" <<"CP" <<"CT" <<"CT3" <<"CU" <<"CX" \ + <<"CY0" <<"CY9" <<"D2" <<"D4" <<"D6" <<"DL" <<"DU" <<"E3" \ + <<"E4" <<"EA" <<"EA6" <<"EA8" <<"EA9" <<"EI" <<"EK" <<"EL" \ + <<"EP" <<"ER" <<"ES" <<"ET" <<"EU" <<"EX" <<"EY" <<"EZ" \ + <<"F" <<"FG" <<"FH" <<"FJ" <<"FK" <<"FKC" <<"FM" <<"FO" \ + <<"FOA" <<"FOC" <<"FOM" <<"FP" <<"FR" <<"FRG" <<"FRJ" <<"FRT" \ + <<"FT5W" <<"FT5X" <<"FT5Z" <<"FW" <<"FY" <<"M" <<"MD" <<"MI" \ + <<"MJ" <<"MM" <<"MU" <<"MW" <<"H4" <<"H40" <<"HA" \ + <<"HB" <<"HB0" <<"HC" <<"HC8" <<"HH" <<"HI" <<"HK" <<"HK0A" \ + <<"HK0M" <<"HL" <<"HM" <<"HP" <<"HR" <<"HS" <<"HV" <<"HZ" \ + <<"I" <<"IS" <<"IS0" <<"J2" <<"J3" <<"J5" <<"J6" \ + <<"J7" <<"J8" <<"JA" <<"JDM" <<"JDO" <<"JT" <<"JW" \ + <<"JX" <<"JY" <<"K" <<"KG4" <<"KH0" <<"KH1" <<"KH2" <<"KH3" \ + <<"KH4" <<"KH5" <<"KH5K" <<"KH6" <<"KH7" <<"KH8" <<"KH9" <<"KL" \ + <<"KP1" <<"KP2" <<"KP4" <<"KP5" <<"LA" <<"LU" <<"LX" <<"LY" \ + <<"LZ" <<"OA" <<"OD" <<"OE" <<"OH" <<"OH0" <<"OJ0" <<"OK" \ + <<"OM" <<"ON" <<"OX" <<"OY" <<"OZ" <<"P2" <<"P4" <<"PA" \ + <<"PJ2" <<"PJ7" <<"PY" <<"PY0F" <<"PT0S" <<"PY0T" <<"PZ" <<"R1F" \ + <<"R1M" <<"S0" <<"S2" <<"S5" <<"S7" <<"S9" <<"SM" <<"SP" \ + <<"ST" <<"SU" <<"SV" <<"SVA" <<"SV5" <<"SV9" <<"T2" <<"T30" \ + <<"T31" <<"T32" <<"T33" <<"T5" <<"T7" <<"T8" <<"T9" <<"TA" \ + <<"TF" <<"TG" <<"TI" <<"TI9" <<"TJ" <<"TK" <<"TL" \ + <<"TN" <<"TR" <<"TT" <<"TU" <<"TY" <<"TZ" <<"UA" <<"UA2" \ + <<"UA9" <<"UK" <<"UN" <<"UR" <<"V2" <<"V3" <<"V4" <<"V5" \ + <<"V6" <<"V7" <<"V8" <<"VE" <<"VK" <<"VK0H" <<"VK0M" <<"VK9C" \ + <<"VK9L" <<"VK9M" <<"VK9N" <<"VK9W" <<"VK9X" <<"VP2E" <<"VP2M" <<"VP2V" \ + <<"VP5" <<"VP6" <<"VP6D" <<"VP8" <<"VP8G" <<"VP8H" <<"VP8O" <<"VP8S" \ + <<"VP9" <<"VQ9" <<"VR" <<"VU" <<"VU4" <<"VU7" <<"XE" <<"XF4" \ + <<"XT" <<"XU" <<"XW" <<"XX9" <<"XZ" <<"YA" <<"YB" <<"YI" \ + <<"YJ" <<"YK" <<"YL" <<"YN" <<"YO" <<"YS" <<"YU" <<"YV" \ + <<"YV0" <<"Z2" <<"Z3" <<"ZA" <<"ZB" <<"ZC4" <<"ZD7" <<"ZD8" \ + <<"ZD9" <<"ZF" <<"ZK1N" <<"ZK1S" <<"ZK2" <<"ZK3" <<"ZL" <<"ZL7" \ + <<"ZL8" <<"ZL9" <<"ZP" <<"ZS" <<"ZS8" <<"KC4" <<"E5"; m_suffix << "P" << "0" << "1" << "2" << "3" << "4" << "5" << "6" \ - << "7" << "8" << "9" << "A"; + << "7" << "8" << "9" << "A"; for(int i=0; i<12; i++) { m_sfx.insert(m_suffix[i],true); @@ -3266,3 +2965,23 @@ bool MainWindow::shortList(QString callsign) bool b=(m_pfx.contains(t1) or m_sfx.contains(t2)); return b; } + +void MainWindow::pskSetLocal () +{ + // find the station row, if any, that matches the band we are on + auto stations = m_config.stations (); + auto matches = stations->match (stations->index (0, 0) + , Qt::DisplayRole + , ui->bandComboBox->currentText () + , 1 + , Qt::MatchExactly); + QString antenna_description; + if (!matches.isEmpty ()) + { + antenna_description = stations->index (matches.first ().row (), 2).data ().toString (); + } + psk_Reporter->setLocalStation( + m_config.my_callsign () + , m_config.my_grid () + , antenna_description, "WSJT-X r" + rev.mid(6,4)); +} diff --git a/mainwindow.h b/mainwindow.h index 71bfd690c..e9d1801ca 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -15,8 +15,10 @@ #include "soundin.h" #include "soundout.h" #include "commons.h" +#include "Radio.hpp" +#include "Configuration.hpp" +#include "Transceiver.hpp" #include "psk_reporter.h" -#include "rigclass.h" #include "signalmeter.h" #include "logbook/logbook.h" #include "Detector.hpp" @@ -39,18 +41,22 @@ namespace Ui { } class QSettings; +class QLineEdit; class WideGraph; class LogQSO; +class Transceiver; +class Astro; class MainWindow : public QMainWindow { - Q_OBJECT + Q_OBJECT; -// Multiple instances: call MainWindow() with *thekey public: - explicit MainWindow(QSettings *, QSharedMemory *shdmem, QString *thekey, - qint32 fontSize2, qint32 fontWeight2, unsigned downSampleFactor, - QWidget *parent = 0); + using Frequency = Radio::Frequency; + + // Multiple instances: call MainWindow() with *thekey + explicit MainWindow(bool multiple, QSettings *, QSharedMemory *shdmem, QString const& thekey, + unsigned downSampleFactor, QWidget *parent = 0); ~MainWindow(); public slots: @@ -80,14 +86,12 @@ private slots: void on_tx2_editingFinished(); void on_tx3_editingFinished(); void on_tx4_editingFinished(); - void on_tx5_editingFinished(); + void on_tx5_currentTextChanged (QString const&); void on_tx6_editingFinished(); - void on_actionDeviceSetup_triggered(); - void on_monitorButton_clicked(); - void on_actionExit_triggered(); + void on_actionSettings_triggered(); + void on_monitorButton_clicked (bool); void on_actionAbout_triggered(); - void OnExit(); - void on_autoButton_clicked(); + void on_autoButton_clicked (bool); void on_stopTxButton_clicked(); void on_stopButton_clicked(); void on_actionOnline_Users_Guide_triggered(); @@ -100,7 +104,7 @@ private slots: void on_actionSave_all_triggered(); void on_actionKeyboard_shortcuts_triggered(); void on_actionSpecial_mouse_commands_triggered(); - void on_DecodeButton_clicked(); + void on_DecodeButton_clicked (bool); void decode(); void decodeBusy(bool b); void on_EraseButton_clicked(); @@ -128,30 +132,11 @@ private slots: void on_actionDeepestDecode_triggered(); void on_inGain_valueChanged(int n); void bumpFqso(int n); - void on_actionMonitor_OFF_at_startup_triggered(); void on_actionErase_ALL_TXT_triggered(); void on_actionErase_wsjtx_log_adi_triggered(); - void showMacros(const QPoint& pos); - void onPopup1(); - void onPopup2(); - void onPopup3(); - void onPopup4(); - void onPopup5(); - void onPopup6(); - void onPopup7(); - void onPopup8(); - void onPopup9(); - void onPopup10(); - void on_actionConvert_JT9_x_to_RTTY_triggered(bool checked); - void on_actionLog_dB_reports_to_Comments_triggered(bool checked); void startTx2(); void stopTx(); void stopTx2(); - void on_actionPrompt_to_log_QSO_triggered(bool checked); - void on_actionBlank_line_between_decoding_periods_triggered(bool checked); - void on_actionEnable_DXCC_entity_triggered(bool checked); - void on_actionClear_DX_Call_and_Grid_after_logging_triggered(bool checked); - void on_actionDisplay_distance_in_miles_triggered(bool checked); void on_pbCallCQ_clicked(); void on_pbAnswerCaller_clicked(); void on_pbSendRRR_clicked(); @@ -160,72 +145,75 @@ private slots: void on_pbSend73_clicked(); void on_rbGenMsg_toggled(bool checked); void on_rbFreeText_toggled(bool checked); - void on_freeTextMsg_editingFinished(); - void on_actionDouble_click_on_call_sets_Tx_Enable_triggered(bool checked); + void on_freeTextMsg_currentTextChanged (QString const&); void on_rptSpinBox_valueChanged(int n); - void on_action_73TxDisable_triggered(bool checked); - void on_actionRunaway_Tx_watchdog_triggered(bool checked); void killFile(); - void on_tuneButton_clicked(); - void on_actionAllow_multiple_instances_triggered(bool checked); + void on_tuneButton_clicked (bool); void on_pbR2T_clicked(); void on_pbT2R_clicked(); void acceptQSO2(bool accepted); - void on_bandComboBox_activated(int index); + void on_bandComboBox_activated (int index); void on_readFreq_clicked(); void on_pbTxMode_clicked(); void on_RxFreqSpinBox_valueChanged(int n); void on_cbTxLock_clicked(bool checked); - void on_actionTx2QSO_triggered(bool checked); void on_cbPlus2kHz_toggled(bool checked); void on_outAttenuation_valueChanged (int); + void rigOpen (); + void handle_transceiver_update (Transceiver::TransceiverState); + void handle_transceiver_failure (QString reason); void on_actionAstronomical_data_triggered(); void on_actionShort_list_of_add_on_prefixes_and_suffixes_triggered(); void getpfx(); void on_actionJT9W_1_triggered(); -private: - Q_SIGNAL void startAudioOutputStream (QAudioDeviceInfo, unsigned channels, unsigned msBuffered); - Q_SIGNAL void stopAudioOutputStream (); - - Q_SIGNAL void startAudioInputStream (QAudioDeviceInfo const&, unsigned channels, int framesPerBuffer, QIODevice * sink, unsigned downSampleFactor); - Q_SIGNAL void stopAudioInputStream (); - - Q_SIGNAL void startDetector (AudioDevice::Channel); - Q_SIGNAL void detectorSetMonitoring (bool); - Q_SIGNAL void detectorClose (); - - Q_SIGNAL void finished (); - Q_SIGNAL void muteAudioOutput (bool = true); - Q_SIGNAL void transmitFrequency (unsigned); - Q_SIGNAL void endTransmitMessage (); - Q_SIGNAL void tune (bool = true); - Q_SIGNAL void sendMessage (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, AudioDevice::Channel, bool synchronize = true, double dBSNR = 99.); - Q_SIGNAL void outAttenuationChanged (qreal); + void band_changed (Frequency); + void monitor (bool); + void tuning (bool); + void auto_tx_mode (bool); private: + void enable_DXCC_entity (bool on); + + Q_SIGNAL void startAudioOutputStream (QAudioDeviceInfo, unsigned channels, unsigned msBuffered) const; + Q_SIGNAL void stopAudioOutputStream () const; + + Q_SIGNAL void startAudioInputStream (QAudioDeviceInfo const&, unsigned channels, int framesPerBuffer, QIODevice * sink, unsigned downSampleFactor) const; + Q_SIGNAL void stopAudioInputStream () const; + + Q_SIGNAL void startDetector (AudioDevice::Channel) const; + Q_SIGNAL void detectorSetMonitoring (bool) const; + Q_SIGNAL void detectorClose () const; + + Q_SIGNAL void finished () const; + Q_SIGNAL void muteAudioOutput (bool = true) const; + Q_SIGNAL void transmitFrequency (unsigned) const; + Q_SIGNAL void endTransmitMessage () const; + Q_SIGNAL void tune (bool = true) const; + Q_SIGNAL void sendMessage (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, AudioDevice::Channel, bool synchronize = true, double dBSNR = 99.) const; + Q_SIGNAL void outAttenuationChanged (qreal) const; + +private: + bool m_multiple; QSettings * m_settings; - Ui::MainWindow *ui; - // other windows - QScopedPointer m_wideGraph; - QScopedPointer m_logDlg; + QScopedPointer ui; - double m_dialFreq; - double m_toneSpacing; - double m_fSpread; + // other windows + Configuration m_config; + QMessageBox m_rigErrorMessageBox; - float m_DTmin; - float m_DTmax; + QScopedPointer m_wideGraph; + QScopedPointer m_logDlg; + QScopedPointer m_astroWidget; + + Frequency m_dialFreq; qint64 m_msErase; qint64 m_secBandChanged; - qint32 m_idInt; qint32 m_waterfallAvg; - qint32 m_pttMethodIndex; qint32 m_ntx; - qint32 m_pttPort; qint32 m_timeout; qint32 m_rxFreq; qint32 m_txFreq; @@ -239,15 +227,11 @@ private: qint32 m_hsym; Detector m_detector; - QAudioDeviceInfo m_audioInputDevice; - AudioDevice::Channel m_audioInputChannel; SoundInput m_soundInput; Modulator m_modulator; - QAudioDeviceInfo m_audioOutputDevice; - AudioDevice::Channel m_audioOutputChannel; SoundOutput m_soundOutput; - QThread m_audioThread; + QThread * m_audioThread; qint32 m_TRperiod; qint32 m_nsps; @@ -255,32 +239,18 @@ private: qint32 m_len1; qint32 m_inGain; qint32 m_nsave; - qint32 m_catPortIndex; - qint32 m_rig; - qint32 m_rigIndex; - qint32 m_serialRate; - qint32 m_serialRateIndex; - qint32 m_dataBits; - qint32 m_dataBitsIndex; - qint32 m_stopBits; - qint32 m_stopBitsIndex; - qint32 m_handshakeIndex; qint32 m_ncw; qint32 m_secID; - qint32 m_band; + QString m_band; qint32 m_repeatMsg; qint32 m_watchdogLimit; - qint32 m_poll; qint32 m_fMax; - qint32 m_bad; qint32 m_EMEbandIndex; qint32 m_toneMultIndex; qint32 m_astroFont; - bool m_monitoring; bool m_btxok; //True if OK to transmit bool m_btxMute; //True if transmit should be muted - bool m_transmitting; bool m_diskData; bool m_loopall; bool m_decoderBusy; @@ -296,13 +266,10 @@ private: bool m_killAll; bool m_bdecoded; bool m_monitorStartOFF; - bool m_pskReporter; bool m_pskReporterInit; bool m_noSuffix; bool m_toRTTY; bool m_dBtoComments; - bool m_catEnabled; - bool m_After73; bool m_promptToLog; bool m_blankLine; bool m_insertBlank; @@ -315,31 +282,20 @@ private: bool m_73TxDisable; bool m_sent73; bool m_runaway; - bool m_tune; - bool m_bRigOpen; bool m_bMultipleOK; - bool m_bDTR; - bool m_bRTS; - bool m_pttData; - bool m_dontReadFreq; bool m_lockTxFreq; bool m_tx2QSO; bool m_CATerror; - bool m_bSplit; - bool m_bXIT; bool m_plus2kHz; bool m_bAstroData; - char m_decoded[80]; - float m_pctZap; - QLabel* lab1; // labels in status bar - QLabel* lab2; - QLabel* lab3; - QLabel* lab4; - QLabel* lab5; - QLabel* lab6; + // labels in status bar + QLabel * tx_status_label; + QLabel * mode_label; + QLabel * last_tx_label; + QLabel * auto_tx_label; QMessageBox msgBox0; @@ -364,13 +320,10 @@ private: QString m_pbmonitor_style; QString m_pbAutoOn_style; QString m_pbTune_style; - QString m_myCall; - QString m_myGrid; QString m_baseCall; QString m_hisCall; QString m_hisGrid; QString m_appDir; - QString m_saveDir; QString m_dxccPfx; QString m_palette; QString m_dateTime; @@ -382,17 +335,10 @@ private: QString m_rptRcvd; QString m_qsoStart; QString m_qsoStop; - QString m_catPort; - QString m_handshake; QString m_cmnd; QString m_msgSent0; QString m_fileToSave; - QString m_azelDir; - QStringList m_macro; - QStringList m_dFreq; // per band frequency in MHz as a string - QStringList m_antDescription; // per band antenna description - QStringList m_bandDescription; // per band description QStringList m_prefix; QStringList m_suffix; @@ -404,7 +350,7 @@ private: QSharedMemory *mem_jt9; // Multiple instances: - QString *mykey_jt9; + QString mykey_jt9; PSK_Reporter *psk_Reporter; SignalMeter *signalMeter; LogBook m_logBook; @@ -413,10 +359,17 @@ private: unsigned m_framesAudioInputBuffered; unsigned m_downSampleFactor; QThread::Priority m_audioThreadPriority; - + bool m_bandEdited; + bool m_splitMode; + bool m_monitoring; + bool m_transmitting; + bool m_tune; + Frequency m_lastMonitoredFrequency; + double m_toneSpacing; //---------------------------------------------------- private functions void readSettings(); + void setDecodedTextFont (QFont const&); void writeSettings(); void createStatusBar(); void updateStatusBar(); @@ -427,14 +380,14 @@ private: void msgtype(QString t, QLineEdit* tx); void stub(); void statusChanged(); - void dialFreqChanged2(double f); - void freeText(); - void rigOpen(); - void pollRigFreq(); + void qsy(Frequency f); bool gridOK(QString g); bool shortList(QString callsign); QString baseCall(QString t); void transmit (double snr = 99.); + void rigFailure (QString const& reason, QString const& detail); + void pskSetLocal (); + void displayDialFrequency (); }; extern void getfile(QString fname, int ntrperiod); diff --git a/mainwindow.ui b/mainwindow.ui index 41288c6f9..49f3e567a 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -178,6 +178,9 @@ p, li { white-space: pre-wrap; } UTC dB DT Freq Dr + + Qt::PlainText + 5 @@ -267,6 +270,9 @@ p, li { white-space: pre-wrap; } UTC dB DT Freq Dr + + Qt::PlainText + 5 @@ -431,9 +437,26 @@ p, li { white-space: pre-wrap; } Start monitoring + + QPushButton:checked { + background-color: #00ff00; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + &Monitor + + true + + + false + @@ -463,9 +486,23 @@ p, li { white-space: pre-wrap; } <html><head/><body><p>Decode most recent Rx period at QSO Frequency</p></body></html> + + QPushButton:checked { + background-color: cyan; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + &Decode + + true + @@ -485,9 +522,23 @@ p, li { white-space: pre-wrap; } Toggle Tx Enable On/Off + + QPushButton:checked { + background-color: red; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + E&nable Tx + + true + @@ -517,9 +568,23 @@ p, li { white-space: pre-wrap; } <html><head/><body><p>Transmit a pure tone</p></body></html> + + QPushButton:checked { + background-color: red; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + &Tune + + true + @@ -1442,31 +1507,6 @@ p, li { white-space: pre-wrap; } - - - - - 200 - 0 - - - - - 150 - 24 - - - - - 150 - 16777215 - - - - Right-click to select a macro message - - - @@ -1718,6 +1758,34 @@ p, li { white-space: pre-wrap; } + + + + + 0 + 200 + + + + + 150 + 24 + + + + + 150 + 16777215 + + + + true + + + QComboBox::NoInsert + + + @@ -1915,19 +1983,19 @@ p, li { white-space: pre-wrap; } - - - - 0 - 0 - - + 150 0 + + true + + + QComboBox::NoInsert + @@ -2016,91 +2084,14 @@ p, li { white-space: pre-wrap; } - Select operating band + Select operating band or frequency in MHz - - 7 + + true + + + QComboBox::NoInsert - - - 2200 m - - - - - 630 m - - - - - 160 m - - - - - 80 m - - - - - 60 m - - - - - 40 m - - - - - 30 m - - - - - 20 m - - - - - 17 m - - - - - 15 m - - - - - 12 m - - - - - 10 m - - - - - 6 m - - - - - 4 m - - - - - 2 m - - - - - Other - - @@ -2268,38 +2259,10 @@ p, li { white-space: pre-wrap; } + + - - - Setup - - - - true - - - Advanced - - - - - - - - - - - - - - - - - - - - View @@ -2344,7 +2307,6 @@ p, li { white-space: pre-wrap; } - @@ -2356,6 +2318,9 @@ p, li { white-space: pre-wrap; } Exit + + QAction::QuitRole + @@ -2385,6 +2350,9 @@ p, li { white-space: pre-wrap; } Open + + Ctrl+O + @@ -2672,6 +2640,14 @@ p, li { white-space: pre-wrap; } Short list of add-on prefixes and suffixes + + + Settings ... + + + F2 + + @@ -2703,7 +2679,6 @@ p, li { white-space: pre-wrap; } tx2 tx3 tx4 - tx5 tx6 txrb1 txrb2 @@ -2723,7 +2698,6 @@ p, li { white-space: pre-wrap; } pbAnswerCaller rbFreeText pbSendRRR - freeTextMsg pbAnswerCQ pbSendReport pbSend73 diff --git a/meterwidget.cpp b/meterwidget.cpp index b48aa65d0..e36097e96 100644 --- a/meterwidget.cpp +++ b/meterwidget.cpp @@ -3,6 +3,8 @@ #include "meterwidget.h" +#include "moc_meterwidget.cpp" + MeterWidget::MeterWidget(QWidget *parent) : QWidget(parent), m_signal(0) diff --git a/pimpl_h.hpp b/pimpl_h.hpp new file mode 100644 index 000000000..b2027615f --- /dev/null +++ b/pimpl_h.hpp @@ -0,0 +1,29 @@ +#ifndef PIMPL_H_HPP_ +#define PIMPL_H_HPP_ + +#include + +// +// opaque implementation type with perfect forwarding of constructor arguments +// +// see pimpl_impl.hpp for the out-of-line definitions of the members and lifetime management +// +// thanks to Herb Sutter (http://herbsutter.com/gotw/_101/) for the implementation +// +template +class pimpl +{ +private: + std::unique_ptr m_; + +public: + pimpl (); + template pimpl (Args&& ...); + ~pimpl (); + T * operator -> (); + T const * operator -> () const; + T& operator * (); + T const& operator * () const; +}; + +#endif diff --git a/pimpl_impl.hpp b/pimpl_impl.hpp new file mode 100644 index 000000000..3d01b7ebe --- /dev/null +++ b/pimpl_impl.hpp @@ -0,0 +1,28 @@ +#ifndef PIMPL_IMPL_HPP_ +#define PIMPL_IMPL_HPP_ + +#include + +template +pimpl::pimpl () : m_ {new T {}} {} + +template +template +pimpl::pimpl (Args&& ...args) : m_ {new T {std::forward (args)...} } {} + +template +pimpl::~pimpl () {} + +template +T * pimpl::operator -> () {return m_.get ();} + +template +T const * pimpl::operator -> () const {return m_.get ();} + +template +T& pimpl::operator * () {return *m_.get ();} + +template +T const& pimpl::operator * () const {return *m_.get ();} + +#endif diff --git a/plotter.cpp b/plotter.cpp index df9d4311d..69461cb8c 100644 --- a/plotter.cpp +++ b/plotter.cpp @@ -2,6 +2,8 @@ #include #include +#include "moc_plotter.cpp" + #define MAX_SCREENSIZE 2048 @@ -179,36 +181,38 @@ void CPlotter::DrawOverlay() //DrawOverlay() // int nHzDiv[11]={0,50,100,200,200,200,500,500,500,500,500}; float pixperdiv; - QRect rect; - QPainter painter(&m_OverlayPixmap); - painter.initFrom(this); - QLinearGradient gradient(0, 0, 0 ,m_h2); //fill background with gradient - gradient.setColorAt(1, Qt::black); - gradient.setColorAt(0, Qt::darkBlue); - painter.setBrush(gradient); - painter.drawRect(0, 0, m_w, m_h2); - painter.setBrush(Qt::SolidPattern); - double df = m_binsPerPixel*m_fftBinWidth; - pixperdiv = m_freqPerDiv/df; - y = m_h2 - m_h2/VERT_DIVS; - m_hdivs = w*df/m_freqPerDiv + 0.9999; - for( int i=1; i= 0 and x<=m_w) { - painter.setPen(QPen(Qt::white, 1,Qt::DotLine)); - painter.drawLine(x, 0, x , y); - painter.drawLine(x, m_h2-5, x , m_h2); - } - } + QPainter painter(&m_OverlayPixmap); + painter.initFrom(this); + QLinearGradient gradient(0, 0, 0 ,m_h2); //fill background with gradient + gradient.setColorAt(1, Qt::black); + gradient.setColorAt(0, Qt::darkBlue); + painter.setBrush(gradient); + painter.drawRect(0, 0, m_w, m_h2); + painter.setBrush(Qt::SolidPattern); - pixperdiv = (float)m_h2 / (float)VERT_DIVS; - painter.setPen(QPen(Qt::white, 1,Qt::DotLine)); - for( int i=1; i= 0 and x<=m_w) { + painter.setPen(QPen(Qt::white, 1,Qt::DotLine)); + painter.drawLine(x, 0, x , y); + painter.drawLine(x, m_h2-5, x , m_h2); + } + } + + pixperdiv = (float)m_h2 / (float)VERT_DIVS; + painter.setPen(QPen(Qt::white, 1,Qt::DotLine)); + for( int i=1; i +#include +#include +#include + +#define ENUM_QDATASTREAM_OPS_DECL(CLASS, ENUM) \ + QDataStream& operator << (QDataStream&, CLASS::ENUM); \ + QDataStream& operator >> (QDataStream&, CLASS::ENUM&); + +#define ENUM_QDATASTREAM_OPS_IMPL(CLASS, ENUM) \ + QDataStream& operator << (QDataStream& os, CLASS::ENUM 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 (me.keyToValue (buffer, &ok)); \ + delete [] buffer; \ + } \ + if (!ok) \ + { \ + v = static_cast (me.value (0)); \ + } \ + return is; \ + } + +#define ENUM_QDEBUG_OPS_DECL(CLASS, ENUM) \ + QDebug operator << (QDebug, CLASS::ENUM); + +#define ENUM_QDEBUG_OPS_IMPL(CLASS, ENUM) \ + QDebug operator << (QDebug d, CLASS::ENUM m) \ + { \ + auto const& mo = CLASS::staticMetaObject; \ + return d << mo.enumerator (mo.indexOfEnumerator (#ENUM)).valueToKey (m); \ + } + +#define ENUM_CONVERSION_OPS_DECL(CLASS, ENUM) \ + QString enum_to_qstring (CLASS::ENUM); + +#define ENUM_CONVERSION_OPS_IMPL(CLASS, ENUM) \ + QString enum_to_qstring (CLASS::ENUM m) \ + { \ + auto const& mo = CLASS::staticMetaObject; \ + return QString {mo.enumerator (mo.indexOfEnumerator (#ENUM)).valueToKey (m)}; \ + } + +#endif diff --git a/rigclass.cpp b/rigclass.cpp deleted file mode 100644 index ce19cce9e..000000000 --- a/rigclass.cpp +++ /dev/null @@ -1,335 +0,0 @@ -/** - * \file src/rigclass.cc - * \brief Ham Radio Control Libraries C++ interface - * \author Stephane Fillod - * \date 2001-2003 - * - * Hamlib C++ interface is a frontend implementing wrapper functions. - */ - -/** - * - * Hamlib C++ bindings - main file - * Copyright (c) 2001-2003 by Stephane Fillod - * - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include "rigclass.h" -#include -#include - -#define NUMTRIES 5 - -static int hamlibpp_freq_event(RIG *rig, vfo_t vfo, freq_t freq, rig_ptr_t arg); - -static int hamlibpp_freq_event(RIG *rig, vfo_t vfo, freq_t freq, rig_ptr_t arg) -{ - if (!rig || !rig->state.obj) - return -RIG_EINVAL; - -/* assert rig == ((Rig*)rig->state.obj).theRig */ - return ((Rig*)rig->state.obj)->FreqEvent(vfo, freq, arg); -} - -Rig::Rig() -{ - rig_set_debug_level( RIG_DEBUG_WARN); -} - -Rig::~Rig() { - theRig->state.obj = NULL; - rig_cleanup(theRig); - caps = NULL; -} - -int Rig::init(rig_model_t rig_model) -{ - int initOk; - - theRig = rig_init(rig_model); - if (!theRig) - { - initOk = false; - } - else - { - initOk = true; - caps = theRig->caps; - theRig->callbacks.freq_event = &hamlibpp_freq_event; - theRig->state.obj = (rig_ptr_t)this; - } - - return initOk; -} - -int Rig::open(int n) { - m_hrd=false; - m_cmndr=false; - if(n<9900) { - if(n==-99999) return -1; //Silence compiler warning - return rig_open(theRig); - } - -#ifdef WIN32 // Ham radio Deluxe or Commander (Windows only) - if(n==9999) { - m_hrd=true; - bool bConnect=false; - bConnect = HRDInterfaceConnect(L"localhost",7809); - if(bConnect) { - const wchar_t* context=HRDInterfaceSendMessage(L"Get Context"); - m_context="[" + QString::fromWCharArray (context,-1) + "] "; - HRDInterfaceFreeString(context); - return 0; - } else { - m_hrd=false; - return -1; - } - } - if(n==9998) { - if(commanderSocket->state()==QAbstractSocket::ConnectedState) { - commanderSocket->abort(); - } - - if(commanderSocket->state()==QAbstractSocket::UnconnectedState) { - commanderSocket->connectToHost(QHostAddress::LocalHost, 52002); - if(!commanderSocket->waitForConnected(1000)) { - return -1; - } - } - QString t; - t="CmdGetFreq"; - QByteArray ba = t.toLocal8Bit(); - const char* buf=ba.data(); - commanderSocket->write(buf); - commanderSocket->waitForReadyRead(1000); - QByteArray reply=commanderSocket->read(128); - if(reply.indexOf("close(); - return 0; - } else -#endif - { - return rig_close(theRig); - } -} - -int Rig::setConf(const char *name, const char *val) -{ - return rig_set_conf(theRig, tokenLookup(name), val); -} - -int Rig::setFreq(freq_t freq, vfo_t vfo) { -#ifdef WIN32 // Ham Radio Deluxe (only on Windows) - if(m_hrd) { - QString t; - int nhz=(int)freq; - t=m_context + "Set Frequency-Hz " + QString::number(nhz); - const wchar_t* cmnd = (const wchar_t*) t.utf16(); - const wchar_t* result=HRDInterfaceSendMessage(cmnd); - QString t2=QString::fromWCharArray (result,-1); - HRDInterfaceFreeString(result); - if(t2=="OK") { - return 0; - } else { - return -1; - } - } else if(m_cmndr) { - QString t; - double f=0.001*freq; - t.sprintf("CmdSetFreq%10.3f",f); - QLocale locale; - t.replace(".",locale.decimalPoint()); - QByteArray ba = t.toLocal8Bit(); - const char* buf=ba.data(); - commanderSocket->write(buf); - commanderSocket->waitForBytesWritten(1000); - return 0; - } else -#endif - { - return rig_set_freq(theRig, vfo, freq); - } -} - -int Rig::setXit(shortfreq_t xit, vfo_t vfo) -{ - return rig_set_xit(theRig, vfo, xit); -} - -int Rig::setVFO(vfo_t vfo) -{ - return rig_set_vfo(theRig, vfo); -} - -vfo_t Rig::getVFO() -{ - vfo_t vfo; - rig_get_vfo(theRig, &vfo); - return vfo; -} - -int Rig::setSplitFreq(freq_t tx_freq, vfo_t vfo) { -#ifdef WIN32 // Ham Radio Deluxe only on Windows - if(m_hrd) { - QString t; - int nhz=(int)tx_freq; - t=m_context + "Set Frequency-Hz " + QString::number(nhz); - const wchar_t* cmnd = (const wchar_t*) t.utf16(); - const wchar_t* result=HRDInterfaceSendMessage(cmnd); - QString t2=QString::fromWCharArray (result,-1); - HRDInterfaceFreeString(result); - if(t2=="OK") { - return 0; - } else { - return -1; - } - } else if(m_cmndr) { - QString t; - double f=0.001*tx_freq; - t.sprintf("CmdSetTxFreq%10.3f",f); - QLocale locale; - t.replace(".",locale.decimalPoint()); - QByteArray ba = t.toLocal8Bit(); - const char* buf=ba.data(); - commanderSocket->write(buf); - commanderSocket->waitForBytesWritten(1000); - return 0; - } else -#endif - { - return rig_set_split_freq(theRig, vfo, tx_freq); - } -} - -freq_t Rig::getFreq(vfo_t vfo) -{ - freq_t freq; -#ifdef WIN32 // Ham Radio Deluxe (only on Windows) - if(m_hrd) { - const wchar_t* cmnd = (const wchar_t*) (m_context+"Get Frequency").utf16(); - const wchar_t* freqString=HRDInterfaceSendMessage(cmnd); - QString t2=QString::fromWCharArray (freqString,-1); - HRDInterfaceFreeString(freqString); - freq=t2.toDouble(); - return freq; - } else if(m_cmndr) { - QString t; - t="CmdGetFreq"; - QByteArray ba = t.toLocal8Bit(); - const char* buf=ba.data(); - commanderSocket->write(buf); - commanderSocket->waitForReadyRead(1000); - QByteArray reply=commanderSocket->read(128); - QString t2(reply); - if(t2.indexOf(""); - t2=t2.mid(i1+1).replace(",",""); - freq=1000.0*t2.toDouble(); - return freq; - } else { - return -1.0; - } - } else -#endif - { - freq=-1.0; - for(int i=0; i0) t="CmdTX"; - QByteArray ba = t.toLocal8Bit(); - const char* buf=ba.data(); - commanderSocket->write(buf); - commanderSocket->waitForBytesWritten(1000); - return 0; - } else -#endif - { - return rig_set_ptt(theRig, vfo, ptt); - } -} - -ptt_t Rig::getPTT(vfo_t vfo) -{ - ptt_t ptt; - rig_get_ptt(theRig, vfo, &ptt); - return ptt; -} - -token_t Rig::tokenLookup(const char *name) -{ - return rig_token_lookup(theRig, name); -} diff --git a/rigclass.h b/rigclass.h deleted file mode 100644 index 77522129e..000000000 --- a/rigclass.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Hamlib C++ bindings - API header - * Copyright (c) 2001-2002 by Stephane Fillod - * - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#ifndef _RIGCLASS_H -#define _RIGCLASS_H 1 - -#include -#include -#include -#include - -extern QTcpSocket* commanderSocket; - -class BACKEND_IMPEXP Rig { -private: - RIG* theRig; // Global ref. to the rig - bool m_hrd; - bool m_cmndr; - QString m_context; - - -protected: -public: - Rig(); - virtual ~Rig(); - - const struct rig_caps *caps; - - // Initialize rig - int init(rig_model_t rig_model); - - // This method open the communication port to the rig - int open(int n); - - // This method close the communication port to the rig - int close(void); - - int setConf(const char *name, const char *val); - token_t tokenLookup(const char *name); - - int setFreq(freq_t freq, vfo_t vfo = RIG_VFO_CURR); - freq_t getFreq(vfo_t vfo = RIG_VFO_CURR); - int setMode(rmode_t, pbwidth_t width = RIG_PASSBAND_NORMAL, vfo_t vfo = RIG_VFO_CURR); - rmode_t getMode(pbwidth_t&, vfo_t vfo = RIG_VFO_CURR); - int setVFO(vfo_t); - vfo_t getVFO(); - int setXit(shortfreq_t xit, vfo_t vfo); - int setSplitFreq(freq_t tx_freq, vfo_t vfo = RIG_VFO_CURR); - int setPTT (ptt_t ptt, vfo_t vfo = RIG_VFO_CURR); - ptt_t getPTT (vfo_t vfo = RIG_VFO_CURR); - - // callbacks available in your derived object - virtual int FreqEvent(vfo_t, freq_t, rig_ptr_t) const { - return RIG_OK; - } - virtual int ModeEvent(vfo_t, rmode_t, pbwidth_t, rig_ptr_t) const { - return RIG_OK; - } - virtual int VFOEvent(vfo_t, rig_ptr_t) const { - return RIG_OK; - } - virtual int PTTEvent(vfo_t, ptt_t, rig_ptr_t) const { - return RIG_OK; - } - virtual int DCDEvent(vfo_t, dcd_t, rig_ptr_t) const { - return RIG_OK; - } -}; - -#ifdef WIN32 -extern "C" { - bool HRDInterfaceConnect(const wchar_t *host, const ushort); - void HRDInterfaceDisconnect(); - bool HRDInterfaceIsConnected(); - wchar_t* HRDInterfaceSendMessage(const wchar_t *msg); - void HRDInterfaceFreeString(const wchar_t *lstring); -} -#endif - -#endif // _RIGCLASS_H diff --git a/signalmeter.cpp b/signalmeter.cpp index 537b900f8..e21cb5a90 100644 --- a/signalmeter.cpp +++ b/signalmeter.cpp @@ -5,6 +5,8 @@ #include "signalmeter.h" +#include "moc_signalmeter.cpp" + SignalMeter::SignalMeter(QWidget *parent) : QWidget(parent) { diff --git a/soundin.cpp b/soundin.cpp index caf61d376..377242bf6 100644 --- a/soundin.cpp +++ b/soundin.cpp @@ -5,6 +5,8 @@ #include #include +#include "moc_soundin.cpp" + bool SoundInput::audioError () const { bool result (true); @@ -65,7 +67,7 @@ void SoundInput::start(QAudioDeviceInfo const& device, unsigned channels, int fr // return; // } - m_stream.reset (new QAudioInput (device, format, this)); + m_stream.reset (new QAudioInput {device, format}); if (audioError ()) { return; diff --git a/soundin.h b/soundin.h index 8f8c663d7..652075960 100644 --- a/soundin.h +++ b/soundin.h @@ -11,13 +11,11 @@ class QAudioInput; class QIODevice; // Gets audio data from sound sample source and passes it to a sink device -class SoundInput : public QObject +class SoundInput + : public QObject { Q_OBJECT; - private: - Q_DISABLE_COPY (SoundInput); - public: SoundInput (QObject * parent = 0) : QObject (parent) @@ -26,14 +24,14 @@ class SoundInput : public QObject ~SoundInput (); - private: - Q_SIGNAL void error (QString message) const; - Q_SIGNAL void status (QString message) const; - // sink must exist from the start call to any following stop () call Q_SLOT void start(QAudioDeviceInfo const&, unsigned channels, int framesPerBuffer, QIODevice * sink, unsigned downSampleFactor); Q_SLOT void stop(); + private: + Q_SIGNAL void error (QString message) const; + Q_SIGNAL void status (QString message) const; + // used internally Q_SLOT void handleStateChanged (QAudio::State) const; diff --git a/soundout.cpp b/soundout.cpp index 49e9c48c1..5a74ac405 100644 --- a/soundout.cpp +++ b/soundout.cpp @@ -6,6 +6,8 @@ #include #include +#include "moc_soundout.cpp" + #if defined (WIN32) # define MS_BUFFERED 1000u #else @@ -76,7 +78,7 @@ void SoundOutput::startStream (QAudioDeviceInfo const& device, \ Q_EMIT error (tr ("Requested output audio format is not supported on device.")); } - m_stream.reset (new QAudioOutput (device, format, this)); + m_stream.reset (new QAudioOutput (device, format)); audioError (); m_stream->setVolume (m_volume); m_stream->setNotifyInterval(100); diff --git a/soundout.h b/soundout.h index 2ce735444..811a8893c 100644 --- a/soundout.h +++ b/soundout.h @@ -10,16 +10,14 @@ class QAudioDeviceInfo; // An instance of this sends audio data to a specified soundcard. -class SoundOutput : public QObject +class SoundOutput + : public QObject { Q_OBJECT; Q_PROPERTY(bool running READ isRunning); Q_PROPERTY(unsigned attenuation READ attenuation WRITE setAttenuation RESET resetAttenuation); - private: - Q_DISABLE_COPY (SoundOutput); - public: SoundOutput (QIODevice * source); ~SoundOutput (); diff --git a/widegraph.cpp b/widegraph.cpp index e3693fe9a..e79bd1895 100644 --- a/widegraph.cpp +++ b/widegraph.cpp @@ -1,17 +1,25 @@ #include "widegraph.h" + +#include +#include + #include "ui_widegraph.h" #include "commons.h" +#include "Configuration.hpp" + +#include "moc_widegraph.cpp" #define MAX_SCREENSIZE 2048 WideGraph::WideGraph(QSettings * settings, QWidget *parent) : QDialog(parent), ui(new Ui::WideGraph), - m_settings (settings) + m_settings (settings), + m_palettes_path {":/Palettes"} { ui->setupUi(this); - setWindowTitle ("Wide Graph"); + setWindowTitle (QApplication::applicationName () + " - " + tr ("Wide Graph")); setWindowFlags (Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint); setMaximumWidth (MAX_SCREENSIZE); setMaximumHeight (880); @@ -58,8 +66,13 @@ WideGraph::WideGraph(QSettings * settings, QWidget *parent) : setRxRange (m_fMin); m_settings->endGroup(); - QDir recoredDir("Palettes"); - QStringList allFiles = recoredDir.entryList(QDir::NoDotAndDotDot | + saveSettings (); // update config with defaults + + //m_palettes_path = configuration->resources_path (); + //QString palettes_dir {":/Palettes"}; + //m_palettes_path.cd (palettes_dir); + + QStringList allFiles = m_palettes_path.entryList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst); int index=0; @@ -72,7 +85,7 @@ WideGraph::WideGraph(QSettings * settings, QWidget *parent) : index++; } // ui->paletteComboBox->lineEdit()->setAlignment(Qt::AlignHCenter); - readPalette("Palettes/" + m_waterfallPalette + ".pal"); + readPalette(m_palettes_path.absoluteFilePath (m_waterfallPalette + ".pal")); } WideGraph::~WideGraph () @@ -352,7 +365,7 @@ void WideGraph::readPalette(QString fileName) void WideGraph::on_paletteComboBox_activated(const QString &palette) { m_waterfallPalette=palette; - readPalette("Palettes/" + palette + ".pal"); + readPalette(m_palettes_path.absoluteFilePath(palette + ".pal")); } void WideGraph::on_cbFlatten_toggled(bool b) diff --git a/widegraph.h b/widegraph.h index 82c7d6abe..b571f3b1f 100644 --- a/widegraph.h +++ b/widegraph.h @@ -3,12 +3,14 @@ #include #include +#include namespace Ui { class WideGraph; } class QSettings; +class Configuration; class WideGraph : public QDialog { @@ -67,6 +69,7 @@ private slots: private: QScopedPointer ui; QSettings * m_settings; + QDir m_palettes_path; qint32 m_rxFreq; qint32 m_txFreq; diff --git a/wsjtx.desktop b/wsjtx.desktop new file mode 100644 index 000000000..dd663036f --- /dev/null +++ b/wsjtx.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Version=1.0 +Name=wsjtx +Comment=Amateur Radio Weak Signal Operating +Exec=wsjtx +Icon=wsjtx_icon.png +Terminal=false +X-MultipleArgs=false +Type=Application +Categories=Amateur Radio; +StartupNotify=true diff --git a/wsjtx.pro b/wsjtx.pro index 3731697e8..d6ffcf157 100644 --- a/wsjtx.pro +++ b/wsjtx.pro @@ -15,49 +15,91 @@ DESTDIR = ../wsjtx_install VERSION = 1.2 TEMPLATE = app DEFINES = QT5 +HAMLIB_DIR = ../../hamlib-1.2.15.3 +QMAKE_CXXFLAGS += -std=c++11 -win32 { -DEFINES += WIN32 -F90 = g95 -g95.output = ${QMAKE_FILE_BASE}.o -g95.commands = $$F90 -c -O2 -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_NAME} -g95.input = F90_SOURCES -QMAKE_EXTRA_COMPILERS += g95 -} - -unix { -DEFINES += UNIX F90 = gfortran gfortran.output = ${QMAKE_FILE_BASE}.o gfortran.commands = $$F90 -c -O2 -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_NAME} gfortran.input = F90_SOURCES QMAKE_EXTRA_COMPILERS += gfortran + +win32 { +DEFINES += WIN32 +#F90 = g95 +#g95.output = ${QMAKE_FILE_BASE}.o +#g95.commands = $$F90 -c -O2 -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_NAME} +#g95.input = F90_SOURCES +#QMAKE_EXTRA_COMPILERS += g95 +QT += axcontainer +TYPELIBS = $$system(dumpcpp -getfile {4FE359C5-A58F-459D-BE95-CA559FB4F270}) +} + +unix { +DEFINES += UNIX } # # Order matters here as the link is in this order so referrers need to be after referred # -SOURCES += logbook/adif.cpp logbook/countrydat.cpp astro.cpp \ - logbook/countriesworked.cpp logbook/logbook.cpp rigclass.cpp \ - psk_reporter.cpp Modulator.cpp Detector.cpp logqso.cpp \ - displaytext.cpp getfile.cpp soundout.cpp soundin.cpp \ - meterwidget.cpp signalmeter.cpp plotter.cpp widegraph.cpp \ - devsetup.cpp about.cpp mainwindow.cpp main.cpp decodedtext.cpp +SOURCES += \ + logbook/adif.cpp \ + logbook/countrydat.cpp \ + logbook/countriesworked.cpp \ + logbook/logbook.cpp \ + astro.cpp \ + NetworkServerLookup.cpp \ + Transceiver.cpp \ + TransceiverBase.cpp \ + TransceiverFactory.cpp \ + PollingTransceiver.cpp \ + EmulateSplitTransceiver.cpp \ + HRDTransceiver.cpp \ + DXLabSuiteCommanderTransceiver.cpp \ + HamlibTransceiver.cpp \ + BandData.cpp \ + Configuration.cpp \ + psk_reporter.cpp \ + Modulator.cpp \ + Detector.cpp \ + logqso.cpp \ + displaytext.cpp \ + getfile.cpp \ + soundout.cpp \ + soundin.cpp \ + meterwidget.cpp \ + signalmeter.cpp \ + plotter.cpp \ + widegraph.cpp \ + about.cpp \ + mainwindow.cpp \ + main.cpp \ + decodedtext.cpp + +HEADERS += qt_helpers.hpp \ + pimpl_h.hpp pimpl_impl.hpp \ + NetworkServerLookup.hpp \ + mainwindow.h plotter.h soundin.h soundout.h astro.h \ + about.h widegraph.h getfile.h \ + commons.h sleep.h displaytext.h logqso.h \ + AudioDevice.hpp Detector.hpp Modulator.hpp psk_reporter.h \ + Transceiver.hpp TransceiverBase.hpp TransceiverFactory.hpp PollingTransceiver.hpp \ + EmulateSplitTransceiver.hpp DXLabSuiteCommanderTransceiver.hpp HamlibTransceiver.hpp \ + BandData.hpp \ + Configuration.hpp \ + signalmeter.h \ + meterwidget.h \ + logbook/logbook.h \ + logbook/countrydat.h \ + logbook/countriesworked.h \ + logbook/adif.h win32 { -SOURCES += killbyname.cpp +SOURCES += killbyname.cpp OmniRigTransceiver.cpp +HEADERS += OmniRigTransceiver.hpp } -HEADERS += mainwindow.h plotter.h soundin.h soundout.h astro.h \ - about.h devsetup.h widegraph.h getfile.h \ - commons.h sleep.h displaytext.h logqso.h \ - AudioDevice.hpp Detector.hpp Modulator.hpp \ - psk_reporter.h rigclass.h signalmeter.h \ - meterwidget.h logbook/logbook.h logbook/countrydat.h \ - logbook/countriesworked.h logbook/adif.h - - -FORMS += mainwindow.ui about.ui devsetup.ui widegraph.ui astro.ui \ +FORMS += mainwindow.ui about.ui Configuration.ui widegraph.ui astro.ui \ logqso.ui RC_FILE = wsjtx.rc @@ -65,19 +107,15 @@ RC_FILE = wsjtx.rc unix { LIBS += -L lib -ljt9 LIBS += -lhamlib -LIBS += -lfftw3f `$$F90 -print-file-name=libgfortran.so` +LIBS += -lfftw3f $$system($$F90 -print-file-name=libgfortran.so) } win32 { -INCLUDEPATH += /wsjt-env//hamlib/include -LIBS += /wsjt-env/hamlib/lib/gcc/libhamlib.dll.a -#LIBS += ../wsjtx/lib/libjt9.a -LIBS += ../wsjtx/lib/libjt9.a -LIBS += ../wsjtx/lib/libastro.a -LIBS += ../wsjtx/libfftw3f_win.a -LIBS += ../wsjtx/libpskreporter.a -LIBS += ../wsjtx/libHRDInterface001.a -LIBS += libwsock32 -LIBS += C:/MinGW/lib/libf95.a - +INCLUDEPATH += ${HAMLIB_DIR}/include +LIBS += -L${HAMLIB_DIR}/lib -lhamlib +#LIBS += -L${HAMLIB_DIR}/lib -lhamlib +LIBS += -L./lib -ljt9 +LIBS += -L. -lfftw3f_win +LIBS += -lwsock32 +LIBS += $$system($$F90 -print-file-name=libgfortran.a) } diff --git a/wsjtx.qrc b/wsjtx.qrc new file mode 100644 index 000000000..160ca21c6 --- /dev/null +++ b/wsjtx.qrc @@ -0,0 +1,39 @@ + + + shortcuts.txt + mouse_commands.txt + prefixes.txt + cty.dat + kvasd.dat + Palettes/Banana.pal + Palettes/Blue1.pal + Palettes/Blue2.pal + Palettes/Blue3.pal + Palettes/Brown.pal + Palettes/Cyan1.pal + Palettes/Cyan2.pal + Palettes/Cyan3.pal + Palettes/Default.pal + Palettes/Digipan.pal + Palettes/Fldigi.pal + Palettes/Gray1.pal + Palettes/Gray2.pal + Palettes/Green1.pal + Palettes/Green2.pal + Palettes/Jungle.pal + Palettes/Linrad.pal + Palettes/Negative.pal + Palettes/Orange.pal + Palettes/Pink.pal + Palettes/Rainbow.pal + Palettes/Scope.pal + Palettes/Sunburst.pal + Palettes/VK4BDJ.pal + Palettes/YL2KF.pal + Palettes/Yellow1.pal + Palettes/Yellow2.pal + Palettes/ZL2FZ.pal + samples/130418_1742.wav + samples/130610_2343.wav + + \ No newline at end of file diff --git a/wsjtx.rc b/wsjtx.rc index 9a0de814a..d54bf4ffa 100644 --- a/wsjtx.rc +++ b/wsjtx.rc @@ -1 +1 @@ -IDI_ICON1 ICON DISCARDABLE "wsjt.ico" +IDI_ICON1 ICON DISCARDABLE "icons/windows-icons/wsjtx.ico" diff --git a/wsjtx_DMG.DS_Store b/wsjtx_DMG.DS_Store new file mode 100644 index 000000000..8475e0e29 Binary files /dev/null and b/wsjtx_DMG.DS_Store differ diff --git a/wsjtx_config.h.in b/wsjtx_config.h.in index fd95b6bd8..cb2b2e93c 100644 --- a/wsjtx_config.h.in +++ b/wsjtx_config.h.in @@ -1,4 +1,31 @@ +#ifndef WSJTX_CONFIG_H__ +#define WSJTX_CONFIG_H__ + #include "svnversion.h" #define WSJTX_VERSION_MAJOR @WSJTX_VERSION_MAJOR@ #define WSJTX_VERSION_MINOR @WSJTX_VERSION_MINOR@ +#define WSJTX_VERSION_PATCH @WSJTX_VERSION_PATCH@ +#cmakedefine WSJTX_RC @WSJTX_RC@ +#cmakedefine01 WSJTX_VERSION_IS_RELEASE + +#define CONFIG_TEST_VERSION_MAJOR @CONFIG_TEST_VERSION_MAJOR@ +#define CONFIG_TEST_VERSION_MINOR @CONFIG_TEST_VERSION_MINOR@ +#define CONFIG_TEST_VERSION_PATCH @CONFIG_TEST_VERSION_PATCH@ + +#cmakedefine WSJT_BIN_DESTINATION "@WSJT_BIN_DESTINATION@" +#cmakedefine WSJT_LIB_DESTINATION "@WSJT_LIB_DESTINATION@" +#cmakedefine WSJT_SHARE_DESTINATION "@WSJT_SHARE_DESTINATION@" +#cmakedefine WSJT_DOC_DESTINATION "@WSJT_DOC_DESTINATION@" + +#cmakedefine01 WSJT_SHARED_RUNTIME +#cmakedefine01 WSJT_QDEBUG_IN_RELEASE +#cmakedefine01 WSJT_TRACE_CAT +#cmakedefine01 WSJT_TRACE_CAT_POLLS +#cmakedefine01 WSJT_HAMLIB_TRACE +#cmakedefine01 WSJT_STANDARD_FILE_LOCATIONS + +#define WSJTX_STRINGIZE1(x) #x +#define WSJTX_STRINGIZE(x) WSJTX_STRINGIZE1(x) + +#endif