code:
#.rst:
# GetPrerequisites
# ----------------
#
# Functions to analyze and list executable file prerequisites.
#
# This module provides functions to list the .dll, .dylib or .so files
# that an executable or shared library file depends on.  (Its
# prerequisites.)
#
# It uses various tools to obtain the list of required shared library
# files:
#
# ::
#
#    dumpbin (Windows)
#    objdump (MinGW on Windows)
#    ldd (Linux/Unix)
#    otool (Mac OSX)
#
# The following functions are provided by this module:
#
# ::
#
#    get_prerequisites
#    list_prerequisites
#    list_prerequisites_by_glob
#    gp_append_unique
#    is_file_executable
#    gp_item_default_embedded_path
#      (projects can override with gp_item_default_embedded_path_override)
#    gp_resolve_item
#      (projects can override with gp_resolve_item_override)
#    gp_resolved_file_type
#      (projects can override with gp_resolved_file_type_override)
#    gp_file_type
#
# Requires CMake 2.6 or greater because it uses function, break, return
# and PARENT_SCOPE.
#
# ::
#
#   GET_PREREQUISITES(<target> <prerequisites_var> <exclude_system> <recurse>
#                     <exepath> <dirs> [<rpaths>])
#
# Get the list of shared library files required by <target>.  The list
# in the variable named <prerequisites_var> should be empty on first
# entry to this function.  On exit, <prerequisites_var> will contain the
# list of required shared library files.
#
# <target> is the full path to an executable file.  <prerequisites_var>
# is the name of a CMake variable to contain the results.
# <exclude_system> must be 0 or 1 indicating whether to include or
# exclude "system" prerequisites.  If <recurse> is set to 1 all
# prerequisites will be found recursively, if set to 0 only direct
# prerequisites are listed.  <exepath> is the path to the top level
# executable used for @executable_path replacment on the Mac.  <dirs> is
# a list of paths where libraries might be found: these paths are
# searched first when a target without any path info is given.  Then
# standard system locations are also searched: PATH, Framework
# locations, /usr/lib...
#
# ::
#
#   LIST_PREREQUISITES(<target> [<recurse> [<exclude_system> [<verbose>]]])
#
# Print a message listing the prerequisites of <target>.
#
# <target> is the name of a shared library or executable target or the
# full path to a shared library or executable file.  If <recurse> is set
# to 1 all prerequisites will be found recursively, if set to 0 only
# direct prerequisites are listed.  <exclude_system> must be 0 or 1
# indicating whether to include or exclude "system" prerequisites.  With
# <verbose> set to 0 only the full path names of the prerequisites are
# printed, set to 1 extra informatin will be displayed.
#
# ::
#
#   LIST_PREREQUISITES_BY_GLOB(<glob_arg> <glob_exp>)
#
# Print the prerequisites of shared library and executable files
# matching a globbing pattern.  <glob_arg> is GLOB or GLOB_RECURSE and
# <glob_exp> is a globbing expression used with "file(GLOB" or
# "file(GLOB_RECURSE" to retrieve a list of matching files.  If a
# matching file is executable, its prerequisites are listed.
#
# Any additional (optional) arguments provided are passed along as the
# optional arguments to the list_prerequisites calls.
#
# ::
#
#   GP_APPEND_UNIQUE(<list_var> <value>)
#
# Append <value> to the list variable <list_var> only if the value is
# not already in the list.
#
# ::
#
#   IS_FILE_EXECUTABLE(<file> <result_var>)
#
# Return 1 in <result_var> if <file> is a binary executable, 0
# otherwise.
#
# ::
#
#   GP_ITEM_DEFAULT_EMBEDDED_PATH(<item> <default_embedded_path_var>)
#
# Return the path that others should refer to the item by when the item
# is embedded inside a bundle.
#
# Override on a per-project basis by providing a project-specific
# gp_item_default_embedded_path_override function.
#
# ::
#
#   GP_RESOLVE_ITEM(<context> <item> <exepath> <dirs> <resolved_item_var>
#                   [<rpaths>])
#
# Resolve an item into an existing full path file.
#
# Override on a per-project basis by providing a project-specific
# gp_resolve_item_override function.
#
# ::
#
#   GP_RESOLVED_FILE_TYPE(<original_file> <file> <exepath> <dirs> <type_var>
#                         [<rpaths>])
#
# Return the type of <file> with respect to <original_file>.  String
# describing type of prerequisite is returned in variable named
# <type_var>.
#
# Use <exepath> and <dirs> if necessary to resolve non-absolute <file>
# values -- but only for non-embedded items.
#
# Possible types are:
#
# ::
#
#    system
#    local
#    embedded
#    other
#
# Override on a per-project basis by providing a project-specific
# gp_resolved_file_type_override function.
#
# ::
#
#   GP_FILE_TYPE(<original_file> <file> <type_var>)
#
# Return the type of <file> with respect to <original_file>.  String
# describing type of prerequisite is returned in variable named
# <type_var>.
#
# Possible types are:
#
# ::
#
#    system
#    local
#    embedded
#    other

#=============================================================================
# Copyright 2008-2009 Kitware, Inc.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
#  License text for the above reference.)

function(gp_append_unique list_var value)
  set(contains 0)

  foreach(item ${${list_var}})
    if(item STREQUAL "${value}")
      set(contains 1)
      break()
    endif()
  endforeach()

  if(NOT contains)
    set(${list_var} ${${list_var}} "${value}" PARENT_SCOPE)
  endif()
endfunction()

function(is_file_executable file result_var)
  #
  # A file is not executable until proven otherwise:
  #
  set(${result_var} 0 PARENT_SCOPE)

  get_filename_component(file_full "${file}" ABSOLUTE)
  string(TOLOWER "${file_full}" file_full_lower)

  # If file name ends in .exe on Windows, *assume* executable:
  #
  if(WIN32 AND NOT UNIX)
    if("${file_full_lower}" MATCHES "\\.exe$")
      set(${result_var} 1 PARENT_SCOPE)
      return()
    endif()

    # A clause could be added here that uses output or return value of dumpbin
    # to determine ${result_var}. In 99%+? practical cases, the exe name
    # match will be sufficient...
    #
  endif()

  # Use the information returned from the Unix shell command "file" to
  # determine if ${file_full} should be considered an executable file...
  #
  # If the file command's output contains "executable" and does *not* contain
  # "text" then it is likely an executable suitable for prerequisite analysis
  # via the get_prerequisites macro.
  #
  if(UNIX)
    if(NOT file_cmd)
      find_program(file_cmd "file")
      mark_as_advanced(file_cmd)
    endif()

    if(file_cmd)
      execute_process(COMMAND "${file_cmd}" "${file_full}"
        RESULT_VARIABLE file_rv
        OUTPUT_VARIABLE file_ov
        ERROR_VARIABLE file_ev
        OUTPUT_STRIP_TRAILING_WHITESPACE
        )
      if(NOT file_rv STREQUAL "0")
        message(FATAL_ERROR "${file_cmd} failed: ${file_rv}\n${file_ev}")
      endif()

      # Replace the name of the file in the output with a placeholder token
      # (the string " _file_full_ ") so that just in case the path name of
      # the file contains the word "text" or "executable" we are not fooled
      # into thinking "the wrong thing" because the file name matches the
      # other 'file' command output we are looking for...
      #
      string(REPLACE "${file_full}" " _file_full_ " file_ov "${file_ov}")
      string(TOLOWER "${file_ov}" file_ov)

      #message(STATUS "file_ov='${file_ov}'")
      if("${file_ov}" MATCHES "executable")
        #message(STATUS "executable!")
        if("${file_ov}" MATCHES "text")
          #message(STATUS "but text, so *not* a binary executable!")
        else()
          set(${result_var} 1 PARENT_SCOPE)
          return()
        endif()
      endif()

      # Also detect position independent executables on Linux,
      # where "file" gives "shared object ... (uses shared libraries)"
      if("${file_ov}" MATCHES "shared object.*\(uses shared libs\)")
        set(${result_var} 1 PARENT_SCOPE)
        return()
      endif()

      # "file" version 5.22 does not print "(used shared libraries)"
      # but uses "interpreter"
      if("${file_ov}" MATCHES "shared object.*interpreter")
        set(${result_var} 1 PARENT_SCOPE)
        return()
      endif()

    else()
      message(STATUS "warning: No 'file' command, skipping execute_process...")
    endif()
  endif()
endfunction()

function(gp_item_default_embedded_path item default_embedded_path_var)

  # On Windows and Linux, "embed" prerequisites in the same directory
  # as the executable by default:
  #
  set(path "@executable_path")
  set(overridden 0)

  # On the Mac, relative to the executable depending on the type
  # of the thing we are embedding:
  #
  if(APPLE)
    #
    # The assumption here is that all executables in the bundle will be
    # in same-level-directories inside the bundle. The parent directory
    # of an executable inside the bundle should be MacOS or a sibling of
    # MacOS and all embedded paths returned from here will begin with
    # "@executable_path/../" and will work from all executables in all
    # such same-level-directories inside the bundle.
    #

    # By default, embed things right next to the main bundle executable:
    #
    set(path "@executable_path/../../Contents/MacOS")

    # Embed .dylibs right next to the main bundle executable:
    #
    if(item MATCHES "\\.dylib$")
      set(path "@executable_path/../MacOS")
      set(overridden 1)
    endif()

    # Embed frameworks in the embedded "Frameworks" directory (sibling of MacOS):
    #
    if(NOT overridden)
      if(item MATCHES "[^/]+\\.framework/")
        set(path "@executable_path/../Frameworks")
        set(overridden 1)
      endif()
    endif()
  endif()

  # Provide a hook so that projects can override the default embedded location
  # of any given library by whatever logic they choose:
  #
  if(COMMAND gp_item_default_embedded_path_override)
    gp_item_default_embedded_path_override("${item}" path)
  endif()

  set(${default_embedded_path_var} "${path}" PARENT_SCOPE)
endfunction()

function(gp_resolve_item context item exepath dirs resolved_item_var)
  set(resolved 0)
  set(resolved_item "${item}")
  if(ARGC GREATER 5)
    set(rpaths "${ARGV5}")
  else()
    set(rpaths "")
  endif()

  # Is it already resolved?
  #
  if(IS_ABSOLUTE "${resolved_item}" AND EXISTS "${resolved_item}")
    set(resolved 1)
  endif()

  if(NOT resolved)
    if(item MATCHES "^@executable_path")
      #
      # @executable_path references are assumed relative to exepath
      #
      string(REPLACE "@executable_path" "${exepath}" ri "${item}")
      get_filename_component(ri "${ri}" ABSOLUTE)

      if(EXISTS "${ri}")
        #message(STATUS "info: embedded item exists (${ri})")
        set(resolved 1)
        set(resolved_item "${ri}")
      else()
        message(STATUS "warning: embedded item does not exist '${ri}'")
      endif()
    endif()
  endif()

  if(NOT resolved)
    if(item MATCHES "^@loader_path")
      #
      # @loader_path references are assumed relative to the
      # PATH of the given "context" (presumably another library)
      #
      get_filename_component(contextpath "${context}" PATH)
      string(REPLACE "@loader_path" "${contextpath}" ri "${item}")
      get_filename_component(ri "${ri}" ABSOLUTE)

      if(EXISTS "${ri}")
        #message(STATUS "info: embedded item exists (${ri})")
        set(resolved 1)
        set(resolved_item "${ri}")
      else()
        message(STATUS "warning: embedded item does not exist '${ri}'")
      endif()
    endif()
  endif()

  if(NOT resolved)
    if(item MATCHES "^@rpath")
      #
      # @rpath references are relative to the paths built into the binaries with -rpath
      # We handle this case like we do for other Unixes
      #
      string(REPLACE "@rpath/" "" norpath_item "${item}")

      set(ri "ri-NOTFOUND")
      find_file(ri "${norpath_item}" ${exepath} ${dirs} ${rpaths} NO_DEFAULT_PATH)
      if(ri)
        #message(STATUS "info: 'find_file' in exepath/dirs/rpaths (${ri})")
        set(resolved 1)
        set(resolved_item "${ri}")
        set(ri "ri-NOTFOUND")
      endif()

    endif()
  endif()

  if(NOT resolved)
    set(ri "ri-NOTFOUND")
    find_file(ri "${item}" ${exepath} ${dirs} NO_DEFAULT_PATH)
    find_file(ri "${item}" ${exepath} ${dirs} /usr/lib)
    if(ri)
      #message(STATUS "info: 'find_file' in exepath/dirs (${ri})")
      set(resolved 1)
      set(resolved_item "${ri}")
      set(ri "ri-NOTFOUND")
    endif()
  endif()

  if(NOT resolved)
    if(item MATCHES "[^/]+\\.framework/")
      set(fw "fw-NOTFOUND")
      find_file(fw "${item}"
        "~/Library/Frameworks"
        "/Library/Frameworks"
        "/System/Library/Frameworks"
      )
      if(fw)
        #message(STATUS "info: 'find_file' found framework (${fw})")
        set(resolved 1)
        set(resolved_item "${fw}")
        set(fw "fw-NOTFOUND")
      endif()
    endif()
  endif()

  # Using find_program on Windows will find dll files that are in the PATH.
  # (Converting simple file names into full path names if found.)
  #
  if(WIN32 AND NOT UNIX)
  if(NOT resolved)
    set(ri "ri-NOTFOUND")
    find_program(ri "${item}" PATHS "${exepath};${dirs}" NO_DEFAULT_PATH)
    find_program(ri "${item}" PATHS "${exepath};${dirs}")
    if(ri)
      #message(STATUS "info: 'find_program' in exepath/dirs (${ri})")
      set(resolved 1)
      set(resolved_item "${ri}")
      set(ri "ri-NOTFOUND")
    endif()
  endif()
  endif()

  # Provide a hook so that projects can override item resolution
  # by whatever logic they choose:
  #
  if(COMMAND gp_resolve_item_override)
    gp_resolve_item_override("${context}" "${item}" "${exepath}" "${dirs}" resolved_item resolved)
  endif()

  if(NOT resolved)
    message(STATUS "
warning: cannot resolve item '${item}'

  possible problems:
    need more directories?
    need to use InstallRequiredSystemLibraries?
    run in install tree instead of build tree?
")
#    message(STATUS "
#******************************************************************************
#warning: cannot resolve item '${item}'
#
#  possible problems:
#    need more directories?
#    need to use InstallRequiredSystemLibraries?
#    run in install tree instead of build tree?
#
#    context='${context}'
#    item='${item}'
#    exepath='${exepath}'
#    dirs='${dirs}'
#    resolved_item_var='${resolved_item_var}'
#******************************************************************************
#")
  endif()

  set(${resolved_item_var} "${resolved_item}" PARENT_SCOPE)
endfunction()

function(gp_resolved_file_type original_file file exepath dirs type_var)
  if(ARGC GREATER 5)
    set(rpaths "${ARGV5}")
  else()
    set(rpaths "")
  endif()
  #message(STATUS "**")

  if(NOT IS_ABSOLUTE "${original_file}")
    message(STATUS "warning: gp_resolved_file_type expects absolute full path for first arg original_file")
  endif()

  set(is_embedded 0)
  set(is_local 0)
  set(is_system 0)

  set(resolved_file "${file}")

  if("${file}" MATCHES "^@(executable|loader)_path")
    set(is_embedded 1)
  endif()

  if(NOT is_embedded)
    if(NOT IS_ABSOLUTE "${file}")
      gp_resolve_item("${original_file}" "${file}" "${exepath}" "${dirs}" resolved_file "${rpaths}")
    endif()

    string(TOLOWER "${original_file}" original_lower)
    string(TOLOWER "${resolved_file}" lower)

    if(UNIX)
      if(resolved_file MATCHES "^(/lib/|/lib32/|/lib64/|/usr/lib/|/usr/lib32/|/usr/lib64/|/usr/X11R6/|/usr/bin/)")
        set(is_system 1)
      endif()
    endif()

    if(APPLE)
      if(resolved_file MATCHES "^(/System/Library/|/usr/lib/)")
        set(is_system 1)
      endif()
    endif()

    if(WIN32)
      string(TOLOWER "$ENV{SystemRoot}" sysroot)
      file(TO_CMAKE_PATH "${sysroot}" sysroot)

      string(TOLOWER "$ENV{windir}" windir)
      file(TO_CMAKE_PATH "${windir}" windir)

      if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)")
        set(is_system 1)
      endif()

      if(UNIX)
        # if cygwin, we can get the properly formed windows paths from cygpath
        find_program(CYGPATH_EXECUTABLE cygpath)

        if(CYGPATH_EXECUTABLE)
          execute_process(COMMAND ${CYGPATH_EXECUTABLE} -W
                          RESULT_VARIABLE env_rv
                          OUTPUT_VARIABLE env_windir
                          ERROR_VARIABLE env_ev
                          OUTPUT_STRIP_TRAILING_WHITESPACE)
          if(NOT env_rv STREQUAL "0")
            message(FATAL_ERROR "${CYGPATH_EXECUTABLE} -W failed: ${env_rv}\n${env_ev}")
          endif()
          execute_process(COMMAND ${CYGPATH_EXECUTABLE} -S
                          RESULT_VARIABLE env_rv
                          OUTPUT_VARIABLE env_sysdir
                          ERROR_VARIABLE env_ev
                          OUTPUT_STRIP_TRAILING_WHITESPACE)
          if(NOT env_rv STREQUAL "0")
            message(FATAL_ERROR "${CYGPATH_EXECUTABLE} -S failed: ${env_rv}\n${env_ev}")
          endif()
          string(TOLOWER "${env_windir}" windir)
          string(TOLOWER "${env_sysdir}" sysroot)

          if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)")
            set(is_system 1)
          endif()
        endif()
      endif()
    endif()

    if(NOT is_system)
      get_filename_component(original_path "${original_lower}" PATH)
      get_filename_component(path "${lower}" PATH)
      if(original_path STREQUAL path)
        set(is_local 1)
      else()
        string(LENGTH "${original_path}/" original_length)
        string(LENGTH "${lower}" path_length)
        if(${path_length} GREATER ${original_length})
          string(SUBSTRING "${lower}" 0 ${original_length} path)
          if("${original_path}/" STREQUAL path)
            set(is_embedded 1)
          endif()
        endif()
      endif()
    endif()
  endif()

  # Return type string based on computed booleans:
  #
  set(type "other")

  if(is_system)
    set(type "system")
  elseif(is_embedded)
    set(type "embedded")
  elseif(is_local)
    set(type "local")
  endif()

  #message(STATUS "gp_resolved_file_type: '${file}' '${resolved_file}'")
  #message(STATUS "                type: '${type}'")

  if(NOT is_embedded)
    if(NOT IS_ABSOLUTE "${resolved_file}")
      if(lower MATCHES "^msvc[^/]+dll" AND is_system)
        message(STATUS "info: non-absolute msvc file '${file}' returning type '${type}'")
      else()
        message(STATUS "warning: gp_resolved_file_type non-absolute file '${file}' returning type '${type}' -- possibly incorrect")
      endif()
    endif()
  endif()

  # Provide a hook so that projects can override the decision on whether a
  # library belongs to the system or not by whatever logic they choose:
  #
  if(COMMAND gp_resolved_file_type_override)
    gp_resolved_file_type_override("${resolved_file}" type)
  endif()

  set(${type_var} "${type}" PARENT_SCOPE)

  #message(STATUS "**")
endfunction()

function(gp_file_type original_file file type_var)
  if(NOT IS_ABSOLUTE "${original_file}")
    message(STATUS "warning: gp_file_type expects absolute full path for first arg original_file")
  endif()

  get_filename_component(exepath "${original_file}" PATH)

  set(type "")
  gp_resolved_file_type("${original_file}" "${file}" "${exepath}" "" type)

  set(${type_var} "${type}" PARENT_SCOPE)
endfunction()

function(get_prerequisites target prerequisites_var exclude_system recurse exepath dirs)
  set(verbose 0)
  set(eol_char "E")
  if(ARGC GREATER 6)
    set(rpaths "${ARGV6}")
  else()
    set(rpaths "")
  endif()

  if(NOT IS_ABSOLUTE "${target}")
    message("warning: target '${target}' is not absolute...")
  endif()

  if(NOT EXISTS "${target}")
    message("warning: target '${target}' does not exist...")
  endif()

  set(gp_cmd_paths ${gp_cmd_paths}
    "C:/Program Files/Microsoft Visual Studio 9.0/VC/bin"
    "C:/Program Files (x86)/Microsoft Visual Studio 9.0/VC/bin"
    "C:/Program Files/Microsoft Visual Studio 8/VC/BIN"
    "C:/Program Files (x86)/Microsoft Visual Studio 8/VC/BIN"
    "C:/Program Files/Microsoft Visual Studio .NET 2003/VC7/BIN"
    "C:/Program Files (x86)/Microsoft Visual Studio .NET 2003/VC7/BIN"
    "/usr/local/bin"
    "/usr/bin"
    )

  # <setup-gp_tool-vars>
  #
  # Try to choose the right tool by default. Caller can set gp_tool prior to
  # calling this function to force using a different tool.
  #
  if(NOT gp_tool)
    set(gp_tool "ldd")

    if(APPLE)
      set(gp_tool "otool")
    endif()

    if(WIN32 AND NOT UNIX) # This is how to check for cygwin, har!
      find_program(gp_dumpbin "dumpbin" PATHS ${gp_cmd_paths})
      if(gp_dumpbin)
        set(gp_tool "dumpbin")
      else() # Try harder. Maybe we're on MinGW
        set(gp_tool "objdump")
      endif()
    endif()
  endif()

  find_program(gp_cmd ${gp_tool} PATHS ${gp_cmd_paths})

  if(NOT gp_cmd)
    message(STATUS "warning: could not find '${gp_tool}' - cannot analyze prerequisites...")
    return()
  endif()

  set(gp_cmd_maybe_filter)      # optional command to pre-filter gp_tool results

  if(gp_tool STREQUAL "ldd")
    set(gp_cmd_args "")
    set(gp_regex "^[\t ]*[^\t ]+ => ([^\t\(]+) .*${eol_char}$")
    set(gp_regex_error "not found${eol_char}$")
    set(gp_regex_fallback "^[\t ]*([^\t ]+) => ([^\t ]+).*${eol_char}$")
    set(gp_regex_cmp_count 1)
  elseif(gp_tool STREQUAL "otool")
    set(gp_cmd_args "-L")
    set(gp_regex "^\t([^\t]+) \\(compatibility version ([0-9]+.[0-9]+.[0-9]+), current version ([0-9]+.[0-9]+.[0-9]+)\\)${eol_char}$")
    set(gp_regex_error "")
    set(gp_regex_fallback "")
    set(gp_regex_cmp_count 3)
  elseif(gp_tool STREQUAL "dumpbin")
    set(gp_cmd_args "/dependents")
    set(gp_regex "^    ([^ ].*[Dd][Ll][Ll])${eol_char}$")
    set(gp_regex_error "")
    set(gp_regex_fallback "")
    set(gp_regex_cmp_count 1)
  elseif(gp_tool STREQUAL "objdump")
    set(gp_cmd_args "-p")
    set(gp_regex "^\t*DLL Name: (.*\\.[Dd][Ll][Ll])${eol_char}$")
    set(gp_regex_error "")
    set(gp_regex_fallback "")
    set(gp_regex_cmp_count 1)
    # objdump generaates copious output so we create a grep filter to pre-filter results
    find_program(gp_grep_cmd grep)
    if(gp_grep_cmd)
      set(gp_cmd_maybe_filter COMMAND ${gp_grep_cmd} "^[[:blank:]]*DLL Name: ")
    endif()
  else()
    message(STATUS "warning: gp_tool='${gp_tool}' is an unknown tool...")
    message(STATUS "CMake function get_prerequisites needs more code to handle '${gp_tool}'")
    message(STATUS "Valid gp_tool values are dumpbin, ldd, objdump and otool.")
    return()
  endif()

  if(gp_tool STREQUAL "dumpbin")
    # When running dumpbin, it also needs the "Common7/IDE" directory in the
    # PATH. It will already be in the PATH if being run from a Visual Studio
    # command prompt. Add it to the PATH here in case we are running from a
    # different command prompt.
    #
    get_filename_component(gp_cmd_dir "${gp_cmd}" PATH)
    get_filename_component(gp_cmd_dlls_dir "${gp_cmd_dir}/../../Common7/IDE" ABSOLUTE)
    # Use cmake paths as a user may have a PATH element ending with a backslash.
    # This will escape the list delimiter and create havoc!
    if(EXISTS "${gp_cmd_dlls_dir}")
      # only add to the path if it is not already in the path
      set(gp_found_cmd_dlls_dir 0)
      file(TO_CMAKE_PATH "$ENV{PATH}" env_path)
      foreach(gp_env_path_element ${env_path})
        if(gp_env_path_element STREQUAL gp_cmd_dlls_dir)
          set(gp_found_cmd_dlls_dir 1)
        endif()
      endforeach()

      if(NOT gp_found_cmd_dlls_dir)
        file(TO_NATIVE_PATH "${gp_cmd_dlls_dir}" gp_cmd_dlls_dir)
        set(ENV{PATH} "$ENV{PATH};${gp_cmd_dlls_dir}")
      endif()
    endif()
  endif()
  #
  # </setup-gp_tool-vars>

  if(gp_tool STREQUAL "ldd")
    set(old_ld_env "$ENV{LD_LIBRARY_PATH}")
    set(new_ld_env "${exepath}")
    foreach(dir ${dirs})
      set(new_ld_env "${new_ld_env}:${dir}")
    endforeach()
    set(ENV{LD_LIBRARY_PATH} "${new_ld_env}:$ENV{LD_LIBRARY_PATH}")
  endif()

  # Track new prerequisites at each new level of recursion. Start with an
  # empty list at each level:
  #
  set(unseen_prereqs)

  # Run gp_cmd on the target:
  #
  execute_process(
    COMMAND ${gp_cmd} ${gp_cmd_args} ${target}
    ${gp_cmd_maybe_filter}
    RESULT_VARIABLE gp_rv
    OUTPUT_VARIABLE gp_cmd_ov
    ERROR_VARIABLE gp_ev
    )
  if(NOT gp_rv STREQUAL "0")
    if(gp_tool STREQUAL "dumpbin")
      # dumpbin error messages seem to go to stdout
      message(FATAL_ERROR "${gp_cmd} failed: ${gp_rv}\n${gp_ev}\n${gp_cmd_ov}")
    else()
      message(FATAL_ERROR "${gp_cmd} failed: ${gp_rv}\n${gp_ev}")
    endif()
  endif()

  if(gp_tool STREQUAL "ldd")
    set(ENV{LD_LIBRARY_PATH} "${old_ld_env}")
  endif()

  if(verbose)
    message(STATUS "<RawOutput cmd='${gp_cmd} ${gp_cmd_args} ${target}'>")
    message(STATUS "gp_cmd_ov='${gp_cmd_ov}'")
    message(STATUS "</RawOutput>")
  endif()

  get_filename_component(target_dir "${target}" PATH)

  # Convert to a list of lines:
  #
  string(REPLACE ";" "\\;" candidates "${gp_cmd_ov}")
  string(REPLACE "\n" "${eol_char};" candidates "${candidates}")

  # check for install id and remove it from list, since otool -L can include a
  # reference to itself
  set(gp_install_id)
  if(gp_tool STREQUAL "otool")
    execute_process(
      COMMAND otool -D ${target}
      RESULT_VARIABLE otool_rv
      OUTPUT_VARIABLE gp_install_id_ov
      ERROR_VARIABLE otool_ev
      )
    if(NOT otool_rv STREQUAL "0")
      message(FATAL_ERROR "otool -D failed: ${otool_rv}\n${otool_ev}")
    endif()
    # second line is install name
    string(REGEX REPLACE ".*:\n" "" gp_install_id "${gp_install_id_ov}")
    if(gp_install_id)
      # trim
      string(REGEX MATCH "[^\n ].*[^\n ]" gp_install_id "${gp_install_id}")
      #message("INSTALL ID is \"${gp_install_id}\"")
    endif()
  endif()

  # Analyze each line for file names that match the regular expression:
  #
  foreach(candidate ${candidates})
  if("${candidate}" MATCHES "${gp_regex}")

    # Extract information from each candidate:
    if(gp_regex_error AND "${candidate}" MATCHES "${gp_regex_error}")
      string(REGEX REPLACE "${gp_regex_fallback}" "\\1" raw_item "${candidate}")
    else()
      string(REGEX REPLACE "${gp_regex}" "\\1" raw_item "${candidate}")
    endif()

    if(gp_regex_cmp_count GREATER 1)
      string(REGEX REPLACE "${gp_regex}" "\\2" raw_compat_version "${candidate}")
      string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" compat_major_version "${raw_compat_version}")
      string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" compat_minor_version "${raw_compat_version}")
      string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" compat_patch_version "${raw_compat_version}")
    endif()

    if(gp_regex_cmp_count GREATER 2)
      string(REGEX REPLACE "${gp_regex}" "\\3" raw_current_version "${candidate}")
      string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" current_major_version "${raw_current_version}")
      string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" current_minor_version "${raw_current_version}")
      string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" current_patch_version "${raw_current_version}")
    endif()

    # Use the raw_item as the list entries returned by this function. Use the
    # gp_resolve_item function to resolve it to an actual full path file if
    # necessary.
    #
    set(item "${raw_item}")

    # Add each item unless it is excluded:
    #
    set(add_item 1)

    if(item STREQUAL gp_install_id)
      set(add_item 0)
    endif()

    if(add_item AND ${exclude_system})
      set(type "")
      gp_resolved_file_type("${target}" "${item}" "${exepath}" "${dirs}" type "${rpaths}")

      if(type STREQUAL "system")
        set(add_item 0)
      endif()
    endif()

    if(add_item)
      list(LENGTH ${prerequisites_var} list_length_before_append)
      gp_append_unique(${prerequisites_var} "${item}")
      list(LENGTH ${prerequisites_var} list_length_after_append)

      if(${recurse})
        # If item was really added, this is the first time we have seen it.
        # Add it to unseen_prereqs so that we can recursively add *its*
        # prerequisites...
        #
        # But first: resolve its name to an absolute full path name such
        # that the analysis tools can simply accept it as input.
        #
        if(NOT list_length_before_append EQUAL list_length_after_append)
          gp_resolve_item("${target}" "${item}" "${exepath}" "${dirs}" resolved_item "${rpaths}")
          set(unseen_prereqs ${unseen_prereqs} "${resolved_item}")
        endif()
      endif()
    endif()
  else()
    if(verbose)
      message(STATUS "ignoring non-matching line: '${candidate}'")
    endif()
  endif()
  endforeach()

  list(LENGTH ${prerequisites_var} prerequisites_var_length)
  if(prerequisites_var_length GREATER 0)
    list(SORT ${prerequisites_var})
  endif()
  if(${recurse})
    set(more_inputs ${unseen_prereqs})
    foreach(input ${more_inputs})
      get_prerequisites("${input}" ${prerequisites_var} ${exclude_system} ${recurse} "${exepath}" "${dirs}" "${rpaths}")
    endforeach()
  endif()

  set(${prerequisites_var} ${${prerequisites_var}} PARENT_SCOPE)
endfunction()

function(list_prerequisites target)
  if(ARGC GREATER 1 AND NOT "${ARGV1}" STREQUAL "")
    set(all "${ARGV1}")
  else()
    set(all 1)
  endif()

  if(ARGC GREATER 2 AND NOT "${ARGV2}" STREQUAL "")
    set(exclude_system "${ARGV2}")
  else()
    set(exclude_system 0)
  endif()

  if(ARGC GREATER 3 AND NOT "${ARGV3}" STREQUAL "")
    set(verbose "${ARGV3}")
  else()
    set(verbose 0)
  endif()

  set(count 0)
  set(count_str "")
  set(print_count "${verbose}")
  set(print_prerequisite_type "${verbose}")
  set(print_target "${verbose}")
  set(type_str "")

  get_filename_component(exepath "${target}" PATH)

  set(prereqs "")
  get_prerequisites("${target}" prereqs ${exclude_system} ${all} "${exepath}" "")

  if(print_target)
    message(STATUS "File '${target}' depends on:")
  endif()

  foreach(d ${prereqs})
    math(EXPR count "${count} + 1")

    if(print_count)
      set(count_str "${count}. ")
    endif()

    if(print_prerequisite_type)
      gp_file_type("${target}" "${d}" type)
      set(type_str " (${type})")
    endif()

    message(STATUS "${count_str}${d}${type_str}")
  endforeach()
endfunction()

function(list_prerequisites_by_glob glob_arg glob_exp)
  message(STATUS "=============================================================================")
  message(STATUS "List prerequisites of executables matching ${glob_arg} '${glob_exp}'")
  message(STATUS "")
  file(${glob_arg} file_list ${glob_exp})
  foreach(f ${file_list})
    is_file_executable("${f}" is_f_executable)
    if(is_f_executable)
      message(STATUS "=============================================================================")
      list_prerequisites("${f}" ${ARGN})
      message(STATUS "")
    endif()
  endforeach()
endfunction()


code:
//  Copyright (c) 2006 Xiaogang Zhang
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_MATH_BESSEL_I0_HPP
#define BOOST_MATH_BESSEL_I0_HPP

#ifdef _MSC_VER
#pragma once
#endif

#include <boost/math/tools/rational.hpp>
#include <boost/math/tools/big_constant.hpp>
#include <boost/assert.hpp>

// Modified Bessel function of the first kind of order zero
// minimax rational approximations on intervals, see
// Blair and Edwards, Chalk River Report AECL-4928, 1974

namespace boost { namespace math { namespace detail{

template <typename T>
T bessel_i0(T x);

template <class T>
struct bessel_i0_initializer
{
   struct init
   {
      init()
      {
         do_init();
      }
      static void do_init()
      {
         bessel_i0(T(1));
      }
      void force_instantiate()const{}
   };
   static const init initializer;
   static void force_instantiate()
   {
      initializer.force_instantiate();
   }
};

template <class T>
const typename bessel_i0_initializer<T>::init bessel_i0_initializer<T>::initializer;

template <typename T>
T bessel_i0(T x)
{
    bessel_i0_initializer<T>::force_instantiate();

    static const T P1[] = {
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -2.2335582639474375249e+15)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -5.5050369673018427753e+14)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -3.2940087627407749166e+13)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -8.4925101247114157499e+11)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -1.1912746104985237192e+10)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -1.0313066708737980747e+08)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -5.9545626019847898221e+05)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -2.4125195876041896775e+03)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -7.0935347449210549190e+00)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -1.5453977791786851041e-02)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -2.5172644670688975051e-05)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -3.0517226450451067446e-08)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -2.6843448573468483278e-11)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -1.5982226675653184646e-14)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -5.2487866627945699800e-18)),
    };
    static const T Q1[] = {
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -2.2335582639474375245e+15)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, 7.8858692566751002988e+12)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -1.2207067397808979846e+10)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, 1.0377081058062166144e+07)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -4.8527560179962773045e+03)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, 1.0)),
    };
    static const T P2[] = {
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -2.2210262233306573296e-04)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, 1.3067392038106924055e-02)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -4.4700805721174453923e-01)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, 5.5674518371240761397e+00)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -2.3517945679239481621e+01)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, 3.1611322818701131207e+01)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -9.6090021968656180000e+00)),
    };
    static const T Q2[] = {
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -5.5194330231005480228e-04)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, 3.2547697594819615062e-02)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -1.1151759188741312645e+00)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, 1.3982595353892851542e+01)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -6.0228002066743340583e+01)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, 8.5539563258012929600e+01)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, -3.1446690275135491500e+01)),
        static_cast<T>(BOOST_MATH_BIG_CONSTANT(T, 64, 1.0)),
    };
    T value, factor, r;

    BOOST_MATH_STD_USING
    using namespace boost::math::tools;

    BOOST_ASSERT(x >= 0); // negative x is handled before we get here
    if (x == 0)
    {
        return static_cast<T>(1);
    }
    if (x <= 15)                        // x in (0, 15]
    {
        T y = x * x;
        value = evaluate_polynomial(P1, y) / evaluate_polynomial(Q1, y);
    }
    else                                // x in (15, \infty)
    {
        T y = 1 / x - T(1) / 15;
        r = evaluate_polynomial(P2, y) / evaluate_polynomial(Q2, y);
        factor = exp(x) / sqrt(x);
        value = factor * r;
    }

    return value;
}

}}} // namespaces

#endif // BOOST_MATH_BESSEL_I0_HPP

license: mit


code:
#include "HRDTransceiver.hpp"

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

#include "NetworkServerLookup.hpp"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      auto tx_A = is_button_checked (tx_A_button_, quiet);

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

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

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

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

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

  QString result;

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

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

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

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

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

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

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

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

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

      total_bytes_sent += bytes_sent;
    }
  return true;
}

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

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


code:
/*=============================================================================
    Copyright (c) 2001-2011 Joel de Guzman

    Distributed under the Boost Software License, Version 1.0. (See accompanying
    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
==============================================================================*/
#if !defined(FUSION_VALUE_AT_IMPL_05052005_0232)
#define FUSION_VALUE_AT_IMPL_05052005_0232

#include <boost/fusion/support/config.hpp>
#include <boost/mpl/at.hpp>

namespace boost { namespace fusion
{
    struct vector_tag;

    namespace extension
    {
        template <typename Tag>
        struct value_at_impl;

        template <>
        struct value_at_impl<vector_tag>
        {
            template <typename Sequence, typename N>
            struct apply
            {
                typedef typename mpl::at<typename Sequence::types, N>::type type;
            };
        };
    }
}}

#endif


code:
//  Copyright Neil Groves 2010. Use, modification and
//  distribution is subject to the Boost Software License, Version
//  1.0. (See accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//
//
// For more information, see http://www.boost.org/libs/range/
//
#ifndef BOOST_RANGE_COMBINE_HPP
#define BOOST_RANGE_COMBINE_HPP

#include <boost/config.hpp>
#include <boost/range/iterator_range_core.hpp>
#include <boost/iterator/zip_iterator.hpp>

namespace boost
{
    namespace range
    {

template<typename IterTuple>
class combined_range
        : public iterator_range<zip_iterator<IterTuple> >
{
    typedef iterator_range<zip_iterator<IterTuple> > base;
public:
    combined_range(IterTuple first, IterTuple last)
        : base(first, last)
    {
    }
};

    } // namespace range
} // namespace boost

#if defined(BOOST_NO_CXX11_AUTO_DECLARATIONS) || \
    defined(BOOST_NO_CXX11_DECLTYPE) || \
    defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || \
    defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
#   include <boost/range/detail/combine_cxx03.hpp>
#else
#   include <boost/range/detail/combine_cxx11.hpp>
#endif

#endif


code:
#ifndef BOOST_THREAD_MUTEX_HPP
#define BOOST_THREAD_MUTEX_HPP

//  mutex.hpp
//
//  (C) Copyright 2007 Anthony Williams
//
//  Distributed under the Boost Software License, Version 1.0. (See
//  accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)

#include <boost/thread/detail/platform.hpp>
#if defined(BOOST_THREAD_PLATFORM_WIN32)
#include <boost/thread/win32/mutex.hpp>
#elif defined(BOOST_THREAD_PLATFORM_PTHREAD)
#include <boost/thread/pthread/mutex.hpp>
#else
#error "Boost threads unavailable on this platform"
#endif

#include <boost/thread/lockable_traits.hpp>

namespace boost
{
  namespace sync
  {
#ifdef BOOST_THREAD_NO_AUTO_DETECT_MUTEX_TYPES
    template<>
    struct is_basic_lockable<mutex>
    {
      BOOST_STATIC_CONSTANT(bool, value = true);
    };
    template<>
    struct is_lockable<mutex>
    {
      BOOST_STATIC_CONSTANT(bool, value = true);
    };
    template<>
    struct is_basic_lockable<timed_mutex>
    {
      BOOST_STATIC_CONSTANT(bool, value = true);
    };
    template<>
    struct is_lockable<timed_mutex>
    {
      BOOST_STATIC_CONSTANT(bool, value = true);
    };
#endif
  }
}

#endif