Index: /trunk/BNC/CHANGELOG.md
===================================================================
--- /trunk/BNC/CHANGELOG.md	(revision 11159)
+++ /trunk/BNC/CHANGELOG.md	(revision 11160)
@@ -82,5 +82,7 @@
 - CHANGED: Plot scripts (cmbPlot.pl, pppPlot.pl, compareSP3Plot.pl) delete only the images they created (cmbPlot.pl deleted all PNG files of the input directory)
 - CHANGED: Linux build with stack protector, bounds-checked std containers (_GLIBCXX_ASSERTIONS) and read-only relocations, independent of the distribution's compiler defaults
-- ADDED: Option 'Credentials files' (key credentialsFile): passwords of Ntrip Broadcasters can be kept in credentials files instead of the configuration file, in netrc format ('machine host[:port] login user password secret', default $NETRC or ~/.netrc) or BNC format ('host port|* user|- password'); several users per host, URLs without user stay anonymous, passwords in the configuration file are used as before
+- ADDED: Option 'Credentials files' (key credentialsFile): passwords of Ntrip Broadcasters can be kept in credentials files instead of the configuration file, in netrc format ('machine host[:port] login user password secret', default $NETRC or ~/.netrc) or BNC format ('host port|* user|- password' or 'host user password'); several users per host, URLs without user stay anonymous, passwords in the configuration file are used as before
+- CHANGED: Help: screenshots updated to the current version; new figures for 'RINEX Ephemeris' and the real-time track map (PPP Panel 4), figures renumbered
+- CHANGED: Example configurations: observation streams from igs-ip.net instead of euref-ip.net
 - FIXED: Ephemerides read from RINEX navigation files (GPS, QZSS, NavIC, Galileo, SBAS, BDS): fields not contained in the record type (e.g. IODE of GPS CNAV) were not initialized; depending on memory content, data sets could be rated WRONG in the RINEX QC
 - CHANGED: Deprecated Qt functions replaced (QRegExp, QFontMetrics::width, QLayout::setMargin, QPrinter::pageRect/paperRect, recursive QMutex); BNC is built with QT_DISABLE_DEPRECATED_BEFORE=0x050F00
Index: /trunk/BNC/Example_Configs/01_RinexObs.bnc
===================================================================
--- /trunk/BNC/Example_Configs/01_RinexObs.bnc	(revision 11159)
+++ /trunk/BNC/Example_Configs/01_RinexObs.bnc	(revision 11160)
@@ -33,5 +33,5 @@
 miscPort=
 miscScanRTCM=0
-mountPoints=//Example:Configs@euref-ip.net:2101/FFMJ00DEU0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@euref-ip.net:2101/KIR000SWE0 RTCM_3.3 SWE 67.88 21.06 no 2
+mountPoints=//Example:Configs@igs-ip.net:2101/FFMJ00DEU0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@igs-ip.net:2101/KIR000SWE0 RTCM_3.3 SWE 67.88 21.06 no 2
 ntripVersion=2
 onTheFlyInterval=no
Index: /trunk/BNC/Example_Configs/07_FeedEngine.bnc
===================================================================
--- /trunk/BNC/Example_Configs/07_FeedEngine.bnc	(revision 11159)
+++ /trunk/BNC/Example_Configs/07_FeedEngine.bnc	(revision 11160)
@@ -33,5 +33,5 @@
 miscPort=
 miscScanRTCM=0
-mountPoints=//Example:Configs@euref-ip.net:2101/FFMJ00DEU0 RTCM_3.3 DEU 50.09 8.66 no 2
+mountPoints=//Example:Configs@igs-ip.net:2101/FFMJ00DEU0 RTCM_3.3 DEU 50.09 8.66 no 2
 ntripVersion=2
 onTheFlyInterval=no
Index: /trunk/BNC/Example_Configs/08_PPP.bnc
===================================================================
--- /trunk/BNC/Example_Configs/08_PPP.bnc	(revision 11159)
+++ /trunk/BNC/Example_Configs/08_PPP.bnc	(revision 11160)
@@ -34,5 +34,5 @@
 miscPort=
 miscScanRTCM=0
-mountPoints=//Example:Configs@euref-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2
+mountPoints=//Example:Configs@igs-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2
 ntripVersion=2
 onTheFlyInterval=no
Index: /trunk/BNC/Example_Configs/09_PPPNet.bnc
===================================================================
--- /trunk/BNC/Example_Configs/09_PPPNet.bnc	(revision 11159)
+++ /trunk/BNC/Example_Configs/09_PPPNet.bnc	(revision 11160)
@@ -33,5 +33,5 @@
 miscPort=
 miscScanRTCM=0
-mountPoints=//Example:Configs@euref-ip.net:2101/FFMJ00DEU0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@euref-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2
+mountPoints=//Example:Configs@igs-ip.net:2101/FFMJ00DEU0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@igs-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2
 ntripVersion=2
 onTheFlyInterval=no
Index: /trunk/BNC/Example_Configs/10_PPPQuickStart.bnc
===================================================================
--- /trunk/BNC/Example_Configs/10_PPPQuickStart.bnc	(revision 11159)
+++ /trunk/BNC/Example_Configs/10_PPPQuickStart.bnc	(revision 11160)
@@ -33,5 +33,5 @@
 miscPort=
 miscScanRTCM=0
-mountPoints=//Example:Configs@euref-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2
+mountPoints=//Example:Configs@igs-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2
 ntripVersion=2
 onTheFlyInterval=no
Index: /trunk/BNC/Example_Configs/13_PPPGalHAS.bnc
===================================================================
--- /trunk/BNC/Example_Configs/13_PPPGalHAS.bnc	(revision 11159)
+++ /trunk/BNC/Example_Configs/13_PPPGalHAS.bnc	(revision 11160)
@@ -34,5 +34,5 @@
 miscPort=
 miscScanRTCM=0
-mountPoints=//Example:Configs@euref-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //user:pass@ntrip.gsc-europa.eu:443/SSRA00EUH0 RTCM_3.3 ESP 40.49 356.54 no 2s, //Example:Configs@products.igs-ip.net:2101/BCEP01BKG0 RTCM_3.1 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP03BKG0 RTCM_3.3 DEU 50.09 8.66 no 2
+mountPoints=//Example:Configs@igs-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //user:pass@ntrip.gsc-europa.eu:443/SSRA00EUH0 RTCM_3.3 ESP 40.49 356.54 no 2s, //Example:Configs@products.igs-ip.net:2101/BCEP01BKG0 RTCM_3.1 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP03BKG0 RTCM_3.3 DEU 50.09 8.66 no 2
 ntripVersion=2
 onTheFlyInterval=no
Index: /trunk/BNC/Example_Configs/15_Sp3ETRFPPP.bnc
===================================================================
--- /trunk/BNC/Example_Configs/15_Sp3ETRFPPP.bnc	(revision 11159)
+++ /trunk/BNC/Example_Configs/15_Sp3ETRFPPP.bnc	(revision 11160)
@@ -34,5 +34,5 @@
 miscPort=
 miscScanRTCM=0
-mountPoints=//Example:Configs@euref-ip.net:2101/FFMJ00DEU0 RTCM_3.0 DEU 50.09 8.66 no 2, //Example:Configs@euref-ip.net:2101/SSRA02IGS1_EUREF RTCM_3.1 DEU 50.00 10.00 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3.3 DEU 50.09 8.66 no 2
+mountPoints=//Example:Configs@igs-ip.net:2101/FFMJ00DEU0 RTCM_3.0 DEU 50.09 8.66 no 2, //Example:Configs@euref-ip.net:2101/SSRA02IGS1_EUREF RTCM_3.1 DEU 50.00 10.00 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3.3 DEU 50.09 8.66 no 2
 ntripVersion=2
 onTheFlyInterval=no
Index: /trunk/BNC/Example_Configs/18_CombiPPP.bnc
===================================================================
--- /trunk/BNC/Example_Configs/18_CombiPPP.bnc	(revision 11159)
+++ /trunk/BNC/Example_Configs/18_CombiPPP.bnc	(revision 11160)
@@ -34,5 +34,5 @@
 miscPort=
 miscScanRTCM=0
-mountPoints=//Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CHC1 RTCM_3.1 CHN 31.16 121.30 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2, //Example:Configs@www.euref-ip.net:2101/FFMJ00DEU0 RTCM_3.3 DEU 50.09 8.66 no 2
+mountPoints=//Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CHC1 RTCM_3.1 CHN 31.16 121.30 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2, //Example:Configs@igs-ip.net:2101/FFMJ00DEU0 RTCM_3.3 DEU 50.09 8.66 no 2
 ntripVersion=2
 onTheFlyInterval=no
Index: /trunk/BNC/Example_Configs/26_ScanLate.sh
===================================================================
--- /trunk/BNC/Example_Configs/26_ScanLate.sh	(revision 11159)
+++ /trunk/BNC/Example_Configs/26_ScanLate.sh	(revision 11160)
@@ -2,5 +2,5 @@
 
 ../bnc -nw -conf /dev/null \
-       -key mountPoints "//Example:Configs@euref-ip.net:2101/KIR000SWE0 RTCM_3.3 SWE 67.88 21.06 no 2" \
+       -key mountPoints "//Example:Configs@igs-ip.net:2101/KIR000SWE0 RTCM_3.3 SWE 67.88 21.06 no 2" \
        -key miscMount KIR000SWE0 \
        -key miscScanRTCM 2 \
Index: /trunk/BNC/Example_Configs/27_RinexObs.sh
===================================================================
--- /trunk/BNC/Example_Configs/27_RinexObs.sh	(revision 11159)
+++ /trunk/BNC/Example_Configs/27_RinexObs.sh	(revision 11160)
@@ -2,5 +2,5 @@
 
 ../bnc -nw -conf /dev/null \
-       -key mountPoints "//Example:Configs@euref-ip.net:2101/KIR000SWE0 RTCM_3.3 SWE 67.88 21.06 no 2;//Example:Configs@euref-ip.net:2101/FFMJ00DEU0 RTCM_3.3 DEU 50.09 8.66 no 2" \
+       -key mountPoints "//Example:Configs@igs-ip.net:2101/KIR000SWE0 RTCM_3.3 SWE 67.88 21.06 no 2;//Example:Configs@igs-ip.net:2101/FFMJ00DEU0 RTCM_3.3 DEU 50.09 8.66 no 2" \
        -key logFile Output/RinexObs.log \
        -key rnxPath Output \
Index: /trunk/BNC/Example_Configs/29_PPPAR_CNES.bnc
===================================================================
--- /trunk/BNC/Example_Configs/29_PPPAR_CNES.bnc	(revision 11159)
+++ /trunk/BNC/Example_Configs/29_PPPAR_CNES.bnc	(revision 11160)
@@ -33,5 +33,5 @@
 miscPort=
 miscScanRTCM=0
-mountPoints=//Example:Configs@euref-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2
+mountPoints=//Example:Configs@igs-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00CNE1 RTCM_3.1 FRA 43.56 1.48 no 2
 ntripVersion=2
 onTheFlyInterval=no
Index: /trunk/BNC/Example_Configs/30_PPPAR_WHU.bnc
===================================================================
--- /trunk/BNC/Example_Configs/30_PPPAR_WHU.bnc	(revision 11159)
+++ /trunk/BNC/Example_Configs/30_PPPAR_WHU.bnc	(revision 11160)
@@ -34,5 +34,5 @@
 miscPort=
 miscScanRTCM=0
-mountPoints=//Example:Configs@euref-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/OSBC00WHU1 RTCM_3.1 DEU 30.52 114.35 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00WHU0 RTCM_3.1 FRA 43.56 1.48 no 2
+mountPoints=//Example:Configs@igs-ip.net:2101/WTZR00DEU0 RTCM_3.3 DEU 49.14 12.88 no 2, //Example:Configs@products.igs-ip.net:2101/BCEP00BKG0 RTCM_3.3 DEU 50.09 8.66 no 2, //Example:Configs@products.igs-ip.net:2101/OSBC00WHU1 RTCM_3.1 DEU 30.52 114.35 no 2, //Example:Configs@products.igs-ip.net:2101/SSRA00WHU0 RTCM_3.1 FRA 43.56 1.48 no 2
 ntripVersion=2
 onTheFlyInterval=no
Index: /trunk/BNC/scripts/compareSP3Plot.pl
===================================================================
--- /trunk/BNC/scripts/compareSP3Plot.pl	(revision 11159)
+++ /trunk/BNC/scripts/compareSP3Plot.pl	(revision 11160)
@@ -45,4 +45,5 @@
 my @plotTypes = ();
 my @logFiles  = ();
+my $keepImages = 0;
 # read options from command line
 GetOptions(
@@ -50,7 +51,8 @@
     'plotTypes=s' => \@plotTypes,
     'logFiles=s'  => \@logFiles,
+    'keepImages'  => \$keepImages,
 );
 help() if $help;
-@plotTypes = qw(NEU) unless (@plotTypes);# Default values
+@plotTypes = qw(ALL) unless (@plotTypes);# Default values
 @plotTypes = map {uc} split ( /[, ]/, join ( ',', @plotTypes ) );# Reformat options
 @logFiles  = split ( /[, ]/, join ( ',', @logFiles ) );
@@ -349,5 +351,5 @@
     $pdf->save();
     $pdf->end();
-    unlink @pngFiles;    # only the images written here
+    unlink @pngFiles unless $keepImages;    # only the images written here
     @pngFiles = ();
     #system("evince $inputDir/$pdf_name&");
@@ -363,7 +365,8 @@
 PARAMETER:
 
-      --plotTypes     possible: ALL => RAO, CLOCK and SIS; CLK => CLOCK and CLOCKRED; RAO => RAO only;
+      --plotTypes     possible: ALL (default) => RAO, CLOCK and SIS; CLK => CLOCK and CLOCKRED; RAO => RAO only;
                       SIS => signal-in-space differences (radial minus clock) only
       --logFiles      comma separated list of BNC's SP3 comparison logfiles
+      --keepImages    keep the PNG images of the plots (deleted after inclusion in the PDF by default)
       --help          show help contents
 
Index: /trunk/BNC/scripts/helpScreenshots/README.md
===================================================================
--- /trunk/BNC/scripts/helpScreenshots/README.md	(revision 11160)
+++ /trunk/BNC/scripts/helpScreenshots/README.md	(revision 11160)
@@ -0,0 +1,59 @@
+# Screenshots for bnchelp.html
+
+Scripts that produce the figures of `src/bnchelp.html` with the current BNC
+build: BNC's GUI runs on a virtual X display (Xvfb + openbox), is configured
+with the example configurations, driven with xdotool and photographed with
+ImageMagick. Figure numbers are those of `bnchelp.html`.
+
+| File | Content |
+|---|---|
+| `start_display.sh` | starts (`stop`: ends) Xvfb 2600x1800 and openbox on `:99` |
+| `shoot.py` | driver: helpers, BNC instances, options |
+| `jobs.py` | one job per figure (or group of figures), click positions |
+| `viewer.py` | small text window, shows the concatenation log in Figure 15 |
+
+## Requirements
+
+- BNC built (`./bnc`), network access for the live figures
+- ImageMagick (`import`, `convert`, `montage`, `composite`), Python 3 with tkinter
+- Figure 17: Perl modules of `scripts/compareSP3Plot.pl` and gnuplot
+- Xvfb, openbox, xdotool: from the system if installed, otherwise
+  `start_display.sh` downloads the Debian/Ubuntu packages with `apt-get download`
+  (no root needed) and unpacks them into `~/.cache/bnc_screenshot_tools`
+  (`BNC_SHOT_PREFIX`); the package names are those of Ubuntu 22.04
+
+## Usage
+
+```
+scripts/helpScreenshots/start_display.sh
+scripts/helpScreenshots/shoot.py --list
+scripts/helpScreenshots/shoot.py --user bkg                 # all jobs, ~1 h
+scripts/helpScreenshots/shoot.py --user bkg fig09 fig23_26  # selected jobs
+scripts/helpScreenshots/shoot.py --user bkg --install       # and copy to src/IMG
+scripts/helpScreenshots/start_display.sh stop
+```
+
+The images are written to `/tmp/bnc_screenshots/out` (`--work`); check them
+before installing. After `--install`, rebuild BNC: the help with its images is
+compiled in (`src/bnc.qrc`, which lists every image).
+
+## Live streams
+
+Jobs marked `live` (`--list`) connect to igs-ip.net and products.igs-ip.net.
+`--user` replaces `Example:Configs` of the example configurations by that
+account; its password comes from the credentials file (`~/.netrc` or option
+`credentialsFile`), never from the command line. The example account itself
+only works on igs-ip.net. An account may be limited to about 20 simultaneous
+connections (Figures 34/35 use 20 streams). Nothing is uploaded: the upload
+panels (30, 32, 33) are shown without starting.
+
+The real-time PPP jobs (23-26, 29) run 20 minutes each for a converged
+solution.
+
+## When the GUI changes
+
+The click positions in `jobs.py` are client coordinates of the main window
+(1200x850, Qt Fusion style, English locale). If a panel's layout changes, take
+a screenshot of it (e.g. with a job that only calls `panel()`), read the new
+position and adapt the constant. Not part of the scripts: the diagrams
+(Figures 1-4, 6, 20).
Index: /trunk/BNC/scripts/helpScreenshots/jobs.py
===================================================================
--- /trunk/BNC/scripts/helpScreenshots/jobs.py	(revision 11160)
+++ /trunk/BNC/scripts/helpScreenshots/jobs.py	(revision 11160)
@@ -0,0 +1,400 @@
+"""Screenshot jobs for src/bnchelp.html, named after the figure numbers.
+
+Live jobs (docstring 'live') connect to igs-ip.net / products.igs-ip.net; with
+--user the account replaces 'Example:Configs' of the example configurations.
+Nothing is uploaded: upload panels are only shown, not started.
+
+Click positions are client coordinates of the main window (1200x850, Fusion
+style); they have to be adapted if the layout of a panel changes.
+"""
+
+import os
+import subprocess
+import time
+import urllib.request
+
+from shoot import (BNC, BNC_ROOT, BNCENV, Bnc, activate, click, crop_file, frame, key,
+                   new_window, place, saved, shot, typetext, visible_windows, wait_window,
+                   wshot, xdo)
+import shoot
+
+W, H = 1200, 850       # main window client size
+X0, Y0 = 40, 60        # main window position
+
+START = (266, 835)        # 'Start' in the bottom bar
+ADDSTREAM = (47, 835)     # 'Add Stream'
+EDITOPTIONS = (779, 121)  # 'Set Edit Options' on 'RINEX Editing & QC'
+CUSTOMTRAFO = (1132, 361) # 'Custom Trafo' on 'Upload Corrections'
+PPP1_TAB = (1123, 47)     # 'PPP (1)' tab with startTab 11 (tab bar scrolled)
+OPEN_MAP = (207, 152)     # 'Open Map' on 'PPP (4)'
+LATENCY_TAB = (196, 618)  # 'Latency' tab of the log canvas
+ADD_CASTER = (51, 77)     # buttons of the 'Add Stream' dialog
+ADD_SERIAL = (310, 77)
+LOG_CANVAS = "1182x212+10+620"   # log canvas in a main window screenshot
+
+NO_UPLOAD = ("uploadMountpointsOut", ",,,2,,,IGS20,IGS-SSR,2,,,,,,,0 byte(s),")
+
+# Throughput/latency (34, 35): observation streams of igs-ip.net; an account
+# may be limited to about 20 simultaneous connections
+MANY = ["ABMF00GLP0", "AC2300USA0", "AC2400USA0", "ACSO00USA0", "AIRA00JPN0", "ALAC00ESP0",
+        "ALBH00CAN0", "ALIC00AUS0", "AREG00PER0", "ASCG00SHN0", "AUCK00NZL0", "BAKE00CAN0",
+        "BAUT00DEU0", "BIK000KGZ0", "BILL00USA0", "BLYT00USA0", "BOAV00BRA0", "BRAZ00BRA0",
+        "BRMG00DEU0", "BRST00FRA0"]
+
+
+def main_window(job, conf, tab, logtab=0, keys=(), live=False):
+    b = Bnc(job, conf, [("startTab", tab), ("statusTab", logtab)] + list(keys), live=live)
+    place(b.main, X0, Y0, W, H)
+    return b
+
+
+def run_and_wait(b, seconds):
+    click(b.main, *START)
+    time.sleep(seconds)
+
+
+def panel(job, name, conf, tab, logtab=0, keys=(), live=False, run=0):
+    """main window showing a panel, optionally running for some seconds"""
+    b = main_window(job, conf, tab, logtab, keys, live)
+    if run:
+        run_and_wait(b, run)
+    shot(name, frame())
+    b.stop()
+
+
+# --- main window panels ---------------------------------------------------
+
+def fig05():
+    """Sections of the main window, labelled"""
+    b = main_window("fig05", "22_Empty.bnc", 0)
+    path = shot("Figure05", frame())
+    b.stop()
+    labels = [("Top menu bar", 58), ("Settings canvas", 240), ("Streams canvas", 530),
+              ("Log canvas", 740), ("Bottom menu bar", 852)]
+    blue, x = "#00a0e3", 1010
+    subprocess.run(["convert", path, "-stroke", blue, "-strokewidth", "4", "-draw",
+                    "line %d,%d %d,%d" % (x, labels[0][1], x, labels[-1][1]), path], check=True)
+    for text, y in labels:
+        lab = path + ".label.png"
+        subprocess.run(["convert", "-background", blue, "-fill", "white", "-font", "DejaVu-Sans",
+                        "-pointsize", "19", "-size", "200x34", "-gravity", "center",
+                        "label:" + text, lab], check=True)
+        subprocess.run(["composite", "-geometry", "+%d+%d" % (x - 100, y - 17), lab, path, path],
+                       check=True)
+        os.remove(lab)
+
+
+def fig07():
+    """Network panel with 'Ignore SSL authorization errors' (upper part)"""
+    b = main_window("fig07", "22_Empty.bnc", 0, keys=[("sslIgnoreErrors", 2)])
+    x, y, w, h = frame()
+    shot("Figure07", (x, y, 760, 330))
+    b.stop()
+
+
+def fig08():
+    """RINEX Observations panel (01_RinexObs)"""
+    panel("fig08", "Figure08", "01_RinexObs.bnc", 2)
+
+
+def fig09():
+    """live: RINEX Ephemeris panel, BCEP00BKG0 running"""
+    panel("fig09", "Figure09", "02_RinexEph.bnc", 3, live=True, run=90)
+
+
+def fig10():
+    """RINEX Editing & QC panel, Analyze (05_RinexQC)"""
+    panel("fig10", "Figure10", "05_RinexQC.bnc", 4)
+
+
+def fig16():
+    """SP3 Comparison panel (upper part)"""
+    b = main_window("fig16", "21_CompareSp3.bnc", 5)
+    x, y, w, h = frame()
+    shot("Figure16", (x, y, 760, 310))
+    b.stop()
+
+
+def fig18():
+    """live: Broadcast Corrections panel, SSRA00BKG1 running"""
+    panel("fig18", "Figure18", "03_BrdcCorr.bnc", 6, keys=[("corrPort", "5656")], live=True,
+          run=60)
+
+
+def fig19():
+    """live: Feed Engine panel, running"""
+    panel("fig19", "Figure19", "07_FeedEngine.bnc", 7, live=True, run=60)
+
+
+def fig21():
+    """live: Serial Output panel, running"""
+    panel("fig21", "Figure21", "06_RTK.bnc", 8, live=True, run=60, keys=[
+        ("mountPoints", "//Example:Configs@igs-ip.net:2101/FFMJ00DEU0 RTCM_3.3 DEU 50.09 8.66 no 2"),
+        ("serialMountPoint", "FFMJ00DEU0")])
+
+
+def fig22():
+    """live: Miscellaneous panel, RTCM scan of CUT000AUS0"""
+    panel("fig22", "Figure22", "22_Empty.bnc", 10, live=True, run=45, keys=[
+        ("mountPoints", "//Example:Configs@igs-ip.net:2101/CUT000AUS0 RTCM_3.3 AUS -32.00 115.89 no 2"),
+        ("miscMount", "CUT000AUS0"), ("miscIntr", "2 sec"), ("miscScanRTCM", "2"),
+        ("miscPort", "4711")])
+
+
+def fig23_26():
+    """live: real-time PPP (08_PPP), panels 1-3 (23-25), panel 4 with track
+    map (26), PPP plot (36); 20 min for a converged solution"""
+    b = main_window("fig23_26", "08_PPP.bnc", 11, 3, live=True)
+    run_and_wait(b, 1200)
+    p = shot("Figure23", frame())
+    crop_file(p, "Figure36", LOG_CANVAS)
+    click(b.main, *PPP1_TAB)       # focus on the tab bar, then arrow keys
+    key(b.main, "Right")
+    time.sleep(1)
+    shot("Figure24", frame())
+    key(b.main, "Right")
+    time.sleep(1)
+    shot("Figure25", frame())
+    key(b.main, "Right")
+    time.sleep(1)
+    click(b.main, *OPEN_MAP)
+    m = wait_window("BNC: Map View", timeout=60)
+    place(m, X0 + 270, Y0 + 110, 900, 640)
+    time.sleep(40)                 # map tiles and some positions
+    shot("Figure26", frame())
+    b.stop()
+
+
+def fig27():
+    """PPP post processing (12_PPPOsm) with track map"""
+    b = main_window("fig27", "12_PPPOsm.bnc", 14)
+    click(b.main, *OPEN_MAP)
+    m = wait_window("BNC: Map View", timeout=60)
+    place(m, X0 + 270, Y0 + 110, 900, 640)
+    time.sleep(10)
+    activate(b.main)
+    click(b.main, *START)
+    time.sleep(90)
+    activate(m)
+    shot("Figure27", frame())
+    b.stop()
+
+
+def fig28():
+    """Combine Corrections panel (17_Combi, not started)"""
+    panel("fig28", "Figure28", "17_Combi.bnc", 15, keys=[NO_UPLOAD])
+
+
+def fig29():
+    """live: INTERNAL PPP from the combination (18_CombiPPP, no upload)"""
+    panel("fig29", "Figure29", "18_CombiPPP.bnc", 11, 3, keys=[NO_UPLOAD], live=True, run=1200)
+
+
+def fig30():
+    """Upload Corrections panel (16_Upload, not started)"""
+    panel("fig30", "Figure30", "16_Upload.bnc", 16)
+
+
+def fig31():
+    """Custom Transformation Parameters dialog"""
+    b = main_window("fig31", "16_Upload.bnc", 16)
+    click(b.main, *CUSTOMTRAFO)
+    d = wait_window("Custom Transformation")
+    time.sleep(1)
+    activate(d)
+    wshot("Figure31", d)
+    b.stop()
+
+
+def fig32():
+    """Upload Corrections of the combination (17_Combi, not started)"""
+    panel("fig32", "Figure32", "17_Combi.bnc", 16)
+
+
+def fig33():
+    """Upload Ephemeris panel (19_UploadEph, not started)"""
+    panel("fig33", "Figure33", "19_UploadEph.bnc", 17)
+
+
+def fig34_35():
+    """live: throughput (34) and latency (35) of 20 igs-ip.net streams"""
+    coord = {}
+    try:   # coordinates from the public sourcetable
+        req = urllib.request.Request("http://igs-ip.net:2101/", headers={"Ntrip-Version": "Ntrip/2.0"})
+        for line in urllib.request.urlopen(req, timeout=20).read().decode("latin-1").splitlines():
+            f = line.split(";")
+            if f[0] == "STR" and f[1] in MANY:
+                coord[f[1]] = (f[8], float(f[9]), float(f[10]))
+    except OSError:
+        pass
+    mps = ";".join("//Example:Configs@igs-ip.net:2101/%s RTCM_3.3 %s %.2f %.2f no 2"
+                   % ((m,) + coord.get(m, ("XXX", 0.0, 0.0))) for m in MANY)
+    b = main_window("fig34_35", "22_Empty.bnc", 0, 1, live=True, keys=[
+        ("mountPoints", mps), ("miscMount", "ALL"), ("miscIntr", "1 min")])
+    run_and_wait(b, 150)
+    crop_file(shot("_fig34_full", frame()), "Figure34", LOG_CANVAS)
+    click(b.main, *LATENCY_TAB)
+    time.sleep(2)
+    crop_file(shot("_fig35_full", frame()), "Figure35", LOG_CANVAS)
+    b.stop()
+
+
+# --- RINEX QC and editing ---------------------------------------------------
+
+def fig11_13():
+    """RINEX QC plot windows: availability (11), multipath (12), SNR (13)"""
+    b = main_window("fig11_13", "05_RinexQC.bnc", 4)
+    click(b.main, *START)
+    wins = wait_window("BRUX00BEL_R_20250750000_01D_30S_MO", timeout=600, count=3)
+    time.sleep(5)
+    for w in [b.main] + wins:
+        xdo("windowminimize", w)
+    time.sleep(1)
+    sizes = {w: shoot.geometry(w)[2:] for w in wins}
+    avail = min(wins, key=lambda w: sizes[w][0] * sizes[w][1])   # the smallest window
+    skyplots = [w for w in wins if w != avail]                   # in order: multipath, SNR
+    for name, w in [("Figure11", avail), ("Figure12", skyplots[0]), ("Figure13", skyplots[1])]:
+        activate(w)
+        place(w, 20, 40)
+        xdo("windowsize", w, *shoot.geometry(w)[2:])   # forces a repaint
+        time.sleep(4)
+        shot(name, frame())
+        xdo("windowminimize", w)
+        time.sleep(1)
+    b.stop()
+
+
+def fig14():
+    """RINEX Editing Options dialog"""
+    b = main_window("fig14", "04_RinexConcat.bnc", 4, keys=[
+        ("reqcRnxVersion", "4"), ("reqcSampling", "30 sec"), ("reqcRunBy", "Andrea Stuerze"),
+        ("reqcUseObsTypes", "E:C1C E:L1C E:D1C E:S1C E:C5Q E:L5Q E:D5Q E:S5Q"),
+        ("reqcComment", "Extracting some Galileo Observations \\n from multi GNSS RINEX file"),
+        ("reqcNewMarkerName", "")])
+    click(b.main, *EDITOPTIONS)
+    d = wait_window("RINEX Editing Options")
+    activate(d)
+    place(d, 300, 200)
+    wshot("Figure14", d)
+    b.stop()
+
+
+def fig15():
+    """RINEX concatenation with its logfile"""
+    b = main_window("fig15", "04_RinexConcat.bnc", 4)
+    click(b.main, *START)
+    log = os.path.join(b.dir, "Output", "RinexConcat.log")
+    for _ in range(120):
+        if os.path.exists(log) and len(open(log).read().splitlines()) >= 20:
+            break
+        time.sleep(1)
+    time.sleep(3)
+    v = subprocess.Popen(["python3", os.path.join(shoot.HERE, "viewer.py"), log, "88", "22"],
+                         env=dict(shoot.XENV, LC_ALL="C.UTF-8"))
+    w = wait_window("RinexConcat.log")
+    place(w, X0 + 420, Y0 + 260)
+    shot("Figure15", frame())
+    v.terminate()
+    b.stop()
+
+
+def fig17():
+    """SP3 comparison plots (scripts/compareSP3Plot.pl, Galileo)"""
+    d = os.path.join(shoot.WORK, "run", "fig17")
+    subprocess.run(["rm", "-rf", d])
+    os.makedirs(os.path.join(d, "Output"))
+    os.symlink(os.path.join(BNC_ROOT, "Example_Configs", "Input"), os.path.join(d, "Input"))
+    subprocess.run([BNC, "--nw", "--conf", os.path.join(BNC_ROOT, "Example_Configs", "21_CompareSp3.bnc")],
+                   cwd=d, env=dict(BNCENV, QT_QPA_PLATFORM="offscreen"), capture_output=True, timeout=600)
+    log = os.path.join(d, "Output", "Sp3Comparison.log")
+    subprocess.run(["perl", "-I", os.path.join(BNC_ROOT, "scripts"),
+                    os.path.join(BNC_ROOT, "scripts", "compareSP3Plot.pl"), "--keepImages",
+                    "--plotTypes", "RAO,CLK", "-l", log], cwd=d, stdin=subprocess.DEVNULL,
+                   capture_output=True, timeout=600)
+    pngs = [os.path.join(d, "Sp3Comparison_%s_E.png" % t) for t in ("RADIAL", "ALONG", "OUT", "CLKRED")]
+    path = os.path.join(shoot.OUT, "Figure17.png")
+    subprocess.run(["montage"] + pngs + ["-tile", "2x2", "-geometry", "+10+10", "-background",
+                    "white", path], check=True)
+    subprocess.run(["convert", path, "-bordercolor", "black", "-border", "1", path], check=True)
+    saved(path)
+
+
+# --- Add Stream dialogs ------------------------------------------------------
+
+CASTER_KEYS = [("casterUrlList", "http://Example:Configs@igs-ip.net:2101"), ("ntripVersion", "2")]
+
+
+def add_stream_dialog(job, button, keys=()):
+    b = main_window(job, "22_Empty.bnc", 0, keys=keys)
+    click(b.main, *ADDSTREAM)
+    d = wait_window("Add Stream")
+    time.sleep(1.5)
+    activate(d)
+    click(d, *button)
+    return b
+
+
+def caster_dialog(job):
+    b = add_stream_dialog(job, ADD_CASTER, keys=CASTER_KEYS)
+    d = wait_window("Add Streams from Caster")
+    activate(d)
+    place(d, 100, 100, 1400, 830)
+    return b, d
+
+
+def fig37():
+    """Add Stream dialog"""
+    b = main_window("fig37", "22_Empty.bnc", 0)
+    click(b.main, *ADDSTREAM)
+    d = wait_window("Add Stream")
+    activate(d)
+    place(d, 300, 200)
+    wshot("Figure37", d)
+    b.stop()
+
+
+def fig38():
+    """Casters table (Select Broadcaster)"""
+    b, d = caster_dialog("fig38")
+    time.sleep(1.5)
+    click(d, 1356, 23)             # 'Show' casters table
+    w = wait_window("Select Broadcaster", timeout=60)
+    time.sleep(3)
+    activate(w)
+    place(w, 150, 150, 960, 900)
+    time.sleep(1)
+    wshot("Figure38", w)
+    b.stop()
+
+
+def fig39_40():
+    """Sourcetable of igs-ip.net (39) and map of its streams (40)"""
+    b, d = caster_dialog("fig39_40")
+    click(d, 1176, 806)            # 'Get table'
+    time.sleep(8)
+    wshot("Figure39", d)
+    before = visible_windows()
+    click(d, 1091, 806)            # 'Map'
+    m = new_window(before)
+    activate(m)
+    place(m, 200, 150, 1360, 900)
+    time.sleep(3)
+    wshot("Figure40", m)
+    b.stop()
+
+
+def fig41():
+    """Add Stream from Serial Port dialog"""
+    b = add_stream_dialog("fig41", ADD_SERIAL)
+    d = wait_window("Serial Port")
+    activate(d)
+    for ii, text in enumerate(["MYROVER", "RTCM_3", "51.2", "8.52", "DEU", "/dev/ttyS0"]):
+        if ii:
+            key(d, "Tab")
+        typetext(d, text)
+    time.sleep(1)
+    wshot("Figure41", d)
+    b.stop()
+
+
+JOBS = {n: f for n, f in globals().items() if n.startswith("fig") and callable(f)}
Index: /trunk/BNC/scripts/helpScreenshots/shoot.py
===================================================================
--- /trunk/BNC/scripts/helpScreenshots/shoot.py	(revision 11160)
+++ /trunk/BNC/scripts/helpScreenshots/shoot.py	(revision 11160)
@@ -0,0 +1,293 @@
+#!/usr/bin/env python3
+"""Screenshots for src/bnchelp.html: BNC's GUI on a virtual X display.
+
+Start the display first (Xvfb 2600x1800 + openbox, see start_display.sh), then
+
+    shoot.py [options] [job ...]        jobs from jobs.py, default: all
+    shoot.py --list
+
+Options:
+    --user NAME     account for the live streams, replaces 'Example:Configs' in
+                    the example configurations; the password is taken from the
+                    credentials file (~/.netrc), never from the command line
+    --work DIR      working directory (default /tmp/bnc_screenshots); BNC runs
+                    in DIR/run/<job>, the images are written to DIR/out
+    --install       copy the produced FigureNN.png files into src/IMG
+
+The screenshots use Qt's Fusion style and an English locale; the main window
+has a client size of 1200x850. The figure numbers are those of bnchelp.html.
+Environment: BNC_SHOT_DISPLAY (default :99), BNC_SHOT_PREFIX (directory of
+the unpacked packages if xdotool is not installed, see start_display.sh).
+"""
+
+import argparse
+import glob
+import os
+import shutil
+import subprocess
+import sys
+import time
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+BNC_ROOT = os.path.dirname(os.path.dirname(HERE))
+BNC = os.path.join(BNC_ROOT, "bnc")
+DISPLAY = os.environ.get("BNC_SHOT_DISPLAY", ":99")
+PREFIX = os.environ.get("BNC_SHOT_PREFIX", os.path.expanduser("~/.cache/bnc_screenshot_tools"))
+
+if shutil.which("xdotool"):
+    XDOTOOL, XLIBS = "xdotool", ""
+else:
+    XDOTOOL = os.path.join(PREFIX, "root/usr/bin/xdotool")
+    XLIBS = os.path.join(PREFIX, "root/usr/lib/x86_64-linux-gnu")
+XENV = dict(os.environ, DISPLAY=DISPLAY, LD_LIBRARY_PATH=XLIBS)
+BNCENV = dict(os.environ, DISPLAY=DISPLAY, QT_STYLE_OVERRIDE="Fusion", LC_ALL="C.UTF-8",
+              LANG="C.UTF-8", TZ="UTC")
+
+WORK = "/tmp/bnc_screenshots"   # set by main()
+OUT = os.path.join(WORK, "out")
+USER = None
+
+
+def xdo(*args):
+    r = subprocess.run([XDOTOOL] + [str(a) for a in args], env=XENV, capture_output=True,
+                       text=True)
+    return r.stdout.strip()
+
+
+def wait_window(title, timeout=30, count=1):
+    """id of the (last) visible window whose title contains title; with
+    count > 1 the list of ids once that many exist"""
+    t0 = time.time()
+    while time.time() - t0 < timeout:
+        ids = xdo("search", "--onlyvisible", "--name", title).split()
+        if len(ids) >= count:
+            time.sleep(0.5)
+            return ids[-1] if count == 1 else ids
+        time.sleep(0.3)
+    raise RuntimeError("window '%s' not found" % title)
+
+
+def new_window(before, timeout=60):
+    """first visible window with a title that was not in before"""
+    for _ in range(timeout):
+        new = set(xdo("search", "--onlyvisible", "--name", "").split()) - before
+        new = [w for w in new if xdo("getwindowname", w)]
+        if new:
+            return new[0]
+        time.sleep(1)
+    raise RuntimeError("no new window")
+
+
+def visible_windows():
+    return set(xdo("search", "--onlyvisible", "--name", "").split())
+
+
+def geometry(wid):
+    out = xdo("getwindowgeometry", "--shell", wid)
+    g = dict(l.split("=") for l in out.splitlines())
+    return int(g["X"]), int(g["Y"]), int(g["WIDTH"]), int(g["HEIGHT"])
+
+
+def place(wid, x, y, w=None, h=None):
+    """move (and resize) a window; repeated until the window manager applied it"""
+    time.sleep(1.0)
+    for _ in range(3):
+        if w:
+            xdo("windowsize", "--sync", wid, w, h)
+        xdo("windowmove", "--sync", wid, x, y)
+        time.sleep(0.5)
+        gx, gy, gw, gh = geometry(wid)
+        if (not w or (gw, gh) == (w, h)) and abs(gx - x) < 30 and abs(gy - y) < 40:
+            break
+
+
+def click(wid, x, y, button=1):
+    """click at x, y relative to the client area of window wid"""
+    xdo("mousemove", "--window", wid, x, y)
+    time.sleep(0.2)
+    xdo("click", button)
+    time.sleep(0.8)
+
+
+def key(wid, *keys):
+    xdo("windowactivate", "--sync", wid)
+    for k in keys:
+        xdo("key", "--clearmodifiers", k)
+        time.sleep(0.2)
+
+
+def typetext(wid, text):
+    xdo("windowactivate", "--sync", wid)
+    xdo("type", "--delay", "40", text)
+
+
+def minimize(wid):
+    xdo("windowminimize", wid)
+    time.sleep(0.5)
+
+
+def activate(wid):
+    xdo("windowactivate", "--sync", wid)
+    time.sleep(0.5)
+
+
+def pointer_away():
+    w, h = xdo("getdisplaygeometry").split()
+    xdo("mousemove", int(w) - 10, int(h) - 10)
+    time.sleep(0.5)
+
+
+def saved(path):
+    size = subprocess.run(["identify", "-format", "%wx%h", path], capture_output=True,
+                          text=True).stdout
+    print("  saved", os.path.relpath(path, WORK), size)
+    return path
+
+
+def frame():
+    """screen rectangle of everything shown (windows incl. title bars) =
+    bounding box of the non-black part of the root window"""
+    raw = os.path.join(OUT, "_bbox.png")
+    subprocess.run(["import", "-window", "root", raw], env=XENV, check=True)
+    bb = subprocess.run(["convert", raw, "-format", "%@", "info:"], capture_output=True,
+                        text=True).stdout.strip()            # WxH+X+Y
+    wh, x, y = bb.split("+")
+    w, h = wh.split("x")
+    return int(x), int(y), int(w), int(h)
+
+
+def shot(name, crop=None):
+    """screenshot of all windows shown, or of crop=(x, y, w, h) in screen
+    coordinates (e.g. from frame()); saved as OUT/<name>.png"""
+    pointer_away()
+    raw = os.path.join(OUT, "_root.png")
+    subprocess.run(["import", "-window", "root", raw], env=XENV, check=True)
+    path = os.path.join(OUT, name + ".png")
+    if crop:
+        x, y, w, h = crop
+        subprocess.run(["convert", raw, "-crop", "%dx%d+%d+%d" % (w, h, x, y), "+repage", path],
+                       check=True)
+    else:
+        subprocess.run(["convert", raw, "-trim", "+repage", path], check=True)
+    return saved(path)
+
+
+def wshot(name, wid):
+    """screenshot of one window incl. its window manager frame"""
+    pointer_away()
+    path = os.path.join(OUT, name + ".png")
+    subprocess.run(["import", "-frame", "-window", wid, path], env=XENV, check=True)
+    return saved(path)
+
+
+def crop_file(src, name, geometry):
+    """crop an image file, geometry 'WxH+X+Y'"""
+    path = os.path.join(OUT, name + ".png")
+    subprocess.run(["convert", src, "-crop", geometry, "+repage", path], check=True)
+    return saved(path)
+
+
+class Bnc:
+    """BNC instance in its own working directory (Input link, Output dir),
+    started with a copy of an example configuration"""
+
+    def __init__(self, job, conf=None, keys=(), live=False):
+        self.dir = os.path.join(WORK, "run", job)
+        shutil.rmtree(self.dir, ignore_errors=True)
+        os.makedirs(os.path.join(self.dir, "Output"))
+        os.symlink(os.path.join(BNC_ROOT, "Example_Configs", "Input"), os.path.join(self.dir, "Input"))
+        cmd = [BNC]
+        if conf:
+            text = open(os.path.join(BNC_ROOT, "Example_Configs", conf), errors="replace").read()
+            if live and USER:
+                text = text.replace("Example:Configs@", USER + "@")
+            open(os.path.join(self.dir, conf), "w").write(text)
+            cmd += ["--conf", conf]
+        for k, v in keys:
+            if live and USER:
+                v = str(v).replace("Example:Configs@", USER + "@")
+            cmd += ["-key", k, str(v)]
+        self.out = open(os.path.join(self.dir, "bnc.out"), "w")
+        self.p = subprocess.Popen(cmd, cwd=self.dir, env=BNCENV, stdout=self.out,
+                                  stderr=subprocess.STDOUT)
+        self.main = wait_window("BKG Ntrip Client")
+
+    def stop(self):
+        if self.p.poll() is None:
+            self.p.terminate()
+            try:
+                self.p.wait(10)
+            except subprocess.TimeoutExpired:
+                self.p.kill()
+        time.sleep(1)
+
+
+def cleanup():
+    """end leftover BNC instances of this driver: processes with a window on
+    the virtual display that run BNC_ROOT/bnc with this DISPLAY (nothing else)"""
+    pids = set()
+    for w in xdo("search", "--name", "").split():
+        p = xdo("getwindowpid", w)
+        if p:
+            pids.add(int(p))
+    for p in pids:
+        try:
+            cmd = open("/proc/%d/cmdline" % p, "rb").read().split(b"\0")[0].decode()
+            env = open("/proc/%d/environ" % p, "rb").read().split(b"\0")
+        except OSError:
+            continue
+        if cmd == BNC and ("DISPLAY=" + DISPLAY).encode() in env:
+            os.kill(p, 15)
+            print("  ended leftover BNC", p)
+    time.sleep(1)
+
+
+def main():
+    global WORK, OUT, USER
+    ap = argparse.ArgumentParser(description=__doc__,
+                                 formatter_class=argparse.RawDescriptionHelpFormatter)
+    ap.add_argument("jobs", nargs="*")
+    ap.add_argument("--user")
+    ap.add_argument("--work", default=WORK)
+    ap.add_argument("--install", action="store_true")
+    ap.add_argument("--list", action="store_true")
+    args = ap.parse_args()
+
+    # jobs.py imports this file as module 'shoot' (a second instance besides
+    # __main__): the settings have to be made there
+    sys.path.insert(0, HERE)
+    import shoot as S
+    S.WORK, S.USER = os.path.abspath(args.work), args.user
+    S.OUT = os.path.join(S.WORK, "out")
+    os.makedirs(S.OUT, exist_ok=True)
+    WORK, OUT, USER = S.WORK, S.OUT, S.USER
+    import jobs
+    if args.list:
+        for n in sorted(jobs.JOBS):
+            print("%-10s %s" % (n, (jobs.JOBS[n].__doc__ or "").strip().splitlines()[0]))
+        return 0
+    if not os.path.exists("/tmp/.X11-unix/X" + DISPLAY.lstrip(":")):
+        sys.exit("display %s not running, start it with start_display.sh" % DISPLAY)
+
+    failed = []
+    for n in args.jobs or sorted(jobs.JOBS):
+        print("job", n)
+        S.cleanup()
+        try:
+            jobs.JOBS[n]()
+        except Exception as e:
+            print("  FAILED:", e)
+            failed.append(n)
+        S.cleanup()
+
+    if args.install:
+        for f in sorted(glob.glob(os.path.join(OUT, "Figure[0-9][0-9].png"))):
+            shutil.copy(f, os.path.join(BNC_ROOT, "src", "IMG"))
+            print("  installed", os.path.basename(f))
+    if failed:
+        print("failed:", " ".join(failed))
+    return 1 if failed else 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
Index: /trunk/BNC/scripts/helpScreenshots/start_display.sh
===================================================================
--- /trunk/BNC/scripts/helpScreenshots/start_display.sh	(revision 11160)
+++ /trunk/BNC/scripts/helpScreenshots/start_display.sh	(revision 11160)
@@ -0,0 +1,53 @@
+#!/bin/sh
+# Start a virtual X display with a window manager for shoot.py:
+# Xvfb (2600x1800) and openbox on $BNC_SHOT_DISPLAY (default :99).
+#
+# Xvfb, openbox and xdotool are taken from the system if installed. Otherwise
+# their Debian/Ubuntu packages are downloaded (apt-get download, no root needed)
+# and unpacked into $BNC_SHOT_PREFIX (default ~/.cache/bnc_screenshot_tools);
+# shoot.py finds xdotool there via the same variable.
+#
+#   scripts/helpScreenshots/start_display.sh          start
+#   scripts/helpScreenshots/start_display.sh stop     end Xvfb and openbox again
+
+set -e
+DISP=${BNC_SHOT_DISPLAY:-:99}
+PREFIX=${BNC_SHOT_PREFIX:-$HOME/.cache/bnc_screenshot_tools}
+PIDS=${PREFIX}/display${DISP#:}.pids
+
+if [ "$1" = "stop" ]; then
+  [ -f "$PIDS" ] && kill $(cat "$PIDS") 2>/dev/null || true
+  rm -f "$PIDS"
+  echo "display $DISP stopped"
+  exit 0
+fi
+
+XVFB=$(command -v Xvfb || true)
+OPENBOX=$(command -v openbox || true)
+XDOTOOL=$(command -v xdotool || true)
+LIBS=""
+if [ -z "$XVFB" ] || [ -z "$OPENBOX" ] || [ -z "$XDOTOOL" ]; then
+  if [ ! -x "$PREFIX/root/usr/bin/xdotool" ]; then
+    echo "downloading Xvfb, openbox and xdotool packages to $PREFIX ..."
+    mkdir -p "$PREFIX/debs" "$PREFIX/root"
+    (cd "$PREFIX/debs" && apt-get download xvfb xdotool libxdo3 openbox libobrender32v5 \
+       libobt2v5 libimlib2 libstartup-notification0 libxcb-util1)
+    for f in "$PREFIX"/debs/*.deb; do dpkg -x "$f" "$PREFIX/root"; done
+  fi
+  [ -n "$XVFB" ] || XVFB=$PREFIX/root/usr/bin/Xvfb
+  [ -n "$OPENBOX" ] || OPENBOX=$PREFIX/root/usr/bin/openbox
+  LIBS=$PREFIX/root/usr/lib/x86_64-linux-gnu
+fi
+
+mkdir -p "$PREFIX"
+"$XVFB" "$DISP" -screen 0 2600x1800x24 -nolisten tcp > "$PREFIX/xvfb.log" 2>&1 &
+echo $! > "$PIDS"
+for i in 1 2 3 4 5 6 7 8 9 10; do
+  [ -e "/tmp/.X11-unix/X${DISP#:}" ] && break
+  sleep 1
+done
+env DISPLAY="$DISP" LD_LIBRARY_PATH="$LIBS" XDG_CONFIG_DIRS="$PREFIX/root/etc/xdg:/etc/xdg" \
+    XDG_DATA_DIRS="$PREFIX/root/usr/share:/usr/share" "$OPENBOX" > "$PREFIX/openbox.log" 2>&1 &
+echo $! >> "$PIDS"
+sleep 2
+echo "display $DISP running (Xvfb + openbox); stop with: $0 stop"
Index: /trunk/BNC/scripts/helpScreenshots/viewer.py
===================================================================
--- /trunk/BNC/scripts/helpScreenshots/viewer.py	(revision 11160)
+++ /trunk/BNC/scripts/helpScreenshots/viewer.py	(revision 11160)
@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+"""Simple text viewer window (stands in for a text editor in screenshots)
+
+    viewer.py <file> [columns] [rows]
+"""
+import os
+import sys
+import tkinter as tk
+
+path = sys.argv[1]
+root = tk.Tk()
+root.title(os.path.basename(path))
+text = tk.Text(root, width=int(sys.argv[2]) if len(sys.argv) > 2 else 90,
+               height=int(sys.argv[3]) if len(sys.argv) > 3 else 22,
+               font=("DejaVu Sans Mono", 9), bg="white", relief="flat", padx=6, pady=4)
+text.insert("1.0", open(path, errors="replace").read())
+text.config(state="disabled")
+text.pack(fill="both", expand=True)
+root.mainloop()
Index: /trunk/BNC/src/bnc.qrc
===================================================================
--- /trunk/BNC/src/bnc.qrc	(revision 11159)
+++ /trunk/BNC/src/bnc.qrc	(revision 11160)
@@ -50,4 +50,5 @@
     <file>IMG/Figure39.png</file>
     <file>IMG/Figure40.png</file>
+    <file>IMG/Figure41.png</file>
 </qresource>
 <qresource>
Index: /trunk/BNC/src/bnccredentials.cpp
===================================================================
--- /trunk/BNC/src/bnccredentials.cpp	(revision 11159)
+++ /trunk/BNC/src/bnccredentials.cpp	(revision 11160)
@@ -155,18 +155,23 @@
       continue;
     }
-    bool ok = tokens.size() == 4;
+    // <host> <port|*> <user|-> <password>, or <host> <user> <password> for any port
+    QStringList hlp = tokens;
+    if (hlp.size() == 3) {
+      hlp.insert(1, "*");
+    }
+    bool ok = hlp.size() == 4;
     t_entry entry;
-    entry.host = tokens.value(0);
-    if (tokens.value(1) == "*") {
+    entry.host = hlp.value(0);
+    if (hlp.value(1) == "*") {
       entry.port = 0;
     }
     else {
       bool okPort;
-      entry.port = tokens.value(1).toInt(&okPort);
+      entry.port = hlp.value(1).toInt(&okPort);
       ok = ok && okPort && entry.port > 0;
     }
-    entry.hasUser = tokens.value(2) != "-";
-    entry.user = entry.hasUser ? tokens.value(2) : QString();
-    entry.password = tokens.value(3);
+    entry.hasUser = hlp.value(2) != "-";
+    entry.user = entry.hasUser ? hlp.value(2) : QString();
+    entry.password = hlp.value(3);
     if (ok) {
       file.entries.append(entry);
@@ -174,5 +179,5 @@
     else {
       message(fileName + QString(":%1").arg(lineNumbers[iLine]),
-              QString("Credentials file %1, line %2: not <host> <port> <user> <password>, ignored")
+              QString("Credentials file %1, line %2: not <host> [<port>] <user> <password>, ignored")
               .arg(fileName).arg(lineNumbers[iLine]));
     }
Index: /trunk/BNC/src/bnccredentials.h
===================================================================
--- /trunk/BNC/src/bnccredentials.h	(revision 11159)
+++ /trunk/BNC/src/bnccredentials.h	(revision 11160)
@@ -11,4 +11,5 @@
 //   netrc:  machine <host>[:<port>] login <user> password <password>
 //   BNC:    <host> <port|*> <user|-> <password>      (one entry per line)
+//           <host> <user> <password>                  (any port)
 // Tokens are separated by white space, may be quoted ("..."), '#' starts a
 // comment. An entry for the exact port is preferred, the netrc 'default'
Index: /trunk/BNC/src/bnchelp.html
===================================================================
--- /trunk/BNC/src/bnchelp.html	(revision 11159)
+++ /trunk/BNC/src/bnchelp.html	(revision 11160)
@@ -414,9 +414,14 @@
     <tr>
       <td>9</td>
+      <td>BNC saving Broadcast Ephemeris received from an RTCM Version 3 stream in hourly RINEX Version 4 Navigation files</td>
+      <td>2.5</td>
+    </tr>
+    <tr>
+      <td>10</td>
       <td>Example for creating RINEX quality check analysis graphics output with BNC</td>
       <td>2.6.6</td>
     </tr>
     <tr>
-      <td>10</td>
+      <td>11</td>
       <td>Example for satellite availability, elevation and PDOP plots as a result of a RINEX quality check analysis
         with BNC</td>
@@ -424,30 +429,30 @@
     </tr>
     <tr>
-      <td>11</td>
+      <td>12</td>
       <td>Sky plot examples for multipath, part of RINEX quality check analysis with BNC</td>
       <td>2.6.6</td>
     </tr>
     <tr>
-      <td>12</td>
+      <td>13</td>
       <td>Sky plot examples for signal-to-noise ratio, part of RINEX quality check analysis with BNC</td>
       <td>2.6.6</td>
     </tr>
     <tr>
-      <td>13</td>
+      <td>14</td>
       <td>Example for BNC's 'RINEX Editing Options' window</td>
       <td>2.6.8</td>
     </tr>
     <tr>
-      <td>14</td>
+      <td>15</td>
       <td>Example for RINEX file concatenation with BNC</td>
       <td>2.6.8</td>
     </tr>
     <tr>
-      <td>15</td>
+      <td>16</td>
       <td>Example for comparing two SP3 files with satellite orbit and clock data using BNC</td>
       <td>2.7</td>
     </tr>
     <tr>
-      <td>16</td>
+      <td>17</td>
       <td>Graphical results from an example comparison of two SP3 files with satellite orbit and clock data using BNC
       </td>
@@ -455,15 +460,15 @@
     </tr>
     <tr>
-      <td>17</td>
+      <td>18</td>
       <td>Example for pulling, saving and output of Broadcast Corrections using BNC</td>
       <td>2.8.3</td>
     </tr>
     <tr>
-      <td>18</td>
+      <td>19</td>
       <td>Synchronized BNC output via IP port to feed a GNSS real-time engine</td>
       <td>2.9</td>
     </tr>
     <tr>
-      <td>19</td>
+      <td>20</td>
       <td>Flowcharts, BNC forwarding a stream to a serially connected receiver; sending NMEA sentences is mandatory for
         VRS streams</td>
@@ -471,5 +476,5 @@
     </tr>
     <tr>
-      <td>20</td>
+      <td>21</td>
       <td>BNC pulling a RTCM Version 3 stream to feed a serial connected receiver with observations from a nearby
         reference station for conventional RTK</td>
@@ -477,15 +482,15 @@
     </tr>
     <tr>
-      <td>21</td>
+      <td>22</td>
       <td>RTCM message numbers, latencies and observation types logged by BNC</td>
       <td>2.12</td>
     </tr>
     <tr>
-      <td>22</td>
+      <td>23</td>
       <td>Real-time Precise Point Positioning with BNC, PPP Panel 1</td>
       <td>2.13.1</td>
     </tr>
     <tr>
-      <td>23</td>
+      <td>24</td>
       <td>Precise Point Positioning with BNC, PPP Panel 2</td>
       <td>2.13.2</td>
@@ -498,19 +503,24 @@
     <tr>
       <td>26</td>
-      <td>Precise Point Positioning with BNC with track of positions using OpenStreetMap, PPP Panel 4</td>
+      <td>Real-time Precise Point Positioning with BNC with track of positions using OpenStreetMap, PPP Panel 4</td>
       <td>2.13.4.3</td>
     </tr>
     <tr>
       <td>27</td>
+      <td>Precise Point Positioning with BNC in post processing mode with track of positions using OpenStreetMap, PPP Panel 4</td>
+      <td>2.13.4.3</td>
+    </tr>
+    <tr>
+      <td>28</td>
       <td>BNC combining Broadcast Correction streams</td>
       <td>2.14</td>
     </tr>
     <tr>
-      <td>28</td>
+      <td>29</td>
       <td>'INTERNAL' PPP with BNC using a combination of Broadcast Corrections</td>
       <td>2.14</td>
     </tr>
     <tr>
-      <td>29</td>
+      <td>30</td>
       <td>BNC producing Broadcast Corrections from incoming precise orbits and clocks and uploading them to an Ntrip
         Broadcaster</td>
@@ -518,15 +528,15 @@
     </tr>
     <tr>
-      <td>30</td>
+      <td>31</td>
       <td>Setting BNC's Custom Transformation Parameters window</td>
       <td>2.15.3</td>
     </tr>
     <tr>
-      <td>31</td>
+      <td>32</td>
       <td>BNC uploading a combined Broadcast Correction stream</td>
       <td>2.15.12</td>
     </tr>
     <tr>
-      <td>32</td>
+      <td>33</td>
       <td>BNC producing Broadcast Ephemeris streams from globally distributed RTCM streams; upload in RTCM format to an
         Ntrip Broadcaster</td>
@@ -534,40 +544,40 @@
     </tr>
     <tr>
-      <td>33</td>
+      <td>34</td>
       <td>Bandwidth consumption of RTCM streams received by BNC</td>
       <td>2.18.2</td>
     </tr>
     <tr>
-      <td>34</td>
+      <td>35</td>
       <td>Latency of RTCM streams received by BNC</td>
       <td>2.18.3</td>
     </tr>
     <tr>
-      <td>35</td>
+      <td>36</td>
       <td>Example for time series plot of displacements produced by BNC</td>
       <td>2.18.4</td>
     </tr>
     <tr>
-      <td>36</td>
+      <td>37</td>
       <td>Steam input communication links accepted by BNC</td>
       <td>2.19</td>
     </tr>
     <tr>
-      <td>37</td>
+      <td>38</td>
       <td>BNC's 'Select Broadcaster' table</td>
       <td>2.19.1.1.2</td>
     </tr>
     <tr>
-      <td>38</td>
+      <td>39</td>
       <td>Broadcaster source-table shown by BNC</td>
       <td>2.19.1.1.4</td>
     </tr>
     <tr>
-      <td>39</td>
+      <td>40</td>
       <td>Stream distribution map shown by BNC as derived from Ntrip Broadcaster source-table</td>
       <td>2.19.1.1.6</td>
     </tr>
     <tr>
-      <td>40</td>
+      <td>41</td>
       <td>BNC configuration for pulling a stream via serial port</td>
       <td>2.19.1.4</td>
@@ -2708,5 +2718,6 @@
       not used.</li>
     <li>BNC: one entry per line '&lt;host&gt; &lt;port&gt; &lt;user&gt; &lt;password&gt;', port '*' for any port,
-      user '-' for an entry without user (Ntrip Version 1 upload).</li>
+      user '-' for an entry without user (Ntrip Version 1 upload), or '&lt;host&gt; &lt;user&gt; &lt;password&gt;'
+      for any port.</li>
   </ul>
     Several users per host are distinguished by the user name. An entry for the exact port is preferred over one
@@ -3518,4 +3529,7 @@
   </p>
 
+  <p><img src="IMG/Figure09.png" width=1000 /></p>
+  <p>Figure 9: BNC saving Broadcast Ephemeris received from an RTCM Version 3 stream in hourly RINEX Version 4 Navigation files</p>
+
   <p>
   <h4 id="ephdir">2.5.1 Directory - optional</h4>
@@ -4069,16 +4083,16 @@
     An example configuration with plot results can be seen below.
   </p>
-  <p><img src="IMG/Figure09.png" width=1000 /></p>
-  <p>Figure 9: Example for creating RINEX quality check analysis graphics output with BNC</p>
-
   <p><img src="IMG/Figure10.png" width=1000 /></p>
-  <p>Figure 10: Example for satellite availability, elevation and PDOP plots as a result of a RINEX quality check
+  <p>Figure 10: Example for creating RINEX quality check analysis graphics output with BNC</p>
+
+  <p><img src="IMG/Figure11.png" width=1000 /></p>
+  <p>Figure 11: Example for satellite availability, elevation and PDOP plots as a result of a RINEX quality check
     analysis with BNC</p>
 
-  <p><img src="IMG/Figure11.png" width=1000 /></p>
-  <p>Figure 11: Sky plot examples for multipath, part of RINEX quality check analysis with BNC</p>
-
   <p><img src="IMG/Figure12.png" width=1000 /></p>
-  <p>Figure 12: Sky plot examples for signal-to-noise ratio, part of RINEX quality check analysis with BNC</p>
+  <p>Figure 12: Sky plot examples for multipath, part of RINEX quality check analysis with BNC</p>
+
+  <p><img src="IMG/Figure13.png" width=1000 /></p>
+  <p>Figure 13: Sky plot examples for signal-to-noise ratio, part of RINEX quality check analysis with BNC</p>
 
   <p>
@@ -4153,9 +4167,9 @@
   </p>
 
-  <p><img src="IMG/Figure13.png" width=500 /></p>
-  <p>Figure 13: Example for BNC's 'RINEX Editing Options' window</p>
-
-  <p><img src="IMG/Figure14.png" width=1000 /></p>
-  <p>Figure 14: Example for RINEX file concatenation with BNC</p>
+  <p><img src="IMG/Figure14.png" width=500 /></p>
+  <p>Figure 14: Example for BNC's 'RINEX Editing Options' window</p>
+
+  <p><img src="IMG/Figure15.png" width=1000 /></p>
+  <p>Figure 15: Example for RINEX file concatenation with BNC</p>
 
   <p>
@@ -4449,6 +4463,6 @@
     sampled files: comparing two daily files with 30-second sampling (2880 epochs) takes a few seconds.
   </p>
-  <p><img src="IMG/Figure15.png" width=800 /></p>
-  <p>Figure 15: Example for comparing two SP3 files with satellite orbit and clock data using BNC</p>
+  <p><img src="IMG/Figure16.png" width=800 /></p>
+  <p>Figure 16: Example for comparing two SP3 files with satellite orbit and clock data using BNC</p>
 
   <p>
@@ -4658,6 +4672,6 @@
   </table>
   <br>
-  <p><img src="IMG/Figure16.png" width=1000 /></p>
-  <p>Figure 16: Graphical results from an example comparison of two SP3 files with satellite orbit and clock data using
+  <p><img src="IMG/Figure17.png" width=1000 /></p>
+  <p>Figure 17: Graphical results from an example comparison of two SP3 files with satellite orbit and clock data using
     BNC</p>
 
@@ -5181,6 +5195,6 @@
   </p>
 
-  <p><img src="IMG/Figure17.png" width=1000 /></p>
-  <p>Figure 17: Example for pulling, saving and output of Broadcast Corrections using BNC</p>
+  <p><img src="IMG/Figure18.png" width=1000 /></p>
+  <p>Figure 18: Example for pulling, saving and output of Broadcast Corrections using BNC</p>
 
   <p>
@@ -5671,6 +5685,6 @@
     to feed a GNSS engine via IP port output.
   </p>
-  <p><img src="IMG/Figure18.png" width=1000 /></p>
-  <p>Figure 18: Synchronized BNC output via IP port to feed a GNSS real-time engine</p>
+  <p><img src="IMG/Figure19.png" width=1000 /></p>
+  <p>Figure 19: Synchronized BNC output via IP port to feed a GNSS real-time engine</p>
 
   <p>
@@ -5785,6 +5799,6 @@
   </p>
 
-  <p><img src="IMG/Figure19.png" width=1000 /></p>
-  <p>Figure 19: Flowcharts, BNC forwarding a stream to a serially connected receiver; sending NMEA sentences is
+  <p><img src="IMG/Figure20.png" width=1000 /></p>
+  <p>Figure 20: Flowcharts, BNC forwarding a stream to a serially connected receiver; sending NMEA sentences is
     mandatory for VRS streams</p>
 
@@ -5795,6 +5809,6 @@
   </p>
 
-  <p><img src="IMG/Figure20.png" width=1000 /></p>
-  <p>Figure 20: BNC pulling a RTCM Version 3 stream to feed a serial connected receiver with observations from a nearby
+  <p><img src="IMG/Figure21.png" width=1000 /></p>
+  <p>Figure 21: BNC pulling a RTCM Version 3 stream to feed a serial connected receiver with observations from a nearby
     reference station for conventional RTK</p>
 
@@ -6088,6 +6102,6 @@
     recorded every 2 seconds.
   </p>
-  <p><img src="IMG/Figure21.png" width=1000 /></p>
-  <p>Figure 21: RTCM message numbers, latencies and observation types logged by BNC</p>
+  <p><img src="IMG/Figure22.png" width=1000 /></p>
+  <p>Figure 22: RTCM message numbers, latencies and observation types logged by BNC</p>
 
 
@@ -6289,6 +6303,6 @@
     post processing PPP.
   </p>
-  <p><img src="IMG/Figure22.png" width=1000 /></p>
-  <p>Figure 22: Real-time Precise Point Positioning with BNC, PPP Panel 1</p>
+  <p><img src="IMG/Figure23.png" width=1000 /></p>
+  <p>Figure 23: Real-time Precise Point Positioning with BNC, PPP Panel 1</p>
 
   <p>
@@ -7419,6 +7433,6 @@
   </p>
 
-  <p><img src="IMG/Figure23.png" width=1000 /></p>
-  <p>Figure 23: Precise Point Positioning with BNC, PPP Panel 2</p>
+  <p><img src="IMG/Figure24.png" width=1000 /></p>
+  <p>Figure 24: Precise Point Positioning with BNC, PPP Panel 2</p>
 
   <p>
@@ -8092,5 +8106,8 @@
 
   <p><img src="IMG/Figure26.png" width=1000 /></p>
-  <p>Figure 26: Precise Point Positioning with BNC with track of positions using OpenStreetMap, PPP Panel 4.</p>
+  <p>Figure 26: Real-time Precise Point Positioning with BNC with track of positions using OpenStreetMap, PPP Panel 4</p>
+
+  <p><img src="IMG/Figure27.png" width=1000 /></p>
+  <p>Figure 27: Precise Point Positioning with BNC in post processing mode with track of positions using OpenStreetMap, PPP Panel 4</p>
 
 
@@ -8259,6 +8276,6 @@
     The following screenshot shows an example setup of BNC when combining several Broadcast Correction streams.
   </p>
-  <p><img src="IMG/Figure27.png" width=1000 /></p>
-  <p>Figure 27: BNC combining Broadcast Correction streams</p>
+  <p><img src="IMG/Figure28.png" width=1000 /></p>
+  <p>Figure 28: BNC combining Broadcast Correction streams</p>
 
   <p>
@@ -8453,6 +8470,6 @@
     domain.
   </p>
-  <p><img src="IMG/Figure28.png" width=1000 /></p>
-  <p>Figure 28: 'INTERNAL' PPP with BNC using a combination of Broadcast Corrections</p>
+  <p><img src="IMG/Figure29.png" width=1000 /></p>
+  <p>Figure 29: 'INTERNAL' PPP with BNC using a combination of Broadcast Corrections</p>
 
   <p>
@@ -9066,6 +9083,6 @@
     Required Broadcast Ephemeris are received via stream 'BCEP00BKG0'.
   </p>
-  <p><img src="IMG/Figure29.png" width=1000 /></p>
-  <p>Figure 29: BNC producing Broadcast Corrections from incoming precise orbits and clocks and uploading them to an
+  <p><img src="IMG/Figure30.png" width=1000 /></p>
+  <p>Figure 30: BNC producing Broadcast Corrections from incoming precise orbits and clocks and uploading them to an
     Ntrip Broadcaster</p>
 
@@ -9342,6 +9359,6 @@
     IGS20/ITRF2020 into your own target system.
   </p>
-  <p><img src="IMG/Figure30.png" width=700 /></p>
-  <p>Figure 30: Setting BNC's Custom Transformation Parameters window</p>
+  <p><img src="IMG/Figure31.png" width=700 /></p>
+  <p>Figure 31: Setting BNC's Custom Transformation Parameters window</p>
 
   <p>
@@ -9821,6 +9838,6 @@
     received via stream 'BCEP00BKG0'.
   </p>
-  <p><img src="IMG/Figure31.png" width=1000 /></p>
-  <p>Figure 31: BNC uploading a combined Broadcast Correction stream</p>
+  <p><img src="IMG/Figure32.png" width=1000 /></p>
+  <p>Figure 32: BNC uploading a combined Broadcast Correction stream</p>
   <p></p>
 
@@ -9910,6 +9927,6 @@
   every 5 seconds.
   </p>
-  <p><img src="IMG/Figure32.png" width=1000 /></p>
-  <p>Figure 32: BNC producing Broadcast Ephemeris streams from globally distributed RTCM streams; upload in RTCM format
+  <p><img src="IMG/Figure33.png" width=1000 /></p>
+  <p>Figure 33: BNC producing Broadcast Ephemeris streams from globally distributed RTCM streams; upload in RTCM format
     to an Ntrip Broadcaster</p>
 
@@ -10123,6 +10140,6 @@
     The following figure shows an example for the bandwidth consumption of incoming streams.
   </p>
-  <p><img src="IMG/Figure33.png" width=1000 /></p>
-  <p>Figure 33: Bandwidth consumption of RTCM streams received by BNC</p>
+  <p><img src="IMG/Figure34.png" width=1000 /></p>
+  <p>Figure 34: Bandwidth consumption of RTCM streams received by BNC</p>
   <p>
   <h4 id="latency">2.19.3 Latency</h4>
@@ -10135,6 +10152,6 @@
     of incoming streams.
   </p>
-  <p><img src="IMG/Figure34.png" width=1000 /></p>
-  <p>Figure 34: Latency of RTCM streams received by BNC</p>
+  <p><img src="IMG/Figure35.png" width=1000 /></p>
+  <p>Figure 35: Latency of RTCM streams received by BNC</p>
 
   <p>
@@ -10152,6 +10169,6 @@
     coordinate displacements.
   </p>
-  <p><img src="IMG/Figure35.png" width=1000 /></p>
-  <p>Figure 35: Example for time series plot of displacements produced by BNC</p>
+  <p><img src="IMG/Figure36.png" width=1000 /></p>
+  <p>Figure 36: Example for time series plot of displacements produced by BNC</p>
 
   <p>
@@ -10163,6 +10180,6 @@
     to select one of several input communication links, see figure below.
   </p>
-  <p><img src="IMG/Figure36.png" width=400 /></p>
-  <p>Figure 36: Steam input communication links accepted by BNC</p>
+  <p><img src="IMG/Figure37.png" width=400 /></p>
+  <p>Figure 37: Steam input communication links accepted by BNC</p>
 
   <p>
@@ -10202,6 +10219,6 @@
     A window opens which allows selecting a broadcaster for stream retrieval, see figure below.
   </p>
-  <p><img src="IMG/Figure37.png" width=1000 /></p>
-  <p>Figure 37: BNC's 'Select Broadcaster' table</p>
+  <p><img src="IMG/Figure38.png" width=1000 /></p>
+  <p>Figure 38: BNC's 'Select Broadcaster' table</p>
 
   <p>
@@ -10346,6 +10363,6 @@
     retrieving streams from different casters.
   </p>
-  <p><img src="IMG/Figure38.png" width=1000 /></p>
-  <p>Figure 38: Broadcaster source-table shown by BNC</p>
+  <p><img src="IMG/Figure39.png" width=1000 /></p>
+  <p>Figure 39: Broadcaster source-table shown by BNC</p>
 
   <p>
@@ -10406,6 +10423,6 @@
     Left button: draw a rectangle to zoom, right button: zoom out, middle button: zoom back.
   </p>
-  <p><img src="IMG/Figure39.png" width=1000 /></p>
-  <p>Figure 39: Stream distribution map shown by BNC as derived from Ntrip Broadcaster source-table</p>
+  <p><img src="IMG/Figure40.png" width=1000 /></p>
+  <p>Figure 40: Stream distribution map shown by BNC as derived from Ntrip Broadcaster source-table</p>
 
   <p>
@@ -10519,6 +10536,6 @@
     The following figure shows a BNC example setup for pulling a stream via serial port on a Windows operating system.
   </p>
-  <p><img src="IMG/Figure40.png" width=400 /></p>
-  <p>Figure 40: BNC configuration for pulling a stream via serial port</p>
+  <p><img src="IMG/Figure41.png" width=400 /></p>
+  <p>Figure 41: BNC configuration for pulling a stream via serial port</p>
 
   <p>
Index: /trunk/BNC/src/bncwindow.cpp
===================================================================
--- /trunk/BNC/src/bncwindow.cpp	(revision 11159)
+++ /trunk/BNC/src/bncwindow.cpp	(revision 11160)
@@ -1562,5 +1562,5 @@
   _sslCaCertPathLineEdit->setWhatsThis(tr("<p>Communication with an Ntrip Broadcaster over SSL requires the exchange of server certificates. Specify the path to a directory where you save CA certificates on your system. </p><p>BNC creates from *.crt and *.pem files a CA certificate database, which is used by the socket during the handshake phase to validate the peer's certificate. </p><p>Note that SSL communication is usually done over port 443. <i>[key: sslCaCertPath]</i></p>"));
   _sslClientCertPathLineEdit->setWhatsThis(tr("<p>Two-sided communication with an Ntrip Broadcaster over SSL requires in addition the exchange of client certificates. Specify the full path to the client certificates on your system.</p><p></p><p>The file naming convention for client certificates in BNC is as follows: &lt;hostname&gt;.&lt;port&gt;.crt for the certificate and &lt;hostname&gt;.&lt;port&gt;.key for the private key, where &lt;hostname&gt; is without https://. </p><p> If available, the client or personal authentication certificate is presented to the peer during the SSL handshake process. Password protected key files are not supported. </p><p>Don't try communication via two sided SSL if you are not sure whether this is supported by the involved Ntrip Broadcaster. </p><p>Note that SSL communication is usually done over port 443. <i>[key: sslClientCertPath]</i></p>"));
-  _credentialsFileLineEdit->setWhatsThis(tr("<p>Passwords of Ntrip Broadcasters can be kept in credentials files instead of the configuration file: give the user name without password in the stream's URL (e.g. '//user@products.igs-ip.net:443/SSRA00BKG1') or in the upload settings (empty password). BNC then takes the password from the first matching entry of these files. Streams without user name are retrieved without authentication.</p><p>Two formats are accepted, detected from the file content:<br>netrc: 'machine products.igs-ip.net login user password secret', where the machine may also be given as 'host:port';<br>BNC: one entry per line '&lt;host&gt; &lt;port&gt; &lt;user&gt; &lt;password&gt;', port '*' for any port, user '-' for an entry without user (Ntrip Version 1 upload).</p><p>An entry for the exact port is preferred. Several files can be given, separated by commas; default is the file named in the environment variable NETRC or ~/.netrc. A file readable for other users is not used. A password still given in the configuration file is used as before. <i>[key: credentialsFile]</i></p>"));
+  _credentialsFileLineEdit->setWhatsThis(tr("<p>Passwords of Ntrip Broadcasters can be kept in credentials files instead of the configuration file: give the user name without password in the stream's URL (e.g. '//user@products.igs-ip.net:443/SSRA00BKG1') or in the upload settings (empty password). BNC then takes the password from the first matching entry of these files. Streams without user name are retrieved without authentication.</p><p>Two formats are accepted, detected from the file content:<br>netrc: 'machine products.igs-ip.net login user password secret', where the machine may also be given as 'host:port';<br>BNC: one entry per line '&lt;host&gt; &lt;port&gt; &lt;user&gt; &lt;password&gt;', port '*' for any port, user '-' for an entry without user (Ntrip Version 1 upload), or '&lt;host&gt; &lt;user&gt; &lt;password&gt;' for any port.</p><p>An entry for the exact port is preferred. Several files can be given, separated by commas; default is the file named in the environment variable NETRC or ~/.netrc. A file readable for other users is not used. A password still given in the configuration file is used as before. <i>[key: credentialsFile]</i></p>"));
   _sslIgnoreErrorsCheckBox->setWhatsThis(tr("<p>Tick 'Ignore SSL authorization errors' to accept a server certificate that cannot be verified, e.g. a self-signed certificate or one issued for another host name. The connection is then still encrypted, but the server is not authenticated: anybody able to intercept the connection may receive your user name and password and feed in manipulated data. The ignored errors are logged. Better save the CA certificate of the Ntrip Broadcaster in the 'Path to SSL certificates' directory. <i>[key: sslIgnoreErrors]</i></p>"));
 
Index: /trunk/BNC/tests/tools/cred_check.py
===================================================================
--- /trunk/BNC/tests/tools/cred_check.py	(revision 11159)
+++ /trunk/BNC/tests/tools/cred_check.py	(revision 11160)
@@ -12,4 +12,5 @@
   $NETRC: entry 'machine host:port' preferred over 'machine host'
   BNC format (credentialsFile): exact port preferred over '*', quoted password
+  BNC format without port: '<host> <user> <password>'
   URL without user: no credentials sent although ~/.netrc has the host
   user without entry: logged, rejected by the caster
@@ -148,4 +149,9 @@
     check("download: BNC format, exact port, quoted", served(c) and not rejected(c), c)
 
+    c, log = download(bnc, "bnc_format_3", 21312, "bnc@", ["bnc:three"],
+                      {"~/bnc.credentials": ("127.0.0.1 other x\n127.0.0.1 bnc three\n", 0o600)},
+                      extra=["-key", "credentialsFile", "~/bnc.credentials"])
+    check("download: BNC format without port", served(c) and not rejected(c), c)
+
     c, log = download(bnc, "anonymous", 21305, "", [],
                       {"~/.netrc": (netrc_two_users, 0o600)})
