[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