[hamradio-commits] [wspr] 01/01: Imported Upstream version 4.0.r4569
Greg Beam
ki7mt-guest at moszumanska.debian.org
Sat Nov 1 20:21:26 UTC 2014
This is an automated email from the git hooks/post-receive script.
ki7mt-guest pushed a commit to branch master
in repository wspr.
commit 66c5ac4172a1b818bc51431d86b6e50ccca5240d
Author: Greg Beam <ki7mt-guest at yahoo.com>
Date: Sat Nov 1 02:13:10 2014 -0600
Imported Upstream version 4.0.r4569
---
AUTHORS | 36 +
COPYING | 622 +++
COPYRIGHT | 14 +
INSTALL.txt | 235 ++
Makefile.in | 210 +
NEWS | 4 +
Pmw.py | 9333 ++++++++++++++++++++++++++++++++++++++++++
PmwBlt.py | 684 ++++
PmwColor.py | 387 ++
README | 14 +
WsprMod/__init__.py | 0
WsprMod/advanced.py | 138 +
WsprMod/g.py | 48 +
WsprMod/hopping.py | 143 +
WsprMod/iq.py | 189 +
WsprMod/options.py | 231 ++
WsprMod/palettes.py | 1599 ++++++++
WsprMod/smeter.py | 99 +
WsprMod/specjt.py | 532 +++
acom1.f90 | 45 +
acom2.f90 | 27 +
afc2.f90 | 234 ++
audiodev.f90 | 65 +
autogen.sh | 91 +
averms.f90 | 45 +
azdist.f90 | 128 +
calibrate.f90 | 161 +
calobs.f90 | 126 +
ccf.f90 | 306 ++
ccf2.f90 | 69 +
chklevel.f90 | 85 +
configure.ac | 552 +++
conv232.f90 | 64 +
cs_stubs.f90 | 33 +
db.f90 | 30 +
decode.f90 | 88 +
decode162.f90 | 162 +
deg2grid.f90 | 55 +
doc/AUTHORS | 36 +
doc/NEWS | 4 +
doc/README | 14 +
doc/examples/README | 15 +
doc/examples/fcal.out | 2 +
doc/examples/fmt.all | 80 +
doc/examples/fmtave.out | 8 +
encode232.f90 | 55 +
fano232.f90 | 182 +
fcal.f90 | 143 +
fchisq.f90 | 122 +
ffa.f90 | 125 +
fftw3.f90 | 89 +
fil1.f90 | 72 +
flat3.f90 | 56 +
fmeasure.f90 | 76 +
fmtave.f90 | 89 +
fmtest.f90 | 172 +
fmtiq.f90 | 147 +
fold1pps.f90 | 49 +
four2a.f90 | 126 +
fthread.c | 103 +
ftn_quit.f90 | 39 +
gencwid.f90 | 63 +
genmept.f90 | 138 +
genwspr.f90 | 147 +
geodist.f90 | 122 +
getfile.f90 | 69 +
getrms.f90 | 50 +
getsound.c | 167 +
getutc.f90 | 57 +
gmtime2.c | 81 +
gocal | 20 +
gran.f90 | 55 +
grid2deg.f90 | 64 +
hamlib_rig_numbers | 145 +
hash.f90 | 40 +
hftoa.f90 | 155 +
inter_mept.f90 | 70 +
iqdemod.f90 | 114 +
loggit.f90 | 44 +
manpages/man1/fcal.1 | 74 +
manpages/man1/fmeasure.1 | 74 +
manpages/man1/fmtave.1 | 74 +
manpages/man1/fmtest.1 | 116 +
manpages/man1/wspr.1 | 95 +
manpages/man1/wspr0.1 | 183 +
manpages/man1/wsprcode.1 | 74 +
mept162.f90 | 147 +
mix162.f90 | 86 +
morse.f90 | 115 +
msgtrim.f90 | 68 +
nchar.f90 | 48 +
nhash.c | 384 ++
pack50.f90 | 51 +
packcall.f90 | 104 +
packgrid.f90 | 72 +
packname.f90 | 48 +
packpfx.f90 | 84 +
packprop.f90 | 61 +
packtext2.f90 | 47 +
padevsub.c | 108 +
paterminate.f90 | 30 +
pctile.f90 | 38 +
peakup.f90 | 33 +
phasetx.f90 | 49 +
playsound.c | 191 +
ps162.f90 | 52 +
ptt_unix.c | 392 ++
qth.f90 | 125 +
read_wav.f90 | 45 +
resample.c | 58 +
rx.f90 | 70 +
rxtest.f90 | 82 +
rxtxcoord.f90 | 92 +
save/Samples/091022_0436.wav | Bin 0 -> 2736044 bytes
set.f90 | 56 +
slope.f90 | 66 +
sort.f90 | 29 +
sound.c | 182 +
spec162.f90 | 124 +
speciq.f90 | 119 +
ssort.f90 | 302 ++
start_threads.c | 105 +
startdec.f90 | 39 +
startrx.f90 | 39 +
starttx.f90 | 39 +
sync162.f90 | 223 +
t1.f90 | 150 +
thcvf.f90 | 93 +
thnix.f90 | 83 +
thnix_stub.f90 | 83 +
twkfreq.f90 | 56 +
tx.f90 | 311 ++
unpack50.f90 | 55 +
unpackcall.f90 | 60 +
unpackgrid.f90 | 60 +
unpackmept.f90 | 82 +
unpackname.f90 | 45 +
unpackpfx.f90 | 64 +
unpackprop.f90 | 53 +
unpacktext2.f90 | 42 +
user_hardware.py | 61 +
wfile5.f90 | 114 +
wqdecode.f90 | 98 +
wqencode.f90 | 91 +
write_wav.f90 | 58 +
wspr.desktop | 11 +
wspr.py | 1941 +++++++++
wspr.sh | 42 +
wspr.xpm | 41 +
wspr0.f90 | 86 +
wspr0_rx.f90 | 95 +
wspr0_tx.f90 | 101 +
wspr0init.f90 | 171 +
wspr1.f90 | 40 +
wspr2.f90 | 258 ++
wspr_nogui.py | 1936 +++++++++
wspr_rxtest.f90 | 68 +
wspr_tr.in0 | 3 +
wsprcode.f90 | 154 +
wsprrc | 9 +
wwv.f90 | 270 ++
xcor162.f90 | 90 +
xfft.f90 | 36 +
163 files changed, 31993 insertions(+)
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..bde63bc
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,36 @@
+AUTHOR: Joseph Taylor
+CALLSIGN: K1JT
+
+AUTHOR: Joop Stakenborg <pg4i at amsat.org>
+CALLSIGN: PG4I
+
+AUTHOR: John Nelson
+CALLSIGN: G4KLA
+
+AUTHOR:
+CALLSIGN: KE6HDU
+
+AUTHOR:
+CALLSIGN: W1BW
+
+AUTHOR:
+CALLSIGN: VA3DB
+
+AUTHOR:
+CALLSIGN: 4X6IZ
+
+Authors can be contacted on the wsjt-devel mailing list or
+through the WSJT Yahoo Group.
+
+PROJECT INFORMATION:
+-------------------
+- Project Manager: ...... Joe Taylor, K1JT, joe -AT- princeton -DOT- edu
+- Project Web Site: ..... http://physics.princeton.edu/pulsar/K1JT/
+- Mailing List: ......... wsjt-devel at lists.sourceforge.net
+- Project Source Code: .. http://sourceforge.net/projects/wsjt/
+- Yahoo Group: .......... https://groups.yahoo.com/neo/groups/wsjtgroup/info
+
+
+
+
+
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..bd8f5dc
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,622 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
\ No newline at end of file
diff --git a/COPYRIGHT b/COPYRIGHT
new file mode 100644
index 0000000..ca25f85
--- /dev/null
+++ b/COPYRIGHT
@@ -0,0 +1,14 @@
+Copyright (C) 2014 Joseph H Taylor, Jr, K1JT
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/INSTALL.txt b/INSTALL.txt
new file mode 100644
index 0000000..b08e429
--- /dev/null
+++ b/INSTALL.txt
@@ -0,0 +1,235 @@
+-------------------------------------------------
+Build and Installation Instructions for WSPR 4.0
+-------------------------------------------------
+
+The following outlines basic installation instructions for Linux and Mac.
+
+
+REQUIRED DEPENDENCIES
+---------------------
+To compile WSPR, you will need the following packages / applications:
+
+ * A C/C++ and Fortran compiler
+ * GNU Autotools: autoconf, make, autotools-dev (config.guess, config.sub, install.sh)
+ * Portaudio-19-dev
+ * FFTW 3.3+-dev (only libraries *.so are required)
+ * Samplerate Development Library.
+ * Python3.2+
+ * Numeric Python (Numpy 1.8.1+), providing F2PY v2.0 built against Python3
+ * PIL, (Python Imaging Library, sometimes referred to as PILLOW)
+ * Python-tk (Tcl\TK 8.6.1 or Later)
+ * Python Imaging Tk (Pil or Pillow)
+ * Optional: Hamlib 1.2.15.x (or current distro version) for rig control
+
+Example: For Ubuntu 14.04 amd64 system install, the following packages
+would make be a good start:
+
+sudo apt-get install autoconf autotools-dev build-essential gfortran libtool
+libfftw3-dev libusb-dev libhamlib-dev libhamlib-utils libsamplerate0-dev
+portaudio19-dev python3-dev python3-tk python3-imaging-tk python3-pip
+python3-numpy
+
+NOTES:
+- Python Imaging and the related package names can be tricky depending
+your version of *Nix. For example, python3-imaging-tk installs / sets up the
+Python Imaging Library (Pillow, a modified version of PIL). Your Linux
+distro may be different.
+
+- You may be able to slim the list down by selective installation, but the above
+provides a solid base to start with.
+
+IMPORTANT F2PY NOTE
+- If you have python-numpy (based on Python27) installed, you should remove
+it unless it is needed for other Numeric Python applications, as WSPR requires
+F2PY (provided by Numpy) to be built with Python3:
+
+sudo apt-get remove python-numpy
+sudo apt-get install python3-numpy
+
+See MULTI PYTHON SYSTEMS for more information.
+
+
+INSTALL
+-------
+To build and install, issue the following:
+
+./autogen.sh
+
+make && sudo make install
+
+
+RUN WSPR
+--------
+To run WSPR after build, type:
+
+ wspr
+
+
+NON-STANDARD LIBRARY LOCATIONS
+------------------------------
+The configure script will search standard Linux header and library locations
+for FFTW3, Samplerate and Portaudio19. If it cannot locate and test the
+required packages, you will be prompted to supply the locations:
+
+Example - (header location):
+
+./configure CPPFLAGS="-I$HOME/test/portaudio/include"
+
+Note the " .. " for parameter expansion for $HOME
+
+
+Example - (library location):
+./configure LIBDIR='-L/home/user-name/test/portaudio/lib'
+
+Note ' .. ' no parameter expansion will occur
+
+NOTE: When using CPPFLAGS or LIBDIR, use absolute paths, for example:
+
+-I/home/$USER/test/portaudio/include *not* ==> ~/test/portaudio/include
+
+or
+
+-L$HOME/test/portaudio/lib *not* ==> ~/test/portaudio/lib
+
+
+MULTI PYTHON SYSTEMS
+--------------------
+On dual Python systems, you *may* need to specify Python3 and F2PY3 locations.
+The configure scripts tests for both Python3 and F2PY, but if your system has
+unique install locations, the following may be helpful:
+
+Example:
+./configure --with-python3='/usr/bin/python3' --with-f2py='/usr/local/bin/f2py3'
+
+Note:
+- If you build a custom Python3x, make sure you include PIL, TK and Numpy
+(which provides F2PY).
+
+- If your default Python installation is Python3, after running ./autogen.sh,
+you will see:
+
+F2py: ............: /usr/bin/f2py
+
+This "OK", as long as Numpy (F2PY) was built using Python3. The same goes for
+Python itself. The configure script may print:
+
+Python3: .........: /usr/bin/python
+
+Yes the actual version is something like Python 3.3 or 3.4. This is also "OK"
+as the Numpy installation will also be based on on Python3.
+
+
+CONFIGURE && MAKE
+-----------------
+To configure or reconfigure, then build WSPR, type the following:
+
+
+./autogen.sh
+
+
+Note: You can re-run ./configure any time after a successful ./autogen.sh run.
+To see available configure options, type:
+
+./configure --help=short
+
+Note the use of --help=short, as that will list the configurable options
+which are enabled in configure.ac
+
+
+MAKE INSTALL
+------------
+This target install WSPR to the ${PREFIX} location specified by the user.
+If --prefix= was not changed, the default installation directory is
+$HOME/.wspr:
+
+sudo make install
+
+
+CLEAN THE BUILD TREE
+--------------------
+This target removes files generated during make.
+
+make clean
+
+
+CLEAN FOR DISTRIBUTION
+----------------------
+This target removes most all of the build files, (configure script, Makefile)
+and so on. This will also remove files generated from wspr0 and fmt.
+
+make distclean
+
+
+UNINSTALL
+---------
+The make script has a provision to uninstall WSPR. It serves the same function
+as simply deleting the ../wspr folder. The delete location is specified in
+the Makefile, based on the users input. To tun uninstall:
+
+sudo make uninstall
+
+Note: All user generated files (WSPR.INI, decodes, fmt.ini, etc) remain in the
+installation directory, unless you specifically delete them.
+
+
+MAKE DIST
+---------
+Included in the WSPR repository is a filed named wspr-dist.sh. This files is
+used to generate distribution tar ball, or tar.gz files.
+
+USAGE: wspr-dist.sh [NAME] [VERSION]
+
+Example 1 cmd line: ./wspr-dist.sh wspr 4.0
+Example 2 Makefile: make dist
+
+Generates .......: wspr-4.0.tar.gz
+File Location ...: $(src-path)/wspr/dist
+
+
+BASIC MAC OSX INSTALLATION INSTRUCTIONS (Example 10.7)
+-----------------------------------------------------
+
+You need to be familiar with command line operations using
+the Terminal window and with downloading and installing
+libraries.
+
+Apple provide python 2.x as part of the system but this must
+be upgraded to python 3.4. Use pip3.4 to install numpy,
+Pillow. Follow installation guides from www.python.org.
+Python3.4 will be installed in /Library/Frameworks/Python.framework/
+
+NOTE: Pmw is no longer a required approbate Pmw files have been added
+to the WSPR repository.
+
+It is important that references to python should then be
+to python3.4 and not the earlier version of python. Watch
+out for Apple's use of soft links to the earlier versions
+found in /System/Library/Frameworks/Python.framework. These
+should be disabled.
+
+You will also need portaudio v19 as well as fftw libraries
+from www.portaudio.org and www.fftw.org respectively.
+
+Then, in the wspr directory:
+
+Example - (library location):
+./configure LIBDIR='-L/home/user-name/test/portaudio/lib'
+
+Note ' .. ' no parameter expansion needed
+
+NOTE: When using CPPFLAGS, use absolute paths, for example:
+
+-I/home/$USER/test/portaudio/include *not* ==> ~/test/portaudio/include
+
+
+Check the new Makefile carefully to see that the correct
+references to python3.4 and the new f2py are made. Then
+
+ make
+
+You might have to move Audio.so or w.so to WsprMod/ manually.
+
+Thereafter python wspr.py should execute.
+
+Additional guidance from g4kla at rmnjmn.demon.co.uk
+
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..5ff8d99
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,210 @@
+# Makefile for Linux
+# Re-direct stdout and stderr: bash
+# make >& junk
+#
+# Prerequisites: Python 3.x, numpy-1.8.1+, PIL, Portaudio, Samplerate, FFTW
+
+# Program Infomation
+PROGRAM := @PROGRAM@
+VERSION := @VERSION@
+BUGS := @BUGS@
+WEB := @WEB@
+
+# System Information
+OS := @OS@
+CPU := @CPU@
+
+# General Use Tools
+AR := @AR@
+CP := @CP@
+LN := @LN@
+MV := @MV@
+RM := @RM@
+CHOWN := @CHOWN@
+CHMOD := @CHMOD@
+SHELL := @SHELL@
+MKDIR := @MKDIR@
+
+# Install locations
+INATALL := install
+BINDIR := $(DESTDIR)@BINDIR@
+DOCDIR := $(DESTDIR)@DOCDIR@
+HOMEDIR := $(DESTDIR)@HOMEDIR@
+MANDIR := $(DESTDIR)@MANDIR@
+SHARED := $(DESTDIR)@SHARED@
+WSPRLIB := $(DESTDIR)@WSPRLIB@
+
+# Compiler Information
+CC := @CC@
+FC := @FC@
+FCV := @FCV@
+FC_LIB_PATH += @FC_LIB_PATH@
+
+# Python Information
+PYTHON := @PYTHON@
+F2PY := @F2PY@
+
+# Libs and Flags
+LIBS := @LIBS@
+LIBDIR += @LIBDIR@
+CPPFLAGS += ${DEFS}
+CFLAGS += @CFLAGS@
+FFLAGS += @FFLAGS@
+LDFLAGS += @LDFLAGS@
+FCOPT += @FCOPT@
+
+# Config Definitions
+DEFS := @DEFS@
+
+# WSPR Targets
+all: libwspr.a WsprMod/w.so fmtest fmtave fcal fmeasure wsprcode wspr0
+ @echo ''
+ @echo '---------------------------------------------'
+ @echo " ${PROGRAM} ${VERSION} Make Summary"
+ @echo '---------------------------------------------'
+ @echo ''
+ @echo " To Install ${PROGRAM}"
+ @echo ' Type .........: sudo make install'
+ @echo ' Then, type ...: wspr'
+ @echo ''
+ @echo ' Clean & Rebuild'
+ @echo ' To Clean, type .......: make clean'
+ @echo ' To Dist Clean, type ..: make distclean'
+ @echo ''
+
+# Default Rules
+%.o: %.c
+ ${CC} ${CPPFLAGS} ${CFLAGS} -c $<
+%.o: %.f
+ ${FC} ${FFLAGS} -c $<
+%.o: %.F
+ ${FC} ${FFLAGS} -c $<
+%.o: %.f90
+ ${FC} ${FFLAGS} -c $<
+%.o: %.F90
+ ${FC} ${FFLAGS} -c $<
+
+# Objects
+OBJS1 = wspr0.o wspr0init.o wspr0_rx.o wspr0_tx.o thnix_stub.o
+
+wspr0: ${OBJS1}
+ ${FC} ${FFLAGS} -o wspr0 ${FFLAGS} ${LDFLAGS} ${OBJS1} libwspr.a -lfftw3f -lportaudio
+
+wsprcode: wsprcode.o thnix_stub.o
+ ${FC} ${FFLAGS} ${LDFLAGS} -o wsprcode wsprcode.o thnix_stub.o libwspr.a -pthread
+
+OBJS3 = azdist.o ccf2.o chklevel.o db.o decode.o decode162.o deg2grid.o \
+ encode232.o fano232.o fchisq.o fil1.o flat3.o four2a.o\
+ fthread.o gencwid.o genmept.o genwspr.o geodist.o getrms.o getutc.o \
+ gmtime2.o gran.o grid2deg.o hash.o inter_mept.o iqdemod.o \
+ mept162.o mix162.o morse.o msgtrim.o nchar.o nhash.o pack50.o \
+ packcall.o packgrid.o packname.o packpfx.o packprop.o packtext2.o \
+ padevsub.o pctile.o peakup.o phasetx.o ps162.o ptt_unix.o rx.o \
+ rxtxcoord.o set.o sort.o sound.o spec162.o speciq.o ssort.o \
+ start_threads.o startdec.o startrx.o starttx.o sync162.o \
+ twkfreq.o tx.o thnix.o unpack50.o unpackcall.o unpackgrid.o \
+ unpackname.o unpackpfx.o unpackprop.o unpacktext2.o wfile5.o \
+ wqdecode.o wqencode.o wspr2.o xcor162.o xfft.o
+
+# Build Library: libwspr.a
+libwspr.a: ${OBJS3} acom1.f90 acom2.f90
+ ${AR} cr libwspr.a ${OBJS3}
+ ranlib libwspr.a
+
+# Build FMT Applications
+fmtest: fmtest.f90 libwspr.a
+ ${FC} ${FFLAGS} ${LDFLAGS} -o fmtest fmtest.f90 libwspr.a -lfftw3f -lportaudio
+
+fmtave: fmtave.f90
+ ${FC} ${FFLAGS} ${LDFLAGS} -o fmtave fmtave.f90
+
+fcal: fcal.f90
+ ${FC} ${FFLAGS} ${LDFLAGS} -o fcal fcal.f90
+
+fmeasure: fmeasure.f90
+ ${FC} ${FFLAGS} ${LDFLAGS} -o fmeasure fmeasure.f90
+
+# SRC for w.so
+F2PYSRCS = wspr1.f90 getfile.f90 paterminate.f90 audiodev.f90
+
+# Build W.SO & MV to WsprMod/
+WsprMod/w.so: libwspr.a ${F2PYSRCS} acom1.f90
+ ${F2PY} -c --quiet --fcompiler=${FCV} --f77exec=${FC} --f90exec=${FC} \
+ --opt="${FCOPT} ${LDFLAGS}" thnix.o ${LIBS} libwspr.a -m w ${F2PYSRCS}
+ ${MV} w*.so WsprMod/w.so
+
+# Install target wspr
+install:
+ @clear
+ @echo '-----------------------------------'
+ @echo " Installing ${PROGRAM}"
+ @echo '-----------------------------------'
+ @echo ''
+ @${MKDIR} -p ${BINDIR}
+ @${MKDIR} -p ${MANDIR}
+ @${MKDIR} -p ${DOCDIR}/examples
+ @${MKDIR} -p ${SHARED}/save/Samples
+ @${MKDIR} -p ${WSPRLIB}/WsprMod
+ @${MKDIR} -p ${SHARED}/../applications
+ @${MKDIR} -p ${SHARED}/../pixmaps
+ @install -m 755 --strip fmtest fcal fmeasure fmtave wspr0 wsprcode ${BINDIR}
+ @install -m 644 manpages/man1/*.1 ${MANDIR}
+ @install -m 644 save/Samples/* ${SHARED}/save/Samples/
+ @install -m 755 ./{gocal,Pmw*.py,wspr.py,wspr.sh,wsprrc} ${SHARED}
+ @install -m 644 ./{AUTHORS,README,NEWS,COPYING,COPYRIGHT} ${DOCDIR}
+ @install -m 644 ./hamlib_rig* ${SHARED}
+ @install -m 755 WsprMod/* ${WSPRLIB}/WsprMod
+ @install -m 644 ./wspr.desktop ${SHARED}/../applications
+ @install -m 644 ./wspr.xpm ${SHARED}/../pixmaps/
+ @${MV} -T ${SHARED}/wspr.sh ${BINDIR}/wspr
+ @${CHMOD} 755 ${BINDIR}/wspr
+ @${CP} -r doc/examples/* ${DOCDIR}/examples/
+ @${CHMOD} -R 644 ${DOCDIR}/examples/*
+ @echo "Finished Installing ${PROGRAM}"
+ @echo ''
+ @echo "Location: ${BINDIR}"
+ @echo '..To run, type..: wspr'
+ @echo ''
+ @echo "..Website ......: ${WEB}"
+ @echo "..Report Bugs ..: ${BUGS}"
+ @echo ''
+
+# Uninstall system files
+uninstall:
+ @clear
+ @echo '--------------------------------------'
+ @echo " Uninstalling ${PROGRAM}"
+ @echo '--------------------------------------'
+ @echo '.. Removing Installed files'
+ @echo ''
+ @${RM} -r ${SHARED}
+ @${RM} -r ${WSPRLIB}
+ @${RM} -r ${DOCDIR}
+ @${RM} -f ${BINDIR}/{fmtest,fcal,fmeasure,fmtave,wsprcode wspr}
+ @${RM} -f ${MANDIR}/{wspr.1,fmtest.1,fmtave.1,fmeasure.1,fcal.1,wsprcode.1}
+ @${RM} -f ${HOMEDIR}/{fmtest,fcal,fmeasure,fmtave,wspr0,wsprcode}
+ @${RM} -rf ${HOMEDIR}/{WsprMod/,examples/,save/,doc/,*.py,hamlib*,*.sh,*.dat,wsprrc,gocal,*cache*}
+ @${RM} -rf ${HOMEDIR}/{AUTHORS,README,NEWS,COPYING,COPYRIGHT}
+ @${RM} -f ${SHARED}/../applications/wspr.desktop
+ @${RM} -f ${SHARED}/../pixmaps/wspr.xpm
+ @echo ''
+ @echo ".. User files remain in: ${HOMEDIR}"
+ @echo ".. To remove, type ....: rm -R ${HOMEDIR}"
+ @echo ''
+
+# Cleanup Source Tree
+.PHONY: clean distclean dist
+clean :
+ ${RM} -f *.o libwspr.a *.pyc *.pyo WsprMod/*.pyc WsprMod/*.pyo \
+ WsprMod/w.so WsprMod/*.so w.so *~ wsprcode wspr0 fmtest fmtave fcal \
+ fmeasure
+
+distclean: clean
+ ${RM} -f config.log config.status ALL_WSPR*.TXT WSPR.INI \
+ audio_caps autoscan.log configure.scan decoded.txt hopping.ini \
+ fmt.ini fmt.all fmt.dat fcal.plt fmtave.out fmtave.out fmt.out fcal.out \
+ pixmap.dat wspr.log configure Makefile
+ ${RM} -rf ./autom4* WsprMod/__pycache__ ./dist
+
+dist: distclean
+ ${SHELL} ./wspr-dist.sh @PROGRAM@ @VERSION@
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..65c8cbe
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,4 @@
+WSPR-4.0 NEWS
+-------------
+
+
diff --git a/Pmw.py b/Pmw.py
new file mode 100644
index 0000000..a20232c
--- /dev/null
+++ b/Pmw.py
@@ -0,0 +1,9333 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: Pmw.py
+# Description: Widgets for Python3 applications
+# Source: http://sourceforge.net/projects/pmw/
+#
+# Copyright 1997-1999 Telstra Corporation Limited, Australia
+# Copyright 2000-2002 Really Good Software Pty Ltd, Australia
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+#-------------------------------------------------------------------------------
+import PmwColor
+Color = PmwColor
+del PmwColor
+
+import PmwBlt
+Blt = PmwBlt
+del PmwBlt
+
+
+### Loader functions:
+
+_VERSION = ''
+
+def setversion(version):
+ if version != _VERSION:
+#JHT raise ValueError, 'Dynamic versioning not available'
+ pass
+
+def setalphaversions(*alpha_versions):
+ if alpha_versions != ():
+#JHT raise ValueError, 'Dynamic versioning not available'
+ pass
+
+def version(alpha = 0):
+ if alpha:
+ return ()
+ else:
+ return _VERSION
+
+def installedversions(alpha = 0):
+ if alpha:
+ return ()
+ else:
+ return (_VERSION,)
+
+
+######################################################################
+### File: PmwBase.py
+# Pmw megawidget base classes.
+
+# This module provides a foundation for building megawidgets. It
+# contains the MegaArchetype class which manages component widgets and
+# configuration options. Also provided are the MegaToplevel and
+# MegaWidget classes, derived from the MegaArchetype class. The
+# MegaToplevel class contains a Tkinter Toplevel widget to act as the
+# container of the megawidget. This is used as the base class of all
+# megawidgets that are contained in their own top level window, such
+# as a Dialog window. The MegaWidget class contains a Tkinter Frame
+# to act as the container of the megawidget. This is used as the base
+# class of all other megawidgets, such as a ComboBox or ButtonBox.
+#
+# Megawidgets are built by creating a class that inherits from either
+# the MegaToplevel or MegaWidget class.
+
+import os
+import string
+import sys
+import traceback
+import types
+import tkinter
+import collections
+
+# Special values used in index() methods of several megawidgets.
+END = ['end']
+SELECT = ['select']
+DEFAULT = ['default']
+
+# Constant used to indicate that an option can only be set by a call
+# to the constructor.
+INITOPT = ['initopt']
+_DEFAULT_OPTION_VALUE = ['default_option_value']
+_useTkOptionDb = 0
+
+# Symbolic constants for the indexes into an optionInfo list.
+_OPT_DEFAULT = 0
+_OPT_VALUE = 1
+_OPT_FUNCTION = 2
+
+# Stacks
+
+_busyStack = []
+ # Stack which tracks nested calls to show/hidebusycursor (called
+ # either directly or from activate()/deactivate()). Each element
+ # is a dictionary containing:
+ # 'newBusyWindows' : List of windows which had busy_hold called
+ # on them during a call to showbusycursor().
+ # The corresponding call to hidebusycursor()
+ # will call busy_release on these windows.
+ # 'busyFocus' : The blt _Busy window which showbusycursor()
+ # set the focus to.
+ # 'previousFocus' : The focus as it was when showbusycursor()
+ # was called. The corresponding call to
+ # hidebusycursor() will restore this focus if
+ # the focus has not been changed from busyFocus.
+
+_grabStack = []
+ # Stack of grabbed windows. It tracks calls to push/popgrab()
+ # (called either directly or from activate()/deactivate()). The
+ # window on the top of the stack is the window currently with the
+ # grab. Each element is a dictionary containing:
+ # 'grabWindow' : The window grabbed by pushgrab(). The
+ # corresponding call to popgrab() will release
+ # the grab on this window and restore the grab
+ # on the next window in the stack (if there is one).
+ # 'globalMode' : True if the grabWindow was grabbed with a
+ # global grab, false if the grab was local
+ # and 'nograb' if no grab was performed.
+ # 'previousFocus' : The focus as it was when pushgrab()
+ # was called. The corresponding call to
+ # popgrab() will restore this focus.
+ # 'deactivateFunction' :
+ # The function to call (usually grabWindow.deactivate) if
+ # popgrab() is called (usually from a deactivate() method)
+ # on a window which is not at the top of the stack (that is,
+ # does not have the grab or focus). For example, if a modal
+ # dialog is deleted by the window manager or deactivated by
+ # a timer. In this case, all dialogs above and including
+ # this one are deactivated, starting at the top of the
+ # stack.
+
+ # Note that when dealing with focus windows, the name of the Tk
+ # widget is used, since it may be the '_Busy' window, which has no
+ # python instance associated with it.
+
+#=============================================================================
+
+# Functions used to forward methods from a class to a component.
+
+# Fill in a flattened method resolution dictionary for a class (attributes are
+# filtered out). Flattening honours the MI method resolution rules
+# (depth-first search of bases in order). The dictionary has method names
+# for keys and functions for values.
+def __methodDict(cls, dict):
+
+ # the strategy is to traverse the class in the _reverse_ of the normal
+ # order, and overwrite any duplicates.
+ baseList = list(cls.__bases__)
+ baseList.reverse()
+
+ # do bases in reverse order, so first base overrides last base
+ for super in baseList:
+ __methodDict(super, dict)
+
+ # do my methods last to override base classes
+ for key, value in list(cls.__dict__.items()):
+ # ignore class attributes
+ if type(value) == types.FunctionType:
+ dict[key] = value
+
+def __methods(cls):
+ # Return all method names for a class.
+
+ # Return all method names for a class (attributes are filtered
+ # out). Base classes are searched recursively.
+
+ dictio = {}
+ __methodDict(cls, dictio)
+ return list(dictio.keys())
+
+# Function body to resolve a forwarding given the target method name and the
+# attribute name. The resulting lambda requires only self, but will forward
+# any other parameters.
+__stringBody = (
+ 'def %(method)s(this, *args, **kw): return ' +
+ #'apply(this.%(attribute)s.%(method)s, args, kw)')
+ 'this.%(attribute)s.%(method)s(*args, **kw)');
+
+# Get a unique id
+__counter = 0
+def __unique():
+ global __counter
+ __counter = __counter + 1
+ return str(__counter)
+
+# Function body to resolve a forwarding given the target method name and the
+# index of the resolution function. The resulting lambda requires only self,
+# but will forward any other parameters. The target instance is identified
+# by invoking the resolution function.
+__funcBody = (
+ 'def %(method)s(this, *args, **kw): return ' +
+ #'apply(this.%(forwardFunc)s().%(method)s, args, kw)')
+ 'this.%(forwardFunc)s().%(method)s(*args, **kw)');
+
+def forwardmethods(fromClass, toClass, toPart, exclude = ()):
+ # Forward all methods from one class to another.
+
+ # Forwarders will be created in fromClass to forward method
+ # invocations to toClass. The methods to be forwarded are
+ # identified by flattening the interface of toClass, and excluding
+ # methods identified in the exclude list. Methods already defined
+ # in fromClass, or special methods with one or more leading or
+ # trailing underscores will not be forwarded.
+
+ # For a given object of class fromClass, the corresponding toClass
+ # object is identified using toPart. This can either be a String
+ # denoting an attribute of fromClass objects, or a function taking
+ # a fromClass object and returning a toClass object.
+
+ # Example:
+ # class MyClass:
+ # ...
+ # def __init__(self):
+ # ...
+ # self.__target = TargetClass()
+ # ...
+ # def findtarget(self):
+ # return self.__target
+ # forwardmethods(MyClass, TargetClass, '__target', ['dangerous1', 'dangerous2'])
+ # # ...or...
+ # forwardmethods(MyClass, TargetClass, MyClass.findtarget,
+ # ['dangerous1', 'dangerous2'])
+
+ # In both cases, all TargetClass methods will be forwarded from
+ # MyClass except for dangerous1, dangerous2, special methods like
+ # __str__, and pre-existing methods like findtarget.
+
+
+ # Allow an attribute name (String) or a function to determine the instance
+ #Python3 strings are not type 'str' but class 'str'...
+ #if type(toPart) != bytes:
+ if not isinstance(toPart, str):
+ # check that it is something like a function
+ #for classes, only works in Python 2: if isinstance(toPart, collections.Callable):pyht
+ if hasattr(toPart, '__call__'):
+ # If a method is passed, use the function within it
+ if hasattr(toPart, 'im_func'):
+ toPart = toPart.__func__
+
+ # After this is set up, forwarders in this class will use
+ # the forwarding function. The forwarding function name is
+ # guaranteed to be unique, so that it can't be hidden by subclasses
+ forwardName = '__fwdfunc__' + __unique()
+ fromClass.__dict__[forwardName] = toPart
+
+ # It's not a valid type
+ else:
+ raise TypeError('toPart must be attribute name, function or method')
+
+ # get the full set of candidate methods
+ dict = {}
+ __methodDict(toClass, dict)
+
+
+ # discard special methods
+ for ex in list(dict.keys()):
+ if ex[:1] == '_' or ex[-1:] == '_':
+ del dict[ex]
+ # discard dangerous methods supplied by the caller
+ for ex in exclude:
+ if ex in dict:
+ del dict[ex]
+ # discard methods already defined in fromClass
+ for ex in __methods(fromClass):
+ if ex in dict:
+ del dict[ex]
+
+ for method, func in list(dict.items()):
+ d = {'method': method, 'func': func}
+ if isinstance(toPart, str):
+ execString = \
+ __stringBody % {'method' : method, 'attribute' : toPart}
+ else:
+ execString = \
+ __funcBody % {'forwardFunc' : forwardName, 'method' : method}
+
+ exec(execString, d)
+
+ # this creates a method
+ #fromClass.__dict__[method] = d[method]
+ setattr(fromClass, method, d[method])
+
+
+#=============================================================================
+
+def setgeometryanddeiconify(window, geom):
+ # To avoid flashes on X and to position the window correctly on NT
+ # (caused by Tk bugs).
+
+ if os.name == 'nt' or \
+ (os.name == 'posix' and sys.platform[:6] == 'cygwin'):
+ # Require overrideredirect trick to stop window frame
+ # appearing momentarily.
+ redirect = window.overrideredirect()
+ if not redirect:
+ window.overrideredirect(1)
+ window.deiconify()
+ if geom is not None:
+ window.geometry(geom)
+ # Call update_idletasks to ensure NT moves the window to the
+ # correct position it is raised.
+ window.update_idletasks()
+ window.tkraise()
+ if not redirect:
+ window.overrideredirect(0)
+ else:
+ if geom is not None:
+ window.geometry(geom)
+
+ # Problem!? Which way around should the following two calls
+ # go? If deiconify() is called first then I get complaints
+ # from people using the enlightenment or sawfish window
+ # managers that when a dialog is activated it takes about 2
+ # seconds for the contents of the window to appear. But if
+ # tkraise() is called first then I get complaints from people
+ # using the twm window manager that when a dialog is activated
+ # it appears in the top right corner of the screen and also
+ # takes about 2 seconds to appear.
+
+ #window.tkraise()
+ # Call update_idletasks to ensure certain window managers (eg:
+ # enlightenment and sawfish) do not cause Tk to delay for
+ # about two seconds before displaying window.
+ #window.update_idletasks()
+ #window.deiconify()
+
+ window.deiconify()
+ if window.overrideredirect():
+ # The window is not under the control of the window manager
+ # and so we need to raise it ourselves.
+ window.tkraise()
+
+#=============================================================================
+
+class MegaArchetype:
+ # Megawidget abstract root class.
+
+ # This class provides methods which are inherited by classes
+ # implementing useful bases (this class doesn't provide a
+ # container widget inside which the megawidget can be built).
+
+ def __init__(self, parent = None, hullClass = None):
+
+ # Mapping from each megawidget option to a list of information
+ # about the option
+ # - default value
+ # - current value
+ # - function to call when the option is initialised in the
+ # call to initialiseoptions() in the constructor or
+ # modified via configure(). If this is INITOPT, the
+ # option is an initialisation option (an option that can
+ # be set by the call to the constructor but can not be
+ # used with configure).
+ # This mapping is not initialised here, but in the call to
+ # defineoptions() which precedes construction of this base class.
+ #
+ # self._optionInfo = {}
+
+ # Mapping from each component name to a tuple of information
+ # about the component.
+ # - component widget instance
+ # - configure function of widget instance
+ # - the class of the widget (Frame, EntryField, etc)
+ # - cget function of widget instance
+ # - the name of the component group of this component, if any
+ self.__componentInfo = {}
+
+ # Mapping from alias names to the names of components or
+ # sub-components.
+ self.__componentAliases = {}
+
+ # Contains information about the keywords provided to the
+ # constructor. It is a mapping from the keyword to a tuple
+ # containing:
+ # - value of keyword
+ # - a boolean indicating if the keyword has been used.
+ # A keyword is used if, during the construction of a megawidget,
+ # - it is defined in a call to defineoptions() or addoptions(), or
+ # - it references, by name, a component of the megawidget, or
+ # - it references, by group, at least one component
+ # At the end of megawidget construction, a call is made to
+ # initialiseoptions() which reports an error if there are
+ # unused options given to the constructor.
+ #
+ # After megawidget construction, the dictionary contains
+ # keywords which refer to a dynamic component group, so that
+ # these components can be created after megawidget
+ # construction and still use the group options given to the
+ # constructor.
+ #
+ # self._constructorKeywords = {}
+
+ # List of dynamic component groups. If a group is included in
+ # this list, then it not an error if a keyword argument for
+ # the group is given to the constructor or to configure(), but
+ # no components with this group have been created.
+ # self._dynamicGroups = ()
+
+ if hullClass is None:
+ self._hull = None
+ else:
+ if parent is None:
+ parent = tkinter._default_root
+
+ # Create the hull.
+ self._hull = self.createcomponent('hull',
+ (), None,
+ hullClass, (parent,))
+ _hullToMegaWidget[self._hull] = self
+
+ if _useTkOptionDb:
+ # Now that a widget has been created, query the Tk
+ # option database to get the default values for the
+ # options which have not been set in the call to the
+ # constructor. This assumes that defineoptions() is
+ # called before the __init__().
+ option_get = self.option_get
+ _VALUE = _OPT_VALUE
+ _DEFAULT = _OPT_DEFAULT
+ for name, info in list(self._optionInfo.items()):
+ value = info[_VALUE]
+ if value is _DEFAULT_OPTION_VALUE:
+ resourceClass = str.upper(name[0]) + name[1:]
+ value = option_get(name, resourceClass)
+ if value != '':
+ try:
+ # Convert the string to int/float/tuple, etc
+ value = eval(value, {'__builtins__': {}})
+ except:
+ pass
+ info[_VALUE] = value
+ else:
+ info[_VALUE] = info[_DEFAULT]
+
+ def destroy(self):
+ # Clean up optionInfo in case it contains circular references
+ # in the function field, such as self._settitle in class
+ # MegaToplevel.
+
+ self._optionInfo = {}
+ if self._hull is not None:
+ del _hullToMegaWidget[self._hull]
+ self._hull.destroy()
+
+ #======================================================================
+ # Methods used (mainly) during the construction of the megawidget.
+
+ def defineoptions(self, keywords, optionDefs, dynamicGroups = ()):
+ # Create options, providing the default value and the method
+ # to call when the value is changed. If any option created by
+ # base classes has the same name as one in <optionDefs>, the
+ # base class's value and function will be overriden.
+
+ # This should be called before the constructor of the base
+ # class, so that default values defined in the derived class
+ # override those in the base class.
+
+ if not hasattr(self, '_constructorKeywords'):
+ # First time defineoptions has been called.
+ tmp = {}
+ for option, value in list(keywords.items()):
+ tmp[option] = [value, 0]
+ self._constructorKeywords = tmp
+ self._optionInfo = {}
+ self._initialiseoptions_counter = 0
+ self._initialiseoptions_counter = self._initialiseoptions_counter + 1
+
+ if not hasattr(self, '_dynamicGroups'):
+ self._dynamicGroups = ()
+ self._dynamicGroups = self._dynamicGroups + tuple(dynamicGroups)
+ self.addoptions(optionDefs)
+
+ def addoptions(self, optionDefs):
+ # Add additional options, providing the default value and the
+ # method to call when the value is changed. See
+ # "defineoptions" for more details
+
+ # optimisations:
+ optionInfo = self._optionInfo
+ #optionInfo_has_key = optionInfo.has_key
+ keywords = self._constructorKeywords
+ #keywords_has_key = keywords.has_key
+ FUNCTION = _OPT_FUNCTION
+
+ for name, default, function in optionDefs:
+ if '_' not in name:
+ # The option will already exist if it has been defined
+ # in a derived class. In this case, do not override the
+ # default value of the option or the callback function
+ # if it is not None.
+ if not name in optionInfo:
+ if name in keywords:
+ value = keywords[name][0]
+ optionInfo[name] = [default, value, function]
+ del keywords[name]
+ else:
+ if _useTkOptionDb:
+ optionInfo[name] = \
+ [default, _DEFAULT_OPTION_VALUE, function]
+ else:
+ optionInfo[name] = [default, default, function]
+ elif optionInfo[name][FUNCTION] is None:
+ optionInfo[name][FUNCTION] = function
+ else:
+ # This option is of the form "component_option". If this is
+ # not already defined in self._constructorKeywords add it.
+ # This allows a derived class to override the default value
+ # of an option of a component of a base class.
+ if not name in keywords:
+ keywords[name] = [default, 0]
+
+ def createcomponent(self, componentName, componentAliases,
+ componentGroup, widgetClass, *widgetArgs, **kw):
+ #print('inCreateComponent', componentName)
+ # Create a component (during construction or later).
+
+ if componentName in self.__componentInfo:
+ raise ValueError('Component "%s" already exists' % componentName)
+
+ if '_' in componentName:
+ raise ValueError('Component name "%s" must not contain "_"' % componentName)
+
+ if hasattr(self, '_constructorKeywords'):
+ keywords = self._constructorKeywords
+ else:
+ keywords = {}
+ for alias, component in componentAliases:
+ # Create aliases to the component and its sub-components.
+ index = str.find(component, '_')
+ if index < 0:
+ self.__componentAliases[alias] = (component, None)
+ else:
+ mainComponent = component[:index]
+ subComponent = component[(index + 1):]
+ self.__componentAliases[alias] = (mainComponent, subComponent)
+
+ # Remove aliases from the constructor keyword arguments by
+ # replacing any keyword arguments that begin with *alias*
+ # with corresponding keys beginning with *component*.
+
+ alias = alias + '_'
+ aliasLen = len(alias)
+ for option in list(keywords.keys()):
+ if len(option) > aliasLen and option[:aliasLen] == alias:
+ newkey = component + '_' + option[aliasLen:]
+ keywords[newkey] = keywords[option]
+ del keywords[option]
+
+ componentPrefix = componentName + '_'
+ nameLen = len(componentPrefix)
+ for option in list(keywords.keys()):
+ if len(option) > nameLen and option[:nameLen] == componentPrefix:
+ # The keyword argument refers to this component, so add
+ # this to the options to use when constructing the widget.
+ kw[option[nameLen:]] = keywords[option][0]
+ del keywords[option]
+ else:
+ # Check if this keyword argument refers to the group
+ # of this component. If so, add this to the options
+ # to use when constructing the widget. Mark the
+ # keyword argument as being used, but do not remove it
+ # since it may be required when creating another
+ # component.
+ index = str.find(option, '_')
+ if index >= 0 and componentGroup == option[:index]:
+ rest = option[(index + 1):]
+ kw[rest] = keywords[option][0]
+ keywords[option][1] = 1
+
+ if 'pyclass' in kw:
+ widgetClass = kw['pyclass']
+ del kw['pyclass']
+ if widgetClass is None:
+ return None
+ if len(widgetArgs) == 1 and type(widgetArgs[0]) == tuple:
+ # Arguments to the constructor can be specified as either
+ # multiple trailing arguments to createcomponent() or as a
+ # single tuple argument.
+ widgetArgs = widgetArgs[0]
+ widget = widgetClass(*widgetArgs, **kw)
+ componentClass = widget.__class__.__name__
+ self.__componentInfo[componentName] = (widget, widget.configure,
+ componentClass, widget.cget, componentGroup)
+
+ return widget
+
+ def destroycomponent(self, name):
+ # Remove a megawidget component.
+
+ # This command is for use by megawidget designers to destroy a
+ # megawidget component.
+
+ self.__componentInfo[name][0].destroy()
+ del self.__componentInfo[name]
+
+ def createlabel(self, parent, childCols = 1, childRows = 1):
+
+ labelpos = self['labelpos']
+ labelmargin = self['labelmargin']
+ if labelpos is None:
+ return
+
+ label = self.createcomponent('label',
+ (), None,
+ tkinter.Label, (parent,))
+
+ if labelpos[0] in 'ns':
+ # vertical layout
+ if labelpos[0] == 'n':
+ row = 0
+ margin = 1
+ else:
+ row = childRows + 3
+ margin = row - 1
+ label.grid(column=2, row=row, columnspan=childCols, sticky=labelpos)
+ parent.grid_rowconfigure(margin, minsize=labelmargin)
+ else:
+ # horizontal layout
+ if labelpos[0] == 'w':
+ col = 0
+ margin = 1
+ else:
+ col = childCols + 3
+ margin = col - 1
+ label.grid(column=col, row=2, rowspan=childRows, sticky=labelpos)
+ parent.grid_columnconfigure(margin, minsize=labelmargin)
+
+ def initialiseoptions(self, dummy = None):
+ self._initialiseoptions_counter = self._initialiseoptions_counter - 1
+ if self._initialiseoptions_counter == 0:
+ unusedOptions = []
+ keywords = self._constructorKeywords
+ for name in list(keywords.keys()):
+ used = keywords[name][1]
+ if not used:
+ # This keyword argument has not been used. If it
+ # does not refer to a dynamic group, mark it as
+ # unused.
+ index = str.find(name, '_')
+ if index < 0 or name[:index] not in self._dynamicGroups:
+ unusedOptions.append(name)
+ if len(unusedOptions) > 0:
+ if len(unusedOptions) == 1:
+ text = 'Unknown option "'
+ else:
+ text = 'Unknown options "'
+ raise KeyError(text + str.join(unusedOptions, ', ') + \
+ '" for ' + self.__class__.__name__)
+
+ # Call the configuration callback function for every option.
+ FUNCTION = _OPT_FUNCTION
+ for info in list(self._optionInfo.values()):
+ func = info[FUNCTION]
+ if func is not None and func is not INITOPT:
+ func()
+
+ #======================================================================
+ # Method used to configure the megawidget.
+
+ def configure(self, option=None, **kw):
+ # Query or configure the megawidget options.
+ #
+ # If not empty, *kw* is a dictionary giving new
+ # values for some of the options of this megawidget or its
+ # components. For options defined for this megawidget, set
+ # the value of the option to the new value and call the
+ # configuration callback function, if any. For options of the
+ # form <component>_<option>, where <component> is a component
+ # of this megawidget, call the configure method of the
+ # component giving it the new value of the option. The
+ # <component> part may be an alias or a component group name.
+ #
+ # If *option* is None, return all megawidget configuration
+ # options and settings. Options are returned as standard 5
+ # element tuples
+ #
+ # If *option* is a string, return the 5 element tuple for the
+ # given configuration option.
+
+ # First, deal with the option queries.
+ if len(kw) == 0:
+ # This configure call is querying the values of one or all options.
+ # Return 5-tuples:
+ # (optionName, resourceName, resourceClass, default, value)
+ if option is None:
+ rtn = {}
+ for option, config in list(self._optionInfo.items()):
+ resourceClass = str.upper(option[0]) + option[1:]
+ rtn[option] = (option, option, resourceClass,
+ config[_OPT_DEFAULT], config[_OPT_VALUE])
+ return rtn
+ else:
+ config = self._optionInfo[option]
+ resourceClass = str.upper(option[0]) + option[1:]
+ return (option, option, resourceClass, config[_OPT_DEFAULT],
+ config[_OPT_VALUE])
+ # optimisations:
+ optionInfo = self._optionInfo
+ #Py2 optionInfo_has_key = optionInfo.has_key
+ componentInfo = self.__componentInfo
+ #Py2 componentInfo_has_key = componentInfo.has_key
+ componentAliases = self.__componentAliases
+ #Py2 componentAliases_has_key = componentAliases.has_key
+ VALUE = _OPT_VALUE
+ FUNCTION = _OPT_FUNCTION
+
+ # This will contain a list of options in *kw* which
+ # are known to this megawidget.
+ directOptions = []
+
+ # This will contain information about the options in
+ # *kw* of the form <component>_<option>, where
+ # <component> is a component of this megawidget. It is a
+ # dictionary whose keys are the configure method of each
+ # component and whose values are a dictionary of options and
+ # values for the component.
+ indirectOptions = {}
+ #Py2 indirectOptions_has_key = indirectOptions.has_key
+
+ for option, value in list(kw.items()):
+ if option in optionInfo:
+ # This is one of the options of this megawidget.
+ # Make sure it is not an initialisation option.
+ if optionInfo[option][FUNCTION] is INITOPT:
+ raise KeyError('Cannot configure initialisation option "' \
+ + option + '" for ' + self.__class__.__name__)
+ optionInfo[option][VALUE] = value
+ directOptions.append(option)
+ else:
+ index = str.find(option, '_')
+ if index >= 0:
+ # This option may be of the form <component>_<option>.
+ component = option[:index]
+ componentOption = option[(index + 1):]
+
+ # Expand component alias
+ if component in componentAliases:
+ component, subComponent = componentAliases[component]
+ if subComponent is not None:
+ componentOption = subComponent + '_' \
+ + componentOption
+
+ # Expand option string to write on error
+ option = component + '_' + componentOption
+
+ if component in componentInfo:
+ # Configure the named component
+ componentConfigFuncs = [componentInfo[component][1]]
+ else:
+ # Check if this is a group name and configure all
+ # components in the group.
+ componentConfigFuncs = []
+ for info in list(componentInfo.values()):
+ if info[4] == component:
+ componentConfigFuncs.append(info[1])
+
+ if len(componentConfigFuncs) == 0 and \
+ component not in self._dynamicGroups:
+ raise KeyError('Unknown option "' + option + \
+ '" for ' + self.__class__.__name__)
+
+ # Add the configure method(s) (may be more than
+ # one if this is configuring a component group)
+ # and option/value to dictionary.
+ for componentConfigFunc in componentConfigFuncs:
+ if not componentConfigFunc in indirectOptions:
+ indirectOptions[componentConfigFunc] = {}
+ indirectOptions[componentConfigFunc][componentOption] \
+ = value
+ else:
+ raise KeyError('Unknown option "' + option + \
+ '" for ' + self.__class__.__name__)
+
+ # Call the configure methods for any components.
+ #list(map(apply, list(indirectOptions.keys()),
+ # ((),) * len(indirectOptions), list(indirectOptions.values())))
+ for func in indirectOptions.keys():
+ func( **indirectOptions[func]);
+
+
+ # Call the configuration callback function for each option.
+ for option in directOptions:
+ info = optionInfo[option]
+ func = info[_OPT_FUNCTION]
+ if func is not None:
+ func()
+
+ def __setitem__(self, key, value):
+ self.configure(*(), **{key: value})
+
+ #======================================================================
+ # Methods used to query the megawidget.
+
+ def component(self, name):
+ # Return a component widget of the megawidget given the
+ # component's name
+ # This allows the user of a megawidget to access and configure
+ # widget components directly.
+
+ # Find the main component and any subcomponents
+ index = str.find(name, '_')
+ if index < 0:
+ component = name
+ remainingComponents = None
+ else:
+ component = name[:index]
+ remainingComponents = name[(index + 1):]
+
+ # Expand component alias
+ if component in self.__componentAliases:
+ component, subComponent = self.__componentAliases[component]
+ if subComponent is not None:
+ if remainingComponents is None:
+ remainingComponents = subComponent
+ else:
+ remainingComponents = subComponent + '_' \
+ + remainingComponents
+
+ widget = self.__componentInfo[component][0]
+ if remainingComponents is None:
+ return widget
+ else:
+ return widget.component(remainingComponents)
+
+ def interior(self):
+ return self._hull
+
+ def hulldestroyed(self):
+ return self._hull not in _hullToMegaWidget
+
+ def __str__(self):
+ return str(self._hull)
+
+ def cget(self, option):
+ # Get current configuration setting.
+
+ # Return the value of an option, for example myWidget['font'].
+ if option in self._optionInfo:
+ return self._optionInfo[option][_OPT_VALUE]
+ else:
+ index = str.find(option, '_')
+ if index >= 0:
+ component = option[:index]
+ componentOption = option[(index + 1):]
+
+ # Expand component alias
+ if component in self.__componentAliases:
+ component, subComponent = self.__componentAliases[component]
+ if subComponent is not None:
+ componentOption = subComponent + '_' + componentOption
+
+ # Expand option string to write on error
+ option = component + '_' + componentOption
+
+ if component in self.__componentInfo:
+ # Call cget on the component.
+ componentCget = self.__componentInfo[component][3]
+ return componentCget(componentOption)
+ else:
+ # If this is a group name, call cget for one of
+ # the components in the group.
+ for info in list(self.__componentInfo.values()):
+ if info[4] == component:
+ componentCget = info[3]
+ return componentCget(componentOption)
+
+ raise KeyError('Unknown option "' + option + \
+ '" for ' + self.__class__.__name__)
+
+ __getitem__ = cget
+
+ def isinitoption(self, option):
+ return self._optionInfo[option][_OPT_FUNCTION] is INITOPT
+
+ def options(self):
+ options = []
+ if hasattr(self, '_optionInfo'):
+ for option, info in list(self._optionInfo.items()):
+ isinit = info[_OPT_FUNCTION] is INITOPT
+ default = info[_OPT_DEFAULT]
+ options.append((option, default, isinit))
+ options.sort()
+ return options
+
+ def components(self):
+ # Return a list of all components.
+
+ # This list includes the 'hull' component and all widget subcomponents
+
+ names = list(self.__componentInfo.keys())
+ names.sort()
+ return names
+
+ def componentaliases(self):
+ # Return a list of all component aliases.
+
+ componentAliases = self.__componentAliases
+
+ names = list(componentAliases.keys())
+ names.sort()
+ rtn = []
+ for alias in names:
+ (mainComponent, subComponent) = componentAliases[alias]
+ if subComponent is None:
+ rtn.append((alias, mainComponent))
+ else:
+ rtn.append((alias, mainComponent + '_' + subComponent))
+
+ return rtn
+
+ def componentgroup(self, name):
+ return self.__componentInfo[name][4]
+
+#=============================================================================
+
+# The grab functions are mainly called by the activate() and
+# deactivate() methods.
+#
+# Use pushgrab() to add a new window to the grab stack. This
+# releases the grab by the window currently on top of the stack (if
+# there is one) and gives the grab and focus to the new widget.
+#
+# To remove the grab from the window on top of the grab stack, call
+# popgrab().
+#
+# Use releasegrabs() to release the grab and clear the grab stack.
+
+def pushgrab(grabWindow, globalMode, deactivateFunction):
+ prevFocus = grabWindow.tk.call('focus')
+ grabInfo = {
+ 'grabWindow' : grabWindow,
+ 'globalMode' : globalMode,
+ 'previousFocus' : prevFocus,
+ 'deactivateFunction' : deactivateFunction,
+ }
+ _grabStack.append(grabInfo)
+ _grabtop()
+ grabWindow.focus_set()
+
+def popgrab(window):
+ # Return the grab to the next window in the grab stack, if any.
+
+ # If this window is not at the top of the grab stack, then it has
+ # just been deleted by the window manager or deactivated by a
+ # timer. Call the deactivate method for the modal dialog above
+ # this one on the stack.
+ if _grabStack[-1]['grabWindow'] != window:
+ for index in range(len(_grabStack)):
+ if _grabStack[index]['grabWindow'] == window:
+ _grabStack[index + 1]['deactivateFunction']()
+ break
+
+ grabInfo = _grabStack[-1]
+ del _grabStack[-1]
+
+ topWidget = grabInfo['grabWindow']
+ prevFocus = grabInfo['previousFocus']
+ globalMode = grabInfo['globalMode']
+
+ if globalMode != 'nograb':
+ topWidget.grab_release()
+
+ if len(_grabStack) > 0:
+ _grabtop()
+ if prevFocus != '':
+ try:
+ topWidget.tk.call('focus', prevFocus)
+ except tkinter.TclError:
+ # Previous focus widget has been deleted. Set focus
+ # to root window.
+ tkinter._default_root.focus_set()
+ else:
+ # Make sure that focus does not remain on the released widget.
+ if len(_grabStack) > 0:
+ topWidget = _grabStack[-1]['grabWindow']
+ topWidget.focus_set()
+ else:
+ tkinter._default_root.focus_set()
+
+def grabstacktopwindow():
+ if len(_grabStack) == 0:
+ return None
+ else:
+ return _grabStack[-1]['grabWindow']
+
+def releasegrabs():
+ # Release grab and clear the grab stack.
+
+ current = tkinter._default_root.grab_current()
+ if current is not None:
+ current.grab_release()
+ _grabStack[:] = []
+
+def _grabtop():
+ grabInfo = _grabStack[-1]
+ topWidget = grabInfo['grabWindow']
+ globalMode = grabInfo['globalMode']
+
+ if globalMode == 'nograb':
+ return
+
+ while 1:
+ try:
+ if globalMode:
+ topWidget.grab_set_global()
+ else:
+ topWidget.grab_set()
+ break
+ except tkinter.TclError:
+ # Another application has grab. Keep trying until
+ # grab can succeed.
+ topWidget.after(100)
+
+#=============================================================================
+
+class MegaToplevel(MegaArchetype):
+
+ def __init__(self, parent = None, **kw):
+ # Define the options for this megawidget.
+ optiondefs = (
+ ('activatecommand', None, None),
+ ('deactivatecommand', None, None),
+ ('master', None, None),
+ ('title', None, self._settitle),
+ ('hull_class', self.__class__.__name__, None),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ #MegaArchetype.__init__(self, parent, tkinter.Toplevel)
+ super().__init__(parent, tkinter.Toplevel)
+
+ # Initialise instance.
+
+ # Set WM_DELETE_WINDOW protocol, deleting any old callback, so
+ # memory does not leak.
+ if hasattr(self._hull, '_Pmw_WM_DELETE_name'):
+ self._hull.tk.deletecommand(self._hull._Pmw_WM_DELETE_name)
+ #changed from self.register and self.protocol - python3 error...
+ self._hull._Pmw_WM_DELETE_name = \
+ self.register(self._userdeletewindow, needcleanup = 0)
+ self.protocol('WM_DELETE_WINDOW', self._hull._Pmw_WM_DELETE_name)
+
+ # Initialise instance variables.
+
+ self._firstShowing = 1
+ # Used by show() to ensure window retains previous position on screen.
+
+ # The IntVar() variable to wait on during a modal dialog.
+ self._wait = None
+
+ self._active = 0
+ self._userdeletefunc = self.destroy
+ self._usermodaldeletefunc = self.deactivate
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def _settitle(self):
+ title = self['title']
+ if title is not None:
+ self.title(title)
+
+ def userdeletefunc(self, func=None):
+ if func:
+ self._userdeletefunc = func
+ else:
+ return self._userdeletefunc
+
+ def usermodaldeletefunc(self, func=None):
+ if func:
+ self._usermodaldeletefunc = func
+ else:
+ return self._usermodaldeletefunc
+
+ def _userdeletewindow(self):
+ if self.active():
+ self._usermodaldeletefunc()
+ else:
+ self._userdeletefunc()
+
+ def destroy(self):
+ # Allow this to be called more than once.
+ if self._hull in _hullToMegaWidget:
+ self.deactivate()
+
+ # Remove circular references, so that object can get cleaned up.
+ del self._userdeletefunc
+ del self._usermodaldeletefunc
+
+ MegaArchetype.destroy(self)
+
+ def show(self, master = None):
+ if self.state() != 'normal':
+ if self._firstShowing:
+ # Just let the window manager determine the window
+ # position for the first time.
+ geom = None
+ else:
+ # Position the window at the same place it was last time.
+ geom = self._sameposition()
+ setgeometryanddeiconify(self, geom)
+
+ if self._firstShowing:
+ self._firstShowing = 0
+ else:
+ if self.transient() == '':
+ self.tkraise()
+
+ # Do this last, otherwise get flashing on NT:
+ if master is not None:
+ if master == 'parent':
+ parent = self.winfo_parent()
+ # winfo_parent() should return the parent widget, but the
+ # the current version of Tkinter returns a string.
+ if type(parent) is str:
+ parent = self._hull._nametowidget(parent)
+ master = parent.winfo_toplevel()
+ self.transient(master)
+
+ self.focus()
+
+ def _centreonscreen(self):
+ # Centre the window on the screen. (Actually halfway across
+ # and one third down.)
+
+ parent = self.winfo_parent()
+ if type(parent) is str:
+ parent = self._hull._nametowidget(parent)
+
+ # Find size of window.
+ self.update_idletasks()
+ width = self.winfo_width()
+ height = self.winfo_height()
+ if width == 1 and height == 1:
+ # If the window has not yet been displayed, its size is
+ # reported as 1x1, so use requested size.
+ width = self.winfo_reqwidth()
+ height = self.winfo_reqheight()
+
+ # Place in centre of screen:
+ x = (self.winfo_screenwidth() - width) / 2 - parent.winfo_vrootx()
+ y = (self.winfo_screenheight() - height) / 3 - parent.winfo_vrooty()
+ if x < 0:
+ x = 0
+ if y < 0:
+ y = 0
+ return '+%d+%d' % (x, y)
+
+ def _sameposition(self):
+ # Position the window at the same place it was last time.
+
+ geometry = self.geometry()
+ index = str.find(geometry, '+')
+ if index >= 0:
+ return geometry[index:]
+ else:
+ return None
+
+ def activate(self, globalMode = 0, geometry = 'centerscreenfirst'):
+ if self._active:
+ raise ValueError('Window is already active')
+ if self.state() == 'normal':
+ self.withdraw()
+
+ self._active = 1
+
+ showbusycursor()
+
+ if self._wait is None:
+ self._wait = tkinter.IntVar()
+ self._wait.set(0)
+
+ if geometry == 'centerscreenalways':
+ geom = self._centreonscreen()
+ elif geometry == 'centerscreenfirst':
+ if self._firstShowing:
+ # Centre the window the first time it is displayed.
+ geom = self._centreonscreen()
+ else:
+ # Position the window at the same place it was last time.
+ geom = self._sameposition()
+ elif geometry[:5] == 'first':
+ if self._firstShowing:
+ geom = geometry[5:]
+ else:
+ # Position the window at the same place it was last time.
+ geom = self._sameposition()
+ else:
+ geom = geometry
+
+ self._firstShowing = 0
+
+ setgeometryanddeiconify(self, geom)
+
+ # Do this last, otherwise get flashing on NT:
+ master = self['master']
+ if master is not None:
+ if master == 'parent':
+ parent = self.winfo_parent()
+ # winfo_parent() should return the parent widget, but the
+ # the current version of Tkinter returns a string.
+ if type(parent) is str:
+ parent = self._hull._nametowidget(parent)
+ master = parent.winfo_toplevel()
+ self.transient(master)
+
+ pushgrab(self._hull, globalMode, self.deactivate)
+ command = self['activatecommand']
+ if isinstance(command, collections.Callable):
+ command()
+ self.wait_variable(self._wait)
+
+ return self._result
+
+ def deactivate(self, result=None):
+ if not self._active:
+ return
+ self._active = 0
+
+ # Restore the focus before withdrawing the window, since
+ # otherwise the window manager may take the focus away so we
+ # can't redirect it. Also, return the grab to the next active
+ # window in the stack, if any.
+ popgrab(self._hull)
+
+ command = self['deactivatecommand']
+ if isinstance(command, collections.Callable):
+ command()
+
+ self.withdraw()
+ hidebusycursor(forceFocusRestore = 1)
+
+ self._result = result
+ self._wait.set(1)
+
+ def active(self):
+ return self._active
+
+forwardmethods(MegaToplevel, tkinter.Toplevel, '_hull')
+
+#=============================================================================
+
+class MegaWidget(MegaArchetype):
+ def __init__(self, parent = None, **kw):
+ # Define the options for this megawidget.
+ optiondefs = (
+ ('hull_class', self.__class__.__name__, None),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ MegaArchetype.__init__(self, parent, tkinter.Frame)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+forwardmethods(MegaWidget, tkinter.Frame, '_hull')
+
+#=============================================================================
+
+# Public functions
+#-----------------
+
+_traceTk = 0
+def tracetk(root = None, on = 1, withStackTrace = 0, file=None):
+ global _withStackTrace
+ global _traceTkFile
+ global _traceTk
+
+ if root is None:
+ root = tkinter._default_root
+
+ _withStackTrace = withStackTrace
+ _traceTk = on
+ if on == 1:
+ #this causes trace not to work - not enabled by default in tk anymore?
+ #if hasattr(root.tk, '__class__'):
+ # Tracing already on
+ # return
+ if file is None:
+ _traceTkFile = sys.stderr
+ else:
+ _traceTkFile = file
+ tk = _TraceTk(root.tk)
+ else:
+ if not hasattr(root.tk, '__class__'):
+ # Tracing already off
+ return
+ tk = root.tk.getTclInterp()
+ _setTkInterps(root, tk)
+
+def showbusycursor():
+
+ _addRootToToplevelBusyInfo()
+ root = tkinter._default_root
+
+ busyInfo = {
+ 'newBusyWindows' : [],
+ 'previousFocus' : None,
+ 'busyFocus' : None,
+ }
+ _busyStack.append(busyInfo)
+
+ if _disableKeyboardWhileBusy:
+ # Remember the focus as it is now, before it is changed.
+ busyInfo['previousFocus'] = root.tk.call('focus')
+
+ if not _havebltbusy(root):
+ # No busy command, so don't call busy hold on any windows.
+ return
+
+ for (window, winInfo) in list(_toplevelBusyInfo.items()):
+ if (window.state() != 'withdrawn' and not winInfo['isBusy']
+ and not winInfo['excludeFromBusy']):
+ busyInfo['newBusyWindows'].append(window)
+ winInfo['isBusy'] = 1
+ _busy_hold(window, winInfo['busyCursorName'])
+
+ # Make sure that no events for the busy window get
+ # through to Tkinter, otherwise it will crash in
+ # _nametowidget with a 'KeyError: _Busy' if there is
+ # a binding on the toplevel window.
+ window.tk.call('bindtags', winInfo['busyWindow'], 'Pmw_Dummy_Tag')
+
+ if _disableKeyboardWhileBusy:
+ # Remember previous focus widget for this toplevel window
+ # and set focus to the busy window, which will ignore all
+ # keyboard events.
+ winInfo['windowFocus'] = \
+ window.tk.call('focus', '-lastfor', window._w)
+ window.tk.call('focus', winInfo['busyWindow'])
+ busyInfo['busyFocus'] = winInfo['busyWindow']
+
+ if len(busyInfo['newBusyWindows']) > 0:
+ if os.name == 'nt':
+ # NT needs an "update" before it will change the cursor.
+ window.update()
+ else:
+ window.update_idletasks()
+
+def hidebusycursor(forceFocusRestore = 0):
+
+ # Remember the focus as it is now, before it is changed.
+ root = tkinter._default_root
+ if _disableKeyboardWhileBusy:
+ currentFocus = root.tk.call('focus')
+
+ # Pop the busy info off the stack.
+ busyInfo = _busyStack[-1]
+ del _busyStack[-1]
+
+ for window in busyInfo['newBusyWindows']:
+ # If this window has not been deleted, release the busy cursor.
+ if window in _toplevelBusyInfo:
+ winInfo = _toplevelBusyInfo[window]
+ winInfo['isBusy'] = 0
+ _busy_release(window)
+
+ if _disableKeyboardWhileBusy:
+ # Restore previous focus window for this toplevel window,
+ # but only if is still set to the busy window (it may have
+ # been changed).
+ windowFocusNow = window.tk.call('focus', '-lastfor', window._w)
+ if windowFocusNow == winInfo['busyWindow']:
+ try:
+ window.tk.call('focus', winInfo['windowFocus'])
+ except tkinter.TclError:
+ # Previous focus widget has been deleted. Set focus
+ # to toplevel window instead (can't leave focus on
+ # busy window).
+ window.focus_set()
+
+ if _disableKeyboardWhileBusy:
+ # Restore the focus, depending on whether the focus had changed
+ # between the calls to showbusycursor and hidebusycursor.
+ if forceFocusRestore or busyInfo['busyFocus'] == currentFocus:
+ # The focus had not changed, so restore it to as it was before
+ # the call to showbusycursor,
+ previousFocus = busyInfo['previousFocus']
+ if previousFocus is not None:
+ try:
+ root.tk.call('focus', previousFocus)
+ except tkinter.TclError:
+ # Previous focus widget has been deleted; forget it.
+ pass
+ else:
+ # The focus had changed, so restore it to what it had been
+ # changed to before the call to hidebusycursor.
+ root.tk.call('focus', currentFocus)
+
+def clearbusycursor():
+ while len(_busyStack) > 0:
+ hidebusycursor()
+
+def setbusycursorattributes(window, **kw):
+ _addRootToToplevelBusyInfo()
+ for name, value in list(kw.items()):
+ if name == 'exclude':
+ _toplevelBusyInfo[window]['excludeFromBusy'] = value
+ elif name == 'cursorName':
+ _toplevelBusyInfo[window]['busyCursorName'] = value
+ else:
+ raise KeyError('Unknown busycursor attribute "' + name + '"')
+
+def _addRootToToplevelBusyInfo():
+ # Include the Tk root window in the list of toplevels. This must
+ # not be called before Tkinter has had a chance to be initialised by
+ # the application.
+
+ root = tkinter._default_root
+ if root == None:
+ root = tkinter.Tk()
+ if root not in _toplevelBusyInfo:
+ _addToplevelBusyInfo(root)
+
+def busycallback(command, updateFunction = None):
+ if not isinstance(command, collections.Callable):
+ raise ValueError('cannot register non-command busy callback %s %s' % \
+ (repr(command), type(command)))
+ wrapper = _BusyWrapper(command, updateFunction)
+ return wrapper.callback
+
+_errorReportFile = None
+_errorWindow = None
+
+def reporterrorstofile(file = None):
+ global _errorReportFile
+ _errorReportFile = file
+
+def displayerror(text):
+ global _errorWindow
+
+ if _errorReportFile is not None:
+ _errorReportFile.write(text + '\n')
+ else:
+ # Print error on standard error as well as to error window.
+ # Useful if error window fails to be displayed, for example
+ # when exception is triggered in a <Destroy> binding for root
+ # window.
+ sys.stderr.write(text + '\n')
+
+ if _errorWindow is None:
+ # The error window has not yet been created.
+ _errorWindow = _ErrorWindow()
+
+ _errorWindow.showerror(text)
+
+_root = None
+_disableKeyboardWhileBusy = 1
+
+def initialise(
+ root = None,
+ size = None,
+ fontScheme = None,
+ useTkOptionDb = 0,
+ noBltBusy = 0,
+ disableKeyboardWhileBusy = None,
+):
+ # Remember if show/hidebusycursor should ignore keyboard events.
+ global _disableKeyboardWhileBusy
+ if disableKeyboardWhileBusy is not None:
+ _disableKeyboardWhileBusy = disableKeyboardWhileBusy
+
+ # Do not use blt busy command if noBltBusy is set. Otherwise,
+ # use blt busy if it is available.
+ global _haveBltBusy
+ if noBltBusy:
+ _haveBltBusy = 0
+
+ # Save flag specifying whether the Tk option database should be
+ # queried when setting megawidget option default values.
+ global _useTkOptionDb
+ _useTkOptionDb = useTkOptionDb
+
+ # If we haven't been given a root window, use the default or
+ # create one.
+ if root is None:
+ if tkinter._default_root is None:
+ root = tkinter.Tk()
+ else:
+ root = tkinter._default_root
+
+ # If this call is initialising a different Tk interpreter than the
+ # last call, then re-initialise all global variables. Assume the
+ # last interpreter has been destroyed - ie: Pmw does not (yet)
+ # support multiple simultaneous interpreters.
+ global _root
+ if _root is not None and _root != root:
+ global _busyStack
+ global _errorWindow
+ global _grabStack
+ global _hullToMegaWidget
+ global _toplevelBusyInfo
+ _busyStack = []
+ _errorWindow = None
+ _grabStack = []
+ _hullToMegaWidget = {}
+ _toplevelBusyInfo = {}
+ _root = root
+
+ # Trap Tkinter Toplevel constructors so that a list of Toplevels
+ # can be maintained.
+ tkinter.Toplevel.title = __TkinterToplevelTitle
+
+ # Trap Tkinter widget destruction so that megawidgets can be
+ # destroyed when their hull widget is destoyed and the list of
+ # Toplevels can be pruned.
+ tkinter.Toplevel.destroy = __TkinterToplevelDestroy
+ tkinter.Widget.destroy = __TkinterWidgetDestroy
+
+ # Modify Tkinter's CallWrapper class to improve the display of
+ # errors which occur in callbacks.
+ tkinter.CallWrapper = __TkinterCallWrapper
+
+ # Make sure we get to know when the window manager deletes the
+ # root window. Only do this if the protocol has not yet been set.
+ # This is required if there is a modal dialog displayed and the
+ # window manager deletes the root window. Otherwise the
+ # application will not exit, even though there are no windows.
+ if root.protocol('WM_DELETE_WINDOW') == '':
+ root.protocol('WM_DELETE_WINDOW', root.destroy)
+
+ # Set the base font size for the application and set the
+ # Tk option database font resources.
+#JHT from .
+ _font_initialise(root, size, fontScheme)
+ return root
+
+def alignlabels(widgets, sticky = None):
+ if len(widgets) == 0:
+ return
+
+ widgets[0].update_idletasks()
+
+ # Determine the size of the maximum length label string.
+ maxLabelWidth = 0
+ for iwid in widgets:
+ labelWidth = iwid.grid_bbox(0, 1)[2]
+ if labelWidth > maxLabelWidth:
+ maxLabelWidth = labelWidth
+
+ # Adjust the margins for the labels such that the child sites and
+ # labels line up.
+ for iwid in widgets:
+ if sticky is not None:
+ iwid.component('label').grid(sticky=sticky)
+ iwid.grid_columnconfigure(0, minsize = maxLabelWidth)
+#=============================================================================
+
+# Private routines
+#-----------------
+_callToTkReturned = 1
+_recursionCounter = 1
+
+class _TraceTk:
+ def __init__(self, tclInterp):
+ self.tclInterp = tclInterp
+
+ def getTclInterp(self):
+ return self.tclInterp
+
+ # Calling from python into Tk.
+ def call(self, *args, **kw):
+ global _callToTkReturned
+ global _recursionCounter
+
+ _callToTkReturned = 0
+ if len(args) == 1 and type(args[0]) == tuple:
+ argStr = str(args[0])
+ else:
+ argStr = str(args)
+ _traceTkFile.write('CALL TK> %d:%s%s' %
+ (_recursionCounter, ' ' * _recursionCounter, argStr))
+ _recursionCounter = _recursionCounter + 1
+ try:
+ result = self.tclInterp.call(*args, **kw)
+ except tkinter.TclError as errorString:
+ _callToTkReturned = 1
+ _recursionCounter = _recursionCounter - 1
+ _traceTkFile.write('\nTK ERROR> %d:%s-> %s\n' %
+ (_recursionCounter, ' ' * _recursionCounter,
+ repr(errorString)))
+ if _withStackTrace:
+ _traceTkFile.write('CALL TK> stack:\n')
+ traceback.print_stack()
+ raise tkinter.TclError(errorString)
+
+ _recursionCounter = _recursionCounter - 1
+ if _callToTkReturned:
+ _traceTkFile.write('CALL RTN> %d:%s-> %s' %
+ (_recursionCounter, ' ' * _recursionCounter, repr(result)))
+ else:
+ _callToTkReturned = 1
+ if result:
+ _traceTkFile.write(' -> %s' % repr(result))
+ _traceTkFile.write('\n')
+ if _withStackTrace:
+ _traceTkFile.write('CALL TK> stack:\n')
+ traceback.print_stack()
+
+ _traceTkFile.flush()
+ return result
+
+ def __getattr__(self, key):
+ return getattr(self.tclInterp, key)
+
+def _setTkInterps(window, tk):
+ window.tk = tk
+ for child in list(window.children.values()):
+ _setTkInterps(child, tk)
+
+#=============================================================================
+
+# Functions to display a busy cursor. Keep a list of all toplevels
+# and display the busy cursor over them. The list will contain the Tk
+# root toplevel window as well as all other toplevel windows.
+# Also keep a list of the widget which last had focus for each
+# toplevel.
+
+# Map from toplevel windows to
+# {'isBusy', 'windowFocus', 'busyWindow',
+# 'excludeFromBusy', 'busyCursorName'}
+_toplevelBusyInfo = {}
+
+# Pmw needs to know all toplevel windows, so that it can call blt busy
+# on them. This is a hack so we get notified when a Tk topevel is
+# created. Ideally, the __init__ 'method' should be overridden, but
+# it is a 'read-only special attribute'. Luckily, title() is always
+# called from the Tkinter Toplevel constructor.
+
+def _addToplevelBusyInfo(window):
+ if window._w == '.':
+ busyWindow = '._Busy'
+ else:
+ busyWindow = window._w + '._Busy'
+
+ _toplevelBusyInfo[window] = {
+ 'isBusy' : 0,
+ 'windowFocus' : None,
+ 'busyWindow' : busyWindow,
+ 'excludeFromBusy' : 0,
+ 'busyCursorName' : None,
+ }
+
+def __TkinterToplevelTitle(self, *args):
+ # If this is being called from the constructor, include this
+ # Toplevel in the list of toplevels and set the initial
+ # WM_DELETE_WINDOW protocol to destroy() so that we get to know
+ # about it.
+ if self not in _toplevelBusyInfo:
+ _addToplevelBusyInfo(self)
+ self._Pmw_WM_DELETE_name = self.register(self.destroy, None, 0)
+ self.protocol('WM_DELETE_WINDOW', self._Pmw_WM_DELETE_name)
+
+ return tkinter.Wm.title(*(self,) + args)
+
+_haveBltBusy = None
+def _havebltbusy(window):
+ global _busy_hold, _busy_release, _haveBltBusy
+ if _haveBltBusy is None:
+ from . import PmwBlt
+ _haveBltBusy = PmwBlt.havebltbusy(window)
+ _busy_hold = PmwBlt.busy_hold
+ if os.name == 'nt':
+ # There is a bug in Blt 2.4i on NT where the busy window
+ # does not follow changes in the children of a window.
+ # Using forget works around the problem.
+ _busy_release = PmwBlt.busy_forget
+ else:
+ _busy_release = PmwBlt.busy_release
+ return _haveBltBusy
+
+class _BusyWrapper:
+ def __init__(self, command, updateFunction):
+ self._command = command
+ self._updateFunction = updateFunction
+
+ def callback(self, *args):
+ showbusycursor()
+ rtn = self._command(*args)
+
+ # Call update before hiding the busy windows to clear any
+ # events that may have occurred over the busy windows.
+ if isinstance(self._updateFunction, collections.Callable):
+ self._updateFunction()
+
+ hidebusycursor()
+ return rtn
+
+#=============================================================================
+
+def drawarrow(canvas, color, direction, tag, baseOffset = 0.25, edgeOffset = 0.15):
+ canvas.delete(tag)
+
+ #Python 3 conversion
+ #bw = (str.atoi(canvas['borderwidth']) +
+ # str.atoi(canvas['highlightthickness']))
+ #width = str.atoi(canvas['width'])
+ #height = str.atoi(canvas['height'])
+ bw = (int(canvas['borderwidth']) +
+ int(canvas['highlightthickness']))
+ width = int(canvas['width'])
+ height = int(canvas['height'])
+
+ if direction in ('up', 'down'):
+ majorDimension = height
+ minorDimension = width
+ else:
+ majorDimension = width
+ minorDimension = height
+
+ offset = round(baseOffset * majorDimension)
+ if direction in ('down', 'right'):
+ base = bw + offset
+ apex = bw + majorDimension - offset
+ else:
+ base = bw + majorDimension - offset
+ apex = bw + offset
+
+ if minorDimension > 3 and minorDimension % 2 == 0:
+ minorDimension = minorDimension - 1
+ half = int(minorDimension * (1 - 2 * edgeOffset)) / 2
+ low = round(bw + edgeOffset * minorDimension)
+ middle = low + half
+ high = low + 2 * half
+
+ if direction in ('up', 'down'):
+ coords = (low, base, high, base, middle, apex)
+ else:
+ coords = (base, low, base, high, apex, middle)
+ kw = {'fill' : color, 'outline' : color, 'tag' : tag}
+ canvas.create_polygon(*coords, **kw)
+
+#=============================================================================
+
+# Modify the Tkinter destroy methods so that it notifies us when a Tk
+# toplevel or frame is destroyed.
+
+# A map from the 'hull' component of a megawidget to the megawidget.
+# This is used to clean up a megawidget when its hull is destroyed.
+_hullToMegaWidget = {}
+
+def __TkinterToplevelDestroy(tkWidget):
+ if tkWidget in _hullToMegaWidget:
+ mega = _hullToMegaWidget[tkWidget]
+ try:
+ mega.destroy()
+ except:
+ _reporterror(mega.destroy, ())
+ else:
+ # Delete the busy info structure for this toplevel (if the
+ # window was created before Pmw.initialise() was called, it
+ # will not have any.
+ if tkWidget in _toplevelBusyInfo:
+ del _toplevelBusyInfo[tkWidget]
+ if hasattr(tkWidget, '_Pmw_WM_DELETE_name'):
+ tkWidget.tk.deletecommand(tkWidget._Pmw_WM_DELETE_name)
+ del tkWidget._Pmw_WM_DELETE_name
+ tkinter.BaseWidget.destroy(tkWidget)
+
+def __TkinterWidgetDestroy(tkWidget):
+ if tkWidget in _hullToMegaWidget:
+ mega = _hullToMegaWidget[tkWidget]
+ try:
+ mega.destroy()
+ except:
+ _reporterror(mega.destroy, ())
+ else:
+ tkinter.BaseWidget.destroy(tkWidget)
+
+#=============================================================================
+
+# Add code to Tkinter to improve the display of errors which occur in
+# callbacks.
+
+class __TkinterCallWrapper:
+ def __init__(self, func, subst, widget):
+ self.func = func
+ self.subst = subst
+ self.widget = widget
+
+ # Calling back from Tk into python.
+ def __call__(self, *args):
+ try:
+ if self.subst:
+ args = self.subst(*args)
+ if _traceTk:
+ if not _callToTkReturned:
+ _traceTkFile.write('\n')
+ if hasattr(self.func, 'im_class'):
+ name = self.func.__self__.__class__.__name__ + '.' + \
+ self.func.__name__
+ else:
+ name = self.func.__name__
+ if len(args) == 1 and hasattr(args[0], 'type'):
+ # The argument to the callback is an event.
+ eventName = _eventTypeToName[int(args[0].type)]
+ if eventName in ('KeyPress', 'KeyRelease',):
+ argStr = '(%s %s Event: %s)' % \
+ (eventName, args[0].keysym, args[0].widget)
+ else:
+ argStr = '(%s Event, %s)' % (eventName, args[0].widget)
+ else:
+ argStr = str(args)
+ _traceTkFile.write('CALLBACK> %d:%s%s%s\n' %
+ (_recursionCounter, ' ' * _recursionCounter, name, argStr))
+ _traceTkFile.flush()
+ return self.func(*args)
+ except SystemExit as msg:
+ raise SystemExit(msg)
+ except:
+ _reporterror(self.func, args)
+
+_eventTypeToName = {
+ 2 : 'KeyPress', 15 : 'VisibilityNotify', 28 : 'PropertyNotify',
+ 3 : 'KeyRelease', 16 : 'CreateNotify', 29 : 'SelectionClear',
+ 4 : 'ButtonPress', 17 : 'DestroyNotify', 30 : 'SelectionRequest',
+ 5 : 'ButtonRelease', 18 : 'UnmapNotify', 31 : 'SelectionNotify',
+ 6 : 'MotionNotify', 19 : 'MapNotify', 32 : 'ColormapNotify',
+ 7 : 'EnterNotify', 20 : 'MapRequest', 33 : 'ClientMessage',
+ 8 : 'LeaveNotify', 21 : 'ReparentNotify', 34 : 'MappingNotify',
+ 9 : 'FocusIn', 22 : 'ConfigureNotify', 35 : 'VirtualEvents',
+ 10 : 'FocusOut', 23 : 'ConfigureRequest', 36 : 'ActivateNotify',
+ 11 : 'KeymapNotify', 24 : 'GravityNotify', 37 : 'DeactivateNotify',
+ 12 : 'Expose', 25 : 'ResizeRequest', 38 : 'MouseWheelEvent',
+ 13 : 'GraphicsExpose', 26 : 'CirculateNotify',
+ 14 : 'NoExpose', 27 : 'CirculateRequest',
+}
+
+def _reporterror(func, args):
+ # Fetch current exception values.
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+
+ # Give basic information about the callback exception.
+ if type(exc_type) == type:
+ # Handle python 1.5 class exceptions.
+ exc_type = exc_type.__name__
+ msg = str(exc_type) + ' Exception in Tk callback\n'
+ msg = msg + ' Function: %s (type: %s)\n' % (repr(func), type(func))
+ msg = msg + ' Args: %s\n' % str(args)
+
+ if type(args) == tuple and len(args) > 0 and \
+ hasattr(args[0], 'type'):
+ eventArg = 1
+ else:
+ eventArg = 0
+
+ # If the argument to the callback is an event, add the event type.
+ if eventArg:
+ #Python 3 conversion
+ #eventNum = str.atoi(args[0].type)
+ eventNum = int(args[0].type)
+ if eventNum in list(_eventTypeToName.keys()):
+ msg = msg + ' Event type: %s (type num: %d)\n' % \
+ (_eventTypeToName[eventNum], eventNum)
+ else:
+ msg = msg + ' Unknown event type (type num: %d)\n' % eventNum
+
+ # Add the traceback.
+ msg = msg + 'Traceback (innermost last):\n'
+ for tr in traceback.extract_tb(exc_traceback):
+ msg = msg + ' File "%s", line %s, in %s\n' % (tr[0], tr[1], tr[2])
+ msg = msg + ' %s\n' % tr[3]
+ msg = msg + '%s: %s\n' % (exc_type, exc_value)
+
+ # If the argument to the callback is an event, add the event contents.
+ if eventArg:
+ msg = msg + '\n================================================\n'
+ msg = msg + ' Event contents:\n'
+ keys = list(args[0].__dict__.keys())
+ keys.sort()
+ for key in keys:
+ msg = msg + ' %s: %s\n' % (key, args[0].__dict__[key])
+
+ clearbusycursor()
+ try:
+ displayerror(msg)
+ except:
+ pass
+
+class _ErrorWindow:
+ def __init__(self):
+
+ self._errorQueue = []
+ self._errorCount = 0
+ self._open = 0
+ self._firstShowing = 1
+
+ # Create the toplevel window
+ self._top = tkinter.Toplevel()
+ self._top.protocol('WM_DELETE_WINDOW', self._hide)
+ self._top.title('Error in background function')
+ self._top.iconname('Background error')
+
+ # Create the text widget and scrollbar in a frame
+ upperframe = tkinter.Frame(self._top)
+
+ scrollbar = tkinter.Scrollbar(upperframe, orient='vertical')
+ scrollbar.pack(side = 'right', fill = 'y')
+
+ self._text = tkinter.Text(upperframe, yscrollcommand=scrollbar.set)
+ self._text.pack(fill = 'both', expand = 1)
+ scrollbar.configure(command=self._text.yview)
+
+ # Create the buttons and label in a frame
+ lowerframe = tkinter.Frame(self._top)
+
+ ignore = tkinter.Button(lowerframe,
+ text = 'Ignore remaining errors', command = self._hide)
+ ignore.pack(side='left')
+
+ self._nextError = tkinter.Button(lowerframe,
+ text = 'Show next error', command = self._next)
+ self._nextError.pack(side='left')
+
+ self._label = tkinter.Label(lowerframe, relief='ridge')
+ self._label.pack(side='left', fill='x', expand=1)
+
+ # Pack the lower frame first so that it does not disappear
+ # when the window is resized.
+ lowerframe.pack(side = 'bottom', fill = 'x')
+ upperframe.pack(side = 'bottom', fill = 'both', expand = 1)
+
+ def showerror(self, text):
+ if self._open:
+ self._errorQueue.append(text)
+ else:
+ self._display(text)
+ self._open = 1
+
+ # Display the error window in the same place it was before.
+ if self._top.state() == 'normal':
+ # If update_idletasks is not called here, the window may
+ # be placed partially off the screen. Also, if it is not
+ # called and many errors are generated quickly in
+ # succession, the error window may not display errors
+ # until the last one is generated and the interpreter
+ # becomes idle.
+ # XXX: remove this, since it causes omppython to go into an
+ # infinite loop if an error occurs in an omp callback.
+ # self._top.update_idletasks()
+
+ pass
+ else:
+ if self._firstShowing:
+ geom = None
+ else:
+ geometry = self._top.geometry()
+ index = str.find(geometry, '+')
+ if index >= 0:
+ geom = geometry[index:]
+ else:
+ geom = None
+ setgeometryanddeiconify(self._top, geom)
+
+ if self._firstShowing:
+ self._firstShowing = 0
+ else:
+ self._top.tkraise()
+
+ self._top.focus()
+
+ self._updateButtons()
+
+ # Release any grab, so that buttons in the error window work.
+ releasegrabs()
+
+ def _hide(self):
+ self._errorCount = self._errorCount + len(self._errorQueue)
+ self._errorQueue = []
+ self._top.withdraw()
+ self._open = 0
+
+ def _next(self):
+ # Display the next error in the queue.
+
+ text = self._errorQueue[0]
+ del self._errorQueue[0]
+
+ self._display(text)
+ self._updateButtons()
+
+ def _display(self, text):
+ self._errorCount = self._errorCount + 1
+ text = 'Error: %d\n%s' % (self._errorCount, text)
+ self._text.delete('1.0', 'end')
+ self._text.insert('end', text)
+
+ def _updateButtons(self):
+ numQueued = len(self._errorQueue)
+ if numQueued > 0:
+ self._label.configure(text='%d more errors' % numQueued)
+ self._nextError.configure(state='normal')
+ else:
+ self._label.configure(text='No more errors')
+ self._nextError.configure(state='disabled')
+
+######################################################################
+### File: PmwDialog.py
+# Based on iwidgets2.2.0/dialog.itk and iwidgets2.2.0/dialogshell.itk code.
+
+# Convention:
+# Each dialog window should have one of these as the rightmost button:
+# Close Close a window which only displays information.
+# Cancel Close a window which may be used to change the state of
+# the application.
+
+import sys
+import types
+import tkinter
+import Pmw
+import collections
+
+# A Toplevel with a ButtonBox and child site.
+
+class Dialog(Pmw.MegaToplevel):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('buttonbox_hull_borderwidth', 1, None),
+ ('buttonbox_hull_relief', 'raised', None),
+ ('buttonboxpos', 's', INITOPT),
+ ('buttons', ('OK',), self._buttons),
+ ('command', None, None),
+ ('dialogchildsite_borderwidth', 1, None),
+ ('dialogchildsite_relief', 'raised', None),
+ ('defaultbutton', None, self._defaultButton),
+ ('master', 'parent', None),
+ ('separatorwidth', 0, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaToplevel.__init__(self, parent)
+
+ # Create the components.
+
+ oldInterior = Pmw.MegaToplevel.interior(self)
+
+ # Set up pack options according to the position of the button box.
+ pos = self['buttonboxpos']
+ if pos not in 'nsew':
+ raise ValueError('bad buttonboxpos option "%s": should be n, s, e, or w' \
+ % pos)
+
+ if pos in 'ns':
+ orient = 'horizontal'
+ fill = 'x'
+ if pos == 'n':
+ side = 'top'
+ else:
+ side = 'bottom'
+ else:
+ orient = 'vertical'
+ fill = 'y'
+ if pos == 'w':
+ side = 'left'
+ else:
+ side = 'right'
+
+ # Create the button box.
+ self._buttonBox = self.createcomponent('buttonbox',
+ (), None,
+ Pmw.ButtonBox, (oldInterior,), orient = orient)
+ self._buttonBox.pack(side = side, fill = fill)
+
+ # Create the separating line.
+ width = self['separatorwidth']
+ if width > 0:
+ self._separator = self.createcomponent('separator',
+ (), None,
+ tkinter.Frame, (oldInterior,), relief = 'sunken',
+ height = width, width = width, borderwidth = width / 2)
+ self._separator.pack(side = side, fill = fill)
+
+ # Create the child site.
+ self.__dialogChildSite = self.createcomponent('dialogchildsite',
+ (), None,
+ tkinter.Frame, (oldInterior,))
+ self.__dialogChildSite.pack(side=side, fill='both', expand=1)
+
+ self.oldButtons = ()
+ self.oldDefault = None
+
+ self.bind('<Return>', self._invokeDefault)
+ self.userdeletefunc(self._doCommand)
+ self.usermodaldeletefunc(self._doCommand)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def interior(self):
+ return self.__dialogChildSite
+
+ def invoke(self, index = Pmw.DEFAULT):
+ return self._buttonBox.invoke(index)
+
+ def _invokeDefault(self, event):
+ try:
+ self._buttonBox.index(Pmw.DEFAULT)
+ except ValueError:
+ return
+ self._buttonBox.invoke()
+
+ def _doCommand(self, name = None):
+ if name is not None and self.active() and \
+ Pmw.grabstacktopwindow() != self.component('hull'):
+ # This is a modal dialog but is not on the top of the grab
+ # stack (ie: should not have the grab), so ignore this
+ # event. This seems to be a bug in Tk and may occur in
+ # nested modal dialogs.
+ #
+ # An example is the PromptDialog demonstration. To
+ # trigger the problem, start the demo, then move the mouse
+ # to the main window, hit <TAB> and then <TAB> again. The
+ # highlight border of the "Show prompt dialog" button
+ # should now be displayed. Now hit <SPACE>, <RETURN>,
+ # <RETURN> rapidly several times. Eventually, hitting the
+ # return key invokes the password dialog "OK" button even
+ # though the confirm dialog is active (and therefore
+ # should have the keyboard focus). Observed under Solaris
+ # 2.5.1, python 1.5.2 and Tk8.0.
+
+ # TODO: Give focus to the window on top of the grabstack.
+ return
+
+ command = self['command']
+ if isinstance(command, collections.Callable):
+ return command(name)
+ else:
+ if self.active():
+ self.deactivate(name)
+ else:
+ self.withdraw()
+
+ def _buttons(self):
+ buttons = self['buttons']
+ if type(buttons) != tuple and type(buttons) != list:
+ raise ValueError('bad buttons option "%s": should be a tuple' % str(buttons))
+ if self.oldButtons == buttons:
+ return
+
+ self.oldButtons = buttons
+
+ for index in range(self._buttonBox.numbuttons()):
+ self._buttonBox.delete(0)
+ for name in buttons:
+ self._buttonBox.add(name,
+ command=lambda self=self, name=name: self._doCommand(name))
+
+ if len(buttons) > 0:
+ defaultbutton = self['defaultbutton']
+ if defaultbutton is None:
+ self._buttonBox.setdefault(None)
+ else:
+ try:
+ self._buttonBox.index(defaultbutton)
+ except ValueError:
+ pass
+ else:
+ self._buttonBox.setdefault(defaultbutton)
+ self._buttonBox.alignbuttons()
+
+ def _defaultButton(self):
+ defaultbutton = self['defaultbutton']
+ if self.oldDefault == defaultbutton:
+ return
+
+ self.oldDefault = defaultbutton
+
+ if len(self['buttons']) > 0:
+ if defaultbutton is None:
+ self._buttonBox.setdefault(None)
+ else:
+ try:
+ self._buttonBox.index(defaultbutton)
+ except ValueError:
+ pass
+ else:
+ self._buttonBox.setdefault(defaultbutton)
+
+######################################################################
+### File: PmwTimeFuncs.py
+# Functions for dealing with dates and times.
+
+import re
+import string
+
+def timestringtoseconds(text, separator = ':'):
+ # to Py3
+ #inputList = string.split(string.strip(text), separator)
+ inputList = text.strip().split(separator)
+
+ if len(inputList) != 3:
+ raise ValueError('invalid value: ' + text)
+
+ sign = 1
+ if len(inputList[0]) > 0 and inputList[0][0] in ('+', '-'):
+ if inputList[0][0] == '-':
+ sign = -1
+ inputList[0] = inputList[0][1:]
+
+ #Py3 if re.search('[^0-9]', string.join(inputList, '')) is not None:
+ if re.search('[^0-9]', ''.join(inputList)) is not None:
+ raise ValueError('invalid value: ' + text)
+
+ hour = int(inputList[0])
+ minute = int(inputList[1])
+ second = int(inputList[2])
+
+ if minute >= 60 or second >= 60:
+ raise ValueError('invalid value: ' + text)
+ return sign * (hour * 60 * 60 + minute * 60 + second)
+
+_year_pivot = 50
+_century = 2000
+
+def setyearpivot(pivot, century = None):
+ global _year_pivot
+ global _century
+ oldvalues = (_year_pivot, _century)
+ _year_pivot = pivot
+ if century is not None:
+ _century = century
+ return oldvalues
+
+def datestringtojdn(text, fmt = 'ymd', separator = '/'):
+ #Py3 inputList = string.split(string.strip(text), separator)
+ inputList = text.strip().split(separator)
+ if len(inputList) != 3:
+ raise ValueError('invalid value: ' + text)
+
+ #Py3 if re.search('[^0-9]', string.join(inputList, '')) is not None:
+ if re.search('[^0-9]', ''.join(inputList)) is not None:
+ raise ValueError('invalid value: ' + text)
+ formatList = list(fmt)
+ day = int(inputList[formatList.index('d')])
+ month = int(inputList[formatList.index('m')])
+ year = int(inputList[formatList.index('y')])
+
+ if _year_pivot is not None:
+ if year >= 0 and year < 100:
+ if year <= _year_pivot:
+ year = year + _century
+ else:
+ year = year + _century - 100
+
+ jdn = ymdtojdn(year, month, day)
+
+ if jdntoymd(jdn) != (year, month, day):
+ raise ValueError('invalid value: ' + text)
+ return jdn
+
+def _cdiv(a, b):
+ # Return a / b as calculated by most C language implementations,
+ # assuming both a and b are integers.
+
+ if a * b > 0:
+ return int(a / b)
+ else:
+ return -int(abs(a) / abs(b))
+
+def ymdtojdn(year, month, day, julian = -1, papal = 1):
+
+ # set Julian flag if auto set
+ if julian < 0:
+ if papal: # Pope Gregory XIII's decree
+ lastJulianDate = 15821004 # last day to use Julian calendar
+ else: # British-American usage
+ lastJulianDate = 17520902 # last day to use Julian calendar
+
+ julian = ((year * 100) + month) * 100 + day <= lastJulianDate
+
+ if year < 0:
+ # Adjust BC year
+ year = year + 1
+
+ if julian:
+ return 367 * year - _cdiv(7 * (year + 5001 + _cdiv((month - 9), 7)), 4) + \
+ _cdiv(275 * month, 9) + day + 1729777
+ else:
+ return (day - 32076) + \
+ _cdiv(1461 * (year + 4800 + _cdiv((month - 14), 12)), 4) + \
+ _cdiv(367 * (month - 2 - _cdiv((month - 14), 12) * 12), 12) - \
+ _cdiv((3 * _cdiv((year + 4900 + _cdiv((month - 14), 12)), 100)), 4) + \
+ 1 # correction by rdg
+
+def jdntoymd(jdn, julian = -1, papal = 1):
+
+ # set Julian flag if auto set
+ if julian < 0:
+ if papal: # Pope Gregory XIII's decree
+ lastJulianJdn = 2299160 # last jdn to use Julian calendar
+ else: # British-American usage
+ lastJulianJdn = 2361221 # last jdn to use Julian calendar
+
+ julian = (jdn <= lastJulianJdn);
+
+ x = jdn + 68569
+ if julian:
+ x = x + 38
+ daysPer400Years = 146100
+ fudgedDaysPer4000Years = 1461000 + 1
+ else:
+ daysPer400Years = 146097
+ fudgedDaysPer4000Years = 1460970 + 31
+
+ z = _cdiv(4 * x, daysPer400Years)
+ x = x - _cdiv((daysPer400Years * z + 3), 4)
+ y = _cdiv(4000 * (x + 1), fudgedDaysPer4000Years)
+ x = x - _cdiv(1461 * y, 4) + 31
+ m = _cdiv(80 * x, 2447)
+ d = x - _cdiv(2447 * m, 80)
+ x = _cdiv(m, 11)
+ m = m + 2 - 12 * x
+ y = 100 * (z - 49) + y + x
+
+ # Convert from longs to integers.
+ yy = int(y)
+ mm = int(m)
+ dd = int(d)
+
+ if yy <= 0:
+ # Adjust BC years.
+ yy = yy - 1
+
+ return (yy, mm, dd)
+
+def stringtoreal(text, separator = '.'):
+ if separator != '.':
+ #Py3 if string.find(text, '.') >= 0:
+ if text.find('.') >= 0:
+ raise ValueError('invalid value: ' + text)
+ #Py3 index = string.find(text, separator)
+ index = text.find(separator)
+ if index >= 0:
+ text = text[:index] + '.' + text[index + 1:]
+ return float(text)
+
+######################################################################
+### File: PmwBalloon.py
+import os
+import string
+import tkinter
+import Pmw
+import collections
+
+class Balloon(Pmw.MegaToplevel):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+ optiondefs = (
+ ('initwait', 500, None), # milliseconds
+ ('label_background', 'lightyellow', None),
+ ('label_foreground', 'black', None),
+ ('label_justify', 'left', None),
+ ('master', 'parent', None),
+ ('relmouse', 'none', self._relmouse),
+ ('state', 'both', self._state),
+ ('statuscommand', None, None),
+ ('xoffset', 20, None), # pixels
+ ('yoffset', 1, None), # pixels
+ ('hull_highlightthickness', 1, None),
+ ('hull_highlightbackground', 'black', None),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaToplevel.__init__(self, parent)
+
+ self.withdraw()
+ self.overrideredirect(1)
+
+ # Create the components.
+ interior = self.interior()
+ self._label = self.createcomponent('label',
+ (), None,
+ tkinter.Label, (interior,))
+ self._label.pack()
+
+ # The default hull configuration options give a black border
+ # around the balloon, but avoids a black 'flash' when the
+ # balloon is deiconified, before the text appears.
+ if 'hull_background' not in kw:
+ self.configure(hull_background = \
+ str(self._label.cget('background')))
+
+ # Initialise instance variables.
+ self._timer = None
+
+ # The widget or item that is currently triggering the balloon.
+ # It is None if the balloon is not being displayed. It is a
+ # one-tuple if the balloon is being displayed in response to a
+ # widget binding (value is the widget). It is a two-tuple if
+ # the balloon is being displayed in response to a canvas or
+ # text item binding (value is the widget and the item).
+ self._currentTrigger = None
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def destroy(self):
+ if self._timer is not None:
+ self.after_cancel(self._timer)
+ self._timer = None
+ Pmw.MegaToplevel.destroy(self)
+
+ def bind(self, widget, balloonHelp, statusHelp = None):
+
+ # If a previous bind for this widget exists, remove it.
+ self.unbind(widget)
+
+ if balloonHelp is None and statusHelp is None:
+ return
+
+ if statusHelp is None:
+ statusHelp = balloonHelp
+ enterId = widget.bind('<Enter>',
+ lambda event, self = self, w = widget,
+ sHelp = statusHelp, bHelp = balloonHelp:
+ self._enter(event, w, sHelp, bHelp, 0))
+
+ # Set Motion binding so that if the pointer remains at rest
+ # within the widget until the status line removes the help and
+ # then the pointer moves again, then redisplay the help in the
+ # status line.
+ # Note: The Motion binding only works for basic widgets, and
+ # the hull of megawidgets but not for other megawidget components.
+ motionId = widget.bind('<Motion>',
+ lambda event = None, self = self, statusHelp = statusHelp:
+ self.showstatus(statusHelp))
+
+ leaveId = widget.bind('<Leave>', self._leave)
+ buttonId = widget.bind('<ButtonPress>', self._buttonpress)
+
+ # Set Destroy binding so that the balloon can be withdrawn and
+ # the timer can be cancelled if the widget is destroyed.
+ destroyId = widget.bind('<Destroy>', self._destroy)
+
+ # Use the None item in the widget's private Pmw dictionary to
+ # store the widget's bind callbacks, for later clean up.
+ if not hasattr(widget, '_Pmw_BalloonBindIds'):
+ widget._Pmw_BalloonBindIds = {}
+ widget._Pmw_BalloonBindIds[None] = \
+ (enterId, motionId, leaveId, buttonId, destroyId)
+
+ def unbind(self, widget):
+ if hasattr(widget, '_Pmw_BalloonBindIds'):
+ if None in widget._Pmw_BalloonBindIds:
+ (enterId, motionId, leaveId, buttonId, destroyId) = \
+ widget._Pmw_BalloonBindIds[None]
+ # Need to pass in old bindings, so that Tkinter can
+ # delete the commands. Otherwise, memory is leaked.
+ widget.unbind('<Enter>', enterId)
+ widget.unbind('<Motion>', motionId)
+ widget.unbind('<Leave>', leaveId)
+ widget.unbind('<ButtonPress>', buttonId)
+ widget.unbind('<Destroy>', destroyId)
+ del widget._Pmw_BalloonBindIds[None]
+
+ if self._currentTrigger is not None and len(self._currentTrigger) == 1:
+ # The balloon is currently being displayed and the current
+ # trigger is a widget.
+ triggerWidget = self._currentTrigger[0]
+ if triggerWidget == widget:
+ if self._timer is not None:
+ self.after_cancel(self._timer)
+ self._timer = None
+ self.withdraw()
+ self.clearstatus()
+ self._currentTrigger = None
+
+ def tagbind(self, widget, tagOrItem, balloonHelp, statusHelp = None):
+
+ # If a previous bind for this widget's tagOrItem exists, remove it.
+ self.tagunbind(widget, tagOrItem)
+
+ if balloonHelp is None and statusHelp is None:
+ return
+
+ if statusHelp is None:
+ statusHelp = balloonHelp
+ enterId = widget.tag_bind(tagOrItem, '<Enter>',
+ lambda event, self = self, w = widget,
+ sHelp = statusHelp, bHelp = balloonHelp:
+ self._enter(event, w, sHelp, bHelp, 1))
+ motionId = widget.tag_bind(tagOrItem, '<Motion>',
+ lambda event = None, self = self, statusHelp = statusHelp:
+ self.showstatus(statusHelp))
+ leaveId = widget.tag_bind(tagOrItem, '<Leave>', self._leave)
+ buttonId = widget.tag_bind(tagOrItem, '<ButtonPress>', self._buttonpress)
+
+ # Use the tagOrItem item in the widget's private Pmw dictionary to
+ # store the tagOrItem's bind callbacks, for later clean up.
+ if not hasattr(widget, '_Pmw_BalloonBindIds'):
+ widget._Pmw_BalloonBindIds = {}
+ widget._Pmw_BalloonBindIds[tagOrItem] = \
+ (enterId, motionId, leaveId, buttonId)
+
+ def tagunbind(self, widget, tagOrItem):
+ if hasattr(widget, '_Pmw_BalloonBindIds'):
+ if tagOrItem in widget._Pmw_BalloonBindIds:
+ (enterId, motionId, leaveId, buttonId) = \
+ widget._Pmw_BalloonBindIds[tagOrItem]
+ widget.tag_unbind(tagOrItem, '<Enter>', enterId)
+ widget.tag_unbind(tagOrItem, '<Motion>', motionId)
+ widget.tag_unbind(tagOrItem, '<Leave>', leaveId)
+ widget.tag_unbind(tagOrItem, '<ButtonPress>', buttonId)
+ del widget._Pmw_BalloonBindIds[tagOrItem]
+
+ if self._currentTrigger is None:
+ # The balloon is not currently being displayed.
+ return
+
+ if len(self._currentTrigger) == 1:
+ # The current trigger is a widget.
+ return
+
+ if len(self._currentTrigger) == 2:
+ # The current trigger is a canvas item.
+ (triggerWidget, triggerItem) = self._currentTrigger
+ if triggerWidget == widget and triggerItem == tagOrItem:
+ if self._timer is not None:
+ self.after_cancel(self._timer)
+ self._timer = None
+ self.withdraw()
+ self.clearstatus()
+ self._currentTrigger = None
+ else: # The current trigger is a text item.
+ (triggerWidget, x, y) = self._currentTrigger
+ if triggerWidget == widget:
+ currentPos = widget.index('@%d,%d' % (x, y))
+ currentTags = widget.tag_names(currentPos)
+ if tagOrItem in currentTags:
+ if self._timer is not None:
+ self.after_cancel(self._timer)
+ self._timer = None
+ self.withdraw()
+ self.clearstatus()
+ self._currentTrigger = None
+
+ def showstatus(self, statusHelp):
+ if self['state'] in ('status', 'both'):
+ cmd = self['statuscommand']
+ if isinstance(cmd, collections.Callable):
+ cmd(statusHelp)
+
+ def clearstatus(self):
+ self.showstatus(None)
+
+ def _state(self):
+ if self['state'] not in ('both', 'balloon', 'status', 'none'):
+ raise ValueError('bad state option ' + repr(self['state']) + \
+ ': should be one of \'both\', \'balloon\', ' + \
+ '\'status\' or \'none\'')
+
+ def _relmouse(self):
+ if self['relmouse'] not in ('both', 'x', 'y', 'none'):
+ raise ValueError('bad relmouse option ' + repr(self['relmouse'])+ \
+ ': should be one of \'both\', \'x\', ' + '\'y\' or \'none\'')
+
+ def _enter(self, event, widget, statusHelp, balloonHelp, isItem):
+
+ # Do not display balloon if mouse button is pressed. This
+ # will only occur if the button was pressed inside a widget,
+ # then the mouse moved out of and then back into the widget,
+ # with the button still held down. The number 0x1f00 is the
+ # button mask for the 5 possible buttons in X.
+ buttonPressed = (event.state & 0x1f00) != 0
+
+ if not buttonPressed and balloonHelp is not None and \
+ self['state'] in ('balloon', 'both'):
+ if self._timer is not None:
+ self.after_cancel(self._timer)
+ self._timer = None
+
+ self._timer = self.after(self['initwait'],
+ lambda self = self, widget = widget, help = balloonHelp,
+ isItem = isItem:
+ self._showBalloon(widget, help, isItem))
+
+ if isItem:
+ if hasattr(widget, 'canvasx'):
+ # The widget is a canvas.
+ item = widget.find_withtag('current')
+ if len(item) > 0:
+ item = item[0]
+ else:
+ item = None
+ self._currentTrigger = (widget, item)
+ else:
+ # The widget is a text widget.
+ self._currentTrigger = (widget, event.x, event.y)
+ else:
+ self._currentTrigger = (widget,)
+
+ self.showstatus(statusHelp)
+
+ def _leave(self, event):
+ if self._timer is not None:
+ self.after_cancel(self._timer)
+ self._timer = None
+ self.withdraw()
+ self.clearstatus()
+ self._currentTrigger = None
+
+ def _destroy(self, event):
+
+ # Only withdraw the balloon and cancel the timer if the widget
+ # being destroyed is the widget that triggered the balloon.
+ # Note that in a Tkinter Destroy event, the widget field is a
+ # string and not a widget as usual.
+
+ if self._currentTrigger is None:
+ # The balloon is not currently being displayed
+ return
+
+ if len(self._currentTrigger) == 1:
+ # The current trigger is a widget (not an item)
+ triggerWidget = self._currentTrigger[0]
+ if str(triggerWidget) == event.widget:
+ if self._timer is not None:
+ self.after_cancel(self._timer)
+ self._timer = None
+ self.withdraw()
+ self.clearstatus()
+ self._currentTrigger = None
+
+ def _buttonpress(self, event):
+ if self._timer is not None:
+ self.after_cancel(self._timer)
+ self._timer = None
+ self.withdraw()
+ self._currentTrigger = None
+
+ def _showBalloon(self, widget, balloonHelp, isItem):
+
+ self._label.configure(text = balloonHelp)
+
+ # First, display the balloon offscreen to get dimensions.
+ screenWidth = self.winfo_screenwidth()
+ screenHeight = self.winfo_screenheight()
+ self.geometry('+%d+0' % (screenWidth + 1))
+ self.update_idletasks()
+
+ if isItem:
+ # Get the bounding box of the current item.
+ bbox = widget.bbox('current')
+ if bbox is None:
+ # The item that triggered the balloon has disappeared,
+ # perhaps by a user's timer event that occured between
+ # the <Enter> event and the 'initwait' timer calling
+ # this method.
+ return
+
+ # The widget is either a text or canvas. The meaning of
+ # the values returned by the bbox method is different for
+ # each, so use the existence of the 'canvasx' method to
+ # distinguish between them.
+ if hasattr(widget, 'canvasx'):
+ # The widget is a canvas. Place balloon under canvas
+ # item. The positions returned by bbox are relative
+ # to the entire canvas, not just the visible part, so
+ # need to convert to window coordinates.
+ leftrel = bbox[0] - widget.canvasx(0)
+ toprel = bbox[1] - widget.canvasy(0)
+ bottomrel = bbox[3] - widget.canvasy(0)
+ else:
+ # The widget is a text widget. Place balloon under
+ # the character closest to the mouse. The positions
+ # returned by bbox are relative to the text widget
+ # window (ie the visible part of the text only).
+ leftrel = bbox[0]
+ toprel = bbox[1]
+ bottomrel = bbox[1] + bbox[3]
+ else:
+ leftrel = 0
+ toprel = 0
+ bottomrel = widget.winfo_height()
+
+ xpointer, ypointer = widget.winfo_pointerxy() # -1 if off screen
+
+ if xpointer >= 0 and self['relmouse'] in ('both', 'x'):
+ x = xpointer
+ else:
+ x = leftrel + widget.winfo_rootx()
+ x = x + self['xoffset']
+
+ if ypointer >= 0 and self['relmouse'] in ('both', 'y'):
+ y = ypointer
+ else:
+ y = bottomrel + widget.winfo_rooty()
+ y = y + self['yoffset']
+ #Python 3 conversion
+ #edges = (string.atoi(str(self.cget('hull_highlightthickness'))) +
+ # string.atoi(str(self.cget('hull_borderwidth')))) * 2
+ edges = (int(str(self.cget('hull_highlightthickness'))) +
+ int(str(self.cget('hull_borderwidth')))) * 2
+ if x + self._label.winfo_reqwidth() + edges > screenWidth:
+ x = screenWidth - self._label.winfo_reqwidth() - edges
+
+ if y + self._label.winfo_reqheight() + edges > screenHeight:
+ if ypointer >= 0 and self['relmouse'] in ('both', 'y'):
+ y = ypointer
+ else:
+ y = toprel + widget.winfo_rooty()
+ y = y - self._label.winfo_reqheight() - self['yoffset'] - edges
+
+ Pmw.setgeometryanddeiconify(self, '+%d+%d' % (x, y))
+
+######################################################################
+### File: PmwButtonBox.py
+# Based on iwidgets2.2.0/buttonbox.itk code.
+
+import types
+import tkinter
+import Pmw
+
+class ButtonBox(Pmw.MegaWidget):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('orient', 'horizontal', INITOPT),
+ ('padx', 3, INITOPT),
+ ('pady', 3, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs, dynamicGroups = ('Button',))
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+ if self['labelpos'] is None:
+ self._buttonBoxFrame = self._hull
+ columnOrRow = 0
+ else:
+ self._buttonBoxFrame = self.createcomponent('frame',
+ (), None,
+ tkinter.Frame, (interior,))
+ self._buttonBoxFrame.grid(column=2, row=2, sticky='nsew')
+ columnOrRow = 2
+
+ self.createlabel(interior)
+
+ orient = self['orient']
+ if orient == 'horizontal':
+ interior.grid_columnconfigure(columnOrRow, weight = 1)
+ elif orient == 'vertical':
+ interior.grid_rowconfigure(columnOrRow, weight = 1)
+ else:
+ raise ValueError('bad orient option ' + repr(orient) + \
+ ': must be either \'horizontal\' or \'vertical\'')
+
+ # Initialise instance variables.
+
+ # List of tuples describing the buttons:
+ # - name
+ # - button widget
+ self._buttonList = []
+
+ # The index of the default button.
+ self._defaultButton = None
+
+ self._timerId = None
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def destroy(self):
+ if self._timerId:
+ self.after_cancel(self._timerId)
+ self._timerId = None
+ Pmw.MegaWidget.destroy(self)
+
+ def numbuttons(self):
+ return len(self._buttonList)
+
+ def index(self, index, forInsert = 0):
+ listLength = len(self._buttonList)
+ if type(index) == int:
+ if forInsert and index <= listLength:
+ return index
+ elif not forInsert and index < listLength:
+ return index
+ else:
+ raise ValueError('index "%s" is out of range' % index)
+ elif index is Pmw.END:
+ if forInsert:
+ return listLength
+ elif listLength > 0:
+ return listLength - 1
+ else:
+ raise ValueError('ButtonBox has no buttons')
+ elif index is Pmw.DEFAULT:
+ if self._defaultButton is not None:
+ return self._defaultButton
+ raise ValueError('ButtonBox has no default')
+ else:
+ names = [t[0] for t in self._buttonList]
+ if index in names:
+ return names.index(index)
+ validValues = 'a name, a number, Pmw.END or Pmw.DEFAULT'
+ raise ValueError('bad index "%s": must be %s' % (index, validValues))
+
+ def insert(self, componentName, beforeComponent = 0, **kw):
+ if componentName in self.components():
+ raise ValueError('button "%s" already exists' % componentName)
+ if 'text' not in kw:
+ kw['text'] = componentName
+ kw['default'] = 'normal'
+ button = self.createcomponent(*(componentName,
+ (), 'Button',
+ tkinter.Button, (self._buttonBoxFrame,)), **kw)
+
+ index = self.index(beforeComponent, 1)
+ horizontal = self['orient'] == 'horizontal'
+ numButtons = len(self._buttonList)
+
+ # Shift buttons up one position.
+ for i in range(numButtons - 1, index - 1, -1):
+ widget = self._buttonList[i][1]
+ pos = i * 2 + 3
+ if horizontal:
+ widget.grid(column = pos, row = 0)
+ else:
+ widget.grid(column = 0, row = pos)
+
+ # Display the new button.
+ if horizontal:
+ button.grid(column = index * 2 + 1, row = 0, sticky = 'ew',
+ padx = self['padx'], pady = self['pady'])
+ self._buttonBoxFrame.grid_columnconfigure(
+ numButtons * 2 + 2, weight = 1)
+ else:
+ button.grid(column = 0, row = index * 2 + 1, sticky = 'ew',
+ padx = self['padx'], pady = self['pady'])
+ self._buttonBoxFrame.grid_rowconfigure(
+ numButtons * 2 + 2, weight = 1)
+ self._buttonList.insert(index, (componentName, button))
+
+ return button
+
+ def add(self, componentName, **kw):
+ return self.insert(*(componentName, len(self._buttonList)), **kw)
+
+ def delete(self, index):
+ index = self.index(index)
+ (name, widget) = self._buttonList[index]
+ widget.grid_forget()
+ self.destroycomponent(name)
+
+ numButtons = len(self._buttonList)
+
+ # Shift buttons down one position.
+ horizontal = self['orient'] == 'horizontal'
+ for i in range(index + 1, numButtons):
+ widget = self._buttonList[i][1]
+ pos = i * 2 - 1
+ if horizontal:
+ widget.grid(column = pos, row = 0)
+ else:
+ widget.grid(column = 0, row = pos)
+
+ if horizontal:
+ self._buttonBoxFrame.grid_columnconfigure(numButtons * 2 - 1,
+ minsize = 0)
+ self._buttonBoxFrame.grid_columnconfigure(numButtons * 2, weight = 0)
+ else:
+ self._buttonBoxFrame.grid_rowconfigure(numButtons * 2, weight = 0)
+ del self._buttonList[index]
+
+ def setdefault(self, index):
+ # Turn off the default ring around the current default button.
+ if self._defaultButton is not None:
+ button = self._buttonList[self._defaultButton][1]
+ button.configure(default = 'normal')
+ self._defaultButton = None
+
+ # Turn on the default ring around the new default button.
+ if index is not None:
+ index = self.index(index)
+ self._defaultButton = index
+ button = self._buttonList[index][1]
+ button.configure(default = 'active')
+
+ def invoke(self, index = Pmw.DEFAULT, noFlash = 0):
+ # Invoke the callback associated with the *index* button. If
+ # *noFlash* is not set, flash the button to indicate to the
+ # user that something happened.
+
+ button = self._buttonList[self.index(index)][1]
+ if not noFlash:
+ state = button.cget('state')
+ relief = button.cget('relief')
+ button.configure(state = 'active', relief = 'sunken')
+ self.update_idletasks()
+ self.after(100)
+ button.configure(state = state, relief = relief)
+ return button.invoke()
+
+ def button(self, buttonIndex):
+ return self._buttonList[self.index(buttonIndex)][1]
+
+ def alignbuttons(self, when = 'later'):
+ if when == 'later':
+ if not self._timerId:
+ self._timerId = self.after_idle(self.alignbuttons, 'now')
+ return
+ self.update_idletasks()
+ self._timerId = None
+
+ # Determine the width of the maximum length button.
+ max = 0
+ horizontal = (self['orient'] == 'horizontal')
+ for index in range(len(self._buttonList)):
+ gridIndex = index * 2 + 1
+ if horizontal:
+ width = self._buttonBoxFrame.grid_bbox(gridIndex, 0)[2]
+ else:
+ width = self._buttonBoxFrame.grid_bbox(0, gridIndex)[2]
+ if width > max:
+ max = width
+
+ # Set the width of all the buttons to be the same.
+ if horizontal:
+ for index in range(len(self._buttonList)):
+ self._buttonBoxFrame.grid_columnconfigure(index * 2 + 1,
+ minsize = max)
+ else:
+ self._buttonBoxFrame.grid_columnconfigure(0, minsize = max)
+
+######################################################################
+### File: PmwEntryField.py
+# Based on iwidgets2.2.0/entryfield.itk code.
+
+import re
+import string
+import types
+import tkinter
+import Pmw
+import collections
+
+# Possible return values of validation functions.
+OK = 1
+ERROR = 0
+PARTIAL = -1
+
+class EntryField(Pmw.MegaWidget):
+ _classBindingsDefinedFor = 0
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('command', None, None),
+ ('errorbackground', 'pink', None),
+ ('invalidcommand', self.bell, None),
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('modifiedcommand', None, None),
+ ('sticky', 'ew', INITOPT),
+ ('validate', None, self._validate),
+ ('extravalidators', {}, None),
+ ('value', '', INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+ self._entryFieldEntry = self.createcomponent('entry',
+ (), None,
+ tkinter.Entry, (interior,))
+ self._entryFieldEntry.grid(column=2, row=2, sticky=self['sticky'])
+ if self['value'] != '':
+ self.__setEntry(self['value'])
+ interior.grid_columnconfigure(2, weight=1)
+ interior.grid_rowconfigure(2, weight=1)
+
+ self.createlabel(interior)
+
+ # Initialise instance variables.
+
+ self.normalBackground = None
+ self._previousText = None
+
+ # Initialise instance.
+
+ _registerEntryField(self._entryFieldEntry, self)
+
+ # Establish the special class bindings if not already done.
+ # Also create bindings if the Tkinter default interpreter has
+ # changed. Use Tkinter._default_root to create class
+ # bindings, so that a reference to root is created by
+ # bind_class rather than a reference to self, which would
+ # prevent object cleanup.
+ if EntryField._classBindingsDefinedFor != tkinter._default_root:
+ tagList = self._entryFieldEntry.bindtags()
+ root = tkinter._default_root
+
+ allSequences = {}
+ for tag in tagList:
+
+ sequences = root.bind_class(tag)
+ if type(sequences) is str:
+ # In old versions of Tkinter, bind_class returns a string
+ sequences = root.tk.splitlist(sequences)
+
+ for sequence in sequences:
+ allSequences[sequence] = None
+ for sequence in list(allSequences.keys()):
+ root.bind_class('EntryFieldPre', sequence, _preProcess)
+ root.bind_class('EntryFieldPost', sequence, _postProcess)
+
+ EntryField._classBindingsDefinedFor = root
+
+ self._entryFieldEntry.bindtags(('EntryFieldPre',) +
+ self._entryFieldEntry.bindtags() + ('EntryFieldPost',))
+ self._entryFieldEntry.bind('<Return>', self._executeCommand)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def destroy(self):
+ _deregisterEntryField(self._entryFieldEntry)
+ Pmw.MegaWidget.destroy(self)
+
+ def _getValidatorFunc(self, validator, index):
+ # Search the extra and standard validator lists for the
+ # given 'validator'. If 'validator' is an alias, then
+ # continue the search using the alias. Make sure that
+ # self-referencial aliases do not cause infinite loops.
+
+ extraValidators = self['extravalidators']
+ traversedValidators = []
+
+ while 1:
+ traversedValidators.append(validator)
+ if validator in extraValidators:
+ validator = extraValidators[validator][index]
+ elif validator in _standardValidators:
+ validator = _standardValidators[validator][index]
+ else:
+ return validator
+ if validator in traversedValidators:
+ return validator
+
+ def _validate(self):
+ dictio = {
+ 'validator' : None,
+ 'min' : None,
+ 'max' : None,
+ 'minstrict' : 1,
+ 'maxstrict' : 1,
+ }
+ opt = self['validate']
+ if type(opt) is dict:
+ dictio.update(opt)
+ else:
+ dictio['validator'] = opt
+
+ # Look up validator maps and replace 'validator' field with
+ # the corresponding function.
+ validator = dictio['validator']
+ valFunction = self._getValidatorFunc(validator, 0)
+ self._checkValidateFunction(valFunction, 'validate', validator)
+ dictio['validator'] = valFunction
+
+ # Look up validator maps and replace 'stringtovalue' field
+ # with the corresponding function.
+ if 'stringtovalue' in dictio:
+ stringtovalue = dictio['stringtovalue']
+ strFunction = self._getValidatorFunc(stringtovalue, 1)
+ self._checkValidateFunction(
+ strFunction, 'stringtovalue', stringtovalue)
+ else:
+ strFunction = self._getValidatorFunc(validator, 1)
+ if strFunction == validator:
+ strFunction = len
+ dictio['stringtovalue'] = strFunction
+
+ self._validationInfo = dictio
+ args = dictio.copy()
+ del args['validator']
+ del args['min']
+ del args['max']
+ del args['minstrict']
+ del args['maxstrict']
+ del args['stringtovalue']
+ self._validationArgs = args
+ self._previousText = None
+
+ if type(dictio['min']) is str and strFunction is not None:
+ dictio['min'] = strFunction(*(dictio['min'],), **args)
+ if type(dictio['max']) is str and strFunction is not None:
+ dictio['max'] = strFunction(*(dictio['max'],), **args)
+
+ self._checkValidity()
+
+ def _checkValidateFunction(self, function, option, validator):
+ # Raise an error if 'function' is not a function or None.
+
+ if function is not None and not isinstance(function, collections.Callable):
+ extraValidators = self['extravalidators']
+ extra = list(extraValidators.keys())
+ extra.sort()
+ extra = tuple(extra)
+ standard = list(_standardValidators.keys())
+ standard.sort()
+ standard = tuple(standard)
+ msg = 'bad %s value "%s": must be a function or one of ' \
+ 'the standard validators %s or extra validators %s'
+ raise ValueError(msg % (option, validator, standard, extra))
+
+ def _executeCommand(self, event = None):
+ cmd = self['command']
+ if isinstance(cmd, collections.Callable):
+ if event is None:
+ # Return result of command for invoke() method.
+ return cmd()
+ else:
+ cmd()
+
+ def _preProcess(self):
+
+ self._previousText = self._entryFieldEntry.get()
+ self._previousICursor = self._entryFieldEntry.index('insert')
+ self._previousXview = self._entryFieldEntry.index('@0')
+ if self._entryFieldEntry.selection_present():
+ self._previousSel= (self._entryFieldEntry.index('sel.first'),
+ self._entryFieldEntry.index('sel.last'))
+ else:
+ self._previousSel = None
+
+ def _postProcess(self):
+
+ # No need to check if text has not changed.
+ previousText = self._previousText
+ if previousText == self._entryFieldEntry.get():
+ return self.valid()
+
+ valid = self._checkValidity()
+ if self.hulldestroyed():
+ # The invalidcommand called by _checkValidity() destroyed us.
+ return valid
+
+ cmd = self['modifiedcommand']
+ if isinstance(cmd, collections.Callable) and previousText != self._entryFieldEntry.get():
+ cmd()
+ return valid
+
+ def checkentry(self):
+ # If there is a variable specified by the entry_textvariable
+ # option, checkentry() should be called after the set() method
+ # of the variable is called.
+
+ self._previousText = None
+ return self._postProcess()
+
+ def _getValidity(self):
+ text = self._entryFieldEntry.get()
+ dictio = self._validationInfo
+ args = self._validationArgs
+
+ if dictio['validator'] is not None:
+ status = dictio['validator'](*(text,), **args)
+ if status != OK:
+ return status
+
+ # Check for out of (min, max) range.
+ if dictio['stringtovalue'] is not None:
+ min = dictio['min']
+ max = dictio['max']
+ if min is None and max is None:
+ return OK
+ val = dictio['stringtovalue'](*(text,), **args)
+ if min is not None and val < min:
+ if dictio['minstrict']:
+ return ERROR
+ else:
+ return PARTIAL
+ if max is not None and val > max:
+ if dictio['maxstrict']:
+ return ERROR
+ else:
+ return PARTIAL
+ return OK
+
+ def _checkValidity(self):
+ valid = self._getValidity()
+ oldValidity = valid
+
+ if valid == ERROR:
+ # The entry is invalid.
+ cmd = self['invalidcommand']
+ if isinstance(cmd, collections.Callable):
+ cmd()
+ if self.hulldestroyed():
+ # The invalidcommand destroyed us.
+ return oldValidity
+
+ # Restore the entry to its previous value.
+ if self._previousText is not None:
+ self.__setEntry(self._previousText)
+ self._entryFieldEntry.icursor(self._previousICursor)
+ self._entryFieldEntry.xview(self._previousXview)
+ if self._previousSel is not None:
+ self._entryFieldEntry.selection_range(self._previousSel[0],
+ self._previousSel[1])
+
+ # Check if the saved text is valid as well.
+ valid = self._getValidity()
+
+ self._valid = valid
+
+ if self.hulldestroyed():
+ # The validator or stringtovalue commands called by
+ # _checkValidity() destroyed us.
+ return oldValidity
+
+ if valid == OK:
+ if self.normalBackground is not None:
+ self._entryFieldEntry.configure(
+ background = self.normalBackground)
+ self.normalBackground = None
+ else:
+ if self.normalBackground is None:
+ self.normalBackground = self._entryFieldEntry.cget('background')
+ self._entryFieldEntry.configure(
+ background = self['errorbackground'])
+
+ return oldValidity
+
+ def invoke(self):
+ return self._executeCommand()
+
+ def valid(self):
+ return self._valid == OK
+
+ def clear(self):
+ self.setentry('')
+
+ def __setEntry(self, text):
+ oldState = str(self._entryFieldEntry.cget('state'))
+ if oldState != 'normal':
+ self._entryFieldEntry.configure(state='normal')
+ self._entryFieldEntry.delete(0, 'end')
+ self._entryFieldEntry.insert(0, text)
+ if oldState != 'normal':
+ self._entryFieldEntry.configure(state=oldState)
+
+ def setentry(self, text):
+ self._preProcess()
+ self.__setEntry(text)
+ return self._postProcess()
+
+ def getvalue(self):
+ return self._entryFieldEntry.get()
+
+ def setvalue(self, text):
+ return self.setentry(text)
+
+Pmw.forwardmethods(EntryField, tkinter.Entry, '_entryFieldEntry')
+
+# ======================================================================
+
+
+# Entry field validation functions
+
+_numericregex = re.compile('^[0-9]*$')
+_alphabeticregex = re.compile('^[a-z]*$', re.IGNORECASE)
+_alphanumericregex = re.compile('^[0-9a-z]*$', re.IGNORECASE)
+
+def numericvalidator(text):
+ if text == '':
+ return PARTIAL
+ else:
+ if _numericregex.match(text) is None:
+ return ERROR
+ else:
+ return OK
+
+def integervalidator(text):
+ if text in ('', '-', '+'):
+ return PARTIAL
+ try:
+ int(text)
+ return OK
+ except ValueError:
+ return ERROR
+
+def alphabeticvalidator(text):
+ if _alphabeticregex.match(text) is None:
+ return ERROR
+ else:
+ return OK
+
+def alphanumericvalidator(text):
+ if _alphanumericregex.match(text) is None:
+ return ERROR
+ else:
+ return OK
+
+def hexadecimalvalidator(text):
+ if text in ('', '0x', '0X', '+', '+0x', '+0X', '-', '-0x', '-0X'):
+ return PARTIAL
+ try:
+ int(text, 16)
+ return OK
+ except ValueError:
+ return ERROR
+
+def realvalidator(text, separator = '.'):
+ if separator != '.':
+ #Py3 if string.find(text, '.') >= 0:
+ if text.find('.') >= 0:
+ return ERROR
+ #Py3 index = string.find(text, separator)
+ index = text.find(separator)
+ if index >= 0:
+ text = text[:index] + '.' + text[index + 1:]
+ try:
+ float(text)
+ return OK
+ except ValueError:
+ # Check if the string could be made valid by appending a digit
+ # eg ('-', '+', '.', '-.', '+.', '1.23e', '1E-').
+ if len(text) == 0:
+ return PARTIAL
+ if text[-1] in string.digits:
+ return ERROR
+ try:
+ float(text + '0')
+ return PARTIAL
+ except ValueError:
+ return ERROR
+
+def timevalidator(text, separator = ':'):
+ try:
+ Pmw.timestringtoseconds(text, separator)
+ return OK
+ except ValueError:
+ if len(text) > 0 and text[0] in ('+', '-'):
+ text = text[1:]
+ if re.search('[^0-9' + separator + ']', text) is not None:
+ return ERROR
+ return PARTIAL
+
+def datevalidator(text, format = 'ymd', separator = '/'):
+ try:
+ Pmw.datestringtojdn(text, format, separator)
+ return OK
+ except ValueError:
+ if re.search('[^0-9' + separator + ']', text) is not None:
+ return ERROR
+ return PARTIAL
+
+_standardValidators = {
+ 'numeric' : (numericvalidator, int),
+ 'integer' : (integervalidator, int),
+ 'hexadecimal' : (hexadecimalvalidator, lambda s: int(s, 16)),
+ 'real' : (realvalidator, Pmw.stringtoreal),
+ 'alphabetic' : (alphabeticvalidator, len),
+ 'alphanumeric' : (alphanumericvalidator, len),
+ 'time' : (timevalidator, Pmw.timestringtoseconds),
+ 'date' : (datevalidator, Pmw.datestringtojdn),
+}
+
+_entryCache = {}
+
+def _registerEntryField(entry, entryField):
+ # Register an EntryField widget for an Entry widget
+
+ _entryCache[entry] = entryField
+
+def _deregisterEntryField(entry):
+ # Deregister an Entry widget
+ del _entryCache[entry]
+
+def _preProcess(event):
+ # Forward preprocess events for an Entry to it's EntryField
+
+ _entryCache[event.widget]._preProcess()
+
+def _postProcess(event):
+ # Forward postprocess events for an Entry to it's EntryField
+
+ # The function specified by the 'command' option may have destroyed
+ # the megawidget in a binding earlier in bindtags, so need to check.
+ if event.widget in _entryCache:
+ _entryCache[event.widget]._postProcess()
+
+######################################################################
+### File: PmwGroup.py
+import string
+import tkinter
+import Pmw
+
+def aligngrouptags(groups):
+ # Adjust the y position of the tags in /groups/ so that they all
+ # have the height of the highest tag.
+
+ maxTagHeight = 0
+ for group in groups:
+ if group._tag is None:
+ #Python 3 conversion
+ #height = (string.atoi(str(group._ring.cget('borderwidth'))) +
+ # string.atoi(str(group._ring.cget('highlightthickness'))))
+ height = (int(str(group._ring.cget('borderwidth'))) +
+ int(str(group._ring.cget('highlightthickness'))))
+ else:
+ height = group._tag.winfo_reqheight()
+ if maxTagHeight < height:
+ maxTagHeight = height
+
+ for group in groups:
+ #Python 3 conversion
+ #ringBorder = (string.atoi(str(group._ring.cget('borderwidth'))) +
+ # string.atoi(str(group._ring.cget('highlightthickness'))))
+ ringBorder = (int(str(group._ring.cget('borderwidth'))) +
+ int(str(group._ring.cget('highlightthickness'))))
+ topBorder = maxTagHeight / 2 - ringBorder / 2
+ group._hull.grid_rowconfigure(0, minsize = topBorder)
+ group._ring.grid_rowconfigure(0,
+ minsize = maxTagHeight - topBorder - ringBorder)
+ if group._tag is not None:
+ group._tag.place(y = maxTagHeight / 2)
+
+class Group( Pmw.MegaWidget ):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ #TODO rename collapsedsize to collapsedheight
+ #after adding collapsedwitdh (Pmw 1.3.3)
+ #will both stay in place for compatibility...
+
+ optiondefs = (
+ ('collapsedsize', 6, INITOPT),
+ ('collapsedheight', 6, INITOPT),
+ ('collapsedwidth', 20, INITOPT),
+ ('ring_borderwidth', 2, None),
+ ('ring_relief', 'groove', None),
+ ('tagindent', 10, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ interior = Pmw.MegaWidget.interior(self)
+
+ self._ring = self.createcomponent(
+ 'ring',
+ (), None,
+ tkinter.Frame, (interior,),
+ )
+
+ self._groupChildSite = self.createcomponent(
+ 'groupchildsite',
+ (), None,
+ tkinter.Frame, (self._ring,)
+ )
+
+ self._tag = self.createcomponent(
+ 'tag',
+ (), None,
+ tkinter.Label, (interior,),
+ )
+
+ ringBorder = (int(str(self._ring.cget('borderwidth'))) +
+ int(str(self._ring.cget('highlightthickness'))))
+ if self._tag is None:
+ tagHeight = ringBorder
+ else:
+ tagHeight = self._tag.winfo_reqheight()
+ self._tag.place(
+ x = ringBorder + self['tagindent'],
+ y = tagHeight / 2,
+ anchor = 'w')
+
+ topBorder = tagHeight / 2 - ringBorder / 2
+ self._ring.grid(column = 0, row = 1, sticky = 'nsew')
+ interior.grid_columnconfigure(0, weight = 1)
+ interior.grid_rowconfigure(1, weight = 1)
+ interior.grid_rowconfigure(0, minsize = topBorder)
+
+ self._groupChildSite.grid(column = 0, row = 1, sticky = 'nsew')
+ self._ring.grid_columnconfigure(0, weight = 1)
+ self._ring.grid_rowconfigure(1, weight = 1)
+ self._ring.grid_rowconfigure(0,
+ minsize = tagHeight - topBorder - ringBorder)
+
+ self.showing = 1
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def toggle(self):
+ if self.showing:
+ self.collapse()
+ else:
+ self.expand()
+ self.showing = not self.showing
+
+ def expand(self):
+ self._groupChildSite.grid(column = 0, row = 1, sticky = 'nsew')
+
+ def collapse(self):
+ self._groupChildSite.grid_forget()
+ #Tracker item 1096289
+ if self._tag is None:
+ tagHeight = 0
+ else:
+ tagHeight = self._tag.winfo_reqheight()
+ tagWidth = self._tag.winfo_reqwidth()
+ self._ring.configure(height=(tagHeight / 2) + self['collapsedheight'],
+ width = tagWidth + self['collapsedwidth'])
+
+
+ def interior(self):
+ return self._groupChildSite
+
+######################################################################
+### File: PmwLabeledWidget.py
+import tkinter
+import Pmw
+
+class LabeledWidget(Pmw.MegaWidget):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('sticky', 'nsew', INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ interior = Pmw.MegaWidget.interior(self)
+ self._labelChildSite = self.createcomponent('labelchildsite',
+ (), None,
+ tkinter.Frame, (interior,))
+ self._labelChildSite.grid(column=2, row=2, sticky=self['sticky'])
+ interior.grid_columnconfigure(2, weight=1)
+ interior.grid_rowconfigure(2, weight=1)
+
+ self.createlabel(interior)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def interior(self):
+ return self._labelChildSite
+
+######################################################################
+### File: PmwMainMenuBar.py
+# Main menubar
+
+import string
+import types
+import tkinter
+import Pmw
+
+class MainMenuBar(Pmw.MegaArchetype):
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('balloon', None, None),
+ ('hotkeys', 1, INITOPT),
+ ('hull_tearoff', 0, None),
+ )
+ self.defineoptions(kw, optiondefs, dynamicGroups = ('Menu',))
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaArchetype.__init__(self, parent, tkinter.Menu)
+
+ self._menuInfo = {}
+ self._menuInfo[None] = (None, [])
+ # Map from a menu name to a tuple of information about the menu.
+ # The first item in the tuple is the name of the parent menu (for
+ # toplevel menus this is None). The second item in the tuple is
+ # a list of status help messages for each item in the menu.
+ # The key for the information for the main menubar is None.
+
+ self._menu = self.interior()
+ self._menu.bind('<Leave>', self._resetHelpmessage)
+ self._menu.bind('<Motion>',
+ lambda event=None, self=self: self._menuHelp(event, None))
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def deletemenuitems(self, menuName, start, end = None):
+ self.component(menuName).delete(start, end)
+ if end is None:
+ del self._menuInfo[menuName][1][start]
+ else:
+ self._menuInfo[menuName][1][start:end+1] = []
+
+ def deletemenu(self, menuName):
+ """Delete should be called for cascaded menus before main menus.
+ """
+
+ parentName = self._menuInfo[menuName][0]
+ del self._menuInfo[menuName]
+ if parentName is None:
+ parentMenu = self._menu
+ else:
+ parentMenu = self.component(parentName)
+
+ menu = self.component(menuName)
+ menuId = str(menu)
+ for item in range(parentMenu.index('end') + 1):
+ if parentMenu.type(item) == 'cascade':
+ itemMenu = str(parentMenu.entrycget(item, 'menu'))
+ if itemMenu == menuId:
+ parentMenu.delete(item)
+ del self._menuInfo[parentName][1][item]
+ break
+
+ self.destroycomponent(menuName)
+
+ def disableall(self):
+ for index in range(len(self._menuInfo[None][1])):
+ self.entryconfigure(index, state = 'disabled')
+
+ def enableall(self):
+ for index in range(len(self._menuInfo[None][1])):
+ self.entryconfigure(index, state = 'normal')
+
+ def addmenu(self, menuName, balloonHelp, statusHelp = None,
+ traverseSpec = None, **kw):
+ if statusHelp is None:
+ statusHelp = balloonHelp
+ self._addmenu(None, menuName, balloonHelp, statusHelp,
+ traverseSpec, kw)
+
+ def addcascademenu(self, parentMenuName, menuName, statusHelp='',
+ traverseSpec = None, **kw):
+ self._addmenu(parentMenuName, menuName, None, statusHelp,
+ traverseSpec, kw)
+
+ def _addmenu(self, parentMenuName, menuName, balloonHelp, statusHelp,
+ traverseSpec, kw):
+
+ if (menuName) in self.components():
+ raise ValueError('menu "%s" already exists' % menuName)
+
+ menukw = {}
+ if 'tearoff' in kw:
+ menukw['tearoff'] = kw['tearoff']
+ del kw['tearoff']
+ else:
+ menukw['tearoff'] = 0
+ if 'name' in kw:
+ menukw['name'] = kw['name']
+ del kw['name']
+
+ if 'label' not in kw:
+ kw['label'] = menuName
+
+ self._addHotkeyToOptions(parentMenuName, kw, traverseSpec)
+
+ if parentMenuName is None:
+ parentMenu = self._menu
+ balloon = self['balloon']
+ # Bug in Tk: balloon help not implemented
+ # if balloon is not None:
+ # balloon.mainmenubind(parentMenu, balloonHelp, statusHelp)
+ else:
+ parentMenu = self.component(parentMenuName)
+
+ parentMenu.add_cascade(*(), **kw)
+
+ menu = self.createcomponent(*(menuName,
+ (), 'Menu',
+ tkinter.Menu, (parentMenu,)), **menukw)
+ parentMenu.entryconfigure('end', menu = menu)
+
+ self._menuInfo[parentMenuName][1].append(statusHelp)
+ self._menuInfo[menuName] = (parentMenuName, [])
+
+ menu.bind('<Leave>', self._resetHelpmessage)
+ menu.bind('<Motion>',
+ lambda event=None, self=self, menuName=menuName:
+ self._menuHelp(event, menuName))
+
+ def addmenuitem(self, menuName, itemType, statusHelp = '',
+ traverseSpec = None, **kw):
+
+ menu = self.component(menuName)
+ if itemType != 'separator':
+ self._addHotkeyToOptions(menuName, kw, traverseSpec)
+
+ if itemType == 'command':
+ command = menu.add_command
+ elif itemType == 'separator':
+ command = menu.add_separator
+ elif itemType == 'checkbutton':
+ command = menu.add_checkbutton
+ elif itemType == 'radiobutton':
+ command = menu.add_radiobutton
+ elif itemType == 'cascade':
+ command = menu.add_cascade
+ else:
+ raise ValueError('unknown menuitem type "%s"' % itemType)
+
+ self._menuInfo[menuName][1].append(statusHelp)
+ command(*(), **kw)
+
+ def _addHotkeyToOptions(self, menuName, kw, traverseSpec):
+
+ if (not self['hotkeys'] or 'underline' in kw or
+ 'label' not in kw):
+ return
+
+ if type(traverseSpec) == int:
+ kw['underline'] = traverseSpec
+ return
+
+ if menuName is None:
+ menu = self._menu
+ else:
+ menu = self.component(menuName)
+ hotkeyList = []
+ end = menu.index('end')
+ if end is not None:
+ for item in range(end + 1):
+ if menu.type(item) not in ('separator', 'tearoff'):
+ #Python 3 conversion
+# underline = \
+# string.atoi(str(menu.entrycget(item, 'underline')))
+ underline = \
+ int(str(menu.entrycget(item, 'underline')))
+ if underline != -1:
+ label = str(menu.entrycget(item, 'label'))
+ if underline < len(label):
+ hotkey = label[underline].lower()
+ if hotkey not in hotkeyList:
+ hotkeyList.append(hotkey)
+
+ name = kw['label']
+
+ if type(traverseSpec) is str:
+ lowerLetter = traverseSpec.lower()
+ if traverseSpec in name and lowerLetter not in hotkeyList:
+ kw['underline'] = name.index(traverseSpec)
+ else:
+ targets = string.digits + string.ascii_letters
+ lowerName = name.lower()
+ for letter_index in range(len(name)):
+ letter = lowerName[letter_index]
+ if letter in targets and letter not in hotkeyList:
+ kw['underline'] = letter_index
+ break
+
+ def _menuHelp(self, event, menuName):
+ if menuName is None:
+ menu = self._menu
+ index = menu.index('@%d'% event.x)
+ else:
+ menu = self.component(menuName)
+ index = menu.index('@%d'% event.y)
+
+ balloon = self['balloon']
+ if balloon is not None:
+ if index is None:
+ balloon.showstatus('')
+ else:
+ if str(menu.cget('tearoff')) == '1':
+ index = index - 1
+ if index >= 0:
+ help = self._menuInfo[menuName][1][index]
+ balloon.showstatus(help)
+
+ def _resetHelpmessage(self, event=None):
+ balloon = self['balloon']
+ if balloon is not None:
+ balloon.clearstatus()
+
+Pmw.forwardmethods(MainMenuBar, tkinter.Menu, '_hull')
+
+######################################################################
+### File: PmwMenuBar.py
+# Manager widget for menus.
+
+import string
+import types
+import tkinter
+import Pmw
+
+class MenuBar(Pmw.MegaWidget):
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('balloon', None, None),
+ ('hotkeys', 1, INITOPT),
+ ('padx', 0, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs, dynamicGroups = ('Menu', 'Button'))
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ self._menuInfo = {}
+ # Map from a menu name to a tuple of information about the menu.
+ # The first item in the tuple is the name of the parent menu (for
+ # toplevel menus this is None). The second item in the tuple is
+ # a list of status help messages for each item in the menu.
+ # The third item in the tuple is the id of the binding used
+ # to detect mouse motion to display status help.
+ # Information for the toplevel menubuttons is not stored here.
+
+ self._mydeletecommand = self.component('hull').tk.deletecommand
+ # Cache this method for use later.
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def deletemenuitems(self, menuName, start, end = None):
+ self.component(menuName + '-menu').delete(start, end)
+ if end is None:
+ del self._menuInfo[menuName][1][start]
+ else:
+ self._menuInfo[menuName][1][start:end+1] = []
+
+ def deletemenu(self, menuName):
+ """Delete should be called for cascaded menus before main menus.
+ """
+
+ # Clean up binding for this menu.
+ parentName = self._menuInfo[menuName][0]
+ bindId = self._menuInfo[menuName][2]
+ _bindtag = 'PmwMenuBar' + str(self) + menuName
+ self.unbind_class(_bindtag, '<Motion>')
+ self._mydeletecommand(bindId) # unbind_class does not clean up
+ del self._menuInfo[menuName]
+
+ if parentName is None:
+ self.destroycomponent(menuName + '-button')
+ else:
+ parentMenu = self.component(parentName + '-menu')
+
+ menu = self.component(menuName + '-menu')
+ menuId = str(menu)
+ for item in range(parentMenu.index('end') + 1):
+ if parentMenu.type(item) == 'cascade':
+ itemMenu = str(parentMenu.entrycget(item, 'menu'))
+ if itemMenu == menuId:
+ parentMenu.delete(item)
+ del self._menuInfo[parentName][1][item]
+ break
+
+ self.destroycomponent(menuName + '-menu')
+
+ def disableall(self):
+ for menuName in list(self._menuInfo.keys()):
+ if self._menuInfo[menuName][0] is None:
+ menubutton = self.component(menuName + '-button')
+ menubutton.configure(state = 'disabled')
+
+ def enableall(self):
+ for menuName in list(self._menuInfo.keys()):
+ if self._menuInfo[menuName][0] is None:
+ menubutton = self.component(menuName + '-button')
+ menubutton.configure(state = 'normal')
+
+ def addmenu(self, menuName, balloonHelp, statusHelp = None,
+ side = 'left', traverseSpec = None, **kw):
+
+ self._addmenu(None, menuName, balloonHelp, statusHelp,
+ traverseSpec, side, 'text', kw)
+
+ def addcascademenu(self, parentMenuName, menuName, statusHelp = '',
+ traverseSpec = None, **kw):
+
+ self._addmenu(parentMenuName, menuName, None, statusHelp,
+ traverseSpec, None, 'label', kw)
+
+ def _addmenu(self, parentMenuName, menuName, balloonHelp, statusHelp,
+ traverseSpec, side, textKey, kw):
+
+ if (menuName + '-menu') in self.components():
+ raise ValueError('menu "%s" already exists' % menuName)
+
+ menukw = {}
+ if 'tearoff' in kw:
+ menukw['tearoff'] = kw['tearoff']
+ del kw['tearoff']
+ else:
+ menukw['tearoff'] = 0
+
+ if textKey not in kw:
+ kw[textKey] = menuName
+
+ self._addHotkeyToOptions(parentMenuName, kw, textKey, traverseSpec)
+
+ if parentMenuName is None:
+ button = self.createcomponent(*(menuName + '-button',
+ (), 'Button',
+ tkinter.Menubutton, (self.interior(),)), **kw)
+ button.pack(side=side, padx = self['padx'])
+ balloon = self['balloon']
+ if balloon is not None:
+ balloon.bind(button, balloonHelp, statusHelp)
+ parentMenu = button
+ else:
+ parentMenu = self.component(parentMenuName + '-menu')
+ parentMenu.add_cascade(*(), **kw)
+ self._menuInfo[parentMenuName][1].append(statusHelp)
+
+ menu = self.createcomponent(*(menuName + '-menu',
+ (), 'Menu',
+ tkinter.Menu, (parentMenu,)), **menukw)
+ if parentMenuName is None:
+ button.configure(menu = menu)
+ else:
+ parentMenu.entryconfigure('end', menu = menu)
+
+ # Need to put this binding after the class bindings so that
+ # menu.index() does not lag behind.
+ _bindtag = 'PmwMenuBar' + str(self) + menuName
+ bindId = self.bind_class(_bindtag, '<Motion>',
+ lambda event=None, self=self, menuName=menuName:
+ self._menuHelp(menuName))
+ menu.bindtags(menu.bindtags() + (_bindtag,))
+ menu.bind('<Leave>', self._resetHelpmessage)
+
+ self._menuInfo[menuName] = (parentMenuName, [], bindId)
+
+ def addmenuitem(self, menuName, itemType, statusHelp = '',
+ traverseSpec = None, **kw):
+
+ menu = self.component(menuName + '-menu')
+ if itemType != 'separator':
+ self._addHotkeyToOptions(menuName, kw, 'label', traverseSpec)
+
+ if itemType == 'command':
+ command = menu.add_command
+ elif itemType == 'separator':
+ command = menu.add_separator
+ elif itemType == 'checkbutton':
+ command = menu.add_checkbutton
+ elif itemType == 'radiobutton':
+ command = menu.add_radiobutton
+ elif itemType == 'cascade':
+ command = menu.add_cascade
+ else:
+ raise ValueError('unknown menuitem type "%s"' % itemType)
+
+ self._menuInfo[menuName][1].append(statusHelp)
+ command(*(), **kw)
+
+ def _addHotkeyToOptions(self, menuName, kw, textKey, traverseSpec):
+
+ if (not self['hotkeys'] or 'underline' in kw or
+ textKey not in kw):
+ return
+
+ if type(traverseSpec) == int:
+ kw['underline'] = traverseSpec
+ return
+
+ hotkeyList = []
+ if menuName is None:
+ for menuName in list(self._menuInfo.keys()):
+ if self._menuInfo[menuName][0] is None:
+ menubutton = self.component(menuName + '-button')
+ #Python 3 conversion
+ #underline = string.atoi(str(menubutton.cget('underline')))
+ underline = int(str(menubutton.cget('underline')))
+ if underline != -1:
+ label = str(menubutton.cget(textKey))
+ if underline < len(label):
+ hotkey = label[underline].lower()
+ if hotkey not in hotkeyList:
+ hotkeyList.append(hotkey)
+ else:
+ menu = self.component(menuName + '-menu')
+ end = menu.index('end')
+ if end is not None:
+ for item in range(end + 1):
+ if menu.type(item) not in ('separator', 'tearoff'):
+ #Python 3 conversion
+ #underline = string.atoi(
+ # str(menu.entrycget(item, 'underline')))
+ underline = int(
+ str(menu.entrycget(item, 'underline')))
+ if underline != -1:
+ label = str(menu.entrycget(item, textKey))
+ if underline < len(label):
+ hotkey = label[underline].lower()
+ if hotkey not in hotkeyList:
+ hotkeyList.append(hotkey)
+
+ name = kw[textKey]
+
+ if type(traverseSpec) is str:
+ lowerLetter = traverseSpec.lower()
+ if traverseSpec in name and lowerLetter not in hotkeyList:
+ kw['underline'] = name.index(traverseSpec)
+ else:
+ targets = string.digits + string.ascii_letters
+ lowerName = name.lower()
+ for letter_index in range(len(name)):
+ letter = lowerName[letter_index]
+ if letter in targets and letter not in hotkeyList:
+ kw['underline'] = letter_index
+ break
+
+ def _menuHelp(self, menuName):
+ menu = self.component(menuName + '-menu')
+ index = menu.index('active')
+
+ balloon = self['balloon']
+ if balloon is not None:
+ if index is None:
+ balloon.showstatus('')
+ else:
+ if str(menu.cget('tearoff')) == '1':
+ index = index - 1
+ if index >= 0:
+ help = self._menuInfo[menuName][1][index]
+ balloon.showstatus(help)
+
+ def _resetHelpmessage(self, event=None):
+ balloon = self['balloon']
+ if balloon is not None:
+ balloon.clearstatus()
+
+######################################################################
+### File: PmwMessageBar.py
+# Class to display messages in an information line.
+
+import string
+import tkinter
+import Pmw
+
+class MessageBar(Pmw.MegaWidget):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ defaultMessageTypes = {
+ # (priority, showtime, bells, logmessage)
+ 'systemerror' : (5, 10, 2, 1),
+ 'usererror' : (4, 5, 1, 0),
+ 'busy' : (3, 0, 0, 0),
+ 'systemevent' : (2, 5, 0, 0),
+ 'userevent' : (2, 5, 0, 0),
+ 'help' : (1, 5, 0, 0),
+ 'state' : (0, 0, 0, 0),
+ }
+ optiondefs = (
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('messagetypes', defaultMessageTypes, INITOPT),
+ ('silent', 0, None),
+ ('sticky', 'ew', INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+ self._messageBarEntry = self.createcomponent('entry',
+ (), None,
+ tkinter.Entry, (interior,))
+
+ # Can't always use 'disabled', since this greys out text in Tk 8.4.2
+ try:
+ self._messageBarEntry.configure(state = 'readonly')
+ except tkinter.TclError:
+ self._messageBarEntry.configure(state = 'disabled')
+
+ self._messageBarEntry.grid(column=2, row=2, sticky=self['sticky'])
+ interior.grid_columnconfigure(2, weight=1)
+ interior.grid_rowconfigure(2, weight=1)
+
+ self.createlabel(interior)
+
+ # Initialise instance variables.
+ self._numPriorities = 0
+ for info in list(self['messagetypes'].values()):
+ if self._numPriorities < info[0]:
+ self._numPriorities = info[0]
+
+ self._numPriorities = self._numPriorities + 1
+ self._timer = [None] * self._numPriorities
+ self._messagetext = [''] * self._numPriorities
+ self._activemessage = [0] * self._numPriorities
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def destroy(self):
+ for timerId in self._timer:
+ if timerId is not None:
+ self.after_cancel(timerId)
+ self._timer = [None] * self._numPriorities
+ Pmw.MegaWidget.destroy(self)
+
+ def message(self, type, text):
+ # Display a message in the message bar.
+
+ (priority, showtime, bells, logmessage) = self['messagetypes'][type]
+
+ if not self['silent']:
+ for i in range(bells):
+ if i != 0:
+ self.after(100)
+ self.bell()
+
+ self._activemessage[priority] = 1
+ if text is None:
+ text = ''
+ self._messagetext[priority] = text.replace('\n', ' ')
+ self._redisplayInfoMessage()
+
+ if logmessage:
+ # Should log this text to a text widget.
+ pass
+
+ if showtime > 0:
+ if self._timer[priority] is not None:
+ self.after_cancel(self._timer[priority])
+
+ # Define a callback to clear this message after a time.
+ def _clearmessage(self=self, priority=priority):
+ self._clearActivemessage(priority)
+
+ mseconds = int(showtime * 1000)
+ self._timer[priority] = self.after(mseconds, _clearmessage)
+
+ def helpmessage(self, text):
+ if text is None:
+ self.resetmessages('help')
+ else:
+ self.message('help', text)
+
+ def resetmessages(self, type):
+ priority = self['messagetypes'][type][0]
+ self._clearActivemessage(priority)
+ for messagetype, info in list(self['messagetypes'].items()):
+ thisPriority = info[0]
+ showtime = info[1]
+ if thisPriority < priority and showtime != 0:
+ self._clearActivemessage(thisPriority)
+
+ def _clearActivemessage(self, priority):
+ self._activemessage[priority] = 0
+ if self._timer[priority] is not None:
+ self.after_cancel(self._timer[priority])
+ self._timer[priority] = None
+ self._redisplayInfoMessage()
+
+ def _redisplayInfoMessage(self):
+ text = ''
+ for priority in range(self._numPriorities - 1, -1, -1):
+ if self._activemessage[priority]:
+ text = self._messagetext[priority]
+ break
+ self._messageBarEntry.configure(state = 'normal')
+ self._messageBarEntry.delete(0, 'end')
+ self._messageBarEntry.insert('end', text)
+
+ # Can't always use 'disabled', since this greys out text in Tk 8.4.2
+ try:
+ self._messageBarEntry.configure(state = 'readonly')
+ except tkinter.TclError:
+ self._messageBarEntry.configure(state = 'disabled')
+
+Pmw.forwardmethods(MessageBar, tkinter.Entry, '_messageBarEntry')
+
+######################################################################
+### File: PmwMessageDialog.py
+# Based on iwidgets2.2.0/messagedialog.itk code.
+
+import tkinter
+import Pmw
+
+class MessageDialog(Pmw.Dialog):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('borderx', 20, INITOPT),
+ ('bordery', 20, INITOPT),
+ ('iconmargin', 20, INITOPT),
+ ('iconpos', None, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.Dialog.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+
+ self._message = self.createcomponent('message',
+ (), None,
+ tkinter.Label, (interior,))
+
+ iconpos = self['iconpos']
+ iconmargin = self['iconmargin']
+ borderx = self['borderx']
+ bordery = self['bordery']
+ border_right = 2
+ border_bottom = 2
+ if iconpos is None:
+ self._message.grid(column = 1, row = 1)
+ else:
+ self._icon = self.createcomponent('icon',
+ (), None,
+ tkinter.Label, (interior,))
+ if iconpos not in 'nsew':
+ raise ValueError('bad iconpos option "%s": should be n, s, e, or w' \
+ % iconpos)
+
+ if iconpos in 'nw':
+ icon = 1
+ message = 3
+ else:
+ icon = 3
+ message = 1
+
+ if iconpos in 'ns':
+ # vertical layout
+ self._icon.grid(column = 1, row = icon)
+ self._message.grid(column = 1, row = message)
+ interior.grid_rowconfigure(2, minsize = iconmargin)
+ border_bottom = 4
+ else:
+ # horizontal layout
+ self._icon.grid(column = icon, row = 1)
+ self._message.grid(column = message, row = 1)
+ interior.grid_columnconfigure(2, minsize = iconmargin)
+ border_right = 4
+
+ interior.grid_columnconfigure(0, minsize = borderx)
+ interior.grid_rowconfigure(0, minsize = bordery)
+ interior.grid_columnconfigure(border_right, minsize = borderx)
+ interior.grid_rowconfigure(border_bottom, minsize = bordery)
+
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+######################################################################
+### File: PmwNoteBook.py
+import string
+import types
+import tkinter
+import Pmw
+
+class NoteBook(Pmw.MegaArchetype):
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('hull_highlightthickness', 0, None),
+ ('hull_borderwidth', 0, None),
+ ('arrownavigation', 1, INITOPT),
+ ('borderwidth', 2, INITOPT),
+ ('createcommand', None, None),
+ ('lowercommand', None, None),
+ ('pagemargin', 4, INITOPT),
+ ('raisecommand', None, None),
+ ('tabpos', 'n', INITOPT),
+ )
+ self.defineoptions(kw, optiondefs, dynamicGroups = ('Page', 'Tab'))
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaArchetype.__init__(self, parent, tkinter.Canvas)
+
+ self.bind('<Map>', self._handleMap)
+ self.bind('<Configure>', self._handleConfigure)
+
+ tabpos = self['tabpos']
+ if tabpos is not None and tabpos != 'n':
+ raise ValueError('bad tabpos option %s: should be n or None' % repr(tabpos))
+ self._withTabs = (tabpos is not None)
+ self._pageMargin = self['pagemargin']
+ self._borderWidth = self['borderwidth']
+
+ # Use a dictionary as a set of bits indicating what needs to
+ # be redisplayed the next time _layout() is called. If
+ # dictionary contains 'topPage' key, the value is the new top
+ # page to be displayed. None indicates that all pages have
+ # been deleted and that _layout() should draw a border under where
+ # the tabs should be.
+ self._pending = {}
+ self._pending['size'] = 1
+ self._pending['borderColor'] = 1
+ self._pending['topPage'] = None
+ if self._withTabs:
+ self._pending['tabs'] = 1
+
+ self._canvasSize = None # This gets set by <Configure> events
+
+ # Set initial height of space for tabs
+ if self._withTabs:
+ self.tabBottom = 35
+ else:
+ self.tabBottom = 0
+
+ self._lightBorderColor, self._darkBorderColor = \
+ Pmw.Color.bordercolors(self, self['hull_background'])
+
+ self._pageNames = [] # List of page names
+
+ # Map from page name to page info. Each item is itself a
+ # dictionary containing the following items:
+ # page the Tkinter.Frame widget for the page
+ # created set to true the first time the page is raised
+ # tabbutton the Tkinter.Button widget for the button (if any)
+ # tabreqwidth requested width of the tab
+ # tabreqheight requested height of the tab
+ # tabitems the canvas items for the button: the button
+ # window item, the lightshadow and the darkshadow
+ # left the left and right canvas coordinates of the tab
+ # right
+ self._pageAttrs = {}
+
+ # Name of page currently on top (actually displayed, using
+ # create_window, not pending). Ignored if current top page
+ # has been deleted or new top page is pending. None indicates
+ # no pages in notebook.
+ self._topPageName = None
+
+ # Canvas items used:
+ # Per tab:
+ # top and left shadow
+ # right shadow
+ # button
+ # Per notebook:
+ # page
+ # top page
+ # left shadow
+ # bottom and right shadow
+ # top (one or two items)
+
+ # Canvas tags used:
+ # lighttag - top and left shadows of tabs and page
+ # darktag - bottom and right shadows of tabs and page
+ # (if no tabs then these are reversed)
+ # (used to color the borders by recolorborders)
+
+ # Create page border shadows.
+ if self._withTabs:
+ self._pageLeftBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
+ fill = self._lightBorderColor, tags = 'lighttag')
+ self._pageBottomRightBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
+ fill = self._darkBorderColor, tags = 'darktag')
+ self._pageTop1Border = self.create_polygon(0, 0, 0, 0, 0, 0,
+ fill = self._darkBorderColor, tags = 'lighttag')
+ self._pageTop2Border = self.create_polygon(0, 0, 0, 0, 0, 0,
+ fill = self._darkBorderColor, tags = 'lighttag')
+ else:
+ self._pageLeftBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
+ fill = self._darkBorderColor, tags = 'darktag')
+ self._pageBottomRightBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
+ fill = self._lightBorderColor, tags = 'lighttag')
+ self._pageTopBorder = self.create_polygon(0, 0, 0, 0, 0, 0,
+ fill = self._darkBorderColor, tags = 'darktag')
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def insert(self, pageName, before = 0, **kw):
+ if pageName in self._pageAttrs:
+ msg = 'Page "%s" already exists.' % pageName
+ raise ValueError(msg)
+
+ # Do this early to catch bad <before> spec before creating any items.
+ beforeIndex = self.index(before, 1)
+
+ pageOptions = {}
+ if self._withTabs:
+ # Default tab button options.
+ tabOptions = {
+ 'text' : pageName,
+ 'borderwidth' : 0,
+ }
+
+ # Divide the keyword options into the 'page_' and 'tab_' options.
+ for key in list(kw.keys()):
+ if key[:5] == 'page_':
+ pageOptions[key[5:]] = kw[key]
+ del kw[key]
+ elif self._withTabs and key[:4] == 'tab_':
+ tabOptions[key[4:]] = kw[key]
+ del kw[key]
+ else:
+ raise KeyError('Unknown option "' + key + '"')
+
+ # Create the frame to contain the page.
+ page = self.createcomponent(*(pageName,
+ (), 'Page',
+ tkinter.Frame, self._hull), **pageOptions)
+
+
+ attributes = {}
+ attributes['page'] = page
+ attributes['created'] = 0
+
+ if self._withTabs:
+ # Create the button for the tab.
+ def raiseThisPage(self = self, pageName = pageName):
+ self.selectpage(pageName)
+ tabOptions['command'] = raiseThisPage
+ tab = self.createcomponent(*(pageName + '-tab',
+ (), 'Tab',
+ tkinter.Button, self._hull), **tabOptions)
+
+ if self['arrownavigation']:
+ # Allow the use of the arrow keys for Tab navigation:
+ def next(event, self = self, pageName = pageName):
+ self.nextpage(pageName)
+ def prev(event, self = self, pageName = pageName):
+ self.previouspage(pageName)
+ tab.bind('<Left>', prev)
+ tab.bind('<Right>', next)
+
+ attributes['tabbutton'] = tab
+ attributes['tabreqwidth'] = tab.winfo_reqwidth()
+ attributes['tabreqheight'] = tab.winfo_reqheight()
+
+ # Create the canvas item to manage the tab's button and the items
+ # for the tab's shadow.
+ windowitem = self.create_window(0, 0, window = tab, anchor = 'nw')
+ lightshadow = self.create_polygon(0, 0, 0, 0, 0, 0,
+ tags = 'lighttag', fill = self._lightBorderColor)
+ darkshadow = self.create_polygon(0, 0, 0, 0, 0, 0,
+ tags = 'darktag', fill = self._darkBorderColor)
+ attributes['tabitems'] = (windowitem, lightshadow, darkshadow)
+ self._pending['tabs'] = 1
+
+ self._pageAttrs[pageName] = attributes
+ self._pageNames.insert(beforeIndex, pageName)
+
+ # If this is the first page added, make it the new top page
+ # and call the create and raise callbacks.
+ if self.getcurselection() is None:
+ self._pending['topPage'] = pageName
+ self._raiseNewTop(pageName)
+
+ self._layout()
+ return page
+
+ def add(self, pageName, **kw):
+ return self.insert(*(pageName, len(self._pageNames)), **kw)
+
+ def delete(self, *pageNames):
+ newTopPage = 0
+ for page in pageNames:
+ pageIndex = self.index(page)
+ pageName = self._pageNames[pageIndex]
+ pageInfo = self._pageAttrs[pageName]
+
+ if self.getcurselection() == pageName:
+ if len(self._pageNames) == 1:
+ newTopPage = 0
+ self._pending['topPage'] = None
+ elif pageIndex == len(self._pageNames) - 1:
+ newTopPage = 1
+ self._pending['topPage'] = self._pageNames[pageIndex - 1]
+ else:
+ newTopPage = 1
+ self._pending['topPage'] = self._pageNames[pageIndex + 1]
+
+ if self._topPageName == pageName:
+ self._hull.delete(self._topPageItem)
+ self._topPageName = None
+
+ if self._withTabs:
+ self.destroycomponent(pageName + '-tab')
+ self._hull.delete(*pageInfo['tabitems'])
+ self.destroycomponent(pageName)
+ del self._pageAttrs[pageName]
+ del self._pageNames[pageIndex]
+
+ # If the old top page was deleted and there are still pages
+ # left in the notebook, call the create and raise callbacks.
+ if newTopPage:
+ pageName = self._pending['topPage']
+ self._raiseNewTop(pageName)
+
+ if self._withTabs:
+ self._pending['tabs'] = 1
+ self._layout()
+
+ def page(self, pageIndex):
+ pageName = self._pageNames[self.index(pageIndex)]
+ return self._pageAttrs[pageName]['page']
+
+ def pagenames(self):
+ return list(self._pageNames)
+
+ def getcurselection(self):
+ if 'topPage' in self._pending:
+ return self._pending['topPage']
+ else:
+ return self._topPageName
+
+ def tab(self, pageIndex):
+ if self._withTabs:
+ pageName = self._pageNames[self.index(pageIndex)]
+ return self._pageAttrs[pageName]['tabbutton']
+ else:
+ return None
+
+ def index(self, index, forInsert = 0):
+ listLength = len(self._pageNames)
+ if type(index) == int:
+ if forInsert and index <= listLength:
+ return index
+ elif not forInsert and index < listLength:
+ return index
+ else:
+ raise ValueError('index "%s" is out of range' % index)
+ elif index is Pmw.END:
+ if forInsert:
+ return listLength
+ elif listLength > 0:
+ return listLength - 1
+ else:
+ raise ValueError('NoteBook has no pages')
+ elif index is Pmw.SELECT:
+ if listLength == 0:
+ raise ValueError('NoteBook has no pages')
+ return self._pageNames.index(self.getcurselection())
+ else:
+ if index in self._pageNames:
+ return self._pageNames.index(index)
+ validValues = 'a name, a number, Pmw.END or Pmw.SELECT'
+ raise ValueError('bad index "%s": must be %s' % (index, validValues))
+
+ def selectpage(self, page):
+ pageName = self._pageNames[self.index(page)]
+ oldTopPage = self.getcurselection()
+ if pageName != oldTopPage:
+ self._pending['topPage'] = pageName
+ if oldTopPage == self._topPageName:
+ self._hull.delete(self._topPageItem)
+ cmd = self['lowercommand']
+ if cmd is not None:
+ cmd(oldTopPage)
+ self._raiseNewTop(pageName)
+
+ self._layout()
+
+ # Set focus to the tab of new top page:
+ if self._withTabs and self['arrownavigation']:
+ self._pageAttrs[pageName]['tabbutton'].focus_set()
+
+ def previouspage(self, pageIndex = None):
+ if pageIndex is None:
+ curpage = self.index(Pmw.SELECT)
+ else:
+ curpage = self.index(pageIndex)
+ if curpage > 0:
+ self.selectpage(curpage - 1)
+
+ def nextpage(self, pageIndex = None):
+ if pageIndex is None:
+ curpage = self.index(Pmw.SELECT)
+ else:
+ curpage = self.index(pageIndex)
+ if curpage < len(self._pageNames) - 1:
+ self.selectpage(curpage + 1)
+
+ def setnaturalsize(self, pageNames = None):
+ self.update_idletasks()
+ maxPageWidth = 1
+ maxPageHeight = 1
+ if pageNames is None:
+ pageNames = self.pagenames()
+ for pageName in pageNames:
+ pageInfo = self._pageAttrs[pageName]
+ page = pageInfo['page']
+ w = page.winfo_reqwidth()
+ h = page.winfo_reqheight()
+ if maxPageWidth < w:
+ maxPageWidth = w
+ if maxPageHeight < h:
+ maxPageHeight = h
+ pageBorder = self._borderWidth + self._pageMargin
+ width = maxPageWidth + pageBorder * 2
+ height = maxPageHeight + pageBorder * 2
+
+ if self._withTabs:
+ maxTabHeight = 0
+ for pageInfo in list(self._pageAttrs.values()):
+ if maxTabHeight < pageInfo['tabreqheight']:
+ maxTabHeight = pageInfo['tabreqheight']
+ height = height + maxTabHeight + self._borderWidth * 1.5
+
+ # Note that, since the hull is a canvas, the width and height
+ # options specify the geometry *inside* the borderwidth and
+ # highlightthickness.
+ self.configure(hull_width = width, hull_height = height)
+
+ def recolorborders(self):
+ self._pending['borderColor'] = 1
+ self._layout()
+
+ def _handleMap(self, event):
+ self._layout()
+
+ def _handleConfigure(self, event):
+ self._canvasSize = (event.width, event.height)
+ self._pending['size'] = 1
+ self._layout()
+
+ def _raiseNewTop(self, pageName):
+ if not self._pageAttrs[pageName]['created']:
+ self._pageAttrs[pageName]['created'] = 1
+ cmd = self['createcommand']
+ if cmd is not None:
+ cmd(pageName)
+ cmd = self['raisecommand']
+ if cmd is not None:
+ cmd(pageName)
+
+ # This is the vertical layout of the notebook, from top (assuming
+ # tabpos is 'n'):
+ # hull highlightthickness (top)
+ # hull borderwidth (top)
+ # borderwidth (top border of tabs)
+ # borderwidth * 0.5 (space for bevel)
+ # tab button (maximum of requested height of all tab buttons)
+ # borderwidth (border between tabs and page)
+ # pagemargin (top)
+ # the page itself
+ # pagemargin (bottom)
+ # borderwidth (border below page)
+ # hull borderwidth (bottom)
+ # hull highlightthickness (bottom)
+ #
+ # canvasBorder is sum of top two elements.
+ # tabBottom is sum of top five elements.
+ #
+ # Horizontal layout (and also vertical layout when tabpos is None):
+ # hull highlightthickness
+ # hull borderwidth
+ # borderwidth
+ # pagemargin
+ # the page itself
+ # pagemargin
+ # borderwidth
+ # hull borderwidth
+ # hull highlightthickness
+ #
+ def _layout(self):
+ if not self.winfo_ismapped() or self._canvasSize is None:
+ # Don't layout if the window is not displayed, or we
+ # haven't yet received a <Configure> event.
+ return
+
+ hullWidth, hullHeight = self._canvasSize
+ borderWidth = self._borderWidth
+ #Python 3 conversion
+ #canvasBorder = string.atoi(self._hull['borderwidth']) + \
+ # string.atoi(self._hull['highlightthickness'])
+ canvasBorder = int(self._hull['borderwidth']) + \
+ int(self._hull['highlightthickness'])
+ if not self._withTabs:
+ self.tabBottom = canvasBorder
+ oldTabBottom = self.tabBottom
+
+ if 'borderColor' in self._pending:
+ self._lightBorderColor, self._darkBorderColor = \
+ Pmw.Color.bordercolors(self, self['hull_background'])
+
+ # Draw all the tabs.
+ if self._withTabs and ('tabs' in self._pending or
+ 'size' in self._pending):
+ # Find total requested width and maximum requested height
+ # of tabs.
+ sumTabReqWidth = 0
+ maxTabHeight = 0
+ for pageInfo in list(self._pageAttrs.values()):
+ sumTabReqWidth = sumTabReqWidth + pageInfo['tabreqwidth']
+ if maxTabHeight < pageInfo['tabreqheight']:
+ maxTabHeight = pageInfo['tabreqheight']
+ if maxTabHeight != 0:
+ # Add the top tab border plus a bit for the angled corners
+ self.tabBottom = canvasBorder + maxTabHeight + borderWidth * 1.5
+
+ # Prepare for drawing the border around each tab button.
+ tabTop = canvasBorder
+ tabTop2 = tabTop + borderWidth
+ tabTop3 = tabTop + borderWidth * 1.5
+ tabBottom2 = self.tabBottom
+ tabBottom = self.tabBottom + borderWidth
+
+ numTabs = len(self._pageNames)
+ availableWidth = hullWidth - 2 * canvasBorder - \
+ numTabs * 2 * borderWidth
+ x = canvasBorder
+ cumTabReqWidth = 0
+ cumTabWidth = 0
+
+ # Position all the tabs.
+ for pageName in self._pageNames:
+ pageInfo = self._pageAttrs[pageName]
+ (windowitem, lightshadow, darkshadow) = pageInfo['tabitems']
+ if sumTabReqWidth <= availableWidth:
+ tabwidth = pageInfo['tabreqwidth']
+ else:
+ # This ugly calculation ensures that, when the
+ # notebook is not wide enough for the requested
+ # widths of the tabs, the total width given to
+ # the tabs exactly equals the available width,
+ # without rounding errors.
+ cumTabReqWidth = cumTabReqWidth + pageInfo['tabreqwidth']
+ tmp = (2*cumTabReqWidth*availableWidth + sumTabReqWidth) \
+ / (2 * sumTabReqWidth)
+ tabwidth = tmp - cumTabWidth
+ cumTabWidth = tmp
+
+ # Position the tab's button canvas item.
+ self.coords(windowitem, x + borderWidth, tabTop3)
+ self.itemconfigure(windowitem,
+ width = tabwidth, height = maxTabHeight)
+
+ # Make a beautiful border around the tab.
+ left = x
+ left2 = left + borderWidth
+ left3 = left + borderWidth * 1.5
+ right = left + tabwidth + 2 * borderWidth
+ right2 = left + tabwidth + borderWidth
+ right3 = left + tabwidth + borderWidth * 0.5
+
+ self.coords(lightshadow,
+ left, tabBottom2, left, tabTop2, left2, tabTop,
+ right2, tabTop, right3, tabTop2, left3, tabTop2,
+ left2, tabTop3, left2, tabBottom,
+ )
+ self.coords(darkshadow,
+ right2, tabTop, right, tabTop2, right, tabBottom2,
+ right2, tabBottom, right2, tabTop3, right3, tabTop2,
+ )
+ pageInfo['left'] = left
+ pageInfo['right'] = right
+
+ x = x + tabwidth + 2 * borderWidth
+
+ # Redraw shadow under tabs so that it appears that tab for old
+ # top page is lowered and that tab for new top page is raised.
+ if self._withTabs and ('topPage' in self._pending or
+ 'tabs' in self._pending or 'size' in self._pending):
+
+ if self.getcurselection() is None:
+ # No pages, so draw line across top of page area.
+ self.coords(self._pageTop1Border,
+ canvasBorder, self.tabBottom,
+ hullWidth - canvasBorder, self.tabBottom,
+ hullWidth - canvasBorder - borderWidth,
+ self.tabBottom + borderWidth,
+ borderWidth + canvasBorder, self.tabBottom + borderWidth,
+ )
+
+ # Ignore second top border.
+ self.coords(self._pageTop2Border, 0, 0, 0, 0, 0, 0)
+ else:
+ # Draw two lines, one on each side of the tab for the
+ # top page, so that the tab appears to be raised.
+ pageInfo = self._pageAttrs[self.getcurselection()]
+ left = pageInfo['left']
+ right = pageInfo['right']
+ self.coords(self._pageTop1Border,
+ canvasBorder, self.tabBottom,
+ left, self.tabBottom,
+ left + borderWidth, self.tabBottom + borderWidth,
+ canvasBorder + borderWidth, self.tabBottom + borderWidth,
+ )
+
+ self.coords(self._pageTop2Border,
+ right, self.tabBottom,
+ hullWidth - canvasBorder, self.tabBottom,
+ hullWidth - canvasBorder - borderWidth,
+ self.tabBottom + borderWidth,
+ right - borderWidth, self.tabBottom + borderWidth,
+ )
+
+ # Prevent bottom of dark border of tabs appearing over
+ # page top border.
+ self.tag_raise(self._pageTop1Border)
+ self.tag_raise(self._pageTop2Border)
+
+ # Position the page border shadows.
+ if 'size' in self._pending or oldTabBottom != self.tabBottom:
+
+ self.coords(self._pageLeftBorder,
+ canvasBorder, self.tabBottom,
+ borderWidth + canvasBorder,
+ self.tabBottom + borderWidth,
+ borderWidth + canvasBorder,
+ hullHeight - canvasBorder - borderWidth,
+ canvasBorder, hullHeight - canvasBorder,
+ )
+
+ self.coords(self._pageBottomRightBorder,
+ hullWidth - canvasBorder, self.tabBottom,
+ hullWidth - canvasBorder, hullHeight - canvasBorder,
+ canvasBorder, hullHeight - canvasBorder,
+ borderWidth + canvasBorder,
+ hullHeight - canvasBorder - borderWidth,
+ hullWidth - canvasBorder - borderWidth,
+ hullHeight - canvasBorder - borderWidth,
+ hullWidth - canvasBorder - borderWidth,
+ self.tabBottom + borderWidth,
+ )
+
+ if not self._withTabs:
+ self.coords(self._pageTopBorder,
+ canvasBorder, self.tabBottom,
+ hullWidth - canvasBorder, self.tabBottom,
+ hullWidth - canvasBorder - borderWidth,
+ self.tabBottom + borderWidth,
+ borderWidth + canvasBorder, self.tabBottom + borderWidth,
+ )
+
+ # Color borders.
+ if 'borderColor' in self._pending:
+ self.itemconfigure('lighttag', fill = self._lightBorderColor)
+ self.itemconfigure('darktag', fill = self._darkBorderColor)
+
+ newTopPage = self._pending.get('topPage')
+ pageBorder = borderWidth + self._pageMargin
+
+ # Raise new top page.
+ if newTopPage is not None:
+ self._topPageName = newTopPage
+ self._topPageItem = self.create_window(
+ pageBorder + canvasBorder, self.tabBottom + pageBorder,
+ window = self._pageAttrs[newTopPage]['page'],
+ anchor = 'nw',
+ )
+
+ # Change position of top page if tab height has changed.
+ if self._topPageName is not None and oldTabBottom != self.tabBottom:
+ self.coords(self._topPageItem,
+ pageBorder + canvasBorder, self.tabBottom + pageBorder)
+
+ # Change size of top page if,
+ # 1) there is a new top page.
+ # 2) canvas size has changed, but not if there is no top
+ # page (eg: initially or when all pages deleted).
+ # 3) tab height has changed, due to difference in the height of a tab
+ if (newTopPage is not None or \
+ 'size' in self._pending and self._topPageName is not None
+ or oldTabBottom != self.tabBottom):
+ self.itemconfigure(self._topPageItem,
+ width = hullWidth - 2 * canvasBorder - pageBorder * 2,
+ height = hullHeight - 2 * canvasBorder - pageBorder * 2 -
+ (self.tabBottom - canvasBorder),
+ )
+
+ self._pending = {}
+
+# Need to do forwarding to get the pack, grid, etc methods.
+# Unfortunately this means that all the other canvas methods are also
+# forwarded.
+Pmw.forwardmethods(NoteBook, tkinter.Canvas, '_hull')
+
+######################################################################
+### File: PmwOptionMenu.py
+import types
+import tkinter
+import Pmw
+import sys
+import collections
+
+class OptionMenu(Pmw.MegaWidget):
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('command', None, None),
+ ('items', (), INITOPT),
+ ('initialitem', None, INITOPT),
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('sticky', 'ew', INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+
+ self._menubutton = self.createcomponent('menubutton',
+ (), None,
+ tkinter.Menubutton, (interior,),
+ borderwidth = 2,
+ indicatoron = 1,
+ relief = 'raised',
+ anchor = 'c',
+ highlightthickness = 2,
+ direction = 'flush',
+ takefocus = 1,
+ )
+ self._menubutton.grid(column = 2, row = 2, sticky = self['sticky'])
+
+ self._menu = self.createcomponent('menu',
+ (), None,
+ tkinter.Menu, (self._menubutton,),
+ tearoff=0
+ )
+ self._menubutton.configure(menu = self._menu)
+
+ interior.grid_columnconfigure(2, weight = 1)
+ interior.grid_rowconfigure(2, weight = 1)
+
+ # Create the label.
+ self.createlabel(interior)
+
+ # Add the items specified by the initialisation option.
+ self._itemList = []
+ self.setitems(self['items'], self['initialitem'])
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def setitems(self, items, index = None):
+ #cleaning up old items only required for Python < 2.5.4
+ if sys.version_info < (2, 5, 4):
+ # Clean up old items and callback commands.
+ for oldIndex in range(len(self._itemList)):
+ tclCommandName = str(self._menu.entrycget(oldIndex, 'command'))
+ if tclCommandName != '':
+ self._menu.deletecommand(tclCommandName)
+ self._menu.delete(0, 'end')
+ self._itemList = list(items)
+
+ # Set the items in the menu component.
+ for item in items:
+ self._menu.add_command(label = item,
+ command = lambda self = self, item = item: self._invoke(item))
+
+ # Set the currently selected value.
+ if index is None:
+ var = str(self._menubutton.cget('textvariable'))
+ if var != '':
+ # None means do not change text variable.
+ return
+ if len(items) == 0:
+ text = ''
+ elif str(self._menubutton.cget('text')) in items:
+ # Do not change selection if it is still valid
+ return
+ else:
+ text = items[0]
+ else:
+ index = self.index(index)
+ text = self._itemList[index]
+
+ self.setvalue(text)
+
+ def getcurselection(self):
+ var = str(self._menubutton.cget('textvariable'))
+ if var == '':
+ return str(self._menubutton.cget('text'))
+ else:
+ return self._menu.tk.globalgetvar(var)
+
+ def getvalue(self):
+ return self.getcurselection()
+
+ def setvalue(self, text):
+ var = str(self._menubutton.cget('textvariable'))
+ if var == '':
+ self._menubutton.configure(text = text)
+ else:
+ self._menu.tk.globalsetvar(var, text)
+
+ def index(self, index):
+ listLength = len(self._itemList)
+ if type(index) == int:
+ if index < listLength:
+ return index
+ else:
+ raise ValueError('index "%s" is out of range' % index)
+ elif index is Pmw.END:
+ if listLength > 0:
+ return listLength - 1
+ else:
+ raise ValueError('OptionMenu has no items')
+ else:
+ if index is Pmw.SELECT:
+ if listLength > 0:
+ index = self.getcurselection()
+ else:
+ raise ValueError('OptionMenu has no items')
+ if index in self._itemList:
+ return self._itemList.index(index)
+ raise ValueError('bad index "%s": must be a ' \
+ 'name, a number, Pmw.END or Pmw.SELECT' % (index,))
+
+ def invoke(self, index = Pmw.SELECT):
+ index = self.index(index)
+ text = self._itemList[index]
+
+ return self._invoke(text)
+
+ def _invoke(self, text):
+ self.setvalue(text)
+
+ command = self['command']
+ if isinstance(command, collections.Callable):
+ return command(text)
+
+######################################################################
+### File: PmwPanedWidget.py
+# PanedWidget
+# a frame which may contain several resizable sub-frames
+
+import string
+import sys
+import types
+import tkinter
+import Pmw
+import collections
+
+class PanedWidget(Pmw.MegaWidget):
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('command', None, None),
+ ('orient', 'vertical', INITOPT),
+ ('separatorrelief', 'sunken', INITOPT),
+ ('separatorthickness', 2, INITOPT),
+ ('handlesize', 8, INITOPT),
+ ('hull_width', 400, None),
+ ('hull_height', 400, None),
+ )
+ self.defineoptions(kw, optiondefs,
+ dynamicGroups = ('Frame', 'Separator', 'Handle'))
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ self.bind('<Configure>', self._handleConfigure)
+
+ if self['orient'] not in ('horizontal', 'vertical'):
+ raise ValueError('bad orient option ' + repr(self['orient']) + \
+ ': must be either \'horizontal\' or \'vertical\'')
+
+ self._separatorThickness = self['separatorthickness']
+ self._handleSize = self['handlesize']
+ self._paneNames = [] # List of pane names
+ self._paneAttrs = {} # Map from pane name to pane info
+
+ self._timerId = None
+ self._frame = {}
+ self._separator = []
+ self._button = []
+ self._totalSize = 0
+ self._movePending = 0
+ self._relsize = {}
+ self._relmin = {}
+ self._relmax = {}
+ self._size = {}
+ self._min = {}
+ self._max = {}
+ self._rootp = None
+ self._curSize = None
+ self._beforeLimit = None
+ self._afterLimit = None
+ self._buttonIsDown = 0
+ self._majorSize = 100
+ self._minorSize = 100
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def insert(self, name, before = 0, **kw):
+ # Parse <kw> for options.
+ self._initPaneOptions(name)
+ self._parsePaneOptions(name, kw)
+
+ insertPos = self._nameToIndex(before)
+ atEnd = (insertPos == len(self._paneNames))
+
+ # Add the frame.
+ self._paneNames[insertPos:insertPos] = [name]
+ self._frame[name] = self.createcomponent(name,
+ (), 'Frame',
+ tkinter.Frame, (self.interior(),))
+
+ # Add separator, if necessary.
+ if len(self._paneNames) > 1:
+ self._addSeparator()
+ else:
+ self._separator.append(None)
+ self._button.append(None)
+
+ # Add the new frame and adjust the PanedWidget
+ if atEnd:
+ size = self._size[name]
+ if size > 0 or self._relsize[name] is not None:
+ if self['orient'] == 'vertical':
+ self._frame[name].place(x=0, relwidth=1,
+ height=size, y=self._totalSize)
+ else:
+ self._frame[name].place(y=0, relheight=1,
+ width=size, x=self._totalSize)
+ else:
+ if self['orient'] == 'vertical':
+ self._frame[name].place(x=0, relwidth=1,
+ y=self._totalSize)
+ else:
+ self._frame[name].place(y=0, relheight=1,
+ x=self._totalSize)
+ else:
+ self._updateSizes()
+
+ self._totalSize = self._totalSize + self._size[name]
+ return self._frame[name]
+
+ def add(self, name, **kw):
+ return self.insert(*(name, len(self._paneNames)), **kw)
+
+ def delete(self, name):
+ deletePos = self._nameToIndex(name)
+ name = self._paneNames[deletePos]
+ self.destroycomponent(name)
+ del self._paneNames[deletePos]
+ del self._frame[name]
+ del self._size[name]
+ del self._min[name]
+ del self._max[name]
+ del self._relsize[name]
+ del self._relmin[name]
+ del self._relmax[name]
+
+ last = len(self._paneNames)
+ del self._separator[last]
+ del self._button[last]
+ if last > 0:
+ self.destroycomponent(self._sepName(last))
+ self.destroycomponent(self._buttonName(last))
+
+ self._plotHandles()
+
+ def setnaturalsize(self):
+ self.update_idletasks()
+ totalWidth = 0
+ totalHeight = 0
+ maxWidth = 0
+ maxHeight = 0
+ for name in self._paneNames:
+ frame = self._frame[name]
+ w = frame.winfo_reqwidth()
+ h = frame.winfo_reqheight()
+ totalWidth = totalWidth + w
+ totalHeight = totalHeight + h
+ if maxWidth < w:
+ maxWidth = w
+ if maxHeight < h:
+ maxHeight = h
+
+ # Note that, since the hull is a frame, the width and height
+ # options specify the geometry *outside* the borderwidth and
+ # highlightthickness.
+
+ #Python 3 conversion
+ #bw = string.atoi(str(self.cget('hull_borderwidth')))
+ #hl = string.atoi(str(self.cget('hull_highlightthickness')))
+ bw = int(str(self.cget('hull_borderwidth')))
+ hl = int(str(self.cget('hull_highlightthickness')))
+ extra = (bw + hl) * 2
+ if str(self.cget('orient')) == 'horizontal':
+ totalWidth = totalWidth + extra
+ maxHeight = maxHeight + extra
+ self.configure(hull_width = totalWidth, hull_height = maxHeight)
+ else:
+ totalHeight = (totalHeight + extra +
+ (len(self._paneNames) - 1) * self._separatorThickness)
+ maxWidth = maxWidth + extra
+ self.configure(hull_width = maxWidth, hull_height = totalHeight)
+
+ def move(self, name, newPos, newPosOffset = 0):
+
+ # see if we can spare ourselves some work
+ numPanes = len(self._paneNames)
+ if numPanes < 2:
+ return
+
+ newPos = self._nameToIndex(newPos) + newPosOffset
+ if newPos < 0 or newPos >=numPanes:
+ return
+
+ deletePos = self._nameToIndex(name)
+
+ if deletePos == newPos:
+ # inserting over ourself is a no-op
+ return
+
+ # delete name from old position in list
+ name = self._paneNames[deletePos]
+ del self._paneNames[deletePos]
+
+ # place in new position
+ self._paneNames[newPos:newPos] = [name]
+
+ # force everything to redraw
+ self._plotHandles()
+ self._updateSizes()
+
+ def _nameToIndex(self, nameOrIndex):
+ try:
+ pos = self._paneNames.index(nameOrIndex)
+ except ValueError:
+ pos = nameOrIndex
+
+ return pos
+
+ def _initPaneOptions(self, name):
+ # Set defaults.
+ self._size[name] = 0
+ self._relsize[name] = None
+ self._min[name] = 0
+ self._relmin[name] = None
+ self._max[name] = 100000
+ self._relmax[name] = None
+
+ def _parsePaneOptions(self, name, args):
+ # Parse <args> for options.
+ for arg, value in list(args.items()):
+ if type(value) == float:
+ relvalue = value
+ value = self._absSize(relvalue)
+ else:
+ relvalue = None
+
+ if arg == 'size':
+ self._size[name], self._relsize[name] = value, relvalue
+ elif arg == 'min':
+ self._min[name], self._relmin[name] = value, relvalue
+ elif arg == 'max':
+ self._max[name], self._relmax[name] = value, relvalue
+ else:
+ raise ValueError('keyword must be "size", "min", or "max"')
+
+ def _absSize(self, relvalue):
+ return int(round(relvalue * self._majorSize))
+
+ def _sepName(self, n):
+ return 'separator-%d' % n
+
+ def _buttonName(self, n):
+ return 'handle-%d' % n
+
+ def _addSeparator(self):
+ n = len(self._paneNames) - 1
+
+ downFunc = lambda event, s = self, num=n: s._btnDown(event, num)
+ upFunc = lambda event, s = self, num=n: s._btnUp(event, num)
+ moveFunc = lambda event, s = self, num=n: s._btnMove(event, num)
+
+ # Create the line dividing the panes.
+ sep = self.createcomponent(self._sepName(n),
+ (), 'Separator',
+ tkinter.Frame, (self.interior(),),
+ borderwidth = 1,
+ relief = self['separatorrelief'])
+ self._separator.append(sep)
+
+ sep.bind('<ButtonPress-1>', downFunc)
+ sep.bind('<Any-ButtonRelease-1>', upFunc)
+ sep.bind('<B1-Motion>', moveFunc)
+
+ if self['orient'] == 'vertical':
+ cursor = 'sb_v_double_arrow'
+ sep.configure(height = self._separatorThickness,
+ width = 10000, cursor = cursor)
+ else:
+ cursor = 'sb_h_double_arrow'
+ sep.configure(width = self._separatorThickness,
+ height = 10000, cursor = cursor)
+
+ self._totalSize = self._totalSize + self._separatorThickness
+
+ # Create the handle on the dividing line.
+ handle = self.createcomponent(self._buttonName(n),
+ (), 'Handle',
+ tkinter.Frame, (self.interior(),),
+ relief = 'raised',
+ borderwidth = 1,
+ width = self._handleSize,
+ height = self._handleSize,
+ cursor = cursor,
+ )
+ self._button.append(handle)
+
+ handle.bind('<ButtonPress-1>', downFunc)
+ handle.bind('<Any-ButtonRelease-1>', upFunc)
+ handle.bind('<B1-Motion>', moveFunc)
+
+ self._plotHandles()
+
+ for i in range(1, len(self._paneNames)):
+ self._separator[i].tkraise()
+ for i in range(1, len(self._paneNames)):
+ self._button[i].tkraise()
+
+ def _btnUp(self, event, item):
+ self._buttonIsDown = 0
+ self._updateSizes()
+ try:
+ self._button[item].configure(relief='raised')
+ except:
+ pass
+
+ def _btnDown(self, event, item):
+ self._button[item].configure(relief='sunken')
+ self._getMotionLimit(item)
+ self._buttonIsDown = 1
+ self._movePending = 0
+
+ def _handleConfigure(self, event = None):
+ self._getNaturalSizes()
+ if self._totalSize == 0:
+ return
+
+ iterRange = list(self._paneNames)
+ iterRange.reverse()
+ if self._majorSize > self._totalSize:
+ n = self._majorSize - self._totalSize
+ self._iterate(iterRange, self._grow, n)
+ elif self._majorSize < self._totalSize:
+ n = self._totalSize - self._majorSize
+ self._iterate(iterRange, self._shrink, n)
+
+ self._plotHandles()
+ self._updateSizes()
+
+ def _getNaturalSizes(self):
+ # Must call this in order to get correct winfo_width, winfo_height
+ self.update_idletasks()
+
+ self._totalSize = 0
+
+ if self['orient'] == 'vertical':
+ self._majorSize = self.winfo_height()
+ self._minorSize = self.winfo_width()
+ majorspec = tkinter.Frame.winfo_reqheight
+ else:
+ self._majorSize = self.winfo_width()
+ self._minorSize = self.winfo_height()
+ majorspec = tkinter.Frame.winfo_reqwidth
+
+ #python 3 conversion
+ #bw = string.atoi(str(self.cget('hull_borderwidth')))
+ #hl = string.atoi(str(self.cget('hull_highlightthickness')))
+ bw = int(str(self.cget('hull_borderwidth')))
+ hl = int(str(self.cget('hull_highlightthickness')))
+ extra = (bw + hl) * 2
+ self._majorSize = self._majorSize - extra
+ self._minorSize = self._minorSize - extra
+
+ if self._majorSize < 0:
+ self._majorSize = 0
+ if self._minorSize < 0:
+ self._minorSize = 0
+
+ for name in self._paneNames:
+ # adjust the absolute sizes first...
+ if self._relsize[name] is None:
+ #special case
+ if self._size[name] == 0:
+ self._size[name] = majorspec(*(self._frame[name],))
+ self._setrel(name)
+ else:
+ self._size[name] = self._absSize(self._relsize[name])
+
+ if self._relmin[name] is not None:
+ self._min[name] = self._absSize(self._relmin[name])
+ if self._relmax[name] is not None:
+ self._max[name] = self._absSize(self._relmax[name])
+
+ # now adjust sizes
+ if self._size[name] < self._min[name]:
+ self._size[name] = self._min[name]
+ self._setrel(name)
+
+ if self._size[name] > self._max[name]:
+ self._size[name] = self._max[name]
+ self._setrel(name)
+
+ self._totalSize = self._totalSize + self._size[name]
+
+ # adjust for separators
+ self._totalSize = (self._totalSize +
+ (len(self._paneNames) - 1) * self._separatorThickness)
+
+ def _setrel(self, name):
+ if self._relsize[name] is not None:
+ if self._majorSize != 0:
+ self._relsize[name] = round(self._size[name]) / self._majorSize
+
+ def _iterate(self, names, proc, n):
+ for i in names:
+ n = proc(*(i, n))
+ if n == 0:
+ break
+
+ def _grow(self, name, n):
+ canGrow = self._max[name] - self._size[name]
+
+ if canGrow > n:
+ self._size[name] = self._size[name] + n
+ self._setrel(name)
+ return 0
+ elif canGrow > 0:
+ self._size[name] = self._max[name]
+ self._setrel(name)
+ n = n - canGrow
+
+ return n
+
+ def _shrink(self, name, n):
+ canShrink = self._size[name] - self._min[name]
+
+ if canShrink > n:
+ self._size[name] = self._size[name] - n
+ self._setrel(name)
+ return 0
+ elif canShrink > 0:
+ self._size[name] = self._min[name]
+ self._setrel(name)
+ n = n - canShrink
+
+ return n
+
+ def _updateSizes(self):
+ totalSize = 0
+
+ for name in self._paneNames:
+ size = self._size[name]
+ if self['orient'] == 'vertical':
+ self._frame[name].place(x = 0, relwidth = 1,
+ y = totalSize,
+ height = size)
+ else:
+ self._frame[name].place(y = 0, relheight = 1,
+ x = totalSize,
+ width = size)
+
+ totalSize = totalSize + size + self._separatorThickness
+
+ # Invoke the callback command
+ cmd = self['command']
+ if isinstance(cmd, collections.Callable):
+ cmd(list(map(lambda x, s = self: s._size[x], self._paneNames)))
+
+ def _plotHandles(self):
+ if len(self._paneNames) == 0:
+ return
+
+ if self['orient'] == 'vertical':
+ btnp = self._minorSize - 13
+ else:
+ h = self._minorSize
+
+ if h > 18:
+ btnp = 9
+ else:
+ btnp = h - 9
+
+ firstPane = self._paneNames[0]
+ totalSize = self._size[firstPane]
+
+ first = 1
+ last = len(self._paneNames) - 1
+
+ # loop from first to last, inclusive
+ for i in range(1, last + 1):
+
+ handlepos = totalSize - 3
+ prevSize = self._size[self._paneNames[i - 1]]
+ nextSize = self._size[self._paneNames[i]]
+
+ offset1 = 0
+
+ if i == first:
+ if prevSize < 4:
+ offset1 = 4 - prevSize
+ else:
+ if prevSize < 8:
+ offset1 = (8 - prevSize) / 2
+
+ offset2 = 0
+
+ if i == last:
+ if nextSize < 4:
+ offset2 = nextSize - 4
+ else:
+ if nextSize < 8:
+ offset2 = (nextSize - 8) / 2
+
+ handlepos = handlepos + offset1
+
+ if self['orient'] == 'vertical':
+ height = 8 - offset1 + offset2
+
+ if height > 1:
+ self._button[i].configure(height = height)
+ self._button[i].place(x = btnp, y = handlepos)
+ else:
+ self._button[i].place_forget()
+
+ self._separator[i].place(x = 0, y = totalSize,
+ relwidth = 1)
+ else:
+ width = 8 - offset1 + offset2
+
+ if width > 1:
+ self._button[i].configure(width = width)
+ self._button[i].place(y = btnp, x = handlepos)
+ else:
+ self._button[i].place_forget()
+
+ self._separator[i].place(y = 0, x = totalSize,
+ relheight = 1)
+
+ totalSize = totalSize + nextSize + self._separatorThickness
+
+ def pane(self, name):
+ return self._frame[self._paneNames[self._nameToIndex(name)]]
+
+ # Return the name of all panes
+ def panes(self):
+ return list(self._paneNames)
+
+ def configurepane(self, name, **kw):
+ name = self._paneNames[self._nameToIndex(name)]
+ self._parsePaneOptions(name, kw)
+ self._handleConfigure()
+
+ def updatelayout(self):
+ self._handleConfigure()
+
+ def _getMotionLimit(self, item):
+ curBefore = (item - 1) * self._separatorThickness
+ minBefore, maxBefore = curBefore, curBefore
+
+ for name in self._paneNames[:item]:
+ curBefore = curBefore + self._size[name]
+ minBefore = minBefore + self._min[name]
+ maxBefore = maxBefore + self._max[name]
+
+ curAfter = (len(self._paneNames) - item) * self._separatorThickness
+ minAfter, maxAfter = curAfter, curAfter
+ for name in self._paneNames[item:]:
+ curAfter = curAfter + self._size[name]
+ minAfter = minAfter + self._min[name]
+ maxAfter = maxAfter + self._max[name]
+
+ beforeToGo = min(curBefore - minBefore, maxAfter - curAfter)
+ afterToGo = min(curAfter - minAfter, maxBefore - curBefore)
+
+ self._beforeLimit = curBefore - beforeToGo
+ self._afterLimit = curBefore + afterToGo
+ self._curSize = curBefore
+
+ self._plotHandles()
+
+ # Compress the motion so that update is quick even on slow machines
+ #
+ # theRootp = root position (either rootx or rooty)
+ def _btnMove(self, event, item):
+ self._rootp = event
+
+ if self._movePending == 0:
+ self._timerId = self.after_idle(
+ lambda s = self, i = item: s._btnMoveCompressed(i))
+ self._movePending = 1
+
+ def destroy(self):
+ if self._timerId is not None:
+ self.after_cancel(self._timerId)
+ self._timerId = None
+ Pmw.MegaWidget.destroy(self)
+
+ def _btnMoveCompressed(self, item):
+ if not self._buttonIsDown:
+ return
+
+ if self['orient'] == 'vertical':
+ p = self._rootp.y_root - self.winfo_rooty()
+ else:
+ p = self._rootp.x_root - self.winfo_rootx()
+
+ if p == self._curSize:
+ self._movePending = 0
+ return
+
+ if p < self._beforeLimit:
+ p = self._beforeLimit
+
+ if p >= self._afterLimit:
+ p = self._afterLimit
+
+ self._calculateChange(item, p)
+ self.update_idletasks()
+ self._movePending = 0
+
+ # Calculate the change in response to mouse motions
+ def _calculateChange(self, item, p):
+
+ if p < self._curSize:
+ self._moveBefore(item, p)
+ elif p > self._curSize:
+ self._moveAfter(item, p)
+
+ self._plotHandles()
+
+ def _moveBefore(self, item, p):
+ n = self._curSize - p
+
+ # Shrink the frames before
+ iterRange = list(self._paneNames[:item])
+ iterRange.reverse()
+ self._iterate(iterRange, self._shrink, n)
+
+ # Adjust the frames after
+ iterRange = self._paneNames[item:]
+ self._iterate(iterRange, self._grow, n)
+
+ self._curSize = p
+
+ def _moveAfter(self, item, p):
+ n = p - self._curSize
+
+ # Shrink the frames after
+ iterRange = self._paneNames[item:]
+ self._iterate(iterRange, self._shrink, n)
+
+ # Adjust the frames before
+ iterRange = list(self._paneNames[:item])
+ iterRange.reverse()
+ self._iterate(iterRange, self._grow, n)
+
+ self._curSize = p
+
+######################################################################
+### File: PmwPromptDialog.py
+# Based on iwidgets2.2.0/promptdialog.itk code.
+
+import Pmw
+
+# A Dialog with an entryfield
+
+class PromptDialog(Pmw.Dialog):
+ def __init__(self, parent = None, **kw):
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('borderx', 20, INITOPT),
+ ('bordery', 20, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.Dialog.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+ aliases = (
+ ('entry', 'entryfield_entry'),
+ ('label', 'entryfield_label'),
+ )
+ self._promptDialogEntry = self.createcomponent('entryfield',
+ aliases, None,
+ Pmw.EntryField, (interior,))
+ self._promptDialogEntry.pack(fill='x', expand=1,
+ padx = self['borderx'], pady = self['bordery'])
+
+ if 'activatecommand' not in kw:
+ # Whenever this dialog is activated, set the focus to the
+ # EntryField's entry widget.
+ tkentry = self.component('entry')
+ self.configure(activatecommand = tkentry.focus_set)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ # Supply aliases to some of the entry component methods.
+ def insertentry(self, index, text):
+ self._promptDialogEntry.insert(index, text)
+
+ def deleteentry(self, first, last=None):
+ self._promptDialogEntry.delete(first, last)
+
+ def indexentry(self, index):
+ return self._promptDialogEntry.index(index)
+
+Pmw.forwardmethods(PromptDialog, Pmw.EntryField, '_promptDialogEntry')
+
+######################################################################
+### File: PmwRadioSelect.py
+import types
+import tkinter
+import Pmw
+import collections
+
+class RadioSelect(Pmw.MegaWidget):
+ # A collection of several buttons. In single mode, only one
+ # button may be selected. In multiple mode, any number of buttons
+ # may be selected.
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('buttontype', 'button', INITOPT),
+ ('command', None, None),
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('orient', 'horizontal', INITOPT),
+ ('padx', 5, INITOPT),
+ ('pady', 5, INITOPT),
+ ('selectmode', 'single', INITOPT),
+ )
+ self.defineoptions(kw, optiondefs, dynamicGroups = ('Button',))
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+ if self['labelpos'] is None:
+ self._radioSelectFrame = self._hull
+ else:
+ self._radioSelectFrame = self.createcomponent('frame',
+ (), None,
+ tkinter.Frame, (interior,))
+ self._radioSelectFrame.grid(column=2, row=2, sticky='nsew')
+ interior.grid_columnconfigure(2, weight=1)
+ interior.grid_rowconfigure(2, weight=1)
+
+ self.createlabel(interior)
+
+ # Initialise instance variables.
+ self._buttonList = []
+ if self['selectmode'] == 'single':
+ self._singleSelect = 1
+ elif self['selectmode'] == 'multiple':
+ self._singleSelect = 0
+ else:
+ raise ValueError('bad selectmode option "' + \
+ self['selectmode'] + '": should be single or multiple')
+
+ if self['buttontype'] == 'button':
+ self.buttonClass = tkinter.Button
+ elif self['buttontype'] == 'radiobutton':
+ self._singleSelect = 1
+ self.var = tkinter.StringVar()
+ self.buttonClass = tkinter.Radiobutton
+ elif self['buttontype'] == 'checkbutton':
+ self._singleSelect = 0
+ self.buttonClass = tkinter.Checkbutton
+ else:
+ raise ValueError('bad buttontype option "' + \
+ self['buttontype'] + \
+ '": should be button, radiobutton or checkbutton')
+
+ if self._singleSelect:
+ self.selection = None
+ else:
+ self.selection = []
+
+ if self['orient'] not in ('horizontal', 'vertical'):
+ raise ValueError('bad orient option ' + repr(self['orient']) + \
+ ': must be either \'horizontal\' or \'vertical\'')
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def getcurselection(self):
+ if self._singleSelect:
+ return self.selection
+ else:
+ return tuple(self.selection)
+
+ def getvalue(self):
+ return self.getcurselection()
+
+ def setvalue(self, textOrList):
+ if self._singleSelect:
+ self.__setSingleValue(textOrList)
+ else:
+ # Multiple selections
+ oldselection = self.selection
+ self.selection = textOrList
+ for button in self._buttonList:
+ if button in oldselection:
+ if button not in self.selection:
+ # button is currently selected but should not be
+ widget = self.component(button)
+ if self['buttontype'] == 'checkbutton':
+ widget.deselect()
+ else: # Button
+ widget.configure(relief='raised')
+ else:
+ if button in self.selection:
+ # button is not currently selected but should be
+ widget = self.component(button)
+ if self['buttontype'] == 'checkbutton':
+ widget.select()
+ else: # Button
+ widget.configure(relief='sunken')
+
+ def numbuttons(self):
+ return len(self._buttonList)
+
+ def index(self, index):
+ # Return the integer index of the button with the given index.
+
+ listLength = len(self._buttonList)
+ if type(index) == int:
+ if index < listLength:
+ return index
+ else:
+ raise ValueError('index "%s" is out of range' % index)
+ elif index is Pmw.END:
+ if listLength > 0:
+ return listLength - 1
+ else:
+ raise ValueError('RadioSelect has no buttons')
+ else:
+ for count in range(listLength):
+ name = self._buttonList[count]
+ if index == name:
+ return count
+ validValues = 'a name, a number or Pmw.END'
+ raise ValueError('bad index "%s": must be %s' % (index, validValues))
+
+ def button(self, buttonIndex):
+ name = self._buttonList[self.index(buttonIndex)]
+ return self.component(name)
+
+ def add(self, componentName, **kw):
+ if componentName in self._buttonList:
+ raise ValueError('button "%s" already exists' % componentName)
+
+ kw['command'] = \
+ lambda self=self, name=componentName: self.invoke(name)
+ if 'text' not in kw:
+ kw['text'] = componentName
+
+ if self['buttontype'] == 'radiobutton':
+ if 'anchor' not in kw:
+ kw['anchor'] = 'w'
+ if 'variable' not in kw:
+ kw['variable'] = self.var
+ if 'value' not in kw:
+ kw['value'] = kw['text']
+ elif self['buttontype'] == 'checkbutton':
+ if 'anchor' not in kw:
+ kw['anchor'] = 'w'
+
+ button = self.createcomponent(*(componentName,
+ (), 'Button',
+ self.buttonClass, (self._radioSelectFrame,)), **kw)
+
+ if self['orient'] == 'horizontal':
+ self._radioSelectFrame.grid_rowconfigure(0, weight=1)
+ col = len(self._buttonList)
+ button.grid(column=col, row=0, padx = self['padx'],
+ pady = self['pady'], sticky='nsew')
+ self._radioSelectFrame.grid_columnconfigure(col, weight=1)
+ else:
+ self._radioSelectFrame.grid_columnconfigure(0, weight=1)
+ row = len(self._buttonList)
+ button.grid(column=0, row=row, padx = self['padx'],
+ pady = self['pady'], sticky='ew')
+ self._radioSelectFrame.grid_rowconfigure(row, weight=1)
+
+ self._buttonList.append(componentName)
+ return button
+
+ def deleteall(self):
+ for name in self._buttonList:
+ self.destroycomponent(name)
+ self._buttonList = []
+ if self._singleSelect:
+ self.selection = None
+ else:
+ self.selection = []
+
+ def __setSingleValue(self, value):
+ self.selection = value
+ if self['buttontype'] == 'radiobutton':
+ widget = self.component(value)
+ widget.select()
+ else: # Button
+ for button in self._buttonList:
+ widget = self.component(button)
+ if button == value:
+ widget.configure(relief='sunken')
+ else:
+ widget.configure(relief='raised')
+
+ def invoke(self, index):
+ index = self.index(index)
+ name = self._buttonList[index]
+
+ if self._singleSelect:
+ self.__setSingleValue(name)
+ command = self['command']
+ if isinstance(command, collections.Callable):
+ return command(name)
+ else:
+ # Multiple selections
+ widget = self.component(name)
+ if name in self.selection:
+ if self['buttontype'] == 'checkbutton':
+ widget.deselect()
+ else:
+ widget.configure(relief='raised')
+ self.selection.remove(name)
+ state = 0
+ else:
+ if self['buttontype'] == 'checkbutton':
+ widget.select()
+ else:
+ widget.configure(relief='sunken')
+ self.selection.append(name)
+ state = 1
+
+ command = self['command']
+ if isinstance(command, collections.Callable):
+ return command(name, state)
+
+######################################################################
+### File: PmwScrolledCanvas.py
+import tkinter
+import Pmw
+
+class ScrolledCanvas(Pmw.MegaWidget):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('borderframe', 0, INITOPT),
+ ('canvasmargin', 0, INITOPT),
+ ('hscrollmode', 'dynamic', self._hscrollMode),
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('scrollmargin', 2, INITOPT),
+ ('usehullsize', 0, INITOPT),
+ ('vscrollmode', 'dynamic', self._vscrollMode),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ self.origInterior = Pmw.MegaWidget.interior(self)
+
+ if self['usehullsize']:
+ self.origInterior.grid_propagate(0)
+
+ if self['borderframe']:
+ # Create a frame widget to act as the border of the canvas.
+ self._borderframe = self.createcomponent('borderframe',
+ (), None,
+ tkinter.Frame, (self.origInterior,),
+ relief = 'sunken',
+ borderwidth = 2,
+ )
+ self._borderframe.grid(row = 2, column = 2, sticky = 'news')
+
+ # Create the canvas widget.
+ self._canvas = self.createcomponent('canvas',
+ (), None,
+ tkinter.Canvas, (self._borderframe,),
+ highlightthickness = 0,
+ borderwidth = 0,
+ )
+ self._canvas.pack(fill = 'both', expand = 1)
+ else:
+ # Create the canvas widget.
+ self._canvas = self.createcomponent('canvas',
+ (), None,
+ tkinter.Canvas, (self.origInterior,),
+ relief = 'sunken',
+ borderwidth = 2,
+ )
+ self._canvas.grid(row = 2, column = 2, sticky = 'news')
+
+ self.origInterior.grid_rowconfigure(2, weight = 1, minsize = 0)
+ self.origInterior.grid_columnconfigure(2, weight = 1, minsize = 0)
+
+ # Create the horizontal scrollbar
+ self._horizScrollbar = self.createcomponent('horizscrollbar',
+ (), 'Scrollbar',
+ tkinter.Scrollbar, (self.origInterior,),
+ orient='horizontal',
+ command=self._canvas.xview
+ )
+
+ # Create the vertical scrollbar
+ self._vertScrollbar = self.createcomponent('vertscrollbar',
+ (), 'Scrollbar',
+ tkinter.Scrollbar, (self.origInterior,),
+ orient='vertical',
+ command=self._canvas.yview
+ )
+
+ self.createlabel(self.origInterior, childCols = 3, childRows = 3)
+
+ # Initialise instance variables.
+ self._horizScrollbarOn = 0
+ self._vertScrollbarOn = 0
+ self.scrollTimer = None
+ self._scrollRecurse = 0
+ self._horizScrollbarNeeded = 0
+ self._vertScrollbarNeeded = 0
+ self.setregionTimer = None
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def destroy(self):
+ if self.scrollTimer is not None:
+ self.after_cancel(self.scrollTimer)
+ self.scrollTimer = None
+ if self.setregionTimer is not None:
+ self.after_cancel(self.setregionTimer)
+ self.setregionTimer = None
+ Pmw.MegaWidget.destroy(self)
+
+ # ======================================================================
+
+ # Public methods.
+
+ def interior(self):
+ return self._canvas
+
+ def resizescrollregion(self):
+ if self.setregionTimer is None:
+ self.setregionTimer = self.after_idle(self._setRegion)
+
+ # ======================================================================
+
+ # Configuration methods.
+
+ def _hscrollMode(self):
+ # The horizontal scroll mode has been configured.
+
+ mode = self['hscrollmode']
+
+ if mode == 'static':
+ if not self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ elif mode == 'dynamic':
+ if self._horizScrollbarNeeded != self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ elif mode == 'none':
+ if self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ else:
+ message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
+ raise ValueError(message)
+
+ self._configureScrollCommands()
+
+ def _vscrollMode(self):
+ # The vertical scroll mode has been configured.
+
+ mode = self['vscrollmode']
+
+ if mode == 'static':
+ if not self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ elif mode == 'dynamic':
+ if self._vertScrollbarNeeded != self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ elif mode == 'none':
+ if self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ else:
+ message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
+ raise ValueError(message)
+
+ self._configureScrollCommands()
+
+ # ======================================================================
+
+ # Private methods.
+
+ def _configureScrollCommands(self):
+ # If both scrollmodes are not dynamic we can save a lot of
+ # time by not having to create an idle job to handle the
+ # scroll commands.
+
+ # Clean up previous scroll commands to prevent memory leak.
+ tclCommandName = str(self._canvas.cget('xscrollcommand'))
+ if tclCommandName != '':
+ self._canvas.deletecommand(tclCommandName)
+ tclCommandName = str(self._canvas.cget('yscrollcommand'))
+ if tclCommandName != '':
+ self._canvas.deletecommand(tclCommandName)
+
+ if self['hscrollmode'] == self['vscrollmode'] == 'dynamic':
+ self._canvas.configure(
+ xscrollcommand=self._scrollBothLater,
+ yscrollcommand=self._scrollBothLater
+ )
+ else:
+ self._canvas.configure(
+ xscrollcommand=self._scrollXNow,
+ yscrollcommand=self._scrollYNow
+ )
+
+ def _scrollXNow(self, first, last):
+ self._horizScrollbar.set(first, last)
+ self._horizScrollbarNeeded = ((first, last) != ('0', '1'))
+
+ if self['hscrollmode'] == 'dynamic':
+ if self._horizScrollbarNeeded != self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+
+ def _scrollYNow(self, first, last):
+ self._vertScrollbar.set(first, last)
+ self._vertScrollbarNeeded = ((first, last) != ('0', '1'))
+
+ if self['vscrollmode'] == 'dynamic':
+ if self._vertScrollbarNeeded != self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+
+ def _scrollBothLater(self, first, last):
+ # Called by the canvas to set the horizontal or vertical
+ # scrollbar when it has scrolled or changed scrollregion.
+
+ if self.scrollTimer is None:
+ self.scrollTimer = self.after_idle(self._scrollBothNow)
+
+ def _scrollBothNow(self):
+ # This performs the function of _scrollXNow and _scrollYNow.
+ # If one is changed, the other should be updated to match.
+ self.scrollTimer = None
+
+ # Call update_idletasks to make sure that the containing frame
+ # has been resized before we attempt to set the scrollbars.
+ # Otherwise the scrollbars may be mapped/unmapped continuously.
+ self._scrollRecurse = self._scrollRecurse + 1
+ self.update_idletasks()
+ self._scrollRecurse = self._scrollRecurse - 1
+ if self._scrollRecurse != 0:
+ return
+
+ xview = self._canvas.xview()
+ yview = self._canvas.yview()
+ self._horizScrollbar.set(xview[0], xview[1])
+ self._vertScrollbar.set(yview[0], yview[1])
+
+ self._horizScrollbarNeeded = (xview != (0.0, 1.0))
+ self._vertScrollbarNeeded = (yview != (0.0, 1.0))
+
+ # If both horizontal and vertical scrollmodes are dynamic and
+ # currently only one scrollbar is mapped and both should be
+ # toggled, then unmap the mapped scrollbar. This prevents a
+ # continuous mapping and unmapping of the scrollbars.
+ if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and
+ self._horizScrollbarNeeded != self._horizScrollbarOn and
+ self._vertScrollbarNeeded != self._vertScrollbarOn and
+ self._vertScrollbarOn != self._horizScrollbarOn):
+ if self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ else:
+ self._toggleVertScrollbar()
+ return
+
+ if self['hscrollmode'] == 'dynamic':
+ if self._horizScrollbarNeeded != self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+
+ if self['vscrollmode'] == 'dynamic':
+ if self._vertScrollbarNeeded != self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+
+ def _toggleHorizScrollbar(self):
+
+ self._horizScrollbarOn = not self._horizScrollbarOn
+
+ interior = self.origInterior
+ if self._horizScrollbarOn:
+ self._horizScrollbar.grid(row = 4, column = 2, sticky = 'news')
+ interior.grid_rowconfigure(3, minsize = self['scrollmargin'])
+ else:
+ self._horizScrollbar.grid_forget()
+ interior.grid_rowconfigure(3, minsize = 0)
+
+ def _toggleVertScrollbar(self):
+
+ self._vertScrollbarOn = not self._vertScrollbarOn
+
+ interior = self.origInterior
+ if self._vertScrollbarOn:
+ self._vertScrollbar.grid(row = 2, column = 4, sticky = 'news')
+ interior.grid_columnconfigure(3, minsize = self['scrollmargin'])
+ else:
+ self._vertScrollbar.grid_forget()
+ interior.grid_columnconfigure(3, minsize = 0)
+
+ def _setRegion(self):
+ self.setregionTimer = None
+
+ region = self._canvas.bbox('all')
+ if region is not None:
+ canvasmargin = self['canvasmargin']
+ region = (region[0] - canvasmargin, region[1] - canvasmargin,
+ region[2] + canvasmargin, region[3] + canvasmargin)
+ self._canvas.configure(scrollregion = region)
+
+ # Need to explicitly forward this to override the stupid
+ # (grid_)bbox method inherited from Tkinter.Frame.Grid.
+ def bbox(self, *args):
+ return self._canvas.bbox(*args)
+
+Pmw.forwardmethods(ScrolledCanvas, tkinter.Canvas, '_canvas')
+
+######################################################################
+### File: PmwScrolledField.py
+import tkinter
+import Pmw
+
+class ScrolledField(Pmw.MegaWidget):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('sticky', 'ew', INITOPT),
+ ('text', '', self._text),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+ self._scrolledFieldEntry = self.createcomponent('entry',
+ (), None,
+ tkinter.Entry, (interior,))
+
+ # Can't always use 'disabled', since this greys out text in Tk 8.4.2
+ try:
+ self._scrolledFieldEntry.configure(state = 'readonly')
+ except tkinter.TclError:
+ self._scrolledFieldEntry.configure(state = 'disabled')
+
+ self._scrolledFieldEntry.grid(column=2, row=2, sticky=self['sticky'])
+ interior.grid_columnconfigure(2, weight=1)
+ interior.grid_rowconfigure(2, weight=1)
+
+ self.createlabel(interior)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def _text(self):
+ text = self['text']
+ self._scrolledFieldEntry.configure(state = 'normal')
+ self._scrolledFieldEntry.delete(0, 'end')
+ self._scrolledFieldEntry.insert('end', text)
+
+ # Can't always use 'disabled', since this greys out text in Tk 8.4.2
+ try:
+ self._scrolledFieldEntry.configure(state = 'readonly')
+ except tkinter.TclError:
+ self._scrolledFieldEntry.configure(state = 'disabled')
+
+Pmw.forwardmethods(ScrolledField, tkinter.Entry, '_scrolledFieldEntry')
+
+######################################################################
+### File: PmwScrolledFrame.py
+import string
+import types
+import tkinter
+import Pmw
+
+class ScrolledFrame(Pmw.MegaWidget):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('borderframe', 1, INITOPT),
+ ('horizflex', 'fixed', self._horizflex),
+ ('horizfraction', 0.05, INITOPT),
+ ('hscrollmode', 'dynamic', self._hscrollMode),
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('scrollmargin', 2, INITOPT),
+ ('usehullsize', 0, INITOPT),
+ ('vertflex', 'fixed', self._vertflex),
+ ('vertfraction', 0.05, INITOPT),
+ ('vscrollmode', 'dynamic', self._vscrollMode),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ self.origInterior = Pmw.MegaWidget.interior(self)
+
+ if self['usehullsize']:
+ self.origInterior.grid_propagate(0)
+
+ if self['borderframe']:
+ # Create a frame widget to act as the border of the clipper.
+ self._borderframe = self.createcomponent('borderframe',
+ (), None,
+ tkinter.Frame, (self.origInterior,),
+ relief = 'sunken',
+ borderwidth = 2,
+ )
+ self._borderframe.grid(row = 2, column = 2, sticky = 'news')
+
+ # Create the clipping window.
+ self._clipper = self.createcomponent('clipper',
+ (), None,
+ tkinter.Frame, (self._borderframe,),
+ width = 400,
+ height = 300,
+ highlightthickness = 0,
+ borderwidth = 0,
+ )
+ self._clipper.pack(fill = 'both', expand = 1)
+ else:
+ # Create the clipping window.
+ self._clipper = self.createcomponent('clipper',
+ (), None,
+ tkinter.Frame, (self.origInterior,),
+ width = 400,
+ height = 300,
+ relief = 'sunken',
+ borderwidth = 2,
+ )
+ self._clipper.grid(row = 2, column = 2, sticky = 'news')
+
+ self.origInterior.grid_rowconfigure(2, weight = 1, minsize = 0)
+ self.origInterior.grid_columnconfigure(2, weight = 1, minsize = 0)
+
+ # Create the horizontal scrollbar
+ self._horizScrollbar = self.createcomponent('horizscrollbar',
+ (), 'Scrollbar',
+ tkinter.Scrollbar, (self.origInterior,),
+ orient='horizontal',
+ command=self.xview
+ )
+
+ # Create the vertical scrollbar
+ self._vertScrollbar = self.createcomponent('vertscrollbar',
+ (), 'Scrollbar',
+ tkinter.Scrollbar, (self.origInterior,),
+ orient='vertical',
+ command=self.yview
+ )
+
+ self.createlabel(self.origInterior, childCols = 3, childRows = 3)
+
+ # Initialise instance variables.
+ self._horizScrollbarOn = 0
+ self._vertScrollbarOn = 0
+ self.scrollTimer = None
+ self._scrollRecurse = 0
+ self._horizScrollbarNeeded = 0
+ self._vertScrollbarNeeded = 0
+ self.startX = 0
+ self.startY = 0
+ self._flexoptions = ('fixed', 'expand', 'shrink', 'elastic')
+
+ # Create a frame in the clipper to contain the widgets to be
+ # scrolled.
+ self._frame = self.createcomponent('frame',
+ (), None,
+ tkinter.Frame, (self._clipper,)
+ )
+
+ # Whenever the clipping window or scrolled frame change size,
+ # update the scrollbars.
+ self._frame.bind('<Configure>', self._reposition)
+ self._clipper.bind('<Configure>', self._reposition)
+
+ # Work around a bug in Tk where the value returned by the
+ # scrollbar get() method is (0.0, 0.0, 0.0, 0.0) rather than
+ # the expected 2-tuple. This occurs if xview() is called soon
+ # after the Pmw.ScrolledFrame has been created.
+ self._horizScrollbar.set(0.0, 1.0)
+ self._vertScrollbar.set(0.0, 1.0)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def destroy(self):
+ if self.scrollTimer is not None:
+ self.after_cancel(self.scrollTimer)
+ self.scrollTimer = None
+ Pmw.MegaWidget.destroy(self)
+
+ # ======================================================================
+
+ # Public methods.
+
+ def interior(self):
+ return self._frame
+
+ # Set timer to call real reposition method, so that it is not
+ # called multiple times when many things are reconfigured at the
+ # same time.
+ def reposition(self):
+ if self.scrollTimer is None:
+ self.scrollTimer = self.after_idle(self._scrollBothNow)
+
+ # Called when the user clicks in the horizontal scrollbar.
+ # Calculates new position of frame then calls reposition() to
+ # update the frame and the scrollbar.
+ def xview(self, mode = None, value = None, units = None):
+
+ if type(value) is str:
+ value = float(value)
+ if mode is None:
+ return self._horizScrollbar.get()
+ elif mode == 'moveto':
+ frameWidth = self._frame.winfo_reqwidth()
+ self.startX = value * float(frameWidth)
+ else: # mode == 'scroll'
+ clipperWidth = self._clipper.winfo_width()
+ if units == 'units':
+ jump = int(clipperWidth * self['horizfraction'])
+ else:
+ jump = clipperWidth
+ self.startX = self.startX + value * jump
+
+ self.reposition()
+
+ # Called when the user clicks in the vertical scrollbar.
+ # Calculates new position of frame then calls reposition() to
+ # update the frame and the scrollbar.
+ def yview(self, mode = None, value = None, units = None):
+
+ if type(value) is str:
+ value = float(value)
+ if mode is None:
+ return self._vertScrollbar.get()
+ elif mode == 'moveto':
+ frameHeight = self._frame.winfo_reqheight()
+ self.startY = value * float(frameHeight)
+ else: # mode == 'scroll'
+ clipperHeight = self._clipper.winfo_height()
+ if units == 'units':
+ jump = int(clipperHeight * self['vertfraction'])
+ else:
+ jump = clipperHeight
+ self.startY = self.startY + value * jump
+
+ self.reposition()
+
+ # ======================================================================
+
+ # Configuration methods.
+
+ def _hscrollMode(self):
+ # The horizontal scroll mode has been configured.
+
+ mode = self['hscrollmode']
+
+ if mode == 'static':
+ if not self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ elif mode == 'dynamic':
+ if self._horizScrollbarNeeded != self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ elif mode == 'none':
+ if self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ else:
+ message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
+ raise ValueError(message)
+
+ def _vscrollMode(self):
+ # The vertical scroll mode has been configured.
+
+ mode = self['vscrollmode']
+
+ if mode == 'static':
+ if not self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ elif mode == 'dynamic':
+ if self._vertScrollbarNeeded != self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ elif mode == 'none':
+ if self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ else:
+ message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
+ raise ValueError(message)
+
+ def _horizflex(self):
+ # The horizontal flex mode has been configured.
+
+ flex = self['horizflex']
+
+ if flex not in self._flexoptions:
+ message = 'bad horizflex option "%s": should be one of %s' % \
+ (flex, str(self._flexoptions))
+ raise ValueError(message)
+
+ self.reposition()
+
+ def _vertflex(self):
+ # The vertical flex mode has been configured.
+
+ flex = self['vertflex']
+
+ if flex not in self._flexoptions:
+ message = 'bad vertflex option "%s": should be one of %s' % \
+ (flex, str(self._flexoptions))
+ raise ValueError(message)
+
+ self.reposition()
+
+ # ======================================================================
+
+ # Private methods.
+
+ def _reposition(self, event):
+ self.reposition()
+
+ def _getxview(self):
+
+ # Horizontal dimension.
+ clipperWidth = self._clipper.winfo_width()
+ frameWidth = self._frame.winfo_reqwidth()
+ if frameWidth <= clipperWidth:
+ # The scrolled frame is smaller than the clipping window.
+
+ self.startX = 0
+ endScrollX = 1.0
+
+ if self['horizflex'] in ('expand', 'elastic'):
+ relwidth = 1
+ else:
+ relwidth = ''
+ else:
+ # The scrolled frame is larger than the clipping window.
+
+ if self['horizflex'] in ('shrink', 'elastic'):
+ self.startX = 0
+ endScrollX = 1.0
+ relwidth = 1
+ else:
+ if self.startX + clipperWidth > frameWidth:
+ self.startX = frameWidth - clipperWidth
+ endScrollX = 1.0
+ else:
+ if self.startX < 0:
+ self.startX = 0
+ endScrollX = (self.startX + clipperWidth) / float(frameWidth)
+ relwidth = ''
+
+ # Position frame relative to clipper.
+ self._frame.place(x = -self.startX, relwidth = relwidth)
+ return (self.startX / float(frameWidth), endScrollX)
+
+ def _getyview(self):
+
+ # Vertical dimension.
+ clipperHeight = self._clipper.winfo_height()
+ frameHeight = self._frame.winfo_reqheight()
+ if frameHeight <= clipperHeight:
+ # The scrolled frame is smaller than the clipping window.
+
+ self.startY = 0
+ endScrollY = 1.0
+
+ if self['vertflex'] in ('expand', 'elastic'):
+ relheight = 1
+ else:
+ relheight = ''
+ else:
+ # The scrolled frame is larger than the clipping window.
+
+ if self['vertflex'] in ('shrink', 'elastic'):
+ self.startY = 0
+ endScrollY = 1.0
+ relheight = 1
+ else:
+ if self.startY + clipperHeight > frameHeight:
+ self.startY = frameHeight - clipperHeight
+ endScrollY = 1.0
+ else:
+ if self.startY < 0:
+ self.startY = 0
+ endScrollY = (self.startY + clipperHeight) / float(frameHeight)
+ relheight = ''
+
+ # Position frame relative to clipper.
+ self._frame.place(y = -self.startY, relheight = relheight)
+ return (self.startY / float(frameHeight), endScrollY)
+
+ # According to the relative geometries of the frame and the
+ # clipper, reposition the frame within the clipper and reset the
+ # scrollbars.
+ def _scrollBothNow(self):
+ self.scrollTimer = None
+
+ # Call update_idletasks to make sure that the containing frame
+ # has been resized before we attempt to set the scrollbars.
+ # Otherwise the scrollbars may be mapped/unmapped continuously.
+ self._scrollRecurse = self._scrollRecurse + 1
+ self.update_idletasks()
+ self._scrollRecurse = self._scrollRecurse - 1
+ if self._scrollRecurse != 0:
+ return
+
+ xview = self._getxview()
+ yview = self._getyview()
+ self._horizScrollbar.set(xview[0], xview[1])
+ self._vertScrollbar.set(yview[0], yview[1])
+
+
+ self._horizScrollbarNeeded = (xview != (0.0, 1.0))
+ self._vertScrollbarNeeded = (yview != (0.0, 1.0))
+
+ # If both horizontal and vertical scrollmodes are dynamic and
+ # currently only one scrollbar is mapped and both should be
+ # toggled, then unmap the mapped scrollbar. This prevents a
+ # continuous mapping and unmapping of the scrollbars.
+ if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and
+ self._horizScrollbarNeeded != self._horizScrollbarOn and
+ self._vertScrollbarNeeded != self._vertScrollbarOn and
+ self._vertScrollbarOn != self._horizScrollbarOn):
+ if self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ else:
+ self._toggleVertScrollbar()
+ return
+
+ if self['hscrollmode'] == 'dynamic':
+ if self._horizScrollbarNeeded != self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+
+ if self['vscrollmode'] == 'dynamic':
+ if self._vertScrollbarNeeded != self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+
+ def _toggleHorizScrollbar(self):
+
+ self._horizScrollbarOn = not self._horizScrollbarOn
+
+ interior = self.origInterior
+ if self._horizScrollbarOn:
+ self._horizScrollbar.grid(row = 4, column = 2, sticky = 'news')
+ interior.grid_rowconfigure(3, minsize = self['scrollmargin'])
+ else:
+ self._horizScrollbar.grid_forget()
+ interior.grid_rowconfigure(3, minsize = 0)
+
+ def _toggleVertScrollbar(self):
+
+ self._vertScrollbarOn = not self._vertScrollbarOn
+
+ interior = self.origInterior
+ if self._vertScrollbarOn:
+ self._vertScrollbar.grid(row = 2, column = 4, sticky = 'news')
+ interior.grid_columnconfigure(3, minsize = self['scrollmargin'])
+ else:
+ self._vertScrollbar.grid_forget()
+ interior.grid_columnconfigure(3, minsize = 0)
+
+######################################################################
+### File: PmwScrolledListBox.py
+# Based on iwidgets2.2.0/scrolledlistbox.itk code.
+
+import types
+import tkinter
+import Pmw
+import collections
+
+class ScrolledListBox(Pmw.MegaWidget):
+ _classBindingsDefinedFor = 0
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('dblclickcommand', None, None),
+ ('hscrollmode', 'dynamic', self._hscrollMode),
+ ('items', (), INITOPT),
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('scrollmargin', 2, INITOPT),
+ ('selectioncommand', None, None),
+ ('usehullsize', 0, INITOPT),
+ ('vscrollmode', 'dynamic', self._vscrollMode),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+
+ if self['usehullsize']:
+ interior.grid_propagate(0)
+
+ # Create the listbox widget.
+ self._listbox = self.createcomponent('listbox',
+ (), None,
+ tkinter.Listbox, (interior,))
+ self._listbox.grid(row = 2, column = 2, sticky = 'news')
+ interior.grid_rowconfigure(2, weight = 1, minsize = 0)
+ interior.grid_columnconfigure(2, weight = 1, minsize = 0)
+
+ # Create the horizontal scrollbar
+ self._horizScrollbar = self.createcomponent('horizscrollbar',
+ (), 'Scrollbar',
+ tkinter.Scrollbar, (interior,),
+ orient='horizontal',
+ command=self._listbox.xview
+ )
+
+ # Create the vertical scrollbar
+ self._vertScrollbar = self.createcomponent('vertscrollbar',
+ (), 'Scrollbar',
+ tkinter.Scrollbar, (interior,),
+ orient='vertical',
+ command=self._listbox.yview
+ )
+
+ self.createlabel(interior, childCols = 3, childRows = 3)
+
+ # Add the items specified by the initialisation option.
+ items = self['items']
+ if type(items) != tuple:
+ items = tuple(items)
+ if len(items) > 0:
+ self._listbox.insert(*('end',) + items)
+
+ _registerScrolledList(self._listbox, self)
+
+ # Establish the special class bindings if not already done.
+ # Also create bindings if the Tkinter default interpreter has
+ # changed. Use Tkinter._default_root to create class
+ # bindings, so that a reference to root is created by
+ # bind_class rather than a reference to self, which would
+ # prevent object cleanup.
+ theTag = 'ScrolledListBoxTag'
+ if ScrolledListBox._classBindingsDefinedFor != tkinter._default_root:
+ root = tkinter._default_root
+
+ def doubleEvent(event):
+ _handleEvent(event, 'double')
+ def keyEvent(event):
+ _handleEvent(event, 'key')
+ def releaseEvent(event):
+ _handleEvent(event, 'release')
+
+ # Bind space and return keys and button 1 to the selectioncommand.
+ root.bind_class(theTag, '<Key-space>', keyEvent)
+ root.bind_class(theTag, '<Key-Return>', keyEvent)
+ root.bind_class(theTag, '<ButtonRelease-1>', releaseEvent)
+
+ # Bind double button 1 click to the dblclickcommand.
+ root.bind_class(theTag, '<Double-ButtonRelease-1>', doubleEvent)
+
+ ScrolledListBox._classBindingsDefinedFor = root
+
+ bindtags = self._listbox.bindtags()
+ self._listbox.bindtags(bindtags + (theTag,))
+
+ # Initialise instance variables.
+ self._horizScrollbarOn = 0
+ self._vertScrollbarOn = 0
+ self.scrollTimer = None
+ self._scrollRecurse = 0
+ self._horizScrollbarNeeded = 0
+ self._vertScrollbarNeeded = 0
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def destroy(self):
+ if self.scrollTimer is not None:
+ self.after_cancel(self.scrollTimer)
+ self.scrollTimer = None
+ _deregisterScrolledList(self._listbox)
+ Pmw.MegaWidget.destroy(self)
+
+ # ======================================================================
+
+ # Public methods.
+
+ def clear(self):
+ self.setlist(())
+
+ def getcurselection(self):
+ rtn = []
+ for sel in self.curselection():
+ rtn.append(self._listbox.get(sel))
+ return tuple(rtn)
+
+ def getvalue(self):
+ return self.getcurselection()
+
+ def setvalue(self, textOrList):
+ self._listbox.selection_clear(0, 'end')
+ listitems = list(self._listbox.get(0, 'end'))
+ if type(textOrList) is str:
+ if textOrList in listitems:
+ self._listbox.selection_set(listitems.index(textOrList))
+ else:
+ raise ValueError('no such item "%s"' % textOrList)
+ else:
+ for item in textOrList:
+ if item in listitems:
+ self._listbox.selection_set(listitems.index(item))
+ else:
+ raise ValueError('no such item "%s"' % item)
+
+ def setlist(self, items):
+ self._listbox.delete(0, 'end')
+ if len(items) > 0:
+ if type(items) != tuple:
+ items = tuple(items)
+ self._listbox.insert(*(0,) + items)
+
+ # Override Tkinter.Listbox get method, so that if it is called with
+ # no arguments, return all list elements (consistent with other widgets).
+ def get(self, first=None, last=None):
+ if first is None:
+ return self._listbox.get(0, 'end')
+ else:
+ return self._listbox.get(first, last)
+
+ # ======================================================================
+
+ # Configuration methods.
+
+ def _hscrollMode(self):
+ # The horizontal scroll mode has been configured.
+
+ mode = self['hscrollmode']
+
+ if mode == 'static':
+ if not self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ elif mode == 'dynamic':
+ if self._horizScrollbarNeeded != self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ elif mode == 'none':
+ if self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ else:
+ message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
+ raise ValueError(message)
+
+ self._configureScrollCommands()
+
+ def _vscrollMode(self):
+ # The vertical scroll mode has been configured.
+
+ mode = self['vscrollmode']
+
+ if mode == 'static':
+ if not self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ elif mode == 'dynamic':
+ if self._vertScrollbarNeeded != self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ elif mode == 'none':
+ if self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ else:
+ message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
+ raise ValueError(message)
+
+ self._configureScrollCommands()
+
+ # ======================================================================
+
+ # Private methods.
+
+ def _configureScrollCommands(self):
+ # If both scrollmodes are not dynamic we can save a lot of
+ # time by not having to create an idle job to handle the
+ # scroll commands.
+
+ # Clean up previous scroll commands to prevent memory leak.
+ tclCommandName = str(self._listbox.cget('xscrollcommand'))
+ if tclCommandName != '':
+ self._listbox.deletecommand(tclCommandName)
+ tclCommandName = str(self._listbox.cget('yscrollcommand'))
+ if tclCommandName != '':
+ self._listbox.deletecommand(tclCommandName)
+
+ if self['hscrollmode'] == self['vscrollmode'] == 'dynamic':
+ self._listbox.configure(
+ xscrollcommand=self._scrollBothLater,
+ yscrollcommand=self._scrollBothLater
+ )
+ else:
+ self._listbox.configure(
+ xscrollcommand=self._scrollXNow,
+ yscrollcommand=self._scrollYNow
+ )
+
+ def _scrollXNow(self, first, last):
+ self._horizScrollbar.set(first, last)
+ self._horizScrollbarNeeded = ((first, last) != ('0', '1'))
+
+ if self['hscrollmode'] == 'dynamic':
+ if self._horizScrollbarNeeded != self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+
+ def _scrollYNow(self, first, last):
+ self._vertScrollbar.set(first, last)
+ self._vertScrollbarNeeded = ((first, last) != ('0', '1'))
+
+ if self['vscrollmode'] == 'dynamic':
+ if self._vertScrollbarNeeded != self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+
+ def _scrollBothLater(self, first, last):
+ # Called by the listbox to set the horizontal or vertical
+ # scrollbar when it has scrolled or changed size or contents.
+
+ if self.scrollTimer is None:
+ self.scrollTimer = self.after_idle(self._scrollBothNow)
+
+ def _scrollBothNow(self):
+ # This performs the function of _scrollXNow and _scrollYNow.
+ # If one is changed, the other should be updated to match.
+ self.scrollTimer = None
+
+ # Call update_idletasks to make sure that the containing frame
+ # has been resized before we attempt to set the scrollbars.
+ # Otherwise the scrollbars may be mapped/unmapped continuously.
+ self._scrollRecurse = self._scrollRecurse + 1
+ self.update_idletasks()
+ self._scrollRecurse = self._scrollRecurse - 1
+ if self._scrollRecurse != 0:
+ return
+
+ xview = self._listbox.xview()
+ yview = self._listbox.yview()
+ self._horizScrollbar.set(xview[0], xview[1])
+ self._vertScrollbar.set(yview[0], yview[1])
+
+ self._horizScrollbarNeeded = (xview != (0.0, 1.0))
+ self._vertScrollbarNeeded = (yview != (0.0, 1.0))
+
+ # If both horizontal and vertical scrollmodes are dynamic and
+ # currently only one scrollbar is mapped and both should be
+ # toggled, then unmap the mapped scrollbar. This prevents a
+ # continuous mapping and unmapping of the scrollbars.
+ if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and
+ self._horizScrollbarNeeded != self._horizScrollbarOn and
+ self._vertScrollbarNeeded != self._vertScrollbarOn and
+ self._vertScrollbarOn != self._horizScrollbarOn):
+ if self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ else:
+ self._toggleVertScrollbar()
+ return
+
+ if self['hscrollmode'] == 'dynamic':
+ if self._horizScrollbarNeeded != self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+
+ if self['vscrollmode'] == 'dynamic':
+ if self._vertScrollbarNeeded != self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+
+ def _toggleHorizScrollbar(self):
+
+ self._horizScrollbarOn = not self._horizScrollbarOn
+
+ interior = self.interior()
+ if self._horizScrollbarOn:
+ self._horizScrollbar.grid(row = 4, column = 2, sticky = 'news')
+ interior.grid_rowconfigure(3, minsize = self['scrollmargin'])
+ else:
+ self._horizScrollbar.grid_forget()
+ interior.grid_rowconfigure(3, minsize = 0)
+
+ def _toggleVertScrollbar(self):
+
+ self._vertScrollbarOn = not self._vertScrollbarOn
+
+ interior = self.interior()
+ if self._vertScrollbarOn:
+ self._vertScrollbar.grid(row = 2, column = 4, sticky = 'news')
+ interior.grid_columnconfigure(3, minsize = self['scrollmargin'])
+ else:
+ self._vertScrollbar.grid_forget()
+ interior.grid_columnconfigure(3, minsize = 0)
+
+ def _handleEvent(self, event, eventType):
+ if eventType == 'double':
+ command = self['dblclickcommand']
+ elif eventType == 'key':
+ command = self['selectioncommand']
+ else: #eventType == 'release'
+ # Do not execute the command if the mouse was released
+ # outside the listbox.
+ if (event.x < 0 or self._listbox.winfo_width() <= event.x or
+ event.y < 0 or self._listbox.winfo_height() <= event.y):
+ return
+
+ command = self['selectioncommand']
+
+ if isinstance(command, collections.Callable):
+ command()
+
+ # Need to explicitly forward this to override the stupid
+ # (grid_)size method inherited from Tkinter.Frame.Grid.
+ def size(self):
+ return self._listbox.size()
+
+ # Need to explicitly forward this to override the stupid
+ # (grid_)bbox method inherited from Tkinter.Frame.Grid.
+ def bbox(self, index):
+ return self._listbox.bbox(index)
+
+Pmw.forwardmethods(ScrolledListBox, tkinter.Listbox, '_listbox')
+
+# ======================================================================
+
+_listboxCache = {}
+
+def _registerScrolledList(listbox, scrolledList):
+ # Register an ScrolledList widget for a Listbox widget
+
+ _listboxCache[listbox] = scrolledList
+
+def _deregisterScrolledList(listbox):
+ # Deregister a Listbox widget
+ del _listboxCache[listbox]
+
+def _handleEvent(event, eventType):
+ # Forward events for a Listbox to it's ScrolledListBox
+
+ # A binding earlier in the bindtags list may have destroyed the
+ # megawidget, so need to check.
+ if event.widget in _listboxCache:
+ _listboxCache[event.widget]._handleEvent(event, eventType)
+
+######################################################################
+### File: PmwScrolledText.py
+# Based on iwidgets2.2.0/scrolledtext.itk code.
+
+import tkinter
+import Pmw
+
+class ScrolledText(Pmw.MegaWidget):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('borderframe', 0, INITOPT),
+ ('columnheader', 0, INITOPT),
+ ('hscrollmode', 'dynamic', self._hscrollMode),
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('rowcolumnheader',0, INITOPT),
+ ('rowheader', 0, INITOPT),
+ ('scrollmargin', 2, INITOPT),
+ ('usehullsize', 0, INITOPT),
+ ('vscrollmode', 'dynamic', self._vscrollMode),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+
+ if self['usehullsize']:
+ interior.grid_propagate(0)
+
+ if self['borderframe']:
+ # Create a frame widget to act as the border of the text
+ # widget. Later, pack the text widget so that it fills
+ # the frame. This avoids a problem in Tk, where window
+ # items in a text widget may overlap the border of the
+ # text widget.
+ self._borderframe = self.createcomponent('borderframe',
+ (), None,
+ tkinter.Frame, (interior,),
+ relief = 'sunken',
+ borderwidth = 2,
+ )
+ self._borderframe.grid(row = 4, column = 4, sticky = 'news')
+
+ # Create the text widget.
+ self._textbox = self.createcomponent('text',
+ (), None,
+ tkinter.Text, (self._borderframe,),
+ highlightthickness = 0,
+ borderwidth = 0,
+ )
+ self._textbox.pack(fill = 'both', expand = 1)
+
+ bw = self._borderframe.cget('borderwidth'),
+ ht = self._borderframe.cget('highlightthickness'),
+ else:
+ # Create the text widget.
+ self._textbox = self.createcomponent('text',
+ (), None,
+ tkinter.Text, (interior,),
+ )
+ self._textbox.grid(row = 4, column = 4, sticky = 'news')
+
+ bw = self._textbox.cget('borderwidth'),
+ ht = self._textbox.cget('highlightthickness'),
+
+ # Create the header text widgets
+ if self['columnheader']:
+ self._columnheader = self.createcomponent('columnheader',
+ (), 'Header',
+ tkinter.Text, (interior,),
+ height=1,
+ wrap='none',
+ borderwidth = bw,
+ highlightthickness = ht,
+ )
+ self._columnheader.grid(row = 2, column = 4, sticky = 'ew')
+ self._columnheader.configure(
+ xscrollcommand = self._columnheaderscrolled)
+
+ if self['rowheader']:
+ self._rowheader = self.createcomponent('rowheader',
+ (), 'Header',
+ tkinter.Text, (interior,),
+ wrap='none',
+ borderwidth = bw,
+ highlightthickness = ht,
+ )
+ self._rowheader.grid(row = 4, column = 2, sticky = 'ns')
+ self._rowheader.configure(
+ yscrollcommand = self._rowheaderscrolled)
+
+ if self['rowcolumnheader']:
+ self._rowcolumnheader = self.createcomponent('rowcolumnheader',
+ (), 'Header',
+ tkinter.Text, (interior,),
+ height=1,
+ wrap='none',
+ borderwidth = bw,
+ highlightthickness = ht,
+ )
+ self._rowcolumnheader.grid(row = 2, column = 2, sticky = 'nsew')
+
+ interior.grid_rowconfigure(4, weight = 1, minsize = 0)
+ interior.grid_columnconfigure(4, weight = 1, minsize = 0)
+
+ # Create the horizontal scrollbar
+ self._horizScrollbar = self.createcomponent('horizscrollbar',
+ (), 'Scrollbar',
+ tkinter.Scrollbar, (interior,),
+ orient='horizontal',
+ command=self._textbox.xview
+ )
+
+ # Create the vertical scrollbar
+ self._vertScrollbar = self.createcomponent('vertscrollbar',
+ (), 'Scrollbar',
+ tkinter.Scrollbar, (interior,),
+ orient='vertical',
+ command=self._textbox.yview
+ )
+
+ self.createlabel(interior, childCols = 5, childRows = 5)
+
+ # Initialise instance variables.
+ self._horizScrollbarOn = 0
+ self._vertScrollbarOn = 0
+ self.scrollTimer = None
+ self._scrollRecurse = 0
+ self._horizScrollbarNeeded = 0
+ self._vertScrollbarNeeded = 0
+ self._textWidth = None
+
+ # These four variables avoid an infinite loop caused by the
+ # row or column header's scrollcommand causing the main text
+ # widget's scrollcommand to be called and vice versa.
+ self._textboxLastX = None
+ self._textboxLastY = None
+ self._columnheaderLastX = None
+ self._rowheaderLastY = None
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def destroy(self):
+ if self.scrollTimer is not None:
+ self.after_cancel(self.scrollTimer)
+ self.scrollTimer = None
+ Pmw.MegaWidget.destroy(self)
+
+ # ======================================================================
+
+ # Public methods.
+
+ def clear(self):
+ self.settext('')
+
+ def importfile(self, fileName, where = 'end'):
+ file = open(fileName, 'r')
+ self._textbox.insert(where, file.read())
+ file.close()
+
+ def exportfile(self, fileName):
+ file = open(fileName, 'w')
+ file.write(self._textbox.get('1.0', 'end'))
+ file.close()
+
+ def settext(self, text):
+ disabled = (str(self._textbox.cget('state')) == 'disabled')
+ if disabled:
+ self._textbox.configure(state='normal')
+ self._textbox.delete('0.0', 'end')
+ self._textbox.insert('end', text)
+ if disabled:
+ self._textbox.configure(state='disabled')
+
+ # Override Tkinter.Text get method, so that if it is called with
+ # no arguments, return all text (consistent with other widgets).
+ def get(self, first=None, last=None):
+ if first is None:
+ return self._textbox.get('1.0', 'end')
+ else:
+ return self._textbox.get(first, last)
+
+ def getvalue(self):
+ return self.get()
+
+ def setvalue(self, text):
+ return self.settext(text)
+
+ def appendtext(self, text):
+ oldTop, oldBottom = self._textbox.yview()
+
+ disabled = (str(self._textbox.cget('state')) == 'disabled')
+ if disabled:
+ self._textbox.configure(state='normal')
+ self._textbox.insert('end', text)
+ if disabled:
+ self._textbox.configure(state='disabled')
+
+ if oldBottom == 1.0:
+ self._textbox.yview('moveto', 1.0)
+
+ # ======================================================================
+
+ # Configuration methods.
+
+ def _hscrollMode(self):
+ # The horizontal scroll mode has been configured.
+
+ mode = self['hscrollmode']
+
+ if mode == 'static':
+ if not self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ elif mode == 'dynamic':
+ if self._horizScrollbarNeeded != self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ elif mode == 'none':
+ if self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ else:
+ message = 'bad hscrollmode option "%s": should be static, dynamic, or none' % mode
+ raise ValueError(message)
+
+ self._configureScrollCommands()
+
+ def _vscrollMode(self):
+ # The vertical scroll mode has been configured.
+
+ mode = self['vscrollmode']
+
+ if mode == 'static':
+ if not self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ elif mode == 'dynamic':
+ if self._vertScrollbarNeeded != self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ elif mode == 'none':
+ if self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+ else:
+ message = 'bad vscrollmode option "%s": should be static, dynamic, or none' % mode
+ raise ValueError(message)
+
+ self._configureScrollCommands()
+
+ # ======================================================================
+
+ # Private methods.
+
+ def _configureScrollCommands(self):
+ # If both scrollmodes are not dynamic we can save a lot of
+ # time by not having to create an idle job to handle the
+ # scroll commands.
+
+ # Clean up previous scroll commands to prevent memory leak.
+ tclCommandName = str(self._textbox.cget('xscrollcommand'))
+ if tclCommandName != '':
+ self._textbox.deletecommand(tclCommandName)
+ tclCommandName = str(self._textbox.cget('yscrollcommand'))
+ if tclCommandName != '':
+ self._textbox.deletecommand(tclCommandName)
+
+ if self['hscrollmode'] == self['vscrollmode'] == 'dynamic':
+ self._textbox.configure(
+ xscrollcommand=self._scrollBothLater,
+ yscrollcommand=self._scrollBothLater
+ )
+ else:
+ self._textbox.configure(
+ xscrollcommand=self._scrollXNow,
+ yscrollcommand=self._scrollYNow
+ )
+
+ def _scrollXNow(self, first, last):
+ self._horizScrollbar.set(first, last)
+ self._horizScrollbarNeeded = ((first, last) != ('0', '1'))
+
+ # This code is the same as in _scrollBothNow. Keep it that way.
+ if self['hscrollmode'] == 'dynamic':
+ currentWidth = self._textbox.winfo_width()
+ if self._horizScrollbarNeeded != self._horizScrollbarOn:
+ if self._horizScrollbarNeeded or \
+ self._textWidth != currentWidth:
+ self._toggleHorizScrollbar()
+ self._textWidth = currentWidth
+
+ if self['columnheader']:
+ if self._columnheaderLastX != first:
+ self._columnheaderLastX = first
+ self._columnheader.xview('moveto', first)
+
+ def _scrollYNow(self, first, last):
+ if first == '0' and last == '0':
+ return
+ self._vertScrollbar.set(first, last)
+ self._vertScrollbarNeeded = ((first, last) != ('0', '1'))
+
+ if self['vscrollmode'] == 'dynamic':
+ if self._vertScrollbarNeeded != self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+
+ if self['rowheader']:
+ if self._rowheaderLastY != first:
+ self._rowheaderLastY = first
+ self._rowheader.yview('moveto', first)
+
+ def _scrollBothLater(self, first, last):
+ # Called by the text widget to set the horizontal or vertical
+ # scrollbar when it has scrolled or changed size or contents.
+
+ if self.scrollTimer is None:
+ self.scrollTimer = self.after_idle(self._scrollBothNow)
+
+ def _scrollBothNow(self):
+ # This performs the function of _scrollXNow and _scrollYNow.
+ # If one is changed, the other should be updated to match.
+ self.scrollTimer = None
+
+ # Call update_idletasks to make sure that the containing frame
+ # has been resized before we attempt to set the scrollbars.
+ # Otherwise the scrollbars may be mapped/unmapped continuously.
+ self._scrollRecurse = self._scrollRecurse + 1
+ self.update_idletasks()
+ self._scrollRecurse = self._scrollRecurse - 1
+ if self._scrollRecurse != 0:
+ return
+
+ xview = self._textbox.xview()
+ yview = self._textbox.yview()
+
+ # The text widget returns a yview of (0.0, 0.0) just after it
+ # has been created. Ignore this.
+ if yview == (0.0, 0.0):
+ return
+
+ if self['columnheader']:
+ if self._columnheaderLastX != xview[0]:
+ self._columnheaderLastX = xview[0]
+ self._columnheader.xview('moveto', xview[0])
+ if self['rowheader']:
+ if self._rowheaderLastY != yview[0]:
+ self._rowheaderLastY = yview[0]
+ self._rowheader.yview('moveto', yview[0])
+
+ self._horizScrollbar.set(xview[0], xview[1])
+ self._vertScrollbar.set(yview[0], yview[1])
+
+ self._horizScrollbarNeeded = (xview != (0.0, 1.0))
+ self._vertScrollbarNeeded = (yview != (0.0, 1.0))
+
+ # If both horizontal and vertical scrollmodes are dynamic and
+ # currently only one scrollbar is mapped and both should be
+ # toggled, then unmap the mapped scrollbar. This prevents a
+ # continuous mapping and unmapping of the scrollbars.
+ if (self['hscrollmode'] == self['vscrollmode'] == 'dynamic' and
+ self._horizScrollbarNeeded != self._horizScrollbarOn and
+ self._vertScrollbarNeeded != self._vertScrollbarOn and
+ self._vertScrollbarOn != self._horizScrollbarOn):
+ if self._horizScrollbarOn:
+ self._toggleHorizScrollbar()
+ else:
+ self._toggleVertScrollbar()
+ return
+
+ if self['hscrollmode'] == 'dynamic':
+
+ # The following test is done to prevent continuous
+ # mapping and unmapping of the horizontal scrollbar.
+ # This may occur when some event (scrolling, resizing
+ # or text changes) modifies the displayed text such
+ # that the bottom line in the window is the longest
+ # line displayed. If this causes the horizontal
+ # scrollbar to be mapped, the scrollbar may "cover up"
+ # the bottom line, which would mean that the scrollbar
+ # is no longer required. If the scrollbar is then
+ # unmapped, the bottom line will then become visible
+ # again, which would cause the scrollbar to be mapped
+ # again, and so on...
+ #
+ # The idea is that, if the width of the text widget
+ # has not changed and the scrollbar is currently
+ # mapped, then do not unmap the scrollbar even if it
+ # is no longer required. This means that, during
+ # normal scrolling of the text, once the horizontal
+ # scrollbar has been mapped it will not be unmapped
+ # (until the width of the text widget changes).
+
+ currentWidth = self._textbox.winfo_width()
+ if self._horizScrollbarNeeded != self._horizScrollbarOn:
+ if self._horizScrollbarNeeded or \
+ self._textWidth != currentWidth:
+ self._toggleHorizScrollbar()
+ self._textWidth = currentWidth
+
+ if self['vscrollmode'] == 'dynamic':
+ if self._vertScrollbarNeeded != self._vertScrollbarOn:
+ self._toggleVertScrollbar()
+
+ def _columnheaderscrolled(self, first, last):
+ if self._textboxLastX != first:
+ self._textboxLastX = first
+ self._textbox.xview('moveto', first)
+
+ def _rowheaderscrolled(self, first, last):
+ if self._textboxLastY != first:
+ self._textboxLastY = first
+ self._textbox.yview('moveto', first)
+
+ def _toggleHorizScrollbar(self):
+
+ self._horizScrollbarOn = not self._horizScrollbarOn
+
+ interior = self.interior()
+ if self._horizScrollbarOn:
+ self._horizScrollbar.grid(row = 6, column = 4, sticky = 'news')
+ interior.grid_rowconfigure(5, minsize = self['scrollmargin'])
+ else:
+ self._horizScrollbar.grid_forget()
+ interior.grid_rowconfigure(5, minsize = 0)
+
+ def _toggleVertScrollbar(self):
+
+ self._vertScrollbarOn = not self._vertScrollbarOn
+
+ interior = self.interior()
+ if self._vertScrollbarOn:
+ self._vertScrollbar.grid(row = 4, column = 6, sticky = 'news')
+ interior.grid_columnconfigure(5, minsize = self['scrollmargin'])
+ else:
+ self._vertScrollbar.grid_forget()
+ interior.grid_columnconfigure(5, minsize = 0)
+
+ # Need to explicitly forward this to override the stupid
+ # (grid_)bbox method inherited from Tkinter.Frame.Grid.
+ def bbox(self, index):
+ return self._textbox.bbox(index)
+
+Pmw.forwardmethods(ScrolledText, tkinter.Text, '_textbox')
+
+######################################################################
+### File: PmwHistoryText.py
+import Pmw
+import collections
+
+_ORIGINAL = 0
+_MODIFIED = 1
+_DISPLAY = 2
+
+class HistoryText(Pmw.ScrolledText):
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+ optiondefs = (
+ ('compressany', 1, None),
+ ('compresstail', 1, None),
+ ('historycommand', None, None),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.ScrolledText.__init__(self, parent)
+
+ # Initialise instance variables.
+ self._list = []
+ self._currIndex = 0
+ self._pastIndex = None
+ self._lastIndex = 0 # pointer to end of history list
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def addhistory(self):
+ text = self.get()
+ if text[-1] == '\n':
+ text = text[:-1]
+
+ if len(self._list) == 0:
+ # This is the first history entry. Add it.
+ self._list.append([text, text, _MODIFIED])
+ return
+
+ currentEntry = self._list[self._currIndex]
+ if text == currentEntry[_ORIGINAL]:
+ # The current history entry has not been modified. Check if
+ # we need to add it again.
+
+ if self['compresstail'] and self._currIndex == self._lastIndex:
+ return
+
+ if self['compressany']:
+ return
+
+ # Undo any changes for the current history entry, since they
+ # will now be available in the new entry.
+ currentEntry[_MODIFIED] = currentEntry[_ORIGINAL]
+
+ historycommand = self['historycommand']
+ if self._currIndex == self._lastIndex:
+ # The last history entry is currently being displayed,
+ # so disable the special meaning of the 'Next' button.
+ self._pastIndex = None
+ nextState = 'disabled'
+ else:
+ # A previous history entry is currently being displayed,
+ # so allow the 'Next' button to go to the entry after this one.
+ self._pastIndex = self._currIndex
+ nextState = 'normal'
+ if isinstance(historycommand, collections.Callable):
+ historycommand('normal', nextState)
+
+ # Create the new history entry.
+ self._list.append([text, text, _MODIFIED])
+
+ # Move the pointer into the history entry list to the end.
+ self._lastIndex = self._lastIndex + 1
+ self._currIndex = self._lastIndex
+
+ def next(self):
+ if self._currIndex == self._lastIndex and self._pastIndex is None:
+ self.bell()
+ else:
+ self._modifyDisplay('next')
+
+ def prev(self):
+ self._pastIndex = None
+ if self._currIndex == 0:
+ self.bell()
+ else:
+ self._modifyDisplay('prev')
+
+ def undo(self):
+ if len(self._list) != 0:
+ self._modifyDisplay('undo')
+
+ def redo(self):
+ if len(self._list) != 0:
+ self._modifyDisplay('redo')
+
+ def gethistory(self):
+ return self._list
+
+ def _modifyDisplay(self, command):
+ # Modify the display to show either the next or previous
+ # history entry (next, prev) or the original or modified
+ # version of the current history entry (undo, redo).
+
+ # Save the currently displayed text.
+ currentText = self.get()
+ if currentText[-1] == '\n':
+ currentText = currentText[:-1]
+
+ currentEntry = self._list[self._currIndex]
+ if currentEntry[_DISPLAY] == _MODIFIED:
+ currentEntry[_MODIFIED] = currentText
+ elif currentEntry[_ORIGINAL] != currentText:
+ currentEntry[_MODIFIED] = currentText
+ if command in ('next', 'prev'):
+ currentEntry[_DISPLAY] = _MODIFIED
+
+ if command in ('next', 'prev'):
+ prevstate = 'normal'
+ nextstate = 'normal'
+ if command == 'next':
+ if self._pastIndex is not None:
+ self._currIndex = self._pastIndex
+ self._pastIndex = None
+ self._currIndex = self._currIndex + 1
+ if self._currIndex == self._lastIndex:
+ nextstate = 'disabled'
+ elif command == 'prev':
+ self._currIndex = self._currIndex - 1
+ if self._currIndex == 0:
+ prevstate = 'disabled'
+ historycommand = self['historycommand']
+ if isinstance(historycommand, collections.Callable):
+ historycommand(prevstate, nextstate)
+ currentEntry = self._list[self._currIndex]
+ else:
+ if command == 'undo':
+ currentEntry[_DISPLAY] = _ORIGINAL
+ elif command == 'redo':
+ currentEntry[_DISPLAY] = _MODIFIED
+
+ # Display the new text.
+ self.delete('1.0', 'end')
+ self.insert('end', currentEntry[currentEntry[_DISPLAY]])
+
+######################################################################
+### File: PmwSelectionDialog.py
+# Not Based on iwidgets version.
+
+import Pmw
+
+class SelectionDialog(Pmw.Dialog):
+ # Dialog window with selection list.
+
+ # Dialog window displaying a list and requesting the user to
+ # select one.
+
+ def __init__(self, parent = None, **kw):
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('borderx', 10, INITOPT),
+ ('bordery', 10, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.Dialog.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+ aliases = (
+ ('listbox', 'scrolledlist_listbox'),
+ ('label', 'scrolledlist_label'),
+ )
+ self._list = self.createcomponent('scrolledlist',
+ aliases, None,
+ Pmw.ScrolledListBox, (interior,),
+ dblclickcommand = self.invoke)
+ self._list.pack(side='top', expand='true', fill='both',
+ padx = self['borderx'], pady = self['bordery'])
+
+ if 'activatecommand' not in kw:
+ # Whenever this dialog is activated, set the focus to the
+ # ScrolledListBox's listbox widget.
+ listbox = self.component('listbox')
+ self.configure(activatecommand = listbox.focus_set)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ # Need to explicitly forward this to override the stupid
+ # (grid_)size method inherited from Tkinter.Toplevel.Grid.
+ def size(self):
+ return self.component('listbox').size()
+
+ # Need to explicitly forward this to override the stupid
+ # (grid_)bbox method inherited from Tkinter.Toplevel.Grid.
+ def bbox(self, index):
+ return self.component('listbox').size(index)
+
+Pmw.forwardmethods(SelectionDialog, Pmw.ScrolledListBox, '_list')
+
+######################################################################
+### File: PmwTextDialog.py
+# A Dialog with a ScrolledText widget.
+
+import Pmw
+
+class TextDialog(Pmw.Dialog):
+ def __init__(self, parent = None, **kw):
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('borderx', 10, INITOPT),
+ ('bordery', 10, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.Dialog.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+ aliases = (
+ ('text', 'scrolledtext_text'),
+ ('label', 'scrolledtext_label'),
+ )
+ self._text = self.createcomponent('scrolledtext',
+ aliases, None,
+ Pmw.ScrolledText, (interior,))
+ self._text.pack(side='top', expand=1, fill='both',
+ padx = self['borderx'], pady = self['bordery'])
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ # Need to explicitly forward this to override the stupid
+ # (grid_)bbox method inherited from Tkinter.Toplevel.Grid.
+ def bbox(self, index):
+ return self._text.bbox(index)
+
+Pmw.forwardmethods(TextDialog, Pmw.ScrolledText, '_text')
+
+######################################################################
+### File: PmwTimeCounter.py
+# Authors: Joe VanAndel and Greg McFarlane
+
+import string
+import sys
+import time
+import tkinter
+import Pmw
+import collections
+
+class TimeCounter(Pmw.MegaWidget):
+ """Up-down counter
+
+ A TimeCounter is a single-line entry widget with Up and Down arrows
+ which increment and decrement the Time value in the entry.
+ """
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('autorepeat', 1, None),
+ ('buttonaspect', 1.0, INITOPT),
+ ('command', None, None),
+ ('initwait', 300, None),
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('max', None, self._max),
+ ('min', None, self._min),
+ ('padx', 0, INITOPT),
+ ('pady', 0, INITOPT),
+ ('repeatrate', 50, None),
+ ('value', None, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ self.arrowDirection = {}
+ self._flag = 'stopped'
+ self._timerId = None
+
+ self._createComponents(kw)
+
+ value = self['value']
+ if value is None:
+ now = time.time()
+ value = time.strftime('%H:%M:%S', time.localtime(now))
+ self.setvalue(value)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def _createComponents(self, kw):
+
+ # Create the components.
+ interior = self.interior()
+
+ # If there is no label, put the arrows and the entry directly
+ # into the interior, otherwise create a frame for them. In
+ # either case the border around the arrows and the entry will
+ # be raised (but not around the label).
+ if self['labelpos'] is None:
+ frame = interior
+ if 'hull_relief' not in kw:
+ frame.configure(relief = 'raised')
+ if 'hull_borderwidth' not in kw:
+ frame.configure(borderwidth = 1)
+ else:
+ frame = self.createcomponent('frame',
+ (), None,
+ tkinter.Frame, (interior,),
+ relief = 'raised', borderwidth = 1)
+ frame.grid(column=2, row=2, sticky='nsew')
+ interior.grid_columnconfigure(2, weight=1)
+ interior.grid_rowconfigure(2, weight=1)
+
+ # Create the down arrow buttons.
+
+ # Create the hour down arrow.
+ self._downHourArrowBtn = self.createcomponent('downhourarrow',
+ (), 'Arrow',
+ tkinter.Canvas, (frame,),
+ width = 16, height = 16, relief = 'raised', borderwidth = 2)
+ self.arrowDirection[self._downHourArrowBtn] = 'down'
+ self._downHourArrowBtn.grid(column = 0, row = 2)
+
+ # Create the minute down arrow.
+ self._downMinuteArrowBtn = self.createcomponent('downminutearrow',
+ (), 'Arrow',
+ tkinter.Canvas, (frame,),
+ width = 16, height = 16, relief = 'raised', borderwidth = 2)
+ self.arrowDirection[self._downMinuteArrowBtn] = 'down'
+ self._downMinuteArrowBtn.grid(column = 1, row = 2)
+
+ # Create the second down arrow.
+ self._downSecondArrowBtn = self.createcomponent('downsecondarrow',
+ (), 'Arrow',
+ tkinter.Canvas, (frame,),
+ width = 16, height = 16, relief = 'raised', borderwidth = 2)
+ self.arrowDirection[self._downSecondArrowBtn] = 'down'
+ self._downSecondArrowBtn.grid(column = 2, row = 2)
+
+ # Create the entry fields.
+
+ # Create the hour entry field.
+ self._hourCounterEntry = self.createcomponent('hourentryfield',
+ (('hourentry', 'hourentryfield_entry'),), None,
+ Pmw.EntryField, (frame,), validate='integer', entry_width = 2)
+ self._hourCounterEntry.grid(column = 0, row = 1, sticky = 'news')
+
+ # Create the minute entry field.
+ self._minuteCounterEntry = self.createcomponent('minuteentryfield',
+ (('minuteentry', 'minuteentryfield_entry'),), None,
+ Pmw.EntryField, (frame,), validate='integer', entry_width = 2)
+ self._minuteCounterEntry.grid(column = 1, row = 1, sticky = 'news')
+
+ # Create the second entry field.
+ self._secondCounterEntry = self.createcomponent('secondentryfield',
+ (('secondentry', 'secondentryfield_entry'),), None,
+ Pmw.EntryField, (frame,), validate='integer', entry_width = 2)
+ self._secondCounterEntry.grid(column = 2, row = 1, sticky = 'news')
+
+ # Create the up arrow buttons.
+
+ # Create the hour up arrow.
+ self._upHourArrowBtn = self.createcomponent('uphourarrow',
+ (), 'Arrow',
+ tkinter.Canvas, (frame,),
+ width = 16, height = 16, relief = 'raised', borderwidth = 2)
+ self.arrowDirection[self._upHourArrowBtn] = 'up'
+ self._upHourArrowBtn.grid(column = 0, row = 0)
+
+ # Create the minute up arrow.
+ self._upMinuteArrowBtn = self.createcomponent('upminutearrow',
+ (), 'Arrow',
+ tkinter.Canvas, (frame,),
+ width = 16, height = 16, relief = 'raised', borderwidth = 2)
+ self.arrowDirection[self._upMinuteArrowBtn] = 'up'
+ self._upMinuteArrowBtn.grid(column = 1, row = 0)
+
+ # Create the second up arrow.
+ self._upSecondArrowBtn = self.createcomponent('upsecondarrow',
+ (), 'Arrow',
+ tkinter.Canvas, (frame,),
+ width = 16, height = 16, relief = 'raised', borderwidth = 2)
+ self.arrowDirection[self._upSecondArrowBtn] = 'up'
+ self._upSecondArrowBtn.grid(column = 2, row = 0)
+
+ # Make it resize nicely.
+ padx = self['padx']
+ pady = self['pady']
+ for col in range(3):
+ frame.grid_columnconfigure(col, weight = 1, pad = padx)
+ frame.grid_rowconfigure(0, pad = pady)
+ frame.grid_rowconfigure(2, pad = pady)
+
+ frame.grid_rowconfigure(1, weight = 1)
+
+ # Create the label.
+ self.createlabel(interior)
+
+ # Set bindings.
+
+ # Up hour
+ self._upHourArrowBtn.bind('<Configure>',
+ lambda event, s=self,button=self._upHourArrowBtn:
+ s._drawArrow(button, 'up'))
+
+ self._upHourArrowBtn.bind('<1>',
+ lambda event, s=self,button=self._upHourArrowBtn:
+ s._countUp(button, 3600))
+
+ self._upHourArrowBtn.bind('<Any-ButtonRelease-1>',
+ lambda event, s=self, button=self._upHourArrowBtn:
+ s._stopUpDown(button))
+
+ # Up minute
+ self._upMinuteArrowBtn.bind('<Configure>',
+ lambda event, s=self,button=self._upMinuteArrowBtn:
+ s._drawArrow(button, 'up'))
+
+
+ self._upMinuteArrowBtn.bind('<1>',
+ lambda event, s=self,button=self._upMinuteArrowBtn:
+ s._countUp(button, 60))
+
+ self._upMinuteArrowBtn.bind('<Any-ButtonRelease-1>',
+ lambda event, s=self, button=self._upMinuteArrowBtn:
+ s._stopUpDown(button))
+
+ # Up second
+ self._upSecondArrowBtn.bind('<Configure>',
+ lambda event, s=self,button=self._upSecondArrowBtn:
+ s._drawArrow(button, 'up'))
+
+
+ self._upSecondArrowBtn.bind('<1>',
+ lambda event, s=self,button=self._upSecondArrowBtn:
+ s._countUp(button, 1))
+
+ self._upSecondArrowBtn.bind('<Any-ButtonRelease-1>',
+ lambda event, s=self, button=self._upSecondArrowBtn:
+ s._stopUpDown(button))
+
+ # Down hour
+ self._downHourArrowBtn.bind('<Configure>',
+ lambda event, s=self,button=self._downHourArrowBtn:
+ s._drawArrow(button, 'down'))
+
+ self._downHourArrowBtn.bind('<1>',
+ lambda event, s=self,button=self._downHourArrowBtn:
+ s._countDown(button, 3600))
+ self._downHourArrowBtn.bind('<Any-ButtonRelease-1>',
+ lambda event, s=self, button=self._downHourArrowBtn:
+ s._stopUpDown(button))
+
+
+ # Down minute
+ self._downMinuteArrowBtn.bind('<Configure>',
+ lambda event, s=self,button=self._downMinuteArrowBtn:
+ s._drawArrow(button, 'down'))
+
+ self._downMinuteArrowBtn.bind('<1>',
+ lambda event, s=self,button=self._downMinuteArrowBtn:
+ s._countDown(button, 60))
+ self._downMinuteArrowBtn.bind('<Any-ButtonRelease-1>',
+ lambda event, s=self, button=self._downMinuteArrowBtn:
+ s._stopUpDown(button))
+
+ # Down second
+ self._downSecondArrowBtn.bind('<Configure>',
+ lambda event, s=self,button=self._downSecondArrowBtn:
+ s._drawArrow(button, 'down'))
+
+ self._downSecondArrowBtn.bind('<1>',
+ lambda event, s=self, button=self._downSecondArrowBtn:
+ s._countDown(button,1))
+ self._downSecondArrowBtn.bind('<Any-ButtonRelease-1>',
+ lambda event, s=self, button=self._downSecondArrowBtn:
+ s._stopUpDown(button))
+
+ self._hourCounterEntry.component('entry').bind(
+ '<Return>', self._invoke)
+ self._minuteCounterEntry.component('entry').bind(
+ '<Return>', self._invoke)
+ self._secondCounterEntry.component('entry').bind(
+ '<Return>', self._invoke)
+
+ self._hourCounterEntry.bind('<Configure>', self._resizeArrow)
+ self._minuteCounterEntry.bind('<Configure>', self._resizeArrow)
+ self._secondCounterEntry.bind('<Configure>', self._resizeArrow)
+
+ def _drawArrow(self, arrow, direction):
+ Pmw.drawarrow(arrow, self['hourentry_foreground'], direction, 'arrow')
+
+ def _resizeArrow(self, event = None):
+ for btn in (self._upHourArrowBtn, self._upMinuteArrowBtn,
+ self._upSecondArrowBtn,
+ self._downHourArrowBtn,
+ self._downMinuteArrowBtn, self._downSecondArrowBtn):
+ bw = (int(btn['borderwidth']) +
+ int(btn['highlightthickness']))
+ newHeight = self._hourCounterEntry.winfo_reqheight() - 2 * bw
+ newWidth = int(newHeight * self['buttonaspect'])
+ btn.configure(width=newWidth, height=newHeight)
+ self._drawArrow(btn, self.arrowDirection[btn])
+
+ def _min(self):
+ min = self['min']
+ if min is None:
+ self._minVal = 0
+ else:
+ self._minVal = Pmw.timestringtoseconds(min)
+
+ def _max(self):
+ max = self['max']
+ if max is None:
+ self._maxVal = None
+ else:
+ self._maxVal = Pmw.timestringtoseconds(max)
+
+ def getvalue(self):
+ return self.getstring()
+
+ def setvalue(self, text):
+ list = text.split(':')
+ if len(list) != 3:
+ raise ValueError('invalid value: ' + text)
+
+ self._hour = int(list[0])
+ self._minute = int(list[1])
+ self._second = int(list[2])
+
+ self._setHMS()
+
+ def getstring(self):
+ return '%02d:%02d:%02d' % (self._hour, self._minute, self._second)
+
+ def getint(self):
+ return self._hour * 3600 + self._minute * 60 + self._second
+
+ def _countUp(self, button, increment):
+ self._relief = self._upHourArrowBtn.cget('relief')
+ button.configure(relief='sunken')
+ self._count(1, 'start', increment)
+
+ def _countDown(self, button, increment):
+
+ self._relief = self._downHourArrowBtn.cget('relief')
+ button.configure(relief='sunken')
+ self._count(-1, 'start', increment)
+
+ def increment(self, seconds = 1):
+ self._count(1, 'force', seconds)
+
+ def decrement(self, seconds = 1):
+ self._count(-1, 'force', seconds)
+
+ def _count(self, factor, newFlag = None, increment = 1):
+ if newFlag != 'force':
+ if newFlag is not None:
+ self._flag = newFlag
+
+ if self._flag == 'stopped':
+ return
+
+ value = (int(self._hourCounterEntry.get()) *3600) + \
+ (int(self._minuteCounterEntry.get()) *60) + \
+ int(self._secondCounterEntry.get()) + \
+ factor * increment
+ min = self._minVal
+ max = self._maxVal
+ if value < min:
+ value = min
+ if max is not None and value > max:
+ value = max
+
+ self._hour = value // 3600
+ self._minute = (value - (self._hour*3600)) // 60
+ self._second = value - (self._hour*3600) - (self._minute*60)
+ self._setHMS()
+
+ if newFlag != 'force':
+ if self['autorepeat']:
+ if self._flag == 'start':
+ delay = self['initwait']
+ self._flag = 'running'
+ else:
+ delay = self['repeatrate']
+ self._timerId = self.after(
+ delay, lambda self=self, factor=factor,increment=increment:
+ self._count(factor,'running', increment))
+
+ def _setHMS(self):
+ self._hourCounterEntry.setentry('%02d' % self._hour)
+ self._minuteCounterEntry.setentry('%02d' % self._minute)
+ self._secondCounterEntry.setentry('%02d' % self._second)
+
+ def _stopUpDown(self, button):
+ if self._timerId is not None:
+ self.after_cancel(self._timerId)
+ self._timerId = None
+ button.configure(relief=self._relief)
+ self._flag = 'stopped'
+
+ def _invoke(self, event):
+ cmd = self['command']
+ if isinstance(cmd, collections.Callable):
+ cmd()
+
+ def invoke(self):
+ cmd = self['command']
+ if isinstance(cmd, collections.Callable):
+ return cmd()
+
+ def destroy(self):
+ if self._timerId is not None:
+ self.after_cancel(self._timerId)
+ self._timerId = None
+ Pmw.MegaWidget.destroy(self)
+
+######################################################################
+### File: PmwAboutDialog.py
+import Pmw
+
+class AboutDialog(Pmw.MessageDialog):
+ # Window to display version and contact information.
+
+ # Class members containing resettable 'default' values:
+ _version = ''
+ _copyright = ''
+ _contact = ''
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('applicationname', '', INITOPT),
+ ('iconpos', 'w', None),
+ ('icon_bitmap', 'info', None),
+ ('buttons', ('Close',), None),
+ ('defaultbutton', 0, None),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MessageDialog.__init__(self, parent)
+
+ applicationname = self['applicationname']
+ if 'title' not in kw:
+ self.configure(title = 'About ' + applicationname)
+
+ if 'message_text' not in kw:
+ text = applicationname + '\n\n'
+ if AboutDialog._version != '':
+ text = text + 'Version ' + AboutDialog._version + '\n'
+ if AboutDialog._copyright != '':
+ text = text + AboutDialog._copyright + '\n\n'
+ if AboutDialog._contact != '':
+ text = text + AboutDialog._contact
+
+ self.configure(message_text=text)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+def aboutversion(value):
+ AboutDialog._version = value
+
+def aboutcopyright(value):
+ AboutDialog._copyright = value
+
+def aboutcontact(value):
+ AboutDialog._contact = value
+
+######################################################################
+### File: PmwComboBox.py
+# Based on iwidgets2.2.0/combobox.itk code.
+
+import os
+import string
+import types
+import tkinter
+import Pmw
+import collections
+
+class ComboBox(Pmw.MegaWidget):
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('autoclear', 0, INITOPT),
+ ('buttonaspect', 1.0, INITOPT),
+ ('dropdown', 1, INITOPT),
+ ('fliparrow', 0, INITOPT),
+ ('history', 1, INITOPT),
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('listheight', 200, INITOPT),
+ ('selectioncommand', None, None),
+ ('sticky', 'ew', INITOPT),
+ ('unique', 1, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+
+ self._entryfield = self.createcomponent('entryfield',
+ (('entry', 'entryfield_entry'),), None,
+ Pmw.EntryField, (interior,))
+ self._entryfield.grid(column=2, row=2, sticky=self['sticky'])
+ interior.grid_columnconfigure(2, weight = 1)
+ self._entryWidget = self._entryfield.component('entry')
+
+ if self['dropdown']:
+ self._isPosted = 0
+ interior.grid_rowconfigure(2, weight = 1)
+
+ # Create the arrow button.
+ self._arrowBtn = self.createcomponent('arrowbutton',
+ (), None,
+ tkinter.Canvas, (interior,), borderwidth = 2,
+ relief = 'raised',
+ width = 16, height = 16)
+ if 'n' in self['sticky']:
+ sticky = 'n'
+ else:
+ sticky = ''
+ if 's' in self['sticky']:
+ sticky = sticky + 's'
+ self._arrowBtn.grid(column=3, row=2, sticky = sticky)
+ self._arrowRelief = self._arrowBtn.cget('relief')
+
+ # Create the label.
+ self.createlabel(interior, childCols=2)
+
+ # Create the dropdown window.
+ self._popup = self.createcomponent('popup',
+ (), None,
+ tkinter.Toplevel, (interior,))
+ self._popup.withdraw()
+ self._popup.overrideredirect(1)
+
+ # Create the scrolled listbox inside the dropdown window.
+ self._list = self.createcomponent('scrolledlist',
+ (('listbox', 'scrolledlist_listbox'),), None,
+ Pmw.ScrolledListBox, (self._popup,),
+ hull_borderwidth = 2,
+ hull_relief = 'raised',
+ hull_height = self['listheight'],
+ usehullsize = 1,
+ listbox_exportselection = 0)
+ self._list.pack(expand=1, fill='both')
+ self.__listbox = self._list.component('listbox')
+
+ # Bind events to the arrow button.
+ self._arrowBtn.bind('<1>', self._postList)
+ self._arrowBtn.bind('<Configure>', self._drawArrow)
+ self._arrowBtn.bind('<3>', self._next)
+ self._arrowBtn.bind('<Shift-3>', self._previous)
+ self._arrowBtn.bind('<Down>', self._next)
+ self._arrowBtn.bind('<Up>', self._previous)
+ self._arrowBtn.bind('<Control-n>', self._next)
+ self._arrowBtn.bind('<Control-p>', self._previous)
+ self._arrowBtn.bind('<Shift-Down>', self._postList)
+ self._arrowBtn.bind('<Shift-Up>', self._postList)
+ self._arrowBtn.bind('<F34>', self._postList)
+ self._arrowBtn.bind('<F28>', self._postList)
+ self._arrowBtn.bind('<space>', self._postList)
+
+ # Bind events to the dropdown window.
+ self._popup.bind('<Escape>', self._unpostList)
+ self._popup.bind('<space>', self._selectUnpost)
+ self._popup.bind('<Return>', self._selectUnpost)
+ self._popup.bind('<ButtonRelease-1>', self._dropdownBtnRelease)
+ self._popup.bind('<ButtonPress-1>', self._unpostOnNextRelease)
+
+ # Bind events to the Tk listbox.
+ self.__listbox.bind('<Enter>', self._unpostOnNextRelease)
+
+ # Bind events to the Tk entry widget.
+ self._entryWidget.bind('<Configure>', self._resizeArrow)
+ self._entryWidget.bind('<Shift-Down>', self._postList)
+ self._entryWidget.bind('<Shift-Up>', self._postList)
+ self._entryWidget.bind('<F34>', self._postList)
+ self._entryWidget.bind('<F28>', self._postList)
+
+ # Need to unpost the popup if the entryfield is unmapped (eg:
+ # its toplevel window is withdrawn) while the popup list is
+ # displayed.
+ self._entryWidget.bind('<Unmap>', self._unpostList)
+
+ else:
+ # Create the scrolled listbox below the entry field.
+ self._list = self.createcomponent('scrolledlist',
+ (('listbox', 'scrolledlist_listbox'),), None,
+ Pmw.ScrolledListBox, (interior,),
+ selectioncommand = self._selectCmd)
+ self._list.grid(column=2, row=3, sticky='nsew')
+ self.__listbox = self._list.component('listbox')
+
+ # The scrolled listbox should expand vertically.
+ interior.grid_rowconfigure(3, weight = 1)
+
+ # Create the label.
+ self.createlabel(interior, childRows=2)
+
+ self._entryWidget.bind('<Down>', self._next)
+ self._entryWidget.bind('<Up>', self._previous)
+ self._entryWidget.bind('<Control-n>', self._next)
+ self._entryWidget.bind('<Control-p>', self._previous)
+ self.__listbox.bind('<Control-n>', self._next)
+ self.__listbox.bind('<Control-p>', self._previous)
+
+ if self['history']:
+ self._entryfield.configure(command=self._addHistory)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def destroy(self):
+ if self['dropdown'] and self._isPosted:
+ Pmw.popgrab(self._popup)
+ Pmw.MegaWidget.destroy(self)
+
+ #======================================================================
+
+ # Public methods
+
+ def get(self, first = None, last=None):
+ if first is None:
+ return self._entryWidget.get()
+ else:
+ return self._list.get(first, last)
+
+ def invoke(self):
+ if self['dropdown']:
+ self._postList()
+ else:
+ return self._selectCmd()
+
+ def selectitem(self, index, setentry=1):
+ if type(index) is str:
+ text = index
+ items = self._list.get(0, 'end')
+ if text in items:
+ index = list(items).index(text)
+ else:
+ raise IndexError('index "%s" not found' % text)
+ elif setentry:
+ text = self._list.get(0, 'end')[index]
+
+ self._list.select_clear(0, 'end')
+ self._list.select_set(index, index)
+ self._list.activate(index)
+ self.see(index)
+ if setentry:
+ self._entryfield.setentry(text)
+
+ # Need to explicitly forward this to override the stupid
+ # (grid_)size method inherited from Tkinter.Frame.Grid.
+ def size(self):
+ return self._list.size()
+
+ # Need to explicitly forward this to override the stupid
+ # (grid_)bbox method inherited from Tkinter.Frame.Grid.
+ def bbox(self, index):
+ return self._list.bbox(index)
+
+ def clear(self):
+ self._entryfield.clear()
+ self._list.clear()
+
+ #======================================================================
+
+ # Private methods for both dropdown and simple comboboxes.
+
+ def _addHistory(self):
+ input = self._entryWidget.get()
+
+ if input != '':
+ index = None
+ if self['unique']:
+ # If item is already in list, select it and return.
+ items = self._list.get(0, 'end')
+ if input in items:
+ index = list(items).index(input)
+
+ if index is None:
+ index = self._list.index('end')
+ self._list.insert('end', input)
+
+ self.selectitem(index)
+ if self['autoclear']:
+ self._entryWidget.delete(0, 'end')
+
+ # Execute the selectioncommand on the new entry.
+ self._selectCmd()
+
+ def _next(self, event):
+ size = self.size()
+ if size <= 1:
+ return
+
+ cursels = self.curselection()
+
+ if len(cursels) == 0:
+ index = 0
+ else:
+ #Python 3 conversion
+ #index = string.atoi(cursels[0])
+ index = int(cursels[0])
+ if index == size - 1:
+ index = 0
+ else:
+ index = index + 1
+
+ self.selectitem(index)
+
+ def _previous(self, event):
+ size = self.size()
+ if size <= 1:
+ return
+
+ cursels = self.curselection()
+
+ if len(cursels) == 0:
+ index = size - 1
+ else:
+ #Python 3 conversion
+ #index = string.atoi(cursels[0])
+ index = int(cursels[0])
+ if index == 0:
+ index = size - 1
+ else:
+ index = index - 1
+
+ self.selectitem(index)
+
+ def _selectCmd(self, event=None):
+
+ sels = self.getcurselection()
+ if len(sels) == 0:
+ item = None
+ else:
+ item = sels[0]
+ self._entryfield.setentry(item)
+
+ cmd = self['selectioncommand']
+ if isinstance(cmd, collections.Callable):
+ if event is None:
+ # Return result of selectioncommand for invoke() method.
+ return cmd(item)
+ else:
+ cmd(item)
+
+ #======================================================================
+
+ # Private methods for dropdown combobox.
+
+ def _drawArrow(self, event=None, sunken=0):
+ arrow = self._arrowBtn
+ if sunken:
+ self._arrowRelief = arrow.cget('relief')
+ arrow.configure(relief = 'sunken')
+ else:
+ arrow.configure(relief = self._arrowRelief)
+
+ if self._isPosted and self['fliparrow']:
+ direction = 'up'
+ else:
+ direction = 'down'
+ Pmw.drawarrow(arrow, self['entry_foreground'], direction, 'arrow')
+
+ def _postList(self, event = None):
+ self._isPosted = 1
+ self._drawArrow(sunken=1)
+
+ # Make sure that the arrow is displayed sunken.
+ self.update_idletasks()
+
+ x = self._entryfield.winfo_rootx()
+ y = self._entryfield.winfo_rooty() + \
+ self._entryfield.winfo_height()
+ w = self._entryfield.winfo_width() + self._arrowBtn.winfo_width()
+ h = self.__listbox.winfo_height()
+ sh = self.winfo_screenheight()
+
+ if y + h > sh and y > sh / 2:
+ y = self._entryfield.winfo_rooty() - h
+
+ self._list.configure(hull_width=w)
+
+ Pmw.setgeometryanddeiconify(self._popup, '+%d+%d' % (x, y))
+
+ # Grab the popup, so that all events are delivered to it, and
+ # set focus to the listbox, to make keyboard navigation
+ # easier.
+ Pmw.pushgrab(self._popup, 1, self._unpostList)
+ self.__listbox.focus_set()
+
+ self._drawArrow()
+
+ # Ignore the first release of the mouse button after posting the
+ # dropdown list, unless the mouse enters the dropdown list.
+ self._ignoreRelease = 1
+
+ def _dropdownBtnRelease(self, event):
+ if (event.widget == self._list.component('vertscrollbar') or
+ event.widget == self._list.component('horizscrollbar')):
+ return
+
+ if self._ignoreRelease:
+ self._unpostOnNextRelease()
+ return
+
+ self._unpostList()
+
+ if (event.x >= 0 and event.x < self.__listbox.winfo_width() and
+ event.y >= 0 and event.y < self.__listbox.winfo_height()):
+ self._selectCmd()
+
+ def _unpostOnNextRelease(self, event = None):
+ self._ignoreRelease = 0
+
+ def _resizeArrow(self, event):
+ #Python 3 conversion
+ #bw = (string.atoi(self._arrowBtn['borderwidth']) +
+ # string.atoi(self._arrowBtn['highlightthickness']))
+ bw = (int(self._arrowBtn['borderwidth']) +
+ int(self._arrowBtn['highlightthickness']))
+ newHeight = self._entryfield.winfo_reqheight() - 2 * bw
+ newWidth = int(newHeight * self['buttonaspect'])
+ self._arrowBtn.configure(width=newWidth, height=newHeight)
+ self._drawArrow()
+
+ def _unpostList(self, event=None):
+ if not self._isPosted:
+ # It is possible to get events on an unposted popup. For
+ # example, by repeatedly pressing the space key to post
+ # and unpost the popup. The <space> event may be
+ # delivered to the popup window even though
+ # Pmw.popgrab() has set the focus away from the
+ # popup window. (Bug in Tk?)
+ return
+
+ # Restore the focus before withdrawing the window, since
+ # otherwise the window manager may take the focus away so we
+ # can't redirect it. Also, return the grab to the next active
+ # window in the stack, if any.
+ Pmw.popgrab(self._popup)
+ self._popup.withdraw()
+
+ self._isPosted = 0
+ self._drawArrow()
+
+ def _selectUnpost(self, event):
+ self._unpostList()
+ self._selectCmd()
+
+Pmw.forwardmethods(ComboBox, Pmw.ScrolledListBox, '_list')
+Pmw.forwardmethods(ComboBox, Pmw.EntryField, '_entryfield')
+
+######################################################################
+### File: PmwComboBoxDialog.py
+# Not Based on iwidgets version.
+
+import Pmw
+
+class ComboBoxDialog(Pmw.Dialog):
+ # Dialog window with simple combobox.
+
+ # Dialog window displaying a list and entry field and requesting
+ # the user to make a selection or enter a value
+
+ def __init__(self, parent = None, **kw):
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('borderx', 10, INITOPT),
+ ('bordery', 10, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.Dialog.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+
+ aliases = (
+ ('listbox', 'combobox_listbox'),
+ ('scrolledlist', 'combobox_scrolledlist'),
+ ('entry', 'combobox_entry'),
+ ('label', 'combobox_label'),
+ )
+ self._combobox = self.createcomponent('combobox',
+ aliases, None,
+ Pmw.ComboBox, (interior,),
+ scrolledlist_dblclickcommand = self.invoke,
+ dropdown = 0,
+ )
+ self._combobox.pack(side='top', expand='true', fill='both',
+ padx = self['borderx'], pady = self['bordery'])
+
+ if 'activatecommand' not in kw:
+ # Whenever this dialog is activated, set the focus to the
+ # ComboBox's listbox widget.
+ listbox = self.component('listbox')
+ self.configure(activatecommand = listbox.focus_set)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ # Need to explicitly forward this to override the stupid
+ # (grid_)size method inherited from Tkinter.Toplevel.Grid.
+ def size(self):
+ return self._combobox.size()
+
+ # Need to explicitly forward this to override the stupid
+ # (grid_)bbox method inherited from Tkinter.Toplevel.Grid.
+ def bbox(self, index):
+ return self._combobox.bbox(index)
+
+Pmw.forwardmethods(ComboBoxDialog, Pmw.ComboBox, '_combobox')
+
+######################################################################
+### File: PmwCounter.py
+import string
+import sys
+import types
+import tkinter
+import Pmw
+import collections
+
+class Counter(Pmw.MegaWidget):
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('autorepeat', 1, None),
+ ('buttonaspect', 1.0, INITOPT),
+ ('datatype', 'numeric', self._datatype),
+ ('increment', 1, None),
+ ('initwait', 300, None),
+ ('labelmargin', 0, INITOPT),
+ ('labelpos', None, INITOPT),
+ ('orient', 'horizontal', INITOPT),
+ ('padx', 0, INITOPT),
+ ('pady', 0, INITOPT),
+ ('repeatrate', 50, None),
+ ('sticky', 'ew', INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.MegaWidget.__init__(self, parent)
+
+ # Initialise instance variables.
+ self._timerId = None
+ self._normalRelief = None
+
+ # Create the components.
+ interior = self.interior()
+
+ # If there is no label, put the arrows and the entry directly
+ # into the interior, otherwise create a frame for them. In
+ # either case the border around the arrows and the entry will
+ # be raised (but not around the label).
+ if self['labelpos'] is None:
+ frame = interior
+ if 'hull_relief' not in kw:
+ frame.configure(relief = 'raised')
+ if 'hull_borderwidth' not in kw:
+ frame.configure(borderwidth = 1)
+ else:
+ frame = self.createcomponent('frame',
+ (), None,
+ tkinter.Frame, (interior,),
+ relief = 'raised', borderwidth = 1)
+ frame.grid(column=2, row=2, sticky=self['sticky'])
+ interior.grid_columnconfigure(2, weight=1)
+ interior.grid_rowconfigure(2, weight=1)
+
+ # Create the down arrow.
+ self._downArrowBtn = self.createcomponent('downarrow',
+ (), 'Arrow',
+ tkinter.Canvas, (frame,),
+ width = 16, height = 16, relief = 'raised', borderwidth = 2)
+
+ # Create the entry field.
+ self._counterEntry = self.createcomponent('entryfield',
+ (('entry', 'entryfield_entry'),), None,
+ Pmw.EntryField, (frame,))
+
+ # Create the up arrow.
+ self._upArrowBtn = self.createcomponent('uparrow',
+ (), 'Arrow',
+ tkinter.Canvas, (frame,),
+ width = 16, height = 16, relief = 'raised', borderwidth = 2)
+
+ padx = self['padx']
+ pady = self['pady']
+ orient = self['orient']
+ if orient == 'horizontal':
+ self._downArrowBtn.grid(column = 0, row = 0)
+ self._counterEntry.grid(column = 1, row = 0,
+ sticky = self['sticky'])
+ self._upArrowBtn.grid(column = 2, row = 0)
+ frame.grid_columnconfigure(1, weight = 1)
+ frame.grid_rowconfigure(0, weight = 1)
+ if tkinter.TkVersion >= 4.2:
+ frame.grid_columnconfigure(0, pad = padx)
+ frame.grid_columnconfigure(2, pad = padx)
+ frame.grid_rowconfigure(0, pad = pady)
+ elif orient == 'vertical':
+ self._upArrowBtn.grid(column = 0, row = 0, sticky = 's')
+ self._counterEntry.grid(column = 0, row = 1,
+ sticky = self['sticky'])
+ self._downArrowBtn.grid(column = 0, row = 2, sticky = 'n')
+ frame.grid_columnconfigure(0, weight = 1)
+ frame.grid_rowconfigure(0, weight = 1)
+ frame.grid_rowconfigure(2, weight = 1)
+ if tkinter.TkVersion >= 4.2:
+ frame.grid_rowconfigure(0, pad = pady)
+ frame.grid_rowconfigure(2, pad = pady)
+ frame.grid_columnconfigure(0, pad = padx)
+ else:
+ raise ValueError('bad orient option ' + repr(orient) + \
+ ': must be either \'horizontal\' or \'vertical\'')
+
+ self.createlabel(interior)
+
+ self._upArrowBtn.bind('<Configure>', self._drawUpArrow)
+ self._upArrowBtn.bind('<1>', self._countUp)
+ self._upArrowBtn.bind('<Any-ButtonRelease-1>', self._stopCounting)
+ self._downArrowBtn.bind('<Configure>', self._drawDownArrow)
+ self._downArrowBtn.bind('<1>', self._countDown)
+ self._downArrowBtn.bind('<Any-ButtonRelease-1>', self._stopCounting)
+ self._counterEntry.bind('<Configure>', self._resizeArrow)
+ entry = self._counterEntry.component('entry')
+ entry.bind('<Down>', lambda event, s = self: s._key_decrement(event))
+ entry.bind('<Up>', lambda event, s = self: s._key_increment(event))
+
+ # Need to cancel the timer if an arrow button is unmapped (eg:
+ # its toplevel window is withdrawn) while the mouse button is
+ # held down. The canvas will not get the ButtonRelease event
+ # if it is not mapped, since the implicit grab is cancelled.
+ self._upArrowBtn.bind('<Unmap>', self._stopCounting)
+ self._downArrowBtn.bind('<Unmap>', self._stopCounting)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ def _resizeArrow(self, event):
+ for btn in (self._upArrowBtn, self._downArrowBtn):
+ bw = (int(btn['borderwidth']) +
+ int(btn['highlightthickness']))
+ newHeight = self._counterEntry.winfo_reqheight() - 2 * bw
+ newWidth = int(newHeight * self['buttonaspect'])
+ btn.configure(width=newWidth, height=newHeight)
+ self._drawArrow(btn)
+
+ def _drawUpArrow(self, event):
+ self._drawArrow(self._upArrowBtn)
+
+ def _drawDownArrow(self, event):
+ self._drawArrow(self._downArrowBtn)
+
+ def _drawArrow(self, arrow):
+ if self['orient'] == 'vertical':
+ if arrow == self._upArrowBtn:
+ direction = 'up'
+ else:
+ direction = 'down'
+ else:
+ if arrow == self._upArrowBtn:
+ direction = 'right'
+ else:
+ direction = 'left'
+ Pmw.drawarrow(arrow, self['entry_foreground'], direction, 'arrow')
+
+ def _stopCounting(self, event = None):
+ if self._timerId is not None:
+ self.after_cancel(self._timerId)
+ self._timerId = None
+ if self._normalRelief is not None:
+ button, relief = self._normalRelief
+ button.configure(relief=relief)
+ self._normalRelief = None
+
+ def _countUp(self, event):
+ self._normalRelief = (self._upArrowBtn, self._upArrowBtn.cget('relief'))
+ self._upArrowBtn.configure(relief='sunken')
+ # Force arrow down (it may come up immediately, if increment fails).
+ self._upArrowBtn.update_idletasks()
+ self._count(1, 1)
+
+ def _countDown(self, event):
+ self._normalRelief = (self._downArrowBtn, self._downArrowBtn.cget('relief'))
+ self._downArrowBtn.configure(relief='sunken')
+ # Force arrow down (it may come up immediately, if increment fails).
+ self._downArrowBtn.update_idletasks()
+ self._count(-1, 1)
+
+ def increment(self):
+ self._forceCount(1)
+
+ def decrement(self):
+ self._forceCount(-1)
+
+ def _key_increment(self, event):
+ self._forceCount(1)
+ self.update_idletasks()
+
+ def _key_decrement(self, event):
+ self._forceCount(-1)
+ self.update_idletasks()
+
+ def _datatype(self):
+ datatype = self['datatype']
+
+ if type(datatype) is dict:
+ self._counterArgs = datatype.copy()
+ if 'counter' in self._counterArgs:
+ datatype = self._counterArgs['counter']
+ del self._counterArgs['counter']
+ else:
+ datatype = 'numeric'
+ else:
+ self._counterArgs = {}
+
+ if datatype in _counterCommands:
+ self._counterCommand = _counterCommands[datatype]
+ elif isinstance(datatype, collections.Callable):
+ self._counterCommand = datatype
+ else:
+ validValues = list(_counterCommands.keys())
+ validValues.sort()
+ raise ValueError(('bad datatype value "%s": must be a' +
+ ' function or one of %s') % (datatype, validValues))
+
+ def _forceCount(self, factor):
+ if not self.valid():
+ self.bell()
+ return
+
+ text = self._counterEntry.get()
+
+ try:
+ value = self._counterCommand(*(text, factor, self['increment']), **self._counterArgs)
+ except ValueError:
+ self.bell()
+ return
+
+ previousICursor = self._counterEntry.index('insert')
+ if self._counterEntry.setentry(value) == Pmw.OK:
+ self._counterEntry.xview('end')
+ self._counterEntry.icursor(previousICursor)
+
+ def _count(self, factor, first):
+ if not self.valid():
+ self.bell()
+ return
+
+ self._timerId = None
+ origtext = self._counterEntry.get()
+ try:
+ value = self._counterCommand(*(origtext, factor, self['increment']), **self._counterArgs)
+ except ValueError:
+ # If text is invalid, stop counting.
+ self._stopCounting()
+ self.bell()
+ return
+
+ # If incrementing produces an invalid value, restore previous
+ # text and stop counting.
+ previousICursor = self._counterEntry.index('insert')
+ valid = self._counterEntry.setentry(value)
+ if valid != Pmw.OK:
+ self._stopCounting()
+ self._counterEntry.setentry(origtext)
+ if valid == Pmw.PARTIAL:
+ self.bell()
+ return
+ self._counterEntry.xview('end')
+ self._counterEntry.icursor(previousICursor)
+
+ if self['autorepeat']:
+ if first:
+ delay = self['initwait']
+ else:
+ delay = self['repeatrate']
+ self._timerId = self.after(delay,
+ lambda self=self, factor=factor: self._count(factor, 0))
+
+ def destroy(self):
+ self._stopCounting()
+ Pmw.MegaWidget.destroy(self)
+
+Pmw.forwardmethods(Counter, Pmw.EntryField, '_counterEntry')
+
+def _changeNumber(text, factor, increment):
+ value = int(text)
+ if factor > 0:
+ value = (value // increment) * increment + increment
+ else:
+ value = ((value - 1) // increment) * increment
+
+ #removed "L" check since we're working with Python 3
+ return str(int(value))
+
+def _changeReal(text, factor, increment, separator = '.'):
+ value = Pmw.stringtoreal(text, separator)
+ div = value / increment
+
+ # Compare reals using str() to avoid problems caused by binary
+ # numbers being only approximations to decimal numbers.
+ # For example, if value is -0.3 and increment is 0.1, then
+ # int(value/increment) = -2, not -3 as one would expect.
+ if str(div)[-2:] == '.0':
+ # value is an even multiple of increment.
+ div = round(div) + factor
+ else:
+ # value is not an even multiple of increment.
+ div = int(div) * 1.0
+ if value < 0:
+ div = div - 1
+ if factor > 0:
+ div = (div + 1)
+
+ value = div * increment
+
+ text = str(value)
+ if separator != '.':
+ index = text.find('.')
+ if index >= 0:
+ text = text[:index] + separator + text[index + 1:]
+ return text
+
+def _changeDate(value, factor, increment, format = 'ymd',
+ separator = '/', yyyy = 0):
+
+ jdn = Pmw.datestringtojdn(value, format, separator) + factor * increment
+
+ y, m, d = Pmw.jdntoymd(jdn)
+ result = ''
+ for index in range(3):
+ if index > 0:
+ result = result + separator
+ f = format[index]
+ if f == 'y':
+ if yyyy:
+ result = result + '%02d' % y
+ else:
+ result = result + '%02d' % (y % 100)
+ elif f == 'm':
+ result = result + '%02d' % m
+ elif f == 'd':
+ result = result + '%02d' % d
+
+ return result
+
+_SECSPERDAY = 24 * 60 * 60
+def _changeTime(value, factor, increment, separator = ':', time24 = 0):
+ unixTime = Pmw.timestringtoseconds(value, separator)
+ if factor > 0:
+ chunks = unixTime // increment + 1
+ else:
+ chunks = (unixTime - 1) // increment
+ unixTime = chunks * increment
+ if time24:
+ while unixTime < 0:
+ unixTime = unixTime + _SECSPERDAY
+ while unixTime >= _SECSPERDAY:
+ unixTime = unixTime - _SECSPERDAY
+ if unixTime < 0:
+ unixTime = -unixTime
+ sign = '-'
+ else:
+ sign = ''
+ secs = unixTime % 60
+ unixTime = unixTime / 60
+ mins = unixTime % 60
+ hours = unixTime / 60
+ return '%s%02d%s%02d%s%02d' % (sign, hours, separator, mins, separator, secs)
+
+# hexadecimal, alphabetic, alphanumeric not implemented
+_counterCommands = {
+ 'numeric' : _changeNumber, # } integer
+ 'integer' : _changeNumber, # } these two use the same function
+ 'real' : _changeReal, # real number
+ 'time' : _changeTime,
+ 'date' : _changeDate,
+}
+
+######################################################################
+### File: PmwCounterDialog.py
+import Pmw
+
+# A Dialog with a counter
+
+class CounterDialog(Pmw.Dialog):
+
+ def __init__(self, parent = None, **kw):
+
+ # Define the megawidget options.
+
+ optiondefs = (
+ ('borderx', 20, INITOPT),
+ ('bordery', 20, INITOPT),
+ )
+ self.defineoptions(kw, optiondefs)
+
+ # Initialise the base class (after defining the options).
+ Pmw.Dialog.__init__(self, parent)
+
+ # Create the components.
+ interior = self.interior()
+
+ # Create the counter.
+ aliases = (
+ ('entryfield', 'counter_entryfield'),
+ ('entry', 'counter_entryfield_entry'),
+ ('label', 'counter_label')
+ )
+ self._cdCounter = self.createcomponent('counter',
+ aliases, None,
+ Pmw.Counter, (interior,))
+ self._cdCounter.pack(fill='x', expand=1,
+ padx = self['borderx'], pady = self['bordery'])
+
+ if 'activatecommand' not in kw:
+ # Whenever this dialog is activated, set the focus to the
+ # Counter's entry widget.
+ tkentry = self.component('entry')
+ self.configure(activatecommand = tkentry.focus_set)
+
+ # Check keywords and initialise options.
+ self.initialiseoptions()
+
+ # Supply aliases to some of the entry component methods.
+ def insertentry(self, index, text):
+ self._cdCounter.insert(index, text)
+
+ def deleteentry(self, first, last=None):
+ self._cdCounter.delete(first, last)
+
+ def indexentry(self, index):
+ return self._cdCounter.index(index)
+
+Pmw.forwardmethods(CounterDialog, Pmw.Counter, '_cdCounter')
+
+######################################################################
+### File: PmwLogicalFont.py
+import os
+import string
+
+def _font_initialise(root, size=None, fontScheme = None):
+ global _fontSize
+ if size is not None:
+ _fontSize = size
+
+ if fontScheme in ('pmw1', 'pmw2'):
+ if os.name == 'posix':
+ defaultFont = logicalfont('Helvetica')
+ menuFont = logicalfont('Helvetica', weight='bold', slant='italic')
+ scaleFont = logicalfont('Helvetica', slant='italic')
+ root.option_add('*Font', defaultFont, 'userDefault')
+ root.option_add('*Menu*Font', menuFont, 'userDefault')
+ root.option_add('*Menubutton*Font', menuFont, 'userDefault')
+ root.option_add('*Scale.*Font', scaleFont, 'userDefault')
+
+ if fontScheme == 'pmw1':
+ balloonFont = logicalfont('Helvetica', -6, pixel = '12')
+ else: # fontScheme == 'pmw2'
+ balloonFont = logicalfont('Helvetica', -2)
+ root.option_add('*Balloon.*Font', balloonFont, 'userDefault')
+ else:
+ defaultFont = logicalfont('Helvetica')
+ root.option_add('*Font', defaultFont, 'userDefault')
+ elif fontScheme == 'default':
+ defaultFont = ('Helvetica', '-%d' % (_fontSize,), 'bold')
+ entryFont = ('Helvetica', '-%d' % (_fontSize,))
+ textFont = ('Courier', '-%d' % (_fontSize,))
+ root.option_add('*Font', defaultFont, 'userDefault')
+ root.option_add('*Entry*Font', entryFont, 'userDefault')
+ root.option_add('*Text*Font', textFont, 'userDefault')
+
+def logicalfont(name='Helvetica', sizeIncr = 0, **kw):
+ if name not in _fontInfo:
+ raise ValueError('font %s does not exist' % name)
+
+ rtn = []
+ for field in _fontFields:
+ if field in kw:
+ logicalValue = kw[field]
+ elif field in _fontInfo[name]:
+ logicalValue = _fontInfo[name][field]
+ else:
+ logicalValue = '*'
+
+ if (field, logicalValue) in _propertyAliases[name]:
+ realValue = _propertyAliases[name][(field, logicalValue)]
+ elif (field, None) in _propertyAliases[name]:
+ realValue = _propertyAliases[name][(field, None)]
+ elif (field, logicalValue) in _propertyAliases[None]:
+ realValue = _propertyAliases[None][(field, logicalValue)]
+ elif (field, None) in _propertyAliases[None]:
+ realValue = _propertyAliases[None][(field, None)]
+ else:
+ realValue = logicalValue
+
+ if field == 'size':
+ if realValue == '*':
+ realValue = _fontSize
+ realValue = str((realValue + sizeIncr) * 10)
+
+ rtn.append(realValue)
+ return '-'.join(rtn)
+ #return str.join(rtn, '-')
+
+def logicalfontnames():
+ return list(_fontInfo.keys())
+
+if os.name == 'nt':
+ _fontSize = 16
+else:
+ _fontSize = 14
+
+_fontFields = (
+ 'registry', 'foundry', 'family', 'weight', 'slant', 'width', 'style',
+ 'pixel', 'size', 'xres', 'yres', 'spacing', 'avgwidth', 'charset', 'encoding')
+
+# <_propertyAliases> defines other names for which property values may
+# be known by. This is required because italics in adobe-helvetica
+# are specified by 'o', while other fonts use 'i'.
+
+_propertyAliases = {}
+
+_propertyAliases[None] = {
+ ('slant', 'italic') : 'i',
+ ('slant', 'normal') : 'r',
+ ('weight', 'light') : 'normal',
+ ('width', 'wide') : 'normal',
+ ('width', 'condensed') : 'normal',
+}
+
+# <_fontInfo> describes a 'logical' font, giving the default values of
+# some of its properties.
+
+_fontInfo = {}
+
+_fontInfo['Helvetica'] = {
+ 'foundry' : 'adobe',
+ 'family' : 'helvetica',
+ 'registry' : '',
+ 'charset' : 'iso8859',
+ 'encoding' : '1',
+ 'spacing' : 'p',
+ 'slant' : 'normal',
+ 'width' : 'normal',
+ 'weight' : 'normal',
+}
+
+_propertyAliases['Helvetica'] = {
+ ('slant', 'italic') : 'o',
+ ('weight', 'normal') : 'medium',
+ ('weight', 'light') : 'medium',
+}
+
+_fontInfo['Times'] = {
+ 'foundry' : 'adobe',
+ 'family' : 'times',
+ 'registry' : '',
+ 'charset' : 'iso8859',
+ 'encoding' : '1',
+ 'spacing' : 'p',
+ 'slant' : 'normal',
+ 'width' : 'normal',
+ 'weight' : 'normal',
+}
+
+_propertyAliases['Times'] = {
+ ('weight', 'normal') : 'medium',
+ ('weight', 'light') : 'medium',
+}
+
+_fontInfo['Fixed'] = {
+ 'foundry' : 'misc',
+ 'family' : 'fixed',
+ 'registry' : '',
+ 'charset' : 'iso8859',
+ 'encoding' : '1',
+ 'spacing' : 'c',
+ 'slant' : 'normal',
+ 'width' : 'normal',
+ 'weight' : 'normal',
+}
+
+_propertyAliases['Fixed'] = {
+ ('weight', 'normal') : 'medium',
+ ('weight', 'light') : 'medium',
+ ('style', None) : '',
+ ('width', 'condensed') : 'semicondensed',
+}
+
+_fontInfo['Courier'] = {
+ 'foundry' : 'adobe',
+ 'family' : 'courier',
+ 'registry' : '',
+ 'charset' : 'iso8859',
+ 'encoding' : '1',
+ 'spacing' : 'm',
+ 'slant' : 'normal',
+ 'width' : 'normal',
+ 'weight' : 'normal',
+}
+
+_propertyAliases['Courier'] = {
+ ('weight', 'normal') : 'medium',
+ ('weight', 'light') : 'medium',
+ ('style', None) : '',
+}
+
+_fontInfo['Typewriter'] = {
+ 'foundry' : 'b&h',
+ 'family' : 'lucidatypewriter',
+ 'registry' : '',
+ 'charset' : 'iso8859',
+ 'encoding' : '1',
+ 'spacing' : 'm',
+ 'slant' : 'normal',
+ 'width' : 'normal',
+ 'weight' : 'normal',
+}
+
+_propertyAliases['Typewriter'] = {
+ ('weight', 'normal') : 'medium',
+ ('weight', 'light') : 'medium',
+}
+
+if os.name == 'nt':
+ # For some reason 'fixed' fonts on NT aren't.
+ _fontInfo['Fixed'] = _fontInfo['Courier']
+ _propertyAliases['Fixed'] = _propertyAliases['Courier']
diff --git a/PmwBlt.py b/PmwBlt.py
new file mode 100644
index 0000000..d4bb5d4
--- /dev/null
+++ b/PmwBlt.py
@@ -0,0 +1,684 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: PmwBlt.py
+# Description: Python interface to some of the commands of the 2.4 version of the
+# BLT extension to tcl.
+# Source: http://sourceforge.net/projects/pmw/
+#
+# Copyright 1997-1999 Telstra Corporation Limited, Australia
+# Copyright 2000-2002 Really Good Software Pty Ltd, Australia
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+#-------------------------------------------------------------------------------
+import string
+import types
+import tkinter
+
+# Supported commands:
+_busyCommand = '::blt::busy'
+_vectorCommand = '::blt::vector'
+_graphCommand = '::blt::graph'
+_testCommand = '::blt::*'
+_chartCommand = '::blt::stripchart'
+_tabsetCommand = '::blt::tabset'
+
+_haveBlt = None
+_haveBltBusy = None
+
+_forceBltDisable = True
+
+def setBltDisable(window, value):
+ global _forceBltDisable
+ _forceBltDisable = value
+ _checkForBlt(window)
+
+def _checkForBlt(window):
+ global _haveBlt
+ global _haveBltBusy
+ global _forceBltDisable
+
+ # Blt may be a package which has not yet been loaded. Try to load it.
+ try:
+ window.tk.call('package', 'require', 'BLT')
+ except tkinter.TclError:
+ # Another way to try to dynamically load blt:
+ try:
+ window.tk.call('load', '', 'Blt')
+ except tkinter.TclError:
+ pass
+
+ _haveBlt= (window.tk.call('info', 'commands', _testCommand) != '')
+ _haveBltBusy = (window.tk.call('info', 'commands', _busyCommand) != '')
+
+ #only force haveBlt, not Busy since Busy might work...
+ if (_forceBltDisable):
+ _haveBlt = False
+
+def haveblt(window):
+ if _haveBlt is None:
+ _checkForBlt(window)
+ return _haveBlt
+
+def havebltbusy(window):
+ if _haveBlt is None:
+ _checkForBlt(window)
+ return _haveBltBusy
+
+def _loadBlt(window):
+ if _haveBlt is None:
+ if window is None:
+ window = tkinter._default_root
+ if window is None:
+ window = tkinter.Tk()
+ _checkForBlt(window)
+
+def busy_hold(window, cursor = None):
+ _loadBlt(window)
+ if cursor is None:
+ window.tk.call(_busyCommand, 'hold', window._w)
+ else:
+ window.tk.call(_busyCommand, 'hold', window._w, '-cursor', cursor)
+
+def busy_release(window):
+ _loadBlt(window)
+ window.tk.call(_busyCommand, 'release', window._w)
+
+def busy_forget(window):
+ _loadBlt(window)
+ window.tk.call(_busyCommand, 'forget', window._w)
+
+#=============================================================================
+# Interface to the blt vector command which makes it look like the
+# builtin python list type.
+# The -variable, -command, -watchunset creation options are not supported.
+# The dup, merge, notify, offset, populate, seq and variable methods
+# and the +, -, * and / operations are not supported.
+
+# Blt vector functions:
+def vector_expr(expression):
+ tk = tkinter._default_root.tk
+ strList = tk.splitlist(tk.call(_vectorCommand, 'expr', expression))
+ return tuple(map(float, strList))
+
+def vector_names(pattern = None):
+ tk = tkinter._default_root.tk
+ return tk.splitlist(tk.call(_vectorCommand, 'names', pattern))
+
+class Vector:
+ _varnum = 0
+ def __init__(self, size=None, master=None):
+ # <size> can be either an integer size, or a string "first:last".
+ _loadBlt(master)
+ if master:
+ self._master = master
+ else:
+ self._master = tkinter._default_root
+ self.tk = self._master.tk
+ self._name = 'PY_VEC' + str(Vector._varnum)
+ Vector._varnum = Vector._varnum + 1
+ if size is None:
+ self.tk.call(_vectorCommand, 'create', self._name)
+ else:
+ self.tk.call(_vectorCommand, 'create', '%s(%s)' % (self._name, size))
+ def __del__(self):
+ self.tk.call(_vectorCommand, 'destroy', self._name)
+ def __str__(self):
+ return self._name
+
+ def __repr__(self):
+ #return '[' + string.join(list(map(str, self)), ', ') + ']'
+ return '[' + ', '.join(list(map(str, self))) + ']'
+ def __cmp__(self, list):
+ return cmp(self[:], list)
+
+ def __len__(self):
+ return self.tk.getint(self.tk.call(self._name, 'length'))
+ def __getitem__(self, key):
+ if isinstance(key, slice):
+ return self.__getslice__(key.start, key.stop)
+ oldkey = key
+ if key < 0:
+ key = key + len(self)
+ try:
+ return self.tk.getdouble(self.tk.globalgetvar(self._name, str(key)))
+ except tkinter.TclError:
+ raise IndexError(oldkey)
+ def __setitem__(self, key, value):
+ if key < 0:
+ key = key + len(self)
+ return self.tk.globalsetvar(self._name, str(key), float(value))
+
+ def __delitem__(self, key):
+ if key < 0:
+ key = key + len(self)
+ return self.tk.globalunsetvar(self._name, str(key))
+
+ def __getslice__(self, start, end):
+ length = len(self)
+ if start is None or start < 0:
+ start = 0
+ if end is None or end > length:
+ end = length
+ if start >= end:
+ return []
+ end = end - 1 # Blt vector slices include end point.
+ text = self.tk.globalgetvar(self._name, str(start) + ':' + str(end))
+ return list(map(self.tk.getdouble, self.tk.splitlist(text)))
+
+ def __setslice__(self, start, end, list):
+ if start > end:
+ end = start
+ self.set(self[:start] + list + self[end:])
+
+ def __delslice__(self, start, end):
+ if start < end:
+ self.set(self[:start] + self[end:])
+
+ def __add__(self, list):
+ return self[:] + list
+ def __radd__(self, list):
+ return list + self[:]
+ def __mul__(self, n):
+ return self[:] * n
+ __rmul__ = __mul__
+
+ # Python builtin list methods:
+ def append(self, *args):
+ self.tk.call(self._name, 'append', args)
+ def count(self, obj):
+ return self[:].count(obj)
+ def index(self, value):
+ return self[:].index(value)
+ def insert(self, index, value):
+ self[index:index] = [value]
+ def remove(self, value):
+ del self[self.index(value)]
+ def reverse(self):
+ s = self[:]
+ s.reverse()
+ self.set(s)
+ def sort(self, *args):
+ s = self[:]
+ s.sort()
+ self.set(s)
+
+ # Blt vector instance methods:
+ # append - same as list method above
+ def clear(self):
+ self.tk.call(self._name, 'clear')
+ def delete(self, *args):
+ self.tk.call((self._name, 'delete') + args)
+ def expr(self, expression):
+ self.tk.call(self._name, 'expr', expression)
+ def length(self, newSize=None):
+ return self.tk.getint(self.tk.call(self._name, 'length', newSize))
+ def range(self, first, last=None):
+ # Note that, unlike self[first:last], this includes the last
+ # item in the returned range.
+ text = self.tk.call(self._name, 'range', first, last)
+ return list(map(self.tk.getdouble, self.tk.splitlist(text)))
+ def search(self, start, end=None):
+ return self._master._getints(self.tk.call(
+ self._name, 'search', start, end))
+ def set(self, list):
+ if type(list) != tuple:
+ list = tuple(list)
+ self.tk.call(self._name, 'set', list)
+
+ # The blt vector sort method has different semantics to the python
+ # list sort method. Call these blt_sort:
+ def blt_sort(self, *args):
+ self.tk.call((self._name, 'sort') + args)
+ def blt_sort_reverse(self, *args):
+ self.tk.call((self._name, 'sort', '-reverse') + args)
+
+ # Special blt vector indexes:
+ def min(self):
+ return self.tk.getdouble(self.tk.globalgetvar(self._name, 'min'))
+ def max(self):
+ return self.tk.getdouble(self.tk.globalgetvar(self._name, 'max'))
+
+ # Method borrowed from Tkinter.Var class:
+ def get(self):
+ return self[:]
+
+#=============================================================================
+
+# This is a general purpose configure routine which can handle the
+# configuration of widgets, items within widgets, etc. Supports the
+# forms configure() and configure('font') for querying and
+# configure(font = 'fixed', text = 'hello') for setting.
+
+def _doConfigure(widget, subcommand, option, kw):
+
+ if not option and not kw:
+ # Return a description of all options.
+ ret = {}
+ options = widget.tk.splitlist(widget.tk.call(subcommand))
+ for optionString in options:
+ optionInfo = widget.tk.splitlist(optionString)
+ option = optionInfo[0][1:]
+ ret[option] = (option,) + optionInfo[1:]
+ return ret
+
+ if option:
+ # Return a description of the option given by <option>.
+ if kw:
+ # Having keywords implies setting configuration options.
+ # Can't set and get in one command!
+ raise ValueError('cannot have option argument with keywords')
+ option = '-' + option
+ optionInfo = widget.tk.splitlist(widget.tk.call(subcommand + (option,)))
+ return (optionInfo[0][1:],) + optionInfo[1:]
+
+ # Otherwise, set the given configuration options.
+ widget.tk.call(subcommand + widget._options(kw))
+
+#=============================================================================
+
+class Graph(tkinter.Widget):
+ # Wrapper for the blt graph widget, version 2.4.
+
+ def __init__(self, master=None, cnf={}, **kw):
+ _loadBlt(master)
+ tkinter.Widget.__init__(self, master, _graphCommand, cnf, kw)
+
+ def bar_create(self, name, **kw):
+ self.tk.call((self._w, 'bar', 'create', name) + self._options(kw))
+
+ def line_create(self, name, **kw):
+ self.tk.call((self._w, 'line', 'create', name) + self._options(kw))
+
+ def extents(self, item):
+ return self.tk.getint(self.tk.call(self._w, 'extents', item))
+
+ def invtransform(self, winX, winY):
+ return self._getdoubles(
+ self.tk.call(self._w, 'invtransform', winX, winY))
+
+ def inside(self, x, y):
+ return self.tk.getint(self.tk.call(self._w, 'inside', x, y))
+
+ def snap(self, photoName):
+ self.tk.call(self._w, 'snap', photoName)
+
+ def transform(self, x, y):
+ return self._getdoubles(self.tk.call(self._w, 'transform', x, y))
+
+ def axis_cget(self, axisName, key):
+ return self.tk.call(self._w, 'axis', 'cget', axisName, '-' + key)
+ def axis_configure(self, axes, option=None, **kw):
+ # <axes> may be a list of axisNames.
+ if type(axes) is str:
+ axes = [axes]
+ subcommand = (self._w, 'axis', 'configure') + tuple(axes)
+ return _doConfigure(self, subcommand, option, kw)
+ def axis_create(self, axisName, **kw):
+ self.tk.call((self._w, 'axis', 'create', axisName) + self._options(kw))
+ def axis_delete(self, *args):
+ self.tk.call((self._w, 'axis', 'delete') + args)
+ def axis_invtransform(self, axisName, value):
+ return self.tk.getdouble(self.tk.call(
+ self._w, 'axis', 'invtransform', axisName, value))
+ def axis_limits(self, axisName):
+ return self._getdoubles(self.tk.call(
+ self._w, 'axis', 'limits', axisName))
+ def axis_names(self, *args):
+ return self.tk.splitlist(
+ self.tk.call((self._w, 'axis', 'names') + args))
+ def axis_transform(self, axisName, value):
+ return self.tk.getint(self.tk.call(
+ self._w, 'axis', 'transform', axisName, value))
+
+ def xaxis_cget(self, key):
+ return self.tk.call(self._w, 'xaxis', 'cget', '-' + key)
+ def xaxis_configure(self, option=None, **kw):
+ subcommand = (self._w, 'xaxis', 'configure')
+ return _doConfigure(self, subcommand, option, kw)
+ def xaxis_invtransform(self, value):
+ return self.tk.getdouble(self.tk.call(
+ self._w, 'xaxis', 'invtransform', value))
+ def xaxis_limits(self):
+ return self._getdoubles(self.tk.call(self._w, 'xaxis', 'limits'))
+ def xaxis_transform(self, value):
+ return self.tk.getint(self.tk.call(
+ self._w, 'xaxis', 'transform', value))
+ def xaxis_use(self, axisName = None):
+ return self.tk.call(self._w, 'xaxis', 'use', axisName)
+
+ def x2axis_cget(self, key):
+ return self.tk.call(self._w, 'x2axis', 'cget', '-' + key)
+ def x2axis_configure(self, option=None, **kw):
+ subcommand = (self._w, 'x2axis', 'configure')
+ return _doConfigure(self, subcommand, option, kw)
+ def x2axis_invtransform(self, value):
+ return self.tk.getdouble(self.tk.call(
+ self._w, 'x2axis', 'invtransform', value))
+ def x2axis_limits(self):
+ return self._getdoubles(self.tk.call(self._w, 'x2axis', 'limits'))
+ def x2axis_transform(self, value):
+ return self.tk.getint(self.tk.call(
+ self._w, 'x2axis', 'transform', value))
+ def x2axis_use(self, axisName = None):
+ return self.tk.call(self._w, 'x2axis', 'use', axisName)
+
+ def yaxis_cget(self, key):
+ return self.tk.call(self._w, 'yaxis', 'cget', '-' + key)
+ def yaxis_configure(self, option=None, **kw):
+ subcommand = (self._w, 'yaxis', 'configure')
+ return _doConfigure(self, subcommand, option, kw)
+ def yaxis_invtransform(self, value):
+ return self.tk.getdouble(self.tk.call(
+ self._w, 'yaxis', 'invtransform', value))
+ def yaxis_limits(self):
+ return self._getdoubles(self.tk.call(self._w, 'yaxis', 'limits'))
+ def yaxis_transform(self, value):
+ return self.tk.getint(self.tk.call(
+ self._w, 'yaxis', 'transform', value))
+ def yaxis_use(self, axisName = None):
+ return self.tk.call(self._w, 'yaxis', 'use', axisName)
+
+ def y2axis_cget(self, key):
+ return self.tk.call(self._w, 'y2axis', 'cget', '-' + key)
+ def y2axis_configure(self, option=None, **kw):
+ subcommand = (self._w, 'y2axis', 'configure')
+ return _doConfigure(self, subcommand, option, kw)
+ def y2axis_invtransform(self, value):
+ return self.tk.getdouble(self.tk.call(
+ self._w, 'y2axis', 'invtransform', value))
+ def y2axis_limits(self):
+ return self._getdoubles(self.tk.call(self._w, 'y2axis', 'limits'))
+ def y2axis_transform(self, value):
+ return self.tk.getint(self.tk.call(
+ self._w, 'y2axis', 'transform', value))
+ def y2axis_use(self, axisName = None):
+ return self.tk.call(self._w, 'y2axis', 'use', axisName)
+
+ def crosshairs_cget(self, key):
+ return self.tk.call(self._w, 'crosshairs', 'cget', '-' + key)
+ def crosshairs_configure(self, option=None, **kw):
+ subcommand = (self._w, 'crosshairs', 'configure')
+ return _doConfigure(self, subcommand, option, kw)
+ def crosshairs_off(self):
+ self.tk.call(self._w, 'crosshairs', 'off')
+ def crosshairs_on(self):
+ self.tk.call(self._w, 'crosshairs', 'on')
+ def crosshairs_toggle(self):
+ self.tk.call(self._w, 'crosshairs', 'toggle')
+
+ def element_activate(self, name, *args):
+ self.tk.call((self._w, 'element', 'activate', name) + args)
+ def element_bind(self, tagName, sequence=None, func=None, add=None):
+ return self._bind((self._w, 'element', 'bind', tagName),
+ sequence, func, add)
+ def element_unbind(self, tagName, sequence, funcid=None):
+ self.tk.call(self._w, 'element', 'bind', tagName, sequence, '')
+ if funcid:
+ self.deletecommand(funcid)
+
+ def element_cget(self, name, key):
+ return self.tk.call(self._w, 'element', 'cget', name, '-' + key)
+
+ def element_closest(self, x, y, *args, **kw):
+ var = 'python_private_1'
+ success = self.tk.getint(self.tk.call(
+ (self._w, 'element', 'closest', x, y, var) +
+ self._options(kw) + args))
+ if success:
+ rtn = {}
+ rtn['dist'] = self.tk.getdouble(self.tk.globalgetvar(var, 'dist'))
+ rtn['x'] = self.tk.getdouble(self.tk.globalgetvar(var, 'x'))
+ rtn['y'] = self.tk.getdouble(self.tk.globalgetvar(var, 'y'))
+ rtn['index'] = self.tk.getint(self.tk.globalgetvar(var, 'index'))
+ rtn['name'] = self.tk.globalgetvar(var, 'name')
+ return rtn
+ else:
+ return None
+
+ def element_configure(self, names, option=None, **kw):
+ # <names> may be a list of elemNames.
+ if type(names) is str:
+ names = [names]
+ subcommand = (self._w, 'element', 'configure') + tuple(names)
+ return _doConfigure(self, subcommand, option, kw)
+
+ def element_deactivate(self, *args):
+ self.tk.call((self._w, 'element', 'deactivate') + args)
+
+ def element_delete(self, *args):
+ self.tk.call((self._w, 'element', 'delete') + args)
+ def element_exists(self, name):
+ return self.tk.getboolean(
+ self.tk.call(self._w, 'element', 'exists', name))
+
+ def element_names(self, *args):
+ return self.tk.splitlist(
+ self.tk.call((self._w, 'element', 'names') + args))
+ def element_show(self, nameList=None):
+ if nameList is not None:
+ nameList = tuple(nameList)
+ return self.tk.splitlist(
+ self.tk.call(self._w, 'element', 'show', nameList))
+ def element_type(self, name):
+ return self.tk.call(self._w, 'element', 'type', name)
+
+ def grid_cget(self, key):
+ return self.tk.call(self._w, 'grid', 'cget', '-' + key)
+ def grid_configure(self, option=None, **kw):
+ subcommand = (self._w, 'grid', 'configure')
+ return _doConfigure(self, subcommand, option, kw)
+
+ def grid_off(self):
+ self.tk.call(self._w, 'grid', 'off')
+ def grid_on(self):
+ self.tk.call(self._w, 'grid', 'on')
+ def grid_toggle(self):
+ self.tk.call(self._w, 'grid', 'toggle')
+
+ def legend_activate(self, *args):
+ self.tk.call((self._w, 'legend', 'activate') + args)
+ def legend_bind(self, tagName, sequence=None, func=None, add=None):
+ return self._bind((self._w, 'legend', 'bind', tagName),
+ sequence, func, add)
+ def legend_unbind(self, tagName, sequence, funcid=None):
+ self.tk.call(self._w, 'legend', 'bind', tagName, sequence, '')
+ if funcid:
+ self.deletecommand(funcid)
+
+ def legend_cget(self, key):
+ return self.tk.call(self._w, 'legend', 'cget', '-' + key)
+ def legend_configure(self, option=None, **kw):
+ subcommand = (self._w, 'legend', 'configure')
+ return _doConfigure(self, subcommand, option, kw)
+ def legend_deactivate(self, *args):
+ self.tk.call((self._w, 'legend', 'deactivate') + args)
+ def legend_get(self, pos):
+ return self.tk.call(self._w, 'legend', 'get', pos)
+
+ def pen_cget(self, name, key):
+ return self.tk.call(self._w, 'pen', 'cget', name, '-' + key)
+ def pen_configure(self, names, option=None, **kw):
+ # <names> may be a list of penNames.
+ if type(names) is str:
+ names = [names]
+ subcommand = (self._w, 'pen', 'configure') + tuple(names)
+ return _doConfigure(self, subcommand, option, kw)
+ def pen_create(self, name, **kw):
+ self.tk.call((self._w, 'pen', 'create', name) + self._options(kw))
+ def pen_delete(self, *args):
+ self.tk.call((self._w, 'pen', 'delete') + args)
+ def pen_names(self, *args):
+ return self.tk.splitlist(self.tk.call((self._w, 'pen', 'names') + args))
+
+ def postscript_cget(self, key):
+ return self.tk.call(self._w, 'postscript', 'cget', '-' + key)
+ def postscript_configure(self, option=None, **kw):
+ subcommand = (self._w, 'postscript', 'configure')
+ return _doConfigure(self, subcommand, option, kw)
+ def postscript_output(self, fileName=None, **kw):
+ prefix = (self._w, 'postscript', 'output')
+ if fileName is None:
+ return self.tk.call(prefix + self._options(kw))
+ else:
+ self.tk.call(prefix + (fileName,) + self._options(kw))
+
+ def marker_after(self, first, second=None):
+ self.tk.call(self._w, 'marker', 'after', first, second)
+ def marker_before(self, first, second=None):
+ self.tk.call(self._w, 'marker', 'before', first, second)
+ def marker_bind(self, tagName, sequence=None, func=None, add=None):
+ return self._bind((self._w, 'marker', 'bind', tagName),
+ sequence, func, add)
+ def marker_unbind(self, tagName, sequence, funcid=None):
+ self.tk.call(self._w, 'marker', 'bind', tagName, sequence, '')
+ if funcid:
+ self.deletecommand(funcid)
+
+ def marker_cget(self, name, key):
+ return self.tk.call(self._w, 'marker', 'cget', name, '-' + key)
+ def marker_configure(self, names, option=None, **kw):
+ # <names> may be a list of markerIds.
+ if type(names) is str:
+ names = [names]
+ subcommand = (self._w, 'marker', 'configure') + tuple(names)
+ return _doConfigure(self, subcommand, option, kw)
+ def marker_create(self, type, **kw):
+ return self.tk.call(
+ (self._w, 'marker', 'create', type) + self._options(kw))
+
+ def marker_delete(self, *args):
+ self.tk.call((self._w, 'marker', 'delete') + args)
+ def marker_exists(self, name):
+ return self.tk.getboolean(
+ self.tk.call(self._w, 'marker', 'exists', name))
+ def marker_names(self, *args):
+ return self.tk.splitlist(
+ self.tk.call((self._w, 'marker', 'names') + args))
+ def marker_type(self, name):
+ type = self.tk.call(self._w, 'marker', 'type', name)
+ if type == '':
+ type = None
+ return type
+
+#=============================================================================
+class Stripchart(Graph):
+ # Wrapper for the blt stripchart widget, version 2.4.
+
+ def __init__(self, master=None, cnf={}, **kw):
+ _loadBlt(master)
+ tkinter.Widget.__init__(self, master, _chartCommand, cnf, kw)
+
+#=============================================================================
+class Tabset(tkinter.Widget):
+
+ # Wrapper for the blt TabSet widget, version 2.4.
+
+ def __init__(self, master=None, cnf={}, **kw):
+ _loadBlt(master)
+ tkinter.Widget.__init__(self, master, _tabsetCommand, cnf, kw)
+
+ def activate(self, tabIndex):
+ self.tk.call(self._w, 'activate', tabIndex)
+
+ # This is the 'bind' sub-command:
+ def tag_bind(self, tagName, sequence=None, func=None, add=None):
+ return self._bind((self._w, 'bind', tagName), sequence, func, add)
+
+ def tag_unbind(self, tagName, sequence, funcid=None):
+ self.tk.call(self._w, 'bind', tagName, sequence, '')
+ if funcid:
+ self.deletecommand(funcid)
+
+ def delete(self, first, last = None):
+ self.tk.call(self._w, 'delete', first, last)
+
+ # This is the 'focus' sub-command:
+ def tab_focus(self, tabIndex):
+ self.tk.call(self._w, 'focus', tabIndex)
+
+ def get(self, tabIndex):
+ return self.tk.call(self._w, 'get', tabIndex)
+
+ def index(self, tabIndex):
+ index = self.tk.call(self._w, 'index', tabIndex)
+ if index == '':
+ return None
+ else:
+ return self.tk.getint(self.tk.call(self._w, 'index', tabIndex))
+
+ def insert(self, position, name1, *names, **kw):
+ self.tk.call(
+ (self._w, 'insert', position, name1) + names + self._options(kw))
+
+ def invoke(self, tabIndex):
+ return self.tk.call(self._w, 'invoke', tabIndex)
+
+ def move(self, tabIndex1, beforeOrAfter, tabIndex2):
+ self.tk.call(self._w, 'move', tabIndex1, beforeOrAfter, tabIndex2)
+
+ def nearest(self, x, y):
+ return self.tk.call(self._w, 'nearest', x, y)
+
+ def scan_mark(self, x, y):
+ self.tk.call(self._w, 'scan', 'mark', x, y)
+
+ def scan_dragto(self, x, y):
+ self.tk.call(self._w, 'scan', 'dragto', x, y)
+
+ def see(self, index):
+ self.tk.call(self._w, 'see', index)
+
+ def see(self, tabIndex):
+ self.tk.call(self._w,'see',tabIndex)
+
+ def size(self):
+ return self.tk.getint(self.tk.call(self._w, 'size'))
+
+ def tab_cget(self, tabIndex, option):
+ if option[:1] != '-':
+ option = '-' + option
+ if option[-1:] == '_':
+ option = option[:-1]
+ return self.tk.call(self._w, 'tab', 'cget', tabIndex, option)
+
+ def tab_configure(self, tabIndexes, option=None, **kw):
+ # <tabIndexes> may be a list of tabs.
+ if type(tabIndexes) in (str, int):
+ tabIndexes = [tabIndexes]
+ subcommand = (self._w, 'tab', 'configure') + tuple(tabIndexes)
+ return _doConfigure(self, subcommand, option, kw)
+
+ def tab_names(self, *args):
+ return self.tk.splitlist(self.tk.call((self._w, 'tab', 'names') + args))
+
+ def tab_tearoff(self, tabIndex, newName = None):
+ if newName is None:
+ name = self.tk.call(self._w, 'tab', 'tearoff', tabIndex)
+ return self.nametowidget(name)
+ else:
+ self.tk.call(self._w, 'tab', 'tearoff', tabIndex, newName)
+
+ def view(self):
+ s = self.tk.call(self._w, 'view')
+ return tuple(map(self.tk.getint, self.tk.splitlist(s)))
+ def view_moveto(self, fraction):
+ self.tk.call(self._w, 'view', 'moveto', fraction)
+ def view_scroll(self, number, what):
+ self.tk.call(self._w, 'view', 'scroll', number, what)
diff --git a/PmwColor.py b/PmwColor.py
new file mode 100644
index 0000000..36aca1e
--- /dev/null
+++ b/PmwColor.py
@@ -0,0 +1,387 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: PmwColor.py
+# Description: Functions for converting colors and modifying the color scheme of
+# an application.
+# Source: http://sourceforge.net/projects/pmw/
+#
+# Copyright 1997-1999 Telstra Corporation Limited, Australia
+# Copyright 2000-2002 Really Good Software Pty Ltd, Australia
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+#-------------------------------------------------------------------------------
+import math
+import string
+import sys
+import tkinter
+
+_PI = math.pi
+_TWO_PI = _PI * 2
+_THIRD_PI = _PI / 3
+_SIXTH_PI = _PI / 6
+_MAX_RGB = float(256 * 256 - 1) # max size of rgb values returned from Tk
+
+def setscheme(root, background=None, **kw):
+ root = root._root()
+ palette = _calcPalette(*(root, background,), **kw)
+ for option, value in list(palette.items()):
+ root.option_add('*' + option, value, 'widgetDefault')
+
+def getdefaultpalette(root):
+ # Return the default values of all options, using the defaults
+ # from a few widgets.
+
+ ckbtn = tkinter.Checkbutton(root)
+ entry = tkinter.Entry(root)
+ scbar = tkinter.Scrollbar(root)
+
+ orig = {}
+ orig['activeBackground'] = str(ckbtn.configure('activebackground')[4])
+ orig['activeForeground'] = str(ckbtn.configure('activeforeground')[4])
+ orig['background'] = str(ckbtn.configure('background')[4])
+ orig['disabledForeground'] = str(ckbtn.configure('disabledforeground')[4])
+ orig['foreground'] = str(ckbtn.configure('foreground')[4])
+ orig['highlightBackground'] = str(ckbtn.configure('highlightbackground')[4])
+ orig['highlightColor'] = str(ckbtn.configure('highlightcolor')[4])
+ orig['insertBackground'] = str(entry.configure('insertbackground')[4])
+ orig['selectColor'] = str(ckbtn.configure('selectcolor')[4])
+ orig['selectBackground'] = str(entry.configure('selectbackground')[4])
+ orig['selectForeground'] = str(entry.configure('selectforeground')[4])
+ orig['troughColor'] = str(scbar.configure('troughcolor')[4])
+
+ ckbtn.destroy()
+ entry.destroy()
+ scbar.destroy()
+
+ return orig
+
+#======================================================================
+
+# Functions dealing with brightness, hue, saturation and intensity of colors.
+
+def changebrightness(root, colorName, brightness):
+ # Convert the color name into its hue and back into a color of the
+ # required brightness.
+
+ rgb = name2rgb(root, colorName)
+ hue, saturation, intensity = rgb2hsi(rgb)
+ if saturation == 0.0:
+ hue = None
+ return hue2name(hue, brightness)
+
+def hue2name(hue, brightness = None):
+ # Convert the requested hue and brightness into a color name. If
+ # hue is None, return a grey of the requested brightness.
+
+ if hue is None:
+ rgb = hsi2rgb(0.0, 0.0, brightness)
+ else:
+ while hue < 0:
+ hue = hue + _TWO_PI
+ while hue >= _TWO_PI:
+ hue = hue - _TWO_PI
+
+ rgb = hsi2rgb(hue, 1.0, 1.0)
+ if brightness is not None:
+ b = rgb2brightness(rgb)
+ i = 1.0 - (1.0 - brightness) * b
+ s = bhi2saturation(brightness, hue, i)
+ rgb = hsi2rgb(hue, s, i)
+
+ return rgb2name(rgb)
+
+def bhi2saturation(brightness, hue, intensity):
+ while hue < 0:
+ hue = hue + _TWO_PI
+ while hue >= _TWO_PI:
+ hue = hue - _TWO_PI
+ hue = hue / _THIRD_PI
+ f = hue - math.floor(hue)
+
+ pp = intensity
+ pq = intensity * f
+ pt = intensity - intensity * f
+ pv = 0
+
+ hue = int(hue)
+ if hue == 0: rgb = (pv, pt, pp)
+ elif hue == 1: rgb = (pq, pv, pp)
+ elif hue == 2: rgb = (pp, pv, pt)
+ elif hue == 3: rgb = (pp, pq, pv)
+ elif hue == 4: rgb = (pt, pp, pv)
+ elif hue == 5: rgb = (pv, pp, pq)
+
+ return (intensity - brightness) / rgb2brightness(rgb)
+
+def hsi2rgb(hue, saturation, intensity):
+ i = intensity
+ if saturation == 0:
+ rgb = [i, i, i]
+ else:
+ while hue < 0:
+ hue = hue + _TWO_PI
+ while hue >= _TWO_PI:
+ hue = hue - _TWO_PI
+ hue = hue / _THIRD_PI
+ f = hue - math.floor(hue)
+ p = i * (1.0 - saturation)
+ q = i * (1.0 - saturation * f)
+ t = i * (1.0 - saturation * (1.0 - f))
+
+ hue = int(hue)
+ if hue == 0: rgb = [i, t, p]
+ elif hue == 1: rgb = [q, i, p]
+ elif hue == 2: rgb = [p, i, t]
+ elif hue == 3: rgb = [p, q, i]
+ elif hue == 4: rgb = [t, p, i]
+ elif hue == 5: rgb = [i, p, q]
+
+ for index in range(3):
+ val = rgb[index]
+ if val < 0.0:
+ val = 0.0
+ if val > 1.0:
+ val = 1.0
+ rgb[index] = val
+
+ return rgb
+
+def average(rgb1, rgb2, fraction):
+ return (
+ rgb2[0] * fraction + rgb1[0] * (1.0 - fraction),
+ rgb2[1] * fraction + rgb1[1] * (1.0 - fraction),
+ rgb2[2] * fraction + rgb1[2] * (1.0 - fraction)
+ )
+
+def rgb2name(rgb):
+ return '#%02x%02x%02x' % \
+ (int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255))
+
+def rgb2brightness(rgb):
+ # Return the perceived grey level of the color
+ # (0.0 == black, 1.0 == white).
+
+ rf = 0.299
+ gf = 0.587
+ bf = 0.114
+ return rf * rgb[0] + gf * rgb[1] + bf * rgb[2]
+
+def rgb2hsi(rgb):
+ maxc = max(rgb[0], rgb[1], rgb[2])
+ minc = min(rgb[0], rgb[1], rgb[2])
+
+ intensity = maxc
+ if maxc != 0:
+ saturation = (maxc - minc) / maxc
+ else:
+ saturation = 0.0
+
+ hue = 0.0
+ if saturation != 0.0:
+ c = []
+ for index in range(3):
+ c.append((maxc - rgb[index]) / (maxc - minc))
+
+ if rgb[0] == maxc:
+ hue = c[2] - c[1]
+ elif rgb[1] == maxc:
+ hue = 2 + c[0] - c[2]
+ elif rgb[2] == maxc:
+ hue = 4 + c[1] - c[0]
+
+ hue = hue * _THIRD_PI
+ if hue < 0.0:
+ hue = hue + _TWO_PI
+
+ return (hue, saturation, intensity)
+
+def name2rgb(root, colorName, asInt = 0):
+ if colorName[0] == '#':
+ # Extract rgb information from the color name itself, assuming
+ # it is either #rgb, #rrggbb, #rrrgggbbb, or #rrrrggggbbbb
+ # This is useful, since tk may return incorrect rgb values if
+ # the colormap is full - it will return the rbg values of the
+ # closest color available.
+ colorName = colorName[1:]
+ digits = int( len(colorName) / 3)
+ factor = 16 ** (4 - digits)
+ rgb = (
+ int(colorName[0:digits], 16) * factor,
+ int(colorName[digits:digits * 2], 16) * factor,
+ int(colorName[digits * 2:digits * 3], 16) * factor,
+ )
+ else:
+ # We have no choice but to ask Tk what the rgb values are.
+ rgb = root.winfo_rgb(colorName)
+
+ if not asInt:
+ rgb = (rgb[0] / _MAX_RGB, rgb[1] / _MAX_RGB, rgb[2] / _MAX_RGB)
+ return rgb
+
+def _calcPalette(root, background=None, **kw):
+ # Create a map that has the complete new palette. If some colors
+ # aren't specified, compute them from other colors that are specified.
+ new = {}
+ for key, value in list(kw.items()):
+ new[key] = value
+ if background is not None:
+ new['background'] = background
+ if 'background' not in new:
+ raise ValueError('must specify a background color')
+
+ if 'foreground' not in new:
+ new['foreground'] = 'black'
+
+ bg = name2rgb(root, new['background'])
+ fg = name2rgb(root, new['foreground'])
+
+ for i in ('activeForeground', 'insertBackground', 'selectForeground',
+ 'highlightColor'):
+ if i not in new:
+ new[i] = new['foreground']
+
+ if 'disabledForeground' not in new:
+ newCol = average(bg, fg, 0.3)
+ new['disabledForeground'] = rgb2name(newCol)
+
+ if 'highlightBackground' not in new:
+ new['highlightBackground'] = new['background']
+
+ # Set <lighterBg> to a color that is a little lighter that the
+ # normal background. To do this, round each color component up by
+ # 9% or 1/3 of the way to full white, whichever is greater.
+ lighterBg = []
+ for i in range(3):
+ lighterBg.append(bg[i])
+ inc1 = lighterBg[i] * 0.09
+ inc2 = (1.0 - lighterBg[i]) / 3
+ if inc1 > inc2:
+ lighterBg[i] = lighterBg[i] + inc1
+ else:
+ lighterBg[i] = lighterBg[i] + inc2
+ if lighterBg[i] > 1.0:
+ lighterBg[i] = 1.0
+
+ # Set <darkerBg> to a color that is a little darker that the
+ # normal background.
+ darkerBg = (bg[0] * 0.9, bg[1] * 0.9, bg[2] * 0.9)
+
+ if 'activeBackground' not in new:
+ # If the foreground is dark, pick a light active background.
+ # If the foreground is light, pick a dark active background.
+ # XXX This has been disabled, since it does not look very
+ # good with dark backgrounds. If this is ever fixed, the
+ # selectBackground and troughColor options should also be fixed.
+
+ if rgb2brightness(fg) < 0.5:
+ new['activeBackground'] = rgb2name(lighterBg)
+ else:
+ new['activeBackground'] = rgb2name(lighterBg)
+
+ if 'selectBackground' not in new:
+ new['selectBackground'] = rgb2name(darkerBg)
+ if 'troughColor' not in new:
+ new['troughColor'] = rgb2name(darkerBg)
+ if 'selectColor' not in new:
+ new['selectColor'] = 'yellow'
+
+ return new
+
+def spectrum(numColors, correction = 1.0, saturation = 1.0, intensity = 1.0,
+ extraOrange = 1, returnHues = 0):
+ colorList = []
+ division = numColors / 7.0
+ for index in range(numColors):
+ if extraOrange:
+ if index < 2 * division:
+ hue = index / division
+ else:
+ hue = 2 + 2 * (index - 2 * division) / division
+ hue = hue * _SIXTH_PI
+ else:
+ hue = index * _TWO_PI / numColors
+ if returnHues:
+ colorList.append(hue)
+ else:
+ rgb = hsi2rgb(hue, saturation, intensity)
+ if correction != 1.0:
+ rgb = correct(rgb, correction)
+ name = rgb2name(rgb)
+ colorList.append(name)
+ return colorList
+
+def correct(rgb, correction):
+ correction = float(correction)
+ rtn = []
+ for index in range(3):
+ rtn.append((1 - (1 - rgb[index]) ** correction) ** (1 / correction))
+ return rtn
+
+#==============================================================================
+
+def _recolorTree(widget, oldpalette, newcolors):
+ # Change the colors in a widget and its descendants.
+
+ # Change the colors in <widget> and all of its descendants,
+ # according to the <newcolors> dictionary. It only modifies
+ # colors that have their default values as specified by the
+ # <oldpalette> variable. The keys of the <newcolors> dictionary
+ # are named after widget configuration options and the values are
+ # the new value for that option.
+
+ for dbOption in list(newcolors.keys()):
+ option = dbOption.lower()
+ try:
+ value = str(widget.cget(option))
+ except:
+ continue
+ if oldpalette is None or value == oldpalette[dbOption]:
+ widget.configure(*(), **{option : newcolors[dbOption]})
+
+ for child in widget.winfo_children():
+ _recolorTree(child, oldpalette, newcolors)
+
+def changecolor(widget, background=None, **kw):
+ root = widget._root()
+ if not hasattr(widget, '_Pmw_oldpalette'):
+ widget._Pmw_oldpalette = getdefaultpalette(root)
+ newpalette = _calcPalette(*(root, background,), **kw)
+ _recolorTree(widget, widget._Pmw_oldpalette, newpalette)
+ widget._Pmw_oldpalette = newpalette
+
+def bordercolors(root, colorName):
+ # This is the same method that Tk uses for shadows, in TkpGetShadows.
+
+ lightRGB = []
+ darkRGB = []
+ for value in name2rgb(root, colorName, 1):
+ value40pc = (14 * value) / 10
+ if value40pc > _MAX_RGB:
+ value40pc = _MAX_RGB
+ valueHalfWhite = (_MAX_RGB + value) / 2;
+ lightRGB.append(max(value40pc, valueHalfWhite))
+
+ darkValue = (60 * value) / 100
+ darkRGB.append(darkValue)
+
+ return (
+ '#%04x%04x%04x' % (lightRGB[0], lightRGB[1], lightRGB[2]),
+ '#%04x%04x%04x' % (darkRGB[0], darkRGB[1], darkRGB[2])
+ )
diff --git a/README b/README
new file mode 100644
index 0000000..2076acc
--- /dev/null
+++ b/README
@@ -0,0 +1,14 @@
+WSPR-4.0
+
+WSPR (pronounced "whisper") stands for "Weak Signal
+Propagation Reporter". The program generates and decodes
+a digital soundcard mode optimized for beacon-like
+transmissions on the LF, MF, and HF bands.
+
+PROJECT INFORMATION:
+-------------------
+- Project Manager: ...... Joe Taylor, K1JT, joe -AT- princeton -DOT- edu
+- Project Web Site: ..... http://physics.princeton.edu/pulsar/K1JT/
+- Mailing List: ......... wsjt-devel at lists.sourceforge.net
+- Project Source Code: .. http://sourceforge.net/projects/wsjt/
+- Yahoo Group: .......... https://groups.yahoo.com/neo/groups/wsjtgroup/info
diff --git a/WsprMod/__init__.py b/WsprMod/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/WsprMod/advanced.py b/WsprMod/advanced.py
new file mode 100644
index 0000000..f2b8825
--- /dev/null
+++ b/WsprMod/advanced.py
@@ -0,0 +1,138 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: advanced.py
+# Description:
+#
+# Copyright (C) 2001-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#-------------------------------------------------------------------------------
+from tkinter import *
+import Pmw
+from WsprMod import g
+from WsprMod import w
+import time
+import tkinter.messagebox
+
+def done():
+ root.withdraw()
+
+root=Toplevel()
+root.withdraw()
+root.protocol('WM_DELETE_WINDOW',done)
+if g.Win32: root.iconbitmap("wsjt.ico")
+root.title("Advanced")
+
+def advanced2(t):
+ root.geometry(t)
+ root.deiconify()
+ root.focus_set()
+
+idint=IntVar()
+bfofreq=IntVar()
+idint=IntVar()
+igrid6=IntVar()
+isc1=IntVar()
+isc1.set(0)
+encal=IntVar()
+fset=IntVar()
+Acal=DoubleVar()
+Bcal=DoubleVar()
+fset.set(0)
+
+#------------------------------------------------------ freqcal
+def freqcal(event=NONE):
+ if w.acom1.ncal==0:
+ bmeas.configure(bg='green')
+ w.acom1.ncal=1
+
+#-------------------------------------------------------- readab
+def readab(event=NONE):
+ try:
+ f=open('fcal.out','r')
+ s=f.readlines()
+ f.close
+ Acal.set(float(s[0]))
+ Bcal.set(float(s[1]))
+ encal.set(1)
+ except:
+ t='Cannot open fcal.out, or invalid data in file'
+ result=tkinter.messagebox.showwarning(message=t)
+ Acal.set(0.0)
+ Bcal.set(0.0)
+
+#-------------------------------------------------------- setfreq
+def setfreq(event=NONE):
+ fset.set(1)
+
+#-------------------------------------------------------- Create GUI widgets
+g1=Pmw.Group(root,tag_pyclass=None)
+
+t="""
+Important: please read the WSPR User's
+Guide (F3 key) before using features on
+this screen.
+"""
+lab1=Label(g1.interior(),text=t,justify=LEFT)
+lab1.pack(fill=X,expand=1,padx=5,pady=0)
+
+sc1=Scale(g1.interior(),orient=HORIZONTAL,length=200,from_=-30, \
+ to=0,variable=isc1,label='Reduce Tx Audio (dB)',relief=SOLID,bg='#FFC0CB')
+sc1.pack(side=TOP,padx=4,pady=4)
+
+cwid=Pmw.EntryField(g1.interior(),labelpos=W,label_text='CW ID (min):',
+ value='0',entry_textvariable=idint,entry_width=5,
+ validate={'validator':'numeric','min':0,'max':60})
+cwid.pack(fill=X,padx=2,pady=2)
+rxbfo=Pmw.EntryField(g1.interior(),labelpos=W,label_text='Rx BFO (Hz): ',
+ value='1500',entry_textvariable=bfofreq,entry_width=10,
+ validate={'validator':'real','min':-3000,'max':3000})
+rxbfo.pack(fill=X,padx=2,pady=2)
+enable_cal=Checkbutton(g1.interior(),text='Enable frequency correction',
+ variable=encal)
+enable_cal.pack(anchor=W,padx=5,pady=5)
+A_entry=Pmw.EntryField(g1.interior(),labelpos=W,label_text='A (Hz):',
+ value='0.0',entry_textvariable=Acal,entry_width=10,
+ validate={'validator':'real','min':-100.0,'max':100.0,
+ 'minstrict':0,'maxstrict':0})
+A_entry.pack(fill=X,padx=2,pady=2)
+B_entry=Pmw.EntryField(g1.interior(),labelpos=W,label_text='B (ppm):',
+ value='0.0',entry_textvariable=Bcal,entry_width=10,
+ validate={'validator':'real','min':-100.0,'max':100.0,
+ 'minstrict':0,'maxstrict':0})
+B_entry.pack(fill=X,padx=2,pady=2)
+Pmw.alignlabels([cwid,rxbfo,A_entry,B_entry])
+
+bmeas=Button(g1.interior(), text='Measure an audio frequency',command=freqcal,
+ width=26,padx=1,pady=2)
+bmeas.pack(padx=5,pady=5)
+
+breadab=Button(g1.interior(), text='Read A and B from fcal.out',command=readab,
+ width=26,padx=1,pady=2)
+breadab.pack(padx=5,pady=5)
+
+bsetfreq=Button(g1.interior(), text='Update rig frequency',command=setfreq,
+ width=26,padx=1,pady=2)
+bsetfreq.pack(padx=5,pady=5)
+bgrid6=Checkbutton(g1.interior(),text='Force transmission of 6-digit locator',
+ variable=igrid6)
+bgrid6.pack(anchor=W,padx=5,pady=2)
+
+f1=Frame(g1.interior(),width=100,height=10)
+f1.pack()
+g1.pack(side=LEFT,fill=BOTH,expand=1,padx=4,pady=4)
diff --git a/WsprMod/g.py b/WsprMod/g.py
new file mode 100644
index 0000000..afe8bae
--- /dev/null
+++ b/WsprMod/g.py
@@ -0,0 +1,48 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: iq.py
+# Description:
+#
+# Copyright (C) 2001-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#-------------------------------------------------------------------------------
+DFreq=0.0
+Freq=0.0
+PingTime=0.0
+PingFile="current"
+report="26"
+rms=1.0
+mode_change=0
+showspecjt=0
+g2font='courier 16 bold'
+
+#------------------------------------------------------ filetime
+def filetime(t):
+# i=t.rfind(".")
+ i=rfnd(t,".")
+ t=t[:i][-6:]
+# t=t[0:2]+":"+t[2:4]+":"+t[4:6]
+ return t
+
+#------------------------------------------------------ rfnd
+#Temporary workaround to replace t.rfind(c)
+def rfnd(t,c):
+ for i in range(len(t)-1,0,-1):
+ if t[i:i+1]==c: return i
+ return -1
diff --git a/WsprMod/hopping.py b/WsprMod/hopping.py
new file mode 100644
index 0000000..0408ed1
--- /dev/null
+++ b/WsprMod/hopping.py
@@ -0,0 +1,143 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: hopping.py
+# Description:
+#
+# Copyright (C) 2001-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#-------------------------------------------------------------------------------
+from tkinter import *
+import Pmw
+from WsprMod import g
+from WsprMod import w
+import os,time
+import tkinter.messagebox
+from functools import partial
+
+def done():
+ root.withdraw()
+
+root=Toplevel()
+root.withdraw()
+root.protocol('WM_DELETE_WINDOW',done)
+if g.Win32: root.iconbitmap("wsjt.ico")
+root.title("Band Hopping")
+
+def hopping2(t):
+ root.geometry(t)
+ root.deiconify()
+ root.focus_set()
+
+# bands, labeled 1 to 14 (and 15 for 'other')
+bandlabels=['dummy','600 m','160 m','80 m','60 m','40 m','30 m',\
+ '20 m','17 m','15 m','12 m','10 m','6 m','4 m','2 m',\
+ 'Other']
+
+coord_bands=IntVar()
+coord_bands.set(1)
+hopping=IntVar()
+hopping.set(0)
+hoppingconfigured=IntVar()
+hoppingconfigured.set(0)
+bhopping =list(range(len(bandlabels)))
+shopping =list(range(len(bandlabels)))
+lhopping =list(range(len(bandlabels)))
+hoppingflag=list(range(len(bandlabels)))
+hoppingpctx=list(range(len(bandlabels)))
+btuneup =list(range(len(bandlabels)))
+tuneupflag =list(range(len(bandlabels)))
+
+#-------------------------------------------------------- Create GUI widgets
+g1=Pmw.Group(root,tag_pyclass=None)
+r=0
+lband=Label(g1.interior(),text='Band')
+lband.grid(row=r,column=0,padx=2,pady=2,sticky='SW')
+lpctx=Label(g1.interior(),text='Tx fraction (%)')
+lpctx.grid(row=r,column=1,padx=2,pady=2,sticky='SW')
+llab=Label(g1.interior(),text=' ') # to make space for the percentage labels without repacking
+llab.grid(row=r,column=2,padx=2,pady=2,sticky='SW')
+ltune=Label(g1.interior(),text='Tuneup')
+ltune.grid(row=r,column=3,padx=2,pady=2,sticky='SW')
+
+def globalupdate():
+ global hopping
+ localhopping=0
+ for band in range(1,len(bandlabels)):
+ if hoppingflag[band].get()!=0: localhopping=1
+ hoppingconfigured.set(localhopping)
+ if not localhopping: hopping.set(0)
+
+def toggle(band):
+ globalupdate()
+
+def chpctx(band, event):
+ pctx = hoppingpctx[band].get()
+ t = "%s" % pctx
+ lhopping[band].configure(text=t)
+
+for r in range(1,16):
+ bcmd = partial(toggle, r)
+ scmd = partial(chpctx, r)
+ hoppingflag[r] = IntVar()
+ hoppingflag[r].set(0)
+ hoppingpctx[r] = IntVar()
+ hoppingpctx[r].set(0)
+ tuneupflag[r] = IntVar()
+ tuneupflag[r].set(0)
+ bhopping[r]=Checkbutton(g1.interior(),text=bandlabels[r],command=bcmd, \
+ variable=hoppingflag[r])
+ bhopping[r].grid(row=r,column=0,padx=2,pady=3,sticky='SW')
+ shopping[r]=Scale(g1.interior(),orient=HORIZONTAL,length=200,from_=0, \
+ to=100,command=scmd,variable=hoppingpctx[r],showvalue=0)
+ shopping[r].grid(row=r,column=1,padx=2,pady=2,sticky='SW')
+ lhopping[r]=Label(g1.interior(),text='0')
+ lhopping[r].grid(row=r,column=2,padx=2,pady=2,sticky='SW')
+ btuneup[r]=Checkbutton(g1.interior(),text="",command=bcmd, \
+ variable=tuneupflag[r])
+ btuneup[r].grid(row=r,column=3,padx=2,pady=3,sticky='SW')
+
+cbcoord=Checkbutton(g1.interior(),text='Coordinated hopping',variable=coord_bands)
+cbcoord.grid(row=18,column=1,padx=2,pady=2,sticky='S')
+g1.pack(side=LEFT,fill=X,expand=0,padx=4,pady=4)
+
+def save_params(appdir):
+ f=open(appdir+'/hopping.ini',mode='w')
+ t="%d %d\n" % (hopping.get(),coord_bands.get())
+ f.write(t)
+ for r in range(1,16):
+ t="%4s %2d %5d %2d\n" % (bandlabels[r][:-2], hoppingflag[r].get(), \
+ hoppingpctx[r].get(),tuneupflag[r].get())
+ f.write(t)
+ f.close()
+
+def restore_params(appdir):
+ if os.path.isfile(appdir+'/hopping.ini'):
+ try:
+ f=open(appdir+'/hopping.ini',mode='r')
+ s=f.readlines()
+ f.close()
+ hopping.set(int(s[0][0:1]))
+ coord_bands.set(int(s[0][2:3]))
+ for r in range(1,16):
+ hoppingflag[r].set(int(s[r][6:7]))
+ hoppingpctx[r].set(int(s[r][8:13]))
+ tuneupflag[r].set(int(s[r][13:16]))
+ globalupdate()
+ except:
+ print('Error reading hopping.ini.')
diff --git a/WsprMod/iq.py b/WsprMod/iq.py
new file mode 100644
index 0000000..4ed78d1
--- /dev/null
+++ b/WsprMod/iq.py
@@ -0,0 +1,189 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: iq.py
+# Description:
+#
+# Copyright (C) 2001-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#-------------------------------------------------------------------------------
+from tkinter import *
+import Pmw
+from WsprMod import g
+from WsprMod import w
+import time
+import tkinter.messagebox
+import pickle
+
+def done():
+ root.withdraw()
+
+root=Toplevel()
+root.withdraw()
+root.protocol('WM_DELETE_WINDOW',done)
+if g.Win32: root.iconbitmap("wsjt.ico")
+root.title("I-Q Mode")
+
+def iq2(t):
+ root.geometry(t)
+ root.deiconify()
+ root.focus_set()
+ j=ib.get()
+ lab0.configure(text=str(mb[j])+' m')
+
+iqmode=IntVar()
+iqrx=IntVar()
+iqtx=IntVar()
+fiq=IntVar()
+iqrxapp=IntVar()
+iqrxadj=IntVar()
+
+isc2=IntVar()
+isc2.set(0)
+isc2a=IntVar()
+isc2a.set(0)
+isc3=IntVar()
+isc3.set(0)
+isc3a=IntVar()
+isc3a.set(0)
+
+ib=IntVar()
+gain=DoubleVar()
+phdeg=DoubleVar()
+mb=[0,600,160,80,60,40,30,20,17,15,12,10,6,4,2,0]
+tbal=[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]
+tpha=[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]
+rbal=[1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]
+rpha=[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]
+allbands=0
+
+def saveband(event=NONE):
+ global allbands,tbal,tpha,rbal,rpha
+ if allbands:
+ for j in range(1,15):
+ tbal[j]=isc2.get() + 0.02*isc2a.get()
+ tpha[j]=isc3.get() + 0.02*isc3a.get()
+ rbal[j]=w.acom1.gain
+ rpha[j]=57.2957795*w.acom1.phase
+ else:
+ j=ib.get()
+ tbal[j]=isc2.get() + 0.02*isc2a.get()
+ tpha[j]=isc3.get() + 0.02*isc3a.get()
+ rbal[j]=w.acom1.gain
+ rpha[j]=57.2957795*w.acom1.phase
+
+ f=open(g.appdir+'/iqpickle',mode='w')
+ pickle.dump(tbal,f)
+ pickle.dump(tpha,f)
+ pickle.dump(rbal,f)
+ pickle.dump(rpha,f)
+ f.close()
+
+def saveall(event=NONE):
+ global allbands
+ allbands=1
+ saveband()
+ allbands=0
+
+def restore():
+ global tbal,tpha,rbal,rpha
+ try:
+ f=open(g.appdir+'/iqpickle',mode='r')
+ tbal=pickle.load(f)
+ tpha=pickle.load(f)
+ rbal=pickle.load(f)
+ rpha=pickle.load(f)
+ f.close()
+ except:
+ pass
+ newband()
+
+def newband():
+ j=ib.get()
+ lab0.configure(text=str(mb[j])+' m')
+ w.acom1.gain=rbal[j]
+ w.acom1.phase=rpha[j]/57.2957795
+ isc2.set(int(tbal[j]))
+ isc2a.set(int((tbal[j]-isc2.get())/0.02))
+ isc3.set(int(tpha[j]))
+ isc3a.set(int((tpha[j]-isc3.get())/0.02))
+
+#-------------------------------------------------------- Create GUI widgets
+g1=Pmw.Group(root,tag_pyclass=None)
+
+lab0=Label(g1.interior(),text='160 m',bg='yellow',pady=5)
+lab0.place(x=180,y=40, anchor='e')
+#lab0.pack(anchor=W,padx=5,pady=4)
+
+
+biqmode=Checkbutton(g1.interior(),text='Enable I/Q mode',variable=iqmode)
+biqmode.pack(anchor=W,padx=5,pady=2)
+
+biqtx=Checkbutton(g1.interior(),text='Reverse Tx I,Q',variable=iqtx)
+biqtx.pack(anchor=W,padx=5,pady=2)
+
+biqrx=Checkbutton(g1.interior(),text='Reverse Rx I,Q',variable=iqrx)
+biqrx.pack(anchor=W,padx=5,pady=2)
+
+biqrxapp=Checkbutton(g1.interior(),text='Apply Rx phasing corrections', \
+ variable=iqrxapp)
+biqrxapp.pack(anchor=W,padx=5,pady=2)
+
+biqrxadj=Checkbutton(g1.interior(),text='Adjust Rx phasing', \
+ variable=iqrxadj)
+biqrxadj.pack(anchor=W,padx=5,pady=2)
+
+lab1=Label(g1.interior(),text='',justify=LEFT)
+lab1.pack(anchor=W,padx=5,pady=4)
+
+fiq_entry=Pmw.EntryField(g1.interior(),labelpos=W,label_text='Fiq (Hz): ',
+ value='12000',entry_textvariable=fiq,entry_width=10,
+ validate={'validator':'integer','min':-24000,'max':24000,
+ 'minstrict':0,'maxstrict':0})
+fiq_entry.pack(fill=X,padx=2,pady=4)
+
+sc2=Scale(g1.interior(),orient=HORIZONTAL,length=200,from_=-30, \
+ to=30,variable=isc2,label='Tx I/Q Balance (0.1 dB)', \
+ relief=SOLID,bg='#EEDD82')
+sc2.pack(side=TOP,padx=4,pady=2)
+
+sc2a=Scale(g1.interior(),orient=HORIZONTAL,length=200,from_=-50, \
+ to=50,variable=isc2a,label='Tx I/Q Balance (0.002 dB)', \
+ relief=SOLID,bg='#EEDD82')
+sc2a.pack(side=TOP,padx=4,pady=2)
+
+sc3=Scale(g1.interior(),orient=HORIZONTAL,length=200,from_=-20, \
+ to=20,variable=isc3,label='Tx Phase (deg)', \
+ relief=SOLID,bg='#AFeeee')
+sc3.pack(side=TOP,padx=4,pady=2)
+sc3a=Scale(g1.interior(),orient=HORIZONTAL,length=200,from_=-50, \
+ to=50,variable=isc3a,label='Tx Phase (0.02 deg)', \
+ relief=SOLID,bg='#AFeeee')
+sc3a.pack(side=TOP,padx=4,pady=2)
+
+bsave=Button(g1.interior(), text='Save for this band',command=saveband,
+ width=32,padx=1,pady=2)
+bsave.pack(padx=2,pady=4)
+
+bsaveall=Button(g1.interior(), text='Save for all bands',command=saveall,
+ width=32,padx=1,pady=2)
+bsaveall.pack(padx=2,pady=4)
+
+f1=Frame(g1.interior(),width=100,height=1)
+f1.pack()
+g1.pack(side=LEFT,fill=BOTH,expand=1,padx=4,pady=4)
diff --git a/WsprMod/options.py b/WsprMod/options.py
new file mode 100644
index 0000000..66e46bc
--- /dev/null
+++ b/WsprMod/options.py
@@ -0,0 +1,231 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: options.py
+# Description:
+#
+# Copyright (C) 2001-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#-------------------------------------------------------------------------------
+from tkinter import *
+import Pmw
+from WsprMod import g
+import math
+
+def done():
+ root.withdraw()
+
+root=Toplevel()
+root.withdraw()
+root.protocol('WM_DELETE_WINDOW',done)
+if g.Win32: root.iconbitmap("wsjt.ico")
+root.title("Station parameters")
+
+balloon=Pmw.Balloon(root)
+
+#------------------------------------------------------ dbm_balloon
+def dbm_balloon():
+ if dBm.get()<0:
+ uW=int(round(math.pow(10.0,0.1*(30+dBm.get()))))
+ if uW==501: uW=500
+ t="%d uW" % (uW,)
+ else:
+ mW=int(round(math.pow(10.0,0.1*dBm.get())))
+ if(mW<1000):
+ if mW==501: mW=500
+ t="%d mW" % (mW,)
+ else:
+ W=int(0.001*mW + 0.5)
+ if W==501: W=500
+ t="%d W" % (W,)
+ balloon.bind(cbpwr,t)
+
+def options2(t):
+ root.geometry(t)
+ root.deiconify()
+ root.focus_set()
+
+#-------------------------------------------------------- Create GUI widgets
+g1=Pmw.Group(root,tag_pyclass=None)
+ptt_port=IntVar()
+CatPort=StringVar()
+PttPort=StringVar()
+CatPort.set('None')
+PttPort.set('None')
+ndevin=IntVar()
+ndevout=IntVar()
+DevinName=StringVar()
+DevoutName=StringVar()
+dBm=IntVar()
+dBm.set(37)
+pttmode=StringVar()
+serial_rate=IntVar()
+serial_rate.set(4800)
+databits=IntVar()
+databits.set(8)
+stopbits=IntVar()
+stopbits.set(2)
+serial_handshake=StringVar()
+cat_enable=IntVar()
+rig=StringVar()
+rig.set('214 Kenwood TS-2000')
+rignum=IntVar()
+inbad=IntVar()
+outbad=IntVar()
+
+pttmode.set('DTR')
+serial_handshake.set('None')
+
+pttlist=("CAT","DTR","RTS","VOX")
+baudlist=(1200,4800,9600,19200,38400,57600)
+hslist=("None","XONXOFF","Hardware")
+pwrlist=(-30,-27,-23,-20,-17,-13,-10,-7,-3, \
+ 0,3,7,10,13,17,20,23,27,30,33,37,40,43,47,50,53,57,60)
+
+if g.Win32:
+ serialportlist=("None","COM1","COM2","COM3","COM4","COM5","COM6", \
+ "COM7","COM8","COM9","COM10","COM11","COM12","COM13","COM14", \
+ "COM15","USB")
+else:
+ serialportlist=("None","/dev/ttyS0","/dev/ttyS1","/dev/ttyUSB0", \
+ "/dev/ttyUSB1","/dev/ttyUSB2","/dev/ttyUSB3","/dev/ttyUSB4", \
+ "/dev/ttyUSB5","/dev/ttyUSB6","/dev/ttyUSB7","/dev/ttyUSB8")
+
+datalist=(7,8)
+stoplist=(1,2)
+indevlist=[]
+outdevlist=[]
+riglist=[]
+
+MyCall=StringVar()
+MyGrid=StringVar()
+
+try:
+ f=open('audio_caps','r')
+ s=f.readlines()
+ f.close
+ t="Input Devices:\n"
+ for i in range(len(s)):
+ col=s[i].split()
+ if int(col[1])>0:
+ t=str(i) + s[i][29:]
+ t=t[:len(t)-1]
+ indevlist.append(t)
+ for i in range(len(s)):
+ col=s[i].split()
+ if int(col[2])>0:
+ t=str(i) + s[i][29:]
+ t=t[:len(t)-1]
+ outdevlist.append(t)
+except:
+ pass
+
+try:
+ f=open('hamlib_rig_numbers','r')
+ s=f.readlines()
+ f.close
+ for i in range(len(s)):
+ t=s[i]
+ riglist.append(t[:len(t)-1])
+except:
+ pass
+
+#------------------------------------------------------ audin
+def audin(event=NONE):
+ g.DevinName.set(DevinName.get())
+ g.ndevin.set(int(DevinName.get()[:2]))
+
+#------------------------------------------------------ audout
+def audout(event=NONE):
+ g.DevoutName.set(DevoutName.get())
+ g.ndevout.set(int(DevoutName.get()[:2]))
+
+#------------------------------------------------------ rig_number
+def rig_number(event=NONE):
+ rignum.set(int(rig.get()[:4]))
+
+#------------------------------------------------------- chkcall
+def chkcall(t):
+ r=-1
+ n=len(t)
+ if n>=3 and n<=10:
+ i1=t.count('/')
+ i2=t.find('/')
+ if i1==1 and i2>0:
+ t=t[:i2-1]+t[i2+1:]
+ if t.isalnum() and t.find(' ')<0:
+ r=1
+ return r
+
+#------------------------------------------------------- chkgrid
+def chkgrid(t):
+ r=-1
+ n=len(t)
+ if n==4 or n==6:
+ if int(t[0:1],36)>=10 and int(t[0:1],36)<=27 and \
+ int(t[1:2],36)>=10 and int(t[1:2],36)<=27 and \
+ int(t[2:3],36)>=0 and int(t[2:3],36)<=9 and \
+ int(t[3:4],36)>=0 and int(t[3:4],36)<=9: r=1
+ if r==1 and n==6:
+ r=-1
+ if int(t[4:5],36)>=10 and int(t[4:5],36)<=33 and \
+ int(t[5:6],36)>=10 and int(t[5:6],36)<=33: r=1
+ return r
+
+lcall=Pmw.EntryField(g1.interior(),labelpos=W,label_text='Call:',
+ value='',entry_textvariable=MyCall,entry_width=8,
+ validate=chkcall)
+lgrid=Pmw.EntryField(g1.interior(),labelpos=W,label_text='Grid:',
+ value='',entry_textvariable=MyGrid,entry_width=5,
+ validate=chkgrid)
+audioin=Pmw.ComboBox(g1.interior(),labelpos=W,label_text='Audio In:',
+ entry_textvariable=DevinName,entry_width=30,
+ scrolledlist_items=indevlist,selectioncommand=audin)
+audioout=Pmw.ComboBox(g1.interior(),labelpos=W,label_text='Audio Out:',
+ entry_textvariable=DevoutName,entry_width=30,
+ scrolledlist_items=outdevlist,selectioncommand=audout)
+cbpwr=Pmw.ComboBox(g1.interior(),labelpos=W,label_text='Power (dBm):',
+ entry_textvariable=dBm,entry_width=4,scrolledlist_items=pwrlist)
+cbptt=Pmw.ComboBox(g1.interior(),labelpos=W,label_text='PTT method:',
+ entry_textvariable=pttmode,entry_width=4,scrolledlist_items=pttlist)
+ptt_port=Pmw.ComboBox(g1.interior(),labelpos=W,label_text='PTT port:',
+ entry_textvariable=PttPort,entry_width=12,\
+ scrolledlist_items=serialportlist)
+encat=Checkbutton(g1.interior(),text='Enable CAT',variable=cat_enable)
+cat_port=Pmw.ComboBox(g1.interior(),labelpos=W,label_text='CAT port:',
+ entry_textvariable=CatPort,entry_width=12,\
+ scrolledlist_items=serialportlist)
+lrignum=Pmw.ComboBox(g1.interior(),labelpos=W,label_text='Rig number:',
+ entry_textvariable=rig,entry_width=30,
+ scrolledlist_items=riglist,selectioncommand=rig_number)
+cbbaud=Pmw.ComboBox(g1.interior(),labelpos=W,label_text='Serial rate:',
+ entry_textvariable=serial_rate,entry_width=4,scrolledlist_items=baudlist)
+cbdata=Pmw.ComboBox(g1.interior(),labelpos=W,label_text='Data bits:',
+ entry_textvariable=databits,entry_width=4,scrolledlist_items=datalist)
+cbstop=Pmw.ComboBox(g1.interior(),labelpos=W,label_text='Stop bits:',
+ entry_textvariable=stopbits,entry_width=4,scrolledlist_items=stoplist)
+cbhs=Pmw.ComboBox(g1.interior(),labelpos=W,label_text='Handshake:',
+ entry_textvariable=serial_handshake,entry_width=4,scrolledlist_items=hslist)
+widgets = (lcall,lgrid,audioin,audioout,cbpwr,cbptt,ptt_port,\
+ encat,cat_port,lrignum,cbbaud,cbdata,cbstop,cbhs)
+for widget in widgets:
+ widget.pack(fill=X,expand=1,padx=10,pady=2)
+Pmw.alignlabels(widgets)
+f1=Frame(g1.interior(),width=100,height=10)
+f1.pack()
+g1.pack(side=LEFT,fill=BOTH,expand=1,padx=4,pady=4)
diff --git a/WsprMod/palettes.py b/WsprMod/palettes.py
new file mode 100644
index 0000000..a7a9770
--- /dev/null
+++ b/WsprMod/palettes.py
@@ -0,0 +1,1599 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: palettes.py
+# Description:
+#
+# Copyright (C) 2001-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#-------------------------------------------------------------------------------
+import string
+import WsprMod.g
+from PIL import ImagePalette
+
+# Several colormaps
+colormapblue = """
+ 0.0000 0.0000 0.0000
+ 0.0902 0.0902 0.2558
+ 0.1176 0.1176 0.2694
+ 0.1412 0.1412 0.2820
+ 0.1569 0.1569 0.2938
+ 0.1725 0.1725 0.3049
+ 0.1843 0.1843 0.3154
+ 0.1961 0.1961 0.3254
+ 0.2039 0.2039 0.3349
+ 0.2157 0.2157 0.3440
+ 0.2235 0.2235 0.3528
+ 0.2314 0.2314 0.3612
+ 0.2392 0.2392 0.3693
+ 0.2471 0.2471 0.3772
+ 0.2549 0.2549 0.3848
+ 0.2588 0.2588 0.3921
+ 0.2667 0.2667 0.3992
+ 0.2706 0.2706 0.4061
+ 0.2784 0.2784 0.4129
+ 0.2824 0.2824 0.4194
+ 0.2902 0.2902 0.4258
+ 0.2941 0.2941 0.4319
+ 0.2980 0.2980 0.4380
+ 0.3059 0.3059 0.4439
+ 0.3098 0.3098 0.4496
+ 0.3137 0.3137 0.4553
+ 0.3176 0.3176 0.4608
+ 0.3216 0.3216 0.4661
+ 0.3294 0.3294 0.4714
+ 0.3333 0.3333 0.4765
+ 0.3373 0.3373 0.4815
+ 0.3412 0.3412 0.4865
+ 0.3451 0.3451 0.4913
+ 0.3490 0.3490 0.4960
+ 0.3529 0.3529 0.5006
+ 0.3569 0.3569 0.5052
+ 0.3608 0.3608 0.5096
+ 0.3647 0.3647 0.5140
+ 0.3686 0.3686 0.5183
+ 0.3725 0.3725 0.5225
+ 0.3765 0.3765 0.5266
+ 0.3804 0.3804 0.5306
+ 0.3843 0.3843 0.5346
+ 0.3843 0.3843 0.5385
+ 0.3882 0.3882 0.5423
+ 0.3922 0.3922 0.5460
+ 0.3961 0.3961 0.5497
+ 0.4000 0.4000 0.5533
+ 0.4039 0.4039 0.5569
+ 0.4078 0.4078 0.5603
+ 0.4118 0.4118 0.5638
+ 0.4118 0.4118 0.5671
+ 0.4157 0.4157 0.5704
+ 0.4196 0.4196 0.5736
+ 0.4235 0.4235 0.5768
+ 0.4275 0.4275 0.5799
+ 0.4314 0.4314 0.5829
+ 0.4314 0.4314 0.5859
+ 0.4353 0.4353 0.5889
+ 0.4392 0.4392 0.5917
+ 0.4431 0.4431 0.5946
+ 0.4471 0.4471 0.5973
+ 0.4471 0.4471 0.6001
+ 0.4510 0.4510 0.6027
+ 0.4549 0.4549 0.6053
+ 0.4588 0.4588 0.6079
+ 0.4627 0.4627 0.6104
+ 0.4627 0.4627 0.6129
+ 0.4667 0.4667 0.6153
+ 0.4706 0.4706 0.6176
+ 0.4745 0.4745 0.6199
+ 0.4745 0.4745 0.6222
+ 0.4784 0.4784 0.6244
+ 0.4824 0.4824 0.6266
+ 0.4863 0.4863 0.6287
+ 0.4863 0.4863 0.6308
+ 0.4902 0.4902 0.6328
+ 0.4941 0.4941 0.6348
+ 0.4980 0.4980 0.6367
+ 0.5020 0.5020 0.6386
+ 0.5020 0.5020 0.6404
+ 0.5059 0.5059 0.6422
+ 0.5098 0.5098 0.6440
+ 0.5098 0.5098 0.6457
+ 0.5137 0.5137 0.6474
+ 0.5176 0.5176 0.6490
+ 0.5216 0.5216 0.6506
+ 0.5216 0.5216 0.6521
+ 0.5255 0.5255 0.6536
+ 0.5294 0.5294 0.6551
+ 0.5333 0.5333 0.6565
+ 0.5333 0.5333 0.6578
+ 0.5373 0.5373 0.6591
+ 0.5412 0.5412 0.6604
+ 0.5451 0.5451 0.6617
+ 0.5451 0.5451 0.6629
+ 0.5490 0.5490 0.6640
+ 0.5529 0.5529 0.6651
+ 0.5569 0.5569 0.6662
+ 0.5569 0.5569 0.6672
+ 0.5608 0.5608 0.6682
+ 0.5647 0.5647 0.6692
+ 0.5647 0.5647 0.6701
+ 0.5686 0.5686 0.6710
+ 0.5725 0.5725 0.6718
+ 0.5765 0.5765 0.6726
+ 0.5765 0.5765 0.6733
+ 0.5804 0.5804 0.6740
+ 0.5843 0.5843 0.6747
+ 0.5843 0.5843 0.6753
+ 0.5882 0.5882 0.6759
+ 0.5922 0.5922 0.6765
+ 0.5961 0.5961 0.6770
+ 0.5961 0.5961 0.6774
+ 0.6000 0.6000 0.6779
+ 0.6039 0.6039 0.6783
+ 0.6039 0.6039 0.6786
+ 0.6078 0.6078 0.6789
+ 0.6118 0.6118 0.6792
+ 0.6157 0.6157 0.6794
+ 0.6157 0.6157 0.6796
+ 0.6196 0.6196 0.6798
+ 0.6235 0.6235 0.6799
+ 0.6235 0.6235 0.6800
+ 0.6275 0.6275 0.6800
+ 0.6314 0.6314 0.6800
+ 0.6353 0.6353 0.6799
+ 0.6353 0.6353 0.6799
+ 0.6392 0.6392 0.6797
+ 0.6431 0.6431 0.6796
+ 0.6431 0.6431 0.6794
+ 0.6471 0.6471 0.6791
+ 0.6510 0.6510 0.6789
+ 0.6549 0.6549 0.6785
+ 0.6549 0.6549 0.6782
+ 0.6588 0.6588 0.6778
+ 0.6627 0.6627 0.6773
+ 0.6627 0.6627 0.6769
+ 0.6667 0.6667 0.6763
+ 0.6706 0.6706 0.6758
+ 0.6745 0.6745 0.6752
+ 0.6745 0.6745 0.6746
+ 0.6784 0.6784 0.6739
+ 0.6824 0.6824 0.6732
+ 0.6824 0.6824 0.6724
+ 0.6863 0.6863 0.6716
+ 0.6902 0.6902 0.6708
+ 0.6941 0.6941 0.6699
+ 0.6941 0.6941 0.6690
+ 0.6980 0.6980 0.6680
+ 0.7020 0.7020 0.6670
+ 0.7020 0.7020 0.6660
+ 0.7059 0.7059 0.6649
+ 0.7098 0.7098 0.6638
+ 0.7098 0.7098 0.6626
+ 0.7137 0.7137 0.6614
+ 0.7176 0.7176 0.6601
+ 0.7216 0.7216 0.6589
+ 0.7216 0.7216 0.6575
+ 0.7255 0.7255 0.6561
+ 0.7294 0.7294 0.6547
+ 0.7294 0.7294 0.6533
+ 0.7333 0.7333 0.6518
+ 0.7373 0.7373 0.6502
+ 0.7412 0.7412 0.6486
+ 0.7412 0.7412 0.6470
+ 0.7451 0.7451 0.6453
+ 0.7490 0.7490 0.6436
+ 0.7490 0.7490 0.6418
+ 0.7529 0.7529 0.6400
+ 0.7569 0.7569 0.6382
+ 0.7608 0.7608 0.6363
+ 0.7608 0.7608 0.6343
+ 0.7647 0.7647 0.6324
+ 0.7686 0.7686 0.6303
+ 0.7686 0.7686 0.6282
+ 0.7725 0.7725 0.6261
+ 0.7765 0.7765 0.6239
+ 0.7804 0.7804 0.6217
+ 0.7804 0.7804 0.6194
+ 0.7843 0.7843 0.6171
+ 0.7882 0.7882 0.6147
+ 0.7882 0.7882 0.6123
+ 0.7922 0.7922 0.6098
+ 0.7961 0.7961 0.6073
+ 0.8000 0.8000 0.6047
+ 0.8000 0.8000 0.6021
+ 0.8039 0.8039 0.5994
+ 0.8078 0.8078 0.5967
+ 0.8078 0.8078 0.5939
+ 0.8118 0.8118 0.5911
+ 0.8157 0.8157 0.5882
+ 0.8196 0.8196 0.5853
+ 0.8196 0.8196 0.5823
+ 0.8235 0.8235 0.5792
+ 0.8275 0.8275 0.5761
+ 0.8275 0.8275 0.5729
+ 0.8314 0.8314 0.5697
+ 0.8353 0.8353 0.5664
+ 0.8392 0.8392 0.5630
+ 0.8392 0.8392 0.5596
+ 0.8431 0.8431 0.5561
+ 0.8471 0.8471 0.5525
+ 0.8471 0.8471 0.5489
+ 0.8510 0.8510 0.5452
+ 0.8549 0.8549 0.5414
+ 0.8588 0.8588 0.5376
+ 0.8588 0.8588 0.5337
+ 0.8627 0.8627 0.5297
+ 0.8667 0.8667 0.5257
+ 0.8667 0.8667 0.5215
+ 0.8706 0.8706 0.5173
+ 0.8745 0.8745 0.5130
+ 0.8784 0.8784 0.5086
+ 0.8784 0.8784 0.5042
+ 0.8824 0.8824 0.4996
+ 0.8863 0.8863 0.4950
+ 0.8863 0.8863 0.4902
+ 0.8902 0.8902 0.4854
+ 0.8941 0.8941 0.4804
+ 0.8980 0.8980 0.4754
+ 0.8980 0.8980 0.4702
+ 0.9020 0.9020 0.4649
+ 0.9059 0.9059 0.4595
+ 0.9098 0.9098 0.4540
+ 0.9098 0.9098 0.4484
+ 0.9137 0.9137 0.4426
+ 0.9176 0.9176 0.4366
+ 0.9176 0.9176 0.4306
+ 0.9216 0.9216 0.4243
+ 0.9255 0.9255 0.4179
+ 0.9294 0.9294 0.4114
+ 0.9294 0.9294 0.4046
+ 0.9333 0.9333 0.3977
+ 0.9373 0.9373 0.3905
+ 0.9373 0.9373 0.3831
+ 0.9412 0.9412 0.3754
+ 0.9451 0.9451 0.3675
+ 0.9490 0.9490 0.3594
+ 0.9490 0.9490 0.3509
+ 0.9529 0.9529 0.3420
+ 0.9569 0.9569 0.3328
+ 0.9608 0.9608 0.3232
+ 0.9608 0.9608 0.3131
+ 0.9647 0.9647 0.3024
+ 0.9686 0.9686 0.2912
+ 0.9686 0.9686 0.2792
+ 0.9725 0.9725 0.2664
+ 0.9765 0.9765 0.2526
+ 0.9804 0.9804 0.2375
+ 0.9804 0.9804 0.2208
+ 0.9843 0.9843 0.2020
+ 0.9882 0.9882 0.1800
+ 1.0000 0.0000 0.0000
+ 1.0000 1.0000 0.0000
+ 0.0000 1.0000 0.0000
+"""
+colormapgray0 = """
+ 0.0000 0.0000 0.0000
+ 0.0039 0.0039 0.0039
+ 0.0078 0.0078 0.0078
+ 0.0118 0.0118 0.0118
+ 0.0157 0.0157 0.0157
+ 0.0196 0.0196 0.0196
+ 0.0235 0.0235 0.0235
+ 0.0275 0.0275 0.0275
+ 0.0314 0.0314 0.0314
+ 0.0353 0.0353 0.0353
+ 0.0392 0.0392 0.0392
+ 0.0431 0.0431 0.0431
+ 0.0471 0.0471 0.0471
+ 0.0510 0.0510 0.0510
+ 0.0549 0.0549 0.0549
+ 0.0588 0.0588 0.0588
+ 0.0627 0.0627 0.0627
+ 0.0667 0.0667 0.0667
+ 0.0706 0.0706 0.0706
+ 0.0745 0.0745 0.0745
+ 0.0784 0.0784 0.0784
+ 0.0824 0.0824 0.0824
+ 0.0863 0.0863 0.0863
+ 0.0902 0.0902 0.0902
+ 0.0941 0.0941 0.0941
+ 0.0980 0.0980 0.0980
+ 0.1020 0.1020 0.1020
+ 0.1059 0.1059 0.1059
+ 0.1098 0.1098 0.1098
+ 0.1137 0.1137 0.1137
+ 0.1176 0.1176 0.1176
+ 0.1216 0.1216 0.1216
+ 0.1255 0.1255 0.1255
+ 0.1294 0.1294 0.1294
+ 0.1333 0.1333 0.1333
+ 0.1373 0.1373 0.1373
+ 0.1412 0.1412 0.1412
+ 0.1451 0.1451 0.1451
+ 0.1490 0.1490 0.1490
+ 0.1529 0.1529 0.1529
+ 0.1569 0.1569 0.1569
+ 0.1608 0.1608 0.1608
+ 0.1647 0.1647 0.1647
+ 0.1686 0.1686 0.1686
+ 0.1725 0.1725 0.1725
+ 0.1765 0.1765 0.1765
+ 0.1804 0.1804 0.1804
+ 0.1843 0.1843 0.1843
+ 0.1882 0.1882 0.1882
+ 0.1922 0.1922 0.1922
+ 0.1961 0.1961 0.1961
+ 0.2000 0.2000 0.2000
+ 0.2039 0.2039 0.2039
+ 0.2078 0.2078 0.2078
+ 0.2118 0.2118 0.2118
+ 0.2157 0.2157 0.2157
+ 0.2196 0.2196 0.2196
+ 0.2235 0.2235 0.2235
+ 0.2275 0.2275 0.2275
+ 0.2314 0.2314 0.2314
+ 0.2353 0.2353 0.2353
+ 0.2392 0.2392 0.2392
+ 0.2431 0.2431 0.2431
+ 0.2471 0.2471 0.2471
+ 0.2510 0.2510 0.2510
+ 0.2549 0.2549 0.2549
+ 0.2588 0.2588 0.2588
+ 0.2627 0.2627 0.2627
+ 0.2667 0.2667 0.2667
+ 0.2706 0.2706 0.2706
+ 0.2745 0.2745 0.2745
+ 0.2784 0.2784 0.2784
+ 0.2824 0.2824 0.2824
+ 0.2863 0.2863 0.2863
+ 0.2902 0.2902 0.2902
+ 0.2941 0.2941 0.2941
+ 0.2980 0.2980 0.2980
+ 0.3020 0.3020 0.3020
+ 0.3059 0.3059 0.3059
+ 0.3098 0.3098 0.3098
+ 0.3137 0.3137 0.3137
+ 0.3176 0.3176 0.3176
+ 0.3216 0.3216 0.3216
+ 0.3255 0.3255 0.3255
+ 0.3294 0.3294 0.3294
+ 0.3333 0.3333 0.3333
+ 0.3373 0.3373 0.3373
+ 0.3412 0.3412 0.3412
+ 0.3451 0.3451 0.3451
+ 0.3490 0.3490 0.3490
+ 0.3529 0.3529 0.3529
+ 0.3569 0.3569 0.3569
+ 0.3608 0.3608 0.3608
+ 0.3647 0.3647 0.3647
+ 0.3686 0.3686 0.3686
+ 0.3725 0.3725 0.3725
+ 0.3765 0.3765 0.3765
+ 0.3804 0.3804 0.3804
+ 0.3843 0.3843 0.3843
+ 0.3882 0.3882 0.3882
+ 0.3922 0.3922 0.3922
+ 0.3961 0.3961 0.3961
+ 0.4000 0.4000 0.4000
+ 0.4039 0.4039 0.4039
+ 0.4078 0.4078 0.4078
+ 0.4118 0.4118 0.4118
+ 0.4157 0.4157 0.4157
+ 0.4196 0.4196 0.4196
+ 0.4235 0.4235 0.4235
+ 0.4275 0.4275 0.4275
+ 0.4314 0.4314 0.4314
+ 0.4353 0.4353 0.4353
+ 0.4392 0.4392 0.4392
+ 0.4431 0.4431 0.4431
+ 0.4471 0.4471 0.4471
+ 0.4510 0.4510 0.4510
+ 0.4549 0.4549 0.4549
+ 0.4588 0.4588 0.4588
+ 0.4627 0.4627 0.4627
+ 0.4667 0.4667 0.4667
+ 0.4706 0.4706 0.4706
+ 0.4745 0.4745 0.4745
+ 0.4784 0.4784 0.4784
+ 0.4824 0.4824 0.4824
+ 0.4863 0.4863 0.4863
+ 0.4902 0.4902 0.4902
+ 0.4941 0.4941 0.4941
+ 0.4980 0.4980 0.4980
+ 0.5020 0.5020 0.5020
+ 0.5059 0.5059 0.5059
+ 0.5098 0.5098 0.5098
+ 0.5137 0.5137 0.5137
+ 0.5176 0.5176 0.5176
+ 0.5216 0.5216 0.5216
+ 0.5255 0.5255 0.5255
+ 0.5294 0.5294 0.5294
+ 0.5333 0.5333 0.5333
+ 0.5373 0.5373 0.5373
+ 0.5412 0.5412 0.5412
+ 0.5451 0.5451 0.5451
+ 0.5490 0.5490 0.5490
+ 0.5529 0.5529 0.5529
+ 0.5569 0.5569 0.5569
+ 0.5608 0.5608 0.5608
+ 0.5647 0.5647 0.5647
+ 0.5686 0.5686 0.5686
+ 0.5725 0.5725 0.5725
+ 0.5765 0.5765 0.5765
+ 0.5804 0.5804 0.5804
+ 0.5843 0.5843 0.5843
+ 0.5882 0.5882 0.5882
+ 0.5922 0.5922 0.5922
+ 0.5961 0.5961 0.5961
+ 0.6000 0.6000 0.6000
+ 0.6039 0.6039 0.6039
+ 0.6078 0.6078 0.6078
+ 0.6118 0.6118 0.6118
+ 0.6157 0.6157 0.6157
+ 0.6196 0.6196 0.6196
+ 0.6235 0.6235 0.6235
+ 0.6275 0.6275 0.6275
+ 0.6314 0.6314 0.6314
+ 0.6353 0.6353 0.6353
+ 0.6392 0.6392 0.6392
+ 0.6431 0.6431 0.6431
+ 0.6471 0.6471 0.6471
+ 0.6510 0.6510 0.6510
+ 0.6549 0.6549 0.6549
+ 0.6588 0.6588 0.6588
+ 0.6627 0.6627 0.6627
+ 0.6667 0.6667 0.6667
+ 0.6706 0.6706 0.6706
+ 0.6745 0.6745 0.6745
+ 0.6784 0.6784 0.6784
+ 0.6824 0.6824 0.6824
+ 0.6863 0.6863 0.6863
+ 0.6902 0.6902 0.6902
+ 0.6941 0.6941 0.6941
+ 0.6980 0.6980 0.6980
+ 0.7020 0.7020 0.7020
+ 0.7059 0.7059 0.7059
+ 0.7098 0.7098 0.7098
+ 0.7137 0.7137 0.7137
+ 0.7176 0.7176 0.7176
+ 0.7216 0.7216 0.7216
+ 0.7255 0.7255 0.7255
+ 0.7294 0.7294 0.7294
+ 0.7333 0.7333 0.7333
+ 0.7373 0.7373 0.7373
+ 0.7412 0.7412 0.7412
+ 0.7451 0.7451 0.7451
+ 0.7490 0.7490 0.7490
+ 0.7529 0.7529 0.7529
+ 0.7569 0.7569 0.7569
+ 0.7608 0.7608 0.7608
+ 0.7647 0.7647 0.7647
+ 0.7686 0.7686 0.7686
+ 0.7725 0.7725 0.7725
+ 0.7765 0.7765 0.7765
+ 0.7804 0.7804 0.7804
+ 0.7843 0.7843 0.7843
+ 0.7882 0.7882 0.7882
+ 0.7922 0.7922 0.7922
+ 0.7961 0.7961 0.7961
+ 0.8000 0.8000 0.8000
+ 0.8039 0.8039 0.8039
+ 0.8078 0.8078 0.8078
+ 0.8118 0.8118 0.8118
+ 0.8157 0.8157 0.8157
+ 0.8196 0.8196 0.8196
+ 0.8235 0.8235 0.8235
+ 0.8275 0.8275 0.8275
+ 0.8314 0.8314 0.8314
+ 0.8353 0.8353 0.8353
+ 0.8392 0.8392 0.8392
+ 0.8431 0.8431 0.8431
+ 0.8471 0.8471 0.8471
+ 0.8510 0.8510 0.8510
+ 0.8549 0.8549 0.8549
+ 0.8588 0.8588 0.8588
+ 0.8627 0.8627 0.8627
+ 0.8667 0.8667 0.8667
+ 0.8706 0.8706 0.8706
+ 0.8745 0.8745 0.8745
+ 0.8784 0.8784 0.8784
+ 0.8824 0.8824 0.8824
+ 0.8863 0.8863 0.8863
+ 0.8902 0.8902 0.8902
+ 0.8941 0.8941 0.8941
+ 0.8980 0.8980 0.8980
+ 0.9020 0.9020 0.9020
+ 0.9059 0.9059 0.9059
+ 0.9098 0.9098 0.9098
+ 0.9137 0.9137 0.9137
+ 0.9176 0.9176 0.9176
+ 0.9216 0.9216 0.9216
+ 0.9255 0.9255 0.9255
+ 0.9294 0.9294 0.9294
+ 0.9333 0.9333 0.9333
+ 0.9373 0.9373 0.9373
+ 0.9412 0.9412 0.9412
+ 0.9451 0.9451 0.9451
+ 0.9490 0.9490 0.9490
+ 0.9529 0.9529 0.9529
+ 0.9569 0.9569 0.9569
+ 0.9608 0.9608 0.9608
+ 0.9647 0.9647 0.9647
+ 0.9686 0.9686 0.9686
+ 0.9725 0.9725 0.9725
+ 0.9765 0.9765 0.9765
+ 0.9804 0.9804 0.9804
+ 0.9843 0.9843 0.9843
+ 0.9882 0.9882 0.9882
+ 1.0000 0.0000 0.0000
+ 1.0000 1.0000 0.0000
+ 0.0000 1.0000 0.0000
+"""
+
+colormapHot="""
+ 0.0000 0.0000 0.0000
+ 0.0118 0.0000 0.0000
+ 0.0235 0.0000 0.0000
+ 0.0353 0.0000 0.0000
+ 0.0471 0.0000 0.0000
+ 0.0588 0.0000 0.0000
+ 0.0706 0.0000 0.0000
+ 0.0824 0.0000 0.0000
+ 0.0941 0.0000 0.0000
+ 0.1059 0.0000 0.0000
+ 0.1176 0.0000 0.0000
+ 0.1294 0.0000 0.0000
+ 0.1412 0.0000 0.0000
+ 0.1529 0.0000 0.0000
+ 0.1647 0.0000 0.0000
+ 0.1765 0.0000 0.0000
+ 0.1882 0.0000 0.0000
+ 0.2000 0.0000 0.0000
+ 0.2118 0.0000 0.0000
+ 0.2235 0.0000 0.0000
+ 0.2353 0.0000 0.0000
+ 0.2471 0.0000 0.0000
+ 0.2588 0.0000 0.0000
+ 0.2706 0.0000 0.0000
+ 0.2824 0.0000 0.0000
+ 0.2941 0.0000 0.0000
+ 0.3059 0.0000 0.0000
+ 0.3176 0.0000 0.0000
+ 0.3294 0.0000 0.0000
+ 0.3412 0.0000 0.0000
+ 0.3529 0.0000 0.0000
+ 0.3647 0.0000 0.0000
+ 0.3765 0.0000 0.0000
+ 0.3882 0.0000 0.0000
+ 0.4000 0.0000 0.0000
+ 0.4118 0.0000 0.0000
+ 0.4235 0.0000 0.0000
+ 0.4353 0.0000 0.0000
+ 0.4471 0.0000 0.0000
+ 0.4588 0.0000 0.0000
+ 0.4706 0.0000 0.0000
+ 0.4824 0.0000 0.0000
+ 0.4941 0.0000 0.0000
+ 0.5059 0.0000 0.0000
+ 0.5176 0.0000 0.0000
+ 0.5294 0.0000 0.0000
+ 0.5412 0.0000 0.0000
+ 0.5529 0.0000 0.0000
+ 0.5647 0.0000 0.0000
+ 0.5765 0.0000 0.0000
+ 0.5882 0.0000 0.0000
+ 0.6000 0.0000 0.0000
+ 0.6118 0.0000 0.0000
+ 0.6235 0.0000 0.0000
+ 0.6353 0.0000 0.0000
+ 0.6471 0.0000 0.0000
+ 0.6588 0.0000 0.0000
+ 0.6706 0.0000 0.0000
+ 0.6824 0.0000 0.0000
+ 0.6941 0.0000 0.0000
+ 0.7059 0.0000 0.0000
+ 0.7176 0.0000 0.0000
+ 0.7294 0.0000 0.0000
+ 0.7412 0.0000 0.0000
+ 0.7529 0.0000 0.0000
+ 0.7647 0.0000 0.0000
+ 0.7765 0.0000 0.0000
+ 0.7882 0.0000 0.0000
+ 0.8000 0.0000 0.0000
+ 0.8118 0.0000 0.0000
+ 0.8235 0.0000 0.0000
+ 0.8353 0.0000 0.0000
+ 0.8471 0.0000 0.0000
+ 0.8588 0.0000 0.0000
+ 0.8706 0.0000 0.0000
+ 0.8824 0.0000 0.0000
+ 0.8941 0.0000 0.0000
+ 0.9059 0.0000 0.0000
+ 0.9176 0.0000 0.0000
+ 0.9294 0.0000 0.0000
+ 0.9412 0.0000 0.0000
+ 0.9529 0.0000 0.0000
+ 0.9647 0.0000 0.0000
+ 0.9765 0.0000 0.0000
+ 0.9882 0.0000 0.0000
+ 1.0000 0.0000 0.0000
+ 1.0000 0.0118 0.0000
+ 1.0000 0.0235 0.0000
+ 1.0000 0.0353 0.0000
+ 1.0000 0.0471 0.0000
+ 1.0000 0.0588 0.0000
+ 1.0000 0.0706 0.0000
+ 1.0000 0.0824 0.0000
+ 1.0000 0.0941 0.0000
+ 1.0000 0.1059 0.0000
+ 1.0000 0.1176 0.0000
+ 1.0000 0.1294 0.0000
+ 1.0000 0.1412 0.0000
+ 1.0000 0.1529 0.0000
+ 1.0000 0.1647 0.0000
+ 1.0000 0.1765 0.0000
+ 1.0000 0.1882 0.0000
+ 1.0000 0.2000 0.0000
+ 1.0000 0.2118 0.0000
+ 1.0000 0.2235 0.0000
+ 1.0000 0.2353 0.0000
+ 1.0000 0.2471 0.0000
+ 1.0000 0.2588 0.0000
+ 1.0000 0.2706 0.0000
+ 1.0000 0.2824 0.0000
+ 1.0000 0.2941 0.0000
+ 1.0000 0.3059 0.0000
+ 1.0000 0.3176 0.0000
+ 1.0000 0.3294 0.0000
+ 1.0000 0.3412 0.0000
+ 1.0000 0.3529 0.0000
+ 1.0000 0.3647 0.0000
+ 1.0000 0.3765 0.0000
+ 1.0000 0.3882 0.0000
+ 1.0000 0.4000 0.0000
+ 1.0000 0.4118 0.0000
+ 1.0000 0.4235 0.0000
+ 1.0000 0.4353 0.0000
+ 1.0000 0.4471 0.0000
+ 1.0000 0.4588 0.0000
+ 1.0000 0.4706 0.0000
+ 1.0000 0.4824 0.0000
+ 1.0000 0.4941 0.0000
+ 1.0000 0.5059 0.0000
+ 1.0000 0.5176 0.0000
+ 1.0000 0.5294 0.0000
+ 1.0000 0.5412 0.0000
+ 1.0000 0.5529 0.0000
+ 1.0000 0.5647 0.0000
+ 1.0000 0.5765 0.0000
+ 1.0000 0.5882 0.0000
+ 1.0000 0.6000 0.0000
+ 1.0000 0.6118 0.0000
+ 1.0000 0.6235 0.0000
+ 1.0000 0.6353 0.0000
+ 1.0000 0.6471 0.0000
+ 1.0000 0.6588 0.0000
+ 1.0000 0.6706 0.0000
+ 1.0000 0.6824 0.0000
+ 1.0000 0.6941 0.0000
+ 1.0000 0.7059 0.0000
+ 1.0000 0.7176 0.0000
+ 1.0000 0.7294 0.0000
+ 1.0000 0.7412 0.0000
+ 1.0000 0.7529 0.0000
+ 1.0000 0.7647 0.0000
+ 1.0000 0.7765 0.0000
+ 1.0000 0.7882 0.0000
+ 1.0000 0.8000 0.0000
+ 1.0000 0.8118 0.0000
+ 1.0000 0.8235 0.0000
+ 1.0000 0.8353 0.0000
+ 1.0000 0.8471 0.0000
+ 1.0000 0.8588 0.0000
+ 1.0000 0.8706 0.0000
+ 1.0000 0.8824 0.0000
+ 1.0000 0.8941 0.0000
+ 1.0000 0.9059 0.0000
+ 1.0000 0.9176 0.0000
+ 1.0000 0.9294 0.0000
+ 1.0000 0.9412 0.0000
+ 1.0000 0.9529 0.0000
+ 1.0000 0.9647 0.0000
+ 1.0000 0.9765 0.0000
+ 1.0000 0.9882 0.0000
+ 1.0000 1.0000 0.0000
+ 1.0000 1.0000 0.0118
+ 1.0000 1.0000 0.0235
+ 1.0000 1.0000 0.0353
+ 1.0000 1.0000 0.0471
+ 1.0000 1.0000 0.0588
+ 1.0000 1.0000 0.0706
+ 1.0000 1.0000 0.0824
+ 1.0000 1.0000 0.0941
+ 1.0000 1.0000 0.1059
+ 1.0000 1.0000 0.1176
+ 1.0000 1.0000 0.1294
+ 1.0000 1.0000 0.1412
+ 1.0000 1.0000 0.1529
+ 1.0000 1.0000 0.1647
+ 1.0000 1.0000 0.1765
+ 1.0000 1.0000 0.1882
+ 1.0000 1.0000 0.2000
+ 1.0000 1.0000 0.2118
+ 1.0000 1.0000 0.2235
+ 1.0000 1.0000 0.2353
+ 1.0000 1.0000 0.2471
+ 1.0000 1.0000 0.2588
+ 1.0000 1.0000 0.2706
+ 1.0000 1.0000 0.2824
+ 1.0000 1.0000 0.2941
+ 1.0000 1.0000 0.3059
+ 1.0000 1.0000 0.3176
+ 1.0000 1.0000 0.3294
+ 1.0000 1.0000 0.3412
+ 1.0000 1.0000 0.3529
+ 1.0000 1.0000 0.3647
+ 1.0000 1.0000 0.3765
+ 1.0000 1.0000 0.3882
+ 1.0000 1.0000 0.4000
+ 1.0000 1.0000 0.4118
+ 1.0000 1.0000 0.4235
+ 1.0000 1.0000 0.4353
+ 1.0000 1.0000 0.4471
+ 1.0000 1.0000 0.4588
+ 1.0000 1.0000 0.4706
+ 1.0000 1.0000 0.4824
+ 1.0000 1.0000 0.4941
+ 1.0000 1.0000 0.5059
+ 1.0000 1.0000 0.5176
+ 1.0000 1.0000 0.5294
+ 1.0000 1.0000 0.5412
+ 1.0000 1.0000 0.5529
+ 1.0000 1.0000 0.5647
+ 1.0000 1.0000 0.5765
+ 1.0000 1.0000 0.5882
+ 1.0000 1.0000 0.6000
+ 1.0000 1.0000 0.6118
+ 1.0000 1.0000 0.6235
+ 1.0000 1.0000 0.6353
+ 1.0000 1.0000 0.6471
+ 1.0000 1.0000 0.6588
+ 1.0000 1.0000 0.6706
+ 1.0000 1.0000 0.6824
+ 1.0000 1.0000 0.6941
+ 1.0000 1.0000 0.7059
+ 1.0000 1.0000 0.7176
+ 1.0000 1.0000 0.7294
+ 1.0000 1.0000 0.7412
+ 1.0000 1.0000 0.7529
+ 1.0000 1.0000 0.7647
+ 1.0000 1.0000 0.7765
+ 1.0000 1.0000 0.7882
+ 1.0000 1.0000 0.8000
+ 1.0000 1.0000 0.8118
+ 1.0000 1.0000 0.8235
+ 1.0000 1.0000 0.8353
+ 1.0000 1.0000 0.8471
+ 1.0000 1.0000 0.8588
+ 1.0000 1.0000 0.8706
+ 1.0000 1.0000 0.8824
+ 1.0000 1.0000 0.8941
+ 1.0000 1.0000 0.9059
+ 1.0000 1.0000 0.9176
+ 1.0000 1.0000 0.9294
+ 1.0000 1.0000 0.9412
+ 1.0000 1.0000 0.9529
+ 1.0000 1.0000 0.9647
+ 1.0000 0.0000 0.0000
+ 1.0000 1.0000 0.0000
+ 0.0000 1.0000 0.0000
+"""
+
+colormapAFMHot="""
+ 0.0000 0.0000 0.0000
+ 0.0078 0.0000 0.0000
+ 0.0157 0.0000 0.0000
+ 0.0235 0.0000 0.0000
+ 0.0314 0.0000 0.0000
+ 0.0392 0.0000 0.0000
+ 0.0471 0.0000 0.0000
+ 0.0549 0.0000 0.0000
+ 0.0627 0.0000 0.0000
+ 0.0706 0.0000 0.0000
+ 0.0784 0.0000 0.0000
+ 0.0863 0.0000 0.0000
+ 0.0941 0.0000 0.0000
+ 0.1020 0.0000 0.0000
+ 0.1098 0.0000 0.0000
+ 0.1176 0.0000 0.0000
+ 0.1255 0.0000 0.0000
+ 0.1333 0.0000 0.0000
+ 0.1412 0.0000 0.0000
+ 0.1490 0.0000 0.0000
+ 0.1569 0.0000 0.0000
+ 0.1647 0.0000 0.0000
+ 0.1725 0.0000 0.0000
+ 0.1804 0.0000 0.0000
+ 0.1882 0.0000 0.0000
+ 0.1961 0.0000 0.0000
+ 0.2039 0.0000 0.0000
+ 0.2118 0.0000 0.0000
+ 0.2196 0.0000 0.0000
+ 0.2275 0.0000 0.0000
+ 0.2353 0.0000 0.0000
+ 0.2431 0.0000 0.0000
+ 0.2510 0.0000 0.0000
+ 0.2588 0.0000 0.0000
+ 0.2667 0.0000 0.0000
+ 0.2745 0.0000 0.0000
+ 0.2824 0.0000 0.0000
+ 0.2902 0.0000 0.0000
+ 0.2980 0.0000 0.0000
+ 0.3059 0.0000 0.0000
+ 0.3137 0.0000 0.0000
+ 0.3216 0.0000 0.0000
+ 0.3294 0.0000 0.0000
+ 0.3373 0.0000 0.0000
+ 0.3451 0.0000 0.0000
+ 0.3529 0.0000 0.0000
+ 0.3608 0.0000 0.0000
+ 0.3686 0.0000 0.0000
+ 0.3765 0.0000 0.0000
+ 0.3843 0.0000 0.0000
+ 0.3922 0.0000 0.0000
+ 0.4000 0.0000 0.0000
+ 0.4078 0.0000 0.0000
+ 0.4157 0.0000 0.0000
+ 0.4235 0.0000 0.0000
+ 0.4314 0.0000 0.0000
+ 0.4392 0.0000 0.0000
+ 0.4471 0.0000 0.0000
+ 0.4549 0.0000 0.0000
+ 0.4627 0.0000 0.0000
+ 0.4706 0.0000 0.0000
+ 0.4784 0.0000 0.0000
+ 0.4863 0.0000 0.0000
+ 0.4941 0.0000 0.0000
+ 0.5020 0.0000 0.0000
+ 0.5098 0.0098 0.0000
+ 0.5176 0.0176 0.0000
+ 0.5255 0.0255 0.0000
+ 0.5333 0.0333 0.0000
+ 0.5412 0.0412 0.0000
+ 0.5490 0.0490 0.0000
+ 0.5569 0.0569 0.0000
+ 0.5647 0.0647 0.0000
+ 0.5725 0.0725 0.0000
+ 0.5804 0.0804 0.0000
+ 0.5882 0.0882 0.0000
+ 0.5961 0.0961 0.0000
+ 0.6039 0.1039 0.0000
+ 0.6118 0.1118 0.0000
+ 0.6196 0.1196 0.0000
+ 0.6275 0.1275 0.0000
+ 0.6353 0.1353 0.0000
+ 0.6431 0.1431 0.0000
+ 0.6510 0.1510 0.0000
+ 0.6588 0.1588 0.0000
+ 0.6667 0.1667 0.0000
+ 0.6745 0.1745 0.0000
+ 0.6824 0.1824 0.0000
+ 0.6902 0.1902 0.0000
+ 0.6980 0.1980 0.0000
+ 0.7059 0.2059 0.0000
+ 0.7137 0.2137 0.0000
+ 0.7216 0.2216 0.0000
+ 0.7294 0.2294 0.0000
+ 0.7373 0.2373 0.0000
+ 0.7451 0.2451 0.0000
+ 0.7529 0.2529 0.0000
+ 0.7608 0.2608 0.0000
+ 0.7686 0.2686 0.0000
+ 0.7765 0.2765 0.0000
+ 0.7843 0.2843 0.0000
+ 0.7922 0.2922 0.0000
+ 0.8000 0.3000 0.0000
+ 0.8078 0.3078 0.0000
+ 0.8157 0.3157 0.0000
+ 0.8235 0.3235 0.0000
+ 0.8314 0.3314 0.0000
+ 0.8392 0.3392 0.0000
+ 0.8471 0.3471 0.0000
+ 0.8549 0.3549 0.0000
+ 0.8627 0.3627 0.0000
+ 0.8706 0.3706 0.0000
+ 0.8784 0.3784 0.0000
+ 0.8863 0.3863 0.0000
+ 0.8941 0.3941 0.0000
+ 0.9020 0.4020 0.0000
+ 0.9098 0.4098 0.0000
+ 0.9176 0.4176 0.0000
+ 0.9255 0.4255 0.0000
+ 0.9333 0.4333 0.0000
+ 0.9412 0.4412 0.0000
+ 0.9490 0.4490 0.0000
+ 0.9569 0.4569 0.0000
+ 0.9647 0.4647 0.0000
+ 0.9725 0.4725 0.0000
+ 0.9804 0.4804 0.0000
+ 0.9882 0.4882 0.0000
+ 0.9961 0.4961 0.0000
+ 1.0000 0.5039 0.0000
+ 1.0000 0.5118 0.0118
+ 1.0000 0.5196 0.0196
+ 1.0000 0.5275 0.0275
+ 1.0000 0.5353 0.0353
+ 1.0000 0.5431 0.0431
+ 1.0000 0.5510 0.0510
+ 1.0000 0.5588 0.0588
+ 1.0000 0.5667 0.0667
+ 1.0000 0.5745 0.0745
+ 1.0000 0.5824 0.0824
+ 1.0000 0.5902 0.0902
+ 1.0000 0.5980 0.0980
+ 1.0000 0.6059 0.1059
+ 1.0000 0.6137 0.1137
+ 1.0000 0.6216 0.1216
+ 1.0000 0.6294 0.1294
+ 1.0000 0.6373 0.1373
+ 1.0000 0.6451 0.1451
+ 1.0000 0.6529 0.1529
+ 1.0000 0.6608 0.1608
+ 1.0000 0.6686 0.1686
+ 1.0000 0.6765 0.1765
+ 1.0000 0.6843 0.1843
+ 1.0000 0.6922 0.1922
+ 1.0000 0.7000 0.2000
+ 1.0000 0.7078 0.2078
+ 1.0000 0.7157 0.2157
+ 1.0000 0.7235 0.2235
+ 1.0000 0.7314 0.2314
+ 1.0000 0.7392 0.2392
+ 1.0000 0.7471 0.2471
+ 1.0000 0.7549 0.2549
+ 1.0000 0.7627 0.2627
+ 1.0000 0.7706 0.2706
+ 1.0000 0.7784 0.2784
+ 1.0000 0.7863 0.2863
+ 1.0000 0.7941 0.2941
+ 1.0000 0.8020 0.3020
+ 1.0000 0.8098 0.3098
+ 1.0000 0.8176 0.3176
+ 1.0000 0.8255 0.3255
+ 1.0000 0.8333 0.3333
+ 1.0000 0.8412 0.3412
+ 1.0000 0.8490 0.3490
+ 1.0000 0.8569 0.3569
+ 1.0000 0.8647 0.3647
+ 1.0000 0.8725 0.3725
+ 1.0000 0.8804 0.3804
+ 1.0000 0.8882 0.3882
+ 1.0000 0.8961 0.3961
+ 1.0000 0.9039 0.4039
+ 1.0000 0.9118 0.4118
+ 1.0000 0.9196 0.4196
+ 1.0000 0.9275 0.4275
+ 1.0000 0.9353 0.4353
+ 1.0000 0.9431 0.4431
+ 1.0000 0.9510 0.4510
+ 1.0000 0.9588 0.4588
+ 1.0000 0.9667 0.4667
+ 1.0000 0.9745 0.4745
+ 1.0000 0.9824 0.4824
+ 1.0000 0.9902 0.4902
+ 1.0000 0.9980 0.4980
+ 1.0000 1.0000 0.5059
+ 1.0000 1.0000 0.5137
+ 1.0000 1.0000 0.5216
+ 1.0000 1.0000 0.5294
+ 1.0000 1.0000 0.5373
+ 1.0000 1.0000 0.5451
+ 1.0000 1.0000 0.5529
+ 1.0000 1.0000 0.5608
+ 1.0000 1.0000 0.5686
+ 1.0000 1.0000 0.5765
+ 1.0000 1.0000 0.5843
+ 1.0000 1.0000 0.5922
+ 1.0000 1.0000 0.6000
+ 1.0000 1.0000 0.6078
+ 1.0000 1.0000 0.6157
+ 1.0000 1.0000 0.6235
+ 1.0000 1.0000 0.6314
+ 1.0000 1.0000 0.6392
+ 1.0000 1.0000 0.6471
+ 1.0000 1.0000 0.6549
+ 1.0000 1.0000 0.6627
+ 1.0000 1.0000 0.6706
+ 1.0000 1.0000 0.6784
+ 1.0000 1.0000 0.6863
+ 1.0000 1.0000 0.6941
+ 1.0000 1.0000 0.7020
+ 1.0000 1.0000 0.7098
+ 1.0000 1.0000 0.7176
+ 1.0000 1.0000 0.7255
+ 1.0000 1.0000 0.7333
+ 1.0000 1.0000 0.7412
+ 1.0000 1.0000 0.7490
+ 1.0000 1.0000 0.7569
+ 1.0000 1.0000 0.7647
+ 1.0000 1.0000 0.7725
+ 1.0000 1.0000 0.7804
+ 1.0000 1.0000 0.7882
+ 1.0000 1.0000 0.7961
+ 1.0000 1.0000 0.8039
+ 1.0000 1.0000 0.8118
+ 1.0000 1.0000 0.8196
+ 1.0000 1.0000 0.8275
+ 1.0000 1.0000 0.8353
+ 1.0000 1.0000 0.8431
+ 1.0000 1.0000 0.8510
+ 1.0000 1.0000 0.8588
+ 1.0000 1.0000 0.8667
+ 1.0000 1.0000 0.8745
+ 1.0000 1.0000 0.8824
+ 1.0000 1.0000 0.8902
+ 1.0000 1.0000 0.8980
+ 1.0000 1.0000 0.9059
+ 1.0000 1.0000 0.9137
+ 1.0000 1.0000 0.9216
+ 1.0000 1.0000 0.9294
+ 1.0000 1.0000 0.9373
+ 1.0000 1.0000 0.9451
+ 1.0000 1.0000 0.9529
+ 1.0000 1.0000 0.9608
+ 1.0000 1.0000 0.9686
+ 1.0000 1.0000 0.9765
+ 1.0000 0.0000 0.0000
+ 1.0000 1.0000 0.0000
+ 0.0000 1.0000 0.0000
+"""
+colormapgray1 = """
+ 1.0000 1.0000 1.0000
+ 0.9961 0.9961 0.9961
+ 0.9922 0.9922 0.9922
+ 0.9882 0.9882 0.9882
+ 0.9843 0.9843 0.9843
+ 0.9804 0.9804 0.9804
+ 0.9765 0.9765 0.9765
+ 0.9725 0.9725 0.9725
+ 0.9686 0.9686 0.9686
+ 0.9647 0.9647 0.9647
+ 0.9608 0.9608 0.9608
+ 0.9569 0.9569 0.9569
+ 0.9529 0.9529 0.9529
+ 0.9490 0.9490 0.9490
+ 0.9451 0.9451 0.9451
+ 0.9412 0.9412 0.9412
+ 0.9373 0.9373 0.9373
+ 0.9333 0.9333 0.9333
+ 0.9294 0.9294 0.9294
+ 0.9255 0.9255 0.9255
+ 0.9216 0.9216 0.9216
+ 0.9176 0.9176 0.9176
+ 0.9137 0.9137 0.9137
+ 0.9098 0.9098 0.9098
+ 0.9059 0.9059 0.9059
+ 0.9020 0.9020 0.9020
+ 0.8980 0.8980 0.8980
+ 0.8941 0.8941 0.8941
+ 0.8902 0.8902 0.8902
+ 0.8863 0.8863 0.8863
+ 0.8824 0.8824 0.8824
+ 0.8784 0.8784 0.8784
+ 0.8745 0.8745 0.8745
+ 0.8706 0.8706 0.8706
+ 0.8667 0.8667 0.8667
+ 0.8627 0.8627 0.8627
+ 0.8588 0.8588 0.8588
+ 0.8549 0.8549 0.8549
+ 0.8510 0.8510 0.8510
+ 0.8471 0.8471 0.8471
+ 0.8431 0.8431 0.8431
+ 0.8392 0.8392 0.8392
+ 0.8353 0.8353 0.8353
+ 0.8314 0.8314 0.8314
+ 0.8275 0.8275 0.8275
+ 0.8235 0.8235 0.8235
+ 0.8196 0.8196 0.8196
+ 0.8157 0.8157 0.8157
+ 0.8118 0.8118 0.8118
+ 0.8078 0.8078 0.8078
+ 0.8039 0.8039 0.8039
+ 0.8000 0.8000 0.8000
+ 0.7961 0.7961 0.7961
+ 0.7922 0.7922 0.7922
+ 0.7882 0.7882 0.7882
+ 0.7843 0.7843 0.7843
+ 0.7804 0.7804 0.7804
+ 0.7765 0.7765 0.7765
+ 0.7725 0.7725 0.7725
+ 0.7686 0.7686 0.7686
+ 0.7647 0.7647 0.7647
+ 0.7608 0.7608 0.7608
+ 0.7569 0.7569 0.7569
+ 0.7529 0.7529 0.7529
+ 0.7490 0.7490 0.7490
+ 0.7451 0.7451 0.7451
+ 0.7412 0.7412 0.7412
+ 0.7373 0.7373 0.7373
+ 0.7333 0.7333 0.7333
+ 0.7294 0.7294 0.7294
+ 0.7255 0.7255 0.7255
+ 0.7216 0.7216 0.7216
+ 0.7176 0.7176 0.7176
+ 0.7137 0.7137 0.7137
+ 0.7098 0.7098 0.7098
+ 0.7059 0.7059 0.7059
+ 0.7020 0.7020 0.7020
+ 0.6980 0.6980 0.6980
+ 0.6941 0.6941 0.6941
+ 0.6902 0.6902 0.6902
+ 0.6863 0.6863 0.6863
+ 0.6824 0.6824 0.6824
+ 0.6784 0.6784 0.6784
+ 0.6745 0.6745 0.6745
+ 0.6706 0.6706 0.6706
+ 0.6667 0.6667 0.6667
+ 0.6627 0.6627 0.6627
+ 0.6588 0.6588 0.6588
+ 0.6549 0.6549 0.6549
+ 0.6510 0.6510 0.6510
+ 0.6471 0.6471 0.6471
+ 0.6431 0.6431 0.6431
+ 0.6392 0.6392 0.6392
+ 0.6353 0.6353 0.6353
+ 0.6314 0.6314 0.6314
+ 0.6275 0.6275 0.6275
+ 0.6235 0.6235 0.6235
+ 0.6196 0.6196 0.6196
+ 0.6157 0.6157 0.6157
+ 0.6118 0.6118 0.6118
+ 0.6078 0.6078 0.6078
+ 0.6039 0.6039 0.6039
+ 0.6000 0.6000 0.6000
+ 0.5961 0.5961 0.5961
+ 0.5922 0.5922 0.5922
+ 0.5882 0.5882 0.5882
+ 0.5843 0.5843 0.5843
+ 0.5804 0.5804 0.5804
+ 0.5765 0.5765 0.5765
+ 0.5725 0.5725 0.5725
+ 0.5686 0.5686 0.5686
+ 0.5647 0.5647 0.5647
+ 0.5608 0.5608 0.5608
+ 0.5569 0.5569 0.5569
+ 0.5529 0.5529 0.5529
+ 0.5490 0.5490 0.5490
+ 0.5451 0.5451 0.5451
+ 0.5412 0.5412 0.5412
+ 0.5373 0.5373 0.5373
+ 0.5333 0.5333 0.5333
+ 0.5294 0.5294 0.5294
+ 0.5255 0.5255 0.5255
+ 0.5216 0.5216 0.5216
+ 0.5176 0.5176 0.5176
+ 0.5137 0.5137 0.5137
+ 0.5098 0.5098 0.5098
+ 0.5059 0.5059 0.5059
+ 0.5020 0.5020 0.5020
+ 0.4980 0.4980 0.4980
+ 0.4941 0.4941 0.4941
+ 0.4902 0.4902 0.4902
+ 0.4863 0.4863 0.4863
+ 0.4824 0.4824 0.4824
+ 0.4784 0.4784 0.4784
+ 0.4745 0.4745 0.4745
+ 0.4706 0.4706 0.4706
+ 0.4667 0.4667 0.4667
+ 0.4627 0.4627 0.4627
+ 0.4588 0.4588 0.4588
+ 0.4549 0.4549 0.4549
+ 0.4510 0.4510 0.4510
+ 0.4471 0.4471 0.4471
+ 0.4431 0.4431 0.4431
+ 0.4392 0.4392 0.4392
+ 0.4353 0.4353 0.4353
+ 0.4314 0.4314 0.4314
+ 0.4275 0.4275 0.4275
+ 0.4235 0.4235 0.4235
+ 0.4196 0.4196 0.4196
+ 0.4157 0.4157 0.4157
+ 0.4118 0.4118 0.4118
+ 0.4078 0.4078 0.4078
+ 0.4039 0.4039 0.4039
+ 0.4000 0.4000 0.4000
+ 0.3961 0.3961 0.3961
+ 0.3922 0.3922 0.3922
+ 0.3882 0.3882 0.3882
+ 0.3843 0.3843 0.3843
+ 0.3804 0.3804 0.3804
+ 0.3765 0.3765 0.3765
+ 0.3725 0.3725 0.3725
+ 0.3686 0.3686 0.3686
+ 0.3647 0.3647 0.3647
+ 0.3608 0.3608 0.3608
+ 0.3569 0.3569 0.3569
+ 0.3529 0.3529 0.3529
+ 0.3490 0.3490 0.3490
+ 0.3451 0.3451 0.3451
+ 0.3412 0.3412 0.3412
+ 0.3373 0.3373 0.3373
+ 0.3333 0.3333 0.3333
+ 0.3294 0.3294 0.3294
+ 0.3255 0.3255 0.3255
+ 0.3216 0.3216 0.3216
+ 0.3176 0.3176 0.3176
+ 0.3137 0.3137 0.3137
+ 0.3098 0.3098 0.3098
+ 0.3059 0.3059 0.3059
+ 0.3020 0.3020 0.3020
+ 0.2980 0.2980 0.2980
+ 0.2941 0.2941 0.2941
+ 0.2902 0.2902 0.2902
+ 0.2863 0.2863 0.2863
+ 0.2824 0.2824 0.2824
+ 0.2784 0.2784 0.2784
+ 0.2745 0.2745 0.2745
+ 0.2706 0.2706 0.2706
+ 0.2667 0.2667 0.2667
+ 0.2627 0.2627 0.2627
+ 0.2588 0.2588 0.2588
+ 0.2549 0.2549 0.2549
+ 0.2510 0.2510 0.2510
+ 0.2471 0.2471 0.2471
+ 0.2431 0.2431 0.2431
+ 0.2392 0.2392 0.2392
+ 0.2353 0.2353 0.2353
+ 0.2314 0.2314 0.2314
+ 0.2275 0.2275 0.2275
+ 0.2235 0.2235 0.2235
+ 0.2196 0.2196 0.2196
+ 0.2157 0.2157 0.2157
+ 0.2118 0.2118 0.2118
+ 0.2078 0.2078 0.2078
+ 0.2039 0.2039 0.2039
+ 0.2000 0.2000 0.2000
+ 0.1961 0.1961 0.1961
+ 0.1922 0.1922 0.1922
+ 0.1882 0.1882 0.1882
+ 0.1843 0.1843 0.1843
+ 0.1804 0.1804 0.1804
+ 0.1765 0.1765 0.1765
+ 0.1725 0.1725 0.1725
+ 0.1686 0.1686 0.1686
+ 0.1647 0.1647 0.1647
+ 0.1608 0.1608 0.1608
+ 0.1569 0.1569 0.1569
+ 0.1529 0.1529 0.1529
+ 0.1490 0.1490 0.1490
+ 0.1451 0.1451 0.1451
+ 0.1412 0.1412 0.1412
+ 0.1373 0.1373 0.1373
+ 0.1333 0.1333 0.1333
+ 0.1294 0.1294 0.1294
+ 0.1255 0.1255 0.1255
+ 0.1216 0.1216 0.1216
+ 0.1176 0.1176 0.1176
+ 0.1137 0.1137 0.1137
+ 0.1098 0.1098 0.1098
+ 0.1059 0.1059 0.1059
+ 0.1020 0.1020 0.1020
+ 0.0980 0.0980 0.0980
+ 0.0941 0.0941 0.0941
+ 0.0902 0.0902 0.0902
+ 0.0863 0.0863 0.0863
+ 0.0824 0.0824 0.0824
+ 0.0784 0.0784 0.0784
+ 0.0745 0.0745 0.0745
+ 0.0706 0.0706 0.0706
+ 0.0667 0.0667 0.0667
+ 0.0627 0.0627 0.0627
+ 0.0588 0.0588 0.0588
+ 0.0549 0.0549 0.0549
+ 0.0510 0.0510 0.0510
+ 0.0471 0.0471 0.0471
+ 0.0431 0.0431 0.0431
+ 0.0392 0.0392 0.0392
+ 0.0353 0.0353 0.0353
+ 0.0314 0.0314 0.0314
+ 0.0275 0.0275 0.0275
+ 0.0235 0.0235 0.0235
+ 0.0196 0.0196 0.0196
+ 0.0157 0.0157 0.0157
+ 0.0118 0.0118 0.0118
+ 1.0000 0.0000 0.0000
+ 1.0000 1.0000 0.0000
+ 0.0000 1.0000 0.0000
+"""
+colormapLinrad = """
+ 0.0000 0.0000 0.0000
+ 0.0000 0.0000 0.0123
+ 0.0000 0.0000 0.0246
+ 0.0000 0.0000 0.0368
+ 0.0000 0.0000 0.0490
+ 0.0000 0.0000 0.0612
+ 0.0000 0.0000 0.0734
+ 0.0000 0.0000 0.0855
+ 0.0000 0.0000 0.0975
+ 0.0000 0.0000 0.1094
+ 0.0000 0.0000 0.1213
+ 0.0000 0.0000 0.1331
+ 0.0000 0.0000 0.1448
+ 0.0000 0.0000 0.1564
+ 0.0000 0.0000 0.1678
+ 0.0000 0.0000 0.1792
+ 0.0000 0.0000 0.1904
+ 0.0000 0.0000 0.2015
+ 0.0000 0.0000 0.2124
+ 0.0000 0.0000 0.2232
+ 0.0000 0.0000 0.2338
+ 0.0000 0.0000 0.2442
+ 0.0000 0.0000 0.2545
+ 0.0000 0.0000 0.2645
+ 0.0000 0.0000 0.2744
+ 0.0000 0.0000 0.2841
+ 0.0000 0.0000 0.2935
+ 0.0000 0.0000 0.3028
+ 0.0000 0.0000 0.3118
+ 0.0000 0.0000 0.3206
+ 0.0000 0.0000 0.3292
+ 0.0000 0.0000 0.3375
+ 0.0000 0.0000 0.3456
+ 0.0000 0.0000 0.3534
+ 0.0000 0.0000 0.3609
+ 0.0000 0.0036 0.3682
+ 0.0000 0.0149 0.3752
+ 0.0000 0.0262 0.3820
+ 0.0000 0.0375 0.3885
+ 0.0000 0.0487 0.3946
+ 0.0000 0.0600 0.4005
+ 0.0000 0.0712 0.4061
+ 0.0000 0.0824 0.4114
+ 0.0000 0.0936 0.4164
+ 0.0000 0.1047 0.4211
+ 0.0000 0.1158 0.4255
+ 0.0000 0.1269 0.4295
+ 0.0000 0.1379 0.4333
+ 0.0000 0.1489 0.4367
+ 0.0000 0.1599 0.4398
+ 0.0000 0.1707 0.4426
+ 0.0000 0.1816 0.4451
+ 0.0000 0.1923 0.4472
+ 0.0000 0.2030 0.4490
+ 0.0000 0.2137 0.4505
+ 0.0000 0.2243 0.4517
+ 0.0000 0.2348 0.4525
+ 0.0000 0.2452 0.4530
+ 0.0000 0.2555 0.4531
+ 0.0000 0.2658 0.4529
+ 0.0000 0.2760 0.4524
+ 0.0000 0.2860 0.4516
+ 0.0000 0.2960 0.4504
+ 0.0000 0.3059 0.4489
+ 0.0000 0.3157 0.4471
+ 0.0000 0.3254 0.4449
+ 0.0000 0.3350 0.4424
+ 0.0000 0.3445 0.4396
+ 0.0000 0.3538 0.4364
+ 0.0000 0.3631 0.4330
+ 0.0000 0.3722 0.4292
+ 0.0000 0.3812 0.4251
+ 0.0000 0.3901 0.4207
+ 0.0000 0.3988 0.4160
+ 0.0000 0.4075 0.4109
+ 0.0000 0.4160 0.4056
+ 0.0000 0.4243 0.4000
+ 0.0000 0.4325 0.3941
+ 0.0000 0.4406 0.3879
+ 0.0000 0.4486 0.3814
+ 0.0000 0.4563 0.3746
+ 0.0000 0.4640 0.3676
+ 0.0000 0.4715 0.3603
+ 0.0000 0.4788 0.3527
+ 0.0000 0.4860 0.3448
+ 0.0000 0.4930 0.3367
+ 0.0000 0.4998 0.3284
+ 0.0000 0.5065 0.3198
+ 0.0000 0.5131 0.3110
+ 0.0000 0.5194 0.3020
+ 0.0000 0.5256 0.2927
+ 0.0000 0.5316 0.2832
+ 0.0000 0.5375 0.2735
+ 0.0000 0.5432 0.2636
+ 0.0000 0.5487 0.2535
+ 0.0000 0.5540 0.2433
+ 0.0000 0.5591 0.2328
+ 0.0000 0.5641 0.2222
+ 0.0000 0.5688 0.2114
+ 0.0000 0.5734 0.2005
+ 0.0000 0.5778 0.1894
+ 0.0000 0.5820 0.1782
+ 0.0000 0.5860 0.1668
+ 0.0000 0.5899 0.1553
+ 0.0000 0.5935 0.1437
+ 0.0120 0.5969 0.1320
+ 0.0289 0.6002 0.1202
+ 0.0458 0.6032 0.1084
+ 0.0628 0.6061 0.0964
+ 0.0797 0.6088 0.0844
+ 0.0966 0.6112 0.0723
+ 0.1134 0.6135 0.0601
+ 0.1302 0.6155 0.0479
+ 0.1470 0.6174 0.0357
+ 0.1638 0.6190 0.0234
+ 0.1805 0.6205 0.0112
+ 0.1971 0.6217 -0.0011
+ 0.2137 0.6228 0.0000
+ 0.2302 0.6236 0.0000
+ 0.2467 0.6243 0.0000
+ 0.2631 0.6247 0.0000
+ 0.2794 0.6250 0.0000
+ 0.2957 0.6250 0.0000
+ 0.3118 0.6248 0.0000
+ 0.3279 0.6244 0.0000
+ 0.3438 0.6239 0.0000
+ 0.3597 0.6231 0.0000
+ 0.3755 0.6221 0.0000
+ 0.3911 0.6209 0.0000
+ 0.4067 0.6195 0.0000
+ 0.4221 0.6179 0.0000
+ 0.4374 0.6161 0.0000
+ 0.4526 0.6141 0.0000
+ 0.4676 0.6119 0.0000
+ 0.4826 0.6095 0.0000
+ 0.4973 0.6069 0.0000
+ 0.5120 0.6041 0.0000
+ 0.5265 0.6011 0.0000
+ 0.5408 0.5980 0.0000
+ 0.5550 0.5946 0.0000
+ 0.5690 0.5910 0.0000
+ 0.5829 0.5872 0.0000
+ 0.5965 0.5833 0.0000
+ 0.6101 0.5791 0.0000
+ 0.6234 0.5748 0.0000
+ 0.6366 0.5702 0.0000
+ 0.6496 0.5655 0.0000
+ 0.6623 0.5606 0.0000
+ 0.6750 0.5556 0.0000
+ 0.6874 0.5503 0.0000
+ 0.6996 0.5449 0.0000
+ 0.7116 0.5392 0.0000
+ 0.7234 0.5334 0.0000
+ 0.7350 0.5275 0.0000
+ 0.7464 0.5213 0.0000
+ 0.7576 0.5150 0.0000
+ 0.7685 0.5085 0.0000
+ 0.7793 0.5019 0.0000
+ 0.7898 0.4951 0.0000
+ 0.8000 0.4881 0.0000
+ 0.8101 0.4810 0.0000
+ 0.8199 0.4737 0.0000
+ 0.8295 0.4663 0.0000
+ 0.8389 0.4587 0.0000
+ 0.8480 0.4509 0.0000
+ 0.8568 0.4430 0.0000
+ 0.8654 0.4350 0.0000
+ 0.8738 0.4268 0.0000
+ 0.8819 0.4185 0.0000
+ 0.8898 0.4101 0.0000
+ 0.8974 0.4015 0.0000
+ 0.9048 0.3928 0.0000
+ 0.9118 0.3839 0.0000
+ 0.9187 0.3749 0.0000
+ 0.9252 0.3658 0.0000
+ 0.9315 0.3566 0.0000
+ 0.9376 0.3473 0.0000
+ 0.9433 0.3379 0.0000
+ 0.9488 0.3283 0.0000
+ 0.9540 0.3187 0.0000
+ 0.9590 0.3089 0.0000
+ 0.9636 0.2990 0.0000
+ 0.9680 0.2891 0.0000
+ 0.9721 0.2790 0.0000
+ 0.9760 0.2689 0.0000
+ 0.9795 0.2586 0.0000
+ 0.9828 0.2483 0.0083
+ 0.9858 0.2379 0.0219
+ 0.9885 0.2274 0.0354
+ 0.9909 0.2169 0.0489
+ 0.9931 0.2063 0.0625
+ 0.9949 0.1956 0.0760
+ 0.9965 0.1848 0.0896
+ 0.9978 0.1740 0.1031
+ 0.9987 0.1632 0.1167
+ 0.9995 0.1522 0.1303
+ 0.9999 0.1413 0.1439
+ 1.0000 0.1302 0.1575
+ 1.0000 0.1712 0.1712
+ 1.0000 0.1848 0.1848
+ 1.0000 0.1986 0.1986
+ 1.0000 0.2123 0.2123
+ 1.0000 0.2261 0.2261
+ 1.0000 0.2400 0.2400
+ 1.0000 0.2539 0.2539
+ 1.0000 0.2678 0.2678
+ 1.0000 0.2819 0.2819
+ 1.0000 0.2960 0.2960
+ 1.0000 0.3102 0.3102
+ 1.0000 0.3245 0.3245
+ 1.0000 0.3389 0.3389
+ 1.0000 0.3535 0.3535
+ 1.0000 0.3681 0.3681
+ 1.0000 0.3829 0.3829
+ 1.0000 0.3978 0.3978
+ 1.0000 0.4129 0.4129
+ 1.0000 0.4282 0.4282
+ 1.0000 0.4436 0.4436
+ 1.0000 0.4592 0.4592
+ 1.0000 0.4750 0.4750
+ 1.0000 0.4910 0.4910
+ 1.0000 0.5072 0.5072
+ 1.0000 0.5237 0.5237
+ 1.0000 0.5405 0.5405
+ 1.0000 0.5574 0.5574
+ 1.0000 0.5747 0.5747
+ 1.0000 0.5923 0.5923
+ 1.0000 0.6102 0.6102
+ 1.0000 0.6284 0.6284
+ 1.0000 0.6469 0.6469
+ 1.0000 0.6658 0.6658
+ 1.0000 0.6851 0.6851
+ 1.0000 0.7047 0.7047
+ 1.0000 0.7248 0.7248
+ 1.0000 0.7453 0.7453
+ 1.0000 0.7662 0.7662
+ 1.0000 0.7876 0.7876
+ 1.0000 0.8095 0.8095
+ 1.0000 0.8319 0.8319
+ 1.0000 0.8548 0.8548
+ 1.0000 0.8782 0.8782
+ 1.0000 0.9022 0.9022
+ 1.0000 0.9268 0.9268
+ 1.0000 0.9519 0.9519
+ 1.0000 0.9777 0.9777
+ 1.0000 1.0000 1.0000
+ 1.0000 1.0000 1.0000
+ 1.0000 1.0000 1.0000
+ 1.0000 1.0000 1.0000
+ 1.0000 1.0000 1.0000
+ 1.0000 1.0000 1.0000
+ 1.0000 1.0000 1.0000
+ 1.0000 1.0000 1.0000
+ 1.0000 0.0000 0.0000
+ 1.0000 1.0000 0.0000
+ 0.0000 1.0000 0.0000
+"""
+
+#----------------------------------------------------- Colormap2Palette
+def Colormap2Palette(colormap=colormapLinrad):
+ r=[]
+ g=[]
+ b=[]
+ for i in range(256):
+ j=31*i +1
+ t=colormap[j:j+10]
+ rr=int(255.0*float(t))
+ t=colormap[j+10:j+20]
+ gg=int(255.0*float(t))
+ t=colormap[j+20:j+30]
+ bb=int(255.0*float(t))
+ r.append(rr)
+ g.append(gg)
+ b.append(bb)
+ palette=ImagePalette.ImagePalette("RGB",r+g+b)
+ WsprMod.g.palette=palette
+ return palette
diff --git a/WsprMod/smeter.py b/WsprMod/smeter.py
new file mode 100644
index 0000000..b19d516
--- /dev/null
+++ b/WsprMod/smeter.py
@@ -0,0 +1,99 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: smeter.py
+# Description:
+#
+# Copyright (C) 2001-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#-------------------------------------------------------------------------------
+"""
+A basic widget for showing the progress
+being made in a task.
+
+"""
+
+from tkinter import *
+
+class Smeter:
+ def __init__(self, master=None, orientation="horizontal",
+ min=0, max=100, width=100, height=18,
+ doLabel=1, appearance="sunken",
+ fillColor="blue", background="gray",
+ labelColor="yellow", labelFont="Verdana",
+ labelText="", labelFormat="%d%%",
+ value=50, bd=2):
+ # preserve various values
+ self.master=master
+ self.orientation=orientation
+ self.min=min
+ self.max=max
+ self.width=width
+ self.height=height
+ self.doLabel=doLabel
+ self.fillColor=fillColor
+ self.labelFont= labelFont
+ self.labelColor=labelColor
+ self.background=background
+ self.labelText=labelText
+ self.labelFormat=labelFormat
+ self.value=value
+ self.frame=Frame(master, relief=appearance, bd=bd)
+ self.canvas=Canvas(self.frame, height=height, width=width, bd=0,
+ highlightthickness=0, background=background)
+ self.scale=self.canvas.create_rectangle(0, 0, width, height,
+ fill=fillColor)
+ self.label=self.canvas.create_text(self.canvas.winfo_reqwidth() / 2,
+ height / 2, text=labelText,
+ anchor="c", fill=labelColor,
+ font=self.labelFont)
+ self.update()
+ self.canvas.pack(side='top', fill='x', expand='no')
+
+ def updateProgress(self, newValue, newColor=None, newMax=None):
+ if newMax:
+ self.max = newMax
+ if newColor:
+ self.fillColor=newColor
+ self.value = newValue
+ self.update()
+
+ def update(self):
+ # Trim the values to be between min and max
+ value=self.value
+ if value > self.max:
+ value = self.max
+ if value < self.min:
+ value = self.min
+ # Adjust the rectangle
+ if self.orientation == "horizontal":
+ self.canvas.coords(self.scale, 0, 0,
+ float(value) / self.max * self.width, self.height)
+ else:
+ self.canvas.coords(self.scale, 0,
+ self.height - (float(value) / self.max*self.height),
+ self.width, self.height)
+ # Now update the colors
+ self.canvas.itemconfig(self.scale, fill=self.fillColor)
+ self.canvas.itemconfig(self.label, fill=self.labelColor)
+ # And update the label
+ if self.doLabel:
+ dB=int((value-50.0)/1.25)
+ t='%d dB'%dB
+ self.canvas.itemconfig(self.label, text=t)
+ self.canvas.update_idletasks()
diff --git a/WsprMod/specjt.py b/WsprMod/specjt.py
new file mode 100644
index 0000000..9cffb0f
--- /dev/null
+++ b/WsprMod/specjt.py
@@ -0,0 +1,532 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: specjt.py
+# Description:
+#
+# Copyright (C) 2001-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#-------------------------------------------------------------------------------
+from tkinter import *
+import time
+import os
+from . import Pmw
+from . import smeter
+#import Audio
+from . import g
+import string
+import pickle
+import tkinter.messagebox
+from . import w
+
+try:
+ from numpy.oldnumeric import zeros, multiarray
+except:
+ from Numeric import zeros, multiarray
+import Image, ImageTk, ImageDraw
+from .palettes import colormapblue, colormapgray0, colormapHot, \
+ colormapAFMHot, colormapgray1, colormapLinrad, Colormap2Palette
+#import wsjt #Is this OK to do?
+
+def hidespecjt():
+ root.withdraw()
+ g.showspecjt=0
+def showspecjt():
+ root.deiconify()
+ g.showspecjt=2
+
+if(__name__=="__main__"):
+ root = Tk()
+else:
+ root=Toplevel()
+ root.protocol('WM_DELETE_WINDOW',hidespecjt)
+
+root.withdraw()
+
+#------------------------------------------- Define globals and initialize.
+b0=0
+c0=0
+g0=0
+g.cmap="Linrad"
+df=2.69165
+fmid=1500
+fmid0=1500
+frange=2000
+frange0=2000
+isec0=-99
+logmap=IntVar()
+logmap.set(0)
+logm0=0
+minsep=IntVar()
+mousedf0=0
+naxis=IntVar()
+ncall=0
+newMinute=0
+nflat=IntVar()
+nfr=IntVar()
+nfr.set(1)
+nfreeze0=0
+nmark=IntVar()
+nmark0=0
+nn=0
+npal=IntVar()
+npal.set(2)
+nscroll=0
+nspeed0=IntVar()
+root_geom=""
+t0=""
+tol0=400
+ttot=0.0
+
+c=Canvas()
+a=zeros(225000,'s')
+im=Image.new('P',(750,300))
+line0=Image.new('P',(750,1)) #Image fragment for top line of waterfall
+draw=ImageDraw.Draw(im)
+pim=ImageTk.PhotoImage(im)
+balloon=Pmw.Balloon(root)
+
+def pal_gray0():
+ g.cmap="gray0"
+ im.putpalette(Colormap2Palette(colormapgray0),"RGB")
+def pal_gray1():
+ g.cmap="gray1"
+ im.putpalette(Colormap2Palette(colormapgray1),"RGB")
+def pal_linrad():
+ g.cmap="Linrad"
+ im.putpalette(Colormap2Palette(colormapLinrad),"RGB")
+def pal_blue():
+ g.cmap="blue"
+ im.putpalette(Colormap2Palette(colormapblue),"RGB")
+def pal_Hot():
+ g.cmap="Hot"
+ im.putpalette(Colormap2Palette(colormapHot),"RGB")
+def pal_AFMHot():
+ g.cmap="AFMHot"
+ im.putpalette(Colormap2Palette(colormapAFMHot),"RGB")
+
+#--------------------------------------------------- Command button routines
+#--------------------------------------------------- rx_volume
+def rx_volume():
+ for path in string.split(os.environ["PATH"], os.pathsep):
+ file = os.path.join(path, "sndvol32") + ".exe"
+ if os.path.exists(file):
+ os.spawnv(os.P_NOWAIT, file, (file,) + (" -r",))
+ return
+ t="WSJT cannot access mixer input control\non this platform. Please invoke " + \
+ "system\nmixer directly."
+ tkinter.messagebox.showwarning(message=t)
+
+#--------------------------------------------------- tx_volume ..extended for Vista
+def tx_volume():
+ for path in string.split(os.environ["PATH"], os.pathsep):
+ file = os.path.join(path, "sndvol32") + ".exe"
+ if os.path.exists(file):
+ os.spawnv(os.P_NOWAIT, file, (file,))
+ return
+ file = os.path.join(path, "sndvol") + ".exe"
+ if os.path.exists(file):
+ os.spawnv(os.P_NOWAIT, file, (file,))
+ return
+
+#---------------------------------------------------- df_mark
+def df_mark():
+ draw_axis()
+
+#---------------------------------------------------- change_fmid
+def change_fmid1():
+ global fmid
+ fmid=fmid+100
+ if fmid>5000-1000*nfr.get(): fmid=5000-1000*nfr.get()
+
+def change_fmid2():
+ global fmid
+ fmid=fmid-100
+ if fmid<1000*nfr.get(): fmid=1000*nfr.get()
+
+def set_fmid():
+ global fmid
+ if nfr.get()==1: fmid=1200
+ if nfr.get()==2: fmid=2200
+
+#---------------------------------------------------- freq_range
+def freq_range(event):
+# Move frequency scale left or right in 100 Hz increments
+ global fmid
+ if event.num==1:
+ fmid=fmid+100
+ else:
+ if event.num==3:
+ fmid=fmid-100
+ if fmid<1000*nfr.get(): fmid=1000*nfr.get()
+ if fmid>5000-1000*nfr.get(): fmid=5000-1000*nfr.get()
+
+def set_frange():
+ nfr.set(3-nfr.get())
+
+
+#---------------------------------------------------- update
+def update():
+ global a,b0,c0,g0,im,isec0,line0,newMinute,nscroll,pim, \
+ root_geom,t0,mousedf0,nfreeze0,tol0,nmark0,logm0, \
+ fmid,fmid0,frange,frange0
+
+ utc=time.gmtime(time.time())
+ isec=utc[5]
+
+ if isec != isec0: #Do once per second
+ isec0=isec
+ t0=time.strftime('%H:%M:%S',utc)
+ ltime.configure(text=t0)
+ root_geom=root.geometry()
+# g.rms=Audio.gcom1.rms
+
+ if g.showspecjt==1:
+ showspecjt()
+ nspeed=nspeed0.get() #Waterfall update rate
+ brightness=sc1.get()
+ contrast=sc2.get()
+ logm=logmap.get()
+ g0=sc3.get()
+
+# Don't calculate spectra for waterfall while transmitting
+## if w.acom1.transmitting==0:
+## w.spec(brightness,contrast,logm,g0,nspeed,a) #Call Fortran routine spec
+## newdat=w.acom1.newdat #True if new data available
+## else:
+## newdat=0
+ newdat=0
+
+ xdb1=int(w.acom1.xdb1 - 41.0)
+ level=50.0 + 1.25*xdb1
+ sm.updateProgress(newValue=level) #S-meter bar
+ newdat=1
+ if newdat or brightness!=b0 or contrast!=c0 or logm!=logm0:
+ if brightness==b0 and contrast==c0 and logm==logm0:
+ n=1
+# n=Audio.gcom2.nlines
+ box=(0,0,750,300-n) #Define region
+ region=im.crop(box) #Get all but last line(s)
+ try:
+ im.paste(region,(0,n)) #Move waterfall down
+ except:
+ print("Images did not match, continuing anyway.")
+ for i in range(n):
+ line0.putdata(a[750*i:750*(i+1)]) #One row of pixels to line0
+ im.paste(line0,(0,i)) #Paste in new top line
+ nscroll=nscroll+n
+ else: #A scale factor has changed
+ im.putdata(a) #Compute whole new image
+ b0=brightness #Save scale values
+ c0=contrast
+ logm0=logm
+
+ if newdat:
+## if Audio.gcom2.monitoring:
+## if minsep.get() and newMinute:
+## draw.line((0,0,749,0),fill=128) #Draw the minute separator
+## if nscroll == 13:
+## draw.text((5,2),t0[0:5],fill=253) #Insert time label
+## else:
+## if minsep.get():
+## draw.line((0,0,749,0),fill=128) #Draw the minute separator
+
+ pim=ImageTk.PhotoImage(im) #Convert Image to PhotoImage
+ graph1.delete(ALL)
+ #For some reason, top two lines are invisible, so we move down 2
+ graph1.create_image(0,0+2,anchor='nw',image=pim)
+ newMinute=0
+
+ if nmark.get()!=nmark0:
+ df_mark()
+ nmark0=nmark.get()
+
+## if newdat: Audio.gcom2.ndiskdat=0
+## Audio.gcom2.nlines=0
+## Audio.gcom2.nflat=nflat.get()
+ frange=nfr.get()*2000
+ if(fmid!=fmid0 or frange!=frange0):
+ if fmid<1000*nfr.get(): fmid=1000*nfr.get()
+ if fmid>5000-1000*nfr.get(): fmid=5000-1000*nfr.get()
+# draw_axis()
+ df_mark()
+ fmid0=fmid
+ frange0=frange
+## Audio.gcom2.nfmid=int(fmid)
+## Audio.gcom2.nfrange=int(frange)
+
+ ltime.after(200,update) #Reset the timer
+
+#-------------------------------------------------------- draw_axis
+def draw_axis():
+ xmid=fmid
+ if naxis.get():
+ xmid=xmid-1270.46
+ c.delete(ALL)
+
+# Draw the frequency or DF tick marks
+ if(frange==2000):
+ for ix in range(-1300,5001,20):
+ i=374.5 + (ix-xmid)/df
+ j=20
+ if (ix%100)==0 :
+ j=16
+ x=i-2
+ if ix<1000 : x=x+2
+ y=8
+ c.create_text(x,y,text=str(ix))
+ c.create_line(i,25,i,j,fill='black')
+
+ if(frange==4000):
+ for ix in range(-2600,5401,50):
+ i=374.5 + (ix-xmid)/(2*df)
+ j=20
+ if (ix%200)==0 :
+ j=16
+ x=i-2
+ if ix<1000 : x=x+2
+ y=8
+ c.create_text(x,y,text=str(ix))
+ c.create_line(i,25,i,j,fill='black')
+
+#-------------------------------------------------------- Create GUI widgets
+
+#-------------------------------------------------------- Menu bar
+frame = Frame(root)
+frame.pack()
+
+if (sys.platform != 'darwin'):
+ mbar = Frame(frame)
+ mbar.pack(fill = X)
+ sbar = mbar
+ button_width=1
+else:
+ mbar = Menu(root)
+ root.config(menu=mbar)
+ sbar = Frame(frame)
+ sbar.pack(fill = X)
+ button_width=5
+
+# Tearoff menus make less sense under darwin
+use_tearoff = (sys.platform != 'darwin')
+
+#--------------------------------------------------------- Options menu
+if (sys.platform != 'darwin'):
+ setupbutton = Menubutton(mbar, text = 'Options', )
+ setupbutton.pack(side = LEFT)
+ setupmenu = Menu(setupbutton, tearoff=1)
+ setupbutton['menu'] = setupmenu
+else:
+ setupmenu = Menu(mbar, tearoff=use_tearoff)
+
+setupmenu.add_checkbutton(label = 'Mark T/R boundaries',variable=minsep)
+setupmenu.add_checkbutton(label='Flatten spectra',variable=nflat)
+setupmenu.add_checkbutton(label='Mark JT65 tones only if Freeze is checked',
+ variable=nmark)
+setupmenu.add_separator()
+setupmenu.add('command', label = 'Rx volume control', command = rx_volume)
+setupmenu.add('command', label = 'Tx volume control', command = tx_volume)
+setupmenu.add_separator()
+setupmenu.add_radiobutton(label='Frequency axis',command=df_mark,
+ value=0,variable=naxis)
+setupmenu.add_radiobutton(label='JT65 DF axis',command=df_mark,
+ value=1,variable=naxis)
+setupmenu.add_separator()
+setupmenu.palettes=Menu(setupmenu,tearoff=0)
+setupmenu.palettes.add_radiobutton(label='Gray0',command=pal_gray0,
+ value=0,variable=npal)
+setupmenu.palettes.add_radiobutton(label='Gray1',command=pal_gray1,
+ value=1,variable=npal)
+setupmenu.palettes.add_radiobutton(label='Linrad',command=pal_linrad,
+ value=2,variable=npal)
+setupmenu.palettes.add_radiobutton(label='Blue',command=pal_blue,
+ value=3,variable=npal)
+setupmenu.palettes.add_radiobutton(label='Hot',command=pal_Hot,
+ value=4,variable=npal)
+setupmenu.palettes.add_radiobutton(label='AFMHot',command=pal_AFMHot,
+ value=5,variable=npal)
+setupmenu.add_cascade(label = 'Palette',menu=setupmenu.palettes)
+setupmenu.add_checkbutton(label='Logarithmic scale',variable=logmap)
+
+if (sys.platform == 'darwin'):
+ mbar.add_cascade(label="Options", menu=setupmenu)
+
+#------------------------------------------------- Freq and DF labels
+lab1=Label(sbar,padx=20,bd=0)
+lab1.pack(side=LEFT)
+fdf=Label(sbar,width=25,bd=0)
+fdf.pack(side=LEFT)
+
+#------------------------------------------------- BW button
+
+lab3=Label(sbar,padx=13,bd=0)
+lab3.pack(side=LEFT)
+bbw=Button(sbar,text='BW',command=set_frange,padx=1,pady=1,width=3)
+bbw.pack(side=LEFT)
+
+lab0=Label(sbar,padx=10,bd=0)
+lab0.pack(side=LEFT)
+bfmid1=Button(sbar,text='<',command=change_fmid1,padx=1,pady=1,width=button_width)
+bfmid2=Button(sbar,text='>',command=change_fmid2,padx=1,pady=1,width=button_width)
+bfmid3=Button(sbar,text='|',command=set_fmid,padx=3,pady=1,width=button_width)
+bfmid1.pack(side=LEFT)
+bfmid3.pack(side=LEFT)
+bfmid2.pack(side=LEFT)
+
+#------------------------------------------------- Speed selection buttons
+for i in (5, 4, 3, 2, 1):
+ t=str(i)
+ Radiobutton(sbar,text=t,value=i,variable=nspeed0).pack(side=RIGHT)
+nspeed0.set(5)
+lab2=Label(sbar,text='Speed: ',bd=0)
+lab2.pack(side=RIGHT)
+#------------------------------------------------- Graphics frame
+iframe1 = Frame(frame, bd=1, relief=SUNKEN)
+c=Canvas(iframe1, bg='white', width=750, height=25,bd=0)
+c.pack(side=TOP)
+Widget.bind(c,"<Shift-Button-1>",freq_range)
+Widget.bind(c,"<Shift-Button-2>",freq_range)
+Widget.bind(c,"<Shift-Button-3>",freq_range)
+graph1=Canvas(iframe1, bg='black', width=750, height=300,bd=0,cursor='crosshair')
+graph1.pack(side=TOP)
+##Widget.bind(graph1,"<Motion>",fdf_change)
+##Widget.bind(graph1,"<Button-1>",set_freezedf)
+##Widget.bind(graph1,"<Button-3>",decode_request)
+##Widget.bind(graph1,"<Double-Button-1>",freeze_decode)
+iframe1.pack(expand=1, fill=X)
+
+#-------------------------------------------------- Status frame
+iframe2 = Frame(frame, bd=1, relief=SUNKEN)
+status=Pmw.MessageBar(iframe2,entry_width=17,entry_relief=GROOVE)
+status.pack(side=LEFT)
+sc1=Scale(iframe2,from_=-100.0,to_=100.0,orient='horizontal',
+ showvalue=0,sliderlength=5)
+sc1.pack(side=LEFT)
+sc2=Scale(iframe2,from_=-100.0,to_=100.0,orient='horizontal',
+ showvalue=0,sliderlength=5)
+sc2.pack(side=LEFT)
+balloon.bind(sc1,"Brightness", "Brightness")
+balloon.bind(sc2,"Contrast", "Contrast")
+balloon.configure(statuscommand=status.helpmessage)
+ltime=Label(iframe2,bg='black',fg='yellow',width=8,bd=2,font=('Helvetica',16))
+ltime.pack(side=LEFT)
+msg1=Label(iframe2,padx=2,bd=2,text=" ")
+msg1.pack(side=LEFT)
+sc3=Scale(iframe2,from_=-100.0,to_=100.0,orient='horizontal',
+ showvalue=0,sliderlength=5)
+sc3.pack(side=LEFT)
+balloon.bind(sc3,"Gain", "Digital Gain")
+sm=smeter.Smeter(iframe2,fillColor='slateblue',width=150,
+ doLabel=1)
+sm.frame.pack(side=RIGHT)
+balloon.bind(sm.frame,"Rx noise level","Rx noise level")
+iframe2.pack(expand=1, fill=X)
+
+#----------------------------------------------- Restore params from INI file
+try:
+ f=open('WSJT.INI',mode='r')
+ params=f.readlines()
+except:
+ params=""
+
+try:
+ for i in range(len(params)):
+ key,value=params[i].split()
+ if key == 'SpecJTGeometry': root.geometry(value)
+ elif key == 'UpdateInterval': nspeed0.set(value)
+ elif key == 'Brightness': sc1.set(value)
+ elif key == 'Contrast': sc2.set(value)
+ elif key == 'DigitalGain': sc3.set(value)
+ elif key == 'MinuteSeparators': minsep.set(value)
+ elif key == 'AxisLabel': naxis.set(value)
+ elif key == 'MarkTones': nmark.set(value)
+ elif key == 'Flatten': nflat.set(value)
+ elif key == 'LogMap': logmap.set(value)
+ elif key == 'Palette': g.cmap=value
+ elif key == 'Frange': nfr.set(value)
+ elif key == 'Fmid': fmid=int(value)
+ else: pass
+except:
+ print('Error reading WSJT.INI in SpecJT, continuing with defaults.')
+ print(key,value)
+
+#------------------------------------------------------ Select palette
+if g.cmap == "gray0":
+ pal_gray0()
+ npal.set(0)
+if g.cmap == "gray1":
+ pal_gray1()
+ npal.set(1)
+if g.cmap == "Linrad":
+ pal_linrad()
+ npal.set(2)
+if g.cmap == "blue":
+ pal_blue()
+ npal.set(3)
+if g.cmap == "Hot":
+ pal_Hot()
+ npal.set(4)
+if g.cmap == "AFMHot":
+ pal_AFMHot()
+ npal.set(5)
+
+#---------------------------------------------- Display GUI and start mainloop
+draw_axis()
+try:
+ ndevin=g.ndevin.get()
+except:
+ ndevin=0
+##Audio.gcom1.ndevin=ndevin
+
+try:
+ ndevout=g.ndevout.get()
+except:
+ ndevout=0
+##Audio.gcom1.ndevout=ndevout
+ # Only valid for windows
+ # for now
+##Audio.audio_init(ndevin,ndevout) #Start the audio stream
+
+ltime.after(200,update)
+
+root.deiconify()
+g.showspecjt=2
+if g.Win32: root.iconbitmap("wsjt.ico")
+root.title(' SpecJT')
+##if(__name__=="__main__"):
+## Audio.gcom2.monitoring=1
+root.mainloop()
+
+#-------------------------------------------------- Save user params and quit
+f=open('WSJT.INI',mode='w')
+f.write("UpdateInterval " + str(nspeed0.get()) + "\n")
+f.write("Brightness " + str(b0)+ "\n")
+f.write("Contrast " + str(c0)+ "\n")
+f.write("DigitalGain " + str(g0)+ "\n")
+f.write("MinuteSeparators " + str(minsep.get()) + "\n")
+f.write("AxisLabel " + str(naxis.get()) + "\n")
+f.write("MarkTones " + str(nmark.get()) + "\n")
+f.write("Flatten " + str(nflat.get()) + "\n")
+f.write("LogMap " + str(logmap.get()) + "\n")
+f.write("Palette " + g.cmap + "\n")
+f.write("Frange " + str(nfr.get()) + "\n")
+f.write("Fmid " + str(fmid) + "\n")
+#root_geom=root_geom[root_geom.index("+"):]
+f.write("SpecJTGeometry " + root_geom + "\n")
+f.close()
+
diff --git a/acom1.f90 b/acom1.f90
new file mode 100644
index 0000000..62cbaa5
--- /dev/null
+++ b/acom1.f90
@@ -0,0 +1,45 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: acom1.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+ parameter (NMAX=120*12000) !Max length of waveform
+ parameter (NZ=2*120*48000)
+ real*8 f0,f0a,f0b,ftx,tsec0
+ logical ltest,receiving,transmitting
+ character*80 infile,outfile,pttport,thisfile
+ character cdate*8,utctime*10,rxtime*4,catport*12
+ character pttmode*3,appdir*80,chs*40
+ character callsign*12,grid*4,grid6*6,ctxmsg*22,sending*22
+ integer*2 iwave,kwave
+ common/acom1/ f0,f0a,f0b,ftx,tsec0,rms,pctx,igrid6,nsec,ndevin, &
+ nfhopping,nfhopok,iband,ncoord,ntrminutes, &
+ ndevout,nsave,nrxdone,ndbm,nport,ndec,ndecdone,ntxdone, &
+ idint,ndiskdat,ndecoding,ntr,nbaud,ndatabits,nstopbits, &
+ receiving,transmitting,nrig,nappdir,iqmode,iqrx,iqtx,nfiq, &
+ ndebug,idevin,idevout,nsectx,nbfo,iqrxapp, &
+ ntxdb,txbal,txpha,iwrite,newdat,iqrxadj,gain,phase,reject, &
+ ntxfirst,ntest,ncat,ltest,iwave(NMAX),kwave(NZ),idle,ntune, &
+ ntxnext,nstoptx,ncal,ndevsok,nsec1,nsec2,xdb1,xdb2, &
+ infile,outfile,pttport,cdate,utctime,callsign,grid,grid6, &
+ rxtime,ctxmsg,sending,thisfile,pttmode,catport,appdir,chs
diff --git a/acom2.f90 b/acom2.f90
new file mode 100644
index 0000000..11a8982
--- /dev/null
+++ b/acom2.f90
@@ -0,0 +1,27 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: acom2.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+ character linetx*40
+ common/acom2/ntune2,linetx
diff --git a/afc2.f90 b/afc2.f90
new file mode 100644
index 0000000..0f1d3b3
--- /dev/null
+++ b/afc2.f90
@@ -0,0 +1,234 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: afc2.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine afc2(c4)
+
+ complex c4(45000)
+ complex c5(45000)
+ complex c6(45000)
+ complex c(0:4095)
+ complex cc(-16:48,0:163)
+ complex ct
+ complex wshift(-16:48)
+ complex*16 w,ws,wt
+ real fpk(162),spk(162)
+ real f2(162),s2(162)
+ real ww(-5:5)
+ real sqn(-16:16,0:7)
+ real*8 dt,f,dphi,twopi
+ integer npr3(162)
+ common/ccom/rr(162)
+ data npr3/ &
+ 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, &
+ 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, &
+ 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, &
+ 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, &
+ 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, &
+ 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, &
+ 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, &
+ 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, &
+ 0,0/
+
+ dt=1.0/375
+ nsps=256
+ nsym=162
+ twopi=8.d0*atan(1.d0)
+ dftone=12000.d0/8192.d0 !1.46484375 Hz
+
+! At this point we have established pretty good time and frequency
+! synchronization. Now remove the sync modulation.
+ k=0
+ w=1.0
+ do j=1,nsym
+! f=dftone*(npr3(j)+2*symbol(j)-1.5)
+ f=dftone*(npr3(j)-1.5)
+ dphi=twopi*dt*f
+ ws=dcmplx(cos(dphi),-sin(dphi))
+ do i=1,nsps
+ w=w*ws
+ k=k+1
+ c5(k)=w*c4(k)
+ enddo
+ enddo
+ kz=k
+! At this point c5 has only data modulation, with 2-FSK frequency steps
+! of size 2*dftone = 2.9296875 Hz.
+
+! Compute oversampled complex spectra of each symbol at frequencies
+! from about -1.5 to +4.5 Hz.
+ nft=2048
+ df1=375.0/nft
+ ia=-dftone/df1
+ ib=-3*ia
+ do j=1,nsym
+ do i=ia,ib
+ w=1.0
+ dphi=i*twopi/nft
+ ws=dcmplx(cos(dphi),-sin(dphi))
+ m=(j-1)*nsps
+ ct=0.
+ do k=1,nsps
+ m=m+1
+ ct=ct + w*c5(m)
+ w=w*ws
+ enddo
+ wshift(i)=w
+ cc(i,j)=ct
+ enddo
+ do i=ia,ib
+ sq=real(cc(i,j))**2 + aimag(cc(i,j))**2
+ pha=atan2(aimag(wshift(i)),real(wshift(i)))
+! write(51,3002) i*df1,sq,pha
+!3002 format(3f10.3)
+ enddo
+ enddo
+
+! Combine the complex coeffs to produce coherent spectra over groups
+! of three successive symbols. Loop over all 8 possible tone combinations
+! to find the best-fit value for the central symbol.
+
+ cc(ia:ib,0)=0.
+ cc(ia:ib,163)=0.
+ do j=1,nsym
+ smax=0.
+ do n=0,7
+ i1=0
+ if(n.eq.2 .or. n.eq.3 .or. n.eq.6 .or. n.eq.7) i1=16
+ i2=0
+ if(n.ge.4) i2=16
+ i3=0
+ if(mod(n,2).eq.1) i3=16
+ do i=ia/2,-ia/2
+ ct=conjg(wshift(i1+i))*cc(i1+i,j-1) + cc(i2+i,j) + &
+ wshift(i3+i)*cc(i3+i,j+1)
+ sq=real(ct)**2 + aimag(ct)**2
+ sqn(i,n)=sq
+ if(sq.gt.smax) then
+ smax=sq
+ nmax=n
+ ipk=i
+ endif
+ enddo
+ enddo
+ if(nmax.lt.4) rsym=sqn(ipk,nmax+4)-smax
+ if(nmax.ge.4) rsym=smax-sqn(ipk,nmax-4)
+! rr(j)=0.004*rsym
+
+ smax=0.
+! do i=ia/2,-ia/2
+ do i=-3,3
+ do n=1,3
+ sqn(i,0)=sqn(i,0)+sqn(i,n)
+ sqn(i,4)=sqn(i,4)+sqn(i,n+4)
+ enddo
+ sq=abs(sqn(i,4)-sqn(i,0))
+ if(sq.gt.abs(smax)) then
+ smax=sqn(i,4)-sqn(i,0)
+ ipk=i
+ endif
+ enddo
+ rr(j)=0.002*smax
+
+ write(52,3001) j,rr(j)
+3001 format(i3,f12.3)
+
+ enddo
+
+! Do coherent FFTs over several symbols. Find peak freq and subtract
+! 2*dftone if it was the upper one of two. Save fpk and smax centered
+! on each symbol. (May want to play with nd and nfft.)
+
+ nd=3
+ ndat=nd*nsps
+ nfft=1024
+ df=375.0/nfft
+ ia=2*dftone/df
+ noff=nint(0.5*nd*nsps)
+ c6(1:noff)=0.
+ c6(noff+1:noff+kz)=c5(1:kz)
+
+ k=1-nsps
+ do j=1,nsym
+ k=k+nsps
+ c(0:ndat-1)=c6(k:k+ndat)
+ c(ndat:nfft-1)=0.
+ call four2a(c,nfft,1,-1,1)
+ smax=0.
+ do i=-ia,ia
+ k1=i
+ if(k1.lt.0) k1=k1+nfft
+ s=real(c(k1))**2 + aimag(c(k1))**2
+ if(s.gt.smax) then
+ ipk=i
+ if(ipk.gt.ia/2) ipk=ipk-ia
+ if(ipk.lt.-ia/2) ipk=ipk+ia
+ smax=s
+ endif
+ enddo
+ fpk(j)=ipk*df
+ spk(j)=smax
+ enddo
+
+ ww(0)=1.0
+ do i=1,5
+ x=(i/3.0)**2
+ ww(i)=exp(-x)
+ ww(-i)=exp(-x)
+ enddo
+
+ do j=1,nsym
+ sum=0.
+ sumw=0.
+ do i=-5,5
+ if(j+i.ge.1 .and. j+i.le.162) then
+ wgt=ww(i)*spk(j+i)
+ sumw=sumw + wgt
+ sum=sum + wgt*fpk(j+i)
+ endif
+ enddo
+ f2(j)=sum/sumw
+ enddo
+
+! do j=1,nsym
+! write(54,3201) j,fpk(j),0.000015*spk(j),f2(j)
+!3201 format(i3,3f10.3)
+! enddo
+! write(54,3201) 163,-4.0,0.0,0.0
+! write(54,3201) 0,-4.0,0.0,0.0
+
+! k=0
+! w=1.0
+! do j=1,nsym
+! dphi=twopi*dt*f2(j)
+! ws=dcmplx(cos(dphi),-sin(dphi))
+! do i=1,nsps
+! w=w*ws
+! k=k+1
+! c4(k)=w*c4(k)
+! enddo
+! enddo
+
+ return
+end subroutine afc2
diff --git a/audiodev.f90 b/audiodev.f90
new file mode 100644
index 0000000..c5ca26b
--- /dev/null
+++ b/audiodev.f90
@@ -0,0 +1,65 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: audiodev.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine audiodev(jdevin,jdevout,inbad,outbad)
+
+! !f2py threadsafe
+!f2py intent(in) jdevin,jdevout
+!f2py intent(out) inbad,outbad
+
+ character cdevice*40,audiocaps*80
+ integer inbad,outbad
+ integer nchin(0:40),nchout(0:40),inerr(0:40),outerr(0:40)
+ include 'acom1.f90'
+
+ call padevsub(numdevs,ndefin,ndefout,nchin,nchout,inerr,outerr)
+
+ audiocaps=appdir(:nappdir)//'/audio_caps'
+ open(17,file=audiocaps,status='unknown')
+ inbad=1
+ do i=0,numdevs-1
+ read(17,1101,end=10,err=10) cdevice
+1101 format(29x,a40)
+ i1=index(cdevice,':')
+ if(i1.gt.10) cdevice=cdevice(:i1-1)
+ if(nchin(i).gt.0 .and. inerr(i).eq.0) then
+ if(i.eq.jdevin) inbad=0
+ endif
+ enddo
+
+10 rewind 17
+ outbad=1
+ do i=0,numdevs-1
+ read(17,1101,end=20,err=20) cdevice
+ i1=index(cdevice,':')
+ if(i1.gt.10) cdevice=cdevice(:i1-1)
+ if(nchout(i).gt.0 .and. outerr(i).eq.0) then
+ if(i.eq.jdevout) outbad=0
+ endif
+ enddo
+20 close(17)
+
+ return
+end subroutine audiodev
diff --git a/autogen.sh b/autogen.sh
new file mode 100644
index 0000000..28eec9a
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+#
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# SVN : $Id: autogen.sh 4522 2014-10-17 05:26:06Z ki7mt $
+#
+# File Name: autogen.sh
+# Description: script to generate configire and makefile
+#
+# Run ./autogen.sh
+#
+# Copyright (C) 2001-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#-------------------------------------------------------------------------------
+
+
+set -e
+
+_BASED=$(exec pwd)
+_PROGRAM=WSPR
+
+# Start main script
+cd $_BASED
+
+autoconf --version > /dev/null 2>&1
+if test "$?" -eq 1; then
+# message if autoconf was found or not "0"=OK, "1"= Not Reachable
+ clear
+ echo
+ echo "You must have autoconf installed to compile $_PROGRAM."
+ echo "Install the appropriate package for your distribution,"
+ echo
+ exit 1
+fi
+
+clear
+echo
+echo "Running ( autoconf -f -i ) to process configure.ac"
+
+# Generate configure script from configure.ac and aclocal.m4
+autoconf -f -i
+
+# simple test for the configure script, after running autogen.sh
+if test -s ./configure; then
+ echo " ..Finished"
+ echo " ..Autoconf will now build the Makefile"
+ echo " ..Running ./configure to generate Makefile"
+ echo
+ sleep 1
+else
+# message if configure was not found
+ echo "There was a problem generating the configure script"
+ echo "Check config.status for details."
+ echo
+ exit 1
+fi
+
+# message if no arguments were presented
+if test -z "$*"; then
+ echo "Using ./configure with default arguments"
+ echo
+ echo "If you wish change paramaters, add the arguments"
+ echo "to use $0 command line."
+ echo
+ sleep 1
+else
+# List user input arguments
+ echo "Using ./configure $@"
+ echo
+ sleep 2
+fi
+
+$_BASED/configure "$@"
+
+exit 0
diff --git a/averms.f90 b/averms.f90
new file mode 100644
index 0000000..1d07871
--- /dev/null
+++ b/averms.f90
@@ -0,0 +1,45 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: averms.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine averms(x,npts,ave,rms,xmax)
+
+ real x(npts)
+
+ s=0.
+ xmax=0.
+ do i=1,npts
+ s=s + x(i)
+ xmax=max(xmax,abs(x(i)))
+ enddo
+ ave=s/npts
+
+ sq=0.
+ do i=1,npts
+ sq=sq + (x(i)-ave)**2
+ enddo
+ rms=sqrt(sq/(npts-1))
+
+ return
+end subroutine averms
diff --git a/azdist.f90 b/azdist.f90
new file mode 100644
index 0000000..5219c15
--- /dev/null
+++ b/azdist.f90
@@ -0,0 +1,128 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: azdist.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine azdist(MyGrid,HisGrid,utch,nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter)
+
+ character*6 MyGrid,HisGrid,mygrid0,hisgrid0
+ real*8 utch,utch0
+ logical HotABetter,IamEast
+ real eltab(22),daztab(22)
+ data eltab/18.,15.,13.,11.,9.,8.,7.,6.,5.3,4.7,4.,3.3,2.7, &
+ 2.,1.5,1.,0.8,0.6,0.4,0.2,0.0,0.0/
+ data daztab/21.,18.,16.,15.,14.,13.,12.,11.,10.7,10.3,10., &
+ 10.,10.,10.,10.,10.,10.,9.,9.,9.,8.,8./
+ data mygrid0/" "/,hisgrid0/" "/,utch0/-999.d0/
+ save
+
+ if(MyGrid.eq.HisGrid) then
+ naz=0
+ nel=0
+ ndmiles=0
+ ndkm=0
+ nhotaz=0
+ nhotabetter=1
+ go to 999
+ endif
+
+ if(mygrid.eq.mygrid0 .and. hisgrid.eq.hisgrid0 .and. &
+ abs(utch-utch0).lt.0.1666667d0) go to 900
+ utch0=utch
+ mygrid0=mygrid
+ hisgrid0=hisgrid
+ utchours=utch
+
+ if(MyGrid(5:5).eq.' ') MyGrid(5:5)='m'
+ if(MyGrid(6:6).eq.' ') MyGrid(6:6)='m'
+ if(HisGrid(5:5).eq.' ') HisGrid(5:5)='m'
+ if(HisGrid(6:6).eq.' ') HisGrid(6:6)='m'
+
+ if(MyGrid.eq.HisGrid) then
+ Az=0.
+ Dmiles=0.
+ Dkm=0.0
+ El=0.
+ HotA=0.
+ HotB=0.
+ HotABetter=.true.
+ go to 900
+ endif
+
+ call grid2deg(MyGrid,dlong1,dlat1)
+ call grid2deg(HisGrid,dlong2,dlat2)
+ call geodist(dlat1,dlong1,dlat2,dlong2,Az,Baz,Dkm)
+
+ j=nint(Dkm/100.0)-4
+ if(j.lt.1) j=1
+ if(j.gt.21)j=21
+ ndkm=Dkm/100
+ d1=100.0*ndkm
+ u=(Dkm-d1)/100.0
+ El=eltab(j) + u * (eltab(j+1)-eltab(j))
+ daz=daztab(j) + u * (daztab(j+1)-daztab(j))
+ Dmiles=Dkm/1.609344
+
+ tmid=mod(UTChours-0.5*(dlong1+dlong2)/15.0+48.0,24.0)
+ IamEast=.false.
+ if(dlong1.lt.dlong2) IamEast=.true.
+ if(dlong1.eq.dlong2 .and. dlat1.gt.dlat2) IamEast=.false.
+ azEast=baz
+ if(IamEast) azEast=az
+ if((azEast.ge.45.0 .and. azEast.lt.135.0) .or. &
+ (azEast.ge.225.0 .and. azEast.lt.315.0)) then
+! The path will be taken as "east-west".
+ HotABetter=.true.
+ if(abs(tmid-6.0).lt.6.0) HotABetter=.false.
+ if((dlat1+dlat2)/2.0 .lt. 0.0) HotABetter=.not.HotABetter
+ else
+! The path will be taken as "north-south".
+ HotABetter=.false.
+ if(abs(tmid-12.0).lt.6.0) HotABetter=.true.
+ endif
+ if(IamEast) then
+ HotA = Az - daz
+ HotB = Az + daz
+ else
+ HotA = Az + daz
+ HotB = Az - daz
+ endif
+ if(HotA.lt.0.0) HotA=HotA+360.0
+ if(HotA.gt.360.0) HotA=HotA-360.0
+ if(HotB.lt.0.0) HotB=HotB+360.0
+ if(HotB.gt.360.0) HotB=HotB-360.0
+
+900 continue
+ naz=nint(Az)
+ nel=nint(el)
+ nDmiles=nint(Dmiles)
+ nDkm=nint(Dkm)
+ nHotAz=nint(HotB)
+ nHotABetter=0
+ if(HotABetter) then
+ nHotAz=nint(HotA)
+ nHotABetter=1
+ endif
+
+999 return
+end subroutine azdist
diff --git a/calibrate.f90 b/calibrate.f90
new file mode 100644
index 0000000..68953a2
--- /dev/null
+++ b/calibrate.f90
@@ -0,0 +1,161 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: calibrate.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program calibrate
+
+! Get calibration and alignment info for an HF Time-of-Arrival *.wav file.
+
+ parameter (NFSMAX=12000)
+ parameter (NMAX=300*NFSMAX) !Max length of data
+ parameter (NFFTMAX=4*1024*1024,NHMAX=NFFTMAX/2)
+ character*40 infile,outfile
+ integer*2 id(NMAX) !Integer data from *.wav file
+ real x(NMAX) !Data converted to floats
+ real xx(NFFTMAX) !Resampled data
+ real xxa(NFFTMAX) !Resampled and time-aligned
+ real prof(NFSMAX+5) !Folded profile, p=nfs
+ real*8 p1 !Measured period of 1 PPS pulse
+ real*8 samfac !Resample factor
+
+ character cdate*8 !CCYYMMDD
+ character ctime*10 !HHMMSS.SSS
+ character*4 mode
+ character*6 mycall,mygrid
+ real*8 fkhz,tsec
+
+ integer resample
+ complex cxx(0:NHMAX)
+ complex z1
+ complex cal(35)
+ equivalence (xx,cxx)
+
+ nargs=iargc()
+ if(nargs.ne.1) then
+ print*,'Usage: calibrate <infile>'
+ print*,'Example: calibrate K1JT_110209_214500.wav'
+ go to 999
+ endif
+
+ call getarg(1,infile)
+ open(12,file=infile,access='stream',status='old')
+ call read_wav(12,id,npts,nfs,nch) !Read data from *.wav file
+ read(12) tsec,fkhz,mycall,mygrid,mode,ctime !Get header info
+ cdate='?'
+ read(12,end=1) cdate
+1 close(12)
+
+ i1=index(infile,'.wav')
+ outfile=infile(:i1-1)//'.prof'
+ open(13,file=outfile,status='unknown') !1 PPS profile
+
+ outfile=infile(:i1-1)//'.bin' !Binary data
+ open(14,file=outfile,form='unformatted',status='unknown')
+
+ outfile=infile(:i1-1)//'.cal' !Calibration data
+ open(15,file=outfile,status='unknown')
+
+ dt=1.0/nfs
+ n=log(float(npts))/log(2.0) + 0.9999
+ nfft=2**n
+ df=float(nfs)/nfft
+
+ x(:npts)=id(:npts) !Convert to floats
+ call averms(x,npts,ave,rms,xmax) !Get ave, rms
+ x(:npts)=(1.0/rms)*(x(:npts)-ave) !Remove DC and normalize
+
+ ip1=nfs-1
+ ip2=nfs
+ call fold1pps(x,npts,ip1,ip2,prof,p1,peak,ipk) !Find sample rates
+
+ write(*,1010) mycall,mygrid,cdate,ctime(:6)
+1010 format(a6,4x,a6,6x,'Date: ',a8,' Time: ',a6)
+ write(*,1011) fkhz,mode,float(npts)/nfs
+1011 format('Freq:',f10.3,' kHz Mode: ',a4,' Duration:',f6.1' s')
+
+! Resample ntype: 0=best, 1=sinc_medium, 2=sinc_fast, 3=hold, 4=linear
+ ntype=1
+ samfac=nfs/p1
+ ierr=resample(x,xx,samfac,npts,ntype) !Resample to nfs Hz, exactly
+ if(ierr.ne.0) print*,'Resample error.',samfac
+ npts1=samfac*npts
+ npts=npts1
+
+ xx(npts+1:nfft)=0.
+ ip=nfs
+ i1=ipk1+ip-100
+ xxa(1:npts-i1+1)=xx(i1:npts) !Align data so that 1 PPS is at start
+ npts=npts-i1+1
+ xxa(npts+1:nfft)=0.
+
+ prof=0.
+ do i=1,npts,nfs !Fold at p=nfs (exactly)
+ prof(:ip)=prof(:ip) + xxa(i:i+ip-1)
+ enddo
+
+ pmax=0.
+ do i=1,ip
+ if(abs(prof(i)).gt.abs(pmax)) then
+ pmax=prof(i)
+ ipk=i
+ endif
+ enddo
+
+ fac=1.0/pmax
+ do i=0,ip-1
+ i1=ipk+i
+ if(i1.gt.ip) i1=i1-ip
+ xx(i+1)=fac*prof(i1)
+ enddo
+ prof(:ip)=xx(:ip) !Save time-domain profile
+
+ do i=-20,250
+ j=i
+ if(j.lt.1) j=j+ip
+ write(13,1020) 1000.0*(i-1)*dt,prof(j)
+1020 format(f12.3,f12.6)
+ enddo
+
+ call four2a(xx,ip,1,-1,0) !FFT of 1 PPS profile
+
+ cal=0.
+ do j=1,35 !Compute calibration array
+ i=100*j
+ z1=0.01*sum(cxx(i-50:i+49))
+ cal(j)=z1
+ s1=real(z1)**2 + aimag(z1)**2
+ pha1=atan2(aimag(z1),real(z1))
+ write(15,1030) i,db(s1),pha1
+1030 format(i6,2f10.3)
+ enddo
+
+ print*,xxa(1:3)
+ call averms(xxa,npts,ave,rms,xmax) !Get ave, rms
+ fac=100.0
+ if(xmax.gt.200.0) fac=20000.0/xmax
+ id(:npts)=fac*xxa(:npts)
+ write(14) tsec,fkhz,mycall,mygrid,mode,ctime,cdate,ip,npts,fac, &
+ prof(:ip),cal,id(:npts)
+
+999 end program calibrate
diff --git a/calobs.f90 b/calobs.f90
new file mode 100644
index 0000000..ebbfac5
--- /dev/null
+++ b/calobs.f90
@@ -0,0 +1,126 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: calobs.90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine calobs(nfs,nsec,ndevin,id,x1)
+
+ parameter (NFSMAX=48000)
+ parameter (NMAX=1210*NFSMAX) !Max length of data
+ integer*2 id(NMAX) !Raw data
+ real*8 p1,samfac
+ real x1(NMAX),xx1(NMAX)
+ real prof1(NFSMAX+5)
+ real xx(NFSMAX+5)
+ complex cc(0:NFSMAX/2)
+ complex z1
+ integer soundin,resample
+ equivalence (xx,cc)
+
+ npts=nfs*nsec
+ nchan=1
+ ierr=soundin(ndevin,nfs,id,npts,nchan-1) !Get audio data
+ if(ierr.ne.0) then
+ print*,'Error in soundin',ierr
+ stop
+ endif
+
+ x1(:npts)=id(:npts)
+ call averms(x1,npts,ave1,rms1,xmax1) !Get ave, rms
+ x1(:npts)=(1.0/rms1)*(x1(:npts)-ave1) !Remove DC and normalize
+
+ ip1=nfs-5
+ ip2=nfs+4
+ call fold1pps(x1,npts,ip1,ip2,prof1,p1,pk1,ipk1) !Find sample rates
+
+! Resample ntype: 0=best, 1=sinc_medium, 2=sinc_fast, 3=hold, 4=linear
+ ntype=1
+ samfac=nfs/p1
+ ierr=resample(x1,xx1,samfac,npts,ntype) !Resample to nfs Hz, exactly
+ if(ierr.ne.0) print*,'Resample error.',samfac
+ npts=samfac*npts
+
+ ip=nfs
+ prof1=0.
+ do i=1,npts,nfs !Fold at p=nfs (exactly)
+ prof1(:ip)=prof1(:ip) + xx1(i:i+ip-1)
+ enddo
+
+ pmax=0.
+ do i=1,ip
+ if(abs(prof1(i)).gt.abs(pmax)) then
+ pmax=prof1(i)
+ ipk=i
+ endif
+ enddo
+ prof1(:ip)=prof1(:ip)/pmax
+
+ ppm=1.d6*(48000.d0/p1 - 1.d0)
+ err=1.e6/npts
+ write(*,1000) ave1,rms1,xmax1,p1,ppm,err
+1000 format('Ave:',f8.2,' Rms:',f8.2,' Max:',f8.0/ &
+ 'Fsample:',f12.4,' Sample interval error:',f8.3,' +/-',f7.3,' ppm')
+ write(71,3001) ave1,rms1,xmax1,p1,ppm,err
+3001 format(2f8.2,f8.0,f12.4,2f9.3)
+ call flush(71)
+
+ open(10,file='prof_1pps.dat',status='unknown')
+ open(11,file='short_1pps.dat',status='unknown')
+ write(10,1010) 0.0,0.0,ave1,rms1,xmax1,p1,ppm,err
+ write(11,1010) 0.0,0.0,ave1,rms1,xmax1,p1,ppm,err
+1010 format(2f10.3,' Ave:',f8.2,' Rms:',f8.2,' Max:',f8.0/ &
+ 'Fsample:',f12.4,' Sample interval error:',f8.3,' +/-',f7.3,' ppm')
+
+ dt=1.0/nfs
+ i0=0.005/dt
+ do i=1,ip
+ j=ipk+i-1
+ if(j.gt.ip) j=j-ip
+ xx(i)=prof1(j)
+ write(10,1030) 1000.0*i*dt,xx(i)
+1030 format(2f10.3)
+ t=1000.0*(i-i0)*dt
+ if(t.ge.-5.0 .and. t.le.20.0) then
+ j=ipk+i-i0
+ if(j.lt.1) j=j+ip
+ if(j.gt.ip) j=j-ip
+ write(11,1030) t,prof1(j)
+ endif
+ enddo
+ close(10)
+ close(11)
+
+ call four2a(xx,ip,1,-1,0)
+
+ open(12,file='cal.dat',status='unknown')
+ do j=1,35 !Compute calibration arrays
+ i=100*j
+ z1=0.01*sum(cc(i-50:i+49))
+ if(j.eq.1) write(12,1040) j,z1,p1
+ if(j.ne.1) write(12,1040) j,z1
+1040 format(i6,2f10.3,f13.4)
+ enddo
+ close(12)
+
+ return
+end subroutine calobs
diff --git a/ccf.f90 b/ccf.f90
new file mode 100644
index 0000000..015851a
--- /dev/null
+++ b/ccf.f90
@@ -0,0 +1,306 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: ccf.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program ccf
+
+! Correlate two-station recordings for HF Time-of-Arrival project.
+
+ parameter (NFSMAX=48000)
+ parameter (NMAX=300*NFSMAX) !Max length of data
+ parameter (NFFTMAX=4*1024*1024,NHMAX=NFFTMAX/2)
+ integer*2 id1(NMAX),id2(NMAX) !Sampled data
+ real*4 x1(NMAX),x2(NMAX)
+ character arg*12 !Command-line arg
+ character*40 file1,file2
+ real prof1(NFSMAX),prof2(NFSMAX)
+ real*8 p1,p2,samfac1,samfac2
+ integer resample
+ real xx1(NFFTMAX),xx2(NFFTMAX),xx(NFFTMAX),xx1pps(NFFTMAX)
+ real xx1a(NFFTMAX),xx2a(NFFTMAX)
+ real xcf1(-512:12511),xcf2(-512:12511)
+ real rfil(0:NHMAX)
+
+ character cdate*8 !CCYYMMDD
+ character ctime*10 !HHMMSS.SSS
+ character*4 mode1,mode2
+ character*6 call1,call2,grid1,grid2
+ real*8 fkhz,tsec
+
+ complex c1(0:NHMAX),c2(0:NHMAX),cc(0:NHMAX)
+ complex z1,z2
+ complex cal1(35),cal2(35)
+ data pi/3.14159265/
+ equivalence (xx1,c1),(xx2,c2),(xx,cc)
+
+ nargs=iargc()
+ if(nargs.ne.4) then
+ print*,'Usage: ccf <f1> <f2> <file1> <file2>'
+ print*,'Example: ccf 300 3000 K1JT_110209_024500.wav AA6E_110209_024500.wav'
+ go to 999
+ endif
+
+ call getarg(1,arg)
+ read(arg,*) nf1
+ call getarg(2,arg)
+ read(arg,*) nf2
+ call getarg(3,file1)
+ call getarg(4,file2)
+ open(12,file=file1,access='stream',status='old')
+ call read_wav(12,id1,npts1,nfs1,nch1) !Read data from disk
+ if(file1(1:4).ne.'K9AN') then
+ read(12) tsec,fkhz,call1,grid1,mode1,ctime !Get header info
+ cdate='?'
+ read(12,end=1) cdate
+ endif
+1 close(12)
+
+ open(12,file=file2,access='stream',status='old')
+ call read_wav(12,id2,npts2,nfs2,nch2)
+ read(12) tsec,fkhz,call2,grid2,mode2,ctime !Get header info
+ cdate='?'
+ read(12,end=2) cdate
+2 close(12)
+
+ open(32,file='ccfprof.dat',status='unknown')
+ open(33,file='ccfcal.dat',status='unknown')
+ open(34,file='ccf.out',status='unknown')
+
+ if(nfs1.ne.nfs2) then
+ print*,'Mismatched sample rates:',nfs1,nfs2
+ go to 999
+ endif
+
+ nfs=nfs1
+ npts0=min(npts1,npts2)
+ npts=npts0
+ dt=1.0/nfs
+ n=log(float(npts))/log(2.0) + 0.9999
+ nfft=2**n
+ df=float(nfs)/nfft
+
+ x1(:npts)=id1(:npts)
+ x2(:npts)=id2(:npts)
+
+ call averms(x1,npts,ave1,rms1,xmax1) !Get ave, rms
+ call averms(x2,npts,ave2,rms2,xmax2)
+ x1(:npts)=(1.0/rms1)*(x1(:npts)-ave1) !Remove DC and normalize
+ x2(:npts)=(1.0/rms2)*(x2(:npts)-ave2)
+
+ ip1=nfs-1
+ ip2=nfs
+ call fold1pps(x1,npts,ip1,ip2,prof1,p1,pk1,ipk1) !Find sample rates
+ call fold1pps(x2,npts,ip1,ip2,prof2,p2,pk2,ipk2)
+
+ write(*,1010) call1,grid1,cdate,ctime(:6),ave1,rms1,xmax1
+ write(*,1010) call2,grid2,cdate,ctime(:6),ave2,rms2,xmax2
+1010 format(a6,2x,a6,2x,'UTC: ',a8,1x,a6,' Ave:',f8.1,' Rms:', &
+ f8.1,' Max:',f8.1)
+ write(*,1011) fkhz,mode1,float(npts)/nfs
+1011 format('Freq:',f10.3,' kHz Mode: ',a4,' Duration:',f6.1' s')
+
+! Resample ntype: 0=best, 1=sinc_medium, 2=sinc_fast, 3=hold, 4=linear
+ ntype=3
+ samfac1=nfs/p1
+ ierr=resample(x1,xx1,samfac1,npts,ntype) !Resample to nfs Hz, exactly
+ if(ierr.ne.0) print*,'Resample error.',samfac1
+ npts1=samfac1*npts
+
+ samfac2=nfs/p2
+ ierr=resample(x2,xx2,samfac2,npts,ntype)
+ if(ierr.ne.0) print*,'Resample error.',samfac2
+ npts2=samfac2*npts
+ npts=min(npts1,npts2)
+
+ xx1(npts+1:nfft)=0.
+ xx2(npts+1:nfft)=0.
+ ip=nfs
+ i1=ipk1+ip-100
+ xx1a(1:npts-i1+1)=xx1(i1:npts) !Align data so that 1 PPS is at start
+ i2=ipk2+ip-100
+ xx2a(1:npts-i2+1)=xx2(i2:npts)
+ npts=min(npts-i1+1,npts-i2+1)
+ xx1a(npts+1:nfft)=0.
+ xx2a(npts+1:nfft)=0.
+
+ prof1=0.
+ prof2=0.
+ do i=1,npts,nfs !Fold at p=nfs (exactly)
+ prof1(:ip)=prof1(:ip) + xx1a(i:i+ip-1)
+ prof2(:ip)=prof2(:ip) + xx2a(i:i+ip-1)
+ enddo
+
+ pmin1=0.
+ pmin2=0.
+ do i=1,ip
+ if(prof1(i).lt.pmin1) then
+ pmin1=prof1(i)
+ ipk1=i
+ endif
+ if(prof2(i).lt.pmin2) then
+ pmin2=prof2(i)
+ ipk2=i
+ endif
+ enddo
+
+ fac1=-1.0/pmin1
+ fac2=-1.0/pmin2
+ do i=0,ip-1
+ i1=ipk1+i
+ if(i1.gt.ip) i1=i1-ip
+ i2=ipk2+i
+ if(i2.gt.ip) i2=i2-ip
+ xx1(i+1)=fac1*prof1(i1)
+ xx2(i+1)=fac2*prof2(i2)
+ enddo
+
+ do i=-20,250
+ j=i
+ if(j.lt.1) j=j+ip
+ write(32,1020) 1000.0*i*dt,xx1(j),xx2(j)
+1020 format(f12.3,2f10.3)
+ enddo
+
+ call four2a(xx1,ip,1,-1,0) !FFTs of 1 PPS profiles
+ call four2a(xx2,ip,1,-1,0)
+
+ do j=1,35 !Compute calibration arrays
+ i=100*j
+ z1=0.01*sum(c1(i-50:i+49))
+ z2=0.01*sum(c2(i-50:i+49))
+ cal1(j)=z1/8.0
+ cal2(j)=z2
+ s1=real(z1)**2 + aimag(z1)**2
+ s2=real(z2)**2 + aimag(z2)**2
+ pha1=atan2(aimag(z1),real(z1))
+ pha2=atan2(aimag(z2),real(z2))
+ write(33,1030) i,db(s1),pha1,db(s2),pha2
+1030 format(i6,4f10.3)
+ enddo
+
+ xx1=xx1a
+ xx2=xx2a
+ nchop=200
+ do i=nchop,npts,nfs !Keep only the 1PPS pulse
+ xx1(i:i+nfs-nchop)=0.
+ xx2(i:i+nfs-nchop)=0.
+ enddo
+
+ call four2a(xx1,nfft,1,-1,0) !Forward FFTs of 1PPS pulses
+ call four2a(xx2,nfft,1,-1,0)
+
+ fac=1.e-12
+ cc=0.
+ ia=100/df !Define rectangular passband
+ ib=3500/df
+ rfil=0.
+ do i=ia,ib
+ j=nint(0.01*i*df)
+ z1=c1(i)/cal1(j) !Apply calibrations
+ z2=c2(i)/cal2(j)
+ cc(i)=fac*z1*conjg(z2) !Multiply transforms
+ f=i*df
+ rfil(i)=1.0
+ if(f.lt.float(nf1)) rfil(i)=exp(-((f-nf1)/300.0)**2)
+ if(f.gt.float(nf2)) rfil(i)=exp(-((f-nf2)/300.0)**2)
+! if(mod(i,1000).eq.0) write(37,7001) f,rfil(i)
+!7001 format(f12.3,f12.6)
+ cc(i)=cc(i)*rfil(i)
+ cc(i)=conjg(cc(i))
+ enddo
+
+ call four2a(cc,nfft,1,1,-1) !Inverse FFT ==> CCF of 1 PPS pulses
+ xx1pps=xx*sqrt(float(nfs)/nchop)
+
+ xx1=xx1a
+ xx2=xx2a
+ do i=1,npts,nfs !Keep signal without 1 PPS pulses
+ xx1(i:i+nchop)=0.
+ xx2(i:i+nchop)=0.
+ enddo
+
+ call four2a(xx1,nfft,1,-1,0) !Forward FFTs of signal
+ call four2a(xx2,nfft,1,-1,0)
+
+ fac=8*1.e-12
+ cc=0.
+ do i=ia,ib
+ j=nint(0.01*i*df)
+ z1=c1(i)/cal1(j) !Apply calibrations
+ z2=c2(i)/cal2(j)
+ cc(i)=fac*z1*conjg(z2) !Multiply transforms
+ cc(i)=cc(i)*rfil(i)
+ cc(i)=conjg(cc(i))
+ enddo
+
+ call four2a(cc,nfft,1,1,-1) !Inverse FFT ==> CCF of signal
+
+ i1=-512
+! i2=511
+ i2=12511
+ pk1=0.
+ pk2=0.
+ do i=i1,i2
+ j=i
+ if(j.le.0) j=i+nfft
+ xcf1(i)=xx1pps(j)
+ xcf2(i)=xx(j)
+ pk1=max(pk1,xcf1(i))
+ pk2=max(pk2,xcf2(i))
+ enddo
+
+ xpk1=0.
+ xpk2=0.
+ do i=i1,i2
+ xcf1(i)=xcf1(i)/pk1
+ xcf2(i)=xcf2(i)/pk2
+ write(34,1110) 1000.0*i*dt,xcf1(i),xcf2(i) !Write CCFs to disk
+1110 format(f10.3,2f12.6)
+ if(xcf1(i).gt.xpk1) then
+ xpk1=xcf1(i)
+ ipk1=i
+ endif
+ if(xcf2(i).gt.xpk2) then
+ xpk2=xcf2(i)
+ ipk2=i
+ endif
+ enddo
+ write(*,1112) samfac1,samfac2,1000.0*(ipk2-ipk1)*dt
+1112 format('sf1:', f12.9,' sf2:',f12.9,' Delay:',f8.2)
+
+ nfft2=1024
+ xx1(:nfft2)=xcf1(-512:511)
+ xx2(:nfft2)=xcf2(-512:511)
+ call four2a(xx1,nfft2,1,-1,0)
+ call four2a(xx2,nfft2,1,-1,0)
+ df2=float(nfs)/nfft2
+ iz=3500.0/df2
+ do i=1,iz
+ s1=real(c1(i))**2 + aimag(c1(i))**2
+ s2=real(c2(i))**2 + aimag(c2(i))**2
+ write(35,1120) i*df2,s1,s2,db(s1),db(s2)
+1120 format(f10.3,2f12.1,2f12.3)
+ enddo
+
+999 end program ccf
diff --git a/ccf2.f90 b/ccf2.f90
new file mode 100644
index 0000000..4b3d701
--- /dev/null
+++ b/ccf2.f90
@@ -0,0 +1,69 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: ccf2.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine ccf2(ss,nz,lag1,lag2,ccfbest,lagpk)
+
+ real ss(nz)
+ real pr(162)
+ logical first
+
+! The WSPR pseudo-random sync pattern:
+ integer npr(162)
+ data npr/ &
+ 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, &
+ 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, &
+ 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, &
+ 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, &
+ 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, &
+ 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, &
+ 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, &
+ 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, &
+ 0,0/
+ data first/.true./
+ save
+
+ if(first) then
+ nsym=162
+ do i=1,nsym
+ pr(i)=2*npr(i)-1
+ enddo
+ endif
+
+ ccfbest=0.
+
+ do lag=lag1,lag2
+ x=0.
+ do i=1,nsym
+ j=16*i + lag
+ if(j.ge.1 .and. j.le.nz) x=x+ss(j)*pr(i)
+ enddo
+ if(x.gt.ccfbest) then
+ ccfbest=x
+ lagpk=lag
+ endif
+ enddo
+
+ return
+end subroutine ccf2
diff --git a/chklevel.f90 b/chklevel.f90
new file mode 100644
index 0000000..7122137
--- /dev/null
+++ b/chklevel.f90
@@ -0,0 +1,85 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: chklevel.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine chklevel(kwave,ntrminutes,iz,jz,nsec1,xdb1,xdb2,i4)
+
+! Called from wspr2 at ~5 Hz rate.
+
+ integer*2 kwave(iz,jz)
+ integer time
+ data nsec3z/-999/
+ save nsec3z
+
+ nfsample=48000
+ if(ntrminutes.eq.15) nfsample=12000
+ nsec3=time()
+ i2=nfsample*(nsec3-nsec1)
+ if(i2.gt.jz) i2=jz
+ i1=max(1,i2-nfsample+1)
+ do i=i2,i1,-1
+ if(kwave(1,i).ne.0) go to 10
+ enddo
+
+10 i4=i
+ tc=0.2 !Level-meter time constant (s)
+ ii=nint(tc*nfsample)
+ i3=max(1,i4-ii+1)
+ if(nsec3.eq.nsec3z) go to 900
+
+ nsec3z=nsec3
+ npts=i4-i3+1
+ s1=0.
+ s2=0.
+ do i=i3,i4
+ s1=s1+kwave(1,i)
+ if(iz.eq.2) s2=s2+kwave(2,i)
+ enddo
+ ave1=s1/npts
+ ave2=s2/npts
+ sq1=0.
+ sq2=0.
+ do i=i3,i4
+ x1=kwave(1,i)-ave1
+ sq1=sq1 + x1*x1
+ if(iz.eq.2) then
+ x2=kwave(2,i)-ave2
+ sq2=sq2 + x2*x2
+ endif
+ enddo
+
+ if(sq1.gt.0.0) then
+ rms1=sqrt(sq1/npts)
+ xdb1=20.0*log10(rms1)
+ endif
+
+ if(sq2.gt.0.0) then
+ rms2=sqrt(sq2/npts)
+ xdb2=20.0*log10(rms2)
+ endif
+
+900 continue
+
+ return
+end subroutine chklevel
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..26e385c
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,552 @@
+dnl $Id: configure.ac 4337 2014-09-18 18:01:13Z ki7mt $
+dnl Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.69])
+AC_COPYRIGHT([$Id: configure.ac 4337 2014-09-18 18:01:13Z ki7mt $])
+AC_INIT([WSPR], [4.0], [wsjt-devel at lists.sourceforge.net],[],
+[http://www.physics.princeton.edu/pulsar/K1JT/wspr.html])
+
+# ------------------------------------------------------------------------------
+# path vars
+# ------------------------------------------------------------------------------
+AC_CONFIG_AUX_DIR([build-aux])
+AC_CANONICAL_HOST
+AC_CANONICAL_BUILD
+
+dnl enable configurable prefix path
+AC_PREFIX_DEFAULT("/usr")
+if test "$prefix" = "NONE"; then
+ prefix=${ac_default_prefix}
+fi
+
+# ------------------------------------------------------------------------------
+# user input paths
+# ------------------------------------------------------------------------------
+DOCDIR="$prefix"/share/doc/wspr
+MANDIR="$prefix"/share/man/man1
+
+AC_ARG_WITH([docdir],
+AC_HELP_STRING([--with-docdir=PATH], [path to documentuments]), [docdir=$with_docdir])
+if test -n "$with_docdir"; then
+ AC_SUBST([DOCDIR], ["$with_docdir"])
+fi
+
+AC_ARG_WITH([mandir],
+AC_HELP_STRING([--with-mandir=PATH], [path to manpages]), [mandir=$with_mandir])
+if test -n "$with_mandir"; then
+ AC_SUBST([MANDIR], ["$with_mandir"])
+fi
+
+# ------------------------------------------------------------------------------
+# check compilers
+# ------------------------------------------------------------------------------
+OCFLAGS="$CFLAGS"
+AC_PROG_CC
+AC_PROG_CXX
+AC_PROG_FC
+AC_PROG_GCC_TRADITIONAL
+AC_PROG_MAKE_SET
+AC_PROG_RANLIB
+AC_PROG_MAKE_SET
+AC_USE_SYSTEM_EXTENSIONS
+CFLAGS="$OCFLAGS"
+
+# ------------------------------------------------------------------------------
+# check standard apps
+# ------------------------------------------------------------------------------
+AC_C_INLINE
+AC_PATH_PROG([AR], [ar])
+AC_PATH_PROG([AWK], [awk])
+AC_PATH_PROG([CP], [cp])
+AC_PATH_PROG([CHOWN], [chown])
+AC_PATH_PROG([CHMOD], [chmod])
+AC_PATH_PROG([LD], [ld])
+AC_PATH_PROG([LN], [ln])
+AC_PATH_PROG([MKDIR], [mkdir])
+AC_PATH_PROG([MV], [mv])
+AC_PATH_PROG([RANLIB], [ranlib])
+AC_PATH_PROG([RM], [rm])
+AC_PATH_PROG([SED], [sed])
+AC_PATH_PROG([SHELL], [bash])
+AC_PATH_PROG([SORT], [sort])
+AC_PATH_PROG([TR], [tr])
+AC_SUBST([OS], ["$host_os"])
+AC_SUBST([CPU], ["$host_cpu"])
+
+# ------------------------------------------------------------------------------
+# check headers
+# ------------------------------------------------------------------------------
+AC_HEADER_STDC
+AC_HEADER_DIRENT
+AC_HEADER_TIME
+AC_TYPE_INT16_T
+AC_TYPE_INT32_T
+AC_TYPE_INT64_T
+AC_TYPE_INT8_T
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_TYPE_UINT16_T
+AC_TYPE_UINT32_T
+AC_TYPE_UINT64_T
+AC_TYPE_UINT8_T
+AC_FUNC_MALLOC
+AC_CHECK_FUNCS([gettimeofday strchr])
+AC_CHECK_HEADERS([errno.h fcntl.h fcntl.h float.h fftw3.h inttypes.h libgen.h \
+limits.h linux/ppdev.h dev/ppbus/ppi.h linux/parport.h pthread.h portaudio.h \
+samplerate.h stddef.h stdint.h stdint.h stdio.h stdlib.h string.h strings.h \
+sys/ioctl.h sys/ioctl.h sys/param.h sys/resource.h sys/stat.h sys/syslog.h \
+sys/time.h sys/wait.h termios.h unistd.h wait.h])
+
+
+# ------------------------------------------------------------------------------
+# add additional lib paths
+# ----------------------------------------------------------------------------
+if test -d "/usr/lib64"; then LIBDIR="-L/usr/lib64 ${LIBDIR}"; fi
+if test -d "/usr/local/lib"; then LIBDIR="-L/usr/local/lib ${LIBDIR}"; fi
+if test -d "/usr/lib/x86_64-linux-gnu"; then LIBDIR="-L/usr/lib/x86_64-linux-gnu ${LIBDIR}"; fi
+if test -d "/usr/lib/i386-linux-gnu"; then LIBDIR="-L/usr/lib/i386-linux-gnu ${LIBDIR}"; fi
+if test -d "/usr/lib"; then LIBDIR="-L/usr/lib ${LIBDIR}"; fi
+if test -d "/usr/lib/arm-linux-gnueabihf"; then LIBDIR="-L/usr/lib/arm-linux-gnueabihf ${LIBDIR}"; fi
+
+# ------------------------------------------------------------------------------
+# find Python3
+# ------------------------------------------------------------------------------
+
+HAVE_PY3=no
+
+# check with-enable first
+AC_MSG_CHECKING([Python3 --with-python3])
+AC_ARG_WITH([f2py], [ --with-python3=PATH path to: ../python3],
+PY3="$withval")
+
+# if --with-python3 is not empty
+if test -n "$PY3"; then
+
+# check if user provided python3 is >= 3.2
+python3 -c "import sys; sys.exit(sys.version < '3.2')" >/dev/null 2>&1
+
+ if test "$?" != "0"; then
+ HAVE_PY3=no
+ AC_MSG_RESULT([no])
+ else
+ HAVE_PY3=yes
+ PY3_PATH="$PY3"
+ PY3V=`$PY3 -V`
+ AC_DEFINE([HAVE_PY3], [1])
+ AC_DEFINE_UNQUOTED([PY3_PATH], ["${PY3}"], [Path to Python3])
+ AC_SUBST([PYTHON], ["${PY3}"])
+ AC_MSG_RESULT([yes ${PY3V}])
+ fi
+else
+ AC_MSG_RESULT([no])
+fi
+
+# if not user supplied, check by calling python
+if test "$HAVE_PY3" = "no"; then
+
+ AC_MSG_CHECKING([Python3 using: python])
+ python -c "import sys; sys.exit(sys.version < '3.2')" >/dev/null 2>&1
+
+ if test "$?" != 0; then
+ AC_MSG_RESULT([no])
+ HAVE_PY3=no
+ else
+ HAVE_PY3=yes
+ PY3_PATH=`which python`
+ PY3V=`"$PY3_PATH" -V`
+ AC_DEFINE([HAVE_PY3], [1])
+ AC_DEFINE_UNQUOTED([PY3_PATH], ["${PY3_PATH}"], [Path to Python3])
+ AC_SUBST([PYTHON], ["${PY3_PATH}"])
+ AC_MSG_RESULT([yes ${PY3V}])
+ fi
+fi
+
+# if not user supplied && not from calling python, check by calling python3
+if test "$HAVE_PY3" = "no"; then
+ AC_MSG_CHECKING([Python3 using: python3])
+ python3 -c "import sys; sys.exit(sys.version < '3.2')" >/dev/null 2>&1
+
+ if test "$?" != 0; then
+ AC_MSG_RESULT([no])
+ HAVE_PY3=no
+ else
+ HAVE_PY3=yes
+ PY3_PATH=`which python3`
+ PY3V=`"$PY3_PATH" -V`
+ AC_DEFINE([HAVE_PY3], [1])
+ AC_DEFINE_UNQUOTED([PY3_PATH], ["${PY3_PATH}"], [Path to Python3])
+ AC_SUBST([PYTHON], ["${PY3_PATH}"])
+ AC_MSG_RESULT([yes ${PY3V}])
+ fi
+fi
+
+# ------------------------------------------------------------------------------
+# find f2py
+# ------------------------------------------------------------------------------
+
+HAVE_F2PY=no
+
+# check with-enable first
+AC_MSG_CHECKING([F2PY --with-f2py])
+AC_ARG_WITH([f2py], [ --with-f2py=PATH path to: ../f2py ../f2py3],
+F2PY="$withval")
+
+# if --with-f2py is not empty
+if test -n "$F2PY"; then
+
+# check if user provided location works
+$F2PY -v >/dev/null 2>&1
+
+ if test "$?" != "0"; then
+ HAVE_F2PY=no
+ AC_MSG_RESULT([no])
+ else
+ HAVE_F2PY=yes
+ F2PY_PATH="$F2PY"
+ F2PYV=`$F2PY -v`
+ AC_DEFINE([HAVE_F2PY], [1])
+ AC_DEFINE_UNQUOTED([F2PY_PATH], ["${F2PY}"], [Path to F2PY])
+ AC_SUBST([F2PY], ["${F2PY}"])
+ AC_MSG_RESULT([yes v${F2PYV}])
+ fi
+else
+ AC_MSG_RESULT([no])
+fi
+
+# if not user supplied, check by calling f2py
+if test "$HAVE_F2PY" = "no"; then
+
+ AC_MSG_CHECKING([F2PY using: f2py])
+ f2py -v >/dev/null 2>&1
+
+ # Check if f2py3 is called using "f2py"
+ if test "$?" != "0"; then
+ AC_MSG_RESULT([no])
+ HAVE_F2PY=no
+ else
+ HAVE_F2PY=yes
+ F2PY=`which f2py`
+ F2PYV=`$F2PY -v`
+ AC_DEFINE([HAVE_F2PY], [1])
+ AC_DEFINE_UNQUOTED([F2PY], ["${F2PY}"], [Path to F2PY])
+ AC_SUBST([F2PY], ["${F2PY}"])
+ AC_MSG_RESULT([yes v${F2PYV}])
+ fi
+fi
+
+# if not user supplied, or by calling f2py, try f2py3
+if test "$HAVE_F2PY" = "no"; then
+ AC_MSG_CHECKING([F2PY using: f2py3])
+ f2py3 -v | head -c 1 >/dev/null 2>&1
+
+ if test "$?" != "0"; then
+ AC_MSG_RESULT([no])
+ HAVE_F2PY=no
+ else
+ HAVE_F2PY=yes
+ F2PY=`which f2py3`
+ F2PYV=`$F2PY -v`
+ AC_DEFINE([HAVE_F2PY], [1])
+ AC_DEFINE_UNQUOTED([F2PY], ["${F2PY}"], [Path to F2PY])
+ AC_SUBST([F2PY], ["${F2PY}"])
+ AC_MSG_RESULT([yes v${F2PYV}])
+ fi
+fi
+
+# ------------------------------------------------------------------------------
+# check gfortran
+# ------------------------------------------------------------------------------
+
+AC_CHECK_LIB([gfortran], [_gfortran_st_write], [], [])
+
+if test "$ac_cv_lib_gfortran__gfortran_st_write" != "yes"; then
+ HAVE_GFORTRAN=0
+else
+ HAVE_GFORTRAN=1
+ FC=gfortran
+ FCV=gnu95
+ FC_LIB_PATH=`${FC} -print-file-name=`
+ AC_DEFINE_UNQUOTED([FC_LIB_PATH], ["${FC_LIB_PATH}"], [Path to Gfortran libs.])
+ AC_SUBST([FC_LIB_PATH], ["${FC_LIB_PATH}"])
+ AC_DEFINE_UNQUOTED([FC], ["${FC}"], [Gfortran Compiler])
+ AC_SUBST([FC], ["${FC}"])
+ AC_SUBST([FCV], ["${FCV}"])
+ AC_DEFINE([HAVE_GFORTRAN], [1],)
+ AC_DEFINE([HAVE_GFORTRAN_LIB], [1],)
+fi
+
+# ------------------------------------------------------------------------------
+# check portaudio
+# ------------------------------------------------------------------------------
+
+AC_CHECK_LIB([portaudio], [Pa_Initialize], [], [])
+if test "$ac_cv_lib_portaudio_Pa_Initialize" = "yes"; then
+ LIBS="-lportaudio ${LIBS}"
+fi
+
+# if headers and libs found, set define
+if test "$ac_cv_header_portaudio_h" = "yes" -a "$ac_cv_lib_portaudio_Pa_Initialize" = "yes"; then
+ HAVE_PORTAUDIO=1
+ AC_DEFINE([HAVE_PORTAUDIO_H], [1], [Portaudio Header])
+ AC_DEFINE([HAVE_PORTAUDIO_LIB], [1], [Portaudio Lib])
+fi
+
+
+# ------------------------------------------------------------------------------
+# check fftw3
+# ------------------------------------------------------------------------------
+
+AC_CHECK_LIB([fftw3f], [sfftw_destroy_plan_], [], [])
+
+if test "$ac_cv_lib_fftw3f_sfftw_destroy_plan_" = "yes"; then
+ LIBS="-lfftw3f ${LIBS}"
+fi
+
+# if headers and libs found, set defines
+if test "$ac_cv_lib_fftw3f_sfftw_destroy_plan_" = "yes"; then
+ HAVE_FFTW3_LIB=1
+ AC_DEFINE([HAVE_FFTW3_LIB], [1], [FFTW3 Libs])
+fi
+
+
+# ------------------------------------------------------------------------------
+# check samplerate
+# ------------------------------------------------------------------------------
+
+AC_CHECK_LIB([samplerate], [src_simple], [], [])
+
+if test "$ac_cv_lib_samplerate_src_simple" = "yes"; then
+ LIBS="-lsamplerate ${LIBS}"
+fi
+
+# if headers and libs found, set define
+if test "$ac_cv_header_samplerate_h" = "yes" -a "$ac_cv_lib_samplerate_src_simple" = "yes"; then
+ HAVE_SAMPLERATE=1
+ AC_DEFINE([HAVE_SAMPLERATE_H], [1], [Samplerate Header])
+ AC_DEFINE([HAVE_SAMPLERATE_LIB], [1], [Samplerate Lib])
+fi
+
+# ------------------------------------------------------------------------------
+# consolidate flags - ( remove dupes and sort )
+# ------------------------------------------------------------------------------
+
+_LBU=$(echo "-lpthread $LIBS" |tr ' ' '\n'|sort -su |tr '\n' ' ')
+LIBS="$_LBU"
+
+_LDU=$(echo "$LIBDIR" |tr ' ' '\n'|sort -su |tr '\n' ' ')
+LIBDIR="$_LDU"
+
+_CPPU=$(echo "-I/usr/include -I/usr/local/include $CPPFLAGS" |tr ' ' '\n'|sort -su |tr '\n' ' ')
+CPPFLAGS="$_CPPU"
+
+case "${host_os}" in
+ *linux* )
+ FCOPT="-cpp -fbounds-check -O2"
+ case "${host_cpu}" in
+ *armv6* )
+ CFLAGS="-O2 -march=armv6j -mfpu=vfp -mfloat-abi=hard -fpic"
+ FFLAGS="-O2 -Wall -fno-range-check -ffixed-line-length-none \
+-Wno-character-truncation -Wno-conversion -Wtabs -mfloat-abi=hard -fPIC"
+ LDFLAGS="-Wl,-O1 -Wl,--as-needed"
+ ;;
+ *x86_64* )
+ CFLAGS="-fPIC"
+ FFLAGS="-O2 -Wall -fbounds-check -fno-second-underscore \
+-Wno-conversion -Wno-character-truncation -fPIC"
+ LDFLAGS="-Wl,-O1 -Wl,--as-needed"
+ ;;
+ *i386* )
+ CFLAGS="-fPIC"
+ FFLAGS="-O2 -Wall -fbounds-check -fno-second-underscore \
+-Wno-conversion -Wno-character-truncation -fPIC"
+ LDFLAGS="-Wl,-O1 -Wl,--as-needed"
+ ;;
+ esac
+ ;;
+ *-*-darwin-* )
+ FCOPT="-cpp -fbounds-check -O2"
+ CFLAGS="-Wall -O0 -fPIC -m64"
+ FFLAGS="-O2 -m64 -Wall -fbounds-check -fno-second-underscore \
+-Wno-conversion -Wno-character-truncation -fPIC"
+ LDFLAGS="-Wl,-O1 -Wl,--as-needed"
+ ;;
+ *)
+ AC_MSG_ERROR([Unsupported System: ${host_os}.])
+ ;;
+esac
+
+# ------------------------------------------------------------------------------
+# report Python
+# ------------------------------------------------------------------------------
+
+if test "$HAVE_PY3" = "no"; then
+ echo
+ echo "Python v3.2+ is required to build $PACKAGE_NAME"
+ echo
+ echo "Possible Solutions:"
+ echo "[1] Install your distro version of: Python v3.2+"
+ echo "[2] Add Python3.2+ to your system paths"
+ echo "[3] Include Python3.2+:"
+ echo
+ echo 'Example:'
+ echo './configure --with-python3="/home/$USER/test/python3"'
+ echo
+ exit 1
+else
+ # add python3 path to wspr.sh script
+ sed -i '/wspr.py/d' wspr.sh
+ echo "$PY3_PATH -O wspr.py" >> wspr.sh
+fi
+
+# ------------------------------------------------------------------------------
+# report f2py
+# ------------------------------------------------------------------------------
+
+if test "$HAVE_F2PY" = "no"; then
+ echo
+ echo "F2PY v2.0 (Numpy 1.8.1+) is required to build $PACKAGE_NAME"
+ echo
+ echo "Possible Solutions:"
+ echo "[1] Install your distro version of: Python3 Numpy 1.8.1+"
+ echo "[2] Add F2PY v2.0 to your system paths"
+ echo "[3] Include F2PY:"
+ echo
+ echo 'Example:'
+ echo './configure --with-f2py="/home/$USER/test/f2py"'
+ echo
+ exit 1
+fi
+
+# ------------------------------------------------------------------------------
+# report gfortran
+# ------------------------------------------------------------------------------
+
+if test ${HAVE_GFORTRAN} -eq 0; then
+ echo
+ echo "CONFIGURATION ERROR"
+ echo
+ echo "Gfortran is required to build $PACKAGE_NAME"
+ echo
+ echo "Standard locations were checked, and not found."
+ echo
+ echo "Possible Solutions:"
+ echo "[1] Install your distro version of: gfortran"
+ echo "[2] On x86_64 systems, ensure you include x86 / i386 libgfortran"
+ echo
+ exit 1
+fi
+
+# ------------------------------------------------------------------------------
+# report samplerate
+# ------------------------------------------------------------------------------
+
+if test ${HAVE_SAMPLERATE} -eq 1; then
+ srstatus="OK"
+else
+ echo "CONFIGURATION ERROR"
+ echo
+ echo "Samplerate-dev is required to build $PACKAGE_NAME"
+ echo
+ echo "Possible Solutions:"
+ echo "[1] Install your distro version of: Saplerate - Libs & Headers"
+ echo "[2] Confiure Samplerate with non-standard paths:"
+ echo
+ echo 'Example:'
+ echo './configure CPPFLAGS="-I/home/$USER/test/samplerate/include" \'
+ echo 'LIBDIR="/home/$USER/test/samplerate/lib"'
+ echo
+ exit 1
+fi
+
+# ------------------------------------------------------------------------------
+# report fftw
+# ------------------------------------------------------------------------------
+
+if test ${HAVE_FFTW3_LIB} -eq 1; then
+ ffstatus="OK"
+else
+ echo "CONFIGURATION ERROR"
+ echo
+ echo "FFTW3 Libs were not found"
+ echo
+ echo "Possible Solutions:"
+ echo "[1] Install your distro version of: libfftw3-dev"
+ echo "[2] Confiure FFTW with non-standard paths:"
+ echo
+ echo './configure LIBDIR="/home/$USER/test/fftw3/lib"'
+ echo
+ exit 1
+fi
+
+# ------------------------------------------------------------------------------
+# report portaudio
+# ------------------------------------------------------------------------------
+
+if test ${HAVE_PORTAUDIO} -eq 1; then
+ pastatus="OK"
+else
+ echo "CONFIGURATION ERROR"
+ echo
+ echo "Portaudio-v19 was not found"
+ echo
+ echo "Possible Solutions:"
+ echo "[1] Install your distro version of: portaudio19-dev"
+ echo "[2] Confiure portaudio with non-standard paths:"
+ echo
+ echo './configure CPPFLAGS="-I/home/$USER/test/portaudio/include" \'
+ echo 'LIBDIR="/home/$USER/test/portaudio/lib"'
+ echo
+ exit 1
+fi
+
+# ------------------------------------------------------------------------------
+# substitutions
+# ------------------------------------------------------------------------------
+
+AC_SUBST([PREFIX], ["$prefix"])
+AC_SUBST([BUGS], ["$PACKAGE_BUGREPORT"])
+AC_SUBST([PROGRAM], ["$PACKAGE_NAME"])
+AC_SUBST([VERSION], ["$PACKAGE_VERSION"])
+AC_SUBST([WEB], ["$PACKAGE_URL"])
+AC_SUBST([CFLAGS], ["$CFLAGS"])
+AC_SUBST([CPPFLAGS], ["$CPPFLAGS"])
+AC_SUBST([FFLAGS], ["$FFLAGS"])
+AC_SUBST([LDFLAGS], ["$LDFLAGS"])
+AC_SUBST([FCOPT], ["$FCOPT"])
+AC_SUBST([LIBDIR], ["$LIBDIR"])
+AC_SUBST([SHARED], ["$PREFIX"/share/wspr])
+AC_SUBST([WSPRLIB], ["$PREFIX"/lib/wspr])
+AC_SUBST([BINDIR], ["$PREFIX"/bin])
+AC_SUBST([LIBS], ["$LIBS"])
+AC_SUBST([HOMEDIR], [/home/"$USER"/.wspr])
+AC_SUBST([USER], ["$USER"])
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
+
+# ------------------------------------------------------------------------------
+# configure summary
+# ------------------------------------------------------------------------------
+
+echo
+echo "-------------------------------------------"
+echo " Configuration Summary"
+echo "-------------------------------------------"
+echo
+echo " Package: .........: ${PROGRAM} ${VERSION}"
+echo " Python3: .........: ${PYTHON}"
+echo " F2py: ............: ${F2PY}"
+echo " Fcomplier: .......: ${FC}"
+echo " Samplerate: ......: ${srstatus}"
+echo " FFTW3: ...........: ${ffstatus}"
+echo " Portaudio: .......: ${pastatus}"
+echo " Website ..........: ${WEB}"
+echo " Report Bugs To ...: ${BUGS}"
+echo " Install prefix ...: ${PREFIX}"
+echo
+echo " Finished creating Makefile"
+echo
+echo " To build and install $PROGRAM, type"
+echo
+echo " make && sudo make install"
+echo
+echo
diff --git a/conv232.f90 b/conv232.f90
new file mode 100644
index 0000000..da498c1
--- /dev/null
+++ b/conv232.f90
@@ -0,0 +1,64 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: conv232.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+
+! Layland-Lushbaugh polynomials for a K=32, r=1/2 convolutional code,
+! and 8-bit parity lookup table.
+
+ data npoly1/-221228207/,npoly2/-463389625/
+ integer*1 partab(0:255)
+ data partab/ &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 1, 0, 0, 1, 0, 1, 1, 0, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 0, 1, 1, 0, 1, 0, 0, 1, &
+ 1, 0, 0, 1, 0, 1, 1, 0/
diff --git a/cs_stubs.f90 b/cs_stubs.f90
new file mode 100644
index 0000000..e932574
--- /dev/null
+++ b/cs_stubs.f90
@@ -0,0 +1,33 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: cs_stubs.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine cs_lock(csub)
+ character*(*) csub
+ return
+end subroutine cs_lock
+
+subroutine cs_unlock
+ return
+end subroutine cs_unlock
diff --git a/db.f90 b/db.f90
new file mode 100644
index 0000000..8a9acd8
--- /dev/null
+++ b/db.f90
@@ -0,0 +1,30 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: db.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+real function db(x)
+ db=-99.0
+ if(x.gt.1.259e-10) db=10.0*log10(x)
+ return
+end function db
diff --git a/decode.f90 b/decode.f90
new file mode 100644
index 0000000..9cdb2d8
--- /dev/null
+++ b/decode.f90
@@ -0,0 +1,88 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: decode.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine decode
+
+! Decode WSPR signals for one 2-minute sequence.
+
+ character*80 savefile
+ real*8 df,fpeak
+ real x(65536)
+ complex c(0:32768)
+ equivalence (x,c)
+ include 'acom1.f90'
+
+ f0b=f0a
+ if(ncal.eq.2) then
+ fac=1.e-6
+ do i=1,65536
+ x(i)=fac*iwave(i)
+ enddo
+ call xfft(x,65536)
+ df=12000.d0/65536.d0
+ smax=0.
+ do i=1,16384
+ s=real(c(i))**2 + aimag(c(i))**2
+ if(s.gt.smax) then
+ smax=s
+ fpeak=i*df
+ endif
+ enddo
+
+ call cs_lock('decode')
+ write(*,1002) fpeak
+1002 format('Measured audio frequency:',f10.2,' Hz')
+ ncal=0
+ ndecoding=0
+ call cs_unlock
+
+ go to 900
+ else
+ ncmdline=0
+! npts=114*12000
+! if(ntrminutes.eq.15) npts=890*12000
+ npts=120*12000
+ if(ntrminutes.eq.15) npts=900*12000
+ if(nsave.gt.0 .and. ndiskdat.eq.0) then
+ savefile=appdir(:nappdir)//'/save/'//thisfile
+ call wfile5(iwave,npts,12000,savefile)
+ endif
+! Sivan Toledo: changed f0 to f0b below, to correct a reporting bug
+! that resulted in f0 being reported for spots even though f0 was
+! changed after the audio was captured.
+ call mept162(thisfile,appdir,nappdir,f0b,ncmdline,iwave,npts,nbfo,ierr)
+ endif
+
+ call cs_lock('decode')
+ write(14,1100)
+1100 format('$EOF')
+ call flush(14)
+ rewind 14
+ ndecdone=1
+ ndecoding=0
+ call cs_unlock
+
+900 return
+end subroutine decode
diff --git a/decode162.f90 b/decode162.f90
new file mode 100644
index 0000000..a9e60b7
--- /dev/null
+++ b/decode162.f90
@@ -0,0 +1,162 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: decode162.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine decode162(c4,npts,message,ncycles,metric,nerr)
+
+! Decode MEPT_JT data, assuming that DT and DF have already been determined.
+
+ complex c4(npts)
+ character*22 message
+ real*8 dt,df,twopi,f0,f1,dphi0,dphi1
+ complex*16 c0,c1
+ complex*16 w0,w1,ws0,ws1
+ integer*1 symbol(162)
+ integer*1 data1(11)
+ integer amp
+ integer mettab(0:255,0:1)
+ logical first
+ data first/.true./
+ integer npr3(162)
+ data npr3/ &
+ 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, &
+ 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, &
+ 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, &
+ 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, &
+ 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, &
+ 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, &
+ 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, &
+ 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, &
+ 0,0/
+ data mettab/ &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, &
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, &
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, &
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, &
+ 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, &
+ -1, -1, -1, -2, -2, -3, -4, -4, -5, -6, &
+ -7, -7, -8, -9, -10, -11, -12, -12, -13, -14, &
+ -15, -16, -17, -17, -18, -19, -20, -21, -22, -22, &
+ -23, -24, -25, -26, -26, -27, -28, -29, -30, -30, &
+ -31, -32, -33, -33, -34, -35, -36, -36, -37, -38, &
+ -38, -39, -40, -41, -41, -42, -43, -43, -44, -45, &
+ -45, -46, -47, -47, -48, -49, -49, -50, -51, -51, &
+ -52, -53, -53, -54, -54, -55, -56, -56, -57, -57, &
+ -58, -59, -59, -60, -60, -61, -62, -62, -62, -63, &
+ -64, -64, -65, -65, -66, -67, -67, -67, -68, -69, &
+ -69, -70, -70, -71, -72, -72, -72, -72, -73, -74, &
+ -75, -75, -75, -77, -76, -76, -78, -78, -80, -81, &
+ -80, -79, -83, -82, -81, -82, -82, -83, -84, -84, &
+ -84, -87, -86, -87, -88, -89, -89, -89, -88, -87, &
+ -86, -87, -84, -84, -84, -83, -82, -82, -81, -82, &
+ -83, -79, -80, -81, -80, -78, -78, -76, -76, -77, &
+ -75, -75, -75, -74, -73, -72, -72, -72, -72, -71, &
+ -70, -70, -69, -69, -68, -67, -67, -67, -66, -65, &
+ -65, -64, -64, -63, -62, -62, -62, -61, -60, -60, &
+ -59, -59, -58, -57, -57, -56, -56, -55, -54, -54, &
+ -53, -53, -52, -51, -51, -50, -49, -49, -48, -47, &
+ -47, -46, -45, -45, -44, -43, -43, -42, -41, -41, &
+ -40, -39, -38, -38, -37, -36, -36, -35, -34, -33, &
+ -33, -32, -31, -30, -30, -29, -28, -27, -26, -26, &
+ -25, -24, -23, -22, -22, -21, -20, -19, -18, -17, &
+ -17, -16, -15, -14, -13, -12, -12, -11, -10, -9, &
+ -8, -7, -7, -6, -5, -4, -4, -3, -2, -2, &
+ -1, -1, -1, 0, 0, 1, 1, 1, 1, 2, &
+ 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, &
+ 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, &
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, &
+ 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5/
+ save
+
+ if(first) then
+ twopi=8*atan(1.d0)
+ dt=1.d0/375.d0 !Sample interval
+ df=375.d0/256.d0
+ nsym=162
+ nbits=50+31
+ amp=20 !### 32 ??? ###
+ ndelta=50
+ limit=10000
+ first=.false.
+ endif
+
+! Should amp be adjusted according to signal strength?
+! Compute soft symbols
+ k=0
+ nsps=256
+ fac2=0.001
+ w0=1.0
+ w1=1.0
+
+ do j=1,nsym
+ f0=(npr3(j)-1.5)*df
+ f1=(2+npr3(j)-1.5)*df
+ dphi0=twopi*dt*f0
+ dphi1=twopi*dt*f1
+ ws0=dcmplx(cos(dphi0),-sin(dphi0))
+ ws1=dcmplx(cos(dphi1),-sin(dphi1))
+ c0=0.
+ c1=0.
+ do i=1,nsps
+ k=k+1
+ w0=w0*ws0
+ w1=w1*ws1
+ c0=c0 + w0*c4(k)
+ c1=c1 + w1*c4(k)
+ enddo
+
+ sq0=fac2*(real(c0)**2 + aimag(c0)**2)
+ sq1=fac2*(real(c1)**2 + aimag(c1)**2)
+ rsym=amp*(sq1-sq0)
+ r=rsym+128.
+ if(r.gt.255.0) r=255.0
+ if(r.lt.0.0) r=0.0
+ n4=nint(r)
+ if(n4.gt.127) n4=n4-256
+ symbol(j)=n4
+ enddo
+
+ call inter_mept(symbol,-1) !Remove interleaving
+ call fano232(symbol,nbits,mettab,ndelta,limit, &
+ data1,ncycles,metric,nerr)
+ message=' '
+ if(nerr.ge.0) call wqdecode(data1,message,ntype2)
+
+ return
+end subroutine decode162
diff --git a/deg2grid.f90 b/deg2grid.f90
new file mode 100644
index 0000000..5e93b0f
--- /dev/null
+++ b/deg2grid.f90
@@ -0,0 +1,55 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: deg2grid.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine deg2grid(dlong0,dlat,grid)
+
+ real dlong !West longitude (deg)
+ real dlat !Latitude (deg)
+ character grid*6
+
+ dlong=dlong0
+ if(dlong.lt.-180.0) dlong=dlong+360.0
+ if(dlong.gt.180.0) dlong=dlong-360.0
+
+! Convert to units of 5 min of longitude, working east from 180 deg.
+ nlong=60.0*(180.0-dlong)/5.0
+ n1=nlong/240 !20-degree field
+ n2=(nlong-240*n1)/24 !2 degree square
+ n3=nlong-240*n1-24*n2 !5 minute subsquare
+ grid(1:1)=char(ichar('A')+n1)
+ grid(3:3)=char(ichar('0')+n2)
+ grid(5:5)=char(ichar('a')+n3)
+
+! Convert to units of 2.5 min of latitude, working north from -90 deg.
+ nlat=60.0*(dlat+90)/2.5
+ n1=nlat/240 !10-degree field
+ n2=(nlat-240*n1)/24 !1 degree square
+ n3=nlat-240*n1-24*n2 !2.5 minuts subsquare
+ grid(2:2)=char(ichar('A')+n1)
+ grid(4:4)=char(ichar('0')+n2)
+ grid(6:6)=char(ichar('a')+n3)
+
+ return
+end subroutine deg2grid
diff --git a/doc/AUTHORS b/doc/AUTHORS
new file mode 100644
index 0000000..bde63bc
--- /dev/null
+++ b/doc/AUTHORS
@@ -0,0 +1,36 @@
+AUTHOR: Joseph Taylor
+CALLSIGN: K1JT
+
+AUTHOR: Joop Stakenborg <pg4i at amsat.org>
+CALLSIGN: PG4I
+
+AUTHOR: John Nelson
+CALLSIGN: G4KLA
+
+AUTHOR:
+CALLSIGN: KE6HDU
+
+AUTHOR:
+CALLSIGN: W1BW
+
+AUTHOR:
+CALLSIGN: VA3DB
+
+AUTHOR:
+CALLSIGN: 4X6IZ
+
+Authors can be contacted on the wsjt-devel mailing list or
+through the WSJT Yahoo Group.
+
+PROJECT INFORMATION:
+-------------------
+- Project Manager: ...... Joe Taylor, K1JT, joe -AT- princeton -DOT- edu
+- Project Web Site: ..... http://physics.princeton.edu/pulsar/K1JT/
+- Mailing List: ......... wsjt-devel at lists.sourceforge.net
+- Project Source Code: .. http://sourceforge.net/projects/wsjt/
+- Yahoo Group: .......... https://groups.yahoo.com/neo/groups/wsjtgroup/info
+
+
+
+
+
diff --git a/doc/NEWS b/doc/NEWS
new file mode 100644
index 0000000..65c8cbe
--- /dev/null
+++ b/doc/NEWS
@@ -0,0 +1,4 @@
+WSPR-4.0 NEWS
+-------------
+
+
diff --git a/doc/README b/doc/README
new file mode 100644
index 0000000..2076acc
--- /dev/null
+++ b/doc/README
@@ -0,0 +1,14 @@
+WSPR-4.0
+
+WSPR (pronounced "whisper") stands for "Weak Signal
+Propagation Reporter". The program generates and decodes
+a digital soundcard mode optimized for beacon-like
+transmissions on the LF, MF, and HF bands.
+
+PROJECT INFORMATION:
+-------------------
+- Project Manager: ...... Joe Taylor, K1JT, joe -AT- princeton -DOT- edu
+- Project Web Site: ..... http://physics.princeton.edu/pulsar/K1JT/
+- Mailing List: ......... wsjt-devel at lists.sourceforge.net
+- Project Source Code: .. http://sourceforge.net/projects/wsjt/
+- Yahoo Group: .......... https://groups.yahoo.com/neo/groups/wsjtgroup/info
diff --git a/doc/examples/README b/doc/examples/README
new file mode 100644
index 0000000..3a5813a
--- /dev/null
+++ b/doc/examples/README
@@ -0,0 +1,15 @@
+WSPR FMTest EXAMPLES
+
+General Process
+---------------
+
+1. Edit wspr/gocal stations, cd ~/.wspr && ./gocal
+2. ftmave fmt.all
+3. fcal fmtave.out
+
+Output Files
+------------
+fmt.out .....: Raw data after running gocal
+fmtave.out ..: Output file after running: ./fmtave fmt.all
+fcal.out ....: Output file after running: ./fcal fmtave.out
+fcal.out ....: Input file used by WSPR > Setup > Advanced > Read A and B
diff --git a/doc/examples/fcal.out b/doc/examples/fcal.out
new file mode 100644
index 0000000..179d1cf
--- /dev/null
+++ b/doc/examples/fcal.out
@@ -0,0 +1,2 @@
+ -0.4914
+ 2.9440
diff --git a/doc/examples/fmt.all b/doc/examples/fmt.all
new file mode 100644
index 0000000..43baca0
--- /dev/null
+++ b/doc/examples/fmt.all
@@ -0,0 +1,80 @@
+02:56:18 550 1 1500 1501.085 1.085 11.9 45.5 KBOW
+02:56:21 550 1 1500 1501.085 1.085 11.8 45.6 KBOW
+02:56:23 550 1 1500 1501.085 1.085 12.7 44.5 KBOW
+02:56:26 550 1 1500 1501.084 1.084 10.8 46.4 KBOW
+02:56:29 550 1 1500 1501.085 1.085 10.9 46.1 KBOW
+02:56:32 550 1 1500 1501.086 1.086 10.8 46.3 KBOW
+02:56:34 550 1 1500 1501.084 1.084 10.6 46.7 KBOW
+02:56:37 550 1 1500 1501.085 1.085 11.7 45.3 KBOW
+02:56:40 550 1 1500 1501.084 1.084 11.0 46.2 KBOW
+02:56:43 550 1 1500 1501.084 1.084 10.4 46.7 KBOW
+02:56:49 580 1 1500 1501.353 1.353 14.1 41.4 KANA
+02:56:52 580 1 1500 1501.352 1.352 12.8 43.1 KANA
+02:56:54 580 1 1500 1501.361 1.361 12.8 42.6 KANA
+02:56:57 580 1 1500 1501.362 1.362 16.8 38.6 KANA
+02:57:00 580 1 1500 1501.354 1.354 13.4 42.3 KANA
+02:57:03 580 1 1500 1501.358 1.358 12.0 44.2 KANA
+02:57:05 580 1 1500 1501.354 1.354 13.9 42.8 KANA
+02:57:08 580 1 1500 1501.361 1.361 12.9 43.4 KANA
+02:57:11 580 1 1500 1501.362 1.362 13.8 42.8 KANA
+02:57:14 580 1 1500 1501.353 1.353 13.8 42.6 KANA
+02:57:20 950 1 1500 1501.641 1.641 8.3 50.6 KMTX
+02:57:23 950 1 1500 1501.641 1.641 10.3 48.2 KMTX
+02:57:25 950 1 1500 1501.641 1.641 9.4 49.2 KMTX
+02:57:28 950 1 1500 1501.641 1.641 3.6 55.8 KMTX
+02:57:31 950 1 1500 1501.641 1.641 6.7 52.5 KMTX
+02:57:34 950 1 1500 1501.641 1.641 9.4 49.2 KMTX
+02:57:36 950 1 1500 1501.640 1.640 10.0 48.6 KMTX
+02:57:39 950 1 1500 1501.641 1.641 10.5 48.1 KMTX
+02:57:42 950 1 1500 1501.641 1.641 8.8 50.1 KMTX
+02:57:45 950 1 1500 1501.641 1.641 10.1 48.6 KMTX
+02:57:51 2500 1 1500 1507.163 7.163 9.4 49.1 WWV
+02:57:54 2500 1 1500 1507.154 7.154 8.5 50.2 WWV
+02:57:56 2500 1 1500 1507.173 7.173 9.6 48.0 WWV
+02:57:59 2500 1 1500 1507.187 7.187 10.7 45.9 WWV
+02:58:02 2500 1 1500 1507.170 7.170 8.8 49.1 WWV
+02:58:05 2500 1 1500 1507.144 7.144 10.4 45.8 WWV
+02:58:07 2500 1 1500 1507.193 7.193 8.8 47.7 WWV
+02:58:10 2500 1 1500 1507.158 7.158 8.6 49.5 WWV
+02:58:13 2500 1 1500 1507.150 7.150 7.6 50.7 WWV
+02:58:16 2500 1 1500 1507.152 7.152 8.2 50.1 WWV
+02:58:21 5000 1 1500 1514.659 14.659 13.5 41.8 WWV
+02:58:24 5000 1 1500 1514.407 14.407 8.0 49.0 WWV
+02:58:26 5000 1 1500 1514.481 14.481 6.5 52.3 WWV
+02:58:29 5000 1 1500 1514.469 14.469 9.0 48.6 WWV
+02:58:32 5000 1 1500 1514.462 14.462 9.4 48.7 WWV
+02:58:35 5000 1 1500 1514.342 14.342 7.5 48.9 WWV
+02:58:37 5000 1 1500 1514.465 14.465 7.7 49.6 WWV
+02:58:40 5000 1 1500 1514.469 14.469 9.6 48.7 WWV
+02:58:43 5000 1 1500 1514.482 14.482 7.4 50.8 WWV
+02:58:46 5000 1 1500 1514.595 14.595 8.0 45.4 WWV
+02:58:52 7850 1 1500 1522.834 22.834 13.8 39.5 CHU
+02:58:55 7850 1 1500 1522.770 22.770 14.4 37.9 CHU
+02:58:57 7850 1 1500 1522.692 22.692 9.3 47.2 CHU
+02:59:00 7850 1 1500 1522.517 22.517 10.6 46.2 CHU
+02:59:03 7850 1 1500 1522.383 22.383 11.3 42.4 CHU
+02:59:06 7850 1 1500 1522.898 22.898 12.6 40.6 CHU
+02:59:08 7850 1 1500 1522.713 22.713 11.2 44.8 CHU
+02:59:11 7850 1 1500 1522.893 22.893 10.8 46.4 CHU
+02:59:14 7850 1 1500 1522.721 22.721 7.7 50.1 CHU
+02:59:17 7850 1 1500 1522.870 22.870 8.6 49.0 CHU
+02:59:23 10000 1 1500 1529.121 29.121 8.1 52.2 WWV
+02:59:26 10000 1 1500 1529.128 29.128 6.4 53.9 WWV
+02:59:28 10000 1 1500 1529.137 29.137 6.4 53.4 WWV
+02:59:31 10000 1 1500 1529.143 29.143 9.4 49.9 WWV
+02:59:34 10000 1 1500 1529.133 29.133 8.5 51.3 WWV
+02:59:37 10000 1 1500 1529.135 29.135 6.7 53.2 WWV
+02:59:39 10000 1 1500 1529.126 29.126 8.4 51.6 WWV
+02:59:42 10000 1 1500 1529.122 29.122 8.1 52.0 WWV
+02:59:45 10000 1 1500 1529.126 29.126 6.6 53.8 WWV
+02:59:48 10000 1 1500 1529.130 29.130 6.7 53.5 WWV
+02:59:54 15000 1 1500 1543.386 43.386 7.7 51.8 WWV
+02:59:57 15000 1 1500 1543.384 43.384 8.8 50.9 WWV
+02:59:59 15000 1 1500 1543.382 43.382 9.9 49.1 WWV
+03:00:02 15000 1 1500 1543.365 43.365 9.9 48.2 WWV
+03:00:05 15000 1 1500 1543.397 43.397 10.5 48.0 WWV
+03:00:08 15000 1 1500 1543.383 43.383 9.0 49.6 WWV
+03:00:10 15000 1 1500 1543.370 43.370 7.7 48.7 WWV
+03:00:13 15000 1 1500 1543.491 43.491 5.5 51.6 WWV
+03:00:16 15000 1 1500 1543.394 43.394 7.4 52.6 WWV
+03:00:19 15000 1 1500 1543.395 43.395 7.5 52.7 WWV
diff --git a/doc/examples/fmtave.out b/doc/examples/fmtave.out
new file mode 100644
index 0000000..b510148
--- /dev/null
+++ b/doc/examples/fmtave.out
@@ -0,0 +1,8 @@
+ 0.550 1.085 1 10 0.00 02:56:18 KBOW
+ 0.580 1.357 1 10 0.00 02:56:49 KANA
+ 0.950 1.641 1 10 0.00 02:57:20 KMTX
+ 2.500 7.164 1 10 0.02 02:57:51 WWV
+ 5.000 14.483 1 10 0.09 02:58:21 WWV
+ 7.850 22.729 1 10 0.17 02:58:52 CHU
+ 10.000 29.130 1 10 0.01 02:59:23 WWV
+ 15.000 43.395 1 10 0.04 02:59:54 WWV
diff --git a/encode232.f90 b/encode232.f90
new file mode 100644
index 0000000..4790bdd
--- /dev/null
+++ b/encode232.f90
@@ -0,0 +1,55 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: encode232.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine encode232(dat,nbytes,symbol,maxsym)
+
+! Convolutional encoder for a K=32, r=1/2 code.
+
+ integer*1 dat(nbytes) !User data, packed 8 bits per byte
+ integer*1 symbol(maxsym) !Channel symbols, one bit per byte
+ integer*1 i1
+ include 'conv232.f90'
+
+ nstate=0
+ k=0
+ do j=1,nbytes
+ do i=7,0,-1
+ i1=dat(j)
+ i4=i1
+ if (i4.lt.0) i4=i4+256
+ nstate=ior(ishft(nstate,1),iand(ishft(i4,-i),1))
+ n=iand(nstate,npoly1)
+ n=ieor(n,ishft(n,-16))
+ k=k+1
+ symbol(k)=partab(iand(ieor(n,ishft(n,-8)),255))
+ n=iand(nstate,npoly2)
+ n=ieor(n,ishft(n,-16))
+ k=k+1
+ symbol(k)=partab(iand(ieor(n,ishft(n,-8)),255))
+ enddo
+ enddo
+
+ return
+end subroutine encode232
diff --git a/fano232.f90 b/fano232.f90
new file mode 100644
index 0000000..d5c1853
--- /dev/null
+++ b/fano232.f90
@@ -0,0 +1,182 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: fano232.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine fano232(symbol,nbits,mettab,ndelta,maxcycles,dat,ncycles,metric,ierr)
+
+! Sequential decoder for K=32, r=1/2 convolutional code using
+! the Fano algorithm. Translated from C routine for same purpose
+! written by Phil Karn, KA9Q.
+
+ parameter (MAXBITS=103)
+ parameter (MAXDAT=(MAXBITS+7)/8)
+ integer*1 symbol(0:2*MAXBITS-1)
+ integer*1 dat(MAXDAT) !Decoded user data, 8 bits per byte
+ integer mettab(0:255,0:1) !Metric table
+
+! These were the "node" structure in Karn's C code:
+ integer nstate(0:MAXBITS-1) !Encoder state of next node
+ integer gamma(0:MAXBITS-1) !Cumulative metric to this node
+ integer metrics(0:3,0:MAXBITS-1) !Metrics indexed by all possible Tx syms
+ integer tm(0:1,0:MAXBITS-1) !Sorted metrics for current hypotheses
+ integer ii(0:MAXBITS-1) !Current branch being tested
+
+ logical noback
+ include 'conv232.f90'
+
+ ntail=nbits-31
+
+! Compute all possible branch metrics for each symbol pair.
+! This is the only place we actually look at the raw input symbols
+ i4a=0
+ i4b=0
+ do np=0,nbits-1
+ j=2*np
+ i4a=symbol(j)
+ i4b=symbol(j+1)
+ if (i4a.lt.0) i4a=i4a+256
+ if (i4b.lt.0) i4b=i4b+256
+ metrics(0,np) = mettab(i4a,0) + mettab(i4b,0)
+ metrics(1,np) = mettab(i4a,0) + mettab(i4b,1)
+ metrics(2,np) = mettab(i4a,1) + mettab(i4b,0)
+ metrics(3,np) = mettab(i4a,1) + mettab(i4b,1)
+ enddo
+
+ np=0
+ nstate(np)=0
+
+! Compute and sort branch metrics from the root node
+ n=iand(nstate(np),npoly1)
+ n=ieor(n,ishft(n,-16))
+ lsym=partab(iand(ieor(n,ishft(n,-8)),255))
+ n=iand(nstate(np),npoly2)
+ n=ieor(n,ishft(n,-16))
+ lsym=lsym+lsym+partab(iand(ieor(n,ishft(n,-8)),255))
+ m0=metrics(lsym,np)
+ m1=metrics(ieor(3,lsym),np)
+ if(m0.gt.m1) then
+ tm(0,np)=m0 !0-branch has better metric
+ tm(1,np)=m1
+ else
+ tm(0,np)=m1 !1-branch is better
+ tm(1,np)=m0
+ nstate(np)=nstate(np) + 1 !Set low bit
+ endif
+
+! Start with best branch
+ ii(np)=0
+ gamma(np)=0
+ nt=0
+
+! Start the Fano decoder
+ do i=1,nbits*maxcycles
+! Look forward
+ ngamma=gamma(np) + tm(ii(np),np)
+ if(ngamma.ge.nt) then
+
+! Node is acceptable. If first time visiting this node, tighten threshold:
+ if(gamma(np).lt.(nt+ndelta)) nt=nt + &
+ ndelta * ((ngamma-nt)/ndelta)
+
+! Move forward
+ gamma(np+1)=ngamma
+ nstate(np+1)=ishft(nstate(np),1)
+ np=np+1
+ if(np.eq.nbits-1) go to 100 !We're done!
+
+ n=iand(nstate(np),npoly1)
+ n=ieor(n,ishft(n,-16))
+ lsym=partab(iand(ieor(n,ishft(n,-8)),255))
+ n=iand(nstate(np),npoly2)
+ n=ieor(n,ishft(n,-16))
+ lsym=lsym+lsym+partab(iand(ieor(n,ishft(n,-8)),255))
+
+ if(np.ge.ntail) then
+ tm(0,np)=metrics(lsym,np) !We're in the tail, all zeros
+ else
+ m0=metrics(lsym,np)
+ m1=metrics(ieor(3,lsym),np)
+ if(m0.gt.m1) then
+ tm(0,np)=m0 !0-branch has better metric
+ tm(1,np)=m1
+ else
+ tm(0,np)=m1 !1-branch is better
+ tm(1,np)=m0
+ nstate(np)=nstate(np) + 1 !Set low bit
+ endif
+ endif
+
+ ii(np)=0 !Start with best branch
+ go to 99
+ endif
+
+! Threshold violated, can't go forward
+10 noback=.false.
+ if(np.eq.0) noback=.true.
+ if(np.gt.0) then
+ if(gamma(np-1).lt.nt) noback=.true.
+ endif
+
+ if(noback) then
+! Can't back up, either. Relax threshold and look forward again
+! to a better branch.
+ nt=nt-ndelta
+ if(ii(np).ne.0) then
+ ii(np)=0
+ nstate(np)=ieor(nstate(np),1)
+ endif
+ go to 99
+ endif
+
+! Back up
+ np=np-1
+ if(np.lt.ntail .and. ii(np).ne.1) then
+! Search the next best branch
+ ii(np)=ii(np)+1
+ nstate(np)=ieor(nstate(np),1)
+ go to 99
+ endif
+ go to 10
+99 continue
+ enddo
+ i=nbits*maxcycles
+
+100 metric=gamma(np) !Final path metric
+
+! Copy decoded data to user's buffer
+ nbytes=(nbits+7)/8
+ np=7
+ do j=1,nbytes-1
+ i4a=nstate(np)
+ dat(j)=i4a
+ np=np+8
+ enddo
+ dat(nbytes)=0
+
+ ncycles=i+1
+ ierr=0
+ if(i.ge.maxcycles*nbits) ierr=-1
+
+ return
+end subroutine fano232
diff --git a/fcal.f90 b/fcal.f90
new file mode 100644
index 0000000..1c6b010
--- /dev/null
+++ b/fcal.f90
@@ -0,0 +1,143 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: fcal.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program fcal
+
+ parameter(NZ=1000)
+ implicit real*8 (a-h,o-z)
+ real*8 fd(NZ),deltaf(NZ),r(NZ)
+ character infile*50
+ character line*80
+ character cutc*8
+ character*6 callsign(NZ)
+
+ nargs=iargc()
+ if(nargs.ne.1) then
+ print*,'Usage: fcal <infile>'
+ print*,'Example: fcal fmtave.out'
+ go to 999
+ endif
+ call getarg(1,infile)
+
+ open(10,file=infile,status='old',err=997)
+ open(12,file='fcal.out',status='unknown')
+ open(13,file='fcal.plt',status='unknown')
+
+ i=0
+ do j=1,9999
+ read(10,1000,end=10) line
+1000 format(a80)
+ i0=index(line,' 0 ')
+ i1=index(line,' 1 ')
+ if(i0.le.0 .and. i1.le.0) then
+ read(line,*,err=5) f,df
+ ncal=1
+ i=i+1
+ fd(i)=f
+ deltaf(i)=df
+ callsign(i)=' '
+ else if(i1.gt.0) then
+ i=i+1
+ read(line,*,err=5) f,df,ncal,nn,rr,cutc,callsign(i)
+ fd(i)=f
+ deltaf(i)=df
+ r(i)=0.d0
+ endif
+5 continue
+ enddo
+
+10 iz=i
+ if(iz.lt.2) go to 998
+ call fit(fd,deltaf,r,iz,a,b,sigmaa,sigmab,rms)
+
+ write(*,1002)
+1002 format(' Freq DF Meas Freq Resid Call'/ &
+ ' (MHz) (Hz) (MHz) (Hz)'/ &
+ '------------------------------------------------')
+ do i=1,iz
+ fm=fd(i) + 1.d-6*deltaf(i)
+ calfac=1.d0 + 1.d-6*deltaf(i)/fd(i)
+ write(*,1010) fd(i),deltaf(i),fm,r(i),callsign(i)
+ write(13,1010) fd(i),deltaf(i),fm,r(i),callsign(i)
+1010 format(f8.3,f9.3,f14.9,f9.3,2x,a6)
+ enddo
+ calfac=1.d0 + 1.d-6*b
+ err=1.d-6*sigmab
+
+ if(iz.ge.3) then
+ write(*,1100) a,b,rms
+1100 format(/'A:',f8.2,' Hz B:',f9.4,' ppm StdDev:',f7.3,' Hz')
+ if(iz.gt.2) write(*,1110) sigmaa,sigmab
+1110 format('err:',f6.2,9x,f9.4,23x,f13.9)
+ else
+ write(*,1120) a,b
+1120 format(/'A:',f8.2,' Hz B:',f9.4)
+ endif
+
+ write(12,1130) a,b
+1130 format(f10.4)
+
+ go to 999
+
+997 print*,'Cannot open input file: ',infile
+ go to 999
+998 print*,'Input file must contain at least 2 valid measurement pairs'
+
+999 end program fcal
+
+subroutine fit(x,y,r,iz,a,b,sigmaa,sigmab,rms)
+ implicit real*8 (a-h,o-z)
+ real*8 x(iz),y(iz),r(iz)
+
+ sx=0.d0
+ sy=0.d0
+ sxy=0.d0
+ sx2=0.d0
+ do i=1,iz
+ sx=sx + x(i)
+ sy=sy + y(i)
+ sxy=sxy + x(i)*y(i)
+ sx2=sx2 + x(i)*x(i)
+ enddo
+ delta=iz*sx2 - sx*sx
+ a=(sx2*sy - sx*sxy)/delta
+ b=(iz*sxy - sx*sy)/delta
+
+ sq=0.d0
+ do i=1,iz
+ r(i)=y(i) - (a + b*x(i))
+ sq=sq + r(i)**2
+ enddo
+ rms=0.
+ sigmaa=0.
+ sigmab=0.
+ if(iz.ge.3) then
+ rms=sqrt(sq/(iz-2))
+ sigmaa=sqrt(rms*rms*sx2/delta)
+ sigmab=sqrt(iz*rms*rms/delta)
+ endif
+
+ return
+end subroutine fit
diff --git a/fchisq.f90 b/fchisq.f90
new file mode 100644
index 0000000..cf61b32
--- /dev/null
+++ b/fchisq.f90
@@ -0,0 +1,122 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: fchisq.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+real function fchisq(cx,npts,fsample,a,lag1,lag2,ccfmax,dtmax)
+
+ parameter (NMAX=120*375)
+ complex cx(npts)
+ real a(5)
+ complex*16 w1,ws1
+ complex*16 w2,ws2
+ complex*16 w3,ws3
+ complex*16 w4,ws4
+ complex*16 cs1(0:NMAX)
+ complex*16 cs2(0:NMAX)
+ complex*16 cs3(0:NMAX)
+ complex*16 cs4(0:NMAX)
+ complex z1,z2,z3,z4
+ real*8 twopi,baud,p2
+ real ss(5624)
+ save
+
+ twopi=8.d0*atan(1.d0)
+ baud=12000.d0/8192
+
+! Mix and integrate four channels
+ cs1(0)=0.
+ cs2(0)=0.
+ cs3(0)=0.
+ cs4(0)=0.
+ w1=1.0
+ w2=1.0
+ w3=1.0
+ w4=1.0
+ x0=0.5*(npts+1) !Middle sample
+ s=2.0/npts
+ dt=1.0/fsample
+ do i=1,npts
+ x=s*(i-x0) !x runs from -1 to +1
+ if(mod(i,100).eq.1) then
+ p2=1.5*x*x - 0.5
+! p3=2.5*(x**3) - 1.5*x
+! p4=4.375*(x**4) - 3.75*(x**2) + 0.375
+ dphi1=twopi*dt*(a(1) + x*a(2) + p2*a(3) + 1.5*baud)
+ dphi2=twopi*dt*(a(1) + x*a(2) + p2*a(3) + 0.5*baud)
+ dphi3=twopi*dt*(a(1) + x*a(2) + p2*a(3) - 0.5*baud)
+ dphi4=twopi*dt*(a(1) + x*a(2) + p2*a(3) - 1.5*baud)
+ ws1=cmplx(cos(dphi1),sin(dphi1))
+ ws2=cmplx(cos(dphi2),sin(dphi2))
+ ws3=cmplx(cos(dphi3),sin(dphi3))
+ ws4=cmplx(cos(dphi4),sin(dphi4))
+ endif
+ w1=w1*ws1
+ w2=w2*ws2
+ w3=w3*ws3
+ w4=w4*ws4
+ cs1(i)=cs1(i-1) + w1*cx(i)
+ cs2(i)=cs2(i-1) + w2*cx(i)
+ cs3(i)=cs3(i-1) + w3*cx(i)
+ cs4(i)=cs4(i-1) + w4*cx(i)
+ enddo
+
+! Compute full-symbol powers at 1/16-symbol steps.
+ nsps=nint(fsample/baud) !Samples per symbol
+ ndiv=16 !Steps per symbol
+ nout=ndiv*npts/nsps !Total steps
+ dtstep=1.0/(ndiv*baud) !Time per output step
+ fac=1.e-5
+
+ ss=0.
+ do i=1,nout
+ j=i*nsps/ndiv
+ k=j - nsps
+ ss(i)=0.
+ if(k.ge.1) then
+ z1=cs1(j)-cs1(k)
+ z2=cs2(j)-cs2(k)
+ z3=cs3(j)-cs3(k)
+ z4=cs4(j)-cs4(k)
+
+ p1=real(z1)**2 + aimag(z1)**2
+ p2=real(z2)**2 + aimag(z2)**2
+ p3=real(z3)**2 + aimag(z3)**2
+ p4=real(z4)**2 + aimag(z4)**2
+! ss(i)=fac*(max(p2,p4) - max(p1,p3))
+ ss(i)=fac*((p2+p4) - (p1+p3))
+ endif
+ enddo
+
+ ccfmax=0.
+ call ccf2(ss,nout,lag1,lag2,ccf,lagpk)
+ if(ccf.gt.ccfmax) then
+ ccfmax=ccf
+ dtmax=lagpk*dtstep
+ endif
+
+! Reverse sign (and offset!) because we will be minimizing fchisq
+ fchisq=-ccfmax + 100.0
+
+ return
+end function fchisq
diff --git a/ffa.f90 b/ffa.f90
new file mode 100644
index 0000000..2ee6f82
--- /dev/null
+++ b/ffa.f90
@@ -0,0 +1,125 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: ffa.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+! Copyright (C) 1995 by Peter Mueller, MPIfR. peter at mpifr-bonn.mpg.de
+!
+! Permission to use, copy, modify, and distribute this software and its
+! documentation for any purpose and without fee is hereby granted, provided
+! that the above copyright notice appear in all copies and that both that
+! copyright notice and this permission notice appear in supporting
+! documentation. This software is provided "as is" without express or
+! implied warranty.
+!
+! dat(1:npts) Raw data
+! ip Period is between ip and ip+1
+! prof(1:ip) Folded profile
+! pmax Best period
+! xmax Peak of profile
+!
+!-------------------------------------------------------------------------------
+subroutine ffa(dat,ndim,npts,ip,prof,pmax,pk,ipk)
+
+ implicit real*4 (a-h,o-z)
+ real puls(NDIM)
+ real prof(*)
+ real*8 p,p1,p2,p3,p4,p5,ps,dp,pmax
+ real*4 x,xp,xmax,xmax0,xmax1
+ real dat(NDIM)
+
+ xmax=-1.e30
+ nmax=ip*2**int(log(float(npts)/float(ip))/log(2.0) + 0.95)
+ if(nmax .gt. NDIM) nmax=nmax/2
+
+ lmax=nmax/(ip +1)
+ kp=nint(log(lmax+1.0)/log(2.0))
+ dp=dfloat(ip+1)/dfloat(nmax)
+ p1=1.d0/dfloat(ip)
+ p2=p1*p1
+ p3=p2*p1
+ p4=p3*p1
+ p5=p4*p1
+ ps=p1+p2+p3+p4+p5
+
+ do n=0, lmax-1
+ x=dp*n
+ xp=(((((p5*x + p4)*x + p3)*x + p2)* x - ps)*x + p1)*x
+ p=ip+x-xp
+ do nn=0,kp-1
+ if(mod(n,2**nn) .eq. 0) kp1=kp-nn
+ enddo
+ do k=kp1,kp
+ np=2**(kp-k)
+ joff=nmax - nmax/2**(k-1)
+ ioff=nmax - nmax/2**max(k-2,0)
+ ish=mod((n+np) / np / 2, ip)
+ do i=0,np-1
+ iip=i*ip
+ i0=iip+joff
+ i1=2*iip + ioff
+ i2=i1+ip
+ do j=1,ip
+ j1=j+ish
+ if(j1.gt.ip) j1=j1-ip
+ ind=j+i0
+ jnd=j+i1
+ knd=j1+i2
+ if(kp1.eq.1 .and. k.eq.1) then
+ puls(ind)=dat(jnd) + dat(knd)
+ else
+ puls(ind)=puls(jnd) + puls(knd)
+ endif
+ enddo
+ enddo
+ enddo
+
+ xmax1=xmax
+ xmax0=-1.e30
+ do j=1,ip
+ pp=puls(j+joff)
+ pp=abs(pp) !JHT (for hftoa)
+ xmax0=max(pp,xmax0)
+ enddo
+ xmax=max(xmax0,xmax)
+
+! write(22,3001) p,xmax0/lmax
+!3001 format(2f12.6)
+
+ if(xmax.gt.xmax1) then
+ pmax=p
+ do j=1,ip
+ prof(j)=puls(j+joff)/lmax
+ enddo
+ endif
+ enddo
+
+ pk=0.
+ do i=1,ip
+ if(abs(prof(i)).gt.abs(pk)) then
+ pk=prof(i)
+ ipk=i
+ endif
+ enddo
+
+ return
+end subroutine ffa
diff --git a/fftw3.f90 b/fftw3.f90
new file mode 100644
index 0000000..efa55d7
--- /dev/null
+++ b/fftw3.f90
@@ -0,0 +1,89 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: fftw3.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+ INTEGER FFTW_R2HC
+ PARAMETER (FFTW_R2HC=0)
+ INTEGER FFTW_HC2R
+ PARAMETER (FFTW_HC2R=1)
+ INTEGER FFTW_DHT
+ PARAMETER (FFTW_DHT=2)
+ INTEGER FFTW_REDFT00
+ PARAMETER (FFTW_REDFT00=3)
+ INTEGER FFTW_REDFT01
+ PARAMETER (FFTW_REDFT01=4)
+ INTEGER FFTW_REDFT10
+ PARAMETER (FFTW_REDFT10=5)
+ INTEGER FFTW_REDFT11
+ PARAMETER (FFTW_REDFT11=6)
+ INTEGER FFTW_RODFT00
+ PARAMETER (FFTW_RODFT00=7)
+ INTEGER FFTW_RODFT01
+ PARAMETER (FFTW_RODFT01=8)
+ INTEGER FFTW_RODFT10
+ PARAMETER (FFTW_RODFT10=9)
+ INTEGER FFTW_RODFT11
+ PARAMETER (FFTW_RODFT11=10)
+ INTEGER FFTW_FORWARD
+ PARAMETER (FFTW_FORWARD=-1)
+ INTEGER FFTW_BACKWARD
+ PARAMETER (FFTW_BACKWARD=+1)
+ INTEGER FFTW_MEASURE
+ PARAMETER (FFTW_MEASURE=0)
+ INTEGER FFTW_DESTROY_INPUT
+ PARAMETER (FFTW_DESTROY_INPUT=1)
+ INTEGER FFTW_UNALIGNED
+ PARAMETER (FFTW_UNALIGNED=2)
+ INTEGER FFTW_CONSERVE_MEMORY
+ PARAMETER (FFTW_CONSERVE_MEMORY=4)
+ INTEGER FFTW_EXHAUSTIVE
+ PARAMETER (FFTW_EXHAUSTIVE=8)
+ INTEGER FFTW_PRESERVE_INPUT
+ PARAMETER (FFTW_PRESERVE_INPUT=16)
+ INTEGER FFTW_PATIENT
+ PARAMETER (FFTW_PATIENT=32)
+ INTEGER FFTW_ESTIMATE
+ PARAMETER (FFTW_ESTIMATE=64)
+ INTEGER FFTW_ESTIMATE_PATIENT
+ PARAMETER (FFTW_ESTIMATE_PATIENT=128)
+ INTEGER FFTW_BELIEVE_PCOST
+ PARAMETER (FFTW_BELIEVE_PCOST=256)
+ INTEGER FFTW_DFT_R2HC_ICKY
+ PARAMETER (FFTW_DFT_R2HC_ICKY=512)
+ INTEGER FFTW_NONTHREADED_ICKY
+ PARAMETER (FFTW_NONTHREADED_ICKY=1024)
+ INTEGER FFTW_NO_BUFFERING
+ PARAMETER (FFTW_NO_BUFFERING=2048)
+ INTEGER FFTW_NO_INDIRECT_OP
+ PARAMETER (FFTW_NO_INDIRECT_OP=4096)
+ INTEGER FFTW_ALLOW_LARGE_GENERIC
+ PARAMETER (FFTW_ALLOW_LARGE_GENERIC=8192)
+ INTEGER FFTW_NO_RANK_SPLITS
+ PARAMETER (FFTW_NO_RANK_SPLITS=16384)
+ INTEGER FFTW_NO_VRANK_SPLITS
+ PARAMETER (FFTW_NO_VRANK_SPLITS=32768)
+ INTEGER FFTW_NO_VRECURSE
+ PARAMETER (FFTW_NO_VRECURSE=65536)
+ INTEGER FFTW_NO_SIMD
+ PARAMETER (FFTW_NO_SIMD=131072)
diff --git a/fil1.f90 b/fil1.f90
new file mode 100644
index 0000000..352478a
--- /dev/null
+++ b/fil1.f90
@@ -0,0 +1,72 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: fil1.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine fil1(id1,n1,id2,n2)
+
+! FIR lowpass filter designed using ScopeFIR
+
+! fsample = 48000 Hz
+! Ntaps = 37
+! fc = 3000 Hz
+! fstop = 6000 Hz
+! Ripple = 1 dB
+! Stop Atten = 60 dB
+! fout = 12000 Hz
+
+ parameter (NTAPS=37)
+ parameter (NH=NTAPS/2)
+ parameter (NDOWN=4) !Downsample ratio
+ integer*2 id1(n1)
+ integer*2 id2(*)
+
+! Filter coefficients:
+ real a(-NH:NH)
+ data a/ &
+ 0.001377395235, 0.002852158900, 0.004767882543, 0.006240206517, &
+ 0.006191755970, 0.003553573051,-0.002243564850,-0.010770446408, &
+ -0.020288158399,-0.027822309390,-0.029710933359,-0.022547471263, &
+ -0.004298056801, 0.024769757851, 0.061669077060, 0.101014185634, &
+ 0.136070596894, 0.160295785231, 0.168947734090, 0.160295785231, &
+ 0.136070596894, 0.101014185634, 0.061669077060, 0.024769757851, &
+ -0.004298056801,-0.022547471263,-0.029710933359,-0.027822309390, &
+ -0.020288158399,-0.010770446408,-0.002243564850, 0.003553573051, &
+ 0.006191755970, 0.006240206517, 0.004767882543, 0.002852158900, &
+ 0.001377395235/
+
+ n2=(n1-NTAPS+NDOWN)/NDOWN
+ k0=NH-NDOWN+1
+
+! Loop over all output samples
+ do i=1,n2
+ s=0.
+ k=k0 + NDOWN*i
+ do j=-NH,NH
+ s=s + id1(j+k)*a(j)
+ enddo
+ id2(i)=nint(s)
+ enddo
+
+ return
+end subroutine fil1
diff --git a/flat3.f90 b/flat3.f90
new file mode 100644
index 0000000..132d0c0
--- /dev/null
+++ b/flat3.f90
@@ -0,0 +1,56 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: flat3.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine flat3(ss0,n,nsum)
+
+ parameter (NZ=256)
+ real ss0(NZ)
+ real ss(NZ)
+ real ref(NZ)
+ real tmp(NZ)
+
+ ss(1:128)=ss0(129:256)
+ ss(129:256)=ss0(1:128)
+! call move(ss0,ss(129),128)
+! call move(ss0(129),ss,128)
+
+ nsmo=20
+ base=50*(float(nsum)**1.5)
+ ia=nsmo+1
+ ib=n-nsmo-1
+ do i=ia,ib
+ call pctile(ss(i-nsmo),tmp,2*nsmo+1,35,ref(i))
+ enddo
+ do i=ia,ib
+ ss(i)=base*ss(i)/ref(i)
+ enddo
+
+ ss0(1:128)=ss(129:256)
+ ss0(129:256)=ss(1:128)
+! call move(ss(129),ss0,128)
+! call move(ss,ss0(129),128)
+
+ return
+end subroutine flat3
diff --git a/fmeasure.f90 b/fmeasure.f90
new file mode 100644
index 0000000..d26f40a
--- /dev/null
+++ b/fmeasure.f90
@@ -0,0 +1,76 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: fmeasure.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program fmeasure
+
+ parameter(NZ=1000)
+ implicit real*8 (a-h,o-z)
+ real*8 fd(NZ),deltaf(NZ),r(NZ)
+ character infile*50
+ character line*80
+
+ nargs=iargc()
+ if(nargs.ne.1) then
+ print*,'Usage: fmeasure <infile>'
+ print*,'Example: fmeasure fmtave.out'
+ go to 999
+ endif
+ call getarg(1,infile)
+
+ open(10,file=infile,status='old',err=997)
+ open(11,file='fcal.out',status='old',err=998)
+ open(12,file='fmeasure.out',status='unknown')
+
+ read(11,*) a,b
+
+ write(*,1000)
+ write(12,1000)
+1000 format(' Freq DF A+B*f Corrected Offset'/ &
+ ' (MHz) (Hz) (Hz) (MHz) (Hz)'/ &
+ '-----------------------------------------------')
+ i=0
+ do j=1,9999
+ read(10,1010,end=999) line
+1010 format(a80)
+ i0=index(line,' 0 ')
+ if(i0.gt.0) then
+ read(line,*,err=5) f,df
+ dial_error=a + b*f
+ fcor=f + 1.d-6*df - 1.d-6*dial_error
+ offset_hz=1.d6*(fcor-f)
+ write(*,1020) f,df,dial_error,fcor,offset_hz
+ write(12,1020) f,df,dial_error,fcor,offset_hz
+1020 format(3f8.3,f15.9,f8.2)
+ endif
+5 continue
+ enddo
+
+ go to 999
+
+997 print*,'Cannot open input file: ',infile
+ go to 999
+998 print*,'Cannot open fcal.out'
+
+999 end program fmeasure
diff --git a/fmtave.f90 b/fmtave.f90
new file mode 100644
index 0000000..fc194eb
--- /dev/null
+++ b/fmtave.f90
@@ -0,0 +1,89 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: fmtave.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program fmtave
+
+ implicit real*8 (a-h,o-z)
+ character infile*80
+ character*8 cutc,cutc1
+ character*6 callsign,callsign0
+
+ nargs=iargc()
+ if(nargs.ne.1) then
+ print*,'Usage: fmtave <infile>'
+ print*,'Example: fmtave fmt.all'
+ go to 999
+ endif
+ call getarg(1,infile)
+
+ open(10,file=infile,status='old')
+ open(12,file='fmtave.out',status='unknown')
+
+ write(*,1000)
+1000 format(' Freq DF CAL N rms UTC Call'/ &
+ ' (kHz) (Hz) ? (Hz)'/ &
+ '----------------------------------------------------')
+ nkhz0=0
+ sum=0.d0
+ sumsq=0.d0
+ n=0
+ do i=1,99999
+ read(10,*,end=10) cutc,nkHz,ncal,noffset,faudio,df,dblevel,snr,callsign
+ if((nkHz.ne.nkHz0 .or. callsign.ne.callsign0) .and. i.ne.1) then
+ ave=sum/n
+ rms=0.d0
+ if(n.gt.1) then
+ rms=sqrt(abs(sumsq - sum*sum/n)/(n-1.d0))
+ endif
+ fMHz=0.001d0*nkHz0
+ write(*,1010) fMHz,ave,ncal0,n,rms,cutc1,callsign0
+ write(12,1010) fMHz,ave,ncal0,n,rms,cutc1,callsign0
+1010 format(f8.3,f9.3,i4,i5,f8.2,2x,a8,2x,a6)
+ sum=0.d0
+ sumsq=0.d0
+ n=0
+ endif
+ dial_error=faudio-noffset
+ sum=sum + dial_error
+ sumsq=sumsq + dial_error**2
+ n=n+1
+ if(n.eq.1) then
+ cutc1=cutc
+ ncal0=ncal
+ endif
+ nkHz0=nkHz
+ callsign0=callsign
+ enddo
+
+10 ave=sum/n
+ rms=0.d0
+ if(n.gt.0) then
+ rms=sqrt((sumsq - sum*sum/n)/(n-1.d0))
+ endif
+ fMHz=0.001d0*nkHz
+ write(*,1010) fMHz,ave,ncal,n,rms,cutc1,callsign0
+ write(12,1010) fMHz,ave,ncal,n,rms,cutc1,callsign0
+
+999 end program fmtave
diff --git a/fmtest.f90 b/fmtest.f90
new file mode 100644
index 0000000..83c40e1
--- /dev/null
+++ b/fmtest.f90
@@ -0,0 +1,172 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: fmtest.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program fmtest
+
+! Conduct measurements for ARRL Frequency Measuring Test, etc.
+
+ parameter (NMAX=600*48000) !Max length of 48 kHz waveform
+ parameter (NMAX2=NMAX/4) !Max length at 12 kHz
+ parameter (NFFT=65536)
+ parameter (NH=NFFT/2)
+ parameter (NQ=NFFT/4)
+
+ integer*2 kwave(NMAX) !Raw data samples at 48 kHz
+ integer*2 iwave(NMAX2) !Downsampled data, 12 kHz
+ character arg*12 !Command-line arg
+ character callsign*6
+ character cmnd*120 !Command to set rig frequency
+ character cflag*1
+ real x(NFFT) !Real data for FFT
+ real w(NFFT) !Window function
+ complex c(0:NH-1) !Complex FFT result
+ real s(NQ) !Power spectrum
+ integer time,soundin !External functions
+ equivalence (x,c)
+
+ nargs=iargc()
+ if(nargs.ne.6) then
+ print*,'Usage: fmtest <kHz> <0|1> <offset> <range> <tsec> <call>'
+ print*,'Example: fmtest 10000 1 1500 100 30 WWV'
+ go to 999
+ endif
+ call getarg(1,arg)
+ read(arg,*) nkhz !Nominal frequency to be measured (kHz)
+ call getarg(2,arg)
+ read(arg,*) ncal !1=CAL, 0=to be measured
+ call getarg(3,arg)
+ read(arg,*) noffset !Offset (Hz)
+ call getarg(4,arg)
+ read(arg,*) nrange !Search range (Hz)
+ call getarg(5,arg)
+ read(arg,*) ntsec !Length of measurement (s)
+ call getarg(6,callsign)
+
+ open(10,file='fmt.ini',status='old',err=910)
+ read(10,'(a120)') cmnd !Get rigctl command to set frequency
+ read(10,*) ndevin
+ close(10)
+ open(12,file='fmt.out',status='unknown')
+ open(13,file='fmt.all',status='unknown',position='append')
+
+ nHz=1000*nkhz - noffset
+ i1=index(cmnd,' F ')
+ write(cmnd(i1+2:),*) nHz !Insert the desired frequency
+ iret=system(cmnd)
+ if(iret.ne.0) then
+ print*,'Error executing rigctl command to set frequency:'
+ print*,cmnd
+ go to 999
+ endif
+
+ df=12000.d0/NFFT
+ do i=1,NFFT
+ w(i)=sin(i*3.14159/NFFT)
+ enddo
+
+ write(*,1000)
+1000 format( &
+ ' UTC Freq CAL Offset fMeas DF Level S/N Call'/ &
+ ' (kHz) ? (Hz) (Hz) (Hz) (dB) (dB) '/ &
+ '------------------------------------------------------------------')
+
+ call soundinit !Initialize Portaudio
+
+ npts=ntsec*48000
+ iqmode=0
+ nsec0=mod(time(),86400)
+ ierr=soundin(ndevin,48000,kwave,npts,iqmode) !Get audio data, 48 kHz rate
+ if(ierr.ne.0) then
+ print*,'Error in soundin',ierr
+ stop
+ endif
+ call fil1(kwave,npts,iwave,n2) !Filter and downsample to 12 kHz
+
+ nrpt=n2/NH - 1
+ fac=1.0/float(NFFT)**2
+ ia=(noffset-nrange)/df
+ ib=(noffset+nrange)/df
+
+ do irpt=0,nrpt
+ k=irpt*NH
+ t0=nsec0 + k/12000.0
+ do i=1,NFFT
+ k=k+1
+ if(k.gt.NMAX2) go to 999
+ x(i)=w(i)*iwave(k)
+ enddo
+
+ call four2a(x,NFFT,1,-1,0) !Compute spectrum
+ do i=1,nq
+ s(i)=fac * (real(c(i))**2 + aimag(c(i))**2)
+ enddo
+
+ smax=0.
+ ipk=ib !Silence compiler warning
+ do i=ia,ib !Find fpeak
+ if(s(i).gt.smax) then
+ smax=s(i)
+ ipk=i
+ endif
+ enddo
+
+ call peakup(s(ipk-1),s(ipk),s(ipk+1),dx)
+ fpeak=df * (ipk+dx)
+
+ sum=0.
+ nsum=0
+ do i=ia,ib
+ if(abs(i-ipk).gt.10) then
+ sum=sum+s(i)
+ nsum=nsum+1
+ endif
+ enddo
+ ave=sum/nsum
+
+ n=nint(mod(t0,86400.0))
+ nhr=n/3600
+ nmin=mod(n/60,60)
+ nsec=mod(n,60)
+ pave=db(ave) + 8.0
+ snr=db(smax/ave)
+ ferr=fpeak-noffset
+ cflag=' '
+ if(snr.lt.20.0) cflag='*'
+ write(*,1100) nhr,nmin,nsec,nkhz,ncal,noffset,fpeak,ferr,pave, &
+ snr,callsign,cflag
+ write(12,1100) nhr,nmin,nsec,nkhz,ncal,noffset,fpeak,ferr,pave, &
+ snr,callsign,cflag
+ write(13,1100) nhr,nmin,nsec,nkhz,ncal,noffset,fpeak,ferr,pave, &
+ snr,callsign,cflag
+1100 format(i2.2,':',i2.2,':',i2.2,i7,i3,i6,2f10.3,2f7.1,2x,a6,2x,a1)
+ call flush(12)
+ call flush(13)
+ enddo
+ go to 999
+
+910 print*,'Cannot open file: fmt.ini'
+
+999 end program fmtest
+
diff --git a/fmtiq.f90 b/fmtiq.f90
new file mode 100644
index 0000000..fa03c61
--- /dev/null
+++ b/fmtiq.f90
@@ -0,0 +1,147 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: fmtiq.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program fmtiq
+
+! Program for ARRL Frequency Measuring Test, etc.
+
+ parameter (NFFT=512*1024) !Length of complex waveform
+! parameter (NFFT=128*1024) !Length of complex waveform
+ integer*2 iwave(2,NFFT)
+ character arg*12,cmnd*120
+ complex c(0:NFFT-1)
+ real s1(NFFT)
+ real*8 s,sq
+ integer time
+ integer soundin
+ equivalence (x,c)
+
+ nargs=iargc()
+ if(nargs.lt.2) then
+ print*,'Usage: fmtiq <kHz> <offset> <nrpt>'
+ go to 999
+ endif
+ call getarg(1,arg)
+ read(arg,*) nkhz
+ call getarg(2,arg)
+ read(arg,*) noffset
+ nrpt=9999999
+ if(nargs.ge.3) then
+ call getarg(3,arg)
+ read(arg,*) nrpt
+ endif
+
+ cmnd='rigctl -m 214 -r COM1 -s 4800 -C data_bits=8 -C stop_bits=2 -C serial_handshake=Hardware F 3592607'
+
+ nHz=1000*nkhz - noffset
+ write(cmnd(92:),*) nHz
+ iret=system(cmnd)
+ if(iret.ne.0) then
+ print*,'Error setting TS-2000 frequency:'
+ print*,cmnd
+ go to 999
+ endif
+
+ open(13,file='fmt.out',status='unknown',position='append')
+ open(14,file='fmt.spec',status='unknown',position='append')
+ open(15,file='fmt.raw',status='unknown',position='append', &
+ form='unformatted')
+
+ call soundinit
+ ndevin=0
+ iqmode=1
+ df=48000.0/NFFT
+ do iter=1,nrpt
+ cmnd='rigctl -m 2509 -r USB F 3592607'
+ nHz=1000*nkhz - noffset
+ write(cmnd(25:),*) nHz + iter - 1
+ iret=system(cmnd)
+ if(iret.ne.0) then
+ print*,'Error setting SoftRock frequency:'
+ print*,cmnd
+ go to 999
+ endif
+
+ nsec=time()
+ ierr=soundin(ndevin,48000,iwave,NFFT,iqmode)
+ if(ierr.ne.0) then
+ print*,'Error in soundin',ierr
+ stop
+ endif
+
+ sq=0.
+ do i=1,NFFT
+ x=iwave(1,i)
+ y=iwave(2,i)
+ c(i-1)=cmplx(y,x)
+ sq=sq + x*x + y*y
+ enddo
+
+ rms=sqrt(sq/(2.0*NFFT))
+
+ call four2a(c,NFFT,1,-1,1)
+
+ smax=0.
+ nz=NFFT/2
+! ia=1400.0/df
+! ib=1600.0/df
+ ia=10/df
+ ib=nz
+ fac=100./float(nfft)**2
+ do i=ia,ib
+ s=fac * (real(c(i))**2 + aimag(c(i))**2)
+ s1(i)=s
+ if(abs(i*df-noffset).le.100.0) then
+ if(s.gt.smax) then
+ smax=s
+ ipk=i
+ endif
+ endif
+ enddo
+
+ fpeak=ipk*df
+ n=mod(nsec,86400)
+ nhr=n/3600
+ nmin=mod(n/60,60)
+ nsec=mod(n,60)
+! smax=100.0*smax/(rms*rms)
+ ave=0.
+ diff=fpeak-noffset + iter - 1
+ write(*,1100) nhr,nmin,nsec,nkhz,noffset,fpeak,diff,smax,rms
+ write(13,1100) nhr,nmin,nsec,nkhz,noffset,fpeak,diff,smax,rms
+1100 format(i2.2,':',i2.2,':',i2.2,i7,i6,4f10.2)
+! write(14,1100) nhr,nmin,nsec,nkhz,noffset,fpeak,smax,ave,rms
+ do i=ia,ib
+ write(14,1102) i*df,s1(i)
+1102 format(2f12.3)
+ enddo
+! write(15) nhr,nmin,nsec,nkhz,noffset,fpeak,smax,ave,rms,iwave
+ call flush(13)
+! call flush(14)
+! call flush(15)
+ enddo
+
+999 end program fmtiq
+
diff --git a/fold1pps.f90 b/fold1pps.f90
new file mode 100644
index 0000000..9046ae2
--- /dev/null
+++ b/fold1pps.f90
@@ -0,0 +1,49 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: fold1pps.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine fold1pps(x,npts,ip1,ip2,prof,p,pk,ipk)
+
+ parameter (NFSMAX=48000)
+ real x(npts)
+ real proftmp(NFSMAX+5),prof(NFSMAX+5)
+ real*8 p,ptmp
+
+ pk=0.
+ do ip=ip1,ip2
+ call ffa(x,npts,npts,ip,proftmp,ptmp,pktmp,ipktmp)
+ if(abs(pktmp).gt.abs(pk)) then
+ p=ptmp
+ pk=pktmp
+ ipk=ipktmp
+ prof(:ip)=proftmp(:ip)
+ endif
+ enddo
+ ip=p
+ if(pk.lt.0.0) then
+ prof(:ip)=-prof(:ip)
+ endif
+
+ return
+end subroutine fold1pps
diff --git a/four2a.f90 b/four2a.f90
new file mode 100644
index 0000000..7f3100f
--- /dev/null
+++ b/four2a.f90
@@ -0,0 +1,126 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: four2a.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine four2a(a,nfft,ndim,isign,iform)
+
+! IFORM = 1, 0 or -1, as data is
+! complex, real, or the first half of a complex array. Transform
+! values are returned in array DATA. They are complex, real, or
+! the first half of a complex array, as IFORM = 1, -1 or 0.
+
+! The transform of a real array (IFORM = 0) dimensioned N(1) by N(2)
+! by ... will be returned in the same array, now considered to
+! be complex of dimensions N(1)/2+1 by N(2) by .... Note that if
+! IFORM = 0 or -1, N(1) must be even, and enough room must be
+! reserved. The missing values may be obtained by complex conjugation.
+
+! The reverse transformation of a half complex array dimensioned
+! N(1)/2+1 by N(2) by ..., is accomplished by setting IFORM
+! to -1. In the N array, N(1) must be the true N(1), not N(1)/2+1.
+! The transform will be real and returned to the input array.
+
+ parameter (NPMAX=200)
+ parameter (NSMALL=16384)
+ complex a(nfft)
+ complex aa(NSMALL)
+ integer nn(NPMAX),ns(NPMAX),nf(NPMAX),nl(NPMAX)
+ integer*8 plan(NPMAX) !Actually should be i*8, but no matter
+ data nplan/0/
+ include 'fftw3.f90'
+ save plan,nplan,nn,ns,nf,nl
+
+ if(nfft.lt.0 .or. ndim.lt.1) go to 999
+
+ nloc=loc(a)
+ do i=1,nplan
+ if(nfft.eq.nn(i) .and. isign.eq.ns(i) .and. &
+ iform.eq.nf(i) .and. nloc.eq.nl(i)) go to 10
+ enddo
+ if(nplan.ge.NPMAX) go to 999
+ nplan=nplan+1
+ i=nplan
+ nn(i)=nfft
+ ns(i)=isign
+ nf(i)=iform
+ nl(i)=nloc
+
+! Planning: FFTW_ESTIMATE, FFTW_ESTIMATE_PATIENT, FFTW_MEASURE,
+! FFTW_PATIENT, FFTW_EXHAUSTIVE
+ npatience=1
+ nflags=FFTW_ESTIMATE
+ if(npatience.eq.1) nflags=FFTW_ESTIMATE_PATIENT
+ if(npatience.eq.2) nflags=FFTW_MEASURE
+ if(npatience.eq.3) nflags=FFTW_PATIENT
+ if(npatience.eq.4) nflags=FFTW_EXHAUSTIVE
+! call cs_lock('four2a')
+
+ if(nfft.le.NSMALL) then
+ jz=nfft
+ if(iform.eq.0) jz=nfft/2
+ do j=1,jz
+ aa(j)=a(j)
+ enddo
+ endif
+
+ if(isign.eq.-1 .and. iform.eq.1) then
+ call sfftw_plan_dft_1d(plan(i),nfft,a,a,FFTW_FORWARD,nflags)
+ else if(isign.eq.1 .and. iform.eq.1) then
+ call sfftw_plan_dft_1d(plan(i),nfft,a,a,FFTW_BACKWARD,nflags)
+ else if(isign.eq.-1 .and. iform.eq.0) then
+ call sfftw_plan_dft_r2c_1d(plan(i),nfft,a,a,nflags)
+ else if(isign.eq.1 .and. iform.eq.-1) then
+ call sfftw_plan_dft_c2r_1d(plan(i),nfft,a,a,nflags)
+ else
+ stop 'Unsupported request in four2a'
+ endif
+ i=nplan
+ if(nfft.le.NSMALL) then
+ jz=nfft
+ if(iform.eq.0) jz=nfft/2
+ do j=1,jz
+ a(j)=aa(j)
+ enddo
+ endif
+
+10 continue
+ call sfftw_execute(plan(i))
+! call cs_unlock
+ return
+
+999 do i=1,nplan
+ call sfftw_destroy_plan(plan(i))
+ enddo
+ if(ndim.lt.0 .or. nplan.ge.NPMAX) then
+ open(24,file='FFT_plans.txt',status='unknown')
+ do i=1,nplan
+ write(24,1999) i,nn(i),ns(i),nf(i),nl(i)
+1999 format(5i12)
+ enddo
+ call flush(24)
+ if(nplan.ge.NPMAX) stop 'Too many FFTW plans requested.'
+ endif
+
+ return
+end subroutine four2a
diff --git a/fthread.c b/fthread.c
new file mode 100644
index 0000000..d47e76f
--- /dev/null
+++ b/fthread.c
@@ -0,0 +1,103 @@
+/*
+ *-------------------------------------------------------------------------------
+ *
+ * This file is part of the WSPR application, Weak Signal Propagation Reporter
+ *
+ * File Name: fthread.c
+ * Description:
+ *
+ * Original Author: V. Ganesh
+ * Source: http://v-ganesh.tripod.com/papers/fthreads.pdf*
+ *
+ * Copyright (C) 2008-2014 Joseph Taylor, K1JT
+ * License: GPL-3
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+ * Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *-------------------------------------------------------------------------------
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef Win32
+ #include "pthread_w32.h"
+#else
+ #include <pthread.h>
+#endif
+
+// Create a new fortran thread through a subroutine.
+void fthread_create_(void *(*thread_func)(void *), pthread_t *theThread)
+{
+ pthread_create(theThread, NULL, thread_func, NULL);
+}
+
+/*
+// Yield control to other threads
+void fthread_yield_()
+{
+ pthread_yield();
+}
+*/
+
+// Return my own thread ID
+pthread_t fthread_self_()
+{
+ return pthread_self();
+}
+
+// Lock the execution of all threads until we have the mutex
+int fthread_mutex_lock_(pthread_mutex_t **theMutex)
+{
+ return(pthread_mutex_lock(*theMutex));
+}
+
+int fthread_mutex_trylock_(pthread_mutex_t **theMutex)
+{
+ return(pthread_mutex_trylock(*theMutex));
+}
+
+// Unlock the execution of all threads that were stopped by this mutex
+void fthread_mutex_unlock_(pthread_mutex_t **theMutex)
+{
+ pthread_mutex_unlock(*theMutex);
+}
+
+// Get a new mutex object
+void fthread_mutex_init_(pthread_mutex_t **theMutex)
+{
+ *theMutex = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t));
+ pthread_mutex_init(*theMutex, NULL);
+}
+
+// Release a mutex object
+void fthread_mutex_destroy_(pthread_mutex_t **theMutex)
+{
+ pthread_mutex_destroy(*theMutex);
+ free(*theMutex);
+}
+
+// Waits for thread ID to join
+void fthread_join(pthread_t *theThread)
+{
+ int value = 0;
+ pthread_join(*theThread, (void **)&value);
+}
+
+// Exit from a thread
+void fthread_exit_(void *status)
+{
+ pthread_exit(status);
+}
+
diff --git a/ftn_quit.f90 b/ftn_quit.f90
new file mode 100644
index 0000000..80c76a6
--- /dev/null
+++ b/ftn_quit.f90
@@ -0,0 +1,39 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: ftn_quit.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine ftn_quit
+
+ call cs_lock('ftn_quit')
+! rewind 17
+! call export_wisdom_to_file(17)
+! close(17)
+! write(*,1000)
+!1000 format('Exported FFTW wisdom')
+ call four2a(a,-1,1,1,1) !Destroy the FFTW plans
+ call cs_unlock
+ call cs_destroy
+
+ return
+end subroutine ftn_quit
diff --git a/gencwid.f90 b/gencwid.f90
new file mode 100644
index 0000000..1fadf12
--- /dev/null
+++ b/gencwid.f90
@@ -0,0 +1,63 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: gencwid.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine gencwid(msg,wpm0,freqcw,iwave,nwave)
+
+ parameter (NMAX=5*48000)
+ character msg*22,msg2*22
+ integer*2 iwave(NMAX)
+
+ integer*1 idat(460)
+ real*8 dt,t,twopi,pha,dpha,tdit
+ data twopi/6.283185307d0/
+
+ do i=1,22
+ if(msg(i:i).eq.' ') go to 10
+ enddo
+10 iz=i-1
+ msg2=msg(1:iz)//' '
+ call morse(msg2,idat,ndits) !Encode part 1 of msg
+ nwave=4.5*48000
+ dt=1.d0/48000.d0
+ wpm=1.2*ndits/(nwave*dt)
+ if(wpm.lt.wpm0) wpm=wpm0
+ tdit=1.2d0/wpm !Key-down dit time, seconds
+ nwave=ndits*tdit/dt
+ pha=0.
+ dpha=twopi*freqcw*dt
+ t=0.d0
+ s=0.
+ u=wpm/(48000.0*0.03)
+ do i=1,nwave
+ t=t+dt
+ pha=pha+dpha
+ j=t/tdit + 1
+ s=s + u*(idat(j)-s)
+ iwave(i)=nint(s*32767.d0*sin(pha))
+ enddo
+
+ return
+end subroutine gencwid
+
diff --git a/genmept.f90 b/genmept.f90
new file mode 100644
index 0000000..baaec37
--- /dev/null
+++ b/genmept.f90
@@ -0,0 +1,138 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: genmept.f90
+! Description:enme
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine genmept(message,ntxdf,ntrminutes,multi,list,snrdb,iwave)
+
+! Encode a WSPR message and generate the corresponding wavefile.
+
+ character*22 message
+ parameter (NMAX=900*12000) !Max length of wave file
+ integer*2 iwave(NMAX) !Generated wave file
+ parameter (MAXSYM=176)
+ integer*1 symbol(MAXSYM)
+ integer*1 data0(11),i1
+ integer npr3(162)
+ logical first
+ real*8 t,dt,phi,f,f0,dfgen,dphi,pi,twopi,tsymbol
+ equivalence(i1,i4)
+ data npr3/ &
+ 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, &
+ 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, &
+ 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, &
+ 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, &
+ 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, &
+ 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, &
+ 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, &
+ 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, &
+ 0,0/
+
+ data first/.true./,idum/0/
+ save
+
+ if(first) then
+ pi=4.d0*atan(1.d0)
+ twopi=2.d0*pi
+ first=.false.
+ endif
+
+ call wqencode(message,ntype,data0)
+ nbytes=(50+31+7)/8
+ call encode232(data0,nbytes,symbol,MAXSYM) !Convolutional encoding
+ call inter_mept(symbol,1) !Apply interleaving
+ do i=1,162
+ i4=0
+ i1=symbol(i)
+ enddo
+
+! Set up necessary constants
+ tsymbol=8192.d0/12000.d0
+ if(ntrminutes.eq.15) tsymbol=65536.d0/12000.d0
+ nz=60*ntrminutes*12000
+ dt=1.d0/12000.d0
+ f0=1500 + ntxdf
+ dfgen=1.d0/tsymbol !1.4649 Hz or 0.1831 Hz
+ nsigs=1
+ if(multi.ne.0) nsigs=10
+ do isig=1,nsigs
+ if(nsigs.eq.1) snr=10.0**(0.05*snrdb)
+ fac=3000.0
+ if(snr.gt.1.0) fac=3000.0/snr
+ t=-2.d0 - 0.1*(isig-1)
+ if(nsigs.eq.10) then
+ if(ntrminutes.eq.2) then
+ ndb=-20-isig
+ snr=10.0**(0.05*ndb)
+ f0=1390 + 20*isig
+ else
+ ndb=-29-isig
+ snr=10.0**(0.05*ndb)
+ f0=1612.5 + 2.5*(isig-5.5)
+ endif
+ dtx=-t-2.0
+ write(*,1000) isig,ndb,dtx,1.d-6*f0,message
+1000 format(i2,2x,i4,f5.1,f11.6,2x,a22)
+ endif
+
+ phi=0.d0
+ j0=0
+
+ do i=1,nz
+ t=t+dt
+ j=int(t/tsymbol) + 1 !Symbol number
+ sig=0.
+ if(j.ge.1 .and. j.le.162) then
+ if(j.ne.j0) then
+ f=f0 + dfgen*(npr3(j)+2*symbol(j)-1.5)
+ j0=j
+ if(list.ne.0) then
+ k=npr3(j) + 2*symbol(j)
+ write(*,1010) j,k,f
+1010 format(i3,i3,f10.3)
+ go to 10
+ else
+ dphi=twopi*dt*f
+ endif
+ endif
+ sig=0.9999
+ endif
+ phi=phi+dphi
+ if(snrdb.gt.50.0) then
+ n=32767.0*sin(phi) !Normal transmission, signal only
+ else
+ if(isig.eq.1) then
+ n=fac*(gran(idum) + sig*snr*sin(phi))
+ else
+ n=iwave(i) + fac*sig*snr*sin(phi)
+ endif
+ if(n.gt.32767) n=32767
+ if(n.lt.-32767) n=-32767
+ endif
+ iwave(i)=n
+10 continue
+ enddo
+ enddo
+
+ return
+end subroutine genmept
diff --git a/genwspr.f90 b/genwspr.f90
new file mode 100644
index 0000000..a90bd14
--- /dev/null
+++ b/genwspr.f90
@@ -0,0 +1,147 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: genwspr.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine genwspr(message,ntxdf,snrdb,iqmode,iqtx,ntrminutes,msg2,jwave)
+
+! Encode an MEPT_JT message and generate the corresponding wavefile.
+
+ parameter (NMAX=2*120*48000) !Max length of wave file
+ character*22 message !Message to be generated
+ character*22 msg2
+ integer*2 jwave(NMAX) !Generated wave file
+ parameter (MAXSYM=176)
+ integer*1 symbol(MAXSYM)
+ integer*1 data0(11),i1
+ integer npr3(162)
+ logical first
+ real*8 t,dt,phi,f,f0,dfgen,dphi,pi,twopi,tsymbol
+ include 'acom2.f90'
+
+ equivalence(i1,i4)
+ data npr3/ &
+ 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, &
+ 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, &
+ 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, &
+ 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, &
+ 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, &
+ 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, &
+ 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, &
+ 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, &
+ 0,0/
+
+ data first/.true./,idum/0/
+ save first,idum,pi,twopi
+
+ if(first) then
+ pi=4.d0*atan(1.d0)
+ twopi=2.d0*pi
+ first=.false.
+ endif
+
+ call wqencode(message,ntype,data0)
+ nbytes=(50+31+7)/8
+ call encode232(data0,nbytes,symbol,MAXSYM) !Convolutional encoding
+ call inter_mept(symbol,1) !Apply interleaving
+ do i=1,162
+ i4=0
+ i1=symbol(i)
+ enddo
+ call wqdecode(data0,msg2,ntype2)
+
+! Set up necessary constants
+ nsps=8192
+! if(ntrminutes.eq.15) nsps=65536
+ tsymbol=4.d0*nsps/48000.d0
+ dt=1.d0/48000.d0
+ f0=1500 + ntxdf
+ dfgen=1.d0/tsymbol !1.4649 Hz in WSPR-2, 0.1831 Hz in WSPR-15
+ snr=10.0**(0.05*(snrdb-6.5)) !Bandwidth correction?
+ fac=3000.0
+ if(snr.gt.1.0) fac=3000.0/snr
+ t=-1.d0
+ phi=0.d0
+ j0=0
+ f=f0
+ dphi=twopi*dt*f
+
+ do i=1,ntrminutes*60*48000
+ t=t+dt
+ j=int(t/tsymbol) + 1 !Symbol number
+ sig=0.
+ if(j.ge.1 .and. j.le.162) then
+ if(j.ne.j0 .and. ntune2.eq.0) then
+ f=f0 + dfgen*(npr3(j)+2*symbol(j)-1.5)
+ j0=j
+ dphi=twopi*dt*f
+ endif
+ sig=0.9999
+ phi=phi+dphi
+ if(snrdb.gt.50.0) then
+ if(iqmode.eq.0) then
+ n=32767.0*sin(phi) !Normal transmission, signal only
+ jwave(i)=n
+ else
+ n1=32767.0*cos(phi) !Normal transmission, signal only
+ n2=32767.0*sin(phi) !Normal transmission, signal only
+ if(iqtx.eq.0) then
+ jwave(2*i-1)=n1
+ jwave(2*i)=n2
+ else
+ jwave(2*i-1)=n2
+ jwave(2*i)=n1
+ endif
+ endif
+ else
+ if(iqmode.eq.0) then
+ n=fac*(gran(idum) + sig*snr*sin(phi))
+ if(n.gt.32767) n=32767
+ if(n.lt.-32767) n=-32767
+ jwave(i)=n
+ else
+ n=fac*(gran(idum) + sig*snr*cos(phi))
+ if(n.gt.32767) n=32767
+ if(n.lt.-32767) n=-32767
+ jwave(2*i-1)=n
+ n=fac*(gran(idum) + sig*snr*sin(phi))
+ if(n.gt.32767) n=32767
+ if(n.lt.-32767) n=-32767
+ jwave(2*i)=n
+ endif
+ endif
+ else
+ if(iqmode.eq.0) then
+ jwave(i)=0
+ else
+ jwave(2*i-1)=0
+ jwave(2*i)=0
+ endif
+ endif
+ enddo
+ if(ntune2.lt.0) msg2='Tune'
+ if(ntune2.eq.-3) msg2='ATU tuneup'
+ ntune2=0
+
+ return
+end subroutine genwspr
diff --git a/geodist.f90 b/geodist.f90
new file mode 100644
index 0000000..461b3cf
--- /dev/null
+++ b/geodist.f90
@@ -0,0 +1,122 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: geodist.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine geodist(Eplat, Eplon, Stlat, Stlon, Az, Baz, Dist)
+ implicit none
+ real eplat, eplon, stlat, stlon, az, baz, dist
+
+!JHT: In actual fact, I use the first two arguments for "My Location",
+! the second two for "His location"; West longitude is positive.
+
+! Taken directly from:
+! Thomas, P.D., 1970, Spheroidal geodesics, reference systems,
+! & local geometry, U.S. Naval Oceanographic Office SP-138,
+! 165 pp.
+
+! assumes North Latitude and East Longitude are positive
+
+! EpLat, EpLon = End point Lat/Long
+! Stlat, Stlon = Start point lat/long
+! Az, BAz = direct & reverse azimuith
+! Dist = Dist (km); Deg = central angle, discarded
+
+ real BOA, F, P1R, P2R, L1R, L2R, DLR, T1R, T2R, TM, &
+ DTM, STM, CTM, SDTM,CDTM, KL, KK, SDLMR, L, &
+ CD, DL, SD, T, U, V, D, X, E, Y, A, FF64, TDLPM, &
+ HAPBR, HAMBR, A1M2, A2M1
+
+ real AL,BL,D2R,Pi2
+
+ data AL/6378206.4/ ! Clarke 1866 ellipsoid
+ data BL/6356583.8/
+! real pi /3.14159265359/
+ data D2R/0.01745329251994/ ! degrees to radians conversion factor
+ data Pi2/6.28318530718/
+
+ BOA = BL/AL
+ F = 1.0 - BOA
+!convert st/end pts to radians
+ P1R = Eplat * D2R
+ P2R = Stlat * D2R
+ L1R = Eplon * D2R
+ L2R = StLon * D2R
+ DLR = L2R - L1R ! DLR = Delta Long in Rads
+ T1R = ATan(BOA * Tan(P1R))
+ T2R = ATan(BOA * Tan(P2R))
+ TM = (T1R + T2R) / 2.0
+ DTM = (T2R - T1R) / 2.0
+ STM = Sin(TM)
+ CTM = Cos(TM)
+ SDTM = Sin(DTM)
+ CDTM = Cos(DTM)
+ KL = STM * CDTM
+ KK = SDTM * CTM
+ SDLMR = Sin(DLR/2.0)
+ L = SDTM * SDTM + SDLMR * SDLMR * (CDTM * CDTM - STM * STM)
+ CD = 1.0 - 2.0 * L
+ DL = ACos(CD)
+ SD = Sin(DL)
+ T = DL/SD
+ U = 2.0 * KL * KL / (1.0 - L)
+ V = 2.0 * KK * KK / L
+ D = 4.0 * T * T
+ X = U + V
+ E = -2.0 * CD
+ Y = U - V
+ A = -D * E
+ FF64 = F * F / 64.0
+ Dist = AL*SD*(T -(F/4.0)*(T*X-Y)+FF64*(X*(A+(T-(A+E) &
+ /2.0)*X)+Y*(-2.0*D+E*Y)+D*X*Y))/1000.0
+ TDLPM = Tan((DLR+(-((E*(4.0-X)+2.0*Y)*((F/2.0)*T+FF64* &
+ (32.0*T+(A-20.0*T)*X-2.0*(D+2.0)*Y))/4.0)*Tan(DLR)))/2.0)
+ HAPBR = ATan2(SDTM,(CTM*TDLPM))
+ HAMBR = Atan2(CDTM,(STM*TDLPM))
+ A1M2 = Pi2 + HAMBR - HAPBR
+ A2M1 = Pi2 - HAMBR - HAPBR
+
+1 If ((A1M2 .ge. 0.0) .AND. (A1M2 .lt. Pi2)) GOTO 5
+ If (A1M2 .lt. Pi2) GOTO 4
+ A1M2 = A1M2 - Pi2
+ GOTO 1
+4 A1M2 = A1M2 + Pi2
+ GOTO 1
+
+!all of this gens the proper az, baz (forward and back azimuth)
+
+5 If ((A2M1 .ge. 0.0) .AND. (A2M1 .lt. Pi2)) GOTO 9
+ If (A2M1 .lt. Pi2) GOTO 8
+ A2M1 = A2M1 - Pi2
+ GOTO 5
+8 A2M1 = A2M1 + Pi2
+ GOTO 5
+
+9 Az = A1M2 / D2R
+ BAZ = A2M1 / D2R
+
+!Fix the mirrored coords here.
+
+ az = 360.0 - az
+ baz = 360.0 - baz
+end subroutine geodist
diff --git a/getfile.f90 b/getfile.f90
new file mode 100644
index 0000000..4b9fd22
--- /dev/null
+++ b/getfile.f90
@@ -0,0 +1,69 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: getfile.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine getfile(fname,len)
+!f2py threadsafe
+
+ character*(*) fname
+ include 'acom1.f90'
+ integer*1 hdr(44),n1
+ integer*2 nfmt2,nchan2,nbitsam2,nbytesam2
+ character*4 ariff,awave,afmt,adata
+ common/hdr/ariff,lenfile,awave,afmt,lenfmt,nfmt2,nchan2, &
+ nsamrate,nbytesec,nbytesam2,nbitsam2,adata,ndata,d2
+ equivalence (ariff,hdr),(n1,n4),(d1,d2)
+
+1 if(ndecoding.eq.0) go to 2
+ call msleep(100)
+ go to 1
+
+!2 ndecoding=1
+2 do i=len,1,-1
+ if(fname(i:i).eq.'/' .or. fname(i:i).eq.'\\') go to 10
+ enddo
+ i=0
+10 continue
+ call cs_lock('getfile')
+ open(10,file=fname,access='stream',status='old')
+ read(10) hdr
+ npts=114*12000
+ if(ntrminutes.eq.15) npts=890*12000
+ read(10) (iwave(i),i=1,npts)
+ close(10)
+ n4=1
+ if (n1.eq.1) goto 8 !skip byteswap if little endian
+ do i=1,npts
+ i4 = iwave(i)
+ iwave(i) = ishft(iand(i4,255),8) + iand(ishft(i4,-8),255)
+ enddo
+8 call getrms(iwave,npts,ave,rms)
+ ndecdone=0 !??? ### ???
+ ndiskdat=1
+ outfile=fname
+ nrxdone=1
+ call cs_unlock
+
+ return
+end subroutine getfile
diff --git a/getrms.f90 b/getrms.f90
new file mode 100644
index 0000000..a25a731
--- /dev/null
+++ b/getrms.f90
@@ -0,0 +1,50 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: getrms.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine getrms(iwave,npts,ave,rms)
+
+ integer*2 iwave(npts)
+ real*8 sq
+
+ s=0.
+ do i=1,npts
+ s=s + iwave(i)
+ enddo
+ ave=s/npts
+ sq=0.
+ do i=1,npts
+ sq=sq + (iwave(i)-ave)**2
+ enddo
+ rms=sqrt(sq/npts)
+ fac=3000.0/rms
+ do i=1,npts
+ n=nint(fac*(iwave(i)-ave))
+ if(n.gt.32767) n=32767
+ if(n.lt.-32767) n=-32767
+ iwave(i)=n
+ enddo
+
+ return
+end subroutine getrms
diff --git a/getsound.c b/getsound.c
new file mode 100644
index 0000000..f95a100
--- /dev/null
+++ b/getsound.c
@@ -0,0 +1,167 @@
+/** @file patest_record.c
+ @brief Record input into an array; Save array to a file; Playback recorded data.
+ @author Phil Burk http://www.softsynth.com
+*/
+/*
+ * $Id: patest_record.c 249 2006-08-09 20:08:01Z va3db $
+ *
+ * This program uses the PortAudio Portable Audio Library.
+ * For more information see: http://www.portaudio.com
+ * Copyright (c) 1999-2006 Ross Bencina and Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "portaudio.h"
+
+#define SAMPLE_RATE (12000)
+#define FRAMES_PER_BUFFER (1024)
+#define NUM_SECONDS (114)
+#define NUM_CHANNELS (1)
+/* #define DITHER_FLAG (paDitherOff) */
+#define DITHER_FLAG (0) /**/
+
+#define PA_SAMPLE_TYPE paInt16
+typedef short SAMPLE;
+#define SAMPLE_SILENCE (0)
+
+typedef struct
+{
+ int frameIndex; /* Index into sample array. */
+ int maxFrameIndex;
+ SAMPLE *recordedSamples;
+} paTestData;
+
+/* This routine will be called by the PortAudio engine when audio is needed.
+** It may be called at interrupt level on some machines so don't do anything
+** that could mess up the system like calling malloc() or free().
+*/
+static int recordCallback( const void *inputBuffer, void *outputBuffer,
+ unsigned long framesPerBuffer,
+ const PaStreamCallbackTimeInfo* timeInfo,
+ PaStreamCallbackFlags statusFlags,
+ void *userData )
+{
+ paTestData *data = (paTestData*)userData;
+ const SAMPLE *rptr = (const SAMPLE*)inputBuffer;
+ SAMPLE *wptr = &data->recordedSamples[data->frameIndex * NUM_CHANNELS];
+ long framesToCalc;
+ long i;
+ int finished;
+ unsigned long framesLeft = data->maxFrameIndex - data->frameIndex;
+
+ (void) outputBuffer; /* Prevent unused variable warnings. */
+ (void) timeInfo;
+ (void) statusFlags;
+ (void) userData;
+
+ if( framesLeft < framesPerBuffer ) {
+ framesToCalc = framesLeft;
+ finished = paComplete;
+ }
+ else {
+ framesToCalc = framesPerBuffer;
+ finished = paContinue;
+ }
+
+ if( inputBuffer == NULL ) {
+ for( i=0; i<framesToCalc; i++ ) {
+ *wptr++ = SAMPLE_SILENCE; /* left */
+ if( NUM_CHANNELS == 2 ) *wptr++ = SAMPLE_SILENCE; /* right */
+ }
+ }
+ else {
+ for( i=0; i<framesToCalc; i++ ) {
+ *wptr++ = *rptr++; /* left */
+ if( NUM_CHANNELS == 2 ) *wptr++ = *rptr++; /* right */
+ }
+ }
+ data->frameIndex += framesToCalc;
+ return finished;
+}
+
+/*******************************************************************/
+extern int getsound_(short int iwave[])
+{
+ PaStreamParameters inputParameters;
+ PaStream* stream;
+ PaError err = paNoError;
+ paTestData data;
+ int i;
+ int totalFrames;
+ int numSamples;
+ int numBytes;
+
+ data.maxFrameIndex = totalFrames = NUM_SECONDS * SAMPLE_RATE;
+ data.frameIndex = 0;
+ numSamples = totalFrames * NUM_CHANNELS;
+ numBytes = numSamples * sizeof(SAMPLE);
+ data.recordedSamples = iwave;
+ for( i=0; i<numSamples; i++ )
+ data.recordedSamples[i] = 0;
+
+ // err = Pa_Initialize();
+ // if( err != paNoError ) goto done;
+
+ inputParameters.device = Pa_GetDefaultInputDevice();
+ inputParameters.channelCount = 1;
+ inputParameters.sampleFormat = PA_SAMPLE_TYPE;
+ inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
+ inputParameters.hostApiSpecificStreamInfo = NULL;
+
+ err = Pa_OpenStream(
+ &stream,
+ &inputParameters,
+ NULL, /* &outputParameters, */
+ SAMPLE_RATE,
+ FRAMES_PER_BUFFER,
+ paClipOff,
+ recordCallback,
+ &data );
+ if( err != paNoError ) goto done;
+
+ err = Pa_StartStream( stream );
+ if( err != paNoError ) goto done;
+
+ while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) {
+ Pa_Sleep(100);
+ }
+ if( err < 0 ) goto done;
+
+ err = Pa_CloseStream( stream );
+
+done:
+ // Pa_Terminate();
+ if( err != paNoError ) {
+ fprintf( stderr, "An error occured while using the portaudio stream\n" );
+ fprintf( stderr, "Error number: %d\n", err );
+ fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
+ err = 1; /* Always return 0 or 1, but no other return codes. */
+ }
+ return err;
+}
+
diff --git a/getutc.f90 b/getutc.f90
new file mode 100644
index 0000000..636622c
--- /dev/null
+++ b/getutc.f90
@@ -0,0 +1,57 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: getutc.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine getutc(cdate,ctime,tsec)
+
+ character cdate*8,ctime*10
+ real*8 tsec
+ integer nt(9)
+! 1 2 3 4 5 6 7 8 9
+! nt: sec min ihr day month year dweek 0 0
+
+ call gmtime2(nt,tsec)
+ cdate(1:1)=char(48+nt(6)/1000)
+ cdate(2:2)=char(48+mod(nt(6),1000)/100)
+ cdate(3:3)=char(48+mod(nt(6),100)/10)
+ cdate(4:4)=char(48+mod(nt(6),10))
+ cdate(5:5)=char(48+nt(5)/10)
+ cdate(6:6)=char(48+mod(nt(5),10))
+ cdate(7:7)=char(48+nt(4)/10)
+ cdate(8:8)=char(48+mod(nt(4),10))
+ ctime(1:1)=char(48+nt(3)/10)
+ ctime(2:2)=char(48+mod(nt(3),10))
+ ctime(3:3)=char(48+nt(2)/10)
+ ctime(4:4)=char(48+mod(nt(2),10))
+ ctime(5:5)=char(48+nt(1)/10)
+ ctime(6:6)=char(48+mod(nt(1),10))
+ ctime(7:7)='.'
+ nsec=tsec
+ msec=1000*(tsec-nsec)
+ ctime(8:8)=char(48+msec/100)
+ ctime(9:9)=char(48+mod(msec,100)/10)
+ ctime(10:10)=char(48+mod(msec,10))
+
+ return
+end subroutine getutc
diff --git a/gmtime2.c b/gmtime2.c
new file mode 100644
index 0000000..9227aa9
--- /dev/null
+++ b/gmtime2.c
@@ -0,0 +1,81 @@
+/*-------------------------------------------------------------------------------
+ *
+ * This file is part of the WSPR application, Weak Signal Propagation Reporter
+ *
+ * File Name: gmtime2.c
+ * Description:
+ *
+ * Copyright (C) 2001-2014 Joseph Taylor, K1JT
+ * License: GPL-3
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+ * Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *-------------------------------------------------------------------------------
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+typedef struct _SYSTEMTIME
+{
+ short Year;
+ short Month;
+ short DayOfWeek;
+ short Day;
+ short Hour;
+ short Minute;
+ short Second;
+ short Millisecond;
+} SYSTEMTIME;
+
+#ifdef Win32
+extern void __stdcall GetSystemTime(SYSTEMTIME *st);
+#else
+#include <sys/time.h>
+#include <time.h>
+
+void GetSystemTime(SYSTEMTIME *st){
+ struct timeval tmptimeofday;
+ struct tm tmptmtime;
+ gettimeofday(&tmptimeofday,NULL);
+ gmtime_r((const time_t *)&tmptimeofday.tv_sec,&tmptmtime);
+ st->Year = (short)tmptmtime.tm_year;
+ st->Month = (short)tmptmtime.tm_mon+1;
+ st->DayOfWeek = (short)tmptmtime.tm_wday;
+ st->Day = (short)tmptmtime.tm_mday;
+ st->Hour = (short)tmptmtime.tm_hour;
+ st->Minute = (short)tmptmtime.tm_min;
+ st->Second = (short)tmptmtime.tm_sec;
+ st->Millisecond = (short)(tmptimeofday.tv_usec/1000);
+}
+#endif
+
+extern void gmtime2_(int it[], double *stime)
+{
+ SYSTEMTIME st;
+
+ GetSystemTime(&st);
+ it[0]=st.Second;
+ it[1]=st.Minute;
+ it[2]=st.Hour;
+ it[3]=st.Day;
+ it[4]=st.Month;
+ it[5]=st.Year;
+ it[6]=st.DayOfWeek;
+ it[7]=0;
+ it[8]=0;
+ *stime = st.Hour*3600.0 + st.Minute*60.0 + st.Second + st.Millisecond*0.001;
+}
+
diff --git a/gocal b/gocal
new file mode 100644
index 0000000..4d41231
--- /dev/null
+++ b/gocal
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+# Example stations, edit for you location
+#
+# To remove stations form list, add a "#" in
+# front of the stations:
+#
+# #./fmtest 15000 1 1500 100 30 WWV
+#
+# Station List
+
+# local stations, edit to suit location
+fmtest 550 1 1500 100 30 KBOW
+fmtest 580 1 1500 100 30 KANA
+fmtest 950 1 1500 100 30 KMTX
+fmtest 2500 1 1500 100 30 WWV
+fmtest 5000 1 1500 100 30 WWV
+fmtest 7850 1 1500 100 30 CHU
+fmtest 10000 1 1500 100 30 WWV
+fmtest 15000 1 1500 100 30 WWV
diff --git a/gran.f90 b/gran.f90
new file mode 100644
index 0000000..91398ed
--- /dev/null
+++ b/gran.f90
@@ -0,0 +1,55 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: gran.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+real function gran(newseed)
+
+! Generate gaussian random numbers with rms=1.0.
+
+ real r(0:31)
+ data i1/0/,i2/0/
+ save i1,i2,g1
+
+ if(newseed.lt.0) then
+ call random_seed
+ newseed=0
+ endif
+ if (i1.eq.0) then
+1 if(i2.eq.0) call random_number(r)
+ v1=2.0*r(2*i2) - 1.0
+ v2=2.0*r(2*i2+1) - 1.0
+ i2=iand(i2+1,15)
+ sq=v1**2 + v2**2
+ if(sq.ge.1..or.sq.eq.0.) go to 1
+ fac=sqrt(-2.*log(sq)/sq)
+ g1=v1*fac
+ gran=v2*fac
+ i1=1
+ else
+ gran=g1
+ i1=0
+ endif
+
+ return
+end function gran
diff --git a/grid2deg.f90 b/grid2deg.f90
new file mode 100644
index 0000000..dd4a2cf
--- /dev/null
+++ b/grid2deg.f90
@@ -0,0 +1,64 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: grid2deg.f90
+! Description: Converts Maidenhead grid locater to degrees of West longitude
+! and North latitude.
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine grid2deg(grid0,dlong,dlat)
+
+! Converts Maidenhead grid locator to degrees of West longitude
+! and North latitude.
+
+ character*6 grid0,grid
+ character*1 g1,g2,g3,g4,g5,g6
+
+ grid=grid0
+ i=ichar(grid(5:5))
+ if(grid(5:5).eq.' ' .or. i.le.64 .or. i.ge.128) grid(5:6)='mm'
+
+ if(grid(1:1).ge.'a' .and. grid(1:1).le.'z') grid(1:1)= &
+ char(ichar(grid(1:1))+ichar('A')-ichar('a'))
+ if(grid(2:2).ge.'a' .and. grid(2:2).le.'z') grid(2:2)= &
+ char(ichar(grid(2:2))+ichar('A')-ichar('a'))
+ if(grid(5:5).ge.'A' .and. grid(5:5).le.'Z') grid(5:5)= &
+ char(ichar(grid(5:5))-ichar('A')+ichar('a'))
+ if(grid(6:6).ge.'A' .and. grid(6:6).le.'Z') grid(6:6)= &
+ char(ichar(grid(6:6))-ichar('A')+ichar('a'))
+
+ g1=grid(1:1)
+ g2=grid(2:2)
+ g3=grid(3:3)
+ g4=grid(4:4)
+ g5=grid(5:5)
+ g6=grid(6:6)
+
+ nlong = 180 - 20*(ichar(g1)-ichar('A'))
+ n20d = 2*(ichar(g3)-ichar('0'))
+ xminlong = 5*(ichar(g5)-ichar('a')+0.5)
+ dlong = nlong - n20d - xminlong/60.0
+ nlat = -90+10*(ichar(g2)-ichar('A')) + ichar(g4)-ichar('0')
+ xminlat = 2.5*(ichar(g6)-ichar('a')+0.5)
+ dlat = nlat + xminlat/60.0
+
+ return
+end subroutine grid2deg
diff --git a/hamlib_rig_numbers b/hamlib_rig_numbers
new file mode 100644
index 0000000..97f35dc
--- /dev/null
+++ b/hamlib_rig_numbers
@@ -0,0 +1,145 @@
+ 101 Yaesu FT-847 0.5 Beta
+ 103 Yaesu FT-1000D 0.0.6 Alpha
+ 104 Yaesu MARK-V FT-1000MP 0.0.5 Alpha
+ 105 Yaesu FT-747GX 0.4.1 Beta
+ 106 Yaesu FT-757GX 0.4.1 Beta
+ 107 Yaesu FT-757GXII 0.4 Stable
+ 109 Yaesu FT-767GX 1.0 Stable
+ 110 Yaesu FT-736R 0.3 Stable
+ 111 Yaesu FT-840 0.1 Untested
+ 113 Yaesu FT-900 0.1 Untested
+ 114 Yaesu FT-920 2010-08-23 Stable
+ 115 Yaesu FT-890 0.1 Stable
+ 116 Yaesu FT-990 0.2.1 Alpha
+ 117 Yaesu FRG-100 0.4 Beta
+ 118 Yaesu FRG-9600 0.2 Untested
+ 119 Yaesu FRG-8800 0.2 Untested
+ 120 Yaesu FT-817 0.5.1 Beta
+ 121 Yaesu FT-100 0.4.1 Beta
+ 122 Yaesu FT-857 0.4 Beta
+ 123 Yaesu FT-897 0.3.3 Beta
+ 124 Yaesu FT-1000MP 0.1.1 Beta
+ 125 Yaesu MARK-V Field FT-1000MP 0.0.5 Alpha
+ 126 Yaesu VR-5000 0.2 Alpha
+ 127 Yaesu FT-450 0.22.1 Beta
+ 128 Yaesu FT-950 0.22.2 Stable
+ 129 Yaesu FT-2000 0.22.1 Stable
+ 130 Yaesu FTDX-9000 0.22.1 Untested
+ 131 Yaesu FT-980 0.1 Alpha
+ 132 Yaesu FT-DX5000 0.22 Alpha
+ 133 Vertex Standart VX-1700 1.1 Alpha
+ 201 Kenwood TS-50S 0.8 Untested
+ 202 Kenwood TS-440 0.8.0.6.1 Alpha
+ 203 Kenwood TS-450S 0.8.1 Beta
+ 204 Kenwood TS-570D 0.8.2 Stable
+ 205 Kenwood TS-690S 0.8.1 Beta
+ 206 Kenwood TS-711 0.8.0.6.1 Untested
+ 207 Kenwood TS-790 0.8.2 Alpha
+ 208 Kenwood TS-811 0.8.0.6.1 Untested
+ 209 Kenwood TS-850 0.8.1 Beta
+ 210 Kenwood TS-870S 0.8.0 Beta
+ 211 Kenwood TS-940S 0.8.0.6.1 Alpha
+ 213 Kenwood TS-950SDX 0.8 Beta
+ 214 Kenwood TS-2000 0.8.4 Beta
+ 215 Kenwood R-5000 0.6.1 Alpha
+ 216 Kenwood TS-570S 0.8.1 Stable
+ 217 Kenwood TH-D7A 0.5 Alpha
+ 219 Kenwood TH-F6A 0.5 Beta
+ 220 Kenwood TH-F7E 0.5.1 Beta
+ 221 Elecraft K2 20110603 Beta
+ 222 Kenwood TS-930 0.8 Untested
+ 223 Kenwood TH-G71 0.5 Beta
+ 224 Kenwood TS-680S 0.8.1 Beta
+ 225 Kenwood TS-140S 0.8.1 Beta
+ 226 Kenwood TM-D700 0.5 Beta
+ 227 Kenwood TM-V7 0.5 Beta
+ 228 Kenwood TS-480 0.8.5 Untested
+ 229 Elecraft K3/KX3 20111122 Beta
+ 230 Kenwood TRC-80 0.8 Alpha
+ 231 Kenwood TS-590S 0.8.1 Beta
+ 232 SigFox Transfox 20111223 Alpha
+ 233 Kenwood TH-D72A 0.5.1 Alpha
+ 234 Kenwood TM-D710 0.5 Untested
+ 302 Icom IC-1275 0.7 Beta
+ 303 Icom IC-271 0.7 Untested
+ 304 Icom IC-275 0.7.1 Beta
+ 306 Icom IC-471 0.7 Untested
+ 307 Icom IC-475 0.7.1 Beta
+ 309 Icom IC-706 0.7.1 Untested
+ 310 Icom IC-706MkII 0.7.1 Untested
+ 311 Icom IC-706MkIIG 0.7.2 Stable
+ 312 Icom IC-707 0.7 Untested
+ 313 Icom IC-718 0.7.1 Beta
+ 314 Icom IC-725 0.7.1 Stable
+ 315 Icom IC-726 0.7 Stable
+ 316 Icom IC-728 0.7 Untested
+ 319 Icom IC-735 0.7.1 Beta
+ 320 Icom IC-736 0.7 Untested
+ 321 Icom IC-737 0.7 Untested
+ 322 Icom IC-738 0.7 Untested
+ 323 Icom IC-746 0.7.1 Beta
+ 324 Icom IC-751 0.7.1 Beta
+ 326 Icom IC-756 0.7.1 Alpha
+ 327 Icom IC-756PRO 0.7 Untested
+ 328 Icom IC-761 0.7.1 Stable
+ 329 Icom IC-765 0.7 Stable
+ 330 Icom IC-775 0.7.1 Untested
+ 331 Icom IC-781 0.7.1 Untested
+ 332 Icom IC-820H 0.7 Alpha
+ 334 Icom IC-821H 0.7 Alpha
+ 335 Icom IC-970 0.7 Untested
+ 336 Icom IC-R10 0.7 Untested
+ 337 Icom IC-R71 0.7 Untested
+ 338 Icom IC-R72 0.7 Untested
+ 339 Icom IC-R75 0.7 Beta
+ 340 Icom IC-R7000 0.7.0 Alpha
+ 341 Icom IC-R7100 0.7.0 Untested
+ 342 Icom ICR-8500 0.7.1 Beta
+ 343 Icom IC-R9000 0.7.1 Alpha
+ 344 Icom IC-910 0.7.1 Beta
+ 345 Icom IC-78 0.7 Untested
+ 346 Icom IC-746PRO 0.7 Stable
+ 347 Icom IC-756PROII 0.7 Alpha
+ 351 Ten-Tec Omni VI Plus 0.2 Beta
+ 352 Optoelectronics OptoScan535 0.3 Beta
+ 353 Optoelectronics OptoScan456 0.3 Beta
+ 354 Icom IC ID-1 0.7 Untested
+ 355 Icom IC-703 0.7 Untested
+ 356 Icom IC-7800 0.7.2 Untested
+ 357 Icom IC-756PROIII 0.7.1 Beta
+ 358 Icom IC-R20 0.7 Untested
+ 360 Icom IC-7000 0.7.2 Beta
+ 361 Icom IC-7200 0.7 Beta
+ 362 Icom IC-7700 0.7.1 Stable
+ 363 Icom IC-7600 0.7 Beta
+ 364 Ten-Tec Delta II 0.1 Untested
+ 365 Icom IC-92D 0.7 Untested
+ 366 Icom IC-R9500 0.7.1 Untested
+ 367 Icom IC-7410 0.7 Untested
+ 368 Icom IC-9100 0.7 Untested
+ 369 Icom IC-RX7 0.7 Untested
+1601 Ten-Tec TT-550 0.2 Beta
+1602 Ten-Tec TT-538 Jupiter 0.6 Beta
+1603 Ten-Tec RX-320 0.6 Stable
+1604 Ten-Tec RX-340 0.3 Untested
+1605 Ten-Tec RX-350 0.1 Untested
+1607 Ten-Tec TT-516 Argonaut V 0.2 Stable
+1608 Ten-Tec TT-565 Orion 0.5 Beta
+1609 Ten-Tec TT-585 Paragon 0.3 Beta
+1611 Ten-Tec TT-588 Omni VII 0.3 Alpha
+1612 Ten-Tec RX-331 0.1 Beta
+1613 Ten-Tec TT-599 Eagle 0.4 Untested
+2301 Flex-radio SDR-1000 0.2 Untested
+2303 DTTS Microwave Society DttSP IPC 0.2 Alpha
+2304 DTTS Microwave Society DttSP UDP 0.2 Alpha
+2501 Elektor Elektor 3/04 0.4 Stable
+2502 SAT-Schneider DRT1 0.2 Beta
+2503 Coding Technologies Digital World Traveller 0.1.1 Stable
+2506 AmQRP DDS-60 0.1 Alpha
+2507 Elektor Elektor SDR-USB 0.3.1 Stable
+2508 mRS miniVNA 0.1 Alpha
+2509 SoftRock Si570 AVR-USB 0.2 Beta
+2511 KTH-SDR kit Si570 PIC-USB 0.2 Beta
+2512 FiFi FiFi-SDR 0.5 Beta
+2513 AMSAT-UK FUNcube Dongle 0.2 Beta
+2514 N2ADR HiQSDR 0.1 Untested
diff --git a/hash.f90 b/hash.f90
new file mode 100644
index 0000000..007d470
--- /dev/null
+++ b/hash.f90
@@ -0,0 +1,40 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: hash.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine hash(string,len,ihash)
+
+ parameter (MASK15=32767)
+ character*(*) string
+ integer*1 ic(12)
+
+ do i=1,len
+ ic(i)=ichar(string(i:i))
+ enddo
+ i=nhash(ic,len,146)
+ ihash=iand(i,MASK15)
+
+! print*,'C',ihash,len,string
+ return
+end subroutine hash
diff --git a/hftoa.f90 b/hftoa.f90
new file mode 100644
index 0000000..493cc83
--- /dev/null
+++ b/hftoa.f90
@@ -0,0 +1,155 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: hftoa.f90
+! Description: Record sound card data for HF Time-of-Arrival
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program hftoa
+
+! Record soundcard data for HF Time-of-Arrival project.
+
+ parameter (NMAX=300*48000) !Max length of data
+ integer*2 id1(NMAX) !Raw data, 48 kHz
+ integer*2 id2(NMAX/4) !Downsampled data, 12 kHz
+ character arg*12 !Command-line arg
+ character label*7 !Label for filename
+ character cdate*8 !CCYYMMDD
+ character ctime*10 !HHMMSS.SSS
+ character start_time*4 !Requested start time (HHMM)
+ character outfile*40 !Output filename
+ character cmnd*120 !Command to set rig frequency
+ character*4 mode
+ character*6 mycall,mygrid
+ real*8 fkhz,tsec
+ integer soundin
+
+ nargs=iargc()
+ if(nargs.ne.1 .and. nargs.ne.4) then
+ print*,'Usage: hftoa <f_kHz> <mode> <nsec> <tstart>'
+ print*,'Example: hftoa 3990 AM 300 2100'
+ go to 999
+ endif
+
+ call getarg(1,arg)
+ if(arg(:2).eq.'-v') then
+ print*,'Version 1.00'
+ go to 999
+ else
+ read(arg,*) fkhz !Rx frequency (kHz)
+ call getarg(2,mode) !Rx mode, e.g. AM, USB, LSB
+ call getarg(3,arg)
+ read(arg,*) nsec !Duration of recording (s)
+ call getarg(4,arg)
+ read(arg,*) start_time !Start time (HHMM)
+ endif
+
+ nfsample=48000
+ ndown=4
+
+ open(10,file='fmt.ini',status='old',err=910)
+ read(10,'(a120)') cmnd !Get rigctl command to set frequency
+ read(10,*) ndevin
+ read(10,*) mycall
+ read(10,*) mygrid
+ close(10)
+
+ if(cmnd(:6).eq.'rigctl') then
+ nHz=nint(1000.d0*fkhz)
+ i1=index(cmnd,' F ')
+ write(cmnd(i1+2:),*) nHz !Insert desired frequency
+ iret=system(cmnd) !Set Rx frequency
+ if(iret.ne.0) then
+ print*,'Error executing rigctl command to set frequency:'
+ print*,cmnd
+ go to 999
+ endif
+
+ if(mode.eq.'am ') mode='AM '
+ if(mode.eq.'usb ') mode='USB '
+ if(mode.eq.'lsb ') mode='LSB '
+ cmnd(i1+1:)='M '//mode//' 0'
+ iret=system(cmnd) !Set Rx mode
+ if(iret.ne.0) then
+ print*,'Error executing rigctl command to set Rx mode:'
+ print*,cmnd
+ go to 999
+ endif
+ endif
+
+ nchan=1
+
+ call soundinit !Initialize Portaudio
+
+ call getutc(cdate,ctime,tsec)
+ if(start_time(1:1).ne.'-') then
+ do while (ctime(1:4).ne.start_time)
+ call getutc(cdate,ctime,tsec)
+ call msleep(100)
+ enddo
+ endif
+
+ label=mycall
+ label(7:7)=' '
+ i1=index(label,' ')
+ outfile=label(1:i1-1)//'_'//cdate(3:8)//'_'//ctime(:6)//'.wav'
+ open(12,file=outfile,access='stream',status='unknown')
+ write(*,1010) cdate,ctime
+1010 format('UTC start time: ',a8,1x,a10)
+
+ npts=nfsample*nsec
+ ierr=soundin(ndevin,nfsample,id1,npts,nchan-1) !Get audio data
+ if(ierr.ne.0) then
+ print*,'Error in soundin',ierr
+ stop
+ endif
+
+ if(ndown.eq.4) then
+ call fil1(id1,npts,id2,ntot) !Downsample by 1/4
+ nfsample=nfsample/4
+ endif
+
+ call write_wav(12,id2,ntot,nfsample,nchan) !Write wav file to disk
+ write(12) tsec,fkhz,mycall,mygrid,mode,ctime,cdate !Append header info
+
+ sum=0.
+ xmax1=0.
+ do i=1,ntot
+ x=id2(i)
+ sum=sum + x
+ xmax1=max(xmax1,abs(x))
+ enddo
+ ave1=sum/ntot
+ sq=0.
+ do i=1,ntot
+ x=id2(i)-ave1
+ sq=sq + x*x
+ enddo
+ rms1=sqrt(sq/(ntot-1))
+
+ write(*,1100) ave1,rms1,xmax1
+1100 format('Ave:',f8.1,' Rms:',f8.1,' Max:',f8.1)
+ go to 999
+
+910 print*,'Cannot open file: fmt.ini'
+
+999 end program hftoa
+
diff --git a/inter_mept.f90 b/inter_mept.f90
new file mode 100644
index 0000000..d47d984
--- /dev/null
+++ b/inter_mept.f90
@@ -0,0 +1,70 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: inter_mept.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine inter_mept(id,ndir)
+
+! Interleave (ndir=1) or de-interleave (ndir=-1) the array id.
+
+ integer*1 id(0:161),itmp(0:161)
+ integer j0(0:161)
+ logical first
+ data first/.true./
+ save
+
+ if(first) then
+! Compute the interleave table using bit reversal.
+ k=-1
+ do i=0,255
+ n=0
+ ii=i
+ do j=0,7
+ n=n+n
+ if(iand(ii,1).ne.0) n=n+1
+ ii=ii/2
+ enddo
+ if(n.le.161) then
+ k=k+1
+ j0(k)=n
+ endif
+ enddo
+ first=.false.
+ endif
+
+ if(ndir.eq.1) then
+ do i=0,161
+ itmp(j0(i))=id(i)
+ enddo
+ else
+ do i=0,161
+ itmp(i)=id(j0(i))
+ enddo
+ endif
+
+ do i=0,161
+ id(i)=itmp(i)
+ enddo
+
+ return
+end subroutine inter_mept
diff --git a/iqdemod.f90 b/iqdemod.f90
new file mode 100644
index 0000000..9a80e66
--- /dev/null
+++ b/iqdemod.f90
@@ -0,0 +1,114 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: iqdemod.f90
+! Description: Convert I/Q data sampled at 48000 Hz to real data sampled
+! at 12000 Hz.
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine iqdemod(kwave,npts,nfiq,nbfo,iqrx,iqrxapp,gain,phase,iwave)
+
+! Convert I/Q data sampled at 48000 Hz to real data sampled at 12000 Hz.
+
+ parameter (NFFT =5760000)
+ parameter (NFFT4=1440000)
+ integer*2 kwave(2,114*48000)
+ integer*2 iwave(114*12000)
+ real*8 df,f0,sq
+ real x1(NFFT4)
+ complex c,c1
+ complex h,u,v
+ common/fftcom/ c(0:NFFT-1),c1(0:NFFT4-1)
+ equivalence (x1,c1)
+
+ df=48000.d0/NFFT
+ f0=nfiq
+ do i=1,npts
+ if(iqrx.eq.0) then
+ x=kwave(2,i)
+ y=kwave(1,i)
+ else
+ x=kwave(1,i)
+ y=kwave(2,i)
+ endif
+ c(i-1)=cmplx(x,y)
+ enddo
+ c(npts:)=0.
+
+ call four2a(c,NFFT,1,-1,1) !Long FFT of entire I/Q dataset
+
+ ia=nint(f0/df)
+ ib=nint((f0+3000.d0)/df)
+ j=-1
+ fac=1.0/NFFT
+
+ h=gain*cmplx(cos(phase),sin(phase))
+ if(iqrxapp.eq.0) then
+ do i=ia,ib
+ j=j+1
+ k=i
+ if(k.lt.0) k=k+nfft
+ c1(j)=fac*c(k)
+ enddo
+ else
+ do i=ia,ib
+ j=j+1
+ k=i
+ if(k.lt.0) k=k+nfft
+ u=fac*c(k)
+ v=fac*c(nfft-k)
+ x=real(u) + real(v) - (aimag(u) + aimag(v))*aimag(h) + &
+ (real(u) - real(v))*real(h)
+ y=aimag(u) - aimag(v) + (aimag(u) + aimag(v))*real(h) + &
+ (real(u) - real(v))*aimag(h)
+ c1(j)=cmplx(x,y)
+ enddo
+ endif
+
+ c1(j+1:)=0.
+ c1(0)=0.
+
+ bw=3000.0
+ if(bw.lt.3000.0) then
+ ja=nint((nbfo-0.5*bw))/df
+ jb=nint((nbfo+0.5*bw))/df
+ c1(:ja-1)=0.
+ c1(jb+1:)=0.
+ endif
+
+ call four2a(c1,NFFT4,1,1,-1)
+
+ sq=0.
+ do i=1,npts/4
+ sq=sq + x1(i)**2
+ enddo
+ rms=sqrt(sq/(npts/4.0))
+
+ fac=3000.0/rms
+ do i=1,npts/4
+ r=fac*x1(i)
+ if(r.gt. 32767.0) r= 32767.0
+ if(r.lt.-32767.0) r=-32767.0
+ iwave(i)=nint(r)
+ enddo
+
+ return
+end subroutine iqdemod
diff --git a/loggit.f90 b/loggit.f90
new file mode 100644
index 0000000..a5df2a0
--- /dev/null
+++ b/loggit.f90
@@ -0,0 +1,44 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: loggit.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine loggit(msg)
+ character*(*) msg
+ character*20 m20
+ real*8 tsec1,trseconds
+ integer nt(9)
+ include 'acom1.f90'
+
+ call cs_lock('loggit')
+ call gmtime2(nt,tsec1)
+ trseconds=60*ntrminutes
+ sectr=mod(tsec1,trseconds)
+ m20=msg//' '
+ write(19,1000) cdate(3:8),utctime(1:2),utctime(3:4),utctime(5:10),sectr,m20
+1000 format(a6,1x,a2,':',a2,':',a5,f8.2,2x,a20)
+ call flush(19)
+ call cs_unlock
+
+ return
+end subroutine loggit
diff --git a/manpages/man1/fcal.1 b/manpages/man1/fcal.1
new file mode 100644
index 0000000..b9abbe0
--- /dev/null
+++ b/manpages/man1/fcal.1
@@ -0,0 +1,74 @@
+'\" t
+.\" Title: fcal
+.\" Author: [see the "AUTHORS" section]
+.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
+.\" Date: 05/24/2014
+.\" Manual: Fcal Man Page
+.\" Source: \ \& Version 4.0
+.\" Language: English
+.\"
+.TH "FCAL" "1" "05/24/2014" "\ \& Version 4\&.0" "Fcal Man Page"
+.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+fcal \- Calculates a best\-fit straight line for a data saved in fmtave\&.out\&. Results are saved in file fcal\&.out\&.
+.SH "SYNOPSIS"
+.sp
+\fBfmtave\fR [file]
+.SH "BUGS"
+.sp
+If you find a bug or suspect \fB\fIfcal\fR\fR is not acting as you think it should, send an email with as much detail as possible to: <wsjt\-devel at lists\&.sourceforge\&.net>
+.SH "AUTHORS"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Joe Taylor, K1JT, <joe at princeton\&.edu> \&.\&. Original Content Provider
+Greg Beam, KI7MT, <ki7mt at yahoo\&.com> \&.\&.\&.\&. Manpage Editor
+.fi
+.if n \{\
+.RE
+.\}
+.SH "RESOURCES"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Project Site: \&.\&.\&. <http://sourceforge\&.net/projects/wsjt/>
+Main web site: \&.\&. <http://www\&.physics\&.princeton\&.edu/pulsar/K1JT/>
+Documentation: \&.\&. <http://physics\&.princeton\&.edu/pulsar/K1JT/fmt\-main\&.html>
+.fi
+.if n \{\
+.RE
+.\}
+.SH "SEE ALSO"
+.sp
+wspr(1), fmtest(1), fmeasure(1), fcal(1)
+.SH "COPYING"
+.sp
+Copyright \(co 2014 Joseph H Taylor, Jr, K1JT
+.sp
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version\&.
+.sp
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\&. See the GNU General Public License for more details\&.
+.sp
+GPL version 3 or later http://gnu\&.org/licenses/gpl\&.html\&.
diff --git a/manpages/man1/fmeasure.1 b/manpages/man1/fmeasure.1
new file mode 100644
index 0000000..9e73d9a
--- /dev/null
+++ b/manpages/man1/fmeasure.1
@@ -0,0 +1,74 @@
+'\" t
+.\" Title: fmeasure
+.\" Author: [see the "AUTHORS" section]
+.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
+.\" Date: 05/24/2014
+.\" Manual: Fmeasure Man Page
+.\" Source: \ \& Version 4.0
+.\" Language: English
+.\"
+.TH "FMEASURE" "1" "05/24/2014" "\ \& Version 4\&.0" "Fmeasure Man Page"
+.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+fmeasure \- Calculates the properly calibrated frequency of each test signal found in file fmtave\&.out\&. Results are saved in file fmeasure\&.out, and these are the numbers you should report if you are entering the Frequency Measuring Test\&.
+.SH "SYNOPSIS"
+.sp
+\fBfmeasure\fR [file]
+.SH "BUGS"
+.sp
+If you find a bug or suspect \fB\fIfmeasure\fR\fR is not acting as you think it should, send an email with as much detail as possible to: <wsjt\-devel at lists\&.sourceforge\&.net>
+.SH "AUTHORS"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Joe Taylor, K1JT, <joe at princeton\&.edu> \&.\&. Original Content Provider
+Greg Beam, KI7MT, <ki7mt at yahoo\&.com> \&.\&.\&.\&. Manpage Editor
+.fi
+.if n \{\
+.RE
+.\}
+.SH "RESOURCES"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Project Site: \&.\&.\&. <http://sourceforge\&.net/projects/wsjt/>
+Main web site: \&.\&. <http://www\&.physics\&.princeton\&.edu/pulsar/K1JT/>
+Documentation: \&.\&. <http://physics\&.princeton\&.edu/pulsar/K1JT/fmt\-main\&.html>
+.fi
+.if n \{\
+.RE
+.\}
+.SH "SEE ALSO"
+.sp
+wspr(1), fmtest(1), fmtave(1), fcal(1)
+.SH "COPYING"
+.sp
+Copyright \(co 2014 Joseph H Taylor, Jr, K1JT
+.sp
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version\&.
+.sp
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\&. See the GNU General Public License for more details\&.
+.sp
+GPL version 3 or later http://gnu\&.org/licenses/gpl\&.html\&.
diff --git a/manpages/man1/fmtave.1 b/manpages/man1/fmtave.1
new file mode 100644
index 0000000..7b6e0ec
--- /dev/null
+++ b/manpages/man1/fmtave.1
@@ -0,0 +1,74 @@
+'\" t
+.\" Title: fmtave
+.\" Author: [see the "AUTHORS" section]
+.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
+.\" Date: 05/24/2014
+.\" Manual: FMTave Man Page
+.\" Source: \ \& Version 4.0
+.\" Language: English
+.\"
+.TH "FMTAVE" "1" "05/24/2014" "\ \& Version 4\&.0" "FMTave Man Page"
+.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+fmtave \- Averages data found in a specified file having the format of fmt\&.all
+.SH "SYNOPSIS"
+.sp
+\fBfmtave\fR [file]
+.SH "BUGS"
+.sp
+If you find a bug or suspect \fB\fIfmtave\fR\fR is not acting as you think it should, send an email with as much detail as possible to: <wsjt\-devel at lists\&.sourceforge\&.net>
+.SH "AUTHORS"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Joe Taylor, K1JT, <joe at princeton\&.edu> \&.\&. Original Content Provider
+Greg Beam, KI7MT, <ki7mt at yahoo\&.com> \&.\&.\&.\&. Manpage Editor
+.fi
+.if n \{\
+.RE
+.\}
+.SH "RESOURCES"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Project Site: \&.\&.\&. <http://sourceforge\&.net/projects/wsjt/>
+Main web site: \&.\&. <http://www\&.physics\&.princeton\&.edu/pulsar/K1JT/>
+Documentation: \&.\&. <http://physics\&.princeton\&.edu/pulsar/K1JT/fmt\-main\&.html>
+.fi
+.if n \{\
+.RE
+.\}
+.SH "SEE ALSO"
+.sp
+wspr(1), fmtest(1), fmeasure(1), fcal(1)
+.SH "COPYING"
+.sp
+Copyright \(co 2014 Joseph H Taylor, Jr, K1JT
+.sp
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version\&.
+.sp
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\&. See the GNU General Public License for more details\&.
+.sp
+GPL version 3 or later http://gnu\&.org/licenses/gpl\&.html\&.
diff --git a/manpages/man1/fmtest.1 b/manpages/man1/fmtest.1
new file mode 100644
index 0000000..2d9186d
--- /dev/null
+++ b/manpages/man1/fmtest.1
@@ -0,0 +1,116 @@
+'\" t
+.\" Title: fmtest
+.\" Author: [see the "AUTHORS" section]
+.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
+.\" Date: 05/24/2014
+.\" Manual: FMTest Man Page
+.\" Source: \ \& Version 4.0
+.\" Language: English
+.\"
+.TH "FMTEST" "1" "05/24/2014" "\ \& Version 4\&.0" "FMTest Man Page"
+.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+fmtest \- Frequency Measurement Testing
+.SH "SYNOPSIS"
+.sp
+\fBfmtest\fR [kHz] [0|1] [offset] [range] [tsec] [call]
+.sp
+Sets the dial frequency of a CAT\-controlled radio and performs a sequence of measurements of the strongest resulting audio tone near a specified offset frequency\&. Input parameters are taken from the command line, or specified in gocal script, and the output goes to files fmt\&.out\&. All 6 parameters are required\&. See \fBResources\fR for full documentaton and examples\&.
+.SH "DESCRIPTION"
+.PP
+\fBkhz\fR
+.RS 4
+Dial frequency in Kilohertz
+.RE
+.PP
+\fB0|1\fR
+.RS 4
+\fB0\fR
+for measuring unknown frequencies,
+\fB1\fR
+for known frequency\&. For rig calibration
+\fB1\fR
+should be used, as
+\fB0\fR
+is for the use with the
+\fBARRL FMT\fR
+tests\&.
+.RE
+.PP
+\fBoffset\fR
+.RS 4
+Frequency offset to measure, in khz
+.RE
+.PP
+\fBrange\fR
+.RS 4
+Range of frequency measurement, in khz
+.RE
+.PP
+\fBtsec\fR
+.RS 4
+Length of time to measure, in seconds
+.RE
+.PP
+\fBcall\fR
+.RS 4
+Station callsign, WWV for example
+.RE
+.SH "BUGS"
+.sp
+If you find a bug or suspect \fB\fIfmtest\fR\fR is not acting as you think it should, send an email with as much detail as possible to: <wsjt\-devel at lists\&.sourceforge\&.net>
+.SH "AUTHORS"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Joe Taylor, K1JT, <joe at princeton\&.edu> \&.\&. Original Content Provider
+Greg Beam, KI7MT, <ki7mt at yahoo\&.com> \&.\&.\&.\&. Manpage Editor
+.fi
+.if n \{\
+.RE
+.\}
+.SH "RESOURCES"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Project Site: \&.\&.\&. <http://sourceforge\&.net/projects/wsjt/>
+Main web site: \&.\&. <http://www\&.physics\&.princeton\&.edu/pulsar/K1JT/>
+Documentation: \&.\&. <http://physics\&.princeton\&.edu/pulsar/K1JT/fmt\-main\&.html>
+.fi
+.if n \{\
+.RE
+.\}
+.SH "SEE ALSO"
+.sp
+wspr(1), fmtave(1), fmeasure(1), fcal(1)
+.SH "COPYING"
+.sp
+Copyright \(co 2014 Joseph H Taylor, Jr, K1JT
+.sp
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version\&.
+.sp
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\&. See the GNU General Public License for more details\&.
+.sp
+GPL version 3 or later http://gnu\&.org/licenses/gpl\&.html\&.
diff --git a/manpages/man1/wspr.1 b/manpages/man1/wspr.1
new file mode 100644
index 0000000..aa44bcd
--- /dev/null
+++ b/manpages/man1/wspr.1
@@ -0,0 +1,95 @@
+'\" t
+.\" Title: wspr
+.\" Author: [see the "AUTHORS" section]
+.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
+.\" Date: 06/04/2014
+.\" Manual: WSPR Man Page
+.\" Source: \ \& Version 4.0
+.\" Language: English
+.\"
+.TH "WSPR" "1" "06/04/2014" "\ \& Version 4\&.0" "WSPR Man Page"
+.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+wspr \- Weak Signal Propagation Reporter
+.SH "SYNOPSIS"
+.sp
+\fBwspr\fR \- "Pronounced Whisper", does not take command\-line options\&. To run \fBwspr\fR from a terminal:
+.sp
+.nf
+Open terminal, type\&.\&.: wspr
+.fi
+.SH "PROGRAMS"
+.sp
+There are (7) programs associated with \fB\fIwspr\fR\fR
+.sp
+\fBwspr\fR \- The program generates and decodes a digital soundcard mode optimized for beacon\-like transmissions on the LF, MF, and HF bands
+.sp
+\fBwspr0\fR \- is a simple command\-line version of WSPR with no frills, no graphics and no GUI\&. Many advanced features of the full \fB\fIwspr\fR\fR program, including I/Q mode, frequency hopping, and automatic uploading of spots to WSPRnet\&.org, are not included\&. \fB\fIwspr0\fR\fR is intended for specialized uses where its easy compilation, small memory requirements, etc\&., can be advantageous\&.
+.sp
+\fBfmtest\fR \- Sets the dial frequency of a CAT\-controlled radio and performs a sequence of measurements for the strongest resulting audio tone near a specified offset frequency\&. Input parameters are taken from the command line, and output goes to files fmt\&.out and fmt\&.all\&. The latter file is cumulative\&.
+.sp
+\fBfmtave\fR \- Averages data found in a specified file having the format of fmt\&.all
+.sp
+\fBfcal\fR \- Calculates a best\-fit straight line for a data saved in fmtave\&.out\&. Results are saved in file fcal\&.out\&.
+.sp
+\fBfmeasure\fR \- Calculates the properly calibrated frequency of each test signal found in file fmtave\&.out\&. Results are saved in file fmeasure\&.out, and these are the numbers you should report if you are entering the Frequency Measuring Test\&.
+.sp
+\fBwsprcode\fR \- This program provides examples of the source encoding, convolutional error\-control coding, bit and symbol ordering, and synchronizing information contained in \fB\fIwspr\fR\fR messages\&.
+.SH "BUGS"
+.sp
+If you find a bug or suspect \fB\fIwspr\fR\fR is not acting as you think it should, send an email with as much detail as possible to: <wsjt\-devel at lists\&.sourceforge\&.net>
+.SH "AUTHORS"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Joe Taylor, K1JT, <joe at princeton\&.edu> \&.\&. Original Content Provider
+Greg Beam, KI7MT, <ki7mt at yahoo\&.com> \&.\&.\&.\&. Manpage Editor
+.fi
+.if n \{\
+.RE
+.\}
+.SH "RESOURCES"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Project Site: \&.\&.\&. <http://sourceforge\&.net/projects/wsjt/>
+Main web site: \&.\&. <http://www\&.physics\&.princeton\&.edu/pulsar/K1JT/>
+Documentation: \&.\&. <http://www\&.physics\&.princeton\&.edu/pulsar/K1JT/wspr\-main\&.html>
+.fi
+.if n \{\
+.RE
+.\}
+.SH "SEE ALSO"
+.sp
+wspr0(1), fmtest(1), wsprcode(1)
+.SH "COPYING"
+.sp
+Copyright \(co 2014 Joseph H Taylor, Jr, K1JT
+.sp
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version\&.
+.sp
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\&. See the GNU General Public License for more details\&.
+.sp
+GPL version 3 or later http://gnu\&.org/licenses/gpl\&.html\&.
diff --git a/manpages/man1/wspr0.1 b/manpages/man1/wspr0.1
new file mode 100644
index 0000000..65c2b51
--- /dev/null
+++ b/manpages/man1/wspr0.1
@@ -0,0 +1,183 @@
+'\" t
+.\" Title: wspr0
+.\" Author: [see the "AUTHORS" section]
+.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
+.\" Date: 05/24/2014
+.\" Manual: WSPR0 Man Page
+.\" Source: AsciiDoc 1.0
+.\" Language: English
+.\"
+.TH "WSPR0" "1" "05/24/2014" "AsciiDoc 1\&.0" "WSPR0 Man Page"
+.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+wspr0 \- limited command\-line WSPR
+.SH "SYNOPSIS"
+.sp
+\fBwspr0\fR [OPTIONS]
+.SH "DESCRIPTION"
+.sp
+\fI\fBwspr0\fR\fR \- is a simple command\-line version of \fB\fIWSPR\fR\fR with no frills, no graphics and no GUI\&. Many advanced features of the full WSPR program, including I/Q mode, frequency hopping, and automatic uploading of spots to <www\&.wsprnet\&.org>, are not included\&. \fBwspr0\fR is intended for specialized uses where its easy compilation, small memory requirements can be advantageous\&.
+.SH "OPTIONS"
+.sp
+By default, the \fIcallsign\fR, \fIgrid locater\fR and \fIpower\-level\fR for the transmitted message are taken from file \fBwspr0\&.def\fR\&. These may be overridden by using the following options:
+.PP
+\fB\-a\fR x
+.RS 4
+Audio frequency of transmission is x (Hz)
+.RE
+.PP
+\fB\-b\fR
+.RS 4
+Pseudo\-random selection of Rx and Tx cycles\&.
+.RE
+.PP
+\fB\-c\fR call
+.RS 4
+Your stations call sign
+.RE
+.PP
+\fB\-D\fR file1\&.wav file1\&.wav
+.RS 4
+Open and decode one or more wav files\&.
+.RE
+.PP
+\fB\-d\fR dBm
+.RS 4
+Your transmit power, in dbm
+.RE
+.PP
+\fB\-F\fR x
+.RS 4
+Center frequency of transmission is x (MHz)
+.RE
+.PP
+\fB\-f\fR x
+.RS 4
+Transceiver dial frequency is x (MHz)
+.RE
+.PP
+\fB\-g\fR grid
+.RS 4
+4 Digit grid location
+.RE
+.PP
+\fB\-m\fR
+.RS 4
+Run in WSPR\-15 mode (default is WSPR\-2)
+.RE
+.PP
+\fB\-n\fR n
+.RS 4
+Number of files to be generated
+.RE
+.PP
+\fB\-o\fR outfile
+.RS 4
+Output filename overrides default nnnnnn\&.
+.RE
+.PP
+\fB\-p\fR n
+.RS 4
+PTT port
+.RE
+.PP
+\fB\-P\fR n
+.RS 4
+Transmitting percent (default=25)
+.RE
+.PP
+\fB\-s\fR x
+.RS 4
+SNR of generated data, dB (default 100)
+.RE
+.PP
+\fB\-t\fR
+.RS 4
+Run in 100% Tx mode\&. (Default is Rx mode\&.)
+.RE
+.PP
+\fB\-x\fR
+.RS 4
+Generate test file(s) with 10 signals in each
+.RE
+.PP
+\fB\-X\fR
+.RS 4
+Generate list of audio tones for this message
+.RE
+.SH "EXAMPLES"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+wspr0 \-t # Transmit default message
+wspr0 \-t \-s \-22 \-o test\&.wav # Generate a test file
+wspr0 \-t \-s \-25 \-n 3 # Generate three test files
+wspr0 \-b # Randomized T/R sequences
+wspr0 \-f 14\&.0956 # Rx only, on 20m::
+wspr0 \-D 00001\&.wav 00002\&.wav # Decode these two files
+.fi
+.if n \{\
+.RE
+.\}
+.SH "BUGS"
+.sp
+If find a bug or suspect \fB\fIwpsr0\fR\fR is not acting as you think it should, send an email with as much detail as possible to: <wsjt\-devel at lists\&.sourceforge\&.net>
+.SH "AUTHORS"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Joe Taylor, K1JT, <joe at princeton\&.edu> \&.\&. Original Content Provider
+Greg Beam, KI7MT, <ki7mt at yahoo\&.com> \&.\&.\&.\&. Manpage Editor
+.fi
+.if n \{\
+.RE
+.\}
+.SH "RESOURCES"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Project Site: \&.\&.\&. <http://sourceforge\&.net/projects/wsjt/>
+Main web site: \&.\&. <http://www\&.physics\&.princeton\&.edu/pulsar/K1JT/>
+
+For more information see:
+<www\&.physics\&.princeton\&.edu/pulsar/K1JT/WSPR0_Instructions\&.TXT>
+.fi
+.if n \{\
+.RE
+.\}
+.SH "SEE ALSO"
+.sp
+wspr(1), wspr\-fmt(1), wspr\-fmtave(1), wspr\-fcal(1), wspr\-fmeasure(1)
+.SH "COPYING"
+.sp
+Copyright \(co 2014 Joseph H Taylor, Jr, K1JT
+.sp
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version\&.
+.sp
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\&. See the GNU General Public License for more details\&.
+.sp
+GPL version 3 or later http://gnu\&.org/licenses/gpl\&.html\&.
diff --git a/manpages/man1/wsprcode.1 b/manpages/man1/wsprcode.1
new file mode 100644
index 0000000..9ce9ead
--- /dev/null
+++ b/manpages/man1/wsprcode.1
@@ -0,0 +1,74 @@
+'\" t
+.\" Title: wsprcode
+.\" Author: [see the "AUTHORS" section]
+.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
+.\" Date: 05/26/2014
+.\" Manual: WSPRcode Man Page
+.\" Source: \ \& Version 4.0
+.\" Language: English
+.\"
+.TH "WSPRCODE" "1" "05/26/2014" "\ \& Version 4\&.0" "WSPRcode Man Page"
+.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+wsprcode \- This program provides examples of the source encoding, convolutional error\-control coding, bit and symbol ordering, and synchronizing information contained in WSPR messages\&.
+.SH "USAGE"
+.sp
+\fBwsprcode\fR "message"
+.SH "BUGS"
+.sp
+If you find a bug or suspect \fB\fIwsprcode\fR\fR is not acting as you think it should, send an email with as much detail as possible to: <wsjt\-devel at lists\&.sourceforge\&.net>
+.SH "AUTHORS"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Joe Taylor, K1JT, <joe at princeton\&.edu> \&.\&. Original Content Provider
+Greg Beam, KI7MT, <ki7mt at yahoo\&.com> \&.\&.\&.\&. Manpage Editor
+.fi
+.if n \{\
+.RE
+.\}
+.SH "RESOURCES"
+.sp
+.if n \{\
+.RS 4
+.\}
+.nf
+Project Site: \&.\&.\&. <http://sourceforge\&.net/projects/wsjt/>
+Main web site: \&.\&. <http://www\&.physics\&.princeton\&.edu/pulsar/K1JT/>
+Documentation: \&.\&. <http://physics\&.princeton\&.edu/pulsar/K1JT/fmt\-main\&.html>
+.fi
+.if n \{\
+.RE
+.\}
+.SH "SEE ALSO"
+.sp
+wspr(1), wspr0(1), fmtest(1), fmtave(1), fmeasure(1), fcal(1)
+.SH "COPYING"
+.sp
+Copyright \(co 2014 Joseph H Taylor, Jr, K1JT
+.sp
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version\&.
+.sp
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\&. See the GNU General Public License for more details\&.
+.sp
+GPL version 3 or later http://gnu\&.org/licenses/gpl\&.html\&.
diff --git a/mept162.f90 b/mept162.f90
new file mode 100644
index 0000000..de8d168
--- /dev/null
+++ b/mept162.f90
@@ -0,0 +1,147 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: mept162.f90
+! Description: Orchestrates the process of finding, synchronizing, and decoding
+! WSPR signals.
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine mept162(outfile,appdir,nappdir,f0,ncmdline,id,npts,nbfo,ierr)
+
+! Orchestrates the process of finding, synchronizing, and decoding
+! WSPR signals.
+
+ integer*2 id(npts)
+ character*22 message
+ character*80 outfile,appdir,alltxt
+ character*11 datetime
+ character cdate*8,ctime*10
+ real*8 f0,freq,tsec
+ real ps(-256:256)
+ real sstf(5,275)
+ real a(5)
+ complex c2(65536)
+ complex c3(45000),c4(45000)
+
+! WSPR-2: mix from nbfo +/- 100 Hz to baseband, downsample by 1/32
+! WSPR-15: mix from (nbfo+112.5) +/- 12.5 Hz to baseband, downsample by 1/256
+ call mix162(id,npts,nbfo,c2,jz,ps)
+ c2(jz+1:)=0.
+ jz=45000
+ if(ncmdline.eq.0) then
+ call spec162(c2,jz,appdir,nappdir) !Compute pixmap.dat
+ endif
+
+ call sync162(c2,jz,ps,sstf,kz) !Look for sync patterns, get DF and DT
+
+ ierr = 0
+ if(kz.eq.0) go to 900
+ if (kz.gt.275 .or. kz.lt.0) then
+ call getutc(cdate,ctime,tsec)
+
+ call cs_lock('mept162')
+ write(*,1000) ctime,kz
+1000 format('Time ',a8,'. Error from sync162: kz is',i10)
+ call cs_unlock
+
+ ierr = 1
+ return
+ endif
+ do k=1,kz
+ snrsync=sstf(1,k)
+ snrx=sstf(2,k)
+ dtx=sstf(3,k)
+ dfx=sstf(4,k)
+ drift=sstf(5,k)
+ a(1)=-dfx
+ a(2)=-0.5*drift
+ a(3)=0.
+ call twkfreq(c2,c3,jz,a) !Remove drift
+
+ minsync=1 !####
+ nsync=nint(snrsync)
+ if(nsync.lt.0) nsync=0
+ if(npts.le.120*12000) then
+ minsnr=-33
+ nsnrx=nint(snrx) !WSPR-2
+ if(nsnrx.lt.minsnr) nsnrx=minsnr
+ freq=f0 + 1.d-6*(dfx+nbfo)
+ else
+ minsnr=-42
+ nsnrx=nint(snrx-9.0) !WSPR-15
+ if(nsnrx.lt.minsnr) nsnrx=minsnr
+ dfx=dfx/8
+ freq=f0 + 1.d-6*(dfx+nbfo+112.5d0)
+ endif
+ message=' '
+ if(nsync.ge.minsync .and. nsnrx.ge.minsnr) then
+ dt=1.0/375
+ do idt=0,128
+ ii=(idt+1)/2
+ if(mod(idt,2).eq.1) ii=-ii
+ i1=nint((dtx+2.0)/dt) + ii !Start index for synced symbols
+ if(i1.ge.1) then
+! Fix this earlier!
+ c4(1:jz-i1+1)=c3(i1:)
+ c4(jz-i1+2:)=0.
+ else
+ c4(:-i1+1)=0.
+ c4(-i1+2:jz)=c3(:i1+jz-1)
+ if(jz.lt.45000) c4(jz:)=0.
+ endif
+ call decode162(c4,45000,message,ncycles,metric,nerr)
+ if(message(1:6).ne.' ' .and. &
+ message(1:6).ne.'000AAA' .and. &
+ index(message,'A000AA').le.0) go to 23
+ enddo
+ go to 24
+
+23 i2=index(outfile,'.wav')-1
+ if(i2.le.0) i2=index(outfile,'.WAV')-1
+ datetime=outfile(max(1,i2-10):i2)
+ datetime(7:7)=' '
+ nf1=nint(-a(2))
+ alltxt=appdir(:nappdir)//'/ALL_WSPR.TXT'
+ if(npts.gt.120*12000) dtx=8*(dtx + 1.8) !### The 1.8 is empirical ###
+
+ call cs_lock('mept162a')
+ if(ncmdline.eq.0) then
+ open(13,file=alltxt,status='unknown',position='append')
+ write(13,1010) datetime,nsync,nsnrx,dtx,freq,message,nf1, &
+ ncycles/81,ii
+ close(13)
+ else
+ write(*,1008) datetime(8:11),nsnrx,dtx,freq,message
+1008 format(a4,i4,f5.1,f11.6,2x,a22)
+ endif
+ write(14,1010) datetime,nsync,nsnrx,dtx,freq,message,nf1, &
+ ncycles/81,ii
+1010 format(a11,i4,i4,f5.1,f11.6,2x,a22,i3,i6,i5)
+ call flush(14)
+ i1=index(message,' ')
+ call cs_unlock
+
+ endif
+24 continue
+ enddo
+
+900 return
+end subroutine mept162
diff --git a/mix162.f90 b/mix162.f90
new file mode 100644
index 0000000..891c8ed
--- /dev/null
+++ b/mix162.f90
@@ -0,0 +1,86 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: mix162.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine mix162(id,npts,nbfo,c2,jz,ps)
+
+! Mix from "nbfo" +/- 100 Hz to baseband, and downsample by 1/32
+
+ parameter (NFFT1MAX=2*1024*1024)
+ parameter (NH1MAX=NFFT1MAX/2)
+ integer*2 id(npts)
+ real x(NFFT1MAX)
+ real ps(-256:256)
+ real*8 df,fbfo
+ complex c(0:NH1MAX)
+ complex c2(0:65535)
+ equivalence (x,c)
+
+ nfft1=2*1024*1024
+ nfft2=65536
+ nh2=nfft2/2
+ ndown=nfft1/nfft2
+
+! Load data into real array x; pad with zeros up to nfft.
+ fac=1.e-4
+ do i=1,npts
+ x(i)=fac*id(i)
+ enddo
+ x(npts+1:nfft1)=0.
+
+ call xfft(x,nfft1) !Do the real-to-complex FFT
+
+ df=12000.d0/nfft1
+ fbfo=nbfo
+ if(npts.gt.120*12000) fbfo=nbfo + 112.5d0
+ i0=nint(fbfo/df)
+ ia=i0-NH2 + 1
+ ib=i0+NH2
+
+ k=-257
+ do i=ia-64,ib,128
+ k=k+1
+ sq=0.
+ do n=0,127
+ sq=sq + real(c(i+n))**2 + aimag(c(i+n))**2
+ enddo
+ ps(k)=4.085e-8*sq
+ enddo
+
+ do i=0,NFFT2-1
+ j=i0 + i
+ if(i.gt.NH2) j=j-NFFT2
+ c2(i)=c(j)
+ enddo
+
+ call four2a(c2,NFFT2,1,1,1) !Return to time domain
+
+ fac=1.e-5
+ jz=npts/ndown
+ do i=0,jz-1
+ c2(i)=fac*c2(i)
+ enddo
+
+ return
+end subroutine mix162
diff --git a/morse.f90 b/morse.f90
new file mode 100644
index 0000000..6cd9b19
--- /dev/null
+++ b/morse.f90
@@ -0,0 +1,115 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: morse.f90
+! Description: Convert ASCII message to a Morse code bit string.
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine morse(msg,idat,n)
+
+! Convert ascii message to a Morse code bit string.
+! Dash = 3 dots
+! Space between dots, dashes = 1 dot
+! Space between letters = 3 dots
+! Space between words = 7 dots
+
+ character*22 msg
+ integer*1 idat(460)
+ integer*1 ic(21,38)
+ data ic/ &
+ 1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,20, &
+ 1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,0,0,18, &
+ 1,0,1,0,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,16, &
+ 1,0,1,0,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,14, &
+ 1,0,1,0,1,0,1,0,1,1,1,0,0,0,0,0,0,0,0,0,12, &
+ 1,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,10, &
+ 1,1,1,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,12, &
+ 1,1,1,0,1,1,1,0,1,0,1,0,1,0,0,0,0,0,0,0,14, &
+ 1,1,1,0,1,1,1,0,1,1,1,0,1,0,1,0,0,0,0,0,16, &
+ 1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,0,0,0,18, &
+ 1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 6, &
+ 1,1,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,10, &
+ 1,1,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,12, &
+ 1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, 8, &
+ 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 2, &
+ 1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,10, &
+ 1,1,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,10, &
+ 1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, 8, &
+ 1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 4, &
+ 1,0,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,14, &
+ 1,1,1,0,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,10, &
+ 1,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,10, &
+ 1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0, 8, &
+ 1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 6, &
+ 1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,12, &
+ 1,0,1,1,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,12, &
+ 1,1,1,0,1,1,1,0,1,0,1,1,1,0,0,0,0,0,0,0,14, &
+ 1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, 8, &
+ 1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 6, &
+ 1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 4, &
+ 1,0,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0, 8, &
+ 1,0,1,0,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,10, &
+ 1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,10, &
+ 1,1,1,0,1,0,1,0,1,1,1,0,0,0,0,0,0,0,0,0,12, &
+ 1,1,1,0,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,14, &
+ 1,1,1,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,12, &
+ 1,1,1,0,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,14, &
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 2/ !Incremental word space
+ save
+
+! Find length of message
+ do i=22,1,-1
+ if(msg(i:i).ne.' ') go to 1
+ enddo
+1 msglen=i
+
+ n=0
+ do k=1,msglen
+ jj=ichar(msg(k:k))
+ if(jj.ge.97 .and. jj.le.122) jj=jj-32 !Convert lower to upper case
+ if(jj.ge.48 .and. jj.le.57) j=jj-48 !Numbers
+ if(jj.ge.65 .and. jj.le.90) j=jj-55 !Letters
+ if(jj.eq.47) j=36 !Slash (/)
+ if(jj.eq.32) j=37 !Word space
+ j=j+1
+
+! Insert this character
+ nmax=ic(21,j)
+ do i=1,nmax
+ n=n+1
+ idat(n)=ic(i,j)
+ enddo
+
+! Insert character space of 2 dit lengths:
+ n=n+1
+ idat(n)=0
+ n=n+1
+ idat(n)=0
+ enddo
+
+! Insert word space at end of message
+ do j=1,4
+ n=n+1
+ idat(n)=0
+ enddo
+
+ return
+end subroutine morse
diff --git a/msgtrim.f90 b/msgtrim.f90
new file mode 100644
index 0000000..287a68d
--- /dev/null
+++ b/msgtrim.f90
@@ -0,0 +1,68 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: msgtrim.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine msgtrim(msg,msglen)
+
+ character*24 msg
+
+! Remove leading blanks
+ do i=1,24
+ if(msg(1:1).ne.' ') go to 2
+ msg=msg(2:)
+ enddo
+ go to 800 !Error return
+
+2 do i=24,1,-1
+ if(msg(i:i).ne.' ') go to 3
+ enddo
+ go to 800
+3 iz=i
+
+! Collapse multiple blanks to one
+ ib2=index(msg,' ')
+ if(ib2.eq.0 .or. ib2.eq.iz+1) go to 10
+ msg=msg(:ib2-1)//msg(ib2+1:)
+ iz=iz-1
+ go to 2
+
+! Convert letters to upper case
+10 do i=1,22
+ if(msg(i:i).ge.'a' .and. msg(i:i).le.'z') &
+ msg(i:i)= char(ichar(msg(i:i))+ichar('A')-ichar('a'))
+ enddo
+
+ do i=24,1,-1
+ if(msg(i:i).ne.' ') go to 20
+ enddo
+ go to 800 !Error return
+
+20 msglen=i
+ go to 999
+
+800 continue
+! print*,'Error in msgtrim: ',msg
+
+999 return
+end subroutine msgtrim
diff --git a/nchar.f90 b/nchar.f90
new file mode 100644
index 0000000..3612109
--- /dev/null
+++ b/nchar.f90
@@ -0,0 +1,48 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: nchar.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+function nchar(c)
+
+! Convert ASCII number, letter, or space to 0-36 for callsign packing.
+
+ character c*1
+ data n/0/ !Silence compiler warning
+
+ if(c.ge.'0' .and. c.le.'9') then
+ n=ichar(c)-ichar('0')
+ else if(c.ge.'A' .and. c.le.'Z') then
+ n=ichar(c)-ichar('A') + 10
+ else if(c.ge.'a' .and. c.le.'z') then
+ n=ichar(c)-ichar('a') + 10
+ else if(c.ge.' ') then
+ n=36
+ else
+ Print*,'Invalid character in callsign ',c,' ',ichar(c)
+ stop
+ endif
+ nchar=n
+
+ return
+end function nchar
diff --git a/nhash.c b/nhash.c
new file mode 100644
index 0000000..aecdeea
--- /dev/null
+++ b/nhash.c
@@ -0,0 +1,384 @@
+/*
+ *-------------------------------------------------------------------------------
+ *
+ * This file is part of the WSPR application, Weak Signal Propagation Reporter
+ *
+ * File Name: nhash.c
+ * Description: Functions to produce 32-bit hashes for hash table lookup
+ *
+ * Copyright (C) 2008-2014 Joseph Taylor, K1JT
+ * License: GPL-3
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+ * Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Files: lookup3.c
+ * Copyright: Copyright (C) 2006 Bob Jenkins <bob_jenkins at burtleburtle.net>
+ * License: public-domain
+ * You may use this code any way you wish, private, educational, or commercial.
+ * It's free.
+ *
+ *-------------------------------------------------------------------------------
+*/
+
+/*
+These are functions for producing 32-bit hashes for hash table lookup.
+hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
+are externally useful functions. Routines to test the hash are included
+if SELF_TEST is defined. You can use this free for any purpose. It's in
+the public domain. It has no warranty.
+
+You probably want to use hashlittle(). hashlittle() and hashbig()
+hash byte arrays. hashlittle() is is faster than hashbig() on
+little-endian machines. Intel and AMD are little-endian machines.
+On second thought, you probably want hashlittle2(), which is identical to
+hashlittle() except it returns two 32-bit hashes for the price of one.
+You could implement hashbig2() if you wanted but I haven't bothered here.
+
+If you want to find a hash of, say, exactly 7 integers, do
+ a = i1; b = i2; c = i3;
+ mix(a,b,c);
+ a += i4; b += i5; c += i6;
+ mix(a,b,c);
+ a += i7;
+ final(a,b,c);
+then use c as the hash value. If you have a variable length array of
+4-byte integers to hash, use hashword(). If you have a byte array (like
+a character string), use hashlittle(). If you have several byte arrays, or
+a mix of things, see the comments above hashlittle().
+
+Why is this so big? I read 12 bytes at a time into 3 4-byte integers,
+then mix those integers. This is fast (you can do a lot more thorough
+mixing with 12*3 instructions on 3 integers than you can with 3 instructions
+on 1 byte), but shoehorning those bytes into integers efficiently is messy.
+*/
+
+#define SELF_TEST 1
+
+#include <stdio.h> /* defines printf for tests */
+#include <time.h> /* defines time_t for timings in the test */
+#ifdef Win32
+#include "win_stdint.h" /* defines uint32_t etc */
+#else
+#include <stdint.h> /* defines uint32_t etc */
+#endif
+//#include <sys/param.h> /* attempt to define endianness */
+//#ifdef linux
+//# include <endian.h> /* attempt to define endianness */
+//#endif
+
+#define HASH_LITTLE_ENDIAN 1
+
+#define hashsize(n) ((uint32_t)1<<(n))
+#define hashmask(n) (hashsize(n)-1)
+#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
+
+/*
+-------------------------------------------------------------------------------
+mix -- mix 3 32-bit values reversibly.
+
+This is reversible, so any information in (a,b,c) before mix() is
+still in (a,b,c) after mix().
+
+If four pairs of (a,b,c) inputs are run through mix(), or through
+mix() in reverse, there are at least 32 bits of the output that
+are sometimes the same for one pair and different for another pair.
+This was tested for:
+* pairs that differed by one bit, by two bits, in any combination
+ of top bits of (a,b,c), or in any combination of bottom bits of
+ (a,b,c).
+* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ is commonly produced by subtraction) look like a single 1-bit
+ difference.
+* the base values were pseudorandom, all zero but one bit set, or
+ all zero plus a counter that starts at zero.
+
+Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
+satisfy this are
+ 4 6 8 16 19 4
+ 9 15 3 18 27 15
+ 14 9 3 7 17 3
+Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
+for "differ" defined as + with a one-bit base and a two-bit delta. I
+used http://burtleburtle.net/bob/hash/avalanche.html to choose
+the operations, constants, and arrangements of the variables.
+
+This does not achieve avalanche. There are input bits of (a,b,c)
+that fail to affect some output bits of (a,b,c), especially of a. The
+most thoroughly mixed value is c, but it doesn't really even achieve
+avalanche in c.
+
+This allows some parallelism. Read-after-writes are good at doubling
+the number of bits affected, so the goal of mixing pulls in the opposite
+direction as the goal of parallelism. I did what I could. Rotates
+seem to cost as much as shifts on every machine I could lay my hands
+on, and rotates are much kinder to the top and bottom bits, so I used
+rotates.
+-------------------------------------------------------------------------------
+*/
+#define mix(a,b,c) \
+{ \
+ a -= c; a ^= rot(c, 4); c += b; \
+ b -= a; b ^= rot(a, 6); a += c; \
+ c -= b; c ^= rot(b, 8); b += a; \
+ a -= c; a ^= rot(c,16); c += b; \
+ b -= a; b ^= rot(a,19); a += c; \
+ c -= b; c ^= rot(b, 4); b += a; \
+}
+
+/*
+-------------------------------------------------------------------------------
+final -- final mixing of 3 32-bit values (a,b,c) into c
+
+Pairs of (a,b,c) values differing in only a few bits will usually
+produce values of c that look totally different. This was tested for
+* pairs that differed by one bit, by two bits, in any combination
+ of top bits of (a,b,c), or in any combination of bottom bits of
+ (a,b,c).
+* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
+ the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
+ is commonly produced by subtraction) look like a single 1-bit
+ difference.
+* the base values were pseudorandom, all zero but one bit set, or
+ all zero plus a counter that starts at zero.
+
+These constants passed:
+ 14 11 25 16 4 14 24
+ 12 14 25 16 4 14 24
+and these came close:
+ 4 8 15 26 3 22 24
+ 10 8 15 26 3 22 24
+ 11 8 15 26 3 22 24
+-------------------------------------------------------------------------------
+*/
+#define final(a,b,c) \
+{ \
+ c ^= b; c -= rot(b,14); \
+ a ^= c; a -= rot(c,11); \
+ b ^= a; b -= rot(a,25); \
+ c ^= b; c -= rot(b,16); \
+ a ^= c; a -= rot(c,4); \
+ b ^= a; b -= rot(a,14); \
+ c ^= b; c -= rot(b,24); \
+}
+
+/*
+-------------------------------------------------------------------------------
+hashlittle() -- hash a variable-length key into a 32-bit value
+ k : the key (the unaligned variable-length array of bytes)
+ length : the length of the key, counting by bytes
+ initval : can be any 4-byte value
+Returns a 32-bit value. Every bit of the key affects every bit of
+the return value. Two keys differing by one or two bits will have
+totally different hash values.
+
+The best hash table sizes are powers of 2. There is no need to do
+mod a prime (mod is sooo slow!). If you need less than 32 bits,
+use a bitmask. For example, if you need only 10 bits, do
+ h = (h & hashmask(10));
+In which case, the hash table should have hashsize(10) elements.
+
+If you are hashing n strings (uint8_t **)k, do it like this:
+ for (i=0, h=0; i<n; ++i) h = hashlittle( k[i], len[i], h);
+
+By Bob Jenkins, 2006. bob_jenkins at burtleburtle.net. You may use this
+code any way you wish, private, educational, or commercial. It's free.
+
+Use for hash table lookup, or anything where one collision in 2^^32 is
+acceptable. Do NOT use for cryptographic purposes.
+-------------------------------------------------------------------------------
+*/
+
+//uint32_t hashlittle( const void *key, size_t length, uint32_t initval)
+#ifdef STDCALL
+uint32_t __stdcall NHASH( const void *key, size_t *length0, uint32_t *initval0)
+#else
+uint32_t nhash_( const void *key, int *length0, uint32_t *initval0)
+#endif
+{
+ uint32_t a,b,c; /* internal state */
+ size_t length;
+ uint32_t initval;
+ union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
+
+ length=*length0;
+ initval=*initval0;
+
+ /* Set up the internal state */
+ a = b = c = 0xdeadbeef + ((uint32_t)length) + initval;
+
+ u.ptr = key;
+ if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
+ const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */
+ const uint8_t *k8;
+
+ k8=0; //Silence compiler warning
+ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ b += k[1];
+ c += k[2];
+ mix(a,b,c);
+ length -= 12;
+ k += 3;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ /*
+ * "k[2]&0xffffff" actually reads beyond the end of the string, but
+ * then masks off the part it's not allowed to read. Because the
+ * string is aligned, the masked-off tail is in the same word as the
+ * rest of the string. Every machine with memory protection I've seen
+ * does it on word boundaries, so is OK with this. But VALGRIND will
+ * still catch it and complain. The masking trick does make the hash
+ * noticably faster for short strings (like English words).
+ */
+#ifndef VALGRIND
+
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
+ case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
+ case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
+ case 6 : b+=k[1]&0xffff; a+=k[0]; break;
+ case 5 : b+=k[1]&0xff; a+=k[0]; break;
+ case 4 : a+=k[0]; break;
+ case 3 : a+=k[0]&0xffffff; break;
+ case 2 : a+=k[0]&0xffff; break;
+ case 1 : a+=k[0]&0xff; break;
+ case 0 : return c; /* zero length strings require no mixing */
+ }
+
+#else /* make valgrind happy */
+
+ k8 = (const uint8_t *)k;
+ switch(length)
+ {
+ case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=((uint32_t)k8[9])<<8; /* fall through */
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[1]; a+=k[0]; break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]; break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */
+ case 1 : a+=k8[0]; break;
+ case 0 : return c;
+ }
+
+#endif /* !valgrind */
+
+ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
+ const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */
+ const uint8_t *k8;
+
+ /*--------------- all but last block: aligned reads and different mixing */
+ while (length > 12)
+ {
+ a += k[0] + (((uint32_t)k[1])<<16);
+ b += k[2] + (((uint32_t)k[3])<<16);
+ c += k[4] + (((uint32_t)k[5])<<16);
+ mix(a,b,c);
+ length -= 12;
+ k += 6;
+ }
+
+ /*----------------------------- handle the last (probably partial) block */
+ k8 = (const uint8_t *)k;
+ switch(length)
+ {
+ case 12: c+=k[4]+(((uint32_t)k[5])<<16);
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 11: c+=((uint32_t)k8[10])<<16; /* fall through */
+ case 10: c+=k[4];
+ b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 9 : c+=k8[8]; /* fall through */
+ case 8 : b+=k[2]+(((uint32_t)k[3])<<16);
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */
+ case 6 : b+=k[2];
+ a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 5 : b+=k8[4]; /* fall through */
+ case 4 : a+=k[0]+(((uint32_t)k[1])<<16);
+ break;
+ case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */
+ case 2 : a+=k[0];
+ break;
+ case 1 : a+=k8[0];
+ break;
+ case 0 : return c; /* zero length requires no mixing */
+ }
+
+ } else { /* need to read the key one byte at a time */
+ const uint8_t *k = (const uint8_t *)key;
+
+ /*--------------- all but the last block: affect some 32 bits of (a,b,c) */
+ while (length > 12)
+ {
+ a += k[0];
+ a += ((uint32_t)k[1])<<8;
+ a += ((uint32_t)k[2])<<16;
+ a += ((uint32_t)k[3])<<24;
+ b += k[4];
+ b += ((uint32_t)k[5])<<8;
+ b += ((uint32_t)k[6])<<16;
+ b += ((uint32_t)k[7])<<24;
+ c += k[8];
+ c += ((uint32_t)k[9])<<8;
+ c += ((uint32_t)k[10])<<16;
+ c += ((uint32_t)k[11])<<24;
+ mix(a,b,c);
+ length -= 12;
+ k += 12;
+ }
+
+ /*-------------------------------- last block: affect all 32 bits of (c) */
+ switch(length) /* all the case statements fall through */
+ {
+ case 12: c+=((uint32_t)k[11])<<24;
+ case 11: c+=((uint32_t)k[10])<<16;
+ case 10: c+=((uint32_t)k[9])<<8;
+ case 9 : c+=k[8];
+ case 8 : b+=((uint32_t)k[7])<<24;
+ case 7 : b+=((uint32_t)k[6])<<16;
+ case 6 : b+=((uint32_t)k[5])<<8;
+ case 5 : b+=k[4];
+ case 4 : a+=((uint32_t)k[3])<<24;
+ case 3 : a+=((uint32_t)k[2])<<16;
+ case 2 : a+=((uint32_t)k[1])<<8;
+ case 1 : a+=k[0];
+ break;
+ case 0 : return c;
+ }
+ }
+
+ final(a,b,c);
+ return c;
+}
+
+//uint32_t __stdcall NHASH(const void *key, size_t length, uint32_t initval)
diff --git a/pack50.f90 b/pack50.f90
new file mode 100644
index 0000000..0c4cba5
--- /dev/null
+++ b/pack50.f90
@@ -0,0 +1,51 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: pack50.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine pack50(n1,n2,dat)
+
+ integer*1 dat(11),i1
+
+ i1=iand(ishft(n1,-20),255) !8 bits
+ dat(1)=i1
+ i1=iand(ishft(n1,-12),255) !8 bits
+ dat(2)=i1
+ i1=iand(ishft(n1, -4),255) !8 bits
+ dat(3)=i1
+ i1=16*iand(n1,15)+iand(ishft(n2,-18),15) !4+4 bits
+ dat(4)=i1
+ i1=iand(ishft(n2,-10),255) !8 bits
+ dat(5)=i1
+ i1=iand(ishft(n2, -2),255) !8 bits
+ dat(6)=i1
+ i1=64*iand(n2,3) !2 bits
+ dat(7)=i1
+ dat(8)=0
+ dat(9)=0
+ dat(10)=0
+ dat(11)=0
+
+ return
+end subroutine pack50
+
diff --git a/packcall.f90 b/packcall.f90
new file mode 100644
index 0000000..fabe1d4
--- /dev/null
+++ b/packcall.f90
@@ -0,0 +1,104 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: packcall.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine packcall(callsign,ncall,text)
+
+! Pack a valid callsign into a 28-bit integer.
+
+ parameter (NBASE=37*36*10*27*27*27)
+ character callsign*6,c*1,tmp*6,digit*10
+ logical text
+ data digit/'0123456789'/
+
+ text=.false.
+
+! Work-around for Swaziland prefix:
+ if(callsign(1:4).eq.'3DA0') callsign='3D0'//callsign(5:6)
+
+ if(callsign(1:3).eq.'CQ ') then
+ ncall=NBASE + 1
+ if(callsign(4:4).ge.'0' .and. callsign(4:4).le.'9' .and. &
+ callsign(5:5).ge.'0' .and. callsign(5:5).le.'9' .and. &
+ callsign(6:6).ge.'0' .and. callsign(6:6).le.'9') then
+ nfreq=100*(ichar(callsign(4:4))-48) + &
+ 10*(ichar(callsign(5:5))-48) + &
+ ichar(callsign(6:6))-48
+ ncall=NBASE + 3 + nfreq
+ endif
+ return
+ else if(callsign(1:4).eq.'QRZ ') then
+ ncall=NBASE + 2
+ return
+ endif
+
+ tmp=' '
+ if(callsign(3:3).ge.'0' .and. callsign(3:3).le.'9') then
+ tmp=callsign
+ else if(callsign(2:2).ge.'0' .and. callsign(2:2).le.'9') then
+ if(callsign(6:6).ne.' ') then
+ text=.true.
+ return
+ endif
+ tmp=' '//callsign
+ else
+ text=.true.
+ return
+ endif
+
+ do i=1,6
+ c=tmp(i:i)
+ if(c.ge.'a' .and. c.le.'z') &
+ tmp(i:i)=char(ichar(c)-ichar('a')+ichar('A'))
+ enddo
+
+ n1=0
+ if((tmp(1:1).ge.'A'.and.tmp(1:1).le.'Z').or.tmp(1:1).eq.' ') n1=1
+ if(tmp(1:1).ge.'0' .and. tmp(1:1).le.'9') n1=1
+ n2=0
+ if(tmp(2:2).ge.'A' .and. tmp(2:2).le.'Z') n2=1
+ if(tmp(2:2).ge.'0' .and. tmp(2:2).le.'9') n2=1
+ n3=0
+ if(tmp(3:3).ge.'0' .and. tmp(3:3).le.'9') n3=1
+ n4=0
+ if((tmp(4:4).ge.'A'.and.tmp(4:4).le.'Z').or.tmp(4:4).eq.' ') n4=1
+ n5=0
+ if((tmp(5:5).ge.'A'.and.tmp(5:5).le.'Z').or.tmp(5:5).eq.' ') n5=1
+ n6=0
+ if((tmp(6:6).ge.'A'.and.tmp(6:6).le.'Z').or.tmp(6:6).eq.' ') n6=1
+
+ if(n1+n2+n3+n4+n5+n6 .ne. 6) then
+ text=.true.
+ return
+ endif
+
+ ncall=nchar(tmp(1:1))
+ ncall=36*ncall+nchar(tmp(2:2))
+ ncall=10*ncall+nchar(tmp(3:3))
+ ncall=27*ncall+nchar(tmp(4:4))-10
+ ncall=27*ncall+nchar(tmp(5:5))-10
+ ncall=27*ncall+nchar(tmp(6:6))-10
+
+ return
+end subroutine packcall
diff --git a/packgrid.f90 b/packgrid.f90
new file mode 100644
index 0000000..ce5bdf5
--- /dev/null
+++ b/packgrid.f90
@@ -0,0 +1,72 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: packgrid.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine packgrid(grid,ng,text)
+
+ parameter (NGBASE=180*180)
+ character*4 grid
+ logical text
+
+ text=.false.
+ if(grid.eq.' ') go to 90 !Blank grid is OK
+
+! Test for numerical signal report, etc.
+ if(grid(1:1).eq.'-') then
+ n=10*(ichar(grid(2:2))-48) + ichar(grid(3:3)) - 48
+ ng=NGBASE+1+n
+ go to 100
+ else if(grid(1:2).eq.'R-') then
+ n=10*(ichar(grid(3:3))-48) + ichar(grid(4:4)) - 48
+ if(n.eq.0) go to 90
+ ng=NGBASE+31+n
+ go to 100
+ else if(grid(1:2).eq.'RO') then
+ ng=NGBASE+62
+ go to 100
+ else if(grid(1:3).eq.'RRR') then
+ ng=NGBASE+63
+ go to 100
+ else if(grid(1:2).eq.'73') then
+ ng=NGBASE+64
+ go to 100
+ endif
+
+ if(grid(1:1).lt.'A' .or. grid(1:1).gt.'R') text=.true.
+ if(grid(2:2).lt.'A' .or. grid(2:2).gt.'R') text=.true.
+ if(grid(3:3).lt.'0' .or. grid(3:3).gt.'9') text=.true.
+ if(grid(4:4).lt.'0' .or. grid(4:4).gt.'9') text=.true.
+ if(text) go to 100
+
+ call grid2deg(grid//'mm',dlong,dlat)
+ long=dlong
+ lat=dlat+ 90.0
+ ng=((long+180)/2)*180 + lat
+ go to 100
+
+90 ng=NGBASE + 1
+
+100 return
+end subroutine packgrid
+
diff --git a/packname.f90 b/packname.f90
new file mode 100644
index 0000000..24da6f7
--- /dev/null
+++ b/packname.f90
@@ -0,0 +1,48 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: packname.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine packname(name,len,n1,n2)
+
+ character*9 name
+ real*8 dn
+
+ dn=0
+ do i=1,len
+ n=ichar(name(i:i))
+ if(n.ge.97 .and. n.le.122) n=n-32
+ dn=27*dn + n-64
+ enddo
+ if(len.lt.9) then
+ do i=len+1,9
+ dn=27*dn
+ enddo
+ endif
+
+ n2=mod(dn,32768.d0)
+ dn=dn/32768.d0
+ n1=dn
+
+ return
+end subroutine packname
diff --git a/packpfx.f90 b/packpfx.f90
new file mode 100644
index 0000000..40324fb
--- /dev/null
+++ b/packpfx.f90
@@ -0,0 +1,84 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: packpfx.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine packpfx(call1,n1,ng,nadd)
+
+ character*12 call1,call0
+ character*3 pfx
+ logical text
+
+ i1=index(call1,'/')
+ if(call1(i1+2:i1+2).eq.' ') then
+! Single-character add-on suffix (maybe also fourth suffix letter?)
+ call0=call1(:i1-1)
+ call packcall(call0,n1,text)
+ nadd=1
+ nc=ichar(call1(i1+1:i1+1))
+ if(nc.ge.48 .and. nc.le.57) then
+ n=nc-48
+ else if(nc.ge.65 .and. nc.le.90) then
+ n=nc-65+10
+ else
+ n=38
+ endif
+ nadd=1
+ ng=60000-32768+n
+ else if(call1(i1+3:i1+3).eq.' ') then
+! Two-character numerical suffix, /10 to /99
+ call0=call1(:i1-1)
+ call packcall(call0,n1,text)
+ nadd=1
+ n=10*(ichar(call1(i1+1:i1+1))-48) + ichar(call1(i1+2:i1+2)) - 48
+ nadd=1
+ ng=60000 + 26 + n
+ else
+! Prefix of 1 to 3 characters
+ pfx=call1(:i1-1)
+ if(pfx(3:3).eq.' ') pfx=' '//pfx
+ if(pfx(3:3).eq.' ') pfx=' '//pfx
+ call0=call1(i1+1:)
+ call packcall(call0,n1,text)
+
+ ng=0
+ do i=1,3
+ nc=ichar(pfx(i:i))
+ if(nc.ge.48 .and. nc.le.57) then
+ n=nc-48
+ else if(nc.ge.65 .and. nc.le.90) then
+ n=nc-65+10
+ else
+ n=36
+ endif
+ ng=37*ng + n
+ enddo
+ nadd=0
+ if(ng.ge.32768) then
+ ng=ng-32768
+ nadd=1
+ endif
+ endif
+
+ return
+end subroutine packpfx
diff --git a/packprop.f90 b/packprop.f90
new file mode 100644
index 0000000..76967f4
--- /dev/null
+++ b/packprop.f90
@@ -0,0 +1,61 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: packprop.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine packprop(k,muf,ccur,cxp,n1)
+
+! Pack propagation indicators into a 21-bit number.
+
+! k k-index, 0-9; 10="N/A"
+! muf muf, 2-60 MHz; 0=N/A, 1="none", 61=">60 MHz"
+! ccur up to two current events, each indicated by single
+! or double letter.
+! cxp zero or one expected event, indicated by single or
+! double letter
+
+ character ccur*4,cxp*2
+
+ j=ichar(ccur(1:1))-64
+ if(j.lt.0) j=0
+ n1=j
+ do i=2,4
+ if(ccur(i:i).eq.' ') go to 10
+ if(ccur(i:i).eq.ccur(i-1:i-1)) then
+ n1=n1+26
+ else
+ j=ichar(ccur(i:i))-64
+ if(j.lt.0) j=0
+ n1=53*n1 + j
+ endif
+ enddo
+
+10 j=ichar(cxp(1:1))-64
+ if(j.lt.0) j=0
+ if(cxp(2:2).eq.cxp(1:1)) j=j+26
+ n1=53*n1 + j
+ n1=11*n1 + k
+ n1=62*n1 + muf
+
+ return
+end subroutine packprop
diff --git a/packtext2.f90 b/packtext2.f90
new file mode 100644
index 0000000..d78e974
--- /dev/null
+++ b/packtext2.f90
@@ -0,0 +1,47 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: packtext2.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine packtext2(msg,n1,ng)
+
+ character*8 msg
+ real*8 dn
+ character*41 c
+ data c/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +./?'/
+
+ dn=0.
+ do i=1,8
+ do j=1,41
+ if(msg(i:i).eq.c(j:j)) go to 10
+ enddo
+ j=37
+10 j=j-1 !Codes should start at zero
+ dn=41.d0*dn + j
+ enddo
+
+ ng=mod(dn,32768.d0)
+ n1=(dn-ng)/32768.d0
+
+ return
+end subroutine packtext2
diff --git a/padevsub.c b/padevsub.c
new file mode 100644
index 0000000..454073c
--- /dev/null
+++ b/padevsub.c
@@ -0,0 +1,108 @@
+/*
+ *-------------------------------------------------------------------------------
+ *
+ * This file is part of the WSPR application, Weak Signal Propagation Reporter
+ *
+ * File Name: padevsub.c
+ * Description:
+ *
+ * Copyright (C) 2001-2014 Joseph Taylor, K1JT
+ * License: GPL-3
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+ * Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *-------------------------------------------------------------------------------
+*/
+
+#include <stdio.h>
+#include <portaudio.h>
+#include <string.h>
+
+#define NUM_CHANNELS (1)
+#define PA_SAMPLE_TYPE paInt16
+#define SAMPLE_RATE (48000)
+
+int padevsub_(int *numdev, int *ndefin, int *ndefout, int nchin[],
+ int nchout[], int inerr[], int outerr[])
+{
+ int i,devIdx;
+ int numDevices;
+ const PaDeviceInfo *pdi;
+ PaError err;
+ PaStreamParameters inputParameters;
+ PaStreamParameters outputParameters;
+ FILE *fp;
+
+ Pa_Initialize();
+ numDevices = Pa_GetDeviceCount();
+ *numdev = numDevices;
+
+ if( numDevices < 0 ) {
+ err = numDevices;
+ Pa_Terminate();
+ return err;
+ }
+
+ if ((devIdx = Pa_GetDefaultInputDevice()) > 0) {
+ *ndefin = devIdx;
+ } else {
+ *ndefin = 0;
+ }
+
+ if ((devIdx = Pa_GetDefaultOutputDevice()) > 0) {
+ *ndefout = devIdx;
+ } else {
+ *ndefout = 0;
+ }
+
+ fp=fopen("audio_caps","w");
+ for( i=0; i < numDevices; i++ ) {
+ pdi = Pa_GetDeviceInfo(i);
+ nchin[i]=pdi->maxInputChannels;
+ nchout[i]=pdi->maxOutputChannels;
+ inerr[i]=1;
+ outerr[i]=1;
+ if(nchin[i]>0) {
+ inputParameters.device = i;
+ inputParameters.channelCount = NUM_CHANNELS;
+ inputParameters.sampleFormat = PA_SAMPLE_TYPE;
+ inputParameters.suggestedLatency = 0.4;
+ inputParameters.hostApiSpecificStreamInfo = NULL;
+ // The following call causes problems on Ubuntu 9.10. Until we figure
+ // that out, we'll assume the required sound format is OK and
+ // learn the truth when we actually select & open the device. --W1BW
+ //inerr[i] = Pa_IsFormatSupported(&inputParameters,NULL,SAMPLE_RATE);
+ inerr[i] = 0;
+ }
+
+ if(nchout[i]>0) {
+ outputParameters.device = i;
+ outputParameters.channelCount = NUM_CHANNELS;
+ outputParameters.sampleFormat = PA_SAMPLE_TYPE;
+ outputParameters.suggestedLatency = 0.4;
+ outputParameters.hostApiSpecificStreamInfo = NULL;
+ // The following call causes problems on Ubuntu 9.10. Until we figure
+ // that out, we'll assume the required sound format is OK and
+ // learn the truth when we actually select & open the device. --W1BW
+ //outerr[i] = Pa_IsFormatSupported(NULL,&outputParameters,SAMPLE_RATE);
+ outerr[i] = 0;
+ }
+ fprintf(fp,"%2d %3d %3d %6d %6d %s\n",i,nchin[i],nchout[i],inerr[i],
+ outerr[i],pdi->name);
+ }
+ fclose(fp);
+ return 0;
+}
+
diff --git a/paterminate.f90 b/paterminate.f90
new file mode 100644
index 0000000..f4cd87c
--- /dev/null
+++ b/paterminate.f90
@@ -0,0 +1,30 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: paterminate.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine paterminate
+ integer soundexit
+ ierr=soundexit()
+ return
+end subroutine paterminate
diff --git a/pctile.f90 b/pctile.f90
new file mode 100644
index 0000000..152dbec
--- /dev/null
+++ b/pctile.f90
@@ -0,0 +1,38 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: pctitle.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine pctile(x,tmp,nmax,npct,xpct)
+ real x(nmax),tmp(nmax)
+
+ do i=1,nmax
+ tmp(i)=x(i)
+ enddo
+ call sort(nmax,tmp)
+ j=nint(nmax*0.01*npct)
+ if(j.lt.1) j=1
+ xpct=tmp(j)
+
+ return
+end subroutine pctile
diff --git a/peakup.f90 b/peakup.f90
new file mode 100644
index 0000000..624a04e
--- /dev/null
+++ b/peakup.f90
@@ -0,0 +1,33 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: peakup.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine peakup(ym,y0,yp,dx)
+
+ b=(yp-ym)/2.0
+ c=(yp+ym-2.0*y0)/2.0
+ dx=-b/(2.0*c)
+
+ return
+end subroutine peakup
diff --git a/phasetx.f90 b/phasetx.f90
new file mode 100644
index 0000000..a9f2a30
--- /dev/null
+++ b/phasetx.f90
@@ -0,0 +1,49 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: phasetx.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine phasetx(id2,npts,txbal,txpha)
+
+ integer*2 id2(2,npts)
+
+ pha=txpha/57.2957795
+ xbal=10.0**(0.005*txbal)
+ if(xbal.gt.1.0) then
+ b1=1.0
+ b2=1.0/xbal
+ else
+ b1=xbal
+ b2=1.0
+ endif
+ do i=1,npts
+ x=id2(1,i)
+ y=id2(2,i)
+ amp=sqrt(x*x+y*y)
+ phi=atan2(y,x)
+ id2(1,i)=nint(b1*amp*cos(phi))
+ id2(2,i)=nint(b2*amp*sin(phi+pha))
+ enddo
+
+ return
+end subroutine phasetx
diff --git a/playsound.c b/playsound.c
new file mode 100644
index 0000000..83da918
--- /dev/null
+++ b/playsound.c
@@ -0,0 +1,191 @@
+/** @file patest_record.c
+ @brief Record input into an array; Save array to a file; Playback recorded data.
+ @author Phil Burk http://www.softsynth.com
+*/
+/*
+ * $Id: patest_record.c 249 2006-08-09 20:08:01Z va3db $
+ *
+ * This program uses the PortAudio Portable Audio Library.
+ * For more information see: http://www.portaudio.com
+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "portaudio.h"
+
+/* #define SAMPLE_RATE (17932) // Test failure to open with this value. */
+#define SAMPLE_RATE (12000)
+#define FRAMES_PER_BUFFER (1024)
+#define NUM_SECONDS (114)
+#define NUM_CHANNELS (1)
+/* #define DITHER_FLAG (paDitherOff) */
+#define DITHER_FLAG (0) /**/
+
+/* Select sample format. */
+#define PA_SAMPLE_TYPE paInt16
+typedef short SAMPLE;
+
+typedef struct
+{
+ int frameIndex; /* Index into sample array. */
+ int maxFrameIndex;
+ SAMPLE *recordedSamples;
+} paTestData;
+
+/* This routine will be called by the PortAudio engine when audio is needed.
+** It may be called at interrupt level on some machines so don't do anything
+** that could mess up the system like calling malloc() or free().
+*/
+static int playCallback( const void *inputBuffer, void *outputBuffer,
+ unsigned long framesPerBuffer,
+ const PaStreamCallbackTimeInfo* timeInfo,
+ PaStreamCallbackFlags statusFlags,
+ void *userData )
+{
+ paTestData *data = (paTestData*)userData;
+ SAMPLE *rptr = &data->recordedSamples[data->frameIndex * NUM_CHANNELS];
+ SAMPLE *wptr = (SAMPLE*)outputBuffer;
+ unsigned int i;
+ int finished;
+ unsigned int framesLeft = data->maxFrameIndex - data->frameIndex;
+
+ (void) inputBuffer; /* Prevent unused variable warnings. */
+ (void) timeInfo;
+ (void) statusFlags;
+ (void) userData;
+
+ if( framesLeft < framesPerBuffer ) {
+ /* final buffer... */
+ for( i=0; i<framesLeft; i++ ) {
+ *wptr++ = *rptr++; /* left */
+ if( NUM_CHANNELS == 2 ) *wptr++ = *rptr++; /* right */
+ }
+ for( ; i<framesPerBuffer; i++ ) {
+ *wptr++ = 0; /* left */
+ if( NUM_CHANNELS == 2 ) *wptr++ = 0; /* right */
+ }
+ data->frameIndex += framesLeft;
+ finished = paComplete;
+ }
+ else {
+ for( i=0; i<framesPerBuffer; i++ ) {
+ *wptr++ = *rptr++; /* left */
+ if( NUM_CHANNELS == 2 ) *wptr++ = *rptr++; /* right */
+ }
+ data->frameIndex += framesPerBuffer;
+ finished = paContinue;
+ }
+ return finished;
+}
+
+/*******************************************************************/
+extern int playsound_(short int iwave[], int *npts)
+{
+ PaStreamParameters outputParameters;
+ PaStream* stream;
+ PaError err = paNoError;
+ paTestData data;
+ int totalFrames;
+ int numSamples;
+ int numBytes;
+ int itemp=0;
+
+ // data.maxFrameIndex = totalFrames = NUM_SECONDS * SAMPLE_RATE;
+ data.maxFrameIndex = totalFrames = *npts;
+ data.frameIndex = 0;
+ numSamples = totalFrames * NUM_CHANNELS;
+ numBytes = numSamples * sizeof(SAMPLE);
+ data.recordedSamples = iwave;
+
+ /* Play the wave file */
+ data.frameIndex = 0;
+ // err = Pa_Initialize();
+ // if( err != paNoError ) goto done;
+ outputParameters.device = Pa_GetDefaultOutputDevice();
+ outputParameters.channelCount = NUM_CHANNELS;
+ outputParameters.sampleFormat = PA_SAMPLE_TYPE;
+ outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
+ outputParameters.hostApiSpecificStreamInfo = NULL;
+
+ err = Pa_OpenStream(
+ &stream,
+ NULL, /* no input */
+ &outputParameters,
+ SAMPLE_RATE,
+ FRAMES_PER_BUFFER,
+ paClipOff,
+ playCallback,
+ &data );
+ if( err != paNoError ) goto done;
+
+ if( stream ) {
+ err = Pa_StartStream( stream );
+ if( err != paNoError ) goto done;
+
+ while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) {
+ itemp++;
+ printf("a %d %d %d\n",itemp,*npts,data.frameIndex);
+ Pa_Sleep(1000);
+ }
+ if( err < 0 ) goto done;
+
+ err = Pa_CloseStream( stream );
+ if( err != paNoError ) goto done;
+ }
+
+done:
+ // Pa_Terminate();
+ // if( data.recordedSamples ) /* Sure it is NULL or valid. */
+ // free( data.recordedSamples );
+ if( err != paNoError ) {
+ fprintf( stderr, "An error occured while using the portaudio stream\n" );
+ fprintf( stderr, "Error number: %d\n", err );
+ fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
+ err = 1; /* Always return 0 or 1, but no other return codes. */
+ }
+ return err;
+}
+
+int pa_init_(void)
+{
+ int err;
+ err = Pa_Initialize();
+ if(err==0) printf("Portaudio initialized\n");
+ return err;
+}
+
+void pa_terminate_(void)
+{
+ Pa_Terminate();
+ printf("Portaudio terminated\n");
+}
+
+void msleep_(int *msec0)
+{
+ Pa_Sleep(*msec0);
+}
diff --git a/ps162.f90 b/ps162.f90
new file mode 100644
index 0000000..e695db9
--- /dev/null
+++ b/ps162.f90
@@ -0,0 +1,52 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: ps162.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine ps162(c2,s)
+
+ parameter (NFFT=512,NH=256)
+ complex c2(0:NFFT)
+ real s(-NH:NH)
+ complex c(0:NFFT)
+ common/fftcom2/c !This keeps the absolute address of c() constant
+
+ do i=0,NH-1
+ c(i)=c2(i)
+ enddo
+ do i=nh,nfft-1
+ c(i)=0.
+ enddo
+
+ call four2a(c,nfft,1,-1,1)
+
+ fac=1.0/nfft
+ do i=0,NFFT-1
+ j=i
+ if(j.gt.NH) j=j-NFFT
+ s(j)=fac*(real(c(i))**2 + aimag(c(i))**2)
+ enddo
+ s(-NH)=s(-NH+1)
+
+ return
+end subroutine ps162
diff --git a/ptt_unix.c b/ptt_unix.c
new file mode 100644
index 0000000..d00fab5
--- /dev/null
+++ b/ptt_unix.c
@@ -0,0 +1,392 @@
+/*
+*-------------------------------------------------------------------------------
+ *
+ * This file is part of the WSPR application, Weak Signal Propagation Reporter
+ *
+ * File Name: ptt_unix.c
+ * Description:
+ *
+ * Code used from cwdaemon for parallel port ptt only.
+ * cwdaemon - morse sounding daemon for the parallel or serial port
+ * Copyright: 2002-2005 Joop Stakenborg <pg4i at amsat.org>
+ * Many authors contributed to this file, see the AUTHORS file.
+ *
+ * Copyright (C) 2001-2014 Joseph Taylor, K1JT
+ * License: GPL-3
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+ * Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+*-------------------------------------------------------------------------------
+*/
+
+#if HAVE_STDIO_H
+# include <stdio.h>
+#endif
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#if HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#if HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+
+#ifdef HAVE_LINUX_PPDEV_H
+# include <linux/ppdev.h>
+# include <linux/parport.h>
+#endif
+#ifdef HAVE_DEV_PPBUS_PPI_H
+# include <dev/ppbus/ppi.h>
+# include <dev/ppbus/ppbconf.h>
+#endif
+
+int lp_reset (int fd);
+int lp_ptt (int fd, int onoff);
+
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#if (defined(__unix__) || defined(unix)) && !defined(USG)
+# include <sys/param.h>
+#endif
+
+#include <string.h>
+/* parport functions */
+
+int dev_is_parport(int fd);
+int ptt_parallel(int fd, int *ntx, int *iptt);
+int ptt_serial(int fd, int *ntx, int *iptt);
+
+int fd=-1; /* Used for both serial and parallel */
+
+/*
+ * ptt_
+ *
+ * generic unix PTT routine called from Fortran
+ *
+ * Inputs
+ * unused Unused, to satisfy old windows calling convention
+ * ptt_port device name serial or parallel
+ * ntx pointer to fortran command on or off
+ * iptt pointer to fortran command status on or off
+ * Returns - non 0 if error
+*/
+
+/* Tiny state machine */
+#define STATE_PORT_CLOSED 0
+#define STATE_PORT_OPEN_PARALLEL 1
+#define STATE_PORT_OPEN_SERIAL 2
+
+int
+ptt_(int *unused, char *ptt_port, int *ntx, int *iptt)
+{
+ static int state=0;
+ char *p;
+
+ /* In the very unlikely event of a NULL pointer, just return.
+ * Yes, I realise this should not be possible in WSJT.
+ */
+ if (ptt_port == NULL) {
+ *iptt = *ntx;
+ return (0);
+ }
+
+ switch (state) {
+ case STATE_PORT_CLOSED:
+
+ /* Remove trailing ' ' */
+ if ((p = strchr(ptt_port, ' ')) != NULL)
+ *p = '\0';
+
+ /* If all that is left is a '\0' then also just return */
+ if (*ptt_port == '\0') {
+ *iptt = *ntx;
+ return(0);
+ }
+
+ if ((fd = open(ptt_port, O_RDWR|O_NONBLOCK)) < 0) {
+ fprintf(stderr, "Can't open %s.\n", ptt_port);
+ return (1);
+ }
+
+ if (dev_is_parport(fd)) {
+ state = STATE_PORT_OPEN_PARALLEL;
+ lp_reset(fd);
+ ptt_parallel(fd, ntx, iptt);
+ } else {
+ state = STATE_PORT_OPEN_SERIAL;
+ ptt_serial(fd, ntx, iptt);
+ }
+ break;
+
+ case STATE_PORT_OPEN_PARALLEL:
+ ptt_parallel(fd, ntx, iptt);
+ break;
+
+ case STATE_PORT_OPEN_SERIAL:
+ ptt_serial(fd, ntx, iptt);
+ break;
+
+ default:
+ close(fd);
+ fd = -1;
+ state = STATE_PORT_CLOSED;
+ break;
+ }
+ return(0);
+}
+
+/*
+ * ptt_serial
+ *
+ * generic serial unix PTT routine called indirectly from Fortran
+ *
+ * fd - already opened file descriptor
+ * ntx - pointer to fortran command on or off
+ * iptt - pointer to fortran command status on or off
+ */
+
+int
+ptt_serial(int fd, int *ntx, int *iptt)
+{
+ int control = TIOCM_RTS | TIOCM_DTR;
+
+ if(*ntx) {
+ ioctl(fd, TIOCMBIS, &control); /* Set DTR and RTS */
+ *iptt = 1;
+ } else {
+ ioctl(fd, TIOCMBIC, &control);
+ *iptt = 0;
+ }
+ return(0);
+}
+
+
+/* parport functions */
+
+/*
+ * dev_is_parport(fd):
+ *
+ * inputs - Already open fd
+ * output - 1 if parallel port, 0 if not
+ * side effects - Unfortunately, this is platform specific.
+ */
+
+#if defined(HAVE_LINUX_PPDEV_H) /* Linux (ppdev) */
+
+int
+dev_is_parport(int fd)
+{
+ struct stat st;
+ int m;
+
+ if ((fstat(fd, &st) == -1) ||
+ ((st.st_mode & S_IFMT) != S_IFCHR) ||
+ (ioctl(fd, PPGETMODE, &m) == -1))
+ return(0);
+
+ return(1);
+}
+
+#elif defined(HAVE_DEV_PPBUS_PPI_H) /* FreeBSD (ppbus/ppi) */
+
+int
+dev_is_parport(int fd)
+{
+ struct stat st;
+ unsigned char c;
+
+ if ((fstat(fd, &st) == -1) ||
+ ((st.st_mode & S_IFMT) != S_IFCHR) ||
+ (ioctl(fd, PPISSTATUS, &c) == -1))
+ return(0);
+
+ return(1);
+}
+
+#else /* Fallback (nothing) */
+
+int
+dev_is_parport(int fd)
+{
+ return(0);
+}
+
+#endif
+/* Linux wrapper around PPFCONTROL */
+#ifdef HAVE_LINUX_PPDEV_H
+static void
+parport_control (int fd, unsigned char controlbits, int values)
+{
+ struct ppdev_frob_struct frob;
+ frob.mask = controlbits;
+ frob.val = values;
+
+ if (ioctl (fd, PPFCONTROL, &frob) == -1)
+ {
+ fprintf(stderr, "Parallel port PPFCONTROL");
+ exit (1);
+ }
+}
+#endif
+
+/* FreeBSD wrapper around PPISCTRL */
+#ifdef HAVE_DEV_PPBUS_PPI_H
+static void
+parport_control (int fd, unsigned char controlbits, int values)
+{
+ unsigned char val;
+
+ if (ioctl (fd, PPIGCTRL, &val) == -1)
+ {
+ fprintf(stderr, "Parallel port PPIGCTRL");
+ exit (1);
+ }
+
+ val &= ~controlbits;
+ val |= values;
+
+ if (ioctl (fd, PPISCTRL, &val) == -1)
+ {
+ fprintf(stderr, "Parallel port PPISCTRL");
+ exit (1);
+ }
+}
+#endif
+
+/* Initialise a parallel port, given open fd */
+int
+lp_init (int fd)
+{
+#ifdef HAVE_LINUX_PPDEV_H
+ int mode;
+#endif
+
+#ifdef HAVE_LINUX_PPDEV_H
+ mode = PARPORT_MODE_PCSPP;
+
+ if (ioctl (fd, PPSETMODE, &mode) == -1)
+ {
+ fprintf(stderr, "Setting parallel port mode");
+ close (fd);
+ return(-1);
+ }
+
+ if (ioctl (fd, PPEXCL, NULL) == -1)
+ {
+ fprintf(stderr, "Parallel port is already in use.\n");
+ close (fd);
+ return(-1);
+ }
+ if (ioctl (fd, PPCLAIM, NULL) == -1)
+ {
+ fprintf(stderr, "Claiming parallel port.\n");
+ fprintf(stderr, "HINT: did you unload the lp kernel module?");
+ close (fd);
+ return(-1);
+ }
+
+ /* Enable CW & PTT - /STROBE bit (pin 1) */
+ parport_control (fd, PARPORT_CONTROL_STROBE, PARPORT_CONTROL_STROBE);
+#endif
+#ifdef HAVE_DEV_PPBUS_PPI_H
+ parport_control (fd, STROBE, STROBE);
+#endif
+ lp_reset (fd);
+ return(0);
+}
+
+/* release ppdev and close port */
+int
+lp_free (int fd)
+{
+#ifdef HAVE_LINUX_PPDEV_H
+ lp_reset (fd);
+
+ /* Disable CW & PTT - /STROBE bit (pin 1) */
+ parport_control (fd, PARPORT_CONTROL_STROBE, 0);
+
+ ioctl (fd, PPRELEASE);
+#endif
+#ifdef HAVE_DEV_PPBUS_PPI_H
+ /* Disable CW & PTT - /STROBE bit (pin 1) */
+ parport_control (fd, STROBE, 0);
+#endif
+ close (fd);
+ return(0);
+}
+
+/* set to a known state */
+int
+lp_reset (int fd)
+{
+#if defined (HAVE_LINUX_PPDEV_H) || defined (HAVE_DEV_PPBUS_PPI_H)
+ lp_ptt (fd, 0);
+#endif
+ return(0);
+}
+
+/* SSB PTT keying - /INIT bit (pin 16) (inverted) */
+int
+lp_ptt (int fd, int onoff)
+{
+#ifdef HAVE_LINUX_PPDEV_H
+ if (onoff == 1)
+ parport_control (fd, PARPORT_CONTROL_INIT,
+ PARPORT_CONTROL_INIT);
+ else
+ parport_control (fd, PARPORT_CONTROL_INIT, 0);
+#endif
+#ifdef HAVE_DEV_PPBUS_PPI_H
+ if (onoff == 1)
+ parport_control (fd, nINIT,
+ nINIT);
+ else
+ parport_control (fd, nINIT, 0);
+#endif
+ return(0);
+}
+
+/*
+ * ptt_parallel
+ *
+ * generic parallel unix PTT routine called indirectly from Fortran
+ *
+ * fd - already opened file descriptor
+ * ntx - pointer to fortran command on or off
+ * iptt - pointer to fortran command status on or off
+ */
+
+int
+ptt_parallel(int fd, int *ntx, int *iptt)
+{
+ if(*ntx) {
+ lp_ptt(fd, 1);
+ *iptt=1;
+ } else {
+ lp_ptt(fd, 0);
+ *iptt=0;
+ }
+ return(0);
+}
diff --git a/qth.f90 b/qth.f90
new file mode 100644
index 0000000..daf9f28
--- /dev/null
+++ b/qth.f90
@@ -0,0 +1,125 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: qth.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program qth
+
+ parameter (NMAX=100)
+ real xlon1(NMAX),xlon2(NMAX)
+ real xlat1(NMAX),xlat2(NMAX)
+ real ddelay(NMAX)
+ real sigma(NMAX)
+ real chisq(-10:10,-10:10)
+ character*6 call1(NMAX),call2(NMAX)
+ character*6 grid1(NMAX),grid2(NMAX)
+ character infile*40
+ character*12 arg
+
+ nargs=iargc()
+ if(nargs.ne.2) then
+ print*,'Usage: qth <infile> <iters>'
+ go to 999
+ endif
+ call getarg(1,infile)
+ call getarg(2,arg)
+ read(arg,*) iters
+
+ open(10,file=infile,status='old')
+ open(12,file='qth.out',status='unknown')
+
+ do i=1,NMAX
+ read(10,1010,end=10) call1(i),grid1(i),call2(i),grid2(i),ddelay(i),sigma(i)
+1010 format(a6,1x,a6,2x,a6,1x,a6,2f7.2)
+ if(sigma(i).eq.0.0) sigma(i)=0.08
+ call grid2deg(grid1(i),xlon1(i),xlat1(i))
+ call grid2deg(grid2(i),xlon2(i),xlat2(i))
+ xlon1(i)=-xlon1(i)
+ xlon2(i)=-xlon2(i)
+ enddo
+ i=NMAX+1
+
+10 iz=i-1
+
+ xlon0=-80
+ xlat0=40
+! xlon0=40
+! xlat0=55
+ dlon=4.0
+ dlat=4.0
+ i0=10
+
+ sqmin=1.e30
+ do iter=1,iters
+ do ilon=-i0,i0
+ xlon=xlon0 + ilon*dlon
+ do ilat=-i0,i0
+ xlat=xlat0 + ilat*dlat
+ sq=0.
+ do i=1,iz
+ call geodist(xlat,-xlon,xlat1(i),-xlon1(i),az1,baz1,dist1)
+ call geodist(xlat,-xlon,xlat2(i),-xlon2(i),az2,baz2,dist2)
+ calc_ddelay=(dist1-dist2)/300.0
+ resid=ddelay(i)-calc_ddelay
+ sq=sq + (resid/sigma(i))**2
+ enddo
+ chisq(ilon,ilat)=sq/(iz-2)
+ if(sq.lt.sqmin) then
+ blon=xlon
+ blat=xlat
+ sqmin=sq
+ endif
+ enddo
+ enddo
+
+! call geodist(39.0653,-84.6075,blat,blon,az,baz,dist)
+! call geodist(55.75,37.28,blat,blon,az,baz,dist)
+ call geodist(41.7292,-72.7083,blat,blon,az,baz,dist)
+ write(*,1030) iter,blon,blat,sqmin,dist
+1030 format(i3,2f10.4,f10.2,3f8.0)
+ if(iter.eq.iters) then
+ call geodist(blat,blon,blat,blon+dlon,az,baz,dx)
+ call geodist(blat,blon,blat+dlat,blon,az,baz,dy)
+ write(*,1030)
+ write(*,1032) blon,blat,dx,dy
+ write(12,1032) blon,blat,dx,dy
+1032 format('Lon:',f7.2,' Lat:',f7.2,' dx_km:',f6.1,' dy_km:',f6.1)
+ write(*,1040) (i*dlon,i=-5,5)
+1040 format(7x,13f6.2)
+ write(12,1042) (i*dlon,i=-5,5)
+1042 format(7x,13f6.2)
+ do j=6,-6,-1
+ write(*,1050) j*dlat,(nint(chisq(i,j)),i=-5,5)
+1050 format(f5.2,2x,13i6)
+ write(12,1060) j*dlat,(nint(chisq(i,j)),i=-5,5)
+1060 format(f5.2,2x,13i6)
+ enddo
+ endif
+ xlon0=blon
+ xlat0=blat
+ dlon=0.5*dlon
+ dlat=0.5*dlat
+ sqmin=1.e30
+ enddo
+
+999 end program qth
diff --git a/read_wav.f90 b/read_wav.f90
new file mode 100644
index 0000000..7b48368
--- /dev/null
+++ b/read_wav.f90
@@ -0,0 +1,45 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: read_wav.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine read_wav(lu,idat,npts,nfsample,nchan)
+
+! Write a wavefile to logical unit lu.
+
+ integer*2 idat(*)
+ integer*2 nfmt2,nchan2,nbitsam2,nbytesam2
+ character*4 ariff,awave,afmt,adata
+ integer*1 hdr(44)
+ common/hdr/ariff,nchunk,awave,afmt,lenfmt,nfmt2,nchan2,nsamrate, &
+ nbytesec,nbytesam2,nbitsam2,adata,ndata
+ equivalence (hdr,ariff)
+
+ read(lu) hdr
+ npts=ndata/(nchan2*nbitsam2/8)
+ nfsample=nsamrate
+ nchan=nchan2
+ read(lu) (idat(i),i=1,npts*nchan)
+
+ return
+end subroutine read_wav
diff --git a/resample.c b/resample.c
new file mode 100644
index 0000000..de090b9
--- /dev/null
+++ b/resample.c
@@ -0,0 +1,58 @@
+/*
+ *-------------------------------------------------------------------------------
+ *
+ * This file is part of the WSPR application, Weak Signal Propagation Reporter
+ *
+ * File Name: resample.c
+ * Description:
+ *
+ * Copyright (C) 2001-2014 Joseph Taylor, K1JT
+ * License: GPL-3
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+ * Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *-------------------------------------------------------------------------------
+*/
+
+#include <stdio.h>
+#include <samplerate.h>
+
+int resample_( float din[], float dout[], double *samfac, int *jz, int *ntype)
+{
+ SRC_DATA src_data;
+ int input_len;
+ int output_len;
+ int ierr;
+ int nchan=1;
+ double src_ratio;
+
+ src_ratio=*samfac;
+ input_len=*jz;
+ output_len=(int) (input_len*src_ratio);
+
+ src_data.data_in=din;
+ src_data.data_out=dout;
+ src_data.src_ratio=src_ratio;
+ src_data.input_frames=input_len;
+ src_data.output_frames=output_len;
+
+ ierr=src_simple(&src_data,*ntype,nchan);
+ *jz=output_len;
+ /* printf("%d %d %d %d %f\n",input_len,output_len,
+ src_data.input_frames_used,
+ src_data.output_frames_gen,src_ratio);
+ */
+ return ierr;
+}
diff --git a/rx.f90 b/rx.f90
new file mode 100644
index 0000000..beaea35
--- /dev/null
+++ b/rx.f90
@@ -0,0 +1,70 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: rx.f90
+! Description: Receive WSPR signals for one 2-minute sequence
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine rx
+
+! Receive WSPR signals for one 2-minute sequence.
+
+ integer time
+
+ integer soundin
+ include 'acom1.f90'
+
+ npts=114*12000
+ if(ntrminutes.eq.15) npts=890*12000
+ if(ncal.eq.1) npts=65536
+ nsec1=time()
+ nfhopok=0 !Don't hop!
+ f0a=f0 !Save rx frequency at start
+ ierr=soundin(ndevin,48000,kwave,4*npts,iqmode)
+ if(f0a.ne.f0) then
+! call cs_lock('rx')
+! write(70,*) 'Error in rx.f90 ',utctime,f0,f0a
+! call flush(70)
+ f0a=f0
+! call cs_unlock
+ endif
+ nfhopok=1 !Data acquisition done, can hop
+ if(ierr.ne.0) then
+ print*,'Error in soundin',ierr
+ stop
+ endif
+
+ if(iqmode.eq.1) then
+ call iqdemod(kwave,4*npts,nfiq,nbfo,iqrx,iqrxapp,gain,phase,iwave)
+ else
+ call fil1(kwave,4*npts,iwave,n2) !Filter and downsample
+ npts=n2
+ endif
+ nsec2=time()
+ call getrms(iwave,npts,ave,rms) !### is this needed any more??
+ call cs_lock('rx')
+ nrxdone=1
+ if(ncal.eq.1) ncal=2
+ call cs_unlock
+
+ return
+end subroutine rx
+
diff --git a/rxtest.f90 b/rxtest.f90
new file mode 100644
index 0000000..32100af
--- /dev/null
+++ b/rxtest.f90
@@ -0,0 +1,82 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: rxtest.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program rxtest
+
+ character*22 message
+ character*11 datetime
+ character*12 arg
+ real*8 freq
+ real a(5)
+ complex c3(45000),c4(45000)
+ complex c(65536)
+
+ nargs=iargc()
+ if(nargs.ne.1) then
+ print*,'Usage: rxtest <ifile>'
+ go to 999
+ endif
+ call getarg(1,arg)
+ read(arg,*) ireq
+
+ dt=1.0/375
+ jz=45000
+ ngood=0
+ do ifile=1,9999
+ read(71,end=900),datetime,nsnrx,dtx,freq,nf1,c3
+ if(ifile.ne.ireq .and. ireq.ne.0) go to 24
+
+ do idt=0,128
+ ii=(idt+1)/2
+ if(mod(idt,2).eq.1) ii=-ii
+ i1=nint((dtx+2.0)/dt) + ii !Start index for synced symbols
+ if(i1.ge.1) then
+ i2=i1 + jz - 1
+ c4(1:jz)=c3(i1:i2)
+ else if(i1.eq.0) then
+ c4(1)=0.
+ c4(2:jz)=c3(jz-1)
+ else
+ c4(:-i1+1)=0
+ i2=jz+i1
+ c4(-i1:)=c3(:i2)
+ endif
+! if(idt.eq.0) call afc2(c4)
+! call afc2(c4)
+ call decode162(c4,jz,message,ncycles,metric,nerr)
+ if(message(1:6).ne.' ') then
+ write(*,1012) ifile,nsnrx,dtx,freq,nf1,message,ii
+1012 format(i4.4,i4,f5.1,f11.6,i3,2x,a22,i5)
+ ngood=ngood+1
+ go to 24
+ endif
+ enddo
+24 continue
+ enddo
+
+900 if(ireq.eq.0) write(*,1024) ngood
+1024 format('ngood:',i5)
+
+999 end program rxtest
diff --git a/rxtxcoord.f90 b/rxtxcoord.f90
new file mode 100644
index 0000000..e6a3b89
--- /dev/null
+++ b/rxtxcoord.f90
@@ -0,0 +1,92 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: rxtxcoord.f90
+! Description: Determine Rx or Tx in coordinated hopping mode
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine rxtxcoord(ns0,pctx,nrx,ntxnext)
+
+! Determine Rx or Tx in coordinated hopping mode.
+
+ integer tx(10,6) !T/R array for 2 hours: 10 bands, 6 time slots
+ real r(6) !Random numbers
+ integer ii(1)
+ data n2hr0/-999/
+ save n2hr0,tx
+
+ nsec=(ns0+10)/120 !Round up to start of next 2-min slot
+ nsec=nsec*120
+ n2hr=nsec/7200 !2-hour slot number
+
+ if(n2hr.ne.n2hr0) then
+! Compute a new Rx/Tx pattern for this 2-hour interval
+ n2hr0=n2hr !Mark this one as done
+ tx=0 !Clear the tx array
+ do j=1,10 !Loop over all 10 bands
+ call random_number(r)
+ do i=1,6,2 !Select one each of 3 pairs of the
+ if(r(i).gt.r(i+1)) then ! 6 slots for Tx
+ tx(j,i)=1
+ r(i+1)=0.
+ else
+ tx(j,i+1)=1
+ r(i)=0.
+ endif
+ enddo
+
+ if(pctx.lt.50.0) then !If pctx < 50, we may kill one Tx slot
+ ii=maxloc(r)
+ i=ii(1)
+ call random_number(rr)
+ rrtest=(50.0-pctx)/16.667
+ if(rr.lt.rrtest) then
+ tx(j,i)=0
+ r(i)=0.
+ endif
+ endif
+
+ if(pctx.lt.33.333) then !If pctx < 33, may kill another
+ ii=maxloc(r)
+ i=ii(1)
+ call random_number(rr)
+ rrtest=(33.333-pctx)/16.667
+ if(rr.lt.rrtest) then
+ tx(j,i)=0
+ r(i)=0.
+ endif
+ endif
+ enddo
+
+! We now have 1 to 3 Tx periods per band in the 2-hour interval.
+ endif
+
+ iband=mod(nsec/120,10) + 1
+ iseq=mod(nsec/1200,6) + 1
+ if(iseq.lt.1) iseq=1
+ if(tx(iband,iseq).eq.1) then
+ ntxnext=1
+ else
+ nrx=1
+ endif
+
+ return
+end subroutine rxtxcoord
diff --git a/save/Samples/091022_0436.wav b/save/Samples/091022_0436.wav
new file mode 100644
index 0000000..0ea3f32
Binary files /dev/null and b/save/Samples/091022_0436.wav differ
diff --git a/set.f90 b/set.f90
new file mode 100644
index 0000000..119d387
--- /dev/null
+++ b/set.f90
@@ -0,0 +1,56 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: set.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine set(a,y,n)
+ real y(n)
+ do i=1,n
+ y(i)=a
+ enddo
+ return
+end subroutine set
+
+subroutine move(x,y,n)
+ real x(n),y(n)
+ do i=1,n
+ y(i)=x(i)
+ enddo
+ return
+end subroutine move
+
+subroutine zero(x,n)
+ real x(n)
+ do i=1,n
+ x(i)=0.0
+ enddo
+ return
+end subroutine zero
+
+subroutine add(a,b,c,n)
+ real a(n),b(n),c(n)
+ do i=1,n
+ c(i)=a(i)+b(i)
+ enddo
+ return
+end subroutine add
diff --git a/slope.f90 b/slope.f90
new file mode 100644
index 0000000..4c04935
--- /dev/null
+++ b/slope.f90
@@ -0,0 +1,66 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: slope.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine slope(y,npts,xpk)
+
+! Remove best-fit slope from data in y(i). When fitting the straight line,
+! ignore the peak around xpk +/- 2.
+
+ real y(npts)
+ real x(100)
+
+ do i=1,npts
+ x(i)=i
+ enddo
+
+ sumw=0.
+ sumx=0.
+ sumy=0.
+ sumx2=0.
+ sumxy=0.
+ sumy2=0.
+
+ do i=1,npts
+ if(abs(i-xpk).gt.2.0) then
+ sumw=sumw + 1.0
+ sumx=sumx + x(i)
+ sumy=sumy + y(i)
+ sumx2=sumx2 + x(i)**2
+ sumxy=sumxy + x(i)*y(i)
+ sumy2=sumy2 + y(i)**2
+ endif
+ enddo
+
+ delta=sumw*sumx2 - sumx**2
+ a=(sumx2*sumy - sumx*sumxy) / delta
+ b=(sumw*sumxy - sumx*sumy) / delta
+
+ do i=1,npts
+ y(i)=y(i)-(a + b*x(i))
+ enddo
+
+ return
+end subroutine slope
+
diff --git a/sort.f90 b/sort.f90
new file mode 100644
index 0000000..dee64d2
--- /dev/null
+++ b/sort.f90
@@ -0,0 +1,29 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: sort.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine sort(n,arr)
+ call ssort(arr,tmp,n,1)
+ return
+end subroutine sort
diff --git a/sound.c b/sound.c
new file mode 100644
index 0000000..7c0609b
--- /dev/null
+++ b/sound.c
@@ -0,0 +1,182 @@
+/*
+ *-------------------------------------------------------------------------------
+ *
+ * This file is part of the WSPR application, Weak Signal Propagation Reporter
+ *
+ * File Name: sound.c
+ * Description:
+ *
+ * Copyright (C) 2001-2014 Joseph Taylor, K1JT
+ * License: GPL-3
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+ * Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *-------------------------------------------------------------------------------
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "portaudio.h"
+
+/* #define DITHER_FLAG (paDitherOff) */
+#define DITHER_FLAG (0) /**/
+#define PA_SAMPLE_TYPE paInt16
+typedef short SAMPLE;
+
+int soundinit_(void)
+{
+ PaError err;
+ err = Pa_Initialize();
+ if( err == paNoError ) {
+ return 0;
+ }
+ else {
+// Pa_Terminate();
+ fprintf( stderr, "An error occured when initializing the audio stream\n");
+ fprintf( stderr, "Error number: %d\n", err );
+ fprintf( stderr, "WSPR will now exit/n");
+ exit(255);
+ }
+}
+
+int soundexit_(void)
+{
+ Pa_Terminate();
+ return 0;
+}
+
+int soundin_(int *idevin, int *nrate0, short recordedSamples[],
+ int *nframes0, int *iqmode)
+{
+ PaStreamParameters inputParameters;
+ PaStream *stream;
+ PaError err;
+ int i;
+ int totalFrames;
+ int numSamples;
+ int numBytes;
+ int num_channels;
+ int nrate;
+ int frames_per_buffer=1024;
+
+ nrate=*nrate0;
+ if(nrate>12000) frames_per_buffer=4096;
+ totalFrames=*nframes0;
+ num_channels=*iqmode + 1;
+ numSamples = totalFrames * num_channels;
+ numBytes = numSamples * sizeof(SAMPLE);
+ for( i=0; i<numSamples; i++ )
+ recordedSamples[i] = 0;
+
+ inputParameters.device = *idevin;
+ if(*idevin<0) inputParameters.device = Pa_GetDefaultInputDevice();
+ inputParameters.channelCount = num_channels;
+ inputParameters.sampleFormat = PA_SAMPLE_TYPE;
+ inputParameters.suggestedLatency = 0.4;
+ inputParameters.hostApiSpecificStreamInfo = NULL;
+
+ err = Pa_OpenStream(
+ &stream,
+ &inputParameters,
+ NULL, /* &outputParameters, */
+ nrate,
+ frames_per_buffer,
+ paClipOff,
+ NULL, /* no callback, use blocking API */
+ NULL ); /* no callback, so no callback userData */
+ if( err != paNoError ) goto error;
+
+ err = Pa_StartStream( stream );
+ if( err != paNoError ) goto error;
+
+ err = Pa_ReadStream( stream, recordedSamples, totalFrames );
+ if( err != paNoError ) goto error;
+
+ err = Pa_CloseStream( stream );
+ if( err != paNoError ) goto error;
+ return 0;
+
+error:
+ Pa_Terminate();
+ fprintf( stderr, "An error occured while using the portaudio stream\n" );
+ fprintf( stderr, "Error number: %d\n", err );
+ fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
+ soundinit_();
+ return -1;
+}
+
+int soundout_(int *idevout, int *nrate0, short recordedSamples[],
+ int *nframes0, int *iqmode)
+{
+ PaStreamParameters outputParameters;
+ PaStream *stream;
+ PaError err;
+ int totalFrames;
+ int numSamples;
+ int numBytes;
+ int num_channels;
+ int nrate;
+ int frames_per_buffer=1024;
+
+ nrate=*nrate0;
+ if(nrate>12000) frames_per_buffer=4096;
+ totalFrames=*nframes0;
+ num_channels=*iqmode + 1;
+ numSamples = totalFrames * num_channels;
+ numBytes = numSamples * sizeof(SAMPLE);
+ outputParameters.device = *idevout;
+ if(*idevout<0) outputParameters.device = Pa_GetDefaultOutputDevice();
+ outputParameters.channelCount = num_channels;
+ outputParameters.sampleFormat = PA_SAMPLE_TYPE;
+ outputParameters.suggestedLatency = 0.4;
+ outputParameters.hostApiSpecificStreamInfo = NULL;
+
+ err = Pa_OpenStream(
+ &stream,
+ NULL, /* no input */
+ &outputParameters,
+ nrate,
+ frames_per_buffer,
+ paClipOff,
+ NULL, /* no callback, use blocking API */
+ NULL ); /* no callback, so no callback userData */
+ if( err != paNoError ) goto error;
+
+ if( stream )
+ {
+ err = Pa_StartStream( stream );
+ if( err != paNoError ) goto error;
+
+ err = Pa_WriteStream( stream, recordedSamples, totalFrames );
+ if( err != paNoError ) goto error;
+
+ err = Pa_CloseStream( stream );
+ if( err != paNoError ) goto error;
+ }
+ return 0;
+
+error:
+ Pa_Terminate();
+ fprintf( stderr, "An error occured while using the portaudio stream\n" );
+ fprintf( stderr, "Error number: %d\n", err );
+ fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
+ soundinit_();
+ return -1;
+}
+
+void msleep_(int *msec0)
+{
+ Pa_Sleep(*msec0);
+}
diff --git a/spec162.f90 b/spec162.f90
new file mode 100644
index 0000000..00d1bb6
--- /dev/null
+++ b/spec162.f90
@@ -0,0 +1,124 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: spec162.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine spec162(c2,jz,appdir,nappdir)
+
+ parameter(NX=500,NY=160)
+ complex c2(65536)
+ complex c(0:255)
+ character*80 appdir,pixmap
+ real s(120,0:255)
+ real ss(0:255)
+ real w(0:255)
+ real savg(0:255)
+ integer*2 a(NX,NY)
+ common/bcom/ntransmitted
+ common/fftcom/c !This keeps the absolute address of c() constant
+
+ nfft=256
+ twopi=6.2831853
+ pi=0.5*twopi
+ do i=0,nfft-1
+ w(i)=sin(i*pi/nfft)
+ enddo
+
+ nadd=9
+ s=0.
+ save=0.
+ istep=nfft/2
+ nsteps=(jz-nfft)/(nadd*istep)
+ pixmap=appdir(:nappdir)//'/pixmap.dat'
+
+ call cs_lock('spec162')
+ open(16,file=pixmap,access='stream',status='unknown',err=1)
+ read(16,end=1) a
+ go to 2
+1 a=0.
+
+2 nmove=nsteps+1
+ call cs_unlock
+
+ do j=1,NY !Move waterfall left
+ do i=1,NX-nmove
+ a(i,j)=a(i+nmove,j)
+ enddo
+ a(NX-nmove+1,j)=255*ntransmitted
+ enddo
+ ntransmitted=0
+
+ i0=-istep+1
+ k=0
+ do n=1,nsteps
+ k=k+1
+ ss=0.
+ do m=1,nadd
+ i0=i0+istep
+ do i=0,nfft-1
+ c(i)=w(i)*c2(i0+i)
+ enddo
+ call four2a(c,nfft,1,-1,1)
+ do i=0,nfft-1
+ sq=real(c(i))**2 + imag(c(i))**2
+ ss(i)=ss(i) + sq
+ savg(i)=savg(i) + sq
+ enddo
+ enddo
+ call flat3(ss,256,nadd)
+ do i=0,nfft-1
+ s(k,i)=ss(i)
+ enddo
+ enddo
+ kz=k
+
+ gain=40
+ offset=-90.
+ fac=20.0/nadd
+
+ do k=1,kz
+ j=k-kz+NX
+ do i=-80,-1
+ x=fac*s(k,i+nfft)
+ n=0
+ if(x.gt.0.0) n=gain*log10(x) + offset
+ n=min(252,max(0,n))
+ a(j,NY-i-80)=n
+ enddo
+ do i=0,79
+ x=fac*s(k,i)
+ n=0
+ if(x.gt.0.0) n=gain*log10(x) + offset
+ n=min(252,max(0,n))
+ a(j,NY-i-80)=n
+ enddo
+ enddo
+
+ call cs_lock('spec162')
+ rewind 16
+ write(16) a
+ close(16)
+ call cs_unlock
+
+ return
+end subroutine spec162
diff --git a/speciq.f90 b/speciq.f90
new file mode 100644
index 0000000..2be5c3c
--- /dev/null
+++ b/speciq.f90
@@ -0,0 +1,119 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: speciq.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine speciq(kwave,npts,iwrite,iqrx,nfiq,ireset,gain,phase,reject)
+
+ parameter (NFFT=32768)
+ parameter (NH=NFFT/2)
+ integer*2 kwave(2,npts)
+ real s(-NH+1:NH)
+ complex c,z,zsum,zave
+ complex c0
+ complex h,u,v
+ common/fftcom2/c(0:NFFT-1),c0(0:NFFT-1)
+ data iw0/-999/
+ save
+
+ if(ireset.eq.1) then
+ nn=0
+ zsum=0.
+ ireset=0
+ rsum=0.
+ endif
+
+ df=48000.0/NFFT
+
+ if(iwrite.lt.nfft .or. iwrite.eq.iw0) go to 900
+ iw0=iwrite
+ nn=nn+1
+ fac=10.0**(-4.3)
+ j=iwrite-nfft
+ do i=0,nfft-1
+ j=j+1
+ if(iqrx.eq.0) then
+ x=kwave(2,j)
+ y=kwave(1,j)
+ else
+ x=kwave(1,j)
+ y=kwave(2,j)
+ endif
+ c(i)=fac*cmplx(x,y)
+ enddo
+ c0=c
+
+ call four2a(c,NFFT,1,-1,1) ! 1d, forward, complex
+
+ smax=0.
+ ia=(nfiq+500)/df
+ ib=(nfiq+2500)/df
+ ipk=0
+ do i=0,nfft-1
+ j=i
+ if(j.gt.NH) j=j-nfft
+ s(j)=real(c(i))**2 + aimag(c(i))**2
+ if(i.ge.ia .and. i.le.ib .and. s(j).gt.smax) then
+ smax=s(j)
+ ipk=i
+ endif
+ enddo
+
+ if(ipk.eq.0) then
+ print*,'b',ia,ib,ipk,iwrite
+ go to 900
+ endif
+
+ p=s(ipk) + s(-ipk)
+ z=c(ipk)*c(nfft-ipk)/p
+ zsum=zsum+z
+ zave=zsum/nn
+ tmp=sqrt(1.0 - (2.0*real(zave))**2)
+ phase=asin(2.0*aimag(zave)/tmp)
+ gain=tmp/(1.0-2.0*real(zave))
+ h=gain*cmplx(cos(phase),sin(phase))
+
+ u=c(ipk)
+ v=c(nfft-ipk)
+ x=real(u) + real(v) - (aimag(u) + aimag(v))*aimag(h) + &
+ (real(u) - real(v))*real(h)
+ y=aimag(u) - aimag(v) + (aimag(u) + aimag(v))*real(h) + &
+ (real(u) - real(v))*aimag(h)
+ p1=x*x + y*y
+
+ u=c(nfft-ipk)
+ v=c(ipk)
+ x=real(u) + real(v) - (aimag(u) + aimag(v))*aimag(h) + &
+ (real(u) - real(v))*real(h)
+ y=aimag(u) - aimag(v) + (aimag(u) + aimag(v))*real(h) + &
+ (real(u) - real(v))*aimag(h)
+ p2=x*x + y*y
+
+ r=db(p1/p2)
+ if(nn.ge.2) then
+ rsum=rsum+r
+ reject=rsum/(nn-1)
+ endif
+
+900 return
+end subroutine speciq
diff --git a/ssort.f90 b/ssort.f90
new file mode 100644
index 0000000..31f462c
--- /dev/null
+++ b/ssort.f90
@@ -0,0 +1,302 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: ssort.f90
+! Description: Sort an array and optionally make the same interchanges in
+! an auxiliary array.
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine ssort (x,y,n,kflag)
+! Purpose: Sort an array and optionally make the same interchanges in
+! an auxiliary array. The array may be sorted in increasing
+! or decreasing order. A slightly modified quicksort
+! algorithm is used.
+
+! x - array of values to be sorted
+! y - array to be (optionally) carried along
+! n - number of values in array x to be sorted
+! kflag - control parameter
+! = 2 means sort x in increasing order and carry y along.
+! = 1 means sort x in increasing order (ignoring y)
+! = -1 means sort x in decreasing order (ignoring y)
+! = -2 means sort x in decreasing order and carry y along.
+
+ integer kflag, n
+ real x(n), y(n)
+ real r, t, tt, tty, ty
+ integer i, ij, j, k, kk, l, m, nn
+ integer il(21), iu(21)
+
+ nn = n
+ if (nn .lt. 1) then
+ print*,'ssort: The number of sort elements is not positive.'
+ print*,'ssort: n = ',nn,' kflag = ',kflag
+ return
+ endif
+
+ kk = abs(kflag)
+ if (kk.ne.1 .and. kk.ne.2) then
+ print *,'The sort control parameter, k, is not 2, 1, -1, or -2.'
+ return
+ endif
+
+! Alter array x to get decreasing order if needed
+
+ if (kflag .le. -1) then
+ do i=1,nn
+ x(i) = -x(i)
+ enddo
+ endif
+
+ if (kk .eq. 2) go to 100
+
+! Sort x only
+
+ m = 1
+ i = 1
+ j = nn
+ r = 0.375e0
+
+20 if (i .eq. j) go to 60
+ if (r .le. 0.5898437e0) then
+ r = r+3.90625e-2
+ else
+ r = r-0.21875e0
+ endif
+
+30 k = i
+
+! Select a central element of the array and save it in location t
+
+ ij = i + int((j-i)*r)
+ t = x(ij)
+
+! If first element of array is greater than t, interchange with t
+
+ if (x(i) .gt. t) then
+ x(ij) = x(i)
+ x(i) = t
+ t = x(ij)
+ endif
+ l = j
+
+! If last element of array is less than than t, interchange with t
+
+ if (x(j) .lt. t) then
+ x(ij) = x(j)
+ x(j) = t
+ t = x(ij)
+
+! If first element of array is greater than t, interchange with t
+
+ if (x(i) .gt. t) then
+ x(ij) = x(i)
+ x(i) = t
+ t = x(ij)
+ endif
+ endif
+
+! Find an element in the second half of the array which is smaller than t
+
+40 l = l-1
+ if (x(l) .gt. t) go to 40
+
+! Find an element in the first half of the array which is greater than t
+
+50 k = k+1
+ if (x(k) .lt. t) go to 50
+
+! Interchange these elements
+
+ if (k .le. l) then
+ tt = x(l)
+ x(l) = x(k)
+ x(k) = tt
+ go to 40
+ endif
+
+! Save upper and lower subscripts of the array yet to be sorted
+
+ if (l-i .gt. j-k) then
+ il(m) = i
+ iu(m) = l
+ i = k
+ m = m+1
+ else
+ il(m) = k
+ iu(m) = j
+ j = l
+ m = m+1
+ endif
+ go to 70
+
+! Begin again on another portion of the unsorted array
+
+60 m = m-1
+ if (m .eq. 0) go to 190
+ i = il(m)
+ j = iu(m)
+
+70 if (j-i .ge. 1) go to 30
+ if (i .eq. 1) go to 20
+ i = i-1
+
+80 i = i+1
+ if (i .eq. j) go to 60
+ t = x(i+1)
+ if (x(i) .le. t) go to 80
+ k = i
+
+90 x(k+1) = x(k)
+ k = k-1
+ if (t .lt. x(k)) go to 90
+ x(k+1) = t
+ go to 80
+
+! Sort x and carry y along
+
+100 m = 1
+ i = 1
+ j = nn
+ r = 0.375e0
+
+110 if (i .eq. j) go to 150
+ if (r .le. 0.5898437e0) then
+ r = r+3.90625e-2
+ else
+ r = r-0.21875e0
+ endif
+
+120 k = i
+
+! Select a central element of the array and save it in location t
+
+ ij = i + int((j-i)*r)
+ t = x(ij)
+ ty = y(ij)
+
+! If first element of array is greater than t, interchange with t
+
+ if (x(i) .gt. t) then
+ x(ij) = x(i)
+ x(i) = t
+ t = x(ij)
+ y(ij) = y(i)
+ y(i) = ty
+ ty = y(ij)
+ endif
+ l = j
+
+! If last element of array is less than t, interchange with t
+
+ if (x(j) .lt. t) then
+ x(ij) = x(j)
+ x(j) = t
+ t = x(ij)
+ y(ij) = y(j)
+ y(j) = ty
+ ty = y(ij)
+
+! If first element of array is greater than t, interchange with t
+
+ if (x(i) .gt. t) then
+ x(ij) = x(i)
+ x(i) = t
+ t = x(ij)
+ y(ij) = y(i)
+ y(i) = ty
+ ty = y(ij)
+ endif
+ endif
+
+! Find an element in the second half of the array which is smaller than t
+
+130 l = l-1
+ if (x(l) .gt. t) go to 130
+
+! Find an element in the first half of the array which is greater than t
+
+140 k = k+1
+ if (x(k) .lt. t) go to 140
+
+! Interchange these elements
+
+ if (k .le. l) then
+ tt = x(l)
+ x(l) = x(k)
+ x(k) = tt
+ tty = y(l)
+ y(l) = y(k)
+ y(k) = tty
+ go to 130
+ endif
+
+! Save upper and lower subscripts of the array yet to be sorted
+
+ if (l-i .gt. j-k) then
+ il(m) = i
+ iu(m) = l
+ i = k
+ m = m+1
+ else
+ il(m) = k
+ iu(m) = j
+ j = l
+ m = m+1
+ endif
+ go to 160
+
+! Begin again on another portion of the unsorted array
+
+150 m = m-1
+ if (m .eq. 0) go to 190
+ i = il(m)
+ j = iu(m)
+
+160 if (j-i .ge. 1) go to 120
+ if (i .eq. 1) go to 110
+ i = i-1
+
+170 i = i+1
+ if (i .eq. j) go to 150
+ t = x(i+1)
+ ty = y(i+1)
+ if (x(i) .le. t) go to 170
+ k = i
+
+180 x(k+1) = x(k)
+ y(k+1) = y(k)
+ k = k-1
+ if (t .lt. x(k)) go to 180
+ x(k+1) = t
+ y(k+1) = ty
+ go to 170
+
+! Clean up
+
+190 if (kflag .le. -1) then
+ do i=1,nn
+ x(i) = -x(i)
+ enddo
+ endif
+
+ return
+end subroutine ssort
diff --git a/start_threads.c b/start_threads.c
new file mode 100644
index 0000000..56b4906
--- /dev/null
+++ b/start_threads.c
@@ -0,0 +1,105 @@
+/*
+ *-------------------------------------------------------------------------------
+ *
+ * This file is part of the WSPR application, Weak Signal Propagation Reporter
+ *
+ * File Name: start_threads.c
+ * Description:
+ *
+ * Copyright (C) 2001-2014 Joseph Taylor, K1JT
+ * License: GPL-3
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+ * Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ *-------------------------------------------------------------------------------
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef Win32
+ #include "pthread_w32.h"
+#else
+ #include <pthread.h>
+#endif
+#include <inttypes.h>
+#include <time.h>
+#include <sys/time.h>
+
+extern void wspr2_(int *iarg);
+extern void decode_(int *iarg);
+extern void rx_(int *iarg);
+extern void tx_(int *iarg);
+
+pthread_t decode_thread;
+static int decode_started=0;
+
+int spawn_thread(void (*f)(int *n)) {
+ pthread_t thread;
+ int iret;
+ int iarg0 = 0;
+
+ iret=pthread_create(&thread,NULL,(void *)f,&iarg0);
+ if (iret) {
+ perror("spawning new thread");
+ return iret;
+ }
+
+ iret = pthread_detach(thread);
+ if (iret) {
+ perror("detaching thread");
+ return iret;
+ }
+ return 0;
+}
+
+
+int th_wspr2_(void)
+{
+ int ierr;
+ ierr=spawn_thread(wspr2_);
+ return ierr;
+}
+
+int th_decode_(void)
+{
+ int iret1;
+ int iarg1 = 1;
+
+ if(decode_started>0) {
+ /*
+ // the following was "< 100":
+ if(time(NULL)-decode_started < 2) {
+ printf("Attempted to start decoder too soon: %d %d",
+ (int)time(NULL),decode_started);
+ return 0;
+ }
+ */
+ pthread_join(decode_thread,NULL);
+ decode_started=0;
+ }
+ iret1 = pthread_create(&decode_thread,NULL,(void *)decode_,&iarg1);
+ // if(iret1==0) decode_started=time(NULL);
+ return iret1;
+}
+
+int th_rx_(void)
+{
+ return spawn_thread(rx_);
+}
+
+int th_tx_(void)
+{
+ return spawn_thread(tx_);
+}
diff --git a/startdec.f90 b/startdec.f90
new file mode 100644
index 0000000..756355e
--- /dev/null
+++ b/startdec.f90
@@ -0,0 +1,39 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: startdec.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine startdec
+
+ integer th_decode
+ external decode
+ include 'acom1.f90'
+
+ ierr=th_decode()
+ if(ierr.ne.0) then
+ print*,'Error starting decode thread',ierr
+ stop
+ endif
+
+ return
+end subroutine startdec
diff --git a/startrx.f90 b/startrx.f90
new file mode 100644
index 0000000..5ef927c
--- /dev/null
+++ b/startrx.f90
@@ -0,0 +1,39 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: startrx.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine startrx
+
+ integer th_rx
+
+ include 'acom1.f90'
+
+ ierr=th_rx()
+ if(ierr.ne.0) then
+ print*,'Error starting rx thread',ierr
+ stop
+ endif
+
+ return
+end subroutine startrx
diff --git a/starttx.f90 b/starttx.f90
new file mode 100644
index 0000000..ba2cf73
--- /dev/null
+++ b/starttx.f90
@@ -0,0 +1,39 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: starttx.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine starttx
+
+ integer th_tx
+
+ include 'acom1.f90'
+
+ ierr=th_tx()
+ if(ierr.ne.0) then
+ print*,'Error starting tx thread',ierr
+ stop
+ endif
+
+ return
+end subroutine starttx
diff --git a/sync162.f90 b/sync162.f90
new file mode 100644
index 0000000..0341999
--- /dev/null
+++ b/sync162.f90
@@ -0,0 +1,223 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: sync162.f90
+! Description: Find MEPT_JT sync signals, with best-fit DT and DF
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine sync162(c2,jz,ps,sstf,kz)
+
+! Find MEPT_JT sync signals, with best-fit DT and DF.
+
+ complex c2(jz)
+ parameter (NFFT=512) !Length of FFTs
+ parameter (NH=NFFT/2) !Length of power spectra
+ parameter (NSMAX=351) !Number of half-symbol steps
+ parameter (NF0=136,NF1=10)
+ parameter (LAGMAX=26)
+ real psavg(-NH:NH) !Average spectrum of whole record
+ real s2(-NH:NH,NSMAX) !2d spectrum, stepped by half-symbols
+ real ps(-NH:NH)
+ real psmo(-NH:NH)
+ real freq(-NH:NH)
+ real p1(-NH:NH)
+ real drift(-NH:NH)
+ real dtx(-NH:NH)
+ integer keep0(-NH:NH)
+ integer keep(-NH:NH)
+ real a(5)
+ real sstf(5,275)
+ real tmp(275)
+ integer npr3(162)
+ real pr3(162)
+ data npr3/ &
+ 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, &
+ 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, &
+ 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, &
+ 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, &
+ 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, &
+ 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, &
+ 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, &
+ 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, &
+ 0,0/
+ save
+
+ nsym=162
+ do i=1,nsym
+ pr3(i)=2*npr3(i)-1
+ enddo
+
+! Do FFTs of twice symbol length, stepped by half symbols.
+ nq=NFFT/4
+ nsteps=jz/nq - 1
+ df=375.0/nfft
+ dt=1.0/375.0
+ psavg=0.
+
+! Compute power spectrum for each step, and get average
+ do j=1,nsteps
+ k=(j-1)*nq + 1
+ call ps162(c2(k),s2(-NH,j))
+ psavg = psavg + s2(-NH:NH,j)
+ enddo
+
+! Normalize and subtract baseline from psavg.
+ call pctile(psavg(-136),tmp,273,35,base)
+ psavg=psavg/base - 1.0
+ base=base/351.0
+ s2=s2/base - 1.0
+
+! Boxcar-smooth the average spectrum over the WSPR signal bandwidth.
+ do i=-NH+3,NH-3
+ psmo(i)=0.
+ do k=-3,3
+ psmo(i)=psmo(i)+ps(i+k)
+ enddo
+ psmo(i)=psmo(i)/7.0
+ enddo
+
+! Mark potential suspects for WSPR signals.
+! (Keep only the best one within a surrounding range of +/- 8 bins.)
+
+ plimit=0.1 !### Are the plimit values OK? ###
+ do i=-NF0,NF0
+ keep0(i)=0
+ keep(i)=0
+ ia=i-4
+ ib=i+4
+ pmax=-1.e30
+ do ii=ia,ib
+ if(psmo(ii).gt.pmax) then
+ ipk=ii
+ pmax=psmo(ii)
+ endif
+ enddo
+ if(ipk.eq.i .and. pmax.ge.plimit) then
+ keep0(i)=1
+! Kill all smaller peaks leading up to this maximum.
+ do ii=ia,i-1
+ keep0(ii)=0
+ enddo
+ endif
+ enddo
+
+! Now mark the bins +/- 1 from each one already marked.
+ do i=-NF0+1,NF0-1
+ if(keep0(i).eq.1) then
+ keep(i-1)=1
+ keep(i)=1
+ keep(i+1)=1
+ endif
+ enddo
+
+! Now do the main search over DT, DF, and drift. (Do CCFs in all marked
+! frequency bins and over a range of reasonable fdot values and lags.)
+ p1=0.
+ do i=-NF0,NF0
+ if(keep(i).eq.0) go to 10
+ smax=0.
+ do k=-NF1,NF1
+ if(abs(k).ne.1) then
+ do lag=0,LAGMAX
+ sum=0.
+ n=lag-1
+ do j=1,162
+ n=n+2
+ ii=i + nint(k*(j-81)/162.0)
+ x=max(s2(ii-1,n),s2(ii+3,n)) - max(s2(ii-3,n),s2(ii+1,n))
+ sum=sum + x*pr3(j)
+ enddo
+ if(sum.gt.smax) then
+ kpk=k
+ lagpk=lag
+ smax=sum
+ endif
+ enddo
+ endif
+ enddo
+
+! Save the CCF value, frequency, drift rate, and DT.
+ p1(i)=smax
+ freq(i)=df*i
+ drift(i)=df*kpk
+ dtx(i)=128.0*dt*lagpk
+10 continue
+ enddo
+
+! Eliminate potential duplicates and peaks smaller than plimit.
+ keep=0
+ plimit=1.0
+ do i=-NF0,NF0
+ ia=max(-NF0,i-8)
+ ib=min(NF0,i+8)
+ pmax=-1.e30
+ do ii=ia,ib
+ if(p1(ii).gt.pmax) then
+ ipk=ii
+ pmax=p1(ii)
+ endif
+ enddo
+ if(ipk.eq.i .and. pmax.ge.plimit) then
+ keep(i)=1
+ do ii=ia,i-1
+ keep(ii)=0
+ enddo
+ endif
+ enddo
+
+! Compress the candidate list, saving only the potentially important ones.
+! Recalibrate sync indicator p1 on a dB scale.
+! (NB: p1 sould be compared with snrx!)
+ k=0
+ do i=-NF0,NF0
+ if(keep(i).ne.0) then
+ x=10.0*log10(p1(i)) - 22
+ if(x.ge.0.5) then
+ k=min(k+1,275)
+ p1(k)=x
+ freq(k)=freq(i)
+ drift(k)=drift(i)
+ dtx(k)=dtx(i) - 2.0
+ endif
+ endif
+ enddo
+ kz=k
+
+ do k=1,kz
+ a(1)=-freq(k)
+ a(2)=-0.5*drift(k)
+ a(3)=0.
+ lagpk=nint((dtx(k)+2)/(128*dt))
+ lag1=max(-200,8*lagpk-16)
+ lag2=min(200,8*lagpk+16)
+ ccf=fchisq(c2,jz,375.0,a,lag1,lag2,ccfbest,dtbest)
+ ipk=nint(freq(k)/df)
+ snrx=db(max(psavg(ipk),0.0001)) - 26.5 !Empirical
+ sstf(1,k)=p1(k)
+ sstf(2,k)=snrx
+ sstf(3,k)=dtbest-2.0
+ sstf(4,k)=freq(k)
+ sstf(5,k)=drift(k)
+ enddo
+
+ return
+end subroutine sync162
+
diff --git a/t1.f90 b/t1.f90
new file mode 100644
index 0000000..791de6b
--- /dev/null
+++ b/t1.f90
@@ -0,0 +1,150 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: t1.f90
+! Description: Find time delay between 1 PPS ticks from GPS and WWV
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program t1
+
+! Find time delay between 1 PPS ticks from GPS and WWV.
+
+ parameter (NFSMAX=48000)
+ parameter (NMAX=310*NFSMAX) !Max length of data
+ character cdate*8 !CCYYMMDD
+ character ctime*6 !HHMMSS.SSS
+ character*6 mycall,mygrid
+ character cjunk*1
+ real*8 p1
+ integer iipk(1)
+ real prof1(NFSMAX)
+ real xx(NFSMAX)
+ real notch(-20:20)
+ complex c(0:NFSMAX/2)
+ complex cal1(0:35)
+ real ccf1(0:NFSMAX/6),ccf2(0:NFSMAX/6)
+ real delay(4)
+ real snr(4)
+ equivalence (iipk,ipk)
+ equivalence (xx,c)
+
+ open(20,file='wwv.bin',form='unformatted',status='old')
+ open(21,file='cal.dat',status='unknown')
+
+ nfs=48000
+ ip=nfs
+ dt=1.0/nfs
+ lagmax=nfs/6
+ irec=0
+ iz=0.150/dt
+
+ do j=1,35
+ read(21,1001) jj,cal1(j)
+1001 format(i6,2f10.3)
+ f=j*100.0
+ x=0.
+ if(f.lt.300.0) x=(f-300.0)/200.0
+ if(f.gt.3000.0) x=(3000-f)/200.0
+ cal1(j)=exp(-x*x)/cal1(j)
+ enddo
+ cal1(0)=0.
+
+ do i=-20,20
+ x=float(i)/20.0
+ notch(i)=1.0 - exp(-x*x)
+ enddo
+
+10 irec=irec+1
+ read(20,end=999) cdate,ctime,day,xdelay,ccfmax1,ikhz,p1,mycall,mygrid,prof1
+ read(ctime(4:4),*) i10
+ xx=prof1
+
+ iipk=maxloc(xx)
+ do i=1,ip
+ j=ipk+i-nint(0.001/dt)
+ if(j.lt.1) j=j+ip
+ if(j.gt.ip) j=j-ip
+ t=1000.0*i*dt - 1.0
+ write(13,3001) t,xx(j)
+3001 format(2f10.3)
+ enddo
+
+ call four2a(xx,ip,1,-1,0) !Forward FFT of profile
+
+ df=float(nfs)/ip
+ ib=nint(3500.0/df)
+ do i=0,ib
+ j=nint(0.01*i*df)
+ c(i)=c(i)*cal1(j)
+ enddo
+! c(0)=0.
+ c(ib:)=0.
+ c(95:105)=0.
+ if(ctime(3:4).eq.'02') then
+ c(420:460)=c(420:460)*notch
+ else
+ if(mod(i10,2).eq.1) then
+ c(580:620)=c(580:620)*notch
+ else
+ c(480:520)=c(480:520)*notch
+ endif
+ endif
+
+! do i=0,ib
+! s=real(c(i))**2 + aimag(c(i))**2
+! pha=atan2(aimag(c(i)),real(c(i)))
+! write(13,1030) i*df,s,db(s),pha
+!1030 format(f10.3,f12.3,2f10.3)
+! enddo
+
+ call four2a(c,ip,1,1,-1) !Inverse FFT ==> calibrated profile
+
+ fac=6.62/ip
+ xx=fac*xx
+
+ iipk=maxloc(xx)
+ do i=1,ip
+ j=ipk+i-nint(0.001/dt)
+ if(j.lt.1) j=j+ip
+ if(j.gt.ip) j=j-ip
+ t=1000.0*i*dt - 1.0
+ write(14,3001) t,xx(j)
+ enddo
+
+ call clean(xx,ipk,snr,delay,nwwv,nd)
+
+ do i=1,nd
+ write(*,1000) irec,ctime,day,ikhz,snr(i),delay(i)
+ write(16,1000) irec,ctime,day,ikhz,snr(i),delay(i)
+1000 format(i6,2x,a6,f10.4,i7,2f8.2)
+ enddo
+
+ call flush(13)
+ call flush(14)
+ call flush(16)
+ rewind 13
+ rewind 14
+
+ read*,cjunk
+ if(cjunk.eq.'q') go to 999
+ go to 10
+
+999 end program t1
diff --git a/thcvf.f90 b/thcvf.f90
new file mode 100644
index 0000000..1213953
--- /dev/null
+++ b/thcvf.f90
@@ -0,0 +1,93 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: thcvf.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine cs_init
+ use dfmt
+ type (RTL_CRITICAL_SECTION) ncrit1
+ character*12 csub0
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+ ltrace=0
+ mtx=loc(ncrit1)
+ mtxstate=0
+ csub0='**unlocked**'
+ call InitializeCriticalSection(mtx)
+ return
+end subroutine cs_init
+
+subroutine cs_destroy
+ use dfmt
+ type (RTL_CRITICAL_SECTION) ncrit1
+ character*12 csub0
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+ call DeleteCriticalSection(mtx)
+ return
+end subroutine cs_destroy
+
+subroutine th_create(sub)
+ use dfmt
+ external sub
+ ith=CreateThread(0,0,sub,0,0,id)
+ return
+end subroutine th_create
+
+subroutine th_exit
+ use dfmt
+ ncode=0
+ call ExitThread(ncode)
+ return
+end subroutine th_exit
+
+subroutine cs_lock(csub)
+ use dfmt
+ character*(*) csub
+ character*12 csub0
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+ n=TryEnterCriticalSection(mtx)
+ if(n.eq.0) then
+! Another thread has already locked the mutex
+ call EnterCriticalSection(mtx)
+ iz=index(csub0,' ')
+ if(ltrace.ge.1) print*,'"',csub,'" requested the mutex when "', &
+ csub0(:iz-1),'" owned it.'
+ endif
+ mtxstate=1
+ csub0=csub
+ if(ltrace.ge.3) print*,'Mutex locked by ',csub
+ return
+end subroutine cs_lock
+
+subroutine cs_unlock
+ use dfmt
+ character*12 csub0
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+ mtxstate=0
+ if(ltrace.ge.3) print*,'Mutex unlocked'
+ call LeaveCriticalSection(mtx)
+ return
+end subroutine cs_unlock
diff --git a/thnix.f90 b/thnix.f90
new file mode 100644
index 0000000..831b16f
--- /dev/null
+++ b/thnix.f90
@@ -0,0 +1,83 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: thnix.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine cs_init
+ character*12 csub0
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+ ltrace=0
+ mtxstate=0
+ csub0='**unlocked**'
+ call fthread_mutex_init(mtx)
+ return
+end subroutine cs_init
+
+subroutine cs_destroy
+ character*12 csub0
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+ call fthread_mutex_destroy(mtx)
+ return
+end subroutine cs_destroy
+
+subroutine th_create(sub)
+ call fthread_create(sub,id)
+ return
+end subroutine th_create
+
+subroutine th_exit
+ call fthread_exit
+ return
+end subroutine th_exit
+
+subroutine cs_lock(csub)
+ character*(*) csub
+ character*12 csub0
+ integer fthread_mutex_lock,fthread_mutex_trylock
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+ n=fthread_mutex_trylock(mtx)
+ if(n.ne.0) then
+! Another thread has already locked the mutex
+ n=fthread_mutex_lock(mtx)
+ iz=index(csub0,' ')
+ if(ltrace.ge.1) print*,'"',csub,'" requested mutex when "', &
+ csub0(:iz-1),'" owned it.'
+ endif
+ mtxstate=1
+ csub0=csub
+ if(ltrace.ge.3) print*,'Mutex locked by ',csub
+ return
+end subroutine cs_lock
+
+subroutine cs_unlock
+ character*12 csub0
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+ if(ltrace.ge.3) print*,'Mutex unlocked,',ltrace,mtx,mtxstate,csub0
+ mtxstate=0
+ call fthread_mutex_unlock(mtx)
+ return
+end subroutine cs_unlock
diff --git a/thnix_stub.f90 b/thnix_stub.f90
new file mode 100644
index 0000000..eb24d8a
--- /dev/null
+++ b/thnix_stub.f90
@@ -0,0 +1,83 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: thnix_stub.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine cs_init
+ character*12 csub0
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+ ltrace=0
+ mtxstate=0
+ csub0='**unlocked**'
+! call fthread_mutex_init(mtx)
+ return
+end subroutine cs_init
+
+subroutine cs_destroy
+ character*12 csub0
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+! call fthread_mutex_destroy(mtx)
+ return
+end subroutine cs_destroy
+
+subroutine th_create(sub)
+! call fthread_create(sub,id)
+ return
+end subroutine th_create
+
+subroutine th_exit
+! call fthread_exit
+ return
+end subroutine th_exit
+
+subroutine cs_lock(csub)
+ character*(*) csub
+ character*12 csub0
+ integer fthread_mutex_lock,fthread_mutex_trylock
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+! n=fthread_mutex_trylock(mtx)
+ if(n.ne.0) then
+! Another thread has already locked the mutex
+! n=fthread_mutex_lock(mtx)
+ iz=index(csub0,' ')
+ if(ltrace.ge.1) print*,'"',csub,'" requested mutex when "', &
+ csub0(:iz-1),'" owned it.'
+ endif
+ mtxstate=1
+ csub0=csub
+ if(ltrace.ge.3) print*,'Mutex locked by ',csub
+ return
+end subroutine cs_lock
+
+subroutine cs_unlock
+ character*12 csub0
+ integer*8 mtx
+ common/mtxcom/mtx,ltrace,mtxstate,csub0
+ if(ltrace.ge.3) print*,'Mutex unlocked,',ltrace,mtx,mtxstate,csub0
+ mtxstate=0
+! call fthread_mutex_unlock(mtx)
+ return
+end subroutine cs_unlock
diff --git a/twkfreq.f90 b/twkfreq.f90
new file mode 100644
index 0000000..aa65b30
--- /dev/null
+++ b/twkfreq.f90
@@ -0,0 +1,56 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: twkfreq.f90
+! Description: Apply AFC corrections to ca, returning corrected data in cb
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine twkfreq(ca,cb,jz,a)
+
+! Apply AFC corrections to ca, returning corrected data in cb
+
+ complex ca(jz),cb(jz)
+ real a(5)
+ real*8 twopi
+ complex*16 w,wstep
+ data twopi/0.d0/
+ save twopi
+
+ if(twopi.eq.0.d0) twopi=8.d0*atan(1.d0)
+ w=1.d0
+ wstep=1.d0
+ x0=0.5*(jz+1)
+ s=2.0/jz
+ do i=1,jz
+ x=s*(i-x0)
+ if(mod(i,100).eq.1) then
+ p2=1.5*x*x - 0.5
+! p3=2.5*(x**3) - 1.5*x
+! p4=4.375*(x**4) - 3.75*(x**2) + 0.375
+ dphi=(a(1) + x*a(2) + p2*a(3)) * (twopi/375.0)
+ wstep=cmplx(cos(dphi),sin(dphi))
+ endif
+ w=w*wstep
+ cb(i)=w*ca(i)
+ enddo
+
+ return
+end subroutine twkfreq
diff --git a/tx.f90 b/tx.f90
new file mode 100644
index 0000000..a552e7c
--- /dev/null
+++ b/tx.f90
@@ -0,0 +1,311 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: tx.f90
+! Description: Make one transmission of a WSPR message, or an unmodulated
+! "Tune" sequence
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine tx
+
+! Make one transmission of a WSPR message, or an unmodulated "Tune" sequence.
+
+ integer system
+
+ parameter (NMAX2=120*48000)
+ parameter (NMAX3=4.5*48000)
+ character message*22,message0*22,call1*12,cdbm*3
+ character*22 msg0,msg1,cwmsg
+ character crig*6,cbaud*6,cdata*1,cstop*1
+ character cmnd*120,snrfile*80
+ character*80 alltxt
+ integer*2 jwave,icwid,id2
+ integer soundout,ptt,nt(9)
+ integer ib(14)
+ real*8 tsec1,tsec2,trseconds
+ include 'acom1.f90'
+ include 'acom2.f90'
+ common/bcom/ntransmitted
+ common/dcom/jwave(NMAX2),icwid(NMAX3),id2(NMAX2)
+ data ntx/0/,ns0/0/
+ data message0/'dummy'/,ntxdf0/-999/,ntune0/-999/,snr0/-999.0/
+ data iqmode0/-999/,iqtx0/-999/,nrpt/10/
+ data ib/500,160,80,60,40,30,20,17,15,12,10,6,4,2/
+ save ntx,ns0,message0,ntxdf0,ntune0,snr0,iqmode0,iqtx0,ib
+
+ trseconds=60.d0*ntrminutes
+ nfhopok=0 ! Transmitting, don't hop
+ ierr=0
+ call1=callsign
+ call cs_lock('tx')
+
+ call gmtime2(nt,tsec1)
+ sectr=mod(tsec1,trseconds)
+ write(19,1031) cdate(3:8),utctime(1:4),sectr,'PTT on '
+1031 format(a6,1x,a4,f7.2,2x,a8)
+ call flush(19)
+
+ iz=1 !Silence compiler warning
+ if(pttmode.eq.'CAT') then
+ if (nrig.eq.2509) then
+ write(crig,'(i6)') nrig
+ write(cbaud,'(i6)') nbaud
+ write(cdata,'(i1)') ndatabits
+ write(cstop,'(i1)') nstopbits
+ cmnd='rigctl '//'-m'//crig//' -r USB T 1'
+ else if(nrig.eq.1901) then
+ cmnd='rigctl -m 1901 -r localhost T 1'
+ else
+ write(crig,'(i6)') nrig
+ write(cbaud,'(i6)') nbaud
+ write(cdata,'(i1)') ndatabits
+ write(cstop,'(i1)') nstopbits
+ do i=40,1,-1
+ if(chs(i:i).ne.' ') go to 1
+ enddo
+1 iz=i
+ cmnd='rigctl '//'-m'//crig//' -r '//catport//' -s'//cbaud// &
+ ' -C data_bits='//cdata//' -C stop_bits='//cstop// &
+ ' -C serial_handshake='//chs(:iz)//' T 1'
+! Example rigctl command:
+! rigctl -m 1608 -r /dev/ttyUSB0 -s 57600 -C data_bits=8 -C stop_bits=1 \
+! -C serial_handshake=Hardware T 1
+ endif
+
+ do irpt=1,nrpt
+ iret=system(cmnd)
+ if(iret.eq.0) go to 2
+ print*,'Error executing rigctl to set Tx mode:',irpt,iret
+ print*,cmnd
+ call msleep(100)
+ enddo
+2 continue
+
+ else
+ if(nport.gt.0 .or. pttport(1:4).eq.'/dev') ierr=ptt(nport,pttport,1,iptt)
+ endif
+
+ write(cdbm,'(i3)'),ndbm
+ call cs_unlock
+
+ if(cdbm(1:1).eq.' ') cdbm=cdbm(2:)
+ if(cdbm(1:1).eq.' ') cdbm=cdbm(2:)
+ ntx=1-ntx
+ i1=index(call1,' ')
+ i2=index(call1,'/')
+
+ if(i2.gt.0 .or. igrid6.ne.0) then
+! WSPR_2 message, in two parts
+ if(i2.le.0) then
+ msg1=call1(1:i1)//grid//' '//cdbm
+ else
+ msg1=call1(:i1)//cdbm
+ endif
+ msg0='<'//call1(:i1-1)//'> '//grid6//' '//cdbm
+ if(ntx.eq.1) message=msg1
+ if(ntx.eq.0) message=msg0
+
+ else
+! Normal WSPR message
+ message=call1(1:i1)//grid//' '//cdbm
+ endif
+
+ ntxdf=nint(1.e6*(ftx-f0)) - 1500
+ if(iqmode.ne.0) then
+ ntxdf=ntxdf + nfiq
+ endif
+ ctxmsg=message
+ snr=99.0
+ snrfile=appdir(:nappdir)//'/test.snr'
+
+ call cs_lock('tx')
+ open(18,file=snrfile,status='old',err=10)
+ read(18,*,err=10,end=10) snr
+10 close(18)
+ call gmtime2(nt,tsec1)
+ if(ntune.eq.0 .and. ntune2.ne.0) ntune2=0 !### ??? ###
+ call cs_unlock
+
+ newgen=0
+ if(message.ne.message0 .or. ntxdf.ne.ntxdf0 .or. &
+ ntune.ne.ntune0 .or. snr.ne.snr0 .or. iqmode.ne.iqmode0 .or. &
+ iqtx.ne.iqtx0) then
+ message0=message
+ ntxdf0=ntxdf
+ ntune0=ntune
+ snr0=snr
+ iqmode0=iqmode
+ iqtx0=iqtx
+ call genwspr(message,ntxdf,snr,iqmode,iqtx,ntrminutes,sending,jwave)
+ newgen=1
+ endif
+ if(ntune.eq.0) then
+ call cs_lock('tx')
+ alltxt=appdir(:nappdir)//'/ALL_WSPR.TXT'
+ open(13,file=alltxt,status='unknown',position='append')
+ write(13,1010) linetx,ib(iband),message
+ 1010 format(a40,i4,' m: ',a22)
+ close(13)
+ call cs_unlock
+ endif
+
+ npts=112*48000
+ if(ntrminutes.eq.15) npts=886*48000
+ if(nsec.lt.ns0) ns0=nsec
+
+ if(idint.ne.0 .and. (nsec-ns0)/60.ge.idint .and. iqmode.eq.0) then
+! Generate and insert the CW ID.
+! NB: CW ID is not yet implemented in I/Q mode, or in WSPR-15
+ wpm=25.
+ freqcw=1500.0 + ntxdf
+ cwmsg=call1(:i1)//' '
+ icwid=0
+ call gencwid(cwmsg,wpm,freqcw,icwid,ncwid)
+ k0=112*48000
+ k1=k0+12000
+ k2=k1+4.5*48000
+ jwave(k0:k1)=0
+ jwave(k1+1:k2)=icwid
+ jwave(k2:)=0
+ npts=k2
+ ns0=nsec
+ endif
+
+ fac=10.0**(0.05*ntxdb)
+ if(ntune.eq.0) then
+
+! Normal WSPR transmission
+ if(newgen.eq.1) then
+ do i=1,npts*(iqmode+1)
+ id2(i)=fac*jwave(i)
+ enddo
+ if(iqmode.eq.1) then
+ call phasetx(id2,npts,txbal,txpha)
+ endif
+ endif
+
+ call msleep(200) !T/R sequencing delay
+ call gmtime2(nt,tsec2)
+
+ call cs_lock('tx')
+ sectr=mod(tsec2,trseconds)
+ write(19,1031) cdate(3:8),utctime(1:4),sectr,'Tx Audio'
+ call flush(19)
+ call cs_unlock('tx')
+
+! tdiff=tsec2-tsec0
+! if(tdiff.lt.0.9) then
+! call msleep(100)
+! go to 20
+! endif
+ istart=48000*(tsec2-tsec0)
+ npts=npts-istart
+ istart=istart*(iqmode+1)+1 !istart must be odd if iqmode=1
+ if(istart.lt.1) istart=1
+ ierr=soundout(ndevout,48000,id2(istart),npts,iqmode)
+
+ else
+
+ istart=2*48000 +1
+ if(pctx.lt.100.0) then
+! This is a "Tune" transmission
+ npts=48000*pctx
+ if(ntune.lt.0) npts=48000*abs(ntune)
+ j=istart-1
+ do i=1,npts*(iqmode+1)
+ j=j+1
+ id2(i)=fac*jwave(j)
+ enddo
+ if(iqmode.eq.1) then
+ call phasetx(id2,npts,txbal,txpha)
+ endif
+ ierr=soundout(ndevout,48000,id2,npts,iqmode)
+
+ else
+! Send a series of dashes, for making I/Q phase adjustments.
+ npts=24*4096
+ do irpt=1,100
+ fac=10.0**(0.05*ntxdb)
+ j=istart-1
+ do i=1,npts*(iqmode+1)
+ j=j+1
+ id2(i)=fac*jwave(j)
+ enddo
+ if(iqmode.eq.1) then
+ call phasetx(id2,npts,txbal,txpha)
+ endif
+ ierr=soundout(ndevout,48000,id2,npts,iqmode)
+ enddo
+ endif
+ endif
+ if(ierr.ne.0) then
+ print*,'Error in soundout',ierr
+ stop
+ endif
+
+ call gmtime2(nt,tsec1)
+ sectr=mod(tsec1,trseconds)
+ write(19,1031) cdate(3:8),utctime(1:4),sectr,'Audio 0 '
+ call flush(19)
+ call cs_unlock('tx')
+
+ call msleep(200) !T/R sequencing delay
+
+ call cs_lock('tx')
+ call gmtime2(nt,tsec1)
+ sectr=mod(tsec1,trseconds)
+ write(19,1031) cdate(3:8),utctime(1:4),sectr,'PTT Off '
+ call flush(19)
+ call cs_unlock('tx')
+
+ if(pttmode.eq.'CAT') then
+ if(nrig.eq.2509) then
+ cmnd='rigctl '//'-m'//crig//' -r USB T 0'
+ else if(nrig.eq.1901) then
+ cmnd='rigctl -m 1901 -r localhost T 0'
+ else
+ cmnd='rigctl '//'-m'//crig//' -r'//catport//' -s'//cbaud// &
+ ' -C data_bits='//cdata//' -C stop_bits='//cstop// &
+ ' -C serial_handshake='//chs(:iz)//' T 0'
+ endif
+
+ call cs_lock('tx')
+ do irpt=1,nrpt
+ iret=system(cmnd)
+ if(iret.eq.0) go to 101
+ print*,'Error executing rigctl to set Rx mode:',irpt,iret
+ print*,cmnd
+ call msleep(100)
+ enddo
+101 continue
+ call cs_unlock
+
+ else
+ if(nport.gt.0 .or. pttport(1:4).eq.'/dev') ierr=ptt(nport,pttport,0,iptt)
+ endif
+
+ ntxdone=1 !Tx done
+ if(ntune.ge.0) nfhopok=1 !Unless this was ATU tuneup, can now hop
+ if(ntune.eq.0) ntransmitted=1 !Flag only "real" transmissions
+ ntune=0 !Clear the "tune" indicator
+
+ return
+end subroutine tx
diff --git a/unpack50.f90 b/unpack50.f90
new file mode 100644
index 0000000..0777343
--- /dev/null
+++ b/unpack50.f90
@@ -0,0 +1,55 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: unpack50.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine unpack50(dat,n1,n2)
+
+ integer*1 dat(11)
+
+ i=dat(1)
+ i4=iand(i,255)
+ n1=ishft(i4,20)
+ i=dat(2)
+ i4=iand(i,255)
+ n1=n1 + ishft(i4,12)
+ i=dat(3)
+ i4=iand(i,255)
+ n1=n1 + ishft(i4,4)
+ i=dat(4)
+ i4=iand(i,255)
+ n1=n1 + iand(ishft(i4,-4),15)
+ n2=ishft(iand(i4,15),18)
+ i=dat(5)
+ i4=iand(i,255)
+ n2=n2 + ishft(i4,10)
+ i=dat(6)
+ i4=iand(i,255)
+ n2=n2 + ishft(i4,2)
+ i=dat(7)
+ i4=iand(i,255)
+ n2=n2 + iand(ishft(i4,-6),3)
+
+ return
+end subroutine unpack50
+
diff --git a/unpackcall.f90 b/unpackcall.f90
new file mode 100644
index 0000000..4817f38
--- /dev/null
+++ b/unpackcall.f90
@@ -0,0 +1,60 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: unpackcall.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine unpackcall(ncall,word)
+
+ character word*12,c*37
+
+ data c/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ '/
+
+ n=ncall
+ word='......'
+ if(n.ge.262177560) go to 999 !Plain text message ...
+ i=mod(n,27)+11
+ word(6:6)=c(i:i)
+ n=n/27
+ i=mod(n,27)+11
+ word(5:5)=c(i:i)
+ n=n/27
+ i=mod(n,27)+11
+ word(4:4)=c(i:i)
+ n=n/27
+ i=mod(n,10)+1
+ word(3:3)=c(i:i)
+ n=n/10
+ i=mod(n,36)+1
+ word(2:2)=c(i:i)
+ n=n/36
+ i=n+1
+ word(1:1)=c(i:i)
+ do i=1,4
+ if(word(i:i).ne.' ') go to 10
+ enddo
+ go to 999
+10 word=word(i:)
+
+999 if(word(1:3).eq.'3D0') word='3DA0'//word(4:)
+ return
+end subroutine unpackcall
diff --git a/unpackgrid.f90 b/unpackgrid.f90
new file mode 100644
index 0000000..2ff0e4b
--- /dev/null
+++ b/unpackgrid.f90
@@ -0,0 +1,60 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: unpackgrid.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine unpackgrid(ng,grid)
+
+ parameter (NGBASE=180*180)
+ character grid*4,grid6*6,digit*10
+ data digit/'0123456789'/
+
+ grid=' '
+ if(ng.ge.32400) go to 10
+ dlat=mod(ng,180)-90
+ dlong=(ng/180)*2 - 180 + 2
+ call deg2grid(dlong,dlat,grid6)
+ grid=grid6(1:4) !XXX explicitly truncate this -db
+ go to 100
+
+10 n=ng-NGBASE-1
+ if(n.ge.1 .and.n.le.30) then
+ grid(1:1)='-'
+ grid(2:2)=char(48+n/10)
+ grid(3:3)=char(48+mod(n,10))
+ else if(n.ge.31 .and.n.le.60) then
+ n=n-30
+ grid(1:2)='R-'
+ grid(3:3)=char(48+n/10)
+ grid(4:4)=char(48+mod(n,10))
+ else if(n.eq.61) then
+ grid='RO'
+ else if(n.eq.62) then
+ grid='RRR'
+ else if(n.eq.63) then
+ grid='73'
+ endif
+
+100 return
+end subroutine unpackgrid
+
diff --git a/unpackmept.f90 b/unpackmept.f90
new file mode 100644
index 0000000..51512c1
--- /dev/null
+++ b/unpackmept.f90
@@ -0,0 +1,82 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: unpackmept.f90
+! Description: Unpack 50 bits to retrieve an MEPT_JT message
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine unpackmept(dat,msg)
+
+! Unpack 50 bits to retrieve an MEPT_JT message.
+
+ parameter (NBASE=37*36*10*27*27*27)
+ integer dat(12)
+ character c1*12,grid*4,msg*22,grid6*6
+
+ nc1=ishft(dat(1),22) + ishft(dat(2),16) + ishft(dat(3),10) + &
+ ishft(dat(4),4) + iand(ishft(dat(5),-2),15)
+
+ n2=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=n2/128
+ ndbm=iand(n2,127) - 64
+
+ if(nc1.lt.NBASE) then
+ call unpackcall(nc1,c1)
+ else
+ print*,'Error in unpackmept: bad callsign?'
+ stop
+ endif
+
+ call unpackgrid(ng,grid)
+ grid6=grid//'ma'
+ call grid2k(grid6,k)
+ if(k.ge.1 .and. k.le.900) then
+ print*,'Error in unpackmept: k=',k
+ stop
+ endif
+
+ i=index(c1,char(0))
+ if(i.ge.3) c1=c1(1:i-1)//' '
+
+ msg=' '
+ j=0
+ 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 if(k.eq.0) then
+ do i=1,4
+ if(j.le.21) j=j+1
+ msg(j:j)=grid(i:i)
+ enddo
+ j=j+1
+ msg(j:j)=' '
+ endif
+
+100 return
+end subroutine unpackmept
diff --git a/unpackname.f90 b/unpackname.f90
new file mode 100644
index 0000000..0ee6a62
--- /dev/null
+++ b/unpackname.f90
@@ -0,0 +1,45 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: unpackname.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine unpackname(n1,n2,name,len)
+
+ character*9 name
+ real*8 dn
+
+ dn=32768.d0*n1 + n2
+ len=0
+ do i=9,1,-1
+ j=mod(dn,27.d0)
+ if(j.ge.1) then
+ name(i:i)=char(64+j)
+ len=len+1
+ else
+ name(i:i)=' '
+ endif
+ dn=dn/27.d0
+ enddo
+
+ return
+end subroutine unpackname
diff --git a/unpackpfx.f90 b/unpackpfx.f90
new file mode 100644
index 0000000..4a02fc3
--- /dev/null
+++ b/unpackpfx.f90
@@ -0,0 +1,64 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: unpackpfx.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine unpackpfx(ng,call1)
+
+ character*12 call1
+ character*3 pfx
+
+ if(ng.lt.60000) then
+! Add-on prefix of 1 to 3 characters
+ n=ng
+ do i=3,1,-1
+ nc=mod(n,37)
+ if(nc.ge.0 .and. nc.le.9) then
+ pfx(i:i)=char(nc+48)
+ else if(nc.ge.10 .and. nc.le.35) then
+ pfx(i:i)=char(nc+55)
+ else
+ pfx(i:i)=' '
+ endif
+ n=n/37
+ enddo
+ call1=pfx//'/'//call1
+ if(call1(1:1).eq.' ') call1=call1(2:)
+ if(call1(1:1).eq.' ') call1=call1(2:)
+ else
+! Add-on suffix, one or teo characters
+ i1=index(call1,' ')
+ nc=ng-60000
+ if(nc.ge.0 .and. nc.le.9) then
+ call1=call1(:i1-1)//'/'//char(nc+48)
+ else if(nc.ge.10 .and. nc.le.35) then
+ call1=call1(:i1-1)//'/'//char(nc+55)
+ else if(nc.ge.36 .and. nc.le.125) then
+ nc1=(nc-26)/10
+ nc2=mod(nc-26,10)
+ call1=call1(:i1-1)//'/'//char(nc1+48)//char(nc2+48)
+ endif
+ endif
+
+ return
+end subroutine unpackpfx
diff --git a/unpackprop.f90 b/unpackprop.f90
new file mode 100644
index 0000000..5836ee9
--- /dev/null
+++ b/unpackprop.f90
@@ -0,0 +1,53 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: unpackprop.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine unpackprop(n1,k,muf,ccur,cxp)
+
+ character ccur*4,cxp*2
+
+ muf=mod(n1,62)
+ n1=n1/62
+
+ k=mod(n1,11)
+ n1=n1/11
+
+ j=mod(n1,53)
+ n1=n1/53
+ if(j.eq.0) cxp='*'
+ if(j.ge.1 .and. j.le.26) cxp=char(64+j)
+ if(j.gt.26) cxp=char(64+j-26)//char(64+j-26)
+
+ j=mod(n1,53)
+ n1=n1/53
+ if(j.eq.0) ccur(2:2)='*'
+ if(j.ge.1 .and. j.le.26) ccur(2:2)=char(64+j)
+ if(j.gt.26) ccur(2:3)=char(64+j-26)//char(64+j-26)
+ j=n1
+ if(j.eq.0) ccur(1:1)='*'
+ if(j.ge.1 .and. j.le.26) ccur(1:1)=char(64+j)
+ if(j.gt.26) ccur=char(64+j-26)//char(64+j-26)//ccur(2:3)
+
+ return
+end subroutine unpackprop
diff --git a/unpacktext2.f90 b/unpacktext2.f90
new file mode 100644
index 0000000..df3c45d
--- /dev/null
+++ b/unpacktext2.f90
@@ -0,0 +1,42 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: unpacktext2.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine unpacktext2(n1,ng,msg)
+
+ character*22 msg
+ real*8 dn
+ character*41 c
+ data c/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +./?'/
+
+ msg=' '
+ dn=32768.d0*n1 + ng
+ do i=8,1,-1
+ j=mod(dn,41.d0)
+ msg(i:i)=c(j+1:j+1)
+ dn=dn/41.d0
+ enddo
+
+ return
+end subroutine unpacktext2
diff --git a/user_hardware.py b/user_hardware.py
new file mode 100644
index 0000000..fdcf315
--- /dev/null
+++ b/user_hardware.py
@@ -0,0 +1,61 @@
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: user_harware.py
+# Description:
+#
+# Copyright (C) 2001-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#-------------------------------------------------------------------------------
+import sys
+from ctypes import windll,c_long,byref
+
+iant= [16,32,64]
+ib={600:1,160:2,80:3,60:4,40:5,30:6,20:7,17:8,15:9,12:10,10:11,6:12,4:13}
+vertical=0
+doublet=1
+mosley=2
+band=int(sys.argv[1])
+nant=doublet #Default antenna is "doublet"
+if band==160: nant=vertical
+##if band==20 or band==15 or band==10: nant=mosley
+iband=ib[band]
+
+# Fixed paremeters for LabJack:
+idnum = c_long(-1) #default labjack ID
+demo = c_long(0) #default 0
+trisD = c_long(65535)
+trisIO = c_long(15)
+updateDigital = c_long(1)
+outputD = c_long(0)
+
+# LabJack band-select and other parameters
+# stateIO sets 4 bits, IO0 - IO3
+# stateD sets 16 bits, D0 - D15
+
+iodata2=0
+iodata=iant[nant]
+
+# Any other LabJack commands should be OR'd into iodata here:
+
+stateD=c_long(iodata)
+stateIO=c_long(iodata2)
+err = windll.ljackuw.DigitalIO(byref(idnum),demo,byref(trisD),trisIO, \
+ byref(stateD),byref(stateIO),updateDigital,byref(outputD))
+if err!=0:
+ print('Error executing Labjack command')
diff --git a/wfile5.f90 b/wfile5.f90
new file mode 100644
index 0000000..319d8ad
--- /dev/null
+++ b/wfile5.f90
@@ -0,0 +1,114 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: wfile5.f90
+! Description: Write a wavefile to disk
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine wfile5(iwave,nmax,nfsample,outfile)
+
+! Write a wavefile to disk.
+
+ integer*1 n4
+ integer*2 iwave(nmax)
+ character*80 outfile
+
+ integer*2 nfmt2,nchan2,nbitsam2,nbytesam2
+ character*4 ariff,awave,afmt,adata
+ integer*1 hdr(44)
+ integer*2 iswap_short
+ common/hdr/ariff,nchunk,awave,afmt,lenfmt,nfmt2,nchan2, &
+ nsamrate,nbytesec,nbytesam2,nbitsam2,adata,ndata
+ equivalence (hdr,ariff),(nfmt2,n4)
+
+! Generate the header
+ ariff='RIFF'
+ awave='WAVE'
+ afmt='fmt '
+ adata='data'
+ lenfmt=16 !Rest of this sub-chunk is 16 bytes long
+ nfmt2=1 !PCM = 1
+ nchan2=1 !1=mono, 2=stereo
+ nbitsam2=16 !Bits per sample
+ nsamrate=nfsample
+ nbytesec=nfsample*nchan2*nbitsam2/8 !Bytes per second
+ nbytesam2=nchan2*nbitsam2/8 !Block-align
+ ndata=nmax*nchan2*nbitsam2/8
+ nbytes=ndata+44
+ nchunk=nbytes-8
+
+ call cs_lock('wfile5')
+ open(12,file=outfile,access='stream',status='unknown')
+ if (n4.ne.nfmt2) then
+ call change_endian !Change hdr to little-endian
+ do i=1,nmax
+ iwave(i) = iswap_short(iwave(i))!Change data to little-endian
+ enddo
+ endif
+ write(12) hdr
+ write(12) iwave
+ close(12)
+ call cs_unlock
+
+ return
+end subroutine wfile5
+
+subroutine change_endian
+
+ integer*1 hdr(44)
+ integer*2 nfmt2,nchan2,nbitsam2,nbytesam2
+ integer*2 iswap_short
+ character*4 ariff,awave,afmt,adata
+ common/hdr/ariff,nchunk,awave,afmt,lenfmt,nfmt2,nchan2, &
+ nsamrate,nbytesec,nbytesam2,nbitsam2,adata,ndata
+ equivalence (ariff,hdr)
+
+ nchunk = iswap_int(nchunk)
+ lenfmt = iswap_int(lenfmt)
+ nfmt2 = iswap_short(nfmt2)
+ nchan2 = iswap_short(nchan2)
+ nsamrate = iswap_int(nsamrate)
+ nbytesec = iswap_int(nbytesec)
+ nbytesam2 = iswap_short(nbytesam2)
+ nbitsam2 = iswap_short(nbitsam2)
+ ndata = iswap_int(ndata)
+
+ return
+end subroutine change_endian
+
+integer function iswap_int(idat)
+
+ itemp1 = ior(ishft(idat,24), iand(ishft(idat,8), z'00ff0000'))
+ itemp0 = ior(iand(ishft(idat,-8), z'0000ff00'), &
+ iand(ishft(idat,-24),z'000000ff'))
+ iswap_int = ior(itemp1,itemp0)
+
+end function iswap_int
+
+integer*2 function iswap_short(idat)
+
+ integer*2 idat,m2
+ data m2/255/
+
+ iswap_short = ior(ishft(idat,8), iand(ishft(idat,-8), m2))
+
+end function iswap_short
+
diff --git a/wqdecode.f90 b/wqdecode.f90
new file mode 100644
index 0000000..1636380
--- /dev/null
+++ b/wqdecode.f90
@@ -0,0 +1,98 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: wqdecode.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine wqdecode(data0,message,ntype)
+
+ parameter (N15=32768)
+ integer*1 data0(11)
+ character*22 message
+ character*12 callsign
+ character*3 cdbm
+ character grid4*4,grid6*6
+ logical first
+ character*12 dcall(0:N15-1)
+ data first/.true./
+ save first,dcall
+
+ call cs_lock('wqdecode')
+! May want to have a timeout (say, one hour?) on calls fetched
+! from the hash table.
+
+ if(first) then
+ dcall=' '
+ first=.false.
+ endif
+
+ message=' '
+ call unpack50(data0,n1,n2)
+ call unpackcall(n1,callsign)
+ i1=index(callsign,' ')
+ call unpackgrid(n2/128,grid4)
+ ntype=iand(n2,127) -64
+
+! Standard WSPR message (types 0 3 7 10 13 17 ... 60)
+ if(ntype.ge.0 .and. ntype.le.62) then
+ nu=mod(ntype,10)
+ if(nu.eq.0 .or. nu.eq.3 .or. nu.eq.7) then
+ write(cdbm,'(i3)'),ntype
+ if(cdbm(1:1).eq.' ') cdbm=cdbm(2:)
+ if(cdbm(1:1).eq.' ') cdbm=cdbm(2:)
+ message=callsign(1:i1)//grid4//' '//cdbm
+ call hash(callsign,i1-1,ih)
+ dcall(ih)=callsign(:i1)
+ else
+ nadd=nu
+ if(nu.gt.3) nadd=nu-3
+ if(nu.gt.7) nadd=nu-7
+ ng=n2/128 + 32768*(nadd-1)
+ call unpackpfx(ng,callsign)
+ ndbm=ntype-nadd
+ write(cdbm,'(i3)'),ndbm
+ if(cdbm(1:1).eq.' ') cdbm=cdbm(2:)
+ if(cdbm(1:1).eq.' ') cdbm=cdbm(2:)
+ i2=index(callsign,' ')
+ message=callsign(:i2)//cdbm
+ call hash(callsign,i2-1,ih)
+ dcall(ih)=callsign(:i2)
+ endif
+ else if(ntype.lt.0) then
+ ndbm=-(ntype+1)
+ grid6=callsign(6:6)//callsign(1:5)
+ ih=(n2-ntype-64)/128
+ callsign=dcall(ih)
+ write(cdbm,'(i3)'),ndbm
+ if(cdbm(1:1).eq.' ') cdbm=cdbm(2:)
+ if(cdbm(1:1).eq.' ') cdbm=cdbm(2:)
+ i2=index(callsign,' ')
+ if(dcall(ih)(1:1).ne.' ') then
+ message='<'//callsign(:i2-1)//'> '//grid6//' '//cdbm
+ else
+ message='<...> '//grid6//' '//cdbm
+ endif
+ endif
+ call cs_unlock
+
+ return
+end subroutine wqdecode
diff --git a/wqencode.f90 b/wqencode.f90
new file mode 100644
index 0000000..f674994
--- /dev/null
+++ b/wqencode.f90
@@ -0,0 +1,91 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: wqencode.f90
+! Description: Parse and encode a WSPR message
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine wqencode(msg,ntype,data0)
+
+! Parse and encode a WSPR message.
+
+ parameter (MASK15=32767)
+ character*22 msg
+ character*12 call1,call2
+ character grid4*4,grid6*6
+ logical lbad1,lbad2
+ integer*1 data0(11)
+ integer nu(0:9)
+ data nu/0,-1,1,0,-1,2,1,0,-1,1/
+
+ call cs_lock('wqencode')
+! Standard WSPR message (types 0 3 7 10 13 17 ... 60)
+ i1=index(msg,' ')
+ i2=index(msg,'/')
+ i3=index(msg,'<')
+ call1=msg(:i1-1)
+ if(i1.lt.3 .or. i1.gt.7 .or. i2.gt.0 .or. i3.gt.0) go to 10
+ grid4=msg(i1+1:i1+4)
+ call packcall(call1,n1,lbad1)
+ call packgrid(grid4,ng,lbad2)
+ if(lbad1 .or. lbad2) go to 10
+ ndbm=0
+ read(msg(i1+5:),*) ndbm
+ if(ndbm.lt.0) ndbm=0
+ if(ndbm.gt.60) ndbm=60
+ ndbm=ndbm+nu(mod(ndbm,10))
+ n2=128*ng + (ndbm+64)
+ call pack50(n1,n2,data0)
+ ntype=ndbm
+ go to 900
+
+10 if(i2.ge.2 .and. i3.lt.1) then
+ call packpfx(call1,n1,ng,nadd)
+ ndbm=0
+ read(msg(i1+1:),*) ndbm
+ if(ndbm.lt.0) ndbm=0
+ if(ndbm.gt.60) ndbm=60
+ ndbm=ndbm+nu(mod(ndbm,10))
+ ntype=ndbm + 1 + nadd
+ n2=128*ng + ntype + 64
+ call pack50(n1,n2,data0)
+ else if(i3.eq.1) then
+ i4=index(msg,'>')
+ call1=msg(2:i4-1)
+ call hash(call1,i4-2,ih)
+ grid6=msg(i1+1:i1+6)
+ call2=grid6(2:6)//grid6(1:1)//' '
+ call packcall(call2,n1,lbad1)
+ ndbm=0
+ read(msg(i1+8:),*) ndbm
+ if(ndbm.lt.0) ndbm=0
+ if(ndbm.gt.60) ndbm=60
+ ndbm=ndbm+nu(mod(ndbm,10))
+ ntype=-(ndbm+1)
+ n2=128*ih + ntype + 64
+ call pack50(n1,n2,data0)
+ endif
+ go to 900
+
+900 continue
+ call cs_unlock
+ return
+end subroutine wqencode
diff --git a/write_wav.f90 b/write_wav.f90
new file mode 100644
index 0000000..8380ad1
--- /dev/null
+++ b/write_wav.f90
@@ -0,0 +1,58 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: write_wav.f90
+! Description: Write a wavefile to logical unit lu
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine write_wav(lu,idat,ntot,nfsample,nchan)
+
+! Write a wavefile to logical unit lu.
+
+ integer*2 idat(ntot)
+ integer*2 nfmt2,nchan2,nbitsam2,nbytesam2
+ character*4 ariff,awave,afmt,adata
+ integer*1 hdr(44)
+ common/hdr/ariff,nchunk,awave,afmt,lenfmt,nfmt2,nchan2,nsamrate, &
+ nbytesec,nbytesam2,nbitsam2,adata,ndata
+ equivalence (hdr,ariff)
+
+! Generate header
+ ariff='RIFF'
+ awave='WAVE'
+ afmt='fmt '
+ adata='data'
+ lenfmt=16 !Rest of this sub-chunk is 16 bytes long
+ nfmt2=1 !PCM = 1
+ nchan2=nchan !1=mono, 2=stereo
+ nbitsam2=16 !Bits per sample
+ nsamrate=nfsample !Sample rate
+ nbytesec=nfsample*nchan2*nbitsam2/8 !Bytes per second
+ nbytesam2=nchan2*nbitsam2/8 !Block-align
+ ndata=ntot*nbitsam2/8
+ nbytes=ndata+44
+ nchunk=nbytes-8
+
+ write(lu) hdr
+ write(lu) idat
+
+ return
+end subroutine write_wav
diff --git a/wspr.desktop b/wspr.desktop
new file mode 100644
index 0000000..750253b
--- /dev/null
+++ b/wspr.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=WSPR v4.0
+Comment=Weak Signal Propagation Reporter
+Exec=/usr/bin/wspr
+Terminal=false
+Categories=Audio;AudioVideo;HamRadio
+Keywords=Scientific;Hamradio;Radio;Signal;Propagation;Whisper;WSPR;WSJT;
+Icon=wspr
+StartupWMClass=Tk
diff --git a/wspr.py b/wspr.py
new file mode 100644
index 0000000..9d9aebb
--- /dev/null
+++ b/wspr.py
@@ -0,0 +1,1941 @@
+# pylint: disable=wildcard-import,bad-whitespace
+#----------------------------------------------------------------------------
+# $Date: 2008-03-17 08:29:04 -0400 (Mon, 17 Mar 2008) $ $Revision: 4522 $
+#
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: wspr.py
+# Source: http://sourceforge.net/projects/wsjt/
+# Contributors: K1JT, VA3DB, G4KLA, W1BW, 4X6IZ, KE6HDU and KI7MT
+#
+# Description: WSPR (pronounced "whisper") stands for "Weak Signal
+# Propagation Reporter". The program generates and decodes
+# a digital sound card mode optimized for beacon-like
+# transmissions on the LF, MF, and HF bands.
+#
+# Copyright (C) 2008-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# Revision for MacOsX 10.6.8 by KE6HDU
+# Apparently the tK library has a leak on MacOsX, so every extra call
+# to that library has been suppressed. This reduced memory use from
+# 100KB/second to the point where memory is only lost when the window
+# is updated for some reason. This sort of loss is typically 100KB per
+# 2 minutes or about 100 times slower than the original. You can reduce
+# even that by decreasing the sensitivity to noise changes. This change
+# also greatly reduced the load on the kernel, from a sustained 13% to
+# essentially nothing. These changes are also licensed under the
+# GNU General Public License (GPL).
+#
+#------------------------------------------------------------------------------
+from tkinter import *
+from tkinter.filedialog import *
+import tkinter.messagebox
+import os, time, sys
+from WsprMod import g
+import Pmw
+from WsprMod import palettes
+from math import log10
+import numpy.core.multiarray
+import array
+##import dircache
+from PIL import Image, ImageTk, ImageDraw
+from WsprMod.palettes import colormapblue, colormapgray0, colormapHot, \
+ colormapAFMHot, colormapgray1, colormapLinrad, Colormap2Palette
+from types import *
+import random
+import math
+import string
+from WsprMod import w
+from WsprMod import smeter
+import socket
+import urllib.request, urllib.parse, urllib.error
+import _thread
+import webbrowser
+import tkinter.font
+
+root = Tk()
+Version="4.0 r" + "$Rev: 4522 $"[6:-2]
+print("******************************************************************")
+print("WSPR Version " + Version + ", by K1JT")
+print("Run date: " + time.asctime(time.gmtime()) + " UTC")
+
+#See if we are running in Windows
+g.Win32=0
+if sys.platform=="win32":
+ g.Win32=1
+ try:
+ root.option_readfile('wsprrc.win')
+ except:
+ pass
+else:
+ try:
+ root.option_readfile('wsprrc')
+ except:
+ pass
+root_geom=""
+appdir=os.getcwd()
+w.acom1.nappdir=len(appdir)
+w.acom1.appdir=(appdir+(' '*80))[:80]
+i1,i2=w.audiodev(0,2)
+from WsprMod import options
+from WsprMod import advanced
+from WsprMod import iq
+from WsprMod import hopping
+
+#------------------------------------------------------ Global variables
+adv0=999
+adv1=999
+band=[-1,600,160,80,60,40,30,20,17,15,12,10,6,4,2,0]
+bandmap=[]
+bm={}
+btune0=999
+encal0=999
+f0=DoubleVar()
+ftx=DoubleVar()
+ftx0=0.
+ft=[]
+fileopened=""
+fmid=0.0
+fmid0=0.0
+font1='Helvetica'
+hopping0=99
+iband=IntVar()
+iband0=0
+idle=IntVar()
+idle0=999
+ierr=0
+inbad0=999
+ipctx=IntVar()
+ndgain=IntVar()
+isec0=0
+isync=1
+itx0=0
+loopall=0
+modpixmap0=0
+mrudir=os.getcwd()
+ndbm0=-999
+ncal0=999
+ncall=0
+ndebug=IntVar()
+ndecoding0=999
+nin0=0
+nout0=0
+nred=0
+ntune0=999
+newdat=1
+newspec=1
+no_beep=IntVar()
+npal=IntVar()
+npal.set(2)
+nparam=0
+nsave=IntVar()
+nscroll=0
+nsec0=0
+nspeed0=IntVar()
+ntr0=0
+ntxfirst=IntVar()
+NX=500
+NY=160
+outbad0=999
+param20=""
+sf0=StringVar()
+sftx=StringVar()
+start_idle=IntVar()
+startup=1
+t0=""
+timer1=0
+txmsg=StringVar()
+txmute=IntVar()
+txmute0=999
+upload0=999
+nreject=0
+gain=1.0
+phdeg=0.0
+
+a=array.array('h')
+im=Image.new('P',(NX,NY))
+draw=ImageDraw.Draw(im)
+im.putpalette(Colormap2Palette(colormapLinrad),"RGB")
+pim=ImageTk.PhotoImage(im)
+receiving=0
+scale0=1.0
+offset0=0.0
+s0=0.0
+c0=0.0
+slabel="MinSync "
+transmitting=0
+tw=[]
+fw=[] # band labels for spectrum display
+upload=IntVar()
+balloon=Pmw.Balloon(root)
+
+g.appdir=appdir
+g.cmap="Linrad"
+g.cmap0="Linrad"
+g.ndevin=IntVar()
+g.ndevout=IntVar()
+g.DevinName=StringVar()
+g.DevoutName=StringVar()
+
+pwrlist=(-30,-27,-23,-20,-17,-13,-10,-7,-3, \
+ 0,3,7,10,13,17,20,23,27,30,33,37,40,43,47,50,53,57,60)
+freq0=[0,0.4742,1.8366,3.5926,5.2872,7.0386,10.1387,14.0956,18.1046,\
+ 21.0946,24.9246,28.1246,50.2930,70.0286,144.4890,0.1360]
+freqtx=[0,0.4742,1.8366,3.5926,5.2872,7.0386,10.1387,14.0956,18.1046,\
+ 21.0946,24.9246,28.1246,50.2930,70.0301,144.4890,0.1375]
+
+for i in range(15):
+ freqtx[i]=freq0[i]+0.001500
+
+socktimeout = 20
+socket.setdefaulttimeout(socktimeout)
+
+def pal_gray0():
+ g.cmap="gray0"
+ im.putpalette(Colormap2Palette(colormapgray0),"RGB")
+def pal_gray1():
+ g.cmap="gray1"
+ im.putpalette(Colormap2Palette(colormapgray1),"RGB")
+def pal_linrad():
+ g.cmap="Linrad"
+ im.putpalette(Colormap2Palette(colormapLinrad),"RGB")
+def pal_blue():
+ g.cmap="blue"
+ im.putpalette(Colormap2Palette(colormapblue),"RGB")
+def pal_Hot():
+ g.cmap="Hot"
+ im.putpalette(Colormap2Palette(colormapHot),"RGB")
+def pal_AFMHot():
+ g.cmap="AFMHot"
+ im.putpalette(Colormap2Palette(colormapAFMHot),"RGB")
+
+#------------------------------------------------------ quit
+def quit(event=NONE):
+ root.destroy()
+
+#------------------------------------------------------ openfile
+def openfile(event=NONE):
+ global mrudir,fileopened,nopen,tw
+ nopen=1 #Work-around for "click feedthrough" bug
+ upload.set(0)
+ try:
+ os.chdir(mrudir)
+ except:
+ pass
+ fname=askopenfilename(filetypes=[("Wave files","*.wav *.WAV")])
+ if fname:
+ w.getfile(fname,len(fname))
+ mrudir=os.path.dirname(fname)
+ fileopened=os.path.basename(fname)
+ i1=fileopened.find('.')
+ t=fileopened[i1-4:i1]
+ t=t[0:2] + ':' + t[2:4]
+ n=len(tw)
+ if n>12: tw=tw[:n-1]
+ tw=[t,] + tw
+ os.chdir(appdir)
+ idle.set(1)
+
+#------------------------------------------------------ stop_loopall
+def stop_loopall(event=NONE):
+ global loopall
+ loopall=0
+
+#------------------------------------------------------ opennext
+def opennext(event=NONE):
+ global ncall,fileopened,loopall,mrudir,tw,ndecoding0
+
+ if int(w.acom1.ndecoding) != 0:
+ return
+ upload.set(0)
+ if fileopened=="" and ncall==0:
+ openfile()
+ ncall=1
+ else:
+# Make a list of *.wav files in mrudir
+ la=os.listdir(mrudir)
+ la.sort()
+ lb=[]
+ for i in range(len(la)):
+ j=la[i].find(".wav") + la[i].find(".WAV")
+ if j>0: lb.append(la[i])
+ for i in range(len(lb)):
+ if lb[i]==fileopened:
+ break
+ if i<len(lb)-1:
+ fname=mrudir+"/"+lb[i+1]
+ w.getfile(fname,len(fname))
+ mrudir=os.path.dirname(fname)
+ fileopened=os.path.basename(fname)
+ i1=fileopened.find('.')
+ t=fileopened[i1-4:i1]
+ t=t[0:2] + ':' + t[2:4]
+ n=len(tw)
+ if n>12: tw=tw[:n-1]
+ tw=[t,] + tw
+ else:
+ t="No more *.wav files in this directory."
+ result=tkinter.messagebox.showwarning(message=t)
+ ncall=0
+ loopall=0
+
+#------------------------------------------------------ decodeall
+def decodeall(event=NONE):
+ global loopall
+ loopall=1
+ opennext()
+
+#------------------------------------------------------ hopping1
+def hopping1(event=NONE):
+ t=''
+ if root_geom.find('+')>=0:
+ t=root_geom[root_geom.index('+'):]
+ hopping.hopping2(t)
+
+#------------------------------------------------------ options1
+def options1(event=NONE):
+ t=''
+ if root_geom.find('+')>=0:
+ t=root_geom[root_geom.index('+'):]
+ options.options2(t)
+
+#------------------------------------------------------ advanced1
+def advanced1(event=NONE):
+ t=""
+ if root_geom.find("+")>=0:
+ t=root_geom[root_geom.index("+"):]
+ advanced.advanced2(t)
+
+#------------------------------------------------------ iq1
+def iq1(event=NONE):
+ t=""
+ if root_geom.find("+")>=0:
+ t=root_geom[root_geom.index("+"):]
+ iq.iq2(t)
+
+#------------------------------------------------------ stub
+def stub(event=NONE):
+ MsgBox("Sorry, this function is not yet implemented.")
+
+#------------------------------------------------------ MsgBox
+def MsgBox(t):
+ result=tkinter.messagebox.showwarning(message=t)
+
+#------------------------------------------------------ msgpos
+def msgpos():
+ g=root_geom[root_geom.index("+"):]
+ t=g[1:]
+ x=int(t[:t.index("+")]) # + 70
+ y=int(t[t.index("+")+1:]) # + 70
+ return "+%d+%d" % (x,y)
+
+#------------------------------------------------------ about
+def about(event=NONE):
+ global Version
+ about=Toplevel(root)
+ about.geometry(msgpos())
+ if g.Win32: about.iconbitmap("wsjt.ico")
+ t="WSPR Version " + Version + ", by K1JT"
+ Label(about,text=t,font=(font1,16)).pack(padx=20,pady=5)
+ t="""
+WSPR (pronounced "whisper") stands for "Weak Signal
+Propagation Reporter". The program generates and decodes
+a digital soundcard mode optimized for beacon-like
+transmissions on the LF, MF, and HF bands.
+
+Copyright (c) 2008-2014 by Joseph H. Taylor, Jr., K1JT, with
+contributions from VA3DB, G4KLA, W1BW, 4X6IZ, KE6HDU and KI7MT.
+WSPR is Open Source software, licensed under the GNU General Public
+License (GPL-3). Source code and programming information may
+be found at http://sourceforge.net/projects/wsjt/.
+"""
+ Label(about,text=t,justify=LEFT).pack(padx=20)
+ t="Revision date: " + \
+ "$Date: 2014-10-16 22:41:00 -0600 (Thu, 16 Oct 2014) $"[7:-1]
+ Label(about,text=t,justify=LEFT).pack(padx=20)
+ about.focus_set()
+
+#------------------------------------------------------
+def help(event=NONE):
+ about=Toplevel(root)
+ about.geometry(msgpos())
+ if g.Win32: about.iconbitmap("wsjt.ico")
+ t="Basic Operating Instructions"
+ Label(about,text=t,font=(font1,14)).pack(padx=20,pady=5)
+ t="""
+1. Open the Setup | Station Parameters screen and enter
+ your callsign and grid locator 6 characters). Select
+ desired devices for Audio In and Audio Out, and your
+ power level in dBm.
+
+2. Select your PTT method (CAT control, DTR, or RTS). If
+ you choose DTR or RTS, select a PTT port. If T/R
+ switching or frequency setting will be done by CAT
+ control, select a CAT port and be sure that "Enable CAT"
+ is checked. You will need to enter a Rig number and
+ correct parameters for the serial connection.
+
+3. Select the desired band from the Band menu and if
+ necessary correct your USB dial frequency on the main
+ screen. Select a Tx frequency by double-clicking
+ somewhere on the waterfall display.
+
+4. Select a desired 'Tx fraction' using the large slider. Zero
+ percent means Rx only; 100% means Tx only.
+
+5. Be sure that your computer clock is correct to +/- 1 s.
+ Many people like to use an automatic internet-based
+ clock-setting utility.
+
+6. WSPR will begin a Tx or Rx sequence at the start of each
+ even-numbered minute. The waterfall will update and
+ decoding will take place at the end of each Rx sequence.
+ During reception, you can adjust the Rx noise level to get
+ something close to 0 dB. Use the operating system's audio
+ mixer control or change your receiver's output level.
+"""
+ Label(about,text=t,justify=LEFT).pack(padx=20)
+ about.focus_set()
+
+#------------------------------------------------------ usersguide
+def usersguide(event=NONE):
+ url='http://physics.princeton.edu/pulsar/K1JT/doc/wspr/wspr-main.html'
+ _thread.start_new_thread(browser,(url,))
+
+#------------------------------------------------------ fmtguide
+def fmtguide(event=NONE):
+ url='http://physics.princeton.edu/pulsar/K1JT/FMT_User.pdf'
+ _thread.start_new_thread(browser,(url,))
+
+#------------------------------------------------------ wsprnet
+def wsprnet(event=NONE):
+ url='http://wsprnet.org/'
+ _thread.start_new_thread(browser,(url,))
+
+#------------------------------------------------------ homepage
+def homepage(event=NONE):
+ url='http://physics.princeton.edu/pulsar/K1JT/'
+ _thread.start_new_thread(browser,(url,))
+
+#------------------------------------------------------- browser
+def browser(url):
+ webbrowser.open(url)
+
+#------------------------------------------------------ erase
+def erase(event=NONE):
+ global bandmap,bm
+ text.configure(state=NORMAL)
+ text.delete('1.0',END)
+ text.configure(state=DISABLED)
+ text1.configure(state=NORMAL)
+ text1.delete('1.0',END)
+ text1.configure(state=DISABLED)
+ bandmap=[]
+ bm={}
+
+#------------------------------------------------------ tune
+def tune(event=NONE):
+ idle.set(1)
+ w.acom1.ntune=1
+ btune.configure(bg='yellow')
+# balloon.configure(state='none')
+
+#------------------------------------------------------ txnext
+def txnext(event=NONE):
+ if ipctx.get()>0:
+ w.acom1.ntxnext=1
+ btxnext.configure(bg="green")
+
+###------------------------------------------------------ stoptx
+##def stoptx(event=NONE):
+## w.acom1.nstoptx=1
+## w.acom1.ntxnext=0
+
+#----------------------------------------------------- df_readout
+# Readout of graphical cursor location
+def df_readout(event):
+ global fmid,nred
+ nred=10
+ df=12000/8192.0
+ nhz=1000000*fmid + (80.0-event.y)*df + 2
+ nhz=int(nhz%1000)
+ t="%3d Hz" % nhz
+ lab02.configure(text=t,bg='red')
+
+#----------------------------------------------------- set_tx_freq
+def set_tx_freq(event):
+ global fmid
+ df=12000/8192.0
+ nftx=int(1000000.0*fmid + (80.0-event.y)*df) + 2
+ fmhz=0.000001*nftx
+ t="Please confirm setting Tx frequency to " + "%.06f MHz" % fmhz
+ result=tkinter.messagebox.askyesno(message=t)
+ if result:
+ ftx.set(0.000001*nftx)
+ sftx.set('%.06f' % ftx.get())
+
+#-------------------------------------------------------- draw_axis
+def draw_axis():
+ global fmid
+ c.delete(ALL)
+ nfmid=int(1.0e6*fmid + 0.5)%1000
+# Draw and label tick marks
+ df=12000.0/8192.0
+ for iy in range(-120,120,10):
+ j=80 - iy/df
+ i1=7
+ if (iy%50)==0:
+ i1=12
+ if (iy%100)==0: i1=15
+ n=nfmid+iy
+ if n<0: n=n+1000
+ c.create_text(27,j,text=str(n))
+ c.create_line(0,j,i1,j,fill='black')
+ iy=1000000.0*(ftx.get()-f0.get()) - 1500
+ if abs(iy)<=100:
+ j=80 - iy/df
+ c.create_line(0,j,13,j,fill='red',width=3)
+
+#------------------------------------------------------ del_all
+def del_all():
+ fname=appdir+'/ALL_WSPR.TXT'
+ try:
+ os.remove(fname)
+ except:
+ pass
+
+#------------------------------------------------------ delwav
+def delwav():
+ t="Are you sure you want to delete\nall *.WAV files in the Save directory?"
+ result=tkinter.messagebox.askyesno(message=t)
+ if result:
+# Make a list of *.wav files in Save
+ la=os.listdir(appdir+'/save')
+ lb=[]
+ for i in range(len(la)):
+ j=la[i].find(".wav") + la[i].find(".WAV")
+ if j>0: lb.append(la[i])
+# Now delete them all.
+ for i in range(len(lb)):
+ fname=appdir+'/save/'+lb[i]
+ os.remove(fname)
+
+#------------------------------------------------------ get_decoded
+def get_decoded():
+ global bandmap,bm,newdat,loopall
+
+# Get lines from decoded.txt and parse each into an associative array
+ try:
+ f=open(appdir+'/decoded.txt',mode='r')
+ decodes = []
+ for line in f:
+ fields = line.split()
+ if len(fields) < 10: continue
+ msg = fields[6:-3]
+ d = {}
+ d['date'] = fields[0]
+ d['time'] = fields[1]
+ d['sync'] = fields[2]
+ d['snr'] = fields[3]
+ d['dt'] = fields[4]
+ d['freq'] = fields[5]
+ d['msg'] = msg
+ d['drift'] = fields[-3]
+ d['cycles'] = fields[-2]
+ d['ii'] = fields[-1]
+
+# Determine message type
+ d['type1'] = True
+ d['type2'] = False
+ d['type3'] = False
+ if len(msg) != 3 or len(msg[1]) != 4 or len(msg[0]) < 3 or \
+ len(msg[0]) > 6 or not msg[2].isdigit():
+ d['type1'] = False
+ else:
+ dbm = int(msg[2])
+ if dbm < 0 or dbm > 60:
+ d['type1'] = False
+ n=dbm%10
+ if n!=0 and n!=3 and n!=7:
+ d['type1'] = False
+ if not d['type1']:
+ if len(msg)==2:
+ d['type2']=True
+ else:
+ d['type3']=True
+# Get callsign
+ callsign = d['msg'][0]
+ if callsign[0]=='<':
+ n=callsign.find('>')
+ callsign=callsign[1:n]
+ d['call'] = callsign
+ decodes.append(d)
+ f.close()
+ except:
+ decodes = []
+
+ if len(decodes) > 0:
+# Write data to text box; append freqs and calls to bandmap.
+ text.configure(state=NORMAL)
+ nseq=0
+ nfmid=int(1.0e6*fmid)%1000
+ for d in decodes:
+ text.insert(END, "%4s %3s %4s %10s %2s %s\n" % \
+ (d['time'],d['snr'],d['dt'],d['freq'],d['drift'],\
+ ' '.join(d['msg'])))
+ try:
+ callsign=d['call']
+ tmin=60*int(d['time'][0:2]) + int(d['time'][2:4])
+ ndf=int(d['freq'][-3:])
+ bandmap.append((ndf,callsign,tmin))
+ except:
+ pass
+ text.configure(state=DISABLED)
+ text.see(END)
+
+# Erase the bm{} dictionary, then repopulate it from "bandmap".
+# Most recent info for each callsign should be saved.
+ bm={}
+ iz=len(bandmap)
+ for i in range(iz):
+ bm[bandmap[i][1]]=(bandmap[i][0],bandmap[i][2])
+
+# Erase bandmap entirely
+ bandmap=[]
+# Repopulate "bandmap" from "bm", which should not contain dupes.
+ for callsign,ft in bm.items():
+ if callsign!='...':
+ ndf,tdecoded=ft
+ tmin=int((time.time()%86400)/60)
+ tdiff=tmin-tdecoded
+ if tdiff<0: tdiff=tdiff+1440
+# Insert info in "bandmap" only if age is less than one hour
+ if w.acom1.ndiskdat==1: tdiff=2
+ if tdiff < 60: #60 minutes
+ bandmap.append((ndf,callsign,tdecoded))
+
+# Once more, erase the bm{} dictionary, then repopulate it from "bandmap"
+ bm={}
+ iz=len(bandmap)
+ for i in range(iz):
+ bm[bandmap[i][1]]=(bandmap[i][0],bandmap[i][2])
+
+# Sort bandmap in reverse frequency order, then display it
+ bandmap.sort()
+ bandmap.reverse()
+ text1.configure(state=NORMAL)
+ text1.delete('1.0',END)
+ for i in range(iz):
+ t="%4d" % (bandmap[i][0],) + " " + bandmap[i][1]
+ nage=int((tmin - bandmap[i][2])/15)
+ if nage<0: nage=nage+96
+ attr='age0'
+ if nage==1: attr='age1'
+ if nage==2: attr='age2'
+ if nage>=3: attr='age3'
+ if w.acom1.ndiskdat==1: attr='age0'
+ text1.insert(END,t+"\n",attr)
+ text1.configure(state=DISABLED)
+ text1.see(END)
+
+ if upload.get():
+ #Dispatch autologger thread.
+ _thread.start_new_thread(autolog, (decodes,))
+
+ if loopall:
+ time.sleep(0.2)
+ opennext()
+
+#------------------------------------------------------ autologger
+def autolog(decodes):
+ # Random delay of up to 20 seconds to spread load out on server --W1BW
+ time.sleep(random.random() * 20.0)
+ try:
+ # This code originally by W6CQZ ... modified by W1BW
+ # TODO: Cache entries for later uploading if net is down.
+ # TODO: (Maybe??) Allow for stations wishing to collect spot data but
+ # only upload in batch form vs real-time.
+ # Any spots to upload?
+ if len(decodes) > 0:
+ for d in decodes:
+ # now to format as a string to use for autologger upload using urlencode
+ # so we get a string formatted for http get/put operations:
+ m=d['msg']
+ tcall=m[0]
+ if d['type2']:
+ tgrid=''
+ dbm=m[1]
+ else:
+ tgrid=m[1]
+ dbm=m[2]
+ if tcall[0]=='<':
+ n=tcall.find('>')
+ tcall=tcall[1:n]
+ if tcall=='...': continue
+ dfreq=float(d['freq'])-w.acom1.f0b-0.001500
+ if abs(dfreq)>0.0001:
+ print('Frequency changed, no upload of spots')
+ continue
+ reportparams = urllib.parse.urlencode({'function': 'wspr',
+ 'rcall': options.MyCall.get(),
+ 'rgrid': options.MyGrid.get(),
+ 'rqrg': str(f0.get()),
+ 'date': d['date'],
+ 'time': d['time'],
+ 'sig': d['snr'],
+ 'dt': d['dt'],
+ 'tqrg': d['freq'],
+ 'drift': d['drift'],
+ 'tcall': tcall,
+ 'tgrid': tgrid,
+ 'dbm': dbm,
+ 'version': Version})
+# reportparams now contains a properly formed http request string for
+# the agreed upon format between W6CQZ and N8FQ.
+# any other data collection point can be added as desired if it conforms
+# to the 'standard format' defined above.
+# The following opens a url and passes the reception report to the database
+# insertion handler for W6CQZ:
+# urlf = urllib.urlopen("http://jt65.w6cqz.org/rbc.php?%s" % reportparams)
+# The following opens a url and passes the reception report to the
+# database insertion handler from W1BW:
+ urlf = urllib.request.urlopen("http://wsprnet.org/post?%s" \
+ % reportparams)
+ reply = urlf.readlines()
+ urlf.close()
+ else:
+ # No spots to report, so upload status message instead. --W1BW
+ reportparams = urllib.parse.urlencode({'function': 'wsprstat',
+ 'rcall': options.MyCall.get(),
+ 'rgrid': options.MyGrid.get(),
+ 'rqrg': str(fmid),
+ 'tpct': str(ipctx.get()),
+ 'tqrg': sftx.get(),
+ 'dbm': str(options.dBm.get()),
+ 'version': Version})
+ urlf = urllib.request.urlopen("http://wsprnet.org/post?%s" \
+ % reportparams)
+ reply = urlf.readlines()
+ urlf.close()
+ except:
+ t=" UTC: attempted access to WSPRnet failed."
+ if not no_beep.get(): t=t + "\a"
+ print(time.asctime(time.gmtime()) + t)
+
+#------------------------------------------------------ put_params
+def put_params(param3=NONE):
+ global param20
+
+## try:
+## w.acom1.f0=f0.get()
+## w.acom1.ftx=ftx.get()
+## except:
+## pass
+ w.acom1.callsign=(options.MyCall.get().strip().upper()+' ')[:12]
+ w.acom1.grid=(options.MyGrid.get().strip().upper()+' ')[:4]
+ w.acom1.grid6=(options.MyGrid.get().strip().upper()+' ')[:6]
+ w.acom1.ctxmsg=(txmsg.get().strip().upper()+' ')[:22]
+
+ # numeric port ==> COM%d, else string of device. --W1BW
+ port = options.PttPort.get()
+ if port=='None': port='0'
+ if port[:3]=='COM': port=port[3:]
+ if port.isdigit():
+ w.acom1.nport = int(port)
+ port = "COM%d" % (int(port))
+ else:
+ w.acom1.nport = 0
+ w.acom1.pttport = (port + 80*' ')[:80]
+
+ try:
+ dbm=int(options.dBm.get())
+ except:
+ dbm=37
+ i1=options.MyCall.get().find('/')
+ if dbm<0 and (i1>0 or advanced.igrid6.get()):
+ MsgBox("Negative dBm values are permitted\n only for Type 1 messages.")
+ dbm=0
+ options.dBm.set(0)
+ mindiff=9999
+ for i in range(len(pwrlist)):
+ if abs(dbm-pwrlist[i])<mindiff:
+ mindiff=abs(dbm-pwrlist[i])
+ ibest=i
+ dbm=pwrlist[ibest]
+ options.dBm.set(dbm)
+ w.acom1.ndbm=dbm
+ w.acom1.ntxfirst=ntxfirst.get()
+ w.acom1.nsave=nsave.get()
+ try:
+ w.acom1.nbfo=advanced.bfofreq.get()
+ except:
+ w.acom1.nbfo=1500
+ try:
+ w.acom1.idint=advanced.idint.get()
+ except:
+ w.acom1.idint=0
+ w.acom1.igrid6=advanced.igrid6.get()
+ w.acom1.iqmode=iq.iqmode.get()
+ w.acom1.iqrx=iq.iqrx.get()
+ w.acom1.iqrxapp=iq.iqrxapp.get()
+ w.acom1.iqrxadj=iq.iqrxadj.get()
+ w.acom1.iqtx=iq.iqtx.get()
+ w.acom1.ntxdb=advanced.isc1.get()
+ bal=iq.isc2.get() + 0.02*iq.isc2a.get()
+ w.acom1.txbal=bal
+ pha=iq.isc3.get() + 0.02*iq.isc3a.get()
+ w.acom1.txpha=pha
+ try:
+ w.acom1.nfiq=iq.fiq.get()
+ except:
+ w.acom1.nfiq=0
+ w.acom1.ndevin=g.ndevin.get()
+ w.acom1.ndevout=g.ndevout.get()
+ w.acom1.nbaud=options.serial_rate.get()
+ w.acom1.ndatabits=options.databits.get()
+ w.acom1.nstopbits=options.stopbits.get()
+ w.acom1.chs=(options.serial_handshake.get() + \
+ ' ')[:40]
+ w.acom1.catport=(options.CatPort.get()+' ')[:12]
+ try:
+ w.acom1.nrig=options.rignum.get()
+ except:
+ pass
+
+#------------------------------------------------------ update
+# the routine will be invoked ~5 times per second
+def update():
+ global root_geom,isec0,im,pim,ndbm0,nsec0,a,ftx0,nin0,nout0, \
+ receiving,transmitting,newdat,nscroll,newspec,scale0,offset0, \
+ modpixmap0,tw,s0,c0,fmid,fmid0,loopall,ntr0,txmsg,iband0, \
+ bandmap,bm,t0,nreject,gain,phdeg,ierr,itx0,timer1,ndecoding0, \
+ hopping0,ntune0,startup,nred
+
+ tsec=time.time()
+ utc=time.gmtime(tsec)
+ nsec=int(tsec)
+ nsec0=nsec
+
+# enable/disable the Band Hop check box
+ if hopping.hoppingconfigured.get()==1:
+ if hopping0!=1:
+ hopping0=1
+ bhopping.configure(state=NORMAL)
+ else:
+ if hopping0!=2:
+ hopping0=2
+ bhopping.configure(state=DISABLED)
+
+# implement band happing if it was selected
+ hopped=0
+ if not idle.get():
+ if hopping.hopping.get()==1:
+ w.acom1.nfhopping=1
+ if w.acom1.nfhopok or startup:
+ w.acom1.nfhopok=0
+ startup=0
+ b=-1
+ if hopping.coord_bands.get()==1:
+ ns=nsec % 86400
+ ns1=ns % (10*120)
+ b=int(ns1/120) + 3
+ if b==12: b=2
+ if hopping.hoppingflag[int(b)].get()==0: b=-1
+ if b<0:
+ found=False
+ while not found:
+ b = random.randint(1,len(hopping.bandlabels)-1)
+ if hopping.hoppingflag[b].get()!=0:
+ found=True
+ ipctx.set(hopping.hoppingpctx[b].get())
+ if b!=iband.get(): hopped=1
+ iband.set(b)
+
+ else:
+ w.acom1.nfhopping=0
+ ns=nsec % 86400
+ ns1=ns % (10*120)
+ b=ns1/120 + 3
+ if b==12: b=2
+ if iband.get()==b and random.randint(1,2)==1 and ipctx.get()>0:
+ w.acom1.ntxnext=1
+
+ try:
+ f0.set(float(sf0.get()))
+ ftx.set(float(sftx.get()))
+ except:
+ pass
+
+ isec=utc[5]
+ trmin=2
+ twait=trmin - (tsec % trmin)
+
+ if iband.get()!=iband0 or advanced.fset.get():
+ advanced.fset.set(0)
+ f0.set(freq0[iband.get()])
+ t="%.6f" % (f0.get(),)
+ sf0.set(t)
+ ftx.set(freqtx[iband.get()])
+ t="%.6f" % (ftx.get(),)
+ sftx.set(t)
+ if options.cat_enable.get():
+ if advanced.encal.get():
+ nHz=int(advanced.Acal.get() + \
+ f0.get()*(1000000.0 + advanced.Bcal.get()) + 0.5)
+ else:
+ nHz=int(1000000.0*f0.get() + 0.5)
+ if options.rignum.get()==2509 or options.rignum.get()==2511:
+ nHzLO=nHz - iq.fiq.get()
+ cmd="rigctl -m %d -r %s F %d" % \
+ (options.rignum.get(),options.CatPort.get(),nHzLO)
+ elif options.rignum.get()==1901:
+ cmd="rigctl -m 1901 -r localhost F %d" % (nHz,)
+ else:
+ cmd="rigctl -m %d -r %s -s %d -C data_bits=%s -C stop_bits=%s -C serial_handshake=%s F %d" % \
+ (options.rignum.get(),options.CatPort.get(), \
+ options.serial_rate.get(),options.databits.get(), \
+ options.stopbits.get(),options.serial_handshake.get(),nHz)
+ ierr=os.system(cmd)
+ if ierr==0:
+ ierr2=0
+ bandmap=[]
+ bm={}
+ text1.configure(state=NORMAL)
+ text1.delete('1.0',END)
+ text1.configure(state=DISABLED)
+ iband0=iband.get()
+ f=open(appdir+'/fmt.ini',mode='w')
+ f.write(cmd+'\n')
+ f.write(str(g.ndevin.get())+'\n')
+ f.write(options.MyCall.get()+'\n')
+ f.write(options.MyGrid.get()+'\n')
+ f.close()
+
+ cmd2=''
+ if os.path.exists('.\\user_hardware.bat') or \
+ os.path.exists('.\\user_hardware.cmd') or \
+ os.path.exists('.\\user_hardware.exe'):
+ cmd2='.\\user_hardware ' + str(band[iband0])
+ elif os.path.exists('./user_hardware'):
+ cmd2='./user_hardware ' + str(band[iband0])
+ if cmd2!='':
+ try:
+ ierr2=os.system(cmd2)
+ except:
+ ierr2=-1
+ if ierr2!=0:
+ print('Execution of "'+cmd2+'" failed.')
+ MsgBox('Execution of "'+cmd2+ \
+ '" failed.\nEntering Idle mode.')
+ else:
+ print('Error attempting to set rig frequency.\a')
+ print(cmd + '\a')
+ iband.set(iband0)
+ f0.set(freq0[iband.get()])
+ t="%.6f" % (f0.get(),)
+ sf0.set(t)
+ ftx.set(freqtx[iband.get()])
+ t="%.6f" % (ftx.get(),)
+ sftx.set(t)
+ if ierr==0 and ierr2==0 and w.acom1.nfhopping==1 and hopped==1 \
+ and hopping.tuneupflag[iband.get()].get(): w.acom1.ntune=-3
+ else:
+ iband0=iband.get()
+ iq.ib.set(iband.get())
+ iq.newband()
+
+ freq0[iband.get()]=f0.get()
+ freqtx[iband.get()]=ftx.get()
+ w.acom1.iband=iband.get()
+
+ try:
+ w.acom1.f0=f0.get()
+ w.acom1.ftx=ftx.get()
+ except:
+ pass
+
+ newsecond=0 # =1 if a new second
+ if isec != isec0: #Do once per second
+# this code block is executed once per second
+ newsecond=1
+ t=time.strftime('%Y %b %d\n%H:%M:%S',utc)
+ ldate.configure(text=t)
+ root_geom=root.geometry()
+ utchours=utc[3]+utc[4]/60.0 + utc[5]/3600.0
+ try:
+ if options.dBm.get()!=ndbm0:
+ ndbm0=options.dBm.get()
+ options.dbm_balloon()
+ except:
+ pass
+
+ put_params()
+ nndf=int(1000000.0*(ftx.get()-f0.get()) + 0.5) - 1500
+ gain=w.acom1.gain
+ phdeg=57.2957795*w.acom1.phase
+ nreject=int(w.acom1.reject)
+
+# this code block is executed ~5 times per second
+# NB: the digital gain control "ndgain" presently has cosmetic effect only.
+ ndb=int(w.acom1.xdb1-41.0+ndgain.get())
+ if ndb<-30: ndb=-30
+ dbave=w.acom1.xdb1
+ if iq.iqmode.get():
+ t='Bal: %6.4f Pha: %6.1f >%3d dB' % (gain,phdeg,nreject)
+ iq.lab1.configure(text=t)
+ ndb2=int(w.acom1.xdb2-41.0)
+ if ndb2<-30: ndb2=-30
+ dbave=0.5*(w.acom1.xdb1 + w.acom1.xdb2)
+ t='Rx Noise: %3d %3d dB' % (ndb,ndb2)
+ else:
+ t='Rx Noise: %3d dB' % (ndb,)
+# update noise display at lower left of screen
+ bg='gray85'
+ r=SUNKEN
+ smcolor="green"
+ if w.acom1.receiving==0:
+ t=''
+ r=FLAT
+
+ if isec!=isec0:
+ msg1.configure(text=t,relief=r)
+ isec0=isec
+
+ dbave=dbave + ndgain.get()
+ if not receiving: dbave=0
+ sm.updateProgress(newValue=dbave,newColor=smcolor)
+
+ if nred>0:
+ nred=nred-1
+ if nred==0: lab02.configure(text="",bg='gray85')
+
+# If T/R status has changed, get new info
+ ntr=int(w.acom1.ntr)
+ itx=w.acom1.transmitting
+ if ntr!=ntr0 or itx!=itx0:
+ ntr0=ntr
+ itx0=int(itx)
+ if ntr==-1 or itx==1:
+ transmitting=1
+ receiving=0
+ elif ntr==0:
+ transmitting=0
+ receiving=0
+ else:
+ transmitting=0
+ receiving=1
+ n=len(tw)
+ if n>12: tw=tw[:n-1]
+ rxtime=w.acom1.rxtime.tostring().decode('utf-8')
+ rxtime=rxtime[:2] + ':' + rxtime[2:]
+ tw=[rxtime,] + tw
+
+ global fw
+ if n>12: fw=fw[:n-1]
+ fw=[hopping.bandlabels[ iband.get()][:-2],] + fw
+ if receiving:
+ filemenu.entryconfig(0,state=DISABLED)
+ filemenu.entryconfig(1,state=DISABLED)
+ filemenu.entryconfig(2,state=DISABLED)
+ else:
+ filemenu.entryconfig(0,state=NORMAL)
+ filemenu.entryconfig(1,state=NORMAL)
+ filemenu.entryconfig(2,state=NORMAL)
+ if transmitting:
+ btxnext.configure(bg="gray85")
+ for i in range(15):
+ bandmenu.entryconfig(i,state=DISABLED)
+ else:
+ for i in range(15):
+ bandmenu.entryconfig(i,state=NORMAL)
+
+# update the receiving status at the lower right of screen
+ bgcolor='gray85'
+ t='Waiting to start'
+ bgcolor='pink'
+ if transmitting:
+ t='Txing: ' + w.acom1.sending.tostring().decode('utf-8')
+ bgcolor='yellow'
+ if receiving:
+ t='Receiving'
+ bgcolor='green'
+ if t!=t0: # dont draw unless changed
+ msg6.configure(text=t,bg=bgcolor)
+ t0=t
+
+# tend to percent scale
+ ntune=int(w.acom1.ntune)
+ if ntune!=ntune0:
+ ntune0=ntune
+ if ntune==0:
+ btune.configure(bg='gray85')
+ pctscale.configure(state=NORMAL)
+ else:
+ pctscale.configure(state=DISABLED)
+
+# set idle switch
+ global ncal0
+ ncal=w.acom1.ncal
+ if ncal!=ncal0:
+ ncal0=ncal
+ if ncal==0:
+ advanced.bmeas.configure(bg='gray85')
+ else:
+ idle.set(1)
+
+ if ierr==0 and txmute.get()==0:
+ w.acom1.pctx=ipctx.get()
+ else:
+ w.acom1.pctx=0
+
+# if mute is pressed turn TxNext gray and mute button red
+ global txmute0
+ if txmute.get()!=txmute0:
+ txmute0=txmute.get()
+ if txmute0:
+ w.acom1.pctx=0
+ w.acom1.ntxnext=0
+ bmute.configure(bg='red')
+ btxnext.configure(state=DISABLED)
+ btxnext.configure(bg='gray85')
+ else:
+ bmute.configure(bg='gray85')
+ btxnext.configure(state=NORMAL)
+
+ w.acom1.idle=idle.get()
+
+# make idle button yellow if checked
+ global idle0
+ if idle0!=idle.get():
+ idle0=idle.get()
+ if idle0==0:
+ bidle.configure(bg='gray85')
+ else:
+ bidle.configure(bg='yellow')
+
+ global btune0
+ if w.acom1.transmitting or w.acom1.receiving or options.outbad.get():
+ if btune0!=1:
+ btune0=1
+ btune.configure(state=DISABLED)
+ else:
+ if btune0!=2:
+ btune0=2
+ btune.configure(state=NORMAL)
+
+ global adv0
+ if w.acom1.transmitting or w.acom1.receiving or twait < 6.0:
+ if adv0!=1:
+ adv0=1
+ advanced.bmeas.configure(state=DISABLED)
+ else:
+ if adv0!=2:
+ adv0=2
+ advanced.bmeas.configure(state=NORMAL)
+
+# update the upload spots button color
+ global upload0
+ if upload.get()==1:
+ if upload0!=1:
+ upload0=1
+ bupload.configure(bg='gray85')
+ else:
+ if upload0!=2:
+ upload0=2
+ bupload.configure(bg='yellow')
+
+# If new decoded text has appeared, display it.
+ if w.acom1.ndecdone:
+ get_decoded()
+ w.acom1.ndecdone=0
+# w.acom1.ndiskdat=0
+
+# Display the waterfall
+ try:
+ modpixmap=os.stat('pixmap.dat')[8]
+ if modpixmap!=modpixmap0:
+ f=open('pixmap.dat','rb')
+ a=array.array('h')
+ a.fromfile(f,NX*NY)
+ f.close()
+ newdat=1
+ modpixmap0=modpixmap
+ except:
+ newdat=0
+
+ scale=math.pow(10.0,0.003*sc1.get())
+ offset=0.3*sc2.get()
+ if newdat or scale!= scale0 or offset!=offset0 or g.cmap!=g.cmap0:
+ im.putdata(a,scale,offset) #Compute whole new image
+ if newdat:
+ n=len(tw)
+ for i in range(n-1,-1,-1):
+ x=465-39*i
+ draw.text((x,148),tw[i],fill=253) #Insert time label
+ if i<len(fw):
+ draw.text((x+10,1),fw[i],fill=253) #Insert band label
+
+ pim=ImageTk.PhotoImage(im) #Convert Image to PhotoImage
+ graph1.delete(ALL)
+ graph1.create_image(0,0+2,anchor='nw',image=pim)
+ g.ndecphase=2
+ newMinute=0
+ scale0=scale
+ offset0=offset
+ g.cmap0=g.cmap
+ newdat=0
+
+ s0=sc1.get()
+ c0=sc2.get()
+ try:
+ fmid=f0.get() + 0.001500
+ except:
+ pass
+
+ if fmid!=fmid0 or ftx.get()!=ftx0:
+ fmid0=fmid
+ ftx0=ftx.get()
+ draw_axis()
+ lftx.configure(validate={'validator':'real',
+ 'min':f0.get()+0.001500-0.000100,'minstrict':0,
+ 'max':f0.get()+0.001500+0.000100,'maxstrict':0})
+
+ w.acom1.ndebug=ndebug.get()
+
+ if options.rignum.get()==2509 or options.rignum.get()==2511:
+ options.pttmode.set('CAT')
+ options.CatPort.set('USB')
+
+ if options.pttmode.get()=='CAT':
+ options.cat_enable.set(1)
+
+ if options.pttmode.get()=='CAT' or options.pttmode.get()=='VOX':
+ options.PttPort.set('None')
+ options.ptt_port._entryWidget['state']=DISABLED
+ else:
+ options.ptt_port._entryWidget['state']=NORMAL
+
+ global adv1
+ if options.cat_enable.get():
+ options.lrignum._entryWidget['state']=NORMAL
+ if options.cat_port.get() != 'USB':
+ options.cat_port._entryWidget['state']=NORMAL
+ options.cbbaud._entryWidget['state']=NORMAL
+ options.cbdata._entryWidget['state']=NORMAL
+ options.cbstop._entryWidget['state']=NORMAL
+ options.cbhs._entryWidget['state']=NORMAL
+ else:
+ options.cat_port._entryWidget['state']=DISABLED
+ options.cbbaud._entryWidget['state']=DISABLED
+ options.cbdata._entryWidget['state']=DISABLED
+ options.cbstop._entryWidget['state']=DISABLED
+ options.cbhs._entryWidget['state']=DISABLED
+ if adv1!=1:
+ adv1=1
+ advanced.bsetfreq.configure(state=NORMAL)
+ advanced.breadab.configure(state=NORMAL)
+ advanced.enable_cal.configure(state=NORMAL)
+ else:
+ options.cat_port._entryWidget['state']=DISABLED
+ options.lrignum._entryWidget['state']=DISABLED
+ options.cbbaud._entryWidget['state']=DISABLED
+ options.cbdata._entryWidget['state']=DISABLED
+ options.cbstop._entryWidget['state']=DISABLED
+ options.cbhs._entryWidget['state']=DISABLED
+ if adv1!=2:
+ adv1=2
+ advanced.bsetfreq.configure(state=DISABLED)
+ advanced.breadab.configure(state=DISABLED)
+ advanced.enable_cal.configure(state=DISABLED)
+ advanced.encal.set(0)
+
+ w.acom1.pttmode=(options.pttmode.get().strip()+' ')[:3]
+ w.acom1.ncat=options.cat_enable.get()
+ w.acom1.ncoord=hopping.coord_bands.get()
+ w.acom1.ntrminutes=2
+
+ if g.ndevin.get()!= nin0 or g.ndevout.get()!=nout0:
+ audio_config()
+ nin0=g.ndevin.get()
+ nout0=g.ndevout.get()
+
+ global inbad0
+ if inbad0!=options.inbad.get():
+ inbad0=options.inbad.get()
+ if inbad0==0:
+ msg2.configure(text='',bg='gray85')
+ else:
+ msg2.configure(text='Invalid audio input device.',bg='red')
+
+ global outbad0
+ if outbad0!=options.outbad.get():
+ outbad0=options.outbad.get()
+ if outbad0==0:
+ msg3.configure(text='',bg='gray85')
+ else:
+ msg3.configure(text='Invalid audio output device.',bg='red')
+
+ if ndecoding0!=int(w.acom1.ndecoding):
+ ndecoding0=int(w.acom1.ndecoding)
+ if ndecoding0:
+ msg5.configure(text='Decoding',bg='#66FFFF',relief=SUNKEN)
+ else:
+ msg5.configure(text='',bg='gray85',relief=FLAT)
+
+ global encal0
+ if encal0!=advanced.encal.get():
+ encal0=advanced.encal.get()
+ if encal0:
+ advanced.A_entry.configure(entry_state=NORMAL,label_state=NORMAL)
+ advanced.B_entry.configure(entry_state=NORMAL,label_state=NORMAL)
+ else:
+ advanced.A_entry.configure(entry_state=DISABLED, \
+ label_state=DISABLED)
+ advanced.B_entry.configure(entry_state=DISABLED, \
+ label_state=DISABLED)
+
+ timer1=ldate.after(200,update)
+
+#------------------------------------------------------ audio_config
+def audio_config():
+ inbad,outbad=w.audiodev(g.ndevin.get(),g.ndevout.get())
+ options.inbad.set(inbad)
+ options.outbad.set(outbad)
+ if inbad or outbad:
+ w.acom1.ndevsok=0
+ options1()
+ else:
+ w.acom1.ndevsok=1
+
+#------------------------------------------------------ save_params
+def save_params():
+ f=open(appdir+'/WSPR.INI',mode='w')
+ f.write("WSPRGeometry " + root_geom + "\n")
+ if options.MyCall.get()=='': options.MyCall.set('##')
+ f.write("MyCall " + options.MyCall.get() + "\n")
+ if options.MyGrid.get()=='': options.MyGrid.set('##')
+ f.write("MyGrid " + options.MyGrid.get() + "\n")
+ f.write("CWID " + str(advanced.idint.get()) + "\n")
+ f.write("dBm " + str(options.dBm.get()) + "\n")
+ f.write("PttPort " + str(options.PttPort.get()) + "\n")
+ f.write("CatPort " + str(options.CatPort.get()) + "\n")
+ if options.DevinName.get()=='': options.DevinName.set('0')
+ f.write("AudioIn " + options.DevinName.get().replace(" ","#") + "\n")
+ if options.DevoutName.get()=='': options.DevoutName.set('2')
+ f.write("AudioOut " + options.DevoutName.get().replace(" ","#") + "\n")
+ f.write("BFOfreq " + str(advanced.bfofreq.get()) + "\n")
+ f.write("PTTmode " + options.pttmode.get() + "\n")
+ f.write("CATenable " + str(options.cat_enable.get()) + "\n")
+ f.write("Acal " + str(advanced.Acal.get()) + "\n")
+ f.write("Bcal " + str(advanced.Bcal.get()) + "\n")
+ f.write("CalEnable " + str(advanced.encal.get()) + "\n")
+ f.write("IQmode " + str(iq.iqmode.get()) + "\n")
+ f.write("IQrx " + str(iq.iqrx.get()) + "\n")
+ f.write("IQtx " + str(iq.iqtx.get()) + "\n")
+ f.write("FIQ " + str(iq.fiq.get()) + "\n")
+ f.write("Ntxdb " + str(advanced.isc1.get()) + "\n")
+ f.write("SerialRate " + str(options.serial_rate.get()) + "\n")
+ f.write("DataBits " + str(options.databits.get()) + "\n")
+ f.write("StopBits " + str(options.stopbits.get()) + "\n")
+ f.write("Handshake " + options.serial_handshake.get().replace(" ","#") \
+ + "\n")
+ t=str(options.rig.get().replace(" ","#"))
+ f.write("Rig " + str(t.replace("\t","#"))[:46] + "\n")
+ f.write("Nsave " + str(nsave.get()) + "\n")
+ f.write("PctTx " + str(ipctx.get()) + "\n")
+ f.write("DGain " + str(ndgain.get()) + "\n")
+ f.write("Upload " + str(upload.get()) + "\n")
+ f.write("Idle " + str(idle.get()) + "\n")
+ f.write("Debug " + str(ndebug.get()) + "\n")
+ f.write("WatScale " + str(s0) + "\n")
+ f.write("WatOffset " + str(c0) + "\n")
+ f.write("Palette " + g.cmap + "\n")
+ mrudir2=mrudir.replace(" ","#")
+ f.write("MRUdir " + mrudir2 + "\n")
+ f.write("freq0_600 " + str( freq0[1]) + "\n")
+ f.write("freqtx_600 " + str(freqtx[1]) + "\n")
+ f.write("freq0_160 " + str( freq0[2]) + "\n")
+ f.write("freqtx_160 " + str(freqtx[2]) + "\n")
+ f.write("freq0_80 " + str( freq0[3]) + "\n")
+ f.write("freqtx_80 " + str(freqtx[3]) + "\n")
+ f.write("freq0_60 " + str( freq0[4]) + "\n")
+ f.write("freqtx_60 " + str(freqtx[4]) + "\n")
+ f.write("freq0_40 " + str( freq0[5]) + "\n")
+ f.write("freqtx_40 " + str(freqtx[5]) + "\n")
+ f.write("freq0_30 " + str( freq0[6]) + "\n")
+ f.write("freqtx_30 " + str(freqtx[6]) + "\n")
+ f.write("freq0_20 " + str( freq0[7]) + "\n")
+ f.write("freqtx_20 " + str(freqtx[7]) + "\n")
+ f.write("freq0_17 " + str( freq0[8]) + "\n")
+ f.write("freqtx_17 " + str(freqtx[8]) + "\n")
+ f.write("freq0_15 " + str( freq0[9]) + "\n")
+ f.write("freqtx_15 " + str(freqtx[9]) + "\n")
+ f.write("freq0_12 " + str( freq0[10]) + "\n")
+ f.write("freqtx_12 " + str(freqtx[10]) + "\n")
+ f.write("freq0_10 " + str( freq0[11]) + "\n")
+ f.write("freqtx_10 " + str(freqtx[11]) + "\n")
+ f.write("freq0_6 " + str( freq0[12]) + "\n")
+ f.write("freqtx_6 " + str(freqtx[12]) + "\n")
+ f.write("freq0_4 " + str( freq0[13]) + "\n")
+ f.write("freqtx_4 " + str(freqtx[13]) + "\n")
+ f.write("freq0_2 " + str( freq0[14]) + "\n")
+ f.write("freqtx_2 " + str(freqtx[14]) + "\n")
+ f.write("freq0_other " + str( freq0[15]) + "\n")
+ f.write("freqtx_other " + str(freqtx[15]) + "\n")
+ f.write("iband " + str(iband.get()) + "\n")
+ f.write("StartIdle " + str(start_idle.get()) + "\n")
+ f.write("NoBeep " + str(no_beep.get()) + "\n")
+ f.write("Reject " + str(nreject) + "\n")
+ f.write("RxApply " + str(iq.iqrxapp.get()) + "\n")
+ f.close()
+ hopping.save_params(appdir)
+
+#------------------------------------------------------ Top level frame
+frame = Frame(root)
+
+#------------------------------------------------------ Menu Bar
+mbar = Frame(frame)
+mbar.pack(fill = X)
+
+#------------------------------------------------------ File Menu
+filebutton = Menubutton(mbar, text = 'File')
+filebutton.pack(side = LEFT)
+filemenu = Menu(filebutton, tearoff=0)
+filebutton['menu'] = filemenu
+filemenu.add('command', label = 'Open', command = openfile, \
+ accelerator='Ctrl+O')
+filemenu.add('command', label = 'Open next in directory', command = opennext, \
+ accelerator='F6')
+filemenu.add('command', label = 'Decode remaining files in directory', \
+ command = decodeall, accelerator='Shift+F6')
+filemenu.add_separator()
+filemenu.add('command', label = 'Delete all *.WAV files in Save', \
+ command = delwav)
+filemenu.add_separator()
+filemenu.add('command', label = 'Erase ALL_WSPR.TXT', command = del_all)
+filemenu.add_separator()
+filemenu.add('command', label = 'Save user parameters', command = save_params)
+filemenu.add_separator()
+filemenu.add('command', label = 'Exit', command = quit, accelerator='Alt+F4')
+
+#------------------------------------------------------ Setup menu
+setupbutton = Menubutton(mbar, text = 'Setup')
+setupbutton.pack(side = LEFT)
+setupmenu = Menu(setupbutton, tearoff=0)
+setupbutton['menu'] = setupmenu
+setupmenu.add('command', label = 'Station parameters', command = options1,
+ accelerator='F2')
+setupmenu.add('command', label = 'Advanced', command = advanced1,
+ accelerator='F7')
+setupmenu.add('command', label = 'IQ Mode', command = iq1,
+ accelerator='F8')
+setupmenu.add('command', label = 'Band Hopping', command = hopping1,
+ accelerator='F9')
+setupmenu.add_separator()
+setupmenu.add_checkbutton(label = 'Always start in Idle mode',
+ variable=start_idle)
+setupmenu.add_checkbutton(label = 'No beep when access to WSPRnet fails',
+ variable=no_beep)
+
+#--------------------------------------------------------- View menu
+viewbutton = Menubutton(mbar, text = 'View', )
+viewbutton.pack(side = LEFT)
+viewmenu = Menu(viewbutton, tearoff=0)
+viewbutton['menu'] = viewmenu
+viewmenu.palettes=Menu(setupmenu,tearoff=0)
+viewmenu.palettes.add_radiobutton(label='Gray0',command=pal_gray0,
+ value=0,variable=npal)
+viewmenu.palettes.add_radiobutton(label='Gray1',command=pal_gray1,
+ value=1,variable=npal)
+viewmenu.palettes.add_radiobutton(label='Linrad',command=pal_linrad,
+ value=2,variable=npal)
+viewmenu.palettes.add_radiobutton(label='Blue',command=pal_blue,
+ value=3,variable=npal)
+viewmenu.palettes.add_radiobutton(label='Hot',command=pal_Hot,
+ value=4,variable=npal)
+viewmenu.palettes.add_radiobutton(label='AFMHot',command=pal_AFMHot,
+ value=5,variable=npal)
+viewmenu.add_cascade(label = 'Palette',menu=viewmenu.palettes)
+
+#------------------------------------------------------ Mode menu
+##modebutton = Menubutton(mbar, text = 'Mode')
+##modebutton.pack(side = LEFT)
+##modemenu = Menu(modebutton, tearoff=0)
+##modebutton['menu'] = modemenu
+##modemenu.add_radiobutton(label = 'WSPR-2', variable=ntrminutes,value=2)
+##modemenu.add_radiobutton(label = 'WSPR-15', variable=ntrminutes,value=15)
+##ntrminutes.set(2)
+
+#------------------------------------------------------ Save menu
+savebutton = Menubutton(mbar, text = 'Save')
+savebutton.pack(side = LEFT)
+savemenu = Menu(savebutton, tearoff=0)
+savebutton['menu'] = savemenu
+savemenu.add_radiobutton(label = 'None', variable=nsave,value=0)
+#savemenu.add_radiobutton(label = 'Save decoded', variable=nsave,value=1)
+savemenu.add_radiobutton(label = 'Save all', variable=nsave,value=2)
+nsave.set(0)
+
+#------------------------------------------------------ Band menu
+bandbutton = Menubutton(mbar, text = 'Band')
+bandbutton.pack(side = LEFT)
+bandmenu = Menu(bandbutton, tearoff=0)
+bandbutton['menu'] = bandmenu
+iband.set(6)
+bandmenu.add_radiobutton(label = '600 m',variable=iband,value=1)
+bandmenu.add_radiobutton(label = '160 m',variable=iband,value=2)
+bandmenu.add_radiobutton(label = '80 m', variable=iband,value=3)
+bandmenu.add_radiobutton(label = '60 m', variable=iband,value=4)
+bandmenu.add_radiobutton(label = '40 m', variable=iband,value=5)
+bandmenu.add_radiobutton(label = '30 m', variable=iband,value=6)
+bandmenu.add_radiobutton(label = '20 m', variable=iband,value=7)
+bandmenu.add_radiobutton(label = '17 m', variable=iband,value=8)
+bandmenu.add_radiobutton(label = '15 m', variable=iband,value=9)
+bandmenu.add_radiobutton(label = '12 m', variable=iband,value=10)
+bandmenu.add_radiobutton(label = '10 m', variable=iband,value=11)
+bandmenu.add_radiobutton(label = '6 m', variable=iband,value=12)
+bandmenu.add_radiobutton(label = '4 m', variable=iband,value=13)
+bandmenu.add_radiobutton(label = '2 m', variable=iband,value=14)
+bandmenu.add_radiobutton(label = 'Other',variable=iband,value=15)
+
+
+#------------------------------------------------------ Help menu
+helpbutton = Menubutton(mbar, text = 'Help')
+helpbutton.pack(side = LEFT)
+helpmenu = Menu(helpbutton, tearoff=0)
+helpbutton['menu'] = helpmenu
+helpmenu.add('command',label='Help',command=help,accelerator='F1')
+helpmenu.add('command',label="Online WSPR User's Guide",command=usersguide, \
+ accelerator='F3')
+helpmenu.add('command',label="Online FMT User's Guide",command=fmtguide)
+helpmenu.add('command',label="WSPRnet.org",command=wsprnet, \
+ accelerator='F4')
+helpmenu.add('command',label="WSJT Home Page",command=homepage)
+helpmenu.add('command', label='About WSPR',command=about,accelerator='F5')
+root.bind_all('<Escape>', stop_loopall)
+root.bind_all('<F1>', help)
+root.bind_all('<F2>', options1)
+root.bind_all('<F3>', usersguide)
+root.bind_all('<F4>', wsprnet)
+root.bind_all('<Alt-F4>', quit)
+root.bind_all('<F5>', about)
+root.bind_all('<F6>', opennext)
+root.bind_all('<F7>', advanced1)
+root.bind_all('<F8>', iq1)
+root.bind_all('<F9>', hopping1)
+root.bind_all('<Shift-F6>', decodeall)
+root.bind_all('<Control-o>',openfile)
+root.bind_all('<Control-O>',openfile)
+
+#------------------------------------------------------ Graphics area
+iframe1 = Frame(frame, bd=1, relief=SUNKEN)
+
+graph1=Canvas(iframe1, bg='black', width=NX, height=NY,cursor='crosshair')
+Widget.bind(graph1,"<Motion>",df_readout)
+Widget.bind(graph1,"<Double-Button-1>",set_tx_freq)
+graph1.pack(side=LEFT)
+c=Canvas(iframe1, bg='white', width=40, height=NY,bd=0)
+c.pack(side=LEFT)
+
+text1=Text(iframe1, height=10, width=15, bg='Navy', fg="yellow")
+text1.pack(side=LEFT, padx=1)
+text1.tag_configure('age0',foreground='red')
+text1.tag_configure('age1',foreground='yellow')
+text1.tag_configure('age2',foreground='gray75')
+text1.tag_configure('age3',foreground='gray50')
+text1.insert(END,'132 ZL1BPU')
+sb = Scrollbar(iframe1, orient=VERTICAL, command=text1.yview)
+sb.pack(side=RIGHT, fill=Y)
+text1.configure(yscrollcommand=sb.set)
+iframe1.pack(expand=1, fill=X, padx=4)
+
+iframe2 = Frame(frame, bd=1, relief=FLAT)
+sc1=Scale(iframe2,from_=-100.0,to_=100.0,orient='horizontal',
+ showvalue=0,sliderlength=5)
+sc1.pack(side=LEFT)
+sc2=Scale(iframe2,from_=-100.0,to_=100.0,orient='horizontal',
+ showvalue=0,sliderlength=5)
+sc2.pack(side=LEFT)
+balloon.bind(sc1,"Brightness")
+balloon.bind(sc2,"Contrast")
+bupload=Checkbutton(iframe2,text='Upload spots',justify=RIGHT,variable=upload)
+balloon.bind(bupload,"Check to send spots to WSPRnet.org")
+bupload.place(x=330,y=12, anchor='e')
+bhopping=Checkbutton(iframe2,text='Band Hop',justify=RIGHT, \
+ variable=hopping.hopping)
+bhopping.place(x=445,y=12, anchor='e')
+bhopping.configure(state=DISABLED)
+balloon.bind(bhopping,"Check to band hop; configure in Setup->Band Hopping")
+lab00=Label(iframe2, text='Band Map').place(x=623,y=10, anchor='e')
+lab02=Label(iframe2,text='',pady=5)
+lab02.place(x=500,y=10, anchor='e')
+iframe2.pack(expand=1, fill=X, padx=4)
+
+#------------------------------------------------------ Stuff under graphics
+iframe2a = Frame(frame, bd=1, relief=FLAT)
+g1=Pmw.Group(iframe2a,tag_text="Frequencies (MHz)")
+lf0=Pmw.EntryField(g1.interior(),labelpos=W,label_text='Dial:',
+ value=10.1387,entry_textvariable=sf0,entry_width=12,
+ validate='real')
+lftx=Pmw.EntryField(g1.interior(),labelpos=W,label_text='Tx: ',
+ value=10.140000,entry_textvariable=sftx,entry_width=12,validate='real')
+
+widgets = (lf0,lftx)
+for widget in widgets:
+ widget.pack(side=TOP,padx=5,pady=4)
+balloon.bind(lf0,"Set radio's dial frequency to this value and select USB mode")
+balloon.bind(lftx,"Will transmit on this frequency")
+
+Pmw.alignlabels(widgets)
+
+g1.pack(side=LEFT,fill=BOTH,expand=0,padx=10,pady=6)
+lab01=Label(iframe2a, text='').pack(side=LEFT,padx=1)
+g2=Pmw.Group(iframe2a,tag_text="Tx fraction (%)")
+#------------------------------------------------------ Tx percentage Select
+pctscale=Scale(g2.interior(),orient=HORIZONTAL,length=350,from_=0, \
+ to=100,tickinterval=10,variable=ipctx)
+pctscale.pack(side=LEFT,padx=4)
+balloon.bind(pctscale,"Select desired fraction of sequences to transmit")
+ipctx.set(0)
+g2.pack(side=LEFT,fill=BOTH,expand=0,padx=10,pady=6)
+#------------------------------------------------------ Special controls
+g3=Pmw.Group(iframe2a,tag_text='Special')
+bidle=Checkbutton(g3.interior(),text='Idle ',justify=RIGHT, \
+ variable=idle,width=5)
+bidle.grid(row=0,column=1,padx=4,pady=3)
+balloon.bind(bidle,"Check for no automatic T/R sequences")
+bmute=Checkbutton(g3.interior(),text='Tx Mute',justify=RIGHT, \
+ variable=txmute,width=7)
+bmute.grid(row=1,column=1,padx=4,pady=3)
+balloon.bind(bmute,"Check for no Tx")
+btune=Button(g3.interior(), text='Tune',underline=0,command=tune,width=9)
+btune.grid(row=1,column=0,padx=2,pady=3)
+balloon.bind(btune,"Transmit for number of seconds set by Tx fraction slider")
+btxnext=Button(g3.interior(), text='Tx Next',underline=3,command=txnext,width=9)
+btxnext.grid(row=0,column=0,padx=2,pady=3)
+balloon.bind(btxnext,"Make the next 2-minute period a transmission")
+g3.pack(side=LEFT,fill=X,expand=0,padx=10,pady=1)
+
+iframe2a.pack(expand=1, fill=X, padx=1)
+
+iframe2 = Frame(frame, bd=1, relief=FLAT,height=15)
+lab2=Label(iframe2,text='UTC dB DT Freq Drift')
+if g.Win32:
+ lab2.place(x=208,y=6, anchor='w')
+else:
+ lab2.place(x=198,y=6, anchor='w')
+iframe2.pack(expand=1, fill=X, padx=4)
+
+#-------------------------------------------------------- UTC, etc
+iframe4 = Frame(frame, bd=1, relief=FLAT)
+f4aa=Frame(iframe4,height=170,bd=2,relief=RIDGE)
+sm=smeter.Smeter(f4aa,fillColor='green',orientation='vertical', \
+ width=10,height=170,doLabel=0,min=0,max=80)
+sm.frame.pack(side=LEFT)
+
+dgainscale=Scale(f4aa,orient=VERTICAL,length=170,from_=50, \
+ to=-50,variable=ndgain,sliderlength=20,showvalue=0,width=9)
+dgainscale.pack(side=LEFT,padx=4)
+balloon.bind(dgainscale,"Digital gain control")
+ndgain.set(0)
+
+g2.pack(side=LEFT,fill=BOTH,expand=0,padx=10,pady=6)
+
+f4aa.pack(side=LEFT,expand=0,fill=Y)
+
+f4a=Frame(iframe4,height=170,bd=2,relief=RIDGE)
+
+berase=Button(f4a, text='Erase',underline=0,command=erase,\
+ width=9,padx=1,pady=1)
+berase.pack(side=TOP,padx=0,pady=40)
+balloon.bind(berase,"Erase decoded text and band map")
+
+ldate=Label(f4a, bg='black', fg='yellow', width=11, bd=4,
+ text='2005 Apr 22\n01:23:45', relief=RIDGE,
+ justify=CENTER, font=(font1,14))
+ldate.pack(side=TOP,padx=10,pady=0)
+f4a.pack(side=LEFT,expand=0,fill=Y)
+
+#--------------------------------------------------------- Decoded text box
+f4b=Frame(iframe4,height=170,bd=2,relief=RIDGE)
+text=Text(f4b, height=11, width=63, bg='white')
+sb = Scrollbar(f4b, orient=VERTICAL, command=text.yview)
+sb.pack(side=RIGHT, fill=Y)
+text.pack(side=RIGHT, fill=X, padx=1)
+text.insert(END,'1054 4 -25 1.12 10.140140 K1JT FN20 25')
+text.configure(yscrollcommand=sb.set)
+f4b.pack(side=LEFT,expand=0,fill=Y)
+iframe4.pack(expand=1, fill=X, padx=4)
+
+
+#------------------------------------------------------------ Status Bar
+iframe6 = Frame(frame, bd=1, relief=SUNKEN)
+msg1=Message(iframe6, text=' ', width=300,relief=SUNKEN)
+msg1.pack(side=LEFT, fill=X, padx=1)
+msg2=Message(iframe6, text=' ', width=300,relief=FLAT)
+msg2.pack(side=LEFT, fill=X, padx=1)
+msg3=Message(iframe6, text=' ',width=300,relief=FLAT)
+msg3.pack(side=LEFT, fill=X, padx=1)
+##msg4=Message(iframe6, text=' ', width=300,relief=FLAT)
+##msg4.pack(side=LEFT, fill=X, padx=1)
+##balloon.configure(statuscommand=msg4)
+
+msg5=Message(iframe6, text=' ', width=300,relief=FLAT)
+msg6=Message(iframe6, text=' ', width=400,relief=SUNKEN)
+msg6.pack(side=RIGHT, fill=X, padx=1)
+msg5.pack(side=RIGHT, fill=X, padx=1)
+iframe6.pack(expand=1, fill=X, padx=4)
+frame.pack()
+
+isync=1
+iband.set(6)
+idle.set(1)
+ipctx.set(20)
+
+#---------------------------------------------------------- Process INI file
+try:
+ f=open(appdir+'/WSPR.INI',mode='r')
+ params=f.readlines()
+except:
+ params=""
+
+badlist=[]
+#----------------------------------------------------------- readinit
+def readinit():
+ global nparam,mrudir
+ try:
+ for i in range(len(params)):
+ if badlist.count(i)>0:
+ print('Skipping bad entry in WSPR.INI:\a',params[i])
+ continue
+ key,value=params[i].split()
+ if key == 'WSPRGeometry': root.geometry(value)
+ elif key == 'MyCall': options.MyCall.set(value)
+ elif key == 'MyGrid': options.MyGrid.set(value)
+ elif key == 'CWID': advanced.idint.set(value)
+ elif key == 'dBm': options.dBm.set(value)
+ elif key == 'PctTx': ipctx.set(value)
+ elif key == 'DGain': ndgain.set(value)
+ elif key == 'PttPort': options.PttPort.set(value)
+ elif key == 'CatPort': options.CatPort.set(value)
+ elif key == 'AudioIn':
+ value=value.replace("#"," ")
+ g.DevinName.set(value)
+ try:
+ g.ndevin.set(int(value[:2]))
+ except:
+ g.ndevin.set(0)
+ options.DevinName.set(value)
+
+
+ elif key == 'AudioOut':
+ value=value.replace("#"," ")
+ g.DevoutName.set(value)
+ try:
+ g.ndevout.set(int(value[:2]))
+ except:
+ g.ndevout.set(0)
+ options.DevoutName.set(value)
+
+ elif key == 'BFOfreq': advanced.bfofreq.set(value)
+ elif key == 'Acal': advanced.Acal.set(value)
+ elif key == 'Bcal': advanced.Bcal.set(value)
+ elif key == 'CalEnable': advanced.encal.set(value)
+ elif key == 'IQmode': iq.iqmode.set(value)
+ elif key == 'IQrx': iq.iqrx.set(value)
+ elif key == 'IQtx': iq.iqtx.set(value)
+ elif key == 'FIQ': iq.fiq.set(value)
+ elif key == 'Ntxphaf': iq.isc3a.set(value)
+ elif key == 'PTTmode': options.pttmode.set(value)
+ elif key == 'CATenable': options.cat_enable.set(value)
+ elif key == 'SerialRate': options.serial_rate.set(int(value))
+ elif key == 'DataBits': options.databits.set(int(value))
+ elif key == 'StopBits': options.stopbits.set(int(value))
+ elif key == 'Handshake': options.serial_handshake.set( \
+ value.replace("#"," ") )
+ elif key == 'Rig':
+ t=value.replace("#"," ")
+ options.rig.set(t)
+ options.rignum.set(int(t[:4]))
+ elif key == 'Nsave': nsave.set(value)
+ elif key == 'Upload': upload.set(value)
+ elif key == 'Idle': idle.set(value)
+ elif key == 'Debug': ndebug.set(value)
+ elif key == 'WatScale': sc1.set(value)
+ elif key == 'WatOffset': sc2.set(value)
+ elif key == 'Palette': g.cmap=value
+ elif key == 'freq0_600': freq0[1]=float(value)
+ elif key == 'freq0_160': freq0[2]=float(value)
+ elif key == 'freq0_80': freq0[3]=float(value)
+ elif key == 'freq0_60': freq0[4]=float(value)
+ elif key == 'freq0_40': freq0[5]=float(value)
+ elif key == 'freq0_30': freq0[6]=float(value)
+ elif key == 'freq0_20': freq0[7]=float(value)
+ elif key == 'freq0_17': freq0[8]=float(value)
+ elif key == 'freq0_15': freq0[9]=float(value)
+ elif key == 'freq0_12': freq0[10]=float(value)
+ elif key == 'freq0_10': freq0[11]=float(value)
+ elif key == 'freq0_6': freq0[12]=float(value)
+ elif key == 'freq0_4': freq0[13]=float(value)
+ elif key == 'freq0_2': freq0[14]=float(value)
+ elif key == 'freq0_other': freq0[15]=float(value)
+ elif key == 'freqtx_600': freqtx[1]=float(value)
+ elif key == 'freqtx_160': freqtx[2]=float(value)
+ elif key == 'freqtx_80': freqtx[3]=float(value)
+ elif key == 'freqtx_60': freqtx[4]=float(value)
+ elif key == 'freqtx_40': freqtx[5]=float(value)
+ elif key == 'freqtx_30': freqtx[6]=float(value)
+ elif key == 'freqtx_20': freqtx[7]=float(value)
+ elif key == 'freqtx_17': freqtx[8]=float(value)
+ elif key == 'freqtx_15': freqtx[9]=float(value)
+ elif key == 'freqtx_12': freqtx[10]=float(value)
+ elif key == 'freqtx_10': freqtx[11]=float(value)
+ elif key == 'freqtx_6': freqtx[12]=float(value)
+ elif key == 'freqtx_4': freqtx[13]=float(value)
+ elif key == 'freqtx_2': freqtx[14]=float(value)
+ elif key == 'freqtx_other': freqtx[15]=float(value)
+ elif key == 'iband': iband.set(value)
+ elif key == 'StartIdle': start_idle.set(value)
+ elif key == 'NoBeep': no_beep.set(value)
+ elif key == 'Reject': w.acom1.reject=float(value)
+ elif key == 'RxApply': iq.iqrxapp.set(value)
+
+ elif key == 'MRUdir':
+ mrudir=value.replace("#"," ")
+ nparam=i
+
+ except:
+ badlist.append(i)
+ nparam=i
+
+w.acom1.gain=1.0
+w.acom1.phase=0.0
+w.acom1.reject=0.
+while nparam < len(params)-1:
+ readinit()
+hopping.restore_params(appdir)
+iq.ib.set(iband.get())
+iq.restore()
+
+r=options.chkcall(options.MyCall.get())
+if r<0:
+ options.lcall._entryFieldEntry['background']='pink'
+ options1()
+else:
+ options.lcall._entryFieldEntry['background']='white'
+
+r=options.chkgrid(options.MyGrid.get())
+if r<0:
+ options.lgrid._entryFieldEntry['background']='pink'
+ options1()
+else:
+ options.lgrid._entryFieldEntry['background']='white'
+
+if g.DevinName.get()=="":
+ g.ndevin.set(-1)
+
+f0.set(freq0[iband.get()])
+ftx.set(freqtx[iband.get()])
+
+if start_idle.get():
+ idle.set(1)
+
+#------------------------------------------------------ Select palette
+if g.cmap == "gray0":
+ pal_gray0()
+ npal.set(0)
+if g.cmap == "gray1":
+ pal_gray1()
+ npal.set(1)
+if g.cmap == "Linrad":
+ pal_linrad()
+ npal.set(2)
+if g.cmap == "blue":
+ pal_blue()
+ npal.set(3)
+if g.cmap == "Hot":
+ pal_Hot()
+ npal.set(4)
+if g.cmap == "AFMHot":
+ pal_AFMHot()
+ npal.set(5)
+
+options.dbm_balloon()
+fmid=f0.get() + 0.001500
+sftx.set('%.06f' % ftx.get())
+draw_axis()
+erase()
+if g.Win32: root.iconbitmap("wsjt.ico")
+Title='WSPR ' + Version + ' by K1JT'
+root.title(Title)
+
+put_params()
+try:
+ os.remove('decoded.txt')
+except:
+ pass
+try:
+ os.remove('pixmap.dat')
+except:
+ pass
+
+##if hopping.hopping.get() and hopping.coord_bands.get() and not idle.get():
+## tsec=time.time()
+## utc=time.gmtime(tsec)
+## ns1=int(tsec) % 1200
+## b=ns1/120 + 3
+## if b==12: b=2
+## if hopping.hoppingflag[b].get():
+## iband.set(b)
+## Issue rigctl command here
+
+iband0=iband.get()
+graph1.focus_set()
+w.acom1.ndevsok=0
+w.acom1.ntxnext=0
+w.acom1.nstoptx=0
+w.wspr1()
+t="%.6f" % (f0.get(),)
+sf0.set(t)
+t="%.6f" % (ftx.get(),)
+sftx.set(t)
+font2=tkinter.font.Font(font=text['font'])
+lab2.config(font=font2)
+
+ldate.after(100,update)
+ldate.after(100,audio_config)
+
+##from WsprMod import specjt
+root.mainloop()
+
+ldate.after_cancel(timer1)
+
+# Clean up and save user options, then terminate.
+if options.pttmode.get()=='CAT':
+ if options.rignum.get()==2509 or options.rignum.get()==2511:
+ cmd="rigctl -m %d -r %s T 0" % \
+ (options.rignum.get(),options.CatPort.get())
+ else:
+ cmd="rigctl -m %d -r %s -s %d -C data_bits=%s -C stop_bits=%s -C serial_handshake=%s T 0" % \
+ (options.rignum.get(),options.CatPort.get(), \
+ options.serial_rate.get(),options.databits.get(), \
+ options.stopbits.get(),options.serial_handshake.get())
+ ierr=os.system(cmd)
+save_params()
+w.paterminate()
+time.sleep(0.5)
diff --git a/wspr.sh b/wspr.sh
new file mode 100644
index 0000000..3bde823
--- /dev/null
+++ b/wspr.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+#-------------------------------------------------------------------------------
+# This file is part of the WSPR application, Weak Signal Propagation Reporter
+#
+# File Name: wspr.sh
+# Description: Shell script wrapper to update or copy files from system install
+#
+# Copyright (C) 2001-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#-------------------------------------------------------------------------------
+
+set -e
+
+# set dir's
+_HOMEDIR="/home/$LOGNAME/.wspr"
+
+# make a few dir's
+mkdir -p $_HOMEDIR
+
+# update files only if newer
+cp -uR /usr/share/wspr/* $_HOMEDIR
+cp -uR /usr/share/doc/wspr/* $_HOMEDIR/
+cp -uR /usr/lib/wspr/* $_HOMEDIR
+
+# run: py location updated by configure.ac
+cd $_HOMEDIR
+/usr/bin/python3 -O wspr.py
diff --git a/wspr.xpm b/wspr.xpm
new file mode 100644
index 0000000..feccb85
--- /dev/null
+++ b/wspr.xpm
@@ -0,0 +1,41 @@
+/* XPM */
+static char *wsjt-0[] = {
+/* columns rows colors chars-per-pixel */
+"32 32 3 1",
+" c black",
+". c blue",
+"X c green",
+/* pixels */
+" ",
+" ..X..... ",
+" XXXX.....XX... ",
+" XXXXX....XX.X....X ",
+" XXXXXX....XX.X....XX ",
+" XXXXXXXX.X.XXXX....XXX ",
+" XXXXXXXXXXXXXXX......XXX ",
+" ..XXXXXXXXXXXXXX......XXXX ",
+" ..XXXXXXXXXXXXXX.......X.. ",
+" ...XXXXXXXXXXXXX...........X ",
+" ...XXXXXXXXXXXXX..........XX ",
+" ...XXXXXXX.XXXXX.........XXX ",
+" .....XXXXXX.XX.XX.........XXXX ",
+" ......XXX.......XX........XXXX ",
+" ......XXXX.......X.........XXX ",
+" .......XXX..................XX ",
+" .........XX..................X ",
+" ...........XXX...............X ",
+" ...........XXX................ ",
+" .............XX.XXX........... ",
+" ............XXXXXXXXXX...... ",
+" ............XXXXXXXXXXX..... ",
+" ............XXXXXXXXXXX..... ",
+" ...........XXXXXXXXXXX.... ",
+" .............XXXXXXX...... ",
+" ............XXXXXXX..... ",
+" .............XXXX..... ",
+" ............XXX..... ",
+" ...........XXX.... ",
+" ..........XX.. ",
+" ........ ",
+" "
+};
diff --git a/wspr0.f90 b/wspr0.f90
new file mode 100644
index 0000000..5e47c2a
--- /dev/null
+++ b/wspr0.f90
@@ -0,0 +1,86 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR0 application, Command-Line WSPR0
+!
+! File Name: wspr0.f90
+! Description: Command-line version of WSPR
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program wspr0
+
+! Command-line version of WSPR.
+
+ integer nt(9)
+ integer soundexit
+ real*8 f0,ftx,tsec
+ character*12 call12
+ character*6 grid6
+ character*80 outfile
+ character*11 utcdate
+ character*3 month(12)
+ data month/'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'/
+
+ call wspr0init(ntrminutes,nrxtx,nport,nfiles,multi,list,snrdb, &
+ pctx,f0,ftx,call12,grid6,ndbm,outfile)
+
+ ntr=0
+ nsec0=999999
+ open(14,file='ALL_WSPR0.TXT',status='unknown',access='append')
+ call soundinit
+
+ if(nrxtx.eq.1) then !Receive only
+ write(*,1026)
+1026 format(' UTC dB DT Freq Message'/54('-'))
+ write(14,1028)
+1028 format(' Date UTC Sync dB DT Freq Message'/50('-'))
+ call wspr0_rx(ntrminutes,nrxtx,nfiles,f0)
+
+ else if(nrxtx.eq.2) then !Transmit only
+ call wspr0_tx(ntrminutes,nport,nfiles,multi,list,snrdb,f0,ftx, &
+ call12,grid6,ndbm,outfile,ntr)
+ else if(nrxtx.eq.3) then !Tx and Rx, choosen randomly
+ call random_seed
+ ntr=1
+20 nsec=time()
+ call gmtime2(nt,tsec)
+ nsec=tsec
+ write(utcdate,1001) nt(4),month(nt(5)),nt(6)
+1001 format(i2,'-',a3,'-',i4)
+ nsec=mod(nsec,86400)
+ if(nsec.lt.nsec0) then
+ write(*,1026)
+ write(14,1028)
+ endif
+ nsec0=nsec
+
+ call random_number(x)
+ if(100.0*x.lt.pctx) then
+ call wspr0_tx(ntrminutes,nport,nfiles,multi,list,snrdb,f0,ftx, &
+ call12,grid6,ndbm,outfile,ntr)
+ else
+ call wspr0_rx(ntrminutes,f0,ntr)
+ endif
+ call msleep(100)
+ go to 20
+ endif
+
+ ierr=soundexit()
+
+end program wspr0
diff --git a/wspr0_rx.f90 b/wspr0_rx.f90
new file mode 100644
index 0000000..2815ae9
--- /dev/null
+++ b/wspr0_rx.f90
@@ -0,0 +1,95 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR0 application, Command-Line WSPR0
+!
+! File Name: wspr0_rx.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine wspr0_rx(ntrminutes,nrxtx,nfiles,f0)
+
+ parameter (NMAX=900*12000) !Max length of waveform
+ integer*2 iwave(NMAX) !Generated waveform
+ integer*1 i1
+ integer*1 hdr(44)
+ integer npr3(162)
+ integer soundin
+ real*8 f0
+ character*80 infile,appdir,thisfile
+ character*6 cfile6,cdate*8,utctime*10
+ equivalence(i1,i4)
+ data appdir/'.'/,nappdir/1/,minsync/1/,nbfo/1500/
+ data npr3/ &
+ 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, &
+ 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, &
+ 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, &
+ 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, &
+ 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, &
+ 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, &
+ 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, &
+ 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, &
+ 0,0/
+
+ data nsec0/999999/
+ save
+
+ nargs=iargc()
+ if(nrxtx.eq.1) ifile1=nargs-nfiles+1
+ npts=(60*ntrminutes-6)*12000
+ nz=60*ntrminutes*12000
+
+ if(nfiles.ge.1) then
+ do ifile=ifile1,nargs
+ call getarg(ifile,infile)
+ open(10,file=infile,access='stream',status='old')
+ read(10) hdr
+ read(10) (iwave(i),i=1,npts)
+ close(10)
+ cfile6=infile
+ i1=index(infile,'.')
+ if(i1.ge.2) then
+ i0=max(1,i1-4)
+ cfile6=infile(i0:i1-1)
+ endif
+ call getrms(iwave,npts,ave,rms)
+ call mept162(infile,appdir,nappdir,f0,1,iwave,nz,nbfo,ierr)
+ enddo
+ else
+20 nsec=time()
+ isec=mod(nsec,86400)
+! ih=isec/3600
+! im=(isec-ih*3600)/60
+! is=mod(isec,60)
+ is120=mod(isec,120)
+ if(is120.eq.0) then
+ call getutc(cdate,utctime,tsec)
+ thisfile=cdate(3:8)//'_'//utctime(1:4)//'.'//'wav'
+ ierr=soundin(-1,12000,iwave,114*12000,0)
+! npts=114*12000
+ call getrms(iwave,npts,ave,rms)
+ call mept162(thisfile,appdir,nappdir,f0,1,iwave,nz,nbfo,ierr)
+ if(nrxtx.ne.1) go to 999
+ endif
+ call msleep(100)
+ go to 20
+ endif
+
+999 return
+end subroutine wspr0_rx
diff --git a/wspr0_tx.f90 b/wspr0_tx.f90
new file mode 100644
index 0000000..d52e228
--- /dev/null
+++ b/wspr0_tx.f90
@@ -0,0 +1,101 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR0 application, Command-Line WSPR0
+!
+! File Name: wspr0_tx.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine wspr0_tx(ntrminutes,nport,nfiles,multi,list,snrdb,f0,ftx, &
+ call12,grid6,ndbm,outfile,ntr)
+
+! Read command-line arguments and generate Tx data for the MEPT_JT mode.
+
+ parameter (NMAX=900*12000)
+ real*8 f0,ftx
+ character*12 call12
+ character*6 grid6
+ character*3 cdbm
+ character*22 message
+ character*80 outfile
+ integer*2 iwave(NMAX)
+ integer ptt,soundout
+
+ ntxdf=nint(1.d6*(ftx-f0))-1500
+ txdf2=1.d6*(ftx-f0)-1612.5d0
+ if(multi.eq.0 .and. list.eq.0) then
+ if(ntrminutes.eq.2 .and. abs(ntxdf).gt.100) then
+ print*,'Error: ftx must be above f0 by 1400 to 1600 Hz'
+ stop
+ else if(ntrminutes.eq.15 .and. abs(txdf2).gt.12.5) then
+ print*,'Error: ftx must be above f0 by 1600 to 1625 Hz'
+ stop
+ endif
+ endif
+
+ i1=index(call12,' ')
+ write(cdbm,'(i3)'),ndbm
+ if(cdbm(1:1).eq.' ') cdbm=cdbm(2:)
+ if(cdbm(1:1).eq.' ') cdbm=cdbm(2:)
+
+! Should allow for compound callsign and/or 6-digit locator
+ message=call12(1:i1)//grid6(1:4)//' '//cdbm
+
+ do ifile=1,nfiles
+ if(nfiles.gt.1 .or. outfile(1:1).eq.' ') write(outfile,1010) ifile
+1010 format(i5.5,'.wav')
+ call genmept(message,ntxdf,ntrminutes,multi,list,snrdb,iwave)
+ if(list.ne.0) go to 999
+ if(outfile.ne."") then
+ nz=60*ntrminutes*12000
+ call wfile5(iwave,nz,12000,outfile)
+ write(*,1020) f0,ftx,snrdb,message,outfile(1:24)
+1020 format(2f11.6,f6.1,2x,a22,2x,a24)
+ else
+20 nsec=time()
+ isec=mod(nsec,86400)
+ ih=isec/3600
+ im=(isec-ih*3600)/60
+! is=mod(isec,60)
+ is120=mod(isec,120)
+ if(is120.eq.0) then
+ if(nport.gt.0) ierr=ptt(nport,junk,1,iptt)
+! if(ntr.eq.0) write(*,1030) ih,im,is,f0,ftx,message
+!1030 format(i2.2,':',i2.2,':',i2.2,2f11.6,2x,a22)
+ do i=22,1,-1
+ if(message(i:i).ne.' ') go to 25
+ enddo
+25 iz=i
+ write(*,1031) ih,im,ftx,message(1:iz)
+1031 format(2i2.2,9x,f11.6,' Transmitting "',a,'"')
+ write(14,1032) ih,im,ftx,message(1:iz)
+1032 format(7x,2i2.2,13x,f11.6,' Transmitting "',a,'"')
+ ierr=soundout(-1,12000,iwave,114*12000,0)
+ if(nport.gt.0) ierr=ptt(nport,junk,0,iptt)
+ if(ntr.ne.0) go to 999
+ endif
+ call msleep(100)
+ go to 20
+ endif
+ if(nfiles.eq.9999) go to 999
+ enddo
+
+999 return
+end subroutine wspr0_tx
diff --git a/wspr0init.f90 b/wspr0init.f90
new file mode 100644
index 0000000..f9cffaa
--- /dev/null
+++ b/wspr0init.f90
@@ -0,0 +1,171 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR0 application, Command-Line WSPR0
+!
+! File Name: wspr0init.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine wspr0init(ntrminutes,nrxtx,nport,nfiles,multi,list,snrdb, &
+ pctx,f0,ftx,call12,grid6,ndbm,outfile)
+
+ real*8 f0,ftx,txaudio
+ character*12 arg
+ character*12 call12
+ character*6 grid6
+ character*80 outfile
+
+ nargs=iargc()
+ if(nargs.eq.0) then
+ print*,' '
+ print*,'wspr0 -- version 4.0'
+ print*,' '
+ print*,'Usage: wspr0 [options...] [files...]'
+ print*,' '
+ print*,'Options:'
+ print*,' '
+ print*,'Transmit/Receive status:'
+ print*,' -t Run in 100% Tx mode. (Default is Rx mode.)'
+ print*,' -b Pseudo-random selection of Rx and Tx cycles.'
+ print*,' -D Open and decode one or more wav files.'
+ print*,' '
+ print*,'Transmitted message: by default, the callsign, grid locator,'
+ print*,'and power level for the transmitted message are taken from'
+ print*,'file wspr0.def. These may be overridden by using the options'
+ print*,' -c call'
+ print*,' -g grid'
+ print*,' -d dBm'
+ print*,' '
+ print*,'Frequencies:'
+ print*,' -f x Transceiver dial frequency is x (MHz)'
+ print*,' -F x Center frequency of transmission is x (MHz)'
+ print*,' -a x Audio frequency of transmission is x (Hz)'
+ print*,' '
+ print*,' -m Run in WSPR-15 mode (default is WSPR-2)'
+ print*,' -n n Number of files to be generated'
+ print*,' -o outfile Output filename, overrides default nnnnnn.'
+ print*,' -p n PTT port'
+ print*,' -P n Transmitting percent (default=25)'
+ print*,' -s x SNR of generated data, dB (default 100)'
+ print*,' -x Generate test file(s) with 10 signals in each'
+ print*,' -X Generate list of audio tones for this message'
+ print*,' '
+ print*,'Examples:'
+ print*,' wspr0 -t #Transmit default message'
+ print*,' wspr0 -t -s -22 -o test.wav #Generate a test file'
+ print*,' wspr0 -t -s -25 -n 3 #Generate three test files'
+ print*,' wspr0 -b #Randomized T/R sequences'
+ print*,' wspr0 -f 14.0956 #Rx only, on 20m'
+ print*,' wspr0 -D 00001.wav 00002.wav #Decode these two files'
+ print*,' '
+ print*,'For more information see:'
+ print*,' physics.princeton.edu/pulsar/K1JT/WSPR0_Instructions.TXT'
+ stop
+ endif
+
+ nrxtx=1
+ ntrminutes=2
+ nfiles=9999
+ nport=2
+ snrdb=100.
+ call12='K1JT'
+ grid6='FN20qi'
+ ndbm=37
+ pctx=25.
+ outfile=" "
+ f0=10.138700d0
+ ftx=10.140200d0
+ txaudio=0.d0
+ mfiles=0
+ k=0
+ multi=0
+ list=0
+
+ do n=1,99
+ k=k+1
+ call getarg(k,arg)
+ if(arg(1:2).eq.'-m') then
+ ntrminutes=15
+ else if(arg(1:2).eq.'-D') then
+ nrxtx=1
+ else if(arg(1:2).eq.'-t') then
+ nrxtx=2
+ else if(arg(1:2).eq.'-b') then
+ nrxtx=3
+ else if(arg(1:2).eq.'-c') then
+ k=k+1
+ call getarg(k,call12)
+ else if(arg(1:2).eq.'-g') then
+ k=k+1
+ call getarg(k,grid6)
+ else if(arg(1:2).eq.'-d') then
+ k=k+1
+ call getarg(k,arg)
+ read(arg,*) ndbm
+ else if(arg(1:2).eq.'-f') then
+ k=k+1
+ call getarg(k,arg)
+ read(arg,*) f0
+ else if(arg(1:2).eq.'-F') then
+ k=k+1
+ call getarg(k,arg)
+ read(arg,*) ftx
+ else if(arg(1:2).eq.'-a') then
+ k=k+1
+ call getarg(k,arg)
+ read(arg,*) txaudio
+ else if(arg(1:2).eq.'-n') then
+ k=k+1
+ call getarg(k,arg)
+ read(arg,*) nfiles
+ else if(arg(1:2).eq.'-s') then
+ k=k+1
+ call getarg(k,arg)
+ read(arg,*) snrdb
+ else if(arg(1:2).eq.'-p') then
+ k=k+1
+ call getarg(k,arg)
+ read(arg,*) nport
+ else if(arg(1:2).eq.'-P') then
+ k=k+1
+ call getarg(k,arg)
+ read(arg,*) pctx
+ pctx=min(max(pctx,0.0),100.0)
+ else if(arg(1:2).eq.'-o') then
+ k=k+1
+ call getarg(k,outfile)
+ else if(arg(1:2).eq.'-x') then
+ multi=1
+ snrdb=0.
+ else if(arg(1:2).eq.'-X') then
+ list=1
+ else
+ mfiles=mfiles+1
+ endif
+ if(k.ge.nargs) exit
+ enddo
+
+ if(outfile(1:1).ne.' ') nfiles=1
+ if(nrxtx.eq.1) nfiles=mfiles
+ if(txaudio.ne.0.d0) ftx=f0 + 1.d-6*txaudio
+
+ return
+end subroutine wspr0init
+
diff --git a/wspr1.f90 b/wspr1.f90
new file mode 100644
index 0000000..a4d6ac6
--- /dev/null
+++ b/wspr1.f90
@@ -0,0 +1,40 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: wspr1.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine wspr1
+
+ integer th_wspr2
+
+ include 'acom1.f90'
+
+! Start a thread for acquiring audio data
+ ierr=th_wspr2()
+ if(ierr.ne.0) then
+ print*,'Error creating thread for wspr2',ierr
+ stop
+ endif
+
+ return
+end subroutine wspr1
diff --git a/wspr2.f90 b/wspr2.f90
new file mode 100644
index 0000000..7e52b2e
--- /dev/null
+++ b/wspr2.f90
@@ -0,0 +1,258 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: wspr2.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine wspr2
+
+! Logical units:
+! 12 Audio data in *.wav file
+! 13 ALL_WSPR.TXT
+! 14 decoded.txt
+! 16 pixmap.dat
+! 17 audio_caps
+! 18 test.snr
+! 19 wspr.log
+
+ character message*24,cdbm*4
+ real*8 tsec,tsec1,trseconds
+ include 'acom1.f90'
+ include 'acom2.f90'
+ character dectxt*80,logfile*80
+ integer nt(9)
+ integer iclock(12)
+ integer ib(15)
+ common/patience/npatience
+ data nrxnormal/0/,ireset/1/
+ data ib/630,160,80,60,40,30,20,17,15,12,10,6,4,2,630/
+ save ireset
+
+ receiving=.false.
+ transmitting=.false.
+ ntrminutes=2
+ call cs_init
+ dectxt=appdir(:nappdir)//'/decoded.txt'
+
+ call cs_lock('wspr2')
+ open(14,file=dectxt,status='unknown')
+ write(14,1002)
+1002 format('$EOF')
+ call flush(14)
+ rewind 14
+ logfile=appdir(:nappdir)//'/wspr.log'
+ open(19,file=logfile,status='unknown',position='append')
+ call cs_unlock
+
+ npatience=1
+ call system_clock(iclock(1))
+ call random_seed(PUT=iclock)
+ nrx=1
+ nfhopping=0 ! hopping scheduling disabled
+ nfhopok=0 ! not a good time to hop
+
+10 call cs_lock('wspr2')
+ trseconds=60.d0*ntrminutes
+ call getutc(cdate,utctime,tsec)
+ nsec=tsec
+ nsectr=mod(nsec,60*ntrminutes)
+ rxavg=1.0
+ if(pctx.gt.0.0) rxavg=100.0/pctx - 1.0
+ call cs_unlock
+! if(transmitting .and. nstoptx.eq.1) then
+! call killtx
+! nstoptx=0
+! transmitting=.false.
+! go to 20
+! endif
+
+ if(nrxdone.gt.0) then
+
+ call cs_lock('wspr2')
+ receiving=.false.
+ nrxdone=0
+ thisfile=cdate(3:8)//'_'//rxtime(1:4)//'.'//'wav' !Tnx to G3WKW !
+ if(ndiskdat.ne.0) thisfile=outfile
+ call cs_unlock
+
+ if((nrxnormal.eq.1 .and. ncal.eq.0) .or. &
+ (nrxnormal.eq.0 .and. ncal.eq.2) .or. ndiskdat.eq.1) then
+ call cs_lock('wspr2')
+ call gmtime2(nt,tsec1)
+ sectr=mod(tsec1,trseconds)
+ write(19,1031) cdate(3:8),utctime(1:4),sectr,'Dec ',iband,ib(iband)
+1031 format(a6,1x,a4,f7.2,2x,a4,2i4,2x,a22)
+ call flush(19)
+ call cs_unlock
+ if(ndecoding.eq.0) then
+ ndecoding=1
+ call startdec
+ else
+ print*,'Attempted to start decode thread when already running.'
+ endif
+ endif
+ endif
+
+ call cs_lock('wspr2')
+ if(ntxdone.gt.0) then
+ transmitting=.false.
+ ntxdone=0
+ ntr=0
+ endif
+ nsecdone=60*ntrminutes - 6 !### Less for WSPR-15 ?
+ if(nsectr.ge.nsecdone .and. ntune.eq.0) then
+ transmitting=.false.
+ receiving=.false.
+ ntr=0
+ endif
+ if(pctx.lt.1.0) ntune=0
+ call cs_unlock
+
+ if (ntune.ne.0 .and. ndevsok.eq.1.and. (.not.transmitting) .and. &
+ (.not.receiving) .and. pctx.ge.1.0) then
+
+! Test transmission of length pctx seconds.
+ call cs_lock('wspr2')
+ nsectx=mod(nsec,86400)
+ ntune2=ntune
+ transmitting=.true.
+ call gmtime2(nt,tsec1)
+ sectr=mod(tsec1,trseconds)
+ if(ntune.eq.-3 .and. sectr.lt.116.5) then
+ write(19,1031) cdate(3:8),utctime(1:4),sectr,'ATU ',iband,ib(iband)
+ else
+ write(19,1031) cdate(3:8),utctime(1:4),sectr,'Tune',iband,ib(iband)
+ endif
+ call flush(19)
+ call cs_unlock
+ call starttx
+ endif
+
+ if (ncal.eq.1 .and. ndevsok.eq.1.and. (.not.transmitting) .and. &
+ (.not.receiving)) then
+
+! Execute one receive sequence
+ call cs_lock('wspr2')
+ receiving=.true.
+ rxtime=utctime(1:4)
+ nrxnormal=0
+ call gmtime2(nt,tsec1)
+ sectr=mod(tsec1,trseconds)
+ write(19,1031) cdate(3:8),utctime(1:4),sectr,'Cal ',iband,ib(iband)
+ call flush(19)
+ call cs_unlock
+ ndiskdat=0
+ call startrx
+ endif
+
+ if(nsectr.eq.0 .and. (.not.transmitting) .and. (.not.receiving) .and. &
+ (idle.eq.0)) go to 30
+ if(receiving) then
+ call chklevel(kwave,ntrminutes,iqmode+1,NZ/2,nsec1,xdb1,xdb2,iwrite)
+ if(iqmode.eq.1 .and. iqrxadj.eq.1) then
+ call speciq(kwave,NZ/2,iwrite,iqrx,nfiq,ireset,gain,phase,reject)
+ else
+ ireset=1
+ endif
+ endif
+
+ call msleep(200)
+ go to 10
+
+30 outfile=cdate(3:8)//'_'//utctime(1:4)//'.'//'wav'
+
+! Frequency hopping scheduling; overrides normal scheduling
+ if (nfhopping.eq.1) then
+ if (pctx.eq.0.0) then
+ nrx=1
+ else
+ if(ncoord.eq.0) then
+ call random_number(x)
+ if (100*x .lt. pctx) then
+ ntxnext=1
+ else
+ nrx=1
+ endif
+ else
+ call rxtxcoord(nsec,pctx,nrx,ntxnext)
+ endif
+ endif
+ else
+ if(pctx.eq.0.0) nrx=1
+ endif
+
+ if(transmitting .or. receiving) go to 10
+
+ if(pctx.gt.0.0 .and. (ntxnext.eq.1 .or. (nrx.eq.0 .and. ntr.ne.-1))) then
+
+ call cs_lock('wspr2')
+ ntune2=ntune
+ transmitting=.true.
+ call random_number(x)
+ if(pctx.lt.50.0) then
+ nrx=nint(rxavg + 3.0*(x-0.5))
+ else
+ nrx=0
+ if(x.lt.rxavg) nrx=1
+ endif
+ write(cdbm,'(i4)') ndbm
+ message=callsign//grid//cdbm
+ call msgtrim(message,msglen)
+ write(linetx,1030) cdate(3:8),utctime(1:4),ftx
+1030 format(a6,1x,a4,f11.6,2x,'Transmitting on ')
+ ntr=-1
+ nsectx=mod(nsec,86400)
+ ntxdone=0
+ ntxnext=0
+ call cs_unlock
+
+ if(ndevsok.eq.1) then
+ call cs_lock('wspr2')
+ call gmtime2(nt,tsec0)
+ sectr=mod(tsec0,trseconds)
+ write(19,1031) cdate(3:8),utctime(1:4),sectr,'Tx ',iband,ib(iband), &
+ message
+ call flush(19)
+ call cs_unlock
+ call starttx
+ endif
+
+ else
+ receiving=.true.
+ rxtime=utctime(1:4)
+ ntr=1
+ if(ndevsok.eq.1) then
+ nrxnormal=1
+ call cs_lock('wspr2')
+ call gmtime2(nt,tsec1)
+ sectr=mod(tsec1,trseconds)
+ write(19,1031) cdate(3:8),utctime(1:4),sectr,'Rx ',iband,ib(iband)
+ call flush(19)
+ call cs_unlock
+ call startrx
+ endif
+ nrx=nrx-1
+ endif
+ go to 10
+
+ return
+end subroutine wspr2
diff --git a/wspr_nogui.py b/wspr_nogui.py
new file mode 100644
index 0000000..84615e3
--- /dev/null
+++ b/wspr_nogui.py
@@ -0,0 +1,1936 @@
+#------------------------------------------------------------------ WSPR
+# $Date: 2008-03-17 08:29:04 -0400 (Mon, 17 Mar 2008) $ $Revision: 2326 $
+#
+#
+# This file is part of the WSPR_NoGui.py application
+#
+# File Name: wspr_nogui.py
+# Source: http://sourceforge.net/projects/wsjt/
+# Contributors: 4X6IZ, K1JT
+#
+# Description: This is a version of wspr.py with all the GUI calls stripped out
+#
+# Copyright (C) 2008-2014 Joseph Taylor, K1JT
+# License: GPL-3
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#------------------------------------------------------------------------------
+
+#from Tkinter import *
+#from tkFileDialog import *
+#import tkMessageBox
+from tkrep import *
+import os,time,sys
+#from WsprMod import g,Pmw
+from WsprMod import palettes
+from WsprModNoGui import g
+#from WsprModNoGui import tkrep
+from math import log10
+from numpy.oldnumeric import zeros
+import array
+#import Image, ImageTk, ImageDraw
+#import Image, ImageDraw
+from PIL import Image, ImageDraw
+from WsprMod.palettes import colormapblue, colormapgray0, colormapHot, \
+ colormapAFMHot, colormapgray1, colormapLinrad, Colormap2Palette
+from types import *
+import array
+import random
+import math
+import string
+from WsprMod import w
+# from WsprMod import smeter
+import socket
+import urllib.request, urllib.parse, urllib.error
+import _thread
+import webbrowser
+
+# root = Tk()
+Version="4.00_r" + "$Rev: 4171 $"[6:-2]
+print("******************************************************************")
+print("WSPR Version " + Version + ", by K1JT")
+print("Run date: " + time.asctime(time.gmtime()) + " UTC")
+
+#See if we are running in Windows
+print('setting g.Win32')
+g.Win32=0
+if sys.platform=="win32":
+ g.Win32=1
+ try:
+ root.option_readfile('wsprrc.win')
+ except:
+ pass
+else:
+ try:
+ root.option_readfile('wsprrc')
+ except:
+ pass
+root_geom=""
+appdir=os.getcwd()
+w.acom1.nappdir=len(appdir)
+w.acom1.appdir=(appdir+(' '*80))[:80]
+i1,i2=w.audiodev(0,2)
+from WsprModNoGui import options
+from WsprModNoGui import advanced
+from WsprModNoGui import iq
+from WsprModNoGui import hopping
+
+#------------------------------------------------------ Global variables
+band=[-1,600,160,80,60,40,30,20,17,15,12,10,6,4,2,0]
+bandmap=[]
+bm={}
+f0=DoubleVar()
+ftx=DoubleVar()
+ftx0=0.
+ft=[]
+fileopened=""
+fmid=0.0
+fmid0=0.0
+font1='Helvetica'
+iband=IntVar()
+iband0=0
+idle=IntVar()
+ierr=0
+ipctx=IntVar()
+isec0=0
+isync=1
+itx0=0
+loopall=0
+modpixmap0=0
+mrudir=os.getcwd()
+ndbm0=-999
+ncall=0
+ndebug=IntVar()
+nin0=0
+nout0=0
+newdat=1
+newspec=1
+no_beep=IntVar()
+npal=IntVar()
+npal.set(2)
+nparam=0
+nsave=IntVar()
+nscroll=0
+nsec0=0
+nspeed0=IntVar()
+ntr0=0
+ntxfirst=IntVar()
+NX=500
+NY=160
+param20=""
+sf0=StringVar()
+sftx=StringVar()
+start_idle=IntVar()
+t0=""
+timer1=0
+txmsg=StringVar()
+nreject=0
+gain=1.0
+phdeg=0.0
+
+a=array.array('h')
+im=Image.new('P',(NX,NY))
+draw=ImageDraw.Draw(im)
+im.putpalette(Colormap2Palette(colormapLinrad),"RGB")
+#pim=ImageTk.PhotoImage(im)
+receiving=0
+scale0=1.0
+offset0=0.0
+s0=0.0
+c0=0.0
+slabel="MinSync "
+transmitting=0
+tw=[]
+fw=[] # band labels for spectrum display
+upload=IntVar()
+#balloon=Pmw.Balloon(root)
+
+g.appdir=appdir
+g.cmap="Linrad"
+g.cmap0="Linrad"
+g.ndevin=IntVar()
+g.ndevout=IntVar()
+g.DevinName=StringVar()
+g.DevoutName=StringVar()
+
+pwrlist=(-30,-27,-23,-20,-17,-13,-10,-7,-3, \
+ 0,3,7,10,13,17,20,23,27,30,33,37,40,43,47,50,53,57,60)
+freq0=[0,0.5024,1.8366,3.5926,5.2872,7.0386,10.1387,14.0956,18.1046,\
+ 21.0946,24.9246,28.1246,50.2930,70.0286,144.4890,0.1360]
+freqtx=[0,0.5024,1.8366,3.5926,5.2872,7.0386,10.1387,14.0956,18.1046,\
+ 21.0946,24.9246,28.1246,50.2930,70.0301,144.4890,0.1375]
+
+for i in range(15):
+ freqtx[i]=freq0[i]+0.001500
+
+socktimeout = 10
+socket.setdefaulttimeout(socktimeout)
+
+def pal_gray0():
+ g.cmap="gray0"
+ im.putpalette(Colormap2Palette(colormapgray0),"RGB")
+def pal_gray1():
+ g.cmap="gray1"
+ im.putpalette(Colormap2Palette(colormapgray1),"RGB")
+def pal_linrad():
+ g.cmap="Linrad"
+ im.putpalette(Colormap2Palette(colormapLinrad),"RGB")
+def pal_blue():
+ g.cmap="blue"
+ im.putpalette(Colormap2Palette(colormapblue),"RGB")
+def pal_Hot():
+ g.cmap="Hot"
+ im.putpalette(Colormap2Palette(colormapHot),"RGB")
+def pal_AFMHot():
+ g.cmap="AFMHot"
+ im.putpalette(Colormap2Palette(colormapAFMHot),"RGB")
+
+#------------------------------------------------------ quit
+def quit(event=NONE):
+ root.destroy()
+
+#------------------------------------------------------ openfile
+def openfile(event=NONE):
+ global mrudir,fileopened,nopen,tw
+ nopen=1 #Work-around for "click feedthrough" bug
+ upload.set(0)
+ try:
+ os.chdir(mrudir)
+ except:
+ pass
+ fname=askopenfilename(filetypes=[("Wave files","*.wav *.WAV")])
+ if fname:
+ w.getfile(fname,len(fname))
+ mrudir=os.path.dirname(fname)
+ fileopened=os.path.basename(fname)
+ i1=fileopened.find('.')
+ t=fileopened[i1-4:i1]
+ t=t[0:2] + ':' + t[2:4]
+ n=len(tw)
+ if n>12: tw=tw[:n-1]
+ tw=[t,] + tw
+ os.chdir(appdir)
+ idle.set(1)
+
+#------------------------------------------------------ stop_loopall
+def stop_loopall(event=NONE):
+ global loopall
+ loopall=0
+
+#------------------------------------------------------ opennext
+def opennext(event=NONE):
+ global ncall,fileopened,loopall,mrudir,tw
+ upload.set(0)
+ if fileopened=="" and ncall==0:
+ openfile()
+ ncall=1
+ else:
+# Make a list of *.wav files in mrudir
+ la=os.listdir(mrudir)
+ la.sort()
+ lb=[]
+ for i in range(len(la)):
+ j=la[i].find(".wav") + la[i].find(".WAV")
+ if j>0: lb.append(la[i])
+ for i in range(len(lb)):
+ if lb[i]==fileopened:
+ break
+ if i<len(lb)-1:
+ fname=mrudir+"/"+lb[i+1]
+ w.getfile(fname,len(fname))
+ mrudir=os.path.dirname(fname)
+ fileopened=os.path.basename(fname)
+ i1=fileopened.find('.')
+ t=fileopened[i1-4:i1]
+ t=t[0:2] + ':' + t[2:4]
+ n=len(tw)
+ if n>12: tw=tw[:n-1]
+ tw=[t,] + tw
+ else:
+ t="No more *.wav files in this directory."
+ result=tkMessageBox.showwarning(message=t)
+ ncall=0
+ loopall=0
+
+#------------------------------------------------------ decodeall
+def decodeall(event=NONE):
+ global loopall
+ loopall=1
+ opennext()
+
+#------------------------------------------------------ hopping1
+def hopping1(event=NONE):
+ t=''
+ if root_geom.find('+')>=0:
+ t=root_geom[root_geom.index('+'):]
+ hopping.hopping2(t)
+
+#------------------------------------------------------ options1
+def options1(event=NONE):
+ t=''
+ if root_geom.find('+')>=0:
+ t=root_geom[root_geom.index('+'):]
+ options.options2(t)
+
+#------------------------------------------------------ advanced1
+def advanced1(event=NONE):
+ t=""
+ if root_geom.find("+")>=0:
+ t=root_geom[root_geom.index("+"):]
+ advanced.advanced2(t)
+
+#------------------------------------------------------ iq1
+def iq1(event=NONE):
+ t=""
+ if root_geom.find("+")>=0:
+ t=root_geom[root_geom.index("+"):]
+ iq.iq2(t)
+
+#------------------------------------------------------ stub
+def stub(event=NONE):
+ MsgBox("Sorry, this function is not yet implemented.")
+
+#------------------------------------------------------ MsgBox
+def MsgBox(t):
+ result=tkMessageBox.showwarning(message=t)
+
+#------------------------------------------------------ msgpos
+def msgpos():
+ g=root_geom[root_geom.index("+"):]
+ t=g[1:]
+ x=int(t[:t.index("+")]) # + 70
+ y=int(t[t.index("+")+1:]) # + 70
+ return "+%d+%d" % (x,y)
+
+#------------------------------------------------------ about
+def about(event=NONE):
+ global Version
+ about=Toplevel(root)
+ about.geometry(msgpos())
+ if g.Win32: about.iconbitmap("wsjt.ico")
+ t="WSPR Version " + Version + ", by K1JT"
+ Label(about,text=t,font=(font1,16)).pack(padx=20,pady=5)
+ t="""
+WSPR (pronounced "whisper") stands for "Weak Signal
+Propagation Reporter". The program generates and decodes
+a digital soundcard mode optimized for beacon-like
+transmissions on the LF, MF, and HF bands.
+
+Copyright (c) 2008-2014 by Joseph H. Taylor, Jr., K1JT, with
+contributions from VA3DB, G4KLA, W1BW, 4X6IZ, KE6HDU and KI7MT.
+WSPR is Open Source software, licensed under the GNU General Public
+License (GPL-3). Source code and programming information may
+be found at http://sourceforge.net/projects/wsjt/.
+"""
+ Label(about,text=t,justify=LEFT).pack(padx=20)
+ t="Revision date: " + \
+ "$Date: 2010-09-17 13:03:38 -0400 (Fri, 17 Sep 2010) $"[7:-1]
+ Label(about,text=t,justify=LEFT).pack(padx=20)
+ about.focus_set()
+
+#------------------------------------------------------
+def help(event=NONE):
+ about=Toplevel(root)
+ about.geometry(msgpos())
+ if g.Win32: about.iconbitmap("wsjt.ico")
+ t="Basic Operating Instructions"
+ Label(about,text=t,font=(font1,14)).pack(padx=20,pady=5)
+ t="""
+1. Open the Setup | Station Parameters screen and enter
+ your callsign and grid locator 6 characters). Select
+ desired devices for Audio In and Audio Out, and your
+ power level in dBm.
+
+2. Select your PTT method (CAT control, DTR, or RTS). If
+ you choose DTR or RTS, select a PTT port. If T/R
+ switching or frequency setting will be done by CAT
+ control, select a CAT port and be sure that "Enable CAT"
+ is checked. You will need to enter a Rig number and
+ correct parameters for the serial connection.
+
+3. Select the desired band from the Band menu and if
+ necessary correct your USB dial frequency on the main
+ screen. Select a Tx frequency by double-clicking
+ somewhere on the waterfall display.
+
+4. Select a desired 'Tx fraction' using the large slider. Zero
+ percent means Rx only; 100% means Tx only.
+
+5. Be sure that your computer clock is correct to +/- 1 s.
+ Many people like to use an automatic internet-based
+ clock-setting utility.
+
+6. WSPR will begin a Tx or Rx sequence at the start of each
+ even-numbered minute. The waterfall will update and
+ decoding will take place at the end of each Rx sequence.
+ During reception, you can adjust the Rx noise level to get
+ something close to 0 dB. Use the operating system's audio
+ mixer control or change your receiver's output level.
+"""
+ Label(about,text=t,justify=LEFT).pack(padx=20)
+ about.focus_set()
+
+#------------------------------------------------------ usersguide
+def usersguide(event=NONE):
+ url='http://physics.princeton.edu/pulsar/K1JT/WSPR_3.0_User.pdf'
+ _thread.start_new_thread(browser,(url,))
+
+#------------------------------------------------------ fmtguide
+def fmtguide(event=NONE):
+ url='http://physics.princeton.edu/pulsar/K1JT/FMT_User.pdf'
+ _thread.start_new_thread(browser,(url,))
+
+#------------------------------------------------------ wsprnet
+def wsprnet(event=NONE):
+ url='http://wsprnet.org/'
+ _thread.start_new_thread(browser,(url,))
+
+#------------------------------------------------------ homepage
+def homepage(event=NONE):
+ url='http://physics.princeton.edu/pulsar/K1JT/'
+ _thread.start_new_thread(browser,(url,))
+
+#------------------------------------------------------- browser
+def browser(url):
+ webbrowser.open(url)
+
+#------------------------------------------------------ erase
+def erase(event=NONE):
+ global bandmap,bm
+ text.configure(state=NORMAL)
+ text.delete('1.0',END)
+ text.configure(state=DISABLED)
+ text1.configure(state=NORMAL)
+ text1.delete('1.0',END)
+ text1.configure(state=DISABLED)
+ bandmap=[]
+ bm={}
+
+#------------------------------------------------------ tune
+def tune(event=NONE):
+ idle.set(1)
+ w.acom1.ntune=1
+ btune.configure(bg='yellow')
+# balloon.configure(state='none')
+
+#------------------------------------------------------ txnext
+def txnext(event=NONE):
+ if ipctx.get()>0:
+ w.acom1.ntxnext=1
+ btxnext.configure(bg="green")
+
+###------------------------------------------------------ stoptx
+##def stoptx(event=NONE):
+## w.acom1.nstoptx=1
+## w.acom1.ntxnext=0
+
+#----------------------------------------------------- df_readout
+# Readout of graphical cursor location
+def df_readout(event):
+ global fmid
+ nhz=1000000*fmid + (80.0-event.y) * 12000/8192.0
+ nhz=int(nhz%1000)
+ t="%3d Hz" % nhz
+ lab02.configure(text=t,bg='red')
+
+#----------------------------------------------------- set_tx_freq
+def set_tx_freq(event):
+ global fmid
+ nftx=int(1000000.0*fmid + (80.0-event.y) * 12000/8192.0)
+ fmhz=0.000001*nftx
+ t="Please confirm setting Tx frequency to " + "%.06f MHz" % fmhz
+ result=tkMessageBox.askyesno(message=t)
+ if result:
+ ftx.set(0.000001*nftx)
+ sftx.set('%.06f' % ftx.get())
+
+#-------------------------------------------------------- draw_axis
+def draw_axis():
+ global fmid
+ c.delete(ALL)
+ df=12000.0/8192.0
+ nfmid=int(1.0e6*fmid + 0.5)%1000
+# Draw and label tick marks
+ for iy in range(-120,120,10):
+ j=80 - iy/df
+ i1=7
+ if (iy%50)==0:
+ i1=12
+ if (iy%100)==0: i1=15
+ n=nfmid+iy
+ if n<0: n=n+1000
+ c.create_text(27,j,text=str(n))
+ c.create_line(0,j,i1,j,fill='black')
+ iy=1000000.0*(ftx.get()-f0.get()) - 1500
+ if abs(iy)<=100:
+ j=80 - iy/df
+ c.create_line(0,j,13,j,fill='red',width=3)
+
+#------------------------------------------------------ del_all
+def del_all():
+ fname=appdir+'/ALL_WSPR.TXT'
+ try:
+ os.remove(fname)
+ except:
+ pass
+
+#------------------------------------------------------ delwav
+def delwav():
+ t="Are you sure you want to delete\nall *.WAV files in the Save directory?"
+ result=tkMessageBox.askyesno(message=t)
+ if result:
+# Make a list of *.wav files in Save
+ la=os.listdir(appdir+'/save')
+ lb=[]
+ for i in range(len(la)):
+ j=la[i].find(".wav") + la[i].find(".WAV")
+ if j>0: lb.append(la[i])
+# Now delete them all.
+ for i in range(len(lb)):
+ fname=appdir+'/save/'+lb[i]
+ os.remove(fname)
+
+#--------------------------------------------------- rx_volume
+def rx_volume():
+ for path in string.split(os.environ["PATH"], os.pathsep):
+ file = os.path.join(path, "sndvol32") + ".exe"
+ try:
+ return os.spawnv(os.P_NOWAIT, file, (file,) + (" -r",))
+ except os.error:
+ pass
+ raise os.error("Cannot find "+file)
+
+#--------------------------------------------------- tx_volume
+def tx_volume():
+ for path in string.split(os.environ["PATH"], os.pathsep):
+ file = os.path.join(path, "sndvol32") + ".exe"
+ try:
+ return os.spawnv(os.P_NOWAIT, file, (file,))
+ except os.error:
+ pass
+ raise os.error("Cannot find "+file)
+
+#------------------------------------------------------ get_decoded
+def get_decoded():
+ global bandmap,bm,newdat,loopall
+
+# Get lines from decoded.txt and parse each into an associative array
+ try:
+ f=open(appdir+'/decoded.txt',mode='r')
+ decodes = []
+ for line in f:
+ fields = line.split()
+ if len(fields) < 10: continue
+ msg = fields[6:-3]
+ d = {}
+ d['date'] = fields[0]
+ d['time'] = fields[1]
+ d['sync'] = fields[2]
+ d['snr'] = fields[3]
+ d['dt'] = fields[4]
+ d['freq'] = fields[5]
+ d['msg'] = msg
+ d['drift'] = fields[-3]
+ d['cycles'] = fields[-2]
+ d['ii'] = fields[-1]
+
+# Determine message type
+ d['type1'] = True
+ d['type2'] = False
+ d['type3'] = False
+ if len(msg) != 3 or len(msg[1]) != 4 or len(msg[0]) < 3 or \
+ len(msg[0]) > 6 or not msg[2].isdigit():
+ d['type1'] = False
+ else:
+ dbm = int(msg[2])
+ if dbm < 0 or dbm > 60:
+ d['type1'] = False
+ n=dbm%10
+ if n!=0 and n!=3 and n!=7:
+ d['type1'] = False
+ if not d['type1']:
+ if len(msg)==2:
+ d['type2']=True
+ else:
+ d['type3']=True
+# Get callsign
+ callsign = d['msg'][0]
+ if callsign[0]=='<':
+ n=callsign.find('>')
+ callsign=callsign[1:n]
+ d['call'] = callsign
+ decodes.append(d)
+ f.close()
+ except:
+ decodes = []
+
+ if len(decodes) > 0:
+# Write data to text box; append freqs and calls to bandmap.
+ #text.configure(state=NORMAL)
+ nseq=0
+ nfmid=int(1.0e6*fmid)%1000
+ for d in decodes:
+ #text.insert(END, "%4s %3s %4s %10s %2s %s\n" % \
+ # (d['time'],d['snr'],d['dt'],d['freq'],d['drift'],' '.join(d['msg'])))
+ print("%4s %3s %4s %10s %2s %s\n" % \
+ (d['time'],d['snr'],d['dt'],d['freq'],d['drift'],' '.join(d['msg'])))
+ try:
+ callsign=d['call']
+ tmin=60*int(d['time'][0:2]) + int(d['time'][2:4])
+ ndf=int(d['freq'][-3:])
+ bandmap.append((ndf,callsign,tmin))
+ except:
+ pass
+ #text.configure(state=DISABLED)
+ #text.see(END)
+
+# Erase the bm{} dictionary, then repopulate it from "bandmap".
+# Most recent info for each callsign should be saved.
+ bm={}
+ iz=len(bandmap)
+ for i in range(iz):
+ bm[bandmap[i][1]]=(bandmap[i][0],bandmap[i][2])
+
+# Erase bandmap entirely
+ bandmap=[]
+# Repopulate "bandmap" from "bm", which should not contain dupes.
+ for callsign,ft in bm.items():
+ if callsign!='...':
+ ndf,tdecoded=ft
+ tmin=int((time.time()%86400)/60)
+ tdiff=tmin-tdecoded
+ if tdiff<0: tdiff=tdiff+1440
+# Insert info in "bandmap" only if age is less than one hour
+ if w.acom1.ndiskdat==1: tdiff=2
+ if tdiff < 60: #60 minutes
+ bandmap.append((ndf,callsign,tdecoded))
+
+# Once more, erase the bm{} dictionary, then repopulate it from "bandmap"
+ bm={}
+ iz=len(bandmap)
+ for i in range(iz):
+ bm[bandmap[i][1]]=(bandmap[i][0],bandmap[i][2])
+
+# Sort bandmap in reverse frequency order, then display it
+ bandmap.sort()
+ bandmap.reverse()
+ #text1.configure(state=NORMAL)
+ #text1.delete('1.0',END)
+ for i in range(iz):
+ t="%4d" % (bandmap[i][0],) + " " + bandmap[i][1]
+ nage=int((tmin - bandmap[i][2])/15)
+ if nage<0: nage=nage+96
+ attr='age0'
+ if nage==1: attr='age1'
+ if nage==2: attr='age2'
+ if nage>=3: attr='age3'
+ if w.acom1.ndiskdat==1: attr='age0'
+ #text1.insert(END,t+"\n",attr)
+ #text1.configure(state=DISABLED)
+ #text1.see(END)
+
+ if upload.get():
+ #Dispatch autologger thread.
+ _thread.start_new_thread(autolog, (decodes,))
+
+ if loopall: opennext()
+
+#------------------------------------------------------ autologger
+def autolog(decodes):
+ # Random delay of up to 20 seconds to spread load out on server --W1BW
+ time.sleep(random.random() * 20.0)
+ try:
+ # This code originally by W6CQZ ... modified by W1BW
+ # TODO: Cache entries for later uploading if net is down.
+ # TODO: (Maybe??) Allow for stations wishing to collect spot data but
+ # only upload in batch form vs real-time.
+ # Any spots to upload?
+ if len(decodes) > 0:
+ for d in decodes:
+ # now to format as a string to use for autologger upload using urlencode
+ # so we get a string formatted for http get/put operations:
+ m=d['msg']
+ tcall=m[0]
+ if d['type2']:
+ tgrid=''
+ dbm=m[1]
+ else:
+ tgrid=m[1]
+ dbm=m[2]
+ if tcall[0]=='<':
+ n=tcall.find('>')
+ tcall=tcall[1:n]
+ if tcall=='...': continue
+ dfreq=float(d['freq'])-w.acom1.f0b-0.001500
+ if abs(dfreq)>0.0001:
+ print('Frequency changed, no upload of spots')
+ continue
+ reportparams = urllib.parse.urlencode({'function': 'wspr',
+ 'rcall': options.MyCall.get(),
+ 'rgrid': options.MyGrid.get(),
+ 'rqrg': str(f0.get()),
+ 'date': d['date'],
+ 'time': d['time'],
+ 'sig': d['snr'],
+ 'dt': d['dt'],
+ 'tqrg': d['freq'],
+ 'drift': d['drift'],
+ 'tcall': tcall,
+ 'tgrid': tgrid,
+ 'dbm': dbm,
+ 'version': Version})
+ # reportparams now contains a properly formed http request string for
+ # the agreed upon format between W6CQZ and N8FQ.
+ # any other data collection point can be added as desired if it conforms
+ # to the 'standard format' defined above.
+ # The following opens a url and passes the reception report to the database
+ # insertion handler for W6CQZ:
+ # urlf = urllib.urlopen("http://jt65.w6cqz.org/rbc.php?%s" % reportparams)
+ # The following opens a url and passes the reception report to the
+ # database insertion handler from W1BW:
+ urlf = urllib.request.urlopen("http://wsprnet.org/post?%s" \
+ % reportparams)
+ reply = urlf.readlines()
+ urlf.close()
+ else:
+ # No spots to report, so upload status message instead. --W1BW
+ reportparams = urllib.parse.urlencode({'function': 'wsprstat',
+ 'rcall': options.MyCall.get(),
+ 'rgrid': options.MyGrid.get(),
+ 'rqrg': str(fmid),
+ 'tpct': str(ipctx.get()),
+ 'tqrg': sftx.get(),
+ 'dbm': str(options.dBm.get()),
+ 'version': Version})
+ urlf = urllib.request.urlopen("http://wsprnet.org/post?%s" \
+ % reportparams)
+ reply = urlf.readlines()
+ urlf.close()
+ except:
+ t=" UTC: attempted access to WSPRnet failed."
+ if not no_beep.get(): t=t + "\a"
+ print(time.asctime(time.gmtime()) + t)
+
+#------------------------------------------------------ put_params
+def put_params(param3=NONE):
+ global param20
+
+## try:
+## w.acom1.f0=f0.get()
+## w.acom1.ftx=ftx.get()
+## except:
+## pass
+ w.acom1.callsign=(options.MyCall.get().strip().upper()+' ')[:12]
+ w.acom1.grid=(options.MyGrid.get().strip().upper()+' ')[:4]
+ w.acom1.grid6=(options.MyGrid.get().strip().upper()+' ')[:6]
+ w.acom1.ctxmsg=(txmsg.get().strip().upper()+' ')[:22]
+
+ # numeric port ==> COM%d, else string of device. --W1BW
+ port = options.PttPort.get()
+ if port=='None': port='0'
+ if port[:3]=='COM': port=port[3:]
+ if port.isdigit():
+ w.acom1.nport = int(port)
+ port = "COM%d" % (int(port))
+ else:
+ w.acom1.nport = 0
+ w.acom1.pttport = (port + 80*' ')[:80]
+
+ try:
+ dbm=int(options.dBm.get())
+ except:
+ dbm=37
+ i1=options.MyCall.get().find('/')
+ if dbm<0 and (i1>0 or advanced.igrid6.get()):
+ MsgBox("Negative dBm values are permitted\n only for Type 1 messages.")
+ dbm=0
+ options.dBm.set(0)
+ mindiff=9999
+ for i in range(len(pwrlist)):
+ if abs(dbm-pwrlist[i])<mindiff:
+ mindiff=abs(dbm-pwrlist[i])
+ ibest=i
+ dbm=pwrlist[ibest]
+ options.dBm.set(dbm)
+ w.acom1.ndbm=dbm
+
+ w.acom1.ntxfirst=ntxfirst.get()
+ w.acom1.nsave=nsave.get()
+ try:
+ w.acom1.nbfo=advanced.bfofreq.get()
+ except:
+ w.acom1.nbfo=1500
+ try:
+ w.acom1.idint=advanced.idint.get()
+ except:
+ w.acom1.idint=0
+ w.acom1.igrid6=advanced.igrid6.get()
+ w.acom1.iqmode=iq.iqmode.get()
+ w.acom1.iqrx=iq.iqrx.get()
+ w.acom1.iqrxapp=iq.iqrxapp.get()
+ w.acom1.iqrxadj=iq.iqrxadj.get()
+ w.acom1.iqtx=iq.iqtx.get()
+ w.acom1.ntxdb=advanced.isc1.get()
+ bal=iq.isc2.get() + 0.02*iq.isc2a.get()
+ w.acom1.txbal=bal
+ pha=iq.isc3.get() + 0.02*iq.isc3a.get()
+ w.acom1.txpha=pha
+ try:
+ w.acom1.nfiq=iq.fiq.get()
+ except:
+ w.acom1.nfiq=0
+ w.acom1.ndevin=g.ndevin.get()
+ w.acom1.ndevout=g.ndevout.get()
+ w.acom1.nbaud=options.serial_rate.get()
+ w.acom1.ndatabits=options.databits.get()
+ w.acom1.nstopbits=options.stopbits.get()
+ w.acom1.chs=(options.serial_handshake.get() + \
+ ' ')[:40]
+ w.acom1.catport=(options.CatPort.get()+' ')[:12]
+ try:
+ w.acom1.nrig=options.rignum.get()
+ except:
+ pass
+
+#------------------------------------------------------ update
+def update():
+ global root_geom,isec0,im,pim,ndbm0,nsec0,a,ftx0,nin0,nout0, \
+ receiving,transmitting,newdat,nscroll,newspec,scale0,offset0, \
+ modpixmap0,tw,s0,c0,fmid,fmid0,loopall,ntr0,txmsg,iband0, \
+ bandmap,bm,t0,nreject,gain,phdeg,ierr,itx0,timer1
+
+ tsec=time.time()
+ utc=time.gmtime(tsec)
+ nsec=int(tsec)
+ nsec0=nsec
+ ns120=nsec % 120
+
+ if hopping.hoppingconfigured.get()==1:
+ bhopping.configure(state=NORMAL)
+ else:
+ bhopping.configure(state=DISABLED)
+
+ hopped=0
+ if not idle.get():
+ if hopping.hopping.get()==1:
+ w.acom1.nfhopping=1
+
+ if w.acom1.nfhopok:
+ w.acom1.nfhopok=0
+ b=-1
+ if hopping.coord_bands.get()==1:
+ ns=nsec % 86400
+ ns1=ns % (10*120)
+ b=ns1/120 + 3
+ if b==12: b=2
+ if hopping.hoppingflag[b].get()==0: b=-1
+ if b<0:
+ found=False
+ while not found:
+ b = random.randint(1,len(hopping.bandlabels)-1)
+ if hopping.hoppingflag[b].get()!=0:
+ found=True
+ ipctx.set(hopping.hoppingpctx[b].get())
+ if b!=iband.get(): hopped=1
+ iband.set(b)
+
+ else:
+ w.acom1.nfhopping=0
+ ns=nsec % 86400
+ ns1=ns % (10*120)
+ b=ns1/120 + 3
+ if b==12: b=2
+ if iband.get()==b and random.randint(1,2)==1 and ipctx.get()>0:
+ w.acom1.ntxnext=1
+
+ try:
+ f0.set(float(sf0.get()))
+ ftx.set(float(sftx.get()))
+ except:
+ pass
+ isec=utc[5]
+ twait=120.0 - (tsec % 120.0)
+
+ if iband.get()!=iband0 or advanced.fset.get():
+ advanced.fset.set(0)
+ f0.set(freq0[iband.get()])
+ t="%.6f" % (f0.get(),)
+ sf0.set(t)
+ ftx.set(freqtx[iband.get()])
+ t="%.6f" % (ftx.get(),)
+ sftx.set(t)
+ if options.cat_enable.get():
+ if advanced.encal.get():
+ nHz=int(advanced.Acal.get() + \
+ f0.get()*(1000000.0 + advanced.Bcal.get()) + 0.5)
+ else:
+ nHz=int(1000000.0*f0.get() + 0.5)
+ if options.rignum.get()==2509 or options.rignum.get()==2511:
+ nHzLO=nHz - iq.fiq.get()
+ cmd="rigctl -m %d -r %s F %d" % \
+ (options.rignum.get(),options.CatPort.get(),nHzLO)
+ else:
+ cmd="rigctl -m %d -r %s -s %d -C data_bits=%s -C stop_bits=%s -C serial_handshake=%s F %d" % \
+ (options.rignum.get(),options.CatPort.get(), \
+ options.serial_rate.get(),options.databits.get(), \
+ options.stopbits.get(),options.serial_handshake.get(), nHz)
+ ierr=os.system(cmd)
+ if ierr==0:
+ ierr2=0
+ bandmap=[]
+ bm={}
+ text1.configure(state=NORMAL)
+ text1.delete('1.0',END)
+ text1.configure(state=DISABLED)
+ iband0=iband.get()
+ f=open(appdir+'/fmt.ini',mode='w')
+ f.write(cmd+'\n')
+ f.write(str(g.ndevin.get())+'\n')
+ f.close()
+
+ cmd2=''
+ if os.path.exists('.\\user_hardware.bat') or \
+ os.path.exists('.\\user_hardware.cmd') or \
+ os.path.exists('.\\user_hardware.exe'):
+ cmd2='.\\user_hardware ' + str(band[iband0])
+ elif os.path.exists('./user_hardware'):
+ cmd2='./user_hardware ' + str(band[iband0])
+ if cmd2!='':
+ try:
+ ierr2=os.system(cmd2)
+ except:
+ ierr2=-1
+ if ierr2!=0:
+ print('Execution of "'+cmd2+'" failed.')
+ MsgBox('Execution of "'+cmd2+'" failed.\nEntering Idle mode.')
+ else:
+ print('Error attempting to set rig frequency.\a')
+ print(cmd + '\a')
+ iband.set(iband0)
+ f0.set(freq0[iband.get()])
+ t="%.6f" % (f0.get(),)
+ sf0.set(t)
+ ftx.set(freqtx[iband.get()])
+ t="%.6f" % (ftx.get(),)
+ sftx.set(t)
+## if ierr==0 and ierr2==0 and w.acom1.nfhopping==1 and hopped==1:
+ if ierr==0 and ierr2==0 and w.acom1.nfhopping==1 and hopped==1 \
+ and hopping.tuneupflag[iband.get()].get(): w.acom1.ntune=-3
+ else:
+ iband0=iband.get()
+ iq.ib.set(iband.get())
+ iq.newband()
+
+ freq0[iband.get()]=f0.get()
+ freqtx[iband.get()]=ftx.get()
+ w.acom1.iband=iband.get()
+ try:
+ w.acom1.f0=f0.get()
+ w.acom1.ftx=ftx.get()
+ except:
+ pass
+
+ if isec != isec0: #Do once per second
+ isec0=isec
+ t=time.strftime('%Y %b %d\n%H:%M:%S',utc)
+ ldate.configure(text=t)
+ root_geom=root.geometry()
+ utchours=utc[3]+utc[4]/60.0 + utc[5]/3600.0
+ try:
+ if options.dBm.get()!=ndbm0:
+ ndbm0=options.dBm.get()
+ options.dbm_balloon()
+ except:
+ pass
+ put_params()
+ nndf=int(1000000.0*(ftx.get()-f0.get()) + 0.5) - 1500
+ gain=w.acom1.gain
+ phdeg=57.2957795*w.acom1.phase
+ nreject=int(w.acom1.reject)
+ t='Bal: %6.4f Pha: %6.1f >%3d dB' % (gain,phdeg,nreject)
+ iq.lab1.configure(text=t)
+ ndb=int(w.acom1.xdb1-41.0)
+ if ndb<-30: ndb=-30
+ dbave=w.acom1.xdb1
+ if iq.iqmode.get():
+ ndb2=int(w.acom1.xdb2-41.0)
+ if ndb2<-30: ndb2=-30
+ dbave=0.5*(w.acom1.xdb1 + w.acom1.xdb2)
+ t='Rx Noise: %3d %3d dB' % (ndb,ndb2)
+ else:
+ t='Rx Noise: %3d dB' % (ndb,)
+ bg='gray85'
+ r=SUNKEN
+ smcolor="green"
+ if w.acom1.receiving:
+ if ndb>10 and ndb<=20:
+ bg='yellow'
+ smcolor='yellow'
+ elif ndb<-20 or ndb>20:
+ bg='red'
+ smcolor='red'
+ else:
+ t=''
+ r=FLAT
+ msg1.configure(text=t,bg=bg,relief=r)
+ if not receiving: dbave=0
+ sm.updateProgress(newValue=dbave,newColor=smcolor)
+
+# If T/R status has changed, get new info
+ ntr=int(w.acom1.ntr)
+ itx=w.acom1.transmitting
+ if ntr!=ntr0 or itx!=itx0:
+ ntr0=ntr
+ itx0=int(itx)
+ if ntr==-1 or itx==1:
+ transmitting=1
+ receiving=0
+ elif ntr==0:
+ transmitting=0
+ receiving=0
+ else:
+ transmitting=0
+ receiving=1
+ n=len(tw)
+ if n>12: tw=tw[:n-1]
+ rxtime=w.acom1.rxtime.tostring().decode('utf-8')
+ rxtime=rxtime[:2] + ':' + rxtime[2:]
+ tw=[rxtime,] + tw
+
+ global fw
+ if n>12: fw=fw[:n-1]
+ fw=[hopping.bandlabels[ iband.get()][:-2],] + fw
+ if receiving:
+ filemenu.entryconfig(0,state=DISABLED)
+ filemenu.entryconfig(1,state=DISABLED)
+ filemenu.entryconfig(2,state=DISABLED)
+ else:
+ filemenu.entryconfig(0,state=NORMAL)
+ filemenu.entryconfig(1,state=NORMAL)
+ filemenu.entryconfig(2,state=NORMAL)
+ if transmitting:
+ btxnext.configure(bg="gray85")
+ for i in range(15):
+ bandmenu.entryconfig(i,state=DISABLED)
+ else:
+ for i in range(15):
+ bandmenu.entryconfig(i,state=NORMAL)
+
+ bgcolor='gray85'
+ t='Waiting to start'
+ bgcolor='pink'
+ if transmitting:
+ t='Txing: ' + w.acom1.sending.tostring().decode('utf-8')
+ bgcolor='yellow'
+ if receiving:
+ t='Receiving'
+ bgcolor='green'
+ if t!=t0:
+ msg6.configure(text=t,bg=bgcolor)
+ t0=t
+ if w.acom1.ntune==0:
+ btune.configure(bg='gray85')
+ pctscale.configure(state=NORMAL)
+ else:
+ pctscale.configure(state=DISABLED)
+ if w.acom1.ncal==0:
+ advanced.bmeas.configure(bg='gray85')
+ else:
+ idle.set(1)
+ if ierr==0:
+ w.acom1.pctx=ipctx.get()
+ else:
+ w.acom1.pctx=0
+ w.acom1.idle=idle.get()
+ if idle.get()==0:
+ bidle.configure(bg='gray85')
+ else:
+ bidle.configure(bg='yellow')
+ if w.acom1.transmitting or w.acom1.receiving or options.outbad.get():
+ btune.configure(state=DISABLED)
+ else:
+ btune.configure(state=NORMAL)
+ if w.acom1.transmitting or w.acom1.receiving or twait < 6.0:
+ advanced.bmeas.configure(state=DISABLED)
+ else:
+ advanced.bmeas.configure(state=NORMAL)
+
+ if upload.get()==1:
+ bupload.configure(bg='gray85')
+ else:
+ bupload.configure(bg='yellow')
+
+# If new decoded text has appeared, display it.
+ if w.acom1.ndecdone:
+ get_decoded()
+ w.acom1.ndecdone=0
+ w.acom1.ndiskdat=0
+
+# Display the waterfall
+ try:
+ modpixmap=os.stat('pixmap.dat')[8]
+ if modpixmap!=modpixmap0:
+ f=open('pixmap.dat','rb')
+ a=array.array('h')
+ a.fromfile(f,NX*NY)
+ f.close()
+ newdat=1
+ modpixmap0=modpixmap
+ except:
+ newdat=0
+ scale=math.pow(10.0,0.003*sc1.get())
+ offset=0.3*sc2.get()
+ if newdat or scale!= scale0 or offset!=offset0 or g.cmap!=g.cmap0:
+ im.putdata(a,scale,offset) #Compute whole new image
+ if newdat:
+ n=len(tw)
+ for i in range(n-1,-1,-1):
+ x=465-39*i
+ draw.text((x,148),tw[i],fill=253) #Insert time label
+ if i<len(fw):
+ draw.text((x+10,1),fw[i],fill=253) #Insert band label
+
+ pim=ImageTk.PhotoImage(im) #Convert Image to PhotoImage
+ graph1.delete(ALL)
+ graph1.create_image(0,0+2,anchor='nw',image=pim)
+ g.ndecphase=2
+ newMinute=0
+ scale0=scale
+ offset0=offset
+ g.cmap0=g.cmap
+ newdat=0
+
+ s0=sc1.get()
+ c0=sc2.get()
+ try:
+ fmid=f0.get() + 0.001500
+ except:
+ pass
+
+ if fmid!=fmid0 or ftx.get()!=ftx0:
+ draw_axis()
+ lftx.configure(validate={'validator':'real',
+ 'min':f0.get()+0.001500-0.000100,'minstrict':0,
+ 'max':f0.get()+0.001500+0.000100,'maxstrict':0})
+ w.acom1.ndebug=ndebug.get()
+
+ if options.rignum.get()==2509 or options.rignum.get()==2511:
+ options.pttmode.set('CAT')
+ options.CatPort.set('USB')
+ if options.pttmode.get()=='CAT':
+ options.cat_enable.set(1)
+ if options.pttmode.get()=='CAT' or options.pttmode.get()=='VOX':
+ options.PttPort.set('None')
+ options.ptt_port._entryWidget['state']=DISABLED
+ else:
+ options.ptt_port._entryWidget['state']=NORMAL
+ if options.cat_enable.get():
+ options.lrignum._entryWidget['state']=NORMAL
+ if options.cat_port.get() != 'USB':
+ options.cat_port._entryWidget['state']=NORMAL
+ options.cbbaud._entryWidget['state']=NORMAL
+ options.cbdata._entryWidget['state']=NORMAL
+ options.cbstop._entryWidget['state']=NORMAL
+ options.cbhs._entryWidget['state']=NORMAL
+ else:
+ options.cat_port._entryWidget['state']=DISABLED
+ options.cbbaud._entryWidget['state']=DISABLED
+ options.cbdata._entryWidget['state']=DISABLED
+ options.cbstop._entryWidget['state']=DISABLED
+ options.cbhs._entryWidget['state']=DISABLED
+ advanced.bsetfreq.configure(state=NORMAL)
+ advanced.breadab.configure(state=NORMAL)
+ advanced.enable_cal.configure(state=NORMAL)
+ else:
+ options.cat_port._entryWidget['state']=DISABLED
+ options.lrignum._entryWidget['state']=DISABLED
+ options.cbbaud._entryWidget['state']=DISABLED
+ options.cbdata._entryWidget['state']=DISABLED
+ options.cbstop._entryWidget['state']=DISABLED
+ options.cbhs._entryWidget['state']=DISABLED
+ advanced.bsetfreq.configure(state=DISABLED)
+ advanced.breadab.configure(state=DISABLED)
+ advanced.enable_cal.configure(state=DISABLED)
+ advanced.encal.set(0)
+ w.acom1.pttmode=(options.pttmode.get().strip()+' ')[:3]
+ w.acom1.ncat=options.cat_enable.get()
+ w.acom1.ncoord=hopping.coord_bands.get()
+
+ if g.ndevin.get()!= nin0 or g.ndevout.get()!=nout0:
+ audio_config()
+ nin0=g.ndevin.get()
+ nout0=g.ndevout.get()
+ if options.inbad.get()==0:
+ msg2.configure(text='',bg='gray85')
+ else:
+ msg2.configure(text='Invalid audio input device.',bg='red')
+ if options.outbad.get()==0:
+ msg3.configure(text='',bg='gray85')
+ else:
+ msg3.configure(text='Invalid audio output device.',bg='red')
+ if w.acom1.ndecoding:
+ msg5.configure(text='Decoding',bg='#66FFFF',relief=SUNKEN)
+ else:
+ msg5.configure(text='',bg='gray85',relief=FLAT)
+
+ if advanced.encal.get():
+ advanced.A_entry.configure(entry_state=NORMAL,label_state=NORMAL)
+ advanced.B_entry.configure(entry_state=NORMAL,label_state=NORMAL)
+ else:
+ advanced.A_entry.configure(entry_state=DISABLED,label_state=DISABLED)
+ advanced.B_entry.configure(entry_state=DISABLED,label_state=DISABLED)
+
+ timer1=ldate.after(200,update)
+
+#------------------------------------------------------ update
+def update_nogui():
+ global root_geom,isec0,im,pim,ndbm0,nsec0,a,ftx0,nin0,nout0, \
+ receiving,transmitting,newdat,nscroll,newspec,scale0,offset0, \
+ modpixmap0,tw,s0,c0,fmid,fmid0,loopall,ntr0,txmsg,iband0, \
+ bandmap,bm,t0,nreject,gain,phdeg,ierr,itx0,timer1
+ while True:
+
+ #if hopping.hoppingconfigured.get()==1:
+ # bhopping.configure(state=NORMAL)
+ #else:
+ # bhopping.configure(state=DISABLED)
+
+ tsec=time.time()
+ utc=time.gmtime(tsec)
+ nsec=int(tsec)
+ nsec0=nsec
+ ns120=nsec % 120
+
+ hopped=0
+ if not idle.get():
+ if hopping.hopping.get()==1:
+ w.acom1.nfhopping=1
+
+ if w.acom1.nfhopok:
+ w.acom1.nfhopok=0
+ b=-1
+ if hopping.coord_bands.get()==1:
+ ns=nsec % 86400
+ ns1=ns % (10*120)
+ b=int(ns1/120) + 3
+ if b==12: b=2
+ if hopping.hoppingflag[b].get()==0: b=-1
+ if b<0:
+ found=False
+ while not found:
+ b = random.randint(1,len(hopping.bandlabels)-1)
+ if hopping.hoppingflag[b].get()!=0:
+ found=True
+ ipctx.set(hopping.hoppingpctx[b].get())
+ if b!=iband.get(): hopped=1
+ iband.set(b)
+
+ else:
+ w.acom1.nfhopping=0
+ ns=nsec % 86400
+ ns1=ns % (10*120)
+ b=ns1/120 + 3
+ if b==12: b=2
+ if iband.get()==b and random.randint(1,2)==1 and ipctx.get()>0:
+ w.acom1.ntxnext=1
+
+ try:
+ f0.set(float(sf0.get()))
+ ftx.set(float(sftx.get()))
+ except:
+ pass
+ isec=utc[5]
+ twait=120.0 - (tsec % 120.0)
+
+ if iband.get()!=iband0 or advanced.fset.get():
+ advanced.fset.set(0)
+ f0.set(freq0[iband.get()])
+ t="%.6f" % (f0.get(),)
+ sf0.set(t)
+ ftx.set(freqtx[iband.get()])
+ t="%.6f" % (ftx.get(),)
+ sftx.set(t)
+ if options.cat_enable.get():
+ if advanced.encal.get():
+ nHz=int(advanced.Acal.get() + \
+ f0.get()*(1000000.0 + advanced.Bcal.get()) + 0.5)
+ else:
+ nHz=int(1000000.0*f0.get() + 0.5)
+ if options.rignum.get()==2509 or options.rignum.get()==2511:
+ nHzLO=nHz - iq.fiq.get()
+ cmd="rigctl -m %d -r %s F %d" % \
+ (options.rignum.get(),options.CatPort.get(),nHzLO)
+ else:
+ cmd="rigctl -m %d -r %s -s %d -C data_bits=%s -C stop_bits=%s -C serial_handshake=%s F %d" % \
+ (options.rignum.get(),options.CatPort.get(), \
+ options.serial_rate.get(),options.databits.get(), \
+ options.stopbits.get(),options.serial_handshake.get(), nHz)
+ ierr=os.system(cmd)
+ if ierr==0:
+ ierr2=0
+ bandmap=[]
+ bm={}
+ #text1.configure(state=NORMAL)
+ #text1.delete('1.0',END)
+ #text1.configure(state=DISABLED)
+ iband0=iband.get()
+ f=open(appdir+'/fmt.ini',mode='w')
+ f.write(cmd+'\n')
+ f.write(str(g.ndevin.get())+'\n')
+ f.close()
+
+ cmd2=''
+ if os.path.exists('.\\user_hardware.bat') or \
+ os.path.exists('.\\user_hardware.cmd') or \
+ os.path.exists('.\\user_hardware.exe'):
+ cmd2='.\\user_hardware ' + str(band[iband0])
+ elif os.path.exists('./user_hardware'):
+ cmd2='./user_hardware ' + str(band[iband0])
+ if cmd2!='':
+ try:
+ ierr2=os.system(cmd2)
+ except:
+ ierr2=-1
+ if ierr2!=0:
+ print('Execution of "'+cmd2+'" failed.')
+ MsgBox('Execution of "'+cmd2+'" failed.\nEntering Idle mode.')
+ else:
+ print('Error attempting to set rig frequency.\a')
+ print(cmd + '\a')
+ iband.set(iband0)
+ f0.set(freq0[iband.get()])
+ t="%.6f" % (f0.get(),)
+ sf0.set(t)
+ ftx.set(freqtx[iband.get()])
+ t="%.6f" % (ftx.get(),)
+ sftx.set(t)
+## if ierr==0 and ierr2==0 and w.acom1.nfhopping==1 and hopped==1:
+ if ierr==0 and ierr2==0 and w.acom1.nfhopping==1 and hopped==1 \
+ and hopping.tuneupflag[iband.get()].get(): w.acom1.ntune=-3
+ else:
+ iband0=iband.get()
+ iq.ib.set(iband.get())
+ iq.newband()
+
+ freq0[iband.get()]=f0.get()
+ freqtx[iband.get()]=ftx.get()
+ w.acom1.iband=iband.get()
+ try:
+ w.acom1.f0=f0.get()
+ w.acom1.ftx=ftx.get()
+ except:
+ pass
+
+ if isec != isec0: #Do once per second
+ isec0=isec
+ t=time.strftime('%Y %b %d\n%H:%M:%S',utc)
+ #ldate.configure(text=t)
+# print(t)
+ #root_geom=root.geometry()
+ utchours=utc[3]+utc[4]/60.0 + utc[5]/3600.0
+ try:
+ if options.dBm.get()!=ndbm0:
+ ndbm0=options.dBm.get()
+ options.dbm_balloon()
+ except:
+ pass
+ put_params()
+ nndf=int(1000000.0*(ftx.get()-f0.get()) + 0.5) - 1500
+ gain=w.acom1.gain
+ phdeg=57.2957795*w.acom1.phase
+ nreject=int(w.acom1.reject)
+ t='Bal: %6.4f Pha: %6.1f >%3d dB' % (gain,phdeg,nreject)
+ #iq.lab1.configure(text=t)
+ #print t
+ ndb=int(w.acom1.xdb1-41.0)
+ if ndb<-30: ndb=-30
+ dbave=w.acom1.xdb1
+ if iq.iqmode.get():
+ ndb2=int(w.acom1.xdb2-41.0)
+ if ndb2<-30: ndb2=-30
+ dbave=0.5*(w.acom1.xdb1 + w.acom1.xdb2)
+ t='Rx Noise: %3d %3d dB' % (ndb,ndb2)
+ else:
+ t='Rx Noise: %3d dB' % (ndb,)
+ bg='gray85'
+ #r=SUNKEN
+ #smcolor="green"
+ #if w.acom1.receiving:
+ # if ndb>10 and ndb<=20:
+ # bg='yellow'
+ # smcolor='yellow'
+ # bg='red'
+ # smcolor='red'
+ #else:
+ # t=''
+ # r=FLAT
+ #msg1.configure(text=t,bg=bg,relief=r)
+# print(t)
+ if not receiving: dbave=0
+ #sm.updateProgress(newValue=dbave,newColor=smcolor)
+
+# If T/R status has changed, get new info
+ ntr=int(w.acom1.ntr)
+ itx=w.acom1.transmitting
+ if ntr!=ntr0 or itx!=itx0:
+ ntr0=ntr
+ itx0=int(itx)
+ if ntr==-1 or itx==1:
+ transmitting=1
+ receiving=0
+ elif ntr==0:
+ transmitting=0
+ receiving=0
+ else:
+ transmitting=0
+ receiving=1
+ n=len(tw)
+ if n>12: tw=tw[:n-1]
+ rxtime=w.acom1.rxtime.tostring().decode('utf-8')
+ rxtime=rxtime[:2] + ':' + rxtime[2:]
+ tw=[rxtime,] + tw
+
+ global fw
+ if n>12: fw=fw[:n-1]
+ fw=[hopping.bandlabels[ iband.get()][:-2],] + fw
+ #if receiving:
+ #filemenu.entryconfig(0,state=DISABLED)
+ #filemenu.entryconfig(1,state=DISABLED)
+ #filemenu.entryconfig(2,state=DISABLED)
+ #else:
+ #filemenu.entryconfig(0,state=NORMAL)
+ #filemenu.entryconfig(1,state=NORMAL)
+ #filemenu.entryconfig(2,state=NORMAL)
+ #if transmitting:
+ #btxnext.configure(bg="gray85")
+ #for i in range(15):
+ # bandmenu.entryconfig(i,state=DISABLED)
+ #else:
+ #for i in range(15):
+ # bandmenu.entryconfig(i,state=NORMAL)
+
+ bgcolor='gray85'
+ t='Waiting to start'
+ bgcolor='pink'
+ if transmitting:
+ t='Txing: ' + w.acom1.sending.tostring().decode('utf-8')
+ bgcolor='yellow'
+ if receiving:
+ t='Receiving'
+ bgcolor='green'
+ if t!=t0:
+ #msg6.configure(text=t,bg=bgcolor)
+ print(t)
+ t0=t
+ #if w.acom1.ntune==0:
+ #btune.configure(bg='gray85')
+ #pctscale.configure(state=NORMAL)
+ #else:
+ # pctscale.configure(state=DISABLED)
+ if w.acom1.ncal==0:
+ #advanced.bmeas.configure(bg='gray85')
+ None
+ else:
+ idle.set(1)
+ if ierr==0:
+ w.acom1.pctx=ipctx.get()
+ else:
+ w.acom1.pctx=0
+ w.acom1.idle=idle.get()
+ #if idle.get()==0:
+ # bidle.configure(bg='gray85')
+ #else:
+ # bidle.configure(bg='yellow')
+ #if w.acom1.transmitting or w.acom1.receiving or options.outbad.get():
+ # btune.configure(state=DISABLED)
+ #else:
+ # btune.configure(state=NORMAL)
+ #if w.acom1.transmitting or w.acom1.receiving or twait < 6.0:
+ # advanced.bmeas.configure(state=DISABLED)
+ #else:
+ # advanced.bmeas.configure(state=NORMAL)
+
+ #if upload.get()==1:
+ # bupload.configure(bg='gray85')
+ #else:
+ # bupload.configure(bg='yellow')
+
+# If new decoded text has appeared, display it.
+ if w.acom1.ndecdone:
+ get_decoded()
+ w.acom1.ndecdone=0
+ w.acom1.ndiskdat=0
+
+# Display the waterfall
+ try:
+ modpixmap=os.stat('pixmap.dat')[8]
+ if modpixmap!=modpixmap0:
+ f=open('pixmap.dat','rb')
+ a=array.array('h')
+ a.fromfile(f,NX*NY)
+ f.close()
+ newdat=1
+ modpixmap0=modpixmap
+ except:
+ newdat=0
+ #scale=math.pow(10.0,0.003*sc1.get())
+ #offset=0.3*sc2.get()
+ #if newdat or scale!= scale0 or offset!=offset0 or g.cmap!=g.cmap0:
+ # im.putdata(a,scale,offset) #Compute whole new image
+ # if newdat:
+ # n=len(tw)
+ # for i in range(n-1,-1,-1):
+ # x=465-39*i
+ # draw.text((x,148),tw[i],fill=253) #Insert time label
+ # if i<len(fw):
+ # draw.text((x+10,1),fw[i],fill=253) #Insert band label
+ #
+ # pim=ImageTk.PhotoImage(im) #Convert Image to PhotoImage
+ # graph1.delete(ALL)
+ # graph1.create_image(0,0+2,anchor='nw',image=pim)
+ # g.ndecphase=2
+ # newMinute=0
+ # scale0=scale
+ # offset0=offset
+ # g.cmap0=g.cmap
+ # newdat=0
+
+ #s0=sc1.get()
+ #c0=sc2.get()
+ #try:
+ # fmid=f0.get() + 0.001500
+ #except:
+ # pass
+
+ #if fmid!=fmid0 or ftx.get()!=ftx0:
+ # draw_axis()
+ # lftx.configure(validate={'validator':'real',
+ # 'min':f0.get()+0.001500-0.000100,'minstrict':0,
+ # 'max':f0.get()+0.001500+0.000100,'maxstrict':0})
+ w.acom1.ndebug=ndebug.get()
+
+ if options.rignum.get()==2509 or options.rignum.get()==2511:
+ options.pttmode.set('CAT')
+ options.CatPort.set('USB')
+ if options.pttmode.get()=='CAT':
+ options.cat_enable.set(1)
+ #if options.pttmode.get()=='CAT' or options.pttmode.get()=='VOX':
+ # options.PttPort.set('None')
+ # options.ptt_port._entryWidget['state']=DISABLED
+ #else:
+ # options.ptt_port._entryWidget['state']=NORMAL
+ #if options.cat_enable.get():
+ # options.lrignum._entryWidget['state']=NORMAL
+ # if options.cat_port.get() != 'USB':
+ # options.cat_port._entryWidget['state']=NORMAL
+ # options.cbbaud._entryWidget['state']=NORMAL
+ # options.cbdata._entryWidget['state']=NORMAL
+ # options.cbstop._entryWidget['state']=NORMAL
+ # options.cbhs._entryWidget['state']=NORMAL
+ # else:
+ # options.cat_port._entryWidget['state']=DISABLED
+ # options.cbbaud._entryWidget['state']=DISABLED
+ # options.cbdata._entryWidget['state']=DISABLED
+ # options.cbstop._entryWidget['state']=DISABLED
+ # options.cbhs._entryWidget['state']=DISABLED
+ # advanced.bsetfreq.configure(state=NORMAL)
+ # advanced.breadab.configure(state=NORMAL)
+ # advanced.enable_cal.configure(state=NORMAL)
+ #else:
+ # options.cat_port._entryWidget['state']=DISABLED
+ # options.lrignum._entryWidget['state']=DISABLED
+ # options.cbbaud._entryWidget['state']=DISABLED
+ # options.cbdata._entryWidget['state']=DISABLED
+ # options.cbstop._entryWidget['state']=DISABLED
+ # options.cbhs._entryWidget['state']=DISABLED
+ # advanced.bsetfreq.configure(state=DISABLED)
+ # advanced.breadab.configure(state=DISABLED)
+ # advanced.enable_cal.configure(state=DISABLED)
+ # advanced.encal.set(0)
+ w.acom1.pttmode=(options.pttmode.get().strip()+' ')[:3]
+ w.acom1.ncat=options.cat_enable.get()
+ w.acom1.ncoord=hopping.coord_bands.get()
+
+ if g.ndevin.get()!= nin0 or g.ndevout.get()!=nout0:
+ audio_config()
+ nin0=g.ndevin.get()
+ nout0=g.ndevout.get()
+ if options.inbad.get()==0:
+ #msg2.configure(text='',bg='gray85')
+ None
+ else:
+ #msg2.configure(text='Invalid audio input device.',bg='red')
+ print('Invalid audio input device.')
+ if options.outbad.get()==0:
+ #msg3.configure(text='',bg='gray85')
+ None
+ else:
+ #msg3.configure(text='Invalid audio output device.',bg='red')
+ print('Invalid audio output device.')
+ if w.acom1.ndecoding:
+ #msg5.configure(text='Decoding',bg='#66FFFF',relief=SUNKEN)
+ print('Decoding')
+ else:
+ #msg5.configure(text='',bg='gray85',relief=FLAT)
+ None
+
+ #if advanced.encal.get():
+ # advanced.A_entry.configure(entry_state=NORMAL,label_state=NORMAL)
+ # advanced.B_entry.configure(entry_state=NORMAL,label_state=NORMAL)
+ #else:
+ # advanced.A_entry.configure(entry_state=DISABLED,label_state=DISABLED)
+ # advanced.B_entry.configure(entry_state=DISABLED,label_state=DISABLED)
+
+ #timer1=ldate.after(200,update)
+ time.sleep(0.2)
+
+#------------------------------------------------------ audio_config
+def audio_config():
+ inbad,outbad=w.audiodev(g.ndevin.get(),g.ndevout.get())
+ options.inbad.set(inbad)
+ options.outbad.set(outbad)
+ if inbad or outbad:
+ w.acom1.ndevsok=0
+ print('Bad audio devices!')
+ #options1()
+ else:
+ print('Audio config ok')
+ w.acom1.ndevsok=1
+
+#------------------------------------------------------ save_params
+def save_params():
+ return # for no gui
+ f=open(appdir+'/WSPR.INI',mode='w')
+ f.write("WSPRGeometry " + root_geom + "\n")
+ if options.MyCall.get()=='': options.MyCall.set('##')
+ f.write("MyCall " + options.MyCall.get() + "\n")
+ if options.MyGrid.get()=='': options.MyGrid.set('##')
+ f.write("MyGrid " + options.MyGrid.get() + "\n")
+ f.write("CWID " + str(advanced.idint.get()) + "\n")
+ f.write("dBm " + str(options.dBm.get()) + "\n")
+ f.write("PttPort " + str(options.PttPort.get()) + "\n")
+ f.write("CatPort " + str(options.CatPort.get()) + "\n")
+ if options.DevinName.get()=='': options.DevinName.set('0')
+ f.write("AudioIn " + options.DevinName.get().replace(" ","#") + "\n")
+ if options.DevoutName.get()=='': options.DevoutName.set('2')
+ f.write("AudioOut " + options.DevoutName.get().replace(" ","#") + "\n")
+ f.write("BFOfreq " + str(advanced.bfofreq.get()) + "\n")
+ f.write("PTTmode " + options.pttmode.get() + "\n")
+ f.write("CATenable " + str(options.cat_enable.get()) + "\n")
+ f.write("Acal " + str(advanced.Acal.get()) + "\n")
+ f.write("Bcal " + str(advanced.Bcal.get()) + "\n")
+ f.write("CalEnable " + str(advanced.encal.get()) + "\n")
+ f.write("IQmode " + str(iq.iqmode.get()) + "\n")
+ f.write("IQrx " + str(iq.iqrx.get()) + "\n")
+ f.write("IQtx " + str(iq.iqtx.get()) + "\n")
+ f.write("FIQ " + str(iq.fiq.get()) + "\n")
+ f.write("Ntxdb " + str(advanced.isc1.get()) + "\n")
+ f.write("SerialRate " + str(options.serial_rate.get()) + "\n")
+ f.write("DataBits " + str(options.databits.get()) + "\n")
+ f.write("StopBits " + str(options.stopbits.get()) + "\n")
+ f.write("Handshake " + options.serial_handshake.get().replace(" ","#") + "\n")
+ t=str(options.rig.get().replace(" ","#"))
+ f.write("Rig " + str(t.replace("\t","#"))[:46] + "\n")
+ f.write("Nsave " + str(nsave.get()) + "\n")
+ f.write("PctTx " + str(ipctx.get()) + "\n")
+ f.write("Upload " + str(upload.get()) + "\n")
+ f.write("Idle " + str(idle.get()) + "\n")
+ f.write("Debug " + str(ndebug.get()) + "\n")
+ f.write("WatScale " + str(s0) + "\n")
+ f.write("WatOffset " + str(c0) + "\n")
+ f.write("Palette " + g.cmap + "\n")
+ mrudir2=mrudir.replace(" ","#")
+ f.write("MRUdir " + mrudir2 + "\n")
+ f.write("freq0_600 " + str( freq0[1]) + "\n")
+ f.write("freqtx_600 " + str(freqtx[1]) + "\n")
+ f.write("freq0_160 " + str( freq0[2]) + "\n")
+ f.write("freqtx_160 " + str(freqtx[2]) + "\n")
+ f.write("freq0_80 " + str( freq0[3]) + "\n")
+ f.write("freqtx_80 " + str(freqtx[3]) + "\n")
+ f.write("freq0_60 " + str( freq0[4]) + "\n")
+ f.write("freqtx_60 " + str(freqtx[4]) + "\n")
+ f.write("freq0_40 " + str( freq0[5]) + "\n")
+ f.write("freqtx_40 " + str(freqtx[5]) + "\n")
+ f.write("freq0_30 " + str( freq0[6]) + "\n")
+ f.write("freqtx_30 " + str(freqtx[6]) + "\n")
+ f.write("freq0_20 " + str( freq0[7]) + "\n")
+ f.write("freqtx_20 " + str(freqtx[7]) + "\n")
+ f.write("freq0_17 " + str( freq0[8]) + "\n")
+ f.write("freqtx_17 " + str(freqtx[8]) + "\n")
+ f.write("freq0_15 " + str( freq0[9]) + "\n")
+ f.write("freqtx_15 " + str(freqtx[9]) + "\n")
+ f.write("freq0_12 " + str( freq0[10]) + "\n")
+ f.write("freqtx_12 " + str(freqtx[10]) + "\n")
+ f.write("freq0_10 " + str( freq0[11]) + "\n")
+ f.write("freqtx_10 " + str(freqtx[11]) + "\n")
+ f.write("freq0_6 " + str( freq0[12]) + "\n")
+ f.write("freqtx_6 " + str(freqtx[12]) + "\n")
+ f.write("freq0_4 " + str( freq0[13]) + "\n")
+ f.write("freqtx_4 " + str(freqtx[13]) + "\n")
+ f.write("freq0_2 " + str( freq0[14]) + "\n")
+ f.write("freqtx_2 " + str(freqtx[14]) + "\n")
+ f.write("freq0_other " + str( freq0[15]) + "\n")
+ f.write("freqtx_other " + str(freqtx[15]) + "\n")
+ f.write("iband " + str(iband.get()) + "\n")
+ f.write("StartIdle " + str(start_idle.get()) + "\n")
+ f.write("NoBeep " + str(no_beep.get()) + "\n")
+ f.write("Reject " + str(nreject) + "\n")
+ f.write("RxApply " + str(iq.iqrxapp.get()) + "\n")
+ f.close()
+ hopping.save_params(appdir)
+
+#------------------------------------------------------ Top level frame
+# ... all gui setup deleted
+
+isync=1
+iband.set(6)
+idle.set(1)
+ipctx.set(20)
+
+#---------------------------------------------------------- Process INI file
+try:
+ f=open(appdir+'/WSPR.INI',mode='r')
+ params=f.readlines()
+except:
+ params=""
+
+badlist=[]
+#----------------------------------------------------------- readinit
+def readinit():
+ global nparam,mrudir
+ try:
+ for i in range(len(params)):
+ if badlist.count(i)>0:
+ print('Skipping bad entry in WSPR.INI:\a',params[i])
+ continue
+ key,value=params[i].split()
+ if key == 'WSPRGeometry': root.geometry(value)
+ elif key == 'MyCall': options.MyCall.set(value)
+ elif key == 'MyGrid': options.MyGrid.set(value)
+ elif key == 'CWID': advanced.idint.set(value)
+ elif key == 'dBm': options.dBm.set(value)
+ elif key == 'PctTx': ipctx.set(value)
+ elif key == 'PttPort': options.PttPort.set(value)
+ elif key == 'CatPort': options.CatPort.set(value)
+ elif key == 'AudioIn':
+ value=value.replace("#"," ")
+ g.DevinName.set(value)
+ try:
+ g.ndevin.set(int(value[:2]))
+ except:
+ g.ndevin.set(0)
+ options.DevinName.set(value)
+
+
+ elif key == 'AudioOut':
+ value=value.replace("#"," ")
+ g.DevoutName.set(value)
+ try:
+ g.ndevout.set(int(value[:2]))
+ except:
+ g.ndevout.set(0)
+ options.DevoutName.set(value)
+
+ elif key == 'BFOfreq': advanced.bfofreq.set(value)
+ elif key == 'Acal': advanced.Acal.set(value)
+ elif key == 'Bcal': advanced.Bcal.set(value)
+ elif key == 'CalEnable': advanced.encal.set(value)
+ elif key == 'IQmode': iq.iqmode.set(value)
+ elif key == 'IQrx': iq.iqrx.set(value)
+ elif key == 'IQtx': iq.iqtx.set(value)
+ elif key == 'FIQ': iq.fiq.set(value)
+ elif key == 'Ntxphaf': iq.isc3a.set(value)
+ elif key == 'PTTmode': options.pttmode.set(value)
+ elif key == 'CATenable': options.cat_enable.set(value)
+ elif key == 'SerialRate': options.serial_rate.set(int(value))
+ elif key == 'DataBits': options.databits.set(int(value))
+ elif key == 'StopBits': options.stopbits.set(int(value))
+ elif key == 'Handshake': options.serial_handshake.set(value.replace("#"," ") )
+ elif key == 'Rig':
+ t=value.replace("#"," ")
+ options.rig.set(t)
+ options.rignum.set(int(t[:4]))
+ elif key == 'Nsave': nsave.set(value)
+ elif key == 'Upload': upload.set(value)
+ elif key == 'Idle': idle.set(value)
+ elif key == 'Debug': ndebug.set(value)
+ elif key == 'WatScale': sc1.set(value)
+ elif key == 'WatOffset': sc2.set(value)
+ elif key == 'Palette': g.cmap=value
+ elif key == 'freq0_600': freq0[1]=float(value)
+ elif key == 'freq0_160': freq0[2]=float(value)
+ elif key == 'freq0_80': freq0[3]=float(value)
+ elif key == 'freq0_60': freq0[4]=float(value)
+ elif key == 'freq0_40': freq0[5]=float(value)
+ elif key == 'freq0_30': freq0[6]=float(value)
+ elif key == 'freq0_20': freq0[7]=float(value)
+ elif key == 'freq0_17': freq0[8]=float(value)
+ elif key == 'freq0_15': freq0[9]=float(value)
+ elif key == 'freq0_12': freq0[10]=float(value)
+ elif key == 'freq0_10': freq0[11]=float(value)
+ elif key == 'freq0_6': freq0[12]=float(value)
+ elif key == 'freq0_4': freq0[13]=float(value)
+ elif key == 'freq0_2': freq0[14]=float(value)
+ elif key == 'freq0_other': freq0[15]=float(value)
+ elif key == 'freqtx_600': freqtx[1]=float(value)
+ elif key == 'freqtx_160': freqtx[2]=float(value)
+ elif key == 'freqtx_80': freqtx[3]=float(value)
+ elif key == 'freqtx_60': freqtx[4]=float(value)
+ elif key == 'freqtx_40': freqtx[5]=float(value)
+ elif key == 'freqtx_30': freqtx[6]=float(value)
+ elif key == 'freqtx_20': freqtx[7]=float(value)
+ elif key == 'freqtx_17': freqtx[8]=float(value)
+ elif key == 'freqtx_15': freqtx[9]=float(value)
+ elif key == 'freqtx_12': freqtx[10]=float(value)
+ elif key == 'freqtx_10': freqtx[11]=float(value)
+ elif key == 'freqtx_6': freqtx[12]=float(value)
+ elif key == 'freqtx_4': freqtx[13]=float(value)
+ elif key == 'freqtx_2': freqtx[14]=float(value)
+ elif key == 'freqtx_other': freqtx[15]=float(value)
+ elif key == 'iband': iband.set(value)
+ elif key == 'StartIdle': start_idle.set(value)
+ elif key == 'NoBeep': no_beep.set(value)
+ elif key == 'Reject': w.acom1.reject=float(value)
+ elif key == 'RxApply': iq.iqrxapp.set(value)
+
+ elif key == 'MRUdir':
+ mrudir=value.replace("#"," ")
+ nparam=i
+
+ except:
+ badlist.append(i)
+ nparam=i
+
+
+w.acom1.gain=1.0
+w.acom1.phase=0.0
+w.acom1.reject=0.
+while nparam < len(params)-1:
+ readinit()
+hopping.restore_params(appdir)
+iq.ib.set(iband.get())
+iq.restore()
+
+r=options.chkcall(options.MyCall.get())
+#if r<0:
+# options.lcall._entryFieldEntry['background']='pink'
+# options1()
+#else:
+# options.lcall._entryFieldEntry['background']='white'
+
+r=options.chkgrid(options.MyGrid.get())
+#if r<0:
+# options.lgrid._entryFieldEntry['background']='pink'
+# options1()
+#else:
+# options.lgrid._entryFieldEntry['background']='white'
+
+if g.DevinName.get()=="":
+ g.ndevin.set(-1)
+
+clearlyint=9
+f0.set(freq0[iband.get()])
+ftx.set(freqtx[iband.get()])
+
+if start_idle.get():
+ idle.set(1)
+
+#------------------------------------------------------ Select palette
+if g.cmap == "gray0":
+ pal_gray0()
+ npal.set(0)
+if g.cmap == "gray1":
+ pal_gray1()
+ npal.set(1)
+if g.cmap == "Linrad":
+ pal_linrad()
+ npal.set(2)
+if g.cmap == "blue":
+ pal_blue()
+ npal.set(3)
+if g.cmap == "Hot":
+ pal_Hot()
+ npal.set(4)
+if g.cmap == "AFMHot":
+ pal_AFMHot()
+ npal.set(5)
+
+# gui related:
+#options.dbm_balloon()
+fmid=f0.get() + 0.001500
+sftx.set('%.06f' % ftx.get())
+#draw_axis()
+#erase()
+#if g.Win32: root.iconbitmap("wsjt.ico")
+#root.title(' WSPR 3.0 by K1JT')
+
+put_params()
+try:
+ os.remove('decoded.txt')
+except:
+ pass
+try:
+ os.remove('pixmap.dat')
+except:
+ pass
+
+##if hopping.hopping.get() and hopping.coord_bands.get() and not idle.get():
+## tsec=time.time()
+## utc=time.gmtime(tsec)
+## ns1=int(tsec) % 1200
+## b=ns1/120 + 3
+## if b==12: b=2
+## if hopping.hoppingflag[b].get():
+## iband.set(b)
+## Issue rigctl command here
+
+iband0=iband.get()
+#graph1.focus_set()
+w.acom1.ndevsok=0
+w.acom1.ntxnext=0
+w.acom1.nstoptx=0
+w.wspr1()
+t="%.6f" % (f0.get(),)
+sf0.set(t)
+t="%.6f" % (ftx.get(),)
+sftx.set(t)
+
+time.sleep(0.1)
+audio_config()
+time.sleep(0.1)
+update_nogui()
+
+#ldate.after(100,update)
+#ldate.after(100,audio_config)
+
+##from WsprMod import specjt
+#root.mainloop()
+
+#ldate.after_cancel(timer1)
+
+# Clean up and save user options, then terminate.
+if options.pttmode.get()=='CAT':
+ if options.rignum.get()==2509 or options.rignum.get()==2511:
+ cmd="rigctl -m %d -r %s T 0" % \
+ (options.rignum.get(),options.CatPort.get())
+ else:
+ cmd="rigctl -m %d -r %s -s %d -C data_bits=%s -C stop_bits=%s -C serial_handshake=%s T 0" % \
+ (options.rignum.get(),options.CatPort.get(), \
+ options.serial_rate.get(),options.databits.get(), \
+ options.stopbits.get(),options.serial_handshake.get())
+ ierr=os.system(cmd)
+# don't save different params save_params()
+w.paterminate()
+time.sleep(0.5)
diff --git a/wspr_rxtest.f90 b/wspr_rxtest.f90
new file mode 100644
index 0000000..62459af
--- /dev/null
+++ b/wspr_rxtest.f90
@@ -0,0 +1,68 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: wspr_rxtest.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program wspr_rxtest
+
+ character arg*8
+ include 'acom1.f90'
+
+ nargs=iargc()
+ if(nargs.ne.1 .and. nargs.ne.5) then
+ print*,'Usage: wspr_rxtest infile [...]'
+ print*,' wspr_rxtest selftest txdf fdot snr iters'
+ go to 999
+ endif
+
+ call getarg(1,arg)
+ if(arg.eq.'selftest') then
+ call getarg(2,arg)
+ read(arg,*) ntxdf
+ call getarg(3,arg)
+ read(arg,*) fdot
+ call getarg(4,arg)
+ read(arg,*) snrdb
+ call getarg(5,arg)
+ read(arg,*) iters
+ do iter=1,iters
+!### call genmept('K1JT ','FN20',30,ntxdf,snrdb,iwave)
+ call decode
+ enddo
+ go to 999
+
+ else
+ ltest=.true.
+ do ifile=1,nargs
+ call getarg(ifile,infile)
+ len=80
+ call getfile(infile,80)
+ call decode
+ enddo
+ endif
+
+999 end program wspr_rxtest
+
+subroutine msleep(n)
+ return
+end subroutine msleep
diff --git a/wspr_tr.in0 b/wspr_tr.in0
new file mode 100644
index 0000000..68fa0e7
--- /dev/null
+++ b/wspr_tr.in0
@@ -0,0 +1,3 @@
+ f0 ftx port call grid dbm pctx dsec in out save
+10.13865 10.140150 0 K1JT FN20 25 25 0 0 0 0
+"save/080320_1740.wav"
diff --git a/wsprcode.f90 b/wsprcode.f90
new file mode 100644
index 0000000..cf691cf
--- /dev/null
+++ b/wsprcode.f90
@@ -0,0 +1,154 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: wsprcode.f90
+! Description: This program provides examples of the source encoding,
+! convulsional error-control coding, bit and symbol ordering,
+! and synchronizing information contained in WSPR messages.
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+
+program wsprcode
+
+ parameter (NSYM=162)
+ parameter (MAXSYM=176)
+ character*22 msg,msg2
+ integer*1 data0(7)
+ integer*1 data1(7)
+ integer*1 dat(NSYM)
+ integer*1 softsym(NSYM)
+
+! Define the sync vector:
+ integer*1 sync(NSYM)
+ data sync/ &
+ 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, &
+ 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, &
+ 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, &
+ 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, &
+ 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, &
+ 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, &
+ 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, &
+ 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, &
+ 0,0/
+
+! Metric table for decoding from soft symbols
+ integer mettab(0:255,0:1)
+ data mettab/ &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, &
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, &
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, &
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, &
+ 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, &
+ -1, -1, -1, -2, -2, -3, -4, -4, -5, -6, &
+ -7, -7, -8, -9, -10, -11, -12, -12, -13, -14, &
+ -15, -16, -17, -17, -18, -19, -20, -21, -22, -22, &
+ -23, -24, -25, -26, -26, -27, -28, -29, -30, -30, &
+ -31, -32, -33, -33, -34, -35, -36, -36, -37, -38, &
+ -38, -39, -40, -41, -41, -42, -43, -43, -44, -45, &
+ -45, -46, -47, -47, -48, -49, -49, -50, -51, -51, &
+ -52, -53, -53, -54, -54, -55, -56, -56, -57, -57, &
+ -58, -59, -59, -60, -60, -61, -62, -62, -62, -63, &
+ -64, -64, -65, -65, -66, -67, -67, -67, -68, -69, &
+ -69, -70, -70, -71, -72, -72, -72, -72, -73, -74, &
+ -75, -75, -75, -77, -76, -76, -78, -78, -80, -81, &
+ -80, -79, -83, -82, -81, -82, -82, -83, -84, -84, &
+ -84, -87, -86, -87, -88, -89, -89, -89, -88, -87, &
+ -86, -87, -84, -84, -84, -83, -82, -82, -81, -82, &
+ -83, -79, -80, -81, -80, -78, -78, -76, -76, -77, &
+ -75, -75, -75, -74, -73, -72, -72, -72, -72, -71, &
+ -70, -70, -69, -69, -68, -67, -67, -67, -66, -65, &
+ -65, -64, -64, -63, -62, -62, -62, -61, -60, -60, &
+ -59, -59, -58, -57, -57, -56, -56, -55, -54, -54, &
+ -53, -53, -52, -51, -51, -50, -49, -49, -48, -47, &
+ -47, -46, -45, -45, -44, -43, -43, -42, -41, -41, &
+ -40, -39, -38, -38, -37, -36, -36, -35, -34, -33, &
+ -33, -32, -31, -30, -30, -29, -28, -27, -26, -26, &
+ -25, -24, -23, -22, -22, -21, -20, -19, -18, -17, &
+ -17, -16, -15, -14, -13, -12, -12, -11, -10, -9, &
+ -8, -7, -7, -6, -5, -4, -4, -3, -2, -2, &
+ -1, -1, -1, 0, 0, 1, 1, 1, 1, 2, &
+ 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, &
+ 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, &
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, &
+ 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, &
+ 5, 5/
+
+! Get command-line argument(s)
+ nargs=iargc()
+ if(nargs.ne.1) then
+ print*,'Usage: WSPRcode "message"'
+ go to 999
+ endif
+ call getarg(1,msg) !Get message from command line
+ write(*,1000) msg
+1000 format('Message: ',a22)
+
+ nbits=50+31 !User bits=50, constraint length=32
+ nbytes=(nbits+7)/8
+ ndelta=50
+ limit=20000
+
+ data0=0
+ call wqencode(msg,ntype0,data0) !Source encoding
+ write(*,1002) data0
+1002 format(/'Source-encoded message (50 bits, hex):',7z3.2)
+
+ call encode232(data0,nbytes,dat,MAXSYM) !Convolutional encoding
+ call inter_mept(dat,1) !Interleaving
+
+ write(*,1004)
+1004 format(/'Data symbols:')
+ write(*,1006) (dat(i),i=1,NSYM)
+1006 format(5x,30i2)
+
+ write(*,1008)
+1008 format(/'Sync symbols:')
+ write(*,1006) (sync(i),i=1,NSYM)
+
+ write(*,1010)
+1010 format(/'Channel symbols:')
+ write(*,1006) (2*dat(i)+sync(i),i=1,NSYM)
+
+ call inter_mept(dat,-1) !Remove interleaving
+ softsym=-dat !Simulate soft symbols
+
+! Call the sequential (Fano algorithm) decoder
+ call fano232(softsym,nbits,mettab,ndelta,limit,data1,ncycles,metric,nerr)
+ call wqdecode(data1,msg2,ntype1)
+
+ write(*,1020) msg2,ntype1
+1020 format(/'Decoded message: ',a22,' ntype:',i3)
+
+999 end program wsprcode
diff --git a/wsprrc b/wsprrc
new file mode 100644
index 0000000..829a414
--- /dev/null
+++ b/wsprrc
@@ -0,0 +1,9 @@
+*font: "DejaVu Sans" 9
+*Label*font: "DejaVu Sans" 9
+*Text*font: "Courier 10 Pitch" 13
+*Canvas*font: "DejaVu Sans Mono" 16
+*background: gray85
+*Text*background: white
+*Entry*background: white
+*foreground: black
+*Listbox*foreground: RoyalBlue
diff --git a/wwv.f90 b/wwv.f90
new file mode 100644
index 0000000..c9f2514
--- /dev/null
+++ b/wwv.f90
@@ -0,0 +1,270 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: wwv.f90
+! Description: Find time delay between 1 PPS ticks from GPS and WWV
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+program wwv
+
+! Find time delay between 1 PPS ticks from GPS and WWV.
+
+ parameter (NFSMAX=48000)
+ parameter (NMAX=1210*NFSMAX) !Max length of data
+ integer*2 id(NMAX) !Raw data
+ character arg*12 !Command-line arg
+ character cdate*8 !CCYYMMDD
+ character ctime*10 !HHMMSS.SSS
+ character*120 cmnd0,cmnd !Command to set rig frequency
+ character*6 mycall,mygrid
+ character param*5
+ real*8 tsec,fkhz,p1,samfac,day2011
+ real x1(NMAX),xx1(NMAX)
+ real prof1(NFSMAX+5)
+ real xx(NFSMAX+5)
+ real notch(-20:20)
+ real snr(4)
+ real delay(4)
+ complex c(0:NFSMAX/2)
+ complex cal1(0:35)
+ integer iipk(1)
+ integer soundin
+ integer resample
+ integer time
+ integer nkhz(0:4)
+ integer nwwv(4)
+ equivalence (iipk,ipk)
+ equivalence (xx,c)
+ data nkhz/2500,5000,10000,15000,20000/
+ data nloop/-1/,nHz0/-99/
+
+ nargs=iargc()
+ if(nargs.lt.1 .or. nargs.gt.2) go to 998
+
+ open(10,file='fmt.ini',status='old',err=910) !Open this WSPR file
+ read(10,'(a120)') cmnd0 !Get rigctl command to set frequency
+ read(10,*) ndevin !Get audio device number
+ read(10,*) mycall !Get my callsign
+ read(10,*) mygrid !Get my grid locator
+ close(10)
+
+ open(13,file='prof.dat',status='unknown') !Files for 1PPS+WWV profiles
+ open(14,file='short.dat',status='unknown')
+
+ dtmin=2.0
+ dtmax=40.0
+ dbmin=0.0
+ nsave=1
+ open(10,file='wwv.ini',status='old',err=1) !Optional parameter file
+ read(10,*) param,dtmin
+ read(10,*) param,dtmax
+ read(10,*) param,dbmin
+ read(10,*) param,nsave
+ close(10)
+
+1 nfs=48000 !Sample rate
+ dt=1.0/nfs
+ nchan=1 !Single-channel recording
+ call soundinit !Initialize Portaudio
+
+ call getarg(1,arg)
+ if(arg(:2).eq.'-v') then
+ print*,'Version 1.04'
+ go to 999
+ else if(arg.eq.'cal' .or. arg.eq.'CAL') then
+ nsec=60
+ if(nargs.eq.2) then
+ call getarg(2,arg)
+ read(arg,*) nsec
+ endif
+ call calobs(nfs,nsec,ndevin,id,x1)
+ go to 999
+ endif
+
+ fkhz=0.
+ if(arg.ne.'all' .and. arg.ne.'ALL') read(arg,*) fkhz !Rx frequency (kHz)
+
+ open(10,file='cal.dat',status='old',err=920) !Open previously recorded cal.dat
+ do j=1,35
+ if(j.eq.1) read(10,1001) jj,cal1(j),p1
+ if(j.ne.1) read(10,1001) jj,cal1(j)
+1001 format(i6,2f10.3,f13.4)
+ f=j*100.0
+ x=0.
+ if(f.lt.300.0) x=(f-300.0)/200.0
+ if(f.gt.3000.0) x=(3000-f)/200.0
+ cal1(j)=exp(-x*x)/cal1(j)
+ enddo
+ cal1(0)=0.
+ close(10)
+
+ do i=-20,20
+ x=float(i)/20.0
+ notch(i)=1.0 - exp(-x*x)
+ enddo
+
+ open(16,file='delay.dat',status='unknown',position='append')
+ open(20,file='wwv.bin',form='unformatted',status='unknown',position='append')
+
+ npts=nfs*51
+
+10 nloop=nloop+1
+ if(fkhz.gt.0.d0) then
+ nHz=nint(1.d3*fkhz)
+ else
+ nHz=1000*nkhz(mod(nloop,5))
+ endif
+
+ if(nHz.ne.nHz0 .and. cmnd0(:6).eq.'rigctl') then
+ cmnd=cmnd0
+ i1=index(cmnd,' F ')
+ write(cmnd(i1+2:),*) nHz !Insert desired frequency
+ iret=system(cmnd) !Set Rx frequency
+ if(iret.ne.0) then
+ print*,'Error executing rigctl command to set frequency:'
+ print*,cmnd
+ go to 999
+ endif
+
+ cmnd(i1+1:)='M AM 0'
+ iret=system(cmnd) !Set Rx mode
+ if(iret.ne.0) then
+ print*,'Error executing rigctl command to set Rx mode:'
+ print*,cmnd
+ go to 999
+ endif
+ nHz0=nHz
+ endif
+
+ call getutc(cdate,ctime,tsec)
+ do while (ctime(5:6).ne.'01')
+ call getutc(cdate,ctime,tsec)
+ call msleep(100)
+ enddo
+
+ ierr=soundin(ndevin,nfs,id,npts,nchan-1) !Get audio data
+ if(ierr.ne.0) then
+ print*,'Error in soundin',ierr
+ stop
+ endif
+
+ x1(:npts)=id(:npts)
+ call averms(x1,npts,ave1,rms1,xmax1) !Get ave, rms
+ x1(:npts)=(1.0/rms1)*(x1(:npts)-ave1) !Remove DC and normalize
+
+! Resample ntype: 0=best, 1=sinc_medium, 2=sinc_fast, 3=hold, 4=linear
+ ntype=1
+ samfac=nfs/p1
+ ierr=resample(x1,xx1,samfac,npts,ntype) !Resample to nfs Hz, exactly
+ if(ierr.ne.0) print*,'Resample error.',samfac
+ npts=samfac*npts
+
+ ip=nfs
+ prof1=0.
+ do i=1,npts,nfs !Fold at p=nfs (exactly)
+ prof1(:ip)=prof1(:ip) + xx1(i:i+ip-1)
+ enddo
+
+ pmax=0.
+ do i=1,ip
+ if(abs(prof1(i)).gt.abs(pmax)) then
+ pmax=prof1(i)
+ ipk=i
+ endif
+ enddo
+ prof1(:ip)=prof1(:ip)/pmax
+ xx=prof1
+
+ if(nsave.ne.0) then
+! iipk=maxloc(prof1)
+ i0=0.001/dt
+ rewind 13
+ rewind 14
+ do i=1,ip
+ j=ipk+i-100
+ if(j.lt.1) j=j+ip
+ if(j.gt.ip) j=j-ip
+ write(13,1010) 1000.0*(i-1)*dt,prof1(j)
+1010 format(2f10.3)
+ t=1000.0*(i-i0)*dt
+ if(t.ge.-1.0 .and. t.le.dtmax) then
+ j=ipk+i-i0
+ if(j.lt.1) j=j+ip
+ if(j.gt.ip) j=j-ip
+ write(14,1010) t,prof1(j)
+ endif
+ enddo
+ call flush(13)
+ call flush(14)
+ endif
+
+ call four2a(xx,ip,1,-1,0) !Forward FFT of profile
+
+ df=float(nfs)/ip
+ ib=nint(3500.0/df)
+ do i=0,ib
+ j=nint(0.01*i*df)
+ c(i)=c(i)*cal1(j)
+ enddo
+
+ c(ib:)=0.
+ c(95:105)=0.
+ if(ctime(3:4).eq.'02') then
+ c(420:460)=c(420:460)*notch
+ else
+ read(ctime(4:4),*) i10
+ if(mod(i10,2).eq.1) then
+ c(580:620)=c(580:620)*notch
+ else
+ c(480:520)=c(480:520)*notch
+ endif
+ endif
+
+ call four2a(c,ip,1,1,-1) !Inverse FFT ==> calibrated profile
+
+ fac=6.62/ip
+ xx=fac*xx
+ iipk=maxloc(xx)
+
+ call clean(xx,ipk,dtmin,dtmax,dbmin,snr,delay,nwwv,nd)
+
+ day2011=time()/86400.d0 - 14974.d0
+ ikhz=nhz/1000
+ do i=1,nd
+ write(*,1000) cdate,ctime(1:6),day2011,ikhz,snr(i),delay(i),nwwv(i)
+ write(16,1000) cdate,ctime(1:6),day2011,ikhz,snr(i),delay(i),nwwv(i)
+1000 format(a8,2x,a6,f10.4,i7,f7.1,f8.2,i4)
+ enddo
+
+ call flush(16)
+ go to 10
+
+910 print*,'Cannot open file: fmt.ini'
+ go to 999
+920 print*,'Cannot open file: cal.dat'
+ go to 999
+
+998 print*,'Usage: wwv cal <nsec> (Calibration, 1 PPS only)'
+ print*, ' wwv <f_kHz> (1 PPS and WWV at one frequency)'
+ print*, ' wwv all (1 PPS and WWV at all frequencies)'
+ print*, ' wwv -v (Print version number and exit'
+
+999 end program wwv
diff --git a/xcor162.f90 b/xcor162.f90
new file mode 100644
index 0000000..eb67438
--- /dev/null
+++ b/xcor162.f90
@@ -0,0 +1,90 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: xcor162.f90
+! Description: Computes ccf of a row of s2 and the pseudo-random array pr3.
+! Returns peak of the CCF and the lag at which peak occurs.
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine xcor162(s2,ipk,nsteps,nsym,lag1,lag2,ccf,ccf0,lagpk)
+
+! Computes ccf of a row of s2 and the pseudo-random array pr3. Returns
+! peak of the CCF and the lag at which peak occurs.
+
+ parameter (NFFT=512)
+ parameter (NH=NFFT/2)
+ parameter (NSMAX=352)
+ real s2(-NH:NH,NSMAX)
+ real a(NSMAX)
+ real ccf(-5:540)
+ logical first
+ data first/.true./
+ integer npr3(162)
+ real pr3(162)
+ data npr3/ &
+ & 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, &
+ & 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, &
+ & 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, &
+ & 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, &
+ & 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, &
+ & 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, &
+ & 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, &
+ & 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, &
+ & 0,0/
+ save
+
+ if(first) then
+ nsym=162
+ do i=1,nsym
+ pr3(i)=2*npr3(i)-1
+ enddo
+ first=.false.
+ endif
+
+ n=2
+ method=2
+ do j=1,nsteps
+ if(method.eq.1) then
+ a(j)=0.5*(s2(ipk+n,j) + s2(ipk+3*n,j) - &
+ & s2(ipk ,j) - s2(ipk+2*n,j))
+ else
+ a(j)=max(s2(ipk+n,j),s2(ipk+3*n,j)) - &
+ & max(s2(ipk ,j),s2(ipk+2*n,j))
+ endif
+ enddo
+
+ ccfmax=0.
+ do lag=lag1,lag2
+ x=0.
+ do i=1,nsym
+ j=2*i-1+lag
+ if(j.ge.1 .and. j.le.nsteps) x=x+a(j)*pr3(i)
+ enddo
+ ccf(lag)=2*x !The 2 is for plotting scale
+ if(ccf(lag).gt.ccfmax) then
+ ccfmax=ccf(lag)
+ lagpk=lag
+ endif
+ enddo
+ ccf0=ccfmax
+
+ return
+end subroutine xcor162
diff --git a/xfft.f90 b/xfft.f90
new file mode 100644
index 0000000..69a6609
--- /dev/null
+++ b/xfft.f90
@@ -0,0 +1,36 @@
+!-------------------------------------------------------------------------------
+!
+! This file is part of the WSPR application, Weak Signal Propagation Reporter
+!
+! File Name: xfft.f90
+! Description:
+!
+! Copyright (C) 2001-2014 Joseph Taylor, K1JT
+! License: GPL-3
+!
+! This program is free software; you can redistribute it and/or modify it under
+! the terms of the GNU General Public License as published by the Free Software
+! Foundation; either version 3 of the License, or (at your option) any later
+! version.
+!
+! This program is distributed in the hope that it will be useful, but WITHOUT
+! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+! details.
+!
+! You should have received a copy of the GNU General Public License along with
+! this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+! Street, Fifth Floor, Boston, MA 02110-1301, USA.
+!
+!-------------------------------------------------------------------------------
+subroutine xfft(x,nfft)
+
+! Real-to-complex FFT.
+
+ real x(nfft)
+
+ call four2a(x,nfft,1,-1,0)
+
+ return
+end subroutine xfft
+
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/wspr.git
More information about the pkg-hamradio-commits
mailing list