Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
aa724fbcef
|
|||
|
8a30efe9c0
|
|||
|
5049d8df74
|
|||
|
badd026055
|
|||
|
ef70ea0f5a
|
|||
|
30b1a77b61
|
|||
|
9bd1676ec1
|
|||
|
cfb32bdca5
|
|||
|
754b1de7cb
|
|||
|
7892229a2c
|
|||
|
14bf7ba4b1
|
|||
|
bdfe6eec2a
|
|||
|
c28c097e25
|
@@ -20,21 +20,21 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Java JDK (Temurin 17)
|
||||
- name: Set up Java JDK (Temurin 11)
|
||||
uses: actions/setup-java@v3
|
||||
continue-on-error: true
|
||||
with:
|
||||
java-version: '17'
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Ensure Java Available
|
||||
run: |
|
||||
if ! command -v javac >/dev/null 2>&1; then
|
||||
echo "Installing OpenJDK 17 via package manager..."
|
||||
echo "Installing OpenJDK 11 via package manager..."
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
apt-get update && apt-get install -y openjdk-17-jdk-headless || true
|
||||
apt-get update && apt-get install -y openjdk-11-jdk-headless || true
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
apk add --no-cache openjdk17 || true
|
||||
apk add --no-cache openjdk11 || true
|
||||
fi
|
||||
fi
|
||||
echo "Java compiler:"
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="124 738 320 320"
|
||||
width="320"
|
||||
height="320"
|
||||
xml:space="preserve"
|
||||
id="svg_j3270_icon"
|
||||
version="1.1">
|
||||
<metadata id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<!-- Pure black background (#000000) for classic 3270 CRT terminal look -->
|
||||
<rect x="124" y="738" width="320" height="320" fill="#000000" />
|
||||
<g transform="matrix(1.3333333,0,0,-1.3333333,0,1122.6667)" id="g10">
|
||||
<g transform="scale(0.1)" id="g12">
|
||||
<g id="g14">
|
||||
<!-- Green Terminal Screen Frame -->
|
||||
<path
|
||||
id="path28"
|
||||
style="fill:none;stroke:#00ff00;stroke-width:3.75079012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 1177.27,2220.71 V 1142.45 H 3084.31 V 2220.71 H 1177.27" />
|
||||
<!-- Blue Wave / Exponential Curve -->
|
||||
<path
|
||||
id="path30"
|
||||
style="fill:none;stroke:#0000ff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1172.41,1137.58 79.1,-1.22 -0.21,-0.38 c 1.08,0 2.18,0 3.27,0 762.95,0 1464.86,417.88 1829.53,1089.21" />
|
||||
<!-- Red Circular Arc -->
|
||||
<path
|
||||
id="path32"
|
||||
style="fill:none;stroke:#ff0000;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2593.86,1620.73 -1.22,-28 -0.08,-0.08 c 0.43,-250.15 202.81,-452.72 452.3,-452.72 0.14,0 0.28,0 0.42,0 l 43.9,2.52" />
|
||||
<!-- Yellow Smiley Head -->
|
||||
<path
|
||||
id="path34"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2976,1363.94 v 0.08 c 44.73,34.29 71.21,87.25 71.8,143.61 l 0.01,-0.15 c -0.06,97.48 -79.11,176.47 -176.59,176.47 -0.36,0 -0.73,0 -1.09,0 l -0.07,0.03 c -0.25,0 -0.51,0 -0.75,0 -37.38,0 -73.79,-11.94 -103.92,-34.08 l 0.02,-0.03 c -44.38,-34.04 -70.82,-86.47 -71.8,-142.39 l 0.06,0.04 c 0.54,-97.43 79.04,-176.47 176.46,-177.69 l 0.08,0.02 c 0.64,-0.01 1.29,-0.01 1.92,-0.01 37.4,0 73.82,11.94 103.96,34.08" />
|
||||
<!-- 'l' -->
|
||||
<path
|
||||
id="path36"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
|
||||
<path
|
||||
id="path38"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
|
||||
<!-- 'i' -->
|
||||
<path
|
||||
id="path40"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
|
||||
<path
|
||||
id="path42"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
|
||||
<!-- 'b' -->
|
||||
<path
|
||||
id="path44"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
|
||||
<path
|
||||
id="path46"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
|
||||
<!-- '3' -->
|
||||
<path
|
||||
id="path48"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
|
||||
<path
|
||||
id="path50"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
|
||||
<!-- '2' -->
|
||||
<path
|
||||
id="path52"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
|
||||
<path
|
||||
id="path54"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
|
||||
<!-- '7' -->
|
||||
<path
|
||||
id="path56"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
|
||||
<path
|
||||
id="path58"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
|
||||
<!-- '0' -->
|
||||
<path
|
||||
id="path60"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
|
||||
<path
|
||||
id="path62"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
|
||||
<!-- 'j' -->
|
||||
<path
|
||||
id="path64"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
|
||||
<path
|
||||
id="path66"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
|
||||
<!-- Smile -->
|
||||
<path
|
||||
id="path68"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2778.85,1478.34 0.04,-0.02 c 21.03,-31.91 56.69,-51.12 94.91,-51.12 0.41,0 0.83,0 1.24,0.01 l -0.02,0.07 c 43.88,0.19 84.11,24.51 104.66,63.28" />
|
||||
<!-- Left Eye (cyan) -->
|
||||
<path
|
||||
id="path70"
|
||||
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2820.22,1595.17 0.01,0.01 c -14.62,-1.91 -25.56,-14.37 -25.56,-29.11 0,-0.03 0,-0.07 0,-0.1 l -0.02,-0.01 c 0,-0.03 0,-0.07 0,-0.1 0,-16.09 13.04,-29.14 29.13,-29.14 0.43,0 0.87,0.01 1.29,0.03 h 3.67 0.01 c 14.61,1.91 25.55,14.37 25.55,29.11 0,0.03 0,0.07 0,0.1 l 0.02,0.01 c 0,0.04 0,0.08 0,0.12 0,16.07 -13.03,29.09 -29.09,29.09 -0.03,0 -0.08,0 -0.12,0 l -4.89,-0.01" />
|
||||
<!-- Right Eye (cyan) -->
|
||||
<path
|
||||
id="path72"
|
||||
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2921.23,1569.61 v -3.65 l -0.01,-0.01 c 0,-0.27 -0.01,-0.54 -0.01,-0.81 0,-18.22 14.66,-33.05 32.87,-33.26 l 0.03,0.01 c 0.28,0 0.57,-0.01 0.84,-0.01 17.29,0 31.72,13.22 33.24,30.44 l -0.02,3.64 0.02,0.02 c 0,0.27 0.01,0.54 0.01,0.81 0,18.37 -14.9,33.28 -33.28,33.28 -0.26,0 -0.54,-0.01 -0.81,-0.02 l -0.02,-0.03 c -17.14,-0.18 -31.36,-13.34 -32.86,-30.42" />
|
||||
<!-- Nose -->
|
||||
<path
|
||||
id="path74"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2892.03,1528.23 -29.21,-20.68" />
|
||||
<path
|
||||
id="path76"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2862.82,1507.55 h 32.86" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="144 814 280 169"
|
||||
width="280"
|
||||
height="169"
|
||||
xml:space="preserve"
|
||||
id="svg_lib3270j_cropped"
|
||||
version="1.1">
|
||||
<metadata id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<!-- Pure black background (#000000) for classic 3270 CRT terminal look -->
|
||||
<rect x="144" y="814" width="280" height="169" fill="#000000" />
|
||||
<g transform="matrix(1.3333333,0,0,-1.3333333,0,1122.6667)" id="g10">
|
||||
<g transform="scale(0.1)" id="g12">
|
||||
<g id="g14">
|
||||
<!-- Green Terminal Screen Frame -->
|
||||
<path
|
||||
id="path28"
|
||||
style="fill:none;stroke:#00ff00;stroke-width:3.75079012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 1177.27,2220.71 V 1142.45 H 3084.31 V 2220.71 H 1177.27" />
|
||||
<!-- Blue Wave / Exponential Curve -->
|
||||
<path
|
||||
id="path30"
|
||||
style="fill:none;stroke:#0000ff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1172.41,1137.58 79.1,-1.22 -0.21,-0.38 c 1.08,0 2.18,0 3.27,0 762.95,0 1464.86,417.88 1829.53,1089.21" />
|
||||
<!-- Red Circular Arc -->
|
||||
<path
|
||||
id="path32"
|
||||
style="fill:none;stroke:#ff0000;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2593.86,1620.73 -1.22,-28 -0.08,-0.08 c 0.43,-250.15 202.81,-452.72 452.3,-452.72 0.14,0 0.28,0 0.42,0 l 43.9,2.52" />
|
||||
<!-- Yellow Smiley Head -->
|
||||
<path
|
||||
id="path34"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2976,1363.94 v 0.08 c 44.73,34.29 71.21,87.25 71.8,143.61 l 0.01,-0.15 c -0.06,97.48 -79.11,176.47 -176.59,176.47 -0.36,0 -0.73,0 -1.09,0 l -0.07,0.03 c -0.25,0 -0.51,0 -0.75,0 -37.38,0 -73.79,-11.94 -103.92,-34.08 l 0.02,-0.03 c -44.38,-34.04 -70.82,-86.47 -71.8,-142.39 l 0.06,0.04 c 0.54,-97.43 79.04,-176.47 176.46,-177.69 l 0.08,0.02 c 0.64,-0.01 1.29,-0.01 1.92,-0.01 37.4,0 73.82,11.94 103.96,34.08" />
|
||||
<!-- 'l' -->
|
||||
<path
|
||||
id="path36"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
|
||||
<path
|
||||
id="path38"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
|
||||
<!-- 'i' -->
|
||||
<path
|
||||
id="path40"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
|
||||
<path
|
||||
id="path42"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
|
||||
<!-- 'b' -->
|
||||
<path
|
||||
id="path44"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
|
||||
<path
|
||||
id="path46"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
|
||||
<!-- '3' -->
|
||||
<path
|
||||
id="path48"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
|
||||
<path
|
||||
id="path50"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
|
||||
<!-- '2' -->
|
||||
<path
|
||||
id="path52"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
|
||||
<path
|
||||
id="path54"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
|
||||
<!-- '7' -->
|
||||
<path
|
||||
id="path56"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
|
||||
<path
|
||||
id="path58"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
|
||||
<!-- '0' -->
|
||||
<path
|
||||
id="path60"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
|
||||
<path
|
||||
id="path62"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
|
||||
<!-- 'j' -->
|
||||
<path
|
||||
id="path64"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
|
||||
<path
|
||||
id="path66"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
|
||||
<!-- Smile -->
|
||||
<path
|
||||
id="path68"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2778.85,1478.34 0.04,-0.02 c 21.03,-31.91 56.69,-51.12 94.91,-51.12 0.41,0 0.83,0 1.24,0.01 l -0.02,0.07 c 43.88,0.19 84.11,24.51 104.66,63.28" />
|
||||
<!-- Left Eye (cyan) -->
|
||||
<path
|
||||
id="path70"
|
||||
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2820.22,1595.17 0.01,0.01 c -14.62,-1.91 -25.56,-14.37 -25.56,-29.11 0,-0.03 0,-0.07 0,-0.1 l -0.02,-0.01 c 0,-0.03 0,-0.07 0,-0.1 0,-16.09 13.04,-29.14 29.13,-29.14 0.43,0 0.87,0.01 1.29,0.03 h 3.67 0.01 c 14.61,1.91 25.55,14.37 25.55,29.11 0,0.03 0,0.07 0,0.1 l 0.02,0.01 c 0,0.04 0,0.08 0,0.12 0,16.07 -13.03,29.09 -29.09,29.09 -0.03,0 -0.08,0 -0.12,0 l -4.89,-0.01" />
|
||||
<!-- Right Eye (cyan) -->
|
||||
<path
|
||||
id="path72"
|
||||
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2921.23,1569.61 v -3.65 l -0.01,-0.01 c 0,-0.27 -0.01,-0.54 -0.01,-0.81 0,-18.22 14.66,-33.05 32.87,-33.26 l 0.03,0.01 c 0.28,0 0.57,-0.01 0.84,-0.01 17.29,0 31.72,13.22 33.24,30.44 l -0.02,3.64 0.02,0.02 c 0,0.27 0.01,0.54 0.01,0.81 0,18.37 -14.9,33.28 -33.28,33.28 -0.26,0 -0.54,-0.01 -0.81,-0.02 l -0.02,-0.03 c -17.14,-0.18 -31.36,-13.34 -32.86,-30.42" />
|
||||
<!-- Nose -->
|
||||
<path
|
||||
id="path74"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2892.03,1528.23 -29.21,-20.68" />
|
||||
<path
|
||||
id="path76"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2862.82,1507.55 h 32.86" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
@@ -3,6 +3,11 @@
|
||||
; or: ./run.sh -c j3270.ini
|
||||
|
||||
[appearance]
|
||||
; UI font settings (menus, dialogs, status bar)
|
||||
; uiFontFamily = SansSerif
|
||||
; uiFontSize = 13
|
||||
;
|
||||
; Terminal font settings (3270 screen display)
|
||||
; fontFamily = IBM 3270
|
||||
; fontSize = 18
|
||||
|
||||
@@ -10,6 +15,7 @@
|
||||
; startupBehavior = AUTO_CONNECT | SHOW_CONNECT | DO_NOTHING
|
||||
; autoConnectHost = mainframe.example.com
|
||||
; autoConnectPort = 23
|
||||
; blockPaste = true
|
||||
|
||||
[colors]
|
||||
; Host colors 0-15 (hex RGB)
|
||||
|
||||
@@ -42,8 +42,10 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
// Dialog instances
|
||||
private FindDialog findDialog;
|
||||
private ScriptDialog scriptDialog;
|
||||
private MacroDialog macroDialog;
|
||||
private FieldInspectorDialog fieldInspectorDialog;
|
||||
private PrinterSessionDialog printerSessionDialog;
|
||||
private ScreenHistoryDialog screenHistoryDialog;
|
||||
|
||||
// Modes menu items
|
||||
private JCheckBoxMenuItem docModeItem;
|
||||
@@ -60,6 +62,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
super("j3270 — Java TN3270 Terminal Emulator");
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setBackground(Color.BLACK);
|
||||
AppIcon.applyTo(this);
|
||||
|
||||
buildUI();
|
||||
buildMenuBar();
|
||||
@@ -93,6 +96,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
if (printerSessionDialog != null) {
|
||||
printerSessionDialog.dispose();
|
||||
}
|
||||
if (screenHistoryDialog != null) {
|
||||
screenHistoryDialog.dispose();
|
||||
}
|
||||
terminalPanel.dispose();
|
||||
}
|
||||
|
||||
@@ -103,6 +109,29 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure clicking anywhere inside the frame brings focus to window and terminalPanel
|
||||
Toolkit.getDefaultToolkit().addAWTEventListener(event -> {
|
||||
if (event instanceof MouseEvent && event.getID() == MouseEvent.MOUSE_PRESSED) {
|
||||
MouseEvent me = (MouseEvent) event;
|
||||
Object src = me.getSource();
|
||||
if (src instanceof Component) {
|
||||
Window win = SwingUtilities.getWindowAncestor((Component) src);
|
||||
if (win == J3270App.this && !isAnyChildDialogActive()) {
|
||||
if (!J3270App.this.isFocused() || !J3270App.this.isActive()) {
|
||||
J3270App.this.toFront();
|
||||
J3270App.this.requestFocus();
|
||||
}
|
||||
boolean isMenu = (src instanceof JMenuBar) || (src instanceof JMenuItem) || (src instanceof JPopupMenu)
|
||||
|| SwingUtilities.getAncestorOfClass(JMenuBar.class, (Component) src) != null
|
||||
|| SwingUtilities.getAncestorOfClass(JPopupMenu.class, (Component) src) != null;
|
||||
if (!isMenu && terminalPanel != null && !terminalPanel.hasFocus()) {
|
||||
terminalPanel.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, AWTEvent.MOUSE_EVENT_MASK);
|
||||
}
|
||||
|
||||
private boolean isAnyChildDialogActive() {
|
||||
@@ -112,8 +141,10 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
}
|
||||
if (findDialog != null && findDialog.isVisible()) return true;
|
||||
if (scriptDialog != null && scriptDialog.isVisible()) return true;
|
||||
if (macroDialog != null && macroDialog.isVisible()) return true;
|
||||
if (fieldInspectorDialog != null && fieldInspectorDialog.isVisible()) return true;
|
||||
if (printerSessionDialog != null && printerSessionDialog.isVisible()) return true;
|
||||
if (screenHistoryDialog != null && screenHistoryDialog.isVisible()) return true;
|
||||
for (Window owned : getOwnedWindows()) {
|
||||
if (owned != null && owned.isVisible()) {
|
||||
return true;
|
||||
@@ -180,6 +211,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
|
||||
// 2. Edit menu
|
||||
JMenu editMenu = createMenu("Edit");
|
||||
editMenu.add(createMenuItem("Cut", KeyEvent.VK_X, () -> terminalPanel.cutSelection()));
|
||||
editMenu.add(createMenuItem("Copy", KeyEvent.VK_C, () -> terminalPanel.copySelection()));
|
||||
editMenu.add(createMenuItem("Paste", KeyEvent.VK_V, () -> terminalPanel.pasteClipboard()));
|
||||
editMenu.add(createMenuItem("Paste with Line Wrap...", -1, this::showPasteLineWrapDialog));
|
||||
@@ -311,6 +343,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
viewMenu.add(gfxMenu);
|
||||
|
||||
viewMenu.addSeparator();
|
||||
viewMenu.add(createMenuItem("Screen History...", KeyEvent.VK_H, this::showScreenHistoryDialog, true));
|
||||
viewMenu.add(createMenuItem("Field Inspector...", -1, this::showFieldInspectorDialog));
|
||||
menuBar.add(viewMenu);
|
||||
|
||||
@@ -429,6 +462,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
}, true));
|
||||
actionsMenu.addSeparator();
|
||||
actionsMenu.add(createMenuItem("Run Keystrokes / Script...", -1, this::showScriptDialog));
|
||||
actionsMenu.add(createMenuItem("Macro Runtime (<HASCRIPT>)...", KeyEvent.VK_M, this::showMacroDialog, true));
|
||||
actionsMenu.add(createMenuItem("Screen History...", KeyEvent.VK_H, this::showScreenHistoryDialog, true));
|
||||
actionsMenu.add(createMenuItem("File Transfer...", KeyEvent.VK_T, this::showFileTransferDialog, true));
|
||||
menuBar.add(actionsMenu);
|
||||
|
||||
@@ -535,6 +570,13 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
scriptDialog.setVisible(true);
|
||||
}
|
||||
|
||||
private void showMacroDialog() {
|
||||
if (macroDialog == null) {
|
||||
macroDialog = new MacroDialog(this, client, terminalPanel);
|
||||
}
|
||||
macroDialog.setVisible(true);
|
||||
}
|
||||
|
||||
private void showFieldInspectorDialog() {
|
||||
if (client == null) {
|
||||
JOptionPane.showMessageDialog(this, "Connect to a host before inspecting fields.", "Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||
@@ -558,6 +600,18 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
printerSessionDialog.setVisible(true);
|
||||
}
|
||||
|
||||
private void showScreenHistoryDialog() {
|
||||
if (screenHistoryDialog == null) {
|
||||
screenHistoryDialog = new ScreenHistoryDialog(this, client);
|
||||
} else {
|
||||
if (client != null && client.getScreenHistoryBuffer() != null) {
|
||||
screenHistoryDialog.refreshHistoryView(client.getScreenHistoryBuffer().size() - 1);
|
||||
}
|
||||
}
|
||||
screenHistoryDialog.setVisible(true);
|
||||
screenHistoryDialog.toFront();
|
||||
}
|
||||
|
||||
private void showPasteLineWrapDialog() {
|
||||
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||
JOptionPane.showMessageDialog(this, "Connect to a 3270 session before pasting.", "Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||
@@ -693,6 +747,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
dialog.setInitialTls(haus.nightmare.j3270.config.Settings.getAutoConnectTls());
|
||||
dialog.setInitialVerifyCert(haus.nightmare.j3270.config.Settings.getAutoConnectVerifyCert());
|
||||
dialog.setInitialCodePage(haus.nightmare.j3270.config.Settings.getCodePage());
|
||||
dialog.setInitialGraphicsMode(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.isConfirmed()) {
|
||||
@@ -716,6 +771,10 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
return client;
|
||||
}
|
||||
|
||||
public StatusBar getStatusBar() {
|
||||
return statusBar;
|
||||
}
|
||||
|
||||
void connect(ConnectionConfig config) {
|
||||
lastHost = config.getHost();
|
||||
lastPort = config.getPort();
|
||||
@@ -725,6 +784,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
haus.nightmare.j3270.config.Settings.setAutoConnectTls(config.isUseTls());
|
||||
haus.nightmare.j3270.config.Settings.setAutoConnectVerifyCert(config.isTlsVerifyCert());
|
||||
haus.nightmare.j3270.config.Settings.setCodePage(config.getCodePage());
|
||||
if (config.getGraphicsMode() != null) {
|
||||
haus.nightmare.j3270.config.Settings.setGraphicsMode(config.getGraphicsMode());
|
||||
}
|
||||
|
||||
disconnect();
|
||||
|
||||
@@ -768,6 +830,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
fileTransfer.cancel();
|
||||
fileTransfer = null;
|
||||
}
|
||||
if (macroDialog != null) {
|
||||
macroDialog.stopMacro();
|
||||
}
|
||||
Telnet3270Client c = client;
|
||||
client = null;
|
||||
new Thread(() -> {
|
||||
@@ -945,7 +1010,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
|
||||
JTextArea area = new JTextArea(text);
|
||||
area.setEditable(false);
|
||||
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||
area.setFont(ThemeManager.getMonospacedUiFont());
|
||||
ThemeManager.styleTextArea(area);
|
||||
JScrollPane sp = new JScrollPane(area);
|
||||
ThemeManager.styleScrollPane(sp);
|
||||
@@ -978,7 +1043,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
|
||||
JTextArea area = new JTextArea(text);
|
||||
area.setEditable(false);
|
||||
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||
area.setFont(ThemeManager.getMonospacedUiFont());
|
||||
ThemeManager.styleTextArea(area);
|
||||
JScrollPane sp = new JScrollPane(area);
|
||||
ThemeManager.styleScrollPane(sp);
|
||||
@@ -1002,55 +1067,23 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
"About j3270", JOptionPane.INFORMATION_MESSAGE);
|
||||
}
|
||||
|
||||
// ========== Main ==========
|
||||
|
||||
public static void main(String[] args) {
|
||||
boolean debug = false;
|
||||
boolean cliTls = false;
|
||||
boolean cliNoVerifyCert = false;
|
||||
Boolean cliTn3270e = null;
|
||||
Boolean cliAutoSysUnlock = null;
|
||||
GraphicsMode cliGraphicsMode = null;
|
||||
String configFile = null;
|
||||
|
||||
java.util.List<String> remainingArgs = new java.util.ArrayList<>();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
if ("-d".equals(arg) || "--debug".equals(arg)) {
|
||||
debug = true;
|
||||
} else if ("-s".equals(arg) || "--tls".equals(arg) || "--ssl".equals(arg)) {
|
||||
cliTls = true;
|
||||
} else if ("--no-verify-cert".equals(arg) || "--insecure".equals(arg) || "-k".equals(arg)) {
|
||||
cliNoVerifyCert = true;
|
||||
} else if ("--no-tn3270e".equals(arg) || "--plain-tn3270".equals(arg) || "--plain".equals(arg) || "-P".equals(arg) || "-p".equals(arg) || "--non-e".equals(arg)) {
|
||||
cliTn3270e = false;
|
||||
} else if ("--tn3270e".equals(arg)) {
|
||||
cliTn3270e = true;
|
||||
} else if ("--auto-sys-unlock".equals(arg)) {
|
||||
cliAutoSysUnlock = true;
|
||||
} else if ("--no-auto-sys-unlock".equals(arg)) {
|
||||
cliAutoSysUnlock = false;
|
||||
} else if (arg.startsWith("--graphics=")) {
|
||||
cliGraphicsMode = GraphicsMode.fromString(arg.substring(11));
|
||||
} else if (("--graphics".equals(arg) || "-g".equals(arg)) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
||||
cliGraphicsMode = GraphicsMode.fromString(args[++i]);
|
||||
} else if ("--no-graphics".equals(arg)) {
|
||||
cliGraphicsMode = GraphicsMode.NONE;
|
||||
} else if (("-c".equals(arg) || "--config".equals(arg)) && i + 1 < args.length) {
|
||||
configFile = args[++i];
|
||||
} else if (arg.startsWith("-")) {
|
||||
System.err.println("Unknown option: " + arg);
|
||||
} else {
|
||||
remainingArgs.add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
Level logLevel = debug ? Level.FINE : Level.INFO;
|
||||
|
||||
/**
|
||||
* Configure application logging. When debug is false, disables all logging
|
||||
* to console and file, preventing creation of j3270.log.
|
||||
*/
|
||||
public static void configureLogging(boolean debug) {
|
||||
Logger globalRoot = Logger.getLogger("");
|
||||
for (java.util.logging.Handler h : globalRoot.getHandlers()) {
|
||||
globalRoot.removeHandler(h);
|
||||
try {
|
||||
h.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Level logLevel = Level.ALL;
|
||||
globalRoot.setLevel(Level.ALL);
|
||||
|
||||
java.util.logging.Filter appFilter = record -> record.getLoggerName() != null &&
|
||||
(record.getLoggerName().startsWith("haus.nightmare") || record.getLoggerName().startsWith("org.pubvm"));
|
||||
@@ -1081,6 +1114,117 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
} catch (Exception e) {
|
||||
System.err.println("Could not create j3270.log: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
globalRoot.setLevel(Level.OFF);
|
||||
for (String pkg : new String[]{"haus.nightmare", "haus.nightmare.j3270", "haus.nightmare.lib3270j", "org.pubvm"}) {
|
||||
Logger l = Logger.getLogger(pkg);
|
||||
l.setLevel(Level.OFF);
|
||||
for (java.util.logging.Handler h : l.getHandlers()) {
|
||||
l.removeHandler(h);
|
||||
try {
|
||||
h.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyProxyUrl(ConnectionConfig config, String proxyUrl) {
|
||||
if (config == null || proxyUrl == null || proxyUrl.trim().isEmpty()) return;
|
||||
try {
|
||||
java.net.URI uri = new java.net.URI(proxyUrl.trim());
|
||||
String scheme = uri.getScheme() != null ? uri.getScheme().toLowerCase() : "http";
|
||||
ConnectionConfig.ProxyType pType;
|
||||
int pPort;
|
||||
if (scheme.equals("http") || scheme.equals("https")) {
|
||||
pType = ConnectionConfig.ProxyType.HTTP;
|
||||
pPort = uri.getPort() > 0 ? uri.getPort() : 8080;
|
||||
} else if (scheme.equals("socks4") || scheme.equals("socks4a")) {
|
||||
pType = ConnectionConfig.ProxyType.SOCKS4;
|
||||
pPort = uri.getPort() > 0 ? uri.getPort() : 1080;
|
||||
} else if (scheme.equals("socks5") || scheme.equals("socks")) {
|
||||
pType = ConnectionConfig.ProxyType.SOCKS5;
|
||||
pPort = uri.getPort() > 0 ? uri.getPort() : 1080;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
String pHost = uri.getHost();
|
||||
String pUser = null;
|
||||
String pPass = null;
|
||||
String userInfo = uri.getUserInfo();
|
||||
if (userInfo != null) {
|
||||
int colon = userInfo.indexOf(':');
|
||||
if (colon >= 0) {
|
||||
pUser = userInfo.substring(0, colon);
|
||||
pPass = userInfo.substring(colon + 1);
|
||||
} else {
|
||||
pUser = userInfo;
|
||||
}
|
||||
}
|
||||
if (pHost != null && !pHost.isEmpty()) {
|
||||
config.setProxy(pType, pHost, pPort, pUser, pPass);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warning("Failed to parse CLI proxy URL: " + proxyUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Main ==========
|
||||
|
||||
public static void main(String[] args) {
|
||||
boolean debug = false;
|
||||
boolean cliTls = false;
|
||||
boolean cliNoVerifyCert = false;
|
||||
Boolean cliTn3270e = null;
|
||||
Boolean cliAutoSysUnlock = null;
|
||||
GraphicsMode cliGraphicsMode = null;
|
||||
String configFile = null;
|
||||
String cliProxyUrl = null;
|
||||
String cliProfileName = null;
|
||||
|
||||
java.util.List<String> remainingArgs = new java.util.ArrayList<>();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
if ("-d".equals(arg) || "--debug".equals(arg)) {
|
||||
debug = true;
|
||||
} else if ("-s".equals(arg) || "--tls".equals(arg) || "--ssl".equals(arg)) {
|
||||
cliTls = true;
|
||||
} else if ("--no-verify-cert".equals(arg) || "--insecure".equals(arg) || "-k".equals(arg)) {
|
||||
cliNoVerifyCert = true;
|
||||
} else if ("--no-tn3270e".equals(arg) || "--plain-tn3270".equals(arg) || "--plain".equals(arg) || "-P".equals(arg) || "-p".equals(arg) || "--non-e".equals(arg)) {
|
||||
cliTn3270e = false;
|
||||
} else if ("--tn3270e".equals(arg)) {
|
||||
cliTn3270e = true;
|
||||
} else if ("--auto-sys-unlock".equals(arg)) {
|
||||
cliAutoSysUnlock = true;
|
||||
} else if ("--no-auto-sys-unlock".equals(arg)) {
|
||||
cliAutoSysUnlock = false;
|
||||
} else if (arg.startsWith("--graphics=")) {
|
||||
cliGraphicsMode = GraphicsMode.fromString(arg.substring(11));
|
||||
} else if (("--graphics".equals(arg) || "-g".equals(arg)) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
||||
cliGraphicsMode = GraphicsMode.fromString(args[++i]);
|
||||
} else if ("--no-graphics".equals(arg)) {
|
||||
cliGraphicsMode = GraphicsMode.NONE;
|
||||
} else if (("-c".equals(arg) || "--config".equals(arg)) && i + 1 < args.length) {
|
||||
configFile = args[++i];
|
||||
} else if (arg.startsWith("--proxy=") || arg.startsWith("-proxy=")) {
|
||||
cliProxyUrl = arg.substring(arg.indexOf('=') + 1).trim();
|
||||
} else if (("--proxy".equals(arg) || "-proxy".equals(arg)) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
||||
cliProxyUrl = args[++i].trim();
|
||||
} else if (arg.startsWith("--profile=")) {
|
||||
cliProfileName = arg.substring(arg.indexOf('=') + 1).trim();
|
||||
} else if ("--profile".equals(arg) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
||||
cliProfileName = args[++i].trim();
|
||||
} else if (arg.startsWith("-")) {
|
||||
System.err.println("Unknown option: " + arg);
|
||||
} else {
|
||||
remainingArgs.add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
configureLogging(debug);
|
||||
|
||||
|
||||
if (configFile != null) {
|
||||
try {
|
||||
@@ -1108,11 +1252,33 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
final Boolean finalTn3270e = cliTn3270e;
|
||||
final Boolean finalAutoSysUnlock = cliAutoSysUnlock;
|
||||
final GraphicsMode finalGraphicsMode = cliGraphicsMode;
|
||||
final String finalProxyUrl = cliProxyUrl;
|
||||
final String finalProfileName = cliProfileName;
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
J3270App app = new J3270App();
|
||||
app.setVisible(true);
|
||||
|
||||
if (finalProfileName != null) {
|
||||
haus.nightmare.j3270.storage.SavedHost profile = haus.nightmare.j3270.storage.HostStorage.findByName(finalProfileName);
|
||||
if (profile == null) {
|
||||
profile = haus.nightmare.j3270.storage.HostStorage.findById(finalProfileName);
|
||||
}
|
||||
if (profile != null) {
|
||||
ConnectionConfig config = profile.toConnectionConfig();
|
||||
if (finalTls) config.setUseTls(true);
|
||||
if (finalNoVerify) config.setTlsVerifyCert(false);
|
||||
if (finalTn3270e != null) config.setTn3270eEnabled(finalTn3270e);
|
||||
if (finalAutoSysUnlock != null) config.setAutoSysUnlock(finalAutoSysUnlock);
|
||||
if (finalGraphicsMode != null) config.setGraphicsMode(finalGraphicsMode);
|
||||
if (finalProxyUrl != null) applyProxyUrl(config, finalProxyUrl);
|
||||
app.connect(config);
|
||||
return;
|
||||
} else {
|
||||
System.err.println("Profile not found: " + finalProfileName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!remainingArgs.isEmpty()) {
|
||||
String hostArg = remainingArgs.get(0);
|
||||
int port = finalTls ? 992 : 23;
|
||||
@@ -1153,6 +1319,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
} else {
|
||||
config.setGraphicsMode(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||
}
|
||||
if (finalProxyUrl != null) {
|
||||
applyProxyUrl(config, finalProxyUrl);
|
||||
}
|
||||
app.connect(config);
|
||||
} else {
|
||||
haus.nightmare.j3270.config.Settings.StartupBehavior behavior = haus.nightmare.j3270.config.Settings
|
||||
@@ -1162,6 +1331,17 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
SwingUtilities.invokeLater(app::showConnectDialog);
|
||||
break;
|
||||
case AUTO_CONNECT:
|
||||
haus.nightmare.j3270.storage.SavedHost autoHost = haus.nightmare.j3270.storage.HostStorage.getAutoConnectHost();
|
||||
if (autoHost != null) {
|
||||
ConnectionConfig config = autoHost.toConnectionConfig();
|
||||
if (finalTls) config.setUseTls(true);
|
||||
if (finalNoVerify) config.setTlsVerifyCert(false);
|
||||
if (finalTn3270e != null) config.setTn3270eEnabled(finalTn3270e);
|
||||
if (finalAutoSysUnlock != null) config.setAutoSysUnlock(finalAutoSysUnlock);
|
||||
if (finalGraphicsMode != null) config.setGraphicsMode(finalGraphicsMode);
|
||||
if (finalProxyUrl != null) applyProxyUrl(config, finalProxyUrl);
|
||||
app.connect(config);
|
||||
} else {
|
||||
String host = haus.nightmare.j3270.config.Settings.getAutoConnectHost();
|
||||
int port = haus.nightmare.j3270.config.Settings.getAutoConnectPort();
|
||||
boolean tls = haus.nightmare.j3270.config.Settings.getAutoConnectTls();
|
||||
@@ -1179,10 +1359,14 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
} else {
|
||||
config.setGraphicsMode(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||
}
|
||||
if (finalProxyUrl != null) {
|
||||
applyProxyUrl(config, finalProxyUrl);
|
||||
}
|
||||
app.connect(config);
|
||||
} else {
|
||||
SwingUtilities.invokeLater(app::showConnectDialog);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DO_NOTHING:
|
||||
default:
|
||||
|
||||
@@ -45,6 +45,24 @@ public class Settings {
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static String getUiFontFamily() {
|
||||
return prefs.get("uiFontFamily", "SansSerif");
|
||||
}
|
||||
|
||||
public static void setUiFontFamily(String family) {
|
||||
prefs.put("uiFontFamily", (family != null && !family.trim().isEmpty()) ? family : "SansSerif");
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static int getUiFontSize() {
|
||||
return prefs.getInt("uiFontSize", 13);
|
||||
}
|
||||
|
||||
public static void setUiFontSize(int size) {
|
||||
prefs.putInt("uiFontSize", Math.max(8, Math.min(size, 48)));
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
private static void flushPrefs() {
|
||||
try {
|
||||
prefs.flush();
|
||||
@@ -390,6 +408,17 @@ public class Settings {
|
||||
prefs.putBoolean("blockSelectMode", block);
|
||||
}
|
||||
|
||||
// ========== Block paste ==========
|
||||
|
||||
public static boolean getBlockPaste() {
|
||||
return prefs.getBoolean("blockPaste", true);
|
||||
}
|
||||
|
||||
public static void setBlockPaste(boolean block) {
|
||||
prefs.putBoolean("blockPaste", block);
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
// ========== Clipboard & Tabular Paste ==========
|
||||
|
||||
public static boolean getEnablePasteFromExcel() {
|
||||
@@ -410,6 +439,33 @@ public class Settings {
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static boolean getTrimRectSizingHandles() {
|
||||
return prefs.getBoolean("trimRectSizingHandles", false);
|
||||
}
|
||||
|
||||
public static void setTrimRectSizingHandles(boolean val) {
|
||||
prefs.putBoolean("trimRectSizingHandles", val);
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static boolean getTrimRectRemainAfterEdit() {
|
||||
return prefs.getBoolean("trimRectRemainAfterEdit", false);
|
||||
}
|
||||
|
||||
public static void setTrimRectRemainAfterEdit(boolean val) {
|
||||
prefs.putBoolean("trimRectRemainAfterEdit", val);
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static boolean getPasteWordBreak() {
|
||||
return prefs.getBoolean("pasteWordBreak", true);
|
||||
}
|
||||
|
||||
public static void setPasteWordBreak(boolean val) {
|
||||
prefs.putBoolean("pasteWordBreak", val);
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
// ========== Crosshair Ruler ==========
|
||||
|
||||
public static boolean getCrosshairRuler() {
|
||||
@@ -420,6 +476,71 @@ public class Settings {
|
||||
prefs.putBoolean("crosshairRuler", enabled);
|
||||
}
|
||||
|
||||
// ========== Phase 25: Hotspots & Mouse Wheel Navigation ==========
|
||||
|
||||
public static boolean getHotspotsEnabled() {
|
||||
return prefs.getBoolean("hotspotsEnabled", true);
|
||||
}
|
||||
|
||||
public static void setHotspotsEnabled(boolean enabled) {
|
||||
prefs.putBoolean("hotspotsEnabled", enabled);
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static boolean getHotspotsUrlEnabled() {
|
||||
return prefs.getBoolean("hotspotsUrlEnabled", true);
|
||||
}
|
||||
|
||||
public static void setHotspotsUrlEnabled(boolean enabled) {
|
||||
prefs.putBoolean("hotspotsUrlEnabled", enabled);
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static boolean getHotspotsPfEnabled() {
|
||||
return prefs.getBoolean("hotspotsPfEnabled", true);
|
||||
}
|
||||
|
||||
public static void setHotspotsPfEnabled(boolean enabled) {
|
||||
prefs.putBoolean("hotspotsPfEnabled", enabled);
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static boolean getHotspotsMenuChoicesEnabled() {
|
||||
return prefs.getBoolean("hotspotsMenuChoicesEnabled", true);
|
||||
}
|
||||
|
||||
public static void setHotspotsMenuChoicesEnabled(boolean enabled) {
|
||||
prefs.putBoolean("hotspotsMenuChoicesEnabled", enabled);
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static boolean getMouseWheelEnabled() {
|
||||
return prefs.getBoolean("mouseWheelEnabled", true);
|
||||
}
|
||||
|
||||
public static void setMouseWheelEnabled(boolean enabled) {
|
||||
prefs.putBoolean("mouseWheelEnabled", enabled);
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static String getMouseWheelUpKey() {
|
||||
return prefs.get("mouseWheelUpKey", "[pf7]");
|
||||
}
|
||||
|
||||
public static void setMouseWheelUpKey(String key) {
|
||||
prefs.put("mouseWheelUpKey", key != null && !key.trim().isEmpty() ? key.trim() : "[pf7]");
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static String getMouseWheelDownKey() {
|
||||
return prefs.get("mouseWheelDownKey", "[pf8]");
|
||||
}
|
||||
|
||||
public static void setMouseWheelDownKey(String key) {
|
||||
prefs.put("mouseWheelDownKey", key != null && !key.trim().isEmpty() ? key.trim() : "[pf8]");
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
// ========== Status Bar Visibility ==========
|
||||
|
||||
public static boolean getStatusBarVisible() {
|
||||
@@ -541,6 +662,16 @@ public class Settings {
|
||||
switch (key) {
|
||||
case "fontFamily": setFontFamily(value); break;
|
||||
case "fontSize": setFontSize(Integer.parseInt(value)); break;
|
||||
case "uiFontFamily":
|
||||
case "uifontfamily":
|
||||
case "uifont":
|
||||
case "uiFont":
|
||||
setUiFontFamily(value);
|
||||
break;
|
||||
case "uiFontSize":
|
||||
case "uifontsize":
|
||||
setUiFontSize(Integer.parseInt(value));
|
||||
break;
|
||||
case "javaUiTheme":
|
||||
case "theme":
|
||||
case "uiTheme":
|
||||
@@ -608,6 +739,7 @@ public class Settings {
|
||||
setDynamicCols(Integer.parseInt(value));
|
||||
break;
|
||||
case "blockSelectMode": setBlockSelectMode(Boolean.parseBoolean(value)); break;
|
||||
case "blockPaste": setBlockPaste(Boolean.parseBoolean(value)); break;
|
||||
case "autoReconnect":
|
||||
case "auto_reconnect":
|
||||
case "autoConnectAutoReconnect":
|
||||
@@ -632,6 +764,9 @@ public class Settings {
|
||||
case "auto_sys_unlock":
|
||||
setAutoSysUnlock(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "blockPasteMode":
|
||||
setBlockPaste(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "enablePasteFromExcel":
|
||||
case "pasteFromExcel":
|
||||
case "excelPaste":
|
||||
@@ -642,6 +777,18 @@ public class Settings {
|
||||
case "pasteStopAtProtected":
|
||||
setPasteStopAtProtectedLine(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "trimRectSizingHandles":
|
||||
case "sizingHandles":
|
||||
setTrimRectSizingHandles(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "trimRectRemainAfterEdit":
|
||||
case "remainAfterEdit":
|
||||
setTrimRectRemainAfterEdit(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "pasteWordBreak":
|
||||
case "wordBreak":
|
||||
setPasteWordBreak(Boolean.parseBoolean(value));
|
||||
break;
|
||||
default:
|
||||
log.warning("Unknown behavior/connection key: " + key);
|
||||
}
|
||||
@@ -649,6 +796,10 @@ public class Settings {
|
||||
|
||||
case "clipboard":
|
||||
switch (key) {
|
||||
case "blockPaste":
|
||||
case "blockPasteMode":
|
||||
setBlockPaste(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "enablePasteFromExcel":
|
||||
case "pasteFromExcel":
|
||||
case "excelPaste":
|
||||
@@ -659,6 +810,18 @@ public class Settings {
|
||||
case "pasteStopAtProtected":
|
||||
setPasteStopAtProtectedLine(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "trimRectSizingHandles":
|
||||
case "sizingHandles":
|
||||
setTrimRectSizingHandles(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "trimRectRemainAfterEdit":
|
||||
case "remainAfterEdit":
|
||||
setTrimRectRemainAfterEdit(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "pasteWordBreak":
|
||||
case "wordBreak":
|
||||
setPasteWordBreak(Boolean.parseBoolean(value));
|
||||
break;
|
||||
default:
|
||||
log.warning("Unknown clipboard key: " + key);
|
||||
}
|
||||
@@ -761,6 +924,8 @@ public class Settings {
|
||||
// [appearance]
|
||||
w.println("[appearance]");
|
||||
w.println("javaUiTheme = " + getJavaUiTheme().name());
|
||||
w.println("uiFontFamily = " + getUiFontFamily());
|
||||
w.println("uiFontSize = " + getUiFontSize());
|
||||
w.println("fontFamily = " + getFontFamily());
|
||||
w.println("fontSize = " + getFontSize());
|
||||
w.println("crosshairRuler = " + getCrosshairRuler());
|
||||
@@ -783,6 +948,7 @@ public class Settings {
|
||||
w.println("dynamicRows = " + getDynamicRows());
|
||||
w.println("dynamicCols = " + getDynamicCols());
|
||||
w.println("blockSelectMode = " + getBlockSelectMode());
|
||||
w.println("blockPaste = " + getBlockPaste());
|
||||
w.println("autoReconnect = " + getAutoConnectAutoReconnect());
|
||||
w.println("autoConnectReconnectMaxRetries = " + getAutoConnectReconnectMaxRetries());
|
||||
w.println("inputMask = " + getInputMask());
|
||||
@@ -790,6 +956,9 @@ public class Settings {
|
||||
w.println("autoSysUnlock = " + getAutoSysUnlock());
|
||||
w.println("enablePasteFromExcel = " + getEnablePasteFromExcel());
|
||||
w.println("pasteStopAtProtectedLine = " + getPasteStopAtProtectedLine());
|
||||
w.println("trimRectSizingHandles = " + getTrimRectSizingHandles());
|
||||
w.println("trimRectRemainAfterEdit = " + getTrimRectRemainAfterEdit());
|
||||
w.println("pasteWordBreak = " + getPasteWordBreak());
|
||||
w.println();
|
||||
|
||||
// [entryassist]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package haus.nightmare.j3270.ft;
|
||||
|
||||
import haus.nightmare.j3270.ui.AppIcon;
|
||||
import haus.nightmare.j3270.ui.HostDirectoryDialog;
|
||||
import haus.nightmare.j3270.ui.ThemeManager;
|
||||
import haus.nightmare.lib3270j.ft.FTConfig;
|
||||
@@ -92,6 +93,7 @@ public class FileTransferDialog extends JDialog {
|
||||
super(owner, "File Transfer (IND$FILE)", false);
|
||||
this.owner = owner;
|
||||
this.coordinator = coordinator;
|
||||
AppIcon.applyTo(this);
|
||||
|
||||
buildUI();
|
||||
pack();
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
package haus.nightmare.j3270.storage;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
/**
|
||||
* Storage manager for saved host profiles, maintaining 1:1 format compatibility with a3270.
|
||||
* Persists profiles in Java user preferences with JSON serialization.
|
||||
*/
|
||||
public class HostStorage {
|
||||
|
||||
private static final Logger log = Logger.getLogger(HostStorage.class.getName());
|
||||
private static final Preferences prefs = Preferences.userNodeForPackage(HostStorage.class);
|
||||
public static final String KEY_SAVED_HOSTS = "saved_hosts";
|
||||
|
||||
public static synchronized List<SavedHost> getSavedHosts() {
|
||||
String jsonStr = prefs.get(KEY_SAVED_HOSTS, null);
|
||||
if (jsonStr == null || jsonStr.trim().isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
return parseHostsJson(jsonStr);
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Failed to parse saved hosts JSON", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void saveHost(SavedHost host) {
|
||||
if (host == null) return;
|
||||
List<SavedHost> current = new ArrayList<>(getSavedHosts());
|
||||
int index = -1;
|
||||
for (int i = 0; i < current.size(); i++) {
|
||||
if (Objects.equals(current.get(i).getId(), host.getId())) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If this host is set to autoConnect, clear autoConnect on all others
|
||||
SavedHost hostToSave = host.copy();
|
||||
if (hostToSave.isAutoConnect()) {
|
||||
for (int i = 0; i < current.size(); i++) {
|
||||
current.get(i).setAutoConnect(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (index >= 0) {
|
||||
current.set(index, hostToSave);
|
||||
} else {
|
||||
current.add(hostToSave);
|
||||
}
|
||||
saveAll(current);
|
||||
}
|
||||
|
||||
public static synchronized void deleteHost(String hostId) {
|
||||
if (hostId == null) return;
|
||||
List<SavedHost> current = new ArrayList<>(getSavedHosts());
|
||||
current.removeIf(h -> Objects.equals(h.getId(), hostId));
|
||||
saveAll(current);
|
||||
}
|
||||
|
||||
public static synchronized SavedHost getAutoConnectHost() {
|
||||
for (SavedHost h : getSavedHosts()) {
|
||||
if (h.isAutoConnect()) {
|
||||
return h;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static synchronized SavedHost findById(String id) {
|
||||
if (id == null) return null;
|
||||
for (SavedHost h : getSavedHosts()) {
|
||||
if (id.equals(h.getId())) {
|
||||
return h;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static synchronized SavedHost findByName(String name) {
|
||||
if (name == null) return null;
|
||||
for (SavedHost h : getSavedHosts()) {
|
||||
if (name.equalsIgnoreCase(h.getName())) {
|
||||
return h;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static synchronized void saveAll(List<SavedHost> hosts) {
|
||||
if (hosts == null) return;
|
||||
String json = serializeHostsJson(hosts);
|
||||
prefs.put(KEY_SAVED_HOSTS, json);
|
||||
try {
|
||||
prefs.flush();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void clearAll() {
|
||||
prefs.remove(KEY_SAVED_HOSTS);
|
||||
try {
|
||||
prefs.flush();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// ================= JSON Serialization =================
|
||||
|
||||
public static String serializeHostsJson(List<SavedHost> hosts) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("[\n");
|
||||
for (int i = 0; i < hosts.size(); i++) {
|
||||
SavedHost h = hosts.get(i);
|
||||
sb.append(" ").append(serializeHostJson(h));
|
||||
if (i < hosts.size() - 1) {
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String serializeHostJson(SavedHost h) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{");
|
||||
appendField(sb, "id", h.getId(), true);
|
||||
appendField(sb, "name", h.getName(), true);
|
||||
appendField(sb, "host", h.getHost(), true);
|
||||
appendField(sb, "port", h.getPort(), true);
|
||||
appendField(sb, "model", h.getModel(), true);
|
||||
appendField(sb, "dynamicRows", h.getDynamicRows(), true);
|
||||
appendField(sb, "dynamicCols", h.getDynamicCols(), true);
|
||||
appendField(sb, "luName", h.getLuName(), true);
|
||||
appendField(sb, "autoConnect", h.isAutoConnect(), true);
|
||||
appendField(sb, "hostType", h.getHostType(), true);
|
||||
appendField(sb, "useTls", h.isUseTls(), true);
|
||||
appendField(sb, "tlsVerifyCert", h.isTlsVerifyCert(), true);
|
||||
appendField(sb, "tn3270e", h.isTn3270e(), true);
|
||||
appendField(sb, "graphicsMode", h.getGraphicsMode(), true);
|
||||
appendField(sb, "codePage", h.getCodePage(), true);
|
||||
appendField(sb, "proxyType", h.getProxyType(), true);
|
||||
appendField(sb, "proxyHost", h.getProxyHost(), true);
|
||||
appendField(sb, "proxyPort", h.getProxyPort(), true);
|
||||
appendField(sb, "proxyUsername", h.getProxyUsername(), true);
|
||||
appendField(sb, "proxyPassword", h.getProxyPassword(), false);
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void appendField(StringBuilder sb, String key, String val, boolean comma) {
|
||||
sb.append("\"").append(key).append("\":\"").append(escapeJson(val != null ? val : "")).append("\"");
|
||||
if (comma) sb.append(",");
|
||||
}
|
||||
|
||||
private static void appendField(StringBuilder sb, String key, int val, boolean comma) {
|
||||
sb.append("\"").append(key).append("\":").append(val);
|
||||
if (comma) sb.append(",");
|
||||
}
|
||||
|
||||
private static void appendField(StringBuilder sb, String key, boolean val, boolean comma) {
|
||||
sb.append("\"").append(key).append("\":").append(val);
|
||||
if (comma) sb.append(",");
|
||||
}
|
||||
|
||||
private static String escapeJson(String s) {
|
||||
if (s == null) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
switch (c) {
|
||||
case '"': sb.append("\\\""); break;
|
||||
case '\\': sb.append("\\\\"); break;
|
||||
case '\b': sb.append("\\b"); break;
|
||||
case '\f': sb.append("\\f"); break;
|
||||
case '\n': sb.append("\\n"); break;
|
||||
case '\r': sb.append("\\r"); break;
|
||||
case '\t': sb.append("\\t"); break;
|
||||
default:
|
||||
if (c < 32) {
|
||||
sb.append(String.format("\\u%04x", (int) c));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// ================= JSON Deserialization =================
|
||||
|
||||
public static List<SavedHost> parseHostsJson(String json) {
|
||||
List<SavedHost> list = new ArrayList<>();
|
||||
if (json == null || json.trim().isEmpty()) return list;
|
||||
|
||||
SimpleJsonParser parser = new SimpleJsonParser(json);
|
||||
Object root = parser.parseValue();
|
||||
if (root instanceof List) {
|
||||
for (Object item : (List<?>) root) {
|
||||
if (item instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) item;
|
||||
list.add(fromMap(map));
|
||||
}
|
||||
}
|
||||
} else if (root instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) root;
|
||||
list.add(fromMap(map));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static SavedHost fromMap(Map<String, Object> map) {
|
||||
SavedHost h = new SavedHost();
|
||||
if (map.containsKey("id")) h.setId(String.valueOf(map.get("id")));
|
||||
if (map.containsKey("name")) h.setName(String.valueOf(map.get("name")));
|
||||
if (map.containsKey("host")) h.setHost(String.valueOf(map.get("host")));
|
||||
if (map.containsKey("port")) h.setPort(parseInt(map.get("port"), 23));
|
||||
if (map.containsKey("model")) h.setModel(parseInt(map.get("model"), 4));
|
||||
if (map.containsKey("dynamicRows")) h.setDynamicRows(parseInt(map.get("dynamicRows"), 62));
|
||||
if (map.containsKey("dynamicCols")) h.setDynamicCols(parseInt(map.get("dynamicCols"), 160));
|
||||
if (map.containsKey("luName")) h.setLuName(String.valueOf(map.get("luName")));
|
||||
if (map.containsKey("autoConnect")) h.setAutoConnect(parseBool(map.get("autoConnect"), false));
|
||||
if (map.containsKey("hostType")) h.setHostType(String.valueOf(map.get("hostType")));
|
||||
if (map.containsKey("useTls")) h.setUseTls(parseBool(map.get("useTls"), false));
|
||||
if (map.containsKey("tlsVerifyCert")) h.setTlsVerifyCert(parseBool(map.get("tlsVerifyCert"), true));
|
||||
if (map.containsKey("tn3270e")) h.setTn3270e(parseBool(map.get("tn3270e"), true));
|
||||
if (map.containsKey("graphicsMode")) {
|
||||
String gm = String.valueOf(map.get("graphicsMode"));
|
||||
h.setGraphicsMode(gm);
|
||||
}
|
||||
if (map.containsKey("codePage")) h.setCodePage(String.valueOf(map.get("codePage")));
|
||||
if (map.containsKey("proxyType")) h.setProxyType(String.valueOf(map.get("proxyType")));
|
||||
if (map.containsKey("proxyHost")) h.setProxyHost(String.valueOf(map.get("proxyHost")));
|
||||
if (map.containsKey("proxyPort")) h.setProxyPort(parseInt(map.get("proxyPort"), 0));
|
||||
if (map.containsKey("proxyUsername")) h.setProxyUsername(String.valueOf(map.get("proxyUsername")));
|
||||
if (map.containsKey("proxyPassword")) h.setProxyPassword(String.valueOf(map.get("proxyPassword")));
|
||||
return h;
|
||||
}
|
||||
|
||||
private static int parseInt(Object obj, int def) {
|
||||
if (obj == null) return def;
|
||||
if (obj instanceof Number) return ((Number) obj).intValue();
|
||||
try {
|
||||
return Integer.parseInt(String.valueOf(obj).trim());
|
||||
} catch (Exception e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean parseBool(Object obj, boolean def) {
|
||||
if (obj == null) return def;
|
||||
if (obj instanceof Boolean) return (Boolean) obj;
|
||||
return Boolean.parseBoolean(String.valueOf(obj).trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal pure-Java JSON parser for nested Maps, Lists, Strings, Numbers, and Booleans.
|
||||
*/
|
||||
static class SimpleJsonParser {
|
||||
private final String src;
|
||||
private int pos = 0;
|
||||
|
||||
SimpleJsonParser(String src) {
|
||||
this.src = src;
|
||||
}
|
||||
|
||||
Object parseValue() {
|
||||
skipWhitespace();
|
||||
if (pos >= src.length()) return null;
|
||||
char c = src.charAt(pos);
|
||||
if (c == '{') return parseObject();
|
||||
if (c == '[') return parseArray();
|
||||
if (c == '"') return parseString();
|
||||
if (c == 't' || c == 'f') return parseBoolean();
|
||||
if (c == 'n') return parseNull();
|
||||
if (c == '-' || Character.isDigit(c)) return parseNumber();
|
||||
throw new IllegalArgumentException("Unexpected char '" + c + "' at position " + pos);
|
||||
}
|
||||
|
||||
private Map<String, Object> parseObject() {
|
||||
match('{');
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
skipWhitespace();
|
||||
if (peek() == '}') {
|
||||
pos++;
|
||||
return map;
|
||||
}
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
String key = parseString();
|
||||
skipWhitespace();
|
||||
match(':');
|
||||
Object val = parseValue();
|
||||
map.put(key, val);
|
||||
skipWhitespace();
|
||||
char c = peek();
|
||||
if (c == ',') {
|
||||
pos++;
|
||||
} else if (c == '}') {
|
||||
pos++;
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private List<Object> parseArray() {
|
||||
match('[');
|
||||
List<Object> list = new ArrayList<>();
|
||||
skipWhitespace();
|
||||
if (peek() == ']') {
|
||||
pos++;
|
||||
return list;
|
||||
}
|
||||
while (true) {
|
||||
Object val = parseValue();
|
||||
list.add(val);
|
||||
skipWhitespace();
|
||||
char c = peek();
|
||||
if (c == ',') {
|
||||
pos++;
|
||||
} else if (c == ']') {
|
||||
pos++;
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private String parseString() {
|
||||
match('"');
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (pos < src.length()) {
|
||||
char c = src.charAt(pos++);
|
||||
if (c == '"') {
|
||||
return sb.toString();
|
||||
} else if (c == '\\') {
|
||||
if (pos >= src.length()) break;
|
||||
char esc = src.charAt(pos++);
|
||||
switch (esc) {
|
||||
case '"': sb.append('"'); break;
|
||||
case '\\': sb.append('\\'); break;
|
||||
case '/': sb.append('/'); break;
|
||||
case 'b': sb.append('\b'); break;
|
||||
case 'f': sb.append('\f'); break;
|
||||
case 'n': sb.append('\n'); break;
|
||||
case 'r': sb.append('\r'); break;
|
||||
case 't': sb.append('\t'); break;
|
||||
case 'u':
|
||||
if (pos + 4 <= src.length()) {
|
||||
String hex = src.substring(pos, pos + 4);
|
||||
pos += 4;
|
||||
try {
|
||||
sb.append((char) Integer.parseInt(hex, 16));
|
||||
} catch (NumberFormatException e) {
|
||||
sb.append("?");
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sb.append(esc);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private Boolean parseBoolean() {
|
||||
if (src.startsWith("true", pos)) {
|
||||
pos += 4;
|
||||
return true;
|
||||
} else if (src.startsWith("false", pos)) {
|
||||
pos += 5;
|
||||
return false;
|
||||
}
|
||||
throw new IllegalArgumentException("Expected boolean at " + pos);
|
||||
}
|
||||
|
||||
private Object parseNull() {
|
||||
if (src.startsWith("null", pos)) {
|
||||
pos += 4;
|
||||
return null;
|
||||
}
|
||||
throw new IllegalArgumentException("Expected null at " + pos);
|
||||
}
|
||||
|
||||
private Number parseNumber() {
|
||||
int start = pos;
|
||||
if (src.charAt(pos) == '-') pos++;
|
||||
while (pos < src.length() && (Character.isDigit(src.charAt(pos)) || src.charAt(pos) == '.' || src.charAt(pos) == 'e' || src.charAt(pos) == 'E' || src.charAt(pos) == '+' || src.charAt(pos) == '-')) {
|
||||
pos++;
|
||||
}
|
||||
String numStr = src.substring(start, pos);
|
||||
if (numStr.contains(".")) {
|
||||
return Double.parseDouble(numStr);
|
||||
}
|
||||
return Long.parseLong(numStr);
|
||||
}
|
||||
|
||||
private void skipWhitespace() {
|
||||
while (pos < src.length() && Character.isWhitespace(src.charAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
private char peek() {
|
||||
return pos < src.length() ? src.charAt(pos) : '\0';
|
||||
}
|
||||
|
||||
private void match(char expected) {
|
||||
skipWhitespace();
|
||||
if (pos >= src.length() || src.charAt(pos) != expected) {
|
||||
throw new IllegalArgumentException("Expected '" + expected + "' at " + pos);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package haus.nightmare.j3270.storage;
|
||||
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.graphics.GraphicsMode;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Data model for a saved 3270 connection profile, matching the schema of a3270 SavedHost.
|
||||
* Includes per-connection host, model, protocol, and proxy configuration.
|
||||
*/
|
||||
public class SavedHost {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String host;
|
||||
private int port;
|
||||
private int model;
|
||||
private int dynamicRows;
|
||||
private int dynamicCols;
|
||||
private String luName;
|
||||
private boolean autoConnect;
|
||||
private String hostType;
|
||||
private boolean useTls;
|
||||
private boolean tlsVerifyCert;
|
||||
private boolean tn3270e;
|
||||
private String graphicsMode;
|
||||
private String codePage;
|
||||
|
||||
// Per-connection proxy configuration
|
||||
private String proxyType;
|
||||
private String proxyHost;
|
||||
private int proxyPort;
|
||||
private String proxyUsername;
|
||||
private String proxyPassword;
|
||||
|
||||
public SavedHost() {
|
||||
this.id = UUID.randomUUID().toString();
|
||||
this.name = "Mainframe";
|
||||
this.host = "127.0.0.1";
|
||||
this.port = 23;
|
||||
this.model = 4;
|
||||
this.dynamicRows = 62;
|
||||
this.dynamicCols = 160;
|
||||
this.luName = "";
|
||||
this.autoConnect = false;
|
||||
this.hostType = "TSO";
|
||||
this.useTls = false;
|
||||
this.tlsVerifyCert = true;
|
||||
this.tn3270e = true;
|
||||
this.graphicsMode = "BOTH";
|
||||
this.codePage = "037";
|
||||
this.proxyType = "NONE";
|
||||
this.proxyHost = "";
|
||||
this.proxyPort = 0;
|
||||
this.proxyUsername = "";
|
||||
this.proxyPassword = "";
|
||||
}
|
||||
|
||||
public SavedHost(String id, String name, String host, int port, int model,
|
||||
int dynamicRows, int dynamicCols, String luName, boolean autoConnect,
|
||||
String hostType, boolean useTls, boolean tlsVerifyCert, boolean tn3270e,
|
||||
String graphicsMode, String codePage, String proxyType, String proxyHost,
|
||||
int proxyPort, String proxyUsername, String proxyPassword) {
|
||||
this.id = (id != null && !id.trim().isEmpty()) ? id : UUID.randomUUID().toString();
|
||||
this.name = name != null ? name : "";
|
||||
this.host = host != null ? host : "127.0.0.1";
|
||||
this.port = port > 0 ? port : 23;
|
||||
this.model = model;
|
||||
this.dynamicRows = dynamicRows > 0 ? dynamicRows : 62;
|
||||
this.dynamicCols = dynamicCols > 0 ? dynamicCols : 160;
|
||||
this.luName = luName != null ? luName : "";
|
||||
this.autoConnect = autoConnect;
|
||||
this.hostType = hostType != null ? hostType : "TSO";
|
||||
this.useTls = useTls;
|
||||
this.tlsVerifyCert = tlsVerifyCert;
|
||||
this.tn3270e = tn3270e;
|
||||
this.graphicsMode = graphicsMode != null ? graphicsMode : "BOTH";
|
||||
this.codePage = codePage != null ? codePage : "037";
|
||||
this.proxyType = (proxyType != null && !proxyType.trim().isEmpty()) ? proxyType.toUpperCase() : "NONE";
|
||||
this.proxyHost = proxyHost != null ? proxyHost : "";
|
||||
this.proxyPort = proxyPort;
|
||||
this.proxyUsername = proxyUsername != null ? proxyUsername : "";
|
||||
this.proxyPassword = proxyPassword != null ? proxyPassword : "";
|
||||
}
|
||||
|
||||
public SavedHost copy() {
|
||||
return new SavedHost(id, name, host, port, model, dynamicRows, dynamicCols, luName,
|
||||
autoConnect, hostType, useTls, tlsVerifyCert, tn3270e, graphicsMode, codePage,
|
||||
proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this saved host profile into an active ConnectionConfig.
|
||||
*/
|
||||
public ConnectionConfig toConnectionConfig() {
|
||||
TerminalModel tm;
|
||||
if (model == 0) {
|
||||
tm = TerminalModel.IBM_DYNAMIC;
|
||||
} else {
|
||||
try {
|
||||
tm = TerminalModel.forModel(model, true);
|
||||
} catch (Exception e) {
|
||||
tm = TerminalModel.IBM_3279_4;
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionConfig config = new ConnectionConfig(host, port, tm, useTls);
|
||||
if (model == 0 || tm.isDynamic()) {
|
||||
config.setDynamicDimensions(dynamicRows, dynamicCols);
|
||||
}
|
||||
if (luName != null && !luName.trim().isEmpty()) {
|
||||
config.setLuName(luName.trim());
|
||||
}
|
||||
config.setTlsVerifyCert(tlsVerifyCert);
|
||||
config.setTn3270eEnabled(tn3270e);
|
||||
if (graphicsMode != null && !graphicsMode.isEmpty()) {
|
||||
config.setGraphicsMode(GraphicsMode.fromString(graphicsMode));
|
||||
}
|
||||
if (codePage != null && !codePage.isEmpty()) {
|
||||
config.setCodePage(codePage);
|
||||
}
|
||||
|
||||
// Apply per-connection proxy if configured
|
||||
if (proxyType != null && !proxyType.equalsIgnoreCase("NONE") && proxyHost != null && !proxyHost.trim().isEmpty()) {
|
||||
try {
|
||||
ConnectionConfig.ProxyType pt = ConnectionConfig.ProxyType.valueOf(proxyType.toUpperCase());
|
||||
int pPort = proxyPort > 0 ? proxyPort : (pt == ConnectionConfig.ProxyType.HTTP ? 8080 : 1080);
|
||||
config.setProxy(pt, proxyHost.trim(), pPort,
|
||||
(proxyUsername != null && !proxyUsername.trim().isEmpty()) ? proxyUsername.trim() : null,
|
||||
(proxyPassword != null && !proxyPassword.isEmpty()) ? proxyPassword : null);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
|
||||
public String getId() { return id; }
|
||||
public void setId(String id) { this.id = id; }
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public String getHost() { return host; }
|
||||
public void setHost(String host) { this.host = host; }
|
||||
|
||||
public int getPort() { return port; }
|
||||
public void setPort(int port) { this.port = port; }
|
||||
|
||||
public int getModel() { return model; }
|
||||
public void setModel(int model) { this.model = model; }
|
||||
|
||||
public int getDynamicRows() { return dynamicRows; }
|
||||
public void setDynamicRows(int dynamicRows) { this.dynamicRows = dynamicRows; }
|
||||
|
||||
public int getDynamicCols() { return dynamicCols; }
|
||||
public void setDynamicCols(int dynamicCols) { this.dynamicCols = dynamicCols; }
|
||||
|
||||
public String getLuName() { return luName; }
|
||||
public void setLuName(String luName) { this.luName = luName; }
|
||||
|
||||
public boolean isAutoConnect() { return autoConnect; }
|
||||
public void setAutoConnect(boolean autoConnect) { this.autoConnect = autoConnect; }
|
||||
|
||||
public String getHostType() { return hostType; }
|
||||
public void setHostType(String hostType) { this.hostType = hostType; }
|
||||
|
||||
public boolean isUseTls() { return useTls; }
|
||||
public void setUseTls(boolean useTls) { this.useTls = useTls; }
|
||||
|
||||
public boolean isTlsVerifyCert() { return tlsVerifyCert; }
|
||||
public void setTlsVerifyCert(boolean tlsVerifyCert) { this.tlsVerifyCert = tlsVerifyCert; }
|
||||
|
||||
public boolean isTn3270e() { return tn3270e; }
|
||||
public void setTn3270e(boolean tn3270e) { this.tn3270e = tn3270e; }
|
||||
|
||||
public String getGraphicsMode() { return graphicsMode; }
|
||||
public void setGraphicsMode(String graphicsMode) { this.graphicsMode = graphicsMode; }
|
||||
|
||||
public String getCodePage() { return codePage; }
|
||||
public void setCodePage(String codePage) { this.codePage = codePage; }
|
||||
|
||||
public String getProxyType() { return proxyType != null ? proxyType : "NONE"; }
|
||||
public void setProxyType(String proxyType) { this.proxyType = (proxyType != null) ? proxyType.toUpperCase() : "NONE"; }
|
||||
|
||||
public String getProxyHost() { return proxyHost != null ? proxyHost : ""; }
|
||||
public void setProxyHost(String proxyHost) { this.proxyHost = proxyHost; }
|
||||
|
||||
public int getProxyPort() { return proxyPort; }
|
||||
public void setProxyPort(int proxyPort) { this.proxyPort = proxyPort; }
|
||||
|
||||
public String getProxyUsername() { return proxyUsername != null ? proxyUsername : ""; }
|
||||
public void setProxyUsername(String proxyUsername) { this.proxyUsername = proxyUsername; }
|
||||
|
||||
public String getProxyPassword() { return proxyPassword != null ? proxyPassword : ""; }
|
||||
public void setProxyPassword(String proxyPassword) { this.proxyPassword = proxyPassword; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (name != null && !name.trim().isEmpty()) {
|
||||
return name;
|
||||
}
|
||||
return host + (port > 0 ? ":" + port : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SavedHost savedHost = (SavedHost) o;
|
||||
return Objects.equals(id, savedHost.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Utility for loading and applying application window and taskbar icons.
|
||||
* Loads multi-resolution lib3270j icons from resources.
|
||||
*/
|
||||
public final class AppIcon {
|
||||
|
||||
private static final Logger log = Logger.getLogger(AppIcon.class.getName());
|
||||
|
||||
private static final int[] ICON_SIZES = {16, 24, 32, 48, 64, 128, 256, 512};
|
||||
private static List<Image> cachedIcons = null;
|
||||
private static Image defaultIcon = null;
|
||||
|
||||
private AppIcon() {}
|
||||
|
||||
/**
|
||||
* Retrieves the list of available application icon images in various resolutions.
|
||||
*
|
||||
* @return an unmodifiable list of Image objects, or empty list if none could be loaded.
|
||||
*/
|
||||
public static synchronized List<Image> getAppIcons() {
|
||||
if (cachedIcons == null) {
|
||||
loadIcons();
|
||||
}
|
||||
return cachedIcons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the default application icon image (256x256 or best available).
|
||||
*
|
||||
* @return the default Image, or null if no icon is available.
|
||||
*/
|
||||
public static synchronized Image getDefaultIcon() {
|
||||
if (cachedIcons == null) {
|
||||
loadIcons();
|
||||
}
|
||||
return defaultIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the application icons to a target Window (JFrame, JDialog, etc.)
|
||||
* and updates the desktop Taskbar icon if supported.
|
||||
*
|
||||
* @param window the Window to set icon images for.
|
||||
*/
|
||||
public static void applyTo(Window window) {
|
||||
if (GraphicsEnvironment.isHeadless()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Image> icons = getAppIcons();
|
||||
if (icons.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window != null) {
|
||||
window.setIconImages(icons);
|
||||
}
|
||||
|
||||
// Apply to OS Taskbar / Dock if supported (Java 9+)
|
||||
try {
|
||||
if (Taskbar.isTaskbarSupported()) {
|
||||
Taskbar taskbar = Taskbar.getTaskbar();
|
||||
if (taskbar.isSupported(Taskbar.Feature.ICON_IMAGE)) {
|
||||
Image bestIcon = defaultIcon != null ? defaultIcon : icons.get(icons.size() - 1);
|
||||
taskbar.setIconImage(bestIcon);
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
log.log(Level.FINE, "Taskbar icon could not be set: {0}", t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadIcons() {
|
||||
List<Image> icons = new ArrayList<>();
|
||||
|
||||
for (int size : ICON_SIZES) {
|
||||
String path = "/icons/lib3270j_" + size + ".png";
|
||||
Image img = loadImage(path);
|
||||
if (img != null) {
|
||||
icons.add(img);
|
||||
}
|
||||
}
|
||||
|
||||
if (icons.isEmpty()) {
|
||||
Image img = loadImage("/icons/lib3270j.png");
|
||||
if (img != null) {
|
||||
icons.add(img);
|
||||
}
|
||||
}
|
||||
|
||||
if (!icons.isEmpty()) {
|
||||
for (Image img : icons) {
|
||||
if (img.getWidth(null) == 256) {
|
||||
defaultIcon = img;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (defaultIcon == null) {
|
||||
defaultIcon = icons.get(icons.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
cachedIcons = Collections.unmodifiableList(icons);
|
||||
}
|
||||
|
||||
private static Image loadImage(String resourcePath) {
|
||||
try {
|
||||
URL url = AppIcon.class.getResource(resourcePath);
|
||||
if (url != null) {
|
||||
BufferedImage img = ImageIO.read(url);
|
||||
if (img != null) {
|
||||
return img;
|
||||
}
|
||||
}
|
||||
try (InputStream in = AppIcon.class.getResourceAsStream(resourcePath)) {
|
||||
if (in != null) {
|
||||
return ImageIO.read(in);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.log(Level.FINE, "Failed to load icon from " + resourcePath, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,31 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.j3270.storage.HostStorage;
|
||||
import haus.nightmare.j3270.storage.SavedHost;
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.border.TitledBorder;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Connection dialog for entering host, port, model, and LU name.
|
||||
* Connection dialog for entering host, port, model, LU name, and per-connection proxy configuration.
|
||||
* Supports loading and saving host profiles matching the a3270 schema.
|
||||
*/
|
||||
public class ConnectDialog extends JDialog {
|
||||
|
||||
private static final String NEW_PROFILE_LABEL = "<New / Custom Connection>";
|
||||
|
||||
// Profile selector
|
||||
private JComboBox<Object> profileCombo;
|
||||
private JButton saveProfileBtn;
|
||||
private JButton deleteProfileBtn;
|
||||
private String selectedProfileId = null;
|
||||
|
||||
// Connection parameters
|
||||
private JTextField hostField;
|
||||
private JTextField portField;
|
||||
private JComboBox<TerminalModel> modelCombo;
|
||||
@@ -26,72 +41,131 @@ public class ConnectDialog extends JDialog {
|
||||
private JCheckBox tn3270eCheckBox;
|
||||
private JCheckBox keepAliveCheckBox;
|
||||
private JCheckBox autoReconnectCheckBox;
|
||||
|
||||
// Per-connection proxy configuration
|
||||
private JComboBox<ConnectionConfig.ProxyType> proxyTypeCombo;
|
||||
private JTextField proxyHostField;
|
||||
private JTextField proxyPortField;
|
||||
private JTextField proxyUserField;
|
||||
private JPasswordField proxyPassField;
|
||||
|
||||
private boolean confirmed;
|
||||
private ConnectionConfig result;
|
||||
|
||||
public ConnectDialog(Frame parent) {
|
||||
super(parent, "Connect to Host", true);
|
||||
AppIcon.applyTo(this);
|
||||
buildUI();
|
||||
pack();
|
||||
setLocationRelativeTo(parent);
|
||||
setResizable(false);
|
||||
setResizable(true);
|
||||
}
|
||||
|
||||
private void buildUI() {
|
||||
JPanel mainPanel = new JPanel(new GridBagLayout());
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
|
||||
JPanel contentPanel = new JPanel(new GridBagLayout());
|
||||
contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 14, 12, 14));
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.insets = new Insets(5, 6, 5, 6);
|
||||
gbc.insets = new Insets(4, 5, 4, 5);
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
|
||||
Font labelFont = ThemeManager.getUiFont();
|
||||
|
||||
// Host
|
||||
int gridy = 0;
|
||||
|
||||
// 0. Profile Row
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 0;
|
||||
gbc.gridy = gridy;
|
||||
gbc.weightx = 0;
|
||||
JLabel profileLabel = new JLabel("Profile:");
|
||||
profileLabel.setFont(labelFont);
|
||||
contentPanel.add(profileLabel, gbc);
|
||||
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 1.0;
|
||||
JPanel profileBar = new JPanel(new BorderLayout(6, 0));
|
||||
profileBar.setOpaque(false);
|
||||
|
||||
profileCombo = new JComboBox<>();
|
||||
profileCombo.setFont(ThemeManager.getUiFont());
|
||||
ThemeManager.styleComboBox(profileCombo);
|
||||
refreshProfileCombo();
|
||||
profileCombo.addActionListener(e -> onProfileSelectionChanged());
|
||||
profileBar.add(profileCombo, BorderLayout.CENTER);
|
||||
|
||||
JPanel profileBtnBar = new JPanel(new FlowLayout(FlowLayout.RIGHT, 4, 0));
|
||||
profileBtnBar.setOpaque(false);
|
||||
|
||||
saveProfileBtn = new JButton("Save Profile...");
|
||||
ThemeManager.styleButton(saveProfileBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
saveProfileBtn.setFont(labelFont);
|
||||
saveProfileBtn.addActionListener(e -> onSaveProfile());
|
||||
|
||||
deleteProfileBtn = new JButton("Delete");
|
||||
ThemeManager.styleButton(deleteProfileBtn, ThemeManager.ButtonVariant.CANCEL);
|
||||
deleteProfileBtn.setFont(labelFont);
|
||||
deleteProfileBtn.setEnabled(false);
|
||||
deleteProfileBtn.addActionListener(e -> onDeleteProfile());
|
||||
|
||||
profileBtnBar.add(saveProfileBtn);
|
||||
profileBtnBar.add(deleteProfileBtn);
|
||||
profileBar.add(profileBtnBar, BorderLayout.EAST);
|
||||
contentPanel.add(profileBar, gbc);
|
||||
|
||||
gridy++;
|
||||
|
||||
// 1. Host
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = gridy;
|
||||
gbc.weightx = 0;
|
||||
JLabel hostLabel = new JLabel("Host:");
|
||||
hostLabel.setFont(labelFont);
|
||||
mainPanel.add(hostLabel, gbc);
|
||||
contentPanel.add(hostLabel, gbc);
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 1.0;
|
||||
hostField = createField(20);
|
||||
mainPanel.add(hostField, gbc);
|
||||
contentPanel.add(hostField, gbc);
|
||||
|
||||
// Port
|
||||
gridy++;
|
||||
|
||||
// 2. Port
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 1;
|
||||
gbc.gridy = gridy;
|
||||
gbc.weightx = 0;
|
||||
JLabel portLabel = new JLabel("Port:");
|
||||
portLabel.setFont(labelFont);
|
||||
mainPanel.add(portLabel, gbc);
|
||||
contentPanel.add(portLabel, gbc);
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 1.0;
|
||||
portField = createField(6);
|
||||
portField.setText("23");
|
||||
mainPanel.add(portField, gbc);
|
||||
contentPanel.add(portField, gbc);
|
||||
|
||||
// Model
|
||||
gridy++;
|
||||
|
||||
// 3. Model
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 2;
|
||||
gbc.gridy = gridy;
|
||||
gbc.weightx = 0;
|
||||
JLabel modelLabel = new JLabel("Model:");
|
||||
modelLabel.setFont(labelFont);
|
||||
mainPanel.add(modelLabel, gbc);
|
||||
contentPanel.add(modelLabel, gbc);
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 1.0;
|
||||
modelCombo = new JComboBox<>(TerminalModel.values());
|
||||
modelCombo.setSelectedItem(TerminalModel.IBM_3279_4);
|
||||
modelCombo.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||
modelCombo.setFont(ThemeManager.getMonospacedUiFont());
|
||||
ThemeManager.styleComboBox(modelCombo);
|
||||
mainPanel.add(modelCombo, gbc);
|
||||
contentPanel.add(modelCombo, gbc);
|
||||
|
||||
// Dynamic Dimensions (shown when IBM-DYNAMIC is selected)
|
||||
gridy++;
|
||||
|
||||
// 4. Dynamic Dimensions
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 3;
|
||||
gbc.gridy = gridy;
|
||||
gbc.weightx = 0;
|
||||
dynamicDimLabel = new JLabel("Screen Size:");
|
||||
dynamicDimLabel.setFont(labelFont);
|
||||
mainPanel.add(dynamicDimLabel, gbc);
|
||||
contentPanel.add(dynamicDimLabel, gbc);
|
||||
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 1.0;
|
||||
@@ -112,7 +186,7 @@ public class ConnectDialog extends JDialog {
|
||||
dynamicDimPanel.add(dynamicRowsSpinner);
|
||||
dynamicDimPanel.add(colsLabel);
|
||||
dynamicDimPanel.add(dynamicColsSpinner);
|
||||
mainPanel.add(dynamicDimPanel, gbc);
|
||||
contentPanel.add(dynamicDimPanel, gbc);
|
||||
|
||||
Runnable updateDynamicVisibility = () -> {
|
||||
TerminalModel m = (TerminalModel) modelCombo.getSelectedItem();
|
||||
@@ -124,40 +198,64 @@ public class ConnectDialog extends JDialog {
|
||||
modelCombo.addActionListener(e -> updateDynamicVisibility.run());
|
||||
updateDynamicVisibility.run();
|
||||
|
||||
// LU Name
|
||||
gridy++;
|
||||
|
||||
// 5. LU Name
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 4;
|
||||
gbc.gridy = gridy;
|
||||
gbc.weightx = 0;
|
||||
JLabel luLabel = new JLabel("LU Name:");
|
||||
luLabel.setFont(labelFont);
|
||||
mainPanel.add(luLabel, gbc);
|
||||
contentPanel.add(luLabel, gbc);
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 1.0;
|
||||
luField = createField(12);
|
||||
mainPanel.add(luField, gbc);
|
||||
contentPanel.add(luField, gbc);
|
||||
|
||||
// Graphics Mode
|
||||
gridy++;
|
||||
|
||||
// 6. Graphics Mode
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 5;
|
||||
gbc.gridy = gridy;
|
||||
gbc.weightx = 0;
|
||||
JLabel graphicsLabel = new JLabel("Graphics:");
|
||||
graphicsLabel.setFont(labelFont);
|
||||
mainPanel.add(graphicsLabel, gbc);
|
||||
contentPanel.add(graphicsLabel, gbc);
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 1.0;
|
||||
graphicsCombo = new JComboBox<>(haus.nightmare.lib3270j.graphics.GraphicsMode.values());
|
||||
graphicsCombo.setSelectedItem(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||
graphicsCombo.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||
graphicsCombo.setFont(ThemeManager.getUiFont());
|
||||
ThemeManager.styleComboBox(graphicsCombo);
|
||||
mainPanel.add(graphicsCombo, gbc);
|
||||
graphicsCombo.setRenderer(new DefaultListCellRenderer() {
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
setBorder(new EmptyBorder(4, 8, 4, 8));
|
||||
if (value instanceof haus.nightmare.lib3270j.graphics.GraphicsMode) {
|
||||
setText(((haus.nightmare.lib3270j.graphics.GraphicsMode) value).getDescription());
|
||||
}
|
||||
if (isSelected) {
|
||||
setBackground(ThemeManager.getSelectionBg());
|
||||
setForeground(ThemeManager.getSelectionFg());
|
||||
} else {
|
||||
setBackground(ThemeManager.getBgComponent());
|
||||
setForeground(ThemeManager.getFgMain());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
contentPanel.add(graphicsCombo, gbc);
|
||||
|
||||
// Code Page
|
||||
gridy++;
|
||||
|
||||
// 7. Code Page
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 6;
|
||||
gbc.gridy = gridy;
|
||||
gbc.weightx = 0;
|
||||
JLabel cpLabel = new JLabel("Code Page:");
|
||||
cpLabel.setFont(labelFont);
|
||||
mainPanel.add(cpLabel, gbc);
|
||||
contentPanel.add(cpLabel, gbc);
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 1.0;
|
||||
String[] commonCodePages = {
|
||||
@@ -178,43 +276,44 @@ public class ConnectDialog extends JDialog {
|
||||
"1155 - Turkey (Turkish Latin-5 Euro \u20AC)",
|
||||
"905 - Turkey (Turkish Latin-3)",
|
||||
"420 - Arabic Bilingual",
|
||||
"424 - Hebrew (with Lowercase)",
|
||||
"803 - Hebrew Old / Standard",
|
||||
"838 - Thai",
|
||||
"1160 - Thai (Euro \u20AC)",
|
||||
"424 - Hebrew (with Latin)",
|
||||
"803 - Hebrew Character Set",
|
||||
"838 - Thai Extended",
|
||||
"1160 - Thai Euro \u20AC",
|
||||
"1025 - Cyrillic Multilingual",
|
||||
"1123 - Cyrillic Ukraine",
|
||||
"1154 - Cyrillic Multilingual (Euro \u20AC)",
|
||||
"880 - Cyrillic Russian",
|
||||
"1154 - Cyrillic Euro \u20AC",
|
||||
"880 - Cyrillic (Russian)",
|
||||
"1140 - US / Canada (Euro \u20AC)",
|
||||
"1141 - Germany / Austria (Euro \u20AC)",
|
||||
"1142 - Denmark / Norway (Euro \u20AC)",
|
||||
"1143 - Sweden / Finland (Euro \u20AC)",
|
||||
"1144 - Italy (Euro \u20AC)",
|
||||
"1145 - Spain / Latin America (Euro \u20AC)",
|
||||
"1146 - United Kingdom (Euro \u20AC)",
|
||||
"1147 - France (Euro \u20AC)",
|
||||
"1148 - International (Euro \u20AC)",
|
||||
"1149 - Iceland (Euro \u20AC)",
|
||||
"930 - Japanese Katakana Mixed DBCS",
|
||||
"939 - Japanese Latin Mixed DBCS",
|
||||
"935 - Simplified Chinese Mixed DBCS",
|
||||
"1388 - Simplified Chinese Extended Mixed DBCS",
|
||||
"937 - Traditional Chinese Mixed DBCS",
|
||||
"1371 - Traditional Chinese Extended Mixed DBCS",
|
||||
"1388 - Simplified Chinese Extended DBCS",
|
||||
"1371 - Traditional Chinese Extended DBCS",
|
||||
"933 - Korean Mixed DBCS"
|
||||
};
|
||||
codePageCombo = new JComboBox<>(commonCodePages);
|
||||
codePageCombo.setEditable(true);
|
||||
String currentCp = haus.nightmare.j3270.config.Settings.getCodePage();
|
||||
for (String item : commonCodePages) {
|
||||
if (item.startsWith(currentCp + " ") || item.equals(currentCp)) {
|
||||
codePageCombo.setSelectedItem(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
codePageCombo.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||
codePageCombo.setFont(ThemeManager.getMonospacedUiFont());
|
||||
ThemeManager.styleComboBox(codePageCombo);
|
||||
mainPanel.add(codePageCombo, gbc);
|
||||
setInitialCodePage(haus.nightmare.j3270.config.Settings.getCodePage());
|
||||
contentPanel.add(codePageCombo, gbc);
|
||||
|
||||
// TLS / SSL Checkbox
|
||||
gridy++;
|
||||
|
||||
// 8. TLS Checkbox
|
||||
gbc.gridx = 1;
|
||||
gbc.gridy = 7;
|
||||
gbc.weightx = 1.0;
|
||||
tlsCheckBox = new JCheckBox("Enable TLS/SSL");
|
||||
gbc.gridy = gridy;
|
||||
tlsCheckBox = new JCheckBox("Use TLS / SSL Encryption");
|
||||
ThemeManager.styleCheckBox(tlsCheckBox);
|
||||
tlsCheckBox.setFont(labelFont);
|
||||
tlsCheckBox.addActionListener(e -> {
|
||||
@@ -227,46 +326,169 @@ public class ConnectDialog extends JDialog {
|
||||
portField.setText("23");
|
||||
}
|
||||
});
|
||||
mainPanel.add(tlsCheckBox, gbc);
|
||||
contentPanel.add(tlsCheckBox, gbc);
|
||||
|
||||
// Verify Certificate Checkbox
|
||||
gridy++;
|
||||
|
||||
// 9. Verify Cert Checkbox
|
||||
gbc.gridx = 1;
|
||||
gbc.gridy = 8;
|
||||
gbc.gridy = gridy;
|
||||
verifyCertCheckBox = new JCheckBox("Verify Server Certificate");
|
||||
ThemeManager.styleCheckBox(verifyCertCheckBox);
|
||||
verifyCertCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||
verifyCertCheckBox.setFont(ThemeManager.getUiFont());
|
||||
verifyCertCheckBox.setSelected(true);
|
||||
verifyCertCheckBox.setEnabled(false);
|
||||
mainPanel.add(verifyCertCheckBox, gbc);
|
||||
contentPanel.add(verifyCertCheckBox, gbc);
|
||||
|
||||
// TN3270E Checkbox
|
||||
gridy++;
|
||||
|
||||
// 10. TN3270E Checkbox
|
||||
gbc.gridx = 1;
|
||||
gbc.gridy = 9;
|
||||
gbc.gridy = gridy;
|
||||
tn3270eCheckBox = new JCheckBox("Enable TN3270E (Extended 3270)");
|
||||
ThemeManager.styleCheckBox(tn3270eCheckBox);
|
||||
tn3270eCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||
tn3270eCheckBox.setFont(ThemeManager.getUiFont());
|
||||
tn3270eCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectTn3270e());
|
||||
mainPanel.add(tn3270eCheckBox, gbc);
|
||||
contentPanel.add(tn3270eCheckBox, gbc);
|
||||
|
||||
// Keep-Alive Checkbox
|
||||
gridy++;
|
||||
|
||||
// 11. Keep-Alive Checkbox
|
||||
gbc.gridx = 1;
|
||||
gbc.gridy = 10;
|
||||
gbc.gridy = gridy;
|
||||
keepAliveCheckBox = new JCheckBox("Enable Keep-Alive Heartbeat (NOP)");
|
||||
ThemeManager.styleCheckBox(keepAliveCheckBox);
|
||||
keepAliveCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||
keepAliveCheckBox.setFont(ThemeManager.getUiFont());
|
||||
keepAliveCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectKeepAlive());
|
||||
mainPanel.add(keepAliveCheckBox, gbc);
|
||||
contentPanel.add(keepAliveCheckBox, gbc);
|
||||
|
||||
// Auto-Reconnect Checkbox
|
||||
gridy++;
|
||||
|
||||
// 12. Auto-Reconnect Checkbox
|
||||
gbc.gridx = 1;
|
||||
gbc.gridy = 11;
|
||||
gbc.gridy = gridy;
|
||||
autoReconnectCheckBox = new JCheckBox("Auto-Reconnect on Disconnect");
|
||||
ThemeManager.styleCheckBox(autoReconnectCheckBox);
|
||||
autoReconnectCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||
autoReconnectCheckBox.setFont(ThemeManager.getUiFont());
|
||||
autoReconnectCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectAutoReconnect());
|
||||
mainPanel.add(autoReconnectCheckBox, gbc);
|
||||
contentPanel.add(autoReconnectCheckBox, gbc);
|
||||
|
||||
gridy++;
|
||||
|
||||
// 13. PER-CONNECTION PROXY CONFIGURATION PANEL
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = gridy;
|
||||
gbc.gridwidth = 2;
|
||||
gbc.weightx = 1.0;
|
||||
|
||||
JPanel proxyGroup = new JPanel(new GridBagLayout());
|
||||
proxyGroup.setBorder(BorderFactory.createTitledBorder(
|
||||
BorderFactory.createEtchedBorder(),
|
||||
"Proxy Configuration (This Connection)",
|
||||
TitledBorder.LEFT,
|
||||
TitledBorder.TOP,
|
||||
labelFont
|
||||
));
|
||||
GridBagConstraints pgbc = new GridBagConstraints();
|
||||
pgbc.insets = new Insets(3, 6, 3, 6);
|
||||
pgbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
// Proxy Type
|
||||
pgbc.gridx = 0;
|
||||
pgbc.gridy = 0;
|
||||
pgbc.weightx = 0;
|
||||
JLabel ptLabel = new JLabel("Proxy Type:");
|
||||
ptLabel.setFont(labelFont);
|
||||
proxyGroup.add(ptLabel, pgbc);
|
||||
|
||||
pgbc.gridx = 1;
|
||||
pgbc.gridwidth = 3;
|
||||
pgbc.weightx = 1.0;
|
||||
proxyTypeCombo = new JComboBox<>(ConnectionConfig.ProxyType.values());
|
||||
proxyTypeCombo.setSelectedItem(ConnectionConfig.ProxyType.NONE);
|
||||
proxyTypeCombo.setFont(ThemeManager.getUiFont());
|
||||
ThemeManager.styleComboBox(proxyTypeCombo);
|
||||
proxyGroup.add(proxyTypeCombo, pgbc);
|
||||
|
||||
// Proxy Host & Port
|
||||
pgbc.gridx = 0;
|
||||
pgbc.gridy = 1;
|
||||
pgbc.gridwidth = 1;
|
||||
pgbc.weightx = 0;
|
||||
JLabel phLabel = new JLabel("Proxy Host:");
|
||||
phLabel.setFont(labelFont);
|
||||
proxyGroup.add(phLabel, pgbc);
|
||||
|
||||
pgbc.gridx = 1;
|
||||
pgbc.weightx = 1.0;
|
||||
proxyHostField = createField(14);
|
||||
proxyGroup.add(proxyHostField, pgbc);
|
||||
|
||||
pgbc.gridx = 2;
|
||||
pgbc.weightx = 0;
|
||||
JLabel ppLabel = new JLabel("Port:");
|
||||
ppLabel.setFont(labelFont);
|
||||
proxyGroup.add(ppLabel, pgbc);
|
||||
|
||||
pgbc.gridx = 3;
|
||||
pgbc.weightx = 0.4;
|
||||
proxyPortField = createField(5);
|
||||
proxyGroup.add(proxyPortField, pgbc);
|
||||
|
||||
// Proxy User & Password
|
||||
pgbc.gridx = 0;
|
||||
pgbc.gridy = 2;
|
||||
pgbc.gridwidth = 1;
|
||||
pgbc.weightx = 0;
|
||||
JLabel puLabel = new JLabel("Username:");
|
||||
puLabel.setFont(labelFont);
|
||||
proxyGroup.add(puLabel, pgbc);
|
||||
|
||||
pgbc.gridx = 1;
|
||||
pgbc.weightx = 1.0;
|
||||
proxyUserField = createField(12);
|
||||
proxyGroup.add(proxyUserField, pgbc);
|
||||
|
||||
pgbc.gridx = 2;
|
||||
pgbc.weightx = 0;
|
||||
JLabel pwLabel = new JLabel("Password:");
|
||||
pwLabel.setFont(labelFont);
|
||||
proxyGroup.add(pwLabel, pgbc);
|
||||
|
||||
pgbc.gridx = 3;
|
||||
pgbc.weightx = 0.4;
|
||||
proxyPassField = createPasswordField(10);
|
||||
proxyGroup.add(proxyPassField, pgbc);
|
||||
|
||||
// Dynamic enable/disable based on Proxy Type
|
||||
Runnable updateProxyFields = () -> {
|
||||
ConnectionConfig.ProxyType pt = (ConnectionConfig.ProxyType) proxyTypeCombo.getSelectedItem();
|
||||
boolean active = (pt != null && pt != ConnectionConfig.ProxyType.NONE);
|
||||
proxyHostField.setEnabled(active);
|
||||
proxyPortField.setEnabled(active);
|
||||
proxyUserField.setEnabled(active);
|
||||
proxyPassField.setEnabled(active && pt != ConnectionConfig.ProxyType.SOCKS4);
|
||||
|
||||
if (active) {
|
||||
String currentPPort = proxyPortField.getText().trim();
|
||||
if (currentPPort.isEmpty() || "0".equals(currentPPort)) {
|
||||
proxyPortField.setText(pt == ConnectionConfig.ProxyType.HTTP ? "8080" : "1080");
|
||||
}
|
||||
}
|
||||
};
|
||||
proxyTypeCombo.addActionListener(e -> updateProxyFields.run());
|
||||
updateProxyFields.run();
|
||||
|
||||
contentPanel.add(proxyGroup, gbc);
|
||||
|
||||
gridy++;
|
||||
|
||||
// 14. Action Buttons
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = gridy;
|
||||
gbc.gridwidth = 2;
|
||||
gbc.weightx = 1.0;
|
||||
|
||||
// Buttons
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 4));
|
||||
|
||||
JButton connectBtn = new JButton("Connect");
|
||||
@@ -284,13 +506,14 @@ public class ConnectDialog extends JDialog {
|
||||
|
||||
buttonPanel.add(cancelBtn);
|
||||
buttonPanel.add(connectBtn);
|
||||
contentPanel.add(buttonPanel, gbc);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 12;
|
||||
gbc.gridwidth = 2;
|
||||
mainPanel.add(buttonPanel, gbc);
|
||||
JScrollPane scrollPane = new JScrollPane(contentPanel);
|
||||
scrollPane.setBorder(BorderFactory.createEmptyBorder());
|
||||
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
scrollPane.getVerticalScrollBar().setUnitIncrement(14);
|
||||
setContentPane(scrollPane);
|
||||
|
||||
setContentPane(mainPanel);
|
||||
ThemeManager.applyThemeToWindow(this);
|
||||
|
||||
// Enter key triggers connect
|
||||
@@ -299,11 +522,194 @@ public class ConnectDialog extends JDialog {
|
||||
|
||||
private JTextField createField(int cols) {
|
||||
JTextField field = new JTextField(cols);
|
||||
field.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
|
||||
field.setFont(ThemeManager.getMonospacedUiFont());
|
||||
ThemeManager.styleTextField(field);
|
||||
return field;
|
||||
}
|
||||
|
||||
private JPasswordField createPasswordField(int cols) {
|
||||
JPasswordField field = new JPasswordField(cols);
|
||||
field.setFont(ThemeManager.getMonospacedUiFont());
|
||||
ThemeManager.styleTextField(field);
|
||||
return field;
|
||||
}
|
||||
|
||||
// ================= Profile Management =================
|
||||
|
||||
private void refreshProfileCombo() {
|
||||
profileCombo.removeAllItems();
|
||||
profileCombo.addItem(NEW_PROFILE_LABEL);
|
||||
List<SavedHost> saved = HostStorage.getSavedHosts();
|
||||
for (SavedHost h : saved) {
|
||||
profileCombo.addItem(h);
|
||||
}
|
||||
if (selectedProfileId != null) {
|
||||
for (int i = 0; i < profileCombo.getItemCount(); i++) {
|
||||
Object item = profileCombo.getItemAt(i);
|
||||
if (item instanceof SavedHost && ((SavedHost) item).getId().equals(selectedProfileId)) {
|
||||
profileCombo.setSelectedIndex(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
profileCombo.setSelectedIndex(0);
|
||||
}
|
||||
|
||||
private void onProfileSelectionChanged() {
|
||||
Object sel = profileCombo.getSelectedItem();
|
||||
if (sel instanceof SavedHost) {
|
||||
SavedHost h = (SavedHost) sel;
|
||||
selectedProfileId = h.getId();
|
||||
deleteProfileBtn.setEnabled(true);
|
||||
loadProfileFields(h);
|
||||
} else {
|
||||
selectedProfileId = null;
|
||||
deleteProfileBtn.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadProfile(SavedHost h) {
|
||||
if (h == null) return;
|
||||
selectedProfileId = h.getId();
|
||||
refreshProfileCombo();
|
||||
loadProfileFields(h);
|
||||
}
|
||||
|
||||
private void loadProfileFields(SavedHost h) {
|
||||
if (h == null) return;
|
||||
hostField.setText(h.getHost());
|
||||
portField.setText(String.valueOf(h.getPort()));
|
||||
if (h.getModel() == 0) {
|
||||
modelCombo.setSelectedItem(TerminalModel.IBM_DYNAMIC);
|
||||
} else {
|
||||
try {
|
||||
modelCombo.setSelectedItem(TerminalModel.forModel(h.getModel(), true));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
dynamicRowsSpinner.setValue(h.getDynamicRows() > 0 ? h.getDynamicRows() : 62);
|
||||
dynamicColsSpinner.setValue(h.getDynamicCols() > 0 ? h.getDynamicCols() : 160);
|
||||
luField.setText(h.getLuName() != null ? h.getLuName() : "");
|
||||
tlsCheckBox.setSelected(h.isUseTls());
|
||||
verifyCertCheckBox.setEnabled(h.isUseTls());
|
||||
verifyCertCheckBox.setSelected(h.isTlsVerifyCert());
|
||||
tn3270eCheckBox.setSelected(h.isTn3270e());
|
||||
setInitialCodePage(h.getCodePage());
|
||||
|
||||
if (h.getGraphicsMode() != null) {
|
||||
graphicsCombo.setSelectedItem(haus.nightmare.lib3270j.graphics.GraphicsMode.fromString(h.getGraphicsMode()));
|
||||
}
|
||||
|
||||
// Per-connection proxy parameters
|
||||
String ptStr = h.getProxyType();
|
||||
try {
|
||||
ConnectionConfig.ProxyType pt = (ptStr != null && !ptStr.trim().isEmpty())
|
||||
? ConnectionConfig.ProxyType.valueOf(ptStr.toUpperCase())
|
||||
: ConnectionConfig.ProxyType.NONE;
|
||||
proxyTypeCombo.setSelectedItem(pt);
|
||||
} catch (IllegalArgumentException e) {
|
||||
proxyTypeCombo.setSelectedItem(ConnectionConfig.ProxyType.NONE);
|
||||
}
|
||||
proxyHostField.setText(h.getProxyHost() != null ? h.getProxyHost() : "");
|
||||
proxyPortField.setText(h.getProxyPort() > 0 ? String.valueOf(h.getProxyPort()) : "");
|
||||
proxyUserField.setText(h.getProxyUsername() != null ? h.getProxyUsername() : "");
|
||||
proxyPassField.setText(h.getProxyPassword() != null ? h.getProxyPassword() : "");
|
||||
}
|
||||
|
||||
private void onSaveProfile() {
|
||||
String hStr = hostField.getText().trim();
|
||||
if (hStr.isEmpty()) {
|
||||
hostField.requestFocus();
|
||||
JOptionPane.showMessageDialog(this, "Please specify a host before saving profile.", "Missing Host", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
String defaultName = hStr;
|
||||
Object currentSel = profileCombo.getSelectedItem();
|
||||
if (currentSel instanceof SavedHost) {
|
||||
defaultName = ((SavedHost) currentSel).getName();
|
||||
}
|
||||
|
||||
String profileName = (String) JOptionPane.showInputDialog(
|
||||
this,
|
||||
"Enter Host Profile Name:",
|
||||
"Save Profile",
|
||||
JOptionPane.PLAIN_MESSAGE,
|
||||
null,
|
||||
null,
|
||||
defaultName
|
||||
);
|
||||
|
||||
if (profileName == null || profileName.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
profileName = profileName.trim();
|
||||
|
||||
SavedHost hostToSave = buildCurrentSavedHost(profileName);
|
||||
HostStorage.saveHost(hostToSave);
|
||||
selectedProfileId = hostToSave.getId();
|
||||
refreshProfileCombo();
|
||||
}
|
||||
|
||||
private void onDeleteProfile() {
|
||||
if (selectedProfileId == null) return;
|
||||
int opt = JOptionPane.showConfirmDialog(this,
|
||||
"Are you sure you want to delete this host profile?",
|
||||
"Delete Profile",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
if (opt == JOptionPane.YES_OPTION) {
|
||||
HostStorage.deleteHost(selectedProfileId);
|
||||
selectedProfileId = null;
|
||||
refreshProfileCombo();
|
||||
}
|
||||
}
|
||||
|
||||
private SavedHost buildCurrentSavedHost(String profileName) {
|
||||
String id = selectedProfileId != null ? selectedProfileId : java.util.UUID.randomUUID().toString();
|
||||
String h = hostField.getText().trim();
|
||||
int port = 23;
|
||||
try {
|
||||
port = Integer.parseInt(portField.getText().trim());
|
||||
} catch (NumberFormatException ignored) {}
|
||||
|
||||
TerminalModel m = (TerminalModel) modelCombo.getSelectedItem();
|
||||
int mNum = (m != null) ? m.getModelNumber() : 4;
|
||||
int dRows = (Integer) dynamicRowsSpinner.getValue();
|
||||
int dCols = (Integer) dynamicColsSpinner.getValue();
|
||||
String lu = luField.getText().trim();
|
||||
boolean tls = tlsCheckBox.isSelected();
|
||||
boolean verify = verifyCertCheckBox.isSelected();
|
||||
boolean tn3270e = tn3270eCheckBox.isSelected();
|
||||
|
||||
String gfx = "BOTH";
|
||||
if (graphicsCombo.getSelectedItem() != null) {
|
||||
gfx = ((haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem()).name();
|
||||
}
|
||||
|
||||
String cp = "037";
|
||||
Object cpSel = codePageCombo.getSelectedItem();
|
||||
if (cpSel != null) {
|
||||
String s = cpSel.toString().trim();
|
||||
int dash = s.indexOf(" -");
|
||||
cp = (dash > 0) ? s.substring(0, dash).trim() : s;
|
||||
}
|
||||
|
||||
ConnectionConfig.ProxyType pt = (ConnectionConfig.ProxyType) proxyTypeCombo.getSelectedItem();
|
||||
String ptStr = (pt != null) ? pt.name() : "NONE";
|
||||
String pHost = proxyHostField.getText().trim();
|
||||
int pPort = 0;
|
||||
try {
|
||||
pPort = Integer.parseInt(proxyPortField.getText().trim());
|
||||
} catch (NumberFormatException ignored) {}
|
||||
String pUser = proxyUserField.getText().trim();
|
||||
String pPass = new String(proxyPassField.getPassword());
|
||||
|
||||
return new SavedHost(id, profileName, h, port, mNum, dRows, dCols, lu, false,
|
||||
"TSO", tls, verify, tn3270e, gfx, cp, ptStr, pHost, pPort, pUser, pPass);
|
||||
}
|
||||
|
||||
// ================= Connect Action =================
|
||||
|
||||
private void onConnect() {
|
||||
String host = hostField.getText().trim();
|
||||
if (host.isEmpty()) {
|
||||
@@ -320,19 +726,34 @@ public class ConnectDialog extends JDialog {
|
||||
}
|
||||
|
||||
TerminalModel selectedModel = (TerminalModel) modelCombo.getSelectedItem();
|
||||
if (selectedModel == null) {
|
||||
selectedModel = TerminalModel.IBM_3279_4;
|
||||
}
|
||||
|
||||
// Support full host string parsing if the user pasted flags or prefixes into host field
|
||||
if (host.contains(" ") || host.startsWith("L:") || host.startsWith("P:") || host.startsWith("--proxy=") || host.startsWith("-proxy=")) {
|
||||
result = ConnectionConfig.parseHostString(host, port, selectedModel);
|
||||
} else {
|
||||
result = new ConnectionConfig(host, port, selectedModel);
|
||||
if (selectedModel != null && selectedModel.isDynamic()) {
|
||||
}
|
||||
|
||||
if (selectedModel.isDynamic()) {
|
||||
int dRows = (Integer) dynamicRowsSpinner.getValue();
|
||||
int dCols = (Integer) dynamicColsSpinner.getValue();
|
||||
result.setDynamicDimensions(dRows, dCols);
|
||||
haus.nightmare.j3270.config.Settings.setDynamicRows(dRows);
|
||||
haus.nightmare.j3270.config.Settings.setDynamicCols(dCols);
|
||||
}
|
||||
|
||||
String lu = luField.getText().trim();
|
||||
if (!lu.isEmpty()) {
|
||||
result.setLuName(lu);
|
||||
}
|
||||
result.setGraphicsMode((haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem());
|
||||
if (graphicsCombo.getSelectedItem() != null) {
|
||||
haus.nightmare.lib3270j.graphics.GraphicsMode gm = (haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem();
|
||||
result.setGraphicsMode(gm);
|
||||
haus.nightmare.j3270.config.Settings.setGraphicsMode(gm);
|
||||
}
|
||||
|
||||
Object cpSelection = codePageCombo.getSelectedItem();
|
||||
if (cpSelection != null) {
|
||||
@@ -360,6 +781,30 @@ public class ConnectDialog extends JDialog {
|
||||
result.setReconnectMaxRetries(haus.nightmare.j3270.config.Settings.getAutoConnectReconnectMaxRetries());
|
||||
haus.nightmare.j3270.config.Settings.setAutoConnectAutoReconnect(ar);
|
||||
|
||||
// Apply per-connection proxy parameters strictly to this connection
|
||||
ConnectionConfig.ProxyType pType = (ConnectionConfig.ProxyType) proxyTypeCombo.getSelectedItem();
|
||||
String pHost = proxyHostField.getText().trim();
|
||||
if (pType != null && pType != ConnectionConfig.ProxyType.NONE && !pHost.isEmpty()) {
|
||||
int pPort = 0;
|
||||
try {
|
||||
pPort = Integer.parseInt(proxyPortField.getText().trim());
|
||||
} catch (NumberFormatException ignored) {}
|
||||
if (pPort <= 0) {
|
||||
pPort = (pType == ConnectionConfig.ProxyType.HTTP) ? 8080 : 1080;
|
||||
}
|
||||
String pUser = proxyUserField.getText().trim();
|
||||
String pPass = new String(proxyPassField.getPassword());
|
||||
result.setProxy(pType, pHost, pPort, pUser.isEmpty() ? null : pUser, pPass.isEmpty() ? null : pPass);
|
||||
}
|
||||
|
||||
if (selectedProfileId != null) {
|
||||
SavedHost existing = HostStorage.findById(selectedProfileId);
|
||||
String pName = (existing != null && existing.getName() != null && !existing.getName().trim().isEmpty())
|
||||
? existing.getName() : host;
|
||||
SavedHost updated = buildCurrentSavedHost(pName);
|
||||
HostStorage.saveHost(updated);
|
||||
}
|
||||
|
||||
confirmed = true;
|
||||
dispose();
|
||||
}
|
||||
@@ -394,6 +839,12 @@ public class ConnectDialog extends JDialog {
|
||||
tn3270eCheckBox.setSelected(tn3270e);
|
||||
}
|
||||
|
||||
public void setInitialGraphicsMode(haus.nightmare.lib3270j.graphics.GraphicsMode mode) {
|
||||
if (mode != null && selectedProfileId == null && graphicsCombo != null) {
|
||||
graphicsCombo.setSelectedItem(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public void setInitialCodePage(String cp) {
|
||||
if (cp != null && codePageCombo != null) {
|
||||
for (int i = 0; i < codePageCombo.getItemCount(); i++) {
|
||||
@@ -406,4 +857,49 @@ public class ConnectDialog extends JDialog {
|
||||
codePageCombo.setSelectedItem(cp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set initial proxy settings for this connection.
|
||||
*/
|
||||
public void setInitialProxy(ConnectionConfig.ProxyType type, String host, int port, String user, String pass) {
|
||||
if (proxyTypeCombo != null) {
|
||||
proxyTypeCombo.setSelectedItem(type != null ? type : ConnectionConfig.ProxyType.NONE);
|
||||
}
|
||||
if (proxyHostField != null) {
|
||||
proxyHostField.setText(host != null ? host : "");
|
||||
}
|
||||
if (proxyPortField != null) {
|
||||
proxyPortField.setText(port > 0 ? String.valueOf(port) : "");
|
||||
}
|
||||
if (proxyUserField != null) {
|
||||
proxyUserField.setText(user != null ? user : "");
|
||||
}
|
||||
if (proxyPassField != null) {
|
||||
proxyPassField.setText(pass != null ? pass : "");
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectionConfig.ProxyType getSelectedProxyType() {
|
||||
return (ConnectionConfig.ProxyType) proxyTypeCombo.getSelectedItem();
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return proxyHostField.getText().trim();
|
||||
}
|
||||
|
||||
public int getProxyPort() {
|
||||
try {
|
||||
return Integer.parseInt(proxyPortField.getText().trim());
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public String getProxyUsername() {
|
||||
return proxyUserField.getText().trim();
|
||||
}
|
||||
|
||||
public String getProxyPassword() {
|
||||
return new String(proxyPassField.getPassword());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
|
||||
super(parent, "3270 Presentation Space Field Inspector", false);
|
||||
this.client = client;
|
||||
this.terminalPanel = terminalPanel;
|
||||
AppIcon.applyTo(this);
|
||||
|
||||
buildUI();
|
||||
setSize(850, 420);
|
||||
@@ -60,7 +61,7 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
|
||||
JPanel topPanel = new JPanel(new BorderLayout());
|
||||
topPanel.setOpaque(false);
|
||||
countLabel = new JLabel("0 fields detected on screen");
|
||||
countLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 13));
|
||||
countLabel.setFont(ThemeManager.getUiFont(Font.BOLD, 0));
|
||||
topPanel.add(countLabel, BorderLayout.WEST);
|
||||
|
||||
JButton refreshBtn = new JButton("Refresh");
|
||||
@@ -83,12 +84,12 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
|
||||
|
||||
table = new JTable(tableModel);
|
||||
ThemeManager.styleTable(table);
|
||||
table.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||
table.setFont(ThemeManager.getMonospacedUiFont());
|
||||
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
table.setRowHeight(20);
|
||||
table.setRowHeight(Math.max(22, ThemeManager.getMonospacedUiFont().getSize() + 8));
|
||||
|
||||
JTableHeader header = table.getTableHeader();
|
||||
header.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
|
||||
header.setFont(ThemeManager.getUiFont(Font.BOLD, 0));
|
||||
|
||||
// Column widths
|
||||
int[] widths = {35, 45, 60, 60, 40, 45, 45, 45, 50, 55, 60, 260};
|
||||
|
||||
@@ -32,6 +32,7 @@ public class FindDialog extends JDialog {
|
||||
super(parent, "Find on Screen", false);
|
||||
this.client = client;
|
||||
this.terminalPanel = terminalPanel;
|
||||
AppIcon.applyTo(this);
|
||||
|
||||
buildUI();
|
||||
pack();
|
||||
@@ -50,7 +51,7 @@ public class FindDialog extends JDialog {
|
||||
gbc.insets = new Insets(4, 4, 4, 4);
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
|
||||
Font labelFont = ThemeManager.getUiFont();
|
||||
|
||||
// Search text
|
||||
gbc.gridx = 0;
|
||||
@@ -63,7 +64,7 @@ public class FindDialog extends JDialog {
|
||||
gbc.weightx = 1.0;
|
||||
searchField = new JTextField(20);
|
||||
searchField.setText(lastSearchText);
|
||||
searchField.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||
searchField.setFont(ThemeManager.getMonospacedUiFont());
|
||||
ThemeManager.styleTextField(searchField);
|
||||
formPanel.add(searchField, gbc);
|
||||
|
||||
@@ -102,7 +103,7 @@ public class FindDialog extends JDialog {
|
||||
gbc.gridwidth = 2;
|
||||
statusLabel = new JLabel(" ");
|
||||
statusLabel.setForeground(ThemeManager.getOiaFgAlert());
|
||||
statusLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||
statusLabel.setFont(labelFont);
|
||||
formPanel.add(statusLabel, gbc);
|
||||
|
||||
mainPanel.add(formPanel, BorderLayout.CENTER);
|
||||
|
||||
@@ -40,6 +40,7 @@ public class HostDirectoryDialog extends JDialog {
|
||||
super(parent, "Host Catalog & Directory Browser", true);
|
||||
this.client = client;
|
||||
this.initialHostType = hostType;
|
||||
AppIcon.applyTo(this);
|
||||
|
||||
buildUI(initialQuery);
|
||||
setSize(700, 420);
|
||||
@@ -50,7 +51,7 @@ public class HostDirectoryDialog extends JDialog {
|
||||
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||
mainPanel.setBorder(new EmptyBorder(12, 12, 12, 12));
|
||||
|
||||
Font font = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
|
||||
Font font = ThemeManager.getUiFont();
|
||||
|
||||
// Top Query bar
|
||||
JPanel topPanel = new JPanel(new GridBagLayout());
|
||||
@@ -78,7 +79,7 @@ public class HostDirectoryDialog extends JDialog {
|
||||
gbc.gridx = 3;
|
||||
gbc.weightx = 1.0;
|
||||
queryField = new JTextField(initialQuery != null ? initialQuery : "", 16);
|
||||
queryField.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||
queryField.setFont(ThemeManager.getMonospacedUiFont());
|
||||
ThemeManager.styleTextField(queryField);
|
||||
topPanel.add(queryField, gbc);
|
||||
|
||||
@@ -96,12 +97,12 @@ public class HostDirectoryDialog extends JDialog {
|
||||
tableModel = new DefaultTableModel();
|
||||
table = new JTable(tableModel);
|
||||
ThemeManager.styleTable(table);
|
||||
table.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||
table.setFont(ThemeManager.getMonospacedUiFont());
|
||||
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
table.setRowHeight(20);
|
||||
table.setRowHeight(Math.max(22, ThemeManager.getMonospacedUiFont().getSize() + 8));
|
||||
|
||||
JTableHeader header = table.getTableHeader();
|
||||
header.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
|
||||
header.setFont(ThemeManager.getUiFont(Font.BOLD, 0));
|
||||
|
||||
table.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
@@ -122,7 +123,7 @@ public class HostDirectoryDialog extends JDialog {
|
||||
|
||||
statusLabel = new JLabel("Enter a dataset pattern or parse active screen.");
|
||||
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||
statusLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||
statusLabel.setFont(font);
|
||||
bottomPanel.add(statusLabel, BorderLayout.WEST);
|
||||
|
||||
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Real-time hotspot and URL auto-detection engine matching IBM Host On-Demand
|
||||
* ShowURLOptions and ScreenText parity.
|
||||
*
|
||||
* Scans screen text using regular expressions for:
|
||||
* 1. URLs (http://, https://, ftp://, file://, mailto:, www.)
|
||||
* 2. Function keys (PF1..PF24, F1..F24, PA1..PA3, [ENTER], ENTER)
|
||||
* 3. Menu choices ("1.", "2.", "===> 3", "=> 4")
|
||||
*
|
||||
* Provides hit-testing for mouse hover/clicks and generates keystroke actions.
|
||||
*/
|
||||
public class HotspotEngine {
|
||||
|
||||
public enum HotspotType {
|
||||
URL,
|
||||
PF_KEY,
|
||||
ENTER,
|
||||
MENU_CHOICE
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an active interactive hotspot region on the terminal screen.
|
||||
*/
|
||||
public static class Hotspot {
|
||||
private final HotspotType type;
|
||||
private final int startRow;
|
||||
private final int startCol;
|
||||
private final int endRow;
|
||||
private final int endCol;
|
||||
private final String displayText;
|
||||
private final String action;
|
||||
private final String tooltip;
|
||||
|
||||
public Hotspot(HotspotType type, int startRow, int startCol, int endRow, int endCol,
|
||||
String displayText, String action, String tooltip) {
|
||||
this.type = type;
|
||||
this.startRow = startRow;
|
||||
this.startCol = startCol;
|
||||
this.endRow = endRow;
|
||||
this.endCol = endCol;
|
||||
this.displayText = displayText;
|
||||
this.action = action;
|
||||
this.tooltip = tooltip;
|
||||
}
|
||||
|
||||
public HotspotType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public int getStartRow() {
|
||||
return startRow;
|
||||
}
|
||||
|
||||
public int getStartCol() {
|
||||
return startCol;
|
||||
}
|
||||
|
||||
public int getEndRow() {
|
||||
return endRow;
|
||||
}
|
||||
|
||||
public int getEndCol() {
|
||||
return endCol;
|
||||
}
|
||||
|
||||
public String getDisplayText() {
|
||||
return displayText;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public String getTooltip() {
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the given row/col coordinate falls within this hotspot.
|
||||
*/
|
||||
public boolean contains(int row, int col) {
|
||||
if (row < startRow || row > endRow) {
|
||||
return false;
|
||||
}
|
||||
if (startRow == endRow) {
|
||||
return col >= startCol && col <= endCol;
|
||||
}
|
||||
if (row == startRow) {
|
||||
return col >= startCol;
|
||||
}
|
||||
if (row == endRow) {
|
||||
return col <= endCol;
|
||||
}
|
||||
return true; // Intermediate rows
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Hotspot{" + type + " [" + startRow + "," + startCol + "]-[" + endRow + "," + endCol + "] '" + displayText + "'}";
|
||||
}
|
||||
}
|
||||
|
||||
// Configurable scan enablement
|
||||
private boolean urlEnabled = true;
|
||||
private boolean pfKeyEnabled = true;
|
||||
private boolean menuChoiceEnabled = true;
|
||||
|
||||
// Regular expression patterns
|
||||
// 1. URLs
|
||||
private static final Pattern URL_PATTERN = Pattern.compile(
|
||||
"(?:https?|ftp|file)://[\\w\\-._~:/?#\\[\\]@!$&'*+,;=%]+|www\\.[\\w\\-._~:/?#\\[\\]@!$&'*+,;=%]+|mailto:[\\w\\-._~:/?#\\[\\]@!$&'*+,;=%]+",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
// 2. Function keys (PF1..PF24, F1..F24, PA1..PA3, [ENTER], ENTER)
|
||||
private static final Pattern PF_KEY_PATTERN = Pattern.compile(
|
||||
"(?:(?<=\\W)|^)(PF(?:[1-9]|1[0-9]|2[0-4])|F(?:[1-9]|1[0-9]|2[0-4])|PA[1-3]|\\[ENTER\\]|ENTER)(?:(?=\\W)|$)",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
// 3. Menu choices: "1.", "2.", "===> 3", "=> 4", "--> 5"
|
||||
private static final Pattern MENU_NUMBER_PATTERN = Pattern.compile(
|
||||
"(?:(?<=\\s)|^)(\\d{1,2})\\.(?:(?=\\s)|$)"
|
||||
);
|
||||
|
||||
private static final Pattern MENU_PROMPT_PATTERN = Pattern.compile(
|
||||
"(?:===>|=>|-->)\\s*(\\d{1,2})(?:(?=\\s)|$)"
|
||||
);
|
||||
|
||||
// Caching state to avoid rescanning unchanged screens
|
||||
private String cachedScreenText = "";
|
||||
private int cachedRows = 0;
|
||||
private int cachedCols = 0;
|
||||
private List<Hotspot> cachedHotspots = Collections.emptyList();
|
||||
|
||||
public HotspotEngine() {
|
||||
}
|
||||
|
||||
public boolean isUrlEnabled() {
|
||||
return urlEnabled;
|
||||
}
|
||||
|
||||
public void setUrlEnabled(boolean urlEnabled) {
|
||||
this.urlEnabled = urlEnabled;
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
public boolean isPfKeyEnabled() {
|
||||
return pfKeyEnabled;
|
||||
}
|
||||
|
||||
public void setPfKeyEnabled(boolean pfKeyEnabled) {
|
||||
this.pfKeyEnabled = pfKeyEnabled;
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
public boolean isMenuChoiceEnabled() {
|
||||
return menuChoiceEnabled;
|
||||
}
|
||||
|
||||
public void setMenuChoiceEnabled(boolean menuChoiceEnabled) {
|
||||
this.menuChoiceEnabled = menuChoiceEnabled;
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
public void invalidateCache() {
|
||||
cachedScreenText = "";
|
||||
cachedHotspots = Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the given ScreenBuffer for interactive hotspots.
|
||||
*/
|
||||
public synchronized List<Hotspot> scan(ScreenBuffer sb) {
|
||||
if (sb == null) return Collections.emptyList();
|
||||
int rows = sb.getDisplayRows();
|
||||
int cols = sb.getDisplayCols();
|
||||
|
||||
// Build plain text representation
|
||||
StringBuilder fullText = new StringBuilder(rows * (cols + 1));
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
int baddr = r * cols + c;
|
||||
ExtendedAttribute ea = sb.getDisplayCell(baddr);
|
||||
if (ea == null || ea.isFieldAttribute() || ea.ucs4 <= 0x20 || ea.ucs4 == 0xFF) {
|
||||
fullText.append(' ');
|
||||
} else {
|
||||
fullText.append((char) ea.ucs4);
|
||||
}
|
||||
}
|
||||
if (r < rows - 1) {
|
||||
fullText.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
return scanText(fullText.toString(), rows, cols);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan formatted screen text (lines separated by '\n') for hotspots.
|
||||
*/
|
||||
public synchronized List<Hotspot> scanText(String screenText, int rows, int cols) {
|
||||
if (screenText == null || rows <= 0 || cols <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (screenText.equals(cachedScreenText) && rows == cachedRows && cols == cachedCols) {
|
||||
return cachedHotspots;
|
||||
}
|
||||
|
||||
List<Hotspot> hotspots = new ArrayList<>();
|
||||
String[] lines = screenText.split("\n", -1);
|
||||
int actualRows = Math.min(rows, lines.length);
|
||||
|
||||
// Scan line by line for precise row/column tracking
|
||||
for (int r = 0; r < actualRows; r++) {
|
||||
String line = lines[r];
|
||||
boolean[] occupied = new boolean[line.length() + 1];
|
||||
|
||||
// 1. Scan URLs
|
||||
if (urlEnabled) {
|
||||
Matcher mUrl = URL_PATTERN.matcher(line);
|
||||
while (mUrl.find()) {
|
||||
int startCol = mUrl.start();
|
||||
int endCol = mUrl.end();
|
||||
String match = mUrl.group();
|
||||
|
||||
// Trim trailing punctuation like . , ; : ) ]
|
||||
while (match.length() > 1) {
|
||||
char last = match.charAt(match.length() - 1);
|
||||
if (last == '.' || last == ',' || last == ';' || last == ':' || last == ')' || last == ']' || last == '>') {
|
||||
match = match.substring(0, match.length() - 1);
|
||||
endCol--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match.isEmpty()) continue;
|
||||
|
||||
String actionUrl = match;
|
||||
if (match.toLowerCase().startsWith("www.")) {
|
||||
actionUrl = "http://" + match;
|
||||
}
|
||||
|
||||
Hotspot h = new Hotspot(
|
||||
HotspotType.URL,
|
||||
r, startCol, r, endCol - 1,
|
||||
match, actionUrl, "Open URL: " + actionUrl
|
||||
);
|
||||
hotspots.add(h);
|
||||
markOccupied(occupied, startCol, endCol);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Scan PF / Function keys
|
||||
if (pfKeyEnabled) {
|
||||
Matcher mPf = PF_KEY_PATTERN.matcher(line);
|
||||
while (mPf.find()) {
|
||||
int startCol = mPf.start(1);
|
||||
int endCol = mPf.end(1);
|
||||
if (isRangeOccupied(occupied, startCol, endCol)) continue;
|
||||
|
||||
String keyText = mPf.group(1).toUpperCase();
|
||||
String action;
|
||||
String tooltip;
|
||||
|
||||
if (keyText.equals("ENTER") || keyText.equals("[ENTER]")) {
|
||||
action = "[enter]";
|
||||
tooltip = "Send [ENTER]";
|
||||
} else if (keyText.startsWith("PF")) {
|
||||
String numStr = keyText.substring(2);
|
||||
action = "[pf" + numStr + "]";
|
||||
tooltip = "Send " + action.toUpperCase();
|
||||
} else if (keyText.startsWith("F")) {
|
||||
String numStr = keyText.substring(1);
|
||||
action = "[pf" + numStr + "]";
|
||||
tooltip = "Send " + action.toUpperCase();
|
||||
} else if (keyText.startsWith("PA")) {
|
||||
String numStr = keyText.substring(2);
|
||||
action = "[pa" + numStr + "]";
|
||||
tooltip = "Send " + action.toUpperCase();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
Hotspot h = new Hotspot(
|
||||
keyText.contains("ENTER") ? HotspotType.ENTER : HotspotType.PF_KEY,
|
||||
r, startCol, r, endCol - 1,
|
||||
mPf.group(1), action, tooltip
|
||||
);
|
||||
hotspots.add(h);
|
||||
markOccupied(occupied, startCol, endCol);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Scan Menu choices ("1.", "2.", "===> 3", "=> 4")
|
||||
if (menuChoiceEnabled) {
|
||||
// Pattern 3a: Number followed by dot (e.g. " 1. ")
|
||||
Matcher mNum = MENU_NUMBER_PATTERN.matcher(line);
|
||||
while (mNum.find()) {
|
||||
int startCol = mNum.start(1);
|
||||
int endCol = mNum.end();
|
||||
if (isRangeOccupied(occupied, startCol, endCol)) continue;
|
||||
|
||||
String choiceNum = mNum.group(1);
|
||||
String action = choiceNum + "[enter]";
|
||||
String tooltip = "Select option " + choiceNum;
|
||||
|
||||
Hotspot h = new Hotspot(
|
||||
HotspotType.MENU_CHOICE,
|
||||
r, startCol, r, endCol - 1,
|
||||
choiceNum + ".", action, tooltip
|
||||
);
|
||||
hotspots.add(h);
|
||||
markOccupied(occupied, startCol, endCol);
|
||||
}
|
||||
|
||||
// Pattern 3b: Prompt followed by number (e.g. "===> 3")
|
||||
Matcher mPrompt = MENU_PROMPT_PATTERN.matcher(line);
|
||||
while (mPrompt.find()) {
|
||||
int startCol = mPrompt.start();
|
||||
int endCol = mPrompt.end();
|
||||
if (isRangeOccupied(occupied, startCol, endCol)) continue;
|
||||
|
||||
String choiceNum = mPrompt.group(1);
|
||||
String action = choiceNum + "[enter]";
|
||||
String tooltip = "Execute choice " + choiceNum;
|
||||
|
||||
Hotspot h = new Hotspot(
|
||||
HotspotType.MENU_CHOICE,
|
||||
r, startCol, r, endCol - 1,
|
||||
mPrompt.group(), action, tooltip
|
||||
);
|
||||
hotspots.add(h);
|
||||
markOccupied(occupied, startCol, endCol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedScreenText = screenText;
|
||||
this.cachedRows = rows;
|
||||
this.cachedCols = cols;
|
||||
this.cachedHotspots = Collections.unmodifiableList(hotspots);
|
||||
|
||||
return cachedHotspots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hit-test for a hotspot at the specified terminal row and column.
|
||||
*/
|
||||
public Hotspot findHotspot(int row, int col) {
|
||||
for (Hotspot h : cachedHotspots) {
|
||||
if (h.contains(row, col)) {
|
||||
return h;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query currently active cached hotspots.
|
||||
*/
|
||||
public List<Hotspot> getHotspots() {
|
||||
return cachedHotspots;
|
||||
}
|
||||
|
||||
private static void markOccupied(boolean[] arr, int start, int end) {
|
||||
for (int i = Math.max(0, start); i < Math.min(arr.length, end); i++) {
|
||||
arr[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isRangeOccupied(boolean[] arr, int start, int end) {
|
||||
for (int i = Math.max(0, start); i < Math.min(arr.length, end); i++) {
|
||||
if (arr[i]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -206,7 +206,8 @@ public class KeyBindings {
|
||||
|
||||
/**
|
||||
* Parse a .kmp file from a Reader.
|
||||
* Supports both INI-style KEY<n>=action formats and bind statements.
|
||||
* Supports both IBM Host On-Demand (HoD) .kmp format (hashKey prefixes)
|
||||
* and IBM Personal Communications (PCOMM) INI-style and bind statements.
|
||||
*
|
||||
* @param reader the reader to parse from
|
||||
* @return Map of j3270 action names to key stroke bindings
|
||||
@@ -214,11 +215,38 @@ public class KeyBindings {
|
||||
*/
|
||||
public static Map<String, String> parseKmp(Reader reader) throws IOException {
|
||||
BufferedReader br = (reader instanceof BufferedReader) ? (BufferedReader) reader : new BufferedReader(reader);
|
||||
Map<String, List<String>> actionToBindings = new LinkedHashMap<>();
|
||||
|
||||
List<String> rawLines = new ArrayList<>();
|
||||
boolean hasHodKeyRemap = false;
|
||||
boolean hasHodHashKey = false;
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
line = line.trim();
|
||||
rawLines.add(line);
|
||||
String trimmed = line.trim();
|
||||
if (trimmed.equalsIgnoreCase("[KeyRemap]")) {
|
||||
hasHodKeyRemap = true;
|
||||
}
|
||||
int eq = trimmed.indexOf('=');
|
||||
if (eq > 0) {
|
||||
String left = trimmed.substring(0, eq).trim();
|
||||
if (left.matches("^[A-Za-z][0-9]+$")) {
|
||||
hasHodHashKey = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasHodKeyRemap || hasHodHashKey) {
|
||||
Map<String, String> hodMap = haus.nightmare.lib3270j.input.HodKeyMap.parseHodKmp(
|
||||
new StringReader(String.join("\n", rawLines))
|
||||
);
|
||||
if (hodMap != null && !hodMap.isEmpty()) {
|
||||
return hodMap;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, List<String>> actionToBindings = new LinkedHashMap<>();
|
||||
|
||||
for (String raw : rawLines) {
|
||||
line = raw.trim();
|
||||
|
||||
// Strip comments
|
||||
int commentIdx = line.indexOf(';');
|
||||
@@ -262,6 +290,22 @@ public class KeyBindings {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export key bindings to a .kmp file using IBM HoD format.
|
||||
*/
|
||||
public static void exportKmp(File file, Map<String, String> keyBindings) throws IOException {
|
||||
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
|
||||
exportKmp(bw, keyBindings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export key bindings to a Writer using IBM HoD format.
|
||||
*/
|
||||
public static void exportKmp(Writer writer, Map<String, String> keyBindings) throws IOException {
|
||||
haus.nightmare.lib3270j.input.HodKeyMap.exportHodKmp(writer, keyBindings);
|
||||
}
|
||||
|
||||
private static void parseBindStatement(String stmt, Map<String, List<String>> actionToBindings) {
|
||||
String[] parts = stmt.split("\\s+", 2);
|
||||
if (parts.length < 2) return;
|
||||
@@ -288,6 +332,19 @@ public class KeyBindings {
|
||||
}
|
||||
|
||||
private static void parseKeyAssignment(String left, String right, Map<String, List<String>> actionToBindings) {
|
||||
if (left.matches("^[A-Za-z][0-9]+$")) {
|
||||
haus.nightmare.lib3270j.input.HodKeyMap.KeyEntry entry =
|
||||
haus.nightmare.lib3270j.input.HodKeyMap.parseHashKey(left, right);
|
||||
if (entry != null) {
|
||||
String stroke = entry.toKeyStrokeString();
|
||||
String act = haus.nightmare.lib3270j.input.HodKeyMap.resolveMnemonicToAction(right);
|
||||
if (act != null && stroke != null && !stroke.isEmpty()) {
|
||||
addBinding(actionToBindings, act, stroke);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Case A: left is KEY<n> or Modifier-KEY<n>, right is mnemonic
|
||||
String action = resolveMnemonic(right);
|
||||
String keyStroke = null;
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||
import haus.nightmare.lib3270j.ecl.ECLSession;
|
||||
import haus.nightmare.lib3270j.macro.Macro;
|
||||
import haus.nightmare.lib3270j.macro.MacroScreen;
|
||||
import haus.nightmare.lib3270j.macro.MacroScreens;
|
||||
import haus.nightmare.lib3270j.macro.MacroXmlParser;
|
||||
import haus.nightmare.lib3270j.macro.action.MacroAction;
|
||||
import haus.nightmare.lib3270j.macro.action.MacroActionPrompt;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Interactive Swing dialog for loading, editing, running, and debugging
|
||||
* IBM Host On-Demand XML Macros (<HASCRIPT> / .mac files).
|
||||
*/
|
||||
public class MacroDialog extends JDialog implements Macro.MacroListener, Macro.MacroPromptHandler {
|
||||
|
||||
private final Telnet3270Client client;
|
||||
private final TerminalPanel terminalPanel;
|
||||
|
||||
private JTextArea xmlArea;
|
||||
private JTextArea logArea;
|
||||
private JLabel statusLabel;
|
||||
private JButton playBtn;
|
||||
private JButton pauseBtn;
|
||||
private JButton stopBtn;
|
||||
|
||||
private Macro macro;
|
||||
|
||||
public MacroDialog(Frame parent, Telnet3270Client client, TerminalPanel terminalPanel) {
|
||||
super(parent, "IBM HoD XML Macro Runtime (<HASCRIPT>)", false);
|
||||
this.client = client;
|
||||
this.terminalPanel = terminalPanel;
|
||||
AppIcon.applyTo(this);
|
||||
|
||||
this.macro = new Macro(client);
|
||||
this.macro.addMacroListener(this);
|
||||
this.macro.setPromptHandler(this);
|
||||
|
||||
buildUI();
|
||||
setSize(780, 580);
|
||||
setLocationRelativeTo(parent);
|
||||
}
|
||||
|
||||
private void buildUI() {
|
||||
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||
mainPanel.setBorder(new EmptyBorder(12, 14, 12, 14));
|
||||
|
||||
Font labelFont = ThemeManager.getUiFont();
|
||||
|
||||
// Top info label
|
||||
JPanel topPanel = new JPanel(new BorderLayout(5, 5));
|
||||
topPanel.setOpaque(false);
|
||||
JLabel descLabel = new JLabel("<html>Load, edit, and execute IBM Host On-Demand XML Macro files (<code>.mac</code> / <code><HASCRIPT></code>).<br>"
|
||||
+ "Automates host flows via screen descriptors, actions, variables, and state transitions.</html>");
|
||||
descLabel.setFont(labelFont);
|
||||
topPanel.add(descLabel, BorderLayout.CENTER);
|
||||
mainPanel.add(topPanel, BorderLayout.NORTH);
|
||||
|
||||
// Center split: XML editor and Execution Log
|
||||
xmlArea = new JTextArea();
|
||||
xmlArea.setFont(ThemeManager.getMonospacedUiFont());
|
||||
xmlArea.setLineWrap(false);
|
||||
xmlArea.setText(getDefaultMacroXml());
|
||||
ThemeManager.styleTextArea(xmlArea);
|
||||
|
||||
JScrollPane xmlScroll = new JScrollPane(xmlArea);
|
||||
ThemeManager.styleScrollPane(xmlScroll);
|
||||
xmlScroll.setBorder(ThemeManager.createTitledBorder("Macro XML (<HASCRIPT>)"));
|
||||
|
||||
logArea = new JTextArea();
|
||||
logArea.setFont(ThemeManager.getMonospacedUiFont(Font.PLAIN, -1));
|
||||
logArea.setEditable(false);
|
||||
logArea.setLineWrap(true);
|
||||
ThemeManager.styleTextArea(logArea);
|
||||
|
||||
JScrollPane logScroll = new JScrollPane(logArea);
|
||||
ThemeManager.styleScrollPane(logScroll);
|
||||
logScroll.setBorder(ThemeManager.createTitledBorder("Execution Log"));
|
||||
|
||||
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, xmlScroll, logScroll);
|
||||
splitPane.setResizeWeight(0.65);
|
||||
splitPane.setOpaque(false);
|
||||
mainPanel.add(splitPane, BorderLayout.CENTER);
|
||||
|
||||
// Bottom control panel
|
||||
JPanel bottomPanel = new JPanel(new BorderLayout(5, 5));
|
||||
bottomPanel.setOpaque(false);
|
||||
|
||||
statusLabel = new JLabel("Status: Stopped");
|
||||
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||
statusLabel.setFont(labelFont);
|
||||
bottomPanel.add(statusLabel, BorderLayout.WEST);
|
||||
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||
buttonPanel.setOpaque(false);
|
||||
|
||||
JButton loadBtn = new JButton("Load .mac...");
|
||||
ThemeManager.styleButton(loadBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
loadBtn.setFont(labelFont);
|
||||
loadBtn.addActionListener(e -> loadMacroFile());
|
||||
|
||||
JButton saveBtn = new JButton("Save .mac...");
|
||||
ThemeManager.styleButton(saveBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
saveBtn.setFont(labelFont);
|
||||
saveBtn.addActionListener(e -> saveMacroFile());
|
||||
|
||||
playBtn = new JButton("Play");
|
||||
ThemeManager.styleButton(playBtn, ThemeManager.ButtonVariant.PRIMARY);
|
||||
playBtn.setFont(labelFont);
|
||||
playBtn.addActionListener(e -> playMacro());
|
||||
|
||||
pauseBtn = new JButton("Pause");
|
||||
ThemeManager.styleButton(pauseBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
pauseBtn.setFont(labelFont);
|
||||
pauseBtn.setEnabled(false);
|
||||
pauseBtn.addActionListener(e -> togglePause());
|
||||
|
||||
stopBtn = new JButton("Stop");
|
||||
ThemeManager.styleButton(stopBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
stopBtn.setFont(labelFont);
|
||||
stopBtn.setEnabled(false);
|
||||
stopBtn.addActionListener(e -> stopMacro());
|
||||
|
||||
JButton clearLogBtn = new JButton("Clear Log");
|
||||
ThemeManager.styleButton(clearLogBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
clearLogBtn.setFont(labelFont);
|
||||
clearLogBtn.addActionListener(e -> logArea.setText(""));
|
||||
|
||||
JButton closeBtn = new JButton("Close");
|
||||
ThemeManager.styleButton(closeBtn, ThemeManager.ButtonVariant.CANCEL);
|
||||
closeBtn.setFont(labelFont);
|
||||
closeBtn.addActionListener(e -> dispose());
|
||||
|
||||
buttonPanel.add(loadBtn);
|
||||
buttonPanel.add(saveBtn);
|
||||
buttonPanel.add(Box.createHorizontalStrut(8));
|
||||
buttonPanel.add(playBtn);
|
||||
buttonPanel.add(pauseBtn);
|
||||
buttonPanel.add(stopBtn);
|
||||
buttonPanel.add(clearLogBtn);
|
||||
buttonPanel.add(Box.createHorizontalStrut(8));
|
||||
buttonPanel.add(closeBtn);
|
||||
|
||||
bottomPanel.add(buttonPanel, BorderLayout.EAST);
|
||||
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||
|
||||
setContentPane(mainPanel);
|
||||
ThemeManager.applyThemeToWindow(this);
|
||||
}
|
||||
|
||||
private void loadMacroFile() {
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
chooser.setDialogTitle("Open IBM HoD Macro");
|
||||
chooser.setFileFilter(new FileNameExtensionFilter("IBM Macro (*.mac, *.xml)", "mac", "xml"));
|
||||
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||
File f = chooser.getSelectedFile();
|
||||
try (BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8))) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = r.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
xmlArea.setText(sb.toString());
|
||||
log("Loaded macro file: " + f.getName());
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(this, "Failed to load macro file: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveMacroFile() {
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
chooser.setDialogTitle("Save IBM HoD Macro");
|
||||
chooser.setFileFilter(new FileNameExtensionFilter("IBM Macro (*.mac, *.xml)", "mac", "xml"));
|
||||
if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||
File f = chooser.getSelectedFile();
|
||||
if (!f.getName().contains(".")) {
|
||||
f = new File(f.getParentFile(), f.getName() + ".mac");
|
||||
}
|
||||
try (Writer w = new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8)) {
|
||||
w.write(xmlArea.getText());
|
||||
log("Saved macro file: " + f.getName());
|
||||
} catch (Exception ex) {
|
||||
JOptionPane.showMessageDialog(this, "Failed to save macro file: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void playMacro() {
|
||||
String xml = xmlArea.getText();
|
||||
if (xml == null || xml.trim().isEmpty()) {
|
||||
JOptionPane.showMessageDialog(this, "Macro XML is empty.", "Warning", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||
int opt = JOptionPane.showConfirmDialog(this,
|
||||
"Session is not currently connected to a host.\nDo you want to run macro anyway (descriptors will wait for connection)?",
|
||||
"Not Connected", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||
if (opt != JOptionPane.YES_OPTION) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
MacroScreens screens = MacroXmlParser.parse(xml);
|
||||
macro.setScreens(screens);
|
||||
macro.setClient(client);
|
||||
|
||||
log("Starting macro: " + (screens.getName().isEmpty() ? "Untitled" : screens.getName()));
|
||||
macro.play();
|
||||
} catch (Exception ex) {
|
||||
log("Parse / Launch Error: " + ex.getMessage());
|
||||
JOptionPane.showMessageDialog(this, "Macro parsing error: " + ex.getMessage(), "Macro Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
public void togglePause() {
|
||||
if (macro.getState() == Macro.STATE_PLAYING) {
|
||||
macro.pause();
|
||||
pauseBtn.setText("Resume");
|
||||
log("Macro paused.");
|
||||
} else if (macro.getState() == Macro.STATE_PAUSED) {
|
||||
macro.resume();
|
||||
pauseBtn.setText("Pause");
|
||||
log("Macro resumed.");
|
||||
}
|
||||
}
|
||||
|
||||
public void stopMacro() {
|
||||
macro.stop();
|
||||
log("Macro stopped by user.");
|
||||
}
|
||||
|
||||
private void log(String msg) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
logArea.append(msg + "\n");
|
||||
logArea.setCaretPosition(logArea.getDocument().getLength());
|
||||
});
|
||||
}
|
||||
|
||||
// ========== MacroListener Callbacks ==========
|
||||
|
||||
@Override
|
||||
public void macroStateChanged(Macro macro, int oldState, int newState) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
switch (newState) {
|
||||
case Macro.STATE_PLAYING:
|
||||
statusLabel.setText("Status: Playing");
|
||||
statusLabel.setForeground(ThemeManager.getFgMain());
|
||||
playBtn.setEnabled(false);
|
||||
pauseBtn.setEnabled(true);
|
||||
pauseBtn.setText("Pause");
|
||||
stopBtn.setEnabled(true);
|
||||
break;
|
||||
case Macro.STATE_PAUSED:
|
||||
statusLabel.setText("Status: Paused");
|
||||
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||
pauseBtn.setText("Resume");
|
||||
break;
|
||||
case Macro.STATE_ERROR:
|
||||
statusLabel.setText("Status: Error");
|
||||
statusLabel.setForeground(Color.RED);
|
||||
playBtn.setEnabled(true);
|
||||
pauseBtn.setEnabled(false);
|
||||
stopBtn.setEnabled(false);
|
||||
break;
|
||||
case Macro.STATE_STOPPED:
|
||||
default:
|
||||
statusLabel.setText("Status: Stopped");
|
||||
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||
playBtn.setEnabled(true);
|
||||
pauseBtn.setEnabled(false);
|
||||
pauseBtn.setText("Pause");
|
||||
stopBtn.setEnabled(false);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void macroScreenMatching(MacroScreen screen) {
|
||||
log("Matched screen: [" + screen.getName() + "]");
|
||||
if (terminalPanel != null) {
|
||||
terminalPanel.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void macroActionExecuting(MacroAction action) {
|
||||
log(" -> Executing: " + action.getClass().getSimpleName());
|
||||
if (terminalPanel != null) {
|
||||
terminalPanel.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void macroMessage(String title, String message) {
|
||||
log("Message [" + title + "]: " + message);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
JOptionPane.showMessageDialog(this, message, title.isEmpty() ? "Macro Message" : title, JOptionPane.INFORMATION_MESSAGE);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void macroError(String error) {
|
||||
log("ERROR: " + error);
|
||||
}
|
||||
|
||||
// ========== MacroPromptHandler Callback ==========
|
||||
|
||||
@Override
|
||||
public String prompt(MacroActionPrompt prompt) {
|
||||
AtomicReference<String> result = new AtomicReference<>(null);
|
||||
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
result.set(showPromptModal(prompt));
|
||||
} else {
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
result.set(showPromptModal(prompt));
|
||||
});
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return result.get();
|
||||
}
|
||||
|
||||
private String showPromptModal(MacroActionPrompt prompt) {
|
||||
JPanel panel = new JPanel(new BorderLayout(5, 5));
|
||||
JLabel msgLabel = new JLabel(prompt.getDescription().isEmpty() ? "Enter parameter:" : prompt.getDescription());
|
||||
panel.add(msgLabel, BorderLayout.NORTH);
|
||||
|
||||
JTextField textField;
|
||||
if (prompt.isEncrypted()) {
|
||||
textField = new JPasswordField(prompt.getDefaultValue(), prompt.getLen() > 0 ? Math.min(prompt.getLen(), 25) : 15);
|
||||
} else {
|
||||
textField = new JTextField(prompt.getDefaultValue(), prompt.getLen() > 0 ? Math.min(prompt.getLen(), 25) : 15);
|
||||
}
|
||||
panel.add(textField, BorderLayout.CENTER);
|
||||
|
||||
int opt = JOptionPane.showConfirmDialog(this, panel,
|
||||
prompt.getTitle().isEmpty() ? "Macro Input Prompt" : prompt.getTitle(),
|
||||
JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
|
||||
|
||||
if (opt == JOptionPane.OK_OPTION) {
|
||||
if (textField instanceof JPasswordField) {
|
||||
return new String(((JPasswordField) textField).getPassword());
|
||||
}
|
||||
return textField.getText();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getDefaultMacroXml() {
|
||||
return "<HASCRIPT name=\"SampleLogon\" timeout=\"15000\" usevars=\"true\">\n"
|
||||
+ " <vars>\n"
|
||||
+ " <create name=\"$userId\" type=\"string\" value=\"USER01\" />\n"
|
||||
+ " <create name=\"$count\" type=\"integer\" value=\"0\" />\n"
|
||||
+ " </vars>\n\n"
|
||||
+ " <screen name=\"LogonScreen\" entryscreen=\"true\">\n"
|
||||
+ " <description>\n"
|
||||
+ " <oia status=\"NOTINHIBITED\" />\n"
|
||||
+ " </description>\n"
|
||||
+ " <actions>\n"
|
||||
+ " <input value=\"$userId + '[enter]'\" movecursor=\"true\" xlatehostkeys=\"true\" />\n"
|
||||
+ " </actions>\n"
|
||||
+ " <nextscreens timeout=\"10000\">\n"
|
||||
+ " <nextscreen name=\"ReadyScreen\" />\n"
|
||||
+ " </nextscreens>\n"
|
||||
+ " </screen>\n\n"
|
||||
+ " <screen name=\"ReadyScreen\" exitscreen=\"true\">\n"
|
||||
+ " <description>\n"
|
||||
+ " <oia status=\"NOTINHIBITED\" />\n"
|
||||
+ " </description>\n"
|
||||
+ " <actions>\n"
|
||||
+ " <message title=\"Success\" value=\"Logon macro completed successfully!\" />\n"
|
||||
+ " </actions>\n"
|
||||
+ " </screen>\n"
|
||||
+ "</HASCRIPT>";
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
|
||||
public PrinterSessionDialog(Frame parent, String defaultHost, int defaultPort, boolean defaultTls) {
|
||||
super(parent, "IBM 3287 Printer Session Manager", false);
|
||||
this.parent = parent;
|
||||
AppIcon.applyTo(this);
|
||||
|
||||
config = new PrinterConfig(defaultHost, defaultPort);
|
||||
config.setUseTls(defaultTls);
|
||||
@@ -71,7 +72,7 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
|
||||
gbc.insets = new Insets(3, 4, 3, 4);
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 12);
|
||||
Font labelFont = ThemeManager.getUiFont();
|
||||
|
||||
// Host / Port
|
||||
gbc.gridx = 0; gbc.gridy = 0;
|
||||
@@ -205,7 +206,7 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
|
||||
|
||||
// Spool text area
|
||||
spoolArea = new JTextArea();
|
||||
spoolArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||
spoolArea.setFont(ThemeManager.getMonospacedUiFont());
|
||||
spoolArea.setEditable(false);
|
||||
ThemeManager.styleTextArea(spoolArea);
|
||||
|
||||
@@ -256,7 +257,7 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
|
||||
|
||||
private JTextField createField(String text, int cols) {
|
||||
JTextField tf = new JTextField(text, cols);
|
||||
tf.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||
tf.setFont(ThemeManager.getMonospacedUiFont());
|
||||
ThemeManager.styleTextField(tf);
|
||||
return tf;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.j3270.J3270App;
|
||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||
import haus.nightmare.lib3270j.screen.ScreenHistoryBuffer;
|
||||
import haus.nightmare.lib3270j.screen.ScreenHistoryBuffer.ScreenHistoryListener;
|
||||
import haus.nightmare.lib3270j.screen.ScreenHistoryBuffer.ScreenSnapshot;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.event.*;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Screen History viewer dialog matching IBM Host On-Demand ScreenHistory UI parity.
|
||||
*
|
||||
* Provides:
|
||||
* - Interactive JSlider scrubber to review past 64 screen buffer frames
|
||||
* - Previous / Next navigation buttons
|
||||
* - Exact monospace terminal frame preview with selection capability
|
||||
* - Copy Screen, Copy Selection, and Copy as Table actions
|
||||
* - Live update synchronization as new host screens arrive
|
||||
*/
|
||||
public class ScreenHistoryDialog extends JDialog implements ScreenHistoryListener {
|
||||
|
||||
private final J3270App parentApp;
|
||||
private final Telnet3270Client client;
|
||||
private final ScreenHistoryBuffer historyBuffer;
|
||||
|
||||
private JSlider scrubSlider;
|
||||
private JButton btnPrev;
|
||||
private JButton btnNext;
|
||||
private JLabel infoLabel;
|
||||
private JTextArea previewArea;
|
||||
private JButton btnCopyScreen;
|
||||
private JButton btnCopySelection;
|
||||
private JButton btnCopyTable;
|
||||
private JButton btnClose;
|
||||
|
||||
private int currentIndex = -1; // 0-based index into historyBuffer
|
||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public ScreenHistoryDialog(J3270App parent, Telnet3270Client client) {
|
||||
super(parent, "Screen History", false);
|
||||
this.parentApp = parent;
|
||||
this.client = client;
|
||||
this.historyBuffer = client != null ? client.getScreenHistoryBuffer() : null;
|
||||
|
||||
AppIcon.applyTo(this);
|
||||
initComponents();
|
||||
setupKeyNavigation();
|
||||
|
||||
if (historyBuffer != null) {
|
||||
historyBuffer.addListener(this);
|
||||
refreshHistoryView(historyBuffer.size() - 1);
|
||||
} else {
|
||||
updateEmptyState();
|
||||
}
|
||||
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosed(WindowEvent e) {
|
||||
if (historyBuffer != null) {
|
||||
historyBuffer.removeListener(ScreenHistoryDialog.this);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setSize(780, 560);
|
||||
setLocationRelativeTo(parent);
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new BorderLayout(6, 6));
|
||||
JPanel contentPane = new JPanel(new BorderLayout(8, 8));
|
||||
contentPane.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||
setContentPane(contentPane);
|
||||
|
||||
// Top Navigation Bar
|
||||
JPanel topPanel = new JPanel(new BorderLayout(8, 8));
|
||||
topPanel.setBorder(BorderFactory.createTitledBorder("History Scrub Navigation"));
|
||||
|
||||
JPanel sliderPanel = new JPanel(new BorderLayout(6, 6));
|
||||
btnPrev = new JButton("◄ Prev");
|
||||
ThemeManager.styleButton(btnPrev, ThemeManager.ButtonVariant.DEFAULT);
|
||||
btnPrev.addActionListener(e -> stepHistory(-1));
|
||||
|
||||
btnNext = new JButton("Next ►");
|
||||
ThemeManager.styleButton(btnNext, ThemeManager.ButtonVariant.DEFAULT);
|
||||
btnNext.addActionListener(e -> stepHistory(1));
|
||||
|
||||
scrubSlider = new JSlider(1, 1, 1);
|
||||
scrubSlider.setMajorTickSpacing(10);
|
||||
scrubSlider.setMinorTickSpacing(1);
|
||||
scrubSlider.setPaintTicks(true);
|
||||
scrubSlider.setSnapToTicks(true);
|
||||
scrubSlider.addChangeListener(e -> {
|
||||
if (!scrubSlider.getValueIsAdjusting()) {
|
||||
int selected = scrubSlider.getValue() - 1;
|
||||
displayScreen(selected);
|
||||
}
|
||||
});
|
||||
|
||||
sliderPanel.add(btnPrev, BorderLayout.WEST);
|
||||
sliderPanel.add(scrubSlider, BorderLayout.CENTER);
|
||||
sliderPanel.add(btnNext, BorderLayout.EAST);
|
||||
|
||||
infoLabel = new JLabel("Screen 0 of 0", SwingConstants.CENTER);
|
||||
infoLabel.setFont(infoLabel.getFont().deriveFont(Font.BOLD, 13f));
|
||||
topPanel.add(sliderPanel, BorderLayout.CENTER);
|
||||
topPanel.add(infoLabel, BorderLayout.SOUTH);
|
||||
|
||||
contentPane.add(topPanel, BorderLayout.NORTH);
|
||||
|
||||
// Center Terminal Preview Pane
|
||||
previewArea = new JTextArea();
|
||||
previewArea.setEditable(false);
|
||||
previewArea.setFont(new Font("Monospaced", Font.PLAIN, 14));
|
||||
previewArea.setBackground(Color.BLACK);
|
||||
previewArea.setForeground(new Color(0, 255, 65)); // 3270 Green Phosphor
|
||||
previewArea.setCaretColor(Color.WHITE);
|
||||
previewArea.setLineWrap(false);
|
||||
previewArea.setMargin(new Insets(6, 6, 6, 6));
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane(previewArea);
|
||||
scrollPane.setBorder(BorderFactory.createLineBorder(new Color(60, 60, 60)));
|
||||
contentPane.add(scrollPane, BorderLayout.CENTER);
|
||||
|
||||
// Bottom Action Bar
|
||||
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 4));
|
||||
|
||||
btnCopyScreen = new JButton("Copy Screen");
|
||||
ThemeManager.styleButton(btnCopyScreen, ThemeManager.ButtonVariant.DEFAULT);
|
||||
btnCopyScreen.setToolTipText("Copy entire historical screen to clipboard");
|
||||
btnCopyScreen.addActionListener(e -> copyCurrentScreen());
|
||||
|
||||
btnCopySelection = new JButton("Copy Selection");
|
||||
ThemeManager.styleButton(btnCopySelection, ThemeManager.ButtonVariant.DEFAULT);
|
||||
btnCopySelection.setToolTipText("Copy selected text to clipboard");
|
||||
btnCopySelection.addActionListener(e -> copySelectedText());
|
||||
|
||||
btnCopyTable = new JButton("Copy as Table");
|
||||
ThemeManager.styleButton(btnCopyTable, ThemeManager.ButtonVariant.DEFAULT);
|
||||
btnCopyTable.setToolTipText("Copy screen text with trimmed trailing spaces per row");
|
||||
btnCopyTable.addActionListener(e -> copyAsTable());
|
||||
|
||||
btnClose = new JButton("Close");
|
||||
ThemeManager.styleButton(btnClose, ThemeManager.ButtonVariant.DEFAULT);
|
||||
btnClose.addActionListener(e -> setVisible(false));
|
||||
|
||||
bottomPanel.add(btnCopyScreen);
|
||||
bottomPanel.add(btnCopySelection);
|
||||
bottomPanel.add(btnCopyTable);
|
||||
bottomPanel.add(Box.createHorizontalStrut(12));
|
||||
bottomPanel.add(btnClose);
|
||||
|
||||
contentPane.add(bottomPanel, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
private void setupKeyNavigation() {
|
||||
// Keyboard shortcuts for rapid scrub navigation
|
||||
Action prevAction = new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) { stepHistory(-1); }
|
||||
};
|
||||
Action nextAction = new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) { stepHistory(1); }
|
||||
};
|
||||
Action homeAction = new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) { displayScreen(0); }
|
||||
};
|
||||
Action endAction = new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) {
|
||||
if (historyBuffer != null) displayScreen(historyBuffer.size() - 1);
|
||||
}
|
||||
};
|
||||
Action closeAction = new AbstractAction() {
|
||||
@Override public void actionPerformed(ActionEvent e) { setVisible(false); }
|
||||
};
|
||||
|
||||
JRootPane root = getRootPane();
|
||||
InputMap im = root.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
||||
ActionMap am = root.getActionMap();
|
||||
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.ALT_DOWN_MASK), "prevScreen");
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), "prevScreen");
|
||||
am.put("prevScreen", prevAction);
|
||||
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.ALT_DOWN_MASK), "nextScreen");
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "nextScreen");
|
||||
am.put("nextScreen", nextAction);
|
||||
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, InputEvent.ALT_DOWN_MASK), "homeScreen");
|
||||
am.put("homeScreen", homeAction);
|
||||
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, InputEvent.ALT_DOWN_MASK), "endScreen");
|
||||
am.put("endScreen", endAction);
|
||||
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeDialog");
|
||||
am.put("closeDialog", closeAction);
|
||||
}
|
||||
|
||||
private void stepHistory(int delta) {
|
||||
if (historyBuffer == null || historyBuffer.isEmpty()) return;
|
||||
int target = currentIndex + delta;
|
||||
target = Math.max(0, Math.min(target, historyBuffer.size() - 1));
|
||||
displayScreen(target);
|
||||
}
|
||||
|
||||
private void displayScreen(int index) {
|
||||
if (historyBuffer == null || historyBuffer.isEmpty()) {
|
||||
updateEmptyState();
|
||||
return;
|
||||
}
|
||||
|
||||
int total = historyBuffer.size();
|
||||
index = Math.max(0, Math.min(index, total - 1));
|
||||
currentIndex = index;
|
||||
|
||||
ScreenSnapshot snap = historyBuffer.get(index);
|
||||
if (snap != null) {
|
||||
previewArea.setText(snap.getText());
|
||||
previewArea.setCaretPosition(0);
|
||||
|
||||
String timeStr = dateFormat.format(new Date(snap.getTimestamp()));
|
||||
infoLabel.setText("Screen " + (index + 1) + " of " + total + " (" + timeStr + ") — "
|
||||
+ snap.getRows() + "x" + snap.getCols() + ", Cursor: ["
|
||||
+ (snap.getCursorRow() + 1) + "," + (snap.getCursorCol() + 1) + "]");
|
||||
|
||||
scrubSlider.setValue(index + 1);
|
||||
}
|
||||
|
||||
btnPrev.setEnabled(index > 0);
|
||||
btnNext.setEnabled(index < total - 1);
|
||||
btnCopyScreen.setEnabled(true);
|
||||
btnCopySelection.setEnabled(true);
|
||||
btnCopyTable.setEnabled(true);
|
||||
}
|
||||
|
||||
private void updateEmptyState() {
|
||||
currentIndex = -1;
|
||||
previewArea.setText("No screen history captured yet.\n\nScreens are automatically recorded as the host session receives updates or unlocks the keyboard.");
|
||||
infoLabel.setText("Screen 0 of 0 — Empty History");
|
||||
scrubSlider.setEnabled(false);
|
||||
btnPrev.setEnabled(false);
|
||||
btnNext.setEnabled(false);
|
||||
btnCopyScreen.setEnabled(false);
|
||||
btnCopySelection.setEnabled(false);
|
||||
btnCopyTable.setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh slider bounds and select target index.
|
||||
*/
|
||||
public synchronized void refreshHistoryView(int targetIndex) {
|
||||
if (historyBuffer == null || historyBuffer.isEmpty()) {
|
||||
updateEmptyState();
|
||||
return;
|
||||
}
|
||||
|
||||
int total = historyBuffer.size();
|
||||
scrubSlider.setEnabled(true);
|
||||
scrubSlider.setMinimum(1);
|
||||
scrubSlider.setMaximum(total);
|
||||
|
||||
int select = targetIndex >= 0 ? Math.min(targetIndex, total - 1) : total - 1;
|
||||
displayScreen(select);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHistoryUpdated(ScreenHistoryBuffer buffer, ScreenSnapshot newSnapshot) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
boolean wasAtLatest = (currentIndex >= 0 && currentIndex == buffer.size() - 2);
|
||||
int newTotal = buffer.size();
|
||||
scrubSlider.setMaximum(newTotal);
|
||||
|
||||
if (wasAtLatest || currentIndex < 0) {
|
||||
// Keep tracked with latest frame
|
||||
displayScreen(newTotal - 1);
|
||||
} else {
|
||||
// Keep operator's active scrub position, update label
|
||||
displayScreen(currentIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void copyCurrentScreen() {
|
||||
if (historyBuffer == null || currentIndex < 0) return;
|
||||
ScreenSnapshot snap = historyBuffer.get(currentIndex);
|
||||
if (snap != null) {
|
||||
setClipboardText(snap.getText());
|
||||
}
|
||||
}
|
||||
|
||||
private void copySelectedText() {
|
||||
String selected = previewArea.getSelectedText();
|
||||
if (selected != null && !selected.isEmpty()) {
|
||||
setClipboardText(selected);
|
||||
} else {
|
||||
copyCurrentScreen();
|
||||
}
|
||||
}
|
||||
|
||||
private void copyAsTable() {
|
||||
if (historyBuffer == null || currentIndex < 0) return;
|
||||
ScreenSnapshot snap = historyBuffer.get(currentIndex);
|
||||
if (snap != null) {
|
||||
setClipboardText(snap.getFormattedTable());
|
||||
}
|
||||
}
|
||||
|
||||
private void setClipboardText(String text) {
|
||||
try {
|
||||
Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||
cb.setContents(new StringSelection(text), null);
|
||||
} catch (Exception e) {
|
||||
Toolkit.getDefaultToolkit().beep();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ public class ScriptDialog extends JDialog {
|
||||
super(parent, "Run Keystrokes / ECL Script", false);
|
||||
this.client = client;
|
||||
this.terminalPanel = terminalPanel;
|
||||
AppIcon.applyTo(this);
|
||||
|
||||
buildUI();
|
||||
setSize(600, 480);
|
||||
@@ -35,7 +36,7 @@ public class ScriptDialog extends JDialog {
|
||||
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||
mainPanel.setBorder(new EmptyBorder(12, 14, 12, 14));
|
||||
|
||||
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
|
||||
Font labelFont = ThemeManager.getUiFont();
|
||||
|
||||
// Header
|
||||
JPanel topPanel = new JPanel(new BorderLayout(5, 5));
|
||||
@@ -48,7 +49,7 @@ public class ScriptDialog extends JDialog {
|
||||
|
||||
// Script area
|
||||
scriptArea = new JTextArea();
|
||||
scriptArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||
scriptArea.setFont(ThemeManager.getMonospacedUiFont());
|
||||
scriptArea.setLineWrap(true);
|
||||
scriptArea.setWrapStyleWord(false);
|
||||
ThemeManager.styleTextArea(scriptArea);
|
||||
@@ -74,7 +75,7 @@ public class ScriptDialog extends JDialog {
|
||||
|
||||
for (String token : tokens) {
|
||||
JButton btn = new JButton(token);
|
||||
btn.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 11));
|
||||
btn.setFont(ThemeManager.getMonospacedUiFont(Font.PLAIN, -2));
|
||||
ThemeManager.styleButton(btn, ThemeManager.ButtonVariant.DEFAULT);
|
||||
btn.setFocusable(false);
|
||||
btn.setMargin(new Insets(2, 4, 2, 4));
|
||||
@@ -94,7 +95,7 @@ public class ScriptDialog extends JDialog {
|
||||
|
||||
statusLabel = new JLabel("Ready");
|
||||
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||
statusLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||
statusLabel.setFont(labelFont);
|
||||
bottomPanel.add(statusLabel, BorderLayout.WEST);
|
||||
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||
|
||||
@@ -29,14 +29,15 @@ public class StatusBar extends JPanel {
|
||||
|
||||
private Telnet3270Client client;
|
||||
private TerminalPanel terminalPanel;
|
||||
private haus.nightmare.lib3270j.ecl.ECLOIANotify oiaNotifyListener;
|
||||
|
||||
public StatusBar() {
|
||||
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
|
||||
setBackground(ThemeManager.getStatusBarBg());
|
||||
setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, ThemeManager.getStatusBarBorder()));
|
||||
setPreferredSize(new Dimension(800, 22));
|
||||
|
||||
Font oiaFont = new Font(Font.MONOSPACED, Font.PLAIN, 12);
|
||||
Font oiaFont = ThemeManager.getMonospacedUiFont();
|
||||
updateBarHeight(oiaFont);
|
||||
|
||||
connectionStatus = createLabel("Not Connected", oiaFont, ThemeManager.getOiaFgDim());
|
||||
tlsStatus = createLabel("", oiaFont, ThemeManager.getOiaFgNormal());
|
||||
@@ -131,8 +132,15 @@ public class StatusBar extends JPanel {
|
||||
}
|
||||
|
||||
public void setClient(Telnet3270Client client, TerminalPanel terminalPanel) {
|
||||
if (this.client != null && this.client.getOIA() != null && this.oiaNotifyListener != null) {
|
||||
this.client.getOIA().unregisterOIAEvent(this.oiaNotifyListener);
|
||||
}
|
||||
this.client = client;
|
||||
this.terminalPanel = terminalPanel;
|
||||
if (this.client != null && this.client.getOIA() != null) {
|
||||
this.oiaNotifyListener = event -> updateStatus();
|
||||
this.client.getOIA().registerOIAEvent(this.oiaNotifyListener);
|
||||
}
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
@@ -143,9 +151,36 @@ public class StatusBar extends JPanel {
|
||||
}
|
||||
setBackground(ThemeManager.getStatusBarBg(theme));
|
||||
setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, ThemeManager.getStatusBarBorder(theme)));
|
||||
Font oiaFont = ThemeManager.getMonospacedUiFont();
|
||||
updateBarHeight(oiaFont);
|
||||
applyFontRecursively(this, oiaFont);
|
||||
revalidate();
|
||||
repaint();
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
private void updateBarHeight(Font font) {
|
||||
int fontHeight = 16;
|
||||
try {
|
||||
FontMetrics fm = getFontMetrics(font);
|
||||
if (fm != null) fontHeight = fm.getHeight();
|
||||
} catch (Exception ignored) {
|
||||
fontHeight = font.getSize() + 4;
|
||||
}
|
||||
int barHeight = Math.max(22, fontHeight + 6);
|
||||
setPreferredSize(new Dimension(800, barHeight));
|
||||
}
|
||||
|
||||
private void applyFontRecursively(Component comp, Font font) {
|
||||
if (comp instanceof JLabel) {
|
||||
comp.setFont(font);
|
||||
} else if (comp instanceof Container) {
|
||||
for (Component child : ((Container) comp).getComponents()) {
|
||||
applyFontRecursively(child, font);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateStatus() {
|
||||
if (isDisplayable() && !SwingUtilities.isEventDispatchThread()) {
|
||||
SwingUtilities.invokeLater(this::updateStatus);
|
||||
@@ -361,4 +396,8 @@ public class StatusBar extends JPanel {
|
||||
cursorPosition.setText(String.format("%03d/%03d [%04d]", row, col, curAddr));
|
||||
cursorPosition.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||
}
|
||||
|
||||
public String getLockStatusText() {
|
||||
return lockStatus != null ? lockStatus.getText() : "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
// Font and cell dimensions
|
||||
private Font terminalFont;
|
||||
private Font boldTerminalFont;
|
||||
private int cellWidth;
|
||||
private int cellHeight;
|
||||
int cellWidth;
|
||||
int cellHeight;
|
||||
private int fontAscent;
|
||||
private int fontDescent;
|
||||
private int padding = 4;
|
||||
@@ -74,8 +74,18 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
private int selectionStartRow = -1, selectionStartCol = -1;
|
||||
private int selectionEndRow = -1, selectionEndCol = -1;
|
||||
private boolean isDragging = false;
|
||||
private boolean isActivationClick = false;
|
||||
private static final Color SELECTION_COLOR = new Color(75, 110, 175, 102); // 40% alpha blend
|
||||
|
||||
public enum DragHandle {
|
||||
NONE, NW, N, NE, E, SE, S, SW, W, MOVE
|
||||
}
|
||||
private boolean trimRectSizingHandles = false;
|
||||
private DragHandle activeDragHandle = DragHandle.NONE;
|
||||
private int dragAnchorRow = -1;
|
||||
private int dragAnchorCol = -1;
|
||||
private int initialBoxR1 = -1, initialBoxR2 = -1, initialBoxC1 = -1, initialBoxC2 = -1;
|
||||
|
||||
// ========== Search Highlight state ==========
|
||||
private int searchHighlightAddr = -1;
|
||||
private int searchHighlightLen = 0;
|
||||
@@ -88,6 +98,14 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
private boolean resizeGuard = false;
|
||||
private StatusBar statusBar = null;
|
||||
|
||||
// ========== Phase 25: Hotspots & Mouse Wheel ==========
|
||||
private final HotspotEngine hotspotEngine = new HotspotEngine();
|
||||
private HotspotEngine.Hotspot hoveredHotspot = null;
|
||||
private boolean hotspotsEnabled = haus.nightmare.j3270.config.Settings.getHotspotsEnabled();
|
||||
private boolean mouseWheelEnabled = haus.nightmare.j3270.config.Settings.getMouseWheelEnabled();
|
||||
private String mouseWheelUpAction = haus.nightmare.j3270.config.Settings.getMouseWheelUpKey();
|
||||
private String mouseWheelDownAction = haus.nightmare.j3270.config.Settings.getMouseWheelDownKey();
|
||||
|
||||
// ========== Mode and Listener State ==========
|
||||
private boolean insertMode = false;
|
||||
|
||||
@@ -117,7 +135,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
/**
|
||||
* Compute the horizontal render offset to center the grid within the panel.
|
||||
*/
|
||||
private int getRenderOffsetX() {
|
||||
int getRenderOffsetX() {
|
||||
int termCols = 80;
|
||||
if (client != null) termCols = client.getScreenBuffer().getDisplayCols();
|
||||
int gridW = termCols * cellWidth;
|
||||
@@ -128,7 +146,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
/**
|
||||
* Compute the vertical render offset to center the grid within the panel.
|
||||
*/
|
||||
private int getRenderOffsetY() {
|
||||
int getRenderOffsetY() {
|
||||
int termRows = 24;
|
||||
if (client != null) termRows = client.getScreenBuffer().getDisplayRows();
|
||||
int gridH = termRows * cellHeight;
|
||||
@@ -178,6 +196,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
setupCursorBlink();
|
||||
|
||||
blockSelectMode = haus.nightmare.j3270.config.Settings.getBlockSelectMode();
|
||||
trimRectSizingHandles = haus.nightmare.j3270.config.Settings.getTrimRectSizingHandles();
|
||||
crosshairRulerEnabled = haus.nightmare.j3270.config.Settings.getCrosshairRuler();
|
||||
String cStyle = haus.nightmare.j3270.config.Settings.getCursorStyle();
|
||||
cursorStyle = "UNDERLINE".equalsIgnoreCase(cStyle) ? CursorStyle.UNDERLINE : CursorStyle.BLOCK;
|
||||
@@ -186,7 +205,16 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
MouseAdapter mouseHandler = new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
requestFocusInWindow();
|
||||
Window win = SwingUtilities.getWindowAncestor(TerminalPanel.this);
|
||||
boolean wasWindowInactive = (win != null && (!win.isActive() || !win.isFocused()));
|
||||
boolean wasPanelUnfocused = !hasFocus();
|
||||
isActivationClick = wasWindowInactive || wasPanelUnfocused;
|
||||
|
||||
if (wasWindowInactive) {
|
||||
win.toFront();
|
||||
win.requestFocus();
|
||||
}
|
||||
requestFocus();
|
||||
|
||||
// Right-click context menu
|
||||
if (SwingUtilities.isRightMouseButton(e)) {
|
||||
@@ -206,18 +234,30 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
row = Math.max(0, Math.min(row, displayRows - 1));
|
||||
|
||||
boolean isGraphic = client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive();
|
||||
boolean isSelectableField = false;
|
||||
if (sb.isFormatted()) {
|
||||
int faPos = sb.findFieldAttribute(row * displayCols + col);
|
||||
if (faPos >= 0) {
|
||||
int fa = sb.getCell(faPos).fa & 0xFF;
|
||||
isSelectableField = haus.nightmare.lib3270j.protocol.DS3270Constants.faIsSelectable(fa) &&
|
||||
!haus.nightmare.lib3270j.protocol.DS3270Constants.faIsZero(fa);
|
||||
}
|
||||
}
|
||||
if (isGraphic || lightPenMode || isSelectableField) {
|
||||
if (isGraphic || lightPenMode) {
|
||||
clearSelection();
|
||||
} else if (SwingUtilities.isLeftMouseButton(e) && trimRectSizingHandles && hasSelection()) {
|
||||
DragHandle handle = getDragHandleAt(e.getX(), e.getY());
|
||||
if (handle != DragHandle.NONE) {
|
||||
activeDragHandle = handle;
|
||||
isDragging = true;
|
||||
dragAnchorRow = row;
|
||||
dragAnchorCol = col;
|
||||
initialBoxR1 = Math.min(selectionStartRow, selectionEndRow);
|
||||
initialBoxR2 = Math.max(selectionStartRow, selectionEndRow);
|
||||
initialBoxC1 = Math.min(selectionStartCol, selectionEndCol);
|
||||
initialBoxC2 = Math.max(selectionStartCol, selectionEndCol);
|
||||
repaint();
|
||||
return;
|
||||
} else {
|
||||
activeDragHandle = DragHandle.NONE;
|
||||
selectionStartRow = row;
|
||||
selectionStartCol = col;
|
||||
selectionEndRow = row;
|
||||
selectionEndCol = col;
|
||||
}
|
||||
} else {
|
||||
activeDragHandle = DragHandle.NONE;
|
||||
selectionStartRow = row;
|
||||
selectionStartCol = col;
|
||||
selectionEndRow = row;
|
||||
@@ -251,9 +291,44 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
col = Math.max(0, Math.min(col, sb.getDisplayCols() - 1));
|
||||
row = Math.max(0, Math.min(row, sb.getDisplayRows() - 1));
|
||||
|
||||
if (activeDragHandle != DragHandle.NONE) {
|
||||
if (activeDragHandle == DragHandle.MOVE) {
|
||||
int deltaR = row - dragAnchorRow;
|
||||
int deltaC = col - dragAnchorCol;
|
||||
int h = initialBoxR2 - initialBoxR1;
|
||||
int w = initialBoxC2 - initialBoxC1;
|
||||
int newR1 = Math.max(0, Math.min(initialBoxR1 + deltaR, sb.getDisplayRows() - 1 - h));
|
||||
int newC1 = Math.max(0, Math.min(initialBoxC1 + deltaC, sb.getDisplayCols() - 1 - w));
|
||||
selectionStartRow = newR1;
|
||||
selectionStartCol = newC1;
|
||||
selectionEndRow = newR1 + h;
|
||||
selectionEndCol = newC1 + w;
|
||||
} else {
|
||||
int newR1 = initialBoxR1;
|
||||
int newR2 = initialBoxR2;
|
||||
int newC1 = initialBoxC1;
|
||||
int newC2 = initialBoxC2;
|
||||
switch (activeDragHandle) {
|
||||
case NW: newR1 = row; newC1 = col; break;
|
||||
case N: newR1 = row; break;
|
||||
case NE: newR1 = row; newC2 = col; break;
|
||||
case E: newC2 = col; break;
|
||||
case SE: newR2 = row; newC2 = col; break;
|
||||
case S: newR2 = row; break;
|
||||
case SW: newR2 = row; newC1 = col; break;
|
||||
case W: newC1 = col; break;
|
||||
default: break;
|
||||
}
|
||||
selectionStartRow = Math.min(newR1, newR2);
|
||||
selectionEndRow = Math.max(newR1, newR2);
|
||||
selectionStartCol = Math.min(newC1, newC2);
|
||||
selectionEndCol = Math.max(newC1, newC2);
|
||||
}
|
||||
} else {
|
||||
selectionEndRow = row;
|
||||
selectionEndCol = col;
|
||||
}
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
@@ -274,12 +349,72 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
py = Math.max(0, Math.min(py, gHeight - 1));
|
||||
client.getGocaDecoder().setGraphicCursorFromPixel(px, py);
|
||||
}
|
||||
|
||||
// Phase 26.1: Drag handle cursor hover detection
|
||||
if (trimRectSizingHandles && hasSelection() && client != null && client.getConnectionState().isFullSession()) {
|
||||
DragHandle handle = getDragHandleAt(e.getX(), e.getY());
|
||||
if (handle != DragHandle.NONE) {
|
||||
setCursor(getCursorForDragHandle(handle));
|
||||
setToolTipText(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 25.2: Real-time hotspot hover detection
|
||||
if (hotspotsEnabled && client != null && client.getScreenBuffer() != null) {
|
||||
int ox = getRenderOffsetX();
|
||||
int oy = getRenderOffsetY();
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
int rows = sb.getDisplayRows();
|
||||
int cols = sb.getDisplayCols();
|
||||
int col = (e.getX() - ox) / cellWidth;
|
||||
int row = (e.getY() - oy) / cellHeight;
|
||||
if (row >= 0 && row < rows && col >= 0 && col < cols) {
|
||||
hotspotEngine.scan(sb);
|
||||
HotspotEngine.Hotspot h = hotspotEngine.findHotspot(row, col);
|
||||
if (h != null) {
|
||||
if (hoveredHotspot != h) {
|
||||
hoveredHotspot = h;
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
setToolTipText(h.getTooltip());
|
||||
repaint();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hoveredHotspot != null) {
|
||||
hoveredHotspot = null;
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
setToolTipText(null);
|
||||
repaint();
|
||||
} else if (activeDragHandle == DragHandle.NONE) {
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
if (hoveredHotspot != null) {
|
||||
hoveredHotspot = null;
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
setToolTipText(null);
|
||||
repaint();
|
||||
} else if (activeDragHandle == DragHandle.NONE) {
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
if (activeDragHandle != DragHandle.NONE) {
|
||||
activeDragHandle = DragHandle.NONE;
|
||||
isActivationClick = false;
|
||||
repaint();
|
||||
return;
|
||||
}
|
||||
if (client != null && client.getConnectionState().isFullSession()) {
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
int ox = getRenderOffsetX();
|
||||
@@ -292,7 +427,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
row = Math.max(0, Math.min(row, displayRows - 1));
|
||||
int clickAddr = row * displayCols + col;
|
||||
|
||||
boolean isGraphic = client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive() && client.getGraphicsPlane() != null;
|
||||
boolean isGraphic = client.getGocaDecoder() != null &&
|
||||
(client.getGocaDecoder().isGraphicsCursorActive() ||
|
||||
(client.getGraphicsPlane() != null && client.getGraphicsPlane().hasContent()));
|
||||
|
||||
if (isGraphic) {
|
||||
clearSelection();
|
||||
@@ -307,8 +444,11 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
py = Math.max(0, Math.min(py, gHeight - 1));
|
||||
client.getGocaDecoder().setGraphicCursorFromPixel(px, py);
|
||||
|
||||
int gx = client.getGocaDecoder().getGraphicCursorX();
|
||||
int gy = client.getGocaDecoder().getGraphicCursorY();
|
||||
int button = javax.swing.SwingUtilities.isRightMouseButton(e) ? 2 : 1;
|
||||
client.getInputProcessor().sendGraphicMouseAid(
|
||||
gx, gy,
|
||||
haus.nightmare.lib3270j.protocol.DS3270Constants.AID_ENTER,
|
||||
button,
|
||||
e.isShiftDown(),
|
||||
@@ -318,14 +458,65 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
return;
|
||||
}
|
||||
|
||||
// Automatic Light-Pen / Selectable Field detection on mouse click:
|
||||
boolean wasDrag = (selectionStartRow != -1 && selectionEndRow != -1 &&
|
||||
!(selectionStartRow == selectionEndRow && selectionStartCol == selectionEndCol));
|
||||
boolean activation = isActivationClick;
|
||||
isActivationClick = false;
|
||||
|
||||
if (wasDrag) {
|
||||
// Preserve drag/block selection so user can interact with menus or press Ctrl+C
|
||||
repaint();
|
||||
return;
|
||||
}
|
||||
|
||||
// Single click (not a drag):
|
||||
clearSelection();
|
||||
sb.setCursorAddress(clickAddr);
|
||||
|
||||
// If this was an activation click (e.g. clicking to resume focus),
|
||||
// do not fire field actions or send AIDs
|
||||
if (activation) {
|
||||
refreshScreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 25.2: Interactive Hotspot click execution (URLs, PF keys, choices)
|
||||
if (hotspotsEnabled && client.getConnectionState().isFullSession()) {
|
||||
HotspotEngine.Hotspot h = hotspotEngine.findHotspot(row, col);
|
||||
if (h != null) {
|
||||
if (h.getType() == HotspotEngine.HotspotType.URL) {
|
||||
openUrl(h.getAction());
|
||||
return;
|
||||
} else {
|
||||
client.getPS().SendKeys(h.getAction());
|
||||
refreshScreen();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Automatic Light-Pen / Selectable Field detection on mouse click (HoD parity):
|
||||
if (sb.isFormatted()) {
|
||||
int faPos = sb.findFieldAttribute(clickAddr);
|
||||
if (faPos >= 0) {
|
||||
int fa = sb.getCell(faPos).fa & 0xFF;
|
||||
if (haus.nightmare.lib3270j.protocol.DS3270Constants.faIsSelectable(fa) &&
|
||||
!haus.nightmare.lib3270j.protocol.DS3270Constants.faIsZero(fa)) {
|
||||
clearSelection();
|
||||
int size = displayRows * displayCols;
|
||||
int desPos = (faPos + 1) % size;
|
||||
char desAscii = (char) sb.getCell(desPos).ucs4;
|
||||
int desEbcdic = sb.getCell(desPos).ec & 0xFF;
|
||||
|
||||
boolean isToggleField = (desEbcdic == 0x6F || desAscii == '?' || desAscii == '\uFF1F' ||
|
||||
desEbcdic == 0x6E || desAscii == '>' || desAscii == '\uFF1E');
|
||||
boolean isEnterField = (desEbcdic == 0x50 || desAscii == '&' || desAscii == '\uFF06');
|
||||
boolean isProtected = haus.nightmare.lib3270j.protocol.DS3270Constants.faIsProtected(fa);
|
||||
|
||||
boolean isDoubleClick = (e.getClickCount() >= 2);
|
||||
// For detectable fields (FA bits 4-5 = 01 or 11):
|
||||
// Double-clicking or invoking mnemonic [cursel] toggles the designator character (? -> >).
|
||||
// If field has immediate select attribute, immediately transmit Cursor Select AID (0x7E).
|
||||
if (lightPenMode || isDoubleClick || isToggleField || isEnterField || !isProtected) {
|
||||
if (client.lightPenSelect(clickAddr)) {
|
||||
refreshScreen();
|
||||
return;
|
||||
@@ -333,10 +524,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lightPenMode) {
|
||||
clearSelection();
|
||||
sb.setCursorAddress(clickAddr);
|
||||
boolean result = client.lightPenSelect(clickAddr);
|
||||
if (result) {
|
||||
refreshScreen();
|
||||
@@ -346,13 +536,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
|
||||
if (selectionStartRow == selectionEndRow && selectionStartCol == selectionEndCol) {
|
||||
sb.setCursorAddress(clickAddr);
|
||||
clearSelection();
|
||||
refreshScreen();
|
||||
return;
|
||||
}
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
@@ -360,6 +546,21 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
addMouseListener(mouseHandler);
|
||||
addMouseMotionListener(mouseHandler);
|
||||
|
||||
// Phase 25.2: Mouse wheel navigation (Wheel Up -> [pf7], Wheel Down -> [pf8])
|
||||
addMouseWheelListener(e -> {
|
||||
if (!mouseWheelEnabled || client == null || !client.getConnectionState().isFullSession()) {
|
||||
return;
|
||||
}
|
||||
int rotation = e.getWheelRotation();
|
||||
if (rotation < 0) {
|
||||
client.getPS().SendKeys(mouseWheelUpAction);
|
||||
refreshScreen();
|
||||
} else if (rotation > 0) {
|
||||
client.getPS().SendKeys(mouseWheelDownAction);
|
||||
refreshScreen();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle resize: keep user configured font persistent without auto-fit reset
|
||||
addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
@@ -370,8 +571,104 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
});
|
||||
}
|
||||
|
||||
// ========== Phase 25: Hotspot URL Opener ==========
|
||||
|
||||
private void openUrl(String url) {
|
||||
if (url == null || url.trim().isEmpty()) return;
|
||||
try {
|
||||
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
||||
Desktop.getDesktop().browse(new java.net.URI(url));
|
||||
return;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
// Fallback: command line launcher
|
||||
try {
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
if (os.contains("win")) {
|
||||
Runtime.getRuntime().exec(new String[]{"rundll32", "url.dll,FileProtocolHandler", url});
|
||||
} else if (os.contains("mac")) {
|
||||
Runtime.getRuntime().exec(new String[]{"open", url});
|
||||
} else {
|
||||
Runtime.getRuntime().exec(new String[]{"xdg-open", url});
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Toolkit.getDefaultToolkit().beep();
|
||||
}
|
||||
}
|
||||
|
||||
public HotspotEngine getHotspotEngine() {
|
||||
return hotspotEngine;
|
||||
}
|
||||
|
||||
// ========== Selection / Copy-Paste ==========
|
||||
|
||||
public boolean isBlockSelectMode() {
|
||||
return blockSelectMode;
|
||||
}
|
||||
|
||||
public void setBlockSelectMode(boolean val) {
|
||||
this.blockSelectMode = val;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public boolean isTrimRectSizingHandles() {
|
||||
return trimRectSizingHandles;
|
||||
}
|
||||
|
||||
public void setTrimRectSizingHandles(boolean val) {
|
||||
this.trimRectSizingHandles = val;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public DragHandle getDragHandleAt(int mx, int my) {
|
||||
if (!hasSelection() || client == null) return DragHandle.NONE;
|
||||
int ox = getRenderOffsetX();
|
||||
int oy = getRenderOffsetY();
|
||||
int r1 = Math.min(selectionStartRow, selectionEndRow);
|
||||
int r2 = Math.max(selectionStartRow, selectionEndRow);
|
||||
int c1 = Math.min(selectionStartCol, selectionEndCol);
|
||||
int c2 = Math.max(selectionStartCol, selectionEndCol);
|
||||
|
||||
int x1 = ox + c1 * cellWidth;
|
||||
int y1 = oy + r1 * cellHeight;
|
||||
int x2 = ox + (c2 + 1) * cellWidth;
|
||||
int y2 = oy + (r2 + 1) * cellHeight;
|
||||
int xm = (x1 + x2) / 2;
|
||||
int ym = (y1 + y2) / 2;
|
||||
|
||||
int tol = 6;
|
||||
if (Math.abs(mx - x1) <= tol && Math.abs(my - y1) <= tol) return DragHandle.NW;
|
||||
if (Math.abs(mx - xm) <= tol && Math.abs(my - y1) <= tol) return DragHandle.N;
|
||||
if (Math.abs(mx - x2) <= tol && Math.abs(my - y1) <= tol) return DragHandle.NE;
|
||||
if (Math.abs(mx - x2) <= tol && Math.abs(my - ym) <= tol) return DragHandle.E;
|
||||
if (Math.abs(mx - x2) <= tol && Math.abs(my - y2) <= tol) return DragHandle.SE;
|
||||
if (Math.abs(mx - xm) <= tol && Math.abs(my - y2) <= tol) return DragHandle.S;
|
||||
if (Math.abs(mx - x1) <= tol && Math.abs(my - y2) <= tol) return DragHandle.SW;
|
||||
if (Math.abs(mx - x1) <= tol && Math.abs(my - ym) <= tol) return DragHandle.W;
|
||||
|
||||
if (mx >= x1 && mx <= x2 && my >= y1 && my <= y2) {
|
||||
return DragHandle.MOVE;
|
||||
}
|
||||
|
||||
return DragHandle.NONE;
|
||||
}
|
||||
|
||||
public Cursor getCursorForDragHandle(DragHandle handle) {
|
||||
switch (handle) {
|
||||
case NW: return Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR);
|
||||
case N: return Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR);
|
||||
case NE: return Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR);
|
||||
case E: return Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
|
||||
case SE: return Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR);
|
||||
case S: return Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR);
|
||||
case SW: return Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR);
|
||||
case W: return Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR);
|
||||
case MOVE: return Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR);
|
||||
default: return Cursor.getDefaultCursor();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearSelection() {
|
||||
selectionStartRow = -1;
|
||||
selectionStartCol = -1;
|
||||
@@ -417,23 +714,30 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
int r2 = Math.max(selectionStartRow, selectionEndRow);
|
||||
int c1, c2;
|
||||
|
||||
if (blockSelectMode) {
|
||||
if (blockSelectMode || trimRectSizingHandles) {
|
||||
c1 = Math.min(selectionStartCol, selectionEndCol);
|
||||
c2 = Math.max(selectionStartCol, selectionEndCol);
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int r = r1; r <= r2; r++) {
|
||||
if (r > r1) result.append('\n');
|
||||
StringBuilder line = new StringBuilder();
|
||||
for (int c = c1; c <= c2; c++) {
|
||||
int baddr = r * cols + c;
|
||||
ExtendedAttribute ea = sb.getCell(baddr);
|
||||
char ch = ea.ucs4;
|
||||
if (ea.isFieldAttribute() || ch <= 0x20 || ch == 0xFF) {
|
||||
result.append(' ');
|
||||
line.append(' ');
|
||||
} else {
|
||||
result.append(ch);
|
||||
line.append(ch);
|
||||
}
|
||||
}
|
||||
// Trim trailing whitespace on copied lines per HoD CutPasteOptions
|
||||
int end = line.length();
|
||||
while (end > 0 && line.charAt(end - 1) == ' ') {
|
||||
end--;
|
||||
}
|
||||
result.append(line.substring(0, end));
|
||||
}
|
||||
return result.toString();
|
||||
} else {
|
||||
@@ -448,21 +752,34 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
StringBuilder line = new StringBuilder();
|
||||
int prevRow = startAddr / cols;
|
||||
for (int addr = startAddr; addr <= endAddr; addr++) {
|
||||
int curRow = addr / cols;
|
||||
if (curRow != prevRow) {
|
||||
result.append('\n');
|
||||
int end = line.length();
|
||||
while (end > 0 && line.charAt(end - 1) == ' ') {
|
||||
end--;
|
||||
}
|
||||
if (result.length() > 0) result.append('\n');
|
||||
result.append(line.substring(0, end));
|
||||
line.setLength(0);
|
||||
prevRow = curRow;
|
||||
}
|
||||
ExtendedAttribute ea = sb.getCell(addr);
|
||||
char ch = ea.ucs4;
|
||||
if (ea.isFieldAttribute() || ch <= 0x20 || ch == 0xFF) {
|
||||
result.append(' ');
|
||||
line.append(' ');
|
||||
} else {
|
||||
result.append(ch);
|
||||
line.append(ch);
|
||||
}
|
||||
}
|
||||
int end = line.length();
|
||||
while (end > 0 && line.charAt(end - 1) == ' ') {
|
||||
end--;
|
||||
}
|
||||
if (result.length() > 0) result.append('\n');
|
||||
result.append(line.substring(0, end));
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -473,6 +790,65 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
StringSelection ss = new StringSelection(text);
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null);
|
||||
}
|
||||
if (!haus.nightmare.j3270.config.Settings.getTrimRectRemainAfterEdit()) {
|
||||
clearSelection();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
public void cutSelection() {
|
||||
if (!hasSelection() || client == null || !client.getConnectionState().isFullSession()) return;
|
||||
String text = getSelectedText();
|
||||
if (!text.isEmpty()) {
|
||||
StringSelection ss = new StringSelection(text);
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null);
|
||||
}
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
int cols = sb.getCols();
|
||||
int r1 = Math.min(selectionStartRow, selectionEndRow);
|
||||
int r2 = Math.max(selectionStartRow, selectionEndRow);
|
||||
int c1 = Math.min(selectionStartCol, selectionEndCol);
|
||||
int c2 = Math.max(selectionStartCol, selectionEndCol);
|
||||
|
||||
if (blockSelectMode || trimRectSizingHandles) {
|
||||
for (int r = r1; r <= r2; r++) {
|
||||
for (int c = c1; c <= c2; c++) {
|
||||
int baddr = r * cols + c;
|
||||
if (!sb.isProtected(baddr) && !sb.getCell(baddr).isFieldAttribute()) {
|
||||
ExtendedAttribute ea = sb.getCell(baddr);
|
||||
ea.ucs4 = ' ';
|
||||
ea.ec = (byte) 0x40;
|
||||
if (sb.isFormatted()) {
|
||||
int faAddr = sb.findFieldAttribute(baddr);
|
||||
if (faAddr >= 0) {
|
||||
sb.getCell(faAddr).fa = (byte) (sb.getCell(faAddr).fa | haus.nightmare.lib3270j.protocol.DS3270Constants.FA_MODIFY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int startAddr = Math.min(selectionStartRow * cols + selectionStartCol, selectionEndRow * cols + selectionEndCol);
|
||||
int endAddr = Math.max(selectionStartRow * cols + selectionStartCol, selectionEndRow * cols + selectionEndCol);
|
||||
for (int baddr = startAddr; baddr <= endAddr; baddr++) {
|
||||
if (!sb.isProtected(baddr) && !sb.getCell(baddr).isFieldAttribute()) {
|
||||
ExtendedAttribute ea = sb.getCell(baddr);
|
||||
ea.ucs4 = ' ';
|
||||
ea.ec = (byte) 0x40;
|
||||
if (sb.isFormatted()) {
|
||||
int faAddr = sb.findFieldAttribute(baddr);
|
||||
if (faAddr >= 0) {
|
||||
sb.getCell(faAddr).fa = (byte) (sb.getCell(faAddr).fa | haus.nightmare.lib3270j.protocol.DS3270Constants.FA_MODIFY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.updateDisplaySnapshot();
|
||||
if (!haus.nightmare.j3270.config.Settings.getTrimRectRemainAfterEdit()) {
|
||||
clearSelection();
|
||||
}
|
||||
refreshScreen();
|
||||
}
|
||||
|
||||
public void pasteClipboard() {
|
||||
@@ -498,11 +874,18 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
int curCol = cols > 0 ? curPos % cols : 0;
|
||||
boolean excelPaste = haus.nightmare.j3270.config.Settings.getEnablePasteFromExcel();
|
||||
boolean stopAtProtected = haus.nightmare.j3270.config.Settings.getPasteStopAtProtectedLine();
|
||||
boolean blockPaste = haus.nightmare.j3270.config.Settings.getBlockPaste();
|
||||
boolean wordBreak = haus.nightmare.j3270.config.Settings.getPasteWordBreak();
|
||||
|
||||
if (client.getInputProcessor() != null && (excelPaste || stopAtProtected || text.contains("\t") || text.contains("\n") || text.contains("\r"))) {
|
||||
client.getInputProcessor().pasteText(text, excelPaste, stopAtProtected);
|
||||
if (client.getInputProcessor() != null && (excelPaste || stopAtProtected || blockPaste || wordBreak || text.contains("\t") || text.contains("\n") || text.contains("\r"))) {
|
||||
client.getInputProcessor().pasteText(text, excelPaste, stopAtProtected, blockPaste, wordBreak);
|
||||
} else if (client.getPS() != null) {
|
||||
client.getPS().setPasteWordBreak(wordBreak);
|
||||
if (blockPaste) {
|
||||
client.getPS().pasteRectangular(text, curRow, curCol);
|
||||
} else {
|
||||
client.getPS().pasteString(text, curRow, curCol);
|
||||
}
|
||||
} else {
|
||||
for (char ch : text.toCharArray()) {
|
||||
if (ch == '\n' || ch == '\r') {
|
||||
@@ -513,6 +896,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!haus.nightmare.j3270.config.Settings.getTrimRectRemainAfterEdit() && hasSelection()) {
|
||||
clearSelection();
|
||||
}
|
||||
refreshScreen();
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
@@ -550,6 +936,12 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
JPopupMenu popup = new JPopupMenu();
|
||||
ThemeManager.stylePopupMenu(popup);
|
||||
|
||||
JMenuItem cutItem = new JMenuItem("Cut");
|
||||
ThemeManager.styleMenuItem(cutItem);
|
||||
cutItem.setEnabled(hasSelection() && client != null && client.getConnectionState().isFullSession());
|
||||
cutItem.addActionListener(ev -> cutSelection());
|
||||
popup.add(cutItem);
|
||||
|
||||
JMenuItem copyItem = new JMenuItem("Copy");
|
||||
ThemeManager.styleMenuItem(copyItem);
|
||||
copyItem.setEnabled(hasSelection());
|
||||
@@ -577,6 +969,23 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
});
|
||||
popup.add(blockModeItem);
|
||||
|
||||
JCheckBoxMenuItem blockPasteItem = new JCheckBoxMenuItem("Block Paste", haus.nightmare.j3270.config.Settings.getBlockPaste());
|
||||
ThemeManager.styleMenuItem(blockPasteItem);
|
||||
blockPasteItem.addActionListener(ev -> {
|
||||
haus.nightmare.j3270.config.Settings.setBlockPaste(blockPasteItem.isSelected());
|
||||
applyModeSettings();
|
||||
});
|
||||
popup.add(blockPasteItem);
|
||||
|
||||
JCheckBoxMenuItem handlesItem = new JCheckBoxMenuItem("Trim Sizing Handles", trimRectSizingHandles);
|
||||
ThemeManager.styleMenuItem(handlesItem);
|
||||
handlesItem.addActionListener(ev -> {
|
||||
trimRectSizingHandles = handlesItem.isSelected();
|
||||
haus.nightmare.j3270.config.Settings.setTrimRectSizingHandles(trimRectSizingHandles);
|
||||
repaint();
|
||||
});
|
||||
popup.add(handlesItem);
|
||||
|
||||
popup.show(this, e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@@ -586,7 +995,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
int r1 = Math.min(selectionStartRow, selectionEndRow);
|
||||
int r2 = Math.max(selectionStartRow, selectionEndRow);
|
||||
|
||||
if (blockSelectMode) {
|
||||
if (blockSelectMode || trimRectSizingHandles) {
|
||||
int c1 = Math.min(selectionStartCol, selectionEndCol);
|
||||
int c2 = Math.max(selectionStartCol, selectionEndCol);
|
||||
return row >= r1 && row <= r2 && col >= c1 && col <= c2;
|
||||
@@ -688,9 +1097,11 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
bindKeyToMap(im, "APL", haus.nightmare.j3270.config.Settings.getKeyBinding("APL", "alt F3"));
|
||||
|
||||
// Copy/Paste/Lightpen bindings
|
||||
String defCut = haus.nightmare.j3270.config.Settings.isMac() ? "meta X, ctrl X" : "ctrl X, shift DELETE";
|
||||
String defCopy = haus.nightmare.j3270.config.Settings.getDefaultBinding("COPY");
|
||||
String defPaste = haus.nightmare.j3270.config.Settings.getDefaultBinding("PASTE");
|
||||
String defSelectAll = haus.nightmare.j3270.config.Settings.getDefaultBinding("SELECTALL");
|
||||
bindKeyToMap(im, "CUT", haus.nightmare.j3270.config.Settings.getKeyBinding("CUT", defCut));
|
||||
bindKeyToMap(im, "COPY", haus.nightmare.j3270.config.Settings.getKeyBinding("COPY", defCopy));
|
||||
bindKeyToMap(im, "PASTE", haus.nightmare.j3270.config.Settings.getKeyBinding("PASTE", defPaste));
|
||||
bindKeyToMap(im, "SELECTALL", haus.nightmare.j3270.config.Settings.getKeyBinding("SELECTALL", defSelectAll));
|
||||
@@ -727,6 +1138,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
am.put("j3270-SYSREQ", createAction(this::handleSysReq));
|
||||
am.put("j3270-CURSEL", createAction(this::handleCursorSelect));
|
||||
|
||||
am.put("j3270-CUT", createAction(this::cutSelection));
|
||||
am.put("j3270-COPY", createAction(this::copySelection));
|
||||
am.put("j3270-PASTE", createAction(this::pasteClipboard));
|
||||
am.put("j3270-SELECTALL", createAction(this::selectAll));
|
||||
@@ -1181,10 +1593,12 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
ip.setInsertOffOnAid(haus.nightmare.j3270.config.Settings.getInsertOffOnAid());
|
||||
ip.setNumericFieldLock(haus.nightmare.j3270.config.Settings.getNumericFieldLock());
|
||||
ip.setAutoSkipEnabled(haus.nightmare.j3270.config.Settings.getAutoSkipEnabled());
|
||||
ip.setBlockPaste(haus.nightmare.j3270.config.Settings.getBlockPaste());
|
||||
}
|
||||
if (client.getPS() != null) {
|
||||
client.getPS().setEnablePasteFromExcel(haus.nightmare.j3270.config.Settings.getEnablePasteFromExcel());
|
||||
client.getPS().setPasteStopAtProtectedLine(haus.nightmare.j3270.config.Settings.getPasteStopAtProtectedLine());
|
||||
client.getPS().setBlockPaste(haus.nightmare.j3270.config.Settings.getBlockPaste());
|
||||
}
|
||||
}
|
||||
if (statusBar != null) {
|
||||
@@ -1240,11 +1654,23 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
setupFont();
|
||||
applyModeSettings();
|
||||
blockSelectMode = haus.nightmare.j3270.config.Settings.getBlockSelectMode();
|
||||
trimRectSizingHandles = haus.nightmare.j3270.config.Settings.getTrimRectSizingHandles();
|
||||
crosshairRulerEnabled = haus.nightmare.j3270.config.Settings.getCrosshairRuler();
|
||||
inputMaskEnabled = haus.nightmare.j3270.config.Settings.getInputMask();
|
||||
inputMaskChar = haus.nightmare.j3270.config.Settings.getInputMaskChar();
|
||||
String cStyle = haus.nightmare.j3270.config.Settings.getCursorStyle();
|
||||
cursorStyle = "UNDERLINE".equalsIgnoreCase(cStyle) ? CursorStyle.UNDERLINE : CursorStyle.BLOCK;
|
||||
|
||||
// Phase 25: Reload Hotspots and Mouse Wheel preferences
|
||||
hotspotsEnabled = haus.nightmare.j3270.config.Settings.getHotspotsEnabled();
|
||||
hotspotEngine.setUrlEnabled(haus.nightmare.j3270.config.Settings.getHotspotsUrlEnabled());
|
||||
hotspotEngine.setPfKeyEnabled(haus.nightmare.j3270.config.Settings.getHotspotsPfEnabled());
|
||||
hotspotEngine.setMenuChoiceEnabled(haus.nightmare.j3270.config.Settings.getHotspotsMenuChoicesEnabled());
|
||||
hotspotEngine.invalidateCache();
|
||||
mouseWheelEnabled = haus.nightmare.j3270.config.Settings.getMouseWheelEnabled();
|
||||
mouseWheelUpAction = haus.nightmare.j3270.config.Settings.getMouseWheelUpKey();
|
||||
mouseWheelDownAction = haus.nightmare.j3270.config.Settings.getMouseWheelDownKey();
|
||||
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
@@ -1294,7 +1720,41 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
blinkTimer = new Timer(500, e -> {
|
||||
cursorVisible = !cursorVisible;
|
||||
textBlinkVisible = !textBlinkVisible;
|
||||
|
||||
// Check if there are any blinking cells or fields on the screen
|
||||
boolean hasBlinkingCells = false;
|
||||
ScreenBuffer sb = (client != null) ? client.getScreenBuffer() : null;
|
||||
if (sb != null) {
|
||||
int total = sb.getDisplayRows() * sb.getDisplayCols();
|
||||
byte curFaGr = 0;
|
||||
for (int i = 0; i < total; i++) {
|
||||
ExtendedAttribute c = sb.getDisplayCell(i);
|
||||
if (c.isFieldAttribute()) {
|
||||
curFaGr = c.gr;
|
||||
}
|
||||
if ((c.gr & ExtendedAttribute.GR_BLINK) != 0 || (curFaGr & ExtendedAttribute.GR_BLINK) != 0) {
|
||||
hasBlinkingCells = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasBlinkingCells) {
|
||||
repaint();
|
||||
} else if (sb != null && client != null && client.getConnectionState() != null && client.getConnectionState().isFullSession()) {
|
||||
// Only repaint the cursor cell bounding rect
|
||||
int cols = sb.getDisplayCols();
|
||||
int ox = getRenderOffsetX();
|
||||
int oy = getRenderOffsetY();
|
||||
int curAddr = sb.getDisplayCursorAddress();
|
||||
int curRow = curAddr / cols;
|
||||
int curCol = curAddr % cols;
|
||||
int cx = ox + curCol * cellWidth;
|
||||
int cy = oy + curRow * cellHeight;
|
||||
repaint(cx, cy, cellWidth, cellHeight);
|
||||
} else {
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
blinkTimer.start();
|
||||
}
|
||||
@@ -1560,6 +2020,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
|
||||
// Field Outlining (0xC2 / TY_FOUTLINE)
|
||||
drawOutlining(g2, ea, currentFieldEa, baddr, row, col, x, y, cellWidth, cellHeight, rows, cols, sb, fgColor);
|
||||
|
||||
if (isCellSelected(row, col)) {
|
||||
g2.setColor(SELECTION_COLOR);
|
||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
||||
@@ -1579,7 +2042,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
|
||||
// Check text blink (500ms cycle)
|
||||
boolean blink = (gr & GR_BLINK) != 0;
|
||||
boolean blink = (gr & GR_BLINK) != 0 || (currentFieldEa != null && (currentFieldEa.gr & GR_BLINK) != 0);
|
||||
boolean suppressGlyph = blink && !textBlinkVisible;
|
||||
|
||||
// Draw character or Programmed Symbol
|
||||
@@ -1613,12 +2076,15 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
|
||||
if (underline) {
|
||||
if (underline && !suppressGlyph) {
|
||||
g2.setColor(fgColor);
|
||||
int ulY = Math.min(y + cellHeight - 1, y + fontAscent + Math.max(0, (cellHeight - (fontAscent + fontDescent)) / 2) + 2);
|
||||
g2.drawLine(x, ulY, x + cellWidth - 1, ulY);
|
||||
}
|
||||
|
||||
// Field Outlining (0xC2 / TY_FOUTLINE)
|
||||
drawOutlining(g2, ea, currentFieldEa, baddr, row, col, x, y, cellWidth, cellHeight, rows, cols, sb, fgColor);
|
||||
|
||||
// Draw search highlight
|
||||
if (isCellSearchHighlighted(row, col)) {
|
||||
g2.setColor(SEARCH_HIGHLIGHT_COLOR);
|
||||
@@ -1633,6 +2099,63 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 26.1: Rectangular Block Selection Sizing Handles & Box Outline
|
||||
if (trimRectSizingHandles && hasSelection() && client != null && client.getConnectionState().isFullSession()) {
|
||||
int r1 = Math.min(selectionStartRow, selectionEndRow);
|
||||
int r2 = Math.max(selectionStartRow, selectionEndRow);
|
||||
int c1 = Math.min(selectionStartCol, selectionEndCol);
|
||||
int c2 = Math.max(selectionStartCol, selectionEndCol);
|
||||
|
||||
int x1 = ox + c1 * cellWidth;
|
||||
int y1 = oy + r1 * cellHeight;
|
||||
int x2 = ox + (c2 + 1) * cellWidth;
|
||||
int y2 = oy + (r2 + 1) * cellHeight;
|
||||
int w = x2 - x1;
|
||||
int h = y2 - y1;
|
||||
int xm = (x1 + x2) / 2;
|
||||
int ym = (y1 + y2) / 2;
|
||||
|
||||
Color oldColor = g2.getColor();
|
||||
Stroke oldStroke = g2.getStroke();
|
||||
|
||||
g2.setColor(Color.YELLOW);
|
||||
g2.drawRect(x1, y1, w, h);
|
||||
|
||||
drawSizingHandle(g2, x1, y1);
|
||||
drawSizingHandle(g2, xm, y1);
|
||||
drawSizingHandle(g2, x2, y1);
|
||||
drawSizingHandle(g2, x2, ym);
|
||||
drawSizingHandle(g2, x2, y2);
|
||||
drawSizingHandle(g2, xm, y2);
|
||||
drawSizingHandle(g2, x1, y2);
|
||||
drawSizingHandle(g2, x1, ym);
|
||||
|
||||
g2.setStroke(oldStroke);
|
||||
g2.setColor(oldColor);
|
||||
}
|
||||
|
||||
// Phase 25.2: Draw visual underline on hovered hotspot
|
||||
if (hotspotsEnabled && hoveredHotspot != null) {
|
||||
Color oldColor = g2.getColor();
|
||||
Stroke oldStroke = g2.getStroke();
|
||||
g2.setColor(new Color(0, 220, 255)); // Bright cyan hotspot underline
|
||||
g2.setStroke(new BasicStroke(2.0f));
|
||||
|
||||
int hr0 = hoveredHotspot.getStartRow();
|
||||
int hr1 = hoveredHotspot.getEndRow();
|
||||
for (int r = hr0; r <= hr1; r++) {
|
||||
int c0 = (r == hr0) ? hoveredHotspot.getStartCol() : 0;
|
||||
int c1 = (r == hr1) ? hoveredHotspot.getEndCol() : cols - 1;
|
||||
int x1 = ox + c0 * cellWidth;
|
||||
int x2 = ox + (c1 + 1) * cellWidth;
|
||||
int ulY = oy + r * cellHeight + fontAscent + Math.max(0, (cellHeight - (fontAscent + fontDescent)) / 2) + 2;
|
||||
ulY = Math.min(oy + (r + 1) * cellHeight - 1, ulY);
|
||||
g2.drawLine(x1, ulY, x2 - 1, ulY);
|
||||
}
|
||||
g2.setStroke(oldStroke);
|
||||
g2.setColor(oldColor);
|
||||
}
|
||||
|
||||
// Draw Graphic Cursor if active
|
||||
boolean isGraphicCursor = (client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive())
|
||||
|| (client.getGraphicsPlane() != null && client.getGraphicsPlane().isGraphicCursorAttached());
|
||||
@@ -1786,6 +2309,64 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
return faIsHigh(fa & 0xFF) ? monoIntensify : monoNormal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render field outline lines around cells based on ea.ol or field attribute outline.
|
||||
* Conforms to IBM 3270 Extended Data Stream / Host On-Demand DS3270 TY_FOUTLINE (0xC2).
|
||||
*
|
||||
* Bit 0 (0x01): Underline -> draw line at y + cellHeight - 1
|
||||
* Bit 1 (0x02): Right vertical line -> draw line at x + cellWidth - 1
|
||||
* Bit 2 (0x04): Overline -> draw line at y
|
||||
* Bit 3 (0x08): Left vertical line -> draw line at x
|
||||
*/
|
||||
private void drawOutlining(Graphics2D g2, ExtendedAttribute ea, ExtendedAttribute currentFieldEa,
|
||||
int baddr, int row, int col, int x, int y,
|
||||
int cellWidth, int cellHeight, int rows, int cols,
|
||||
ScreenBuffer sb, Color fgColor) {
|
||||
int outline = ea.ol & 0xFF;
|
||||
if (outline == 0 && currentFieldEa != null && currentFieldEa.ol != 0) {
|
||||
int fol = currentFieldEa.ol & 0xFF;
|
||||
if ((fol & ExtendedAttribute.OL_UNDERLINE) != 0) outline |= ExtendedAttribute.OL_UNDERLINE;
|
||||
if ((fol & ExtendedAttribute.OL_OVERLINE) != 0) outline |= ExtendedAttribute.OL_OVERLINE;
|
||||
|
||||
int size = rows * cols;
|
||||
int prevAddr = (baddr - 1 + size) % size;
|
||||
if (sb.getDisplayCell(prevAddr).isFieldAttribute() || col == 0) {
|
||||
if ((fol & ExtendedAttribute.OL_LEFT) != 0) outline |= ExtendedAttribute.OL_LEFT;
|
||||
}
|
||||
int nextAddr = (baddr + 1) % size;
|
||||
if (sb.getDisplayCell(nextAddr).isFieldAttribute() || col == cols - 1) {
|
||||
if ((fol & ExtendedAttribute.OL_RIGHT) != 0) outline |= ExtendedAttribute.OL_RIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
if (outline != 0) {
|
||||
g2.setColor(fgColor);
|
||||
// Bit 0 (0x01): Underline -> draw line at y + cellHeight - 1
|
||||
if ((outline & ExtendedAttribute.OL_UNDERLINE) != 0) {
|
||||
g2.drawLine(x, y + cellHeight - 1, x + cellWidth - 1, y + cellHeight - 1);
|
||||
}
|
||||
// Bit 1 (0x02): Right vertical line -> draw line at x + cellWidth - 1
|
||||
if ((outline & ExtendedAttribute.OL_RIGHT) != 0) {
|
||||
g2.drawLine(x + cellWidth - 1, y, x + cellWidth - 1, y + cellHeight - 1);
|
||||
}
|
||||
// Bit 2 (0x04): Overline -> draw line at y
|
||||
if ((outline & ExtendedAttribute.OL_OVERLINE) != 0) {
|
||||
g2.drawLine(x, y, x + cellWidth - 1, y);
|
||||
}
|
||||
// Bit 3 (0x08): Left vertical line -> draw line at x
|
||||
if ((outline & ExtendedAttribute.OL_LEFT) != 0) {
|
||||
g2.drawLine(x, y, x, y + cellHeight - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drawSizingHandle(Graphics2D g2, int cx, int cy) {
|
||||
g2.setColor(Color.YELLOW);
|
||||
g2.fillRect(cx - 2, cy - 2, 5, 5);
|
||||
g2.setColor(Color.BLACK);
|
||||
g2.drawRect(cx - 2, cy - 2, 5, 5);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (blinkTimer != null)
|
||||
blinkTimer.stop();
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import haus.nightmare.j3270.config.Settings;
|
||||
|
||||
/**
|
||||
* Centralized theme manager for j3270 Java desktop UI.
|
||||
@@ -33,6 +34,25 @@ public final class ThemeManager {
|
||||
|
||||
private ThemeManager() {}
|
||||
|
||||
public static Font getUiFont() {
|
||||
return getUiFont(Font.PLAIN, 0);
|
||||
}
|
||||
|
||||
public static Font getUiFont(int style, int sizeDelta) {
|
||||
String family = Settings.getUiFontFamily();
|
||||
int size = Math.max(8, Settings.getUiFontSize() + sizeDelta);
|
||||
return new Font(family, style, size);
|
||||
}
|
||||
|
||||
public static Font getMonospacedUiFont() {
|
||||
return getMonospacedUiFont(Font.PLAIN, 0);
|
||||
}
|
||||
|
||||
public static Font getMonospacedUiFont(int style, int sizeDelta) {
|
||||
int size = Math.max(8, Settings.getUiFontSize() + sizeDelta);
|
||||
return new Font(Font.MONOSPACED, style, size);
|
||||
}
|
||||
|
||||
public static UITheme getTheme() {
|
||||
return currentTheme;
|
||||
}
|
||||
@@ -433,6 +453,47 @@ public final class ThemeManager {
|
||||
UIManager.put("TitledBorder.titleColor", fgMain);
|
||||
UIManager.put("OptionPane.background", bgPanel);
|
||||
UIManager.put("OptionPane.messageForeground", fgMain);
|
||||
|
||||
// UI Font Defaults
|
||||
Font uiFont = getUiFont();
|
||||
Font uiFontBold = getUiFont(Font.BOLD, 0);
|
||||
Font monoUiFont = getMonospacedUiFont();
|
||||
|
||||
UIManager.put("Button.font", uiFont);
|
||||
UIManager.put("ToggleButton.font", uiFont);
|
||||
UIManager.put("RadioButton.font", uiFont);
|
||||
UIManager.put("CheckBox.font", uiFont);
|
||||
UIManager.put("ColorChooser.font", uiFont);
|
||||
UIManager.put("ComboBox.font", uiFont);
|
||||
UIManager.put("Label.font", uiFont);
|
||||
UIManager.put("List.font", uiFont);
|
||||
UIManager.put("MenuBar.font", uiFont);
|
||||
UIManager.put("MenuItem.font", uiFont);
|
||||
UIManager.put("RadioButtonMenuItem.font", uiFont);
|
||||
UIManager.put("CheckBoxMenuItem.font", uiFont);
|
||||
UIManager.put("Menu.font", uiFont);
|
||||
UIManager.put("PopupMenu.font", uiFont);
|
||||
UIManager.put("OptionPane.font", uiFont);
|
||||
UIManager.put("OptionPane.messageFont", uiFont);
|
||||
UIManager.put("OptionPane.buttonFont", uiFont);
|
||||
UIManager.put("Panel.font", uiFont);
|
||||
UIManager.put("ProgressBar.font", uiFont);
|
||||
UIManager.put("ScrollPane.font", uiFont);
|
||||
UIManager.put("Viewport.font", uiFont);
|
||||
UIManager.put("TabbedPane.font", uiFont);
|
||||
UIManager.put("Table.font", uiFont);
|
||||
UIManager.put("TableHeader.font", uiFontBold);
|
||||
UIManager.put("TextField.font", uiFont);
|
||||
UIManager.put("PasswordField.font", uiFont);
|
||||
UIManager.put("TextArea.font", monoUiFont);
|
||||
UIManager.put("TextPane.font", uiFont);
|
||||
UIManager.put("EditorPane.font", uiFont);
|
||||
UIManager.put("TitledBorder.font", uiFontBold);
|
||||
UIManager.put("ToolBar.font", uiFont);
|
||||
UIManager.put("ToolTip.font", uiFont);
|
||||
UIManager.put("Tree.font", uiFont);
|
||||
UIManager.put("Spinner.font", uiFont);
|
||||
UIManager.put("FormattedTextField.font", uiFont);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
@@ -446,6 +507,7 @@ public final class ThemeManager {
|
||||
public static JButton styleButton(JButton button, ButtonVariant variant) {
|
||||
if (button == null) return null;
|
||||
button.setUI(new StyledButtonUI(variant));
|
||||
button.setFont(getUiFont());
|
||||
button.setFocusPainted(false);
|
||||
button.setOpaque(false);
|
||||
button.setContentAreaFilled(false);
|
||||
@@ -465,6 +527,7 @@ public final class ThemeManager {
|
||||
|
||||
public static JTextField styleTextField(JTextField field) {
|
||||
if (field == null) return null;
|
||||
field.setFont(getUiFont());
|
||||
field.setBackground(getBgComponent());
|
||||
field.setForeground(getFgMain());
|
||||
field.setCaretColor(getFgMain());
|
||||
@@ -478,6 +541,7 @@ public final class ThemeManager {
|
||||
|
||||
public static JTextArea styleTextArea(JTextArea area) {
|
||||
if (area == null) return null;
|
||||
area.setFont(getMonospacedUiFont());
|
||||
area.setBackground(getCodeAreaBg());
|
||||
area.setForeground(getCodeAreaFg());
|
||||
area.setCaretColor(getFgMain());
|
||||
@@ -488,6 +552,7 @@ public final class ThemeManager {
|
||||
|
||||
public static JComboBox<?> styleComboBox(JComboBox<?> box) {
|
||||
if (box == null) return null;
|
||||
box.setFont(getUiFont());
|
||||
box.setBackground(getBgComponent());
|
||||
box.setForeground(getFgMain());
|
||||
box.setRenderer(new DefaultListCellRenderer() {
|
||||
@@ -510,12 +575,14 @@ public final class ThemeManager {
|
||||
|
||||
public static JTable styleTable(JTable table) {
|
||||
if (table == null) return null;
|
||||
table.setFont(getUiFont());
|
||||
table.setBackground(getBgComponent());
|
||||
table.setForeground(getFgMain());
|
||||
table.setSelectionBackground(getSelectionBg());
|
||||
table.setSelectionForeground(getSelectionFg());
|
||||
table.setGridColor(getTableGrid());
|
||||
table.setRowHeight(24);
|
||||
int rowHeight = Math.max(24, getUiFont().getSize() + 10);
|
||||
table.setRowHeight(rowHeight);
|
||||
|
||||
table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
|
||||
@Override
|
||||
@@ -537,14 +604,14 @@ public final class ThemeManager {
|
||||
if (header != null) {
|
||||
header.setBackground(getTableHeaderBg());
|
||||
header.setForeground(getTableHeaderFg());
|
||||
header.setFont(header.getFont().deriveFont(Font.BOLD));
|
||||
header.setFont(getUiFont(Font.BOLD, 0));
|
||||
header.setDefaultRenderer(new DefaultTableCellRenderer() {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable tbl, Object val, boolean isSel, boolean hasFocus, int row, int col) {
|
||||
super.getTableCellRendererComponent(tbl, val, isSel, hasFocus, row, col);
|
||||
setBackground(getTableHeaderBg());
|
||||
setForeground(getTableHeaderFg());
|
||||
setFont(getFont().deriveFont(Font.BOLD));
|
||||
setFont(getUiFont(Font.BOLD, 0));
|
||||
setBorder(BorderFactory.createCompoundBorder(
|
||||
BorderFactory.createMatteBorder(0, 0, 1, 1, getBorderColor()),
|
||||
new EmptyBorder(4, 6, 4, 6)));
|
||||
@@ -558,6 +625,7 @@ public final class ThemeManager {
|
||||
public static JTabbedPane styleTabbedPane(JTabbedPane tp) {
|
||||
if (tp == null) return null;
|
||||
tp.setUI(new StyledTabbedPaneUI());
|
||||
tp.setFont(getUiFont());
|
||||
tp.setBackground(getBgMain());
|
||||
tp.setForeground(getFgMain());
|
||||
return tp;
|
||||
@@ -566,6 +634,7 @@ public final class ThemeManager {
|
||||
public static JMenuBar styleMenuBar(JMenuBar bar) {
|
||||
if (bar == null) return null;
|
||||
bar.setUI(new StyledMenuBarUI());
|
||||
bar.setFont(getUiFont());
|
||||
bar.setBackground(getMenuBarBg());
|
||||
bar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, getBorder()));
|
||||
return bar;
|
||||
@@ -574,6 +643,7 @@ public final class ThemeManager {
|
||||
public static JMenu styleMenu(JMenu menu) {
|
||||
if (menu == null) return null;
|
||||
menu.setUI(new StyledMenuUI());
|
||||
menu.setFont(getUiFont());
|
||||
menu.setForeground(getMenuBarFg());
|
||||
menu.setBackground(getMenuBarBg());
|
||||
menu.setOpaque(false);
|
||||
@@ -583,6 +653,7 @@ public final class ThemeManager {
|
||||
public static JMenuItem styleMenuItem(JMenuItem item) {
|
||||
if (item == null) return null;
|
||||
item.setUI(new StyledMenuItemUI());
|
||||
item.setFont(getUiFont());
|
||||
item.setBackground(getMenuPopupBg());
|
||||
item.setForeground(getMenuItemFg());
|
||||
return item;
|
||||
@@ -591,6 +662,7 @@ public final class ThemeManager {
|
||||
public static JPopupMenu stylePopupMenu(JPopupMenu popup) {
|
||||
if (popup == null) return null;
|
||||
popup.setUI(new StyledPopupMenuUI());
|
||||
popup.setFont(getUiFont());
|
||||
popup.setBackground(getMenuPopupBg());
|
||||
popup.setBorder(BorderFactory.createCompoundBorder(
|
||||
new LineBorder(getBorder(), 1),
|
||||
@@ -610,12 +682,14 @@ public final class ThemeManager {
|
||||
|
||||
public static JSpinner styleSpinner(JSpinner sp) {
|
||||
if (sp == null) return null;
|
||||
sp.setFont(getUiFont());
|
||||
sp.setBackground(getBgComponent());
|
||||
sp.setForeground(getFgMain());
|
||||
sp.setBorder(new LineBorder(getBorder(), 1));
|
||||
JComponent editor = sp.getEditor();
|
||||
if (editor instanceof JSpinner.DefaultEditor) {
|
||||
JTextField tf = ((JSpinner.DefaultEditor) editor).getTextField();
|
||||
tf.setFont(getUiFont());
|
||||
tf.setBackground(getBgComponent());
|
||||
tf.setForeground(getFgMain());
|
||||
tf.setCaretColor(getFgMain());
|
||||
@@ -626,6 +700,7 @@ public final class ThemeManager {
|
||||
|
||||
public static JCheckBox styleCheckBox(JCheckBox cb) {
|
||||
if (cb == null) return null;
|
||||
cb.setFont(getUiFont());
|
||||
cb.setOpaque(false);
|
||||
cb.setForeground(getFgMain());
|
||||
cb.setFocusPainted(false);
|
||||
@@ -634,6 +709,7 @@ public final class ThemeManager {
|
||||
|
||||
public static JRadioButton styleRadioButton(JRadioButton rb) {
|
||||
if (rb == null) return null;
|
||||
rb.setFont(getUiFont());
|
||||
rb.setOpaque(false);
|
||||
rb.setForeground(getFgMain());
|
||||
rb.setFocusPainted(false);
|
||||
@@ -646,7 +722,7 @@ public final class ThemeManager {
|
||||
title,
|
||||
TitledBorder.DEFAULT_JUSTIFICATION,
|
||||
TitledBorder.DEFAULT_POSITION,
|
||||
new Font(Font.SANS_SERIF, Font.BOLD, 12),
|
||||
getUiFont(Font.BOLD, 0),
|
||||
getFgMain());
|
||||
}
|
||||
|
||||
@@ -732,6 +808,7 @@ public final class ThemeManager {
|
||||
|
||||
if (comp instanceof JButton) {
|
||||
JButton btn = (JButton) comp;
|
||||
btn.setFont(getUiFont());
|
||||
if (!(btn.getUI() instanceof StyledButtonUI)) {
|
||||
styleButton(btn, ButtonVariant.DEFAULT);
|
||||
}
|
||||
@@ -770,6 +847,21 @@ public final class ThemeManager {
|
||||
|
||||
if (comp instanceof JLabel) {
|
||||
comp.setForeground(getFgMain(theme));
|
||||
JLabel label = (JLabel) comp;
|
||||
Font cur = label.getFont();
|
||||
int style = (cur != null && cur.isBold()) ? Font.BOLD : Font.PLAIN;
|
||||
Object deltaProp = label.getClientProperty("uiFontSizeDelta");
|
||||
int delta = 0;
|
||||
if (deltaProp instanceof Integer) {
|
||||
delta = (Integer) deltaProp;
|
||||
} else {
|
||||
int baseSize = Settings.getUiFontSize();
|
||||
if (cur != null && cur.getSize() > baseSize) {
|
||||
delta = cur.getSize() - baseSize;
|
||||
}
|
||||
label.putClientProperty("uiFontSizeDelta", delta);
|
||||
}
|
||||
label.setFont(getUiFont(style, delta));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -783,6 +875,7 @@ public final class ThemeManager {
|
||||
if (b instanceof TitledBorder) {
|
||||
TitledBorder tb = (TitledBorder) b;
|
||||
tb.setTitleColor(getFgMain(theme));
|
||||
tb.setTitleFont(getUiFont(Font.BOLD, 0));
|
||||
tb.setBorder(new LineBorder(getBorder(theme), 1));
|
||||
}
|
||||
} else if (comp instanceof Container) {
|
||||
|
||||
@@ -17,6 +17,7 @@ public class UntrustedCertificateDialog extends JDialog {
|
||||
|
||||
public UntrustedCertificateDialog(Frame parent, String host, int port, X509Certificate[] chain, CertificateException exception) {
|
||||
super(parent, "Untrusted SSL/TLS Certificate", true);
|
||||
AppIcon.applyTo(this);
|
||||
buildUI(host, port, chain, exception);
|
||||
pack();
|
||||
setLocationRelativeTo(parent);
|
||||
@@ -31,18 +32,20 @@ public class UntrustedCertificateDialog extends JDialog {
|
||||
JPanel headerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0));
|
||||
headerPanel.setOpaque(false);
|
||||
JLabel iconLabel = new JLabel("⚠️");
|
||||
iconLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 28));
|
||||
iconLabel.putClientProperty("uiFontSizeDelta", 15);
|
||||
iconLabel.setFont(ThemeManager.getUiFont(Font.PLAIN, 15));
|
||||
headerPanel.add(iconLabel);
|
||||
|
||||
JPanel titleBox = new JPanel(new GridLayout(2, 1, 0, 2));
|
||||
titleBox.setOpaque(false);
|
||||
JLabel titleLabel = new JLabel("Untrusted SSL Certificate");
|
||||
titleLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 16));
|
||||
titleLabel.putClientProperty("uiFontSizeDelta", 3);
|
||||
titleLabel.setFont(ThemeManager.getUiFont(Font.BOLD, 3));
|
||||
titleLabel.setForeground(ThemeManager.getOiaFgWarn());
|
||||
titleBox.add(titleLabel);
|
||||
|
||||
JLabel subtitleLabel = new JLabel("The server certificate for " + host + ":" + port + " could not be verified.");
|
||||
subtitleLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||
subtitleLabel.setFont(ThemeManager.getUiFont());
|
||||
subtitleLabel.setForeground(ThemeManager.getFgMuted());
|
||||
titleBox.add(subtitleLabel);
|
||||
headerPanel.add(titleBox);
|
||||
@@ -69,7 +72,7 @@ public class UntrustedCertificateDialog extends JDialog {
|
||||
|
||||
JTextArea detailsArea = new JTextArea(sb.toString());
|
||||
detailsArea.setEditable(false);
|
||||
detailsArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||
detailsArea.setFont(ThemeManager.getMonospacedUiFont());
|
||||
ThemeManager.styleTextArea(detailsArea);
|
||||
detailsArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
|
||||
@@ -91,7 +94,7 @@ public class UntrustedCertificateDialog extends JDialog {
|
||||
|
||||
JButton trustBtn = new JButton("Connect Anyway");
|
||||
ThemeManager.styleButton(trustBtn, ThemeManager.ButtonVariant.DANGER);
|
||||
trustBtn.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 13));
|
||||
trustBtn.setFont(ThemeManager.getUiFont(Font.BOLD, 0));
|
||||
trustBtn.addActionListener(e -> {
|
||||
accepted = true;
|
||||
dispose();
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="124 738 320 320"
|
||||
width="320"
|
||||
height="320"
|
||||
xml:space="preserve"
|
||||
id="svg_j3270_icon"
|
||||
version="1.1">
|
||||
<metadata id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<!-- Pure black background (#000000) for classic 3270 CRT terminal look -->
|
||||
<rect x="124" y="738" width="320" height="320" fill="#000000" />
|
||||
<g transform="matrix(1.3333333,0,0,-1.3333333,0,1122.6667)" id="g10">
|
||||
<g transform="scale(0.1)" id="g12">
|
||||
<g id="g14">
|
||||
<!-- Green Terminal Screen Frame -->
|
||||
<path
|
||||
id="path28"
|
||||
style="fill:none;stroke:#00ff00;stroke-width:3.75079012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 1177.27,2220.71 V 1142.45 H 3084.31 V 2220.71 H 1177.27" />
|
||||
<!-- Blue Wave / Exponential Curve -->
|
||||
<path
|
||||
id="path30"
|
||||
style="fill:none;stroke:#0000ff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1172.41,1137.58 79.1,-1.22 -0.21,-0.38 c 1.08,0 2.18,0 3.27,0 762.95,0 1464.86,417.88 1829.53,1089.21" />
|
||||
<!-- Red Circular Arc -->
|
||||
<path
|
||||
id="path32"
|
||||
style="fill:none;stroke:#ff0000;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2593.86,1620.73 -1.22,-28 -0.08,-0.08 c 0.43,-250.15 202.81,-452.72 452.3,-452.72 0.14,0 0.28,0 0.42,0 l 43.9,2.52" />
|
||||
<!-- Yellow Smiley Head -->
|
||||
<path
|
||||
id="path34"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2976,1363.94 v 0.08 c 44.73,34.29 71.21,87.25 71.8,143.61 l 0.01,-0.15 c -0.06,97.48 -79.11,176.47 -176.59,176.47 -0.36,0 -0.73,0 -1.09,0 l -0.07,0.03 c -0.25,0 -0.51,0 -0.75,0 -37.38,0 -73.79,-11.94 -103.92,-34.08 l 0.02,-0.03 c -44.38,-34.04 -70.82,-86.47 -71.8,-142.39 l 0.06,0.04 c 0.54,-97.43 79.04,-176.47 176.46,-177.69 l 0.08,0.02 c 0.64,-0.01 1.29,-0.01 1.92,-0.01 37.4,0 73.82,11.94 103.96,34.08" />
|
||||
<!-- 'l' -->
|
||||
<path
|
||||
id="path36"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
|
||||
<path
|
||||
id="path38"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
|
||||
<!-- 'i' -->
|
||||
<path
|
||||
id="path40"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
|
||||
<path
|
||||
id="path42"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
|
||||
<!-- 'b' -->
|
||||
<path
|
||||
id="path44"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
|
||||
<path
|
||||
id="path46"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
|
||||
<!-- '3' -->
|
||||
<path
|
||||
id="path48"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
|
||||
<path
|
||||
id="path50"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
|
||||
<!-- '2' -->
|
||||
<path
|
||||
id="path52"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
|
||||
<path
|
||||
id="path54"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
|
||||
<!-- '7' -->
|
||||
<path
|
||||
id="path56"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
|
||||
<path
|
||||
id="path58"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
|
||||
<!-- '0' -->
|
||||
<path
|
||||
id="path60"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
|
||||
<path
|
||||
id="path62"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
|
||||
<!-- 'j' -->
|
||||
<path
|
||||
id="path64"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
|
||||
<path
|
||||
id="path66"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
|
||||
<!-- Smile -->
|
||||
<path
|
||||
id="path68"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2778.85,1478.34 0.04,-0.02 c 21.03,-31.91 56.69,-51.12 94.91,-51.12 0.41,0 0.83,0 1.24,0.01 l -0.02,0.07 c 43.88,0.19 84.11,24.51 104.66,63.28" />
|
||||
<!-- Left Eye (cyan) -->
|
||||
<path
|
||||
id="path70"
|
||||
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2820.22,1595.17 0.01,0.01 c -14.62,-1.91 -25.56,-14.37 -25.56,-29.11 0,-0.03 0,-0.07 0,-0.1 l -0.02,-0.01 c 0,-0.03 0,-0.07 0,-0.1 0,-16.09 13.04,-29.14 29.13,-29.14 0.43,0 0.87,0.01 1.29,0.03 h 3.67 0.01 c 14.61,1.91 25.55,14.37 25.55,29.11 0,0.03 0,0.07 0,0.1 l 0.02,0.01 c 0,0.04 0,0.08 0,0.12 0,16.07 -13.03,29.09 -29.09,29.09 -0.03,0 -0.08,0 -0.12,0 l -4.89,-0.01" />
|
||||
<!-- Right Eye (cyan) -->
|
||||
<path
|
||||
id="path72"
|
||||
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2921.23,1569.61 v -3.65 l -0.01,-0.01 c 0,-0.27 -0.01,-0.54 -0.01,-0.81 0,-18.22 14.66,-33.05 32.87,-33.26 l 0.03,0.01 c 0.28,0 0.57,-0.01 0.84,-0.01 17.29,0 31.72,13.22 33.24,30.44 l -0.02,3.64 0.02,0.02 c 0,0.27 0.01,0.54 0.01,0.81 0,18.37 -14.9,33.28 -33.28,33.28 -0.26,0 -0.54,-0.01 -0.81,-0.02 l -0.02,-0.03 c -17.14,-0.18 -31.36,-13.34 -32.86,-30.42" />
|
||||
<!-- Nose -->
|
||||
<path
|
||||
id="path74"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2892.03,1528.23 -29.21,-20.68" />
|
||||
<path
|
||||
id="path76"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2862.82,1507.55 h 32.86" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="144 814 280 169"
|
||||
width="280"
|
||||
height="169"
|
||||
xml:space="preserve"
|
||||
id="svg_lib3270j_cropped"
|
||||
version="1.1">
|
||||
<metadata id="metadata8">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<!-- Pure black background (#000000) for classic 3270 CRT terminal look -->
|
||||
<rect x="144" y="814" width="280" height="169" fill="#000000" />
|
||||
<g transform="matrix(1.3333333,0,0,-1.3333333,0,1122.6667)" id="g10">
|
||||
<g transform="scale(0.1)" id="g12">
|
||||
<g id="g14">
|
||||
<!-- Green Terminal Screen Frame -->
|
||||
<path
|
||||
id="path28"
|
||||
style="fill:none;stroke:#00ff00;stroke-width:3.75079012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 1177.27,2220.71 V 1142.45 H 3084.31 V 2220.71 H 1177.27" />
|
||||
<!-- Blue Wave / Exponential Curve -->
|
||||
<path
|
||||
id="path30"
|
||||
style="fill:none;stroke:#0000ff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1172.41,1137.58 79.1,-1.22 -0.21,-0.38 c 1.08,0 2.18,0 3.27,0 762.95,0 1464.86,417.88 1829.53,1089.21" />
|
||||
<!-- Red Circular Arc -->
|
||||
<path
|
||||
id="path32"
|
||||
style="fill:none;stroke:#ff0000;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2593.86,1620.73 -1.22,-28 -0.08,-0.08 c 0.43,-250.15 202.81,-452.72 452.3,-452.72 0.14,0 0.28,0 0.42,0 l 43.9,2.52" />
|
||||
<!-- Yellow Smiley Head -->
|
||||
<path
|
||||
id="path34"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2976,1363.94 v 0.08 c 44.73,34.29 71.21,87.25 71.8,143.61 l 0.01,-0.15 c -0.06,97.48 -79.11,176.47 -176.59,176.47 -0.36,0 -0.73,0 -1.09,0 l -0.07,0.03 c -0.25,0 -0.51,0 -0.75,0 -37.38,0 -73.79,-11.94 -103.92,-34.08 l 0.02,-0.03 c -44.38,-34.04 -70.82,-86.47 -71.8,-142.39 l 0.06,0.04 c 0.54,-97.43 79.04,-176.47 176.46,-177.69 l 0.08,0.02 c 0.64,-0.01 1.29,-0.01 1.92,-0.01 37.4,0 73.82,11.94 103.96,34.08" />
|
||||
<!-- 'l' -->
|
||||
<path
|
||||
id="path36"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
|
||||
<path
|
||||
id="path38"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
|
||||
<!-- 'i' -->
|
||||
<path
|
||||
id="path40"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
|
||||
<path
|
||||
id="path42"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
|
||||
<!-- 'b' -->
|
||||
<path
|
||||
id="path44"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
|
||||
<path
|
||||
id="path46"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
|
||||
<!-- '3' -->
|
||||
<path
|
||||
id="path48"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
|
||||
<path
|
||||
id="path50"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
|
||||
<!-- '2' -->
|
||||
<path
|
||||
id="path52"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
|
||||
<path
|
||||
id="path54"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
|
||||
<!-- '7' -->
|
||||
<path
|
||||
id="path56"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
|
||||
<path
|
||||
id="path58"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
|
||||
<!-- '0' -->
|
||||
<path
|
||||
id="path60"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
|
||||
<path
|
||||
id="path62"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
|
||||
<!-- 'j' -->
|
||||
<path
|
||||
id="path64"
|
||||
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
|
||||
<path
|
||||
id="path66"
|
||||
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
|
||||
<!-- Smile -->
|
||||
<path
|
||||
id="path68"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2778.85,1478.34 0.04,-0.02 c 21.03,-31.91 56.69,-51.12 94.91,-51.12 0.41,0 0.83,0 1.24,0.01 l -0.02,0.07 c 43.88,0.19 84.11,24.51 104.66,63.28" />
|
||||
<!-- Left Eye (cyan) -->
|
||||
<path
|
||||
id="path70"
|
||||
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2820.22,1595.17 0.01,0.01 c -14.62,-1.91 -25.56,-14.37 -25.56,-29.11 0,-0.03 0,-0.07 0,-0.1 l -0.02,-0.01 c 0,-0.03 0,-0.07 0,-0.1 0,-16.09 13.04,-29.14 29.13,-29.14 0.43,0 0.87,0.01 1.29,0.03 h 3.67 0.01 c 14.61,1.91 25.55,14.37 25.55,29.11 0,0.03 0,0.07 0,0.1 l 0.02,0.01 c 0,0.04 0,0.08 0,0.12 0,16.07 -13.03,29.09 -29.09,29.09 -0.03,0 -0.08,0 -0.12,0 l -4.89,-0.01" />
|
||||
<!-- Right Eye (cyan) -->
|
||||
<path
|
||||
id="path72"
|
||||
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2921.23,1569.61 v -3.65 l -0.01,-0.01 c 0,-0.27 -0.01,-0.54 -0.01,-0.81 0,-18.22 14.66,-33.05 32.87,-33.26 l 0.03,0.01 c 0.28,0 0.57,-0.01 0.84,-0.01 17.29,0 31.72,13.22 33.24,30.44 l -0.02,3.64 0.02,0.02 c 0,0.27 0.01,0.54 0.01,0.81 0,18.37 -14.9,33.28 -33.28,33.28 -0.26,0 -0.54,-0.01 -0.81,-0.02 l -0.02,-0.03 c -17.14,-0.18 -31.36,-13.34 -32.86,-30.42" />
|
||||
<!-- Nose -->
|
||||
<path
|
||||
id="path74"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2892.03,1528.23 -29.21,-20.68" />
|
||||
<path
|
||||
id="path76"
|
||||
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2862.82,1507.55 h 32.86" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 220 B |
|
After Width: | Height: | Size: 311 B |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 452 B |
|
After Width: | Height: | Size: 714 B |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
@@ -0,0 +1,57 @@
|
||||
package haus.nightmare.j3270;
|
||||
|
||||
import haus.nightmare.j3270.ui.AppIcon;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class AppIconTest {
|
||||
|
||||
@Test
|
||||
public void testIconsAreLoaded() {
|
||||
List<Image> icons = AppIcon.getAppIcons();
|
||||
assertNotNull(icons, "Icons list must not be null");
|
||||
assertFalse(icons.isEmpty(), "At least one icon image must be loaded");
|
||||
|
||||
// Verify standard sizes are present
|
||||
int[] expectedSizes = {16, 24, 32, 48, 64, 128, 256, 512};
|
||||
for (int expectedSize : expectedSizes) {
|
||||
boolean found = icons.stream().anyMatch(img -> img.getWidth(null) == expectedSize && img.getHeight(null) == expectedSize);
|
||||
assertTrue(found, "Expected icon of size " + expectedSize + "x" + expectedSize + " was not found");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultIcon() {
|
||||
Image defaultIcon = AppIcon.getDefaultIcon();
|
||||
assertNotNull(defaultIcon, "Default icon must not be null");
|
||||
assertEquals(256, defaultIcon.getWidth(null), "Default icon width should be 256");
|
||||
assertEquals(256, defaultIcon.getHeight(null), "Default icon height should be 256");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyToInHeadless() {
|
||||
// Must execute without exception even in headless environment
|
||||
assertDoesNotThrow(() -> AppIcon.applyTo(null));
|
||||
|
||||
if (!GraphicsEnvironment.isHeadless()) {
|
||||
JFrame frame = new JFrame();
|
||||
assertDoesNotThrow(() -> AppIcon.applyTo(frame));
|
||||
frame.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSvgResourcesExist() {
|
||||
URL svgUrl = getClass().getResource("/icons/lib3270j.svg");
|
||||
assertNotNull(svgUrl, "lib3270j.svg must exist in /icons/");
|
||||
|
||||
URL iconSvgUrl = getClass().getResource("/icons/j3270_icon.svg");
|
||||
assertNotNull(iconSvgUrl, "j3270_icon.svg must exist in /icons/");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package haus.nightmare.j3270;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class LoggingConfigurationTest {
|
||||
|
||||
private PrintStream originalOut;
|
||||
private PrintStream originalErr;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
originalOut = System.out;
|
||||
originalErr = System.err;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
System.setOut(originalOut);
|
||||
System.setErr(originalErr);
|
||||
// Ensure all handlers are closed and logging reset
|
||||
J3270App.configureLogging(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoggingDisabledWhenDebugFalse() {
|
||||
File logFile = new File("j3270.log");
|
||||
if (logFile.exists()) {
|
||||
logFile.delete();
|
||||
}
|
||||
|
||||
ByteArrayOutputStream outContent = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream errContent = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(outContent));
|
||||
System.setErr(new PrintStream(errContent));
|
||||
|
||||
J3270App.configureLogging(false);
|
||||
|
||||
// Root logger should have no handlers attached
|
||||
Logger rootLogger = Logger.getLogger("");
|
||||
assertEquals(0, rootLogger.getHandlers().length, "Root logger should have no handlers when debug is disabled");
|
||||
assertEquals(Level.OFF, rootLogger.getLevel(), "Root logger level should be OFF when debug is disabled");
|
||||
|
||||
// Application loggers should be OFF
|
||||
assertEquals(Level.OFF, Logger.getLogger("haus.nightmare").getLevel());
|
||||
assertEquals(Level.OFF, Logger.getLogger("haus.nightmare.j3270").getLevel());
|
||||
assertEquals(Level.OFF, Logger.getLogger("haus.nightmare.lib3270j").getLevel());
|
||||
|
||||
// Emit log records at all levels
|
||||
Logger appLogger = Logger.getLogger("haus.nightmare.j3270.J3270App");
|
||||
appLogger.severe("Test SEVERE message");
|
||||
appLogger.warning("Test WARNING message");
|
||||
appLogger.info("Test INFO message");
|
||||
appLogger.fine("Test FINE message");
|
||||
|
||||
// Verify nothing was written to stdout or stderr
|
||||
assertEquals(0, outContent.size(), "Standard output should be empty when debug is disabled");
|
||||
assertEquals(0, errContent.size(), "Standard error should be empty when debug is disabled");
|
||||
|
||||
// Verify j3270.log was NOT created
|
||||
assertFalse(logFile.exists(), "j3270.log should NOT be created when debug is disabled");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoggingEnabledWhenDebugTrue() {
|
||||
J3270App.configureLogging(true);
|
||||
|
||||
Logger rootLogger = Logger.getLogger("");
|
||||
Handler[] handlers = rootLogger.getHandlers();
|
||||
assertTrue(handlers.length >= 2, "Root logger should have at least ConsoleHandler and FileHandler when debug is enabled");
|
||||
|
||||
assertEquals(Level.ALL, rootLogger.getLevel());
|
||||
assertEquals(Level.ALL, Logger.getLogger("haus.nightmare").getLevel());
|
||||
assertEquals(Level.ALL, Logger.getLogger("haus.nightmare.j3270").getLevel());
|
||||
assertEquals(Level.ALL, Logger.getLogger("haus.nightmare.lib3270j").getLevel());
|
||||
|
||||
File logFile = new File("j3270.log");
|
||||
assertTrue(logFile.exists(), "j3270.log should be created when debug is enabled");
|
||||
|
||||
// Clean up
|
||||
J3270App.configureLogging(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
package haus.nightmare.j3270.storage;
|
||||
|
||||
import haus.nightmare.j3270.ui.ConnectDialog;
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.Frame;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class HostStorageAndProxyTest {
|
||||
|
||||
private List<SavedHost> originalHosts;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
originalHosts = HostStorage.getSavedHosts();
|
||||
HostStorage.clearAll();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
HostStorage.clearAll();
|
||||
HostStorage.saveAll(originalHosts);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SavedHost correctly models per-connection proxy and converts to ConnectionConfig")
|
||||
public void testSavedHostProxyToConnectionConfig() {
|
||||
// 1. Host with SOCKS5 proxy and auth
|
||||
SavedHost host1 = new SavedHost(
|
||||
UUID.randomUUID().toString(),
|
||||
"Prod Mainframe",
|
||||
"mvs.corp.local",
|
||||
2323,
|
||||
4,
|
||||
43,
|
||||
80,
|
||||
"TSO01",
|
||||
false,
|
||||
"TSO",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
"BOTH",
|
||||
"037",
|
||||
"SOCKS5",
|
||||
"proxy.internal.corp",
|
||||
1080,
|
||||
"admin",
|
||||
"secret123"
|
||||
);
|
||||
|
||||
ConnectionConfig cfg1 = host1.toConnectionConfig();
|
||||
assertEquals("mvs.corp.local", cfg1.getHost());
|
||||
assertEquals(2323, cfg1.getPort());
|
||||
assertTrue(cfg1.isUseTls());
|
||||
assertEquals("TSO01", cfg1.getLuName());
|
||||
assertEquals(ConnectionConfig.ProxyType.SOCKS5, cfg1.getProxyType());
|
||||
assertEquals("proxy.internal.corp", cfg1.getProxyHost());
|
||||
assertEquals(1080, cfg1.getProxyPort());
|
||||
assertEquals("admin", cfg1.getProxyUsername());
|
||||
assertEquals("secret123", cfg1.getProxyPassword());
|
||||
|
||||
// 2. Host with direct connection (NONE proxy)
|
||||
SavedHost host2 = new SavedHost(
|
||||
UUID.randomUUID().toString(),
|
||||
"Local VM370",
|
||||
"127.0.0.1",
|
||||
23,
|
||||
2,
|
||||
24,
|
||||
80,
|
||||
"",
|
||||
false,
|
||||
"CMS",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
"BOTH",
|
||||
"037",
|
||||
"NONE",
|
||||
"",
|
||||
0,
|
||||
"",
|
||||
""
|
||||
);
|
||||
|
||||
ConnectionConfig cfg2 = host2.toConnectionConfig();
|
||||
assertEquals("127.0.0.1", cfg2.getHost());
|
||||
assertEquals(23, cfg2.getPort());
|
||||
assertFalse(cfg2.isUseTls());
|
||||
assertEquals(ConnectionConfig.ProxyType.NONE, cfg2.getProxyType());
|
||||
assertNull(cfg2.getProxyHost());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("HostStorage persists and restores multiple host profiles with isolated per-connection proxies")
|
||||
public void testHostStoragePersistenceAndIsolation() {
|
||||
SavedHost h1 = new SavedHost(
|
||||
"id-1",
|
||||
"System A (SOCKS5)",
|
||||
"sysa.corp",
|
||||
23,
|
||||
4,
|
||||
62,
|
||||
160,
|
||||
"",
|
||||
false,
|
||||
"TSO",
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
"BOTH",
|
||||
"037",
|
||||
"SOCKS5",
|
||||
"socks.gateway.net",
|
||||
1080,
|
||||
"alice",
|
||||
"passA"
|
||||
);
|
||||
|
||||
SavedHost h2 = new SavedHost(
|
||||
"id-2",
|
||||
"System B (HTTP CONNECT)",
|
||||
"sysb.dmz",
|
||||
992,
|
||||
5,
|
||||
27,
|
||||
132,
|
||||
"LU99",
|
||||
false,
|
||||
"TSO",
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
"BOTH",
|
||||
"1047",
|
||||
"HTTP",
|
||||
"squid.proxy.org",
|
||||
3128,
|
||||
"bob",
|
||||
"passB"
|
||||
);
|
||||
|
||||
SavedHost h3 = new SavedHost(
|
||||
"id-3",
|
||||
"System C (Direct)",
|
||||
"sysc.lan",
|
||||
23,
|
||||
2,
|
||||
24,
|
||||
80,
|
||||
"",
|
||||
true, // autoConnect
|
||||
"CMS",
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
"BOTH",
|
||||
"500",
|
||||
"NONE",
|
||||
"",
|
||||
0,
|
||||
"",
|
||||
""
|
||||
);
|
||||
|
||||
HostStorage.saveHost(h1);
|
||||
HostStorage.saveHost(h2);
|
||||
HostStorage.saveHost(h3);
|
||||
|
||||
List<SavedHost> loaded = HostStorage.getSavedHosts();
|
||||
assertEquals(3, loaded.size());
|
||||
|
||||
SavedHost loadedA = HostStorage.findById("id-1");
|
||||
assertNotNull(loadedA);
|
||||
assertEquals("System A (SOCKS5)", loadedA.getName());
|
||||
assertEquals("SOCKS5", loadedA.getProxyType());
|
||||
assertEquals("socks.gateway.net", loadedA.getProxyHost());
|
||||
assertEquals(1080, loadedA.getProxyPort());
|
||||
assertEquals("alice", loadedA.getProxyUsername());
|
||||
assertEquals("passA", loadedA.getProxyPassword());
|
||||
|
||||
SavedHost loadedB = HostStorage.findById("id-2");
|
||||
assertNotNull(loadedB);
|
||||
assertEquals("HTTP", loadedB.getProxyType());
|
||||
assertEquals("squid.proxy.org", loadedB.getProxyHost());
|
||||
assertEquals(3128, loadedB.getProxyPort());
|
||||
assertEquals("bob", loadedB.getProxyUsername());
|
||||
assertEquals("passB", loadedB.getProxyPassword());
|
||||
|
||||
SavedHost loadedC = HostStorage.findById("id-3");
|
||||
assertNotNull(loadedC);
|
||||
assertEquals("NONE", loadedC.getProxyType());
|
||||
assertEquals("", loadedC.getProxyHost());
|
||||
assertTrue(loadedC.isAutoConnect());
|
||||
|
||||
// Verify getAutoConnectHost returns System C with its own connection settings
|
||||
SavedHost auto = HostStorage.getAutoConnectHost();
|
||||
assertNotNull(auto);
|
||||
assertEquals("id-3", auto.getId());
|
||||
assertEquals(ConnectionConfig.ProxyType.NONE, auto.toConnectionConfig().getProxyType());
|
||||
|
||||
// Verify deleteHost
|
||||
HostStorage.deleteHost("id-2");
|
||||
assertEquals(2, HostStorage.getSavedHosts().size());
|
||||
assertNull(HostStorage.findById("id-2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("HostStorage JSON matches a3270 format and parses a3270 JSON strings")
|
||||
public void testA3270JsonFormatCompatibility() {
|
||||
// String representative of a3270 saved_hosts preference JSON
|
||||
String a3270Json = "[\n" +
|
||||
" {\n" +
|
||||
" \"id\": \"android-uuid-1\",\n" +
|
||||
" \"name\": \"Dallas Mainframe\",\n" +
|
||||
" \"host\": \"dallas.ibm.com\",\n" +
|
||||
" \"port\": 2323,\n" +
|
||||
" \"model\": 4,\n" +
|
||||
" \"dynamicRows\": 62,\n" +
|
||||
" \"dynamicCols\": 160,\n" +
|
||||
" \"luName\": \"DAL01\",\n" +
|
||||
" \"autoConnect\": false,\n" +
|
||||
" \"hostType\": \"TSO\",\n" +
|
||||
" \"useTls\": true,\n" +
|
||||
" \"tlsVerifyCert\": false,\n" +
|
||||
" \"tn3270e\": true,\n" +
|
||||
" \"graphicsMode\": \"BOTH\",\n" +
|
||||
" \"codePage\": \"037\",\n" +
|
||||
" \"proxyType\": \"SOCKS5\",\n" +
|
||||
" \"proxyHost\": \"10.20.30.40\",\n" +
|
||||
" \"proxyPort\": 9050,\n" +
|
||||
" \"proxyUsername\": \"dallasuser\",\n" +
|
||||
" \"proxyPassword\": \"dallaspass\"\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
|
||||
List<SavedHost> parsed = HostStorage.parseHostsJson(a3270Json);
|
||||
assertEquals(1, parsed.size());
|
||||
SavedHost h = parsed.get(0);
|
||||
assertEquals("android-uuid-1", h.getId());
|
||||
assertEquals("Dallas Mainframe", h.getName());
|
||||
assertEquals("dallas.ibm.com", h.getHost());
|
||||
assertEquals(2323, h.getPort());
|
||||
assertEquals(4, h.getModel());
|
||||
assertTrue(h.isUseTls());
|
||||
assertFalse(h.isTlsVerifyCert());
|
||||
assertEquals("SOCKS5", h.getProxyType());
|
||||
assertEquals("10.20.30.40", h.getProxyHost());
|
||||
assertEquals(9050, h.getProxyPort());
|
||||
assertEquals("dallasuser", h.getProxyUsername());
|
||||
assertEquals("dallaspass", h.getProxyPassword());
|
||||
|
||||
// Roundtrip serialization
|
||||
String serialized = HostStorage.serializeHostsJson(parsed);
|
||||
List<SavedHost> roundtripped = HostStorage.parseHostsJson(serialized);
|
||||
assertEquals(1, roundtripped.size());
|
||||
SavedHost r = roundtripped.get(0);
|
||||
assertEquals(h.getId(), r.getId());
|
||||
assertEquals(h.getProxyType(), r.getProxyType());
|
||||
assertEquals(h.getProxyHost(), r.getProxyHost());
|
||||
assertEquals(h.getProxyPort(), r.getProxyPort());
|
||||
assertEquals(h.getProxyUsername(), r.getProxyUsername());
|
||||
assertEquals(h.getProxyPassword(), r.getProxyPassword());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ConnectDialog loads profile proxy and applies per-connection proxy to ConnectionConfig")
|
||||
public void testConnectDialogProxyHandling() {
|
||||
if (!GraphicsEnvironment.isHeadless()) {
|
||||
ConnectDialog dialog = new ConnectDialog((Frame) null);
|
||||
dialog.setInitialHost("test.mainframe.com");
|
||||
dialog.setInitialPort(23);
|
||||
dialog.setInitialProxy(ConnectionConfig.ProxyType.SOCKS5, "127.0.0.1", 1080, "testuser", "testpass");
|
||||
|
||||
assertEquals(ConnectionConfig.ProxyType.SOCKS5, dialog.getSelectedProxyType());
|
||||
assertEquals("127.0.0.1", dialog.getProxyHost());
|
||||
assertEquals(1080, dialog.getProxyPort());
|
||||
assertEquals("testuser", dialog.getProxyUsername());
|
||||
assertEquals("testpass", dialog.getProxyPassword());
|
||||
|
||||
// Loading a profile updates dialog proxy fields
|
||||
SavedHost profile = new SavedHost(
|
||||
"p1", "Profile 1", "prod.ibm.net", 992, 4, 43, 80, "", false,
|
||||
"TSO", true, true, true, "BOTH", "037",
|
||||
"HTTP", "proxy.corp.net", 8080, "corpuser", "corppass"
|
||||
);
|
||||
dialog.loadProfile(profile);
|
||||
|
||||
assertEquals(ConnectionConfig.ProxyType.HTTP, dialog.getSelectedProxyType());
|
||||
assertEquals("proxy.corp.net", dialog.getProxyHost());
|
||||
assertEquals(8080, dialog.getProxyPort());
|
||||
assertEquals("corpuser", dialog.getProxyUsername());
|
||||
assertEquals("corppass", dialog.getProxyPassword());
|
||||
dialog.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SavedHost preserves all GraphicsMode settings across JSON serialization without clobbering to BOTH")
|
||||
public void testSavedHostGraphicsModeSerializationRoundtrip() {
|
||||
for (haus.nightmare.lib3270j.graphics.GraphicsMode gm : haus.nightmare.lib3270j.graphics.GraphicsMode.values()) {
|
||||
SavedHost host = new SavedHost(
|
||||
UUID.randomUUID().toString(),
|
||||
"Gfx " + gm.name(),
|
||||
"test.host.com",
|
||||
23,
|
||||
4,
|
||||
24,
|
||||
80,
|
||||
"",
|
||||
false,
|
||||
"TSO",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
gm.name(),
|
||||
"037",
|
||||
"NONE",
|
||||
"",
|
||||
0,
|
||||
"",
|
||||
""
|
||||
);
|
||||
String json = HostStorage.serializeHostsJson(List.of(host));
|
||||
List<SavedHost> parsed = HostStorage.parseHostsJson(json);
|
||||
assertEquals(1, parsed.size());
|
||||
assertEquals(gm.name(), parsed.get(0).getGraphicsMode(),
|
||||
"Graphics mode " + gm.name() + " must be preserved and not coerced to BOTH");
|
||||
assertEquals(gm, parsed.get(0).toConnectionConfig().getGraphicsMode(),
|
||||
"ConnectionConfig must reflect GraphicsMode." + gm.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ public class BehaviorSettingsTest {
|
||||
private int origReconnectMaxRetries;
|
||||
private boolean origInputMask;
|
||||
private String origInputMaskChar;
|
||||
private boolean origBlockPaste;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
@@ -27,6 +28,7 @@ public class BehaviorSettingsTest {
|
||||
origReconnectMaxRetries = Settings.getAutoConnectReconnectMaxRetries();
|
||||
origInputMask = Settings.getInputMask();
|
||||
origInputMaskChar = Settings.getInputMaskChar();
|
||||
origBlockPaste = Settings.getBlockPaste();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -35,6 +37,7 @@ public class BehaviorSettingsTest {
|
||||
Settings.setAutoConnectReconnectMaxRetries(origReconnectMaxRetries);
|
||||
Settings.setInputMask(origInputMask);
|
||||
Settings.setInputMaskChar(origInputMaskChar);
|
||||
Settings.setBlockPaste(origBlockPaste);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -74,6 +77,39 @@ public class BehaviorSettingsTest {
|
||||
assertEquals("*", Settings.getInputMaskChar());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Block paste setting defaults to true and binds to SettingsDialog")
|
||||
public void testBlockPasteSettingAndDialogBinding() throws Exception {
|
||||
// Default must be true
|
||||
assertTrue(Settings.getBlockPaste(), "Settings.getBlockPaste() should default to true");
|
||||
|
||||
Settings.setBlockPaste(false);
|
||||
assertFalse(Settings.getBlockPaste());
|
||||
Settings.setBlockPaste(true);
|
||||
assertTrue(Settings.getBlockPaste());
|
||||
|
||||
J3270App app;
|
||||
try {
|
||||
app = new J3270App();
|
||||
} catch (HeadlessException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SettingsDialog dialog = new SettingsDialog(app);
|
||||
Field blockPasteField = SettingsDialog.class.getDeclaredField("blockPasteCheck");
|
||||
blockPasteField.setAccessible(true);
|
||||
JCheckBox blockPasteCheck = (JCheckBox) blockPasteField.get(dialog);
|
||||
assertNotNull(blockPasteCheck, "blockPasteCheck must exist in SettingsDialog");
|
||||
assertEquals("Block paste", blockPasteCheck.getText());
|
||||
assertEquals(Settings.getBlockPaste(), blockPasteCheck.isSelected());
|
||||
|
||||
dialog.dispose();
|
||||
app.dispose();
|
||||
} catch (HeadlessException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("INI export and load preserves autoReconnect and inputMask")
|
||||
public void testIniExportAndLoad() throws Exception {
|
||||
@@ -173,4 +209,90 @@ public class BehaviorSettingsTest {
|
||||
} catch (HeadlessException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SettingsDialog tabs are organized into Appearance, Behavior, Connection, and Entry Assist")
|
||||
public void testSettingsDialogReorganizedTabsAndGrouping() throws Exception {
|
||||
J3270App app;
|
||||
try {
|
||||
app = new J3270App();
|
||||
} catch (HeadlessException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SettingsDialog dialog = new SettingsDialog(app);
|
||||
|
||||
// Find top-level JTabbedPane
|
||||
JTabbedPane mainTabs = null;
|
||||
for (Component c : dialog.getContentPane().getComponents()) {
|
||||
if (c instanceof JTabbedPane) {
|
||||
mainTabs = (JTabbedPane) c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertNotNull(mainTabs, "Main JTabbedPane must exist");
|
||||
assertEquals(4, mainTabs.getTabCount(), "Must have 4 main tabs");
|
||||
assertEquals("Appearance", mainTabs.getTitleAt(0));
|
||||
assertEquals("Behavior", mainTabs.getTitleAt(1));
|
||||
assertEquals("Connection", mainTabs.getTitleAt(2));
|
||||
assertEquals("Entry Assist", mainTabs.getTitleAt(3));
|
||||
|
||||
// Verify Appearance subtabs: Display & Font, Colors
|
||||
Component appComp = mainTabs.getComponentAt(0);
|
||||
assertTrue(appComp instanceof JPanel);
|
||||
JTabbedPane appTabs = findTabbedPane((JPanel) appComp);
|
||||
assertNotNull(appTabs, "Appearance panel must have subtabs");
|
||||
assertEquals(2, appTabs.getTabCount());
|
||||
assertEquals("Display & Font", appTabs.getTitleAt(0));
|
||||
assertEquals("Colors", appTabs.getTitleAt(1));
|
||||
|
||||
// Verify Behavior subtabs: General, Keymap
|
||||
Component behComp = mainTabs.getComponentAt(1);
|
||||
assertTrue(behComp instanceof JPanel);
|
||||
JTabbedPane behTabs = findTabbedPane((JPanel) behComp);
|
||||
assertNotNull(behTabs, "Behavior panel must have subtabs");
|
||||
assertEquals(2, behTabs.getTabCount());
|
||||
assertEquals("General", behTabs.getTitleAt(0));
|
||||
assertEquals("Keymap", behTabs.getTitleAt(1));
|
||||
|
||||
// Verify controls exist in SettingsDialog
|
||||
Field cursorStyleField = SettingsDialog.class.getDeclaredField("cursorStyleBox");
|
||||
cursorStyleField.setAccessible(true);
|
||||
assertNotNull(cursorStyleField.get(dialog), "cursorStyleBox must exist");
|
||||
|
||||
Field crosshairField = SettingsDialog.class.getDeclaredField("crosshairRulerCheck");
|
||||
crosshairField.setAccessible(true);
|
||||
assertNotNull(crosshairField.get(dialog), "crosshairRulerCheck must exist");
|
||||
|
||||
Field fourColorField = SettingsDialog.class.getDeclaredField("fourColorOverrideCheck");
|
||||
fourColorField.setAccessible(true);
|
||||
assertNotNull(fourColorField.get(dialog), "fourColorOverrideCheck must exist");
|
||||
|
||||
Field numericLockField = SettingsDialog.class.getDeclaredField("numericFieldLockCheck");
|
||||
numericLockField.setAccessible(true);
|
||||
assertNotNull(numericLockField.get(dialog), "numericFieldLockCheck must exist");
|
||||
|
||||
Field autoSkipField = SettingsDialog.class.getDeclaredField("autoSkipCheck");
|
||||
autoSkipField.setAccessible(true);
|
||||
assertNotNull(autoSkipField.get(dialog), "autoSkipCheck must exist");
|
||||
|
||||
dialog.dispose();
|
||||
app.dispose();
|
||||
} catch (HeadlessException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static JTabbedPane findTabbedPane(Container container) {
|
||||
for (Component c : container.getComponents()) {
|
||||
if (c instanceof JTabbedPane) {
|
||||
return (JTabbedPane) c;
|
||||
}
|
||||
if (c instanceof Container) {
|
||||
JTabbedPane nested = findTabbedPane((Container) c);
|
||||
if (nested != null) return nested;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,23 @@ public class FontSettingsPersistenceTest {
|
||||
|
||||
private int originalFontSize;
|
||||
private String originalFontFamily;
|
||||
private int originalUiFontSize;
|
||||
private String originalUiFontFamily;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
originalFontSize = Settings.getFontSize();
|
||||
originalFontFamily = Settings.getFontFamily();
|
||||
originalUiFontSize = Settings.getUiFontSize();
|
||||
originalUiFontFamily = Settings.getUiFontFamily();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
Settings.setFontSize(originalFontSize);
|
||||
Settings.setFontFamily(originalFontFamily);
|
||||
Settings.setUiFontSize(originalUiFontSize);
|
||||
Settings.setUiFontFamily(originalUiFontFamily);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -39,6 +45,76 @@ public class FontSettingsPersistenceTest {
|
||||
assertEquals(18, Settings.getFontSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUiFontSettingsIndependence() {
|
||||
// Configure terminal fonts
|
||||
Settings.setFontFamily("Courier");
|
||||
Settings.setFontSize(20);
|
||||
|
||||
// Configure UI fonts
|
||||
Settings.setUiFontFamily("Dialog");
|
||||
Settings.setUiFontSize(15);
|
||||
|
||||
// Verify independent getters
|
||||
assertEquals("Courier", Settings.getFontFamily());
|
||||
assertEquals(20, Settings.getFontSize());
|
||||
assertEquals("Dialog", Settings.getUiFontFamily());
|
||||
assertEquals(15, Settings.getUiFontSize());
|
||||
|
||||
// Change UI font and ensure terminal font is untouched
|
||||
Settings.setUiFontSize(18);
|
||||
Settings.setUiFontFamily("SansSerif");
|
||||
assertEquals("Courier", Settings.getFontFamily(), "Terminal font family must not change when UI font changes");
|
||||
assertEquals(20, Settings.getFontSize(), "Terminal font size must not change when UI font changes");
|
||||
assertEquals("SansSerif", Settings.getUiFontFamily());
|
||||
assertEquals(18, Settings.getUiFontSize());
|
||||
|
||||
// Change terminal font and ensure UI font is untouched
|
||||
Settings.setFontSize(24);
|
||||
Settings.setFontFamily("IBM 3270");
|
||||
assertEquals("SansSerif", Settings.getUiFontFamily(), "UI font family must not change when terminal font changes");
|
||||
assertEquals(18, Settings.getUiFontSize(), "UI font size must not change when terminal font changes");
|
||||
assertEquals("IBM 3270", Settings.getFontFamily());
|
||||
assertEquals(24, Settings.getFontSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUiFontIniImportExport() throws Exception {
|
||||
java.io.File tempIni = java.io.File.createTempFile("j3270_ui_font_test", ".ini");
|
||||
tempIni.deleteOnExit();
|
||||
|
||||
try {
|
||||
try (java.io.PrintWriter pw = new java.io.PrintWriter(tempIni)) {
|
||||
pw.println("[appearance]");
|
||||
pw.println("uiFontFamily = Dialog");
|
||||
pw.println("uiFontSize = 16");
|
||||
pw.println("fontFamily = Monospaced");
|
||||
pw.println("fontSize = 22");
|
||||
}
|
||||
|
||||
Settings.loadFromIniFile(tempIni.getAbsolutePath());
|
||||
|
||||
assertEquals("Dialog", Settings.getUiFontFamily());
|
||||
assertEquals(16, Settings.getUiFontSize());
|
||||
assertEquals("Monospaced", Settings.getFontFamily());
|
||||
assertEquals(22, Settings.getFontSize());
|
||||
|
||||
// Export to another file and check contents
|
||||
java.io.File exportIni = java.io.File.createTempFile("j3270_ui_font_export", ".ini");
|
||||
exportIni.deleteOnExit();
|
||||
|
||||
Settings.exportToIniFile(exportIni.getAbsolutePath());
|
||||
String exported = new String(java.nio.file.Files.readAllBytes(exportIni.toPath()));
|
||||
|
||||
assertTrue(exported.contains("uiFontFamily = Dialog"), "Exported INI must contain uiFontFamily");
|
||||
assertTrue(exported.contains("uiFontSize = 16"), "Exported INI must contain uiFontSize");
|
||||
assertTrue(exported.contains("fontFamily = Monospaced"), "Exported INI must contain terminal fontFamily");
|
||||
assertTrue(exported.contains("fontSize = 22"), "Exported INI must contain terminal fontSize");
|
||||
} finally {
|
||||
tempIni.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTerminalPanelResizeDoesNotResetFontSize() {
|
||||
try {
|
||||
@@ -77,4 +153,57 @@ public class FontSettingsPersistenceTest {
|
||||
// In headless environment without display, Settings persistence is verified above
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingsDialogApplyDoesNotIncrementFontSize() throws Exception {
|
||||
try {
|
||||
Settings.setUiFontSize(16);
|
||||
Settings.setFontSize(22);
|
||||
Settings.setUiFontFamily("Dialog");
|
||||
Settings.setFontFamily("Monospaced");
|
||||
ThemeManager.applyUIManagerDefaults(ThemeManager.getTheme());
|
||||
|
||||
SettingsDialog dialog = new SettingsDialog(null);
|
||||
|
||||
java.lang.reflect.Method applyMethod = SettingsDialog.class.getDeclaredMethod("applySettings");
|
||||
applyMethod.setAccessible(true);
|
||||
|
||||
// Find all JLabels in SettingsDialog
|
||||
java.util.List<javax.swing.JLabel> labels = new java.util.ArrayList<>();
|
||||
findLabelsRecursively(dialog.getContentPane(), labels);
|
||||
assertFalse(labels.isEmpty(), "SettingsDialog must contain labels");
|
||||
|
||||
for (javax.swing.JLabel label : labels) {
|
||||
assertEquals(16, label.getFont().getSize(),
|
||||
"Label '" + label.getText() + "' must initially have size 16");
|
||||
}
|
||||
|
||||
// Click Apply multiple times
|
||||
for (int click = 1; click <= 5; click++) {
|
||||
applyMethod.invoke(dialog);
|
||||
|
||||
assertEquals(16, Settings.getUiFontSize(), "Settings uiFontSize must stay 16");
|
||||
assertEquals(22, Settings.getFontSize(), "Settings fontSize must stay 22");
|
||||
|
||||
for (javax.swing.JLabel label : labels) {
|
||||
assertEquals(16, label.getFont().getSize(),
|
||||
"Label '" + label.getText() + "' must remain at size 16 after click #" + click);
|
||||
}
|
||||
}
|
||||
|
||||
dialog.dispose();
|
||||
} catch (HeadlessException e) {
|
||||
// Ignored in headless environment if dialog creation fails
|
||||
}
|
||||
}
|
||||
|
||||
private void findLabelsRecursively(java.awt.Container container, java.util.List<javax.swing.JLabel> labels) {
|
||||
for (java.awt.Component c : container.getComponents()) {
|
||||
if (c instanceof javax.swing.JLabel) {
|
||||
labels.add((javax.swing.JLabel) c);
|
||||
} else if (c instanceof java.awt.Container) {
|
||||
findLabelsRecursively((java.awt.Container) c, labels);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Unit tests for HotspotEngine (Phase 25.2).
|
||||
* Verifies regex detection of URLs, PF keys, Enter, and menu choices,
|
||||
* hit-testing, action strings, and word boundary isolation.
|
||||
*/
|
||||
public class HotspotEngineTest {
|
||||
|
||||
private HotspotEngine engine;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
engine = new HotspotEngine();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Detects HTTP, HTTPS, FTP, and WWW URLs with action strings")
|
||||
public void testUrlDetection() {
|
||||
String screen = "Visit https://ibm.com/support or http://example.org:8080/path?q=1 for help.\n" +
|
||||
"Download from ftp://ftp.archive.org/pub/files or browse www.google.com.";
|
||||
List<HotspotEngine.Hotspot> hotspots = engine.scanText(screen, 2, 80);
|
||||
|
||||
assertEquals(4, hotspots.size());
|
||||
|
||||
HotspotEngine.Hotspot h0 = hotspots.get(0);
|
||||
assertEquals(HotspotEngine.HotspotType.URL, h0.getType());
|
||||
assertEquals("https://ibm.com/support", h0.getDisplayText());
|
||||
assertEquals("https://ibm.com/support", h0.getAction());
|
||||
assertEquals(0, h0.getStartRow());
|
||||
assertEquals(6, h0.getStartCol());
|
||||
assertTrue(h0.contains(0, 10));
|
||||
assertFalse(h0.contains(0, 5));
|
||||
|
||||
HotspotEngine.Hotspot h1 = hotspots.get(1);
|
||||
assertEquals("http://example.org:8080/path?q=1", h1.getDisplayText());
|
||||
assertEquals("http://example.org:8080/path?q=1", h1.getAction());
|
||||
|
||||
HotspotEngine.Hotspot h2 = hotspots.get(2);
|
||||
assertEquals("ftp://ftp.archive.org/pub/files", h2.getDisplayText());
|
||||
assertEquals("ftp://ftp.archive.org/pub/files", h2.getAction());
|
||||
|
||||
HotspotEngine.Hotspot h3 = hotspots.get(3);
|
||||
assertEquals("www.google.com", h3.getDisplayText());
|
||||
assertEquals("http://www.google.com", h3.getAction(), "www. URLs should have http:// prepended for action");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Detects PF1..PF24, F1..F24, PA1..PA3, and [ENTER] function keys")
|
||||
public void testFunctionKeyDetection() {
|
||||
String screen = "PF1=Help PF3=Exit PF7=Up PF8=Down PF12=Cancel\n" +
|
||||
"F1=Help F12=Cancel PA1=Abort [ENTER]=Continue";
|
||||
List<HotspotEngine.Hotspot> hotspots = engine.scanText(screen, 2, 80);
|
||||
|
||||
assertEquals(9, hotspots.size());
|
||||
|
||||
// PF1
|
||||
assertEquals(HotspotEngine.HotspotType.PF_KEY, hotspots.get(0).getType());
|
||||
assertEquals("PF1", hotspots.get(0).getDisplayText());
|
||||
assertEquals("[pf1]", hotspots.get(0).getAction());
|
||||
|
||||
// PF3
|
||||
assertEquals("PF3", hotspots.get(1).getDisplayText());
|
||||
assertEquals("[pf3]", hotspots.get(1).getAction());
|
||||
|
||||
// PF7
|
||||
assertEquals("PF7", hotspots.get(2).getDisplayText());
|
||||
assertEquals("[pf7]", hotspots.get(2).getAction());
|
||||
|
||||
// PF8
|
||||
assertEquals("PF8", hotspots.get(3).getDisplayText());
|
||||
assertEquals("[pf8]", hotspots.get(3).getAction());
|
||||
|
||||
// PF12
|
||||
assertEquals("PF12", hotspots.get(4).getDisplayText());
|
||||
assertEquals("[pf12]", hotspots.get(4).getAction());
|
||||
|
||||
// F1 -> [pf1]
|
||||
assertEquals("F1", hotspots.get(5).getDisplayText());
|
||||
assertEquals("[pf1]", hotspots.get(5).getAction());
|
||||
|
||||
// F12 -> [pf12]
|
||||
assertEquals("F12", hotspots.get(6).getDisplayText());
|
||||
assertEquals("[pf12]", hotspots.get(6).getAction());
|
||||
|
||||
// PA1 -> [pa1]
|
||||
assertEquals(HotspotEngine.HotspotType.PF_KEY, hotspots.get(7).getType());
|
||||
assertEquals("PA1", hotspots.get(7).getDisplayText());
|
||||
assertEquals("[pa1]", hotspots.get(7).getAction());
|
||||
|
||||
// [ENTER] -> [enter]
|
||||
assertEquals(HotspotEngine.HotspotType.ENTER, hotspots.get(8).getType());
|
||||
assertEquals("[ENTER]", hotspots.get(8).getDisplayText());
|
||||
assertEquals("[enter]", hotspots.get(8).getAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Isolates function keys with strict word boundaries to avoid false positives")
|
||||
public void testWordBoundaryIsolation() {
|
||||
String screen = "STAFF1 MEMBER SPF12 FILE TRANSFER PROMPT";
|
||||
List<HotspotEngine.Hotspot> hotspots = engine.scanText(screen, 1, 80);
|
||||
|
||||
// Neither STAFF1 nor SPF12 nor TRANSFER should be detected as PF1, F1, or F12
|
||||
assertEquals(0, hotspots.size(), "Embedded letters in words should not trigger PF key hotspots");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Detects numbered menu choices and arrow prompts")
|
||||
public void testMenuChoiceDetection() {
|
||||
String screen = " 1. User Settings\n" +
|
||||
" 2. Session Profiles\n" +
|
||||
"===> 3";
|
||||
List<HotspotEngine.Hotspot> hotspots = engine.scanText(screen, 3, 80);
|
||||
|
||||
assertEquals(3, hotspots.size());
|
||||
|
||||
HotspotEngine.Hotspot m1 = hotspots.get(0);
|
||||
assertEquals(HotspotEngine.HotspotType.MENU_CHOICE, m1.getType());
|
||||
assertEquals("1.", m1.getDisplayText());
|
||||
assertEquals("1[enter]", m1.getAction());
|
||||
assertEquals(0, m1.getStartRow());
|
||||
|
||||
HotspotEngine.Hotspot m2 = hotspots.get(1);
|
||||
assertEquals("2.", m2.getDisplayText());
|
||||
assertEquals("2[enter]", m2.getAction());
|
||||
assertEquals(1, m2.getStartRow());
|
||||
|
||||
HotspotEngine.Hotspot m3 = hotspots.get(2);
|
||||
assertEquals("===> 3", m3.getDisplayText());
|
||||
assertEquals("3[enter]", m3.getAction());
|
||||
assertEquals(2, m3.getStartRow());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Hit-testing finds hotspot under specific coordinate")
|
||||
public void testFindHotspot() {
|
||||
String screen = "PF3=Exit https://github.com ===> 1";
|
||||
engine.scanText(screen, 1, 80);
|
||||
|
||||
// Under PF3 (cols 0..2)
|
||||
HotspotEngine.Hotspot hPf = engine.findHotspot(0, 1);
|
||||
assertNotNull(hPf);
|
||||
assertEquals("PF3", hPf.getDisplayText());
|
||||
|
||||
// Space between hotspots
|
||||
assertNull(engine.findHotspot(0, 5));
|
||||
|
||||
// Under URL (cols 11..28)
|
||||
HotspotEngine.Hotspot hUrl = engine.findHotspot(0, 15);
|
||||
assertNotNull(hUrl);
|
||||
assertEquals("https://github.com", hUrl.getDisplayText());
|
||||
|
||||
// Under prompt (cols 32..37)
|
||||
HotspotEngine.Hotspot hPrompt = engine.findHotspot(0, 34);
|
||||
assertNotNull(hPrompt);
|
||||
assertEquals("===> 1", hPrompt.getDisplayText());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Toggling engine options enables or disables specific hotspot categories")
|
||||
public void testTogglingCategories() {
|
||||
String screen = "PF3=Exit https://example.com 1. Options";
|
||||
|
||||
engine.scanText(screen, 1, 80);
|
||||
assertEquals(3, engine.getHotspots().size());
|
||||
|
||||
// Disable URLs
|
||||
engine.setUrlEnabled(false);
|
||||
List<HotspotEngine.Hotspot> noUrls = engine.scanText(screen, 1, 80);
|
||||
assertEquals(2, noUrls.size());
|
||||
assertFalse(noUrls.stream().anyMatch(h -> h.getType() == HotspotEngine.HotspotType.URL));
|
||||
|
||||
// Disable PF keys
|
||||
engine.setPfKeyEnabled(false);
|
||||
List<HotspotEngine.Hotspot> menuOnly = engine.scanText(screen, 1, 80);
|
||||
assertEquals(1, menuOnly.size());
|
||||
assertEquals(HotspotEngine.HotspotType.MENU_CHOICE, menuOnly.get(0).getType());
|
||||
|
||||
// Disable menu choices
|
||||
engine.setMenuChoiceEnabled(false);
|
||||
List<HotspotEngine.Hotspot> empty = engine.scanText(screen, 1, 80);
|
||||
assertEquals(0, empty.size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.j3270.J3270App;
|
||||
import haus.nightmare.j3270.config.Settings;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class ImportConfigTest {
|
||||
|
||||
private String origFontFamily;
|
||||
private int origFontSize;
|
||||
private String origHost;
|
||||
private int origPort;
|
||||
private boolean origAutoReconnect;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
origFontFamily = Settings.getFontFamily();
|
||||
origFontSize = Settings.getFontSize();
|
||||
origHost = Settings.getAutoConnectHost();
|
||||
origPort = Settings.getAutoConnectPort();
|
||||
origAutoReconnect = Settings.getAutoConnectAutoReconnect();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
Settings.setFontFamily(origFontFamily);
|
||||
Settings.setFontSize(origFontSize);
|
||||
Settings.setAutoConnectHost(origHost);
|
||||
Settings.setAutoConnectPort(origPort);
|
||||
Settings.setAutoConnectAutoReconnect(origAutoReconnect);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SettingsDialog contains Import Config button positioned to the left of Export Config")
|
||||
public void testImportButtonPlacementAndProperties() {
|
||||
J3270App app;
|
||||
try {
|
||||
app = new J3270App();
|
||||
} catch (HeadlessException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SettingsDialog dialog = new SettingsDialog(app);
|
||||
|
||||
JButton btnImport = dialog.getBtnImport();
|
||||
JButton btnExport = dialog.getBtnExport();
|
||||
|
||||
assertNotNull(btnImport, "Import Config button must exist");
|
||||
assertNotNull(btnExport, "Export Config button must exist");
|
||||
|
||||
assertEquals("Import Config...", btnImport.getText());
|
||||
assertEquals("Export Config...", btnExport.getText());
|
||||
|
||||
// Verify btnImport is in the same container and positioned before (to the left of) btnExport
|
||||
Container parent = btnImport.getParent();
|
||||
assertNotNull(parent, "Import button must have a parent container");
|
||||
assertSame(parent, btnExport.getParent(), "Import and Export buttons must share the same parent panel");
|
||||
|
||||
Component[] comps = parent.getComponents();
|
||||
int importIdx = -1;
|
||||
int exportIdx = -1;
|
||||
for (int i = 0; i < comps.length; i++) {
|
||||
if (comps[i] == btnImport) importIdx = i;
|
||||
if (comps[i] == btnExport) exportIdx = i;
|
||||
}
|
||||
|
||||
assertTrue(importIdx >= 0, "btnImport must be in parent container");
|
||||
assertTrue(exportIdx >= 0, "btnExport must be in parent container");
|
||||
assertTrue(importIdx < exportIdx, "btnImport must be placed to the left of btnExport (index " + importIdx + " < " + exportIdx + ")");
|
||||
|
||||
dialog.dispose();
|
||||
app.dispose();
|
||||
} catch (HeadlessException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("importConfigFile updates Settings and refreshes SettingsDialog controls")
|
||||
public void testImportConfigFileUpdatesSettingsAndDialog() throws IOException {
|
||||
J3270App app;
|
||||
try {
|
||||
app = new J3270App();
|
||||
} catch (HeadlessException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
File tempIni = File.createTempFile("j3270_test_import", ".ini");
|
||||
try {
|
||||
try (FileWriter writer = new FileWriter(tempIni)) {
|
||||
writer.write("[appearance]\n");
|
||||
writer.write("fontSize = 24\n");
|
||||
writer.write("fontFamily = Monospaced\n");
|
||||
writer.write("\n[behavior]\n");
|
||||
writer.write("startupBehavior = AUTO_CONNECT\n");
|
||||
writer.write("autoConnectHost = testhost.example.org\n");
|
||||
writer.write("autoConnectPort = 2323\n");
|
||||
writer.write("autoConnectAutoReconnect = true\n");
|
||||
}
|
||||
|
||||
SettingsDialog dialog = new SettingsDialog(app);
|
||||
dialog.importConfigFile(tempIni);
|
||||
|
||||
// Verify Settings updated
|
||||
assertEquals(24, Settings.getFontSize());
|
||||
assertEquals("testhost.example.org", Settings.getAutoConnectHost());
|
||||
assertEquals(2323, Settings.getAutoConnectPort());
|
||||
assertTrue(Settings.getAutoConnectAutoReconnect());
|
||||
|
||||
// Verify dialog state reflects the imported values
|
||||
assertEquals(Settings.StartupBehavior.AUTO_CONNECT, Settings.getStartupBehavior());
|
||||
|
||||
dialog.dispose();
|
||||
app.dispose();
|
||||
} catch (HeadlessException ignored) {
|
||||
} finally {
|
||||
if (tempIni.exists()) {
|
||||
tempIni.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("importConfigFile throws FileNotFoundException for nonexistent file")
|
||||
public void testImportNonExistentFileThrows() {
|
||||
J3270App app;
|
||||
try {
|
||||
app = new J3270App();
|
||||
} catch (HeadlessException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SettingsDialog dialog = new SettingsDialog(app);
|
||||
File nonExistent = new File("/path/to/nonexistent/file_" + System.currentTimeMillis() + ".ini");
|
||||
assertThrows(FileNotFoundException.class, () -> {
|
||||
dialog.importConfigFile(nonExistent);
|
||||
});
|
||||
|
||||
dialog.dispose();
|
||||
app.dispose();
|
||||
} catch (HeadlessException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.j3270.config.Settings;
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.ConnectionState;
|
||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.Cursor;
|
||||
import java.awt.HeadlessException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class SelectionHandlesAndTrimTest {
|
||||
|
||||
private boolean origRemainAfterEdit;
|
||||
private boolean origSizingHandles;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
origRemainAfterEdit = Settings.getTrimRectRemainAfterEdit();
|
||||
origSizingHandles = Settings.getTrimRectSizingHandles();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
Settings.setTrimRectRemainAfterEdit(origRemainAfterEdit);
|
||||
Settings.setTrimRectSizingHandles(origSizingHandles);
|
||||
}
|
||||
|
||||
private TerminalPanel createTestPanel() {
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
panel.setSize(800, 600);
|
||||
panel.cellWidth = 10;
|
||||
panel.cellHeight = 20;
|
||||
|
||||
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
|
||||
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
sb.erase(false);
|
||||
panel.setClient(client);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Trailing whitespace is trimmed on copied lines in block selection mode")
|
||||
public void testBlockSelectionTrailingWhitespaceTrim() {
|
||||
try {
|
||||
TerminalPanel panel = createTestPanel();
|
||||
ScreenBuffer sb = panel.getClient().getScreenBuffer();
|
||||
int cols = sb.getCols();
|
||||
|
||||
// Populate row 0: "HELLO "
|
||||
String text0 = "HELLO";
|
||||
for (int i = 0; i < text0.length(); i++) {
|
||||
sb.getCell(i).ucs4 = text0.charAt(i);
|
||||
}
|
||||
|
||||
// Populate row 1: "WORLD 123 "
|
||||
String text1 = "WORLD 123";
|
||||
for (int i = 0; i < text1.length(); i++) {
|
||||
sb.getCell(cols + i).ucs4 = text1.charAt(i);
|
||||
}
|
||||
|
||||
// Select rectangle from (row 0, col 0) to (row 1, col 20)
|
||||
panel.setBlockSelectMode(true);
|
||||
panel.setSelectionRange(0, cols + 20);
|
||||
|
||||
String copied = panel.getSelectedText();
|
||||
// Should be "HELLO\nWORLD 123" without the trailing spaces up to col 20
|
||||
assertEquals("HELLO\nWORLD 123", copied);
|
||||
} catch (HeadlessException ignored) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Trailing whitespace is trimmed on copied lines in stream selection mode")
|
||||
public void testStreamSelectionTrailingWhitespaceTrim() {
|
||||
try {
|
||||
TerminalPanel panel = createTestPanel();
|
||||
ScreenBuffer sb = panel.getClient().getScreenBuffer();
|
||||
int cols = sb.getCols();
|
||||
|
||||
// Populate row 0: "LINE1 "
|
||||
String text0 = "LINE1";
|
||||
for (int i = 0; i < text0.length(); i++) {
|
||||
sb.getCell(i).ucs4 = text0.charAt(i);
|
||||
}
|
||||
|
||||
// Populate row 1: "LINE2"
|
||||
String text1 = "LINE2";
|
||||
for (int i = 0; i < text1.length(); i++) {
|
||||
sb.getCell(cols + i).ucs4 = text1.charAt(i);
|
||||
}
|
||||
|
||||
panel.setBlockSelectMode(false);
|
||||
panel.setTrimRectSizingHandles(false);
|
||||
// Select from row 0, col 0 to row 1, col 10
|
||||
panel.setSelectionRange(0, cols + 10);
|
||||
|
||||
String copied = panel.getSelectedText();
|
||||
assertEquals("LINE1\nLINE2", copied);
|
||||
} catch (HeadlessException ignored) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Drag handle detection identifies 8 resize handles, MOVE inside box, and NONE outside")
|
||||
public void testDragHandleDetection() {
|
||||
try {
|
||||
TerminalPanel panel = createTestPanel();
|
||||
panel.setBlockSelectMode(true);
|
||||
panel.setTrimRectSizingHandles(true);
|
||||
|
||||
// Without selection, should return NONE
|
||||
assertEquals(TerminalPanel.DragHandle.NONE, panel.getDragHandleAt(100, 100));
|
||||
|
||||
// Select rectangle: rows 2 to 5, cols 10 to 20
|
||||
ScreenBuffer sb = panel.getClient().getScreenBuffer();
|
||||
int cols = sb.getCols();
|
||||
panel.setSelectionRange(2 * cols + 10, 5 * cols + 20);
|
||||
|
||||
int ox = panel.getRenderOffsetX();
|
||||
int oy = panel.getRenderOffsetY();
|
||||
int cw = panel.cellWidth;
|
||||
int ch = panel.cellHeight;
|
||||
|
||||
int x1 = ox + 10 * cw;
|
||||
int y1 = oy + 2 * ch;
|
||||
int x2 = ox + 21 * cw;
|
||||
int y2 = oy + 6 * ch;
|
||||
int xm = (x1 + x2) / 2;
|
||||
int ym = (y1 + y2) / 2;
|
||||
|
||||
// Test 8 handles
|
||||
assertEquals(TerminalPanel.DragHandle.NW, panel.getDragHandleAt(x1, y1));
|
||||
assertEquals(TerminalPanel.DragHandle.N, panel.getDragHandleAt(xm, y1));
|
||||
assertEquals(TerminalPanel.DragHandle.NE, panel.getDragHandleAt(x2, y1));
|
||||
assertEquals(TerminalPanel.DragHandle.E, panel.getDragHandleAt(x2, ym));
|
||||
assertEquals(TerminalPanel.DragHandle.SE, panel.getDragHandleAt(x2, y2));
|
||||
assertEquals(TerminalPanel.DragHandle.S, panel.getDragHandleAt(xm, y2));
|
||||
assertEquals(TerminalPanel.DragHandle.SW, panel.getDragHandleAt(x1, y2));
|
||||
assertEquals(TerminalPanel.DragHandle.W, panel.getDragHandleAt(x1, ym));
|
||||
|
||||
// Test inside box -> MOVE
|
||||
assertEquals(TerminalPanel.DragHandle.MOVE, panel.getDragHandleAt(xm, ym));
|
||||
|
||||
// Test far outside -> NONE
|
||||
assertEquals(TerminalPanel.DragHandle.NONE, panel.getDragHandleAt(0, 0));
|
||||
assertEquals(TerminalPanel.DragHandle.NONE, panel.getDragHandleAt(x2 + 50, y2 + 50));
|
||||
} catch (HeadlessException ignored) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Cursor mapping for all drag handles")
|
||||
public void testCursorForDragHandles() {
|
||||
try {
|
||||
TerminalPanel panel = createTestPanel();
|
||||
assertEquals(Cursor.NW_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.NW).getType());
|
||||
assertEquals(Cursor.N_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.N).getType());
|
||||
assertEquals(Cursor.NE_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.NE).getType());
|
||||
assertEquals(Cursor.E_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.E).getType());
|
||||
assertEquals(Cursor.SE_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.SE).getType());
|
||||
assertEquals(Cursor.S_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.S).getType());
|
||||
assertEquals(Cursor.SW_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.SW).getType());
|
||||
assertEquals(Cursor.W_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.W).getType());
|
||||
assertEquals(Cursor.MOVE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.MOVE).getType());
|
||||
assertEquals(Cursor.DEFAULT_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.NONE).getType());
|
||||
} catch (HeadlessException ignored) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("trimRectRemainAfterEdit controls whether selection is retained after copy")
|
||||
public void testTrimRectRemainAfterEdit() {
|
||||
try {
|
||||
TerminalPanel panel = createTestPanel();
|
||||
ScreenBuffer sb = panel.getClient().getScreenBuffer();
|
||||
|
||||
sb.getCell(0).ucs4 = 'A';
|
||||
panel.setSelectionRange(0, 5);
|
||||
assertTrue(panel.hasSelection());
|
||||
|
||||
// By default (false), copySelection() should clear the selection
|
||||
Settings.setTrimRectRemainAfterEdit(false);
|
||||
panel.copySelection();
|
||||
assertFalse(panel.hasSelection(), "Selection should be cleared after copy when remainAfterEdit is false");
|
||||
|
||||
// When set to true, copySelection() should preserve the selection
|
||||
Settings.setTrimRectRemainAfterEdit(true);
|
||||
panel.setSelectionRange(0, 5);
|
||||
assertTrue(panel.hasSelection());
|
||||
panel.copySelection();
|
||||
assertTrue(panel.hasSelection(), "Selection should remain active after copy when remainAfterEdit is true");
|
||||
} catch (HeadlessException ignored) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("cutSelection clears unprotected cells, sets FA_MODIFY, and respects remainAfterEdit")
|
||||
public void testCutSelectionBehavior() {
|
||||
try {
|
||||
TerminalPanel panel = createTestPanel();
|
||||
ScreenBuffer sb = panel.getClient().getScreenBuffer();
|
||||
|
||||
// Field at 0: Unprotected field
|
||||
sb.setCellFA(0, (byte) (FA_PRINTABLE | FA_MODIFY));
|
||||
String data = "TESTDATA";
|
||||
for (int i = 0; i < data.length(); i++) {
|
||||
sb.getCell(1 + i).ucs4 = data.charAt(i);
|
||||
}
|
||||
|
||||
// Select the field contents
|
||||
panel.setSelectionRange(1, 8);
|
||||
assertTrue(panel.hasSelection());
|
||||
|
||||
Settings.setTrimRectRemainAfterEdit(false);
|
||||
panel.cutSelection();
|
||||
|
||||
// Characters should be replaced with spaces
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
assertEquals(' ', sb.getCell(i).ucs4, "Cell " + i + " should be erased to space");
|
||||
}
|
||||
// FA_MODIFY should remain or be set
|
||||
assertTrue((sb.getCell(0).ec & FA_MODIFY) != 0, "FA_MODIFY should be set on field attribute");
|
||||
assertFalse(panel.hasSelection(), "Selection should be cleared when remainAfterEdit is false");
|
||||
} catch (HeadlessException ignored) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("KeyBindings exportKmp generates HoD format and round-trips through parseKmp")
|
||||
public void testKeyBindingsExportAndParseRoundTrip() throws Exception {
|
||||
Map<String, String> keymap = new LinkedHashMap<>();
|
||||
keymap.put("ENTER", "ENTER");
|
||||
keymap.put("shift TAB", "TAB");
|
||||
keymap.put("CLEAR", "PAUSE");
|
||||
keymap.put("PF1", "F1");
|
||||
keymap.put("PF13", "shift F1");
|
||||
keymap.put("ESCAPE", "ESCAPE");
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
KeyBindings.exportKmp(writer, keymap);
|
||||
String exported = writer.toString();
|
||||
|
||||
assertTrue(exported.contains("[KeyRemap]"), "Exported KMP must contain [KeyRemap] section header");
|
||||
assertTrue(exported.contains("[enter]"), "Exported KMP must contain HoD mnemonics");
|
||||
assertTrue(exported.contains("[backtab]"), "Exported KMP must contain [backtab]");
|
||||
|
||||
// Roundtrip parse
|
||||
Map<String, String> parsed = KeyBindings.parseKmp(new StringReader(exported));
|
||||
assertNotNull(parsed);
|
||||
assertEquals("ENTER", parsed.get("ENTER"));
|
||||
assertEquals("TAB", parsed.get("shift TAB"));
|
||||
assertEquals("PAUSE", parsed.get("CLEAR"));
|
||||
assertEquals("F1", parsed.get("PF1"));
|
||||
assertEquals("shift F1", parsed.get("PF13"));
|
||||
assertEquals("ESCAPE", parsed.get("ESCAPE"));
|
||||
}
|
||||
}
|
||||
@@ -59,4 +59,49 @@ public class StatusBarTest {
|
||||
// Ignored in headless environments
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatusBarXSystemAndXProt() {
|
||||
try {
|
||||
ConnectionConfig config = new ConnectionConfig("mvs.example.com", 23, TerminalModel.IBM_3279_2, false);
|
||||
Telnet3270Client client = new Telnet3270Client(config);
|
||||
client.getTelnetFSM().setConnectionState(haus.nightmare.lib3270j.ConnectionState.CONNECTED_3270);
|
||||
|
||||
StatusBar statusBar = new StatusBar();
|
||||
statusBar.setClient(client, null);
|
||||
statusBar.updateStatus();
|
||||
|
||||
// Initially not locked
|
||||
assertEquals("", statusBar.getLockStatusText());
|
||||
|
||||
// 1. Setup protected field and type into it -> triggers X PROT
|
||||
client.getScreenBuffer().setFieldAttribute(0, (byte) haus.nightmare.lib3270j.protocol.DS3270Constants.FA_PROTECT);
|
||||
client.getScreenBuffer().setCursorAddress(1);
|
||||
client.typeCharacter('A');
|
||||
statusBar.updateStatus();
|
||||
assertEquals("X PROT", statusBar.getLockStatusText());
|
||||
|
||||
// 2. Reset -> clears X PROT
|
||||
client.reset();
|
||||
statusBar.updateStatus();
|
||||
assertEquals("", statusBar.getLockStatusText());
|
||||
|
||||
// 3. Send Enter -> triggers X SYSTEM
|
||||
client.sendEnter();
|
||||
statusBar.updateStatus();
|
||||
assertEquals("X SYSTEM", statusBar.getLockStatusText());
|
||||
|
||||
// 4. Host unlocks keyboard
|
||||
client.getInputProcessor().setKeyboardLocked(false);
|
||||
statusBar.updateStatus();
|
||||
assertEquals("", statusBar.getLockStatusText());
|
||||
|
||||
// 5. Subsequent Enter -> triggers X SYSTEM again
|
||||
client.sendEnter();
|
||||
statusBar.updateStatus();
|
||||
assertEquals("X SYSTEM", statusBar.getLockStatusText());
|
||||
} catch (HeadlessException e) {
|
||||
// Ignored in headless environments
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.ConnectionState;
|
||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.HeadlessException;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TerminalPanelSelectionTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Selection drag over protected/intensified fields is preserved on mouse release (Bugs 3 & 4)")
|
||||
public void testSelectionOverProtectedFieldsAndReleasePreservation() {
|
||||
try {
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
panel.setSize(800, 600);
|
||||
|
||||
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
|
||||
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
sb.erase(false);
|
||||
|
||||
// Put a protected, intensified selectable field at pos 0 (ISPF header simulation)
|
||||
sb.setCellFA(0, (byte) (FA_PRINTABLE | FA_PROTECT | FA_INT_HIGH_SEL));
|
||||
String headerText = " ISPF PRIMARY OPTION MENU";
|
||||
for (int i = 0; i < headerText.length(); i++) {
|
||||
sb.getCell(1 + i).ucs4 = headerText.charAt(i);
|
||||
sb.getCell(1 + i).ec = (byte) (0x40 + i);
|
||||
}
|
||||
|
||||
panel.setClient(client);
|
||||
|
||||
// Cell dimensions in panel
|
||||
int cellW = panel.cellWidth > 0 ? panel.cellWidth : 10;
|
||||
int cellH = panel.cellHeight > 0 ? panel.cellHeight : 18;
|
||||
int ox = panel.getRenderOffsetX();
|
||||
int oy = panel.getRenderOffsetY();
|
||||
|
||||
MouseListener[] mouseListeners = panel.getMouseListeners();
|
||||
MouseMotionListener[] motionListeners = panel.getMouseMotionListeners();
|
||||
assertTrue(mouseListeners.length > 0);
|
||||
assertTrue(motionListeners.length > 0);
|
||||
|
||||
MouseListener ml = mouseListeners[0];
|
||||
MouseMotionListener mml = motionListeners[0];
|
||||
|
||||
// 1. Mouse Pressed at cell (row 0, col 2)
|
||||
int startX = ox + 2 * cellW + cellW / 2;
|
||||
int startY = oy + cellH / 2;
|
||||
MouseEvent pressEvent = new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
|
||||
0, startX, startY, 1, false, MouseEvent.BUTTON1);
|
||||
ml.mousePressed(pressEvent);
|
||||
|
||||
// 2. Drag to cell (row 0, col 15)
|
||||
int dragX = ox + 15 * cellW + cellW / 2;
|
||||
int dragY = oy + cellH / 2;
|
||||
MouseEvent dragEvent = new MouseEvent(panel, MouseEvent.MOUSE_DRAGGED, System.currentTimeMillis(),
|
||||
0, dragX, dragY, 1, false, MouseEvent.BUTTON1);
|
||||
mml.mouseDragged(dragEvent);
|
||||
|
||||
assertTrue(panel.hasSelection(), "Drag over protected field must create active selection (Bug 3)");
|
||||
|
||||
// 3. Mouse Released at cell (row 0, col 15)
|
||||
MouseEvent releaseEvent = new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(),
|
||||
0, dragX, dragY, 1, false, MouseEvent.BUTTON1);
|
||||
ml.mouseReleased(releaseEvent);
|
||||
|
||||
// Selection MUST NOT be dropped upon mouse release! (Bug 4)
|
||||
assertTrue(panel.hasSelection(), "Selection must remain active after mouse release (Bug 4)");
|
||||
String selected = panel.getSelectedText();
|
||||
assertNotNull(selected);
|
||||
assertTrue(selected.length() > 0, "Selected text must not be empty");
|
||||
|
||||
// 4. A subsequent single click without drag should clear the selection and move the cursor
|
||||
int clickX = ox + 5 * cellW + cellW / 2;
|
||||
int clickY = oy + cellH / 2;
|
||||
MouseEvent clickPress = new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
|
||||
0, clickX, clickY, 1, false, MouseEvent.BUTTON1);
|
||||
ml.mousePressed(clickPress);
|
||||
MouseEvent clickRelease = new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(),
|
||||
0, clickX, clickY, 1, false, MouseEvent.BUTTON1);
|
||||
ml.mouseReleased(clickRelease);
|
||||
|
||||
assertFalse(panel.hasSelection(), "Single click must clear previous selection");
|
||||
assertEquals(5, sb.getCursorAddress(), "Single click must position cursor at clicked cell");
|
||||
|
||||
} catch (HeadlessException e) {
|
||||
// Handled gracefully in headless environments
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -262,4 +262,81 @@ public class ThemeManagerTest {
|
||||
// Handled in headless CI
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUiFontRetrievalAndDefaults() {
|
||||
String prevFamily = Settings.getUiFontFamily();
|
||||
int prevSize = Settings.getUiFontSize();
|
||||
|
||||
try {
|
||||
Settings.setUiFontFamily("Dialog");
|
||||
Settings.setUiFontSize(16);
|
||||
|
||||
Font uiFont = ThemeManager.getUiFont();
|
||||
assertEquals("Dialog", uiFont.getFamily());
|
||||
assertEquals(16, uiFont.getSize());
|
||||
assertEquals(Font.PLAIN, uiFont.getStyle());
|
||||
|
||||
Font boldOffset = ThemeManager.getUiFont(Font.BOLD, 2);
|
||||
assertEquals(18, boldOffset.getSize());
|
||||
assertEquals(Font.BOLD, boldOffset.getStyle());
|
||||
|
||||
Font monoFont = ThemeManager.getMonospacedUiFont();
|
||||
assertEquals(Font.MONOSPACED, monoFont.getName());
|
||||
assertEquals(16, monoFont.getSize());
|
||||
|
||||
// Test applyUIManagerDefaults sets font properties
|
||||
ThemeManager.applyUIManagerDefaults(UITheme.DARK);
|
||||
assertEquals(uiFont, UIManager.get("Button.font"));
|
||||
assertEquals(uiFont, UIManager.get("Label.font"));
|
||||
assertEquals(uiFont, UIManager.get("Menu.font"));
|
||||
assertEquals(uiFont, UIManager.get("Table.font"));
|
||||
assertEquals(monoFont, UIManager.get("TextArea.font"));
|
||||
} finally {
|
||||
Settings.setUiFontFamily(prevFamily);
|
||||
Settings.setUiFontSize(prevSize);
|
||||
ThemeManager.applyUIManagerDefaults(UITheme.DARK);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLabelFontNotIncrementedOnRepeatedThemeApplication() {
|
||||
String prevFamily = Settings.getUiFontFamily();
|
||||
int prevSize = Settings.getUiFontSize();
|
||||
|
||||
try {
|
||||
Settings.setUiFontFamily("Dialog");
|
||||
Settings.setUiFontSize(16);
|
||||
ThemeManager.applyUIManagerDefaults(UITheme.DARK);
|
||||
|
||||
JLabel label = new JLabel("Settings Label");
|
||||
ThemeManager.applyTheme(label);
|
||||
assertEquals(16, label.getFont().getSize(), "Initial label font size must match configured uiFontSize");
|
||||
|
||||
// Repeated theme applications (e.g. subsequent clicks of 'Apply' in Settings dialog)
|
||||
// must never increment font size
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
ThemeManager.applyTheme(label);
|
||||
assertEquals(16, label.getFont().getSize(),
|
||||
"Label font size must remain 16 on subsequent application #" + i);
|
||||
}
|
||||
|
||||
// Test label with custom delta
|
||||
JLabel headerLabel = new JLabel("Header Label");
|
||||
headerLabel.setFont(ThemeManager.getUiFont(Font.BOLD, 3));
|
||||
headerLabel.putClientProperty("uiFontSizeDelta", 3);
|
||||
ThemeManager.applyTheme(headerLabel);
|
||||
assertEquals(19, headerLabel.getFont().getSize(), "Custom header label should have base + 3 size");
|
||||
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
ThemeManager.applyTheme(headerLabel);
|
||||
assertEquals(19, headerLabel.getFont().getSize(),
|
||||
"Header label font size must remain 19 on subsequent application #" + i);
|
||||
}
|
||||
} finally {
|
||||
Settings.setUiFontFamily(prevFamily);
|
||||
Settings.setUiFontSize(prevSize);
|
||||
ThemeManager.applyUIManagerDefaults(UITheme.DARK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import com.ibm.eNetwork.ECL.event.ECLEvent;
|
||||
|
||||
/**
|
||||
* Communications event matching IBM Host On-Demand ECLCommEvent.
|
||||
*/
|
||||
public class ECLCommEvent extends ECLEvent {
|
||||
|
||||
private final boolean status;
|
||||
private final int eventType;
|
||||
|
||||
public ECLCommEvent(Object object, boolean status) {
|
||||
super(object);
|
||||
this.status = status;
|
||||
this.eventType = status ? 1 : 2;
|
||||
}
|
||||
|
||||
public ECLCommEvent(Object object, int eventType, boolean status) {
|
||||
super(object);
|
||||
this.eventType = eventType;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public boolean connected() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
public int getEventType() {
|
||||
return this.eventType;
|
||||
}
|
||||
|
||||
public ECLConnection GetConnection() {
|
||||
Object src = GetSource();
|
||||
if (src instanceof ECLConnection) return (ECLConnection) src;
|
||||
if (src instanceof haus.nightmare.lib3270j.ecl.ECLConnection) {
|
||||
return new ECLConnection((haus.nightmare.lib3270j.ecl.ECLConnection) src);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
/**
|
||||
* Communications notification interface matching IBM Host On-Demand ECLCommNotify.
|
||||
*/
|
||||
public interface ECLCommNotify {
|
||||
void NotifyEvent(ECLConnection conn, boolean connected);
|
||||
default void NotifyStop(ECLConnection conn, int reason) {}
|
||||
default void NotifyError(ECLConnection conn, ECLErr err) {}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* Communications and connection management facade matching IBM Host On-Demand ECLConnection.
|
||||
*/
|
||||
public class ECLConnection extends haus.nightmare.lib3270j.ecl.ECLConnection implements ECLConstants {
|
||||
|
||||
private final List<ECLCommNotify> commNotifies = new CopyOnWriteArrayList<>();
|
||||
|
||||
public ECLConnection() {
|
||||
super();
|
||||
setupListener();
|
||||
}
|
||||
|
||||
public ECLConnection(Properties props) {
|
||||
super(props);
|
||||
setupListener();
|
||||
}
|
||||
|
||||
public ECLConnection(String host, int port) {
|
||||
super(host, port);
|
||||
setupListener();
|
||||
}
|
||||
|
||||
public ECLConnection(haus.nightmare.lib3270j.ecl.ECLSession session) {
|
||||
super(session);
|
||||
setupListener();
|
||||
}
|
||||
|
||||
public ECLConnection(haus.nightmare.lib3270j.ecl.ECLConnection delegate) {
|
||||
super(delegate != null ? delegate.getSession() : null);
|
||||
setupListener();
|
||||
}
|
||||
|
||||
private void setupListener() {
|
||||
RegisterCommEvent(new haus.nightmare.lib3270j.ecl.ECLCommListener() {
|
||||
@Override
|
||||
public void CommNotifyEvent(haus.nightmare.lib3270j.ecl.ECLCommEvent event) {
|
||||
boolean conn = event != null && event.isConnected();
|
||||
for (ECLCommNotify n : commNotifies) {
|
||||
try {
|
||||
n.NotifyEvent(ECLConnection.this, conn);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void RegisterCommEvent(ECLCommNotify notify) {
|
||||
if (notify != null && !commNotifies.contains(notify)) {
|
||||
commNotifies.add(notify);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerCommEvent(ECLCommNotify notify) { RegisterCommEvent(notify); }
|
||||
|
||||
public void UnregisterCommEvent(ECLCommNotify notify) {
|
||||
if (notify != null) {
|
||||
commNotifies.remove(notify);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterCommEvent(ECLCommNotify notify) { UnregisterCommEvent(notify); }
|
||||
|
||||
@Override
|
||||
public void StartCommunication() throws IOException {
|
||||
super.StartCommunication();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void StopCommunication() {
|
||||
super.StopCommunication();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
/**
|
||||
* IBM Host Access Class Library (HACL) common constants.
|
||||
* Conforms 1:1 to IBM Host On-Demand com.ibm.eNetwork.ECL.ECLConstants.
|
||||
*/
|
||||
public interface ECLConstants extends haus.nightmare.lib3270j.ecl.ECLConstants {
|
||||
|
||||
// Presentation space logical planes (HoD bitmask values)
|
||||
int TEXT_PLANE = 1;
|
||||
int COLOR_PLANE = 2;
|
||||
int DBCS_PLANE = 4;
|
||||
int GRID_PLANE = 8;
|
||||
int FIELD_PLANE = 16;
|
||||
int EXFIELD_PLANE = 32;
|
||||
int NLS_PLANE = 64;
|
||||
int HOST_PLANE = 128;
|
||||
int RAWTEXT_PLANE = 256;
|
||||
int COLOR_ATTRIBUTES_PLANE = 512;
|
||||
int ALL_PLANES = 319;
|
||||
|
||||
// Search directions
|
||||
int SEARCH_FORWARD = 1;
|
||||
int SEARCH_BACKWARD = 2;
|
||||
|
||||
// Field filters
|
||||
int ALL_FIELDS = 0;
|
||||
int MODIFIED_FIELD = 1;
|
||||
int ALPHANUM_FIELD = 2;
|
||||
int HILIGHT_FIELD = 16;
|
||||
int PROTECTED_FIELD = 32;
|
||||
int DISPLAYABLE_FIELD = 64;
|
||||
int PENDETECTABLE_FIELD = 128;
|
||||
int UNMODIFIED_FIELD = 256;
|
||||
int NUMERIC_FIELD = 512;
|
||||
int LOLIGHT_FIELD = 4096;
|
||||
int UNPROTECTED_FIELD = 8192;
|
||||
int NON_DISPLAYABLE_FIELD = 16384;
|
||||
int NON_PENDETECTABLE_FIELD = 32768;
|
||||
|
||||
// Standard Mnemonics Constants
|
||||
String ENTER_STR = "[enter]";
|
||||
String CLEAR_STR = "[clear]";
|
||||
String RESET_STR = "[reset]";
|
||||
String TAB_STR = "[tab]";
|
||||
String FWDTAB_STR = "[tab]";
|
||||
String BACKTAB_STR = "[backtab]";
|
||||
String BACKSP_STR = "[backspace]";
|
||||
String DELCHAR_STR = "[delete]";
|
||||
String INSERT_STR = "[insert]";
|
||||
String HOME_STR = "[home]";
|
||||
String NEWLINE_STR = "[newline]";
|
||||
String ENDLINE_STR = "[eof]";
|
||||
String ERASEEOF_STR = "[eraseeof]";
|
||||
String ERASEFLD_STR = "[erasefld]";
|
||||
String ERASEINPUT_STR = "[erinp]";
|
||||
String ATTN_STR = "[attn]";
|
||||
String SYSREQ_STR = "[sysreq]";
|
||||
String DUP_STR = "[dup]";
|
||||
String FLDMRK_STR = "[fieldmark]";
|
||||
String PA1_STR = "[pa1]";
|
||||
String PA2_STR = "[pa2]";
|
||||
String PA3_STR = "[pa3]";
|
||||
String CURUP_STR = "[up]";
|
||||
String CURDOWN_STR = "[down]";
|
||||
String CURLEFT_STR = "[left]";
|
||||
String CURRIGHT_STR = "[right]";
|
||||
String PAGEUP_STR = "[pageup]";
|
||||
String PAGEDWN_STR = "[pagedn]";
|
||||
String COPY_STR = "[copy]";
|
||||
String PASTE_STR = "[paste]";
|
||||
String CUT_STR = "[cut]";
|
||||
String SELECTALL_STR = "[selectall]";
|
||||
|
||||
// Function keys F1 - F24
|
||||
String F1_STR = "[pf1]";
|
||||
String F2_STR = "[pf2]";
|
||||
String F3_STR = "[pf3]";
|
||||
String F4_STR = "[pf4]";
|
||||
String F5_STR = "[pf5]";
|
||||
String F6_STR = "[pf6]";
|
||||
String F7_STR = "[pf7]";
|
||||
String F8_STR = "[pf8]";
|
||||
String F9_STR = "[pf9]";
|
||||
String F10_STR = "[pf10]";
|
||||
String F11_STR = "[pf11]";
|
||||
String F12_STR = "[pf12]";
|
||||
String F13_STR = "[pf13]";
|
||||
String F14_STR = "[pf14]";
|
||||
String F15_STR = "[pf15]";
|
||||
String F16_STR = "[pf16]";
|
||||
String F17_STR = "[pf17]";
|
||||
String F18_STR = "[pf18]";
|
||||
String F19_STR = "[pf19]";
|
||||
String F20_STR = "[pf20]";
|
||||
String F21_STR = "[pf21]";
|
||||
String F22_STR = "[pf22]";
|
||||
String F23_STR = "[pf23]";
|
||||
String F24_STR = "[pf24]";
|
||||
|
||||
// Shift F1 - F24
|
||||
String SHIFT_F1_STR = "[Spf1]";
|
||||
String SHIFT_F2_STR = "[Spf2]";
|
||||
String SHIFT_F3_STR = "[Spf3]";
|
||||
String SHIFT_F4_STR = "[Spf4]";
|
||||
String SHIFT_F5_STR = "[Spf5]";
|
||||
String SHIFT_F6_STR = "[Spf6]";
|
||||
String SHIFT_F7_STR = "[Spf7]";
|
||||
String SHIFT_F8_STR = "[Spf8]";
|
||||
String SHIFT_F9_STR = "[Spf9]";
|
||||
String SHIFT_F10_STR = "[Spf10]";
|
||||
String SHIFT_F11_STR = "[Spf11]";
|
||||
String SHIFT_F12_STR = "[Spf12]";
|
||||
String SHIFT_F13_STR = "[Spf13]";
|
||||
String SHIFT_F14_STR = "[Spf14]";
|
||||
String SHIFT_F15_STR = "[Spf15]";
|
||||
String SHIFT_F16_STR = "[Spf16]";
|
||||
String SHIFT_F17_STR = "[Spf17]";
|
||||
String SHIFT_F18_STR = "[Spf18]";
|
||||
String SHIFT_F19_STR = "[Spf19]";
|
||||
String SHIFT_F20_STR = "[Spf20]";
|
||||
|
||||
// Field attributes
|
||||
short ATTR_PROTECTED = 32;
|
||||
short ATTR_ALPHANUM = 16;
|
||||
short ATTR_DISPLAY1 = 8;
|
||||
short ATTR_DISPLAY2 = 4;
|
||||
short ATTR_MDT = 1;
|
||||
|
||||
// Extended attributes
|
||||
short EXT_ATTR_3270_UNDERLINE = 192;
|
||||
short EXT_ATTR_3270_REVERSE = 128;
|
||||
short EXT_ATTR_3270_BLINK = 64;
|
||||
short EXT_ATTR_3270_EBCDIC = 0;
|
||||
|
||||
// Timeout
|
||||
int NO_TIMEOUT = -1;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
/**
|
||||
* IBM Host Access Class Library (HACL) ECLErr checked exception facade.
|
||||
* Conforms 1:1 to IBM Host On-Demand com.ibm.eNetwork.ECL.ECLErr.
|
||||
*/
|
||||
public class ECLErr extends haus.nightmare.lib3270j.ecl.ECLErr {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ECLErr() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ECLErr(String text) {
|
||||
super(text);
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum) {
|
||||
super(location, msgNum);
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum, String insert) {
|
||||
super(location, msgNum, insert);
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum, String insert1, String insert2) {
|
||||
super(location, msgNum, insert1, insert2);
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum, String insert1, String insert2, boolean log) {
|
||||
super(location, msgNum, insert1, insert2, log);
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum, String insert1, String insert2, String insert3) {
|
||||
super(location, msgNum, insert1, insert2, insert3);
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum, int reasonCode) {
|
||||
super(location, msgNum, reasonCode);
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum, String insert1, int reasonCode) {
|
||||
super(location, msgNum, insert1, reasonCode);
|
||||
}
|
||||
|
||||
public ECLErr(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ECLErr(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
/**
|
||||
* Discrete 3270 field facade matching IBM Host On-Demand ECLField.
|
||||
*/
|
||||
public class ECLField extends haus.nightmare.lib3270j.ecl.ECLField implements ECLConstants {
|
||||
|
||||
private final haus.nightmare.lib3270j.ecl.ECLField delegate;
|
||||
|
||||
public ECLField(haus.nightmare.lib3270j.ecl.ECLField delegate) {
|
||||
super(delegate != null ? delegate.getPS() : null,
|
||||
delegate != null ? delegate.getStart() : 0,
|
||||
delegate != null ? delegate.getDataStart() : 0,
|
||||
delegate != null ? delegate.getEnd() : 0,
|
||||
delegate != null ? delegate.getLength() : 0,
|
||||
(byte) (delegate != null ? delegate.getAttribute() : 0));
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public int GetStart() {
|
||||
return delegate != null ? delegate.GetStart() + 1 : 1;
|
||||
}
|
||||
|
||||
public int getStart() { return GetStart(); }
|
||||
|
||||
public int GetStartRow() {
|
||||
return delegate != null ? delegate.GetStartRow() + 1 : 1;
|
||||
}
|
||||
|
||||
public int getStartRow() { return GetStartRow(); }
|
||||
|
||||
public int GetStartCol() {
|
||||
return delegate != null ? delegate.GetStartCol() + 1 : 1;
|
||||
}
|
||||
|
||||
public int getStartCol() { return GetStartCol(); }
|
||||
|
||||
public int GetEnd() {
|
||||
return delegate != null ? delegate.GetEnd() + 1 : 1;
|
||||
}
|
||||
|
||||
public int getEnd() { return GetEnd(); }
|
||||
|
||||
public int GetEndRow() {
|
||||
return delegate != null ? delegate.GetEndRow() + 1 : 1;
|
||||
}
|
||||
|
||||
public int getEndRow() { return GetEndRow(); }
|
||||
|
||||
public int GetEndCol() {
|
||||
return delegate != null ? delegate.GetEndCol() + 1 : 1;
|
||||
}
|
||||
|
||||
public int getEndCol() { return GetEndCol(); }
|
||||
|
||||
public int GetLength() {
|
||||
return delegate != null ? delegate.GetLength() : 0;
|
||||
}
|
||||
|
||||
public int getLength() { return GetLength(); }
|
||||
|
||||
public String GetString() {
|
||||
return delegate != null ? delegate.getString() : "";
|
||||
}
|
||||
|
||||
public String getString() { return GetString(); }
|
||||
|
||||
public int GetString(char[] buffer, int len) throws ECLErr {
|
||||
if (buffer == null) throw new ECLErr("ECLField:GetString", "ECL0011", "1");
|
||||
String text = GetString();
|
||||
int count = Math.min(len, Math.min(buffer.length, text.length()));
|
||||
for (int i = 0; i < count; i++) {
|
||||
buffer[i] = text.charAt(i);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public void SetString(String text) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
delegate.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetText(String text) throws ECLErr {
|
||||
SetString(text);
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
if (delegate != null) {
|
||||
delegate.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean IsModified() {
|
||||
return delegate != null && delegate.isModified();
|
||||
}
|
||||
|
||||
public boolean isModified() { return IsModified(); }
|
||||
|
||||
public boolean IsProtected() {
|
||||
return delegate != null && delegate.isProtected();
|
||||
}
|
||||
|
||||
public boolean isProtected() { return IsProtected(); }
|
||||
|
||||
public boolean IsNumeric() {
|
||||
return delegate != null && delegate.isNumeric();
|
||||
}
|
||||
|
||||
public boolean isNumeric() { return IsNumeric(); }
|
||||
|
||||
public boolean IsHighIntensity() {
|
||||
return delegate != null && delegate.isHighIntensity();
|
||||
}
|
||||
|
||||
public boolean isHighIntensity() { return IsHighIntensity(); }
|
||||
|
||||
public boolean IsPenDetectable() {
|
||||
return delegate != null && delegate.isPenDetectable();
|
||||
}
|
||||
|
||||
public boolean isPenDetectable() { return IsPenDetectable(); }
|
||||
|
||||
public boolean IsDisplay() {
|
||||
return delegate != null && delegate.isDisplay();
|
||||
}
|
||||
|
||||
public boolean isDisplay() { return IsDisplay(); }
|
||||
|
||||
public short GetAttribute() {
|
||||
return delegate != null ? (short) (delegate.getAttribute() & 0xFF) : 0;
|
||||
}
|
||||
|
||||
public short getAttribute() { return GetAttribute(); }
|
||||
|
||||
public haus.nightmare.lib3270j.ecl.ECLField getDelegate() { return delegate; }
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Collection of 3270 fields matching IBM Host On-Demand ECLFieldList.
|
||||
*/
|
||||
public class ECLFieldList extends Vector<ECLField> implements ECLConstants {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final haus.nightmare.lib3270j.ecl.ECLFieldList delegate;
|
||||
private final ECLPS ps;
|
||||
|
||||
public ECLFieldList(haus.nightmare.lib3270j.ecl.ECLFieldList delegate, ECLPS ps) {
|
||||
this.delegate = delegate;
|
||||
this.ps = ps;
|
||||
refreshFields();
|
||||
}
|
||||
|
||||
public ECLFieldList(haus.nightmare.lib3270j.ecl.ECLFieldList delegate) {
|
||||
this(delegate, null);
|
||||
}
|
||||
|
||||
private void refreshFields() {
|
||||
clear();
|
||||
if (delegate != null) {
|
||||
delegate.refresh();
|
||||
for (haus.nightmare.lib3270j.ecl.ECLField f : delegate.getFields()) {
|
||||
add(new ECLField(f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh() {
|
||||
refreshFields();
|
||||
}
|
||||
|
||||
public void Refresh(int planes) throws ECLErr {
|
||||
refreshFields();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
refreshFields();
|
||||
}
|
||||
|
||||
public int GetFieldCount() {
|
||||
return size();
|
||||
}
|
||||
|
||||
public int getFieldCount() { return GetFieldCount(); }
|
||||
|
||||
public ECLField GetFirstField() {
|
||||
return GetFirstField(0);
|
||||
}
|
||||
|
||||
public ECLField GetFirstField(int filter) {
|
||||
for (ECLField f : this) {
|
||||
if (matchesFilter(f, filter)) return f;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ECLField GetNextField(ECLField current) {
|
||||
return GetNextField(current, 0);
|
||||
}
|
||||
|
||||
public ECLField GetNextField(ECLField current, int filter) {
|
||||
if (current == null) return GetFirstField(filter);
|
||||
int idx = indexOf(current);
|
||||
if (idx >= 0) {
|
||||
for (int i = idx + 1; i < size(); i++) {
|
||||
ECLField f = get(i);
|
||||
if (matchesFilter(f, filter)) return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ECLField FindField(int pos) throws ECLErr {
|
||||
if (ps != null && (pos < 1 || pos > ps.GetSize())) {
|
||||
throw new ECLErr("ECLFieldList:FindField", "ECL0010", "1", String.valueOf(pos));
|
||||
}
|
||||
for (ECLField f : this) {
|
||||
int start = f.GetStart();
|
||||
int end = f.GetEnd();
|
||||
if (start <= end) {
|
||||
if (pos >= start && pos <= end) return f;
|
||||
} else {
|
||||
// Wrapped field
|
||||
if (pos >= start || pos <= end) return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ECLField FindField(int row, int col) throws ECLErr {
|
||||
if (ps == null) return null;
|
||||
int pos = ps.ConvertRowColToPos(row, col);
|
||||
return FindField(pos);
|
||||
}
|
||||
|
||||
private boolean matchesFilter(ECLField f, int filter) {
|
||||
if (filter == 0 || filter == ALL_FIELDS) return true;
|
||||
if ((filter & MODIFIED_FIELD) != 0 && !f.IsModified()) return false;
|
||||
if ((filter & PROTECTED_FIELD) != 0 && !f.IsProtected()) return false;
|
||||
if ((filter & UNPROTECTED_FIELD) != 0 && f.IsProtected()) return false;
|
||||
if ((filter & NUMERIC_FIELD) != 0 && !f.IsNumeric()) return false;
|
||||
if ((filter & HILIGHT_FIELD) != 0 && !f.IsHighIntensity()) return false;
|
||||
if ((filter & DISPLAYABLE_FIELD) != 0 && !f.IsDisplay()) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Host Print Session facade matching IBM Host On-Demand ECLHostPrintSession.
|
||||
*/
|
||||
public class ECLHostPrintSession extends ECLSession {
|
||||
|
||||
public static final String SESSION_PRINT_SESSION_LABEL_DEFAULT = "";
|
||||
public static final String SESSION_PRINT_SESSION_TYPE_DEFAULT = "5";
|
||||
public static final String SESSION_PRINT_SESSION_HOST_DEFAULT = "";
|
||||
public static final String SESSION_PRINT_SESSION_HOST_PORT_DEFAULT = "23";
|
||||
public static final String SESSION_PRINT_SESSION_LU_NAME_DEFAULT = "";
|
||||
public static final String SESSION_PRINT_SESSION_AUTOCONNECT_DEFAULT = "true";
|
||||
public static final String SESSION_PRINT_SESSION_AUTORECONNECT_DEFAULT = "true";
|
||||
public static final String SESSION_LU_TYPE = "printerLUType";
|
||||
|
||||
public ECLHostPrintSession(Properties properties) throws ECLErr {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public ECLHostPrintSession() throws ECLErr {
|
||||
super();
|
||||
}
|
||||
|
||||
public void cancelPrintJob() {
|
||||
ECLPS ps = GetPS();
|
||||
if (ps != null) {
|
||||
try {
|
||||
ps.SendKeys("[cancelpjob]");
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelPrintJob() {
|
||||
cancelPrintJob();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* Operator Information Area facade matching IBM Host On-Demand ECLOIA.
|
||||
*/
|
||||
public class ECLOIA implements ECLConstants {
|
||||
|
||||
private final haus.nightmare.lib3270j.ecl.ECLOIA delegate;
|
||||
private final List<ECLOIANotify> oiaNotifies = new CopyOnWriteArrayList<>();
|
||||
|
||||
public ECLOIA(haus.nightmare.lib3270j.ecl.ECLOIA delegate) {
|
||||
this.delegate = delegate;
|
||||
if (delegate != null) {
|
||||
delegate.RegisterOIAEvent(new haus.nightmare.lib3270j.ecl.ECLOIAListener() {
|
||||
@Override
|
||||
public void OIANotifyEvent(haus.nightmare.lib3270j.ecl.ECLOIAEvent event) {
|
||||
ECLOIAEvent facadeEvt = new ECLOIAEvent(event);
|
||||
for (ECLOIANotify n : oiaNotifies) {
|
||||
try {
|
||||
n.OIANotifyEvent(facadeEvt);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterOIAEvent(ECLOIANotify notify) {
|
||||
if (notify != null && !oiaNotifies.contains(notify)) {
|
||||
oiaNotifies.add(notify);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerOIAEvent(ECLOIANotify notify) { RegisterOIAEvent(notify); }
|
||||
|
||||
public void RegisterOIAEvent(haus.nightmare.lib3270j.ecl.ECLOIAListener listener) {
|
||||
if (delegate != null) {
|
||||
delegate.RegisterOIAEvent(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterOIAEvent(ECLOIANotify notify) {
|
||||
if (notify != null) {
|
||||
oiaNotifies.remove(notify);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterOIAEvent(ECLOIANotify notify) { UnregisterOIAEvent(notify); }
|
||||
|
||||
public void UnregisterOIAEvent(haus.nightmare.lib3270j.ecl.ECLOIAListener listener) {
|
||||
if (delegate != null) {
|
||||
delegate.UnregisterOIAEvent(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean IsAlphanumeric() {
|
||||
return delegate != null && delegate.IsAlphanumeric();
|
||||
}
|
||||
|
||||
public boolean isAlphanumeric() { return IsAlphanumeric(); }
|
||||
|
||||
public boolean IsNumeric() {
|
||||
return delegate != null && delegate.IsNumeric();
|
||||
}
|
||||
|
||||
public boolean isNumeric() { return IsNumeric(); }
|
||||
|
||||
public boolean IsDBCS() {
|
||||
return delegate != null && delegate.IsDBCS();
|
||||
}
|
||||
|
||||
public boolean isDBCS() { return IsDBCS(); }
|
||||
|
||||
public boolean IsInsertMode() {
|
||||
return delegate != null && delegate.IsInsertMode();
|
||||
}
|
||||
|
||||
public boolean isInsertMode() { return IsInsertMode(); }
|
||||
|
||||
public boolean IsCommError() {
|
||||
return delegate != null && delegate.IsCommError();
|
||||
}
|
||||
|
||||
public boolean isCommError() { return IsCommError(); }
|
||||
|
||||
public boolean IsKeyLocked() {
|
||||
return delegate != null && delegate.isKeyLocked();
|
||||
}
|
||||
|
||||
public boolean isKeyLocked() { return IsKeyLocked(); }
|
||||
|
||||
public boolean IsMessageWaiting() {
|
||||
return delegate != null && delegate.IsMessageWaiting();
|
||||
}
|
||||
|
||||
public boolean isMessageWaiting() { return IsMessageWaiting(); }
|
||||
|
||||
public int GetInputInhibited() {
|
||||
return delegate != null ? delegate.getInputInhibited() : 0;
|
||||
}
|
||||
|
||||
public int getInputInhibited() { return GetInputInhibited(); }
|
||||
|
||||
public int GetAlphanumericType() {
|
||||
return delegate != null ? delegate.getAlphanumericType() : 0;
|
||||
}
|
||||
|
||||
public int getAlphanumericType() { return GetAlphanumericType(); }
|
||||
|
||||
public boolean WaitForInput(long timeout) {
|
||||
return delegate != null && delegate.WaitForInput(timeout);
|
||||
}
|
||||
|
||||
public boolean WaitForSystemAvailable(long timeout) {
|
||||
return delegate != null && delegate.WaitForSystemAvailable(timeout);
|
||||
}
|
||||
|
||||
public boolean WaitForAppAvailable(long timeout) {
|
||||
return delegate != null && delegate.WaitForAppAvailable(timeout);
|
||||
}
|
||||
|
||||
public boolean WaitForTransition(long timeout) {
|
||||
return delegate != null && delegate.WaitForTransition(timeout);
|
||||
}
|
||||
|
||||
public haus.nightmare.lib3270j.ecl.ECLOIA getDelegate() { return delegate; }
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import com.ibm.eNetwork.ECL.event.ECLEvent;
|
||||
|
||||
/**
|
||||
* Event object dispatched on Operator Information Area (ECLOIA) status changes matching IBM HoD.
|
||||
*/
|
||||
public class ECLOIAEvent extends ECLEvent {
|
||||
|
||||
public String data;
|
||||
public long newState;
|
||||
public long changedMask;
|
||||
|
||||
private int inputInhibited = 0;
|
||||
private int alphanumericType = 0;
|
||||
private boolean insertMode = false;
|
||||
|
||||
public ECLOIAEvent(Object source, long newState, long changedMask, String data) {
|
||||
super(source);
|
||||
this.newState = newState;
|
||||
this.changedMask = changedMask;
|
||||
this.data = data != null ? data : "";
|
||||
}
|
||||
|
||||
public ECLOIAEvent(Object source, int eventType, int inputInhibited, int alphanumericType,
|
||||
boolean insertMode, String statusString) {
|
||||
super(source);
|
||||
this.inputInhibited = inputInhibited;
|
||||
this.alphanumericType = alphanumericType;
|
||||
this.insertMode = insertMode;
|
||||
this.data = statusString != null ? statusString : "";
|
||||
this.newState = inputInhibited;
|
||||
this.changedMask = eventType;
|
||||
}
|
||||
|
||||
public ECLOIAEvent(haus.nightmare.lib3270j.ecl.ECLOIAEvent delegate) {
|
||||
super(delegate != null ? delegate.getSource() : null);
|
||||
if (delegate != null) {
|
||||
this.inputInhibited = delegate.getInputInhibited();
|
||||
this.alphanumericType = delegate.getAlphanumericType();
|
||||
this.insertMode = delegate.isInsertMode();
|
||||
this.data = delegate.getStatusString();
|
||||
this.newState = delegate.getInputInhibited();
|
||||
this.changedMask = delegate.getEventType();
|
||||
}
|
||||
}
|
||||
|
||||
public ECLOIA GetOIA() {
|
||||
Object src = GetSource();
|
||||
if (src instanceof ECLOIA) {
|
||||
return (ECLOIA) src;
|
||||
} else if (src instanceof haus.nightmare.lib3270j.ecl.ECLOIA) {
|
||||
return new ECLOIA((haus.nightmare.lib3270j.ecl.ECLOIA) src);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ECLOIA getOIA() { return GetOIA(); }
|
||||
|
||||
public int GetInputInhibited() { return inputInhibited; }
|
||||
public int getInputInhibited() { return inputInhibited; }
|
||||
|
||||
public int GetAlphanumericType() { return alphanumericType; }
|
||||
public int getAlphanumericType() { return alphanumericType; }
|
||||
|
||||
public boolean IsInsertMode() { return insertMode; }
|
||||
public boolean isInsertMode() { return insertMode; }
|
||||
|
||||
public boolean IsInputInhibited() { return inputInhibited != 0; }
|
||||
public boolean isInputInhibited() { return IsInputInhibited(); }
|
||||
|
||||
public String GetStatusString() { return data; }
|
||||
public String getStatusString() { return data; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ECLOIAEvent: changedMask=0x" + Long.toHexString(this.changedMask) +
|
||||
", newState=0x" + Long.toHexString(this.newState) + ", data=" + this.data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
/**
|
||||
* Operator Information Area notification interface matching IBM Host On-Demand ECLOIANotify.
|
||||
*/
|
||||
public interface ECLOIANotify {
|
||||
void NotifyEvent(ECLOIA oia, int inhibitReason, int state, String text);
|
||||
default void NotifyStop(ECLOIA oia, int reason) {}
|
||||
default void NotifyError(ECLOIA oia, ECLErr err) {}
|
||||
default void OIANotifyStop(ECLOIA oia, int reason) { NotifyStop(oia, reason); }
|
||||
default void OIANotifyError(ECLOIA oia, ECLErr err) { NotifyError(oia, err); }
|
||||
default void OIANotifyEvent(ECLOIAEvent event) {
|
||||
if (event != null) {
|
||||
NotifyEvent(event.GetOIA(), event.GetInputInhibited(), 0, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import com.ibm.eNetwork.ECL.event.ECLPSListener;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Presentation Space facade matching IBM Host On-Demand ECLPS.
|
||||
* Provides 1-based indexing, method signature parity, and checked ECLErr exception semantics.
|
||||
*/
|
||||
public class ECLPS implements ECLConstants, Serializable, Cloneable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final haus.nightmare.lib3270j.ecl.ECLPS delegate;
|
||||
|
||||
public ECLPS(haus.nightmare.lib3270j.ecl.ECLPS delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public ECLPS(haus.nightmare.lib3270j.ecl.ECLSession session) {
|
||||
this(session != null ? session.GetPS() : null);
|
||||
}
|
||||
|
||||
public ECLPS(ECLSession session) {
|
||||
this(session != null ? session.getDelegate() : null);
|
||||
}
|
||||
|
||||
public haus.nightmare.lib3270j.ecl.ECLPS getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
// ========== Dimensions & Position Conversion (1-based) ==========
|
||||
|
||||
public int GetSize() {
|
||||
return delegate != null ? delegate.GetSize() : 0;
|
||||
}
|
||||
|
||||
public int getSize() { return GetSize(); }
|
||||
|
||||
public int GetRows() {
|
||||
return delegate != null ? delegate.GetRows() : 0;
|
||||
}
|
||||
|
||||
public int getRows() { return GetRows(); }
|
||||
|
||||
public int GetCols() {
|
||||
return delegate != null ? delegate.GetCols() : 0;
|
||||
}
|
||||
|
||||
public int getCols() { return GetCols(); }
|
||||
|
||||
public int GetSizeRows() { return GetRows(); }
|
||||
public int GetSizeCols() { return GetCols(); }
|
||||
|
||||
public int ConvertRowColToPos(int row, int col) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
return delegate.ConvertRowColToPos(row, col);
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int convertRowColToPos(int row, int col) throws ECLErr {
|
||||
return ConvertRowColToPos(row, col);
|
||||
}
|
||||
|
||||
public int ConvertPosToRow(int pos) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
return delegate.ConvertPosToRow(pos);
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int convertPosToRow(int pos) throws ECLErr {
|
||||
return ConvertPosToRow(pos);
|
||||
}
|
||||
|
||||
public int ConvertPosToCol(int pos) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
return delegate.ConvertPosToCol(pos);
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int convertPosToCol(int pos) throws ECLErr {
|
||||
return ConvertPosToCol(pos);
|
||||
}
|
||||
|
||||
public void ConvertPosToRowCol(int pos, int[] rowCol) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
delegate.ConvertPosToRowCol(pos, rowCol);
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void convertPosToRowCol(int pos, int[] rowCol) throws ECLErr {
|
||||
ConvertPosToRowCol(pos, rowCol);
|
||||
}
|
||||
|
||||
public int[] ConvertPosToRowCol(int pos) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
return delegate.ConvertPosToRowCol(pos);
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
return new int[]{1, 1};
|
||||
}
|
||||
|
||||
public int[] convertPosToRowCol(int pos) throws ECLErr {
|
||||
return ConvertPosToRowCol(pos);
|
||||
}
|
||||
|
||||
// ========== Cursor Operations (1-based) ==========
|
||||
|
||||
public int GetCursorPos() {
|
||||
return delegate != null ? delegate.GetCursorPos() : 1;
|
||||
}
|
||||
|
||||
public int getCursorPos() { return GetCursorPos(); }
|
||||
|
||||
public int GetCursorRow() {
|
||||
return delegate != null ? delegate.GetCursorRow() : 1;
|
||||
}
|
||||
|
||||
public int getCursorRow() { return GetCursorRow(); }
|
||||
|
||||
public int GetCursorCol() {
|
||||
return delegate != null ? delegate.GetCursorCol() : 1;
|
||||
}
|
||||
|
||||
public int getCursorCol() { return GetCursorCol(); }
|
||||
|
||||
public void SetCursorPos(int pos) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
delegate.SetCursorPos(pos);
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setCursorPos(int pos) throws ECLErr {
|
||||
SetCursorPos(pos);
|
||||
}
|
||||
|
||||
public void SetCursorPos(int row, int col) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
delegate.SetCursorPos(row, col);
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setCursorPos(int row, int col) throws ECLErr {
|
||||
SetCursorPos(row, col);
|
||||
}
|
||||
|
||||
// ========== GetString & SetString Operations (1-based) ==========
|
||||
|
||||
public int GetString(char[] buffer, int len, int pos) throws ECLErr {
|
||||
return GetString(buffer, len, pos, len);
|
||||
}
|
||||
|
||||
public int GetString(char[] buffer, int len, int pos, int length) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
return delegate.GetString(buffer, len, pos, length);
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int GetString(char[] buffer, int len, int row, int col, int length) throws ECLErr {
|
||||
return GetString(buffer, len, ConvertRowColToPos(row, col), length);
|
||||
}
|
||||
|
||||
public String GetString(int pos, int length) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
return delegate.GetString(pos, length);
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getString(int pos, int length) throws ECLErr {
|
||||
return GetString(pos, length);
|
||||
}
|
||||
|
||||
public String GetString(int row, int col, int length) throws ECLErr {
|
||||
return GetString(ConvertRowColToPos(row, col), length);
|
||||
}
|
||||
|
||||
public String getString(int row, int col, int length) throws ECLErr {
|
||||
return GetString(row, col, length);
|
||||
}
|
||||
|
||||
public void SetString(String text, int pos) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
delegate.SetString(text, pos);
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setString(String text, int pos) throws ECLErr {
|
||||
SetString(text, pos);
|
||||
}
|
||||
|
||||
public void SetString(String text, int row, int col) throws ECLErr {
|
||||
SetString(text, ConvertRowColToPos(row, col));
|
||||
}
|
||||
|
||||
public void setString(String text, int row, int col) throws ECLErr {
|
||||
SetString(text, row, col);
|
||||
}
|
||||
|
||||
public void SetString(String text) throws ECLErr {
|
||||
SetString(text, GetCursorPos());
|
||||
}
|
||||
|
||||
public void setString(String text) throws ECLErr {
|
||||
SetString(text);
|
||||
}
|
||||
|
||||
public void SetText(String text, int pos) throws ECLErr {
|
||||
SetString(text, pos);
|
||||
}
|
||||
|
||||
public void SetText(String text, int row, int col) throws ECLErr {
|
||||
SetString(text, row, col);
|
||||
}
|
||||
|
||||
public void setText(String text, int pos) {
|
||||
if (delegate != null) delegate.setText(text, pos);
|
||||
}
|
||||
|
||||
public void setText(String text, int row, int col) {
|
||||
if (delegate != null) delegate.setText(text, row, col);
|
||||
}
|
||||
|
||||
// ========== Search Operations (1-based) ==========
|
||||
|
||||
public int SearchText(String text, int dir, int startPos) {
|
||||
return delegate != null ? delegate.SearchText(text, dir, startPos) : 0;
|
||||
}
|
||||
|
||||
public int SearchText(String text, int dir) {
|
||||
return delegate != null ? delegate.SearchText(text, dir) : 0;
|
||||
}
|
||||
|
||||
public int SearchText(String text) {
|
||||
return delegate != null ? delegate.SearchText(text) : 0;
|
||||
}
|
||||
|
||||
public int SearchText(String text, int row, int col, int dir) throws ECLErr {
|
||||
return SearchText(text, dir, ConvertRowColToPos(row, col));
|
||||
}
|
||||
|
||||
public int SearchString(String text, int dir, int startPos) {
|
||||
return SearchText(text, dir, startPos);
|
||||
}
|
||||
|
||||
public int SearchString(String text, int dir) {
|
||||
return SearchText(text, dir);
|
||||
}
|
||||
|
||||
public int SearchString(String text) {
|
||||
return SearchText(text);
|
||||
}
|
||||
|
||||
public int SearchString(String text, int row, int col, int dir) throws ECLErr {
|
||||
return SearchText(text, row, col, dir);
|
||||
}
|
||||
|
||||
public int SearchPS(String text) {
|
||||
return delegate != null ? delegate.SearchPS(text) : 0;
|
||||
}
|
||||
|
||||
public int SearchPS(String text, int startPos) {
|
||||
return delegate != null ? delegate.SearchPS(text, startPos) : 0;
|
||||
}
|
||||
|
||||
public int SearchPSExt(String text, int startPos, int endPos, int dir, boolean ignoreCase, boolean wrap) {
|
||||
return delegate != null ? delegate.SearchPSExt(text, startPos, endPos, dir, ignoreCase, wrap) : 0;
|
||||
}
|
||||
|
||||
// ========== Keystrokes & Mnemonics ==========
|
||||
|
||||
public void SendKeys(String keys) {
|
||||
if (delegate != null) delegate.SendKeys(keys);
|
||||
}
|
||||
|
||||
public void sendKeys(String keys) { SendKeys(keys); }
|
||||
|
||||
public void SendKeys(String keys, int pos) {
|
||||
if (delegate != null) delegate.SendKeys(keys, pos);
|
||||
}
|
||||
|
||||
public void sendKeys(String keys, int pos) { SendKeys(keys, pos); }
|
||||
|
||||
public void SendKeys(String keys, int row, int col) {
|
||||
if (delegate != null) delegate.SendKeys(keys, row, col);
|
||||
}
|
||||
|
||||
public void sendKeys(String keys, int row, int col) { SendKeys(keys, row, col); }
|
||||
|
||||
public void SendCharacters(String keys) {
|
||||
if (delegate != null) delegate.SendCharacters(keys);
|
||||
}
|
||||
|
||||
public void sendCharacters(String keys) { SendCharacters(keys); }
|
||||
|
||||
public void SendCharacters(String keys, int delayMs) {
|
||||
if (delegate != null) delegate.SendCharacters(keys, delayMs);
|
||||
}
|
||||
|
||||
public void sendCharacters(String keys, int delayMs) { SendCharacters(keys, delayMs); }
|
||||
|
||||
public void SendCharacters(String keys, int pos, int delayMs) {
|
||||
if (delegate != null) delegate.SendCharacters(keys, pos, delayMs);
|
||||
}
|
||||
|
||||
public void sendCharacters(String keys, int pos, int delayMs) { SendCharacters(keys, pos, delayMs); }
|
||||
|
||||
public void SendCharacters(String keys, int row, int col, int delayMs) {
|
||||
if (delegate != null) delegate.SendCharacters(keys, row, col, delayMs);
|
||||
}
|
||||
|
||||
public void sendCharacters(String keys, int row, int col, int delayMs) { SendCharacters(keys, row, col, delayMs); }
|
||||
|
||||
public String[] GetSendKeyMnemonics() {
|
||||
return delegate != null ? delegate.GetSendKeyMnemonics() : new String[0];
|
||||
}
|
||||
|
||||
public String[] getSendKeyMnemonics() { return GetSendKeyMnemonics(); }
|
||||
|
||||
// ========== Field Operations ==========
|
||||
|
||||
public ECLFieldList GetFieldList() {
|
||||
haus.nightmare.lib3270j.ecl.ECLFieldList fl = delegate != null ? delegate.GetFieldList() : null;
|
||||
return new ECLFieldList(fl, this);
|
||||
}
|
||||
|
||||
public ECLFieldList getFieldList() { return GetFieldList(); }
|
||||
|
||||
public ECLField GetField(int pos) throws ECLErr {
|
||||
return GetFieldList().FindField(pos);
|
||||
}
|
||||
|
||||
public ECLField getField(int pos) throws ECLErr { return GetField(pos); }
|
||||
|
||||
public ECLField GetField(int row, int col) throws ECLErr {
|
||||
return GetFieldList().FindField(row, col);
|
||||
}
|
||||
|
||||
public ECLField getField(int row, int col) throws ECLErr { return GetField(row, col); }
|
||||
|
||||
public ECLField GetFirstField() {
|
||||
return GetFieldList().GetFirstField();
|
||||
}
|
||||
|
||||
public ECLField getFirstField() { return GetFirstField(); }
|
||||
|
||||
public ECLField GetNextField(ECLField prev) {
|
||||
return GetFieldList().GetNextField(prev);
|
||||
}
|
||||
|
||||
public ECLField getNextField(ECLField prev) { return GetNextField(prev); }
|
||||
|
||||
// ========== Listener Overloads for HoD Interfaces ==========
|
||||
|
||||
public void RegisterPSEvent(ECLPSNotify notify) {
|
||||
if (delegate != null) delegate.RegisterPSEvent(notify);
|
||||
}
|
||||
|
||||
public void registerPSEvent(ECLPSNotify notify) { RegisterPSEvent(notify); }
|
||||
|
||||
public void RegisterPSEvent(ECLPSNotify notify, int eventType) {
|
||||
if (delegate != null) delegate.RegisterPSEvent(notify, eventType);
|
||||
}
|
||||
|
||||
public void registerPSEvent(ECLPSNotify notify, int eventType) { RegisterPSEvent(notify, eventType); }
|
||||
|
||||
public void RegisterPSEvent(ECLPSNotify notify, ECLScreenDesc desc) {
|
||||
if (delegate != null) delegate.RegisterPSEvent(notify, desc);
|
||||
}
|
||||
|
||||
public void registerPSEvent(ECLPSNotify notify, ECLScreenDesc desc) { RegisterPSEvent(notify, desc); }
|
||||
|
||||
public void RegisterPSEvent(ECLPSNotify notify, ECLScreenDesc desc, int eventType) {
|
||||
if (delegate != null) delegate.RegisterPSEvent(notify, desc, eventType);
|
||||
}
|
||||
|
||||
public void registerPSEvent(ECLPSNotify notify, ECLScreenDesc desc, int eventType) { RegisterPSEvent(notify, desc, eventType); }
|
||||
|
||||
public void UnregisterPSEvent(ECLPSNotify notify) {
|
||||
if (delegate != null) delegate.UnregisterPSEvent(notify);
|
||||
}
|
||||
|
||||
public void unregisterPSEvent(ECLPSNotify notify) { UnregisterPSEvent(notify); }
|
||||
|
||||
public void RegisterPSEvent(ECLPSListener listener) {
|
||||
if (delegate != null) delegate.RegisterPSEvent(listener);
|
||||
}
|
||||
|
||||
public void registerPSEvent(ECLPSListener listener) { RegisterPSEvent(listener); }
|
||||
|
||||
public void UnregisterPSEvent(ECLPSListener listener) {
|
||||
if (delegate != null) delegate.UnregisterPSEvent(listener);
|
||||
}
|
||||
|
||||
public void unregisterPSEvent(ECLPSListener listener) { UnregisterPSEvent(listener); }
|
||||
|
||||
// ========== Synchronization & Waits ==========
|
||||
|
||||
public boolean WaitForScreen(ECLScreenDesc desc, long timeoutMs) {
|
||||
return delegate != null && delegate.WaitForScreen(desc, timeoutMs);
|
||||
}
|
||||
|
||||
public boolean waitForScreen(ECLScreenDesc desc, long timeoutMs) { return WaitForScreen(desc, timeoutMs); }
|
||||
|
||||
public boolean WaitForScreen(ECLScreenDesc desc) {
|
||||
return delegate != null && delegate.WaitForScreen(desc);
|
||||
}
|
||||
|
||||
public boolean waitForScreen(ECLScreenDesc desc) { return WaitForScreen(desc); }
|
||||
|
||||
public boolean WaitWhileScreen(ECLScreenDesc desc, long timeoutMs) {
|
||||
return delegate != null && delegate.WaitWhileScreen(desc, timeoutMs);
|
||||
}
|
||||
|
||||
public boolean waitWhileScreen(ECLScreenDesc desc, long timeoutMs) { return WaitWhileScreen(desc, timeoutMs); }
|
||||
|
||||
public boolean WaitWhileScreen(ECLScreenDesc desc) {
|
||||
return delegate != null && delegate.WaitWhileScreen(desc);
|
||||
}
|
||||
|
||||
public boolean waitWhileScreen(ECLScreenDesc desc) { return WaitWhileScreen(desc); }
|
||||
|
||||
public boolean WaitForString(String text, long timeoutMs) {
|
||||
return delegate != null && delegate.WaitForString(text, timeoutMs);
|
||||
}
|
||||
|
||||
public boolean waitForString(String text, long timeoutMs) { return WaitForString(text, timeoutMs); }
|
||||
|
||||
public boolean WaitForString(String text) {
|
||||
return delegate != null && delegate.WaitForString(text);
|
||||
}
|
||||
|
||||
public boolean waitForString(String text) { return WaitForString(text); }
|
||||
|
||||
public boolean WaitForString(String text, int row, int col, long timeoutMs) {
|
||||
return delegate != null && delegate.WaitForString(text, row, col, timeoutMs);
|
||||
}
|
||||
|
||||
public boolean waitForString(String text, int row, int col, long timeoutMs) { return WaitForString(text, row, col, timeoutMs); }
|
||||
|
||||
public boolean WaitForCursor(int row, int col, long timeoutMs) {
|
||||
return delegate != null && delegate.WaitForCursor(row, col, timeoutMs);
|
||||
}
|
||||
|
||||
public boolean waitForCursor(int row, int col, long timeoutMs) { return WaitForCursor(row, col, timeoutMs); }
|
||||
|
||||
// ========== Screen Update Inquiries ==========
|
||||
|
||||
public ECLPSUpdate GetScreen(int startRow, int startCol, int length) throws ECLErr {
|
||||
return GetScreen(startRow, startCol, length, false);
|
||||
}
|
||||
|
||||
public ECLPSUpdate GetScreen(int startRow, int startCol, int length, boolean fillNulls) throws ECLErr {
|
||||
int pos = ConvertRowColToPos(startRow, startCol);
|
||||
char[] buf = new char[length];
|
||||
GetString(buf, length, pos, length);
|
||||
if (fillNulls) {
|
||||
for (int i = 0; i < buf.length; i++) {
|
||||
if (buf[i] == '\0') buf[i] = ' ';
|
||||
}
|
||||
}
|
||||
int endPos = pos + length - 1;
|
||||
int endRow = ConvertPosToRow(endPos);
|
||||
int endCol = ConvertPosToCol(endPos);
|
||||
return new ECLPSUpdate(this, startRow, startCol, endRow, endCol, new String(buf));
|
||||
}
|
||||
|
||||
public int GetScreen(char[] buffer, int len, int row, int col, int length, int plane) {
|
||||
return delegate.GetScreen(buffer, len, row, col, length, plane);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
/**
|
||||
* Presentation space notification interface matching IBM Host On-Demand ECLPSNotify.
|
||||
*/
|
||||
public interface ECLPSNotify {
|
||||
void NotifyEvent(ECLPS ps);
|
||||
default void NotifyStop(ECLPS ps, int reason) {}
|
||||
default void NotifyError(ECLPS ps, ECLErr err) {}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Conforms to IBM Host On-Demand ECLPSUpdate.
|
||||
*/
|
||||
public class ECLPSUpdate implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final haus.nightmare.lib3270j.ecl.ECLPSUpdate delegate;
|
||||
private final int startRow;
|
||||
private final int startCol;
|
||||
private final int endRow;
|
||||
private final int endCol;
|
||||
private final int start;
|
||||
private final int end;
|
||||
private final boolean fullUpdate;
|
||||
private final String text;
|
||||
private final transient ECLPS ps;
|
||||
|
||||
public ECLPSUpdate(ECLPS ps, int startRow, int startCol, int endRow, int endCol,
|
||||
int start, int end, boolean fullUpdate, String text) {
|
||||
this.ps = ps;
|
||||
this.startRow = startRow;
|
||||
this.startCol = startCol;
|
||||
this.endRow = endRow;
|
||||
this.endCol = endCol;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.fullUpdate = fullUpdate;
|
||||
this.text = text != null ? text : "";
|
||||
this.delegate = null;
|
||||
}
|
||||
|
||||
public ECLPSUpdate(ECLPS ps, int startRow, int startCol, int endRow, int endCol, String text) {
|
||||
this(ps, startRow, startCol, endRow, endCol, 0, 0, false, text);
|
||||
}
|
||||
|
||||
public ECLPSUpdate(int startRow, int startCol, int endRow, int endCol, String text) {
|
||||
this(null, startRow, startCol, endRow, endCol, 0, 0, false, text);
|
||||
}
|
||||
|
||||
public ECLPSUpdate(haus.nightmare.lib3270j.ecl.ECLPSUpdate delegate) {
|
||||
this.delegate = delegate;
|
||||
this.ps = (delegate != null && delegate.getPS() != null) ? new ECLPS(delegate.getPS()) : null;
|
||||
this.startRow = delegate != null ? delegate.getStartRow() : 0;
|
||||
this.startCol = delegate != null ? delegate.getStartCol() : 0;
|
||||
this.endRow = delegate != null ? delegate.getEndRow() : 0;
|
||||
this.endCol = delegate != null ? delegate.getEndCol() : 0;
|
||||
this.start = delegate != null ? delegate.getStart() : 0;
|
||||
this.end = delegate != null ? delegate.getEnd() : 0;
|
||||
this.fullUpdate = delegate != null && delegate.isFullUpdate();
|
||||
this.text = delegate != null ? delegate.getText() : "";
|
||||
}
|
||||
|
||||
public ECLPS getPS() { return ps; }
|
||||
public ECLPS GetPS() { return ps; }
|
||||
|
||||
public int getStartRow() { return startRow; }
|
||||
public int GetStartRow() { return startRow + 1; }
|
||||
|
||||
public int getStartCol() { return startCol; }
|
||||
public int GetStartCol() { return startCol + 1; }
|
||||
|
||||
public int getEndRow() { return endRow; }
|
||||
public int GetEndRow() { return endRow + 1; }
|
||||
|
||||
public int getEndCol() { return endCol; }
|
||||
public int GetEndCol() { return endCol + 1; }
|
||||
|
||||
public int getStart() { return start; }
|
||||
public int GetStart() { return start + 1; }
|
||||
|
||||
public int getEnd() { return end; }
|
||||
public int GetEnd() { return end + 1; }
|
||||
|
||||
public boolean isFullUpdate() { return fullUpdate; }
|
||||
public boolean IsFullUpdate() { return fullUpdate; }
|
||||
|
||||
public String getText() { return text; }
|
||||
public String GetText() { return text; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ECLPSUpdate[start=(%d,%d), end=(%d,%d), full=%b]",
|
||||
startRow, startCol, endRow, endCol, fullUpdate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Screen descriptor matching engine facade matching IBM Host On-Demand ECLScreenDesc.
|
||||
*/
|
||||
public class ECLScreenDesc extends haus.nightmare.lib3270j.ecl.ECLScreenDesc implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ECLScreenDesc() {
|
||||
super();
|
||||
}
|
||||
|
||||
public boolean Matches(ECLPS ps, ECLOIA oia) {
|
||||
haus.nightmare.lib3270j.ecl.ECLPS p = (ps != null) ? ps.getDelegate() : null;
|
||||
haus.nightmare.lib3270j.ecl.ECLOIA o = (oia != null) ? oia.getDelegate() : null;
|
||||
return super.Matches(p, o);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
/**
|
||||
* Listener interface for ECLScreenReco matching notifications matching IBM HoD.
|
||||
*/
|
||||
public interface ECLScreenNotify {
|
||||
void screenMatching(ECLScreenRecoEvent event);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Screen recognition facade matching IBM Host On-Demand ECLScreenReco.
|
||||
*/
|
||||
public class ECLScreenReco {
|
||||
|
||||
private final haus.nightmare.lib3270j.ecl.ECLScreenReco delegate;
|
||||
private final ECLPS ps;
|
||||
private final Map<ECLScreenDesc, ECLScreenNotify> notifyMap = new ConcurrentHashMap<>();
|
||||
|
||||
public ECLScreenReco() {
|
||||
this((ECLPS) null);
|
||||
}
|
||||
|
||||
public ECLScreenReco(ECLSession session) {
|
||||
this(session != null ? session.GetPS() : null);
|
||||
}
|
||||
|
||||
public ECLScreenReco(ECLPS ps) {
|
||||
this(ps, (ECLOIA) null);
|
||||
}
|
||||
|
||||
public ECLScreenReco(ECLPS ps, ECLOIA oia) {
|
||||
this.ps = ps;
|
||||
this.delegate = new haus.nightmare.lib3270j.ecl.ECLScreenReco(ps != null ? ps.getDelegate() : null,
|
||||
oia != null ? oia.getDelegate() : null);
|
||||
}
|
||||
|
||||
public ECLScreenReco(haus.nightmare.lib3270j.ecl.ECLScreenReco delegate) {
|
||||
this.delegate = delegate;
|
||||
this.ps = null;
|
||||
}
|
||||
|
||||
public void RegisterScreen(ECLScreenDesc desc, ECLScreenNotify notify) {
|
||||
if (desc != null && notify != null) {
|
||||
notifyMap.put(desc, notify);
|
||||
if (delegate != null) {
|
||||
delegate.RegisterScreen(desc, event -> notify.screenMatching(new ECLScreenRecoEvent(this, desc, ps)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void registerScreen(ECLScreenDesc desc, ECLScreenNotify notify) {
|
||||
RegisterScreen(desc, notify);
|
||||
}
|
||||
|
||||
public void UnregisterScreen(ECLScreenDesc desc) {
|
||||
if (desc != null) {
|
||||
notifyMap.remove(desc);
|
||||
if (delegate != null) {
|
||||
delegate.UnregisterScreen(desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterScreen(ECLScreenDesc desc) {
|
||||
UnregisterScreen(desc);
|
||||
}
|
||||
|
||||
public boolean IsMatch(ECLScreenDesc desc) {
|
||||
if (delegate != null) {
|
||||
return delegate.IsMatch(desc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean IsMatch(ECLPS ps, ECLScreenDesc desc) {
|
||||
if (ps == null) return false;
|
||||
if (desc == null) return true;
|
||||
return desc.Matches(ps, (ECLOIA) null);
|
||||
}
|
||||
|
||||
public haus.nightmare.lib3270j.ecl.ECLScreenReco getDelegate() { return delegate; }
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import com.ibm.eNetwork.ECL.event.ECLEvent;
|
||||
|
||||
/**
|
||||
* Event delivered when an ECLScreenDesc matches the Presentation Space.
|
||||
*/
|
||||
public class ECLScreenRecoEvent extends ECLEvent {
|
||||
|
||||
private final ECLScreenReco source;
|
||||
private final ECLScreenDesc screenDesc;
|
||||
private final ECLPS ps;
|
||||
|
||||
public ECLScreenRecoEvent(ECLScreenReco source, ECLScreenDesc screenDesc, ECLPS ps) {
|
||||
super(source);
|
||||
this.source = source;
|
||||
this.screenDesc = screenDesc;
|
||||
this.ps = ps;
|
||||
}
|
||||
|
||||
public ECLScreenRecoEvent(Object source, ECLPS ps) {
|
||||
super(source);
|
||||
this.source = (source instanceof ECLScreenReco) ? (ECLScreenReco) source : null;
|
||||
this.screenDesc = null;
|
||||
this.ps = ps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object GetSource() { return source != null ? source : super.GetSource(); }
|
||||
|
||||
public ECLScreenReco GetScreenReco() { return source; }
|
||||
public ECLScreenReco getScreenReco() { return source; }
|
||||
|
||||
public ECLScreenDesc GetScreenDesc() { return screenDesc; }
|
||||
public ECLScreenDesc getScreenDesc() { return screenDesc; }
|
||||
|
||||
public ECLPS GetPS() { return ps; }
|
||||
public ECLPS getPS() { return ps; }
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Top-level session facade matching IBM Host On-Demand ECLSession.
|
||||
* Extends ECLConnection and provides access to Presentation Space (ECLPS),
|
||||
* Operator Information Area (ECLOIA), File Transfer (ECLXfer), and Screen Recognition (ECLScreenReco).
|
||||
*/
|
||||
public class ECLSession extends ECLConnection implements ECLConstants {
|
||||
|
||||
public static final String SESSION_NAME = "SESSION_NAME";
|
||||
public static final String SESSION_LABEL = "SESSION_LABEL";
|
||||
public static final String SESSION_TYPE = "SESSION_TYPE";
|
||||
public static final String SESSION_HOST = "SESSION_HOST";
|
||||
public static final String SESSION_HOST_PORT = "SESSION_HOST_PORT";
|
||||
public static final String SESSION_PORT = "SESSION_PORT";
|
||||
public static final String SESSION_PS_SIZE = "SESSION_PS_SIZE";
|
||||
public static final String SESSION_CODE_PAGE = "SESSION_CODE_PAGE";
|
||||
public static final String SESSION_SSL = "SESSION_SSL";
|
||||
public static final String SESSION_SECURITY_PROTOCOL = "SESSION_SECURITY_PROTOCOL";
|
||||
public static final String SESSION_TLS_PROTOCOL_VERSION = "tlsProtocolVersion";
|
||||
public static final String SESSION_LU_NAME = "SESSION_LU_NAME";
|
||||
public static final String SESSION_TN3270E = "SESSION_TN3270E";
|
||||
public static final String SESSION_AUTO_CONNECT = "SESSION_AUTO_CONNECT";
|
||||
public static final String SESSION_AUTORECONNECT = "SESSION_AUTORECONNECT";
|
||||
public static final String SESSION_RECONNECT_RETRIES = "SESSION_RECONNECT_RETRIES";
|
||||
public static final String SESSION_KEEPALIVE = "SESSION_KEEPALIVE";
|
||||
public static final String KEY_KEEPALIVE_TYPE = "keepAliveType";
|
||||
public static final String KEY_KEEPALIVE_TIMEOUT = "keepAliveTimeout";
|
||||
public static final String SESSION_AUTO_SYS_UNLOCK = "autoSysUnlock";
|
||||
public static final String ENABLE_PASTE_FROM_EXCEL = "enablePasteFromExcel";
|
||||
public static final String PASTE_TAB_OPTIONS = "pasteTabOptions";
|
||||
public static final String PASTE_STOP_AT_PROTECTED_LINE = "pasteStopAtProtectedLine";
|
||||
public static final String BLOCK_PASTE = "blockPaste";
|
||||
public static final String PASTE_FIELD_WRAP = "pasteFieldWrap";
|
||||
public static final String PASTE_LINE_WRAP = "pasteLineWrap";
|
||||
public static final String PASTE_WORD_BREAK = "pasteWordBreak";
|
||||
|
||||
private final haus.nightmare.lib3270j.ecl.ECLSession delegate;
|
||||
private final ECLPS ps;
|
||||
private final ECLOIA oia;
|
||||
private final ECLXfer xfer;
|
||||
private final ECLScreenReco screenReco;
|
||||
|
||||
public ECLSession(Properties properties) throws ECLErr {
|
||||
super(properties);
|
||||
this.delegate = new haus.nightmare.lib3270j.ecl.ECLSession(properties);
|
||||
this.ps = new ECLPS(delegate.getPS());
|
||||
this.oia = new ECLOIA(delegate.getOIA());
|
||||
this.xfer = new ECLXfer(delegate.getXfer());
|
||||
this.screenReco = new ECLScreenReco(this.ps, this.oia);
|
||||
}
|
||||
|
||||
public ECLSession() throws ECLErr {
|
||||
this(new Properties());
|
||||
}
|
||||
|
||||
public ECLSession(String host, int port) throws ECLErr {
|
||||
super(host, port);
|
||||
Properties props = new Properties();
|
||||
if (host != null) props.setProperty(SESSION_HOST, host);
|
||||
props.setProperty(SESSION_PORT, String.valueOf(port));
|
||||
this.delegate = new haus.nightmare.lib3270j.ecl.ECLSession(props);
|
||||
this.ps = new ECLPS(delegate.getPS());
|
||||
this.oia = new ECLOIA(delegate.getOIA());
|
||||
this.xfer = new ECLXfer(delegate.getXfer());
|
||||
this.screenReco = new ECLScreenReco(this.ps, this.oia);
|
||||
}
|
||||
|
||||
public ECLSession(haus.nightmare.lib3270j.ecl.ECLSession delegate) {
|
||||
super(delegate);
|
||||
this.delegate = delegate;
|
||||
this.ps = new ECLPS(delegate != null ? delegate.getPS() : null);
|
||||
this.oia = new ECLOIA(delegate != null ? delegate.getOIA() : null);
|
||||
this.xfer = new ECLXfer(delegate != null ? delegate.getXfer() : null);
|
||||
this.screenReco = new ECLScreenReco(this.ps, this.oia);
|
||||
}
|
||||
|
||||
public ECLPS GetPS() {
|
||||
return ps;
|
||||
}
|
||||
|
||||
public ECLPS getPS() {
|
||||
return ps;
|
||||
}
|
||||
|
||||
public ECLOIA GetOIA() {
|
||||
return oia;
|
||||
}
|
||||
|
||||
public ECLOIA getOIA() {
|
||||
return oia;
|
||||
}
|
||||
|
||||
public ECLXfer GetXfer() {
|
||||
return xfer;
|
||||
}
|
||||
|
||||
public ECLXfer getXfer() {
|
||||
return xfer;
|
||||
}
|
||||
|
||||
public ECLScreenReco GetScreenReco() {
|
||||
return screenReco;
|
||||
}
|
||||
|
||||
public ECLScreenReco getScreenReco() {
|
||||
return screenReco;
|
||||
}
|
||||
|
||||
public ECLConnection GetConnection() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ECLConnection getConnection() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public haus.nightmare.lib3270j.ecl.ECLSession getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void StartCommunication() throws IOException {
|
||||
if (delegate != null) {
|
||||
delegate.StartCommunication();
|
||||
} else {
|
||||
super.StartCommunication();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void StopCommunication() {
|
||||
if (delegate != null) {
|
||||
delegate.StopCommunication();
|
||||
} else {
|
||||
super.StopCommunication();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean StartCommunicationWithBlocking(long timeout) throws InterruptedException, ECLErr {
|
||||
return StartCommunicationWithBlocking(timeout, (ECLScreenDesc) null);
|
||||
}
|
||||
|
||||
public boolean startCommunicationWithBlocking(long timeout) throws InterruptedException, ECLErr {
|
||||
return StartCommunicationWithBlocking(timeout);
|
||||
}
|
||||
|
||||
public boolean StartCommunicationWithBlocking(long timeout, ECLScreenDesc desc) throws InterruptedException, ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
return delegate.StartCommunicationWithBlocking(timeout, desc);
|
||||
} catch (IOException e) {
|
||||
throw new ECLErr("ECLSession.StartCommunicationWithBlocking", "ECL0001", e.getMessage());
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean startCommunicationWithBlocking(long timeout, ECLScreenDesc desc) throws InterruptedException, ECLErr {
|
||||
return StartCommunicationWithBlocking(timeout, desc);
|
||||
}
|
||||
|
||||
public boolean RestartCommunicationWithBlocking(long timeout) throws InterruptedException, ECLErr {
|
||||
if (delegate != null) {
|
||||
try {
|
||||
return delegate.RestartCommunicationWithBlocking(timeout);
|
||||
} catch (IOException e) {
|
||||
throw new ECLErr("ECLSession.RestartCommunicationWithBlocking", "ECL0001", e.getMessage());
|
||||
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||
throw new ECLErr(e.getMessage());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean restartCommunicationWithBlocking(long timeout) throws InterruptedException, ECLErr {
|
||||
return RestartCommunicationWithBlocking(timeout);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (delegate != null) {
|
||||
delegate.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.ibm.eNetwork.ECL;
|
||||
|
||||
import com.ibm.eNetwork.ECL.event.ECLXferListener;
|
||||
|
||||
/**
|
||||
* File transfer facade matching IBM Host On-Demand ECLXfer.
|
||||
*/
|
||||
public class ECLXfer {
|
||||
|
||||
private final haus.nightmare.lib3270j.ecl.ECLXfer delegate;
|
||||
|
||||
public ECLXfer(ECLSession session, Boolean bidi) {
|
||||
this.delegate = (session != null && session.getDelegate() != null)
|
||||
? session.getDelegate().GetXfer() : null;
|
||||
}
|
||||
|
||||
public ECLXfer(haus.nightmare.lib3270j.ecl.ECLXfer delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public void SendFile(String localFile, String hostFile, String options) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
int rc = delegate.SendFile(localFile, hostFile, options);
|
||||
if (rc != 0) {
|
||||
throw new ECLErr("ECLXfer:SendFile", "ECL0150", String.valueOf(rc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveFile(String localFile, String hostFile, String options) throws ECLErr {
|
||||
if (delegate != null) {
|
||||
int rc = delegate.ReceiveFile(localFile, hostFile, options);
|
||||
if (rc != 0) {
|
||||
throw new ECLErr("ECLXfer:ReceiveFile", "ECL0150", String.valueOf(rc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel() {
|
||||
if (delegate != null) {
|
||||
delegate.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterXferEvent(ECLXferListener listener) {
|
||||
// Can bridge listener if needed
|
||||
}
|
||||
|
||||
public void UnregisterXferEvent(ECLXferListener listener) {
|
||||
// Can bridge listener if needed
|
||||
}
|
||||
|
||||
public haus.nightmare.lib3270j.ecl.ECLXfer getDelegate() { return delegate; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ibm.eNetwork.ECL.event;
|
||||
|
||||
/**
|
||||
* Base event class matching IBM Host On-Demand ECLEvent.
|
||||
*/
|
||||
public class ECLEvent {
|
||||
private final Object originator;
|
||||
|
||||
public ECLEvent(Object object) {
|
||||
this.originator = object;
|
||||
}
|
||||
|
||||
public Object GetSource() {
|
||||
return this.originator;
|
||||
}
|
||||
|
||||
public Object getSource() {
|
||||
return this.originator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ": " + (this.originator != null ? this.originator.toString() : "null");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.ibm.eNetwork.ECL.event;
|
||||
|
||||
import com.ibm.eNetwork.ECL.ECLPS;
|
||||
import com.ibm.eNetwork.ECL.ECLPSUpdate;
|
||||
|
||||
/**
|
||||
* Presentation Space event class matching IBM Host On-Demand ECLPSEvent.
|
||||
*/
|
||||
public class ECLPSEvent extends ECLEvent {
|
||||
|
||||
public static final int USER_EVENTS = 1;
|
||||
public static final int HOST_EVENTS = 2;
|
||||
public static final int ALL_EVENTS = 3;
|
||||
|
||||
private int type = 0;
|
||||
private int start = 1;
|
||||
private int end = 1;
|
||||
private int startRow = 1;
|
||||
private int startCol = 1;
|
||||
private int endRow = 1;
|
||||
private int endCol = 1;
|
||||
private int oldCursorPos = 1;
|
||||
private int newCursorPos = 1;
|
||||
private boolean fullUpdate = true;
|
||||
private boolean cursorVisible = true;
|
||||
private int ringCounter = 0;
|
||||
private ECLPSUpdate psUpdate = null;
|
||||
private haus.nightmare.lib3270j.ecl.ECLPSEvent delegate = null;
|
||||
|
||||
public ECLPSEvent(Object source) {
|
||||
super(source);
|
||||
if (source instanceof ECLPS) {
|
||||
ECLPS ps = (ECLPS) source;
|
||||
this.end = ps.GetSize();
|
||||
this.endRow = ps.GetRows();
|
||||
this.endCol = ps.GetCols();
|
||||
this.newCursorPos = ps.GetCursorPos();
|
||||
} else if (source instanceof haus.nightmare.lib3270j.ecl.ECLPS) {
|
||||
haus.nightmare.lib3270j.ecl.ECLPS ps = (haus.nightmare.lib3270j.ecl.ECLPS) source;
|
||||
this.end = ps.GetSize();
|
||||
this.endRow = ps.GetRows();
|
||||
this.endCol = ps.GetCols();
|
||||
this.newCursorPos = ps.GetCursorPos();
|
||||
}
|
||||
}
|
||||
|
||||
public ECLPSEvent(Object source, int type) {
|
||||
this(source);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public ECLPSEvent(haus.nightmare.lib3270j.ecl.ECLPSEvent delegate) {
|
||||
super(delegate != null ? delegate.getSource() : null);
|
||||
this.delegate = delegate;
|
||||
if (delegate != null) {
|
||||
this.type = delegate.getType();
|
||||
this.start = delegate.getStart();
|
||||
this.end = delegate.getEnd();
|
||||
this.startRow = delegate.getStartRow();
|
||||
this.startCol = delegate.getStartCol();
|
||||
this.endRow = delegate.getEndRow();
|
||||
this.endCol = delegate.getEndCol();
|
||||
this.oldCursorPos = delegate.getOldCursorAddress() + 1;
|
||||
this.newCursorPos = delegate.getNewCursorAddress() + 1;
|
||||
this.fullUpdate = delegate.isFullUpdate();
|
||||
this.cursorVisible = delegate.isCursorVisible();
|
||||
this.ringCounter = delegate.getRingCounter();
|
||||
if (delegate.getPSUpdate() != null) {
|
||||
this.psUpdate = new ECLPSUpdate(delegate.getPSUpdate());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetType() { return type; }
|
||||
public int getType() { return type; }
|
||||
|
||||
public ECLPS GetPS() {
|
||||
Object src = GetSource();
|
||||
if (src instanceof ECLPS) {
|
||||
return (ECLPS) src;
|
||||
} else if (src instanceof haus.nightmare.lib3270j.ecl.ECLPS) {
|
||||
return new ECLPS((haus.nightmare.lib3270j.ecl.ECLPS) src);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetStart(int start) { this.start = start; }
|
||||
public int GetStart() { return start; }
|
||||
public int getStart() { return start; }
|
||||
|
||||
public int GetStartRow() { return startRow; }
|
||||
public int getStartRow() { return startRow; }
|
||||
|
||||
public int GetStartCol() { return startCol; }
|
||||
public int getStartCol() { return startCol; }
|
||||
|
||||
public void SetEnd(int end) { this.end = end; }
|
||||
public int GetEnd() { return end; }
|
||||
public int getEnd() { return end; }
|
||||
|
||||
public int GetEndRow() { return endRow; }
|
||||
public int getEndRow() { return endRow; }
|
||||
|
||||
public int GetEndCol() { return endCol; }
|
||||
public int getEndCol() { return endCol; }
|
||||
|
||||
public int GetOldCursorPos() { return oldCursorPos; }
|
||||
public int getOldCursorPos() { return oldCursorPos; }
|
||||
|
||||
public int GetNewCursorPos() { return newCursorPos; }
|
||||
public int getNewCursorPos() { return newCursorPos; }
|
||||
|
||||
public boolean IsFullUpdate() { return fullUpdate; }
|
||||
public boolean isFullUpdate() { return fullUpdate; }
|
||||
|
||||
public boolean IsCursorVisible() { return cursorVisible; }
|
||||
public boolean isCursorVisible() { return cursorVisible; }
|
||||
|
||||
public int GetRingCounter() { return ringCounter; }
|
||||
public int getRingCounter() { return ringCounter; }
|
||||
|
||||
public ECLPSUpdate GetPSUpdate() { return psUpdate; }
|
||||
public ECLPSUpdate getPSUpdate() { return psUpdate; }
|
||||
|
||||
public haus.nightmare.lib3270j.ecl.ECLPSEvent getDelegate() { return delegate; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ibm.eNetwork.ECL.event;
|
||||
|
||||
import com.ibm.eNetwork.ECL.ECLErr;
|
||||
import com.ibm.eNetwork.ECL.ECLPS;
|
||||
|
||||
/**
|
||||
* Presentation Space listener interface matching IBM Host On-Demand ECLPSListener.
|
||||
*/
|
||||
public interface ECLPSListener {
|
||||
void PSNotifyEvent(ECLPSEvent event);
|
||||
default void PSNotifyStop(ECLPS ps, int reason) {}
|
||||
default void PSNotifyError(ECLPS ps, ECLErr err) {}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.ibm.eNetwork.ECL.event;
|
||||
|
||||
import com.ibm.eNetwork.ECL.ECLXfer;
|
||||
|
||||
/**
|
||||
* File transfer event matching IBM Host On-Demand ECLXferEvent.
|
||||
*/
|
||||
public class ECLXferEvent extends ECLEvent {
|
||||
|
||||
private final String sourceFile;
|
||||
private final String targetFile;
|
||||
private final boolean receive;
|
||||
private final long unitsDone;
|
||||
private final long unitsToDo;
|
||||
|
||||
public ECLXferEvent(Object source, long unitsDone, String sourceFile, String targetFile, boolean receive) {
|
||||
this(source, unitsDone, -1L, sourceFile, targetFile, receive);
|
||||
}
|
||||
|
||||
public ECLXferEvent(Object source, long unitsDone, long unitsToDo, String sourceFile, String targetFile, boolean receive) {
|
||||
super(source);
|
||||
this.unitsDone = unitsDone;
|
||||
this.unitsToDo = unitsToDo;
|
||||
this.sourceFile = sourceFile;
|
||||
this.targetFile = targetFile;
|
||||
this.receive = receive;
|
||||
}
|
||||
|
||||
public ECLXfer GetECLXfer() {
|
||||
Object src = GetSource();
|
||||
return (src instanceof ECLXfer) ? (ECLXfer) src : null;
|
||||
}
|
||||
|
||||
public String GetSourceFile() { return sourceFile; }
|
||||
public String getSourceFile() { return sourceFile; }
|
||||
|
||||
public String GetTargetFile() { return targetFile; }
|
||||
public String getTargetFile() { return targetFile; }
|
||||
|
||||
public long GetBytesSent() { return unitsDone; }
|
||||
public long getBytesSent() { return unitsDone; }
|
||||
|
||||
public long GetBytesToSend() { return unitsToDo; }
|
||||
public long getBytesToSend() { return unitsToDo; }
|
||||
|
||||
public boolean IsReceive() { return receive; }
|
||||
public boolean isReceive() { return receive; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ibm.eNetwork.ECL.event;
|
||||
|
||||
import com.ibm.eNetwork.ECL.ECLErr;
|
||||
import com.ibm.eNetwork.ECL.ECLXfer;
|
||||
|
||||
/**
|
||||
* File transfer listener interface matching IBM Host On-Demand ECLXferListener.
|
||||
*/
|
||||
public interface ECLXferListener {
|
||||
void XferNotifyEvent(ECLXferEvent event);
|
||||
default void XferNotifyStop(ECLXfer xfer, int reason) {}
|
||||
default void XferNotifyError(ECLXfer xfer, ECLErr err) {}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ibm.eNetwork.ECL.macrovariable;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Compatibility facade for IBM Host On-Demand MacroExpressionParser.
|
||||
*/
|
||||
public class MacroExpressionParser extends haus.nightmare.lib3270j.macro.variable.MacroExpressionParser implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MacroExpressionParser() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ibm.eNetwork.ECL.macrovariable;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Compatibility facade for IBM Host On-Demand MacroVariables.
|
||||
*/
|
||||
public class MacroVariables extends haus.nightmare.lib3270j.macro.variable.MacroVariables implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MacroVariables() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ibm.eNetwork.ECL.screenreco;
|
||||
|
||||
/**
|
||||
* IBM Host On-Demand ECLSDAttrib drop-in facade.
|
||||
*/
|
||||
public class ECLSDAttrib extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDAttrib {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ECLSDAttrib() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ECLSDAttrib(char attrib, int row, int col, int plane) {
|
||||
super(attrib, row, col, plane);
|
||||
}
|
||||
|
||||
public ECLSDAttrib(char attrib, int row, int col, int plane, boolean optional) {
|
||||
super(attrib, row, col, plane, optional);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ibm.eNetwork.ECL.screenreco;
|
||||
|
||||
/**
|
||||
* IBM Host On-Demand ECLSDBlock drop-in facade.
|
||||
*/
|
||||
public class ECLSDBlock extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDBlock {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ECLSDBlock() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ECLSDBlock(int srow, int scol, boolean caseSense, boolean optional) {
|
||||
super(srow, scol, caseSense, optional);
|
||||
}
|
||||
|
||||
public ECLSDBlock(int srow, int scol, int erow, int ecol, boolean caseSense, boolean optional) {
|
||||
super(srow, scol, erow, ecol, caseSense, optional);
|
||||
}
|
||||
|
||||
public ECLSDBlock(String[] textLines, int srow, int scol, boolean caseSense) {
|
||||
super(textLines, srow, scol, caseSense);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ibm.eNetwork.ECL.screenreco;
|
||||
|
||||
/**
|
||||
* IBM Host On-Demand ECLSDBlockString drop-in facade.
|
||||
*/
|
||||
public class ECLSDBlockString extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDBlockString {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ECLSDBlockString() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ECLSDBlockString(String text) {
|
||||
super(text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ibm.eNetwork.ECL.screenreco;
|
||||
|
||||
/**
|
||||
* IBM Host On-Demand ECLSDChecksum drop-in facade.
|
||||
*/
|
||||
public class ECLSDChecksum extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDChecksum {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ECLSDChecksum() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ECLSDChecksum(int value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
public ECLSDChecksum(int value, boolean optional) {
|
||||
super(value, optional);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ibm.eNetwork.ECL.screenreco;
|
||||
|
||||
/**
|
||||
* IBM Host On-Demand ECLSDCursor drop-in facade.
|
||||
*/
|
||||
public class ECLSDCursor extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDCursor {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ECLSDCursor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ECLSDCursor(int row, int col) {
|
||||
super(row, col);
|
||||
}
|
||||
|
||||
public ECLSDCursor(int row, int col, boolean optional) {
|
||||
super(row, col, optional);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ibm.eNetwork.ECL.screenreco;
|
||||
|
||||
/**
|
||||
* IBM Host On-Demand ECLSDFields drop-in facade.
|
||||
*/
|
||||
public class ECLSDFields extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDFields {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ECLSDFields() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ECLSDFields(int num) {
|
||||
super(num);
|
||||
}
|
||||
|
||||
public ECLSDFields(int num, boolean optional) {
|
||||
super(num, optional);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ibm.eNetwork.ECL.screenreco;
|
||||
|
||||
/**
|
||||
* IBM Host On-Demand ECLSDInputFields drop-in facade.
|
||||
*/
|
||||
public class ECLSDInputFields extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDInputFields {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ECLSDInputFields() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ECLSDInputFields(int num) {
|
||||
super(num);
|
||||
}
|
||||
|
||||
public ECLSDInputFields(int num, boolean optional) {
|
||||
super(num, optional);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ibm.eNetwork.ECL.screenreco;
|
||||
|
||||
/**
|
||||
* IBM Host On-Demand ECLSDOIA drop-in facade.
|
||||
*/
|
||||
public class ECLSDOIA extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDOIA {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ECLSDOIA() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ECLSDOIA(int type) {
|
||||
super(type);
|
||||
}
|
||||
|
||||
public ECLSDOIA(int type, boolean optional) {
|
||||
super(type, optional);
|
||||
}
|
||||
|
||||
public ECLSDOIA(String status, boolean optional) {
|
||||
super(status, optional);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.ibm.eNetwork.ECL.screenreco;
|
||||
|
||||
/**
|
||||
* IBM Host On-Demand ECLSDString drop-in facade.
|
||||
*/
|
||||
public class ECLSDString extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDString {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ECLSDString() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ECLSDString(String text) {
|
||||
super(text);
|
||||
}
|
||||
|
||||
public ECLSDString(String text, int row, int col) {
|
||||
super(text, row, col);
|
||||
}
|
||||
|
||||
public ECLSDString(String text, int row, int col, boolean caseSense) {
|
||||
super(text, row, col, caseSense);
|
||||
}
|
||||
|
||||
public ECLSDString(String text, int row, int col, boolean caseSense, boolean optional) {
|
||||
super(text, row, col, caseSense, optional);
|
||||
}
|
||||
|
||||
public ECLSDString(String text, int srow, int scol, int erow, int ecol, boolean caseSense, boolean optional) {
|
||||
super(text, srow, scol, erow, ecol, caseSense, optional);
|
||||
}
|
||||
|
||||
public ECLSDString(String text, int srow, int scol, int erow, int ecol, boolean caseSense, boolean optional, boolean wrap) {
|
||||
super(text, srow, scol, erow, ecol, caseSense, optional, wrap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.ibm.eNetwork.ECL.screenreco;
|
||||
|
||||
/**
|
||||
* IBM Host On-Demand ECLScreenDescriptor drop-in facade.
|
||||
*/
|
||||
public abstract class ECLScreenDescriptor extends haus.nightmare.lib3270j.ecl.screenreco.ECLScreenDescriptor {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected ECLScreenDescriptor() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected ECLScreenDescriptor(boolean optional) {
|
||||
super(optional);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ibm.eNetwork.beans.HOD;
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.ibm.eNetwork.ECL.ECLSession;
|
||||
|
||||
/**
|
||||
* Compatibility facade for IBM Host On-Demand Macro bean.
|
||||
*/
|
||||
public class Macro extends haus.nightmare.lib3270j.macro.Macro implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public Macro() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Macro(ECLSession session) {
|
||||
super(session != null ? session.getDelegate() : null);
|
||||
}
|
||||
|
||||
public Macro(haus.nightmare.lib3270j.ecl.ECLSession session) {
|
||||
super(session);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.ibm.eNetwork.beans.HOD;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Compatibility facade for IBM Host On-Demand MacroAction.
|
||||
*/
|
||||
public abstract class MacroAction extends haus.nightmare.lib3270j.macro.action.MacroAction implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ibm.eNetwork.beans.HOD;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Compatibility facade for IBM Host On-Demand MacroActions.
|
||||
*/
|
||||
public class MacroActions extends haus.nightmare.lib3270j.macro.MacroActions implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MacroActions() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ibm.eNetwork.beans.HOD;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Compatibility facade for IBM Host On-Demand MacroNextScreens.
|
||||
*/
|
||||
public class MacroNextScreens extends haus.nightmare.lib3270j.macro.MacroNextScreens implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MacroNextScreens() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ibm.eNetwork.beans.HOD;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Compatibility facade for IBM Host On-Demand MacroScreen.
|
||||
*/
|
||||
public class MacroScreen extends haus.nightmare.lib3270j.macro.MacroScreen implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MacroScreen() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MacroScreen(String name) {
|
||||
super(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.ibm.eNetwork.beans.HOD;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Compatibility facade for IBM Host On-Demand MacroScreens.
|
||||
*/
|
||||
public class MacroScreens extends haus.nightmare.lib3270j.macro.MacroScreens implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MacroScreens() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -435,6 +435,14 @@ public class ConnectionConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse [lu@]host[:port] (e.g. "00C2@mvs.host.com:1023")
|
||||
String parsedLu = null;
|
||||
int atIdx = s.indexOf('@');
|
||||
if (atIdx > 0 && atIdx < s.length() - 1) {
|
||||
parsedLu = s.substring(0, atIdx).trim();
|
||||
s = s.substring(atIdx + 1).trim();
|
||||
}
|
||||
|
||||
String host = s;
|
||||
int port = (defaultPort > 0) ? defaultPort : (tls ? 992 : 23);
|
||||
|
||||
@@ -458,6 +466,9 @@ public class ConnectionConfig {
|
||||
}
|
||||
|
||||
ConnectionConfig config = new ConnectionConfig(host, port, defaultModel != null ? defaultModel : TerminalModel.IBM_3279_4);
|
||||
if (parsedLu != null && !parsedLu.isEmpty()) {
|
||||
config.setLuName(parsedLu);
|
||||
}
|
||||
config.setUseTls(tls);
|
||||
config.setTn3270eEnabled(tn3270e);
|
||||
config.setKeepAliveEnabled(keepAlive);
|
||||
|
||||
@@ -92,6 +92,9 @@ public class Telnet3270Client {
|
||||
ps.notifyAlarm();
|
||||
}
|
||||
@Override public void onKeyboardUnlocked() {
|
||||
if (screenBuffer != null) {
|
||||
screenBuffer.getHistoryBuffer().snapshot(screenBuffer);
|
||||
}
|
||||
ps.notifyKeyUnlocked();
|
||||
}
|
||||
});
|
||||
@@ -658,6 +661,9 @@ public class Telnet3270Client {
|
||||
public void setWordWrap(boolean b) { if (screenBuffer != null) screenBuffer.setEntryAssistWordWrap(b); if (oia != null) oia.notifyOIAChanged(); }
|
||||
public void toggleWordWrap() { setWordWrap(!isWordWrap()); }
|
||||
|
||||
public boolean isPasteWordBreak() { return inputProcessor != null && inputProcessor.isPasteWordBreak(); }
|
||||
public void setPasteWordBreak(boolean b) { if (inputProcessor != null) inputProcessor.setPasteWordBreak(b); }
|
||||
|
||||
/** APL Keyboard Mode operations. */
|
||||
public boolean isAplMode() { return inputProcessor != null && inputProcessor.isAplKeyboardMode(); }
|
||||
public void setAplMode(boolean b) { if (inputProcessor != null) inputProcessor.setAplKeyboardMode(b); }
|
||||
@@ -705,4 +711,8 @@ public class Telnet3270Client {
|
||||
public haus.nightmare.lib3270j.graphics.GocaDecoder getGocaDecoder() {
|
||||
return dsProcessor.getGocaDecoder();
|
||||
}
|
||||
|
||||
public haus.nightmare.lib3270j.screen.ScreenHistoryBuffer getScreenHistoryBuffer() {
|
||||
return screenBuffer != null ? screenBuffer.getHistoryBuffer() : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,13 @@ public class DataStreamProcessor {
|
||||
private boolean unlockSysPending = false;
|
||||
private boolean rcvdRead = false;
|
||||
|
||||
// Modal SA (set attribute) character attributes
|
||||
private byte currentFg = 0;
|
||||
private byte currentBg = 0;
|
||||
private byte currentGr = 0;
|
||||
private byte currentCs = 0;
|
||||
private byte currentOl = 0;
|
||||
|
||||
/** Functional interface for sending output back through the telnet stack. */
|
||||
@FunctionalInterface
|
||||
public interface OutputSender {
|
||||
@@ -240,6 +247,11 @@ public class DataStreamProcessor {
|
||||
programSymbolManager.commitStagedSymbols();
|
||||
log.info(">>> EAU: erasing all unprotected fields");
|
||||
screen.eraseAllUnprotected();
|
||||
currentFg = 0;
|
||||
currentBg = 0;
|
||||
currentGr = 0;
|
||||
currentCs = 0;
|
||||
currentOl = 0;
|
||||
break;
|
||||
case CMD_WSF:
|
||||
case SNA_CMD_WSF:
|
||||
@@ -378,11 +390,21 @@ public class DataStreamProcessor {
|
||||
if (wccReset(wcc)) {
|
||||
// Reset all character attributes to defaults
|
||||
log.fine("WCC reset: clearing default attributes");
|
||||
currentFg = 0;
|
||||
currentBg = 0;
|
||||
currentGr = 0;
|
||||
currentCs = 0;
|
||||
currentOl = 0;
|
||||
}
|
||||
|
||||
if (eraseFirst) {
|
||||
screen.clear();
|
||||
log.fine("Cleared screen for Erase/Write");
|
||||
currentFg = 0;
|
||||
currentBg = 0;
|
||||
currentGr = 0;
|
||||
currentCs = 0;
|
||||
currentOl = 0;
|
||||
}
|
||||
|
||||
// Process orders and data starting at byte 2
|
||||
@@ -390,9 +412,6 @@ public class DataStreamProcessor {
|
||||
int end = offset + length;
|
||||
int baddr = screen.getBufferAddress();
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
|
||||
// Current SA (set attribute) values for character-mode
|
||||
byte currentFg = 0, currentBg = 0, currentGr = 0, currentCs = 0;
|
||||
boolean lastWasOrder = false;
|
||||
|
||||
while (pos < end) {
|
||||
@@ -427,10 +446,6 @@ public class DataStreamProcessor {
|
||||
// FA position is a display position that shows as blank
|
||||
ea.ec = 0;
|
||||
ea.ucs4 = ' ';
|
||||
currentFg = 0;
|
||||
currentBg = 0;
|
||||
currentGr = 0;
|
||||
currentCs = 0;
|
||||
screen.setFormatted(true);
|
||||
baddr = (baddr + 1) % size;
|
||||
screen.setBufferAddress(baddr);
|
||||
@@ -455,10 +470,6 @@ public class DataStreamProcessor {
|
||||
ea.clear();
|
||||
ea.ec = 0;
|
||||
ea.ucs4 = ' ';
|
||||
currentFg = 0;
|
||||
currentBg = 0;
|
||||
currentGr = 0;
|
||||
currentCs = 0;
|
||||
|
||||
for (int i = 0; i < nPairs; i++) {
|
||||
int attrType = data[pos + 2 + i * 2] & 0xFF;
|
||||
@@ -485,6 +496,13 @@ public class DataStreamProcessor {
|
||||
int attrType = data[pos + 1] & 0xFF;
|
||||
int attrValue = data[pos + 2] & 0xFF;
|
||||
switch (attrType) {
|
||||
case XA_ALL:
|
||||
currentFg = 0;
|
||||
currentBg = 0;
|
||||
currentGr = 0;
|
||||
currentCs = 0;
|
||||
currentOl = 0;
|
||||
break;
|
||||
case XA_FOREGROUND:
|
||||
currentFg = (byte) attrValue;
|
||||
break;
|
||||
@@ -508,6 +526,10 @@ public class DataStreamProcessor {
|
||||
case XA_CHARSET:
|
||||
currentCs = (byte) attrValue;
|
||||
break;
|
||||
case XA_OUTLINING:
|
||||
case 0x84:
|
||||
currentOl = (byte) attrValue;
|
||||
break;
|
||||
}
|
||||
pos += 3;
|
||||
lastWasOrder = true;
|
||||
@@ -535,6 +557,8 @@ public class DataStreamProcessor {
|
||||
applyExtendedAttribute(ea, attrType, attrValue);
|
||||
}
|
||||
}
|
||||
baddr = (baddr + 1) % size;
|
||||
screen.setBufferAddress(baddr);
|
||||
pos += 2 + nPairs * 2;
|
||||
lastWasOrder = true;
|
||||
break;
|
||||
@@ -590,6 +614,7 @@ public class DataStreamProcessor {
|
||||
ea.bg = currentBg;
|
||||
ea.gr = (byte) currentGr;
|
||||
ea.cs = fillCs;
|
||||
ea.ol = currentOl;
|
||||
baddr = (baddr + 1) % size;
|
||||
} while (baddr != toAddr);
|
||||
screen.setBufferAddress(baddr);
|
||||
@@ -618,6 +643,7 @@ public class DataStreamProcessor {
|
||||
ea.bg = 0;
|
||||
ea.gr = 0;
|
||||
ea.cs = 0;
|
||||
ea.ol = 0;
|
||||
}
|
||||
}
|
||||
baddr = (baddr + 1) % size;
|
||||
@@ -642,6 +668,7 @@ public class DataStreamProcessor {
|
||||
ea.fg = currentFg;
|
||||
ea.bg = currentBg;
|
||||
ea.gr = (byte) currentGr;
|
||||
ea.ol = currentOl;
|
||||
baddr = (baddr + 1) % size;
|
||||
screen.setBufferAddress(baddr);
|
||||
pos += 2;
|
||||
@@ -668,6 +695,7 @@ public class DataStreamProcessor {
|
||||
ea.bg = currentBg;
|
||||
ea.gr = (byte) currentGr;
|
||||
ea.cs = currentCs;
|
||||
ea.ol = currentOl;
|
||||
|
||||
baddr = (baddr + 1) % size;
|
||||
screen.setBufferAddress(baddr);
|
||||
@@ -980,11 +1008,18 @@ public class DataStreamProcessor {
|
||||
byte[] psData = new byte[fieldLen - 3];
|
||||
System.arraycopy(data, pos + 3, psData, 0, psData.length);
|
||||
programSymbolManager.loadps(psData);
|
||||
notifyScreenUpdated();
|
||||
}
|
||||
break;
|
||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA: // 0x85: Graphics Object Data / GOCA
|
||||
if (fieldLen > 3) {
|
||||
graphicsPlane.setCharDimensions(qrBuilder.getCharWidth(), qrBuilder.getCharHeight());
|
||||
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
||||
int targetW = screen.getCols() * qrBuilder.getCharWidth();
|
||||
int targetH = screen.getRows() * qrBuilder.getCharHeight();
|
||||
if (graphicsPlane.getCanvasWidth() != targetW || graphicsPlane.getCanvasHeight() != targetH) {
|
||||
graphicsPlane.resize(targetW, targetH);
|
||||
}
|
||||
gocaDecoder.decodeStream(data, pos + 3, fieldLen - 3);
|
||||
notifyScreenUpdated();
|
||||
}
|
||||
@@ -1284,6 +1319,7 @@ public class DataStreamProcessor {
|
||||
byte[] psData = new byte[fieldLen - 4];
|
||||
System.arraycopy(data, offset + 4, psData, 0, psData.length);
|
||||
programSymbolManager.loadps(psData);
|
||||
notifyScreenUpdated();
|
||||
}
|
||||
break;
|
||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA_SUB: { // 0x0F: Object Control / Data Unit
|
||||
@@ -1298,9 +1334,10 @@ public class DataStreamProcessor {
|
||||
int orderOffset = (fieldLen >= 7) ? (offset + 7) : (offset + 4);
|
||||
int orderLen = Math.max(0, fieldLen - (orderOffset - offset));
|
||||
|
||||
graphicsPlane.setCharDimensions(qrBuilder.getCharWidth(), qrBuilder.getCharHeight());
|
||||
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
||||
int targetW = screen.getCols() * 9;
|
||||
int targetH = screen.getRows() * 16;
|
||||
int targetW = screen.getCols() * qrBuilder.getCharWidth();
|
||||
int targetH = screen.getRows() * qrBuilder.getCharHeight();
|
||||
if (graphicsPlane.getCanvasWidth() != targetW || graphicsPlane.getCanvasHeight() != targetH) {
|
||||
graphicsPlane.resize(targetW, targetH);
|
||||
}
|
||||
|
||||
@@ -424,8 +424,8 @@ public class QueryReplyBuilder {
|
||||
out.write((Yr_HOD >> 16) & 0xFF);
|
||||
out.write((Yr_HOD >> 8) & 0xFF);
|
||||
out.write(Yr_HOD & 0xFF);
|
||||
int charW = getCharWidth();
|
||||
int charH = getCharHeight();
|
||||
int charW = getCharWidth(maxRows);
|
||||
int charH = getCharHeight(maxRows);
|
||||
out.write(charW); // AW
|
||||
out.write(charH); // AH
|
||||
int buf = maxCols * maxRows;
|
||||
@@ -435,6 +435,11 @@ public class QueryReplyBuilder {
|
||||
}
|
||||
|
||||
public int getCharWidth() {
|
||||
int rows = (screen != null) ? screen.getMaxRows() : MODEL_2_ROWS;
|
||||
return getCharWidth(rows);
|
||||
}
|
||||
|
||||
public int getCharWidth(int rows) {
|
||||
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||
return 12;
|
||||
}
|
||||
@@ -442,18 +447,11 @@ public class QueryReplyBuilder {
|
||||
}
|
||||
|
||||
public int getCharHeight() {
|
||||
// ARCHITECTURAL NOTE ON 3179G GOCA VERTICAL ALIGNMENT & QUERY REPLIES:
|
||||
// Why hardcoding SH = 12 (0x0C) in Character Sets & Usable Area failed in past iterations:
|
||||
// When SDH/AH is declared as 12 (0x0C) in Query Reply, the mainframe host GDDM engine computes
|
||||
// total presentation space as rows * 12 (e.g. 43 * 12 = 516 units, yMax = 257).
|
||||
// GDDM then places the top menu bar at Row 1 (gy = 187..200).
|
||||
// Meanwhile, the client emulator rendered into a 16-pitch grid (43 * 16 = 688 units, yMax = 343).
|
||||
// On a 688-unit canvas, gy = 200 mapped to Row 9.2 (middle of the screen), leaving a massive void above.
|
||||
// When the user clicked on the visual menu drawn at Row 9, the client emitted gy = 189 with cursor at Row 9,
|
||||
// which GDDM rejected as outside its menu hit box (causing terminal alarm beeps).
|
||||
//
|
||||
// Solution: Declare SDH = 16 (0x10) when Vector Graphics is enabled (3179G standard), ensuring host GDDM
|
||||
// and client GraphicsPlane share the exact same 16-pitch presentation space (720x688, yMax = 343).
|
||||
int rows = (screen != null) ? screen.getMaxRows() : MODEL_2_ROWS;
|
||||
return getCharHeight(rows);
|
||||
}
|
||||
|
||||
public int getCharHeight(int rows) {
|
||||
return graphicsMode.isVectorGraphicsEnabled() ? 0x10 : SH_3279_2;
|
||||
}
|
||||
|
||||
@@ -484,8 +482,8 @@ public class QueryReplyBuilder {
|
||||
cpgid = screen.getTranslator().getCpgid();
|
||||
}
|
||||
|
||||
if (graphicsMode == GraphicsMode.PROGRAMMED_SYMBOLS) {
|
||||
// Programmed Symbols only mode (3279 PS with LoadPS 0x0A, flags1 = 0xA2 for GE + PS + CGCSGID)
|
||||
if (graphicsMode.isProgrammedSymbolsEnabled()) {
|
||||
// Programmed Symbols mode (3279 PS / 3179G with LoadPS 0x0A, flags1 = 0xA2 for GE + PS + CGCSGID)
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(65);
|
||||
out.write(0xa2); // flags: GE (0x80), PS/Loadable Charsets (0x20), CGCSGID present (0x02)
|
||||
out.write(0x00); // more flags
|
||||
@@ -683,16 +681,18 @@ public class QueryReplyBuilder {
|
||||
public byte[] buildGrSymbolSet() {
|
||||
int charW = getCharWidth();
|
||||
int charH = getCharHeight();
|
||||
int cgcsgid = 0x02B9;
|
||||
int cpgid = 0x0025;
|
||||
if (screen != null && screen.getTranslator() != null) {
|
||||
cgcsgid = screen.getTranslator().getCgcsgid();
|
||||
cpgid = screen.getTranslator().getCpgid();
|
||||
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||
return new byte[]{
|
||||
0x00, 0x00, (byte) charW, (byte) charH, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||
0x01, 0x00, 0x00, (byte) 0xF0, (byte) 0xC1, (byte) 0xC1, 0x00, 0x00, (byte) charW, (byte) charH,
|
||||
0x00, 0x01, 0x4C, 0x01, 0x22, 0x00, 0x00, 0x12, 0x01, 0x00, 0x20, (byte) 0xF8, (byte) 0xC1, (byte) 0xC1,
|
||||
0x00, 0x00, 0x18, 0x18, 0x00, 0x01, 0x72, 0x01, 0x2C, 0x00, (byte) 0xFF
|
||||
};
|
||||
}
|
||||
return new byte[]{
|
||||
0x00, 0x00, (byte) charW, (byte) charH, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||
0x01, 0x00, 0x00, (byte) 0xF0, (byte) ((cgcsgid >> 8) & 0xFF), (byte) (cgcsgid & 0xFF), 0x00, 0x00,
|
||||
(byte) charW, (byte) charH, 0x00, 0x00, (byte) 0xF0, (byte) ((cgcsgid >> 8) & 0xFF), (byte) (cgcsgid & 0xFF), 0x00, 0x00
|
||||
0x01, 0x00, 0x00, (byte) 0xF0, (byte) 0xC1, (byte) 0xC1, 0x00, 0x00,
|
||||
(byte) charW, (byte) charH, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,34 @@ public class ECLConnection {
|
||||
if (props != null) {
|
||||
this.properties.putAll(props);
|
||||
convertData(this.properties);
|
||||
|
||||
String h = this.properties.getProperty(ECLSession.SESSION_HOST);
|
||||
if (h == null) h = this.properties.getProperty("host");
|
||||
if (h == null) h = this.properties.getProperty("Host");
|
||||
if (h == null) h = this.properties.getProperty("SESSION_PRINT_SESSION_HOST");
|
||||
if (h != null && !h.trim().isEmpty()) this.host = h.trim();
|
||||
|
||||
String p = this.properties.getProperty(ECLSession.SESSION_PORT);
|
||||
if (p == null) p = this.properties.getProperty("port");
|
||||
if (p == null) p = this.properties.getProperty("Port");
|
||||
if (p == null) p = this.properties.getProperty("SESSION_HOST_PORT");
|
||||
if (p == null) p = this.properties.getProperty("SESSION_PRINT_SESSION_HOST_PORT");
|
||||
if (p != null && !p.trim().isEmpty()) {
|
||||
try { this.port = Integer.parseInt(p.trim()); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
String cp = this.properties.getProperty(ECLSession.SESSION_CODE_PAGE);
|
||||
if (cp == null) cp = this.properties.getProperty("code_page");
|
||||
if (cp == null) cp = this.properties.getProperty("codepage");
|
||||
if (cp == null) cp = this.properties.getProperty("CodePage");
|
||||
if (cp != null && !cp.trim().isEmpty()) this.codePage = cp.trim();
|
||||
|
||||
String lu = this.properties.getProperty(ECLSession.SESSION_LU_NAME);
|
||||
if (lu == null) lu = this.properties.getProperty("lu_name");
|
||||
if (lu == null) lu = this.properties.getProperty("luname");
|
||||
if (lu == null) lu = this.properties.getProperty("SESSION_PRINT_SESSION_LU_NAME");
|
||||
if (lu != null && !lu.trim().isEmpty()) this.luName = lu.trim();
|
||||
|
||||
String asu = this.properties.getProperty(ECLSession.SESSION_AUTO_SYS_UNLOCK);
|
||||
if (asu != null) {
|
||||
this.autoSysUnlock = "true".equalsIgnoreCase(asu) || "1".equals(asu);
|
||||
|
||||
@@ -11,6 +11,8 @@ public class ECLErr extends Exception {
|
||||
private String id = "";
|
||||
private String text = "";
|
||||
|
||||
private int reasonCode = 0;
|
||||
|
||||
public ECLErr() {
|
||||
super();
|
||||
}
|
||||
@@ -20,6 +22,21 @@ public class ECLErr extends Exception {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum, int reasonCode) {
|
||||
super(formatMsg(location, msgNum, "Reason " + reasonCode));
|
||||
this.tag = location;
|
||||
this.id = msgNum;
|
||||
this.text = "Reason " + reasonCode;
|
||||
this.reasonCode = reasonCode;
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum) {
|
||||
super(formatMsg(location, msgNum, ""));
|
||||
this.tag = location;
|
||||
this.id = msgNum;
|
||||
this.text = "";
|
||||
}
|
||||
|
||||
public ECLErr(String tag, String id, String text) {
|
||||
super(formatMsg(tag, id, text));
|
||||
this.tag = tag;
|
||||
@@ -27,11 +44,30 @@ public class ECLErr extends Exception {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public ECLErr(String tag, String id, String text, String extra) {
|
||||
super(formatMsg(tag, id, text + " " + extra));
|
||||
this.tag = tag;
|
||||
this.id = id;
|
||||
this.text = text + " " + extra;
|
||||
public ECLErr(String location, String msgNum, String insert1, int reasonCode) {
|
||||
super(formatMsg(location, msgNum, insert1 + " (Reason " + reasonCode + ")"));
|
||||
this.tag = location;
|
||||
this.id = msgNum;
|
||||
this.text = insert1 + " (Reason " + reasonCode + ")";
|
||||
this.reasonCode = reasonCode;
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum, String insert1, String insert2, boolean log) {
|
||||
super(formatMsg(location, msgNum, insert1 + " " + insert2));
|
||||
this.tag = location;
|
||||
this.id = msgNum;
|
||||
this.text = insert1 + " " + insert2;
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum, String insert1, String insert2) {
|
||||
this(location, msgNum, insert1, insert2, false);
|
||||
}
|
||||
|
||||
public ECLErr(String location, String msgNum, String insert1, String insert2, String insert3) {
|
||||
super(formatMsg(location, msgNum, insert1 + " " + insert2 + " " + insert3));
|
||||
this.tag = location;
|
||||
this.id = msgNum;
|
||||
this.text = insert1 + " " + insert2 + " " + insert3;
|
||||
}
|
||||
|
||||
public ECLErr(Throwable cause) {
|
||||
@@ -57,14 +93,33 @@ public class ECLErr extends Exception {
|
||||
public String getID() { return id; }
|
||||
public String GetID() { return id; }
|
||||
|
||||
public String getLocation() { return tag; }
|
||||
public String GetLocation() { return tag; }
|
||||
|
||||
public int getReasonCode() { return reasonCode; }
|
||||
public int GetReasonCode() { return reasonCode; }
|
||||
|
||||
public int getMsgNumber() {
|
||||
if (id != null && id.length() >= 7 && id.startsWith("ECL")) {
|
||||
try {
|
||||
return Integer.parseInt(id.substring(3, 7));
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
public int GetMsgNumber() { return getMsgNumber(); }
|
||||
|
||||
public String getText() { return text; }
|
||||
public String GetText() { return text; }
|
||||
|
||||
public String getMsgText() { return getMessage(); }
|
||||
public String GetMsgText() { return getMessage(); }
|
||||
|
||||
public String getErrorText() { return getMessage(); }
|
||||
public String GetErrorText() { return getMessage(); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ECLErr[tag=" + tag + ", id=" + id + ", text=" + text + "]";
|
||||
return "ECLErr[tag=" + tag + ", id=" + id + ", text=" + text + ", reasonCode=" + reasonCode + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ public class ECLField {
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
public ECLPS getPS() { return ps; }
|
||||
public ECLPS GetPS() { return ps; }
|
||||
public byte getAttributeByte() { return attribute; }
|
||||
|
||||
/** Buffer address of the field attribute character. */
|
||||
public int getStart() { return startPos; }
|
||||
public int GetStart() { return getStart(); }
|
||||
@@ -154,6 +158,8 @@ public class ECLField {
|
||||
return ps.getString(dataStart, length);
|
||||
}
|
||||
public String GetText() { return getText(); }
|
||||
public String getString() { return getText(); }
|
||||
public String GetString() { return getText(); }
|
||||
|
||||
/**
|
||||
* Set the text contents of this field.
|
||||
@@ -162,7 +168,13 @@ public class ECLField {
|
||||
if (isProtected() || length <= 0) return;
|
||||
ps.setText(text, dataStart);
|
||||
}
|
||||
public void SetText(String text) { setText(text); }
|
||||
public void SetText(String text) throws ECLErr {
|
||||
if (isProtected()) {
|
||||
throw new ECLErr("ECLField:SetText", "ECL0015", "Field is protected");
|
||||
}
|
||||
setText(text);
|
||||
}
|
||||
public void SetString(String text) throws ECLErr { SetText(text); }
|
||||
|
||||
/**
|
||||
* Get selector light pen type.
|
||||
|
||||
@@ -7,15 +7,17 @@ import java.util.List;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Manages the collection of all 3270 fields currently present in the Presentation Space.
|
||||
* Conforms to IBM ECL ECLFieldList specification.
|
||||
*/
|
||||
public class ECLFieldList {
|
||||
public class ECLFieldList extends Vector<ECLField> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final ECLPS ps;
|
||||
private final ScreenBuffer screen;
|
||||
private final List<ECLField> fields = new ArrayList<>();
|
||||
|
||||
public ECLFieldList(ECLPS ps, ScreenBuffer screen) {
|
||||
this.ps = ps;
|
||||
@@ -23,11 +25,14 @@ public class ECLFieldList {
|
||||
refresh();
|
||||
}
|
||||
|
||||
public ECLPS getPS() { return ps; }
|
||||
public ScreenBuffer getScreenBuffer() { return screen; }
|
||||
|
||||
/**
|
||||
* Refresh the field list by scanning the presentation space buffer.
|
||||
*/
|
||||
public synchronized void refresh() {
|
||||
fields.clear();
|
||||
clear();
|
||||
if (screen == null || !screen.isFormatted()) {
|
||||
return;
|
||||
}
|
||||
@@ -66,35 +71,35 @@ public class ECLFieldList {
|
||||
}
|
||||
|
||||
byte faVal = screen.getCell(startPos).fa;
|
||||
fields.add(new ECLField(ps, startPos, dataStart, endPos, len, faVal));
|
||||
add(new ECLField(ps, startPos, dataStart, endPos, len, faVal));
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh() { refresh(); }
|
||||
|
||||
public synchronized int getFieldCount() {
|
||||
return fields.size();
|
||||
return size();
|
||||
}
|
||||
public int GetFieldCount() { return getFieldCount(); }
|
||||
|
||||
public synchronized List<ECLField> getFields() {
|
||||
return Collections.unmodifiableList(new ArrayList<>(fields));
|
||||
return Collections.unmodifiableList(new ArrayList<>(this));
|
||||
}
|
||||
public List<ECLField> GetFields() { return getFields(); }
|
||||
|
||||
public synchronized ECLField getFirstField() {
|
||||
if (fields.isEmpty()) return null;
|
||||
return fields.get(0);
|
||||
if (isEmpty()) return null;
|
||||
return get(0);
|
||||
}
|
||||
public ECLField GetFirstField() { return getFirstField(); }
|
||||
|
||||
public synchronized ECLField getNextField(ECLField prev) {
|
||||
if (prev == null || fields.isEmpty()) return getFirstField();
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
ECLField f = fields.get(i);
|
||||
if (prev == null || isEmpty()) return getFirstField();
|
||||
for (int i = 0; i < size(); i++) {
|
||||
ECLField f = get(i);
|
||||
if (f.equals(prev) || f.getStart() == prev.getStart()) {
|
||||
if (i + 1 < fields.size()) {
|
||||
return fields.get(i + 1);
|
||||
if (i + 1 < size()) {
|
||||
return get(i + 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -107,12 +112,12 @@ public class ECLFieldList {
|
||||
* Find the field that contains the specified buffer position.
|
||||
*/
|
||||
public synchronized ECLField findField(int pos) {
|
||||
if (fields.isEmpty() || screen == null) return null;
|
||||
if (isEmpty() || screen == null) return null;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return null;
|
||||
pos = ((pos % size) + size) % size;
|
||||
|
||||
for (ECLField f : fields) {
|
||||
for (ECLField f : this) {
|
||||
int start = f.getStart();
|
||||
int end = f.getEnd();
|
||||
if (start <= end) {
|
||||
@@ -140,14 +145,14 @@ public class ECLFieldList {
|
||||
* Get the field preceding the given field in the field list.
|
||||
*/
|
||||
public synchronized ECLField getPreviousField(ECLField next) {
|
||||
if (next == null || fields.isEmpty()) return null;
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
ECLField f = fields.get(i);
|
||||
if (next == null || isEmpty()) return null;
|
||||
for (int i = 0; i < size(); i++) {
|
||||
ECLField f = get(i);
|
||||
if (f.equals(next) || f.getStart() == next.getStart()) {
|
||||
if (i > 0) {
|
||||
return fields.get(i - 1);
|
||||
return get(i - 1);
|
||||
} else {
|
||||
return fields.get(fields.size() - 1);
|
||||
return get(size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,29 +200,29 @@ public class ECLFieldList {
|
||||
* Find field containing the given text string.
|
||||
*/
|
||||
public synchronized ECLField findField(String text, int startPos) {
|
||||
if (text == null || text.isEmpty() || fields.isEmpty()) return null;
|
||||
if (text == null || text.isEmpty() || isEmpty()) return null;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return null;
|
||||
startPos = ((startPos % size) + size) % size;
|
||||
|
||||
// Find starting index in field list
|
||||
int startIdx = 0;
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
if (fields.get(i).getStart() >= startPos) {
|
||||
for (int i = 0; i < size(); i++) {
|
||||
if (get(i).getStart() >= startPos) {
|
||||
startIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = startIdx; i < fields.size(); i++) {
|
||||
ECLField f = fields.get(i);
|
||||
for (int i = startIdx; i < size(); i++) {
|
||||
ECLField f = get(i);
|
||||
if (f.getText().contains(text)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
// Wrap around search to beginning of field list
|
||||
for (int i = 0; i < startIdx; i++) {
|
||||
ECLField f = fields.get(i);
|
||||
ECLField f = get(i);
|
||||
if (f.getText().contains(text)) {
|
||||
return f;
|
||||
}
|
||||
@@ -265,18 +270,18 @@ public class ECLFieldList {
|
||||
public boolean MatchAttributes(ECLField f, int mask) { return matchAttributes(f, mask); }
|
||||
|
||||
public synchronized ECLField locateField(int attrMask, ECLField prev) {
|
||||
if (fields.isEmpty()) return null;
|
||||
if (isEmpty()) return null;
|
||||
int startIndex = 0;
|
||||
if (prev != null) {
|
||||
int idx = fields.indexOf(prev);
|
||||
if (idx >= 0 && idx + 1 < fields.size()) {
|
||||
int idx = indexOf(prev);
|
||||
if (idx >= 0 && idx + 1 < size()) {
|
||||
startIndex = idx + 1;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
for (int i = startIndex; i < fields.size(); i++) {
|
||||
ECLField f = fields.get(i);
|
||||
for (int i = startIndex; i < size(); i++) {
|
||||
ECLField f = get(i);
|
||||
if (matchAttributes(f, attrMask)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -109,6 +110,8 @@ public class ECLOIA implements ECLConstants {
|
||||
session != null && session.getClient() != null ? session.getClient().getTelnetFSM() : null);
|
||||
}
|
||||
|
||||
private final List<com.ibm.eNetwork.ECL.ECLOIANotify> hodOiaNotifies = new CopyOnWriteArrayList<>();
|
||||
|
||||
public void RegisterOIAEvent(haus.nightmare.lib3270j.ecl.ECLOIANotify listener) {
|
||||
if (listener != null && !listeners.contains(listener)) {
|
||||
listeners.add(listener);
|
||||
@@ -132,6 +135,29 @@ public class ECLOIA implements ECLConstants {
|
||||
UnregisterOIAEvent(listener);
|
||||
}
|
||||
|
||||
public void RegisterOIAEvent(com.ibm.eNetwork.ECL.ECLOIANotify notify) {
|
||||
if (notify != null && !hodOiaNotifies.contains(notify)) {
|
||||
hodOiaNotifies.add(notify);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerOIAEvent(com.ibm.eNetwork.ECL.ECLOIANotify notify) {
|
||||
RegisterOIAEvent(notify);
|
||||
}
|
||||
|
||||
public void UnregisterOIAEvent(com.ibm.eNetwork.ECL.ECLOIANotify notify) {
|
||||
if (notify != null) {
|
||||
hodOiaNotifies.remove(notify);
|
||||
try {
|
||||
notify.NotifyStop(new com.ibm.eNetwork.ECL.ECLOIA(this), STOP_UNREGISTER);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterOIAEvent(com.ibm.eNetwork.ECL.ECLOIANotify notify) {
|
||||
UnregisterOIAEvent(notify);
|
||||
}
|
||||
|
||||
public void RegisterOIAEvent(ECLOIAListener listener) {
|
||||
if (listener != null && !oiaListeners.contains(listener)) {
|
||||
oiaListeners.add(listener);
|
||||
@@ -166,6 +192,14 @@ public class ECLOIA implements ECLConstants {
|
||||
l.OIANotifyError(this, err);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
for (com.ibm.eNetwork.ECL.ECLOIANotify l : hodOiaNotifies) {
|
||||
try {
|
||||
com.ibm.eNetwork.ECL.ECLErr ferr = (err instanceof com.ibm.eNetwork.ECL.ECLErr)
|
||||
? (com.ibm.eNetwork.ECL.ECLErr) err
|
||||
: new com.ibm.eNetwork.ECL.ECLErr(err.getMessage());
|
||||
l.NotifyError(new com.ibm.eNetwork.ECL.ECLOIA(this), ferr);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public void notifyOIAStop(int reason) {
|
||||
@@ -179,6 +213,11 @@ public class ECLOIA implements ECLConstants {
|
||||
l.OIANotifyStop(this, reason);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
for (com.ibm.eNetwork.ECL.ECLOIANotify l : hodOiaNotifies) {
|
||||
try {
|
||||
l.NotifyStop(new com.ibm.eNetwork.ECL.ECLOIA(this), reason);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
private final ReentrantLock oiaLock = new ReentrantLock();
|
||||
@@ -210,6 +249,11 @@ public class ECLOIA implements ECLConstants {
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
for (com.ibm.eNetwork.ECL.ECLOIANotify l : hodOiaNotifies) {
|
||||
try {
|
||||
l.OIANotifyEvent(new com.ibm.eNetwork.ECL.ECLOIAEvent(event));
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInsertMode() {
|
||||
@@ -267,6 +311,11 @@ public class ECLOIA implements ECLConstants {
|
||||
public void setInputInhibited(int reason) {
|
||||
if (this.inhibitOverride != reason) {
|
||||
this.inhibitOverride = reason;
|
||||
if (reason == INHIBIT_NOT_INHIBITED && inputProcessor != null && inputProcessor.isKeyboardLocked()) {
|
||||
inputProcessor.setKeyboardLocked(false);
|
||||
} else if (reason == INHIBIT_SYSTEM_LOCK && inputProcessor != null && !inputProcessor.isKeyboardLocked()) {
|
||||
inputProcessor.setKeyboardLocked(true);
|
||||
}
|
||||
notifyOIAChanged();
|
||||
}
|
||||
}
|
||||
@@ -424,6 +473,14 @@ public class ECLOIA implements ECLConstants {
|
||||
return getInputInhibited();
|
||||
}
|
||||
|
||||
public boolean isKeyLocked() {
|
||||
return getInputInhibited() != INHIBIT_NOT_INHIBITED;
|
||||
}
|
||||
|
||||
public boolean IsKeyLocked() {
|
||||
return isKeyLocked();
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until the keyboard becomes unlocked and ready for user input, or timeout expires.
|
||||
* @return true if keyboard unlocked, false if timeout occurred.
|
||||
|
||||