mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-11-03 21:40:52 -05:00 
			
		
		
		
	Extend UDP status message - added Rx/Tx DF, call and grid information
Build now creates and installs a UDP library that contains the server side of the UDP messaging facility. This library is used by the udp_daemon and message_aggregator reference examples. The new library is currently a static archive but can also be built as a shared library. The library allows third party Qt applications to easily access UDP messages from WSJT-X. Refactored the message_aggregator reference example to split out classes into separate translation units. Added new functionality to exercise the new UDP status fields, highlight own call, CQ/QRZ messages and decodes near Rx DF. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@6691 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
This commit is contained in:
		
							parent
							
								
									3ff3a192ee
								
							
						
					
					
						commit
						ae3749bb46
					
				
							
								
								
									
										124
									
								
								CMakeLists.txt
									
									
									
									
									
								
							
							
						
						
									
										124
									
								
								CMakeLists.txt
									
									
									
									
									
								
							@ -40,6 +40,9 @@ if (POLICY CMP0043)
 | 
				
			|||||||
  cmake_policy (SET CMP0043 NEW) # ignore COMPILE_DEFINITIONS_<CONFIG>
 | 
					  cmake_policy (SET CMP0043 NEW) # ignore COMPILE_DEFINITIONS_<CONFIG>
 | 
				
			||||||
endif (POLICY CMP0043)
 | 
					endif (POLICY CMP0043)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (POLICY CMP0063)
 | 
				
			||||||
 | 
					  cmake_policy (SET CMP0063 NEW) # honour visibility properties for all library types
 | 
				
			||||||
 | 
					endif (POLICY CMP0063)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
include (${PROJECT_SOURCE_DIR}/CMake/VersionCompute.cmake)
 | 
					include (${PROJECT_SOURCE_DIR}/CMake/VersionCompute.cmake)
 | 
				
			||||||
message (STATUS "Building ${CMAKE_PROJECT_NAME}-${wsjtx_VERSION}")
 | 
					message (STATUS "Building ${CMAKE_PROJECT_NAME}-${wsjtx_VERSION}")
 | 
				
			||||||
@ -50,7 +53,7 @@ message (STATUS "Building ${CMAKE_PROJECT_NAME}-${wsjtx_VERSION}")
 | 
				
			|||||||
set (PROJECT_NAME "WSJT-X")
 | 
					set (PROJECT_NAME "WSJT-X")
 | 
				
			||||||
set (PROJECT_VENDOR "Joe Taylor, K1JT")
 | 
					set (PROJECT_VENDOR "Joe Taylor, K1JT")
 | 
				
			||||||
set (PROJECT_CONTACT "Joe Taylor <k1jt@arrl.net>")
 | 
					set (PROJECT_CONTACT "Joe Taylor <k1jt@arrl.net>")
 | 
				
			||||||
set (PROJECT_COPYRIGHT "Copyright (C) 2001-2015 by Joe Taylor, K1JT")
 | 
					set (PROJECT_COPYRIGHT "Copyright (C) 2001-2016 by Joe Taylor, K1JT")
 | 
				
			||||||
set (PROJECT_HOMEPAGE http://www.physics.princeton.edu/pulsar/K1JT/wsjtx.html)
 | 
					set (PROJECT_HOMEPAGE http://www.physics.princeton.edu/pulsar/K1JT/wsjtx.html)
 | 
				
			||||||
set (PROJECT_MANUAL wsjtx-main)
 | 
					set (PROJECT_MANUAL wsjtx-main)
 | 
				
			||||||
set (PROJECT_MANUAL_DIRECTORY_URL http://www.physics.princeton.edu/pulsar/K1JT/wsjtx-doc/)
 | 
					set (PROJECT_MANUAL_DIRECTORY_URL http://www.physics.princeton.edu/pulsar/K1JT/wsjtx-doc/)
 | 
				
			||||||
@ -104,6 +107,15 @@ endif ()
 | 
				
			|||||||
#
 | 
					#
 | 
				
			||||||
include (CMakeDependentOption)
 | 
					include (CMakeDependentOption)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Allow the developer to select if Dynamic or Static libraries are built
 | 
				
			||||||
 | 
					OPTION (BUILD_SHARED_LIBS "Build Shared Libraries" OFF)
 | 
				
			||||||
 | 
					# Set the LIB_TYPE variable to STATIC
 | 
				
			||||||
 | 
					SET (LIB_TYPE STATIC)
 | 
				
			||||||
 | 
					if (BUILD_SHARED_LIBS)
 | 
				
			||||||
 | 
					  # User wants to build Dynamic Libraries, so change the LIB_TYPE variable to CMake keyword 'SHARED'
 | 
				
			||||||
 | 
					  set (LIB_TYPE SHARED)
 | 
				
			||||||
 | 
					endif (BUILD_SHARED_LIBS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
option (UPDATE_TRANSLATIONS "Update source translation translations/*.ts
 | 
					option (UPDATE_TRANSLATIONS "Update source translation translations/*.ts
 | 
				
			||||||
files (WARNING: make clean will delete the source .ts files! Danger!)")
 | 
					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_SHARED_RUNTIME "Debugging option that allows running from a shared Cloud directory.")
 | 
				
			||||||
@ -145,6 +157,7 @@ message (STATUS "******************************************************")
 | 
				
			|||||||
#
 | 
					#
 | 
				
			||||||
set (BIN_DESTINATION bin)
 | 
					set (BIN_DESTINATION bin)
 | 
				
			||||||
set (LIB_DESTINATION lib)
 | 
					set (LIB_DESTINATION lib)
 | 
				
			||||||
 | 
					set (INCLUDE_DESTINATION include)
 | 
				
			||||||
set (SHARE_DESTINATION share)
 | 
					set (SHARE_DESTINATION share)
 | 
				
			||||||
set (DOC_DESTINATION doc/${CMAKE_PROJECT_NAME})
 | 
					set (DOC_DESTINATION doc/${CMAKE_PROJECT_NAME})
 | 
				
			||||||
set (DATA_DESTINATION ${CMAKE_PROJECT_NAME})
 | 
					set (DATA_DESTINATION ${CMAKE_PROJECT_NAME})
 | 
				
			||||||
@ -171,6 +184,7 @@ endif (APPLE)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
set (WSJT_BIN_DESTINATION ${BIN_DESTINATION} CACHE PATH "Path for executables")
 | 
					set (WSJT_BIN_DESTINATION ${BIN_DESTINATION} CACHE PATH "Path for executables")
 | 
				
			||||||
set (WSJT_LIB_DESTINATION ${LIB_DESTINATION} CACHE PATH "Path for libraries")
 | 
					set (WSJT_LIB_DESTINATION ${LIB_DESTINATION} CACHE PATH "Path for libraries")
 | 
				
			||||||
 | 
					set (WSJT_INCLUDE_DESTINATION ${INCLUDE_DESTINATION} CACHE PATH "Path for library headers")
 | 
				
			||||||
set (WSJT_SHARE_DESTINATION ${SHARE_DESTINATION} CACHE PATH "Path for shared content")
 | 
					set (WSJT_SHARE_DESTINATION ${SHARE_DESTINATION} CACHE PATH "Path for shared content")
 | 
				
			||||||
set (WSJT_DOC_DESTINATION ${DOC_DESTINATION} CACHE PATH "Path for documentation")
 | 
					set (WSJT_DOC_DESTINATION ${DOC_DESTINATION} CACHE PATH "Path for documentation")
 | 
				
			||||||
set (WSJT_DATA_DESTINATION ${DATA_DESTINATION} CACHE PATH "Path for shared RO data")
 | 
					set (WSJT_DATA_DESTINATION ${DATA_DESTINATION} CACHE PATH "Path for shared RO data")
 | 
				
			||||||
@ -188,6 +202,7 @@ set (wsjt_qt_CXXSRCS
 | 
				
			|||||||
  revision_utils.cpp
 | 
					  revision_utils.cpp
 | 
				
			||||||
  WFPalette.cpp
 | 
					  WFPalette.cpp
 | 
				
			||||||
  Radio.cpp
 | 
					  Radio.cpp
 | 
				
			||||||
 | 
					  RadioMetaType.cpp
 | 
				
			||||||
  Bands.cpp
 | 
					  Bands.cpp
 | 
				
			||||||
  Modes.cpp
 | 
					  Modes.cpp
 | 
				
			||||||
  FrequencyList.cpp
 | 
					  FrequencyList.cpp
 | 
				
			||||||
@ -512,16 +527,23 @@ set (wsjtx_UISRCS
 | 
				
			|||||||
  Configuration.ui
 | 
					  Configuration.ui
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set (message_aggregator_CXXSRCS
 | 
					set (UDP_library_CXXSRCS
 | 
				
			||||||
  MessageServer.cpp
 | 
					  Radio.cpp
 | 
				
			||||||
  MessageAggregator.cpp
 | 
					  RadioMetaType.cpp
 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
set (UDPDaemon_CXXSRCS
 | 
					 | 
				
			||||||
  NetworkMessage.cpp
 | 
					  NetworkMessage.cpp
 | 
				
			||||||
  MessageServer.cpp
 | 
					  MessageServer.cpp
 | 
				
			||||||
  Radio.cpp
 | 
					  )
 | 
				
			||||||
  UDPDaemon.cpp
 | 
					
 | 
				
			||||||
 | 
					set (message_aggregator_CXXSRCS
 | 
				
			||||||
 | 
					  UDPExamples/MessageAggregator.cpp
 | 
				
			||||||
 | 
					  UDPExamples/MessageAggregatorMainWindow.cpp
 | 
				
			||||||
 | 
					  UDPExamples/DecodesModel.cpp
 | 
				
			||||||
 | 
					  UDPExamples/BeaconsModel.cpp
 | 
				
			||||||
 | 
					  UDPExamples/ClientWidget.cpp
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set (message_aggregator_STYLESHEETS
 | 
				
			||||||
 | 
					  UDPExamples/qss/default.qss
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set (all_CXXSRCS
 | 
					set (all_CXXSRCS
 | 
				
			||||||
@ -530,8 +552,6 @@ set (all_CXXSRCS
 | 
				
			|||||||
  ${wsjt_qtmm_CXXSRCS}
 | 
					  ${wsjt_qtmm_CXXSRCS}
 | 
				
			||||||
  ${jt9_CXXSRCS}
 | 
					  ${jt9_CXXSRCS}
 | 
				
			||||||
  ${wsjtx_CXXSRCS}
 | 
					  ${wsjtx_CXXSRCS}
 | 
				
			||||||
  ${message_aggregator_CXXSRCS}
 | 
					 | 
				
			||||||
  ${UDPDaemon_CXXSRCS}
 | 
					 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set (all_C_and_CXXSRCS
 | 
					set (all_C_and_CXXSRCS
 | 
				
			||||||
@ -541,10 +561,6 @@ set (all_C_and_CXXSRCS
 | 
				
			|||||||
  ${all_CXXSRCS}
 | 
					  ${all_CXXSRCS}
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set (message_aggregator_STYLESHEETS
 | 
					 | 
				
			||||||
  qss/default.qss
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
set (TOP_LEVEL_RESOURCES
 | 
					set (TOP_LEVEL_RESOURCES
 | 
				
			||||||
  shortcuts.txt
 | 
					  shortcuts.txt
 | 
				
			||||||
  mouse_commands.txt
 | 
					  mouse_commands.txt
 | 
				
			||||||
@ -620,7 +636,7 @@ else (WSJT_QDEBUG_IN_RELEASE)
 | 
				
			|||||||
endif (WSJT_QDEBUG_IN_RELEASE)
 | 
					endif (WSJT_QDEBUG_IN_RELEASE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set_property (SOURCE ${all_C_and_CXXSRCS} APPEND_STRING PROPERTY COMPILE_FLAGS " -include wsjtx_config.h")
 | 
					set_property (SOURCE ${all_C_and_CXXSRCS} APPEND_STRING PROPERTY COMPILE_FLAGS " -include wsjtx_config.h")
 | 
				
			||||||
set_property (SOURCE ${all_C_and_CXXSRCS} APPEND PROPERTY OBJECT_DEPENDS wsjtx_config.h)
 | 
					set_property (SOURCE ${all_C_and_CXXSRCS} APPEND PROPERTY OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/wsjtx_config.h)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (WIN32)
 | 
					if (WIN32)
 | 
				
			||||||
  # generate the OmniRig COM interface source
 | 
					  # generate the OmniRig COM interface source
 | 
				
			||||||
@ -672,6 +688,18 @@ if (WSJT_GENERATE_DOCS)
 | 
				
			|||||||
  add_subdirectory (doc)
 | 
					  add_subdirectory (doc)
 | 
				
			||||||
endif (WSJT_GENERATE_DOCS)
 | 
					endif (WSJT_GENERATE_DOCS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Library building setup
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					include (GenerateExportHeader)
 | 
				
			||||||
 | 
					set (CMAKE_CXX_VISIBILITY_PRESET hidden)
 | 
				
			||||||
 | 
					set (CMAKE_C_VISIBILITY_PRESET hidden)
 | 
				
			||||||
 | 
					set (CMAKE_Fortran_VISIBILITY_PRESET hidden)
 | 
				
			||||||
 | 
					set (CMAKE_VISIBILITY_INLINES_HIDDEN ON)
 | 
				
			||||||
 | 
					#set (CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# C & C++ setup
 | 
					# C & C++ setup
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
@ -688,7 +716,7 @@ if (NOT APPLE)
 | 
				
			|||||||
endif (NOT APPLE)
 | 
					endif (NOT APPLE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (WIN32)
 | 
					if (WIN32)
 | 
				
			||||||
  set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-keep-inline-dllexport")
 | 
					  set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
 | 
				
			||||||
endif (WIN32)
 | 
					endif (WIN32)
 | 
				
			||||||
if (APPLE)
 | 
					if (APPLE)
 | 
				
			||||||
  if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
 | 
					  if (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
 | 
				
			||||||
@ -849,6 +877,7 @@ set (QT_MKSPECS_DIR ${QT_DATA_DIR}/mkspecs)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Tell CMake to run moc when necessary
 | 
					# Tell CMake to run moc when necessary
 | 
				
			||||||
set (CMAKE_AUTOMOC ON)
 | 
					set (CMAKE_AUTOMOC ON)
 | 
				
			||||||
 | 
					include_directories (${CMAKE_CURRENT_BINARY_DIR})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# don't use Qt "keywords" signal, slot, emit in generated files to
 | 
					# don't use Qt "keywords" signal, slot, emit in generated files to
 | 
				
			||||||
# avoid compatability issue with other libraries
 | 
					# avoid compatability issue with other libraries
 | 
				
			||||||
@ -883,7 +912,7 @@ add_custom_target (etags COMMAND ${ETAGS} -o ${CMAKE_SOURCE_DIR}/TAGS -R ${sourc
 | 
				
			|||||||
function (add_resources resources path)
 | 
					function (add_resources resources path)
 | 
				
			||||||
  foreach (resource_file_ ${ARGN})
 | 
					  foreach (resource_file_ ${ARGN})
 | 
				
			||||||
    get_filename_component (name_ ${resource_file_} NAME)
 | 
					    get_filename_component (name_ ${resource_file_} NAME)
 | 
				
			||||||
    file (TO_NATIVE_PATH ${CMAKE_SOURCE_DIR}/${resource_file_} source_)
 | 
					    file (TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${resource_file_} source_)
 | 
				
			||||||
    file (TO_NATIVE_PATH ${path}/${name_} dest_)
 | 
					    file (TO_NATIVE_PATH ${path}/${name_} dest_)
 | 
				
			||||||
    set (resources_ "${resources_}\n    <file alias=\"${dest_}\">${source_}</file>")
 | 
					    set (resources_ "${resources_}\n    <file alias=\"${dest_}\">${source_}</file>")
 | 
				
			||||||
    set (${resources} ${${resources}}${resources_} PARENT_SCOPE)
 | 
					    set (${resources} ${${resources}}${resources_} PARENT_SCOPE)
 | 
				
			||||||
@ -942,6 +971,8 @@ endif (${OPENMP_FOUND} OR APPLE)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# build a library of package Qt functionality
 | 
					# build a library of package Qt functionality
 | 
				
			||||||
add_library (wsjt_qt STATIC ${wsjt_qt_CXXSRCS} ${wsjt_qt_GENUISRCS} ${GENAXSRCS})
 | 
					add_library (wsjt_qt STATIC ${wsjt_qt_CXXSRCS} ${wsjt_qt_GENUISRCS} ${GENAXSRCS})
 | 
				
			||||||
 | 
					# set wsjtx_udp exports to static variants
 | 
				
			||||||
 | 
					set_target_properties (wsjt_qt PROPERTIES COMPILE_FLAGS -DUDP_STATIC_DEFINE)
 | 
				
			||||||
target_link_libraries (wsjt_qt Qt5::Widgets Qt5::Network)
 | 
					target_link_libraries (wsjt_qt Qt5::Widgets Qt5::Network)
 | 
				
			||||||
target_include_directories (wsjt_qt BEFORE PRIVATE ${hamlib_INCLUDE_DIRS})
 | 
					target_include_directories (wsjt_qt BEFORE PRIVATE ${hamlib_INCLUDE_DIRS})
 | 
				
			||||||
if (WIN32)
 | 
					if (WIN32)
 | 
				
			||||||
@ -1033,24 +1064,34 @@ set_target_properties (wsjtx PROPERTIES
 | 
				
			|||||||
  MACOSX_BUNDLE_GUI_IDENTIFIER "org.k1jt.wsjtx"
 | 
					  MACOSX_BUNDLE_GUI_IDENTIFIER "org.k1jt.wsjtx"
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# set wsjtx_udp exports to static variants
 | 
				
			||||||
 | 
					set_target_properties (wsjtx PROPERTIES COMPILE_FLAGS -DUDP_STATIC_DEFINE)
 | 
				
			||||||
target_link_libraries (wsjtx wsjt_fort wsjt_cxx wsjt_qt wsjt_qtmm ${hamlib_LIBRARIES} ${FFTW3_LIBRARIES})
 | 
					target_link_libraries (wsjtx wsjt_fort wsjt_cxx wsjt_qt wsjt_qtmm ${hamlib_LIBRARIES} ${FFTW3_LIBRARIES})
 | 
				
			||||||
qt5_use_modules (wsjtx SerialPort) # not sure why the interface link library syntax above doesn't work
 | 
					qt5_use_modules (wsjtx SerialPort) # not sure why the interface link library syntax above doesn't work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# make a library for WSJT-X UDP servers
 | 
				
			||||||
 | 
					add_library (wsjtx_udp ${LIB_TYPE} ${UDP_library_CXXSRCS})
 | 
				
			||||||
 | 
					target_include_directories (wsjtx_udp
 | 
				
			||||||
 | 
					  INTERFACE
 | 
				
			||||||
 | 
					  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
 | 
				
			||||||
 | 
					  $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
 | 
				
			||||||
 | 
					  $<INSTALL_INTERFACE:${WSJT_INCLUDE_DESTINATION}/wsjtx>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					qt5_use_modules (wsjtx_udp Network)
 | 
				
			||||||
 | 
					generate_export_header (wsjtx_udp BASE_NAME udp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_executable (udp_daemon UDPExamples/UDPDaemon.cpp UDPExamples/udp_daemon.rc)
 | 
				
			||||||
 | 
					target_link_libraries (udp_daemon wsjtx_udp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_resources (message_aggregator_RESOURCES /qss ${message_aggregator_STYLESHEETS})
 | 
					add_resources (message_aggregator_RESOURCES /qss ${message_aggregator_STYLESHEETS})
 | 
				
			||||||
configure_file (message_aggregator.qrc.in message_aggregator.qrc @ONLY)
 | 
					configure_file (UDPExamples/message_aggregator.qrc.in message_aggregator.qrc @ONLY)
 | 
				
			||||||
qt5_add_resources (message_aggregator_RESOURCES_RCC ${CMAKE_BINARY_DIR}/message_aggregator.qrc)
 | 
					qt5_add_resources (message_aggregator_RESOURCES_RCC ${CMAKE_CURRENT_BINARY_DIR}/message_aggregator.qrc)
 | 
				
			||||||
add_executable (message_aggregator
 | 
					add_executable (message_aggregator
 | 
				
			||||||
  ${message_aggregator_CXXSRCS}
 | 
					  ${message_aggregator_CXXSRCS}
 | 
				
			||||||
  wsjtx.rc
 | 
					  UDPExamples/message_aggregator.rc
 | 
				
			||||||
  ${message_aggregator_RESOURCES_RCC}
 | 
					  ${message_aggregator_RESOURCES_RCC}
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
target_link_libraries (message_aggregator wsjt_qt Qt5::Widgets)
 | 
					target_link_libraries (message_aggregator wsjt_qt Qt5::Widgets wsjtx_udp)
 | 
				
			||||||
 | 
					 | 
				
			||||||
add_executable (udp_daemon
 | 
					 | 
				
			||||||
  ${UDPDaemon_CXXSRCS}
 | 
					 | 
				
			||||||
  wsjtx.rc
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
target_link_libraries (udp_daemon Qt5::Core Qt5::Network)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (WSJT_CREATE_WINMAIN)
 | 
					if (WSJT_CREATE_WINMAIN)
 | 
				
			||||||
  set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON)
 | 
					  set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON)
 | 
				
			||||||
@ -1075,7 +1116,25 @@ install (TARGETS wsjtx
 | 
				
			|||||||
  BUNDLE DESTINATION . COMPONENT runtime
 | 
					  BUNDLE DESTINATION . COMPONENT runtime
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
install (TARGETS jt9 jt65code jt9code jt4code wsprd message_aggregator udp_daemon
 | 
					install (TARGETS wsjtx_udp EXPORT udp
 | 
				
			||||||
 | 
					  DESTINATION ${WSJT_LIB_DESTINATION}
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					install (EXPORT udp NAMESPACE wsjtx::
 | 
				
			||||||
 | 
					  DESTINATION ${WSJT_LIB_DESTINATION}/cmake/wsjtx
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					export (EXPORT udp NAMESPACE wsjtx:: FILE udp-exports.cmake)
 | 
				
			||||||
 | 
					install (FILES
 | 
				
			||||||
 | 
					  Radio.hpp
 | 
				
			||||||
 | 
					  MessageServer.hpp
 | 
				
			||||||
 | 
					  ${PROJECT_BINARY_DIR}/udp_export.h
 | 
				
			||||||
 | 
					  DESTINATION ${WSJT_INCLUDE_DESTINATION}/wsjtx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					install (TARGETS udp_daemon message_aggregator
 | 
				
			||||||
 | 
					  RUNTIME DESTINATION ${WSJT_BIN_DESTINATION} COMPONENT runtime
 | 
				
			||||||
 | 
					  BUNDLE DESTINATION ${WSJT_BIN_DESTINATION} COMPONENT runtime
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					install (TARGETS jt9 jt65code jt9code jt4code wsprd
 | 
				
			||||||
  RUNTIME DESTINATION ${WSJT_BIN_DESTINATION} COMPONENT runtime
 | 
					  RUNTIME DESTINATION ${WSJT_BIN_DESTINATION} COMPONENT runtime
 | 
				
			||||||
  BUNDLE DESTINATION ${WSJT_BIN_DESTINATION} COMPONENT runtime
 | 
					  BUNDLE DESTINATION ${WSJT_BIN_DESTINATION} COMPONENT runtime
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
@ -1152,10 +1211,9 @@ add_dependencies(wsjt_qt revisiontag)
 | 
				
			|||||||
# versioning and configuration
 | 
					# versioning and configuration
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
configure_file (
 | 
					configure_file (
 | 
				
			||||||
  "${PROJECT_SOURCE_DIR}/wsjtx_config.h.in"
 | 
					  "${CMAKE_CURRENT_SOURCE_DIR}/wsjtx_config.h.in"
 | 
				
			||||||
  "${PROJECT_BINARY_DIR}/wsjtx_config.h"
 | 
					  "${CMAKE_CURRENT_BINARY_DIR}/wsjtx_config.h"
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
include_directories (BEFORE "${PROJECT_BINARY_DIR}")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (NOT WIN32 AND NOT APPLE)
 | 
					if (NOT WIN32 AND NOT APPLE)
 | 
				
			||||||
@ -1258,7 +1316,7 @@ if (NOT is_debug_build)
 | 
				
			|||||||
      #set (hamlib_lib_dir ${hamlib_lib_dir}/../bin)
 | 
					      #set (hamlib_lib_dir ${hamlib_lib_dir}/../bin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      get_filename_component (fftw_lib_dir ${FFTW3F_LIBRARY} PATH)
 | 
					      get_filename_component (fftw_lib_dir ${FFTW3F_LIBRARY} PATH)
 | 
				
			||||||
      list (APPEND fixup_library_dirs ${fftw_lib_dir})
 | 
					      list (APPEND fixup_library_dirs ${WSJT_LIB_DESTINATION} ${fftw_lib_dir})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      # install required Qt plugins
 | 
					      # install required Qt plugins
 | 
				
			||||||
      install (
 | 
					      install (
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <utility>
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QMetaType>
 | 
				
			||||||
#include <QAbstractTableModel>
 | 
					#include <QAbstractTableModel>
 | 
				
			||||||
#include <QString>
 | 
					#include <QString>
 | 
				
			||||||
#include <QList>
 | 
					#include <QList>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,687 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// MessageAggregator - an example application that utilizes the WSJT-X
 | 
					 | 
				
			||||||
//                     messaging facility
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// This  application is  only  provided as  a  simple GUI  application
 | 
					 | 
				
			||||||
// example to demonstrate the WSJT-X messaging facility. It allows the
 | 
					 | 
				
			||||||
// user to set  the server details either as a  unicast UDP server or,
 | 
					 | 
				
			||||||
// if a  multicast group address  is provided, as a  multicast server.
 | 
					 | 
				
			||||||
// The benefit of the multicast server is that multiple servers can be
 | 
					 | 
				
			||||||
// active at  once each  receiving all  WSJT-X broadcast  messages and
 | 
					 | 
				
			||||||
// each able to  respond to individual WSJT_X clients.  To utilize the
 | 
					 | 
				
			||||||
// multicast  group features  each  WSJT-X client  must  set the  same
 | 
					 | 
				
			||||||
// multicast  group address  as  the UDP  server  address for  example
 | 
					 | 
				
			||||||
// 239.255.0.0 for a site local multicast group.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// The  UI is  a small  panel  to input  the service  port number  and
 | 
					 | 
				
			||||||
// optionally  the  multicast  group  address.   Below  that  a  table
 | 
					 | 
				
			||||||
// representing  the  log  entries   where  any  QSO  logged  messages
 | 
					 | 
				
			||||||
// broadcast  from WSJT-X  clients are  displayed. The  bottom of  the
 | 
					 | 
				
			||||||
// application main  window is a  dock area  where a dock  window will
 | 
					 | 
				
			||||||
// appear for each WSJT-X client, this  window contains a table of the
 | 
					 | 
				
			||||||
// current decode  messages broadcast  from that  WSJT-X client  and a
 | 
					 | 
				
			||||||
// status line showing  the status update messages  broadcast from the
 | 
					 | 
				
			||||||
// WSJT_X client. The dock windows may  be arranged in a tab bar, side
 | 
					 | 
				
			||||||
// by side,  below each  other or, completely  detached from  the dock
 | 
					 | 
				
			||||||
// area as floating windows. Double clicking the dock window title bar
 | 
					 | 
				
			||||||
// or  dragging and  dropping with  the mouse  allows these  different
 | 
					 | 
				
			||||||
// arrangements.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// The application  also provides a  simple menu bar including  a view
 | 
					 | 
				
			||||||
// menu that allows each dock window to be hidden or revealed.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <iostream>
 | 
					 | 
				
			||||||
#include <exception>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <QtWidgets>
 | 
					 | 
				
			||||||
#include <QFile>
 | 
					 | 
				
			||||||
#include <QStandardItemModel>
 | 
					 | 
				
			||||||
#include <QStandardItem>
 | 
					 | 
				
			||||||
#include <QSortFilterProxyModel>
 | 
					 | 
				
			||||||
#include <QFont>
 | 
					 | 
				
			||||||
#include <QDateTime>
 | 
					 | 
				
			||||||
#include <QTime>
 | 
					 | 
				
			||||||
#include <QHash>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "MessageServer.hpp"
 | 
					 | 
				
			||||||
#include "NetworkMessage.hpp"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "qt_helpers.hpp"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using port_type = MessageServer::port_type;
 | 
					 | 
				
			||||||
using Frequency = MessageServer::Frequency;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"};
 | 
					 | 
				
			||||||
QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>]*"};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Decodes Model - simple data model for all decodes
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// The model is a basic table with uniform row format. Rows consist of
 | 
					 | 
				
			||||||
// QStandardItem instances containing the string representation of the
 | 
					 | 
				
			||||||
// column data  and if the underlying  field is not a  string then the
 | 
					 | 
				
			||||||
// UserRole+1 role contains the underlying data item.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Three slots  are provided to add  a new decode, remove  all decodes
 | 
					 | 
				
			||||||
// for a client  and, to build a  reply to CQ message for  a given row
 | 
					 | 
				
			||||||
// which is emitted as a signal respectively.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
class DecodesModel
 | 
					 | 
				
			||||||
  : public QStandardItemModel
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  Q_OBJECT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
  DecodesModel (QObject * parent = nullptr)
 | 
					 | 
				
			||||||
    : QStandardItemModel {0, 7, parent}
 | 
					 | 
				
			||||||
    , text_font_ {"Courier", 10}
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    setHeaderData (0, Qt::Horizontal, tr ("Client"));
 | 
					 | 
				
			||||||
    setHeaderData (1, Qt::Horizontal, tr ("Time"));
 | 
					 | 
				
			||||||
    setHeaderData (2, Qt::Horizontal, tr ("Snr"));
 | 
					 | 
				
			||||||
    setHeaderData (3, Qt::Horizontal, tr ("DT"));
 | 
					 | 
				
			||||||
    setHeaderData (4, Qt::Horizontal, tr ("DF"));
 | 
					 | 
				
			||||||
    setHeaderData (5, Qt::Horizontal, tr ("Md"));
 | 
					 | 
				
			||||||
    setHeaderData (6, Qt::Horizontal, tr ("Message"));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Q_SLOT void add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
 | 
					 | 
				
			||||||
                          , quint32 delta_frequency, QString const& mode, QString const& message)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    if (!is_new)
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        int target_row {-1};
 | 
					 | 
				
			||||||
        for (auto row = 0; row < rowCount (); ++row)
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            if (data (index (row, 0)).toString () == client_id)
 | 
					 | 
				
			||||||
              {
 | 
					 | 
				
			||||||
                auto row_time = item (row, 1)->data ().toTime ();
 | 
					 | 
				
			||||||
                if (row_time == time
 | 
					 | 
				
			||||||
                    && item (row, 2)->data ().toInt () == snr
 | 
					 | 
				
			||||||
                    && item (row, 3)->data ().toFloat () == delta_time
 | 
					 | 
				
			||||||
                    && item (row, 4)->data ().toUInt () == delta_frequency
 | 
					 | 
				
			||||||
                    && data (index (row, 5)).toString () == mode
 | 
					 | 
				
			||||||
                    && data (index (row, 6)).toString () == message)
 | 
					 | 
				
			||||||
                  {
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                if (time <= row_time)
 | 
					 | 
				
			||||||
                  {
 | 
					 | 
				
			||||||
                    target_row = row; // last row with same time
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        if (target_row >= 0)
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, delta_frequency, mode, message));
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    appendRow (make_row (client_id, time, snr, delta_time, delta_frequency, mode, message));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time
 | 
					 | 
				
			||||||
                                   , quint32 delta_frequency, QString const& mode, QString const& message) const
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    auto time_item = new QStandardItem {time.toString ("hh:mm")};
 | 
					 | 
				
			||||||
    time_item->setData (time);
 | 
					 | 
				
			||||||
    time_item->setTextAlignment (Qt::AlignRight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto snr_item = new QStandardItem {QString::number (snr)};
 | 
					 | 
				
			||||||
    snr_item->setData (snr);
 | 
					 | 
				
			||||||
    snr_item->setTextAlignment (Qt::AlignRight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto dt = new QStandardItem {QString::number (delta_time)};
 | 
					 | 
				
			||||||
    dt->setData (delta_time);
 | 
					 | 
				
			||||||
    dt->setTextAlignment (Qt::AlignRight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto df = new QStandardItem {QString::number (delta_frequency)};
 | 
					 | 
				
			||||||
    df->setData (delta_frequency);
 | 
					 | 
				
			||||||
    df->setTextAlignment (Qt::AlignRight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto md = new QStandardItem {mode};
 | 
					 | 
				
			||||||
    md->setTextAlignment (Qt::AlignHCenter);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    QList<QStandardItem *> row {
 | 
					 | 
				
			||||||
      new QStandardItem {client_id}, time_item, snr_item, dt, df, md, new QStandardItem {message}};
 | 
					 | 
				
			||||||
    Q_FOREACH (auto& item, row)
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        item->setEditable (false);
 | 
					 | 
				
			||||||
        item->setFont (text_font_);
 | 
					 | 
				
			||||||
        item->setTextAlignment (item->textAlignment () | Qt::AlignVCenter);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    return row;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Q_SLOT void clear_decodes (QString const& client_id)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    for (auto row = rowCount () - 1; row >= 0; --row)
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        if (data (index (row, 0)).toString () == client_id)
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            removeRow (row);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Q_SLOT void do_reply (QModelIndex const& source)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    auto row = source.row ();
 | 
					 | 
				
			||||||
    Q_EMIT reply (data (index (row, 0)).toString ()
 | 
					 | 
				
			||||||
                  , item (row, 1)->data ().toTime ()
 | 
					 | 
				
			||||||
                  , item (row, 2)->data ().toInt ()
 | 
					 | 
				
			||||||
                  , item (row, 3)->data ().toFloat ()
 | 
					 | 
				
			||||||
                  , item (row, 4)->data ().toInt ()
 | 
					 | 
				
			||||||
                  , data (index (row, 5)).toString ()
 | 
					 | 
				
			||||||
                  , data (index (row, 6)).toString ());
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Q_SIGNAL void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
 | 
					 | 
				
			||||||
                       , QString const& mode, QString const& message);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
  QFont text_font_;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Beacons Model - simple data model for all beacon spots
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// The model is a basic table with uniform row format. Rows consist of
 | 
					 | 
				
			||||||
// QStandardItem instances containing the string representation of the
 | 
					 | 
				
			||||||
// column data  and if the underlying  field is not a  string then the
 | 
					 | 
				
			||||||
// UserRole+1 role contains the underlying data item.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// Two slots are provided to add a new decode and remove all spots for
 | 
					 | 
				
			||||||
// a client.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
class BeaconsModel
 | 
					 | 
				
			||||||
  : public QStandardItemModel
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  Q_OBJECT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
  BeaconsModel (QObject * parent = nullptr)
 | 
					 | 
				
			||||||
    : QStandardItemModel {0, 9, parent}
 | 
					 | 
				
			||||||
    , text_font_ {"Courier", 10}
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    setHeaderData (0, Qt::Horizontal, tr ("Client"));
 | 
					 | 
				
			||||||
    setHeaderData (1, Qt::Horizontal, tr ("Time"));
 | 
					 | 
				
			||||||
    setHeaderData (2, Qt::Horizontal, tr ("Snr"));
 | 
					 | 
				
			||||||
    setHeaderData (3, Qt::Horizontal, tr ("DT"));
 | 
					 | 
				
			||||||
    setHeaderData (4, Qt::Horizontal, tr ("Frequency"));
 | 
					 | 
				
			||||||
    setHeaderData (5, Qt::Horizontal, tr ("Drift"));
 | 
					 | 
				
			||||||
    setHeaderData (6, Qt::Horizontal, tr ("Callsign"));
 | 
					 | 
				
			||||||
    setHeaderData (7, Qt::Horizontal, tr ("Grid"));
 | 
					 | 
				
			||||||
    setHeaderData (8, Qt::Horizontal, tr ("Power"));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Q_SLOT void add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
 | 
					 | 
				
			||||||
                          , Frequency frequency, qint32 drift, QString const& callsign, QString const& grid
 | 
					 | 
				
			||||||
                          , qint32 power)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    if (!is_new)
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        int target_row {-1};
 | 
					 | 
				
			||||||
        for (auto row = 0; row < rowCount (); ++row)
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            if (data (index (row, 0)).toString () == client_id)
 | 
					 | 
				
			||||||
              {
 | 
					 | 
				
			||||||
                auto row_time = item (row, 1)->data ().toTime ();
 | 
					 | 
				
			||||||
                if (row_time == time
 | 
					 | 
				
			||||||
                    && item (row, 2)->data ().toInt () == snr
 | 
					 | 
				
			||||||
                    && item (row, 3)->data ().toFloat () == delta_time
 | 
					 | 
				
			||||||
                    && item (row, 4)->data ().value<Frequency> () == frequency
 | 
					 | 
				
			||||||
                    && data (index (row, 5)).toInt () == drift
 | 
					 | 
				
			||||||
                    && data (index (row, 6)).toString () == callsign
 | 
					 | 
				
			||||||
                    && data (index (row, 7)).toString () == grid
 | 
					 | 
				
			||||||
                    && data (index (row, 8)).toInt () == power)
 | 
					 | 
				
			||||||
                  {
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                if (time <= row_time)
 | 
					 | 
				
			||||||
                  {
 | 
					 | 
				
			||||||
                    target_row = row; // last row with same time
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        if (target_row >= 0)
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power));
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    appendRow (make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time
 | 
					 | 
				
			||||||
                                   , Frequency frequency, qint32 drift, QString const& callsign
 | 
					 | 
				
			||||||
                                   , QString const& grid, qint32 power) const
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    auto time_item = new QStandardItem {time.toString ("hh:mm")};
 | 
					 | 
				
			||||||
    time_item->setData (time);
 | 
					 | 
				
			||||||
    time_item->setTextAlignment (Qt::AlignRight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto snr_item = new QStandardItem {QString::number (snr)};
 | 
					 | 
				
			||||||
    snr_item->setData (snr);
 | 
					 | 
				
			||||||
    snr_item->setTextAlignment (Qt::AlignRight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto dt = new QStandardItem {QString::number (delta_time)};
 | 
					 | 
				
			||||||
    dt->setData (delta_time);
 | 
					 | 
				
			||||||
    dt->setTextAlignment (Qt::AlignRight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto freq = new QStandardItem {Radio::pretty_frequency_MHz_string (frequency)};
 | 
					 | 
				
			||||||
    freq->setData (frequency);
 | 
					 | 
				
			||||||
    freq->setTextAlignment (Qt::AlignRight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto dri = new QStandardItem {QString::number (drift)};
 | 
					 | 
				
			||||||
    dri->setData (drift);
 | 
					 | 
				
			||||||
    dri->setTextAlignment (Qt::AlignRight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto gd = new QStandardItem {grid};
 | 
					 | 
				
			||||||
    gd->setTextAlignment (Qt::AlignRight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto pwr = new QStandardItem {QString::number (power)};
 | 
					 | 
				
			||||||
    pwr->setData (power);
 | 
					 | 
				
			||||||
    pwr->setTextAlignment (Qt::AlignRight);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    QList<QStandardItem *> row {
 | 
					 | 
				
			||||||
      new QStandardItem {client_id}, time_item, snr_item, dt, freq, dri, new QStandardItem {callsign}, gd, pwr};
 | 
					 | 
				
			||||||
    Q_FOREACH (auto& item, row)
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        item->setEditable (false);
 | 
					 | 
				
			||||||
        item->setFont (text_font_);
 | 
					 | 
				
			||||||
        item->setTextAlignment (item->textAlignment () | Qt::AlignVCenter);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    return row;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Q_SLOT void clear_decodes (QString const& client_id)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    for (auto row = rowCount () - 1; row >= 0; --row)
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        if (data (index (row, 0)).toString () == client_id)
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            removeRow (row);
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
  QFont text_font_;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ClientWidget
 | 
					 | 
				
			||||||
  : public QDockWidget
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  Q_OBJECT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
  explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
 | 
					 | 
				
			||||||
                         , QString const& id, QWidget * parent = 0)
 | 
					 | 
				
			||||||
    : QDockWidget {id, parent}
 | 
					 | 
				
			||||||
    , id_ {id}
 | 
					 | 
				
			||||||
    , decodes_table_view_ {new QTableView}
 | 
					 | 
				
			||||||
    , beacons_table_view_ {new QTableView}
 | 
					 | 
				
			||||||
    , message_line_edit_ {new QLineEdit}
 | 
					 | 
				
			||||||
    , decodes_stack_ {new QStackedLayout}
 | 
					 | 
				
			||||||
    , auto_off_button_ {new QPushButton {tr ("&Auto Off")}}
 | 
					 | 
				
			||||||
    , halt_tx_button_ {new QPushButton {tr ("&Halt Tx")}}
 | 
					 | 
				
			||||||
    , mode_label_ {new QLabel}
 | 
					 | 
				
			||||||
    , dx_call_label_ {new QLabel}
 | 
					 | 
				
			||||||
    , frequency_label_ {new QLabel}
 | 
					 | 
				
			||||||
    , report_label_ {new QLabel}
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    // set up widgets
 | 
					 | 
				
			||||||
    auto decodes_proxy_model = new IdFilterModel {id, this};
 | 
					 | 
				
			||||||
    decodes_proxy_model->setSourceModel (decodes_model);
 | 
					 | 
				
			||||||
    decodes_table_view_->setModel (decodes_proxy_model);
 | 
					 | 
				
			||||||
    decodes_table_view_->verticalHeader ()->hide ();
 | 
					 | 
				
			||||||
    decodes_table_view_->hideColumn (0);
 | 
					 | 
				
			||||||
    decodes_table_view_->horizontalHeader ()->setStretchLastSection (true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto form_layout = new QFormLayout;
 | 
					 | 
				
			||||||
    form_layout->addRow (tr ("Free text:"), message_line_edit_);
 | 
					 | 
				
			||||||
    message_line_edit_->setValidator (new QRegExpValidator {message_alphabet, this});
 | 
					 | 
				
			||||||
    connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) {
 | 
					 | 
				
			||||||
        Q_EMIT do_free_text (id_, text, false);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    connect (message_line_edit_, &QLineEdit::editingFinished, [this] () {
 | 
					 | 
				
			||||||
        Q_EMIT do_free_text (id_, message_line_edit_->text (), true);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto decodes_page = new QWidget;
 | 
					 | 
				
			||||||
    auto decodes_layout = new QVBoxLayout {decodes_page};
 | 
					 | 
				
			||||||
    decodes_layout->setContentsMargins (QMargins {2, 2, 2, 2});
 | 
					 | 
				
			||||||
    decodes_layout->addWidget (decodes_table_view_);
 | 
					 | 
				
			||||||
    decodes_layout->addLayout (form_layout);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto beacons_proxy_model = new IdFilterModel {id, this};
 | 
					 | 
				
			||||||
    beacons_proxy_model->setSourceModel (beacons_model);
 | 
					 | 
				
			||||||
    beacons_table_view_->setModel (beacons_proxy_model);
 | 
					 | 
				
			||||||
    beacons_table_view_->verticalHeader ()->hide ();
 | 
					 | 
				
			||||||
    beacons_table_view_->hideColumn (0);
 | 
					 | 
				
			||||||
    beacons_table_view_->horizontalHeader ()->setStretchLastSection (true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto beacons_page = new QWidget;
 | 
					 | 
				
			||||||
    auto beacons_layout = new QVBoxLayout {beacons_page};
 | 
					 | 
				
			||||||
    beacons_layout->setContentsMargins (QMargins {2, 2, 2, 2});
 | 
					 | 
				
			||||||
    beacons_layout->addWidget (beacons_table_view_);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    decodes_stack_->addWidget (decodes_page);
 | 
					 | 
				
			||||||
    decodes_stack_->addWidget (beacons_page);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // stack alternative views
 | 
					 | 
				
			||||||
    auto content_layout = new QVBoxLayout;
 | 
					 | 
				
			||||||
    content_layout->setContentsMargins (QMargins {2, 2, 2, 2});
 | 
					 | 
				
			||||||
    content_layout->addLayout (decodes_stack_);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // set up controls
 | 
					 | 
				
			||||||
    auto control_button_box = new QDialogButtonBox;
 | 
					 | 
				
			||||||
    control_button_box->addButton (auto_off_button_, QDialogButtonBox::ActionRole);
 | 
					 | 
				
			||||||
    control_button_box->addButton (halt_tx_button_, QDialogButtonBox::ActionRole);
 | 
					 | 
				
			||||||
    connect (auto_off_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
 | 
					 | 
				
			||||||
        Q_EMIT do_halt_tx (id_, true);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
 | 
					 | 
				
			||||||
        Q_EMIT do_halt_tx (id_, false);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    content_layout->addWidget (control_button_box);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // set up status area
 | 
					 | 
				
			||||||
    auto status_bar = new QStatusBar;
 | 
					 | 
				
			||||||
    status_bar->addPermanentWidget (mode_label_);
 | 
					 | 
				
			||||||
    status_bar->addPermanentWidget (dx_call_label_);
 | 
					 | 
				
			||||||
    status_bar->addPermanentWidget (frequency_label_);
 | 
					 | 
				
			||||||
    status_bar->addPermanentWidget (report_label_);
 | 
					 | 
				
			||||||
    content_layout->addWidget (status_bar);
 | 
					 | 
				
			||||||
    connect (this, &ClientWidget::topLevelChanged, status_bar, &QStatusBar::setSizeGripEnabled);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // set up central widget
 | 
					 | 
				
			||||||
    auto content_widget = new QFrame;
 | 
					 | 
				
			||||||
    content_widget->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken);
 | 
					 | 
				
			||||||
    content_widget->setLayout (content_layout);
 | 
					 | 
				
			||||||
    setWidget (content_widget);
 | 
					 | 
				
			||||||
    // setMinimumSize (QSize {550, 0});
 | 
					 | 
				
			||||||
    setFeatures (DockWidgetMovable | DockWidgetFloatable);
 | 
					 | 
				
			||||||
    setAllowedAreas (Qt::BottomDockWidgetArea);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // connect up table view signals
 | 
					 | 
				
			||||||
    connect (decodes_table_view_, &QTableView::doubleClicked, this, [this, decodes_proxy_model] (QModelIndex const& index) {
 | 
					 | 
				
			||||||
        Q_EMIT do_reply (decodes_proxy_model->mapToSource (index));
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
 | 
					 | 
				
			||||||
                             , QString const& report, QString const& tx_mode, bool tx_enabled
 | 
					 | 
				
			||||||
                             , bool transmitting, bool decoding)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    if (id == id_)
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        mode_label_->setText (QString {"Mode: %1%2"}
 | 
					 | 
				
			||||||
           .arg (mode)
 | 
					 | 
				
			||||||
           .arg (tx_mode.isEmpty () || tx_mode == mode ? "" : '(' + tx_mode + ')'));
 | 
					 | 
				
			||||||
        dx_call_label_->setText ("DX CALL: " + dx_call);
 | 
					 | 
				
			||||||
        frequency_label_->setText ("QRG: " + Radio::pretty_frequency_MHz_string (f));
 | 
					 | 
				
			||||||
        report_label_->setText ("SNR: " + report);
 | 
					 | 
				
			||||||
        update_dynamic_property (frequency_label_, "transmitting", transmitting);
 | 
					 | 
				
			||||||
        auto_off_button_->setEnabled (tx_enabled);
 | 
					 | 
				
			||||||
        halt_tx_button_->setEnabled (transmitting);
 | 
					 | 
				
			||||||
        update_dynamic_property (mode_label_, "decoding", decoding);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Q_SLOT void decode_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/
 | 
					 | 
				
			||||||
      , float /*delta_time*/, quint32 /*delta_frequency*/, QString const& /*mode*/
 | 
					 | 
				
			||||||
      , QString const& /*message*/)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    if (client_id == id_)
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        decodes_stack_->setCurrentIndex (0);
 | 
					 | 
				
			||||||
        decodes_table_view_->resizeColumnsToContents ();
 | 
					 | 
				
			||||||
        decodes_table_view_->scrollToBottom ();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Q_SLOT void beacon_spot_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/
 | 
					 | 
				
			||||||
      , float /*delta_time*/, Frequency /*delta_frequency*/, qint32 /*drift*/, QString const& /*callsign*/
 | 
					 | 
				
			||||||
      , QString const& /*grid*/, qint32 /*power*/)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    if (client_id == id_)
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        decodes_stack_->setCurrentIndex (1);
 | 
					 | 
				
			||||||
        beacons_table_view_->resizeColumnsToContents ();
 | 
					 | 
				
			||||||
        beacons_table_view_->scrollToBottom ();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Q_SIGNAL void do_reply (QModelIndex const&);
 | 
					 | 
				
			||||||
  Q_SIGNAL void do_halt_tx (QString const& id, bool auto_only);
 | 
					 | 
				
			||||||
  Q_SIGNAL void do_free_text (QString const& id, QString const& text, bool);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
  class IdFilterModel final
 | 
					 | 
				
			||||||
    : public QSortFilterProxyModel
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
  public:
 | 
					 | 
				
			||||||
    IdFilterModel (QString const& id, QObject * parent = nullptr)
 | 
					 | 
				
			||||||
      : QSortFilterProxyModel {parent}
 | 
					 | 
				
			||||||
      , id_ {id}
 | 
					 | 
				
			||||||
    {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  protected:
 | 
					 | 
				
			||||||
    bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      auto source_index_col0 = sourceModel ()->index (source_row, 0, source_parent);
 | 
					 | 
				
			||||||
      return sourceModel ()->data (source_index_col0).toString () == id_;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private:
 | 
					 | 
				
			||||||
    QString id_;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  QString id_;
 | 
					 | 
				
			||||||
  QTableView * decodes_table_view_;
 | 
					 | 
				
			||||||
  QTableView * beacons_table_view_;
 | 
					 | 
				
			||||||
  QLineEdit * message_line_edit_;
 | 
					 | 
				
			||||||
  QStackedLayout * decodes_stack_;
 | 
					 | 
				
			||||||
  QAbstractButton * auto_off_button_;
 | 
					 | 
				
			||||||
  QAbstractButton * halt_tx_button_;
 | 
					 | 
				
			||||||
  QLabel * mode_label_;
 | 
					 | 
				
			||||||
  QLabel * dx_call_label_;
 | 
					 | 
				
			||||||
  QLabel * frequency_label_;
 | 
					 | 
				
			||||||
  QLabel * report_label_;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MainWindow
 | 
					 | 
				
			||||||
  : public QMainWindow
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  Q_OBJECT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
  MainWindow ()
 | 
					 | 
				
			||||||
    : log_ {new QStandardItemModel {0, 10, this}}
 | 
					 | 
				
			||||||
    , decodes_model_ {new DecodesModel {this}}
 | 
					 | 
				
			||||||
    , beacons_model_ {new BeaconsModel {this}}
 | 
					 | 
				
			||||||
    , server_ {new MessageServer {this}}
 | 
					 | 
				
			||||||
    , multicast_group_line_edit_ {new QLineEdit}
 | 
					 | 
				
			||||||
    , log_table_view_ {new QTableView}
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    // logbook
 | 
					 | 
				
			||||||
    log_->setHeaderData (0, Qt::Horizontal, tr ("Date/Time"));
 | 
					 | 
				
			||||||
    log_->setHeaderData (1, Qt::Horizontal, tr ("Callsign"));
 | 
					 | 
				
			||||||
    log_->setHeaderData (2, Qt::Horizontal, tr ("Grid"));
 | 
					 | 
				
			||||||
    log_->setHeaderData (3, Qt::Horizontal, tr ("Name"));
 | 
					 | 
				
			||||||
    log_->setHeaderData (4, Qt::Horizontal, tr ("Frequency"));
 | 
					 | 
				
			||||||
    log_->setHeaderData (5, Qt::Horizontal, tr ("Mode"));
 | 
					 | 
				
			||||||
    log_->setHeaderData (6, Qt::Horizontal, tr ("Sent"));
 | 
					 | 
				
			||||||
    log_->setHeaderData (7, Qt::Horizontal, tr ("Rec'd"));
 | 
					 | 
				
			||||||
    log_->setHeaderData (8, Qt::Horizontal, tr ("Power"));
 | 
					 | 
				
			||||||
    log_->setHeaderData (9, Qt::Horizontal, tr ("Comments"));
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::qso_logged, this, &MainWindow::log_qso);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // menu bar
 | 
					 | 
				
			||||||
    auto file_menu = menuBar ()->addMenu (tr ("&File"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto exit_action = new QAction {tr ("E&xit"), this};
 | 
					 | 
				
			||||||
    exit_action->setShortcuts (QKeySequence::Quit);
 | 
					 | 
				
			||||||
    exit_action->setToolTip (tr ("Exit the application"));
 | 
					 | 
				
			||||||
    file_menu->addAction (exit_action);
 | 
					 | 
				
			||||||
    connect (exit_action, &QAction::triggered, this, &MainWindow::close);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    view_menu_ = menuBar ()->addMenu (tr ("&View"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // central layout
 | 
					 | 
				
			||||||
    auto central_layout = new QVBoxLayout;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // server details
 | 
					 | 
				
			||||||
    auto port_spin_box = new QSpinBox;
 | 
					 | 
				
			||||||
    port_spin_box->setMinimum (1);
 | 
					 | 
				
			||||||
    port_spin_box->setMaximum (std::numeric_limits<port_type>::max ());
 | 
					 | 
				
			||||||
    auto group_box_layout = new QFormLayout;
 | 
					 | 
				
			||||||
    group_box_layout->addRow (tr ("Port number:"), port_spin_box);
 | 
					 | 
				
			||||||
    group_box_layout->addRow (tr ("Multicast Group (blank for unicast server):"), multicast_group_line_edit_);
 | 
					 | 
				
			||||||
    auto group_box = new QGroupBox {tr ("Server Details")};
 | 
					 | 
				
			||||||
    group_box->setLayout (group_box_layout);
 | 
					 | 
				
			||||||
    central_layout->addWidget (group_box);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    log_table_view_->setModel (log_);
 | 
					 | 
				
			||||||
    log_table_view_->verticalHeader ()->hide ();
 | 
					 | 
				
			||||||
    central_layout->addWidget (log_table_view_);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // central widget
 | 
					 | 
				
			||||||
    auto central_widget = new QWidget;
 | 
					 | 
				
			||||||
    central_widget->setLayout (central_layout);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // main window setup
 | 
					 | 
				
			||||||
    setCentralWidget (central_widget);
 | 
					 | 
				
			||||||
    setDockOptions (AnimatedDocks | AllowNestedDocks | AllowTabbedDocks);
 | 
					 | 
				
			||||||
    setTabPosition (Qt::BottomDockWidgetArea, QTabWidget::North);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // connect up server
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::error, [this] (QString const& message) {
 | 
					 | 
				
			||||||
        QMessageBox::warning (this, tr ("Network Error"), message);
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::client_opened, this, &MainWindow::add_client);
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::client_closed, this, &MainWindow::remove_client);
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::client_closed, decodes_model_, &DecodesModel::clear_decodes);
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::client_closed, beacons_model_, &BeaconsModel::clear_decodes);
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::decode, decodes_model_, &DecodesModel::add_decode);
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::WSPR_decode, beacons_model_, &BeaconsModel::add_beacon_spot);
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::clear_decodes, decodes_model_, &DecodesModel::clear_decodes);
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::clear_decodes, beacons_model_, &BeaconsModel::clear_decodes);
 | 
					 | 
				
			||||||
    connect (decodes_model_, &DecodesModel::reply, server_, &MessageServer::reply);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // UI behaviour
 | 
					 | 
				
			||||||
    connect (port_spin_box, static_cast<void (QSpinBox::*)(int)> (&QSpinBox::valueChanged)
 | 
					 | 
				
			||||||
             , [this] (port_type port) {server_->start (port);});
 | 
					 | 
				
			||||||
    connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this, port_spin_box] () {
 | 
					 | 
				
			||||||
        server_->start (port_spin_box->value (), QHostAddress {multicast_group_line_edit_->text ()});
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    port_spin_box->setValue (2237); // start up in unicast mode
 | 
					 | 
				
			||||||
    show ();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Q_SLOT void log_qso (QString const& /*id*/, QDateTime time, QString const& dx_call, QString const& dx_grid
 | 
					 | 
				
			||||||
                       , Frequency dial_frequency, QString const& mode, QString const& report_sent
 | 
					 | 
				
			||||||
                       , QString const& report_received, QString const& tx_power, QString const& comments
 | 
					 | 
				
			||||||
                       , QString const& name)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    QList<QStandardItem *> row;
 | 
					 | 
				
			||||||
    row << new QStandardItem {time.toString ("dd-MMM-yyyy hh:mm")}
 | 
					 | 
				
			||||||
        << new QStandardItem {dx_call}
 | 
					 | 
				
			||||||
        << new QStandardItem {dx_grid}
 | 
					 | 
				
			||||||
        << new QStandardItem {name}
 | 
					 | 
				
			||||||
        << new QStandardItem {Radio::frequency_MHz_string (dial_frequency)}
 | 
					 | 
				
			||||||
        << new QStandardItem {mode}
 | 
					 | 
				
			||||||
        << new QStandardItem {report_sent}
 | 
					 | 
				
			||||||
        << new QStandardItem {report_received}
 | 
					 | 
				
			||||||
        << new QStandardItem {tx_power}
 | 
					 | 
				
			||||||
        << new QStandardItem {comments};
 | 
					 | 
				
			||||||
    log_->appendRow (row);
 | 
					 | 
				
			||||||
    log_table_view_->resizeColumnsToContents ();
 | 
					 | 
				
			||||||
    log_table_view_->horizontalHeader ()->setStretchLastSection (true);
 | 
					 | 
				
			||||||
    log_table_view_->scrollToBottom ();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
  void add_client (QString const& id)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    auto dock = new ClientWidget {decodes_model_, beacons_model_, id, this};
 | 
					 | 
				
			||||||
    dock->setAttribute (Qt::WA_DeleteOnClose);
 | 
					 | 
				
			||||||
    auto view_action = dock->toggleViewAction ();
 | 
					 | 
				
			||||||
    view_action->setEnabled (true);
 | 
					 | 
				
			||||||
    view_menu_->addAction (view_action);
 | 
					 | 
				
			||||||
    addDockWidget (Qt::BottomDockWidgetArea, dock);
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::status_update, dock, &ClientWidget::update_status);
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::decode, dock, &ClientWidget::decode_added);
 | 
					 | 
				
			||||||
    connect (server_, &MessageServer::WSPR_decode, dock, &ClientWidget::beacon_spot_added);
 | 
					 | 
				
			||||||
    connect (dock, &ClientWidget::do_reply, decodes_model_, &DecodesModel::do_reply);
 | 
					 | 
				
			||||||
    connect (dock, &ClientWidget::do_halt_tx, server_, &MessageServer::halt_tx);
 | 
					 | 
				
			||||||
    connect (dock, &ClientWidget::do_free_text, server_, &MessageServer::free_text);
 | 
					 | 
				
			||||||
    connect (view_action, &QAction::toggled, dock, &ClientWidget::setVisible);
 | 
					 | 
				
			||||||
    dock_widgets_[id] = dock;
 | 
					 | 
				
			||||||
    server_->replay (id);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void remove_client (QString const& id)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    auto iter = dock_widgets_.find (id);
 | 
					 | 
				
			||||||
    if (iter != std::end (dock_widgets_))
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        (*iter)->close ();
 | 
					 | 
				
			||||||
        dock_widgets_.erase (iter);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  QStandardItemModel * log_;
 | 
					 | 
				
			||||||
  QMenu * view_menu_;
 | 
					 | 
				
			||||||
  DecodesModel * decodes_model_;
 | 
					 | 
				
			||||||
  BeaconsModel * beacons_model_;
 | 
					 | 
				
			||||||
  MessageServer * server_;
 | 
					 | 
				
			||||||
  QLineEdit * multicast_group_line_edit_;
 | 
					 | 
				
			||||||
  QTableView * log_table_view_;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // maps client id to widgets
 | 
					 | 
				
			||||||
  QHash<QString, ClientWidget *> dock_widgets_;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "MessageAggregator.moc"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int main (int argc, char * argv[])
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  QApplication app {argc, argv};
 | 
					 | 
				
			||||||
  try
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      QObject::connect (&app, SIGNAL (lastWindowClosed ()), &app, SLOT (quit ()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      app.setApplicationName ("WSJT-X Reference UDP Message Aggregator Server");
 | 
					 | 
				
			||||||
      app.setApplicationVersion ("1.0");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        QFile file {":/qss/default.qss"};
 | 
					 | 
				
			||||||
        if (!file.open (QFile::ReadOnly))
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            throw_qstring ("failed to open \"" + file.fileName () + "\": " + file.errorString ());
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        app.setStyleSheet (file.readAll());
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      MainWindow window;
 | 
					 | 
				
			||||||
      return app.exec ();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  catch (std::exception const & e)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      QMessageBox::critical (nullptr, app.applicationName (), e.what ());
 | 
					 | 
				
			||||||
      std:: cerr << "Error: " << e.what () << '\n';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  catch (...)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      QMessageBox::critical (nullptr, app.applicationName (), QObject::tr ("Unexpected error"));
 | 
					 | 
				
			||||||
      std:: cerr << "Unexpected error\n";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  return -1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -331,14 +331,17 @@ void MessageClient::send_raw_datagram (QByteArray const& message, QHostAddress c
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void MessageClient::status_update (Frequency f, QString const& mode, QString const& dx_call
 | 
					void MessageClient::status_update (Frequency f, QString const& mode, QString const& dx_call
 | 
				
			||||||
                                   , QString const& report, QString const& tx_mode
 | 
					                                   , QString const& report, QString const& tx_mode
 | 
				
			||||||
                                   , bool tx_enabled, bool transmitting, bool decoding)
 | 
					                                   , bool tx_enabled, bool transmitting, bool decoding
 | 
				
			||||||
 | 
					                                   , qint32 rx_df, qint32 tx_df, QString const& de_call
 | 
				
			||||||
 | 
					                                   , QString const& de_grid, QString const& dx_grid)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  if (m_->server_port_ && !m_->server_string_.isEmpty ())
 | 
					  if (m_->server_port_ && !m_->server_string_.isEmpty ())
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      QByteArray message;
 | 
					      QByteArray message;
 | 
				
			||||||
      NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_, m_->schema_};
 | 
					      NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_, m_->schema_};
 | 
				
			||||||
      out << f << mode.toUtf8 () << dx_call.toUtf8 () << report.toUtf8 () << tx_mode.toUtf8 ()
 | 
					      out << f << mode.toUtf8 () << dx_call.toUtf8 () << report.toUtf8 () << tx_mode.toUtf8 ()
 | 
				
			||||||
          << tx_enabled << transmitting << decoding;
 | 
					          << tx_enabled << transmitting << decoding << rx_df << tx_df << de_call.toUtf8 ()
 | 
				
			||||||
 | 
					          << de_grid.toUtf8 () << dx_grid.toUtf8 ();
 | 
				
			||||||
      m_->send_message (out, message);
 | 
					      m_->send_message (out, message);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -47,7 +47,9 @@ public:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // outgoing messages
 | 
					  // outgoing messages
 | 
				
			||||||
  Q_SLOT void status_update (Frequency, QString const& mode, QString const& dx_call, QString const& report
 | 
					  Q_SLOT void status_update (Frequency, QString const& mode, QString const& dx_call, QString const& report
 | 
				
			||||||
                             , QString const& tx_mode, bool tx_enabled, bool transmitting, bool decoding);
 | 
					                             , QString const& tx_mode, bool tx_enabled, bool transmitting, bool decoding
 | 
				
			||||||
 | 
					                             , qint32 rx_df, qint32 tx_df, QString const& de_call, QString const& de_grid
 | 
				
			||||||
 | 
					                             , QString const& dx_grid);
 | 
				
			||||||
  Q_SLOT void decode (bool is_new, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
 | 
					  Q_SLOT void decode (bool is_new, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
 | 
				
			||||||
                      , QString const& mode, QString const& message);
 | 
					                      , QString const& mode, QString const& message);
 | 
				
			||||||
  Q_SLOT void WSPR_decode (bool is_new, QTime time, qint32 snr, float delta_time, Frequency
 | 
					  Q_SLOT void WSPR_decode (bool is_new, QTime time, qint32 snr, float delta_time, Frequency
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@
 | 
				
			|||||||
#include <QTimer>
 | 
					#include <QTimer>
 | 
				
			||||||
#include <QHash>
 | 
					#include <QHash>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "Radio.hpp"
 | 
				
			||||||
#include "NetworkMessage.hpp"
 | 
					#include "NetworkMessage.hpp"
 | 
				
			||||||
#include "qt_helpers.hpp"
 | 
					#include "qt_helpers.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -25,6 +26,9 @@ public:
 | 
				
			|||||||
    , port_ {0u}
 | 
					    , port_ {0u}
 | 
				
			||||||
    , clock_ {new QTimer {this}}
 | 
					    , clock_ {new QTimer {this}}
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
 | 
					    // register the required types with Qt
 | 
				
			||||||
 | 
					    Radio::register_types ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    connect (this, &QIODevice::readyRead, this, &MessageServer::impl::pending_datagrams);
 | 
					    connect (this, &QIODevice::readyRead, this, &MessageServer::impl::pending_datagrams);
 | 
				
			||||||
    connect (this, static_cast<void (impl::*) (SocketError)> (&impl::error)
 | 
					    connect (this, static_cast<void (impl::*) (SocketError)> (&impl::error)
 | 
				
			||||||
             , [this] (SocketError /* e */)
 | 
					             , [this] (SocketError /* e */)
 | 
				
			||||||
@ -194,12 +198,20 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
 | 
				
			|||||||
                bool tx_enabled {false};
 | 
					                bool tx_enabled {false};
 | 
				
			||||||
                bool transmitting {false};
 | 
					                bool transmitting {false};
 | 
				
			||||||
                bool decoding {false};
 | 
					                bool decoding {false};
 | 
				
			||||||
                in >> f >> mode >> dx_call >> report >> tx_mode >> tx_enabled >> transmitting >> decoding;
 | 
					                qint32 rx_df {-1};
 | 
				
			||||||
 | 
					                qint32 tx_df {-1};
 | 
				
			||||||
 | 
					                QByteArray de_call;
 | 
				
			||||||
 | 
					                QByteArray de_grid;
 | 
				
			||||||
 | 
					                QByteArray dx_grid;
 | 
				
			||||||
 | 
					                in >> f >> mode >> dx_call >> report >> tx_mode >> tx_enabled >> transmitting >> decoding
 | 
				
			||||||
 | 
					                   >> rx_df >> tx_df >> de_call >> de_grid >> dx_grid;
 | 
				
			||||||
                if (check_status (in) != Fail)
 | 
					                if (check_status (in) != Fail)
 | 
				
			||||||
                  {
 | 
					                  {
 | 
				
			||||||
                    Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call)
 | 
					                    Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call)
 | 
				
			||||||
                                                 , QString::fromUtf8 (report), QString::fromUtf8 (tx_mode)
 | 
					                                                 , QString::fromUtf8 (report), QString::fromUtf8 (tx_mode)
 | 
				
			||||||
                                                 , tx_enabled, transmitting, decoding);
 | 
					                                                 , tx_enabled, transmitting, decoding, rx_df, tx_df
 | 
				
			||||||
 | 
					                                                 , QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid)
 | 
				
			||||||
 | 
					                                                 , QString::fromUtf8 (dx_grid));
 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              break;
 | 
					              break;
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@
 | 
				
			|||||||
#include <QDateTime>
 | 
					#include <QDateTime>
 | 
				
			||||||
#include <QHostAddress>
 | 
					#include <QHostAddress>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "udp_export.h"
 | 
				
			||||||
#include "Radio.hpp"
 | 
					#include "Radio.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "pimpl_h.hpp"
 | 
					#include "pimpl_h.hpp"
 | 
				
			||||||
@ -21,7 +22,7 @@ class QString;
 | 
				
			|||||||
// applications that use the Qt framework. Other applications should
 | 
					// applications that use the Qt framework. Other applications should
 | 
				
			||||||
// use this classes' implementation as a reference implementation.
 | 
					// use this classes' implementation as a reference implementation.
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
class MessageServer
 | 
					class UDP_EXPORT MessageServer
 | 
				
			||||||
  : public QObject
 | 
					  : public QObject
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  Q_OBJECT;
 | 
					  Q_OBJECT;
 | 
				
			||||||
@ -61,7 +62,8 @@ public:
 | 
				
			|||||||
  Q_SIGNAL void client_opened (QString const& id);
 | 
					  Q_SIGNAL void client_opened (QString const& id);
 | 
				
			||||||
  Q_SIGNAL void status_update (QString const& id, Frequency, QString const& mode, QString const& dx_call
 | 
					  Q_SIGNAL void status_update (QString const& id, Frequency, QString const& mode, QString const& dx_call
 | 
				
			||||||
                               , QString const& report, QString const& tx_mode, bool tx_enabled
 | 
					                               , QString const& report, QString const& tx_mode, bool tx_enabled
 | 
				
			||||||
                               , bool transmitting, bool decoding);
 | 
					                               , bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
 | 
				
			||||||
 | 
					                               , QString const& de_call, QString const& de_grid, QString const& dx_grid);
 | 
				
			||||||
  Q_SIGNAL void client_closed (QString const& id);
 | 
					  Q_SIGNAL void client_closed (QString const& id);
 | 
				
			||||||
  Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time
 | 
					  Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time
 | 
				
			||||||
                        , quint32 delta_frequency, QString const& mode, QString const& message);
 | 
					                        , quint32 delta_frequency, QString const& mode, QString const& message);
 | 
				
			||||||
@ -77,7 +79,7 @@ public:
 | 
				
			|||||||
  Q_SIGNAL void error (QString const&) const;
 | 
					  Q_SIGNAL void error (QString const&) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
  class impl;
 | 
					  class UDP_NO_EXPORT impl;
 | 
				
			||||||
  pimpl<impl> m_;
 | 
					  pimpl<impl> m_;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -23,22 +23,16 @@ QItemEditorFactory * item_editor_factory ()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void register_types ()
 | 
					void register_types ()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					  // types in Radio.hpp are registered in their own translation unit
 | 
				
			||||||
 | 
					  // as they are needed in the wsjtx_udp shared library too
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // we still have to register the fully qualified names of enum types
 | 
					  // we still have to register the fully qualified names of enum types
 | 
				
			||||||
  // used as signal/slot connection arguments since the new Qt 5.5
 | 
					  // used as signal/slot connection arguments since the new Qt 5.5
 | 
				
			||||||
  // Q_ENUM macro only seems to register the unqualified name
 | 
					  // Q_ENUM macro only seems to register the unqualified name
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  // Radio namespace
 | 
					  item_editor_factory ()->registerEditor (qMetaTypeId<Radio::Frequency> (), new QStandardItemEditorCreator<FrequencyLineEdit> ());
 | 
				
			||||||
  auto frequency_type_id = qRegisterMetaType<Radio::Frequency> ("Frequency");
 | 
					  //auto frequency_delta_type_id = qRegisterMetaType<Radio::FrequencyDelta> ("FrequencyDelta");
 | 
				
			||||||
  qRegisterMetaType<Radio::Frequencies> ("Frequencies");
 | 
					  item_editor_factory ()->registerEditor (qMetaTypeId<Radio::FrequencyDelta> (), new QStandardItemEditorCreator<FrequencyDeltaLineEdit> ());
 | 
				
			||||||
 | 
					 | 
				
			||||||
  // This is required to preserve v1.5 "frequencies" setting for
 | 
					 | 
				
			||||||
  // backwards compatibility, without it the setting gets trashed by
 | 
					 | 
				
			||||||
  // later versions.
 | 
					 | 
				
			||||||
  qRegisterMetaTypeStreamOperators<Radio::Frequencies> ("Frequencies");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  item_editor_factory ()->registerEditor (frequency_type_id, new QStandardItemEditorCreator<FrequencyLineEdit> ());
 | 
					 | 
				
			||||||
  auto frequency_delta_type_id = qRegisterMetaType<Radio::FrequencyDelta> ("FrequencyDelta");
 | 
					 | 
				
			||||||
  item_editor_factory ()->registerEditor (frequency_delta_type_id, new QStandardItemEditorCreator<FrequencyDeltaLineEdit> ());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Frequency list model
 | 
					  // Frequency list model
 | 
				
			||||||
  qRegisterMetaType<FrequencyList::Item> ("Item");
 | 
					  qRegisterMetaType<FrequencyList::Item> ("Item");
 | 
				
			||||||
 | 
				
			|||||||
@ -114,6 +114,11 @@
 | 
				
			|||||||
 *                         Tx Enabled             bool
 | 
					 *                         Tx Enabled             bool
 | 
				
			||||||
 *                         Transmitting           bool
 | 
					 *                         Transmitting           bool
 | 
				
			||||||
 *                         Decoding               bool
 | 
					 *                         Decoding               bool
 | 
				
			||||||
 | 
					 *                         Rx DF                  qint32
 | 
				
			||||||
 | 
					 *                         Tx DF                  qint32
 | 
				
			||||||
 | 
					 *                         DE call                utf8
 | 
				
			||||||
 | 
					 *                         DE grid                utf8
 | 
				
			||||||
 | 
					 *                         DX grid                utf8
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 *    WSJT-X  sends this  status message  when various  internal state
 | 
					 *    WSJT-X  sends this  status message  when various  internal state
 | 
				
			||||||
 *    changes to allow the server to  track the relevant state of each
 | 
					 *    changes to allow the server to  track the relevant state of each
 | 
				
			||||||
@ -129,7 +134,11 @@
 | 
				
			|||||||
 *      Changes to the "Rpt" spinner,
 | 
					 *      Changes to the "Rpt" spinner,
 | 
				
			||||||
 *      After an old decodes replay sequence (see Replay below),
 | 
					 *      After an old decodes replay sequence (see Replay below),
 | 
				
			||||||
 *      When switching between Tx and Rx mode,
 | 
					 *      When switching between Tx and Rx mode,
 | 
				
			||||||
 *      At the start and end of decoding.
 | 
					 *      At the start and end of decoding,
 | 
				
			||||||
 | 
					 *      When the Rx DF changes,
 | 
				
			||||||
 | 
					 *      When the Tx DF changes,
 | 
				
			||||||
 | 
					 *      When the DE call or grid changes (currently when settings are exited),
 | 
				
			||||||
 | 
					 *      When the DX call or grid changes.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Decode        Out       2                      quint32
 | 
					 * Decode        Out       2                      quint32
 | 
				
			||||||
 | 
				
			|||||||
@ -4,8 +4,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <QString>
 | 
					#include <QString>
 | 
				
			||||||
#include <QChar>
 | 
					#include <QChar>
 | 
				
			||||||
#include <QDebug>
 | 
					 | 
				
			||||||
#include <QDataStream>
 | 
					 | 
				
			||||||
#include <QRegularExpression>
 | 
					#include <QRegularExpression>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Radio
 | 
					namespace Radio
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										32
									
								
								Radio.hpp
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								Radio.hpp
									
									
									
									
									
								
							@ -1,8 +1,11 @@
 | 
				
			|||||||
#ifndef RADIO_HPP_
 | 
					#ifndef RADIO_HPP__
 | 
				
			||||||
#define RADIO_HPP_
 | 
					#define RADIO_HPP__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <QObject>
 | 
					#include <QObject>
 | 
				
			||||||
#include <QLocale>
 | 
					#include <QLocale>
 | 
				
			||||||
 | 
					#include <QList>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "udp_export.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class QVariant;
 | 
					class QVariant;
 | 
				
			||||||
class QString;
 | 
					class QString;
 | 
				
			||||||
@ -20,30 +23,35 @@ namespace Radio
 | 
				
			|||||||
  using Frequencies = QList<Frequency>;
 | 
					  using Frequencies = QList<Frequency>;
 | 
				
			||||||
  using FrequencyDelta = qint64;
 | 
					  using FrequencyDelta = qint64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //
 | 
				
			||||||
 | 
					  // Qt type registration
 | 
				
			||||||
 | 
					  //
 | 
				
			||||||
 | 
					  void UDP_NO_EXPORT register_types ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  //
 | 
					  //
 | 
				
			||||||
  // Frequency type conversion.
 | 
					  // Frequency type conversion.
 | 
				
			||||||
  //
 | 
					  //
 | 
				
			||||||
  //	QVariant argument is convertible to double and is assumed to
 | 
					  //	QVariant argument is convertible to double and is assumed to
 | 
				
			||||||
  //	be scaled by (10 ** -scale).
 | 
					  //	be scaled by (10 ** -scale).
 | 
				
			||||||
  //
 | 
					  //
 | 
				
			||||||
  Frequency frequency (QVariant const&, int scale, QLocale const& = QLocale ());
 | 
					  Frequency UDP_EXPORT frequency (QVariant const&, int scale, QLocale const& = QLocale ());
 | 
				
			||||||
  FrequencyDelta frequency_delta (QVariant const&, int scale, QLocale const& = QLocale ());
 | 
					  FrequencyDelta UDP_EXPORT frequency_delta (QVariant const&, int scale, QLocale const& = QLocale ());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  //
 | 
					  //
 | 
				
			||||||
  // Frequency type formatting
 | 
					  // Frequency type formatting
 | 
				
			||||||
  //
 | 
					  //
 | 
				
			||||||
  QString frequency_MHz_string (Frequency, QLocale const& = QLocale ());
 | 
					  QString UDP_EXPORT frequency_MHz_string (Frequency, QLocale const& = QLocale ());
 | 
				
			||||||
  QString frequency_MHz_string (FrequencyDelta, QLocale const& = QLocale ());
 | 
					  QString UDP_EXPORT frequency_MHz_string (FrequencyDelta, QLocale const& = QLocale ());
 | 
				
			||||||
  QString pretty_frequency_MHz_string (Frequency, QLocale const& = QLocale ());
 | 
					  QString UDP_EXPORT pretty_frequency_MHz_string (Frequency, QLocale const& = QLocale ());
 | 
				
			||||||
  QString pretty_frequency_MHz_string (double, int scale, QLocale const& = QLocale ());
 | 
					  QString UDP_EXPORT pretty_frequency_MHz_string (double, int scale, QLocale const& = QLocale ());
 | 
				
			||||||
  QString pretty_frequency_MHz_string (FrequencyDelta, QLocale const& = QLocale ());
 | 
					  QString UDP_EXPORT pretty_frequency_MHz_string (FrequencyDelta, QLocale const& = QLocale ());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  //
 | 
					  //
 | 
				
			||||||
  // Callsigns
 | 
					  // Callsigns
 | 
				
			||||||
  //
 | 
					  //
 | 
				
			||||||
  bool is_callsign (QString const&);
 | 
					  bool UDP_EXPORT is_callsign (QString const&);
 | 
				
			||||||
  bool is_compound_callsign (QString const&);
 | 
					  bool UDP_EXPORT is_compound_callsign (QString const&);
 | 
				
			||||||
  QString base_callsign (QString);
 | 
					  QString UDP_EXPORT base_callsign (QString);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Q_DECLARE_METATYPE (Radio::Frequency);
 | 
					Q_DECLARE_METATYPE (Radio::Frequency);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										20
									
								
								RadioMetaType.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								RadioMetaType.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					#include "Radio.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QMetaType>
 | 
				
			||||||
 | 
					#include <QDebug>
 | 
				
			||||||
 | 
					#include <QDataStream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Radio
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  void register_types ()
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    qRegisterMetaType<Radio::Frequency> ("Frequency");
 | 
				
			||||||
 | 
					    qRegisterMetaType<Radio::FrequencyDelta> ("FrequencyDelta");
 | 
				
			||||||
 | 
					    qRegisterMetaType<Radio::Frequencies> ("Frequencies");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // This is required to preserve v1.5 "frequencies" setting for
 | 
				
			||||||
 | 
					    // backwards compatibility, without it the setting gets trashed
 | 
				
			||||||
 | 
					    // by later versions.
 | 
				
			||||||
 | 
					    qRegisterMetaTypeStreamOperators<Radio::Frequencies> ("Frequencies");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										125
									
								
								UDPExamples/BeaconsModel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								UDPExamples/BeaconsModel.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,125 @@
 | 
				
			|||||||
 | 
					#include "BeaconsModel.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QStandardItem>
 | 
				
			||||||
 | 
					#include <QFont>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  char const * const headings[] = {
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("BeaconsModel", "Client"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("BeaconsModel", "Time"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("BeaconsModel", "Snr"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("BeaconsModel", "DT"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("BeaconsModel", "Frequency"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("BeaconsModel", "Drift"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("BeaconsModel", "Callsign"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("BeaconsModel", "Grid"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("BeaconsModel", "Power"),
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QFont text_font {"Courier", 10};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time
 | 
				
			||||||
 | 
					                                   , Frequency frequency, qint32 drift, QString const& callsign
 | 
				
			||||||
 | 
					                                   , QString const& grid, qint32 power)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    auto time_item = new QStandardItem {time.toString ("hh:mm")};
 | 
				
			||||||
 | 
					    time_item->setData (time);
 | 
				
			||||||
 | 
					    time_item->setTextAlignment (Qt::AlignRight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto snr_item = new QStandardItem {QString::number (snr)};
 | 
				
			||||||
 | 
					    snr_item->setData (snr);
 | 
				
			||||||
 | 
					    snr_item->setTextAlignment (Qt::AlignRight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto dt = new QStandardItem {QString::number (delta_time)};
 | 
				
			||||||
 | 
					    dt->setData (delta_time);
 | 
				
			||||||
 | 
					    dt->setTextAlignment (Qt::AlignRight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto freq = new QStandardItem {Radio::pretty_frequency_MHz_string (frequency)};
 | 
				
			||||||
 | 
					    freq->setData (frequency);
 | 
				
			||||||
 | 
					    freq->setTextAlignment (Qt::AlignRight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto dri = new QStandardItem {QString::number (drift)};
 | 
				
			||||||
 | 
					    dri->setData (drift);
 | 
				
			||||||
 | 
					    dri->setTextAlignment (Qt::AlignRight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto gd = new QStandardItem {grid};
 | 
				
			||||||
 | 
					    gd->setTextAlignment (Qt::AlignRight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto pwr = new QStandardItem {QString::number (power)};
 | 
				
			||||||
 | 
					    pwr->setData (power);
 | 
				
			||||||
 | 
					    pwr->setTextAlignment (Qt::AlignRight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QList<QStandardItem *> row {
 | 
				
			||||||
 | 
					      new QStandardItem {client_id}, time_item, snr_item, dt, freq, dri, new QStandardItem {callsign}, gd, pwr};
 | 
				
			||||||
 | 
					    Q_FOREACH (auto& item, row)
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        item->setEditable (false);
 | 
				
			||||||
 | 
					        item->setFont (text_font);
 | 
				
			||||||
 | 
					        item->setTextAlignment (item->textAlignment () | Qt::AlignVCenter);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    return row;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BeaconsModel::BeaconsModel (QObject * parent)
 | 
				
			||||||
 | 
					  : QStandardItemModel {0, 9, parent}
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  int column {0};
 | 
				
			||||||
 | 
					  for (auto const& heading : headings)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      setHeaderData (column++, Qt::Horizontal, tr (heading));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
 | 
				
			||||||
 | 
					                                    , Frequency frequency, qint32 drift, QString const& callsign
 | 
				
			||||||
 | 
					                                    , QString const& grid, qint32 power)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (!is_new)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      int target_row {-1};
 | 
				
			||||||
 | 
					      for (auto row = 0; row < rowCount (); ++row)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          if (data (index (row, 0)).toString () == client_id)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              auto row_time = item (row, 1)->data ().toTime ();
 | 
				
			||||||
 | 
					              if (row_time == time
 | 
				
			||||||
 | 
					                  && item (row, 2)->data ().toInt () == snr
 | 
				
			||||||
 | 
					                  && item (row, 3)->data ().toFloat () == delta_time
 | 
				
			||||||
 | 
					                  && item (row, 4)->data ().value<Frequency> () == frequency
 | 
				
			||||||
 | 
					                  && data (index (row, 5)).toInt () == drift
 | 
				
			||||||
 | 
					                  && data (index (row, 6)).toString () == callsign
 | 
				
			||||||
 | 
					                  && data (index (row, 7)).toString () == grid
 | 
				
			||||||
 | 
					                  && data (index (row, 8)).toInt () == power)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              if (time <= row_time)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  target_row = row; // last row with same time
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      if (target_row >= 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power));
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  appendRow (make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BeaconsModel::clear_decodes (QString const& client_id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  for (auto row = rowCount () - 1; row >= 0; --row)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      if (data (index (row, 0)).toString () == client_id)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          removeRow (row);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "moc_BeaconsModel.cpp"
 | 
				
			||||||
							
								
								
									
										38
									
								
								UDPExamples/BeaconsModel.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								UDPExamples/BeaconsModel.hpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					#ifndef WSJTX_UDP_BEACONS_MODEL_HPP__
 | 
				
			||||||
 | 
					#define WSJTX_UDP_BEACONS_MODEL_HPP__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QStandardItemModel>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MessageServer.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using Frequency = MessageServer::Frequency;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QString;
 | 
				
			||||||
 | 
					class QTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Beacons Model - simple data model for all beacon spots
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The model is a basic table with uniform row format. Rows consist of
 | 
				
			||||||
 | 
					// QStandardItem instances containing the string representation of the
 | 
				
			||||||
 | 
					// column data  and if the underlying  field is not a  string then the
 | 
				
			||||||
 | 
					// UserRole+1 role contains the underlying data item.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Two slots are provided to add a new decode and remove all spots for
 | 
				
			||||||
 | 
					// a client.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					class BeaconsModel
 | 
				
			||||||
 | 
					  : public QStandardItemModel
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  explicit BeaconsModel (QObject * parent = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Q_SLOT void add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
 | 
				
			||||||
 | 
					                               , Frequency frequency, qint32 drift, QString const& callsign, QString const& grid
 | 
				
			||||||
 | 
					                               , qint32 power);
 | 
				
			||||||
 | 
					  Q_SLOT void clear_decodes (QString const& client_id);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										241
									
								
								UDPExamples/ClientWidget.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								UDPExamples/ClientWidget.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,241 @@
 | 
				
			|||||||
 | 
					#include "ClientWidget.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QRegExp>
 | 
				
			||||||
 | 
					#include <QColor>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  //QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"};
 | 
				
			||||||
 | 
					  QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>]*"};
 | 
				
			||||||
 | 
					  QRegularExpression cq_re {"[^A-Z0-9]*(CQ|QRZ)[^A-Z0-9]*"};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void update_dynamic_property (QWidget * widget, char const * property, QVariant const& value)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    widget->setProperty (property, value);
 | 
				
			||||||
 | 
					    widget->style ()->unpolish (widget);
 | 
				
			||||||
 | 
					    widget->style ()->polish (widget);
 | 
				
			||||||
 | 
					    widget->update ();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id)
 | 
				
			||||||
 | 
					  : client_id_ {client_id}
 | 
				
			||||||
 | 
					  , rx_df_ (-1)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QVariant ClientWidget::IdFilterModel::data (QModelIndex const& proxy_index, int role) const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (role == Qt::BackgroundRole)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      switch (proxy_index.column ())
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					        case 6:                 // message
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            auto message = QSortFilterProxyModel::data (proxy_index).toString ();
 | 
				
			||||||
 | 
					            if (base_call_re_.pattern ().size ()
 | 
				
			||||||
 | 
					                && message.contains (base_call_re_))
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                return QColor {255,200,200};
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            if (message.contains (cq_re))
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                return QColor {200, 255, 200};
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case 4:                 // DF
 | 
				
			||||||
 | 
					          if (qAbs (QSortFilterProxyModel::data (proxy_index).toInt () - rx_df_) <= 10)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              return QColor {255, 200, 200};
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  return QSortFilterProxyModel::data (proxy_index, role);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ClientWidget::IdFilterModel::filterAcceptsRow (int source_row
 | 
				
			||||||
 | 
					                                                    , QModelIndex const& source_parent) const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  auto source_index_col0 = sourceModel ()->index (source_row, 0, source_parent);
 | 
				
			||||||
 | 
					  return sourceModel ()->data (source_index_col0).toString () == client_id_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ClientWidget::IdFilterModel::de_call (QString const& call)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (call != call_)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      beginResetModel ();
 | 
				
			||||||
 | 
					      if (call.size ())
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          base_call_re_.setPattern ("[^A-Z0-9]*" + Radio::base_callsign (call) + "[^A-Z0-9]*");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          base_call_re_.setPattern (QString {});
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      call_ = call;
 | 
				
			||||||
 | 
					      endResetModel ();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ClientWidget::IdFilterModel::rx_df (int df)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (df != rx_df_)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      beginResetModel ();
 | 
				
			||||||
 | 
					      rx_df_ = df;
 | 
				
			||||||
 | 
					      endResetModel ();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
 | 
				
			||||||
 | 
					                            , QString const& id, QWidget * parent)
 | 
				
			||||||
 | 
					  : QDockWidget {id, parent}
 | 
				
			||||||
 | 
					  , id_ {id}
 | 
				
			||||||
 | 
					  , decodes_proxy_model_ {id_}
 | 
				
			||||||
 | 
					  , decodes_table_view_ {new QTableView}
 | 
				
			||||||
 | 
					  , beacons_table_view_ {new QTableView}
 | 
				
			||||||
 | 
					  , message_line_edit_ {new QLineEdit}
 | 
				
			||||||
 | 
					  , decodes_stack_ {new QStackedLayout}
 | 
				
			||||||
 | 
					  , auto_off_button_ {new QPushButton {tr ("&Auto Off")}}
 | 
				
			||||||
 | 
					  , halt_tx_button_ {new QPushButton {tr ("&Halt Tx")}}
 | 
				
			||||||
 | 
					  , mode_label_ {new QLabel}
 | 
				
			||||||
 | 
					  , frequency_label_ {new QLabel}
 | 
				
			||||||
 | 
					  , rx_df_label_ {new QLabel}
 | 
				
			||||||
 | 
					  , tx_df_label_ {new QLabel}
 | 
				
			||||||
 | 
					  , report_label_ {new QLabel}
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  // set up widgets
 | 
				
			||||||
 | 
					  decodes_proxy_model_.setSourceModel (decodes_model);
 | 
				
			||||||
 | 
					  decodes_table_view_->setModel (&decodes_proxy_model_);
 | 
				
			||||||
 | 
					  decodes_table_view_->verticalHeader ()->hide ();
 | 
				
			||||||
 | 
					  decodes_table_view_->hideColumn (0);
 | 
				
			||||||
 | 
					  decodes_table_view_->horizontalHeader ()->setStretchLastSection (true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto form_layout = new QFormLayout;
 | 
				
			||||||
 | 
					  form_layout->addRow (tr ("Free text:"), message_line_edit_);
 | 
				
			||||||
 | 
					  message_line_edit_->setValidator (new QRegExpValidator {message_alphabet, this});
 | 
				
			||||||
 | 
					  connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) {
 | 
				
			||||||
 | 
					      Q_EMIT do_free_text (id_, text, false);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  connect (message_line_edit_, &QLineEdit::editingFinished, [this] () {
 | 
				
			||||||
 | 
					      Q_EMIT do_free_text (id_, message_line_edit_->text (), true);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto decodes_page = new QWidget;
 | 
				
			||||||
 | 
					  auto decodes_layout = new QVBoxLayout {decodes_page};
 | 
				
			||||||
 | 
					  decodes_layout->setContentsMargins (QMargins {2, 2, 2, 2});
 | 
				
			||||||
 | 
					  decodes_layout->addWidget (decodes_table_view_);
 | 
				
			||||||
 | 
					  decodes_layout->addLayout (form_layout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto beacons_proxy_model = new IdFilterModel {id_};
 | 
				
			||||||
 | 
					  beacons_proxy_model->setSourceModel (beacons_model);
 | 
				
			||||||
 | 
					  beacons_table_view_->setModel (beacons_proxy_model);
 | 
				
			||||||
 | 
					  beacons_table_view_->verticalHeader ()->hide ();
 | 
				
			||||||
 | 
					  beacons_table_view_->hideColumn (0);
 | 
				
			||||||
 | 
					  beacons_table_view_->horizontalHeader ()->setStretchLastSection (true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto beacons_page = new QWidget;
 | 
				
			||||||
 | 
					  auto beacons_layout = new QVBoxLayout {beacons_page};
 | 
				
			||||||
 | 
					  beacons_layout->setContentsMargins (QMargins {2, 2, 2, 2});
 | 
				
			||||||
 | 
					  beacons_layout->addWidget (beacons_table_view_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  decodes_stack_->addWidget (decodes_page);
 | 
				
			||||||
 | 
					  decodes_stack_->addWidget (beacons_page);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // stack alternative views
 | 
				
			||||||
 | 
					  auto content_layout = new QVBoxLayout;
 | 
				
			||||||
 | 
					  content_layout->setContentsMargins (QMargins {2, 2, 2, 2});
 | 
				
			||||||
 | 
					  content_layout->addLayout (decodes_stack_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // set up controls
 | 
				
			||||||
 | 
					  auto control_button_box = new QDialogButtonBox;
 | 
				
			||||||
 | 
					  control_button_box->addButton (auto_off_button_, QDialogButtonBox::ActionRole);
 | 
				
			||||||
 | 
					  control_button_box->addButton (halt_tx_button_, QDialogButtonBox::ActionRole);
 | 
				
			||||||
 | 
					  connect (auto_off_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
 | 
				
			||||||
 | 
					      Q_EMIT do_halt_tx (id_, true);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
 | 
				
			||||||
 | 
					      Q_EMIT do_halt_tx (id_, false);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  content_layout->addWidget (control_button_box);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // set up status area
 | 
				
			||||||
 | 
					  auto status_bar = new QStatusBar;
 | 
				
			||||||
 | 
					  status_bar->addPermanentWidget (mode_label_);
 | 
				
			||||||
 | 
					  status_bar->addPermanentWidget (frequency_label_);
 | 
				
			||||||
 | 
					  status_bar->addPermanentWidget (rx_df_label_);
 | 
				
			||||||
 | 
					  status_bar->addPermanentWidget (tx_df_label_);
 | 
				
			||||||
 | 
					  status_bar->addPermanentWidget (report_label_);
 | 
				
			||||||
 | 
					  content_layout->addWidget (status_bar);
 | 
				
			||||||
 | 
					  connect (this, &ClientWidget::topLevelChanged, status_bar, &QStatusBar::setSizeGripEnabled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // set up central widget
 | 
				
			||||||
 | 
					  auto content_widget = new QFrame;
 | 
				
			||||||
 | 
					  content_widget->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken);
 | 
				
			||||||
 | 
					  content_widget->setLayout (content_layout);
 | 
				
			||||||
 | 
					  setWidget (content_widget);
 | 
				
			||||||
 | 
					  // setMinimumSize (QSize {550, 0});
 | 
				
			||||||
 | 
					  setFeatures (DockWidgetMovable | DockWidgetFloatable);
 | 
				
			||||||
 | 
					  setAllowedAreas (Qt::BottomDockWidgetArea);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // connect up table view signals
 | 
				
			||||||
 | 
					  connect (decodes_table_view_, &QTableView::doubleClicked, this, [this] (QModelIndex const& index) {
 | 
				
			||||||
 | 
					      Q_EMIT do_reply (decodes_proxy_model_.mapToSource (index));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ClientWidget::update_status (QString const& id, Frequency f, QString const& mode, QString const& /*dx_call*/
 | 
				
			||||||
 | 
					                                  , QString const& report, QString const& tx_mode, bool tx_enabled
 | 
				
			||||||
 | 
					                                  , bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
 | 
				
			||||||
 | 
					                                  , QString const& de_call, QString const& /*de_grid*/, QString const& /*dx_grid*/)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (id == id_)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      decodes_proxy_model_.de_call (de_call);
 | 
				
			||||||
 | 
					      decodes_proxy_model_.rx_df (rx_df);
 | 
				
			||||||
 | 
					      mode_label_->setText (QString {"Mode: %1%2"}
 | 
				
			||||||
 | 
					           .arg (mode)
 | 
				
			||||||
 | 
					           .arg (tx_mode.isEmpty () || tx_mode == mode ? "" : '(' + tx_mode + ')'));
 | 
				
			||||||
 | 
					        frequency_label_->setText ("QRG: " + Radio::pretty_frequency_MHz_string (f));
 | 
				
			||||||
 | 
					        rx_df_label_->setText (rx_df >= 0 ? QString {"Rx: %1"}.arg (rx_df) : "");
 | 
				
			||||||
 | 
					        tx_df_label_->setText (tx_df >= 0 ? QString {"Tx: %1"}.arg (tx_df) : "");
 | 
				
			||||||
 | 
					        report_label_->setText ("SNR: " + report);
 | 
				
			||||||
 | 
					        update_dynamic_property (frequency_label_, "transmitting", transmitting);
 | 
				
			||||||
 | 
					        auto_off_button_->setEnabled (tx_enabled);
 | 
				
			||||||
 | 
					        halt_tx_button_->setEnabled (transmitting);
 | 
				
			||||||
 | 
					        update_dynamic_property (mode_label_, "decoding", decoding);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/
 | 
				
			||||||
 | 
					                                 , float /*delta_time*/, quint32 /*delta_frequency*/, QString const& /*mode*/
 | 
				
			||||||
 | 
					                                 , QString const& /*message*/)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (client_id == id_)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      decodes_stack_->setCurrentIndex (0);
 | 
				
			||||||
 | 
					      decodes_table_view_->resizeColumnsToContents ();
 | 
				
			||||||
 | 
					      decodes_table_view_->scrollToBottom ();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/
 | 
				
			||||||
 | 
					                                      , float /*delta_time*/, Frequency /*delta_frequency*/, qint32 /*drift*/
 | 
				
			||||||
 | 
					                                      , QString const& /*callsign*/, QString const& /*grid*/, qint32 /*power*/)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (client_id == id_)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      decodes_stack_->setCurrentIndex (1);
 | 
				
			||||||
 | 
					      beacons_table_view_->resizeColumnsToContents ();
 | 
				
			||||||
 | 
					      beacons_table_view_->scrollToBottom ();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "moc_ClientWidget.cpp"
 | 
				
			||||||
							
								
								
									
										76
									
								
								UDPExamples/ClientWidget.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								UDPExamples/ClientWidget.hpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					#ifndef WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__
 | 
				
			||||||
 | 
					#define WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QObject>
 | 
				
			||||||
 | 
					#include <QSortFilterProxyModel>
 | 
				
			||||||
 | 
					#include <QString>
 | 
				
			||||||
 | 
					#include <QRegularExpression>
 | 
				
			||||||
 | 
					#include <QtWidgets>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MessageServer.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QAbstractItemModel;
 | 
				
			||||||
 | 
					class QModelIndex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using Frequency = MessageServer::Frequency;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ClientWidget
 | 
				
			||||||
 | 
					  : public QDockWidget
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
 | 
				
			||||||
 | 
					                         , QString const& id, QWidget * parent = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
 | 
				
			||||||
 | 
					                             , QString const& report, QString const& tx_mode, bool tx_enabled
 | 
				
			||||||
 | 
					                             , bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
 | 
				
			||||||
 | 
					                             , QString const& de_call, QString const& de_grid, QString const& dx_grid);
 | 
				
			||||||
 | 
					  Q_SLOT void decode_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/
 | 
				
			||||||
 | 
					                            , float /*delta_time*/, quint32 /*delta_frequency*/, QString const& /*mode*/
 | 
				
			||||||
 | 
					                            , QString const& /*message*/);
 | 
				
			||||||
 | 
					  Q_SLOT void beacon_spot_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/
 | 
				
			||||||
 | 
					                                 , float /*delta_time*/, Frequency /*delta_frequency*/, qint32 /*drift*/
 | 
				
			||||||
 | 
					                                 , QString const& /*callsign*/, QString const& /*grid*/, qint32 /*power*/);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Q_SIGNAL void do_reply (QModelIndex const&);
 | 
				
			||||||
 | 
					  Q_SIGNAL void do_halt_tx (QString const& id, bool auto_only);
 | 
				
			||||||
 | 
					  Q_SIGNAL void do_free_text (QString const& id, QString const& text, bool);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					  QString id_;
 | 
				
			||||||
 | 
					  class IdFilterModel final
 | 
				
			||||||
 | 
					    : public QSortFilterProxyModel
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					  public:
 | 
				
			||||||
 | 
					    IdFilterModel (QString const& client_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void de_call (QString const&);
 | 
				
			||||||
 | 
					    void rx_df (int);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QVariant data (QModelIndex const& proxy_index, int role = Qt::DisplayRole) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected:
 | 
				
			||||||
 | 
					    bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private:
 | 
				
			||||||
 | 
					    QString client_id_;
 | 
				
			||||||
 | 
					    QString call_;
 | 
				
			||||||
 | 
					    QRegularExpression base_call_re_;
 | 
				
			||||||
 | 
					    int rx_df_;
 | 
				
			||||||
 | 
					  } decodes_proxy_model_;
 | 
				
			||||||
 | 
					  QTableView * decodes_table_view_;
 | 
				
			||||||
 | 
					  QTableView * beacons_table_view_;
 | 
				
			||||||
 | 
					  QLineEdit * message_line_edit_;
 | 
				
			||||||
 | 
					  QStackedLayout * decodes_stack_;
 | 
				
			||||||
 | 
					  QAbstractButton * auto_off_button_;
 | 
				
			||||||
 | 
					  QAbstractButton * halt_tx_button_;
 | 
				
			||||||
 | 
					  QLabel * mode_label_;
 | 
				
			||||||
 | 
					  QLabel * frequency_label_;
 | 
				
			||||||
 | 
					  QLabel * rx_df_label_;
 | 
				
			||||||
 | 
					  QLabel * tx_df_label_;
 | 
				
			||||||
 | 
					  QLabel * report_label_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										127
									
								
								UDPExamples/DecodesModel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								UDPExamples/DecodesModel.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,127 @@
 | 
				
			|||||||
 | 
					#include "DecodesModel.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QStandardItem>
 | 
				
			||||||
 | 
					#include <QModelIndex>
 | 
				
			||||||
 | 
					#include <QTime>
 | 
				
			||||||
 | 
					#include <QString>
 | 
				
			||||||
 | 
					#include <QFont>
 | 
				
			||||||
 | 
					#include <QList>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  char const * const headings[] = {
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("DecodesModel", "Client"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("DecodesModel", "Time"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("DecodesModel", "Snr"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("DecodesModel", "DT"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("DecodesModel", "DF"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("DecodesModel", "Md"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("DecodesModel", "Message"),
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QFont text_font {"Courier", 10};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time
 | 
				
			||||||
 | 
					                                   , quint32 delta_frequency, QString const& mode, QString const& message)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    auto time_item = new QStandardItem {time.toString ("hh:mm")};
 | 
				
			||||||
 | 
					    time_item->setData (time);
 | 
				
			||||||
 | 
					    time_item->setTextAlignment (Qt::AlignRight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto snr_item = new QStandardItem {QString::number (snr)};
 | 
				
			||||||
 | 
					    snr_item->setData (snr);
 | 
				
			||||||
 | 
					    snr_item->setTextAlignment (Qt::AlignRight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto dt = new QStandardItem {QString::number (delta_time)};
 | 
				
			||||||
 | 
					    dt->setData (delta_time);
 | 
				
			||||||
 | 
					    dt->setTextAlignment (Qt::AlignRight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto df = new QStandardItem {QString::number (delta_frequency)};
 | 
				
			||||||
 | 
					    df->setData (delta_frequency);
 | 
				
			||||||
 | 
					    df->setTextAlignment (Qt::AlignRight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto md = new QStandardItem {mode};
 | 
				
			||||||
 | 
					    md->setTextAlignment (Qt::AlignHCenter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    QList<QStandardItem *> row {
 | 
				
			||||||
 | 
					      new QStandardItem {client_id}, time_item, snr_item, dt, df, md, new QStandardItem {message}};
 | 
				
			||||||
 | 
					    Q_FOREACH (auto& item, row)
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        item->setEditable (false);
 | 
				
			||||||
 | 
					        item->setFont (text_font);
 | 
				
			||||||
 | 
					        item->setTextAlignment (item->textAlignment () | Qt::AlignVCenter);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    return row;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DecodesModel::DecodesModel (QObject * parent)
 | 
				
			||||||
 | 
					  : QStandardItemModel {0, 7, parent}
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  int column {0};
 | 
				
			||||||
 | 
					  for (auto const& heading : headings)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      setHeaderData (column++, Qt::Horizontal, tr (heading));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
 | 
				
			||||||
 | 
					                               , quint32 delta_frequency, QString const& mode, QString const& message)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (!is_new)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      int target_row {-1};
 | 
				
			||||||
 | 
					      for (auto row = 0; row < rowCount (); ++row)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          if (data (index (row, 0)).toString () == client_id)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              auto row_time = item (row, 1)->data ().toTime ();
 | 
				
			||||||
 | 
					              if (row_time == time
 | 
				
			||||||
 | 
					                  && item (row, 2)->data ().toInt () == snr
 | 
				
			||||||
 | 
					                  && item (row, 3)->data ().toFloat () == delta_time
 | 
				
			||||||
 | 
					                  && item (row, 4)->data ().toUInt () == delta_frequency
 | 
				
			||||||
 | 
					                  && data (index (row, 5)).toString () == mode
 | 
				
			||||||
 | 
					                  && data (index (row, 6)).toString () == message)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              if (time <= row_time)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  target_row = row; // last row with same time
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      if (target_row >= 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, delta_frequency, mode, message));
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  appendRow (make_row (client_id, time, snr, delta_time, delta_frequency, mode, message));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DecodesModel::clear_decodes (QString const& client_id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  for (auto row = rowCount () - 1; row >= 0; --row)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      if (data (index (row, 0)).toString () == client_id)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          removeRow (row);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DecodesModel::do_reply (QModelIndex const& source)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  auto row = source.row ();
 | 
				
			||||||
 | 
					  Q_EMIT reply (data (index (row, 0)).toString ()
 | 
				
			||||||
 | 
					                , item (row, 1)->data ().toTime ()
 | 
				
			||||||
 | 
					                , item (row, 2)->data ().toInt ()
 | 
				
			||||||
 | 
					                , item (row, 3)->data ().toFloat ()
 | 
				
			||||||
 | 
					                , item (row, 4)->data ().toInt ()
 | 
				
			||||||
 | 
					                , data (index (row, 5)).toString ()
 | 
				
			||||||
 | 
					                , data (index (row, 6)).toString ());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "moc_DecodesModel.cpp"
 | 
				
			||||||
							
								
								
									
										43
									
								
								UDPExamples/DecodesModel.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								UDPExamples/DecodesModel.hpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					#ifndef WSJTX_UDP_DECODES_MODEL_HPP__
 | 
				
			||||||
 | 
					#define WSJTX_UDP_DECODES_MODEL_HPP__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QStandardItemModel>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MessageServer.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using Frequency = MessageServer::Frequency;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QTime;
 | 
				
			||||||
 | 
					class QString;
 | 
				
			||||||
 | 
					class QModelIndex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Decodes Model - simple data model for all decodes
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The model is a basic table with uniform row format. Rows consist of
 | 
				
			||||||
 | 
					// QStandardItem instances containing the string representation of the
 | 
				
			||||||
 | 
					// column data  and if the underlying  field is not a  string then the
 | 
				
			||||||
 | 
					// UserRole+1 role contains the underlying data item.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Three slots  are provided to add  a new decode, remove  all decodes
 | 
				
			||||||
 | 
					// for a client  and, to build a  reply to CQ message for  a given row
 | 
				
			||||||
 | 
					// which is emitted as a signal respectively.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					class DecodesModel
 | 
				
			||||||
 | 
					  : public QStandardItemModel
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  explicit DecodesModel (QObject * parent = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Q_SLOT void add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
 | 
				
			||||||
 | 
					                          , quint32 delta_frequency, QString const& mode, QString const& message);
 | 
				
			||||||
 | 
					  Q_SLOT void clear_decodes (QString const& client_id);
 | 
				
			||||||
 | 
					  Q_SLOT void do_reply (QModelIndex const& source);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Q_SIGNAL void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
 | 
				
			||||||
 | 
					                       , QString const& mode, QString const& message);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										90
									
								
								UDPExamples/MessageAggregator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								UDPExamples/MessageAggregator.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					// MessageAggregator - an example application that utilizes the WSJT-X
 | 
				
			||||||
 | 
					//                     messaging facility
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This  application is  only  provided as  a  simple GUI  application
 | 
				
			||||||
 | 
					// example to demonstrate the WSJT-X messaging facility. It allows the
 | 
				
			||||||
 | 
					// user to set  the server details either as a  unicast UDP server or,
 | 
				
			||||||
 | 
					// if a  multicast group address  is provided, as a  multicast server.
 | 
				
			||||||
 | 
					// The benefit of the multicast server is that multiple servers can be
 | 
				
			||||||
 | 
					// active at  once each  receiving all  WSJT-X broadcast  messages and
 | 
				
			||||||
 | 
					// each able to  respond to individual WSJT_X clients.  To utilize the
 | 
				
			||||||
 | 
					// multicast  group features  each  WSJT-X client  must  set the  same
 | 
				
			||||||
 | 
					// multicast  group address  as  the UDP  server  address for  example
 | 
				
			||||||
 | 
					// 239.255.0.0 for a site local multicast group.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The  UI is  a small  panel  to input  the service  port number  and
 | 
				
			||||||
 | 
					// optionally  the  multicast  group  address.   Below  that  a  table
 | 
				
			||||||
 | 
					// representing  the  log  entries   where  any  QSO  logged  messages
 | 
				
			||||||
 | 
					// broadcast  from WSJT-X  clients are  displayed. The  bottom of  the
 | 
				
			||||||
 | 
					// application main  window is a  dock area  where a dock  window will
 | 
				
			||||||
 | 
					// appear for each WSJT-X client, this  window contains a table of the
 | 
				
			||||||
 | 
					// current decode  messages broadcast  from that  WSJT-X client  and a
 | 
				
			||||||
 | 
					// status line showing  the status update messages  broadcast from the
 | 
				
			||||||
 | 
					// WSJT-X client. The dock windows may  be arranged in a tab bar, side
 | 
				
			||||||
 | 
					// by side,  below each  other or, completely  detached from  the dock
 | 
				
			||||||
 | 
					// area as floating windows. Double clicking the dock window title bar
 | 
				
			||||||
 | 
					// or  dragging and  dropping with  the mouse  allows these  different
 | 
				
			||||||
 | 
					// arrangements.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The application  also provides a  simple menu bar including  a view
 | 
				
			||||||
 | 
					// menu that allows each dock window to be hidden or revealed.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <clocale>
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <exception>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QFile>
 | 
				
			||||||
 | 
					#include <QApplication>
 | 
				
			||||||
 | 
					#include <QMessageBox>
 | 
				
			||||||
 | 
					#include <QObject>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MessageAggregatorMainWindow.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// deduce the size of an array
 | 
				
			||||||
 | 
					template<class T, size_t N>
 | 
				
			||||||
 | 
					inline
 | 
				
			||||||
 | 
					size_t size (T (&)[N]) {return N;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main (int argc, char * argv[])
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  QApplication app {argc, argv};
 | 
				
			||||||
 | 
					  try
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      setlocale (LC_NUMERIC, "C"); // ensure number forms are in
 | 
				
			||||||
 | 
					                                   // consistent format, do this after
 | 
				
			||||||
 | 
					                                   // instantiating QApplication so
 | 
				
			||||||
 | 
					                                   // that GUI has correct l18n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      app.setApplicationName ("WSJT-X Reference UDP Message Aggregator Server");
 | 
				
			||||||
 | 
					      app.setApplicationVersion ("1.0");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      QObject::connect (&app, SIGNAL (lastWindowClosed ()), &app, SLOT (quit ()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        QFile file {":/qss/default.qss"};
 | 
				
			||||||
 | 
					        if (!file.open (QFile::ReadOnly))
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            throw std::runtime_error {
 | 
				
			||||||
 | 
					              QString {"failed to open \"" + file.fileName () + "\": " + file.errorString ()}
 | 
				
			||||||
 | 
					              .toLocal8Bit ().constData ()};
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        app.setStyleSheet (file.readAll());
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      MessageAggregatorMainWindow window;
 | 
				
			||||||
 | 
					      return app.exec ();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  catch (std::exception const & e)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      QMessageBox::critical (nullptr, app.applicationName (), e.what ());
 | 
				
			||||||
 | 
					      std::cerr << "Error: " << e.what () << '\n';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  catch (...)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      QMessageBox::critical (nullptr, app.applicationName (), QObject::tr ("Unexpected error"));
 | 
				
			||||||
 | 
					      std::cerr << "Unexpected error\n";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  return -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										158
									
								
								UDPExamples/MessageAggregatorMainWindow.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								UDPExamples/MessageAggregatorMainWindow.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,158 @@
 | 
				
			|||||||
 | 
					#include "MessageAggregatorMainWindow.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QtWidgets>
 | 
				
			||||||
 | 
					#include <QDateTime>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "DecodesModel.hpp"
 | 
				
			||||||
 | 
					#include "BeaconsModel.hpp"
 | 
				
			||||||
 | 
					#include "ClientWidget.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using port_type = MessageServer::port_type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  char const * const headings[] = {
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Date/Time"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Callsign"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Grid"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Name"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Frequency"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Mode"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Sent"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Rec'd"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Power"),
 | 
				
			||||||
 | 
					    QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Comments"),
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
 | 
				
			||||||
 | 
					  : log_ {new QStandardItemModel {0, 10, this}}
 | 
				
			||||||
 | 
					  , decodes_model_ {new DecodesModel {this}}
 | 
				
			||||||
 | 
					  , beacons_model_ {new BeaconsModel {this}}
 | 
				
			||||||
 | 
					  , server_ {new MessageServer {this}}
 | 
				
			||||||
 | 
					  , multicast_group_line_edit_ {new QLineEdit}
 | 
				
			||||||
 | 
					  , log_table_view_ {new QTableView}
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  // logbook
 | 
				
			||||||
 | 
					  int column {0};
 | 
				
			||||||
 | 
					  for (auto const& heading : headings)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      log_->setHeaderData (column++, Qt::Horizontal, tr (heading));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::qso_logged, this, &MessageAggregatorMainWindow::log_qso);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // menu bar
 | 
				
			||||||
 | 
					  auto file_menu = menuBar ()->addMenu (tr ("&File"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  auto exit_action = new QAction {tr ("E&xit"), this};
 | 
				
			||||||
 | 
					  exit_action->setShortcuts (QKeySequence::Quit);
 | 
				
			||||||
 | 
					  exit_action->setToolTip (tr ("Exit the application"));
 | 
				
			||||||
 | 
					  file_menu->addAction (exit_action);
 | 
				
			||||||
 | 
					  connect (exit_action, &QAction::triggered, this, &MessageAggregatorMainWindow::close);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  view_menu_ = menuBar ()->addMenu (tr ("&View"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // central layout
 | 
				
			||||||
 | 
					  auto central_layout = new QVBoxLayout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // server details
 | 
				
			||||||
 | 
					  auto port_spin_box = new QSpinBox;
 | 
				
			||||||
 | 
					  port_spin_box->setMinimum (1);
 | 
				
			||||||
 | 
					  port_spin_box->setMaximum (std::numeric_limits<port_type>::max ());
 | 
				
			||||||
 | 
					  auto group_box_layout = new QFormLayout;
 | 
				
			||||||
 | 
					  group_box_layout->addRow (tr ("Port number:"), port_spin_box);
 | 
				
			||||||
 | 
					  group_box_layout->addRow (tr ("Multicast Group (blank for unicast server):"), multicast_group_line_edit_);
 | 
				
			||||||
 | 
					  auto group_box = new QGroupBox {tr ("Server Details")};
 | 
				
			||||||
 | 
					  group_box->setLayout (group_box_layout);
 | 
				
			||||||
 | 
					  central_layout->addWidget (group_box);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  log_table_view_->setModel (log_);
 | 
				
			||||||
 | 
					  log_table_view_->verticalHeader ()->hide ();
 | 
				
			||||||
 | 
					  central_layout->addWidget (log_table_view_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // central widget
 | 
				
			||||||
 | 
					  auto central_widget = new QWidget;
 | 
				
			||||||
 | 
					  central_widget->setLayout (central_layout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // main window setup
 | 
				
			||||||
 | 
					  setCentralWidget (central_widget);
 | 
				
			||||||
 | 
					  setDockOptions (AnimatedDocks | AllowNestedDocks | AllowTabbedDocks);
 | 
				
			||||||
 | 
					  setTabPosition (Qt::BottomDockWidgetArea, QTabWidget::North);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // connect up server
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::error, [this] (QString const& message) {
 | 
				
			||||||
 | 
					      QMessageBox::warning (this, tr ("Network Error"), message);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::client_opened, this, &MessageAggregatorMainWindow::add_client);
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::client_closed, this, &MessageAggregatorMainWindow::remove_client);
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::client_closed, decodes_model_, &DecodesModel::clear_decodes);
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::client_closed, beacons_model_, &BeaconsModel::clear_decodes);
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::decode, decodes_model_, &DecodesModel::add_decode);
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::WSPR_decode, beacons_model_, &BeaconsModel::add_beacon_spot);
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::clear_decodes, decodes_model_, &DecodesModel::clear_decodes);
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::clear_decodes, beacons_model_, &BeaconsModel::clear_decodes);
 | 
				
			||||||
 | 
					  connect (decodes_model_, &DecodesModel::reply, server_, &MessageServer::reply);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // UI behaviour
 | 
				
			||||||
 | 
					  connect (port_spin_box, static_cast<void (QSpinBox::*)(int)> (&QSpinBox::valueChanged)
 | 
				
			||||||
 | 
					           , [this] (port_type port) {server_->start (port);});
 | 
				
			||||||
 | 
					  connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this, port_spin_box] () {
 | 
				
			||||||
 | 
					      server_->start (port_spin_box->value (), QHostAddress {multicast_group_line_edit_->text ()});
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  port_spin_box->setValue (2237); // start up in unicast mode
 | 
				
			||||||
 | 
					  show ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time, QString const& dx_call, QString const& dx_grid
 | 
				
			||||||
 | 
					                                           , Frequency dial_frequency, QString const& mode, QString const& report_sent
 | 
				
			||||||
 | 
					                                           , QString const& report_received, QString const& tx_power, QString const& comments
 | 
				
			||||||
 | 
					                                           , QString const& name)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  QList<QStandardItem *> row;
 | 
				
			||||||
 | 
					  row << new QStandardItem {time.toString ("dd-MMM-yyyy hh:mm")}
 | 
				
			||||||
 | 
					  << new QStandardItem {dx_call}
 | 
				
			||||||
 | 
					  << new QStandardItem {dx_grid}
 | 
				
			||||||
 | 
					  << new QStandardItem {name}
 | 
				
			||||||
 | 
					  << new QStandardItem {Radio::frequency_MHz_string (dial_frequency)}
 | 
				
			||||||
 | 
					  << new QStandardItem {mode}
 | 
				
			||||||
 | 
					  << new QStandardItem {report_sent}
 | 
				
			||||||
 | 
					  << new QStandardItem {report_received}
 | 
				
			||||||
 | 
					  << new QStandardItem {tx_power}
 | 
				
			||||||
 | 
					  << new QStandardItem {comments};
 | 
				
			||||||
 | 
					  log_->appendRow (row);
 | 
				
			||||||
 | 
					  log_table_view_->resizeColumnsToContents ();
 | 
				
			||||||
 | 
					  log_table_view_->horizontalHeader ()->setStretchLastSection (true);
 | 
				
			||||||
 | 
					  log_table_view_->scrollToBottom ();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MessageAggregatorMainWindow::add_client (QString const& id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  auto dock = new ClientWidget {decodes_model_, beacons_model_, id, this};
 | 
				
			||||||
 | 
					  dock->setAttribute (Qt::WA_DeleteOnClose);
 | 
				
			||||||
 | 
					  auto view_action = dock->toggleViewAction ();
 | 
				
			||||||
 | 
					  view_action->setEnabled (true);
 | 
				
			||||||
 | 
					  view_menu_->addAction (view_action);
 | 
				
			||||||
 | 
					  addDockWidget (Qt::BottomDockWidgetArea, dock);
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::status_update, dock, &ClientWidget::update_status);
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::decode, dock, &ClientWidget::decode_added);
 | 
				
			||||||
 | 
					  connect (server_, &MessageServer::WSPR_decode, dock, &ClientWidget::beacon_spot_added);
 | 
				
			||||||
 | 
					  connect (dock, &ClientWidget::do_reply, decodes_model_, &DecodesModel::do_reply);
 | 
				
			||||||
 | 
					  connect (dock, &ClientWidget::do_halt_tx, server_, &MessageServer::halt_tx);
 | 
				
			||||||
 | 
					  connect (dock, &ClientWidget::do_free_text, server_, &MessageServer::free_text);
 | 
				
			||||||
 | 
					  connect (view_action, &QAction::toggled, dock, &ClientWidget::setVisible);
 | 
				
			||||||
 | 
					  dock_widgets_[id] = dock;
 | 
				
			||||||
 | 
					  server_->replay (id);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MessageAggregatorMainWindow::remove_client (QString const& id)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  auto iter = dock_widgets_.find (id);
 | 
				
			||||||
 | 
					  if (iter != std::end (dock_widgets_))
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      (*iter)->close ();
 | 
				
			||||||
 | 
					      dock_widgets_.erase (iter);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "moc_MessageAggregatorMainWindow.cpp"
 | 
				
			||||||
							
								
								
									
										51
									
								
								UDPExamples/MessageAggregatorMainWindow.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								UDPExamples/MessageAggregatorMainWindow.hpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					#ifndef WSJTX_MESSAGE_AGGREGATOR_MAIN_WINDOW_MODEL_HPP__
 | 
				
			||||||
 | 
					#define WSJTX_MESSAGE_AGGREGATOR_MAIN_WINDOW_MODEL_HPP__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QMainWindow>
 | 
				
			||||||
 | 
					#include <QHash>
 | 
				
			||||||
 | 
					#include <QString>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MessageServer.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QDateTime;
 | 
				
			||||||
 | 
					class QStandardItemModel;
 | 
				
			||||||
 | 
					class QMenu;
 | 
				
			||||||
 | 
					class DecodesModel;
 | 
				
			||||||
 | 
					class BeaconsModel;
 | 
				
			||||||
 | 
					class QLineEdit;
 | 
				
			||||||
 | 
					class QTableView;
 | 
				
			||||||
 | 
					class ClientWidget;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using Frequency = MessageServer::Frequency;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MessageAggregatorMainWindow
 | 
				
			||||||
 | 
					  : public QMainWindow
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					  MessageAggregatorMainWindow ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Q_SLOT void log_qso (QString const& /*id*/, QDateTime time, QString const& dx_call, QString const& dx_grid
 | 
				
			||||||
 | 
					                       , Frequency dial_frequency, QString const& mode, QString const& report_sent
 | 
				
			||||||
 | 
					                       , QString const& report_received, QString const& tx_power, QString const& comments
 | 
				
			||||||
 | 
					                       , QString const& name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					  void add_client (QString const& id);
 | 
				
			||||||
 | 
					  void remove_client (QString const& id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QStandardItemModel * log_;
 | 
				
			||||||
 | 
					  QMenu * view_menu_;
 | 
				
			||||||
 | 
					  DecodesModel * decodes_model_;
 | 
				
			||||||
 | 
					  BeaconsModel * beacons_model_;
 | 
				
			||||||
 | 
					  MessageServer * server_;
 | 
				
			||||||
 | 
					  QLineEdit * multicast_group_line_edit_;
 | 
				
			||||||
 | 
					  QTableView * log_table_view_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // maps client id to widgets
 | 
				
			||||||
 | 
					  using ClientsDictionary = QHash<QString, ClientWidget *>;
 | 
				
			||||||
 | 
					  ClientsDictionary dock_widgets_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
@ -47,7 +47,8 @@ public:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  Q_SLOT void update_status (QString const& id, Frequency f, QString const& /*mode*/, QString const& /*dx_call*/
 | 
					  Q_SLOT void update_status (QString const& id, Frequency f, QString const& /*mode*/, QString const& /*dx_call*/
 | 
				
			||||||
                             , QString const& /*report*/, QString const& /*tx_mode*/, bool /*tx_enabled*/
 | 
					                             , QString const& /*report*/, QString const& /*tx_mode*/, bool /*tx_enabled*/
 | 
				
			||||||
                             , bool /*transmitting*/, bool /*decoding*/)
 | 
					                             , bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/
 | 
				
			||||||
 | 
					                             , QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/)
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    if (id == id_)
 | 
					    if (id == id_)
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
							
								
								
									
										1
									
								
								UDPExamples/message_aggregator.rc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								UDPExamples/message_aggregator.rc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					IDI_ICON1       ICON    DISCARDABLE     "../icons/windows-icons/wsjtx.ico"
 | 
				
			||||||
							
								
								
									
										1
									
								
								UDPExamples/udp_daemon.rc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								UDPExamples/udp_daemon.rc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					IDI_ICON1       ICON    DISCARDABLE     "../icons/windows-icons/wsjtx.ico"
 | 
				
			||||||
							
								
								
									
										8
									
								
								main.cpp
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								main.cpp
									
									
									
									
									
								
							@ -33,6 +33,8 @@
 | 
				
			|||||||
#include "mainwindow.h"
 | 
					#include "mainwindow.h"
 | 
				
			||||||
#include "commons.h"
 | 
					#include "commons.h"
 | 
				
			||||||
#include "lib/init_random_seed.h"
 | 
					#include "lib/init_random_seed.h"
 | 
				
			||||||
 | 
					#include "Radio.hpp"
 | 
				
			||||||
 | 
					#include "FrequencyList.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace
 | 
					namespace
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -80,9 +82,11 @@ int main(int argc, char *argv[])
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  init_random_seed ();
 | 
					  init_random_seed ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  register_types ();            // make the Qt magic happen
 | 
					  // make the Qt type magic happen
 | 
				
			||||||
 | 
					  Radio::register_types ();
 | 
				
			||||||
 | 
					  register_types ();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Multiple instances:
 | 
					  // Multiple instances communicate with jt9 via this
 | 
				
			||||||
  QSharedMemory mem_jt9;
 | 
					  QSharedMemory mem_jt9;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  QApplication a(argc, argv);
 | 
					  QApplication a(argc, argv);
 | 
				
			||||||
 | 
				
			|||||||
@ -1044,10 +1044,8 @@ void MainWindow::dataSink(qint64 frames)
 | 
				
			|||||||
      cmnd=t3.mid(0,i1+7) + t3.mid(i1+7);
 | 
					      cmnd=t3.mid(0,i1+7) + t3.mid(i1+7);
 | 
				
			||||||
      if (ui) ui->DecodeButton->setChecked (true);
 | 
					      if (ui) ui->DecodeButton->setChecked (true);
 | 
				
			||||||
      p1.start(QDir::toNativeSeparators(cmnd));
 | 
					      p1.start(QDir::toNativeSeparators(cmnd));
 | 
				
			||||||
      if (ui) m_messageClient->status_update (m_freqNominal, m_mode, m_hisCall,
 | 
					      m_decoderBusy = true;
 | 
				
			||||||
                                              QString::number (ui->rptSpinBox->value ()),
 | 
					      statusUpdate ();
 | 
				
			||||||
                                              m_modeTx, ui->autoButton->isChecked (),
 | 
					 | 
				
			||||||
                                              m_transmitting, (m_decoderBusy = true));
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    m_rxDone=true;
 | 
					    m_rxDone=true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -1260,10 +1258,7 @@ void MainWindow::on_actionAbout_triggered()                  //Display "About"
 | 
				
			|||||||
void MainWindow::on_autoButton_clicked (bool checked)
 | 
					void MainWindow::on_autoButton_clicked (bool checked)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  m_auto = checked;
 | 
					  m_auto = checked;
 | 
				
			||||||
  m_messageClient->status_update (m_freqNominal, m_mode, m_hisCall,
 | 
					  statusUpdate ();
 | 
				
			||||||
                                  QString::number (ui->rptSpinBox->value ()),
 | 
					 | 
				
			||||||
                                  m_modeTx, ui->autoButton->isChecked (),
 | 
					 | 
				
			||||||
                                  m_transmitting, m_decoderBusy);
 | 
					 | 
				
			||||||
  m_bEchoTxOK=false;
 | 
					  m_bEchoTxOK=false;
 | 
				
			||||||
  if(m_auto and (m_mode=="Echo")) {
 | 
					  if(m_auto and (m_mode=="Echo")) {
 | 
				
			||||||
    m_nclearave=1;
 | 
					    m_nclearave=1;
 | 
				
			||||||
@ -1427,11 +1422,7 @@ void MainWindow::displayDialFrequency ()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void MainWindow::statusChanged()
 | 
					void MainWindow::statusChanged()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  m_messageClient->status_update (m_freqNominal, m_mode, m_hisCall,
 | 
					  statusUpdate ();
 | 
				
			||||||
                                  QString::number (ui->rptSpinBox->value ()),
 | 
					 | 
				
			||||||
                                  m_modeTx, ui->autoButton->isChecked (),
 | 
					 | 
				
			||||||
                                  m_transmitting, m_decoderBusy);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  QFile f {m_config.temp_dir ().absoluteFilePath ("wsjtx_status.txt")};
 | 
					  QFile f {m_config.temp_dir ().absoluteFilePath ("wsjtx_status.txt")};
 | 
				
			||||||
  if(f.open(QFile::WriteOnly | QIODevice::Text)) {
 | 
					  if(f.open(QFile::WriteOnly | QIODevice::Text)) {
 | 
				
			||||||
    QTextStream out(&f);
 | 
					    QTextStream out(&f);
 | 
				
			||||||
@ -2268,10 +2259,7 @@ void MainWindow::decodeBusy(bool b)                             //decodeBusy()
 | 
				
			|||||||
  ui->actionOpen_next_in_directory->setEnabled(!b);
 | 
					  ui->actionOpen_next_in_directory->setEnabled(!b);
 | 
				
			||||||
  ui->actionDecode_remaining_files_in_directory->setEnabled(!b);
 | 
					  ui->actionDecode_remaining_files_in_directory->setEnabled(!b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  m_messageClient->status_update (m_freqNominal, m_mode, m_hisCall,
 | 
					  statusUpdate ();
 | 
				
			||||||
                                  QString::number (ui->rptSpinBox->value ()),
 | 
					 | 
				
			||||||
                                  m_modeTx, ui->autoButton->isChecked (),
 | 
					 | 
				
			||||||
                                  m_transmitting, m_decoderBusy);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//------------------------------------------------------------- //guiUpdate()
 | 
					//------------------------------------------------------------- //guiUpdate()
 | 
				
			||||||
@ -2601,10 +2589,7 @@ void MainWindow::guiUpdate()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      m_transmitting = true;
 | 
					      m_transmitting = true;
 | 
				
			||||||
      transmitDisplay (true);
 | 
					      transmitDisplay (true);
 | 
				
			||||||
      m_messageClient->status_update (m_freqNominal, m_mode, m_hisCall,
 | 
					      statusUpdate ();
 | 
				
			||||||
                                      QString::number (ui->rptSpinBox->value ()),
 | 
					 | 
				
			||||||
                                      m_modeTx, ui->autoButton->isChecked (),
 | 
					 | 
				
			||||||
                                      m_transmitting, m_decoderBusy);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if(!m_btxok && m_btxok0 && g_iptt==1) stopTx();
 | 
					  if(!m_btxok && m_btxok0 && g_iptt==1) stopTx();
 | 
				
			||||||
@ -2727,10 +2712,7 @@ void MainWindow::stopTx()
 | 
				
			|||||||
  tx_status_label->setText("");
 | 
					  tx_status_label->setText("");
 | 
				
			||||||
  ptt0Timer->start(200);                       //Sequencer delay
 | 
					  ptt0Timer->start(200);                       //Sequencer delay
 | 
				
			||||||
  monitor (true);
 | 
					  monitor (true);
 | 
				
			||||||
  m_messageClient->status_update (m_freqNominal, m_mode, m_hisCall,
 | 
					  statusUpdate ();
 | 
				
			||||||
                                  QString::number (ui->rptSpinBox->value ()),
 | 
					 | 
				
			||||||
                                  m_modeTx, ui->autoButton->isChecked (),
 | 
					 | 
				
			||||||
                                  m_transmitting, m_decoderBusy);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MainWindow::stopTx2()
 | 
					void MainWindow::stopTx2()
 | 
				
			||||||
@ -3480,6 +3462,7 @@ void MainWindow::on_dxCallEntry_textChanged(const QString &t) //dxCall changed
 | 
				
			|||||||
  m_hisCall=t.toUpper().trimmed();
 | 
					  m_hisCall=t.toUpper().trimmed();
 | 
				
			||||||
  ui->dxCallEntry->setText(m_hisCall);
 | 
					  ui->dxCallEntry->setText(m_hisCall);
 | 
				
			||||||
  statusChanged();
 | 
					  statusChanged();
 | 
				
			||||||
 | 
					  statusUpdate ();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MainWindow::on_dxGridEntry_textChanged(const QString &t) //dxGrid changed
 | 
					void MainWindow::on_dxGridEntry_textChanged(const QString &t) //dxGrid changed
 | 
				
			||||||
@ -3519,6 +3502,7 @@ void MainWindow::on_dxGridEntry_textChanged(const QString &t) //dxGrid changed
 | 
				
			|||||||
    ui->labAz->setText("");
 | 
					    ui->labAz->setText("");
 | 
				
			||||||
    ui->labDist->setText("");
 | 
					    ui->labDist->setText("");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  statusUpdate ();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MainWindow::on_genStdMsgsPushButton_clicked()         //genStdMsgs button
 | 
					void MainWindow::on_genStdMsgsPushButton_clicked()         //genStdMsgs button
 | 
				
			||||||
@ -3977,6 +3961,7 @@ void MainWindow::on_TxFreqSpinBox_valueChanged(int n)
 | 
				
			|||||||
  m_wideGraph->setTxFreq(n);
 | 
					  m_wideGraph->setTxFreq(n);
 | 
				
			||||||
  if(m_lockTxFreq) ui->RxFreqSpinBox->setValue(n);
 | 
					  if(m_lockTxFreq) ui->RxFreqSpinBox->setValue(n);
 | 
				
			||||||
  Q_EMIT transmitFrequency (n - m_XIT);
 | 
					  Q_EMIT transmitFrequency (n - m_XIT);
 | 
				
			||||||
 | 
					  statusUpdate ();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MainWindow::on_RxFreqSpinBox_valueChanged(int n)
 | 
					void MainWindow::on_RxFreqSpinBox_valueChanged(int n)
 | 
				
			||||||
@ -3986,6 +3971,10 @@ void MainWindow::on_RxFreqSpinBox_valueChanged(int n)
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
      ui->TxFreqSpinBox->setValue (n);
 | 
					      ui->TxFreqSpinBox->setValue (n);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  else
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      statusUpdate ();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MainWindow::on_actionQuickDecode_triggered()
 | 
					void MainWindow::on_actionQuickDecode_triggered()
 | 
				
			||||||
@ -5082,10 +5071,8 @@ void MainWindow::p1ReadFromStdout()                        //p1readFromStdout
 | 
				
			|||||||
      m_RxLog=0;
 | 
					      m_RxLog=0;
 | 
				
			||||||
      m_startAnother=m_loopall;
 | 
					      m_startAnother=m_loopall;
 | 
				
			||||||
      m_blankLine=true;
 | 
					      m_blankLine=true;
 | 
				
			||||||
      m_messageClient->status_update (m_freqNominal, m_mode, m_hisCall,
 | 
					      m_decoderBusy = false;
 | 
				
			||||||
                                      QString::number (ui->rptSpinBox->value ()),
 | 
					      statusUpdate ();
 | 
				
			||||||
                                      m_modeTx, ui->autoButton->isChecked (),
 | 
					 | 
				
			||||||
                                      m_transmitting, (m_decoderBusy = false));
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      int n=t.length();
 | 
					      int n=t.length();
 | 
				
			||||||
@ -5412,3 +5399,16 @@ void MainWindow::CQRxFreq()
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void MainWindow::statusUpdate () const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  if (ui)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      m_messageClient->status_update (m_freqNominal, m_mode, m_hisCall,
 | 
				
			||||||
 | 
					                                      QString::number (ui->rptSpinBox->value ()),
 | 
				
			||||||
 | 
					                                      m_modeTx, ui->autoButton->isChecked (),
 | 
				
			||||||
 | 
					                                      m_transmitting, m_decoderBusy,
 | 
				
			||||||
 | 
					                                      ui->RxFreqSpinBox->value (), ui->TxFreqSpinBox->value (),
 | 
				
			||||||
 | 
					                                      m_config.my_callsign (), m_config.my_grid (),
 | 
				
			||||||
 | 
					                                      m_hisGrid);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -572,6 +572,7 @@ private:
 | 
				
			|||||||
  void decodeDone ();
 | 
					  void decodeDone ();
 | 
				
			||||||
  void subProcessFailed (QProcess *, int exit_code, QProcess::ExitStatus);
 | 
					  void subProcessFailed (QProcess *, int exit_code, QProcess::ExitStatus);
 | 
				
			||||||
  void subProcessError (QProcess *, QProcess::ProcessError);
 | 
					  void subProcessError (QProcess *, QProcess::ProcessError);
 | 
				
			||||||
 | 
					  void statusUpdate () const;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern int killbyname(const char* progName);
 | 
					extern int killbyname(const char* progName);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user