diff --git a/CMake/Modules/Findhamlib.cmake b/CMake/Modules/Findhamlib.cmake index 1590f051d..7eebf1066 100644 --- a/CMake/Modules/Findhamlib.cmake +++ b/CMake/Modules/Findhamlib.cmake @@ -16,7 +16,7 @@ set (hamlib_LIBRARY_DIRS) # pkg-config? find_path (__hamlib_pc_path NAMES hamlib.pc - PATH_SUFFIXES lib/pkgconfig + PATH_SUFFIXES lib/pkgconfig lib64/pkgconfig ) if (__hamlib_pc_path) set (ENV{PKG_CONFIG_PATH} "${__hamlib_pc_path}" "$ENV{PKG_CONFIG_PATH}") diff --git a/CMakeLists.txt b/CMakeLists.txt index c684ab4aa..e2afabd6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,7 @@ set (wsjt_qt_CXXSRCS models/DecodeHighlightingModel.cpp widgets/DecodeHighlightingListView.cpp models/FoxLog.cpp + widgets/AbstractLogWindow.cpp widgets/FoxLogWindow.cpp widgets/CabrilloLogWindow.cpp item_delegates/CallsignDelegate.cpp @@ -382,7 +383,6 @@ set (wsjt_FSRCS lib/badmsg.f90 lib/ft8/baseline.f90 lib/bpdecode40.f90 - lib/bpdecode144.f90 lib/bpdecode128_90.f90 lib/ft8/bpdecode174.f90 lib/ft8/bpdecode174_91.f90 @@ -414,7 +414,6 @@ set (wsjt_FSRCS lib/encode232.f90 lib/encode4.f90 lib/encode_msk40.f90 - lib/encode_msk144.f90 lib/encode_128_90.f90 lib/ft8/encode174.f90 lib/ft8/encode174_91.f90 @@ -422,7 +421,6 @@ set (wsjt_FSRCS lib/ephem.f90 lib/extract.f90 lib/extract4.f90 - lib/extractmessage144.f90 lib/extractmessage77.f90 lib/ft8/extractmessage174.f90 lib/ft8/extractmessage174_91.f90 @@ -497,10 +495,8 @@ set (wsjt_FSRCS lib/moondopjpl.f90 lib/morse.f90 lib/move.f90 - lib/msk144d.f90 lib/msk40decodeframe.f90 lib/msk144decodeframe.f90 - lib/msk144sd.f90 lib/msk40spd.f90 lib/msk144spd.f90 lib/msk40sync.f90 @@ -519,6 +515,8 @@ set (wsjt_FSRCS lib/peakdt9.f90 lib/peakup.f90 lib/plotsave.f90 + lib/platanh.f90 + lib/pltanh.f90 lib/polyfit.f90 lib/prog_args.f90 lib/ps4.f90 @@ -561,7 +559,6 @@ set (wsjt_FSRCS lib/twkfreq.f90 lib/ft8/twkfreq1.f90 lib/twkfreq65.f90 - lib/unpackmsg144.f90 lib/update_recent_calls.f90 lib/update_hasharray.f90 lib/ft8/watterson.f90 @@ -1252,14 +1249,9 @@ target_link_libraries (ft8code wsjt_fort wsjt_cxx) add_executable (ft8sim lib/ft8/ft8sim.f90 wsjtx.rc) target_link_libraries (ft8sim wsjt_fort wsjt_cxx) -add_executable (msk144sd lib/msk144sd.f90 wsjtx.rc) -target_link_libraries (msk144sd wsjt_fort wsjt_cxx) - add_executable (msk144sim lib/msk144sim.f90 wsjtx.rc) target_link_libraries (msk144sim wsjt_fort wsjt_cxx) -add_executable (msk144d lib/msk144d.f90 wsjtx.rc) -target_link_libraries (msk144d wsjt_fort wsjt_cxx) endif(WSJT_BUILD_UTILS) # build the main application @@ -1708,11 +1700,11 @@ endif () set (CPACK_DEBIAN_PACKAGE_DESCRIPTION "${PROJECT_DESCRIPTION}") set (CPACK_DEBIAN_PACKAGE_HOMEPAGE "${PROJECT_HOMEPAGE}") -set (CPACK_DEBIAN_PACKAGE_DEPENDS "libgfortran3 (>=4.8.2), libqt5serialport5 (>=5.2), libqt5multimedia5-plugins (>=5.2), libqt5widgets5 (>=5.2), libusb-1.0-0, libudev1, libc6 (>=2.19)") +set (CPACK_DEBIAN_PACKAGE_DEPENDS "libgfortran3 (>=4.8.2), libqt5serialport5 (>=5.5), libqt5multimedia5-plugins (>=5.5), libqt5widgets5 (>=5.5), libqt5sql5-sqlite (>=5.5), libusb-1.0-0, libudev1, libc6 (>=2.19)") set (CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set (CPACK_RPM_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) -set (CPACK_RPM_PACKAGE_REQUIRES "qt5-qtserialport >= 5.2, qt5-qtmultimedia >= 5.2, qt5-qtsvg, libusb, systemd-udev, glibc >= 2, libgfortran >= 4.8.2") +set (CPACK_RPM_PACKAGE_REQUIRES "qt5-qtserialport >= 5.5, qt5-qtmultimedia >= 5.5, qt5-qtsvg, libusb, systemd-udev, glibc >= 2, libgfortran >= 4.8.2") set (CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION /usr/share/pixmaps /usr/share/applications /usr/share/man /usr/share/man1) configure_file ("${PROJECT_SOURCE_DIR}/CMakeCPackOptions.cmake.in" diff --git a/Configuration.cpp b/Configuration.cpp index 03f5b2ac2..bf57206b2 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -1091,6 +1091,8 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network frequencies_.sort (FrequencyList_v2::frequency_column); ui_->frequencies_table_view->setModel (&next_frequencies_); + ui_->frequencies_table_view->horizontalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); + ui_->frequencies_table_view->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); ui_->frequencies_table_view->sortByColumn (FrequencyList_v2::frequency_column, Qt::AscendingOrder); ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::frequency_mhz_column, true); @@ -1131,6 +1133,8 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network // stations_.sort (StationList::band_column); ui_->stations_table_view->setModel (&next_stations_); + ui_->stations_table_view->horizontalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); + ui_->stations_table_view->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); ui_->stations_table_view->sortByColumn (StationList::band_column, Qt::AscendingOrder); // stations delegates @@ -2275,10 +2279,10 @@ void Configuration::impl::delete_selected_macros (QModelIndexList selected_rows) { // sort in reverse row order so that we can delete without changing // indices underneath us - qSort (selected_rows.begin (), selected_rows.end (), [] (QModelIndex const& lhs, QModelIndex const& rhs) - { - return rhs.row () < lhs.row (); // reverse row ordering - }); + std::sort (selected_rows.begin (), selected_rows.end (), [] (QModelIndex const& lhs, QModelIndex const& rhs) + { + return rhs.row () < lhs.row (); // reverse row ordering + }); // now delete them Q_FOREACH (auto index, selected_rows) diff --git a/Configuration.ui b/Configuration.ui index 1e8feae69..7a3ec96be 100644 --- a/Configuration.ui +++ b/Configuration.ui @@ -1712,24 +1712,24 @@ QListView::item:hover { - Some logging programs will not accept JT-65 or JT9 as a recognized mode. + Some logging programs will not accept the type of reports +saved by this program. +Check this option to save the sent and received reports in the +comments field. - Con&vert mode to RTTY + d&B reports to comments - <html><head/><body><p>The callsign of the operator, if different from the station callsign.</p></body></html> + Check this option to force the clearing of the DX Call +and DX Grid fields when a 73 or free text message is sent. - - - - - Log automatically + Clear &DX call and grid after logging diff --git a/Darwin/Info.plist.in b/Darwin/Info.plist.in index b62e03682..830975da5 100644 --- a/Darwin/Info.plist.in +++ b/Darwin/Info.plist.in @@ -36,5 +36,7 @@ NSApplication NSHighResolutionCapable True + NSRequiresAquaSystemAppearance + diff --git a/Release_Notes.txt b/Release_Notes.txt index 8c483f454..c2b2c711b 100644 --- a/Release_Notes.txt +++ b/Release_Notes.txt @@ -12,6 +12,31 @@ Copyright 2001 - 2018 by Joe Taylor, K1JT. + Release: WSJT-X 2.0-rc5 + November 26, 2018 + ----------------------- + +Release Candidate 5 ("RC5") is stable, works well, and fixes the known +problems in RC4. It is likely that the General Availability (GA) +release of WSJT-X 2.0 will be nearly identical to RC5. + +Changes from WSJT-X 2.0-rc4 include the following: + + - Make the "Auto Seq" checkbox sticky, again + - Remove the 5-minute mouse timer + - Correct the "worked before" logic for color highlighting + - Add "No own call decodes" checkbox in WSPR mode + - Display and log UTC (not local time) in contest operations + - Validate contest QSO details before allowing logging + - Force Aqua theme on macOS to avoid issues with Mojave dark theme + - Move Fox log Reset action to Fox log window context menu + - Improve layout of Working Frequencies and Station Information tables + - Allow deletes and editing in Fox and Contest log windows + - Add Tool Tips for Fox and Contest log windows + - Fix a bug causing false AP decodes in JT65 VHF+ operation + - Fix a bug that could switch unexpectedly from JT65 to JT9 mode + + Release: WSJT-X 2.0-rc4 November 12, 2018 ----------------------- diff --git a/Versions.cmake b/Versions.cmake index 6cb4d89c2..3ba5db895 100644 --- a/Versions.cmake +++ b/Versions.cmake @@ -1,6 +1,6 @@ # Version number components set (WSJTX_VERSION_MAJOR 2) set (WSJTX_VERSION_MINOR 0) -set (WSJTX_VERSION_PATCH 1) -#set (WSJTX_RC 1) # release candidate number, comment out or zero for development versions +set (WSJTX_VERSION_PATCH 0) +set (WSJTX_RC 5) # release candidate number, comment out or zero for development versions set (WSJTX_VERSION_IS_RELEASE 0) # set to 1 for final release build diff --git a/item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp b/item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp index 66d3b6769..5b7f76eb8 100644 --- a/item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp +++ b/item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp @@ -28,7 +28,7 @@ public: static QDateTime to_date_time (QVariant const& value) { - return QDateTime::fromMSecsSinceEpoch (value.toULongLong () * 1000ull); + return QDateTime::fromMSecsSinceEpoch (value.toULongLong () * 1000ull, Qt::UTC); } QString displayText (QVariant const& value, QLocale const& locale) const override diff --git a/item_delegates/ForeignKeyDelegate.cpp b/item_delegates/ForeignKeyDelegate.cpp index 09df6a731..204738163 100644 --- a/item_delegates/ForeignKeyDelegate.cpp +++ b/item_delegates/ForeignKeyDelegate.cpp @@ -1,7 +1,10 @@ #include "ForeignKeyDelegate.hpp" +#include #include - +#include +#include +#include #include "CandidateKeyFilter.hpp" ForeignKeyDelegate::ForeignKeyDelegate (QAbstractItemModel const * referenced_model @@ -40,3 +43,20 @@ QWidget * ForeignKeyDelegate::createEditor (QWidget * parent editor->setSizeAdjustPolicy (QComboBox::AdjustToContents); return editor; } + +QSize ForeignKeyDelegate::sizeHint (QStyleOptionViewItem const& option, QModelIndex const& index) const +{ + auto size_hint = QStyledItemDelegate::sizeHint (option, index); + QFontMetrics metrics {option.font}; + QStyleOptionComboBox combo_box_option; + combo_box_option.rect = option.rect; + combo_box_option.state = option.state | QStyle::State_Enabled; + for (auto row = 0; row < candidate_key_filter_->rowCount (); ++row) + { + size_hint = size_hint.expandedTo (qApp->style ()->sizeFromContents (QStyle::CT_ComboBox + , &combo_box_option + , {metrics.width (candidate_key_filter_->data (candidate_key_filter_->index (row, 0)).toString ()) + , metrics.height ()})); + } + return size_hint; +} diff --git a/item_delegates/ForeignKeyDelegate.hpp b/item_delegates/ForeignKeyDelegate.hpp index 01b03ee5b..4f58f4608 100644 --- a/item_delegates/ForeignKeyDelegate.hpp +++ b/item_delegates/ForeignKeyDelegate.hpp @@ -33,9 +33,10 @@ public: , int referencing_key_role = Qt::EditRole); ~ForeignKeyDelegate (); - QWidget * createEditor (QWidget * parent, QStyleOptionViewItem const&, QModelIndex const&) const override; - private: + QWidget * createEditor (QWidget * parent, QStyleOptionViewItem const&, QModelIndex const&) const override; + QSize sizeHint (QStyleOptionViewItem const&, QModelIndex const&) const override; + QScopedPointer candidate_key_filter_; }; diff --git a/lib/77bit/77bit.txt b/lib/77bit/77bit.txt index 0d6c4b733..ef370c111 100644 --- a/lib/77bit/77bit.txt +++ b/lib/77bit/77bit.txt @@ -10,12 +10,12 @@ subtypes. i3.n3 Example message Bits Total Purpose ---------------------------------------------------------------------------------- 0.0 FREE TEXT MSG 71 71 Free text -0.1 K1ABC RR73; W9XYZ -11 28 28 10 5 71 DXpedition Mode +0.1 K1ABC RR73; W9XYZ -12 28 28 10 5 71 DXpedition Mode 0.2 PA3XYZ/P R 590003 IO91NP 28 1 1 3 12 25 70 EU VHF contest 0.3 WA9XYZ KA1ABC R 16A EMA 28 28 1 4 3 7 71 ARRL Field Day 0.4 WA9XYZ KA1ABC R 32A EMA 28 28 1 4 3 7 71 ARRL Field Day 0.5 123456789ABCDEF012 71 71 Telemetry (18 hex) -0.6 ... tbd +0.6 K1ABC RR73; CQ W9XYZ EN37 28 28 15 71 Contesting 0.7 ... tbd 1 WA9XYZ/R KA1ABC/R R FN42 28 1 28 1 1 15 74 Standard msg diff --git a/lib/Makefile.mskWin b/lib/Makefile.mskWin deleted file mode 100644 index 9d5e6e62b..000000000 --- a/lib/Makefile.mskWin +++ /dev/null @@ -1,75 +0,0 @@ - -# Set paths -EXE_DIR = ..\\..\\wsjtx_install -QT_DIR = C:/wsjt-env/Qt5/5.2.1/mingw48_32 -FFTW3_DIR = .. - -INCPATH = -I${QT_DIR}/include/QtCore -I${QT_DIR}/include - -# Compilers -CC = gcc -CXX = g++ -FC = gfortran -AR = ar cr -RANLIB = ranlib -MKDIR = mkdir -p -CP = cp -RM = rm -f - -FFLAGS = -O2 -fbounds-check -Wall -Wno-conversion -CFLAGS = -O2 -I. - -# Default rules -%.o: %.c - ${CC} ${CFLAGS} -c $< -%.o: %.f - ${FC} ${FFLAGS} -c $< -%.o: %.F - ${FC} ${FFLAGS} -c $< -%.o: %.f90 - ${FC} ${FFLAGS} -c $< -%.o: %.F90 - ${FC} ${FFLAGS} -c $< - -#all: jt9code JTMSKcode.exe -all: jtmsk.exe JTMSKsim.exe JTMSKcode.exe fixwav.exe - -OBJS3 = JTMSKsim.o wavhdr.o gran.o four2a.o db.o -JTMSKsim.exe: $(OBJS3) - $(FC) -o JTMSKsim.exe $(OBJS3) C:\JTSDK\fftw3f\libfftw3f-3.dll - -OBJS4 = jt9code.o packjt.o fmtmsg.o gen9.o deg2grid.o grid2deg.o \ - entail.o encode232.o interleave9.o graycode.o igray.o -jt9code: $(OBJS4) - $(FC) -o jt9code $(OBJS4) - -OBJS5 = JTMSKcode.o packjt.o fmtmsg.o genmsk.o deg2grid.o grid2deg.o \ - entail.o tab.o vit213.o hashing.o nhash.o -JTMSKcode.exe: $(OBJS5) - $(FC) -o JTMSKcode.exe $(OBJS5) - -OBJS6 = jtmsk.o analytic.o four2a.o db.o pctile.o \ - shell.o tweak1.o syncmsk.o genmsk.o packjt.o fmtmsg.o indexx.o \ - deg2grid.o grid2deg.o entail.o hashing.o nhash.o tab.o vit213.o \ - mskdt.o rectify_msk.o timer.o jtmsk_decode.o genmsk_short.o \ - jtmsk_short.o golay24_table.o hash.o - -jtmsk.exe: $(OBJS6) - $(FC) -o jtmsk.exe $(OBJS6) C:\JTSDK\fftw3f\libfftw3f-3.dll - -OBJS1 = fixwav.o wavhdr.o -fixwav.exe: $(OBJS1) - $(FC) -o fixwav.exe $(OBJS1) - -OBJS2 = t2.o four2a.o db.o -t2: $(OBJS2) - $(FC) -o t2 $(OBJS2) C:\JTSDK\fftw3f\libfftw3f-3.dll - -OBJS6 = t6.o four2a.o db.o -t6: $(OBJS6) - $(FC) -o t6 $(OBJS6) C:\JTSDK\fftw3f\libfftw3f-3.dll - -.PHONY : clean - -clean: - $(RM) *.o JTMSKcode JTMSKcode.exe diff --git a/lib/encode_msk144.f90 b/lib/encode_msk144.f90 deleted file mode 100644 index 4e4d8968e..000000000 --- a/lib/encode_msk144.f90 +++ /dev/null @@ -1,111 +0,0 @@ -subroutine encode_msk144(message,codeword) -! Encode an 80-bit message and return a 128-bit codeword. -! The generator matrix has dimensions (48,80). -! The code is a (128,80) regular ldpc code with column weight 3. -! The code was generated using the PEG algorithm. -! After creating the codeword, the columns are re-ordered according to -! "colorder" to make the codeword compatible with the parity-check -! matrix stored in Radford Neal's "pchk" format. -! -character*20 g(48) -integer*1 codeword(128) -integer*1 colorder(128) -integer*1 gen144(48,80) -integer*1 itmp(128) -integer*1 message(80) -integer*1 pchecks(48) -logical first -data first/.true./ -data g/ & !parity-check generator matrix for (128,80) code - "24084000800020008000", & - "b39678f7ccdb1baf5f4c", & - "10001000400408012000", & - "08104000100002010800", & - "dc9c18f61ea0e4b7f05c", & - "42c040160909ca002c00", & - "cc50b52b9a80db0d7f9e", & - "dde5ace80780bae74740", & - "00800080020000890080", & - "01020040010400400040", & - "20008010020000100030", & - "80400008004000040050", & - "a4b397810915126f5604", & - "04040100001040200008", & - "00800006000888000800", & - "00010c00000104040001", & - "cc7cd7d953cdc204eba0", & - "0094abe7dd146beb16ce", & - "5af2aec8c7b051c7544a", & - "14040508801840200088", & - "7392f5e720f8f5a62c1e", & - "503cc2a06bff4e684ec9", & - "5a2efd46f1efbb513b80", & - "ac06e9513fd411f1de03", & - "16a31be3dd3082ca2bd6", & - "28542e0daf62fe1d9332", & - "00210c002001540c0401", & - "0ed90d56f84298706a98", & - "939670f7ecdf9baf4f4c", & - "cfe41dec47a433e66240", & - "16d2179c2d5888222630", & - "408000160108ca002800", & - "808000830a00018900a0", & - "9ae2ed8ef3afbf8c3a52", & - "5aaafd86f3efbfc83b02", & - "f39658f68cdb0baf1f4c", & - "9414bb6495106261366a", & - "71ba18670c08411bf682", & - "7298f1a7217cf5c62e5e", & - "86d7a4864396a981369b", & - "a8042c01ae22fe191362", & - "9235ae108b2d60d0e306", & - "dfe5ade807a03be74640", & - "d2451588e6e27ccd9bc4", & - "12b51ae39d20e2ea3bde", & - "a49387810d95136fd604", & - "467e7578e51d5b3b8a0e", & - "f6ad1ac7cc3aaa3fe580"/ - -data colorder/0,1,2,3,4,5,6,7,8,9, & - 10,11,12,13,14,15,24,26,29,30, & - 32,43,44,47,60,77,79,97,101,111, & - 96,38,64,53,93,34,59,94,74,90, & - 108,123,85,57,70,25,69,62,48,49, & - 50,51,52,33,54,55,56,21,58,36, & - 16,61,23,63,20,65,66,67,68,46, & - 22,71,72,73,31,75,76,45,78,17, & - 80,81,82,83,84,42,86,87,88,89, & - 39,91,92,35,37,95,19,27,98,99, & - 100,28,102,103,104,105,106,107,40,109, & - 110,18,112,113,114,115,116,117,118,119, & - 120,121,122,41,124,125,126,127/ - -save first,gen144 - -if( first ) then ! fill the generator matrix - gen144=0 - do i=1,48 - do j=1,5 - read(g(i)( (j-1)*4+1:(j-1)*4+4 ),"(Z4)") istr - do jj=1,16 - icol=(j-1)*16+jj - if( btest(istr,16-jj) ) gen144(i,icol)=1 - enddo - enddo - enddo -first=.false. -endif - -do i=1,48 - nsum=0 - do j=1,80 - nsum=nsum+message(j)*gen144(i,j) - enddo - pchecks(i)=mod(nsum,2) -enddo -itmp(1:48)=pchecks -itmp(49:128)=message(1:80) -codeword(colorder+1)=itmp(1:128) - -return -end subroutine encode_msk144 diff --git a/lib/extractmessage144.f90 b/lib/extractmessage144.f90 deleted file mode 100644 index a5dadb0b4..000000000 --- a/lib/extractmessage144.f90 +++ /dev/null @@ -1,51 +0,0 @@ -subroutine extractmessage144(decoded,msgreceived,nhashflag,recent_calls,nrecent) - use iso_c_binding, only: c_loc,c_size_t - use packjt - use hashing - - character*22 msgreceived - character*12 call1,call2 - character*12 recent_calls(nrecent) - integer*1 decoded(80) - integer*1, target:: i1Dec8BitBytes(10) - integer*1 i1hashdec - integer*4 i4Dec6BitWords(12) - -! Collapse 80 decoded bits to 10 bytes. Bytes 1-9 are the message, byte 10 is the hash - do ibyte=1,10 - itmp=0 - do ibit=1,8 - itmp=ishft(itmp,1)+iand(1,decoded((ibyte-1)*8+ibit)) - enddo - i1Dec8BitBytes(ibyte)=itmp - enddo - -! Calculate the hash using the first 9 bytes. - ihashdec=nhash(c_loc(i1Dec8BitBytes),int(9,c_size_t),146) - ihashdec=2*iand(ihashdec,255) - -! Compare calculated hash with received byte 10 - if they agree, keep the message. - i1hashdec=ihashdec - if( i1hashdec .eq. i1Dec8BitBytes(10) ) then -! Good hash --- unpack 72-bit message - do ibyte=1,12 - itmp=0 - do ibit=1,6 - itmp=ishft(itmp,1)+iand(1,decoded((ibyte-1)*6+ibit)) - enddo - i4Dec6BitWords(ibyte)=itmp - enddo - call unpackmsg144(i4Dec6BitWords,msgreceived,call1,call2) - nhashflag=1 - if( call1(1:2) .ne. 'CQ' .and. call1(1:2) .ne. ' ' ) then - call update_recent_calls(call1,recent_calls,nrecent) - endif - if( call2(1:2) .ne. ' ' ) then - call update_recent_calls(call2,recent_calls,nrecent) - endif - else - msgreceived=' ' - nhashflag=-1 - endif - return - end subroutine extractmessage144 diff --git a/lib/genmsk144.f90 b/lib/genmsk144.f90 deleted file mode 100644 index f02344439..000000000 --- a/lib/genmsk144.f90 +++ /dev/null @@ -1,146 +0,0 @@ -subroutine genmsk144(msg0,mygrid,ichk,msgsent,i4tone,itype) -! s8 + 48bits + s8 + 80 bits = 144 bits (72ms message duration) -! -! Encode an MSK144 message -! Input: -! - msg0 requested message to be transmitted -! - ichk if ichk=1, return only msgsent -! if ichk.ge.10000, set imsg=ichk-10000 for short msg -! - msgsent message as it will be decoded -! - i4tone array of audio tone values, 0 or 1 -! - itype message type -! 1 = standard message "Call_1 Call_2 Grid/Rpt" -! 2 = type 1 prefix -! 3 = type 1 suffix -! 4 = type 2 prefix -! 5 = type 2 suffix -! 6 = free text (up to 13 characters) -! 7 = short message " Rpt" - - use iso_c_binding, only: c_loc,c_size_t - use packjt - use hashing - character*22 msg0 - character*22 message !Message to be generated - character*22 msgsent !Message as it will be received - character*6 mygrid - integer*4 i4Msg6BitWords(13) !72-bit message as 6-bit words - integer*4 i4tone(144) ! - integer*1, target:: i1Msg8BitBytes(10) !80 bits represented in 10 bytes - integer*1 codeword(128) !Encoded bits before re-ordering - integer*1 msgbits(80) !72-bit message + 8-bit hash - integer*1 bitseq(144) !Tone #s, data and sync (values 0-1) - integer*1 i1hash(4) - integer*1 s8(8) - real*8 pp(12) - real*8 xi(864),xq(864),pi,twopi - data s8/0,1,1,1,0,0,1,0/ - equivalence (ihash,i1hash) - logical first - data first/.true./ - save - - if(first) then - first=.false. - nsym=128 - pi=4.0*atan(1.0) - twopi=8.*atan(1.0) - do i=1,12 - pp(i)=sin((i-1)*pi/12) - enddo - endif - - if(msg0(1:1).eq.'@') then !Generate a fixed tone - read(msg0(2:5),*,end=1,err=1) nfreq !at specified frequency - go to 2 -1 nfreq=1000 -2 i4tone(1)=nfreq - else - message=msg0 - do i=1,22 - if(ichar(message(i:i)).eq.0) then - message(i:)=' ' - exit - endif - enddo - - do i=1,22 !Strip leading blanks - if(message(1:1).ne.' ') exit - message=message(i+1:) - enddo - - if(message(1:1).eq.'<') then - call genmsk40(message,msgsent,ichk,i4tone,itype) - if(itype.lt.0) go to 999 - i4tone(41)=-40 - go to 999 - endif - - call packmsg(message,i4Msg6BitWords,itype) !Pack into 12 6-bit bytes - call unpackmsg(i4Msg6BitWords,msgsent) !Unpack to get msgsent - - if(ichk.eq.1) go to 999 - i4=0 - ik=0 - im=0 - do i=1,12 - nn=i4Msg6BitWords(i) - do j=1, 6 - ik=ik+1 - i4=i4+i4+iand(1,ishft(nn,j-6)) - i4=iand(i4,255) - if(ik.eq.8) then - im=im+1 - i1Msg8BitBytes(im)=i4 - ik=0 - endif - enddo - enddo - - ihash=nhash(c_loc(i1Msg8BitBytes),int(9,c_size_t),146) - ihash=2*iand(ihash,32767) !Generate the 8-bit hash - i1Msg8BitBytes(10)=i1hash(1) !Hash code to byte 10 - - mbit=0 - do i=1, 10 - i1=i1Msg8BitBytes(i) - do ibit=1,8 - mbit=mbit+1 - msgbits(mbit)=iand(1,ishft(i1,ibit-8)) - enddo - enddo - - call encode_msk144(msgbits,codeword) - -!Create 144-bit channel vector: -!8-bit sync word + 48 bits + 8-bit sync word + 80 bits - bitseq=0 - bitseq(1:8)=s8 - bitseq(9:56)=codeword(1:48) - bitseq(57:64)=s8 - bitseq(65:144)=codeword(49:128) - bitseq=2*bitseq-1 - - xq(1:6)=bitseq(1)*pp(7:12) !first bit is mapped to 1st half-symbol on q - do i=1,71 - is=(i-1)*12+7 - xq(is:is+11)=bitseq(2*i+1)*pp - enddo - xq(864-5:864)=bitseq(1)*pp(1:6) !last half symbol - do i=1,72 - is=(i-1)*12+1 - xi(is:is+11)=bitseq(2*i)*pp - enddo -! Map I and Q to tones. - i4tone=0 - do i=1,72 - i4tone(2*i-1)=(bitseq(2*i)*bitseq(2*i-1)+1)/2; - i4tone(2*i)=-(bitseq(2*i)*bitseq(mod(2*i,144)+1)-1)/2; - enddo - endif - -! Flip polarity - i4tone=-i4tone+1 - -999 return -end subroutine genmsk144 diff --git a/lib/genmsk_short.f90 b/lib/genmsk_short.f90 deleted file mode 100644 index f1b555f34..000000000 --- a/lib/genmsk_short.f90 +++ /dev/null @@ -1,66 +0,0 @@ -subroutine genmsk_short(msg,msgsent,ichk,itone,itype) - - use hashing - character*22 msg,msgsent - character*3 crpt,rpt(0:7) - logical first - integer itone(35) - integer ig24(0:4096-1) !Codewords for Golay (24,12) code - integer b11(11) - data b11/1,1,1,0,0,0,1,0,0,1,0/ !Barker 11 code - data rpt /'26 ','27 ','28 ','R26','R27','R28','RRR','73 '/ - data first/.true./ - save first,ig24 - - if(first) then - call golay24_table(ig24) !Define the Golay(24,12) codewords - first=.false. - endif - - itype=-1 - msgsent='*** bad message ***' - itone=0 - i1=index(msg,'>') - if(i1.lt.9) go to 900 - call fmtmsg(msg,iz) - crpt=msg(i1+2:i1+5) - do i=0,7 - if(crpt.eq.rpt(i)) go to 10 - enddo - go to 900 - -10 irpt=i !Report index, 0-7 - if(ichk.lt.10000) then - call hash(msg(2:i1-1),i1-2,ihash) - ihash=iand(ihash,511) !9-bit hash for the two callsigns - ig=8*ihash + irpt !12-bit message information - else - ig=ichk-10000 - endif - ncodeword=ig24(ig) - itone(1:11)=b11 !Insert the Barker-11 code - n=2**24 - do i=12,35 !Insert codeword into itone array - n=n/2 - itone(i)=0 - if(iand(ncodeword,n).ne.0) itone(i)=1 - enddo - msgsent=msg - itype=7 - - n=count(itone(1:35).eq.0) - if(mod(n,2).ne.0) stop 'Parity error in genmsk_short.' - -900 return -end subroutine genmsk_short - -subroutine hash_calls(calls,ih9) - - use hashing - character*(*) calls - i1=index(calls,'>') - call hash(calls(2:i1-1),i1-2,ih9) - ih9=iand(ih9,511) !9-bit hash for the two callsigns - - return -end subroutine hash_calls diff --git a/lib/ldpcsim144.f90 b/lib/ldpcsim144.f90 deleted file mode 100644 index 3121ada2c..000000000 --- a/lib/ldpcsim144.f90 +++ /dev/null @@ -1,175 +0,0 @@ -program ldpcsim - -use, intrinsic :: iso_c_binding -use iso_c_binding, only: c_loc,c_size_t -use hashing -use packjt -parameter(NRECENT=10) -character*12 recent_calls(NRECENT) -character*22 msg,msgsent,msgreceived -character*8 arg -integer*1, allocatable :: codeword(:), decoded(:), message(:) -integer*1, target:: i1Msg8BitBytes(10) -integer*1 i1hash(4) -integer*1 msgbits(80) -integer*4 i4Msg6BitWords(13) -integer ihash -integer nerrtot(0:128),nerrdec(0:128) -real*8, allocatable :: lratio(:), rxdata(:), rxavgd(:) -real, allocatable :: yy(:), llr(:) -equivalence(ihash,i1hash) - -do i=1,NRECENT - recent_calls(i)=' ' -enddo -nerrtot=0 -nerrdec=0 - -nargs=iargc() -if(nargs.ne.4) then - print*,'Usage: ldpcsim niter navg #trials s ' - print*,'eg: ldpcsim 10 1 1000 0.75' - return -endif -call getarg(1,arg) -read(arg,*) max_iterations -call getarg(2,arg) -read(arg,*) navg -call getarg(3,arg) -read(arg,*) ntrials -call getarg(4,arg) -read(arg,*) s - -! don't count hash bits as data bits -K=72 -N=128 -rate=real(K)/real(N) - -write(*,*) "rate: ",rate - -write(*,*) "niter= ",max_iterations," navg= ",navg," s= ",s - -allocate ( codeword(N), decoded(K), message(K) ) -allocate ( lratio(N), rxdata(N), rxavgd(N), yy(N), llr(N) ) - -msg="K9AN K1JT EN50" - call packmsg(msg,i4Msg6BitWords,itype) !Pack into 12 6-bit bytes - call unpackmsg(i4Msg6BitWords,msgsent) !Unpack to get msgsent - write(*,*) "message sent ",msgsent - - i4=0 - ik=0 - im=0 - do i=1,12 - nn=i4Msg6BitWords(i) - do j=1, 6 - ik=ik+1 - i4=i4+i4+iand(1,ishft(nn,j-6)) - i4=iand(i4,255) - if(ik.eq.8) then - im=im+1 -! if(i4.gt.127) i4=i4-256 - i1Msg8BitBytes(im)=i4 - ik=0 - endif - enddo - enddo - - ihash=nhash(c_loc(i1Msg8BitBytes),int(9,c_size_t),146) - ihash=2*iand(ihash,32767) !Generate the 8-bit hash - i1Msg8BitBytes(10)=i1hash(1) !Hash code to byte 10 - mbit=0 - do i=1, 10 - i1=i1Msg8BitBytes(i) - do ibit=1,8 - mbit=mbit+1 - msgbits(mbit)=iand(1,ishft(i1,ibit-8)) - enddo - enddo - call encode_msk144(msgbits,codeword) - call init_random_seed() - -write(*,*) "Eb/N0 SNR2500 ngood nundetected nbadhash sigma" -do idb = 14,-6,-1 - db=idb/2.0-1.0 - sigma=1/sqrt( 2*rate*(10**(db/10.0)) ) - ngood=0 - nue=0 - nbadhash=0 - - do itrial=1, ntrials - rxavgd=0d0 - do iav=1,navg - call sgran() -! Create a realization of a noisy received word - do i=1,N - rxdata(i) = 2.0*codeword(i)-1.0 + sigma*gran() - enddo - rxavgd=rxavgd+rxdata - enddo - rxdata=rxavgd - nerr=0 - do i=1,N - if( rxdata(i)*(2*codeword(i)-1.0) .lt. 0 ) nerr=nerr+1 - enddo - nerrtot(nerr)=nerrtot(nerr)+1 - -! Correct signal normalization is important for this decoder. - rxav=sum(rxdata)/N - rx2av=sum(rxdata*rxdata)/N - rxsig=sqrt(rx2av-rxav*rxav) - rxdata=rxdata/rxsig -! To match the metric to the channel, s should be set to the noise standard deviation. -! For now, set s to the value that optimizes decode probability near threshold. -! The s parameter can be tuned to trade a few tenth's dB of threshold for an order of -! magnitude in UER - if( s .lt. 0 ) then - ss=sigma - else - ss=s - endif - - llr=2.0*rxdata/(ss*ss) - lratio=exp(llr) - yy=rxdata - -! max_iterations is max number of belief propagation iterations -! call ldpc_decode(lratio, decoded, max_iterations, niterations, max_dither, ndither) -! call amsdecode(yy, max_iterations, decoded, niterations) -! call bitflipmsk144(rxdata, decoded, niterations) - call bpdecode144(llr, max_iterations, decoded, niterations) - -! If the decoder finds a valid codeword, niterations will be .ge. 0. - if( niterations .ge. 0 ) then - call extractmessage144(decoded,msgreceived,nhashflag,recent_calls,nrecent) - if( nhashflag .ne. 1 ) then - nbadhash=nbadhash+1 - endif - nueflag=0 - -! Check the message plus hash against what was sent. - do i=1,K - if( msgbits(i) .ne. decoded(i) ) then - nueflag=1 - endif - enddo - if( nhashflag .eq. 1 .and. nueflag .eq. 0 ) then - ngood=ngood+1 - nerrdec(nerr)=nerrdec(nerr)+1 - else if( nhashflag .eq. 1 .and. nueflag .eq. 1 ) then - nue=nue+1; - endif - endif - enddo - snr2500=db-3.5 - write(*,"(f4.1,4x,f5.1,1x,i8,1x,i8,1x,i8,8x,f5.2)") db,snr2500,ngood,nue,nbadhash,ss - -enddo - -open(unit=23,file='nerrhisto.dat',status='unknown') -do i=0,128 - write(23,'(i4,2x,i10,i10,f10.2)') i,nerrdec(i),nerrtot(i),real(nerrdec(i))/real(nerrtot(i)+1e-10) -enddo -close(23) - -end program ldpcsim diff --git a/lib/msk144d.f90 b/lib/msk144d.f90 deleted file mode 100644 index 60c8b0c1d..000000000 --- a/lib/msk144d.f90 +++ /dev/null @@ -1,136 +0,0 @@ -program msk144d - - ! Test the msk144 decoder for WSJT-X - - use options - use timer_module, only: timer - use timer_impl, only: init_timer - use readwav - - character c - character*80 line - character*512 datadir - character*500 infile - character*12 mycall,hiscall - character*6 mygrid - character(len=500) optarg - - logical :: display_help=.false. - logical*1 bShMsgs - logical*1 btrain - logical*1 bswl - - type(wav_header) :: wav - - integer*2 id2(30*12000) - integer*2 ichunk(7*1024) - - real*8 pcoeffs(5) - - type (option) :: long_options(9) = [ & - option ('ndepth',.true.,'c','ndepth',''), & - option ('dxcall',.true.,'d','hiscall',''), & - option ('evemode',.true.,'e','Must be used with -s.',''), & - option ('frequency',.true.,'f','rxfreq',''), & - option ('help',.false.,'h','Display this help message',''), & - option ('mycall',.true.,'m','mycall',''), & - option ('nftol',.true.,'n','nftol',''), & - option ('rxequalize',.false.,'r','Rx Equalize',''), & - option ('short',.false.,'s','enable Sh','') & - ] - t0=0.0 - ndepth=3 - ntol=100 - nrxfreq=1500 - mycall='' - mygrid='EN50WC' - hiscall='' - bShMsgs=.false. - btrain=.false. - bswl=.false. - datadir='.' - pcoeffs=0.d0 - - do - call getopt('c:d:ef:hm:n:rs',long_options,c,optarg,narglen,nstat,noffset,nremain,.true.) - if( nstat .ne. 0 ) then - exit - end if - select case (c) - case ('c') - read (optarg(:narglen), *) ndepth - case ('d') - read (optarg(:narglen), *) hiscall - case ('e') - bswl=.true. - case ('f') - read (optarg(:narglen), *) nrxfreq - case ('h') - display_help = .true. - case ('m') - read (optarg(:narglen), *) mycall - case ('n') - read (optarg(:narglen), *) ntol - case ('r') - btrain=.true. - case ('s') - bShMsgs=.true. - end select - end do - - if(display_help .or. nstat.lt.0 .or. nremain.lt.1) then - print *, '' - print *, 'Usage: msk144d [OPTIONS] file1 [file2 ...]' - print *, '' - print *, ' msk144 decode pre-recorded .WAV file(s)' - print *, '' - print *, 'OPTIONS:' - do i = 1, size (long_options) - call long_options(i) % print (6) - end do - go to 999 - endif - - call init_timer ('timer.out') - call timer('msk144 ',0) - ndecoded=0 - do ifile=noffset+1,noffset+nremain - call get_command_argument(ifile,optarg,narglen) - infile=optarg(:narglen) - call timer('read ',0) - call wav%read (infile) - i1=index(infile,'.wav') - if( i1 .eq. 0 ) i1=index(infile,'.WAV') - read(infile(i1-6:i1-1),*,err=998) nutc - inquire(FILE=infile,SIZE=isize) - npts=min((isize-216)/2,360000) - read(unit=wav%lun) id2(1:npts) - close(unit=wav%lun) - call timer('read ',1) - - do i=1,npts-7*1024+1,7*512 - ichunk=id2(i:i+7*1024-1) - tsec=(i-1)/12000.0 - tt=sum(float(abs(id2(i:i+7*512-1)))) - if( tt .ne. 0.0 ) then - call mskrtd(ichunk,nutc,tsec,ntol,nrxfreq,ndepth,mycall,mygrid,hiscall,bShMsgs, & - btrain,pcoeffs,bswl,datadir,line) - if( index(line,"&") .ne. 0 .or. & - index(line,"^") .ne. 0 .or. & - index(line,"!") .ne. 0 .or. & - index(line,"@") .ne. 0 ) then - write(*,*) line - endif - endif - enddo - enddo - - call timer('msk144 ',1) - call timer('msk144 ',101) - go to 999 - -998 print*,'Cannot read from file:' - print*,infile - -999 continue -end program msk144d diff --git a/lib/msk144sd.f90 b/lib/msk144sd.f90 deleted file mode 100644 index 267fff545..000000000 --- a/lib/msk144sd.f90 +++ /dev/null @@ -1,197 +0,0 @@ -program msk144sd -! -! A simple decoder for slow msk144. -! Can be used as a (slow) brute-force multi-decoder by looping -! over a set of carrier frequencies. -! - use options - use timer_module, only: timer - use timer_impl, only: init_timer - use readwav - - parameter (NRECENT=10) - parameter (NSPM=864) - parameter (NPATTERNS=4) - - character ch - character*80 line - character*500 infile - character*12 mycall,hiscall - character*6 mygrid - character(len=500) optarg - character*22 msgreceived - character*12 recent_calls(NRECENT) - - complex cdat(30*375) - complex c(NSPM) - complex ct(NSPM) - - real softbits(144) - real xmc(NPATTERNS) - - logical :: display_help=.false. - - type(wav_header) :: wav - - integer iavmask(8) - integer iavpatterns(8,NPATTERNS) - integer npkloc(10) - - integer*2 id2(30*12000) - integer*2 ichunk(7*1024) - - data iavpatterns/ & - 1,1,1,1,0,0,0,0, & - 0,1,1,1,1,0,0,0, & - 0,0,1,1,1,1,0,0, & - 1,1,1,1,1,1,0,0/ - data xmc/2.0,4.5,2.5,3.0/ - - type (option) :: long_options(2) = [ & - option ('frequency',.true.,'f','rxfreq',''), & - option ('help',.false.,'h','Display this help message','') & - ] - t0=0.0 - ntol=100 - nrxfreq=1500 - - do - call getopt('f:h',long_options,ch,optarg,narglen,nstat,noffset,nremain,.true.) - if( nstat .ne. 0 ) then - exit - end if - select case (ch) - case ('f') - read (optarg(:narglen), *) nrxfreq - case ('h') - display_help = .true. - end select - end do - - if(display_help .or. nstat.lt.0 .or. nremain.lt.1) then - print *, '' - print *, 'Usage: msk144sd [OPTIONS] file1 [file2 ...]' - print *, '' - print *, ' decode pre-recorded .WAV file(s)' - print *, '' - print *, 'OPTIONS:' - do i = 1, size (long_options) - call long_options(i) % print (6) - end do - go to 999 - endif - - call init_timer ('timer.out') - call timer('msk144 ',0) - ndecoded=0 - do ifile=noffset+1,noffset+nremain - call get_command_argument(ifile,optarg,narglen) - infile=optarg(:narglen) - call timer('read ',0) - call wav%read (infile) - i1=index(infile,'.wav') - if( i1 .eq. 0 ) i1=index(infile,'.WAV') - read(infile(i1-6:i1-1),*,err=998) nutc - inquire(FILE=infile,SIZE=isize) - npts=min((isize-216)/2,360000) - read(unit=wav%lun) id2(1:npts) - close(unit=wav%lun) - call timer('read ',1) - -! do if=1,89 ! brute force multi-decoder - fo=nrxfreq -! fo=(if-1)*25.0+300.0 - call msksddc(id2,npts,fo,cdat) - np=npts/32 - ntol=200 ! actual ntol is ntol/32=6.25 Hz. Detection window is 12.5 Hz wide - fc=1500.0 - call msk144spd(cdat,np,ntol,ndecodesuccess,msgreceived,fc,fest,tdec,navg,ct, & - softbits,recent_calls,nrecent) - nsnr=0 ! need an snr estimate - if( ndecodesuccess .eq. 1 ) then - fest=fo+fest-fc ! fudging because spd thinks input signal is at 1500 Hz - goto 900 - endif -! If short ping decoder doesn't find a decode - npat=NPATTERNS - do iavg=1,npat - iavmask=iavpatterns(1:8,iavg) - navg=sum(iavmask) - deltaf=4.0/real(navg) ! search increment for frequency sync - npeaks=4 - ntol=200 - fc=1500.0 - call msk144sync(cdat(1:6*NSPM),6,ntol,deltaf,iavmask,npeaks,fc, & - fest,npkloc,nsyncsuccess,xmax,c) - if( nsyncsuccess .eq. 0 ) cycle - - do ipk=1,npeaks - do is=1,3 - ic0=npkloc(ipk) - if(is.eq.2) ic0=max(1,ic0-1) - if(is.eq.3) ic0=min(NSPM,ic0+1) - ct=cshift(c,ic0-1) - call msk144decodeframe(ct,softbits,msgreceived,ndecodesuccess, & - recent_calls,nrecent) - if(ndecodesuccess .gt. 0) then - tdec=tsec+xmc(iavg)*tframe - fest=fo+(fest-fc)/32.0 - goto 900 - endif - enddo !Slicer dither - enddo !Peak loop - enddo - -! enddo -900 continue - if( ndecodesuccess .gt. 0 ) then - write(*,1020) nutc,nsnr,tdec,nint(fest),' % ',msgreceived,navg -1020 format(i6.6,i4,f5.1,i5,a3,a22,i4) - endif - enddo - - call timer('msk144 ',1) - call timer('msk144 ',101) - go to 999 - -998 print*,'Cannot read from file:' - print*,infile - -999 continue -end program msk144sd - -subroutine msksddc(id2,npts,fc,cdat) - -! The msk144 detector/demodulator/decoder will decode signals -! with carrier frequency, fc, in the range fN/4 +/- 0.03333*fN. -! -! For slow MSK144 with nslow=32: -! fs=12000/32=375 Hz, fN=187.5 Hz -! -! This routine accepts input samples with fs=12000 Hz. It -! downconverts and decimates by 32 to center a signal with input carrier -! frequency fc at new carrier frequency 1500/32=46.875 Hz. -! The analytic signal is returned. - - parameter (NFFT1=30*12000,NFFT2=30*375) - integer*2 id2(npts) - complex cx(0:NFFT1) - complex cdat(30*375) - - dt=1.0/12000.0 - df=1.0/(NFFT1*dt) - icenter=int(fc/df+0.5) - i46p875=int(46.875/df+0.5) - ishift=icenter-i46p875 - cx=cmplx(0.0,0.0) - cx(1:npts)=id2 - call four2a(cx,NFFT1,1,-1,1) - cx=cshift(cx,ishift) - cx(1)=0.5*cx(1) - cx(2*i46p875+1:)=cmplx(0.0,0.0) - call four2a(cx,NFFT2,1,1,1) - cdat(1:npts/32)=cx(0:npts/32-1)/NFFT1 - return - -end subroutine msksddc - diff --git a/lib/msk144sim.f90 b/lib/msk144sim.f90 index 75c6a1e64..7b2a8a00b 100644 --- a/lib/msk144sim.f90 +++ b/lib/msk144sim.f90 @@ -13,10 +13,10 @@ program msk144sim integer itone(144) !Message bits nargs=iargc() - if(nargs.ne.6) then - print*,'Usage: msk144sim message freq width nslow snr nfiles' - print*,'Example: msk144sim "K1ABC W9XYZ EN37" 1500 0.12 1 2 1' - print*,' msk144sim "K1ABC W9XYZ EN37" 1500 2.5 32 15 1' + if(nargs.ne.5) then + print*,'Usage: msk144sim message freq width snr nfiles' + print*,'Example: msk144sim "K1ABC W9XYZ EN37" 1500 0.12 2 1' + print*,' msk144sim "K1ABC W9XYZ EN37" 1500 2.5 15 1' go to 999 endif call getarg(1,msg) @@ -25,10 +25,8 @@ program msk144sim call getarg(3,arg) read(arg,*) width call getarg(4,arg) - read(arg,*) nslow - call getarg(5,arg) read(arg,*) snrdb - call getarg(6,arg) + call getarg(5,arg) read(arg,*) nfiles !sig is the peak amplitude of the ping. @@ -50,9 +48,9 @@ program msk144sim twopi=8.d0*atan(1.d0) nsym=144 - nsps=6*nslow + nsps=6 if( itone(41) .lt. 0 ) nsym=40 - baud=2000.d0/nslow + baud=2000.d0 dphi0=twopi*(freq-0.25d0*baud)/12000.d0 dphi1=twopi*(freq+0.25d0*baud)/12000.d0 phi=0.0 @@ -79,7 +77,7 @@ program msk144sim go to 999 endif - if(nslow.eq.1) call makepings(pings,NMAX,width,sig) + call makepings(pings,NMAX,width,sig) ! call sgran() do ifile=1,nfiles !Loop over requested number of files @@ -92,8 +90,7 @@ program msk144sim fac=sqrt(6000.0/2500.0) do i=0,NMAX-1 xx=gran() - if(nslow.eq.1) wave(i)=pings(i)*waveform(i) + fac*xx - if(nslow.gt.1) wave(i)=sig*waveform(i) + fac*xx + wave(i)=pings(i)*waveform(i) + fac*xx iwave(i)=30.0*wave(i) enddo diff --git a/lib/platanh.f90 b/lib/platanh.f90 new file mode 100644 index 000000000..e610366d7 --- /dev/null +++ b/lib/platanh.f90 @@ -0,0 +1,24 @@ +subroutine platanh(x,y) + isign=+1 + z=x + if( x.lt.0 ) then + isign=-1 + z=abs(x) + endif + if( z.le. 0.664 ) then + y=x/0.83 + return + elseif( z.le. 0.9217 ) then + y=isign*(z-0.4064)/0.322 + return + elseif( z.le. 0.9951 ) then + y=isign*(z-0.8378)/0.0524 + return + elseif( z.le. 0.9998 ) then + y=isign*(z-0.9914)/0.0012 + return + else + y=isign*7.0 + return + endif +end subroutine platanh diff --git a/lib/pltanh.f90 b/lib/pltanh.f90 new file mode 100644 index 000000000..4c6c2b6d6 --- /dev/null +++ b/lib/pltanh.f90 @@ -0,0 +1,24 @@ +subroutine pltanh(x,y) + isign=+1 + z=x + if( x.lt.0 ) then + isign=-1 + z=abs(x) + endif + if( z.le. 0.8 ) then + y=0.83*x + return + elseif( z.le. 1.6 ) then + y=isign*(0.322*z+0.4064) + return + elseif( z.le. 3.0 ) then + y=isign*(0.0524*z+0.8378) + return + elseif( z.lt. 7.0 ) then + y=isign*(0.0012*z+0.9914) + return + else + y=isign*0.9998 + return + endif +end subroutine pltanh diff --git a/lib/sync65.f90 b/lib/sync65.f90 index e0bc924a0..4a681a0a7 100644 --- a/lib/sync65.f90 +++ b/lib/sync65.f90 @@ -57,8 +57,9 @@ subroutine sync65(nfa,nfb,naggressive,ntol,nqsym,ca,ncand,nrobust, & freq=i*df itry=0 ! if(naggressive.gt.0 .and. ntol.lt.1000 .and. ccfmax.ge.thresh0) then - if(naggressive.gt.0 .and. ccfmax.ge.thresh0) then - if(i.ne.ipk) cycle +! if(naggressive.gt.0 .and. ccfmax.ge.thresh0) then + if(bVHF) then + if(i.ne.ipk .or. ccfmax.lt.thresh0) cycle itry=1 ncand=ncand+1 else diff --git a/lib/unpackmsg144.f90 b/lib/unpackmsg144.f90 deleted file mode 100644 index 96423ff69..000000000 --- a/lib/unpackmsg144.f90 +++ /dev/null @@ -1,117 +0,0 @@ - subroutine unpackmsg144(dat,msg,c1,c2) -! special unpackmsg for MSK144 - returns call1 and call2 to enable -! maintenance of a recent-calls-heard list - - use packjt - parameter (NBASE=37*36*10*27*27*27) - parameter (NGBASE=180*180) - integer dat(12) - character c1*12,c2*12,grid*4,msg*22,grid6*6,psfx*4,junk2*4 - logical cqnnn - - cqnnn=.false. - nc1=ishft(dat(1),22) + ishft(dat(2),16) + ishft(dat(3),10)+ & - ishft(dat(4),4) + iand(ishft(dat(5),-2),15) - - nc2=ishft(iand(dat(5),3),26) + ishft(dat(6),20) + & - ishft(dat(7),14) + ishft(dat(8),8) + ishft(dat(9),2) + & - iand(ishft(dat(10),-4),3) - - ng=ishft(iand(dat(10),15),12) + ishft(dat(11),6) + dat(12) - - if(ng.ge.32768) then - call unpacktext(nc1,nc2,ng,msg) - c1(1:12)=' ' - c2(1:12)=' ' - go to 100 - endif - - call unpackcall(nc1,c1,iv2,psfx) - if(iv2.eq.0) then - ! This is an "original JT65" message - if(nc1.eq.NBASE+1) c1='CQ ' - if(nc1.eq.NBASE+2) c1='QRZ ' - nfreq=nc1-NBASE-3 - if(nfreq.ge.0 .and. nfreq.le.999) then - write(c1,1002) nfreq - 1002 format('CQ ',i3.3) - cqnnn=.true. - endif - endif - - call unpackcall(nc2,c2,junk1,junk2) - call unpackgrid(ng,grid) - - if(iv2.gt.0) then - ! This is a JT65v2 message - do i=1,4 - if(ichar(psfx(i:i)).eq.0) psfx(i:i)=' ' - enddo - - n1=len_trim(psfx) - n2=len_trim(c2) - if(iv2.eq.1) msg='CQ '//psfx(:n1)//'/'//c2(:n2)//' '//grid - if(iv2.eq.2) msg='QRZ '//psfx(:n1)//'/'//c2(:n2)//' '//grid - if(iv2.eq.3) msg='DE '//psfx(:n1)//'/'//c2(:n2)//' '//grid - if(iv2.eq.4) msg='CQ '//c2(:n2)//'/'//psfx(:n1)//' '//grid - if(iv2.eq.5) msg='QRZ '//c2(:n2)//'/'//psfx(:n1)//' '//grid - if(iv2.eq.6) msg='DE '//c2(:n2)//'/'//psfx(:n1)//' '//grid - if(iv2.eq.7) msg='DE '//c2(:n2)//' '//grid - if(iv2.eq.8) msg=' ' - go to 100 - else - - endif - - grid6=grid//'ma' - call grid2k(grid6,k) - if(k.ge.1 .and. k.le.450) call getpfx2(k,c1) - if(k.ge.451 .and. k.le.900) call getpfx2(k,c2) - - i=index(c1,char(0)) - if(i.ge.3) c1=c1(1:i-1)//' ' - i=index(c2,char(0)) - if(i.ge.3) c2=c2(1:i-1)//' ' - - msg=' ' - j=0 - if(cqnnn) then - msg=c1//' ' - j=7 !### ??? ### - go to 10 - endif - - do i=1,12 - j=j+1 - msg(j:j)=c1(i:i) - if(c1(i:i).eq.' ') go to 10 - enddo - j=j+1 - msg(j:j)=' ' - - 10 do i=1,12 - if(j.le.21) j=j+1 - msg(j:j)=c2(i:i) - if(c2(i:i).eq.' ') go to 20 - enddo - if(j.le.21) j=j+1 - msg(j:j)=' ' - - 20 if(k.eq.0) then - do i=1,4 - if(j.le.21) j=j+1 - msg(j:j)=grid(i:i) - enddo - if(j.le.21) j=j+1 - msg(j:j)=' ' - endif - - 100 continue - if(msg(1:6).eq.'CQ9DX ') msg(3:3)=' ' - if(msg(1:2).eq.'E9' .and. & - msg(3:3).ge.'A' .and. msg(3:3).le.'Z' .and. & - msg(4:4).ge.'A' .and. msg(4:4).le.'Z' .and. & - msg(5:5).eq.' ') msg='CQ '//msg(3:) - - return - end subroutine unpackmsg144 diff --git a/logbook/WorkedBefore.cpp b/logbook/WorkedBefore.cpp index b6cd9eca8..41723e003 100644 --- a/logbook/WorkedBefore.cpp +++ b/logbook/WorkedBefore.cpp @@ -1,18 +1,34 @@ #include "WorkedBefore.hpp" +#include +#include #include +#include #include #include +#include +#include #include #include #include #include #include +#include +#include "qt_helpers.hpp" #include "pimpl_impl.hpp" using namespace boost::multi_index; +// hash function for QString members in hashed indexes +inline +std::size_t hash_value (QString const& s) +{ + return std::hash {} (s); +} + +// // worked before set element +// struct worked_entry { explicit worked_entry (QString const& call, QString const& grid, QString const& band @@ -39,7 +55,52 @@ struct worked_entry int ITU_zone_; }; -// less then predidate for the Continent enum class +bool operator == (worked_entry const& lhs, worked_entry const& rhs) +{ + return + lhs.continent_ == rhs.continent_ // check 1st as it is fast + && lhs.CQ_zone_ == rhs.CQ_zone_ // ditto + && lhs.ITU_zone_ == rhs.ITU_zone_ // ditto + && lhs.call_ == rhs.call_ // check the rest in decreasing + && lhs.grid_ == rhs.grid_ // domain size order to shortcut + && lhs.country_ == rhs.country_ // differences as quickly as possible + && lhs.band_ == rhs.band_ + && lhs.mode_ == rhs.mode_; +} + +std::size_t hash_value (worked_entry const& we) +{ + std::size_t seed {0}; + boost::hash_combine (seed, we.call_); + boost::hash_combine (seed, we.grid_); + boost::hash_combine (seed, we.band_); + boost::hash_combine (seed, we.mode_); + boost::hash_combine (seed, we.country_); + boost::hash_combine (seed, we.continent_); + boost::hash_combine (seed, we.CQ_zone_); + boost::hash_combine (seed, we.ITU_zone_); + return seed; +} + +#if !defined (QT_NO_DEBUG_STREAM) +QDebug operator << (QDebug dbg, worked_entry const& e) +{ + QDebugStateSaver saver {dbg}; + dbg.nospace () << "worked_entry(" + << e.call_ << ", " + << e.grid_ << ", " + << e.band_ << ", " + << e.mode_ << ", " + << e.country_ << ", " + << e.continent_ << ", " + << e.CQ_zone_ << ", " + << e.ITU_zone_ << ')'; + return dbg; +} +#endif + +// less then predidate for the Continent enum class, needed for +// ordered indexes struct Continent_less { bool operator () (AD1CCty::Continent lhs, AD1CCty::Continent rhs) const @@ -48,7 +109,7 @@ struct Continent_less } }; -// tags +// index tags struct call_mode_band {}; struct call_band {}; struct grid_mode_band {}; @@ -62,85 +123,92 @@ struct CQ_zone_band {}; struct ITU_zone_mode_band {}; struct ITU_zone_band {}; -// set with multiple ordered unique indexes that allow for efficient -// determination of various categories of worked before status +// set with multiple ordered unique indexes that allow for optimally +// efficient determination of various categories of worked before +// status typedef multi_index_container< worked_entry, indexed_by< + // basic unordered set constraint - we don't need duplicate worked entries + hashed_unique>, + + // + // The following indexes are used to discover worked before stuff. + // + // They are ordered so as to support partial lookups and + // non-unique because container inserts must be valid for all + // indexes. + // + // call+mode+band - ordered_unique, - composite_key, - member, - member > >, + ordered_non_unique, + composite_key, + member, + member > >, // call+band - ordered_unique, - composite_key, - member > >, + ordered_non_unique, + composite_key, + member > >, // grid+mode+band - ordered_unique, - composite_key, - member, - member > >, + ordered_non_unique, + composite_key, + member, + member > >, // grid+band - ordered_unique, - composite_key, - member > >, + ordered_non_unique, + composite_key, + member > >, // country+mode+band - ordered_unique, - composite_key, - member, - member > >, + ordered_non_unique, + composite_key, + member, + member > >, // country+band - ordered_unique, - composite_key, - member > >, + ordered_non_unique, + composite_key, + member > >, // continent+mode+band - ordered_unique, - composite_key, - member, - member >, - composite_key_compare< - Continent_less, - std::less, - std::less > >, + ordered_non_unique, + composite_key, + member, + member >, + composite_key_compare, std::less > >, // continent+band - ordered_unique, - composite_key, - member >, - composite_key_compare< - Continent_less, - std::less > >, + ordered_non_unique, + composite_key, + member >, + composite_key_compare > >, // CQ-zone+mode+band - ordered_unique, - composite_key, - member, - member > >, + ordered_non_unique, + composite_key, + member, + member > >, // CQ-zone+band - ordered_unique, - composite_key, - member > >, + ordered_non_unique, + composite_key, + member > >, // ITU-zone+mode+band - ordered_unique, - composite_key, - member, - member > >, + ordered_non_unique, + composite_key, + member, + member > >, // ITU-zone+band - ordered_unique, - composite_key, - member > > > - > worked_type; + ordered_non_unique, + composite_key, + member > > > + > worked_before_database_type; namespace { @@ -188,7 +256,7 @@ public: QString path_; AD1CCty prefixes_; - worked_type worked_; + worked_before_database_type worked_; }; WorkedBefore::WorkedBefore () @@ -196,7 +264,7 @@ WorkedBefore::WorkedBefore () QFile inputFile {m_->path_}; if (inputFile.open (QFile::ReadOnly)) { - QTextStream in(&inputFile); + QTextStream in {&inputFile}; QString buffer; bool pre_read {false}; int end_position {-1}; diff --git a/main.cpp b/main.cpp index 784b709cd..601ae6a7e 100644 --- a/main.cpp +++ b/main.cpp @@ -52,6 +52,38 @@ namespace qsrand (seed); // this is good for rand() as well } } seeding; + + // We can't use the GUI after QApplication::exit() is called so + // uncaught exceptions can get lost on Windows systems where there + // is no console terminal, so here we override + // QApplication::notify() and wrap the base class call with a try + // block to catch and display exceptions in a message box. + class ExceptionCatchingApplication final + : public QApplication + { + public: + explicit ExceptionCatchingApplication (int& argc, char * * argv) + : QApplication {argc, argv} + { + } + bool notify (QObject * receiver, QEvent * e) override + { + try + { + return QApplication::notify (receiver, e); + } + catch (std::exception const& e) + { + MessageBox::critical_message (nullptr, translate ("main", "Fatal error"), e.what ()); + throw; + } + catch (...) + { + MessageBox::critical_message (nullptr, translate ("main", "Unexpected fatal error")); + throw; + } + } + }; } int main(int argc, char *argv[]) @@ -68,7 +100,7 @@ int main(int argc, char *argv[]) // Multiple instances communicate with jt9 via this QSharedMemory mem_jt9; - QApplication a(argc, argv); + ExceptionCatchingApplication a(argc, argv); try { setlocale (LC_NUMERIC, "C"); // ensure number forms are in @@ -339,12 +371,10 @@ int main(int argc, char *argv[]) } catch (std::exception const& e) { - MessageBox::critical_message (nullptr, a.translate ("main", "Fatal error"), e.what ()); std::cerr << "Error: " << e.what () << '\n'; } catch (...) { - MessageBox::critical_message (nullptr, a.translate ("main", "Unexpected fatal error")); std::cerr << "Unexpected fatal error\n"; throw; // hoping the runtime might tell us more about the exception } diff --git a/models/CabrilloLog.cpp b/models/CabrilloLog.cpp index 3e218d643..965d6f957 100644 --- a/models/CabrilloLog.cpp +++ b/models/CabrilloLog.cpp @@ -52,7 +52,7 @@ CabrilloLog::impl::impl (Configuration const * configuration) SQL_error_check (export_query_, &QSqlQuery::prepare, "SELECT frequency, \"when\", exchange_sent, call, exchange_rcvd FROM cabrillo_log ORDER BY \"when\""); - setEditStrategy (QSqlTableModel::OnManualSubmit); + setEditStrategy (QSqlTableModel::OnRowChange); setTable ("cabrillo_log"); setHeaderData (fieldIndex ("frequency"), Qt::Horizontal, tr ("Freq(kHz)")); setHeaderData (fieldIndex ("when"), Qt::Horizontal, tr ("Date & Time(UTC)")); @@ -73,24 +73,49 @@ CabrilloLog::~CabrilloLog () { } -QAbstractItemModel * CabrilloLog::model () +QSqlTableModel * CabrilloLog::model () { return &*m_; } -void CabrilloLog::add_QSO (Frequency frequency, QDateTime const& when, QString const& call +namespace +{ + void set_value_maybe_null (QSqlRecord& record, QString const& name, QString const& value) + { + if (value.size ()) + { + record.setValue (name, value); + } + else + { + record.setNull (name); + } + } +} + +bool CabrilloLog::add_QSO (Frequency frequency, QDateTime const& when, QString const& call , QString const& exchange_sent, QString const& exchange_received) { - ConditionalTransaction transaction {*m_}; auto record = m_->record (); record.setValue ("frequency", frequency / 1000ull); // kHz - record.setValue ("when", when.toMSecsSinceEpoch () / 1000ull); - record.setValue ("call", call); - record.setValue ("exchange_sent", exchange_sent); - record.setValue ("exchange_rcvd", exchange_received); - record.setValue ("band", m_->configuration_->bands ()->find (frequency)); - SQL_error_check (*m_, &QSqlTableModel::insertRecord, -1, record); - transaction.submit (); + if (!when.isNull ()) + { + record.setValue ("when", when.toMSecsSinceEpoch () / 1000ull); + } + else + { + record.setNull ("when"); + } + set_value_maybe_null (record, "call", call); + set_value_maybe_null (record, "exchange_sent", exchange_sent); + set_value_maybe_null (record, "exchange_rcvd", exchange_received); + set_value_maybe_null (record, "band", m_->configuration_->bands ()->find (frequency)); + auto ok = m_->insertRecord (-1, record); + if (ok) + { + m_->select (); // to refresh views + } + return ok; } bool CabrilloLog::dupe (Frequency frequency, QString const& call) const @@ -106,9 +131,12 @@ void CabrilloLog::reset () { if (m_->rowCount ()) { + m_->setEditStrategy (QSqlTableModel::OnManualSubmit); ConditionalTransaction transaction {*m_}; SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {}); transaction.submit (); + m_->select (); // to refresh views + m_->setEditStrategy (QSqlTableModel::OnRowChange); } } @@ -128,7 +156,7 @@ void CabrilloLog::export_qsos (QTextStream& stream) const frequency = frequency > 50000000ull ? frequency / 1000ull : frequency; stream << QString {"QSO: %1 DG %2 %3 %4 %5 %6\n"} .arg (frequency, 5) - .arg (QDateTime::fromMSecsSinceEpoch (m_->export_query_.value (when_index).toULongLong () * 1000ull).toString ("yyyy-MM-dd hhmm")) + .arg (QDateTime::fromMSecsSinceEpoch (m_->export_query_.value (when_index).toULongLong () * 1000ull, Qt::UTC).toString ("yyyy-MM-dd hhmm")) .arg (my_call, -12) .arg (m_->export_query_.value (sent_index).toString (), -13) .arg (m_->export_query_.value (call_index).toString (), -12) diff --git a/models/CabrilloLog.hpp b/models/CabrilloLog.hpp index 9571adeeb..43b9e8f94 100644 --- a/models/CabrilloLog.hpp +++ b/models/CabrilloLog.hpp @@ -8,7 +8,7 @@ class Configuration; class QDateTime; class QString; -class QAbstractItemModel; +class QSqlTableModel; class QTextStream; class CabrilloLog final @@ -21,11 +21,11 @@ public: ~CabrilloLog (); // returns false if insert fails - void add_QSO (Frequency, QDateTime const&, QString const& call + bool add_QSO (Frequency, QDateTime const&, QString const& call , QString const& report_sent, QString const& report_received); bool dupe (Frequency, QString const& call) const; - QAbstractItemModel * model (); + QSqlTableModel * model (); void reset (); void export_qsos (QTextStream&) const; diff --git a/models/FoxLog.cpp b/models/FoxLog.cpp index 81763d9be..1503e5662 100644 --- a/models/FoxLog.cpp +++ b/models/FoxLog.cpp @@ -43,7 +43,7 @@ FoxLog::impl::impl () SQL_error_check (dupe_query_, &QSqlQuery::prepare, "SELECT COUNT(*) FROM fox_log WHERE call = :call AND band = :band"); - setEditStrategy (QSqlTableModel::OnManualSubmit); + setEditStrategy (QSqlTableModel::OnRowChange); setTable ("fox_log"); setHeaderData (fieldIndex ("when"), Qt::Horizontal, tr ("Date & Time(UTC)")); setHeaderData (fieldIndex ("call"), Qt::Horizontal, tr ("Call")); @@ -62,7 +62,7 @@ FoxLog::~FoxLog () { } -QAbstractItemModel * FoxLog::model () +QSqlTableModel * FoxLog::model () { return &*m_; } @@ -86,21 +86,26 @@ bool FoxLog::add_QSO (QDateTime const& when, QString const& call, QString const& , QString const& report_sent, QString const& report_received , QString const& band) { - ConditionalTransaction transaction {*m_}; auto record = m_->record (); - record.setValue ("when", when.toMSecsSinceEpoch () / 1000); - record.setValue ("call", call); + if (!when.isNull ()) + { + record.setValue ("when", when.toMSecsSinceEpoch () / 1000); + } + else + { + record.setNull ("when"); + } + set_value_maybe_null (record, "call", call); set_value_maybe_null (record, "grid", grid); set_value_maybe_null (record, "report_sent", report_sent); set_value_maybe_null (record, "report_rcvd", report_received); - record.setValue ("band", band); - SQL_error_check (*m_, &QSqlTableModel::insertRecord, -1, record); - if (!transaction.submit (false)) + set_value_maybe_null (record, "band", band); + auto ok = m_->insertRecord (-1, record); + if (ok) { - transaction.revert (); - return false; + m_->select (); // to refresh views } - return true; + return ok; } bool FoxLog::dupe (QString const& call, QString const& band) const @@ -116,8 +121,11 @@ void FoxLog::reset () { if (m_->rowCount ()) { + m_->setEditStrategy (QSqlTableModel::OnManualSubmit); ConditionalTransaction transaction {*m_}; SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {}); transaction.submit (); + m_->select (); // to refresh views + m_->setEditStrategy (QSqlTableModel::OnRowChange); } } diff --git a/models/FoxLog.hpp b/models/FoxLog.hpp index 028f8205c..caa8e358f 100644 --- a/models/FoxLog.hpp +++ b/models/FoxLog.hpp @@ -6,7 +6,7 @@ class QDateTime; class QString; -class QAbstractItemModel; +class QSqlTableModel; class FoxLog final : private boost::noncopyable @@ -21,7 +21,7 @@ public: , QString const& band); bool dupe (QString const& call, QString const& band) const; - QAbstractItemModel * model (); + QSqlTableModel * model (); void reset (); private: diff --git a/models/FrequencyList.cpp b/models/FrequencyList.cpp index e50afe2a4..55844bdfa 100644 --- a/models/FrequencyList.cpp +++ b/models/FrequencyList.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -350,14 +351,6 @@ bool FrequencyList_v2::remove (Item f) return m_->removeRow (row); } -namespace -{ - bool row_is_higher (QModelIndex const& lhs, QModelIndex const& rhs) - { - return lhs.row () > rhs.row (); - } -} - bool FrequencyList_v2::removeDisjointRows (QModelIndexList rows) { bool result {true}; @@ -371,7 +364,10 @@ bool FrequencyList_v2::removeDisjointRows (QModelIndexList rows) } // reverse sort by row - qSort (rows.begin (), rows.end (), row_is_higher); + std::sort (rows.begin (), rows.end (), [] (QModelIndex const& lhs, QModelIndex const& rhs) + { + return rhs.row () < lhs.row (); // reverse row ordering + }); Q_FOREACH (auto index, rows) { if (result && !m_->removeRow (index.row ())) diff --git a/models/StationList.cpp b/models/StationList.cpp index cfd629ede..eeeb4d607 100644 --- a/models/StationList.cpp +++ b/models/StationList.cpp @@ -139,14 +139,6 @@ bool StationList::remove (Station s) return removeRow (row); } -namespace -{ - bool row_is_higher (QModelIndex const& lhs, QModelIndex const& rhs) - { - return lhs.row () > rhs.row (); - } -} - bool StationList::removeDisjointRows (QModelIndexList rows) { bool result {true}; @@ -160,7 +152,10 @@ bool StationList::removeDisjointRows (QModelIndexList rows) } // reverse sort by row - qSort (rows.begin (), rows.end (), row_is_higher); + std::sort (rows.begin (), rows.end (), [] (QModelIndex const& lhs, QModelIndex const& rhs) + { + return rhs.row () < lhs.row (); // reverse row ordering + }); Q_FOREACH (auto index, rows) { if (result && !m_->removeRow (index.row ())) diff --git a/qt_db_helpers.hpp b/qt_db_helpers.hpp index 3f159a889..49a082f72 100644 --- a/qt_db_helpers.hpp +++ b/qt_db_helpers.hpp @@ -25,37 +25,56 @@ class ConditionalTransaction final { public: explicit ConditionalTransaction (QSqlTableModel& model) - : model_ {model} + : model_ (model) , submitted_ {false} { model_.database ().transaction (); } + bool submit (bool throw_on_error = true) { - Q_ASSERT (model_.isDirty ()); bool ok {true}; if (throw_on_error) { - SQL_error_check (model_, &QSqlTableModel::submitAll); + SQL_error_check (model_ + , QSqlTableModel::OnManualSubmit == model_.editStrategy () + ? &QSqlTableModel::submitAll + : &QSqlTableModel::submit); } else { - ok = model_.submitAll (); + ok = QSqlTableModel::OnManualSubmit == model_.editStrategy () + ? model_.submitAll () : model_.submit (); } submitted_ = submitted_ || ok; return ok; } + void revert () { - Q_ASSERT (model_.isDirty ()); - model_.revertAll (); + if (QSqlTableModel::OnManualSubmit == model_.editStrategy ()) + { + model_.revertAll (); + } + else + { + model_.revert (); + } } + ~ConditionalTransaction () { if (model_.isDirty ()) { // abandon un-submitted changes to the model - model_.revertAll (); + if (QSqlTableModel::OnManualSubmit == model_.editStrategy ()) + { + model_.revertAll (); + } + else + { + model_.revert (); + } } auto database = model_.database (); if (submitted_) @@ -67,6 +86,7 @@ public: database.rollback (); } } + private: QSqlTableModel& model_; bool submitted_; diff --git a/qt_helpers.hpp b/qt_helpers.hpp index 0e9439a8a..b2ec7a49f 100644 --- a/qt_helpers.hpp +++ b/qt_helpers.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -77,6 +78,26 @@ public: } }; +namespace std +{ + // std::hash<> specialization for QString based on the dbj2 + // algorithm http://www.cse.yorku.ca/~oz/hash.html because qHash() + // is poor on 64-bit platforms due to being a 32-bit hash value + template<> + struct hash + { + std::size_t operator () (QString const& s) const noexcept + { + std::size_t hash {5381}; + for (int i = 0; i < s.size (); ++i) + { + hash = ((hash << 5) + hash) + ((s.at (i).row () << 8) | s.at (i).cell ()); + } + return hash; + } + }; +} + // Register some useful Qt types with QMetaType Q_DECLARE_METATYPE (QHostAddress); diff --git a/widgets/AbstractLogWindow.cpp b/widgets/AbstractLogWindow.cpp new file mode 100644 index 000000000..e796cdc3e --- /dev/null +++ b/widgets/AbstractLogWindow.cpp @@ -0,0 +1,133 @@ +#include "AbstractLogWindow.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Configuration.hpp" +#include "SettingsGroup.hpp" +#include "MessageBox.hpp" +#include "models/FontOverrideModel.hpp" +#include "pimpl_impl.hpp" + +class AbstractLogWindow::impl final +{ +public: + impl (AbstractLogWindow * self, QString const& settings_key, QSettings * settings + , Configuration const * configuration) + : self_ {self} + , settings_key_ {settings_key} + , settings_ {settings} + , configuration_ {configuration} + , log_view_ {nullptr} + { + } + + void delete_QSOs (); + + AbstractLogWindow * self_; + QString settings_key_; + QSettings * settings_; + Configuration const * configuration_; + QTableView * log_view_; + FontOverrideModel model_; +}; + +namespace +{ + bool row_is_higher (QModelIndex const& lhs, QModelIndex const& rhs) + { + return lhs.row () > rhs.row (); + } +} + +void AbstractLogWindow::impl::delete_QSOs () +{ + auto selection_model = log_view_->selectionModel (); + selection_model->select (selection_model->selection (), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); + auto row_indexes = selection_model->selectedRows (); + + if (row_indexes.size () + && MessageBox::Yes == MessageBox::query_message (self_ + , tr ("Confirm Delete") + , tr ("Are you sure you want to delete the %n " + "selected QSO(s) from the log", "" + , row_indexes.size ()))) + { + // We must work with source model indexes because we don't want row + // removes to invalidate model indexes we haven't yet processed. We + // achieve that by processing them in decending row order. + for (auto& row_index : row_indexes) + { + row_index = model_.mapToSource (row_index); + } + + // reverse sort by row + std::sort (row_indexes.begin (), row_indexes.end (), row_is_higher); + for (auto index : row_indexes) + { + auto row = model_.mapFromSource (index).row (); + model_.removeRow (row); + self_->log_model_changed (); + } + } +} + +AbstractLogWindow::AbstractLogWindow (QString const& settings_key, QSettings * settings + , Configuration const * configuration + , QWidget * parent) + : QWidget {parent} + , m_ {this, settings_key, settings, configuration} +{ + // ensure view scrolls to latest new row + connect (&m_->model_, &QAbstractItemModel::rowsInserted, [this] (QModelIndex const& /*parent*/, int /*first*/, int /*last*/) { + if (m_->log_view_) m_->log_view_->scrollToBottom (); + }); +} + +AbstractLogWindow::~AbstractLogWindow () +{ + SettingsGroup g {m_->settings_, m_->settings_key_}; + m_->settings_->setValue ("window/geometry", saveGeometry ()); +} + +void AbstractLogWindow::set_log_view (QTableView * log_view) +{ + // do this here because we know the UI must be setup before this + SettingsGroup g {m_->settings_, m_->settings_key_}; + restoreGeometry (m_->settings_->value ("window/geometry").toByteArray ()); + m_->log_view_ = log_view; + m_->log_view_->setContextMenuPolicy (Qt::ActionsContextMenu); + m_->log_view_->setAlternatingRowColors (true); + m_->log_view_->setSelectionBehavior (QAbstractItemView::SelectRows); + m_->log_view_->setSelectionMode (QAbstractItemView::ExtendedSelection); + m_->model_.setSourceModel (m_->log_view_->model ()); + m_->log_view_->setModel (&m_->model_); + m_->log_view_->setColumnHidden (0, true); + auto horizontal_header = log_view->horizontalHeader (); + horizontal_header->setSectionResizeMode (QHeaderView::ResizeToContents); + horizontal_header->setSectionsMovable (true); + m_->log_view_->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents); + set_log_view_font (m_->configuration_->decoded_text_font ()); + m_->log_view_->scrollToBottom (); + + // actions + auto delete_action = new QAction {tr ("&Delete ..."), m_->log_view_}; + m_->log_view_->insertAction (nullptr, delete_action); + connect (delete_action, &QAction::triggered, [this] (bool /*checked*/) { + m_->delete_QSOs (); + }); +} + +void AbstractLogWindow::set_log_view_font (QFont const& font) +{ + // m_->log_view_->setFont (font); + // m_->log_view_->horizontalHeader ()->setFont (font); + // m_->log_view_->verticalHeader ()->setFont (font); + m_->model_.set_font (font); +} diff --git a/widgets/AbstractLogWindow.hpp b/widgets/AbstractLogWindow.hpp new file mode 100644 index 000000000..581212d82 --- /dev/null +++ b/widgets/AbstractLogWindow.hpp @@ -0,0 +1,41 @@ +#ifndef ABSTRACT_LOG_WINDOW_HPP_ +#define ABSTRACT_LOG_WINDOW_HPP_ + +#include +#include "pimpl_h.hpp" + +class QString; +class QSettings; +class Configuration; +class QTableView; +class QFont; + +// +// AbstractLogWindow - Base class for log view windows +// +// QWidget that manages the common functionality shared by windows +// that include a QSO log view. +// +class AbstractLogWindow + : public QWidget +{ +public: + AbstractLogWindow (QString const& settings_key, QSettings * settings + , Configuration const * configuration + , QWidget * parent = nullptr); + virtual ~AbstractLogWindow () = 0; + + // set the QTableView that shows the log records, must have its + // model set before calling this + void set_log_view (QTableView *); + + void set_log_view_font (QFont const&); + +private: + virtual void log_model_changed (int row = -1) = 0; + + class impl; + pimpl m_; +}; + +#endif diff --git a/widgets/CabrilloLogWindow.cpp b/widgets/CabrilloLogWindow.cpp index 4a86b4509..20f39e4e6 100644 --- a/widgets/CabrilloLogWindow.cpp +++ b/widgets/CabrilloLogWindow.cpp @@ -1,77 +1,87 @@ #include "CabrilloLogWindow.hpp" -#include #include -#include -#include -#include -#include -#include - -#include "SettingsGroup.hpp" +#include +#include #include "Configuration.hpp" #include "models/Bands.hpp" #include "item_delegates/ForeignKeyDelegate.hpp" #include "item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp" #include "item_delegates/CallsignDelegate.hpp" -#include "item_delegates/MaidenheadLocatorDelegate.hpp" -#include "widgets/MessageBox.hpp" -#include "qt_helpers.hpp" +#include "pimpl_impl.hpp" #include "ui_CabrilloLogWindow.h" -CabrilloLogWindow::CabrilloLogWindow (QSettings * settings, Configuration const * configuration - , QAbstractItemModel * cabrillo_log_model, QWidget * parent) - : QWidget {parent} - , settings_ {settings} - , configuration_ {configuration} - , ui_ {new Ui::CabrilloLogWindow} +namespace { - cabrillo_log_model_.setSourceModel (cabrillo_log_model); - setWindowTitle (QApplication::applicationName () + " - Cabrillo Log"); - ui_->setupUi (this); - read_settings (); - change_font (configuration_->decoded_text_font ()); - ui_->log_table_view->setModel (&cabrillo_log_model_); - ui_->log_table_view->setColumnHidden (0, true); - ui_->log_table_view->setItemDelegateForColumn (2, new DateTimeAsSecsSinceEpochDelegate {this}); - ui_->log_table_view->setItemDelegateForColumn (3, new CallsignDelegate {this}); - ui_->log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration_->bands (), &cabrillo_log_model_, 0, 6, this}); - ui_->log_table_view->setSelectionMode (QTableView::SingleSelection); - auto horizontal_header = ui_->log_table_view->horizontalHeader (); - horizontal_header->setStretchLastSection (true); - horizontal_header->setSectionResizeMode (QHeaderView::ResizeToContents); - horizontal_header->setSectionsMovable (true); - horizontal_header->moveSection (6, 1); // band to first column - ui_->log_table_view->scrollToBottom (); + class FormatProxyModel final + : public QIdentityProxyModel + { + public: + explicit FormatProxyModel (QObject * parent = nullptr) + : QIdentityProxyModel {parent} + { + } - // ensure view scrolls to latest new row - connect (&cabrillo_log_model_, &QAbstractItemModel::rowsInserted, [this] (QModelIndex const& /*parent*/, int /*first*/, int /*last*/) { - ui_->log_table_view->scrollToBottom (); - }); + QVariant data (QModelIndex const& index, int role) const override + { + if (Qt::TextAlignmentRole == role && index.isValid ()) + { + switch (index.column ()) + { + case 1: + case 6: + return Qt::AlignRight + Qt::AlignVCenter; + default: + break; + } + } + return QIdentityProxyModel::data (index, role); + } + }; +} + +class CabrilloLogWindow::impl final +{ +public: + explicit impl (QSqlTableModel * log_model) + : log_model_ {log_model} + { + } + + QSqlTableModel * log_model_; + FormatProxyModel format_model_; + Ui::CabrilloLogWindow ui_; +}; + +CabrilloLogWindow::CabrilloLogWindow (QSettings * settings, Configuration const * configuration + , QSqlTableModel * cabrillo_log_model, QWidget * parent) + : AbstractLogWindow {"Cabrillo Log Window", settings, configuration, parent} + , m_{cabrillo_log_model} +{ + setWindowTitle (QApplication::applicationName () + " - Cabrillo Log"); + m_->ui_.setupUi (this); + m_->format_model_.setSourceModel (m_->log_model_); + m_->ui_.log_table_view->setModel (&m_->format_model_); + set_log_view (m_->ui_.log_table_view); + m_->ui_.log_table_view->setItemDelegateForColumn (2, new DateTimeAsSecsSinceEpochDelegate {this}); + m_->ui_.log_table_view->setItemDelegateForColumn (3, new CallsignDelegate {this}); + m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), m_->log_model_, 0, 6, this}); + m_->ui_.log_table_view->horizontalHeader ()->moveSection (6, 1); // band to first column } CabrilloLogWindow::~CabrilloLogWindow () { - write_settings (); } -void CabrilloLogWindow::read_settings () +void CabrilloLogWindow::log_model_changed (int row) { - SettingsGroup g {settings_, "Cabrillo Log Window"}; - restoreGeometry (settings_->value ("window/geometery").toByteArray ()); -} - -void CabrilloLogWindow::write_settings () const -{ - SettingsGroup g {settings_, "Cabrillo Log Window"}; - settings_->setValue ("window/geometery", saveGeometry ()); -} - -void CabrilloLogWindow::change_font (QFont const& font) -{ - // ui_->log_table_view->setFont (font); - // ui_->log_table_view->horizontalHeader ()->setFont (font); - // ui_->log_table_view->verticalHeader ()->setFont (font); - cabrillo_log_model_.set_font (font); + if (row >= 0) + { + m_->log_model_->selectRow (row); + } + else + { + m_->log_model_->select (); + } } diff --git a/widgets/CabrilloLogWindow.hpp b/widgets/CabrilloLogWindow.hpp index 9a352ce9d..e2aa1627a 100644 --- a/widgets/CabrilloLogWindow.hpp +++ b/widgets/CabrilloLogWindow.hpp @@ -1,39 +1,27 @@ #ifndef CABRILLO_LOG_WINDOW_HPP_ #define CABRILLO_LOG_WINDOW_HPP_ -#include -#include -#include -#include "models/FontOverrideModel.hpp" +#include "AbstractLogWindow.hpp" +#include "pimpl_h.hpp" class QSettings; class Configuration; class QFont; -class QDateTime; -class QAbstractItemModel; -namespace Ui -{ - class CabrilloLogWindow; -} +class QSqlTableModel; class CabrilloLogWindow final - : public QWidget + : public AbstractLogWindow { public: - explicit CabrilloLogWindow (QSettings *, Configuration const *, QAbstractItemModel * cabrillo_log_model - , QWidget * parent = nullptr); + explicit CabrilloLogWindow (QSettings *, Configuration const *, QSqlTableModel * cabrillo_log_model + , QWidget * parent = nullptr); ~CabrilloLogWindow (); - void change_font (QFont const&); - private: - void read_settings (); - void write_settings () const; + void log_model_changed (int row) override; - QSettings * settings_; - Configuration const * configuration_; - FontOverrideModel cabrillo_log_model_; - QScopedPointer ui_; + class impl; + pimpl m_; }; #endif diff --git a/widgets/CabrilloLogWindow.ui b/widgets/CabrilloLogWindow.ui index eb25d1a6b..efefe5d3c 100644 --- a/widgets/CabrilloLogWindow.ui +++ b/widgets/CabrilloLogWindow.ui @@ -2,12 +2,27 @@ CabrilloLogWindow + + + 0 + 0 + 493 + 210 + + Contest Log - + + + <html><head/><body><p>Right-click here for available actions.</p></body></html> + + + true + + diff --git a/widgets/ExportCabrillo.cpp b/widgets/ExportCabrillo.cpp index 928a60dc9..0e61f0f08 100644 --- a/widgets/ExportCabrillo.cpp +++ b/widgets/ExportCabrillo.cpp @@ -76,7 +76,7 @@ void ExportCabrillo::save_log () auto fname = QFileDialog::getSaveFileName (this , tr ("Save Log File") , configuration_->writeable_data_dir ().absolutePath () - , tr ("Cabrillo Log (*.log)")); + , tr ("Cabrillo Log (*.cbr)")); if (fname.size ()) { QFile f {fname}; diff --git a/widgets/FoxLogWindow.cpp b/widgets/FoxLogWindow.cpp index 252045359..0f96529aa 100644 --- a/widgets/FoxLogWindow.cpp +++ b/widgets/FoxLogWindow.cpp @@ -1,96 +1,97 @@ #include "FoxLogWindow.hpp" -#include #include -#include +#include +#include +#include #include -#include -#include -#include #include "SettingsGroup.hpp" #include "Configuration.hpp" +#include "MessageBox.hpp" #include "models/Bands.hpp" #include "item_delegates/ForeignKeyDelegate.hpp" #include "item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp" #include "item_delegates/CallsignDelegate.hpp" #include "item_delegates/MaidenheadLocatorDelegate.hpp" -#include "widgets/MessageBox.hpp" -#include "qt_helpers.hpp" +#include "pimpl_impl.hpp" #include "ui_FoxLogWindow.h" +#include "moc_FoxLogWindow.cpp" + +class FoxLogWindow::impl final +{ +public: + explicit impl (QSqlTableModel * log_model) + : log_model_ {log_model} + { + } + + QSqlTableModel * log_model_; + Ui::FoxLogWindow ui_; +}; FoxLogWindow::FoxLogWindow (QSettings * settings, Configuration const * configuration - , QAbstractItemModel * fox_log_model, QWidget * parent) - : QWidget {parent} - , settings_ {settings} - , configuration_ {configuration} - , ui_ {new Ui::FoxLogWindow} + , QSqlTableModel * fox_log_model, QWidget * parent) + : AbstractLogWindow {"Fox Log Window", settings, configuration, parent} + , m_ {fox_log_model} { - fox_log_model_.setSourceModel (fox_log_model); setWindowTitle (QApplication::applicationName () + " - Fox Log"); - ui_->setupUi (this); - read_settings (); - change_font (configuration_->decoded_text_font ()); - ui_->log_table_view->setModel (&fox_log_model_); - ui_->log_table_view->setColumnHidden (0, true); - ui_->log_table_view->setItemDelegateForColumn (1, new DateTimeAsSecsSinceEpochDelegate {this}); - ui_->log_table_view->setItemDelegateForColumn (2, new CallsignDelegate {this}); - ui_->log_table_view->setItemDelegateForColumn (3, new MaidenheadLocatorDelegate {this}); - ui_->log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration_->bands (), &fox_log_model_, 0, 6, this}); - ui_->log_table_view->setSelectionMode (QTableView::SingleSelection); - auto horizontal_header = ui_->log_table_view->horizontalHeader (); - horizontal_header->setStretchLastSection (true); - horizontal_header->setSectionResizeMode (QHeaderView::ResizeToContents); - horizontal_header->setSectionsMovable (true); - horizontal_header->moveSection (6, 1); // move band to first column - ui_->rate_label->setNum (0); - ui_->queued_label->setNum (0); - ui_->callers_label->setNum (0); - ui_->log_table_view->scrollToBottom (); + m_->ui_.setupUi (this); + m_->ui_.log_table_view->setModel (m_->log_model_); + set_log_view (m_->ui_.log_table_view); + m_->ui_.log_table_view->setItemDelegateForColumn (1, new DateTimeAsSecsSinceEpochDelegate {this}); + m_->ui_.log_table_view->setItemDelegateForColumn (2, new CallsignDelegate {this}); + m_->ui_.log_table_view->setItemDelegateForColumn (3, new MaidenheadLocatorDelegate {this}); + m_->ui_.log_table_view->setItemDelegateForColumn (6, new ForeignKeyDelegate {configuration->bands (), m_->log_model_, 0, 6, this}); + m_->ui_.log_table_view->horizontalHeader ()->moveSection (6, 1); // move band to first column + m_->ui_.rate_label->setNum (0); + m_->ui_.queued_label->setNum (0); + m_->ui_.callers_label->setNum (0); - // ensure view scrolls to latest new row - connect (&fox_log_model_, &QAbstractItemModel::rowsInserted, [this] (QModelIndex const& /*parent*/, int /*first*/, int /*last*/) { - ui_->log_table_view->scrollToBottom (); + // actions + auto reset_action = new QAction {tr ("&Reset ..."), m_->ui_.log_table_view}; + m_->ui_.log_table_view->insertAction (nullptr, reset_action); + connect (reset_action, &QAction::triggered, [this, configuration] (bool /*checked*/) { + if (MessageBox::Yes == MessageBox::query_message( this + , tr ("Confirm Reset") + , tr ("Are you sure you want to erase file FoxQSO.txt " + "and start a new Fox log?"))) + { + QFile f{configuration->writeable_data_dir ().absoluteFilePath ("FoxQSO.txt")}; + f.remove (); + Q_EMIT reset_log_model (); + } }); } FoxLogWindow::~FoxLogWindow () { - write_settings (); -} - -void FoxLogWindow::read_settings () -{ - SettingsGroup g {settings_, "Fox Log Window"}; - restoreGeometry (settings_->value ("window/geometery").toByteArray ()); -} - -void FoxLogWindow::write_settings () const -{ - SettingsGroup g {settings_, "Fox Log Window"}; - settings_->setValue ("window/geometery", saveGeometry ()); -} - -void FoxLogWindow::change_font (QFont const& font) -{ - // ui_->log_table_view->setFont (font); - // ui_->log_table_view->horizontalHeader ()->setFont (font); - // ui_->log_table_view->verticalHeader ()->setFont (font); - fox_log_model_.set_font (font); } void FoxLogWindow::callers (int n) { - ui_->callers_label->setNum (n); + m_->ui_.callers_label->setNum (n); } void FoxLogWindow::queued (int n) { - ui_->queued_label->setNum (n); + m_->ui_.queued_label->setNum (n); } void FoxLogWindow::rate (int n) { - ui_->rate_label->setNum (n); + m_->ui_.rate_label->setNum (n); +} + +void FoxLogWindow::log_model_changed (int row) +{ + if (row >= 0) + { + m_->log_model_->selectRow (row); + } + else + { + m_->log_model_->select (); + } } diff --git a/widgets/FoxLogWindow.hpp b/widgets/FoxLogWindow.hpp index 62895a763..65bcb0bd0 100644 --- a/widgets/FoxLogWindow.hpp +++ b/widgets/FoxLogWindow.hpp @@ -1,42 +1,35 @@ #ifndef FOX_LOG_WINDOW_HPP_ #define FOX_LOG_WINDOW_HPP_ -#include -#include -#include -#include "models/FontOverrideModel.hpp" +#include "AbstractLogWindow.hpp" +#include "pimpl_h.hpp" class QSettings; class Configuration; class QFont; -class QDateTime; -class QAbstractItemModel; -namespace Ui -{ - class FoxLogWindow; -} +class QSqlTableModel; class FoxLogWindow final - : public QWidget + : public AbstractLogWindow { + Q_OBJECT + public: - explicit FoxLogWindow (QSettings *, Configuration const *, QAbstractItemModel * fox_log_model + explicit FoxLogWindow (QSettings *, Configuration const *, QSqlTableModel * fox_log_model , QWidget * parent = nullptr); ~FoxLogWindow (); - void change_font (QFont const&); void callers (int); void queued (int); void rate (int); -private: - void read_settings (); - void write_settings () const; + Q_SIGNAL void reset_log_model () const; - QSettings * settings_; - Configuration const * configuration_; - FontOverrideModel fox_log_model_; - QScopedPointer ui_; +private: + void log_model_changed (int row) override; + + class impl; + pimpl m_; }; #endif diff --git a/widgets/FoxLogWindow.ui b/widgets/FoxLogWindow.ui index 2a3e84aba..cd224ed33 100644 --- a/widgets/FoxLogWindow.ui +++ b/widgets/FoxLogWindow.ui @@ -2,12 +2,33 @@ FoxLogWindow + + + 0 + 0 + 453 + 238 + + + + Qt::DefaultContextMenu + Fox Log - + + + Qt::ActionsContextMenu + + + <html><head/><body><p>Right-click here for available actions.</p></body></html> + + + true + + diff --git a/widgets/astro.cpp b/widgets/astro.cpp index c0e2b1fe3..4ee55da4a 100644 --- a/widgets/astro.cpp +++ b/widgets/astro.cpp @@ -213,7 +213,7 @@ auto Astro::astroUpdate(QDateTime const& t, QString const& mygrid, QString const // we do the next period if we calculate just before it starts auto sec_since_epoch = t.toMSecsSinceEpoch () / 1000 + 2; auto target_sec = sec_since_epoch - sec_since_epoch % TR_period + TR_period / 2; - auto target_date_time = QDateTime::fromMSecsSinceEpoch (target_sec * 1000, QTimeZone::utc ()); + auto target_date_time = QDateTime::fromMSecsSinceEpoch (target_sec * 1000, Qt::UTC); QString date {target_date_time.date().toString("yyyy MMM dd").trimmed ()}; QString utc {target_date_time.time().toString().trimmed ()}; int nyear {target_date_time.date().year()}; diff --git a/widgets/logqso.cpp b/widgets/logqso.cpp index 77324e52b..b6412cb59 100644 --- a/widgets/logqso.cpp +++ b/widgets/logqso.cpp @@ -10,6 +10,7 @@ #include "MessageBox.hpp" #include "Configuration.hpp" #include "models/Bands.hpp" +#include "models/CabrilloLog.hpp" #include "validators/MaidenheadLocatorValidator.hpp" #include "ui_logqso.h" @@ -57,11 +58,10 @@ void LogQSO::storeSettings () const void LogQSO::initLogQSO(QString const& hisCall, QString const& hisGrid, QString mode, QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn, QDateTime const& dateTimeOff, - Radio::Frequency dialFreq, bool noSuffix, QString xSent, QString xRcvd) + Radio::Frequency dialFreq, bool noSuffix, QString xSent, QString xRcvd, + CabrilloLog * cabrillo_log) { if(!isHidden()) return; - m_xSent=xSent; - m_xRcvd=xRcvd; ui->call->setText(hisCall); ui->grid->setText(hisGrid); ui->name->setText(""); @@ -87,17 +87,23 @@ void LogQSO::initLogQSO(QString const& hisCall, QString const& hisGrid, QString m_myGrid=m_config->my_grid(); ui->band->setText (m_config->bands ()->find (dialFreq)); ui->loggedOperator->setText(m_config->opCall()); - ui->exchSent->setText(m_xSent); - ui->exchRcvd->setText(m_xRcvd); + ui->exchSent->setText (xSent); + ui->exchRcvd->setText (xRcvd); + m_cabrilloLog = cabrillo_log; using SpOp = Configuration::SpecialOperatingActivity; - if( SpOp::FOX == m_config->special_op_id() or - (m_config->autoLog() and SpOp::NONE < m_config->special_op_id() and - m_xSent!="" and m_xRcvd!="")) { - accept(); - } else { - show(); - } + auto special_op = m_config->special_op_id (); + if (SpOp::FOX == special_op + || (m_config->autoLog () + && SpOp::NONE < special_op && special_op < SpOp::FOX)) + { + // allow auto logging in Fox mode and contests + accept(); + } + else + { + show(); + } } void LogQSO::accept() @@ -120,6 +126,29 @@ void LogQSO::accept() QString strDialFreq(QString::number(m_dialFreq / 1.e6,'f',6)); operator_call = ui->loggedOperator->text(); + // validate + using SpOp = Configuration::SpecialOperatingActivity; + auto special_op = m_config->special_op_id (); + if (SpOp::NONE < special_op && special_op < SpOp::FOX) + { + if (ui->exchSent->text ().isEmpty () || ui->exchRcvd->text ().isEmpty ()) + { + show (); + MessageBox::warning_message (this, tr ("Invalid QSO Data"), + tr ("Check exchange sent and received")); + return; // without accepting + } + + if (!m_cabrilloLog->add_QSO (m_dialFreq, QDateTime::currentDateTimeUtc (), hisCall, + ui->exchSent->text (), ui->exchRcvd->text ())) + { + show (); + MessageBox::warning_message (this, tr ("Invalid QSO Data"), + tr ("Check all fields")); + return; // without accepting + } + } + //Log this QSO to file "wsjtx.log" static QFile f {QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}.absoluteFilePath ("wsjtx.log")}; if(!f.open(QIODevice::Text | QIODevice::Append)) { @@ -169,8 +198,8 @@ void LogQSO::accept() , m_myGrid , m_txPower , operator_call - , m_xSent - , m_xRcvd)); + , ui->exchSent->text () + , ui->exchRcvd->text ())); QDialog::accept(); } diff --git a/widgets/logqso.h b/widgets/logqso.h index 2f2e4c29e..b0e703a93 100644 --- a/widgets/logqso.h +++ b/widgets/logqso.h @@ -17,6 +17,7 @@ namespace Ui { class QSettings; class Configuration; class QByteArray; +class CabrilloLog; class LogQSO : public QDialog { @@ -28,7 +29,7 @@ public: void initLogQSO(QString const& hisCall, QString const& hisGrid, QString mode, QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn, QDateTime const& dateTimeOff, Radio::Frequency dialFreq, - bool noSuffix, QString xSent, QString xRcvd); + bool noSuffix, QString xSent, QString xRcvd, CabrilloLog *); public slots: void accept(); @@ -56,10 +57,9 @@ private: Radio::Frequency m_dialFreq; QString m_myCall; QString m_myGrid; - QString m_xSent; - QString m_xRcvd; QDateTime m_dateTimeOn; QDateTime m_dateTimeOff; + CabrilloLog * m_cabrilloLog; }; #endif // LogQSO_H diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 19243df09..175dff49d 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -735,6 +736,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, ui->labDXped->setStyleSheet("QLabel {background-color: red; color: white;}"); ui->labNextCall->setText(""); ui->labNextCall->setVisible(false); + ui->labNextCall->setToolTip(""); //### Possibly temporary ? ### for(int i=0; i<28; i++) { //Initialize dBm values float dbm=(10.0*i)/3.0 - 30.0; @@ -931,13 +933,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, ui->cbMenus->setChecked(true); ui->cbMenus->setChecked(false); } - - mouseLastPos=QCursor::pos(); - m_mouseIdleSeconds=0; - connect(&mouseTimer, &QTimer::timeout, this, &MainWindow::mouseTimerTick); - mouseTimer.start(1000); - - // this must be the last statement of constructor +// this must be the last statement of constructor if (!m_valid) throw std::runtime_error {"Fatal initialization exception"}; } @@ -948,10 +944,16 @@ void MainWindow::not_GA_warning_message () MessageBox::critical_message (this, "This version of WSJT-X is a beta-level Release Candidate.\n\n" + "In FT8 and MSK144 modes it uses ONLY the new 77-bit\n" + "message formats. It will not decode 75-bit or 72-bit\n" + "messages.\n\n" + "On December 10, 2018, 77-bit messages will become the\n" + "standard. Everyone should upgrade to WSJT-X 2.0 by\n" + "January 1, 2019.\n\n" "On-the-air use carries an obligation to report problems\n" "to the WSJT Development group and to upgrade to a GA\n" "(General Availability) release when it becomes available.\n\n" - "This version cannot be used after December 31, 2018\n\n"); + "This version cannot be used after December 31, 2018.\n\n"); if(now.daysTo(timeout) < 0) Q_EMIT finished(); } @@ -962,22 +964,6 @@ void MainWindow::initialize_fonts () setDecodedTextFont (m_config.decoded_text_font ()); } -void MainWindow::mouseTimerTick() -{ - QPoint point = QCursor::pos(); - if(point != mouseLastPos) - m_mouseIdleSeconds = 0; - else - m_mouseIdleSeconds++; - mouseLastPos = point; -//Here we should do what's necessary when mouseIdleSeconds gets too big. -// qDebug() << m_mouseIdleSeconds; - if(ui->cbAutoSeq->isChecked() and m_mouseIdleSeconds>300) { - auto_tx_mode (false); - } -} - - void MainWindow::splash_done () { m_splash && m_splash->close (); @@ -1077,6 +1063,7 @@ void MainWindow::writeSettings() m_settings->setValue("RR73",m_send_RR73); m_settings->setValue ("WSPRPreferType1", ui->WSPR_prefer_type_1_check_box->isChecked ()); m_settings->setValue("UploadSpots",m_uploadSpots); + m_settings->setValue("NoOwnCall",ui->cbNoOwnCall->isChecked()); m_settings->setValue ("BandHopping", ui->band_hopping_group_box->isChecked ()); m_settings->setValue ("TRPeriod", ui->sbTR->value ()); m_settings->setValue("FastMode",m_bFastMode); @@ -1168,6 +1155,7 @@ void MainWindow::readSettings() ui->WSPR_prefer_type_1_check_box->setChecked (m_settings->value ("WSPRPreferType1", true).toBool ()); m_uploadSpots=m_settings->value("UploadSpots",false).toBool(); if(!m_uploadSpots) ui->cbUploadWSPR_Spots->setStyleSheet("QCheckBox{background-color: yellow}"); + ui->cbNoOwnCall->setChecked(m_settings->value("NoOwnCall",false).toBool()); ui->band_hopping_group_box->setChecked (m_settings->value ("BandHopping", false).toBool()); // setup initial value of tx attenuator m_block_pwr_tooltip = true; @@ -1248,10 +1236,10 @@ void MainWindow::setDecodedTextFont (QFont const& font) m_msgAvgWidget->changeFont (font); } if (m_foxLogWindow) { - m_foxLogWindow->change_font (font); + m_foxLogWindow->set_log_view_font (font); } if (m_contestLogWindow) { - m_contestLogWindow->change_font (font); + m_contestLogWindow->set_log_view_font (font); } updateGeometry (); } @@ -1880,7 +1868,6 @@ void MainWindow::keyPressEvent (QKeyEvent * e) if((e->modifiers() & Qt::ControlModifier) and (e->modifiers() & Qt::ShiftModifier)) { m_bandEdited = true; band_changed(m_freqNominal-2000); -// qDebug() << "Down" << m_freqNominal; } else { n=11; if(e->modifiers() & Qt::ControlModifier) n+=100; @@ -1895,7 +1882,6 @@ void MainWindow::keyPressEvent (QKeyEvent * e) if((e->modifiers() & Qt::ControlModifier) and (e->modifiers() & Qt::ShiftModifier)) { m_bandEdited = true; band_changed(m_freqNominal+2000); -// qDebug() << "Up " << m_freqNominal; } else { n=12; if(e->modifiers() & Qt::ControlModifier) n+=100; @@ -2440,6 +2426,10 @@ void MainWindow::on_fox_log_action_triggered() // Connect signals from fox log window connect (this, &MainWindow::finished, m_foxLogWindow.data (), &FoxLogWindow::close); + connect (m_foxLogWindow.data (), &FoxLogWindow::reset_log_model, [this] () { + if (!m_foxLog) m_foxLog.reset (new FoxLog); + m_foxLog->reset (); + }); } m_foxLogWindow->showNormal (); m_foxLogWindow->raise (); @@ -5353,9 +5343,15 @@ void MainWindow::on_logQSOButton_clicked() //Log QSO button default: break; } + auto special_op = m_config.special_op_id (); + if (SpecOp::NONE < special_op && special_op < SpecOp::FOX) + { + if (!m_cabrilloLog) m_cabrilloLog.reset (new CabrilloLog {&m_config}); + } m_logDlg->initLogQSO (m_hisCall, grid, m_modeTx, m_rptSent, m_rptRcvd, m_dateTimeQSOOn, dateTimeQSOOff, m_freqNominal + - ui->TxFreqSpinBox->value(), m_noSuffix, m_xSent, m_xRcvd); + ui->TxFreqSpinBox->value(), m_noSuffix, m_xSent, m_xRcvd, + m_cabrilloLog.data ()); } void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, QString const& grid @@ -5372,15 +5368,6 @@ void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, tr ("Cannot open \"%1\"").arg (m_logBook.path ())); } - - if (SpecOp::NONE < m_config.special_op_id() && SpecOp::FOX > m_config.special_op_id()) { - if (!m_cabrilloLog) m_cabrilloLog.reset (new CabrilloLog {&m_config}); - m_cabrilloLog->add_QSO (m_freqNominal, QDateTime::currentDateTimeUtc (), m_hisCall, m_xSent, m_xRcvd); - m_xSent=""; - m_xRcvd=""; - ui->sbSerialNumber->setValue (ui->sbSerialNumber->value () + 1); - } - m_messageClient->qso_logged (QSO_date_off, call, grid, dial_freq, mode, rpt_sent, rpt_received , tx_power, comments, name, QSO_date_on, operator_call, my_call, my_grid); m_messageClient->logged_ADIF (ADIF); @@ -5400,6 +5387,14 @@ void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, if (m_config.clear_DX () and SpecOp::HOUND != m_config.special_op_id()) clearDX (); m_dateTimeQSOOn = QDateTime {}; + auto special_op = m_config.special_op_id (); + if (SpecOp::NONE < special_op && special_op < SpecOp::FOX) + { + ui->sbSerialNumber->setValue (ui->sbSerialNumber->value () + 1); + } + + m_xSent.clear (); + m_xRcvd.clear (); } qint64 MainWindow::nWidgets(QString t) @@ -5464,7 +5459,6 @@ void MainWindow::displayWidgets(qint64 n) if(i==32) ui->cbCQonly->setVisible(b); j=j>>1; } - if(!ui->cbAutoSeq->isVisible()) ui->cbAutoSeq->setChecked(false); b=SpecOp::EU_VHF==m_config.special_op_id() or (SpecOp::RTTY==m_config.special_op_id() and (m_config.RTTY_Exchange()=="#" or m_config.RTTY_Exchange()=="DX")); ui->sbSerialNumber->setVisible(b); @@ -6134,25 +6128,14 @@ void MainWindow::on_actionErase_ALL_TXT_triggered() //Erase ALL.TXT } } -void MainWindow::on_reset_fox_log_action_triggered () -{ - int ret = MessageBox::query_message(this, tr("Confirm Reset"), - tr("Are you sure you want to erase file FoxQSO.txt and start a new Fox log?")); - if(ret==MessageBox::Yes) { - QFile f{m_config.writeable_data_dir().absoluteFilePath("FoxQSO.txt")}; - f.remove(); - if (!m_foxLog) m_foxLog.reset (new FoxLog); - m_foxLog->reset (); - } -} - void MainWindow::on_reset_cabrillo_log_action_triggered () { - if (MessageBox::Yes == MessageBox::query_message(this, tr("Confirm Erase"), - tr("Are you sure you want to erase file cabrillo.log" - " and start a new Cabrillo log?"))) + if (MessageBox::Yes == MessageBox::query_message (this, tr ("Confirm Reset"), + tr ("Are you sure you want to erase your contest log?"), + tr ("Doing this will remove all QSO records for the current contest. " + "They will be kept in the ADIF log file but will not be available " + "for export in your Cabrillo log."))) { - QFile {m_config.writeable_data_dir ().absoluteFilePath ("cabrillo.log")}.remove (); ui->sbSerialNumber->setValue (1); if (!m_cabrilloLog) m_cabrilloLog.reset (new CabrilloLog {&m_config}); m_cabrilloLog->reset (); @@ -6505,15 +6488,17 @@ void MainWindow::on_readFreq_clicked() void MainWindow::on_pbTxMode_clicked() { - if(m_modeTx=="JT9") { - m_modeTx="JT65"; - ui->pbTxMode->setText("Tx JT65 #"); - } else { - m_modeTx="JT9"; - ui->pbTxMode->setText("Tx JT9 @"); + if(m_mode=="JT9+JT65") { + if(m_modeTx=="JT9") { + m_modeTx="JT65"; + ui->pbTxMode->setText("Tx JT65 #"); + } else { + m_modeTx="JT9"; + ui->pbTxMode->setText("Tx JT9 @"); + } + m_wideGraph->setModeTx(m_modeTx); + statusChanged(); } - m_wideGraph->setModeTx(m_modeTx); - statusChanged(); } void MainWindow::setXIT(int n, Frequency base) @@ -7266,6 +7251,10 @@ void MainWindow::p1ReadFromStdout() //p1readFromStdout QString t1; while(p1.canReadLine()) { QString t(p1.readLine()); + if(ui->cbNoOwnCall->isChecked()) { + if(t.contains(" " + m_config.my_callsign() + " ")) continue; + if(t.contains(" <" + m_config.my_callsign() + "> ")) continue; + } if(t.indexOf("") >= 0) { m_bDecoded = m_nWSPRdecodes > 0; if(!m_diskData) { @@ -7391,7 +7380,6 @@ void MainWindow::WSPR_history(Frequency dialFreq, int ndecodes) } } - void MainWindow::uploadSpots() { // do not spot replays or if rig control not working @@ -7933,9 +7921,9 @@ QString MainWindow::sortHoundCalls(QString t, int isort, int max_dB) if(isort>1) { if(bReverse) { - qSort(list.begin(),list.end(),qGreater()); + std::sort (list.begin (), list.end (), std::greater ()); } else { - qSort(list.begin(),list.end()); + std::sort (list.begin (), list.end ()); } } diff --git a/widgets/mainwindow.h b/widgets/mainwindow.h index 2ef380e74..4cab787b5 100644 --- a/widgets/mainwindow.h +++ b/widgets/mainwindow.h @@ -204,7 +204,6 @@ private slots: void on_actionDeepestDecode_toggled (bool); void bumpFqso(int n); void on_actionErase_ALL_TXT_triggered(); - void on_reset_fox_log_action_triggered (); void on_reset_cabrillo_log_action_triggered (); void on_actionErase_wsjtx_log_adi_triggered(); void on_actionExport_Cabrillo_log_triggered(); @@ -446,7 +445,6 @@ private: qint32 m_Nslots=5; qint32 m_nFoxMsgTimes[5]={0,0,0,0,0}; qint32 m_tAutoOn; - qint32 m_mouseIdleSeconds; qint32 m_tFoxTx=0; qint32 m_tFoxTx0=0; qint32 m_maxStrikes=3; //Max # of repeats: 3 strikes and you're out @@ -570,9 +568,6 @@ private: QTimer minuteTimer; QTimer splashTimer; QTimer p1Timer; - QTimer mouseTimer; - - QPoint mouseLastPos; QString m_path; QString m_baseCall; @@ -701,7 +696,6 @@ private: void CQTxFreq(); void useNextCall(); void abortQSO(); - void mouseTimerTick(); bool isWorked(int itype, QString key, float fMHz=0, QString=""); QString save_wave_file (QString const& name diff --git a/widgets/mainwindow.ui b/widgets/mainwindow.ui index fe9aa30bc..21799434e 100644 --- a/widgets/mainwindow.ui +++ b/widgets/mainwindow.ui @@ -573,6 +573,430 @@ QLabel[oob="true"] { + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + <html><head/><body><p>30dB recommended when only noise present<br/>Green when good<br/>Red when clipping may occur<br/>Yellow when too low</p></body></html> + + + QFrame::Panel + + + QFrame::Sunken + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + + 252 + 252 + 252 + + + + + + + 159 + 175 + 213 + + + + + + + + + 252 + 252 + 252 + + + + + + + 159 + 175 + 213 + + + + + + + + + 159 + 175 + 213 + + + + + + + 159 + 175 + 213 + + + + + + + + true + + + DX Call + + + Qt::AlignCenter + + + 5 + + + 2 + + + + + + + + 0 + 0 + + + + + + + + + 252 + 252 + 252 + + + + + + + 159 + 175 + 213 + + + + + + + + + 252 + 252 + 252 + + + + + + + 159 + 175 + 213 + + + + + + + + + 159 + 175 + 213 + + + + + + + 159 + 175 + 213 + + + + + + + + true + + + DX Grid + + + Qt::AlignCenter + + + 5 + + + 2 + + + + + + + Callsign of station to be worked + + + + + + Qt::AlignCenter + + + + + + + Search for callsign in database + + + &Lookup + + + + + + + Locator of station to be worked + + + + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + true + + + Az: 251 16553 km + + + Qt::AlignCenter + + + 4 + + + + + + + Add callsign and locator to database + + + Add + + + + + + + + + + Pwr + + + + + + + false + + + <html><head/><body><p>If orange or red there has been a rig control failure, click to reset and read the dial frequency. S implies split mode.</p></body></html> + + + QPushButton { + font-family: helvetica; + font-size: 9pt; + font-weight: bold; + background-color: white; + color: black; + border-style: solid; + border-width:1px; + border-radius:10px; + border-color: gray; + max-width:20px; + max-height:20px; + min-width:20px; + min-height:20px; +} +QPushButton[state="error"] { + background-color: red; +} +QPushButton[state="warning"] { + background-color: orange; +} +QPushButton[state="ok"] { + background-color: #00ff00; +} + + + ? + + + + + + + Adjust Tx audio level + + + 450 + + + 0 + + + Qt::Vertical + + + true + + + true + + + QSlider::TicksBelow + + + 50 + + + + + + + <html><head/><body><p>Select operating band or enter frequency in MHz or enter kHz increment followed by k.</p></body></html> + + + true + + + QComboBox::NoInsert + + + QComboBox::AdjustToMinimumContentsLength + + + + + + + + 0 + 0 + + + + QLabel { + font-family: MS Shell Dlg 2; + font-size: 16pt; + background-color : black; + color : yellow; +} + + + QFrame::StyledPanel + + + QFrame::Sunken + + + 2 + + + 0 + + + <html><head/><body><p align="center"> 2015 Jun 17 </p><p align="center"> 01:23:45 </p></body></html> + + + Qt::AlignCenter + + + 5 + + + @@ -845,7 +1269,7 @@ QLabel[oob="true"] { false - <html><head/><body><p>Check this to call CQ on the &quot;Tx CQ&quot; frequency. Rx will be on the current frequency and the CQ message wiill include the current Rx frequency so callers know which frequency to reply on.</p><p>Not available to type 2 compound callsign holders.</p></body></html> + <html><head/><body><p>Check this to call CQ on the &quot;Tx CQ&quot; frequency. Rx will be on the current frequency and the CQ message wiill include the current Rx frequency so callers know which frequency to reply on.</p><p>Not available to nonstandard callsign holders.</p></body></html> @@ -2105,6 +2529,13 @@ list. The list can be maintained in Settings (F2). + + + + No own call decodes + + + @@ -2199,430 +2630,6 @@ list. The list can be maintained in Settings (F2). - - - - - 0 - 0 - - - - - 100 - 16777215 - - - - <html><head/><body><p>30dB recommended when only noise present<br/>Green when good<br/>Red when clipping may occur<br/>Yellow when too low</p></body></html> - - - QFrame::Panel - - - QFrame::Sunken - - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - - - - - 252 - 252 - 252 - - - - - - - 159 - 175 - 213 - - - - - - - - - 252 - 252 - 252 - - - - - - - 159 - 175 - 213 - - - - - - - - - 159 - 175 - 213 - - - - - - - 159 - 175 - 213 - - - - - - - - true - - - DX Call - - - Qt::AlignCenter - - - 5 - - - 2 - - - - - - - - 0 - 0 - - - - - - - - - 252 - 252 - 252 - - - - - - - 159 - 175 - 213 - - - - - - - - - 252 - 252 - 252 - - - - - - - 159 - 175 - 213 - - - - - - - - - 159 - 175 - 213 - - - - - - - 159 - 175 - 213 - - - - - - - - true - - - DX Grid - - - Qt::AlignCenter - - - 5 - - - 2 - - - - - - - Callsign of station to be worked - - - - - - Qt::AlignCenter - - - - - - - Locator of station to be worked - - - - - - Qt::AlignCenter - - - - - - - Search for callsign in database - - - &Lookup - - - - - - - Add callsign and locator to database - - - Add - - - - - - - - 0 - 0 - - - - true - - - Az: 251 16553 km - - - Qt::AlignCenter - - - 4 - - - - - - - - - - Pwr - - - - - - - false - - - <html><head/><body><p>If orange or red there has been a rig control failure, click to reset and read the dial frequency. S implies split mode.</p></body></html> - - - QPushButton { - font-family: helvetica; - font-size: 9pt; - font-weight: bold; - background-color: white; - color: black; - border-style: solid; - border-width:1px; - border-radius:10px; - border-color: gray; - max-width:20px; - max-height:20px; - min-width:20px; - min-height:20px; -} -QPushButton[state="error"] { - background-color: red; -} -QPushButton[state="warning"] { - background-color: orange; -} -QPushButton[state="ok"] { - background-color: #00ff00; -} - - - ? - - - - - - - Adjust Tx audio level - - - 450 - - - 0 - - - Qt::Vertical - - - true - - - true - - - QSlider::TicksBelow - - - 50 - - - - - - - <html><head/><body><p>Select operating band or enter frequency in MHz or enter kHz increment followed by k.</p></body></html> - - - true - - - QComboBox::NoInsert - - - QComboBox::AdjustToMinimumContentsLength - - - - - - - - 0 - 0 - - - - QLabel { - font-family: MS Shell Dlg 2; - font-size: 16pt; - background-color : black; - color : yellow; -} - - - QFrame::StyledPanel - - - QFrame::Sunken - - - 2 - - - 0 - - - <html><head/><body><p align="center"> 2015 Jun 17 </p><p align="center"> 01:23:45 </p></body></html> - - - Qt::AlignCenter - - - 5 - - - @@ -2646,7 +2653,6 @@ QPushButton[state="ok"] { - diff --git a/widgets/widgets.pri b/widgets/widgets.pri index 019fdcd17..e70c5cb9e 100644 --- a/widgets/widgets.pri +++ b/widgets/widgets.pri @@ -7,7 +7,8 @@ SOURCES += \ widgets/echoplot.cpp widgets/echograph.cpp widgets/fastgraph.cpp \ widgets/fastplot.cpp widgets/MessageBox.cpp \ widgets/colorhighlighting.cpp widgets/ExportCabrillo.cpp \ - widgets/CabrilloLogWindow.cpp + widgets/AbstractLogWindow.cpp \ + widgets/FoxLogWindow.cpp widgets/CabrilloLogWindow.cpp HEADERS += \ widgets/mainwindow.h widgets/plotter.h \ @@ -17,8 +18,8 @@ HEADERS += \ widgets/meterwidget.h widgets/messageaveraging.h \ widgets/echoplot.h widgets/echograph.h widgets/fastgraph.h \ widgets/fastplot.h widgets/MessageBox.hpp widgets/colorhighlighting.h \ - widgets/ExportCabrillo.h \ - widgets/CabrilloLogWindow.cpp + widgets/ExportCabrillo.h widgets/AbstractLogWindow.hpp \ + widgets/FoxLogWindow.cpp widgets/CabrilloLogWindow.cpp FORMS += \ widgets/mainwindow.ui widgets/about.ui \ @@ -26,5 +27,4 @@ FORMS += \ widgets/logqso.ui widgets/messageaveraging.ui \ widgets/echograph.ui widgets/fastgraph.ui \ widgets/colorhighlighting.ui widgets/ExportCabrillo.ui \ - widgets/FoxLogWindow.ui \ - widgets/CabrilloLogWindow.ui + widgets/FoxLogWindow.ui widgets/CabrilloLogWindow.ui