diff --git a/README.html b/README.html index 9574906..783685b 100644 --- a/README.html +++ b/README.html @@ -60,9 +60,8 @@

3. Von der GNU-GPL ausgenommene Programmteile

Modifizierung und Weitergabe von JKCEMU eingeräumt werden, gelten nicht für die ROM- und Disketteninhalte! Jegliche Benutzung dieser ROM- und Disketten-Images außerhalb - von JKCEMU oder außerhalb eines rein privaten, - nicht kommerziellen Umfeldes müssen Sie im Zweifelsfall - mit den Urhebern bzw. deren Rechtsnachfolgern klären. + von JKCEMU müssen Sie im Zweifelsfall mit den Urhebern + bzw. deren Rechtsnachfolgern klären.

4. Urheberschaften

@@ -100,7 +99,7 @@

4.1. Urheberschaft am Programmcode

Autoren der Programme compress.c und gifcompress.c, - auf denen die Implementierung des in JKCEMU enthaltenen + auf denen die Implementierung des im JKCEMU enthaltenen LZW-Encoders basiert
(wird benötigt für das Erzeugen animierter GIF-Dateien (Bildschirmvideos)) @@ -247,29 +246,29 @@

7. Dank

  • Prof. Dr. Albrecht Mugler für die freundliche Genehmigung zur Integration der PC/M-Systemsoftware - (BIOS, V-Tape, Debugger, CCP und BDOS) in JKCEMU + (BIOS, V-Tape, Debugger, CCP und BDOS) im JKCEMU
  • Dr. Dieter Scheuschner für die Bereitstellung des ROM-Inhalts des SLC1 und für die freundliche Genehmigung, - diesen in JKCEMU integrieren zu dürfen + diesen im JKCEMU integrieren zu dürfen
  • Dr. Gerd Maudrich für die freundliche Genehmigung zur Integration des LLC1-ROM-Images (Monitorprogramm und Tiny-BASIC-Interpreter) - in JKCEMU + im JKCEMU
  • Dr. Hans-Jürgen Gatsche für die freundliche Genehmigung, von ihm entwickelte RBASIC-Programme für den A5105 - in JKCEMU integrieren zu dürfen + im JKCEMU integrieren zu dürfen
  • André Schenk für das ANT-Skript
  • Andreas Suske für die freundliche Genehmigung zur Integration seiner AC1-2010-Software (Monitorprogramm, FDC-Programm und ROM-Bank-Verwaltung) - in JKCEMU sowie für seine Hilfe bei der AC1-Emulation + im JKCEMU sowie für seine Hilfe bei der AC1-Emulation
  • Claus-Peter Fischer für die Bereitstellung @@ -282,7 +281,7 @@

    7. Dank

  • Eckhard Schiller für die freundliche Genehmigung - zur Integration des VCS80- und der BCS3-ROM-Images in JKCEMU + zur Integration des VCS80- und der BCS3-ROM-Images im JKCEMU
  • Enrico Grämer für die Bereitstellung von Material @@ -290,18 +289,23 @@

    7. Dank

  • Frank Prüfer für die freundliche Genehmigung - zur Integration von S/P-BASIC 3.3 in JKCEMU + zur Integration von S/P-BASIC 3.3 im JKCEMU sowie für die Unterstützung bei der BCS3-Emulation
  • Gunar Hänke für seine Hilfe bei der AC1- und Diskettenemulation
  • -
  • Heiko Poppe für seine Hilfe bei der AC1- und USB-Emulation
  • +
  • + Heiko Poppe für die freundliche Genehmigung + zur Integration des CP/M File-Commanders im JKCEMU + sowie für seine Hilfe bei der AC1-, + K1520-Farbgrafikkarten- und USB-Emulation +
  • Herbert Mathes für die freundliche Genehmigung zur Integration der PC/M-Systemsoftware - (BIOS, V-Tape, Debugger, CCP und BDOS) in JKCEMU + (BIOS, V-Tape, Debugger, CCP und BDOS) im JKCEMU
  • Holger Bretfeld für die leihweise Bereitstellung eines KC85/5 @@ -318,19 +322,32 @@

    7. Dank

    Programmcodeteile aus dem Projekt LIBDSK übernehmen zu dürfen (CRC-Berechnung für das CopyQM-Dateiformat)
  • +
  • + Jörg Felgentreu für seine Unterstützung + bei der A5105-Emulation +
  • Jürgen Helas für das intensive Testen des Assemblers und Reassemblers
  • +
  • + Klaus Wilfling für die freundliche Genehmigung zur Integration + des von Ihm um Farbfunktionalität erweiterten EPOS + sowie für die Unterstützung bei der NANOS-Emulation +
  • +
  • + Klaus Junge für die Unterstützung bei der NANOS-Emulation +
  • Manfred Kramer für die freundliche Genehmigung zur Integration - der Systemsoftware des Kramer-MC in JKCEMU + der Systemsoftware des Kramer-MC im JKCEMU
  • Mario Leubner für die freundliche Genehmigung zur Integration der von ihm weiterentwickelten CAOS-, EDAS-, - D004- und USB-Software in JKCEMU sowie für seine aktive Hilfe - bei der KC85/2..5, USB- und Festplattenemulation + D004- und USB-Software im JKCEMU sowie für seine + sehr aktive und umfangreiche Hilfe im KC85/2..5-, USB-, + Disketten- und Festplattenumfeld
  • Norbert Richter für die Bereitstellung von Informationen @@ -342,7 +359,7 @@

    7. Dank

  • Ralf Kästner für die freundliche Genehmigung - zur Integration der von ihm entwickelten KCNet-Software in JKCEMU, + zur Integration der von ihm entwickelten KCNet-Software im JKCEMU, für seine Homepage susowa.homeftp.net sowie für die Hilfe bei der KC85/2..5- und Netzwerk-Emulation @@ -356,7 +373,7 @@

    7. Dank

  • Rolf Weidlich für die Unterstützung - bei der AC1- und LLC1-Emulation + bei der AC1-, LLC1- und LLC2-Emulation
  • Siegfried Schenk für die Bereitstellung von Informationen @@ -367,7 +384,8 @@

    7. Dank

    und KC-compact-Emulation
  • - Stephan Linz für seine Homepage + Stephan Linz für seine Hilfe bei der PC/M-Emulation + sowie für seine Homepage www.li-pro.net
  • diff --git a/README.txt b/README.txt index 6cf161d..bfb3a55 100644 --- a/README.txt +++ b/README.txt @@ -51,9 +51,8 @@ d.h., die Rechte, die Ihnen von der GNU-GPL bzgl. der Benutzung, Modifizierung und Weitergabe von JKCEMU eingeraeumt werden, gelten nicht fuer die ROM- und Disketteninhalte! Jegliche Benutzung dieser ROM- und Disketten-Images ausserhalb -von JKCEMU oder ausserhalb eines rein privaten, -nicht kommerziellen Umfeldes muessen Sie im Zweifelsfall -mit den Urhebern bzw. deren Rechtsnachfolgern klaeren. +von JKCEMU muessen Sie im Zweifelsfall mit den Urhebern +bzw. deren Rechtsnachfolgern klaeren. 4. Urheberschaften @@ -77,7 +76,7 @@ John Elliott, Per Ola Ingvarsson: Spencer W. Thomas, Jim McKie, Steve Davies, Ken Turkowski, James A. Woods, Joe Orost, David Rowley: Autoren der Programme compress.c und gifcompress.c, - auf denen die Implementierung des in JKCEMU enthaltenen LZW-Encoders basiert + auf denen die Implementierung des im JKCEMU enthaltenen LZW-Encoders basiert (wird benoetigt fuer das Erzeugen animierter GIF-Dateien (Bildschirmvideos)) @@ -168,33 +167,35 @@ indem er auf seiner Homepage wichtige Informationen bereithaelt. Besonders bedanken moechte ich mich bei: - Prof. Dr. Albrecht Mugler fuer die freundliche Genehmigung zur Integration - der PC/M-Systemsoftware (BIOS, V-Tape, Debugger, CCP und BDOS) in JKCEMU + der PC/M-Systemsoftware (BIOS, V-Tape, Debugger, CCP und BDOS) im JKCEMU - Dr. Dieter Scheuschner fuer die Bereitstellung des ROM-Inhalts des SLC1 und fuer die freundliche Genehmigung, - diesen in JKCEMU integrieren zu duerfen + diesen im JKCEMU integrieren zu duerfen - Dr. Gerd Maudrich fuer die freundliche Genehmigung zur Integration - des LLC1-ROM-Images (Monitorprogramm und Tiny-BASIC-Interpreter) in JKCEMU + des LLC1-ROM-Images (Monitorprogramm und Tiny-BASIC-Interpreter) im JKCEMU - Dr. Hans-Juergen Gatsche fuer die freundliche Genehmigung, von ihm entwickelte RBASIC-Programme fuer den A5105 - in JKCEMU integrieren zu duerfen + im JKCEMU integrieren zu duerfen - Andre Schenk fuer das ANT-Skript - Andreas Suske fuer die freundliche Genehmigung zur Integration seiner AC1-2010-Software (Monitorprogramm, FDC-Programm und ROM-Bank-Verwaltung) - in JKCEMU sowie fuer seine Hilfe bei der AC1-Emulation + im JKCEMU sowie fuer seine Hilfe bei der AC1-Emulation - Claus-Peter Fischer fuer die Bereitstellung eines ROM-Images zum PC/M - Cliff Lawson (Amstrad plc) fuer die allgemeine Erlaubnis zur Integration der unter dem Urheberrecht von Amstrad stehenden ROMs in Emulatoren. - Eckhard Schiller fuer die freundliche Genehmigung zur Integration - des VCS80- und der BCS3-ROM-Images in JKCEMU + des VCS80- und der BCS3-ROM-Images im JKCEMU - Enrico Graemer fuer die Bereitstellung von Material zum KC compact - Frank Pruefer fuer die freundliche Genehmigung zur Integration - von S/P-BASIC 3.3 in JKCEMU und fuer die Unterstuetzung + von S/P-BASIC 3.3 im JKCEMU und fuer die Unterstuetzung bei der BCS3-Emulation - Gunar Haenke fuer seine Hilfe bei der AC1- und Diskettenemulation -- Heiko Poppe fuer seine Hilfe bei der AC1- und USB-Emulation +- Heiko Poppe fuer die freundliche Genehmigung zur Integration + des CP/M File-Commanders im JKCEMU sowie fuer seine Hilfe + bei der AC1-, K1520-Farbgrafikkarten- und USB-Emulation - Herbert Mathes fuer die freundliche Genehmigung zur Integration - der PC/M-Systemsoftware (BIOS, V-Tape, Debugger, CCP und BDOS) in JKCEMU + der PC/M-Systemsoftware (BIOS, V-Tape, Debugger, CCP und BDOS) inM JKCEMU - Holger Bretfeld fuer die leihweise Bereitstellung eines KC85/5 mit D004 - Jan Kuhnert fuer das intensive Testen des Emulators - Johann Spannenkrebs fuer seine Homepage http://www.ac1-info.de @@ -202,13 +203,18 @@ Besonders bedanken moechte ich mich bei: - John Elliott fuer die freundliche Genehmigung, Programmcodeteile aus dem Projekt LIBDSK uebernehmen zu duerfen (CRC-Berechnung fuer das CopyQM-Dateiformat) +- Joerg Felgentreu fuer seine Unterstuetzung bei der A5105-Emulation - Juergen Helas fuer das intensive Testen des Assemblers und Reassemblers +- Klaus Wilfling fuer die freundliche Genehmigung zur Integration + des von Ihm um Farbfunktionalitaet erweiterten EPOS + sowie fuer die Unterstuetzung bei der NANOS-Emulation +- Klaus Junge fuer die Unterstuetzung bei der NANOS-Emulation - Manfred Kramer fuer die freundliche Genehmigung zur Integration - der Systemsoftware des Kramer-MC in JKCEMU + der Systemsoftware des Kramer-MC im JKCEMU - Mario Leubner fuer die freundliche Genehmigung zur Integration der von ihm weiterentwickelten CAOS-, EDAS-, D004- und USB-Software - sowie fuer seine aktive Hilfe bei der KC85/2..5, - USB- und Festplattenemulation + sowie fuer seine seine sehr aktive und umfangreiche Hilfe + im KC85/2..5-, USB-, Disketten- und Festplattenumfeld - Norbert Richter fuer die Bereitstellung von Informationen und Software zum AC1 - Peter Salomon fuer seine Homepage http://www.robotron-net.de @@ -219,11 +225,12 @@ Besonders bedanken moechte ich mich bei: - Ralph Haensel fuer seine umfangreiche Hilfe bei der AC1-, Disketten-, Festplatten- und USB-Emulation - Rene Nitzsche fuer die leihweise Bereitstellung eines KC85/5 -- Rolf Weidlich fuer die Unterstuetzung bei der AC1- und LLC1-Emulation +- Rolf Weidlich fuer die Unterstuetzung bei der AC1-, LLC1- und LLC2-Emulation - Siegfried Schenk fuer die Bereitstellung von Informationen und Software zum LLC2 und zu den SCCH-Modulen - Steffen Gruhn fuer seine Hilfe bei der A5105- und KC-compact-Emulation -- Stephan Linz fuer seine Homepage http://www.li-pro.net +- Stephan Linz fuer seine Hilfe bei der PC/M-Emulation + sowie fuer seine Homepage http://www.li-pro.net - Thomas Scherrer fuer seine Z80-Seite http://www.z80.info - Torsten Paul fuer seinen Emulator KCemu (http://kcemu.sourceforge.net) und fuer die Bereitstellung von Informationen und ROM-Images diff --git a/cmd/makejar.cmd b/cmd/makejar.cmd new file mode 100644 index 0000000..7dc7c40 --- /dev/null +++ b/cmd/makejar.cmd @@ -0,0 +1,93 @@ +@ECHO OFF + +cd ..\src +jar cvmf Manifest.txt ..\jkcemu.jar ^ + lib\jkcemu_*.dll ^ + jkcemu\*.class ^ + jkcemu\audio\*.class ^ + jkcemu\base\*.class ^ + jkcemu\disk\*.class ^ + jkcemu\emusys\*.class ^ + jkcemu\emusys\a5105\*.class ^ + jkcemu\emusys\ac1_llc2\*.class ^ + jkcemu\emusys\etc\*.class ^ + jkcemu\emusys\huebler\*.class ^ + jkcemu\emusys\kccompact\*.class ^ + jkcemu\emusys\kc85\*.class ^ + jkcemu\emusys\lc80\*.class ^ + jkcemu\emusys\poly880\*.class ^ + jkcemu\emusys\z1013\*.class ^ + jkcemu\emusys\z9001\*.class ^ + jkcemu\emusys\zxspectrum\*.class ^ + jkcemu\etc\*.class ^ + jkcemu\filebrowser\*.class ^ + jkcemu\image\*.class ^ + jkcemu\joystick\*.class ^ + jkcemu\net\*.class ^ + jkcemu\print\*.class ^ + jkcemu\programming\*.class ^ + jkcemu\programming\assembler\*.class ^ + jkcemu\programming\basic\*.class ^ + jkcemu\programming\basic\target\*.class ^ + jkcemu\text\*.class ^ + jkcemu\tools\*.class ^ + jkcemu\tools\calculator\*.class ^ + jkcemu\tools\debugger\*.class ^ + jkcemu\tools\fileconverter\*.class ^ + jkcemu\tools\hexdiff\*.class ^ + jkcemu\tools\hexedit\*.class ^ + z80emu\*.class ^ + rom\a5105\*.bin ^ + rom\ac1\*.bin ^ + rom\bcs3\*.bin ^ + rom\c80\*.bin ^ + rom\huebler\*.bin ^ + rom\kc85\*.bin ^ + rom\kccompact\*.bin ^ + rom\kramermc\*.bin ^ + rom\lc80\*.bin ^ + rom\llc1\*.bin ^ + rom\llc2\*.bin ^ + rom\nanos\*.bin ^ + rom\pcm\*.bin ^ + rom\poly880\*.bin ^ + rom\sc2\*.bin ^ + rom\slc1\*.bin ^ + rom\vcs80\*.bin ^ + rom\z1013\*.bin ^ + rom\z9001\*.bin ^ + rom\z9001\*.gz ^ + rom\zxspectrum\*.bin ^ + images\chess\*.png ^ + images\debug\*.png ^ + images\disk\*.png ^ + images\edit\*.png ^ + images\file\*.png ^ + images\icon\*.png ^ + images\keyboard\*.png ^ + images\keyboard\a5105\*.png ^ + images\keyboard\kc85\*.png ^ + images\keyboard\lc80\*.png ^ + images\keyboard\poly880\*.png ^ + images\keyboard\sc2\*.png ^ + images\keyboard\z1013\*.png ^ + images\keyboard\z9001\*.png ^ + images\keyboard\zxspectrum\*.png ^ + images\nav\*.png ^ + disks\harddisks.csv ^ + disks\a5105\*.gz ^ + disks\kc85\*.gz ^ + disks\kccompact\*.gz ^ + disks\nanos\*.gz ^ + disks\pcm\*.gz ^ + disks\z1013\*.gz ^ + disks\z9001\*.gz ^ + help\*.htm ^ + help\bcs3\*.htm ^ + help\disk\*.htm ^ + help\kramermc\*.htm ^ + help\tips\*.htm ^ + help\tools\*.htm ^ + help\tools\basicc\*.htm ^ + help\z1013\*.htm +cd ..\cmd diff --git a/cmd/makejar.sh b/cmd/makejar.sh index 6b18ae4..587b2e3 100644 --- a/cmd/makejar.sh +++ b/cmd/makejar.sh @@ -48,6 +48,7 @@ jar cvmf Manifest.txt ../jkcemu.jar \ rom/lc80/*.bin \ rom/llc1/*.bin \ rom/llc2/*.bin \ + rom/nanos/*.bin \ rom/pcm/*.bin \ rom/poly880/*.bin \ rom/sc2/*.bin \ @@ -77,6 +78,7 @@ jar cvmf Manifest.txt ../jkcemu.jar \ disks/a5105/*.gz \ disks/kc85/*.gz \ disks/kccompact/*.gz \ + disks/nanos/*.gz \ disks/pcm/*.gz \ disks/z1013/*.gz \ disks/z9001/*.gz \ diff --git a/src/disks/a5105/a5105rbasicprg.dump.gz b/src/disks/a5105/a5105rbasicprg.dump.gz index 05692d2..a37901d 100644 Binary files a/src/disks/a5105/a5105rbasicprg.dump.gz and b/src/disks/a5105/a5105rbasicprg.dump.gz differ diff --git a/src/disks/a5105/a5105rbasicsys.dump.gz b/src/disks/a5105/a5105rbasicsys.dump.gz index 3414ff9..10ead39 100644 Binary files a/src/disks/a5105/a5105rbasicsys.dump.gz and b/src/disks/a5105/a5105rbasicsys.dump.gz differ diff --git a/src/disks/a5105/a5105scpxsys.dump.gz b/src/disks/a5105/a5105scpxsys.dump.gz index 3e67532..b928f21 100644 Binary files a/src/disks/a5105/a5105scpxsys.dump.gz and b/src/disks/a5105/a5105scpxsys.dump.gz differ diff --git a/src/disks/harddisks.csv b/src/disks/harddisks.csv index f23a360..ee367de 100644 --- a/src/disks/harddisks.csv +++ b/src/disks/harddisks.csv @@ -14,6 +14,7 @@ Hitachi DK227A-41 7944 16 63 Hitachi DK227A-50 10380 15 63 Hitachi DK237A-21 4200 16 63 Hitachi DK237A-32 6304 16 63 +PQI DJ0128M22RF0 512 16 32 PQI DJ0256M88RIO 503 16 63 Seagate ST157A 560 6 26 Seagate ST251 820 6 17 diff --git a/src/disks/kc85/kc85microdos.dump.gz b/src/disks/kc85/kc85microdos.dump.gz index 79300d0..80f3178 100644 Binary files a/src/disks/kc85/kc85microdos.dump.gz and b/src/disks/kc85/kc85microdos.dump.gz differ diff --git a/src/disks/kccompact/kccmicrodos.dump.gz b/src/disks/kccompact/kccmicrodos.dump.gz index 3ad65da..f1b4f1e 100644 Binary files a/src/disks/kccompact/kccmicrodos.dump.gz and b/src/disks/kccompact/kccmicrodos.dump.gz differ diff --git a/src/disks/nanos/epos20_64x32.dump.gz b/src/disks/nanos/epos20_64x32.dump.gz new file mode 100644 index 0000000..4d293ef Binary files /dev/null and b/src/disks/nanos/epos20_64x32.dump.gz differ diff --git a/src/disks/nanos/epos20_80x24.dump.gz b/src/disks/nanos/epos20_80x24.dump.gz new file mode 100644 index 0000000..b089200 Binary files /dev/null and b/src/disks/nanos/epos20_80x24.dump.gz differ diff --git a/src/disks/nanos/nanos22_80x25.dump.gz b/src/disks/nanos/nanos22_80x25.dump.gz new file mode 100644 index 0000000..53f91f5 Binary files /dev/null and b/src/disks/nanos/nanos22_80x25.dump.gz differ diff --git a/src/disks/pcm/pcmsys.dump.gz b/src/disks/pcm/pcmsys.dump.gz deleted file mode 100644 index 1c37651..0000000 Binary files a/src/disks/pcm/pcmsys.dump.gz and /dev/null differ diff --git a/src/disks/pcm/pcmsys330_64x16.dump.gz b/src/disks/pcm/pcmsys330_64x16.dump.gz new file mode 100644 index 0000000..4480533 Binary files /dev/null and b/src/disks/pcm/pcmsys330_64x16.dump.gz differ diff --git a/src/disks/pcm/pcmsys330_80x24.dump.gz b/src/disks/pcm/pcmsys330_80x24.dump.gz new file mode 100644 index 0000000..8e369bd Binary files /dev/null and b/src/disks/pcm/pcmsys330_80x24.dump.gz differ diff --git a/src/disks/z9001/z9cpasys.dump.gz b/src/disks/z9001/z9cpasys.dump.gz index 564e637..a088b2f 100644 Binary files a/src/disks/z9001/z9cpasys.dump.gz and b/src/disks/z9001/z9cpasys.dump.gz differ diff --git a/src/help/a5105.htm b/src/help/a5105.htm index 244059d..9910795 100644 --- a/src/help/a5105.htm +++ b/src/help/a5105.htm @@ -12,14 +12,21 @@

    Hinweise zur Emulation des A5105 (BIC, ALBA PC 1505)

  • 1.2.1. Sound-Beispielprogramme
  • -
  • 1.3. V24-Schnittstelle mit Drucker
  • +
  • + 1.3. V24-Schnittstelle mit Drucker +
  • 2. Im ROM enthaltene Software
  • 3. Enthaltene Diskettenabbilder
  • @@ -29,17 +36,20 @@

    Hinweise zur Emulation des A5105 (BIC, ALBA PC 1505)

    4.1. BASIC-Programme im Texteditor öffnen
  • - 4.2. BASIC-Programme speichern und laden + 4.2. BASIC-Programme speichern und laden +
  • +
  • + 4.3. Dateien in den Arbeistspeicher laden
  • -
  • 4.3. Zeichensatz
  • +
  • 4.4. Zeichensatz
  • - 4.4. Größe und Seitenverhältnis der Bildschirmausgabe + 4.5. Größe und Seitenverhältnis der Bildschirmausgabe
  • - 4.5. Einfügen von Text aus der Zwischenablage + 4.6 Einfügen von Text aus der Zwischenablage
  • - 4.6. A5105-Bilddateien + 4.7. A5105-Bilddateien
  • @@ -63,7 +73,7 @@

    1. Emulierte Hardware

  • USB-Anschluss (Vinculum VDIP Modul) - an der E/A-Basisadresse FCh + an den E/A-Basisadressen 2Ch und FCh
  • @@ -103,52 +113,77 @@

    1.2. Sound-Generator



    1.2.1. Sound-Beispielprogramme

    + Hier finden Sie einige Beispielprogramme, + die die Fähigkeiten des Sound-Generators demonstrieren. + Das Programm Hubschrauber basiert auf dem entsprechenden + Beispielprogramm in der Dokumentation + BILDUNGSCOMPUTER robotron A5105 - Das Musiksystem. + Die anderen Programme stammen vom JKCEMU-Entwickler selbst. +

    - + + - - @@ -176,6 +211,8 @@

    2. Im ROM enthaltende Software



    3. Enthaltene Diskettenabbilder

    + JKCEMU enthält für die A5105-Emulation + folgende Diskettenabbilder:
    + Für die Nutzung der Diskettenabbilder muss die Emulation + der Floppy-Disk-Station aktiviert werden. +

    + +

    3.2. Hinweise zu den RBASIC-Disketten

    + Die RBASIC-Programme haben die Endung .bas + und werden gestartet mit: +

    +   run "programm.bas" +

    + Anstelle von programm.bas ist der richtige Dateiname anzugeben. + Das BIC-Demo-Programm auf der RBASIC-Systemdiskette hat keine Dateiendung + und wird deshalb gestartet mit: +

    +   run "bicdemo" +

    + Das Verzeichnis lässt man sich anzeigen mit: +

    +   files +

    + oder z.B. nur die BASIC-Dateien mit: +

    +   files "*.bas" +

    -

    3.1. Hinweise zur SCPX-Systemdiskette

    +

    3.2. Hinweise zur SCPX-Systemdiskette

    Das SCPX verlangt beim Booten eine beschreibbare Diskette. Da die im Emulator integrierten Diskettenabbilder aber nicht beschreibbar sind, @@ -205,15 +266,17 @@

    3.1. Hinweise zur SCPX-Systemdiskette

    Diese können Sie jedoch mit einem Tastendruck übergehen und so trotzdem mit SCPX arbeiten.

    - Die SCPX-Systemdiskette enthält auch Treiber für - die beiden RAM-Floppies + Die SCPX-Systemdiskette ist als Laufwerk A: sichtbar + und enthält auch Treiber für die beiden RAM-Floppies (RAFBIC20.COM und RAFBIC24.COM).

    4. Sonstiges

    -

    4.1. BASIC-Programme im Texteditor öffnen

    +

    + 4.1. BASIC-Programme im Texteditor öffnen +

    Der Menüpunkt DateiBASIC-Programm im Texteditor öffnen... ist in der A5105-Emulation nicht aktiv, @@ -229,22 +292,22 @@

    4.1. BASIC-Programme im Texteditor öffnen

    (siehe Drucken).

    -

    4.2. BASIC-Programme speichern und laden

    - JKCEMU bietet eine spezielle Unterstützung - für das Speichern und Laden von BASIC-Programmen - unter dem Betriebssystem RBASIC. +

    4.2. BASIC-Programme speichern und laden

    + JKCEMU bietet eine spezielle Unterstützung für das + Speichern und Laden von BASIC-Programmen. Zum Speichern nutzen Sie bitte die Funktion BASIC-Programm speichern... im Menü Datei - und speichern das Programm als - RBASIC-Datei. + und speichern das jeweilge Programm als + BASIC-/RBASIC-Programmdatei (*.bas). + Alternativ ist auch das Speichern als + Headersave-Datei (*.z80) + mit dem Dateityp B möglich.

    - Nur wenn Sie das BASIC-Programm so speichern, - werden später beim Laden der Datei die Systemzellen des - BASIC-Interpreters richtig angepasst. - Sie können ein RBASIC-Programm zwar auch in einem anderen - Dateiformat speichern, jedoch werden dann beim späteren Laden - die Systemzellen nicht angepasst, - und das Programm lässt sich nicht mehr richtig nutzen. + Wenn Sie ein BASIC-Programm in einem anderen Dateiformat speichern, + erkennt JKCEMU später beim Laden der Datei nicht mehr, + dass es sich um ein BASIC-Programm handelt und passt die + Systemzellen des BASIC-Interpreters nicht an. + Das geladene Programm lässt sich dann nicht nutzen.

    Haben Sie in den Einstellungen zum A5105 die Emulation des Floppy Disk Moduls aktiviert, @@ -254,7 +317,18 @@

    4.2. BASIC-Programme speichern und laden

    und von dort wieder laden.

    -

    4.3. Zeichensatz

    +

    + 4.3. Dateien in den Arbeistspeicher laden +

    + In der SCPX-Betriebsart wird durch das Betriebssystem automatisch + der ROM zyklisch eingeblendet. + Damit bei diesem Verhalten das Laden von Dateien in den Arbeitsspeicher + mit Hilfe der Emulatorfunktionen sicher funktioniert, + werden Dateien immer in den RAM geladen, + auch wenn dieser gerade nicht eingeblendet ist. +

    + +

    4.4. Zeichensatz

    Der A5105 enthält einen programmierbaren Zeichengenerator, der standardmäßig mit dem alten DOS-Zeichensatz (Codepage 437) gefüllt ist. @@ -267,7 +341,9 @@

    4.3. Zeichensatz

    wie der Zeichengenerator tatsächlich programmiert ist.

    -

    4.4. Größe und Seitenverhältnis der Bildschirmausgabe

    +

    + 4.5. Größe und Seitenverhältnis der Bildschirmausgabe +

    Der A5105 bietet verschiedene Bildschirmauflösungen. Wenn zwischen diesen umgeschaltet wird (z.B. mit dem BASIC-Befehl screen 1 in den 80-Zeichenmodus), @@ -282,7 +358,7 @@

    4.4. Größe und Seitenverhältnis der Bil

    - 4.5. Einfügen von Text aus der Zwischenablage + 4.6. Einfügen von Text aus der Zwischenablage

    Das Einfügen von Text aus der Zwichenablage erfolgt gewöhnlich in der Form, dass für jedes einzufügende Zeichen @@ -303,7 +379,7 @@

    und Sie müssen die Option ausschalten.

    -

    4.6. A5105-Bilddateien

    +

    4.7. A5105-Bilddateien

    Der JKCEMU Bildbetrachter unterstützt sowohl lesend als auch schreibend A5105-Bilddateien, die dem RBASIC-Grafikmodus SCREEN 5 diff --git a/src/help/ac1.htm b/src/help/ac1.htm index e0cfbbd..df1dfc1 100644 --- a/src/help/ac1.htm +++ b/src/help/ac1.htm @@ -25,7 +25,7 @@

    Hinweise zur Emulation des AC1

    3. BASIC @@ -164,7 +164,7 @@

    1. Emulierte Hardware

  • USB-Anschluss (Vinculum VDIP Modul) - an der E/A-Basisadresse FCh + an den E/A-Basisadresse DCh und FCh
  • Joystick
  • @@ -255,7 +255,7 @@

    1.3. Farbgrafik

    Ist die Emulation der Farbgrafik aktiviert, muss auch das Monitorprogramm den Farbspeicher initialisieren. Anderenfalls sehen Sie nur ein buntes Zufallsbild. - Da aber die meisten der in JKCEMU integrierten AC1-Monitorprogramme + Da aber die meisten der im JKCEMU integrierten AC1-Monitorprogramme keine Farbgrafik kennen und somit auch keinen Farbspeicher initialisieren, erledigt das der Emulator bei jedem RESET automatisch, allerdings nur für die betreffenden integrierten Monitorprogramme. @@ -365,9 +365,9 @@

    1.6. SCCH-Modul 3

    emuliert JKCEMU die theoretisch möglichen 1 MByte. Das hat den Vorteil, dass Software, die diesen zusätzlichen RAM verwendet, - nicht speziell für den Einsatz in JKCEMU konfiguriert werden muss, + nicht speziell für den Einsatz im JKCEMU konfiguriert werden muss, denn auf jeder Adresse, wo die jeweilige Software RAM voraussetzt, - wird sie in JKCEMU auch RAM vorfinden. + wird sie im JKCEMU auch RAM vorfinden.

    1.7. SCCH-Inversschaltung

    @@ -391,7 +391,7 @@

    1.9. Grafiktaste (F2)

    über die Tastatur möglich. Dies muss allerdings vom jeweiligen Anwendungsprogramm auch so unterstützt werden. - Das in JKCEMU enthaltene Grafik/Sound-BASIC + Das im JKCEMU enthaltene Grafik/Sound-BASIC unterstützt die Grafiktaste.

    @@ -601,32 +601,39 @@

    2. Im ROM enthaltene Software

    3. BASIC

    - 3.1. Laden, Speichern und Öffnen von BASIC-Programmen + 3.1. BASIC-Programme speichern, laden und öffnen

    - JKCEMU bietet spezielle Unterstützung für das Laden - und Speichern von BASIC-Programmen sowie für das Öffnen - von im Arbeitsspeicher befindlichen BASIC-Programmen im Texteditor. + JKCEMU bietet eine spezielle Unterstützung für das + Speichern und Laden von BASIC-Programmen + sowie für das Öffnen von im Arbeitsspeicher befindlichen + BASIC-Programmen im Texteditor. Die entsprechenden Funktionen finden Sie im Menü Datei.

    Folgende BASIC-Interpreter werden unterstützt:

    Beim Speichern von BASIC-Programmen erscheint ein Fenster zur Auswahl des Dateiformats. - Speichern Sie bitte im Headersave-Format, - welches bereits vorausgewählt ist. - Nur dann ist sichergestellt, dass Sie das BASIC-Programm - später auch wieder problemlos laden können. - Bei einem anderen Dateiformat werden nämlich beim Laden - die Systemzellen des BASIC-Interpreters nicht angepasst, - und Sie können das BASIC-Programm im Interpreter nicht richtig nutzen. + Speichern Sie bitte Mini-BASIC-Programme als + Headersave-Datei (*.z80) + mit dem Dateityp b und die anderen BASIC-Programme als + BASIC-/RBASIC-Programmdatei (*.bas) + oder alternativ auch im Headersave-Format mit dem Dateityp B. +

    + Wenn Sie ein BASIC-Programm in einem anderen Dateiformat speichern, + erkennt JKCEMU später beim Laden der Datei nicht mehr, + dass es sich um ein BASIC-Programm handelt und passt die + Systemzellen des BASIC-Interpreters nicht an. + Das geladene Programm lässt sich dann nicht nutzen.

    3.2. Grafik/Sound-BASIC

    diff --git a/src/help/audio.htm b/src/help/audio.htm index 75fb1de..6178f92 100644 --- a/src/help/audio.htm +++ b/src/help/audio.htm @@ -14,8 +14,10 @@

    Audio/Kassette

    2.3. Daten vom Audio-Eingang lesen
  • 2.4. Sound-Datei speichern
  • -
  • 2.5. Sound-Datei lesen
  • -
  • 2.6. KC-TAP-Datei lesen
  • +
  • 2.5. Sound-/Tape-Datei lesen
  • +
  • + 2.6. Letzte Sound-/Tape-Datei (noch einmal) lesen +
  • @@ -68,13 +70,6 @@

    Wählen Sie diese Funktion, wenn Sie die Töne hören möchten, die ein Programm zum Zwecke der Unterhaltung ausgibt (z.B. akustische Untermalung bei Spielprogrammen). -

    - Bei dieser Funktion wird die Ausführungsgeschwindigkeit des Emulators - konstant gehalten, um einen gleichmäßigen Programmablauf - zu gewährleisten. - Eine ununterbrochene Versorgung des Audio-Systems mit Daten kann - deshalb nicht immer gewährleistet werden, - so dass kurze Unterbrechungen in der Tonausgabe auftreten können.

    Achtung! Manche der emulierten Computer besitzen zwei Audio-Ausgänge, einen für den Kassettenrecorder @@ -91,11 +86,6 @@

    2.2. Daten am Audio-Ausgang ausgeben

    mit Kassettenrecorderanschluss übertragen möchten. Tipps & Tricks dazu finden Sie unter Dateien auf Magnettonband speichern. -

    - JKCEMU versucht sicherzustellen, dass das Audio-System - ununterbrochen mit Daten versorgt wird. - Die Ausführungsgeschwindigkeit des Emulators richtet sich - nach den Anforderungen des Audio-Systems und kann somit variieren.

    Achtung! Manche der emulierten Computer besitzen zwei Audio-Ausgänge, einen für den Kassettenrecorder @@ -121,41 +111,42 @@

    2.3. Daten vom Audio-Eingang lesen

    nicht zur Verfügung.

    -

    2.4. Sound-Datei speichern

    +

    2.4. Sound- oder Tape-Datei speichern

    Bei dieser Funktion werden die Daten, die am emulierten Anschluss für das Magnettonbandgerät ausgegeben werden, - in eine Sound-Datei geschrieben. - Die Sound-Datei beginnt mit dem ersten Phasenwechsel + in eine Sound- oder Tape-Datei geschrieben. + Die Datei beginnt mit dem ersten Phasenwechsel nach Aktivierung der Funktion und endet beim letzten Phasenwechsel vor Deaktivierung, d.h, die Datei enthält weder an ihrem Anfang noch an ihrem Ende eine Pause.

    Die Funktion deaktiviert sich automatisch, - wenn etwa eine Sekunde lang kein Phasenwechsel - und damit kein Ton mehr ausgegeben wurde. + wenn in den ausgegebenen Tönen eine Pause größer + als drei Sekunden auftritt.

    - Die eigentliche Sound-Datei wird erst beim Deaktivieren - erzeugt, d.h., bis dahin merkt sich JKCEMU die Audio-Daten im Speicher. - Aus diesem Grund kann diese Funktion auch nicht über + Die eigentliche Datei wird erst beim Deaktivieren erzeugt, + d.h., bis dahin merkt sich JKCEMU die Audio-Daten im Speicher. + Aus diesem Grund kann diese Funktion nicht über einen längeren Zeitraum aktiviert bleiben.

    - Die mit der Audio-Funktion Sound-Datei speichern erzeugte Dateien - enthalten nur Rechteckschwingungen, d.h. nur zwei Pegelzustände. + Die mit der Audio-Funktion Sound- oder Tape-Datei speichern + erzeugte Dateien enthalten nur Rechteckschwingungen, + d.h. nur zwei Pegelzustände. Aus diesem Grund lassen sich diese Dateien auch gut komprimieren. JKCEMU bietet die Möglichkeit, beim Speichern von Sound-Dateien diese gleich mit GZIP komprimieren zu lassen. Wenn Sie das nutzen möchten, dann hängen Sie an den Dateinamen - hinter der Endung für das Sound-Dateiformat einfach nur - die Endung .gz an, also z.B.: ausgabe.wav.gz + hinter der Endung einfach nur die Endung .gz an, + also z.B.: ausgabe.wav.gz

    -

    2.5. Sound-Datei lesen

    +

    2.5. Sound- oder Tape-Datei lesen

    Diese Funktion emuliert den Anschluss für das Magnettonbandgerät, - indem die Audio-Daten von einer Sound-Datei gelesen werden. + indem die Audio-Daten von einer Sound- oder Tape-Datei gelesen werden. Ein Fortschrittsbalken visualisiert den Einleseprozess. - Es können auch mit GZIP komplrimierte Sound-Dateien gelesen werden, + Es können auch mit GZIP komprimierte Sound-Dateien gelesen werden, wenn sie die Dateiendung .gz haben, also z.B.: datei.wav.gz

    @@ -167,7 +158,7 @@

    2.5. Sound-Datei lesen

    Ist das gerade nicht der Fall, bleibt der Fortschrittsbalken stehen.

    Achtung! Nach dem Aktivieren dieser Audio-Funktion - und dem Auswählen der Sound-Datei müssen Sie noch + und dem Auswählen der Datei müssen Sie noch auf Abspielen drücken, um das eigentliche Einlesen zu starten. Sie können auch jederzeit mit Pause anhalten und @@ -178,14 +169,19 @@

    2.5. Sound-Datei lesen

    ausgeschaltet, so dass der Emulator mit maximaler Geschwindigkeit läuft.

    - -

    2.6. KC-TAP-Datei lesen

    - Diese Funktion ist ähnlich wie Sound-Datei lesen, - nur mit dem Unterschied, dass die Eingangsdatei keine Sound- - sondern eine KC-TAP-Datei ist. + Hinweis! Sie können die Audio-Funktion + Sound- oder Tape-Datei lesen auch aktivieren, + indem Sie per Drag&Drop eine passende Datei in das + Audio/Kassette-Fenster ziehen und über dem Feld Datei + im unteren Fensterbereich loslassen.

    - Während des Einlesens werden die Audio-Daten erzeugt - und dem emulierten Kassettenrecorderanschluss zugeführt. + +

    + 2.6. Letzte Sound-/Tape-Datei (noch einmal) lesen +

    + Diese Audio-Funktion entspricht der vorherigen nur mit dem Unterschied, + dass kein Dateiauswahldialog erscheint, sondern die zuletzt gelesene + oder gespeicherte Sound- bzw. Tape-Datei wieder eingelesen wird.

    @@ -240,14 +236,8 @@

    3.4. Mithören

    4. Hinweise

    4.1. Lautstärke

    - Das JKCEMU-Audio-Fenster enthält einen Schieberegler - für die Lautstärke. - Dieser ist jedoch nur aktiv, - wenn das Audio-System und die installierten Java-Version - die Regelung der Lautstärke für den geöffneten - Audio-Kanal auch unterstützten. - Ist das nicht der Fall, müssen Sie auf die entsprechende - Bedienungssoftware des Betriebssystems zurückgreifen. + Die Lautstärke regeln Sie mit der entsprechenden + Bedienungssoftware des Betriebssystems auf Ihrem Computers.

    4.2. Sonstiges

    diff --git a/src/help/autoloadinput.htm b/src/help/autoloadinput.htm new file mode 100644 index 0000000..5515017 --- /dev/null +++ b/src/help/autoloadinput.htm @@ -0,0 +1,33 @@ + + +

    AutoLoad und AutoInput

    + AutoLoad und AutoInput sind Funktionen zur Herstellung eines + benutzerabhängigen Initialzustandes des jeweils emulierten Systems + und dienen der Erhöhung des Komforts für den Anwender. +

    + AutoLoad lädt nach jedem emulierten Einschalten bzw. nach jedem RESET + automatisch Dateien in den Arbeitsspeicher. + AutoInput führt nach jedem emulierten Einschalten bzw. nach jedem RESET + automatisch Tastatureingaben aus. + Beide Funktionen arbeiten voneinander unabhängig, + lassen sich aber gut miteinander kombinieren. + So kann man sich z.B. mit AutoLoad eine ausfühbare Programmdatei laden + und mit AutoInput nach einer kurzen Wartezeit das Kommando zum Starten + des Programms ausführen lassen. +

    + AutoLoad und AutoInput stehen für viele, nicht aber für alle + von JKCEMU emulierten Systeme zur Verfügung. + Bei den unterstützten Systemen finden Sie in den den jeweiligen + Einstellungen die beiden Reiter AutoLoad und AutoInput. + Darin können Sie beliebig viele Einträge anlegen, + die nach dem emulierten Einschalten und nach RESET von oben nach unten + der Reihe nach abgearbeitet werden. + Bei jedem Eintrag können Sie auch eine Zeit angeben, + die JKCEMU wartet, bevor der jeweilige Eintrag ausgeführt wird. +

    + Die automatischen Tastatureingaben bei AutoInput erfolgen über + die Funktion zum Einfügen von Text in den Emulator. + Je nach emulierten System kann dies unterschiedlich lang dauern. + + + diff --git a/src/help/bcs3.htm b/src/help/bcs3.htm index 35fd535..9965b34 100644 --- a/src/help/bcs3.htm +++ b/src/help/bcs3.htm @@ -1,6 +1,13 @@

    Hinweise zur Emulation des BCS3

    + Der BCS3 ist ein von Eckhard Schiller entwickelter Selbstbaucomputer, + dessen Bauanleitung 1985 in der Zeitschrift + Radio Fernsehen Elektronik erschienen ist. + Dieser Computer zeichnet sich durch ein ausgefuchstes Konzept aus, + welches trotzt minimalem Bauelementeaufwand eine akzeptable + Rechenleistung bietet. +


    -

    1. Emulierte Hardware

  • + + + + +
    + + + 100 PRINT:PRINT "Hubschrauber"
    + 110 SOUND 0,111
    + 120 SOUND 1,0
    + 130 SOUND 6,30
    + 140 SOUND 8,16
    + 150 SOUND 11,44
    + 160 SOUND 12,1
    + 170 SOUND 13,14
    + 180 SOUND 7,9
    + 190 PRINT:PRINT "Abbruch mit beliebiger Taste"
    + 200 IF LEN(INKEY$)=0 THEN 200
    + 210 SOUND 7,0
    + 220 END +
    +
      - 10 PRINT:PRINT "Explosion"
    - 20 SOUND 5,20
    - 30 SOUND 11,0
    - 40 SOUND 12,31
    - 50 SOUND 13,0
    - 60 SOUND 9,16
    - 70 SOUND 7,16
    + 10 PRINT:PRINT "Explosion"
    + 20 SOUND 5,20
    + 30 SOUND 11,0
    + 40 SOUND 12,31
    + 50 SOUND 13,0
    + 60 SOUND 9,16
    + 70 SOUND 7,16
    80 END
      + - 100 PRINT:PRINT "Meeresrauschen"
    - 110 SOUND 6,20
    - 120 SOUND 8,0
    - 130 SOUND 9,16
    - 140 SOUND 10,8
    - 150 SOUND 11,0
    - 160 SOUND 12,50
    - 170 SOUND 13,14
    - 180 SOUND 7,48
    - 190 PRINT:PRINT "Abbruch mit beliebiger Taste"
    - 200 IF LEN(INKEY$)=0 THEN 200
    - 210 SOUND 7,0
    + 100 PRINT:PRINT "Meeresrauschen"
    + 110 SOUND 6,20
    + 120 SOUND 8,0
    + 130 SOUND 9,16
    + 140 SOUND 10,8
    + 150 SOUND 11,0
    + 160 SOUND 12,50
    + 170 SOUND 13,14
    + 180 SOUND 7,48
    + 190 PRINT:PRINT "Abbruch mit beliebiger Taste"
    + 200 IF LEN(INKEY$)=0 THEN 200
    + 210 SOUND 7,0
    220 END
      + - 100 PRINT:PRINT "Schranke"
    - 110 SOUND 2,200
    - 120 SOUND 3,1
    - 130 SOUND 9,16
    - 140 SOUND 11,0
    - 150 SOUND 12,40
    - 160 SOUND 13,8
    - 170 SOUND 7,2
    - 180 PRINT:PRINT "Abbruch mit beliebiger Taste"
    - 190 IF LEN(INKEY$)=0 THEN 190
    - 200 SOUND 7,0
    + 100 PRINT:PRINT "Schranke"
    + 110 SOUND 2,200
    + 120 SOUND 3,1
    + 130 SOUND 9,16
    + 140 SOUND 11,0
    + 150 SOUND 12,40
    + 160 SOUND 13,8
    + 170 SOUND 7,2
    + 180 PRINT:PRINT "Abbruch mit beliebiger Taste"
    + 190 IF LEN(INKEY$)=0 THEN 190
    + 200 SOUND 7,0
    210 END
    + --ff
    + --findfiles
    +
    + java -jar jkcemu.jar --ff [<Verzeichnis>]
    + java -jar jkcemu.jar --findfiles [<Verzeichnis>]
    +
    + Dateisuche starten,
    + Optional kann ein Verzeichnis angegeben werden, + welches durchsucht werden soll. +
    --hd
    diff --git a/src/help/copyright.htm b/src/help/copyright.htm index a2a462f..15d411a 100644 --- a/src/help/copyright.htm +++ b/src/help/copyright.htm @@ -35,7 +35,7 @@

    1. Programmcode

    Autoren der Programme compress.c und gifcompress.c, - auf denen die Implementierung des in JKCEMU enthaltenen + auf denen die Implementierung des im JKCEMU enthaltenen LZW-Encoders basiert (wird benötigt für das Erzeugen animierter GIF-Dateien (Bildschirmvideos)) @@ -53,9 +53,8 @@

    2. ROM- und Disketteninhalte

    Modifizierung und Weitergabe von JKCEMU eingeräumt werden, gelten nicht für die ROM- und Disketteninhalte! Jegliche Benutzung dieser ROM- und Disketten-Images außerhalb - von JKCEMU oder außerhalb eines rein privaten, - nicht kommerziellen Umfeldes müssen Sie im Zweifelsfall - mit den Urhebern bzw. deren Rechtsnachfolgern klären. + von JKCEMU müssen Sie im Zweifelsfall mit den Urhebern + bzw. deren Rechtsnachfolgern klären.

    Die Urheberschaften an den ROM- und Disketteninhalten liegen bei:
      @@ -67,11 +66,16 @@

      2. ROM- und Disketteninhalte

      of their copyrighted material but retain that copyright. +
    • + Ingenieurhochschule für Seefahrt Warnemünde/Wustrow + (NANOS 2.2) +
    • International Research Institute for Management Sciences (IRIMS), Moskau (MicroDOS)
    • Universität Rostock (MicroDOS)
    • +
    • VEB Datenverarbeitungszentrum Rostock (EPOS)
    • VEB Meßelektronik Dresden (A5105, KC85/1, KC87, Z9001)
    • VEB Mikroelektronik Erfurt (LC80, SC2)
    • @@ -105,8 +109,10 @@

      2. ROM- und Disketteninhalte

    • Frank Heyder (Monitorprogramm 3.1 und MiniBASIC für AC1)
    • Frank Prüfer (S/P-BASIC V3.3 für BCS3)
    • Harald Saegert (RBASIC-Programme für A5105)
    • +
    • Heiko Poppe (CP/M File-Commander)
    • Hertbert Mathes (PC/M)
    • Joachim Czepa (C-80)
    • +
    • Klaus Wilfling (EPOS-Anpassungen)
    • Klaus-Peter Evert † (Hübler/Evert-MC)
    • Manfred Kramer (Kramer-MC)
    • diff --git a/src/help/disk/convertdiskimg.htm b/src/help/disk/convertdiskimg.htm deleted file mode 100644 index af149b8..0000000 --- a/src/help/disk/convertdiskimg.htm +++ /dev/null @@ -1,41 +0,0 @@ - - -

      Diskettenabbilddateien umwandeln

      - Es gibt verschiedene Formate - für Abbilddateien. - Gelegentlich besteht die Notwendigkeit, - diese Formate ineinander umzuwandeln. -

      - JKCEMU bietet die Möglichkeit, - eine gegebene Diskettenabbilddatei in eine neue Datei - mit einem anderen Format zu exportieren: -
      -
        -
      1. - Markieren Sie im Datei-Browser die Abbilddatei, - die Sie in ein anderes Format umwandeln möchten. -
      2. -
      3. - Rufen Sie die entsprechende Funktion im Menü Datei, - Untermenü Exportieren in auf. -
      4. -
      5. - Sie werden nach dem Namen der neuen Datei gefragt. - Wählen Sie den Namen aus oder geben Sie einen ein. -
      6. -
      7. - Wenn der Export fertig ist, erscheint eine entsprechende Meldung. -
      8. -
      -
      - Als Zielformate sind - einfache Abbilddateien - und AnaDisk-Dateien - möglich. -

      - Achtung! Ein Export in ein anderes Format ist nur möglich, - wenn die im Quellformat gespeicherte physische Diskettenstruktur - im Zielformat auch abbildbar ist. - - - diff --git a/src/help/disk/creatediskimg.htm b/src/help/disk/creatediskimg.htm index 197b0a3..4559f4e 100644 --- a/src/help/disk/creatediskimg.htm +++ b/src/help/disk/creatediskimg.htm @@ -1,83 +1,111 @@ -

      Diskettenabbilddatei erstellen

      +

      Abbilddatei erstellen

      -

      1. Abbilddatei von Diskette erstellen

      - Diese Funktion liest eine reale Diskette ein - und speichert ihren Inhalt in eine - einfache Abbilddatei. - Rufen Sie dazu im Hauptfenster den Menüpunkt - ExtraWerkzeuge → - Abbilddatei von Diskette erstellen... auf. -

      - Achtung! Beim Zugriff auf Disketten gelten einige - Einschränkungen (siehe hier). - Es kann deshalb nicht garantiert werden, - dass das Erstellen von Abbilddateien von Disketten in jedem Fall - möglich ist. -

      +

      1. Abbilddatei von Diskette oder einem anderen Datenträger erstellen

      + Sie können eine + einfache Abbilddatei + von einer Diskette oder einem anderen Datenträger erstellen, + wenn im oder am Emulatorrechner ein entsprechendes Laufwerk vorhanden + bzw. angeschlossen ist und dieses vom Betriebssystem unterstützt wird. + Bei Diskettenlaufwerken ist besonders wichtig, + dass das physische Diskettenformat + (Anzahl Spuren, Anzahl Sektoren pro Spur, Sektorgröße) + unterstützt wird. + Zum Erstellen der Abbilddatei rufen Sie im Hauptfenster den Menüpunkt + ExtraWerkzeuge → + Abbilddatei von Datenträger erstellen... auf. +

      + Achtung! Der Zugriff auf physische Laufwerke hängt + von verschiedenen Faktoren ab und kann deshalb nicht auf + allen Plattformen und Betriebssystemumgebungen garantiert werden. + Möglicherweise steht die Funktion auf Ihrem System + nicht zur Verfügung. +

      +

      2. Abbilddatei manuell erstellen

      + JKCEMU bietet die Möglichkeit, + Diskettenabbilddateien manuell zu erstellen, + die dem Diskettenformat von CP/M 2.2 entsprechen. + Das Werkzeug dazu finden Sie im Hauptfenster im Menü + ExtraWerkzeuge → + CP/M-Diskettenabbilddatei manuell erstellen. + Es kann aber auch als eigenständiges Programm + ohne Emulator gestartet werden. + Dazu gibt man beim Aufruf von JKCEMU in der Kommandozeile + die Option --dc oder --diskcreator an. +

      + Das Fenster des Werkzeugs enthält zwei Unterfenster, + zwischen denen Sie über die beiden Reiter umschalten können. + Im ersten Reiter fügen Sie die Dateien hinzu, + die die Abbilddatei enthalten soll. + Das erledigen Sie mit Hilfe des Menüpunkts Datei → + Hinzufügen... oder mittels Drag&Drop. + Dateien, die in die Zwischenablage kopiert wurden, + können Sie mit dem Menüpunkt Bearbeiten → + Einfügen hinzufügen. +

      + Da der Dateiname in der Abbilddatei dem 8.3-Format entsprechen muss, + werden Sie bei Dateien, die dieser Konvention nicht entsprechen, + nach einem passenden Namen gefragt. + Sollte die Reihenfolge der Dateien in der Abbilddatei eine Rolle spielen, + so können Sie diese mit den Pfeiltasten in der Werkzeugleiste + verändern. +

      + Im zweiten Unterfenster legen Sie das Format fest. + Dabei muss sowohl das physische Format (Anzahl Seiten, Anzahl Spuren, + Sektoren pro Spur und Sektorgröße) als auch + das logisch Format (Blockgröße, Blocknummernformat + und Directory-Größe) angegeben werden. + Für die gebräuchstlichen CP/M-Varianten einiger emulierter + Computer-Typen gibt es jeweils einen eigenen Auswahlknopf, + so dass Sie dafür das Format im Detail nicht angeben müssen. +

      + Enthält das ausgewählte Format eine oder mehrere Systemspuren, + so können Sie im unteren Bereich eine Datei angeben, + die dann in die Systemspuren kopiert wird. +

      + Nachdem Sie alle gewünschten Dateien hinzugefügt + und das Format ausgewählt haben, + müssen Sie noch die eigentliche Abbilddatei erzeugen. + Das erledigen Sie mit dem Menüpunkt + Abbilddatei speichern.... + Dabei werden Sie nach dem Dateinamen gefragt. + Aus der Endung des Dateinamens ermittelt JKCEMU das Dateiformat. + Wählen Sie deshalb einen Dateinamen mit einer für + Diskettenabbilddateien üblichen Endung. +

      -

      2. Abbilddatei manuell erstellen

      - JKCEMU bietet die Möglichkeit, - Diskettenabbilddateien manuell zu erstellen, - die dem Diskettenformat von CP/M 2.2 entsprechen. - Das Werkzeug dazu finden Sie im Hauptfenster im Menü - ExtraWerkzeuge → - CP/M-Diskettenabbilddatei manuell erstellen. - Es kann aber auch als eigenständiges Programm - ohne Emulator gestartet werden. - Dazu gibt man beim Aufruf von JKCEMU in der Kommandozeile - die Option --dc oder --diskcreator an. -

      - Das Fenster des Werkzeugs enthält zwei Unterfenster, - zwischen denen Sie über die beiden Reiter umschalten können. - Im ersten Reiter fügen Sie die Dateien hinzu, - die die Abbilddatei enthalten soll. - Das erledigen Sie mit Hilfe des Menüpunkts Datei → - Hinzufügen... oder mittels Drag&Drop. - Dateien, die in die Zwischenablage kopiert wurden, - können Sie mit dem Menüpunkt Bearbeiten → - Einfügen hinzufügen. -

      - Da der Dateiname in der Abbilddatei dem 8.3-Format entsprechen muss, - werden Sie bei Dateien, die dieser Konvention nicht entsprechen, - nach einem passenden Namen gefragt. - Sollte die Reihenfolge der Dateien in der Abbilddatei eine Rolle spielen, - so können Sie diese mit den Pfeiltasten in der Werkzeugleiste - verändern. -

      - Im zweiten Unterfenster legen Sie das Format fest. - Dabei muss sowohl das physische Format (Anzahl Seiten, Anzahl Spuren, - Sektoren pro Spur und Sektorgröße) als auch - das logisch Format (Blockgröße, Blocknummernformat - und Directory-Größe) angegeben werden. - Für die gebräuchstlichen CP/M-Varianten einiger emulierter - Computer-Typen gibt es jeweils einen eigenen Auswahlknopf, - so dass Sie dafür das Format im Detail nicht angeben müssen. -

      - Enthält das ausgewählte Format eine oder mehrere Systemspuren, - so können Sie im unteren Bereich eine Datei angeben, - die dann in die Systemspuren kopiert wird. -

      - Nachdem Sie alle gewünschten Dateien hinzugefügt - und das Format ausgewählt haben, - müssen Sie noch die eigentliche Abbilddatei erzeugen. - Das erledigen Sie mit dem Menüpunkt - Abbilddatei speichern.... - Dabei werden Sie nach dem Dateinamen gefragt. - Aus der Endung des Dateinamens ermittelt JKCEMU das Dateiformat. - Wählen Sie deshalb einen Dateinamen mit einer für - Diskettenabbilddateien üblichen Endung. -

      +

      2.1. Interleave

      + Im Unterfenster Format werden mehrere Diskettenformate angezeigt, + bei denen ein Interleave angegeben ist + bzw. die sich nur im Interleave unterscheiden. + Interleave dient zur Reduzierung der Zugriffszeit beim sequenziellen + Lesen und Schreiben von bzw. auf realen Disketten + und hat im Emulator keinerlei Wirkung. + Der Grund, warum hier überhaupt ein Interleave angegeben + und damit auswählbar ist, liegt ausschließlich darin, + Diskettenabbilder mit der gleichen physischen Sektoranordnung + erzeugen zu können, + wie es auch die jeweiligen originalen Formatierprogramme tun. + Wenn Sie ein Format ohne Interleave oder das falsche Interleave + auswählen, dann ist das nicht weiter schlimm. + Eine mit so einer Abbilddatei beschriebene reale Diskette hätte + in bestimmten Fällen nur etwas längere Zugriffszeiten. +

      +

      2.2. DateStamper

      + Wenn Sie für die zu erstellende Diskettenabbilddatei ein + Diskettenformat mit DateStamper-Unterstützung ausgewählt haben, + wird automatisch die Datei !!!TIME&.DAT angelegt, + die die Zeitstempel der einzelnen Dateien enthält. +

      -

      3. Abbilddatei vom emulierten System aus erstellen

      - Bei dieser Variante legen Sie im Fenster - JKCEMU Diskettenstation eine neue, d.h. leere, - Diskettenabbilddatei an und formatieren - diese vom emulierten System aus mit einem geeigneten Programm. - Beim Formatieren erhält die Abbilddatei ihren Inhalt. +

      3. Abbilddatei vom emulierten System aus erstellen

      + Bei dieser Variante legen Sie im Fenster + JKCEMU Diskettenstation eine neue, d.h. leere, + Diskettenabbilddatei an und formatieren + diese vom emulierten System aus mit einem geeigneten Programm. + Beim Formatieren erhält die Abbilddatei ihren Inhalt. - diff --git a/src/help/disk/datestamper.htm b/src/help/disk/datestamper.htm new file mode 100644 index 0000000..5354f54 --- /dev/null +++ b/src/help/disk/datestamper.htm @@ -0,0 +1,65 @@ + + +

      DateStamper

      + DateStamper ist ein Verfahren zur Speicherung von Zeitstempel + für Dateien in CP/M-kompatiblen Dateisystemen. + Die einzelnen Zeitstempel liegen in der Datei !!!TIME&.DAT, + die physisch die erste Datei auf der Diskette sein muss. + Pro Datei können minutengenau der Zeitpunkt der Erstellung, + der Zeitpunkt des letzten Zugriffs sowie + der Zeitpunkt der letzten Änderung gespeichert werden. +

      + +

      1. DateStamper-Unterstützung im JKCEMU

      + JKCEMU unterstützt DateStamper optional bei: + +

      + +

      2. Aufbau der Zeitstempel-Datei

      + Die Zeitstempeldatei !!!TIME&.DAT enthält + 16 Byte große Datensätze, + wobei jeder Datensatz einem Directory-Eintrag zugeordnet ist. + Die Zuordnung erfolgt der Reihenfolge nach, d.h. + der erste Datensatz in der Zeitstempeldatei bezieht sich + auf den ersten Directory-Eintrag usw. + Die Datei !!!TIME&.DAT ist damit exakt halb so groß + wie das Directory (dieses hat 32 Bytes pro Eintrag). +

      + Jeder Datensatz in der Zeitstempeldatei enthält die jeweils + fünf Byte großen Zeitstempel für Zeitpunkt + der Erzeugung, Zeitpunkt des letzten Zugriffs und + Zeitpunkt der letzten Änderung sowie ein Prüfbyte. +

      + Ein Zeitstempel besteht aus fünf BCD-kodierten Bytes: +
        +
      1. Jahr (bei 0...77 +2000, bei 78...99 +1900)
      2. +
      3. Monat (1...12)
      4. +
      5. Tag (1...31)
      6. +
      7. Stunde (0...23)
      8. +
      9. Minute (0...59)
      10. +
      +
      + Enthält ein Zeitstempel ungültige Werte, + z.B. Monat oder Tag gleich Null, + bedeutet das einen nicht gesetzten Zeitstempel. +

      + Bezüglich des Prüfbytes bilden jeweils acht Datensätze + eine Gruppe. + Die Prüfbytes der ersten sieben Datensätze einer Gruppe + ergeben die Zeichenkette !!!TIME. + Das Prüfbyte des achten Datensatzes ist die Summe + aller vorangegangener 127 Bytes der Gruppe. + + diff --git a/src/help/disk/diskimgformats.htm b/src/help/disk/diskimgformats.htm index a3ff758..4e7b226 100644 --- a/src/help/disk/diskimgformats.htm +++ b/src/help/disk/diskimgformats.htm @@ -15,12 +15,20 @@

      Dateiformate für Abbilddateien

      Diese Dateien können dabei auch GZIP-komprimiert sein.

      + Einfache Abbilddateien, AnaDisk-, CPC-Disk und ImageDisk-Dateien + können von JKCEMU erzeugt werden + (siehe hier + sowie die Tabelle am Ende dieser Seite). +

      1. Einfache Abbilddateien

      Eine einfache Abbilddatei ist eine Aneinanderkettung aller Sektoren einer Diskette, jedoch ohne Verwaltungs- und Geometriedaten, d.h., eine einfache Abbilddatei enthält nur Nutzdaten. - Die Abbilddatei einer 720K-Diskette ist somit auch 720 KByte groß. + Die Abbilddatei einer 720K-Diskette ist somit exakt 720 KByte groß. + Eine freie Sektoranordnung, Interleave + oder Lücken in der Sektornummerierung sind bei dem Dateiformat + nicht möglich.

      Einfache Abbilddateien sind zwar weit verbreitet, jedoch hat das Format keinen einheitlichen Namen. @@ -37,11 +45,10 @@

      1. Einfache Abbilddateien

      Unter DOS sind die Programme rawread.exe und zum Zurückschreiben rawwrite.exe bekannt.

      - JKCEMU kann ebenfalls einfache Abbilddateien von Disketten erstellen. + JKCEMU kann ebenfalls einfache Abbilddateien von Disketten + und anderen Laufwerken erstellen. Die entsprechende Funktion finden Sie im Hauptfenster im Menü Extra. - Des Weiteren können auch einfache Abbilddateien manuell - erstellt werden, siehe hier.

      Da einfache Abbilddatei keine Verwaltungs- bzw. Geometriedaten enthalten, muss man bei deren Verwendung das Diskettenformat in einem @@ -102,54 +109,102 @@

      2. AnaDisk-Dateien



      Aus den Kopfdaten vor jedem Sektor lassen sich die Geometriedaten und damit das Diskettenformat ermitteln. + Da jeder Sektor seine eigenen Kopfdaten hat, + ist nicht nur eine freie Sektoranordnung abbildbar, + sondern auch unterschiedliche Sektorlängen innerhalb einer Spur. Gelöschte Sektoren sind in einer AnaDisk-Datei allerdings nicht möglich bzw. nicht als solche markierbar.

      3. CopyQM-Dateien

      JKCEMU unterstützt das Dateiformat des Diskettenkopierprogramms - CopyQM, allerdings nur lesend. + CopyQM lesend und schreibend. + Die Schreibunterstützung beschränkt sich aber + auf das manuelle Erstellen einer Abbilddatei sowie auf den Dateikonverter. + Im Fenster JKCEMU Diskettenstation kann eine + CopyQM-Datei nur lesend geöffnet werden, + da der Datenbereich in der Datei als ganzes komprimiert und + somit das einzelne Schreiben eines Sektors nicht möglich ist.

      Das CopyQM-Format ist proprietär und nicht offen gelegt. - Die in JKCEMU enthaltene CopyQM-Unterstützung basiert + Die im JKCEMU enthaltene CopyQM-Unterstützung basiert auf den wenigen im Internet frei verfügbaren Informationen und könnte deshalb auch unvollständig sein. Aus diesem Grund kann nicht garantiert werden, - dass jede CopyQM-Datei in JKCEMU auch funktionieren wird. + dass jede CopyQM-Datei im JKCEMU funktionieren bzw. + jede mit JKCEMU erzeugte Datei von CopyQM aktzeptiert wird.

      4. CPC-Disk-Dateien

      Das bei CPC-Emulatoren gebräuchliche Format wird von JKCEMU - lesen und schreibend unterstützt. + lesend und schreibend unterstützt. + Es gibt zwei Unterformate (Standard- und erweitertes Format), + die auch beide voll unterstützt werden. + Beim Erzeugen einer CPC-Disk-Datei wählt JKCEMU selbstständig + das für den konkreten Fall passende Unterformat aus.

      5. ImageDisk-Dateien

      Das Dateiformat des von Dave Dunfield entwickelten Diskettenarchivierungswerkzeugs ImageDisk wird von JKCEMU unterstützt. Bei der manuellen Erstellung einer Diskettenabbilddatei und - im Dateikonverter kann dieses Dateiformat erzeugt werden. - Im Fenster JKCEMU Diskettenstation kann dagegen eine - ImageDisk-Datei nur lesend geöffnet werden, - da die Sektordaten in dem Dateiformat komprimiert sein können. + im Dateikonverter kann dieses Dateiformat auch erzeugt werden. + Im Fenster JKCEMU Diskettenstation wird dagegen nur eine lesende + Unterstützung geboten, + da die Sektordaten komprimiert sein können und somit + ein nahtloses Schreiben eines einzelnen Sektors mitten + in der Abbilddatei nicht problemlos möglich ist. +

      + Das ImageDisk-Dateiformat ist sehr flexibel und kann gelöschte + Sektoren enthalten sowie die Information speichern, + ob ein Sektor mit CRC-Fehler gelesen wurde.

      6. TeleDisk-Dateien

      - JKCEMU unterstützt TeleDisk-Dateien, allerdings nur lesend. + JKCEMU unterstützt TeleDisk-Dateien schreibend und lesend, + allerdings ohne Advanced Compression. + Die Schreibunterstützung beschränkt sich + auf das manuelle Erstellen einer Abbilddatei sowie auf den Dateikonverter. + Im Fenster JKCEMU Diskettenstation kann eine + TeleDisk-Datei nur lesend geöffnet werden, + da wie beim ImageDisk-Format die Sektoren in der Datei komprimiert + gespeichert sein können + und somit ein nahtloses Schreiben eines einzelnen Sektors mitten + in der Abbilddatei nicht problemlos möglich ist. +

      TeleDisk ist ein Programm zum Erzeugen und Zurückschreiben von speziellen Diskettenabbilddateien (TeleDisk-Dateien) und war um 1990 sehr populär. Eine TeleDisk-Datei enthält neben den Nutzdaten - auch sehr detailierte Verwaltungs- und Geometriedaten. + sehr detailierte Verwaltungs- und Geometriedaten. Dadurch kann TeleDisk eine nahezu identische Kopie einer Diskette - anfertigen, auch wenn das Diskettenformat vom Standard abweicht - oder die Diskette gelöschte Sektoren enthält. + anfertigen, auch wenn das Diskettenformat vom Standard abweicht.

      - Das TeleDisk-Format ist proprietär und nicht offen gelegt. - Die in JKCEMU enthaltene TeleDisk-Unterstützung basiert - auf den wenigen im Internet frei verfügbaren Informationen - und ist deshalb auch nicht vollständig. - So lassen sich z.B. TeleDisk-Dateien mit Advanced Compression - nicht verwenden. + Das TeleDisk-Dateiformat ist sehr flexibel und kann neben + gelöschten Sektoren auf die Informationen über mit + CRC-Fehler gelesene Sektoren enthalten. + Des Weiteren kann TeleDisk auch Sektoren speichern, + bei denen nur der Datenbereich, nicht aber der Kopfbereich lesbar war. + In dem Fall generiert es eine Sektor-ID + (d.h., es erfindet eine), und markiert den Sektor entsprechend. +

      + Eine weitere Besonderheit ist, + dass TeleDisk Sektoren manchmal mehrfach liest und speichert, + d.h., der gleiche Sektor kann mehrfach in der Datei vorhanden sein. + Da dies sowie Sektoren mit generierter Sektor-ID im Emulator + Probleme bereiten, versucht JKCEMU die von einer TeleDisk-Datei + eingelesenen Daten bei Bedarf zu reparieren, + d.h. eine generierte Sektor-ID mit der wahrscheinlich richtigen + zu ersetzen und mehrfache vorhandene Sektoren zu eliminieren. + Findet eine solche Reparatur statt, wird der Anwender darüber + informiert. + Die Reparatur bezieht sich nur auf die eingelesenen Daten, + nicht auf die Datei selbst, d.h. diese wird nicht verändert. +

      + Achtung! Wenn Sie eine TeleDisk-Datei im + Diskettenabbilddatei-Inspektor öffnen, + wird diese nicht automatisch repariert, + da man ja in dem Fall den Originalzustand sehen möchte.

      @@ -215,13 +270,14 @@

      7. Komprimierte Abbilddateien


    Achtung! Mit GZIP komprimierte Diskettenabbilddateien - werden in JKCEMU nur lesend unterstützt. + werden bei der + Emulation einer Diskette + nur lesend unterstützt.

    -

    8. Zusammenfassung

    Die Tabelle zeigt zusammenfassend die Unterstützung der einzelnen - Formate für Diskettenabbilddateien in JKCEMU: + Formate für Diskettenabbilddateien im JKCEMU: @@ -250,8 +306,8 @@

    8. Zusammenfassung

    - - + + @@ -262,8 +318,8 @@

    8. Zusammenfassung

    - - + +
    Dateiformat
    CopyQM-Datei (*.cqm; *.qm) RRWRW
    ImageDisk-Datei (*.imd)
    TeleDisk-Datei (*.td0) RRWRW
    R: nur lesend, diff --git a/src/help/disk/diskimgviewer.htm b/src/help/disk/diskimgviewer.htm new file mode 100644 index 0000000..76bece0 --- /dev/null +++ b/src/help/disk/diskimgviewer.htm @@ -0,0 +1,69 @@ + + +

    Diskettenabbilddatei-Inspektor

    + Der Diskettenabbilddatei-Inspektor dient zur Begutachtung + von Diskettenabbilddateien. + Man kann damit die Anordnung und den Inhalt der einzelnen Sektoren sehen + sowie in der Abbilddatei suchen. + Des Weiteren wird im Fall einer CP/M-kompatiblen Abbilddatei + das logische Diskettenformat angezeigt, + wenn es automatisch erkannt werden konnte. +

    + Der hauptsächlie Sinn des Werkzeugs besteht darin, + von realen Disketten erstellte Abbilder zu untersuchen, + die im Emulator Probleme bereiten. + Für eine Fehlersuche ist es sehr hilfreich, + wenn die Abbilddatei neben den eigentlichen Nutzdaten + auch möglichst viele zusätzliche Informationen enthält. + Das hängt wiederum davon ab, mit welchem Werkzeug die Abbilddatei + erzeugt und somit welches Dateiformat verwendet wurde. + Eine freie Sektoranordnung können z.B. nur + AnaDisk-, + CPC-Disk-, + ImageDisk- und + TeleDisk-Dateien speichern. + CopyQM-Dateien enthalten + dagegen nur Angaben zu Interleave und Skew, + wobei diese Werte häufig noch nicht einmal korrekt gesetzt sind. + Wenn also in einer CopyQM-Datei die Sektoren aufsteigend angeordnet sind, + heißt das noch lange nicht, dass das auch auf der urspünglichen + Diskette so war. +

    + Unterschiedliche Sektorgrößen innerhalb einer Spur + lassen sich nur in + AnaDisk-, + ImageDisk- und + TeleDisk-Dateien abbilden. + Letztere beiden speichern auch, ob beim Lesen eines Sektors ein CRC-Fehler + aufgetreten ist und ob der Sektor eine Löschmarkierung hat. + TeleDisk kann zusätzlich + auch Sektoren speichern, deren Kopfbereich nicht gelesen werden konnte. + Diese Sektoren enthalten den Datenbereich sowie eine Informationen, + dass die Sektor-ID generiert (d.h. von TeleDisk erfunden) wurde. + Alle diese Informationen können Sie im + Diskettenabbilddatei-Inspektor sehen. +

    + Achtung! Im Feld Zeitstempel wird nicht der im + Dateisystem hinterlegte Änderungszeitpunkt angezeigt, + sondern der vom Erstellungsprogramm in der Datei gespeicherte Zeitstempel. + Nur CopyQM-, + ImageDisk- und + TeleDisk-Dateien + können einen solchen Zeitstempel haben. +

    + +

    Logisches Diskettenformat

    + Der Diskettenabbilddatei-Inspektor versucht, + das logische CP/M-Diskettenformat + (Systemspuren, Directory-Größe, Blockgröße + und Blocknummernformat) automatisch zu erkennen. + Das ist jedoch nur möglich, wenn das Directory ausreichend + gefüllt ist. + Dabei ist zu beachten, dass die Blöckgröße + nicht zu 100% sicher erkannt werden kann. + Das Blocknummernformat (8 oder 16 Bit) ist nur erkennbar, + wenn das Diskettenabbild Dateien enthält, + die mindestens zwei Blöcke groß sind. + + + diff --git a/src/help/disk/floppydiskintro.htm b/src/help/disk/floppydiskintro.htm index 06ed76e..85b91a9 100644 --- a/src/help/disk/floppydiskintro.htm +++ b/src/help/disk/floppydiskintro.htm @@ -1,6 +1,6 @@ -

    Floppy-Disk-Emulation in JKCEMU

    +

    Floppy-Disk-Emulation im JKCEMU

    JKCEMU emuliert den Floppy Disk Controller U8272, der kompatibel zum Intel 8272A und zum NEC 765A ist. Dieser kann bis zu vier Diskettenlaufwerke bedienen, @@ -28,12 +28,15 @@

    Floppy-Disk-Emulation in JKCEMU

    Eine emulierte Diskette kann abgebildet werden durch:
    1. - eine reale Diskette, + eine reale Diskette, die in einem physischen Diskettenlaufwerk steckt, welches am Emulatorrechner angeschlossen ist
    2. -
    3. eine Diskettenabbilddatei
    4. -
    5. ein Verzeichnis im Dateisystem des Emulatorrechner
    6. +
    7. eine Diskettenabbilddatei
    8. +
    9. + ein Verzeichnis + im Dateisystem des Emulatorrechner +

    Durch Drücken auf den Öffnen/Laden-Knopf @@ -67,6 +70,7 @@

    1. Emulation mit einer realen Diskette

    (und wird i.d.R. auch nicht), d.h., die Dateien auf der Diskette werden Sie im Dateisystem des Emulatorrechners nicht sehen können. +

  • Wenn Sie im emulierten System die Diskette formatieren, @@ -77,18 +81,27 @@

    1. Emulation mit einer realen Diskette

    und das Formatierprogramm sollte diesen Fehler auch ausgeben.
  • +
    + Achtung! Der Zugriff auf physische Laufwerke hängt + von verschiedenen Faktoren ab und kann deshalb nicht auf + allen Plattformen und Betriebssystemumgebungen garantiert werden. + Möglicherweise steht die Funktion auf Ihrem System + nicht zur Verfügung.

    2. Emulation mit einer Diskettenabbilddatei

    - Eine Diskettenabbilddatei (Disk Image File) ist ein 1:1-Abbild - einer Diskette und enthält somit auch die Spuren und Sektoren, + Eine Diskettenabbilddatei + (Disk Image File) ist ein 1:1-Abbild einer Diskette und + enthält somit auch die Spuren und Sektoren, wie sie auf einer realen Diskette zu finden sind. Es gibt verschiedene Dateiformate für Abbilddateien, die sich inhaltlich vorallem im Umfang der gespeicherten Geometrie- und Verwaltungsdaten unterscheiden.

    -

    3. Abbildung einer emulierten Diskette auf ein Verzeichnis

    +

    + 3. Abbildung einer emulierten Diskette auf ein Verzeichnis +

    Bei dieser Form wählen Sie ein Verzeichnis im Dateisystem des Emulatorrechners aus. Die Dateien, die sich in dem Verzeichnis befinden und @@ -157,8 +170,6 @@

    3.1. Aktualisierung der emulierten Diskette

    JKCEMU bietet auch eine automatische Aktualisierung. Dazu müssen Sie in dem Dialog zur Auswahl des Diskettenformats die Option Automatisch aktualisieren einschalten. - Diese Option kann nur zusammen mit dem Schreibschutz eingeschaltet - werden.

    Achtung! Manche Programme oder Betriebssysteme kommen möglicherweise mit der automatischen Aktualisierung @@ -172,6 +183,34 @@

    3.1. Aktualisierung der emulierten Diskette

    in der Eingabeschleife befindet und gerade nicht auf die Diskette zugreift! Außerdem sollten Sie unbedingt auch das erneute Einlesen des Directorys erzwingen (Control-C bzw. Strg-C). +

    + Achtung! Wenn Sie das Verzeichnis ohne Schreibschutz + und automatischer Aktualisierung öffnen möchten, + sollten Sie unbedingt die Dateien in dem Verzeichnis vorher + an anderer Stelle sichern, + da das eine potenziell besondere kritische Konstellation ist. + Testen Sie bitte das im Emulator laufende Betriebssystem + und die Anwendungsprogramme ausgiebig, ob diese mit der automatischen + Aktualisierung klarkommen und es nicht zu Datenverlust oder + anderweitig zerstörten Dateien kommt. + Jedesmal, wenn die Dateien in dem Verzeichnis durch Programme + außerhalb der Emulation geändert werden, + sollten Sie im Emulator mit Control-C bzw. Strg-C + das erneute Einlesen des Directorys erzwingen. +

    + +

    3.2. DateStamper

    + Wenn Sie ein Diskettenformat mit + DateStamper-Unterstützung + ausgewählt haben, enthält die emulierte Diskette + automatisch die Datei !!!TIME&.DAT, + in der die Zeitstempel der einzelnen Dateien abgelegt sind. + Eine eventuell bereits in dem Verzeichnis vorhandene Zeitstempeldatei + wird dabei ignoriert. +

    + Schreibzugriffe auf die Datei !!!TIME&.DAT + führen zum Setzen der Zeitstempel der Dateien in dem Verzeichnis, + vorausgesetzt, die verwendete Java-Laufzeitumgebung + als auch das Dateisystem ermöglichen das Setzen der Zeitstempel. - diff --git a/src/help/disk/interleave.htm b/src/help/disk/interleave.htm new file mode 100644 index 0000000..03ed9af --- /dev/null +++ b/src/help/disk/interleave.htm @@ -0,0 +1,114 @@ + + +

    Interleave

    + Interleave (zu deutsch Verzahnung) gibt die physische + Anordnung der Sektoren auf einer Spur an + und dient zur Reduzierung der Zugriffszeit. + Wenn die Sektoren aufsteigend angeordnet sind, passiert beim + sequenziellen Lesen von mehreren Sektoren folgendes: + Wenn der erste Sektor gelesen wurde, + muss der Floppy Disk Controller zum Lesen des zweiten Sektors + neu programmiert werden. + Vielleicht muss auch noch etwas mit den gelesenen Daten getan werden. + Während dieser Zeit wird der zweite Sektor am Lesekopf vorbei sein, + so dass nun fast eine ganze Diskettenumdrehung gewartet werden muss, + bis der Sektor wieder gelesen werden kann. + Wenn nun aber zwischen zwei nacheinander zu lesende Sektoren + noch andere Sektoren plaziert wären, + ergäbe das eine Zeit, + in der der Floppy Disk Controller umprogrammiert werden kann. + In der Folge würde der nächste Sektor noch in der gleichen + Diskettenumdrehung gelesen werden können. + Und genau das wird mit Interleave erreicht. +

    + Interleave gibt an, nach wieviel physischen Sektoren der Sektor + mit der nächst höheren logischen Sektornummer kommt. + Bei einem Interleave von 1:1 sind die Sektoren aufsteigend angeordnet, + d.h., sie sind nicht miteinander verzahnt. + Es liegt also kein Interleave vor. + Bei 2:1 liegt jeweils ein Sektor dazwischen, bei 3:1 sind es zwei usw. + Die Tabelle zeigt die physische Sektoranordnung bei 5 und 9 Sektoren + pro Spur: +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    InterleaveSektoranordnung bei 5 Sektoren pro SpurSektoranordnung bei 9 Sektoren pro Spur
    1:11 2 3 4 51 2 3 4 5 6 7 8 9
    2:11 4 2 5 31 6 2 7 3 8 4 9 5
    3:11 3 5 2 4 + 1 4 7 2 5 8 3 6 9 ¹ +
    4:11 5 4 3 21 8 6 4 2 9 7 5 3
    5:1nicht relevant1 3 5 7 9 2 4 6 8
    6:1nicht relevant + 1 4 7 3 6 9 2 5 8 ¹ +
    7:1nicht relevant1 5 9 4 8 3 7 2 6
    8:1nicht relevant1 9 8 7 6 5 4 3 2
    +
    + ¹) In diesen Fällen ist ein exakt gleicher physischer Abstand + zwischen zwei logisch zusammenhängenden Sektoren nicht + vollständig möglich. +

    + Das Interleave-Verhältnis gibt implizit auch an, + wieviel Diskettenumdrehungen zum sequenziellen Lesen aller Sektoren + einer Spur notwendig sind. + Bei einem Interleave von 1:1 reicht theoretisch eine Diskettenumdrehung aus, + bei 2:1 sind zwei und bei 3:1 drei Diskettenumdrehungen notwendig. + Je kleiner das Interleave, desto kürzer ist die Zugriffszeit. + Wenn aber das Interleave zu klein gewählt wird, + hat es gar keine Wirkung, denn dann muss pro Sektor jeweils eine ganze + Diskettenumdrehung gewartet werden. + Je größer das Interleave, desto geringer ist zwar die Wirkung, + aber desto sicherer ist, das es überhaupt eine Wirkung hat. + Das optimale Interleave-Verhältnis hängt + von verschiedenen Faktoren ab wie z.B. der Geschwindigkeit des Rechners + und der konkreten Implementierung der Lese- und Schreib-Routinen. + Aus diesem Grund erzeugen die jeweiligen Formatierprogramme + auch bei sonst gleichen Diskettenformaten unterschiedliche + Interleave-Verhältnisse. +

    + Im JKCEMU können Sie ein Interleave beim manuellen + Erstellen einer Diskettenabbilddatei + angeben. + Die physische Sektoranordnung sehen Sie im + Diskettenabbilddatei-Inspektor. + Eine Auswirkung auf die Zugriffszeiten hat es aber im Emulator nicht. + + diff --git a/src/help/disk/unpackdisk.htm b/src/help/disk/unpackdisk.htm index bcfb608..527b991 100644 --- a/src/help/disk/unpackdisk.htm +++ b/src/help/disk/unpackdisk.htm @@ -1,38 +1,47 @@ -

    CP/M-Disketten und -Abbilddateien entpacken

    - Disketten und Abbilddateien können von JKCEMU entpackt werden, - wenn sie dem Diskettenformat von CP/M 2.2 entsprechen. - Beim Entpacken werden die einzelnen Dateien, - die auf der Diskette bzw. in der Abbilddatei enthalten sind, - extrahiert und in das Dateisystem des Emulatorrechners kopiert. -

    - Die Menüpunkte zum Aufruf dieser beiden Funktionen - finden Sie im Hauptfenster, MenüExtra, - Untermenü Werkzeuge. -

    - Nach dem Start wählen Sie das Laufwerk bzw. die Abbilddatei aus. - Anschließend werden Sie nach dem Format gefragt. - JKCEMU analysiert die Diskette bzw. Abbilddatei, - so dass dabei das wahrscheinlich richtige Format bereits - voreingestellt ist. -

    - In dem Dialog zur Formatabfrage gibt es auch die Option - Schreibschutzattribut anwenden. - Wenn Sie diese aktivieren, - wird bei jeder zu extrahierenden Datei das Schreibschutzattribut - ausgewertet und abhängig davon die kopierte Datei - auf Ready Only gesetzt. -

    - Nach der Formatabfrage müssen Sie nur noch das Verzeichnis - festlegen, in das die extrahierten Dateien kopiert werden sollen. - Dateien, die einem Benutzerbereich ungleich Null zugeordnet sind, - werden in separate Unterverzeichnisse kopiert. -

    - Achtung! Beim Zugriff auf Disketten gelten einige - Einschränkungen (siehe hier). - Es kann deshalb nicht garantiert werden, - dass das Entpacken von Disketten in jedem Fall möglich ist. +

    CP/M-Disketten und -Abbilddateien entpacken

    + Disketten und Abbilddateien können von JKCEMU entpackt werden, + wenn sie dem Diskettenformat von CP/M 2.2 entsprechen. + Beim Entpacken werden die einzelnen Dateien, + die auf der Diskette bzw. in der Abbilddatei enthalten sind, + extrahiert und in das Dateisystem des Emulatorrechners kopiert. +

    + Die Menüpunkte zum Aufruf dieser beiden Funktionen + finden Sie im Hauptfenster, MenüExtra, + Untermenü Werkzeuge. +

    + Nach dem Start wählen Sie das Laufwerk bzw. die Abbilddatei aus. + Anschließend werden Sie nach dem Format gefragt. + JKCEMU analysiert die Diskette bzw. Abbilddatei, + so dass dabei das wahrscheinlich richtige Format bereits + voreingestellt ist. +

    + In dem Dialog zur Formatabfrage gibt es auch die Option + Schreibschutzattribut anwenden. + Wenn Sie diese aktivieren, + wird bei jeder zu extrahierenden Datei das Schreibschutzattribut + ausgewertet und abhängig davon die kopierte Datei + auf Ready Only gesetzt. +

    + Nach der Formatabfrage müssen Sie nur noch das Verzeichnis + festlegen, in das die extrahierten Dateien kopiert werden sollen. + Dateien, die einem Benutzerbereich ungleich Null zugeordnet sind, + werden in separate Unterverzeichnisse kopiert. +

    + Achtung! Beim Zugriff auf Disketten gelten einige + Einschränkungen (siehe hier). + Es kann deshalb nicht garantiert werden, + dass das Entpacken von Disketten in jedem Fall möglich ist. +

    + +

    Zeitstempel (DateStamper)

    + Wenn die zu entpackende Diskette bzw. Diskettenabbilddatei die Datei + !!!TIME&.DAT enthält + (siehe DateStamper), + werden die Zeitstempel der entpackten Dateien auf die darin + enthaltenen Werte gesetzt, vorausgesetzt, + die verwendete Java-Laufzeitumgebung sowie das Dateisystem + ermöglichen das Setzen der Zeitstempel. - diff --git a/src/help/disk/writediskimg.htm b/src/help/disk/writediskimg.htm new file mode 100644 index 0000000..57a7728 --- /dev/null +++ b/src/help/disk/writediskimg.htm @@ -0,0 +1,32 @@ + + +

    Abbilddatei auf Datenträger schreiben

    + JKCEMU kann einfache Abbilddateien auf einen Datenträger schreiben, + wenn im oder am Emulatorrechner ein entsprechendes Laufwerk + vorhanden bzw. eingebaut ist und vom Betriebssystem unterstützt wird. + Bei Diskettenlaufwerken ist besonders wichtig, + dass das physische Diskettenformat + (Anzahl Spuren, Anzahl Sektoren pro Spur, Sektorgröße) + unterstützt wird. + Die Funktion zum Schreiben einer Abbilddatei auf einen Datenträger + finden Sie im Hauptfenster im Menü Extra. +

    + Wenn die Größe der Abbilddatei nicht annähernd + duch 512 teilbar ist, wird davon ausgegangen, + dass es sich um eine einfache Festplattenabbilddatei mit einem + 256 Byte großen Kopfblock handelt + (siehe hier). + In dem Fall wird der Kopfblock übersprungen und nur die Daten + hinter dem Kopfblock auf den Datenträger geschrieben. + Abbilddateien in anderen Formaten müssen erst in eine + einfache Abbilddatei umgewandelt werden + (z.B. mit dem Dateikonverter), + um sie auf einen Datenträger schreiben zu können. +

    + Achtung! Der Zugriff auf physische Laufwerke hängt + von verschiedenen Faktoren ab und kann deshalb nicht auf + allen Plattformen und Betriebssystemumgebungen garantiert werden. + Möglicherweise steht die Funktion auf Ihrem System + nicht zur Verfügung. + + diff --git a/src/help/fileformats.htm b/src/help/fileformats.htm index bbb8ae0..c62b458 100644 --- a/src/help/fileformats.htm +++ b/src/help/fileformats.htm @@ -38,9 +38,10 @@

    Dateiformate

  • 4. KC-BASIC-Programmdatei
  • 5. KC-TAP-Format
  • -
  • 6. RBASIC-Programmdatei
  • -
  • 7. Intel-HEX-Format
  • -
  • 8. Speicherabbilddatei
  • +
  • 6. BASIC-Programmdatei
  • +
  • 7. RBASIC-Programmdatei
  • +
  • 8. Intel-HEX-Format
  • +
  • 9. Speicherabbilddatei


  • @@ -116,15 +117,19 @@

    1. Headersave-Format

    B: - BASIC-Programm (KC-BASIC) + BASIC-Programm b: - BASIC-Programm (Mini-/Tiny-BASIC) + Mini-/Tiny-BASIC-Programm C: - Ausführbares Maschinencodeprogramm + Maschinencodeprogramm, selbststartend + + + M: + Maschinencodeprogramm @@ -140,10 +145,10 @@

    1. Headersave-Format

    16 Bytes Dateiname - Das ist eine 16 Zeichen lange Bezeichnung, + Das ist eine maximal 16 Zeichen lange Bezeichnung, die bei Speicherung auf Magnettonband als Dateiname dient. - Ist die Bezeichnung kürzer als 16 Zeichen, + Ist die Bezeichnung kürzer als 16 Zeichen, wird mit Leerzeichen aufgefüllt.
    Da beim Emulator die Datei im Dateisystem des Computers liegt @@ -239,15 +244,17 @@

    3. KCC-Format

    8 Bytes Dateiname - Das ist eine maximal 8 Zeichen lange Bezeichnung, + Das ist eine maximal 8 Zeichen lange Bezeichnung, die bei Speicherung auf Magnettonband als Dateiname dient. -
    - Da beim Emulator die Datei im Dateisystem des Computers liegt - und somit bereits einen Namen hat, - hat dieses Feld nur die Bedeutung - einer zusätzlichen Beschreibung. - Diese Beschreibung muss nicht mit dem Namen im Dateisystem +

    + Achtung! Ist der Name kürzer als 8 Zeichen, + werden bei KC85/1, KC87 und Z9001 Null-Bytes eingetragen, + bei HC900 und KC85/2..5 dagegen Leerzeichen. +

    + Da beim Emulator die Datei im Dateisystem des Computers liegt, + hat sie bereits einen Namen. + Dieser Name muss nicht mit dem im Dateikopf eingetragenen Namen übereinstimmen. @@ -256,13 +263,46 @@

    3. KCC-Format

    3 Bytes Dateityp - Das ist eine maximal 3 Zeichen lange Dateinamenerweiterung. - Da die Dateinamenerweiterung, die als Dateityp fungiert, - sich direkt an den Dateinamen anschließt, - werden häufig beide Felder zu einer 11-Zeichen langen - Dateibezeichnung zusammengefasst. - Auch JKCEMU bietet hierfür nur ein Feld an - und füllt ggf. mit Leerzeichen auf. + Das ist ein maximal 3 Zeichen langer Dateityp. + Die wichtigsten Typen sind: + + + + + + + + + + + + + + + + + + + + + + + + + +
    ASM:Assemblerquelltext
    COM:Maschinencodeprogramm
    DUM:Speicherabzug
    KCB:KC-BASIC-Programm als Maschinencodeprogramm gespeichert
    KCC:CAOS-Maschinencodeprogramm
    TXT:Textdatei
    +
    + Achtung! Bei HC900 und KC85/2..5 ist dieses Feld + zwar als Dateityp definiert (siehe KC85/3 Systemhandbuch, Seite 88), + jedoch wird beim Speichern und Laden mittels Kassette ein Datetyp + nicht berücksichtigt. + Stattdessen werden Dateiname und Dateityp zusammen als eine + 11 Zeichen lange Dateibezeichnung verwendet + (siehe KC85/3 Systemhandbuch, Seite 32), + die, sofern sie kürzer als 11 Zeichen ist, + mit Leerzeichen aufgefüllt wird. + Aus diesem Grund benutzt auch JKCEMU bei HC900 und KC85/2..5 + eine 11 Zeichen lange Bezeichnung. @@ -271,8 +311,11 @@

    3. KCC-Format

    Anzahl nachfolgender Adressen Dieses Feld gibt die Anzahl der nachfolgenden Adressen an - (Anfangs-, End-, Start-/Kaltstart- und Warmstartadresse). - Hier steht meistens eine zwei oder drei und selten eine vier. + (Anfangs-, End- und ggf. Startadresse). + Steht hier eine Zahl größer 3, + enthält die Datei trotzdem nur 3 Adressen. + Allerdings rechnet dann ein HC900 bzw. KC85/2..5 die Startadresse + beim Laden der Datei mit einem Adressoffset nicht um. @@ -284,8 +327,13 @@

    3. KCC-Format

    19 2 Bytes - Endadresse + 1 - Endadresse + 1 im emulierten Arbeitsspeicher + Endadresse + + Endadresse im emulierten Arbeitsspeicher +

    + Achtung! Bei HC900 und KC85/2..5 wird die Endadresse + 1 + eingetragen (siehe KC85/3 Systemhandbuch, Seite 88). + 21 @@ -297,22 +345,7 @@

    3. KCC-Format


    Die Startadresse ist nur gültig, wenn das Feld Anzahl nachfolgender Adressen - den Wert drei oder vier hat. - - - - 23 - 2 Bytes - Warmstartadresse - - Wenn ein Programm zwei Startadressen hat (Kalt- und Warmstart), - ist hier die Warmstartadresse zu finden. -
    - Die Warmstartadresse ist nur gültig, - wenn das Feld Anzahl nachfolgender Adressen - den Wert vier hat. -
    - JKCEMU wertet die Warmstartadresse nicht aus. + den Wert drei oder größer hat. @@ -322,14 +355,16 @@

    3. KCC-Format

    Das gilt auch für die Adressangaben im Kopfblock.

    Achtung! Die im Dateikopf - eingetragene zweite Adresse ist die Endadresse + 1 - (siehe KC85/3 Systemhandbuch, Seite 88). + eingetragene zweite Adresse ist bei KC85/1, KC87 und Z9001 + die reale Endadresse und bei HC900 und KC85/2..5 die Endadresse + 1. + Da nicht Allerdings scheint das nicht überall so implementiert zu sein. Um sicherzustellen, dass das letzte Byte nicht verloren geht, behandelt JKCEMU das KCC-Format folgendermaßen:
    1. - Beim Speichern wird die Endadresse + 1 in den Dateikopf eingetragen. + Beim Speichern wird die Endadresse entsprechend der o.a. Regel + in den Dateikopf eingetragen. Der KC-Header entspricht somit der offiziellen Dokumentation.
    2. @@ -345,41 +380,36 @@

      3. KCC-Format

      3.1. KCM-Format

      Das KCM-Format ist identisch zum KCC-Format. Der Unterschied in der Dateiendung soll anzeigen, - dass die Datei zwar Maschinencode, aber kein ausführbares Programm enthät. + dass die Datei zwar Maschinencode, + aber kein ausführbares Programm enthät.

      3.2. JTC-Format

      Der Jugend+Technik-Computer verwendet in der Ausbaustufe mit dem - erweiterten Betriebssystem EMR-ES 1988 + erweiterten Betriebssystem EMR-ES 1988 das Kassettenaufzeichnungsformat der KC-Computer. Aus diesem Grund wird beim JTCEMU, dem Ju+Te-Computer-Emulator, das KCC-Dateiformat verwendet. Um jedoch deutlich zu machen, dass eine solche Datei kein KC-Programm sondern ein Ju+Te-Computer-Programm enthält, - wird das Format bei JTCEMU JTC-Format genannt, - und die Dateiendung ist *.jtc. + wird das Format bei JTCEMU JTC-Format genannt + (Dateiendung ist *.jtc).

      Achtung! Entgegen der KC85/3-Dokumentation trägt - der Ju+Te-Computer die tatsächliche Endadresse in den Dateikopf - ein und nicht die Endadresse + 1. + der Ju+Te-Computer die tatsächliche Endadresse in den Dateikopf ein. Aus diesem Grund tut es auch der Emulator JTCEMU so. - Insofern ist das von JTCEMU erzeugte JTC-Format nicht ganz identisch - zu dem vom JKCEMU erzeugten KCC-Format. - Allerdings geht beim Einlesen von JTC-Dateien in JKCEMU trotzdem - kein Byte verloren, da bis einschließlich zu der - im Dateikopf eingetragenen Endadresse gelesen wird.

      4. KC-BASIC-Programmdatei

      Eine KC-BASIC-Programmdatei hat üblicherweise die Dateiendung - *.sss und enthält am Anfang zwei Bytes + *.sss und enthält am Anfang zwei Bytes, die die Läge des BASIC-Programms angeben. Ab dem dritten Byte folgt das eigentliche Programm. - Abgeschlossen wird die Datei mit einem Byte mit dem Wert 3. - Die Programmdatei ist somit drei Bytes länger - als die Längenangabe in den ersten beiden Bytes. + Danach folgt ein Byte mit dem Wert 3. + Dahinter folgen Füllbytes, + bis die Datelänge ein Vielfaches von 128 Bytes hat.

      Manche KC-BASIC-Programmdateien haben einen 11-Byte großen Dateikopf, der mit einer BASIC-Programmdateikennung beginnt @@ -403,7 +433,7 @@

      5. KC-TAP-Format

      denn es enthält neben den Nutzbytes auch Blocknummern.

      Das KC-TAP-Format beginnt mit einer 16 Byte großen Kennung, - an den sich 129 Byte große Blöcke anschließen. + an die sich 129 Byte große Blöcke anschließen. Jeder Block beginnt mit einer Blocknummer gefolgt von 128 Nutzbytes. Der erste Block ist gewöhnlich der Kopfblock und gibt Auskunft über die Art und die Länge der Datei. @@ -467,12 +497,13 @@

      5. KC-TAP-Format

      Hinweis: KC-TAP-Dateien können auch mit den Audio-Funktionen wie Sound-Dateien eingelesen werden. - Das ist übrigens der einzige Weg, KC-TAP-Dateien in JKCEMU zu laden, - die ein KC-BASIC-Programm im ASCII-Format oder ein KC-BASIC-Datenfeld - enthalten. + Das ist übrigens der einzige Weg, + KC-TAP-Dateien in den JKCEMU zu laden, die ein KC-BASIC-Programm + im ASCII-Format oder ein KC-BASIC-Datenfeld enthalten.

      Hinweis: Im Datei-Browser - können KC-TAP-Dateien in Sound-Dateien exportiert werden. + und im Dateikonverter können + KC-TAP-Dateien in Sound-Dateien exportiert bzw. konvertiert werden.

      5.1. Multi-TAP-Dateien

      @@ -508,7 +539,16 @@

      5.1. Multi-TAP-Dateien



      -

      6. RBASIC-Programmdatei

      +

      6. BASIC-Programmdatei

      + Ein BASIC-Programmdatei enthält ein BASIC-Programm, + so wie es im Arbeitsspeicher vorliegt. + Das erste Byte in der Datei ist das erste Byte des eigentlichen + BASIC-Programms. + Die Dateiendung ist *.bas + (Ausnahme bei AC1-BASIC6: *.abc). +

      + +

      7. RBASIC-Programmdatei

      Der A5105 (BIC, ALBA PC) benutzt dieses Dateiformat zur Speicherung von BASIC-Programmen auf Diskette. Das erste Byte in der Datei hat den Wert 0FFh. @@ -519,7 +559,7 @@

      6. RBASIC-Programmdatei

      Die übliche Dateiendung ist *.bas.

      -

      7. Intel-HEX-Format

      +

      8. Intel-HEX-Format

      In einer Intel-HEX-Datei werden die binären Daten als hexadezimale Zahlen in textueller Form gespeichert, d.h., die Intel-HEX-Datei ist eine reine ASCII-Datei. @@ -594,7 +634,7 @@

      7. Intel-HEX-Format



      -

      8. Speicherabbilddatei

      +

      9. Speicherabbilddatei

      Eine Speicherabbilddatei enthält ein reines Abbild eines Teils des Arbeitsspeichers des Emulators. Es sind keine Kopfdaten enthalten, d.h., diff --git a/src/help/floppydisk.htm b/src/help/floppydisk.htm index be2678b..750f7f1 100644 --- a/src/help/floppydisk.htm +++ b/src/help/floppydisk.htm @@ -1,31 +1,33 @@ -

      Diskettenlaufwerke

      - Einige der emulierten Systeme verfügen im Original über - Diskettenlaufwerke. - Diese werden auch von JKCEMU emuliert. -

      - Die Hilfe zur Floppy-Disk-Emulation unterteilt sich in folgende Themen: - +

      Diskettenlaufwerke

      + Einige der emulierten Systeme verfügen im Original über + Diskettenlaufwerke. + Diese werden auch von JKCEMU emuliert. +

      + Die Hilfe zur Floppy-Disk-Emulation unterteilt sich in folgende Themen: + - diff --git a/src/help/gide.htm b/src/help/gide.htm index cd1c962..fd6d3f9 100644 --- a/src/help/gide.htm +++ b/src/help/gide.htm @@ -11,11 +11,8 @@

      GIDE und Festplatten



      Zur Emulation einer Festplatte müssen Sie in den Einstellungen im Bereich für - das jeweilige System im Reiter GIDE das Festplattenmodell - und eine Abbilddatei auswählen. - Wenn Sie keine Festplatte emulieren lassen, - wird auch das GIDE als Ganzes nicht emuliert. -

      + das jeweilige System im Reiter GIDE die GIDE-Emulation aktivieren + sowie das Festplattenmodell und eine Abbilddatei auswählen. Zur Auswahl des Festplattenmodells unterstützt Sie JKCEMU mit einem Festplattenverzeichnis, das bereits einige passende Modelle enthält. @@ -32,5 +29,38 @@

      GIDE und Festplatten



      Das GIDE enthält auch eine Echtzeituhr. Im Emulator kann die Zeit nur gelesen, nicht aber gesetzt werden. +

      + Achtung! Wenn Sie das GIDE-Modul aktiviert, + aber keine zu emulierende Festplatte eingerichtet haben, + wird das Modul trotzdem emuliert. + In dem Fall ist aber nur die auf dem GIDE-Modul enthaltene + Echtzeituhr praktisch nutzbar. +

      + +

      + Dateiformate für Festplattenabbilddateien +

      + Für die Emulation von Festplatten unterstützt JKCEMU + zwei Arten von Abbilddateien: +
        +
      1. Einfache Abbilddateien
      2. +
      3. + Einfache Abbilddateien mit einem 256 Byte großen Kopfblock +
      4. +
      +
      + Das erste Format ist identisch zu den + einfachen Abbilddateien bei der Diskettenemulation. + Abbilddateien mit diesem Format kann JKCEMU von einem Datenträger + erzeugen (siehe hier). +

      + Das zweite Format wird aus Gründen der Kompatibilität + zu anderen Emulatoren unterstützt. + Der 256 Byte große Kopfblock wird von JKCEMU nicht ausgewertet + und auch nicht beschrieben. +

      + Abbilddateien in beiden Formaten lassen sich mit JKCEMU auch wieder + auf einen Datenträger schreiben + (siehe hier). diff --git a/src/help/hgmc.htm b/src/help/hgmc.htm index 7a8754b..13d647d 100644 --- a/src/help/hgmc.htm +++ b/src/help/hgmc.htm @@ -33,6 +33,19 @@

      1. Emulierte Hardware

    3. 64 KByte RAM inklusive Bildwiederholspeicher
    4. CTC
    5. PIO
    6. +
    7. + optional: + +
    8. Von dem IO-System werden nur die CTC und die PIO emuliert. Die SIO und das Kassettenrecorderinterface sowie ggf. @@ -85,7 +98,7 @@

      3.1. BASIC-Interpreter

      Der originale BASIC-Interpreter stellt aber auch ein Kommando mit dem Namen BASIC zur Verfügung. Damit es nun zu keinen Verwechslungen kommt, - wurde dieses Kommando in der in JKCEMU enthaltenen BASIC-Version + wurde dieses Kommando in der im JKCEMU enthaltenen BASIC-Version in WBASIC umbenannt. Damit ist mit BASIC ein Kaltstart und mit WBASIC ein Warmstart des Interpreters möglich. @@ -95,18 +108,20 @@

      3.1. BASIC-Interpreter



      3.2. BASIC-Programme speichern und laden

      - JKCEMU bietet eine spezielle Unterstützung - für das Speichern und Laden von BASIC-Programmen. + JKCEMU bietet eine spezielle Unterstützung für das + Speichern und Laden von BASIC-Programmen. Zum Speichern nutzen Sie bitte die Funktion BASIC-Programm speichern... im Menü Datei - und speichern das Programm als - Headersave-Datei - mit dem Dateityp B. + und speichern das jeweilige Programm als + BASIC-/RBASIC-Programmdatei (*.bas). + Alternativ ist auch das Speichern als + Headersave-Datei (*.z80) + mit dem Dateityp B möglich.

      - Nur wenn Sie das BASIC-Programm so speichern, - werden beim Laden des Programms die Systemzellen des - BASIC-Interpreters richtig angepasst. - In jedem anderen Fall können Sie zwar das BASIC-Programm laden, - aber im Interpreter nicht richtig nutzen. + Wenn Sie ein BASIC-Programm in einem anderen Dateiformat speichern, + erkennt JKCEMU später beim Laden der Datei nicht mehr, + dass es sich um ein BASIC-Programm handelt und passt die + Systemzellen des BASIC-Interpreters nicht an. + Das geladene Programm lässt sich dann nicht nutzen. diff --git a/src/help/home.htm b/src/help/home.htm index 80d5eff..e19f9fc 100644 --- a/src/help/home.htm +++ b/src/help/home.htm @@ -27,13 +27,15 @@

      Willkommen bei der JKCEMU Hilfe!

    9. Tastatur
    10. Audio/Kassette
    11. Diskettenlaufwerke
    12. -
    13. Festplatten
    14. +
    15. GIDE und Festplatten
    16. Joysticks (Spielhebel)
    17. Drucken
    18. Plotter
    19. Netzwerk
    20. USB-Anschluss
    21. Vollbildmodus
    22. +
    23. Geschwindigkeit
    24. +
    25. AutoLoad und AutoInput
    26. Tipps & Tricks
    27. @@ -55,6 +57,7 @@

      Willkommen bei der JKCEMU Hilfe!

    28. LC-80
    29. LLC1
    30. LLC2
    31. +
    32. NANOS
    33. PC/M (Mugler/Mathes-PC)
    34. Poly-Computer 880
    35. Schachcomputer SC2
    36. @@ -68,12 +71,17 @@

      Willkommen bei der JKCEMU Hilfe!

      Werkzeuge: -
      + Beim KC85/4 kann der normalerweise nur 4 KByte große + CAOS-ROM C000h-CFFFh auch mit 8 KByte (C000h-DFFFh) + emuliert werden, + indem in den Einstellungen eine ROM-Datei eingebunden wird, + die größer als 4 Kbyte ist. + Damit ist in der KC85/4-Emulation z.B. auch CAOS 4.5 lauffähig. +

      1.1. Tastatur

      Die KC85-Tastatur enthält in der oberen Reihe einige Tasten, @@ -124,102 +137,79 @@

      1.1. Tastatur

      KC85-Taste JKCEMU-Taste - Tastencode Bedeutung bei Shift - Tastencode bei Shift Cursor links Cursor links - 08h CCR - 19h Cursor rechts Cursor rechts - 09h - CEL - 18h + CEL (Taste End) Cursor nach unten Cursor nach unten - 0Ah - SCROL - 12h + SCROL (Taste Page Down) Cursor nach oben Cursor nach oben - 0Bh - PAGE - 11h + PAGE (Taste Page Up) - F1F10F1hF70F7h - F2F20F2hF80F8h - F3F30F3hF90F9h - F4F40F4hFA0FAh - F5F50F5hFB0FBh - F6F60F6hFC0FCh - BRKF703h - STOPF813hESC1Bh - CLRF901hHCOPY0Fh - INSEinfg1AhCLICK14h - DELEntf1Fh + F1F1F7 + F2F2F8 + F3F3F9 + F4F4FA + F5F5FB + F6F6FC + BRKF7 + STOPF8ESC + + CLR + Back Space
      F9
      HCOPY + + INSEinfgCLICK + DELEntf HOME HOME bzw. Pos1 - 10h CLS - 0Ch -

      - -

      - 1.1.1. Deutsche Umlaute und andere fehlende Zeichen -

      - Deutsche Umlaute, eckige und geschweifte Klammern sowie Backslash und Tilde - können mit der originalen KC85-Tastatur nicht eingegeben werden, - da es dafür keine Tastencodes gibt. - Aus diesem Grund können diese Zeichen auch im Emulator - nicht eingegeben werden. - Die KC85-Komforttastatur D005 kann aber in einen spezielles Mode - (MicroDOS-Mode) geschaltet werden, mit dem diese Zeichen eingegeben - und in Form von 2-Byte-Tastencodes an das KC85-Grundgerät - gesendet werden. - Das CAOS-Betriebssystem kann zwar mit diesen 2-Byte-Tastencodes - nichts anfangen bzw. interpretiert sie falsch, - doch unter MicroDOS werden damit die richtigen Zeichen angezeigt. -

      - Im JKCEMU gibt es die Option - Tasten für ÄÖÜäöüß[]{}\~ - entsprechend MicroDOS-Mode der D005 behandeln. - Ist diese Option eingeschaltet, werden beim Drücken - der betreffenen Tasten auf der Emulatortastatur 2-Byte-Tastencodes - entsprechend dem D005-MircoDOS-Mode erzeugt, - d.h. diese Tasten haben dann im MicroDOS eine Wirkung. - Unschön ist allerdings, dass das CAOS-Betriebssystem - bei diesen Tastencodes falsche Zeichen anzeigt. -

      +
      + Weitere Steuertasten werden auf KC85-Tastenkombinationen gemappt: + +

      - 1.1.2. Ctrl-A...Z, ESC und TAB direkt in den Tastaturpuffer + 1.1.1. Schnellere Tastatureingaben durch direktes Schreiben in den Tastaturpuffer

      - Auf der originalen Tastatur gibt es keine Control-Taste, - was bei Verwendung eines CP/M-kompatiblen Betriebssystems - recht hinderlich ist. - Zwar lässt sich das Problem durch Verwendung - spezieller Tasten und Tastenkombinationen teilweise umgehen, - allerdings nicht vollständig und dann auch nur auf eine - ziemlich umständliche Art und Weise. - Eine komfortable Lösung bietet JKCEMU mit der Option - Ctrl-A...Z, ESC und TAB direkt in den Tastaturpuffer. - Damit sind solche Tastatureingaben möglich. - Technisch wird das Problem der fehlenden Control-Taste in der Form - gelöst, indem die entsprechenden Codes unter Umgehung der - emulierten Tastatur direkt in den Tastaturpuffer geschrieben werden. + Die originale Tastatursteuerung ist aufgrund der speziellen Hardware + und der Interrupt-gesteuerten Tastencode-Dekodierung relativ träge. + JKCEMU emuliert die originale Tastatursteuerung vollständig, + weshalb diese auch in der Emulation recht träge wirkt. + Mit Einschalten der Option + Schnellere Tastatureingaben durch direktes Schreiben in den + Tastaturpuffer + wird die Emulation der Tastatursteuerung umgangen und die + eingegebenen Zeichen unter Berücksichtigung des in der CAOS- + bzw. MicroDOS-/ML-DOS-Tastaturtabelle eingetragenen Tastencode-Mappings + direkt in den Tastaturpuffer geschrieben. + Dadurch wirkt die Tastatur deutlich flüssiger. + Sollten jedoch aufgrund spezieller Umstände oder durch Verwendung + eines anderen Betriebssystems anstelle von CAOS + keine Tastatureingaben mehr möglich sein, + müssen Sie die Option ausschalten.

      1.2. Module

      @@ -256,14 +246,19 @@

      1.2. Module

      M035x44 MByte Segmented RAM M036128 KByte Segmented RAM M0408/16 KByte User PROM + M0454x8 KByte Segmented User PROM + M0468x8 KByte Segmented User PROM + M04716x8 KByte Segmented User PROM + M04816x16 KByte Segmented User PROM M052USB/Netzwerk M1208 KByte CMOS RAM M12216 KByte CMOS RAM M12432 KByte CMOS RAM
      - Bei den User-PROM-Modulen M025, M028 und M040 müssen Sie - eine ROM-Datei angeben, die den Inhalt des Moduls enthält. + Bei den User-PROM-Modulen M025, M028, M040 und M045 bis M048 + müssen Sie eine ROM-Datei angeben, + die den Inhalt des Moduls enthält.

      Die CMOS-RAM-Module unterscheiden sich von den anderen RAM-Modulen in der Form, dass die CMOS-RAM-Module beim emulierten @@ -301,14 +296,17 @@

      1.3. Drucker



      1.4. Tongeneratoren und Kassettenrecorderanschluss

      - Welche erzeugten Töne vom Emulator ausgegeben werden, + Welche Töne vom Emulator ausgegeben werden, legen Sie durch Auswahl einer entsprechenden Funktion in dem Fenster Audio/Kassette fest. Bei der Funktion Töne ausgeben - hören Sie das Mischsignal aus beiden Tongeneratoren, - welches auch der Lautstärkebeeinflussung unterliegt. - Bei allen anderen Audio-Funktionen wird das Signal des Tongenerators 1 - (linker Kanal) ohne Lautstärkebeeinflussung ausgegeben, + hören Sie je nach Einstellung + entweder das Mischsignal aus beiden Tongeneratoren, + welches auch der Lautstärkesteuerung unterliegt (Mono) + oder die beiden Tongeneratoren links und rechts getrennt + ohne Lautstärkesteuerung (Stereo). + Bei allen anderen Audio-Funktionen wird das Signal des Tongenerators 1 + (linker Kanal) ohne Lautstärkesteuerung ausgegeben, d.h. das Signal, mit dem Dateien auf Kassette gespeichert werden.

      @@ -336,7 +334,7 @@

      1.5. Diskettenerweiterung D004


      - Einige in JKCEMU integrierten Werkzeuge sind auch mit dem D004-eigenen + Einige im JKCEMU integrierte Werkzeuge sind auch mit dem D004-eigenen Prozessorsystem benutzbar. Beim Aufruf des Debuggers, Reassemblers und Speichereditors werden Sie gefragt, ob Sie auf das Hauptprozessorsystem @@ -407,15 +405,74 @@

      2. Im ROM enthaltene Software

      D004 - je nach Einstellung Version 2.0, 3.2 oder 3.3 + je nach Einstellung Version 2.0 oder 3.3
      -

      3. Sonstiges

      +

      3. Enthaltene Diskettenabbilder

      + JKCEMU enthält für die KC85/3..5-Emulation + folgende Diskettenabbilder: + + Für die Nutzung der Diskettenabbilder muss die Emulation + der Floppy-Disk-Erweiterung D004 aktiviert und die entsprechende + Diskette in das erste emulierte Diskettenlaufwerk eingelegt werden. +

      + +

      3.1. CAOS-Systemdiskette

      + Die CAOS-Systemdiskette enthält die notwendigen Programme + zur Nutzung von Disketten unter dem Betriebssystem CAOS. + Mit +

      +   JUMP FC +

      + wird die auf der Diskette befindliche Software aktiviert. + Das CAOS-Menu enthält nun den neuen Eintrag FLOAD, + mit dem Systemprogramme von Diskette geladen werden können. + Eines dieser Systemprogramme ist BASEX. + Dieses stellt die Diskettenanbindung für den BASIC-Interpreter her. + Zum Speichern von Systemprogrammen auf Diskette gibt es das Programm + FSAVE. +

      -

      3.1. Speichern eines BASIC-Programms

      +

      3.2. MicroDOS-Systemdiskette

      + MicroDOS starten Sie mit +

      +   JUMP FC +

      + Das Laufwerk A: ist eine RAM-Floppy, + Laufwerk B: das erste Diskettenlaufwerk. + Die Anzeige des Verzeichnisses erfolgt mit dem Kommando + D, und nicht wie bei CP/M-kompatiblen Betriebssystemen + sonst üblich mit DIR. +

      + + +

      4. Sonstiges

      + +

      4.1. Laden von Dateien

      + Das CAOS-Betriebssystem blendet standardmäßig den Videospeicher + (IRM) in den Adressraum ein und überdeckt somit einen ab + Adresse 8000h liegenden RAM (bei HC900 und KC85/2..3 nur dann der Fall, + wenn dort ein RAM-Modul eingeblendet ist). + Damit man aber auch BASIC-Programme laden kann, + die größer als 32 KByte sind, + werden KC-BASIC-Dateien bzw. Dateien, + die üblicherweise ein KC-BASIC-Programm enthalten, + ab Adresse 8000h nicht in den IRM, + sondern in den dahinter liegenden RAM geladen. + Alle anderen Dateien werden dagegen in den gerade eingeblendeten Speicher + geladen, ggf. auch in den IRM. + Damit verhält sich die Ladefunktion des Emulators genauso + wie das CLOAD-Kommando des BASIC-Interpreters (im Fall einer KC-BASIC-Datei) + bzw. wie das LOAD-Kommando im CAOS (im Fall keiner KC-BASIC-Datei). +

      + +

      4.2. BASIC-Programme speichern und laden

      JKCEMU bietet eine spezielle Unterstützung für das Speichern und Laden von BASIC-Programmen. Wird ein System mit im ROM enthaltenen BASIC-Interpreter emuliert, @@ -429,7 +486,7 @@

      3.1. Speichern eines BASIC-Programms



      - 3.2. Einfügen von Text aus der Zwischenablage + 4.3. Einfügen von Text aus der Zwischenablage

      Das Einfügen von Text aus der Zwichenablage erfolgt gewöhnlich in der Form, dass für jedes einzufügende Zeichen @@ -442,16 +499,24 @@

      direkt in den Tastaturpuffer geschrieben. Das ist wesentlich schneller, da dadurch das Drücken der entsprechenden Tasten nicht simuliert werden muss. + Dadurch wird aber auch das CAOS-interne Tastencode-Mapping umgangen. + Andererseits ist es damit aber auch möglich, Zeichen einzufügen, + die mit der originalen Tastatur nicht eingebbar sind. +

      + Sollten aufgrund spezieller Umstände oder durch Verwendung + eines anderen Betriebssystems anstelle von CAOS das Einfügen + von Zeichen nicht möglich sein, müssen Sie die Option ausschalten.

      - Sollten Sie jedoch ein anderes als das in JKCEMU integrierte - Betriebssystem verwenden, - wird diese Option möglicherweise nicht mehr funktionieren, - da dort die Tastaturabfrage anders gestaltet sein kann. - In dem Fall müssen Sie die Option ausschalten. + Wenn Sie die Option + Schnellere Tastatureingaben durch direktes Schreiben in den Tastaturpuffer + eingeschaltet haben, ist die Option für das Einfügen von Text + hinfällig und deshalb nicht mehr auswählbar, + da in dem Fall die Tastatureingaben ja sowieso direkt + in den Tastaturpuffer geschrieben werden.

      - 3.3. Zeitverhalten der Bildschirmsteuerung + 4.4. Zeitverhalten der Bildschirmsteuerung

      An den Eingängen des Zähler-/Zeitgeberbausteins (CTC) liegen der Bildsynchronimpuls und der doppelte Zeilensynchronimpuls an. @@ -477,17 +542,17 @@

      Aktualiserungszyklus findet keine Anwendung.

      -

      3.4. Vermeintlicher D004-Speicherfehler

      +

      4.5. Vermeintlicher D004-Speicherfehler

      In den neueren D004-ROM-Versionen ist ein Speichertest enthalten, - so auch in der Version 3.3, die in JKCEMU integriert ist. + so auch in der Version 3.3, die im JKCEMU integriert ist. Diesen Test können Sie mit JUMP FC FF starten. Dabei kann es vorkommen, - dass der Speichertest in JKCEMU einen Fehler meldet. + dass der Speichertest im JKCEMU einen Fehler meldet. Die Ursache liegt dabei nicht etwa in einer fehlerhaften Speicherzelle, sondern in einem Timeout, der bedingt durch die Art und Weise der Emulation auftreten kann.

      - In JKCEMU werden die beiden Mikroprozessorsysteme + Im JKCEMU werden die beiden Mikroprozessorsysteme (Grundgerät und D004) in separaten Threads emuliert. Da der Emulator keinen Einfluss darauf hat, wann das Betriebssystem welchen Thread zum Zuge kommen lässt, diff --git a/src/help/kccompact.htm b/src/help/kccompact.htm index 154acab..4a5d1c9 100644 --- a/src/help/kccompact.htm +++ b/src/help/kccompact.htm @@ -33,7 +33,7 @@

      Hinweise zur Emulation des KC compact

      -

      +

      1. Emulierte Hardware

      @@ -48,7 +48,7 @@

      1.1. Tastatur

      des Emulatorrechners. Diese Tasten werden trotzdem emuliert. Die Tabelle zeigt das Tastenmapping für einige ausgewählte Tasten: -
      +

      @@ -153,7 +153,7 @@

      1.2.2 Diskettenformate

      AMSDOS-Format ohne Systemspur:
      180 KByte: 40 Spuren; einseitig, keine Systemspur, 9 Sektoren, - 512 Bytes Sektorgröße, Sektoroffset C0H, + 512 Bytes Sektorgröße, Sektoroffset C0h, 4 KByte Blockgröße

      @@ -161,7 +161,7 @@

      1.2.2 Diskettenformate

      AMSDOS-Format mit Systemspuren:
      180/171 KByte: 40 Spuren; einseitig, 2 Systemspuren, 9 Sektoren, - 512 Bytes Sektorgröße, Sektoroffset 40H, + 512 Bytes Sektorgröße, Sektoroffset 40h, 4 KByte Blockgröße @@ -172,7 +172,8 @@

      1.2.2 Diskettenformate



      Die zweite Betriebsart ist das CP/M-kompatible MicroDOS. In diese Betriebsart gelangt man mit dem Kommando |cpm. - Dazu muss vorher die MicroDOS-Systemdiskette eingelegt worden sein. + Dazu muss vorher die MicroDOS-Systemdiskette im ersten + Diskettenlaufwerk eingelegt worden sein.

      MicroDOS unterstützt zwei Diskettenformate:
      @@ -195,10 +196,9 @@

      1.2.2 Diskettenformate


      Die im JKCEMU enthaltene MicroDOS-Systemdiskette ist so konfiguriert, - dass das erste physische Diskettenlaufwerk sowohl als - Laufwerk B: als auch als Laufwerk C: - angesprochen werden kann, je nachdem, - welches Format die eingelegte Diskette hat. + dass das erste Diskettenlaufwerk sowohl als Laufwerk B: + als auch als Laufwerk C: angesprochen werden kann, + je nachdem, welches Format die eingelegte Diskette hat. Laufwerk B: ist für das MicroDOS-Standardformat konfiguriert und Laufwerk C: für das BASDOS-Standardformat. @@ -247,14 +247,34 @@

      1.3. Sound-Generator

      2. Enthaltene Diskettenabbilder

      + JKCEMU enthält für die KC-compact-Emulation + folgendes Diskettenabbild: Sie starten MicroDOS durch Eingabe des Befehls: |cpm
      Dazu muss die Emulation der Floppy-Disk-Station aktiviert und - die MicroDOS-Systemdiskette eingelegt sein. + die MicroDOS-Systemdiskette in das erste emulierte Diskettenlaufwerk + eingelegt sein. +

      + Das MicroDOS auf der Systemdiskette ist mit drei Laufwerken konfiguriert: + +
      + Die Anzeige des Verzeichnisses erfolgt in MicroDOS mit dem Kommando + D, und nicht wie bei CP/M-kompatiblen Betriebssystemen + sonst üblich mit DIR.

      diff --git a/src/help/kcnet.htm b/src/help/kcnet.htm index d8ccacc..cee1a56 100644 --- a/src/help/kcnet.htm +++ b/src/help/kcnet.htm @@ -2,12 +2,13 @@

      Netzwerkemulation

      -

      Allgemeines

      +

      1. Allgemeines

      Ralf Kästner hat eine TCP/IP-fähige Lösung zum Anschluss der KC85-Computer aus Mühlhausen an ein Ethernet-Netzwerk entwickelt. @@ -59,10 +60,10 @@

      Allgemeines


      -

      Konfiguration

      +

      2. Konfiguration

      KCNet muss nach dem Start des Computers erst konfiguriert werden, bevor es verwendet werden kann. - Dafür gibt es ein spezielles Programm, + Dafür gibt es spezielle Programme, mit dem man die Werte für IP-Adresse, Subnetzmaske, Gateway und DNS-Server im Netzwerkmodul eintragen kann. In der Emulation werden aber die im KCNet eingestellten Werte @@ -81,7 +82,7 @@

      Konfiguration



      JKCEMU bietet die Möglichkeit, KCNet vorzukonfigurieren, d.h., beim Start der Emulation ist das Netzwerk bereits konfiguriert, - und sie brauchen das spezielle Konfigurationsprogramm nicht mehr starten. + und sie brauchen kein spezielles Konfigurationsprogramm mehr starten. Die vier einzelnen Werte für die Netzwerkkonfiguration können Sie in den Einstellungen, Bereich Netzwerk, festlegen. @@ -111,7 +112,33 @@

      Konfiguration


      -

      Hinweise zu IPv6

      +

      2.1. DHCP

      + Wenn Sie auf die Möglichkeit der Vorkonfiguration verzichten, + ist KCNet nach dem Start von JKCEMU erst einmal nicht konfiguriert. + Nun könnten Sie, + wie üblicherweise auf einem realen System auch, + ein Programm starten, welches KCNet mittels DHCP konfiguriert. + Da aber im Fall der Emulation das Netzwerk von dem darunter liegenden + Betriebssystem verwaltet wird und der Emulator normalerweise auch + in einer Umgebung ohne Systemberechtigungen läuft, + würde dieses DHCP-Konfigurationsprogramm (DHCP-Client) + auf einen Fehler laufen. + Damit aber trotzdem in der Emulation ein DHCP-Prozess fehlerfrei + durchlaufen kann, wird durch JKCEMU ein DHCP-Server simuliert. + Der Emulator fängt dazu alle DHCP-Anfragen ab und beantwortet sie so, + wie es auch ein realer DHCP-Server tun würde. +

      + Achtung! Die vom simulierten DHCP-Server gelieferten Werte + für die Server-IP-Adresse sowie für die Lease-, Renew- + und Rebind-Zeit der Client-IP-Adresse haben keinen realen Bezug! + Diese Werte dienen nur dazu, den in der Emulation laufenden + DHCP-Client zufieden zu stellen. + Desweiteren sollte beachtet werden, dass kein DHCP-Server simuliert wird, + wenn JKCEMU keine IP-Adresse ermitteln kann und in den Einstellungen + auch keine angegeben wurde. +

      + +

      3. Hinweise zu IPv6

      Die emulierte Netzwerklösung KCNet verwendet ausschließlich die alte TCP/IP-Version IPv4 mit den 4 Byte langen IP-Adressen, die in Form von vier mit einem Punkt getrennten Dezimalzahlen diff --git a/src/help/kramermc.htm b/src/help/kramermc.htm index 1fc2db1..e1e8d7a 100644 --- a/src/help/kramermc.htm +++ b/src/help/kramermc.htm @@ -15,7 +15,9 @@

      Hinweise zur Emulation des Kramer-MC

    37. 2. Im ROM enthaltene Software
    38. 3. Speicheraufteilung
    39. -
    40. 4. BASIC
    41. +
    42. + 4. BASIC-Programme speichern und laden +

    43. @@ -95,19 +97,21 @@

      3. Speicheraufteilung



      -

      4. BASIC

      - JKCEMU bietet eine spezielle Unterstützung - für das Speichern und Laden von BASIC-Programmen. +

      4. BASIC-Programme speichern und laden

      + JKCEMU bietet eine spezielle Unterstützung für das + Speichern und Laden von BASIC-Programmen. Zum Speichern nutzen Sie bitte die Funktion BASIC-Programm speichern... im Menü Datei - und speichern das Programm als - Headersave-Datei - mit dem Dateityp B. + und speichern das jeweilge Programm als + BASIC-/RBASIC-Programmdatei (*.bas). + Alternativ ist auch das Speichern als + Headersave-Datei (*.z80) + mit dem Dateityp B möglich.

      - Nur wenn Sie das BASIC-Programm so speichern, - werden beim Laden des Programms die Systemzellen des - BASIC-Interpreters richtig angepasst. - In jedem anderen Fall können Sie zwar das BASIC-Programm laden, - aber im Interpreter nicht richtig nutzen. + Wenn Sie ein BASIC-Programm in einem anderen Dateiformat speichern, + erkennt JKCEMU später beim Laden der Datei nicht mehr, + dass es sich um ein BASIC-Programm handelt und passt die + Systemzellen des BASIC-Interpreters nicht an. + Das geladene Programm lässt sich dann nicht nutzen. diff --git a/src/help/lc80.htm b/src/help/lc80.htm index 357ec8c..de32613 100644 --- a/src/help/lc80.htm +++ b/src/help/lc80.htm @@ -111,7 +111,7 @@

      1.1. Tastatur

      des Emulatorrechners abgebildet werden:
      KC-compact-TasteEmuliert mittels
      RETURNENTER/RETURN
      - + @@ -248,7 +248,7 @@

      2.1.2. Programm von Kassette laden

      Nach dem Einlesen sehen Sie im Display die Endadresse + 1 oder im Fehlerfall die Ausschrift Error.

      - Achtung! In JKCEMU müssen Sie auch die eingangsseitige + Achtung! Im JKCEMU müssen Sie auch die eingangsseitige Emulation des Kassettenrecorderanschlusses aktivieren, entweder mit der Audio-Funktion Daten vom Audio-Eingang lesen @@ -265,7 +265,7 @@

      2.1.3. Programm auf Kassette speichern

      Dateiname und Adressangaben sind jeweils eine vierstellige hexadezimale Zahl.

      - Achtung! In JKCEMU müssen Sie auch die ausgangsseitige + Achtung! Im JKCEMU müssen Sie auch die ausgangsseitige Emulation des Kassettenrecorderanschlusses aktivieren, entweder mit der Audio-Funktion Daten am Audio-Ausgang ausgeben @@ -330,7 +330,7 @@

      2.2.1. Tastatur

      Die Tabelle zeigt das Tastenmapping im SC-80-Mode:
      LC-80-TasteTaste in JKCEMUBedeutung
      LC-80-TasteTaste im JKCEMUBedeutung
      RES Escape
      - + diff --git a/src/help/llc1.htm b/src/help/llc1.htm index b238135..2bba66a 100644 --- a/src/help/llc1.htm +++ b/src/help/llc1.htm @@ -52,10 +52,10 @@

      Hinweise zur Emulation des LLC1

      3. BASIC
      @@ -64,7 +74,10 @@

      1. Emulierte Hardware

    44. V24-Schnittstelle mit Drucker
    45. -
    46. Vollgrafikerweiterung HIRES
    47. +
    48. + Vollgrafikerweiterung HIRES mit + Split Screen +
    49. Optional:
      -

      1.1. Speicher

      +

      1.1. Tastatur

      + Neben den gewöhnlichen Tasten für Buchstaben, Ziffern und + Sonderzeichen werden folgende Steuertasten unterstützt: +
      +
    50. SC-80-TasteTaste in JKCEMUBedeutung
      SC-80-TasteTaste im JKCEMUBedeutung
      RES Escape
      + + + + + + + + + + + + + + + + + + + +
      TasteErzeugter Tastencode / Bedeutung
      Home / Pos11
      Cursor links8
      Cursor rechts9
      Cursor runter0Ah
      Cursor hoch0Bh
      Enter / Return0Dh
      Backspace7Fh
      Delete / Entf4
      Insert / Einfg5
      Bild auf11h
      Bild ab15h
      End / Ende1Ah
      F1Inverstaste
      F2Grafiktaste
      +

      + +

      1.2. Speicher

      Die unteren 48 KByte RAM sind nach dem Einschalten bzw. nach einem RESET nicht sichtbar. Erst mit einem Ein-/Ausgabebefehl auf die Adresse E0h @@ -97,7 +141,7 @@

      1.1. Speicher

      vom ROM in den RAM um.

      -

      1.2. SCCH-Modul 1

      +

      1.3. SCCH-Modul 1

      Das SCCH-Modul 1 stellt zusätzlichen ROM zur Verfügung, und zwar:


    @@ -95,8 +107,6 @@

    1. Assembler-Optionen

    In allen anderen Fällen wird kein Dateityp eingetragen. Außerdem beginnen dann TAP-Dateien entsprechend den HC900- und KC85/2..5-Konventionen mit der Blocknummer 1. - -

    Wenn Sie mehrere Assembler-Quelltexte im Texteditor geöffnet haben, werden die Assembler-Optionen für jeden Quelltext separat verwaltet. @@ -128,100 +138,72 @@

    3.1. Groß- und Kleinschreibung

    3.2. Aufbau einer Assembler-Quelltextzeile

    Die allgemeine Syntax einer Quelltextzeile lautet: -

    [Marke[:]] [Mnemonik [Argument_1[,Argument_2[,...]]]] [;Kommentar]


    - In eckigen Klammern stehende Ausdrücke sind optional. Eine Marke muss am Anfang einer Zeile beginnen. Ein Assembler-Befehl (angegeben durch eine Mnemonik) darf nicht am Anfang einer Zeile beginnen, d.h., wenn die Zeile keine Marke enthält, muss vor der Mnemonik mindestens ein Leerzeichen oder ein Tabulator stehen. + Eine Ausnahme bilden die Anweisungen für die bedingte Assemblierung. + Diese können am Zeilenanfang stehen, aber auch eingerückt werden.

    -

    3.3. Numerische Ausdrücke

    - Bei numerischen Ausdrücken unterstützt der Assembler - die Vorzeichen + und - sowie die arithmetischen - Operationen Addition und Subtraktion, die ebenfalls durch - + und - angegeben werden. - Die einzelnen Elemente eines Ausdrucks sind entweder eine Marke, - ein Zahlenliteral, ein Zeichenliteral oder der Zugriff auf das - höherwerte beziehungsweise niederwertige Byte - eines untergeordneten Ausdrucks. +

    3.3. Marken

    + Marken haben einen numerischen Wert und werden definiert, + indem sie am Anfang einer Assembler-Zeile stehen. + Wenn diese Zeile den Pseudobefehl EQU enthält, + bekommt die Marke den Wert des hinter EQU stehenden Ausdrucks, + anderenfalls den Wert der aktuellen Adresse.

    - -

    3.4. Zahlenliteral

    - Zahlenliterale beginnen mit einer Ziffer und können mit - einem Buchstaben enden, der die Basis der Zahl angibt. - Folgende Buchstaben sind dabei möglich: - - - - - - - - - - - -
    Abschließender BuchstabeBasis der ZahlBemerkung
    nicht vorhanden10Dezimalzahl
    B, oder b2Binärzahl
    O, o, Q oder q8Oktalzahl
    H oder h16Hexadezimalzahl
    -
    - Hexadezimalzahlen müssen auch mit einer Ziffer beginnen! + Marken können als Sprungziele oder in numerischen Ausdrücken + verwendet werden.

    - Beispiele: - - - - - - - -
    BeispielDezimaler Wert
    123123
    101b5
    123q83
    0AFh175
    + Marken können Buchstaben, Ziffern, den Unterstrich, + das @-Zeichen sowie Fragezeichen enthalten. + Ziffern und das Fragezeichen sind aber nicht am Anfang der Marke erlaubt.

    - -

    3.5. Zeichenliteral

    - Ein Zeichenliteral ist ein in einfachen Hochkommas - eingeschlossenes Zeichen, z.B. 'A'. - Ein Zeichenliteral kann innerhalb eines numerischen Ausdrucks - verwendet werden. + Achtung! Der Assembler legt zur Unterstützung der + bedingten Assemblierung + automatisch immer die Marke __JKCEMU__ mit dem Wert -1 + (alle Bits gesetzt) an.

    -

    3.6. Zeichenkettenliteral

    - Ein Zeichenkettenliteral umfasst ein oder mehrere in einfachen - oder doppelten Hochkommas eingeschlossene Zeichen. - Zeichenkettenliterale können nur bei den Pseudobefehlen - DB, DEFB und DEFM verwendet werden. -

    +

    3.4. Assembler-Befehle

    + Die Assembler-Befehle werden in Form einer Mnemonik geschrieben. + Der Assembler versteht folgende Befehle: + +
    -

    - - 3.7. Zugriff auf höher- oder niederwertiges Byte in einem Ausdruck - -

    - - - - - - - - - - -
    Zugriff aufSyntax
    höherwertiges Byte: - H(...)
    - HIGH(...) -
    niederwertiges Byte: - L(...)
    - LOW(...) -
    +

    3.5. Kommentare

    + Kommentare beginnen mit einem Semikolon. + Alles ab dem Semikolon bis zum Ende der Quelltextzeile wird + als Kommentar gewertet und vom Assembler nicht ausgewertet. + Dies gilt jedoch nicht, wenn das Semikolon in einen + Zeichenliteral oder einem + Zeichenkettenliteral steht. + In dem Fall markiert das Semikolon keinen Kommentar.

    -

    3.8. Pseudobefehle

    + +

    4. Pseudobefehle

    Der Assembler versteht folgende Pseudobefehle: @@ -230,22 +212,39 @@

    3.8. Pseudobefehle

    + + + + + - + @@ -254,6 +253,7 @@

    3.8. Pseudobefehle

    @@ -269,6 +269,7 @@

    3.8. Pseudobefehle

    @@ -285,8 +286,9 @@

    3.8. Pseudobefehle

    @@ -323,6 +325,18 @@

    3.8. Pseudobefehle

    die mit dem angegebenen Wert definiert wird. + + + + + + @@ -354,7 +368,7 @@

    3.8. Pseudobefehle

    - 4. Mnemonik und Syntax: Zilog oder Robotron? + 5. Mnemonik und Syntax: Zilog oder Robotron?

    Robotron hat bei einigen Assembler-Befehlen eine von Zilog abweichende Mnemonik eingeführt, @@ -476,7 +490,6 @@

    Typ der Argumente Beschreibung
    BINCLUDE1Zeichenkette + Einbinden einer Binärdatei,
    + Die angegebene Datei wird unverändert an der aktuellen Stelle + in den erzeugten Programmcode eingefügt. + Der Dateiname kann ohne oder mit Pfad angegeben werden, + wobei sowohl ein absoluter als auch ein relativer Pfad erlaubt sind. + Die Pfadaangabe muss den Regeln des Betriebssystems entsprechen, + auf dem JKCEMU läuft. + Unabhängig davon kann aber ein relativer Pfad auch in einer + betriebssystemunabhängigen Form (also auch unter Windows) + mit einem Slash (/) als Pfadtrennzeichen angegeben werden. +
    CPU 1Zeichenkette U880 oder Z80Schlüsselwort U880 oder Z80 Angabe des Prozessortyps,
    Dieser Pseudobefehl ist aus Kompatibilitätsgründen vorhanden und hat keine Wirkung, - da der Assembler sowieso nur einen Prozessortyp unterstützt. + da der Assembler sowieso nur diesen einen Prozessortyp unterstützt.
    + DB
    DEFB
    DEFM
    - DB + DFB
    mindestens 1 Numerische Ausdrücke oder Zeichenketten
    DEFH
    + DFH
    HEX
    mindestens 1
    DEFS
    + DFS
    DS
    mindestens 1
    - DEFW
    DA
    + DEFW
    + DFW
    DW
    mindestens 1
    INCLUDE1Zeichenkette + Einbinden einer Quelltextdatei,
    + Die angegebene Datei wird an der aktuellen Stelle in den Quelltext + eingefügt und mit assembliert. + Bzgl. des Dateinamens gelten die die gleichen Regeln wie beim + weiter oben beschriebenen Pseudobefehl BINCLUDE. +
    ORG 1
    -

    Die Syntax unterscheidet sich bei folgenden Befehlen: @@ -541,7 +554,7 @@



    -

    5. Undokumentierte Befehle

    +

    6. Undokumentierte Prozessorbefehle

    Einige Operationscodes führen im Mikroprozessor Befehle aus, die vom Hersteller nicht dokumentiert sind. Dabei verhält sich der Prozessor U880 genau so wie die Z80 CPU. @@ -557,7 +570,7 @@

    5. Undokumentierte Befehle



    - 5.1. Zugriff auf die einzelnen Bytes der Index-Register + 6.1. Zugriff auf die einzelnen Bytes der Index-Register

    Viele 8-Bit-Register-Befehle können auch auf das niederwertige beziehungsweise höherwertige Byte der beiden Indexregister @@ -637,7 +650,7 @@

    HX, LX, HY und LY möglich.

    -

    5.2. Eingabebefehl: Nur Flag-Register setzen

    +

    6.2. Eingabebefehl: Nur Flag-Register setzen

    Der Befehl liest ein Byte vom Eingabetor, dessen Adresse im Register C steht und setzt das Flag-Register entsprechend. Der eingelesene Wert selbst wird nicht behalten. @@ -651,7 +664,7 @@

    5.2. Eingabebefehl: Nur Flag-Register setzen

    offizieller Befehl verstanden.

    -

    5.3. Logischer Linksschiebebefehl

    +

    6.3. Logischer Linksschiebebefehl

    Der Befehl schiebt das Argument um ein Bit nach links, wobei das Bit 0 danach auf 1 gesetzt ist.
    @@ -675,8 +688,8 @@

    5.3. Logischer Linksschiebebefehl

    - 5.4. Rotations-, Schiebe- und Einzelbitbefehle mit Indexregistern, - die auch auf ein anderes Register wirken + 6.4. Rotations-, Schiebe- und Einzelbitbefehle mit Indexregistern, + die auch auf ein anderes Register wirken

    Die offiziell dokumentierten Rotations-, Schiebe- und Einzelbitbefehle, @@ -689,8 +702,8 @@

    - - + + + + + @@ -774,6 +1164,19 @@

    6. Kommandozeile

    + + + + + + + + diff --git a/src/help/tools/audiorecorder.htm b/src/help/tools/audiorecorder.htm new file mode 100644 index 0000000..d0668cb --- /dev/null +++ b/src/help/tools/audiorecorder.htm @@ -0,0 +1,40 @@ + + +

    Audio-Recorder

    + Mit dem Audio-Recorder können die am Mikrofon- oder Line-In-Eingang + anliegenden Tonsignale aufgezeichnet und in eine Sound-Datei gespeichert + werden. + Mit Drücken auf die Schaltfläche Start + beginnt die Aufnahme. + Mit Stop wird die Aufnahme beendet. + Vergisst man dies, + wird die Aufnahme nach 45 Minuten automatisch beendet. +

    + Nach dem Ende der Aufnahme können Sie diese sich anhören + (Wiedergabe...) oder in eine Datei speichern. + Beim Speichern wird auch die Komprimierung mit GZIP unterstützt, + wenn der Dateiname auf *.gz endet. +

    + Achtung! Sie müssen ggf. den Mikrofon- oder Line-In-Eingang + im Betriebssystem ihres Computers erst aufregeln, + damit die entsprechenden Tonsignale aufgenommen werden können. + Ob dies der Fall ist und ob Sie die Tonquelle an der richtigen Buchse + ihres Computers angeschlossen haben, + sehen Sie an der im Audio-Recorder enthaltenen Pegelanzeige. + Diese wird mit Drücken auf Start aktiv. +

    + +

    Optionen

    + Bevor Sie mit der Aufnahme beginnen, können Sie festlegen, + von welchem Gerät und mit welcher Qualität + (Abtastrate, Auflösung, Mono/Stereo) + aufgenommen werden soll. + Voreingestellt ist CD-Qualität (44,1 kHz, 16 Bit, Stereo). +

    + Wenn Sie eine Kassettenaufzeichnung eines alten Computers aufnehmen + möchten, reicht die Auflösung von 8 Bit in Mono aus. + Sollten Sie allerdings einen Stereo-Kassettenrecorder angeschlossen haben, + dann empfiehlt sich auch die Aufnahme in Stereo. + + + diff --git a/src/help/tools/basicc.htm b/src/help/tools/basicc.htm index 12e6f05..79241dc 100644 --- a/src/help/tools/basicc.htm +++ b/src/help/tools/basicc.htm @@ -26,6 +26,7 @@

    BASIC-Compiler

    Zeichenkettenverarbeitung
  • Grafik
  • +
  • Ein-/Ausgabegeräte
  • Einschränkungen gegenüber einem Interpreter
  • diff --git a/src/help/tools/basicc/cmdline.htm b/src/help/tools/basicc/cmdline.htm index 93c4677..732cceb 100644 --- a/src/help/tools/basicc/cmdline.htm +++ b/src/help/tools/basicc/cmdline.htm @@ -27,6 +27,31 @@

    BASIC-Compiler mit Kommandozeile aufrufen

    + + + - + + + + + @@ -189,7 +254,7 @@

    BASIC-Compiler mit Kommandozeile aufrufen

    diff --git a/src/help/tools/basicc/constants.htm b/src/help/tools/basicc/constants.htm index 93adbb5..2027188 100644 --- a/src/help/tools/basicc/constants.htm +++ b/src/help/tools/basicc/constants.htm @@ -13,8 +13,11 @@

    Konstanten


    @@ -98,7 +101,72 @@

    2. Farben

    Mnemonik und SyntaxErläuterungMnemonik und SyntaxErläuterung
    @@ -718,8 +731,360 @@

    dass sie auch auf das zusätzlich angegebene 8-Bit-Register wirken.

    +

    7. Numerische Ausdrücke

    + Numerischen Ausdrücke kommen überall dort zur Anwendung, + wo ein numerischer Wert benötrigt wird. + Beim Assemblerbefehl +

    +   LD A,3 +

    + ist z.B. die 3 ein numerischer Ausdruck, + und zwar konkret ein Zahlenliteral. + Numerische Ausdrücke können aber auch komplexer sein + und Operationen, Funktionen, Zeichenliterale und Marken enthalten. + Außerdem können numerische Ausdrücke mit runden Klammern + beliebig geschachtelt werden. +

    + +

    7.1. Zahlenliteral

    + Zahlenliterale beginnen mit einer Ziffer und können mit + einem Buchstaben enden, der die Basis der Zahl angibt. + Folgende Buchstaben sind dabei möglich: + + + + + + + + + + + + + + +
    Abschließender BuchstabeBasis der ZahlBemerkung
    nicht vorhanden10Dezimalzahl
    B, oder b2Binärzahl
    O, o, Q oder q8Oktalzahl
    H oder h16Hexadezimalzahl, muss mit einer Ziffer (z.B. 0) beginnen!
    +
    + Hexadezimalzahlen können auch in einer anderen Syntax + geschrieben werden: X gefolgt von der in einfachen Hochkommas + eingeschlossenen Hexadezimalzahl. +

    + Beispiele: + + + + + + + +
    BeispielDezimaler Wert
    123123
    101b5
    123q83
    0AFh175
    X'AF'175
    +

    + +

    7.2. Zeichenliteral

    + Ein Zeichenliteral ist ein in einfachen Hochkommas + eingeschlossenes Zeichen, z.B. 'A'. + Ein Zeichenliteral kann innerhalb eines numerischen Ausdrucks + verwendet werden. +

    + +

    7.3. Funktionen

    + Der Assembler bietet Funktionen für den Zugriff auf das + niederwerte und höherwertige Byte eines numerischen Wertes. +
    + + + + + + + + + + +
    Zugriff aufSyntax
    + HIGH(...) + Zugriff auf höherwertiges Byte
    + LOW(...) + Zugriff auf niederwertiges Byte
    +

    + +

    7.4. Unäre Operatoren

    + Unäre Operatoren wirken auf den nachfolgenden Ausdruck. +
    + + + + + + + + + + + + + + +
    OperatorBeschreibung
    + + Vorzeichen
    + Der nachfolgende Wert wird nicht verändert. +
    - + Vorzeichen
    + Der nachfolgende Wert wird mathematisch negiert. +
    NOTDer nachfolgende Wert wird bitweise negiert
    +

    + +

    7.5. Binäre Operatoren

    + Binäre Operatoren stehen zwischen zwei Ausdrücken. +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    OperatorBeschreibung
    +Addition
    -Subtraktion
    *Multiplikation
    /Division
    MODModule - Rest der Division
    ANDbitweise UND-Verknüpfung
    ORbitweise Inklusiv-ODER-Verknüpfung
    XORbitweise Exklusiv-ODER-Verknüpfung
    + <<
    + SHL +
    + links schieben
    + Der vor dem Operator stehende Wert wird um soviele Bits nach links + geschoben, wie der Wert hinter dem Operator angibt. +
    + >>
    + SHR +
    + links schieben
    + Der vor dem Operator stehende Wert wird um soviele Bits nach rechts + geschoben, wie der Wert hinter dem Operator angibt. +
    + <
    + LT +
    + Vergleich auf kleiner als
    + Ist der erste Wert kleiner als der zweite, + ergibt das den Wert -1 (alle Bits gesetzt), anderenfalls 0. +
    + <=
    + LE +
    + Vergleich auf kleiner oder gleich
    + Ist der erste Wert kleiner oder gleich dem zweiten, + ergibt das den Wert -1 (alle Bits gesetzt), anderenfalls 0. +
    + >
    + GT +
    + Vergleich auf größer als
    + Ist der erste Wert größer als der zweite, + ergibt das den Wert -1 (alle Bits gesetzt), anderenfalls 0. +
    + >=
    + GE +
    + Vergleich auf größer oder gleich
    + Ist der erste Wert größer oder gleich dem zweiten, + ergibt das den Wert -1 (alle Bits gesetzt), anderenfalls 0. +
    + =
    + EQ +
    + Vergleich auf Gleichheit
    + Ist der erste Wert gleich dem zweiten, + ergibt das den Wert -1 (alle Bits gesetzt), anderenfalls 0. +
    + <>
    + NE +
    + Vergleich auf Ungleichheit
    + Ist der erste Wert ungleich dem zweiten, + ergibt das den Wert -1 (alle Bits gesetzt), anderenfalls 0. +
    +
    + Binäre Operatoren haben eine geringere Bindungskraft als unäre. + Innerhalb der binäre Operatoren gibt folgende Tabelle Auskunft + über die Bindungskraft. + Je weiter oben ein Operator steht, desto höher ist die Bindungskraft. +
    + + + + + + + + + +
    * / MOD
    + -
    << >> SHL SHR
    < <= > >= LT LE GT GE
    = <> EQ NE
    AND
    XOR
    OR
    +

    + +

    7.6. Aktuelle Adresse

    + Die aktuelle Adresse wird durch das $-Zeichen repräsentiert. + Wenn Sie also in einem Ausdruck den Wert des Programm Counters + (Register PC) zu Beginn der aktuellen Assembler-Zeile benötigen, + schreiben Sie dafür das Zeichen: $ +

    -

    6. Kommandozeile

    +

    8. Zeichenketten

    + Eine Zeichenkette (Zeichenkettenliteral) umfasst ein oder mehrere + in einfachen oder doppelten Hochkommas eingeschlossene Zeichen. + Zeichenkettenliterale können nur bei den Pseudobefehlen + BINCLUDE, DB, DEFB, DEFM, + DFB und INCLUDE verwendet werden. +

    + + +

    + 9. Bedingte Assemblierung +

    + Unter bedingter Assemblierung versteht man, + dass Teile des Quelltextes nur dann assembliert werden, + wenn eine bestimmte Bedingung erfüllt ist. +

    + Ein der bedingten Assemblierung unterliegender Quelltextabschnitt + beginnt mit einer IF-Anweisung, + die es in mehreren Varianten gibt. + Die Anweisung ENDIF markiert das Ende dess betreffenden + Quelltextabschnitts und schließt somit die IF-Anweisung ab. +

    + Zwischen IF und ENDIF kann optional auch + die Anwisung ELSE stehen. + Der Quelltext zwischen ELSE und ENDIF wird nur + dann assembliert, wenn der Bereich zwischen IF und ELSE + nicht assembliert wird und umgekehrt. +

    + Die Tabelle beschreibt die verschiedenen IF-Anweisungen + für die bedingte Assemblierung: + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Anweisungen/SyntaxBeschreibung
    + IF <numersicher_Ausdruck>
    + IFT <numersicher_Ausdruck>
    +
    + Der nachfolgende Quelltext wird assembliert, + wenn der Wert des numerischen Ausdrucks ungleich 0 ist. +
    + IFE <numersicher_Ausdruck>
    + IFF <numersicher_Ausdruck>
    +
    + Der nachfolgende Quelltext wird assembliert, + wenn der Wert des numerischen Ausdrucks gleich 0 ist. +
    + IFDEF <Marke> + + Der nachfolgende Quelltext wird assembliert, + wenn die angegebene Marke existiert. +
    + IFNDEF <Marke> + + Der nachfolgende Quelltext wird assembliert, + wenn die angegebene Marke nicht existiert. +
    + IF1 + + Der nachfolgende Quelltext wird im ersten Assembler-Lauf assembliert. +
    + IF2 + + Der nachfolgende Quelltext wird im zweiten Assembler-Lauf assembliert. +
    +
    + Die Anweisungen für die bedingte Assemblierung können + sowohl am Zeilenanfang stehen als auch eingerückt werden. + Des Weiteren können bedingte Assemblierungen auch in sich + geschachtelt werden. +

    + Der JKCEMU-Assembler legt automatisch die Marke __JKCEMU__ + mit dem Wert -1 (alle Bits gesetzt) an. + Damit kann erkannt werden, + ob das Programm mit dem JKCEMU-Assembler assembliert wird. +

    + Beispiel: +

    +   DB 'Dieses Programm wurde mit'
    + IFDEF __JKCEMU__
    +   DB ' dem JKCEMU-'
    + ELSE
    +   DB ' einem anderen '
    + ENDIF
    +   DB 'Assembler assembliert'
    +

    + + +

    10. Kommandozeile

    Der Assembler kann mit einer Kommandozeile auch ohne den Emulator als eigenständiges Programm aufgerufen werden. Unter Windows müssen Sie dazu die Eingabeaufforderung starten. @@ -741,6 +1106,31 @@

    6. Kommandozeile

    -h Hilfe zu den Assembler-Optionen anzeigen
    -f <Datei> + weitere Kommandozeile aus Datei lesen +

    + Die Kommandozeile kann recht lang sein, + möglicherweise länger als von der Eingabeaufforderung + oder dem Betriebssystem unterstützt. + Aus diesem Grund kann die Kommandozeile aus einer Datei gelesen werden. +

    + Beim Erreichen der Option -f öffnet der Assembler + die angegebene Datei und liest daraus die weitere Kommandozeile. + Wenn das Ende der Datei erreicht wurde, + wird der Rest der ursprünglichen Kommandozeile weiter verarbeitet. + Der Assembler tut also so, als ob der Inhalt der Datei anstelle + der Option -f in der Kommandozeile stehen würde. +

    + In der Datei müssen die einzelnen Kommandozeilenargumente + nicht in einer Zeile stehen, sondern können auf mehrere Zeilen + aufgeteilt werden. + Wenn ein Kommandozeilenargument Leerzeichen enthält, + muss dieses in doppelte Hochkommas eingeschlossen werden, + damit der Assembler es als ein Argument verarbeitet. +
    +
    -l Markentabelle ausgeben -C Groß-/Kleinschreibung bei Marken beachten
    -D <Marke>Marke mit dem Wert -1 (alle Bits gesetzt) definieren
    -D <Marke=Wert> + Marke mit dem angegebenen Wert definieren
    + Der Wert wird in Form einer Dezimal-, Hexadezimal-, Oktal- + oder Binärzahl angegeben + (siehe Zahlenliteral). +
    -U Undokumentierte Befehle erlauben -h Hilfe zu den BASIC-Compiler-Optionen anzeigen
    -f <Datei> + weitere Kommandozeile aus Datei lesen +

    + Die Kommandozeile kann recht lang sein, + möglicherweise länger als von der Eingabeaufforderung + oder dem Betriebssystem unterstützt. + Aus diesem Grund kann die Kommandozeile aus einer Datei gelesen werden. +

    + Beim Erreichen der Option -f öffnet der Compiler + die angegebene Datei und liest daraus die weitere Kommandozeile. + Wenn das Ende der Datei erreicht wurde, + wird der Rest der ursprünglichen Kommandozeile weiter verarbeitet. + Der Compiler tut also so, als ob der Inhalt der Datei anstelle + der Option -f in der Kommandozeile stehen würde. +

    + In der Datei müssen die einzelnen Kommandozeilenargumente + nicht in einer Zeile stehen, sondern können auf mehrere Zeilen + aufgeteilt werden. + Wenn ein Kommandozeilenargument Leerzeichen enthält, + muss dieses in doppelte Hochkommas eingeschlossen werden, + damit der Compiler es als ein Argument verarbeitet. +
    +
    -g @@ -57,13 +82,16 @@

    BASIC-Compiler mit Kommandozeile aufrufen

    Diese Option muss angegeben werden. Mögliche Schlüsselwörter sind:
      +
    • AC1
    • CPM
    • HUEBLER
    • KC85
    • +
    • KC85_4
    • KRAMER
    • LLC2_HIRES
    • SCCH
    • Z1013
    • +
    • Z1013_64X16
    • Z9001
    • Z9001_KRT
    @@ -72,17 +100,54 @@

    BASIC-Compiler mit Kommandozeile aufrufen

    -A <Adresse> + -A <Anfangsadresse>
    + -A <Anfangsadresse>:<BSS-Adresse> +
    - Anfangsadresse festlegen (hexadezimal),
    + Anfangsadresse für den Programmcode und ggf. auch für + den BSS-Bereich (Block Storage Segment: Variablen, Speicherzellen, Stack) + festlegen (hexadezimal),
    Fehlt die Option, gilt abhängig vom Zielsystem folgende Anfangsadresse:
      -
    • 0100h bei CPM, HUEBLER und Z1013
    • -
    • 0300h bei KC85, Z9001 und Z9001_KRT
    • +
    • 0100h bei CPM, HUEBLER, Z1013 und Z1013_64X16
    • +
    • 0300h bei KC85, KC85_4, Z9001 und Z9001_KRT
    • 1000h bei KRAMER
    • 2000h bei SCCH und LLC2_HIRES
    + Wird die BSS-Adresse nicht angegeben, liegen die Variablen, + die Speicherzellen und, sofern ein eigener Stack verwendet wird, + der Stack direkt hinter dem Programmcode. +
    -D <Treiberliste> + Die Option gibt an, welche Treiber in das compilierte Programm + eingebunden werden sollen. + Dazu werden die Treiber mit Komma getrennt hinter der Option angegeben. + Die Treiberliste kann auch in Doppelhochkommas eingeschlossen werden. +

    + Beispiele:
    +  -D vdip
    +  -D crt,lpt
    +  -D "CRT,LPT"
    +
    + Folgende Treiber stehen zur Verfügung: + CRT, LPT, VDIP +
    + Die Groß/Kleinschreibung der Treibernamen spielt keine Rolle. +

    + Wird ein Treiber angegeben obwohl das BASIC-Programm gar + keine Anweisung oder Funktion enthält, + die den Treiber verwenden könnte + (CRT, LPT, FILE und VDIP werden z.B. nur von + OPEN verwendet), + wird der Treiber nicht eingebunden. +

    + Wird die Option -D nicht angegeben, + werden alle sinnvollen Treiber eingebunden.
    UNUSED - Bei nicht verwendezen Funktionen, Prozeduren und Variablen warnen + Bei nicht verwendeten Funktionen, Prozeduren und Variablen warnen

    -

    3. Fehlercodes

    +

    3. Grafikstifte

    + Die Konstanten für die Grafikstifte werden als Parameter für die + PEN-Anweisung verwendet. + und sind auch dort beschrieben. +

    + + + + + + + + + + + + + + + + + + +
    KonstanteBedeutung
    PEN_NONEStift angehoben
    PEN_NORMALnormaler Zeichenmodus
    PEN_RUBBERLöschmodus
    PEN_XORXOR-Modus
    +
    + +

    4. Joystick

    + Die Joystick-Konstanten dienen zur Auswertung des Rückgabewertes + der JOYST-Funktion. + Dazu ist der Rückgabewert mit der jeweiligen Konstante mit + AND zu verknüpfen. +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KonstanteBedeutung
    JOYST_LEFTJoystick nach links gedrückt
    JOYST_RIGHTJoystick nach rechts gedrückt
    JOYST_UPJoystick nach nach oben gedrückt
    JOYST_DOWNJoystick nach nach unten gedrückt
    JOYST_BUTTON1Aktionsknopf 1 gedrückt
    JOYST_BUTTON2Aktionsknopf 2 gedrückt
    JOYST_BUTTONSAktionsknopf 1 oder 2 gedrückt
    +
    + +

    5. Fehlercodes

    Die Fehlercodekonstanten dienen zur Auswertung der Systemvariable ERR.

    @@ -155,8 +223,6 @@

    3. Fehlercodes

    E_EOF Es wurde versucht, über das Dateiende hinaus zu lesen. - Bei einer Netzwerkverbindung bedeutet dieser Fehlercode, - dass die Verbindung von der Gegenstelle beendet wurde. @@ -218,6 +284,12 @@

    3. Fehlercodes

    Es ist ein numerischer Überlauf aufgetreten. + + E_PATH_NOT_FOUND + + Der angegebene Pfad wurde nicht gefunden. + + E_READ_ONLY @@ -226,22 +298,96 @@

    3. Fehlercodes

    zum Schreiben zu öffnen. + +
    + +

    6. Konstanten für Zielsysteme

    + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KonstanteZielsystem
    TARGET_AC1 + AC1-2010, AC1-SCCH mit optionaler Farbgrafikkarte +
    E_SOCKET_STATUSTARGET_CPM - Der KCNet-Socket befindet sich im falschen Zustand. + CP/M-kompatibel +
    TARGET_HUEBLER + Hübler-Grafik-MC +
    TARGET_KC85 + KC85/2..5, HC900 +
    TARGET_KC85_4 + KC85/4..5 mit Unterstützung beider Bildspeicher +
    TARGET_KRAMER + Kramer-MC +
    TARGET_LLC2 + LLC2 mit HIRES-Grafik +
    TARGET_SCCH + SCCH (AC1-2010, AC1-SCCH, LLC2) +
    TARGET_Z1013 + Z1013 +
    TARGET_Z1013_64X16 + Z1013 mit Peters-Platine +
    TARGET_Z9001 + KC85/1, KC87, Z9001 +
    TARGET_Z9001_KRT + KC85/1, KC87, Z9001 mit KRT-Grafik

    -

    4. Systemkonstanten

    +

    7. Systemkonstanten

    @@ -252,13 +398,6 @@

    4. Systemkonstanten

    die mit der SCREEN-Anweisung eingestellt werden kann - - - -
    KonstanteBedeutung
    GRAPHICSCREEN - Nummer des grafikfähigen Bildschirms,
    + Die Konstante ist als Parameter für die + SCREEN-Anweisung gedacht + und enthält die Nummer des grafikfähigen Bildschirms. Unterstützt das Zielsystem keine Grafik, hat die Konstante den Wert -1.
    TARGETADDR - Anfangsadresse des compilierten BASIC-Programms, - so wie sie in den Einstellungen angegeben wurde -
    TOP diff --git a/src/help/tools/basicc/datatypes.htm b/src/help/tools/basicc/datatypes.htm index 091d77c..a8234d3 100644 --- a/src/help/tools/basicc/datatypes.htm +++ b/src/help/tools/basicc/datatypes.htm @@ -22,7 +22,7 @@

    Datentypen

    Der Datentyp String wird mit einem nachgestellten $-Zeichen markiert, d.h., alle Variablen und Funktionen, deren Namen auf ein Dollarzeichen enden, sind String-Variablen bzw. Funktionen, - die eine Zeichenkette liefern. + die eine Zeichenkette speichern bzw. liefern.
    diff --git a/src/help/tools/basicc/devices.htm b/src/help/tools/basicc/devices.htm new file mode 100644 index 0000000..67e878c --- /dev/null +++ b/src/help/tools/basicc/devices.htm @@ -0,0 +1,219 @@ + + +

    BASIC-Compiler

    + +

    Ein-/Ausgabegeräte

    + +
    + Ein- und Ausgaben von Text und Daten erfolgen über Geräte. + Am häufigsten verwendet werden Tastatur und Bildschirm. + Diese beiden Geräte können mit verschiedenen Anweisungen + und Funktionen bedient werden, + z.B. INPUT, + INKEY$ und + PRINT. + Bei einigen Zielsystemen wird auch ein Drucker unterstützt, + auf dem Sie mit der Anweisung LPRINT + Ausgaben tätigen können. + Neben diesen speziellen Anweisungen ist es aber auch möglich, + Bildschirm und Drucker über einen Ausgabekanal anzusprechen. + Mit allen anderen Geräten lässt sich sogar nur über + Ein-/Ausgabekanäle kommunizieren, die mit der Anweisung + OPEN geöffnet werden. +

    + Zurück zur Anweisung OPEN: + Wenn damit ein Ein-/Ausgabekanal geöffnet werden soll, + ist der Name des Gerätes und/oder der Name einer Datei anzugeben. + Der Gerätename kann sowohl groß als auch klein geschrieben + werden. + Manche Geräte unterstützen keine Dateinamen, + d.h., bei diesen wird nur der Gerätename angegeben und + das Gerät als solches geöffnet. + Andere Geräte bieten Zugriff auf Dateien. + Hier muss hinter dem Gerätenamen, d.h. nach dem Doppelpunkt, + ein Dateiname folgen. + Abhängig vom konkreten Gerät kann der Dateiname auch + einen Pfad enthalten. +

    + Wenn kein Gerät angegeben wird, d.h., + wenn der angegebene Text keinen Doppelpunkt enthält, + werden die unterstützten Geräte, + oder genauer gesagt die zugehörigen Treiber, + der Reihe nach durchgegangen und versucht, + damit die Datei zu öffnen. + Standardmäß enthält das kompilierte Programm alle + sinnvollen Treiber. + Wenn Sie jedoch wissen, dass z.B. ein bestimmter Treiber + nicht benötigt wird, können Sie diesen + in den Compiler-Optionen ausschalten. +

    + Es ist prinzipiell auch möglich, + Geräte unter Umgehung der Ein-/Ausgabekanäle und + je nach Hardware direkt mit PEEK- + und POKE- bzw. mit + IN- und + OUT-Befehlen anzusprechen. + Doch das ist nur für spezielle Anwendungen sinnvoll + und erfordert auch spezielles Wissen. + Aus diesem Grund wird darauf nicht weiter eingegangen. +

    + Es folgt eine Beschreibung der vom JKCEMU-BASIC-Compiler + unterstützten Geräte: +

    + +

    1. Bildschirm (CRT:)

    + Ein Ausgabekanal auf das Gerät CRT: dient zur Ausgabe + von Text auf dem Bildschirm. + Grafische Ausgaben sind damit nicht möglich. + Der Sinn, einen Ausgabekanal auf den Bildschirm zu öffnen, + liegt darin, Programme schreiben zu können, + die die gleichen Ausgaben entweder parallel oder umschaltbar + auf mehreren Geräten ermöglichen. + Man braucht dann die Logik zur Erzeugung der Ausgaben nur einmal + zu implementieren und gibt nur in den OPEN-Anweisung an, + auf welchem Gerät die Ausgabe erfolgen soll. +

    + +

    2. Drucker (LPT:)

    + Ähnlich wie beim Bildschirm ist es mit dem Gerät + LPT: möglich, + einen Ausgabekanal auf den Drucker zu öffnen. + Dies ist allerdings nur möglich, + wenn das Zielsystem einen Drucker unterstützt. +

    + +

    + 3. Dateisystem (A: bis P:) +

    + Das Zielsystem CP/M-kompatibel + bietet als einziges Zugriff auf ein Dateisystem. + Dazu gibt man bei der OPEN-Anweisung + das Laufwerk (A: bis P:) gefolgt von dem + Namen der zu öffnenden Datei an. + Das Laufwerk repräsentiert das Gerät und kann weggelassen werden. + In dem Fall wird die Datei auf dem aktuellen Laufwerk geöffnet. +

    + Zwischen dem Laufwerksbuchstaben und dem Doppelpunkt kann ein + Benutzerbereich angegeben werden. + Das ist eine numerische Zahl zwischen 0 und 15. + Die Datei wird dann in dem angegeben Benutzerbereich angelegt, + anderenfalls im aktuellen Benutzerbereich + (siehe CP/M-Kommando USER). +

    + Beispiel ohne Angabe eines Benutzerbereichs: +

    +   OPEN "A:DATEI.TXT" FOR INPUT AS #1 +

    + Beispiel mit Angabe eines Benutzerbereichs: +

    +   OPEN "A12:DATEI.TXT" FOR INPUT AS #1 +

    + Dateien in einem CP/M-kompatiblen System werden immer in Blöcken + zu 128 Byte gespeichert, + d.h., die Dateilänge ist auch nur in 128-Byte-Schritten möglich. + Um aber trotzdem Dateioperationen mit einer Byte-exakte Dateilänge + zu ermöglichen, + wird das Byte 1Ah als Dateiendezeichen verwendet. + Dieses Byte wird deshalb automatisch beim Schließen + einer zum Schreiben geöffneten Datei angehängt. + Umgekehrt kann eine Datei nur bis zum Byte 1Ah gelesen werden. + Ein Null-Byte wird ebenfalls als Dateiende gewertet. +

    + Das Dateiendezeichen wirkt sich auch auf die Betriebsart APPEND + (Anhängen) aus. + Die Datei wird geöffnet und bis zum ersten Auftreten eines + 00h- oder 1Ah-Bytes gelesen. + An genau dieser Position beginnt dann das Schreiben. +

    + Die Verwendung der Bytes 00h und 1Ah als Dateiendezeichen hat aber + den Nachteil, das man diese Bytes nicht lesen kann und somit + Einschränkungen beim Zugriff auf Binärdateien hat. + Benötigen Sie vollen Zugriff auf Binärdateien, + müssen Sie diese im Binärmodus öffnen + (siehe Schlüselwort BINARY in der Anweisung + OPEN). + Der Binärmodus verwendet keine Dateiendezeichen, + d.h. es wird weder ein solches Zeichen automatisch angehägt + noch beim Lesen eines speziellen Zeichens ein Dateiende erkennt. +

    + +

    + 4. USB-Speicher an VDIP-Modul (V:) +

    + Das Gerät steht für einen FAT-formatierten USB-Speicher, + der an Port 2 eines VDIP-Moduls angeschlossen ist. + Der Gerätename V: hat absichtlich die gleiche Form + wie die aus der CP/M- und DOS-Welt bekannten Laufwerksbuchstaben, + da es wie die dortigen Laufwerke zur Speicherung von Dateien dient + und somit auch wie ein Laufwerk betrachtet werden kann. +

    + Der Dateiname kann, muss aber nicht, mit einem Pfad beginnen. + Dieser Pfad ist sowohl absolut (Trennzeichen am Anfang) + oder relativ (kein Trennzeichen am Anfang) möglich. + Als Trennzeichen sind sowohl der Backslash (\) als auch + der Slash (/) möglich. + Die Tabelle zeigt einige Beispiele: +

    + + + + + + + + + + + + + + + + + +
    V:abc.txt + Dateiname ohne Pfad,
    + Die Datei wird im aktuellen Verzeichnis gesucht. +
    V:\abc.txt + Dateiname mit absoluter Pfadangabe auf das Wurzelverzeichnis +
    V:/xxx/abc.txtDateiname mit absoluter Pfadangabe
    V:xxx/abc.txt + Dateiname mit relativer Pfadangabe,
    + Die Datei wird vom aktuellen Verzeichnis ausgehend + im Unterverzeichnis xxx gesucht. +
    +
    + Achtung! Systembedingt wird beim Öffnen einer Datei + mit Pfadangabe zuerst in das Verzeichnis gewechselt und + danach die Datei geöffnet. + Nach Schließen der Datei, + aber auch wenn die Datei nicht geöffnet werden konnte, + bleibt dieses Verzeichnis weiterhin als aktuelles Verzeichnis + im VDIP-Modul gesetzt, d.h., wenn danach eine andere Datei + ohne Pfad oder mit relativer Pfadangabe geöffnet werden soll, + wird sie wahrscheinlich nicht gefunden, + da nun in einem anderen bzw. von einem anderen Verzeichnis aus + gesucht wird. +

    + Hinweis! + Da Dateien auf dem USB-Speicher eine Byte-exakte Länge haben, + ist die Verwendung von speziellen Dateiendezeichen wie beim weiter + oben beschriebenen CP/M-kompatiblen Dateisysten nicht nötig. + Aus diesem Grund gibt es auch keine Unterscheidung zwischen einem + Text- und einem Binärmodus, d.h., + das Schlüsselwort BINARY in der Anweisung + OPEN hat keine Wirkung. + Die Datei wird immer in einer Art Binärmodus geöffnet. + Trotzdem sollten Sie aus Gründen der Portabilität + das Schlüsselwort BINARY mit angeben, + wenn es sich um Binärdateien handelt. + + diff --git a/src/help/tools/basicc/expressions.htm b/src/help/tools/basicc/expressions.htm index 989ea37..adb1c50 100644 --- a/src/help/tools/basicc/expressions.htm +++ b/src/help/tools/basicc/expressions.htm @@ -48,7 +48,7 @@

    1.3. Funktionen

    Diese liefern eine Zeichenkette zurück.

    -

    1.4. Operatoren mit einem Operanden

    +

    1.4. Operatoren mit einem Operanden

    @@ -69,7 +69,7 @@

    1.4. Operatoren mit einem Operanden

    OperatorBedeutung


    -

    1.5. Operatoren mit zwei Operanden

    +

    1.5. Operatoren mit zwei Operanden

    @@ -275,156 +275,126 @@

    3. Grammatik

    Folgende Grammatik beschreibt die Syntax der Ausdrücke:

    Ausdruck:
    -   Negationsausdruck
    -   Ausdruck   AND -   Negationsausdruck
    -   Ausdruck   OR -   Negationsausdruck
    -   Ausdruck   XOR -   Negationsausdruck +   Negationsausdruck
    +   Ausdruck AND Negationsausdruck
    +   Ausdruck OR Negationsausdruck
    +   Ausdruck XOR Negationsausdruck

    Negationsausdruck:
    -   Vergleichsausdruck
    -   NOT   Vergleichsausdruck +   Vergleichsausdruck
    +   NOT Vergleichsausdruck

    Vergleichsausdruck:
    -   Verschiebeausdruck
    -   Vergleichsausdruck -   Vergleichsoperator -   Verschiebeausdruck
    -   Einfacher_String-Ausdruck -   Vergleichsoperator -   Einfacher_String-Ausdruck +   Verschiebeausdruck
    +   Vergleichsausdruck Vergleichsoperator Verschiebeausdruck
    +   Einfacher_String-Ausdruck Vergleichsoperator Einfacher_String-Ausdruck

    Vergleichsoperator:
    -   <
    -   <=
    -   =
    -   =>
    -   >
    -   <> +   <
    +   <=
    +   =
    +   =>
    +   >
    +   <>

    Verschiebeausdruck:
    -   Additionsausdruck
    -   Verschiebeausdruck -   Verschiebeoperator -   Additionssausdruck +   Additionsausdruck
    +   Verschiebeausdruck Verschiebeoperator Additionssausdruck

    Verschiebeoperator:
    -   SHL
    -   SHR +   SHL
    +   SHR

    Additionsausdruck:
    -   Multiplikationsausdruck
    -   Additionsausdruck -   Additionsoperator -   Multiplikationsausdruck +   Multiplikationsausdruck
    +   Additionsausdruck Additionsoperator Multiplikationsausdruck

    Additionsoperator:
    -   ADD
    -   SUB
    -   +
    -   - +   ADD
    +   SUB
    +   +
    +   -

    Multiplikationsausdruck:
    -   Unärer_Ausdruck
    -   Multiplikationsausdruck -   Multiplikationsoperator -   Unärer_Ausdruck +   Unärer_Ausdruck
    +   Multiplikationsausdruck Multiplikationsoperator Unärer_Ausdruck

    Multiplikationsoperator:
    -   *
    -   /
    -   MOD +   *
    +   /
    +   MOD

    Unärer_Ausdruck:
    -   Primärausdruck
    -   Vorzeichen   Primärausdruck +   Primärausdruck
    +   Vorzeichen Primärausdruck

    Vorzeichen:
    -   +
    -   - +   +
    +   -

    Primärausdruck:
    -   (   Ausdruck   )
    -   Zahlenliteral
    -   Zeichenliteral
    -   Einfache_numerische_Variable
    -   Numerische_Feldvariable
    -   Numerischer_Funktionsaufruf +   ( Ausdruck )
    +   Zahlenliteral
    +   Zeichenliteral
    +   Einfache_numerische_Variable
    +   Numerische_Feldvariable
    +   Numerischer_Funktionsaufruf

    Zahlenliteral:
    -   Folge_von_Ziffern_0...9
    -   &B   Folge_von_Ziffern_0_oder_1
    -   &H   Folge_von_Hexadezimalziffern +   Folge_von_Ziffern_0...9
    +   &B Folge_von_Ziffern_0_oder_1
    +   &H Folge_von_Hexadezimalziffern

    Zeichenliteral:
    -   '   8-Bit-Zeichen   ' +   ' 8-Bit-Zeichen '

    Einfache_numerische_Variable:
    -   numerischer_Bezeichner +   numerischer_Bezeichner

    Numerische_Feldvariable:
    -   numerischer_Bezeichner -   (   Ausdruck -   )
    -   numerischer_Bezeichner -   (   Ausdruck -   ,   Ausdruck -   ) +   numerischer_Bezeichner ( Ausdruck )
    +   numerischer_Bezeichner ( Ausdruck , Ausdruck )

    Numerischer_Funktionsaufruf:
    -   numerischer_Bezeichner
    -   numerischer_Bezeichner -   (   Argumentliste -   ) +   numerischer_Bezeichner
    +   numerischer_Bezeichner ( Argumentliste )

    numerischer_Bezeichner:
    -   Buchstabe_A...Z
    -   Buchstabe_A...Z -   Folge_von_Buchstaben_A...Z_und_Zifferrn_0...9 +   Buchstabe_A...Z
    +   Buchstabe_A...Z Folge_von_Buchstaben_A...Z_und_Zifferrn_0...9

    Argumentliste:
    -   Argument
    -   Argumentliste -   ,   Argument +   Argument
    +   Argumentliste , Argument

    Argument:
    -   Ausdruck
    -   Einfacher_String-Ausdruck +   Ausdruck
    +   Einfacher_String-Ausdruck

    Einfacher_String-Ausdruck:
    -   String-Literal
    -   String-Variable
    -   String-Feldvariable
    -   String-Funktion +   String-Literal
    +   String-Variable
    +   String-Feldvariable
    +   String-Funktion

    String-Literal:
    -   " -   Folge_von_8-Bit-Zeichen -   " +   " Folge_von_8-Bit-Zeichen "

    String-Variable:
    -   String-Bezeichner   $ +   String-Bezeichner $

    String-Feldvariable:
    -   String-Bezeichner   $ -   (   Ausdruck -   )
    -   String-Bezeichner   $ -   (   Ausdruck -   ,   Ausdruck   ) +   String-Bezeichner $ ( Ausdruck )
    +   String-Bezeichner $ ( Ausdruck , Ausdruck )

    String-Funktion:
    -   String-Bezeichner
    -   String-Bezeichner( -   Argumentliste   ) +   String-Bezeichner
    +   String-Bezeichner ( Argumentliste )

    String-Bezeichner:
    -   Buchstabe A...Z   $
    -   Buchstabe A...Z -   Folge_von_Buchstaben_A...Z_und_Zifferrn_0...9 -   $ +   Buchstabe A...Z $
    +   Buchstabe A...Z Folge_von_Buchstaben_A...Z_und_Zifferrn_0...9 $
    + diff --git a/src/help/tools/basicc/functions.htm b/src/help/tools/basicc/functions.htm index 7691788..646257f 100644 --- a/src/help/tools/basicc/functions.htm +++ b/src/help/tools/basicc/functions.htm @@ -20,18 +20,14 @@

    Funktionen und Systemvariablen

     ABS  ASC  ASM -  AVAILABLE  BIN$  CHR$  DEEK -  DNSSERVER$  EOF  ERR  ERR$ -  GATEWAY$  HEX$  HIBYTE -  HOSTBYNAME$  H_CHAR  H_PIXEL  IN @@ -39,27 +35,22 @@

    Funktionen und Systemvariablen

     INP  INPUT$  INSTR +  IS_TARGET  JOYST  LCASE$  LEFT$  LEN  LOBYTE -  LOCALADDR$ -  LOCALPORT  LOWER$  LTRIM$ -  MACADDR$  MAX  MEMSTR$  MID$  MIN  MIRROR$ -  NETMASK$  PEEK  POINT  PTEST -  REMOTEADDR$ -  REMOTEPORT  RIGHT$  RND  RTRIM$ @@ -68,7 +59,6 @@

    Funktionen und Systemvariablen

     SQR  STR$  STRING$ -  TARGETID$  TRIM$  UCASE$  UPPER$ @@ -77,7 +67,6 @@

    Funktionen und Systemvariablen

     W_CHAR  W_PIXEL  XPOS -  YELLOW  YPOS

    @@ -124,10 +113,6 @@

    4. Liste der Funktionen

    - - - - @@ -140,10 +125,6 @@

    4. Liste der Funktionen

    - - - - @@ -156,10 +137,6 @@

    4. Liste der Funktionen

    - - - - @@ -168,10 +145,6 @@

    4. Liste der Funktionen

    - - - - + + + + @@ -225,14 +202,6 @@

    4. Liste der Funktionen

    - - - - - - - - @@ -241,10 +210,6 @@

    4. Liste der Funktionen

    - - - - @@ -265,30 +230,18 @@

    4. Liste der Funktionen

    - - - - - + - - - - - - - - @@ -321,10 +274,6 @@

    4. Liste der Funktionen

    - - - - @@ -359,10 +308,6 @@

    4. Liste der Funktionen

    - - - - @@ -454,39 +399,23 @@

    ASM

    ASM DATA "mein_string: DB 'ABC',0"

    + Achtung! Der BASIC-Compiler enthält einen Global-Optimizer, + der den erzeugten Assembler-Code durchgeht und unnötige Befehle + entfernt. + Dabei kann auch der mit ASM-Funktionen erzeugte Assembler-Code + verändert werden. + Der Global-Optimizer fasst aber nur solche Assembler-Zeilen an, + bei denen die Befehls-Memonik durch einen Tabulator vom Zeilenanfang + bzw. der Marke getrennt ist. + Wenn sie also sichergehen wollen, dass der Optimizer Ihren + Assembler-Code nicht verändert, + dann verwenden Sie keine Tabulatoren in den ASM-Funktionen. +

    Achtung! Die ASM-Funktion greift direkt in die Programmcodeerzeugung ein und ist deshalb nur für Experten gedacht, die genau wissen, was sie tun!

    -

    AVAILABLE

    -
    OperatorBedeutung
    ANDbitweises UND
    ASM Assemblerquelltext einfügen
    AVAILABLEAnzahl der im Eingabekanal verfügbaren Bytes ermitteln
    BIN$ numerischen Wert in eine Binärzahl umwandeln DEEK 16-Bit-Wert (2 Bytes) aus dem Arbeitsspeicher lesen
    DNSSERVER$IP-Adresse des DNS-Servers ermitteln
    EOF Ende eines Eingabekanals bzw. einer Datei erkennen ERR$ Letzten Fehlertext ermitteln
    GATEWAY$IP-Adresse des Gateways ermitteln
    HEX$ numerischen Wert in eine Hexadezimalzahl umwandeln HIBYTE Höherwertiges Byte ermitteln
    HOSTBYNAME$IP-Adresse anhand des Hostnamens ermitteln
    H_CHAR @@ -205,6 +178,10 @@

    4. Liste der Funktionen

    INSTR Zeichenkette in einer anderen Zeichenkette suchen
    IS_TARGETTesten auf ein Zielsystem
    JOYST Joystick abfragen LOBYTE Niederwertiges Byte ermitteln
    LOCALADDR$Eigene IP-Adresse ermitteln
    LOCALPORTLokale Portnummer ermitteln
    LOWER$ Zeichenkette in Kleinbuchstaben wandeln LTRIM$ Führende weiße Leerzeichen abschneiden
    MACADDR$MAC-Adresse ermitteln
    MAX Größter Wert aus einer Liste von Werten ermitteln MIRROR$ Zeichenkette spiegeln
    NETMASK$Netzwerkmaske ermitteln
    PEEK 8-Bit-Wert (1 Byte) aus dem Arbeitsspeicher lesen
    POINTTesten eines PixelsFarbe eines Pixels ermitteln
    PTEST Testen eines Pixels
    REMOTEADDR$IP-Adresse der Gegenstelle ermitteln
    REMOTEPORTPortnummer der Gegenstelle ermitteln
    RIGHT$ Ende einer Zeichenkette extrahieren STRING$ Zeichen oder Zeichenkette vervielfältigen
    TARGETID$Zielsystem ermitteln
    TRIM$ weiße Leerzeichen abschneiden XPOS aktuelle X-Koordinate des Grafikcursors ermitteln
    YELLOWNummer der Farbe gelb ermitteln
    YPOS aktuelle Y-Koordinate des Grafikcursors ermitteln
    - - - - - - - - -
    Syntax: - AVAILABLE ( <Kanal> )
    - AVAILABLE ( # <Kanal> ) -
    Kanal:numerischer Ausdruck mit dem Wert 1 oder 2
    -
    - Die Funktion ermittelt die Anzahl der im Eingabekanal verfügbaren - Bytes, die ohne zu warten gelesen werden können. - Bei einer Netzwerkkommunikation bedeutet das die Anzahl - der empfangenen Bytes, die noch nicht gelesen wurden. - Beim Lesen von einer Datei liefert die Funktion die Anzahl der Bytes - im Puffer, d.h., die Bytes, - die ohne Zugriff auf das Speichermedium gelesen werden können. -

    - Die Funktion setzt die Fehlervariablen - ERR und - ERR$. -

    -

    BIN$

    @@ -503,7 +432,7 @@

    BIN$


    Die Funktion BIN$ erzeugt eine Zeichenkette, die den übergebenen Wert als Binärzahl darstellt. - Wird das zweite Argument weggelassen oder dieses ist Null, + Wird das zweite Argument weggelassen oder dieses ist 0, dann richtet sich die Anzahl der Stellen, d.h. die Länge der erzeugten Zeichenkette, nach der Größe des übergebenen Wertes. @@ -524,13 +453,13 @@

    CHR$

    die nur aus dem einen Zeichen besteht, welches dem übergebenen Zeichencode (i.d.R. ASCII-Code) entspricht. - Wird der Wert Null übergeben, ist die zurückgelieferte + Wird der Wert 0 übergeben, ist die zurückgelieferte Zeichenkette leer.

    Achtung! Bei den Anweisungen PRINT und LPRINT wird mit CHR$(0) - ein Null-Byte ausgegegen. + ein Null-Byte ausgegeben.

    DEEK

    @@ -548,13 +477,6 @@

    DEEK

    ab der angegebenen Adresse und liefert sie als 16-Bit-Wert zurück.

    -

    DNSSERVER$

    - Die Funktion liefert die im KCNet eingetragene IP-Adresse - des DNS-Servers zurück. - Die Fehlervariablen ERR und - ERR$ werden gesetzt. -

    -

    EOF

    @@ -575,13 +497,17 @@

    EOF

    Dabei werden die Fehlervariablen ERR und ERR$ gesetzt.

    + Bei Dateien liefert die Funktion erst dann sicher + TRUE zurück, + wenn versucht wurde, über das Dateiende hinaus zu lesen. +

    ERR

    Die Systemvariable ERR enthält den letzten Fehlercode. Der Wert 0 (Konstante E_OK) bedeutet, dass bei der letzten Anweisung oder Funktion, die die Fehlervariable gesetzt hat, kein Fehler aufgetreten ist. - Ein Wert ungleich Null steht für einen Fehler. + Ein Wert ungleich 0 steht für einen Fehler. Zum Auswerten der Fehlervariable ERR gibt es Fehlercodekonstanten.

    @@ -596,9 +522,9 @@

    ERR$


    ...:'Funktion oder Anweisung, die die Fehlervariablen setzt
    IF ERR THEN
    -   PRINT ERR$
    +   PRINT ERR$
    ELSE
    - ...:'kein Fehler aufgetreten
    +   ...:'kein Fehler aufgetreten
    ENDIF

    @@ -612,13 +538,6 @@

    ERR$

    und ERR$ nicht verwenden.

    -

    GATEWAY$

    - Die Funktion liefert die im KCNet eingetragene IP-Adresse - des Gateways zurück. - Die Fehlervariablen ERR und - ERR$ werden gesetzt. -

    -

    HEX$

    @@ -635,7 +554,7 @@

    HEX$


    Die Funktion HEX$ erzeugt eine Zeichenkette, die den übergebenen Wert als Hexadezimalzahl darstellt. - Wird das zweite Argument weggelassen oder dieses ist Null, + Wird das zweite Argument weggelassen oder dieses ist 0, dann richtet sich die Anzahl der Stellen, d.h. die Länge der erzeugten Zeichenkette, nach der Größe des übergebenen Wertes. @@ -659,30 +578,6 @@

    HIBYTE

    erzeugt aber kürzeren und schnelleren Programmcode.

    -

    HOSTBYNAME$

    -
    - - - - - -
    Syntax: - HOSTBYNAME$ ( <Hostname> ) -
    Hostname:einfacher String-Ausdruck
    -
    - Die Funktion ermittelt die IP-Adresse zu dem übergebenen - Rechnernamen. - Dazu wird der im KCNet eingetragene DNS-Server angefragt. - Als Rechnername kann auch eine IP-Adresse in der üblichen - Schreibweise übergeben werden. - In dem Fall wird der DNS-Server nicht angefragt, - sondern die übergebene IP-Adresse ohne eventuell führende - und angehängten Leerzeichen zurückgegeben. - Die Funktion setzt die Fehlervariablen - ERR und - ERR$. -

    -

    H_CHAR

    Die Systemvariable enthält die Höhe des Bildschirms in Zeichenpositionen, d.h., @@ -731,6 +626,14 @@

    IN
    INP

    Beide Funktionen sind identisch. Aus Gründen der Kompatibilität zu diversen BASIC-Dialekten werden beide Funktionsnamen unterstützt.

    + Achtung! Obwohl der Mikroprozessor offiziell nur + 8 Bit große Ein-/Ausgabeadressen unterstützt, + ist die Nutzung von 16-Bit-Adressen möglich. + Der Compiler übersetzt die Anweisung in solch einen Programmcode, + der aufgrund des undokumentierten Verhaltens des Mikroprozessors + auch eine 16 Bit große Ein-/Ausgabeadresse + wirksam werden lässt. +

    INPUT$

    @@ -780,22 +683,30 @@

    INPUT$

    (Konstante E_OK, kein Fehler aufgetreten), dann wurde hinter der Zeichenkette ein Null-Byte gelesen. Damit ist auch generell das Lesen von Binärdateien möglich. - Das Beispiel zeigt, wie eine Datei Byte für Byte - in die Variable B eingelesen wird: + Das Beispiel zeigt, wie eine Datei Byte für Byte eingelesen + und als Hex-Dump auf dem Bildschirm ausgegeben wird:

    - OPEN "Datei" FOR INPUT AS #1
    + INPUT "Datei:";F$
    + OPEN F$ FOR BINARY INPUT AS #1
    IF ERR THEN
    -   PRINT ERR$
    +   PRINT ERR$
    ELSE
    -   WHILE NOT EOF(#1)
    -     B=ASC(INPUT$(1,#1))
    -     IF ERR THEN EXIT
    -     ...:'mit dem gelesenen Byte etwas tun
    -   WEND
    -   IF ERR THEN PRINT ERR$
    -   CLOSE #1
    +   N=0
    +   WHILE NOT EOF(#1)
    +     B=ASC(INPUT$(1,#1))
    +     IF ERR THEN
    +       PRINT ERR$:EXIT
    +     ELSE
    +       PRINT CHR$(32);HEX$(B,2);
    +       N=N+1
    +       IF N=8 THEN N=0:PRINT
    +     ENDIF
    +   WEND
    +   PRINT
    +   IF ERR THEN PRINT ERR$
    +   CLOSE #1
    ENDIF

    @@ -848,12 +759,48 @@

    INSTR

    Bei den meisten der international üblichen BASIC-Dialekte ist die erste Zeichenkette die durchsuchte und die zweite die gesuchte Zeichenkette. - Aus diesem Grund ist das auch in JKCEMU so realisiert. - Beim KC-BASIC ist das dagegen genau anders herum. + Aus diesem Grund ist das auch im JKCEMU so realisiert. + Beim KC-BASIC ist es dagegen genau anders herum. Wenn Sie also KC-BASIC-Programme compilieren bzw. portieren möchten, müssen Sie die beiden Zeichenketten vertauschen.

    +

    IS_TARGET

    + Die Funktion IS_TARGET ermittelt, + ob das Programm für ein bestimmtes Zielsystem compiliert wurde. + Damit kann man BASIC-Programme portabel schreiben und diese + trotzdem auf ganz spezifische Eigenheiten des Zielsystems anpassen. + Man ruft die Funktion üblicherweise mit einer + Konstante für das Zielsystem + auf. + Folgendes Beispiel demonstriert die Anwendung: +
    + +
    + IF IS_TARGET(TARGET_KC85) THEN PRINT "Ich bin ein KC85/2..5 oder HC900"
    + IF IS_TARGET(TARGET_Z9001) THEN PRINT "Ich bin ein KC85/1, KC87 oder Z9001"
    + IF IS_TARGET(TARGET_Z1013) THEN PRINT "Ich bin ein Z1013"
    +
    +
    + Achtung! Die Funktion IS_TARGET liefert auch dann + TRUE, + wenn für ein abgeleitetes Zielsystem compiliert wird + und man auf das allgemeinere Zielsystem testet, + d.h., wenn z.B. in den Compiler-Optionen das Zielsystem + TARGET_KC85_4 (KC85/4..5 mit Unterstützung beider Bildspeicher) + eingestellt ist, liefert die Funktion sowohl bei + TARGET_KC85 als auch bei TARGET_KC85_4 TRUE. +

    + Achtung! Die Funktion IS_TARGET ermittelt nicht, + auf welchem Computertyp das Programm tatsächlich ausgeführt wird, + sondern für welchen Computertyp das BASIC-Programm compiliert wurde. + So kann z.B. ein Programm für den AC1 übersetzt werden, + aber trotzdem auf einem LLC2 ausgeführt werden, + wenn es keine AC1-spezifischen Dinge verwendet. + Die Funktion IS_TARGET(TARGET_AC1) würde dann trotzdem TRUE + und IS_TARGET(TARGET_LLC2) FALSE liefern. +

    +

    JOYST

    @@ -870,61 +817,72 @@

    JOYST


    Die Funktionen liest den aktuellen Status des angegebenen Joysticks. Der erste Joystick hat die Nummer 0. - Der Rückgabewert hat folgende Bedeutung: + Die Bit-Belegung des Rückgabewertes unterscheidet sich zwischen + den einzelnen Zielsystemen und sollte deshalb + mit folgenden Konstanten ausgewertet werden:

    - - - - - + - + - + - - + + - - + + - + - - + + + + + +
    WertBedeutung
    0Joystick nicht betätigt oder nicht angeschlossen
    KonstanteBedeutung
    1JOYST_LEFT Joystick nach links gedrückt
    2JOYST_RIGHT Joystick nach rechts gedrückt
    4Joystick nach unten gedrücktJOYST_UPJoystick nach oben gedrückt
    8Joystick nach oben gedrücktJOYST_DOWNJoystick nach unten gedrückt
    16JOYST_BUTTON1 Aktionsknopf 1 gedrückt
    32Aktionsknopf 2 gedrückt (falls vorhanden)JOYST_BUTTON2Aktionsknopf 2 gedrückt
    JOYST_BUTTONSAktionsknopf 1 oder 2 gedrückt

    - Zwischenstellungen werden durch die Addition der einzelnen Werte - ausgedrückt. - So bedeutet z.B. der Wert 21, dass der Joystick nach - links unten und gleichzeitig der Aktionsknopf 1 - gedrückt ist ((1=links) + (4=unten) + (16=Aktionsknopf) = 21). - Mit dem AND-Operator lassen sich die Einzelaktionen extrahieren: + Die JOYST-Funktion liefert einen numerischen Wert zurück, + bei der jedes gesetzte Bit für eine bestimmte Aktion steht. + Dabei können mehrere Bits gleichzeitig gesetzt sein, + z.B. die Bits für links und nach oben, + wenn der Joystick nach links oben gedrückt wird. + Die Joystick-Konstanten sind so definiert, + dass das jeweilige Bit der betreffenden Aktion gesetzt ist. + Mit Hilfe der + AND-Verknüpfung + können die einzelnen Aktionen getestet werden:

    A=JOYST(0)
    - IF A AND 1 THEN PRINT "links"
    - IF A AND 2 THEN PRINT "rechts"
    - IF A AND 4 THEN PRINT "ab"
    - IF A AND 8 THEN PRINT "auf"
    - IF A AND 16 THEN PRINT "Feuer 1"
    - IF A AND 32 THEN PRINT "Feuer 2"
    - IF A AND 48 THEN PRINT "Feuer 1 oder 2"
    + IF A AND JOYST_LEFT THEN PRINT "links"
    + IF A AND JOYST_RIGHT THEN PRINT "rechts"
    + IF A AND JOYST_DOWN THEN PRINT "ab"
    + IF A AND JOYST_UP THEN PRINT "auf"
    + IF A AND JOYST_BUTTON1 THEN PRINT "Feuer 1"
    + IF A AND JOYST_BUTTON2 THEN PRINT "Feuer 2"
    + IF A AND JOYST_BUTTONS THEN PRINT "Feuer 1 oder 2"

    + Auf einem Zielsystem, bei dem nur ein Feuerknopf pro Joystick + unterstützt wird, hat die Konstante JOYST_BUTTONS + den gleichen Wert wie JOYST_BUTTON1 + und JOYST_BUTTON2 ist 0. Auf Systemen, die keine Joystick-Unterstützung bieten, - liefert die JOYST-Funktion immer 0. + liefert die JOYST-Funktion immer 0 + und die Joystick-Konstanten haben auch alle den Wert 0.

    LCASE$

    @@ -980,10 +938,10 @@

    LEN

    zurück.

    Achtung! Bei Zeichenketten, die mehr als 32767 Zeichen - enthalten, ist der Rückgabewert kleiner Null! + enthalten, ist der Rückgabewert kleiner 0! Die interne String-Verarbeitung kann zwar so lange Zeichenketten nicht erzeugen, aber mit der Funktion - MEMSTR$ sind überlange + MEMSTR$ sind überlange Zeichenketten prinzipiell möglich.

    @@ -1005,38 +963,6 @@

    LOBYTE

    erzeugt aber kürzeren und schnelleren Programmcode.

    -

    LOCALADDR$

    - Die Funktion liefert die im KCNet eingetragene lokale, - d.h. eigene, IP-Adresse zurück. - Die Fehlervariablen ERR und - ERR$ werden gesetzt. -

    - -

    LOCALPORT

    - - - - - - - - - -
    Syntax: - LOCALPORT ( <Kanal> )
    - LOCALPORT ( # <Kanal> ) -
    Kanal:numerischer Ausdruck mit dem Wert 1 oder 2
    -
    - Die Funktion ermittelt die lokale Portnummer, - die der Kanal verwendet. - Der Aufruf ist nur erlaubt, wenn der Kanal mit. - ACCEPT, - CONNECT oder - DATAGRAM geöffnet wurde. - Die Fehlervariablen ERR und - ERR$ werden gesetzt. -

    -

    LOWER$

    @@ -1075,12 +1001,6 @@

    LTRIM$

    Es werden somit die führenden weißen Leerzeichen abgeschnitten.

    -

    MACADDR$

    - Die Funktion liefert die MAC-Adresse des KCNet-Moduls zurück. - Die Fehlervariablen ERR und - ERR$ werden gesetzt. -

    -

    MAX

    @@ -1120,8 +1040,8 @@

    MEMSTR$


    A=TOP
    FOR I=65 TO 90
    -  POKE A,I
    -  A=A+1
    +   POKE A,I
    +   A=A+1
    NEXT I
    POKE A,0
    PRINT MEMSTR$(TOP)
    @@ -1187,12 +1107,6 @@

    MIRROR$

    ist das erste Zeichen in der zurückgelieferten.

    -

    NETMASK$

    - Die Funktion liefert die im KCNet eingetragene Netzwerkmaske zurück. - Die Fehlervariablen ERR und - ERR$ werden gesetzt. -

    -

    PEEK

    @@ -1225,18 +1139,18 @@

    POINT


    - Die Funktion ermittelt den Zustand eines Pixels. - Ist das Pixel an der angegebenen Position gesetzt, - wird 1 zurückgeliefert, bei nicht gesetztem Pixel eine 0. + Die Funktion ermittelt die aktuelle Farbe des Pixels. Im Fehlerfall, d.h. wenn der angegebene Punkt außerhalb des Bildschirms liegt oder keine Grafikumgebung vorhanden ist, wird -1 zurückgeliefert. Lesen Sie bitte auch die Hinweise zur Grafikunterstützung.

    - Die POINT-Funktion ist identisch zur PTEST-Funktion. - Der BASIC-Compiler unterstützt beide Funktionsnamen - aus Gründen der Kompatibilität zu diversen BASIC-Dialekten. + Achtung! Wenn das BASIC-Programm für ein Zielsystem + mit Farbunterstützung compiliert aber auf einem Computer + ohne Farbunterstützung gestartet wird, + liefert die Funktion falsche Werte zurück, + da sie die Farbe aus dem nicht vorhandenen Farbspeicher liest.

    PTEST

    @@ -1257,61 +1171,14 @@

    PTEST


    - Die Funktion testet ein Pixel und ist identisch zur POINT-Funktion. - Der BASIC-Compiler unterstützt beide Funktionsnamen - aus Gründen der Kompatibilität zu diversen BASIC-Dialekten. -

    - -

    REMOTEADDR$

    - - - - - - - - - -
    Syntax: - REMOTEADDR$ ( <Kanal> )
    - REMOTEADDR$ ( # <Kanal> ) -
    Kanal:numerischer Ausdruck mit dem Wert 1 oder 2
    -
    - Die Funktion ermittelt die IP-Adresse der Gegenstelle. - Der Aufruf ist nur erlaubt, wenn der Kanal mit - ACCEPT, - CONNECT oder - DATAGRAM geöffnet wurde. - Im Fall von DATAGRAM liefert die Funktion die Absenderadresse - der zuletzt empfangenen Daten. - Die Fehlervariablen ERR und - ERR$ werden gesetzt. -

    - -

    REMOTEPORT

    - - - - - - - - - -
    Syntax: - REMOTEPORT ( <Kanal> )
    - REMOTEPORT ( # <Kanal> ) -
    Kanal:numerischer Ausdruck mit dem Wert 1 oder 2
    -
    - Die Funktion ermittelt die Portnummer der Gegenstelle. - Der Aufruf ist nur erlaubt, wenn der Kanal mit. - ACCEPT, - CONNECT oder - DATAGRAM geöffnet wurde. - Im Fall von DATAGRAM liefert die Funktion die Absenderportnummer - der zuletzt empfangenen Daten. - Die Fehlervariablen ERR und - ERR$ werden gesetzt. + Die Funktion ermittelt den Zustand eines Pixels. + Ist das Pixel an der angegebenen Position gesetzt, + wird 1 zurückgeliefert, bei nicht gesetztem Pixel eine 0. + Im Fehlerfall, d.h. wenn der angegebene Punkt außerhalb + des Bildschirms liegt oder keine Grafikumgebung vorhanden ist, + wird -1 zurückgeliefert. + Lesen Sie bitte auch die Hinweise zur + Grafikunterstützung.

    RIGHT$

    @@ -1376,9 +1243,10 @@

    SGN


    - Die Funktion liefert -1, wenn der Wert des angegebenen Ausdrucks - kleiner Null ist, 1 wenn der Wert größer Null ist - und Null, wenn der Wert Null ist. + Die Funktion liefert -1 zurück, + wenn der Wert des angegebenen Ausdrucks kleiner 0 ist, + 1 wenn der Wert größer 0 ist + und 0, wenn der Wert 0 ist.

    SPACE$

    @@ -1441,44 +1309,10 @@

    STRING$

    die aus Anzahl Wiederholungen der übergebenen Zeichenkette bzw. des mit seinem Code übergebenen Zeichens besteht. Die Anzahl der Wiederholungen wird im ersten Argument übergeben - und kann auch Null sein. + und kann auch 0 sein. In dem Fall wird eine leere Zeichenkette zurückgeliefert.

    -

    TARGETID$

    - Die Funktion TARGETID$ liefert eine Zeichenkette, - die das Zielsystem identifiziert. - Mit Hilfe dieser Zeichenkette kann ein BASIC-Programm portabel geschrieben - und dabei trotzdem auf ganz spezifische Eigenheiten des Zielsystems - angepasst werden. - Folgendes Beispiel demonstriert die Anwendung: -
    - -
    - IF TARGETID$="KC85" THEN PRINT "Farbe"
    - IF INSTR(TARGETID$,"Z9001")>0 THEN PRINT "Farbe optional"
    - IF TARGETID$="Z1013" THEN PRINT "schwarz/weiss"
    -
    -
    - Welche Zeichenkette die einzelnen Zielsysteme liefern, - finden Sie in den Hinweisen zu den Zielsystemen. -

    - Achtung! Das Zielsystem ist das System, - für welches der Compiler den Programmcode erzeugt hat. - Es muss nicht zwingend genau das System sein, - auf dem das compilierte BASIC-Programm gerade ausgeführt wird. - Wenn man z.B. ein Programm für das Zielsystem Z9001_KRT - übersetzt, kann man es auch auf einem Z9001 ohne KRT-Grafik starten. - Probleme treten erst auf, wenn das Programm mit der SCREEN-Anweisung - auf die KRT-Grafik umschaltet. - Solange das jedoch nicht der Fall ist, also in Textmodus, - ist das Programm lauffähig. - TARGETID$ liefert in dem Fall "Z9001_KRT", - obwohl es auf einem Z9001 ohne KRT-Grafik ausgeführt wird. -

    - Die Funktion TARGETID$ hat keine Argumente. -

    -

    TRIM$

    @@ -1533,7 +1367,8 @@

    USR


    Mit USR wird eine von 10 möglichen Funktionen aufgerufen, die der Anwender in Maschinencode frei implementieren kann. - Die Adresse muss vorher mit der Anweisung DEF USR + Die Adresse muss vorher mit der Anweisung + DEF USR festgelegt worden sein. Das Argument wird im DE-Register der Benutzerfunktion übergeben. Die Benutzerfunktion selbst muss mit einem Return-Befehl abgeschlossen @@ -1617,13 +1452,13 @@

    VAL


    DO
    -   INPUT "Eingabe:";E$
    -   V=VAL(E$,2)
    -   IF ERR=E_OK THEN PRINT "Binaerzahl, Dezimalwert=";V
    -   V=VAL(E$,10)
    -   IF ERR=E_OK THEN PRINT "Dezimalzahl, Dezimalwert=";V
    -   V=VAL(E$,16)
    -   IF ERR=E_OK THEN PRINT "Hexadezimalzahl, Dezimalwert=";V
    +   INPUT "Eingabe:";E$
    +   V=VAL(E$,2)
    +   IF ERR=E_OK THEN PRINT "Binaerzahl, Dezimalwert=";V
    +   V=VAL(E$,10)
    +   IF ERR=E_OK THEN PRINT "Dezimalzahl, Dezimalwert=";V
    +   V=VAL(E$,16)
    +   IF ERR=E_OK THEN PRINT "Hexadezimalzahl, Dezimalwert=";V
    LOOP

    diff --git a/src/help/tools/basicc/graphics.htm b/src/help/tools/basicc/graphics.htm index 0501a3a..4bf0c0f 100644 --- a/src/help/tools/basicc/graphics.htm +++ b/src/help/tools/basicc/graphics.htm @@ -56,8 +56,10 @@

    Grafikbefehle

  • DRAW
  • DRAWR
  • LABEL
  • +
  • LINE
  • MOVE
  • MOVER
  • +
  • PAINT
  • PEN
  • PLOT
  • PLOTR
  • @@ -67,15 +69,39 @@

    Grafikbefehle


    +

    Farben

    + Der BASIC-Compiler kennt Anweisungen zum Setzen der Farbe + (BORDER, + COLOR, + INK , + PAPER). + Jedoch unterstützt nicht jedes Zielsystem Farben. + Auf Zielsystemen, die keine Farbgrafik bieten, + haben die Anweisungen zum Setzen einer Farbe keine Wirkung + und erzeugen auch keinen Programmcode, + d.h., es können getrost portable BASIC-Programme + mit Farbunterstützung geschrieben und auf einem + Schwarz-/Weiß-Zielsystem compiliert werden, + ohne dass dadurch unnützer Programmcode erzeugt wird. +

    + Die Farbnummern unterscheiden sich zwischen den einzelnen Zielsystemen. + Um trotzdem portablen Programmcode schreiben zu können, + gibt es für einige häufig verwendete Farben + Konstanten, + die Sie auch verwenden sollten. +

    +

    Programmbeispiel

    - Das Beispiel gibt im Grafikmodus einen eingerahmten Text aus. + Das Beispiel gibt im Grafikmodus einen eingerahmten Text aus, + zeichnet einen Kreis drumherum, füllt diesen aus + und verwendet den XOR-Modus zum Invertieren eines Bildschirmbereichs. Das Programm ist portabel gehalten, d.h., es ist ohne Änderung auf allen Zielsystemen lauffähig, die Grafik unterstützten.
    - -
    +
    DECLARE SUB boxed_text(x,y,t$)
    @@ -83,34 +109,56 @@

    Programmbeispiel

    CURSOR 0
    SCREEN GRAPHICSCREEN
    CLS
    - x=W_PIXEL/5
    - y=H_PIXEL/5
    - boxed_text(x,y,"Hallo")
    - PAUSE 50
    - CLS
    +
    + tx=W_PIXEL/5
    + ty=H_PIXEL/3
    + boxed_text(tx,ty,"Hallo")
    +
    + cx=tx+((XPOS-tx)/2)
    + cy=ty+6
    + cr=((XPOS-tx)/2)+3
    + CIRCLE cx,cy,cr
    +
    + PAINT cx,cy-cr+2
    +
    + PEN PEN_XOR
    + LINE (cx-cr-2,cy-cr-2)-(cx+cr+2,cy+cr+2),BF
    +
    + IF GRAPHICSCREEN<>0 THEN PAUSE 50
    SCREEN 0
    END

    SUB boxed_text(x,y,t$)
    -   MOVE x+3,y+2
    -   LABEL t$
    -   LINE (x,y)-(XPOS,y+12),B
    +   MOVE x+3,y+2
    +   LABEL t$
    +   LINE (x,y)-(XPOS,y+12),B
    END SUB
    +
    :'Prozedur deklarieren

    :'Cursor ausschalten
    - :'Grafikmodus einschalten
    + :'Grafikmodus (Grafikbildschirm) einschalten
    :'Grafikbildschirm loeschen
    - :'X-Koordinate
    - :'Y-Koordinate
    +
    :'eingerahmten Text ausgeben
    - :'5 Sekunden warten
    - :'Bildschirm loeschen
    +
    +
    +
    + :'Kreis zeichnen
    +
    +
    +
    +
    + :'Kreis ausfuellen
    +
    + :'Bereich mittels XOR-Stift invertieren
    +
    +
    + :'bei separatem Grafikbildschirm warten und dann Bildschirm loeschen
    :'Textbildschirm einschalten
    :'Programmende

    diff --git a/src/help/tools/basicc/instructions.htm b/src/help/tools/basicc/instructions.htm index 6b20a21..1ce190c 100644 --- a/src/help/tools/basicc/instructions.htm +++ b/src/help/tools/basicc/instructions.htm @@ -14,7 +14,6 @@

    Anweisungen


    Der Compiler kennt folgende BASIC-Anweisungen:
    -  ACCEPT  ASM  BORDER  CALL @@ -22,10 +21,8 @@

    Anweisungen

     CLOSE  CLS  COLOR -  CONNECT  CURSOR  DATA -  DATAGRAM  DECLARE  DEF  DEFUSR @@ -39,12 +36,12 @@

    Anweisungen

     END  ENDIF  EXIT -  FLUSH  FOR  FUNCTION  GOSUB  GOTO  IF +  INCLUDE  INK  INPUT  LABEL @@ -62,6 +59,7 @@

    Anweisungen

     ON...GOTO  OPEN  OUT +  PAINT  PAPER  PASSWORD INPUT  PAUSE @@ -77,8 +75,6 @@

    Anweisungen

     RESTORE  RETURN  SCREEN -  SEND -  SET  SUB  WAIT  WEND @@ -93,10 +89,6 @@

    Anweisungen

    1. Liste der Anweisungen

    - - - - @@ -125,10 +117,6 @@

    1. Liste der Anweisungen

    - - - - @@ -140,12 +128,6 @@

    1. Liste der Anweisungen

    die mit READ gelesen werden können - - - - @@ -198,10 +180,6 @@

    1. Liste der Anweisungen

    - - - - @@ -222,6 +200,10 @@

    1. Liste der Anweisungen

    + + + + @@ -297,6 +279,10 @@

    1. Liste der Anweisungen

    + + + + @@ -309,7 +295,7 @@

    1. Liste der Anweisungen

    - + @@ -361,14 +347,6 @@

    1. Liste der Anweisungen

    - - - - - - - - @@ -397,75 +375,6 @@

    und können weggelassen werden.

    -

    ACCEPT

    -
    AnweisungBedeutung
    ACCEPTAuf eine TCP-Verbindung warten
    ASM Assemblerquelltext einfügen COLOR Farben setzen
    CONNECTTCP-Verbindung aufbauen
    CURSOR Cursor aus- oder einschalten
    DATAGRAM - Verbindungslose Netzwerkkommunikation beginnen (UDP-Port öffnen) -
    DECLARE Benutzerdefinierte Funktion oder Prozedur deklarieren EXIT Vorzeitiges Verlassen einer Schleife
    FLUSHPuffer eines Ausgabekanals leeren
    FOR Beginn einer FOR-Schleife IF Bedingte Verzweigung
    INCLUDEEinbinden einer weiteren Quelltextdatei
    INK Vordergrundfarbe setzen OUT Ausgabe eines Wertes an einem Ausgabetor (Port)
    PAINTFläche füllen
    PAPER Hintergrundfarbe setzen
    PAUSEProgramm für eine vorgegebene Zeit anhaltenProgramm anhalten
    PEN SCREEN Einstellen eines Bildschirm- bzw. Grafikmodus
    SENDUDP-Datenpaket senden
    SETKonfigurieren von KCNet
    SUB Implementierung einer benutzerdefinierten Prozedur
    - - - - - - - - - -
    Syntax: - ACCEPT <Port> AS # <Kanal> -
    Port:numerischer Ausdruck
    Kanal:numerischer Ausdruck mit dem Wert 1 oder 2
    -
    - Die Anweisung dient zur Programmierung eines TCP-Servers. - Es wird ein KCNet-Socket - geöffnet und an dem angegebenen Port gelauscht. - Sobald eine Verbindungsanforderung eintrifft, wird diese aktzeptiert. - Die Anweisung kehrt zurück, - wenn die Netzwerkverbindung steht oder ein Fehler aufgetreten ist - (siehe Fehlervariablen ERR - und ERR$). -

    - Wenn die Anweisung ohne Fehler zurückkehrt, - können Daten gesendet und empfangen werden. - Zum Senden schreibt man die Daten in den Ein-/Ausgabekanal und ruft - anschließend FLUSH auf. - Den Empfang von Daten kann man mit der Funktion - AVAILABLE prüfen. -

    - Das Beispiel zeigt einen einfachen Echo-Server: -
    - -
    - INPUT "Port: ";P
    - PRINT "Lausche am Port";P;"..."
    - ACCEPT P AS #1
    - IF ERR THEN
    -   PRINT ERR$
    - ELSE
    -   PRINT "Verbunden mit ";REMOTEADDR$(1)
    -   DO
    -     B=ASC(INPUT$(1,1))
    -     IF ERR THEN EXIT
    -     PRINT CHR$(B);
    -     PRINT #1,CHR$(B);
    -     IF AVAILABLE(1)=0 THEN FLUSH #1
    -   LOOP
    -   IF ERR=E_EOF THEN
    -     PRINT "Verbindung beendet"
    -   ELSE
    -     PRINT ERR$
    -   ENDIF
    -   CLOSE #1
    - ENDIF
    -
    -
    - Achtung! Portnummern bis 1024 sind privilegierte Ports. - Wenn Sie das Programm im Emulator laufen lassen und - es möchte an so einem Port lauschen, - müssen Sie über entsprechende Berechtigungen verfügen. - Benutzerkonten für gewöhnliche Anwender verfügen - i.d.R. nicht über diese Berechtigungen. - In dem Fall zeigt JKCEMU eine entsprechende Fehlermeldung an. - Auf einem realen Zielsystem gibt es dieses Berechtigungsproblem - dagegen nicht. -

    -

    ASM

    @@ -502,6 +411,18 @@

    ASM



    Neben der ASM-Anweisung gibt es auch die ASM-Funktion. +

    + Achtung! Der BASIC-Compiler enthält einen Global-Optimizer, + der den erzeugten Assembler-Code durchgeht und unnötige Befehle + entfernt. + Dabei kann auch der mit ASM-Anweisungen erzeugte Assembler-Code + verändert werden. + Der Global-Optimizer fasst aber nur solche Assembler-Zeilen an, + bei denen die Befehls-Mnemonik durch einen Tabulator vom Zeilenanfang + bzw. der Marke getrennt ist. + Wenn sie also sichergehen wollen, dass der Optimizer Ihren + Assembler-Code nicht verändert, + dann verwenden Sie keine Tabulatoren in den ASM-Anweisungen.

    Achtung! Die ASM-Anweisung greift direkt in die Programmcodeerzeugung ein und ist deshalb nur @@ -641,73 +562,6 @@

    COLOR

    BORDER festgelegt werden.

    -

    CONNECT

    -
    - - - - - - - - - - -
    Syntax: - CONNECT <Host> , <Port> - AS # <Kanal> -
    Host:einfacher String-Ausdruck
    Port:numerischer Ausdruck
    Kanal:numerischer Ausdruck mit dem Wert 1 oder 2
    -
    - Die Anweisung baut eine TCP-Verbindung zu einem Server auf - und dient damit zur Programmierung eines TCP-Clients. - Im ersten Argument wird der Rechnername oder die IP-Adresse - in textueller Form übergeben. - Kann die IP-Adresse nicht ausgewertet werden, - wird der im KCNet - eingetragene DNS-Server angefragt. - Das zweite Argument gibt die Portnummer an, - zu der die Verbindung aufgebaut werden soll. - Nach Abarbeitung der Anweisung erkennt man anhand der Fehlervariable - ERR, ob die Verbindung aufgebaut wurde. - Steht dort der Wert Null drin (Konstante E_OK), - war der Verbindungsaufbau erfolgreich, anderenfalls nicht. -

    - Wenn die Anweisung ohne Fehler zurückkehrt, - können Daten gesendet und empfangen werden. - Zum Senden schreibt man die Daten in den Ein-/Ausgabekanal und ruft - anschließend FLUSH auf. - Den Empfang von Daten kann man mit der Funktion - AVAILABLE prüfen. -

    - Das Beispiel zeigt ein kleines Programm, - dass die Startseite eines Web-Servers herunterlädt - und den HTML-Text auf dem Bildschirm ausgibt: -
    - -
    - DO
    -   INPUT "Web-Server: ";H$
    - LOOP WHILE H$=""
    - CONNECT H$,80 AS #1
    - IF ERR THEN
    -   PRINT ERR$
    - ELSE
    -   PRINT #1,"GET / HTTP/1.1"
    -   PRINT #1,"Host: ";H$
    -   PRINT #1
    -   FLUSH #1
    -   DO
    -     LINE INPUT #1,A$
    -     IF ERR THEN EXIT
    -     PRINT A$
    -   LOOP
    -   PRINT
    -   IF ERR<>E_EOF THEN PRINT ERR$
    -   CLOSE #1
    - ENDIF
    -
    -
    -

    CURSOR

    @@ -755,78 +609,15 @@

    DATA



    - 100 FOR I=1 TO 12
    - 110 READ M$,N
    - 120 PRINT M$;":";N;"Tage"
    - 130 NEXT I
    - 140 END
    - 200 DATA "Jan",31,"Feb",28,"Mar",31,"Apr",30,"Mai",31,"Jun",30
    - 210 DATA "Jul",31,"Aug",31,"Sep",30,"Okt",31,"Nov",31,"Dez",30
    -
    - -

    DATAGRAM

    -
    - - - - - - - - - -
    Syntax: - DATAGRAM <Port> AS # <Kanal> -
    Port:numerischer Ausdruck
    Kanal:numerischer Ausdruck mit dem Wert 1 oder 2
    -
    - Die Anweisung bereitet eine verbindungslose Netzwerkkommunikation vor. - Dazu wird ein KCNet-Socket im UDP-Mode - geöffnet. - Dieser ist sofort empfangsbereit. - Mit der AVAILABLE-Funktion - kann gepüft werden, ob Daten empfangen wurden. - Wenn ja, lässt sich der Absender mit den Funktionen - REMOTEADDR$ und - REMOTEPORT ermitteln. -

    - In der entgegengesetzten Richtung schreibt man zuerst die zu sendenden - Daten in den Ein-/Ausgabekanal und schickt sie dann mit der - SEND-Anweisung ab. -

    - Das Beispiel zeigt einen einfachen Echo-Server: -
    - + FOR I=1 TO 12
    +   READ M$,N
    +   PRINT M$;":";N;"Tage"
    + NEXT I
    + END

    - INPUT "Port: ";P
    - PRINT "Lausche am Port";P;"..."
    - DATAGRAM P AS #1
    - IF ERR THEN
    -   PRINT ERR$
    - ELSE
    -   DO
    -     B=ASC(INPUT$(1,1))
    -     IF ERR THEN EXIT
    -     PRINT CHR$(B);
    -     PRINT #1,CHR$(B);
    -     IF AVAILABLE(1)=0 THEN SEND #1,REMOTEADDR$(1),REMOTEPORT(1)
    -   LOOP
    -   IF ERR THEN
    -     PRINT ERR$
    -   ENDIF
    -   CLOSE #1
    - ENDIF
    -
    + DATA "Jan",31,"Feb",28,"Mar",31,"Apr",30,"Mai",31,"Jun",30
    + DATA "Jul",31,"Aug",31,"Sep",30,"Okt",31,"Nov",31,"Dez",30

    - Achtung! Portnummern bis 1024 sind privilegierte Ports. - Wenn Sie das Programm im Emulator laufen lassen und - es möchte an so einem Port lauschen, - müssen Sie über entsprechende Berechtigungen verfügen. - Benutzerkonten für gewöhnliche Anwender verfügen - i.d.R. nicht über diese Berechtigungen. - In dem Fall zeigt JKCEMU eine entsprechende Fehlermeldung an. - Auf einem realen Zielsystem gibt es dieses Berechtigungsproblem - dagegen nicht. -

    DECLARE

    @@ -989,7 +780,8 @@

    DRAW

    DRAW TO <X> , <Y>
    DRAW ( <X> , <Y> )
    DRAW TO ( <X> , <Y> )
    - DRAW STEP ( <X> , <Y> ) + DRAW STEP ( <X> , <Y> )
    + DRAW <Macro> @@ -1000,23 +792,229 @@

    DRAW

    + + + +
    Y: numerischer Ausdruck
    Macro:einfacher String-Ausdruck

    - Die DRAW-Anweisung zieht eine Linie von der aktuellen Position - des Grafik-Cursors zu der angegebenen Position (Endpunkt der Linie). + Die DRAW-Anweisung gibt es in drei Varianten. + Die erste Variante mit einer Positionsangabe zieht eine Linie + von der aktuellen Position des Grafik-Cursors zu der angegebenen + absoluten Position (Endpunkt der Linie). Der Grafik-Cursor zieht dabei mit, d.h., er steht nach dem Zeichnen auf dem Endpunkt der Linie.

    - Bei der Variante mit dem Schlüsselwort STEP - wird der Endpunkt nicht absolut sondern relativ angegeben, + Die zweite Variante mit dem Schlüsselwort STEP + unterscheidet sich von der ersten Variante nur in dem Punkt, + dass der Endpunkt der Linie nicht absolut sondern relativ angegeben wird, d.h., diese Variante ist identisch zur - DRAWR-Anweisung. + DRAWR-Anweisung. +

    + Die dritte Variante mit einem String-Ausdruck als Argument + ist der sogenannte Macro-Modus. + Dieser dient zum Zeichnen von komplexen Figuren. + Die übergebene Zeichenkette enthält ein Macro, + d.h. eine Folge von Kommandos, die nacheinander abgearbeitet werden. + Jedes einzelne Kommando beginnt mit einem Buchstaben + (Groß-/Kleinschreibung ist dabei egal), + dem ein Argument folgen kann. + Es gibt acht Kommandos zum Zeichnen einer Linie in eine bestimmte Richtung: +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    HUE
    \|/
    L-Grafik-Cursor-R
    /|\
    GDF
    +

    + Direkt hinter dem Buchstaben kann mit einer Zahl + die Länge der zu zeichnenden Linie angegeben werden. + Fehlt die Zahl, wird nur ein Pixel gezeichnet. +

    + Weitere Kommandos sind M und B. + Bei M wird eine Position angegeben, zu der die Linie gezeichnet wird. + Die Positionsangabe folgt direkt hinter dem Buchstaben und + besteht aus der X-Koordinate, einem Komma und der Y-Koordinate. + Wenn die X-Koordinate mit einem Vorzeichen beginnt (+ oder -), + wird die Position als relative Position angesehen, + anderenfalls als absolute. +

    + Das letzte vorhandene Kommando B besagt, + dass beim nächsten Kommando nicht gezeichnet, + sondern nur der Grafik-Cursor entsprechend verschoben wird.

    - Die DRAW-Anweisung setzt nicht das Pixel im Anfangspunkt, - sondern beginnt erst mit dem ersten Pixel hinter dem Anfangspunkt. + Die folgende Tabelle fasst die Marco-Kommandos der DRAW-Anweisung zusammen: +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Macro-KommadoBedeutung
    U + Linie einen Pixel nach oben zeichnen
    +
    U<Anzahl> + Linie Anzahl Pixel nach oben zeichnen
    +
    D + Linie einen Pixel nach unten zeichnen
    +
    D<Anzahl> + Linie Anzahl Pixel nach unten zeichnen
    +
    L + Linie einen Pixel nach links zeichnen
    +
    L<Anzahl> + Linie Anzahl Pixel nach links zeichnen
    +
    R + Linie einen Pixel nach rechts zeichnen
    +
    R<Anzahl> + Linie Anzahl Pixel nach rechts zeichnen
    +
    E + Linie einen Pixel nach rechts oben zeichnen
    +
    E<Anzahl> + Linie Anzahl Pixel nach rechts oben zeichnen
    +
    F + Linie einen Pixel nach rechts unten zeichnen
    +
    F<Anzahl> + Linie Anzahl Pixel nach rechts unten zeichnen
    +
    G + Linie einen Pixel nach links unten zeichnen
    +
    G<Anzahl> + Linie Anzahl Pixel nach links unten zeichnen
    +
    H + Linie einen Pixel nach links oben zeichnen
    +
    H<Anzahl> + Linie Anzahl Pixel nach links oben zeichnen
    +
    M<X>,<Y> + Linie zur angegebenen absoluten Position zeichnen +
    + M<Vorzeichen><X>,[<Vorzeichen>]<Y> + + Linie zur angegebenen relativen Position zeichnen +
    B + beim nächsten Macro-Befehl nicht zeichnen, + sondern nur den Grafik-Cursor verschieben +
    +
    + Beispiel: Zeichnen von nebeneinander stehenden Häusern: +

    + + H$="U25E15F15D25L30BR4U18R8D17BM+6,7U10R8D10L8"
    + FOR X=0 TO W_PIXEL STEP 40
    +   MOVE X,0
    +   DRAW H$
    + NEXT X +
    +

    + Achtung! + Für alle Varianten der DRAW-Anweisung gilt, + dass das Pixel im Anfangspunkt nicht gesetzt wird. + Das Zeichnen beginnt erst mit dem ersten Pixel hinter dem Anfangspunkt. Dadurch wird sichergestellt, dass beim Zeichnen von Polygonen - mit Hilfe mehrerer hintereinander folgender DRAW-Anweisungen - kein Pixel doppelt gesetzt wird. + mit Hilfe von DRAW-Macros oder mehreren hintereinander folgenden + DRAW-Anweisungen kein Pixel doppelt gesetzt wird. Das ist besonders wichtig bei der Verwendung des XOR-Stiftes (siehe PEN-Anweisung). Wegen diesem Verhalten gibt es auch die unterschiedlichen @@ -1072,6 +1070,7 @@

    END

    +
    Steht die END-Anweisung innerhalb einer benutzerdefinierten Funktion oder Prozedur, wird diese beendet. In dem Fall kann hinter END zusätzlich das Schlüsselwort @@ -1106,45 +1105,14 @@

    EXIT


    I=0
    DO
    -   I=I+1
    -   PRINT I
    -   IF I=5 THEN EXIT DO
    +   I=I+1
    +   PRINT I
    +   IF I=5 THEN EXIT DO
    LOOP
    PRINT "Schleife zu Ende"

    -

    FLUSH

    - - - - - - - - - -
    Syntax:FLUSH # <Kanal>
    Kanal:numerischer Ausdruck mit dem Wert 1 oder 2
    -
    - Ein Ausgabekanal kann die zu schreibenden Daten puffern. - Das ist z.B. aus Gründen der Performance sinnvoll. - Die FLUSH-Anweisung sorgt dafür, dass die gepufferten Daten - physisch geschrieben und somit der Ausgabepuffer geleert wird. - Der Ausgabepuffer wird automatisch auch beim Schließen - des Kanals geleert - (siehe CLOSE-Anweisung), - d.h., der explizite Aufruf von FLUSH vor einem CLOSE ist nicht notwendig. -

    - Ob bei einem Ausgabekanal eine Pufferung stattfindet oder nicht, - hängt vom konkreten Ausgabegerät bzw. dessen Treiber ab. - Wenn die Daten nicht gepuffert werden, - hat die FLUSH-Anweisung nichts zu tun und somit auch keine Wirkung. -

    - Die FLUSH-Anweisung setzt die Fehlervariablen - ERR und - ERR$. -

    -

    FOR

    @@ -1173,10 +1141,10 @@

    FOR

    Anderenfalls wird die FOR-Schleife verlassen, d.h., die Programmabarbeitung setzt hinter der NEXT-Anweisung fort.

    - Ist die Schrittweite größer Null, + Ist die Schrittweite größer 0, gilt der Endwert als erreicht, wenn der Wert der Variable gleich oder größer dem Endwert ist. - Ist dagegen die Schrittweite kleiner Null, + Ist dagegen die Schrittweite kleiner 0, gilt der Endwert als erreicht, wenn der Wert der Variable gleich oder kleiner dem Endwert ist.

    @@ -1325,8 +1293,8 @@

    IF

    bei anderen BASIC-Dialekten üblichen Syntax nicht zu empfehlen.

    Eine Bedingung ist erfüllte, - wenn der numerische Ausdruck einen Wert ungleich Null liefert - (siehe Operatoren, + wenn der numerische Ausdruck einen Wert ungleich 0 liefert + (siehe Operatoren, insbesondere auch die Vergleichsoperatoren).

    Die IF-Anweisung gibt es in einer einzeiligen und in einer @@ -1354,13 +1322,13 @@

    IF


    IF A=1 THEN
    -   PRINT "eins"
    +   PRINT "eins"
    ELSEIF A=2 THEN
    -   PRINT "zwei"
    +   PRINT "zwei"
    ELSEIF A=3 THEN
    -   PRINT "drei"
    +   PRINT "drei"
    ELSE
    -   PRINT "nichts von alldem"
    +   PRINT "nichts von alledem"
    ENDIF

    @@ -1369,17 +1337,71 @@

    IF


    IF A=0 THEN
    -   IF B=0 THEN
    -     PRINT "A und B sind Null."
    -   ELSEIF B=1 THEN
    -     PRINT "A=0 und B=1"
    -   ENDIF
    +   IF B=0 THEN
    +     PRINT "A und B sind Null."
    +   ELSEIF B=1 THEN
    +     PRINT "A=0 und B=1"
    +   ENDIF
    ELSE
    -   PRINT "A ist nicht Null."
    +   PRINT "A ist nicht Null."
    ENDIF

    +

    INCLUDE

    +
    + + + + +
    Syntax:INCLUDE "Dateiname"
    +
    + Die Anweisung bindet die angegebene Datei an der aktuellen Position + in den Quelltext ein, d.h., diese Datei wird mit compiliert + und ist somit Teil des BASIC-Programms. + Der Dateiname kann mit oder ohne Pfad angegeben werden. + Fehlt der Pfad, wird die eingebundene Datei im gleichen Verzeichnis bzw., + falls der Quelltext im Texteditor eingetippt und noch nicht + gespeichert wurde, im aktuellen Verzeichnis gesucht. + Wird ein Pfad angegeben, + ist dieser sowohl relativ als auch absolut möglich. + Die Schreibweise des Pfads (Laufwerk, Trennzeichen) ist abhängig + vom Betriebssystem, auf dem JKCEMU läuft. + Es ist aber auch möglich, + den Pfad betriebssystemunabhäbgig anzugeben, allerdings nur relativ. + Als Trennzeichen ist in dem Fall der Slash (/) zu verwenden. +

    + Beispiele: +
    + + + + + + + + + + + + + + + + + +
    Absolute Pfadangabe bei Windows:INCLUDE "C:\basic\bibliothek.bas"
    Absolute Pfadangabe bei Linux/Unix:INCLUDE "/home/benutzer/basic/bibliothek.bas"
    Relative Pfadangabe bei Windows:INCLUDE "..\bibliothek.bas"
    + Relative und betriebssystemunabhängige Pfadangabe + (Linux/Unix/Windows): + INCLUDE "../bibliothek.bas"
    +
    + Achtung! Hinter der INCLUDE-Anweisung darf + in der gleichen Zeile keine weitere BASIC-Anweisung mehr folgen. + Außerdem können eingebundene Dateien selbst + keine weiteren Dateien mehr einbinden, + d.h., in sich geschachtelte INCLUDE-Anweisungen sind nicht erlaubt. +

    +

    INK

    @@ -1561,7 +1583,7 @@

    LINE

    Grafikunterstützung.

    -

    LINE INPUT

    +

    LINE INPUT

    @@ -1597,10 +1619,33 @@

    LINE INPUT

    (Zeilenendebyte 0Ah) erkannt. Die Anweisung liest die Textzeile bis einschließlich dem Zeilenende, speichert aber den Text ohne die Zeilenendebytes in der Variable. - Wenn die letzte Zeile keine Zeilenendebytes hat, - wird der Text bis zum Kanal- bzw. Dateiende gelesen - und die Fehlervariablen auf "Dateiende erreicht" gesetzt. + Da die Anweisung das Zeilenende sucht, + wird gewöhnlich beim Lesen der letzten Zeile der Fehlerstatus + auf "Dateiende erreicht" gesetzt, d.h., + der in der Variable gespeicherte Text ist sowohl bei + ERR=E_OK als auch bei ERR=E_EOF gütig. + Bei einem anderen Fehlerstatus wird eine leere Zeichenkette + in die Variable geschrieben.

    + Beispiel: Lesen einer Textdatei und zeilenweise Ausgabe + auf dem Bildschirm: +
    + +
    + INPUT "Datei:";F$
    + OPEN F$ AS #1
    + IF ERR THEN
    +   PRINT ERR$
    + ELSE
    +   DO
    +     LINE INPUT #1,S$
    +     PRINT S$
    +   LOOP UNTIL ERR OR EOF(#1)
    +   IF ERR THEN PRINT ERR$
    +   CLOSE #1
    + ENDIF
    +
    +

    LOCAL

    Syntax:
    @@ -1685,7 +1730,7 @@

    LOOP


    DO
    -   PRINT "Endlosschleife"
    +   PRINT "Endlosschleife"
    LOOP

    @@ -1696,8 +1741,8 @@

    LOOP


    I=1
    DO
    -   PRINT I
    -   I=I+1
    +   PRINT I
    +   I=I+1
    LOOP UNTIL I=10

    @@ -1711,8 +1756,8 @@

    LOOP


    I=1
    DO
    -   PRINT I
    -   I=I+1
    +   PRINT I
    +   I=I+1
    LOOP WHILE I<10

    @@ -1875,11 +1920,11 @@

    ON...GOSUB

    ON A GOSUB up1,up2,up3
    ...
    up1:
    -   PRINT "UP 1":RETURN
    +   PRINT "UP 1":RETURN
    up2:
    -   PRINT "UP 2":RETURN
    +   PRINT "UP 2":RETURN
    up3:
    -   PRINT "UP 3":RETURN
    +   PRINT "UP 3":RETURN

    @@ -1931,11 +1976,11 @@

    ON...GOTO

    ON A GOTO up1,up2,up3
    ...
    up1:
    -   PRINT "A war 1":END
    +   PRINT "A war 1":END
    up2:
    -   PRINT "A war 2":END
    +   PRINT "A war 2":END
    up3:
    -   PRINT "A war 3":END
    +   PRINT "A war 3":END

    @@ -1948,6 +1993,9 @@

    OPEN


    OPEN <Geräte-/Dateiname> FOR <Betriebsart> AS # Kanalnummer +
    + OPEN <Geräte-/Dateiname> + FOR BINARY <Betriebsart> AS # Kanalnummer @@ -1956,7 +2004,9 @@

    OPEN

    - + + INPUT, OUTPUT oder APPEND + @@ -2007,25 +2057,44 @@

    OPEN

    - - + + - - + + - - + + + + + + + +
    Betriebsart:INPUT, OUTPUT oder APPEND
    Kanal: Standard-Betriebsart
    CRT:Ausgabe auf dem BildschirmCRT:Bildschirm OUTPUT OUTPUT
    LPT:Ausgabe auf dem DruckerLPT:Drucker OUTPUT OUTPUT
    V:USB-Speicher an einem VDIP-Modul + A: bis P: + Laufwerke im DateisystemINPUT, OUTPUT, APPENDINPUT
    V:USB-Speicher an VDIP-Modul INPUT, OUTPUT, APPEND INPUT

    + Bei Angabe des Schlüsselworts BINARY vor der Betriebsart + wird der Ein-/Ausgabekanal im Binärmodus geöffnet, + anderenfalls im Textmodus. + Einen Unterschied zwischen diesen beiden Modi gibt es nur + beim Zugriff auf ein Dateisystem, + welches keine Byte-exakten Dateilängen bietet + (siehe Dateisystem). + Aus Gründen der Portabilität sollten Sie aber immer + das Schlüsselwort BINARY angeben, + wenn es sich um Binärdateien handelt. +

    Bei einem Eingabekanal können die Anweisungen INPUT und LINE INPUT @@ -2033,13 +2102,12 @@

    OPEN

    EOF und INPUT$, verwendet werden. - Für einen Ausgabekanal stehen die Anweisungen - FLUSH und + Für einen Ausgabekanal steht die Anweisung PRINT zur Verfügung. Wenn die Arbeit mit dem Kanal beendet ist, muss dieser mit CLOSE wieder geschlossen werden. - Danach kann dieser Kanal mit einem anderen Gerät oder + Danach kann der Kanal mit einem anderen Gerät oder einer anderen Datei erneut geöffnet werden. Insgesamt stehen zwei Kanäle zur Verfügung (1 und 2).

    @@ -2047,6 +2115,33 @@

    OPEN

    Fehlervariablen ERR und ERR$.

    + Beispiel: Speichern von Zahlen in der Datei ZAHLEN.TXT + im obersten Verzeichnis auf dem USB-Speicher: +
    + +
    + OPEN "V:\ZAHLEN.TXT" FOR OUTPUT AS #1
    + IF ERR THEN
    +   PRINT ERR$
    + ELSE
    +   FOR I=1 TO 10
    +     PRINT #1,I
    +     IF ERR THEN PRINT ERR$:EXIT
    +   NEXT I
    +   CLOSE #1
    +   IF ERR THEN PRINT ERR$
    + ENDIF
    +
    +
    + Achtung! Die Datei wurde nur dann sicher gespeichert, + wenn sowohl beim Schreiben in die Datei (Anweisung PRINT #) + als auch beim Schließen der Datei (Anweisung CLOSE) + kein Fehler auftrat. +

    + Beispiele zum Lesen einer Datei finden Sie bei der + LINE INPUT-Anweisung + und der INPUT$-Funktion. +

    OUT

    @@ -2067,6 +2162,70 @@

    OUT

    der zweite Ausdruck bestimmt den auszugebenden Wert, wobei nur die unteren 8 Bits relevant sind.

    + Achtung! Obwohl der Mikroprozessor offiziell nur + 8 Bit große Ein-/Ausgabeadressen unterstützt, + ist die Nutzung von 16-Bit-Adressen möglich. + Der Compiler übersetzt die Anweisung in solch einen Programmcode, + der aufgrund des undokumentierten Verhaltens des Mikroprozessors + auch eine 16 Bit große Ein-/Ausgabeadresse + wirksam werden lässt. +

    + +

    PAINT

    +
    + + + + + + + + + + + + +
    Syntax: + PAINT <X> , <Y>
    + PAINT ( <X> , <Y> ) +
    X:numerischer Ausdruck
    Y:numerischer Ausdruck
    +
    + Die PAINT-Anweisung füllt die Fläche aus, + in der sich der angegebenen Punkt befindet. + Die Fläche ergibt sich aus allen zusammenhängenden Pixel, + die auf die Hintergrundfarbe gesetzt sind. + Durch die Anweisung werden diese Pixel auf die aktuell + eingestellte Vordergrundfarbe gesetzt. + Wenn das Pixel an der Startposition bereits die Vordergrundfarbe hat + oder die Startposition liegt außerhalb des Bildschirmbereiches, + passiert nichts. + Der Grafik-Cursor wird durch die Anweisung nicht beeinflusst. +

    + Beispiel: Zeichnen von zwei unterschiedlich großen Kreisen, + deren Zwischenraum gefüllt wird, + so dass ein Kreis mit dicker Außenlinie entsteht: +

    + + CLS
    + X=W_PIXEL/2
    + Y=H_PIXEL/2
    + R1=H_PIXEL/4
    + R2=H_PIXEL/3
    + CIRCLE (X,Y),R1
    + CIRCLE (X,Y),R2
    + PAINT X-R2+(R2-R1)/2,Y +
    +

    + Die PAINT-Anweisung benötigt für ihre Ausführung einen + temporären Speicher. + Dafür wird ein freier Bereich des Zeichenkettenspeichers verwendet. + Je häufiger die zu füllende Fläche in Teilflächen + zerlegt werden muss, z.B. beim Füllen um Ecken herum, + desto mehr Speicher wird benöigt. + Sollte der verwendete Speicherbereich aus dem Zeichenkettenspeicher + nicht ausreichen, bricht das Programm mit einer entsprechenden + Fehlermeldung ab. +

    PAPER

    @@ -2109,19 +2268,24 @@

    PAUSE

    - +
    Syntax:PAUSE <numerischer Ausdruck> + PAUSE
    + PAUSE <numerischer Ausdruck> +

    - Die Anweisung hält das Programm an, - bis die angegebene Zeit abgelaufern ist oder - die Leertaste gedrückt wurde. - Der numerische Ausdruck gibt die Wartezeit in Zehntel Sekunden an, - d.h., der Wert 30 entspricht einer Wartezeit von 3 Sekunden. + Die Anweisung hält das Programm an. + Mit Drücken der Leertaste wird das Programm fortgesetzt. + Optional kann eine Wartezeit in Zehntel Sekunden angegeben werden. + In dem Fall wird nach dieser Zeit das Programm automatisch fortgesetzt, + auch wenn keine Leertaste gedrückt wurde.

    - Achtung! Die Wartezeit entspricht nur in ungefähr + Achtung! Die Wartezeit entspricht nur ungefähr dem angegebenen Wert und hängt maßgeblich von der Taktfrequenz ab. + Der Wert 30 entspricht somit nur in etwa einer Wartezeit + von 3 Sekunden.

    PEN

    @@ -2135,12 +2299,14 @@

    PEN

    Die Anweisung legt den Stift fest, mit dem gezeichnet wird. Ein Stift steht für eine bestimmte Art und Weise, wie die Pixel gesetzt werden. - Es gibt vier verschiedene Stifte: + Es gibt vier verschiedene Stifte. + Für jeden Stift ist eine Konstante definiert.

    - + + + + + + + + + + diff --git a/src/help/tools/debugger.htm b/src/help/tools/debugger.htm index 970f9c9..d2c5e80 100644 --- a/src/help/tools/debugger.htm +++ b/src/help/tools/debugger.htm @@ -11,9 +11,70 @@

    Debugger

    Zum Ändern des Speicherinhalts gibt es den Speichereditor.

    + +
    -

    1. Debugger starten

    +

    1. Debugger starten

    Sie befinden sich im Hauptfenster und klicken im Menü Extra, Untermenü Werkzeuge, den Punkt Debugger... an. @@ -21,7 +82,7 @@

    1. Debugger starten



    -

    2. Programmausführung anhalten

    +

    2. Programmausführung anhalten

    Damit Sie die Speicher- und Registerinhalte ansehen können, muss die Programmausführung angehalten werden. Dafür gibt es zwei Möglichkeiten: @@ -29,7 +90,7 @@

    2. Programmausführung anhalten

  • Sie legen einen oder mehrere Haltepunkte an. Wenn während der Befehlsabarbeitung ein solcher Haltepunkt - zutrifft, der zudem auch aktiviert sein muss, wird angehalten. + zutrifft, wird angehalten.
  • Sie klicken im Menü Debuggen auf den Punkt @@ -40,27 +101,52 @@

    2. Programmausführung anhalten

    wird der Inhalt des Debugger-Fensters aktualisiert, d.h., Sie sehen im linken Bereich des Fensters die Registerinhalte, in der Mitte die ersten Bytes der Speicherbereiche, - auf denen die Doppelregister zeigen und + auf die die Doppelregister zeigen und unten die nächsten auszuführenden Befehle.

    -

    3. Haltepunkte

    - Es gibt fünf verschiedene Arten von Haltepunkten, - die bei unterschiedlichen Bedingungen zum Anhalten - der Programmausführung führen. +

    3. Programm bis Haltepunkt ausführen

    + Mit dieser Funktion wird die Programmausführung in normaler + bzw. voller Geschwindigkeit fortgesetzt. + Wenn dabei das Programm auf einen Haltepunkt trifft, + dessen Bedingung erfüllt ist, wird angehalten.

    -

    3.1. Haltepunkt auf Programmadresse

    - Das ist die klassische Form eines Haltepunktes. - Es wird angehalten, wenn die Programmausführung - an der angegebenen Adresse (Haltepunktadresse) angelangt ist. + +

    4. Programm bis Haltepunkt langsam ausführen

    + Bei dieser Funktion führt der Debugger das Programm langsam aus. + Sie können so die Abarbeitung des Programms im Debugger verfolgen. + Die Geschwindigkeit der Programmabarbeitung ist in drei Stufen wählbar. + Wenn das Programm auf einen Haltepunkt trifft, + dessen Bedingung erfüllt ist, wird angehalten. +

    + + +

    5. Halte-/Log-Punkte

    + Halte-/Log-Punkt verkörpern eine Bedingung, + bei der die Programmausführung angehalten (Haltepunkt) + und/oder eine Log-Meldung (Log-Punkt) ausgegeben wird. + Es gibt fünf verschiedene Arten von Halte-/Log-Punkten. +

    + Das Anlegen von Halte-/Log-Punkten erfolgt über das Menü + am Fenster oder über das Kontextmenü (rechte Maustaste) + über dem jeweiligen Halte-/Log-Punktbereich. +

    + +

    + 5.1. Halte-/Log-Punkt auf Programmadresse +

    + Das ist die klassische Form eines Halte-/Log-Punktes. + Es wird angehalten bzw. eine Log-Meldung ausgegeben, + wenn die Programmausführung an der angegebenen Adresse + angelangt ist. Zusätzlich können Sie den Wert eines Registers prüfen lassen. - Dann wird nur angehalten, wenn neben der Haltepunktadresse - auch die Registerwertprüfung erfolgreich war. + Dann wird nur angehalten bzw. eine Log-Meldung ausgegeben, + wenn neben der Adresse auch die Registerwertprüfung erfolgreich war. Diese läuft folgendermaßen ab: - Vor Abarbeitung des Befehls auf der Haltepunktadresse wird der Wert + Vor Abarbeitung des Befehls auf der Halte-/Log-Punktadresse wird der Wert des ausgewählten Registers mit einer von Ihnen anzugebenden Maske UND-verknüpft und das Ergebnis dann mit einem ebenfalls von Ihnen anzugebenden Vergleichswert verglichen. @@ -68,12 +154,20 @@

    3.1. Haltepunkt auf Programmadresse

    sondern auch auf Ungleichheit sowie auf größer als und kleiner als.

    + Halte-/Log-Punkte auf Programmadressen können Sie neben den + oben genanten Möglichkeiten auch über das Kontextmenü + der Programmcodeanzeige sowie im Reassembler + über das Kontextmenü anlegen. + Bei diesen beiden Varianten wird gleich die Adresse entsprechend + der Position des Kontextmenüs vorbelegt. +

    -

    3.2. Haltepunkt auf Speicherbereich

    - Ein solcher Haltepunkt reagiert auf Zugriffe auf eine Speicherzelle - bzw. auf einen Speicherbereich. +

    5.2. Halte-/Log-Punkt auf Speicherbereich

    + Ein solcher Halte-/Log-Punkt reagiert auf Zugriffe auf eine + Speicherzelle bzw. auf einen Speicherbereich. Sie können angeben, ob nur bei Lese-, nur bei Schreib- - oder bei Lese- und Schreibzugriffe angehalten werden soll. + oder bei Lese- und Schreibzugriffe angehalten bzw. eine Log-Meldung + ausgegeben werden soll. Zusätzlich können Sie den Wert der betreffenden Speicherzelle (Lesezugriff) bzw. den zu schreibenden Wert (Schreibzugriff) überprüfen lassen. @@ -85,14 +179,14 @@

    3.2. Haltepunkt auf Speicherbereich

    Aufgrund dieser Arbeitsweise werden jedoch die Speicherzugriffe, die während einer Interrupt-Annahme getätigt werden (Lesen der Interrupt-Tabelle im Interrupt-Mode 2 und - Kellern der Rückkehradresse) von keinem Haltepunkt erkannt. + Kellern der Rückkehradresse) von keinem Halte-/Log-Punkt erkannt.

    -

    3.3. Haltepunkt auf Eingabetor

    - Bei diesem Haltepunkt geben Sie die Adresse oder den Adressbereich - eines Eingabetors an. - Es wird dann an einem Eingabebefehl angehalten, - der von diesem Eingabetor liest. +

    5.3. Halte-/Log-Punkt auf Eingabetor

    + Bei diesem Halte-/Log-Punkt geben Sie die Adresse oder + den Adressbereich eines Eingabetors an. + Es wird dann vor einem Eingabebefehl angehalten bzw. + eine Log-Meldung ausgegeben, der von diesem Eingabetor liest.

    Die Eingabeadresse kann sowohl mit 8 Bit (2 hexadezimale Ziffern) als auch mit 16 Bit (4 hexadezimale Ziffern) angegeben werden. @@ -100,65 +194,70 @@

    3.3. Haltepunkt auf Eingabetor

    geprüft.

    -

    3.4. Haltepunkt auf Ausgabetor

    - Dieser Haltepunkt ist ähnlich wie einer auf einem Eingabetor, +

    5.4. Halte-/Log-Punkt auf Ausgabetor

    + Dieser Halte-Log-Punkt ist ähnlich wie einer auf einem Eingabetor, nur dass er auf Ausgabebefehle reagiert. Zusätzlich können Sie den auszugebenden Wert überprüfen lassen.

    -

    3.5. Haltepunkt auf Interrupt-Quelle

    - Bei dieser Form eines Haltepunktes geben Sie eine im emulierten +

    5.5. Halte-/Log-Punkt auf Interrupt-Quelle

    + Bei dieser Form eines Halte-/Log-Punktes geben Sie eine im emulierten System vorhandene Interrupt-Quelle (z.B. PIO oder CTC) an. Es wird dann angehalten, sobald die Interrupt-Quelle einen Interrupt auslöst und dieser vom Mikroprozessor auch angenommen wird, d.h., - es wird vor dem ersten Befehl der Interrupt-Service-Routine angehalten. + es wird vor dem ersten Befehl der Interrupt-Service-Routine + angehalten bzw. eine Log-Meldung ausgegeben.

    -

    3.6. Verwaltung der Haltepunkte

    +

    5.6. Verwaltung der Halte-/Log-Punkte

    Über das Menü Debuggen können Sie beliebig - viele Haltepunkte anlegen. + viele Halte-/Log-Punkte anlegen. Mit dem Kontextmenü über der jeweiligen Haltepunktliste ist das auch möglich.

    - Um einen Haltepunkt wieder zu entfernen, + Um einen Halte-/Log-Punkt wieder zu entfernen, müssen Sie diesen im rechten Bereich des Debugger-Fensters durch Anklicken markieren. - Sie können auch mehrere Haltepunkte markieren. + Sie können auch mehrere Halte-/Log-Punkte markieren. Anschließend klicken Sie im Menü Debuggen - auf den Eintrag Haltepunkte entfernen. + auf den Eintrag Ausgewählte Halte-/Log-Punkte entfernen.

    Optional können Sie auch alle Haltepunkte auf einmal entfernen - (Menüeintrag Alle Haltepunkte entfernen). + (Menüeintrag Alle Halte-/Log-Punkte entfernen).

    - Haltepunkte, die Sie temporär nicht benötigen, + Halte-/Log-Punkte, die Sie temporär nicht benötigen, müssen nicht unbedingt entfernt werden. - Sie können diese Haltepunkte auch einfach deaktivieren - und später bei Bedarf wieder aktivieren. + Sie können diese einfach deaktivieren, + indem Sie die beiden Aktionen (Anhalten und Loggen) deaktivieren. + Bei Bedarf können Sie die gewünschte Akion später + wieder aktivieren.

    Der Debugger ist nur solange aktiv, wie das Debugger-Fenster sichtbar ist. - Wenn Sie Haltepunkte angelegt haben und Sie schließen - das Fenster, werden die Haltepunkte zwar nicht gelöscht, - jedoch wird dann dort nicht mehr angehalten. + Wenn Sie Halte-/Log-Punkte angelegt haben und Sie schließen + das Fenster, werden die Halte-/Log-Punkte zwar nicht gelöscht, + jedoch sind sie nicht mehr aktiv.

    -

    3.7. Haltepunkte importieren

    +

    5.7. Halte-/Log-Punkte importieren

    Der Debugger bietet die Möglichkeit, - Haltepunkte aus einer Datei oder aus der Zwischenablage zu + Halte-/Log-Punkte aus einer Datei oder aus der Zwischenablage zu importieren. - Diese Haltepunkte können dabei auch einen Namen haben. + Diese Halte-/Log-Punkte können dabei auch einen Namen haben. So ist z.B. der Import der Markentabelle eines Assemblers möglich, um für jede Marke einen Haltepunkt anzulegen.

    Die Markentabelle des JKCEMU-Assemblers können Sie sogar automatisch in den Debugger importieren lassen. - Schalten Sie dazu die entsprechende - Assembler-Option ein. + Schalten Sie dazu die + Assembler-Option + Im Debugger Halte-/Log-Punkte bzw. Variablen auf Marken anlegen + ein.

    -

    4. Schrittbetrieb

    +

    6. Schrittbetrieb

    Mit Schrittbetrieb ist gemeint, dass nach dem Anhalten der Programmausführung einzelne Befehle oder Befehlsgruppen abgearbeitet werden und danach automatisch wieder @@ -182,24 +281,15 @@

    4. Schrittbetrieb

    oder Sie öffnen wieder den Debugger.

    -

    4.1. Einzelschritt über Aufruf hinweg

    +

    6.1. Einzelschritt über Aufruf hinweg

    Es wird ein einzelner Maschinenbefehl abgearbeitet. Handelt es sich jedoch um den Aufruf eines Unterprogramms, so wird das ganze Unterprogramm ausgeführt und erst danach wieder angehalten. - Das Ende eines Unterprogramms wird dabei anhand des Stack Pointers erkannt. - Sobald dieser wieder den gleichen Wert wie vor dem Unterprogrammaufruf - hat, wird angehalten. -

    - Blockbefehle und leere DJNZ-Schleifen werden ebenfalls übersprungen, - d.h. sie werden bis zum Ende abgearbeitet. - Bei der Funktion Einzelschritt über Aufruf hinweg - wird nämlich die Programmausführung niemals angehalten, - wenn der Befehlszeiger (Program Counter) nach Abarbeitung - eines Maschinenbefehls immer noch auf den gleichen Befehl zeigt. + Das gleiche gilt für Blockbefehle und leere DJNZ-Schleifen.

    -

    4.2. Einzelschritt in Aufruf hinein

    +

    6.2. Einzelschritt in Aufruf hinein

    Es wird immer nur ein einzelner Maschinenbefehl abgearbeitet. Handelt es sich um den Aufruf eines Unterprogramms, so wird nur der CALL-Befehl abgearbeitet und somit vor dem @@ -209,14 +299,16 @@

    4.2. Einzelschritt in Aufruf hinein

    und danach wieder angehalten.

    -

    4.3. Bis RETURN ausführen

    +

    6.3. Bis RET ausführen

    Es werden die Maschinenbefehle bis zum nächsten Return-Befehl ausgeführt. Werden dabei Unterprogramme aufgerufen, so werden diese vollständig ausgeführt.

    -

    4.4. Registerinhalte und Flags ändern

    +

    + 6.4. Registerinhalte und Flags ändern +

    Im Schrittbetrieb, d.h. wenn die Programmausführung angehalten wurde, können die Inhalte der Register geändert werden. Geben Sie dazu den gewünschten neuen Wert in das jeweilige Feld @@ -227,7 +319,7 @@

    4.4. Registerinhalte und Flags ändern



    -

    5. Befehle aufzeichnen

    +

    7. Befehle aufzeichnen

    Eine weitere Möglichkeit der Verfolgung des Geschehens im Emulator ist die Befehlsaufzeichnung. Dabei werden vor der Abarbeitung eines jeden Maschinenbefehls @@ -235,7 +327,7 @@

    5. Befehle aufzeichnen

    anstehende Maschinenbefehl in Form einer Zeile in eine Textdatei geschrieben. Diese können Sie sich dann mit einem Editor ansehen, - z.B. mit dem in JKCEMU eingebauten + z.B. mit dem im JKCEMU eingebauten Texteditor.

    Die Befehlsaufzeichnung schalten Sie über den Schalter @@ -253,5 +345,45 @@

    5. Befehle aufzeichnen

    ausreichend Platz ist. Außerdem wird die Programmausführung in Abhängigkeit von der Schreibgeschwindigkeit des Speichermediums deutlich gebremst. +

    + +

    8. Variablen

    + Ein Programm speichert seine Daten üblicherweise in Variablen. + Da es beim Debuggen sehr hilfreich ist, + die aktuellen Variableninhalte zu sehen und ggf. auch zu ändern, + bietet der JKCEMU Debugger dafür eine Unterstützung. + Allerdings weiß der Debugger nicht, welche Speicherbereiche + für Variablen verwendet werden und welche Datentypen diese haben. + Aus diesem Grund müssen die Variablen dem Debugger erst bekannt + gemacht werden, d.h., sie müssen im Debugger angelegt werden. + Für jede Variable geben Sie eine Adresse, einen Datentyp + und optional einen Namen an. + Wenn Sie das erledigt haben, + zeigt der Debugger die aktuellen Variableninhalte an, + sobald die Programmausführung anhält. +

    + Neben dem manuellen Anlegen können Variablen auch von dem + im JKCEMU integrierten Assembler + importiert werden. + Wenn Sie die Assembler-Option Im Debugger Halte-/Log-Punkte + bzw. Variablen auf Marken anlegen eingeschaltet haben, + wird für jede Marke, die unmittelbar vor einer DS- + oder DEFS-Anweisung steht, eine Variable angelegt. +

    + Im technischen Sinne ist eine Variable im JKCEMU Debugger + nur eine Deklaration, die besagt, wie ein ein oder mehrere Bytes + großer Bereich im Arbeitsspeicher zu betrachten ist, + d.h. ob dort ein numerischer, alphanumerischer oder binärer Wert + gespeichert wird. + Sie können beliebig viele Variablen anlegen. +

    + Den Inhalt des Arbeitsspeichers können Sie sich auch mit dem + Speichereditor anzeigen lassen + und auch ändern. + Das Anlegen von Variablen im Debugger hat jedoch den Vorteil, + dass die Anzeige beim Anhalten der Programmausführung + automatisch aktualisiert wird und dass Sie nur die Bytes sehen, + die Sie interessieren, + auch wenn die Bytes im ganzen Arbeitsspeicher verstreut sind. diff --git a/src/help/tools/filebrowser.htm b/src/help/tools/filebrowser.htm index e768b8a..29f52a0 100644 --- a/src/help/tools/filebrowser.htm +++ b/src/help/tools/filebrowser.htm @@ -1,222 +1,295 @@ -

    Datei-Browser

    - Der Datei-Browser dient dazu, - Programme und Dateien besonders bequem zu laden bzw. zu öffnen. - Während beim gewöhnlichen Laden mit einem Dateiauswahldialog - viele Mausklicks notwendig sind, kann der Datei-Browser offen bleiben, - so dass zu jedem Zeitpunkt eine Datei ausgewählt und mit nur wenigen - Mausklicks geladen bzw. geöffnet werden kann. - Außerdem sind je nach Einstellung auch versteckte Dateien sichtbar, - was beim JKCEMU-Dateiauswahldialog nicht der Fall ist. -

    +

    Datei-Browser

    + +
    + Der Datei-Browser dient dazu, + Programme und Dateien besonders bequem zu laden bzw. zu öffnen. + Während beim gewöhnlichen Laden mit einem Dateiauswahldialog + viele Mausklicks notwendig sind, kann der Datei-Browser offen bleiben, + so dass zu jedem Zeitpunkt eine Datei ausgewählt und mit nur wenigen + Mausklicks geladen bzw. geöffnet werden kann. + Außerdem sind je nach Einstellung auch versteckte Dateien sichtbar, + was beim JKCEMU-Dateiauswahldialog nicht der Fall ist. +

    + Neben dem bequemen Laden von Dateien in den Emulator bietet + der Datei-Browser aber auch vielen Funktionen eines + Datei-Managers, wie z.B. Dateien und Verzeichnisse kopieren, + verschieben (ausschneiden und einfügen), löschen usw. +

    -

    1. Ansicht

    - Der Datei-Browser zeigt auf der linken Seite das Dateisystem - in einer Baumdarstellung an. - Die rechte Seite dient zur Anzeige von Details. - Zwischen beiden Seiten befindet sich ein Balken, - den Sie nach links und rechts verschieben können. -

    - Wenn Sie eine Datei anklicken, - werden Detailinformationen auf der rechten Seite angezeigt. - Bei einer Bilddatei sehen Sie das ganze Bild, vorausgesetzt, - das Dateiformat wird unterstützt und die eingestellte - maximale Dateigröße für die Vorschau - wurde nicht überschritten. - Bei einer Archivdatei (TAR und ZIP) sowie bei einem Verzeichnis - wird der jeweilige Inhalt angezeigt. -

    +

    1. Ansicht

    + Der Datei-Browser zeigt auf der linken Seite das Dateisystem + in einer Baumdarstellung an. + Die rechte Seite dient zur Anzeige von Details. + Zwischen beiden Seiten befindet sich ein Balken, + den Sie nach links und rechts verschieben können. +

    + Wenn Sie eine Datei anklicken, + werden Detailinformationen auf der rechten Seite angezeigt. + Bei einer Bilddatei sehen Sie das ganze Bild, vorausgesetzt, + das Dateiformat wird unterstützt und die eingestellte + maximale Dateigröße für die Vorschau + wurde nicht überschritten. + Bei einer Archivdatei (TAR und ZIP) sowie bei einem Verzeichnis + wird der jeweilige Inhalt angezeigt. +

    -

    2. Drag&Drop

    - Der Datei-Browser kann als Quelle einer Drag&Drop-Operation - verwendet werden, d.h., Sie können eine Datei markieren, - mit gedrückter Maustaste in ein anderes Fenster ziehen - und dort loslassen. - Dann liegt es an dem anderen Fenster, was mit der Datei passiert. - Das JKCEMU-Emulator-Fenster wird in einem solchen Fall die Datei laden. -

    +

    2. Drag&Drop

    + Der Datei-Browser kann sowohl als Quelle als auch als Ziel + einer Drag&Drop-Operation verwendet werden, + d.h., Sie können eine Datei markieren, + mit gedrückter Maustaste in ein anderes Fenster ziehen + und dort loslassen. + Dann liegt es an dem anderen Fenster, was mit der Datei passiert. + Das JKCEMU-Emulator-Fenster wird in einem solchen Fall die Datei laden. +

    + In der anderen Richtung, d.h., wenn Sie eine Datei in die linke Ansicht + des Datei-Browsers hineinziehen und dort über einem Verzeichnis + loslassen, wird die Datei in das Verzeichnis kopiert bzw. verschoben. +

    + Mit Drag&Drop kann man aber nicht nur eine einzelne Datei + übertragen, sondern auch mehrere und auch Verzeichnisse. + Es kann aber sein, das das Zielfenster nur etwas mit einer Datei + anfangen kann. + So macht es i.d.R. wenig Sinn, ein Verzeichnis in einen Texteditor + hinziehen und dort öffnen zu wollen. +

    -

    3. Dateien laden und starten

    - Es ist die Hauptaufgabe des Datei-Browsers, - Dateien bequem zu laden und ggf. auch zu starten. - Im Menü Datei gibt es dafür verschiedene Funktionen, - von denen die wichtigsten auch in der Werkzeugleiste im oberen - Bereich des Fensters zu finden sind. -

    - Achtung! Wenn der Datei-Browser mit der JKCEMU-Kommandozeilenoption - --fb oder --filebrowser ohne Emulator gestartet wurde - (siehe Aufruf und Kommandozeile), - kann auch nichts in den Emulator geladen werden. - In dem Fall sind die Menüeinträge und Schaltflächen - zum Laden in den Emulator nicht sichtbar, - und ein Doppelklick führt auch kein Laden durch. -

    +

    3. Dateien laden und starten

    + Es ist die Hauptaufgabe des Datei-Browsers, + Dateien bequem zu laden und ggf. auch zu starten. + Im Menü Datei gibt es dafür verschiedene Funktionen, + von denen die wichtigsten auch in der Werkzeugleiste im oberen + Bereich des Fensters zu finden sind. +

    + Achtung! Wenn der Datei-Browser mit der JKCEMU-Kommandozeilenoption + --fb oder --filebrowser ohne Emulator gestartet wurde + (siehe Aufruf und Kommandozeile), + kann auch nichts in den Emulator geladen werden. + In dem Fall sind die Menüeinträge und Schaltflächen + zum Laden in den Emulator nicht sichtbar, + und ein Doppelklick führt auch kein Laden durch. +

    -

    3.1. In Emulator laden mit...

    - Bei dieser Funktion wird vor dem Laden der selektierten Datei immer - der Dialog mit den Ladeoptionen - angezeigt. - Sie können damit z.B. die Datei auf eine andere als in der Datei - angegebenen Adresse laden. -

    +

    3.1. In Emulator laden mit...

    + Bei dieser Funktion wird vor dem Laden der selektierten Datei immer + der Dialog mit den Ladeoptionen + angezeigt. + Sie können damit z.B. die Datei auf eine andere als in der Datei + angegebenen Adresse laden. +

    -

    3.2. In Emulator laden

    - Bei dieser Funktion wird die selektierte Datei ohne Nachfrage - in den Arbeitsspeicher des Emulators geladen. - Voraussetzung ist allerdings, - dass das Format der Datei auch Ladeadressen kennt. - Ist das nicht der Fall, muss zwangsweise eine Ladeadresse angegeben werden. - In dem Fall erscheint dann doch der Dialog mit den - Ladeoptionen. -

    +

    3.2. In Emulator laden

    + Bei dieser Funktion wird die selektierte Datei ohne Nachfrage + in den Arbeitsspeicher des Emulators geladen. + Voraussetzung ist allerdings, + dass das Format der Datei auch Ladeadressen kennt. + Ist das nicht der Fall, muss zwangsweise eine Ladeadresse angegeben werden. + In dem Fall erscheint dann doch der Dialog mit den + Ladeoptionen. +

    -

    3.3. Im Emulator starten

    - Enthält die selektierte Datei ein ausführbares - Maschinencodeprogramm und eine Startadresse, - wird die Datei ohne Nachfrage geladen und das Programm gestartet. -

    +

    3.3. Im Emulator starten

    + Enthält die selektierte Datei ein ausführbares + Maschinencodeprogramm und eine Startadresse, + wird die Datei ohne Nachfrage geladen und das Programm gestartet. +

    -

    3.4. Doppelklick

    - Ein Doppelklick auf eine Datei führt eine für die Datei - sinnvolle Aktion aus. - Eine Textdatei wird im Texteditor - geöffnet, eine unterstützte Bilddatei im - Bildbetrachter angezeigt, - eine unterstützte Sound-Datei abgespielt, usw. - Gegebenfalls wird auch das Betriebssystem gefragt, - ob es die Datei sinnvoll öffnen kann. - Ist das alles nicht der Fall, wird davon ausgegangen, - dass sie in den Arbeitsspeicher des Emulator geladen werden soll. - Dazu erscheint der Dialog mit den - Ladeoptionen. - Da es aber hinderlich sein kann, - jedesmal diesen Dialog bestätigen zu müssen, - können Sie dieses Verhalten im Menü Einstellungen - abschalten. Mit dem Menüpunkt - Doppelklick führt Laden/Starten ohne Nachfrage aus - legen Sie fest, dass beim Doppelklick auf eine Datei, - deren Format Ladeadressen kennt, keine - Ladeoptionen mehr abgefragt werden. - Enthält die Datei ein ausführbares Maschinencodeprogramm, - d.h. ist eine Startadresse eingetragen, - wird das Programm auch sofort gestartet. -

    +

    3.4. Doppelklick

    + Ein Doppelklick auf eine Datei führt eine für die Datei + sinnvolle Aktion aus. + Eine Textdatei wird im Texteditor + geöffnet, eine Bilddatei im + Bildbetrachter angezeigt + (wenn das Dateiformat unterstützt wird) + und eine Sound-Datei abgespielt + (wenn das Dateiformat unterstützt wird). + Wenn JKCEMU selbst nicht weiß, was es mit der Datei machen soll, + wird das Betriebssystem gefragt, + ob es die Datei sinnvoll öffnen kann. + Ist das alles nicht der Fall, wird davon ausgegangen, + dass die Datei in den Arbeitsspeicher des Emulator geladen werden soll. + In dem Fall erscheint der Dialog mit den + Ladeoptionen. + Da es aber hinderlich sein kann, + jedesmal diesen Dialog bestätigen zu müssen, + können Sie dieses Verhalten im Menü Einstellungen + abschalten. Mit dem Menüpunkt + Doppelklick führt Laden/Starten ohne Nachfrage aus + legen Sie fest, dass beim Doppelklick auf eine Datei, + deren Format Ladeadressen kennt, keine + Ladeoptionen mehr abgefragt werden. + Enthält die Datei ein ausführbares Maschinencodeprogramm, + d.h. ist eine Startadresse eingetragen, + wird das Programm auch sofort gestartet. +

    -

    4. Weitere Funktionen

    - Der Datei-Browser bietet neben dem Laden bzw. Öffnen - von Dateien auch weiterer Funktionen, - wie z.B. Verzeichnisse anlegen, - Verzeichnisse und Dateien umbenennen und löschen, - Prüf- und Hashwerte berechnen, - Packen und Entpacken sowie - den Änderungszeitpunkt auf einen beliebigen Wert setzen. - Alle diese Funktionen finden Sie im Menü Datei. -

    +

    4. Ausschneiden, Kopieren und Einfügen

    + Über das Menü Bearbeiten sowie über das + Kontextmenü können Sie Dateien und Verzeichnisse + ausschneiden, kopieren und wieder einfügen. + Dazu wird (falls vorhanden) die Zwischenablage des Betriebssystems + verwendet, d.h., Sie können auf diese Art und Weise + auch Dateien zwischen dem Datei-Browser und anderen Fenstern + bzw. Programmen hin und her übertragen. +

    + Achtung! Die Funktion Ausschneiden funktioniert + nur innerhalb des Datei-Browsers. + Wenn Sie im Datei-Browser eine Datei ausscheiden und in einem anderen + Programm wieder einfügen, + wird die Datei kopiert, nicht aber verschoben. +

    + Als eine Besonderheit bietet der Datei-Browser auch die Möglichkeit, + nur die vollständigen Namen von Dateien und Verzeichnissen + zu kopieren, und zwar sowohl in reiner Textform als auch in Form einer URL. +

    -

    - 4.1. Audio-Wiedergabe von Speicherabbilddateien -

    - Eine Besonderheit des JKCEMU-Datei-Browsers ist, - dass er Speicherabbilddateien im Kassettenaufzeichnungsformat - einiger der emulierten Computer wiedergeben kann. - Damit eignet sich JKCEMU als Zuspieler für diese 8-Bit-Computer. - Die entsprechenden Funktionen finden Sie im Untermenü - DateiWiedergeben im -

    - Die Tabelle zeigt, welche Dateiformate in welchem - Kassettenaufzeichnungsformat wiedergegeben werden können: -
  • StiftBedeutung
    StiftKonstanteBedeutung
    0PEN_NONE Stift angehoben,
    Bei diesem Stift passiert nichts. @@ -2149,6 +2315,7 @@

    PEN

    1PEN_NORMAL Zeichnen,
    Die zu zeichnenden Pixel werden auf die Vordergrundfarbe gesetzt. @@ -2160,6 +2327,7 @@

    PEN

    2PEN_RUBBER Löschen,
    Die zu zeichnenden Pixel werden auf die Hintergrundfarbe gesetzt. @@ -2167,6 +2335,7 @@

    PEN

    3PEN_XOR XOR-Modus,
    Bei jedem zu zeichnenden Pixel wird geprüft, @@ -2208,7 +2377,7 @@

    PLOT

    Die PLOT-Anweisung positioniert den Grafik-Cursor und setzt das dortige Pixel. Damit eignet sich z.B. die PLOT-Anweisung in Verbindung mit der - DRAW-Anweisungen zum Zeichnen + DRAW-Anweisung zum Zeichnen von nicht geschlossenen Polygonen.

    Bei der Variante mit dem Schlüsselwort STEP @@ -2340,13 +2509,74 @@

    PRINT

    Die PRINT-Anweisung kann mehrere Abschnitte enthalten, die durch Kommas oder Semikolons getrennt sind. Ein Abschnitt dient entweder zur Ausgabe einer Zeichenkette - oder einer Dezimalzahl. - Mit der Funktion CHR$ - können auch beliebige Bytes, auch Null-Bytes, ausgegeben werden. + oder einer numerischen Zahl, + je nachdem, ob der Abschnitt einen Zeichenkettenausdruck + oder einen numerischen Ausdruck enthält. +
    +

    - Ist ein Abschnitt zur Ausgabe einer Dezimalzahl vom vorherigen Abschnitt - mit einem Komma getrennt, wird die Zahl in einem 14 Zeichen breiten - Feld rechtsbündig ausgegeben. + CHR$-Funktion +

    + Mit der Funktion CHR$ + können beliebige Bytes, auch Null-Bytes, ausgegeben werden. + Übergibt man der Funktion den Wert 0, + liefert sie normalerweise eine leere Zeichenkette zurück. + Das gilt jedoch nicht, wenn die CHR$-Funktion ein eigenständiger + Abschnitt in der PRINT-Anweisung ist. + In dem Fall wird immer das Byte ausgegeben, + dessen Wert der CHR$-Funktion übergeben wurde, + auch wenn der Wert 0 ist. + Das Beispiel zeigt das Schreiben einer binären Datei, + welche nur aus einem Null-Byte besteht: +
    + +
    + OPEN "datei.bin" FOR BINARY OUTPUT AS #1
    + IF ERR THEN
    +   PRINT ERR$
    + ELSE
    +   PRINT #1,CHR$(0);
    +   CLOSE #1
    + ENDIF
    +
    + +

    + SPC-Funktion +

    + Die SPC-Funktion dient zur Ausgabe einer bestimmten Anzahl + von Leerzeichen. Die Anzahl der Leerzeichen übergibt man + der Funktion als Argument und ist in Klammern zu schreiben. + Das Beispiel zeigt die Ausgabe von 10 Leerzeichen + zwischen den spitzen Klammern: +
    + +
    + PRINT ">";SPC(10);"<"
    +
    +
    + Das Besondere an der SPC-Funktion ist, dass sie nur innerhalb + der PRINT-Anweisung zur Verfügung steht. + Aus diesem Grund wird sie auch hier und nicht bei den normalen + BASIC-Funktionen beschrieben. +

    + Die SPC-Funktion hat die gleiche Wirkung wie die + Funktion SPACE$. + Allerdings ist die SPC-Funktion insbesondere bei der Ausgabe + von vielen Leerzeichen besser geeignet, + da bei ihr nicht die interne Zeichenkettenverarbeitung zur Anwendung + kommt und somit auch deren Größbeschränkung + nicht gilt. +
    + +

    + Rechts- und linksbündige Ausgabe +

    + Zeichenketten werden immer linksbündig ausgegeben. + Bei numerischen Abschnitten hängt die Bündigekeit + vom voranstehenden Trennzeichen ab. + Ist der Abschnitt vom vorherigen Abschnitt durch ein Komma getrennt, + wird die Zahl in einem 14 Zeichen breiten Feld rechtsbündig + ausgegeben. Anderenfalls erfolgt die Ausgabe linksbündig. Folgende Beispiele demonstrieren das:

    @@ -2397,6 +2627,10 @@

    PRINT

    PRINT "Anteil: ";STR$(P);"%"

    + +
    + Zeilenschaltung +

    Die PRINT-Anweisung setzt nach der Ausgabe aller Abschnitte standardmäßig den Cursor auf den Anfang der nächsten Zeile. Diese Zeilenschaltung kann unterdrückt werden, @@ -2480,13 +2714,13 @@

    RESTORE


    RESTORE Wochentage
    FOR I=1 TO 7
    -   READ A$
    -   PRINT A$
    +   READ A$
    +   PRINT A$
    NEXT I
    END

    Wochentage:
    -   DATA "Mo","Di","Mi","Do","Fr","Sa","So"
    +   DATA "Mo","Di","Mi","Do","Fr","Sa","So"

    Wird keine Zeilennummer bzw. keine Marke angegeben, @@ -2498,7 +2732,7 @@

    RESTORE

    Achtung! Wenn eine Marke angegeben wird, muss es die Marke sein, die unmittelbar vor der betreffenden DATA-Anweisung steht. Wenn zwischen der angegebenen Marke und der DATA-Anweisung - eine weitere Marke oder BASIC-Zeilennummer steht, + eine weitere Marke oder eine BASIC-Zeilennummer steht, meldet der Compiler einen Fehler.

    @@ -2551,63 +2785,6 @@

    SCREEN

    in der man sich gerade befindet, passiert nichts.

    -

    SEND

    - - - - - - - - - - - -
    Syntax: - SEND # <Kanal> , <Host> - , <Port> -
    Kanal:numerischer Ausdruck mit dem Wert 1 oder 2
    Host:einfacher String-Ausdruck
    Port:numerischer Ausdruck
    -
    - Die Anweisung sendet die zuvor in den Ein-/Ausgabekanal - geschriebenen Daten als UDP-Paket an die mit Host - und Port angegebene Netzwerkadresse. - Der Ausgabekanal muss dazu mit - DATAGRAM - geöffnet worden sein. -

    - Die SEND-Anweisung setzt die Fehlervariablen - ERR und - ERR$. -

    - -

    SET

    - - - - - - - - - -
    Syntax: - SET DNSSERVER <IP-Adresse>
    - SET GATEWAY <IP-Adresse>
    - SET NETMASK <IP-Adresse>
    - SET LOCALADDR <IP-Adresse> -
    IP-Adresse:Zeichenkette mit der IP-Adresse in textueller Form
    -
    - Die SET-Anweisung dient zum Konfigurieren von KCNet. - Das Beispiel zeigt das Setzen des DNS-Servers: -
    - -
    - INPUT "DNS-Server: ";E$
    - SET DNSSERVER E$
    - IF ERR THEN PRINT ERR$
    -
    -
    -

    SUB

    @@ -2688,10 +2865,11 @@

    WHILE


    I=1
    WHILE i<10
    -   PRINT I
    -   I=I+1
    +   PRINT I
    +   I=I+1
    WEND

    + diff --git a/src/help/tools/basicc/limitations.htm b/src/help/tools/basicc/limitations.htm index b0c57f9..f740400 100644 --- a/src/help/tools/basicc/limitations.htm +++ b/src/help/tools/basicc/limitations.htm @@ -17,8 +17,7 @@

    1. CALL und USR

    keine Einschränkung gegenüber dem Interpreter. Jedoch rufen manche BASIC-Programme mit CALL oder USR Maschinenunterprogramme des BASIC-Interpreters auf. - Andere Programme dagegen enthalten in REM-Zeilen Maschinencode, - der aufgerufen wird. + Andere Programme enthalten in REM-Zeilen Maschinencode, der aufgerufen wird. Untergrogramme des BASIC-Interpreters beziehungsweise in REM-Zeilen versteckter Maschinencode sind im erzeugten Maschinencodeprogramm nicht (mehr) vorhanden und können somit auch nicht aufgerufen werden. @@ -33,7 +32,7 @@

    1. CALL und USR



    2. GOTO und GOSUB

    - WIrd hinter GOTO oder GOSUB eine Zeilennummer angegeben, + Wird hinter GOTO oder GOSUB eine Zeilennummer angegeben, muss es eine konstante Zahl (Literal) sein. Ein variabler Ausdruck, wie bei manchen Interpretern möglich, ist nicht erlaubt. @@ -46,25 +45,18 @@

    2. GOTO und GOSUB

    Der Compiler könnte zwar in das erzeugte Maschinencodeprogramm auch eine Zuordnung von Zeilennummern zu Maschinencodeadressen einbauen und so auch variable Sprungziele ermöglichen, - jedoch würde dadurch das Programm ziemlich aufgebläht werden. + jedoch würde dann das Programm ziemlich aufgebläht werden. Da das aber nicht das Ziel des BASIC-Compilers ist, werden variable Sprungziele eben nicht unterstützt. + Mit gewissen Einschränkungen lassen sich jedoch solche Anforderungen + mit den Anweisungen ON...GOTO + und ON...GOSUB realisieren.

    - Die hinter GOTO und GOSUB stehende Zeilennummer oder Marke - muss auch existieren. + Des Weiteren müssen die hinter GOTO und GOSUB stehenden + Zeilennummern bzw. Marken auch existieren. Manche BASIC-Interpreter springen bei einer nicht existierenden Zeilennummer zur nächst höheren existierenden Zeile. Der JKCEMU-BASIC-Compiler unterstützt soetwas jedoch nicht. -

    - -

    3. INPUT

    - Bei der Eingabe von numerischen Werten bieten manche BASIC-Interpreter - die Möglichkeit, komplexe numerische Ausdrücke - einschließlich Funktionsaufrufen anzugeben, - so wie sie auch im BASIC-Quelltext erlaubt sind. - Das erzeugte Maschinencodeprogramm enthält jedoch diese - typische Interpreter-Funktionalität nicht. - Aus diesem Grund können nur ganze Dezimalzahlen eingegeben werden.
    diff --git a/src/help/tools/basicc/options.htm b/src/help/tools/basicc/options.htm index 4a23702..23b6c04 100644 --- a/src/help/tools/basicc/options.htm +++ b/src/help/tools/basicc/options.htm @@ -45,6 +45,21 @@

    1. Optionen in der Gruppe Allgemein

    Adressbereich liegt bzw. dort hineinpasst.

    +
  • + Variablen/Speicherzellen
    + Hiermit legen Sie fest, wo die Variablen, die Speicherzellen und, + sofern gewünscht, der eigene Stack-Bereich im Adressbereich + des Arbeitsspeichers liegen sollen. + Dieser Bereich muss RAM sein. + Wenn Sie für die Variablen und Speicherzellen keine Adresse angeben, + liegen sie direkt hinter dem Programmcode. + Das bedeutet aber auch, dass das compilierte Programm + nur im RAM lauffähig ist. + Möchten Sie ROM-fähigen Programmcode erzeugen, + müssen Sie eine im RAM liegende Adresse für + die Variablen und Speicherzellen angeben. +

    +
  • Größe Zeichenkettenspeicher
    Im Zeichenkettenspeicher werden die Zeichenketten abgelegt, @@ -113,7 +128,32 @@

    2. Optionen in der Gruppe Laufzeiteigenschaften



    -

    3. Optionen in der Gruppe Erzeugter Programmcode

    +

    3. Optionen in der Gruppe Treiber

    +
      +
    • + Bei Verwendung der OPEN-Anweisung folgende Treiber einbinden: ... +
      + Mit der OPEN-Anweisung werden + Ein-/Ausgabekanäle geöffnet. + Dazu müssen bestimmte Treiber im kompilierten Programm + enthalten sein. + Der Compiler bindet standardmäßig alle Treiber ein, + die er entsprechend des BASIC-Programms und des Zielsystems + als benötigt erachtet. + Es kann jedoch sein, + dass ein bestimmter Treiber gar nicht benötigt wird, + weil z.B. das Programm so geschrieben ist, + dass es niemals einen Ein-/Ausgabekanal zu dem betreffenden Gerät + öffnen wird. + Wenn Sie das wissen, können Sie die nicht benötigten Treiber + ausschalten und so die Größe des kompilierten Programms + verringern. +

      +
    • +
    +

    + +

    4. Optionen in der Gruppe Erzeugter Programmcode

    • Programmcode in Emulator laden
      @@ -141,7 +181,7 @@

      3. Optionen in der Gruppe Erzeugter Programmcode



    -

    4. Optionen in der Gruppe Sonstiges

    +

    5. Optionen in der Gruppe Sonstiges

    • Bei Nicht-ASCII-Zeichen warnen
      @@ -203,4 +243,3 @@

      4. Optionen in der Gruppe Sonstiges


      - diff --git a/src/help/tools/basicc/source.htm b/src/help/tools/basicc/source.htm index 41790e1..4f43ab7 100644 --- a/src/help/tools/basicc/source.htm +++ b/src/help/tools/basicc/source.htm @@ -75,7 +75,7 @@

      3. Zeilennummern



  • - Im BASIC-Programm stehende Zeilennummern werden in JKCEMU + Im BASIC-Programm stehende Zeilennummern werden im JKCEMU und der Hilfe BASIC-Zeilennummern genannt.

    @@ -103,7 +103,7 @@

    4. Marken

    RESTORE datenanfang
    ...
    datenanfang:
    -   DATA 1,2,3
    +   DATA 1,2,3

    Achtung! Hinter dem abschließenden Dopppelpunkt der Marke diff --git a/src/help/tools/basicc/strings.htm b/src/help/tools/basicc/strings.htm index db08d6c..7826af3 100644 --- a/src/help/tools/basicc/strings.htm +++ b/src/help/tools/basicc/strings.htm @@ -96,14 +96,11 @@

    String-Funktionen

  • BIN$
  • CHR$
  • HEX$
  • -
  • HOSTBYNAME$
  • INPUT$
  • LEFT$
  • -
  • LOCALADDR$
  • LOWER$
  • MID$
  • MIRROR$
  • -
  • REMOTEADDR$
  • RTRIM$
  • SPACE$
  • STRING$
  • diff --git a/src/help/tools/basicc/targets.htm b/src/help/tools/basicc/targets.htm index 6f0faa1..bb8113f 100644 --- a/src/help/tools/basicc/targets.htm +++ b/src/help/tools/basicc/targets.htm @@ -30,7 +30,8 @@

    Zielsysteme

  • KC85/1, KC87, Z9001 mit KRT-Grafik
  • -
  • KC85/2...5, HC900
  • +
  • KC85/2..5, HC900
  • +
  • KC85/4..5 mit Unterstützung beider Bildspeicher
  • SCCH (AC1-2010, AC1-SCCH, LLC2)
  • Z1013
  • Z1013 mit Peters-Platine
  • @@ -54,7 +55,10 @@

    AC1-2010, AC1-SCCH mit optionaler Farbgrafikkarte

    auf dem Bildschirm neben dem Bildwiederholspeicher auch der Farbspeicher beschrieben wird. Das compilierte Programm ist aber auch auf einem AC1 ohne Farbgrafikkarte - problemlos lauffähig. + lauffähig. + Einzig die POINT-Funktion + zum Ermitteln der Farbe eines Pixels würde in dem Fall + falsche Werte liefern.

    Die AC1-Monitorprogramme unterstützen standardmäßig keine Farben. @@ -143,11 +147,24 @@

    AC1-2010, AC1-SCCH mit optionaler Farbgrafikkarte

    Die JOYST-Funktion unterstützt einen Joystick und ruft dazu die Systemfunktion auf Adresse 0EB4h auf. -
  • KCNet wird an der E/A-Basisadresse C0h unterstützt.
  • - Ein VDIP-Modul wird an der E/A-Basisadresse FCh unterstützt. + Ein VDIP-Modul wird an den E/A-Basisadressen DCh, FCh + und 08h (PIO2) unterstützt, d.h., es wird zuerst versucht, + das VDIP-Modul an der Basisadresse DCh zu initialisieren. + Wenn das nicht gelingt, ist die Basisadresse FCh und danach 08h + an der Reihe. +
  • +
  • + Konstante für das Zielsystem ist: + TARGET_AC1 +
  • +
  • + Das Zielsystem ist vom Zielsystem + SCCH (AC1-2010, AC1-SCCH, LLC2) + abgeleitet, d.h., + die Funktion IS_TARGET + liefert auch bei TARGET_SCCH TRUE.
  • -
  • TARGETID$ liefert "AC1".

  • @@ -156,11 +173,13 @@

    CP/M-kompatibel

  • Die Grafikbefehle stehen nicht zur Verfügung.
  • Die CURSOR-Anweisung hat keine Wirkung.
  • Die JOYST-Funktion liefert immer 0.
  • -
  • KCNet wird an der E/A-Basisadresse C0h unterstützt.
  • Ein VDIP-Modul wird an der E/A-Basisadresse FCh unterstützt.
  • -
  • TARGETID$ liefert "CP/M".
  • +
  • + Konstante für das Zielsystem ist: + TARGET_CPM +

  • @@ -171,11 +190,13 @@

    Hübler-Grafik-MC

    Dieser muss dazu auf die Standardadresse C000h eingestellt sein.
  • Die JOYST-Funktion liefert immer 0.
  • -
  • KCNet wird an der E/A-Basisadresse C0h unterstützt.
  • Ein VDIP-Modul wird an der E/A-Basisadresse FCh unterstützt.
  • -
  • TARGETID$ liefert "HUEBLER".
  • +
  • + Konstante für das Zielsystem ist: + TARGET_HUEBLER +

  • Achtung! Für den Hübler-Grafik-MC compilierte Programme @@ -193,21 +214,18 @@

    Kramer-MC

  • Die CURSOR-Anweisung hat keine Wirkung.
  • Die LOCATE-Anweisung steht nicht zur Verfügung.
  • - Das in JKCEMU integrierte Monitorprogramm des Kramer-MC erkennt + Das im JKCEMU integrierte Monitorprogramm des Kramer-MC erkennt bei der INKEY-Funktion nur die Tastenkombination CTRL-C.
  • Druckerausgaben werden über den LIST-Kanal (Einsprungsadresse 00ECh) getätigt. - Das in JKCEMU enthaltene Monitorprogramm des Kramer-MC + Das im JKCEMU enthaltene Monitorprogramm des Kramer-MC ist standardmäßig jedoch so eingestellt, dass der LIST-Kanal und damit die Druckerausgaben auf dem Bildschirm erscheinen.
  • Die JOYST-Funktion liefert immer 0.
  • -
  • - Die Netzwerkanweisungen und -funktionen stehen nicht zur Verfügung. -
  • Es wird kein VDIP-Modul unterstützt.
  • Sie dürfen die Compiler-Option System-Stack verwenden @@ -217,7 +235,10 @@

    Kramer-MC

    hineinlaufen würde. Compilieren Sie immer mit der Option Eigener Stack-Bereich.
  • -
  • TARGETID$ liefert "KRAMER".
  • +
  • + Konstante für das Zielsystem ist: + TARGET_KRAMER +

  • @@ -251,14 +272,24 @@

    LLC2 mit HIRES-Grafik

    Die JOYST-Funktion unterstützt einen Joystick und ruft dazu die Systemfunktion auf Adresse 0EB4h auf. -
  • KCNet wird an der E/A-Basisadresse C0h unterstützt.
  • - Ein VDIP-Modul wird an den E/A-Basisadressen DCh und FCh - unterstützt, d.h., es wird zuerst versucht, + Ein VDIP-Modul wird an den E/A-Basisadressen DCh, FCh + und E4h (PIO2) unterstützt, d.h., es wird zuerst versucht, das VDIP-Modul an der Basisadresse DCh zu initialisieren. - Wenn das nicht gelingt, ist die Basisadresse FCh an der Reihe. + Wenn das nicht gelingt, ist die Basisadresse FCh und danach E4h + an der Reihe. +
  • +
  • + Konstante für das Zielsystem ist: + TARGET_LLC2 +
  • +
  • + Das Zielsystem ist vom Zielsystem + SCCH (AC1-2010, AC1-SCCH, LLC2) + abgeleitet, d.h., + die Funktion IS_TARGET + liefert auch bei TARGET_SCCH TRUE.
  • -
  • TARGETID$ liefert "LLC2_HIRES".

  • @@ -277,11 +308,13 @@

    KC85/1, KC87, Z9001

    Kommen dagegen keine Anweisungen zum Setzen der Farbe vor, wird Programmcode erzeugt, der nicht auf den Farbspeicher zugreift. -
  • KCNet wird an der E/A-Basisadresse C0h unterstützt.
  • Ein VDIP-Modul wird an der E/A-Basisadresse DCh unterstützt.
  • -
  • TARGETID$ liefert "Z9001".
  • +
  • + Konstante für das Zielsystem ist: + TARGET_Z9001 +

  • @@ -300,14 +333,28 @@

    KC85/1, KC87, Z9001 mit KRT-Grafik

    mit der LABEL-Anweisung).
  • - Bezüglich Farbunterstützung, KCNet und VDIP gilt das gleiche + Bezüglich Farbunterstützung und VDIP gilt das gleiche wie beim Zielsystem KC85/1, KC87, Z9001.
  • -
  • TARGETID$ liefert "Z9001_KRT".
  • +
  • + Konstante für das Zielsystem ist: + TARGET_Z9001_KRT +
  • +
  • + Das Zielsystem ist vom Zielsystem + KC85/1, KC87, Z9001 abgeleitet, d.h., + die Funktion IS_TARGET + liefert auch bei TARGET_Z9001 TRUE. +

  • -

    KC85/2...5, HC900

    +

    KC85/2..5, HC900

    + Dieses Zielsystem erzeugt Programmcode, der auf allen + Mühlhäuser KCs lauffähig ist, + also vom HC900 bis zum KC85/5. +

    + Eigenschaften des Zielsystems im Detail:
    • Tastaturein- und Bildschirmausgaben erfolgen über den @@ -335,8 +382,8 @@

      KC85/2...5, HC900

    • Die CURSOR-Anweisung hat keine Wirkung. - CAOS schaltet den Cursor automatisch aus, - wenn nicht auf eine Eingabe gewartet wird. + CAOS schaltet den Cursor automatisch ein und aus, + je nachdem, ob gerade auf eine Eingabe gewartet wird oder nicht.
    • Die JOYST-Funktion fragt direkt das Joystick-Modul (M008 bzw. M021) ab, @@ -349,12 +396,64 @@

      KC85/2...5, HC900

      Dieser wird dadurch nicht beeinträchtigt.
    • - KCNet und VDIP werden über das Modul M052 unterstützt. + VDIP wird über das Modul M052 unterstützt. Das compilierte Programm aktiviert dazu das Modul selbstständig, d.h., es kann, muss aber nicht vor dem Programmstart schon aktiv sein. - Allerdings muss KCNet beim Programmstart konfiguriert sein.
    • -
    • TARGETID$ liefert "KC85".
    • +
    • + Konstante für das Zielsystem ist: + TARGET_KC85 +
    • +
    +
    + +

    + KC85/4..5 mit Unterstützung beider Bildspeicher +

    + Dieses Zielsystem ist speziell auf die Hardware des KC85/4 und KC85/5 + zugeschnitten und ermöglicht somit beide Bildspeicher zu nutzen. + Außerdem wurden die Grafikroutinen auf die spezielle Hardware + optimiert, so dass diese noch etwas schneller sind als beim + weiter oben beschriebenen allgemeinen KC85/2..5-Zielsystem. +

    + Das Zielsystem KC85/4..5 stellt die beiden + SCREENs 0 und 1 bereit, + die die beiden Bildspeicher (IRM-Bänke) 0 und 1 + repräsentieren. + Für jeden Bildspeicher, d.h. für jeden SCREEN, + wird der Cursor separat verwaltet. + Man kann deshalb durch Umschalten des SCREENs auf beiden Bildspeichern + Text ausgeben, ohne das das eine Auswirkung auf den jeweils anderen + Bildspeicher hat. +

    + Eigenschaften des Zielsystems im Detail: +
      +
    • + SCREEN 0 ermöglicht die Ausgabe von Text und Grafik + unter Nutzung der IRM-Bank 0. +
    • +
    • + SCREEN 1 ermöglicht die Ausgabe von Text und Grafik + unter Nutzung der IRM-Bank 1. +
    • +
    • + Für jeden SCREEN wird ein eigenes CAOS-Fenster verwendet, + so dass jeder SCREEN auch eine eigene Cursor-Verwaltung hat. +
    • +
    • + alles weitere wie beim Zielsystem + KC85/2..5, HC900 +
    • +
    • + Konstante für das Zielsystem ist: + TARGET_KC85_4 +
    • +
    • + Das Zielsystem ist vom Zielsystem + KC85/2..5, HC900 abgeleitet, d.h., + die Funktion IS_TARGET + liefert auch bei TARGET_KC84 TRUE. +

    @@ -385,14 +484,16 @@

    SCCH (AC1-2010, AC1-SCCH, LLC2)

    Die JOYST-Funktion unterstützt einen Joystick und ruft dazu die Systemfunktion auf Adresse 0EB4h auf. -
  • KCNet wird an der E/A-Basisadresse C0h unterstützt.
  • Ein VDIP-Modul wird an den E/A-Basisadressen DCh und FCh unterstützt, d.h., es wird zuerst versucht, das VDIP-Modul an der Basisadresse DCh zu initialisieren. Wenn das nicht gelingt, ist die Basisadresse FCh an der Reihe.
  • -
  • TARGETID$ liefert "SCCH".
  • +
  • + Konstante für das Zielsystem ist: + TARGET_SCCH +

  • @@ -421,14 +522,16 @@

    Z1013

    des Z1013-Monitorprogramms, die für den Test auf Abbruch regelmäßig aufgerufen wird. -
  • KCNet wird an der E/A-Basisadresse C0h unterstützt.
  • Ein VDIP-Modul wird an der E/A-Basisadresse DCh und FCh unterstützt, d.h., es wird zuerst versucht, das VDIP-Modul an der Basisadresse DCh zu initialisieren. Wenn das nicht gelingt, ist die Basisadresse FCh an der Reihe.
  • -
  • TARGETID$ liefert "Z1013".
  • +
  • + Konstante für das Zielsystem ist: + TARGET_Z1013 +

  • @@ -450,27 +553,35 @@

    Z1013 mit Peters-Platine

  • SCREEN 1 ist ein reiner Textbildschirm mit 64x16 Zeichen.
  • -
  • TARGETID$ liefert "Z1013_64X16".
  • Alles andere gilt entsprechend dem Zielsystem Z1013.
  • +
  • + Konstante für das Zielsystem ist: + TARGET_Z1013_64X16 +
  • +
  • + Das Zielsystem ist vom Zielsystem + Z1013 abgeleitet, d.h., + die Funktion IS_TARGET + liefert auch bei TARGET_Z1013 TRUE. +

  • Zusammenfassung

    - Die Tabelle zeigt anhand der mit - TARGETID$ - gelieferten Kennung alle Zielsysteme und fasst die wesentlichsten - Eigenschaften zusammen: + Die Tabelle zeigt anhand der zugehörigen + Konstante + alle Zielsysteme und fasst die wesentlichsten Eigenschaften zusammen:

    - - + + - - - - - - + - + + + + + + - - - - @@ -564,7 +690,8 @@

    Zusammenfassung

    Zielsystem (TARGETID$)Programm ist auffähig aufZielsystem (Konstante)Programm ist lauffähig auf Text Grafik Farbe
    AC1 + TARGET_AC1 AC1-2010, AC1-SCCH,
    mit Einschränkungen auch AC1-ACC @@ -480,7 +591,7 @@

    Zusammenfassung

    ja ¹)
    CP/M + TARGET_CPM CP/M-kompatible Systeme mit den von Robotron verwendeten Bildschirmsteuerzeichen @@ -491,7 +602,7 @@

    Zusammenfassung

    nein
    HUEBLER + TARGET_HUEBLER Hübler-Grafik-MC,
    mit Einschränkungen auch Hübler/Evert-MC @@ -501,28 +612,41 @@

    Zusammenfassung

    nein
    KRAMER + TARGET_KRAMER Kramer-MC SCREEN 0: 64x16 Zeichen - nein
    LLC2_HIRES + TARGET_LLC2 LLC2 mit HiRes-Grafik SCREEN 0: 64x32 Zeichen SCREEN 1: 512x256 Pixel nein
    KC85 - HC900, KC85/2..5TARGET_KC85 + KC85/2..5, HC900 SCREEN 0: 40x32 Zeichen SCREEN 0: 320x256 Pixel ja
    SCCH + TARGET_KC85_4 + KC85/4, KC85/5 + SCREEN 0: 40x32 Zeichen
    + SCREEN 1: 40x32 Zeichen +
    + SCREEN 0: 320x256 Pixel
    + SCREEN 1: 320x256 Pixel +
    ja
    TARGET_SCCH AC1-2010, AC1-SCCH, LLC2
    mit Einschränkungen auch Ur-AC1 und AC1-ACC @@ -532,14 +656,16 @@

    Zusammenfassung

    nein
    Z1013 + TARGET_Z1013 Z1013 SCREEN 0: 32x32 Zeichen SCREEN 0: 64x64 Pixel nein
    Z1013_64X16 + + TARGET_Z1013_64X16 + Z1013 mit Peters-Platine SCREEN 0: 32x32 Zeichen
    @@ -549,14 +675,14 @@

    Zusammenfassung

    nein
    Z9001 + TARGET_Z9001 KC85/1, KC87, Z9001 SCREEN 0: 40x24 Zeichen SCREEN 0: 80x48 Pixel ja ¹)
    Z9001_KRT + TARGET_Z9001_KRT KC85/1, KC87, Z9001 mit KRT-Grafik SCREEN 0: 40x24 Zeichen SCREEN 1: 320x192 Pixel

    - ¹) lauffähig auch auf einem Rechner ohne Farbgrafikkarte + ¹) Ein für dieses Zielsystem compiliertes Programm + ist auch auf einem Rechner ohne Farbgrafikkarte lauffähig.
    diff --git a/src/help/tools/basicc/usersubs.htm b/src/help/tools/basicc/usersubs.htm index 09878af..6663c5a 100644 --- a/src/help/tools/basicc/usersubs.htm +++ b/src/help/tools/basicc/usersubs.htm @@ -14,7 +14,7 @@

    Benutzerdefinierte Funktionen und Prozeduren

    Struktur des BASIC-Programms

    Benutzerdefinierte Funktionen und Prozeduren werden nach - dem Haupprogramm implementiert, d.h., + dem Hauptprogramm implementiert, d.h., die erste FUNCTION- bzw. SUB-Anweisung beendet das Hauptprogramm. @@ -119,37 +119,37 @@

    Programmbeispiel

    DECLARE FUNCTION roemische_zahl$(z)

    DO
    -   INPUT "Zahl: ";Z
    -   IF Z>0 THEN
    -     PRINT "Roemische Zahl: ";roemische_zahl$(Z)
    -   ELSEIF Z=0 THEN
    -     PRINT "Ende"
    -     EXIT
    -   ELSE
    -     PRINT "Keine negative Zahl!"
    -   ENDIF
    +   INPUT "Zahl: ";Z
    +   IF Z>0 THEN
    +     PRINT "Roemische Zahl: ";roemische_zahl$(Z)
    +   ELSEIF Z=0 THEN
    +     PRINT "Ende"
    +     EXIT
    +   ELSE
    +     PRINT "Keine negative Zahl!"
    +   ENDIF
    LOOP
    END

    'Zuordnung Dezimalzahl zu roemischer Zahl
    roem_map:
    -   DATA 1000,"M",900,"CM",500,"D",400,"CD"
    -   DATA 100,"C",90,"XC",50,"L",40,"XL"
    -   DATA 10,"X",9,"IX",5,"V",4,"IV",1,"I"
    +   DATA 1000,"M",900,"CM",500,"D",400,"CD"
    +   DATA 100,"C",90,"XC",50,"L",40,"XL"
    +   DATA 10,"X",9,"IX",5,"V",4,"IV",1,"I"

    'Implementierung der benutzerdefinierten Funktion
    FUNCTION roemische_zahl$(z)
    -   LOCAL n,t$,rv$
    -   rv$=""
    -   RESTORE roem_map
    -   WHILE z>0
    -     READ n,t$
    -     WHILE z>=n
    -       z=z-n
    -       rv$=rv$+t$
    -     WEND
    -   WEND
    -   roemische_zahl$=rv$:'Rueckgabewert zuweisen
    +   LOCAL n,t$,rv$
    +   rv$=""
    +   RESTORE roem_map
    +   WHILE z>0
    +     READ n,t$
    +     WHILE z>=n
    +       z=z-n
    +       rv$=rv$+t$
    +     WEND
    +   WEND
    +   roemische_zahl$=rv$:'Rueckgabewert zuweisen
    END FUNCTION

    diff --git a/src/help/tools/calculator.htm b/src/help/tools/calculator.htm index 9709eab..e430aed 100644 --- a/src/help/tools/calculator.htm +++ b/src/help/tools/calculator.htm @@ -1,7 +1,7 @@

    Rechner

    - In JKCEMU ist ein Rechner integriert, der neben den üblichen + Im JKCEMU ist ein Rechner integriert, der neben den üblichen Taschenrechnerfunktionen auch das Rechnen mit Binär-, Oktal- und Hexadezimalzahlen sowie die Umrechnung zwischen Unicode-Zeichen und seinem numerischen Wert unterstützt. @@ -21,16 +21,12 @@

    Rechner

    mit bis zu 1000 Vor- und 1000 Nachkommastellen eingeben kann und die vier Grundrechenarten auch mit dieser Genauigkeit berechnet werden. - Alle anderen Berechnungen erfolgen mit der bei Computern - üblichen Genauigkeit von etwa 15 Stellen - (64 Bit Zahlenformat). -

    - Bzgl. der Rechengenauigkeit bei der Division gibt es folgende Ausnahme: + Bei der Division gibt es jedoch folgende Ausnahme: Hat das Ergbnis einen unendlich langen Nachkommaanteil, wird dieser auf ein sinnvolles Maß gekürzt ausgegeben. - Alle anderen Berechnungen erfolgen mit der bei Computern - üblichen Genauigkeit von etwa 15 Stellen - (64 Bit Zahlenformat). + Alle über die vier Grundrechenarten hinausgehenden Berechnungen + erfolgen mit der bei Computern üblichen Genauigkeit + von etwa 15 Stellen (64 Bit Zahlenformat).

    Der Rechner kann auch als eigenständiges Programm ohne Emulator gestartet werden. @@ -127,6 +123,11 @@

    4. Funktionen

    RND( x )
    RANDOM( x )
    Zufallszahl im Bereich 0 bis x
    ROUND( x )Runden auf ganze Zahl
    SIG( x )
    SIGNUM( x )
    Vorzeichen bestimmen,
    Rückgabewerte: -1, 0 oder 1
    SIN( x )Sinus
    SINH( x )Sinus Hyperbolicus
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Format der SpeicherabbilddateiAudio-Wiedergabe im Kassettenaufzeichnungsformat
    Einfache Speicherabbilddateien (*.bin) -
      -
    • AC1
    • -
    • AC1/LLC2 TurboSave
    • -
    • Z1013
    • -
    -
    Headersave-Dateien (*.z80) -
      -
    • Z1013-Headersave
    • -
    -
    - Headersave-Dateien (*.z80)
    - mit Typ B und Anfangsadresse 60F7
    - (Format, in dem JKCEMU AC1- und LLC2-BASIC-Programme speichert) -
    -
      -
    • AC1-BASIC
    • -
    • AC1/LLC2 TurboSave
    • -
    • Z1013-Headersave
    • -
    -
    - KC-System-Dateien (*.kcc)
    - KC-System-Dateien mit BASIC-Programm (*.kcb)
    -
    -
      -
    • KC-Format
    • -
    -
    KC-BASIC-Dateien (*.sss) -
      -
    • KC-Format
    • -
    -
    KC-TAP-Dateien (*.tap) -
      -
    • KC-Format
    • -
    -
    -

    - Der Dateikonverter bietet ebensfalls - die Möglichkeit zur Audio-Wiedergabe von Speicherabbilddateien. - Aufgrund der dort vorhandenen Konvertierungsfunktionen lassen sich - sogar noch mehr Speicherabbilddateiformate in den unterstützten - Kassettenaufzeichnungsformaten wiedergeben. -

    +

    + 5. Audio-Wiedergabe von Speicherabbilddateien +

    + Eine Besonderheit des JKCEMU-Datei-Browsers ist, + dass er Speicherabbilddateien im Kassettenaufzeichnungsformat + einiger der emulierten Computer wiedergeben kann. + Damit eignet sich JKCEMU als Zuspieler für diese 8-Bit-Computer. + Die entsprechenden Funktionen finden Sie im Untermenü + DateiWiedergeben im +

    + Die Tabelle zeigt, welche Dateiformate in welchem + Kassettenaufzeichnungsformat wiedergegeben werden können: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Format der SpeicherabbilddateiAudio-Wiedergabe im Kassettenaufzeichnungsformat
    Einfache Speicherabbilddateien (*.bin) +
      +
    • AC1
    • +
    • AC1/LLC2 TurboSave
    • +
    • Z1013
    • +
    +
    Headersave-Dateien (*.z80) +
      +
    • Z1013-Headersave
    • +
    +
    + Headersave-Dateien (*.z80)
    + mit Typ B und Anfangsadresse 60F7
    + (Format, in dem JKCEMU AC1- und LLC2-BASIC-Programme speichert) +
    +
      +
    • AC1-BASIC
    • +
    • AC1/LLC2 TurboSave
    • +
    • Z1013-Headersave
    • +
    +
    + KC-System-Dateien (*.kcc)
    + KC-System-Dateien mit BASIC-Programm (*.kcb)
    +
    +
      +
    • KC-Format
    • +
    +
    KC-BASIC-Dateien (*.sss) +
      +
    • KC-Format
    • +
    +
    KC-TAP-Dateien (*.tap) +
      +
    • KC-Format
    • +
    +
    +

    + Der Dateikonverter bietet ebensfalls + die Möglichkeit zur Audio-Wiedergabe von Speicherabbilddateien. + Aufgrund der dort vorhandenen Konvertierungsfunktionen lassen sich + sogar noch mehr Speicherabbilddateiformate in den unterstützten + Kassettenaufzeichnungsformaten wiedergeben. +

    -

    5. Tastaturbedienung

    - Der Datei-Browser lässt sich auch bequem und schnell - mit den vier Cursor-Tasten bedienen. - Das Auf- und Zuklappen der Verzeichnisse geschieht mit den Tasten - Links und Rechts. - Die Enter-Taste hat die gleiche Funktion wir ein - Doppelklick. -

    +

    6. Weitere Funktionen

    + Der Datei-Browser bietet neben dem Laden bzw. Öffnen + von Dateien auch weiterer Funktionen, + wie z.B. Verzeichnisse anlegen, + Verzeichnisse und Dateien umbenennen und löschen, + Prüf- und Hashwerte berechnen, + Packen und Entpacken sowie + den Änderungszeitpunkt auf einen beliebigen Wert setzen. + Alle diese Funktionen finden Sie im Menü Datei. +

    -

    6. Einstellungen

    - Im Menü Einstellungen können Sie den Datei-Browser - an Ihre Bedürfnisse anpassen. - Diese Einstellungen, - die Position des Schiebers zwischen Baumdarstellung und Vorschau - sowie die Fenstergröße und -position - können Sie auch dauerhaft in einem Profil speichern. - Das tun Sie in dem separaten Fenster für - Einstellungen. +

    7. Tastaturbedienung

    + Der Datei-Browser lässt sich auch bequem und schnell + mit den vier Cursor-Tasten bedienen. + Das Auf- und Zuklappen der Verzeichnisse geschieht mit den Tasten + Links und Rechts. + Die Enter-Taste hat die gleiche Funktion wir ein + Doppelklick. +

    + +

    8. Einstellungen

    + Im Menü Einstellungen können Sie den Datei-Browser + an Ihre Bedürfnisse anpassen. + Diese Einstellungen, + die Position des Schiebers zwischen Baumdarstellung und Vorschau + sowie die Fenstergröße und -position + können Sie auch dauerhaft in einem Profil speichern. + Das tun Sie in dem separaten Fenster für + Einstellungen. diff --git a/src/help/tools/fileconverter.htm b/src/help/tools/fileconverter.htm index de57798..375a6b1 100644 --- a/src/help/tools/fileconverter.htm +++ b/src/help/tools/fileconverter.htm @@ -66,8 +66,10 @@

    Dateikonverter

    Einfache Diskettenabbilddatei (*.img; *.image; *.raw)
  • AnaDisk-Datei (*.dump)
  • +
  • CopyQM-Datei (*.cqm)
  • CPC-Disk-Datei (*.dsk)
  • ImageDisk-Datei (*.imd)
  • +
  • TeleDisk-Datei (*.td0)
  • @@ -134,6 +136,15 @@

    Dateikonverter

    +

    + Achtung! Wenn Sie beim Konvertieren einer Diskettenabbilddatei + eine Fehlermeldung erhalten, + dass ein bestimmter Sektor nicht gefunden wurde, + enthält die Quelldatei eine unregelmäßige Sektoranordnung, + aber das Format der Ausgabedatei unterstützt das nicht. + In dem Fall müssen Sie das Ausgabeformat ändern. + Folgende Formate unterstützten eine freie Sektoranordnung: + AnaDisk, CPC-Disk, ImageDisk und TeleDisk.

    Achtung! KCB-Dateien und bestimmte Headersave-Dateien enthalten KC-BASIC-Programme mit eventuell zusätzlichen Maschinencode- diff --git a/src/help/tools/findfiles.htm b/src/help/tools/findfiles.htm new file mode 100644 index 0000000..7cd825f --- /dev/null +++ b/src/help/tools/findfiles.htm @@ -0,0 +1,94 @@ + + +

    Dateien suchen

    + JKCEMU bietet die Möglichkeit, + in einem Verzeichnis einschließlich deren Unterverzeichnisse + Dateien nach bestimmten Kriterien zu suchen. +

    + +

    1. Suchkriterien

    + Nachfolgend werden die Kriterien beschrieben, + nach denen die Dateien gesucht werden können. + Die Suchkriterien sind UND-verknüpft, + d.h., es werden nur die Dateien gefunden, + die allen angegebenen Suchkriterien entsprechen. +

    + +

    1.1. Dateiname

    + Den Dateinamen können Sie in Form einer Dateinamensmaske vorgeben. + In dieser Maske werden die Zeichen * und ? als Platzhalter interpretiert. + Dabei steht das Sternchen für beliebig viele Zeichen + und das Fragezeichen für exakt ein beliebiges Zeichen. + Alle anderen Zeichen müssen übereinstimmen, + damit die betreffenden Datei gefunden wird. +

    + Sie können in dem Eingabefeld auch mehrere Dateinamensmasken angeben, + indem Sie diese einfach mit Leerzeichen, Komma oder Semikolon getrennt + hintereinander schreiben. + Soll in einer Dateinamensmaske ein Leerzeichen, Komma oder Semikolon + vorkommen, müssen Sie diese in doppelte Hochkommas schreiben, + damit diese Zeichen nicht als Trennzeichen gewertet werden. + So suchen Sie z.B. mit "* *" nach allen Dateien, + die ein Leerzeichen in ihrem Namen enthalten. + Dagegen bedeutet * * ohne doppelte Hochkommas + zweimal hintereinander die Maske für alle Dateinamen. +

    + Die Dateinamensmaske ist ein Pflichfeld, + d.h. Sie müssen in dem Feld etwas eingeben. + Möchten Sie den Dateinamen nicht einschränken, + d.h., es sollen alle Dateinamen gefunden werden, + dann geben Sie einfach nur ein Sternchen ein. +

    + +

    1.2. Dateigröße

    + Hier können Sie die zu suchenden Dateien nach ihrer Größe + einschränken, indem Sie eine minimale und/oder eine maximale + Größe angeben. + Es ist auch möglich, nur eins der beiden Felder auszufüllen. +

    + Die Größe muss in Bytes angegeben werden. +

    + +

    1.3. Änderungszeitpunkt

    + Mit den beiden Feldern hinter "Zuletzt geändert am" + können Sie die Suche nach dem Zeitstempel der letzten Änderung + einschränken. + Als Zeitangaben sind möglich: +
    + +
    + Der von Ihnen eingegebene Zeitpunkt ist genau genommen eine Zeitspanne, + und zwar je nach Art der Eingabe ein Tag, eine Minute oder eine Sekunde. + Diese Zeitspanne ist immer "inklusive" zu sehen, + sowohl beim Anfangs- als auch beim Endzeitpunkt. + Wenn Sie also nur im ersten Feld ein Datum eingeben, + werden die Dateien gefunden, die im Laufe des angegebenen Tages + geändert wurden. +

    + +

    1.4. Enthaltener Text

    + Möchten Sie Dateien suchen, die einen bestimmten Text enthalten, + so geben Sie den gesuchten Text in dem Feld hinter + "Enthaltener Text" ein. + Es werden dann nur die Dateien gefunden, die den Text enthalten. + Sollte in einer Datei der enthaltene Text durch Formatierungen + oder andere Bytes in sich aufgesplittet bzw. getrennt sein, + wird die Datei nicht gefunden. +

    + Enthält der Suchtext Nicht-ASCII-Zeichen (z.B. Umlaute), + muss der Zeichensatz der zu durchsuchenden Dateien bekannt sein. + Dazu testet JKCEMU die Dateien auf eine eventuell vorhandene + Byte-Order-Markierung für UTF-8 und UTF-16. + Ist eine solche vorhanden, wird der entsprechende Zeichensatz + verwendet, anderenfalls der lokale Systemzeichensatz. + Das bedeutet aber auch, dass Dateien ohne Byte-Order-Markierung, + die mit einem anderen als den lokalen Systemzeichensatz erzeugt wurden, + wahrscheinlich nicht gefunden werden, + wenn der gesuchte Text Nicht-ASCII-Zeichen enthält. +
    + + diff --git a/src/help/tools/hexdiff.htm b/src/help/tools/hexdiff.htm index fe87d35..fb0d6db 100644 --- a/src/help/tools/hexdiff.htm +++ b/src/help/tools/hexdiff.htm @@ -3,7 +3,7 @@

    Hex-Dateivergleicher

    Das Werkzeug Hex-Dateivergleicher dient zur hexadezimalen Anzeige der binären Unterschiede von Dateien. - Es ist in JKCEMU integriert, + Es ist im JKCEMU integriert, um z.B. bei Vorhandensein von mehreren Versionen eines Programms herauszufinden, ob und wie sich diese Versionen unterscheiden. Damit soll eine Möglichkeit bestehen, diff --git a/src/help/tools/imageviewer.htm b/src/help/tools/imageviewer.htm index e4569a1..133a643 100644 --- a/src/help/tools/imageviewer.htm +++ b/src/help/tools/imageviewer.htm @@ -2,7 +2,7 @@

    Bildbetrachter

    Der Bildbetrachter dient dazu, - Bildschirmfotos anzuzeigen und zu speichern. + Bildschirmfotos anzuzeigen, zu konvertieren und zu speichern. Sie können aber auch Bilddateien öffnen oder Bilder aus der Zwischenablage einfügen. Neben den üblichen Dateiformaten werden auch @@ -10,7 +10,7 @@

    Bildbetrachter

    und LLC2-HIRES-Bilddateien (*.pix, 16384 Bytes groß) lesend und schreibend unterstützt, d.h., mit dem Bildbetrachter können auch Bilder für - den A5105 und LLC2 konvertiert werden, + den A5105 und LLC2 erzeugt werden, wobei natürlich aufgrund der begrenzten Auflösung und Farbanzahl entsprechende Qualitätseinbußen entstehen.

    diff --git a/src/help/tools/labelimport.htm b/src/help/tools/labelimport.htm index 15b8695..1cbf08b 100644 --- a/src/help/tools/labelimport.htm +++ b/src/help/tools/labelimport.htm @@ -1,11 +1,16 @@ -

    Importieren von Marken und Haltepunkten

    +

    Importieren von Marken

    Debugger und Reassembler bieten die Möglichkeit, eine Markentabelle (z.B. von einem Assembler) zu importieren. - Im Debugger wird dabei für jede Marke ein Haltepunkt angelegt, - so dass dies auch einem Import von Haltepunkten gleichkommt. + Im Debugger wird dabei für jede Marke ein Halte-/Log-Punkt angelegt, + so dass dies auch einem Import von Halte-/Log-Punkten gleichkommt. + Da man aber üblicherweise nicht an jeder Marke anhalten möchte, + sind die importierten Halte-/Log-Punkte standardmäßig + erstmal deaktiviert, d.h., das Anhalten und das Loggen ist ausgeschaltet. + Bei Bedarf müssen Sie die einzelnen Halte-/Log-Punkte + manuell aktivieren.

    Die Markentabelle muss keinem strengen Format entsprechen: Pro Zeile werden die ersten beiden Ziffern-/Buchstabengruppen ausgewertet. @@ -70,7 +75,7 @@

    Importieren von Marken und Haltepunkten



    Enthält eine Zeile nur eine vierstellige hexadezimale Zahl ohne eine Marke, wird Sie in den Debugger trotzdem importiert - und dort ein Haltepunkt angelegt. + und dort ein Halte-/Log-Punkt angelegt. Der Reassembler dagegen kann mit einer einzelnen Adresse nichts anfangen und ignoriert so eine Zeile.

    diff --git a/src/help/tools/reassembler.htm b/src/help/tools/reassembler.htm index 419a306..20d6e21 100644 --- a/src/help/tools/reassembler.htm +++ b/src/help/tools/reassembler.htm @@ -10,7 +10,10 @@

    Reassembler

    1. Speicherbereich reassemblieren
  • - 2. Assembler-Quelltext erzeugen + 2. Halte-/Log-Punkt im Debugger anlegen +
  • +
  • + 3. Assembler-Quelltext erzeugen

  • @@ -51,7 +54,16 @@

    1. Speicherbereich reassemblieren

    ausführbaren Programmcode darstellen.

    -

    2. Assembler-Quelltext erzeugen

    +

    2. Halte-/Log-Punkt im Debugger anlegen

    + Im Kontextmenü (rechte Maustaste) finden Sie einen Menüeintrag, + mit dem Sie im Debugger einen + Halte-/Log-Punkt auf eine Programmadresse + anlegen können. + Dabei wird die Adresse, über der Sie das Kontextmenü + aufgerufen haben, als Vorbelegung im Halte-/Log-Punkt verwendet. +

    + +

    3. Assembler-Quelltext erzeugen

    Der Reassembler ist in der Lage, aus dem Reassembler-Listing einen Assembler-Quelltext zu erzeugen, den Sie dann in einem Assembler weiter bearbeiten können. diff --git a/src/help/tools/texteditor.htm b/src/help/tools/texteditor.htm index 1fc2317..15f4b6e 100644 --- a/src/help/tools/texteditor.htm +++ b/src/help/tools/texteditor.htm @@ -13,9 +13,6 @@

    Texteditor

  • 3.1.1. Dateien der emulierten Systeme
  • -
  • - 3.1.2. WordStar-Dateien -
  • @@ -34,6 +31,9 @@

    Texteditor

  • 3.9. Deutsche Umlaute konvertieren
  • +
  • + 3.10. WordStar-Formatierungen entfernen +
  • @@ -41,34 +41,32 @@

    Texteditor

    1. Vorbemerkungen

    - Die Text- und Dokumentationsdateien der emulierten Computer - sind häufig in einem Format gespeichert, - welches aufgrund eines anderen Zeilenendezeichens - mit heute üblichen Texteditoren nicht angesehen werden kann. - Außerdem werden deutsche Umlaute häufig in einem - speziellen Zeichensatz (ISO-646 Variante 21) dargestellt, - der heute nicht mehr gebräuchlich ist. - Wird ein solcher Text in einem ASCII-Editor angezeigt, - so sind anstelle der deutschen Umlaute die Zeichen - [\]{|}~ zu sehen. - Des Weiteren muss zum Anzeigen von Assembler-Quelltexten und - BASIC-Programmen immer erst der Assembler bzw. der BASIC-Interpreter - gestartet werden. - Zusammengefasst ist das Betrachten von alten Dateien, - die in irgendeiner Form Text enthalten, - auf heutigen Computern recht mühselig. + Ein Texteditor dient natürlich zum Anzeigen und Bearbeiten + von Textdateien. + Der JKCEMU-Texteditor ist aber insbesondere auf die Belange + des Emulators ausgelegt. + So bietet er einerseits Zugang zum Assembler + und zum BASIC-Compiler + und andereseits kann er mit den üblichen Zeichensätzen + und teilweise auch mit den Dateiformaten der emulierten Systeme umgehen. +

    + Der Zugang zum Assembler und zum BASIC-Compiler sieht so aus, + dass Sie das jeweilige Assembler- bzw. BASIC-Programm im JKCEMU-Texteditor + schreiben und anschließend den Assembler bzw. den BASIC-Compiler + über das Menü Programmierung aufrufen. + Ein angefangenes Assembler- oder BASIC-Programm können Sie + als Projekt speichern und wieder öffnen, + damit auch die jeweils eingestellten Optionen erhalten bleiben.

    - Der JKCEMU-Texteditor dient dazu, Textdateien, - EDAS*4-Assemblerquelltexte, AC1-BASIC, AC1-Mini-BASIC-, - KC-BASIC- und Z1013-Tiny-BASIC-Programme anzuzeigen - und in heute übliche Textdateien zu konvertieren. - Außerdem bietet der Texteditor Zugriff auf den - Assembler und den - BASIC-Compiler, d.h, - die zu übersetzenden Quelltexte müssen - in dem Texteditor geöffnet oder geschrieben werden. - Ein angefangenes Assembler- oder BASIC-Programm können Sie auch - als Projekt speichern und wieder öffnen. + Möchten Sie zur Programmierung einen anderen Texteditor verwenden, + können Sie den Assembler und den BASIC-Compiler über die + Kommandozeile aufrufen (siehe hier + und hier). +

    + Alte Dateiformate werden in der Form unterstützt, + dass beim Öffnen von EDAS*4-Assemblerquelltextdateien + und diversen BASIC-Programmdateien der jeweilige Quelltext extrahiert + und in Form einer neuen Textdatei bereitstellen wird.

    @@ -131,13 +129,34 @@

    3.1.1. Dateien der emulierten Systeme

    Konkret kann der Editor Texte aus folgenden Dateitypen extrahieren:


    -

    3.1.2. WordStar-Dateien

    - Erkennt der Texteditor beim Öffnen einer Datei - WordStar-artige Formatierungen, werden Sie gefragt, - ob Sie die Datei als WordStar-Datei importieren oder - als Textdatei öffnen möchten. - Beim Importieren wird der in der Datei enthaltene Text extrahiert - und ohne Formatierungen als neue Textdatei geöffnet. - So haben Sie die Möglichkeit, WordStar-Dateien vernünftig - zu lesen, wenn auch ohne Formatierungen. -

    - Die andere Möglichkeit, das Öffnen als Textdatei, - ist für den Fall gedacht, dass die Datei keine WordStar-Datei ist - und nur fälschlicherweise als solche erkannt wurde. -

    -

    3.2. Datei öffnen mit Zeichensatz

    Mit dieser Funktion öffnen Sie eine Datei, die in einem anderen als den Systemzeichensatz gespeichert wurde. @@ -210,7 +214,9 @@

    3.4. Datei speichern unter...

    Nach dem Bestätigen sehen Sie ein neues Fenster, in dem Sie die Eigenschaften angeben können. Damit ist es auch möglich, eine Textdatei in einem anderen - als den Systemzeichensatz zu speichern. + als den Systemzeichensatz zu speichern oder + die Zeilenende-Bytes (DOS/Windows- bzw. Unix/Linux-Format) + zu ändern.

    3.5. Drucken

    @@ -287,5 +293,35 @@

    3.9. Deutsche Umlaute konvertieren

    mit welchem Zeichensatz der Text erstellt wurde und überlegen Sie bitte sorgfältig, ob und wie Sie Zeichen umwandeln müssen. +

    + +

    3.10. WordStar-Formatierungen entfernen

    + WordStar war zu Zeiten der 8-Bit-Computer eine weit verbreitete + Textverarbung. + Aus diesem Grund wurden viele Dokumentationen in WordStar erstellt. + Damit Sie solche Dateien lesen können, bietet der JKCEMU-Texteditor + dafür eine spezielle Unterstützung an: + Wenn Sie nach dem Öffnen einer Datei erkennen, + dass es sich um eine WordStar-Datei handelt, + können Sie mit dem Menüpunkt die WordStar-Formatierungen + entfernen und so eine einfache Textdatei daraus machen. + Eine WordStar-Datei erkennt man meistens daran, + dass der letzte Buchstabe eines jeden Wortes durch ein + seltsames Zeichen dargestellt wird. +

    + Achtung! Eine WordStar-Datei enthält auch Bytes, + die abhängig vom verwendeten Zeichensatz + keine lesbaren oder falsche Textzeichen ergeben. + Aus diesem Grund sollte eine WordStar-Datei mit einem Zeichensatz + geöffnet werden, der nicht zu solchen Effekten führt. + Wenn Sie also feststellen, dass Sie eine WordStar-Datei + geöffnet haben, + dann sollten Sie am besten die Datei wieder schließen + und anschließend mit dem Menüpunkt + Datei öffnen mit Zeichensatz + erneut öffnen und dabei den Zeichensatz + ISO-8859-1 (Latin 1) ausw&auuml;hlen. + Danach können Sie dann bedenkenlos die WordStar-Formatierungen + entfernen. diff --git a/src/help/tradenames.htm b/src/help/tradenames.htm index 5f61e49..93df2db 100644 --- a/src/help/tradenames.htm +++ b/src/help/tradenames.htm @@ -3,7 +3,7 @@

    Markennamen

    In der Programmoberfläche und in der Hilfe werden an einigen Stellen Markennamen genannt (z.B. Amstrad, CP/M, Intel, - Linux, Mac, Robotron, Solaris, WordStar, Zilog). + Linux, Mac, Robotron, Sinclair, Solaris, WordStar, Zilog). Diese Namen sind registrierte Markennamen der jeweiligen Markeninhaber und werden hier entsprechend dem deutschen und europäischen Markenrecht nur zum Zweck der Nennung der bzw. des Verweises auf die diff --git a/src/help/usb.htm b/src/help/usb.htm index 3c8557c..dd4814b 100644 --- a/src/help/usb.htm +++ b/src/help/usb.htm @@ -124,14 +124,6 @@

    Fehlermeldungen bei unterbundenen Schreibzugriffen

    Disk Full ausgegeben. In diesen Fällen entspricht somit die angezeigte Fehlermeldungen nicht ganz dem wirklichen Grund des missglückten Schreibzugriffs. -

    - -

    Zeitstempel

    - Als Zeitstempel wird nur der letzte Änderungszeitpunkt - unterstützt. - Beim VDAP-Kommando DIRT haben deshalb alle drei - Zeitstempel (Dateierzeugung, letzter Zugriff und letzte Änderung) - immer den gleichen Wert, nämlich den der letzten Änderung. diff --git a/src/help/z1013.htm b/src/help/z1013.htm index a2fc7a6..7f056f2 100644 --- a/src/help/z1013.htm +++ b/src/help/z1013.htm @@ -15,7 +15,7 @@

    Hinweise zur Emulation des Z1013

  • 1.8. USB-Anschluss (Vinculum VDIP Modul)
  • -
  • 1.9. Echtzeituhrmodul mit RTC-72421
  • +
  • 1.9. Echtzeituhrmodul
  • 2. Im ROM enthaltene Software
  • @@ -23,7 +23,10 @@

    Hinweise zur Emulation des Z1013

  • 4. Sonstiges
  • @@ -68,17 +71,21 @@

    1. Emulierte Hardware

  • Zwei RAM-Floppies nach Mikroprozessortechnik - Heft 3/1988 an den IO-Basis-Adressen 98h und 58h + Heft 3/1988 an den E/A-Basis-Adressen 98h und 58h
  • Floppy-Disk-Modul
  • +
  • + GIDE mit bis zu zwei Festplatten + an der E/A-Basisadresse 40h und 80h +
  • KCNet-kompatible Netzwerkkarte - an der E/A-Basisadressen C0h + an der E/A-Basisadressen C0h
  • USB-Anschluss (Vinculum VDIP Modul)
  • -
  • Echtzeituhrmodul mit RTC-72421
  • +
  • Echtzeituhrmodul
  • Grafikkarte des CC Jena (80x25 Zeichen)
  • Vollgrafikerweiterung nach Kleinstrechnertips @@ -138,11 +145,11 @@

    1.2. Alphatastatur



    1.3. Peters-Platine

    - Die Peters-Platine ermöglicht über IO-Port 4 + Die Peters-Platine ermöglicht über E/A-Port 4 die Abschaltung des ROMs sowie die Umschaltung des Zeichensatzes, der Taktfrequenz und der Bildschirmorganisation.

    - Bedeutung der Bits von IO-Port 4: + Bedeutung der Bits von E/A-Port 4:
    @@ -211,7 +218,7 @@

    1.4. Mega-ROM-Modul

    Eine softwaremäßige Abschaltung des Moduls wie beim Z9001, KC85/1 und KC87 ist mit der Z1013-Variante nicht möglich.

    - In JKCEMU ist kein Inhalt für das Mega-ROM-Modul enthalten. + Im JKCEMU ist kein Inhalt für das Mega-ROM-Modul enthalten. Sie müssen deshalb in den Einstellungen eine entsprechende ROM-Datei auswählen.

    @@ -301,9 +308,10 @@

    1.8. USB-Anschluss (Vinculum VDIP Modul)

    da es sonst einen Konflikt mit der E/A-Adresse FFh geben würde.

    -

    1.9. Echtzeituhrmodul mit RTC-72421

    - Die Echtzeituhr wird an den IO-Adressen 70h bis 7Fh emuliert. - Die Zeit kann nur gelesen, nicht aber gesetzt werden. +

    1.9. Echtzeituhrmodul

    + Es wird eine Echtzeituhr RTC-62421 bzw. RTC-72421 an den + E/A-Adressen 70h bis 7Fh emuliert. + Die Uhr kann nur gelesen, nicht aber gestellt werden.

    2. Im ROM enthaltene Software

    @@ -346,22 +354,109 @@

    2. Im ROM enthaltene Software

    3. Enthaltene Diskettenabbilder

    + JKCEMU enthält für die Z1013-Emulation + folgende Diskettenabbilder: +
    + Die Laufwerkszuordnung bei beiden Diskettenabbildern ist: +

    +
    + + + + + + + + + + + + + + + + + +
    A:RAM-Floppy
    B:erstes Diskettenlaufwerk, 820-KByte-Format
    C:erstes Diskettenlaufwerk, 624-KByte-Format
    D: + erstes Diskettenlaufwerk, 780-KByte-Format +
    + Achtung! Dieses Format haben die Diskettenabbilder selbst, + d.h., sie sind unter dem Laufwerk D: ansprechbar. +
    E:erstes Diskettenlaufwerk, 800-KByte-Format
    +
    Auf der CP/M-Boot-Diskette für 80x25 Zeichen - sind auch in User-Ebene 15 die Quelltexte für das BIOS + sind in User-Ebene 15 auch die Quelltexte für das BIOS enthalten. + Anleitungen dazu finden Sie als Textdateien in User-Ebene 0. + Diese Dateien können mit dem CP/M-Kommando TYPE + gelesen werden.

    4. Sonstiges

    -

    4.1. Einfügen von Text

    +

    + 4.1. BASIC-Programme speichern, laden und öffnen +

    + JKCEMU bietet eine spezielle Unterstützung für das + Speichern und Laden von BASIC-Programmen + sowie für das Öffnen von im Arbeitsspeicher befindlichen + BASIC-Programmen im Texteditor. + Die entsprechenden Funktionen finden Sie im Menü Datei. +

    + Folgende BASIC-Interpreter werden unterstützt: +
    + +
    + Beim Speichern von BASIC-Programmen erscheint ein Fenster zur Auswahl + des Dateiformats. + Speichern Sie bitte Tiny-BASIC-Programme als + Headersave-Datei (*.z80) + mit dem Dateityp b und KC-BASIC-Programme entweder als + KC-BASIC-Programmdatei (*.sss) + oder als + Headersave-Datei (*.z80) + mit dem Dateityp B. +

    + Wenn Sie ein BASIC-Programm in einem anderen Dateiformat speichern, + erkennt JKCEMU später beim Laden der Datei nicht mehr, + dass es sich um ein BASIC-Programm handelt und passt die + Systemzellen des BASIC-Interpreters nicht an. + Das geladene Programm lässt sich dann nicht nutzen. +

    + +

    4.2. Einfügen von Text

    Das Einfügen von Text aus der Zwichenablage erfolgt gewöhnlich in der Form, dass für jedes einzufügende Zeichen das Drücken der entsprechenden Taste bzw. Tastenkombination diff --git a/src/help/z9001.htm b/src/help/z9001.htm index d791c95..71a635d 100644 --- a/src/help/z9001.htm +++ b/src/help/z9001.htm @@ -68,6 +68,7 @@

    Hinweise zur Emulation des Z9001, KC85/1 und KC87

  • 1.12. KCNet-kompatible Netzwerkkarte
  • +
  • 1.13. Echtzeituhrmodul
  • 2. Im ROM enthaltene Software
  • @@ -76,7 +77,7 @@

    Hinweise zur Emulation des Z9001, KC85/1 und KC87

    4. Sonstiges @@ -148,13 +149,18 @@

    1. Emulierte Hardware

    an den E/A-Adressen 20h/21h und 24h/25h
  • - KCNet-kompatible Netzwerkkarte - an der E/A-Basisadressen C0h + GIDE mit bis zu zwei Festplatten + an der E/A-Basisadresse 50h +
  • +
  • + KCNet-kompatible Netzwerkkarte + an der E/A-Basisadressen C0h
  • USB-Anschluss (Vinculum VDIP Modul) - an der E/A-Basisadressen DCh + an der E/A-Basisadressen DCh
  • +
  • 1.13. Echtzeituhrmodul
  • Plotter XY4131 / XY4140
  • @@ -243,7 +249,7 @@

    1.3.2. KRT-Vollgrafikerweiterung

    einfache Vollgrafikerweiterung für den Z1013 vorgestellt. Volker Pohlers und Ulrich Zander haben diese Schaltung mit kleinen Modifikationen - (andere IO-Adressen, Pixeldaten nicht invertierend) + (andere E/A-Adressen, Pixeldaten nicht invertierend) an den Z9001 angepasst und ihr in Anlehnung an Kleinstrechnertips den Name KRT-Vollgrafikerweiterung gegeben. @@ -364,7 +370,7 @@

    1.5. 80-Zeichen-Modus mit Zeichensatzumschaltung

    In dem Fall wird dann zwischen den unteren und den oberen 2 KByte umgeschaltet.

    - Auf der in JKCEMU enthaltenen Z9001 CP/A Systemdiskette + Auf der im JKCEMU enthaltenen Z9001 CP/A Systemdiskette befinden sich auch Treiber für den 80-Zeichen-Modus. Da diese Treiber die Hardware über die E/A-Adressen nach Ulrich Zander ansteuern, beginnen sie mit uz80.... @@ -407,8 +413,10 @@

    1.6. Floppy-Disk-Modul



    Achtung! Im Emulator ist ein Abbild einer CP/A-Systemdiskette enthalten (CP/M-Variante für Z9001 von Dr. Frank Schwarzenberg). - Zum Booten dieses CP/M-Systems muss zusätzlich auch das - 64 KByte RAM-Modul aktiviert sein. + Zum Booten dieses CP/M-Systems müssen zusätzlich auch das + 64 KByte RAM-Modul sowie + entweder das Boot-Modul oder das + Mega-ROM-Modul aktiviert sein.

    1.7. Boot-Modul

    @@ -438,12 +446,12 @@

    1.8. Mega-ROM-Modul

    Das Mega-ROM-Modul bietet 2,5 MByte Speicher, aufgeteilt auf 256 einzelne 10 KByte Segmente. Das jeweils aktive Segment wird im Adressbereich C000h-E7FFh eingeblendet. - Die Umschaltung des aktive Segments erfolgt über einen Ausgabebefehl + Die Umschaltung des aktiven Segments erfolgt über einen Ausgabebefehl auf E/A-Adresse FFh. Mit einem Schreibzugriff auf den Adressbereich F800h-FBFFh wird das Mega-ROM-Modul ab- und mit einem Schreibzugriff auf FC00h-FFFFh angeschaltet.

    - Der in JKCEMU enthaltene Inhalt für dieses Modul füllt + Der im JKCEMU enthaltene Inhalt für dieses Modul füllt den möglichen Speicher zwar bei weitem nicht aus, enthält jedoch trotzdem eine ganze Menge von Programmen, u.a. auch eine Verwaltungssoftware. @@ -513,6 +521,12 @@

    1.12. KCNet-kompatible Netzwerkkarte

    Netzwerkemulation.

    +

    1.12. Echtzeituhrmodul

    + Es wird eine Echtzeituhr RTC-62421 bzw. RTC-72421 an den + E/A-Adressen 60h bis 6Fh emuliert. + Die Uhr kann nur gelesen, nicht aber gestellt werden. +

    +

    2. Im ROM enthaltene Software

    @@ -541,15 +555,45 @@

    2. Im ROM enthaltene Software

    3. Enthaltene Diskettenabbilder

    + JKCEMU enthält für die KC85/1-, KC87- und Z9001-Emulation + folgendes Diskettenabbild:
    - + Für die Nutzung des Diskettenabbildes müssen Sie + mindestens folgende Konfiguration einstellen: + +
    + Das Boot-ROM-Modul meldet sich beim Start mit der Ausschrift + CP/M-System und das Mega-ROM-Modul + mit einer eigenen Oberfäche. + Egal, welches der beiden Module Sie nutzen, + nachdem die CP/A-Systemdiskette eingelegt und der Prompt zu sehen ist, + geben Sie zum Starten von CP/A den Befehl BOOT ein. +

    + Die Laufwerkszuordnung ist einfach: +
    +   A: erstes Laufwerk
    +   B: zweites Laufwerk +

    + Die Diskette selbst ist somit unter dem Laufwerk A: + ansprechbar. +

    + Auf der Diskette befinden sich auch Treiber für zwei RAM-Floppies, + 40/80-Zeichen-Umschaltung und Systemuhr. + Wenn Sie diese Treiber laden möchten, muss natürlich + die Emulation der entsprechenden Hardware aktiviert sein. +

    4. Sonstiges

    -

    4.1. Speichern eines BASIC-Programms

    +

    4.1. BASIC-Programme speichern und laden

    JKCEMU bietet eine spezielle Unterstützung für das Speichern und Laden von BASIC-Programmen. Wird ein Z9001 bzw. KC85/1 emuliert, @@ -574,7 +618,7 @@

    4.2. Einfügen von Text

    Das ist wesentlich schneller, da dadurch das Drücken der entsprechenden Tasten nicht simuliert werden muss.

    - Sollten Sie jedoch ein anderes als das in JKCEMU integrierte + Sollten Sie jedoch ein anderes als das im JKCEMU integrierte Betriebssystem verwenden, wird diese Option möglicherweise nicht mehr funktionieren, da dort die Tastaturabfrage anders gestaltet sein kann. diff --git a/src/help/zxspectrum.htm b/src/help/zxspectrum.htm index 767223b..df0dbdc 100644 --- a/src/help/zxspectrum.htm +++ b/src/help/zxspectrum.htm @@ -4,19 +4,54 @@

    Hinweise zur Emulation des ZX Spectrum

    Für den Sinclair ZX Spectrum gab es in der DDR mehrere Nachbauprojekte, z.B. KUB64, Spectral, ZX-HFO und ZX-Jena. Insbesondere der von der Erfurter Firma Hübner Elektronik - entwickelte Spectral erreichte eine größere Verbreitung, + vertriebene Spectral erreichte eine größere Verbreitung, u.a. weil er vollständig zu einem ZX Spectrum 48K kompatibel ist und auch zu einem ZX Spectrum 128K erweitert werden konnte. -

    +
    + +
    -

    1. Emulierte Hardware

    - Emuliert wird ein ZX Spectrum mit 48 KByte RAM, - Lautsprecher- und Kassettenrecorderanschluss - sowie dem Kempston Joystick Interface. -

    +

    1. Emulierte Hardware

    + Je nach Einstellung werden emuliert: + +
    -

    1.1. Tastatur

    +

    1.1. Tastatur

    Beim ZX Spectrum sind die Tasten mehrfach benutzt. Dem entsprechend gibt es auch in der Emulation mehrere Eingabemodi:

    @@ -139,6 +174,66 @@

    1.1. Tastatur

    Entf / Del
    +

    + +

    2. Sonstiges

    + +

    2.1. Sound-/Kassettenausgabe

    + Zum Hören, Ausgeben oder Aufzeichnen der Ton- bzw. Kassettenausgabe + müssen Sie eine Audio-Funktion aktivieren. + Die Funktion Töne ausgeben + unterscheidet sich dabei bei den beiden emulierten Spectrum-Modellen. + Beim ZX Spectrum 48K hören Sie nur die Töne, + die über das Bit EAR (Bit 4) + des Ausgabetors FEh ausgegeben werden. + Beim ZX Spectrum+ 128K hören Sie dagegen + die Ausgaben des Sound-Generators AY-3-8912. +

    + Die Funktionen + Daten am Audio-Ausgang ausgeben + und Sound-Datei speichern + sind bei beiden emulierten Spectrum-Modellen gleich. + Es werden sowohl die mit dem Bit MIC + (Bit 3, gewöhnlich benutzt zum Speichern auf Kassette) + als auch die mit dem Bit EAR + (Bit 4, gewöhnlich benutzt für Tonausgaben) + erzeugten Signale ausgegeben bzw. gespeichert. + Wenn Sie also einen ZX Spectrum+ 128K emulieren lassen + und darin Programme ausführen, + die zur Tonausgabe den Port FEh nutzen + (z.B. Programme für den ZX Spectrum 48K), + müssen Sie zum Hören dieser Töne die Audio-Funktion + Daten am Audio-Ausgang ausgeben + aktivieren. +

    + +

    2.2. Sound-Datei speichern

    + Beim Speichern eines BASIC-Programms in eine Sound-Datei + wird folgende Vorgehensweise empfohlen: +
      +
    1. + Kommando zum Speichern auf Kassette eingeben und + Enter drücken,
      + Es erscheint die Meldung, dass man den Kassettenrecorder starten + und anschließend eine Taste drücken soll. +
    2. +
    3. + Audio-Funktion Sound-Datei speichern + aktivieren +
    4. +
    5. + Fenster mit dem emulierten ZX Spectrum wieder aktiviern + und eine Taste drücken. +
    6. +
    7. + Nachdem der emulierte ZX Spectrum mit dem Speichern fertig ist, + die Audio-Funktion deaktivieren +
    8. +
    +
    + Bei einer anderen Vorgehensweise kann es bei langsamer Bedienung vorkommen, + dass der Emulator das Ende der Datenausgabe erkennt, + bevor Sie das Kommando zum Speichern eingegeben haben + und dadurch die Audio-Funktion automatisch beendet wird. - diff --git a/src/images/debug/bp_disabled.png b/src/images/debug/bp_disabled.png new file mode 100644 index 0000000..554a33d Binary files /dev/null and b/src/images/debug/bp_disabled.png differ diff --git a/src/images/debug/bp_log.png b/src/images/debug/bp_log.png new file mode 100644 index 0000000..78bb189 Binary files /dev/null and b/src/images/debug/bp_log.png differ diff --git a/src/images/debug/bp_stop.png b/src/images/debug/bp_stop.png new file mode 100644 index 0000000..38bb82e Binary files /dev/null and b/src/images/debug/bp_stop.png differ diff --git a/src/images/debug/bp_stop_log.png b/src/images/debug/bp_stop_log.png new file mode 100644 index 0000000..2c155f9 Binary files /dev/null and b/src/images/debug/bp_stop_log.png differ diff --git a/src/images/debug/walk.png b/src/images/debug/walk.png new file mode 100644 index 0000000..ba579dd Binary files /dev/null and b/src/images/debug/walk.png differ diff --git a/src/images/file/closetab.png b/src/images/file/close.png similarity index 100% rename from src/images/file/closetab.png rename to src/images/file/close.png diff --git a/src/jkcemu/Main.java b/src/jkcemu/Main.java index 98b88d1..b4be3ca 100644 --- a/src/jkcemu/Main.java +++ b/src/jkcemu/Main.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -12,16 +12,18 @@ import java.io.*; import java.lang.*; import java.net.URL; +import java.nio.file.*; import java.util.*; import javax.print.attribute.PrintRequestAttributeSet; import javax.swing.*; +import jkcemu.audio.AudioRecorderFrm; import jkcemu.base.*; import jkcemu.disk.*; import jkcemu.filebrowser.*; import jkcemu.image.ImageFrm; import jkcemu.programming.assembler.CmdLineAssembler; import jkcemu.programming.basic.CmdLineBasicCompiler; -import jkcemu.text.TextEditFrm; +import jkcemu.text.*; import jkcemu.tools.calculator.CalculatorFrm; import jkcemu.tools.fileconverter.FileConvertFrm; import jkcemu.tools.hexdiff.HexDiffFrm; @@ -30,10 +32,18 @@ public class Main { - public static final String VERSION = "JKCEMU Version 0.9.3"; + public static final String APPNAME = "JKCEMU"; + public static final String VERSION = "0.9.4"; + public static final String APPINFO = APPNAME + " Version " + VERSION; + + public static final int WINDOW_MASK_SCREEN = 0x01; + public static final int WINDOW_MASK_JOYSTICK = 0x02; + public static final int WINDOW_MASK_KEYBOARD = 0x04; public static PrintWriter consoleWriter = null; + private static ThreadGroup threadGroup + = new ThreadGroup( "JKCEMU thread group" ); private static String[] iconResources = { "/images/icon/jkcemu16x16.png", @@ -53,23 +63,26 @@ public class Main + " starten", " -h oder --help diese Hilfe anzeigen", " -v oder --version Versionsnummer anzeigen", + " --ar oder --audiorecorder Audio-Recorder starten", " --as oder --assembler Assembler starten", " --as -h Hilfe zum Assembler anzeigen", " --bc oder --basiccompiler BASIC-Compiler starten", " --bc -h Hilfe zum BASIC-Compiler anzeigen", " --ca oder --calculator Rechner starten", " --dc oder --diskcreator Diskettenabbilddatei erstellen", + " --dv oder --diskviewer Diskettenabbilddatei-Inspector" + + " starten", " --fb oder --filebrowser Datei-Browser starten", " --fc oder --fileconverter Dateikonverter starten", + " --ff oder --findfiles Dateisuche starten", " --hd oder --hexdiff Hex-Dateivergeicher starten", " --he oder --hexeditor Hex-Editor starten", " --iv oder --imageviewer Bildbetrachter starten", " --te oder --texteditor Texteditor starten", "" }; - private static Map images = new Hashtable(); - private static Map lastFiles = new Hashtable(); - private static Properties lastDirs = new Properties(); + private static Map images = new HashMap<>(); + private static Properties lastDirs = new Properties(); private static String lastDriveFileName = null; private static File configDir = null; @@ -86,6 +99,8 @@ public class Main private static ScreenFrm screenFrm = null; private static java.util.List iconImages = null; + private static volatile int activeWindowMask = 0; + public static void main( String[] args ) { @@ -109,9 +124,10 @@ public static void main( String[] args ) String subDirName = "jkcemu"; String baseDirName = null; try { - baseDirName = emptyToNull( System.getenv( "APPDATA" ) ); + baseDirName = TextUtil.emptyToNull( System.getenv( "APPDATA" ) ); if( baseDirName == null ) { - baseDirName = emptyToNull( System.getProperty( "user.home" ) ); + baseDirName = TextUtil.emptyToNull( + System.getProperty( "user.home" ) ); if( isUnixLikeOS() ) { subDirName = ".jkcemu"; } @@ -147,16 +163,30 @@ public static void main( String[] args ) || arg.equalsIgnoreCase( "--help" ) ) { EmuUtil.printlnOut(); - EmuUtil.printlnOut( VERSION ); + EmuUtil.printlnOut( APPINFO ); for( String s : usageLines ) { EmuUtil.printlnOut( s ); } exitSuccess(); } else if( arg.equals( "-v" ) || arg.equalsIgnoreCase( "--version" ) ) { - System.out.println( VERSION ); + System.out.println( APPINFO ); exitSuccess(); } + else if( arg.equalsIgnoreCase( "--ar" ) + || arg.equalsIgnoreCase( "--audiorecorder" ) ) + { + properties = loadProfileAndSetLAF( prfName ); + EventQueue.invokeLater( + new Runnable() + { + public void run() + { + AudioRecorderFrm.open(); + } + } ); + done = true; + } else if( arg.equals( "--as" ) || arg.equalsIgnoreCase( "--assembler" ) ) { @@ -203,6 +233,21 @@ public void run() } ); done = true; } + else if( arg.equalsIgnoreCase( "--dv" ) + || arg.equalsIgnoreCase( "--diskviewer" ) ) + { + properties = loadProfileAndSetLAF( prfName ); + final File file = getArgFile( args, argIdx ); + EventQueue.invokeLater( + new Runnable() + { + public void run() + { + DiskImgViewFrm.open( file ); + } + } ); + done = true; + } else if( arg.equalsIgnoreCase( "--fb" ) || arg.equalsIgnoreCase( "--filebrowser" ) ) { @@ -232,6 +277,32 @@ public void run() } ); done = true; } + else if( arg.equalsIgnoreCase( "--ff" ) + || arg.equalsIgnoreCase( "--findfiles" ) ) + { + properties = loadProfileAndSetLAF( prfName ); + Path path = null; + File file = getArgFile( args, argIdx ); + if( file != null ) { + try { + path = file.toPath().toAbsolutePath().normalize(); + if( !Files.isDirectory( path, LinkOption.NOFOLLOW_LINKS ) ) { + path = null; + } + } + catch( Exception ex ) {} + } + final Path path2 = path; + EventQueue.invokeLater( + new Runnable() + { + public void run() + { + FindFilesFrm.open( null, path2 ); + } + } ); + done = true; + } else if( arg.equalsIgnoreCase( "--hd" ) || arg.equalsIgnoreCase( "--hexdiff" ) ) { @@ -297,7 +368,7 @@ public void run() } else { if( arg.startsWith( "-" ) ) { EmuUtil.printlnErr(); - EmuUtil.printErr( VERSION ); + EmuUtil.printErr( APPINFO ); EmuUtil.printlnErr( ":" ); EmuUtil.printErr( arg ); EmuUtil.printlnErr( ": Unbekannte Option" ); @@ -323,7 +394,9 @@ public void run() screenFrm, "Das Verzeichnis " + configDir.getPath() + "\nkonnte nicht angelegt werden." - + "\nDadurch ist JKCEMU nur mit einigen" + + "\nDadurch ist " + + APPNAME + + " nur mit einigen" + " Einschr\u00E4nkungen lauff\u00E4hig." + "\nInsbesondere k\u00F6nnen keine Einstellungen" + " und Profile gespeichert werden." ); @@ -366,7 +439,8 @@ public void run() applyProfileToFrames( file, props, true, null ); screenFrm.lookAndFeelChanged(); if( props != null ) { - FloppyDiskStationFrm.getSharedInstance( screenFrm ).openDisks( props ); + FloppyDiskStationFrm.getSharedInstance( + screenFrm ).openDisks( props ); } else { screenFrm.applySettings( null, screenFrm.isResizable() ); screenFrm.pack(); @@ -439,20 +513,6 @@ public static void applyProfileToFrames( } - /* - * Da nicht sicher ist, ob sich zur Laufzeit die Anzahl der fuer die JavaVM - * zur Verfuegung stehenden Prozessoren bzw. -kerne aendern kann, - * wird der Wert nur einmal ermittelt und dann gemerkt. - */ - public static int availableProcessors() - { - if( nProcessors == null ) { - nProcessors = new Integer( Runtime.getRuntime().availableProcessors() ); - } - return nProcessors.intValue(); - } - - public static boolean checkQuit( Frame frm ) { boolean rv = false; @@ -500,16 +560,16 @@ public static File getConfigDir() } - public static Image getImage( Window window, String imgName ) + public static Image getImage( Component owner, String imgName ) { Image img = images.get( imgName ); if( img == null ) { - URL url = window.getClass().getResource( imgName ); + URL url = owner.getClass().getResource( imgName ); if( url != null ) { - img = window.getToolkit().createImage( url ); + img = owner.getToolkit().createImage( url ); if( img != null ) { try { - MediaTracker mt = new MediaTracker( window ); + MediaTracker mt = new MediaTracker( owner); mt.addImage( img, 0 ); mt.waitForAll(); } @@ -528,19 +588,21 @@ public static String getLastDriveFileName() } - public static File getLastPathFile( String category ) + public static File getLastDirFile( String category ) { + File file = null; + if( category != null ) { + if( category.isEmpty() ) { + category = null; + } + } if( category == null ) { - category = ""; + category = "*"; } - File file = lastFiles.get( category ); - if( file == null ) { - String s = lastDirs.getProperty( category ); - if( s != null ) { - if( !s.isEmpty() ) { - file = new File( s ); - lastFiles.put( category, file ); - } + String s = lastDirs.getProperty( category ); + if( s != null ) { + if( !s.isEmpty() ) { + file = new File( s ); } } return file; @@ -646,6 +708,12 @@ public static ScreenFrm getScreenFrm() } + public static ThreadGroup getThreadGroup() + { + return threadGroup; + } + + public static boolean isFirstExecution() { return firstExec; @@ -658,6 +726,12 @@ public static boolean isUnixLikeOS() } + public static boolean isWindowActive() + { + return (activeWindowMask != 0); + } + + public static Properties loadProperties( File file ) { Properties props = null; @@ -703,24 +777,34 @@ public static void setLastDriveFileName( String drvFileName ) public static void setLastFile( File file, String category ) { if( file != null ) { - File pathFile = file.getParentFile(); - if( pathFile != null ) { - if( category == null ) { - category = ""; - } - lastFiles.put( category, pathFile ); - if( configDir.exists() ) { - String path = pathFile.getPath(); - if( path != null ) { - lastDirs.setProperty( category, path ); - OutputStream out = null; - try { - out = new FileOutputStream( lastDirsFile ); - lastDirs.storeToXML( out, "JKCEMU Zuletzt verwendete Pfade" ); + if( !file.isDirectory() ) { + file = file.getParentFile(); + } + if( file != null ) { + String path = file.getPath(); + if( path != null ) { + if( !path.isEmpty() ) { + if( category != null ) { + if( category.isEmpty() ) { + category = null; + } } - catch( Exception ex ) {} - finally { - EmuUtil.doClose( out ); + if( category == null ) { + category = "*"; + } + lastDirs.setProperty( category, path ); + if( configDir.exists() ) { + OutputStream out = null; + try { + out = new FileOutputStream( lastDirsFile ); + lastDirs.storeToXML( + out, + APPNAME + " zuletzt verwendete Verzeichnisse" ); + } + catch( Exception ex ) {} + finally { + EmuUtil.doClose( out ); + } } } } @@ -772,10 +856,22 @@ public static void setProperty( String keyword, String value ) } + public static void setWindowActivated( int windowMask ) + { + activeWindowMask |= windowMask; + } + + + public static void setWindowDeactivated( int windowMask ) + { + activeWindowMask &= ~windowMask; + } + + public static void updIcon( Window window ) { if( iconImages == null ) { - iconImages = new ArrayList(); + iconImages = new ArrayList<>(); for( String resource : iconResources ) { URL url = window.getClass().getResource( resource ); if( url != null ) { @@ -804,17 +900,6 @@ private static File buildProfileFile( String prfName ) } - private static String emptyToNull( String text ) - { - if( text != null ) { - if( text.isEmpty() ) { - text = null; - } - } - return text; - } - - private static File getArgFile( String[] args, int pos ) { File file = null; @@ -834,7 +919,7 @@ private static java.util.List getArgFileList( String[] args, int pos ) { java.util.List list = null; if( (pos >= 0) && (pos < args.length) ) { - list = new ArrayList( args.length - pos ); + list = new ArrayList<>( args.length - pos ); for( int i = pos; i < args.length; i++ ) { String arg = args[ i ]; if( arg != null ) { diff --git a/src/jkcemu/audio/AbstractAudioFrm.java b/src/jkcemu/audio/AbstractAudioFrm.java new file mode 100644 index 0000000..05af702 --- /dev/null +++ b/src/jkcemu/audio/AbstractAudioFrm.java @@ -0,0 +1,218 @@ +/* + * (c) 2008-2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Basisklasse fuer Fenster mit Audio-Funktionen (Pegel-Anzeige) + */ + +package jkcemu.audio; + +import java.awt.EventQueue; +import java.awt.event.*; +import java.lang.*; +import javax.sound.sampled.*; +import javax.swing.*; +import jkcemu.Main; +import jkcemu.base.*; + + +public class AbstractAudioFrm extends BasicFrm +{ + protected static final int[] sampleRates = { + 96000, 48000, 44100, 32000, + 22050, 16000, 11025, 8000 }; + + protected Mixer.Info[] mixers; + protected javax.swing.Timer volumeTimer; + protected JLabel labelMixer; + protected JComboBox comboMixer; + protected JLabel labelSampleRate; + protected JComboBox comboSampleRate; + protected JProgressBar volumeBar; + + private static final int VOLUME_BAR_MAX = 1000; + + private int minVolumeLimit; + private int maxVolumeLimit; + private int minVolumeValue; + private int maxVolumeValue; + + + protected AbstractAudioFrm() + { + this.mixers = AudioSystem.getMixerInfo(); + Main.updIcon( this ); + + // Mixer + this.labelMixer = new JLabel( "Ger\u00E4t:" ); + this.comboMixer = new JComboBox<>(); + this.comboMixer.setEditable( false ); + this.comboMixer.addItem( "Standard" ); + if( this.mixers != null ) { + for( int i = 0; i < this.mixers.length; i++ ) { + String s = this.mixers[ i ].getName(); + if( s != null ) { + if( s.isEmpty() ) { + s = null; + } + } + this.comboMixer.addItem( s != null ? s : "unbekannt" ); + } + } + + // Abtastrate + this.labelSampleRate = new JLabel( "Abtastrate (Hz):" ); + this.comboSampleRate = new JComboBox<>(); + this.comboSampleRate.setEditable( false ); + this.comboSampleRate.addItem( "Standard" ); + for( int i = 0; i < this.sampleRates.length; i++ ) { + this.comboSampleRate.addItem( new Integer( this.sampleRates[ i ] ) ); + } + + // Pegel-Anzeige + this.minVolumeLimit = 0; + this.maxVolumeLimit = 255; + this.minVolumeValue = this.maxVolumeLimit; + this.maxVolumeValue = this.minVolumeLimit; + this.volumeBar = new JProgressBar( + SwingConstants.VERTICAL, + 0, + VOLUME_BAR_MAX ); + this.volumeTimer = new javax.swing.Timer( + 100, + new ActionListener() + { + public void actionPerformed( ActionEvent e ) + { + updVolumeBar(); + } + } ); + } + + + protected synchronized void setVolumeLimits( int minLimit, int maxLimit ) + { + if( minLimit < maxLimit ) { + this.minVolumeLimit = minLimit; + this.maxVolumeLimit = maxLimit; + this.maxVolumeValue = maxLimit; + this.minVolumeValue = minLimit; + this.volumeBar.setValue( 0 ); + } + } + + + protected Mixer getSelectedMixer() + { + Mixer mixer = null; + if( this.mixers != null ) { + int idx = this.comboMixer.getSelectedIndex() - 1; + if( (idx >= 0) && (idx < this.mixers.length) ) { + try { + mixer = AudioSystem.getMixer( (Mixer.Info) this.mixers[ idx ] ); + } + catch( IllegalArgumentException ex ) {} + } + } + return mixer; + } + + + protected int getSelectedSampleRate() + { + int rv = 0; + Object o = this.comboSampleRate.getSelectedItem(); + if( o != null ) { + if( o instanceof Integer ) { + rv = ((Integer) o).intValue(); + } + } + return rv; + } + + + protected void setMixerState( boolean state ) + { + this.labelMixer.setEnabled( state ); + this.comboMixer.setEnabled( state ); + } + + + protected void setSampleRateState( boolean state ) + { + this.labelSampleRate.setEnabled( state ); + this.comboSampleRate.setEnabled( state ); + } + + + protected void setVolumeBarState( boolean state ) + { + if( state ) { + this.volumeTimer.start(); + } else { + this.volumeBar.setValue( 0 ); + this.volumeTimer.stop(); + } + this.volumeBar.setEnabled( state ); + } + + + protected void showError( String errorText ) + { + if( errorText == null ) { + if( getSelectedSampleRate() > 0 ) { + errorText = "Es konnte kein Audiokanal mit den angegebenen" + + " Optionen ge\u00F6ffnet werden."; + } else { + errorText = "Es konnte kein Audiokanal ge\u00F6ffnet werden."; + } + } + BasicDlg.showErrorDlg( this, errorText ); + } + + + public synchronized void updVolume( int value ) + { + if( value < this.minVolumeValue ) { + this.minVolumeValue = value; + } + if( value > this.maxVolumeValue ) { + this.maxVolumeValue = value; + } + } + + + /* --- private Methoden --- */ + + private void updVolumeBar() + { + int barValue = 0; + int volume = 0; + synchronized( this ) { + volume = this.maxVolumeValue - this.minVolumeValue; + this.minVolumeValue = this.maxVolumeLimit; + this.maxVolumeValue = this.minVolumeLimit; + } + /* + * Logarithmische Pegelanzeige: + * Der Pegel wird auf den Bereich 0 bis 100 normiert, + * aus dem Wert plus eins der Logarithmus gebildet + * und anschliessend auf den Bereich der Anzeige skaliert. + */ + double v = (double) volume + / (double) (this.maxVolumeLimit - this.minVolumeLimit) + * 100.0; + if( v > 0.0 ) { + barValue = (int) Math.round( Math.log( 1.0 + v ) + * (double) VOLUME_BAR_MAX + / 4.6 ); // log(100) + if( barValue < 0 ) { + barValue = 0; + } else if( barValue > VOLUME_BAR_MAX ) { + barValue = VOLUME_BAR_MAX; + } + } + this.volumeBar.setValue( barValue ); + } +} diff --git a/src/jkcemu/audio/AudioDataQueue.java b/src/jkcemu/audio/AudioDataQueue.java index b5ece0d..07f4249 100644 --- a/src/jkcemu/audio/AudioDataQueue.java +++ b/src/jkcemu/audio/AudioDataQueue.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2011 Jens Mueller + * (c) 2008-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -31,27 +31,25 @@ public class AudioDataQueue extends InputStream private byte[] phaseData; private int size; private int pos; - private int len; - private int remainSamples; + private int nSamples; private boolean lastPhase; private String errorText; public AudioDataQueue( int initSize ) { - this.phaseData = new byte[ initSize ]; - this.size = 0; - this.pos = 0; - this.len = 0; - this.remainSamples = 0; - this.lastPhase = false; - this.errorText = null; + this.phaseData = new byte[ initSize ]; + this.size = 0; + this.pos = 0; + this.nSamples = 0; + this.lastPhase = false; + this.errorText = null; } - public void appendPauseFrames( int nSamples ) + public void appendPauseSamples( int nSamples ) { - this.remainSamples += nSamples; + this.nSamples += nSamples; } @@ -63,10 +61,7 @@ public String getErrorText() public int length() { - if( this.len < this.remainSamples ) { - this.len = this.remainSamples; - } - return this.len; + return this.nSamples; } @@ -88,7 +83,7 @@ public void putPhase( boolean phase ) this.phaseData[ 0 ] = (byte) (phase ? 1 : -1); this.size = 1; } - this.remainSamples++; + this.nSamples++; } } @@ -109,13 +104,13 @@ public int read() while( this.pos < this.size ) { if( this.phaseData[ this.pos ] > 0 ) { this.phaseData[ this.pos ]--; - rv = AudioOut.MAX_VALUE; + rv = AudioOut.SIGNED_VALUE_1; break; } if( this.phaseData[ this.pos ] < 0 ) { this.phaseData[ this.pos ]++; - rv = 0; + rv = AudioOut.SIGNED_VALUE_0; break; } @@ -125,8 +120,8 @@ public int read() */ this.pos++; } - if( (rv == -1) && (this.remainSamples > 0) ) { - --this.remainSamples; + if( (rv == -1) && (this.nSamples > 0) ) { + --this.nSamples; rv = 0; } return rv; diff --git a/src/jkcemu/audio/AudioFrm.java b/src/jkcemu/audio/AudioFrm.java index c11e9f6..7e5001e 100644 --- a/src/jkcemu/audio/AudioFrm.java +++ b/src/jkcemu/audio/AudioFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -25,29 +25,20 @@ public class AudioFrm - extends BasicFrm + extends AbstractAudioFrm implements - ChangeListener, DropTargetListener, Z80MaxSpeedListener { - private static final int[] sampleRates = { - 96000, 48000, 44100, 32000, - 22050, 16000, 11025, 8000 }; - private static AudioFrm instance = null; private ScreenFrm screenFrm; private EmuThread emuThread; private Z80CPU z80cpu; - private Mixer.Info[] mixers; private AudioFormat audioFmt; private AudioIO audioIO; - private FloatControl controlVolume; private File curFile; private File lastFile; - private int lastVolumeSliderValue; - private boolean lastIsTAP; private boolean maxSpeedTriggered; private volatile int usedKHz; private boolean blinkState; @@ -58,14 +49,9 @@ public class AudioFrm private JRadioButton btnSoundOut; private JRadioButton btnDataOut; private JRadioButton btnDataIn; - private JRadioButton btnSoundFileOut; - private JRadioButton btnSoundFileIn; - private JRadioButton btnTAPFileIn; - private JRadioButton btnFileLastIn; - private JLabel labelMixer; - private JComboBox comboMixer; - private JLabel labelSampleRate; - private JComboBox comboSampleRate; + private JRadioButton btnFileOut; + private JRadioButton btnFileIn; + private JRadioButton btnLastFileIn; private JLabel labelChannel; private JRadioButton btnChannel0; private JRadioButton btnChannel1; @@ -77,7 +63,6 @@ public class AudioFrm private JLabel labelProgress; private JProgressBar progressBar; private JPanel panelVolume; - private JSlider sliderVolume; private JButton btnEnable; private JButton btnDisable; private JButton btnHelp; @@ -130,70 +115,33 @@ public void run() } - public void openFile( File file, byte[] fileBytes, int offs ) + public void fireReopenLine() { - boolean hasSrc = false; - boolean tap = false; - if( fileBytes != null ) { - tap = FileInfo.isTAPHeaderAt( - fileBytes, - fileBytes.length - offs, - offs ); - hasSrc = true; - } else if( file != null ) { - if( file.isFile() ) { - String fName = file.getName(); - if( fName != null ) { - tap = fName.toLowerCase().endsWith( ".tap" ); - } - hasSrc = true; - } - } - if( hasSrc ) { - stopAudio(); - if( checkSpeed() ) { - if( tap ) { - this.btnTAPFileIn.setSelected( true ); - } else { - this.btnSoundFileIn.setSelected( true ); - } - updOptFields( tap ); - enableAudioInFile( this.usedKHz, file, fileBytes, offs, tap ); - } - } + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + reopenLine(); + } + } ); } - /* --- ChangeListener --- */ + public EmuThread getEmuThread() + { + return this.emuThread; + } - @Override - public void stateChanged( ChangeEvent e ) + + public void openFile( File file, byte[] fileBytes, int offs ) { - Object src = e.getSource(); - if( src != null ) { - if( (src == this.sliderVolume) && (this.controlVolume != null) ) { - int vMinSlider = this.sliderVolume.getMinimum(); - int vMaxSlider = this.sliderVolume.getMaximum(); - int rangeSlider = vMaxSlider - vMinSlider; - if( rangeSlider > 0 ) { - float vNorm = (float) (this.sliderVolume.getValue() - vMinSlider) - / (float) rangeSlider; - if( vNorm < 0F ) { - vNorm = 0F; - } else if( vNorm > 1F ) { - vNorm = 1F; - } - float vMinCtrl = this.controlVolume.getMinimum(); - float vMaxCtrl = this.controlVolume.getMaximum(); - float vCtrl = vMinCtrl + (vNorm * (vMaxCtrl - vMinCtrl)); - if( vCtrl < vMinCtrl ) { - vCtrl = vMinCtrl; - } else if( vCtrl > vMaxCtrl ) { - vCtrl = vMaxCtrl; - } - this.controlVolume.setValue( vCtrl ); - } - } + stopAudio(); + if( checkSpeed() ) { + this.btnFileIn.setSelected( true ); + updOptFields(); + enableAudioInFile( this.usedKHz, file, fileBytes, offs ); } } @@ -260,15 +208,8 @@ public void dropActionChanged( DropTargetDragEvent e ) @Override public void z80MaxSpeedChanged( Z80CPU cpu ) { - EventQueue.invokeLater( - new Runnable() - { - @Override - public void run() - { - maxSpeedChanged(); - } - } ); + if( this.z80cpu.getMaxSpeedKHz() != this.usedKHz ) + fireReopenLine(); } @@ -284,14 +225,12 @@ protected boolean doAction( EventObject e ) if( (src == this.btnSoundOut) || (src == this.btnDataOut) || (src == this.btnDataIn) - || (src == this.btnSoundFileOut) - || (src == this.btnSoundFileIn) - || (src == this.btnTAPFileIn) - || (src == this.btnFileLastIn) ) + || (src == this.btnFileOut) + || (src == this.btnFileIn) + || (src == this.btnLastFileIn) ) { rv = true; - updOptFields( this.btnTAPFileIn.isSelected() - || (this.btnFileLastIn.isSelected() && this.lastIsTAP) ); + updOptFields(); } else if( (src == this.btnChannel0) || (src == this.btnChannel1) ) { rv = true; @@ -356,15 +295,11 @@ private AudioFrm( ScreenFrm screenFrm ) this.mixers = AudioSystem.getMixerInfo(); this.audioFmt = null; this.audioIO = null; - this.controlVolume = null; this.curFile = null; this.lastFile = null; - this.lastIsTAP = false; this.maxSpeedTriggered = false; this.usedKHz = -1; - setTitle( "JKCEMU Audio/Kassette" ); - Main.updIcon( this ); // Fensterinhalt @@ -415,31 +350,25 @@ private AudioFrm( ScreenFrm screenFrm ) gbcFct.gridy++; panelFct.add( this.btnDataIn, gbcFct ); - this.btnSoundFileOut = new JRadioButton( "Sound-Datei speichern" ); - grpFct.add( this.btnSoundFileOut ); - this.btnSoundFileOut.addActionListener( this ); - gbcFct.gridy++; - panelFct.add( this.btnSoundFileOut, gbcFct ); - - this.btnSoundFileIn = new JRadioButton( "Sound-Datei lesen" ); - grpFct.add( this.btnSoundFileIn ); - this.btnSoundFileIn.addActionListener( this ); + this.btnFileOut = new JRadioButton( "Sound- oder Tape-Datei speichern" ); + grpFct.add( this.btnFileOut ); + this.btnFileOut.addActionListener( this ); gbcFct.gridy++; - panelFct.add( this.btnSoundFileIn, gbcFct ); + panelFct.add( this.btnFileOut, gbcFct ); - this.btnTAPFileIn = new JRadioButton( "KC-TAP-Datei lesen" ); - grpFct.add( this.btnTAPFileIn ); - this.btnTAPFileIn.addActionListener( this ); + this.btnFileIn = new JRadioButton( "Sound- oder Tape-Datei lesen" ); + grpFct.add( this.btnFileIn ); + this.btnFileIn.addActionListener( this ); gbcFct.gridy++; - panelFct.add( this.btnTAPFileIn, gbcFct ); + panelFct.add( this.btnFileIn, gbcFct ); - this.btnFileLastIn = new JRadioButton( - "Letzte Sound-/TAP-Datei (noch einmal) lesen" ); - grpFct.add( this.btnFileLastIn ); - this.btnFileLastIn.addActionListener( this ); + this.btnLastFileIn = new JRadioButton( + "Letzte Sound-/Tape-Datei (noch einmal) lesen" ); + grpFct.add( this.btnLastFileIn ); + this.btnLastFileIn.addActionListener( this ); gbcFct.insets.bottom = 5; gbcFct.gridy++; - panelFct.add( this.btnFileLastIn, gbcFct ); + panelFct.add( this.btnLastFileIn, gbcFct ); add( panelFct, gbc ); @@ -457,40 +386,18 @@ private AudioFrm( ScreenFrm screenFrm ) new Insets( 5, 5, 2, 5 ), 0, 0 ); - this.labelMixer = new JLabel( "Ger\u00E4t:" ); panelOpt.add( this.labelMixer, gbcOpt ); - this.comboMixer = new JComboBox(); - this.comboMixer.setEditable( false ); - this.comboMixer.addItem( "Standard" ); - if( this.mixers != null ) { - for( int i = 0; i < this.mixers.length; i++ ) { - String s = this.mixers[ i ].getName(); - if( s != null ) { - if( s.isEmpty() ) { - s = null; - } - } - this.comboMixer.addItem( s != null ? s : "unbekannt" ); - } - } gbcOpt.gridwidth = GridBagConstraints.REMAINDER; gbcOpt.gridx++; panelOpt.add( this.comboMixer, gbcOpt ); - this.labelSampleRate = new JLabel( "Abtastrate (Hz):" ); gbcOpt.insets.top = 2; gbcOpt.gridwidth = 1; gbcOpt.gridx = 0; gbcOpt.gridy++; panelOpt.add( this.labelSampleRate, gbcOpt ); - this.comboSampleRate = new JComboBox(); - this.comboSampleRate.setEditable( false ); - this.comboSampleRate.addItem( "Standard" ); - for( int i = 0; i < this.sampleRates.length; i++ ) { - this.comboSampleRate.addItem( String.valueOf( this.sampleRates[ i ] ) ); - } gbcOpt.gridwidth = GridBagConstraints.REMAINDER; gbcOpt.gridx++; panelOpt.add( this.comboSampleRate, gbcOpt ); @@ -636,10 +543,9 @@ private AudioFrm( ScreenFrm screenFrm ) panelLeft.add( this.btnClose, gbcLeft ); - // Lautstaerke + // Pegel-Anzeige this.panelVolume = new JPanel( new GridBagLayout() ); - this.panelVolume.setBorder( BorderFactory.createTitledBorder( - "Lautst\u00E4rke" ) ); + this.panelVolume.setBorder( BorderFactory.createTitledBorder( "Pegel" ) ); gbcLeft.fill = GridBagConstraints.BOTH; gbcLeft.weighty = 1.0; gbcLeft.insets.top = 30; @@ -654,10 +560,7 @@ private AudioFrm( ScreenFrm screenFrm ) GridBagConstraints.VERTICAL, new Insets( 5, 5, 5, 5 ), 0, 0 ); - - this.sliderVolume = new JSlider( SwingConstants.VERTICAL, 0, 100, 0 ); - this.sliderVolume.addChangeListener( this ); - this.panelVolume.add( this.sliderVolume, gbcVolume ); + this.panelVolume.add( this.volumeBar, gbcVolume ); // Blinken this.blinkState = false; @@ -674,10 +577,10 @@ public void actionPerformed( ActionEvent e ) } ); /* - * Timer, der nach dem Ende einer eingelesenen Sound- oder TAP-Datei - * Die Audio-Funktion deaktiviert + * Timer, der nach dem Ende einer eingelesenen Datei + * die Audio-Funktion deaktiviert */ - this.disableTimer = new javax.swing.Timer( + this.disableTimer = new javax.swing.Timer( 10000, new ActionListener() { @@ -688,8 +591,8 @@ public void actionPerformed( ActionEvent e ) } ); // Initialzustand + setVolumeLimits( 0, 255 ); setAudioState( false, false ); - this.lastVolumeSliderValue = -1; // nach setAudioState(...) !!! // sonstiges pack(); @@ -706,22 +609,20 @@ public void actionPerformed( ActionEvent e ) private void audioFinished() { - File file = null; - byte[] fileBytes = null; - boolean tap = false; - int speedKHz = 0; + File file = null; + byte[] fileBytes = null; + int speedKHz = 0; if( (this.audioFmt != null) && (this.audioIO != null) ) { if( this.audioIO instanceof AudioInFile ) { // Datei noch einmal oeffnen speedKHz = ((AudioInFile) this.audioIO).getSpeedKHz(); file = ((AudioInFile) this.audioIO).getFile(); fileBytes = ((AudioInFile) this.audioIO).getFileBytes(); - tap = ((AudioInFile) this.audioIO).isTAPFile(); } } doDisable(); if( (speedKHz > 0) && (file != null) ) { - if( enableAudioInFileInternal( speedKHz, file, fileBytes, 0, tap ) ) { + if( enableAudioInFileInternal( speedKHz, file, fileBytes, 0 ) ) { this.disableTimer.start(); } } @@ -771,41 +672,31 @@ private void doEnable() else if( this.btnDataIn.isSelected() ) { doEnableAudioInLine( this.usedKHz ); } - else if( this.btnSoundFileOut.isSelected() ) { + else if( this.btnFileOut.isSelected() ) { doEnableAudioOutFile( this.usedKHz ); } - else if( this.btnSoundFileIn.isSelected() ) { - doEnableAudioInFile( this.usedKHz, null, false ); + else if( this.btnFileIn.isSelected() ) { + doEnableAudioInFile( this.usedKHz, null ); } - else if( this.btnTAPFileIn.isSelected() ) { - doEnableAudioInFile( this.usedKHz, null, true ); - } - else if( this.btnFileLastIn.isSelected() ) { - doEnableAudioInFile( this.usedKHz, this.lastFile, this.lastIsTAP ); + else if( this.btnLastFileIn.isSelected() ) { + doEnableAudioInFile( this.usedKHz, this.lastFile ); } } } - private void doEnableAudioInFile( int speedKHz, File file, boolean tap ) + private void doEnableAudioInFile( int speedKHz, File file ) { if( file == null ) { - if( tap ) { - file = EmuUtil.showFileOpenDlg( + file = EmuUtil.showFileOpenDlg( this, - "KC-TAP-Datei \u00F6ffnen", - Main.getLastPathFile( "software" ), - EmuUtil.getTapFileFilter() ); - } else { - file = EmuUtil.showFileOpenDlg( - this, - "Sound-Datei \u00F6ffnen", - Main.getLastPathFile( "audio" ), - AudioUtil.getAudioInFileFilter() ); - } + "Sound- oder Tape-Datei \u00F6ffnen", + Main.getLastDirFile( "audio" ), + AudioUtil.getAudioInFileFilter(), + EmuUtil.getTapeFileFilter() ); } if( file != null ) { - enableAudioInFile( speedKHz, file, null, 0, tap ); + enableAudioInFile( speedKHz, file, null, 0 ); } } @@ -813,19 +704,19 @@ private void doEnableAudioInFile( int speedKHz, File file, boolean tap ) private void doEnableAudioInLine( int speedKHz ) { stopAudio(); - AudioIn audioIn = new AudioInLine( this.z80cpu ); - this.audioFmt = audioIn.startAudio( - getSelectedMixer(), - speedKHz, - getSampleRate() ); + AudioInLine audioInLine = new AudioInLine( this, this.z80cpu ); + this.audioFmt = audioInLine.startAudio( + getSelectedMixer(), + speedKHz, + getSelectedSampleRate() ); if( this.audioFmt != null ) { - this.audioIO = audioIn; + this.audioIO = audioInLine; updChannel(); - this.emuThread.setAudioIn( audioIn ); + this.emuThread.setAudioIn( audioInLine ); setAudioState( true, false ); } else { - audioIn.stopAudio(); - showError( audioIn.getErrorText() ); + audioInLine.stopAudio(); + showError( audioInLine.getAndClearErrorText() ); } } @@ -834,31 +725,58 @@ private void doEnableAudioOutFile( int speedKHz ) { File file = EmuUtil.showFileSaveDlg( this, - "Sound-Datei speichern", - Main.getLastPathFile( "audio" ), - AudioUtil.getAudioOutFileFilter() ); + "Sound- oder Tape-Datei speichern", + Main.getLastDirFile( "audio" ), + AudioUtil.getAudioOutFileFilter(), + EmuUtil.getCswFileFilter(), + EmuUtil.getCdtFileFilter(), + EmuUtil.getTzxFileFilter() ); if( file != null ) { - AudioFileFormat.Type fileType = AudioUtil.getAudioFileType( this, file ); - if( fileType != null ) { + AudioFileFormat.Type fileType = null; + int sampleRate = getSelectedSampleRate(); + boolean csw = false; + boolean tzx = false; + String fName = file.getName(); + if( fName != null ) { + fName = fName.toUpperCase(); + csw = fName.endsWith( ".CSW" ); + tzx = (fName.endsWith( ".CDT" ) || fName.endsWith( ".TZX" )); + } + if( tzx ) { + if( sampleRate == 0 ) { + sampleRate = 44100; + } + if( (sampleRate != 22050) && (sampleRate != 44100) ) { + tzx = false; + BasicDlg.showErrorDlg( + this, + "Bei dem Dateityp sind nur die Abtastfrequenzen\n" + + "22050 Hz und 44100 Hz m\u00F6glich." ); + } + } else if( !csw ) { + fileType = AudioUtil.getAudioFileType( this, file ); + } + if( csw || tzx || (fileType != null) ) { stopAudio(); - AudioOut audioOut = new AudioOutFile( - this.z80cpu, + AudioOutFile audioOutFile = new AudioOutFile( this, + this.z80cpu, file, fileType ); - this.audioFmt = audioOut.startAudio( null, speedKHz, getSampleRate() ); + this.audioFmt = audioOutFile.startAudio( + speedKHz, + getSelectedSampleRate() ); if( this.audioFmt != null ) { - this.audioIO = audioOut; + this.audioIO = audioOutFile; this.curFile = file; this.lastFile = file; - this.lastIsTAP = false; - this.emuThread.setAudioOut( audioOut ); + this.emuThread.setAudioOut( audioOutFile ); Main.setLastFile( file, "audio" ); - setAudioState( true, false ); + setAudioState( true, tzx ); updMonitorEnabled(); } else { - audioOut.stopAudio(); - showError( audioOut.getErrorText() ); + audioOutFile.stopAudio(); + showError( audioOutFile.getAndClearErrorText() ); } } } @@ -868,20 +786,26 @@ private void doEnableAudioOutFile( int speedKHz ) private void doEnableAudioOutLine( int speedKHz, boolean forDataTransfer ) { stopAudio(); - AudioOut audioOut = new AudioOutLine( - this.z80cpu, - this.btnSoundOut.isSelected() ); - this.audioFmt = audioOut.startAudio( + boolean soundOut = this.btnSoundOut.isSelected(); + EmuSys emuSys = this.emuThread.getEmuSys(); + if( emuSys != null ) { + AudioOutLine audioOutLine = new AudioOutLine( + this, + this.z80cpu, + soundOut ); + this.audioFmt = audioOutLine.startAudio( getSelectedMixer(), speedKHz, - getSampleRate() ); - if( this.audioFmt != null ) { - this.audioIO = audioOut; - this.emuThread.setAudioOut( audioOut ); - setAudioState( true, false ); - } else { - audioOut.stopAudio(); - showError( audioOut.getErrorText() ); + getSelectedSampleRate(), + soundOut && emuSys.supportsStereoSound() ); + if( this.audioFmt != null ) { + this.audioIO = audioOutLine; + this.emuThread.setAudioOut( audioOutLine ); + setAudioState( true, false ); + } else { + audioOutLine.stopAudio(); + showError( audioOutLine.getAndClearErrorText() ); + } } } @@ -947,11 +871,10 @@ private void enableAudioInFile( int speedKHz, File file, byte[] fileBytes, - int offs, - boolean tap ) + int offs ) { - if( !enableAudioInFileInternal( speedKHz, file, fileBytes, offs, tap ) ) { - showError( this.audioIO.getErrorText() ); + if( !enableAudioInFileInternal( speedKHz, file, fileBytes, offs ) ) { + showError( this.audioIO.getAndClearErrorText() ); } } @@ -960,28 +883,25 @@ private boolean enableAudioInFileInternal( int speedKHz, File file, byte[] fileBytes, - int offs, - boolean tap ) + int offs ) { boolean rv = false; stopAudio(); - AudioIn audioIn = new AudioInFile( - this.z80cpu, - this, - file, - fileBytes, - offs, - tap ); - this.audioFmt = audioIn.startAudio( null, speedKHz, getSampleRate() ); - this.audioIO = audioIn; + AudioInFile audioInFile = new AudioInFile( + this, + this.z80cpu, + file, + fileBytes, + offs ); + this.audioFmt = audioInFile.startAudio( speedKHz ); + this.audioIO = audioInFile; if( this.audioFmt != null ) { updChannel(); - this.emuThread.setAudioIn( audioIn ); - this.curFile = file; - this.lastFile = file; - this.lastIsTAP = tap; - Main.setLastFile( file, tap ? "software" : "audio" ); - setAudioState( true, tap ); + this.emuThread.setAudioIn( audioInFile ); + this.curFile = file; + this.lastFile = file; + Main.setLastFile( file, "audio" ); + setAudioState( true, audioInFile.isTapeFile() ); updMonitorEnabled(); this.btnPlay.requestFocus(); this.blinkTimer.restart(); @@ -991,58 +911,13 @@ private boolean enableAudioInFileInternal( } - /* - * Die Methode liefert den Wert im Bereich von 0.0 bis 1.0 - */ - private static float getNormalizedValue( FloatControl ctrl ) - { - float rv = 0F; - if( ctrl != null ) { - float range = ctrl.getMaximum() - ctrl.getMinimum(); - if( range > 0F ) { - rv = (ctrl.getValue() - ctrl.getMinimum()) / range; - if( rv < 0F ) { - rv = 0F; - } else if( rv > 1F ) { - rv = 1F; - } - } - } - return rv; - } - - - private int getSampleRate() - { - int i = this.comboSampleRate.getSelectedIndex() - 1; // 0: automatisch - return ((i >= 0) && (i < this.sampleRates.length)) ? - this.sampleRates[ i ] : 0; - } - - - private Mixer getSelectedMixer() - { - Mixer mixer = null; - if( this.mixers != null ) { - int idx = this.comboMixer.getSelectedIndex() - 1; - if( (idx >= 0) && (idx < this.mixers.length) ) { - try { - mixer = AudioSystem.getMixer( (Mixer.Info) this.mixers[ idx ] ); - } - catch( IllegalArgumentException ex ) {} - } - } - return mixer; - } - - - private void maxSpeedChanged() + private void reopenLine() { - int khz = this.z80cpu.getMaxSpeedKHz(); - if( khz != this.usedKHz ) { - boolean state = (this.audioFmt != null); - doDisable(); - if( state && (khz > 0) ) { + boolean state = (this.audioFmt != null); + doDisable(); + if( state ) { + int khz = this.z80cpu.getMaxSpeedKHz(); + if( khz > 0 ) { this.usedKHz = khz; if( this.btnSoundOut.isSelected() || this.btnDataOut.isSelected() ) @@ -1057,19 +932,21 @@ else if( this.btnDataIn.isSelected() ) { } - private void setAudioState( boolean state, boolean tap ) + private void setAudioState( boolean state, boolean tapeFile ) { if( state && (this.audioFmt != null) ) { this.labelFormat.setEnabled( true ); - if( tap ) { - this.fldFormat.setText( "KC-TAP-Datei" ); + if( tapeFile ) { + this.fldFormat.setText( "Tape-Datei" ); } else { this.fldFormat.setText( AudioUtil.getAudioFormatText( this.audioFmt ) ); } + setVolumeBarState( true ); } else { this.labelFormat.setEnabled( false ); this.fldFormat.setText( "" ); + setVolumeBarState( false ); } if( state && (this.curFile != null) ) { @@ -1115,7 +992,10 @@ private void setAudioState( boolean state, boolean tap ) } this.btnPlay.setEnabled( fileIn && pause ); this.btnPause.setEnabled( fileIn && !pause ); - updVolumeSliderEnabled( state ); + if( !state ) { + this.btnMaxSpeed.setEnabled( false ); + } + this.volumeBar.setEnabled( state ); state = !state; this.btnEnable.setEnabled( state ); @@ -1123,11 +1003,10 @@ private void setAudioState( boolean state, boolean tap ) this.btnSoundOut.setEnabled( state ); this.btnDataOut.setEnabled( state ); this.btnDataIn.setEnabled( state ); - this.btnSoundFileOut.setEnabled( state ); - this.btnSoundFileIn.setEnabled( state ); - this.btnTAPFileIn.setEnabled( state ); - this.btnFileLastIn.setEnabled( state && (this.lastFile != null) ); - updOptFields( tap ); + this.btnFileOut.setEnabled( state ); + this.btnFileIn.setEnabled( state ); + this.btnLastFileIn.setEnabled( state && (this.lastFile != null) ); + updOptFields(); } @@ -1153,20 +1032,6 @@ private void setMaxSpeed( boolean state ) } - private void showError( String errorText ) - { - if( errorText == null ) { - if( this.comboSampleRate.getSelectedIndex() > 0 ) { - errorText = "Es konnte kein Audiokanal mit den angegebenen" - + " Optionen ge\u00F6ffnet werden."; - } else { - errorText = "Es konnte kein Audiokanal ge\u00F6ffnet werden."; - } - } - BasicDlg.showErrorDlg( this, errorText ); - } - - private void stopAudio() { AudioIO audioIO = this.audioIO; @@ -1175,7 +1040,7 @@ private void stopAudio() this.emuThread.setAudioOut( null ); if( audioIO != null ) { audioIO.stopAudio(); - String errorText = audioIO.getErrorText(); + String errorText = audioIO.getAndClearErrorText(); if( errorText != null ) { BasicDlg.showErrorDlg( this, errorText ); } @@ -1233,7 +1098,6 @@ private void updMonitorEnabled() } else { audioIO.closeMonitorLine(); } - updVolumeSliderEnabled( !err && btnState ); } else { curState = audioIO.isMonitorActive(); } @@ -1252,7 +1116,7 @@ private void updMonitorEnabled() } - private void updOptFields( boolean tap ) + private void updOptFields() { AudioIO audioIO = this.audioIO; AudioFormat audioFmt = this.audioFmt; @@ -1262,17 +1126,15 @@ private void updOptFields( boolean tap ) && (this.btnSoundOut.isSelected() || this.btnDataOut.isSelected() || this.btnDataIn.isSelected())); - this.labelMixer.setEnabled( state ); - this.comboMixer.setEnabled( state ); + setMixerState( state ); // Sample-Rate state = ((audioIO == null) && (this.btnSoundOut.isSelected() || this.btnDataOut.isSelected() || this.btnDataIn.isSelected() - || this.btnSoundFileOut.isSelected())); - this.labelSampleRate.setEnabled( state ); - this.comboSampleRate.setEnabled( state ); + || this.btnFileOut.isSelected())); + setSampleRateState( state ); // Kanalauswahl state = false; @@ -1283,19 +1145,16 @@ private void updOptFields( boolean tap ) } } } else { - state = (this.btnDataIn.isSelected() - || this.btnSoundFileIn.isSelected() - || (this.btnFileLastIn.isSelected() && !tap)); + state = (this.btnDataIn.isSelected() || this.btnFileIn.isSelected()); } this.labelChannel.setEnabled( state ); this.btnChannel0.setEnabled( state ); this.btnChannel1.setEnabled( state ); // Mithoeren - state = (this.btnSoundFileOut.isSelected() - || this.btnSoundFileIn.isSelected() - || this.btnTAPFileIn.isSelected() - || this.btnFileLastIn.isSelected()); + state = (this.btnFileOut.isSelected() + || this.btnFileIn.isSelected() + || this.btnLastFileIn.isSelected()); this.btnMonitor.setEnabled( state ); } @@ -1313,63 +1172,5 @@ else if( intVal > this.progressBar.getMaximum() ) { } this.progressBar.setValue( intVal ); } - - - private void updVolumeSliderEnabled( boolean state ) - { - FloatControl control = null; - if( state && (this.audioIO != null) ) { - Control[] controls = null; - if( this.audioIO.supportsMonitor() ) { - controls = this.audioIO.getMonitorControls(); - } else { - controls = this.audioIO.getDataControls(); - } - if( controls != null ) { - if( controls.length > 0 ) { - for( int i = 0; i < controls.length; i++ ) { - if( controls[ i ] instanceof FloatControl ) { - Control.Type ctrlType = controls[ i ].getType(); - if( ctrlType != null ) { - if( ctrlType.equals( FloatControl.Type.MASTER_GAIN ) ) { - control = (FloatControl) controls[ i ]; - break; - } - else if( ctrlType.equals( FloatControl.Type.VOLUME ) ) { - control = (FloatControl) controls[ i ]; - } - } - } - } - } - } - } - this.controlVolume = control; - - int vMin = this.sliderVolume.getMinimum(); - if( this.controlVolume != null ) { - int vMax = this.sliderVolume.getMaximum(); - int v = this.lastVolumeSliderValue; - if( (v < vMin) || (v > vMax) ) { - v = vMin + Math.round( getNormalizedValue( this.controlVolume ) - * ((float) (vMax - vMin)) ); - if( v < vMin ) { - v = vMin; - } else if( v > vMax ) { - v = vMax; - } - } - this.sliderVolume.setValue( v ); - this.sliderVolume.setEnabled( true ); - this.panelVolume.setEnabled( true ); - } else { - if( this.sliderVolume.isEnabled() ) { - this.lastVolumeSliderValue = this.sliderVolume.getValue(); - } - this.sliderVolume.setValue( vMin ); - this.sliderVolume.setEnabled( false ); - this.panelVolume.setEnabled( false ); - } - } } diff --git a/src/jkcemu/audio/AudioIO.java b/src/jkcemu/audio/AudioIO.java index 93b98b9..282af78 100644 --- a/src/jkcemu/audio/AudioIO.java +++ b/src/jkcemu/audio/AudioIO.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2011 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -17,6 +17,7 @@ public abstract class AudioIO { protected Z80CPU z80cpu; + protected AudioFrm audioFrm; protected AudioFormat audioFmt; protected boolean firstCall; protected boolean progressEnabled; @@ -30,8 +31,9 @@ public abstract class AudioIO private volatile int monitorPos; - protected AudioIO( Z80CPU z80cpu ) + protected AudioIO( AudioFrm audioFrm, Z80CPU z80cpu ) { + this.audioFrm = audioFrm; this.z80cpu = z80cpu; this.firstCall = true; this.progressEnabled = false; @@ -46,14 +48,16 @@ protected AudioIO( Z80CPU z80cpu ) /* - * Die Methode wird aufgerufen, um die Anzahl der Taktzyklen - * seit dem letzten Aufruf mitzuteilen. - * Abgeleitete Klassen koennen diese Methode ueberschreiben, - * um z.B. auf eine zu lange Pause zu reagieren. + * Mit dieser Methode erfaehrt die Klasse die Anzahl + * der seit dem letzten Aufruf vergangenen Taktzyklen. + * + * Rueckgabewert: + * true: Audio-Daten verwenden + * false: Audio-Daten verwerfen */ - protected void currentDiffTStates( long diffTStates ) + protected boolean currentDiffTStates( long diffTStates ) { - // empty + return true; } @@ -63,22 +67,11 @@ public AudioFormat getAudioFormat() } - public Control[] getDataControls() + public String getAndClearErrorText() { - return null; - } - - - public String getErrorText() - { - return this.errorText; - } - - - public Control[] getMonitorControls() - { - Line line = this.monitorLine; - return line != null ? line.getControls() : null; + String text = this.errorText; + this.errorText = null; + return text; } @@ -144,10 +137,11 @@ protected void closeMonitorLine() } - public abstract AudioFormat startAudio( - Mixer mixer, - int speedKHz, - int sampleRate ); + public void reset() + { + // leer + } + public abstract void stopAudio(); diff --git a/src/jkcemu/audio/AudioIn.java b/src/jkcemu/audio/AudioIn.java index e99050b..b8a5502 100644 --- a/src/jkcemu/audio/AudioIn.java +++ b/src/jkcemu/audio/AudioIn.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2011 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -30,9 +30,9 @@ public abstract class AudioIn extends AudioIO private boolean dataSigned; - protected AudioIn( Z80CPU z80cpu ) + protected AudioIn( AudioFrm audioFrm, Z80CPU z80cpu ) { - super( z80cpu ); + super( audioFrm, z80cpu ); this.minValue = 0; this.maxValue = 0; this.selectedChannel = 0; @@ -50,6 +50,12 @@ public boolean isPause() } + public boolean isTapeFile() + { + return false; + } + + protected abstract byte[] readFrame(); @@ -69,6 +75,22 @@ protected void setAudioFormat( AudioFormat fmt ) this.dataSigned = fmt.getEncoding().equals( AudioFormat.Encoding.PCM_SIGNED ); + // Wertebereich der Pegelanzeige, + if( this.sampleSizeInBytes == 1 ) { + if( this.dataSigned ) { + this.audioFrm.setVolumeLimits( -128, 127 ); + } else { + this.audioFrm.setVolumeLimits( 0, 255 ); + } + } else { + if( this.dataSigned ) { + this.audioFrm.setVolumeLimits( -32768, 32767 ); + } else { + this.audioFrm.setVolumeLimits( 0, 65535 ); + } + } + + // Vorzeichenbit this.sampleSignMask = 0; if( this.dataSigned ) { this.sampleSignMask = 1; @@ -84,7 +106,7 @@ protected void setAudioFormat( AudioFormat fmt ) * zueinander um einen Schritt angenaehert, * um so einen dynamischen Mittelwert errechnen zu koennen. */ - this.adjustPeriodLen = (int) fmt.getFrameRate() / 256; + this.adjustPeriodLen = (int) fmt.getSampleRate() / 256; if( this.adjustPeriodLen < 1 ) { this.adjustPeriodLen = 1; } @@ -113,66 +135,74 @@ public boolean readPhase() } else { long tStates = z80cpu.getProcessedTStates(); - long diffTStates = z80cpu.calcTStatesDiff( this.lastTStates, tStates ); + long diffTStates = z80cpu.calcTStatesDiff( + this.lastTStates, + tStates ); if( diffTStates > 0 ) { - currentDiffTStates( diffTStates ); - - // bis zum naechsten auszuwertenden Samples lesen - int nSamples = (int) (diffTStates / this.tStatesPerFrame); - if( nSamples > 0 ) { - int v = 0; - int i = nSamples; - do { - v = readSample(); - if( v != -1 ) { + if( currentDiffTStates( diffTStates ) ) { + + // bis zum naechsten auszuwertenden Samples lesen + int nSamples = (int) (diffTStates / this.tStatesPerFrame); + if( nSamples > 0 ) { + int v = 0; + int i = nSamples; + do { + v = readSample(); + if( v >= 0 ) { + + // dynamische Mittelwertbestimmung + if( this.adjustPeriodCnt > 0 ) { + --this.adjustPeriodCnt; + } else { + this.adjustPeriodCnt = this.adjustPeriodLen; + if( this.minValue < this.maxValue ) { + this.minValue++; + } + if( this.maxValue > this.minValue ) { + --this.maxValue; + } + } - // dynamische Mittelwertbestimmung - if( this.adjustPeriodCnt > 0 ) { - --this.adjustPeriodCnt; - } else { - this.adjustPeriodCnt = this.adjustPeriodLen; - if( this.minValue < this.maxValue ) { - this.minValue++; + // Wenn gelesender Wert negativ ist, Zahl korrigieren + if( this.dataSigned && ((v & this.sampleSignMask) != 0) ) { + v |= ~this.sampleBitMask; } - if( this.maxValue > this.minValue ) { - --this.maxValue; + + // Minimum-/Maximum-Werte aktualisieren + if( v < this.minValue ) { + this.minValue = v; + } + else if( v > this.maxValue ) { + this.maxValue = v; } - } - // Wenn gelesender Wert negativ ist, Zahl korrigieren - if( this.dataSigned && ((v & this.sampleSignMask) != 0) ) { - v |= ~this.sampleBitMask; + // Pegelanzeige + this.audioFrm.updVolume( v ); } + } while( --i > 0 ); - // Minimum-/Maximum-Werte aktualisieren - if( v < this.minValue ) { - this.minValue = v; - } - else if( v > this.maxValue ) { - this.maxValue = v; + if( v != -1 ) { + int d = this.maxValue - this.minValue; + if( this.lastPhase ) { + if( v < this.minValue + (d / 3) ) { + this.lastPhase = false; + } + } else { + if( v > this.maxValue - (d / 3) ) { + this.lastPhase = true; + } } } - } while( --i > 0 ); - if( v != -1 ) { - int d = this.maxValue - this.minValue; - if( this.lastPhase ) { - if( v < this.minValue + (d / 3) ) { - this.lastPhase = false; - } - } else { - if( v > this.maxValue - (d / 3) ) { - this.lastPhase = true; - } - } + /* + * Anzahl der verstrichenen Taktzyklen auf den Wert + * des letzten gelesenen Samples korrigieren + */ + this.lastTStates += nSamples * this.tStatesPerFrame; } - - /* - * Anzahl der verstrichenen Taktzyklen auf den Wert - * des letzten gelesenen Samples korrigieren - */ - this.lastTStates += nSamples * this.tStatesPerFrame; + } else { + this.lastTStates = this.z80cpu.getProcessedTStates(); } } } @@ -183,6 +213,15 @@ else if( v > this.maxValue ) { /* --- private Methoden --- */ + /* + * Die Methode liest ein Sample. + * Im Rueckgabewert sind nur die betreffenden Bits gesetzt, + * die hoeherwertigen Bits sind 0. + * Dadurch ist der Rueckgabewert bei einem gueltigen Sample niemals negativ, + * auch wenn der eigentlich gelesene Wert negativ ist. + * + * Rueckgabewert = -1: kein Sample gelesen + */ private int readSample() { int value = -1; @@ -208,4 +247,3 @@ private int readSample() return value; } } - diff --git a/src/jkcemu/audio/AudioInFile.java b/src/jkcemu/audio/AudioInFile.java index 1094ede..d16c3e0 100644 --- a/src/jkcemu/audio/AudioInFile.java +++ b/src/jkcemu/audio/AudioInFile.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -14,43 +14,44 @@ import java.lang.*; import java.util.zip.GZIPInputStream; import javax.sound.sampled.*; -import jkcemu.base.EmuUtil; +import jkcemu.base.*; import jkcemu.emusys.kc85.KCAudioDataStream; +import jkcemu.emusys.zxspectrum.ZXSpectrumAudioDataStream; +import jkcemu.text.TextUtil; import z80emu.Z80CPU; public class AudioInFile extends AudioIn { - private AudioFrm audioFrm; - private File file; - private byte[] fileBytes; - private int offs; - private boolean tapFile; - private AudioInputStream audioIn; - private BufferedInputStream rawIn; - private byte[] frameBuf; - private long frameCount; - private long framePos; - private int progressStepSize; - private int progressStepCnt; - private int speedKHz; - private volatile boolean pause; + private static final int MAX_MEM_FILE_SIZE = 0x100000; + + private File file; + private byte[] fileBytes; + private int offs; + private boolean tapeFile; + private AudioInputStream audioIn; + private InputStream rawIn; + private byte[] frameBuf; + private long frameCount; + private long framePos; + private int progressStepSize; + private int progressStepCnt; + private int speedKHz; + private volatile boolean pause; public AudioInFile( - Z80CPU z80cpu, AudioFrm audioFrm, + Z80CPU z80cpu, File file, byte[] fileBytes, - int offs, - boolean tapFile ) + int offs ) { - super( z80cpu ); - this.audioFrm = audioFrm; + super( audioFrm, z80cpu ); this.file = file; this.fileBytes = fileBytes; this.offs = offs; - this.tapFile = tapFile; + this.tapeFile = false; this.audioIn = null; this.rawIn = null; this.frameBuf = null; @@ -81,74 +82,93 @@ public int getSpeedKHz() } - public boolean isTAPFile() - { - return this.tapFile; - } - - public void setPause( boolean state ) { this.pause = state; } - /* --- ueberschriebene Methoden --- */ - - @Override - public boolean isPause() - { - return this.pause; - } - - - @Override - public AudioFormat startAudio( Mixer mixer, int speedKHz, int sampleRate ) + public AudioFormat startAudio( int speedKHz ) { AudioFormat fmt = null; if( (this.audioIn == null) && (speedKHz > 0) ) { this.pause = true; this.framePos = 0; try { - if( this.tapFile ) { - if( this.fileBytes == null ) { - this.fileBytes = EmuUtil.readFile( this.file, 0x100000 ); - } - if( this.fileBytes == null ) { - throw new IOException(); + this.tapeFile = false; + if( (this.fileBytes == null) && (file != null) ) { + if( file.isFile() ) { + String fName = file.getName(); + if( fName != null ) { + fName = fName.toLowerCase(); + if( TextUtil.endsWith( fName, AudioUtil.tapeFileExtensions ) ) { + this.fileBytes = EmuUtil.readFile( + this.file, + false, + MAX_MEM_FILE_SIZE ); + } + } } + } + if( this.fileBytes != null ) { /* * Wird in der Mitte einer Multi-TAP-Datei begonnen, * soll auch die Fortschrittsanzeige in der Mitte beginnen. * Aus diesem Grund wird in dem Fall sowohl die Gesamtlaenge * als auch die Restlaenge der Multi-TAP-Datei ermittelt. */ - KCAudioDataStream ads = new KCAudioDataStream( - true, - 0, - this.fileBytes, - 0, - this.fileBytes.length - offs ); - this.frameCount = ads.getFrameLength(); - if( this.offs > 0 ) { + EmuSysAudioDataStream ads = null; + if( FileInfo.isKCTapHeaderAt( + this.fileBytes, + this.fileBytes.length - this.offs, + this.offs ) ) + { ads = new KCAudioDataStream( true, 0, + this.fileBytes, + 0, + this.fileBytes.length - this.offs ); + this.frameCount = ads.getFrameLength(); + this.framePos = 0; + if( this.offs > 0 ) { + ads = new KCAudioDataStream( + true, + 0, + this.fileBytes, + this.offs, + this.fileBytes.length - this.offs ); + this.framePos = this.frameCount - ads.getFrameLength(); + } + } else { + ads = new ZXSpectrumAudioDataStream( this.fileBytes, this.offs, - this.fileBytes.length - offs ); - this.framePos = this.frameCount - ads.getFrameLength(); + this.fileBytes.length - this.offs ); + this.frameCount = ads.getFrameLength(); + this.framePos = 0; + if( this.offs > 0 ) { + ads = new ZXSpectrumAudioDataStream( + this.fileBytes, + this.offs, + this.fileBytes.length - this.offs ); + this.framePos = this.frameCount - ads.getFrameLength(); + } + } + if( ads != null ) { if( this.framePos < 0 ) { this.framePos = 0; } - } - this.audioIn = new AudioInputStream( + this.audioIn = new AudioInputStream( ads, ads.getAudioFormat(), ads.getFrameLength() ); - } else { - this.rawIn = EmuUtil.openBufferedOptionalGZipFile( this.file ); - this.audioIn = AudioSystem.getAudioInputStream( this.rawIn ); + this.tapeFile = true; + } + } + if( this.audioIn == null ) { + this.rawIn = EmuUtil.openBufferedOptionalGZipFile( this.file ); + this.audioIn = AudioSystem.getAudioInputStream( this.rawIn ); this.frameCount = this.audioIn.getFrameLength(); } fmt = this.audioIn.getFormat(); @@ -188,6 +208,30 @@ public AudioFormat startAudio( Mixer mixer, int speedKHz, int sampleRate ) } + /* --- ueberschriebene Methoden --- */ + + @Override + public boolean isPause() + { + return this.pause; + } + + + @Override + public boolean isTapeFile() + { + return this.tapeFile; + } + + + @Override + public void reset() + { + stopAudio(); + this.audioFrm.fireFinished(); + } + + @Override public void stopAudio() { @@ -248,4 +292,3 @@ private void closeStreams() this.rawIn = null; } } - diff --git a/src/jkcemu/audio/AudioInLine.java b/src/jkcemu/audio/AudioInLine.java index cec2902..a1c52c3 100644 --- a/src/jkcemu/audio/AudioInLine.java +++ b/src/jkcemu/audio/AudioInLine.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -26,9 +26,9 @@ public class AudioInLine extends AudioIn private int maxPauseTStates; - public AudioInLine( Z80CPU z80cpu ) + public AudioInLine( AudioFrm audioFrm, Z80CPU z80cpu ) { - super( z80cpu ); + super( audioFrm, z80cpu ); this.dataLine = null; this.frameBuf = null; this.audioDataBuf = null; @@ -38,10 +38,51 @@ public AudioInLine( Z80CPU z80cpu ) } - public Control[] getDataControls() + public AudioFormat startAudio( Mixer mixer, int speedKHz, int sampleRate ) { - Line line = this.dataLine; - return line != null ? line.getControls() : null; + if( (this.dataLine == null) && (speedKHz > 0) ) { + + // Audio-Eingabekanal oeffnen + TargetDataLine line = null; + if( sampleRate > 0 ) { + line = openTargetDataLine( mixer, sampleRate ); + } else { + for( int i = 0; + (line == null) && (i < this.sampleRates.length); + i++ ) + { + line = openTargetDataLine( mixer, this.sampleRates[ i ] ); + } + } + + if( line != null ) { + this.errorText = null; + + AudioFormat fmt = line.getFormat(); + this.dataLine = line; + this.maxPauseTStates = speedKHz * 1000; // 1 Sekunde + this.tStatesPerFrame = (int) (((float) speedKHz) * 1000.0F + / fmt.getFrameRate()); + + // Buffer fuer ein Frame anlegen + this.frameBuf = new byte[ fmt.getFrameSize() ]; + + // Buffer fuer Leseoperationen anlegen + int r = Math.round( fmt.getFrameRate() ); + int n = this.dataLine.getBufferSize() / 32; + if( n > r / 2 ) { // max. 1/2 Sekunde puffern + n = r / 2; + } + if( n < 1 ) { + n = 1; + } + this.audioDataBuf = new byte[ n * this.frameBuf.length ]; + this.audioDataLen = 0; + this.audioDataPos = this.audioDataLen; + setAudioFormat( fmt ); + } + } + return this.audioFmt; } @@ -51,22 +92,22 @@ public Control[] getDataControls() * Mit dieser Methode erfaehrt die Klasse die Anzahl * der seit dem letzten Aufruf vergangenen Taktzyklen. * - * Sollte die Zeit zu gross sein, werden die im Puffer stehenden - * Audio-Daten ignoriert. + * Rueckgabewert: + * true: Audio-Daten verwenden + * false: Audio-Daten verwerfen */ @Override - protected void currentDiffTStates( long diffTStates ) + protected boolean currentDiffTStates( long diffTStates ) { + boolean rv = false; if( diffTStates > this.maxPauseTStates ) { + /* + * Sollte die Zeit zu gross sein, werden die im Puffer stehenden + * Audio-Daten ignoriert und die Mittelwertregelung initialisiert. + */ this.minValue = 0; this.maxValue = 0; - DataLine line = this.dataLine; - if( line != null ) { - line.flush(); - } - } else { - /* * Wenn Daten gelesen werden, darf das Soundsystem * auf keinen Fall auf die CPU-Emulation warten. @@ -75,7 +116,9 @@ protected void currentDiffTStates( long diffTStates ) * bis mindestens zum naechsten Soundsystemaufruf, abgeschaltet. */ this.z80cpu.setSpeedUnlimitedFor( diffTStates * 8 ); + rv = true; } + return rv; } @@ -108,53 +151,6 @@ protected byte[] readFrame() } - @Override - public AudioFormat startAudio( Mixer mixer, int speedKHz, int sampleRate ) - { - if( (this.dataLine == null) && (speedKHz > 0) ) { - - // Audio-Eingabekanal oeffnen - TargetDataLine line = null; - if( sampleRate > 0 ) { - line = openTargetDataLine( mixer, sampleRate ); - } else { - for( int i = 0; - (line == null) && (i < this.sampleRates.length); - i++ ) - { - line = openTargetDataLine( mixer, this.sampleRates[ i ] ); - } - } - - if( line != null ) { - AudioFormat fmt = line.getFormat(); - this.dataLine = line; - this.maxPauseTStates = speedKHz * 1000; // 1 Sekunde - this.tStatesPerFrame = (int) (((float) speedKHz) * 1000.0F - / fmt.getFrameRate()); - - // Buffer fuer ein Frame anlegen - this.frameBuf = new byte[ fmt.getFrameSize() ]; - - // Buffer fuer Leseoperationen anlegen - int r = Math.round( fmt.getFrameRate() ); - int n = this.dataLine.getBufferSize() / 32; - if( n > r / 2 ) { // max. 1/2 Sekunde puffern - n = r / 2; - } - if( n < 1 ) { - n = 1; - } - this.audioDataBuf = new byte[ n * this.frameBuf.length ]; - this.audioDataLen = 0; - this.audioDataPos = this.audioDataLen; - setAudioFormat( fmt ); - } - } - return this.audioFmt; - } - - @Override public void stopAudio() { @@ -224,12 +220,16 @@ private TargetDataLine openTargetDataLine( } if( line != null ) { line.open( fmt ); + line.flush(); line.start(); } } catch( Exception ex ) { DataLineCloser.closeDataLine( line ); line = null; + if( ex instanceof LineUnavailableException ) { + this.errorText = AudioUtil.ERROR_TEXT_LINE_UNAVAILABLE; + } } return line; } diff --git a/src/jkcemu/audio/AudioOut.java b/src/jkcemu/audio/AudioOut.java index 85300c1..1743294 100644 --- a/src/jkcemu/audio/AudioOut.java +++ b/src/jkcemu/audio/AudioOut.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2011 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -19,22 +19,31 @@ public abstract class AudioOut extends AudioIO { - public static final int MAX_VALUE = 220; + public static final int MAX_OUT_VALUE = 255; + public static final int MAX_USED_VALUE = MAX_OUT_VALUE * 9 / 10; + + protected static final int SIGNED_VALUE_1 = MAX_USED_VALUE / 2; + protected static final int SIGNED_VALUE_0 = -SIGNED_VALUE_1; + protected EmuThread emuThread; protected boolean enabled; - protected int maxPauseTStates; private boolean firstPhaseChange; private long lastTStates; - protected AudioOut( Z80CPU z80cpu ) + protected AudioOut( AudioFrm audioFrm, Z80CPU z80cpu ) { - super( z80cpu ); + super( audioFrm, z80cpu ); this.enabled = false; this.firstPhaseChange = false; - this.maxPauseTStates = 0; + } + + + public void fireReopenLine() + { + this.audioFrm.fireReopenLine(); } @@ -44,14 +53,24 @@ public boolean isSoundOutEnabled() } - protected abstract void writeSamples( int nSamples, boolean phase ); + protected void writeSamples( int nSamples, boolean phase ) + { + int value = (phase ? MAX_USED_VALUE : 0); + writeSamples( nSamples, value, value, value ); + } /* * Wo ein direkter Zugriff auf den Audio-Kanal notwendig ist, * muss die Methode ueberschrieben werden. + * Der Wertebereich ist unabhaengig vom konkreten AudioFormat + * 0...MAX_OUT_VALUE. */ - public void writeSamples( int nSamples, byte value ) + public void writeSamples( + int nSamples, + int monoValue, + int leftValue, + int rightValue ) { // leer } @@ -83,11 +102,12 @@ public void writePhase( boolean phase ) this.lastTStates, tStates ); if( diffTStates > 0 ) { - currentDiffTStates( diffTStates ); // Anzahl der zu erzeugenden Samples int nSamples = (int) (diffTStates / this.tStatesPerFrame); - writeSamples( nSamples, phase ); + if( currentDiffTStates( diffTStates ) ) { + writeSamples( nSamples, phase ); + } /* * Anzahl der verstrichenen Taktzyklen auf den Wert @@ -106,8 +126,9 @@ public void writePhase( boolean phase ) * Die Methode wird im CPU-Emulations-Thread aufgerufen * und schreibt synchron zur verstrichenen CPU-Taktzyklenzahl * einen Byte-Wert in den Audiokanal. + * Wertebereich: 0...MAX_OUT_VALUE */ - public void writeValue( byte value ) + public void writeValue( int monoValue, int leftValue, int rightValue ) { if( this.enabled && (this.tStatesPerFrame > 0) ) { if( this.firstCall ) { @@ -122,19 +143,18 @@ public void writeValue( byte value ) this.lastTStates, tStates ); if( diffTStates > 0 ) { - currentDiffTStates( diffTStates ); - if( tStates > this.lastTStates ) { - - // Anzahl der zu erzeugenden Samples - int nSamples = (int) (diffTStates / this.tStatesPerFrame); - writeSamples( nSamples, value ); - - /* - * Anzahl der verstrichenen Taktzyklen auf den Wert - * des letzten ausgegebenen Samples korrigieren - */ - this.lastTStates += (nSamples * this.tStatesPerFrame); + + // Anzahl der zu erzeugenden Samples + int nSamples = (int) (diffTStates / this.tStatesPerFrame); + if( currentDiffTStates( diffTStates ) ) { + writeSamples( nSamples, monoValue, leftValue, rightValue ); } + + /* + * Anzahl der verstrichenen Taktzyklen auf den Wert + * des letzten ausgegebenen Samples korrigieren + */ + this.lastTStates += (nSamples * this.tStatesPerFrame); } } } diff --git a/src/jkcemu/audio/AudioOutFile.java b/src/jkcemu/audio/AudioOutFile.java index 2ed5496..b9ab8e1 100644 --- a/src/jkcemu/audio/AudioOutFile.java +++ b/src/jkcemu/audio/AudioOutFile.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2011 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -13,74 +13,109 @@ import java.io.*; import java.lang.*; import javax.sound.sampled.*; -import jkcemu.base.EmuThread; +import jkcemu.Main; +import jkcemu.base.*; import z80emu.*; public class AudioOutFile extends AudioOut { - private AudioFrm audioFrm; + private static final int MAX_PAUSE_SECONDS = 3; + private File file; private AudioFileFormat.Type fileType; private AudioDataQueue queue; private int sampleRate; + private int maxPauseTStates; public AudioOutFile( - Z80CPU z80cpu, AudioFrm audioFrm, + Z80CPU z80cpu, File file, AudioFileFormat.Type fileType ) { - super( z80cpu ); - this.audioFrm = audioFrm; - this.file = file; - this.fileType = fileType; - this.audioFmt = null; - this.queue = null; - this.sampleRate = 0; + super( audioFrm, z80cpu ); + this.file = file; + this.fileType = fileType; + this.audioFmt = null; + this.queue = null; + this.sampleRate = 0; + this.maxPauseTStates = 0; + } + + + public AudioFormat startAudio( int speedKHz, int sampleRate ) + { + if( this.queue == null ) { + if( speedKHz > 0 ) { + if( sampleRate <= 0 ) { + sampleRate = 44100; + } + if( this.file.exists() ) { + if( !this.file.delete() ) { + this.errorText = "Bereits vorhandene Ausgabedatei" + + " kann nicht \u00FCberschrieben werden."; + } + } + if( this.errorText == null ) { + this.sampleRate = sampleRate; + this.queue = new AudioDataQueue( sampleRate * 60 ); + this.maxPauseTStates = speedKHz * MAX_PAUSE_SECONDS * 1000; + this.enabled = true; + this.audioFmt = new AudioFormat( + sampleRate, + 8, + 1, + true, + false ); + this.tStatesPerFrame = (int) (((float) speedKHz) * 1000.0F + / this.audioFmt.getSampleRate() ); + + // Fuer die Pegelanzeige gilt der Wertebereich 0...MAX_OUT_VALUE. + this.audioFrm.setVolumeLimits( 0, MAX_OUT_VALUE ); + } + } + } + return this.audioFmt; } /* --- ueberschrieben Methoden --- */ /* - * Wenn die Pause zu groess ist, - * wird das Schreiben der Sound-Datei abgebrochen. + * Mit dieser Methode erfaehrt die Klasse die Anzahl + * der seit dem letzten Aufruf vergangenen Taktzyklen. + * + * Rueckgabewert: + * true: Audio-Daten verwenden + * false: Audio-Daten verwerfen */ @Override - protected void currentDiffTStates( long diffTStates ) + protected boolean currentDiffTStates( long diffTStates ) { + boolean rv = true; + + /* + * Wenn die Pause zu groess ist, + * wird das Schreiben der Sound-Datei abgebrochen. + */ if( diffTStates > this.maxPauseTStates ) { this.enabled = false; this.audioFrm.fireFinished(); + rv = false; } + return rv; } @Override - public AudioFormat startAudio( Mixer mixer, int speedKHz, int sampleRate ) + public void reset() { - if( this.queue == null ) { - if( speedKHz > 0 ) { - if( sampleRate <= 0 ) { - sampleRate = 44100; - } - this.sampleRate = sampleRate; - this.queue = new AudioDataQueue( sampleRate * 60 ); - this.maxPauseTStates = speedKHz * 1000; // 1 Sekunde - this.enabled = true; - this.audioFmt = new AudioFormat( - sampleRate, - 8, - 1, - true, - false ); - this.tStatesPerFrame = (int) (((float) speedKHz) * 1000.0F - / this.audioFmt.getFrameRate() ); - } - } - return this.audioFmt; + closeMonitorLine(); + this.enabled = false; + this.queue = null; + this.audioFrm.fireFinished(); } @@ -89,28 +124,192 @@ public void stopAudio() { closeMonitorLine(); - AudioDataQueue queue = this.queue; this.enabled = false; - this.errorText = queue.getErrorText(); - if( (this.errorText == null) - && (queue.length() > 0) - && (this.audioFmt != null) ) - { - // Puffer leeren - try { - queue.appendPauseFrames( this.sampleRate / 10 ); - AudioUtil.write( - new AudioInputStream( queue, this.audioFmt, queue.length() ), - this.fileType, - this.file ); - } - catch( Exception ex ) { - this.errorText = "Die Sound-Datei kann nicht gespeichert werden.\n\n" + AudioDataQueue queue = this.queue; + if( queue != null ) { + this.errorText = queue.getErrorText(); + int len = queue.length(); + if( (this.errorText == null) + && (len > 0) + && (this.audioFmt != null) ) + { + // Puffer leeren + try { + if( this.fileType != null ) { + + // Audio-Datei erzeugen + queue.appendPauseSamples( this.sampleRate / 10 ); + AudioUtil.write( + new AudioInputStream( queue, this.audioFmt, len ), + this.fileType, + this.file ); + + } else { + + boolean done = false; + String fName = this.file.getName(); + if( fName != null ) { + if( fName.toLowerCase().endsWith( ".csw" ) ) { + + // CSW-Datei erzeugen + queue.appendPauseSamples( this.sampleRate / 10 ); + + boolean lastPhase = (queue.read() > 0); + OutputStream out = null; + try { + out = new BufferedOutputStream( + new FileOutputStream( this.file ) ); + + // CSW-Signatur mit Versionsnummer + EmuUtil.writeASCII( out, FileInfo.CSW_HEADER ); + out.write( 2 ); + out.write( 0 ); + + // 4 Bytes Abtastrate + out.write( this.sampleRate & 0xFF ); + out.write( (this.sampleRate >> 8) & 0xFF ); + out.write( (this.sampleRate >> 16) & 0xFF ); + out.write( this.sampleRate >> 24 ); + + // 4 Bytes Gesamtanzahl Pulse + out.write( len & 0xFF ); + out.write( (len >> 8) & 0xFF ); + out.write( (len >> 16) & 0xFF ); + out.write( len >> 24 ); + + // Kompression + out.write( 1 ); // RLE + + // Flags + out.write( lastPhase ? 0x01 : 0 ); // B0: Initial-Phase + + // Header Extension + out.write( 0 ); // keine Erweiterung + + // Encoding Application + EmuUtil.writeFixLengthASCII( out, Main.APPNAME, 16, 0 ); + + // CSW-Daten + --len; + int n = 1; + while( len > 0 ) { + boolean phase = (queue.read() > 0); + if( phase == lastPhase ) { + n++; + } else { + writeCswSampleCount( out, n ); + n = 1; + lastPhase = phase; + } + --len; + } + writeCswSampleCount( out, n ); + } + finally { + if( out != null ) { + out.close(); + } + } + done = true; + } + } + if( !done ) { + + // TZX-Datei erzeugen + int nBytes = (len + 7) / 8; + if( nBytes > 0x7FFFFF ) { + nBytes = 0x7FFFFF; + this.errorText = "Es wurden nicht alle Daten in die Datei" + + " geschrieben,\n" + + "da die mit dem Dateiformat maximal" + + " speicherbare Datenmenge erreicht wurde."; + } + OutputStream out = null; + try { + out = new BufferedOutputStream( + new FileOutputStream( this.file ) ); + + // TZX-Signatur mit Versionsnummer + EmuUtil.writeASCII( out, FileInfo.TZX_HEADER ); + out.write( 1 ); + out.write( 20 ); + + // Text Description + StringBuilder textBuf = new StringBuilder( 64 ); + textBuf.append( "File created by JKCEMU" ); + EmuThread emuThread = this.audioFrm.getEmuThread(); + if( emuThread != null ) { + EmuSys emuSys = emuThread.getEmuSys(); + if( emuSys != null ) { + String s = emuSys.getTitle(); + if( s != null ) { + if( !s.isEmpty() ) { + textBuf.append( " (" ); + textBuf.append( s ); + textBuf.append( " emulation)" ); + } + } + } + } + out.write( 0x32 ); // Block-ID + out.write( textBuf.length() + 3 ); // L-Byte Blocklaenge + out.write( 0 ); // H-Byte Blocklaenge + out.write( 1 ); // Anzahl Textabschnitte + out.write( 8 ); // Typ: Herkunft + out.write( textBuf.length() ); // Laenge Textabschnitt + EmuUtil.writeASCII( out, textBuf ); + + // Direct Recording Block + out.write( 0x15 ); // Block-ID + + // Spectrum-T-States pro Sample + out.write( this.sampleRate == 22050 ? 158 : 79 ); + out.write( 0 ); + + // anschliessende Pause in ms + out.write( 100 ); + out.write( 0 ); + + // benutzte Bits im letzten Byte + int lastBits = (nBytes * 8) - len; + out.write( (lastBits > 0) && (lastBits < 8) ? lastBits : 8 ); + + // Anzahl Datenbytes + out.write( nBytes & 0xFF ); + out.write( (nBytes >> 8) & 0xFF ); + out.write( nBytes >> 16 ); + + // Datenbytes + while( nBytes > 0 ) { + int b = 0; + int m = 0x80; + for( int i = 0; i < 8; i++ ) { + if( queue.read() > 0 ) { + b |= m; + } + m >>= 1; + } + out.write( b ); + --nBytes; + } + } + finally { + if( out != null ) { + out.close(); + } + } + } + } + } + catch( Exception ex ) { + this.file.delete(); + this.errorText = "Die Datei kann nicht gespeichert werden.\n\n" + ex.getMessage(); - } - } else { - this.errorText = "Die Sound-Datei wurde nicht gespeichert,\n" + } + } else { + this.errorText = "Die Datei wurde nicht gespeichert,\n" + "da keine Audio-Daten erzeugt wurden."; + } } this.audioFmt = null; } @@ -127,15 +326,38 @@ protected boolean supportsMonitor() protected void writeSamples( int nSamples, boolean phase ) { if( nSamples > 0 ) { - for( int i = 0; i < nSamples; i++ ) { - this.queue.putPhase( phase ); - } - if( isMonitorActive() ) { - int value = (phase ? MAX_VALUE : 0); + AudioDataQueue queue = this.queue; + if( queue != null ) { for( int i = 0; i < nSamples; i++ ) { - writeMonitorLine( value ); + queue.putPhase( phase ); + } + this.audioFrm.updVolume( phase ? MAX_USED_VALUE : 0 ); + if( isMonitorActive() ) { + int value = (phase ? SIGNED_VALUE_1 : SIGNED_VALUE_0); + for( int i = 0; i < nSamples; i++ ) { + writeMonitorLine( value ); + } } } } } + + + /* --- private Methoden --- */ + + private static void writeCswSampleCount( + OutputStream out, + int n ) throws IOException + { + if( (n & 0xFF) == n ) { + out.write( n ); + } else { + out.write( 0 ); + out.write( n & 0xFF ); + out.write( (n >> 8) & 0xFF ); + out.write( (n >> 16) & 0xFF ); + out.write( n >> 24 ); + } + } } + diff --git a/src/jkcemu/audio/AudioOutLine.java b/src/jkcemu/audio/AudioOutLine.java index f02c9ba..e014f63 100644 --- a/src/jkcemu/audio/AudioOutLine.java +++ b/src/jkcemu/audio/AudioOutLine.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2011 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -16,31 +16,75 @@ public class AudioOutLine extends AudioOut { - private static int[] sampleRatesSound = { 22050, 16000, 8000 }; - private static int[] sampleRatesData = { 44100, 32000, 22050 }; + private static int[] sampleRates = { 44100, 32000, 22050, 16000, 8000 }; private boolean isSound; private volatile SourceDataLine dataLine; - private byte[] audioDataBuf; - private int audioDataPos; + private byte[] audioBuf; + private int audioPos; + private int channels; + private int maxWaveTStates; public AudioOutLine( - Z80CPU z80cpu, - boolean isSound ) + AudioFrm audioFrm, + Z80CPU z80cpu, + boolean isSound ) { - super( z80cpu ); - this.isSound = isSound; - this.dataLine = null; - this.audioDataBuf = null; - this.audioDataPos = 0; + super( audioFrm, z80cpu ); + this.isSound = isSound; + this.dataLine = null; + this.audioBuf = null; + this.audioPos = 0; + this.channels = 0; + this.maxWaveTStates = 0; } - public Control[] getDataControls() + public AudioFormat startAudio( + Mixer mixer, + int speedKHz, + int sampleRate, + boolean stereo ) { - Line line = this.dataLine; - return line != null ? line.getControls() : null; + if( (this.dataLine == null) && (speedKHz > 0) ) { + + // Audio-Ausgabekanal oeffnen, Mono + SourceDataLine line = null; + if( sampleRate > 0 ) { + line = openSourceDataLine( mixer, sampleRate, stereo ); + } else { + for( int i = 0; (line == null) && (i < sampleRates.length); i++ ) { + line = openSourceDataLine( mixer, sampleRates[ i ], stereo ); + } + } + if( line != null ) { + this.errorText = null; + this.maxWaveTStates = speedKHz * 100; // 0.1 Sekunde + this.enabled = true; + this.dataLine = line; + this.audioFmt = this.dataLine.getFormat(); + this.channels = this.audioFmt.getChannels(); + this.tStatesPerFrame = (int) (((float) speedKHz) * 1000.0F + / this.audioFmt.getSampleRate() ); + + // Audio-Buffer anlegen + int r = Math.round( this.audioFmt.getSampleRate() ); + int n = line.getBufferSize() / 32; + if( n > r / 2 ) { // max. 1/2 Sekunde puffern + n = r / 2; + } + if( n < 1 ) { + n = 1; + } + this.audioBuf = new byte[ n * this.channels ]; + this.audioPos = 0; + + // Fuer die Pegelanzeige gilt der Wertebereich 0...MAX_OUT_VALUE. + this.audioFrm.setVolumeLimits( 0, MAX_OUT_VALUE ); + } + } + return this.audioFmt; } @@ -50,14 +94,16 @@ public Control[] getDataControls() * Mit dieser Methode erfaehrt die Klasse die Anzahl * der seit dem letzten Aufruf vergangenen Taktzyklen. * - * Sollte die Zeit zu gross sein, werden die im Puffer stehenden - * Audio-Daten ignoriert. + * Rueckgabewert: + * true: Audio-Daten verwenden + * false: Audio-Daten verwerfen */ @Override - protected void currentDiffTStates( long diffTStates ) + protected boolean currentDiffTStates( long diffTStates ) { - if( diffTStates > this.maxPauseTStates ) { - this.audioDataPos = 0; + boolean rv = false; + if( diffTStates > this.maxWaveTStates ) { + this.audioPos = 0; DataLine line = this.dataLine; if( line != null ) { line.flush(); @@ -72,10 +118,10 @@ protected void currentDiffTStates( long diffTStates ) * der CPU-Emulation temporaer, d.h., * bis mindestens zum naechsten Soundsystemaufruf, abgeschaltet. */ - if( !this.isSound ) { - this.z80cpu.setSpeedUnlimitedFor( diffTStates * 8 ); - } + this.z80cpu.setSpeedUnlimitedFor( diffTStates * 8 ); + rv = true; } + return rv; } @@ -86,47 +132,6 @@ public boolean isSoundOutEnabled() } - @Override - public AudioFormat startAudio( Mixer mixer, int speedKHz, int sampleRate ) - { - if( (this.dataLine == null) && (speedKHz > 0) ) { - - // Audio-Ausgabekanal oeffnen, Mono - SourceDataLine line = null; - if( sampleRate > 0 ) { - line = openSourceDataLine( mixer, sampleRate ); - } else { - int[] sampleRates = this.isSound ? - this.sampleRatesSound : this.sampleRatesData; - for( int i = 0; (line == null) && (i < sampleRates.length); i++ ) { - line = openSourceDataLine( mixer, sampleRates[ i ] ); - } - } - if( line != null ) { - this.maxPauseTStates = speedKHz * 1000; // 1 Sekunde - this.enabled = true; - this.dataLine = line; - this.audioFmt = this.dataLine.getFormat(); - this.tStatesPerFrame = (int) (((float) speedKHz) * 1000.0F - / this.audioFmt.getFrameRate() ); - - // Audio-Buffer anlegen - int r = Math.round( this.audioFmt.getFrameRate() ); - int n = line.getBufferSize() / 32; - if( n > r / 2 ) { // max. 1/2 Sekunde puffern - n = r / 2; - } - if( n < 1 ) { - n = 1; - } - this.audioDataBuf = new byte[ n * this.audioFmt.getFrameSize() ]; - this.audioDataPos = 0; - } - } - return this.audioFmt; - } - - @Override public void stopAudio() { @@ -134,11 +139,13 @@ public void stopAudio() SourceDataLine line = this.dataLine; if( line != null ) { - // Puffer schreiben - byte[] audioDataBuf = this.audioDataBuf; - if( audioDataBuf != null ) { - if( this.audioDataPos >= audioDataBuf.length ) { - line.write( audioDataBuf, 0, audioDataBuf.length ); + // Puffer schreiben, wenn gerade Audio-Daten ausgegeben werden + if( line.isActive() ) { + byte[] audioBuf = this.audioBuf; + if( audioBuf != null ) { + if( this.audioPos >= audioBuf.length ) { + line.write( audioBuf, 0, audioBuf.length ); + } } } } @@ -149,36 +156,51 @@ public void stopAudio() @Override - protected void writeSamples( int nSamples, boolean phase ) - { - writeSamples( nSamples, (byte) (phase ? MAX_VALUE : 0) ); - } - - - @Override - public void writeSamples( int nSamples, byte value ) + public void writeSamples( + int nSamples, + int monoValue, + int leftValue, + int rightValue ) { - SourceDataLine line = this.dataLine; - byte[] audioDataBuf = this.audioDataBuf; - if( (line != null) && (audioDataBuf != null) ) { + SourceDataLine line = this.dataLine; + byte[] audioBuf = this.audioBuf; + if( (line != null) && (audioBuf != null) && (nSamples > 0) ) { for( int i = 0; i < nSamples; i++ ) { - if( this.audioDataPos >= audioDataBuf.length ) { - line.write( audioDataBuf, 0, audioDataBuf.length ); - this.audioDataPos = 0; + if( (this.audioPos + this.channels - 1) >= audioBuf.length ) { + if( !line.isActive() ) { + line.start(); + } + line.write( audioBuf, 0, audioBuf.length ); + this.audioPos = 0; + } + if( this.channels == 2 ) { + audioBuf[ this.audioPos++ ] = (byte) (leftValue + SIGNED_VALUE_0); + audioBuf[ this.audioPos++ ] = (byte) (rightValue + SIGNED_VALUE_0); + } else { + audioBuf[ this.audioPos++ ] = (byte) (monoValue + SIGNED_VALUE_0); } - audioDataBuf[ this.audioDataPos++ ] = value; } + this.audioFrm.updVolume( this.channels == 2 ? + (leftValue + rightValue) / 2 + : monoValue ); } } /* --- private Methoden --- */ - private SourceDataLine openSourceDataLine( Mixer mixer, int sampleRate ) + private SourceDataLine openSourceDataLine( + Mixer mixer, + int sampleRate, + boolean stereo ) { - SourceDataLine line = openSourceDataLine2( mixer, sampleRate, false ); + SourceDataLine line = openSourceDataLine2( + mixer, + sampleRate, + stereo, + false ); if( line == null ) { - line = openSourceDataLine2( mixer, sampleRate, true ); + line = openSourceDataLine2( mixer, sampleRate, stereo, true ); } return line; } @@ -187,12 +209,13 @@ private SourceDataLine openSourceDataLine( Mixer mixer, int sampleRate ) private SourceDataLine openSourceDataLine2( Mixer mixer, int sampleRate, + boolean stereo, boolean bigEndian ) { AudioFormat fmt = new AudioFormat( (float) sampleRate, 8, - 1, + stereo ? 2 : 1, true, bigEndian ); @@ -210,12 +233,14 @@ private SourceDataLine openSourceDataLine2( } if( line != null ) { line.open( fmt, sampleRate / (this.isSound ? 8 : 4) ); - line.start(); } } catch( Exception ex ) { DataLineCloser.closeDataLine( line ); line = null; + if( ex instanceof LineUnavailableException ) { + this.errorText = AudioUtil.ERROR_TEXT_LINE_UNAVAILABLE; + } } return line; } diff --git a/src/jkcemu/audio/AudioPlayer.java b/src/jkcemu/audio/AudioPlayer.java index caf28f1..967ae66 100644 --- a/src/jkcemu/audio/AudioPlayer.java +++ b/src/jkcemu/audio/AudioPlayer.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -13,6 +13,7 @@ import java.lang.*; import javax.sound.sampled.*; import javax.swing.*; +import jkcemu.Main; import jkcemu.base.*; @@ -161,6 +162,7 @@ private static void play( && (title != null) ) { (new Thread( + Main.getThreadGroup(), new AudioPlayer( owner, ais, ads, file, title ), "JKCEMU audio player" )).start(); } diff --git a/src/jkcemu/audio/AudioRecorderFrm.java b/src/jkcemu/audio/AudioRecorderFrm.java new file mode 100644 index 0000000..33e30b0 --- /dev/null +++ b/src/jkcemu/audio/AudioRecorderFrm.java @@ -0,0 +1,711 @@ +/* + * (c) 2014-2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Audio Recorder + */ + +package jkcemu.audio; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.lang.*; +import java.util.EventObject; +import java.util.zip.*; +import javax.sound.sampled.*; +import javax.swing.*; +import javax.swing.event.*; +import jkcemu.Main; +import jkcemu.base.*; + + +public class AudioRecorderFrm + extends AbstractAudioFrm + implements Runnable +{ + public class DataBuf extends ByteArrayOutputStream + { + public DataBuf( int size ) + { + super( size ); + } + + public InputStream newInputStream() + { + return new ByteArrayInputStream( this.buf, 0, this.count ); + } + } + + + private final static String TEXT_START = "Start"; + private final static String TEXT_STOP = "Stop"; + private final static String DEFAULT_STATUS_TEXT = "Bereit"; + + private static AudioRecorderFrm instance = null; + + private AudioFormat audioFmt; + private Thread audioThread; + private Mixer mixer; + private DataBuf dataBuf; + private boolean dataSaved; + private boolean suppressAskDataSaved; + private volatile boolean recording; + private volatile boolean recEnabled; + private volatile long begMillis; + private int sampleRate; + private int sampleSizeInBits; + private int channels; + private volatile int dataLen; + private javax.swing.Timer timerDuration; + private JLabel labelDuration; + private JLabel labelDurationValue; + private JLabel labelSampleSize; + private JLabel labelVolume; + private JRadioButton btn8Bit; + private JRadioButton btn16Bit; + private JRadioButton btnMono; + private JRadioButton btnStereo; + private JButton btnStartStop; + private JButton btnPlay; + private JButton btnSave; + private JButton btnHelp; + private JButton btnClose; + + + public static AudioRecorderFrm open() + { + if( instance != null ) { + if( instance.getExtendedState() == Frame.ICONIFIED ) { + instance.setExtendedState( Frame.NORMAL ); + } + } else { + instance = new AudioRecorderFrm(); + } + instance.toFront(); + instance.setVisible( true ); + return instance; + } + + + /* --- Runnable --- */ + + @Override + public void run() + { + Exception errEx = null; + TargetDataLine line = null; + if( (this.sampleRate > 0) + && (this.sampleSizeInBits > 0) + && (this.channels > 0) + && (this.dataBuf != null) ) + { + GZIPOutputStream out = null; + try { + AudioFormat fmt = null; + boolean isSigned = false; + boolean lineUnavailable = false; + + /* + * Bzgl. signed / unsigned und little endian / big endian + * laesst sich ein Audio-Kanal nicht unbedingt mit allen + * Kombinationen oeffnen. + * Aus diesem Grund werden die Kombinationen durchprobiert. + */ + for( int i = 0; (line == null) && (i < 2); i++ ) { + for( int k = 0; (line == null) && (k < 2); k++ ) { + try { + isSigned = (k == 1); + fmt = new AudioFormat( + (float) this.sampleRate, + this.sampleSizeInBits, + this.channels, + isSigned, + i == 1 ); + DataLine.Info info = new DataLine.Info( + TargetDataLine.class, + fmt ); + if( this.mixer != null ) { + if( this.mixer.isLineSupported( info ) ) { + line = (TargetDataLine) this.mixer.getLine( info ); + } + } else { + if( AudioSystem.isLineSupported( info ) ) { + line = (TargetDataLine) AudioSystem.getLine( info ); + } + } + if( line != null ) { + line.open( fmt ); + line.flush(); + line.start(); + this.recording = true; + } + } + catch( Exception ex ) { + close( line ); + line = null; + fmt = null; + if( ex instanceof LineUnavailableException ) { + lineUnavailable = true; + } + } + } + } + if( (fmt != null) && (line != null) ) { + int frameSize = fmt.getFrameSize(); + if( frameSize > 0 ) { + this.dataSaved = false; + this.dataLen = 0; + this.dataBuf.reset(); + + // intern gespeicherte Daten komprimieren + out = new GZIPOutputStream( this.dataBuf ); + + /* + * Wertebereich der Pegelanzeige, + * Bei 16 Bit wird nur das hoechstwertige Byte verwendet. + */ + if( isSigned ) { + setVolumeLimits( -128, 127 ); + } else { + setVolumeLimits( 0, 255 ); + } + + /* + * Puffer fuer 100 ms anlegen und ermitteln, + * welche Bytes davon (Index) das jeweils hoechstwertige ist + */ + int bufFrameCnt = this.sampleRate / 10; + if( bufFrameCnt < 1 ) { + bufFrameCnt = 1; + } + byte[] audioBuf = new byte[ frameSize * bufFrameCnt ]; + int hiIdx = 0; + if( (frameSize > 1) && !fmt.isBigEndian() ) { + hiIdx = frameSize - 1; + } + + // Aufnahme beginnen + this.audioFmt = fmt; + this.begMillis = System.currentTimeMillis(); + while( this.recEnabled ) { + if( line.read( audioBuf, 0, audioBuf.length ) + != audioBuf.length ) + { + break; + } + out.write( audioBuf ); + this.dataLen += bufFrameCnt; + + // Anzeige aktualisieren + int idx = hiIdx; + while( idx < audioBuf.length ) { + if( isSigned ) { + updVolume( (int) audioBuf[ idx ] ); + } else { + updVolume( ((int) audioBuf[ idx ]) & 0xFF ); + } + idx += frameSize; + } + } + out.finish(); + out.close(); + } + } + if( (this.dataLen == 0) && lineUnavailable ) { + errEx = new IOException( AudioUtil.ERROR_TEXT_LINE_UNAVAILABLE ); + } + } + catch( Exception ex ) { + errEx = ex; + } + finally { + EmuUtil.doClose( out ); + close( line ); + } + } + final Exception retEx = (this.recEnabled ? errEx : null); + EventQueue.invokeLater( + new Runnable() + { + public void run() + { + recFinished( retEx ); + } + } ); + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + protected boolean doAction( EventObject e ) + { + boolean rv = false; + Object src = e.getSource(); + if( src == this.btnStartStop ) { + rv = true; + doStartStop(); + } + else if( src == this.btnPlay ) { + rv = true; + doPlay(); + } + else if( src == this.btnSave ) { + rv = true; + doSave(); + } + else if( src == this.btnHelp ) { + rv = true; + HelpFrm.open( "/help/tools/audiorecorder.htm" ); + } + else if( src == this.btnClose ) { + rv = true; + doClose(); + } + return rv; + } + + + @Override + public boolean doClose() + { + boolean rv = false; + if( this.recEnabled ) { + doStartStop(); + } + if( confirmDataSaved() ) { + rv = super.doClose(); + if( rv ) { + this.dataLen = 0; + this.dataSaved = true; + updDuration(); + Main.checkQuit( this ); + } + } + return rv; + } + + + /* --- Konstruktor --- */ + + private AudioRecorderFrm() + { + this.audioFmt = null; + this.audioThread = null; + this.mixer = null; + this.dataBuf = null; + this.suppressAskDataSaved = false; + this.dataSaved = true; + this.dataLen = 0; + this.sampleRate = 0; + this.sampleSizeInBits = 0; + this.channels = 0; + this.begMillis = -1; + this.recording = false; + this.recEnabled = false; + setTitle( "JKCEMU Audio-Recorder" ); + + + // Fensterinhalt + setLayout( new GridBagLayout() ); + + GridBagConstraints gbc = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.EAST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + // Labels + add( this.labelMixer, gbc ); + + gbc.gridy++; + add( this.labelSampleRate, gbc ); + + this.labelSampleSize = new JLabel( "Aufl\u00F6sung:" ); + gbc.gridy++; + add( this.labelSampleSize, gbc ); + + this.labelVolume = new JLabel( "Pegel:" ); + gbc.gridy += 2; + add( this.labelVolume, gbc ); + + this.labelDuration = new JLabel( "Aufgenommene Zeit:" ); + this.labelDuration.setEnabled( false ); + gbc.insets.bottom = 5; + gbc.gridy++; + add( this.labelDuration, gbc ); + + + // Mixer + gbc.anchor = GridBagConstraints.WEST; + gbc.gridy = 0; + gbc.insets.bottom = 0; + gbc.gridx++; + add( this.comboMixer, gbc ); + + + // Abtastrate + gbc.gridy++; + add( this.comboSampleRate, gbc ); + + + // Aufloesung + JPanel panelBits = new JPanel(); + panelBits.setLayout( new BoxLayout( panelBits, BoxLayout.X_AXIS ) ); + gbc.gridy++; + add( panelBits, gbc ); + + ButtonGroup grpBits = new ButtonGroup(); + + this.btn8Bit = new JRadioButton( "8 Bit", false ); + this.btn8Bit.setAlignmentX( Component.LEFT_ALIGNMENT ); + grpBits.add( this.btn8Bit ); + panelBits.add( this.btn8Bit ); + panelBits.add( Box.createRigidArea( new Dimension( 10, 0 ) ) ); + + this.btn16Bit = new JRadioButton( "16 Bit", true ); + this.btn16Bit.setAlignmentX( Component.LEFT_ALIGNMENT ); + grpBits.add( this.btn16Bit ); + panelBits.add( this.btn16Bit ); + + + // Mono/Stereo + JPanel panelChannels = new JPanel(); + panelChannels.setLayout( + new BoxLayout( panelChannels, BoxLayout.X_AXIS ) ); + gbc.gridy++; + add( panelChannels, gbc ); + + ButtonGroup grpChannels = new ButtonGroup(); + + this.btnMono = new JRadioButton( "Mono", false ); + this.btnMono.setAlignmentX( Component.LEFT_ALIGNMENT ); + grpChannels.add( this.btnMono ); + panelChannels.add( this.btnMono ); + panelChannels.add( Box.createRigidArea( new Dimension( 10, 0 ) ) ); + + this.btnStereo = new JRadioButton( "Stereo", true ); + this.btnStereo.setAlignmentX( Component.LEFT_ALIGNMENT ); + grpChannels.add( this.btnStereo ); + panelChannels.add( this.btnStereo ); + + + // Pegel + this.volumeBar.setOrientation( SwingConstants.HORIZONTAL ); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.gridy++; + add( this.volumeBar, gbc ); + + + // Dauer + this.labelDurationValue = new JLabel(); + this.labelDurationValue.setEnabled( false ); + gbc.insets.bottom = 5; + gbc.gridy++; + add( this.labelDurationValue, gbc ); + + + // Knoepfe + JPanel panelBtn = new JPanel( new GridLayout( 5, 1, 5, 5 ) ); + gbc.anchor = GridBagConstraints.NORTHEAST; + gbc.insets.left = 10; + gbc.gridheight = 6; + gbc.gridy = 0; + gbc.gridx++; + add( panelBtn, gbc ); + + this.btnStartStop = new JButton( TEXT_START ); + panelBtn.add( this.btnStartStop ); + + this.btnPlay = new JButton( "Wiedergeben..." ); + this.btnPlay.setEnabled( false ); + panelBtn.add( this.btnPlay ); + + this.btnSave = new JButton( "Speichern..." ); + this.btnSave.setEnabled( false ); + panelBtn.add( this.btnSave ); + + this.btnHelp = new JButton( "Hilfe" ); + panelBtn.add( this.btnHelp ); + + this.btnClose = new JButton( "Schlie\u00DFen" ); + panelBtn.add( this.btnClose ); + + + // sonstiges + pack(); + if( !applySettings( Main.getProperties(), false ) ) { + setScreenCentered(); + } + setResizable( false ); + updFieldsEnabled(); + + + // Listener + this.btnStartStop.addActionListener( this ); + this.btnPlay.addActionListener( this ); + this.btnSave.addActionListener( this ); + this.btnHelp.addActionListener( this ); + this.btnClose.addActionListener( this ); + + + // Timer + this.timerDuration = new javax.swing.Timer( + 1000, + new ActionListener() + { + public void actionPerformed( ActionEvent e ) + { + updDuration(); + } + } ); + } + + + /* --- Aktionen --- */ + + private void doStartStop() + { + if( this.recEnabled ) { + this.recEnabled = false; + this.btnStartStop.setText( TEXT_START ); + } else { + if( this.audioThread == null ) { + if( confirmDataSaved() ) { + this.mixer = getSelectedMixer(); + this.sampleRate = getSelectedSampleRate(); + if( this.sampleRate <= 0 ) { + this.sampleRate = 44100; + } + this.sampleSizeInBits = (this.btn8Bit.isSelected() ? 8 : 16); + this.channels = (this.btnMono.isSelected() ? 1 : 2); + if( this.dataBuf != null ) { + this.dataBuf.reset(); + } else { + this.dataBuf = new DataBuf( 0x100000 ); + } + this.dataLen = 0; + this.dataSaved = false; + this.btnPlay.setEnabled( false ); + this.btnSave.setEnabled( false ); + this.recEnabled = true; + this.audioThread = new Thread( + Main.getThreadGroup(), + this, + "JKCEMU sound recorder" ); + this.audioThread.start(); + this.labelDuration.setEnabled( true ); + this.labelDurationValue.setEnabled( true ); + this.begMillis = -1; + updDuration(); + updFieldsEnabled(); + setVolumeBarState( true ); + this.timerDuration.start(); + this.btnStartStop.setText( TEXT_STOP ); + } + } else { + this.btnStartStop.setText( TEXT_STOP ); + } + } + } + + + private void doPlay() + { + if( (this.audioFmt != null) + && (this.dataBuf != null) + && (this.dataLen > 0) ) + { + try { + AudioPlayer.play( + this, + createAudioInputStream(), + "Wiedergabe der Aufnahme..." ); + } + catch( Exception ex ) { + BasicDlg.showErrorDlg( this, ex ); + } + } + } + + + private void doSave() + { + if( (this.audioFmt != null) + && (this.dataBuf != null) + && (this.dataLen > 0) ) + { + File file = EmuUtil.showFileSaveDlg( + this, + "Sound-Datei speichern", + Main.getLastDirFile( "audio" ), + AudioUtil.getAudioOutFileFilter() ); + if( file != null ) { + AudioFileFormat.Type type = AudioUtil.getAudioFileType( this, file ); + if( type != null ) { + try { + AudioUtil.write( createAudioInputStream(), type, file ); + Main.setLastFile( file, "audio" ); + this.dataSaved = true; + } + catch( Exception ex ) { + BasicDlg.showErrorDlg( this, ex ); + } + } + } + } + } + + + /* --- private Methoden --- */ + + private void close( DataLine line ) + { + if( line != null ) { + this.recording = false; + line.stop(); + line.flush(); + line.close(); + } + } + + + private boolean confirmDataSaved() + { + boolean rv = false; + if( !this.suppressAskDataSaved + && (this.audioFmt != null) + && (this.dataBuf != null) + && (this.dataLen > 0) + && !this.dataSaved ) + { + String[] options = { "Speichern", "Verwerfen", "Abbrechen" }; + String msg = "Was soll mit der noch nicht" + + " gespeicherten Aufnahme geschehen?"; + JCheckBox btnSuppress = new JCheckBox( + "Diesen Dialog zuk\u00FCnftig nicht mehr anzeigen" ); + JOptionPane pane = new JOptionPane( + new Object[] { msg, btnSuppress }, + JOptionPane.WARNING_MESSAGE ); + pane.setWantsInput( false ); + pane.setOptions( options ); + pane.setInitialValue( options[ 0 ] ); + setState( Frame.NORMAL ); + toFront(); + pane.createDialog( + this, + "Aufnahme nicht gespeichert" ).setVisible( true ); + Object value = pane.getValue(); + if( value != null ) { + if( value.equals( options[ 0 ] ) ) { + doSave(); + rv = this.dataSaved; + this.suppressAskDataSaved = btnSuppress.isSelected(); + } + else if( value.equals( options[ 1 ] ) ) { + this.suppressAskDataSaved = btnSuppress.isSelected(); + this.dataSaved = true; + rv = true; + } + } + } else { + rv = true; + } + return rv; + } + + + private AudioInputStream createAudioInputStream() throws IOException + { + return new AudioInputStream( + new GZIPInputStream( this.dataBuf.newInputStream() ), + this.audioFmt, + this.dataLen ); + } + + + private void recFinished( Exception ex ) + { + setVolumeBarState( false ); + this.timerDuration.stop(); + this.recEnabled = false; + this.audioThread = null; + updDuration(); + updFieldsEnabled(); + boolean state = false; + if( this.dataLen > 0 ) { + this.dataSaved = false; + if( (this.audioFmt != null) && (this.dataBuf != null) ) { + state = true; + } + if( ex != null ) { + BasicDlg.showErrorDlg( this, ex ); + } + } else { + showError( ex != null ? ex.getMessage() : null ); + } + this.labelDuration.setEnabled( state ); + this.labelDurationValue.setEnabled( state ); + this.btnStartStop.setText( TEXT_START ); + this.btnPlay.setEnabled( state ); + this.btnSave.setEnabled( state ); + } + + + private void updDuration() + { + int seconds = -1; + if( this.dataBuf != null ) { + if( this.recording && (this.begMillis > 0) ) { + seconds = (int) ((System.currentTimeMillis() - this.begMillis) + / 1000); + } else { + if( this.audioFmt != null ) { + seconds = this.dataLen / (int) this.audioFmt.getSampleRate(); + } + } + } + String text = ""; + if( seconds >= 0 ) { + int minutes = seconds / 60; + seconds = seconds % 60; + if( minutes > 0 ) { + text = String.format( "%d:%02d Minuten", minutes, seconds ); + } else { + if( seconds == 1 ) { + text = "1 Sekunde"; + } else { + text = String.format( "%d Sekunden", seconds ); + } + } + this.labelDurationValue.setText( text ); + if( minutes >= 45 ) { + this.recEnabled = false; + } + } + } + + + private void updFieldsEnabled() + { + boolean state = (this.audioThread == null); + setMixerState( state ); + setSampleRateState( state ); + this.labelSampleSize.setEnabled( state ); + this.btn8Bit.setEnabled( state ); + this.btn16Bit.setEnabled( state ); + this.btnMono.setEnabled( state ); + this.btnStereo.setEnabled( state ); + + state = !state; + this.labelVolume.setEnabled( state ); + this.volumeBar.setEnabled( state ); + } +} + diff --git a/src/jkcemu/audio/AudioUtil.java b/src/jkcemu/audio/AudioUtil.java index c6844a7..c47ae1b 100644 --- a/src/jkcemu/audio/AudioUtil.java +++ b/src/jkcemu/audio/AudioUtil.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -12,7 +12,6 @@ import java.io.*; import java.lang.*; import java.util.*; -import java.util.zip.GZIPOutputStream; import javax.sound.sampled.*; import javax.swing.filechooser.FileNameExtensionFilter; import jkcemu.base.*; @@ -20,6 +19,13 @@ public class AudioUtil { + public static final String[] tapeFileExtensions = { + ".cdt", ".csw", ".tap", ".tzx" }; + + public static final String ERROR_TEXT_LINE_UNAVAILABLE = + "Der Audio-Kanal kann nicht ge\u00F6ffnet werden,\n" + + "da er bereits durch eine andere Anwendung benutzt wird."; + private static javax.swing.filechooser.FileFilter audioInFileFilter = null; private static javax.swing.filechooser.FileFilter audioOutFileFilter = null; @@ -162,6 +168,10 @@ public static String getAudioFormatText( AudioFormat fmt ) public static javax.swing.filechooser.FileFilter getAudioInFileFilter() { if( audioInFileFilter == null ) { + /* + * Es wird davon ausgegangen, + * dass die Ausgabeformate auch gelesen werden koennen. + */ String[] ext = getAudioOutFileExtensions( null, null ); if( ext != null ) { if( ext.length == 0 ) { @@ -169,6 +179,10 @@ public static javax.swing.filechooser.FileFilter getAudioInFileFilter() } } if( ext == null ) { + /* + * Sollten keine Ausgabeformate bekannt sein, + * dann die Formate fest vorgeben + */ ext = new String[] { AudioFileFormat.Type.WAVE.getExtension(), AudioFileFormat.Type.AU.getExtension(), @@ -198,7 +212,7 @@ public static String[] getAudioOutFileExtensions( } if( t != null ) { if( t.length > 0 ) { - java.util.List list = new ArrayList( t.length ); + java.util.List list = new ArrayList<>( t.length ); for( AudioFileFormat.Type tmpT : t ) { String s = tmpT.getExtension(); if( s != null ) { @@ -235,7 +249,7 @@ public static javax.swing.filechooser.FileFilter getAudioOutFileFilter() StringBuilder buf = new StringBuilder( 256 ); buf.append( "Sound-Dateien" ); appendAudioFileExtensionText( buf, 3, ext ); - audioOutFileFilter = new FileNameExtensionFilter( buf.toString(), ext ); + audioOutFileFilter = new FileFilterWithGZ( buf.toString(), ext ); } return audioOutFileFilter; } @@ -276,23 +290,15 @@ public static void write( File file ) throws IllegalArgumentException, IOException { - if( EmuUtil.isGZipFile( file ) ) { - OutputStream out = null; - GZIPOutputStream gzip = null; - try { - out = new FileOutputStream( file ); - gzip = new GZIPOutputStream( out ); - AudioSystem.write( in, fileType, gzip ); - gzip.finish(); - gzip.close(); - gzip = null; - } - finally { - EmuUtil.doClose( gzip ); - EmuUtil.doClose( out ); - } - } else { - AudioSystem.write( in, fileType, file ); + OutputStream out = null; + try { + out = EmuUtil.createOptionalGZipOutputStream( file ); + AudioSystem.write( in, fileType, out ); + out.close(); + out = null; + } + finally { + EmuUtil.doClose( out ); } } } diff --git a/src/jkcemu/audio/DataLineCloser.java b/src/jkcemu/audio/DataLineCloser.java index 2703ca3..04726d4 100644 --- a/src/jkcemu/audio/DataLineCloser.java +++ b/src/jkcemu/audio/DataLineCloser.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -14,6 +14,7 @@ import java.lang.*; import javax.sound.sampled.*; +import jkcemu.Main; public class DataLineCloser extends Thread @@ -45,21 +46,22 @@ public void run() { if( this.dataLine != null ) { try { - this.dataLine.flush(); - } - catch( Exception ex ) {} - - try { + if( this.dataLine.isActive() + && (this.dataLine instanceof SourceDataLine) ) + { + this.dataLine.drain(); + } this.dataLine.stop(); + this.dataLine.flush(); } catch( Exception ex ) {} - - try { - this.dataLine.close(); + finally { + try { + this.dataLine.close(); + } + catch( Exception ex ) {} + this.dataLine = null; } - catch( Exception ex ) {} - - this.dataLine = null; } } @@ -68,8 +70,7 @@ public void run() private DataLineCloser( DataLine dataLine ) { - super( "JKCEMU data line closer" ); + super( Main.getThreadGroup(), "JKCEMU data line closer" ); this.dataLine = dataLine; } } - diff --git a/src/jkcemu/base/AboutDlg.java b/src/jkcemu/base/AboutDlg.java index 7f2fa5b..414046c 100644 --- a/src/jkcemu/base/AboutDlg.java +++ b/src/jkcemu/base/AboutDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -12,7 +12,7 @@ import java.io.IOException; import java.lang.*; import java.net.URL; -import java.util.EventObject; +import java.util.*; import javax.swing.*; import jkcemu.Main; @@ -62,7 +62,7 @@ public AboutDlg( Window owner ) gbcGeneral.gridx++; } - JLabel label = new JLabel( Main.VERSION ); + JLabel label = new JLabel( Main.APPINFO ); label.setFont( new Font( "SansSerif", Font.BOLD, 18 ) ); gbcGeneral.insets.bottom = 0; gbcGeneral.gridheight = 1; @@ -77,7 +77,7 @@ public AboutDlg( Window owner ) gbcGeneral.insets.top = 12; gbcGeneral.gridy++; panelGeneral.add( - new JLabel( "(c) 2008-2013 Jens M\u00FCller" ), + new JLabel( "(c) 2008-2016 Jens M\u00FCller" ), gbcGeneral ); gbcGeneral.gridy++; @@ -87,7 +87,7 @@ public AboutDlg( Window owner ) gbcGeneral.gridy++; panelGeneral.add( - new JLabel( "In JKCEMU sind ROM- und Disketteninhalte enthaltenen," ), + new JLabel( "Im JKCEMU sind ROM- und Disketteninhalte enthaltenen," ), gbcGeneral ); gbcGeneral.insets.top = 0; @@ -145,6 +145,70 @@ public AboutDlg( Window owner ) } + // Tab Java + Properties props = System.getProperties(); + if( props != null ) { + int n = props.size(); + if( n > 0 ) { + java.util.List propNames = new ArrayList( n ); + try { + Enumeration keys = props.propertyNames(); + while( keys.hasMoreElements() ) { + Object o = keys.nextElement(); + if( o != null ) { + String s = o.toString(); + if( s != null ) { + propNames.add( s ); + } + } + } + } + catch( NoSuchElementException ex ) {} + if( !propNames.isEmpty() ) { + try { + Collections.sort( propNames ); + } + catch( ClassCastException ex1 ) {} + catch( IllegalArgumentException ex2 ) {} + try { + StringBuilder buf = new StringBuilder( 2048 ); + buf.append( "\n" + + "

    Eigenschaften der Java-Laufzeitumgebung

    \n" + + "\n" + + "" + + "\n" ); + for( String propName : propNames ) { + buf.append( "\n" ); + } + buf.append( "
    EigenschaftWert
    " ); + EmuUtil.appendHTML( buf, propName ); + buf.append( "" ); + String s = props.getProperty( propName ); + if( s != null ) { + s = s.replace( "\t", "\\t" ); + s = s.replace( "\r", "\\r" ); + s = s.replace( "\n", "\\n" ); + EmuUtil.appendHTML( buf, s ); + } + buf.append( "
    \n" + + "\n" ); + JEditorPane editorPane = createJEditorPane( null ); + editorPane.setContentType( "text/html" ); + editorPane.setText( buf.toString() ); + try { + editorPane.setCaretPosition( 0 ); + } + catch( IllegalArgumentException ex ) {} + JPanel panel= new JPanel( new BorderLayout() ); + panel.add( new JScrollPane( editorPane ), BorderLayout.CENTER ); + tabbedPane.addTab( "Java", panel ); + } + catch( IOException ex ) {} + } + } + } + + // Knopf this.btnOK = new JButton( "OK" ); this.btnOK.addActionListener( this ); @@ -191,7 +255,9 @@ private JEditorPane createJEditorPane( URL url ) throws IOException JEditorPane fld = new JEditorPane(); fld.setMargin( new Insets( 5, 5, 5, 5 ) ); fld.setEditable( false ); - fld.setPage( url ); + if( url != null ) { + fld.setPage( url ); + } fld.setPreferredSize( new Dimension( 1, 1 ) ); return fld; } diff --git a/src/jkcemu/base/AbstractFileWorker.java b/src/jkcemu/base/AbstractFileWorker.java new file mode 100644 index 0000000..6fc0c0a --- /dev/null +++ b/src/jkcemu/base/AbstractFileWorker.java @@ -0,0 +1,490 @@ +/* + * (c) 2014-2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Basisklasse fuer Dateibaumoperationen + */ + +package jkcemu.base; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.lang.*; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; +import javax.swing.*; +import jkcemu.Main; + + +public abstract class AbstractFileWorker + implements FileVisitor, Runnable +{ + public interface PathListener + { + public void pathsPasted( Set files ); + public void pathsRemoved( Set files ); + }; + + + protected volatile Path curParent; + protected volatile Path curPath; + protected volatile boolean cancelled; + + private Window owner; + private PathListener pathListener; + private Collection register; + private JDialog progressDlg; + private JLabel progressLabel; + private javax.swing.Timer progressTimer; + private java.util.List paths; + private volatile boolean skipAll; + private boolean failed; + private Thread thread; + private Object syncMonitor; + private Set pastedPaths; + private Set removedPaths; + + + /* + * Der Paramater files ist absichtlich nicht Generics-maessig + * typisiert, um auch die von + * Transferable.getTransferData( DataFlavor.javaFileListFlavor ) + * zurueckgelieferte Collection hier problemlos uebergeben zu koennen. + */ + protected AbstractFileWorker( + Window owner, + Collection files, + PathListener pathListener, + Collection register ) + { + this.owner = owner; + this.pathListener = pathListener; + this.register = register; + this.paths = null; + this.curParent = null; + this.curPath = null; + this.progressDlg = null; + this.progressLabel = null; + this.progressTimer = null; + this.cancelled = false; + this.failed = false; + this.skipAll = false; + this.progressDlg = null; + this.thread = null; + this.syncMonitor = new Object(); + this.pastedPaths = EmuUtil.createPathSet(); + this.removedPaths = EmuUtil.createPathSet(); + + if( files != null ) { + int n = files.size(); + if( n > 0 ) { + this.paths = new ArrayList<>( n ); + for( Object o : files ) { + if( o != null ) { + if( o instanceof Path ) { + this.paths.add( (Path) o ); + } else if( o instanceof File ) { + try { + Path path = ((File) o).toPath(); + if( path != null ) { + this.paths.add( path ); + } + } + catch( InvalidPathException ex ) { + this.failed = true; + } + } + } + } + } + } + } + + + public void cancelWork() + { + this.cancelled = true; + setProgressText( "Vorgang abgebrochen, bitte warten..." ); + Thread thread = this.thread; + if( thread != null ) { + thread.interrupt(); + } + } + + + protected void checkSrcPathsAgainstDstPath( + Path dstPath, + Collection files, + String errMsg ) throws IOException + { + for( Object f : files ) { + if( f != null ) { + try { + Path path = null; + if( f instanceof Path ) { + path = (Path) f; + } else if( f instanceof File ) { + path = ((File) f).toPath(); + } + if( path != null ) { + if( dstPath.startsWith( path ) ) { + throw new IOException( errMsg ); + } + } + } + catch( InvalidPathException ex ) {} + } + } + } + + + public static boolean checkWindowClosing( + Component owner, + final Collection workers ) + { + boolean rv = true; + if( workers != null ) { + if( !workers.isEmpty() ) { + JOptionPane pane = new JOptionPane( + "Das Fenster wird erst geschlossen, wenn die laufenden\n" + + "Datei-Operationen fertig sind" + + " oder abgebrochen wurden.\n", + JOptionPane.WARNING_MESSAGE, + JOptionPane.OK_OPTION ); + pane.setOptions( new String[] { "Fenster nicht schlie\u00DFen" } ); + final JDialog dlg = pane.createDialog( owner, "Hinweis" ); + + final javax.swing.Timer timer = new javax.swing.Timer( 100, null ); + timer.addActionListener( + new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + if( workers.isEmpty() ) { + timer.stop(); + dlg.setVisible( false ); + dlg.dispose(); + } + } + } ); + timer.start(); + + dlg.setVisible( true ); + rv = workers.isEmpty(); + } + } + return rv; + } + + + protected void pathPasted( Path path ) + { + this.pastedPaths.add( path ); + } + + + protected void pathRemoved( Path path ) + { + this.removedPaths.add( path ); + } + + + public abstract String getFileFailedMsg( String fileName ); + public abstract String getProgressDlgTitle(); + public abstract String getUncompletedWorkMsg(); + + + protected void handleError( final Path path, final IOException ex ) + { + this.failed = true; + if( !this.skipAll ) { + StringBuilder buf = new StringBuilder( 512 ); + buf.append( getFileFailedMsg( path.toString() ) ); + if( ex != null ) { + String msg = ex.getMessage(); + if( msg != null ) { + msg = msg.trim(); + if( !msg.isEmpty() ) { + msg = null; + } + } + if( msg != null ) { + buf.append( "\n\n" ); + buf.append( msg ); + } + } + switch( showJOptionPane( + buf.toString(), + JOptionPane.ERROR_MESSAGE, + "Fehler", + new String[] { + "\u00DCberspringen", + "Alle \u00FCberspringen", + "Abbrechen" } ) ) + { + case 0: + // leer + break; + case 1: + this.skipAll = true; + break; + default: + this.cancelled = true; + } + } + } + + + /* + * Die Methode wird im Worker-Thread aufgerufen + * und zeigt einen JOptionPane-Dialog an. + * + * Rueckgabewert: + * Index der ausgewaehlten Option bzw. -1, wenn der Dialog + * am Fenster geschlossen wurde + */ + protected int showJOptionPane( + final String msg, + final int msgType, + final String title, + final String[] options ) + { + final JOptionPane pane = new JOptionPane( msg, msgType ); + pane.setOptions( options ); + synchronized( this.syncMonitor ) { + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + pane.createDialog( owner, title ).setVisible( true ); + synchronized( syncMonitor ) { + try { + syncMonitor.notifyAll(); + } + catch( IllegalMonitorStateException ex ) {} + } + } + } ); + try { + this.syncMonitor.wait(); + } + catch( IllegalMonitorStateException ex1 ) {} + catch( InterruptedException ex2 ) { + this.cancelled = true; + } + } + int rv = -1; + Object value = pane.getValue(); + if( value != null ) { + for( int i = 0; i < options.length; i++ ) { + if( value.equals( options[ i ] ) ) { + rv = i; + break; + } + } + } + return rv; + } + + + public void startWork() + { + if( (this.thread == null) + && (this.progressDlg == null) + && (this.paths != null) ) + { + if( this.register != null ) { + this.register.add( this ); + } + this.progressLabel = new JLabel( "In Arbeit..." ); + this.progressLabel.setBorder( + BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); + this.progressLabel.setAlignmentX( Component.CENTER_ALIGNMENT ); + this.progressLabel.setAlignmentY( Component.CENTER_ALIGNMENT ); + + JButton cancelBtn = new JButton( "Abbrechen" ); + cancelBtn.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); + cancelBtn.setAlignmentX( Component.CENTER_ALIGNMENT ); + cancelBtn.setAlignmentY( Component.CENTER_ALIGNMENT ); + cancelBtn.addActionListener( + new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + cancelWork(); + } + } ); + + this.progressDlg = new JDialog( + this.owner, + getProgressDlgTitle(), + Dialog.ModalityType.MODELESS ); + + this.progressDlg.setDefaultCloseOperation( + WindowConstants.DO_NOTHING_ON_CLOSE ); + + Container contentPane = this.progressDlg.getContentPane(); + contentPane.setLayout( + new BoxLayout( contentPane, BoxLayout.Y_AXIS ) ); + contentPane.add( this.progressLabel ); + contentPane.add( cancelBtn ); + + this.progressDlg.pack(); + BasicDlg.setParentCentered( this.progressDlg ); + this.progressDlg.setVisible( true ); + + this.progressTimer = new javax.swing.Timer( + 100, + new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + updProgressDlg(); + } + } ); + this.progressTimer.start(); + + this.thread = new Thread( + Main.getThreadGroup(), + this, + "JKCEMU file worker" ); + this.thread.start(); + } + } + + + /* --- FileVisistor --- */ + + @Override + public FileVisitResult postVisitDirectory( Path dir, IOException ex ) + { + return this.cancelled ? + FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } + + + @Override + public FileVisitResult preVisitDirectory( + Path dir, + BasicFileAttributes attrs ) + { + return this.cancelled ? + FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } + + + @Override + public FileVisitResult visitFile( Path file, BasicFileAttributes attrs ) + { + return this.cancelled ? + FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } + + + @Override + public FileVisitResult visitFileFailed( Path file, IOException ex ) + { + return this.cancelled ? + FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } + + + /* --- Runnable --- */ + + @Override + public void run() + { + try { + if( this.paths != null ) { + for( Path path : this.paths ) { + if( this.cancelled ) { + break; + } + try { + this.curParent = path.getParent(); + Files.walkFileTree( path, this ); + } + catch( IOException ex ) { + handleError( path, ex ); + } + } + this.curParent = null; + this.curPath = null; + } + } + finally { + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + workFinished(); + } + } ); + } + } + + + /* --- private Methoden --- */ + + private void setProgressText( String text ) + { + if( text != null ) { + JDialog dlg = this.progressDlg; + JLabel label = this.progressLabel; + if( (dlg != null) && (label != null) ) { + label.setText( text ); + dlg.pack(); + } + } + } + + + private void updProgressDlg() + { + if( this.cancelled ) { + if( this.progressTimer != null ) { + this.progressTimer.stop(); + } + } else { + Path path = this.curPath; + if( path != null ) { + setProgressText( path.toString() ); + } + } + } + + + private void workFinished() + { + if( this.register != null ) { + this.register.remove( this ); + } + if( this.pathListener != null ) { + if( !this.pastedPaths.isEmpty() ) { + this.pathListener.pathsPasted( this.pastedPaths ); + } + if( !this.removedPaths.isEmpty() ) { + this.pathListener.pathsRemoved( this.removedPaths ); + } + } + if( this.progressDlg != null ) { + this.progressDlg.setVisible( false ); + this.progressDlg.dispose(); + this.progressDlg = null; + } + if( this.failed && !this.cancelled ) { + BasicDlg.showErrorDlg( owner, getUncompletedWorkMsg() ); + } + } +} + diff --git a/src/jkcemu/base/AbstractKeyboardFld.java b/src/jkcemu/base/AbstractKeyboardFld.java index 6c1301b..ec53d7c 100644 --- a/src/jkcemu/base/AbstractKeyboardFld.java +++ b/src/jkcemu/base/AbstractKeyboardFld.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -86,7 +86,7 @@ protected AbstractKeyboardFld( { this.emuSys = emuSys; this.keys = new KeyData[ numKeys ]; - this.selectedKeys = new ArrayList(); + this.selectedKeys = new ArrayList<>(); this.shiftKeys = null; this.holdShift = true; this.lastShiftPressed = false; diff --git a/src/jkcemu/base/AbstractSettingsFld.java b/src/jkcemu/base/AbstractSettingsFld.java index 33b6515..09de52a 100644 --- a/src/jkcemu/base/AbstractSettingsFld.java +++ b/src/jkcemu/base/AbstractSettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2013 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -105,15 +105,15 @@ protected File selectFile( File oldFile, javax.swing.filechooser.FileFilter... fileFilters ) { - File rv = null; - Frame frm = EmuUtil.getAncestorFrame( this ); - if( frm != null ) { + File rv = null; + Window window = EmuUtil.getWindow( this ); + if( window != null ) { File file = EmuUtil.showFileOpenDlg( - frm, + window, title, oldFile != null ? oldFile - : Main.getLastPathFile( category ), + : Main.getLastDirFile( category ), fileFilters ); if( file != null ) { String msg = null; diff --git a/src/jkcemu/base/AbstractThreadDlg.java b/src/jkcemu/base/AbstractThreadDlg.java index ce17033..c78d6ab 100644 --- a/src/jkcemu/base/AbstractThreadDlg.java +++ b/src/jkcemu/base/AbstractThreadDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2012 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -21,10 +21,10 @@ public abstract class AbstractThreadDlg extends BasicDlg implements Runnable { protected boolean canceled; + protected int errorCount; private Thread thread; private boolean autoClose; - private int errorCount; private JTextArea fldLog; private JProgressBar progressBar; private JButton btnClose; @@ -38,8 +38,8 @@ protected AbstractThreadDlg( super( owner, Dialog.ModalityType.MODELESS ); this.autoClose = true; this.canceled = false; - this.thread = new Thread( this, threadName ); this.errorCount = 0; + this.thread = new Thread( Main.getThreadGroup(), this, threadName ); // Fensterinhalt @@ -124,6 +124,14 @@ protected void appendErrorToLog( Object errObj ) } buf.append( (char) '\n' ); appendToLog( buf.toString() ); + this.autoClose = false; + } + + + protected void appendIgnoredToLog() + { + appendToLog( " Ignoriert\n" ); + this.autoClose = false; } @@ -151,14 +159,6 @@ protected void disableAutoClose() } - protected void fireDirectoryChanged( File dirFile ) - { - ScreenFrm screenFrm = Main.getScreenFrm(); - if( screenFrm != null ) - screenFrm.fireDirectoryChanged( dirFile ); - } - - protected abstract void doProgress(); @@ -237,4 +237,3 @@ private void progressFinished() } } } - diff --git a/src/jkcemu/base/AutoInputDocument.java b/src/jkcemu/base/AutoInputDocument.java new file mode 100644 index 0000000..db54ac9 --- /dev/null +++ b/src/jkcemu/base/AutoInputDocument.java @@ -0,0 +1,165 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Dokument fuer den Eingabetext eines AutoInput-Eintrag + */ + +package jkcemu.base; + +import java.lang.*; +import java.util.*; +import javax.swing.text.*; + + +public class AutoInputDocument extends PlainDocument +{ + private static char[][] codeMapping = { + { '\u0003', '\u21B1' }, + { '\u0008', '\u2190' }, + { '\t', '\u2192' }, + { '\n', '\u2193' }, + { '\u000B', '\u2191' }, + { '\r', '\u21B5' } }; + + private static Map raw2Visible = null; + private static Map visible2Raw = null; + + private boolean swapCase; + + + public AutoInputDocument( boolean swapCase ) + { + this.swapCase = swapCase; + } + + + public static String toRawText( String text ) + { + String rv = null; + if( text != null ) { + int len = text.length(); + if( len > 0 ) { + char[] a = new char[ len ]; + for( int i = 0; i < len; i++ ) { + char ch = text.charAt( i ); + Character ch1 = getVisible2RawMap().get( ch ); + if( ch1 != null ) { + ch = ch1.charValue(); + } + a[ i ] = ch; + } + rv = new String( a ); + } else { + rv = ""; + } + } + return rv; + } + + + public static String toVisibleText( String text ) + { + String rv = null; + if( text != null ) { + int len = text.length(); + if( len > 0 ) { + char[] a = new char[ len ]; + for( int i = 0; i < len; i++ ) { + char ch = text.charAt( i ); + Character ch1 = getRaw2VisibleMap().get( ch ); + if( ch1 != null ) { + ch = ch1.charValue(); + } + a[ i ] = ch; + } + rv = new String( a ); + } else { + rv = ""; + } + } + return rv; + } + + + public boolean getSwapCase() + { + return this.swapCase; + } + + + public void setSwapCase( boolean state ) + { + this.swapCase = state; + } + + + /* --- ueberschriebene Methoden --- */ + + /* + * nur ASCII-Zeichen sowie im Mapping + * eingetragene sichtbare Zeichen zulassen + */ + @Override + public synchronized void insertString( + int offs, + String s, + AttributeSet a ) throws BadLocationException + { + if( s != null ) { + int len = s.length(); + if( len > 0 ) { + StringBuilder buf = new StringBuilder( len ); + for( int i = 0; i < len; i++ ) { + char ch = s.charAt( i ); + if( (ch >= '\u0020') && (ch <= '\u007E') ) { + if( this.swapCase ) { + if( (ch >= 'A') && (ch <= 'Z') ) { + ch = Character.toLowerCase( ch ); + } else if( (ch >= 'a') && (ch <= 'z') ) { + ch = Character.toUpperCase( ch ); + } + } + buf.append( ch ); + } else if( getVisible2RawMap().containsKey( ch ) ) { + buf.append( ch ); + } + } + if( buf.length() > 0 ) { + super.insertString( offs, buf.toString(), a ); + } + } + } + } + + + /* --- private Methoden --- */ + + private static Map getRaw2VisibleMap() + { + synchronized( AutoInputDocument.class ) { + if( raw2Visible == null ) { + raw2Visible = new HashMap<>(); + for( int i = 0; i < codeMapping.length; i++ ) { + raw2Visible.put( codeMapping[ i ][ 0 ], codeMapping[ i ][ 1 ] ); + } + } + } + return raw2Visible; + } + + + private static Map getVisible2RawMap() + { + synchronized( AutoInputDocument.class ) { + if( visible2Raw == null ) { + visible2Raw = new HashMap<>(); + for( int i = 0; i < codeMapping.length; i++ ) { + visible2Raw.put( codeMapping[ i ][ 1 ], codeMapping[ i ][ 0 ] ); + } + } + } + return visible2Raw; + } +} diff --git a/src/jkcemu/base/AutoInputEntry.java b/src/jkcemu/base/AutoInputEntry.java new file mode 100644 index 0000000..153e73d --- /dev/null +++ b/src/jkcemu/base/AutoInputEntry.java @@ -0,0 +1,81 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Element einer automatischen Tastatureingabe + */ + +package jkcemu.base; + +import java.io.UnsupportedEncodingException; +import java.lang.*; +import java.net.URLDecoder; +import java.util.*; + + +public class AutoInputEntry +{ + private int millisToWait; + private String inputText; + private String remark; + + + public AutoInputEntry( int millisToWait, String inputText, String remark ) + { + this.millisToWait = millisToWait; + this.inputText = inputText; + this.remark = remark; + } + + + public int getMillisToWait() + { + return this.millisToWait; + } + + + public String getInputText() + { + return this.inputText; + } + + + public String getRemark() + { + return this.remark; + } + + + public static java.util.List readEntries( + Properties props, + String propPrefix ) + { + java.util.List rv = null; + if( (props != null) && (propPrefix != null) ) { + int n = EmuUtil.getIntProperty( props, propPrefix + "count", 0 ); + if( n > 0 ) { + rv = new ArrayList<>(); + for( int i = 0; i < n; i++ ) { + String prefix = propPrefix + String.valueOf( i ); + String inputText = props.getProperty( prefix + ".input_text" ); + if( inputText != null ) { + if( !inputText.isEmpty() ) { + try { + rv.add( new AutoInputEntry( + EmuUtil.getIntProperty( + props, + prefix + ".wait.millis", + 0 ), + URLDecoder.decode( inputText, "UTF-8" ), + props.getProperty( prefix + ".remark" ) ) ); + } + catch( UnsupportedEncodingException ex ) {} + } + } + } + } + } + return rv; + } +} diff --git a/src/jkcemu/base/AutoInputEntryDlg.java b/src/jkcemu/base/AutoInputEntryDlg.java new file mode 100644 index 0000000..1c199c1 --- /dev/null +++ b/src/jkcemu/base/AutoInputEntryDlg.java @@ -0,0 +1,320 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Dialog fuer einen AutoInput-Eintrag + */ + +package jkcemu.base; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.lang.*; +import java.text.*; +import java.util.*; +import javax.swing.*; +import javax.swing.text.BadLocationException; + + +public class AutoInputEntryDlg extends BasicDlg +{ + private static final String LABEL_WAIT_TIME = "Wartezeit vor Eingabe:"; + private static final String CMD_CHAR_PREFIX = "char."; + + private static int[] waitMillis = { + 0, 200, 500, 1000, 1500, + 2000, 3000, 4000, 5000, + 6000, 7000, 8000, 9000 }; + + private static String[][] specialChars = { + { "\u0003", "Ctrl-C / Abbruch" }, + { "\u0008", "Cursor links / Back Space" }, + { "\t", "Cursor rechts / Tabulator" }, + { "\n", "Cursor runter" }, + { "\u000B", "Cursor hoch" }, + { "\r", "Enter / Return" } }; + + private static NumberFormat waitFmt = null; + + private AutoInputEntry appliedAutoInputEntry; + private JComboBox comboWaitSeconds; + private AutoInputDocument docInputText; + private JTextField fldInputText; + private JTextField fldRemark; + private JPopupMenu mnuSpecialChars; + private JButton btnSpecialChars; + private JButton btnOK; + private JButton btnCancel; + + + public static AutoInputEntry openNewEntryDlg( + Window owner, + boolean swapKeyCharCase, + int defaultMillisToWait ) + { + AutoInputEntryDlg dlg = new AutoInputEntryDlg( + owner, + swapKeyCharCase, + "Neuer AutoInput-Eintrag" ); + dlg.setMillisToWait( defaultMillisToWait ); + dlg.setVisible( true ); + return dlg.appliedAutoInputEntry; + } + + + public static AutoInputEntry openEditEntryDlg( + Window owner, + boolean swapKeyCharCase, + AutoInputEntry entry ) + { + AutoInputEntryDlg dlg = new AutoInputEntryDlg( + owner, + swapKeyCharCase, + "AutoInput-Eintrag bearbeiten" ); + dlg.setMillisToWait( entry.getMillisToWait() ); + dlg.setInputText( entry.getInputText() ); + dlg.fldRemark.setText( entry.getRemark() ); + dlg.setVisible( true ); + return dlg.appliedAutoInputEntry; + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public boolean doAction( EventObject e ) + { + boolean rv = false; + Object src = e.getSource(); + if( src != null ) { + if( src == this.btnSpecialChars ) { + rv = true; + this.mnuSpecialChars.show( + getContentPane(), + this.btnSpecialChars.getX(), + this.btnSpecialChars.getY() + + this.btnSpecialChars.getHeight() ); + } else if( src == this.btnOK ) { + rv = true; + doApply(); + } else if( src == this.btnCancel ) { + rv = true; + doClose(); + } else if( e instanceof ActionEvent ) { + String cmd = ((ActionEvent) e).getActionCommand(); + if( cmd != null ) { + int prefixLen = CMD_CHAR_PREFIX.length(); + if( cmd.startsWith( CMD_CHAR_PREFIX ) + && (cmd.length() > prefixLen) ) + { + try { + this.docInputText.insertString( + this.fldInputText.getCaretPosition(), + cmd.substring( prefixLen ), + null ); + } + catch( BadLocationException ex ) {} + } + } + } + } + return rv; + } + + + /* --- Konstruktor --- */ + + private AutoInputEntryDlg( + Window owner, + boolean swapKeyCharCase, + String title ) + { + super( owner, title ); + this.appliedAutoInputEntry = null; + + // Format fuer Wartezeit + if( waitFmt == null ) { + waitFmt = NumberFormat.getNumberInstance(); + if( waitFmt instanceof DecimalFormat ) { + ((DecimalFormat) waitFmt).applyPattern( "#0.0" ); + } + } + + + // Fensterinhalt + setLayout( new GridBagLayout() ); + + GridBagConstraints gbc = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 5, 5 ), + 0, 0 ); + + add( new JLabel( LABEL_WAIT_TIME ), gbc ); + + this.comboWaitSeconds = new JComboBox<>(); + for( int millis : waitMillis ) { + this.comboWaitSeconds.addItem( + waitFmt.format( (double) millis / 1000.0 ) ); + } + this.comboWaitSeconds.setEditable( true ); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridx++; + add( this.comboWaitSeconds, gbc ); + + gbc.fill = GridBagConstraints.NONE; + gbc.gridx++; + add( new JLabel( "Sekunden" ), gbc ); + + gbc.gridx = 0; + gbc.gridy++; + add( new JLabel( "Eingabetext:" ), gbc ); + + this.docInputText = new AutoInputDocument( swapKeyCharCase ); + this.fldInputText = new JTextField( this.docInputText, "", 0 ); + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx++; + add( this.fldInputText, gbc ); + + Font font = this.fldInputText.getFont(); + if( font != null ) { + this.comboWaitSeconds.setFont( font ); + } + + this.btnSpecialChars = new JButton( + "Bet\u00E4tigung einer Steuertaste eingeben" ); + gbc.weightx = 0.0; + gbc.fill = GridBagConstraints.NONE; + gbc.gridy++; + add( this.btnSpecialChars, gbc ); + + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy++; + add( new JLabel( "Bemerkung:" ), gbc ); + + this.fldRemark = new JTextField(); + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx++; + add( this.fldRemark, gbc ); + + JPanel panelBtn = new JPanel( new GridLayout( 1, 2, 5, 5 ) ); + gbc.anchor = GridBagConstraints.CENTER; + gbc.weightx = 0.0; + gbc.fill = GridBagConstraints.NONE; + gbc.insets.top = 10; + gbc.insets.bottom = 10; + gbc.gridx = 0; + gbc.gridy++; + add( panelBtn, gbc ); + + this.btnOK = new JButton( "OK" ); + panelBtn.add( this.btnOK ); + + this.btnCancel = new JButton( "Abbrechen" ); + panelBtn.add( this.btnCancel ); + + + // Menu fuer Sonderzeichen + this.mnuSpecialChars = new JPopupMenu(); + for( int i = 0; i < specialChars.length; i++ ) { + String code = specialChars[ i ][ 0 ]; + if( code != null ) { + if( !code.isEmpty() ) { + String s = AutoInputDocument.toVisibleText( code ); + if( s != null ) { + JMenuItem item = new JMenuItem( specialChars[ i ][ 1 ] ); + item.setActionCommand( CMD_CHAR_PREFIX + s ); + item.addActionListener( this ); + this.mnuSpecialChars.add( item ); + } + } + } + } + + + // Fenstergroesse und -position + pack(); + setParentCentered(); + setResizable( true ); + + + // Listeners + this.btnSpecialChars.addActionListener( this ); + this.btnOK.addActionListener( this ); + this.btnCancel.addActionListener( this ); + } + + + /* --- Aktionen --- */ + + private void doApply() + { + try { + String inputText = this.fldInputText.getText(); + if( inputText != null ) { + if( inputText.isEmpty() ) { + inputText = null; + } + if( inputText != null ) { + int millis = 0; + try { + Object o = this.comboWaitSeconds.getSelectedItem(); + if( o != null ) { + String s = o.toString(); + if( s != null ) { + Number value = waitFmt.parse( s ); + if( value != null ) { + millis = (int) Math.round( value.doubleValue() * 1000.0 ); + } + } + } + this.appliedAutoInputEntry = new AutoInputEntry( + millis, + AutoInputDocument.toRawText( inputText ), + this.fldRemark.getText() ); + doClose(); + } + catch( ParseException ex ) { + throw new NumberFormatException( + LABEL_WAIT_TIME + ": Ung\u00FCltiges Format" ); + } + } else { + showErrorDlg( + this, + "Eingabetext: Sie m\u00FCssen mindestens" + + " ein Zeichen eingeben!" ); + } + } + } + catch( NumberFormatException ex ) { + showErrorDlg( this, ex ); + } + } + + + /* --- private Methoden --- */ + + private void setInputText( String text ) + { + boolean swapCase = this.docInputText.getSwapCase(); + this.docInputText.setSwapCase( false ); + this.fldInputText.setText( AutoInputDocument.toVisibleText( text ) ); + this.docInputText.setSwapCase( swapCase ); + } + + + private void setMillisToWait( int millis ) + { + this.comboWaitSeconds.setSelectedItem( + waitFmt.format( (double) millis / 1000.0 ) ); + } +} diff --git a/src/jkcemu/base/AutoInputSettingsFld.java b/src/jkcemu/base/AutoInputSettingsFld.java new file mode 100644 index 0000000..9d036de --- /dev/null +++ b/src/jkcemu/base/AutoInputSettingsFld.java @@ -0,0 +1,372 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Komponente fuer die Einstellungen zur automatischen Tastatureingabe + */ + +package jkcemu.base; + +import java.awt.*; +import java.awt.event.*; +import java.io.UnsupportedEncodingException; +import java.lang.*; +import java.net.URLEncoder; +import java.util.*; +import javax.swing.*; +import javax.swing.event.*; +import jkcemu.Main; + + +public class AutoInputSettingsFld + extends AbstractSettingsFld + implements + ListSelectionListener, + MouseListener +{ + private static final int DEFAULT_MILLIS_TO_WAIT = 200; + + private boolean swapKeyCharCase; + private int defaultFirstMillisToWait; + private AutoInputTableModel tableModel; + private JTable table; + private JScrollPane scrollPane; + private ListSelectionModel selectionModel; + private JButton btnAdd; + private JButton btnEdit; + private JButton btnRemove; + private JButton btnUp; + private JButton btnDown; + + + public AutoInputSettingsFld( + SettingsFrm settingsFrm, + String propPrefix, + boolean swapKeyCharCase, + int defaultFirstMillisToWait ) + { + super( settingsFrm, propPrefix + "autoinput."); + this.defaultFirstMillisToWait = defaultFirstMillisToWait; + this.swapKeyCharCase = swapKeyCharCase; + setLayout( new GridBagLayout() ); + + GridBagConstraints gbc = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + add( + new JLabel( "Tastatureingaben, die nach dem Einschalten" + + " bzw. nach RESET" ), + gbc ); + gbc.insets.top = 0; + gbc.insets.bottom = 5; + gbc.gridy++; + add( + new JLabel( "automatisch get\u00E4tigt werden sollen:" ), + gbc ); + + this.tableModel = new AutoInputTableModel(); + this.table = new JTable( this.tableModel ); + this.table.setAutoCreateRowSorter( false ); + this.table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF ); + this.table.setColumnSelectionAllowed( false ); + this.table.setDragEnabled( false ); + this.table.setFillsViewportHeight( false ); + this.table.setPreferredScrollableViewportSize( new Dimension( 1, 1 ) ); + this.table.setRowSelectionAllowed( true ); + this.table.setSelectionMode( + ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); + this.table.addMouseListener( this ); + EmuUtil.setTableColWidths( this.table, 100, 200, 300 ); + + this.scrollPane = new JScrollPane( this.table ); + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.fill = GridBagConstraints.BOTH; + gbc.gridy++; + add( this.scrollPane, gbc ); + + JPanel panelBtnRight = new JPanel( new GridLayout( 2, 1, 5, 5 ) ); + gbc.anchor = GridBagConstraints.CENTER; + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.gridx++; + add( panelBtnRight, gbc ); + + this.btnUp = createImageButton( "/images/nav/up.png", "Auf" ); + panelBtnRight.add( this.btnUp ); + + this.btnDown = createImageButton( "/images/nav/down.png", "Ab" ); + panelBtnRight.add( this.btnDown ); + + JPanel panelBtnBottom = new JPanel( new GridLayout( 1, 3, 5, 5 ) ); + gbc.gridx = 0; + gbc.gridy++; + add( panelBtnBottom, gbc ); + + this.btnAdd = new JButton( "Hinzuf\u00FCgen" ); + this.btnAdd.addActionListener( this ); + this.btnAdd.addKeyListener( this ); + panelBtnBottom.add( this.btnAdd ); + + this.btnEdit = new JButton( "Bearbeiten" ); + this.btnEdit.addActionListener( this ); + this.btnEdit.addKeyListener( this ); + panelBtnBottom.add( this.btnEdit ); + + this.btnRemove = new JButton( "Entfernen" ); + this.btnRemove.addActionListener( this ); + this.btnRemove.addKeyListener( this ); + panelBtnBottom.add( this.btnRemove ); + + this.selectionModel = this.table.getSelectionModel(); + if( this.selectionModel != null ) { + this.selectionModel.addListSelectionListener( this ); + this.btnUp.setEnabled( false ); + this.btnDown.setEnabled( false ); + this.btnEdit.setEnabled( false ); + this.btnRemove.setEnabled( false ); + } + } + + + /* --- ListSelectionListener --- */ + + @Override + public void valueChanged( ListSelectionEvent e ) + { + if( e.getSource() == this.selectionModel ) { + int nRows = this.table.getRowCount(); + int nSelRows = this.table.getSelectedRowCount(); + int selRowNum = this.table.getSelectedRow(); + boolean stateOne = (nSelRows == 1) && (selRowNum >= 0); + this.btnUp.setEnabled( (nSelRows == 1) && (selRowNum > 0) ); + this.btnDown.setEnabled( stateOne && (selRowNum < (nRows - 1)) ); + this.btnEdit.setEnabled( stateOne ); + this.btnRemove.setEnabled( nSelRows > 0 ); + } + } + + + /* --- MouseListener --- */ + + @Override + public void mouseClicked( MouseEvent e ) + { + if( (e.getButton() == MouseEvent.BUTTON1) + && (e.getClickCount() > 1) + && (e.getComponent() == this.table) ) + { + doEdit(); + e.consume(); + } + } + + + @Override + public void mouseEntered( MouseEvent e ) + { + // leer + } + + + @Override + public void mouseExited( MouseEvent e ) + { + // leer + } + + + @Override + public void mousePressed( MouseEvent e ) + { + // leer + } + + + @Override + public void mouseReleased( MouseEvent e ) + { + // leer + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public void applyInput( + Properties props, + boolean selected ) throws UserInputException + { + int nRows = this.tableModel.getRowCount(); + for( int i = 0; i < nRows; i++ ) { + AutoInputEntry entry = this.tableModel.getRow( i ); + if( entry != null ) { + try { + String prefix = this.propPrefix + String.valueOf( i ); + EmuUtil.setProperty( + props, + prefix + ".wait.millis", + String.valueOf( entry.getMillisToWait() ) ); + EmuUtil.setProperty( + props, + prefix + ".input_text", + URLEncoder.encode( entry.getInputText(), "UTF-8" ) ); + EmuUtil.setProperty( + props, + prefix + ".remark", + entry.getRemark() ); + } + catch( UnsupportedEncodingException ex ) {} + } + } + props.setProperty( + this.propPrefix + "count", + Integer.toString( nRows ) ); + + } + + + @Override + public boolean doAction( EventObject e ) + { + boolean rv = false; + this.settingsFrm.setWaitCursor( true ); + + Object src = e.getSource(); + if( src != null ) { + if( src == this.btnAdd ) { + rv = true; + doAdd(); + } else if( src == this.btnEdit ) { + rv = true; + doEdit(); + } else if( src == this.btnRemove ) { + rv = true; + doRemove(); + } else if( src == this.btnUp ) { + rv = true; + doMove( -1 ); + } else if( src == this.btnDown ) { + rv = true; + doMove( 1 ); + } + } + + this.settingsFrm.setWaitCursor( false ); + return rv; + } + + + @Override + public void updFields( Properties props ) + { + this.tableModel.clear(); + java.util.List entries = AutoInputEntry.readEntries( + props, + this.propPrefix ); + if( entries != null ) { + this.tableModel.addRows( entries ); + } + } + + + /* --- Aktionen --- */ + + private void doAdd() + { + int millisToWait = this.defaultFirstMillisToWait; + if( (this.tableModel.getRowCount() > 0) + && (millisToWait > DEFAULT_MILLIS_TO_WAIT) ) + { + millisToWait = DEFAULT_MILLIS_TO_WAIT; + } + AutoInputEntry entry = AutoInputEntryDlg.openNewEntryDlg( + this.settingsFrm, + this.swapKeyCharCase, + millisToWait ); + if( entry != null ) { + int nRows = this.tableModel.getRowCount(); + this.tableModel.addRow( entry ); + int viewRow = this.table.convertRowIndexToView( nRows ); + if( viewRow >= 0 ) { + EmuUtil.fireSelectRow( this.table, viewRow ); + } + fireDataChanged(); + } + } + + + private void doEdit() + { + int[] rows = this.table.getSelectedRows(); + if( rows != null ) { + if( rows.length == 1 ) { + int modelRow = this.table.convertRowIndexToModel( rows[ 0 ] ); + if( modelRow >= 0 ) { + AutoInputEntry oldEntry = this.tableModel.getRow( modelRow ); + if( oldEntry != null ) { + AutoInputEntry newEntry = AutoInputEntryDlg.openEditEntryDlg( + this.settingsFrm, + this.swapKeyCharCase, + oldEntry ); + if( newEntry != null ) { + this.tableModel.setRow( modelRow, newEntry ); + fireDataChanged(); + } + } + } + } + } + } + + + private void doRemove() + { + int[] rows = this.table.getSelectedRows(); + if( rows != null ) { + if( rows.length > 0 ) { + Arrays.sort( rows ); + for( int i = rows.length - 1; i >= 0; --i ) { + int row = this.table.convertRowIndexToModel( rows[ i ] ); + if( row >= 0 ) { + this.tableModel.removeRow( row ); + } + } + fireDataChanged(); + } + } + } + + + private void doMove( int diffRows ) + { + int[] rows = this.table.getSelectedRows(); + if( rows != null ) { + if( rows.length == 1 ) { + int nRows = this.tableModel.getRowCount(); + int row1 = rows[ 0 ]; + int row2 = row1 + diffRows; + if( (row1 >= 0) && (row1 < nRows) + && (row2 >= 0) && (row2 < nRows) ) + { + AutoInputEntry rowData1 = this.tableModel.getRow( row1 ); + AutoInputEntry rowData2 = this.tableModel.getRow( row2 ); + if( (rowData1 != null) && (rowData2 != null) ) { + this.tableModel.setRow( row1, rowData2 ); + this.tableModel.setRow( row2, rowData1 ); + EmuUtil.fireSelectRow( this.table, row2 ); + fireDataChanged(); + } + } + } + } + } +} diff --git a/src/jkcemu/base/AutoInputTableModel.java b/src/jkcemu/base/AutoInputTableModel.java new file mode 100644 index 0000000..499ec36 --- /dev/null +++ b/src/jkcemu/base/AutoInputTableModel.java @@ -0,0 +1,168 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Tabellenfeld fuer automatische Tastatureingabe + */ + +package jkcemu.base; + +import java.lang.*; +import java.text.*; +import java.util.*; +import javax.swing.table.*; + + +public class AutoInputTableModel extends AbstractTableModel +{ + private static NumberFormat waitTimeFmt = null; + + + private static String[] colNames = { + "Wartezeit", + "Eingabetext", + "Bermerkung" }; + + + private java.util.List rows; + + + public AutoInputTableModel() + { + if( waitTimeFmt == null ) { + waitTimeFmt = NumberFormat.getNumberInstance(); + if( waitTimeFmt instanceof DecimalFormat ) { + ((DecimalFormat) waitTimeFmt).applyPattern( "##0.0" ); + } + } + this.rows = new java.util.ArrayList<>(); + } + + + public void addRow( AutoInputEntry entry ) + { + int row = this.rows.size(); + this.rows.add( entry ); + fireTableRowsInserted( row, row ); + } + + + public void addRows( Collection entries ) + { + if( entries != null ) { + if( !entries.isEmpty() ) { + int row = this.rows.size(); + this.rows.addAll( entries ); + fireTableRowsInserted( row, this.rows.size() - 1 ); + } + } + } + + + public void clear() + { + this.rows.clear(); + fireTableDataChanged(); + } + + + public AutoInputEntry getRow( int row ) + { + return (row >= 0) && (row < this.rows.size()) ? + this.rows.get( row ) + : null; + } + + + public void removeRow( int row ) + { + if( (row >= 0) && (row < this.rows.size()) ) { + this.rows.remove( row ); + fireTableRowsDeleted( row, row ); + } + } + + + public void setRow( int row, AutoInputEntry entry ) + { + if( (row >= 0) && (row < this.rows.size()) ) { + this.rows.set( row, entry ); + fireTableRowsUpdated( row, row ); + } + } + + + /* --- TableModel --- */ + + @Override + public Class getColumnClass( int col ) + { + Class rv = Object.class; + switch( col ) { + case 0: + case 1: + case 2: + rv = String.class; + break; + } + return rv; + } + + + @Override + public int getColumnCount() + { + return this.colNames.length; + } + + + @Override + public String getColumnName( int col ) + { + return (col >= 0) && (col < this.colNames.length) ? colNames[ col ] : ""; + } + + + @Override + public int getRowCount() + { + return this.rows.size(); + } + + + @Override + public Object getValueAt( int row, int col ) + { + Object rv = null; + if( (row >= 0) && (row < this.rows.size()) + && (col >= 0) && (col < this.colNames.length) ) + { + AutoInputEntry entry = this.rows.get( row ); + if( entry != null ) { + switch( col ) { + case 0: + rv = waitTimeFmt.format( + (double) entry.getMillisToWait() / 1000.0 ) + " s"; + break; + + case 1: + rv = AutoInputDocument.toVisibleText( entry.getInputText() ); + break; + + case 2: + rv = entry.getRemark(); + break; + } + } + } + return rv; + } + + + @Override + public boolean isCellEditable( int row, int col ) + { + return false; + } +} diff --git a/src/jkcemu/base/AutoInputWorker.java b/src/jkcemu/base/AutoInputWorker.java new file mode 100644 index 0000000..ba4118b --- /dev/null +++ b/src/jkcemu/base/AutoInputWorker.java @@ -0,0 +1,75 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Erzeugen von automatischen Tastatureingaben + */ + +package jkcemu.base; + +import java.lang.*; +import java.util.*; +import jkcemu.Main; + + +public class AutoInputWorker extends Thread +{ + private EmuThread emuThread; + private java.util.List entries; + + + public static void start( EmuThread emuThread, Properties props ) + { + EmuSys emuSys = emuThread.getEmuSys(); + if( emuSys != null ) { + java.util.List entries = AutoInputEntry.readEntries( + props, + emuSys.getPropPrefix() + "autoinput." ); + if( entries != null ) { + if( !entries.isEmpty() ) { + (new AutoInputWorker( emuThread, entries )).start(); + } + } + } + } + + + /* --- Runnable --- */ + + public void run() + { + EmuSys emuSys = this.emuThread.getEmuSys(); + if( emuSys != null ) { + try { + for( AutoInputEntry entry : this.entries ) { + + // ggf. warten + int millis = entry.getMillisToWait(); + if( millis > 0 ) { + sleep( millis ); + } + + // Text einfuegen + emuSys.startPastingText( entry.getInputText() ); + while( emuSys.isPastingText() ) { + sleep( 10 ); + } + } + } + catch( InterruptedException ex ) {} + } + } + + + /* --- Konstruktor --- */ + + private AutoInputWorker( + EmuThread emuThread, + java.util.List entries ) + { + super( Main.getThreadGroup(), "JKCEMU auto input" ); + this.emuThread = emuThread; + this.entries = entries; + } +} diff --git a/src/jkcemu/base/AutoLoadEntry.java b/src/jkcemu/base/AutoLoadEntry.java new file mode 100644 index 0000000..e691354 --- /dev/null +++ b/src/jkcemu/base/AutoLoadEntry.java @@ -0,0 +1,109 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Automatisch zu ladende Datei + */ + +package jkcemu.base; + +import java.lang.*; +import java.util.*; + + +public class AutoLoadEntry +{ + private int millisToWait; + private String fileName; + private Integer loadAddr; + + + public AutoLoadEntry( + int millisToWait, + String fileName, + Integer loadAddr ) + { + this.millisToWait = millisToWait; + this.fileName = fileName; + this.loadAddr = loadAddr; + } + + + public String getFileName() + { + return this.fileName; + } + + + public int getMillisToWait() + { + return this.millisToWait; + } + + + public Integer getLoadAddr() + { + return this.loadAddr; + } + + + public static java.util.List readEntries( + Properties props, + String propPrefix ) + { + java.util.List rv = null; + if( (props != null) && (propPrefix != null) ) { + int n = EmuUtil.getIntProperty( props, propPrefix + "count", 0 ); + if( n > 0 ) { + rv = new ArrayList<>(); + for( int i = 0; i < n; i++ ) { + String prefix = propPrefix + String.valueOf( i ); + String fileName = props.getProperty( prefix + ".file" ); + if( fileName != null ) { + fileName = fileName.trim(); + if( !fileName.isEmpty() ) { + rv.add( + new AutoLoadEntry( + EmuUtil.getIntProperty( + props, + prefix + ".wait.millis", + 0 ), + fileName, + getAddrProperty( + props, + prefix + ".address.load" ) ) ); + } + } + } + } + } + return rv; + } + + + /* --- private Methoden --- */ + + private static Integer getAddrProperty( + Properties props, + String keyword ) + { + Integer rv = null; + if( props != null ) { + String s = props.getProperty( keyword ); + if( s != null ) { + s = s.trim(); + if( !s.isEmpty() ) { + try { + int value = Integer.parseInt( s, 16 ); + if( (value >= 0) && (value <= 0xFFFF) ) { + rv = new Integer( value ); + } + } + catch( NumberFormatException ex ) {} + } + } + } + return rv; + } +} diff --git a/src/jkcemu/base/AutoLoadEntryDlg.java b/src/jkcemu/base/AutoLoadEntryDlg.java new file mode 100644 index 0000000..744e86d --- /dev/null +++ b/src/jkcemu/base/AutoLoadEntryDlg.java @@ -0,0 +1,277 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Dialog fuer einen AutoLoad-Eintrag + */ + +package jkcemu.base; + +import java.awt.*; +import java.io.File; +import java.lang.*; +import java.text.*; +import java.util.*; +import javax.swing.*; + + +public class AutoLoadEntryDlg extends BasicDlg +{ + private static final String LABEL_LOAD_ADDR = "Ladeadresse (optional):"; + private static final String LABEL_WAIT_TIME = "Wartezeit vor dem Laden:"; + + private static int[] waitMillis = { + 0, 200, 500, 1000, 1500, + 2000, 3000, 4000, 5000, + 6000, 7000, 8000, 9000 }; + + private static NumberFormat waitFmt = null; + + private boolean checkLoadAddr; + private AutoLoadEntry appliedAutoLoadEntry; + private FileNameFld fldFile; + private JComboBox comboWaitSeconds; + private JLabel labelLoadAddr; + private HexDocument docLoadAddr; + private JTextField fldLoadAddr; + private JButton btnOK; + private JButton btnCancel; + + + public static AutoLoadEntry openNewEntryDlg( + Window owner, + File file, + int defaultMillisToWait, + boolean checkLoadAddr ) + { + AutoLoadEntryDlg dlg = new AutoLoadEntryDlg( + owner, + "Neuer AutoLoad-Eintrag", + checkLoadAddr ); + dlg.fldFile.setFile( file ); + dlg.setMillisToWait( defaultMillisToWait ); + dlg.setVisible( true ); + return dlg.appliedAutoLoadEntry; + } + + + public static AutoLoadEntry openEditEntryDlg( + Window owner, + AutoLoadEntry entry, + boolean checkLoadAddr ) + { + AutoLoadEntryDlg dlg = new AutoLoadEntryDlg( + owner, + "AutoLoad-Eintrag bearbeiten", + checkLoadAddr ); + dlg.fldFile.setFileName( entry.getFileName() ); + dlg.setMillisToWait( entry.getMillisToWait() ); + dlg.setLoadAddr( entry.getLoadAddr() ); + dlg.setVisible( true ); + return dlg.appliedAutoLoadEntry; + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public boolean doAction( EventObject e ) + { + boolean rv = false; + Object src = e.getSource(); + if( src != null ) { + if( src == this.btnOK ) { + rv = true; + doApply(); + } else if( src == this.btnCancel ) { + rv = true; + doClose(); + } + } + return rv; + } + + + /* --- Konstruktor --- */ + + private AutoLoadEntryDlg( + Window owner, + String title, + boolean checkLoadAddr ) + { + super( owner, title ); + this.checkLoadAddr = checkLoadAddr; + this.appliedAutoLoadEntry = null; + + // Format fuer Wartezeit + if( waitFmt == null ) { + waitFmt = NumberFormat.getNumberInstance(); + if( waitFmt instanceof DecimalFormat ) { + ((DecimalFormat) waitFmt).applyPattern( "#0.0" ); + } + } + + // Fensterinhalt + setLayout( new GridBagLayout() ); + + GridBagConstraints gbc = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 5, 5 ), + 0, 0 ); + + add( new JLabel( "Datei:" ), gbc ); + + this.fldFile = new FileNameFld(); + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx++; + add( this.fldFile, gbc ); + + gbc.weightx = 0.0; + gbc.fill = GridBagConstraints.NONE; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy++; + add( new JLabel( LABEL_WAIT_TIME ), gbc ); + + this.comboWaitSeconds = new JComboBox<>(); + for( int millis : waitMillis ) { + this.comboWaitSeconds.addItem( + waitFmt.format( (double) millis / 1000.0 ) ); + } + Font font = this.fldFile.getFont(); + if( font != null ) { + this.comboWaitSeconds.setFont( font ); + } + this.comboWaitSeconds.setEditable( true ); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridx++; + add( this.comboWaitSeconds, gbc ); + + gbc.fill = GridBagConstraints.NONE; + gbc.gridx++; + add( new JLabel( "Sekunden" ), gbc ); + + gbc.gridx = 0; + gbc.gridy++; + add( new JLabel( LABEL_LOAD_ADDR ), gbc ); + + this.docLoadAddr = new HexDocument( 4, LABEL_LOAD_ADDR ); + this.fldLoadAddr = new JTextField( this.docLoadAddr, "", 5 ); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridx++; + add( this.fldLoadAddr, gbc ); + + gbc.fill = GridBagConstraints.NONE; + gbc.gridx++; + add( new JLabel( "hex" ), gbc ); + + JPanel panelBtn = new JPanel( new GridLayout( 1, 2, 5, 5 ) ); + gbc.anchor = GridBagConstraints.CENTER; + gbc.insets.top = 10; + gbc.insets.bottom = 10; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx = 0; + gbc.gridy++; + add( panelBtn, gbc ); + + this.btnOK = new JButton( "OK" ); + panelBtn.add( this.btnOK ); + + this.btnCancel = new JButton( "Abbrechen" ); + panelBtn.add( this.btnCancel ); + + // Fenstergroesse und -position + pack(); + setParentCentered(); + setResizable( true ); + + // Listeners + this.btnOK.addActionListener( this ); + this.btnCancel.addActionListener( this ); + } + + + /* --- Aktionen --- */ + + private void doApply() + { + try { + File file = this.fldFile.getFile(); + if( file != null ) { + int millis = 0; + try { + Object o = this.comboWaitSeconds.getSelectedItem(); + if( o != null ) { + String s = o.toString(); + if( s != null ) { + Number value = waitFmt.parse( s ); + if( value != null ) { + millis = (int) Math.round( value.doubleValue() * 1000.0 ); + } + } + } + boolean state = true; + Integer loadAddr = this.docLoadAddr.getInteger(); + if( this.checkLoadAddr && (loadAddr == null) ) { + FileInfo fileInfo = FileInfo.analyzeFile( file ); + if( fileInfo != null ) { + if( fileInfo.getBegAddr() < 0 ) { + state = showYesNoWarningDlg( + this, + "Das automatiche Laden wird nicht" + + " funktionieren,\n" + + "da Sie keine Ladeadresse" + + " angegeben haben\n" + + "und in der Datei keine" + + " enthalten ist.\n" + + "\nM\u00F6chten Sie trotzdem" + + " fortsetzen?", + "Ladeadresse" ); + } + } + } + if( state ) { + this.appliedAutoLoadEntry = new AutoLoadEntry( + millis, + file.getPath(), + loadAddr ); + doClose(); + } + } + catch( ParseException ex ) { + throw new NumberFormatException( + LABEL_WAIT_TIME + ": Ung\u00FCltiges Format" ); + } + } + } + catch( NumberFormatException ex ) { + showErrorDlg( this, ex ); + } + } + + + /* --- private Methoden --- */ + + private void setMillisToWait( int millis ) + { + this.comboWaitSeconds.setSelectedItem( + waitFmt.format( (double) millis / 1000.0 ) ); + } + + + private void setLoadAddr( Integer addr ) + { + if( addr != null ) { + this.docLoadAddr.setValue( addr.intValue(), 4 ); + } else { + this.fldLoadAddr.setText( "" ); + } + } +} diff --git a/src/jkcemu/base/AutoLoadSettingsFld.java b/src/jkcemu/base/AutoLoadSettingsFld.java new file mode 100644 index 0000000..2ccbb61 --- /dev/null +++ b/src/jkcemu/base/AutoLoadSettingsFld.java @@ -0,0 +1,409 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Komponente fuer die Einstellungen zum automatischen Laden + * von Dateien in den Arbeitsspeicher + */ + +package jkcemu.base; + +import java.awt.*; +import java.awt.dnd.*; +import java.awt.event.*; +import java.io.File; +import java.lang.*; +import java.util.*; +import javax.swing.*; +import javax.swing.event.*; +import jkcemu.Main; + + +public class AutoLoadSettingsFld + extends AbstractSettingsFld + implements + ListSelectionListener, + MouseListener +{ + private int defaultFirstMillisToWait; + private boolean checkAddrs; + private AutoLoadTableModel tableModel; + private JTable table; + private JScrollPane scrollPane; + private ListSelectionModel selectionModel; + private JButton btnAdd; + private JButton btnEdit; + private JButton btnRemove; + private JButton btnUp; + private JButton btnDown; + + + public AutoLoadSettingsFld( + SettingsFrm settingsFrm, + String propPrefix, + int defaultFirstMillisToWait, + boolean checkAddrs ) + { + super( settingsFrm, propPrefix + "autoload."); + this.defaultFirstMillisToWait = defaultFirstMillisToWait; + this.checkAddrs = checkAddrs; + setLayout( new GridBagLayout() ); + + GridBagConstraints gbc = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + add( + new JLabel( "Dateien, die nach dem Einschalten bzw. nach RESET" ), + gbc ); + gbc.insets.top = 0; + gbc.insets.bottom = 5; + gbc.gridy++; + add( + new JLabel( "automatisch in den Arbeitsspeicher" + + " geladen werden sollen:" ), + gbc ); + + this.tableModel = new AutoLoadTableModel(); + this.table = new JTable( this.tableModel ); + this.table.setAutoCreateRowSorter( false ); + this.table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF ); + this.table.setColumnSelectionAllowed( false ); + this.table.setDragEnabled( false ); + this.table.setFillsViewportHeight( false ); + this.table.setPreferredScrollableViewportSize( new Dimension( 1, 1 ) ); + this.table.setRowSelectionAllowed( true ); + this.table.setSelectionMode( + ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); + this.table.addMouseListener( this ); + EmuUtil.setTableColWidths( this.table, 100, 100, 400 ); + + this.scrollPane = new JScrollPane( this.table ); + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.fill = GridBagConstraints.BOTH; + gbc.gridy++; + add( this.scrollPane, gbc ); + + JPanel panelBtnRight = new JPanel( new GridLayout( 2, 1, 5, 5 ) ); + gbc.anchor = GridBagConstraints.CENTER; + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.weighty = 0.0; + gbc.gridx++; + add( panelBtnRight, gbc ); + + this.btnUp = createImageButton( "/images/nav/up.png", "Auf" ); + panelBtnRight.add( this.btnUp ); + + this.btnDown = createImageButton( "/images/nav/down.png", "Ab" ); + panelBtnRight.add( this.btnDown ); + + JPanel panelBtnBottom = new JPanel( new GridLayout( 1, 3, 5, 5 ) ); + gbc.gridx = 0; + gbc.gridy++; + add( panelBtnBottom, gbc ); + + this.btnAdd = new JButton( "Hinzuf\u00FCgen" ); + this.btnAdd.addActionListener( this ); + this.btnAdd.addKeyListener( this ); + panelBtnBottom.add( this.btnAdd ); + + this.btnEdit = new JButton( "Bearbeiten" ); + this.btnEdit.addActionListener( this ); + this.btnEdit.addKeyListener( this ); + panelBtnBottom.add( this.btnEdit ); + + this.btnRemove = new JButton( "Entfernen" ); + this.btnRemove.addActionListener( this ); + this.btnRemove.addKeyListener( this ); + panelBtnBottom.add( this.btnRemove ); + + this.selectionModel = this.table.getSelectionModel(); + if( this.selectionModel != null ) { + this.selectionModel.addListSelectionListener( this ); + this.btnUp.setEnabled( false ); + this.btnDown.setEnabled( false ); + this.btnEdit.setEnabled( false ); + this.btnRemove.setEnabled( false ); + } + + // Drag&Drop ermoeglichen + (new DropTarget( this.table, this )).setActive( true ); + (new DropTarget( this.scrollPane, this )).setActive( true ); + } + + + /* --- ListSelectionListener --- */ + + @Override + public void valueChanged( ListSelectionEvent e ) + { + if( e.getSource() == this.selectionModel ) { + int nRows = this.table.getRowCount(); + int nSelRows = this.table.getSelectedRowCount(); + int selRowNum = this.table.getSelectedRow(); + boolean stateOne = (nSelRows == 1) && (selRowNum >= 0); + this.btnUp.setEnabled( (nSelRows == 1) && (selRowNum > 0) ); + this.btnDown.setEnabled( stateOne && (selRowNum < (nRows - 1)) ); + this.btnEdit.setEnabled( stateOne ); + this.btnRemove.setEnabled( nSelRows > 0 ); + } + } + + + /* --- MouseListener --- */ + + @Override + public void mouseClicked( MouseEvent e ) + { + if( (e.getButton() == MouseEvent.BUTTON1) + && (e.getClickCount() > 1) + && (e.getComponent() == this.table) ) + { + doEdit(); + e.consume(); + } + } + + + @Override + public void mouseEntered( MouseEvent e ) + { + // leer + } + + + @Override + public void mouseExited( MouseEvent e ) + { + // leer + } + + + @Override + public void mousePressed( MouseEvent e ) + { + // leer + } + + + @Override + public void mouseReleased( MouseEvent e ) + { + // leer + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public void applyInput( + Properties props, + boolean selected ) throws UserInputException + { + int nRows = this.tableModel.getRowCount(); + for( int i = 0; i < nRows; i++ ) { + AutoLoadEntry entry = this.tableModel.getRow( i ); + if( entry != null ) { + String prefix = this.propPrefix + String.valueOf( i ); + EmuUtil.setProperty( + props, + prefix + ".wait.millis", + String.valueOf( entry.getMillisToWait() ) ); + EmuUtil.setProperty( + props, + prefix + ".file", + entry.getFileName() ); + EmuUtil.setProperty( + props, + prefix + ".address.load", + AutoLoadTableModel.toHex4( entry.getLoadAddr() ) ); + } + } + props.setProperty( + this.propPrefix + "count", + Integer.toString( nRows ) ); + + } + + + @Override + public boolean doAction( EventObject e ) + { + boolean rv = false; + this.settingsFrm.setWaitCursor( true ); + + Object src = e.getSource(); + if( src != null ) { + if( src == this.btnAdd ) { + rv = true; + doAdd(); + } else if( src == this.btnEdit ) { + rv = true; + doEdit(); + } else if( src == this.btnRemove ) { + rv = true; + doRemove(); + } else if( src == this.btnUp ) { + rv = true; + doMove( -1 ); + } else if( src == this.btnDown ) { + rv = true; + doMove( 1 ); + } + } + + this.settingsFrm.setWaitCursor( false ); + return rv; + } + + + @Override + public void dragEnter( DropTargetDragEvent e ) + { + // leer + } + + + @Override + protected boolean fileDropped( Component c, File file ) + { + boolean rv = false; + if( (c == this.table) || (c == this.scrollPane) ) { + addFile( file ); + rv = true; + } + return rv; + } + + + @Override + public void updFields( Properties props ) + { + this.tableModel.clear(); + java.util.List entries = AutoLoadEntry.readEntries( + props, + this.propPrefix ); + if( entries != null ) { + this.tableModel.addRows( entries ); + } + } + + + /* --- Aktionen --- */ + + private void doAdd() + { + File file = EmuUtil.showFileOpenDlg( + this.settingsFrm, + "Datei ausw\u00E4hlen", + Main.getLastDirFile( "software" ), + EmuUtil.getBinaryFileFilter(), + EmuUtil.getBasicFileFilter(), + EmuUtil.getKCSystemFileFilter(), + EmuUtil.getKCBasicFileFilter(), + EmuUtil.getTapeFileFilter(), + EmuUtil.getHeadersaveFileFilter(), + EmuUtil.getHexFileFilter() ); + if( file != null ) { + addFile( file ); + } + } + + + private void doEdit() + { + int[] rows = this.table.getSelectedRows(); + if( rows != null ) { + if( rows.length == 1 ) { + int modelRow = this.table.convertRowIndexToModel( rows[ 0 ] ); + if( modelRow >= 0 ) { + AutoLoadEntry oldEntry = this.tableModel.getRow( modelRow ); + if( oldEntry != null ) { + AutoLoadEntry newEntry = AutoLoadEntryDlg.openEditEntryDlg( + this.settingsFrm, + oldEntry, + this.checkAddrs ); + if( newEntry != null ) { + this.tableModel.setRow( modelRow, newEntry ); + fireDataChanged(); + } + } + } + } + } + } + + + private void doRemove() + { + int[] rows = this.table.getSelectedRows(); + if( rows != null ) { + if( rows.length > 0 ) { + Arrays.sort( rows ); + for( int i = rows.length - 1; i >= 0; --i ) { + int row = this.table.convertRowIndexToModel( rows[ i ] ); + if( row >= 0 ) { + this.tableModel.removeRow( row ); + } + } + fireDataChanged(); + } + } + } + + + private void doMove( int diffRows ) + { + int[] rows = this.table.getSelectedRows(); + if( rows != null ) { + if( rows.length == 1 ) { + int nRows = this.tableModel.getRowCount(); + int row1 = rows[ 0 ]; + int row2 = row1 + diffRows; + if( (row1 >= 0) && (row1 < nRows) + && (row2 >= 0) && (row2 < nRows) ) + { + AutoLoadEntry rowData1 = this.tableModel.getRow( row1 ); + AutoLoadEntry rowData2 = this.tableModel.getRow( row2 ); + if( (rowData1 != null) && (rowData2 != null) ) { + this.tableModel.setRow( row1, rowData2 ); + this.tableModel.setRow( row2, rowData1 ); + EmuUtil.fireSelectRow( this.table, row2 ); + fireDataChanged(); + } + } + } + } + } + + + /* --- private Methoden --- */ + + private boolean addFile( File file ) + { + int nRows = this.tableModel.getRowCount(); + AutoLoadEntry entry = AutoLoadEntryDlg.openNewEntryDlg( + this.settingsFrm, + file, + nRows > 0 ? 0 : defaultFirstMillisToWait, + this.checkAddrs ); + if( entry != null ) { + this.tableModel.addRow( entry ); + int viewRow = this.table.convertRowIndexToView( nRows ); + if( viewRow >= 0 ) { + EmuUtil.fireSelectRow( this.table, viewRow ); + } + Main.setLastFile( file, "software" ); + fireDataChanged(); + } + return entry != null; + } +} diff --git a/src/jkcemu/base/AutoLoadTableModel.java b/src/jkcemu/base/AutoLoadTableModel.java new file mode 100644 index 0000000..ebf728d --- /dev/null +++ b/src/jkcemu/base/AutoLoadTableModel.java @@ -0,0 +1,173 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Tabellenfeld fuer automatisch zu ladende Dateien + */ + +package jkcemu.base; + +import java.lang.*; +import java.text.*; +import java.util.*; +import javax.swing.table.*; + + +public class AutoLoadTableModel extends AbstractTableModel +{ + private static NumberFormat waitTimeFmt = null; + + + private static String[] colNames = { + "Wartezeit", + "Ladeadresse", + "Dateiname" }; + + + private java.util.List rows; + + + public AutoLoadTableModel() + { + if( waitTimeFmt == null ) { + waitTimeFmt = NumberFormat.getNumberInstance(); + if( waitTimeFmt instanceof DecimalFormat ) { + ((DecimalFormat) waitTimeFmt).applyPattern( "##0.0" ); + } + } + this.rows = new java.util.ArrayList<>(); + } + + + public void addRow( AutoLoadEntry entry ) + { + int row = this.rows.size(); + this.rows.add( entry ); + fireTableRowsInserted( row, row ); + } + + + public void addRows( Collection entries ) + { + if( entries != null ) { + if( !entries.isEmpty() ) { + int row = this.rows.size(); + this.rows.addAll( entries ); + fireTableRowsInserted( row, this.rows.size() - 1 ); + } + } + } + + + public void clear() + { + this.rows.clear(); + fireTableDataChanged(); + } + + + public AutoLoadEntry getRow( int row ) + { + return (row >= 0) && (row < this.rows.size()) ? + this.rows.get( row ) + : null; + } + + + public void removeRow( int row ) + { + if( (row >= 0) && (row < this.rows.size()) ) { + this.rows.remove( row ); + fireTableRowsDeleted( row, row ); + } + } + + + public void setRow( int row, AutoLoadEntry entry ) + { + if( (row >= 0) && (row < this.rows.size()) ) { + this.rows.set( row, entry ); + fireTableRowsUpdated( row, row ); + } + } + + + public static String toHex4( Integer value ) + { + String rv = null; + if( value != null ) { + if( (value.intValue() >= 0) && (value.intValue() <= 0xFFFF) ) { + rv = String.format( "%04X", value & 0xFFFF ); + } + } + return rv; + } + + + /* --- TableModel --- */ + + @Override + public Class getColumnClass( int col ) + { + return (col >= 0) && (col < colNames.length) ? + String.class : Object.class; + } + + + @Override + public int getColumnCount() + { + return this.colNames.length; + } + + + @Override + public String getColumnName( int col ) + { + return (col >= 0) && (col < this.colNames.length) ? colNames[ col ] : ""; + } + + + @Override + public int getRowCount() + { + return this.rows.size(); + } + + + @Override + public Object getValueAt( int row, int col ) + { + Object rv = null; + if( (row >= 0) && (row < this.rows.size()) + && (col >= 0) && (col < this.colNames.length) ) + { + AutoLoadEntry entry = this.rows.get( row ); + if( entry != null ) { + switch( col ) { + case 0: + rv = waitTimeFmt.format( + (double) entry.getMillisToWait() / 1000.0 ) + " s"; + break; + + case 1: + rv = toHex4( entry.getLoadAddr() ); + break; + + case 2: + rv = entry.getFileName(); + break; + } + } + } + return rv; + } + + + @Override + public boolean isCellEditable( int row, int col ) + { + return false; + } +} diff --git a/src/jkcemu/base/AutoLoader.java b/src/jkcemu/base/AutoLoader.java new file mode 100644 index 0000000..59e98ed --- /dev/null +++ b/src/jkcemu/base/AutoLoader.java @@ -0,0 +1,153 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Automatisches Laden von Dateien in den Arbeitsspeicher + */ + +package jkcemu.base; + +import java.io.*; +import java.lang.*; +import java.util.*; +import jkcemu.Main; +import jkcemu.audio.AudioUtil; + + +public class AutoLoader extends Thread +{ + private static final String TEXT_CANNOT_LOAD = "Kann nicht geladen werden"; + + private EmuThread emuThread; + private java.util.List entries; + + + public static void start( EmuThread emuThread, Properties props ) + { + EmuSys emuSys = emuThread.getEmuSys(); + if( emuSys != null ) { + java.util.List entries = AutoLoadEntry.readEntries( + props, + emuSys.getPropPrefix() + "autoload." ); + if( entries != null ) { + if( !entries.isEmpty() ) { + (new AutoLoader( emuThread, entries )).start(); + } + } + } + } + + + /* --- Runnable --- */ + + public void run() + { + try { + for( AutoLoadEntry entry : this.entries ) { + String fileName = entry.getFileName(); + if( fileName != null ) { + if( !fileName.isEmpty() ) { + this.emuThread.getScreenFrm().showStatusText( + "AutoLoad: " + fileName ); + try { + File file = new File( fileName ); + + // Dateityp ermitteln + if( AudioUtil.isAudioFile( file ) ) { + throw new IOException( + "Audio-Datei bei AutoLoad nicht unterst\u00FCtzt" ); + } + byte[] fileBuf = EmuUtil.readFile( file, true, 0x10000 ); + if( fileBuf == null ) { + throw new IOException( TEXT_CANNOT_LOAD ); + } + FileInfo fileInfo = FileInfo.analyzeFile( fileBuf, file ); + if( fileInfo == null ) { + throw new IOException( "Dateiformat unbekannt" ); + } + if( fileInfo.isTapeFile() ) { + throw new IOException( + "Tape-Datei bei AutoLoad nicht unterst\u00FCtzt" ); + } + + // Ladeadresse ermitteln + Integer loadAddr = entry.getLoadAddr(); + if( loadAddr == null ) { + int begAddr = fileInfo.getBegAddr(); + if( begAddr >= 0 ) { + loadAddr = new Integer( begAddr ); + } + } + if( loadAddr == null ) { + EmuSys emuSys = emuThread.getEmuSys(); + if( emuSys != null ) { + loadAddr = emuSys.getLoadAddr(); + } + } + if( loadAddr == null ) { + throw new IOException( "Ladeadresse nicht angegeben" + + " und in der Datei auch nicht enthalten" ); + } + LoadData loadData = fileInfo.createLoadData( fileBuf ); + if( loadData == null ) { + throw new IOException( TEXT_CANNOT_LOAD ); + } + String msg = loadData.getInfoMsg(); + if( msg != null ) { + if( !msg.isEmpty() ) { + addMsg( fileName, msg ); + } + } + loadData.setBegAddr( loadAddr.intValue() ); + loadData.setStartAddr( -1 ); + + // ggf. warten + int millis = entry.getMillisToWait(); + if( millis > 0 ) { + sleep( millis ); + } + + // Datei laden + emuThread.loadIntoMemory( loadData ); + } + catch( IOException ex ) { + String msg = ex.getMessage(); + if( msg != null ) { + if( msg.isEmpty() ) { + msg = null; + } + } + if( msg == null ) { + msg = TEXT_CANNOT_LOAD; + } + addMsg( fileName, msg != null ? msg : TEXT_CANNOT_LOAD ); + } + } + } + } + } + catch( InterruptedException ex ) {} + } + + + /* --- Konstruktor --- */ + + private AutoLoader( + EmuThread emuThread, + java.util.List entries ) + { + super( Main.getThreadGroup(), "JKCEMU auto loader" ); + this.emuThread = emuThread; + this.entries = entries; + } + + + /* --- private Methoden --- */ + + private void addMsg( String fileName, String msg ) + { + this.emuThread.getScreenFrm().fireAppendMsg( + "AutoLoad:\n" + fileName + ":\n" + msg ); + } +} diff --git a/src/jkcemu/base/BasicDlg.java b/src/jkcemu/base/BasicDlg.java index 5a608e9..6142794 100644 --- a/src/jkcemu/base/BasicDlg.java +++ b/src/jkcemu/base/BasicDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -237,6 +237,17 @@ protected boolean showPopup( MouseEvent e ) /* --- statische Methoden --- */ + public static boolean showConfirmDlg( Component owner, String msg ) + { + EmuUtil.frameToFront( owner ); + return (JOptionPane.showConfirmDialog( + owner, + msg, + "Best\u00E4tigung", + JOptionPane.OK_CANCEL_OPTION ) == JOptionPane.OK_OPTION); + } + + /* * Diese Methoden zeigen eine Fehlermeldung an. */ diff --git a/src/jkcemu/base/BasicFrm.java b/src/jkcemu/base/BasicFrm.java index 9d5fb64..ab80c8f 100644 --- a/src/jkcemu/base/BasicFrm.java +++ b/src/jkcemu/base/BasicFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2012 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -22,6 +22,14 @@ public class BasicFrm extends JFrame implements KeyListener, WindowListener { + public static final String PROP_WINDOW_X = "window.x"; + public static final String PROP_WINDOW_Y = "window.y"; + public static final String PROP_WINDOW_WIDTH = "window.width"; + public static final String PROP_WINDOW_HEIGHT = "window.height"; + public static final String PROP_WINDOW_ICONIFIED = "window.iconified"; + public static final String PROP_WINDOW_MAXIMIZED = "window.maximized"; + + protected BasicFrm() { setDefaultCloseOperation( DO_NOTHING_ON_CLOSE ); @@ -45,9 +53,53 @@ protected BasicFrm() */ public boolean applySettings( Properties props, boolean resizable ) { - return ((props != null) && !isVisible()) ? - EmuUtil.applyWindowSettings( props, this, resizable ) - : false; + boolean rv = false; + if( (props != null) && !isVisible() ) { + String prefix = getSettingsPrefix(); + + int x = EmuUtil.parseInt( + props.getProperty( prefix + PROP_WINDOW_X ), + -1, + -1 ); + int y = EmuUtil.parseInt( + props.getProperty( prefix + PROP_WINDOW_Y ), + -1, + -1 ); + if( (x >= 0) && (y >= 0) ) { + if( resizable ) { + int w = EmuUtil.parseInt( + props.getProperty( prefix + PROP_WINDOW_WIDTH ), + -1, + -1 ); + int h = EmuUtil.parseInt( + props.getProperty( prefix + PROP_WINDOW_HEIGHT ), + -1, + -1 ); + setBounds( x, y, w, h ); + rv = true; + } else { + setLocation( x, y ); + rv = true; + } + } + int frmState = 0; + if( EmuUtil.getBooleanProperty( + props, + prefix + "window.iconified", + false ) ) + { + frmState |= Frame.ICONIFIED; + } + if( EmuUtil.getBooleanProperty( + props, + prefix + "window.maximized", + false ) ) + { + frmState |= Frame.MAXIMIZED_BOTH; + } + setExtendedState( frmState != 0 ? frmState : Frame.NORMAL ); + } + return rv; } @@ -169,9 +221,24 @@ public void run() } + public void fireShowErrorMsg( final String msg ) + { + final Component owner = this; + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + BasicDlg.showErrorDlg( owner, msg ); + } + } ); + } + + public String getSettingsPrefix() { - return getClass().getName(); + return getClass().getName() + "."; } @@ -200,10 +267,10 @@ public void putSettingsTo( Properties props ) Point location = getLocation(); if( location != null ) { props.setProperty( - prefix + ".window.x", + prefix + PROP_WINDOW_X, String.valueOf( location.x ) ); props.setProperty( - prefix + ".window.y", + prefix + PROP_WINDOW_Y, String.valueOf( location.y ) ); } @@ -212,14 +279,24 @@ public void putSettingsTo( Properties props ) if( size != null ) { if( (size.width > 0) && (size.height > 0) ) { props.setProperty( - prefix + ".window.width", + prefix + PROP_WINDOW_WIDTH, String.valueOf( size.width ) ); props.setProperty( - prefix + ".window.height", + prefix + PROP_WINDOW_HEIGHT, String.valueOf( size.height ) ); } } } + + int frameState = getExtendedState(); + EmuUtil.setProperty( + props, + prefix + PROP_WINDOW_ICONIFIED, + (frameState & Frame.ICONIFIED) == Frame.ICONIFIED ); + EmuUtil.setProperty( + props, + prefix + PROP_WINDOW_MAXIMIZED, + (frameState & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH ); } } diff --git a/src/jkcemu/base/ByteDataSource.java b/src/jkcemu/base/ByteDataSource.java new file mode 100644 index 0000000..acb35bc --- /dev/null +++ b/src/jkcemu/base/ByteDataSource.java @@ -0,0 +1,17 @@ +/* + * (c) 2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Interface fuer eine Byte-orientierte Datenquelle + */ + +package jkcemu.base; + + +public interface ByteDataSource +{ + public int getAddrOffset(); + public int getDataByte( int addr ); + public int getDataLength(); +} diff --git a/src/jkcemu/base/DirSelectDlg.java b/src/jkcemu/base/DirSelectDlg.java index 3f0f5b3..bbfb5f5 100644 --- a/src/jkcemu/base/DirSelectDlg.java +++ b/src/jkcemu/base/DirSelectDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2010 Jens Mueller + * (c) 2009-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -9,8 +9,9 @@ package jkcemu.base; import java.awt.*; -import java.io.File; +import java.io.*; import java.lang.*; +import java.nio.file.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; @@ -21,16 +22,19 @@ public class DirSelectDlg extends BasicDlg implements TreeSelectionListener, TreeWillExpandListener { - private File selectedFile; - private JButton btnApprove; - private JButton btnCancel; - private JTree tree; - private FileTreeNode rootNode; + private File selectedFile; + private FileTreeNodeComparator comparator; + private FileTreeNode rootNode; + private DefaultTreeModel treeModel; + private JTree tree; + private JButton btnApprove; + private JButton btnCancel; + private JButton btnNew; - public static File selectDirectory( Window owner ) + public static File selectDirectory( Window owner, File preselection ) { - DirSelectDlg dlg = new DirSelectDlg( owner ); + DirSelectDlg dlg = new DirSelectDlg( owner, preselection ); dlg.setVisible( true ); return dlg.selectedFile; } @@ -42,7 +46,19 @@ public static File selectDirectory( Window owner ) public void valueChanged( TreeSelectionEvent e ) { if( e.getSource() == this.tree ) { - this.btnApprove.setEnabled( getSelectedFile() != null ); + /* + * Pruefen, ob der ausgewaehlte Eintrag ein Verzeichnis ist. + * Das ist notwendig, + * da ein symbolischer Link ausgewaehlt sein koennte, + * der auf etwas anderes als ein Verzeichnis zeigt. + */ + boolean state = false; + File file = getSelectedFile(); + if( file != null ) { + state = file.isDirectory(); + } + this.btnApprove.setEnabled( state ); + this.btnNew.setEnabled( state ); } } @@ -88,6 +104,10 @@ protected boolean doAction( EventObject e ) rv = true; doApprove(); } + else if( src == this.btnNew ) { + rv = true; + doNew(); + } else if( src == this.btnCancel ) { rv = true; this.selectedFile = null; @@ -101,9 +121,11 @@ else if( src == this.btnCancel ) { /* --- private Konstruktoren und Mothoden --- */ - private DirSelectDlg( Window owner ) + private DirSelectDlg( Window owner, File preselection ) { super( owner, "Verzeichnisauswahl" ); + this.selectedFile = null; + this.comparator = FileTreeNodeComparator.getIgnoreCaseInstance(); // Fensterinhalt @@ -125,7 +147,8 @@ private DirSelectDlg( Window owner ) this.rootNode = new FileTreeNode( null, null, true ); refreshNode( this.rootNode ); - this.tree = new JTree( this.rootNode ); + this.treeModel = new DefaultTreeModel( this.rootNode ); + this.tree = new JTree( this.treeModel ); this.tree.setSelectionModel( selModel ); this.tree.setEditable( false ); this.tree.setRootVisible( false ); @@ -135,7 +158,7 @@ private DirSelectDlg( Window owner ) // Knoepfe - JPanel panelBtn = new JPanel( new GridLayout( 1, 2, 5, 5 ) ); + JPanel panelBtn = new JPanel( new GridLayout( 1, 3, 5, 5 ) ); gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0.0; gbc.weighty = 0.0; @@ -146,6 +169,10 @@ private DirSelectDlg( Window owner ) this.btnApprove.setEnabled( false ); panelBtn.add( this.btnApprove ); + this.btnNew = new JButton( "Neu..." ); + this.btnNew.setEnabled( false ); + panelBtn.add( this.btnNew ); + this.btnCancel = new JButton( "Abbrechen" ); panelBtn.add( this.btnCancel ); @@ -161,11 +188,44 @@ private DirSelectDlg( Window owner ) this.btnCancel.addActionListener( this ); this.btnCancel.addKeyListener( this ); + this.btnNew.addActionListener( this ); + this.btnNew.addKeyListener( this ); + // Fenstergroesse setSize( 300, 300 ); setResizable( true ); setParentCentered(); + + + // vorausgewaehltes Verzeichnis einstellen + if( preselection != null ) { + final java.util.List nameItems = new ArrayList<>(); + while( preselection != null ) { + String s = preselection.getName(); + if( s != null ) { + if( s.isEmpty() ) { + s = null; + } + } + if( s == null ) { + s = preselection.getPath(); + } + if( s == null ) { + break; + } + nameItems.add( s ); + preselection = preselection.getParentFile(); + } + EventQueue.invokeLater( + new Runnable() + { + public void run() + { + selectPath( nameItems ); + } + } ); + } } @@ -181,6 +241,51 @@ private void doApprove() } + private void doNew() + { + TreePath parentPath = this.tree.getSelectionPath(); + if( parentPath != null ) { + Object o = parentPath.getLastPathComponent(); + if( o != null ) { + if( o instanceof FileTreeNode ) { + FileTreeNode parent = (FileTreeNode) o; + if( parent != null ) { + refreshNode( parent ); + File parentFile = parent.getFile(); + if( parentFile != null ) { + if( parentFile.isDirectory() ) { + File newDir = EmuUtil.createDir( this, parentFile ); + if( newDir != null ) { + try { + FileTreeNode newNode = new FileTreeNode( + parent, + newDir.toPath(), + false ); + parent.add( newNode ); + this.treeModel.nodeStructureChanged( parent ); + final JTree tree = this.tree; + final TreePath newPath = parentPath.pathByAddingChild( + newNode ); + EventQueue.invokeLater( + new Runnable() + { + public void run() + { + tree.setSelectionPath( newPath ); + } + } ); + } + catch( InvalidPathException ex ) {} + } + } + } + } + } + } + } + } + + private File getSelectedFile() { File rv = null; @@ -200,29 +305,86 @@ private File getSelectedFile() private void refreshNode( FileTreeNode node ) { if( !node.hasChildrenLoaded() ) { + boolean fsRoot = false; + Iterable entries = null; try { - boolean fsRoot = false; - File[] entries = null; - File file = node.getFile(); - if( file != null ) { - entries = file.listFiles(); + Path path = node.getPath(); + if( path != null ) { + if( Files.isDirectory( path, LinkOption.NOFOLLOW_LINKS ) + && !Files.isSymbolicLink( path ) ) + { + entries = Files.newDirectoryStream( path ); + } } else { - entries = File.listRoots(); + entries = FileSystems.getDefault().getRootDirectories(); fsRoot = true; } - if( entries != null ) { - Arrays.sort( entries ); - for( int i = 0; i < entries.length; i++ ) { - File f = entries[ i ]; - if( f.isDirectory() && (fsRoot || !f.isHidden()) ) { - node.add( new FileTreeNode( node, f, fsRoot ) ); + for( Path tmpPath : entries ) { + try { + if( Files.isDirectory( tmpPath ) + && (fsRoot || !Files.isHidden( tmpPath )) ) + { + node.add( new FileTreeNode( node, tmpPath, fsRoot ) ); } } + catch( IOException ex ) {} } } catch( Exception ex ) {} + finally { + if( entries != null ) { + if( entries instanceof Closeable ) { + EmuUtil.doClose( (Closeable) entries ); + } + } + } + node.sort( this.comparator ); node.setChildrenLoaded( true ); } } + + + private void selectPath( java.util.List nameItems ) + { + TreeNode node = this.rootNode; + java.util.List nodePath = new ArrayList<>(); + nodePath.add( node ); + int nameIdx = nameItems.size() - 1; + while( (node != null) && (nameIdx >= 0) ) { + if( node instanceof FileTreeNode ) { + refreshNode( (FileTreeNode) node ); + } + String name = nameItems.get( nameIdx ); + int n = node.getChildCount(); + for( int i = 0; i < n; i++ ) { + TreeNode child = node.getChildAt( i ); + if( child != null ) { + String s = child.toString(); + if( s != null ) { + if( s.equals( name ) ) { + nodePath.add( child ); + node = child; + break; + } + } + } + } + --nameIdx; + } + if( nodePath.size() > 1 ) { + final TreePath tp = new TreePath( nodePath.toArray() ); + final JTree tree = this.tree; + EventQueue.invokeLater( + new Runnable() + { + public void run() + { + tree.expandPath( tp ); + tree.makeVisible( tp ); + tree.setSelectionPath( tp ); + } + } ); + } + } } diff --git a/src/jkcemu/base/EmuMemView.java b/src/jkcemu/base/EmuMemView.java new file mode 100644 index 0000000..17bcaa4 --- /dev/null +++ b/src/jkcemu/base/EmuMemView.java @@ -0,0 +1,18 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Interface fuer Zugriff auf den Arbeitsspeicher + */ + +package jkcemu.base; + +import java.lang.*; +import z80emu.Z80MemView; + + +public interface EmuMemView extends Z80MemView +{ + public int getBasicMemByte( int addr ); +} diff --git a/src/jkcemu/base/EmuSys.java b/src/jkcemu/base/EmuSys.java index 6eb83e9..9eec437 100644 --- a/src/jkcemu/base/EmuSys.java +++ b/src/jkcemu/base/EmuSys.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -16,6 +16,7 @@ import java.text.*; import java.util.*; import javax.swing.JOptionPane; +import jkcemu.Main; import jkcemu.audio.AudioOut; import jkcemu.base.ScreenFrm; import jkcemu.disk.*; @@ -45,6 +46,7 @@ public enum Chessman { protected EmuThread emuThread; protected ScreenFrm screenFrm; + protected String propPrefix; protected Color colorWhite; protected Color colorRedLight; protected Color colorRedDark; @@ -67,12 +69,16 @@ public enum Chessman { private static int[] tmp7SegYPoints = new int[ base7SegHYPoints.length ]; - public EmuSys( EmuThread emuThread, Properties props ) + public EmuSys( + EmuThread emuThread, + Properties props, + String propPrefix ) { this.emuThread = emuThread; this.screenFrm = emuThread.getScreenFrm(); this.pasteThread = null; this.pasteIter = null; + this.propPrefix = propPrefix; createColors( props ); } @@ -122,6 +128,7 @@ public synchronized void cancelPastingText() this.pasteIter = null; this.screenFrm.firePastingTextFinished(); } + keyReleased(); } @@ -175,6 +182,12 @@ public int getAppStartStackInitValue() } + public int getBasicMemByte( int addr ) + { + return getMemByte( addr, false ); + } + + public int getBorderColorIndex() { return BLACK; @@ -322,6 +335,12 @@ public Plotter getPlotter() } + public String getPropPrefix() + { + return this.propPrefix; + } + + protected int getScreenChar( CharRaster chRaster, int chX, int chY ) { return -1; @@ -511,6 +530,12 @@ public boolean isAutoScreenRefresh() } + public boolean isPastingText() + { + return this.pasteThread != null; + } + + protected boolean isReloadExtROMsOnPowerOnEnabled( Properties props ) { return EmuUtil.getBooleanProperty( @@ -569,9 +594,23 @@ public void loadIntoSecondSystem( * als die gerade aktuelle Bank geladen werden, * muss die Methode ueberschrieben werden. */ - public void loadMemByte( int addr, int value ) - { - setMemByte( addr & 0xFFFF, value ); + public void loadIntoMem( + int begAddr, + byte[] data, + int idx, + int len, + FileFormat fileFmt, + int fileType ) + { + if( data != null ) { + int n = len; + int dst = begAddr; + while( (idx < data.length) && (dst < 0x10000) && (n > 0) ) { + setMemByte( dst++, data[ idx++ ] ); + --n; + } + updSysCells( begAddr, len, fileFmt, fileType ); + } } @@ -621,7 +660,7 @@ protected static void paint7SegDigit( * die Moeglichkeit, * selbst die Bildschirmausgabe grafisch darzustellen. * Wenn nicht (Rueckgabewert false) werden die Methoden getColorCount() - * und getColorIndex( x, y ) ausgerufen. + * und getColorIndex( x, y ) aufgerufen. */ public boolean paintScreen( Graphics g, int x, int y, int screenScale ) { @@ -659,50 +698,23 @@ protected boolean pasteChar( char ch ) } - protected byte[] readFile( String fileName, int maxLen, String objName ) - { - return EmuUtil.readFile( - this.emuThread.getScreenFrm(), - fileName, - maxLen, - objName ); - } - - - protected byte[] readFileByProperty( + protected byte[] readFontByProperty( Properties props, String propName, - int maxLen, - String objName ) + int maxLen ) { return props != null ? - readFile( + EmuUtil.readFile( + this.emuThread.getScreenFrm(), props.getProperty( propName ), + true, maxLen, - objName ) + "Zeichensatzdatei" ) : null; } - protected byte[] readFontByProperty( - Properties props, - String propName, - int maxLen ) - { - return readFileByProperty( props, propName, maxLen, "Zeichensatzdatei" ); - } - - - protected byte[] readROMByProperty( - Properties props, - String propName, - int maxLen ) - { - return readFileByProperty( props, propName, maxLen, "ROM-Datei" ); - } - - - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { return 0xFF; } @@ -720,6 +732,17 @@ protected byte[] readResource( String resource ) } + protected byte[] readROMFile( String fileName, int maxLen, String objName ) + { + return EmuUtil.readFile( + this.emuThread.getScreenFrm(), + fileName, + true, + maxLen, + objName ); + } + + /* * Diese Methode reassembliert Bytes als Zeichenkette * bis einschliesslich das Byte, bei dem Bit 7 gesetzt ist. @@ -945,6 +968,12 @@ public void saveBasicProgram() } + public boolean setBasicMemByte( int addr, int value ) + { + return setMemByte( addr, value ); + } + + public void setFloppyDiskDrive( int idx, FloppyDiskDrive drive ) { // leer @@ -1025,7 +1054,10 @@ public synchronized void startPastingText( String text ) if( !text.isEmpty() ) { cancelPastingText(); this.pasteIter = new StringCharacterIterator( text ); - this.pasteThread = new Thread( this ); + this.pasteThread = new Thread( + Main.getThreadGroup(), + this, + "JKCEMU text paste" ); this.pasteThread.start(); done = true; } @@ -1108,6 +1140,12 @@ public boolean supportsSaveBasic() } + public boolean supportsStereoSound() + { + return false; + } + + public boolean supportsUSB() { return (getVDIP() != null); @@ -1120,17 +1158,23 @@ public void updKeyboardMatrix( int[] kbMatrix ) } + public void updDebugScreen() + { + // leer + } + + public void updSysCells( - int begAddr, - int len, - Object fileFmt, - int fileType ) + int begAddr, + int len, + FileFormat fileFmt, + int fileType ) { // leer } - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { // leer } diff --git a/src/jkcemu/base/EmuSysAudioDataStream.java b/src/jkcemu/base/EmuSysAudioDataStream.java index e0da90b..f392490 100644 --- a/src/jkcemu/base/EmuSysAudioDataStream.java +++ b/src/jkcemu/base/EmuSysAudioDataStream.java @@ -1,5 +1,5 @@ /* - * (c) 2011 Jens Mueller + * (c) 2011-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -8,6 +8,7 @@ package jkcemu.base; +import jkcemu.audio.AudioOut; import java.io.*; import java.lang.*; import javax.sound.sampled.AudioFormat; @@ -21,16 +22,6 @@ public class EmuSysAudioDataStream extends InputStream * Die abgeleitete Klasse bietet Zugriff auf das interne Byte-Array, * um daraus die Audio-Daten wieder lesen zu koennen. * Dadurch wird eine Duplizierung der Audio-Daten vermieden. - * - * Sind in diesen Audio-Daten auch neative Werte enthalten, - * dann gilt folgende Bedeutung: - * negativer Wert -> negative Phase - * positiver Wert -> positiver Phase - * Betrag des Wertes -> Anzahl Samples - * - * Bei ausschliesslich positiven Werten gilt: - * Nach jedem Byte erfolgt ein Phasenwechsel. - * Der Wert des Bytes gibt die Anzahl der Samples ab. */ public static class ByteOutBuf extends ByteArrayOutputStream { @@ -55,10 +46,10 @@ public byte[] getBuf() private byte[] audioBuf; private int audioLen; private int audioPos; + private float sampleRate; private long frameLen; private int lastCnt; private boolean lastPhase; - private boolean negValues; protected EmuSysAudioDataStream( @@ -67,35 +58,55 @@ protected EmuSysAudioDataStream( int offs, int len ) { - this.srcBuf = buf; - this.srcPos = offs; - this.srcEOF = Math.min( offs + len, buf.length ); - this.outBuf = new ByteOutBuf( 0x10000 ); - this.audioFmt = new AudioFormat( sampleRate, 8, 1, false, false ); - this.audioBuf = null; - this.audioLen = 0; - this.audioPos = 0; - this.frameLen = 0; - this.lastCnt = 0; - this.lastPhase = false; - this.negValues = false; + this.sampleRate = sampleRate; + this.srcBuf = buf; + this.srcPos = offs; + this.srcEOF = Math.min( offs + len, buf.length ); + this.outBuf = new ByteOutBuf( 0x10000 ); + this.audioFmt = null; + this.audioBuf = null; + this.audioLen = 0; + this.audioPos = 0; + this.frameLen = 0; + this.lastCnt = 0; + this.lastPhase = true; } - protected void addSamples( int value ) + protected void addPhaseChangeSamples( int samples ) { - this.outBuf.write( value ); - if( value < 0 ) { - this.frameLen -= value; - this.negValues = true; - } else { - this.frameLen += value; + if( samples > 0 ) { + this.lastPhase = !this.lastPhase; + addSamples( this.lastPhase ? samples : -samples ); + } + } + + + protected void addSamples( int samples ) + { + this.lastPhase = (samples > 0); + while( samples <= -128 ) { + this.outBuf.write( -128 ); + this.frameLen += 128; + samples += 128; + } + while( samples >= 127 ) { + this.outBuf.write( 127 ); + this.frameLen += 127; + samples -= 127; + } + if( samples != 0 ) { + this.outBuf.write( samples ); + this.frameLen += Math.abs( samples ); } } public AudioFormat getAudioFormat() { + if( this.audioFmt == null ) { + this.audioFmt = new AudioFormat( this.sampleRate, 8, 1, false, false ); + } return this.audioFmt; } @@ -134,13 +145,32 @@ protected int readSourceBytes( byte[] buf ) } + protected void setSampleRate( int sampleRate ) + { + this.sampleRate = sampleRate; + } + + protected boolean sourceByteAvailable() { return (this.srcPos < this.srcEOF); } - protected boolean skipString( String text ) + protected void skipSourceBytes( int len ) + { + int pos = this.srcPos + len; + if( pos < 0 ) { + this.srcPos = 0; + } else if( pos > this.srcEOF ) { + this.srcPos = this.srcEOF; + } else { + this.srcPos = pos; + } + } + + + protected boolean skipSourceString( String text ) { boolean rv = false; int len = text.length(); @@ -197,23 +227,18 @@ public int read() } if( this.audioPos < this.audioLen ) { byte value = this.audioBuf[ this.audioPos++ ]; - if( this.negValues ) { - if( value < 0 ) { - this.lastCnt = (int) -value; - this.lastPhase = false; - } else { - this.lastCnt = (int) value; - this.lastPhase = true; - } + if( value < 0 ) { + this.lastCnt = (int) -value; + this.lastPhase = false; } else { - this.lastCnt = (int) value & 0xFF; - this.lastPhase = !this.lastPhase; + this.lastCnt = (int) value; + this.lastPhase = true; } } } if( this.lastCnt > 0 ) { --this.lastCnt; - rv = (this.lastPhase ? 255 : 0); + rv = (this.lastPhase ? AudioOut.MAX_USED_VALUE : 0); } return rv; } diff --git a/src/jkcemu/base/EmuThread.java b/src/jkcemu/base/EmuThread.java index d392e36..9cd2ae2 100644 --- a/src/jkcemu/base/EmuThread.java +++ b/src/jkcemu/base/EmuThread.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -26,48 +26,51 @@ public class EmuThread extends Thread implements Z80IOSystem, - Z80Memory + Z80Memory, + EmuMemView { public enum ResetLevel { NO_RESET, WARM_RESET, COLD_RESET, POWER_ON }; - private ScreenFrm screenFrm; - private Z80CPU z80cpu; - private Object monitor; - private JoystickFrm joyFrm; - private JoystickThread[] joyThreads; - private byte[] ram; - private byte[] ramExtended; - private RAMFloppy ramFloppy1; - private RAMFloppy ramFloppy2; - private PrintMngr printMngr; - private volatile AudioIn audioIn; - private volatile AudioOut audioOut; - private volatile LoadData loadData; - private volatile ResetLevel resetLevel; - private volatile boolean emuRunning; - private volatile EmuSys emuSys; - private volatile Boolean iso646de; + private ScreenFrm screenFrm; + private Z80CPU z80cpu; + private Object monitor; + private FileTimesViewFactory fileTimesViewFactory; + private JoystickFrm joyFrm; + private JoystickThread[] joyThreads; + private byte[] ram; + private byte[] ramExtended; + private RAMFloppy ramFloppy1; + private RAMFloppy ramFloppy2; + private PrintMngr printMngr; + private volatile AudioIn audioIn; + private volatile AudioOut audioOut; + private volatile LoadData loadData; + private volatile ResetLevel resetLevel; + private volatile boolean emuRunning; + private volatile EmuSys emuSys; + private volatile Boolean iso646de; public EmuThread( ScreenFrm screenFrm, Properties props ) { - super( "JKCEMU CPU" ); - this.screenFrm = screenFrm; - this.z80cpu = new Z80CPU( this, this ); - this.monitor = "a monitor object for synchronization"; - this.joyFrm = null; - this.joyThreads = new JoystickThread[ 2 ]; - this.ram = new byte[ 0x10000 ]; - this.ramExtended = null; - this.ramFloppy1 = new RAMFloppy(); - this.ramFloppy2 = new RAMFloppy(); - this.printMngr = new PrintMngr(); - this.audioIn = null; - this.audioOut = null; - this.loadData = null; - this.resetLevel = ResetLevel.POWER_ON; - this.emuRunning = false; - this.emuSys = null; + super( Main.getThreadGroup(), "JKCEMU CPU" ); + this.screenFrm = screenFrm; + this.z80cpu = new Z80CPU( this, this ); + this.monitor = "a monitor object for synchronization"; + this.fileTimesViewFactory = new NIOFileTimesViewFactory(); + this.joyFrm = null; + this.joyThreads = new JoystickThread[ 2 ]; + this.ram = new byte[ 0x10000 ]; + this.ramExtended = null; + this.ramFloppy1 = new RAMFloppy(); + this.ramFloppy2 = new RAMFloppy(); + this.printMngr = new PrintMngr(); + this.audioIn = null; + this.audioOut = null; + this.loadData = null; + this.resetLevel = ResetLevel.POWER_ON; + this.emuRunning = false; + this.emuSys = null; Arrays.fill( this.joyThreads, null ); applySettings( props ); } @@ -123,6 +126,8 @@ public synchronized void applySettings( Properties props ) emuSys = new LLC1( this, props ); } else if( sysName.startsWith( "LLC2" ) ) { emuSys = new LLC2( this, props ); + } else if( sysName.startsWith( "NANOS" ) ) { + emuSys = new NANOS( this, props ); } else if( sysName.startsWith( "PC/M" ) ) { emuSys = new PCM( this, props ); } else if( sysName.startsWith( "Poly880" ) ) { @@ -245,6 +250,12 @@ public AudioIn getAudioIn() } + public AudioOut getAudioOut() + { + return this.audioOut; + } + + public static int getDefaultSpeedKHz( Properties props ) { int rv = A5105.getDefaultSpeedKHz(); @@ -294,6 +305,9 @@ else if( sysName.startsWith( "LLC1" ) ) { else if( sysName.startsWith( "LLC2" ) ) { rv = LLC2.getDefaultSpeedKHz(); } + else if( sysName.startsWith( "NANOS" ) ) { + rv = NANOS.getDefaultSpeedKHz(); + } else if( sysName.startsWith( "PC/M" ) ) { rv = PCM.getDefaultSpeedKHz(); } @@ -313,7 +327,7 @@ else if( sysName.startsWith( "Z1013" ) ) { rv = Z1013.getDefaultSpeedKHz( props ); } else if( sysName.startsWith( "ZXSpectrum" ) ) { - rv = ZXSpectrum.getDefaultSpeedKHz(); + rv = ZXSpectrum.getDefaultSpeedKHz( props ); } } return rv; @@ -326,12 +340,6 @@ public EmuSys getEmuSys() } - public Boolean getISO646DE() - { - return this.iso646de; - } - - public synchronized byte[] getExtendedRAM( int size ) { byte[] rv = null; @@ -360,6 +368,18 @@ public synchronized byte[] getExtendedRAM( int size ) } + public FileTimesViewFactory getFileTimesViewFactory() + { + return this.fileTimesViewFactory; + } + + + public Boolean getISO646DE() + { + return this.iso646de; + } + + public PrintMngr getPrintMngr() { return this.printMngr; @@ -450,11 +470,7 @@ public void keyTyped( char ch ) { if( this.emuSys != null ) { if( this.emuSys.getSwapKeyCharCase() ) { - if( Character.isUpperCase( ch ) ) { - ch = Character.toLowerCase( ch ); - } else if( Character.isLowerCase( ch ) ) { - ch = Character.toUpperCase( ch ); - } + ch = TextUtil.toReverseCase( ch ); } if( this.emuSys.getConvertKeyCharToISO646DE() ) { this.emuSys.keyTyped( TextUtil.toISO646DE( ch ) ); @@ -492,6 +508,13 @@ public void setAudioOut( AudioOut audioOut ) } + public void setBasicMemWord( int addr, int value ) + { + this.emuSys.setBasicMemByte( addr, value & 0xFF ); + this.emuSys.setBasicMemByte( addr + 1, (value >> 8) & 0xFF ); + } + + public void setISO646DE( boolean state ) { this.iso646de = state; @@ -561,16 +584,9 @@ public void updCPUSpeed( Properties props ) public void writeAudioPhase( boolean phase ) { AudioOut audioOut = this.audioOut; - if( audioOut != null ) + if( audioOut != null ) { audioOut.writePhase( phase ); - } - - - public void writeAudioValue( byte value ) - { - AudioOut audioOut = this.audioOut; - if( audioOut != null ) - audioOut.writeValue( value ); + } } @@ -596,11 +612,19 @@ public void fireReset( ResetLevel resetLevel ) this.loadData = null; this.z80cpu.fireExit(); } + AudioIn audioIn = this.audioIn; + if( audioIn != null ) { + audioIn.reset(); + } + AudioOut audioOut = this.audioOut; + if( audioOut != null ) { + audioOut.reset(); + } } /* - * Diese Methode ladet Daten in den Arbeitsspeicher und startet + * Diese Methode laedt Daten in den Arbeitsspeicher und startet * diese bei Bedarf. * Sind die Datenbytes als Programm zu starten, so werden sie * in den Emulations-Thread ueberfuehrt und dort geladen und gestartet. @@ -638,24 +662,33 @@ public void stopEmulator() } + /* --- EmuMemView --- */ + + @Override + public int getBasicMemByte( int addr ) + { + return this.emuSys.getBasicMemByte( addr & 0xFFFF ); + } + + /* --- Z80IOSystem --- */ @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; if( this.emuSys != null ) { - rv = this.emuSys.readIOByte( port ); + rv = this.emuSys.readIOByte( port, tStates ); } return rv; } @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { if( this.emuSys != null ) - this.emuSys.writeIOByte( port, value ); + this.emuSys.writeIOByte( port, value, tStates ); } @@ -742,46 +775,52 @@ public void run() this.z80cpu.setRegPC( this.emuSys.getResetStartAddress( this.resetLevel ) ); } - } - // RAM-Floppies und Druckmanager zuruecksetzen - this.printMngr.reset(); - this.ramFloppy1.reset(); - this.ramFloppy2.reset(); - if( (this.emuSys != null) - && (this.resetLevel == ResetLevel.POWER_ON) - && Main.getBooleanProperty( - "jkcemu.ramfloppy.clear_on_power_on", - false ) ) - { - if( this.emuSys.supportsRAMFloppy1() - && (this.ramFloppy1.getUsedSize() > 0) ) - { - this.ramFloppy1.clear(); - } - if( this.emuSys.supportsRAMFloppy2() - && (this.ramFloppy2.getUsedSize() > 0) ) + // RAM-Floppies und Druckmanager zuruecksetzen + this.printMngr.reset(); + this.ramFloppy1.reset(); + this.ramFloppy2.reset(); + if( (this.emuSys != null) + && (this.resetLevel == ResetLevel.POWER_ON) + && Main.getBooleanProperty( + "jkcemu.ramfloppy.clear_on_power_on", + false ) ) { - this.ramFloppy2.clear(); + if( this.emuSys.supportsRAMFloppy1() + && (this.ramFloppy1.getUsedSize() > 0) ) + { + this.ramFloppy1.clear(); + } + if( this.emuSys.supportsRAMFloppy2() + && (this.ramFloppy2.getUsedSize() > 0) ) + { + this.ramFloppy2.clear(); + } } - } - // Fenster informieren - final Frame[] frms = Frame.getFrames(); - if( frms != null ) { - EventQueue.invokeLater( - new Runnable() - { - @Override - public void run() - { - for( Frame f : frms ) { - if( f instanceof BasicFrm ) { - ((BasicFrm) f).resetFired(); - } - } - } - } ); + // AutoLoader starten + AutoLoader.start( this, Main.getProperties() ); + + // AutoInputWorker starten + AutoInputWorker.start( this, Main.getProperties() ); + + // Fenster informieren + final Frame[] frms = Frame.getFrames(); + if( frms != null ) { + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + for( Frame f : frms ) { + if( f instanceof BasicFrm ) { + ((BasicFrm) f).resetFired(); + } + } + } + } ); + } } // in die Z80-Emulation verzweigen diff --git a/src/jkcemu/base/EmuUtil.java b/src/jkcemu/base/EmuUtil.java index 0c46c25..714dfad 100644 --- a/src/jkcemu/base/EmuUtil.java +++ b/src/jkcemu/base/EmuUtil.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -15,12 +15,13 @@ import java.lang.*; import java.net.*; import java.nio.channels.*; +import java.nio.file.*; import java.text.*; import java.util.*; import java.util.regex.*; import java.util.zip.*; import javax.swing.*; -import javax.swing.filechooser.*; +import javax.swing.filechooser.FileSystemView; import javax.swing.table.*; import jkcemu.Main; import jkcemu.text.TextUtil; @@ -28,6 +29,17 @@ public class EmuUtil { + public static String[] headersaveFileTypeItems = { + "", + "A - Assemblerquelltext", + "B - BASIC-Programm", + "b - Tiny-BASIC-Programm", + "C - MC-Programm, selbststartend", + "M - MC-Programm", + "E - EPROM-Inhalt", + "I - Information (Text)", + "T - Text" }; + public static final String[] archiveFileExtensions = { ".jar", ".tar.gz", ".tar", ".tgz", ".zip" }; @@ -46,6 +58,14 @@ public class EmuUtil fmt2FileFilter = null; + public static void addHeadersaveFileTypeItemsTo( JComboBox combo ) + { + for( String s : headersaveFileTypeItems ) { + combo.addItem( s ); + } + } + + public static void appendHTML( StringBuilder buf, String text ) { if( text != null ) { @@ -129,63 +149,26 @@ else if( size >= kb ) { } if( buf.length() > 0 ) { if( forceSizeInBytes ) { - buf.append( " (" ); + if( size == 1 ) { + buf.append( "1 Byte" ); + } else { + buf.append( " (" ); + buf.append( getIntegerFormat().format( size ) ); + buf.append( " Bytes" ); + buf.append( (char) ')' ); + } + } + } else { + if( size == 1 ) { + buf.append( "1 Byte" ); + } else { buf.append( getIntegerFormat().format( size ) ); buf.append( " Bytes" ); - buf.append( (char) ')' ); } - } else { - buf.append( getIntegerFormat().format( size ) ); - buf.append( " Bytes" ); } } - public static boolean applyWindowSettings( - Properties props, - BasicFrm frm, - boolean resizable ) - { - boolean rv = false; - if( (frm != null) && (props != null) ) { - String prefix = frm.getSettingsPrefix(); - - int x = parseInt( - props.getProperty( prefix + ".window.x" ), - -1, - -1 ); - int y = parseInt( - props.getProperty( prefix + ".window.y" ), - -1, - -1 ); - if( (x >= 0) && (y >= 0) ) { - if( resizable ) { - int w = parseInt( - props.getProperty( prefix + ".window.width" ), - -1, - -1 ); - int h = parseInt( - props.getProperty( prefix + ".window.height" ), - -1, - -1 ); - if( frm.isVisible() ) { - frm.setSize( w, h ); - } else { - frm.setBounds( x, y, w, h ); - } - rv = true; - } else { - if( !frm.isVisible() ) { - frm.setLocation( x, y ); - rv = true; - } - } - } - } - return rv; - } - - public static File askForOutputDir( Window owner, File presetDir, @@ -312,40 +295,6 @@ public static Pattern compileFileNameMask( String text ) } - public static boolean copyFile( - File srcFile, - File dstFile ) throws IOException - { - boolean done = false; - boolean created = false; - long millis = srcFile.lastModified(); - InputStream in = null; - OutputStream out = null; - try { - in = new BufferedInputStream( new FileInputStream( srcFile ) ); - out = new BufferedOutputStream( new FileOutputStream( dstFile ) ); - created = true; - - int b = in.read(); - while( b >= 0 ) { - out.write( b ); - b = in.read(); - } - out.close(); - dstFile.setLastModified( millis ); - done = true; - } - finally { - EmuUtil.doClose( in ); - EmuUtil.doClose( out ); - } - if( created && !done ) { - dstFile.delete(); - } - return done; - } - - public static void copyToClipboard( Component owner, String text ) { try { @@ -397,7 +346,7 @@ public static File createDir( Component owner, File parent ) public static JButton createImageButton( - Window window, + Window window, String imgName, String text ) { @@ -465,130 +414,35 @@ public long skip( long n ) throws IOException } - /* - * Rueckgabewert: - * true: Aktion ausgefuehrt - * false: Aktion abgebrochen - */ - public static boolean deleteFiles( Component owner, File[] files ) - { - boolean rv = false; - if( files != null ) { - File aFile = null; - File aDir = null; - int nDirs = 0; - int nFiles = 0; - for( int i = 0; i < files.length; i++ ) { - File file = files[ i ]; - if( file != null ) { - if( file.isDirectory() ) { - aDir = file; - nDirs++; - } else { - aFile = file; - nFiles++; - } - } - } - if( (nDirs > 0) || (nFiles > 0) ) { - StringBuilder buf = new StringBuilder( 128 ); - buf.append( "M\u00F6chten Sie " ); - if( nDirs > 0 ) { - if( nDirs == 1 ) { - if( aDir != null ) { - buf.append( "das Verzeichnis\n\'" ); - buf.append( aDir.getPath() ); - buf.append( "\'\n" ); - } else { - buf.append( "ein Verzeichnis " ); - } - } else { - buf.append( nDirs ); - buf.append( " Verzeichnisse " ); - } - } - if( nFiles > 0 ) { - if( nDirs > 0 ) { - buf.append( "und " ); - } - if( nFiles == 1 ) { - if( aFile != null ) { - buf.append( "die Datei\n\'" ); - buf.append( aFile.getPath() ); - buf.append( "\'\n" ); - } else { - buf.append( "eine Datei " ); - } - } else { - buf.append( nFiles ); - buf.append( " Dateien " ); - } - } - buf.append( "l\u00F6schen?" ); - if( BasicDlg.showYesNoDlg( owner, buf.toString() ) ) { - RunStatus runStatus = new RunStatus(); - for( - int k = 0; - (k < files.length) && !runStatus.wasCancelled(); - k++ ) - { - deleteFile( owner, files[ k ], runStatus ); - } - if( runStatus.isQuiet() - && runStatus.wasFailed() - && !runStatus.wasCancelled() ) - { - BasicDlg.showErrorDlg( - owner, - "Es konnten nicht alle Dateien/Verzeichnisse\n" - + " gel\u00F6scht werden." ); - } - rv = true; - } - } - } - return rv; - } - - - public static void doClose( Closeable stream ) + public static OutputStream createOptionalGZipOutputStream( File file ) + throws IOException { - if( stream != null ) { + boolean gzip = isGZipFile( file ); + OutputStream out = new FileOutputStream( file ); + if( gzip ) { try { - stream.close(); + out = new GZIPOutputStream( out ); } - catch( IOException ex ) {} - } - } - - - public static void doClose( ServerSocket serverSocket ) - { - if( serverSocket != null ) { - try { - serverSocket.close(); + catch( IOException ex ) { + doClose( out ); + throw ex; } - catch( IOException ex ) {} } + return out; } - public static void doClose( Socket socket ) + public static Set createPathSet() { - if( socket != null ) { - try { - socket.close(); - } - catch( IOException ex ) {} - } + return new TreeSet<>( createPathComparator() ); } - public static void doClose( ZipFile zip ) + public static void doClose( Closeable stream ) { - if( zip != null ) { + if( stream != null ) { try { - zip.close(); + stream.close(); } catch( IOException ex ) {} } @@ -634,6 +488,34 @@ public static boolean equalsLookAndFeel( String className ) } + public static boolean equalsRegion( + byte[] a1, + int idx1, + byte[] a2, + int idx2, + int len ) + { + boolean rv = false; + if( (a1 != null) && (a2 != null) + && (idx1 >= 0) && (idx2 >= 0) ) + { + if( ((idx1 + len) <= a1.length) + && ((idx2 + len) <= a2.length) ) + { + rv = true; + for( int i = 0; i < len; i++ ) { + if( a1[ idx1++ ] != a2[ idx2++ ] ) { + rv = false; + break; + } + } + } + } + return rv; + } + + + public static void exitSysError( Component parent, String msg, @@ -655,13 +537,10 @@ public static void exitSysError( PrintWriter errWriter = null; try { String fName = "jkcemu_err.log"; - String fDir = System.getProperty( "user.home" ); - if( fDir == null ) { - fDir = ""; - } - errFile = (fDir.length() > 0 ? - new File( fDir, fName ) - : new File( fName ) ); + File fDir = getHomeDirFile(); + errFile = (fDir != null ? + new File( fDir, fName ) + : new File( fName )); errWriter = new PrintWriter( new FileWriter( errFile ) ); errWriter.println( "Bitte senden Sie diese Datei an" @@ -669,7 +548,7 @@ public static void exitSysError( errWriter.println( "Please send this file to info@jens-mueller.org" ); errWriter.println(); errWriter.println( "--- Program ---" ); - errWriter.write( Main.VERSION ); + errWriter.write( Main.APPINFO ); errWriter.println(); if( msg != null ) { @@ -744,31 +623,13 @@ public static int[] extractAddressesFromFileName( String fileName ) break; } pos++; - boolean hex = true; - long value = 0; - for( int i = 0; i < 4; i++ ) { - char ch = fileName.charAt( pos + i ); - if( (ch >= '0') && (ch <= '9') ) { - value = (value << 4) | (ch - '0'); - } else if( (ch >= 'A') && (ch <= 'F') ) { - value = (value << 4) | (ch - 'A' + 10); - } else if( (ch >= 'a') && (ch <= 'f') ) { - value = (value << 4) | (ch - 'a' + 10); - } else { - hex = false; - break; - } - } + long value = getHex4( fileName, pos ); if( (pos + 4) < len ) { - char ch = fileName.charAt( pos + 4 ); - if( ((ch >= '0') && (ch <= '9')) - || ((ch >= 'A') && (ch <= 'Z')) - || ((ch >= 'a') && (ch <= 'z')) ) - { - hex = false; + if( isHexChar( fileName.charAt( pos + 4 ) ) ) { + value = -1; } } - if( hex ) { + if( value >= 0 ) { m = (m << 16) | value; n++; pos += 4; @@ -835,43 +696,24 @@ public static File fileDrop( Component owner, DropTargetDropEvent e ) { File file = null; if( isFileDrop( e ) ) { - e.acceptDrop( DnDConstants.ACTION_COPY ); // Quelle nicht loeschen + e.acceptDrop( DnDConstants.ACTION_COPY ); // Quelle nicht loeschen Transferable t = e.getTransferable(); if( t != null ) { try { Object o = t.getTransferData( DataFlavor.javaFileListFlavor ); if( o != null ) { if( o instanceof Collection ) { - Iterator iter = ((Collection) o).iterator(); - if( iter != null ) { - while( iter.hasNext() ) { - o = iter.next(); - if( o != null ) { - File tmpFile = null; - if( o instanceof File ) { - String path = ((File) o).getPath(); - if( path != null ) { - if( !path.isEmpty() ) { - tmpFile = (File) o; - } - } - } - else if( o instanceof String ) { - String s = (String) o; - if( !s.isEmpty() ) { - tmpFile = new File( s ); - } - } - if( tmpFile != null ) { - if( file == null ) { - file = tmpFile; - } else { - BasicDlg.showErrorDlg( + for( Object f : (Collection) o ) { + if( f != null ) { + if( f instanceof File ) { + if( file == null ) { + file = (File) f; + } else { + BasicDlg.showErrorDlg( owner, "Bitte nur eine Datei hier hineinziehen!" ); - file = null; - break; - } + file = null; + break; } } } @@ -881,7 +723,7 @@ else if( o instanceof String ) { } catch( Exception ex ) {} } - e.dropComplete( true ); + e.dropComplete( file != null ); } else { e.rejectDrop(); } @@ -1031,17 +873,10 @@ public static void frameToFront( Component c ) } - public static Frame getAncestorFrame( Component c ) + public static int getBasicMemWord( EmuMemView memory, int addr ) { - Frame rv = null; - while( c != null ) { - if( c instanceof Frame ) { - rv = (Frame) c; - break; - } - c = c.getParent(); - } - return rv; + return (memory.getBasicMemByte( addr + 1 ) << 8) + | memory.getBasicMemByte( addr ); } @@ -1162,6 +997,61 @@ public static File getDestFile( } + /* + * Die Methode liest aus einem Text an der angegebenen Stelle eine + * vierstellige Hexadezimalzahl. + * Die Gross-/Kleinschreibung ist egal. + * + * Rueckgabewert: + * >= 0: gelesene Hexadezimalzahl + * -1: keine vierstellige Hexadezimalzahl an der Stelle vorhanden + */ + public static int getHex4( String text, int pos ) + { + int rv = -1; + if( text != null ) { + if( (pos + 3) < text.length() ) { + boolean hex = true; + int value = 0; + for( int i = 0; i < 4; i++ ) { + char ch = text.charAt( pos + i ); + if( (ch >= '0') && (ch <= '9') ) { + value = (value << 4) | (ch - '0'); + } else if( (ch >= 'A') && (ch <= 'F') ) { + value = (value << 4) | (ch - 'A' + 10); + } else if( (ch >= 'a') && (ch <= 'f') ) { + value = (value << 4) | (ch - 'a' + 10); + } else { + hex = false; + break; + } + } + if( hex ) { + rv = value; + } + } + } + return rv; + } + + + public static File getHomeDirFile() + { + File rv = null; + FileSystemView fsv = FileSystemView.getFileSystemView(); + if( fsv != null ) { + rv = fsv.getHomeDirectory(); + } + if( rv == null ) { + String homeDir = System.getProperty( "user.home" ); + if( homeDir != null ) { + rv = new File( homeDir ); + } + } + return rv; + } + + public static NumberFormat getIntegerFormat() { if( intFmt == null ) { @@ -1201,18 +1091,42 @@ public static char getHexChar( int value ) } + public static javax.swing.filechooser.FileFilter getAC1Basic6FileFilter() + { + return getFileFilter( "AC1-BASIC6-Dateien (*.abc)", "abc" ); + } + + public static javax.swing.filechooser.FileFilter getAnaDiskFileFilter() { return getFileFilter( "AnaDisk-Dateien (*.dump)", "dump" ); } + public static javax.swing.filechooser.FileFilter getBasicFileFilter() + { + return getFileFilter( "BASIC-/RBASIC-Dateien (*.bas)", "bas" ); + } + + public static javax.swing.filechooser.FileFilter getBinaryFileFilter() { return getFileFilter( "Einfache Speicherabbilddateien (*.bin)", "bin" ); } + public static javax.swing.filechooser.FileFilter getCdtFileFilter() + { + return getFileFilter( "CPC-Tape-Dateien (*.cdt)", "cdt" ); + } + + + public static javax.swing.filechooser.FileFilter getCswFileFilter() + { + return getFileFilter( "CSW-Dateien (*.csw)", "csw" ); + } + + public static javax.swing.filechooser.FileFilter getComFileFilter() { return getFileFilter( "CP/M-Programmdateien (*.com)", "com" ); @@ -1279,10 +1193,16 @@ public static javax.swing.filechooser.FileFilter getKCSystemFileFilter() } + public static javax.swing.filechooser.FileFilter getKCTapFileFilter() + { + return getFileFilter( "KC-TAP-Dateien (*.tap)", "tap" ); + } + + public static javax.swing.filechooser.FileFilter getPlainDiskFileFilter() { return getFileFilter( - "Einfache Diskettenabbilddateien (*.img; *.image, *.raw)", + "Einfache Abbilddateien (*.img; *.image, *.raw)", "img", "image", "raw" ); } @@ -1303,12 +1223,6 @@ public synchronized static Random getRandom() } - public static javax.swing.filechooser.FileFilter getRBasicFileFilter() - { - return getFileFilter( "RBASIC-Dateien (*.bas)", "bas" ); - } - - public static javax.swing.filechooser.FileFilter getRMCFileFilter() { return getFileFilter( "RBASIC-Maschinencodedateien (*.rmc)", "rmc" ); @@ -1321,9 +1235,11 @@ public static javax.swing.filechooser.FileFilter getROMFileFilter() } - public static javax.swing.filechooser.FileFilter getTapFileFilter() + public static javax.swing.filechooser.FileFilter getTapeFileFilter() { - return getFileFilter( "TAP-Dateien (*.tap)", "tap" ); + return getFileFilter( + "Tape-Dateien (*.cdt; *.csw; *.tap; *.tzx)", + "cdt", "csw", "tap", "tzx" ); } @@ -1341,6 +1257,31 @@ public static javax.swing.filechooser.FileFilter getTextFileFilter() } + public static javax.swing.filechooser.FileFilter getTzxFileFilter() + { + return getFileFilter( "ZX-Tape-Dateien (*.tzx)", "tzx" ); + } + + + public static File getDirectory( File file ) + { + if( file != null ) { + FileSystemView fsv = FileSystemView.getFileSystemView(); + while( file != null ) { + if( file.isDirectory() ) { + break; + } + if( fsv != null ) { + file = fsv.getParentDirectory( file ); + } else { + file = file.getParentFile(); + } + } + } + return file; + } + + // Die Methode liefert niemals null zurueck public static String getProperty( Properties props, String keyword ) { @@ -1404,11 +1345,10 @@ public static boolean isGZipFile( File file ) public static boolean isFileDrop( DropTargetDragEvent e ) { boolean rv = false; - int action = e.getDropAction(); - if( (action == DnDConstants.ACTION_COPY) - || (action == DnDConstants.ACTION_MOVE) - || (action == DnDConstants.ACTION_COPY_OR_MOVE) - || (action == DnDConstants.ACTION_LINK) ) + if( (e.getDropAction() + & (DnDConstants.ACTION_COPY + | DnDConstants.ACTION_MOVE + | DnDConstants.ACTION_LINK)) != 0 ) { rv = e.isDataFlavorSupported( DataFlavor.javaFileListFlavor ); } @@ -1419,11 +1359,10 @@ public static boolean isFileDrop( DropTargetDragEvent e ) public static boolean isFileDrop( DropTargetDropEvent e ) { boolean rv = false; - int action = e.getDropAction(); - if( (action == DnDConstants.ACTION_COPY) - || (action == DnDConstants.ACTION_MOVE) - || (action == DnDConstants.ACTION_COPY_OR_MOVE) - || (action == DnDConstants.ACTION_LINK) ) + if( (e.getDropAction() + & (DnDConstants.ACTION_COPY + | DnDConstants.ACTION_MOVE + | DnDConstants.ACTION_LINK)) != 0 ) { rv = e.isDataFlavorSupported( DataFlavor.javaFileListFlavor ); } @@ -1439,9 +1378,9 @@ public static boolean isHexChar( int ch ) } - public static boolean isNativeFileDialogSelected() + public static boolean isJKCEMUFileDialogSelected() { - return TextUtil.equalsIgnoreCase( + return !TextUtil.equalsIgnoreCase( Main.getProperty( "jkcemu.filedialog" ), "native" ); } @@ -1560,6 +1499,7 @@ public static void printOut( String text ) { if( Main.consoleWriter != null ) { Main.consoleWriter.print( text ); + Main.consoleWriter.flush(); } else { System.out.print( text ); } @@ -1570,6 +1510,7 @@ public static void printlnErr() { if( Main.consoleWriter != null ) { Main.consoleWriter.println(); + Main.consoleWriter.flush(); } else { System.err.println(); } @@ -1580,6 +1521,7 @@ public static void printlnOut() { if( Main.consoleWriter != null ) { Main.consoleWriter.println(); + Main.consoleWriter.flush(); } else { System.out.println(); } @@ -1590,6 +1532,7 @@ public static void printlnErr( String text ) { if( Main.consoleWriter != null ) { Main.consoleWriter.println( text ); + Main.consoleWriter.flush(); } else { System.err.println( text ); } @@ -1600,6 +1543,7 @@ public static void printlnOut( String text ) { if( Main.consoleWriter != null ) { Main.consoleWriter.println( text ); + Main.consoleWriter.flush(); } else { System.out.println( text ); } @@ -1634,14 +1578,21 @@ public static int read( InputStream in, byte[] buf ) throws IOException } - public static byte[] readFile( File file, int maxLen ) throws IOException + public static byte[] readFile( + File file, + boolean allowUncompress, + int maxLen ) throws IOException { byte[] rv = null; if( file != null ) { long len = file.length(); InputStream in = null; try { - in = new FileInputStream( file ); + if( allowUncompress && EmuUtil.isGZipFile( file ) ) { + in = new GZIPInputStream( new FileInputStream( file ) ); + } else { + in = new FileInputStream( file ); + } if( len < 0 ) { ByteArrayOutputStream buf = new ByteArrayOutputStream( 0x10000 ); int b = in.read(); @@ -1680,6 +1631,7 @@ else if( len > 0 ) { public static byte[] readFile( Component owner, String fileName, + boolean allowUncompress, int maxLen, String objName ) { @@ -1687,7 +1639,10 @@ public static byte[] readFile( if( fileName != null ) { if( !fileName.isEmpty() ) { try { - rv = EmuUtil.readFile( new File( fileName ), maxLen ); + rv = EmuUtil.readFile( + new File( fileName ), + allowUncompress, + maxLen ); } catch( IOException ex ) { String msg = ex.getMessage(); @@ -1706,29 +1661,6 @@ public static byte[] readFile( } - public static String readTextFile( File file ) throws IOException - { - String rv = ""; - BufferedReader in = null; - try { - in = new BufferedReader( new FileReader( file ) ); - - StringBuilder buf = new StringBuilder( 0x8000 ); - String line = in.readLine(); - while( line != null ) { - buf.append( line ); - buf.append( (char) '\n' ); - line = in.readLine(); - } - rv = buf.toString(); - } - finally { - doClose( in ); - } - return rv; - } - - public static byte[] readResource( Component owner, String resource ) { ByteArrayOutputStream buf = new ByteArrayOutputStream( 0x0800 ); @@ -1772,42 +1704,60 @@ public static byte[] readResource( Component owner, String resource ) public static File renameFile( Component owner, File file ) { - File rvFile = null; + File newFile = null; if( file != null ) { - boolean isDir = file.isDirectory(); - String fileName = ReplyTextDlg.showReplyTextDlg( - owner, - isDir ? "Verzeichnisname:" : "Dateiname:", - isDir ? "Verzeichnis umbenennen" - : "Datei umbenennen", - file.getName() ); - if( fileName != null ) { - fileName = fileName.trim(); - if( fileName.length() > 0 ) { - File replyFile = new File( fileName ); - if( replyFile.isAbsolute() || (replyFile.getParent() != null) ) { - BasicDlg.showErrorDlg( + try { + Path newPath = renamePath( owner, file.toPath() ); + if( newPath != null ) { + try { + newFile = newPath.toFile(); + } + catch( InvalidPathException ex ) {} + } + } + catch( UnsupportedOperationException ex ) { + BasicDlg.showErrorDlg( owner, - "Mit dieser Funktion k\u00F6nnen Dateien und Verzeichnisse\n" - + "nur umbenannt, nicht aber verschoben werden." ); - } else { - File parent = file.getParentFile(); - if( parent != null ) - replyFile = new File( parent, fileName ); + "Umbenennen der Datei wird nicht unterst\u00FCtzt." ); + } + } + return newFile; + } - if( file.renameTo( replyFile ) ) { - rvFile = replyFile; - } else { - BasicDlg.showErrorDlg( - owner, - (isDir ? "Verzeichnis" : "Datei") - + " konnte nicht umbenannt werden." ); - } - } + + public static Path renamePath( Component owner, Path path ) + { + Path newPath = null; + if( path != null ) { + String oldName = null; + Path namePath = path.getFileName(); + if( namePath != null ) { + oldName = namePath.toString(); + } + String title = "Datei umbenennen"; + String msgPrefix = "Die Datei"; + if( Files.isSymbolicLink( path ) ) { + title = "Symbolischer Link umbenennen"; + msgPrefix = "Der symbolische Link"; + } else if( Files.isDirectory( path ) ) { + title = "Verzeichnis umbenennen"; + msgPrefix = "Das Verzeichnis"; + } + String newName = ReplyTextDlg.showReplyTextDlg( + owner, + "Neuer Name:", + title, + oldName != null ? oldName : "" ); + if( newName != null ) { + try { + newPath = Files.move( path, path.resolveSibling( newName ) ); + } + catch( Exception ex ) { + BasicDlg.showErrorDlg( owner, ex ); } } } - return rvFile; + return newPath; } @@ -1843,6 +1793,24 @@ public static void setProperty( } + public static boolean setSelectedHeadersaveFileTypeItem( + JComboBox combo, + int fileType ) + { + boolean rv = false; + for( String s : headersaveFileTypeItems ) { + if( !s.isEmpty() ) { + if( s.charAt( 0 ) == fileType ) { + combo.setSelectedItem( s ); + rv = true; + break; + } + } + } + return rv; + } + + public static void setTableColWidths( JTable table, int... colWidths ) { if( (table != null) && (colWidths != null) ) { @@ -1859,141 +1827,110 @@ public static void setTableColWidths( JTable table, int... colWidths ) } - public static File showNativeFileDlg( - Frame owner, - boolean forSave, - String title, - File preSelection ) + public static File showFileOpenDlg( + Window owner, + String title, + File preSelection, + javax.swing.filechooser.FileFilter... fileFilters ) { - File file = null; - FileDialog dlg = new FileDialog( + preSelection = getDirectory( preSelection ); + File file = null; + if( isJKCEMUFileDialogSelected() ) { + FileSelectDlg dlg = new FileSelectDlg( owner, + FileSelectDlg.Mode.LOAD, + false, // kein Startknopf + false, // kein "Laden mit..."-Knopf title, - forSave ? FileDialog.SAVE : FileDialog.LOAD ); - dlg.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL ); - dlg.setResizable( true ); - if( preSelection != null ) { - if( preSelection.isDirectory() ) { - dlg.setDirectory( preSelection.getPath() ); - } else { - String dirName = preSelection.getParent(); - if( dirName != null ) { - dlg.setDirectory( dirName ); - } - if( !preSelection.exists() || preSelection.isFile() ) { - dlg.setFile( preSelection.getName() ); + preSelection, + fileFilters ); + dlg.setVisible( true ); + file = dlg.getSelectedFile(); + } else { + File[] files = showNativeFileDlg( + owner, + false, + false, + title, + preSelection ); + if( files != null ) { + if( files.length > 0 ) { + file = files[ 0 ]; } } } - BasicDlg.setParentCentered( dlg ); - dlg.setVisible( true ); - String fileName = dlg.getFile(); - if( fileName != null ) { - String dirName = dlg.getDirectory(); - if( dirName != null ) { - file = new File( new File( dirName ), fileName ); - } else { - file = new File( fileName ); - } - } return file; } - public static File showFileOpenDlg( - Frame owner, + public static File showFileSaveDlg( + Window owner, String title, File preSelection, javax.swing.filechooser.FileFilter... fileFilters ) { - if( isNativeFileDialogSelected() ) { - return showNativeFileDlg( - owner, - false, - title, - preSelection ); - } - FileSelectDlg dlg = new FileSelectDlg( + File file = null; + if( isJKCEMUFileDialogSelected() ) { + FileSelectDlg dlg = new FileSelectDlg( owner, - false, // fuer Laden + FileSelectDlg.Mode.SAVE, false, // kein Startknopf false, // kein "Laden mit..."-Knopf title, preSelection, fileFilters ); - dlg.setVisible( true ); - return dlg.getSelectedFile(); + dlg.setVisible( true ); + file = dlg.getSelectedFile(); + } else { + File[] files = showNativeFileDlg( + owner, + true, + false, + title, + preSelection ); + if( files != null ) { + if( files.length > 0 ) { + file = files[ 0 ]; + } + } + } + return file; } - public static File showFileSaveDlg( - Frame owner, + public static java.util.List showMultiFileOpenDlg( + Window owner, String title, File preSelection, javax.swing.filechooser.FileFilter... fileFilters ) { - File file = null; - if( isNativeFileDialogSelected() ) { - file = showNativeFileDlg( - owner, - true, - title, - preSelection ); - } else { + java.util.List files = null; + preSelection = getDirectory( preSelection ); + if( isJKCEMUFileDialogSelected() ) { FileSelectDlg dlg = new FileSelectDlg( owner, - true, // fuer Speichern + FileSelectDlg.Mode.MULTIPLE_LOAD, false, // kein Startknopf false, // kein "Laden mit..."-Knopf title, preSelection, fileFilters ); dlg.setVisible( true ); - file = dlg.getSelectedFile(); - } - - // Dateiname aufbereiten - if( file != null ) { - String fName = file.getName(); - if( fName != null ) { - if( fName.startsWith( "\"" ) && fName.endsWith( "\"" ) ) { - if( fName.length() > 2 ) { - fName = fName.substring( 1, fName.length() - 1 ); - } else { - fName = ""; - } - file = replaceName( file, fName ); - } else { - // ggf. Dateiendung anhaengen - if( fileFilters != null ) { - if( fileFilters.length == 1 ) { - javax.swing.filechooser.FileFilter ff = fileFilters[ 0 ]; - if( ff != null ) { - if( ff instanceof FileNameExtensionFilter ) { - String[] extensions = ((FileNameExtensionFilter) ff) - .getExtensions(); - if( extensions != null ) { - if( extensions.length == 1 ) { - String ext = extensions[ 0 ]; - if( ext != null ) { - if( !fName.toUpperCase().endsWith( - ext.toUpperCase() ) ) - { - file = replaceName( - file, - String.format( "%s.%s", fName, ext ) ); - } - } - } - } - } - } - } - } + files = dlg.getSelectedFiles(); + } else { + File[] tmpFiles = showNativeFileDlg( + owner, + false, + true, + title, + preSelection ); + if( tmpFiles != null ) { + if( tmpFiles.length > 0 ) { + files = Arrays.asList( tmpFiles ); } } } - return file; + return files; } @@ -2033,12 +1970,13 @@ public static void stopCellEditing( JTable table ) public static void writeASCII( OutputStream out, - String text ) throws IOException + CharSequence text ) throws IOException { if( text != null ) { + checkASCII( text ); int len = text.length(); for( int i = 0; i < len; i++ ) { - out.write( text.charAt( i ) & 0x7F ); + out.write( text.charAt( i ) ); } } } @@ -2046,147 +1984,74 @@ public static void writeASCII( public static void writeFixLengthASCII( OutputStream out, - String text, + CharSequence text, int len, int filler ) throws IOException { if( text != null ) { + checkASCII( text ); int srcLen = text.length(); - for( int i = 0; i < len; i++ ) { - int b = filler; - if( i < srcLen ) { - b = text.charAt( i ) & 0x7F; - } - out.write( b ); + int srcIdx = 0; + while( (len > 0) && (srcIdx < srcLen) ) { + out.write( text.charAt( srcIdx++ ) ); + --len; } } + while( len > 0 ) { + out.write( 0 ); + --len; + } } /* --- private Methoden --- */ - private static boolean deleteFile( - Component owner, - File file, - RunStatus runStatus ) + private static void checkASCII( CharSequence text ) + throws CharConversionException { - boolean rv = true; - if( file != null ) { - if( file.exists() ) { - boolean failed = false; - boolean isDir = file.isDirectory(); - if( isDir ) { - File[] files = file.listFiles(); - if( files != null ) { - for( - int i = 0; - (i < files.length) && !runStatus.wasCancelled(); - i++ ) - { - /* - * Sollte durch einen symbolischen Link eine Ringkettung - * bestehen, wuerde der Stack ueberlaufen. - * Um das zu verhindern, wird versucht, * das Verzeichnis - * respektive dem ggf. verhandenen symbolischen Link - * direkt zu loeschen, - * was bei einem symbolischen Link auch moeglich ist. - * Damit wird eine evtl. vorhandene Ringkettung unterbrochen. - */ - if( !files[ i ].delete() ) { - // direktes Loeschen fehlgeschlagen -> rekursiv versuchen - if( !deleteFile( owner, files[ i ], runStatus ) ) { - failed = true; - } - } - } - } - } - rv = false; - if( !failed && !runStatus.wasCancelled() ) { - boolean deleteEnabled = true; - if( !runStatus.getForce() && !file.canWrite() ) { - String[] options = { - "\u00DCberspringen", - "L\u00F6schen", - "Alle l\u00F6schen", - "Abbrechen" }; - - StringBuilder buf = new StringBuilder( 512 ); - buf.append( "Sie besitzen keine Schreibrechte f\u00FCr " ); - if( isDir ) { - buf.append( "die Datei" ); - } else { - buf.append( "das Verzeichnis" ); - } - buf.append( "\n\'" ); - buf.append( file.getPath() ); - buf.append( "\'.\nM\u00F6chten Sie trotzem" - + " das L\u00F6schen versuchen?" ); - - JOptionPane pane = new JOptionPane( - buf.toString(), - JOptionPane.ERROR_MESSAGE ); - pane.setOptions( options ); - pane.setWantsInput( false ); - pane.createDialog( owner, "Fehler" ).setVisible( true ); - Object value = pane.getValue(); - if( value != null ) { - if( value.equals( options[ 0 ] ) ) { - deleteEnabled = false; - } - else if( value.equals( options[ 2 ] ) ) { - runStatus.setForce( true ); - } - else if( value.equals( options[ 3 ] ) ) { - runStatus.setCancelled( true ); - } - } else { - runStatus.setCancelled( true ); - } + if( text != null ) { + int len = text.length(); + for( int i = 0; i < len; i++ ) { + char ch = text.charAt( i ); + if( (ch < '\u0020') || (ch > '\u007E') ) { + StringBuilder buf = new StringBuilder( 256 ); + buf.append( "Nicht-ASCII-Zeichen" ); + if( (ch > '\u0020') + && !Character.isSpaceChar( ch ) + && !Character.isWhitespace( ch ) + && !Character.isISOControl( ch ) ) + { + buf.append( " \'" ); + buf.append( ch ); + buf.append( (char) '\'' ); } - if( deleteEnabled && !runStatus.wasCancelled() ) { - rv = file.delete(); - if( !rv ) { - if( runStatus.isQuiet() ) { - runStatus.setFailed( true ); - } else { - String[] options = { - "Ignorieren", - "Alle ignorieren", - "Abbrechen" }; - - JOptionPane pane = new JOptionPane(); - if( isDir ) { - pane.setMessage( "Verzeichnis \'" - + file.getPath() - + "\'\nkonnte nicht gel\u00F6scht werden." ); - } else { - pane.setMessage( "Datei \'" - + file.getPath() - + "\'\nkonnte nicht gel\u00F6scht werden." ); - } - pane.setMessageType( JOptionPane.ERROR_MESSAGE ); - pane.setOptions( options ); - pane.setWantsInput( false ); - pane.createDialog( owner, "Fehler" ).setVisible( true ); - Object value = pane.getValue(); - if( value != null ) { - if( value.equals( options[ 1 ] ) ) { - runStatus.setQuiet( true ); - } - else if( value.equals( options[ 2 ] ) ) { - runStatus.setCancelled( true ); - } - } else { - runStatus.setCancelled( true ); - } - } - } + buf.append( " im Text enthalten" ); + if( len > 1 ) { + buf.append( "\n\nText:\n" ); + buf.append( text ); } + throw new CharConversionException( buf.toString() ); } } } - return rv; + } + + + private static Comparator createPathComparator() + { + return new Comparator() + { + @Override + public int compare( Path p1, Path p2 ) + { + String s1 = (p1 != null ? p1.toString() : null); + String s2 = (p2 != null ? p2.toString() : null); + if( s1 == null ) { + s1 = ""; + } + return s1.compareTo( s2 != null ? s2 : "" ); + } + }; } @@ -2195,19 +2060,17 @@ private static javax.swing.filechooser.FileFilter getFileFilter( String... formats ) { javax.swing.filechooser.FileFilter rv = null; - if( formats != null ) { + if( (formats != null) && (text != null) ) { if( formats.length > 0 ) { if( fmt2FileFilter == null ) { - fmt2FileFilter = new Hashtable< - String, - javax.swing.filechooser.FileFilter>(); + fmt2FileFilter = new HashMap<>(); } - rv = fmt2FileFilter.get( formats[ 0 ] ); + rv = fmt2FileFilter.get( text ); if( rv == null ) { rv = new javax.swing.filechooser.FileNameExtensionFilter( text, formats ); - fmt2FileFilter.put( formats[ 0 ], rv ); + fmt2FileFilter.put( text, rv ); } } } @@ -2215,16 +2078,60 @@ private static javax.swing.filechooser.FileFilter getFileFilter( } - private static File replaceName( File file, String fName ) + private static File[] showNativeFileDlg( + Window owner, + boolean forSave, + boolean multiMode, + String title, + File preSelection ) { - if( file != null ) { - File parent = file.getParentFile(); - if( parent != null ) { - file = new File( parent, fName ); - } else { - file = new File( fName ); + File[] files = null; + Dialog ownerDlg = null; + Frame ownerFrm = null; + while( owner != null ) { + if( owner instanceof Dialog ) { + ownerDlg = (Dialog) owner; + break; } + if( owner instanceof Frame ) { + ownerFrm = (Frame) owner; + break; + } + owner = owner.getOwner(); } - return file; + FileDialog dlg = null; + if( ownerDlg != null ) { + dlg = new FileDialog( + ownerDlg, + title, + forSave ? FileDialog.SAVE : FileDialog.LOAD ); + } else if( ownerFrm != null ) { + dlg = new FileDialog( + ownerFrm, + title, + forSave ? FileDialog.SAVE : FileDialog.LOAD ); + } + if( dlg != null ) { + dlg.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL ); + dlg.setMultipleMode( !forSave && multiMode ); + dlg.setResizable( true ); + if( preSelection != null ) { + if( preSelection.isDirectory() ) { + dlg.setDirectory( preSelection.getPath() ); + } else { + String dirName = preSelection.getParent(); + if( dirName != null ) { + dlg.setDirectory( dirName ); + } + if( !preSelection.exists() || preSelection.isFile() ) { + dlg.setFile( preSelection.getName() ); + } + } + } + BasicDlg.setParentCentered( dlg ); + dlg.setVisible( true ); + files = dlg.getFiles(); + } + return files; } } diff --git a/src/jkcemu/base/FileCopier.java b/src/jkcemu/base/FileCopier.java new file mode 100644 index 0000000..e26b5e7 --- /dev/null +++ b/src/jkcemu/base/FileCopier.java @@ -0,0 +1,230 @@ +/* + * (c) 2014-2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Kopieren von Dateien und Dateibaeumen + */ + +package jkcemu.base; + + +import java.awt.Window; +import java.io.IOException; +import java.lang.*; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collection; +import javax.swing.JOptionPane; + + +public class FileCopier extends AbstractFileWorker +{ + private Path dstPath; + private volatile Boolean copyAllDirs; + private volatile Boolean replaceAllFiles; + + + /* + * Der Paramater srcFiles ist absichtlich nicht Generics-maessig + * typisiert, um auch die von + * Transferable.getTransferData( DataFlavor.javaFileListFlavor ) + * zurueckgelieferte Collection hier problemlos uebergeben zu koennen. + */ + public FileCopier( + Window owner, + Collection srcFiles, + Path dstPath, + PathListener pathListener, + Collection register ) throws IOException + { + super( owner, srcFiles, pathListener, register ); + checkSrcPathsAgainstDstPath( + dstPath, + srcFiles, + "Ein Verzeichnis kann nicht in sich selbst hinein" + + " kopiert werden." ); + this.dstPath = dstPath; + this.copyAllDirs = null; + this.replaceAllFiles = null; + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public String getFileFailedMsg( String fileName ) + { + return fileName + "\nkann nicht kopiert werden."; + } + + + @Override + public String getProgressDlgTitle() + { + return "Kopieren"; + } + + + @Override + public String getUncompletedWorkMsg() + { + return "Es konnten nicht alle Dateien, Verzeichnisse bzw.\n" + + "symbolische Links kopiert werden."; + } + + + @Override + public FileVisitResult preVisitDirectory( + Path dir, + BasicFileAttributes attrs ) + { + FileVisitResult rv = FileVisitResult.CONTINUE; + if( !this.cancelled ) { + this.curPath = dir; + Path dstDir = resolveDst( dir ); + try { + boolean exists = false; + boolean forceCopy = false; + if( this.copyAllDirs != null ) { + forceCopy = this.copyAllDirs.booleanValue(); + } + if( !forceCopy ) { + exists = Files.exists( dstDir, LinkOption.NOFOLLOW_LINKS ); + } + if( (this.copyAllDirs == null) && exists ) { + switch( showJOptionPane( + "Das Verzeichnis \'" + dstDir.toString() + + "\' existiert bereits.\n" + + "M\u00F6chten Sie trotzdem in das" + + " Verzeichnis hinein kopieren?", + JOptionPane.WARNING_MESSAGE, + "Zielverzeichnis bereits vorhanden", + new String[] { + "Kopieren", + "Alle kopieren", + "\u00DCberspringen", + "Alle \u00FCberspringen", + "Abbrechen" } ) ) + { + case 0: + forceCopy = true; + break; + case 1: + forceCopy = true; + this.copyAllDirs = Boolean.TRUE; + break; + case 2: + forceCopy = false; + break; + case 3: + forceCopy = false; + this.copyAllDirs = Boolean.FALSE; + break; + default: + this.cancelled = true; + } + } + if( !this.cancelled && (!exists || forceCopy) ) { + Files.copy( + dir, + dstDir, + StandardCopyOption.COPY_ATTRIBUTES, + LinkOption.NOFOLLOW_LINKS ); + pathPasted( dstDir ); + } else { + rv = FileVisitResult.SKIP_SUBTREE; + } + } + catch( IOException ex ) { + if( !(ex instanceof FileAlreadyExistsException) + || !Files.isDirectory( dstDir ) ) + { + handleError( dir, ex ); + if( !this.cancelled ) { + rv = FileVisitResult.SKIP_SUBTREE; + } + } + } + } + return this.cancelled ? FileVisitResult.TERMINATE : rv; + } + + + @Override + public FileVisitResult visitFile( Path file, BasicFileAttributes attrs ) + { + if( !this.cancelled ) { + this.curPath = file; + try { + Path dstFile = resolveDst( file ); + boolean exists = false; + boolean forceReplace = false; + if( this.replaceAllFiles != null ) { + forceReplace = this.replaceAllFiles.booleanValue(); + } + if( !forceReplace ) { + exists = Files.exists( dstFile, LinkOption.NOFOLLOW_LINKS ); + } + if( (this.replaceAllFiles == null) && exists ) { + switch( showJOptionPane( + "Die Datei \'" + dstFile.toString() + + "\' existiert bereits.\n" + + "M\u00F6chten Sie die Datei ersetzen?", + JOptionPane.WARNING_MESSAGE, + "Datei bereits vorhanden", + new String[] { + "Ersetzen", + "Alle ersetzen", + "Nicht ersetzen", + "Alle nicht ersetzen", + "Abbrechen" } ) ) + { + case 0: + forceReplace = true; + break; + case 1: + forceReplace = true; + this.replaceAllFiles = Boolean.TRUE; + break; + case 2: + forceReplace = false; + break; + case 3: + forceReplace = false; + this.replaceAllFiles = Boolean.FALSE; + break; + default: + this.cancelled = true; + } + } + if( !this.cancelled && (!exists || forceReplace) ) { + Files.copy( + file, + dstFile, + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.COPY_ATTRIBUTES, + LinkOption.NOFOLLOW_LINKS ); + pathPasted( dstFile ); + } + } + catch( IOException ex ) { + handleError( file, ex ); + } + } + return this.cancelled ? + FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } + + + /* --- private Methoden --- */ + + private Path resolveDst( Path path ) + { + Path parent = this.curParent; + if( parent != null ) { + path = parent.relativize( path ); + } + return this.dstPath.resolve( path ); + } +} diff --git a/src/jkcemu/base/FileEntry.java b/src/jkcemu/base/FileEntry.java index 528724f..bf7f558 100644 --- a/src/jkcemu/base/FileEntry.java +++ b/src/jkcemu/base/FileEntry.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2011 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -22,7 +22,7 @@ public class FileEntry private Object value; private boolean readOnly; private boolean sysFile; - private boolean archived; + private boolean archive; private boolean dir; private boolean marked; @@ -37,7 +37,7 @@ public FileEntry() this.value = null; this.readOnly = false; this.sysFile = false; - this.archived = false; + this.archive = false; this.dir = false; this.marked = false; } @@ -98,9 +98,9 @@ public boolean isDirectory() } - public boolean isArchived() + public boolean isArchive() { - return this.archived; + return this.archive; } @@ -122,9 +122,9 @@ public boolean isSystemFile() } - public void setArchived( boolean state ) + public void setArchive( boolean state ) { - this.archived = state; + this.archive = state; } diff --git a/src/jkcemu/base/FileFormat.java b/src/jkcemu/base/FileFormat.java new file mode 100644 index 0000000..d191d87 --- /dev/null +++ b/src/jkcemu/base/FileFormat.java @@ -0,0 +1,115 @@ +/* + * (c) 2015-2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Dateiformate fuer Speicherabbilddateien + */ + +package jkcemu.base; + +import java.lang.*; + + +public class FileFormat +{ + public static FileFormat BIN + = new FileFormat( "BIN-Datei (Speicherabbild ohne Kopfdaten)", 0 ); + + public static FileFormat BASIC_PRG + = new FileFormat( "BASIC-Programmdatei", 0 ); + + public static FileFormat CDT + = new FileFormat( "CPC-Tape-Datei", 0 ); + + public static FileFormat CSW + = new FileFormat( "CSW-Datei", 0 ); + + public static FileFormat HEADERSAVE + = new FileFormat( "Headersave-Datei", 16 ); + + public static FileFormat INTELHEX + = new FileFormat( "Intel-HEX-Datei", 0 ); + + public static FileFormat KCB + = new FileFormat( "KCB-Datei (BASIC-Programm)", 11 ); + + public static FileFormat KCC + = new FileFormat( "KCC/JTC-Datei", 11 ); + + public static FileFormat KCTAP_SYS + = new FileFormat( "KC-TAP-Datei", 11 ); + + public static FileFormat KCTAP_KC85 + = new FileFormat( "KC-TAP-Datei (KC85)", 11 ); + + public static FileFormat KCTAP_Z9001 + = new FileFormat( "KC-TAP-Datei (Z9001)", 11 ); + + public static FileFormat KCTAP_BASIC_PRG + = new FileFormat( "KC-TAP-BASIC-Programmdatei", 11 ); + + public static FileFormat KCTAP_BASIC_DATA + = new FileFormat( "KC-TAP-BASIC-Datenfeld", 8 ); + + public static FileFormat KCTAP_BASIC_ASC + = new FileFormat( "KC-TAP-BASIC-ASCII-Listing", 8 ); + + public static FileFormat KCBASIC_HEAD_PRG + = new FileFormat( "KC-BASIC-Programmdatei mit Kopfdaten", 8 ); + + public static FileFormat KCBASIC_HEAD_DATA + = new FileFormat( "KC-BASIC-Datenfeld mit Kopfdaten", 8 ); + + public static FileFormat KCBASIC_HEAD_ASC + = new FileFormat( "KC-BASIC-ASCII-Listing mit Kopfdaten", 8 ); + + public static FileFormat KCBASIC_PRG + = new FileFormat( "KC-BASIC-Programmdatei", 0 ); + + public static FileFormat RBASIC_PRG + = new FileFormat( "RBASIC-Programmdatei", 0 ); + + public static FileFormat RMC + = new FileFormat( "RBASIC-Maschinencodedatei", 0 ); + + public static FileFormat TZX + = new FileFormat( "ZX-Spectrum-Tape-Datei", 0 ); + + public static FileFormat ZXTAP + = new FileFormat( "ZX-TAP-Datei", 0 ); + + + private String text; + private int maxFileDescLen; + + + public int getMaxFileDescLength() + { + return this.maxFileDescLen; + } + + + public static int getTotalMaxFileDescLength() + { + return 16; + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public String toString() + { + return this.text; + } + + + /* --- Konstruktor --- */ + + private FileFormat( String text, int maxFileDescLen ) + { + this.text = text; + this.maxFileDescLen = maxFileDescLen; + } +} diff --git a/src/jkcemu/base/FileInfo.java b/src/jkcemu/base/FileInfo.java index a16e611..a317210 100644 --- a/src/jkcemu/base/FileInfo.java +++ b/src/jkcemu/base/FileInfo.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -15,39 +15,19 @@ public class FileInfo { - public static final String HEADERSAVE = "Headersave-Datei"; - public static final String KCB = "KCB-Datei (BASIC-Programm)"; - public static final String KCC = "KCC/JTC-Datei"; - public static final String KCTAP_SYS = "KC-TAP-Datei"; - public static final String KCTAP_KC85 = "KC-TAP-Datei (KC85)"; - public static final String KCTAP_Z9001 = "KC-TAP-Datei (Z9001)"; - public static final String KCTAP_BASIC_PRG = "KC-TAP-BASIC-Programmdatei"; - public static final String KCTAP_BASIC_DATA = "KC-TAP-BASIC-Datenfeld"; - public static final String KCTAP_BASIC_ASC = "KC-TAP-BASIC-ASCII-Listing"; - public static final String KCTAP_HEADER = "\u00C3KC-TAPE by AF.\u0020"; - public static final String KCBASIC_HEAD_PRG = - "KC-BASIC-Programmdatei mit Kopfdaten"; - public static final String KCBASIC_HEAD_DATA = - "KC-BASIC-Datenfeld mit Kopfdaten"; - public static final String KCBASIC_HEAD_ASC = - "KC-BASIC-ASCII-Listing mit Kopfdaten"; - public static final String KCBASIC_PRG = "KC-BASIC-Programmdatei"; - public static final String RBASIC = "RBASIC-Programmdatei"; - public static final String RMC = "RBASIC-Maschinencodedatei"; - public static final String INTELHEX = "Intel-HEX-Datei"; - public static final String BIN = "BIN-Datei (Speicherabbild ohne Kopfdaten)"; - - public static final int KCTAP_HLEN = KCTAP_HEADER.length(); - - private byte[] header; - private long fileLen; - private int fileType; - private String fileFmt; - private String fileText; - private String fileDesc; - private String addrText; - private String infoText; - private int nextTAPOffs; + public static final String KCTAP_HEADER = "\u00C3KC-TAPE by AF.\u0020"; + public static final String CSW_HEADER = "Compressed Square Wave\u001A"; + public static final String TZX_HEADER = "ZXTape!\u001A"; + + private byte[] header; + private long fileLen; + private int fileType; + private FileFormat fileFmt; + private String fileText; + private String fileDesc; + private String addrText; + private String infoText; + private int nextTAPOffs; public static FileInfo analyzeFile( File file ) @@ -61,16 +41,11 @@ public static FileInfo analyzeFile( File file ) try { in = new FileInputStream( file ); - byte[] header = new byte[ 40 ]; + byte[] header = new byte[ 256 ]; rv = analyzeFile( header, EmuUtil.read( in, header ), file ); } finally { - if( in != null ) { - try { - in.close(); - } - catch( IOException ex ) {} - } + EmuUtil.doClose( in ); } } } @@ -82,7 +57,7 @@ public static FileInfo analyzeFile( File file ) public static FileInfo analyzeFile( byte[] header, File file ) { - return analyzeFile( header, header.length, file ); + return header != null ? analyzeFile( header, header.length, file ) : null; } @@ -96,14 +71,14 @@ public static FileInfo analyzeFile( if( headerLen > header.length ) { headerLen = header.length; } - String upperFileName = null; - String fileFmt = null; - String fileText = null; - int fileType = -1; - int begAddr = -1; - int endAddr = -1; - int nextTAPOffs = -1; - long fileLen = headerLen; + String upperFileName = null; + FileFormat fileFmt = null; + String fileText = null; + int fileType = -1; + int begAddr = -1; + int endAddr = -1; + int nextTAPOffs = -1; + long fileLen = headerLen; if( file != null ) { String fileName = file.getName(); if( fileName != null ) { @@ -119,7 +94,7 @@ public static FileInfo analyzeFile( && (header[ 14 ] == (byte) 0xD3) && (header[ 15 ] == (byte) 0xD3) ) { - fileFmt = HEADERSAVE; + fileFmt = FileFormat.HEADERSAVE; begAddr = getBegAddr( header, fileFmt ); endAddr = getEndAddr( header, fileFmt ); int b12 = (int) header[ 12 ] & 0xFF; @@ -129,30 +104,25 @@ public static FileInfo analyzeFile( } } if( (fileFmt == null) && (fileLen > 144) && (headerLen > 33) ) { - if( isTAPHeaderAt( header, headerLen, 0 ) ) { + if( isKCTapHeaderAt( header, headerLen, 0 ) ) { int nextOffs = -1; - int b16 = header[ 16 ] & 0xFF; - int b17 = header[ 17 ] & 0xFF; - int b18 = header[ 18 ] & 0xFF; - int b19 = header[ 19 ] & 0xFF; - int b33 = header[ 33 ] & 0xFF; + int b16 = (int) header[ 16 ] & 0xFF; + int b17 = (int) header[ 17 ] & 0xFF; + int b18 = (int) header[ 18 ] & 0xFF; + int b19 = (int) header[ 19 ] & 0xFF; if( ((b17 < 0xD3) || (b17 > 0xD8)) && ((b18 < 0xD3) || (b18 > 0xD8)) - && ((b19 < 0xD3) || (b19 > 0xD8)) - && ((b33 >= 2) && (b33 <= 4)) ) + && ((b19 < 0xD3) || (b19 > 0xD8)) ) { if( b16 == 0 ) { - fileFmt = KCTAP_Z9001; + fileFmt = FileFormat.KCTAP_Z9001; } else if( b16 == 1 ) { - fileFmt = KCTAP_KC85; + fileFmt = FileFormat.KCTAP_KC85; } else { - fileFmt = KCTAP_SYS; + fileFmt = FileFormat.KCTAP_SYS; } begAddr = getBegAddr( header, fileFmt ); endAddr = getEndAddr( header, fileFmt ); - if( endAddr == 0 ) { - endAddr = 0xFFFF; - } if( (begAddr >= 0) && (begAddr <= endAddr) ) { int nBlks = (endAddr - begAddr + 127) / 128; nextOffs = 16 + (129 * (nBlks + 1)); @@ -161,7 +131,7 @@ public static FileInfo analyzeFile( if( ((b17 == 0xD3) && (b18 == 0xD3) && (b19 == 0xD3)) || ((b17 == 0xD7) && (b18 == 0xD7) && (b19 == 0xD7)) ) { - fileFmt = KCTAP_BASIC_PRG; + fileFmt = FileFormat.KCTAP_BASIC_PRG; begAddr = getBegAddr( header, fileFmt ); endAddr = getEndAddr( header, fileFmt ); if( (begAddr >= 0) && (begAddr <= endAddr) ) { @@ -172,39 +142,60 @@ public static FileInfo analyzeFile( else if( ((b17 == 0xD4) && (b18 == 0xD4) && (b19 == 0xD4)) || ((b17 == 0xD8) && (b18 == 0xD8) && (b19 == 0xD8)) ) { - fileFmt = KCTAP_BASIC_DATA; + fileFmt = FileFormat.KCTAP_BASIC_DATA; } else if( ((b17 == 0xD5) && (b18 == 0xD5) && (b19 == 0xD5)) || ((b17 == 0xD9) && (b18 == 0xD9) && (b19 == 0xD9)) ) { - fileFmt = KCTAP_BASIC_ASC; + fileFmt = FileFormat.KCTAP_BASIC_ASC; } } if( nextOffs > 0 ) { - if( isTAPHeaderAt( header, headerLen, nextOffs ) ) { + if( isKCTapHeaderAt( header, headerLen, nextOffs ) ) { nextTAPOffs = nextOffs; } } } } if( (fileFmt == null) && (fileLen > 20) && (headerLen > 2) ) { - int b0 = header[ 0 ] & 0xFF; - int b1 = header[ 1 ] & 0xFF; - int b2 = header[ 2 ] & 0xFF; + int b0 = (int) header[ 0 ] & 0xFF; + int b1 = (int) header[ 1 ] & 0xFF; + int b2 = (int) header[ 2 ] & 0xFF; if( ((b0 == 0xD3) && (b1 == 0xD3) && (b2 == 0xD3)) || ((b0 == 0xD7) && (b1 == 0xD7) && (b2 == 0xD7)) ) { - fileFmt = KCBASIC_HEAD_PRG; + fileFmt = FileFormat.KCBASIC_HEAD_PRG; } else if( ((b0 == 0xD4) && (b1 == 0xD4) && (b2 == 0xD4)) || ((b0 == 0xD8) && (b1 == 0xD8) && (b2 == 0xD8)) ) { - fileFmt = KCBASIC_HEAD_DATA; + fileFmt = FileFormat.KCBASIC_HEAD_DATA; } else if( ((b0 == 0xD5) && (b1 == 0xD5) && (b2 == 0xD5)) || ((b0 == 0xD9) && (b1 == 0xD9) && (b2 == 0xD9)) ) { - fileFmt = KCBASIC_HEAD_ASC; + fileFmt = FileFormat.KCBASIC_HEAD_ASC; + } + } + if( (fileFmt == null) + && (fileLen > CSW_HEADER.length()) + && (headerLen > CSW_HEADER.length()) ) + { + if( isCswHeaderAt( header, headerLen, 0 ) ) { + fileFmt = FileFormat.CSW; + } + } + if( (fileFmt == null) + && (fileLen > TZX_HEADER.length()) + && (headerLen > TZX_HEADER.length()) ) + { + if( isTzxHeaderAt( header, headerLen, 0 ) ) { + fileFmt = FileFormat.TZX; + if( upperFileName != null ) { + if( upperFileName.endsWith( ".CDT" ) ) { + fileFmt = FileFormat.CDT; + } + } } } if( (fileFmt == null) && (fileLen > 10) && (headerLen > 10) ) { @@ -224,7 +215,7 @@ else if( ((b0 == 0xD5) && (b1 == 0xD5) && (b2 == 0xD5)) && EmuUtil.isHexChar( header[ 9 ] & 0xFF ) && EmuUtil.isHexChar( header[ 10 ] & 0xFF ) ) { - fileFmt = INTELHEX; + fileFmt = FileFormat.INTELHEX; } } if( (fileFmt == null) @@ -234,63 +225,149 @@ else if( ((b0 == 0xD5) && (b1 == 0xD5) && (b2 == 0xD5)) if( upperFileName.endsWith( ".KCB" ) && (fileLen > 127) && (headerLen > 20) ) { - int b16 = header[ 16 ] & 0xFF; + int b16 = (int) header[ 16 ] & 0xFF; if( (b16 >= 2) && (b16 <= 4) && (EmuUtil.getWord( header, 17 ) <= 0x0401) && (EmuUtil.getWord( header, 19 ) >= 0x0409) ) { - fileFmt = KCB; + fileFmt = FileFormat.KCB; } } - if( fileFmt == null ) { - if( (upperFileName.endsWith( ".KCC" ) - || upperFileName.endsWith( ".JTC" )) - && (fileLen > 127) && (headerLen > 16) ) - { - int b16 = header[ 16 ] & 0xFF; - if( (b16 >= 2) && (b16 <= 4) ) { - fileFmt = KCC; - begAddr = getBegAddr( header, fileFmt ); - endAddr = getEndAddr( header, fileFmt ); - if( endAddr == 0 ) { - endAddr = 0xFFFF; - } - } - } + if( (fileFmt == null) + && (upperFileName.endsWith( ".KCC" ) + || upperFileName.endsWith( ".851" ) + || upperFileName.endsWith( ".852" ) + || upperFileName.endsWith( ".853" ) + || upperFileName.endsWith( ".854" ) + || upperFileName.endsWith( ".855" )) + && (fileLen > 127) && (headerLen > 16) ) + { + fileFmt = FileFormat.KCC; + begAddr = getBegAddr( header, fileFmt ); + endAddr = getEndAddr( header, fileFmt ); } if( (fileFmt == null) && upperFileName.endsWith( ".SSS" ) && (fileLen >= 9) && (headerLen >= 9) ) { - fileFmt = KCBASIC_PRG; + fileFmt = FileFormat.KCBASIC_PRG; + } + if( (fileFmt == null) && upperFileName.endsWith( ".ABC" ) + && (fileLen >= 8) && (headerLen >= 8) ) + { + if( ((header[ 1 ] & 0xFF) == 0x63) + && (EmuUtil.getWord( header, 0 ) >= 0x6307) ) + { + // wahrscheinlich AC1-BASIC6-Programm + fileFmt = FileFormat.BASIC_PRG; + begAddr = 0x6300; + fileText = "AC1-BASIC6-Programmdatei"; + } + } + if( (fileFmt == null) && upperFileName.endsWith( ".BAC" ) + && (fileLen >= 8) && (headerLen >= 8) ) + { + if( ((header[ 1 ] & 0xFE) == 0x60) + && (EmuUtil.getWord( header, 0 ) >= 0x60FD) ) + { + // wahrscheinlich BACOBAS-Programm + fileFmt = FileFormat.BASIC_PRG; + begAddr = 0x60F7; + fileText = "BACOBAS-Programmdatei"; + } } if( (fileFmt == null) && upperFileName.endsWith( ".BAS" ) - && (fileLen >= 9) && (headerLen >= 9) ) + && (fileLen >= 8) && (headerLen >= 8) ) { if( ((header[ 0 ] & 0xFF) == 0xFF) - && (header[ 2 ] & 0xFF) == 0x80 ) + && ((header[ 2 ] & 0xFE) == 0x80) + && (EmuUtil.getWord( header, 1 ) >= 0x8007) ) + { + fileFmt = FileFormat.RBASIC_PRG; + begAddr = 0x8001; + } + else if( ((header[ 1 ] & 0xFE) == 0x04) + && (EmuUtil.getWord( header, 0 ) >= 0x0407) ) { - fileFmt = RBASIC; + // wahrscheinlich KC-BASIC-Programm (Interpreter im ROM) + fileFmt = FileFormat.BASIC_PRG; + begAddr = 0x0401; + fileText = "BASIC-Programmdatei f\u00FCr KC-ROM-BASIC"; + } + else if( ((header[ 1 ] & 0xFE) == 0x10) + && (EmuUtil.getWord( header, 0 ) >= 0x1007) ) + { + // wahrscheinlich KramerMC-BASIC-Programm + fileFmt = FileFormat.BASIC_PRG; + begAddr = 0x1001; + fileText = "BASIC-Programmdatei f\u00FCr Kramer-MC"; + } + else if( ((header[ 1 ] & 0xFE) == 0x60) + && (EmuUtil.getWord( header, 0 ) >= 0x60FD) ) + { + // wahrscheinlich AC1-/LLC2-BASIC-Programm + fileFmt = FileFormat.BASIC_PRG; + begAddr = 0x60F7; + fileText = "BASIC-Programmdatei f\u00FCr AC1/LLC2"; + } + else if( ((header[ 1 ] & 0xFF) == 0x63) + && (EmuUtil.getWord( header, 0 ) >= 0x6307) ) + { + // wahrscheinlich AC1-BASIC6-Programm + fileFmt = FileFormat.BASIC_PRG; + begAddr = 0x6300; + fileText = "AC1-BASIC6-Programmdatei"; + } + else if( (((header[ 1 ] & 0xFF) == 0x6F) + || ((header[ 1 ] & 0xFF) == 0x70)) + && (EmuUtil.getWord( header, 0 ) >= 0x6FBD) ) + { + // wahrscheinlich AC1-12K-BASIC-Programm + fileFmt = FileFormat.BASIC_PRG; + begAddr = 0x6FB7; + fileText = "BASIC-Programmdatei f\u00FCr AC1 (12K BASIC)"; + } + else if( ((header[ 1 ] & 0xFE) == 0x2C) + && (EmuUtil.getWord( header, 0 ) >= 0x2C07) ) + { + // wahrscheinlich KC-BASIC-Programm (Interpreter im RAM) + fileFmt = FileFormat.BASIC_PRG; + begAddr = 0x2C01; + fileText = "BASIC-Programmdatei f\u00FCr KC-RAM-BASIC"; + } + else if( (((header[ 1 ] & 0xFF) == 0x37) + || ((header[ 1 ] & 0xFF) == 0x38)) + && (EmuUtil.getWord( header, 0 ) >= 0x3776) ) + { + // wahrscheinlich HUEBLER-BASIC-Programm + fileFmt = FileFormat.BASIC_PRG; + begAddr = 0x3770; + fileText = "BASIC-Programmdatei f\u00FCr H\u00FCbler-Grafik-MC"; } } if( (fileFmt == null) && upperFileName.endsWith( ".RMC" ) && (fileLen >= 8) && (headerLen >= 7) ) { if( (header[ 0 ] & 0xFF) == 0xFE ) { - fileFmt = RMC; + fileFmt = FileFormat.RMC; begAddr = getBegAddr( header, fileFmt ); endAddr = getEndAddr( header, fileFmt ); } } + if( (fileFmt == null) && upperFileName.endsWith( ".TAP" ) + && !isKCTapHeaderAt( header, headerLen, 0 ) ) + { + fileFmt = FileFormat.ZXTAP; + } if( (fileFmt == null) && upperFileName.endsWith( ".BIN" ) && (fileLen > 0) ) { - fileFmt = BIN; + fileFmt = FileFormat.BIN; fileText = "BIN-Datei"; } if( (fileFmt == null) && upperFileName.endsWith( ".ROM" ) && (fileLen > 0) ) { - fileFmt = BIN; + fileFmt = FileFormat.BIN; fileText = "ROM-Datei"; } } @@ -348,35 +425,51 @@ public LoadData createLoadData( byte[] fileBuf ) throws IOException } + /* + * In einer KCC- oder KC-TAP-Datei wird geweohnlich die Endadresse + 1 + * eingetragen. Es gibt aber auch Faelle, + * in denen die tatsaechlie Endadresse eingetragen ist. + * Damit kein Byte zu wenig geladen wird, wird so getan, + * als ob die tatsaechliche Endadresse eingetragen ist. + */ public static LoadData createLoadData( - byte[] fileBuf, - Object fileFmt ) throws IOException + byte[] fileBuf, + FileFormat fileFmt ) throws IOException { LoadData rv = null; if( fileFmt != null ) { - if( fileFmt.equals( KCTAP_SYS ) - || fileFmt.equals( KCTAP_Z9001 ) - || fileFmt.equals( KCTAP_KC85 ) - || fileFmt.equals( KCTAP_BASIC_PRG ) ) + if( fileFmt.equals( FileFormat.CDT ) + || fileFmt.equals( FileFormat.TZX ) + || fileFmt.equals( FileFormat.ZXTAP ) ) + { + throw new IOException( + "Die Datei ist eine Tape-Datei und kann nur\n" + + "\u00FCber die emulierte Kassettenschnittstelle\n" + + "(Audio-Funktion) geladen werden." ); + } + if( fileFmt.equals( FileFormat.KCTAP_SYS ) + || fileFmt.equals( FileFormat.KCTAP_Z9001 ) + || fileFmt.equals( FileFormat.KCTAP_KC85 ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ) ) { rv = createLoadDataFromKCTAP( fileBuf, fileFmt ); - } if( fileFmt.equals( KCTAP_BASIC_DATA ) - || fileFmt.equals( KCTAP_BASIC_ASC ) - || fileFmt.equals( KCBASIC_HEAD_DATA ) - || fileFmt.equals( KCBASIC_HEAD_ASC ) ) + } if( fileFmt.equals( FileFormat.KCTAP_BASIC_DATA ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_ASC ) + || fileFmt.equals( FileFormat.KCBASIC_HEAD_DATA ) + || fileFmt.equals( FileFormat.KCBASIC_HEAD_ASC ) ) { throw new IOException( "Laden von KC-BASIC-Datenfeldern" + " und KC-BASIC-ASCII-Listings\n" + "wird nicht unterst\u00FCtzt" ); - } else if( fileFmt.equals( INTELHEX ) ) { + } else if( fileFmt.equals( FileFormat.INTELHEX ) ) { rv = createLoadDataFromINTELHEX( fileBuf ); } else { int begAddr = -1; int len = -1; - if( fileFmt.equals( HEADERSAVE ) ) { + if( fileFmt.equals( FileFormat.HEADERSAVE ) ) { if( fileBuf.length >= 32 ) { begAddr = EmuUtil.getWord( fileBuf, 0 ); - len = ((EmuUtil.getWord( fileBuf, 2 ) - begAddr) & 0xFFFF) + 1; + len = ((EmuUtil.getWord( fileBuf, 2 ) - begAddr) & 0xFFFF) + 1; } rv = new LoadData( fileBuf, 32, len, begAddr, -1, fileFmt ); int fileType = fileBuf[ 12 ] & 0xFF; @@ -384,7 +477,7 @@ public static LoadData createLoadData( rv.setStartAddr( EmuUtil.getWord( fileBuf, 4 ) ); } rv.setFileType( fileType ); - } else if( fileFmt.equals( KCB ) ) { + } else if( fileFmt.equals( FileFormat.KCB ) ) { int fileBegAddr = EmuUtil.getWord( fileBuf, 17 ); int fileEndAddr = EmuUtil.getWord( fileBuf, 19 ); if( (fileBegAddr > 0x0401) || (fileEndAddr < 0x0401 + 8) ) { @@ -420,16 +513,16 @@ public static LoadData createLoadData( + "Wenn Sie das w\u00FCnschen," + " m\u00FCssen Sie die Datei\n" + "als KCC-Datei laden." ); - } else if( fileFmt.equals( KCC ) ) { + } else if( fileFmt.equals( FileFormat.KCC ) ) { if( fileBuf.length >= 128 ) { begAddr = EmuUtil.getWord( fileBuf, 17 ); - len = ((EmuUtil.getWord( fileBuf, 19 ) - begAddr) & 0xFFFF); + len = ((EmuUtil.getWord( fileBuf, 19 ) - begAddr + 1) & 0xFFFF); } rv = new LoadData( fileBuf, 128, len, begAddr, -1, fileFmt ); if( fileBuf[ 16 ] >= 3 ) { rv.setStartAddr( EmuUtil.getWord( fileBuf, 21 ) ); } - } else if( fileFmt.equals( KCBASIC_HEAD_PRG ) ) { + } else if( fileFmt.equals( FileFormat.KCBASIC_HEAD_PRG ) ) { if( fileBuf.length > 12 ) { len = EmuUtil.getWord( fileBuf, 11 ); } @@ -440,10 +533,10 @@ public static LoadData createLoadData( fileBuf, 13, len, - (((int) fileBuf[ 14 ] & 0xFF) << 8) | 0x01, + getKCBasicBegAddr( fileBuf, 14 ), -1, fileFmt ); - } else if( fileFmt.equals( KCBASIC_PRG ) ) { + } else if( fileFmt.equals( FileFormat.KCBASIC_PRG ) ) { if( fileBuf.length > 1) { len = EmuUtil.getWord( fileBuf, 0 ); } @@ -454,12 +547,32 @@ public static LoadData createLoadData( fileBuf, 2, len, - (((int) fileBuf[ 3 ] & 0xFF) << 8) | 0x01, + getKCBasicBegAddr( fileBuf, 3 ), -1, fileFmt ); - } else if( fileFmt.equals( RBASIC ) ) { - rv = new LoadData( fileBuf, 1, fileBuf.length, 0x8001, -1, fileFmt ); - } else if( fileFmt.equals( RMC ) ) { + } else if( fileFmt.equals( FileFormat.BASIC_PRG ) ) { + if( fileBuf.length > 1) { + begAddr = getBegAddr( fileBuf, fileFmt ); + } + if( begAddr < 0 ) { + new IOException( "Laden als BASIC-Datei nicht m\u00F6glich" ); + } + rv = new LoadData( + fileBuf, + 0, + fileBuf.length, + begAddr, + -1, + fileFmt ); + } else if( fileFmt.equals( FileFormat.RBASIC_PRG ) ) { + rv = new LoadData( + fileBuf, + 1, + fileBuf.length, + 0x8001, + -1, + fileFmt ); + } else if( fileFmt.equals( FileFormat.RMC ) ) { begAddr = EmuUtil.getWord( fileBuf, 1 ); rv = new LoadData( fileBuf, @@ -494,10 +607,10 @@ public static LoadData createLoadData( File file ) throws IOException } - public boolean equalsFileFormat( String format ) + public boolean equalsFileFormat( FileFormat fmt ) { - return (this.fileFmt != null) && (format != null) ? - this.fileFmt.equals( format ) + return (this.fileFmt != null) && (fmt != null) ? + this.fileFmt.equals( fmt ) : false; } @@ -508,46 +621,83 @@ public String getAddrText() } - public static int getBegAddr( byte[] header, Object fileFmt ) + public static int getBegAddr( byte[] header, FileFormat fileFmt ) { int rv = -1; if( (header != null) && (fileFmt != null) ) { - if( fileFmt.equals( HEADERSAVE ) && (header.length > 1) ) { + if( fileFmt.equals( FileFormat.HEADERSAVE ) && (header.length > 1) ) { rv = EmuUtil.getWord( header, 0 ); } - else if( fileFmt.equals( KCB ) && (header.length > 20) ) { + else if( fileFmt.equals( FileFormat.KCB ) && (header.length > 20) ) { if( (EmuUtil.getWord( header, 17 ) <= 0x0401) && (EmuUtil.getWord( header, 19 ) >= 0x0409) ) { rv = 0x0401; } } - else if( fileFmt.equals( KCC ) && (header.length > 18) ) { + else if( fileFmt.equals( FileFormat.KCC ) && (header.length > 18) ) { rv = EmuUtil.getWord( header, 17 ); } - else if( (fileFmt.equals( KCTAP_SYS ) - || fileFmt.equals( KCTAP_Z9001 ) - || fileFmt.equals( KCTAP_KC85 )) + else if( (fileFmt.equals( FileFormat.KCTAP_SYS ) + || fileFmt.equals( FileFormat.KCTAP_Z9001) + || fileFmt.equals( FileFormat.KCTAP_KC85 )) && (header.length > 35) ) { rv = EmuUtil.getWord( header, 34 ); } - else if( fileFmt.equals( KCTAP_BASIC_PRG ) && (header.length > 31) ) { - rv = ((int) (header[ 31 ] & 0xFF) << 8) | 0x01; + else if( fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ) + && (header.length > 31) ) + { + rv = getKCBasicBegAddr( header, 31 ); } - else if( fileFmt.equals( KCBASIC_HEAD_PRG ) && (header.length > 14) ) { - rv = ((int) (header[ 14 ] & 0xFF) << 8) | 0x01; + else if( fileFmt.equals( FileFormat.KCBASIC_HEAD_PRG ) + && (header.length > 14) ) + { + rv = getKCBasicBegAddr( header, 14 ); } - else if( fileFmt.equals( KCBASIC_PRG ) && (header.length > 3) ) { - rv = ((int) (header[ 3 ] & 0xFF) << 8) | 0x01; + else if( fileFmt.equals( FileFormat.KCBASIC_PRG ) + && (header.length > 3) ) + { + rv = getKCBasicBegAddr( header, 3 ); } - else if( fileFmt.equals( RBASIC ) ) { + else if( fileFmt.equals( FileFormat.BASIC_PRG ) ) { + switch( header[ 1 ] & 0xFF ) { + case 0x10: + case 0x11: + rv = 0x1001; + break; + case 0x04: + case 0x05: + rv = 0x0401; + break; + case 0x60: + case 0x61: + rv = 0x60F7; + break; + case 0x63: + rv = 0x6300; + break; + case 0x6F: + case 0x70: + rv = 0x6FB7; + break; + case 0x2C: + case 0x2D: + rv = 0x2C01; + break; + case 0x36: + case 0x37: + rv = 0x3770; + break; + } + } + else if( fileFmt.equals( FileFormat.RBASIC_PRG ) ) { rv = 0x8001; } - else if( fileFmt.equals( RMC ) && (header.length > 2) ) { + else if( fileFmt.equals( FileFormat.RMC ) && (header.length > 2) ) { rv = EmuUtil.getWord( header, 1 ); } - else if( fileFmt.equals( INTELHEX ) && (header.length > 6) ) { + else if( fileFmt.equals( FileFormat.INTELHEX ) && (header.length > 6) ) { char c3 = (char) (header[ 3 ] & 0xFF); char c4 = (char) (header[ 4 ] & 0xFF); char c5 = (char) (header[ 5 ] & 0xFF); @@ -575,14 +725,14 @@ public int getBegAddr() } - public static int getEndAddr( byte[] header, Object fileFmt ) + public static int getEndAddr( byte[] header, FileFormat fileFmt ) { int rv = -1; if( (header != null) && (fileFmt != null) ) { - if( fileFmt.equals( HEADERSAVE ) && (header.length > 3) ) { + if( fileFmt.equals( FileFormat.HEADERSAVE ) && (header.length > 3) ) { rv = EmuUtil.getWord( header, 2 ); } - else if( fileFmt.equals( KCB ) && (header.length > 20) ) { + else if( fileFmt.equals( FileFormat.KCB ) && (header.length > 20) ) { int fileEndAddr = (EmuUtil.getWord( header, 19 ) - 1) & 0xFFFF; if( (EmuUtil.getWord( header, 17 ) <= 0x0401) && (fileEndAddr >= 0x0409) ) @@ -590,32 +740,45 @@ else if( fileFmt.equals( KCB ) && (header.length > 20) ) { rv = fileEndAddr > 0 ? fileEndAddr : 0xFFFF; } } - else if( fileFmt.equals( KCC ) && (header.length > 20) ) { + else if( fileFmt.equals( FileFormat.KCC ) && (header.length > 20) ) { rv = (EmuUtil.getWord( header, 19 ) - 1) & 0xFFFF; - if( rv == 0 ) { + if( (rv == 0) && (EmuUtil.getWord( header, 17 ) != 0) ) { + rv = 0xFFFF; + } + } + else if( (fileFmt.equals( FileFormat.KCTAP_SYS ) + || fileFmt.equals( FileFormat.KCTAP_Z9001 )) + && (header.length > 37) ) + { + rv = EmuUtil.getWord( header, 36 ) & 0xFFFF; + if( (rv == 0) && (EmuUtil.getWord( header, 34 ) != 0) ) { rv = 0xFFFF; } } - else if( (fileFmt.equals( KCTAP_SYS ) - || fileFmt.equals( KCTAP_Z9001 ) - || fileFmt.equals( KCTAP_KC85 )) + else if( (fileFmt.equals( FileFormat.KCTAP_KC85 )) && (header.length > 37) ) { rv = (EmuUtil.getWord( header, 36 ) - 1) & 0xFFFF; - if( rv == 0 ) { + if( (rv == 0) && (EmuUtil.getWord( header, 34 ) != 0) ) { rv = 0xFFFF; } } - else if( fileFmt.equals( KCTAP_BASIC_PRG ) && (header.length > 31) ) { + else if( fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ) + && (header.length > 31) ) + { rv = EmuUtil.getWord( header, 28 ) + ((header[ 31 ] & 0xFF) << 8); } - else if( fileFmt.equals( KCBASIC_HEAD_PRG ) && (header.length > 14) ) { + else if( fileFmt.equals( FileFormat.KCBASIC_HEAD_PRG ) + && (header.length > 14) ) + { rv = EmuUtil.getWord( header, 11 ) + ((header[ 14 ] & 0xFF) << 8); } - else if( fileFmt.equals( KCBASIC_PRG ) && (header.length > 3) ) { + else if( fileFmt.equals( FileFormat.KCBASIC_PRG ) + && (header.length > 3) ) + { rv = EmuUtil.getWord( header, 0 ) + ((header[ 3 ] & 0xFF) << 8); } - else if( fileFmt.equals( RMC ) && (header.length > 4) ) { + else if( fileFmt.equals( FileFormat.RMC ) && (header.length > 4) ) { rv = EmuUtil.getWord( header, 3 ); } } @@ -635,16 +798,16 @@ public String getFileDesc() } - public static String getFileDesc( byte[] header, Object fileFmt ) + public static String getFileDesc( byte[] header, FileFormat fileFmt ) { String rv = null; if( (header != null) && (fileFmt != null) ) { - if( fileFmt.equals( HEADERSAVE ) && (header.length >= 32) ) { + if( fileFmt.equals( FileFormat.HEADERSAVE ) && (header.length >= 32) ) { rv = getFileDesc( header, 16, 16 ); } - else if( (fileFmt.equals( KCTAP_SYS ) - || fileFmt.equals( KCTAP_Z9001 ) - || fileFmt.equals( KCTAP_KC85 )) + else if( (fileFmt.equals( FileFormat.KCTAP_SYS ) + || fileFmt.equals( FileFormat.KCTAP_Z9001 ) + || fileFmt.equals( FileFormat.KCTAP_KC85 )) && (header.length >= 28) ) { rv = getFileDesc( header, 17, 11 ); @@ -656,14 +819,15 @@ else if( (fileFmt.equals( KCTAP_SYS ) } } } - else if( (fileFmt.equals( KCTAP_BASIC_PRG ) - || fileFmt.equals( KCTAP_BASIC_DATA ) - || fileFmt.equals( KCTAP_BASIC_ASC )) + else if( (fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_DATA ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_ASC )) && (header.length >= 28) ) { rv = getFileDesc( header, 20, 8 ); } - else if( (fileFmt.equals( KCB ) || fileFmt.equals( KCC )) + else if( (fileFmt.equals( FileFormat.KCB ) + || fileFmt.equals( FileFormat.KCC )) && (header.length >= 11) ) { rv = getFileDesc( header, 0, 11 ); @@ -676,9 +840,9 @@ else if( (fileFmt.equals( KCB ) || fileFmt.equals( KCC )) } } } - else if( (fileFmt.equals( KCBASIC_HEAD_PRG ) - || fileFmt.equals( KCBASIC_HEAD_DATA ) - || fileFmt.equals( KCBASIC_HEAD_ASC )) + else if( (fileFmt.equals( FileFormat.KCBASIC_HEAD_PRG ) + || fileFmt.equals( FileFormat.KCBASIC_HEAD_DATA ) + || fileFmt.equals( FileFormat.KCBASIC_HEAD_ASC )) && (header.length >= 11) ) { rv = getFileDesc( header, 3, 8 ); @@ -688,7 +852,7 @@ else if( (fileFmt.equals( KCBASIC_HEAD_PRG ) } - public String getFileFormat() + public FileFormat getFileFormat() { return this.fileFmt; } @@ -706,12 +870,12 @@ public int getFileType() } - public static int getFileType( byte[] header, Object fileFmt ) + public static int getFileType( byte[] header, FileFormat fileFmt ) { int rv = -1; if( (header != null) && (fileFmt != null) ) { - if( fileFmt.equals( HEADERSAVE ) && (header.length > 12) ) { - rv = header[ 12 ] & 0xFF; + if( fileFmt.equals( FileFormat.HEADERSAVE ) && (header.length > 12) ) { + rv = (int) header[ 12 ] & 0xFF; } } return rv; @@ -730,24 +894,24 @@ public int getNextTAPOffset() } - public static int getStartAddr( byte[] header, Object fileFmt ) + public static int getStartAddr( byte[] header, FileFormat fileFmt ) { int rv = -1; if( (header != null) && (fileFmt != null) ) { - if( fileFmt.equals( HEADERSAVE ) && (header.length > 12) ) { + if( fileFmt.equals( FileFormat.HEADERSAVE ) && (header.length > 12) ) { if( (header[ 12 ] & 0xFF) == 'C' ) { rv = EmuUtil.getWord( header, 4 ); } } - else if( fileFmt.equals( KCC ) && (header.length > 22) ) { + else if( fileFmt.equals( FileFormat.KCC ) && (header.length > 22) ) { rv = EmuUtil.getWord( header, 21 ); if( rv == 0 ) { rv = -1; } } - else if( (fileFmt.equals( KCTAP_SYS ) - || fileFmt.equals( KCTAP_Z9001 ) - || fileFmt.equals( KCTAP_KC85 )) + else if( (fileFmt.equals( FileFormat.KCTAP_SYS ) + || fileFmt.equals( FileFormat.KCTAP_Z9001 ) + || fileFmt.equals( FileFormat.KCTAP_KC85 )) && (header.length > 39) ) { rv = EmuUtil.getWord( header, 38 ); @@ -755,7 +919,7 @@ else if( (fileFmt.equals( KCTAP_SYS ) rv = -1; } } - else if( fileFmt.equals( RMC ) && fileFmt.equals( "RMC" ) ) { + else if( fileFmt.equals( FileFormat.RMC ) ) { rv = EmuUtil.getWord( header, 5 ); } } @@ -769,14 +933,23 @@ public int getStartAddr() } - public static boolean isKCBasicProgramFormat( Object fileFmt ) + public static boolean isCswHeaderAt( + byte[] fileBytes, + int fileLen, + int offs ) + { + return isHeaderAt( CSW_HEADER, fileBytes, fileLen, offs ); + } + + + public static boolean isKCBasicProgramFormat( FileFormat fileFmt ) { boolean rv = false; if( fileFmt != null ) { - rv = fileFmt.equals( KCB ) - || fileFmt.equals( KCTAP_BASIC_PRG ) - || fileFmt.equals( KCBASIC_HEAD_PRG ) - || fileFmt.equals( KCBASIC_PRG ); + rv = fileFmt.equals( FileFormat.KCB ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ) + || fileFmt.equals( FileFormat.KCBASIC_HEAD_PRG ) + || fileFmt.equals( FileFormat.KCBASIC_PRG ); } return rv; } @@ -788,27 +961,40 @@ public boolean isKCBasicProgramFormat() } - public static boolean isTAPHeaderAt( + public static boolean isKCTapHeaderAt( byte[] fileBytes, int fileLen, int offs ) + { + return isHeaderAt( KCTAP_HEADER, fileBytes, fileLen, offs ); + } + + + public boolean isTapeFile() { boolean rv = false; - if( (offs + KCTAP_HLEN) < Math.min( fileBytes.length, fileLen ) ) { - rv = true; - for( int i = 0; i < KCTAP_HLEN; i++ ) { - if( ((int) fileBytes[ offs + i ] & 0xFF) - != (int) KCTAP_HEADER.charAt( i ) ) - { - rv = false; - break; - } + if( this.fileFmt != null ) { + if( this.fileFmt.equals( FileFormat.CDT ) + || this.fileFmt.equals( FileFormat.CSW ) + || this.fileFmt.equals( FileFormat.TZX ) + || this.fileFmt.equals( FileFormat.ZXTAP ) ) + { + rv = true; } } return rv; } + public static boolean isTzxHeaderAt( + byte[] fileBytes, + int fileLen, + int offs ) + { + return isHeaderAt( TZX_HEADER, fileBytes, fileLen, offs ); + } + + /* * Diese Methode liest eine Datei und liefert ihren Inhalt als Byte-Array. * Um bei einer sehr grossen Datei einen Speicherueberlauf zu verhindern, @@ -821,31 +1007,34 @@ public static boolean isTAPHeaderAt( */ public static byte[] readFile( File file ) throws IOException { - return EmuUtil.readFile( file, 0x10000 * 13 ); + return EmuUtil.readFile( file, false, 0x10000 * 13 ); } /* --- private Konstruktoren und Methoden --- */ private FileInfo( - byte[] header, - long fileLen, - int fileType, - String fileFmt, - String fileText, - String fileDesc, - String addrText, - int nextTAPOffs ) + byte[] header, + long fileLen, + int fileType, + FileFormat fileFmt, + String fileText, + String fileDesc, + String addrText, + int nextTAPOffs ) { this.header = header; this.fileLen = fileLen; this.fileType = fileType; this.fileFmt = fileFmt; - this.fileText = (fileText != null ? fileText : fileFmt); + this.fileText = fileText; this.fileDesc = fileDesc; this.addrText = addrText; this.nextTAPOffs = nextTAPOffs; this.infoText = null; + if( (this.fileText == null) && (fileFmt != null) ) { + this.fileText = fileFmt.toString(); + } if( this.fileText != null ) { if( !this.fileText.isEmpty() ) { boolean colon = true; @@ -987,7 +1176,7 @@ private static LoadData createLoadDataFromINTELHEX( dataBytes.length, firstAddr, -1, - FileInfo.INTELHEX ); + FileFormat.INTELHEX ); } } } @@ -999,9 +1188,16 @@ private static LoadData createLoadDataFromINTELHEX( } + /* + * In einer KC-TAP-Datei wird geweohnlich die Endadresse + 1 eingetragen. + * Es gibt aber auch Faelle, + * in denen die tatsaechlie Endadresse eingetragen ist. + * Damit kein Byte zu wenig geladen wird, wird so getan, + * als ob die tatsaechliche Endadresse eingetragen ist. + */ private static LoadData createLoadDataFromKCTAP( - byte[] fileBuf, - Object fileFmt ) throws IOException + byte[] fileBuf, + FileFormat fileFmt ) throws IOException { int begAddr = -1; int startAddr = -1; @@ -1009,21 +1205,23 @@ private static LoadData createLoadDataFromKCTAP( int pos = 0; int blkRemain = 0; boolean kcbasic = false; - if( (fileFmt.equals( KCTAP_SYS ) - || fileFmt.equals( KCTAP_Z9001 ) - || fileFmt.equals( KCTAP_KC85 )) + if( (fileFmt.equals( FileFormat.KCTAP_SYS ) + || fileFmt.equals( FileFormat.KCTAP_Z9001 ) + || fileFmt.equals( FileFormat.KCTAP_KC85 )) && (fileBuf.length > 39) ) { begAddr = EmuUtil.getWord( fileBuf, 34 ); - len = (EmuUtil.getWord( fileBuf, 36 ) - begAddr) & 0xFFFF; + len = (EmuUtil.getWord( fileBuf, 36 ) - begAddr + 1) & 0xFFFF; startAddr = EmuUtil.getWord( fileBuf, 38 ); if( startAddr == 0 ) { startAddr = -1; } pos = 145; } - else if( fileFmt.equals( KCTAP_BASIC_PRG ) && (fileBuf.length > 31) ) { - begAddr = ((fileBuf[ 31 ] & 0xFF) << 8) | 0x01; + else if( fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ) + && (fileBuf.length > 31) ) + { + begAddr = getKCBasicBegAddr( fileBuf, 31 ); len = EmuUtil.getWord( fileBuf, 28 ); pos = 30; blkRemain = 115; @@ -1097,6 +1295,41 @@ else if( (ch >= 'a') && (ch <= 'z') ) { } + private static int getKCBasicBegAddr( byte[] header, int pos ) + { + int rv = -1; + if( header != null ) { + if( (pos >= 0) && (pos < header.length) ) { + rv = (((int) header[ pos ]) & 0xFF) < 0x2C ? 0x0401 : 0x2C01; + } + } + return rv; + } + + + private static boolean isHeaderAt( + String pattern, + byte[] fileBytes, + int fileLen, + int offs ) + { + boolean rv = false; + if( (pattern != null) && (fileBytes != null) ) { + int pLen = pattern.length(); + if( (offs + pLen) < Math.min( fileBytes.length, fileLen ) ) { + rv = true; + for( int i = 0; i < pLen; i++ ) { + if( ((char) fileBytes[ offs + i ] & 0xFF) != pattern.charAt( i ) ) { + rv = false; + break; + } + } + } + } + return rv; + } + + private static int parseHex( InputStream in, int cnt ) throws IOException { int value = 0; @@ -1117,4 +1350,3 @@ private static int parseHex( InputStream in, int cnt ) throws IOException return value; } } - diff --git a/src/jkcemu/base/FileMover.java b/src/jkcemu/base/FileMover.java new file mode 100644 index 0000000..40cbf4c --- /dev/null +++ b/src/jkcemu/base/FileMover.java @@ -0,0 +1,174 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Verschieben von Dateien und Dateibaeumen + */ + +package jkcemu.base; + + +import java.awt.Window; +import java.io.IOException; +import java.lang.*; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collection; + + +public class FileMover extends AbstractFileWorker +{ + private Path dstPath; + private boolean dirFailed; + + + /* + * Der Paramater srcFiles ist absichtlich nicht Generics-maessig + * typisiert, um auch die von + * Transferable.getTransferData( DataFlavor.javaFileListFlavor ) + * zurueckgelieferte Collection hier problemlos uebergeben zu koennen. + */ + public FileMover( + Window owner, + Collection srcFiles, + Path dstPath, + PathListener pathListener, + Collection register ) throws IOException + { + super( owner, srcFiles, pathListener, register ); + checkSrcPathsAgainstDstPath( + dstPath, + srcFiles, + "Ein Verzeichnis kann nicht in sich selbst hinein" + + " verschoben werden." ); + this.dstPath = dstPath; + this.dirFailed = false; + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public String getFileFailedMsg( String fileName ) + { + return fileName + "\nkann nicht verschoben werden."; + } + + + @Override + public String getProgressDlgTitle() + { + return "Verschieben"; + } + + + @Override + public String getUncompletedWorkMsg() + { + return "Es konnten nicht alle Dateien, Verzeichnisse bzw.\n" + + "symbolische Links verschoben werden."; + } + + + @Override + protected void handleError( Path path, IOException ex ) + { + this.dirFailed = true; + super.handleError( path, ex ); + } + + + @Override + public FileVisitResult postVisitDirectory( Path dir, IOException ex ) + { + if( !this.cancelled && !this.dirFailed ) { + try { + Files.delete( dir ); + } + catch( NoSuchFileException ex1 ) {} + catch( IOException ex1 ) { + String msg = dir.toString() + + ":\nDas Quellverzeichnis konnte nicht" + + " gel\u00F6scht werden."; + String exMsg = ex1.getMessage(); + if( exMsg != null ) { + if( !exMsg.isEmpty() ) { + msg = msg + "\n\n" + exMsg; + } + } + handleError( dir, new IOException( msg ) ); + } + } + return this.cancelled ? + FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } + + + @Override + public FileVisitResult preVisitDirectory( + Path dir, + BasicFileAttributes attrs ) + { + FileVisitResult rv = FileVisitResult.CONTINUE; + if( !this.cancelled ) { + this.curPath = dir; + Path dstDir = resolveDst( dir ); + try { + this.dirFailed = false; + Files.copy( + dir, + dstDir, + StandardCopyOption.COPY_ATTRIBUTES, + LinkOption.NOFOLLOW_LINKS ); + pathPasted( dstDir ); + } + catch( IOException ex ) { + if( !(ex instanceof FileAlreadyExistsException) + || !Files.isDirectory( dstDir ) ) + { + handleError( dir, ex ); + if( !this.cancelled ) { + rv = FileVisitResult.SKIP_SUBTREE; + } + } + } + } + return this.cancelled ? FileVisitResult.TERMINATE : rv; + } + + + @Override + public FileVisitResult visitFile( Path file, BasicFileAttributes attrs ) + { + if( !this.cancelled ) { + this.curPath = file; + try { + Path dstFile = resolveDst( file ); + Files.move( + file, + resolveDst( file ), + StandardCopyOption.REPLACE_EXISTING ); + pathRemoved( file ); + pathPasted( dstFile ); + } + catch( IOException ex ) { + handleError( file, ex ); + } + } + return this.cancelled ? + FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } + + + /* --- private Methoden --- */ + + private Path resolveDst( Path path ) + { + Path parent = this.curParent; + if( parent != null ) { + path = parent.relativize( path ); + } + return this.dstPath.resolve( path ); + } +} diff --git a/src/jkcemu/base/FileNameFld.java b/src/jkcemu/base/FileNameFld.java index ab120ee..6b382be 100644 --- a/src/jkcemu/base/FileNameFld.java +++ b/src/jkcemu/base/FileNameFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2011 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -47,11 +47,24 @@ public File getFile() } - public void setFile( File file ) + public boolean setFile( File file ) { - this.file = file; - this.filePathElems = null; - updView(); + boolean differs = true; + if( (file != null) && (this.file != null) ) { + if( file.getPath().equals( this.file.getPath() ) ) { + differs = false; + } + } else { + if( (file == null) && (this.file == null) ) { + differs = false; + } + } + if( differs ) { + this.file = file; + this.filePathElems = null; + updView(); + } + return differs; } @@ -102,7 +115,7 @@ private void updView() int width = getWidth(); if( getPrefWidth() > width ) { if( this.filePathElems == null ) { - java.util.List elems = new ArrayList(); + java.util.List elems = new ArrayList<>(); File file = this.file; while( file != null ) { String lastElem = file.getName(); diff --git a/src/jkcemu/base/FileRemover.java b/src/jkcemu/base/FileRemover.java new file mode 100644 index 0000000..af0a44b --- /dev/null +++ b/src/jkcemu/base/FileRemover.java @@ -0,0 +1,177 @@ +/* + * (c) 2014-2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Loeschen von Dateien und Dateibaeumen + */ + +package jkcemu.base; + + +import java.awt.Window; +import java.io.IOException; +import java.lang.*; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collection; + + +public class FileRemover extends AbstractFileWorker +{ + public static void startRemove( + Window owner, + java.util.List paths, + PathListener pathListener, + Collection register ) + { + if( paths != null ) { + int n = paths.size(); + if( n > 0 ) { + StringBuilder buf = new StringBuilder( 128 ); + buf.append( "M\u00F6chten Sie " ); + if( n == 1 ) { + Path p = paths.get( 0 ); + if( Files.isDirectory( p ) ) { + buf.append( "das Verzeichnis" ); + } else if( Files.isSymbolicLink( p ) ) { + buf.append( "den symbolischen Link" ); + } else { + buf.append( "die Datei" ); + } + buf.append( "\n\'" ); + buf.append( p ); + buf.append( "\'\n" ); + } else { + int nDirs = 0; + int nFiles = 0; + int nLinks = 0; + for( Path p : paths ) { + if( Files.isDirectory( p ) ) { + nDirs++; + } else if( Files.isSymbolicLink( p ) ) { + nLinks++; + } else { + nFiles++; + } + } + if( nDirs == 1 ) { + buf.append( "das Verzeichnis" ); + } else if( nDirs > 1 ) { + buf.append( nDirs ); + buf.append( " Verzeichnisse" ); + } + if( nFiles > 0 ) { + if( nDirs > 0 ) { + if( nLinks > 0 ) { + buf.append( ", " ); + } else { + buf.append( " und " ); + } + } + if( nFiles == 1 ) { + buf.append( "die Datei" ); + } else if( nFiles > 1 ) { + buf.append( nFiles ); + buf.append( " Dateien" ); + } + } + if( nLinks > 0 ) { + if( (nDirs > 0) && (nFiles > 0) ) { + buf.append( " und " ); + } + if( nLinks == 1 ) { + buf.append( "den symbolischen Link" ); + } else if( nLinks > 1 ) { + buf.append( nLinks ); + buf.append( " symbolische Links" ); + } + } + buf.append( (char) '\u0020' ); + } + buf.append( "l\u00F6schen?" ); + if( BasicDlg.showYesNoDlg( owner, buf.toString() ) ) { + (new FileRemover( + owner, + paths, + pathListener, + register )).startWork(); + } + } + } + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public String getFileFailedMsg( String fileName ) + { + return fileName + "\nkann nicht gel\u00F6scht werden."; + } + + + @Override + public String getProgressDlgTitle() + { + return "L\u00F6schen"; + } + + + @Override + public String getUncompletedWorkMsg() + { + return "Es konnten nicht alle Dateien, Verzeichnisse bzw.\n" + + "symbolische Links gel\u00F6scht werden."; + } + + + @Override + public FileVisitResult postVisitDirectory( Path dir, IOException ex ) + { + if( !this.cancelled ) { + this.curPath = dir; + try { + Files.delete( dir ); + pathRemoved( dir ); + } + catch( NoSuchFileException ex1 ) {} + catch( IOException ex1 ) { + handleError( dir, ex1 ); + } + } + return this.cancelled ? + FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } + + + @Override + public FileVisitResult visitFile( Path file, BasicFileAttributes attrs ) + { + if( !this.cancelled ) { + this.curPath = file; + try { + Files.delete( file ); + pathRemoved( file ); + } + catch( NoSuchFileException ex1 ) {} + catch( IOException ex1 ) { + handleError( file, ex1 ); + } + } + return this.cancelled ? + FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } + + + /* --- Konstruktor --- */ + + private FileRemover( + Window owner, + java.util.List paths, + PathListener pathListener, + Collection register ) + { + super( owner, paths, pathListener, register ); + } +} diff --git a/src/jkcemu/base/FileSaver.java b/src/jkcemu/base/FileSaver.java index d4e3bc0..ac36ea0 100644 --- a/src/jkcemu/base/FileSaver.java +++ b/src/jkcemu/base/FileSaver.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -13,154 +13,46 @@ import java.util.Arrays; import jkcemu.Main; import jkcemu.emusys.Z1013; -import z80emu.*; +import z80emu.Z80Memory; public class FileSaver { - public static final String HEADERSAVE = "HEADERSAVE"; - public static final String KCC = "KCC"; - public static final String KCTAP_0 = "KCTAP_0"; - public static final String KCTAP_1 = "KCTAP_1"; - public static final String KCBASIC = "KCBASIC"; - public static final String RBASIC = "RBASIC"; - public static final String RMC = "RMC"; - public static final String INTELHEX = "INTELHEX"; - public static final String BIN = "BIN"; - - - public static void checkFileDesc( - String format, - boolean kcbasic, - String fileDesc ) throws UserInputException - { - if( (format != null) && (fileDesc != null) ) { - int m = getMaxFileDescLength( format, false ); - if( m > 0 ) { - if( fileDesc.length() > m ) { - throw new UserInputException( - "Die Bezeichnung der Datei ist zu lang (max. " - + String.valueOf( m ) + " Zeichen)." ); - } - } - } - } - - - public static String getFormatText( String format ) - { - String rv = null; - if( format != null ) { - if( format.equals( HEADERSAVE ) ) { - rv = "Headersave-Datei"; - } - else if( format.equals( KCC ) ) { - rv = "KCC-Datei"; - } - else if( format.equals( KCTAP_0 ) ) { - rv = "KC-TAP-Datei mit Block 0 (KC85/1, KC87, Z9001)"; - } - else if( format.equals( KCTAP_1 ) ) { - rv = "KC-TAP-Datei mit Block 1 (HC900, KC85/2-5, KC-BASIC)"; - } - else if( format.equals( KCBASIC ) ) { - rv = "KC-BASIC-Programmdatei"; - } - else if( format.equals( RBASIC ) ) { - rv = "RBASIC-Programmdatei"; - } - else if( format.equals( RBASIC ) ) { - rv = "RBASIC-Maschinencodedatei"; - } - else if( format.equals( INTELHEX ) ) { - rv = "Intel-HEX-Datei"; - } - } - return rv != null ? rv : "Bin\u00E4rdatei ohne Kopfdaten"; - } - - - public static int getMaxFileDescLength() - { - return 16; - } - - - public static int getMaxFileDescLength( String format, boolean kcbasic ) - { - int rv = 0; - if( format != null ) { - if( format.equals( HEADERSAVE ) ) { - rv = 16; - } else if( format.equals( KCC ) && !kcbasic ) { - rv = 11; - } else if( format.equals( KCTAP_0 ) || format.equals( KCTAP_1 ) ) { - rv = (kcbasic ? 8 : 11); - } - } - return rv; - } - - public static void saveFile( File file, - String format, - Z80MemView memory, + FileFormat fmt, + EmuMemView memory, int begAddr, int endAddr, - boolean kcbasic, - boolean rbasic, + boolean basic, int headBegAddr, Integer headStartAddr, - int headFileType, String headFileDesc, + String headFileType, Z80Memory memForHeader ) throws IOException { if( file != null ) { - boolean isHS = false; - boolean isKCC = false; - boolean isTAP_0 = false; - boolean isTAP_1 = false; - boolean isSSS = false; - boolean isRBAS = false; - boolean isRMC = false; - boolean isHEX = false; - boolean isBIN = false; - if( format != null ) { - if( format.equals( HEADERSAVE ) ) { - isHS = true; - } else if( format.equals( KCC ) ) { - isKCC = true; - } else if( format.equals( KCTAP_0 ) ) { - isTAP_0 = true; - } else if( format.equals( KCTAP_1 ) ) { - isTAP_1 = true; - } else if( format.equals( KCBASIC ) ) { - isSSS = true; - } else if( format.equals( RBASIC ) ) { - isRBAS = true; - } else if( format.equals( RMC ) ) { - isRMC = true; - } else if( format.equals( INTELHEX ) ) { - isHEX = true; - } else { - isBIN = true; - } - } else { - isBIN = true; - } BufferedOutputStream out = null; try { int headEndAddr = headBegAddr + endAddr - begAddr; byte[] hsHeaderBytes = null; out = new BufferedOutputStream( new FileOutputStream( file ) ); - if( isHS ) { - hsHeaderBytes = new byte[ 32 ]; - hsHeaderBytes[ 0 ] = (byte) (headBegAddr & 0xFF); - hsHeaderBytes[ 1 ] = (byte) ((headBegAddr >> 8) & 0xFF); - hsHeaderBytes[ 2 ] = (byte) (headEndAddr & 0xFF); - hsHeaderBytes[ 3 ] = (byte) ((headEndAddr >> 8) & 0xFF); + if( fmt.equals( FileFormat.HEADERSAVE ) ) { + int typeChar = 0x20; + if( headFileType != null ) { + if( headFileType.isEmpty() ) { + char ch = headFileType.charAt( 0 ); + if( (ch >= '\u0020') && (ch < '\u007F') ) { + typeChar = ch; + } + } + } + hsHeaderBytes = new byte[ 32 ]; + hsHeaderBytes[ 0 ] = (byte) (headBegAddr & 0xFF); + hsHeaderBytes[ 1 ] = (byte) ((headBegAddr >> 8) & 0xFF); + hsHeaderBytes[ 2 ] = (byte) (headEndAddr & 0xFF); + hsHeaderBytes[ 3 ] = (byte) ((headEndAddr >> 8) & 0xFF); if( headStartAddr != null ) { hsHeaderBytes[ 4 ] = (byte) (headStartAddr.intValue() & 0xFF); hsHeaderBytes[ 5 ] = (byte) ((headStartAddr.intValue() >> 8) @@ -175,7 +67,7 @@ public static void saveFile( hsHeaderBytes[ 9 ] = (byte) 'E'; hsHeaderBytes[ 10 ] = (byte) 'M'; hsHeaderBytes[ 11 ] = (byte) 'U'; - hsHeaderBytes[ 12 ] = (byte) headFileType; + hsHeaderBytes[ 12 ] = (byte) typeChar; hsHeaderBytes[ 13 ] = (byte) 0xD3; hsHeaderBytes[ 14 ] = (byte) 0xD3; hsHeaderBytes[ 15 ] = (byte) 0xD3; @@ -204,11 +96,17 @@ public static void saveFile( * nach einem Kaltstart entsprechen. */ int addr = begAddr; - if( kcbasic ) { - int endOfMem = memory.getMemWord( begAddr + 0x04 ); - int begOfVars = memory.getMemWord( begAddr + 0x17 ); - int begOfFields = memory.getMemWord( begAddr + 0x19 ); - int topAddr = memory.getMemWord( begAddr + 0x1B ); + if( (typeChar == 'B') + && ((begAddr == 0x0401) || (begAddr == 0x2C01)) ) + { + begAddr -= 0x0041; + int endOfMem = EmuUtil.getBasicMemWord( + memory, begAddr + 0x04 ); + int begOfVars = EmuUtil.getBasicMemWord( + memory, begAddr + 0x17 ); + int begOfFields = EmuUtil.getBasicMemWord( + memory, begAddr + 0x19 ); + int topAddr = EmuUtil.getBasicMemWord( memory, begAddr + 0x1B ); if( (endOfMem < begAddr) || (endOfMem >= 0xFF00) || (begOfVars < begAddr) || (begOfVars > endOfMem) || (begOfFields < begAddr) || (begOfFields > endOfMem) @@ -250,10 +148,14 @@ public static void saveFile( out.write( sysBytes ); addr += sysBytes.length; } - } - while( addr <= endAddr ) { - out.write( memory.getMemByte( addr, false ) ); - addr++; + while( addr <= endAddr ) { + out.write( memory.getBasicMemByte( addr ) ); + addr++; + } + } else { + while( addr <= endAddr ) { + out.write( getMemByte( memory, addr++, basic ) ); + } } int n = (addr - begAddr) % 0x20; if( n > 0 ) { @@ -261,25 +163,28 @@ public static void saveFile( out.write( 0 ); } } - if( isHS && (memForHeader != null) ) { + if( fmt.equals( FileFormat.HEADERSAVE ) + && (memForHeader != null) ) + { for( int i = 0; i < hsHeaderBytes.length; i++ ) { memForHeader.setMemByte( Z1013.MEM_HEAD + i, hsHeaderBytes[ i ] ); } } - - } else if( isKCC ) { + } + else if( fmt.equals( FileFormat.KCC ) ) { writeKCHeader( out, headBegAddr, headEndAddr, headStartAddr, - headFileDesc ); + false, + headFileDesc, + null ); int addr = begAddr; while( addr <= endAddr ) { - out.write( memory.getMemByte( addr, false ) ); - addr++; + out.write( getMemByte( memory, addr++, basic ) ); } int n = (addr - begAddr) % 0x80; if( n > 0 ) { @@ -287,15 +192,20 @@ public static void saveFile( out.write( 0 ); } } - - } else if( isTAP_0 || isTAP_1 ) { + } + else if( fmt.equals( FileFormat.KCTAP_KC85 ) + || fmt.equals( FileFormat.KCTAP_Z9001 ) + || fmt.equals( FileFormat.KCTAP_BASIC_PRG ) ) + { String s = FileInfo.KCTAP_HEADER; int n = s.length(); for( int i = 0; i < n; i++ ) { out.write( s.charAt( i ) ); } - int blkNum = isTAP_0 ? 0 : 1; - if( kcbasic ) { + boolean z9001 = fmt.equals( FileFormat.KCTAP_Z9001 ); + int blkNum = (z9001 ? 0 : 1); + if( fmt.equals( FileFormat.KCTAP_BASIC_PRG ) ) { + blkNum = 1; out.write( blkNum++ ); out.write( 0xD3 ); out.write( 0xD3 ); @@ -318,7 +228,7 @@ public static void saveFile( --n; } - int addr = begAddr + 65; + int addr = begAddr; int len = endAddr - addr + 1; out.write( len & 0xFF ); out.write( (len >> 8) & 0xFF ); @@ -333,7 +243,7 @@ public static void saveFile( } n = 128; } - out.write( memory.getMemByte( addr, false ) ); + out.write( memory.getBasicMemByte( addr ) ); addr++; --n; } @@ -355,7 +265,9 @@ public static void saveFile( headBegAddr, headEndAddr, headStartAddr, - headFileDesc ); + z9001, + headFileDesc, + headFileType ); int addr = begAddr; n = 0; while( addr <= endAddr ) { @@ -363,7 +275,7 @@ public static void saveFile( out.write( (addr + 128) > endAddr ? 0xFF : blkNum++ ); n = 128; } - out.write( memory.getMemByte( addr++, false ) ); + out.write( getMemByte( memory, addr++, basic ) ); --n; } while( n > 0 ) { @@ -371,15 +283,14 @@ public static void saveFile( --n; } } - - } else if( isSSS ) { - int addr = begAddr + 65; + } else if( fmt.equals( FileFormat.KCBASIC_PRG ) ) { + int addr = begAddr; int len = endAddr - addr + 1; int n = 2; out.write( len & 0xFF ); out.write( (len >> 8) & 0xFF ); while( addr <= endAddr ) { - out.write( memory.getMemByte( addr, false ) ); + out.write( memory.getBasicMemByte( addr ) ); n++; addr++; } @@ -390,13 +301,13 @@ public static void saveFile( out.write( 0 ); } } - - } else if( isRBAS ) { + } + else if( fmt.equals( FileFormat.RBASIC_PRG ) ) { out.write( 0xFF ); int n = 1; int addr = begAddr; while( addr <= endAddr ) { - out.write( memory.getMemByte( addr, false ) ); + out.write( memory.getBasicMemByte( addr ) ); n++; addr++; } @@ -407,8 +318,8 @@ public static void saveFile( out.write( 0 ); } } - - } else if( isRMC ) { + } + else if( fmt.equals( FileFormat.RMC ) ) { out.write( 0xFE ); out.write( begAddr & 0xFF ); out.write( begAddr >> 8 ); @@ -421,8 +332,20 @@ public static void saveFile( out.write( 0 ); out.write( 0 ); } - - } else if( isHEX ) { + int n = 7; + int addr = begAddr; + while( addr <= endAddr ) { + out.write( getMemByte( memory, addr++, basic ) ); + n++; + } + n %= 0x80; + if( n > 0 ) { + for( int i = n; i < 0x80; i++ ) { + out.write( 0 ); + } + } + } + else if( fmt.equals( FileFormat.INTELHEX ) ) { int addr = begAddr; while( addr <= endAddr ) { int cnt = writeHexSegment( @@ -430,6 +353,7 @@ public static void saveFile( memory, addr, endAddr, + basic, headBegAddr ); addr += cnt; headBegAddr += cnt; @@ -445,15 +369,14 @@ public static void saveFile( } else { - // BIN-Datei + // BASIC-Programm- und BIN-Datei int addr = begAddr; while( addr <= endAddr ) { - out.write( memory.getMemByte( addr, false ) ); - addr++; + out.write( getMemByte( memory, addr++, basic ) ); } } out.close(); - out = null; + out = null; } finally { EmuUtil.doClose( out ); @@ -467,35 +390,29 @@ public static void writeKCHeader( int begAddr, int endAddr, Integer startAddr, - String fileDesc ) throws IOException + boolean z9001, + String fileDesc, + String fileType ) throws IOException { - int n = 11; - int src = 0; - if( fileDesc != null ) { - int len = fileDesc.length(); - while( (src < len) && (n > 0) ) { - char ch = fileDesc.charAt( src++ ); - if( ch <= 0xFF ) { - out.write( ch ); - --n; - } - } - } - while( n > 0 ) { - out.write( '\u0020' ); - --n; + if( z9001 ) { + EmuUtil.writeFixLengthASCII( out, fileDesc, 8, 0 ); + EmuUtil.writeFixLengthASCII( out, fileType, 3, 0 ); + } else { + EmuUtil.writeFixLengthASCII( out, fileDesc, 11, 0x20 ); } for( int i = 0; i < 5; i++ ) { out.write( 0 ); } out.write( startAddr != null ? 3 : 2 ); - out.write( begAddr & 0xFF ); + out.write( begAddr ); out.write( begAddr >> 8 ); - endAddr++; - out.write( endAddr & 0xFF ); + if( !z9001 ) { + endAddr++; + } + out.write( endAddr ); out.write( endAddr >> 8 ); if( startAddr != null ) { - out.write( startAddr.intValue() & 0xFF ); + out.write( startAddr.intValue() ); out.write( startAddr.intValue() >> 8 ); } else { out.write( 0 ); @@ -509,6 +426,18 @@ public static void writeKCHeader( /* --- private Methoden --- */ + private static int getMemByte( EmuMemView memory, int addr, boolean basic ) + { + int b = 0; + if( basic ) { + b = memory.getBasicMemByte( addr ); + } else { + b = memory.getMemByte( addr, false ); + } + return b; + } + + private static void writeHexByte( OutputStream out, int value ) throws IOException @@ -525,9 +454,10 @@ private static void writeHexByte( */ private static int writeHexSegment( OutputStream out, - Z80MemView memory, + EmuMemView memory, int addr, int endAddr, + boolean basic, int headBegAddr ) throws IOException { int cnt = 0; @@ -546,7 +476,7 @@ private static int writeHexSegment( int cks = (cnt & 0xFF) + (hHeadBegAddr & 0xFF) + (headBegAddr & 0xFF); for( int i = 0; i < cnt; i++ ) { - int b = memory.getMemByte( addr++, false ); + int b = getMemByte( memory, addr++, basic ); writeHexByte( out, b ); cks += b; } @@ -557,4 +487,3 @@ private static int writeHexSegment( return cnt; } } - diff --git a/src/jkcemu/base/FileSelectDlg.java b/src/jkcemu/base/FileSelectDlg.java index d734bbc..1e19c7d 100644 --- a/src/jkcemu/base/FileSelectDlg.java +++ b/src/jkcemu/base/FileSelectDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2012 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -10,13 +10,16 @@ import java.awt.*; import java.awt.event.*; +import java.awt.datatransfer.*; +import java.awt.dnd.*; import java.io.File; import java.lang.*; +import java.nio.file.*; import java.util.*; import java.util.regex.*; import javax.swing.*; import javax.swing.event.*; -import javax.swing.filechooser.FileSystemView; +import javax.swing.filechooser.*; import javax.swing.text.Document; import javax.swing.table.*; import jkcemu.Main; @@ -25,10 +28,14 @@ public class FileSelectDlg extends BasicDlg implements + AbstractFileWorker.PathListener, DocumentListener, + DropTargetListener, FocusListener, ListSelectionListener { + public enum Mode { SAVE, LOAD, MULTIPLE_LOAD }; + public static class DirItem { private File dirFile; @@ -54,7 +61,7 @@ public int getLevel() private static final String defaultStatusText = "Bereit"; - private boolean forSave; + private Mode mode; private boolean startSelected; private boolean loadWithOptionsSelected; private boolean loadable; @@ -62,20 +69,27 @@ public int getLevel() private javax.swing.filechooser.FileFilter[] fileFilters; private File curDir; private File selectedFile; + private java.util.List selectedFiles; private FileSystemView fsv; - private JList list; + private JList list; private JScrollPane scrollPane; private JTextField fldFileName; - private JComboBox comboFileType; - private JComboBox comboDir; + private JComboBox comboFileType; + private JComboBox comboDir; private JButton btnCreateDir; - private JButton btnDirUp; + private JButton btnGoUp; private JButton btnApprove; private JButton btnStart; private JButton btnLoadWithOptions; private JButton btnCancel; private JLabel labelStatus; - private DefaultComboBoxModel modelDir; + private JPopupMenu mnuPopup; + private JMenuItem mnuCreateDir; + private JMenuItem mnuDelete; + private JMenuItem mnuGoUp; + private JMenuItem mnuRefresh; + private JMenuItem mnuRename; + private DefaultComboBoxModel modelDir; private Document docFileName; private java.util.List baseDirItems; private Cursor defaultCursor; @@ -85,7 +99,7 @@ public int getLevel() public FileSelectDlg( Window owner, - boolean forSave, + Mode mode, boolean startEnabled, boolean loadWithOptionsEnabled, String title, @@ -93,7 +107,7 @@ public FileSelectDlg( javax.swing.filechooser.FileFilter... fileFilters ) { super( owner, title ); - this.forSave = forSave; + this.mode = mode; this.startSelected = false; this.loadWithOptionsSelected = false; this.loadable = false; @@ -101,8 +115,9 @@ public FileSelectDlg( this.fileFilters = fileFilters; this.curDir = null; this.selectedFile = null; + this.selectedFiles = null; this.fsv = FileSystemView.getFileSystemView(); - this.baseDirItems = new ArrayList(); + this.baseDirItems = new ArrayList<>(); File defaultDir = null; File[] roots = null; @@ -194,8 +209,8 @@ public FileSelectDlg( // Verzeichnisauswahl add( new JLabel( "Suchen in:" ), gbc ); - this.modelDir = new DefaultComboBoxModel(); - this.comboDir = new JComboBox( this.modelDir ); + this.modelDir = new DefaultComboBoxModel<>(); + this.comboDir = new JComboBox<>( this.modelDir ); this.comboDir.setEditable( false ); this.comboDir.setRenderer( new FileSelectRenderer( this.fsv ) ); this.comboDir.addActionListener( this ); @@ -215,22 +230,25 @@ public FileSelectDlg( gbc.gridx++; add( toolBar, gbc ); - this.btnDirUp = createImageButton( + this.btnGoUp = createImageButton( "/images/file/folder_up.png", "Eine Ebene h\u00F6her" ); - toolBar.add( this.btnDirUp, gbc ); + toolBar.add( this.btnGoUp, gbc ); this.btnCreateDir = createImageButton( "/images/file/createdir.png", - "Neuen Ordner erstellen" ); + "Neues Verzeichnis erstellen" ); toolBar.add( this.btnCreateDir, gbc ); // Liste - this.list = new JList(); + this.list = new JList<>(); this.list.setDragEnabled( false ); this.list.setLayoutOrientation( JList.VERTICAL_WRAP ); - this.list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); + this.list.setSelectionMode( + this.mode.equals( Mode.MULTIPLE_LOAD ) ? + ListSelectionModel.MULTIPLE_INTERVAL_SELECTION + :ListSelectionModel.SINGLE_SELECTION ); this.list.setVisibleRowCount( 0 ); this.list.setCellRenderer( new FileSelectRenderer( this.fsv ) ); this.list.addKeyListener( this ); @@ -277,7 +295,7 @@ public FileSelectDlg( gbc.gridy++; add( new JLabel( "Dateityp:" ), gbc ); - this.comboFileType = new JComboBox(); + this.comboFileType = new JComboBox<>(); this.comboFileType.setEditable( false ); this.comboFileType.addItem( "Alle Dateien" ); @@ -348,7 +366,7 @@ public FileSelectDlg( add( panelBtn, gbc ); this.approveBtnText = "\u00D6ffnen"; - if( forSave ) { + if( isForSave() ) { this.approveBtnText = "Speichern"; } else { if( loadWithOptionsEnabled ) { @@ -382,6 +400,34 @@ public FileSelectDlg( panelBtn.add( this.btnCancel ); + // Popup-Menu + this.mnuPopup = new JPopupMenu(); + + this.mnuGoUp = new JMenuItem( "Eine Ebene h\u00F6her" ); + this.mnuGoUp.addActionListener( this ); + this.mnuPopup.add( this.mnuGoUp ); + + this.mnuCreateDir = new JMenuItem( "Verzeichnis erstellen..." ); + this.mnuCreateDir.addActionListener( this ); + this.mnuPopup.add( this.mnuCreateDir ); + this.mnuPopup.addSeparator(); + + this.mnuRename = new JMenuItem( "Umbenennen..." ); + this.mnuRename.setEnabled( false ); + this.mnuRename.addActionListener( this ); + this.mnuPopup.add( this.mnuRename ); + + this.mnuDelete = new JMenuItem( "L\u00F6schen" ); + this.mnuDelete.setEnabled( false ); + this.mnuDelete.addActionListener( this ); + this.mnuPopup.add( this.mnuDelete ); + this.mnuPopup.addSeparator(); + + this.mnuRefresh = new JMenuItem( "Aktualisieren" ); + this.mnuRefresh.addActionListener( this ); + this.mnuPopup.add( this.mnuRefresh ); + + // Cursors this.defaultCursor = this.list.getCursor(); if( this.defaultCursor == null ) { @@ -393,7 +439,7 @@ public FileSelectDlg( // Vorbelegung if( preSelection != null ) { if( preSelection.isDirectory() ) { - setCurDir( preSelection ); + setCurDir( preSelection, null ); } else { File parentDir = null; if( this.fsv != null ) { @@ -402,14 +448,16 @@ public FileSelectDlg( if( parentDir == null ) { parentDir = preSelection.getParentFile(); } - setCurDir( parentDir != null ? parentDir : defaultDir ); + setCurDir( parentDir != null ? parentDir : defaultDir, null ); this.fldFileName.setText( preSelection.getName() ); - if( !selectFile( preSelection ) ) { - fileSelected( preSelection ); + java.util.List preSelections + = Collections.singletonList( preSelection ); + if( selectFiles( preSelections ) == 0 ) { + filesSelected( preSelections ); } } } else { - setCurDir( defaultDir ); + setCurDir( defaultDir, null ); } this.docFileName = this.fldFileName.getDocument(); if( this.docFileName != null ) { @@ -424,6 +472,10 @@ public FileSelectDlg( updApproveBtn( false ); + // Drop aktivieren + (new DropTarget( this.fldFileName, this )).setActive( true ); + + // Fenstergroesse und -position setSize( 600, 400 ); setParentCentered(); @@ -437,6 +489,12 @@ public File getSelectedFile() } + public java.util.List getSelectedFiles() + { + return this.selectedFiles; + } + + public boolean isLoadWithOptionsSelected() { return this.loadWithOptionsSelected; @@ -449,6 +507,22 @@ public boolean isStartSelected() } + /* --- AbstractFileWorker.PathListener --- */ + + @Override + public void pathsPasted( Set paths ) + { + // leer + } + + + @Override + public void pathsRemoved( Set paths ) + { + updList( null ); + } + + /* --- DocumentListener --- */ @Override @@ -472,6 +546,47 @@ public void removeUpdate( DocumentEvent e ) } + /* --- DropTargetListener --- */ + + @Override + public void dragEnter( DropTargetDragEvent e ) + { + if( !EmuUtil.isFileDrop( e ) ) + e.rejectDrag(); + } + + + @Override + public void dragExit( DropTargetEvent e ) + { + // leer + } + + + @Override + public void dragOver( DropTargetDragEvent e ) + { + // leer + } + + + @Override + public void drop( DropTargetDropEvent e ) + { + File file = EmuUtil.fileDrop( this, e ); + if( file != null ) { + this.fldFileName.setText( file.getPath() ); + } + } + + + @Override + public void dropActionChanged( DropTargetDragEvent e ) + { + // leer + } + + /* --- FocusListener --- */ @Override @@ -508,29 +623,40 @@ public void valueChanged( ListSelectionEvent e ) this.loadable = false; this.startable = false; - String statusText = null; - boolean dirSelected = false; - File file = getSelectedListFile(); - if( file != null ) { + boolean dirSelected = false; + String fileName = null; + java.util.List files = this.list.getSelectedValuesList(); + for( File file : files ) { if( file.isDirectory() ) { dirSelected = true; - } else { - String fileName = file.getName(); - if( fileName != null ) { - if( !fileName.isEmpty() ) { - if( this.docFileName != null ) { - this.docFileName.removeDocumentListener( this ); - this.fldFileName.setText( fileName ); - this.docFileName.addDocumentListener( this ); - } else { - this.fldFileName.setText( fileName ); - } - } + break; + } + String s = file.getName(); + if( s != null ) { + if( s.isEmpty() ) { + s = null; } - statusText = fileSelected( file ); + } + if( fileName != null ) { + fileName = ""; + } else { + fileName = s; } } + if( !dirSelected && (fileName != null) ) { + if( this.docFileName != null ) { + this.docFileName.removeDocumentListener( this ); + this.fldFileName.setText( fileName ); + this.docFileName.addDocumentListener( this ); + } else { + this.fldFileName.setText( fileName ); + } + } + this.mnuDelete.setEnabled( !files.isEmpty() ); + this.mnuRename.setEnabled( files.size() == 1 ); updApproveBtn( dirSelected ); + + String statusText = filesSelected( files ); this.labelStatus.setText( statusText != null ? statusText : defaultStatusText ); } @@ -550,34 +676,34 @@ protected boolean doAction( EventObject e ) || (src == this.comboFileType) ) { rv = true; - if( !this.forSave ) { + if( !isForSave() ) { this.fldFileName.setText( "" ); } - updList(); + updList( null ); } else if( src == this.list ) { rv = true; doListAction( this.list.getSelectedIndex() ); } - else if( src == this.btnDirUp ) { + else if( (src == this.btnGoUp) || (src == this.mnuGoUp) ) { rv = true; - doDirUp(); + doGoUp(); } - else if( src == this.btnCreateDir ) { + else if( (src == this.btnCreateDir) || (src == this.mnuCreateDir) ) { rv = true; doCreateDir(); } else if( src == this.btnApprove ) { rv = true; - doApproveFile( false, false ); + doApprove( false, false ); } else if( src == this.btnStart ) { rv = true; - doApproveFile( true, false ); + doApprove( true, false ); } else if( src == this.btnLoadWithOptions ) { rv = true; - doApproveFile( false, true ); + doApprove( false, true ); } else if( src == this.fldFileName ) { rv = true; @@ -587,12 +713,36 @@ else if( src == this.btnCancel ) { rv = true; doClose(); } + else if( src == this.mnuDelete ) { + rv = true; + doDelete(); + } + else if( src == this.mnuRefresh ) { + rv = true; + doRefresh(); + } + else if( src == this.mnuRename ) { + rv = true; + doRename(); + } } } return rv; } + @Override + public void keyPressed( KeyEvent e ) + { + if( e != null ) { + if( e.getKeyCode() == KeyEvent.VK_ENTER ) { + doApprove( false, false ); + e.consume(); + } + } + } + + @Override public void mouseClicked( MouseEvent e ) { @@ -614,6 +764,19 @@ public void mouseClicked( MouseEvent e ) } + @Override + protected boolean showPopup( MouseEvent e ) + { + boolean rv = false; + Component c = e.getComponent(); + if( c != null ) { + this.mnuPopup.show( c, e.getX(), e.getY() ); + rv = true; + } + return rv; + } + + @Override public void windowOpened( WindowEvent e ) { @@ -624,30 +787,29 @@ public void windowOpened( WindowEvent e ) /* --- Aktionen --- */ - private void doApproveFile( + private void doApprove( boolean startSelected, boolean loadWithOptionsSelected ) { - File file = getSelectedListFile(); - if( file == null ) { - if( this.curDir != null ) { - String fileName = this.fldFileName.getText(); - if( fileName != null ) { - fileName = fileName.trim(); - if( !fileName.isEmpty() ) { - file = new File( fileName ); - if( !file.isAbsolute() ) { - if( this.fsv != null ) { - file = this.fsv.getChild( this.curDir, fileName ); - } else { - file = new File( this.curDir, fileName ); - } + java.util.List files = this.list.getSelectedValuesList(); + if( (files.isEmpty()) && (this.curDir != null) ) { + String fileName = this.fldFileName.getText(); + if( fileName != null ) { + fileName = fileName.trim(); + if( !fileName.isEmpty() ) { + File file = new File( fileName ); + if( !file.isAbsolute() ) { + if( this.fsv != null ) { + file = this.fsv.getChild( this.curDir, fileName ); + } else { + file = new File( this.curDir, fileName ); } } + files = Collections.singletonList( file ); } } } - approveFile( file, startSelected, loadWithOptionsSelected ); + approveFiles( files, startSelected, loadWithOptionsSelected ); } @@ -656,22 +818,46 @@ private void doCreateDir() if( this.curDir != null ) { File dirFile = EmuUtil.createDir( this, this.curDir ); if( dirFile != null ) { - setCurDir( dirFile ); + setCurDir( dirFile, null ); } } } - private void doDirUp() + private void doDelete() { - if( this.curDir != null ) { - if( this.fsv != null ) { - setCurDir( this.fsv.getParentDirectory( this.curDir ) ); - } else { - setCurDir( this.curDir.getParentFile() ); + int idx = this.list.getSelectedIndex(); + if( idx >= 0 ) { + java.util.List files = this.list.getSelectedValuesList(); + int nFiles = files.size(); + if( nFiles > 0 ) { + File file = null; + try { + java.util.List paths = new ArrayList<>( nFiles ); + for( File f : files ) { + file = f; + paths.add( f.toPath() ); + } + FileRemover.startRemove( this, paths, this, null ); + } + catch( InvalidPathException ex ) { + String msg = null; + if( file != null ) { + msg = file.getPath(); + } + if( msg != null ) { + if( msg.isEmpty() ) { + msg = null; + } + } + if( msg != null ) { + msg += "\nkonnte nicht gel\u00F6scht werden."; + } else { + msg = "L\u00F6schen fehlgeschlagen"; + } + showErrorDlg( this, msg ); + } } - } else { - setCurDir( null ); } } @@ -695,14 +881,28 @@ private void doFileNameAction() file = new File( this.curDir, fName ); } } - approveFile( file, false, true ); + approveFiles( Collections.singletonList( file ), false, true ); done = true; } } } } } - updList(); + updList( null ); + } + + + private void doGoUp() + { + File upDir = null; + if( this.curDir != null ) { + if( this.fsv != null ) { + upDir = this.fsv.getParentDirectory( this.curDir ); + } else { + upDir = this.curDir.getParentFile(); + } + } + setCurDir( upDir, this.curDir ); } @@ -715,9 +915,10 @@ private void doListAction( int idx ) Object o = model.getElementAt( idx ); if( o != null ) { if( o instanceof File ) { - File file = (File) o; - if( file != null ) - approveFile( file, false, true ); + approveFiles( + Collections.singletonList( (File) o ), + false, + true ); } } } @@ -726,6 +927,24 @@ private void doListAction( int idx ) } + private void doRefresh() + { + updList( this.list.getSelectedValuesList() ); + } + + + private void doRename() + { + java.util.List files = this.list.getSelectedValuesList(); + if( files.size() == 1 ) { + File file = EmuUtil.renameFile( this, files.get( 0 ) ); + if( file != null ) { + updList( Collections.singletonList( file ) ); + } + } + } + + /* --- private Methoden --- */ private DirItem addDirItem( java.util.List dstList, File file ) @@ -741,7 +960,7 @@ private DirItem addDirItem( java.util.List dstList, File file ) break; } if( srcList == null ) { - srcList = new ArrayList(); + srcList = new ArrayList<>(); } srcList.add( file ); if( this.fsv != null ) { @@ -776,32 +995,100 @@ private DirItem addDirItem( java.util.List dstList, File file ) } - private void approveFile( - File file, - boolean startSelected, - boolean loadWithOptionsSelected ) + private void approveFiles( + java.util.List files, + boolean startSelected, + boolean loadWithOptionsSelected ) { - if( file != null ) { - if( file.isDirectory() ) { - setCurDir( file ); - } else { - if( this.forSave && file.exists() ) { - if( !BasicDlg.showYesNoWarningDlg( + if( files != null ) { + boolean done = false; + for( File file : files ) { + if( file.isDirectory() ) { + setCurDir( file, null ); + done = true; + break; + } + } + if( !done && !files.isEmpty() ) { + File file = files.get( 0 ); + + // spezielle Behandlung beim Speichern + if( isForSave() && (files.size() == 1) ) { + + // Dateiname aufbereiten + String fName = file.getName(); + if( fName != null ) { + if( fName.startsWith( "\"" ) && fName.endsWith( "\"" ) ) { + /* + * Dateiname in doppelten Anfuehrungsstriche + * unveraendert uebernehmen + */ + if( fName.length() > 2 ) { + fName = fName.substring( 1, fName.length() - 1 ); + } else { + fName = ""; + } + file = replaceName( file, fName ); + } else { + /* + * Dateiendung automatisch anhaengen, + * wenn laut Dateifilter nur eine Endung angegeben ist + */ + if( this.fileFilters != null ) { + if( this.fileFilters.length == 1 ) { + javax.swing.filechooser.FileFilter ff = fileFilters[ 0 ]; + if( ff != null ) { + if( ff instanceof FileNameExtensionFilter ) { + String[] extensions = ((FileNameExtensionFilter) ff) + .getExtensions(); + if( extensions != null ) { + if( extensions.length == 1 ) { + String ext = extensions[ 0 ]; + if( ext != null ) { + if( !fName.toUpperCase().endsWith( + ext.toUpperCase() ) ) + { + file = replaceName( + file, + String.format( + "%s.%s", + fName, + ext ) ); + } + } + } + } + } + } + } + } + } + } + + // bei Vorhandensein der Datei warnen + if( file.exists() ) { + if( !BasicDlg.showYesNoWarningDlg( this, - "Die Datei \'" - + file.getName() + "Die Datei \'" + file.getName() + "\' existiert bereits.\n" + "M\u00F6chten Sie die Datei" + " \u00FCberschreiben?", "Best\u00E4tigung" ) ) - { - file = null; + { + file = null; + files = null; + } } } if( file != null ) { this.selectedFile = file; + this.selectedFiles = files; this.startSelected = startSelected; - this.loadWithOptionsSelected = loadWithOptionsSelected; + if( this.mode.equals( Mode.LOAD ) ) { + this.loadWithOptionsSelected = loadWithOptionsSelected; + } else { + this.loadWithOptionsSelected = false; + } doClose(); } } @@ -861,34 +1148,41 @@ private void docChanged( DocumentEvent e ) } - private String fileSelected( File file ) + private String filesSelected( java.util.List files ) { String statusText = null; - if( file != null ) { - if( file.isFile() && file.canRead() ) { - FileInfo fileInfo = FileInfo.analyzeFile( file ); - if( fileInfo != null ) { - if( !fileInfo.isKCBasicProgramFormat() - && (fileInfo.getBegAddr() >= 0) ) - { - this.loadable = true; - if( fileInfo.getStartAddr() >= 0 ) { - this.startable = true; + File file = null; + if( files != null ) { + if( files.size() == 1 ) { + file = files.get( 0 ); + if( file.isFile() && file.canRead() ) { + FileInfo fileInfo = FileInfo.analyzeFile( file ); + if( fileInfo != null ) { + if( !fileInfo.isKCBasicProgramFormat() + && (fileInfo.getBegAddr() >= 0) ) + { + this.loadable = true; + if( fileInfo.getStartAddr() >= 0 ) { + this.startable = true; + } } + statusText = fileInfo.getInfoText(); } - statusText = fileInfo.getInfoText(); } } } if( this.btnApprove != null ) { - this.btnApprove.setEnabled( (!this.forSave && this.loadable) - || (this.btnLoadWithOptions == null) ); + this.btnApprove.setEnabled( + (this.mode.equals( Mode.LOAD ) && this.loadable) + || (this.btnLoadWithOptions == null) ); } if( this.btnStart != null ) { - this.btnStart.setEnabled( !this.forSave && this.startable ); + this.btnStart.setEnabled( + this.mode.equals( Mode.LOAD ) && this.startable ); } if( this.btnLoadWithOptions != null ) { - this.btnLoadWithOptions.setEnabled( !this.forSave && (file != null) ); + this.btnLoadWithOptions.setEnabled( + this.mode.equals( Mode.LOAD ) && (file != null) ); } if( statusText != null ) { if( statusText.isEmpty() ) { @@ -899,17 +1193,20 @@ private String fileSelected( File file ) } - private void fireSetListData( final File dirFile, final File[] files ) + private void fireSetListData( + final File dirFile, + final File[] files, + final java.util.List filesToSelect ) { EventQueue.invokeLater( - new Runnable() - { - @Override - public void run() - { - setListData( dirFile, files ); - } - } ); + new Runnable() + { + @Override + public void run() + { + setListData( dirFile, files, filesToSelect ); + } + } ); } @@ -930,33 +1227,51 @@ private static int getIndexOf( java.util.List list, File file ) } - private File getSelectedListFile() + private boolean isForSave() + { + return this.mode.equals( Mode.SAVE ); + } + + + private static File replaceName( File file, String fName ) { - File file = null; - Object value = this.list.getSelectedValue(); - if( value != null ) { - if( value instanceof File ) - file = (File) value; + if( file != null ) { + File parent = file.getParentFile(); + if( parent != null ) { + file = new File( parent, fName ); + } else { + file = new File( fName ); + } } return file; } - private boolean selectFile( File file ) + private int selectFiles( Collection filesToSelect ) { - boolean rv = false; - if( file != null ) { - ListModel model = this.list.getModel(); - if( model != null ) { - int n = model.getSize(); - for( int i = 0; i < n; i++ ) { - Object o = model.getElementAt( i ); - if( o != null ) { - if( o instanceof File ) { - if( EmuUtil.equals( (File) o, file ) ) { - this.list.setSelectedIndex( i ); - rv = true; - break; + int rv = 0; + this.list.clearSelection(); + if( filesToSelect != null ) { + if( !filesToSelect.isEmpty() ) { + ListModel model = this.list.getModel(); + if( model != null ) { + Set pathsToSelect = new TreeSet<>(); + for( File file : filesToSelect ) { + String path = file.getPath(); + if( path != null ) { + if( pathsToSelect == null ) { + pathsToSelect = new TreeSet<>(); + } + pathsToSelect.add( path ); + } + } + int n = model.getSize(); + for( int i = 0; i < n; i++ ) { + String path = model.getElementAt( i ).getPath(); + if( path != null ) { + if( pathsToSelect.contains( path ) ) { + this.list.addSelectionInterval( i, i ); + rv++; } } } @@ -967,14 +1282,14 @@ private boolean selectFile( File file ) } - private void setCurDir( File dirFile ) + private void setCurDir( File dirFile, File fileToSelect ) { - if( !this.forSave ) { + if( !isForSave() ) { this.fldFileName.setText( "" ); } this.comboDir.removeActionListener( this ); DirItem defaultDirItem = null; - java.util.List items = new ArrayList(); + java.util.List items = new ArrayList<>(); items.addAll( this.baseDirItems ); if( dirFile != null ) { defaultDirItem = addDirItem( items, dirFile ); @@ -986,12 +1301,17 @@ private void setCurDir( File dirFile ) if( defaultDirItem != null ) { this.comboDir.setSelectedItem( defaultDirItem ); } - updList(); + updList( fileToSelect != null ? + Collections.singletonList( fileToSelect ) + : null ); this.comboDir.addActionListener( this ); } - private void setListData( File dirFile, File[] files ) + private void setListData( + File dirFile, + File[] files, + final java.util.List filesToSelect ) { if( (this.curDir != null) && (dirFile != null) && (files != null) ) { if( this.curDir.equals( dirFile ) ) { @@ -1019,8 +1339,9 @@ private void setListData( File dirFile, File[] files ) } } } - java.util.List entries = new ArrayList( + java.util.List entries = new ArrayList<>( files.length > 1 ? files.length : 1 ); + for( int i = 0; i < files.length; i++ ) { File file = files[ i ]; if( file.isDirectory() && !file.isHidden() ) { @@ -1056,7 +1377,21 @@ private void setListData( File dirFile, File[] files ) } } } - this.list.setListData( entries.toArray() ); + this.list.setListData( + entries.toArray( new File[ entries.size() ] ) ); + if( filesToSelect != null ) { + if( !filesToSelect.isEmpty() ) { + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + selectFiles( filesToSelect ); + } + } ); + } + } } } if( this.defaultCursor != null ) { @@ -1082,24 +1417,26 @@ private void updApproveBtn( boolean dirSelected ) stateApprove = true; } else { this.btnApprove.setText( this.approveBtnText ); - boolean hasFileName = false; - if( this.docFileName != null ) { - String fileName = this.fldFileName.getText(); - if( fileName != null ) { - fileName = fileName.trim(); - if( !fileName.isEmpty() ) { - hasFileName = true; + boolean stateSelection = (this.list.getSelectedIndex() > 0); + if( !stateSelection ) { + if( this.docFileName != null ) { + String fileName = this.fldFileName.getText(); + if( fileName != null ) { + fileName = fileName.trim(); + if( !fileName.isEmpty() ) { + stateSelection = true; + } } + } else { + stateSelection = true; } - } else { - hasFileName = true; } if( this.btnLoadWithOptions != null ) { stateApprove = this.loadable; stateStart = this.startable; - stateLoadWithOptions = hasFileName; + stateLoadWithOptions = stateSelection; } else { - stateApprove = hasFileName; + stateApprove = stateSelection; } } this.btnApprove.setEnabled( stateApprove ); @@ -1113,7 +1450,7 @@ private void updApproveBtn( boolean dirSelected ) } - private void updList() + private void updList( final java.util.List filesToSelect ) { this.curDir = null; Object dirItem = this.comboDir.getSelectedItem(); @@ -1122,18 +1459,23 @@ private void updList() this.curDir = ((DirItem) dirItem).getDirectory(); } } - this.list.setListData( new Object[ 0 ] ); + this.list.setListData( new File[ 0 ] ); if( this.curDir != null ) { this.btnCreateDir.setEnabled( true ); + this.mnuCreateDir.setEnabled( true ); + boolean state = false; if( this.fsv != null ) { - this.btnDirUp.setEnabled( - this.fsv.getParentDirectory( this.curDir ) != null ); + state = (this.fsv.getParentDirectory( this.curDir ) != null); } else { - this.btnDirUp.setEnabled( this.curDir.getParentFile() != null ); + state = (this.curDir.getParentFile() != null); } + this.btnGoUp.setEnabled( state ); + this.mnuGoUp.setEnabled( state ); } else { this.btnCreateDir.setEnabled( false ); - this.btnDirUp.setEnabled( false ); + this.mnuCreateDir.setEnabled( false ); + this.btnGoUp.setEnabled( false ); + this.mnuGoUp.setEnabled( false ); } final FileSystemView fsv = this.fsv; final File dirFile = this.curDir; @@ -1147,27 +1489,28 @@ private void updList() } } this.labelStatus.setText( "Lese Verzeichnis..." ); - Thread t = new Thread( "JKCEMU directory reader of file select dialog" ) - { - @Override - public void run() - { - File[] files = null; - try { - if( fsv != null ) { - files = fsv.getFiles( dirFile, true ); - } else { - files = dirFile.listFiles(); - } - } - catch( Exception ex ) {} - finally { - fireSetListData( dirFile, files ); - } - } - }; + Thread t = new Thread( + Main.getThreadGroup(), + "JKCEMU directory reader of file select dialog" ) + { + @Override + public void run() + { + File[] files = null; + try { + if( fsv != null ) { + files = fsv.getFiles( dirFile, true ); + } else { + files = dirFile.listFiles(); + } + } + catch( Exception ex ) {} + finally { + fireSetListData( dirFile, files, filesToSelect ); + } + } + }; t.start(); } } } - diff --git a/src/jkcemu/base/FileTableModel.java b/src/jkcemu/base/FileTableModel.java index 1cba3ff..f0cfee4 100644 --- a/src/jkcemu/base/FileTableModel.java +++ b/src/jkcemu/base/FileTableModel.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2011 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -29,7 +29,7 @@ public enum Column { VALUE, READ_ONLY, SYSTEM_FILE, - ARCHIVED }; + ARCHIVE }; private static DateFormat dateFmt = null; @@ -43,7 +43,7 @@ public enum Column { public FileTableModel( Column... cols ) { this.cols = cols; - this.rows = new java.util.ArrayList(); + this.rows = new java.util.ArrayList<>(); this.sortCol = Column.NAME; this.sortCaseSensitive = false; this.sortDesc = false; @@ -202,7 +202,7 @@ public Class getColumnClass( int col ) case READ_ONLY: case SYSTEM_FILE: - case ARCHIVED: + case ARCHIVE: rv = Boolean.class; break; } @@ -260,8 +260,8 @@ public String getColumnName( int col ) rv = "System-Datei"; break; - case ARCHIVED: - rv = "Archiviert"; + case ARCHIVE: + rv = "Archiv"; break; } } @@ -284,56 +284,58 @@ public Object getValueAt( int row, int col ) && (col >= 0) && (col < this.cols.length) ) { FileEntry entry = this.rows.get( row ); - switch( this.cols[ col ] ) { - case NAME: - rv = entry.getName(); - break; - - case INFO: - rv = entry.getInfo(); - break; - - case SIZE: - rv = entry.getSize(); - break; - - case LAST_MODIFIED: - { - Long lastModified = entry.getLastModified(); - if( lastModified != null ) { - if( dateFmt == null ) { - dateFmt = DateFormat.getDateTimeInstance( + if( entry != null ) { + switch( this.cols[ col ] ) { + case NAME: + rv = entry.getName(); + break; + + case INFO: + rv = entry.getInfo(); + break; + + case SIZE: + rv = entry.getSize(); + break; + + case LAST_MODIFIED: + { + Long lastModified = entry.getLastModified(); + if( lastModified != null ) { + if( dateFmt == null ) { + dateFmt = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM ); + } + rv = dateFmt.format( new java.util.Date( lastModified ) ); } - rv = dateFmt.format( new java.util.Date( lastModified ) ); } - } - break; + break; - case FILE: - rv = entry.getFile(); - break; + case FILE: + rv = entry.getFile(); + break; - case USER_NUM: - rv = entry.getUserNum(); - break; + case USER_NUM: + rv = entry.getUserNum(); + break; - case VALUE: - rv = entry.getValue(); - break; + case VALUE: + rv = entry.getValue(); + break; - case READ_ONLY: - rv = toBoolean( entry.isReadOnly() ); - break; + case READ_ONLY: + rv = toBoolean( entry.isReadOnly() ); + break; - case SYSTEM_FILE: - rv = toBoolean( entry.isSystemFile() ); - break; + case SYSTEM_FILE: + rv = toBoolean( entry.isSystemFile() ); + break; - case ARCHIVED: - rv = toBoolean( entry.isArchived() ); - break; + case ARCHIVE: + rv = toBoolean( entry.isArchive() ); + break; + } } } return rv; @@ -348,7 +350,7 @@ public boolean isCellEditable( int row, int col ) switch( this.cols[ col ] ) { case READ_ONLY: case SYSTEM_FILE: - case ARCHIVED: + case ARCHIVE: rv = true; break; } @@ -363,26 +365,28 @@ public void setValueAt( Object value, int row, int col ) if( (row >= 0) && (row < this.rows.size()) && (col >= 0) && (col < this.cols.length) ) { - boolean done = true; FileEntry entry = this.rows.get( row ); - switch( this.cols[ col ] ) { - case READ_ONLY: - entry.setReadOnly( parseBoolean( value ) ); - done = true; - break; - - case SYSTEM_FILE: - entry.setSystemFile( parseBoolean( value ) ); - done = true; - break; - - case ARCHIVED: - entry.setArchived( parseBoolean( value ) ); - done = true; - break; - } - if( done ) { - fireTableRowsUpdated( row, row ); + if( entry != null ) { + boolean done = true; + switch( this.cols[ col ] ) { + case READ_ONLY: + entry.setReadOnly( parseBoolean( value ) ); + done = true; + break; + + case SYSTEM_FILE: + entry.setSystemFile( parseBoolean( value ) ); + done = true; + break; + + case ARCHIVE: + entry.setArchive( parseBoolean( value ) ); + done = true; + break; + } + if( done ) { + fireTableRowsUpdated( row, row ); + } } } } diff --git a/src/jkcemu/base/FileTimesView.java b/src/jkcemu/base/FileTimesView.java new file mode 100644 index 0000000..28ab1a5 --- /dev/null +++ b/src/jkcemu/base/FileTimesView.java @@ -0,0 +1,23 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Sicht auf die Zeitstempel einer Datei + */ + +package jkcemu.base; + +import java.lang.*; + + +public interface FileTimesView +{ + public Long getCreationMillis(); + public Long getLastAccessMillis(); + public Long getLastModifiedMillis(); + public void setTimesInMillis( + Long creationMillis, + Long lastAccessMillis, + Long lastModifiedMillis ); +}; diff --git a/src/jkcemu/base/FileTimesViewFactory.java b/src/jkcemu/base/FileTimesViewFactory.java new file mode 100644 index 0000000..aa3ef2e --- /dev/null +++ b/src/jkcemu/base/FileTimesViewFactory.java @@ -0,0 +1,68 @@ +/* + * (c) 2015-2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Factory-Klasse zum Erzeugen von FileTimesView-Objekten + * + * Der Zugriff auf die Datei erfolgt ausschliesslich ueber die Klasse File. + * Da man damit jedoch den Zeitpunkt der Erzeugung und + * den des letzten Zugriffs nicht ermitteln kann, + * liefern die FileTimesView-Objekte nur den Zeitpunkt + * der letzten Aenderung. + */ + +package jkcemu.base; + +import java.io.File; +import java.lang.*; + + +public class FileTimesViewFactory +{ + public FileTimesViewFactory() + { + // leer + } + + + public FileTimesView getFileTimesView( final File file ) + { + return new FileTimesView() + { + @Override + public Long getCreationMillis() + { + return null; + } + + @Override + public Long getLastAccessMillis() + { + return null; + } + + @Override + public Long getLastModifiedMillis() + { + long millis = file.lastModified(); + return millis != 0L ? new Long( millis ) : null; + } + + @Override + public void setTimesInMillis( + Long creationMillis, + Long lastAccessMillis, + Long lastModifiedMillis ) + { + if( lastModifiedMillis != null ) { + try { + file.setLastModified( + lastModifiedMillis.longValue() ); + } + catch( Exception ex ) {} + } + } + }; + } +} diff --git a/src/jkcemu/base/FileTreeNode.java b/src/jkcemu/base/FileTreeNode.java index ae4a823..f7402ae 100644 --- a/src/jkcemu/base/FileTreeNode.java +++ b/src/jkcemu/base/FileTreeNode.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2010 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -10,55 +10,68 @@ import java.io.File; import java.lang.*; +import java.nio.file.*; import java.util.*; import javax.swing.tree.TreeNode; public class FileTreeNode implements TreeNode { + protected Path path; protected File file; protected boolean fileSystemRoot; protected boolean childrenLoaded; protected Vector vChildren; private TreeNode parent; + private boolean allowsChildren; private boolean leaf; private String nodeName; - public FileTreeNode( TreeNode parent, File file, boolean fileSystemRoot ) + public FileTreeNode( TreeNode parent, Path path, boolean fileSystemRoot ) { - this.parent = parent; - this.file = file; - this.fileSystemRoot = fileSystemRoot; - this.leaf = !fileSystemRoot; - this.childrenLoaded = false; - this.vChildren = null; - if( this.leaf && (this.file != null) ) { - if( this.file.isDirectory() ) { - this.leaf = false; - } - } - updNodeText(); + this.parent = parent; + this.path = path; + this.file = null; + this.fileSystemRoot = fileSystemRoot; + this.allowsChildren = fileSystemRoot; + this.leaf = !fileSystemRoot; + this.childrenLoaded = false; + this.vChildren = null; + updNode(); } public void add( FileTreeNode fileNode ) { if( this.vChildren == null ) { - this.vChildren = new Vector(); + this.vChildren = new Vector<>(); } this.vChildren.add( fileNode ); - this.leaf = false; + this.allowsChildren = true; + this.leaf = false; } - public File getFile() + public synchronized File getFile() { + if( (this.file == null) && (this.path != null) ) { + try { + this.file = this.path.toFile(); + } + catch( UnsupportedOperationException ex ) {} + } return this.file; } + public Path getPath() + { + return this.path; + } + + public boolean hasChildrenLoaded() { return this.childrenLoaded; @@ -73,7 +86,8 @@ public boolean isFileSystemRoot() public void removeAllChildren() { - this.vChildren.clear(); + if( this.vChildren != null ) + this.vChildren.clear(); } @@ -81,7 +95,10 @@ public void removeFromParent() { if( this.parent != null ) { if( this.parent instanceof FileTreeNode ) { - ((FileTreeNode) this.parent).vChildren.remove( this ); + FileTreeNode node = (FileTreeNode) this.parent; + if( node.vChildren != null ) { + node.vChildren.remove( this ); + } } } } @@ -95,18 +112,76 @@ public void setChildrenLoaded( boolean state ) public void setFile( File file ) { + this.path = null; this.file = file; - updNodeText(); + if( file != null ) { + try { + this.path = file.toPath(); + } + catch( InvalidPathException ex ) {} + } + updNode(); + } + + + public void sort( FileTreeNodeComparator comparator ) + { + if( (this.vChildren != null) && (comparator != null) ) { + try { + Collections.sort( this.vChildren, comparator ); + } + catch( ClassCastException ex ) {} + } } - protected void updNodeText() + protected void updNode() { - this.nodeName = null; - if( this.file != null ) { - this.nodeName = (this.fileSystemRoot ? - this.file.getPath() - : this.file.getName()); + if( this.path != null ) { + if( this.fileSystemRoot ) { + this.nodeName = this.path.toString(); + } else { + Path p = null; + int n = this.path.getNameCount(); + if( n > 0 ) { + p = this.path.getName( n - 1 ); + } + if( p == null ) { + p = this.path; + } + this.nodeName = p.toString(); + if( Files.isDirectory( this.path ) ) { + this.allowsChildren = true; + this.leaf = false; + } else { + this.allowsChildren = false; + this.leaf = true; + } + try { + if( Files.isSymbolicLink( path ) ) { + StringBuilder buf = new StringBuilder( 256 ); + if( this.nodeName != null ) { + buf.append( this.nodeName ); + } + buf.append( " \u2192" ); + Path targetPath = Files.readSymbolicLink( path ); + if( targetPath != null ) { + String s = targetPath.toString(); + if( s != null ) { + if( !s.isEmpty() ) { + buf.append( (char) '\u0020' ); + buf.append( s ); + } + } + } + this.allowsChildren = false; + this.nodeName = buf.toString(); + } + } + catch( Exception ex ) { + this.allowsChildren = false; + } + } } if( this.nodeName == null ) { this.nodeName = "?"; @@ -120,7 +195,7 @@ protected void updNodeText() public Enumeration children() { if( this.vChildren == null ) { - this.vChildren = new Vector(); + this.vChildren = new Vector<>(); } return this.vChildren.elements(); } @@ -129,7 +204,7 @@ public Enumeration children() @Override public boolean getAllowsChildren() { - return !this.leaf; + return this.allowsChildren; } diff --git a/src/jkcemu/base/FileTreeNodeComparator.java b/src/jkcemu/base/FileTreeNodeComparator.java new file mode 100644 index 0000000..12eeb5a --- /dev/null +++ b/src/jkcemu/base/FileTreeNodeComparator.java @@ -0,0 +1,121 @@ +/* + * (c) 2008-2014 Jens Mueller + * + * Kleincomputer-Emulator + * + * Vergleicher fuer Dateien, die in einem FileTreeNode-Objekt gekapselt sind + * + * Beim Sortieren der Dateisystemwurzeln wird nach dem + * abstrakten Pfad sortiert. + * Ansonsten vergleicht dieser Comparator nur die Dateinamen + * und stellt sicher, dass Verzeichnisse vor den Dateien gestellt werden. + */ + +package jkcemu.base; + +import java.lang.*; +import java.io.File; +import jkcemu.base.FileTreeNode; + + +public class FileTreeNodeComparator + implements java.util.Comparator +{ + private static FileTreeNodeComparator caseSensitiveInstance = null; + private static FileTreeNodeComparator ignoreCaseInstance = null; + + private boolean caseSensitive; + private boolean forFileSystemRoots; + + + public static FileTreeNodeComparator getCaseSensitiveInstance() + { + if( caseSensitiveInstance == null ) { + caseSensitiveInstance = new FileTreeNodeComparator( true ); + } + return caseSensitiveInstance; + } + + + public static FileTreeNodeComparator getIgnoreCaseInstance() + { + if( ignoreCaseInstance == null ) { + ignoreCaseInstance = new FileTreeNodeComparator( false ); + } + return ignoreCaseInstance; + } + + + public void setForFileSystemRoots( boolean state ) + { + this.forFileSystemRoots = state; + } + + + /* --- Comparator --- */ + + @Override + public int compare( FileTreeNode o1, FileTreeNode o2 ) + { + int rv = -1; + if( (o1 != null) && (o2 != null) ) { + File f1 = o1.getFile(); + File f2 = o2.getFile(); + if( (f1 != null) && (f2 != null) ) { + if( this.forFileSystemRoots ) { + String s1 = f1.getPath(); + String s2 = f2.getPath(); + if( s1 == null ) { + s1 = ""; + } + if( s2 == null ) { + s2 = ""; + } + if( this.caseSensitive ) { + rv = s1.compareTo( s2 ); + } else { + rv = s1.compareToIgnoreCase( s2 ); + } + } else { + if( f1.isDirectory() && !f2.isDirectory() ) { + rv = -1; + } else if( !f1.isDirectory() && f2.isDirectory() ) { + rv = 1; + } else { + String s1 = f1.getName(); + String s2 = f2.getName(); + if( s1 == null ) { + s1 = ""; + } + if( s2 == null ) { + s2 = ""; + } + if( this.caseSensitive ) { + rv = s1.compareTo( s2 ); + } else { + rv = s1.compareToIgnoreCase( s2 ); + } + } + } + } + } + return rv; + } + + + @Override + public boolean equals( Object o ) + { + return o == this; + } + + + /* --- private Konstruktoren --- */ + + private FileTreeNodeComparator( boolean caseSensitive ) + { + this.caseSensitive = caseSensitive; + this.forFileSystemRoots = false; + } +} + diff --git a/src/jkcemu/base/HelpFrm.java b/src/jkcemu/base/HelpFrm.java index e046ae8..f3d173c 100644 --- a/src/jkcemu/base/HelpFrm.java +++ b/src/jkcemu/base/HelpFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -118,7 +118,7 @@ private HelpFrm() setTitle( "JKCEMU Hilfe" ); Main.updIcon( this ); this.timer = new javax.swing.Timer( 500, this ); - this.urlStack = new Stack(); + this.urlStack = new Stack<>(); this.urlHome = getClass().getResource( "/help/home.htm" ); @@ -311,7 +311,7 @@ private void setUrl( boolean saveCurViewPos, URL url, Double viewPos ) this.mnuNavHome.setEnabled( stateHome ); this.btnHome.setEnabled( stateHome ); } - catch( IOException ex ) { + catch( Exception ex ) { BasicDlg.showErrorDlg( this, "Die Hilfeseite kann nicht angezeigt werden.\n\n" diff --git a/src/jkcemu/base/HexCharFld.java b/src/jkcemu/base/HexCharFld.java new file mode 100644 index 0000000..7d67d02 --- /dev/null +++ b/src/jkcemu/base/HexCharFld.java @@ -0,0 +1,818 @@ +/* + * (c) 2008-2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Komponente fuer eine Hex-Character-Anzeige + */ + +package jkcemu.base; + +import java.awt.*; +import java.awt.event.*; +import java.lang.*; +import java.util.ArrayList; +import javax.swing.*; +import javax.swing.event.*; + + +public class HexCharFld extends JComponent implements Scrollable +{ + public static final int BYTES_PER_ROW = 16; + public static final int MARGIN = 5; + + private static final int SEP_W = 20; + private static final int PAD_Y = 1; + + private static final JComponent fontPrototypeFld = new JTextArea(); + + private ByteDataSource dataSrc; + private java.util.List caretListeners; + private Dimension prefScrollableVPSize; + private boolean asciiSelected; + private int caretPos; + private int markPos; + private int hRow; + private int hChar; + private int wChar; + private int wHex; + private int xHex; + private int xAscii; + private int visibleRows; + private int visibleXLeft; + private int visibleYTop; + private int visibleWidth; + private int visibleHeight; + + + public HexCharFld( ByteDataSource dataSrc ) + { + this.dataSrc = dataSrc; + this.caretListeners = null; + this.prefScrollableVPSize = null; + this.asciiSelected = false; + this.caretPos = -1; + this.markPos = -1; + this.hRow = 0; + this.hChar = 0; + this.wChar = 0; + this.xHex = 0; + this.xAscii = 0; + this.visibleRows = 0; + this.visibleXLeft = 0; + this.visibleYTop = 0; + this.visibleWidth = 0; + this.visibleHeight = 0; + + Font font = fontPrototypeFld.getFont(); + if( font != null ) { + font = new Font( Font.MONOSPACED, font.getStyle(), font.getSize() ); + } else { + font = new Font( Font.MONOSPACED, Font.PLAIN, 12 ); + } + setFont( font ); + setBackground( SystemColor.text ); + setForeground( SystemColor.textText ); + addKeyListener( + new KeyAdapter() + { + @Override + public void keyPressed( KeyEvent e ) + { + if( keyAction( e.getKeyCode(), e.isShiftDown() ) ) { + e.consume(); + } + } + } ); + addMouseListener( + new MouseAdapter() + { + @Override + public void mousePressed( MouseEvent e ) + { + mouseAction( e.getX(), e.getY(), false ); + e.consume(); + } + } ); + addMouseMotionListener( + new MouseAdapter() + { + @Override + public void mouseDragged( MouseEvent e ) + { + mouseAction( e.getX(), e.getY(), true ); + e.consume(); + } + } ); + } + + + public synchronized void addCaretListener( CaretListener listener ) + { + if( this.caretListeners == null ) { + this.caretListeners = new ArrayList<>(); + } + this.caretListeners.add( listener ); + } + + + public synchronized void removeCaretListener( CaretListener listener ) + { + if( this.caretListeners != null ) + this.caretListeners.remove( listener ); + } + + + public void copySelectedBytesAsAscii() + { + int dataLen = this.dataSrc.getDataLength(); + int m1 = -1; + int m2 = -1; + if( (this.caretPos >= 0) && (this.markPos >= 0) ) { + m1 = Math.min( this.caretPos, this.markPos ); + m2 = Math.max( this.caretPos, this.markPos ); + } else { + m1 = this.caretPos; + m2 = this.caretPos; + } + if( m2 >= dataLen ) { + m2 = dataLen - 1; + } + if( m1 >= 0 ) { + StringBuilder buf = new StringBuilder( m2 - m1 + 1 ); + while( m1 <= m2 ) { + int b = this.dataSrc.getDataByte( m1++ ); + buf.append( (char) ((b >= 0x20) && (b < 0x7F) ? b : '.') ); + } + if( buf.length() > 0 ) { + EmuUtil.copyToClipboard( this, buf.toString() ); + } + } + } + + + public void copySelectedBytesAsHex() + { + int dataLen = this.dataSrc.getDataLength(); + int m1 = -1; + int m2 = -1; + if( (this.caretPos >= 0) && (this.markPos >= 0) ) { + m1 = Math.min( this.caretPos, this.markPos ); + m2 = Math.max( this.caretPos, this.markPos ); + } else { + m1 = this.caretPos; + m2 = this.caretPos; + } + if( m2 >= dataLen ) { + m2 = dataLen - 1; + } + if( m1 >= 0 ) { + StringBuilder buf = new StringBuilder( (m2 - m1 + 1) * 3 ); + boolean sp = false; + while( m1 <= m2 ) { + if( sp ) { + buf.append( '\u0020' ); + } + int b = this.dataSrc.getDataByte( m1++ ); + buf.append( EmuUtil.getHexChar( b >> 4 ) ); + buf.append( EmuUtil.getHexChar( b ) ); + sp = true; + } + if( buf.length() > 0 ) { + EmuUtil.copyToClipboard( this, buf.toString() ); + } + } + } + + + public void copySelectedBytesAsDump() + { + int dataLen = this.dataSrc.getDataLength(); + int m1 = -1; + int m2 = -1; + if( (this.caretPos >= 0) && (this.markPos >= 0) ) { + m1 = Math.min( this.caretPos, this.markPos ); + m2 = Math.max( this.caretPos, this.markPos ); + } else { + m1 = this.caretPos; + m2 = this.caretPos; + } + if( m2 >= dataLen ) { + m2 = dataLen - 1; + } + if( m1 >= 0 ) { + int rows = m1 / BYTES_PER_ROW; + int pos = rows * BYTES_PER_ROW; // auf Anfang der Zeile setzen + rows = (m2 + BYTES_PER_ROW - 1) / BYTES_PER_ROW; + int endPos = rows * BYTES_PER_ROW; + + StringBuilder buf = new StringBuilder( + ((m2 - m1) / BYTES_PER_ROW) * ((BYTES_PER_ROW * 4) + 12) ); + + int addrOffs = this.dataSrc.getAddrOffset(); + String addrFmt = createAddrFmtString(); + while( pos < m2 ) { + buf.append( String.format( addrFmt, addrOffs + pos ) ); + buf.append( "\u0020\u0020" ); + for( int i = 0; i < BYTES_PER_ROW; i++ ) { + int idx = pos + i; + if( idx < dataLen ) { + buf.append( + String.format( " %02X", this.dataSrc.getDataByte( idx ) ) ); + } else { + buf.append( "\u0020\u0020\u0020" ); + } + } + buf.append( "\u0020\u0020\u0020" ); + for( int i = 0; i < BYTES_PER_ROW; i++ ) { + int idx = pos + i; + if( idx >= dataLen ) { + break; + } + char ch = (char) this.dataSrc.getDataByte( pos + i ); + if( (ch < 0x20) || (ch > 0x7F) ) { + ch = '.'; + } + buf.append( (char) ch ); + } + buf.append( (char) '\n' ); + pos += BYTES_PER_ROW; + } + if( buf.length() > 0 ) { + EmuUtil.copyToClipboard( this, buf.toString() ); + } + } + } + + + public String createAddrFmtString() + { + int maxAddr = this.dataSrc.getAddrOffset() + + this.dataSrc.getDataLength() + - 1; + if( maxAddr < 0 ) { + maxAddr = 0; + } + int addrDigits = 0; + while( maxAddr > 0 ) { + addrDigits++; + maxAddr >>= 4; + } + return String.format( "%%0%dX", addrDigits > 4 ? addrDigits : 4 ); + } + + + public int getCaretPosition() + { + return this.caretPos; + } + + + public int getCharWidth() + { + if( this.wChar == 0 ) { + calcPositions(); + } + return this.wChar; + } + + + public int getDataIndexAt( int x, int y ) + { + int rv = -1; + if( (this.wChar > 0) && (this.wHex > 0) + && (y > MARGIN) && (this.hRow > 0) ) + { + int col = -1; + if( (x >= this.xHex) + && (x < this.xHex + (BYTES_PER_ROW * this.wHex)) ) + { + int m = (x - this.xHex) % this.wHex; + if( m < (2 * this.wChar) ) { + col = (x - this.xHex) / this.wHex; + } + } + else if( (x >= this.xAscii) + && (x < this.xAscii + (BYTES_PER_ROW * this.wChar)) ) + { + col = (x - this.xAscii) / this.wChar; + } + if( col >= 0 ) { + int row = (y - MARGIN) / this.hRow; + rv = (row * BYTES_PER_ROW) + col; + } + } + if( rv > this.dataSrc.getDataLength() ) { + rv = -1; + } + return rv; + } + + + public int getMarkPosition() + { + return this.markPos; + } + + + public int getDefaultPreferredWidth() + { + if( this.wChar == 0 ) { + calcPositions(); + } + return this.xAscii + (BYTES_PER_ROW * this.wChar) + MARGIN; + } + + + public int getRowHeight() + { + return this.hRow; + } + + + public void refresh() + { + setCaretPosition( -1, false ); + + Component parent = getParent(); + if( parent != null ) { + if( parent instanceof JViewport ) { + ((JViewport) parent).setView( null ); + ((JViewport) parent).setView( this ); + } + } + invalidate(); + repaint(); + } + + + public void setCaretPosition( int pos, boolean moveOp ) + { + if( (pos < 0) && (pos >= this.dataSrc.getDataLength()) ) { + pos = -1; + moveOp = false; + } + boolean changed = (this.caretPos != pos); + this.caretPos = pos; + if( !moveOp ) { + this.markPos = pos; + } + scrollCaretToVisible(); + repaint(); + + if( changed ) { + fireCaretListeners(); + } + } + + + public void setSelection( int begPos, int endPos ) + { + if( (begPos < 0) && (begPos >= this.dataSrc.getDataLength()) ) { + begPos = -1; + endPos = -1; + } + boolean changed = (this.caretPos != begPos); + this.caretPos = endPos; + this.markPos = begPos; + scrollCaretToVisible(); + repaint(); + + if( changed ) { + fireCaretListeners(); + } + } + + + /* --- Scrollable --- */ + + @Override + public Dimension getPreferredScrollableViewportSize() + { + Dimension prefSize = this.prefScrollableVPSize; + return prefSize != null ? prefSize : getPreferredSize(); + } + + + @Override + public int getScrollableBlockIncrement( + Rectangle visibleRect, + int orientation, + int direction ) + { + int rv = 0; + if( direction < 0 ) { + rv = this.visibleRows * this.hRow; + } else if( direction > 0 ) { + rv = this.wChar; + } + return rv; + } + + + @Override + public boolean getScrollableTracksViewportHeight() + { + boolean rv = false; + Component p = getParent(); + if( p != null ) { + if( (p instanceof JViewport) + && (p.getHeight() > getPreferredSize().height) ) + { + // Viewport voll ausfuellen + rv = true; + } + } + return rv; + } + + + @Override + public boolean getScrollableTracksViewportWidth() + { + boolean rv = false; + Component p = getParent(); + if( p != null ) { + if( (p instanceof JViewport) + && (p.getWidth() > getPreferredSize().width) ) + { + // Viewport voll ausfuellen + rv = true; + } + } + return rv; + } + + + @Override + public int getScrollableUnitIncrement( + Rectangle visibleRect, + int orientation, + int direction ) + { + int rv = 0; + if( direction < 0 ) { + rv = this.hRow; + } else if( direction > 0 ) { + rv = this.wChar; + } + return rv; + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public Dimension getPreferredSize() + { + Dimension rv = null; + if( isPreferredSizeSet() ) { + rv = super.getPreferredSize(); + } else { + int h = 0; + int w = 0; + int len = this.dataSrc.getDataLength(); + if( len > 0 ) { + int rows = (len + BYTES_PER_ROW - 1) / BYTES_PER_ROW; + h = (rows * this.hRow) + (2 * MARGIN); + w = getDefaultPreferredWidth(); + } + rv = new Dimension( w, h ); + } + return rv; + } + + + @Override + public boolean isFocusable() + { + return true; + } + + + @Override + public void paintComponent( Graphics g ) + { + // sichtbarer Bereich ermitteln + int visibleRows = 0; + int xVisible = 0; + int yVisible = 0; + int yOffs = 0; + int wVisible = getWidth(); + int hVisible = getHeight(); + if( (wVisible > 0) && (hVisible > 0) ) { + + // sichtbarer Bereich ermitteln + Component parent = getParent(); + if( parent != null ) { + if( parent instanceof JViewport ) { + Rectangle r = ((JViewport) parent).getViewRect(); + if( r != null ) { + xVisible += r.x; + yVisible += r.y; + wVisible = r.width; + hVisible = r.height; + yOffs = r.y; + } + } + } + + // Hintergrund loeschen + g.setColor( getBackground() ); + g.setPaintMode(); + g.fillRect( xVisible, yVisible, wVisible, hVisible ); + + // Inhalt erzeugen + int dataLen = this.dataSrc.getDataLength(); + if( (dataLen > 0) && (this.hRow > 0) ) { + if( this.wChar == 0 ) { + calcPositions(); + } + g.setFont( getFont() ); + + // Anfang und Ende der Markierung + int m1 = -1; + int m2 = -1; + if( (this.caretPos >= 0) && (this.markPos >= 0) ) { + m1 = Math.min( this.caretPos, this.markPos ); + m2 = Math.max( this.caretPos, this.markPos ); + } else { + m1 = this.caretPos; + m2 = this.caretPos; + } + + // Hex-ASCII-Text ausgeben + int pos = ((yOffs - MARGIN) / this.hRow) * BYTES_PER_ROW; + int y = (pos / BYTES_PER_ROW) * this.hRow; + if( y < 0 ) { + y = 0; + } + y += MARGIN; + y += this.hChar; + int addrOffs = this.dataSrc.getAddrOffset(); + String addrFmt = createAddrFmtString(); + while( (pos < dataLen) + && (y < (yVisible + hVisible + this.hChar)) ) + { + visibleRows++; + g.setColor( getForeground() ); + g.drawString( String.format( addrFmt, addrOffs + pos ), MARGIN, y ); + int x = this.xHex; + for( int i = 0; i < BYTES_PER_ROW; i++ ) { + int idx = pos + i; + if( idx >= dataLen ) { + break; + } + if( (idx >= m1) && (idx <= m2) ) { + int nMark = 2; + if( (i < BYTES_PER_ROW - 1) && (idx < m2) ) { + nMark = 3; + } + g.setColor( SystemColor.textHighlight ); + g.fillRect( + x, + y - this.hChar + 2, + nMark * this.wChar, + this.hRow ); + g.setColor( SystemColor.textHighlightText ); + } else { + g.setColor( getForeground() ); + } + g.drawString( + String.format( "%02X", this.dataSrc.getDataByte( idx ) ), + x, + y ); + x += this.wHex; + } + x = this.xAscii; + for( int i = 0; i < BYTES_PER_ROW; i++ ) { + int idx = pos + i; + if( idx >= dataLen ) { + break; + } + char ch = (char) this.dataSrc.getDataByte( pos + i ); + if( (ch < 0x20) || (ch >= 0x7F) ) { + ch = '.'; + } + if( (idx >= m1) && (idx <= m2) ) { + g.setColor( SystemColor.textHighlight ); + g.fillRect( x, y - this.hChar + 2, this.wChar, this.hRow ); + g.setColor( SystemColor.textHighlightText ); + } else { + g.setColor( getForeground() ); + } + g.drawString( Character.toString( ch ), x, y ); + x += this.wChar; + } + y += this.hRow; + pos += BYTES_PER_ROW; + } + if( visibleRows < 1 ) { + visibleRows = 1; + } + } + } + this.visibleRows = visibleRows; + this.visibleXLeft = xVisible; + this.visibleYTop = yVisible; + this.visibleWidth = wVisible; + this.visibleHeight = hVisible; + } + + + @Override + public void setFont( Font font ) + { + super.setFont( font ); + this.hChar = font.getSize(); + this.hRow = this.hChar + PAD_Y; + } + + + @Override + public void updateUI() + { + super.updateUI(); + fontPrototypeFld.updateUI(); + Font font = fontPrototypeFld.getFont(); + if( font != null ) { + setFont( font ); + } + } + + + /* --- private Methoden --- */ + + private void calcPositions() + { + FontMetrics fm = getFontMetrics( getFont() ); + if( fm != null ) { + this.wChar = fm.stringWidth( "0" ); + this.wHex = 3 * wChar; + this.xHex = MARGIN + (6 * this.wChar) + SEP_W; + this.xAscii = this.xHex + (BYTES_PER_ROW * this.wHex) + SEP_W; + } + } + + + private void fireCaretListeners() + { + if( this.caretListeners != null ) { + synchronized( this.caretListeners ) { + final Object source = this; + final int caretPos = this.caretPos; + final int markPos = this.markPos; + + CaretEvent e = new CaretEvent( source ) + { + @Override + public int getDot() + { + return caretPos; + } + + @Override + public int getMark() + { + return markPos; + } + }; + for( CaretListener listener : this.caretListeners ) { + listener.caretUpdate( e ); + } + } + } + } + + + private boolean keyAction( int keyCode, boolean moveOp ) + { + boolean rv = false; + int dataLen = this.dataSrc.getDataLength(); + if( (this.caretPos >= 0) && (this.caretPos < dataLen) ) { + rv = true; + int steps = 0; + switch( keyCode ) { + case KeyEvent.VK_LEFT: + steps = -1; + break; + case KeyEvent.VK_RIGHT: + steps = 1; + break; + case KeyEvent.VK_UP: + steps = -BYTES_PER_ROW; + break; + case KeyEvent.VK_DOWN: + steps = BYTES_PER_ROW; + break; + case KeyEvent.VK_PAGE_UP: + steps = -(this.visibleRows * BYTES_PER_ROW); + break; + case KeyEvent.VK_PAGE_DOWN: + steps = this.visibleRows * BYTES_PER_ROW; + break; + case KeyEvent.VK_BEGIN: + case KeyEvent.VK_HOME: + steps = -this.caretPos; + break; + case KeyEvent.VK_END: + steps = dataLen - this.caretPos - 1; + break; + default: + rv = false; + } + if( steps != 0 ) { + int idx = this.caretPos + steps; + if( Math.abs( steps ) > 1 ) { + if( idx < 0 ) { + // gleichen Spalte ersten Zeile + idx += ((1 - (idx / BYTES_PER_ROW)) * BYTES_PER_ROW); + } else if( idx >= dataLen ) { + // gleichen Spalte letzten Zeile + idx -= ((1 + ((idx - dataLen) / BYTES_PER_ROW)) * BYTES_PER_ROW); + } + } + if( (idx >= 0) && (idx < dataLen) ) { + setCaretPosition( idx, moveOp ); + } + } + } + return rv; + } + + + private void mouseAction( int x, int y, boolean moveOp ) + { + int idx = getDataIndexAt( x, y ); + if( idx >= 0 ) { + if( (x >= this.xAscii) + && (x < (this.xAscii + (this.wChar * BYTES_PER_ROW))) ) + { + this.asciiSelected = true; + } else { + this.asciiSelected = false; + } + setCaretPosition( idx, moveOp ); + } + requestFocus(); + } + + + private void scrollCaretToVisible() + { + if( (this.caretPos >= 0) + && (this.caretPos < this.dataSrc.getDataLength()) ) + { + Component parent = getParent(); + if( parent != null ) { + if( parent instanceof JViewport ) { + Point p = null; + int c = this.caretPos % BYTES_PER_ROW; + int x = 0; + int wB = this.wChar; + if( this.asciiSelected ) { + x = this.xAscii + (c * this.wChar); + } else { + x = this.xHex + (c * this.wHex); + wB += this.wChar; + } + int y = MARGIN + ((this.caretPos / BYTES_PER_ROW) * this.hRow); + if( x < this.visibleXLeft ) { + Point vp = ((JViewport) parent).getViewPosition(); + if( vp != null ) { + p = new Point( x, vp.y ); + } + } + else if( (x + wB) > (this.visibleXLeft + this.visibleWidth) ) { + Point vp = ((JViewport) parent).getViewPosition(); + if( vp != null ) { + p = new Point( x - this.visibleWidth + wB, vp.y ); + } + } + else if( (y - this.hChar) < this.visibleYTop ) { + Point vp = ((JViewport) parent).getViewPosition(); + if( vp != null ) { + p = new Point( vp.x, y - this.hChar ); + } + } + else if( y > (this.visibleYTop + this.visibleHeight) ) + { + Point vp = ((JViewport) parent).getViewPosition(); + if( vp != null ) { + p = new Point( vp.x, y - this.visibleHeight + this.hChar ); + } + } + if( p != null ) { + if( p.x < 0 ) { + p.x = 0; + } + if( p.y < 0 ) { + p.y = 0; + } + ((JViewport) parent).setViewPosition( p ); + } + } + } + } + } +} diff --git a/src/jkcemu/base/IntegerDocument.java b/src/jkcemu/base/IntegerDocument.java index 009455f..c761e62 100644 --- a/src/jkcemu/base/IntegerDocument.java +++ b/src/jkcemu/base/IntegerDocument.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -107,7 +107,7 @@ private int parseInt( String s ) throws NumberFormatException catch( NumberFormatException ex ) { this.textComp.requestFocus(); throw new NumberFormatException( - "Ung\u00FCltiges Format!\nBitte geben Sie eine ganze Zahl ein." ); + "Ung\u00FCltiges Format!\nBitte geben Sie eine Zahl ein." ); } if( this.minValue != null ) { if( value < this.minValue.intValue() ) { diff --git a/src/jkcemu/base/KeyboardFrm.java b/src/jkcemu/base/KeyboardFrm.java index 29bcf8b..1eecbbb 100644 --- a/src/jkcemu/base/KeyboardFrm.java +++ b/src/jkcemu/base/KeyboardFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -9,7 +9,7 @@ package jkcemu.base; import java.awt.*; -import java.awt.event.KeyEvent; +import java.awt.event.*; import java.lang.*; import java.util.EventObject; import javax.swing.*; @@ -191,6 +191,24 @@ public void resetFired() } + @Override + public void windowActivated( WindowEvent e ) + { + if( e.getWindow() == this ) { + Main.setWindowActivated( Main.WINDOW_MASK_KEYBOARD ); + } + } + + + @Override + public void windowDeactivated( WindowEvent e ) + { + if( e.getWindow() == this ) { + Main.setWindowDeactivated( Main.WINDOW_MASK_KEYBOARD ); + } + } + + /* --- private Methoden --- */ private void updWindowElements( EmuSys emuSys ) diff --git a/src/jkcemu/base/LimitedDocument.java b/src/jkcemu/base/LimitedDocument.java index d6b0a61..0217fcd 100644 --- a/src/jkcemu/base/LimitedDocument.java +++ b/src/jkcemu/base/LimitedDocument.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -15,20 +15,33 @@ public class LimitedDocument extends PlainDocument { private int maxLen; + private boolean asciiOnly; private boolean swapCase; public LimitedDocument( int maxLen ) { - this.maxLen = maxLen; - this.swapCase = false; + this.maxLen = maxLen; + this.asciiOnly = false; + this.swapCase = false; } - public LimitedDocument( int maxLen, boolean swapCase ) + public LimitedDocument() { - this.maxLen = maxLen; - this.swapCase = swapCase; + this( 0 ); + } + + + public int getMaxLength() + { + return this.maxLen; + } + + + public void setAsciiOnly( boolean state ) + { + this.asciiOnly = state; } @@ -50,19 +63,29 @@ public void setSwapCase( boolean state ) public void insertString( int offs, String str, AttributeSet a ) throws BadLocationException { - if( (str != null) && this.swapCase ) { + if( (str != null) && (this.asciiOnly || this.swapCase) ) { char[] ary = str.toCharArray(); if( ary != null ) { + int n = 0; for( int i = 0; i < ary.length; i++ ) { char ch = ary[ i ]; - if( Character.isUpperCase( ch ) ) { - ary[ i ] = Character.toLowerCase( ch ); - } - else if( Character.isLowerCase( ch ) ) { - ary[ i ] = Character.toUpperCase( ch ); + if( !this.asciiOnly || ((ch >= '\u0020') && (ch <= '\u007E')) ) { + if( this.swapCase ) { + if( Character.isUpperCase( ch ) ) { + ch = Character.toLowerCase( ch ); + } + else if( Character.isLowerCase( ch ) ) { + ch = Character.toUpperCase( ch ); + } + } + ary[ n++ ] = ch; } } - str = new String( ary ); + if( n > 0 ) { + str = new String( ary, 0, n ); + } else { + str = null; + } } } if( str != null ) { diff --git a/src/jkcemu/base/LoadData.java b/src/jkcemu/base/LoadData.java index 7d07944..5115a1d 100644 --- a/src/jkcemu/base/LoadData.java +++ b/src/jkcemu/base/LoadData.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -11,29 +11,29 @@ import java.io.*; import java.lang.*; -import z80emu.Z80MemView; +import jkcemu.base.EmuMemView; -public class LoadData implements Z80MemView +public class LoadData implements EmuMemView { - private byte[] data; - private int offset; - private int len; - private int begAddr; - private int startAddr; - private int fileType; - private Object fileFmt; - private String infoMsg; - private FileInfo fileInfo; + private byte[] data; + private int offset; + private int len; + private int begAddr; + private int startAddr; + private int fileType; + private FileFormat fileFmt; + private String infoMsg; + private FileInfo fileInfo; public LoadData( - byte[] data, - int offset, - int len, - int begAddr, - int startAddr, - Object fileFmt ) + byte[] data, + int offset, + int len, + int begAddr, + int startAddr, + FileFormat fileFmt ) { this.data = data; this.offset = offset; @@ -114,15 +114,10 @@ public void loadIntoMemory( EmuThread emuThread ) if( (emuThread != null) && (this.data != null) ) { EmuSys emuSys = emuThread.getEmuSys(); if( emuSys != null ) { - int src = this.offset; - int dst = this.begAddr; - int len = this.len; - while( (src < this.data.length) && (dst < 0x10000) && (len > 0) ) { - emuSys.loadMemByte( dst++, this.data[ src++ ] ); - --len; - } - emuSys.updSysCells( + emuSys.loadIntoMem( this.begAddr, + this.data, + this.offset, this.len, this.fileFmt, this.fileType ); @@ -203,6 +198,15 @@ public void setStartAddr( int addr ) } + /* --- EmuMemView --- */ + + @Override + public int getBasicMemByte( int addr ) + { + return getAbsoluteByte( this.offset + addr - this.begAddr ); + } + + /* --- Z80MemView --- */ @Override @@ -218,4 +222,3 @@ public int getMemWord( int addr ) return (getMemByte( addr + 1, false ) << 8) | getMemByte( addr, false ); } } - diff --git a/src/jkcemu/base/LoadDlg.java b/src/jkcemu/base/LoadDlg.java index baf3b6d..334fced 100644 --- a/src/jkcemu/base/LoadDlg.java +++ b/src/jkcemu/base/LoadDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -22,18 +22,19 @@ public class LoadDlg extends BasicDlg implements DocumentListener { - private static final String[] fileFormats = { - FileInfo.KCB, - FileInfo.KCC, - FileInfo.KCTAP_SYS, - FileInfo.KCTAP_BASIC_PRG, - FileInfo.KCBASIC_HEAD_PRG, - FileInfo.KCBASIC_PRG, - FileInfo.RBASIC, - FileInfo.RMC, - FileInfo.HEADERSAVE, - FileInfo.INTELHEX, - FileInfo.BIN }; + private static final FileFormat[] fileFormats = { + FileFormat.KCB, + FileFormat.KCC, + FileFormat.KCTAP_SYS, + FileFormat.KCTAP_BASIC_PRG, + FileFormat.KCBASIC_HEAD_PRG, + FileFormat.KCBASIC_PRG, + FileFormat.RBASIC_PRG, + FileFormat.RMC, + FileFormat.BASIC_PRG, + FileFormat.HEADERSAVE, + FileFormat.INTELHEX, + FileFormat.BIN }; private static final String textSelectFmt = "--- Bitte ausw\u00E4hlen ---"; private static final String errMsgEmptyFile = @@ -42,36 +43,36 @@ public class LoadDlg extends BasicDlg implements DocumentListener private static boolean suppressAudioMsg = false; - private Frame owner; - private ScreenFrm screenFrm; - private boolean startEnabled; - private File file; - private byte[] fileBuf; - private JLabel labelInfoBegAddr; - private JLabel labelInfoEndAddr; - private JLabel labelInfoStartAddr; - private JLabel labelInfoType; - private JLabel labelInfoDesc; - private JLabel labelLoadBasicAddr; - private JLabel labelLoadBegAddr; - private JLabel labelLoadEndAddr; - private JTextField fldInfoBegAddr; - private JTextField fldInfoEndAddr; - private JTextField fldInfoStartAddr; - private JTextField fldInfoType; - private JTextField fldInfoDesc; - private JTextField fldLoadBegAddr; - private JTextField fldLoadEndAddr; - private JRadioButton btnLoadForRAMBasic; - private JRadioButton btnLoadForROMBasic; - private HexDocument docLoadBegAddr; - private HexDocument docLoadEndAddr; - private JCheckBox btnKeepHeader; - private JComboBox comboFileFmt; - private JButton btnLoad; - private JButton btnStart; - private JButton btnHelp; - private JButton btnCancel; + private Frame owner; + private ScreenFrm screenFrm; + private boolean startEnabled; + private File file; + private byte[] fileBuf; + private JLabel labelInfoBegAddr; + private JLabel labelInfoEndAddr; + private JLabel labelInfoStartAddr; + private JLabel labelInfoType; + private JLabel labelInfoDesc; + private JLabel labelLoadBasicAddr; + private JLabel labelLoadBegAddr; + private JLabel labelLoadEndAddr; + private JTextField fldInfoBegAddr; + private JTextField fldInfoEndAddr; + private JTextField fldInfoStartAddr; + private JTextField fldInfoType; + private JTextField fldInfoDesc; + private JTextField fldLoadBegAddr; + private JTextField fldLoadEndAddr; + private JRadioButton btnLoadForRAMBasic; + private JRadioButton btnLoadForROMBasic; + private HexDocument docLoadBegAddr; + private HexDocument docLoadEndAddr; + private JCheckBox btnKeepHeader; + private JComboBox comboFileFmt; + private JButton btnLoad; + private JButton btnStart; + private JButton btnHelp; + private JButton btnCancel; public static void loadFile( @@ -82,7 +83,24 @@ public static void loadFile( boolean startEnabled, boolean startSelected ) { - if( AudioUtil.isAudioFile( file ) ) { + /* + * pruefen, ob die Datei nur ueber die emulierte + * Kassettenschnittstelle geladen werden kann + */ + byte[] fileBuf = null; + FileInfo fileInfo = null; + FileFormat fileFmt = null; + boolean tapeFile = false; + boolean audioFile = AudioUtil.isAudioFile( file ); + if( !audioFile ) { + fileBuf = readFile( owner, file ); + fileInfo = FileInfo.analyzeFile( fileBuf, file ); + if( fileInfo != null ) { + fileFmt = fileInfo.getFileFormat(); + tapeFile = fileInfo.isTapeFile(); + } + } + if( audioFile || tapeFile ) { if( suppressAudioMsg ) { AudioFrm.open( screenFrm ).openFile( file, null, 0 ); } else { @@ -99,7 +117,7 @@ public static void loadFile( if( JOptionPane.showConfirmDialog( screenFrm, new Object[] { msg, cb }, - "Sound-Datei", + "Sound-/Tape-Datei", JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE ) == JOptionPane.OK_OPTION ) @@ -111,7 +129,6 @@ public static void loadFile( } else { EmuThread emuThread = screenFrm.getEmuThread(); EmuSys emuSys = screenFrm.getEmuSys(); - byte[] fileBuf = readFile( owner, file ); if( (emuThread != null) && (emuSys != null) && (fileBuf != null) ) { /* @@ -124,117 +141,112 @@ public static void loadFile( } // ggf. muss Dialog mit den Ladeoptionen angezeigt werden - boolean done = false; - String fileFmt = null; - FileInfo fileInfo = FileInfo.analyzeFile( fileBuf, file ); - if( fileInfo != null ) { - fileFmt = fileInfo.getFileFormat(); - if( (begAddr == null) && !interactive && (fileFmt != null) ) { - LoadData loadData = null; - try { - loadData = fileInfo.createLoadData( fileBuf, fileFmt ); - } - catch( IOException ex ) {} - if( loadData != null ) { - int originBegAddr = loadData.getBegAddr(); - if( originBegAddr >= 0 ) { - boolean isA5105 = (emuSys instanceof A5105); - boolean isAC1 = (emuSys instanceof AC1); - boolean isLLC2 = (emuSys instanceof LLC2); - boolean isHGMC = (emuSys instanceof HueblerGraphicsMC); - boolean isKC85 = (emuSys instanceof KC85); - boolean isKramerMC = (emuSys instanceof KramerMC); - boolean isZ1013 = (emuSys instanceof Z1013); - boolean isZ9001 = (emuSys instanceof Z9001); - - if( !startSelected ) { + boolean done = false; + if( (begAddr == null) && !interactive && (fileFmt != null) ) { + LoadData loadData = null; + try { + loadData = fileInfo.createLoadData( fileBuf, fileFmt ); + } + catch( IOException ex ) {} + if( loadData != null ) { + int originBegAddr = loadData.getBegAddr(); + if( originBegAddr >= 0 ) { + boolean isA5105 = (emuSys instanceof A5105); + boolean isAC1 = (emuSys instanceof AC1); + boolean isLLC2 = (emuSys instanceof LLC2); + boolean isHGMC = (emuSys instanceof HueblerGraphicsMC); + boolean isKC85 = (emuSys instanceof KC85); + boolean isKramerMC = (emuSys instanceof KramerMC); + boolean isZ1013 = (emuSys instanceof Z1013); + boolean isZ9001 = (emuSys instanceof Z9001); + + if( !startSelected ) { + loadData.setStartAddr( -1 ); + } + if( fileFmt.equals( FileFormat.HEADERSAVE ) ) { + if( fileInfo.getFileType() != 'C' ) { loadData.setStartAddr( -1 ); } - if( fileFmt.equals( FileInfo.HEADERSAVE ) ) { - if( fileInfo.getFileType() != 'C' ) { - loadData.setStartAddr( -1 ); - } - } + } - /* - * Warnung, wenn Dateityp unueblich beim - * gerade emulierten System ist - */ - if( loadData.getStartAddr() >= 0 ) { - if( (isA5105 && !fileFmt.equals( FileInfo.RMC)) - || ((isAC1 || isHGMC || isKramerMC || isLLC2 || isZ1013) - && !fileFmt.equals( FileInfo.HEADERSAVE )) - || (isKC85 && - !(fileFmt.equals( FileInfo.KCC ) - || fileFmt.equals( FileInfo.KCTAP_KC85 ) - || fileFmt.equals( FileInfo.KCTAP_SYS ))) - || (isZ9001 && - !(fileFmt.equals( FileInfo.KCC ) - || fileFmt.equals( FileInfo.KCTAP_Z9001 ) - || fileFmt.equals( FileInfo.KCTAP_SYS ))) ) - { - String[] options = { + /* + * Warnung, wenn Dateityp unueblich beim + * gerade emulierten System ist + */ + if( loadData.getStartAddr() >= 0 ) { + if( (isA5105 && !fileFmt.equals( FileFormat.RMC)) + || ((isAC1 || isHGMC || isKramerMC || isLLC2 || isZ1013) + && !fileFmt.equals( FileFormat.HEADERSAVE )) + || (isKC85 && + !(fileFmt.equals( FileFormat.KCC ) + || fileFmt.equals( FileFormat.KCTAP_KC85 ) + || fileFmt.equals( FileFormat.KCTAP_SYS ))) + || (isZ9001 && + !(fileFmt.equals( FileFormat.KCC ) + || fileFmt.equals( FileFormat.KCTAP_Z9001 ) + || fileFmt.equals( FileFormat.KCTAP_SYS ))) ) + { + String[] options = { "Laden und Starten", "Nur Laden", "Abbrechen" }; - JOptionPane pane = new JOptionPane( + JOptionPane pane = new JOptionPane( "Der Dateityp enth\u00E4lt \u00FCblicherweise" + " keine Programme f\u00FCr das gerade" + " emulierte System.\n" + "M\u00F6chten Sie trotzdem das in der Datei" + " enthaltene Programm laden und starten?", JOptionPane.WARNING_MESSAGE ); - pane.setOptions( options ); - pane.createDialog( owner, "Warnung" ).setVisible( true ); - - done = true; - Object value = pane.getValue(); - if( value != null ) { - if( value.equals( options[ 0 ] ) ) { - done = false; - } - else if( value.equals( options[ 1 ] ) ) { - loadData.setStartAddr( -1 ); - done = false; - } + pane.setOptions( options ); + pane.createDialog( owner, "Warnung" ).setVisible( true ); + + done = true; + Object value = pane.getValue(); + if( value != null ) { + if( value.equals( options[ 0 ] ) ) { + done = false; + } + else if( value.equals( options[ 1 ] ) ) { + loadData.setStartAddr( -1 ); + done = false; } } } - if( !done ) { + } + if( !done ) { - // Datei in Arbeitsspeicher laden und ggf. starten - if( confirmLoadDataInfo( owner, loadData ) ) { - emuThread.loadIntoMemory( loadData ); - Main.setLastFile( file, "software" ); + // Datei in Arbeitsspeicher laden und ggf. starten + if( confirmLoadDataInfo( owner, loadData ) ) { + emuThread.loadIntoMemory( loadData ); + Main.setLastFile( file, "software" ); - // ggf. Dateikopf in Arbeitsspeicher kopieren - if( fileFmt.equals( FileInfo.HEADERSAVE ) ) { - if( isZ1013 - && EmuUtil.parseBoolean( + // ggf. Dateikopf in Arbeitsspeicher kopieren + if( fileFmt.equals( FileFormat.HEADERSAVE ) ) { + if( isZ1013 + && EmuUtil.parseBoolean( Main.getProperty( "jkcemu.loadsave.header.keep" ), false ) - && (loadData.getOffset() == 32) ) - { - for( int i = 0; i < 32; i++ ) { - emuThread.setMemByte( + && (loadData.getOffset() == 32) ) + { + for( int i = 0; i < 32; i++ ) { + emuThread.setMemByte( Z1013.MEM_HEAD + i, loadData.getAbsoluteByte( i ) ); - } } } + } - // ggf. Meldung anzeigen und Multi-TAP-Handling - showLoadMsg( screenFrm, loadData ); - checkMultiTAPHandling( + // ggf. Meldung anzeigen und Multi-TAP-Handling + showLoadMsg( screenFrm, loadData ); + checkMultiTAPHandling( owner, screenFrm, file, fileBuf, fileInfo.getNextTAPOffset() ); - } - done = true; } + done = true; } } } @@ -245,7 +257,7 @@ else if( value.equals( options[ 1 ] ) ) { screenFrm, file, fileBuf, - fileFmt != null ? fileFmt : FileInfo.BIN, + fileFmt != null ? fileFmt : FileFormat.BIN, begAddr, startEnabled ); dlg.setVisible( true ); @@ -322,13 +334,13 @@ else if( src instanceof JTextField ) { /* --- private Konstruktoren und Methoden --- */ private LoadDlg( - Frame owner, - ScreenFrm screenFrm, - File file, - byte[] fileBuf, - String fileFmt, - Integer begAddr, - boolean startEnabled ) + Frame owner, + ScreenFrm screenFrm, + File file, + byte[] fileBuf, + FileFormat fileFmt, + Integer begAddr, + boolean startEnabled ) { super( owner, "Datei laden" ); if( file != null ) { @@ -373,27 +385,27 @@ private LoadDlg( boolean fmtSupported = false; if( fileFmt != null ) { - if( fileFmt.equals( FileInfo.KCTAP_KC85 ) - || fileFmt.equals( FileInfo.KCTAP_Z9001 ) ) + if( fileFmt.equals( FileFormat.KCTAP_KC85 ) + || fileFmt.equals( FileFormat.KCTAP_Z9001 ) ) { - fileFmt = FileInfo.KCTAP_SYS; + fileFmt = FileFormat.KCTAP_SYS; fmtSupported = true; } else { - for( String s : fileFormats ) { - if( s.equals( fileFmt ) ) { + for( FileFormat fmt : fileFormats ) { + if( fmt.equals( fileFmt ) ) { fmtSupported = true; break; } } } } - this.comboFileFmt = new JComboBox(); + this.comboFileFmt = new JComboBox<>(); this.comboFileFmt.setEditable( false ); if( !fmtSupported ) { this.comboFileFmt.addItem( textSelectFmt ); } - for( String s : fileFormats ) { - this.comboFileFmt.addItem( s ); + for( FileFormat fmt : fileFormats ) { + this.comboFileFmt.addItem( fmt ); } if( fmtSupported ) { this.comboFileFmt.setSelectedItem( fileFmt ); @@ -607,15 +619,16 @@ private LoadDlg( * die Ladeadressen aus dem Dateinamen zu ermitteln */ if( (file != null) && (fileFmt != null) ) { - if( fileFmt.equals( FileInfo.BIN ) ) { - int[] addrs = EmuUtil.extractAddressesFromFileName( - file.getName() ); + if( fileFmt.equals( FileFormat.BIN ) ) { + int[] addrs = EmuUtil.extractAddressesFromFileName( file.getName() ); if( addrs != null ) { if( addrs.length > 0 ) { - this.fldLoadBegAddr.setText( String.format( "%04X", addrs[ 0 ] ) ); + this.fldLoadBegAddr.setText( + String.format( "%04X", addrs[ 0 ] ) ); } if( addrs.length > 1 ) { - this.fldLoadEndAddr.setText( String.format( "%04X", addrs[ 1 ] ) ); + this.fldLoadEndAddr.setText( + String.format( "%04X", addrs[ 1 ] ) ); } } } @@ -688,8 +701,8 @@ private static boolean confirmLoadDataInfo( private void doLoad( boolean startSelected ) { - EmuThread emuThread = this.screenFrm.getEmuThread(); - Object fileFmt = this.comboFileFmt.getSelectedItem(); + EmuThread emuThread = this.screenFrm.getEmuThread(); + FileFormat fileFmt = getSelectedFileFormat(); if( (emuThread != null) && (fileFmt != null) ) { try { @@ -751,7 +764,7 @@ private void doLoad( boolean startSelected ) // ggf. Dateikopf in Arbeitsspeicher kopieren if( (emuThread.getEmuSys() instanceof Z1013) - && fileFmt.equals( FileInfo.HEADERSAVE ) + && fileFmt.equals( FileFormat.HEADERSAVE ) && this.btnKeepHeader.isSelected() && (loadData.getOffset() == 32) ) { @@ -766,10 +779,10 @@ private void doLoad( boolean startSelected ) showLoadMsg( this.screenFrm, loadData ); // Multi-TAP-Handling - if( fileFmt.equals( FileInfo.KCTAP_SYS ) - || fileFmt.equals( FileInfo.KCTAP_Z9001 ) - || fileFmt.equals( FileInfo.KCTAP_KC85 ) - || fileFmt.equals( FileInfo.KCTAP_BASIC_PRG ) ) + if( fileFmt.equals( FileFormat.KCTAP_SYS ) + || fileFmt.equals( FileFormat.KCTAP_Z9001 ) + || fileFmt.equals( FileFormat.KCTAP_KC85 ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ) ) { FileInfo fileInfo = FileInfo.analyzeFile( this.fileBuf, @@ -799,6 +812,19 @@ private void doLoad( boolean startSelected ) } + private FileFormat getSelectedFileFormat() + { + FileFormat fmt = null; + Object obj = this.comboFileFmt.getSelectedItem(); + if( obj != null ) { + if( obj instanceof FileFormat ) { + fmt = (FileFormat) obj; + } + } + return fmt; + } + + private static int parseHex( InputStream in, int cnt ) throws IOException { int value = 0; @@ -849,7 +875,6 @@ private static void showLoadMsg( ScreenFrm screenFrm, LoadData loadData ) if( endAddr > 0xFFFF ) { endAddr = 0xFFFF; } - screenFrm.toFront(); screenFrm.showStatusText( String.format( "Datei nach %04X-%04X geladen", @@ -872,8 +897,8 @@ private void updFields() boolean isKCBASIC_HEAD_DATA = false; boolean isKCBASIC_HEAD_ASC = false; boolean isKCBASIC_PRG = false; - boolean isRBASIC = false; boolean isRMC = false; + boolean isBASIC = false; boolean isINTELHEX = false; boolean loadable = false; int begAddr = -1; @@ -882,20 +907,21 @@ private void updFields() int fileType = -1; String fileDesc = null; - Object fileFmt = this.comboFileFmt.getSelectedItem(); + FileFormat fileFmt = getSelectedFileFormat(); if( fileFmt != null ) { - isHS = fileFmt.equals( FileInfo.HEADERSAVE ); - isKCB = fileFmt.equals( FileInfo.KCB ); - isKCC = fileFmt.equals( FileInfo.KCC ); - isKCTAP_SYS = fileFmt.equals( FileInfo.KCTAP_SYS ) - || fileFmt.equals( FileInfo.KCTAP_Z9001 ) - || fileFmt.equals( FileInfo.KCTAP_KC85 ); - isKCTAP_BASIC_PRG = fileFmt.equals( FileInfo.KCTAP_BASIC_PRG ); - isKCBASIC_HEAD_PRG = fileFmt.equals( FileInfo.KCBASIC_HEAD_PRG ); - isKCBASIC_PRG = fileFmt.equals( FileInfo.KCBASIC_PRG ); - isRBASIC = fileFmt.equals( FileInfo.RBASIC ); - isRMC = fileFmt.equals( FileInfo.RMC ); - isINTELHEX = fileFmt.equals( FileInfo.INTELHEX ); + isHS = fileFmt.equals( FileFormat.HEADERSAVE ); + isKCB = fileFmt.equals( FileFormat.KCB ); + isKCC = fileFmt.equals( FileFormat.KCC ); + isKCTAP_SYS = fileFmt.equals( FileFormat.KCTAP_SYS ) + || fileFmt.equals( FileFormat.KCTAP_Z9001 ) + || fileFmt.equals( FileFormat.KCTAP_KC85 ); + isKCTAP_BASIC_PRG = fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ); + isKCBASIC_HEAD_PRG = fileFmt.equals( FileFormat.KCBASIC_HEAD_PRG ); + isKCBASIC_PRG = fileFmt.equals( FileFormat.KCBASIC_PRG ); + isRMC = fileFmt.equals( FileFormat.RMC ); + isBASIC = fileFmt.equals( FileFormat.BASIC_PRG ) + || fileFmt.equals( FileFormat.RBASIC_PRG ); + isINTELHEX = fileFmt.equals( FileFormat.INTELHEX ); loadable = !fileFmt.equals( textSelectFmt ); begAddr = FileInfo.getBegAddr( this.fileBuf, fileFmt ); @@ -910,7 +936,7 @@ private void updFields() boolean stateBegAddr = (isHS || isKCB || isKCC || isKCTAP_SYS || isKCTAP_BASIC_PRG || isKCBASIC_HEAD_PRG || isKCBASIC_PRG - || isRBASIC || isRMC || isINTELHEX); + || isBASIC || isRMC || isINTELHEX); this.labelInfoBegAddr.setEnabled( stateBegAddr ); if( stateBegAddr && (begAddr >= 0) ) { this.fldInfoBegAddr.setText( String.format( "%04X", begAddr ) ); @@ -955,7 +981,7 @@ private void updFields() boolean stateKCBasicPrg = (isKCB || isKCTAP_BASIC_PRG || isKCBASIC_HEAD_PRG || isKCBASIC_PRG); - boolean stateLoadAddr = loadable && !stateKCBasicPrg && !isRBASIC; + boolean stateLoadAddr = loadable && !stateKCBasicPrg && !isBASIC; this.labelLoadBegAddr.setEnabled( stateLoadAddr ); this.labelLoadEndAddr.setEnabled( stateLoadAddr ); this.fldLoadBegAddr.setEditable( stateLoadAddr ); @@ -975,7 +1001,7 @@ private void updFields() if( emuSys != null ) { this.btnKeepHeader.setEnabled( (emuSys instanceof Z1013) - && fileFmt.equals( FileInfo.HEADERSAVE ) ); + && fileFmt.equals( FileFormat.HEADERSAVE ) ); } this.btnLoad.setEnabled( loadable && (this.fileBuf.length > 0) ); updStartButton( loadable, begAddr, endAddr, startAddr ); @@ -1029,11 +1055,11 @@ private void updStartButton( private void updStartButton() { if( this.btnStart != null ) { - boolean loadable = false; - int begAddr = -1; - int endAddr = -1; - int startAddr = -1; - Object fileFmt = this.comboFileFmt.getSelectedIndex(); + boolean loadable = false; + int begAddr = -1; + int endAddr = -1; + int startAddr = -1; + FileFormat fileFmt = getSelectedFileFormat(); if( fileFmt != null ) { loadable = !fileFmt.equals( textSelectFmt ); if( loadable ) { diff --git a/src/jkcemu/base/MsgFrm.java b/src/jkcemu/base/MsgFrm.java new file mode 100644 index 0000000..9f19818 --- /dev/null +++ b/src/jkcemu/base/MsgFrm.java @@ -0,0 +1,85 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Fenster zur Ausgabe von Meldungen + */ + +package jkcemu.base; + +import java.awt.*; +import java.lang.*; +import java.util.EventObject; +import javax.swing.*; +import jkcemu.Main; +import jkcemu.base.*; + + +public class MsgFrm extends BasicFrm +{ + private static final int DEFAULT_WIDHTH = 500; + private static final int DEFAULT_HEIGHT = 120; + + private boolean frameEmpty; + private JTextArea fldText; + + + public MsgFrm( Window owner ) + { + setTitle( "JKCEMU Meldungen" ); + Main.updIcon( this ); + this.frameEmpty = true; + + // Fensterinhalt + setLayout( new BorderLayout() ); + + this.fldText = new JTextArea(); + this.fldText.setEditable( false ); + this.fldText.setMargin( new Insets( 5, 5, 5, 5 ) ); + add( new JScrollPane( this.fldText ), BorderLayout.CENTER ); + + Font font = this.fldText.getFont(); + if( font != null ) { + this.fldText.setFont( font.deriveFont( Font.PLAIN ) ); + } + + + // Fenstergroesse + if( !applySettings( Main.getProperties(), true ) ) { + setSize( DEFAULT_WIDHTH, DEFAULT_HEIGHT ); + boolean done = false; + if( owner != null ) { + try { + Dimension pSize = owner.getSize(); + Point pLocation = owner.getLocationOnScreen(); + setLocation( + pLocation.x + ((pSize.width - DEFAULT_WIDHTH) / 2), + pLocation.y + ((pSize.height - DEFAULT_HEIGHT) / 2) ); + done = true; + } + catch( Exception ex ) {} + } + if( !done ) { + setLocationByPlatform( true ); + } + } + setResizable( true ); + } + + + public void appendMsg( String text ) + { + if( text != null ) { + if( !text.isEmpty() ) { + if( this.frameEmpty ) { + this.frameEmpty = false; + } else { + this.fldText.append( "\n\n" ); + } + this.fldText.append( text ); + this.fldText.append( "\n" ); + } + } + } +} diff --git a/src/jkcemu/base/NIOFileTimesViewFactory.java b/src/jkcemu/base/NIOFileTimesViewFactory.java new file mode 100644 index 0000000..7db3d11 --- /dev/null +++ b/src/jkcemu/base/NIOFileTimesViewFactory.java @@ -0,0 +1,111 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Factory-Klasse zum Erzeugen von FileTimesView-Objekten + * + * Die Zeitstempel werden mit Hilfe der java.nio.file-Klassen gelesen. + * Dadurch koennen auch der die Zeitstempel der Erzeugung + * und des letzten Zugriffs ermittelt werden. + */ + +package jkcemu.base; + +import java.io.*; +import java.lang.*; +import java.nio.file.*; +import java.nio.file.attribute.*; + + +public class NIOFileTimesViewFactory extends FileTimesViewFactory +{ + public NIOFileTimesViewFactory() + { + // leer + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public FileTimesView getFileTimesView( final File file ) + { + FileTimesView rv = null; + try { + final BasicFileAttributes attrs = Files.readAttributes( + file.toPath(), + BasicFileAttributes.class ); + if( attrs != null ) { + rv = new FileTimesView() + { + @Override + public Long getCreationMillis() + { + return toMillis( attrs.creationTime() ); + } + + @Override + public Long getLastAccessMillis() + { + return toMillis( attrs.lastAccessTime() ); + } + + @Override + public Long getLastModifiedMillis() + { + return toMillis( attrs.lastModifiedTime() ); + } + + @Override + public void setTimesInMillis( + Long creationMillis, + Long lastAccessMillis, + Long lastModifiedMillis ) + { + boolean done = false; + try { + FileAttributeView v = Files.getFileAttributeView( + file.toPath(), + BasicFileAttributeView.class ); + if( v != null ) { + if( v instanceof BasicFileAttributeView ) { + ((BasicFileAttributeView) v).setTimes( + toFileTime( lastModifiedMillis ), + toFileTime( lastAccessMillis ), + toFileTime( creationMillis ) ); + done = true; + } + } + } + catch( Exception ex ) {} + if( !done && (lastModifiedMillis != null) ) { + try { + file.setLastModified( + lastModifiedMillis.longValue() ); + } + catch( Exception ex ) {} + } + } + }; + } + } + catch( InvalidPathException ex ) {} + catch( IOException ex ) {} + return rv != null ? rv : super.getFileTimesView( file ); + } + + + /* --- private Methoden --- */ + + private static FileTime toFileTime( Long millis ) + { + return millis != null ? FileTime.fromMillis( millis.longValue() ) : null; + } + + + private static Long toMillis( FileTime fileTime ) + { + return fileTime != null ? new Long( fileTime.toMillis() ) : null; + } +} diff --git a/src/jkcemu/base/ProfileDlg.java b/src/jkcemu/base/ProfileDlg.java index 2390e57..9b344db 100644 --- a/src/jkcemu/base/ProfileDlg.java +++ b/src/jkcemu/base/ProfileDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2011 Jens Mueller + * (c) 2008-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -23,14 +23,14 @@ public class ProfileDlg extends BasicDlg implements DocumentListener, ListSelectionListener { - private File selectedProfile; - private DefaultListModel listModel; - private JList list; - private Document docProfileName; - private JTextField fldProfileName; - private JButton btnOK; - private JButton btnDelete; - private JButton btnCancel; + private File selectedProfile; + private DefaultListModel listModel; + private JList list; + private Document docProfileName; + private JTextField fldProfileName; + private JButton btnOK; + private JButton btnDelete; + private JButton btnCancel; public ProfileDlg( @@ -47,7 +47,7 @@ public ProfileDlg( // Profile laden int preSelectedIdx = -1; String preSelectedName = null; - this.listModel = new DefaultListModel(); + this.listModel = new DefaultListModel<>(); try { File configDir = Main.getConfigDir(); if( configDir != null ) { @@ -95,7 +95,7 @@ public ProfileDlg( add( new JLabel( "Profile:" ), gbc ); - this.list = new JList( this.listModel ); + this.list = new JList<>( this.listModel ); this.list.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); this.list.setVisibleRowCount( 10 ); this.list.setPrototypeCellValue( "123456789012345678901234567890" ); diff --git a/src/jkcemu/base/RAMFloppyFld.java b/src/jkcemu/base/RAMFloppyFld.java index 3971d0f..9683f72 100644 --- a/src/jkcemu/base/RAMFloppyFld.java +++ b/src/jkcemu/base/RAMFloppyFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2012 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -199,7 +199,7 @@ private void doLoad() "RAM-Floppy laden", file != null ? file - : Main.getLastPathFile( "ramfloppy" ) ); + : Main.getLastDirFile( "ramfloppy" ) ); if( file != null ) { try { this.ramFloppy.load( file ); @@ -225,7 +225,7 @@ private void doSave() "RAM-Floppy speichern", file != null ? file - : Main.getLastPathFile( "ramfloppy" ) ); + : Main.getLastDirFile( "ramfloppy" ) ); if( file != null ) { try { this.ramFloppy.save( file ); diff --git a/src/jkcemu/base/RAMFloppyFrm.java b/src/jkcemu/base/RAMFloppyFrm.java index c53b893..238d79c 100644 --- a/src/jkcemu/base/RAMFloppyFrm.java +++ b/src/jkcemu/base/RAMFloppyFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2012 Jens Mueller + * (c) 2010-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -32,8 +32,10 @@ public class RAMFloppyFrm extends BasicFrm public static void close() { - if( instance != null ) + if( instance != null ) { instance.doClose(); + instance = null; + } } @@ -77,7 +79,7 @@ && equalsRF( rf2, this.rfSize2, this.rfInfo2 ) ) } } if( different ) { - doClose(); + close(); } return rv; } diff --git a/src/jkcemu/base/RAMFloppySettingsFld.java b/src/jkcemu/base/RAMFloppySettingsFld.java index ea15811..92d8e0b 100644 --- a/src/jkcemu/base/RAMFloppySettingsFld.java +++ b/src/jkcemu/base/RAMFloppySettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2011 Jens Mueller + * (c) 2011-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -20,13 +20,13 @@ public class RAMFloppySettingsFld extends AbstractSettingsFld { - private JCheckBox btnRF; - private JLabel labelRF; - private JLabel labelFile; - private JComboBox comboSize; - private FileNameFld fileNameFld; - private JButton btnSelect; - private JButton btnRemove; + private JCheckBox btnRF; + private JLabel labelRF; + private JLabel labelFile; + private JComboBox comboSize; + private FileNameFld fileNameFld; + private JButton btnSelect; + private JButton btnRemove; public RAMFloppySettingsFld( @@ -60,7 +60,7 @@ public RAMFloppySettingsFld( this.labelRF = new JLabel( labelText + ":" ); add( this.labelRF, gbc ); - this.comboSize = new JComboBox(); + this.comboSize = new JComboBox<>(); this.comboSize.setEditable( false ); this.comboSize.addItem( "Nicht emulieren" ); this.comboSize.addItem( "128 KByte" ); diff --git a/src/jkcemu/base/ROMFileSettingsFld.java b/src/jkcemu/base/ROMFileSettingsFld.java index 777d5a6..de09196 100644 --- a/src/jkcemu/base/ROMFileSettingsFld.java +++ b/src/jkcemu/base/ROMFileSettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2011 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -79,7 +79,7 @@ public ROMFileSettingsFld( public synchronized void addChangeListener( ChangeListener listener ) { if( this.changeListeners == null ) { - this.changeListeners = new ArrayList(); + this.changeListeners = new ArrayList<>(); } this.changeListeners.add( listener ); } @@ -100,19 +100,7 @@ public synchronized void removeChangeListener( ChangeListener listener ) public void setFile( File file ) { - boolean differs = true; - File oldFile = this.fileNameFld.getFile(); - if( (file != null) && (oldFile != null) ) { - if( file.getPath().equals( oldFile.getPath() ) ) { - differs = false; - } - } else { - if( (file == null) && (oldFile == null) ) { - differs = false; - } - } - if( differs ) { - this.fileNameFld.setFile( file ); + if( this.fileNameFld.setFile( file ) ) { this.btnRemove.setEnabled( (file != null) && this.label.isEnabled() ); if( file != null ) { Main.setLastFile( file, "rom" ); diff --git a/src/jkcemu/tools/hexedit/ReplyBytesDlg.java b/src/jkcemu/base/ReplyBytesDlg.java similarity index 99% rename from src/jkcemu/tools/hexedit/ReplyBytesDlg.java rename to src/jkcemu/base/ReplyBytesDlg.java index d55923c..fc324a7 100644 --- a/src/jkcemu/tools/hexedit/ReplyBytesDlg.java +++ b/src/jkcemu/base/ReplyBytesDlg.java @@ -1,12 +1,12 @@ /* - * (c) 2008-2012 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * * Dialog fuer Eingabe von Bytes */ -package jkcemu.tools.hexedit; +package jkcemu.base; import java.awt.*; import java.awt.event.*; diff --git a/src/jkcemu/base/ReplyDirDlg.java b/src/jkcemu/base/ReplyDirDlg.java index c4ccd27..53d1f49 100644 --- a/src/jkcemu/base/ReplyDirDlg.java +++ b/src/jkcemu/base/ReplyDirDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2012 Jens Mueller + * (c) 2010-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -162,9 +162,18 @@ private void doApprove() private void doSelect() { - File file = DirSelectDlg.selectDirectory( this ); - if( file != null ) + File lastDir = null; + String text = this.textFld.getText(); + if( text != null ) { + text = text.trim(); + if( !text.isEmpty() ) { + lastDir = new File( text ); + } + } + File file = DirSelectDlg.selectDirectory( this, lastDir ); + if( file != null ) { this.textFld.setText( file.getPath() ); + } } } diff --git a/src/jkcemu/base/ReplyFileHeadDlg.java b/src/jkcemu/base/ReplyFileHeadDlg.java index 777a279..c800eb3 100644 --- a/src/jkcemu/base/ReplyFileHeadDlg.java +++ b/src/jkcemu/base/ReplyFileHeadDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -29,20 +29,20 @@ public enum Option { FILE_NAME_16, SCCH_FILE_TYPE }; - private boolean approved; - private int approvedBegAddr; - private int approvedEndAddr; - private int approvedStartAddr; - private char approvedScchFileType; - private String approvedFileName; - private HexDocument docBegAddr; - private HexDocument docEndAddr; - private HexDocument docStartAddr; - private Document docFileName; - private JComboBox comboScchFileType; - private JTextField fldFileName; - private JButton btnApprove; - private JButton btnCancel; + private boolean approved; + private int approvedBegAddr; + private int approvedEndAddr; + private int approvedStartAddr; + private char approvedScchFileType; + private String approvedFileName; + private HexDocument docBegAddr; + private HexDocument docEndAddr; + private HexDocument docStartAddr; + private LimitedDocument docFileName; + private JComboBox comboScchFileType; + private JTextField fldFileName; + private JButton btnApprove; + private JButton btnCancel; public ReplyFileHeadDlg( @@ -125,7 +125,8 @@ public ReplyFileHeadDlg( } else { len = 16; } - this.docFileName = new LimitedDocument( len, false ); + this.docFileName = new LimitedDocument( len ); + this.docFileName.setAsciiOnly( true ); this.fldFileName = new JTextField( this.docFileName, presetText, @@ -134,7 +135,7 @@ public ReplyFileHeadDlg( break; case SCCH_FILE_TYPE: labelText = "Dateityp:"; - this.comboScchFileType = new JComboBox( new Object[] { + this.comboScchFileType = new JComboBox<>( new String[] { "P Programm", "D Daten", "B BASIC-Programm", diff --git a/src/jkcemu/base/ReplyHexDlg.java b/src/jkcemu/base/ReplyHexDlg.java deleted file mode 100644 index 90ff049..0000000 --- a/src/jkcemu/base/ReplyHexDlg.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * (c) 2008-2010 Jens Mueller - * - * Kleincomputer-Emulator - * - * Eingabe-Dialog fuer eine Hexadezimalzahl - */ - -package jkcemu.base; - -import java.awt.Frame; -import java.lang.*; - - -public class ReplyHexDlg extends AbstractReplyDlg -{ - private HexDocument docHex; - private Integer reply; - - - public ReplyHexDlg( - Frame owner, - String msg, - int numDigits, - Integer value ) - { - super( owner, msg, "Eingabe Hexadezimalzahl", null ); - this.reply = null; - this.docHex = new HexDocument( this.replyTextField, numDigits ); - if( value != null ) { - this.docHex.setValue( value.intValue(), numDigits ); - } - this.replyTextField.setColumns( numDigits + 1 ); - fitWindowBounds(); - } - - - public Integer getReply() - { - return this.reply; - } - - - /* --- ueberschriebende Methoden --- */ - - @Override - protected boolean approveSelection() - { - boolean rv = false; - try { - this.reply = new Integer( this.docHex.intValue() ); - if( this.reply != null ) - rv = true; - } - catch( NumberFormatException ex ) { - showErrorDlg( this, ex.getMessage() ); - } - return rv; - } -} - diff --git a/src/jkcemu/base/SaveDlg.java b/src/jkcemu/base/SaveDlg.java index 1528fd1..c522220 100644 --- a/src/jkcemu/base/SaveDlg.java +++ b/src/jkcemu/base/SaveDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -18,73 +18,77 @@ import javax.swing.text.*; import jkcemu.Main; import jkcemu.emusys.*; -import z80emu.Z80Memory; -public class SaveDlg extends BasicDlg implements - DocumentListener, - FocusListener +public class SaveDlg extends BasicDlg implements DocumentListener { - private static String[] hsFileTypes = { - "A Assemblerquelltext", - "B BASIC-Programm", - "b Mini-/Tiny-BASIC-Programm", - "C Ausf\u00FChrbares MC-Programm", - "E EPROM-Inhalt", - "I Information (Text)", - "T Text" }; - - private ScreenFrm screenFrm; - private Z80Memory memory; - private boolean kcbasic; - private boolean rbasic; - private JTextField autoFillTarget; - private JTextField fldMemBegAddr; - private JTextField fldMemEndAddr; - private JTextField fldHeadBegAddr; - private JTextField fldHeadStartAddr; - private JTextField fldHeadFileDesc; - private JComboBox comboHeadFileType; - private JLabel labelHeadBegAddr; - private JLabel labelHeadStartAddr; - private JLabel labelHeadFileType; - private JLabel labelHeadFileDesc; - private JCheckBox btnKeepHeader; - private JRadioButton btnFileFmtBIN; - private JRadioButton btnFileFmtKCC; - private JRadioButton btnFileFmtTAP; - private JRadioButton btnFileFmtSSS; - private JRadioButton btnFileFmtRBAS; - private JRadioButton btnFileFmtRMC; - private JRadioButton btnFileFmtHS; - private JRadioButton btnFileFmtHEX; - private JRadioButton btnBegBlkNum0; - private JRadioButton btnBegBlkNum1; - private JButton btnSave; - private JButton btnHelp; - private JButton btnCancel; - private HexDocument docMemBegAddr; - private HexDocument docMemEndAddr; - private HexDocument docHeadBegAddr; - private HexDocument docHeadStartAddr; - private LimitedDocument docFileDesc; + public static enum BasicType { + NO_BASIC, + TINYBASIC, + MS_DERIVED_BASIC, + KCBASIC, + RBASIC, + OTHER_BASIC }; + + private ScreenFrm screenFrm; + private int begAddr; + private int endAddr; + private BasicType basicType; + private javax.swing.filechooser.FileFilter basicFileFilter; + private boolean kcbasic; + private boolean fileTypeFixed; + private boolean z9001; + private boolean autoFillHeadBegAddr; + private JTextField fldMemBegAddr; + private JTextField fldMemEndAddr; + private JTextField fldHeadBegAddr; + private JTextField fldHeadStartAddr; + private JTextField fldHeadFileDesc; + private JComboBox comboHeadFileType; + private JLabel labelHeadBegAddr; + private JLabel labelHeadStartAddr; + private JLabel labelHeadFileType; + private JLabel labelHeadFileDesc; + private JCheckBox btnKeepHeader; + private JRadioButton btnFileFmtBIN; + private JRadioButton btnFileFmtKCC; + private JRadioButton btnFileFmtTAP; + private JRadioButton btnFileFmtSSS; + private JRadioButton btnFileFmtBAS; + private JRadioButton btnFileFmtRMC; + private JRadioButton btnFileFmtHS; + private JRadioButton btnFileFmtHEX; + private JRadioButton btnBegBlkNum0; + private JRadioButton btnBegBlkNum1; + private JButton btnSave; + private JButton btnHelp; + private JButton btnCancel; + private HexDocument docMemBegAddr; + private HexDocument docMemEndAddr; + private HexDocument docHeadBegAddr; + private HexDocument docHeadStartAddr; + private LimitedDocument docHeadFileDesc; + private LimitedDocument docHeadFileType; public SaveDlg( - ScreenFrm screenFrm, - int begAddr, - int endAddr, - int hsFileType, - boolean kcbasic, - boolean rbasic, - String title ) + ScreenFrm screenFrm, + int begAddr, + int endAddr, + String title, + BasicType basicType, + javax.swing.filechooser.FileFilter basicFileFilter ) { super( screenFrm, title ); - this.screenFrm = screenFrm; - this.memory = screenFrm.getEmuThread(); - this.kcbasic = kcbasic; - this.rbasic = rbasic; - this.autoFillTarget = null; + this.screenFrm = screenFrm; + this.begAddr = begAddr; + this.endAddr = endAddr; + this.basicType = basicType; + this.basicFileFilter = basicFileFilter; + this.kcbasic = false; + this.fileTypeFixed = false; + this.z9001 = false; + this.autoFillHeadBegAddr = true; // Fensterinhalt @@ -122,12 +126,8 @@ public SaveDlg( 4, "Anfangsadresse des Speicherbereichs" ); this.fldMemBegAddr = new JTextField( this.docMemBegAddr, "", 5 ); - this.fldMemBegAddr.addActionListener( this ); - this.fldMemBegAddr.addFocusListener( this ); - this.docMemBegAddr.addDocumentListener( this ); if( begAddr >= 0 ) { - this.fldMemBegAddr.setText( String.format( "%04X", begAddr ) ); - this.fldMemBegAddr.setEditable( false ); + this.docMemBegAddr.setValue( begAddr, 4 ); } gbcMem.fill = GridBagConstraints.HORIZONTAL; gbcMem.weightx = 1.0; @@ -144,8 +144,6 @@ public SaveDlg( 4, "Endadresse des Speicherbereichs" ); this.fldMemEndAddr = new JTextField( this.docMemEndAddr, "", 5 ); - this.fldMemEndAddr.addActionListener( this ); - this.docMemEndAddr.addDocumentListener( this ); if( (begAddr >= 0) && (endAddr >= 0) ) { this.fldMemEndAddr.setText( String.format( "%04X", endAddr ) ); } @@ -176,19 +174,16 @@ public SaveDlg( this.btnFileFmtBIN = new JRadioButton( "Speicherabbilddatei ohne Kopfdaten (*.bin)", false ); - this.btnFileFmtBIN.addActionListener( this ); grpFileFmt.add( this.btnFileFmtBIN ); panelFileFmt.add( this.btnFileFmtBIN, gbcFileFmt ); this.btnFileFmtKCC = new JRadioButton( "KC-Systemdatei (*.kcc)", false ); - this.btnFileFmtKCC.addActionListener( this ); grpFileFmt.add( this.btnFileFmtKCC ); gbcFileFmt.insets.top = 0; gbcFileFmt.gridy++; panelFileFmt.add( this.btnFileFmtKCC, gbcFileFmt ); this.btnFileFmtTAP = new JRadioButton( "KC-TAP-Datei (*.tap)", false ); - this.btnFileFmtTAP.addActionListener( this ); grpFileFmt.add( this.btnFileFmtTAP ); gbcFileFmt.gridy++; panelFileFmt.add( this.btnFileFmtTAP, gbcFileFmt ); @@ -196,16 +191,14 @@ public SaveDlg( ButtonGroup grpBegBlkNum = new ButtonGroup(); this.btnBegBlkNum0 = new JRadioButton( - "Erster Block hat Nr. 0 (KC85/1, KC87, Z9001)", - false ); + "Erster Block hat Nr. 0 (KC85/1, KC87, Z9001)" ); grpBegBlkNum.add( this.btnBegBlkNum0 ); gbcFileFmt.insets.left = 50; gbcFileFmt.gridy++; panelFileFmt.add( this.btnBegBlkNum0, gbcFileFmt ); this.btnBegBlkNum1 = new JRadioButton( - "Erster Block hat Nr. 1 (HC900, KC85/2-5, KC-BASIC)", - true ); + "Erster Block hat Nr. 1 (HC900, KC85/2-5, KC-BASIC)" ); grpBegBlkNum.add( this.btnBegBlkNum1 ); gbcFileFmt.gridy++; panelFileFmt.add( this.btnBegBlkNum1, gbcFileFmt ); @@ -213,39 +206,29 @@ public SaveDlg( gbcFileFmt.insets.left = 5; this.btnFileFmtSSS = new JRadioButton( - "KC-BASIC-Programmdatei (*.sss)", - false ); - this.btnFileFmtSSS.setEnabled( kcbasic ); - this.btnFileFmtSSS.addActionListener( this ); + "KC-BASIC-Programmdatei (*.sss)" ); grpFileFmt.add( this.btnFileFmtSSS ); gbcFileFmt.gridy++; panelFileFmt.add( this.btnFileFmtSSS, gbcFileFmt ); - this.btnFileFmtRBAS = new JRadioButton( - "RBASIC-Programmdatei (*.bas)", - false ); - this.btnFileFmtRBAS.setEnabled( rbasic ); - this.btnFileFmtRBAS.addActionListener( this ); - grpFileFmt.add( this.btnFileFmtRBAS ); + this.btnFileFmtBAS = new JRadioButton( + "BASIC-/RBASIC-Programmdatei (*.bas; *.abc)" ); + grpFileFmt.add( this.btnFileFmtBAS ); gbcFileFmt.gridy++; - panelFileFmt.add( this.btnFileFmtRBAS, gbcFileFmt ); + panelFileFmt.add( this.btnFileFmtBAS, gbcFileFmt ); this.btnFileFmtRMC = new JRadioButton( - "RBASIC-Maschinencodedatei (*.rmc)", - false ); - this.btnFileFmtRMC.addActionListener( this ); + "RBASIC-Maschinencodedatei (*.rmc)" ); grpFileFmt.add( this.btnFileFmtRMC ); gbcFileFmt.gridy++; panelFileFmt.add( this.btnFileFmtRMC, gbcFileFmt ); - this.btnFileFmtHS = new JRadioButton( "Headersave-Datei (*.z80)", false ); - this.btnFileFmtHS.addActionListener( this ); + this.btnFileFmtHS = new JRadioButton( "Headersave-Datei (*.z80)" ); grpFileFmt.add( this.btnFileFmtHS ); gbcFileFmt.gridy++; panelFileFmt.add( this.btnFileFmtHS, gbcFileFmt ); - this.btnFileFmtHEX = new JRadioButton( "Intel-HEX-Datei (*.hex)", false ); - this.btnFileFmtHEX.addActionListener( this ); + this.btnFileFmtHEX = new JRadioButton( "Intel-HEX-Datei (*.hex)" ); grpFileFmt.add( this.btnFileFmtHEX ); gbcFileFmt.insets.bottom = 5; gbcFileFmt.gridy++; @@ -267,10 +250,60 @@ public SaveDlg( new Insets( 5, 5, 0, 5 ), 0, 0 ); + // Dateibezeichnung + this.labelHeadFileDesc = new JLabel( "Bezeichnung:" ); + panelFileHead.add( this.labelHeadFileDesc, gbcFileHead ); + + this.docHeadFileDesc = new LimitedDocument( + FileFormat.getTotalMaxFileDescLength() ); + this.docHeadFileDesc.setAsciiOnly( true ); + this.fldHeadFileDesc = new JTextField( this.docHeadFileDesc, "", 0 ); + gbcFileHead.fill = GridBagConstraints.HORIZONTAL; + gbcFileHead.weightx = 1.0; + gbcFileHead.gridwidth = GridBagConstraints.REMAINDER; + gbcFileHead.gridx++; + panelFileHead.add( this.fldHeadFileDesc, gbcFileHead ); + + // Dateityp + this.labelHeadFileType = new JLabel( "Typ:" ); + gbcFileHead.fill = GridBagConstraints.NONE; + gbcFileHead.weightx = 0.0; + gbcFileHead.insets.top = 5; + gbcFileHead.gridx = 0; + gbcFileHead.gridy++; + panelFileHead.add( this.labelHeadFileType, gbcFileHead ); + + this.comboHeadFileType = new JComboBox<>(); + this.comboHeadFileType.setEditable( true ); + gbcFileHead.fill = GridBagConstraints.HORIZONTAL; + gbcFileHead.weightx = 1.0; + gbcFileHead.gridwidth = GridBagConstraints.REMAINDER; + gbcFileHead.gridx++; + panelFileHead.add( this.comboHeadFileType, gbcFileHead ); + + this.docHeadFileType = new LimitedDocument(); + this.docHeadFileType.setAsciiOnly( true ); + ComboBoxEditor editor = this.comboHeadFileType.getEditor(); + if( editor != null ) { + Component c = editor.getEditorComponent(); + if( c != null ) { + if( c instanceof JTextComponent ) { + ((JTextComponent) c).setDocument( this.docHeadFileType ); + } + } + } + + Font font = this.fldHeadFileDesc.getFont(); + if( font != null ) { + this.comboHeadFileType.setFont( font ); + } + // Anfangsadresse this.labelHeadBegAddr = new JLabel( "Anfangsadresse:" ); - gbcFileHead.gridwidth = 1; - gbcFileHead.insets.top = 5; + gbcFileHead.fill = GridBagConstraints.NONE; + gbcFileHead.weightx = 0.0; + gbcFileHead.gridwidth = 1; + gbcFileHead.gridx = 0; gbcFileHead.gridy++; panelFileHead.add( this.labelHeadBegAddr, gbcFileHead ); @@ -278,7 +311,6 @@ public SaveDlg( 4, "Anfangsadresse in den Kopfdaten" ); this.fldHeadBegAddr = new JTextField( this.docHeadBegAddr, "", 5 ); - this.fldHeadBegAddr.addActionListener( this ); if( begAddr >= 0 ) { this.fldHeadBegAddr.setText( String.format( "%04X", begAddr ) ); } @@ -289,8 +321,8 @@ public SaveDlg( // Startadresse this.labelHeadStartAddr = new JLabel( "Startadresse:" ); - gbcFileHead.fill = GridBagConstraints.NONE; - gbcFileHead.weightx = 0.0; + gbcFileHead.fill = GridBagConstraints.NONE; + gbcFileHead.weightx = 0.0; gbcFileHead.gridx++; panelFileHead.add( this.labelHeadStartAddr, gbcFileHead ); @@ -298,84 +330,11 @@ public SaveDlg( 4, "Startadresse in den Kopfdaten" ); this.fldHeadStartAddr = new JTextField( this.docHeadStartAddr, "", 5 ); - this.fldHeadStartAddr.addActionListener( this ); gbcFileHead.fill = GridBagConstraints.HORIZONTAL; gbcFileHead.weightx = 0.5; gbcFileHead.gridx++; panelFileHead.add( this.fldHeadStartAddr, gbcFileHead ); - /* - * Wenn die Adressen vollstaendig vorgegeben sind, - * sollen sie in den Kopfdaten nicht aenderbar sein, - * Da eine Startadresse nicht immer vorgegeben wird, - * gelten vorgegebene Anfangs- und Endadressen als vollstaendig - * vorgegebene Adressen. - */ - if( (begAddr >= 0) && (endAddr >= begAddr) ) { - this.fldHeadBegAddr.setEditable( false ); - this.fldHeadStartAddr.setEditable( false ); - } - - // Dateityp - this.labelHeadFileType = new JLabel( "Dateityp (1. Zeichen):" ); - gbcFileHead.fill = GridBagConstraints.NONE; - gbcFileHead.weightx = 0.0; - gbcFileHead.gridx = 0; - gbcFileHead.gridy++; - panelFileHead.add( this.labelHeadFileType, gbcFileHead ); - - this.comboHeadFileType = new JComboBox(); - this.comboHeadFileType.addItem( "" ); - for( int i = 0; i < hsFileTypes.length; i++ ) { - this.comboHeadFileType.addItem( hsFileTypes[ i ] ); - } - this.comboHeadFileType.setEnabled( true ); - this.comboHeadFileType.setEditable( true ); - - Font font = this.fldHeadBegAddr.getFont(); - if( font != null ) { - this.comboHeadFileType.setFont( font ); - } - - if( (hsFileType > '\u0020') && (hsFileType <= '\u007E') ) { - this.btnFileFmtHS.setSelected( true ); - for( int k = 0; k < hsFileTypes.length; k++ ) { - String item = hsFileTypes[ k ]; - if( item.length() > 0 ) { - if( item.charAt( 0 ) == hsFileType ) { - this.comboHeadFileType.setSelectedItem( item ); - this.comboHeadFileType.setEditable( false ); - this.comboHeadFileType.setEnabled( false ); - break; - } - } - } - } - gbcFileHead.fill = GridBagConstraints.HORIZONTAL; - gbcFileHead.weightx = 1.0; - gbcFileHead.gridwidth = GridBagConstraints.REMAINDER; - gbcFileHead.gridx++; - panelFileHead.add( this.comboHeadFileType, gbcFileHead ); - - // Dateibeschreibung - this.labelHeadFileDesc = new JLabel( "Bezeichnung:" ); - gbcFileHead.fill = GridBagConstraints.NONE; - gbcFileHead.weightx = 0.0; - gbcFileHead.gridwidth = 1; - gbcFileHead.gridx = 0; - gbcFileHead.gridy++; - panelFileHead.add( this.labelHeadFileDesc, gbcFileHead ); - - this.docFileDesc = new LimitedDocument( - FileSaver.getMaxFileDescLength() ); - this.fldHeadFileDesc = new JTextField( this.docFileDesc, "", 0 ); - this.fldHeadFileDesc.addActionListener( this ); - gbcFileHead.fill = GridBagConstraints.HORIZONTAL; - gbcFileHead.weightx = 1.0; - gbcFileHead.gridwidth = 3; - gbcFileHead.gridx++; - panelFileHead.add( this.fldHeadFileDesc, gbcFileHead ); - // Schalter fuer Kopfdaten in Arbeitsspeicher kopieren this.btnKeepHeader = new JCheckBox( "Headersave-Kopfdaten nach 00E0-00FF kopieren" ); @@ -397,55 +356,85 @@ public SaveDlg( add( panelBtn, gbc ); this.btnSave = new JButton( "Speichern" ); - this.btnSave.addActionListener( this ); - this.btnSave.addKeyListener( this ); panelBtn.add( this.btnSave ); this.btnHelp = new JButton( "Hilfe" ); - this.btnHelp.addActionListener( this ); - this.btnHelp.addKeyListener( this ); panelBtn.add( this.btnHelp ); this.btnCancel = new JButton( "Abbrechen" ); - this.btnCancel.addActionListener( this ); - this.btnCancel.addKeyListener( this ); panelBtn.add( this.btnCancel ); // Vorbelegungen - boolean a5105 = false; - boolean kc85 = false; - boolean z1013 = false; - boolean z9001 = false; - EmuSys emuSys = this.screenFrm.getEmuThread().getEmuSys(); - if( emuSys != null ) { - a5105 = (emuSys instanceof A5105); - kc85 = (emuSys instanceof KC85); - z1013 = (emuSys instanceof Z1013); - z9001 = (emuSys instanceof Z9001); + boolean basEnabled = false; + boolean sssEnabled = false; + boolean kc85 = false; + boolean basic = false; + int hsFileType = -1; + switch( basicType ) { + case TINYBASIC: + this.btnFileFmtHS.setSelected( true ); + hsFileType = 'b'; + basic = true; + break; + case MS_DERIVED_BASIC: + this.btnFileFmtBAS.setSelected( true ); + hsFileType = 'B'; + basEnabled = true; + basic = true; + break; + case KCBASIC: + this.btnFileFmtSSS.setSelected( true ); + this.btnBegBlkNum1.setSelected( true ); + this.kcbasic = true; + hsFileType = 'B'; + sssEnabled = true; + basic = true; + break; + case RBASIC: + this.btnFileFmtBAS.setSelected( true ); + hsFileType = 'B'; + basEnabled = true; + basic = true; + break; + } + this.btnFileFmtBAS.setEnabled( basEnabled ); + this.btnFileFmtSSS.setEnabled( sssEnabled ); + if( hsFileType > 0 ) { + this.fileTypeFixed = true; + if( EmuUtil.setSelectedHeadersaveFileTypeItem( + this.comboHeadFileType, + hsFileType ) ) + { + this.comboHeadFileType.setEditable( false ); + this.comboHeadFileType.setEnabled( false ); + } } - if( a5105 && !rbasic ) { - this.btnFileFmtRMC.setSelected( true ); - } else if( z1013 ) { - this.btnFileFmtHS.setSelected( true ); - this.btnKeepHeader.setSelected( + if( !basic ) { + boolean a5105 = false; + boolean z1013 = false; + EmuSys emuSys = this.screenFrm.getEmuThread().getEmuSys(); + if( emuSys != null ) { + a5105 = (emuSys instanceof A5105); + kc85 = (emuSys instanceof KC85); + z1013 = (emuSys instanceof Z1013); + this.z9001 = (emuSys instanceof Z9001); + } + if( a5105 ) { + this.btnFileFmtRMC.setSelected( true ); + } else if( kc85 || this.z9001 ) { + this.btnFileFmtKCC.setSelected( true ); + } else if( z1013 ) { + this.btnFileFmtHS.setSelected( true ); + this.btnKeepHeader.setSelected( EmuUtil.parseBoolean( Main.getProperty( "jkcemu.loadsave.header.keep" ), false ) ); - } else { - if( kcbasic ) { - this.btnFileFmtSSS.setSelected( true ); - } else if( rbasic ) { - this.btnFileFmtRBAS.setSelected( true ); - } else if( kc85 || z9001 ) { - this.btnFileFmtKCC.setSelected( true ); - } else if( (hsFileType > '\u0020') && (hsFileType <= '\u007E') ) { - this.btnFileFmtHS.setSelected( true ); } else { this.btnFileFmtBIN.setSelected( true ); } } - if( kcbasic || kc85 ) { + if( basic || kc85 ) { this.btnBegBlkNum1.setSelected( true ); } else { this.btnBegBlkNum0.setSelected( true ); @@ -466,6 +455,30 @@ public SaveDlg( // sonstiges updSaveBtn(); + this.fldMemBegAddr.addActionListener( this ); + this.docMemBegAddr.addDocumentListener( this ); + this.fldMemEndAddr.addActionListener( this ); + this.docMemEndAddr.addDocumentListener( this ); + this.btnFileFmtBIN.addActionListener( this ); + this.btnFileFmtKCC.addActionListener( this ); + this.btnFileFmtTAP.addActionListener( this ); + this.btnBegBlkNum0.addActionListener( this ); + this.btnBegBlkNum1.addActionListener( this ); + this.btnFileFmtSSS.addActionListener( this ); + this.btnFileFmtBAS.addActionListener( this ); + this.btnFileFmtRMC.addActionListener( this ); + this.btnFileFmtHS.addActionListener( this ); + this.btnFileFmtHEX.addActionListener( this ); + this.fldHeadFileDesc.addActionListener( this ); + this.fldHeadBegAddr.addActionListener( this ); + this.docHeadBegAddr.addDocumentListener( this ); + this.fldHeadStartAddr.addActionListener( this ); + this.btnSave.addActionListener( this ); + this.btnSave.addKeyListener( this ); + this.btnHelp.addActionListener( this ); + this.btnHelp.addKeyListener( this ); + this.btnCancel.addActionListener( this ); + this.btnCancel.addKeyListener( this ); } @@ -492,34 +505,6 @@ public void removeUpdate( DocumentEvent e ) } - /* --- Methoden fuer FocusListener --- */ - - /* - * Wenn das Feld "Anfangsadresse Speicherbereich" den Focus bekommen, - * wird geprueft, ob das Feld "Anfangsadresse Kopfdaten" leer ist. - * Wenn ja, wird das automatische Fuellen des zweiten Feldes aktiviert. - */ - @Override - public void focusGained( FocusEvent e ) - { - if( !e.isTemporary() ) { - Component c = e.getComponent(); - if( c == this.fldMemBegAddr ) { - this.autoFillTarget = (this.docHeadBegAddr.getLength() < 1 ? - this.fldHeadBegAddr : null); - } - } - } - - - @Override - public void focusLost( FocusEvent e ) - { - if( !e.isTemporary() ) - this.autoFillTarget = null; - } - - /* --- ueberschriebene Methoden --- */ @Override @@ -538,12 +523,14 @@ protected boolean doAction( EventObject e ) if( (src == this.btnFileFmtBIN) || (src == this.btnFileFmtKCC) || (src == this.btnFileFmtTAP) - || (src == this.btnFileFmtSSS) - || (src == this.btnFileFmtRBAS) + || (src == this.btnBegBlkNum0) + || (src == this.btnBegBlkNum1) + || (src == this.btnFileFmtSSS) + || (src == this.btnFileFmtBAS) || (src == this.btnFileFmtRMC) || (src == this.btnFileFmtHS) || (src == this.btnFileFmtHEX) ) - { + { rv = true; updHeadFields(); } @@ -567,29 +554,59 @@ else if( src instanceof JTextField ) { } } return rv; - } + } /* --- private Methoden --- */ private void doSave() { - boolean saved = false; - String fileFmt = getSelectedFileFmt(); + boolean saved = false; + FileFormat fileFmt = getSelectedFileFmt(); try { - FileSaver.checkFileDesc( - fileFmt, - this.kcbasic, - this.fldHeadFileDesc.getText() ); - - boolean isBIN = this.btnFileFmtBIN.isSelected(); - boolean isKCC = this.btnFileFmtKCC.isSelected(); - boolean isTAP = this.btnFileFmtTAP.isSelected(); - boolean isSSS = this.btnFileFmtTAP.isSelected(); - boolean isRBAS = this.btnFileFmtRBAS.isSelected(); - boolean isRMC = this.btnFileFmtRMC.isSelected(); - boolean isHS = this.btnFileFmtHS.isSelected(); - boolean isHEX = this.btnFileFmtHEX.isSelected(); + if( fileFmt == null ) { + throw new UserInputException( "Dateiformat nicht ausgew\u00E4hlt" ); + } + + // Adressbereich pruefen + int begAddr = this.docMemBegAddr.intValue() & 0xFFFF; + int endAddr = this.docMemEndAddr.intValue() & 0xFFFF; + if( (this.begAddr >= 0) && (this.endAddr >= 0) + && ((begAddr > this.begAddr) || (endAddr < this.endAddr)) ) + { + throw new UserInputException( + String.format( + "Der angegebene Adressbereich beinhaltet nicht" + + " vollst\u00E4ndig\n" + + "das zu speichernde Programm (%04X-%04X).", + this.begAddr, + this.endAddr ) ); + } + + // Dateibezeichnung pruefen + String headFileDesc = this.fldHeadFileDesc.getText(); + if( headFileDesc != null ) { + int m = fileFmt.getMaxFileDescLength(); + if( m > 0 ) { + if( headFileDesc.length() > m ) { + throw new UserInputException( + String.format( + "Die Bezeichnung der Datei ist zu lang" + + "(max. %d Zeichen).", + m ) ); + } + } + } + + // Dateiformat + boolean isBIN = this.btnFileFmtBIN.isSelected(); + boolean isKCC = this.btnFileFmtKCC.isSelected(); + boolean isTAP = this.btnFileFmtTAP.isSelected(); + boolean isSSS = this.btnFileFmtSSS.isSelected(); + boolean isBAS = this.btnFileFmtBAS.isSelected(); + boolean isRMC = this.btnFileFmtRMC.isSelected(); + boolean isHS = this.btnFileFmtHS.isSelected(); + boolean isHEX = this.btnFileFmtHEX.isSelected(); String title = "Datei speichern"; javax.swing.filechooser.FileFilter fileFilter = null; @@ -603,15 +620,23 @@ else if( isKCC ) { } else if( isTAP ) { title = "KC-TAP-Datei speichern"; - fileFilter = EmuUtil.getTapFileFilter(); + fileFilter = EmuUtil.getKCTapFileFilter(); } else if( isSSS ) { title = "KC-BASIC-Programmdatei speichern"; fileFilter = EmuUtil.getKCBasicFileFilter(); } - else if( isRBAS ) { - title = "RBASIC-Programmdatei speichern"; - fileFilter = EmuUtil.getRBasicFileFilter(); + else if( isBAS ) { + if( this.basicType.equals( BasicType.RBASIC ) ) { + title = "RBASIC-Programmdatei speichern"; + } else { + title = "BASIC-Programmdatei speichern"; + } + if( this.basicFileFilter != null ) { + fileFilter = this.basicFileFilter; + } else { + fileFilter = EmuUtil.getBasicFileFilter(); + } } else if( isRMC ) { title = "RBASIC-Maschinencodedatei speichern"; @@ -625,122 +650,169 @@ else if( isHEX ) { title = "Intel-HEX-Datei speichern"; fileFilter = EmuUtil.getHexFileFilter(); } - File file = EmuUtil.showFileSaveDlg( + String prefBasicFmt = null; + switch( this.basicType ) { + case MS_DERIVED_BASIC: + case RBASIC: + if( !isBAS && !isHS ) { + prefBasicFmt = this.btnFileFmtBAS.getText(); + } + break; + case KCBASIC: + if( !isTAP && !isSSS && !isHS ) { + prefBasicFmt = this.btnFileFmtSSS.getText(); + } + break; + } + boolean status = true; + if( prefBasicFmt != null ) { + status = showYesNoWarningDlg( + this, + "Das von Ihnen ausgew\u00E4hlte Dateiformat wird" + + " \u00FCblicherweise\n" + + "nicht zum Speichern von BASIC-Programmen" + + " verwendet.\n" + + "Zwar k\u00F6nnen Sie das Programm" + + " in dem Format speichern, jedoch\n" + + "wird JKCEMU beim Laden der Datei" + + " das BASIC-Programm nicht erkennen\n" + + "und somit die Systemzellen des" + + " BASIC-Interpreters nicht anpassen.\n" + + "Dadurch l\u00E4sst sich das Programm" + + " dann nicht nutzen.\n\n" + + "Das bevorzugte Dateiformat zum Speichern" + + " von BASIC-Programmen ist:\n\n " + + prefBasicFmt + + "\n\nM\u00F6chten Sie trotzdem in dem" + + " von Ihnen gew\u00E4hlten Dateiformat" + + " speichern?", + "Warnung" ); + } + if( status ) { + + // Dateiauswahldialog + File file = EmuUtil.showFileSaveDlg( this.screenFrm, title, - Main.getLastPathFile( "software" ), + Main.getLastDirFile( "software" ), fileFilter ); - if( file != null ) { - try { - int begAddr = this.docMemBegAddr.intValue() & 0xFFFF; - int endAddr = this.docMemEndAddr.intValue() & 0xFFFF; - int headBegAddr = begAddr; - Integer headStartAddr = null; - int headFileType = '\u0020'; - if( isKCC || (isTAP && !this.kcbasic) || isHS || isHEX ) { - String headBegAddrText = this.fldHeadBegAddr.getText(); - if( headBegAddrText != null ) { - if( !headBegAddrText.trim().isEmpty() ) { - headBegAddr = this.docHeadBegAddr.intValue() & 0xFFFF; + if( file != null ) { + try { + int headBegAddr = begAddr; + Integer headStartAddr = null; + String headFileType = null; + if( isKCC || (isTAP && !this.kcbasic) || isHS || isHEX ) { + String headBegAddrText = this.fldHeadBegAddr.getText(); + if( headBegAddrText != null ) { + if( !headBegAddrText.trim().isEmpty() ) { + headBegAddr = this.docHeadBegAddr.intValue() & 0xFFFF; + } } } - } - if( isKCC || (isTAP && !this.kcbasic) || isHS ) { - String s = this.fldHeadStartAddr.getText(); - if( s != null ) { - if( !s.trim().isEmpty() ) { - headStartAddr = new Integer( + if( isKCC || (isTAP && !this.kcbasic) || isRMC || isHS ) { + String s = this.fldHeadStartAddr.getText(); + if( s != null ) { + if( !s.trim().isEmpty() ) { + headStartAddr = new Integer( this.docHeadStartAddr.intValue() & 0xFFFF ); + } } } - } - if( isHS ) { Object o = this.comboHeadFileType.getSelectedItem(); if( o != null ) { - String s = o.toString(); - if( s != null ) { - if( s.length() > 0 ) { - headFileType = s.charAt( 0 ); - } - } + headFileType = o.toString(); } - } - FileSaver.saveFile( + EmuThread emuThread = this.screenFrm.getEmuThread(); + FileSaver.saveFile( file, fileFmt, - this.memory, + emuThread, begAddr, endAddr, - this.kcbasic, - this.rbasic, + !this.basicType.equals( BasicType.NO_BASIC ), headBegAddr, headStartAddr, + headFileDesc, headFileType, - this.fldHeadFileDesc.getText(), - this.btnKeepHeader.isSelected() ? this.memory : null ); - saved = true; - Main.setLastFile( file, "software" ); - this.screenFrm.showStatusText( "Datei gespeichert" ); - } - catch( IOException ex ) { - showErrorDlg( - this, - "Datei kann nicht gespeichert werden.\n\n" - + ex.getMessage() ); - } - catch( Exception ex ) { - showErrorDlg( this, ex.getMessage() ); + this.btnKeepHeader.isSelected() ? emuThread : null ); + saved = true; + Main.setLastFile( file, "software" ); + this.screenFrm.showStatusText( "Datei gespeichert" ); + } + catch( IOException ex ) { + showErrorDlg( + this, + "Datei kann nicht gespeichert werden.\n\n" + + ex.getMessage() ); + } + catch( Exception ex ) { + showErrorDlg( this, ex.getMessage() ); + } } } } catch( UserInputException ex ) { showErrorDlg( this, ex.getMessage() ); } - if( saved ) + if( saved ) { doClose(); + } } private void docContentChanged( DocumentEvent e ) { - if( this.autoFillTarget != null ) { + if( this.autoFillHeadBegAddr ) { Document doc = e.getDocument(); - if( (doc != null) && (doc != this.autoFillTarget.getDocument()) ) { - try { - this.autoFillTarget.setText( doc.getText( 0, doc.getLength() ) ); - } - catch( BadLocationException ex ) {} + if( doc == this.docMemBegAddr ) { + this.fldHeadBegAddr.setText( this.fldMemBegAddr.getText() ); + } + else if( (doc == this.docHeadBegAddr) + && this.fldHeadBegAddr.hasFocus() ) + { + this.autoFillHeadBegAddr = false; } } updSaveBtn(); } - private String getSelectedFileFmt() + private FileFormat getSelectedFileFmt() { - String rv = FileSaver.BIN; - if( this.btnFileFmtKCC.isSelected() ) { - rv = FileSaver.KCC; + FileFormat rv = null; + if( this.btnFileFmtBIN.isSelected() ) { + rv = FileFormat.BIN; + } + else if( this.btnFileFmtKCC.isSelected() ) { + rv = FileFormat.KCC; } else if( this.btnFileFmtTAP.isSelected() ) { - rv = this.btnBegBlkNum0.isSelected() ? - FileSaver.KCTAP_0 - : FileSaver.KCTAP_1; + if( this.kcbasic ) { + rv = FileFormat.KCTAP_BASIC_PRG; + } else { + rv = (this.btnBegBlkNum0.isSelected() ? + FileFormat.KCTAP_Z9001 + : FileFormat.KCTAP_KC85); + } } else if( this.btnFileFmtSSS.isSelected() ) { - rv = FileSaver.KCBASIC; + rv = FileFormat.KCBASIC_PRG; } - else if( this.btnFileFmtRBAS.isSelected() ) { - rv = FileSaver.RBASIC; + else if( this.btnFileFmtBAS.isSelected() ) { + if( this.basicType == BasicType.RBASIC ) { + rv = FileFormat.RBASIC_PRG; + } else { + rv = FileFormat.BASIC_PRG; + } } else if( this.btnFileFmtRMC.isSelected() ) { - rv = FileSaver.RMC; + rv = FileFormat.RMC; } else if( this.btnFileFmtHS.isSelected() ) { - rv = FileSaver.HEADERSAVE; + rv = FileFormat.HEADERSAVE; } else if( this.btnFileFmtHEX.isSelected() ) { - rv = FileSaver.INTELHEX; + rv = FileFormat.INTELHEX; } return rv; } @@ -748,39 +820,68 @@ else if( this.btnFileFmtHEX.isSelected() ) { private void updHeadFields() { - boolean stateKCC = this.btnFileFmtKCC.isSelected(); - boolean stateTAP = this.btnFileFmtTAP.isSelected(); - boolean stateSSS = this.btnFileFmtSSS.isSelected(); - boolean stateRBAS = this.btnFileFmtRBAS.isSelected(); - boolean stateRMC = this.btnFileFmtRMC.isSelected(); - boolean stateHS = this.btnFileFmtHS.isSelected(); - boolean stateHEX = this.btnFileFmtHEX.isSelected(); + boolean stateKCC = this.btnFileFmtKCC.isSelected(); + boolean stateTAP = this.btnFileFmtTAP.isSelected(); + boolean stateSSS = this.btnFileFmtSSS.isSelected(); + boolean stateBAS = this.btnFileFmtBAS.isSelected(); + boolean stateRMC = this.btnFileFmtRMC.isSelected(); + boolean stateHS = this.btnFileFmtHS.isSelected(); + boolean stateHEX = this.btnFileFmtHEX.isSelected(); this.btnBegBlkNum0.setEnabled( stateTAP && !this.kcbasic ); this.btnBegBlkNum1.setEnabled( stateTAP && !this.kcbasic ); - boolean stateBegAddr = (stateKCC || (stateTAP && !this.kcbasic) - || stateHS || stateRMC || stateHEX); - this.labelHeadBegAddr.setEnabled( stateBegAddr ); - this.fldHeadBegAddr.setEnabled( stateBegAddr ); - - boolean stateStartAddr = (stateKCC || (stateTAP && !this.kcbasic) - || stateHS || stateRMC); - this.labelHeadStartAddr.setEnabled( stateStartAddr ); - this.fldHeadStartAddr.setEnabled( stateStartAddr ); - - this.labelHeadFileType.setEnabled( stateHS ); - this.comboHeadFileType.setEnabled( stateHS ); + boolean stateMemBegAddr = true; + if( (this.begAddr >= 0) + && ((stateTAP && this.kcbasic) || stateSSS || stateBAS) ) + { + stateMemBegAddr = false; + this.docMemBegAddr.setValue( this.begAddr, 4 ); + } + this.fldMemBegAddr.setEnabled( stateMemBegAddr ); + + boolean stateHeadBegAddr = stateMemBegAddr + && (stateKCC || (stateTAP && !this.kcbasic) + || stateHS || stateRMC || stateHEX); + this.labelHeadBegAddr.setEnabled( stateHeadBegAddr ); + this.fldHeadBegAddr.setEnabled( stateHeadBegAddr ); + + boolean stateHeadStartAddr = stateMemBegAddr + && (stateKCC || (stateTAP && !this.kcbasic) || stateHS || stateRMC); + this.labelHeadStartAddr.setEnabled( stateHeadStartAddr ); + this.fldHeadStartAddr.setEnabled( stateHeadStartAddr ); + + String oldDesc = this.fldHeadFileDesc.getText(); + int maxDescLen = 0; + int maxTypeLen = 0; + if( !this.fileTypeFixed ) { + this.comboHeadFileType.removeAllItems(); + if( stateHS ) { + EmuUtil.addHeadersaveFileTypeItemsTo( this.comboHeadFileType ); + maxDescLen = 16; + maxTypeLen = 1; + } else if( stateKCC || stateTAP ) { + if( (this.z9001 && stateKCC) + || (stateTAP && this.btnBegBlkNum0.isSelected()) ) + { + maxDescLen = 8; + maxTypeLen = 3; + this.comboHeadFileType.addItem( "" ); + this.comboHeadFileType.addItem( "COM" ); + } else { + maxDescLen = 11; + } + } + } + this.docHeadFileDesc.setMaxLength( maxDescLen ); + this.fldHeadFileDesc.setText( oldDesc ); + this.docHeadFileType.setMaxLength( maxTypeLen ); + this.labelHeadFileType.setEnabled( maxTypeLen > 0 ); + this.comboHeadFileType.setEnabled( maxTypeLen > 0 ); boolean stateFileDesc = (stateKCC || stateTAP || stateHS); this.labelHeadFileDesc.setEnabled( stateFileDesc ); this.fldHeadFileDesc.setEnabled( stateFileDesc ); - if( stateFileDesc ) { - this.docFileDesc.setMaxLength( - FileSaver.getMaxFileDescLength( - getSelectedFileFmt(), - false ) ); - } this.btnKeepHeader.setEnabled( stateHS diff --git a/src/jkcemu/base/ScreenFrm.java b/src/jkcemu/base/ScreenFrm.java index 01f4887..8541b1b 100644 --- a/src/jkcemu/base/ScreenFrm.java +++ b/src/jkcemu/base/ScreenFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -21,7 +21,7 @@ import javax.imageio.ImageIO; import javax.swing.*; import jkcemu.Main; -import jkcemu.audio.AudioFrm; +import jkcemu.audio.*; import jkcemu.disk.*; import jkcemu.etc.*; import jkcemu.joystick.JoystickFrm; @@ -117,6 +117,7 @@ public class ScreenFrm extends BasicFrm implements private EmuThread emuThread; private Clipboard clipboard; private KeyboardFrm keyboardFrm; + private MsgFrm msgFrm; private DebugFrm primDebugFrm; private MemEditFrm primMemEditFrm; private ReassFrm primReassFrm; @@ -133,6 +134,7 @@ public ScreenFrm() // Initialisierungen this.emuThread = null; this.keyboardFrm = null; + this.msgFrm = null; this.primDebugFrm = null; this.primMemEditFrm = null; this.primReassFrm = null; @@ -184,13 +186,19 @@ public ScreenFrm() InputEvent.ALT_MASK ) ) ); mnuFile.addSeparator(); this.mnuBasicOpen = createJMenuItem( - "BASIC-Programm im Texteditor \u00F6ffnen...", - "file.basic.open" ); + "BASIC-Programm im Texteditor \u00F6ffnen...", + "file.basic.open", + KeyStroke.getKeyStroke( + KeyEvent.VK_T, + InputEvent.ALT_MASK | InputEvent.SHIFT_MASK ) ); mnuFile.add( this.mnuBasicOpen ); this.mnuBasicSave = createJMenuItem( - "BASIC-Programm speichern...", - "file.basic.save" ); + "BASIC-Programm speichern...", + "file.basic.save", + KeyStroke.getKeyStroke( + KeyEvent.VK_S, + InputEvent.ALT_MASK | InputEvent.SHIFT_MASK ) ); mnuFile.add( this.mnuBasicSave ); mnuFile.addSeparator(); @@ -235,14 +243,18 @@ public ScreenFrm() mnuScreen.add( this.mnuScreenTextSave ); mnuFile.addSeparator(); - mnuFile.add( createJMenuItem( - "Datei-Browser...", - "file.browser" ) ); mnuFile.add( createJMenuItem( "Texteditor/Programmierung...", "file.texteditor", KeyStroke.getKeyStroke( - KeyEvent.VK_T, + KeyEvent.VK_T, + InputEvent.ALT_MASK ) ) ); + mnuFile.add( createJMenuItem( "Datei-Browser...", "file.browser" ) ); + mnuFile.add( createJMenuItem( + "Dateien suchen...", + "file.find", + KeyStroke.getKeyStroke( + KeyEvent.VK_F, InputEvent.ALT_MASK ) ) ); mnuFile.addSeparator(); mnuFile.add( createJMenuItem( "Beenden", "file.quit" ) ); @@ -409,38 +421,43 @@ public ScreenFrm() KeyStroke.getKeyStroke( KeyEvent.VK_M, InputEvent.ALT_MASK | InputEvent.SHIFT_MASK ) ) ); - mnuExtraTools.add( createJMenuItem( "Rechner...", "extra.calculator" ) ); - mnuExtraTools.add( createJMenuItem( - "Hex-Dateivergleicher...", - "extra.hexdiff" ) ); mnuExtraTools.add( createJMenuItem( "Hex-Editor...", "extra.hexeditor", KeyStroke.getKeyStroke( KeyEvent.VK_H, InputEvent.ALT_MASK | InputEvent.SHIFT_MASK ) ) ); + mnuExtraTools.add( createJMenuItem( + "Hex-Dateivergleicher...", + "extra.hexdiff" ) ); mnuExtraTools.add( createJMenuItem( "Dateikonverter...", "extra.fileconverter" ) ); mnuExtraTools.add( createJMenuItem( "Bildbetrachter...", "extra.imageviewer" ) ); + mnuExtraTools.add( createJMenuItem( + "Audio-Recorder...", "extra.audiorecorder" ) ); + mnuExtraTools.add( createJMenuItem( "Rechner...", "extra.calculator" ) ); + mnuExtraTools.add( createJMenuItem( + "Diskettenabbilddatei-Inspektor...", + "extra.diskviewer" ) ); mnuExtraTools.addSeparator(); mnuExtraTools.add( createJMenuItem( "CP/M-Diskettenabbilddatei manuell erstellen...", "extra.diskimage.create_manually" ) ); mnuExtraTools.add( createJMenuItem( - "CP/M-Diskettenabbilddatei entpacken...", - "extra.diskimage.unpack" ) ); + "CP/M-Diskettenabbilddatei entpacken...", + "extra.diskimage.unpack" ) ); mnuExtraTools.add( createJMenuItem( - "CP/M-Diskette entpacken...", - "extra.disk.unpack" ) ); + "CP/M-Diskette entpacken...", + "extra.disk.unpack" ) ); mnuExtraTools.addSeparator(); mnuExtraTools.add( createJMenuItem( - "Abbilddatei von Diskette erstellen...", - "extra.diskimage.create_from_disk" ) ); + "Abbilddatei von Datentr\u00E4ger erstellen...", + "extra.diskimage.create_from_disk" ) ); mnuExtraTools.add( createJMenuItem( - "Abbilddatei auf Diskette schreiben...", - "extra.diskimage.write_to_disk" ) ); + "Abbilddatei auf Datentr\u00E4ger schreiben...", + "extra.diskimage.write_to_disk" ) ); mnuExtra.add( mnuExtraTools ); mnuExtra.addSeparator(); @@ -732,16 +749,17 @@ public BufferedImage createSnapshot() } - public void fireDirectoryChanged( File dirFile ) + public void fireAppendMsg( final String msg ) { - Frame[] frms = Frame.getFrames(); - if( frms != null ) { - for( Frame f : frms ) { - if( f instanceof FileBrowserFrm ) { - ((FileBrowserFrm) f).fireDirectoryChanged( dirFile ); - } - } - } + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + appendMsg( msg ); + } + } ); } @@ -793,24 +811,41 @@ public void run() } - public void fireUpdScreenTextActionsEnabled() + public void fireShowStatusText( final String text ) { - boolean state = false; - EmuSys emuSys = getEmuSys(); - if( emuSys != null ) { - state = emuSys.canExtractScreenText(); - } - if( !state ) { - clearScreenSelection(); + if( text != null ) { + if( !text.isEmpty() ) { + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + showStatusText( text ); + } + } ); + } } - final boolean state2 = state; + } + + + public void fireUpdScreenTextActionsEnabled() + { + final EmuSys emuSys = getEmuSys(); EventQueue.invokeLater( new Runnable() { @Override public void run() { - setScreenTextActionsEnabled( state2 ); + boolean state = false; + if( emuSys != null ) { + state = emuSys.canExtractScreenText(); + } + if( !state ) { + clearScreenSelection(); + } + setScreenTextActionsEnabled( state ); } } ); } @@ -824,10 +859,11 @@ public EmuThread getEmuThread() public static int getDefaultScreenRefreshMillis() { - int rv = 100; - if( Main.availableProcessors() > 1 ) { + int rv = 100; + int nProcs = Runtime.getRuntime().availableProcessors(); + if( nProcs > 1 ) { rv = 50; - if( Main.availableProcessors() >= 4 ) { + if( nProcs >= 4 ) { rv = 20; } } @@ -1165,6 +1201,24 @@ public void keyPressed( KeyEvent e ) if( (this.emuThread != null) && !e.isAltDown() && (e.getKeyCode() != KeyEvent.VK_F10) ) { + /* + * CTRL-M liefert auf verschiedenen Betriebssystemen + * unterschiedliche ASCII-Codes (10 bzw. 13). + * Aus diesem Grund wird hier CTRL-M fest auf 13 gemappt, + * so wie es auch die von JKCEMU emulierten Computer im Original tun. + */ + if( (e.getKeyCode() == KeyEvent.VK_M) + && !e.isAltDown() + && !e.isAltGraphDown() + && e.isControlDown() + && !e.isMetaDown() + && !e.isShiftDown() ) + { + this.emuThread.keyTyped( '\r' ); + this.ignoreKeyChar = true; + e.consume(); + } else + if( this.emuThread.keyPressed( e ) ) { this.ignoreKeyChar = true; e.consume(); @@ -1237,15 +1291,28 @@ else if( (m & InputEvent.BUTTON2_MASK) != 0 ) { } + /* --- ueberschriebene Methoden fuer WindowListener --- */ + @Override - public void windowDeactivated( WindowEvent e ) + public void windowActivated( WindowEvent e ) { - if( (e.getWindow() == this) && (this.emuThread != null) ) - this.emuThread.keyReleased(); + if( e.getWindow() == this ) { + Main.setWindowActivated( Main.WINDOW_MASK_SCREEN ); + } } - /* --- ueberschriebene Methoden fuer WindowListener --- */ + @Override + public void windowDeactivated( WindowEvent e ) + { + if( e.getWindow() == this ) { + Main.setWindowDeactivated( Main.WINDOW_MASK_SCREEN ); + if( this.emuThread != null ) { + this.emuThread.keyReleased(); + } + } + } + @Override public void windowOpened( WindowEvent e ) @@ -1508,6 +1575,10 @@ else if( actionCmd.equals( "file.screen.text.copy" ) ) { rv = true; doFileScreenTextCopy(); } + else if( actionCmd.equals( "file.find" ) ) { + rv = true; + FindFilesFrm.open( this ); + } else if( actionCmd.equals( "file.browser" ) ) { rv = true; FileBrowserFrm.open( this ); @@ -1588,6 +1659,10 @@ else if( actionCmd.equals( "extra.imageviewer" ) ) { rv = true; ImageFrm.open(); } + else if( actionCmd.equals( "extra.audiorecorder" ) ) { + rv = true; + AudioRecorderFrm.open(); + } else if( actionCmd.equals( "extra.debugger" ) ) { rv = true; doExtraDebugger(); @@ -1636,6 +1711,10 @@ else if( actionCmd.equals( "extra.diskimage.write_to_disk" ) ) { rv = true; DiskImgProcessDlg.writeDiskImageToDrive( this ); } + else if( actionCmd.equals( "extra.diskviewer" ) ) { + rv = true; + DiskImgViewFrm.open(); + } else if( actionCmd.equals( "extra.settings" ) ) { rv = true; SettingsFrm.open( this ); @@ -1740,9 +1819,10 @@ public boolean doQuit() } } - // Emulator-Thread beenden + // Threads beenden if( this.emuThread != null ) { this.emuThread.stopEmulator(); + Main.getThreadGroup().interrupt(); // max. eine halbe Sekunde auf Thread-Beendigung warten try { @@ -1771,16 +1851,16 @@ public void putSettingsTo( Properties props ) { String prefix = getSettingsPrefix(); props.setProperty( - prefix + ".window.x", + prefix + BasicFrm.PROP_WINDOW_X, String.valueOf( this.windowLocation.x ) ); props.setProperty( - prefix + ".window.y", + prefix + BasicFrm.PROP_WINDOW_Y, String.valueOf( this.windowLocation.y ) ); props.setProperty( - prefix + ".window.width", + prefix + BasicFrm.PROP_WINDOW_WIDTH, String.valueOf( this.windowSize.width ) ); props.setProperty( - prefix + ".window.height", + prefix + BasicFrm.PROP_WINDOW_HEIGHT, String.valueOf( this.windowSize.height ) ); } props.setProperty( @@ -1826,34 +1906,36 @@ protected boolean showPopup( MouseEvent e ) private void doFileLoad( boolean startEnabled ) { - File file = null; boolean startSelected = false; boolean loadWithOptions = true; - if( EmuUtil.isNativeFileDialogSelected() ) { - file = EmuUtil.showNativeFileDlg( - this, - false, - "Datei in Arbeitsspeicher laden", - Main.getLastPathFile( "software" ) ); - } else { + File file = null; + File preSelection = EmuUtil.getDirectory( + Main.getLastDirFile( "software" ) ); + if( EmuUtil.isJKCEMUFileDialogSelected() ) { FileSelectDlg dlg = new FileSelectDlg( this, - false, // forSave + FileSelectDlg.Mode.LOAD, startEnabled, - true, // loadWithOptionsEnabled + true, // loadWithOptionsEnabled "Datei in Arbeitsspeicher laden", - Main.getLastPathFile( "software" ), + preSelection, EmuUtil.getBinaryFileFilter(), + EmuUtil.getAC1Basic6FileFilter(), + EmuUtil.getBasicFileFilter(), EmuUtil.getKCSystemFileFilter(), EmuUtil.getKCBasicFileFilter(), - EmuUtil.getRBasicFileFilter(), - EmuUtil.getTapFileFilter(), + EmuUtil.getTapeFileFilter(), EmuUtil.getHeadersaveFileFilter(), EmuUtil.getHexFileFilter() ); dlg.setVisible( true ); file = dlg.getSelectedFile(); loadWithOptions = dlg.isLoadWithOptionsSelected(); startSelected = dlg.isStartSelected(); + } else { + file = EmuUtil.showFileOpenDlg( + this, + "Datei in Arbeitsspeicher laden", + preSelection ); } if( file != null ) { LoadDlg.loadFile( @@ -1869,15 +1951,13 @@ private void doFileLoad( boolean startEnabled ) private void doFileSave() { - SaveDlg dlg = new SaveDlg( - this, - -1, // Anfangsadresse - -1, // Endadresse - -1, // Dateityp - false, // KC-BASIC - false, // RBASIC - "Programm/Adressbereich speichern" ); - dlg.setVisible( true ); + (new SaveDlg( + this, + -1, // Anfangsadresse + -1, // Endadresse + "Programm/Adressbereich speichern", + SaveDlg.BasicType.NO_BASIC, + null )).setVisible( true ); } @@ -1936,7 +2016,7 @@ private void doFileScreenTextSave() File file = EmuUtil.showFileSaveDlg( this, "Textdatei speichern", - Main.getLastPathFile( "screen" ), + Main.getLastDirFile( "screen" ), EmuUtil.getTextFileFilter() ); if( file != null ) { String fileName = file.getPath(); @@ -2210,7 +2290,7 @@ private void doExtraDiskImgUnpack() File file = EmuUtil.showFileOpenDlg( this, "CP/M-Diskettenabbilddatei entpacken", - Main.getLastPathFile( "disk" ), + Main.getLastDirFile( "disk" ), EmuUtil.getPlainDiskFileFilter(), EmuUtil.getAnaDiskFileFilter(), EmuUtil.getCopyQMFileFilter(), @@ -2233,7 +2313,8 @@ private void doExtraDiskImgUnpack() } else { AbstractFloppyDisk disk = DiskUtil.readNonPlainDiskFile( this, - file ); + file, + true ); if( disk != null ) { if( DiskUtil.checkAndConfirmWarning( this, disk ) ) { DiskUtil.unpackDisk( this, file, disk, true ); @@ -2499,6 +2580,20 @@ private void doHelpSystem() /* --- private Methoden --- */ + private void appendMsg( String msg ) + { + if( this.msgFrm != null ) { + this.msgFrm.setVisible( true ); + this.msgFrm.setState( Frame.NORMAL ); + } else { + this.msgFrm = new MsgFrm( this ); + this.msgFrm.setVisible( true ); + } + this.msgFrm.toFront(); + this.msgFrm.appendMsg( msg ); + } + + private int askAccessToSysNum( String sysName ) { int rv = -1; @@ -2724,6 +2819,13 @@ private void pasteText( String text ) EmuSys emuSys = getEmuSys(); if( (emuSys != null) && (text != null) ) { if( !text.isEmpty() ) { + // ggf. Zeichen mappen + if( text.indexOf( '\r' ) >= 0 ) { + text = text.replace( '\r', '\n' ); + } + if( text.indexOf( '\u00A0' ) >= 0 ) { + text = text.replace( '\u00A0', '\u0020' ); + } this.mnuEditPasteCancel.setEnabled( true ); updPasteBtns(); emuSys.startPastingText( text ); @@ -2745,6 +2847,12 @@ private void refreshStatus() } else { msg = "Pause"; } + EmuSys emuSys = getEmuSys(); + if( emuSys != null ) { + emuSys.updDebugScreen(); + } + this.screenDirty = true; + this.screenFld.repaint(); } else { String mhzText = createMHzText( z80cpu ); if( mhzText != null ) { diff --git a/src/jkcemu/base/SettingsFrm.java b/src/jkcemu/base/SettingsFrm.java index 570c1f9..759c399 100644 --- a/src/jkcemu/base/SettingsFrm.java +++ b/src/jkcemu/base/SettingsFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -28,6 +28,7 @@ import jkcemu.emusys.poly880.Poly880SettingsFld; import jkcemu.emusys.z1013.Z1013SettingsFld; import jkcemu.emusys.z9001.Z9001SettingsFld; +import jkcemu.emusys.zxspectrum.ZXSpectrumSettingsFld; import jkcemu.net.KCNetSettingsFld; @@ -61,7 +62,7 @@ public class SettingsFrm extends BasicFrm private JCheckBox btnConfirmQuit; private JCheckBox btnClearRFsOnPowerOn; private JCheckBox btnReloadROMsOnPowerOn; - private JComboBox comboScreenRefresh; + private JComboBox comboScreenRefresh; private CardLayout cardLayoutSysOpt; private String curSysOptCard; private JRadioButton btnSysA5105; @@ -82,6 +83,7 @@ public class SettingsFrm extends BasicFrm private JRadioButton btnSysLC80; private JRadioButton btnSysLLC1; private JRadioButton btnSysLLC2; + private JRadioButton btnSysNANOS; private JRadioButton btnSysPCM; private JRadioButton btnSysPoly880; private JRadioButton btnSysSC2; @@ -90,12 +92,6 @@ public class SettingsFrm extends BasicFrm private JRadioButton btnSysZ1013; private JRadioButton btnSysZ9001; private JRadioButton btnSysZXSpectrum; - private JRadioButton btnBCS3se24_27; - private JRadioButton btnBCS3se31_29; - private JRadioButton btnBCS3se31_40; - private JRadioButton btnBCS3sp33_29; - private JRadioButton btnBCS3Ram1k; - private JRadioButton btnBCS3Ram17k; private JCheckBox btnHEMCCatchPrintCalls; private JCheckBox btnKramerMCCatchPrintCalls; private JRadioButton btnLC80_U505; @@ -105,6 +101,7 @@ public class SettingsFrm extends BasicFrm private A5105SettingsFld a5105SettingsFld; private AC1SettingsFld ac1SettingsFld; private LLC2SettingsFld llc2SettingsFld; + private BCS3SettingsFld bcs3SettingsFld; private HueblerGraphicsMCSettingsFld hgmcSettingsFld; private KC85SettingsFld hc900SettingsFld; private Z9001SettingsFld kc85_1_SettingsFld; @@ -114,11 +111,13 @@ public class SettingsFrm extends BasicFrm private KC85SettingsFld kc85_5_SettingsFld; private KCcompactSettingsFld kcCompactSettingsFld; private LC80SettingsFld lc80SettingsFld; + private NANOSSettingsFld nanosSettingsFld; private PCMSettingsFld pcmSettingsFld; private Poly880SettingsFld poly880SettingsFld; private Z1013SettingsFld z1013SettingsFld; private Z9001SettingsFld kc87SettingsFld; private Z9001SettingsFld z9001SettingsFld; + private ZXSpectrumSettingsFld zxSpectrumSettingsFld; private JRadioButton btnSpeedDefault; private JRadioButton btnSpeedValue; private JRadioButton btnSRAMInit00; @@ -265,6 +264,7 @@ else if( (src == this.btnSysA5105) || (src == this.btnSysLC80) || (src == this.btnSysLLC1) || (src == this.btnSysLLC2) + || (src == this.btnSysNANOS) || (src == this.btnSysPCM) || (src == this.btnSysPoly880) || (src == this.btnSysSC2) @@ -319,6 +319,7 @@ public void lookAndFeelChanged() this.z1013SettingsFld.lookAndFeelChanged(); this.kc87SettingsFld.lookAndFeelChanged(); this.z9001SettingsFld.lookAndFeelChanged(); + this.zxSpectrumSettingsFld.lookAndFeelChanged(); } @@ -603,7 +604,7 @@ private SettingsFrm( ScreenFrm screenFrm ) Main.updIcon( this ); this.screenFrm = screenFrm; this.emuThread = screenFrm.getEmuThread(); - this.lafClass2Button = new Hashtable(); + this.lafClass2Button = new HashMap<>(); this.profileFile = Main.getProfileFile(); this.fmtSpeed = NumberFormat.getNumberInstance(); if( this.fmtSpeed instanceof DecimalFormat ) { @@ -720,29 +721,29 @@ private SettingsFrm( ScreenFrm screenFrm ) this.btnSysKC87 = new JRadioButton( "KC87", false ); this.btnSysKC87.addActionListener( this ); grpSys.add( this.btnSysKC87 ); - gbcSys.insets.bottom = 5; gbcSys.gridy++; panelSys.add( this.btnSysKC87, gbcSys ); this.btnSysKCcompact = new JRadioButton( "KC compact", false ); this.btnSysKCcompact.addActionListener( this ); grpSys.add( this.btnSysKCcompact ); - gbcSys.insets.top = 5; - gbcSys.insets.bottom = 0; - gbcSys.gridy = 0; - gbcSys.gridx++; + gbcSys.insets.bottom = 5; + gbcSys.gridy++; panelSys.add( this.btnSysKCcompact, gbcSys ); this.btnSysKramerMC = new JRadioButton( "Kramer-MC", false ); this.btnSysKramerMC.addActionListener( this ); grpSys.add( this.btnSysKramerMC ); - gbcSys.insets.top = 0; - gbcSys.gridy++; + gbcSys.insets.top = 5; + gbcSys.insets.bottom = 0; + gbcSys.gridy = 0; + gbcSys.gridx++; panelSys.add( this.btnSysKramerMC, gbcSys ); this.btnSysLC80 = new JRadioButton( "LC-80", false ); this.btnSysLC80.addActionListener( this ); grpSys.add( this.btnSysLC80 ); + gbcSys.insets.top = 0; gbcSys.gridy++; panelSys.add( this.btnSysLC80, gbcSys ); @@ -758,6 +759,12 @@ private SettingsFrm( ScreenFrm screenFrm ) gbcSys.gridy++; panelSys.add( this.btnSysLLC2, gbcSys ); + this.btnSysNANOS = new JRadioButton( "NANOS", false ); + this.btnSysNANOS.addActionListener( this ); + grpSys.add( this.btnSysNANOS ); + gbcSys.gridy++; + panelSys.add( this.btnSysNANOS, gbcSys ); + this.btnSysPCM = new JRadioButton( "PC/M", false ); this.btnSysPCM.addActionListener( this ); grpSys.add( this.btnSysPCM ); @@ -803,7 +810,6 @@ private SettingsFrm( ScreenFrm screenFrm ) this.btnSysZXSpectrum = new JRadioButton( "ZX Spectrum", false ); this.btnSysZXSpectrum.addActionListener( this ); grpSys.add( this.btnSysZXSpectrum ); - gbcSys.insets.bottom = 5; gbcSys.gridy++; panelSys.add( this.btnSysZXSpectrum, gbcSys ); @@ -853,71 +859,8 @@ private SettingsFrm( ScreenFrm screenFrm ) // Optionen fuer BCS3 - JPanel panelBCS3 = new JPanel( new GridBagLayout() ); - this.panelSysOpt.add( panelBCS3, "BCS3" ); - - GridBagConstraints gbcBCS3 = new GridBagConstraints( - 0, 0, - 1, 1, - 0.0, 0.0, - GridBagConstraints.NORTHWEST, - GridBagConstraints.NONE, - new Insets( 5, 5, 0, 5 ), - 0, 0 ); - - ButtonGroup grpBCS3os = new ButtonGroup(); - - this.btnBCS3se24_27 = new JRadioButton( - "2 KByte BASIC-SE 2.4, 2,5 MHz, 27 Zeichen pro Zeile", - true ); - this.btnBCS3se24_27.addActionListener( this ); - grpBCS3os.add( this.btnBCS3se24_27 ); - panelBCS3.add( this.btnBCS3se24_27, gbcBCS3 ); - - this.btnBCS3se31_29 = new JRadioButton( - "4 KByte BASIC-SE 3.1, 2,5 MHz, 29 Zeichen pro Zeile", - false ); - this.btnBCS3se31_29.addActionListener( this ); - grpBCS3os.add( this.btnBCS3se31_29 ); - gbcBCS3.insets.top = 0; - gbcBCS3.gridy++; - panelBCS3.add( this.btnBCS3se31_29, gbcBCS3 ); - - this.btnBCS3se31_40 = new JRadioButton( - "4 KByte BASIC-SE 3.1, 3,5 MHz, 40 Zeichen pro Zeile", - false ); - this.btnBCS3se31_40.addActionListener( this ); - grpBCS3os.add( this.btnBCS3se31_40 ); - gbcBCS3.insets.top = 0; - gbcBCS3.gridy++; - panelBCS3.add( this.btnBCS3se31_40, gbcBCS3 ); - - this.btnBCS3sp33_29 = new JRadioButton( - "4 KByte S/P-BASIC V3.3, 2,5 MHz, 29 Zeichen pro Zeile", - false ); - this.btnBCS3sp33_29.addActionListener( this ); - grpBCS3os.add( this.btnBCS3sp33_29 ); - gbcBCS3.gridy++; - panelBCS3.add( this.btnBCS3sp33_29, gbcBCS3 ); - - ButtonGroup grpBCS3ram = new ButtonGroup(); - - this.btnBCS3Ram1k = new JRadioButton( "1 KByte RAM", true ); - this.btnBCS3Ram1k.addActionListener( this ); - grpBCS3ram.add( this.btnBCS3Ram1k ); - gbcBCS3.insets.top = 10; - gbcBCS3.gridy++; - panelBCS3.add( this.btnBCS3Ram1k, gbcBCS3 ); - - this.btnBCS3Ram17k = new JRadioButton( - "17 KByte RAM (16 KByte RAM-Erweiterung)", - false ); - this.btnBCS3Ram17k.addActionListener( this ); - grpBCS3ram.add( this.btnBCS3Ram17k ); - gbcBCS3.insets.top = 0; - gbcBCS3.insets.bottom = 5; - gbcBCS3.gridy++; - panelBCS3.add( this.btnBCS3Ram17k, gbcBCS3 ); + this.bcs3SettingsFld = new BCS3SettingsFld( this, "jkcemu.bcs3." ); + this.panelSysOpt.add( this.bcs3SettingsFld, "BCS3" ); // Optionen fuer HC900 @@ -1017,6 +960,11 @@ private SettingsFrm( ScreenFrm screenFrm ) this.panelSysOpt.add( this.llc2SettingsFld, "LLC2" ); + // Optionen fuer NANOS + this.nanosSettingsFld = new NANOSSettingsFld( this, "jkcemu.nanos." ); + this.panelSysOpt.add( this.nanosSettingsFld, "NANOS" ); + + // Optionen fuer PC/M this.pcmSettingsFld = new PCMSettingsFld( this, "jkcemu.pcm." ); this.panelSysOpt.add( this.pcmSettingsFld, "PC/M" ); @@ -1042,6 +990,13 @@ private SettingsFrm( ScreenFrm screenFrm ) this.panelSysOpt.add( this.z9001SettingsFld, "Z9001" ); + // Optionen fuer ZXSpectrum + this.zxSpectrumSettingsFld = new ZXSpectrumSettingsFld( + this, + "jkcemu.zxspectrum." ); + this.panelSysOpt.add( this.zxSpectrumSettingsFld, "ZXSpectrum" ); + + // Bereich Geschwindigkeit this.tabSpeed = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Geschwindigkeit", this.tabSpeed ); @@ -1151,7 +1106,7 @@ private SettingsFrm( ScreenFrm screenFrm ) gbcScreen.gridy++; this.tabScreen.add( new JLabel( "Aktualisierungszyklus:" ), gbcScreen ); - this.comboScreenRefresh = new JComboBox(); + this.comboScreenRefresh = new JComboBox<>(); this.comboScreenRefresh.setEditable( false ); this.comboScreenRefresh.addItem( "10" ); this.comboScreenRefresh.addItem( "20" ); @@ -1657,6 +1612,9 @@ else if( this.btnSysLLC1.isSelected() ) { else if( this.btnSysLLC2.isSelected() ) { valueSys = "LLC2"; } + else if( this.btnSysNANOS.isSelected() ) { + valueSys = "NANOS"; + } else if( this.btnSysPCM.isSelected() ) { valueSys = "PC/M"; } @@ -1690,24 +1648,7 @@ else if( this.btnSysZXSpectrum.isSelected() ) { this.ac1SettingsFld.applyInput( props, valueSys.equals( "AC1" ) ); // Optionen fuer BCS3 - if( this.btnBCS3se31_29.isSelected() ) { - props.setProperty( "jkcemu.bcs3.os.version", "3.1" ); - props.setProperty( "jkcemu.bcs3.chars_per_line", "29" ); - } else if( this.btnBCS3se31_40.isSelected() ) { - props.setProperty( "jkcemu.bcs3.os.version", "3.1" ); - props.setProperty( "jkcemu.bcs3.chars_per_line", "40" ); - } else if( this.btnBCS3sp33_29.isSelected() ) { - props.setProperty( "jkcemu.bcs3.os.version", "3.3" ); - props.setProperty( "jkcemu.bcs3.chars_per_line", "29" ); - } else { - props.setProperty( "jkcemu.bcs3.os.version", "2.4" ); - props.setProperty( "jkcemu.bcs3.chars_per_line", "27" ); - } - if( this.btnBCS3Ram17k.isSelected() ) { - props.setProperty( "jkcemu.bcs3.ram.kbyte", "17" ); - } else { - props.setProperty( "jkcemu.bcs3.ram.kbyte", "1" ); - } + this.bcs3SettingsFld.applyInput( props, valueSys.equals( "BCS3" ) ); // Optionen fuer HC900 this.hc900SettingsFld.applyInput( props, valueSys.equals( "HC900" ) ); @@ -1751,6 +1692,9 @@ else if( this.btnSysZXSpectrum.isSelected() ) { // Optionen fuer LLC2 this.llc2SettingsFld.applyInput( props, valueSys.equals( "LLC2" ) ); + // Optionen fuer NANOS + this.nanosSettingsFld.applyInput( props, valueSys.equals( "NANOS" ) ); + // Optionen fuer PC/M this.pcmSettingsFld.applyInput( props, valueSys.equals( "PC/M" ) ); @@ -1764,6 +1708,11 @@ else if( this.btnSysZXSpectrum.isSelected() ) { // Optionen fuer Z9001 this.z9001SettingsFld.applyInput( props, valueSys.equals( "Z9001" ) ); + + // Optionen fuer ZXSpectrum + this.zxSpectrumSettingsFld.applyInput( + props, + valueSys.equals( "ZXSpectrum" ) ); } @@ -1920,6 +1869,9 @@ else if( sysName.startsWith( "LLC1" ) ) { else if( sysName.startsWith( "LLC2" ) ) { this.btnSysLLC2.setSelected( true ); } + else if( sysName.startsWith( "NANOS" ) ) { + this.btnSysNANOS.setSelected( true ); + } else if( sysName.startsWith( "PC/M" ) ) { this.btnSysPCM.setSelected( true ); } @@ -1954,31 +1906,7 @@ else if( sysName.startsWith( "ZXSpectrum" ) ) { this.ac1SettingsFld.updFields( props ); // Optionen fuer BCS3 - String bcs3Version = EmuUtil.getProperty( - props, - "jkcemu.bcs3.os.version" ); - if( bcs3Version.equals( "3.1" ) ) { - if( EmuUtil.getProperty( - props, - "jkcemu.bcs3.chars_per_line" ).equals( "40" ) ) - { - this.btnBCS3se31_40.setSelected( true ); - } else { - this.btnBCS3se31_29.setSelected( true ); - } - } else if( bcs3Version.equals( "3.3" ) ) { - this.btnBCS3sp33_29.setSelected( true ); - } else { - this.btnBCS3se24_27.setSelected( true ); - } - if( EmuUtil.getProperty( - props, - "jkcemu.bcs3.ram.kbyte" ).equals( "17" ) ) - { - this.btnBCS3Ram17k.setSelected( true ); - } else { - this.btnBCS3Ram1k.setSelected( true ); - } + this.bcs3SettingsFld.updFields( props ); // Optionen fuer HC900 this.hc900SettingsFld.updFields( props ); @@ -2016,6 +1944,9 @@ else if( sysName.startsWith( "ZXSpectrum" ) ) { // Optionen fuer LLC2 this.llc2SettingsFld.updFields( props ); + // Optionen fuer NANOS + this.nanosSettingsFld.updFields( props ); + // Optionen fuer PC/M this.pcmSettingsFld.updFields( props ); @@ -2031,6 +1962,9 @@ else if( sysName.startsWith( "ZXSpectrum" ) ) { // Optionen fuer Z9001 this.z9001SettingsFld.updFields( props ); + // Optionen fuer ZXSpectrum + this.zxSpectrumSettingsFld.updFields( props ); + // Optionen anpassen updSysOptCard(); @@ -2217,6 +2151,9 @@ else if( this.btnSysKramerMC.isSelected() ) { else if( this.btnSysLC80.isSelected() ) { cardName = "LC80"; } + else if( this.btnSysNANOS.isSelected() ) { + cardName = "NANOS"; + } else if( this.btnSysPCM.isSelected() ) { cardName = "PC/M"; } @@ -2232,6 +2169,9 @@ else if( this.btnSysZ1013.isSelected() ) { else if( this.btnSysZ9001.isSelected() ) { cardName = "Z9001"; } + else if( this.btnSysZXSpectrum.isSelected() ) { + cardName = "ZXSpectrum"; + } this.cardLayoutSysOpt.show( this.panelSysOpt, cardName ); this.curSysOptCard = cardName; } diff --git a/src/jkcemu/base/SourceUtil.java b/src/jkcemu/base/SourceUtil.java index ee1ef43..bf72f12 100644 --- a/src/jkcemu/base/SourceUtil.java +++ b/src/jkcemu/base/SourceUtil.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -10,13 +10,12 @@ import java.awt.Component; import java.lang.*; -import z80emu.Z80MemView; public class SourceUtil { public static String getEDAS4Text( - Z80MemView memory, + EmuMemView memory, int addr ) { String rv = null; @@ -52,45 +51,55 @@ public static String getEDAS4Text( } - public static int getKCBasicStyleEndAddr( Z80MemView memory, int begAddr ) + public static int getBasicEndAddr( EmuMemView memory, int begAddr ) { int endAddr = -1; int curLineAddr = begAddr; - int nextLineAddr = memory.getMemWord( curLineAddr ); + int nextLineAddr = EmuUtil.getBasicMemWord( memory, curLineAddr ); while( (nextLineAddr > curLineAddr + 5) - && (memory.getMemByte( nextLineAddr - 1, false ) == 0) ) + && (memory.getBasicMemByte( nextLineAddr - 1 ) == 0) ) { curLineAddr = nextLineAddr; - nextLineAddr = memory.getMemWord( curLineAddr ); + nextLineAddr = EmuUtil.getBasicMemWord( memory, curLineAddr ); } if( curLineAddr > begAddr ) { - endAddr = curLineAddr + 2; + endAddr = curLineAddr + 1; } return endAddr; } - public static String getKCBasicStyleProgram( - Z80MemView memory, + public static String getBasicProgram( + EmuMemView memory, int addr, String[] tokens ) + { + return getBasicProgram( memory, addr, tokens, null ); + } + + + public static String getBasicProgram( + EmuMemView memory, + int addr, + String[] tokens, + String[] tokensFF ) { StringBuilder buf = new StringBuilder( 0x4000 ); - int nextLineAddr = memory.getMemWord( addr ); + int nextLineAddr = EmuUtil.getBasicMemWord( memory, addr ); while( (nextLineAddr > addr + 5) - && (memory.getMemByte( nextLineAddr - 1, false ) == 0) ) + && (memory.getBasicMemByte( nextLineAddr - 1 ) == 0) ) { // Zeilennummer addr += 2; - buf.append( memory.getMemWord( addr ) ); + buf.append( EmuUtil.getBasicMemWord( memory, addr ) ); addr += 2; // Anzahl Leerzeichen vor der Anweisung ermitteln boolean sep = true; int n = 0; while( addr < nextLineAddr ) { - int ch = memory.getMemByte( addr, false ); + int ch = memory.getBasicMemByte( addr ); if( ch == '\u0020' ) { n++; addr++; @@ -107,30 +116,35 @@ public static String getKCBasicStyleProgram( // Programmzeile extrahieren while( addr < nextLineAddr ) { - int ch = memory.getMemByte( addr++, false ); - if( ch == 0 ) { + int b = memory.getBasicMemByte( addr++ ); + if( b == 0 ) { break; } - if( ch == '\"' ) { + if( b == '\"' ) { if( sep ) { buf.append( (char) '\u0020' ); } - buf.append( (char) ch ); + buf.append( (char) b ); while( addr < nextLineAddr ) { - ch = memory.getMemByte( addr++, false ); - if( ch == 0 ) { + b = memory.getBasicMemByte( addr++ ); + if( b == 0 ) { break; } - buf.append( (char) ch ); - if( ch == '\"' ) { + buf.append( (char) b ); + if( b == '\"' ) { break; } } } else { - if( ch >= 0x80 ) { - int pos = ch - 0x80; - if( (pos >= 0) && (pos < tokens.length) ) { - String s = tokens[ pos ]; + String[] tmpTokens = tokens; + if( b == 0xFF ) { + b = memory.getBasicMemByte( addr++ ); + tmpTokens = tokensFF; + } + if( b >= 0x80 ) { + int pos = b - 0x80; + if( (pos >= 0) && (pos < tmpTokens.length) ) { + String s = tmpTokens[ pos ]; if( s != null ) { int len = s.length(); if( len > 0 ) { @@ -146,17 +160,17 @@ && isIdentifierChar( s.charAt( 0 ) ) ) sep = false; } } - ch = 0; + b = 0; } } } - if( ch > 0 ) { + if( b > 0 ) { if( sep - && (isIdentifierChar( ch ) || (ch == '\'') || (ch == '\"')) ) + && (isIdentifierChar( b ) || (b == '\'') || (b == '\"')) ) { buf.append( (char) '\u0020' ); } - buf.append( (char) ch ); + buf.append( (char) b ); sep = false; } } @@ -165,14 +179,14 @@ && isIdentifierChar( s.charAt( 0 ) ) ) // naechste Zeile addr = nextLineAddr; - nextLineAddr = memory.getMemWord( addr ); + nextLineAddr = EmuUtil.getBasicMemWord( memory, addr ); } return buf.length() > 0 ? buf.toString() : null; } public static String getTinyBasicProgram( - Z80MemView memory, + EmuMemView memory, int begAddr, int endAddr ) { @@ -184,13 +198,13 @@ public static String getTinyBasicProgram( // Tiny-BASIC-Programm extrahieren int addr = begAddr; while( addr < endAddr - 1 ) { - buf.append( memory.getMemWord( addr ) ); + buf.append( EmuUtil.getBasicMemWord( memory, addr ) ); addr += 2; // Anzahl Leerzeichen vor der Anweisung ermitteln int n = 0; while( addr < endAddr ) { - int ch = memory.getMemByte( addr, false ); + int ch = memory.getBasicMemByte( addr ); if( ch == '\u0020' ) { n++; addr++; @@ -205,7 +219,7 @@ public static String getTinyBasicProgram( // Zeile ausgeben while( addr < endAddr ) { - int ch = memory.getMemByte( addr++, false ); + int ch = memory.getBasicMemByte( addr++ ); if( ch == '\r' ) { break; } @@ -223,26 +237,24 @@ public static String getTinyBasicProgram( } - public static void openKCBasicStyleProgram( + public static void openKCBasicProgram( ScreenFrm screenFrm, int begAddr, String[] tokens ) { + EmuMemView memory = screenFrm.getEmuThread(); if( begAddr == 0x0401 ) { - int tmpAddr = screenFrm.getEmuThread().getMemWord( 0x035F ); + int tmpAddr = EmuUtil.getBasicMemWord( memory, 0x035F ); if( tmpAddr > 0 ) { begAddr = tmpAddr; } } else if( begAddr == 0x2C01 ) { - int tmpAddr = screenFrm.getEmuThread().getMemWord( 0x2B5F ); + int tmpAddr = EmuUtil.getBasicMemWord( memory, 0x2B5F ); if( tmpAddr > 0 ) { begAddr = tmpAddr; } } - String text = getKCBasicStyleProgram( - screenFrm.getEmuThread(), - begAddr, - tokens ); + String text = getBasicProgram( memory, begAddr, tokens ); if( text != null ) { Component owner = screenFrm.openText( text ); if( (owner != null) && (begAddr != 0x0401) && (begAddr != 0x2C01) ) { @@ -252,37 +264,37 @@ public static void openKCBasicStyleProgram( + "des standardm\u00E4\u00DFigen Adressbereichs." ); } } else { - showNoKCBasic( screenFrm ); + showNoBasic( screenFrm ); } } - public static void saveKCBasicStyleProgram( + public static void saveKCBasicProgram( ScreenFrm screenFrm, int begAddr ) { + EmuMemView memory = screenFrm.getEmuThread(); if( begAddr == 0x0401 ) { - int tmpAddr = screenFrm.getEmuThread().getMemWord( 0x035F ); + int tmpAddr = EmuUtil.getBasicMemWord( memory, 0x035F ); if( tmpAddr > 0 ) { begAddr = tmpAddr; } } else if( begAddr == 0x2C01 ) { - int tmpAddr = screenFrm.getEmuThread().getMemWord( 0x2B5F ); + int tmpAddr = EmuUtil.getBasicMemWord( memory, 0x2B5F ); if( tmpAddr > 0 ) { begAddr = tmpAddr; } } - int endAddr = getKCBasicStyleEndAddr( screenFrm.getEmuThread(), begAddr ); + int endAddr = getBasicEndAddr( memory, begAddr ); if( endAddr >= begAddr ) { if( (begAddr == 0x0401) || (begAddr == 0x2C01) ) { (new SaveDlg( screenFrm, - begAddr - 0x41, + begAddr, endAddr, - 'B', - true, // KC-BASIC - false, // kein RBASIC - "KC-BASIC-Programm speichern" )).setVisible( true ); + "KC-BASIC-Programm speichern", + SaveDlg.BasicType.KCBASIC, + EmuUtil.getKCBasicFileFilter() )).setVisible( true ); } else { BasicDlg.showErrorDlg( screenFrm, @@ -293,7 +305,7 @@ public static void saveKCBasicStyleProgram( + " gespeichert werden." ); } } else { - showNoKCBasic( screenFrm ); + showNoBasic( screenFrm ); } } @@ -306,26 +318,40 @@ public static void updKCBasicSysCells( int fileType ) { if( fileFmt != null ) { - boolean state = false; - if( ((begAddr == 0x2BC0) || (begAddr == 0x2C00) || (begAddr == 0x2C01)) - && fileFmt.equals( FileInfo.HEADERSAVE ) - && (fileType == 'B') ) + int basicBegAddr = -1; + if( ((fileFmt.equals( FileFormat.KCBASIC_HEAD_PRG ) + || fileFmt.equals( FileFormat.KCBASIC_PRG ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ) + || fileFmt.equals( FileFormat.BASIC_PRG )) + && (begAddr == 0x0401) + && (len > 7)) + || (fileFmt.equals( FileFormat.HEADERSAVE ) + && (fileType == 'B') + && (begAddr <= 0x0401) + && ((begAddr + len) > 0x0407)) ) { - state = true; + basicBegAddr = 0x0401; } - else if( ((begAddr == 0x0401) || (begAddr == 0x2C01)) - && (fileFmt.equals( FileInfo.KCB ) - || fileFmt.equals( FileInfo.KCBASIC_HEAD_PRG ) - || fileFmt.equals( FileInfo.KCBASIC_PRG ) - || fileFmt.equals( FileInfo.KCTAP_BASIC_PRG )) ) + else if( ((fileFmt.equals( FileFormat.KCBASIC_HEAD_PRG ) + || fileFmt.equals( FileFormat.KCBASIC_PRG ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ) + || fileFmt.equals( FileFormat.BASIC_PRG )) + && (begAddr == 0x2C01) + && (len > 7)) + || (fileFmt.equals( FileFormat.HEADERSAVE ) + && (fileType == 'B') + && (begAddr <= 0x2C01) + && ((begAddr + len) > 0x2C07)) ) { - state = true; + basicBegAddr = 0x2C01; } - if( state ) { - int topAddr = begAddr + len; - emuThread.setMemWord( begAddr - 42, topAddr ); - emuThread.setMemWord( begAddr - 40, topAddr ); - emuThread.setMemWord( begAddr - 38, topAddr ); + if( basicBegAddr >= 0 ) { + int topAddr = getBasicEndAddr( emuThread, basicBegAddr ); + if( topAddr > basicBegAddr ) { + emuThread.setBasicMemWord( begAddr - 42, topAddr ); + emuThread.setBasicMemWord( begAddr - 40, topAddr ); + emuThread.setBasicMemWord( begAddr - 38, topAddr ); + } } } } @@ -341,11 +367,11 @@ private static boolean isIdentifierChar( int ch ) } - private static void showNoKCBasic( Component owner ) + private static void showNoBasic( Component owner ) { BasicDlg.showErrorDlg( owner, - "Es ist kein KC-BASIC-Programm im entsprechenden\n" + "Es ist kein BASIC-Programm im entsprechenden\n" + "Adressbereich des Arbeitsspeichers vorhanden." ); } } diff --git a/src/jkcemu/disk/AbstractFloppyDisk.java b/src/jkcemu/disk/AbstractFloppyDisk.java index d916d3d..8fadc27 100644 --- a/src/jkcemu/disk/AbstractFloppyDisk.java +++ b/src/jkcemu/disk/AbstractFloppyDisk.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -25,14 +25,16 @@ public abstract class AbstractFloppyDisk private volatile String fmtText; private String mediaText; private String warningText; + private int[] sectorIdxMap; protected AbstractFloppyDisk( - Frame owner, - int sides, - int cyls, - int sectorsPerCyl, - int sectorSize ) + Frame owner, + int sides, + int cyls, + int sectorsPerCyl, + int sectorSize, + int interleave ) { this.owner = owner; this.sides = sides; @@ -42,6 +44,34 @@ protected AbstractFloppyDisk( this.fmtText = null; this.mediaText = null; this.warningText = null; + this.sectorIdxMap = null; + if( (interleave > 1) + && (interleave < sectorsPerCyl) + && (sectorsPerCyl > 2) ) + { + this.sectorIdxMap = new int[ sectorsPerCyl ]; + Arrays.fill( this.sectorIdxMap, -1 ); + int srcIdx = 0; + int dstIdx = 0; + while( srcIdx < sectorsPerCyl ) { + while( this.sectorIdxMap[ dstIdx ] >= 0 ) { + dstIdx = (dstIdx + 1) % sectorsPerCyl; + } + this.sectorIdxMap[ dstIdx ] = srcIdx++; + dstIdx = (dstIdx + interleave) % sectorsPerCyl; + } + } + } + + + protected AbstractFloppyDisk( + Frame owner, + int sides, + int cyls, + int sectorsPerCyl, + int sectorSize ) + { + this( owner, sides, cyls, sectorsPerCyl, sectorSize, 0 ); } @@ -379,13 +409,14 @@ public void putSettingsTo( Properties props, String prefix ) } - protected static int readByte( InputStream in ) throws IOException + protected int sectorIndexToInterleave( int sectorIdx ) { - int b = in.read(); - if( b < 0 ) { - throwUnexpectedEOF(); + if( this.sectorIdxMap != null ) { + if( (sectorIdx >= 0) && (sectorIdx < this.sectorIdxMap.length) ) { + sectorIdx = this.sectorIdxMap[ sectorIdx ]; + } } - return b; + return sectorIdx; } diff --git a/src/jkcemu/disk/AnaDisk.java b/src/jkcemu/disk/AnaDisk.java index 6a32ad5..b830901 100644 --- a/src/jkcemu/disk/AnaDisk.java +++ b/src/jkcemu/disk/AnaDisk.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -35,7 +35,7 @@ public static String export( StringBuilder msgBuf = null; OutputStream out = null; try { - out = new FileOutputStream( file ); + out = EmuUtil.createOptionalGZipOutputStream( file ); boolean hasDeleted = false; int sides = disk.getSides(); @@ -205,19 +205,19 @@ public boolean formatTrack( Map> map = null; if( (physHead & 0x01) != 0 ) { if( this.side1 == null ) { - this.side1 = new HashMap>(); + this.side1 = new HashMap<>(); } map = this.side1; } else { if( this.side0 == null ) { - this.side0 = new HashMap>(); + this.side0 = new HashMap<>(); } map = this.side0; } Integer cylObj = new Integer( physCyl ); java.util.List cylSects = map.get( cylObj ); if( cylSects == null ) { - cylSects = new ArrayList( sectorIDs.length ); + cylSects = new ArrayList<>( sectorIDs.length ); map.put( cylObj, cylSects ); } if( cylSects.isEmpty() ) { @@ -241,8 +241,6 @@ public boolean formatTrack( sectorID.getHead(), sectorID.getSectorNum(), sectorID.getSizeCode(), - false, - false, dataBuf, 0, dataBuf.length ); @@ -481,12 +479,12 @@ private static AnaDisk createInstance( Map> map = null; if( physHead == 0 ) { if( side0 == null ) { - side0 = new HashMap>(); + side0 = new HashMap<>(); } map = side0; } else if( physHead == 1 ) { if( side1 == null ) { - side1 = new HashMap>(); + side1 = new HashMap<>(); } map = side1; } @@ -495,9 +493,9 @@ private static AnaDisk createInstance( java.util.List sectors = map.get( keyObj ); if( sectors == null ) { if( (physCyl > 0) && (sectorsPerCyl > 0) ) { - sectors = new ArrayList( sectorsPerCyl ); + sectors = new ArrayList<>( sectorsPerCyl ); } else { - sectors = new ArrayList(); + sectors = new ArrayList<>(); } map.put( keyObj, sectors ); } @@ -523,8 +521,6 @@ private static AnaDisk createInstance( sectHead, sectNum, sectSizeCode, - false, - false, sectBuf, 0, sectBuf != null ? sectBuf.length : 0 ); diff --git a/src/jkcemu/disk/CPCDisk.java b/src/jkcemu/disk/CPCDisk.java index 07095c2..c3baf60 100644 --- a/src/jkcemu/disk/CPCDisk.java +++ b/src/jkcemu/disk/CPCDisk.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -42,7 +42,7 @@ public TrackData( long trackPos, int trackSize ) { this.trackPos = trackPos; this.trackSize = trackSize; - sectors = new ArrayList( MAX_SECTORS_PER_TRACK ); + sectors = new ArrayList<>( MAX_SECTORS_PER_TRACK ); } public void add( SectorData sector ) @@ -88,126 +88,240 @@ public static void export( AbstractFloppyDisk disk, File file ) throws IOException { - OutputStream out = null; - try { - - // Format pruefen - int cyls = disk.getCylinders(); - if( (cyls < 0) || (cyls > 255) ) { - throw new IOException( + // Format pruefen + int cyls = disk.getCylinders(); + if( (cyls < 0) || (cyls > 255) ) { + throw new IOException( String.format( "%d Zylinder nicht unterst\u00FCtzt", cyls ) ); - } - int sides = disk.getSides(); - int sectorsPerCyl = disk.getSectorsPerCylinder(); - if( (sectorsPerCyl < 0) || (sectorsPerCyl > 255) ) { - throw new IOException( + } + int sides = disk.getSides(); + if( (sides < 1) || (sides > 2) ) { + throw new IOException( + String.format( + "%d Seiten nicht unterst\u00FCtzt", + sides ) ); + } + int sectorsPerCyl = disk.getSectorsPerCylinder(); + if( (sectorsPerCyl < 0) || (sectorsPerCyl > 255) ) { + throw new IOException( String.format( "%d Sektoren pro Zylinder nicht unterst\u00FCtzt", sectorsPerCyl ) ); - } - int sectorSize = disk.getSectorSize(); - int sectorSizeCode = getSectorSizeCode( sectorSize ); - - // Datei oeffnen - out = new FileOutputStream( file ); - - // Disk Information Block - int trackSize = 0x0100 + (sectorsPerCyl * sectorSize); - EmuUtil.writeASCII( out, FILE_HEADER_STD ); - EmuUtil.writeFixLengthASCII( out, "JKCEMU", 14, 0 ); - out.write( cyls ); - out.write( sides ); - out.write( trackSize & 0xFF ); - out.write( trackSize >> 8 ); - for( int i = 0; i < 204; i++ ) { - out.write( 0 ); - } - for( int cyl = 0; cyl < cyls; cyl++ ) { - for( int head = 0; head < sides; head++ ) { - int cylSectors = disk.getSectorsOfCylinder( cyl, head ); - if( (cylSectors < 0) || (cylSectors > MAX_SECTORS_PER_TRACK) ) { + } + int sectorSize = disk.getSectorSize(); + int sectorSizeCode = getSectorSizeCode( sectorSize ); + int initBufSize = (disk.getDiskSize() * 5 / 4) + 0x4000; + boolean needsExtFmt = false; + + // Sektoren lesen + SectorData[][] tracks = new SectorData[ cyls * sides ][]; + Arrays.fill( tracks, null ); + int trackIdx = 0; + for( int cyl = 0; cyl < cyls; cyl++ ) { + for( int head = 0; head < sides; head++ ) { + int nTrackSectors = disk.getSectorsOfCylinder( cyl, head ); + if( (nTrackSectors < 0) || (nTrackSectors > MAX_SECTORS_PER_TRACK) ) { throw new IOException( String.format( "Seite %d, Spur %d: %d Sektoren" + " nicht unterst\u00FCtzt", head + 1, cyl, - cylSectors ) ); - } - - // Track Information Block - EmuUtil.writeFixLengthASCII( out, TRACK_HEADER, 16, 0 ); - out.write( cyl ); - out.write( head ); - out.write( 0 ); - out.write( 0 ); - out.write( sectorSizeCode ); - out.write( cylSectors ); - out.write( 0x4E ); // GAP 3 Laenge - out.write( 0xE5 ); // Fuellbyte - for( int i = 0; i < cylSectors; i++ ) { - SectorData sector = disk.getSectorByIndex( cyl, head, i ); - if( sector == null ) { - throw new IOException( + nTrackSectors ) ); + } + if( nTrackSectors != sectorsPerCyl ) { + needsExtFmt = true; + } + SectorData[] trackSectors = new SectorData[ nTrackSectors ]; + for( int i = 0; i < trackSectors.length; i++ ) { + SectorData sector = disk.getSectorByIndex( cyl, head, i ); + if( sector == null ) { + throw new IOException( String.format( "Seite %d, Spur %d: Sektor %d nicht gefunden", head + 1, cyl, i + 1 ) ); - } - if( sector.isDeleted() ) { - throw new IOException( + } + if( sector.isDeleted() ) { + throw new IOException( String.format( "Seite %d, Spur %d: Sektor %d ist als gel\u00F6scht" - + " markiert\n" + + " markiert.\n" + "Gel\u00F6schte Sektoren werden" + " in CPC-Disk-Image-Dateien nicht" + " unterst\u00FCtzt.", head + 1, cyl, sector.getSectorNum() ) ); + } + if( (sector.getSizeCode() != sectorSizeCode) + || (sector.getDataLength() != sectorSize) ) + { + needsExtFmt = true; + } + trackSectors[ i ] = sector; + } + tracks[ trackIdx++ ] = trackSectors; + } + } + + /* + * Datei im Standardformat in temporaeren Puffer schreiben, + * wenn bisher noch kein Grund bekannt ist, + * dass das erweiterte Format verwendet werden muss + */ + if( !needsExtFmt ) { + ByteArrayOutputStream outBuf = new ByteArrayOutputStream( initBufSize ); + try { + + // Disk Information Block + int trackSize = 0x0100 + (sectorsPerCyl * sectorSize); + EmuUtil.writeASCII( outBuf, FILE_HEADER_STD ); + EmuUtil.writeFixLengthASCII( outBuf, "JKCEMU", 14, 0 ); + outBuf.write( cyls ); + outBuf.write( sides ); + outBuf.write( trackSize & 0xFF ); + outBuf.write( trackSize >> 8 ); + for( int i = outBuf.size(); i < 0x100; i++ ) { + outBuf.write( 0 ); + } + for( int i = 0; i < tracks.length; i++ ) { + int cyl = i / sides; + int head = i % sides; + SectorData[] trackSectors = tracks[ i ]; + if( trackSectors.length != sectorsPerCyl ) { + needsExtFmt = true; + break; + } + + // Track Information Block + int trackBegPos = outBuf.size(); + EmuUtil.writeFixLengthASCII( outBuf, TRACK_HEADER, 16, 0 ); + outBuf.write( cyl ); + outBuf.write( head ); + outBuf.write( 0 ); + outBuf.write( 0 ); + outBuf.write( sectorSizeCode ); + outBuf.write( trackSectors.length ); + outBuf.write( 0x4E ); // GAP 3 Laenge + outBuf.write( 0xE5 ); // Fuellbyte + for( SectorData sector : trackSectors ) { + if( sector.getDataLength() != sectorSize ) { + needsExtFmt = true; + break; } - if( sector.getDataLength() > sectorSize ) { - throw new IOException( - String.format( - "Seite %d, Spur %d: Sektor %d ist zu gro\u00DF.", - head + 1, - cyl, - sector.getSectorNum() ) ); - } - out.write( cyl ); - out.write( head ); + outBuf.write( sector.getCylinder() ); + outBuf.write( sector.getHead() ); + outBuf.write( sector.getSectorNum() ); + outBuf.write( sectorSizeCode ); + outBuf.write( 0 ); // FDC Statusregister 1 + outBuf.write( 0 ); // FDC Statusregister 2 + outBuf.write( 0 ); + outBuf.write( 0 ); + } + for( int k = outBuf.size() - trackBegPos; k < 0x100; k++ ) { + outBuf.write( 0 ); + } + for( SectorData sector : trackSectors ) { + int nBytes = 0; + if( sector != null ) { + nBytes = sector.writeTo( outBuf, sectorSize ); + } + for( int k = nBytes; k < sectorSize; k++ ) { + outBuf.write( 0 ); + } + } + } + } + finally { + EmuUtil.doClose( outBuf ); + } + if( !needsExtFmt ) { + OutputStream out = null; + try { + out = EmuUtil.createOptionalGZipOutputStream( file ); + outBuf.writeTo( out ); + out.close(); + out = null; + } + finally { + EmuUtil.doClose( out ); + } + } + } + + /* + * Wenn das Standardformat nicht ausreicht, + * dann das erweiterte Format erzeugen + */ + if( needsExtFmt ) { + OutputStream out = null; + try { + out = EmuUtil.createOptionalGZipOutputStream( file ); + + // Disk Information Block + EmuUtil.writeASCII( out, FILE_HEADER_EXT ); + EmuUtil.writeFixLengthASCII( out, "JKCEMU", 14, 0 ); + out.write( cyls ); + out.write( sides ); + out.write( 0 ); + out.write( 0 ); + for( SectorData[] trackSectors : tracks ) { + int trackSize = 0; + for( SectorData sector : trackSectors ) { + trackSize += sector.getDataLength(); + } + out.write( trackSize > 0 ? ((trackSize + 0x1FF) >> 8) : 0 ); + } + for( int i = 0x34 + tracks.length; i < 0x0100; i++ ) { + out.write( 0 ); + } + + // Tracks + for( int i = 0; i < tracks.length; i++ ) { + int cyl = i / sides; + int head = i % sides; + SectorData[] trackSectors = tracks[ i ]; + + // Track Information Block + EmuUtil.writeFixLengthASCII( out, TRACK_HEADER, 16, 0 ); + out.write( cyl ); + out.write( head ); + out.write( 0 ); + out.write( 0 ); + out.write( sectorSizeCode ); + out.write( trackSectors.length ); + out.write( 0x4E ); // GAP 3 Laenge + out.write( 0xE5 ); // Fuellbyte + for( SectorData sector : trackSectors ) { + out.write( sector.getCylinder() ); + out.write( sector.getHead() ); out.write( sector.getSectorNum() ); out.write( sectorSizeCode ); out.write( 0 ); // FDC Statusregister 1 out.write( 0 ); // FDC Statusregister 2 - out.write( 0 ); - out.write( 0 ); + int dataLen = sector.getDataLength(); + out.write( dataLen & 0xFF ); + out.write( dataLen >> 8 ); } - for( int i = cylSectors; i < MAX_SECTORS_PER_TRACK; i++ ) { - for( int k = 0; k < 8; k++ ) { + for( int k = trackSectors.length; k < MAX_SECTORS_PER_TRACK; k++ ) { + for( int l = 0; l < 8; l++ ) { out.write( 0 ); } } - for( int i = 0; i < cylSectors; i++ ) { - int nBytes = 0; - SectorData sector = disk.getSectorByIndex( cyl, head, i ); - if( sector != null ) { - nBytes = sector.writeTo( out, sectorSize ); - } - for( int k = nBytes; k < sectorSize; k++ ) { - out.write( 0 ); - } + for( SectorData sector : trackSectors ) { + sector.writeTo( out, sector.getDataLength() ); } } + out.close(); + out = null; + } + finally { + EmuUtil.doClose( out ); } - out.close(); - out = null; - } - finally { - EmuUtil.doClose( out ); } } @@ -503,8 +617,6 @@ public boolean formatTrack( sectorID.getHead(), sectorID.getSectorNum(), sectorID.getSizeCode(), - false, - false, dataBuf, 0, dataBuf.length ); @@ -838,8 +950,6 @@ private static CPCDisk createInstance( idHead, idRecord, idSizeCode, - false, - false, trackBuf, dataPos, Math.min( sectorSize, trackBuf.length - dataPos ) ); diff --git a/src/jkcemu/disk/ChangeFileAttrsDlg.java b/src/jkcemu/disk/ChangeFileAttrsDlg.java new file mode 100644 index 0000000..da14cff --- /dev/null +++ b/src/jkcemu/disk/ChangeFileAttrsDlg.java @@ -0,0 +1,206 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Dialog zum Aendern der Dateiattribute + */ + +package jkcemu.disk; + +import java.awt.*; +import java.lang.*; +import java.util.EventObject; +import javax.swing.*; +import jkcemu.base.BasicDlg; + + +public class ChangeFileAttrsDlg extends BasicDlg +{ + private Boolean readOnly; + private Boolean sysFile; + private Boolean archive; + private JRadioButton btnReadOnlyUnchanged; + private JRadioButton btnReadOnlyYes; + private JRadioButton btnReadOnlyNo; + private JRadioButton btnSysFileUnchanged; + private JRadioButton btnSysFileYes; + private JRadioButton btnSysFileNo; + private JRadioButton btnArchiveUnchanged; + private JRadioButton btnArchiveYes; + private JRadioButton btnArchiveNo; + private JButton btnOK; + private JButton btnCancel; + + + public ChangeFileAttrsDlg( Window owner ) + { + super( owner, "Dateiattribute \u00E4ndern" ); + this.readOnly = null; + this.sysFile = null; + this.archive = null; + + + // Fensterinhalt + setLayout( new GridBagLayout() ); + + GridBagConstraints gbc = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.EAST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + // Schreibgeschuetzt + add( new JLabel( "Schreibgesch\u00FCtzt:" ), gbc ); + + ButtonGroup readOnlyGrp = new ButtonGroup(); + + this.btnReadOnlyUnchanged = new JRadioButton( "Nicht \u00E4ndern", true ); + readOnlyGrp.add( this.btnReadOnlyUnchanged ); + gbc.anchor = GridBagConstraints.WEST; + gbc.gridx++; + add( this.btnReadOnlyUnchanged, gbc ); + + this.btnReadOnlyYes = new JRadioButton( "Ja", false ); + readOnlyGrp.add( this.btnReadOnlyYes ); + gbc.gridx++; + add( this.btnReadOnlyYes, gbc ); + + this.btnReadOnlyNo = new JRadioButton( "Nein", false ); + readOnlyGrp.add( this.btnReadOnlyNo ); + gbc.gridx++; + add( this.btnReadOnlyNo, gbc ); + + + // Systemdatei + gbc.anchor = GridBagConstraints.EAST; + gbc.insets.top = 0; + gbc.gridx = 0; + gbc.gridy++; + add( new JLabel( "Systemdatei:" ), gbc ); + + ButtonGroup sysFileGrp = new ButtonGroup(); + + this.btnSysFileUnchanged = new JRadioButton( "Nicht \u00E4ndern", true ); + sysFileGrp.add( this.btnSysFileUnchanged ); + gbc.anchor = GridBagConstraints.WEST; + gbc.gridx++; + add( this.btnSysFileUnchanged, gbc ); + + this.btnSysFileYes = new JRadioButton( "Ja", false ); + sysFileGrp.add( this.btnSysFileYes ); + gbc.gridx++; + add( this.btnSysFileYes, gbc ); + + this.btnSysFileNo = new JRadioButton( "Nein", false ); + sysFileGrp.add( this.btnSysFileNo ); + gbc.gridx++; + add( this.btnSysFileNo, gbc ); + + + // Archiviert + gbc.anchor = GridBagConstraints.EAST; + gbc.gridx = 0; + gbc.gridy++; + add( new JLabel( "Archiv:" ), gbc ); + + ButtonGroup archiveGrp = new ButtonGroup(); + + this.btnArchiveUnchanged = new JRadioButton( "Nicht \u00E4ndern", true ); + archiveGrp.add( this.btnArchiveUnchanged ); + gbc.anchor = GridBagConstraints.WEST; + gbc.gridx++; + add( this.btnArchiveUnchanged, gbc ); + + this.btnArchiveYes = new JRadioButton( "Ja", false ); + archiveGrp.add( this.btnArchiveYes ); + gbc.gridx++; + add( this.btnArchiveYes, gbc ); + + this.btnArchiveNo = new JRadioButton( "Nein", false ); + archiveGrp.add( this.btnArchiveNo ); + gbc.gridx++; + add( this.btnArchiveNo, gbc ); + + + // Knopfe + JPanel panelBtn = new JPanel( new GridLayout( 1, 2, 5, 5 ) ); + gbc.anchor = GridBagConstraints.CENTER; + gbc.insets.top = 20; + gbc.insets.bottom = 10; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx = 0; + gbc.gridy++; + add( panelBtn, gbc ); + + this.btnOK = new JButton( "OK" ); + this.btnOK.addActionListener( this ); + this.btnOK.addKeyListener( this ); + panelBtn.add( this.btnOK ); + + this.btnCancel = new JButton( "Abbrechen" ); + this.btnCancel.addActionListener( this ); + this.btnCancel.addKeyListener( this ); + panelBtn.add( this.btnCancel ); + + pack(); + setParentCentered(); + } + + + public Boolean getArchiveValue() + { + return this.archive; + } + + + public Boolean getReadOnlyValue() + { + return this.readOnly; + } + + + public Boolean getSystemFileValue() + { + return this.sysFile; + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + protected boolean doAction( EventObject e ) + { + boolean rv = false; + if( e != null ) { + Object src = e.getSource(); + if( src == this.btnOK ) { + if( this.btnReadOnlyYes.isSelected() ) { + this.readOnly = Boolean.TRUE; + } else if( this.btnReadOnlyNo.isSelected() ) { + this.readOnly = Boolean.FALSE; + } + if( this.btnSysFileYes.isSelected() ) { + this.sysFile = Boolean.TRUE; + } else if( this.btnSysFileNo.isSelected() ) { + this.sysFile = Boolean.FALSE; + } + if( this.btnArchiveYes.isSelected() ) { + this.archive = Boolean.TRUE; + } else if( this.btnArchiveNo.isSelected() ) { + this.archive = Boolean.FALSE; + } + doClose(); + rv = true; + } + else if( src == this.btnCancel ) { + doClose(); + rv = true; + } + } + return rv; + } +} diff --git a/src/jkcemu/disk/CopyQMDisk.java b/src/jkcemu/disk/CopyQMDisk.java index 9b4ab82..c39acbd 100644 --- a/src/jkcemu/disk/CopyQMDisk.java +++ b/src/jkcemu/disk/CopyQMDisk.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2012 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -17,6 +17,7 @@ import java.util.*; import java.util.zip.GZIPInputStream; import jkcemu.base.*; +import jkcemu.text.CharConverter; public class CopyQMDisk extends AbstractFloppyDisk @@ -87,10 +88,385 @@ public class CopyQMDisk extends AbstractFloppyDisk 0xB3667A2EL, 0xC4614AB8L, 0x5D681B02L, 0x2A6F2B94L, 0xB40BBE37L, 0xC30C8EA1L, 0x5A05DF1BL, 0x2D02EF8DL }; - private String fileName; - private String remark; - private byte[] diskBytes; - private int sectorSizeCode; + private String fileName; + private String remark; + private java.util.Date diskDate; + private byte[] diskBytes; + private int sectorSizeCode; + private int sectorOffset; + private int skew; + + + public static String export( + AbstractFloppyDisk disk, + File file, + String remark ) throws IOException + { + StringBuilder msgBuf = null; + OutputStream out = null; + try { + boolean hasDeleted = false; + int diskSize = disk.getDiskSize(); + int sides = disk.getSides(); + int cyls = disk.getCylinders(); + int sectorsPerCyl = disk.getSectorsPerCylinder(); + int sectorSize = disk.getSectorSize(); + int totalSectorCnt = sides * cyls * sectorsPerCyl; + + // kleinste Sektornummer ermitteln + int minSectorNum = -1; + for( int cyl = 0; (minSectorNum != 1) && (cyl < cyls); cyl++ ) { + for( int head = 0; (minSectorNum != 1) && (head < sides); head++ ) { + int cylSectors = disk.getSectorsOfCylinder( cyl, head ); + for( int i = 0; i < sectorsPerCyl; i++ ) { + SectorData sector = disk.getSectorByIndex( cyl, head, i ); + if( sector != null ) { + int sectorNum = sector.getSectorNum(); + if( (minSectorNum < 0) || (sectorNum < minSectorNum) ) { + minSectorNum = sectorNum; + if( minSectorNum == 1 ) { + break; + } + } + } + } + } + } + + // Interleave ermitteln + int interleave = 1; + int firstSectorNum = -1; + if( cyls > 0 ) { + int cylSectors = disk.getSectorsOfCylinder( 0, 0 ); + if( cylSectors > 2 ) { + SectorData sector = disk.getSectorByIndex( 0, 0, 0 ); + if( sector != null ) { + int nextSectorNum = sector.getSectorNum() + 1; + for( int i = 1; i < cylSectors; i++ ) { + sector = disk.getSectorByIndex( 0, 0, i ); + if( sector != null ) { + if( sector.getSectorNum() == nextSectorNum ) { + interleave = i; + break; + } + } + } + } + } + } + + // Skew ermitteln + int skew = 0; + if( (cyls > 1) && (firstSectorNum > 0) ) { + int cylSectors = disk.getSectorsOfCylinder( 1, 0 ); + if( cylSectors > 1 ) { + for( int i = 0; i < cylSectors; i++ ) { + SectorData sector = disk.getSectorByIndex( 1, 0, i ); + if( sector == null ) { + break; + } + if( sector.getSectorNum() == firstSectorNum ) { + skew = i; + } + } + } + } + + // Datenbereich erzeugen + ByteArrayOutputStream dataBuf = new ByteArrayOutputStream( diskSize ); + for( int cyl = 0; cyl < cyls; cyl++ ) { + for( int head = 0; head < sides; head++ ) { + int cylSectors = disk.getSectorsOfCylinder( cyl, head ); + if( cylSectors != sectorsPerCyl ) { + if( msgBuf == null ) { + msgBuf = new StringBuilder( 1024 ); + } + msgBuf.append( + String.format( + "Seite %d, Spur %d: %d anstelle von %d Sektoren" + + " vorhanden", + head + 1, + cyl, + cylSectors, + sectorsPerCyl ) ); + } + for( int i = 0; i < sectorsPerCyl; i++ ) { + SectorData sector = disk.getSectorByID( + cyl, + head, + cyl, + head, + i + minSectorNum, + -1 ); + if( sector == null ) { + throw new IOException( + String.format( + "Seite %d, Spur %d: Sektor %d nicht gefunden", + head + 1, + cyl, + i + 1 ) ); + } + if( sector.isDeleted() ) { + hasDeleted = true; + if( msgBuf == null ) { + msgBuf = new StringBuilder( 1024 ); + } + msgBuf.append( + String.format( + "Seite %d, Spur %d: Sektor %d ist als" + + " gel\u00F6scht markiert\n", + head + 1, + cyl, + sector.getSectorNum() ) ); + } + if( sector.getDataLength() > sectorSize ) { + throw new IOException( + String.format( + "Seite %d, Spur %d: Sektor %d ist zu gro\u00DF.", + head + 1, + cyl, + sector.getSectorNum() ) ); + } + int n = sector.writeTo( dataBuf, sectorSize ); + while( n < sectorSize ) { + out.write( 0 ); + n++; + } + } + } + } + byte[] dataBytes = dataBuf.toByteArray(); + + // Kommentar aufbereiten + int remarkLen = 0; + ByteArrayOutputStream remarkBuf = null; + if( remark == null ) { + remark = disk.getRemark(); + } + if( remark != null ) { + int len = remark.length(); + if( len > 0 ) { + CharConverter cc = new CharConverter( + CharConverter.Encoding.CP850 ); + remarkBuf = new ByteArrayOutputStream( len ); + for( int i = 0; i < len; i++ ) { + char ch = remark.charAt( i ); + if( (ch == '\u0000') || (ch == '\u001A') ) { + break; + } + ch = (char) cc.toCharsetByte( ch ); + if( (ch > '\u0000') && (remarkBuf.size() < 0x7FFE) ) { + remarkBuf.write( ch ); + } + } + remarkLen = remarkBuf.size(); + } + } + + // Kopfbereich erzeugen + ByteArrayOutputStream headerBuf = new ByteArrayOutputStream( 256 ); + EmuUtil.writeASCII( headerBuf, "CQ" ); + headerBuf.write( 0x14 ); + headerBuf.write( sectorSize ); + headerBuf.write( sectorSize >> 8 ); + + // im Blind-Mode 6 Null-Bytes + for( int i = 0; i < 6; i++ ) { + headerBuf.write( 0 ); + } + + /* + * Das folgende Byte gibt im DOS-Mode die Anzahl der Sektoren + * bei kleiner Diskettengroesse an. + * Bei von CopyQM im Blind Mode erzeugten Dateien + * wurden folgende Werte vorgefunden: + * 800K-Format (2x80x5x1024): 5Ah / 90 dez + * 640K-Format (2x80x16x256): 70h / 112 dez + * 720K-Format (2x80x9x512): 62h / 98 dez + * 1440K-Format (2x80x18x512): 74h / 116 dez + * 1760K-Format (2x80x11x1024): 66h / 102 dez + * + * Da die Bedeutung des Bytes im Blind Mode unbekannt ist, + * wurde hier eine Formel erfunden, + * die die oben genannten Werte erzeugt. + */ + headerBuf.write( cyls + (sides * sectorsPerCyl) ); + + // im Blind-Mode 4 Null-Bytes + for( int i = 0; i < 4; i++ ) { + headerBuf.write( 0 ); + } + + headerBuf.write( sectorsPerCyl ); + headerBuf.write( sectorsPerCyl >> 8 ); + headerBuf.write( sides ); + headerBuf.write( sides >> 8 ); + + // im Blind-Mode 8 Null-Bytes + for( int i = 0; i < 8; i++ ) { + headerBuf.write( 0 ); + } + + // Beschreibung Medium + EmuUtil.writeASCII( headerBuf, String.valueOf( diskSize / 1024 ) ); + headerBuf.write( 'K' ); + if( sides == 1 ) { + EmuUtil.writeASCII( headerBuf, " Single-Sided" ); + } else if( sides == 2 ) { + EmuUtil.writeASCII( headerBuf, " Double-Sided" ); + } + for( int i = headerBuf.size(); i < 0x58; i++ ) { + headerBuf.write( 0 ); + } + + // Mode + headerBuf.write( 1 ); // Blind Mode + + // 0:=DD, 1=HD, 2=ED + headerBuf.write( diskSize / 1024 / 1024 ); + + // Anzahl Zylinder + headerBuf.write( cyls ); // in der erzeugten Datei + headerBuf.write( cyls ); // auf der Quell-Diskette + + // CRC der Daten + long dataCRC = computeCRC( dataBytes, dataBytes.length ); + for( int i = 0; i < 4; i++ ) { + headerBuf.write( (int) dataCRC ); + dataCRC >>= 8; + } + + // Volume Label + EmuUtil.writeASCII( headerBuf, "** NONE **" ); + headerBuf.write( 0 ); + + // Uhrzeit und Datum + Calendar cal = new GregorianCalendar(); + java.util.Date diskDate = disk.getDiskDate(); + if( diskDate != null ) { + cal.clear(); + cal.setTime( diskDate ); + } + int time = ((cal.get( Calendar.HOUR_OF_DAY ) << 11) & 0xF800) + | ((cal.get( Calendar.MINUTE ) << 5) & 0x07E0) + | ((cal.get( Calendar.SECOND ) / 2) & 0x001F); + int date = (((cal.get( Calendar.YEAR ) - 1980) << 9) & 0xFE00) + | (((cal.get( Calendar.MONTH ) + 1) << 5) & 0x01E0) + | (cal.get( Calendar.DAY_OF_MONTH ) & 0x001F); + headerBuf.write( time ); + headerBuf.write( time >> 8 ); + headerBuf.write( date ); + headerBuf.write( date >> 8 ); + + // Kommentarlaenge + headerBuf.write( remarkLen ); + headerBuf.write( remarkLen >> 8 ); + + // Sektoroffset + headerBuf.write( minSectorNum - 1 ); + + // 2 Null-Bytes, Bedeutung unbekannt + headerBuf.write( 0 ); + headerBuf.write( 0 ); + + // Interleave + headerBuf.write( interleave ); + + // Skew + headerBuf.write( skew ); + + /* + * Laufwerkstyp: + * Es kann auf Basis der Parameter nur ein wahrscheinlicher + * Laufwerkstyp ermittelt werden. + */ + int driveType = 1; // 5.25 Zoll, 360 KByte + if( disk.getDiskSize() >= (360 * 1024) ) { + driveType = 2; // 5.25 Zoll, 48 tpi + } + if( (disk.getCylinders() >= 50) + && (disk.getSectorSize() == 512) ) + { + int n = disk.getSectorsPerCylinder(); + if( (n >= 8) && (n <= 10) ) { + driveType = 3; // 3.5 Zoll DD + } else if( n >= 17 ) { + driveType = 4; // 3.5 Zoll HD + } + } + headerBuf.write( driveType ); + + // 13 Null-Bytes, Bedeutung unbekannt + for( int i = 0; i < 13; i++ ) { + headerBuf.write( 0 ); + } + + // Pruefsumme fuer Kopfbereich + byte[] headerBytes = headerBuf.toByteArray(); + int headerCks = 0; + for( byte b : headerBytes ) { + headerCks += ((int) b & 0xFF); + } + + // Datei schreiben + out = EmuUtil.createOptionalGZipOutputStream( file ); + if( headerBytes != null ) { + out.write( headerBytes ); + out.write( -headerCks ); + if( (remarkBuf != null) && (remarkLen > 0) ) { + remarkBuf.writeTo( out ); + } + if( dataBytes != null ) { + int maxSegLen = sectorSize * sectorsPerCyl; + if( maxSegLen >= 0x8000 ) { + maxSegLen = sectorSize; + } + if( maxSegLen >= 0x8000 ) { + maxSegLen = 0x7FFF; + } + int segBegPos = 0; + while( segBegPos < dataBytes.length ) { + int p = segBegPos; + byte b = dataBytes[ p++ ]; + int n = 1; + while( (n < maxSegLen) && (p < dataBytes.length) ) { + if( dataBytes[ p ] != b ) { + break; + } + n++; + p++; + } + int nEqSectors = n / sectorSize; + if( nEqSectors > 0 ) { + n = nEqSectors * sectorSize; + int len = -n; + out.write( len ); + out.write( len >> 8 ); + out.write( b ); + } else { + n = Math.min( sectorSize, dataBytes.length - segBegPos ); + out.write( n ); + out.write( n >> 8 ); + out.write( dataBytes, segBegPos, n ); + } + segBegPos += n; + } + } + } + out.close(); + out = null; + + if( hasDeleted && (msgBuf != null) ) { + msgBuf.append( "\nGel\u00F6schte Sektoren werden" + + " in CopyQM-Dateien nicht unterst\u00FCtzt\n" + + "und sind deshalb als normale Sektoren enthalten.\n" ); + } + } + finally { + EmuUtil.doClose( out ); + } + return msgBuf != null ? msgBuf.toString() : null; + } public static boolean isCopyQMFileHeader( byte[] header ) @@ -124,33 +500,37 @@ public static CopyQMDisk readFile( } // Kopfblock lesen - byte[] head = new byte[ 133 ]; - if( EmuUtil.read( in, head ) != head.length ) { + byte[] header = new byte[ 133 ]; + if( EmuUtil.read( in, header ) != header.length ) { throwNoCopyQMFile(); } - if( (head[ 0 ] != 'C') || (head[ 1 ] != 'Q') || (head[ 2 ] != 0x14) ) { + if( (header[ 0 ] != 'C') + || (header[ 1 ] != 'Q') + || (header[ 2 ] != 0x14) ) + { throwNoCopyQMFile(); } - int sectorSize = (head[ 4 ] << 8) | head[ 3 ]; + int sectorSize = EmuUtil.getWord( header, 3 ); int sectorSizeCode = SectorData.getSizeCode( sectorSize ); if( (sectorSize < 1) || (sectorSizeCode < 0) ) { throwUnsupportedCopyQMFmt( String.format( - "%d Byte Sektorgr\u00F6\u00DFe nicht unterst\u00FCtzt", + "%d Byte Sektorgr\u00F6\u00DFe" + + " nicht unterst\u00FCtzt", sectorSize ) ); } - int sectorsPerCyl = (head[ 0x11 ] << 8) | head[ 0x10 ]; + int sectorsPerCyl = EmuUtil.getWord( header, 0x10 ); if( sectorsPerCyl < 1 ) { throwUnsupportedCopyQMFmt( String.format( "Sektoren pro Spur", sectorsPerCyl ) ); } - int sides = (int) head[ 0x12 ] & 0xFF; + int sides = (int) header[ 0x12 ] & 0xFF; if( (sides < 1) || (sides > 2) ) { throwUnsupportedCopyQMFmt( String.format( "%d Seiten nicht unterst\u00FCtzt", sides ) ); } - int usedCyls = (int) head[ 0x5A ] & 0xFF; - int cyls = (int) head[ 0x5B ] & 0xFF; + int usedCyls = (int) header[ 0x5A ] & 0xFF; + int cyls = (int) header[ 0x5B ] & 0xFF; if( cyls < usedCyls ) { cyls = usedCyls; } @@ -158,13 +538,30 @@ public static CopyQMDisk readFile( throwUnsupportedCopyQMFmt( String.format( "%d Spuren nicht unterst\u00FCtzt", sides ) ); } - if( head[ 0x71 ] != 0 ) { - throwUnsupportedCopyQMFmt( "Mysteri\u00F6se Sektornummerierung" ); + int sectorOffset = (int) header[ 0x71 ] & 0xFF; + + // Datum lesen + java.util.Date diskDate = null; + int time = EmuUtil.getWord( header, 0x6B ); + int date = EmuUtil.getWord( header, 0x6d ); + int year = 1980 + ((date >> 9) & 0x7F); + int month = (date >> 5) & 0x0F; + int day = date & 0x1F; + int hour = (time >> 11) & 0x1F; + int minute = (time >> 5) & 0x3F; + int second = (time & 0x1F) * 2; + if( (month >= 1) && (month <= 12) + && (day >= 1) && (day <= 31) + && (hour < 24) && (minute < 60) && (second < 60) ) + { + diskDate = (new GregorianCalendar( + year, month - 1, day, + hour, minute, second )).getTime(); } // Kommentar lesen String remark = null; - int remarkLen = (head[ 0x70 ] << 8) | head[ 0x6F]; + int remarkLen = EmuUtil.getWord( header, 0x6F ); if( remarkLen < 0 ) { throwUnsupportedCopyQMFmt( String.format( @@ -172,18 +569,17 @@ public static CopyQMDisk readFile( remarkLen ) ); } if( remarkLen > 0 ) { + CharConverter cc = new CharConverter( CharConverter.Encoding.CP850 ); StringBuilder buf = new StringBuilder( remarkLen ); while( remarkLen > 0 ) { int b = in.read(); if( b < 0 ) { break; } - if( b < 0x20 ) { - b = 0x20; - } else if( b > 0x7E ) { - b = '?'; + b = cc.toUnicode( (char) b ); + if( b > 0 ) { + buf.append( (char) b ); } - buf.append( (char) b ); --remarkLen; } remark = buf.toString().trim(); @@ -232,21 +628,21 @@ public static CopyQMDisk readFile( sectorSize, file.getPath(), remark, + diskDate, diskBytes, - sectorSizeCode ); + sectorSizeCode, + sectorOffset, + (int) header[ 0x74 ] & 0xFF, // Interleave + (int) header[ 0x75 ] & 0xFF ); // Skew // CRC ueber die entpackten Daten berechnen long orgCRC = 0L; long newCRC = 0L; for( int i = 0x5F; i >= 0x5C; --i ) { - orgCRC = (orgCRC << 8) | ((int) head[ i ] & 0xFF); + orgCRC = (orgCRC << 8) | ((int) header[ i ] & 0xFF); } if( orgCRC != 0 ) { - for( int i = 0; i < dstPos; i++ ) { - int b = (int) diskBytes[ i ] & 0x7F; - newCRC = crcTable[ ((int) ((long) b ^ newCRC)) & 0x3F ] - ^ (newCRC >> 8); - } + newCRC = computeCRC( diskBytes, dstPos ); } if( orgCRC != newCRC ) { rv.setWarningText( @@ -262,6 +658,13 @@ public static CopyQMDisk readFile( /* --- ueberschriebene Methoden --- */ + @Override + public java.util.Date getDiskDate() + { + return this.diskDate; + } + + @Override public String getFileFormatText() { @@ -282,36 +685,27 @@ public SectorData getSectorByIndex( int physHead, int sectorIdx ) { - SectorData rv = null; - int sectorSize = getSectorSize(); - if( (physCyl >= 0) && (sectorIdx >= 0) ) { - int sides = getSides(); - int cyls = getCylinders(); + // Sektorindex entsprechend Interleave umrechnen + sectorIdx = sectorIndexToInterleave( sectorIdx ); + + // Sektorindex entsprechend Skew umrechnen + if( this.skew > 0 ) { int sectorsPerCyl = getSectorsPerCylinder(); - if( (physHead < sides) - && (physCyl < cyls) - && (sectorIdx < sectorsPerCyl) - && (sectorSize > 0) ) - { - int nSkipSectors = sides * sectorsPerCyl * physCyl; - if( physHead > 0 ) { - nSkipSectors += sectorsPerCyl; - } - nSkipSectors += sectorIdx; - rv = new SectorData( - sectorIdx, - physCyl, - physHead, - sectorIdx + 1, - this.sectorSizeCode, - false, - false, - this.diskBytes, - nSkipSectors * sectorSize, - sectorSize ); + if( (sectorsPerCyl > 0) && (this.skew < sectorsPerCyl) ) { + sectorIdx += (sectorsPerCyl + - (this.skew * (physCyl % sectorsPerCyl))); + if( sectorIdx < 0 ) { + sectorIdx += ((-sectorIdx / sectorsPerCyl) * sectorsPerCyl); + if( sectorIdx < 0 ) { + sectorIdx += sectorsPerCyl; + } + } else if( sectorIdx >= sectorsPerCyl ) { + sectorIdx -= ((sectorIdx / sectorsPerCyl) * sectorsPerCyl); + } } } - return rv; + + return getSectorByIndexInternal( physCyl, physHead, sectorIdx ); } @@ -324,7 +718,10 @@ public SectorData getSectorByID( int sectorNum, int sizeCode ) { - SectorData rv = getSectorByIndex( physCyl, physHead, sectorNum - 1 ); + SectorData rv = getSectorByIndexInternal( + physCyl, + physHead, + sectorNum - 1 - this.sectorOffset ); if( rv != null ) { if( (rv.getCylinder() != cyl) || (rv.getHead() != head) @@ -351,21 +748,82 @@ public void putSettingsTo( Properties props, String prefix ) /* --- private Konstruktoren und Methoden --- */ private CopyQMDisk( - Frame owner, - int sides, - int cyls, - int sectorsPerCyl, - int sectorSize, - String fileName, - String remark, - byte[] diskBytes, - int sectorSizeCode ) + Frame owner, + int sides, + int cyls, + int sectorsPerCyl, + int sectorSize, + String fileName, + String remark, + java.util.Date diskDate, + byte[] diskBytes, + int sectorSizeCode, + int sectorOffset, + int interleave, + int skew ) { - super( owner, sides, cyls, sectorsPerCyl, sectorSize ); + super( owner, sides, cyls, sectorsPerCyl, sectorSize, interleave ); this.fileName = fileName; this.remark = remark; + this.diskDate = diskDate; this.diskBytes = diskBytes; this.sectorSizeCode = sectorSizeCode; + this.sectorOffset = sectorOffset; + this.skew = skew; + } + + + private static long computeCRC( byte[] dataBytes, int len ) + { + long crc = 0; + if( dataBytes != null ) { + for( int i = 0; i < len; i++ ) { + int b = (int) dataBytes[ i ] & 0x7F; + crc = crcTable[ ((int) ((long) b ^ crc)) & 0x3F ] ^ (crc >> 8); + } + } + return crc; + } + + + /* + * Die Methode liefert den Sektor an der angegebenen Position + * im internen Datenbereich zurueck. + * Interleave wird dabei nicht beruecksichtigt. + */ + private SectorData getSectorByIndexInternal( + int physCyl, + int physHead, + int sectorIdx ) + { + SectorData rv = null; + if( (physCyl >= 0) && (physHead >= 0) && (sectorIdx >= 0) ) { + int sides = getSides(); + int cyls = getCylinders(); + int sectorsPerCyl = getSectorsPerCylinder(); + int sectorSize = getSectorSize(); + if( (physHead < sides) + && (physCyl < cyls) + && (sectorIdx < sectorsPerCyl) + && (sectorSize > 0) ) + { + int nSkipSectors = sides * sectorsPerCyl * physCyl; + if( physHead > 0 ) { + nSkipSectors += sectorsPerCyl; + } + nSkipSectors += sectorIdx; + rv = new SectorData( + sectorIdx, + physCyl, + physHead, + sectorIdx + 1 + this.sectorOffset, + this.sectorSizeCode, + this.diskBytes, + nSkipSectors * sectorSize, + sectorSize ); + } + } + return rv; } diff --git a/src/jkcemu/disk/DateStamper.java b/src/jkcemu/disk/DateStamper.java new file mode 100644 index 0000000..66888de --- /dev/null +++ b/src/jkcemu/disk/DateStamper.java @@ -0,0 +1,198 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * DateStamper-Unterstuetzung + */ + +package jkcemu.disk; + +import java.io.File; +import java.lang.*; +import java.util.*; +import jkcemu.base.*; + + +public class DateStamper +{ + public static final String ENTRYNAME = "!!!TIME&DAT"; + public static final String FILENAME = "!!!TIME&.DAT"; + + private static Calendar calendar = Calendar.getInstance(); + + private FileTimesViewFactory ftvFactory; + private byte[] dateTimeBytes; + private int dateTimeLen; + private int pos; + + + public DateStamper( + FileTimesViewFactory ftvFactory, + int dirEntries, + byte[] dateTimeBytes ) + { + this.ftvFactory = ftvFactory; + this.dateTimeLen = dirEntries * 16; + if( dateTimeBytes != null ) { + this.dateTimeBytes = dateTimeBytes; + } else { + this.dateTimeBytes = new byte[ dirEntries * 16 ]; + } + if( this.dateTimeLen < this.dateTimeBytes.length ) { + Arrays.fill( this.dateTimeBytes, 0, this.dateTimeLen, (byte) 0x00 ); + } else { + Arrays.fill( this.dateTimeBytes, (byte) 0x00 ); + this.dateTimeLen = this.dateTimeBytes.length; + } + String text = "!!!TIME\u0092"; + int textLen = text.length(); + int srcPos = 0; + int dstPos = 0x0F; + while( dstPos < this.dateTimeLen ) { + if( srcPos >= textLen ) { + srcPos = 0; + } + this.dateTimeBytes[ dstPos ] = (byte) text.charAt( srcPos++ ); + dstPos += 0x10; + } + } + + + public void addFileTimes( File file ) + { + FileTimesView ftv = null; + if( file != null ) { + ftv = this.ftvFactory.getFileTimesView( file ); + } + if( ftv != null ) { + writeDateTimeEntry( ftv.getCreationMillis() ); + writeDateTimeEntry( ftv.getLastAccessMillis() ); + writeDateTimeEntry( ftv.getLastModifiedMillis() ); + } else { + for( int i = 0; i < 15; i++ ) { + if( (pos < this.dateTimeLen) && (pos < this.dateTimeBytes.length) ) { + this.dateTimeBytes[ this.pos++ ] = (byte) 0; + } + } + } + this.pos++; + } + + + public byte[] getDateTimeByteBuffer() + { + // DateStamper Pruefsummen berechnen + int pos = 0; + while( pos < this.dateTimeLen ) { + int cks = 0; + for( int i = 0; (i < 0x7F) && (pos < this.dateTimeLen); i++ ) { + cks += ((int) this.dateTimeBytes[ pos++ ] & 0xFF); + } + if( pos < this.dateTimeLen ) { + this.dateTimeBytes[ pos++ ] = (byte) cks; + } + } + return this.dateTimeBytes; + } + + + public static Long getMillis( byte[] buf, int pos ) + { + Long millis = null; + if( buf != null ) { + if( (pos >= 0) && ((pos + 4) < buf.length) ) { + int year = fromBcdByte( buf[ pos ] ); + int month = fromBcdByte( buf[ pos + 1 ] ); + int day = fromBcdByte( buf[ pos + 2 ] ); + int hour = fromBcdByte( buf[ pos + 3 ] ); + int minute = fromBcdByte( buf[ pos + 4 ] ); + if( year >= 78 ) { + year += 1900; + } else { + year += 2000; + } + if( (month >= 1) && (month <= 12) + && (day >= 1) && (day <= 31) + && (hour < 24) && (minute < 60) ) + { + boolean valid = true; + if( month == 2 ) { + if( ((year % 4) == 0) && ((year % 100) != 0) ) { + if( day > 29 ) { + valid = false; + } + } else { + if( day > 28 ) { + valid = false; + } + } + } + else if( (month == 4) || (month == 6) + || (month == 9) || (month == 11) ) + { + if( day > 30 ) { + valid = false; + } + } + if( valid ) { + synchronized( calendar ) { + calendar.clear(); + calendar.set( year, month - 1, day, hour, minute ); + millis = new Long( calendar.getTimeInMillis() ); + } + } + } + } + } + return millis; + } + + + /* --- private Methoden --- */ + + private static int fromBcdByte( byte b ) + { + return ((((int) b >> 4) & 0x0F) * 10) + ((int) b & 0x0F); + } + + + private static byte toBcdByte( int value ) + { + return (byte) (((((value / 10) % 10) << 4) & 0xF0) + | ((value % 10) & 0x0F)); + } + + + private void writeDateTimeEntry( Long millis ) + { + int endPos = Math.min( this.dateTimeLen, this.dateTimeBytes.length ); + if( (this.pos + 4) < endPos ) { + boolean done = false; + if( millis != null ) { + synchronized( calendar ) { + calendar.clear(); + calendar.setTimeInMillis( millis.longValue() ); + int year = this.calendar.get( Calendar.YEAR ); + if( (year >= 1978) && (year < 2078) ) { + this.dateTimeBytes[ this.pos++ ] = toBcdByte( year ); + this.dateTimeBytes[ this.pos++ ] = + toBcdByte( calendar.get( Calendar.MONTH ) + 1 ); + this.dateTimeBytes[ this.pos++ ] = + toBcdByte( calendar.get( Calendar.DAY_OF_MONTH ) ); + this.dateTimeBytes[ this.pos++ ] = + toBcdByte( calendar.get( Calendar.HOUR_OF_DAY ) ); + this.dateTimeBytes[ this.pos++ ] = + toBcdByte( calendar.get( Calendar.MINUTE ) ); + done = true; + } + } + } + if( !done ) { + for( int i = 0; i < 15; i++ ) { + this.dateTimeBytes[ this.pos++ ] = (byte) 0; + } + } + } + } +} diff --git a/src/jkcemu/disk/DirectoryFloppyDisk.java b/src/jkcemu/disk/DirectoryFloppyDisk.java index 7725675..f531b0a 100644 --- a/src/jkcemu/disk/DirectoryFloppyDisk.java +++ b/src/jkcemu/disk/DirectoryFloppyDisk.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -14,7 +14,7 @@ import java.lang.*; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; -import jkcemu.base.EmuUtil; +import jkcemu.base.*; public class DirectoryFloppyDisk extends AbstractFloppyDisk @@ -38,8 +38,8 @@ public int compare( String s1, String s2 ) if( s2 == null ) { s2 = ""; } - boolean at1 = s1.startsWith( "\u0000@" ); - boolean at2 = s2.startsWith( "\u0000@" ); + boolean at1 = s1.startsWith( "0@" ); + boolean at2 = s2.startsWith( "0@" ); if( at1 && !at2 ) { rv = -1; } else if( !at1 && at2 ) { @@ -77,24 +77,31 @@ public boolean equals( Object o ) private boolean autoRefresh; private boolean forceLowerCase; private boolean readOnly; + private boolean dsEnabled; + private byte[] dsBytes; + private int dsFirstSector; + private int dsSectors; + private FileTimesViewFactory ftvFactory; private String remark; private volatile boolean refreshFired; public DirectoryFloppyDisk( - Frame owner, - int sides, - int cyls, - int sectorsPerCyl, - int sectorSize, - int sysTracks, - int dirBlocks, - int blockSize, - boolean blockNum16Bit, - File dirFile, - boolean autoRefresh, - boolean readOnly, - boolean forceLowerCase ) + Frame owner, + int sides, + int cyls, + int sectorsPerCyl, + int sectorSize, + int sysTracks, + int dirBlocks, + int blockSize, + boolean blockNum16Bit, + boolean dateStamperEnabled, + FileTimesViewFactory ftvFactory, + File dirFile, + boolean autoRefresh, + boolean readOnly, + boolean forceLowerCase ) { super( owner, sides, cyls, sectorsPerCyl, sectorSize ); this.dirFile = dirFile; @@ -107,13 +114,18 @@ public DirectoryFloppyDisk( this.dirBlocks = dirBlocks; this.dirSectors = dirBlocks * this.sectorsPerBlock; this.sectorSizeCode = SectorData.getSizeCode( sectorSize ); - this.autoRefresh = readOnly ? autoRefresh : false; + this.autoRefresh = autoRefresh; this.readOnly = readOnly; this.forceLowerCase = forceLowerCase; this.lastBuildMillis = -1L; - this.fileMap = new HashMap(); + this.fileMap = new HashMap<>(); this.errorMap = null;; this.dirBytes = null; + this.dsEnabled = dateStamperEnabled; + this.dsBytes = null; + this.dsFirstSector = -1; + this.dsSectors = 0; + this.ftvFactory = ftvFactory; this.maxDirEntries = 0; this.sectors = new SectorData[ sides * cyls * sectorsPerCyl ]; this.refreshFired = false; @@ -132,6 +144,12 @@ public void fireRefresh() } + public File getDirFile() + { + return this.dirFile; + } + + /* --- ueberschriebene Methoden --- */ @Override @@ -141,6 +159,17 @@ public String getFileFormatText() } + @Override + public String getFormatText() + { + String text = super.getFormatText(); + if( (text != null) && this.dsEnabled ) { + text += ", DateStamper"; + } + return text; + } + + @Override public String getRemark() { @@ -163,7 +192,7 @@ public synchronized SectorData getSectorByIndex( * aktiviert ist, wird beim naechsten lesenden Zugriff * auf den ersten Sektor der Diskette oder des Directories * die virtuelle Diskette neu erzeugt, bei AutoRefresh aber nur, - * wenn die letzte Aktualisierung mehr als 10 Sekunden zurueckliegt. + * wenn die letzte Aktualisierung mehr als 5 Sekunden zurueckliegt. */ if( ((physCyl == 0) || (physCyl == this.sysTracks)) && (physHead == 0) @@ -176,7 +205,7 @@ public synchronized SectorData getSectorByIndex( long curMillis = System.currentTimeMillis(); if( (curMillis != -1L) && ((this.lastBuildMillis == -1L) - || (this.lastBuildMillis < (curMillis - 10000))) ) + || (this.lastBuildMillis < (curMillis - 5000))) ) { rebuildDisk(); } @@ -299,6 +328,9 @@ public void putSettingsTo( Properties props, String prefix ) props.setProperty( prefix + "dir_blocks", Integer.toString( this.dirBlocks ) ); + props.setProperty( + prefix + "datestamper", + Boolean.toString( this.dsEnabled ) ); props.setProperty( prefix + "auto_refresh", Boolean.toString( this.autoRefresh ) ); @@ -360,6 +392,13 @@ else if( (absSectorIdx >= this.sysSectors) // Directory writeDirSector( absSectorIdx, dataBuf, dataLen ); } + else if( this.dsEnabled + && (absSectorIdx >= this.dsFirstSector) + && (absSectorIdx < (this.dsFirstSector + this.dsSectors)) ) + { + // DateStamper + writeDsSector( absSectorIdx, dataBuf, dataLen ); + } else if( absSectorIdx >= (this.sysSectors + this.dirSectors) ) { // Datenbereich int relSectorIdx = absSectorIdx - this.sysSectors; @@ -469,7 +508,7 @@ private String extractEntryName( byte[] dirBytes, int entryBegPos ) int b0 = (int) dirBytes[ entryBegPos ] & 0xFF; if( (b0 != 0xE5) && ((b0 & 0xF0) == 0) ) { char[] buf = new char[ 12 ]; - buf[ 0 ] = (char) b0; + buf[ 0 ] = (char) (b0 + '0'); for( int i = 1; i < 12; i++ ) { buf[ i ] = (char) ((int) dirBytes[ entryBegPos + i ] & 0x7F); } @@ -496,7 +535,7 @@ private int findEntryBegPosByNameAndExtent( == ((extentNum >> 5) & 0x3F)) ) { boolean found = false; - if( (b0 & 0x0F) == entryName.charAt( 0 ) ) { + if( (b0 & 0x0F) == (entryName.charAt( 0 ) - '0') ) { found = true; for( int i = 1; i < 12; i++ ) { if( ((int) this.dirBytes[ pos + i ] & 0x7F) @@ -575,7 +614,7 @@ private java.util.List getBlockNumsByEntryName( int blockNum = EmuUtil.getWord( this.dirBytes, pos ); if( blockNum > 0 ) { if( rv == null ) { - rv = new ArrayList( 32 ); + rv = new ArrayList<>( 32 ); } rv.add( new Integer( blockNum ) ); } @@ -586,7 +625,7 @@ private java.util.List getBlockNumsByEntryName( int blockNum = (int) this.dirBytes[ pos ] & 0xFF; if( blockNum > 0 ) { if( rv == null ) { - rv = new ArrayList( 32 ); + rv = new ArrayList<>( 32 ); } rv.add( new Integer( blockNum ) ); if( len == 0 ) { @@ -611,6 +650,20 @@ private java.util.List getBlockNumsByEntryName( } + private static boolean isInSameMinute( Long minuteMillis, Long exactMillis ) + { + boolean rv = false; + if( (minuteMillis != null) && (exactMillis != null) ) { + if( (exactMillis >= minuteMillis) + && (exactMillis < (minuteMillis + 60000L)) ) + { + rv = true; + } + } + return rv; + } + + private void loadFileIntoSectors( File file, java.util.List blockNums ) @@ -644,14 +697,14 @@ private byte[] readFile( File file ) { byte[] fileBytes = null; try { - fileBytes = EmuUtil.readFile( file, getDiskSize() ); + fileBytes = EmuUtil.readFile( file, false, getDiskSize() ); } catch( IOException ex ) { String fileName = file.getPath(); if( fileName != null ) { if( !fileName.isEmpty() ) { if( this.errorMap == null ) { - this.errorMap = new HashMap(); + this.errorMap = new HashMap<>(); } this.errorMap.put( fileName, ex ); } @@ -680,8 +733,18 @@ private void rebuildDisk() } this.lastBuildMillis = System.currentTimeMillis(); try { - Map fileMap = null; - boolean dirFull = false; + + // Dateien ermitteln, die in der emulierten Disketten enthalten sind + Map fileMap = new HashMap<>(); + DateStamper dateStamper = null; + boolean dirFull = false; + if( this.dsEnabled ) { + fileMap.put( "0" + DateStamper.ENTRYNAME, null ); + dateStamper = new DateStamper( + this.ftvFactory, + this.dirBlocks * this.blockSize / 32, + this.dsBytes ); + } for( int userNum = 0; !dirFull && (userNum < 16); userNum++ ) { File dirFile = this.dirFile; if( userNum > 0 ) { @@ -700,9 +763,13 @@ private void rebuildDisk() String fName2 = fName.trim().toUpperCase(); if( fName2 != null ) { int len = fName2.length(); - if( (len > 0) && (len < 13) && (len == fName.length()) ) { + if( (!this.dsEnabled + || !fName.equals( DateStamper.FILENAME )) + &&(len > 0) && (len < 13) + && (len == fName.length()) ) + { StringBuilder buf = new StringBuilder( 12 ); - buf.append( (char) userNum ); + buf.append( (char) (userNum + '0') ); int nChars = 1; boolean ignore = false; boolean point = false; @@ -741,9 +808,6 @@ private void rebuildDisk() nChars++; } String entryName = buf.toString(); - if( fileMap == null ) { - fileMap = new HashMap(); - } if( fileMap.containsKey( entryName ) ) { /* * Falls mehrere Dateien existieren und deren @@ -782,6 +846,7 @@ private void rebuildDisk() Arrays.sort( entryNames, entryNameComparator ); } catch( ClassCastException ex ) {} + int firstDsBlkIdx = -1; int dirIdx = 0; int blkIdx = this.dirBlocks; int nRemainBlocks = (getDiskSize() / this.blockSize) - blkIdx; @@ -794,77 +859,114 @@ private void rebuildDisk() for( int i = 0; i < entryNames.length; i++ ) { String entryName = entryNames[ i ]; if( entryName != null ) { - File file = fileMap.get( entryName ); - if( file != null ) { - long fSize = file.length(); - if( fSize >= 0 ) { - long nEntries = (fSize + maxEntrySize - 1) / maxEntrySize; - if( nEntries < 1 ) { - nEntries = 1; - } - if( ((dirIdx + (nEntries * 32)) <= this.dirBytes.length) - && (nEntries <= maxFileEntries) ) - { - long nBlocks = (fSize + this.blockSize - 1) / this.blockSize; - if( nBlocks <= nRemainBlocks ) { - for( int k = 0; k < nEntries; k++ ) { - int roAttrPos = dirIdx + 9; - int len = entryName.length(); - if( len > 0 ) { - this.dirBytes[ dirIdx++ ] = - (byte) entryName.charAt( 0 ); - } else { - this.dirBytes[ dirIdx++ ] = (byte) 0; - } - for( int p = 1; p < 12; p++ ) { - char ch = '\u0020'; - if( p < len ) { - ch = entryName.charAt( p ); - } - this.dirBytes[ dirIdx++ ] = (byte) (ch & 0x7F); - } - if( !file.canWrite() ) { - this.dirBytes[ roAttrPos ] |= 0x80; - } - this.dirBytes[ dirIdx++ ] = (byte) k; - this.dirBytes[ dirIdx++ ] = (byte) 0; + File file = null; + long fSize = -1; + boolean writable = false; + if( this.dsEnabled + && entryName.equals( "0" + DateStamper.ENTRYNAME ) ) + { + fSize = maxDirEntries * 16; + writable = true; + firstDsBlkIdx = blkIdx; + } else { + file = fileMap.get( entryName ); + if( file != null ) { + fSize = file.length(); + writable = file.canWrite(); + } + } + if( fSize >= 0 ) { + long nEntries = (fSize + maxEntrySize - 1) / maxEntrySize; + if( nEntries < 1 ) { + nEntries = 1; + } + if( ((dirIdx + (nEntries * 32)) <= this.dirBytes.length) + && (nEntries <= maxFileEntries) ) + { + long nBlocks = (fSize + this.blockSize - 1) / this.blockSize; + if( nBlocks <= nRemainBlocks ) { + for( int k = 0; k < nEntries; k++ ) { + int roAttrPos = dirIdx + 9; + int len = entryName.length(); + if( len > 0 ) { + this.dirBytes[ dirIdx++ ] = + (byte) (entryName.charAt( 0 ) - '0'); + } else { this.dirBytes[ dirIdx++ ] = (byte) 0; - long nSegs = 0; - if( fSize > 0 ) { - nSegs = (Math.min( fSize, maxEntrySize ) + 127) / 128; + } + for( int p = 1; p < 12; p++ ) { + char ch = '\u0020'; + if( p < len ) { + ch = entryName.charAt( p ); } - this.dirBytes[ dirIdx++ ] = (byte) nSegs; - if( this.blockNum16Bit ) { - for( int m = 0; m < 8; m++ ) { - if( nBlocks > 0 ) { - this.dirBytes[ dirIdx++ ] = (byte) blkIdx; - this.dirBytes[ dirIdx++ ] = (byte) (blkIdx >> 8); - blkIdx++; - --nBlocks; - --nRemainBlocks; - fSize -= this.blockSize; - } else { - this.dirBytes[ dirIdx++ ] = (byte) 0; - this.dirBytes[ dirIdx++ ] = (byte) 0; - } + this.dirBytes[ dirIdx++ ] = (byte) (ch & 0x7F); + } + if( !writable ) { + this.dirBytes[ roAttrPos ] |= 0x80; + } + this.dirBytes[ dirIdx++ ] = (byte) k; + this.dirBytes[ dirIdx++ ] = (byte) 0; + this.dirBytes[ dirIdx++ ] = (byte) 0; + long nSegs = 0; + if( fSize > 0 ) { + nSegs = (Math.min( fSize, maxEntrySize ) + 127) / 128; + } + this.dirBytes[ dirIdx++ ] = (byte) nSegs; + if( this.blockNum16Bit ) { + for( int m = 0; m < 8; m++ ) { + if( nBlocks > 0 ) { + this.dirBytes[ dirIdx++ ] = (byte) blkIdx; + this.dirBytes[ dirIdx++ ] = (byte) (blkIdx >> 8); + blkIdx++; + --nBlocks; + --nRemainBlocks; + fSize -= this.blockSize; + } else { + this.dirBytes[ dirIdx++ ] = (byte) 0; + this.dirBytes[ dirIdx++ ] = (byte) 0; } - } else { - for( int m = 0; m < 16; m++ ) { - if( nBlocks > 0 ) { - this.dirBytes[ dirIdx++ ] = (byte) blkIdx++; - --nBlocks; - --nRemainBlocks; - fSize -= this.blockSize; - } else { - this.dirBytes[ dirIdx++ ] = (byte) 0; - } + } + } else { + for( int m = 0; m < 16; m++ ) { + if( nBlocks > 0 ) { + this.dirBytes[ dirIdx++ ] = (byte) blkIdx++; + --nBlocks; + --nRemainBlocks; + fSize -= this.blockSize; + } else { + this.dirBytes[ dirIdx++ ] = (byte) 0; } } } - this.fileMap.put( entryName, file ); + if( dateStamper != null ) { + dateStamper.addFileTimes( file ); + } } } } + this.fileMap.put( entryName, file ); + } + } + } + + // DateStamper-Sektoren eintragen + if( (dateStamper != null) && (firstDsBlkIdx >= 0) ) { + this.dsBytes = dateStamper.getDateTimeByteBuffer(); + int sectorSize = getSectorSize(); + if( (this.dsBytes != null) && (sectorSize > 0) ) { + int srcPos = 0; + int absSectIdx = this.sysSectors + + (firstDsBlkIdx * this.sectorsPerBlock); + this.dsFirstSector = absSectIdx; + this.dsSectors = dsBytes.length / sectorSize; + while( srcPos < this.dsBytes.length ) { + setSectorData( + absSectIdx++, + this.dsBytes, + srcPos, + sectorSize, + false ); + srcPos += sectorSize; } } } @@ -891,7 +993,7 @@ private void rebuildDisk() private void setSectorData( - int absSectIdx, + int absSectIdx, byte[] dataBuf, int dataOffs, int sectorSize, @@ -916,11 +1018,10 @@ private void setSectorData( head, idx + 1, this.sectorSizeCode, - err, - false, dataBuf, dataOffs, sectorSize ); + this.sectors[ absSectIdx ].setError( err ); } } } @@ -962,7 +1063,7 @@ private void writeDirSector( if( entryDiffers ) { if( entryName != null ) { if( newEntryNames == null ) { - newEntryNames = new TreeSet(); + newEntryNames = new TreeSet<>(); } newEntryNames.add( entryName ); } @@ -978,7 +1079,7 @@ private void writeDirSector( String oldEntryName = extractEntryName( this.dirBytes, bufPos ); if( oldEntryName != null ) { if( oldEntryNames == null ) { - oldEntryNames = new TreeSet(); + oldEntryNames = new TreeSet<>(); } oldEntryNames.add( oldEntryName ); } @@ -1058,6 +1159,114 @@ private void writeDirSector( } + private void writeDsSector( + int absSectorIdx, + byte[] dataBuf, + int dataLen ) throws IOException + { + byte[] oldDsBytes = this.dsBytes; + int dsFirstSectopr = this.dsFirstSector; + if( this.dsEnabled && (oldDsBytes != null) && (dsFirstSector >= 0) ) { + int sectorSize = getSectorSize(); + int oldIdx = (absSectorIdx - dsFirstSector) * sectorSize; + if( (sectorSize > 0 ) && (oldIdx >= 0) ) { + int newIdx = 0; + int dirIdx = oldIdx / 16 * 32; + while( ((newIdx + 15) < dataLen) + && ((oldIdx + 15)< oldDsBytes.length) ) + { + // Zeitstempeleintraege nur bei Extent=0 auswerten + if( (dirIdx + 31) < this.dirBytes.length ) { + if( (((int) this.dirBytes[ dirIdx + 12 ] & 0x1F) == 0) + && (((int) this.dirBytes[ dirIdx + 14 ] & 0x3F) == 0) ) + { + Long creationMillis = null; + Long lastAccessMillis = null; + Long lastModifiedMillis = null; + for( int i = 0; i < 5; i++ ) { + if( dataBuf[ newIdx + i ] != oldDsBytes[ oldIdx + i ] ) { + creationMillis = DateStamper.getMillis( dataBuf, newIdx ); + break; + } + } + for( int i = 5; i < 10; i++ ) { + if( dataBuf[ newIdx + i ] != oldDsBytes[ oldIdx + i ] ) { + lastAccessMillis = DateStamper.getMillis( + dataBuf, + newIdx + 5 ); + break; + } + } + for( int i = 10; i < 15; i++ ) { + if( dataBuf[ newIdx + i ] != oldDsBytes[ oldIdx + i ] ) { + lastModifiedMillis = DateStamper.getMillis( + dataBuf, + newIdx + 10 ); + break; + } + } + if( (creationMillis != null) + || (lastAccessMillis != null) + || (lastModifiedMillis != null) ) + { + String entryName = extractEntryName( this.dirBytes, dirIdx ); + if( entryName != null ) { + File file = this.fileMap.get( entryName ); + if( file != null ) { + FileTimesView ftv = this.ftvFactory.getFileTimesView( + file ); + if( ftv != null ) { + /* + * Wenn ein zu setzender Zeitstempel + * der ja nur minutengenau ist, + * in der gleichen Minute liegt wie der bereits + * vorhandene Dateizeitstempel, + * dann soll der Zeitstempel nicht gesetzt werden, + * um die Sekunden nicht zu loeschen. + */ + if( isInSameMinute( + creationMillis, + ftv.getCreationMillis() ) ) + { + creationMillis = null; + } + if( isInSameMinute( + lastAccessMillis, + ftv.getLastAccessMillis() ) ) + { + lastAccessMillis = null; + } + if( isInSameMinute( + lastModifiedMillis, + ftv.getLastModifiedMillis() ) ) + { + lastModifiedMillis = null; + } + if( (creationMillis != null) + || (lastAccessMillis != null) + || (lastModifiedMillis != null) ) + { + ftv.setTimesInMillis( + creationMillis, + lastAccessMillis, + lastModifiedMillis ); + } + } + } + } + } + } + } + System.arraycopy( dataBuf, newIdx, this.dsBytes, oldIdx, 16 ); + newIdx += 16; + oldIdx += 16; + dirIdx += 32; + } + } + } + } + + private void writeFileByEntryName( String entryName, File file ) throws IOException @@ -1080,7 +1289,7 @@ private void writeFileByEntryName( */ for( int i = 1; i < entryNameLen; i++ ) { /* - * Eine Pruefung auf einen Wert groesser 7Fh ist noetig, + * Eine Pruefung auf einen Wert groesser 7Fh ist nicht noetig, * da die Eintraege sowieso nur aus 7-Bit-Zeichen bestehen * und deshalb das 8. Bit bereits ausgeblendet wurde. */ @@ -1114,7 +1323,7 @@ private void writeFileByEntryName( if( this.forceLowerCase ) { fileName = fileName.toLowerCase(); } - int userNum = entryName.charAt( 0 ); + int userNum = (entryName.charAt( 0 ) - '0'); if( (userNum == 0) && fileName.equals( SYS_FILE_NAME ) ) { throw new IOException( "Eine Datei mit dem Namen " + SYS_FILE_NAME diff --git a/src/jkcemu/disk/DiskImgCreateFrm.java b/src/jkcemu/disk/DiskImgCreateFrm.java index f61c06d..c4a4c8c 100644 --- a/src/jkcemu/disk/DiskImgCreateFrm.java +++ b/src/jkcemu/disk/DiskImgCreateFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -15,7 +15,6 @@ import java.io.*; import java.lang.*; import java.util.*; -import java.util.zip.GZIPOutputStream; import javax.swing.*; import javax.swing.event.*; import jkcemu.Main; @@ -37,31 +36,35 @@ public class DiskImgCreateFrm private File lastOutFile; private boolean dataChanged; private JMenuItem mnuClose; + private JMenuItem mnuNew; private JMenuItem mnuFileAdd; private JMenuItem mnuFileRemove; private JMenuItem mnuSort; private JMenuItem mnuSave; + private JMenuItem mnuChangeAttrs; private JMenuItem mnuChangeUser; private JMenuItem mnuPaste; + private JMenuItem mnuSelectAll; private JMenuItem mnuHelpContent; private JButton btnFileAdd; private JButton btnFileRemove; private JButton btnSave; private JButton btnFileUp; private JButton btnFileDown; - private JButton btnSysFileSelect; - private JButton btnSysFileRemove; + private JButton btnSysTrackFileSelect; + private JButton btnSysTrackFileRemove; private JTabbedPane tabbedPane; private FileTableModel tableModel; private JTable table; private JScrollPane scrollPane; private FloppyDiskFormatSelectFld fmtSelectFld; - private JLabel labelSysFile; - private FileNameFld fldSysFileName; + private JLabel labelSysTrackFile; + private FileNameFld fldSysTrackFileName; + private JTextField fldRemark; private JLabel labelStatus; private DropTarget dropTargetFile1; private DropTarget dropTargetFile2; - private DropTarget dropTargetSysFile; + private DropTarget dropTargetSysTrackFile; public static void open() @@ -83,17 +86,15 @@ public static void open() @Override public void stateChanged( ChangeEvent e ) { - if( e != null ) { - Object src = e.getSource(); - if( src != null ) { - if( src == this.tabbedPane ) { - updActionButtons(); - updStatusText(); - } - else if( src == this.fmtSelectFld ) { - updSysFileFieldsEnabled(); - updStatusText(); - } + Object src = e.getSource(); + if( src != null ) { + if( src == this.tabbedPane ) { + updActionButtons(); + updStatusText(); + } + else if( src == this.fmtSelectFld ) { + updSysTrackFileFieldsEnabled(); + updStatusText(); } } } @@ -132,10 +133,10 @@ public void drop( DropTargetDropEvent e ) e.acceptDrop( DnDConstants.ACTION_COPY ); // Quelle nicht loeschen pasteFiles( e.getTransferable() ); } - } else if( src == this.dropTargetSysFile ) { + } else if( src == this.dropTargetSysTrackFile ) { File file = EmuUtil.fileDrop( this, e ); if( file != null ) { - addSysFile( file ); + addSysTrackFile( file ); } } } @@ -172,58 +173,68 @@ public void valueChanged( ListSelectionEvent e ) @Override protected boolean doAction( EventObject e ) { - boolean rv = false; updStatusText(); - if( e != null ) { - Object src = e.getSource(); - if( src == this.mnuClose ) { - rv = true; - doClose(); - } - else if( (src == this.mnuFileAdd) || (src == this.btnFileAdd) ) { - rv = true; - doFileAdd(); - } - else if( (src == this.mnuFileRemove) || (src == this.btnFileRemove) ) { - rv = true; - doFileRemove(); - } - else if( src == this.mnuSort ) { - rv = true; - this.tableModel.sortAscending( 0 ); - } - else if( (src == this.mnuSave) || (src == this.btnSave) ) { - rv = true; - doSave(); - } - else if( src == this.btnFileDown ) { - rv = true; - doFileDown(); - } - else if( src == this.btnFileUp ) { - rv = true; - doFileUp(); - } - else if( src == this.btnSysFileSelect ) { - rv = true; - doSysFileSelect(); - } - else if( src == this.mnuChangeUser ) { - rv = true; - doChangeUser(); - } - else if( src == this.mnuPaste ) { - rv = true; - doPaste(); - } - else if( src == this.btnSysFileRemove ) { - rv = true; - doSysFileRemove(); - } - else if( src == this.mnuHelpContent ) { - rv = true; - HelpFrm.open( "/help/disk/creatediskimg.htm" ); - } + boolean rv = false; + Object src = e.getSource(); + if( src == this.mnuClose ) { + rv = true; + doClose(); + } + else if( (src == this.mnuFileAdd) || (src == this.btnFileAdd) ) { + rv = true; + doFileAdd(); + } + else if( (src == this.mnuFileRemove) || (src == this.btnFileRemove) ) { + rv = true; + doFileRemove(); + } + else if( src == this.mnuNew ) { + rv = true; + doNew(); + } + else if( src == this.mnuSort ) { + rv = true; + this.tableModel.sortAscending( 0 ); + } + else if( (src == this.mnuSave) || (src == this.btnSave) ) { + rv = true; + doSave(); + } + else if( src == this.btnFileDown ) { + rv = true; + doFileDown(); + } + else if( src == this.btnFileUp ) { + rv = true; + doFileUp(); + } + else if( src == this.btnSysTrackFileSelect ) { + rv = true; + doSysTrackFileSelect(); + } + else if( src == this.mnuChangeAttrs ) { + rv = true; + doChangeAttrs(); + } + else if( src == this.mnuChangeUser ) { + rv = true; + doChangeUser(); + } + else if( src == this.mnuPaste ) { + rv = true; + doPaste(); + } + else if( src == this.mnuSelectAll ) { + rv = true; + doSelectAll(); + } + else if( src == this.btnSysTrackFileRemove ) { + rv = true; + doSysTrackFileRemove(); + } + else if( src == this.mnuHelpContent ) { + rv = true; + HelpFrm.open( "/help/disk/creatediskimg.htm" ); } return rv; } @@ -259,6 +270,39 @@ public boolean doClose() /* --- Aktionen --- */ + private void doChangeAttrs() + { + int[] rowNums = this.table.getSelectedRows(); + if( rowNums != null ) { + if( rowNums.length > 0 ) { + ChangeFileAttrsDlg dlg = new ChangeFileAttrsDlg( this ); + dlg.setVisible( true ); + Boolean readOnly = dlg.getReadOnlyValue(); + Boolean sysFile = dlg.getSystemFileValue(); + Boolean archive = dlg.getArchiveValue(); + if( (readOnly != null) || (sysFile != null) || (archive != null) ) { + for( int i = 0; i< rowNums.length; i++ ) { + int rowNum = rowNums[ i ]; + FileEntry entry = this.tableModel.getRow( rowNums[ i ] ); + if( entry != null ) { + if( readOnly != null ) { + entry.setReadOnly( readOnly.booleanValue() ); + } + if( sysFile != null ) { + entry.setSystemFile( sysFile.booleanValue() ); + } + if( archive != null ) { + entry.setArchive( archive.booleanValue() ); + } + this.tableModel.fireTableRowsUpdated( rowNum, rowNum ); + } + } + } + } + } + } + + private void doChangeUser() { int[] rowNums = this.table.getSelectedRows(); @@ -326,6 +370,34 @@ private void doChangeUser() } + public void doNew() + { + boolean status = true; + StringBuilder buf = new StringBuilder( 256 ); + if( this.dataChanged ) { + buf.append( "Die letzten \u00C3nderungen wurden nicht gespeichert!" ); + } + if( (this.tableModel.getRowCount() > 0) + || (this.fldSysTrackFileName.getFile() != null) ) + { + if( buf.length() > 0 ) { + buf.append( (char) '\n' ); + } + buf.append( "Die hinzugef\u00FCgten Dateien werden entfernt." ); + } + if( buf.length() > 0 ) { + status = BasicDlg.showConfirmDlg( this, buf.toString() ); + } + if( status ) { + this.tableModel.clear( true ); + this.fldSysTrackFileName.setFile( null ); + this.dataChanged = false; + this.lastOutFile = null; + updTitle(); + } + } + + private void doPaste() { if( this.clipboard != null ) { @@ -337,6 +409,15 @@ private void doPaste() } + private void doSelectAll() + { + int nRows = this.table.getRowCount(); + if( nRows > 0 ) { + this.table.setRowSelectionInterval( 0, nRows - 1 ); + } + } + + private void doSave() { boolean status = true; @@ -347,232 +428,141 @@ private void doSave() + "M\u00F6chten Sie eine leere Diskettenabbilddatei" + " erstellen?" ); } + int sysTracks = this.fmtSelectFld.getSysTracks(); + File sysTrackFile = this.fldSysTrackFileName.getFile(); + if( (sysTrackFile != null) && (sysTracks == 0) ) { + if( JOptionPane.showConfirmDialog( + this, + "Sie haben ein Format ohne Systemspuren ausgew\u00E4hlt,\n" + + "aber eine Datei f\u00FCr die Systemspuren" + + " angegeben.\n" + + "Diese Datei wird ignoriert.", + "Warnung", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE ) != JOptionPane.OK_OPTION ) + { + status = false; + } + } if( status ) { File file = EmuUtil.showFileSaveDlg( this, "Diskettenabbilddatei speichern", this.lastOutFile != null ? this.lastOutFile - : Main.getLastPathFile( "disk" ), + : Main.getLastDirFile( "disk" ), EmuUtil.getPlainDiskFileFilter(), EmuUtil.getAnaDiskFileFilter(), + EmuUtil.getCopyQMFileFilter(), EmuUtil.getDskFileFilter(), - EmuUtil.getImageDiskFileFilter() ); + EmuUtil.getImageDiskFileFilter(), + EmuUtil.getTeleDiskFileFilter() ); if( file != null ) { - boolean plainDisk = false; - boolean anaDisk = false; - boolean cpcDisk = false; - boolean imageDisk = false; - String fileName = file.getName(); + boolean plainDisk = false; + boolean anaDisk = false; + boolean cpcDisk = false; + boolean copyQMDisk = false; + boolean imageDisk = false; + boolean teleDisk = false; + String fileName = file.getName(); if( fileName != null ) { String lowerName = fileName.toLowerCase(); + if( lowerName.endsWith( ".gz" ) ) { + lowerName = lowerName.substring( 0, lowerName.length() - 3 ); + } plainDisk = TextUtil.endsWith( lowerName, DiskUtil.plainDiskFileExt ); - anaDisk = TextUtil.endsWith( + anaDisk = TextUtil.endsWith( lowerName, DiskUtil.anaDiskFileExt ); - cpcDisk = TextUtil.endsWith( + copyQMDisk = TextUtil.endsWith( + lowerName, + DiskUtil.copyQMFileExt ); + cpcDisk = TextUtil.endsWith( lowerName, DiskUtil.dskFileExt ); - imageDisk = TextUtil.endsWith( + imageDisk = TextUtil.endsWith( lowerName, DiskUtil.imageDiskFileExt ); + teleDisk = TextUtil.endsWith( + lowerName, + DiskUtil.teleDiskFileExt ); } - if( plainDisk || anaDisk || cpcDisk || imageDisk ) { - OutputStream out = null; + if( plainDisk || anaDisk || copyQMDisk || cpcDisk + || imageDisk || teleDisk ) + { + boolean cancelled = false; + OutputStream out = null; try { - int sides = this.fmtSelectFld.getSides(); - int cyls = this.fmtSelectFld.getCylinders(); - int sysTracks = this.fmtSelectFld.getSysTracks(); - int sectPerCyl = this.fmtSelectFld.getSectorsPerCylinder(); - int sectorSize = this.fmtSelectFld.getSectorSize(); - int blockSize = this.fmtSelectFld.getBlockSize(); - int dirBlocks = this.fmtSelectFld.getDirBlocks(); - int diskSize = sides * cyls * sectPerCyl * sectorSize; - int dstDirPos = sysTracks * sides * sectPerCyl * sectorSize; - int dirSize = dirBlocks * blockSize; - int begFileArea = dstDirPos + dirSize; - int dstFilePos = begFileArea; - File sysFile = null; - if( dstDirPos > 0 ) { - sysFile = this.fldSysFileName.getFile(); - if( sysFile != null ) { - if( sysFile.length() > dstDirPos ) { - throw new IOException( - "Die Datei f\u00FCr die Systemspuren" - + " ist zu gro\u00DF!" ); - } - } - } - if( (dstDirPos < 0) - || (dstDirPos >= dstFilePos) - || ((dstFilePos + (calcFileAreaKBytes( blockSize ) * 1024)) - > diskSize) ) - { - throw new IOException( - "Das ausgew\u00E4hlten Diskettenformat bietet nicht" - + " gen\u00FCgend Platz\n" - + "f\u00FCr die hinzugef\u00FCgten" - + " Dateien." ); - } - byte[] diskBuf = new byte[ diskSize ]; - if( dstDirPos > 0 ) { - Arrays.fill( - diskBuf, - 0, - Math.min( dstDirPos, diskBuf.length ), - (byte) 0 ); - if( sysFile != null ) { - readFile( sysFile, diskBuf, 0 ); - } + int sides = this.fmtSelectFld.getSides(); + int cyls = this.fmtSelectFld.getCylinders(); + int sectPerCyl = this.fmtSelectFld.getSectorsPerCylinder(); + int sectorSize = this.fmtSelectFld.getSectorSize(); + + DiskImgCreator diskImgCreator = new DiskImgCreator( + new NIOFileTimesViewFactory(), + sides, + cyls, + sysTracks, + sectPerCyl, + sectorSize, + this.fmtSelectFld.isBlockNum16Bit(), + this.fmtSelectFld.getBlockSize(), + this.fmtSelectFld.getDirBlocks(), + this.fmtSelectFld.isDateStamperEnabled() ); + if( (sysTrackFile != null) && (sysTracks > 0) ) { + diskImgCreator.fillSysTracks( sysTrackFile ); } - Arrays.fill( diskBuf, dstDirPos, diskBuf.length, (byte) 0xE5 ); - boolean blockNum16Bit = false; - int extendSize = 16 * blockSize; - if( this.fmtSelectFld.isBlockNum16Bit() ) { - blockNum16Bit = true; - extendSize = 8 * blockSize; - } - int blkNum = dirBlocks; - int nRows = this.tableModel.getRowCount(); + int nRows = this.tableModel.getRowCount(); for( int i = 0; i < nRows; i++ ) { FileEntry entry = this.tableModel.getRow( i ); if( entry != null ) { - String entryName = entry.getName(); - if( entryName != null ) { - int nBlocks = 0; - int nBytes = readFile( - entry.getFile(), - diskBuf, - dstFilePos ); - if( nBytes > 0 ) { - nBlocks = nBytes / blockSize; - if( (nBlocks * blockSize) < nBytes ) { - nBlocks++; - } - // Rest der Datei bis zum Blockende mit 0x1A auffuellen - int idxFrom = dstFilePos + nBytes; - int idxTo = Math.min( - dstFilePos + (nBlocks * blockSize), - diskBuf.length ); - if( (idxFrom >= 0) && (idxFrom < idxTo) ) { - Arrays.fill( diskBuf, idxFrom, idxTo, (byte) 0x1A ); + try { + diskImgCreator.addFile( + entry.getUserNum(), + entry.getName(), + entry.getFile(), + entry.isReadOnly(), + entry.isSystemFile(), + entry.isArchive() ); + } + catch( IOException ex ) { + fireSelectRowInterval( i, i ); + String msg = ex.getMessage(); + if( msg != null ) { + if( msg.isEmpty() ) { + msg = null; } } - dstFilePos += (nBlocks * blockSize); - - int extendNum = 0; - do { - if( dstDirPos >= begFileArea ) { - throw new IOException( "Directory zu klein" ); - } - - int entryBegPos = dstDirPos; - - diskBuf[ dstDirPos ] = (byte) 0; - Integer userNum = entry.getUserNum(); - if( userNum != null ) { - if( (userNum.intValue() > 0) - && (userNum.intValue() <= 15) ) - { - diskBuf[ dstDirPos ] = userNum.byteValue(); - } - } - dstDirPos++; - - int[] sizes = { 8, 3 }; - int len = entryName.length(); - int pos = 0; - for( int k = 0; k < sizes.length; k++ ) { - int n = sizes[ k ]; - while( (pos < len) && (n > 0) ) { - char ch = entryName.charAt( pos++ ); - if( ch == '.' ) { - break; - } - diskBuf[ dstDirPos++ ] = (byte) ch; - --n; - } - while( (n > 0) ) { - diskBuf[ dstDirPos++ ] = (byte) '\u0020'; - --n; - } - if( pos < len ) { - if( entryName.charAt( pos ) == '.' ) { - pos++; - } - } - } - diskBuf[ dstDirPos++ ] = (byte) (extendNum & 0x1F); - diskBuf[ dstDirPos++ ] = (byte) 0; - diskBuf[ dstDirPos++ ] = (byte) ((extendNum >> 5) & 0x3F); - extendNum++; - - if( entry.isReadOnly() ) { - diskBuf[ entryBegPos + 9 ] |= 0x80; - } - if( entry.isSystemFile() ) { - diskBuf[ entryBegPos + 10 ] |= 0x80; - } - if( entry.isArchived() ) { - diskBuf[ entryBegPos + 11 ] |= 0x80; - } - int nTmp = nBytes; - if( nTmp > extendSize ) { - nTmp = extendSize; - nBytes -= extendSize; - } - int entrySizeCode = (nTmp + 127) / 128; - if( (entrySizeCode & 0xFF) != entrySizeCode ) { - String msg = entry.getName() + ": Datei zu gro\u00DF"; - if( !blockNum16Bit ) { - msg = msg + "\nBei 8-Bit-Blocknummern betr\u00E4gt" - + " die max. Dateigr\u00F6\u00DFe 32640 Byte."; - } - throw new IOException( msg ); - } - diskBuf[ dstDirPos++ ] = (byte) entrySizeCode; - if( blockNum16Bit ) { - for( int k = 0; k < 8; k++ ) { - if( nBlocks > 0 ) { - if( blkNum > 0xFFFF ) { - throwBlockNumOverflow(); - } - diskBuf[ dstDirPos++ ] = (byte) blkNum; - diskBuf[ dstDirPos++ ] = (byte) (blkNum >> 8); - blkNum++; - --nBlocks; - } else { - diskBuf[ dstDirPos++ ] = (byte) 0; - diskBuf[ dstDirPos++ ] = (byte) 0; - } - } - } else { - for( int k = 0; k < 16; k++ ) { - if( nBlocks > 0 ) { - if( blkNum > 0xFF ) { - throwBlockNumOverflow(); - } - diskBuf[ dstDirPos++ ] = (byte) blkNum; - blkNum++; - --nBlocks; - } else { - diskBuf[ dstDirPos++ ] = (byte) 0; - } - } - } - } while( nBlocks > 0 ); + if( msg != null ) { + msg = entry.getName() + ":\n" + msg; + } else { + msg = entry.getName() + + " kann nicht hinzugef\u00FCgt werden."; + } + if( JOptionPane.showConfirmDialog( + this, + msg, + "Fehler", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.ERROR_MESSAGE ) != JOptionPane.OK_OPTION ) + { + cancelled = true; + break; + } } } } - if( plainDisk ) { - out = new FileOutputStream( file ); - out.write( diskBuf ); - out.close(); - out = null; - } else { - PlainDisk disk = PlainDisk.createForByteArray( + if( !cancelled ) { + byte[] diskBuf = diskImgCreator.getPlainDiskByteBuffer(); + if( plainDisk ) { + out = EmuUtil.createOptionalGZipOutputStream( file ); + out.write( diskBuf ); + out.close(); + out = null; + } else { + PlainDisk disk = PlainDisk.createForByteArray( this, file.getPath(), diskBuf, @@ -580,21 +570,28 @@ private void doSave() sides, cyls, sectPerCyl, - sectorSize ) ); - if( disk != null ) { - if( anaDisk ) { - AnaDisk.export( disk, file ); - } else if( cpcDisk ) { - CPCDisk.export( disk, file ); - } else if( imageDisk ) { - ImageDisk.export( disk, file, "Created by JKCEMU" ); + sectorSize ), + this.fmtSelectFld.getInterleave() ); + if( disk != null ) { + if( anaDisk ) { + AnaDisk.export( disk, file ); + } else if( copyQMDisk ) { + CopyQMDisk.export( disk, file, this.fldRemark.getText() ); + } else if( cpcDisk ) { + CPCDisk.export( disk, file ); + } else if( imageDisk ) { + ImageDisk.export( disk, file, this.fldRemark.getText() ); + } else if( teleDisk ) { + TeleDisk.export( disk, file, this.fldRemark.getText() ); + } } } + this.dataChanged = false; + this.lastOutFile = file; + updTitle(); + Main.setLastFile( file, "disk" ); + this.labelStatus.setText( "Diskettenabbilddatei gespeichert" ); } - this.dataChanged = false; - this.lastOutFile = file; - Main.setLastFile( file, "disk" ); - this.labelStatus.setText( "Diskettenabbilddatei gespeichert" ); } catch( Exception ex ) { BasicDlg.showErrorDlg( this, ex ); @@ -619,14 +616,19 @@ private void doSave() private void doFileAdd() { - File file = EmuUtil.showFileOpenDlg( - this, - "Datei hinzuf\u00FCgen", - Main.getLastPathFile( "software" ) ); - if( file != null ) { - if( addFile( file ) ) { - Main.setLastFile( file, "software" ); + java.util.List files = EmuUtil.showMultiFileOpenDlg( + this, + "Dateien hinzuf\u00FCgen", + Main.getLastDirFile( "software" ) ); + if( files != null ) { + int firstRowToSelect = this.table.getRowCount(); + for( File file : files ) { + if( addFile( file ) ) { + Main.setLastFile( file, "software" ); + } } + updSelectAllEnabled(); + fireSelectRowInterval( firstRowToSelect, this.table.getRowCount() - 1 ); } } @@ -642,6 +644,7 @@ private void doFileRemove() this.mnuSort.setEnabled( this.tableModel.getRowCount() > 1 ); this.dataChanged = true; } + updSelectAllEnabled(); updStatusText(); } } @@ -650,21 +653,27 @@ private void doFileRemove() private void doFileDown() { - int[] rowNums = this.table.getSelectedRows(); + final int[] rowNums = this.table.getSelectedRows(); if( rowNums != null ) { - if( rowNums.length == 1 ) { - int row = rowNums[ 0 ]; - if( (row >= 0) && (row < (this.tableModel.getRowCount() - 1)) ) { - FileEntry e1 = this.tableModel.getRow( row ); - FileEntry e2 = this.tableModel.getRow( row + 1 ); - if( (e1 != null) && (e2 != null) ) { - this.tableModel.setRow( row, e2 ); - this.tableModel.setRow( row + 1, e1 ); - this.tableModel.fireTableDataChanged(); - fireSelectRow( row + 1 ); - this.dataChanged = true; + if( rowNums.length > 0) { + Arrays.sort( rowNums ); + if( (rowNums[ rowNums.length - 1 ] + 1) + < this.tableModel.getRowCount() ) + { + for( int i = rowNums.length - 1 ; i >= 0; --i ) { + int row = rowNums[ i ]; + FileEntry e1 = this.tableModel.getRow( row ); + FileEntry e2 = this.tableModel.getRow( row + 1 ); + if( (e1 != null) && (e2 != null) ) { + this.tableModel.setRow( row, e2 ); + this.tableModel.setRow( row + 1, e1 ); + this.tableModel.fireTableDataChanged(); + this.dataChanged = true; + } + rowNums[ i ]++; } } + fireSelectRows( rowNums ); } } } @@ -672,42 +681,46 @@ private void doFileDown() private void doFileUp() { - int[] rowNums = this.table.getSelectedRows(); + final int[] rowNums = this.table.getSelectedRows(); if( rowNums != null ) { - if( rowNums.length == 1 ) { - int row = rowNums[ 0 ]; - if( (row >= 1) && (row < this.tableModel.getRowCount()) ) { - FileEntry e1 = this.tableModel.getRow( row - 1); - FileEntry e2 = this.tableModel.getRow( row ); - if( (e1 != null) && (e2 != null) ) { - this.tableModel.setRow( row, e1 ); - this.tableModel.setRow( row - 1, e2 ); - this.tableModel.fireTableDataChanged(); - fireSelectRow( row - 1 ); - this.dataChanged = true; + if( rowNums.length > 0) { + Arrays.sort( rowNums ); + if( rowNums[ 0 ] > 0 ) { + for( int i = 0; i < rowNums.length; i++ ) { + int row = rowNums[ i ]; + FileEntry e1 = this.tableModel.getRow( row ); + FileEntry e2 = this.tableModel.getRow( row - 1 ); + if( (e1 != null) && (e2 != null) ) { + this.tableModel.setRow( row, e2 ); + this.tableModel.setRow( row - 1, e1 ); + this.tableModel.fireTableDataChanged(); + this.dataChanged = true; + } + --rowNums[ i ]; } } + fireSelectRows( rowNums ); } } } - private void doSysFileSelect() + private void doSysTrackFileSelect() { File file = EmuUtil.showFileOpenDlg( this, "Datei \u00FCffnen", - Main.getLastPathFile( "software" ) ); + Main.getLastDirFile( "software" ) ); if( file != null ) { - addSysFile( file ); + addSysTrackFile( file ); } } - private void doSysFileRemove() + private void doSysTrackFileRemove() { - this.fldSysFileName.setFile( null ); - this.btnSysFileRemove.setEnabled( false ); + this.fldSysTrackFileName.setFile( null ); + this.btnSysTrackFileRemove.setEnabled( false ); } @@ -716,9 +729,9 @@ private void doSysFileRemove() private DiskImgCreateFrm() { this.clipboard = null; - this.lastOutFile = null; this.dataChanged = false; - setTitle( "JKCEMU CP/M-Diskettenabbilddatei erstellen" ); + this.lastOutFile = null; + updTitle(); Main.updIcon( this ); Toolkit tk = getToolkit(); @@ -737,6 +750,10 @@ private DiskImgCreateFrm() mnuFile.setMnemonic( KeyEvent.VK_D ); mnuBar.add( mnuFile ); + this.mnuNew = createJMenuItem( "Neue Diskettenabbilddatei" ); + mnuFile.add( this.mnuNew ); + mnuFile.addSeparator(); + this.mnuFileAdd = createJMenuItem( "Hinzuf\u00FCgen..." ); mnuFile.add( this.mnuFileAdd ); @@ -768,8 +785,16 @@ private DiskImgCreateFrm() mnuEdit.add( this.mnuPaste ); mnuEdit.addSeparator(); + this.mnuChangeAttrs = createJMenuItem( "Dateiattribute \u00E4ndern..." ); + mnuEdit.add( this.mnuChangeAttrs ); + this.mnuChangeUser = createJMenuItem( "User-Bereich \u00E4ndern..." ); mnuEdit.add( this.mnuChangeUser ); + mnuEdit.addSeparator(); + + this.mnuSelectAll = createJMenuItem( "Alles ausw\u00E4hlem" ); + this.mnuSelectAll.setEnabled( false ); + mnuEdit.add( this.mnuSelectAll ); // Menu Hilfe @@ -845,7 +870,7 @@ private DiskImgCreateFrm() FileTableModel.Column.USER_NUM, FileTableModel.Column.READ_ONLY, FileTableModel.Column.SYSTEM_FILE, - FileTableModel.Column.ARCHIVED ); + FileTableModel.Column.ARCHIVE ); this.table = new JTable( this.tableModel ); this.table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF ); @@ -887,40 +912,89 @@ private DiskImgCreateFrm() new Insets( 5, 5, 0, 5 ), 0, 0 ); - this.fmtSelectFld = new FloppyDiskFormatSelectFld(); + this.fmtSelectFld = new FloppyDiskFormatSelectFld( true ); panelFmt.add( this.fmtSelectFld, gbcFmt ); - this.labelSysFile = new JLabel( "Datei f\u00FCr Systemspuren:" ); - gbcFmt.insets.top = 5; - gbcFmt.insets.left = 5; - gbcFmt.gridwidth = 1; - gbcFmt.gridx = 0; + this.labelSysTrackFile = new JLabel( "Datei f\u00FCr Systemspuren:" ); + gbcFmt.insets.top = 5; + gbcFmt.insets.left = 5; + gbcFmt.insets.bottom = 5; + gbcFmt.gridwidth = 1; + gbcFmt.gridx = 0; gbcFmt.gridy++; - panelFmt.add( this.labelSysFile, gbcFmt ); + panelFmt.add( this.labelSysTrackFile, gbcFmt ); - this.fldSysFileName = new FileNameFld(); - this.fldSysFileName.setEditable( false ); + this.fldSysTrackFileName = new FileNameFld(); + this.fldSysTrackFileName.setEditable( false ); gbcFmt.fill = GridBagConstraints.HORIZONTAL; gbcFmt.weightx = 1.0; gbcFmt.gridwidth = 5; gbcFmt.gridx++; - panelFmt.add( this.fldSysFileName, gbcFmt ); + panelFmt.add( this.fldSysTrackFileName, gbcFmt ); - this.btnSysFileSelect = createImageButton( + this.btnSysTrackFileSelect = createImageButton( "/images/file/open.png", "\u00D6ffnen" ); gbcFmt.fill = GridBagConstraints.NONE; gbcFmt.weightx = 0.0; gbcFmt.gridwidth = 1; gbcFmt.gridx += 5; - panelFmt.add( this.btnSysFileSelect, gbcFmt ); + panelFmt.add( this.btnSysTrackFileSelect, gbcFmt ); - this.btnSysFileRemove = createImageButton( + this.btnSysTrackFileRemove = createImageButton( "/images/file/delete.png", "\u00D6ffnen" ); - this.btnSysFileRemove.setEnabled( false ); + this.btnSysTrackFileRemove.setEnabled( false ); gbcFmt.gridx++; - panelFmt.add( this.btnSysFileRemove, gbcFmt ); + panelFmt.add( this.btnSysTrackFileRemove, gbcFmt ); + + + // Kommentar + JPanel panelRemark = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Kommentar", new JScrollPane( panelRemark ) ); + + GridBagConstraints gbcRemark = new GridBagConstraints( + 0, 0, + GridBagConstraints.REMAINDER, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + panelRemark.add( + new JLabel( "Kommentar zur Diskettenabbilddatei:" ), + gbcRemark ); + + this.fldRemark = new JTextField( "Erzeugt mit JKCEMU" ); + gbcRemark.fill = GridBagConstraints.HORIZONTAL; + gbcRemark.weightx = 1.0; + gbcRemark.insets.top = 0; + gbcRemark.insets.bottom = 5; + gbcRemark.gridy++; + panelRemark.add( this.fldRemark, gbcRemark ); + + JLabel label = new JLabel( "Achtung!" ); + Font font = label.getFont(); + if( font != null ) { + label.setFont( font.deriveFont( Font.BOLD ) ); + } + gbcRemark.fill = GridBagConstraints.NONE; + gbcRemark.weightx = 0.0; + gbcRemark.insets.top = 10; + gbcRemark.insets.bottom = 0; + gbcRemark.gridy++; + panelRemark.add( label, gbcRemark ); + + gbcRemark.fill = GridBagConstraints.NONE; + gbcRemark.weightx = 0.0; + gbcRemark.insets.top = 0; + gbcRemark.insets.bottom = 5; + gbcRemark.gridy++; + panelRemark.add( + new JLabel( "Ein Kommentar wird nur bei CopyQM-, ImageDisk-" + + " und TeleDisk-Dateien unterst\u00FCtzt." ), + gbcRemark ); // Statuszeile @@ -950,13 +1024,15 @@ private DiskImgCreateFrm() // Drop-Ziele - this.dropTargetFile1 = new DropTarget( this.table, this ); - this.dropTargetFile2 = new DropTarget( this.scrollPane, this ); - this.dropTargetSysFile = new DropTarget( this.fldSysFileName, this ); + this.dropTargetFile1 = new DropTarget( this.table, this ); + this.dropTargetFile2 = new DropTarget( this.scrollPane, this ); + this.dropTargetSysTrackFile = new DropTarget( + this.fldSysTrackFileName, + this ); this.dropTargetFile1.setActive( true ); this.dropTargetFile1.setActive( true ); - this.dropTargetSysFile.setActive( true ); + this.dropTargetSysTrackFile.setActive( true ); // sonstiges @@ -969,6 +1045,47 @@ private DiskImgCreateFrm() /* --- private Methoden --- */ private boolean addFile( File file ) + { + boolean rv = false; + boolean done = false; + if( file.isDirectory() ) { + String s = file.getName(); + if( s != null ) { + try { + int userNum = Integer.parseInt( s ); + if( (userNum >= 1) && (userNum <= 15) ) { + done = true; + if( BasicDlg.showYesNoDlg( + this, + "Sollen die Dateien im Verzeichnis " + s + + "\nin der Benutzerebene " + s + + " hinzugef\u00FCgt werden?" ) ) + { + File[] files = file.listFiles(); + if( files != null ) { + for( File f : files ) { + if( f.isFile() ) { + rv = addFileInternal( userNum, f ); + if( !rv ) { + break; + } + } + } + } + } + } + } + catch( NumberFormatException ex ) {} + } + } + if( !done ) { + rv = addFileInternal( 0, file ); + } + return rv; + } + + + private boolean addFileInternal( int userNum, File file ) { boolean rv = false; long size = -1L; @@ -1004,7 +1121,7 @@ else if( size > (1024 * 16 * 32) ) { { if( BasicDlg.showYesNoDlg( this, - "Datei " + DirectoryFloppyDisk.SYS_FILE_NAME + ":\n" + DirectoryFloppyDisk.SYS_FILE_NAME + ":\n" + "JKCEMU verwendet Dateien mit diesem Namen" + " f\u00FCr den Inhalt der Systemspuren.\n" + "M\u00F6chten Sie deshalb nun diese Datei" @@ -1012,8 +1129,8 @@ else if( size > (1024 * 16 * 32) ) { + "anstelle Sie als gew\u00F6hnliche Datei" + " im Directory einzubinden?" ) ) { - this.fldSysFileName.setFile( file ); - this.btnSysFileRemove.setEnabled( true ); + this.fldSysTrackFileName.setFile( file ); + this.btnSysTrackFileRemove.setEnabled( true ); file = null; rv = true; } @@ -1109,7 +1226,7 @@ else if( ch == '.' ) { "Es existiert bereits ein Eintrag mit diesem Namen." ); } else { FileEntry entry = new FileEntry( entryName ); - entry.setUserNum( new Integer( 0 ) ); + entry.setUserNum( new Integer( userNum ) ); if( size >= 0 ) { entry.setSize( new Long( size ) ); } @@ -1120,12 +1237,10 @@ else if( ch == '.' ) { entry.setFile( file ); entry.setReadOnly( !file.canWrite() ); entry.setSystemFile( false ); - entry.setArchived( false ); + entry.setArchive( false ); this.tableModel.addRow( entry, true ); - nRows = this.tableModel.getRowCount(); - fireSelectRow( nRows - 1 ); updStatusText(); - this.mnuSort.setEnabled( nRows > 1 ); + this.mnuSort.setEnabled( this.tableModel.getRowCount() > 1 ); this.dataChanged = true; rv = true; } @@ -1135,7 +1250,7 @@ else if( ch == '.' ) { } - private void addSysFile( File file ) + private void addSysTrackFile( File file ) { String errMsg = null; if( !file.exists() ) { @@ -1147,35 +1262,12 @@ else if( !file.isFile() ) { if( errMsg != null ) { BasicDlg.showErrorDlg( this, errMsg ); } else { - this.fldSysFileName.setFile( file ); - this.btnSysFileRemove.setEnabled( true ); + this.fldSysTrackFileName.setFile( file ); + this.btnSysTrackFileRemove.setEnabled( true ); } } - private int calcFileAreaKBytes( int blockSize ) - { - int kbytes = 0; - int nRows = this.tableModel.getRowCount(); - for( int i = 0; i < nRows; i++ ) { - FileEntry entry = this.tableModel.getRow( i ); - if( entry != null ) { - Long size = entry.getSize(); - if( size != null ) { - if( size.longValue() > 0 ) { - int nBlocks = (int) (size.longValue() / blockSize); - if( ((long) nBlocks * (long) blockSize) < size.longValue() ) { - nBlocks++; - } - kbytes += (nBlocks * blockSize / 1024); - } - } - } - } - return kbytes; - } - - private static String createEntryName( String fileName ) { StringBuilder buf = new StringBuilder( 12 ); @@ -1211,20 +1303,7 @@ private static String createEntryName( String fileName ) } - private static JComboBox createJComboBox( int... items ) - { - JComboBox combo = new JComboBox(); - combo.setEditable( false ); - if( items != null ) { - for( int i = 0; i < items.length; i++ ) { - combo.addItem( new Integer( items[ i ] ) ); - } - } - return combo; - } - - - private void fireSelectRow( final int row ) + private void fireSelectRows( final int[] rowNums ) { final JTable table = this.table; EventQueue.invokeLater( @@ -1233,41 +1312,38 @@ private void fireSelectRow( final int row ) @Override public void run() { - if( (row >= 0) && (row < table.getRowCount()) ) { - table.setRowSelectionInterval( row, row ); + table.clearSelection(); + for( int row : rowNums ) { + table.addRowSelectionInterval( row, row ); } } } ); } - private int getIntValue( JComboBox combo ) - { - int rv = 0; - if( combo != null ) { - Object o = combo.getSelectedItem(); - if( o != null ) { - if( o instanceof Number ) { - rv = ((Number) o).intValue(); - } - } - } - return rv; - } - - - private int getIntValue( JSpinner spinner ) + private void fireSelectRowInterval( final int begRow, final int endRow ) { - int rv = 0; - if( spinner != null ) { - Object o = spinner.getValue(); - if( o != null ) { - if( o instanceof Number ) { - rv = ((Number) o).intValue(); - } - } - } - return rv; + final JTable table = this.table; + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + try { + int nRows = table.getRowCount(); + if( (begRow >= 0) + && (begRow < nRows) + && (begRow <= endRow) ) + { + table.setRowSelectionInterval( + begRow, + Math.min( endRow, nRows -1 ) ); + } + } + catch( IllegalArgumentException ex ) {} + } + } ); } @@ -1278,6 +1354,7 @@ private void pasteFiles( Transferable t ) Object o = t.getTransferData( DataFlavor.javaFileListFlavor ); if( o != null ) { if( o instanceof Collection ) { + int firstRowToSelect = this.table.getRowCount(); for( Object item : (Collection) o ) { if( item != null ) { File file = null; @@ -1293,94 +1370,51 @@ private void pasteFiles( Transferable t ) } } } + fireSelectRowInterval( + firstRowToSelect, + this.table.getRowCount() - 1 ); } } } } catch( IOException ex ) {} catch( UnsupportedFlavorException ex ) {} - } - - - private int readFile( File file, byte[] buf, int pos ) throws IOException - { - int rv = 0; - if( file != null ) { - InputStream in = null; - try { - in = new FileInputStream( file ); - - int n = 0; - do { - rv += n; - pos += n; - - int len = buf.length - pos; - if( len > 0 ) { - n = in.read( buf, pos, len ); - } else { - break; - } - } while( n > 0 ); - if( pos >= buf.length ) { - if( in.read() == -1 ) { - throw new IOException( - "Die Diskettenabbilddatei bietet nicht gen\u00FCgend Platz!" ); - } - } - } - finally { - EmuUtil.doClose( in ); - } + finally { + updSelectAllEnabled(); } - return rv; - } - - - private static void setValue( JComboBox combo, int value ) - { - if( combo != null ) - combo.setSelectedItem( new Integer( value ) ); - } - - - private static void setValue( JSpinner spinner, int value ) - { - if( spinner != null ) { - try { - spinner.setValue( new Integer( value ) ); - } - catch( IllegalArgumentException ex ) {} - } - } - - - private static void throwBlockNumOverflow() throws IOException - { - throw new IOException( "Blocknummernüberlauf:\n" - + "Die Anzahl der möglichen Blocknummern reicht nicht aus." ); } private void updActionButtons() { - int rowNum = -1; - int rowCnt = 0; - int nSel = 0; - boolean fileTab = (this.tabbedPane.getSelectedIndex() == 0); + boolean stateSelected = false; + boolean stateDown = false; + boolean stateUp = false; + boolean fileTab = (this.tabbedPane.getSelectedIndex() == 0); if( fileTab ) { - nSel = this.table.getSelectedRowCount(); - if( nSel == 1 ) { - rowNum = this.table.getSelectedRow(); - rowCnt = this.table.getRowCount(); + int[] rowNums = this.table.getSelectedRows(); + if( rowNums != null ) { + if( rowNums.length > 0 ) { + stateSelected = true; + Arrays.sort( rowNums ); + if( (rowNums[ rowNums.length - 1 ] + 1) + < this.tableModel.getRowCount() ) + { + stateDown = true; + } + if( rowNums[ 0 ] > 0 ) { + stateUp = true; + } + } } } this.btnFileAdd.setEnabled( fileTab ); - this.mnuFileRemove.setEnabled( nSel > 0 ); - this.btnFileRemove.setEnabled( nSel > 0 ); - this.btnFileDown.setEnabled( (rowNum >= 0) && (rowNum < (rowCnt - 1)) ); - this.btnFileUp.setEnabled( (rowNum >= 1) && (rowNum < rowCnt) ); - this.mnuChangeUser.setEnabled( nSel > 0 ); + this.mnuFileRemove.setEnabled( stateSelected ); + this.btnFileRemove.setEnabled( stateSelected ); + this.btnFileDown.setEnabled( stateDown ); + this.btnFileUp.setEnabled( stateUp ); + this.mnuChangeAttrs.setEnabled( stateSelected ); + this.mnuChangeUser.setEnabled( stateSelected ); } @@ -1388,8 +1422,9 @@ private void updBgColor() { Color color = this.table.getBackground(); JViewport vp = this.scrollPane.getViewport(); - if( (color != null) && (vp != null) ) + if( (color != null) && (vp != null) ) { vp.setBackground( color ); + } } @@ -1407,6 +1442,12 @@ private void updPasteButton() } + private void updSelectAllEnabled() + { + this.mnuSelectAll.setEnabled( this.table.getRowCount() > 0 ); + } + + private void updStatusText() { String text = "Bereit"; @@ -1462,14 +1503,30 @@ private void updStatusText() } - private void updSysFileFieldsEnabled() + private void updSysTrackFileFieldsEnabled() { boolean state = (this.fmtSelectFld.getSysTracks() > 0); - this.labelSysFile.setEnabled( state ); - this.fldSysFileName.setEnabled( state ); - this.btnSysFileSelect.setEnabled( state ); - this.btnSysFileRemove.setEnabled( - state && (this.fldSysFileName.getFile() != null) ); + this.labelSysTrackFile.setEnabled( state ); + this.fldSysTrackFileName.setEnabled( state ); + this.btnSysTrackFileSelect.setEnabled( state ); + this.btnSysTrackFileRemove.setEnabled( + state && (this.fldSysTrackFileName.getFile() != null) ); } -} + + private void updTitle() + { + String text = "JKCEMU CP/M-Diskettenabbilddatei erstellen"; + if( this.lastOutFile != null ) { + StringBuilder buf = new StringBuilder( 256 ); + buf.append( text ); + buf.append( ": " ); + String fName = this.lastOutFile.getName(); + if( fName != null ) { + buf.append( fName ); + } + text = buf.toString(); + } + setTitle( text ); + } +} diff --git a/src/jkcemu/disk/DiskImgCreator.java b/src/jkcemu/disk/DiskImgCreator.java new file mode 100644 index 0000000..361ce9d --- /dev/null +++ b/src/jkcemu/disk/DiskImgCreator.java @@ -0,0 +1,403 @@ +/* + * (c) 2009-2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Erzeugen einer Diskettenabbilddatei + */ + +package jkcemu.disk; + +import java.io.*; +import java.lang.*; +import java.util.*; +import jkcemu.base.*; + + +public class DiskImgCreator +{ + private FileTimesViewFactory ftvFactory; + private boolean blockNum16Bit; + private int extentBlocks; + private int blockSize; + private int dirBlocks; + private int begDirArea; + private int begFileArea; + private int dstDirPos; + private int dstFilePos; + private int blockNum; + private int begTimeFile; + private int dstTimeFile; + private int endTimeFile; + private Calendar calendar; + private byte[] diskBuf; + + + public DiskImgCreator( + FileTimesViewFactory fileTimesViewFactory, + int sides, + int cyls, + int sysTracks, + int sectPerCyl, + int sectorSize, + boolean blockNum16Bit, + int blockSize, + int dirBlocks, + boolean dateStamper ) throws IOException + { + this.ftvFactory = fileTimesViewFactory; + this.blockNum16Bit = blockNum16Bit; + this.blockSize = blockSize; + this.dirBlocks = dirBlocks; + this.diskBuf = new byte[ sides * cyls * sectPerCyl * sectorSize ]; + this.begDirArea = sysTracks * sides * sectPerCyl * sectorSize; + this.begFileArea = this.begDirArea + (this.dirBlocks * this.blockSize); + this.dstDirPos = this.begDirArea; + this.dstFilePos = this.begFileArea; + this.extentBlocks = (this.blockNum16Bit ? 8 : 16); + this.blockNum = this.dirBlocks; + this.begTimeFile = -1; + this.dstTimeFile = -1; + this.endTimeFile = -1; + this.calendar = null; + if( this.begDirArea > 0 ) { + Arrays.fill( + this.diskBuf, + 0, + Math.min( this.begDirArea, this.diskBuf.length ), + (byte) 0x00 ); + } + if( this.begDirArea < this.diskBuf.length ) { + Arrays.fill( + this.diskBuf, + this.begDirArea, + this.diskBuf.length, + (byte) 0xE5 ); + } + if( dateStamper ) { + byte[] timeBytes = new byte[ this.dirBlocks * this.blockSize / 2 ]; + Arrays.fill( timeBytes, (byte) 0x00 ); + String text = "!!!TIME\u0092"; + int textLen = text.length(); + int srcPos = 0; + int dstPos = 0x0F; + while( dstPos < timeBytes.length ) { + if( srcPos >= textLen ) { + srcPos = 0; + } + timeBytes[ dstPos ] = (byte) text.charAt( srcPos++ ); + dstPos += 0x10; + } + try( ByteArrayInputStream in = new ByteArrayInputStream( timeBytes ) ) { + this.begTimeFile = this.dstFilePos; + addFile( + 0, + DateStamper.FILENAME, + in, + false, + false, + false, + null ); + this.dstTimeFile = this.begTimeFile + 0x10; + this.endTimeFile = this.dstFilePos; + } + this.calendar = Calendar.getInstance(); + } + } + + + /* + * Rueckgabewert: + * Anzahl der Directory-Eintraege + */ + public int addFile( + int userNum, + String entryName, + File file, + boolean readOnly, + boolean sysFile, + boolean archived ) throws IOException + { + int rv = 0; + if( (entryName != null) && (file != null) ) { + if( (this.begTimeFile >= 0) + && entryName.equals( DateStamper.FILENAME ) ) + { + throw new IOException( "Bei einem Diskettenformat mit" + + " DateStamper-Unterst\u00FCtzung\n" + + "werden die Zeitstempel in der Datei " + + DateStamper.FILENAME + " gespeichert.\n" + + "Diese Datei wird automatisch angelegt und" + + " kann deshalb\n" + + "nicht vom Anwender hinzugef\u00FCgt werden." ); + } + InputStream in = null; + try { + in = new BufferedInputStream( new FileInputStream( file ) ); + rv = addFile( + userNum, + entryName, + in, + readOnly, + sysFile, + archived, + this.ftvFactory.getFileTimesView( file ) ); + } + finally { + EmuUtil.doClose( in ); + } + } + return rv; + } + + + public void fillSysTracks( File file ) throws IOException + { + if( file != null ) { + InputStream in = null; + try { + in = new BufferedInputStream( new FileInputStream( file ) ); + int p = 0; + int b = in.read(); + while( b >= 0 ) { + if( p >= this.begDirArea ) { + throw new IOException( + "Datei f\u00FCr Systemspuren ist zu gro\u00DF!" ); + } + this.diskBuf[ p++ ] = (byte) b; + b = in.read(); + } + } + finally { + EmuUtil.doClose( in ); + } + } + } + + + public byte[] getPlainDiskByteBuffer() + { + // DateStamper Pruefsummen berechnen + if( (this.begTimeFile >= 0) && (this.begTimeFile < this.endTimeFile) ) { + int pos = this.begTimeFile; + while( pos < this.endTimeFile ) { + int cks = 0; + for( int i = 0; (i < 0x7F) && (pos < this.endTimeFile); i++ ) { + cks += ((int) this.diskBuf[ pos++ ] & 0xFF); + } + if( pos < this.endTimeFile ) { + this.diskBuf[ pos++ ] = (byte) cks; + } + } + } + return this.diskBuf; + } + + + /* --- private Methoden --- */ + + private int addDirEntry( + int userNum, + String entryName, + boolean readOnly, + boolean sysFile, + boolean archived, + FileTimesView fileTimesView, + int extentNum ) throws IOException + { + if( this.dstDirPos >= this.begFileArea ) { + throw new IOException( "Directory voll" ); + } + + int entryBegPos = this.dstDirPos; + this.diskBuf[ this.dstDirPos++ ] = (byte) (userNum & 0x0F); + + int[] sizes = { 8, 3 }; + int len = entryName.length(); + int pos = 0; + for( int i = 0; i < sizes.length; i++ ) { + int n = sizes[ i ]; + while( (pos < len) && (n > 0) ) { + char ch = entryName.charAt( pos++ ); + if( ch == '.' ) { + break; + } + this.diskBuf[ this.dstDirPos++ ] = (byte) ch; + --n; + } + while( (n > 0) ) { + this.diskBuf[ this.dstDirPos++ ] = (byte) '\u0020'; + --n; + } + if( pos < len ) { + if( entryName.charAt( pos ) == '.' ) { + pos++; + } + } + } + this.diskBuf[ this.dstDirPos++ ] = (byte) (extentNum & 0x1F); + this.diskBuf[ this.dstDirPos++ ] = (byte) 0; + this.diskBuf[ this.dstDirPos++ ] = (byte) ((extentNum >> 5) & 0x3F); + + if( readOnly ) { + this.diskBuf[ entryBegPos + 9 ] |= 0x80; + } + if( sysFile ) { + this.diskBuf[ entryBegPos + 10 ] |= 0x80; + } + if( archived ) { + this.diskBuf[ entryBegPos + 11 ] |= 0x80; + } + for( int i = 0; i < 17; i++ ) { + this.diskBuf[ this.dstDirPos++ ] = (byte) 0; + } + if( (this.dstTimeFile >= 0) && (this.dstTimeFile < this.endTimeFile) ) { + if( fileTimesView != null ) { + writeTimeEntry( fileTimesView.getCreationMillis() ); + writeTimeEntry( fileTimesView.getLastAccessMillis() ); + writeTimeEntry( fileTimesView.getLastModifiedMillis() ); + this.dstTimeFile++; + } else { + this.dstTimeFile += 0x10; + } + } + return entryBegPos; + } + + + /* + * Rueckgabewert: + * Anzahl der Directory-Eintraege + */ + private int addFile( + int userNum, + String entryName, + InputStream in, + boolean readOnly, + boolean sysFile, + boolean archived, + FileTimesView fileTimesView ) throws IOException + { + int extentNum = 0; + int b = in.read(); + if( b >= 0 ) { + while( b >= 0 ) { + if( extentNum >= 0x800 ) { + throwFileTooBig( entryName ); + } + int dirPos = addDirEntry( + userNum, + entryName, + readOnly, + sysFile, + archived, + fileTimesView, + extentNum++ ); + int blkPos = dirPos + 0x10; + + // Extent schreiben + int begExtentPos = this.dstFilePos; + int endExtentPos = begExtentPos; + int remainExtentBlocks = this.extentBlocks; + while( (remainExtentBlocks > 0) && (b >= 0) ) { + int remainBlockBytes = this.blockSize; + while( (remainBlockBytes > 0) && (b >= 0) ) { + if( this.dstFilePos >= this.diskBuf.length ) { + throw new IOException( + "Das ausgew\u00E4hlten Diskettenformat bietet nicht" + + " gen\u00FCgend Platz." ); + } + this.diskBuf[ this.dstFilePos++ ] = (byte) b; + --remainBlockBytes; + b = in.read(); + } + endExtentPos = this.dstFilePos; + int endOf128BlkPos = (this.dstFilePos + 127) & ~0x7F; + if( endOf128BlkPos > this.diskBuf.length ) { + endOf128BlkPos = this.diskBuf.length; + } + while( (remainBlockBytes > 0) + && (this.dstFilePos < endOf128BlkPos) ) + { + this.diskBuf[ this.dstFilePos++ ] = (byte) 0x1A; + --remainBlockBytes; + } + while( (remainBlockBytes > 0) + && (this.dstFilePos < this.diskBuf.length) ) + { + this.diskBuf[ this.dstFilePos++ ] = (byte) 0x00; + --remainBlockBytes; + } + if( this.blockNum16Bit ) { + this.diskBuf[ blkPos++ ] = (byte) (this.blockNum & 0xFF); + this.diskBuf[ blkPos++ ] = (byte) ((this.blockNum >> 8) & 0xFF); + this.blockNum++; + if( this.blockNum > 0xFFFF ) { + throwFileTooBig( entryName ); + } + } else { + this.diskBuf[ blkPos++ ] = (byte) this.blockNum++; + if( this.blockNum > 0xFF ) { + throwFileTooBig( entryName ); + } + } + --remainExtentBlocks; + } + this.diskBuf[ dirPos + 15 ] = + (byte) ((endExtentPos - begExtentPos + 127) / 128); + } + } else { + // leere Datei + addDirEntry( + userNum, + entryName, + readOnly, + sysFile, + archived, + fileTimesView, + extentNum++ ); + } + return extentNum; + } + + + private static void throwFileTooBig( String entryName ) throws IOException + { + throw new IOException( entryName + ": Datei zu gro\u00DF!" ); + } + + + private static byte toBcdByte( int value ) + { + return (byte) (((((value / 10) % 10) << 4) & 0xF0) + | ((value % 10) & 0x0F)); + } + + + private void writeTimeEntry( Long millis ) + { + if( (this.dstTimeFile + 4) < this.endTimeFile ) { + boolean done = false; + if( (this.calendar != null) && (millis != null) ) { + this.calendar.clear(); + this.calendar.setTimeInMillis( millis.longValue() ); + int year = this.calendar.get( Calendar.YEAR ); + if( (year >= 1978) && (year < 2078) ) { + this.diskBuf[ this.dstTimeFile++ ] = toBcdByte( year ); + this.diskBuf[ this.dstTimeFile++ ] = + toBcdByte( this.calendar.get( Calendar.MONTH ) + 1 ); + this.diskBuf[ this.dstTimeFile++ ] = + toBcdByte( this.calendar.get( Calendar.DAY_OF_MONTH ) ); + this.diskBuf[ this.dstTimeFile++ ] = + toBcdByte( this.calendar.get( Calendar.HOUR_OF_DAY ) ); + this.diskBuf[ this.dstTimeFile++ ] = + toBcdByte( this.calendar.get( Calendar.MINUTE ) ); + done = true; + } + } + if( !done ) { + this.dstTimeFile += 5; + } + } + } +} diff --git a/src/jkcemu/disk/DiskImgProcessDlg.java b/src/jkcemu/disk/DiskImgProcessDlg.java index fba52ac..15cecd0 100644 --- a/src/jkcemu/disk/DiskImgProcessDlg.java +++ b/src/jkcemu/disk/DiskImgProcessDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2012 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -46,8 +46,8 @@ public static void createDiskImageFromDrive( Frame owner ) if( drvFileName != null ) { File imgFile = EmuUtil.showFileSaveDlg( owner, - "Einfache Diskettenabbilddatei speichern", - Main.getLastPathFile( "disk" ), + "Einfache Abbilddatei speichern", + Main.getLastDirFile( "disk" ), EmuUtil.getPlainDiskFileFilter(), EmuUtil.getISOFileFilter() ); if( imgFile != null ) { @@ -77,48 +77,51 @@ public static void writeDiskImageToDrive( Frame owner ) { File imgFile = EmuUtil.showFileOpenDlg( owner, - "Einfach Diskettenabbilddatei \u00F6ffnen", - Main.getLastPathFile( "disk" ), + "Einfache Abbilddatei \u00F6ffnen", + Main.getLastDirFile( "disk" ), EmuUtil.getPlainDiskFileFilter() ); if( imgFile != null ) { - boolean state = false; - String fileName = imgFile.getName(); - if( fileName != null ) { - String lowerFileName = fileName.toLowerCase(); - state = (TextUtil.endsWith( + if( imgFile.exists() && (imgFile.length() == 0) ) { + BasicDlg.showErrorDlg( owner, "Die Datei ist leer." ); + } else { + boolean state = false; + String fileName = imgFile.getName(); + if( fileName != null ) { + String lowerFileName = fileName.toLowerCase(); + state = (TextUtil.endsWith( lowerFileName, DiskUtil.plainDiskFileExt ) || TextUtil.endsWith( lowerFileName, DiskUtil.gzPlainDiskFileExt )); - } - if( !state ) { - state = BasicDlg.showYesNoWarningDlg( + } + if( !state ) { + state = BasicDlg.showYesNoWarningDlg( owner, "JKCEMU kann nur einfache Abbilddateien" - + " auf Diskette schreiben.\n" + + " auf einen Datentr\u00E4ger schreiben.\n" + "Laut Dateiendung scheint die Datei jedoch" + " keine einfache Abbilddatei zu sein.\n" + "M\u00F6chten Sie trotzdem fortsetzen?", "Dateityp" ); - } - if( state ) { - String drvFileName = DriveSelectDlg.selectDriveFileName( owner ); - if( drvFileName != null ) { - String drvName = getDriveName( drvFileName ); - if( JOptionPane.showConfirmDialog( - owner, - String.format( - "Die Abbilddatei wird nun auf das Laufwerk %s" - + " geschrieben.\n" + } + if( state ) { + String drvFileName = DriveSelectDlg.selectDriveFileName( owner ); + if( drvFileName != null ) { + String drvName = getDriveName( drvFileName ); + if( JOptionPane.showConfirmDialog( + owner, + String.format( + "Die Abbilddatei wird nun auf den Datentr\u00E4ger" + + " im Laufwerk %s geschrieben.\n" + "Dabei werden alle bisherigen Daten" + " auf dem Datentr\u00E4ger gel\u00F6scht!", drvName ), - "Achtung", - JOptionPane.OK_CANCEL_OPTION, - JOptionPane.WARNING_MESSAGE ) == JOptionPane.OK_OPTION ) - { - (new DiskImgProcessDlg( + "Achtung", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE ) == JOptionPane.OK_OPTION ) + { + (new DiskImgProcessDlg( owner, String.format( "Schreibe Abbilddatei auf %s...", @@ -126,6 +129,7 @@ public static void writeDiskImageToDrive( Frame owner ) Direction.FILE_TO_DISK, drvFileName, imgFile )).setVisible( true ); + } } } } @@ -146,10 +150,7 @@ public void run() OutputStream out = null; try { in = DeviceIO.openDeviceForSequentialRead( this.drvFileName ); - out = new FileOutputStream( this.imgFile ); - if( EmuUtil.isGZipFile( this.imgFile ) ) { - out = new GZIPOutputStream( out ); - } + out = EmuUtil.createOptionalGZipOutputStream( this.imgFile ); Main.setLastDriveFileName( this.drvFileName ); Main.setLastFile( this.imgFile, "disk" ); @@ -185,10 +186,19 @@ public void run() InputStream in = null; OutputStream out = null; try { - in = new FileInputStream( this.imgFile ); + long len = this.imgFile.length(); + in = new FileInputStream( this.imgFile ); if( EmuUtil.isGZipFile( this.imgFile ) ) { in = new GZIPInputStream( in ); } + if( (len > (2880 * 1024)) && ((len % 512L) >= 0x100) ) { + // kompatible Festplattenabbilddatei -> Dateikopf ueberspringen + for( int i = 0; i < 0x100; i++ ) { + if( in.read() < 0 ) { + break; + } + } + } out = DeviceIO.openDeviceForSequentialWrite( this.drvFileName ); Main.setLastDriveFileName( this.drvFileName ); Main.setLastFile( this.imgFile, "disk" ); @@ -303,7 +313,7 @@ private DiskImgProcessDlg( add( this.labelStatus, gbc ); this.labelRunning = new JLabel(); - this.labelRunning.setFont( new Font( "Monospaced", Font.BOLD, 12 ) ); + this.labelRunning.setFont( new Font( Font.MONOSPACED, Font.BOLD, 12 ) ); gbc.gridy++; add( this.labelRunning, gbc ); @@ -329,6 +339,7 @@ public void actionPerformed( ActionEvent e ) this.timer.start(); this.thread = new Thread( + Main.getThreadGroup(), this, direction == Direction.FILE_TO_DISK ? "JKCEMU disk image writer" diff --git a/src/jkcemu/disk/DiskImgViewFrm.java b/src/jkcemu/disk/DiskImgViewFrm.java new file mode 100644 index 0000000..687c3aa --- /dev/null +++ b/src/jkcemu/disk/DiskImgViewFrm.java @@ -0,0 +1,1101 @@ +/* + * (c) 2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Inspektor fuer Diskettenabbilddateien + */ + +package jkcemu.disk; + +import java.awt.*; +import java.awt.datatransfer.*; +import java.awt.dnd.*; +import java.awt.event.*; +import java.io.*; +import java.lang.*; +import java.text.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.PatternSyntaxException; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.text.JTextComponent; +import jkcemu.Main; +import jkcemu.base.*; +import jkcemu.disk.*; + + +public class DiskImgViewFrm extends BasicFrm + implements + ByteDataSource, + CaretListener, + DropTargetListener, + HyperlinkListener +{ + private static final String MARK_BOGUS_ID = "*"; + private static final String MARK_NO_DATA = "no_data"; + private static final String MARK_DELETED = "del"; + private static final String MARK_ERROR = "err"; + + + private static class DataFoundException extends Exception + { + private DataFoundException() {} + }; + + + private static final int PREF_SECTORLIST_W = 300; + private static final int PREF_SECTORDATA_W = 300; + private static final int PREF_SECTORDATA_H = 200; + + private static final String HTML_ACTION_KEY_PREFIX = "sector:"; + private static final String HTML_ACTION_HREF_PREFIX + = "href=" + HTML_ACTION_KEY_PREFIX; + + private static final String TITLE = "JKCEMU Diskettenabbilddatei-Inspektor"; + + private static final String NOT_RECOGNIZED = "nicht erkannt"; + + private static DiskImgViewFrm instance = null; + + private AbstractFloppyDisk disk; + private SectorData selectedSector; + private boolean lastBigEndian; + private ReplyBytesDlg.InputFormat lastInputFmt; + private String lastFindText; + private byte[] findBytes; + private int findCyl; + private int findHead; + private int findSectorIdx; + private int findDataPos; + private JMenuItem mnuOpen; + private JMenuItem mnuClose; + private JMenuItem mnuBytesCopyAscii; + private JMenuItem mnuBytesCopyHex; + private JMenuItem mnuBytesCopyDump; + private JMenuItem mnuFind; + private JMenuItem mnuFindNext; + private JMenuItem mnuHelpContent; + private JSplitPane splitPane; + private JEditorPane fldFileContent; + private JTextField fldFileName; + private JTextField fldPhysFormat; + private JTextField fldRemark; + private JTextField fldTimestamp; + private JTextField fldLogFormat; + private JTextField fldBlockSize; + private JTextField fldBlockNumFmt; + private JTextField fldSectorPos; + private JTextField fldSectorID; + private JTextField fldSectorEtc; + private HexCharFld fldSectorData; + private JScrollPane spSectorData; + private JLabel sectorDataInfo; + + + public static void open() + { + if( instance != null ) { + if( instance.getExtendedState() == Frame.ICONIFIED ) { + instance.setExtendedState( Frame.NORMAL ); + } + } else { + instance = new DiskImgViewFrm(); + } + instance.toFront(); + instance.setVisible( true ); + } + + + public static void open( File file ) + { + open(); + if( file != null ) { + instance.openFile( file ); + } + } + + + /* --- ByteSourceData --- */ + + @Override + public int getAddrOffset() + { + return 0; + } + + + @Override + public int getDataByte( int addr ) + { + SectorData sector = this.selectedSector; + return sector != null ? sector.getDataByte( addr ) : 0; + } + + + @Override + public int getDataLength() + { + SectorData sector = this.selectedSector; + return sector != null ? sector.getDataLength() : 0; + } + + + /* --- CaretListener --- */ + + @Override + public void caretUpdate( CaretEvent e ) + { + int pos = this.fldSectorData.getCaretPosition(); + boolean state = ((pos >= 0) && (pos < getDataLength())); + this.mnuBytesCopyAscii.setEnabled( state ); + this.mnuBytesCopyHex.setEnabled( state ); + this.mnuBytesCopyDump.setEnabled( state ); + } + + + /* --- DropTargetListener --- */ + + @Override + public void dragEnter( DropTargetDragEvent e ) + { + if( !EmuUtil.isFileDrop( e ) ) + e.rejectDrag(); + } + + + @Override + public void dragExit( DropTargetEvent e ) + { + // leer + } + + + @Override + public void dragOver( DropTargetDragEvent e ) + { + // leer + } + + + @Override + public void drop( DropTargetDropEvent e ) + { + File file = EmuUtil.fileDrop( this, e ); + if( file != null ) { + openFile( file ); + } + } + + + @Override + public void dropActionChanged( DropTargetDragEvent e ) + { + // leer + } + + + /* --- HyperlinkListener --- */ + + @Override + public void hyperlinkUpdate( HyperlinkEvent e ) + { + if( (this.disk != null) + && (e.getSource() == this.fldFileContent) + && e.getEventType().equals( HyperlinkEvent.EventType.ACTIVATED ) ) + { + javax.swing.text.Element elem = e.getSourceElement(); + if( elem != null ) { + javax.swing.text.AttributeSet atts = elem.getAttributes(); + if( atts != null ) { + Object a = atts.getAttribute( javax.swing.text.html.HTML.Tag.A ); + if( a != null ) { + String text = a.toString(); + if( text != null ) { + if( text.startsWith( HTML_ACTION_HREF_PREFIX ) ) { + text = text.substring( HTML_ACTION_HREF_PREFIX.length() ); + try { + String[] items = text.split( ":" ); + if( items != null ) { + if( items.length == 3 ) { + showSector( + Integer.parseInt( items[ 0 ].trim() ), + Integer.parseInt( items[ 1 ].trim() ), + Integer.parseInt( items[ 2 ].trim() ) ); + } + } + } + catch( NumberFormatException ex ) {} + catch( PatternSyntaxException ex ) {} + } + } + } + } + } + } + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public boolean applySettings( Properties props, boolean resizable ) + { + boolean rv = super.applySettings( props, resizable ); + int splitPos = EmuUtil.parseIntProperty( + props, + getSettingsPrefix() + "split.position", + -1, + -1 ); + if( splitPos >= 0 ) { + this.splitPane.setDividerLocation( splitPos ); + } + return rv; + } + + + @Override + public boolean doAction( EventObject e ) + { + boolean rv = false; + Object src = e.getSource(); + if( src != null ) { + if( src == this.mnuOpen ) { + rv = true; + doFileOpen(); + } + else if( src == this.mnuClose ) { + rv = true; + doClose(); + } else if( src == this.mnuBytesCopyAscii ) { + rv = true; + this.fldSectorData.copySelectedBytesAsAscii(); + } else if( src == this.mnuBytesCopyHex ) { + rv = true; + this.fldSectorData.copySelectedBytesAsHex(); + } else if( src == this.mnuBytesCopyDump ) { + rv = true; + this.fldSectorData.copySelectedBytesAsDump(); + } else if( src == this.mnuFind ) { + rv = true; + doFind(); + } else if( src == this.mnuFindNext ) { + rv = true; + doFindNext(); + } else if( src == this.mnuHelpContent ) { + rv = true; + HelpFrm.open( "/help/disk/diskimgviewer.htm" ); + } + } + return rv; + } + + + @Override + public boolean doClose() + { + boolean rv = super.doClose(); + if( rv && (this.disk != null) ) { + this.disk.doClose(); + this.fldFileName.setText( "" ); + this.fldPhysFormat.setText( "" ); + this.fldRemark.setText( "" ); + this.fldTimestamp.setText( "" ); + this.fldLogFormat.setText( "" ); + this.fldBlockSize.setText( "" ); + this.fldBlockNumFmt.setText( "" ); + this.fldFileContent.setContentType( "text/plain" ); + this.fldFileContent.setText( "" ); + this.fldFileContent.setCaretPosition( 0 ); + clearSectorDetails(); + } + return rv; + } + + + @Override + public void putSettingsTo( Properties props ) + { + if( props != null ) { + super.putSettingsTo( props ); + props.setProperty( + getSettingsPrefix() + "split.position", + String.valueOf( this.splitPane.getDividerLocation() ) ); + } + } + + + /* --- Konstruktor --- */ + + private DiskImgViewFrm() + { + setTitle( TITLE ); + Main.updIcon( this ); + this.disk = null; + this.selectedSector = null; + + + // Menu + JMenuBar mnuBar = new JMenuBar(); + setJMenuBar( mnuBar ); + + + // Menu Datei + JMenu mnuFile = new JMenu( "Datei" ); + mnuFile.setMnemonic( KeyEvent.VK_D ); + mnuBar.add( mnuFile ); + + this.mnuOpen = createJMenuItem( + "\u00D6ffnen...", + KeyStroke.getKeyStroke( + KeyEvent.VK_O, + InputEvent.CTRL_MASK ) ); + mnuFile.add( this.mnuOpen ); + mnuFile.addSeparator(); + + this.mnuClose = createJMenuItem( "Schlie\u00DFen" ); + mnuFile.add( this.mnuClose ); + + + // Menu Bearbeiten + JMenu mnuEdit = new JMenu( "Bearbeiten" ); + mnuEdit.setMnemonic( KeyEvent.VK_B ); + mnuBar.add( mnuEdit ); + + this.mnuBytesCopyHex = createJMenuItem( + "Ausgw\u00E4hlte Bytes als Hexadezimalzahlen kopieren" ); + this.mnuBytesCopyHex.setEnabled( false ); + mnuEdit.add( this.mnuBytesCopyHex ); + + this.mnuBytesCopyAscii = createJMenuItem( + "Ausgw\u00E4hlte Bytes als ASCII-Text kopieren" ); + this.mnuBytesCopyAscii.setEnabled( false ); + mnuEdit.add( this.mnuBytesCopyAscii ); + + this.mnuBytesCopyDump = createJMenuItem( + "Ausgw\u00E4hlte Bytes als Hex-ASCII-Dump kopieren" ); + this.mnuBytesCopyDump.setEnabled( false ); + mnuEdit.add( this.mnuBytesCopyDump ); + mnuEdit.addSeparator(); + + this.mnuFind = createJMenuItem( + "Suchen...", + KeyStroke.getKeyStroke( KeyEvent.VK_F, Event.CTRL_MASK ) ); + this.mnuFind.setEnabled( false ); + mnuEdit.add( this.mnuFind ); + + this.mnuFindNext = createJMenuItem( + "Weitersuchen", + KeyStroke.getKeyStroke( KeyEvent.VK_F3, 0 ) ); + this.mnuFindNext.setEnabled( false ); + mnuEdit.add( this.mnuFindNext ); + + + // Menu Hilfe + JMenu mnuHelp = new JMenu( "?" ); + mnuBar.add( mnuHelp ); + + this.mnuHelpContent = createJMenuItem( "Hilfe..." ); + mnuHelp.add( this.mnuHelpContent ); + + + // Fensterinhalt + setLayout( new BorderLayout() ); + + this.fldFileContent = new JEditorPane(); + this.fldFileContent.setEditable( false ); + this.fldFileContent.setPreferredSize( + new Dimension( PREF_SECTORDATA_W, 1 ) ); + + JPanel panelDetails = new JPanel( new GridBagLayout() ); + + this.splitPane = new JSplitPane( + JSplitPane.HORIZONTAL_SPLIT, + false, + new JScrollPane( this.fldFileContent ), + panelDetails ); + add( this.splitPane, BorderLayout.CENTER ); + + + // linke Seite Detailansicht + GridBagConstraints gbcDetails = new GridBagConstraints( + 0, 0, + 1, 1, + 1.0, 0.0, + GridBagConstraints.NORTHWEST, + GridBagConstraints.HORIZONTAL, + new Insets( 5, 5, 5, 5 ), + 0, 0 ); + + // Bereich Datei + JPanel panelFile = new JPanel( new GridBagLayout() ); + panelFile.setBorder( BorderFactory.createTitledBorder( "Datei" ) ); + panelDetails.add( panelFile, gbcDetails ); + + GridBagConstraints gbcFile = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + panelFile.add( new JLabel( "Dateiname:" ), gbcFile ); + gbcFile.gridy++; + panelFile.add( new JLabel( "Format:" ), gbcFile ); + gbcFile.gridy++; + panelFile.add( new JLabel( "Bemerkung:" ), gbcFile ); + gbcFile.insets.bottom = 5; + gbcFile.gridy++; + panelFile.add( new JLabel( "Zeitstempel:" ), gbcFile ); + + this.fldFileName = new JTextField(); + this.fldFileName.setEditable( false ); + gbcFile.fill = GridBagConstraints.HORIZONTAL; + gbcFile.weightx = 1.0; + gbcFile.insets.bottom = 0; + gbcFile.gridy = 0; + gbcFile.gridx++; + panelFile.add( this.fldFileName, gbcFile ); + + this.fldPhysFormat = new JTextField(); + this.fldPhysFormat.setEditable( false ); + gbcFile.gridy++; + panelFile.add( this.fldPhysFormat, gbcFile ); + + this.fldRemark = new JTextField(); + this.fldRemark.setEditable( false ); + gbcFile.gridy++; + panelFile.add( this.fldRemark, gbcFile ); + + this.fldTimestamp = new JTextField(); + this.fldTimestamp.setEditable( false ); + gbcFile.insets.bottom = 5; + gbcFile.gridy++; + panelFile.add( this.fldTimestamp, gbcFile ); + + // Bereich automatische CP/M-Diskettenformaterkannung + JPanel panelRecognizedFmt = new JPanel( new GridBagLayout() ); + panelRecognizedFmt.setBorder( + BorderFactory.createTitledBorder( + "Automatische CP/M-Diskettenformaterkennung" ) ); + gbcDetails.gridy++; + panelDetails.add( panelRecognizedFmt, gbcDetails ); + + GridBagConstraints gbcRecognizedFmt = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + panelRecognizedFmt.add( + new JLabel( "Systemspuren / Directory:" ), + gbcRecognizedFmt ); + gbcRecognizedFmt.gridy++; + panelRecognizedFmt.add( + new JLabel( "Blockgr\u00F6\u00DFe:" ), + gbcRecognizedFmt ); + gbcRecognizedFmt.insets.bottom = 5; + gbcRecognizedFmt.gridy++; + panelRecognizedFmt.add( + new JLabel( "Blocknummernformat:" ), + gbcRecognizedFmt ); + + this.fldLogFormat = new JTextField(); + this.fldLogFormat.setEditable( false ); + gbcRecognizedFmt.fill = GridBagConstraints.HORIZONTAL; + gbcRecognizedFmt.weightx = 1.0; + gbcRecognizedFmt.insets.bottom = 0; + gbcRecognizedFmt.gridy = 0; + gbcRecognizedFmt.gridx++; + panelRecognizedFmt.add( this.fldLogFormat, gbcRecognizedFmt ); + + this.fldBlockSize = new JTextField(); + this.fldBlockSize.setEditable( false ); + gbcRecognizedFmt.gridy++; + panelRecognizedFmt.add( this.fldBlockSize, gbcRecognizedFmt ); + + this.fldBlockNumFmt = new JTextField(); + this.fldBlockNumFmt.setEditable( false ); + gbcRecognizedFmt.insets.bottom = 5; + gbcRecognizedFmt.gridy++; + panelRecognizedFmt.add( this.fldBlockNumFmt, gbcRecognizedFmt ); + + // Bereich Sektor + JPanel panelSector = new JPanel( new GridBagLayout() ); + panelSector.setBorder( BorderFactory.createTitledBorder( "Sektor" ) ); + gbcDetails.fill = GridBagConstraints.BOTH; + gbcDetails.weighty = 1.0; + gbcDetails.gridy++; + panelDetails.add( panelSector, gbcDetails ); + + GridBagConstraints gbcSector = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + panelSector.add( new JLabel( "Sektorposition:" ), gbcSector ); + gbcSector.gridy++; + panelSector.add( new JLabel( "Sektor-ID:" ), gbcSector ); + gbcSector.insets.bottom = 5; + gbcSector.gridy++; + panelSector.add( new JLabel( "Sonstiges:" ), gbcSector ); + + this.fldSectorPos = new JTextField(); + this.fldSectorPos.setEditable( false ); + gbcSector.fill = GridBagConstraints.HORIZONTAL; + gbcSector.weightx = 1.0; + gbcSector.insets.bottom = 0; + gbcSector.gridy = 0; + gbcSector.gridx++; + panelSector.add( this.fldSectorPos, gbcSector ); + + this.fldSectorID = new JTextField(); + this.fldSectorID.setEditable( false ); + gbcSector.gridy++; + panelSector.add( this.fldSectorID, gbcSector ); + + this.fldSectorEtc = new JTextField(); + this.fldSectorEtc.setEditable( false ); + gbcSector.insets.bottom = 5; + gbcSector.gridy++; + panelSector.add( this.fldSectorEtc, gbcSector ); + + this.sectorDataInfo = new JLabel( + "Bitte in der linken Ansicht einen Sektor anklicken" ); + this.sectorDataInfo.setHorizontalAlignment( SwingConstants.CENTER ); + this.sectorDataInfo.setVerticalAlignment( SwingConstants.CENTER ); + this.fldSectorData = new HexCharFld( this ); + + JPanel sectorDataPlaceholder = new JPanel(); + sectorDataPlaceholder.setPreferredSize( + new Dimension( PREF_SECTORDATA_W, PREF_SECTORDATA_H ) ); + this.spSectorData = new JScrollPane( sectorDataPlaceholder ); + gbcSector.fill = GridBagConstraints.BOTH; + gbcSector.weighty = 1.0; + gbcSector.gridwidth = GridBagConstraints.REMAINDER; + gbcSector.gridx = 0; + gbcSector.gridy++; + panelSector.add( this.spSectorData, gbcSector ); + + + // Drag&Drop aktivieren + (new DropTarget( this.fldFileContent, this )).setActive( true ); + + + // Listener + this.fldFileContent.addHyperlinkListener( this ); + this.fldSectorData.addCaretListener( this ); + + + // sonstiges + resetFind(); + setResizable( true ); + if( !applySettings( Main.getProperties(), true ) ) { + pack(); + setLocationByPlatform( true ); + } + this.fldFileContent.setPreferredSize( null ); + } + + + /* --- Aktionen --- */ + + private void doFileOpen() + { + openFile( EmuUtil.showFileOpenDlg( + this, + "Diskettenabbilddatei \u00F6ffnen", + Main.getLastDirFile( "disk" ), + EmuUtil.getPlainDiskFileFilter(), + EmuUtil.getAnaDiskFileFilter(), + EmuUtil.getCopyQMFileFilter(), + EmuUtil.getDskFileFilter(), + EmuUtil.getImageDiskFileFilter(), + EmuUtil.getTeleDiskFileFilter() ) ); + } + + + private void doFind() + { + if( this.disk != null ) { + ReplyBytesDlg dlg = new ReplyBytesDlg( + this, + "Bytes suchen", + this.lastInputFmt, + this.lastBigEndian, + this.lastFindText ); + dlg.setVisible( true ); + byte[] findBytes = dlg.getApprovedBytes(); + if( findBytes != null ) { + if( findBytes.length > 0 ) { + resetFind(); + this.findBytes = findBytes; + this.lastInputFmt = dlg.getApprovedInputFormat(); + this.lastBigEndian = dlg.getApprovedBigEndian(); + this.lastFindText = dlg.getApprovedText(); + doFindNext(); + this.mnuFindNext.setEnabled( true ); + } + } + } + } + + + private void doFindNext() + { + if( (this.disk != null) && (this.findBytes != null) ) { + if( this.findBytes.length > 0 ) { + if( this.findCyl < 0 ) { + this.findCyl = 0; + } + if( this.findHead < 0 ) { + this.findHead = 0; + } + int nCyls = this.disk.getCylinders(); + int sides = this.disk.getSides(); + for( int cyl = this.findCyl; cyl < nCyls; cyl++ ) { + for( int head = this.findHead; head < sides; head++ ) { + if( this.findSectorIdx < 0 ) { + this.findSectorIdx = 0; + } + int nSectors = this.disk.getSectorsOfCylinder( cyl, head ); + for( int idx = this.findSectorIdx; idx < nSectors; idx++ ) { + SectorData sector = this.disk.getSectorByIndex( cyl, head, idx ); + if( sector != null ) { + if( this.findDataPos < 0 ) { + this.findDataPos = 0; + } + int dataLen = sector.getDataLength(); + for( int i = this.findDataPos; i < dataLen; i++ ) { + boolean found = true; + for( int k = 0; k < this.findBytes.length; k++ ) { + int p = i + k; + if( p < dataLen ) { + if( (byte) sector.getDataByte( p ) + != this.findBytes[ k ] ) + { + found = false; + break; + } + } else { + found = false; + break; + } + } + if( found ) { + + // Sektor anzeigen + showSector( cyl, head, idx ); + + /* + * gefundene Bytes rueckwaerts selektieren, + * damit der Cursor auf der ersten, + * d.h. der gefundenen Position steht + */ + final int p1 = i + this.findBytes.length - 1; + final int p2 = i; + final HexCharFld fld = this.fldSectorData; + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + fld.setSelection( p1, p2 ); + } + } ); + + // gefundene Position inkrementieren + this.findDataPos = i + 1; + if( this.findDataPos >= dataLen ) { + this.findDataPos = 0; + this.findSectorIdx++; + if( this.findSectorIdx >= nSectors ) { + this.findSectorIdx = 0; + head++; + if( head >= sides ) { + this.findCyl++; + } + } + } + + // Suche verlassen + return; + } + } + } + } + } + } + BasicDlg.showInfoDlg( this, "Byte-Folge nicht gefunden" ); + } + } + } + + + /* --- private Methoden --- */ + + private void clearSectorDetails() + { + this.selectedSector = null; + this.fldSectorPos.setText( "" ); + this.fldSectorID.setText( "" ); + this.fldSectorEtc.setText( "" ); + if( this.disk != null ) { + setSectorDataView( this.sectorDataInfo ); + } else { + setSectorDataView( null ); + } + } + + + private void openFile( File file ) + { + if( file != null ) { + try { + AbstractFloppyDisk disk = DiskUtil.readDiskFile( this, file, false ); + if( disk != null ) { + + // alte Abbilddatei schliessen und neue uebernehmen + if( this.disk != null ) { + this.disk.doClose(); + } + this.disk = disk; + Main.setLastFile( file, "disk" ); + setTitle( TITLE + ": " + file.getPath() ); + + // Allgemeine Infos + int cyls = disk.getCylinders(); + int sides = disk.getSides(); + int sectorsPerCyl = disk.getSectorsPerCylinder(); + int sectorSize = disk.getSectorSize(); + + setText( this.fldFileName, file.getName() ); + setText( + this.fldPhysFormat, + String.format( + "%d KByte brutto (%d x %d x %d x %d)", + cyls * sides * sectorSize * sectorsPerCyl / 1024, + cyls, + sides, + sectorsPerCyl, + sectorSize ) ); + setText( this.fldRemark, disk.getRemark() ); + java.util.Date diskDate = disk.getDiskDate(); + if( diskDate != null ) { + setText( + this.fldTimestamp, + DateFormat.getDateTimeInstance( + DateFormat.MEDIUM, + DateFormat.MEDIUM ).format( diskDate ) ); + } + + // Automatische Formaterkennung + StringBuilder buf = new StringBuilder( 0x2000 ); + int blockSize = DiskUtil.DEFAULT_BLOCK_SIZE; + float sysTracks = 0F; + AtomicInteger rvSysBytes = new AtomicInteger( -1 ); + AtomicInteger rvBlockSize = new AtomicInteger( -1 ); + byte[] dirBytes = DiskUtil.findAndReadDirBytes( + null, + null, + null, + disk, + rvSysBytes ); + if( rvSysBytes.get() > 0 ) { + int bytesPerTrack = disk.getSides() + * disk.getSectorsPerCylinder() + * disk.getSectorSize(); + if( bytesPerTrack > 0 ) { + sysTracks = rvSysBytes.floatValue() / (float) bytesPerTrack; + } + } + Boolean blkNum16Bit = DiskUtil.checkBlockNum16Bit( + dirBytes, + disk.getDiskSize(), + rvBlockSize ); + if( rvBlockSize.get() > 0 ) { + blockSize = rvBlockSize.get(); + } + if( dirBytes.length > 0 ) { + int sysTracks100 = Math.round( sysTracks * 100F ); + if( sysTracks100 == 0 ) { + buf.append( "Keine Systemspur" ); + } else if( sysTracks100 == 100 ) { + buf.append( "1 Systemspur" ); + } else { + NumberFormat numFmt = NumberFormat.getNumberInstance(); + if( numFmt instanceof DecimalFormat ) { + ((DecimalFormat) numFmt).applyPattern( "#0.##" ); + } + buf.append( numFmt.format( sysTracks ) ); + buf.append( " Systemspuren" ); + } + buf.append( ", " ); + boolean dirFilled = false; + if( DiskUtil.isFilledDir( dirBytes ) ) { + if( (dirBytes.length % 1024) == 0 ) { + buf.append( String.format( + "%d KByte", + dirBytes.length / 1024 ) ); + } else { + buf.append( String.format( "%d Byte", dirBytes.length ) ); + } + } else { + buf.append( "leeres" ); + } + buf.append( " Directory" ); + } else { + buf.append( NOT_RECOGNIZED ); + } + setText( this.fldLogFormat, buf.toString() ); + if( rvBlockSize.get() > 0 ) { + buf.setLength( 0 ); + if( (blockSize % 1024) == 0 ) { + buf.append( String.format( "%d KByte", blockSize / 1024 ) ); + } else { + buf.append( String.format( "%d Byte", blockSize ) ); + } + buf.append( ", nicht zu 100% sicher erkennbar!" ); + setText( this.fldBlockSize, buf.toString() ); + } else { + setText( this.fldBlockSize, NOT_RECOGNIZED ); + } + if( blkNum16Bit != null ) { + setText( + this.fldBlockNumFmt, + blkNum16Bit.booleanValue() ? "16 Bit" : "8 Bit" ); + } else { + setText( this.fldBlockNumFmt, NOT_RECOGNIZED ); + } + + // Sektortabelle + buf.setLength( 0 ); + buf.append( "\n" ); + + if( (cyls < 1) || (sides < 1) ) { + buf.append( "Diskettenabbilddatei ist leer!" ); + } else { + boolean hasBogusIdSectors = false; + boolean hasNoDataSectors = false; + boolean hasDeletedSectors = false; + boolean hasErrorSectors = false; + buf.append( "\n" + + "" ); + for( int side = 0; side < sides; side++ ) { + buf.append( "" ); + } + buf.append( "\n" ); + for( int physCyl = 0; physCyl < cyls; physCyl++ ) { + buf.append( "" ); + for( int physHead = 0; physHead < sides; physHead++ ) { + buf.append( "" ); + } + buf.append( "\n" ); + } + buf.append( "
    SpurSektoren Seite " ); + buf.append( side + 1 ); + buf.append( " (Kopf " ); + buf.append( side ); + buf.append( ")
    " ); + buf.append( physCyl ); + buf.append( "" ); + int n = disk.getSectorsOfCylinder( physCyl, physHead ); + for( int i = 0; i < n; i++ ) { + SectorData sector = disk.getSectorByIndex( + physCyl, + physHead, + i ); + if( sector != null ) { + if( i > 0 ) { + buf.append( " " ); + } + buf.append( "[" ); + buf.append( sector.getSectorNum() ); + if( sector.hasBogusID() ) { + hasBogusIdSectors = true; + buf.append( MARK_BOGUS_ID ); + } + int c = sector.getCylinder(); + if( c != physCyl ) { + buf.append( ",c=" ); + buf.append( c ); + } + int h = sector.getHead(); + if( h != physHead ) { + buf.append( ",h=" ); + buf.append( h ); + } + int dataLen = sector.getDataLength(); + if( dataLen != sectorSize ) { + buf.append( ",n=" ); + buf.append( sector.getSizeCode() ); + } + if( dataLen == 0 ) { + hasNoDataSectors = true; + buf.append( (char) ',' ); + buf.append( MARK_NO_DATA ); + } + if( sector.isDeleted() ) { + hasDeletedSectors = true; + buf.append( (char) ',' ); + buf.append( MARK_DELETED ); + } + if( sector.checkError() ) { + hasErrorSectors = true; + buf.append( (char) ',' ); + buf.append( MARK_ERROR ); + } + buf.append( "]" ); + } + } + buf.append( "
    \n" ); + if( hasBogusIdSectors + || hasNoDataSectors + || hasDeletedSectors + || hasErrorSectors ) + { + buf.append( "
    \n" + + "Agenda:
    \n" + + "\n" ); + if( hasBogusIdSectors ) { + buf.append( "\n" ); + } + if( hasNoDataSectors ) { + buf.append( "\n" ); + } + if( hasDeletedSectors ) { + buf.append( "\n" ); + } + if( hasDeletedSectors ) { + buf.append( "\n" ); + } + buf.append( "
    " ); + buf.append( MARK_BOGUS_ID ); + buf.append( "Sektor-ID generiert," + + " da Sektorkopf nicht gelesen werden konnte" + + "
    " ); + buf.append( MARK_NO_DATA ); + buf.append( "" + + "Sektor ohne Datenbereich
    " ); + buf.append( MARK_DELETED ); + buf.append( "" + + "Sektor mit Löschmarkierung
    " ); + buf.append( MARK_ERROR ); + buf.append( "" + + "Sektordaten mit CRC-Fehler gelesen
    \n" ); + } + } + buf.append( "" ); + this.fldFileContent.setContentType( "text/html" ); + setText( this.fldFileContent, buf.toString() ); + + // Sonstiges + clearSectorDetails(); + resetFind(); + this.mnuFind.setEnabled( true ); + } + } + catch( IOException ex ) { + BasicDlg.showErrorDlg( this, ex ); + } + } + } + + + private void resetFind() + { + this.lastBigEndian = false; + this.lastInputFmt = null; + this.lastFindText = null; + this.findBytes = null; + this.findCyl = 0; + this.findHead = 0; + this.findSectorIdx = 0; + this.findDataPos = 0; + this.mnuFindNext.setEnabled( false ); + } + + + private void setSectorDataView( Component view ) + { + JViewport vp = this.spSectorData.getViewport(); + if( vp != null ) { + vp.setView( view ); + } + } + + + private static void setText( JTextComponent c, String text ) + { + c.setText( text != null ? text : "" ); + c.setCaretPosition( 0 ); + } + + + private void showSector( int cyl, int head, int idx ) + { + SectorData sector = this.disk.getSectorByIndex( cyl, head, idx ); + if( sector != null ) { + setText( + this.fldSectorPos, + String.format( + "Spur %d, Seite %d, Index %d\n", + cyl, + head + 1, + idx ) ); + setText( + this.fldSectorID, + String.format( + "C=%d, H=%d, R=%d, N=%d\n", + sector.getCylinder(), + sector.getHead(), + sector.getSectorNum(), + sector.getSizeCode() ) ); + String etcText = ""; + if( sector.isDeleted() || sector.checkError() || sector.hasBogusID() ) { + StringBuilder buf = new StringBuilder( 128 ); + if( sector.isDeleted() ) { + buf.append( "Sektor gel\u00F6scht" ); + } + if( sector.checkError() ) { + if( buf.length() > 0 ) { + buf.append( ", " ); + } + buf.append( "Lesefehler" ); + } + if( sector.hasBogusID() ) { + if( buf.length() > 0 ) { + buf.append( ", " ); + } + buf.append( "Sektor-ID generiert (Sektorkopf war nicht lesbar)" ); + } + etcText = buf.toString(); + } + setText( this.fldSectorEtc, etcText ); + this.selectedSector = sector; + setSectorDataView( this.fldSectorData ); + this.fldSectorData.refresh(); + } else { + clearSectorDetails(); + } + } +} + diff --git a/src/jkcemu/disk/DiskUnpacker.java b/src/jkcemu/disk/DiskUnpacker.java index 577037c..bbff161 100644 --- a/src/jkcemu/disk/DiskUnpacker.java +++ b/src/jkcemu/disk/DiskUnpacker.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -14,6 +14,7 @@ import java.util.*; import jkcemu.Main; import jkcemu.base.*; +import jkcemu.filebrowser.FileBrowserFrm; public class DiskUnpacker extends AbstractThreadDlg @@ -32,6 +33,7 @@ public class DiskUnpacker extends AbstractThreadDlg private boolean applyReadOnly; private boolean forceLowerCase; private boolean fileErr; + private boolean blockNumErr; public static void unpackDisk( @@ -143,12 +145,12 @@ protected void doProgress() } // Directory-Bloecke lesen - java.util.List dirBlocks = new ArrayList(); + java.util.List dirBlocks = new ArrayList<>(); int blkIdx = 0; for(;;) { byte[] blkBuf = readLogicalBlock( blkIdx++ ); if( blkBuf != null ) { - if( DiskUtil.isFilledDirBlock( blkBuf ) ) { + if( DiskUtil.isFilledDir( blkBuf ) ) { dirBlocks.add( blkBuf ); continue; } @@ -157,10 +159,14 @@ protected void doProgress() } // Directory durchgehen + byte[] timeBytes = null; if( dirBlocks.isEmpty() ) { appendToLog( this.diskDesc + " ist leer.\n" ); disableAutoClose(); } else { + boolean firstEntry = true; + int dirBlkOffs = 0; + FileTimesViewFactory ftvFactory = new NIOFileTimesViewFactory(); this.outDir.mkdirs(); for( byte[] dirBlockBuf : dirBlocks ) { if( dirBlockBuf != null ) { @@ -168,8 +174,8 @@ protected void doProgress() while( (entryPos + 31) < dirBlockBuf.length ) { /* * Gueltige Extent-0-Eintraege suchen, - * Das 1. Bye wird dabei mit ausgeblendetem Bit 4 (Passwort-Bit) - * auswertet. + * Das 1. Bye wird dabei mit ausgeblendetem Bit 4 + * (Passwort-Bit) auswertet. */ int b0 = (int) dirBlockBuf[ entryPos ] & 0xEF; if( (b0 >= 0) && (b0 <= 0x1F) @@ -199,7 +205,16 @@ protected void doProgress() File file = new File( outDir, fileName ); OutputStream out = null; try { - out = new FileOutputStream( file ); + if( firstEntry + && fileName.equalsIgnoreCase( + DateStamper.FILENAME ) ) + { + int blocks = (int) dirBlockBuf[ entryPos + 15 ] & 0xFF; + out = new ByteArrayOutputStream( + blocks > 0 ? (blocks * 128) : 32 ); + } else { + out = new FileOutputStream( file ); + } int extentNum = 0; writeEntryContentTo( @@ -219,6 +234,14 @@ protected void doProgress() entryPos, extentNum ) ); out.close(); + if( out instanceof ByteArrayOutputStream ) { + timeBytes = ((ByteArrayOutputStream) out).toByteArray(); + if( timeBytes != null ) { + out = new FileOutputStream( file ); + out.write( timeBytes ); + out.close(); + } + } out = null; } catch( IOException ex ) { @@ -229,6 +252,21 @@ protected void doProgress() finally { EmuUtil.doClose( out ); } + + // Zeitstempel setzen + if( timeBytes != null ) { + int p = (dirBlkOffs + entryPos) / 2; + if( (p + 15) < timeBytes.length ) { + FileTimesView ftv = ftvFactory.getFileTimesView( file ); + if( ftv != null ) { + ftv.setTimesInMillis( + DateStamper.getMillis( timeBytes, p ), + DateStamper.getMillis( timeBytes, p + 5 ), + DateStamper.getMillis( timeBytes, p + 10 ) ); + } + } + } + // Bei Fehler Datei umbenennen if( this.fileErr ) { String fName2 = fileName + ".error"; if( file.renameTo( new File( this.outDir, fName2 ) ) ) { @@ -244,23 +282,51 @@ protected void doProgress() } } } + firstEntry = false; entryPos += 32; } } + dirBlkOffs += this.blockSize; } } - ScreenFrm screenFrm = Main.getScreenFrm(); - if( screenFrm != null ) { - screenFrm.fireDirectoryChanged( this.outDir.getParentFile() ); - } + FileBrowserFrm.fireFileChanged( this.outDir.getParentFile() ); + + // Fertig-Meldung final Window owner = getOwner(); if( owner != null ) { + final boolean error = (this.errorCount > 0); + + StringBuilder buf = new StringBuilder(); + if( error ) { + buf.append( "Beim Entpacken traten Fehler auf!" ); + if( this.blockNumErr ) { + buf.append( "\n\nWahrscheinlich ist die Blockgr\u00F6\u00DFe" + + " zu gro\u00DF oder das Blocknummernformat (8/16 Bit)" + + " falsch eingestellt.\n" + + "Wenn dem so ist, sind auch die ohne Fehlermeldung" + + " entpackten Dateien korrupt!" ); + } + } else { + buf.append( "Fertig!" ); + } + if( timeBytes != null ) { + buf.append( "\n\nDateStamper erkannt:\n" + + "Die Zeitstempel der entpackten Dateien" + + " wurden auf die in der Datei\n" + + DateStamper.FILENAME + + " enthaltenen Werte gesetzt." ); + } + final String msg = buf.toString(); EventQueue.invokeLater( new Runnable() { public void run() { - BasicDlg.showInfoDlg( owner, "Fertig!", "Entpacken" ); + if( error ) { + BasicDlg.showWarningDlg( owner, msg ); + } else { + BasicDlg.showInfoDlg( owner, msg, "Entpacken" ); + } } } ); } @@ -297,6 +363,7 @@ private DiskUnpacker( this.applyReadOnly = applyReadOnly; this.forceLowerCase = forceLowerCase; this.fileErr = false; + this.blockNumErr = false; } @@ -395,10 +462,12 @@ private void writeEntryContentTo( int nBytes = ((int) dirBlockBuf[ entryPos + 15 ] & 0xFF) * 128; int nBlocks = (nBytes + this.blockSize - 1) / this.blockSize; if( nBlocks > this.maxBlocksPerEntry ) { + this.blockNumErr = true; appendErrorToLog( String.format( "Ung\u00FCltiger Gr\u00F6\u00DFeneintrag in Extent %d", - extentNum ) ); + extentNum, + this.blockNum16Bit ? 16 : 8 ) ); incErrorCount(); this.fileErr = true; nBlocks = this.maxBlocksPerEntry; @@ -413,9 +482,10 @@ private void writeEntryContentTo( blockIdx = (int) dirBlockBuf[ pos++ ] & 0xFF; } if( blockIdx == 0 ) { + this.blockNumErr = true; throw new IOException( String.format( - "Ung\u00FCltiger Blocknummer 0 in Extent %d", + "Ung\u00FCltige Blocknummer 0 in Extent %d", extentNum ) ); } for( int k = 0; k < this.sectPerBlock; k++ ) { @@ -449,6 +519,7 @@ private void writeEntryContentTo( } nBytes -= sector.writeTo( out, nBytes ); } else { + this.blockNumErr = true; throw new IOException( String.format( "Sektor [H=%d,C=%d,R=%d] im Block %02Xh" diff --git a/src/jkcemu/disk/DiskUtil.java b/src/jkcemu/disk/DiskUtil.java index faf8b50..b835a17 100644 --- a/src/jkcemu/disk/DiskUtil.java +++ b/src/jkcemu/disk/DiskUtil.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -12,9 +12,9 @@ import java.io.*; import java.lang.*; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.zip.GZIPInputStream; import javax.swing.JOptionPane; -import javax.swing.filechooser.FileSystemView; import jkcemu.Main; import jkcemu.base.*; import jkcemu.text.TextUtil; @@ -41,7 +41,9 @@ public class DiskUtil ".image.gz", ".raw.gz" }; - private static final int DEFAULT_BLOCK_SIZE = 2048; + public static final int DEFAULT_BLOCK_SIZE = 2048; + + private enum DirStatus { NO_DIR, EMPTY_DIR, FILLED_DIR }; public static boolean checkAndConfirmWarning( @@ -67,6 +69,190 @@ public static boolean checkAndConfirmWarning( } + public static Boolean checkBlockNum16Bit( + byte[] dirBytes, + long diskSize, + AtomicInteger rvBlockSize ) + { + Boolean rv = null; + Integer blockSize = null; + if( dirBytes != null ) { + int maxBlkNum8 = -1; + int maxBlkNum16 = -1; + int halfBlkSize = DEFAULT_BLOCK_SIZE / 2; + if( diskSize <= 0 ) { + diskSize = FloppyDiskFormat.getMaxDiskSize(); + } + int pos = 0; + while( ((rv == null) || (blockSize == null)) + && ((pos + 31) < dirBytes.length) ) + { + int b0 = (int) dirBytes[ pos ] & 0xFF; + int b15 = (int) dirBytes[ pos + 15 ] & 0xFF; + if( (b0 >= 0) && (b0 <= 0x1F) && (b15 > 0) ) { + + /* + * Anzahl der benoetigten Bloecke bei Standard- + * und bei halber Standardblockgroesse ermitteln + */ + int nBlocksHalf = ((b15 * 128) + halfBlkSize - 1) + / halfBlkSize; + int nBlocksStd = ((b15 * 128) + DEFAULT_BLOCK_SIZE - 1) + / DEFAULT_BLOCK_SIZE; + if( nBlocksStd > 8 ) { + /* + * Bei 16-Bit-Blocknummern koennen nur max. 8 Bloecke + * pro Directory-Eintrag referenziert werden. + */ + rv = Boolean.FALSE; + } + + /* + * Anzahl der eingetragenen Blocknummern bei 8 Bit ermitteln, + * Gleichzeitig wird geprueft, ob bei den mindestens + * benoetigten Blocknummern (siehe nBlocksStd) + * Dopplungen auftreten. + * Wenn das der Fall ist, + * koennen es keine 8-Bit-Blocknummern sein. + */ + int tmpPos = pos + 16; + int nBlocks8 = 0; + Set blockNums8 = new TreeSet<>(); + for( int i = 0; i < 16; i++ ) { + int blockNum = (int) dirBytes[ tmpPos++ ] & 0xFF; + if( blockNum == 0 ) { + break; + } + nBlocks8++; + if( i < nBlocksStd ) { + if( blockNum > maxBlkNum8 ) { + maxBlkNum8 = blockNum; + } + if( !blockNums8.add( blockNum ) ) { + rv = Boolean.TRUE; + nBlocks8 = 0; + break; + } + } + } + + /* + * Anzahl der eingetragenen Blocknummern bei 16 Bit ermitteln, + * Gleichzeitig wird geprueft bei den mindestens + * benoetigten Blocknummern (siehe nBlocksStd) geprueft, + * ob so eine Blocknummer ausserhalb der Diskette liegt. + */ + tmpPos = pos + 16; + int nBlocks16 = 0; + for( int i = 0; i < 8; i++ ) { + int blockNum = EmuUtil.getWord( dirBytes, tmpPos ); + tmpPos += 2; + if( blockNum == 0 ) { + break; + } + nBlocks16++; + if( i < nBlocksStd ) { + if( blockNum > maxBlkNum16 ) { + maxBlkNum16 = blockNum; + } + if( (blockNum * halfBlkSize) >= diskSize ) { + rv = Boolean.FALSE; + } + } + } + + // Benoetigte und vorhandene Blockanzahlen vergleichen + if( nBlocks8 != nBlocks16 ) { + boolean only16Bit = false; + boolean only8Bit = false; + boolean tmp16Bit = false; + int tmpBlkSize = 0; + int nMatches = 0; + if( rv != null ) { + if( rv.booleanValue() ) { + only16Bit = true; + } else { + only8Bit = true; + } + } + if( nBlocksHalf > 0 ) { + if( !only16Bit && (nBlocksHalf == nBlocks8) ) { + // halbe Standardblockgroesse + 8 Bit Blocknummern passt + tmp16Bit = false; + tmpBlkSize = halfBlkSize; + nMatches++; + } + if( !only8Bit && (nBlocksHalf == nBlocks16) ) { + // halbe Standardblockgroesse + 16 Bit Blocknummern passt + tmp16Bit = true; + tmpBlkSize = halfBlkSize; + nMatches++; + } + } + if( nBlocksStd > 0 ) { + if( !only16Bit && (nBlocksStd == nBlocks8) ) { + // Standardblockgroesse + 8 Bit Blocknummern passt + tmp16Bit = false; + tmpBlkSize = DEFAULT_BLOCK_SIZE; + nMatches++; + } + if( !only8Bit && (nBlocksStd == nBlocks16) ) { + // Standardblockgroesse + 16 Bit Blocknummern passt + tmp16Bit = true; + tmpBlkSize = DEFAULT_BLOCK_SIZE; + nMatches++; + } + } + if( nMatches == 1 ) { + // eindeutige Kombination gefunden + if( rv != null ) { + if( rv.booleanValue() == tmp16Bit ) { + blockSize = tmpBlkSize; + } + } else { + rv = new Boolean( tmp16Bit ); + blockSize = tmpBlkSize; + } + } + } + } + pos += 32; + } + if( rv != null ) { + if( blockSize == null ) { + if( rv.booleanValue() ) { + // 16-Bit-Blocknummern + if( (maxBlkNum16 > 0) + && ((maxBlkNum16 * DEFAULT_BLOCK_SIZE) >= diskSize) + && ((maxBlkNum16 * halfBlkSize) < diskSize) ) + { + blockSize = new Integer( halfBlkSize ); + } + } else { + // 8-Bit-Blocknummern + if( (maxBlkNum8 > 0) + && ((maxBlkNum8 * DEFAULT_BLOCK_SIZE) >= diskSize) + && ((maxBlkNum8 * halfBlkSize) < diskSize) ) + { + blockSize = new Integer( halfBlkSize ); + } + } + } + } else { + if( (maxBlkNum16 > 0) + && ((maxBlkNum16 * halfBlkSize) > diskSize) ) + { + rv = Boolean.FALSE; + } + } + } + if( (rvBlockSize != null) && (blockSize != null) ) { + rvBlockSize.set( blockSize.intValue() ); + } + return rv; + } + + public static boolean checkFileExt( Component owner, File file, @@ -132,9 +318,120 @@ public static boolean equalsDiskSize( } - public static boolean isFilledDirBlock( byte[] blockBuf ) + /* + * Die Methode sucht und liest das Directory. + * Es wird dazu die halbe Standardblockgroesse verwendet. + * Optional kann ueber einen Parameter die Anzahl der uebersprungenen + * Bytes (Systemspuren) zurueckgegeben werden. + * + * Es werden maximal soviel Bytes gelesen und ausgewertet wie + * 3 Spuren vom groessten Format (2.88 MByte). + */ + public static byte[] findAndReadDirBytes( + InputStream in, + DeviceIO.RandomAccessDevice rad, + RandomAccessFile raf, + AbstractFloppyDisk disk, + AtomicInteger rvSysBytes ) + { + ByteArrayOutputStream outBuf = new ByteArrayOutputStream( + 3 * DEFAULT_BLOCK_SIZE ); + byte[] blockBuf = null; + byte[] emptyDirBlockBuf = null; + int nEmptyDirBlocks = 0; + int emptyDirBlockIdx = -1; + int firstDirBlockIdx = -1; + int blockIdx = 0; + int blockSize = DEFAULT_BLOCK_SIZE / 2; + int maxBlocks = 2880 * 1024 / 80 * 3 / blockSize; + boolean readStatus = true; + while( readStatus && (blockIdx < maxBlocks) ) { + if( blockBuf == null ) { + blockBuf = new byte[ blockSize ]; + } + Arrays.fill( blockBuf, (byte) 0 ); + try { + if( in != null ) { + if( EmuUtil.read( in, blockBuf ) <= 0 ) { + readStatus = false; + } + } else { + readStatus = readBlock( + blockBuf, + blockIdx, + rad, + raf, + disk ); + } + } + catch( IOException ex ) { + readStatus = false; + } + if( readStatus ) { + switch( getDirStatus( blockBuf ) ) { + case EMPTY_DIR: + /* + * Es koennte sein, dass die Systemspuren mit E5h gefuellt sind, + * was als leerer Directory-Block erkannt wird. + * Aus diesem Grund werden weitere Directory-Bloecke abgewartet, + * um zu sehen, ob noch ein gefuellter Directory-Block folgt. + */ + if( emptyDirBlockBuf == null ) { + emptyDirBlockBuf = blockBuf; + emptyDirBlockIdx = blockIdx; + } + nEmptyDirBlocks++; + if( outBuf.size() > 0 ) { + outBuf.write( blockBuf, 0, blockBuf.length ); + } + blockBuf = null; + break; + + case FILLED_DIR: + if( firstDirBlockIdx < 0 ) { + firstDirBlockIdx = blockIdx; + } + outBuf.write( blockBuf, 0, blockBuf.length ); + blockBuf = null; + break; + + default: + // beim ersten Block hinter Directory abbrechen + if( firstDirBlockIdx >= 0 ) { + readStatus = false; + } + break; + } + } + blockIdx++; + } + + /* + * Wenn kein Directory gefunden wurde, + * dann den ersten leeren Directory-Block nehmen, + * falls vorhanden + */ + if( (outBuf.size() == 0) + && (emptyDirBlockBuf != null) + && (emptyDirBlockIdx >= 0) ) + { + outBuf.write( emptyDirBlockBuf, 0, emptyDirBlockBuf.length ); + firstDirBlockIdx = emptyDirBlockIdx; + } + + if( (rvSysBytes != null) + && (outBuf.size() > 0) + && (firstDirBlockIdx >= 0) ) + { + rvSysBytes.set( firstDirBlockIdx * blockSize ); + } + return outBuf.toByteArray(); + } + + + public static boolean isFilledDir( byte[] dirBytes ) { - return getBlockDirStatus( blockBuf ) == 2; + return getDirStatus( dirBytes ) == DirStatus.FILLED_DIR; } @@ -153,9 +450,8 @@ public static boolean isValidCPMFileNameChar( char ch ) public static java.util.List readDirectory( AbstractFloppyDisk disk ) { - java.util.List dirBlocks = new ArrayList(); - readDirBlocks( dirBlocks, null, null, null, disk ); - return extractDirectory( dirBlocks ); + return extractDirectory( + findAndReadDirBytes( null, null, null, disk, null ) ); } @@ -169,9 +465,8 @@ public static java.util.List readDirFromPlainDisk( File file ) if( EmuUtil.isGZipFile( file ) ) { in = new GZIPInputStream( in ); } - java.util.List dirBlocks = new ArrayList(); - readDirBlocks( dirBlocks, in, null, null, null ); - rv = extractDirectory( dirBlocks ); + rv = extractDirectory( + findAndReadDirBytes( in, null, null, null, null ) ); } catch( IOException ex ) {} finally { @@ -182,6 +477,53 @@ public static java.util.List readDirFromPlainDisk( File file ) } + /* + * Lesen einer Diskettenabbilddatei + * Rueckgabe: + * null: Abbrechen gedrueckt + */ + public static AbstractFloppyDisk readDiskFile( + Frame owner, + File file, + boolean enableAutoRepair ) throws IOException + { + AbstractFloppyDisk disk = readNonPlainDiskFile( + owner, + file, + enableAutoRepair ); + if( (disk == null) && (file != null) ) { + String fName = file.getName(); + if( fName != null ) { + fName = fName.toLowerCase(); + if( TextUtil.endsWith( fName, DiskUtil.plainDiskFileExt ) + || TextUtil.endsWith( fName, DiskUtil.gzPlainDiskFileExt ) ) + { + FloppyDiskFormatDlg dlg = new FloppyDiskFormatDlg( + owner, + FloppyDiskFormat.getFormatByDiskSize( file.length() ), + FloppyDiskFormatDlg.Flag.PHYS_FORMAT ); + dlg.setVisible( true ); + FloppyDiskFormat fmt = dlg.getFormat(); + if( fmt != null ) { + disk = PlainDisk.createForByteArray( + owner, + file.getPath(), + EmuUtil.readFile( + file, + true, + FloppyDiskFormat.getMaxDiskSize() ), + fmt ); + } + } else { + throw new IOException( + "Unbekanntes Format einer Diskettenabbilddatei" ); + } + } + } + return disk; + } + + /* * Diese Methode testet die Kapazitaet der Diskette, * die in dem mit raf geoeffneten Laufwerk liegt. @@ -208,8 +550,9 @@ public static int readDiskSize( public static AbstractFloppyDisk readNonPlainDiskFile( - Frame owner, - File file ) throws IOException + Frame owner, + File file, + boolean enableAutoRepair ) throws IOException { AbstractFloppyDisk disk = null; if( file != null ) { @@ -223,7 +566,7 @@ public static AbstractFloppyDisk readNonPlainDiskFile( } } if( disk == null ) { - byte[] header = EmuUtil.readFile( file, 0x100 ); + byte[] header = EmuUtil.readFile( file, true, 0x100 ); if( header != null ) { if( CopyQMDisk.isCopyQMFileHeader( header ) ) { disk = CopyQMDisk.readFile( owner, file ); @@ -235,7 +578,7 @@ else if( ImageDisk.isImageDiskFileHeader( header ) ) { disk = ImageDisk.readFile( owner, file ); } else if( TeleDisk.isTeleDiskFileHeader( header ) ) { - disk = TeleDisk.readFile( owner, file ); + disk = TeleDisk.readFile( owner, file, enableAutoRepair ); } } } @@ -250,23 +593,36 @@ public static void unpackDisk( AbstractFloppyDisk disk, boolean forceAskLogicalFmt ) { - // sinnvolle Vorbelegung ermitteln - java.util.List dirBlocks = new ArrayList(); - int sysTracks = 0; - int sysBlocks = readDirBlocks( dirBlocks, null, null, null, disk ); - if( sysBlocks > 0 ) { - int blocksPerTrack = (disk.getSides() + // Vorbelegung ermitteln + int sysTracks = 0; + int blockSize = DEFAULT_BLOCK_SIZE; + boolean blockNum16Bit = true; + AtomicInteger rvSysBytes = new AtomicInteger( -1 ); + AtomicInteger rvBlockSize = new AtomicInteger( -1 ); + byte[] dirBytes = findAndReadDirBytes( + null, + null, + null, + disk, + rvSysBytes ); + if( rvSysBytes.get() > 0 ) { + int bytesPerTrack = disk.getSides() * disk.getSectorsPerCylinder() - * disk.getSectorSize()) / DEFAULT_BLOCK_SIZE; - if( blocksPerTrack > 0 ) { - sysTracks = sysBlocks / blocksPerTrack; + * disk.getSectorSize(); + if( bytesPerTrack > 0 ) { + sysTracks = rvSysBytes.get() / bytesPerTrack; } } - int blockSize = DEFAULT_BLOCK_SIZE; - boolean blockNum16Bit = check16BitBlockNums( - dirBlocks, - disk.getDiskSize() ); - + Boolean tmp16Bit = checkBlockNum16Bit( + dirBytes, + disk.getDiskSize(), + rvBlockSize ); + if( tmp16Bit != null ) { + blockNum16Bit = tmp16Bit.booleanValue(); + } + if( rvBlockSize.get() > 0 ) { + blockSize = rvBlockSize.get(); + } FloppyDiskFormatDlg dlg = null; if( forceAskLogicalFmt ) { dlg = new FloppyDiskFormatDlg( @@ -277,9 +633,15 @@ public static void unpackDisk( FloppyDiskFormatDlg.Flag.BLOCK_NUM_SIZE, FloppyDiskFormatDlg.Flag.APPLY_READONLY, FloppyDiskFormatDlg.Flag.FORCE_LOWERCASE ); - dlg.setSysTracks( sysTracks ); - dlg.setBlockSize( blockSize ); - dlg.setBlockNum16Bit( blockNum16Bit ); + if( (dirBytes.length > 0) && (rvSysBytes.get() >= 0) ) { + dlg.setRecognizedSysTracks( sysTracks ); + } + if( tmp16Bit != null ) { + dlg.setRecognizedBlockNum16Bit( tmp16Bit ); + if( rvBlockSize.get() > 0 ) { + dlg.setRecognizedBlockSize( rvBlockSize.get() ); + } + } } else { dlg = new FloppyDiskFormatDlg( owner, @@ -336,7 +698,7 @@ public static void unpackPlainDisk( null, "Diskette", readDiskSize( rad ), - getHomeDirFile(), + EmuUtil.getHomeDirFile(), "diskette" ) ) { rad = null; // Schliessen verhindern @@ -386,121 +748,48 @@ public static void unpackPlainDiskFile( /* --- private Methoden --- */ - private static boolean check16BitBlockNums( - java.util.List dirBlocks, - long diskSize ) + private static java.util.List extractDirectory( byte[] dirBytes ) { - boolean rv = (diskSize > (512 * 1024)); // Default-Wert - boolean done = false; - if( dirBlocks != null ) { - java.util.List tmpList = new ArrayList( 8 ); - int maxHValue = (int) ((diskSize / DEFAULT_BLOCK_SIZE) >> 8); - for( byte[] blockBuf : dirBlocks ) { - if( blockBuf != null ) { - int pos = 0; - while( !done && ((pos + 31) < blockBuf.length) ) { - int b0 = (int) blockBuf[ pos ] & 0xFF; - int b15 = (int) blockBuf[ pos + 15 ] & 0xFF; - if( (b0 >= 0) && (b0 <= 0x1F) && (b15 > 8) ) { - /* - * gueltiger Dateieintrag mit mehr als 2 KByte Groesse, - * d.h. mit mindestens zwei Bloecken - */ - int nBlocks = ((b15 * 128) + 2047) / 2048; - if( nBlocks > 8 ) { - /* - * Bei 16-Bit-Blocknummern koennen nur max. 8 Bloecke - * pro Directory-Eintrag referenziert werden. - */ - rv = false; - done = true; - } else { - /* - * Wenn ein Byte auf der Position eines hoeherwertigen Bytes - * groesser als das fuer die Diskettengroesse maximal - * moegliche hoeherwertige Byte ist, - * muessen es 8-Bit-Nummern sein. - * Wenn dagegen so ein Byte groesser Null ist - * und innerhalb des Eintrags mehrfach vorkommt, - * muessen es 16-Bit-Nummern sein. - */ - tmpList.clear(); - int hPos = pos + 17; - for( int i = 0; i < nBlocks; i++ ) { - int b = (int) blockBuf[ hPos ] & 0xFF; - if( b > maxHValue ) { - rv = false; - done = true; - break; - } - Integer tmpValue = new Integer( b ); - if( tmpList.contains( tmpValue ) ) { - rv = true; - done = true; - break; - } - tmpList.add( tmpValue ); - hPos += 2; - } + java.util.List rv = null; + if( dirBytes != null ) { + if( dirBytes.length > 0 ) { + Map entrySizes = new HashMap<>(); + int entryPos = 0; + while( (entryPos + 15) < dirBytes.length ) { + int b = (int) dirBytes[ entryPos ] & 0xFF; + if( (b >= 0) && (b <= 15) ) { + int pos = entryPos + 1; + StringBuilder buf = new StringBuilder( 12 ); + for( int i = 0; i < 8; i++ ) { + int ch = (int) dirBytes[ pos++ ] & 0x7F; + if( (ch > 0x20) && (ch < 0x7F) ) { + buf.append( (char) ch ); } } - pos += 32; - } - } - if( done ) { - break; - } - } - } - return rv; - } - - - private static java.util.List extractDirectory( - java.util.List dirBlocks ) - { - java.util.List rv = null; - if( dirBlocks != null ) { - if( !dirBlocks.isEmpty() ) { - Map entrySizes = new HashMap(); - for( byte[] blockBuf : dirBlocks ) { - int entryPos = 0; - while( (entryPos + 15) < blockBuf.length ) { - int b = (int) blockBuf[ entryPos ] & 0xFF; - if( (b >= 0) && (b <= 15) ) { - int pos = entryPos + 1; - StringBuilder buf = new StringBuilder( 12 ); - for( int i = 0; i < 8; i++ ) { - int ch = (int) blockBuf[ pos++ ] & 0x7F; + if( buf.length() > 0 ) { + boolean point = true; + for( int i = 0; i < 3; i++ ) { + int ch = (int) dirBytes[ pos++ ] & 0x7F; if( (ch > 0x20) && (ch < 0x7F) ) { + if( point ) { + buf.append( (char) '.' ); + point = false; + } buf.append( (char) ch ); } } - if( buf.length() > 0 ) { - boolean point = true; - for( int i = 0; i < 3; i++ ) { - int ch = (int) blockBuf[ pos++ ] & 0x7F; - if( (ch > 0x20) && (ch < 0x7F) ) { - if( point ) { - buf.append( (char) '.' ); - point = false; - } - buf.append( (char) ch ); - } - } - int eLen = ((int) blockBuf[ entryPos + 15 ] & 0xFF) * 128; - String eName = buf.toString(); - Long value = entrySizes.get( eName ); - if( value != null ) { - value = new Long( value.longValue() + eLen ); - } else { - value = new Long( eLen ); - } - entrySizes.put( eName, value ); + int eLen = ((int) dirBytes[ entryPos + 15 ] & 0xFF) * 128; + String eName = buf.toString(); + Long value = entrySizes.get( eName ); + if( value != null ) { + value = new Long( value.longValue() + eLen ); + } else { + value = new Long( eLen ); } + entrySizes.put( eName, value ); } - entryPos += 32; } + entryPos += 32; } Set entryNames = entrySizes.keySet(); if( entryNames != null ) { @@ -509,7 +798,7 @@ private static java.util.List extractDirectory( String[] a = entryNames.toArray( new String[ n ] ); if( a != null ) { Arrays.sort( a ); - rv = new ArrayList( n ); + rv = new ArrayList<>( n ); for( int i = 0; i < a.length; i++ ) { String s = a[ i ]; if( s != null ) { @@ -523,7 +812,7 @@ private static java.util.List extractDirectory( } if( rv == null ) { // leere aber gueltige Abbilddatei - rv = new ArrayList(); + rv = new ArrayList<>(); } } } @@ -531,24 +820,18 @@ private static java.util.List extractDirectory( } - /* - * Die Methoden prueft einen Block auf CP/M-Directory-Eintraege - * - * Rueckgabewert: - * 0: kein Directory-Block - * 1: leerer Directory-Block - * 2. gefuellter Directory-Block - */ - private static int getBlockDirStatus( byte[] blockBuf ) + // Die Methoden prueft Bytes auf CP/M-Directory-Eintraege + private static DirStatus getDirStatus( byte[] dirBytes ) { boolean isDir = false; - boolean isEmpty = true; - if( blockBuf != null ) { - if( blockBuf.length > 31 ) { + boolean isEmpty = false; + if( dirBytes != null ) { + if( dirBytes.length > 31 ) { isDir = true; + isEmpty = true; int entryPos = 0; - while( (entryPos + 31) < blockBuf.length ) { - int b = (int) blockBuf[ entryPos ] & 0xFF; + while( (entryPos + 31) < dirBytes.length ) { + int b = (int) dirBytes[ entryPos ] & 0xFF; if( b != 0xE5 ) { isEmpty = false; } @@ -563,7 +846,7 @@ private static int getBlockDirStatus( byte[] blockBuf ) if( b <= 15 ) { int pos = entryPos + 1; for( int i = 0; isDir && (i < 11); i++ ) { - int ch = (int) blockBuf[ pos++ ] & 0x7F; + int ch = (int) dirBytes[ pos++ ] & 0x7F; if( (ch < 0x20) || (ch >= 0x7F) ) { isDir = false; break; @@ -574,157 +857,79 @@ private static int getBlockDirStatus( byte[] blockBuf ) } } } - return isDir ? (isEmpty ? 1 : 2) : 0; - } - - - private static File getHomeDirFile() - { - File rv = null; - FileSystemView fsv = FileSystemView.getFileSystemView(); - if( fsv != null ) { - rv = fsv.getHomeDirectory(); - } - if( rv == null ) { - String homeDir = System.getProperty( "user.home" ); - if( homeDir != null ) { - rv = new File( homeDir ); - } + DirStatus rv = DirStatus.NO_DIR; + if( isDir ) { + rv = (isEmpty ? DirStatus.EMPTY_DIR : DirStatus.FILLED_DIR ); } return rv; } private static boolean readBlock( - byte[] buf, + byte[] outBuf, int blockIdx, DeviceIO.RandomAccessDevice rad, RandomAccessFile raf, - AbstractFloppyDisk disk, - int blockSize ) + AbstractFloppyDisk disk ) { boolean rv = false; - if( (blockSize > 0) && (blockSize <= buf.length) ) { - try { - if( raf != null ) { - raf.seek( blockIdx * blockSize ); - if( raf.read( buf, 0, blockSize ) == blockSize ) { - rv = true; - } + try { + if( rad != null ) { + rad.seek( blockIdx * outBuf.length ); + if( rad.read( outBuf, 0, outBuf.length ) == outBuf.length ) { + rv = true; } - else if( disk != null ) { - int nRemain = blockSize; - int sides = disk.getSides(); - int sectPerCyl = disk.getSectorsPerCylinder(); - int sectorSize = disk.getSectorSize(); - if( (sides > 0) && (sectPerCyl > 0) && (sectorSize > 0) ) { - int sectPerBlock = blockSize / sectorSize; - if( sectPerBlock > 0 ) { - for( int i = 0; i < sectPerBlock; i++ ) { - int nRead = 0; - int absSectIdx = (sectPerBlock * blockIdx) + i; - int cyl = absSectIdx / sectPerCyl / sides; - int head = 0; - int sectIdx = absSectIdx - (cyl * sectPerCyl * sides); - if( sectIdx >= sectPerCyl ) { - head++; - sectIdx -= sectPerCyl; - } - SectorData sector = disk.getSectorByID( + } + if( raf != null ) { + raf.seek( blockIdx * outBuf.length ); + if( raf.read( outBuf ) == outBuf.length ) { + rv = true; + } + } + else if( disk != null ) { + int nRemain = outBuf.length; + int sides = disk.getSides(); + int sectPerCyl = disk.getSectorsPerCylinder(); + int sectorSize = disk.getSectorSize(); + if( (sides > 0) && (sectPerCyl > 0) && (sectorSize > 0) ) { + int sectPerBlock = outBuf.length / sectorSize; + if( sectPerBlock > 0 ) { + for( int i = 0; i < sectPerBlock; i++ ) { + int nRead = 0; + int absSectIdx = (sectPerBlock * blockIdx) + i; + int cyl = absSectIdx / sectPerCyl / sides; + int head = 0; + int sectIdx = absSectIdx - (cyl * sectPerCyl * sides); + if( sectIdx >= sectPerCyl ) { + head++; + sectIdx -= sectPerCyl; + } + SectorData sector = disk.getSectorByID( cyl, head, cyl, head, sectIdx + 1, -1 ); - if( sector != null ) { - nRead = sector.read( buf, i * sectorSize, sectorSize ); - nRemain -= nRead; - } - if( nRead <= 0 ) { - break; - } + if( sector != null ) { + nRead = sector.read( outBuf, i * sectorSize, sectorSize ); + nRemain -= nRead; + } + if( nRead <= 0 ) { + break; } } } - if( nRemain == 0 ) { - rv = true; - } } - } - catch( IOException ex ) { - rv = false; - } - } - return rv; - } - - - /* - * Die Methode liest die Directory-Bloecke - * und haengt sie an die uebergebene Liste an (sofern not null). - * Es wird die Standardblockgroesse angenommen. - * - * Rueckgabewert: Anzahl der Bloecke der Systemspuren - */ - private static int readDirBlocks( - java.util.List list, - InputStream in, - DeviceIO.RandomAccessDevice rad, - RandomAccessFile raf, - AbstractFloppyDisk disk ) - { - byte[] blockBuf = null; - int firstDirBlock = -1; - int blockIdx = 0; - boolean readStatus = true; - while( readStatus ) { - if( blockBuf == null ) { - blockBuf = new byte[ DEFAULT_BLOCK_SIZE ]; - } - Arrays.fill( blockBuf, (byte) 0 ); - try { - if( in != null ) { - if( EmuUtil.read( in, blockBuf ) <= 0 ) { - readStatus = false; - } - } else { - readStatus = readBlock( - blockBuf, - blockIdx, - rad, - raf, - disk, - DEFAULT_BLOCK_SIZE ); - } - } - catch( IOException ex ) { - readStatus = false; - } - if( readStatus ) { - int dirStatus = getBlockDirStatus( blockBuf ); - if( dirStatus > 0 ) { - if( firstDirBlock < 0 ) { - firstDirBlock = blockIdx; - } - if( list != null ) { - list.add( blockBuf ); - } - blockBuf = null; - if( dirStatus == 1 ) { - // nach leeren Directory-Block abbrechen - readStatus = false; - } - } else { - if( firstDirBlock >= 0 ) { - // beim erstem Block hinter Directory abbrechen - readStatus = false; - } + if( nRemain == 0 ) { + rv = true; } } - blockIdx++; } - return firstDirBlock > 0 ? firstDirBlock : 0; + catch( IOException ex ) { + rv = false; + } + return rv; } @@ -738,27 +943,33 @@ private static boolean unpackPlainDisk( File presetDir, String presetName ) throws IOException { - boolean rv = false; - int preSysTracks = 0; - java.util.List dirBlocks = null; - - // sinnvolle Vorbelegung ermitteln + boolean rv = false; + int blockSize = DEFAULT_BLOCK_SIZE; + int sysTracks = 0; + AtomicInteger rvSysBytes = new AtomicInteger( -1 ); + AtomicInteger rvBlockSize = new AtomicInteger( -1 ); + byte[] dirBytes = findAndReadDirBytes( + null, + rad, + raf, + null, + rvSysBytes ); FloppyDiskFormat fmt = FloppyDiskFormat.getFormatByDiskSize( diskSize ); - if( (diskSize > 0) && (diskSize < Integer.MAX_VALUE) ) { - if( fmt != null ) { - dirBlocks = new ArrayList(); - int preSysBlocks = readDirBlocks( dirBlocks, null, rad, raf, null ); - if( preSysBlocks > 0 ) { - int blocksPerTrack = (fmt.getSides() + if( (fmt != null) && (rvSysBytes.get() > 0) ) { + int bytesPerTrack = fmt.getSides() * fmt.getSectorsPerCylinder() - * fmt.getSectorSize()) / DEFAULT_BLOCK_SIZE; - if( blocksPerTrack > 0 ) { - preSysTracks = preSysBlocks / blocksPerTrack; - } - } + * fmt.getSectorSize(); + if( bytesPerTrack > 0 ) { + sysTracks = rvSysBytes.get() / bytesPerTrack; } } - boolean blockNum16Bit = check16BitBlockNums( dirBlocks, diskSize ); + Boolean blkNum16Bit = checkBlockNum16Bit( + dirBytes, + diskSize, + rvBlockSize ); + if( rvBlockSize.get() > 0 ) { + blockSize = rvBlockSize.get(); + } FloppyDiskFormatDlg dlg = new FloppyDiskFormatDlg( owner, fmt, @@ -768,14 +979,20 @@ private static boolean unpackPlainDisk( FloppyDiskFormatDlg.Flag.BLOCK_NUM_SIZE, FloppyDiskFormatDlg.Flag.APPLY_READONLY, FloppyDiskFormatDlg.Flag.FORCE_LOWERCASE ); - dlg.setSysTracks( preSysTracks ); - dlg.setBlockSize( DEFAULT_BLOCK_SIZE ); - dlg.setBlockNum16Bit( blockNum16Bit ); + if( (dirBytes.length > 0) && (rvSysBytes.get() >= 0) && (fmt != null) ) { + dlg.setRecognizedSysTracks( sysTracks ); + } + if( blkNum16Bit != null ) { + dlg.setRecognizedBlockNum16Bit( blkNum16Bit ); + if( rvBlockSize.get() > 0 ) { + dlg.setRecognizedBlockSize( rvBlockSize.get() ); + } + } dlg.setVisible( true ); fmt = dlg.getFormat(); if( fmt != null ) { - int sysTracks = dlg.getSysTracks(); - int blockSize = dlg.getBlockSize(); + sysTracks = dlg.getSysTracks(); + blockSize = dlg.getBlockSize(); if( (sysTracks >= 0) && (blockSize > 0) ) { File outDir = EmuUtil.askForOutputDir( owner, @@ -819,4 +1036,3 @@ private static boolean unpackPlainDisk( return rv; } } - diff --git a/src/jkcemu/disk/DriveSelectDlg.java b/src/jkcemu/disk/DriveSelectDlg.java index 81448e6..3c9e01e 100644 --- a/src/jkcemu/disk/DriveSelectDlg.java +++ b/src/jkcemu/disk/DriveSelectDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2012 Jens Mueller + * (c) 2009-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -50,8 +50,8 @@ public String toString() }; - private String driveFileName; - private JComboBox comboDrive; + private String driveFileName; + private JComboBox comboDrive; private JCheckBox btnReadOnly; private JButton btnOK; private JButton btnCancel; @@ -82,7 +82,7 @@ public DriveSelectDlg( Window owner, boolean askReadOnly ) 0, 0 ); String lastDriveFileName = Main.getLastDriveFileName(); - this.comboDrive = new JComboBox(); + this.comboDrive = new JComboBox<>(); if( Main.isUnixLikeOS() ) { add( new JLabel( "Ger\u00E4tedatei:" ), gbc ); this.comboDrive.setEditable( true ); diff --git a/src/jkcemu/disk/FDC8272.java b/src/jkcemu/disk/FDC8272.java index eccf636..dbe321b 100644 --- a/src/jkcemu/disk/FDC8272.java +++ b/src/jkcemu/disk/FDC8272.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2012 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -11,6 +11,7 @@ import java.lang.*; import java.util.Arrays; +import jkcemu.Main; import jkcemu.base.EmuThread; import z80emu.*; @@ -198,7 +199,10 @@ public FDC8272( DriveSelector driveSelector, int mhz ) this.ioTaskCmd = IOTaskCmd.IDLE; this.ioTaskEnabled = true; this.ioTaskNoWait = false; - this.ioTaskThread = new Thread( this, "JKCEMU FDC" ); + this.ioTaskThread = new Thread( + Main.getThreadGroup(), + this, + "JKCEMU FDC" ); String text = System.getProperty( "jkcemu.debug.fdc" ); if( text != null ) { diff --git a/src/jkcemu/disk/FloppyDiskFormat.java b/src/jkcemu/disk/FloppyDiskFormat.java index 84d6be5..4c42809 100644 --- a/src/jkcemu/disk/FloppyDiskFormat.java +++ b/src/jkcemu/disk/FloppyDiskFormat.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -14,53 +14,65 @@ public class FloppyDiskFormat { public static final FloppyDiskFormat FMT_400K - = new FloppyDiskFormat( - 1, 80, 5, 1024, - 0, 2, 2048, true, false, - "400K (LLC2 CP/L)" ); + = new FloppyDiskFormat( + 1, 80, 5, 1024, 1, + 0, 2, 2048, true, false, + "400K (LLC2 CP/L)" ); public static final FloppyDiskFormat FMT_624K - = new FloppyDiskFormat( - 2, 80, 16, 256, - 2, 2, 2048, true, false, - "624K (PC/M)" ); + = new FloppyDiskFormat( + 2, 80, 16, 256, 1, + 2, 2, 2048, true, false, + "624K Standard (PC/M)" ); - public static final FloppyDiskFormat FMT_711K_BASDOS - = new FloppyDiskFormat( - 2, 80, 9, 512, - 1, 1, 4096, true, false, - "711K (KC compact BASDOS)" ); + public static final FloppyDiskFormat FMT_711K_I5_BASDOS + = new FloppyDiskFormat( + 2, 80, 9, 512, 5, + 1, 1, 4096, true, false, + "711K (KC compact BASDOS)" ); public static final FloppyDiskFormat FMT_720K - = new FloppyDiskFormat( - 2, 80, 9, 512, - 0, 2, 2048, true, false, - "720K (A5105 RBASIC/SCPX)" ); + = new FloppyDiskFormat( + 2, 80, 9, 512, 1, + 0, 2, 2048, true, false, + "720K Standard (A5105 RBASIC/SCPX)" ); public static final FloppyDiskFormat FMT_780K - = new FloppyDiskFormat( - 2, 80, 5, 1024, - 2, 2, 2048, true, false, - "780K (A5105 RBASIC/SCPX, CAOS," - + " MicroDOS, Z1013 CP/M)" ); - - public static final FloppyDiskFormat FMT_780K_DATESTAMPER - = new FloppyDiskFormat( - 2, 80, 5, 1024, - 2, 2, 2048, true, true, - "780K mit DateStamper (ZDOS/ML-DOS)" ); - - public static final FloppyDiskFormat FMT_800K - = new FloppyDiskFormat( - 2, 80, 5, 1024, - 0, 3, 2048, true, false, - "800K (Z9001 CP/A)" ); + = new FloppyDiskFormat( + 2, 80, 5, 1024, 1, + 2, 2, 2048, true, false, + "780K Standard (CAOS, NANOS, Z1013 CP/M)" ); + + public static final FloppyDiskFormat FMT_780K_I2 + = new FloppyDiskFormat( + 2, 80, 5, 1024, 2, + 2, 2, 2048, true, false, + "780K mit Interleave 2:1 (A5105 RBASIC/SCPX)" ); + + public static final FloppyDiskFormat FMT_780K_I3 + = new FloppyDiskFormat( + 2, 80, 5, 1024, 3, + 2, 2, 2048, true, false, + "780K mit Interleave 3:1 (MicroDOS)" ); + + public static final FloppyDiskFormat FMT_780K_I3_DATESTAMPER + = new FloppyDiskFormat( + 2, 80, 5, 1024, 3, + 2, 2, 2048, true, true, + "780K mit Interleave 3:1 und DateStamper (ZDOS/ML-DOS)" ); + + public static final FloppyDiskFormat FMT_800K_I4 + = new FloppyDiskFormat( + 2, 80, 5, 1024, 4, + 0, 3, 2048, true, false, + "800K mit Interleave 4:1 (Z9001 CP/A)" ); private static final FloppyDiskFormat[] formats = { new FloppyDiskFormat( 2, 80, 9, 512 ), new FloppyDiskFormat( 2, 80, 5, 1024 ), new FloppyDiskFormat( 2, 80, 15, 512 ), new FloppyDiskFormat( 2, 80, 18, 512 ), + new FloppyDiskFormat( 2, 80, 11, 1024 ), new FloppyDiskFormat( 2, 80, 36, 512 ), new FloppyDiskFormat( 2, 80, 16, 256 ), new FloppyDiskFormat( 1, 80, 5, 1024 ), @@ -80,6 +92,7 @@ public class FloppyDiskFormat private int cyls; private int sectorsPerCyl; private int sectorSize; + private int interleave; private int diskSize; private int sysTracks; private int dirBlocks; @@ -94,6 +107,7 @@ public FloppyDiskFormat( int cyls, int sectorsPerCyl, int sectorSize, + int interleave, int sysTracks, int dirBlocks, int blockSize, @@ -105,6 +119,7 @@ public FloppyDiskFormat( this.cyls = cyls; this.sectorsPerCyl = sectorsPerCyl; this.sectorSize = sectorSize; + this.interleave = interleave; this.diskSize = sides * cyls * sectorsPerCyl * sectorSize; this.sysTracks = sysTracks; this.dirBlocks = dirBlocks; @@ -147,6 +162,7 @@ public FloppyDiskFormat( cyls, sectorsPerCyl, sectorSize, + 1, -1, -1, -1, @@ -226,6 +242,12 @@ public static FloppyDiskFormat[] getFormats() } + public int getInterleave() + { + return this.interleave; + } + + public static int getMaxDiskSize() { if( maxDiskSize == null ) { @@ -272,7 +294,7 @@ public boolean isBlockNum16Bit() } - public boolean supportsDateStamper() + public boolean isDateStamperEnabled() { return this.dateStamper; } diff --git a/src/jkcemu/disk/FloppyDiskFormatDlg.java b/src/jkcemu/disk/FloppyDiskFormatDlg.java index a1e3f28..c2e9c60 100644 --- a/src/jkcemu/disk/FloppyDiskFormatDlg.java +++ b/src/jkcemu/disk/FloppyDiskFormatDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -31,23 +31,32 @@ public static enum Flag { AUTO_REFRESH, FORCE_LOWERCASE }; + private static final String TEXT_CHOOSE = "Bitte ausw\u00E4hlen!"; + private static final Color COLOR_EMPHASIZED = Color.red; + private static final Color COLOR_RECOGNIZED = new Color( 0xFF007F00 ); + private static boolean lastApplyReadOnly = false; private static boolean lastForceLowerCase = true; private boolean approved; + private Boolean recognizedBlockNum16Bit; + private Integer recognizedSysTracks; private FloppyDiskFormat selectedFmt; private FloppyDiskFormatSelectFld fmtSelectFld; - private JComboBox comboFmt; + private JComboBox comboFmt; private JCheckBox btnReadOnly; private JCheckBox btnApplyReadOnly; private JCheckBox btnAutoRefresh; private JCheckBox btnForceLowerCase; private JSpinner spinnerSysTracks; - private JComboBox comboBlockSize; + private JComboBox comboBlockSize; private JRadioButton btnBlockNum8Bit; private JRadioButton btnBlockNum16Bit; private JSpinner spinnerDirBlocks; private JLabel labelDirSizeUnit; + private JLabel infoSysTracks; + private JLabel infoBlockSize; + private JLabel infoBlockNumFmt; private JButton btnOK; private JButton btnCancel; @@ -59,8 +68,10 @@ public FloppyDiskFormatDlg( { super( owner, "Datei laden" ); setTitle( "JKCEMU Diskettenformat" ); - this.approved = false; - this.selectedFmt = null; + this.approved = false; + this.recognizedBlockNum16Bit = null; + this.recognizedSysTracks = null; + this.selectedFmt = null; // Fensterinhalt @@ -77,7 +88,7 @@ public FloppyDiskFormatDlg( // vollstaendiges Format if( containsFlag( flags, Flag.FULL_FORMAT ) ) { - this.fmtSelectFld = new FloppyDiskFormatSelectFld(); + this.fmtSelectFld = new FloppyDiskFormatSelectFld( false ); this.fmtSelectFld.setFormat( preSelFmt ); add( this.fmtSelectFld, gbc ); gbc.gridy++; @@ -87,7 +98,7 @@ public FloppyDiskFormatDlg( // Formatauswahl if( containsFlag( flags, Flag.PHYS_FORMAT ) ) { - this.comboFmt = new JComboBox(); + this.comboFmt = new JComboBox<>(); this.comboFmt.setEditable( false ); if( preSelFmt == null ) { this.comboFmt.addItem( "--- Bitte ausw\u00E4hlen ---" ); @@ -138,31 +149,44 @@ public FloppyDiskFormatDlg( new SpinnerNumberModel( 0, 0, 9, 1 ) ); gbc.gridx++; add( this.spinnerSysTracks, gbc ); + + this.infoSysTracks = new JLabel( TEXT_CHOOSE ); + this.infoSysTracks.setForeground( COLOR_EMPHASIZED ); + gbc.gridx += 2; + add( this.infoSysTracks, gbc ); gbc.gridx = 0; gbc.gridy++; } else { this.spinnerSysTracks = null; + this.infoSysTracks = null; } - // Block-Groesse + // Blockgroesse this.comboBlockSize = null; if( containsFlag( flags, Flag.BLOCK_SIZE ) ) { gbc.anchor = GridBagConstraints.WEST; gbc.gridwidth = 1; - add( new JLabel( "Block-Gr\u00F6\u00DFe:" ), gbc ); + add( new JLabel( "Blockgr\u00F6\u00DFe:" ), gbc ); - this.comboBlockSize = new JComboBox(); + this.comboBlockSize = new JComboBox<>(); this.comboBlockSize.setEditable( false ); this.comboBlockSize.addItem( new Integer( 1 ) ); this.comboBlockSize.addItem( new Integer( 2 ) ); this.comboBlockSize.addItem( new Integer( 4 ) ); this.comboBlockSize.addItem( new Integer( 8 ) ); this.comboBlockSize.addItem( new Integer( 16 ) ); + this.comboBlockSize.setSelectedItem( + new Integer( DiskUtil.DEFAULT_BLOCK_SIZE / 1024 ) ); gbc.gridx++; add( this.comboBlockSize, gbc ); gbc.gridx++; add( new JLabel( "KByte" ), gbc ); + + this.infoBlockSize = new JLabel( TEXT_CHOOSE ); + this.infoBlockSize.setForeground( COLOR_EMPHASIZED ); + gbc.gridx++; + add( this.infoBlockSize, gbc ); gbc.gridx = 0; gbc.gridy++; } @@ -182,10 +206,15 @@ public FloppyDiskFormatDlg( gbc.gridx++; add( this.btnBlockNum8Bit, gbc ); - this.btnBlockNum16Bit = new JRadioButton( "16 Bit", false ); + this.btnBlockNum16Bit = new JRadioButton( "16 Bit", true ); grpBlkNumSize.add( this.btnBlockNum16Bit ); gbc.gridx++; add( this.btnBlockNum16Bit, gbc ); + + this.infoBlockNumFmt = new JLabel( TEXT_CHOOSE ); + this.infoBlockNumFmt.setForeground( COLOR_EMPHASIZED ); + gbc.gridx++; + add( this.infoBlockNumFmt, gbc ); gbc.gridx = 0; gbc.gridy++; } @@ -218,7 +247,7 @@ public FloppyDiskFormatDlg( this.btnAutoRefresh = null; this.btnForceLowerCase = null; if( flags != null ) { - checkBoxes = new ArrayList( 4 ); + checkBoxes = new ArrayList<>( 4 ); for( int i = 0; i < flags.length; i++ ) { if( flags[ i ] != null ) { switch( flags[ i ] ) { @@ -314,7 +343,7 @@ public FloppyDiskFormatDlg( // Fenstergroesse und -position pack(); setParentCentered(); - setResizable( false ); + setResizable( true ); } @@ -330,9 +359,7 @@ public boolean getAutoRefresh() { boolean state = false; if( this.btnAutoRefresh != null ) { - if( this.btnAutoRefresh.isEnabled() ) { - state = this.btnAutoRefresh.isSelected(); - } + state = this.btnAutoRefresh.isSelected(); } return state; } @@ -418,67 +445,104 @@ public void setAutoRefresh( boolean state ) } - public void setBlockSize( int value ) + public void setForceLowerCase( boolean state ) { - if( this.fmtSelectFld != null ) { - this.fmtSelectFld.setBlockSize( value ); - } - if( this.comboBlockSize != null ) { - this.comboBlockSize.setSelectedItem( new Integer( value / 1024 ) ); - updDirSizeUnitLabel(); - } + if( this.btnForceLowerCase != null ) + this.btnForceLowerCase.setSelected( state ); } - public void setBlockNum16Bit( boolean state ) + public void setRecognizedBlockNum16Bit( Boolean value ) { - if( this.fmtSelectFld != null ) { - this.fmtSelectFld.setBlockNum16Bit( state ); - } - if( state ) { - if( this.btnBlockNum16Bit != null ) { - this.btnBlockNum16Bit.setSelected( true ); + if( value != null ) { + boolean state = value.booleanValue(); + if( this.fmtSelectFld != null ) { + this.fmtSelectFld.setBlockNum16Bit( state ); } - } else { - if( this.btnBlockNum8Bit != null ) { - this.btnBlockNum8Bit.setSelected( true ); + if( state ) { + if( this.btnBlockNum16Bit != null ) { + this.btnBlockNum16Bit.setSelected( true ); + } + } else { + if( this.btnBlockNum8Bit != null ) { + this.btnBlockNum8Bit.setSelected( true ); + } + } + } + if( this.infoBlockNumFmt != null ) { + String text = TEXT_CHOOSE; + Color color = COLOR_EMPHASIZED; + if( value != null ) { + text = String.format( + "%s Bit Blocknummern erkannt", + value.booleanValue() ? "16" : "8" ); + color = COLOR_RECOGNIZED; } + this.infoBlockNumFmt.setText( text ); + this.infoBlockNumFmt.setForeground( color ); + pack(); } } - public void setDirBlocks( int value ) + public void setRecognizedBlockSize( Integer value ) { - if( this.fmtSelectFld != null ) { - this.fmtSelectFld.setDirBlocks( value ); - } - if( this.spinnerDirBlocks != null ) { - try { - this.spinnerDirBlocks.setValue( new Integer( value ) ); + if( value != null ) { + if( this.fmtSelectFld != null ) { + this.fmtSelectFld.setBlockSize( value ); + } + if( this.comboBlockSize != null ) { + this.comboBlockSize.setSelectedItem( new Integer( value / 1024 ) ); updDirSizeUnitLabel(); } - catch( IllegalArgumentException ex ) {} + } + if( this.infoBlockSize != null ) { + /* + * Die Blockgroesse laesst sich nicht zu 100% sicher erkennen. + * Aus diesem Grund wird die Info hervorgehoben mit dem Hinweis + * zur Pruefung angezeigt. + */ + String text = TEXT_CHOOSE; + Color color = COLOR_EMPHASIZED; + if( value != null ) { + text = String.format( + "%d KByte erkannt, aber bitte trotzdem pr\u00FCfen!", + value.intValue() / 1024 ); + } + this.infoBlockSize.setText( text ); + this.infoBlockSize.setForeground( color ); + pack(); } } - public void setForceLowerCase( boolean state ) - { - if( this.btnForceLowerCase != null ) - this.btnForceLowerCase.setSelected( state ); - } - - - public void setSysTracks( int value ) + public void setRecognizedSysTracks( Integer value ) { - if( this.fmtSelectFld != null ) { - this.fmtSelectFld.setSysTracks( value ); + if( value != null ) { + if( this.fmtSelectFld != null ) { + this.fmtSelectFld.setSysTracks( value.intValue() ); + } + if( this.spinnerSysTracks != null ) { + try { + this.spinnerSysTracks.setValue( value ); + } + catch( IllegalArgumentException ex ) {} + } } - if( this.spinnerSysTracks != null ) { - try { - this.spinnerSysTracks.setValue( new Integer( value ) ); + if( this.infoSysTracks != null ) { + String text = TEXT_CHOOSE; + Color color = COLOR_EMPHASIZED; + if( value != null ) { + if( value.intValue() == 1 ) { + text = "1 Systemspur erkannt"; + } else { + text = String.format( "%d Systemspuren erkannt", value ); + } + color = COLOR_RECOGNIZED; } - catch( IllegalArgumentException ex ) {} + this.infoSysTracks.setText( text ); + this.infoSysTracks.setForeground( color ); + pack(); } } @@ -494,8 +558,9 @@ public boolean wasApproved() @Override public void stateChanged( ChangeEvent e ) { - if( e.getSource() == this.spinnerDirBlocks ) + if( e.getSource() == this.spinnerDirBlocks ) { updDirSizeUnitLabel(); + } } @@ -504,48 +569,46 @@ public void stateChanged( ChangeEvent e ) @Override protected boolean doAction( EventObject e ) { - boolean rv = false; - if( e != null ) { - Object src = e.getSource(); - if( src != null ) { - if( src == this.btnOK ) { - rv = true; - if( this.fmtSelectFld != null ) { - this.selectedFmt = this.fmtSelectFld.getFormat(); - this.approved = true; - } else if( this.comboFmt != null ) { - Object value = this.comboFmt.getSelectedItem(); - if( value != null ) { - if( value instanceof FloppyDiskFormat ) { - this.selectedFmt = (FloppyDiskFormat) value; - this.approved = true; - } - } - } else { - this.approved = true; - } - if( this.approved ) { - if( this.btnApplyReadOnly != null ) { - lastApplyReadOnly = this.btnApplyReadOnly.isSelected(); - } - if( this.btnForceLowerCase != null ) { - lastForceLowerCase = this.btnForceLowerCase.isSelected(); + boolean rv = false; + Object src = e.getSource(); + if( src != null ) { + if( src == this.btnOK ) { + rv = true; + if( this.fmtSelectFld != null ) { + this.selectedFmt = this.fmtSelectFld.getFormat(); + this.approved = true; + } else if( this.comboFmt != null ) { + Object value = this.comboFmt.getSelectedItem(); + if( value != null ) { + if( value instanceof FloppyDiskFormat ) { + this.selectedFmt = (FloppyDiskFormat) value; + this.approved = true; } - doClose(); } + } else { + this.approved = true; } - else if( src == this.btnCancel ) { - rv = true; + if( this.approved ) { + if( this.btnApplyReadOnly != null ) { + lastApplyReadOnly = this.btnApplyReadOnly.isSelected(); + } + if( this.btnForceLowerCase != null ) { + lastForceLowerCase = this.btnForceLowerCase.isSelected(); + } doClose(); } - else if( src == this.comboBlockSize ) { - rv = true; - updDirSizeUnitLabel(); - } - else if( src == this.btnReadOnly ) { - rv = true; - updReadOnlyDependingFlds(); - } + } + else if( src == this.btnCancel ) { + rv = true; + doClose(); + } + else if( src == this.comboBlockSize ) { + rv = true; + updDirSizeUnitLabel(); + } + else if( src == this.btnReadOnly ) { + rv = true; + updReadOnlyDependingFlds(); } } return rv; @@ -628,12 +691,8 @@ private void updDirSizeUnitLabel() private void updReadOnlyDependingFlds() { if( this.btnReadOnly != null ) { - boolean state = this.btnReadOnly.isSelected(); - if( this.btnAutoRefresh != null ) { - this.btnAutoRefresh.setEnabled( state ); - } if( this.btnForceLowerCase != null ) { - this.btnForceLowerCase.setEnabled( !state ); + this.btnForceLowerCase.setEnabled( !this.btnReadOnly.isSelected() ); } } } diff --git a/src/jkcemu/disk/FloppyDiskFormatSelectFld.java b/src/jkcemu/disk/FloppyDiskFormatSelectFld.java index 2c31f3d..6365878 100644 --- a/src/jkcemu/disk/FloppyDiskFormatSelectFld.java +++ b/src/jkcemu/disk/FloppyDiskFormatSelectFld.java @@ -1,5 +1,5 @@ /* - * (c) 2013 Jens Mueller + * (c) 2013-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -19,10 +19,14 @@ public class FloppyDiskFormatSelectFld extends JPanel { private java.util.List changeListeners; - private JRadioButton btnFmt800K; + private JRadioButton btnFmt800Ki4; private JRadioButton btnFmt780K; + private JRadioButton btnFmt780Ki2; + private JRadioButton btnFmt780Ki3; + private JRadioButton btnFmt780Ki3ds; + private JRadioButton btnFmt780Ki4; private JRadioButton btnFmt720K; - private JRadioButton btnFmt711K; + private JRadioButton btnFmt711Ki5; private JRadioButton btnFmt624K; private JRadioButton btnFmt400K; private JRadioButton btnFmtEtc; @@ -30,6 +34,7 @@ public class FloppyDiskFormatSelectFld extends JPanel private JLabel labelCyls; private JLabel labelSysTracks; private JLabel labelSectPerCyl; + private JLabel labelInterleave; private JLabel labelSectorSize; private JLabel labelSectorSizeUnit; private JLabel labelBlockSize; @@ -38,17 +43,20 @@ public class FloppyDiskFormatSelectFld extends JPanel private JLabel labelBlockNumSizeUnit; private JLabel labelDirBlocks; private JLabel labelDirEntriesInfo; - private JComboBox comboSides; - private JComboBox comboCyls; + private JComboBox comboSides; + private JComboBox comboCyls; private JSpinner spinnerSysTracks; - private JComboBox comboSectPerCyl; - private JComboBox comboSectorSize; - private JComboBox comboBlockSizeKB; - private JComboBox comboBlockNumSize; + private JSpinner spinnerInterleave; + private SpinnerNumberModel modelInterleave; + private JComboBox comboSectPerCyl; + private JComboBox comboSectorSize; + private JComboBox comboBlockSizeKB; + private JComboBox comboBlockNumSize; private JSpinner spinnerDirBlocks; + private JCheckBox btnDateStamper; - public FloppyDiskFormatSelectFld() + public FloppyDiskFormatSelectFld( boolean askForInterleave ) { this.changeListeners = null; setLayout( new GridBagLayout() ); @@ -64,11 +72,11 @@ public FloppyDiskFormatSelectFld() ButtonGroup grpFmt = new ButtonGroup(); - this.btnFmt800K = new JRadioButton( - FloppyDiskFormat.FMT_800K.toString(), + this.btnFmt800Ki4 = new JRadioButton( + FloppyDiskFormat.FMT_800K_I4.toString(), false ); - grpFmt.add( this.btnFmt800K ); - add( this.btnFmt800K, gbc ); + grpFmt.add( this.btnFmt800Ki4 ); + add( this.btnFmt800Ki4, gbc ); this.btnFmt780K = new JRadioButton( FloppyDiskFormat.FMT_780K.toString(), @@ -77,6 +85,27 @@ public FloppyDiskFormatSelectFld() gbc.gridy++; add( this.btnFmt780K, gbc ); + this.btnFmt780Ki2 = new JRadioButton( + FloppyDiskFormat.FMT_780K_I2.toString(), + false ); + grpFmt.add( this.btnFmt780Ki2 ); + gbc.gridy++; + add( this.btnFmt780Ki2, gbc ); + + this.btnFmt780Ki3 = new JRadioButton( + FloppyDiskFormat.FMT_780K_I3.toString(), + false ); + grpFmt.add( this.btnFmt780Ki3 ); + gbc.gridy++; + add( this.btnFmt780Ki3, gbc ); + + this.btnFmt780Ki3ds = new JRadioButton( + FloppyDiskFormat.FMT_780K_I3_DATESTAMPER.toString(), + false ); + grpFmt.add( this.btnFmt780Ki3ds ); + gbc.gridy++; + add( this.btnFmt780Ki3ds, gbc ); + this.btnFmt720K = new JRadioButton( FloppyDiskFormat.FMT_720K.toString(), false ); @@ -84,12 +113,12 @@ public FloppyDiskFormatSelectFld() gbc.gridy++; add( this.btnFmt720K, gbc ); - this.btnFmt711K = new JRadioButton( - FloppyDiskFormat.FMT_711K_BASDOS.toString(), + this.btnFmt711Ki5 = new JRadioButton( + FloppyDiskFormat.FMT_711K_I5_BASDOS.toString(), false ); - grpFmt.add( this.btnFmt711K ); + grpFmt.add( this.btnFmt711Ki5 ); gbc.gridy++; - add( this.btnFmt711K, gbc ); + add( this.btnFmt711Ki5, gbc ); this.btnFmt624K = new JRadioButton( FloppyDiskFormat.FMT_624K.toString(), @@ -134,11 +163,13 @@ public FloppyDiskFormatSelectFld() add( this.comboCyls, gbc ); this.labelSysTracks = new JLabel( "davon Systemspuren:" ); + gbc.anchor = GridBagConstraints.EAST; gbc.gridx += 2; add( this.labelSysTracks, gbc ); this.spinnerSysTracks = new JSpinner( - new SpinnerNumberModel( 0, 0, 9, 1 ) ); + new SpinnerNumberModel( 0, 0, 9, 1 ) ); + gbc.anchor = GridBagConstraints.WEST; gbc.insets.left = 5; gbc.gridx++; add( this.spinnerSysTracks, gbc ); @@ -154,6 +185,23 @@ public FloppyDiskFormatSelectFld() gbc.gridx++; add( this.comboSectPerCyl, gbc ); + this.labelInterleave = null; + this.modelInterleave = null; + this.spinnerInterleave = null; + if( askForInterleave ) { + this.labelInterleave = new JLabel( + "Interleave (1 = kein Interleave):" ); + gbc.anchor = GridBagConstraints.EAST; + gbc.gridx += 2; + add( this.labelInterleave, gbc ); + + this.modelInterleave = new SpinnerNumberModel( 1, 1, 1, 1 ); + this.spinnerInterleave = new JSpinner( this.modelInterleave ); + gbc.anchor = GridBagConstraints.WEST; + gbc.gridx++; + add( this.spinnerInterleave, gbc ); + } + this.labelSectorSize = new JLabel( "Sektorgr\u00F6\u00DFe:" ); gbc.insets.left = 50; gbc.gridx = 0; @@ -176,7 +224,7 @@ public FloppyDiskFormatSelectFld() add( this.labelBlockSize, gbc ); this.comboBlockSizeKB = createJComboBox( 1, 2, 4, 8, 16 ); - gbc.insets.left = 5; + gbc.insets.left = 5; gbc.gridx++; add( this.comboBlockSizeKB, gbc ); @@ -192,7 +240,6 @@ public FloppyDiskFormatSelectFld() this.comboBlockNumSize = createJComboBox( 8, 16 ); gbc.anchor = GridBagConstraints.WEST; gbc.insets.left = 5; - gbc.insets.bottom = 5; gbc.gridx++; add( this.comboBlockNumSize, gbc ); @@ -217,6 +264,15 @@ public FloppyDiskFormatSelectFld() gbc.gridx++; add( this.labelDirEntriesInfo, gbc ); + this.btnDateStamper = new JCheckBox( + "Dateien mit Zeitstempel versehen (DateStamper)" ); + gbc.insets.left = 5; + gbc.insets.bottom = 5; + gbc.gridx = 1; + gbc.gridy++; + add( this.btnDateStamper, gbc ); + + // Listener ActionListener fmtListener = new ActionListener() { @@ -226,16 +282,20 @@ public void actionPerformed( ActionEvent e ) fireFormatChanged(); } }; - this.btnFmt800K.addActionListener( fmtListener ); + this.btnFmt800Ki4.addActionListener( fmtListener ); this.btnFmt780K.addActionListener( fmtListener ); + this.btnFmt780Ki2.addActionListener( fmtListener ); + this.btnFmt780Ki3.addActionListener( fmtListener ); + this.btnFmt780Ki3ds.addActionListener( fmtListener ); this.btnFmt720K.addActionListener( fmtListener ); - this.btnFmt711K.addActionListener( fmtListener ); + this.btnFmt711Ki5.addActionListener( fmtListener ); this.btnFmt624K.addActionListener( fmtListener ); this.btnFmt400K.addActionListener( fmtListener ); this.btnFmtEtc.addActionListener( fmtListener ); ActionListener fmtChangeListener = new ActionListener() { + @Override public void actionPerformed( ActionEvent e ) { fireFormatChanged(); @@ -243,14 +303,25 @@ public void actionPerformed( ActionEvent e ) }; this.comboSides.addActionListener( fmtChangeListener ); this.comboCyls.addActionListener( fmtChangeListener ); - this.comboSectPerCyl.addActionListener( fmtChangeListener ); this.comboSectorSize.addActionListener( fmtChangeListener ); this.comboBlockSizeKB.addActionListener( new ActionListener() { + @Override + public void actionPerformed( ActionEvent e ) + { + updDirEntriesInfo(); + fireFormatChanged(); + } + } ); + this.comboSectPerCyl.addActionListener( + new ActionListener() + { + @Override public void actionPerformed( ActionEvent e ) { updDirEntriesInfo(); + updMaxInterleave(); fireFormatChanged(); } } ); @@ -258,6 +329,7 @@ public void actionPerformed( ActionEvent e ) this.spinnerDirBlocks.addChangeListener( new ChangeListener() { + @Override public void stateChanged( ChangeEvent e ) { updDirEntriesInfo(); @@ -268,6 +340,7 @@ public void stateChanged( ChangeEvent e ) this.spinnerSysTracks.addChangeListener( new ChangeListener() { + @Override public void stateChanged( ChangeEvent e ) { fireFormatChanged(); @@ -279,13 +352,14 @@ public void stateChanged( ChangeEvent e ) setFmtDetailsFields( FloppyDiskFormat.FMT_780K ); updFmtDetailsFields(); updDirEntriesInfo(); + updMaxInterleave(); } public synchronized void addChangeListener( ChangeListener listener ) { if( this.changeListeners == null ) { - this.changeListeners = new ArrayList(); + this.changeListeners = new ArrayList<>(); } this.changeListeners.add( listener ); } @@ -323,14 +397,20 @@ public int getDirBlocks() public FloppyDiskFormat getFormat() { FloppyDiskFormat rv = null; - if( this.btnFmt800K.isSelected() ) { - rv = FloppyDiskFormat.FMT_800K; + if( this.btnFmt800Ki4.isSelected() ) { + rv = FloppyDiskFormat.FMT_800K_I4; } else if( this.btnFmt780K.isSelected() ) { rv = FloppyDiskFormat.FMT_780K; + } else if( this.btnFmt780Ki2.isSelected() ) { + rv = FloppyDiskFormat.FMT_780K_I2; + } else if( this.btnFmt780Ki3.isSelected() ) { + rv = FloppyDiskFormat.FMT_780K_I3; + } else if( this.btnFmt780Ki3ds.isSelected() ) { + rv = FloppyDiskFormat.FMT_780K_I3_DATESTAMPER; } else if( this.btnFmt720K.isSelected() ) { rv = FloppyDiskFormat.FMT_720K; - } else if( this.btnFmt711K.isSelected() ) { - rv = FloppyDiskFormat.FMT_711K_BASDOS; + } else if( this.btnFmt711Ki5.isSelected() ) { + rv = FloppyDiskFormat.FMT_711K_I5_BASDOS; } else if( this.btnFmt624K.isSelected() ) { rv = FloppyDiskFormat.FMT_624K; } else if( this.btnFmt400K.isSelected() ) { @@ -341,17 +421,24 @@ public FloppyDiskFormat getFormat() getCylinders(), getSectorsPerCylinder(), getSectorSize(), + getInterleave(), getSysTracks(), getDirBlocks(), getBlockSize(), isBlockNum16Bit(), - false, + isDateStamperEnabled(), null ); } return rv; } + public int getInterleave() + { + return getIntValue( this.spinnerInterleave ); + } + + public int getSectorsPerCylinder() { return getIntValue( this.comboSectPerCyl ); @@ -382,6 +469,12 @@ public boolean isBlockNum16Bit() } + public boolean isDateStamperEnabled() + { + return this.btnDateStamper.isSelected(); + } + + public void setBlockSize( int value ) { if( getBlockSize() != value ) { @@ -402,6 +495,12 @@ public void setBlockNum16Bit( boolean state ) } + public void setDateStamperEnabled( boolean state ) + { + this.btnDateStamper.setSelected( state ); + } + + public void setDirBlocks( int value ) { if( getDirBlocks() != value ) { @@ -415,14 +514,20 @@ public void setDirBlocks( int value ) public void setFormat( FloppyDiskFormat fmt ) { if( fmt != null ) { - if( fmt.equals( FloppyDiskFormat.FMT_800K ) ) { - this.btnFmt800K.setSelected( true ); + if( fmt.equals( FloppyDiskFormat.FMT_800K_I4 ) ) { + this.btnFmt800Ki4.setSelected( true ); } else if( fmt.equals( FloppyDiskFormat.FMT_780K ) ) { this.btnFmt780K.setSelected( true ); + } else if( fmt.equals( FloppyDiskFormat.FMT_780K_I2 ) ) { + this.btnFmt780Ki2.setSelected( true ); + } else if( fmt.equals( FloppyDiskFormat.FMT_780K_I3 ) ) { + this.btnFmt780Ki3.setSelected( true ); + } else if( fmt.equals( FloppyDiskFormat.FMT_780K_I3_DATESTAMPER ) ) { + this.btnFmt780Ki3ds.setSelected( true ); } else if( fmt.equals( FloppyDiskFormat.FMT_720K ) ) { this.btnFmt720K.setSelected( true ); - } else if( fmt.equals( FloppyDiskFormat.FMT_711K_BASDOS ) ) { - this.btnFmt711K.setSelected( true ); + } else if( fmt.equals( FloppyDiskFormat.FMT_711K_I5_BASDOS ) ) { + this.btnFmt711Ki5.setSelected( true ); } else if( fmt.equals( FloppyDiskFormat.FMT_624K ) ) { this.btnFmt624K.setSelected( true ); } else if( fmt.equals( FloppyDiskFormat.FMT_400K ) ) { @@ -448,9 +553,9 @@ public void setSysTracks( int value ) /* --- private Methoden --- */ - private static JComboBox createJComboBox( int... items ) + private static JComboBox createJComboBox( int... items ) { - JComboBox combo = new JComboBox(); + JComboBox combo = new JComboBox<>(); combo.setEditable( false ); if( items != null ) { for( int i = 0; i < items.length; i++ ) { @@ -508,6 +613,7 @@ private void setFmtDetailsFields( FloppyDiskFormat fmt ) setValue( this.comboCyls, fmt.getCylinders() ); setValue( this.comboSectPerCyl, fmt.getSectorsPerCylinder() ); setValue( this.comboSectorSize, fmt.getSectorSize() ); + setValue( this.spinnerInterleave, fmt.getInterleave() ); int sysTracks = fmt.getSysTracks(); int dirBlocks = fmt.getDirBlocks(); int blockSize = fmt.getBlockSize(); @@ -517,6 +623,8 @@ private void setFmtDetailsFields( FloppyDiskFormat fmt ) setValue( this.comboBlockSizeKB, blockSize / 1024 ); setValue( this.comboBlockNumSize, fmt.isBlockNum16Bit() ? 16 : 8 ); } + setDateStamperEnabled( fmt.isDateStamperEnabled() ); + updMaxInterleave(); } @@ -554,14 +662,20 @@ private void updDirEntriesInfo() private void updFmtDetailsFields() { FloppyDiskFormat fmt = null; - if( this.btnFmt800K.isSelected() ) { - fmt = FloppyDiskFormat.FMT_800K; + if( this.btnFmt800Ki4.isSelected() ) { + fmt = FloppyDiskFormat.FMT_800K_I4; } else if( this.btnFmt780K.isSelected() ) { fmt = FloppyDiskFormat.FMT_780K; + } else if( this.btnFmt780Ki2.isSelected() ) { + fmt = FloppyDiskFormat.FMT_780K_I2; + } else if( this.btnFmt780Ki3.isSelected() ) { + fmt = FloppyDiskFormat.FMT_780K_I3; + } else if( this.btnFmt780Ki3ds.isSelected() ) { + fmt = FloppyDiskFormat.FMT_780K_I3_DATESTAMPER; } else if( this.btnFmt720K.isSelected() ) { fmt = FloppyDiskFormat.FMT_720K; - } else if( this.btnFmt711K.isSelected() ) { - fmt = FloppyDiskFormat.FMT_711K_BASDOS; + } else if( this.btnFmt711Ki5.isSelected() ) { + fmt = FloppyDiskFormat.FMT_711K_I5_BASDOS; } else if( this.btnFmt624K.isSelected() ) { fmt = FloppyDiskFormat.FMT_624K; } else if( this.btnFmt400K.isSelected() ) { @@ -585,6 +699,12 @@ private void updFmtDetailsFieldsEnabled() this.spinnerSysTracks.setEnabled( state ); this.labelSectPerCyl.setEnabled( state ); this.comboSectPerCyl.setEnabled( state ); + if( this.labelInterleave != null ) { + this.labelInterleave.setEnabled( state ); + } + if( this.spinnerInterleave != null ) { + this.spinnerInterleave.setEnabled( state ); + } this.labelSectorSize.setEnabled( state ); this.comboSectorSize.setEnabled( state ); this.labelSectorSizeUnit.setEnabled( state ); @@ -599,5 +719,22 @@ private void updFmtDetailsFieldsEnabled() this.labelDirEntriesInfo.setEnabled( state ); this.spinnerDirBlocks.setEnabled( state ); this.labelDirEntriesInfo.setEnabled( state ); + this.btnDateStamper.setEnabled( state ); + } + + + private void updMaxInterleave() + { + if( this.modelInterleave != null ) { + int maxInterleave = getIntValue( this.comboSectPerCyl ) - 1; + if( maxInterleave < 1 ) { + maxInterleave = 1; + } + int curInterleave = getIntValue( this.spinnerInterleave ); + if( curInterleave > maxInterleave ) { + this.modelInterleave.setValue( 1 ); + } + this.modelInterleave.setMaximum( maxInterleave ); + } } } diff --git a/src/jkcemu/disk/FloppyDiskInfo.java b/src/jkcemu/disk/FloppyDiskInfo.java index 7dd7140..5b5f08f 100644 --- a/src/jkcemu/disk/FloppyDiskInfo.java +++ b/src/jkcemu/disk/FloppyDiskInfo.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2010 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -18,14 +18,37 @@ public class FloppyDiskInfo implements Comparable { - private String resource; - private String infoText; + private String resource; + private String infoText; + private int sysTracks; + private int blockSize; + private boolean blockNum16Bit; + + + public FloppyDiskInfo( + String resource, + String infoText, + int sysTracks, + int blockSize, + boolean blockNum16Bit ) + { + this.resource = resource; + this.infoText = infoText; + this.sysTracks = sysTracks; + this.blockSize = blockSize; + this.blockNum16Bit = blockNum16Bit; + } + + + public boolean getBlockNum16Bit() + { + return this.blockNum16Bit; + } - public FloppyDiskInfo( String resource, String infoText ) + public int getBlockSize() { - this.resource = resource; - this.infoText = infoText; + return this.blockSize; } @@ -35,6 +58,12 @@ public String getResource() } + public int getSysTracks() + { + return this.sysTracks; + } + + public AbstractFloppyDisk openDisk( Frame owner ) throws IOException { AbstractFloppyDisk disk = null; diff --git a/src/jkcemu/disk/FloppyDiskStationFrm.java b/src/jkcemu/disk/FloppyDiskStationFrm.java index 5527074..4c84974 100644 --- a/src/jkcemu/disk/FloppyDiskStationFrm.java +++ b/src/jkcemu/disk/FloppyDiskStationFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -43,6 +43,7 @@ public class FloppyDiskStationFrm private FloppyDiskInfo[] etcDisks; private FloppyDiskDrive[] drives; private FloppyDiskFormat lastFmt; + private File lastDir; private volatile boolean diskErrorShown; private int[] driveAccessCounters; private int driveCnt; @@ -222,7 +223,9 @@ public void drop( DropTargetDropEvent e ) if( file.isDirectory() ) { openDirectory( i, file ); } else { - openFile( i, file, null, true, null, null ); + if( openFile( i, file, null, true, null, null ) ) { + setLastFile( file ); + } } break; } @@ -277,10 +280,11 @@ public boolean applySettings( Properties props, boolean resizable ) this.allDisks = null; this.etcDisks = null; try { - Set disks = new TreeSet(); + Set disks = new TreeSet<>(); addFloppyDiskInfo( disks, A5105.getAvailableFloppyDisks() ); addFloppyDiskInfo( disks, KC85.getAvailableFloppyDisks() ); addFloppyDiskInfo( disks, KCcompact.getAvailableFloppyDisks() ); + addFloppyDiskInfo( disks, NANOS.getAvailableFloppyDisks() ); addFloppyDiskInfo( disks, PCM.getAvailableFloppyDisks() ); addFloppyDiskInfo( disks, Z1013.getAvailableFloppyDisks() ); addFloppyDiskInfo( disks, Z9001.getAvailableFloppyDisks() ); @@ -346,7 +350,7 @@ else if( cmd.startsWith( "disk.export." ) && (cmd.length() > 12) ) { rv = true; - doDiskExport( cmd.substring( 12 ), this.allDisks ); + doDiskExport( cmd.substring( 12 ) ); } else if( cmd.equals( "disk.refresh" ) ) { rv = true; @@ -428,7 +432,14 @@ private void doDirectoryOpen() { int idx = this.tabbedPane.getSelectedIndex(); if( (idx >= 0) && (idx < this.drives.length) ) { - File file = DirSelectDlg.selectDirectory( this ); + File lastDir = this.lastDir; + AbstractFloppyDisk disk = this.drives[ idx ].getDisk(); + if( disk != null ) { + if( disk instanceof DirectoryFloppyDisk ) { + lastDir = ((DirectoryFloppyDisk) disk).getDirFile(); + } + } + File file = DirSelectDlg.selectDirectory( this, lastDir ); if( file != null ) { if( file.isDirectory() ) { openDirectory( idx, file ); @@ -438,94 +449,117 @@ private void doDirectoryOpen() } - private void doDiskExport( String srcIdxText, FloppyDiskInfo[] disks ) + private void doDiskExport( String srcIdxText ) { - int idx = this.tabbedPane.getSelectedIndex(); - if( (idx >= 0) && (idx < this.drives.length) ) { - if( (srcIdxText != null) && (disks != null) ) { - if( !srcIdxText.isEmpty() ) { - try { - int srcIdx = Integer.parseInt( srcIdxText ); - if( (srcIdx >= 0) && (srcIdx < disks.length) ) { - String resource = disks[ srcIdx ].getResource(); - if( resource != null ) { - String fName = ""; - boolean gzip = false; - int pos = resource.lastIndexOf( '/' ); - if( pos >= 0 ) { - if( (pos + 1) < resource.length() ) { - fName = resource.substring( pos + 1 ); - } - } else { - fName = resource; + if( (srcIdxText != null) && (this.allDisks != null) ) { + if( !srcIdxText.isEmpty() ) { + try { + int srcIdx = Integer.parseInt( srcIdxText ); + if( (srcIdx >= 0) && (srcIdx < this.allDisks.length) ) { + FloppyDiskInfo diskInfo = this.allDisks[ srcIdx ]; + String resource = diskInfo.getResource(); + if( resource != null ) { + String fName = ""; + boolean gzip = false; + int pos = resource.lastIndexOf( '/' ); + if( pos >= 0 ) { + if( (pos + 1) < resource.length() ) { + fName = resource.substring( pos + 1 ); } - if( fName.endsWith( ".gz" ) ) { - fName = fName.substring( 0, fName.length() - 3 ); - gzip = true; + } else { + fName = resource; + } + if( fName.endsWith( ".gz" ) ) { + fName = fName.substring( 0, fName.length() - 3 ); + gzip = true; + } + if( !fName.isEmpty() ) { + File preSel = Main.getLastDirFile( "disk" ); + if( preSel != null ) { + preSel = new File( preSel, fName ); + } else { + preSel = new File( fName ); } - if( !fName.isEmpty() ) { - File preSel = Main.getLastPathFile( "disk" ); - if( preSel != null ) { - preSel = new File( preSel, fName ); - } else { - preSel = new File( fName ); - } - File file = EmuUtil.showFileSaveDlg( + File file = EmuUtil.showFileSaveDlg( this, "Diskette exportieren", preSel, EmuUtil.getAnaDiskFileFilter() ); - if( file != null ) { - OutputStream out = null; - InputStream in = null; - InputStream is = null; - try { - in = getClass().getResourceAsStream( resource ); - if( in != null ) { - if( gzip ) { - is = in; - in = new GZIPInputStream( in ); - } - out = new BufferedOutputStream( - new FileOutputStream( file ) ); - int b = in.read(); - while( b >= 0 ) { - out.write( b ); - b = in.read(); - } - out.close(); - out = null; + if( file != null ) { + OutputStream out = null; + InputStream in = null; + InputStream is = null; + try { + in = getClass().getResourceAsStream( resource ); + if( in != null ) { + if( gzip ) { + is = in; + in = new GZIPInputStream( in ); } + out = new BufferedOutputStream( + new FileOutputStream( file ) ); + int b = in.read(); + while( b >= 0 ) { + out.write( b ); + b = in.read(); + } + out.close(); + out = null; } - finally { - EmuUtil.doClose( out ); - EmuUtil.doClose( in ); - EmuUtil.doClose( is ); - } - Main.setLastFile( file, "disk" ); + } + finally { + EmuUtil.doClose( out ); + EmuUtil.doClose( in ); + EmuUtil.doClose( is ); + } + setLastFile( file ); - // Fertigmeldung und weitere Aktionen - int option = BasicDlg.showOptionDlg( + // Fertigmeldung und weitere Aktionen + int option = BasicDlg.showOptionDlg( this, "Die Diskettenabbilddatei wurde im" + " AnaDisk-Format exportiert.\n" - + "Bei Bedarf k\u00F6nnen Sie nun" - + " die Datei in ein anderes Format" - + " konvertieren\n" + + "Sie k\u00F6nnen nun die Datei" + + " in ein anderes Format konvertieren\n" + "oder die in dem Diskettenabbild" + " enthaltenen einzelnen Dateien entpacken.", "Export fertig", "Konvertieren", "Entpacken", "Schlie\u00DFen" ); - if( option == 0 ) { - FileConvertFrm.open( file ); - } else if( option == 1 ) { - AbstractFloppyDisk disk = DiskUtil.readNonPlainDiskFile( + if( option == 0 ) { + FileConvertFrm.open( file ); + } else if( option == 1 ) { + AbstractFloppyDisk disk = DiskUtil.readNonPlainDiskFile( this, - file ); - if( disk != null ) { - DiskUtil.unpackDisk( this, file, disk, false ); + file, + true ); + if( disk != null ) { + File outDir = EmuUtil.askForOutputDir( + this, + file, + "Entpacken nach:", + diskInfo.toString() + " entpacken" ); + if( outDir != null ) { + FloppyDiskFormatDlg dlg = new FloppyDiskFormatDlg( + this, + null, + FloppyDiskFormatDlg.Flag.APPLY_READONLY, + FloppyDiskFormatDlg.Flag.FORCE_LOWERCASE ); + dlg.setTitle( "Entpacken" ); + dlg.setVisible( true ); + if( dlg.wasApproved() ) { + DiskUnpacker.unpackDisk( + this, + disk, + diskInfo.toString(), + outDir, + diskInfo.getSysTracks(), + diskInfo.getBlockSize(), + diskInfo.getBlockNum16Bit(), + dlg.getApplyReadOnly(), + dlg.getForceLowerCase() ); + } } } } @@ -533,11 +567,11 @@ private void doDiskExport( String srcIdxText, FloppyDiskInfo[] disks ) } } } - catch( IOException ex ) { - BasicDlg.showErrorDlg( this, ex ); - } - catch( NumberFormatException ex ) {} } + catch( IOException ex ) { + BasicDlg.showErrorDlg( this, ex ); + } + catch( NumberFormatException ex ) {} } } } @@ -570,13 +604,16 @@ private void doDriveOpen() dlg.setVisible( true ); String driveFileName = dlg.getSelectedDriveFileName(); if( driveFileName != null ) { - openDrive( + if( openDrive( idx, driveFileName, dlg.isReadOnlySelected(), true, null, - null ); + null ) ) + { + Main.setLastDriveFileName( driveFileName ); + } } } } @@ -589,7 +626,7 @@ private void doFileOpen() File file = EmuUtil.showFileOpenDlg( this, "Diskettenabbilddatei \u00F6ffnen", - Main.getLastPathFile( "disk" ), + Main.getLastDirFile( "disk" ), EmuUtil.getPlainDiskFileFilter(), EmuUtil.getAnaDiskFileFilter(), EmuUtil.getCopyQMFileFilter(), @@ -597,7 +634,9 @@ private void doFileOpen() EmuUtil.getImageDiskFileFilter(), EmuUtil.getTeleDiskFileFilter() ); if( file != null ) { - openFile( idx, file, null, true, null, null ); + if( openFile( idx, file, null, true, null, null ) ) { + setLastFile( file ); + } } } } @@ -610,7 +649,7 @@ private void doFileAnaDiskNew() File file = EmuUtil.showFileSaveDlg( this, "Neue AnaDisk-Datei anlegen", - Main.getLastPathFile( "disk" ), + Main.getLastDirFile( "disk" ), EmuUtil.getAnaDiskFileFilter() ); if( file != null ) { if( DiskUtil.checkFileExt( this, file, DiskUtil.anaDiskFileExt ) ) { @@ -622,7 +661,7 @@ private void doFileAnaDiskNew() AnaDisk.newFile( this, file ), null ) ) { - Main.setLastFile( file, "disk" ); + setLastFile( file ); } } catch( IOException ex ) { @@ -642,7 +681,7 @@ private void doFileCPCDskNew() File file = EmuUtil.showFileSaveDlg( this, "Neue CPC-Disk-Datei anlegen", - Main.getLastPathFile( "disk" ), + Main.getLastDirFile( "disk" ), EmuUtil.getDskFileFilter() ); if( file != null ) { if( DiskUtil.checkFileExt( this, file, DiskUtil.dskFileExt ) ) { @@ -654,7 +693,7 @@ private void doFileCPCDskNew() CPCDisk.newFile( this, file ), null ) ) { - Main.setLastFile( file, "disk" ); + setLastFile( file ); } } catch( IOException ex ) { @@ -674,7 +713,7 @@ private void doFilePlainNew() File file = EmuUtil.showFileSaveDlg( this, "Einfache Abbilddatei anlegen", - Main.getLastPathFile( "disk" ), + Main.getLastDirFile( "disk" ), EmuUtil.getPlainDiskFileFilter() ); if( file != null ) { if( DiskUtil.checkFileExt( this, file, DiskUtil.plainDiskFileExt ) ) { @@ -686,7 +725,7 @@ private void doFilePlainNew() PlainDisk.newFile( this, file ), null ) ) { - Main.setLastFile( file, "disk" ); + setLastFile( file ); } } catch( IOException ex ) { @@ -721,12 +760,7 @@ private void doDiskRemove() int idx = this.tabbedPane.getSelectedIndex(); if( (idx >= 0) && (idx < this.drives.length) ) { if( this.drives[ idx ].getDisk() != null ) { - if( EmuUtil.parseBoolean( - Main.getProperty( "jkcemu.confirm.disk.remove" ), - true ) ) - { - removeDisk( idx ); - } + removeDisk( idx ); } } } @@ -740,6 +774,7 @@ private FloppyDiskStationFrm( ScreenFrm screenFrm ) this.emuSys = null; this.suitableDisks = null; this.etcDisks = null; + this.lastDir = null; this.lastFmt = null; this.lastAutoRefresh = false; this.lastForceLowerCase = false; @@ -864,7 +899,7 @@ public void actionPerformed( ActionEvent e ) // Sonstiges Runtime.getRuntime().addShutdownHook( - new Thread( "JKCEMU disk closer" ) + new Thread( Main.getThreadGroup(), "JKCEMU disk closer" ) { @Override public void run() @@ -1149,7 +1184,10 @@ private void openDirectory( int idx, File file ) + " liegenden Dateien kommen!\n\n" + "Stellen Sie bitte sicher, dass die Dateien" + " in dem Verzeichnis an einer anderen Stelle" - + " nochmals gesichert sind!", + + " nochmals gesichert sind\n" + + "und lesen Sie in der Hilfe den Abschnitt" + + " \u00FCber die Emulation einer Diskette" + + " auf Basis eines Verzeichnisses!", "Warnung", JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE ) != JOptionPane.OK_OPTION ) @@ -1171,32 +1209,37 @@ private void openDirectory( int idx, File file ) fmt.getDirBlocks(), fmt.getBlockSize(), fmt.isBlockNum16Bit(), + fmt.isDateStamperEnabled(), + new NIOFileTimesViewFactory(), file, this.lastAutoRefresh, readOnly, this.lastForceLowerCase ), null ); + this.lastDir = file; } } } - private void openDisk( + private boolean openDisk( int idx, FloppyDiskInfo diskInfo, Boolean skipOddCyls ) { + boolean rv = false; if( diskInfo != null ) { try { AbstractFloppyDisk disk = diskInfo.openDisk( this ); if( disk != null ) { - setDisk( idx, diskInfo.toString(), disk, skipOddCyls ); + rv = setDisk( idx, diskInfo.toString(), disk, skipOddCyls ); } } catch( IOException ex ) { BasicDlg.showErrorDlg( isVisible() ? this : this.screenFrm, ex ); } } + return rv; } @@ -1233,6 +1276,7 @@ private boolean openDisk( int idx, Properties props ) prefix + "sectors_per_cylinder", 0 ), EmuUtil.getIntProperty( props, prefix + "sectorsize", 0 ), + 1, EmuUtil.getIntProperty( props, prefix + "system_tracks", @@ -1280,6 +1324,8 @@ private boolean openDisk( int idx, Properties props ) fmt.getDirBlocks(), fmt.getBlockSize(), fmt.isBlockNum16Bit(), + fmt.isDateStamperEnabled(), + new NIOFileTimesViewFactory(), file, this.lastAutoRefresh, readOnly, @@ -1290,14 +1336,12 @@ private boolean openDisk( int idx, Properties props ) } } else if( !driveName.isEmpty() ) { - openDrive( idx, driveName, readOnly, false, fmt, skipOddCyls ); - rv = true; + rv = openDrive( idx, driveName, readOnly, false, fmt, skipOddCyls ); } else if( !fileName.isEmpty() ) { File file = new File( fileName ); if( file.isFile() ) { - openFile( idx, file, readOnly, false, fmt, skipOddCyls ); - rv = true; + rv = openFile( idx, file, readOnly, false, fmt, skipOddCyls ); } } else if( !resource.isEmpty() ) { @@ -1305,9 +1349,8 @@ else if( !resource.isEmpty() ) { if( this.suitableDisks != null ) { for( int i = 0; i < this.suitableDisks.length; i++ ) { if( resource.equals( this.suitableDisks[ i ].getResource() ) ) { - openDisk( idx, this.suitableDisks[ i ], skipOddCyls ); + rv = openDisk( idx, this.suitableDisks[ i ], skipOddCyls ); done = true; - rv = true; break; } } @@ -1315,8 +1358,7 @@ else if( !resource.isEmpty() ) { if( !done && (this.etcDisks != null) ) { for( int i = 0; i < this.etcDisks.length; i++ ) { if( resource.equals( this.etcDisks[ i ].getResource() ) ) { - openDisk( idx, this.etcDisks[ i ], skipOddCyls ); - rv = true; + rv = openDisk( idx, this.etcDisks[ i ], skipOddCyls ); break; } } @@ -1328,7 +1370,7 @@ else if( !resource.isEmpty() ) { } - private void openDrive( + private boolean openDrive( int idx, String fileName, boolean readOnly, @@ -1336,8 +1378,9 @@ private void openDrive( FloppyDiskFormat fmt, Boolean skipOddCyls ) { - DeviceIO.RandomAccessDevice rad = null; + boolean rv = true; boolean done = true; + DeviceIO.RandomAccessDevice rad = null; String errMsg = null; String driveName = fileName; if( fileName.startsWith( "\\\\.\\" ) && (fileName.length() > 4) ) { @@ -1345,7 +1388,7 @@ private void openDrive( } try { rad = DeviceIO.openDeviceForRandomAccess( fileName, readOnly ); - Main.setLastDriveFileName( fileName ); + rv = true; // Format erfragen int diskSize = DiskUtil.readDiskSize( rad ); @@ -1439,10 +1482,11 @@ private void openDrive( if( errMsg != null ) { BasicDlg.showErrorDlg( this, errMsg ); } + return rv; } - private void openFile( + private boolean openFile( int idx, File file, Boolean readOnly, @@ -1450,6 +1494,7 @@ private void openFile( FloppyDiskFormat fmt, Boolean skipOddCyls ) { + boolean rv = false; if( ((readOnly == null) && !interactive) || !file.canWrite() ) { readOnly = Boolean.TRUE; } @@ -1461,14 +1506,14 @@ private void openFile( { fileName = fileName.toLowerCase(); if( TextUtil.endsWith( fileName, DiskUtil.plainDiskFileExt ) ) { - openPlainDiskFile( - idx, - file, - fileLen, - readOnly, - interactive, - fmt, - skipOddCyls ); + rv = openPlainDiskFile( + idx, + file, + fileLen, + readOnly, + interactive, + fmt, + skipOddCyls ); } else if( TextUtil.endsWith( fileName, DiskUtil.gzPlainDiskFileExt ) ) @@ -1501,14 +1546,11 @@ private void openFile( fBuf, fmt ); if( disk != null ) { - if( setDisk( + rv = setDisk( idx, "Einfache Abbilddatei: " + fName, disk, - skipOddCyls ) ) - { - Main.setLastFile( file, "disk" ); - } + skipOddCyls ); } } } @@ -1528,14 +1570,11 @@ private void openFile( } } if( disk != null ) { - if( setDisk( + rv = setDisk( idx, "AnaDisk-Datei: " + file.getPath(), disk, - skipOddCyls ) ) - { - Main.setLastFile( file, "disk" ); - } + skipOddCyls ); } } } else if( TextUtil.endsWith( fileName, DiskUtil.dskFileExt ) ) { @@ -1554,33 +1593,28 @@ private void openFile( } } if( disk != null ) { - if( setDisk( + rv = setDisk( idx, "CPC-Disk-Datei: " + file.getPath(), disk, - skipOddCyls ) ) - { - Main.setLastFile( file, "disk" ); - } + skipOddCyls ); } } } else { AbstractFloppyDisk disk = DiskUtil.readNonPlainDiskFile( this, - file ); + file, + true ); if( disk != null ) { String fmtText = disk.getFileFormatText(); if( fmtText == null ) { fmtText = "Diskettenabbilddatei"; } - if( setDisk( + rv = setDisk( idx, fmtText + ": " + file.getPath(), disk, - skipOddCyls ) ) - { - Main.setLastFile( file, "disk" ); - } + skipOddCyls ); } else { boolean state = true; if( interactive ) { @@ -1614,14 +1648,14 @@ private void openFile( } } if( state ) { - openPlainDiskFile( - idx, - file, - fileLen, - readOnly, - interactive, - fmt, - skipOddCyls ); + rv = openPlainDiskFile( + idx, + file, + fileLen, + readOnly, + interactive, + fmt, + skipOddCyls ); } } } @@ -1630,10 +1664,11 @@ private void openFile( catch( IOException ex ) { showError( ex ); } + return rv; } - private void openPlainDiskFile( + private boolean openPlainDiskFile( int idx, File file, long fileLen, @@ -1642,6 +1677,7 @@ private void openPlainDiskFile( FloppyDiskFormat fmt, Boolean skipOddCyls ) throws IOException { + boolean rv = false; if( interactive && (fmt == null) ) { FloppyDiskFormatDlg dlg = null; if( readOnly == null ) { @@ -1699,16 +1735,14 @@ private void openPlainDiskFile( } } if( disk != null ) { - if( setDisk( - idx, - "Einfache Diskettenabbilddatei: " + file.getPath(), - disk, - skipOddCyls ) ) - { - Main.setLastFile( file, "disk" ); - } + rv = setDisk( + idx, + "Einfache Diskettenabbilddatei: " + file.getPath(), + disk, + skipOddCyls ); } } + return rv; } @@ -1831,6 +1865,12 @@ private boolean setDisk( } + private void setLastFile( File file ) + { + Main.setLastFile( file, "disk" ); + } + + private void showError( Exception ex ) { BasicDlg.showErrorDlg( isVisible() ? this : this.screenFrm, ex ); diff --git a/src/jkcemu/disk/GIDE.java b/src/jkcemu/disk/GIDE.java index 247003b..87e756e 100644 --- a/src/jkcemu/disk/GIDE.java +++ b/src/jkcemu/disk/GIDE.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2012 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -73,6 +73,7 @@ private enum Command { private volatile int curCmd; private volatile int curDiskIdx; private HardDisk curDisk; + private int[] offsets; private int[] cylinders; private int[] heads; private int[] sectorsPerTrack; @@ -121,14 +122,12 @@ public static GIDE getGIDE( Properties props, String propPrefix ) { - GIDE gide = null; - HardDisk[] disks = getHardDisks( props, propPrefix ); - if( disks != null ) { - if( disks.length > 0 ) { - gide = new GIDE( owner, propPrefix, disks ); - } - } - return gide; + return emulatesGIDE( props, propPrefix ) ? + new GIDE( + owner, + propPrefix, + getHardDisks( props, propPrefix ) ) + : null; } @@ -141,117 +140,115 @@ public boolean isInterruptRequest() public synchronized int read( int port ) { int rv = -1; - if( this.disks != null ) { - switch( port & 0x0F ) { + switch( port & 0x0F ) { - // Real Time Clock - case 0x05: - rv = this.rtc.read( port >> 8 ); - if( this.debugLevel > 0 ) { - System.out.printf( + // Real Time Clock + case 0x05: + rv = this.rtc.read( port >> 8 ); + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: read rtc reg %d: %02X\n", (port >> 8) & 0xFF, rv ); - } - break; + } + break; - /* - * Alternatives Status-Register - * Im Gegensatz zum regulaeren Status-Register fuehrt - * ein Lesezugriff nicht zu einer Interrupt-Bestaetigung. - */ - case 0x06: - rv = this.statusReg; - if( this.debugLevel > 2 ) { - System.out.printf( "GIDE: read alternate status: %02X\n", rv ); - } - break; + /* + * Alternatives Status-Register + * Im Gegensatz zum regulaeren Status-Register fuehrt + * ein Lesezugriff nicht zu einer Interrupt-Bestaetigung. + */ + case 0x06: + rv = this.statusReg; + if( this.debugLevel > 2 ) { + System.out.printf( "GIDE: read alternate status: %02X\n", rv ); + } + break; - /* - * Drive Address Register: - * Laufwerks- und Kopfnummer werden widergespiegelt. - */ - case 0x07: - rv = this.sdhReg & 0x1F; - if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: read drive+head: %02X\n", rv ); - } - break; + /* + * Drive Address Register: + * Laufwerks- und Kopfnummer werden widergespiegelt. + */ + case 0x07: + rv = this.sdhReg & 0x1F; + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: read drive+head: %02X\n", rv ); + } + break; - // Datenregister - case 0x08: - this.interruptRequest = false; - rv = readDataReg(); - if( this.debugLevel > 1 ) { - System.out.printf( "GIDE: read data: %02X\n", rv ); - } - break; + // Datenregister + case 0x08: + this.interruptRequest = false; + rv = readDataReg(); + if( this.debugLevel > 1 ) { + System.out.printf( "GIDE: read data: %02X\n", rv ); + } + break; - // Fehlerregister - case 0x09: - rv = this.errorReg; - if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: read error: %02X\n", rv ); - } - break; + // Fehlerregister + case 0x09: + rv = this.errorReg; + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: read error: %02X\n", rv ); + } + break; - // Sektoranzahl - case 0x0A: - rv = this.sectorCnt; - if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: read sector count: %02X\n", rv ); - } - break; + // Sektoranzahl + case 0x0A: + rv = this.sectorCnt; + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: read sector count: %02X\n", rv ); + } + break; - // Sektornummer - case 0x0B: - rv = this.sectorNum; - if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: read sector number: %02X\n", rv ); - } - break; + // Sektornummer + case 0x0B: + rv = this.sectorNum; + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: read sector number: %02X\n", rv ); + } + break; - // Zylindernummer (L-Byte) - case 0x0C: - rv = this.cylNum & 0xFF; - if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: cyl number (low byte): %02X\n", rv ); - } - break; + // Zylindernummer (L-Byte) + case 0x0C: + rv = this.cylNum & 0xFF; + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: cyl number (low byte): %02X\n", rv ); + } + break; - // Zylindernummer (H-Byte) - case 0x0D: - rv = (this.cylNum >> 8) & 0xFF; - if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: cyl number (high byte): %02X\n", rv ); - } - break; + // Zylindernummer (H-Byte) + case 0x0D: + rv = (this.cylNum >> 8) & 0xFF; + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: cyl number (high byte): %02X\n", rv ); + } + break; - /* - * SDH-Register: - * Bit 0-3: Kopfnummer - * Bit 4: Laufwerksauswahl (0: Master, 1: Slave) - * Bit 5-6: Sektorgroesse, - * muss immer auf 01 gesetzt sein (512 Bytes) - * Bit 8: keine CRC-Daten an Sektor anhaengen, - * muss immer gesetzt sein - */ - case 0x0E: - rv = this.sdhReg; - if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: read sdh: %02X\n", rv ); - } - break; + /* + * SDH-Register: + * Bit 0-3: Kopfnummer + * Bit 4: Laufwerksauswahl (0: Master, 1: Slave) + * Bit 5-6: Sektorgroesse, + * muss immer auf 01 gesetzt sein (512 Bytes) + * Bit 8: keine CRC-Daten an Sektor anhaengen, + * muss immer gesetzt sein + */ + case 0x0E: + rv = this.sdhReg; + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: read sdh: %02X\n", rv ); + } + break; - // Statusregister - case 0x0F: - rv = this.statusReg; - this.interruptRequest = false; - if( this.debugLevel > 2 ) { - System.out.printf( "GIDE: read status: %02X\n", rv ); - } - break; - } + // Statusregister + case 0x0F: + rv = this.statusReg; + this.interruptRequest = false; + if( this.debugLevel > 2 ) { + System.out.printf( "GIDE: read status: %02X\n", rv ); + } + break; } return rv; } @@ -268,7 +265,8 @@ public synchronized void reset() this.ioTaskThread.interrupt(); if( this.disks != null ) { boolean sizeOK = false; - if( (this.cylinders != null) + if( (this.offsets != null) + && (this.cylinders != null) && (this.heads != null) && (this.sectorsPerTrack != null) && (this.totalSectors != null) ) @@ -282,6 +280,7 @@ public synchronized void reset() } } if( !sizeOK ) { + this.offsets = new int[ this.disks.length ]; this.cylinders = new int[ this.disks.length ]; this.heads = new int[ this.disks.length ]; this.sectorsPerTrack = new int[ this.disks.length ]; @@ -289,6 +288,7 @@ public synchronized void reset() } int maxSectsPerTrack = 0; for( int i = 0; i < this.disks.length; i++ ) { + this.offsets[ i ] = disks[ i ].getOffset(); this.cylinders[ i ] = disks[ i ].getCylinders(); this.heads[ i ] = disks[ i ].getHeads(); this.sectorsPerTrack[ i ] = disks[ i ].getSectorsPerTrack(); @@ -319,99 +319,97 @@ public synchronized void reset() public synchronized void write( int port, int value ) { value &= 0xFF; - if( this.disks != null ) { - switch( port & 0x0F ) { + switch( port & 0x0F ) { - // Real Time Clock - case 0x05: - if( this.debugLevel > 0 ) { - System.out.printf( + // Real Time Clock + case 0x05: + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: write rtc reg %d: %02X\n", (port >> 8) & 0xFF, value ); - } - this.rtc.write( port >> 8, value ); - break; + } + this.rtc.write( port >> 8, value ); + break; - // Digital Output - case 0x06: - { + // Digital Output + case 0x06: + { + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: write digital output: %02X\n", value ); + } + boolean b = ((value & 0x04) != 0); + if( b && !this.resetFlag ) { if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: write digital output: %02X\n", value ); - } - boolean b = ((value & 0x04) != 0); - if( b && !this.resetFlag ) { - if( this.debugLevel > 0 ) { - System.out.println( "GIDE: soft reset" ); - } - softReset(); + System.out.println( "GIDE: soft reset" ); } - this.resetFlag = b; + softReset(); } - this.interruptEnabled = ((value & 0x02) != 0); - fireInterrupt(); - break; + this.resetFlag = b; + } + this.interruptEnabled = ((value & 0x02) != 0); + fireInterrupt(); + break; - // Datenregister - case 0x08: - if( this.debugLevel > 1 ) { - System.out.printf( "GIDE: write data: %02X\n", value ); - } - writeDataReg( value ); - break; + // Datenregister + case 0x08: + if( this.debugLevel > 1 ) { + System.out.printf( "GIDE: write data: %02X\n", value ); + } + writeDataReg( value ); + break; - // Sektoranzahl - case 0x0A: - if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: write sector count: %02X\n", value ); - } - this.sectorCnt = value; - break; + // Sektoranzahl + case 0x0A: + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: write sector count: %02X\n", value ); + } + this.sectorCnt = value; + break; - // Sektornummer - case 0x0B: - if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: write sector number: %02X\n", value ); - } - this.sectorNum = value; - break; + // Sektornummer + case 0x0B: + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: write sector number: %02X\n", value ); + } + this.sectorNum = value; + break; - // Zylindernummer (L-Byte) - case 0x0C: - if( this.debugLevel > 0 ) { - System.out.printf( + // Zylindernummer (L-Byte) + case 0x0C: + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: write cyl number (low byte): %02X\n", value ); - } - this.cylNum = (this.cylNum & 0xFF00) | value; - break; + } + this.cylNum = (this.cylNum & 0xFF00) | value; + break; - // Zylindernummer (H-Byte) - case 0x0D: - if( this.debugLevel > 0 ) { - System.out.printf( + // Zylindernummer (H-Byte) + case 0x0D: + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: write cyl number (high byte): %02X\n", value ); - } - this.cylNum = ((value << 8) & 0xFF00) | (this.cylNum & 0x00FF); - break; + } + this.cylNum = ((value << 8) & 0xFF00) | (this.cylNum & 0x00FF); + break; - // SDH - case 0x0E: - if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: write sdh: %02X\n", value ); - } - this.sdhReg = value; - break; + // SDH + case 0x0E: + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: write sdh: %02X\n", value ); + } + this.sdhReg = value; + break; - // Kommando - case 0x0F: - if( this.debugLevel > 0 ) { - System.out.printf( "GIDE: write command: %02X\n", value ); - } - execCmd( value ); - break; - } + // Kommando + case 0x0F: + if( this.debugLevel > 0 ) { + System.out.printf( "GIDE: write command: %02X\n", value ); + } + execCmd( value ); + break; } } @@ -460,7 +458,9 @@ private GIDE( Component owner, String propPrefix, HardDisk[] disks ) this.owner = owner; this.propPrefix = propPrefix; this.disks = disks; + this.debugLevel = 0; this.rtc = new RTC7242X(); + this.offsets = null; this.cylinders = null; this.heads = null; this.sectorsPerTrack = null; @@ -468,8 +468,10 @@ private GIDE( Component owner, String propPrefix, HardDisk[] disks ) this.ioBuf = null; this.ioTaskEnabled = true; this.ioTaskNoWait = false; - this.ioTaskThread = new Thread( this, "JKCEMU GIDE" ); - this.debugLevel = 0; + this.ioTaskThread = new Thread( + Main.getThreadGroup(), + this, + "JKCEMU GIDE" ); String text = System.getProperty( "jkcemu.debug.gide" ); if( text != null ) { @@ -506,13 +508,14 @@ private long calcFilePos() + (headNum * spt) + (this.sectorNum - 1); if( (nSec >= 0) && (nSec <= this.totalSectors[ this.curDiskIdx ]) ) { - rv = 0x0100 + (nSec * SECTOR_SIZE); + rv = this.offsets[ this.curDiskIdx ] + (nSec * SECTOR_SIZE); } } if( (rv < 0) && (this.debugLevel > 3) ) { System.out.printf( - "GIDE calc file pos error: cyl=%d head=%d sector=%d" + "GIDE calc file pos error: offs=%d cyl=%d head=%d sector=%d" + " cyls=%d heads=%d sectors_per_track=%d\n", + this.offsets[ this.curDiskIdx ], this.cylNum, headNum, this.sectorNum, @@ -588,14 +591,19 @@ private static boolean emulatesGIDE( String propPrefix ) { boolean rv = true; - for( int i = 0; i < propKeys.length; i++ ) { - if( EmuUtil.getProperty( + String s = EmuUtil.getProperty( props, propPrefix + "enabled" ); + if( s.isEmpty() ) { + for( int i = 0; i < propKeys.length; i++ ) { + if( EmuUtil.getProperty( props, propPrefix + "harddisk.1." + propKeys[ i ] ).isEmpty() ) - { - rv = false; - break; + { + rv = false; + break; + } } + } else { + rv = Boolean.parseBoolean( s ); } return rv; } @@ -739,7 +747,7 @@ private void execCmdIdentifyDrive() setIOBufWord( 8, this.curDisk.getSectorsPerTrack() * SECTOR_SIZE ); setIOBufWord( 10, SECTOR_SIZE ); setIOBufWord( 12, this.curDisk.getSectorsPerTrack() ); - setIOBufASCII( 20, Main.VERSION, 20 ); + setIOBufASCII( 20, Main.APPINFO, 20 ); setIOBufWord( 42, 1 ); // 1 Sektor Puffer setIOBufASCII( 46, "JKCEMU", 8 ); @@ -1065,8 +1073,15 @@ private static HardDisk[] getHardDisks( props, prefix + "sectors_per_track", 0 ); + int offset = EmuUtil.getIntProperty( props, prefix + "offset", 0 ); + File file = new File( fileName ); + if( file.exists() ) { + if( (file.length() % 512L) >= 0x100 ) { + offset = 0x100; + } + } if( (c > 0) && (h > 0) && (n > 0) ) { - disk = new HardDisk( diskModel, c, h, n, fileName ); + disk = new HardDisk( diskModel, c, h, n, fileName, offset ); } } if( disk != null ) { @@ -1309,9 +1324,10 @@ private void writeFormatByte( int value ) { long sectOffs = (this.cylNum * heads * spt) + (headNum * spt); startIOTask( - this.curDisk.getFile(), - 0x0100 + (sectOffs * ((long) SECTOR_SIZE)), - spt * SECTOR_SIZE ); + this.curDisk.getFile(), + this.offsets[ this.curDiskIdx ] + + (sectOffs * ((long) SECTOR_SIZE)), + spt * SECTOR_SIZE ); cmdFinished = false; } } diff --git a/src/jkcemu/disk/GIDESettingsFld.java b/src/jkcemu/disk/GIDESettingsFld.java index 3b2875c..1318cf5 100644 --- a/src/jkcemu/disk/GIDESettingsFld.java +++ b/src/jkcemu/disk/GIDESettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2013 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -25,12 +25,14 @@ public class GIDESettingsFld extends AbstractSettingsFld private int popupDiskIdx; private JPopupMenu popupMenu; private JMenuItem mnuDiskNone; + private JCheckBox btnEnabled; private HardDiskInfo[] diskTypes; private JLabel[] titleLabels; private JTextField[] diskTypeFlds; private JButton[] diskTypeBtns; private FileNameFld[] fileNameFlds; private JButton[] selectBtns; + private int[] offsets; public GIDESettingsFld( @@ -47,6 +49,8 @@ public GIDESettingsFld( this.diskTypeBtns = new JButton[ DISK_CNT ]; this.fileNameFlds = new FileNameFld[ DISK_CNT ]; this.selectBtns = new JButton[ DISK_CNT ]; + this.offsets = new int[ DISK_CNT ]; + Arrays.fill( this.offsets, 0 ); // Fensterinhalt @@ -60,10 +64,14 @@ public GIDESettingsFld( new Insets( 5, 5, 5, 5 ), 0, 0 ); + this.btnEnabled = new JCheckBox( "GIDE emulieren" ); + add( this.btnEnabled, gbc ); + gbc.gridy++; for( int i = 0; i < DISK_CNT; i++ ) { addDiskFlds( i, gbc ); gbc.gridy++; } + this.btnEnabled.addActionListener( this ); } @@ -76,6 +84,10 @@ public void applyInput( { try { if( props != null ) { + EmuUtil.setProperty( + props, + this.propPrefix + "enabled", + this.btnEnabled.isSelected() ); for( int i = 0; i < DISK_CNT; i++ ) { String prefix = createPrefix( i ); HardDiskInfo info = this.diskTypes[ i ]; @@ -92,6 +104,10 @@ public void applyInput( i + 1 ) ); } } + EmuUtil.setProperty( + props, + prefix + "offset", + this.offsets[ i ] ); EmuUtil.setProperty( props, prefix + "model", @@ -130,48 +146,54 @@ public boolean doAction( EventObject e ) boolean rv = false; Object src = e.getSource(); if( src != null ) { - int idx = indexOf( this.diskTypeBtns, src ); - if( idx >= 0 ) { + if( src == this.btnEnabled ) { rv = true; - showDiskTypePopup( idx ); - } - if( !rv ) { - idx = indexOf( this.selectBtns, src ); + updFieldsEnabled(); + fireDataChanged(); + } else { + int idx = indexOf( this.diskTypeBtns, src ); if( idx >= 0 ) { rv = true; - doFileSelect( idx ); + showDiskTypePopup( idx ); } - } - if( !rv && (e instanceof ActionEvent) ) { - String actionCmd = ((ActionEvent) e).getActionCommand(); - if( actionCmd != null ) { - if( actionCmd.startsWith( "harddisk." ) ) { + if( !rv ) { + idx = indexOf( this.selectBtns, src ); + if( idx >= 0 ) { rv = true; - if( actionCmd.equals( "harddisk.select" ) ) { - HardDiskInfo info = HardDiskListDlg.showHardDiskListDlg( + doFileSelect( idx ); + } + } + if( !rv && (e instanceof ActionEvent) ) { + String actionCmd = ((ActionEvent) e).getActionCommand(); + if( actionCmd != null ) { + if( actionCmd.startsWith( "harddisk." ) ) { + rv = true; + if( actionCmd.equals( "harddisk.select" ) ) { + HardDiskInfo info = HardDiskListDlg.showHardDiskListDlg( this.settingsFrm ); - if( info != null ) { + if( info != null ) { + idx = this.popupDiskIdx; + if( (idx >= 0) && (idx < this.diskTypes.length) ) { + this.diskTypes[ idx ] = info; + this.diskTypeFlds[ idx ].setText( info.toString() ); + try { + this.diskTypeFlds[ idx ].setCaretPosition( 0 ); + } + catch( IllegalArgumentException ex ) {} + updFieldsEnabled(); + fireDataChanged(); + } + } + } else { idx = this.popupDiskIdx; if( (idx >= 0) && (idx < this.diskTypes.length) ) { - this.diskTypes[ idx ] = info; - this.diskTypeFlds[ idx ].setText( info.toString() ); - try { - this.diskTypeFlds[ idx ].setCaretPosition( 0 ); + if( actionCmd.equals( "harddisk.none" ) ) { + this.diskTypes[ idx ] = null; + this.diskTypeFlds[ idx ].setText( "" ); + setFile( idx, null, 0 ); + updFieldsEnabled(); + fireDataChanged(); } - catch( IllegalArgumentException ex ) {} - updFieldsEnabled(); - fireDataChanged(); - } - } - } else { - idx = this.popupDiskIdx; - if( (idx >= 0) && (idx < this.diskTypes.length) ) { - if( actionCmd.equals( "harddisk.none" ) ) { - this.diskTypes[ idx ] = null; - this.diskTypeFlds[ idx ].setText( "" ); - this.fileNameFlds[ idx ].setFile( null ); - updFieldsEnabled(); - fireDataChanged(); } } } @@ -214,7 +236,7 @@ public boolean fileDropped( Component c, File file ) for( int i = 0; i < DISK_CNT; i++ ) { if( c == this.fileNameFlds[ i ] ) { if( this.fileNameFlds[ i ].isEnabled() ) { - this.fileNameFlds[ i ].setFile( file ); + setFile( i, file, 0 ); updFieldsEnabled(); Main.setLastFile( file, "disk" ); rv = true; @@ -241,6 +263,7 @@ public void lookAndFeelChanged() public void setEnabled( boolean state ) { super.setEnabled( state ); + this.btnEnabled.setEnabled( state ); updFieldsEnabled(); } @@ -248,6 +271,11 @@ public void setEnabled( boolean state ) @Override public void updFields( Properties props ) { + this.btnEnabled.setSelected( + EmuUtil.getBooleanProperty( + props, + this.propPrefix + "enabled", + false ) ); for( int i = 0; i < DISK_CNT; i++ ) { String prefix = createPrefix( i ); String diskModel = EmuUtil.getProperty( props, prefix + "model" ); @@ -262,8 +290,7 @@ public void updFields( Properties props ) try { this.diskTypes[ i ] = new HardDiskInfo( null, diskModel, c, h, n ); String fName = EmuUtil.getProperty( props, prefix + "file" ); - this.fileNameFlds[ i ].setFile( - fName.isEmpty() ? null : new File( fName ) ); + setFile( i, fName.isEmpty() ? null : new File( fName ), 0 ); this.diskTypeFlds[ i ].setText( this.diskTypes[ i ].toString() ); } catch( IllegalArgumentException ex ) {} @@ -281,18 +308,16 @@ private void addDiskFlds( int idx, GridBagConstraints gbc ) this.diskTypes[ idx ] = null; // Trennzeile - if( idx > 0 ) { - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weightx = 1.0; - gbc.insets.top = 5; - gbc.insets.left = 5; - gbc.insets.right = 5; - gbc.insets.bottom = 5; - gbc.gridwidth = GridBagConstraints.REMAINDER; - gbc.gridx = 0; - add( new JSeparator(), gbc ); - gbc.gridy++; - } + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.insets.top = 10; + gbc.insets.left = 5; + gbc.insets.right = 5; + gbc.insets.bottom = 10; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx = 0; + add( new JSeparator(), gbc ); + gbc.gridy++; // 1. Zeile if( idx == 0 ) { @@ -372,9 +397,10 @@ private void doFileSelect( int idx ) if( (idx >= 0) && (idx < DISK_CNT) ) { File preSelection = this.fileNameFlds[ idx ].getFile(); if( preSelection == null ) { - preSelection = Main.getLastPathFile( "disk" ); + preSelection = Main.getLastDirFile( "disk" ); } - File file = EmuUtil.showFileOpenDlg( + int offset = -1; + File file = EmuUtil.showFileOpenDlg( this.settingsFrm, "Festplattenabbilddatei ausw\u00E4hlen", preSelection ); @@ -385,14 +411,39 @@ private void doFileSelect( int idx ) msg = "Datei ist keine regul\u00E4re Datei"; } } else { - BasicDlg.showInfoDlg( - this, + switch( OptionDlg.showOptionDlg( + this.settingsFrm, "Die angegebene Datei existiert nicht.\n" + "Formatieren Sie bitte das betreffende Laufwerk" + " vom emulierten System aus,\n" + "damit die Datei angelegt wird" - + " und einen Inhalt erh\u00E4lt.", - "Datei nicht gefunden" ); + + " und einen Inhalt erh\u00E4lt.\n\n" + + "Welchen Typ soll die Datei haben?\n" + + "Eine RAW-Datei enth\u00E4lt das reine Abbild" + + " und kann mit geeigneten Programmen\n" + + "direkt auf eine Festplatte oder DOM" + + " geschrieben werden.\n" + + "Eine kompatible Abbilddatei entspricht dem Format" + + " \u00E4lterer JKCEMU-Versionen\n" + + "und kann auch in manch anderem Emulator verwendet" + + " werden.\n" + + "Allerdings lassen sich kompatible Abbilddateien" + + " nicht mit \u00FCblichen Werkzeugen\n" + + "direkt auf eine Festplatte oder DOM schreiben.", + "Neue Abbilddatei", + 0, + "RAW-Datei", + "Kompatible Abbilddatei" ) ) + { + case 0: + offset = 0; + break; + case 1: + offset = 0x100; + break; + default: + file = null; + } } if( msg != null ) { BasicDlg.showErrorDlg( this, file.getPath() + ": " + msg ); @@ -400,7 +451,7 @@ private void doFileSelect( int idx ) } } if( file != null ) { - this.fileNameFlds[ idx ].setFile( file ); + setFile( idx, file, offset ); updFieldsEnabled(); Main.setLastFile( file, "disk" ); fireDataChanged(); @@ -422,6 +473,22 @@ private int indexOf( Object[] a, Object o ) } + private void setFile( int idx, File file, int defaultOffset ) + { + this.fileNameFlds[ idx ].setFile( file ); + if( file != null ) { + if( file.exists() ) { + int remainBytes = (int) (file.length() % 512L); + this.offsets[ idx ] = (remainBytes >= 0x100 ? 0x100 : 0); + } else { + this.offsets[ idx ] = defaultOffset; + } + } else { + this.offsets[ idx ] = 0; + } + } + + private void showDiskTypePopup( int idx ) { if( this.popupMenu == null ) { @@ -445,7 +512,7 @@ private void showDiskTypePopup( int idx ) public void updFieldsEnabled() { - boolean stateGeneral = isEnabled(); + boolean stateGeneral = (isEnabled() && this.btnEnabled.isSelected()); for( int i = 0; i < DISK_CNT; i++ ) { boolean state = false; if( stateGeneral ) { diff --git a/src/jkcemu/disk/HardDisk.java b/src/jkcemu/disk/HardDisk.java index 8710d8d..16f1dc4 100644 --- a/src/jkcemu/disk/HardDisk.java +++ b/src/jkcemu/disk/HardDisk.java @@ -1,5 +1,5 @@ /* - * (c) 2010 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -16,6 +16,7 @@ public class HardDisk { private String diskModel; private File file; + private int offset; private int cylinders; private int heads; private int sectorsPerTrack; @@ -26,10 +27,12 @@ public HardDisk( int cylinders, int heads, int sectorsPerTrack, - String fileName ) + String fileName, + int offset ) { this.diskModel = diskModel; this.file = new File( fileName ); + this.offset = offset; this.cylinders = cylinders; this.heads = heads; this.sectorsPerTrack = sectorsPerTrack; @@ -60,6 +63,12 @@ public int getHeads() } + public int getOffset() + { + return this.offset; + } + + public int getSectorsPerTrack() { return this.sectorsPerTrack; @@ -71,6 +80,7 @@ public boolean isSameDisk( HardDisk disk ) boolean rv = false; if( disk != null ) { if( disk.file.equals( this.file ) + && (disk.offset == this.offset) && (disk.cylinders == this.cylinders) && (disk.heads == this.heads) && (disk.sectorsPerTrack == this.sectorsPerTrack) ) @@ -81,4 +91,3 @@ public boolean isSameDisk( HardDisk disk ) return rv; } } - diff --git a/src/jkcemu/disk/HardDiskDetailsDlg.java b/src/jkcemu/disk/HardDiskDetailsDlg.java index b81f2d8..d6a3e7d 100644 --- a/src/jkcemu/disk/HardDiskDetailsDlg.java +++ b/src/jkcemu/disk/HardDiskDetailsDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2012 Jens Mueller + * (c) 2011-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -21,15 +21,15 @@ public class HardDiskDetailsDlg extends BasicDlg { - private HardDiskInfo approvedData; - private JComboBox comboProducer; - private JTextField fldProducer; - private JTextField fldModel; - private JTextField fldCylinders; - private JTextField fldHeads; - private JTextField fldSectors; - private JButton btnOK; - private JButton btnCancel; + private HardDiskInfo approvedData; + private JComboBox comboProducer; + private JTextField fldProducer; + private JTextField fldModel; + private JTextField fldCylinders; + private JTextField fldHeads; + private JTextField fldSectors; + private JButton btnOK; + private JButton btnCancel; public static HardDiskInfo showHardDiskDetailsDlg( @@ -122,7 +122,7 @@ private HardDiskDetailsDlg( Window owner, Set producers ) this.fldProducer = null; if( producers != null ) { if( !producers.isEmpty() ) { - this.comboProducer = new JComboBox(); + this.comboProducer = new JComboBox<>(); for( String producer : producers ) { this.comboProducer.addItem( producer ); } diff --git a/src/jkcemu/disk/HardDiskListDlg.java b/src/jkcemu/disk/HardDiskListDlg.java index 072a77b..0ac37ee 100644 --- a/src/jkcemu/disk/HardDiskListDlg.java +++ b/src/jkcemu/disk/HardDiskListDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2012 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -306,7 +306,7 @@ private void doAdd() { EmuUtil.stopCellEditing( this.table ); - Set producers = new TreeSet(); + Set producers = new TreeSet<>(); int nRows = this.tableModel.getRowCount(); for( int i = 0; i < nRows; i++ ) { diff --git a/src/jkcemu/disk/HardDiskTableModel.java b/src/jkcemu/disk/HardDiskTableModel.java index 95cd563..224545d 100644 --- a/src/jkcemu/disk/HardDiskTableModel.java +++ b/src/jkcemu/disk/HardDiskTableModel.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2011 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -32,7 +32,7 @@ public class HardDiskTableModel extends AbstractTableModel public HardDiskTableModel( HardDiskListDlg owner ) { this.owner = owner; - this.rows = new ArrayList(); + this.rows = new ArrayList<>(); } diff --git a/src/jkcemu/disk/ImageDisk.java b/src/jkcemu/disk/ImageDisk.java index ef1311a..fdbff4f 100644 --- a/src/jkcemu/disk/ImageDisk.java +++ b/src/jkcemu/disk/ImageDisk.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -16,6 +16,7 @@ import java.util.*; import java.util.zip.GZIPInputStream; import jkcemu.base.EmuUtil; +import jkcemu.text.CharConverter; public class ImageDisk extends AbstractFloppyDisk @@ -42,7 +43,7 @@ public static void export( int sectorBufSize = 1024; for( int cyl = 0; !errorOrDeleted && (cyl < cyls); cyl++ ) { for( int head = 0; !errorOrDeleted && (head < sides); head++ ) { - int n = disk.getSectorsPerCylinder(); + int n = disk.getSectorsOfCylinder( cyl, head ); for( int i = 0; i < n; i++ ) { SectorData sector = disk.getSectorByIndex( cyl, head, i ); if( sector != null ) { @@ -59,8 +60,8 @@ public static void export( } // Zeitstempel aufbereiten + Calendar calendar = new GregorianCalendar(); java.util.Date diskDate = disk.getDiskDate(); - Calendar calendar = Calendar.getInstance(); if( diskDate != null ) { calendar.clear(); calendar.setTime( diskDate ); @@ -75,7 +76,7 @@ public static void export( // Datei oeffnen OutputStream out = null; try { - out = new FileOutputStream( file ); + out = EmuUtil.createOptionalGZipOutputStream( file ); // Dateikopf EmuUtil.writeASCII( @@ -94,13 +95,17 @@ public static void export( remark = disk.getRemark(); } if( remark != null ) { - int len = remark.length(); + CharConverter cc = new CharConverter( CharConverter.Encoding.CP850 ); + int len = remark.length(); for( int i = 0; i < len; i++ ) { - int ch = remark.charAt( i ); - if( (ch == 0x00) || (ch == 0x1A) || (ch >= 0x80) ) { + char ch = remark.charAt( i ); + if( (ch == '\u0000') || (ch == '\u001A') ) { break; } - out.write( ch ); + ch = (char) cc.toCharsetByte( ch ); + if( ch > '\u0000' ) { + out.write( ch ); + } } } out.write( 0x1A ); @@ -108,7 +113,7 @@ public static void export( // Spuren for( int cyl = 0; cyl < cyls; cyl++ ) { for( int head = 0; head < sides; head++ ) { - int nSec = disk.getSectorsPerCylinder(); + int nSec = disk.getSectorsOfCylinder( cyl, head ); if( nSec > 0 ) { SectorData[] sectors = new SectorData[ nSec ]; boolean cylMap = false; @@ -264,7 +269,7 @@ public static ImageDisk readFile( throwNoImageDiskFile(); } for( int i = 0; i < 6; i++ ) { - readByte( in ); + readMandatoryByte( in ); } // Zeitstempel lesen @@ -272,7 +277,7 @@ public static ImageDisk readFile( char[] cBuf = new char[ 19 ]; int pos = 0; while( pos < cBuf.length ) { - cBuf[ pos++ ] = (char) readByte( in ); + cBuf[ pos++ ] = (char) readMandatoryByte( in ); } try { diskDate = (new SimpleDateFormat( @@ -283,14 +288,18 @@ public static ImageDisk readFile( // Kommentar String remark = null; - int b = readByte( in ); + int b = readMandatoryByte( in ); if( b != 0x1A ) { + CharConverter cc = new CharConverter( CharConverter.Encoding.CP850 ); StringBuilder buf = new StringBuilder( 1024 ); while( b != 0x1A ) { - buf.append( (char) (b & 0x7F) ); - b = readByte( in ); + b = cc.toUnicode( (char) b ); + if( b > 0 ) { + buf.append( (char) b ); + } + b = readMandatoryByte( in ); } - remark = buf.toString().trim(); + remark = buf.toString(); } if( b != 0x1A ) { throwNoImageDiskFile(); @@ -302,10 +311,10 @@ public static ImageDisk readFile( int transferRate = in.read(); while( transferRate >= 0 ) { - int cyl = readByte( in ); - int head = readByte( in ); - int nSec = readByte( in ); - int sizeCode = readByte( in ); + int cyl = readMandatoryByte( in ); + int head = readMandatoryByte( in ); + int nSec = readMandatoryByte( in ); + int sizeCode = readMandatoryByte( in ); if( sizeCode > 6 ) { throw new IOException( String.format( @@ -320,7 +329,7 @@ public static ImageDisk readFile( // Sektornummerntabelle int[] sectorNums = new int[ nSec ]; for( int i = 0; i < nSec; i++ ) { - sectorNums[ i ] = readByte( in ); + sectorNums[ i ] = readMandatoryByte( in ); } // Sektorzylindertabelle @@ -328,7 +337,7 @@ public static ImageDisk readFile( if( (head & 0x80) != 0 ) { sectorCyls = new int[ nSec ]; for( int i = 0; i < nSec; i++ ) { - sectorCyls[ i ] = readByte( in ); + sectorCyls[ i ] = readMandatoryByte( in ); } } @@ -337,7 +346,7 @@ public static ImageDisk readFile( if( (head & 0x40) != 0 ) { sectorHeads = new int[ nSec ]; for( int i = 0; i < nSec; i++ ) { - sectorHeads[ i ] = readByte( in ); + sectorHeads[ i ] = readMandatoryByte( in ); } } @@ -349,7 +358,7 @@ public static ImageDisk readFile( byte[] secBuf = null; int fillByte = 0; int secNum = sectorNums[ i ]; - int secType = readByte( in ); + int secType = readMandatoryByte( in ); switch( secType ) { case 0: // keine Daten break; @@ -369,7 +378,7 @@ public static ImageDisk readFile( case 6: // komprimierte Daten mit Fehler case 8: // komprimierte und geloeschte Daten mit Fehler secBuf = new byte[ sectorSize ]; - fillByte = readByte( in ); + fillByte = readMandatoryByte( in ); Arrays.fill( secBuf, (byte) fillByte ); break; @@ -396,12 +405,12 @@ public static ImageDisk readFile( Map> map = null; if( head == 0 ) { if( side0 == null ) { - side0 = new HashMap>(); + side0 = new HashMap<>(); } map = side0; } else if( head == 1 ) { if( side1 == null ) { - side1 = new HashMap>(); + side1 = new HashMap<>(); } map = side1; } @@ -409,7 +418,7 @@ public static ImageDisk readFile( Integer keyObj = new Integer( cyl ); java.util.List sectors = map.get( keyObj ); if( sectors == null ) { - sectors = new ArrayList( nSec > 0 ? nSec : 1 ); + sectors = new ArrayList<>( nSec > 0 ? nSec : 1 ); map.put( keyObj, sectors ); } int secCyl = cyl; @@ -424,18 +433,18 @@ public static ImageDisk readFile( secHead = sectorHeads[ i ]; } } - sectors.add( - new SectorData( - i, - secCyl, - secHead, - secNum, - sizeCode, - crcError, - deleted, - secBuf, - 0, - secBuf != null ? secBuf.length : 0 ) ); + SectorData sector = new SectorData( + i, + secCyl, + secHead, + secNum, + sizeCode, + secBuf, + 0, + secBuf != null ? secBuf.length : 0 ); + sector.setError( crcError ); + sector.setDeleted( deleted ); + sectors.add( sector ); if( cyl >= cyls ) { cyls = cyl + 1; @@ -574,6 +583,16 @@ private java.util.List getSectorList( } + private static int readMandatoryByte( InputStream in ) throws IOException + { + int b = in.read(); + if( b < 0 ) { + throwUnexpectedEOF(); + } + return b; + } + + private static void throwNoImageDiskFile() throws IOException { throw new IOException( "Datei ist keine ImageDisk-Datei." ); diff --git a/src/jkcemu/disk/PlainDisk.java b/src/jkcemu/disk/PlainDisk.java index 1b99bfe..a9ce12b 100644 --- a/src/jkcemu/disk/PlainDisk.java +++ b/src/jkcemu/disk/PlainDisk.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -42,6 +42,7 @@ public static PlainDisk createForDrive( fmt.getCylinders(), fmt.getSectorsPerCylinder(), fmt.getSectorSize(), + 0, // kein Interleave driveFileName, rad, null, @@ -53,10 +54,11 @@ public static PlainDisk createForDrive( public static PlainDisk createForByteArray( - Frame owner, - String fileName, - byte[] fileBytes, - FloppyDiskFormat fmt ) throws IOException + Frame owner, + String fileName, + byte[] fileBytes, + FloppyDiskFormat fmt, + int interleave ) throws IOException { PlainDisk rv = null; if( fileBytes != null ) { @@ -66,6 +68,7 @@ public static PlainDisk createForByteArray( fmt.getCylinders(), fmt.getSectorsPerCylinder(), fmt.getSectorSize(), + interleave, fileName, null, null, @@ -78,6 +81,16 @@ public static PlainDisk createForByteArray( } + public static PlainDisk createForByteArray( + Frame owner, + String fileName, + byte[] fileBytes, + FloppyDiskFormat fmt ) throws IOException + { + return createForByteArray( owner, fileName, fileBytes, fmt, 0 ); + } + + public static PlainDisk createForFile( Frame owner, String driveFileName, @@ -91,6 +104,7 @@ public static PlainDisk createForFile( fmt.getCylinders(), fmt.getSectorsPerCylinder(), fmt.getSectorSize(), + 0, // kein Interleave driveFileName, null, raf, @@ -207,6 +221,7 @@ public static PlainDisk newFile( Frame owner, File file ) throws IOException 0, 0, 0, + 0, // kein Interleave file.getPath(), null, raf, @@ -245,6 +260,7 @@ public static PlainDisk openFile( fmt.getCylinders(), fmt.getSectorsPerCylinder(), fmt.getSectorSize(), + 0, // kein Interleave file.getPath(), null, raf, @@ -350,88 +366,10 @@ public synchronized SectorData getSectorByIndex( int physHead, int sectorIdx ) { - SectorData rv = null; - int sectorSize = getSectorSize(); - long filePos = calcFilePos( physCyl, physHead, sectorIdx ); - if( (sectorSize > 0) && (filePos >= 0) ) { - if( this.diskBytes != null ) { - if( filePos <= Integer.MAX_VALUE ) { - rv = new SectorData( - sectorIdx, - physCyl, - physHead, - sectorIdx + 1, - this.sectorSizeCode, - false, - false, - this.diskBytes, - (int) filePos, - sectorSize ); - } - } - else if( (this.rad != null) || (this.raf != null) ) { - try { - byte[] buf = new byte[ sectorSize ]; - int len = -1; - if( this.rad != null ) { - this.rad.seek( filePos ); - len = this.rad.read( buf, 0, buf.length ); - } else { - this.raf.seek( filePos ); - len = this.raf.read( buf ); - } - if( len > 0 ) { - // falls nicht vollstaendig gelesen wurde - while( len < buf.length ) { - int n = -1; - if( this.rad != null ) { - n = this.rad.read( buf, len, buf.length - len ); - } else { - n = this.raf.read( buf, len, buf.length - len ); - } - if( n > 0 ) { - len += n; - } else { - break; - } - } - } - if( len > 0 ) { - rv = new SectorData( - sectorIdx, + return getSectorByIndexInternal( physCyl, physHead, - sectorIdx + 1, - this.sectorSizeCode, - false, - false, - buf, - 0, - len ); - } - } - catch( IOException ex ) { - fireShowReadError( physCyl, physHead, sectorIdx + 1, ex ); - rv = new SectorData( - sectorIdx, - physCyl, - physHead, - sectorIdx + 1, - this.sectorSizeCode, - true, // CRC-Fehler - false, - null, - 0, - 0 ); - } - } - if( rv != null ) { - rv.setDisk( this ); - rv.setFilePos( filePos ); - rv.setFilePortionLen( sectorSize ); - } - } - return rv; + sectorIndexToInterleave( sectorIdx ) ); } @@ -444,7 +382,10 @@ public SectorData getSectorByID( int sectorNum, int sizeCode ) { - SectorData rv = getSectorByIndex( physCyl, physHead, sectorNum - 1 ); + SectorData rv = getSectorByIndexInternal( + physCyl, + physHead, + sectorNum - 1 ); if( rv != null ) { if( (rv.getCylinder() != cyl) || (rv.getHead() != head) @@ -532,6 +473,7 @@ private PlainDisk( int cyls, int sectorsPerCyl, int sectorSize, + int interleave, String fileName, DeviceIO.RandomAccessDevice rad, RandomAccessFile raf, @@ -540,7 +482,7 @@ private PlainDisk( boolean readOnly, boolean appendable ) { - super( owner, sides, cyls, sectorsPerCyl, sectorSize ); + super( owner, sides, cyls, sectorsPerCyl, sectorSize, interleave ); this.fileName = fileName; this.rad = rad; this.raf = raf; @@ -599,4 +541,89 @@ else if( (head == 1) && (sectorsPerCyl > 0) ) { } return rv; } + + + private synchronized SectorData getSectorByIndexInternal( + int physCyl, + int physHead, + int sectorIdx ) + { + SectorData rv = null; + int sectorSize = getSectorSize(); + long filePos = calcFilePos( physCyl, physHead, sectorIdx ); + if( (sectorSize > 0) && (filePos >= 0) ) { + if( this.diskBytes != null ) { + if( filePos <= Integer.MAX_VALUE ) { + rv = new SectorData( + sectorIdx, + physCyl, + physHead, + sectorIdx + 1, + this.sectorSizeCode, + this.diskBytes, + (int) filePos, + sectorSize ); + } + } + else if( (this.rad != null) || (this.raf != null) ) { + try { + byte[] buf = new byte[ sectorSize ]; + int len = -1; + if( this.rad != null ) { + this.rad.seek( filePos ); + len = this.rad.read( buf, 0, buf.length ); + } else { + this.raf.seek( filePos ); + len = this.raf.read( buf ); + } + if( len > 0 ) { + // falls nicht vollstaendig gelesen wurde + while( len < buf.length ) { + int n = -1; + if( this.rad != null ) { + n = this.rad.read( buf, len, buf.length - len ); + } else { + n = this.raf.read( buf, len, buf.length - len ); + } + if( n > 0 ) { + len += n; + } else { + break; + } + } + } + if( len > 0 ) { + rv = new SectorData( + sectorIdx, + physCyl, + physHead, + sectorIdx + 1, + this.sectorSizeCode, + buf, + 0, + len ); + } + } + catch( IOException ex ) { + fireShowReadError( physCyl, physHead, sectorIdx + 1, ex ); + rv = new SectorData( + sectorIdx, + physCyl, + physHead, + sectorIdx + 1, + this.sectorSizeCode, + null, + 0, + 0 ); + rv.setError( true ); + } + } + if( rv != null ) { + rv.setDisk( this ); + rv.setFilePos( filePos ); + rv.setFilePortionLen( sectorSize ); + } + } + return rv; + } } diff --git a/src/jkcemu/disk/SectorData.java b/src/jkcemu/disk/SectorData.java index 18954eb..2e9de30 100644 --- a/src/jkcemu/disk/SectorData.java +++ b/src/jkcemu/disk/SectorData.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -65,10 +65,11 @@ public int read() }; - private int dataPos; + private int dataOffs; private int dataLen; private byte[] dataBuf; private boolean shared; + private boolean bogusID; private boolean err; private boolean deleted; private long filePos; @@ -83,24 +84,23 @@ public SectorData( int head, int sectorNum, int sizeCode, - boolean err, - boolean deleted, byte[] dataBuf, int dataOffs, int dataLen ) { super( cyl, head, sectorNum, sizeCode ); this.idxOnCyl = idxOnCyl; - this.err = err; - this.deleted = deleted; this.dataBuf = dataBuf; - this.dataPos = dataOffs; + this.dataOffs = dataOffs; this.dataLen = dataLen; this.shared = true; + this.bogusID = false; + this.err = false; + this.deleted = false; this.filePos = -1; this.filePortionLen = 0; this.disk = null; - if( (getSizeCode() < 0) && (this.dataLen > 0) ) { + if( (sizeCode < 0) && (this.dataLen > 0) ) { setSizeCode( getSizeCode( this.dataLen ) ); } } @@ -135,18 +135,13 @@ public boolean equalsData( byte[] dataBuf, int pos, int dataLen ) } - public int getDataLength() - { - return this.dataLen; - } - - public synchronized int getDataByte( int idx ) { int rv = -1; if( (idx >= 0) && (idx < this.dataLen) ) { rv = 0; if( this.dataBuf != null ) { + idx += this.dataOffs; if( idx < this.dataBuf.length ) { rv = (int) this.dataBuf[ idx ] & 0xFF; } @@ -156,6 +151,12 @@ public synchronized int getDataByte( int idx ) } + public int getDataLength() + { + return this.dataLen; + } + + public AbstractFloppyDisk getDisk() { return this.disk; @@ -202,6 +203,12 @@ public static int getSizeCode( int sectorSize ) } + public boolean hasBogusID() + { + return this.bogusID; + } + + public boolean isDeleted() { return this.deleted; @@ -219,7 +226,7 @@ public synchronized int read( byte[] dstBuf, int dstPos, int dstLen ) int rv = 0; if( dstBuf != null ) { if( this.dataBuf != null ) { - int srcIdx = this.dataPos; + int srcIdx = this.dataOffs; int srcLen = this.dataLen; while( (srcIdx < this.dataBuf.length) && (srcLen > 0) && (dstPos < dstBuf.length) && (dstLen > 0) ) @@ -244,19 +251,25 @@ public synchronized Reader reader() { return new Reader( this.dataBuf, - this.dataPos, + this.dataOffs, this.dataLen, this.deleted ); } + public void setBogusID( boolean state ) + { + this.bogusID = state; + } + + public synchronized void setData( boolean deleted, byte[] dataBuf, int dataLen ) { - this.deleted = deleted; - this.dataPos = 0; + this.deleted = deleted; + this.dataOffs = 0; if( dataBuf != null ) { int newLen = Math.min( dataBuf.length, dataLen ); int oldLen = 0; @@ -292,9 +305,9 @@ public synchronized void setData( } - public void setDeleted( boolean deleted ) + public void setDeleted( boolean state ) { - this.deleted = deleted; + this.deleted = state; } @@ -322,18 +335,24 @@ public void setFilePos( long filePos ) } + public void setSectorID( int cyl, int head, int sectorNum ) + { + super.setSectorID( cyl, head, sectorNum ); + } + + public int writeTo( OutputStream out, int maxDataLen ) throws IOException { int n = 0; if( this.dataBuf != null ) { - n = Math.min( this.dataLen, this.dataBuf.length - this.dataPos ); + n = Math.min( this.dataLen, this.dataBuf.length - this.dataOffs ); if( n > 0 ) { if( (maxDataLen >= 0) && (maxDataLen < n) ) { n = maxDataLen; } - out.write( this.dataBuf, this.dataPos, n ); + out.write( this.dataBuf, this.dataOffs, n ); } } while( ((maxDataLen < 0) || (n < maxDataLen)) && (n < this.dataLen) ) { @@ -350,12 +369,12 @@ public int writeTo( { int n = 0; if( this.dataBuf != null ) { - n = Math.min( this.dataLen, this.dataBuf.length - this.dataPos ); + n = Math.min( this.dataLen, this.dataBuf.length - this.dataOffs ); if( n > 0 ) { if( (maxDataLen >= 0) && (maxDataLen < n) ) { n = maxDataLen; } - raf.write( this.dataBuf, this.dataPos, n ); + raf.write( this.dataBuf, this.dataOffs, n ); } } while( ((maxDataLen < 0) || (n < maxDataLen)) && (n < this.dataLen) ) { diff --git a/src/jkcemu/disk/SectorID.java b/src/jkcemu/disk/SectorID.java index c70c678..2d84213 100644 --- a/src/jkcemu/disk/SectorID.java +++ b/src/jkcemu/disk/SectorID.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -56,20 +56,29 @@ public int getHead() return this.head; } + public int getSectorNum() { return this.sectorNum; } + public int getSizeCode() { return this.sizeCode; } + protected void setSectorID( int cyl, int head, int sectorNum ) + { + this.cyl = cyl; + this.head = head; + this.sectorNum = sectorNum; + } + + protected void setSizeCode( int sizeCode ) { this.sizeCode = sizeCode; } } - diff --git a/src/jkcemu/disk/TeleDisk.java b/src/jkcemu/disk/TeleDisk.java index 5f9d6fe..c729449 100644 --- a/src/jkcemu/disk/TeleDisk.java +++ b/src/jkcemu/disk/TeleDisk.java @@ -1,12 +1,13 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * * Emulation einer Diskette, deren Daten in einer TeleDisk-Datei vorliegen * * Der hier implementierte Algorithmus basiert auf den Informationen von: - * http://www.fpns.net/willy/wteledsk.htm + * http://www.willsworks.net/wteledsk.htm + * http://www.classiccmp.org/dunfield/img54306/td0notes.txt */ package jkcemu.disk; @@ -17,10 +18,15 @@ import java.util.*; import java.util.zip.GZIPInputStream; import jkcemu.base.EmuUtil; +import jkcemu.etc.CRC16; +import jkcemu.text.CharConverter; public class TeleDisk extends AbstractFloppyDisk { + private static final int CRC_POLYNOM = 0xA097; + private static final int CRC_INIT = 0; + private String fileName; private String remark; private java.util.Date diskDate; @@ -28,6 +34,270 @@ public class TeleDisk extends AbstractFloppyDisk private Map> side1; + public static void export( + AbstractFloppyDisk disk, + File file, + String remark ) throws IOException + { + // Kommentar aufbereiten + byte[] remarkBytes = null; + if( remark == null ) { + remark = disk.getRemark(); + } + if( remark != null ) { + int len = remark.length(); + if( len > 0 ) { + ByteArrayOutputStream buf + = new ByteArrayOutputStream( remark.length() + 8 ); + Calendar calendar = new GregorianCalendar(); + java.util.Date diskDate = disk.getDiskDate(); + if( diskDate != null ) { + calendar.clear(); + calendar.setTime( diskDate ); + } + buf.write( calendar.get( Calendar.YEAR ) - 1900 ); + buf.write( calendar.get( Calendar.MONTH ) ); + buf.write( calendar.get( Calendar.DAY_OF_MONTH ) ); + buf.write( calendar.get( Calendar.HOUR_OF_DAY ) ); + buf.write( calendar.get( Calendar.MINUTE ) ); + buf.write( calendar.get( Calendar.SECOND ) ); + CharConverter cc = new CharConverter( CharConverter.Encoding.CP850 ); + for( int i = 0; i < len; i++ ) { + char ch = remark.charAt( i ); + if( (ch == '\u0000') || (ch == '\u001A') ) { + break; + } + ch = (char) cc.toCharsetByte( ch ); + if( (ch > '\u0000') && (buf.size() < 0x7E) ) { + buf.write( ch ); + } + } + if( buf.size() > 6 ) { + buf.write( 0 ); + remarkBytes = buf.toByteArray(); + } + } + } + + // Datei oeffnen + OutputStream out = null; + try { + int nCyls = disk.getCylinders(); + int nSides = disk.getSides(); + if( (nCyls < 1) || (nSides < 1) ) { + throw new IOException( "Kein Inhalt vorhanden" ); + } + + // Datei oeffnen + out = EmuUtil.createOptionalGZipOutputStream( file ); + CRC16 crc = new CRC16( CRC_POLYNOM, CRC_INIT ); + + // Kennung + writeByte( out, 'T', crc ); + writeByte( out, 'D', crc ); + writeByte( out, 0, crc ); + + /* + * Pruefsequenz: + * Alle Dateien eines Multi-File-Archivs muessen + * hier den gleichen Wert haben. + * Zwischen den Archiven sollte sich dieser Wert moeglichst + * unterscheiden. + * Deshalb wird hier eine Zufallszahl geschrieben. + */ + writeByte( + out, + (new Random( System.currentTimeMillis() )).nextInt( 0x100 ), + crc ); + + // TeleDisk-Format + writeByte( out, 0x15, crc ); + + // Datenrate + int dataRate = 0; // 250 kbps + if( disk.getDiskSize() >= (1024 * 1024) ) { + dataRate = 2; // 500 kbps + } + writeByte( out, dataRate, crc ); + + /* + * Laufwerkstyp: + * Es kann auf Basis der Parameter nur ein wahrscheinlicher + * Laufwerkstyp ermittelt werden. + */ + int driveType = 1; // 5.25 Zoll, 360 KByte + if( disk.getDiskSize() >= (360 * 1024) ) { + driveType = 2; // 5.25 Zoll, 48 tpi + } + if( (disk.getCylinders() >= 50) + && (disk.getSectorSize() == 512) ) + { + int n = disk.getSectorsPerCylinder(); + if( (n >= 8) && (n <= 10) ) { + driveType = 3; // 3.5 Zoll DD + } else if( n >= 17 ) { + driveType = 4; // 3.5 Zoll HD + } + } + writeByte( out, driveType, crc ); + + // Stepping und Remark Flag + writeByte( + out, + remarkBytes != null ? 0x80 : 0, // Flag + Einzelschritt + crc ); + + // DOS Allocation Flag + writeByte( out, 0, crc ); + + // Anzahl Seiten + writeByte( out, disk.getSides(), crc ); + + // CRC des Dateikopfs + long v = crc.getValue(); + out.write( (int) v ); + out.write( (int) (v >> 8) ); + + // Kommentar + if( remarkBytes != null ) { + int len = remarkBytes.length - 6; // Laenge ohne Zeit + crc.reset(); + crc.update( len ); + crc.update( len >> 8 ); + crc.update( remarkBytes, 0, remarkBytes.length ); + int crcValue = (int) crc.getValue(); + out.write( crcValue ); + out.write( crcValue >> 8 ); + out.write( len ); + out.write( len >> 8 ); + out.write( remarkBytes ); + } + + // Spuren + int lastCyl = -1; + int lastHead = -1; + int lastTrackCRC = -1; + for( int physCyl = 0; physCyl < nCyls; physCyl++ ) { + lastCyl = physCyl; + for( int physHead = 0; physHead < nSides; physHead++ ) { + lastHead = physHead; + + // Sektoren der Spur + int n = disk.getSectorsOfCylinder( physCyl, physHead ); + if( n > 0 ) { + java.util.List sectors = new ArrayList<>( n ); + for( int i = 0; i < n; i++ ) { + SectorData sector = disk.getSectorByIndex( + physCyl, + physHead, + i ); + if( sector != null ) { + sectors.add( sector ); + } + } + n = sectors.size(); + if( n > 0 ) { + crc.reset(); + writeByte( out, n, crc ); + writeByte( out, physCyl, crc ); + writeByte( out, physHead, crc ); + lastTrackCRC = (int) crc.getValue(); + out.write( lastTrackCRC ); + for( SectorData sector : sectors ) { + int dataLen = sector.getDataLength(); + + // Sektor-ID + crc.reset(); + writeByte( out, sector.getCylinder(), crc ); + writeByte( out, sector.getHead(), crc ); + writeByte( out, sector.getSectorNum(), crc ); + writeByte( out, sector.getSizeCode(), crc ); + + // Control-Byte + int secCtrl = 0; + if( sector.checkError() ) { + secCtrl |= 0x02; + } + if( sector.isDeleted() ) { + secCtrl |= 0x04; + } + if( dataLen <= 0 ) { + secCtrl |= 0x20; + } + + // Pruefsumme des Sektorkopfs + writeByte( out, secCtrl, crc ); + + // Sektordaten aufbereiten + if( dataLen > 0 ) { + crc.reset(); + boolean allEquals = true; + int firstByte = sector.getDataByte( 0 ); + crc.update( firstByte ); + for( int i = 1; i < dataLen; i++ ) { + int b = sector.getDataByte( i ); + crc.update( b ); + if( b != firstByte ) { + allEquals = false; + } + } + out.write( (int) crc.getValue() ); + if( allEquals ) { + ByteArrayOutputStream buf + = new ByteArrayOutputStream( 128 ); + int nRemain = dataLen; + while( nRemain > 5 ) { + int nSeg = nRemain / 2; + if( nSeg > 255 ) { + nSeg = 255; + } + buf.write( 1 ); // RLE + buf.write( nSeg ); + buf.write( firstByte ); + buf.write( firstByte ); + nRemain -= (nSeg * 2); + } + buf.write( 0 ); // COPY + buf.write( nRemain ); + while( nRemain > 0 ) { + buf.write( firstByte ); + --nRemain; + } + int size = buf.size() + 1; + out.write( size ); + out.write( size >> 8 ); + out.write( 2 ); // Kodierung: segmentiert + buf.writeTo( out ); + } else { + int len = dataLen + 1; // + 1 Byte Kodierung + out.write( len ); + out.write( len >> 8 ); + out.write( 0 ); // Kodierung: unkomprimiert + sector.writeTo( out, dataLen ); + } + } else { + out.write( (int) crc.getValue() ); + } + } + } + } + } + } + + // Dateiende + out.write( 0xFF ); + out.write( lastCyl ); + out.write( lastHead ); + out.write( lastTrackCRC ); + out.close(); + out = null; + } + finally { + EmuUtil.doClose( out ); + } + } + + public static boolean isTeleDiskFileHeader( byte[] header ) { boolean rv = false; @@ -46,7 +316,10 @@ public static boolean isTeleDiskFileHeader( byte[] header ) } - public static TeleDisk readFile( Frame owner, File file ) throws IOException + public static TeleDisk readFile( + Frame owner, + File file, + boolean enableAutoRepair ) throws IOException { TeleDisk rv = null; InputStream in = null; @@ -56,6 +329,7 @@ public static TeleDisk readFile( Frame owner, File file ) throws IOException if( EmuUtil.isGZipFile( file ) ) { in = new GZIPInputStream( in ); } + boolean autoRepaired = false; // Kopfblock lesen int head0 = in.read(); @@ -68,59 +342,71 @@ public static TeleDisk readFile( Frame owner, File file ) throws IOException if( (head0 != 'T') || (head1 != 'D') || (head2 != 0) ) { throw new IOException( "Datei ist keine TeleDisk-Datei." ); } - in.read(); - int fmtVersion = readByte( in ); - if( fmtVersion != 0x15 ) { // Version + readMandatoryByte( in ); // Check Sequence + int fmtVersion = readMandatoryByte( in ); // Version + if( fmtVersion != 0x15 ) { throwUnsupportedTeleDiskFmt( String.format( "Formatversion %02X nicht unterst\u00FCtzt", fmtVersion ) ); } - in.read(); - in.read(); - boolean hasRemark = ((readByte( in ) & 0x80) != 0); - in.read(); - int sides = in.read(); + readMandatoryByte( in ); // Data Rate + readMandatoryByte( in ); // Drive Type + // Stepping und Remark Flag + boolean hasRemark = ((readMandatoryByte( in ) & 0x80) != 0); + readMandatoryByte( in ); // DOS Allocation Flag + int sides = readMandatoryByte( in ); // Anzahl Seiten if( sides != 1 ) { sides = 2; } - in.read(); // CRC - in.read(); // CRC + readMandatoryWord( in ); // CRC Kopfbereich - // Kommantarblock lesen + // Kommantarblock mit Zeitstempel lesen String remark = null; String dateText = null; java.util.Date diskDate = null; if( hasRemark ) { - in.read(); // CRC - in.read(); // CRC - int len = readWord( in ); - int year = readByte( in ) + 1900; - int month = readByte( in ); - int day = readByte( in ); - int hour = readByte( in ); - int minute = readByte( in ); - int second = readByte( in ); - Calendar calendar = Calendar.getInstance(); - if( calendar != null ) { - calendar.clear(); - calendar.set( year, month, day, hour, minute, second ); - diskDate = calendar.getTime(); + int crcValue = readMandatoryWord( in ); + int len = readMandatoryWord( in ); + int year = readMandatoryByte( in ) + 1900; + int month = readMandatoryByte( in ); + int day = readMandatoryByte( in ); + int hour = readMandatoryByte( in ); + int minute = readMandatoryByte( in ); + int second = readMandatoryByte( in ); + if( (month < 12) && (day >= 1) && (day <= 31) + && (hour < 24) && (minute < 60) && (second < 60) ) + { + diskDate = (new GregorianCalendar( + year, month, day, + hour, minute, second )).getTime(); } if( len < 0 ) { len = 0; } StringBuilder buf = new StringBuilder( len + 32 ); + CharConverter cc = new CharConverter( CharConverter.Encoding.CP850 ); if( len > 0 ) { + int trimLen = -1; while( len > 0 ) { - int ch = readByte( in ); - if( ch == 0x00 ) { - ch = '\r'; + int ch = readMandatoryByte( in ); + if( ch == '\u0000' ) { + ch = '\n'; + if( trimLen < 0 ) { + trimLen = buf.length(); + } + } else { + trimLen = -1; + } + ch = cc.toUnicode( ch ); + if( ch > '\u0000' ) { + buf.append( (char) ch ); } - buf.append( (char) ch ); --len; } - buf.append( (char) '\n' ); + if( trimLen >= 0 ) { + buf.setLength( trimLen ); + } } remark = buf.toString().trim(); } @@ -136,11 +422,16 @@ public static TeleDisk readFile( Frame owner, File file ) throws IOException int nSec = in.read(); int track = in.read(); int head = in.read(); - in.read(); + in.read(); // CRC Kopfbereich der Spur - if( (nSec == 0xFF) || (nSec == -1) || (track == -1) || (head == -1) ) { + if( (nSec == 0xFF) + || (nSec < 0) + || (track < 0) + || (head < 0) ) + { break; } + if( nSec > 0 ) { head &= 0x01; if( track >= cyls ) { @@ -151,14 +442,17 @@ public static TeleDisk readFile( Frame owner, File file ) throws IOException } // Sektoren lesen + boolean abnormalFmt = false; + java.util.List bogusHeaderSectors = null; for( int i = 0; i < nSec; i++ ) { - int secTrack = readByte( in ); - int secHead = readByte( in ); - int secNum = readByte( in ); - int secSizeCode = readByte( in ); - int secCtrl = readByte( in ); - in.read(); // CRC - + int secTrack = readMandatoryByte( in ); + int secHead = readMandatoryByte( in ); + int secNum = readMandatoryByte( in ); + int secSizeCode = readMandatoryByte( in ); + int secCtrl = readMandatoryByte( in ); + int secCrcValue = readMandatoryByte( in ); + + // Datenpuffer anlegen byte[] secBuf = null; if( (secSizeCode >= 0) && (secSizeCode <= 6) ) { int secSize = 128; @@ -170,7 +464,8 @@ public static TeleDisk readFile( Frame owner, File file ) throws IOException if( secSize > diskSectorSize ) { diskSectorSize = secSize; } - } else { + } + if( secBuf == null ) { throwUnsupportedTeleDiskFmt( String.format( "Code=%02X f\u00FCr Sektorgr\u00F6\u00DFe" @@ -178,33 +473,57 @@ public static TeleDisk readFile( Frame owner, File file ) throws IOException secSizeCode ) ); } - boolean crcError = ((secCtrl & 0x02) != 0); - boolean deleted = ((secCtrl & 0x04) != 0); + /* + * Bits in secCtrl: + * 0x01: Sektor mehrfach auf der Spur enthalten + * 0x02: Sektor mit CRC-Fehler gelesen + * 0x04: Sektor hat Deleted Data Address Mark + * 0x10: Datenbereich wurde uebersprungen + * (keine Daten enthalten) + * 0x20: Sektor hat ID-Feld, aber keine Daten + * 0x40: Sektor hat Daten, aber keinen Kopf + * (Kopfdaten generiert) + */ + boolean secCrcError = ((secCtrl & 0x02) != 0); + boolean secDeleted = ((secCtrl & 0x04) != 0); + boolean bogusHeader = ((secCtrl & 0x40) != 0); + if( !bogusHeader + && ((secTrack != track) || (secHead != head)) ) + { + abnormalFmt = true; + } - if( ((secCtrl & 0x30) == 0) && (secBuf != null) ) { - int len = readWord( in ); + if( (secCtrl & 0x30) == 0 ) { + int len = readMandatoryWord( in ); if( len > 0 ) { - int secEncoding = readByte( in ); + int secEncoding = readMandatoryByte( in ); --len; int pos = 0; switch( secEncoding ) { case 0: - while( (len > 0) && (pos < secBuf.length) ) { - secBuf[ pos++ ] = (byte) readByte( in ); + while( len > 0 ) { + int b = readMandatoryByte( in ); + if( pos < secBuf.length ) { + secBuf[ pos++ ] = (byte) b; + } --len; } break; case 1: if( len >= 4 ) { - int n = readWord( in ); - int b0 = readByte( in ); - int b1 = readByte( in ); + int n = readMandatoryWord( in ); + int b0 = readMandatoryByte( in ); + int b1 = readMandatoryByte( in ); len -= 4; - while( (n > 0) && (pos < secBuf.length) ) { - secBuf[ pos++ ] = (byte) b0; - secBuf[ pos++ ] = (byte) b1; + while( n > 0 ) { + if( pos < secBuf.length ) { + secBuf[ pos++ ] = (byte) b0; + } + if( pos < secBuf.length ) { + secBuf[ pos++ ] = (byte) b1; + } --n; } } @@ -212,15 +531,16 @@ public static TeleDisk readFile( Frame owner, File file ) throws IOException case 2: while( len >= 2 ) { - int t = readByte( in ); - int n = readByte( in ); + int t = readMandatoryByte( in ); + int n = readMandatoryByte( in ); len -= 2; switch( t ) { case 0: - while( (len > 0) && (n > 0) - && (pos < secBuf.length) ) - { - secBuf[ pos++ ] = (byte) readByte( in ); + while( (len > 0) && (n > 0) ) { + int b = readMandatoryByte( in ); + if( pos < secBuf.length ) { + secBuf[ pos++ ] = (byte) b; + } --n; --len; } @@ -231,12 +551,16 @@ public static TeleDisk readFile( Frame owner, File file ) throws IOException case 1: if( len >= 2 ) { - int b0 = readByte( in ); - int b1 = readByte( in ); + int b0 = readMandatoryByte( in ); + int b1 = readMandatoryByte( in ); len -= 2; - while( (n > 0) && (pos < secBuf.length) ) { - secBuf[ pos++ ] = (byte) b0; - secBuf[ pos++ ] = (byte) b1; + while( n > 0 ) { + if( pos < secBuf.length ) { + secBuf[ pos++ ] = (byte) b0; + } + if( pos < secBuf.length ) { + secBuf[ pos++ ] = (byte) b1; + } --n; } } @@ -250,6 +574,10 @@ public static TeleDisk readFile( Frame owner, File file ) throws IOException t ) ); } } + while( len > 0 ) { + readMandatoryByte( in ); + --len; + } break; default: @@ -263,15 +591,16 @@ public static TeleDisk readFile( Frame owner, File file ) throws IOException } } } + Map> map = null; if( head == 0 ) { if( side0 == null ) { - side0 = new HashMap>(); + side0 = new HashMap<>(); } map = side0; } else if( head == 1 ) { if( side1 == null ) { - side1 = new HashMap>(); + side1 = new HashMap<>(); } map = side1; } @@ -279,85 +608,172 @@ public static TeleDisk readFile( Frame owner, File file ) throws IOException Integer keyObj = new Integer( track ); java.util.List sectors = map.get( keyObj ); if( sectors == null ) { - sectors = new ArrayList( nSec > 0 ? nSec : 1 ); + sectors = new ArrayList<>( nSec > 0 ? nSec : 1 ); map.put( keyObj, sectors ); } // doppelte Sektoren herausfiltern boolean found = false; int secLen = (secBuf != null ? secBuf.length : 0); - for( SectorData sector : sectors ) { - if( sector.equalsSectorID( + if( enableAutoRepair ) { + for( SectorData sector : sectors ) { + if( sector.equalsSectorID( secTrack, secHead, secNum, secSizeCode ) ) - { - if( sector.equalsData( secBuf, 0, secLen ) ) { - /* - * Daten sind gleich -> sofern moeglich, - * den Sektor als fehlerfrei und nicht geloescht behalten - */ - if( !crcError ) { - sector.setError( false ); - } - if( !deleted ) { - sector.setDeleted( false ); - } + { found = true; - break; - } - // Daten sind unterschiedlich - if( sector.checkError() ) { - /* - * den fehlerhaften durch den fehlerfreien Sektor - * ersetzen - */ - if( !crcError ) { - sector.setData( deleted, secBuf, secLen ); + if( sector.equalsData( secBuf, 0, secLen ) ) { + /* + * Daten sind gleich: + * sofern mofern moeglich, den Sektor als fehlerfrei + * und nicht geloescht behalten + */ + if( !secCrcError ) { + sector.setError( false ); + } + if( !secDeleted ) { + sector.setDeleted( false ); + } + } else { + /* + * Daten sind unterschiedlich + * sofern moeglich den fehlerhaften durch den + * fehlerfreien Sektor ersetzen + */ + if( sector.checkError() && !secCrcError ) { + if( secDeleted && !sector.isDeleted() ) { + secDeleted = false; + autoRepaired = true; + } + sector.setData( secDeleted, secBuf, secLen ); + sector.setError( false ); + } } - found = true; - break; - } - /* - * den geloeschten durch den nicht geloeschten Sektor - * ersetzen sofern er fehlerfrei ist - */ - if( sector.isDeleted() && !deleted && !crcError ) { - sector.setData( deleted, secBuf, secLen ); - found = true; - break; } } } if( !found ) { - sectors.add( - new SectorData( - sectors.size(), - secTrack, - secHead, - secNum, - secSizeCode, - crcError, - deleted, - secBuf, - 0, - secLen ) ); + SectorData sector = new SectorData( + sectors.size(), + secTrack, + secHead, + secNum, + secSizeCode, + secBuf, + 0, + secLen ); + sector.setBogusID( bogusHeader ); + sector.setError( secCrcError ); + sector.setDeleted( secDeleted ); + sectors.add( sector ); + if( bogusHeader && !abnormalFmt ) { + if( bogusHeaderSectors == null ) { + bogusHeaderSectors = new ArrayList<>(); + } + bogusHeaderSectors.add( sector ); + } + } + } + } + + /* + * ggf. automatische Reparatur von Sektoren, + * deren Kopf nicht gelesen werden konnte + * + * Das ist jedoch nicht moeglich, + * wenn mehr als ein Sektor pro Spur betroffen sind. + */ + if( enableAutoRepair + && (bogusHeaderSectors != null) + && !abnormalFmt ) + { + if( bogusHeaderSectors.size() == 1 ) { + SectorData bogusHeaderSector = bogusHeaderSectors.get( 0 ); + + // Sektoren der Spur holen + Map> sideData = null; + java.util.List trackSectors = null; + if( head == 0 ) { + sideData = side0; + } else if( head == 1 ) { + sideData = side1; + } + if( sideData != null ) { + trackSectors = sideData.get( track ); + } + if( trackSectors != null ) { + if( (trackSectors.size() == nSec) + && trackSectors.contains( bogusHeaderSector ) ) + { + // Sektornummern ermitteln + SortedSet secNums = new TreeSet<>(); + for( SectorData sector : trackSectors ) { + if( sector != bogusHeaderSector ) { + secNums.add( sector.getSectorNum() ); + } + } + if( !secNums.isEmpty() && (secNums.size() + 1) == nSec ) { + int secNumRange = secNums.last() - secNums.first() + 1; + if( secNumRange == (nSec - 1) ) { + // Sektornummer fehlt am Anfang oder Ende + switch( secNums.first().intValue() ) { + case 1: + // Sektornummer fehlt am Ende + bogusHeaderSector.setSectorID( + track, + head, + secNums.last() + 1 ); + autoRepaired = true; + break; + case 2: + // Sektornummer fehlt am Anfang + bogusHeaderSector.setSectorID( track, head, 1 ); + autoRepaired = true; + } + } else if( secNumRange == nSec ) { + // Sektornummer fehlt in der Mitte + int tmpSecNum = secNums.first().intValue() + 1; + int lastSecNum = secNums.last().intValue(); + while( tmpSecNum <= lastSecNum ) { + if( !secNums.contains( tmpSecNum ) ) { + bogusHeaderSector.setSectorID( + track, + head, + tmpSecNum ); + autoRepaired = true; + } + tmpSecNum++; + } + } + } + } } } } } } + + // Diskattenobjekt anlegen rv = new TeleDisk( - owner, - sides, - cyls, - sectorsPerCyl, - diskSectorSize, - file.getPath(), - remark, - diskDate, - side0, - side1 ); + owner, + sides, + cyls, + sectorsPerCyl, + diskSectorSize, + file.getPath(), + remark, + diskDate, + side0, + side1 ); + + // ggf. Warnung + if( autoRepaired ) { + rv.setWarningText( "JKCEMU hat Sektoren repariert," + + " die beim Erzeugen der\n" + + "Teledisk-Datei nicht korrekt gelesen" + + " werden konnten." ); + } } finally { EmuUtil.doClose( in ); @@ -468,10 +884,20 @@ private java.util.List getSectorList( } - private static int readWord( InputStream in ) throws IOException + private static int readMandatoryByte( InputStream in ) throws IOException { - int b0 = readByte( in ); - int b1 = readByte( in ); + int b = in.read(); + if( b < 0 ) { + throwUnexpectedEOF(); + } + return b; + } + + + private static int readMandatoryWord( InputStream in ) throws IOException + { + int b0 = readMandatoryByte( in ); + int b1 = readMandatoryByte( in ); return (b1 << 8) | b0; } @@ -491,4 +917,14 @@ private static void throwUnsupportedTeleDiskFmt( String msg ) + " unterst\u00FCtztes TeleDisk-Format:\n" + msg ); } + + + private static void writeByte( + OutputStream out, + int b, + CRC16 crc ) throws IOException + { + crc.update( b ); + out.write( b ); + } } diff --git a/src/jkcemu/emusys/A5105.java b/src/jkcemu/emusys/A5105.java index ac43975..edc6e3a 100644 --- a/src/jkcemu/emusys/A5105.java +++ b/src/jkcemu/emusys/A5105.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2013 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -41,7 +41,7 @@ public class A5105 extends EmuSys implements { /* * Das SCPX bringt einen Fehler, wenn die Diskette schreibgeschuetzt ist. - * Das ist somit der Fall, wenn die in JKCEMU integrierte + * Das ist somit der Fall, wenn die im JKCEMU integrierte * SCPX-Systemdiskette eingelegt wird. * Um diese Unschoenheit zu umgehen, sollte die SCPX-Systemdiskette * exportiert und die so entstandene Abbilddatei ohne Schreibschutz @@ -52,26 +52,29 @@ public class A5105 extends EmuSys implements private static FloppyDiskInfo rbasicPrgDisk = new FloppyDiskInfo( "/disks/a5105/a5105rbasicprg.dump.gz", - "BIC A5105 RBASIC Programmdiskette" ); + "BIC A5105 RBASIC Programmdiskette", + 2, 2048, true ); private static FloppyDiskInfo rbasicSysDisk = new FloppyDiskInfo( "/disks/a5105/a5105rbasicsys.dump.gz", - "BIC A5105 RBASIC Systemdiskette" ); + "BIC A5105 RBASIC Systemdiskette", + 2, 2048, true ); private static final FloppyDiskInfo[] availableFloppyDisks = { rbasicPrgDisk, rbasicSysDisk, new FloppyDiskInfo( "/disks/a5105/a5105scpxsys.dump.gz", - "BIC A5105 SCPX Systemdiskette" ) }; + "BIC A5105 SCPX Systemdiskette", + 2, 2048, true ) }; private static final FloppyDiskInfo[] suitableFloppyDisks = { rbasicPrgDisk, rbasicSysDisk }; private static final int BIOS_ADDR_CONIN = 0xFD09; - private static final int V24_TSTATES_PER_BIT = 347; + private static final int V24_TSTATES_PER_BIT = 430; private static CharConverter cp437 = null; private static byte[] romK1505 = null; @@ -113,6 +116,7 @@ public class A5105 extends EmuSys implements { 11, 12, 13, 14, 15, 16, 17, 18 }, { 19, 20, 21, 22, 23, 24, 25, 26 } }; + private Z80CTC ctc50; private Z80CTC ctc80; private Z80PIO pio90; private GDC82720 gdc; @@ -130,6 +134,7 @@ public class A5105 extends EmuSys implements private boolean joy1Selected; private int joy0ActionMask; private int joy1ActionMask; + private boolean audioInPhase; private boolean capsLockLED; private boolean tapeLED; private boolean v24BitOut; @@ -148,7 +153,7 @@ public class A5105 extends EmuSys implements public A5105( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.a5105." ); if( romK1505 == null ) { romK1505 = readResource( "/rom/a5105/k1505_0000.bin" ); } @@ -182,42 +187,49 @@ public A5105( EmuThread emuThread, Properties props ) this.emuThread.getRAMFloppy1(), "A5105", RAMFloppy.RFType.ADW, - "RAM-Floppy an IO-Adressen 20h/21h", + "RAM-Floppy an E/A-Adressen 20h/21h", props, - "jkcemu.a5105.ramfloppy.1." ); + this.propPrefix + "ramfloppy.1." ); this.ramFloppy2 = RAMFloppy.prepare( this.emuThread.getRAMFloppy2(), "A5105", RAMFloppy.RFType.ADW, - "RAM-Floppy an IO-Adressen 24h/25h", + "RAM-Floppy an E/A-Adressen 24h/25h", props, - "jkcemu.a5105.ramfloppy.2." ); + this.propPrefix + "ramfloppy.2." ); this.psgAudioOut = null; this.psg = new PSG8910( getDefaultSpeedKHz() * 1000 / 2, - AudioOut.MAX_VALUE, + AudioOut.MAX_USED_VALUE, this ); this.psg.start(); - this.ctc80 = new Z80CTC( "CTC (IO-Adressen 80h-83h)" ); - this.pio90 = new Z80PIO( "PIO (IO-Adressen 90h-93h)" ); + if( this.fdc != null ) { + this.ctc50 = new Z80CTC( "CTC (E/A-Adressen 50h-53h)" ); + } + this.ctc80 = new Z80CTC( "CTC (E/A-Adressen 80h-83h)" ); + this.pio90 = new Z80PIO( "PIO (E/A-Adressen 90h-93h)" ); this.kcNet = null; if( emulatesKCNet( props ) ) { - this.kcNet = new KCNet( "Netzwerk-PIO (IO-Adressen C0h-C3h)" ); + this.kcNet = new KCNet( "Netzwerk-PIO (E/A-Adressen C0h-C3h)" ); } this.vdip = null; if( emulatesUSB( props ) ) { - this.vdip = new VDIP( "USB-PIO (IO-Adressen FCh-FFh)" ); + this.vdip = new VDIP( + this.emuThread.getFileTimesViewFactory(), + "USB-PIO (E/A-Adressen FCh-FFh)" ); } - java.util.List iSources - = new ArrayList(); + java.util.List iSources = new ArrayList<>(); iSources.add( this.ctc80 ); iSources.add( this.pio90 ); + if( this.ctc50 != null ) { + iSources.add( this.ctc50 ); + } if( this.kcNet != null ) { iSources.add( this.kcNet ); } @@ -265,6 +277,12 @@ public static int getDefaultSpeedKHz() } + public static boolean getDefaultSwapKeyCharCase() + { + return false; + } + + public boolean getTapeLED() { return this.tapeLED; @@ -325,7 +343,8 @@ public void psgWriteSample( PSG8910 psg, int a, int b, int c ) { AudioOut audioOut = this.psgAudioOut; if( audioOut != null ) { - audioOut.writeSamples( 1, (byte) ((a + b + c) / 6) ); + int value = (a + b + c) / 6; + audioOut.writeSamples( 1, value, value, value ); } } @@ -373,7 +392,15 @@ public synchronized void z80PCChanged( Z80CPU cpu, int pc ) @Override public void z80TStatesProcessed( Z80CPU cpu, int tStates ) { + boolean phase = this.emuThread.readAudioPhase(); + if( phase != this.audioInPhase ) { + this.audioInPhase = phase; + this.pio90.putInValuePortB( this.audioInPhase ? 0x80 : 0, 0x80 ); + } this.ctc80.z80TStatesProcessed( cpu, tStates ); + if( this.ctc50 != null ) { + this.ctc50.z80TStatesProcessed( cpu, tStates ); + } this.gdc.z80TStatesProcessed( cpu, tStates ); if( this.fdc != null ) { this.fdc.z80TStatesProcessed( cpu, tStates ); @@ -527,7 +554,7 @@ public boolean canApplySettings( Properties props ) "A5105", RAMFloppy.RFType.ADW, props, - "jkcemu.a5105.ramfloppy.1." ); + this.propPrefix + "ramfloppy.1." ); } if( rv ) { rv = RAMFloppy.complies( @@ -535,7 +562,7 @@ public boolean canApplySettings( Properties props ) "A5105", RAMFloppy.RFType.ADW, props, - "jkcemu.a5105.ramfloppy.2." ); + this.propPrefix + "ramfloppy.2." ); } if( rv && emulatesFloppyDisk( props ) != (this.fdc != null) ) { rv = false; @@ -652,7 +679,7 @@ public CharRaster getCurScreenCharRaster() @Override public FloppyDiskFormat getDefaultFloppyDiskFormat() { - return FloppyDiskFormat.FMT_780K; + return FloppyDiskFormat.FMT_780K_I2; } @@ -996,6 +1023,33 @@ public boolean keyTyped( char ch ) } + /* + * In der SCP-Betriebsart wird zyklisch der ROM eingeblendet. + * Dadurch funktioniert das Laden in den RAM nicht sicher. + * Aus diesem Grund ist die Methoden ueberschrieben, + * um ein sicheres Laden in den RAM zu gewaehrleisten. + */ + @Override + public void loadIntoMem( + int begAddr, + byte[] data, + int idx, + int len, + FileFormat fileFmt, + int fileType ) + { + if( data != null ) { + int n = len; + int dst = begAddr; + while( (idx < data.length) && (dst < 0x10000) && (n > 0) ) { + this.emuThread.setRAMByte( dst++, data[ idx++ ] ); + --n; + } + updSysCells( begAddr, len, fileFmt, fileType ); + } + } + + @Override public boolean paintScreen( Graphics g, int x, int y, int screenScale ) { @@ -1005,7 +1059,7 @@ public boolean paintScreen( Graphics g, int x, int y, int screenScale ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; switch( port & 0xFF ) { @@ -1021,6 +1075,20 @@ public int readIOByte( int port ) } break; + + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + if( this.vdip != null ) { + rv = this.vdip.read( port ); + } + break; + case 0x40: case 0x42: case 0x44: @@ -1039,15 +1107,24 @@ public int readIOByte( int port ) } break; + case 0x50: + case 0x51: + case 0x52: + case 0x53: + if( this.ctc50 != null ) { + rv = this.ctc50.read( port & 0x03, tStates ); + } + break; + case 0x80: case 0x81: case 0x82: case 0x83: - rv = this.ctc80.read( port & 0x03 ); + rv = this.ctc80.read( port & 0x03, tStates ); break; case 0x90: - rv = this.pio90.readPortA(); + rv = this.pio90.readDataA(); break; case 0x91: @@ -1055,9 +1132,8 @@ public int readIOByte( int port ) * Bit 4: V24-Status (auf 0 setzen) * Bit 7: Kassettenrecordereingang */ - this.pio90.putInValuePortB( - this.emuThread.readAudioPhase() ? 0x80 : 0, 0x90 ); - rv = this.pio90.readPortB(); + this.pio90.putInValuePortB( 0, 0x10 ); + rv = this.pio90.readDataB(); break; case 0x92: @@ -1148,20 +1224,21 @@ public int readIOByte( int port ) rv = this.kcNet.read( port ); } break; - - case 0xFC: - case 0xFD: - case 0xFE: - case 0xFF: - if( this.vdip != null ) { - rv = this.vdip.read( port ); - } - break; } return rv; } + @Override + public int readMemByte( int addr, boolean m1 ) + { + if( m1 ) { + this.emuThread.getZ80CPU().addWaitStates( 1 ); + } + return getMemByte( addr, m1 ); + } + + @Override public void reset( EmuThread.ResetLevel resetLevel, Properties props ) { @@ -1186,6 +1263,7 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) } } } + this.audioInPhase = this.emuThread.readAudioPhase(); this.fdcReset = false; this.joyEnabled = false; this.joy1Selected = false; @@ -1206,16 +1284,15 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) @Override public void saveBasicProgram() { - int endAddr = SourceUtil.getKCBasicStyleEndAddr( this.emuThread, 0x8001 ); + int endAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x8001 ); if( endAddr >= 0x8001 ) { (new SaveDlg( this.screenFrm, 0x8001, endAddr, - -1, - false, // kein KC-BASIC - true, // RBASIC - "RBASIC-Programm speichern" )).setVisible( true ); + "RBASIC-Programm speichern", + SaveDlg.BasicType.RBASIC, + EmuUtil.getBasicFileFilter() )).setVisible( true ); } else { showNoBasic(); } @@ -1289,11 +1366,11 @@ public synchronized void startPastingText( String text ) */ keyTyped( ch ); this.pasteIter = iter; - done = false; + done = true; } } else { super.startPastingText( text ); - done = false; + done = true; } } } @@ -1361,14 +1438,22 @@ public boolean supportsSaveBasic() @Override public void updSysCells( - int begAddr, - int len, - Object fileFmt, - int fileType ) + int begAddr, + int len, + FileFormat fileFmt, + int fileType ) { if( (begAddr == 0x8001) && (fileFmt != null) ) { - if( fileFmt.equals( FileInfo.RBASIC ) ) { - int endAddr = SourceUtil.getKCBasicStyleEndAddr( + if( ((fileFmt.equals( FileFormat.RBASIC_PRG ) + || fileFmt.equals( FileFormat.BASIC_PRG )) + && (begAddr == 0x8001) + && (len > 7)) + || (fileFmt.equals( FileFormat.HEADERSAVE ) + && (fileType == 'B') + && (begAddr <= 0x8001) + && ((begAddr + len) > 0x8008)) ) + { + int endAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x8001 ); if( endAddr > 0x8001 ) { @@ -1386,7 +1471,7 @@ public void updSysCells( @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { switch( port & 0xFF ) { case 0x20: @@ -1403,6 +1488,19 @@ public void writeIOByte( int port, int value ) } break; + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + if( this.vdip != null ) { + this.vdip.write( port, value ); + } + break; + case 0x40: case 0x41: case 0x42: @@ -1436,20 +1534,29 @@ public void writeIOByte( int port, int value ) } break; + case 0x50: + case 0x51: + case 0x52: + case 0x53: + if( this.ctc50 != null ) { + this.ctc50.write( port & 0x03, value, tStates ); + } + break; + case 0x80: case 0x81: case 0x82: case 0x83: - this.ctc80.write( port & 0x03, value ); + this.ctc80.write( port & 0x03, value, tStates ); break; case 0x90: - this.pio90.writePortA( value ); + this.pio90.writeDataA( value ); break; case 0x91: synchronized( this ) { - this.pio90.writePortB( value ); + this.pio90.writeDataB( value ); int v = this.pio90.fetchOutValuePortB( false ); /* * Bit 1: V24 TxD, @@ -1543,15 +1650,6 @@ public void writeIOByte( int port, int value ) this.kcNet.write( port, value ); } break; - - case 0xFC: - case 0xFD: - case 0xFE: - case 0xFF: - if( this.vdip != null ) { - this.vdip.write( port, value ); - } - break; } } @@ -1564,7 +1662,7 @@ private synchronized void checkAddPCListener( Properties props ) if( cpu != null ) { boolean pasteFast = EmuUtil.getBooleanProperty( props, - "jkcemu.a5105.paste.fast", + this.propPrefix + "paste.fast", true ); if( pasteFast != this.pasteFast ) { this.pasteFast = pasteFast; @@ -1587,11 +1685,11 @@ private void createColors( Properties props ) } - private static boolean emulatesFloppyDisk( Properties props ) + private boolean emulatesFloppyDisk( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.a5105.floppydisk.enabled", + this.propPrefix + "floppydisk.enabled", true ); } @@ -1599,26 +1697,26 @@ private static boolean emulatesFloppyDisk( Properties props ) private boolean emulatesKCNet( Properties props ) { return EmuUtil.getBooleanProperty( - props, - "jkcemu.a5105.kcnet.enabled", - false ); + props, + this.propPrefix + "kcnet.enabled", + false ); } private boolean emulatesUSB( Properties props ) { return EmuUtil.getBooleanProperty( - props, - "jkcemu.a5105.vdip.enabled", - false ); + props, + this.propPrefix + "vdip.enabled", + false ); } - private static boolean isFixedScreenSize( Properties props ) + private boolean isFixedScreenSize( Properties props ) { return EmuUtil.parseBooleanProperty( props, - "jkcemu.a5105.fixed_screen_size", + this.propPrefix + "fixed_screen_size", false ); } diff --git a/src/jkcemu/emusys/AC1.java b/src/jkcemu/emusys/AC1.java index fba0f33..d32542a 100644 --- a/src/jkcemu/emusys/AC1.java +++ b/src/jkcemu/emusys/AC1.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -12,7 +12,6 @@ import java.awt.event.KeyEvent; import java.io.File; import java.lang.*; -import java.text.*; import java.util.*; import javax.swing.JOptionPane; import jkcemu.base.*; @@ -29,7 +28,6 @@ public class AC1 extends AbstractSCCHSys implements FDC8272.DriveSelector, - Z80PCListener, Z80TStatesListener { /* @@ -40,7 +38,7 @@ public class AC1 * Tatsaechlich werden aber in der Standardeinstellung pro Bit * 4 Ausgabe-Befehle auf dem Port getaetigt, die zusammen * 248 Takte benoetigen, was etwa 8065 Bit/s entspricht. - * Damit im Emulator die Ausgabe auf den emulierten Drucker ueber diese + * Damit im Emulator die Ausgabe auf dem emulierten Drucker ueber diese * serielle Schnittstelle funktioniert, * wird somit der Drucker ebenfalls mit dieser Bitrate emuliert. * Ist allerdings eine externe ROM-Datei als Monitorprogramm eingebunden, @@ -49,15 +47,14 @@ public class AC1 private static int V24_TSTATES_PER_BIT_INTERN = 248; private static int V24_TSTATES_PER_BIT_EXTERN = 208; - // Einprung zum Lesen eines Zeichens von der Tastatur - private static int ADDR_INCH = 0x1802; - private enum BasicType { AC1_MINI, AC1_8K, AC1_12K, + AC1_BASIC6, SCCH, - BACOBAS, + BACOBAS2, + BACOBAS3, ADDR_60F7 }; /* @@ -125,11 +122,11 @@ private enum BasicType { "DELETE", "SWITCH", "SCREEN" }; /* - * Diese Tabelle mappt die Tokens des BASOBAS-Interpreters + * Diese Tabelle mappt die Tokens des BACOBAS2-Interpreters * in ihre entsprechenden Texte. * Der Index fuer die Tabelle ergibt sich aus "Wert des Tokens - 0x80". */ - private static final String[] bacobasTokens = { + private static final String[] bacobas2Tokens = { "END", "FOR", "NEXT", "DATA", // 0x80 "INPUT", "DIM", "READ", "LET", "GOTO", "RUN", "IF", "RESTORE", @@ -157,6 +154,88 @@ private enum BasicType { "CONV", "TRANS", "BLIST", "BEDIT", "BSAVE", "BLOAD", "DELETE", "DIR" }; + /* + * Diese Tabelle mappt die Tokens des BACOBAS3-Interpreters + * in ihre entsprechenden Texte. + * Der Index fuer die Tabelle ergibt sich aus "Wert des Tokens - 0x80". + */ + private static final String[] bacobas3Tokens = { + "END", "FOR", "NEXT", "DATA", // 0x80 + "INPUT", "DIM", "READ", "LET", + "GOTO", "RUN", "IF", "RESTORE", + "GOSUB", "RETURN", "REM", "STOP", + "OUT", "ON", "NULL", "WAIT", // 0x90 + "DEF", "POKE", "DOKE", "AUTO", + "LINES", "CLS", "WIDTH", "BYE", + "KEY", "CALL", "PRINT", "CONT", + "LIST", "CLEAR", "LOAD", "SAVE", // 0xA0 + "NEW", "TAB(", "TO", "FN", + "SPC(", "THEN", "NOT", "STEP", + "+", "-", "*", "/", + "^", "AND", "OR", ">", // 0xB0 + "=", "<", "SGN", "INT", + "ABS", "USR", "FRE", "INP", + "POS", "SQR", "RND", "LN", + "EXP", "COS", "SIN", "TAN", // 0xC0 + "ATN", "PEEK", "DEEK", "POINT", + "LEN", "STR$", "VAL", "ASC", + "CHR$", "LEFT$", "RIGHT$", "MID$", + "SET", "RESET", "RENUMBER", "LOCATE", // 0xD0 + "SOUND", "INKEY", "MODE", "TRON", + "TROFF", "HELP", "EDIT", "BRON", + "BROFF", "LPRINT", "LLIST", "BACO", + "CONV", "TRANS", "ALIST", "AEDIT", // 0xE0 + "ASAVE", "ALOAD", "DIR", "CD", + "BEEP", "DRAW" }; + + /* + * Diese beiden Tabellen mappen die Tokens des AC1-BASIC6-Interpreters + * von Rolf Weidlich in ihre entsprechenden Texte. + * Der Index fuer die Tabelle ergibt sich aus "Wert des Tokens - 0x80". + * Beim Token FFh folgt dahinter ein weiteres Token, + * welches ueber die zweite Tabelle gemappt werden muss. + */ + private static final String[] basic6Tokens = { + "END", "FOR", "NEXT", "DATA", // 0x80 + "COLOR", "BLOAD", "INPUT", "DIM", + "READ", "LET", "GOTO", "FNEND", + "IF", "RESTORE", "GOSUB", "RETURN", + "REM", "STOP", "OUT", "ON", // 0x90 + "KEY", "WAIT", "DEF", "POKE", + "PRINT", "CLEAR", "FNRETURN", "SAVE", + "!", "ELSE", "LPRINT", "TRACE", + "BSAVE", "RANDOMIZE", "LINES", "LWIDTH", // 0xA0 + "LNULL", "WIDTH", "LVAR", "WINDOW", + "\'", "PRECISION", "CALL", "ERASE", + "SWAP", "LINE", "RUN", "LOAD", + "NEW", "AUTO", "COPY", "DIR", // 0xB0 + "MODE", "SOUND", "LIST", "LLIST", + "RENUMBER", "DELETE", "EDIT", "DEG", + "RAD", "WHILE", "WEND", "REPEAT", + "UNTIL", "ERROR", "RESUME", "PAUSE", // 0xC0 + "DOKE", "CLS", "CURSOR", "DRAW", + "CIRCLE", "SET", "RESET", "CD", + "BYE", "CONT", "USING", "PI", + "TAB(", "TO", "FN", "SPC(", // 0xD0 + "THEN", "NOT", "STEP", "+", + "-", "*", "/", "DIV", + "MOD", "^", "AND", "OR", + "XOR", ">", "=", "<" }; // 0xE0 + + private static final String[] basic6TokensFF = { + "SGN", "INT", "FIX", "ABS", // 0x80 + "USR", "FRE", "INP", "POS", + "LPOS", "GETCL", "SQR", "RND", + "LOG", "EXP", "COS", "SIN", + "TAN", "ATN", "PEEK", "FRAC", // 0x90 + "LGT", "SQU", "BIN$", "HEX$", + "LEN", "STR$", "VAL", "ASC", + "SPACE$", "CHR$", "LEFT$", "RIGHT$", + "MID$", "INKEY$", "STRING$", "ERR", // 0xA0 + "ERL", "POINT", "INSTR", "TIME$", + "JOY", "DEEK", "VARPTR" }; + + private static final int[] ccdCharToUnicode = { '\u0020', '\u2598', '\u259D', '\u2580', // 00h '\u2596', '\u258C', '\u259E', '\u259B', @@ -313,7 +392,6 @@ private enum BasicType { private static byte[] monSCCH1088 = null; private static byte[] mon2010c = null; private static byte[] minibasic = null; - private static byte[] gsbasic = null; private static byte[] pio2Rom2010 = null; private static byte[] font2010 = null; private static byte[] fontACC = null; @@ -324,22 +402,15 @@ private enum BasicType { private byte[] ramColor; private byte[] ramVideo; private byte[] ramStatic; - private byte[] ramModule3; private byte[] fontBytes; private byte[] osBytes; private byte[] pio2Rom2010Bytes; private byte[] romBank2010Bytes; - private byte[] scchBasicBytes; - private byte[] scchPrgXBytes; - private byte[] scchRomdiskBytes; private String fontFile; private String osFile; private String osVersion; private String pio2Rom2010File; private String romBank2010File; - private String scchBasicFile; - private String scchPrgXFile; - private String scchRomdiskFile; private BasicType lastBasicType; private RAMFloppy ramFloppy; private Z80CTC ctc; @@ -349,6 +420,7 @@ private enum BasicType { private FloppyDiskDrive[] fdDrives; private KCNet kcNet; private VDIP vdip; + private boolean audioInPhase; private boolean fdcWaitEnabled; private boolean tcEnabled; private boolean mode64x16; @@ -358,43 +430,34 @@ private enum BasicType { private boolean inverseBySW; private boolean inverseByKey; private boolean extFont; + private boolean lastMemReadM1; + private boolean ctcM1ToClk2; + private boolean ctcWritten; private boolean pio1B3State; private boolean keyboardUsed; private volatile boolean graphicKeyState; - private volatile boolean pasteFast; private boolean lowerDRAMEnabled; private boolean osRomEnabled; - private boolean scchRomdiskEnabled; - private boolean scchPrgXEnabled; - private boolean scchBasicEnabled; - private boolean rf32KActive; - private boolean rf32NegA15; - private boolean rfReadEnabled; - private boolean rfWriteEnabled; - private int rfAddr16to19; - private int scchRomdiskBegAddr; - private int scchRomdiskBankAddr; private int regF0; private int pio2Rom2010Offs; private int romBank2010Offs; private int romBank2010Len; private int fontOffs; - private boolean v24BitOut; - private int v24BitNum; - private int v24ShiftBuf; - private int v24TStateCounter; - private int v24TStatesPerBit; + private int m1Cnt; public AC1( EmuThread emuThread, Properties props ) { - super( emuThread, props ); - this.pasteFast = false; + super( emuThread, props, "jkcemu.ac1." ); this.fontSwitchable = false; this.mode64x16 = false; this.mode2010 = false; this.modeSCCH = false; - this.osVersion = EmuUtil.getProperty( props, "jkcemu.ac1.os.version" ); + this.pasteFast = false; + this.ctcM1ToClk2 = emulatesCTCM1ToClk2( props ); + this.osVersion = EmuUtil.getProperty( + props, + this.propPrefix + "os.version" ); if( this.osVersion.equals( "3.1_64x16" ) ) { this.mode64x16 = true; } @@ -414,14 +477,6 @@ else if( this.osVersion.equals( "2010" ) ) { this.pio2Rom2010File = null; this.romBank2010Bytes = null; this.romBank2010File = null; - this.scchBasicBytes = null; - this.scchBasicFile = null; - this.scchPrgXBytes = null; - this.scchPrgXFile = null; - this.scchRomdiskBytes = null; - this.scchRomdiskFile = null; - this.scchRomdiskBegAddr = getScchRomdiskBegAddr( props ); - this.scchRomdiskBankAddr = 0; this.pio2Rom2010Offs = -1; this.romBank2010Offs = -1; this.romBank2010Len = 0; @@ -446,7 +501,6 @@ else if( this.osVersion.equals( "2010" ) ) { this.ramVideo = new byte[ 0x0800 ]; } - this.ramModule3 = null; if( this.modeSCCH ) { // 1 MByte this.ramModule3 = this.emuThread.getExtendedRAM( 0x100000 ); @@ -456,9 +510,9 @@ else if( this.osVersion.equals( "2010" ) ) { this.emuThread.getRAMFloppy1(), "AC1", RAMFloppy.RFType.MP_3_1988, - "RAM-Floppy IO-Adressen E0h-E7h", + "RAM-Floppy E/A-Adressen E0h-E7h", props, - "jkcemu.ac1.ramfloppy." ); + this.propPrefix + "ramfloppy." ); this.fdDrives = null; this.fdc = null; @@ -470,27 +524,26 @@ else if( this.osVersion.equals( "2010" ) ) { this.kcNet = null; if( emulatesKCNet( props ) ) { - this.kcNet = new KCNet( "Netzwerk-PIO (IO-Adressen C0-C3)" ); + this.kcNet = new KCNet( "Netzwerk-PIO (E/A-Adressen C0-C3)" ); } this.vdip = null; if( emulatesUSB( props ) ) { - this.vdip = new VDIP( "USB-PIO (IO-Adressen FC-FF)" ); + this.vdip = new VDIP( + this.emuThread.getFileTimesViewFactory(), + "USB-PIO (E/A-Adressen FC-FF)" ); } - this.joystickEnabled = emulatesJoystick( props ); + this.gide = GIDE.getGIDE( this.screenFrm, props, this.propPrefix ); - this.gide = GIDE.getGIDE( this.screenFrm, props, "jkcemu.ac1." ); + java.util.List iSources = new ArrayList<>(); - java.util.List iSources - = new ArrayList(); - - this.ctc = new Z80CTC( "CTC (IO-Adressen 00-03)" ); - this.pio1 = new Z80PIO( "PIO (IO-Adressen 04-07)" ); + this.ctc = new Z80CTC( "CTC (E/A-Adressen 00-03)" ); + this.pio1 = new Z80PIO( "PIO (E/A-Adressen 04-07)" ); iSources.add( this.ctc ); iSources.add( this.pio1 ); if( this.modeSCCH || this.mode2010 ) { - this.pio2 = new Z80PIO( "V24-PIO (IO-Adressen 08-0B)" ); + this.pio2 = new Z80PIO( "V24-PIO (E/A-Adressen 08-0B)" ); iSources.add( this.pio2 ); } if( this.kcNet != null ) { @@ -507,7 +560,9 @@ else if( this.osVersion.equals( "2010" ) ) { catch( ArrayStoreException ex ) {} this.ctc.setTimerConnection( 0, 1 ); - this.ctc.setTimerConnection( 1, 2 ); + if( !this.ctcM1ToClk2 ) { + this.ctc.setTimerConnection( 1, 2 ); + } this.ctc.setTimerConnection( 2, 3 ); cpu.addTStatesListener( this ); if( this.fdc != null ) { @@ -535,46 +590,74 @@ public static String getBasicProgram( String rv = null; int addr = loadData.getBegAddr(); if( addr == 0x60F7 ) { - String ac1_8k = SourceUtil.getKCBasicStyleProgram( - loadData, - addr, - ac1_8kTokens ); - String scch = SourceUtil.getKCBasicStyleProgram( - loadData, - addr, - scchTokens ); + /* + * BASIC-Programmtext entsprechend der Tokens + * der verschiedenen Interpreter erzeugen + */ + java.util.List options = new ArrayList<>( 4 ); + java.util.List texts = new ArrayList<>( 4 ); + + String ac1_8k = SourceUtil.getBasicProgram( + loadData, + addr, + ac1_8kTokens ); + if( ac1_8k != null ) { + options.add( "AC1-8K-BASIC" ); + texts.add( ac1_8k ); + } + + String scch = SourceUtil.getBasicProgram( + loadData, + addr, + scchTokens ); + if( scch != null ) { + options.add( "SCCH-BASIC" ); + texts.add( scch ); + } - String bacobas = SourceUtil.getKCBasicStyleProgram( + String bacobas2 = SourceUtil.getBasicProgram( + loadData, + addr, + bacobas2Tokens ); + if( bacobas2 != null ) { + options.add( "BACOBAS 2" ); + texts.add( bacobas2 ); + } + + String bacobas3 = SourceUtil.getBasicProgram( loadData, addr, - bacobasTokens ); - - if( (ac1_8k != null) && (scch != null) && (bacobas != null) ) { - if( ac1_8k.equals( scch ) && scch.equals( bacobas ) ) { - rv = ac1_8k; - } else { - java.util.List options = new ArrayList( 4 ); - java.util.List texts = new ArrayList( 3 ); - if( ac1_8k != null ) { - options.add( "8K-AC1-BASIC" ); - texts.add( ac1_8k ); - } - if( scch != null ) { - options.add( "SCCH-BASIC" ); - texts.add( scch ); - } - if( bacobas != null ) { - options.add( "BACOBAS" ); - texts.add( bacobas ); - } - int n = options.size(); - if( n == 1 ) { - rv = texts.get( 0 ); - } - else if( n > 1 ) { - try { - int v = OptionDlg.showOptionDlg( + bacobas3Tokens ); + if( bacobas3 != null ) { + options.add( "BACOBAS 3" ); + texts.add( bacobas3 ); + } + + // BASIC-Programmtexte vergleichen und zusammenfassen + int n = Math.min( options.size(), texts.size() ); + int idx1 = 0; + while( idx1 < (n - 1) ) { + int idx2 = 1; + while( (idx2 < n) && (idx2 < n) ) { + if( texts.get( idx1 ).equals( texts.get( idx2 ) ) ) { + options.set( + idx1, + options.get( idx1 ) + ", " + options.get( idx2 ) ); + options.remove( idx2 ); + texts.remove( idx2 ); + continue; + } + idx2++; + } + idx1++; + } + n = Math.min( options.size(), texts.size() ); + if( n == 1 ) { + rv = texts.get( 0 ); + } else if( n > 1 ) { + try { + int v = OptionDlg.showOptionDlg( owner, "Das BASIC-Programm enth\u00E4lt Tokens," + " die von den einzelnen Interpretern\n" @@ -586,22 +669,27 @@ else if( n > 1 ) { "BASIC-Interpreter", -1, options.toArray( new String[ n ] ) ); - if( (v >= 0) && (v < texts.size()) ) { - rv = texts.get( v ); - } - if( rv == null ) { - throw new UserCancelException(); - } - } - catch( ArrayStoreException ex ) { - EmuUtil.exitSysError( owner, null, ex ); - } + if( (v >= 0) && (v < texts.size()) ) { + rv = texts.get( v ); + } + if( rv == null ) { + throw new UserCancelException(); } } + catch( ArrayStoreException ex ) { + EmuUtil.exitSysError( owner, null, ex ); + } } } + else if( addr == 0x6300 ) { + rv = SourceUtil.getBasicProgram( + loadData, + addr, + basic6Tokens, + basic6TokensFF ); + } else if( addr == 0x6FB7 ) { - rv = SourceUtil.getKCBasicStyleProgram( loadData, addr, ac1_12kTokens ); + rv = SourceUtil.getBasicProgram( loadData, addr, ac1_12kTokens ); } return rv; } @@ -631,7 +719,13 @@ public static int getDefaultSpeedKHz() } - public static String getTinyBasicProgram( Z80MemView memory ) + public static boolean getDefaultSwapKeyCharCase() + { + return true; + } + + + public static String getTinyBasicProgram( EmuMemView memory ) { return SourceUtil.getTinyBasicProgram( memory, @@ -640,6 +734,96 @@ public static String getTinyBasicProgram( Z80MemView memory ) } + protected void loadROMs( Properties props ) + { + if( this.modeSCCH ) { + super.loadROMs( props, "/rom/ac1/gsbasic.bin" ); + } else { + this.scchBasicRomFile = null; + this.scchBasicRomBytes = null; + this.scchPrgXRomFile = null; + this.scchPrgXRomBytes = null; + this.scchRomdiskFile = null; + this.scchRomdiskBytes = null; + } + + // OS-ROM + this.osFile = EmuUtil.getProperty( props, this.propPrefix + "os.file" ); + this.osBytes = readROMFile( this.osFile, 0x1000, "Monitorprogramm" ); + if( this.osBytes == null ) { + if( this.modeSCCH ) { + if( this.osVersion.startsWith( "SCCH8.0" ) ) { + if( monSCCH80 == null ) { + monSCCH80 = readResource( "/rom/ac1/scchmon_80g.bin" ); + } + this.osBytes = monSCCH80; + } else { + if( monSCCH1088 == null ) { + monSCCH1088 = readResource( "/rom/ac1/scchmon_1088g.bin" ); + } + this.osBytes = monSCCH1088; + } + } else if( this.mode2010 ) { + if( mon2010c == null ) { + mon2010c = readResource( "/rom/ac1/mon2010c.bin" ); + } + this.osBytes = mon2010c; + } else { + if( this.mode64x16 ) { + if( mon31_64x16 == null ) { + mon31_64x16 = readResource( "/rom/ac1/mon_31_64x16.bin" ); + } + this.osBytes = mon31_64x16; + } else { + if( mon31_64x32 == null ) { + mon31_64x32 = readResource( "/rom/ac1/mon_31_64x32.bin" ); + } + this.osBytes = mon31_64x32; + } + } + } + + // Mini-BASIC + if( !this.modeSCCH ) { + if( minibasic == null ) { + minibasic = readResource( "/rom/ac1/minibasic.bin" ); + } + } + + // AC1-2010 ROM-Baenke + if( this.mode2010 ) { + this.pio2Rom2010File = EmuUtil.getProperty( + props, + this.propPrefix + "2010.pio2rom.file" ); + this.pio2Rom2010Bytes = readROMFile( + this.pio2Rom2010File, + 0x2000, + "AC1-2010 PIO2 ROM" ); + if( this.pio2Rom2010Bytes == null ) { + if( pio2Rom2010 == null ) { + pio2Rom2010 = readResource( "/rom/ac1/pio2rom2010.bin" ); + } + this.pio2Rom2010Bytes = pio2Rom2010; + } + this.romBank2010File = EmuUtil.getProperty( + props, + this.propPrefix + "2010.rombank.file" ); + this.romBank2010Bytes = readROMFile( + this.romBank2010File, + 0x20000, + "AC1-2010 ROM-Bank" ); + } else { + this.pio2Rom2010File = null; + this.pio2Rom2010Bytes = null; + this.romBank2010File = null; + this.romBank2010Bytes = null; + } + + // Zeichensatz + loadFont( props ); + } + + /* --- FDC8272.DriveSelector --- */ @Override @@ -655,38 +839,16 @@ public FloppyDiskDrive getFloppyDiskDrive( int driveNum ) } - /* --- Z80PCListener --- */ - - @Override - public synchronized void z80PCChanged( Z80CPU cpu, int pc ) - { - if( this.pasteFast && (pc == ADDR_INCH) ) { - CharacterIterator iter = this.pasteIter; - if( iter != null ) { - char ch = iter.next(); - while( ch != CharacterIterator.DONE ) { - if( (ch > 0) && (ch < 0x7F) ) { - cpu.setRegA( ch == '\n' ? '\r' : ch ); - cpu.setRegPC( cpu.doPop() ); - break; - } - ch = iter.next(); - } - if( ch == CharacterIterator.DONE ) { - keyReleased(); - cancelPastingText(); - } - } - } - } - - /* --- Z80TStatesListener --- */ @Override public void z80TStatesProcessed( Z80CPU cpu, int tStates ) { - this.ctc.z80TStatesProcessed( cpu, tStates ); + boolean phase = this.emuThread.readAudioPhase(); + if( phase != this.audioInPhase ) { + this.audioInPhase = phase; + this.pio1.putInValuePortB( this.audioInPhase ? 0x80 : 0, 0x80 ); + } if( this.fdc != null ) { this.fdc.z80TStatesProcessed( cpu, tStates ); } @@ -711,6 +873,39 @@ public void z80TStatesProcessed( Z80CPU cpu, int tStates ) } } } + if( this.ctcM1ToClk2 && !this.ctcWritten ) { + /* + * Die CTC muss taktzyklengenau mit dem /M1-Signal getriggert werden, + * da sie erst einen Taktzyklus nach dem programmierenden + * Ausgabebefehl gestartet wird und somit die fallende Flanke + * des ersten /M1-Signals noch nicht wirksam ist. + * Um die CTC-Emulation nicht zu stoeren, + * duerfen die Taktzyklen eines Ausgabebefehls auf die CTC + * nicht zerlegt werden. + * Deshalb wird dieser Zweig nicht bei "ctcWritten" durchlaufen. + */ + while( (tStates > 0) && (this.m1Cnt > 0) ) { + this.ctc.externalUpdate( 2, false ); // fallende Flanke + int t = Math.min( tStates, 3 ); + if( t > 0 ) { + this.ctc.z80TStatesProcessed( cpu, t ); + tStates -= t; + } + this.ctc.externalUpdate( 2, true ); // steigende Flanke + if( tStates > 0 ) { + this.ctc.z80TStatesProcessed( cpu, 1 ); + --tStates; + } + --this.m1Cnt; + } + if( tStates > 0 ) { + this.ctc.z80TStatesProcessed( cpu, tStates ); + } + this.m1Cnt = 0; + } else { + this.ctc.z80TStatesProcessed( cpu, tStates ); + this.ctcWritten = false; + } } @@ -741,10 +936,10 @@ public void appendStatusHTMLTo( StringBuilder buf, Z80CPU cpu ) } buf.append( "\n" + "Programmpaket X ROM:" ); - buf.append( this.scchPrgXEnabled ? "ein" : "aus" ); + buf.append( this.scchPrgXRomEnabled ? "ein" : "aus" ); buf.append( "\n" + "SCCH-BASIC ROM:" ); - buf.append( this.scchBasicEnabled ? "ein" : "aus" ); + buf.append( this.scchBasicRomEnabled ? "ein" : "aus" ); buf.append( "\n" ); } if( this.mode2010 ) { @@ -790,36 +985,28 @@ public void applySettings( Properties props ) @Override public boolean canApplySettings( Properties props ) { - boolean rv = EmuUtil.getProperty( props, "jkcemu.system" ).equals( "AC1" ); + boolean rv = EmuUtil.getProperty( + props, + "jkcemu.system" ).equals( "AC1" ); if( rv ) { rv = TextUtil.equals( this.osVersion, - EmuUtil.getProperty( props, "jkcemu.ac1.os.version" ) ); + EmuUtil.getProperty( + props, + this.propPrefix + "os.version" ) ); } if( rv ) { rv = TextUtil.equals( this.osFile, - EmuUtil.getProperty( props, "jkcemu.ac1.os.file" ) ); + EmuUtil.getProperty( props, this.propPrefix + "os.file" ) ); } - if( this.modeSCCH ) { - if( rv ) { - rv = TextUtil.equals( - this.scchBasicFile, - EmuUtil.getProperty( props, "jkcemu.ac1.scch.basic.file" ) ); - } - if( rv ) { - rv = TextUtil.equals( - this.scchPrgXFile, - EmuUtil.getProperty( - props, - "jkcemu.ac1.scch.program_x.file" ) ); - } - if( rv ) { - rv = TextUtil.equals( - this.scchRomdiskFile, - EmuUtil.getProperty( - props, - "jkcemu.ac1.scch.romdisk.file" ) ); + if( rv ) { + if( this.modeSCCH ) { + rv = super.canApplySettings( props ); + } else { + if( rv && (emulatesJoystick( props ) != this.joystickEnabled) ) { + rv = false; + } } } if( this.mode2010 ) { @@ -828,14 +1015,14 @@ public boolean canApplySettings( Properties props ) this.pio2Rom2010File, EmuUtil.getProperty( props, - "jkcemu.ac1.2010.pio2rom.file" ) ); + this.propPrefix + "2010.pio2rom.file" ) ); } if( rv ) { rv = TextUtil.equals( this.romBank2010File, EmuUtil.getProperty( props, - "jkcemu.ac1.2010.rombank.file" ) ); + this.propPrefix + "2010.rombank.file" ) ); } } if( rv ) { @@ -844,10 +1031,10 @@ public boolean canApplySettings( Properties props ) "AC1", RAMFloppy.RFType.MP_3_1988, props, - "jkcemu.ac1.ramfloppy." ); + this.propPrefix + "ramfloppy." ); } if( rv ) { - rv = GIDE.complies( this.gide, props, "jkcemu.ac1." ); + rv = GIDE.complies( this.gide, props, this.propPrefix ); } if( rv && (emulatesFloppyDisk( props ) != (this.fdc != null)) ) { rv = false; @@ -855,7 +1042,7 @@ public boolean canApplySettings( Properties props ) if( rv && (emulatesColors( props ) != (this.ramColor != null)) ) { rv = false; } - if( rv && (emulatesJoystick( props ) != this.joystickEnabled) ) { + if( rv && (emulatesCTCM1ToClk2( props ) != this.ctcM1ToClk2) ) { rv = false; } if( rv && (emulatesKCNet( props ) != (this.kcNet != null)) ) { @@ -995,59 +1182,14 @@ public int getMemByte( int addr, boolean m1 ) int rv = 0xFF; boolean done = false; if( this.modeSCCH ) { - if( !m1 && this.rfReadEnabled && (this.ramModule3 != null) ) { - int idx = this.rfAddr16to19 | addr; - if( (idx >= 0) && (idx < this.ramModule3.length) ) { - rv = (int) this.ramModule3[ idx ] & 0xFF; - } - done = true; - } - if( !done && this.rf32KActive && (this.ramModule3 != null) - && (addr >= 0x4000) && (addr < 0xC000) ) - { - int idx = this.rfAddr16to19 | addr; - if( this.rf32NegA15 ) { - idx ^= 0x8000; - } - if( (idx >= 0) && (idx < this.ramModule3.length) ) { - rv = (int) this.ramModule3[ idx ] & 0xFF; - } - done = true; - } - if( !done && this.scchBasicEnabled - && (addr >= 0x4000) && (addr < 0x6000) ) - { - if( this.scchBasicBytes != null ) { - int idx = addr - 0x4000; - if( idx < this.scchBasicBytes.length ) { - rv = (int) this.scchBasicBytes[ idx ] & 0xFF; - } - } - done = true; - } - if( !done && this.scchRomdiskEnabled - && (addr >= this.scchRomdiskBegAddr) ) - { - if( this.scchRomdiskBytes != null ) { - int idx = this.scchRomdiskBankAddr - | (addr - this.scchRomdiskBegAddr); - if( idx < this.scchRomdiskBytes.length ) { - rv = (int) this.scchRomdiskBytes[ idx ] & 0xFF; - } - } - done = true; - } - if( !done && this.scchPrgXEnabled && (addr >= 0xE000) ) { - if( this.scchPrgXBytes != null ) { - int idx = addr - 0xE000; - if( idx < this.scchPrgXBytes.length ) { - rv = (int) this.scchPrgXBytes[ idx ] & 0xFF; - } - } + rv = getScchMemByte( addr, m1 ); + if( rv < 0 ) { + rv = 0xFF; + } else { done = true; } } - if( this.mode2010 ) { + if( !done && this.mode2010 ) { if( (addr >= 0x2000) && (addr < 0x2800) && (this.pio2Rom2010Offs >= 0) ) { @@ -1179,7 +1321,7 @@ public int getSupportedFloppyDiskDriveCount() @Override public boolean getSwapKeyCharCase() { - return true; + return getDefaultSwapKeyCharCase(); } @@ -1219,30 +1361,6 @@ public boolean keyPressed( rv = true; break; - case KeyEvent.VK_LEFT: - ch = 8; - break; - - case KeyEvent.VK_RIGHT: - ch = 9; - break; - - case KeyEvent.VK_DOWN: - ch = 0x0A; - break; - - case KeyEvent.VK_UP: - ch = 0x0B; - break; - - case KeyEvent.VK_ENTER: - ch = 0x0D; - break; - - case KeyEvent.VK_SPACE: - ch = 0x20; - break; - case KeyEvent.VK_BACK_SPACE: ch = ((this.modeSCCH || this.mode2010) ? 0x7F : 8); break; @@ -1251,25 +1369,8 @@ public boolean keyPressed( ch = ((this.modeSCCH || this.mode2010) ? 4 : 0x7F); break; - case KeyEvent.VK_INSERT: - ch = 5; - break; - - case KeyEvent.VK_PAGE_UP: - ch = 0x11; - break; - - case KeyEvent.VK_PAGE_DOWN: - ch = 0x15; - break; - - case KeyEvent.VK_HOME: - ch = 1; - break; - - case KeyEvent.VK_END: - ch = 0x1A; - break; + default: + rv = super.keyPressed( keyCode, ctrlDown, shiftDown ); } if( ch > 0 ) { setKeyboardValue( ch | 0x80 ); @@ -1279,25 +1380,6 @@ public boolean keyPressed( } - @Override - public void keyReleased() - { - setKeyboardValue( 0 ); - } - - - @Override - public boolean keyTyped( char ch ) - { - boolean rv = false; - if( (ch > 0) && (ch < 0x7F) ) { - setKeyboardValue( ch | 0x80 ); - rv = true; - } - return rv; - } - - @Override public void openBasicProgram() { @@ -1319,17 +1401,25 @@ public void openBasicProgram() preIdx = 2; break; - case SCCH: + case AC1_BASIC6: preIdx = 3; break; - case BACOBAS: + case SCCH: preIdx = 4; break; + + case BACOBAS2: + preIdx = 5; + break; + + case BACOBAS3: + preIdx = 6; + break; } } if( (preIdx < 0) && (this.modeSCCH || this.mode2010) ) { - preIdx = 3; + preIdx = 4; } switch( OptionDlg.showOptionDlg( this.screenFrm, @@ -1341,10 +1431,12 @@ public void openBasicProgram() "BASIC-Interpreter", preIdx, "Mini-BASIC", - "8K-AC1-BASIC", - "12K-AC1-BASIC", + "AC1-8K-BASIC", + "AC1-12K-BASIC", + "AC1-BASIC6", "SCCH-BASIC", - "BACOBAS" ) ) + "BACOBAS 2", + "BACOBAS 3" ) ) { case 0: bType = BasicType.AC1_MINI; @@ -1353,7 +1445,7 @@ public void openBasicProgram() case 1: bType = BasicType.AC1_8K; - text = SourceUtil.getKCBasicStyleProgram( + text = SourceUtil.getBasicProgram( this.emuThread, 0x60F7, ac1_8kTokens ); @@ -1361,26 +1453,43 @@ public void openBasicProgram() case 2: bType = BasicType.AC1_12K; - text = SourceUtil.getKCBasicStyleProgram( + text = SourceUtil.getBasicProgram( this.emuThread, 0x6FB7, ac1_12kTokens ); break; case 3: + bType = BasicType.AC1_BASIC6; + text = SourceUtil.getBasicProgram( + this.emuThread, + 0x6300, + basic6Tokens, + basic6TokensFF ); + break; + + case 4: bType = BasicType.SCCH; - text = SourceUtil.getKCBasicStyleProgram( + text = SourceUtil.getBasicProgram( this.emuThread, 0x60F7, scchTokens ); break; - case 4: - bType = BasicType.BACOBAS; - text = SourceUtil.getKCBasicStyleProgram( + case 5: + bType = BasicType.BACOBAS2; + text = SourceUtil.getBasicProgram( + this.emuThread, + 0x60F7, + bacobas2Tokens ); + break; + + case 6: + bType = BasicType.BACOBAS3; + text = SourceUtil.getBasicProgram( this.emuThread, 0x60F7, - bacobasTokens ); + bacobas3Tokens ); break; default: @@ -1504,7 +1613,7 @@ public boolean paintScreen( @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; if( (this.gide != null) && ((port & 0xF0) == 0x80) ) { @@ -1523,7 +1632,7 @@ else if( (port & 0xF8) == 0xE0 ) { case 1: case 2: case 3: - rv = this.ctc.read( port & 0x03 ); + rv = this.ctc.read( port & 0x03, tStates ); break; case 4: @@ -1535,21 +1644,12 @@ else if( (port & 0xF8) == 0xE0 ) { this.keyboardUsed = true; } } - rv = this.pio1.readPortA(); + rv = this.pio1.readDataA(); break; case 5: - { - int v = 0x04; // PIO B2: Grafiktaste, L-aktiv - if( this.graphicKeyState ) { - v = 0; - } - if( this.emuThread.readAudioPhase() ) { - v |= 0x80; - } - this.pio1.putInValuePortB( v, 0x84 ); - rv = this.pio1.readPortB(); - } + this.pio1.putInValuePortB( this.graphicKeyState ? 0 : 0x04, 0x04 ); + rv = this.pio1.readDataB(); break; case 6: @@ -1564,13 +1664,13 @@ else if( (port & 0xF8) == 0xE0 ) { if( this.pio2 != null ) { // V24: CTS=L (empfangsbereit) this.pio2.putInValuePortA( 0, 0x04 ); - rv = this.pio2.readPortA(); + rv = this.pio2.readDataA(); } break; case 9: if( this.pio2 != null ) { - rv = this.pio2.readPortB(); + rv = this.pio2.readDataB(); } break; @@ -1615,6 +1715,10 @@ else if( (port & 0xF8) == 0xE0 ) { } break; + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: case 0xFC: case 0xFD: case 0xFE: @@ -1629,6 +1733,22 @@ else if( (port & 0xF8) == 0xE0 ) { } + @Override + public int readMemByte( int addr, boolean m1 ) + { + if( this.ctcM1ToClk2 ) { + if( m1 ) { + if( !this.lastMemReadM1 ) { + this.m1Cnt = 0; + } + this.m1Cnt++; + } + this.lastMemReadM1 = m1; + } + return getMemByte( addr, m1 ); + } + + @Override public void reset( EmuThread.ResetLevel resetLevel, Properties props ) { @@ -1682,6 +1802,7 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) } } } + this.audioInPhase = this.emuThread.readAudioPhase(); this.inverseBySW = false; this.pio1B3State = false; this.fdcWaitEnabled = false; @@ -1690,21 +1811,13 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) this.graphicKeyState = false; this.lowerDRAMEnabled = false; this.osRomEnabled = true; - this.scchPrgXEnabled = false; - this.scchBasicEnabled = false; - this.rf32KActive = false; - this.rf32NegA15 = false; - this.rfReadEnabled = false; - this.rfWriteEnabled = false; - this.rfAddr16to19 = 0; this.regF0 = 0; this.pio2Rom2010Offs = -1; this.romBank2010Offs = -1; this.fontOffs = 0; - this.v24BitOut = true; // V24: H-Pegel - this.v24BitNum = 0; - this.v24ShiftBuf = 0; - this.v24TStateCounter = 0; + this.m1Cnt = 0; + this.lastMemReadM1 = false; + this.ctcWritten = false; if( (this.osBytes == monSCCH80) || (this.osBytes == monSCCH1088) || (this.osBytes == mon2010c) ) @@ -1720,21 +1833,22 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) @Override public void saveBasicProgram() { - boolean cancelled = false; - int begAddr = -1; - int endAddr = -1; - int hsType = -1; - BasicType bType = null; - int preIdx = -1; + boolean cancelled = false; + int begAddr = -1; + int endAddr = -1; + BasicType ac1BasicType = null; + SaveDlg.BasicType dstBasicType = SaveDlg.BasicType.NO_BASIC; + String title = "BASIC-Programm speichern"; + int preIdx = -1; if( this.lastBasicType != null ) { switch( this.lastBasicType ) { case AC1_MINI: + title = "Mini-BASIC-Programm speichern"; preIdx = 0; break; case AC1_8K: case SCCH: - case BACOBAS: case ADDR_60F7: preIdx = 1; break; @@ -1742,11 +1856,22 @@ public void saveBasicProgram() case AC1_12K: preIdx = 2; break; + + case AC1_BASIC6: + preIdx = 3; + break; + + case BACOBAS2: + case BACOBAS3: + preIdx = 4; + break; } } if( (preIdx < 0) && this.modeSCCH ) { preIdx = 1; } + String fileInfo = null; + javax.swing.filechooser.FileFilter fileFilter = null; switch( OptionDlg.showOptionDlg( this.screenFrm, "W\u00E4hlen Sie bitte den BASIC-Interpreter aus,\n" @@ -1754,34 +1879,57 @@ public void saveBasicProgram() "BASIC-Interpreter", preIdx, "Mini-BASIC", - "8K-AC1-BASIC, SCCH-BASIC oder BACOBAS", - "12K-AC1-BASIC" ) ) + "AC1-8K-BASIC oder SCCH-BASIC", + "AC1-12K-BASIC", + "AC1-BASIC6", + "BACOBAS" ) ) { case 0: - bType = BasicType.AC1_MINI; - begAddr = 0x18C0; - endAddr = this.emuThread.getMemWord( 0x18E9 ); - hsType = 'b'; + ac1BasicType = BasicType.AC1_MINI; + dstBasicType = SaveDlg.BasicType.TINYBASIC; + begAddr = 0x18C0; + endAddr = this.emuThread.getMemWord( 0x18E9 ); break; case 1: - bType = this.lastBasicType; - if( (bType != BasicType.AC1_8K) - && (bType != BasicType.SCCH) - && (bType != BasicType.BACOBAS) ) + ac1BasicType = this.lastBasicType; + if( (ac1BasicType != BasicType.AC1_8K) + && (ac1BasicType != BasicType.SCCH) ) { - bType = BasicType.ADDR_60F7; + ac1BasicType = BasicType.ADDR_60F7; } - begAddr = 0x6000; - endAddr = SourceUtil.getKCBasicStyleEndAddr( this.emuThread, 0x60F7 ); - hsType = 'B'; + dstBasicType = SaveDlg.BasicType.MS_DERIVED_BASIC; + begAddr = 0x60F7; + endAddr = SourceUtil.getBasicEndAddr( this.emuThread, begAddr ); + fileInfo = "BASIC-Programmdatei (*.bas)"; + fileFilter = EmuUtil.getBasicFileFilter(); break; case 2: - bType = BasicType.AC1_12K; - begAddr = 0x6FB7; - endAddr = SourceUtil.getKCBasicStyleEndAddr( this.emuThread, begAddr ); - hsType = 'B'; + ac1BasicType = BasicType.AC1_12K; + dstBasicType = SaveDlg.BasicType.MS_DERIVED_BASIC; + begAddr = 0x6FB7; + endAddr = SourceUtil.getBasicEndAddr( this.emuThread, begAddr ); + fileInfo = "BASIC-Programmdatei (*.bas)"; + fileFilter = EmuUtil.getBasicFileFilter(); + break; + + case 3: + ac1BasicType = BasicType.AC1_BASIC6; + dstBasicType = SaveDlg.BasicType.MS_DERIVED_BASIC; + begAddr = 0x6300; + endAddr = SourceUtil.getBasicEndAddr( this.emuThread, begAddr ); + fileInfo = "AC1-BASIC6-Programmdatei (*.abc)"; + fileFilter = EmuUtil.getAC1Basic6FileFilter(); + break; + + case 4: + ac1BasicType = BasicType.BACOBAS3; + dstBasicType = SaveDlg.BasicType.MS_DERIVED_BASIC; + begAddr = 0x60F7; + endAddr = SourceUtil.getBasicEndAddr( this.emuThread, begAddr ); + fileInfo = "BASIC-Programmdatei (*.bas)"; + fileFilter = EmuUtil.getBasicFileFilter(); break; default: @@ -1789,15 +1937,14 @@ public void saveBasicProgram() } if( !cancelled ) { if( (begAddr > 0) && (endAddr > begAddr) ) { - this.lastBasicType = bType; + this.lastBasicType = ac1BasicType; (new SaveDlg( this.screenFrm, begAddr, endAddr, - hsType, - false, // kein KC-BASIC - false, // kein RBASIC - "BASIC-Programm speichern" )).setVisible( true ); + "BASIC-Programm speichern", + dstBasicType, + fileFilter )).setVisible( true ); } else { showNoBasic(); } @@ -1854,27 +2001,9 @@ public boolean setMemByte( int addr, int value ) boolean rv = false; boolean done = false; if( this.modeSCCH ) { - if( this.rfWriteEnabled && (this.ramModule3 != null) ) { - int idx = this.rfAddr16to19 | addr; - if( (idx >= 0) && (idx < this.ramModule3.length) ) { - this.ramModule3[ idx ] = (byte) value; - rv = true; - } - done = true; - } - if( !done && this.rf32KActive && (this.ramModule3 != null) - && (addr >= 0x4000) && (addr < 0xC000) ) - { - int idx = this.rfAddr16to19 | addr; - if( this.rf32NegA15 ) { - idx ^= 0x8000; - } - if( (idx >= 0) && (idx < this.ramModule3.length) ) { - this.ramModule3[ idx ] = (byte) value; - rv = true; - } - done = true; - } + int status = setScchMemByte( addr, value ); + done = (status >= 0); + rv = (status > 0); if( !done && (addr < 0x1000) ) { // Durchschreiben auf den DRAM this.emuThread.setRAMByte( addr, value ); @@ -1929,54 +2058,6 @@ public boolean shouldAskConvertScreenChar() } - @Override - public synchronized void startPastingText( String text ) - { - boolean done = false; - if( text != null ) { - if( !text.isEmpty() ) { - if( this.pasteFast ) { - cancelPastingText(); - CharacterIterator iter = new StringCharacterIterator( text ); - char ch = iter.first(); - if( ch != CharacterIterator.DONE ) { - if( ch == '\n' ) { - ch = '\r'; - } - /* - * Da sich die Programmausfuehrung i.d.R. bereits - * in der betreffenden Systemfunktion befindet, - * muss das erste Zeichen direkt an der Tastatur - * angelegt werden, - * damit der Systemaufruf beendet wird und somit - * der naechste Aufruf dann abgefangen werden kann. - */ - keyTyped( ch ); - long millis = (ch == '\r' ? - getDelayMillisAfterPasteEnter() - : getDelayMillisAfterPasteChar()); - if( millis > 0 ) { - try { - Thread.sleep( millis ); - } - catch( InterruptedException ex ) {} - keyReleased(); - } - this.pasteIter = iter; - done = false; - } - } else { - super.startPastingText( text ); - done = false; - } - } - } - if( !done ) { - this.screenFrm.firePastingTextFinished(); - } - } - - @Override public boolean supportsAudio() { @@ -1991,13 +2072,6 @@ public boolean supportsCopyToClipboard() } - @Override - public boolean supportsPasteFromClipboard() - { - return true; - } - - @Override public boolean supportsPrinter() { @@ -2021,21 +2095,52 @@ public boolean supportsSaveBasic() @Override public void updSysCells( - int begAddr, - int len, - Object fileFmt, - int fileType ) + int begAddr, + int len, + FileFormat fileFmt, + int fileType ) { if( fileFmt != null ) { - if( fileFmt.equals( FileInfo.HEADERSAVE ) && (fileType == 'B') ) { - if( begAddr == 0x60F7 ) { - int topAddr = begAddr + len; + if( (fileFmt.equals( FileFormat.BASIC_PRG ) + && (begAddr == 0x60F7) + && (len > 7)) + || (fileFmt.equals( FileFormat.HEADERSAVE ) + && (fileType == 'B') + && (begAddr <= 0x60F7) + && ((begAddr + len) > 0x60FE)) ) + { + int topAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x60F7 ); + if( topAddr > 0x60F7 ) { this.emuThread.setMemWord( 0x60D2, topAddr ); this.emuThread.setMemWord( 0x60D4, topAddr ); this.emuThread.setMemWord( 0x60D6, topAddr ); } - else if( begAddr == 0x6FB7 ) { - int topAddr = begAddr + len; + } + if( (fileFmt.equals( FileFormat.BASIC_PRG ) + && (begAddr == 0x6300) + && (len > 7)) + || (fileFmt.equals( FileFormat.HEADERSAVE ) + && (fileType == 'B') + && (begAddr <= 0x6300) + && ((begAddr + len) > 0x6307)) ) + { + int topAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x6300 ); + if( topAddr > 0x6300 ) { + this.emuThread.setMemWord( 0x60A8, topAddr ); + this.emuThread.setMemWord( 0x60AA, topAddr ); + this.emuThread.setMemWord( 0x60AC, topAddr ); + } + } + else if( (fileFmt.equals( FileFormat.BASIC_PRG ) + && (begAddr == 0x6FB7) + && (len > 7)) + || (fileFmt.equals( FileFormat.HEADERSAVE ) + && (fileType == 'B') + && (begAddr > 0x60F7) && (begAddr <= 0x6FB7) + && ((begAddr + len) > 0x6FBE)) ) + { + int topAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x6FB7 ); + if( topAddr > 0x6FB7 ) { this.emuThread.setMemWord( 0x415E, topAddr ); this.emuThread.setMemWord( 0x4160, topAddr ); this.emuThread.setMemWord( 0x4162, topAddr ); @@ -2046,7 +2151,7 @@ else if( begAddr == 0x6FB7 ) { @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { value &= 0xFF; if( (this.gide != null) && ((port & 0xF0) == 0x80) ) { @@ -2061,15 +2166,16 @@ public void writeIOByte( int port, int value ) case 1: case 2: case 3: - this.ctc.write( port & 0x03, value ); + this.ctc.write( port & 0x03, value, tStates ); + this.ctcWritten = true; break; case 4: - this.pio1.writePortA( value ); + this.pio1.writeDataA( value ); break; case 5: - this.pio1.writePortB( value ); + this.pio1.writeDataB( value ); synchronized( this.pio1 ) { int v = this.pio1.fetchOutValuePortB( false ); this.emuThread.writeAudioPhase( @@ -2131,7 +2237,7 @@ public void writeIOByte( int port, int value ) case 8: if( this.pio2 != null ) { - this.pio2.writePortA( value ); + this.pio2.writeDataA( value ); synchronized( this ) { boolean state = ((this.pio2.fetchOutValuePortA( false ) & 0x02) != 0); @@ -2146,13 +2252,13 @@ public void writeIOByte( int port, int value ) } this.v24BitOut = state; } - this.pio2.writePortA( value ); + this.pio2.writeDataA( value ); } break; case 9: if( this.pio2 != null ) { - this.pio2.writePortB( value ); + this.pio2.writeDataB( value ); } break; @@ -2208,9 +2314,9 @@ public void writeIOByte( int port, int value ) case 0x14: if( this.modeSCCH ) { - this.scchBasicEnabled = ((value & 0x02) != 0); - this.lowerDRAMEnabled = ((value & 0x04) != 0); - this.scchRomdiskEnabled = ((value & 0x08) != 0); + this.scchBasicRomEnabled = ((value & 0x02) != 0); + this.lowerDRAMEnabled = ((value & 0x04) != 0); + this.scchRomdiskEnabled = ((value & 0x08) != 0); if( this.scchRomdiskEnabled ) { int bank = (value & 0x01) | ((value >> 3) & 0x0E); if( this.scchRomdiskBegAddr == 0x8000 ) { @@ -2218,9 +2324,9 @@ public void writeIOByte( int port, int value ) } else { this.scchRomdiskBankAddr = (bank << 14); } - this.scchPrgXEnabled = false; + this.scchPrgXRomEnabled = false; } else { - this.scchPrgXEnabled = ((value & 0x01) != 0); + this.scchPrgXRomEnabled = ((value & 0x01) != 0); } } break; @@ -2268,11 +2374,11 @@ public void writeIOByte( int port, int value ) if( this.fdc != null ) { if( this.fdcWaitEnabled ) { // max. 20 Mikrosekunden warten - Z80CPU cpu = this.emuThread.getZ80CPU(); - int tStates = cpu.getMaxSpeedKHz() / 50; - while( !this.fdc.isInterruptRequest() && (tStates > 0) ) { + Z80CPU cpu = this.emuThread.getZ80CPU(); + int waitStates = cpu.getMaxSpeedKHz() / 50; + while( !this.fdc.isInterruptRequest() && (waitStates > 0) ) { z80TStatesProcessed( cpu, 4 ); - tStates -= 4; + waitStates -= 4; } } } @@ -2330,6 +2436,10 @@ public void writeIOByte( int port, int value ) } break; + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: case 0xFC: case 0xFD: case 0xFE: @@ -2345,26 +2455,6 @@ public void writeIOByte( int port, int value ) /* --- private Methoden --- */ - private synchronized void checkAddPCListener( Properties props ) - { - Z80CPU cpu = this.emuThread.getZ80CPU(); - if( cpu != null ) { - boolean pasteFast = EmuUtil.getBooleanProperty( - props, - "jkcemu.ac1.paste.fast", - true ); - if( pasteFast != this.pasteFast ) { - this.pasteFast = pasteFast; - if( pasteFast ) { - cpu.addPCListener( this, ADDR_INCH ); - } else { - cpu.removePCListener( this ); - } - } - } - } - - private void createColors( Properties props ) { float f = getBrightness( props ); @@ -2380,75 +2470,31 @@ private void createColors( Properties props ) } - private static boolean emulatesColors( Properties props ) + private boolean emulatesColors( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.ac1.color", + this.propPrefix + "color", false ); } - private static boolean emulatesJoystick( Properties props ) + private boolean emulatesCTCM1ToClk2( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.ac1.joystick.enabled", + this.propPrefix + "ctc.m1_to_clk2", false ); } - private static boolean emulatesFloppyDisk( Properties props ) - { - return EmuUtil.getBooleanProperty( - props, - "jkcemu.ac1.floppydisk.enabled", - false ); - } - - - private boolean emulatesKCNet( Properties props ) - { - return EmuUtil.getBooleanProperty( - props, - "jkcemu.ac1.kcnet.enabled", - false ); - } - - - private boolean emulatesUSB( Properties props ) - { - return EmuUtil.getBooleanProperty( - props, - "jkcemu.ac1.vdip.enabled", - false ); - } - - - private static int getScchRomdiskBegAddr( Properties props ) - { - int rv = 0xC000; - if( props != null ) { - String text = EmuUtil.getProperty( - props, - "jkcemu.ac1.scch.romdisk.address.begin" ); - if( text != null ) { - if( text.trim().equals( "8000" ) ) { - rv = 0x8000; - } - } - } - return rv; - } - - private void loadFont( Properties props ) { this.extFont = true; this.fontBytes = readFontByProperty( - props, - "jkcemu.ac1.font.file", - this.fontSwitchable ? 0x1000 : 0x0800 ); + props, + this.propPrefix + "font.file", + this.fontSwitchable ? 0x1000 : 0x0800 ); if( this.fontBytes == null ) { this.extFont = false; @@ -2522,132 +2568,4 @@ private void loadFont( Properties props ) } } } - - - private void loadROMs( Properties props ) - { - // OS-ROM - this.osFile = EmuUtil.getProperty( props, "jkcemu.ac1.os.file" ); - this.osBytes = readFile( this.osFile, 0x1000, "Monitorprogramm" ); - if( this.osBytes == null ) { - if( this.modeSCCH ) { - if( this.osVersion.startsWith( "SCCH8.0" ) ) { - if( monSCCH80 == null ) { - monSCCH80 = readResource( "/rom/ac1/scchmon_80g.bin" ); - } - this.osBytes = monSCCH80; - } else { - if( monSCCH1088 == null ) { - monSCCH1088 = readResource( "/rom/ac1/scchmon_1088g.bin" ); - } - this.osBytes = monSCCH1088; - } - } else if( this.mode2010 ) { - if( mon2010c == null ) { - mon2010c = readResource( "/rom/ac1/mon2010c.bin" ); - } - this.osBytes = mon2010c; - } else { - if( this.mode64x16 ) { - if( mon31_64x16 == null ) { - mon31_64x16 = readResource( "/rom/ac1/mon_31_64x16.bin" ); - } - this.osBytes = mon31_64x16; - } else { - if( mon31_64x32 == null ) { - mon31_64x32 = readResource( "/rom/ac1/mon_31_64x32.bin" ); - } - this.osBytes = mon31_64x32; - } - } - } - - // Mini-BASIC - if( !this.modeSCCH ) { - if( minibasic == null ) { - minibasic = readResource( "/rom/ac1/minibasic.bin" ); - } - } - - // SCCH BASIC-ROM - if( this.modeSCCH ) { - this.scchBasicFile = EmuUtil.getProperty( - props, - "jkcemu.ac1.scch.basic.file" ); - this.scchBasicBytes = readFile( - this.scchBasicFile, - 0x2000, - "BASIC" ); - if( this.scchBasicBytes == null ) { - if( gsbasic == null ) { - gsbasic = readResource( "/rom/ac1/gsbasic.bin" ); - } - this.scchBasicBytes = gsbasic; - } - } else { - this.scchBasicFile = null; - this.scchBasicBytes = null; - } - - // SCCH Programmpaket X - if( this.modeSCCH ) { - this.scchPrgXFile = EmuUtil.getProperty( - props, - "jkcemu.ac1.scch.program_x.file" ); - this.scchPrgXBytes = readFile( - this.scchPrgXFile, - 0x2000, - "Programmpaket X" ); - } else { - this.scchPrgXFile = null; - this.scchPrgXBytes = null; - } - - // SCCH ROM-Disk - if( this.modeSCCH ) { - this.scchRomdiskFile = EmuUtil.getProperty( - props, - "jkcemu.ac1.scch.romdisk.file" ); - this.scchRomdiskBytes = readFile( - this.scchRomdiskFile, - 0x40000, - "SCCH-Modul 1 ROM-Disk" ); - } else { - this.scchRomdiskFile = null; - this.scchRomdiskBytes = null; - } - - - // AC1-2010 ROM-Baenke - if( this.mode2010 ) { - this.pio2Rom2010File = EmuUtil.getProperty( - props, - "jkcemu.ac1.2010.pio2rom.file" ); - this.pio2Rom2010Bytes = readFile( - this.pio2Rom2010File, - 0x2000, - "AC1-2010 PIO2 ROM" ); - if( this.pio2Rom2010Bytes == null ) { - if( pio2Rom2010 == null ) { - pio2Rom2010 = readResource( "/rom/ac1/pio2rom2010.bin" ); - } - this.pio2Rom2010Bytes = pio2Rom2010; - } - this.romBank2010File = EmuUtil.getProperty( - props, - "jkcemu.ac1.2010.rombank.file" ); - this.romBank2010Bytes = readFile( - this.romBank2010File, - 0x20000, - "AC1-2010 ROM-Bank" ); - } else { - this.pio2Rom2010File = null; - this.pio2Rom2010Bytes = null; - this.romBank2010File = null; - this.romBank2010Bytes = null; - } - - // Zeichensatz - loadFont( props ); - } } diff --git a/src/jkcemu/emusys/BCS3.java b/src/jkcemu/emusys/BCS3.java index 8491e6c..48f05d1 100644 --- a/src/jkcemu/emusys/BCS3.java +++ b/src/jkcemu/emusys/BCS3.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -12,6 +12,7 @@ import java.lang.*; import java.util.*; import jkcemu.base.*; +import jkcemu.text.TextUtil; import z80emu.*; @@ -98,6 +99,8 @@ public class BCS3 extends EmuSys implements Z80CTCListener "NEW", null, "RANDOMIZE", null, "READ", null, "DATA", null }; + private static final String PROP_PREFIX = "jkcemu.bcs3."; + private static int[] endInstBytesSE24 = { 0x0F, 0x27, 0xDE, 0x1E }; private static int[] endInstBytesSE31 = { 0x0F, 0x27, 0xCE, 0x1E }; private static int[] endInstBytesSP33 = { 0x00, 0x27, 0xCA, 0x1E }; @@ -112,8 +115,9 @@ public class BCS3 extends EmuSys implements Z80CTCListener private static byte[] mcEdtitorSE31 = null; private Z80CTC ctc; + private String osFile; + private byte[] osBytes; private byte[] fontBytes; - private byte[] rom0000; private byte[] romF000; private byte[] ram; private int ramEndAddr; @@ -130,18 +134,18 @@ public class BCS3 extends EmuSys implements Z80CTCListener public BCS3( EmuThread emuThread, Properties props ) { - super( emuThread, props ); - this.romF000 = null; - this.rom0000 = readROMByProperty( - props, - "jkcemu.bcs3.os.file", - 0x1000 ); - this.fontBytes = readFontByProperty( - props, - "jkcemu.bcs3.font.file", - 0x0800 ); + super( emuThread, props, PROP_PREFIX ); + this.osFile = null; + this.osBytes = null; + this.fontBytes = null; + this.romF000 = null; + if( !isReloadExtROMsOnPowerOnEnabled( props ) ) { + loadROMs( props ); + } - String version = EmuUtil.getProperty( props, "jkcemu.bcs3.os.version" ); + String version = EmuUtil.getProperty( + props, + this.propPrefix + "os.version" ); if( version.equals( "3.1" ) ) { this.osVersion = 31; this.screenRowHeight = 8; @@ -157,25 +161,27 @@ public BCS3( EmuThread emuThread, Properties props ) if( is40CharsPerLineMode( props ) ) { this.screenCharCols = 40; this.screenRowOffset = 41; - if( this.rom0000 == null ) { + if( this.osBytes == null ) { if( osBytesSE31_40 == null ) { osBytesSE31_40 = readResource( "/rom/bcs3/se31_40.bin" ); } - this.rom0000 = osBytesSE31_40; + this.osBytes = osBytesSE31_40; } } else { this.screenCharCols = 29; this.screenRowOffset = 30; - if( this.rom0000 == null ) { + if( this.osBytes == null ) { if( osBytesSE31_29 == null ) { osBytesSE31_29 = readResource( "/rom/bcs3/se31_29.bin" ); } - this.rom0000 = osBytesSE31_29; + this.osBytes = osBytesSE31_29; } - if( mcEdtitorSE31 == null ) { - mcEdtitorSE31 = readResource( "/rom/bcs3/se31mceditor.bin" ); + if( this.romF000 == null ) { + if( mcEdtitorSE31 == null ) { + mcEdtitorSE31 = readResource( "/rom/bcs3/se31mceditor.bin" ); + } + this.romF000 = mcEdtitorSE31; } - this.romF000 = mcEdtitorSE31; } } else if( version.equals( "3.3" ) ) { this.osVersion = 33; @@ -186,11 +192,11 @@ public BCS3( EmuThread emuThread, Properties props ) } this.fontBytes = fontBytesSP33; } - if( this.rom0000 == null ) { + if( this.osBytes == null ) { if( osBytesSP33_29 == null ) { osBytesSP33_29 = readResource( "/rom/bcs3/sp33_29.bin" ); } - this.rom0000 = osBytesSP33_29; + this.osBytes = osBytesSP33_29; } this.screenOffset = 0x00B4; this.screenBaseHeight = 232; @@ -206,11 +212,11 @@ public BCS3( EmuThread emuThread, Properties props ) } this.fontBytes = fontBytesSE24; } - if( this.rom0000 == null ) { + if( this.osBytes == null ) { if( osBytesSE24 == null ) { osBytesSE24 = readResource( "/rom/bcs3/se24.bin" ); } - this.rom0000 = osBytesSE24; + this.osBytes = osBytesSE24; } this.screenOffset = 0x0050; this.screenBaseHeight = 184; @@ -349,11 +355,17 @@ public static int getDefaultSpeedKHz( Properties props ) { return (EmuUtil.getProperty( props, - "jkcemu.bcs3.os.version" ).equals( "3.1" ) + PROP_PREFIX + "os.version" ).equals( "3.1" ) && is40CharsPerLineMode( props )) ? 3500 : 2500; } + public static boolean getDefaultSwapKeyCharCase() + { + return false; + } + + /* --- Z80CTCListener --- */ @Override @@ -385,23 +397,30 @@ public boolean canApplySettings( Properties props ) props, "jkcemu.system" ).equals( "BCS3" ); if( rv ) { - String version = EmuUtil.getProperty( props, "jkcemu.bcs3.os.version" ); + rv = TextUtil.equals( + this.osFile, + EmuUtil.getProperty( props, "jkcemu.z1013.os.file" ) ); + } + if( rv ) { + String version = EmuUtil.getProperty( + props, + this.propPrefix + "os.version" ); if( version.equals( "3.1" ) ) { if( is40CharsPerLineMode( props ) ) { - if( this.rom0000 != osBytesSE31_40 ) { + if( this.osBytes != osBytesSE31_40 ) { rv = false; } } else { - if( this.rom0000 != osBytesSE31_29 ) { + if( this.osBytes != osBytesSE31_29 ) { rv = false; } } } else if( version.equals( "3.3" ) ) { - if( this.rom0000 != osBytesSP33_29 ) { + if( this.osBytes != osBytesSP33_29 ) { rv = false; } } else { - if( this.rom0000 != osBytesSE24 ) { + if( this.osBytes != osBytesSE24 ) { rv = false; } } @@ -436,7 +455,7 @@ public void die() @Override public int getAppStartStackInitValue() { - return this.rom0000 == this.osBytesSE24 ? 0x3C50 : 0x3C80; + return this.osBytes == this.osBytesSE24 ? 0x3C50 : 0x3C80; } @@ -506,7 +525,7 @@ public CharRaster getCurScreenCharRaster() return new CharRaster( this.screenCharCols, this.screenCharRows, - this.rom0000 == osBytesSE24 ? 16 : 8, + this.osBytes == osBytesSE24 ? 16 : 8, 8, 8, 0 ); @@ -545,12 +564,12 @@ public String getHelpPage() public Integer getLoadAddr() { Integer rv = null; - if( this.rom0000 == osBytesSE24 ) { + if( this.osBytes == osBytesSE24 ) { rv = new Integer( 0x3DA1 ); } - else if( (this.rom0000 == osBytesSE31_29) - || (this.rom0000 == osBytesSE31_40) - || (this.rom0000 == osBytesSP33_29) ) + else if( (this.osBytes == osBytesSE31_29) + || (this.osBytes == osBytesSE31_40) + || (this.osBytes == osBytesSP33_29) ) { rv = new Integer( getMemWord( 0x3C00 ) ); } @@ -604,9 +623,9 @@ else if( (addr >= 0x1C00) && (addr < 0x2000)) { rv = (int) (this.ram[ idx ] & 0xFF); } } - else if( this.rom0000 != null ) { - if( addr < this.rom0000.length ) { - rv = (int) this.rom0000[ addr ] & 0xFF; + else if( this.osBytes != null ) { + if( addr < this.osBytes.length ) { + rv = (int) this.osBytes[ addr ] & 0xFF; } } } @@ -754,7 +773,7 @@ public int getScreenWidth() @Override public boolean getSwapKeyCharCase() { - return false; + return getDefaultSwapKeyCharCase(); } @@ -860,19 +879,19 @@ public void openBasicProgram() String[] tokens = null; int addr = -1; int endLineNum = -1; - if( this.rom0000 == osBytesSE24 ) { + if( this.osBytes == osBytesSE24 ) { addr = 0x3DA1; endLineNum = 9999; tokens = se24Tokens; } - else if( (this.rom0000 == osBytesSE31_29) - || (this.rom0000 == osBytesSE31_40) ) + else if( (this.osBytes == osBytesSE31_29) + || (this.osBytes == osBytesSE31_40) ) { addr = getMemWord( 0x3C00 ); endLineNum = 9999; tokens = se31Tokens; } - else if( this.rom0000 == osBytesSP33_29 ) { + else if( this.osBytes == osBytesSP33_29 ) { addr = getMemWord( 0x3C00 ); endLineNum = 9984; tokens = sp33Tokens; @@ -953,11 +972,11 @@ && isIdentifierChar( s.charAt( 0 ) ) ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; if( (port & 0x04) == 0 ) { // A2=0 - rv = this.ctc.read( port & 0x03 ); + rv = this.ctc.read( port & 0x03, tStates ); } return rv; } @@ -966,12 +985,17 @@ public int readIOByte( int port ) @Override public void reset( EmuThread.ResetLevel resetLevel, Properties props ) { + if( (resetLevel == EmuThread.ResetLevel.POWER_ON) + && isReloadExtROMsOnPowerOnEnabled( props ) ) + { + loadROMs( props ); + } if( (resetLevel == EmuThread.ResetLevel.POWER_ON) || (resetLevel == EmuThread.ResetLevel.COLD_RESET) ) { - if( (this.rom0000 == osBytesSE31_29) - || (this.rom0000 == osBytesSE31_40) - || (this.rom0000 == osBytesSP33_29) ) + if( (this.osBytes == osBytesSE31_29) + || (this.osBytes == osBytesSE31_40) + || (this.osBytes == osBytesSP33_29) ) { this.screenCharRows = 3; } @@ -989,17 +1013,17 @@ public void saveBasicProgram() { int begAddr = -1; int endLineNum = -1; - if( this.rom0000 == osBytesSE24 ) { + if( this.osBytes == osBytesSE24 ) { begAddr = 0x3DA1; endLineNum = 9999; } - else if( (this.rom0000 == osBytesSE31_29) - || (this.rom0000 == osBytesSE31_40) ) + else if( (this.osBytes == osBytesSE31_29) + || (this.osBytes == osBytesSE31_40) ) { begAddr = getMemWord( 0x3C00 ); endLineNum = 9999; } - else if( this.rom0000 == osBytesSP33_29 ) { + else if( this.osBytes == osBytesSP33_29 ) { begAddr = getMemWord( 0x3C00 ); endLineNum = 9984; } @@ -1028,10 +1052,9 @@ else if( this.rom0000 == osBytesSP33_29 ) { this.screenFrm, begAddr, endAddr - 1, - 'B', - false, // kein KC-BASIC - false, // kein RBASIC - "BASIC-Programm speichern" )).setVisible( true ); + "BASIC-Programm speichern", + SaveDlg.BasicType.OTHER_BASIC, + EmuUtil.getBinaryFileFilter() )).setVisible( true ); } else { showNoBasic(); } @@ -1053,9 +1076,9 @@ else if( (addr >= 0x1C00) && (addr < 0x2000)) { int idx = addr - 0x1C00; if( (idx >= 0) && (idx < this.ram.length) ) { this.ram[ idx ] = (byte) value; - if( ((this.rom0000 == osBytesSE31_29) - || (this.rom0000 == osBytesSE31_40) - || (this.rom0000 == osBytesSP33_29)) + if( ((this.osBytes == osBytesSE31_29) + || (this.osBytes == osBytesSE31_40) + || (this.osBytes == osBytesSP33_29)) && (addr == 0x1C06) ) { int nRows = (int) (this.ram[ 6 ] & 0xFF); @@ -1128,24 +1151,24 @@ public boolean supportsSaveBasic() @Override public void updSysCells( - int begAddr, - int len, - Object fileFmt, - int fileType ) + int begAddr, + int len, + FileFormat fileFmt, + int fileType ) { int prgBegAddr = -1; int[] endInstBytes = null; - if( this.rom0000 == osBytesSE24 ) { + if( this.osBytes == osBytesSE24 ) { prgBegAddr = 0x3DA1; endInstBytes = endInstBytesSE24; } - else if( (this.rom0000 == osBytesSE31_29) - || (this.rom0000 == osBytesSE31_40) ) + else if( (this.osBytes == osBytesSE31_29) + || (this.osBytes == osBytesSE31_40) ) { prgBegAddr = getMemWord( 0x3C00 ); endInstBytes = endInstBytesSE31; } - else if( this.rom0000 == osBytesSP33_29 ) { + else if( this.osBytes == osBytesSP33_29 ) { prgBegAddr = getMemWord( 0x3C00 ); endInstBytes = endInstBytesSP33; } @@ -1155,14 +1178,15 @@ else if( this.rom0000 == osBytesSP33_29 ) { { boolean state = false; if( fileFmt != null ) { - if( fileFmt.equals( FileInfo.HEADERSAVE ) ) { + if( fileFmt.equals( FileFormat.HEADERSAVE ) ) { if( fileType == 'B' ) { state = true; } } - else if( fileFmt.equals( FileInfo.KCC ) - || fileFmt.equals( FileInfo.INTELHEX ) - || fileFmt.equals( FileInfo.BIN ) ) + else if( fileFmt.equals( FileFormat.BASIC_PRG ) + || fileFmt.equals( FileFormat.KCC ) + || fileFmt.equals( FileFormat.INTELHEX ) + || fileFmt.equals( FileFormat.BIN ) ) { state = true; } @@ -1183,7 +1207,7 @@ else if( fileFmt.equals( FileInfo.KCC ) } if( b == endInstBytes.length ) { int prgLen = addr - begAddr + endInstBytes.length; - if( this.rom0000 == osBytesSE24 ) { + if( this.osBytes == osBytesSE24 ) { this.emuThread.setMemWord( 0x3C06, prgLen ); } else { this.emuThread.setMemWord( 0x3C02, prgLen ); @@ -1197,10 +1221,10 @@ else if( fileFmt.equals( FileInfo.KCC ) @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { if( (port & 0x04) == 0 ) // A2=0 - this.ctc.write( port & 0x03, value ); + this.ctc.write( port & 0x03, value, tStates ); } @@ -1224,15 +1248,16 @@ private static int getRAMEndAddr( Properties props ) { return EmuUtil.getProperty( props, - "jkcemu.bcs3.ram.kbyte" ).equals( "17" ) ? 0x7FFF : 0x3FFF; + PROP_PREFIX + "ram.kbyte" ).equals( "17" ) ? + 0x7FFF : 0x3FFF; } private static boolean is40CharsPerLineMode( Properties props ) { return EmuUtil.getProperty( - props, - "jkcemu.bcs3.chars_per_line" ).equals( "40" ); + props, + PROP_PREFIX + "chars_per_line" ).equals( "40" ); } @@ -1242,5 +1267,17 @@ private static boolean isIdentifierChar( int ch ) || ((ch >= 'a') && (ch <= 'z')) || ((ch >= '0') && (ch <= '9')); } -} + + private void loadROMs( Properties props ) + { + this.osFile = EmuUtil.getProperty( + props, + this.propPrefix + "os.file" ); + this.osBytes = readROMFile( this.osFile, 0x1000, "Betriebssystem" ); + this.fontBytes = readFontByProperty( + props, + this.propPrefix + "font.file", + 0x0800 ); + } +} diff --git a/src/jkcemu/emusys/C80.java b/src/jkcemu/emusys/C80.java index 3e2ff49..e8b6317 100644 --- a/src/jkcemu/emusys/C80.java +++ b/src/jkcemu/emusys/C80.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -39,20 +39,19 @@ public class C80 extends EmuSys implements private long curDisplayTStates; private long displayCheckTStates; private boolean displayReset; - private boolean audioOutPhase; + private boolean audioInPhase; private Z80PIO pio1; private Z80PIO pio2; public C80( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.c80." ); if( mon == null ) { mon = readResource( "/rom/c80/c80mon.bin" ); } this.ram = new byte[ 0x0400 ]; - this.audioOutPhase = false; this.a4TStates = 0; this.curDisplayTStates = 0; this.displayCheckTStates = 0; @@ -110,6 +109,11 @@ public void z80MaxSpeedChanged( Z80CPU cpu ) @Override public void z80TStatesProcessed( Z80CPU cpu, int tStates ) { + boolean phase = this.emuThread.readAudioPhase(); + if( phase != this.audioInPhase ) { + this.audioInPhase = phase; + this.pio1.putInValuePortA( this.audioInPhase ? 0x80 : 0, 0x80 ); + } if( this.a4TStates > 0 ) { this.a4TStates -= tStates; if( this.a4TStates <= 0 ) { @@ -344,19 +348,17 @@ public boolean paintScreen( Graphics g, int x, int y, int screenScale ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; if( (port & 0x40) == 0 ) { switch( port & 0x03 ) { case 0: - this.pio1.putInValuePortA( - this.emuThread.readAudioPhase() ? 0x80 : 0, 0x80 ); - rv &= this.pio1.readPortA(); + rv &= this.pio1.readDataA(); break; case 1: - rv &= this.pio1.readPortB(); + rv &= this.pio1.readDataB(); break; case 2: @@ -371,11 +373,11 @@ public int readIOByte( int port ) if( (port & 0x80) == 0 ) { switch( port & 0x03 ) { case 0: - rv &= this.pio2.readPortA(); + rv &= this.pio2.readDataA(); break; case 1: - rv &= this.pio2.readPortB(); + rv &= this.pio2.readDataB(); break; case 2: @@ -406,6 +408,7 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) } this.pio1.putInValuePortA( 0xFF, false ); this.displayReset = false; + this.audioInPhase = this.emuThread.readAudioPhase(); } @@ -441,7 +444,7 @@ public boolean supportsKeyboardFld() @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { boolean dirty = false; boolean ready = false; @@ -449,7 +452,7 @@ public void writeIOByte( int port, int value ) if( (port & 0x40) == 0 ) { switch( port & 0x03 ) { case 0: - this.pio1.writePortA( value ); + this.pio1.writeDataA( value ); v = this.pio1.fetchOutValuePortA( false ); if( (v & 0x20) == 0 ) { this.displayReset = true; @@ -458,7 +461,7 @@ public void writeIOByte( int port, int value ) break; case 1: - this.pio1.writePortB( value ); + this.pio1.writeDataB( value ); ready = this.pio1.isReadyPortB(); this.pio1BValue = this.pio1.fetchOutValuePortB( ready ); v = 0xFF; @@ -519,11 +522,11 @@ public void writeIOByte( int port, int value ) if( (port & 0x80) == 0 ) { switch( port & 0x03 ) { case 0: - this.pio2.writePortA( value ); + this.pio2.writeDataA( value ); break; case 1: - this.pio2.writePortB( value ); + this.pio2.writeDataB( value ); break; case 2: diff --git a/src/jkcemu/emusys/HueblerEvertMC.java b/src/jkcemu/emusys/HueblerEvertMC.java index a24a4dc..ec9467c 100644 --- a/src/jkcemu/emusys/HueblerEvertMC.java +++ b/src/jkcemu/emusys/HueblerEvertMC.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -24,14 +24,14 @@ public class HueblerEvertMC extends AbstractHueblerMC "CPOE", "MEMSI", "MAIN", "EXT" }; private static final int[] charToUnicode = { - '\u25C0', '\u2016', -1, '=', - '\u00F1', '\u03B1', '\u03B2', '\u03B4', - '\u2302', '\u03B7', '\u03B8', '\u03BB', - '\u03BC', -1, '\u03C3', '\u03A3', - -1, '\u03C6', '\u03A9', '\u00C5', - '\u00E5', '\u00C4', '\u00E4', '\u00D6', - '\u00F6', '\u00DC', '\u00FC', '\u2192', - '\u221A', '\u00B2', '\u00A3', '\u00A5' }; + '\u25C0', '\u2016', -1, '=', + '\u00F1', '\u03B1', '\u03B2', '\u03B4', + '\u2302', '\u03B7', '\u03B8', '\u03BB', + '\u03BC', -1, '\u03C3', '\u03A3', + -1, '\u03C6', '\u03A9', '\u00C5', + '\u00E5', '\u00C4', '\u00E4', '\u00D6', + '\u00F6', '\u00DC', '\u00FC', '\u2192', + '\u221A', '\u00B2', '\u00A3', '\u00A5' }; private static byte[] hemcFontBytes = null; private static byte[] monBytes = null; @@ -44,10 +44,10 @@ public class HueblerEvertMC extends AbstractHueblerMC public HueblerEvertMC( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.hemc." ); this.fontBytes = readFontByProperty( props, - "jkcemu.hemc.font.file", + this.propPrefix + "font.file", 0x0800 ); if( this.fontBytes == null ) { if( hemcFontBytes == null ) { @@ -60,7 +60,7 @@ public HueblerEvertMC( EmuThread emuThread, Properties props ) } this.ramVideo = new byte[ 0x0800 ]; this.ramStatic = new byte[ 0x0400 ]; - this.pio2 = new Z80PIO( "PIO (IO-Adressen 10h-13h)" ); + this.pio2 = new Z80PIO( "PIO (E/A-Adressen 10h-13h)" ); createIOSystem(); this.emuThread.getZ80CPU().setInterruptSources( this.ctc, @@ -355,14 +355,14 @@ public boolean keyTyped( char ch ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { port &= 0x3F; int rv = 0; switch( port ) { case 0x10: - rv = this.pio2.readPortA(); + rv = this.pio2.readDataA(); break; case 0x11: @@ -370,7 +370,7 @@ public int readIOByte( int port ) break; case 0x12: - rv = this.pio2.readPortB(); + rv = this.pio2.readDataB(); break; case 0x13: @@ -378,7 +378,7 @@ public int readIOByte( int port ) break; default: - rv = super.readIOByte( port ); + rv = super.readIOByte( port, tStates ); } return rv; } @@ -462,12 +462,12 @@ public boolean supportsCopyToClipboard() @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { port &= 0x3F; switch( port ) { case 0x10: - this.pio2.writePortA( value ); + this.pio2.writeDataA( value ); break; case 0x11: @@ -475,7 +475,7 @@ public void writeIOByte( int port, int value ) break; case 0x12: - this.pio2.writePortB( value ); + this.pio2.writeDataB( value ); break; case 0x13: @@ -483,7 +483,7 @@ public void writeIOByte( int port, int value ) break; default: - super.writeIOByte( port, value ); + super.writeIOByte( port, value, tStates ); } } @@ -492,7 +492,7 @@ public void writeIOByte( int port, int value ) private void checkAddPCListener( Properties props ) { - checkAddPCListener( props, "jkcemu.hemc.catch_print_calls" ); + checkAddPCListener( props, this.propPrefix + "catch_print_calls" ); } } diff --git a/src/jkcemu/emusys/HueblerGraphicsMC.java b/src/jkcemu/emusys/HueblerGraphicsMC.java index caf7c50..df08094 100644 --- a/src/jkcemu/emusys/HueblerGraphicsMC.java +++ b/src/jkcemu/emusys/HueblerGraphicsMC.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -20,9 +20,7 @@ import z80emu.*; -public class HueblerGraphicsMC - extends AbstractHueblerMC - implements Z80TStatesListener +public class HueblerGraphicsMC extends AbstractHueblerMC { private static final String[] basicTokens = { @@ -82,7 +80,7 @@ public class HueblerGraphicsMC public HueblerGraphicsMC( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.hgmc." ); if( hasBasic( props ) ) { monBasicBytes = readResource( "/rom/huebler/mon30p_hbasic33p.bin" ); this.basic = true; @@ -95,15 +93,16 @@ public HueblerGraphicsMC( EmuThread emuThread, Properties props ) this.kcNet = null; if( emulatesKCNet( props ) ) { - this.kcNet = new KCNet( "Netzwerk-PIO (IO-Adressen C0h-C3h)" ); + this.kcNet = new KCNet( "Netzwerk-PIO (E/A-Adressen C0h-C3h)" ); } this.vdip = null; if( emulatesUSB( props ) ) { - this.vdip = new VDIP( "USB-PIO (IO-Adressen FCh-FFh)" ); + this.vdip = new VDIP( + this.emuThread.getFileTimesViewFactory(), + "USB-PIO (E/A-Adressen FCh-FFh)" ); } if( (this.kcNet != null) || (this.vdip != null) ) { - java.util.List iSources - = new ArrayList(); + java.util.List iSources = new ArrayList<>(); iSources.add( this.ctc ); iSources.add( this.pio ); if( this.kcNet != null ) { @@ -122,6 +121,7 @@ public HueblerGraphicsMC( EmuThread emuThread, Properties props ) if( this.kcNet != null ) { this.kcNet.z80MaxSpeedChanged( cpu ); cpu.addMaxSpeedListener( this.kcNet ); + cpu.addTStatesListener( this.kcNet ); } if( this.vdip != null ) { this.vdip.applySettings( props ); @@ -226,14 +226,9 @@ public static int getDefaultSpeedKHz() } - /* --- Z80TStatesListener --- */ - - @Override - public synchronized void z80TStatesProcessed( Z80CPU cpu, int tStates ) + public static boolean getDefaultSwapKeyCharCase() { - if( this.kcNet != null ) { - this.kcNet.z80TStatesProcessed( cpu, tStates ); - } + return true; } @@ -297,7 +292,9 @@ public void die() { super.die(); if( this.kcNet != null ) { - this.emuThread.getZ80CPU().removeMaxSpeedListener( this.kcNet ); + Z80CPU cpu = this.emuThread.getZ80CPU(); + cpu.removeTStatesListener( this.kcNet ); + cpu.removeMaxSpeedListener( this.kcNet ); this.kcNet.die(); } if( this.vdip != null ) { @@ -419,7 +416,7 @@ public int getScreenWidth() @Override public boolean getSwapKeyCharCase() { - return true; + return getDefaultSwapKeyCharCase(); } @@ -503,7 +500,7 @@ public void openBasicProgram() } @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0; switch( port & 0xFF ) { @@ -526,7 +523,7 @@ public int readIOByte( int port ) break; default: - rv = super.readIOByte( port ); + rv = super.readIOByte( port, tStates ); } return rv; } @@ -566,16 +563,15 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) @Override public void saveBasicProgram() { - int endAddr = SourceUtil.getKCBasicStyleEndAddr( this.emuThread, 0x3770 ); + int endAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x3770 ); if( endAddr >= 0x3770 ) { (new SaveDlg( this.screenFrm, 0x3770, endAddr, - 'B', - false, // kein KC-BASIC - false, // kein RBASIC - "BASIC-Programm speichern" )).setVisible( true ); + "BASIC-Programm speichern", + SaveDlg.BasicType.MS_DERIVED_BASIC, + EmuUtil.getBasicFileFilter() )).setVisible( true ); } else { showNoBasic(); } @@ -624,15 +620,22 @@ public boolean supportsSaveBasic() @Override public void updSysCells( - int begAddr, - int len, - Object fileFmt, - int fileType ) - { - if( (begAddr == 0x3770) && (fileFmt != null) ) { - if( fileFmt.equals( FileInfo.HEADERSAVE ) ) { - if( fileType == 'B' ) { - int topAddr = begAddr + len; + int begAddr, + int len, + FileFormat fileFmt, + int fileType ) + { + if( fileFmt != null ) { + if( (fileFmt.equals( FileFormat.BASIC_PRG ) + && (begAddr == 0x3770) + && (len > 7)) + || (fileFmt.equals( FileFormat.HEADERSAVE ) + && (fileType == 'B') + && (begAddr <= 0x3770) + && ((begAddr + len) > 0x3777)) ) + { + int topAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x3770 ); + if( topAddr > 0x3770 ) { this.emuThread.setMemWord( 0x0199, topAddr ); this.emuThread.setMemWord( 0x019B, topAddr ); this.emuThread.setMemWord( 0x019D, topAddr ); @@ -643,7 +646,7 @@ public void updSysCells( @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { switch( port & 0xFC ) { case 0: @@ -678,7 +681,7 @@ public void writeIOByte( int port, int value ) break; default: - super.writeIOByte( port, value ); + super.writeIOByte( port, value, tStates ); } } @@ -687,13 +690,16 @@ public void writeIOByte( int port, int value ) private void checkAddPCListener( Properties props ) { - checkAddPCListener( props, "jkcemu.hgmc.catch_print_calls" ); + checkAddPCListener( props, this.propPrefix + "catch_print_calls" ); } - private static boolean hasBasic( Properties props ) + private boolean hasBasic( Properties props ) { - return EmuUtil.getBooleanProperty( props, "jkcemu.hgmc.basic", true ); + return EmuUtil.getBooleanProperty( + props, + this.propPrefix + "basic", + true ); } @@ -701,7 +707,7 @@ private boolean emulatesKCNet( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.hgmc.kcnet.enabled", + this.propPrefix + "kcnet.enabled", false ); } @@ -710,7 +716,7 @@ private boolean emulatesUSB( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.hgmc.vdip.enabled", + this.propPrefix + "vdip.enabled", false ); } @@ -728,7 +734,7 @@ private Map getPixelCRC32ToCharMap() ensureMonBytesLoaded(); if( monBytes != null ) { if( monBytes.length >= 0x0E0D ) { - Map map = new HashMap(); + Map map = new HashMap<>(); CRC32 crc = new CRC32(); int addr = 0x0B0D; for( int c = 0x20; c <= 0x7E; c++ ) { diff --git a/src/jkcemu/emusys/KC85.java b/src/jkcemu/emusys/KC85.java index 37ddfcd..ed3f7a2 100644 --- a/src/jkcemu/emusys/KC85.java +++ b/src/jkcemu/emusys/KC85.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -14,6 +14,7 @@ import java.lang.*; import java.util.*; import jkcemu.Main; +import jkcemu.audio.AudioOut; import jkcemu.base.*; import jkcemu.disk.*; import jkcemu.emusys.kc85.*; @@ -59,17 +60,21 @@ public class KC85 extends EmuSys implements "RANDOMIZE", "VGET$", "LINE", "CIRCLE", // 0xF0 "CSRLIN" }; - private static final int SUTAB = 0xB7B0; - private static final int SCREEN_WIDTH = 320; - private static final int SCREEN_HEIGHT = 256; + private static final int DEFAULT_SPEED_2_KHZ = 1750; + private static final int DEFAULT_SPEED_4_KHZ = 1773; + private static final int SUTAB = 0xB7B0; + private static final int SCREEN_WIDTH = 320; + private static final int SCREEN_HEIGHT = 256; private static final FloppyDiskInfo[] availableFloppyDisks = { new FloppyDiskInfo( "/disks/kc85/kc85caos.dump.gz", - "KC85 CAOS Systemdiskette" ), + "KC85 CAOS Systemdiskette", + 2, 2048, true ), new FloppyDiskInfo( "/disks/kc85/kc85microdos.dump.gz", - "KC85 MicroDOS Systemdiskette" ) }; + "KC85 MicroDOS Systemdiskette", + 2, 2048, true ) }; private static final int[][] basicRGBValues = { @@ -122,9 +127,9 @@ public class KC85 extends EmuSys implements * 7E ~ Umlaut sz */ private static int[] char2KeyNum = { - -1, 24, 41, 60, -1, -1, -1, 6, // 0x00 - -1, 122, 118, 120, 9, 126, -1, 25, - 8, 121, 119, 76, 57, -1, -1, -1, // 0x10 + -1, 24, 41, 60, 61, 115, -1, -1, // 0x00 + 6, 122, 118, 120, 9, 126, 127, 25, + 8, 121, 119, 76, 57, -1, 114, -1, // 0x10 123, 7, 56, 77, 86, 87, -1, 40, 70, 117, 5, 21, 101, 37, 85, 53, // 0x20 !"#$%&' 69, 59, 27, 104, 74, 10, 90, 106, // 0x28 ()*+,-./ @@ -137,23 +142,9 @@ public class KC85 extends EmuSys implements -1, 3, 95, 111, 99, 17, 35, 83, // 0x60 abcdefg 51, 65, 67, 73, 89, 79, 63, 55, // 0x68 hijklmno 39, 113, 97, 19, 33, 49, 47, 1, // 0x70 pqrstuvw - 31, 15, 81, -1, 103, -1, -1, -1, // 0x78 xyz | - -1, -1, -1, -1, -1, -1, -1, -1, // 0x80 - -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, // 0x90 - -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, // 0xA0 - -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, // 0xB0 - -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, // 0xC0 - -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, // 0xD0 - -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, // 0xE0 - -1, -1, -1, -1, -1, -1, -1, -1, - -1, 124, 12, 28, 108, 44, 92, 125, // 0xF0 F0...F7 - 13, 29, 109, 45, 93, -1, -1, -1 }; // 0xF8 F8...FC + 31, 15, 81, -1, 103, -1, -1, -1 }; // 0x78 xyz | + + private static final int PRE_F1_MASK = (124 << 8) & 0xFF00; private static final String[] cpuFlags = { "NZ", "Z", "NC", "C", "PO", "PE", "P", "M" }; @@ -179,7 +170,7 @@ public class KC85 extends EmuSys implements "INIME", "ZKOUT", "MENU", "V24OUT", "V24DUP" }; - private static Map resources = new HashMap(); + private static Map resources = new HashMap<>(); private static byte[] basic_x4_c000 = null; private static byte[] basic_c000 = null; @@ -196,7 +187,6 @@ public class KC85 extends EmuSys implements private static Boolean defaultEmulateVideoTiming = null; private int kcTypeNum; - private String propPrefix; private String basicFile; private String caosFileC; private String caosFileE; @@ -207,8 +197,7 @@ public class KC85 extends EmuSys implements private volatile boolean blinkState; private volatile boolean hiColorRes; private boolean charSetUnknown; - private boolean ctrlKeysDirect; - private boolean umlautsTo2Codes; + private boolean keyDirectToBuf; private boolean pasteFast; private boolean basicC000Enabled; private boolean caosC000Enabled; @@ -230,7 +219,7 @@ public class KC85 extends EmuSys implements private boolean audioOutPhaseL; private boolean audioOutPhaseR; private boolean audioInPhase; - private int audioInTStates; + private boolean soundStereo; private int ram8SegNum; private int keyNumStageBuf; private int keyNumStageNum; @@ -240,13 +229,14 @@ public class KC85 extends EmuSys implements private int keyShiftBitCnt; private int keyShiftValue; private int keyTStates; + private int lastIX; private volatile int tStatesLinePos0; private volatile int tStatesLinePos1; private volatile int tStatesLinePos2; private volatile int tStatesPerLine; private int lineTStateCounter; private int lineCounter; - private int basicSegAddr; + private int basicSegNum; private byte[] basicC000; private byte[] caosC000; private byte[] caosE000; @@ -274,9 +264,9 @@ public class KC85 extends EmuSys implements public KC85( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "" ); this.charSetUnknown = false; - this.ctrlKeysDirect = false; + this.keyDirectToBuf = false; this.pasteFast = false; this.keyboardFld = null; this.basicFile = null; @@ -316,8 +306,8 @@ public KC85( EmuThread emuThread, Properties props ) this.ramPixel1 = new byte[ 0x4000 ]; Z80CPU cpu = emuThread.getZ80CPU(); - this.ctc = new Z80CTC( "CTC (IO-Adressen 8Ch-8Fh)" ); - this.pio = new Z80PIO( "PIO (IO-Adressen 88h-8Bh)" ); + this.ctc = new Z80CTC( "CTC (E/A-Adressen 8Ch-8Fh)" ); + this.pio = new Z80PIO( "PIO (E/A-Adressen 88h-8Bh)" ); this.joyModule = null; this.d004 = null; this.d004RomProp = ""; @@ -325,11 +315,12 @@ public KC85( EmuThread emuThread, Properties props ) byte[] romBytes = null; this.d004RomProp = getD004RomProp( props ); if( (this.d004RomProp.length() > 5) - && this.d004RomProp.startsWith( "file:" ) ) + && this.d004RomProp.toUpperCase().startsWith( "FILE:" ) ) { try { romBytes = EmuUtil.readFile( new File( this.d004RomProp.substring( 5 ) ), + true, 16 * 1024 ); } catch( IOException ex ) { @@ -371,8 +362,7 @@ public KC85( EmuThread emuThread, Properties props ) this.modules = createModules( props ); if( this.modules != null ) { try { - java.util.List iSources - = new ArrayList(); + java.util.List iSources = new ArrayList<>(); iSources.add( this.ctc ); iSources.add( this.pio ); for( int i = 0; i < this.modules.length; i++ ) { @@ -411,6 +401,7 @@ public KC85( EmuThread emuThread, Properties props ) this.colors = new Color[ basicRGBValues.length ]; this.charRecognizer = new KC85CharRecognizer(); applySettings( props ); + applySoundStereo( props ); z80MaxSpeedChanged( cpu ); if( !isReloadExtROMsOnPowerOnEnabled( props ) ) { loadROMs( props ); @@ -433,7 +424,7 @@ public static FloppyDiskInfo[] getAvailableFloppyDisks() */ public static boolean getDefaultEmulateVideoTiming() { - return (Main.availableProcessors() >= 2); + return (Runtime.getRuntime().availableProcessors() >= 2); } @@ -441,7 +432,13 @@ public static int getDefaultSpeedKHz( Properties props ) { String sysName = EmuUtil.getProperty( props, "jkcemu.system" ); return sysName.equals( "KC85/4" ) || sysName.equals( "KC85/5" ) ? - 1773 : 1750; + DEFAULT_SPEED_4_KHZ : DEFAULT_SPEED_2_KHZ; + } + + + public static boolean getDefaultSwapKeyCharCase() + { + return true; } @@ -487,7 +484,7 @@ public void z80CTCUpdate( Z80CTC ctc, int timerNum ) break; case 2: - this.blinkState = !this.blinkState; + this.blinkState = !this.blinkState; if( this.screenBufUsed != null ) { this.screenDirty = true; } else { @@ -504,7 +501,7 @@ public void z80CTCUpdate( Z80CTC ctc, int timerNum ) @Override public void z80MaxSpeedChanged( Z80CPU cpu ) { - int f = (this.kcTypeNum < 4 ? 1750 : 1773); + int f = (this.kcTypeNum < 4 ? DEFAULT_SPEED_2_KHZ : DEFAULT_SPEED_4_KHZ); int t = cpu.getMaxSpeedKHz() * 112 / f; this.tStatesLinePos0 = (int) Math.round( t * 32.0 / 112.0 ); this.tStatesLinePos1 = (int) Math.round( t * 64.0 / 112.0 ); @@ -578,20 +575,10 @@ public void z80TStatesProcessed( Z80CPU cpu, int tStates ) * indem zyklisch geschaut wird, ob sich die Eingangsphase geaendert hat. * Wenn ja, wird ein Impuls an der Strobe-Leitung der zugehoerigen PIO * emuliert. - * Auf der einen Seite soll das Audiosystem nicht zu oft abgefragt - * werden. - * Auf der anderen Seite soll aber die Zykluszeit nicht so gross sein, - * dass die Genauigkeit der Zeitmessung kuenstlich verschlechert wird. - * Aus diesem Grund werden genau soviele Taktzyklen abgezaehlt, - * wie auch der Vorteile der CTC mindestens zaehlen muss. */ - this.audioInTStates += tStates; - if( this.audioInTStates > 15 ) { - this.audioInTStates = 0; - if( this.emuThread.readAudioPhase() != this.audioInPhase ) { - this.audioInPhase = !this.audioInPhase; - this.pio.strobePortA(); - } + if( this.emuThread.readAudioPhase() != this.audioInPhase ) { + this.audioInPhase = !this.audioInPhase; + this.pio.strobePortA(); } /* @@ -672,6 +659,7 @@ else if( this.keyShiftBitCnt < 8 ) { public void appendStatusHTMLTo( StringBuilder buf, Z80CPU cpu ) { if( cpu == this.emuThread.getZ80CPU() ) { + int pioA = this.pio.fetchOutValuePortA( false ); // Status Grundgeraet buf.append( "

    " ); @@ -702,7 +690,7 @@ public void appendStatusHTMLTo( StringBuilder buf, Z80CPU cpu ) } else { if( this.kcTypeNum > 4 ) { buf.append( "Bank " ); - buf.append( (this.basicSegAddr >> 12) & 0x03 ); + buf.append( this.basicSegNum ); buf.append( (char) '\u0020' ); } buf.append( "ein" ); @@ -757,7 +745,8 @@ public void appendStatusHTMLTo( StringBuilder buf, Z80CPU cpu ) if( this.kcTypeNum >= 4 ) { buf.append( "Bank " ); buf.append( this.screen1Enabled ? "1" : "0" ); - buf.append( (char) '\u0020' ); + buf.append( this.ramColorEnabled ? " Farb" : " Pixel" ); + buf.append( "ebene " ); } buf.append( "ein" ); } else { @@ -768,11 +757,23 @@ public void appendStatusHTMLTo( StringBuilder buf, Z80CPU cpu ) buf.append( "Bildausgabe:IRM-Bank " ); buf.append( this.screen1Visible ? "1" : "0" ); buf.append( "\n" - + "HIRES-Farbmodus:" ); + + "Hohe Farbauflösung:" ); buf.append( this.hiColorRes ? "ein" : "aus" ); buf.append( "\n" ); } - buf.append( "\n" ); + buf.append( "Blinken:" ); + buf.append( this.blinkEnabled ? "ein" : "aus" ); + buf.append( "\n" + + "K OUT:" ); + buf.append( (pioA >> 4) & 0x01 ); + buf.append( "\n" + + "Motorschaltspannung:" ); + buf.append( (pioA & 0x40) != 0 ? "ein" : "aus" ); + buf.append( "\n" + + "System-LED:" ); + buf.append( (pioA & 0x20) != 0 ? "ein" : "aus" ); + buf.append( "\n" + + "\n" ); // Status der Module AbstractKC85Module[] modules = this.modules; @@ -841,12 +842,9 @@ public void applySettings( Properties props ) } else { this.screenBufUsed = null; } - this.umlautsTo2Codes = EmuUtil.getBooleanProperty( - props, - this.propPrefix + "umlauts_to_2_keycodes", - false ); createColors( props ); applyPasteFast( props ); + applySoundStereo( props ); if( this.d004 != null ) { this.d004.applySettings( props ); } @@ -912,78 +910,67 @@ public boolean canApplySettings( Properties props ) rv = this.d004.canApplySettings( props ); } if( rv ) { - int nModules = 0; - AbstractKC85Module[] modules = this.modules; - if( modules != null ) { - nModules = modules.length; - if( hasD004 && (nModules > 0) ) { - --nModules; - } - } + /* + * Module vergleichen, + * Dabei M035x4 in 4 x M035 umwandeln + */ if( props != null ) { int nRemain = EmuUtil.getIntProperty( props, this.propPrefix + "module.count", 0 ); - if( nRemain != nModules ) { - rv = false; - } - if( rv && (nRemain > 0) && (modules != null) ) { - java.util.List modEntries - = new ArrayList( nRemain + 1 ); - if( props != null ) { - boolean loop = true; - int slot = 8; - while( loop && (slot < 0x100) && (nRemain > 0) ) { - loop = false; + java.util.List modEntries = new ArrayList<>( nRemain + 1 ); + boolean loop = true; + int slot = 8; + while( loop && (nRemain > 0) && (slot < 0x100) ) { + loop = false; - String prefix = String.format( - "%smodule.%02X.", - this.propPrefix, - slot ); - String modName = props.getProperty( prefix + "name" ); - if( modName != null ) { - if( !modName.isEmpty() ) { - String typeByte = props.getProperty( prefix + "typebyte" ); - String fileName = props.getProperty( prefix + "file" ); - - String[] modEntry = new String[ 3 ]; - modEntry[ 0 ] = modName; - modEntry[ 1 ] = typeByte; - modEntry[ 2 ] = fileName; - modEntries.add( modEntry ); - loop = true; - } + String prefix = String.format( + "%smodule.%02X.", + this.propPrefix, + slot ); + String modName = props.getProperty( prefix + "name" ); + if( modName != null ) { + if( !modName.isEmpty() ) { + String typeByte = props.getProperty( prefix + "typebyte" ); + String fileName = props.getProperty( prefix + "file" ); + int nItems = 1; + if( modName.equals( "M035x4" ) ) { + modName = "M035"; + nItems = 4; } - if( loop ) { - slot += 4; - --nRemain; + for( int i = 0; i < nItems; i++ ) { + String[] modEntry = new String[ 4 ]; + modEntry[ 0 ] = String.valueOf( slot + i ); + modEntry[ 1 ] = modName; + modEntry[ 2 ] = typeByte; + modEntry[ 3 ] = fileName; + modEntries.add( modEntry ); } - } - if( hasD004 && (this.d004 != null) ) { - String[] modEntry = new String[ 3 ]; - modEntry[ 0 ] = this.d004.getModuleName(); - modEntry[ 1 ] = this.d004.getTypeByteText(); - modEntry[ 2 ] = null; - modEntries.add( modEntry ); + --nRemain; + slot += 4; + loop = true; } } - int n = modEntries.size(); - if( modules.length == n ) { - for( int i = 0; i < n; i++ ) { + } + if( hasD004 && (this.d004 != null) ) { + String[] modEntry = new String[ 4 ]; + modEntry[ 0 ] = String.valueOf( this.d004.getSlot() ); + modEntry[ 1 ] = this.d004.getModuleName(); + modEntry[ 2 ] = this.d004.getTypeByteText(); + modEntry[ 3 ] = null; + modEntries.add( modEntry ); + } + AbstractKC85Module[] modules = this.modules; + if( modules != null ) { + if( modEntries.size() == modules.length ) { + for( int i = 0; i < modules.length; i++ ) { String[] modEntry = modEntries.get( i ); - if( modEntry == null ) { - rv = false; - break; - } - if( modEntry.length < 3 ) { - rv = false; - break; - } if( !modules[ i ].equalsModule( modEntry[ 0 ], modEntry[ 1 ], - modEntry[ 2 ] ) ) + modEntry[ 2 ], + modEntry[ 3 ] ) ) { rv = false; break; @@ -992,10 +979,10 @@ public boolean canApplySettings( Properties props ) } else { rv = false; } - } - } else { - if( nModules > 0 ) { - rv = false; + } else { + if( !modEntries.isEmpty() ) { + rv = false; + } } } } @@ -1064,6 +1051,13 @@ public void die() } + @Override + public int getBasicMemByte( int addr ) + { + return getMemByteInternal( addr, false, false, false ); + } + + @Override public int getBorderColorIndex() { @@ -1192,7 +1186,7 @@ public CharRaster getCurScreenCharRaster() @Override public FloppyDiskFormat getDefaultFloppyDiskFormat() { - return FloppyDiskFormat.FMT_780K; + return FloppyDiskFormat.FMT_780K_I3; } @@ -1306,7 +1300,7 @@ public int getSupportedJoystickCount() @Override public boolean getSwapKeyCharCase() { - return true; + return getDefaultSwapKeyCharCase(); } @@ -1351,11 +1345,14 @@ public boolean keyPressed( boolean ctrlDown, boolean shiftDown ) { - boolean rv = false; - int keyNum = -1; + boolean rv = false; + int pcKeyTabAddr = getKeyTabAddrIfPCMode(); + boolean pcMode = (pcKeyTabAddr >= 0); + int ch = -1; + int keyNum = -1; switch( keyCode ) { case KeyEvent.VK_BACK_SPACE: - keyNum = 6; + keyNum = (pcMode ? 40 : 24); break; case KeyEvent.VK_LEFT: @@ -1374,8 +1371,20 @@ public boolean keyPressed( keyNum = (shiftDown ? 121 : 120); break; + case KeyEvent.VK_END: + keyNum = 123; + break; + + case KeyEvent.VK_PAGE_DOWN: + keyNum = 119; + break; + + case KeyEvent.VK_PAGE_UP: + keyNum = 121; + break; + case KeyEvent.VK_ENTER: - keyNum = 126; + keyNum = (shiftDown ? 127 : 126); break; case KeyEvent.VK_HOME: @@ -1387,33 +1396,37 @@ public boolean keyPressed( break; case KeyEvent.VK_TAB: - if( this.ctrlKeysDirect ) { - pasteCharToKeyBuffer( '\u0009', false ); - rv = true; - } else { - keyNum = 122; - } + keyNum = (pcMode ? 71 : 115); break; case KeyEvent.VK_ESCAPE: - if( this.ctrlKeysDirect ) { - pasteCharToKeyBuffer( '\u001B', false ); - rv = true; - } else { - keyNum = 77; - } + keyNum = (pcMode ? 125 : 77); break; case KeyEvent.VK_DELETE: - keyNum = 40; + if( pcMode ) { + if( ctrlDown ) { + keyNum |= PRE_F1_MASK; + } else { + keyNum = 24; + } + } else { + keyNum = (shiftDown ? 41 : 40); + } break; case KeyEvent.VK_SPACE: keyNum = (shiftDown ? 71 : 70); + if( pcMode && ctrlDown ) { + keyNum |= PRE_F1_MASK; + } break; case KeyEvent.VK_F1: keyNum = (shiftDown ? 125 : 124); + if( pcMode ) { + ch = (shiftDown ? 0xF7 : 0xF1); + } break; case KeyEvent.VK_F2: @@ -1426,32 +1439,69 @@ public boolean keyPressed( case KeyEvent.VK_F4: keyNum = (shiftDown ? 109 : 108); + if( pcMode && ctrlDown ) { + keyNum |= PRE_F1_MASK; + } break; case KeyEvent.VK_F5: keyNum = (shiftDown ? 45 : 44); + if( pcMode && ctrlDown ) { + keyNum |= PRE_F1_MASK; + } break; case KeyEvent.VK_F6: keyNum = (shiftDown ? 93 : 92); break; - case KeyEvent.VK_F7: // BRK - keyNum = 60; + case KeyEvent.VK_F7: // BRK + keyNum = (shiftDown ? 61 : 60); break; - case KeyEvent.VK_F8: // STOP + case KeyEvent.VK_F8: // STOP keyNum = (shiftDown ? 77 : 76); break; - case KeyEvent.VK_F9: // CLR + case KeyEvent.VK_F9: // CLR keyNum = (shiftDown ? 25 : 24); break; + + case KeyEvent.VK_F11: // LIST + keyNum = 86; + break; + + case KeyEvent.VK_F12: // RUN + keyNum = 87; + break; + + case KeyEvent.VK_1: // 1 + if( pcMode && ctrlDown ) { + keyNum = PRE_F1_MASK | 52; // F1 7 + } + break; + + case KeyEvent.VK_2: // 2 + if( pcMode && ctrlDown ) { + keyNum = PRE_F1_MASK | 68; // F1 8 + } } if( keyNum >= 0 ) { - this.keyNumPressed = keyNum; - rv = true; - updKeyboardFld(); + boolean done = false; + if( this.keyDirectToBuf ) { + if( ch <= 0 ) { + ch = keyNumToChar( keyNum, pcKeyTabAddr, ctrlDown ); + } + if( ch > 0 ) { + pasteCharToKeyBuffer( (char) ch, false ); + done = true; + } + } + if( !done ) { + setKeyNumPressed( keyNum ); + } + updKeyboardFld( keyNum ); + rv = true; } return rv; } @@ -1461,25 +1511,85 @@ public boolean keyPressed( public void keyReleased() { this.keyNumPressed = -1; - updKeyboardFld(); + updKeyboardFld( -1 ); } @Override public boolean keyTyped( char ch ) { - boolean rv = false; - if( this.ctrlKeysDirect && (ch > 0) && (ch < 0x20) ) { - pasteCharToKeyBuffer( ch, false ); + boolean rv = false; + int pcKeyTabAddr = getKeyTabAddrIfPCMode(); + boolean pcMode = (pcKeyTabAddr >= 0); + int keyNum = charToKeyNum( ch, pcMode ); + if( keyNum >= 0 ) { + if( this.keyDirectToBuf ) { + int ch1 = ch; + if( keyNum == (keyNum & 0xFF) ) { + ch1 = keyNumToChar( keyNum, pcKeyTabAddr, ch < '\u0020' ); + } else { + if( ch > 0 ) { + ch1 = TextUtil.umlautToISO646DE( ch ); + } + } + if( ch1 > 0 ) { + pasteCharToKeyBuffer( (char) ch1, false ); + } + } + if( !rv ) { + setKeyNumPressed( keyNum ); + } rv = true; } else { - rv = keyTypedInternal( ch ); - updKeyboardFld(); + if( (ch > 0) && this.keyDirectToBuf ) { + pasteCharToKeyBuffer( TextUtil.umlautToISO646DE( ch ), false ); + rv = true; + } } + updKeyboardFld( keyNum ); return rv; } + /* + * KC-BASIC-Programme sollen bei Adressen ab 8000h nicht in den + * IRM geladen werden, auch wenn dieser gerade eingeblendet ist. + */ + @Override + public void loadIntoMem( + int begAddr, + byte[] data, + int idx, + int len, + FileFormat fileFmt, + int fileType ) + { + if( data != null ) { + boolean irmEnabled = this.irmEnabled; + if( fileFmt != null ) { + if( fileFmt.equals( FileFormat.KCB ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_DATA ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_ASC ) + || fileFmt.equals( FileFormat.KCBASIC_HEAD_PRG ) + || fileFmt.equals( FileFormat.KCBASIC_HEAD_DATA ) + || fileFmt.equals( FileFormat.KCBASIC_HEAD_ASC ) + || fileFmt.equals( FileFormat.KCBASIC_PRG ) ) + { + irmEnabled = false; + } + } + int n = len; + int dst = begAddr; + while( (idx < data.length) && (dst < 0x10000) && (n > 0) ) { + setMemByteInternal( dst++, data[ idx++ ], irmEnabled ); + --n; + } + updSysCells( begAddr, len, fileFmt, fileType ); + } + } + + @Override public void loadIntoSecondSystem( byte[] loadData, @@ -1491,20 +1601,10 @@ public void loadIntoSecondSystem( } - /* - * Dateien sollen nicht in den IRM geladen werden. - */ - @Override - public void loadMemByte( int addr, int value ) - { - setMemByteInternal( addr, value, false ); - } - - @Override public void openBasicProgram() { - SourceUtil.openKCBasicStyleProgram( + SourceUtil.openKCBasicProgram( this.screenFrm, getKCBasicBegAddr(), basicTokens ); @@ -1515,12 +1615,12 @@ public void openBasicProgram() protected boolean pasteChar( char ch ) { boolean rv = false; - if( this.pasteFast ) { + if( this.pasteFast || this.keyDirectToBuf ) { if( (ch > 0) && (ch <= 0xFF) ) { if( ch == '\n' ) { ch = '\r'; } - pasteCharToKeyBuffer( ch, true ); + pasteCharToKeyBuffer( TextUtil.umlautToISO646DE( ch ), true ); rv = true; } } else { @@ -1531,7 +1631,7 @@ protected boolean pasteChar( char ch ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; switch( port & 0xFF ) { @@ -1548,11 +1648,11 @@ public int readIOByte( int port ) break; case 0x88: - rv = this.pio.readPortA(); + rv = this.pio.readDataA(); break; case 0x89: - rv = this.pio.readPortB(); + rv = this.pio.readDataB(); break; case 0x8A: @@ -1567,13 +1667,13 @@ public int readIOByte( int port ) case 0x8D: case 0x8E: case 0x8F: - rv = this.ctc.read( port & 0x03 ); + rv = this.ctc.read( port & 0x03, tStates ); break; default: if( this.modules != null ) { for( int i = 0; i < this.modules.length; i++ ) { - int v = this.modules[ i ].readIOByte( port ); + int v = this.modules[ i ].readIOByte( port, tStates ); if( v >= 0 ) { rv = v; break; @@ -1603,8 +1703,8 @@ public int reassembleSysCall( int colRemark ) { int rv = 0; - if( memory == this.emuThread ) { - int ix = this.emuThread.getZ80CPU().getRegIX(); + int ix = getRegIX(); + if( (ix >= 0) && (memory == this.emuThread) ) { int prolog = memory.getMemByte( ix + 9, false ); int b = memory.getMemByte( addr, false ); if( (addr <= 0xFFFE) && ((b == 0x7F) || (b == prolog)) ) { @@ -1751,6 +1851,16 @@ public int reassembleSysCall( } buf.append( (char) '\n' ); rv = 4; + if( idx == 0x23 ) { + // hinter OSTR folgenden String reassemblieren + rv += reassembleStringTerm0( + memory, + addr + rv, + buf, + sourceOnly, + colMnemonic, + colArgs ); + } } } } @@ -1813,6 +1923,16 @@ public int reassembleSysCall( buf.append( sysCallNames[ idx ] ); buf.append( (char) '\n' ); rv = 3; + if( idx == 0x23 ) { + // hinter OSTR folgenden String reassemblieren + rv += reassembleStringTerm0( + memory, + addr + rv, + buf, + sourceOnly, + colMnemonic, + colArgs ); + } break; } idx++; @@ -1850,7 +1970,7 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) if( resetLevel == EmuThread.ResetLevel.COLD_RESET) { coldReset = true; } - this.basicSegAddr = 0x3000; + this.basicSegNum = 0; this.basicC000Enabled = false; this.caosC000Enabled = false; this.caosE000Enabled = true; @@ -1876,19 +1996,12 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) this.audioOutPhaseL = false; this.audioOutPhaseL = false; this.audioInPhase = this.emuThread.readAudioPhase(); - this.audioInTStates = 0; this.lineTStateCounter = 0; this.lineCounter = 0; - this.keyNumStageBuf = 0; - this.keyNumStageNum = 0; - this.keyNumStageMillis = -1; - this.keyNumPressed = -1; - this.keyNumProcessing = -1; - this.keyShiftBitCnt = 0; - this.keyShiftValue = 0; - this.keyTStates = 0; + this.lastIX = -1; this.ctc.reset( coldReset ); this.pio.reset( coldReset ); + resetKeyInput(); updAudioOut(); } @@ -1896,9 +2009,14 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) @Override public void saveBasicProgram() { - SourceUtil.saveKCBasicStyleProgram( - this.screenFrm, - getKCBasicBegAddr() ); + SourceUtil.saveKCBasicProgram( this.screenFrm, getKCBasicBegAddr() ); + } + + + @Override + public boolean setBasicMemByte( int addr, int value ) + { + return setMemByteInternal( addr, value, false ); } @@ -1914,8 +2032,9 @@ public void setFloppyDiskDrive( int idx, FloppyDiskDrive drive ) public void setJoystickAction( int joyNum, int actionMask ) { KC85JoystickModule joyModule = this.joyModule; - if( joyModule != null ) + if( joyModule != null ) { joyModule.setJoystickAction( joyNum, actionMask ); + } } @@ -1992,12 +2111,19 @@ public boolean supportsSaveBasic() } + @Override + public boolean supportsStereoSound() + { + return this.soundStereo; + } + + @Override public void updSysCells( - int begAddr, - int len, - Object fileFmt, - int fileType ) + int begAddr, + int len, + FileFormat fileFmt, + int fileType ) { SourceUtil.updKCBasicSysCells( this.emuThread, @@ -2008,7 +2134,7 @@ public void updSysCells( } @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { int m = 0; switch( port & 0xFF ) { @@ -2045,13 +2171,13 @@ public void writeIOByte( int port, int value ) if( this.kcTypeNum > 3 ) { this.ram4Enabled = ((value & 0x01) != 0); this.ram4Writeable = ((value & 0x02) != 0); - this.basicSegAddr = (value << 8) & 0x6000; + this.basicSegNum = (~value >> 5) & 0x03; this.caosC000Enabled = ((value & 0x80) != 0); } break; case 0x88: - this.pio.writePortA( value ); + this.pio.writeDataA( value ); m = this.pio.fetchOutValuePortA( false ); this.caosE000Enabled = ((m & 0x01) != 0); this.ram0Enabled = ((m & 0x02) != 0); @@ -2079,7 +2205,7 @@ public void writeIOByte( int port, int value ) break; case 0x89: - this.pio.writePortB( value ); + this.pio.writeDataB( value ); m = this.pio.fetchOutValuePortB( false ); if( this.kcTypeNum > 3 ) { if( (m & 0x01) == 0 ) { @@ -2105,13 +2231,13 @@ public void writeIOByte( int port, int value ) case 0x8D: case 0x8E: case 0x8F: - this.ctc.write( port & 0x03, value ); + this.ctc.write( port & 0x03, value, tStates ); break; default: if( this.modules != null ) { for( int i = 0; i < this.modules.length; i++ ) { - if( this.modules[ i ].writeIOByte( port, value ) ) { + if( this.modules[ i ].writeIOByte( port, value, tStates ) ) { break; } } @@ -2124,9 +2250,9 @@ public void writeIOByte( int port, int value ) private void applyPasteFast( Properties props ) { - this.ctrlKeysDirect = EmuUtil.getBooleanProperty( + this.keyDirectToBuf = EmuUtil.getBooleanProperty( props, - this.propPrefix + "control_keys.direct", + this.propPrefix + "keys.direct_to_buffer", false ); this.pasteFast = EmuUtil.getBooleanProperty( props, @@ -2135,6 +2261,73 @@ private void applyPasteFast( Properties props ) } + private void applySoundStereo( Properties props ) + { + boolean state = EmuUtil.getBooleanProperty( + props, + this.propPrefix + "sound.stereo", + false ); + if( state != this.soundStereo ) { + this.soundStereo = state; + AudioOut audioOut = this.emuThread.getAudioOut(); + if( audioOut != null ) { + audioOut.fireReopenLine(); + } + } + } + + + /* + * Die Methode wandelt ein Zeichencode in einen Tastencode um. + * Im PC-Mode koenen 2-Byte-Tastencodes zurueckgegeben werden. + */ + private int charToKeyNum( char ch, boolean pcMode ) + { + int keyNum = -1; + if( pcMode ) { + if( (ch > '\u0000') && (ch < '\u0020') ) { + keyNum = PRE_F1_MASK | char2KeyNum[ ch + 0x40 ]; + } else { + switch( ch ) { + case '[': + case '\u00C4': // Ae + keyNum = PRE_F1_MASK | 100; + break; + case '\\': + case '\u00D6': // Oe + keyNum = PRE_F1_MASK | 36; + break; + case ']': + case '\u00DC': // Ue + keyNum = PRE_F1_MASK | 84; + break; + case '{': + case '\u00E4': // ae + keyNum = PRE_F1_MASK | 116; + break; + case '\u00F6': // oe + keyNum = PRE_F1_MASK | 4; + break; + case '}': + case '\u00FC': // ue + keyNum = PRE_F1_MASK | 20; + break; + case '~': + case '\u00DF': // sz + keyNum = PRE_F1_MASK | 42; + break; + } + } + } + if( keyNum < 0 ) { + if( (ch > 0) && (ch < char2KeyNum.length) ) { + keyNum = char2KeyNum[ ch ]; + } + } + return keyNum; + } + + private void copyPixelsToCharRecognizer() { if( this.kcTypeNum > 3 ) { @@ -2202,7 +2395,7 @@ private AbstractKC85Module[] createModules( Properties props ) this.propPrefix + "module.count", 0 ); if( nRemain > 0 ) { - modules = new ArrayList(); + modules = new ArrayList<>(); int slot = 8; boolean loop = true; while( loop && (slot < 0x100) && (nRemain > 0) ) { @@ -2257,7 +2450,6 @@ else if( moduleName.equals( "M025" ) ) { modules.add( new M025( slot, - this.emuThread, typeByte, this.screenFrm, props.getProperty( prefix + "file" ) ) ); @@ -2287,7 +2479,6 @@ else if( moduleName.equals( "M028" ) ) { modules.add( new M028( slot, - this.emuThread, typeByte, this.screenFrm, props.getProperty( prefix + "file" ) ) ); @@ -2334,10 +2525,39 @@ else if( moduleName.equals( "M040" ) ) { this.screenFrm, props.getProperty( prefix + "file" ) ) ); } + else if( moduleName.equals( "M045" ) ) { + modules.add( + new M045( + slot, + this.screenFrm, + props.getProperty( prefix + "file" ) ) ); + } + else if( moduleName.equals( "M046" ) ) { + modules.add( + new M046( + slot, + this.screenFrm, + props.getProperty( prefix + "file" ) ) ); + } + else if( moduleName.equals( "M047" ) ) { + modules.add( + new M047( + slot, + this.screenFrm, + props.getProperty( prefix + "file" ) ) ); + } + else if( moduleName.equals( "M048" ) ) { + modules.add( + new M048( + slot, + this.screenFrm, + props.getProperty( prefix + "file" ) ) ); + } else if( moduleName.equals( "M052" ) ) { modules.add( new M052( slot, this.emuThread.getScreenFrm(), + this.emuThread.getFileTimesViewFactory(), props.getProperty( this.propPrefix + "rom.m052.file" ) ) ); } else if( moduleName.equals( "M120" ) ) { @@ -2438,6 +2658,66 @@ private int getKCBasicBegAddr() } + /* + * Die Methode liefert die Anfangsadresse der Tastaturtabelle, + * wenn sich der KC85 im PC-Mode befindet, + * d.h. wenn gerade MicroDOS oder ein anderes CP/M-kompatibles + * Betriebssystem in der D004 ausgefuehrt werden. + * Ist der PC-Mode nicht aktiv, liefert die Methode -1. + */ + private int getKeyTabAddrIfPCMode() + { + int rv = -1; + if( this.d004 != null ) { + if( this.d004.isRunning() ) { + /* + * Wenn die D004 laeuft, kann trotzdem der CAOS-Modus aktiv sein. + * Zur Entscheidungsfindung wird der Interrupt-Vektor + * von PIO Port B geprueft. + * Wenn dieser in den ROM zeigt, muss CAOS aktiv sein. + */ + boolean caos = false; + int iVector = this.pio.getInterruptVectorPortB(); + int ptrAddr = -1; + switch( this.emuThread.getZ80CPU().getInterruptMode() ) { + case 0: + if( getMemByte( 0x0038, false ) == 0xC3 ) { + ptrAddr = 0x0039; + } + break; + case 1: + if( (iVector & 0xC7) == 0xC7 ) { // RST-Befehl? + int rstAddr = iVector & 0x0038; + if( getMemByte( rstAddr, false ) == 0xC3 ) { + ptrAddr = rstAddr + 1; + } + } + break; + case 2: + ptrAddr = ((this.emuThread.getZ80CPU().getRegI() << 8) & 0xFF00) + | (iVector & 0x00FF); + break; + } + if( ptrAddr >= 0 ) { + if( getMemWord( ptrAddr ) >= 0xE000 ) { + caos = true; + } + } + if( !caos ) { + /* + * Bei MicroDOS und ML-DOS steht der Zeiger + * auf die Tastaturtabelle in der D004 + * auf Adresse FFB4h/FFB5h. + * Die Tastaturtabelle selbst liegt jedoch im Grundgeraet. + */ + rv = this.d004.getZ80Memory().getMemWord( 0xFFB4 ); + } + } + } + return rv; + } + + private int getMemByteInternal( int addr, boolean irmEnabled, @@ -2446,14 +2726,17 @@ private int getMemByteInternal( { addr &= 0xFFFF; - int rv = -1; + int rv = 0xFF; + boolean done = false; if( (addr >= 0) && (addr < 0x4000) ) { if( this.ram0Enabled ) { - rv = this.emuThread.getRAMByte( addr ); + rv = this.emuThread.getRAMByte( addr ); + done = true; } } else if( (addr >= 0x4000) && (addr < 0x8000) ) { if( this.ram4Enabled ) { - rv = this.emuThread.getRAMByte( addr ); + rv = this.emuThread.getRAMByte( addr ); + done = true; } } else if( (addr >= 0x8000) && (addr < 0xC000) ) { int idx = addr - 0x8000; @@ -2475,6 +2758,7 @@ private int getMemByteInternal( rv = (int) a[ idx ] & 0xFF; } } + done = true; } else if( this.ram8Enabled && (this.ram8 != null) ) { if( this.kcTypeNum == 4 ) { if( (this.ram8SegNum & 0x01) != 0 ) { @@ -2495,22 +2779,48 @@ private int getMemByteInternal( } } } + done = true; } } else if( (addr >= 0xC000) && (addr < 0xE000) ) { int idx = addr - 0xC000; if( (this.kcTypeNum >= 4) && this.caosC000Enabled ) { if( this.caosC000 != null ) { if( idx < this.caosC000.length ) { - rv = (int) this.caosC000[ idx ] & 0xFF; + rv = (int) this.caosC000[ idx ] & 0xFF; + } + /* + * Wenn beim KC85/4 die ROM_C-Datei > 4K ist, + * dann volle 8K emulieren. + */ + if( (addr < 0xD000) + || (this.caosC000.length > 0x1000) + || (this.kcTypeNum > 4) ) + { + done = true; + } + } else { + if( (addr < 0xD000) || (this.kcTypeNum > 4) ) { + done = true; } } } else if( this.basicC000Enabled && (this.basicC000 != null) ) { if( (this.kcTypeNum > 4) && (this.basicC000.length > 0x2000) ) { - idx += this.basicSegAddr; + switch( this.basicSegNum ) { + case 0: + idx += 0x6000; + break; + case 1: + idx += 0x2000; + break; + case 2: + idx += 0x4000; + break; + } } if( idx < this.basicC000.length ) { rv = (int) basicC000[ idx ] & 0xFF; } + done = true; } } else if( addr >= 0xE000 ) { if( this.caosE000Enabled ) { @@ -2529,9 +2839,12 @@ private int getMemByteInternal( } } } + if( (this.kcTypeNum >= 3) || ((addr & 0x0800) == 0) ) { + done = true; + } } } - if( (rv < 0) && (this.modules != null) ) { + if( !done && (this.modules != null) ) { for( int i = 0; i < this.modules.length; i++ ) { if( m1 && enabledWaitStates && (this.modules[ i ].getSlot() >= 0x10) ) @@ -2556,6 +2869,37 @@ private String getProperty( Properties props, String keyword ) } + /* + * IX zeigt auf bestimmte Systemzellen. + * Es koennte aber sein, dass das Anwendungsprogramm + * den Interrupt sperrt und das IX-Register temporaer + * anders verwendet. + * Aus diesem Grund wird das IX-Register nur dann ausgelesen, + * wenn der Interrupt nicht gesperrt ist. + * Da aber aus Performance-Gruenden auf eine Synchronisierung + * der CPU-Emulation verzichtet wird, + * wird der Interrupt-Status zur Sicherheit hier + * vor und nach dem Auslesen des IX-Status getestet. + * Ist der Interrupt gerade gesperrt, wird der letzte gelesene + * IX-Wert genommen. + * + * Rueckgabewert: + * >= 0: IX-Register + * -1: Register konnte nicht gelsen werden + */ + private int getRegIX() + { + Z80CPU cpu = this.emuThread.getZ80CPU(); + if( cpu.getIFF1() ) { + int ix = cpu.getRegIX(); + if( cpu.getIFF1() ) { + this.lastIX = ix; + } + } + return this.lastIX; + } + + private byte[] getResource( String resource ) { byte[] rv = null; @@ -2575,9 +2919,9 @@ private byte[] getResource( String resource ) * 2-Byte-Tastencodes werden in keyNumStageBuf zwischengespeichert, * damit daraus die Einzelcodes entsprechend folgender * zeitlicher Abfolge gelesen werden koennen: - * 1. zweimal erster Tastencode lesen + * 1. zweimal ersten Tastencode lesen * 2. zweimal kein Tastencode lesen - * 3. viermal zweiter Tastencode lesen + * 3. viermal zweiten Tastencode lesen * 4. etwas laengere Pause * Gesteuert wird das ganze durch Zustandsnummern in keyNumStageNum, * wobei der erste Zustand (erstes mal Senden des ersten Tastencode) @@ -2586,38 +2930,42 @@ private byte[] getResource( String resource ) private int getSingleKeyNum() { int rv = -1; - if( this.keyNumStageNum > 0 ) { - switch( this.keyNumStageNum ) { - case 1: - if( System.currentTimeMillis() < this.keyNumStageMillis ) { - this.keyNumStageNum++; // im Wartezustand verbleiben + if( this.keyDirectToBuf ) { + resetKeyInput(); + } else { + if( this.keyNumStageNum > 0 ) { + switch( this.keyNumStageNum ) { + case 1: + if( System.currentTimeMillis() < this.keyNumStageMillis ) { + this.keyNumStageNum++; // im Wartezustand verbleiben + } else { + this.keyNumStageMillis = -1L; + } + break; + case 2: + case 3: + case 4: + case 5: + rv = this.keyNumStageBuf & 0xFF; // 2. Tastencode + break; + case 8: + rv = (this.keyNumStageBuf >> 8) & 0xFF; // 1. Tastencode + break; + } + --this.keyNumStageNum; + } else { + int keyNum = this.keyNumPressed; + if( keyNum >= 0 ) { + if( (keyNum & 0xFF00) != 0 ) { + // 2-Byte-Tastencode + this.keyNumStageBuf = keyNum; + this.keyNumStageNum = 8; + this.keyNumStageMillis = System.currentTimeMillis() + 300L; + rv = (keyNum >> 8) & 0xFF; } else { - this.keyNumStageMillis = -1L; + // 1-Byte-Tastencode + rv = keyNum & 0xFF; } - break; - case 2: - case 3: - case 4: - case 5: - rv = this.keyNumStageBuf & 0xFF; // 2. Tastencode - break; - case 8: - rv = (this.keyNumStageBuf >> 8) & 0xFF; // 1. Tastencode - break; - } - --this.keyNumStageNum; - } else { - int keyNum = this.keyNumPressed; - if( keyNum >= 0 ) { - if( (keyNum & 0xFF00) != 0 ) { - // 2-Byte-Tastencode - this.keyNumStageBuf = keyNum; - this.keyNumStageNum = 8; - this.keyNumStageMillis = System.currentTimeMillis() + 300L; - rv = (keyNum >> 8) & 0xFF; - } else { - // 1-Byte-Tastencode - rv = keyNum & 0xFF; } } } @@ -2625,60 +2973,29 @@ private int getSingleKeyNum() } - /* - * Deutsche Umlaute, geschweifte und eckige Klammern, Backslash - * sowie Tilde koennen mit der originalen KC85-Tastatur - * nicht eingegeben werden, da es dafuer keine Tastencodes gibt. - * Aus diesem Grund werden diese Tasten je nach eingeschalteter Option - * in 2-Byte-Tastencodes entsprechend des MicroDOS-Modes der D005 gemappt. - */ - private boolean keyTypedInternal( char ch ) + private int keyNumToChar( int keyNum, int pcKeyTabAddr, boolean ctrlDown ) { - boolean rv = false; - if( this.umlautsTo2Codes ) { - rv = true; - switch( ch ) { - case '[': - case '\u00C4': // Ae - this.keyNumPressed = ((124 << 8) & 0xFF00) | 100; - break; - case '\\': - case '\u00D6': // Oe - this.keyNumPressed = ((124 << 8) & 0xFF00) | 36; - break; - case ']': - case '\u00DC': // Ue - this.keyNumPressed = ((124 << 8) & 0xFF00) | 84; - break; - case '{': - case '\u00E4': // ae - this.keyNumPressed = ((124 << 8) & 0xFF00) | 116; - break; - case '\u00F6': // oe - this.keyNumPressed = ((124 << 8) & 0xFF00) | 4; - break; - case '}': - case '\u00FC': // ue - this.keyNumPressed = ((124 << 8) & 0xFF00) | 20; - break; - case '~': - case '\u00DF': // sz - this.keyNumPressed = ((124 << 8) & 0xFF00) | 42; - break; - default: - rv = false; - } - } - if( !rv ) { - if( (ch > 0) && (ch < char2KeyNum.length) ) { - int keyNum = char2KeyNum[ ch ]; - if( keyNum >= 0 ) { - this.keyNumPressed = keyNum; - rv = true; + int ch = -1; + if( keyNum >= 0 ) { + int keyNum2 = keyNum & 0xFF; + if( pcKeyTabAddr >= 0 ) { + int tabIdx = (keyNum2 / 2) * 3; + if( ctrlDown ) { + // Ctrl-Ebene + tabIdx += 2; + } if( (keyNum2 % 2) != 0 ) { + // Shift-Ebene + tabIdx++; + } + ch = getMemByte( pcKeyTabAddr + tabIdx, false ); + } else { + int ix = getRegIX(); + if( ix >= 0 ) { + ch = getMemByte( getMemWord( ix + 14 ) + keyNum2, false ); } } } - return rv; + return ch; } @@ -2714,10 +3031,10 @@ private void loadROMs( Properties props ) this.basicC000 = null; if( this.kcTypeNum > 2 ) { this.basicFile = getProperty( props, "rom.basic.file" ); - this.basicC000 = readFile( - this.basicFile, - this.kcTypeNum > 4 ? 0x8000 : 0x2000, - "BASIC-ROM" ); + this.basicC000 = readROMFile( + this.basicFile, + this.kcTypeNum > 4 ? 0x8000 : 0x2000, + "BASIC-ROM" ); } if( this.basicC000 == null ) { this.basicC000 = getResource( resourceBasic ); @@ -2726,12 +3043,18 @@ private void loadROMs( Properties props ) // CAOS-ROM C this.caosFileC = null; this.caosC000 = null; - if( this.kcTypeNum > 3 ) { + if( this.kcTypeNum >= 4 ) { + /* + * Beim KC85/4 ist der ROM C normalerweise nur 4 KByte gross. + * Hier werden aber bis zu 8 KByte erlaubt, + * da der Emulator absichtlich auch die Moeglichkeit bietet, + * auf diese Art und Weise einen 8 KByte grossen ROM C zu emulieren. + */ this.caosFileC = getProperty( props, "rom.caos_c.file" ); - this.caosC000 = readFile( - this.caosFileC, - this.kcTypeNum < 5 ? 0x1000 : 0x2000, - "CAOS-ROM C" ); + this.caosC000 = readROMFile( + this.caosFileC, + 0x2000, + "CAOS-ROM C" ); } if( this.caosC000 != null ) { this.charSetUnknown = true; @@ -2741,10 +3064,10 @@ private void loadROMs( Properties props ) // CAOS-ROM E this.caosFileE = getProperty( props, "rom.caos_e.file" ); - this.caosE000 = readFile( - this.caosFileE, - this.kcTypeNum < 3 ? 0x0800 : 0x2000, - "CAOS-ROM E oder E+F" ); + this.caosE000 = readROMFile( + this.caosFileE, + this.kcTypeNum < 3 ? 0x0800 : 0x2000, + "CAOS-ROM E oder E+F" ); if( this.caosE000 != null ) { this.charSetUnknown = true; } else { @@ -2753,10 +3076,10 @@ private void loadROMs( Properties props ) // CAOS-ROM F this.caosFileF = getProperty( props, "rom.caos_f.file" ); - this.caosF000 = readFile( - this.caosFileF, - this.kcTypeNum < 3 ? 0x0800 : 0x1000, - "CAOS-ROM F" ); + this.caosF000 = readROMFile( + this.caosFileF, + this.kcTypeNum < 3 ? 0x0800 : 0x1000, + "CAOS-ROM F" ); if( this.caosF000 != null ) { this.charSetUnknown = true; } @@ -2791,24 +3114,103 @@ private void loadROMs( Properties props ) private void pasteCharToKeyBuffer( char ch, boolean forceWait ) { try { - Z80CPU cpu = emuThread.getZ80CPU(); - int ix = cpu.getRegIX(); - int n = 10; - while( (n > 0) && (getMemByte( ix + 8, false ) & 0x01) != 0 ) { - Thread.sleep( 10 ); - if( !forceWait ) { - --n; + int ix = getRegIX(); + if( ix >= 0 ) { + int n = 10; + while( (n > 0) && (getMemByte( ix + 8, false ) & 0x01) != 0 ) { + Thread.sleep( 10 ); + if( !forceWait ) { + --n; + } + } + if( n > 0 ) { + setMemByte( ix + 13, ch ); + setMemByte( ix + 8, getMemByte( ix + 8, false ) | 0x01 ); } - } - if( n > 0 ) { - setMemByte( ix + 13, ch ); - setMemByte( ix + 8, getMemByte( ix + 8, false ) | 0x01 ); } } catch( InterruptedException ex ) {} } + private int reassembleStringTerm0( + Z80MemView memory, + int addr, + StringBuilder buf, + boolean sourceOnly, + int colMnemonic, + int colArgs ) + { + int rv = 0; + int[] bBuf = new int[ 4 ]; + boolean loop = true; + do { + int n = 0; + while( n < bBuf.length ) { + int b = memory.getMemByte( addr + n, false ); + bBuf[ n++ ] = b; + if( b == 0 ) { + loop = false; + break; + } + } + int bol = buf.length(); + if( !sourceOnly ) { + buf.append( String.format( "%04X ", addr ) ); + for( int i = 0; i < n; i++ ) { + buf.append( String.format( " %02X", bBuf[ i ] ) ); + } + } + appendSpacesToCol( buf, bol, colMnemonic ); + buf.append( "DB" ); + appendSpacesToCol( buf, bol, colArgs ); + boolean quoted = false; + for( int i = 0; i < n; i++ ) { + int b = bBuf[ i ]; + if( (b >= 0x20) && (b < 0x7F) ) { + if( !quoted ) { + if( i > 0 ) { + buf.append( (char) ',' ); + } + buf.append( (char) '\'' ); + quoted = true; + } + buf.append( (char) b ); + } else { + if( quoted ) { + buf.append( (char) '\'' ); + quoted = false; + } + if( i > 0 ) { + buf.append( (char) ',' ); + } + buf.append( String.format( "%02XH", b ) ); + } + } + if( quoted ) { + buf.append( (char) '\'' ); + } + buf.append( (char) '\n' ); + addr += n; + rv += n; + } while( loop ); + return rv; + } + + + private void resetKeyInput() + { + this.keyNumStageBuf = 0; + this.keyNumStageNum = 0; + this.keyNumStageMillis = -1; + this.keyNumPressed = -1; + this.keyNumProcessing = -1; + this.keyShiftBitCnt = 0; + this.keyShiftValue = 0; + this.keyTStates = 0; + } + + private boolean setMemByteInternal( int addr, int value, boolean irmEnabled ) { addr &= 0xFFFF; @@ -2884,33 +3286,30 @@ private boolean setMemByteInternal( int addr, int value, boolean irmEnabled ) done = true; } } else if( (addr >= 0xC000) && (addr < 0xE000) ) { - int idx = addr - 0xC000; - if( this.caosC000Enabled && (this.caosC000 != null) ) { - if( idx < this.caosC000.length ) { - done = true; - } - } else if( this.basicC000Enabled && (this.basicC000 != null) ) { - if( idx < this.basicC000.length ) { - done = true; - } - } - } else if( addr >= 0xE000 ) { - if( this.caosE000Enabled ) { - if( ((this.kcTypeNum >= 3) || (addr < 0xF000)) - && (this.caosE000 != null) ) - { - int idx = addr - 0xE000; - if( idx < caosE000.length ) { + if( (this.kcTypeNum >= 4) && this.caosC000Enabled ) { + if( this.caosC000 != null ) { + /* + * Wenn beim KC85/4 die ROM-C_Datei > 4K ist, + * dann voll 8K emulieren. + */ + if( (addr < 0xD000) + || (this.caosC000.length > 0x1000) + || (this.kcTypeNum > 4) ) + { done = true; } - } - else if( this.caosF000 != null ) { - int idx = addr - 0xF000; - if( idx < caosF000.length ) { + } else { + if( (addr < 0xD000) || (this.kcTypeNum > 4) ) { done = true; } } } + } else if( addr >= 0xE000 ) { + if( this.caosE000Enabled + && ((this.kcTypeNum >= 3) || ((addr & 0x0800) == 0)) ) + { + done = true; + } } if( !done && (this.modules != null) ) { for( int i = 0; i < this.modules.length; i++ ) { @@ -2930,38 +3329,49 @@ else if( v > 1 ) { private void updAudioOut() { - if( this.emuThread.isSoundOutEnabled() ) { - int value = 0; - if( this.audioOutPhaseL == this.audioOutPhaseR ) { - value = (this.audioOutPhaseL ? 127 : -127); - int m = this.pio.fetchOutValuePortB( false ); - if( (m & 0x10) != 0 ) { - value = (value * 30) / 100; // 1.0 / (2.35 + 1.0) - } - if( (m & 0x08) != 0 ) { - value = (value * 46) / 100; // 2.0 / (2.35 + 2.0) - } - if( (m & 0x04) != 0 ) { - value = (value * 62) / 100; // 3.9 / (2.35 + 3.9) - } - if( (m & 0x02) != 0 ) { - value = (value * 78) / 100; // 8.2 / (2.35 + 8.2) - } - if( (this.kcTypeNum < 4) && ((m & 0x01) != 0) ) { - value = (value * 87) / 100; // 16.0 / (2.35 + 16.0) + AudioOut audioOut = this.emuThread.getAudioOut(); + if( audioOut != null ) { + if( this.emuThread.isSoundOutEnabled() ) { + if( this.soundStereo ) { + int leftValue = (this.audioOutPhaseL ? 0 : AudioOut.MAX_USED_VALUE); + audioOut.writeValue( + leftValue, + leftValue, + this.audioOutPhaseR ? 0 : AudioOut.MAX_USED_VALUE ); + } else { + int value = 0; + if( this.audioOutPhaseL == this.audioOutPhaseR ) { + value = (this.audioOutPhaseL ? 0 : AudioOut.MAX_USED_VALUE); + int m = this.pio.fetchOutValuePortB( false ); + if( (m & 0x10) != 0 ) { + value = (value * 30) / 100; // 1.0 / (2.35 + 1.0) + } + if( (m & 0x08) != 0 ) { + value = (value * 46) / 100; // 2.0 / (2.35 + 2.0) + } + if( (m & 0x04) != 0 ) { + value = (value * 62) / 100; // 3.9 / (2.35 + 3.9) + } + if( (m & 0x02) != 0 ) { + value = (value * 78) / 100; // 8.2 / (2.35 + 8.2) + } + if( (this.kcTypeNum < 4) && ((m & 0x01) != 0) ) { + value = (value * 87) / 100; // 16.0 / (2.35 + 16.0) + } + } + audioOut.writeValue( value, value, value ); } + } else { + audioOut.writePhase( this.audioOutPhaseL ); } - this.emuThread.writeAudioValue( (byte) value ); - } else { - this.emuThread.writeAudioPhase( this.audioOutPhaseL ); } } - private void updKeyboardFld() + private void updKeyboardFld( int keyNum ) { if( this.keyboardFld != null ) - this.keyboardFld.updKeySelection( this.keyNumPressed ); + this.keyboardFld.updKeySelection( keyNum ); } diff --git a/src/jkcemu/emusys/KCcompact.java b/src/jkcemu/emusys/KCcompact.java index 31c1714..84f0d5a 100644 --- a/src/jkcemu/emusys/KCcompact.java +++ b/src/jkcemu/emusys/KCcompact.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -38,7 +38,8 @@ public class KCcompact extends EmuSys implements private static final FloppyDiskInfo[] availableFloppyDisks = { new FloppyDiskInfo( "/disks/kccompact/kccmicrodos.dump.gz", - "KC compact MicroDOS Systemdiskette" ) }; + "KC compact MicroDOS Systemdiskette", + 2, 2048, true ) }; /* * Diese Tabelle mappt die unteren 4 Bits des Farbpalettenwertes @@ -129,15 +130,18 @@ public class KCcompact extends EmuSys implements private int regColorNum; private int borderColorIdx; private int romSelect; + private String osFile; + private String basicFile; private String fdcROMFile; private byte[] fdcROMBytes; + private byte[] osBytes; + private byte[] basicBytes; private byte[] screenBuf; private byte[] ramExt; private int[] ram16KOffs; private int[] keyboardMatrix; private int keyboardIdx; private int keyboardValue; - private int psgRegNum; private int psgValue; private int lineIrqCounter; private int joy0ActionMask; @@ -151,6 +155,7 @@ public class KCcompact extends EmuSys implements private boolean centronicsStrobe; private boolean basicROMEnabled; private boolean osROMEnabled; + private boolean soundStereo; private AudioOut psgAudioOut; private PSG8910 psg; private PPI8255 ppi; @@ -162,16 +167,25 @@ public class KCcompact extends EmuSys implements public KCcompact( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.kccompact." ); + this.colors = new Color[ 27 ]; + createColors( props ); + + /* + * Auch bei einer extern eingebundenen Betriebssystem-ROM-Datei + * muss der integrierte ROM-Inhalt geladen werden, + * da daraus die Pixeldaten fuer die Zeichenerkennung gelesen werden. + */ if( romOS == null ) { romOS = readResource( "/rom/kccompact/kccos.bin" ); } - if( romBASIC == null ) { - romBASIC = readResource( "/rom/kccompact/kccbasic.bin" ); - } - this.colors = new Color[ 27 ]; - createColors( props ); + this.osBytes = null; + this.osFile = null; + this.basicBytes = null; + this.basicFile = null; + this.fdcROMBytes = null; + this.fdcROMFile = null; this.keyboardFld = null; this.fixedScreenSize = isFixedScreenSize( props ); this.screenMode = 0; @@ -184,7 +198,7 @@ public KCcompact( EmuThread emuThread, Properties props ) this.ram16KOffs = new int[ 4 ]; this.crtc = new CRTC6845( 1000, this ); this.ppi = new PPI8255( this ); - this.psg = new PSG8910( 1000000, AudioOut.MAX_VALUE, this ); + this.psg = new PSG8910( 1000000, AudioOut.MAX_USED_VALUE, this ); this.psgAudioOut = null; if( emulatesFloppyDisk( props ) ) { @@ -204,6 +218,7 @@ public KCcompact( EmuThread emuThread, Properties props ) cpu.addMaxSpeedListener( this ); cpu.addTStatesListener( this ); + applySoundStereo( props ); z80MaxSpeedChanged( cpu ); if( !isReloadExtROMsOnPowerOnEnabled( props ) ) { loadROMs( props ); @@ -440,7 +455,11 @@ public void psgWriteSample( PSG8910 psg, int a, int b, int c ) { AudioOut audioOut = this.psgAudioOut; if( audioOut != null ) { - audioOut.writeSamples( 1, (byte) ((a + b + c) / 6) ); + audioOut.writeSamples( + 1, + (a + b + c) / 6, + (a / 2) + (b / 4), + (c / 2) + (b / 4) ); } } @@ -519,8 +538,6 @@ public void reset( boolean powerOn ) Arrays.fill( this.ramExt, (byte) 0 ); } Arrays.fill( this.keyboardMatrix, 0 ); - this.psgRegNum = -1; - this.psgRegNum = 0xFF; this.regColorNum = 0; this.borderColorIdx = 0; this.keyboardIdx = 0; @@ -601,6 +618,7 @@ public void appendStatusHTMLTo( StringBuilder buf, Z80CPU cpu ) public void applySettings( Properties props ) { super.applySettings( props ); + applySoundStereo( props ); boolean state = isFixedScreenSize( props ); if( state != this.fixedScreenSize ) { @@ -634,6 +652,20 @@ public boolean canApplySettings( Properties props ) boolean rv = EmuUtil.getProperty( props, "jkcemu.system" ).equals( "KCcompact" ); + if( rv ) { + rv = TextUtil.equals( + this.osFile, + EmuUtil.getProperty( + props, + this.propPrefix + "os.file" ) ); + } + if( rv ) { + rv = TextUtil.equals( + this.basicFile, + EmuUtil.getProperty( + props, + this.propPrefix + "basic.file" ) ); + } if( rv && ((this.fdc != null) != emulatesFloppyDisk( props )) ) { rv = false; } @@ -642,7 +674,7 @@ public boolean canApplySettings( Properties props ) this.fdcROMFile, EmuUtil.getProperty( props, - "jkcemu.kccompact.fdc.rom.file" ) ); + this.propPrefix + "fdc.rom.file" ) ); } return rv; } @@ -776,9 +808,9 @@ public int getMemByte( int addr, boolean m1 ) int rv = 0xFF; if( (addr < 0x4000) && this.osROMEnabled ) { - if( romOS != null ) { - if( addr < romOS.length ) { - rv = (int) romOS[ addr ] & 0xFF; + if( this.osBytes != null ) { + if( addr < this.osBytes.length ) { + rv = (int) this.osBytes[ addr ] & 0xFF; } } } else if( (addr >= 0xC000) && this.basicROMEnabled ) { @@ -801,7 +833,7 @@ public int getMemByte( int addr, boolean m1 ) } } if( !done ) { - rom = romBASIC; + rom = this.basicBytes; if( rom != null ) { int idx = addr - 0xC000; if( (idx >= 0) && (idx < rom.length) ) { @@ -1061,7 +1093,7 @@ public boolean keyTyped( char ch ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; if( (this.fdc != null) @@ -1181,6 +1213,13 @@ public boolean supportsPrinter() } + @Override + public boolean supportsStereoSound() + { + return this.soundStereo; + } + + @Override public String toString() { @@ -1189,7 +1228,7 @@ public String toString() @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { value &= 0xFF; if( (this.fdc != null) @@ -1300,6 +1339,22 @@ private void addPixelCRC32ToMap( } + private void applySoundStereo( Properties props ) + { + boolean state = EmuUtil.getBooleanProperty( + props, + this.propPrefix + "sound.stereo", + false ); + if( state != this.soundStereo ) { + this.soundStereo = state; + AudioOut audioOut = this.psgAudioOut; + if( audioOut != null ) { + audioOut.fireReopenLine(); + } + } + } + + private void createColors( Properties props ) { float brightness = getBrightness( props ); @@ -1338,11 +1393,11 @@ private void createColors( Properties props ) } - private static boolean emulatesFloppyDisk( Properties props ) + private boolean emulatesFloppyDisk( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.kccompact.floppydisk.enabled", + this.propPrefix + "floppydisk.enabled", false ); } @@ -1377,7 +1432,7 @@ private Map getPixelCRC32ToCharMap() if( pixelCRC32ToChar == null ) { if( romOS != null ) { if( romOS.length >= 0x4000 ) { - Map map = new HashMap(); + Map map = new HashMap<>(); for( int c = 0x20; c <= 0x7E; c++ ) { addPixelCRC32ToMap( map, c, (char) c ); } @@ -1395,22 +1450,44 @@ private Map getPixelCRC32ToCharMap() } - private static boolean isFixedScreenSize( Properties props ) + private boolean isFixedScreenSize( Properties props ) { return EmuUtil.parseBooleanProperty( props, - "jkcemu.kccompact.fixed_screen_size", + this.propPrefix + "fixed_screen_size", false ); } private void loadROMs( Properties props ) { + // OS-ROM + this.osFile = EmuUtil.getProperty( + props, + this.propPrefix + "os.file" ); + this.osBytes = readROMFile( this.osFile, 0x4000, "Betriebssystem-ROM" ); + if( this.osBytes == null ) { + this.osBytes = romOS; + } + + // BASIC-ROM + this.basicFile = EmuUtil.getProperty( + props, + this.propPrefix + "basic.file" ); + this.basicBytes = readROMFile( this.basicFile, 0x4000, "BASIC-ROM" ); + if( this.basicBytes == null ) { + if( romBASIC == null ) { + romBASIC = readResource( "/rom/kccompact/kccbasic.bin" ); + } + this.basicBytes = romBASIC; + } + + // FDC-ROM if( this.fdc != null ) { this.fdcROMFile = EmuUtil.getProperty( props, - "jkcemu.kccompact.fdc.rom.file" ); - this.fdcROMBytes = readFile( this.fdcROMFile, 0x8000, "FDC-ROM" ); + this.propPrefix + "fdc.rom.file" ); + this.fdcROMBytes = readROMFile( this.fdcROMFile, 0x8000, "FDC-ROM" ); if( this.fdcROMBytes == null ) { if( romFDC == null ) { romFDC = readResource( "/rom/kccompact/basdos.bin" ); diff --git a/src/jkcemu/emusys/KramerMC.java b/src/jkcemu/emusys/KramerMC.java index e94cf14..9c3df8a 100644 --- a/src/jkcemu/emusys/KramerMC.java +++ b/src/jkcemu/emusys/KramerMC.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -94,10 +94,10 @@ public class KramerMC extends EmuSys implements public KramerMC( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.kramermmc." ); this.fontBytes = readFontByProperty( props, - "jkcemu.kramermmc.font.file", + this.propPrefix + "font.file", 0x0800 ); if( this.fontBytes == null ) { if( romFont == null ) { @@ -121,7 +121,7 @@ public KramerMC( EmuThread emuThread, Properties props ) this.kbMatrix = new int[ 8 ]; Z80CPU cpu = emuThread.getZ80CPU(); - this.pio = new Z80PIO( "PIO (IO-Adressen FC-FF)" ); + this.pio = new Z80PIO( "PIO (E/A-Adressen FC-FF)" ); cpu.setInterruptSources( this.pio ); cpu.addMaxSpeedListener( this ); cpu.addTStatesListener( this ); @@ -132,9 +132,9 @@ public KramerMC( EmuThread emuThread, Properties props ) } - public static String getBasicProgram( Z80MemView memory ) + public static String getBasicProgram( EmuMemView memory ) { - return SourceUtil.getKCBasicStyleProgram( memory, 0x1001, basicTokens ); + return SourceUtil.getBasicProgram( memory, 0x1001, basicTokens ); } @@ -491,7 +491,7 @@ public boolean keyTyped( char ch ) @Override public void openBasicProgram() { - String text = SourceUtil.getKCBasicStyleProgram( + String text = SourceUtil.getBasicProgram( this.emuThread, 0x1001, basicTokens ); @@ -533,17 +533,17 @@ protected boolean pasteChar( char ch ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int value = 0xFF; switch( port & 0xFF ) { case 0xFC: - value = this.pio.readPortA(); + value = this.pio.readDataA(); break; case 0xFD: updKBColValue(); - value = this.pio.readPortB(); + value = this.pio.readDataB(); break; case 0xFE: @@ -608,16 +608,15 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) @Override public void saveBasicProgram() { - int endAddr = SourceUtil.getKCBasicStyleEndAddr( this.emuThread, 0x1001 ); + int endAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x1001 ); if( endAddr >= 0x1001 ) { (new SaveDlg( this.screenFrm, 0x1001, endAddr, - 'B', - false, // kein KC-BASIC - false, // kein RBASIC - "BASIC-Programm speichern" )).setVisible( true ); + "BASIC-Programm speichern", + SaveDlg.BasicType.MS_DERIVED_BASIC, + EmuUtil.getBasicFileFilter() )).setVisible( true ); } else { showNoBasic(); } @@ -712,15 +711,22 @@ public boolean supportsSaveBasic() @Override public void updSysCells( - int begAddr, - int len, - Object fileFmt, - int fileType ) - { - if( (begAddr == 0x1001) && (fileFmt != null) ) { - if( fileFmt.equals( FileInfo.HEADERSAVE ) ) { - if( fileType == 'B' ) { - int topAddr = begAddr + len; + int begAddr, + int len, + FileFormat fileFmt, + int fileType ) + { + if( fileFmt != null ) { + if( (fileFmt.equals( FileFormat.BASIC_PRG ) + && (begAddr == 0x1001) + && (len > 7)) + || (fileFmt.equals( FileFormat.HEADERSAVE ) + && (fileType == 'B') + && (begAddr <= 0x1001) + && ((begAddr + len) > 0x1007)) ) + { + int topAddr = begAddr + len; + if( topAddr > 0x1001 ) { this.emuThread.setMemWord( 0x0C5E, topAddr ); this.emuThread.setMemWord( 0x0C60, topAddr ); this.emuThread.setMemWord( 0x0C62, topAddr ); @@ -731,16 +737,16 @@ public void updSysCells( @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { switch( port & 0xFF ) { case 0xFC: - this.pio.writePortA( value ); + this.pio.writeDataA( value ); this.kbRow = (this.pio.fetchOutValuePortA( false ) >> 1) & 0x07; break; case 0xFD: - this.pio.writePortB( value ); + this.pio.writeDataB( value ); break; case 0xFE: diff --git a/src/jkcemu/emusys/LC80.java b/src/jkcemu/emusys/LC80.java index d39f03c..e5f1d64 100644 --- a/src/jkcemu/emusys/LC80.java +++ b/src/jkcemu/emusys/LC80.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -42,6 +42,7 @@ public class LC80 extends EmuSys implements private volatile int pio1BValue; private long curDisplayTStates; private long displayCheckTStates; + private boolean audioInPhase; private boolean audioOutLED; private volatile boolean audioOutPhase; private volatile boolean audioOutState; @@ -57,7 +58,7 @@ public class LC80 extends EmuSys implements public LC80( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.lc80." ); this.chessComputer = false; this.romOSFile = null; this.romC000File = null; @@ -71,6 +72,7 @@ public LC80( EmuThread emuThread, Properties props ) } } + this.audioInPhase = false; this.audioOutLED = false; this.audioOutPhase = false; this.audioOutState = false; @@ -179,6 +181,11 @@ public void z80MaxSpeedChanged( Z80CPU cpu ) @Override public void z80TStatesProcessed( Z80CPU cpu, int tStates ) { + boolean phase = this.emuThread.readAudioPhase(); + if( phase != this.audioInPhase ) { + this.audioInPhase = phase; + this.pio1.putInValuePortB( this.audioInPhase ? 1 : 0, false ); + } this.ctc.z80TStatesProcessed( cpu, tStates ); if( this.displayCheckTStates > 0 ) { this.curDisplayTStates += tStates; @@ -226,12 +233,14 @@ public boolean canApplySettings( Properties props ) if( rv ) { rv = TextUtil.equals( this.romOSFile, - EmuUtil.getProperty( props, "jkcemu.lc80.os.file" ) ); + EmuUtil.getProperty( props, this.propPrefix + "os.file" ) ); } if( rv && this.sysName.equals( "LC80e" ) ) { rv = TextUtil.equals( this.romC000File, - EmuUtil.getProperty( props, "jkcemu.lc80.rom_c000.file" ) ); + EmuUtil.getProperty( + props, + this.propPrefix + "rom_c000.file" ) ); } return rv; } @@ -757,20 +766,17 @@ public boolean paintScreen( Graphics g, int x, int y, int screenScale ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; if( (port & 0x08) == 0 ) { switch( port & 0x03 ) { case 0: - rv &= this.pio1.readPortA(); + rv &= this.pio1.readDataA(); break; case 1: - this.pio1.putInValuePortB( - this.emuThread.readAudioPhase() ? 1 : 0, - false ); - rv &= this.pio1.readPortB(); + rv &= this.pio1.readDataB(); break; case 2: @@ -785,11 +791,11 @@ public int readIOByte( int port ) if( (port & 0x04) == 0 ) { switch( port & 0x03 ) { case 0: - rv &= this.pio2.readPortA(); + rv &= this.pio2.readDataA(); break; case 1: - rv &= this.pio2.readPortB(); + rv &= this.pio2.readDataB(); break; case 2: @@ -802,7 +808,7 @@ public int readIOByte( int port ) } } if( (port & 0x10) == 0 ) { - rv &= this.ctc.read( port & 0x03 ); + rv &= this.ctc.read( port & 0x03, tStates ); } return rv; } @@ -825,6 +831,7 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) Arrays.fill( this.digitValues, 0 ); } setChessMode( false ); + this.audioInPhase = this.emuThread.readAudioPhase(); } @@ -874,12 +881,12 @@ public boolean supportsKeyboardFld() @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { if( (port & 0x08) == 0 ) { switch( port & 0x03 ) { case 0: - this.pio1.writePortA( value ); + this.pio1.writeDataA( value ); this.curDigitValue = toDigitValue( this.pio1.fetchOutValuePortA( false ) ); putKBMatrixRowValueToPort(); @@ -887,7 +894,7 @@ public void writeIOByte( int port, int value ) break; case 1: - this.pio1.writePortB( value ); + this.pio1.writeDataB( value ); this.pio1BValue = this.pio1.fetchOutValuePortB( false ); boolean audioPhase = ((this.pio1BValue & 0x02) != 0); this.emuThread.writeAudioPhase( audioPhase ); @@ -912,11 +919,11 @@ public void writeIOByte( int port, int value ) if( (port & 0x04) == 0 ) { switch( port & 0x03 ) { case 0: - this.pio2.writePortA( value ); + this.pio2.writeDataA( value ); break; case 1: - this.pio2.writePortB( value ); + this.pio2.writeDataB( value ); break; case 2: @@ -929,7 +936,7 @@ public void writeIOByte( int port, int value ) } } if( (port & 0x10) == 0 ) { - this.ctc.write( port & 0x03, value ); + this.ctc.write( port & 0x03, value, tStates ); } } @@ -940,8 +947,8 @@ private void loadROMs( Properties props ) { this.romOSFile = EmuUtil.getProperty( props, - "jkcemu.lc80.os.file" ); - this.romOS = readFile( this.romOSFile, 0x2000, "Monitorprogramm" ); + this.propPrefix + "os.file" ); + this.romOS = readROMFile( this.romOSFile, 0x2000, "Monitorprogramm" ); if( this.romOS == null ) { if( this.sysName.equals( "LC80_U505" ) ) { if( lc80_u505 == null ) { @@ -968,8 +975,8 @@ private void loadROMs( Properties props ) if( this.sysName.equals( "LC80e" ) ) { this.romC000File = EmuUtil.getProperty( props, - "jkcemu.lc80.rom_c000.file" ); - this.romC000 = readFile( + this.propPrefix + "rom_c000.file" ); + this.romC000 = readROMFile( this.romC000File, 0x4000, "ROM C000h / Schachprogramm" ); diff --git a/src/jkcemu/emusys/LLC1.java b/src/jkcemu/emusys/LLC1.java index c383269..b590961 100644 --- a/src/jkcemu/emusys/LLC1.java +++ b/src/jkcemu/emusys/LLC1.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -50,7 +50,7 @@ public class LLC1 extends EmuSys implements public LLC1( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.llc1." ); if( rom0000 == null ) { rom0000 = readResource( "/rom/llc1/llc1mon.bin" ); } @@ -82,7 +82,7 @@ public LLC1( EmuThread emuThread, Properties props ) } - public static String getBasicProgram( Z80MemView memory ) + public static String getBasicProgram( EmuMemView memory ) { return SourceUtil.getTinyBasicProgram( memory, @@ -893,11 +893,11 @@ public boolean paintScreen( Graphics g, int x, int y, int screenScale ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; if( (port & 0x04) == 0 ) { - rv &= this.ctc.read( port & 0x03 ); + rv &= this.ctc.read( port & 0x03, tStates ); } else if( (port & 0x08) == 0 ) { switch( port & 0x03 ) { @@ -905,11 +905,11 @@ else if( (port & 0x08) == 0 ) { synchronized( this.keyboardMatrix ) { this.pio1.putInValuePortA( getHexKeyMatrixValue(), 0x8F ); } - rv &= this.pio1.readPortA(); + rv &= this.pio1.readDataA(); break; case 1: - rv &= this.pio1.readPortB(); + rv &= this.pio1.readDataB(); break; case 2: @@ -924,7 +924,7 @@ else if( (port & 0x08) == 0 ) { else if( (port & 0x10) == 0 ) { switch( port & 0x03 ) { case 0: - rv &= this.pio2.readPortA(); + rv &= this.pio2.readDataA(); break; case 1: @@ -933,7 +933,7 @@ else if( (port & 0x10) == 0 ) { this.keyStartTStateCounter > 0 ? 0xFF : this.keyChar, false ); } - rv &= this.pio2.readPortB(); + rv &= this.pio2.readDataB(); this.keyAlphaTStateCounter = this.keyAlphaTStates; break; @@ -993,10 +993,9 @@ public void saveBasicProgram() this.screenFrm, 0x1400, endAddr, - 'b', - false, // kein KC-BASIC - false, // kein RBASIC - "LLC1-BASIC-Programm speichern" )).setVisible( true ); + "LLC1-BASIC-Programm speichern", + SaveDlg.BasicType.TINYBASIC, + null )).setVisible( true ); } else { showNoBasic(); } @@ -1075,20 +1074,20 @@ public boolean supportsSaveBasic() @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { if( (port & 0x04) == 0 ) { - this.ctc.write( port & 0x03, value ); + this.ctc.write( port & 0x03, value, tStates ); } else if( (port & 0x08) == 0 ) { switch( port & 0x03 ) { case 0: - this.pio1.writePortA( value ); + this.pio1.writeDataA( value ); break; case 1: boolean oldReady = this.pio1.isReadyPortB(); - this.pio1.writePortB( value ); + this.pio1.writeDataB( value ); if( !oldReady && this.pio1.isReadyPortB() ) { this.digitIdx = (this.digitIdx + 1) & 0x07; } @@ -1124,11 +1123,11 @@ else if( (port & 0x08) == 0 ) { else if( (port & 0x10) == 0 ) { switch( port & 0x03 ) { case 0: - this.pio2.writePortA( value ); + this.pio2.writeDataA( value ); break; case 1: - this.pio2.writePortB( value ); + this.pio2.writeDataB( value ); break; case 2: diff --git a/src/jkcemu/emusys/LLC2.java b/src/jkcemu/emusys/LLC2.java index ced36d9..1679bf0 100644 --- a/src/jkcemu/emusys/LLC2.java +++ b/src/jkcemu/emusys/LLC2.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -10,6 +10,7 @@ import java.awt.Graphics; import java.awt.event.KeyEvent; +import java.awt.image.*; import java.lang.*; import java.util.*; import jkcemu.base.*; @@ -27,6 +28,7 @@ public class LLC2 implements FDC8272.DriveSelector, Z80CTCListener, + Z80MaxSpeedListener, Z80TStatesListener { /* @@ -37,7 +39,7 @@ public class LLC2 * Tatsaechlich werden aber in der Standardeinstellung pro Bit * 4 Ausgabe-Befehle auf dem Port getaetigt, die zusammen * ca. 336 bis 339 Takte benoetigen, was etwa 8900 Bit/s entspricht. - * Damit im Emulator die Ausgabe auf den emulierten Drucker ueber diese + * Damit im Emulator die Ausgabe auf dem emulierten Drucker ueber diese * serielle Schnittstelle funktioniert, * wird somit der Drucker ebenfalls mit dieser Bitrate emuliert. * Ist allerdings eine externe ROM-Datei als Monitorprogramm eingebunden, @@ -48,7 +50,6 @@ public class LLC2 private static byte[] llc2Font = null; private static byte[] scchMon91 = null; - private static byte[] gsbasic = null; private Z80CTC ctc; private Z80PIO pio2; @@ -56,42 +57,27 @@ public class LLC2 private FDC8272 fdc; private RAMFloppy ramFloppy1; private RAMFloppy ramFloppy2; - private byte[] ramModule3; + private BufferedImage screenImg; + private byte[] screenBuf; private byte[] fontBytes; private byte[] osBytes; - private byte[] basicBytes; - private byte[] prgXBytes; - private byte[] romdiskBytes; private String osFile; - private String basicFile; - private String prgXFile; - private String romdiskFile; - private boolean romEnabled; - private boolean romdiskEnabled; - private boolean prgXEnabled; - private boolean basicEnabled; + private boolean osRomEnabled; private boolean bit7InverseMode; private boolean screenInverseMode; private boolean loudspeakerEnabled; private boolean loudspeakerPhase; + private boolean audioInPhase; private boolean audioOutPhase; private boolean keyboardUsed; private volatile boolean graphicKeyState; private boolean hiRes; - private boolean rf32KActive; - private boolean rf32NegA15; - private boolean rfReadEnabled; - private boolean rfWriteEnabled; - private int rfAddr16to19; private int fontOffset; private int videoPixelAddr; private int videoTextAddr; - private boolean v24BitOut; - private int v24BitNum; - private int v24ShiftBuf; - private int v24TStateCounter; - private int v24TStatesPerBit; - private int romdiskBankAddr; + private volatile int tStatesPerLine; + private int lineTStateCounter; + private int lineCounter; private FloppyDiskDrive curFDDrive; private FloppyDiskDrive[] fdDrives; private KCNet kcNet; @@ -100,34 +86,28 @@ public class LLC2 public LLC2( EmuThread emuThread, Properties props ) { - super( emuThread, props ); - this.osBytes = null; - this.osFile = null; - this.basicBytes = null; - this.basicFile = null; - this.prgXBytes = null; - this.prgXFile = null; - this.romdiskBytes = null; - this.romdiskFile = null; - this.romdiskBankAddr = 0; - + super( emuThread, props, "jkcemu.llc2." ); + this.screenImg = null; + this.screenBuf = new byte[ 64 * 256 ]; + this.osBytes = null; + this.osFile = null; + this.pasteFast = false; this.ramModule3 = this.emuThread.getExtendedRAM( 0x100000 ); // 1MByte - this.ramFloppy1 = RAMFloppy.prepare( this.emuThread.getRAMFloppy1(), "LLC2", RAMFloppy.RFType.MP_3_1988, - "RAM-Floppy an IO-Adressen D0h-D7h", + "RAM-Floppy an E/A-Adressen D0h-D7h", props, - "jkcemu.llc2.ramfloppy.1." ); + this.propPrefix + "ramfloppy.1." ); this.ramFloppy2 = RAMFloppy.prepare( this.emuThread.getRAMFloppy2(), "LLC2", RAMFloppy.RFType.MP_3_1988, - "RAM-Floppy an IO-Adressen B0h-B7h", + "RAM-Floppy an E/A-Adressen B0h-B7h", props, - "jkcemu.llc2.ramfloppy.2." ); + this.propPrefix + "ramfloppy.2." ); this.curFDDrive = null; this.fdDrives = null; @@ -138,26 +118,25 @@ public LLC2( EmuThread emuThread, Properties props ) this.fdc = new FDC8272( this, 4 ); } - this.ctc = new Z80CTC( "CTC (IO-Adressen F8-FB)" ); - this.pio1 = new Z80PIO( "PIO (IO-Adressen E8-EB)" ); - this.pio2 = new Z80PIO( "V24-PIO (IO-Adressen E4-E7)" ); + this.ctc = new Z80CTC( "CTC (E/A-Adressen F8-FB)" ); + this.pio1 = new Z80PIO( "PIO (E/A-Adressen E8-EB)" ); + this.pio2 = new Z80PIO( "V24-PIO (E/A-Adressen E4-E7)" ); this.kcNet = null; if( emulatesKCNet( props ) ) { - this.kcNet = new KCNet( "Netzwerk-PIO (IO-Adressen C0-C3)" ); + this.kcNet = new KCNet( "Netzwerk-PIO (E/A-Adressen C0-C3)" ); } this.vdip = null; if( emulatesUSB( props ) ) { - this.vdip = new VDIP( "USB-PIO (IO-Adressen DC-DF, FC-FF)" ); + this.vdip = new VDIP( + this.emuThread.getFileTimesViewFactory(), + "USB-PIO (E/A-Adressen DC-DF, FC-FF)" ); } - this.gide = GIDE.getGIDE( this.screenFrm, props, "jkcemu.llc2." ); + this.gide = GIDE.getGIDE( this.screenFrm, props, this.propPrefix ); - this.joystickEnabled = emulatesJoystick( props ); - - java.util.List iSources - = new ArrayList(); + java.util.List iSources = new ArrayList<>(); iSources.add( this.ctc ); iSources.add( this.pio1 ); iSources.add( this.pio2 ); @@ -176,21 +155,17 @@ public LLC2( EmuThread emuThread, Properties props ) this.ctc.setTimerConnection( 1, 3 ); this.ctc.addCTCListener( this ); + cpu.addMaxSpeedListener( this ); cpu.addTStatesListener( this ); - if( this.fdc != null ) { - this.fdc.setTStatesPerMilli( cpu.getMaxSpeedKHz() ); - cpu.addMaxSpeedListener( this.fdc ); - } - if( this.kcNet != null ) { - this.kcNet.z80MaxSpeedChanged( cpu ); - cpu.addMaxSpeedListener( this.kcNet ); - } if( this.vdip != null ) { this.vdip.applySettings( props ); } + z80MaxSpeedChanged( cpu ); if( !isReloadExtROMsOnPowerOnEnabled( props ) ) { loadROMs( props ); } + checkAddPCListener( props ); + updScreenRatio( props ); } @@ -200,6 +175,31 @@ public static int getDefaultSpeedKHz() } + public static boolean getDefaultSwapKeyCharCase() + { + return true; + } + + + protected void loadROMs( Properties props ) + { + super.loadROMs( props, "/rom/llc2/gsbasic.bin" ); + + // OS-ROM + this.osFile = EmuUtil.getProperty( props, this.propPrefix + "os.file" ); + this.osBytes = readROMFile( this.osFile, 0x1000, "Monitorprogramm" ); + if( this.osBytes == null ) { + if( scchMon91 == null ) { + scchMon91 = readResource( "/rom/llc2/scchmon_91g.bin" ); + } + this.osBytes = scchMon91; + } + + // Zeichensatz + loadFont( props ); + } + + /* --- FDC8272.DriveSelector --- */ @Override @@ -225,6 +225,22 @@ public void z80CTCUpdate( Z80CTC ctc, int timerNum ) } + /* --- Z80MaxSpeedListener --- */ + + @Override + public void z80MaxSpeedChanged( Z80CPU cpu ) + { + // KHz * 1000 / 50 / 312 + this.tStatesPerLine = cpu.getMaxSpeedKHz() * 20 / 312; + if( this.fdc != null ) { + this.fdc.z80MaxSpeedChanged( cpu ); + } + if( this.kcNet != null ) { + this.kcNet.z80MaxSpeedChanged( cpu ); + } + } + + /* --- Z80TStatesListener --- */ @Override @@ -237,6 +253,19 @@ public synchronized void z80TStatesProcessed( Z80CPU cpu, int tStates ) if( this.kcNet != null ) { this.kcNet.z80TStatesProcessed( cpu, tStates ); } + if( this.tStatesPerLine > 0 ) { + this.lineTStateCounter += tStates; + if( this.lineTStateCounter >= this.tStatesPerLine ) { + this.lineTStateCounter -= this.tStatesPerLine; + fillScreenBufLine( this.lineCounter - 31 ); + this.lineCounter++; + if( this.lineCounter >= 312 ) { + this.lineCounter = 0; + this.ctc.externalUpdate( 2, 1 ); + this.screenFrm.setScreenDirty( true ); + } + } + } if( this.v24BitNum > 0 ) { synchronized( this ) { this.v24TStateCounter -= tStates; @@ -255,6 +284,11 @@ public synchronized void z80TStatesProcessed( Z80CPU cpu, int tStates ) } } } + boolean phase = this.emuThread.readAudioPhase(); + if( phase != this.audioInPhase ) { + this.audioInPhase = phase; + this.pio1.putInValuePortB( this.audioInPhase ? 0x02 : 0, 0x02 ); + } } @@ -266,19 +300,19 @@ public void appendStatusHTMLTo( StringBuilder buf, Z80CPU cpu ) buf.append( "

    LLC2 Status

    \n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" - + "\n" - + "\n" + + "" + + "\n", + ramBegAddr, + this.ramBank ) ); + if( ramBegAddr > 0 ) { + buf.append( + String.format( + "\n", + ramBegAddr - 1) ); } - buf.append( "\n" - + "
    Monitor-ROM:" ); - buf.append( this.romEnabled ? "ein" : "aus" ); + buf.append( this.osRomEnabled ? "ein" : "aus" ); buf.append( "
    ROM-Disk:" ); - buf.append( this.romdiskEnabled ? "ein" : "aus" ); + buf.append( this.scchRomdiskEnabled ? "ein" : "aus" ); buf.append( "
    ROM-Disk Bank:" ); - buf.append( (this.romdiskBankAddr >> 14) & 0x0F ); + buf.append( (this.scchRomdiskBankAddr >> 14) & 0x0F ); buf.append( "
    Programmpaket X ROM:" ); - buf.append( this.prgXEnabled ? "ein" : "aus" ); + buf.append( this.scchPrgXRomEnabled ? "ein" : "aus" ); buf.append( "
    BASIC-ROM:" ); - buf.append( this.basicEnabled ? "ein" : "aus" ); + buf.append( this.scchBasicRomEnabled ? "ein" : "aus" ); buf.append( "
    Grafikmodus:" ); buf.append( this.hiRes ? "HIRES-Vollgrafik" : "Text" ); @@ -306,6 +340,10 @@ public void applySettings( Properties props ) { super.applySettings( props ); loadFont( props ); + checkAddPCListener( props ); + if( updScreenRatio( props ) ) { + this.screenFrm.fireScreenSizeChanged(); + } } @@ -316,24 +354,12 @@ public boolean canApplySettings( Properties props ) props, "jkcemu.system" ).equals( "LLC2" ); if( rv ) { - rv = TextUtil.equals( - this.osFile, - EmuUtil.getProperty( props, "jkcemu.llc2.os.file" ) ); - } - if( rv ) { - rv = TextUtil.equals( - this.basicFile, - EmuUtil.getProperty( props, "jkcemu.llc2.basic.file" ) ); - } - if( rv ) { - rv = TextUtil.equals( - this.prgXFile, - EmuUtil.getProperty( props, "jkcemu.llc2.program_x.file" ) ); + rv = super.canApplySettings( props ); } if( rv ) { rv = TextUtil.equals( - this.romdiskFile, - EmuUtil.getProperty( props, "jkcemu.llc2.romdisk.file" ) ); + this.osFile, + EmuUtil.getProperty( props, this.propPrefix + "os.file" ) ); } if( rv ) { rv = RAMFloppy.complies( @@ -341,7 +367,7 @@ public boolean canApplySettings( Properties props ) "LLC2", RAMFloppy.RFType.MP_3_1988, props, - "jkcemu.llc2.ramfloppy.1." ); + this.propPrefix + "ramfloppy.1." ); } if( rv ) { rv = RAMFloppy.complies( @@ -349,17 +375,14 @@ public boolean canApplySettings( Properties props ) "LLC2", RAMFloppy.RFType.MP_3_1988, props, - "jkcemu.llc2.ramfloppy.2." ); + this.propPrefix + "ramfloppy.2." ); } if( rv ) { - rv = GIDE.complies( this.gide, props, "jkcemu.llc2." ); + rv = GIDE.complies( this.gide, props, this.propPrefix ); } if( rv && emulatesFloppyDisk( props ) != (this.fdc != null) ) { rv = false; } - if( rv && (emulatesJoystick( props ) != this.joystickEnabled) ) { - rv = false; - } if( rv && (emulatesKCNet( props ) != (this.kcNet != null)) ) { rv = false; } @@ -393,14 +416,17 @@ public void die() Z80CPU cpu = this.emuThread.getZ80CPU(); cpu.removeTStatesListener( this ); + cpu.removeMaxSpeedListener( this ); cpu.setInterruptSources( (Z80InterruptSource[]) null ); + if( this.pasteFast ) { + cpu.removePCListener( this ); + this.pasteFast = false; + } if( this.fdc != null ) { - cpu.removeMaxSpeedListener( this.fdc ); this.fdc.die(); } if( this.kcNet != null ) { - cpu.removeMaxSpeedListener( this.kcNet ); this.kcNet.die(); } if( this.vdip != null ) { @@ -423,10 +449,42 @@ public int getBorderColorIndex() } + @Override + public int getColorIndex( int x, int y ) + { + int rv = BLACK; + if( (x == 0) && (this.tStatesPerLine <= 1) ) { + fillScreenBufLine( y ); + } + int b = 0; + int idx = (y * 64) + (x / 8); + if( (idx >= 0) && (idx < this.screenBuf.length) ) { + b = this.screenBuf[ idx ]; + } + int m = 0x80; + int n = x % 8; + if( n > 0 ) { + m >>= n; + } + if( (b & m) != 0 ) { + rv = WHITE; + } + return rv; + } + + @Override public CharRaster getCurScreenCharRaster() { - return this.hiRes ? null : new CharRaster( 64, 32, 8, 8, 8, 0 ); + CharRaster raster = null; + if( !this.hiRes ) { + if( this.screenImg != null ) { + raster = new CharRaster( 64, 32, 12, 12, 8, 0 ); + } else { + raster = new CharRaster( 64, 32, 8, 8, 8, 0 ); + } + } + return raster; } @@ -440,21 +498,21 @@ public FloppyDiskFormat getDefaultFloppyDiskFormat() @Override protected long getDelayMillisAfterPasteChar() { - return 50; + return 80; } @Override protected long getDelayMillisAfterPasteEnter() { - return 150; + return 200; } @Override protected long getHoldMillisPasteChar() { - return 50; + return 80; } @@ -470,66 +528,18 @@ public int getMemByte( int addr, boolean m1 ) { addr &= 0xFFFF; - int rv = 0xFF; - boolean done = false; - if( !m1 && this.rfReadEnabled && (this.ramModule3 != null) ) { - int idx = this.rfAddr16to19 | addr; - if( (idx >= 0) && (idx < this.ramModule3.length) ) { - rv = (int) this.ramModule3[ idx ] & 0xFF; - } - done = true; - } - if( !done && this.rf32KActive && (this.ramModule3 != null) - && (addr >= 0x4000) && (addr < 0xC000) ) - { - int idx = this.rfAddr16to19 | addr; - if( this.rf32NegA15 ) { - idx ^= 0x8000; - } - if( (idx >= 0) && (idx < this.ramModule3.length) ) { - rv = (int) this.ramModule3[ idx ] & 0xFF; - } - done = true; - } - if( !done && this.basicEnabled - && (addr >= 0x4000) && (addr < 0x6000) ) - { - if( this.basicBytes != null ) { - int idx = addr - 0x4000; - if( idx < this.basicBytes.length ) { - rv = (int) this.basicBytes[ idx ] & 0xFF; - } - } - done = true; - } - if( !done && this.romdiskEnabled && (addr >= 0xC000) ) { - if( this.romdiskBytes != null ) { - int idx = this.romdiskBankAddr | (addr - 0xC000); - if( idx < this.romdiskBytes.length ) { - rv = (int) this.romdiskBytes[ idx ] & 0xFF; - } - } - done = true; - } - if( !done && this.prgXEnabled && (addr >= 0xE000) ) { - if( this.prgXBytes != null ) { - int idx = addr - 0xE000; - if( idx < this.prgXBytes.length ) { - rv = (int) this.prgXBytes[ idx ] & 0xFF; - } - } - done = true; - } - if( !done && this.romEnabled && (addr < 0xC000) ) { - if( this.osBytes != null ) { - if( addr < this.osBytes.length ) { - rv = (int) this.osBytes[ addr ] & 0xFF; + int rv = getScchMemByte( addr, m1 ); + if( rv < 0 ) { + rv = 0xFF; + if( this.osRomEnabled && (addr < 0xC000) ) { + if( this.osBytes != null ) { + if( addr < this.osBytes.length ) { + rv = (int) this.osBytes[ addr ] & 0xFF; + } } + } else { + rv = this.emuThread.getRAMByte( addr ); } - done = true; - } - if( !done ) { - rv = this.emuThread.getRAMByte( addr ); } return rv; } @@ -557,7 +567,7 @@ protected int getScreenChar( CharRaster chRaster, int chX, int chY ) @Override public int getScreenHeight() { - return 256; + return this.screenImg != null ? 384 : 256; } @@ -578,7 +588,7 @@ public int getSupportedFloppyDiskDriveCount() @Override public boolean getSwapKeyCharCase() { - return true; + return getDefaultSwapKeyCharCase(); } @@ -603,7 +613,6 @@ public boolean keyPressed( boolean shiftDown ) { boolean rv = false; - int ch = 0; switch( keyCode ) { case KeyEvent.VK_F1: this.screenInverseMode = !this.screenInverseMode; @@ -616,175 +625,42 @@ public boolean keyPressed( rv = true; break; - case KeyEvent.VK_LEFT: - ch = 8; - break; - - case KeyEvent.VK_RIGHT: - ch = 9; - break; - - case KeyEvent.VK_DOWN: - ch = 0x0A; - break; - - case KeyEvent.VK_UP: - ch = 0x0B; - break; - - case KeyEvent.VK_ENTER: - ch = 0x0D; - break; - - case KeyEvent.VK_SPACE: - ch = 0x20; - break; - - case KeyEvent.VK_BACK_SPACE: - ch = (this.graphicKeyState ? 8 : 0x7F); - break; - - case KeyEvent.VK_DELETE: - ch = 0x7F; - break; - } - if( ch > 0 ) { - setKeyboardValue( ch | 0x80 ); - rv = true; + default: + rv = super.keyPressed( keyCode, ctrlDown, shiftDown ); } return rv; } @Override - public void keyReleased() - { - setKeyboardValue( 0 ); - } - - - @Override - public boolean keyTyped( char ch ) + public boolean paintScreen( Graphics g, int x, int y, int screenScale ) { - boolean rv = false; - if( (ch > 0) && (ch < 0x7F) ) { - setKeyboardValue( ch | 0x80 ); - rv = true; - } - return rv; - } - - - @Override - public boolean paintScreen( - Graphics g, - int xOffs, - int yOffs, - int screenScale ) - { - byte[] fontBytes = null; - if( !this.hiRes ) { - fontBytes = this.fontBytes; - } - if( this.hiRes || (fontBytes != null) ) { - int bgColorIdx = getBorderColorIndex(); - int wBase = getScreenWidth(); - int hBase = getScreenHeight(); - - if( (xOffs > 0) || (yOffs > 0) ) { - g.translate( xOffs, yOffs ); - } - - /* - * Aus Gruenden der Performance werden nebeneinander liegende - * weisse Punkte zusammengefasst und als Linie gezeichnet. - */ - for( int y = 0; y < hBase; y++ ) { - int lastColorIdx = -1; - int xColorBeg = -1; - boolean inverse = false; - for( int x = 0; x < wBase; x++ ) { - int col = x / 8; - int row = y / 8; - int rPix = y % 8; - boolean pixel = false; - if( this.hiRes ) { - int b = this.emuThread.getRAMByte( - this.videoPixelAddr + (rPix * 0x0800) - + (row * 64) + (x / 8) ); - int m = 0x80; - int n = x % 8; - if( n > 0 ) { - m >>= n; - } - pixel = ((b & m) != 0); - if( this.screenInverseMode ) { - pixel = !pixel; - } - } else if( fontBytes != null ) { - int ch = this.emuThread.getRAMByte( - this.videoTextAddr + (row * 64) + col ); - if( ch == 0x10 ) { - inverse = false; - } else if( ch == 0x11 ) { - inverse = true; - } - int idx = this.fontOffset - + ((ch & (this.bit7InverseMode ? 0x7F : 0xFF)) * 8) - + (y % 8); - if( (idx >= 0) && (idx < fontBytes.length) ) { - int m = 0x80; - int n = x % 8; - if( n > 0 ) { - m >>= n; - } - pixel = ((fontBytes[ idx ] & m) != 0); - if( (inverse || (this.bit7InverseMode && ((ch & 0x80) != 0))) - != this.screenInverseMode ) - { - pixel = !pixel; - } - } - } - int curColorIdx = (pixel ? 1 : 0); - if( curColorIdx != lastColorIdx ) { - if( (lastColorIdx >= 0) - && (lastColorIdx != bgColorIdx) - && (xColorBeg >= 0) ) - { - g.setColor( getColor( lastColorIdx ) ); - g.fillRect( - xColorBeg * screenScale, - y * screenScale, - (x - xColorBeg) * screenScale, - screenScale ); - } - xColorBeg = x; - lastColorIdx = curColorIdx; - } + boolean rv = false; + BufferedImage img = this.screenImg; + if( img != null ) { + for( int ix = 0; ix < 512; ix++ ) { + for( int iy = 0; iy < 256; iy++ ) { + img.setRGB( + ix, + iy, + getColorIndex( ix, iy ) > 0 ? 0xFFFFFFFF : 0xFF000000 ); } - if( (lastColorIdx >= 0) - && (lastColorIdx != bgColorIdx) - && (xColorBeg >= 0) ) - { - g.setColor( getColor( lastColorIdx ) ); - g.fillRect( - xColorBeg * screenScale, - y * screenScale, - (wBase - xColorBeg) * screenScale, - screenScale ); - } - } - if( (xOffs > 0) || (yOffs > 0) ) { - g.translate( -xOffs, -yOffs ); } + g.drawImage( + img, + x, + y, + getScreenWidth() * screenScale, + getScreenHeight() * screenScale, + this ); + rv = true; } - return true; + return rv; } @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; if( (this.gide != null) && ((port & 0xF0) == 0x80) ) { @@ -872,17 +748,17 @@ public int readIOByte( int port ) case 0xE1: case 0xE2: case 0xE3: - this.romEnabled = false; + this.osRomEnabled = false; break; case 0xE4: // V24: CTS=L (empfangsbereit) this.pio2.putInValuePortA( 0, 0x04 ); - rv = this.pio2.readPortA(); + rv = this.pio2.readDataA(); break; case 0xE5: - rv = this.pio2.readPortB(); + rv = this.pio2.readDataB(); break; case 0xE6: @@ -902,21 +778,12 @@ public int readIOByte( int port ) this.keyboardUsed = true; } } - rv = this.pio1.readPortA(); + rv = this.pio1.readDataA(); break; case 0xE9: - { - int v = 0x04; // PIO B2: Grafiktaste, L-aktiv - if( this.graphicKeyState ) { - v = 0; - } - if( this.emuThread.readAudioPhase() ) { - v |= 0x02; - } - this.pio1.putInValuePortB( v, 0x06 ); - } - rv = this.pio1.readPortB(); + this.pio1.putInValuePortB( this.graphicKeyState ? 0 : 0x04, 0x04 ); + rv = this.pio1.readDataB(); break; case 0xEA: @@ -931,7 +798,7 @@ public int readIOByte( int port ) case 0xF9: case 0xFA: case 0xFB: - rv = this.ctc.read( port & 0x03 ); + rv = this.ctc.read( port & 0x03, tStates ); break; } } @@ -974,33 +841,24 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) } } this.curFDDrive = null; - this.romEnabled = true; - this.romdiskEnabled = false; - this.prgXEnabled = false; - this.basicEnabled = false; + this.osRomEnabled = true; this.bit7InverseMode = false; this.screenInverseMode = false; this.loudspeakerEnabled = false; this.loudspeakerPhase = false; + this.audioInPhase = this.emuThread.readAudioPhase(); this.audioOutPhase = false; this.keyboardUsed = false; this.joystickSelected = false; this.graphicKeyState = false; this.hiRes = false; - this.rf32KActive = false; - this.rf32NegA15 = false; - this.rfReadEnabled = false; - this.rfWriteEnabled = false; - this.rfAddr16to19 = 0; this.joystickValue = 0x1F; this.keyboardValue = 0; this.fontOffset = 0; + this.lineCounter = 0; + this.lineTStateCounter = 0; this.videoPixelAddr = 0xC000; this.videoTextAddr = 0xC000; - this.v24BitOut = true; // V24: H-Pegel - this.v24BitNum = 0; - this.v24ShiftBuf = 0; - this.v24TStateCounter = 0; if( this.osBytes == scchMon91 ) { this.v24TStatesPerBit = V24_TSTATES_PER_BIT_INTERN; } else { @@ -1013,16 +871,15 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) @Override public void saveBasicProgram() { - int endAddr = SourceUtil.getKCBasicStyleEndAddr( this.emuThread, 0x60F7 ); + int endAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x60F7 ); if( endAddr >= 0x60F7 ) { (new SaveDlg( this.screenFrm, - 0x6000, + 0x60F7, endAddr, - 'B', - false, // kein KC-BASIC - false, // kein RBASIC - "LLC2-BASIC-Programm speichern" )).setVisible( true ); + "LLC2-BASIC-Programm speichern", + SaveDlg.BasicType.MS_DERIVED_BASIC, + EmuUtil.getBasicFileFilter() )).setVisible( true ); } else { showNoBasic(); } @@ -1075,30 +932,10 @@ public boolean setMemByte( int addr, int value ) { addr &= 0xFFFF; - boolean rv = false; - boolean done = false; - if( this.rfWriteEnabled && (this.ramModule3 != null) ) { - int idx = this.rfAddr16to19 | addr; - if( (idx >= 0) && (idx < this.ramModule3.length) ) { - this.ramModule3[ idx ] = (byte) value; - rv = true; - } - done = true; - } - if( !done && this.rf32KActive && (this.ramModule3 != null) - && (addr >= 0x4000) && (addr < 0xC000) ) - { - int idx = this.rfAddr16to19 | addr; - if( this.rf32NegA15 ) { - idx ^= 0x8000; - } - if( (idx >= 0) && (idx < this.ramModule3.length) ) { - this.ramModule3[ idx ] = (byte) value; - rv = true; - } - done = true; - } - if( !done && (!this.romEnabled || (addr >= 0xC000)) ) { + int status = setScchMemByte( addr, value ); + boolean done = (status >= 0); + boolean rv = (status > 0); + if( !done && (!this.osRomEnabled || (addr >= 0xC000)) ) { this.emuThread.setRAMByte( addr, value ); if( this.hiRes ) { if( (addr >= this.videoPixelAddr) @@ -1140,13 +977,6 @@ public boolean supportsCopyToClipboard() } - @Override - public boolean supportsPasteFromClipboard() - { - return true; - } - - @Override public boolean supportsPrinter() { @@ -1175,28 +1005,42 @@ public boolean supportsSaveBasic() } + @Override + public void updDebugScreen() + { + for( int y = 0; y < 0x100; y++ ) { + fillScreenBufLine( y ); + } + } + + @Override public void updSysCells( - int begAddr, - int len, - Object fileFmt, - int fileType ) + int begAddr, + int len, + FileFormat fileFmt, + int fileType ) { - if( (begAddr == 0x60F7) && (fileFmt != null) ) { - if( fileFmt.equals( FileInfo.HEADERSAVE ) ) { - if( fileType == 'B' ) { - int topAddr = begAddr + len; - this.emuThread.setMemWord( 0x60D2, topAddr ); - this.emuThread.setMemWord( 0x60D4, topAddr ); - this.emuThread.setMemWord( 0x60D6, topAddr ); - } + if( fileFmt != null ) { + if( (fileFmt.equals( FileFormat.BASIC_PRG ) + && (begAddr == 0x60F7) + && (len > 7)) + || (fileFmt.equals( FileFormat.HEADERSAVE ) + && (fileType == 'B') + && (begAddr <= 0x60F7) + && ((begAddr + len) > 0x60FE)) ) + { + int topAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x60F7 ); + this.emuThread.setMemWord( 0x60D2, topAddr ); + this.emuThread.setMemWord( 0x60D4, topAddr ); + this.emuThread.setMemWord( 0x60D6, topAddr ); } } } @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { if( (this.gide != null) && ((port & 0xF0) == 0x80) ) { this.gide.write( port, value ); @@ -1256,6 +1100,10 @@ public void writeIOByte( int port, int value ) case 0xDD: case 0xDE: case 0xDF: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: if( this.vdip != null ) { this.vdip.write( port, value ); } @@ -1265,11 +1113,11 @@ public void writeIOByte( int port, int value ) case 0xE1: case 0xE2: case 0xE3: - this.romEnabled = false; + this.osRomEnabled = false; break; case 0xE4: - this.pio2.writePortA( value ); + this.pio2.writeDataA( value ); synchronized( this ) { boolean state = ((this.pio2.fetchOutValuePortA( false ) & 0x02) != 0); @@ -1287,7 +1135,7 @@ public void writeIOByte( int port, int value ) break; case 0xE5: - this.pio2.writePortB( value ); + this.pio2.writeDataB( value ); break; case 0xE6: @@ -1299,11 +1147,11 @@ public void writeIOByte( int port, int value ) break; case 0xE8: - this.pio1.writePortA( value ); + this.pio1.writeDataA( value ); break; case 0xE9: - this.pio1.writePortB( value ); + this.pio1.writeDataB( value ); synchronized( this.pio1 ) { int v = this.pio1.fetchOutValuePortB( false ); boolean b = ((v & 0x01) != 0); @@ -1315,8 +1163,15 @@ public void writeIOByte( int port, int value ) } if( this.fontBytes != llc2Font ) { if( this.fontBytes.length > 0x0800 ) { - b = ((v & 0x08) != 0); - this.fontOffset = (b ? 0x0800 : 0); + if( (v & 0x08) != 0 ) { + if( this.fontBytes.length > 0x1000 ) { + this.fontOffset = 0x1000; + } else { + this.fontOffset = 0x0800; + } + } else { + this.fontOffset = 0; + } } } if( this.joystickEnabled ) { @@ -1353,15 +1208,15 @@ public void writeIOByte( int port, int value ) break; case 0xEC: - this.prgXEnabled = ((value & 0x01) != 0); - this.basicEnabled = ((value & 0x02) != 0); - this.romdiskEnabled = ((value & 0x08) != 0); - if( this.romdiskEnabled ) { - this.romdiskBankAddr = (((value & 0x01) | ((value >> 3) & 0x0E)) - << 14); - this.prgXEnabled = false; + this.scchPrgXRomEnabled = ((value & 0x01) != 0); + this.scchBasicRomEnabled = ((value & 0x02) != 0); + this.scchRomdiskEnabled = ((value & 0x08) != 0); + if( this.scchRomdiskEnabled ) { + this.scchRomdiskBankAddr = + (((value & 0x01) | ((value >> 3) & 0x0E)) << 14); + this.scchPrgXRomEnabled = false; } else { - this.prgXEnabled = ((value & 0x01) != 0); + this.scchPrgXRomEnabled = ((value & 0x01) != 0); } break; @@ -1411,7 +1266,7 @@ public void writeIOByte( int port, int value ) case 0xF9: case 0xFA: case 0xFB: - this.ctc.write( port & 0x03, value ); + this.ctc.write( port & 0x03, value, tStates ); break; } if( dirty ) { @@ -1423,39 +1278,60 @@ public void writeIOByte( int port, int value ) /* --- private Methoden --- */ - private static boolean emulatesFloppyDisk( Properties props ) + private void fillScreenBufLine( int y ) { - return EmuUtil.getBooleanProperty( - props, - "jkcemu.llc2.floppydisk.enabled", - false ); - } - - - private static boolean emulatesJoystick( Properties props ) - { - return EmuUtil.getBooleanProperty( - props, - "jkcemu.llc2.joystick.enabled", - false ); - } - - - private boolean emulatesKCNet( Properties props ) - { - return EmuUtil.getBooleanProperty( - props, - "jkcemu.llc2.kcnet.enabled", - false ); - } - - - private boolean emulatesUSB( Properties props ) - { - return EmuUtil.getBooleanProperty( - props, - "jkcemu.llc2.vdip.enabled", - false ); + if( (y >= 0) && (y < 256) ) { + int row = y / 8; + int rPix = y % 8; + if( this.hiRes ) { + for( int col = 0; col < 64; col++ ) { + int b = this.emuThread.getRAMByte( this.videoPixelAddr + + (rPix * 0x0800) + + (row * 64) + col ); + if( this.screenInverseMode ) { + b = ~b; + } + this.screenBuf[ (y * 64) + col ] = (byte) b; + } + } else { + boolean inverse = false; + byte[] fontBytes = null; + if( !this.hiRes ) { + fontBytes = this.fontBytes; + } + for( int col = 0; col < 64; col++ ) { + int b = 0; + int ch = this.emuThread.getRAMByte( + this.videoTextAddr + (row * 64) + col ); + if( ch == 0x10 ) { + inverse = false; + } else if( ch == 0x11 ) { + inverse = true; + } + if( fontBytes != null ) { + int idx = this.fontOffset + + ((ch & (this.bit7InverseMode ? 0x7F : 0xFF)) * 8) + + rPix; + if( (idx >= 0) && (idx < fontBytes.length) ) { + int m = 0x80; + int f = fontBytes[ idx ]; + for( int i = 0; i < 8; i++ ) { + if( (f & m) != 0 ) { + b |= m; + } + m >>= 1; + } + } + } + if( (inverse || (this.bit7InverseMode && ((ch & 0x80) != 0))) + != this.screenInverseMode ) + { + b = ~b; + } + this.screenBuf[ (y * 64) + col ] = (byte) b; + } + } + } } @@ -1463,7 +1339,7 @@ private void loadFont( Properties props ) { this.fontBytes = readFontByProperty( props, - "jkcemu.llc2.font.file", 0x1000 ); + this.propPrefix + "font.file", 0x2000 ); if( this.fontBytes == null ) { if( llc2Font == null ) { llc2Font = readResource( "/rom/llc2/llc2font.bin" ); @@ -1473,41 +1349,24 @@ private void loadFont( Properties props ) } - private void loadROMs( Properties props ) + private boolean updScreenRatio( Properties props ) { - // OS-ROM - this.osFile = EmuUtil.getProperty( props, "jkcemu.llc2.os.file" ); - this.osBytes = readFile( this.osFile, 0x1000, "Monitorprogramm" ); - if( this.osBytes == null ) { - if( scchMon91 == null ) { - scchMon91 = readResource( "/rom/llc2/scchmon_91g.bin" ); - } - this.osBytes = scchMon91; - } - - // BASIC-ROM - this.basicFile = EmuUtil.getProperty( props, "jkcemu.llc2.basic.file" ); - this.basicBytes = readFile( this.basicFile, 0x2000, "BASIC" ); - if( this.basicBytes == null ) { - if( gsbasic == null ) { - gsbasic = readResource( "/rom/llc2/gsbasic.bin" ); + boolean changed = false; + boolean mode43 = EmuUtil.getProperty( + props, + this.propPrefix + "screen.ratio" ).startsWith( "4" ); + if( mode43 != (this.screenImg != null) ) { + if( mode43 ) { + this.screenImg = new BufferedImage( + 512, + 256, + BufferedImage.TYPE_INT_RGB ); + } else { + this.screenImg = null; } - this.basicBytes = gsbasic; + changed = true; } - - // Programmpaket X - this.prgXFile = EmuUtil.getProperty( - props, - "jkcemu.llc2.program_x.file" ); - this.prgXBytes = readFile( this.prgXFile, 0x2000, "Programmpaket X" ); - - // ROM-Disk - this.romdiskFile = EmuUtil.getProperty( - props, - "jkcemu.llc2.romdisk.file" ); - this.romdiskBytes = readFile( this.romdiskFile, 0x40000, "ROM-Disk" ); - - // Zeichensatz - loadFont( props ); + return changed; } } + diff --git a/src/jkcemu/emusys/NANOS.java b/src/jkcemu/emusys/NANOS.java new file mode 100644 index 0000000..8c4bc12 --- /dev/null +++ b/src/jkcemu/emusys/NANOS.java @@ -0,0 +1,1584 @@ +/* + * (c) 2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Emulation des NANOS-Systems + */ + +package jkcemu.emusys; + +import java.awt.Color; +import java.awt.event.KeyEvent; +import java.lang.*; +import java.text.*; +import java.util.*; +import jkcemu.base.*; +import jkcemu.disk.*; +import jkcemu.etc.VDIP; +import jkcemu.net.KCNet; +import jkcemu.text.TextUtil; +import z80emu.*; + + +public class NANOS extends EmuSys implements + FDC8272.DriveSelector, + Z80PIOPortListener, + Z80SIOChannelListener, + Z80TStatesListener +{ + public static final String PROP_KEYBOARD_KEY = "keyboard"; + public static final String PROP_KEYBOARD_VALUE_PIO00A_HS = "pio00a_hs"; + public static final String PROP_KEYBOARD_VALUE_PIO00A_BIT7 = "pio00a_bit7"; + public static final String PROP_KEYBOARD_SWAP_CASE = "swap_case"; + + public static final String PROP_GRAPHIC_KEY = "graphic"; + public static final String PROP_GRAPHIC_VALUE_64X32 = "64x32"; + public static final String PROP_GRAPHIC_VALUE_80X24 = "80x24"; + public static final String PROP_GRAPHIC_VALUE_80X25 = "80x25"; + public static final String PROP_GRAPHIC_VALUE_POPPE = "poppe"; + + private enum GraphicHW { + Video2_64x32, + Video3_80x24, + Video3_80x25, + Poppe_64x32_80x24 }; + private enum KeyboardHW { PIO00A_HS, PIO00A_BIT7 }; + + private static FloppyDiskInfo epos20Disk64x32 = + new FloppyDiskInfo( + "/disks/nanos/epos20_64x32.dump.gz", + "EPOS 2.0 Boot-Diskette (64x32 Zeichen)", + 2, 2048, true ); + + private static FloppyDiskInfo epos20Disk80x24 = + new FloppyDiskInfo( + "/disks/nanos/epos20_80x24.dump.gz", + "EPOS 2.0 Boot-Diskette (80x24 Zeichen)", + 2, 2048, true ); + + private static FloppyDiskInfo nanos22Disk80x25 = + new FloppyDiskInfo( + "/disks/nanos/nanos22_80x25.dump.gz", + "NANOS 2.2 Boot-Diskette", + 2, 2048, true ); + + private static byte[] romEpos = null; + private static byte[] romNanos = null; + private static byte[] fontNanos = null; + + private byte[] fontBytes8x6; + private byte[] fontBytes8x8; + private byte[] ram1000; + private byte[] ramVideoText; + private byte[] ramVideoColor; + private byte[] romBytes; + private String romProp; + private byte[] ram256k; + private Z80PIO pio00; + private Z80PIO pio80; + private Z80SIO sio84; + private Z80PIO pio88; + private Z80CTC ctc8C; + private KCNet kcNet; + private VDIP vdip; + private GIDE gide; + private FDC8272 fdc; + private FloppyDiskDrive[] fdDrives; + private boolean fdcTC; + private boolean audioInPhase; + private boolean altFontSelected; + private boolean colorRamSelected; + private boolean mode64x32; + private boolean fillPixLines8And9; + private boolean bootMemEnabled; + private boolean swapKeyCharCase; + private boolean ram256kEnabled; + private boolean ram256kReadable; + private int ram256kMemBaseAddr; + private int ram256kRFBaseAddr; + private long pasteTStates; + private Color[] colors; + private GraphicHW graphicHW; + private KeyboardHW keyboardHW; + + + public NANOS( EmuThread emuThread, Properties props ) + { + super( emuThread, props, "jkcemu.nanos." ); + this.graphicHW = getGraphicHW( props ); + if( this.graphicHW == GraphicHW.Poppe_64x32_80x24 ) { + this.ramVideoText = new byte[ 0x0800 ]; + this.ramVideoColor = new byte[ 0x0800 ]; + this.colors = new Color[ 16 ]; + float f = getBrightness( props ); + if( (this.colors != null) && (f >= 0F) && (f <= 1F) ) { + for( int i = 0; i < this.colors.length; i++ ) { + int v = Math.round( ((i & 0x08) != 0 ? 0xFF : 0xBF) * f ); + this.colors[ i ] = new Color( + (i & 0x01) != 0 ? v : 0, + (i & 0x02) != 0 ? v : 0, + (i & 0x04) != 0 ? v : 0 ); + } + } + } else { + this.ramVideoText = null; + this.ramVideoColor = null; + this.colors = null; + } + this.keyboardHW = getKeyboardHW( props ); + this.swapKeyCharCase = false; + this.fontBytes8x6 = null; + this.fontBytes8x8 = null; + this.romBytes = null; + this.romProp = null; + this.ram1000 = new byte[ 0x0400 ]; + this.ram256k = new byte[ 0x40000 ]; + this.fdc = new FDC8272( this, 4 ); + this.fdDrives = new FloppyDiskDrive[ 4 ]; + Arrays.fill( this.fdDrives, null ); + setFDCSpeed( false ); + + this.kcNet = null; + if( emulatesKCNet( props ) ) { + this.kcNet = new KCNet( "Netzwerk-PIO (E/A-Adressen 80h-83h)" ); + } + + this.vdip = null; + if( emulatesUSB( props ) ) { + this.vdip = new VDIP( + this.emuThread.getFileTimesViewFactory(), + "USB-PIO (E/A-Adressen 88h-8Bh)" ); + } + + this.gide = GIDE.getGIDE( this.screenFrm, props, this.propPrefix ); + + this.pio00 = new Z80PIO( "ZRE-PIO (00-03)" ); + this.pio80 = null; + if( this.kcNet == null ) { + this.pio80 = new Z80PIO( "PIO (80-83)" ); + } + this.sio84 = new Z80SIO( "SIO (84-87)" ); + this.pio88 = null; + if( this.vdip == null ) { + this.pio88 = new Z80PIO( "PIO (88-8B)" ); + } + this.ctc8C = new Z80CTC( "CTC (8C-8F)" ); + + java.util.List iSources = new ArrayList<>(); + iSources.add( this.pio00 ); + if( this.kcNet != null ) { + iSources.add( this.kcNet ); + } else if( this.pio80 != null ) { + iSources.add( this.pio80 ); + } + iSources.add( this.sio84 ); + if( this.vdip != null ) { + iSources.add( this.vdip ); + } else if( this.pio88 != null ) { + iSources.add( this.pio88 ); + } + iSources.add( this.ctc8C ); + + Z80CPU cpu = emuThread.getZ80CPU(); + try { + cpu.setInterruptSources( + iSources.toArray( new Z80InterruptSource[ iSources.size() ] ) ); + } + catch( ArrayStoreException ex ) {} + cpu.addTStatesListener( this ); + this.pio00.addPIOPortListener( this, Z80PIO.PortInfo.A ); + this.sio84.addChannelListener( this, 0 ); + + if( this.kcNet != null ) { + this.kcNet.z80MaxSpeedChanged( cpu ); + cpu.addMaxSpeedListener( this.kcNet ); + } + if( this.vdip != null ) { + this.vdip.applySettings( props ); + } + + if( !isReloadExtROMsOnPowerOnEnabled( props ) ) { + loadROMs( props ); + } + } + + + public static FloppyDiskInfo[] getAvailableFloppyDisks() + { + return new FloppyDiskInfo[] { + epos20Disk64x32, + epos20Disk80x24, + nanos22Disk80x25 }; + } + + + public static int getDefaultSpeedKHz() + { + return 2457; + } + + + public static boolean getDefaultSwapKeyCharCase() + { + return false; + } + + + /* --- FDC8272.DriveSelector --- */ + + @Override + public FloppyDiskDrive getFloppyDiskDrive( int driveNum ) + { + FloppyDiskDrive rv = null; + if( this.fdDrives != null ) { + if( (driveNum >= 0) && (driveNum < this.fdDrives.length) ) { + rv = this.fdDrives[ driveNum ]; + } + } + return rv; + } + + + /* --- Z80PIOPortListener --- */ + + @Override + public void z80PIOPortStatusChanged( + Z80PIO pio, + Z80PIO.PortInfo port, + Z80PIO.Status status ) + { + if( (pio == this.pio00) + && (port == Z80PIO.PortInfo.A) + && (status == Z80PIO.Status.READY_FOR_INPUT) + && (this.keyboardHW == KeyboardHW.PIO00A_HS) + && (this.pasteIter != null) ) + { + this.pasteTStates = 50000; + } + } + + + /* --- Z80SIOChannelListener --- */ + + @Override + public void z80SIOChannelByteAvailable( Z80SIO sio, int channel, int value ) + { + if( (sio == this.sio84) && (channel == 1) ) + this.emuThread.getPrintMngr().putByte( value ); + } + + + /* --- Z80TStatesListener --- */ + + @Override + public void z80TStatesProcessed( Z80CPU cpu, int tStates ) + { + boolean phase = this.emuThread.readAudioPhase(); + if( phase != this.audioInPhase ) { + this.audioInPhase = phase; + this.pio00.putInValuePortB( this.audioInPhase ? 0x20 : 0, 0x20 ); + } + this.ctc8C.z80TStatesProcessed( cpu, tStates ); + this.fdc.z80TStatesProcessed( cpu, tStates ); + if( this.kcNet != null ) { + this.kcNet.z80TStatesProcessed( cpu, tStates ); + } + if( (this.keyboardHW == KeyboardHW.PIO00A_HS) + && (this.pasteTStates > 0) ) + { + this.pasteTStates -= tStates; + synchronized( this ) { + if( this.pasteTStates <= 0 ) { + if( this.pasteIter != null ) { + char ch = this.pasteIter.next(); + if( ch == CharacterIterator.DONE ) { + cancelPastingText(); + } else { + putKeyChar( ch ); + } + } + } + } + } + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public void appendStatusHTMLTo( StringBuilder buf, Z80CPU cpu ) + { + buf.append( "

    NANOS Konfiguration

    \n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" ); + if( this.graphicHW == GraphicHW.Poppe_64x32_80x24 ) { + buf.append( "\n" ); + if( this.mode64x32 ) { + buf.append( "\n" ); + } else { + buf.append( "\n" ); + } + buf.append( "\n" ); + } + buf.append( "
    ZRE-ROM/RAM:" ); + buf.append( this.bootMemEnabled ? "ein" : "aus" ); + buf.append( "
    256K RAM:" ); + if( this.ram256kEnabled ) { + buf.append( this.ram256kReadable ? + "Lesen und Schreiben" + : "Nur Schreiben" ); + } else { + buf.append( "aus" ); + } + buf.append( "
    RAM-Bank für Hauptspeicher:" ); + buf.append( this.ram256kMemBaseAddr >> 16 ); + buf.append( "
    RAM-Floppy Sektor-Adresse:" ); + buf.append( String.format( "%05Xh", this.ram256kRFBaseAddr ) ); + buf.append( "
    Bildschirmformat:" ); + buf.append( this.mode64x32 ? "64x32" : "80x24" ); + buf.append( "
    Zeichensatz:" ); + buf.append( this.altFontSelected ? "2" : "1 (Standard)" ); + buf.append( "
    Zeilenzwischenraum:" ); + buf.append( + this.fillPixLines8And9 ? "Blockgrafik" : "Hintergrundfarbe" ); + buf.append( "
    F800h-FFFFh:" ); + if( this.colorRamSelected ) { + buf.append( "Farb" ); + } else { + buf.append( "Text" ); + } + buf.append( "-RAM
    \n" ); + } + + + @Override + public void applySettings( Properties props ) + { + super.applySettings( props ); + if( EmuUtil.getProperty( + props, + this.propPrefix + PROP_KEYBOARD_KEY ).equals( + PROP_KEYBOARD_VALUE_PIO00A_BIT7 ) ) + { + this.keyboardHW = KeyboardHW.PIO00A_BIT7; + } else { + this.keyboardHW = KeyboardHW.PIO00A_HS; + } + this.swapKeyCharCase = EmuUtil.getBooleanProperty( + props, + this.propPrefix + PROP_KEYBOARD_SWAP_CASE, + false ); + loadFonts( props ); + } + + + @Override + public boolean canApplySettings( Properties props ) + { + boolean rv = EmuUtil.getProperty( + props, + "jkcemu.system" ).equals( "NANOS" ); + if( rv ) { + rv = TextUtil.equals( + this.romProp, + EmuUtil.getProperty( props, this.propPrefix + "rom" ) ); + } + if( rv && (this.graphicHW != getGraphicHW( props )) ) { + rv = false; + } + if( rv && (this.keyboardHW != getKeyboardHW( props )) ) { + rv = false; + } + if( rv && (emulatesKCNet( props ) != (this.kcNet != null)) ) { + rv = false; + } + if( rv && (emulatesUSB( props ) != (this.vdip != null)) ) { + rv = false; + } + if( rv ) { + rv = GIDE.complies( this.gide, props, this.propPrefix ); + } + return rv; + } + + + public synchronized void cancelPastingText() + { + if( this.keyboardHW == KeyboardHW.PIO00A_HS ) { + if( this.pasteIter != null ) { + this.pasteIter = null; + this.screenFrm.firePastingTextFinished(); + } + } else { + super.cancelPastingText(); + } + } + + + + @Override + public boolean canExtractScreenText() + { + return true; + } + + + @Override + public void die() + { + this.sio84.removeChannelListener( this, 0 ); + + Z80CPU cpu = this.emuThread.getZ80CPU(); + cpu.setInterruptSources( (Z80InterruptSource[]) null ); + cpu.removeTStatesListener( this ); + this.fdc.die(); + if( this.kcNet != null ) { + cpu.removeMaxSpeedListener( this.kcNet ); + this.kcNet.die(); + } + if( this.vdip != null ) { + this.vdip.die(); + } + if( this.gide != null ) { + this.gide.die(); + } + } + + + @Override + public int getAppStartStackInitValue() + { + return 0xE600; + } + + + @Override + public Color getColor( int colorIdx ) + { + Color color = Color.black; + if( (this.ramVideoColor != null) && (this.colors != null) ) { + if( (colorIdx >= 0) && (colorIdx < this.colors.length) ) { + color = this.colors[ colorIdx ]; + } + } else { + if( colorIdx > 0 ) { + color = Color.white; + } + } + return color; + } + + + @Override + public int getColorCount() + { + return this.ramVideoColor != null ? 16 : 2; + } + + + @Override + public int getColorIndex( int x, int y ) + { + int rv = BLACK; + if( (this.graphicHW == GraphicHW.Poppe_64x32_80x24) + && (this.ramVideoText != null) && (this.ramVideoColor != null) ) + { + byte[] fontBytes = this.fontBytes8x8; + int fontOffs = 0; + int pixPerCol = 8; + int pixPerRow = 10; + int colsPerRow = 80; + int charsOnScreen = 1920; // 80x24 + if( this.mode64x32 ) { + fontBytes = this.fontBytes8x6; + pixPerCol = 6; + pixPerRow = 8; + colsPerRow = 64; + charsOnScreen = 2048; + if( this.altFontSelected && (fontBytes != null) ) { + if( fontBytes.length > 0x0800 ) { + fontOffs = 0x0800; + } + } + } else { + y -= 8; + } + if( (y >= 0) && (fontBytes != null) ) { + int rPix = y % pixPerRow; + int row = y / pixPerRow; + int col = x / pixPerCol; + if( (rPix >= 8) && this.fillPixLines8And9 ) { + rPix -= 8; + fontOffs = 0x0800; + } + int mIdx = (row * colsPerRow) + col; + if( (mIdx >= 0) && (mIdx < charsOnScreen) ) { + if( mIdx < this.ramVideoText.length ) { + rv = (int) this.ramVideoColor[ mIdx ] & 0xFF; + } + if( rPix < 8 ) { + int ch = 0; + if( mIdx < this.ramVideoText.length ) { + ch = (int) this.ramVideoText[ mIdx ] & 0xFF; + } + int fIdx = (ch * 8) + rPix; + if( (fIdx >= 0) && (fIdx < fontBytes.length ) ) { + int m = 0x80; + int n = x % pixPerCol; + if( n > 0 ) { + m >>= n; + } + if( (fontBytes[ fIdx ] & m) == 0 ) { + rv >>= 4; + } + } + } else { + rv >>= 4; + } + rv &= 0x0F; + } + } + } else { + if( this.fontBytes8x8 != null ) { + int pixPerRow = 10; + int colsPerRow = 80; + int charsOnScreen = 1920; // 80x24 + switch( this.graphicHW ) { + case Video2_64x32: + pixPerRow = 8; + colsPerRow = 64; + charsOnScreen = 2048; + break; + case Video3_80x25: + charsOnScreen = 2000; + break; + } + int rPix = y % pixPerRow; + int row = y / pixPerRow; + int col = x / 8; + if( rPix < 8 ) { + int mIdx = (row * colsPerRow) + col; + if( (mIdx >= 0) && (mIdx < charsOnScreen) ) { + int ch = this.emuThread.getRAMByte( mIdx + 0xF800 ); + int fIdx = (ch * 8) + rPix; + if( (fIdx >= 0) && (fIdx < this.fontBytes8x8.length ) ) { + int m = 0x80; + int n = x % 8; + if( n > 0 ) { + m >>= n; + } + if( (this.fontBytes8x8[ fIdx ] & m) != 0 ) { + rv = WHITE; + } + } + } + } + } + } + return rv; + } + + + @Override + public CharRaster getCurScreenCharRaster() + { + CharRaster rv = null; + switch( this.graphicHW ) { + case Video2_64x32: + rv = new CharRaster( 64, 32, 8, 8, 8, 0 ); + break; + case Video3_80x24: + rv = new CharRaster( 80, 24, 10, 8, 8, 0 ); + break; + case Poppe_64x32_80x24: + if( this.mode64x32 ) { + rv = new CharRaster( 64, 32, 8, 8, 6, 0 ); + } else { + rv = new CharRaster( 80, 24, 10, 8, 8, 8 ); + } + break; + } + return rv != null ? rv : new CharRaster( 80, 25, 10, 8, 8, 0 ); + } + + + @Override + public FloppyDiskFormat getDefaultFloppyDiskFormat() + { + return FloppyDiskFormat.FMT_780K; + } + + + @Override + protected long getDelayMillisAfterPasteChar() + { + return 80; + } + + + @Override + protected long getDelayMillisAfterPasteEnter() + { + return 200; + } + + + @Override + protected long getHoldMillisPasteChar() + { + return 80; + } + + + @Override + public String getHelpPage() + { + return "/help/nanos.htm"; + } + + + @Override + public int getMemByte( int addr, boolean m1 ) + { + addr &= 0xFFFF; + + int rv = 0xFF; + if( this.bootMemEnabled && (addr < 0x1400) ) { + if( (addr < 0x1000) && (this.romBytes != null) ) { + if( addr < this.romBytes.length ) { + rv = (int) this.romBytes[ addr ] & 0xFF; + } + } + if( (addr >= 0x1000) && (addr < 0x1400) ) { + int idx = addr - 0x1000; + if( idx < this.ram1000.length ) { + rv = (int) this.ram1000[ idx ] & 0xFF; + } + } + } + else if( (addr >= 0xF800) + && (this.ramVideoText != null) + && (this.ramVideoColor != null) ) + { + int idx = addr - 0xF800; + if( this.colorRamSelected ) { + if( idx < this.ramVideoColor.length ) { + rv = (int) this.ramVideoColor[ idx ] & 0xFF; + } + } else { + if( idx < this.ramVideoText.length ) { + rv = (int) this.ramVideoText[ idx ] & 0xFF; + } + } + } else { + if( this.ram256kEnabled && this.ram256kReadable ) { + if( (addr >= 0xF700) && (addr < 0xF800) ) { + int idx = this.ram256kRFBaseAddr | ((addr - 0xF700) & 0x000FF); + if( idx < this.ram256k.length ) { + rv = (int) this.ram256k[ idx ] & 0xFF; + } + } else { + int idx = this.ram256kMemBaseAddr | addr; + if( idx < this.ram256k.length ) { + rv = this.ram256k[ idx ] & 0xFF; + } + } + } + } + return rv; + } + + + @Override + protected int getScreenChar( CharRaster chRaster, int chX, int chY ) + { + int ch = -1; + int nCols = 0; + int nRows = 0; + switch( this.graphicHW ) { + case Video2_64x32: + nCols = 64; + nRows = 32; + break; + case Video3_80x24: + nCols = 80; + nRows = 24; + break; + case Video3_80x25: + nCols = 80; + nRows = 25; + break; + case Poppe_64x32_80x24: + if( this.mode64x32 ) { + nCols = 64; + nRows = 32; + } else { + nCols = 80; + nRows = 24; + } + break; + } + if( (chX >= 0) && (chX < nCols) && (chY >= 0) && (chY < nRows) ) { + int b = 0; + int idx = (chY * nCols) + chX; + if( this.ramVideoText != null ) { + if( idx < this.ramVideoText.length ) { + b = (int) this.ramVideoText[ idx ] & 0xFF; + } + } else { + b = this.emuThread.getRAMByte( 0xF800 + idx ); + } + if( this.fontBytes8x8 == fontNanos ) { + switch( b ) { + case 0x5C: + ch = '\u0278'; + break; + case 0x5E: + ch = '\u00AC'; + break; + case 0x60: + ch = '\\'; + break; + default: + if( (b >= 0x20) && (b < 0x7F) ) { + ch = b; + } + } + } + } + return ch; + } + + + @Override + public int getScreenHeight() + { + int rv = 0; + switch( this.graphicHW ) { + case Video2_64x32: + case Poppe_64x32_80x24: + rv = 256; + break; + case Video3_80x24: + rv = 240; + break; + case Video3_80x25: + rv = 250; + break; + } + return rv; + } + + + @Override + public int getScreenWidth() + { + int rv = 640; + switch( this.graphicHW ) { + case Video2_64x32: + rv = 512; + break; + case Poppe_64x32_80x24: + if( this.mode64x32 ) { + rv = 384; + } + break; + } + return rv; + } + + + @Override + public FloppyDiskInfo[] getSuitableFloppyDisks() + { + FloppyDiskInfo[] rv = null; + if( (this.romBytes == romEpos) + && (this.keyboardHW == KeyboardHW.PIO00A_BIT7) ) + { + if( this.graphicHW == GraphicHW.Video2_64x32 ) { + rv = new FloppyDiskInfo[] { epos20Disk64x32 }; + } + else if( this.graphicHW == GraphicHW.Video3_80x24 ) { + rv = new FloppyDiskInfo[] { epos20Disk80x24 }; + } + else if( this.graphicHW == GraphicHW.Poppe_64x32_80x24 ) { + rv = new FloppyDiskInfo[] { epos20Disk64x32, epos20Disk80x24 }; + + } + } + else if( (this.romBytes == romNanos) + && (this.keyboardHW == KeyboardHW.PIO00A_HS) + && (this.graphicHW == GraphicHW.Video3_80x25) ) + { + rv = new FloppyDiskInfo[] { nanos22Disk80x25 }; + } + return rv; + } + + + @Override + public int getSupportedFloppyDiskDriveCount() + { + return this.fdDrives.length; + } + + + @Override + public boolean getSwapKeyCharCase() + { + return this.swapKeyCharCase; + } + + + @Override + public String getTitle() + { + return "NANOS"; + } + + + @Override + protected VDIP getVDIP() + { + return this.vdip; + } + + + @Override + public boolean isPastingText() + { + return this.keyboardHW == KeyboardHW.PIO00A_HS ? + (this.pasteIter != null) + : super.isPastingText(); + } + + + @Override + public boolean keyPressed( + int keyCode, + boolean ctrlDown, + boolean shiftDown ) + { + boolean rv = false; + int ch = 0; + switch( keyCode ) { + case KeyEvent.VK_LEFT: + case KeyEvent.VK_BACK_SPACE: + ch = 8; + break; + + case KeyEvent.VK_RIGHT: + ch = 9; + break; + + case KeyEvent.VK_DOWN: + ch = 0x0A; + break; + + case KeyEvent.VK_UP: + ch = 0x0B; + break; + + case KeyEvent.VK_ENTER: + ch = 0x0D; + break; + + case KeyEvent.VK_SPACE: + ch = 0x20; + break; + + case KeyEvent.VK_DELETE: + ch = 0x7F; + break; + } + if( ch > 0 ) { + rv = keyTyped( (char) ch ); + } + return rv; + } + + + @Override + public void keyReleased() + { + putKeyChar( '\u0000' ); + } + + + @Override + public boolean keyTyped( char ch ) + { + boolean rv = false; + if( (ch > 0) && (ch < 0x7F) ) { + if( this.pio00.isReadyPortA() ) { + putKeyChar( ch ); + rv = true; + } + } + return rv; + } + + + @Override + protected boolean pasteChar( char ch ) + { + boolean rv = false; + if( this.keyboardHW == KeyboardHW.PIO00A_HS ) { + if( ch == '\n' ) { + ch = '\r'; + } + if( (ch > 0) && (ch < 0x7F) ) { + try { + while( !keyTyped( ch ) ) { + Thread.sleep( 100 ); + } + rv = true; + } + catch( InterruptedException ex ) {} + } + } else { + rv = super.pasteChar( ch ); + } + return rv; + } + + + @Override + public int readIOByte( int port, int tStates ) + { + int rv = 0xFF; + + port &= 0xFF; + if( (this.kcNet != null) && ((port & 0xFC) == 0x80) ) { + rv = this.kcNet.read( port ); + } else if( (this.vdip != null) && ((port & 0xFC) == 0x88) ) { + rv = this.vdip.read( port ); + } else if( (this.gide != null) && ((port & 0xD0) == 0x80) ) { + int value = this.gide.read( port ); + if( value >= 0 ) { + rv = value; + } + } else { + if( port < 0x80 ) { + // PIO auf ZRE-Karte + switch( port & 0x03 ) { + case 0: + rv = this.pio00.readDataA(); + break; + case 1: + rv = this.pio00.readDataB(); + break; + case 2: + rv = this.pio00.readControlA(); + break; + case 0x3: + rv = this.pio00.readControlB(); + break; + } + } else { + switch( port ) { + // PIO 0 auf IO-Karte + case 0x80: + if( this.pio80 != null ) { + rv = this.pio80.readDataA(); + } + break; + case 0x81: + if( this.pio80 != null ) { + rv = this.pio80.readDataB(); + } + break; + case 0x82: + if( this.pio80 != null ) { + rv = this.pio80.readControlA(); + } + break; + case 0x83: + if( this.pio80 != null ) { + rv = this.pio80.readControlB(); + } + break; + + // SIO auf IO-Karte + case 0x84: + rv = this.sio84.readDataA(); + break; + case 0x85: + rv = this.sio84.readDataB(); + break; + case 0x86: + rv = this.sio84.readControlA(); + break; + case 0x87: + rv = this.sio84.readControlB(); + break; + + // PIO 1 auf IO-Karte + case 0x88: + if( this.pio88 != null ) { + rv = this.pio88.readDataA(); + } + break; + case 0x89: + if( this.pio88 != null ) { + rv = this.pio88.readDataB(); + } + break; + case 0x8A: + if( this.pio88 != null ) { + rv = this.pio88.readControlA(); + } + break; + case 0x8B: + if( this.pio88 != null ) { + rv = this.pio88.readControlB(); + } + break; + + // CTC auf IO-Karte + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + rv = this.ctc8C.read( port & 0x03, tStates ); + break; + + // FDC-Karte + case 0x94: + rv = this.fdc.readMainStatusReg(); + break; + case 0x95: + rv = this.fdc.readData(); + break; + + // Farbgrafikkarte + case 0xF2: + if( this.graphicHW == GraphicHW.Poppe_64x32_80x24 ) { + rv = 0xF0; + if( this.colorRamSelected ) { + rv |= 0x01; + } + if( !this.mode64x32 ) { + rv |= 0x02; + } + if( this.fillPixLines8And9 ) { + rv |= 0x04; + } + if( this.altFontSelected ) { + rv |= 0x08; + } + } + break; + } + } + } + return rv; + } + + + @Override + public void reset( EmuThread.ResetLevel resetLevel, Properties props ) + { + if( resetLevel == EmuThread.ResetLevel.POWER_ON ) { + if( isReloadExtROMsOnPowerOnEnabled( props ) ) { + loadROMs( props ); + } + initSRAM( this.ram1000, props ); + Arrays.fill( this.ram256k, (byte) 0xFF ); + if( this.ramVideoText != null ) { + fillRandom( this.ramVideoText ); + } + if( this.ramVideoColor != null ) { + fillRandom( this.ramVideoColor ); + } + } + this.fdc.reset( resetLevel == EmuThread.ResetLevel.POWER_ON ); + boolean coldReset = ((resetLevel == EmuThread.ResetLevel.POWER_ON) + || (resetLevel == EmuThread.ResetLevel.COLD_RESET)); + this.pio00.reset( coldReset ); + if( this.pio80 != null ) { + this.pio80.reset( coldReset ); + } + this.sio84.reset( coldReset ); + if( this.pio88 != null ) { + this.pio88.reset( coldReset ); + } + this.ctc8C.reset( coldReset ); + if( this.gide != null ) { + this.gide.reset(); + } + setFDCSpeed( false ); + this.fdcTC = false; + for( int i = 0; i < this.fdDrives.length; i++ ) { + FloppyDiskDrive drive = this.fdDrives[ i ]; + if( drive != null ) { + drive.reset(); + } + } + switch( this.graphicHW ) { + case Video2_64x32: + case Poppe_64x32_80x24: + setMode64x32( true ); + break; + case Video3_80x24: + case Video3_80x25: + setMode64x32( false ); + break; + } + this.altFontSelected = false; + this.colorRamSelected = false; + this.fillPixLines8And9 = false; + this.audioInPhase = this.emuThread.readAudioPhase(); + this.bootMemEnabled = true; + this.ram256kEnabled = false; + this.ram256kReadable = false; + this.ram256kMemBaseAddr = 0; + this.ram256kRFBaseAddr = 0; + this.pasteTStates = 0; + + // Initialzustand fuer Tastatur + this.pio00.putInValuePortA( 0x00, 0xFF ); + } + + + @Override + public void setFloppyDiskDrive( int idx, FloppyDiskDrive drive ) + { + if( this.fdDrives != null ) { + if( (idx >= 0) && (idx < this.fdDrives.length) ) { + this.fdDrives[ idx ] = drive; + } + } + } + + + @Override + public boolean setMemByte( int addr, int value ) + { + addr &= 0xFFFF; + + boolean rv = false; + if( this.bootMemEnabled && (addr >= 0x1000) && (addr < 0x1400) ) { + int idx = addr - 0x1000; + if( idx < this.ram1000.length ) { + this.ram1000[ idx ] = (byte) value; + } + rv = true; + } + if( this.ram256kEnabled ) { + if( (addr >= 0xF700) && (addr < 0xF800) ) { + int idx = this.ram256kRFBaseAddr | ((addr - 0xF700) & 0x000FF); + if( idx < this.ram256k.length ) { + this.ram256k[ idx ] = (byte) value; + } + } else { + int idx = this.ram256kMemBaseAddr | addr; + if( idx < this.ram256k.length ) { + this.ram256k[ idx ] = (byte) value; + } + } + rv = true; + } + if( addr >= 0xF800 ) { + boolean done = false; + if( (this.graphicHW == GraphicHW.Poppe_64x32_80x24) + && (this.ramVideoText != null) && (this.ramVideoColor != null) ) + { + int idx = addr - 0xF800; + if( this.colorRamSelected ) { + if( idx < this.ramVideoColor.length ) { + this.ramVideoColor[ idx ] = (byte) value; + done = true; + } + } else { + if( idx < this.ramVideoText.length ) { + this.ramVideoText[ idx ] = (byte) value; + done = true; + } + } + } + if( !done ) { + this.emuThread.setRAMByte( addr, value ); + } + this.screenFrm.setScreenDirty( true ); + rv = true; + } + return rv; + } + + + @Override + public boolean shouldAskConvertScreenChar() + { + return (this.fontBytes8x8 != fontNanos); + } + + + public synchronized void startPastingText( String text ) + { + if( this.keyboardHW == KeyboardHW.PIO00A_HS ) { + boolean done = false; + if( text != null ) { + if( !text.isEmpty() ) { + cancelPastingText(); + this.pasteIter = new StringCharacterIterator( text ); + if( this.pio00.isReadyPortA() ) { + putKeyChar( this.pasteIter.first() ); + } + done = true; + } + } + if( !done ) { + this.screenFrm.firePastingTextFinished(); + } + } else { + super.startPastingText( text ); + } + } + + + @Override + public boolean supportsAudio() + { + return true; + } + + + @Override + public boolean supportsPrinter() + { + return true; + } + + + @Override + public boolean supportsCopyToClipboard() + { + return true; + } + + + @Override + public boolean supportsPasteFromClipboard() + { + return true; + } + + + @Override + public void writeIOByte( int port, int value, int tStates ) + { + port &= 0xFF; + if( (this.kcNet != null) && ((port & 0xFC) == 0x80) ) { + this.kcNet.write( port, value ); + } else if( (this.vdip != null) && ((port & 0xFC) == 0x88) ) { + this.vdip.write( port, value ); + } else if( (this.gide != null) && ((port & 0xF0) == 0xD0) ) { + this.gide.write( port, value ); + } else { + if( port < 0x80 ) { + // PIO auf ZRE-Karte + switch( port & 0x03 ) { + case 0: + this.pio00.writeDataA( value ); + break; + case 1: + { + this.pio00.writeDataB( value ); + int outValue = this.pio00.fetchOutValuePortB( false ); + this.bootMemEnabled = ((outValue & 0x80) != 0); + this.emuThread.writeAudioPhase( (outValue & 0x40) != 0 ); + } + break; + case 2: + this.pio00.writeControlA( value ); + break; + case 3: + this.pio00.writeControlB( value ); + break; + } + } else { + switch( port ) { + // 256K RAM Karte + case 0xC0: + this.ram256kRFBaseAddr = (this.ram256kRFBaseAddr & 0x30000) + | ((value << 8) & 0x0FF00); + break; + case 0xC2: + this.ram256kMemBaseAddr = ((value << 12) & 0x30000); + this.ram256kRFBaseAddr = (this.ram256kRFBaseAddr & 0x0FF00) + | ((value << 10) & 0x30000); + break; + case 0xC4: + this.ram256kEnabled = false; + break; + case 0xC5: + this.ram256kEnabled = true; + break; + case 0xC6: + this.ram256kReadable = false; + break; + case 0xC7: + this.ram256kReadable = true; + break; + + // PIO 0 auf IO-Karte + case 0x80: + if( this.pio80 != null ) { + this.pio80.writeDataA( value ); + } + break; + case 0x81: + if( this.pio80 != null ) { + this.pio80.writeDataB( value ); + } + break; + case 0x82: + if( this.pio80 != null ) { + this.pio80.writeControlA( value ); + } + break; + case 0x83: + if( this.pio80 != null ) { + this.pio80.writeControlB( value ); + } + break; + + // SIO auf IO-Karte + case 0x84: + this.sio84.writeDataA( value ); + break; + case 0x85: + this.sio84.writeDataB( value ); + break; + case 0x86: + this.sio84.writeControlA( value ); + break; + case 0x87: + this.sio84.writeControlB( value ); + break; + + // PIO 1 auf IO-Karte + case 0x88: + if( this.pio88 != null ) { + this.pio88.writeDataA( value ); + } + break; + case 0x89: + if( this.pio88 != null ) { + this.pio88.writeDataB( value ); + } + break; + case 0x8A: + if( this.pio88 != null ) { + this.pio88.writeControlA( value ); + } + break; + case 0x8B: + if( this.pio88 != null ) { + this.pio88.writeControlB( value ); + } + break; + + // CTC auf IO-Karte + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + this.ctc8C.write( port & 0x03, value, tStates ); + break; + + // FDC-Karte + case 0x92: + { + setFDCSpeed( (value & 0x01) != 0 ); + boolean tc = ((value & 0x02) != 0); + if( tc && (tc != this.fdcTC) ) { + this.fdc.fireTC(); + } + this.fdcTC = tc; + } + break; + case 0x95: + this.fdc.write( value ); + break; + + // Farbgrafikkarte + case 0xF1: + if( this.graphicHW == GraphicHW.Poppe_64x32_80x24 ) { + this.altFontSelected = ((value & 0x08) != 0); + this.fillPixLines8And9 = ((value & 0x04) != 0); + setMode64x32( (value & 0x02) == 0 ); + } + break; + case 0xF2: + if( this.graphicHW == GraphicHW.Poppe_64x32_80x24 ) { + this.colorRamSelected = ((value & 0x01) != 0); + } + break; + } + } + } + } + + + /* --- private Methoden --- */ + + private boolean emulatesKCNet( Properties props ) + { + return EmuUtil.getBooleanProperty( + props, + this.propPrefix + "kcnet.enabled", + false ); + } + + + private boolean emulatesUSB( Properties props ) + { + return EmuUtil.getBooleanProperty( + props, + this.propPrefix + "vdip.enabled", + false ); + } + + + private GraphicHW getGraphicHW( Properties props ) + { + GraphicHW rv = GraphicHW.Video3_80x25; + switch( EmuUtil.getProperty( + props, + this.propPrefix + PROP_GRAPHIC_KEY ) ) + { + case PROP_GRAPHIC_VALUE_64X32: + rv = GraphicHW.Video2_64x32; + break; + case PROP_GRAPHIC_VALUE_80X24: + rv = GraphicHW.Video3_80x24; + break; + case PROP_GRAPHIC_VALUE_POPPE: + rv = GraphicHW.Poppe_64x32_80x24; + break; + } + return rv; + } + + + private KeyboardHW getKeyboardHW( Properties props ) + { + return EmuUtil.getProperty( + props, + this.propPrefix + PROP_KEYBOARD_KEY ).equals( + PROP_KEYBOARD_VALUE_PIO00A_BIT7 ) ? + KeyboardHW.PIO00A_BIT7 : KeyboardHW.PIO00A_HS; + } + + + private byte[] getNanosFontBytes() + { + if( fontNanos == null ) { + fontNanos = readResource( "/rom/nanos/nanosfont.bin" ); + } + return fontNanos; + } + + + private void loadFonts( Properties props ) + { + this.fontBytes8x8 = readFontByProperty( + props, + this.propPrefix + "font.8x8.file", + 0x1000 ); + if( this.fontBytes8x8 == null ) { + this.fontBytes8x8 = getNanosFontBytes(); + } + if( this.graphicHW == GraphicHW.Poppe_64x32_80x24 ) { + this.fontBytes8x6 = readFontByProperty( + props, + this.propPrefix + "font.8x6.file", + 0x1000 ); + if( this.fontBytes8x6 == null ) { + this.fontBytes8x6 = getNanosFontBytes(); + } + } + } + + + private void loadROMs( Properties props ) + { + this.romProp = EmuUtil.getProperty( + props, + this.propPrefix + "rom" ); + String upperProp = this.romProp.toUpperCase(); + if( (this.romProp.length() > 5) && upperProp.startsWith( "FILE:" ) ) { + this.romBytes = readROMFile( + this.romProp.substring( 5 ), + 0x1000, + "ROM-Inhalt" ); + } + if( (this.romBytes == null) && upperProp.startsWith( "EPOS" ) ) { + if( romEpos == null ) { + romEpos = readResource( "/rom/nanos/eposrom.bin" ); + } + this.romBytes = romEpos; + } + if( this.romBytes == null ) { + if( romNanos == null ) { + romNanos = readResource( "/rom/nanos/nanosrom.bin" ); + } + this.romBytes = romNanos; + } + loadFonts( props ); + } + + + private void putKeyChar( char ch ) + { + if( this.fontBytes8x8 == fontNanos ) { + switch( ch ) { + case '\u0278': + ch = '|'; + break; + case '\u00AC': + ch = '~'; + break; + case '\\': + ch = '\u0060'; + break; + } + } + switch( this.keyboardHW ) { + case PIO00A_HS: + if( ch > '\u0000' ) { + this.pio00.putInValuePortA( TextUtil.toReverseCase( ch ), true ); + } + break; + case PIO00A_BIT7: + if( ch > '\u0000' ) { + this.pio00.putInValuePortA( ch | 0x80, 0xFF ); + } else { + this.pio00.putInValuePortA( 0, 0xFF ); + } + break; + } + } + + + private void setFDCSpeed( boolean mini ) + { + this.fdc.setTStatesPerMilli( mini ? 4000 : 8000 ); + } + + + private void setMode64x32( boolean state ) + { + boolean oldMode64x32 = this.mode64x32; + this.mode64x32 = state; + if( this.mode64x32 != oldMode64x32 ) { + this.screenFrm.fireScreenSizeChanged(); + } + } +} diff --git a/src/jkcemu/emusys/PCM.java b/src/jkcemu/emusys/PCM.java index b48c774..2e48a2f 100644 --- a/src/jkcemu/emusys/PCM.java +++ b/src/jkcemu/emusys/PCM.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -20,12 +20,28 @@ public class PCM extends EmuSys implements FDC8272.DriveSelector, Z80CTCListener, - Z80SIOChannelListener + Z80SIOChannelListener, + Z80TStatesListener { + public static final String PROP_AUTO_LOAD_BDOS = "auto_load_bdos"; + public static final String PROP_FDC_ENABLDED = "floppydisk.enabled"; + public static final String PROP_GRAPHIC_KEY = "graphic"; + public static final String PROP_GRAPHIC_VALUE_80X24 = "80x24"; + public static final String PROP_GRAPHIC_VALUE_64X32 = "64x32"; + + private static FloppyDiskInfo disk64x16 = new FloppyDiskInfo( + "/disks/pcm/pcmsys330_64x16.dump.gz", + "PC/M Boot-Diskette (64x16 Zeichen)", + 2, 2048, true ); + + private static FloppyDiskInfo disk80x24 = new FloppyDiskInfo( + "/disks/pcm/pcmsys330_80x24.dump.gz", + "PC/M Boot-Diskette (80x24 Zeichen)", + 2, 2048, true ); + private static final FloppyDiskInfo[] availableFloppyDisks = { - new FloppyDiskInfo( - "/disks/pcm/pcmsys.dump.gz", - "PC/M Boot-Diskette" ) }; + disk64x16, + disk80x24 }; private static byte[] bdos = null; private static byte[] romRF64x16 = null; @@ -46,18 +62,21 @@ public class PCM extends EmuSys implements private FloppyDiskDrive curFDDrive; private FloppyDiskDrive[] fdDrives; private boolean fdcTC; + private boolean audioInPhase; private boolean audioOutPhase; private boolean keyboardUsed; private boolean mode80x24; private boolean romEnabled; private boolean upperBank0Enabled; private int ramBank; + private int romSize; private int nmiCounter; public PCM( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.pcm." ); + this.romSize = 0x2000; this.fontBytes = null; this.romBytes = null; this.romFile = null; @@ -86,7 +105,7 @@ public PCM( EmuThread emuThread, Properties props ) "PC/M RAM-B\u00E4nke 1 und 2", EmuUtil.getProperty( props, - "jkcemu.pcm.ramfloppy.file" ) ); + this.propPrefix + "ramfloppy.file" ) ); } Z80CPU cpu = emuThread.getZ80CPU(); @@ -97,8 +116,8 @@ public PCM( EmuThread emuThread, Properties props ) if( this.fdc != null ) { this.fdc.setTStatesPerMilli( cpu.getMaxSpeedKHz() ); cpu.addMaxSpeedListener( this.fdc ); - cpu.addTStatesListener( this.fdc ); } + cpu.addTStatesListener( this ); this.ctc.addCTCListener( this ); this.sio.addChannelListener( this, 0 ); @@ -120,6 +139,12 @@ public static int getDefaultSpeedKHz() } + public static boolean getDefaultSwapKeyCharCase() + { + return true; + } + + /* --- FDC8272.DriveSelector --- */ @Override @@ -153,6 +178,23 @@ public void z80SIOChannelByteAvailable( Z80SIO sio, int channel, int value ) } + /* --- Z80TStatesListener --- */ + + @Override + public void z80TStatesProcessed( Z80CPU cpu, int tStates ) + { + boolean phase = this.emuThread.readAudioPhase(); + if( phase != this.audioInPhase ) { + this.audioInPhase = phase; + this.pio.putInValuePortB( this.audioInPhase ? 0x80 : 0, 0x80 ); + } + this.ctc.z80TStatesProcessed( cpu, tStates ); + if( this.fdc != null ) { + this.fdc.z80TStatesProcessed( cpu, tStates ); + } + } + + /* --- ueberschriebene Methoden --- */ @Override @@ -167,19 +209,21 @@ public void appendStatusHTMLTo( StringBuilder buf, Z80CPU cpu ) } else { buf.append( this.ramBank ); } - buf.append( "
    2000h-BFFFh:RAM Bank " ); - buf.append( this.ramBank ); - buf.append( "
    0000h-1FFFh:" ); - if( this.romEnabled ) { - buf.append( "ROM" ); - } else { - buf.append( "RAM Bank " ); - buf.append( this.ramBank ); + int ramBegAddr = (this.romEnabled ? this.romSize : 0x0000); + buf.append( + String.format( + "
    %04Xh-BFFFh:RAM Bank %d
    0000h-%04Xh:ROM
    \n" ); + buf.append( "\n" ); } @@ -200,7 +244,7 @@ public boolean canApplySettings( Properties props ) if( rv ) { rv = TextUtil.equals( this.romFile, - EmuUtil.getProperty( props, "jkcemu.pcm.rom.file" ) ); + EmuUtil.getProperty( props, this.propPrefix + "rom.file" ) ); } if( rv && emulatesFloppyDisk( props ) != (this.fdc != null) ) { rv = false; @@ -230,8 +274,8 @@ public void die() Z80CPU cpu = this.emuThread.getZ80CPU(); cpu.setInterruptSources( (Z80InterruptSource[]) null ); + cpu.removeTStatesListener( this ); if( this.fdc != null ) { - cpu.removeTStatesListener( this.fdc ); cpu.removeMaxSpeedListener( this.fdc ); this.fdc.die(); } @@ -352,7 +396,7 @@ public int getMemByte( int addr, boolean m1 ) addr &= 0xFFFF; int rv = 0xFF; - if( (addr < 0x2000) && this.romEnabled ) { + if( (addr < this.romSize) && this.romEnabled ) { if( this.romBytes != null ) { if( addr < this.romBytes.length ) { rv = (int) this.romBytes[ addr ] & 0xFF; @@ -480,7 +524,15 @@ public int getScreenWidth() @Override public FloppyDiskInfo[] getSuitableFloppyDisks() { - return this.fdc != null ? availableFloppyDisks : null; + FloppyDiskInfo[] rv = null; + if( this.fdc != null ) { + if( this.mode80x24 ) { + rv = new FloppyDiskInfo[] { disk80x24 }; + } else { + rv = new FloppyDiskInfo[] { disk64x16 }; + } + } + return rv; } @@ -494,7 +546,7 @@ public int getSupportedFloppyDiskDriveCount() @Override public boolean getSwapKeyCharCase() { - return true; + return getDefaultSwapKeyCharCase(); } @@ -596,7 +648,7 @@ protected boolean pasteChar( char ch ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; switch( port & 0xFF ) { @@ -604,7 +656,7 @@ public int readIOByte( int port ) case 0x81: case 0x82: case 0x83: - rv = this.ctc.read( port & 0x03 ); + rv = this.ctc.read( port & 0x03, tStates ); break; case 0x84: @@ -612,13 +664,11 @@ public int readIOByte( int port ) this.pio.putInValuePortA( 0, 0xFF ); this.keyboardUsed = true; } - rv = this.pio.readPortA(); + rv = this.pio.readDataA(); break; case 0x85: - this.pio.putInValuePortB( - this.emuThread.readAudioPhase() ? 0x80 : 0, 0x80 ); - rv = this.pio.readPortB(); + rv = this.pio.readDataB(); break; case 0x86: @@ -692,7 +742,7 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) } if( EmuUtil.getBooleanProperty( props, - "jkcemu.pcm.auto_load_bdos", + this.propPrefix + PROP_AUTO_LOAD_BDOS, true ) ) { if( bdos == null ) { @@ -725,6 +775,7 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) } } } + this.audioInPhase = this.emuThread.readAudioPhase(); this.curFDDrive = null; this.fdcTC = false; this.keyboardUsed = false; @@ -751,7 +802,7 @@ public boolean setMemByte( int addr, int value ) { addr &= 0xFFFF; - boolean rv = false; + boolean rv = false; if( addr >= 0xF800 ) { int idx = addr - 0xF800; if( idx < this.ramVideo.length ) { @@ -760,7 +811,7 @@ public boolean setMemByte( int addr, int value ) rv = true; } } else { - if( (addr >= 0x2000) || !this.romEnabled ) { + if( (addr >= this.romSize) || !this.romEnabled ) { if( (this.ramBank == 0) || ((addr >= 0xC000) && this.upperBank0Enabled) ) { @@ -825,22 +876,22 @@ public boolean supportsRAMFloppy1() @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { switch( port & 0xFF ) { case 0x80: case 0x81: case 0x82: case 0x83: - this.ctc.write( port & 0x03, value ); + this.ctc.write( port & 0x03, value, tStates ); break; case 0x84: - this.pio.writePortA( value ); + this.pio.writeDataA( value ); break; case 0x85: - this.pio.writePortB( value ); + this.pio.writeDataB( value ); if( !this.emuThread.isSoundOutEnabled() ) { this.audioOutPhase = ((this.pio.fetchOutValuePortB( false ) & 0x40) != 0); @@ -899,8 +950,8 @@ public void writeIOByte( int port, int value ) if( this.fdc != null ) { if( this.fdDrives != null ) { FloppyDiskDrive fdd = null; - int v = (value - 1) & 0x03; - int mask = 0x01; + int v = ((value & 0x0f) >> 1) | ((value & 0x01) << 3); + int mask = 0x01; for( int i = 0; i < this.fdDrives.length; i++ ) { if( (v & mask) != 0 ) { fdd = this.fdDrives[ i ]; @@ -923,19 +974,20 @@ public void writeIOByte( int port, int value ) /* --- private Methoden --- */ - private static boolean emulates80x24( Properties props ) + private boolean emulates80x24( Properties props ) { return EmuUtil.getProperty( - props, - "jkcemu.pcm.graphic" ).equals( "80x24" ); + props, + this.propPrefix + PROP_GRAPHIC_KEY ).equals( + PROP_GRAPHIC_VALUE_80X24 ); } - private static boolean emulatesFloppyDisk( Properties props ) + private boolean emulatesFloppyDisk( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.pcm.floppydisk.enabled", + this.propPrefix + PROP_FDC_ENABLDED, true ); } @@ -944,7 +996,7 @@ private void loadFont( Properties props ) { this.fontBytes = readFontByProperty( props, - "jkcemu.pcm.font.file", + this.propPrefix + "font.file", 0x0800 ); if( this.fontBytes == null ) { if( this.mode80x24 ) { @@ -964,12 +1016,23 @@ private void loadFont( Properties props ) private void loadROMs( Properties props ) { - this.romFile = EmuUtil.getProperty( props, "jkcemu.pcm.rom.file" ); - this.romBytes = readFile( + this.romFile = EmuUtil.getProperty( + props, + this.propPrefix + "rom.file" ); + this.romBytes = readROMFile( this.romFile, - 0x2000, + 0x8000, "ROM-Inhalt (Grundbetriebssystem)" ); - if( this.romBytes == null ) { + if( this.romBytes != null ) { + // ROM-Groesse ermitteln + int n8k = (this.romBytes.length + 0x1FFF) / 0x2000; + if( n8k < 1 ) { + n8k = 1; + } else if( n8k > 4 ) { + n8k = 4; + } + this.romSize = n8k * 0x2000; + } else { if( this.fdc != null ) { if( this.mode80x24 ) { if( romFDC80x24 == null ) { diff --git a/src/jkcemu/emusys/Poly880.java b/src/jkcemu/emusys/Poly880.java index 2a9c87f..1b2ecf9 100644 --- a/src/jkcemu/emusys/Poly880.java +++ b/src/jkcemu/emusys/Poly880.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -45,6 +45,7 @@ public class Poly880 extends EmuSys implements private int colMask; private long curDisplayTStates; private long displayCheckTStates; + private boolean audioInPhase; private boolean nmiEnabled; private boolean nmiTrigger; private boolean ram8000; @@ -57,7 +58,7 @@ public class Poly880 extends EmuSys implements public Poly880( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.poly880." ); this.rom0Bytes = null; this.rom1Bytes = null; this.rom2Bytes = null; @@ -152,6 +153,11 @@ public void z80MaxSpeedChanged( Z80CPU cpu ) @Override public void z80TStatesProcessed( Z80CPU cpu, int tStates ) { + boolean phase = this.emuThread.readAudioPhase(); + if( phase != this.audioInPhase ) { + this.audioInPhase = phase; + this.pio1.putInValuePortB( this.audioInPhase ? 0x02 : 0, 0x02 ); + } this.ctc.z80TStatesProcessed( cpu, tStates ); if( this.displayCheckTStates > 0 ) { this.curDisplayTStates += tStates; @@ -189,22 +195,30 @@ public boolean canApplySettings( Properties props ) if( rv ) { rv = TextUtil.equals( this.rom0File, - EmuUtil.getProperty( props, "jkcemu.poly880.rom_0000.file" ) ); + EmuUtil.getProperty( + props, + this.propPrefix + "rom_0000.file" ) ); } if( rv ) { rv = TextUtil.equals( this.rom1File, - EmuUtil.getProperty( props, "jkcemu.poly880.rom_1000.file" ) ); + EmuUtil.getProperty( + props, + this.propPrefix + "rom_1000.file" ) ); } if( rv ) { rv = TextUtil.equals( this.rom2File, - EmuUtil.getProperty( props, "jkcemu.poly880.rom_2000.file" ) ); + EmuUtil.getProperty( + props, + this.propPrefix + "rom_2000.file" ) ); } if( rv ) { rv = TextUtil.equals( this.rom3File, - EmuUtil.getProperty( props, "jkcemu.poly880.rom_3000.file" ) ); + EmuUtil.getProperty( + props, + this.propPrefix + "rom_3000.file" ) ); } if( rv && ((this.rom0File != null) || (this.rom1File != null) @@ -446,13 +460,13 @@ public boolean paintScreen( Graphics g, int x, int y, int screenScale ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; switch( port & 0x8F ) { // A4 bis A6 ignorieren case 0x80: - rv = this.pio1.readPortA(); + rv = this.pio1.readDataA(); break; case 0x81: @@ -471,12 +485,8 @@ public int readIOByte( int port ) m <<= 1; } } - v &= 0xB0; - if( this.emuThread.readAudioPhase() ) { - v |= 0x02; - } - this.pio1.putInValuePortB( v, false ); - rv = this.pio1.readPortB(); + this.pio1.putInValuePortB( v & 0xB0, 0xFD ); + rv = this.pio1.readDataB(); } break; @@ -485,7 +495,7 @@ public int readIOByte( int port ) break; case 0x84: - rv = this.pio2.readPortA(); + rv = this.pio2.readDataA(); break; case 0x85: @@ -493,7 +503,7 @@ public int readIOByte( int port ) break; case 0x86: - rv = this.pio2.readPortB(); + rv = this.pio2.readDataB(); break; case 0x87: @@ -504,7 +514,7 @@ public int readIOByte( int port ) case 0x89: case 0x8A: case 0x8B: - rv = this.ctc.read( port & 0x03 ); + rv = this.ctc.read( port & 0x03, tStates ); } return rv; } @@ -538,9 +548,10 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) synchronized( this.keyboardMatrix ) { Arrays.fill( this.keyboardMatrix, 0 ); } - this.colMask = 0; - this.nmiEnabled = true; - this.nmiTrigger = false; + this.colMask = 0; + this.audioInPhase = this.emuThread.readAudioPhase(); + this.nmiEnabled = true; + this.nmiTrigger = false; } @@ -584,12 +595,12 @@ public boolean supportsKeyboardFld() @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { int v = 0; switch( port & 0x8F ) { // A4 bis A6 ignorieren case 0x80: - this.pio1.writePortA( value ); + this.pio1.writeDataA( value ); break; case 0x81: @@ -597,7 +608,7 @@ public void writeIOByte( int port, int value ) break; case 0x82: - this.pio1.writePortB( value ); + this.pio1.writeDataB( value ); v = this.pio1.fetchOutValuePortB( false ); this.emuThread.writeAudioPhase( (v & (this.emuThread.isSoundOutEnabled() ? @@ -610,7 +621,7 @@ public void writeIOByte( int port, int value ) break; case 0x84: - this.pio2.writePortA( value ); + this.pio2.writeDataA( value ); break; case 0x85: @@ -618,7 +629,7 @@ public void writeIOByte( int port, int value ) break; case 0x86: - this.pio2.writePortB( value ); + this.pio2.writeDataB( value ); break; case 0x87: @@ -629,7 +640,7 @@ public void writeIOByte( int port, int value ) case 0x89: case 0x8A: case 0x8B: - this.ctc.write( port & 0x03, value ); + this.ctc.write( port & 0x03, value, tStates ); } if( (port & 0xB0) == 0xB0 ) { this.colMask = value; @@ -657,20 +668,20 @@ public void writeIOByte( int port, int value ) /* --- private Methoden --- */ - private static boolean emulatesNegatedROMs( Properties props ) + private boolean emulatesNegatedROMs( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.poly880.rom.negated", + this.propPrefix + "rom.negated", false ); } - private static boolean emulatesRAM8000( Properties props ) + private boolean emulatesRAM8000( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.poly880.ram_8000.enabled", + this.propPrefix + "ram_8000.enabled", false ); } @@ -679,8 +690,8 @@ private void loadROMs( Properties props ) { this.rom0File = EmuUtil.getProperty( props, - "jkcemu.poly880.rom_0000.file" ); - this.rom0Bytes = readROMFile( this.rom0File, "ROM 0" ); + this.propPrefix + "rom_0000.file" ); + this.rom0Bytes = readPoly880ROMFile( this.rom0File, "ROM 0" ); if( this.rom0Bytes == null ) { if( mon0000 == null ) { mon0000 = readResource( "/rom/poly880/poly880_0000.bin" ); @@ -690,8 +701,8 @@ private void loadROMs( Properties props ) this.rom1File = EmuUtil.getProperty( props, - "jkcemu.poly880.rom_1000.file" ); - this.rom1Bytes = readROMFile( this.rom1File, "ROM 1" ); + this.propPrefix + "rom_1000.file" ); + this.rom1Bytes = readPoly880ROMFile( this.rom1File, "ROM 1" ); if( this.rom1Bytes == null ) { if( mon1000 == null ) { mon1000 = readResource( "/rom/poly880/poly880_1000.bin" ); @@ -701,19 +712,19 @@ private void loadROMs( Properties props ) this.rom2File = EmuUtil.getProperty( props, - "jkcemu.poly880.rom_2000.file" ); - this.rom2Bytes = readROMFile( this.rom2File, "ROM 2" ); + this.propPrefix + "rom_2000.file" ); + this.rom2Bytes = readPoly880ROMFile( this.rom2File, "ROM 2" ); this.rom3File = EmuUtil.getProperty( props, - "jkcemu.poly880.rom_3000.file" ); - this.rom3Bytes = readROMFile( this.rom3File, "ROM 3" ); + this.propPrefix + "rom_3000.file" ); + this.rom3Bytes = readPoly880ROMFile( this.rom3File, "ROM 3" ); } - private byte[] readROMFile( String fileName, String objName ) + private byte[] readPoly880ROMFile( String fileName, String objName ) { - byte[] rom = readFile( fileName, 0x0400, objName ); + byte[] rom = readROMFile( fileName, 0x0400, objName ); if( (rom != null) && this.extRomsNegated ) { for( int i = 0; i < rom.length; i++ ) { rom[ i ] = (byte) ~rom[ i ]; diff --git a/src/jkcemu/emusys/SC2.java b/src/jkcemu/emusys/SC2.java index 4fc78aa..361b9c9 100644 --- a/src/jkcemu/emusys/SC2.java +++ b/src/jkcemu/emusys/SC2.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -46,7 +46,7 @@ public class SC2 extends EmuSys implements public SC2( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.sc2." ); if( rom0000 == null ) { rom0000 = readResource( "/rom/sc2/sc2_0000.bin" ); } @@ -537,13 +537,13 @@ public boolean paintScreen( Graphics g, int x, int y, int screenScale ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; if( (port & 0x08) == 0 ) { switch( port & 0x03 ) { case 0: - rv = this.pio.readPortA(); + rv = this.pio.readDataA(); break; case 1: @@ -560,7 +560,7 @@ public int readIOByte( int port ) } this.pio.putInValuePortB( v, 0xF0 ); } - rv = this.pio.readPortB(); + rv = this.pio.readDataB(); break; case 2: @@ -643,17 +643,17 @@ public boolean supportsKeyboardFld() @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { if( (port & 0x08) == 0 ) { switch( port & 0x03 ) { case 0: - this.pio.writePortA( value ); + this.pio.writeDataA( value ); updDisplay(); break; case 1: - this.pio.writePortB( value ); + this.pio.writeDataB( value ); updDisplay(); break; diff --git a/src/jkcemu/emusys/SLC1.java b/src/jkcemu/emusys/SLC1.java index 2c46ee4..0ed8fc2 100644 --- a/src/jkcemu/emusys/SLC1.java +++ b/src/jkcemu/emusys/SLC1.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -40,7 +40,7 @@ public class SLC1 extends EmuSys implements public SLC1( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.slc1." ); if( rom == null ) { rom = readResource( "/rom/slc1/slc1_0000.bin" ); } @@ -540,7 +540,7 @@ public boolean paintScreen( Graphics g, int x, int y, int screenScale ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; synchronized( this.keyboardMatrix ) { @@ -608,7 +608,7 @@ public boolean supportsKeyboardFld() } @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { // Tastaturspalten int col = value & 0x0F; diff --git a/src/jkcemu/emusys/VCS80.java b/src/jkcemu/emusys/VCS80.java index 85ce2e1..1f75911 100644 --- a/src/jkcemu/emusys/VCS80.java +++ b/src/jkcemu/emusys/VCS80.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2012 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -42,7 +42,7 @@ public class VCS80 extends EmuSys implements public VCS80( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.vcs80." ); if( mon == null ) { mon = readResource( "/rom/vcs80/vcs80mon.bin" ); } @@ -350,7 +350,7 @@ public boolean paintScreen( Graphics g, int x, int y, int screenScale ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; @@ -364,11 +364,11 @@ public int readIOByte( int port ) break; case 6: - rv = this.pio.readPortB(); + rv = this.pio.readDataB(); break; case 7: - rv = this.pio.readPortA(); + rv = this.pio.readDataA(); break; } return rv; @@ -415,7 +415,7 @@ public boolean supportsKeyboardFld() @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { switch( port & 0x07 ) { // A3 bis A7 ignorieren case 4: @@ -427,11 +427,11 @@ public void writeIOByte( int port, int value ) break; case 6: - this.pio.writePortB( value ); + this.pio.writeDataB( value ); break; case 7: - this.pio.writePortA( value ); + this.pio.writeDataA( value ); break; } } diff --git a/src/jkcemu/emusys/Z1013.java b/src/jkcemu/emusys/Z1013.java index e8927d6..15c1148 100644 --- a/src/jkcemu/emusys/Z1013.java +++ b/src/jkcemu/emusys/Z1013.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -84,12 +84,14 @@ public class Z1013 extends EmuSys implements private static final FloppyDiskInfo cpm64x16FloppyDisk = new FloppyDiskInfo( "/disks/z1013/z1013cpm64x16.dump.gz", - "Z1013 CP/M Boot-Diskette (64x16 Zeichen)" ); + "Z1013 CP/M Boot-Diskette (64x16 Zeichen)", + 2, 2048, true ); private static final FloppyDiskInfo cpm80x25FloppyDisk = new FloppyDiskInfo( "/disks/z1013/z1013cpm80x25.dump.gz", - "Z1013 CP/M Boot-Diskette (80x25 Zeichen)" ); + "Z1013 CP/M Boot-Diskette (80x25 Zeichen)", + 2, 2048, true ); private static final FloppyDiskInfo[] availableFloppyDisks = { cpm64x16FloppyDisk, @@ -140,6 +142,7 @@ private enum UserPort { private String romBasicFile; private String romMegaFile; private String monCode; + private boolean audioInPhase; private boolean romDisabled; private boolean altFontEnabled; private boolean graphCCJActive; @@ -163,7 +166,7 @@ private enum UserPort { public Z1013( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "jkcemu.z1013." ); this.stdFontBytes = null; this.altFontBytes = null; this.osBytes = null; @@ -207,20 +210,20 @@ public Z1013( EmuThread emuThread, Properties props ) this.emuThread.getRAMFloppy1(), "Z1013", RAMFloppy.RFType.MP_3_1988, - "RAM-Floppy an IO-Adressen 98h-9Fh", + "RAM-Floppy an E/A-Adressen 98h-9Fh", props, - "jkcemu.z1013.ramfloppy.1." ); + this.propPrefix + "ramfloppy.1." ); this.ramFloppy2 = RAMFloppy.prepare( this.emuThread.getRAMFloppy2(), "Z1013", RAMFloppy.RFType.MP_3_1988, - "RAM-Floppy an IO-Adressen 58h-5Fh", + "RAM-Floppy an E/A-Adressen 58h-5Fh", props, - "jkcemu.z1013.ramfloppy.2." ); + this.propPrefix + "ramfloppy.2." ); Z80CPU cpu = this.emuThread.getZ80CPU(); - this.pio = new Z80PIO( "PIO (IO-Adressen 00-03)" ); + this.pio = new Z80PIO( "PIO (E/A-Adressen 00-03)" ); cpu.addAddressListener( this ); checkAddPCListener( props ); @@ -243,27 +246,28 @@ public Z1013( EmuThread emuThread, Properties props ) this.screenFrm, EmuUtil.getProperty( props, - "jkcemu.z1013.graph_ccj.font.file" ) ); + this.propPrefix + "graph_ccj.font.file" ) ); } else { this.graphCCJ = null; } if( emulatesKCNet( props ) ) { - this.kcNet = new KCNet( "Netzwerk-PIO (IO-Adressen C0-C3)" ); + this.kcNet = new KCNet( "Netzwerk-PIO (E/A-Adressen C0-C3)" ); } else { this.kcNet = null; } if( emulatesUSB( props ) ) { - this.vdip = new VDIP( "USB-PIO (IO-Adressen FC-FF)" ); + this.vdip = new VDIP( + this.emuThread.getFileTimesViewFactory(), + "USB-PIO (E/A-Adressen FC-FF)" ); } else { this.vdip = null; } - this.gide = GIDE.getGIDE( this.screenFrm, props, "jkcemu.z1013." ); + this.gide = GIDE.getGIDE( this.screenFrm, props, this.propPrefix ); - java.util.List iSources - = new ArrayList(); + java.util.List iSources = new ArrayList<>(); iSources.add( this.pio ); if( this.kcNet != null ) { iSources.add( this.kcNet ); @@ -308,7 +312,13 @@ public static int getDefaultSpeedKHz( Properties props ) } - public static String getTinyBasicProgram( Z80MemView memory ) + public static boolean getDefaultSwapKeyCharCase() + { + return true; + } + + + public static String getTinyBasicProgram( EmuMemView memory ) { return SourceUtil.getTinyBasicProgram( memory, @@ -438,6 +448,11 @@ public void z80PCChanged( Z80CPU cpu, int pc ) @Override public void z80TStatesProcessed( Z80CPU cpu, int tStates ) { + boolean phase = this.emuThread.readAudioPhase(); + if( phase != this.audioInPhase ) { + this.audioInPhase = phase; + this.pio.putInValuePortB( this.audioInPhase ? 0x40 : 0, 0x40 ); + } if( this.fdc != null ) { this.fdc.z80TStatesProcessed( cpu, tStates ); } @@ -552,12 +567,12 @@ public boolean canApplySettings( Properties props ) if( rv ) { rv = TextUtil.equals( this.osFile, - EmuUtil.getProperty( props, "jkcemu.z1013.os.file" ) ); + EmuUtil.getProperty( props, this.propPrefix + "os.file" ) ); } if( rv ) { rv = TextUtil.equals( this.monCode, - EmuUtil.getProperty( props, "jkcemu.z1013.monitor" ) ); + EmuUtil.getProperty( props, this.propPrefix + "monitor" ) ); } if( rv && (getRAMEndAddr( props ) != this.ramEndAddr) ) { rv = false; @@ -568,7 +583,7 @@ public boolean canApplySettings( Properties props ) this.romBasicFile, EmuUtil.getProperty( props, - "jkcemu.z1013.rom_basic.file" ) ) ) + this.propPrefix + "rom_basic.file" ) ) ) { rv = false; } @@ -581,7 +596,7 @@ public boolean canApplySettings( Properties props ) this.romMegaFile, EmuUtil.getProperty( props, - "jkcemu.z1013.rom_mega.file" ) ) ) + this.propPrefix + "rom_mega.file" ) ) ) { rv = false; } @@ -589,7 +604,7 @@ public boolean canApplySettings( Properties props ) rv = false; } if( rv ) { - rv = GIDE.complies( this.gide, props, "jkcemu.z1013." ); + rv = GIDE.complies( this.gide, props, this.propPrefix ); } if( rv && emulatesFloppyDisk( props ) != (this.fdc != null) ) { rv = false; @@ -615,7 +630,7 @@ public boolean canApplySettings( Properties props ) "Z1013", RAMFloppy.RFType.MP_3_1988, props, - "jkcemu.z1013.ramfloppy.1." ); + this.propPrefix + "ramfloppy.1." ); } if( rv ) { rv = RAMFloppy.complies( @@ -623,7 +638,7 @@ public boolean canApplySettings( Properties props ) "Z1013", RAMFloppy.RFType.MP_3_1988, props, - "jkcemu.z1013.ramfloppy.2." ); + this.propPrefix + "ramfloppy.2." ); } return rv; } @@ -1029,7 +1044,7 @@ public int getSupportedJoystickCount() @Override public boolean getSwapKeyCharCase() { - return true; + return getDefaultSwapKeyCharCase(); } @@ -1126,7 +1141,7 @@ public void openBasicProgram() case 1: bType = BasicType.KC_RAM; - text = SourceUtil.getKCBasicStyleProgram( + text = SourceUtil.getBasicProgram( this.emuThread, 0x2C01, basicTokens ); @@ -1134,7 +1149,7 @@ public void openBasicProgram() case 2: bType = BasicType.KC_ROM; - text = SourceUtil.getKCBasicStyleProgram( + text = SourceUtil.getBasicProgram( this.emuThread, 0x0401, basicTokens ); @@ -1180,7 +1195,7 @@ protected boolean pasteChar( char ch ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0x0F; // wird von unbelegten Ports gelesen @@ -1206,17 +1221,19 @@ else if( (port == 0x18) && (this.graphCCJ != null) ) { else if( (port == 0x19) && (this.graphCCJ != null) ) { rv = this.graphCCJ.readData(); } - else if( (port & 0xF0) == 0x70 ) { - if( this.rtc != null ) { - rv = this.rtc.read( port ); - } - } - if( (this.gide != null) && ((port & 0xF0) == 0x80) ) { + if( (this.gide != null) + && (((port & 0xF0) == 0x40) || ((port & 0xF0) == 0x80)) ) + { int value = this.gide.read( port ); if( value >= 0 ) { rv = value; } } + else if( (port & 0xF0) == 0x70 ) { + if( this.rtc != null ) { + rv = this.rtc.read( port ); + } + } else if( ((port & 0xF8) == 0x98) && (this.ramFloppy1 != null) ) { rv = this.ramFloppy1.readByte( port & 0x07 ); } @@ -1269,7 +1286,7 @@ else if( (port >= 0xF0) && (port <= 0xFF) ) { case 0: // IOSEL0 -> PIO switch( port & 0x03 ) { case 0: - rv = this.pio.readPortA(); + rv = this.pio.readDataA(); break; case 1: @@ -1277,9 +1294,7 @@ else if( (port >= 0xF0) && (port <= 0xFF) ) { break; case 2: - this.pio.putInValuePortB( - this.emuThread.readAudioPhase() ? 0x40 : 0, 0x40 ); - rv = this.pio.readPortB(); + rv = this.pio.readDataB(); break; case 3: @@ -1384,6 +1399,7 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) this.joy1ActionMask = 0; this.romMegaSeg = 0; this.lastWrittenAddr = -1; + this.audioInPhase = this.emuThread.readAudioPhase(); this.romDisabled = false; this.altFontEnabled = false; this.modeGraph = false; @@ -1442,16 +1458,17 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) @Override public void saveBasicProgram() { - boolean cancelled = false; - boolean kcbasic = false; - int begAddr = -1; - int endAddr = -1; - int hsType = -1; - BasicType bType = null; - int preIdx = -1; + boolean cancelled = false; + int begAddr = -1; + int endAddr = -1; + BasicType z1013BasicType = null; + SaveDlg.BasicType dstBasicType = SaveDlg.BasicType.NO_BASIC; + String title = "KC-BASIC-Programm speichern"; + int preIdx = -1; if( this.lastBasicType != null ) { switch( this.lastBasicType ) { case Z1013_TINY: + title = "Tiny-BASIC-Programm speichern"; preIdx = 0; break; @@ -1479,29 +1496,27 @@ public void saveBasicProgram() if( (endAddr > 0x1152) && (this.emuThread.getMemByte( endAddr - 1, false ) == 0x0D) ) { - this.lastBasicType = BasicType.Z1013_TINY; - begAddr = 0x1000; - hsType = 'b'; + z1013BasicType = BasicType.Z1013_TINY; + dstBasicType = SaveDlg.BasicType.TINYBASIC; + begAddr = 0x1000; } break; case 1: - endAddr = SourceUtil.getKCBasicStyleEndAddr( this.emuThread, 0x2C01 ); + endAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x2C01 ); if( endAddr > 0x2C01 ) { - this.lastBasicType = BasicType.KC_RAM; - begAddr = 0x2BC0; - hsType = 'B'; - kcbasic = true; + z1013BasicType = BasicType.KC_RAM; + dstBasicType = SaveDlg.BasicType.KCBASIC; + begAddr = 0x2C01; } break; case 2: - endAddr = SourceUtil.getKCBasicStyleEndAddr( this.emuThread, 0x0401 ); + endAddr = SourceUtil.getBasicEndAddr( this.emuThread, 0x0401 ); if( endAddr > 0x0401 ) { - this.lastBasicType = BasicType.KC_ROM; - begAddr = 0x03C0; - hsType = 'B'; - kcbasic = true; + z1013BasicType = BasicType.KC_ROM; + dstBasicType = SaveDlg.BasicType.KCBASIC; + begAddr = 0x0401; } break; @@ -1510,14 +1525,14 @@ public void saveBasicProgram() } if( !cancelled ) { if( (begAddr > 0) && (endAddr > begAddr) ) { + this.lastBasicType = z1013BasicType; (new SaveDlg( this.screenFrm, begAddr, endAddr, - hsType, - kcbasic, - false, // kein RBASIC - "BASIC-Programm speichern" )).setVisible( true ); + title, + dstBasicType, + null )).setVisible( true ); } else { showNoBasic(); } @@ -1712,10 +1727,10 @@ public boolean supportsSaveBasic() @Override public void updSysCells( - int begAddr, - int len, - Object fileFmt, - int fileType ) + int begAddr, + int len, + FileFormat fileFmt, + int fileType ) { SourceUtil.updKCBasicSysCells( this.emuThread, @@ -1727,7 +1742,7 @@ public void updSysCells( @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { port &= 0xFF; if( port == 4 ) { @@ -1771,14 +1786,16 @@ else if( (port == 0x19) && (this.graphCCJ != null) ) { this.screenFrm.fireScreenSizeChanged(); } } + if( (this.gide != null) + && (((port & 0xF0) == 0x40) || ((port & 0xF0) == 0x80)) ) + { + this.gide.write( port, value ); + } else if( (port & 0xF0) == 0x70 ) { if( this.rtc != null ) { this.rtc.write( port, value ); } } - else if( (this.gide != null) && ((port & 0xF0) == 0x80) ) { - this.gide.write( port, value ); - } else if( ((port & 0xF8) == 0x98) && (this.ramFloppy1 != null) ) { this.ramFloppy1.writeByte( port & 0x07, value ); } @@ -1831,7 +1848,7 @@ else if( (port >= 0xF0) && (port <= 0xFF) ) { case 0: // IOSEL0 -> PIO switch( port & 0x03 ) { case 0: - this.pio.writePortA( value ); + this.pio.writeDataA( value ); if( this.userPort == UserPort.JOY_PRACTIC_1_1988 ) { putJoyPractic0188ValuesToPort(); } @@ -1865,7 +1882,7 @@ else if( (this.userPort == UserPort.CENTR7_PRACTIC_2_1989) break; case 2: - this.pio.writePortB( value ); + this.pio.writeDataB( value ); this.keyboard.putRowValuesToPIO(); this.emuThread.writeAudioPhase( (this.pio.fetchOutValuePortB( false ) & 0x80) != 0 ); @@ -1945,7 +1962,7 @@ public void writeMemByte( int addr, int value ) private void applyUserPortSettings( Properties props ) { - String text = EmuUtil.getProperty( props, "jkcemu.z1013.userport" ); + String text = EmuUtil.getProperty( props, this.propPrefix + "userport" ); if( text.equals( "joystick:jute0687" ) ) { this.userPort = UserPort.JOY_JUTE_6_1987; } else if( text.equals( "joystick:practic0487" ) ) { @@ -1967,11 +1984,13 @@ private synchronized void checkAddPCListener( Properties props ) { this.pasteFast = EmuUtil.getBooleanProperty( props, - "jkcemu.z1013.paste.fast", + this.propPrefix + "paste.fast", true ); - java.util.List addrs = new ArrayList(); + java.util.List addrs = new ArrayList<>(); if( this.pasteFast ) { - String monText = EmuUtil.getProperty( props, "jkcemu.z1013.monitor" ); + String monText = EmuUtil.getProperty( + props, + this.propPrefix + "monitor" ); if( monText.equals( "A.2" ) ) { addrs.add( new Integer( 0xF119 ) ); } else if( monText.equals( "JM_1992" ) ) { @@ -1981,8 +2000,8 @@ private synchronized void checkAddPCListener( Properties props ) } } this.catchPrintCalls = EmuUtil.getBooleanProperty( - props, - "jkcemu.z1013.catch_print_calls", + props, + this.propPrefix + "catch_print_calls", true ); if( this.catchPrintCalls ) { addrs.add( new Integer( 0xFFCA ) ); @@ -1994,7 +2013,7 @@ private synchronized void checkAddPCListener( Properties props ) } this.catchJoyCalls = EmuUtil.getBooleanProperty( props, - "jkcemu.z1013.catch_joystick_calls", + this.propPrefix + "catch_joystick_calls", true ); if( this.catchJoyCalls ) { addrs.add( new Integer( 0xFFBB ) ); @@ -2035,25 +2054,25 @@ private boolean emulatesFloppyDisk( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.z1013.floppydisk.enabled", + this.propPrefix + "floppydisk.enabled", false ); } - private static boolean emulatesGraphCCJ( Properties props ) + private boolean emulatesGraphCCJ( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.z1013.graph_ccj.enabled", + this.propPrefix + "graph_ccj.enabled", false ); } - private static boolean emulatesGraphic( Properties props ) + private boolean emulatesGraphic( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.z1013.graphic.enabled", + this.propPrefix + "graphic.enabled", false ); } @@ -2062,7 +2081,7 @@ private boolean emulatesKCNet( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.z1013.kcnet.enabled", + this.propPrefix + "kcnet.enabled", false ); } @@ -2071,7 +2090,7 @@ private boolean emulatesModuleBasic( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.z1013.rom_basic.enabled", + this.propPrefix + "rom_basic.enabled", false ); } @@ -2080,16 +2099,16 @@ private boolean emulatesModuleMegaROM( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.z1013.rom_mega.enabled", + this.propPrefix + "rom_mega.enabled", false ); } - private static boolean emulatesRTC( Properties props ) + private boolean emulatesRTC( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.z1013.rtc.enabled", + this.propPrefix + "rtc.enabled", false ); } @@ -2098,7 +2117,7 @@ private boolean emulatesUSB( Properties props ) { return EmuUtil.getBooleanProperty( props, - "jkcemu.z1013.vdip.enabled", + this.propPrefix + "vdip.enabled", false ); } @@ -2123,7 +2142,7 @@ private void loadFont( Properties props ) { this.stdFontBytes = readFontByProperty( props, - "jkcemu.z1013.font.file", + this.propPrefix + "font.file", 0x1000 ); if( this.stdFontBytes != null ) { if( this.stdFontBytes.length >= 0x1000 ) { @@ -2148,16 +2167,16 @@ private void loadFont( Properties props ) this.graphCCJ.loadFont( EmuUtil.getProperty( props, - "jkcemu.z1013.graph_ccj.font.file" ) ); + this.propPrefix + "graph_ccj.font.file" ) ); } } private void loadROMs( Properties props ) { - this.monCode = EmuUtil.getProperty( props, "jkcemu.z1013.monitor" ); - this.osFile = EmuUtil.getProperty( props, "jkcemu.z1013.os.file" ); - this.osBytes = readFile( this.osFile, 0x1000, "Monitorprogramm" ); + this.monCode = EmuUtil.getProperty( props, this.propPrefix + "monitor" ); + this.osFile = EmuUtil.getProperty( props, this.propPrefix + "os.file" ); + this.osBytes = readROMFile( this.osFile, 0x1000, "Monitorprogramm" ); if( this.osBytes == null ) { if( this.monCode.equals( "A.2" ) ) { if( monA2 == null ) { @@ -2198,9 +2217,12 @@ private void loadROMs( Properties props ) } if( emulatesModuleBasic( props ) ) { this.romBasicFile = EmuUtil.getProperty( - props, - "jkcemu.z1013.rom_basic.file" ); - this.romBasic = readFile( this.romBasicFile, 0x2C00, "KC-BASIC-Modul" ); + props, + this.propPrefix + "rom_basic.file" ); + this.romBasic = readROMFile( + this.romBasicFile, + 0x2C00, + "KC-BASIC-Modul" ); if( this.romBasic == null ) { if( modBasic == null ) { modBasic = readResource( "/rom/z1013/kcbasic.bin" ); @@ -2209,9 +2231,12 @@ private void loadROMs( Properties props ) } } else if( emulatesModuleMegaROM( props ) ) { this.romMegaFile = EmuUtil.getProperty( - props, - "jkcemu.z1013.rom_mega.file" ); - this.romMega = readFile( this.romMegaFile, 0x280000, "Mega-ROM-Modul" ); + props, + this.propPrefix + "rom_mega.file" ); + this.romMega = readROMFile( + this.romMegaFile, + 0x280000, + "Mega-ROM-Modul" ); } loadFont( props ); } diff --git a/src/jkcemu/emusys/Z9001.java b/src/jkcemu/emusys/Z9001.java index d530cb7..85bf8aa 100644 --- a/src/jkcemu/emusys/Z9001.java +++ b/src/jkcemu/emusys/Z9001.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -140,7 +140,8 @@ public class Z9001 extends EmuSys implements private static final FloppyDiskInfo[] availableFloppyDisks = { new FloppyDiskInfo( "/disks/z9001/z9cpasys.dump.gz", - "Z9001 CP/A Systemdiskette" ) }; + "Z9001 CP/A Systemdiskette", + 0, 2048, true ) }; private static byte[] os11 = null; private static byte[] os12 = null; @@ -204,7 +205,6 @@ public class Z9001 extends EmuSys implements private boolean ramFontEnabled; private boolean audioOutPhase; private boolean audioInPhase; - private int audioInTStates; private int lineNum; private int lineTStates; private int tStatesPerLine; @@ -214,7 +214,6 @@ public class Z9001 extends EmuSys implements private int joy1ActionMask; private int[] kbMatrix; private String sysName; - private String propPrefix; private String romOSFile; private String romBasicFile; private String romModuleFile; @@ -226,6 +225,8 @@ public class Z9001 extends EmuSys implements private Z80CTC ctcA8; // CTC im Druckermodul private Z80SIO sioB0; // SIO im Druckermodul private FDC8272 fdc; + private GIDE gide; + private RTC7242X rtc; private Plotter plotter; private KCNet kcNet; private VDIP vdip; @@ -237,7 +238,7 @@ public class Z9001 extends EmuSys implements public Z9001( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, "" ); this.romOS = null; this.romOSFile = null; this.romBasic = null; @@ -283,12 +284,14 @@ public Z9001( EmuThread emuThread, Properties props ) this.plotter = null; } if( emulatesKCNet( props ) ) { - this.kcNet = new KCNet( "Netzwerk-PIO (IO-Adressen C0-C3)" ); + this.kcNet = new KCNet( "Netzwerk-PIO (E/A-Adressen C0-C3)" ); } else { this.kcNet = null; } if( emulatesUSB( props ) ) { - this.vdip = new VDIP( "USB-PIO (IO-Adressen DC-DF)" ); + this.vdip = new VDIP( + this.emuThread.getFileTimesViewFactory(), + "USB-PIO (E/A-Adressen DC-DF)" ); this.vdip.applySettings( props ); } else { this.vdip = null; @@ -299,6 +302,9 @@ public Z9001( EmuThread emuThread, Properties props ) printerModBytes = readResource( "/rom/z9001/modprinter.bin" ); } } + this.gide = GIDE.getGIDE( this.screenFrm, props, this.propPrefix ); + this.rtc = emulatesRTC( props ) ? new RTC7242X() : null; + this.c80Active = false; this.c80Enabled = emulates80CharsMode( props ); this.fixedScreenSize = isFixedScreenSize( props ); @@ -316,7 +322,7 @@ public Z9001( EmuThread emuThread, Properties props ) this.emuThread.getRAMFloppy1(), this.sysName, RAMFloppy.RFType.ADW, - "RAM-Floppy an IO-Adressen 20h/21h", + "RAM-Floppy an E/A-Adressen 20h/21h", props, this.propPrefix + "ramfloppy.1." ); @@ -324,13 +330,12 @@ public Z9001( EmuThread emuThread, Properties props ) this.emuThread.getRAMFloppy2(), this.sysName, RAMFloppy.RFType.ADW, - "RAM-Floppy an IO-Adressen 24h/25h", + "RAM-Floppy an E/A-Adressen 24h/25h", props, this.propPrefix + "ramfloppy.2." ); this.lineNum = 0; this.lineTStates = 0; - this.audioInTStates = 0; this.audioInPhase = this.emuThread.readAudioPhase(); this.audioOutPhase = false; this.pcListenerAdded = false; @@ -344,18 +349,16 @@ public Z9001( EmuThread emuThread, Properties props ) this.kbMatrix = new int[ 8 ]; - java.util.List iSources - = new ArrayList(); - - this.pio90 = new Z80PIO( "Tastatur-PIO (IO-Adressen 90-93)" ); - this.pio88 = new Z80PIO( "System-PIO (IO-Adressen 88-8B)" ); - this.ctc80 = new Z80CTC( "System-CTC (IO-Adressen 80-83)" ); + java.util.List iSources = new ArrayList<>(); + this.pio90 = new Z80PIO( "Tastatur-PIO (E/A-Adressen 90-93)" ); + this.pio88 = new Z80PIO( "System-PIO (E/A-Adressen 88-8B)" ); + this.ctc80 = new Z80CTC( "System-CTC (E/A-Adressen 80-83)" ); iSources.add( this.pio90 ); iSources.add( this.pio88 ); iSources.add( this.ctc80 ); if( this.printerModule ) { - this.ctcA8 = new Z80CTC( "Druckermodul-CTC (IO-Adressen A8-AB)" ); - this.sioB0 = new Z80SIO( "Druckermodul-SIO (IO-Adressen B0-B3)" ); + this.ctcA8 = new Z80CTC( "Druckermodul-CTC (E/A-Adressen A8-AB)" ); + this.sioB0 = new Z80SIO( "Druckermodul-SIO (E/A-Adressen B0-B3)" ); iSources.add( this.ctcA8 ); iSources.add( this.sioB0 ); } else { @@ -433,6 +436,12 @@ public static int getDefaultSpeedKHz() } + public static boolean getDefaultSwapKeyCharCase() + { + return true; + } + + public boolean getGraphicLED() { return this.graphicLED; @@ -513,9 +522,7 @@ public void z80CTCUpdate( Z80CTC ctc, int timerNum ) if( (ctc == this.ctc80) && (timerNum == 0) ) { this.audioOutPhase = !this.audioOutPhase; if( this.emuThread.isSoundOutEnabled() ) { - if( (this.pio88.fetchOutValuePortA( false ) & 0x80) != 0 ) { - this.emuThread.writeAudioPhase( this.audioOutPhase ); - } + updLoudspeaker( this.pio88.fetchOutValuePortA( false ) ); } else { this.emuThread.writeAudioPhase( this.audioOutPhase ); } @@ -578,26 +585,16 @@ public void z80TStatesProcessed( Z80CPU cpu, int tStates ) if( this.kcNet != null ) { this.kcNet.z80TStatesProcessed( cpu, tStates ); } - this.audioInTStates += tStates; /* * Der Kassettenrecorderanschluss eingangsseitig wird emuliert, * indem zyklisch geschaut wird, ob sich die Eingangsphase geaendert hat. * Wenn ja, wird ein Impuls an der Strobe-Leitung der zugehoerigen PIO * emuliert. - * Auf der einen Seite soll das Audiosystem nicht zu oft abgefragt - * werden. - * Auf der anderen Seite sollen aber die Zykluszeit nicht so gross werden, - * dass die Genauigkeit der Zeitmessung kuenstlich verschlechert wird. - * Aus diesem Grund werden genau soviele Taktzyklen abgezaehlt, - * wie auch der Vorteile der CTC mindestens zaehlen muss. */ - if( this.audioInTStates > 15 ) { - this.audioInTStates = 0; - if( this.emuThread.readAudioPhase() != this.audioInPhase ) { - this.audioInPhase = !this.audioInPhase; - this.pio88.strobePortA(); - } + if( this.emuThread.readAudioPhase() != this.audioInPhase ) { + this.audioInPhase = !this.audioInPhase; + this.pio88.strobePortA(); } // Zugriffe auf den Bildwiederhol- und Farbspeicher verlangsamen @@ -823,6 +820,12 @@ public boolean canApplySettings( Properties props ) if( rv && (emulatesUSB( props ) != (this.vdip != null)) ) { rv = false; } + if( rv && (emulatesRTC( props ) != (this.rtc != null)) ) { + rv = false; + } + if( rv ) { + rv = GIDE.complies( this.gide, props, this.propPrefix ); + } if( rv ) { rv = RAMFloppy.complies( this.ramFloppy1, @@ -904,6 +907,9 @@ public void die() if( this.plotter != null ) { this.plotter.die(); } + if( this.gide != null ) { + this.gide.die(); + } if( this.kcNet != null ) { this.kcNet.die(); } @@ -1103,7 +1109,7 @@ public CharRaster getCurScreenCharRaster() @Override public FloppyDiskFormat getDefaultFloppyDiskFormat() { - return FloppyDiskFormat.FMT_800K; + return FloppyDiskFormat.FMT_800K_I4; } @@ -1257,7 +1263,7 @@ public int getSupportedJoystickCount() @Override public boolean getSwapKeyCharCase() { - return true; + return getDefaultSwapKeyCharCase(); } @@ -1466,10 +1472,10 @@ else if( setCharInKBMatrix( ch + 0x40, kbMatrixShift ) ) { @Override public void openBasicProgram() { - SourceUtil.openKCBasicStyleProgram( - this.screenFrm, - this.kc87 ? 0x0401 : 0x2C01, - basicTokens ); + SourceUtil.openKCBasicProgram( + this.screenFrm, + this.kc87 ? 0x0401 : 0x2C01, + basicTokens ); } @@ -1500,7 +1506,7 @@ protected boolean pasteChar( char ch ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; switch( port & 0xFF ) { @@ -1571,17 +1577,17 @@ public int readIOByte( int port ) case 0x85: case 0x86: case 0x87: - rv = this.ctc80.read( port & 0x03 ); + rv = this.ctc80.read( port & 0x03, tStates ); break; case 0x88: // A2=0 case 0x8C: // A2=1 - rv = this.pio88.readPortA(); + rv = this.pio88.readDataA(); break; case 0x89: // A2=0 case 0x8D: // A2=1 - rv = this.pio88.readPortB(); + rv = this.pio88.readDataB(); break; case 0x8A: // A2=0 @@ -1596,12 +1602,12 @@ public int readIOByte( int port ) case 0x90: // A2=0 case 0x94: // A2=1 - rv = this.pio90.readPortA(); + rv = this.pio90.readDataA(); break; case 0x91: // A2=0 case 0x95: // A2=1 - rv = this.pio90.readPortB(); + rv = this.pio90.readDataB(); break; case 0x92: // A2=0 @@ -1623,7 +1629,7 @@ public int readIOByte( int port ) case 0xAE: case 0xAF: if( this.ctcA8 != null ) { - rv = this.ctcA8.read( port & 0x03 ); + rv = this.ctcA8.read( port & 0x03, tStates ); } break; @@ -1702,6 +1708,17 @@ public int readIOByte( int port ) rv = this.vdip.read( port ); } break; + + default: + if( (this.gide != null) && ((port & 0xF0) == 0x50) ) { + int value = this.gide.read( port ); + if( value >= 0 ) { + rv = value; + } + } + else if( (this.rtc != null) && ((port & 0xF0) == 0x60) ) { + rv = this.rtc.read( port ); + } } return rv; } @@ -1810,6 +1827,7 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) this.graphBorder = false; this.graphMode = false; this.c80MemSwap = false; + this.audioOutPhase = false; this.plotterPenState = false; this.plotterMoveState = false; this.fdcReset = false; @@ -1826,6 +1844,9 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) this.plotter.reset(); this.pio88.putInValuePortB( 0x00, 0x20 ); // Plotter Ready } + if( this.gide != null ) { + this.gide.reset(); + } setGraphicLED( false ); upd80CharsMode( false ); updScreenConfig( 0 ); @@ -1836,9 +1857,9 @@ public void reset( EmuThread.ResetLevel resetLevel, Properties props ) @Override public void saveBasicProgram() { - SourceUtil.saveKCBasicStyleProgram( - this.screenFrm, - this.kc87 ? 0x0401 : 0x2C01 ); + SourceUtil.saveKCBasicProgram( + this.screenFrm, + this.kc87 ? 0x0401 : 0x2C01 ); } @@ -1945,10 +1966,10 @@ public boolean supportsSaveBasic() @Override public void updSysCells( - int begAddr, - int len, - Object fileFmt, - int fileType ) + int begAddr, + int len, + FileFormat fileFmt, + int fileType ) { SourceUtil.updKCBasicSysCells( this.emuThread, @@ -1960,7 +1981,7 @@ public void updSysCells( @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { switch( port & 0xFF ) { case 4: @@ -2022,19 +2043,17 @@ public void writeIOByte( int port, int value ) case 0x85: case 0x86: case 0x87: - this.ctc80.write( port & 0x03, value ); + this.ctc80.write( port & 0x03, value, tStates ); break; case 0x88: // A2=0 case 0x8C: // A2=1 - this.pio88.writePortA( value ); + this.pio88.writeDataA( value ); { int v = this.pio88.fetchOutValuePortA( false ); updScreenConfig( v ); - if( this.emuThread.isSoundOutEnabled() - && ((v & 0x80) != 0) ) - { - this.emuThread.writeAudioPhase( this.audioOutPhase ); + if( this.emuThread.isSoundOutEnabled() ) { + updLoudspeaker( v ); } setGraphicLED( (v & 0x40) != 0 ); } @@ -2042,7 +2061,7 @@ public void writeIOByte( int port, int value ) case 0x89: // A2=0 case 0x8D: // A2=1 - this.pio88.writePortB( value ); + this.pio88.writeDataB( value ); if( this.plotter != null ) { int v = this.pio88.fetchOutValuePortB( false ); boolean p = ((v & 0x80) != 0); @@ -2077,13 +2096,13 @@ public void writeIOByte( int port, int value ) case 0x90: // A2=0 case 0x94: // A2=1 - this.pio90.writePortA( value ); // Tastatur Spalten + this.pio90.writeDataA( value ); // Tastatur Spalten this.pio90.putInValuePortB( getKeyboardRowValue(), 0xFF ); break; case 0x91: // A2=0 case 0x95: // A2=1 - this.pio90.writePortB( value ); // Tastatur Zeilen + this.pio90.writeDataB( value ); // Tastatur Zeilen this.pio90.putInValuePortA( getKeyboardColValue(), 0xFF ); break; @@ -2148,7 +2167,7 @@ public void writeIOByte( int port, int value ) case 0xAE: case 0xAF: if( this.ctcA8 != null ) { - this.ctcA8.write( port & 0x03, value ); + this.ctcA8.write( port & 0x03, value, tStates ); } else { if( this.c80Enabled && ((port & 0xFE) == 0xA8) ) { upd80CharsMode( (port & 0x01) != 0 ); @@ -2282,6 +2301,14 @@ public void writeIOByte( int port, int value ) case 0xFF: this.megaROMSeg = (value & 0xFF); break; + + default: + if( (this.gide != null) && ((port & 0xF0) == 0x50) ) { + this.gide.write( port, value ); + } + else if( (this.rtc != null) && ((port & 0xF0)) == 0x60 ) { + this.rtc.write( port, value ); + } } } @@ -2505,6 +2532,15 @@ private boolean emulatesProgrammableFont( Properties props ) } + private boolean emulatesRTC( Properties props ) + { + return EmuUtil.getBooleanProperty( + props, + this.propPrefix + "rtc.enabled", + false ); + } + + private boolean emulatesUSB( Properties props ) { return EmuUtil.getBooleanProperty( @@ -2850,7 +2886,7 @@ private void loadROMs( Properties props ) this.romOSFile = EmuUtil.getProperty( props, this.propPrefix + "os.file" ); - this.romOS = readFile( this.romOSFile, 0x1000, "Betriebssystem" ); + this.romOS = readROMFile( this.romOSFile, 0x1000, "Betriebssystem" ); if( this.romOS == null ) { if( this.sysName.equals( "KC87" ) ) { if( os13 == null ) { @@ -2875,7 +2911,7 @@ private void loadROMs( Properties props ) this.romBasicFile = EmuUtil.getProperty( props, this.propPrefix + "basic.file" ); - this.romBasic = readFile( this.romBasicFile, 0x2800, "BASIC" ); + this.romBasic = readROMFile( this.romBasicFile, 0x2800, "BASIC" ); if( this.romBasic == null ) { if( basic86 == null ) { basic86 = readResource( "/rom/z9001/basic86.bin" ); @@ -2891,31 +2927,31 @@ private void loadROMs( Properties props ) this.romModuleFile = props.getProperty( this.propPrefix + "rom_module.file" ); if( emulatesROM16K4000( props ) ) { - this.rom16k4000 = readFile( + this.rom16k4000 = readROMFile( this.romModuleFile, 0x4000, "ROM-Modul 4000h-7FFFh" ); } else if( emulatesROM32K4000( props ) ) { - this.rom32k4000 = readFile( + this.rom32k4000 = readROMFile( this.romModuleFile, 0x8000, "ROM-Modul 4000h-BFFFh" ); } else if( emulatesROM16K8000( props ) ) { - this.rom16k8000 = readFile( + this.rom16k8000 = readROMFile( this.romModuleFile, 0x4000, "ROM-Modul 8000h-BFFFh" ); } else if( emulatesROM10KC000( props ) ) { - this.rom10kC000 = readFile( + this.rom10kC000 = readROMFile( this.romModuleFile, 0x4000, "ROM-Modul C000h-E7FFh" ); } else if( emulatesBootROM( props ) ) { - this.romBoot = readFile( + this.romBoot = readROMFile( this.romModuleFile, 0x4000, "Boot-ROM-Modul" ); @@ -2927,7 +2963,7 @@ else if( emulatesBootROM( props ) ) { } } else if( emulatesMegaROM( props ) ) { - this.romMega = readFile( + this.romMega = readROMFile( this.romModuleFile, 256 * 10240, "Mega-ROM-Modul" ); @@ -3096,6 +3132,13 @@ private void updKeyboardFld() } + private void updLoudspeaker( int pio88PortAValue ) + { + this.emuThread.writeAudioPhase( + !(!this.audioOutPhase && ((pio88PortAValue & 0x80) != 0)) ); + } + + private void updScreenConfig( int value ) { boolean mode20Rows = ((value & 0x04) != 0); diff --git a/src/jkcemu/emusys/ZXSpectrum.java b/src/jkcemu/emusys/ZXSpectrum.java index c00315b..e3edc84 100644 --- a/src/jkcemu/emusys/ZXSpectrum.java +++ b/src/jkcemu/emusys/ZXSpectrum.java @@ -1,5 +1,5 @@ /* - * (c) 2013 Jens Mueller + * (c) 2013-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -14,25 +14,27 @@ import java.lang.*; import java.util.*; import java.util.zip.CRC32; +import javax.sound.sampled.AudioFormat; import jkcemu.Main; -import jkcemu.audio.AudioIn; +import jkcemu.audio.*; import jkcemu.base.*; import jkcemu.emusys.zxspectrum.ZXSpectrumKeyboardFld; +import jkcemu.etc.PSG8910; import jkcemu.joystick.JoystickThread; import jkcemu.text.TextUtil; import z80emu.*; public class ZXSpectrum extends EmuSys implements + PSG8910.Callback, Z80InterruptSource, Z80MaxSpeedListener, Z80TStatesListener { private static final int SCREEN_WIDTH = 256; private static final int SCREEN_HEIGHT = 192; - private static final int LINES_PER_SCREEN = 312; - private static final int FIRST_VISIBLE_LINE = 64; - private static final int LAST_VISIBLE_LINE = 255; + private static final int DEFAULT_48K_KHZ = 3500; + private static final int DEFAULT_128K_KHZ = 3547; private static final int MIN_AUDIO_IN_1TO0_DELAY = 180; private static final int MAX_AUDIO_IN_1TO0_DELAY = 2000; @@ -99,41 +101,82 @@ public class ZXSpectrum extends EmuSys implements KeyEvent.VK_B } }; // A15 private static byte[] os48k = null; + private static byte[] os128k = null; private static Map pixelCRC32ToChar = null; + private static final String PROP_PREFIX = "jkcemu.zxspectrum."; + private Color[] colors; private String osFile; private volatile float fTStatesPerLine; private volatile int tStatesPerLine; + private boolean mode128k; + private boolean cfgRegProtected; private boolean interruptRequested; + private boolean audioOutPhase; private boolean earPhase; private boolean blinkState; private int blinkLineCounter; + private int audioOutBits; private int audioInLDelayTStateCounter; private int joyActionMask; + private int linesPerScreen; + private int firstScreenLine; + private int lastScreenLine; + private int curScreenLine; private int screenTStateCounter; private int lineTStateCounter; - private int curScreenLine; private int borderColorNum; + private int romOffs; + private int ramC000Offs; + private volatile int screenOffs; + private int psgRegNum; private int[] kbMatrix; private byte[] osBytes; private byte[] borderColorNums; private byte[] screenColorNums; + private byte[] ram; + private AudioOut audioOut; + private PSG8910 psg; private ZXSpectrumKeyboardFld keyboardFld; public ZXSpectrum( EmuThread emuThread, Properties props ) { - super( emuThread, props ); + super( emuThread, props, PROP_PREFIX ); this.colors = new Color[ 16 ]; - this.borderColorNums = new byte[ LINES_PER_SCREEN ]; - this.screenColorNums = new byte[ SCREEN_WIDTH * SCREEN_HEIGHT ]; this.kbMatrix = new int[ 8 ]; this.osFile = null; this.osBytes = null; this.keyboardFld = null; + this.cfgRegProtected = false; + this.psgRegNum = 0; + this.romOffs = 0; + this.ramC000Offs = 0; + this.ram = null; + this.audioOut = null; + this.psg = null; // emulierte Hardware konfigurieren + this.mode128k = emulates128K( props ); + if( this.mode128k ) { + this.screenOffs = 5 * 0x4000; + this.linesPerScreen = 311; + this.firstScreenLine = 63; + this.ram = this.emuThread.getExtendedRAM( 0x20000 ); + this.psg = new PSG8910( + DEFAULT_128K_KHZ * 500, + AudioOut.MAX_USED_VALUE, + this ); + } else { + this.screenOffs = 0x4000; + this.linesPerScreen = 312; + this.firstScreenLine = 64; + } + this.lastScreenLine = this.firstScreenLine + SCREEN_HEIGHT; + this.borderColorNums = new byte[ this.linesPerScreen ]; + this.screenColorNums = new byte[ SCREEN_WIDTH * SCREEN_HEIGHT ]; + Z80CPU cpu = emuThread.getZ80CPU(); cpu.setInterruptSources( this ); cpu.addTStatesListener( this ); @@ -144,12 +187,15 @@ public ZXSpectrum( EmuThread emuThread, Properties props ) if( !isReloadExtROMsOnPowerOnEnabled( props ) ) { loadROMs( props ); } + if( this.psg != null ) { + this.psg.start(); + } } - public static int getDefaultSpeedKHz() + public static int getDefaultSpeedKHz( Properties props ) { - return 3500; + return emulates128K( props ) ? DEFAULT_128K_KHZ : DEFAULT_48K_KHZ; } @@ -170,6 +216,35 @@ public void updKeyboardMatrix( int[] kbMatrix ) } + /* --- PSG8910.Callback --- */ + + @Override + public int psgReadPort( PSG8910 psg, int port ) + { + return 0xFF; + } + + + @Override + public void psgWritePort( PSG8910 psg, int port, int value ) + { + // leer + } + + + @Override + public void psgWriteSample( PSG8910 psg, int a, int b, int c ) + { + AudioOut audioOut = this.audioOut; + if( audioOut != null ) { + if( audioOut.isSoundOutEnabled() ) { + int value = (a + b + c) / 6; + audioOut.writeSamples( 1, value, value, value ); + } + } + } + + /* --- Z80InterruptSource --- */ /* @@ -230,16 +305,26 @@ public void reset( boolean powerOn ) } Arrays.fill( this.borderColorNums, (byte) 0 ); Arrays.fill( this.screenColorNums, (byte) 0 ); - this.interruptRequested = false; + this.audioOutPhase = false; this.earPhase = true; + this.cfgRegProtected = false; + this.interruptRequested = false; this.blinkState = false; this.blinkLineCounter = 0; + this.audioOutBits = 0; this.audioInLDelayTStateCounter = 0; this.joyActionMask = 0; this.screenTStateCounter = 0; this.lineTStateCounter = 0; this.curScreenLine = 0; this.borderColorNum = 0; + this.ramC000Offs = 0; + this.romOffs = 0; + this.screenOffs = (this.mode128k ? 5 : 1) * 0x4000; + this.psgRegNum = 0; + if( this.psg != null ) { + this.psg.reset(); + } } @@ -248,7 +333,13 @@ public void reset( boolean powerOn ) @Override public void z80MaxSpeedChanged( Z80CPU cpu ) { - this.tStatesPerLine = (int) Math.round( cpu.getMaxSpeedKHz() / 15.625 ); + if( mode128k ) { + // 228 T-States bei normaler Taktfrequenz + this.tStatesPerLine = (int) Math.round( cpu.getMaxSpeedKHz() / 15.557 ); + } else { + // 224 T-States bei normaler Taktfrequenz + this.tStatesPerLine = (int) Math.round( cpu.getMaxSpeedKHz() / 15.625 ); + } this.fTStatesPerLine = (float) this.tStatesPerLine; } @@ -263,7 +354,7 @@ public void z80TStatesProcessed( Z80CPU cpu, int tStates ) this.lineTStateCounter -= this.tStatesPerLine; updScreenLine(); this.curScreenLine++; - if( this.curScreenLine >= LINES_PER_SCREEN ) { + if( this.curScreenLine >= this.linesPerScreen ) { this.curScreenLine = 0; this.interruptRequested = true; this.blinkLineCounter++; @@ -307,17 +398,33 @@ public void applySettings( Properties props ) } + @Override + public void audioOutChanged( AudioOut audioOut ) + { + if( (audioOut != null) && (this.psg != null) ) { + AudioFormat fmt = audioOut.getAudioFormat(); + if( fmt != null ) { + this.psg.setSampleRate( Math.round( fmt.getSampleRate() ) ); + } + } + this.audioOut = audioOut; + } + + @Override public boolean canApplySettings( Properties props ) { boolean rv = EmuUtil.getProperty( props, "jkcemu.system" ).equals( "ZXSpectrum" ); + if( rv && (emulates128K( props ) != this.mode128k) ) { + rv = false; + } if( rv && !TextUtil.equals( this.osFile, EmuUtil.getProperty( props, - "jkcemu.zxspectrum.rom.os.file" ) ) ) + this.propPrefix + "rom.file" ) ) ) { rv = false; } @@ -347,6 +454,9 @@ public void die() cpu.removeTStatesListener( this ); cpu.removeMaxSpeedListener( this ); cpu.setInterruptSources( (Z80InterruptSource[]) null ); + if( this.psg != null ) { + this.psg.die(); + } } @@ -360,7 +470,7 @@ public int getBorderColorIndex() @Override public int getBorderColorIndexByLine( int line ) { - line += FIRST_VISIBLE_LINE; + line += this.firstScreenLine; if( (line < 0) || (line >= this.borderColorNums.length) ) { line = Math.abs( line ) % this.borderColorNums.length; } @@ -430,12 +540,25 @@ public int getMemByte( int addr, boolean m1 ) int rv = 0xFF; if( addr < 0x4000 ) { if( this.osBytes != null ) { - if( addr < this.osBytes.length ) { - rv = (int) this.osBytes[ addr ] & 0xFF; + int idx = addr + this.romOffs; + if( idx < this.osBytes.length ) { + rv = (int) this.osBytes[ idx ] & 0xFF; } } } else { - rv = this.emuThread.getRAMByte( addr ); + if( this.ram != null ) { + int idx = addr; + if( (addr >= 0x4000) && (addr < 0x8000) ) { + idx += (4 * 0x4000); // - 0x4000 + (5 * 0x4000) + } else if( addr >= 0xC000 ) { + idx = addr - 0xC000 + this.ramC000Offs; + } + if( idx < this.ram.length ) { + rv = (int) (this.ram[ idx ] & 0xFF); + } + } else { + rv = this.emuThread.getRAMByte( addr ); + } } return rv; } @@ -664,7 +787,7 @@ protected boolean pasteChar( char ch ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; if( (port & 0x01) == 0 ) { @@ -700,11 +823,10 @@ public int readIOByte( int port ) if( !running ) { /* * Wenn am Mic-Eingang kein Pegel anliegt, - * wird das Bit vom Ausgangspegel des Ear-Anschlusses beeinflusst. - * Beeinflussung von Ausgabebit 4 (Ear) emulieren - * Ear 1 -> 0: Verzoegerung, abhangig von der Dauer - * des vorherigen H-Pegels - * Ear 0 -> 1: keine Verzoegerung + * wird das Bit vom EAR-Ausgangspegel beeinflusst: + * 1 -> 0: Verzoegerung, abhangig von der Dauer + * des vorherigen H-Pegels + * 0 -> 1: keine Verzoegerung */ if( !this.earPhase && (this.audioInLDelayTStateCounter <= 0) ) { rv &= ~0x40; @@ -730,6 +852,17 @@ public int readIOByte( int port ) rv |= 0x10; } } + if( this.mode128k ) { + if( (port & 0x8002) == 0x0000 ) { + // 7FFDh (A15=0, A1=0) + writeCfgReg( 0xFF ); + } else if( (port & 0xC002) == 0xC000 ) { + // FFFDh (A15=1, A14=1, A1=0) + if( this.psg != null ) { + rv = this.psg.getRegister( this.psgRegNum ); + } + } + } checkInsertWaitStates( port, 4 ); } return rv; @@ -747,7 +880,7 @@ public int readMemByte( int addr, boolean m1 ) * dass bereits ein 1-Byte-Befehlscode verarbeitet wurde * und somit 4 Takte zu beruecksichtigen sind. * Mit diesen Annahmen wird zwar nicht bei allen Befehlen - * die exakte Anzahl der Wait States emuliert, + * die exakte Anzahl der Wait States emuliert, * aber bei einem grossen Teil. */ checkInsertWaitStates( addr, m1 ? 0 : 4 ); @@ -781,7 +914,19 @@ public boolean setMemByte( int addr, int value ) boolean rv = false; if( addr >= 0x4000 ) { - this.emuThread.setRAMByte( addr, value ); + if( this.ram != null ) { + int idx = addr; + if( (addr >= 0x4000) && (addr < 0x8000) ) { + idx += (4 * 0x4000); // - 0x4000 + (5 * 0x4000) + } else if( addr >= 0xC000 ) { + idx = idx - 0xC000 + this.ramC000Offs; + } + if( idx < this.ram.length ) { + this.ram[ idx ] = (byte) value; + } + } else { + this.emuThread.setRAMByte( addr, value ); + } rv = true; } return rv; @@ -831,7 +976,7 @@ public String toString() @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { if( (port & 0x01) == 0 ) { /* @@ -843,13 +988,26 @@ public void writeIOByte( int port, int value ) * nicht ganz exakt. */ checkInsertWaitStates( (port & 0x3FFF) | 0x4000, 4 ); - this.borderColorNum = value & 0x07; - boolean earPhase = ((value & 0x10) != 0); - if( this.emuThread.isSoundOutEnabled() ) { - this.emuThread.writeAudioPhase( earPhase ); - } else { - this.emuThread.writeAudioPhase( (value & 0x08) != 0 ); + this.borderColorNum = value & 0x07; + int audioOutBits = (value & 0x18); + boolean earPhase = ((value & 0x10) != 0); + AudioOut audioOut = this.audioOut; + if( audioOut != null ) { + if( audioOut.isSoundOutEnabled() ) { + if( this.psg == null ) { + audioOut.writePhase( earPhase ); + } + } else { + if( audioOutBits != this.audioOutBits ) { + this.audioOutPhase = !this.audioOutPhase; + } + audioOut.writePhase( this.audioOutPhase ); + } } + this.audioOutBits = audioOutBits; + /* + * Emulation der Rueckkopplung vom EAR-Ausgang zum Eingang + */ if( earPhase != this.earPhase ) { if( earPhase ) { this.audioInLDelayTStateCounter = 0; @@ -861,6 +1019,20 @@ public void writeIOByte( int port, int value ) this.earPhase = earPhase; } } else { + if( this.mode128k ) { + if( (port & 0x8002) == 0x0000 ) { + // 7FFDh (A15=0, A1=0) + writeCfgReg( value ); + } else if( (port & 0xC002) == 0x8000 ) { + // BFFDh (A15=0, A14=1, A1=0) + if( this.psg != null ) { + this.psg.setRegister( this.psgRegNum, value ); + } + } else if( (port & 0xC002) == 0xC000 ) { + // FFFDh (A15=1, A14=1, A1=0) + this.psgRegNum = value & 0x0F; + } + } checkInsertWaitStates( port, 4 ); } } @@ -886,9 +1058,11 @@ public void writeMemByte( int addr, int value ) private void checkInsertWaitStates( int addr, int diffTStates ) { addr &= 0xFFFF; - if( (addr >= 0x4000) && (addr < 0x8000) - && (this.curScreenLine >= FIRST_VISIBLE_LINE) - && (this.curScreenLine <= LAST_VISIBLE_LINE) ) + if( (((addr >= 0x4000) && (addr < 0x8000)) + || (this.mode128k && (addr >= 0xC000) + && ((this.ramC000Offs & 0x4000) != 0))) + && (this.curScreenLine >= this.firstScreenLine) + && (this.curScreenLine <= this.lastScreenLine) ) { int t = Math.round( (float) (this.lineTStateCounter + diffTStates) @@ -919,13 +1093,21 @@ private void createColors( Properties props ) } + private static boolean emulates128K( Properties props ) + { + return EmuUtil.getProperty( + props, + PROP_PREFIX + "model" ).equals( "128k" ); + } + + private Map getPixelCRC32ToCharMap() { if( pixelCRC32ToChar == null ) { byte[] os48kBytes = getOS48kBytes(); if( os48kBytes != null ) { if( os48kBytes.length >= 0x4000 ) { - Map map = new HashMap(); + Map map = new HashMap<>(); CRC32 crc = new CRC32(); int addr = 0x3D00; for( int c = 0x20; c <= 0x7F; c++ ) { @@ -957,12 +1139,24 @@ private byte[] getOS48kBytes() } + private byte[] getOS128kBytes() + { + if( os128k == null ) { + os128k = readResource( "/rom/zxspectrum/os128k.bin" ); + } + return os128k; + } + + private void loadROMs( Properties props ) { - this.osFile = EmuUtil.getProperty( props, "jkcemu.zxspectrum.rom.file" ); - this.osBytes = readFile( this.osFile, 0x4000, "ROM" ); + this.osFile = EmuUtil.getProperty( props, this.propPrefix + "rom.file" ); + this.osBytes = readROMFile( + this.osFile, + this.mode128k ? 0x8000 : 0x4000, + "ROM" ); if( osBytes == null ) { - this.osBytes = getOS48kBytes(); + this.osBytes = (this.mode128k ? getOS128kBytes() : getOS48kBytes()); } } @@ -996,18 +1190,29 @@ private void updKeyboardFld() private void updScreenLine() { int screenLine = this.curScreenLine; - int pixelLine = screenLine - FIRST_VISIBLE_LINE; + int pixelLine = screenLine - this.firstScreenLine; if( (pixelLine >= 0) && (pixelLine < SCREEN_HEIGHT) ) { int dstPos = pixelLine * SCREEN_WIDTH; int charRow = pixelLine / 8; - int attrAddr = 0x5800 + (charRow * 32); - int pixelAddr = 0x4000 + int attrAddr = this.screenOffs + 0x1800 + (charRow * 32); + int pixelAddr = this.screenOffs | ((pixelLine << 5) & 0x1800) | ((pixelLine << 2) & 0x00E0) | ((pixelLine << 8) & 0x0700); for( int i = 0; i < 32; i++ ) { - int attr = getMemByte( attrAddr++, false ); - int pixels = getMemByte( pixelAddr++, false ); + int attr = 0; + int pixels = 0; + if( this.ram != null ) { + if( attrAddr < this.ram.length ) { + attr = (int) this.ram[ attrAddr++ ] & 0xFF; + } + if( pixelAddr < this.ram.length ) { + pixels = (int) this.ram[ pixelAddr++ ] & 0xFF; + } + } else { + attr = getMemByte( attrAddr++, false ); + pixels = getMemByte( pixelAddr++, false ); + } boolean blink = ((attr & 0x80) != 0); boolean bright = ((attr & 0x40) != 0); for( int k = 0; k < 8; k++ ) { @@ -1034,5 +1239,16 @@ private void updScreenLine() this.borderColorNums[ screenLine ] = (byte) this.borderColorNum; } } + + + private void writeCfgReg( int value ) + { + if( this.mode128k && !this.cfgRegProtected ) { + this.ramC000Offs = 0x4000 * (value & 0x07); + this.screenOffs = ((value & 0x08) != 0 ? 7 : 5) * 0x4000; + this.romOffs = ((value & 0x10) != 0 ? 0x4000 : 0x0000); + this.cfgRegProtected = ((value & 0x20) != 0); + } + } } diff --git a/src/jkcemu/emusys/a5105/A5105SettingsFld.java b/src/jkcemu/emusys/a5105/A5105SettingsFld.java index 902b0e7..48706f3 100644 --- a/src/jkcemu/emusys/a5105/A5105SettingsFld.java +++ b/src/jkcemu/emusys/a5105/A5105SettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2012 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -13,13 +13,18 @@ import java.util.*; import javax.swing.*; import jkcemu.base.*; +import jkcemu.emusys.A5105; public class A5105SettingsFld extends AbstractSettingsFld { + private static final int DEFAULT_AUTO_ACTION_WAIT_MILLIS = 3000; + private JTabbedPane tabbedPane; private JPanel tabEtc; private RAMFloppiesSettingsFld tabRF; + private AutoLoadSettingsFld tabAutoLoad; + private AutoInputSettingsFld tabAutoInput; private JCheckBox btnFloppyDisk; private JCheckBox btnKCNet; private JCheckBox btnVDIP; @@ -36,18 +41,18 @@ public A5105SettingsFld( SettingsFrm settingsFrm, String propPrefix ) add( this.tabbedPane, BorderLayout.CENTER ); - // Bereich RAM-Floppies + // Tab RAM-Floppies this.tabRF = new RAMFloppiesSettingsFld( settingsFrm, propPrefix, - "RAM-Floppy an IO-Adressen 20h/21h", + "RAM-Floppy an E/A-Adressen 20h/21h", RAMFloppy.RFType.ADW, - "RAM-Floppy an IO-Adressen 24h/25h", + "RAM-Floppy an E/A-Adressen 24h/25h", RAMFloppy.RFType.ADW ); this.tabbedPane.addTab( "RAM-Floppies", this.tabRF ); - // Bereich Sonstiges + // Tab Sonstiges this.tabEtc = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Sonstiges", this.tabEtc ); @@ -88,6 +93,25 @@ public A5105SettingsFld( SettingsFrm settingsFrm, String propPrefix ) gbcEtc.gridy++; this.tabEtc.add( this.btnFixedScreenSize, gbcEtc ); + + // Tab AutoLoad + this.tabAutoLoad = new AutoLoadSettingsFld( + settingsFrm, + propPrefix, + DEFAULT_AUTO_ACTION_WAIT_MILLIS, + true ); + this.tabbedPane.addTab( "AutoLoad", this.tabAutoLoad ); + + + // Tab AutoInput + this.tabAutoInput = new AutoInputSettingsFld( + settingsFrm, + propPrefix, + A5105.getDefaultSwapKeyCharCase(), + DEFAULT_AUTO_ACTION_WAIT_MILLIS ); + this.tabbedPane.addTab( "AutoInput", this.tabAutoInput ); + + // Listener this.btnFloppyDisk.addActionListener( this ); this.btnKCNet.addActionListener( this ); @@ -105,11 +129,11 @@ public void applyInput( Component tab = null; try { - // RAM-Floppies + // Tab RAM-Floppies tab = this.tabRF; this.tabRF.applyInput( props, selected ); - // Sonstiges + // Tab Sonstiges EmuUtil.setProperty( props, this.propPrefix + "floppydisk.enabled", @@ -130,6 +154,14 @@ public void applyInput( props, this.propPrefix + "fixed_screen_size", this.btnFixedScreenSize.isSelected() ); + + // Tab AutoLoad + tab = this.tabAutoLoad; + this.tabAutoLoad.applyInput( props, selected ); + + // Tab AutoInput + tab = this.tabAutoInput; + this.tabAutoInput.applyInput( props, selected ); } catch( UserInputException ex ) { if( tab != null ) { @@ -146,7 +178,11 @@ protected boolean doAction( EventObject e ) boolean rv = false; Object src = e.getSource(); if( src != null ) { - if( src instanceof AbstractButton ) { + rv = this.tabAutoLoad.doAction( e ); + if( !rv ) { + rv = this.tabAutoInput.doAction( e ); + } + if( !rv && (src instanceof AbstractButton) ) { rv = true; fireDataChanged(); } @@ -155,15 +191,28 @@ protected boolean doAction( EventObject e ) } + @Override + public void lookAndFeelChanged() + { + this.tabRF.lookAndFeelChanged(); + this.tabAutoLoad.lookAndFeelChanged(); + this.tabAutoInput.lookAndFeelChanged(); + } + + @Override public void updFields( Properties props ) { this.tabRF.updFields( props ); + this.tabAutoLoad.updFields( props ); + this.tabAutoInput.updFields( props ); + this.btnFloppyDisk.setSelected( EmuUtil.getBooleanProperty( props, this.propPrefix + "floppydisk.enabled", true ) ); + this.btnKCNet.setSelected( EmuUtil.getBooleanProperty( props, @@ -175,11 +224,13 @@ public void updFields( Properties props ) props, this.propPrefix + "vdip.enabled", false ) ); + this.btnPasteFast.setSelected( EmuUtil.getBooleanProperty( props, this.propPrefix + "paste.fast", true ) ); + this.btnFixedScreenSize.setSelected( EmuUtil.getBooleanProperty( props, diff --git a/src/jkcemu/emusys/ac1_llc2/AC1SettingsFld.java b/src/jkcemu/emusys/ac1_llc2/AC1SettingsFld.java index 566f573..8e2ed9f 100644 --- a/src/jkcemu/emusys/ac1_llc2/AC1SettingsFld.java +++ b/src/jkcemu/emusys/ac1_llc2/AC1SettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2013 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -15,16 +15,21 @@ import javax.swing.*; import jkcemu.base.*; import jkcemu.disk.GIDESettingsFld; +import jkcemu.emusys.AC1; public class AC1SettingsFld extends AbstractSettingsFld { + private static final int DEFAULT_AUTO_ACTION_WAIT_MILLIS = 500; + private JTabbedPane tabbedPane; private JRadioButton btnMon31_64x16; private JRadioButton btnMon31_64x32; private JRadioButton btnMonSCCH80; private JRadioButton btnMonSCCH1088; private JRadioButton btnMon2010; + private JRadioButton btnCTCAllSerial; + private JRadioButton btnCTCM1ToClk2; private JCheckBox btnColor; private JCheckBox btnFloppyDisk; private JCheckBox btnJoystick; @@ -41,7 +46,10 @@ public class AC1SettingsFld extends AbstractSettingsFld private GIDESettingsFld tabGIDE; private SCCHModule1SettingsFld tabSCCH; private JPanel tabExt; + private JPanel tabCtc; private JPanel tabEtc; + private AutoLoadSettingsFld tabAutoLoad; + private AutoInputSettingsFld tabAutoInput; public AC1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) @@ -53,7 +61,7 @@ public AC1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) add( this.tabbedPane, BorderLayout.CENTER ); - // Modell + // Tab Modell this.tabModel = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Modell", this.tabModel ); @@ -106,7 +114,7 @@ public AC1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.tabModel.add( this.btnMon2010, gbcModel ); - // SCCH-Modul 1 + // Tab SCCH-Modul 1 this.tabSCCH = new SCCHModule1SettingsFld( settingsFrm, propPrefix + "scch."); @@ -114,7 +122,7 @@ public AC1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) updSCCHFieldsEnabled(); - // AC1-2010 + // Tab AC1-2010 this.tab2010 = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "AC1-2010", this.tab2010 ); @@ -143,21 +151,21 @@ public AC1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) upd2010FieldsEnabled(); - // Bereich RAM-Floppy + // Tab RAM-Floppy this.tabRF = new RAMFloppySettingsFld( settingsFrm, propPrefix + "ramfloppy.", - "RAM-Floppy nach MP 3/88 (256 KByte) an IO-Adressen E0h-E7h", + "RAM-Floppy nach MP 3/88 (256 KByte) an E/A-Adressen E0h-E7h", RAMFloppy.RFType.MP_3_1988 ); this.tabbedPane.addTab( "RAM-Floppy", this.tabRF ); - // GIDE + // Tab GIDE this.tabGIDE = new GIDESettingsFld( settingsFrm, propPrefix ); this.tabbedPane.addTab( "GIDE", this.tabGIDE ); - // Erweiterungen + // Tab Erweiterungen this.tabExt = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Erweiterungen", this.tabExt ); @@ -196,26 +204,61 @@ public AC1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.tabExt.add( this.btnJoystick, gbcExt ); - // Sonstiges + // Tab CTC + this.tabCtc = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "CTC", this.tabCtc ); + + GridBagConstraints gbcCtc = new GridBagConstraints( + 0, 0, + 1, 1, + 1.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.REMAINDER, + new Insets( 5, 5, 5, 5 ), + 0, 0 ); + + this.tabCtc.add( new JLabel( "Kopplung der CTC-Kan\u00E4le:" ), gbcCtc ); + + ButtonGroup grpCTC = new ButtonGroup(); + + this.btnCTCAllSerial = new JRadioButton( + "Alle Kan\u00E4le in Reihe gekoppelt", + true ); + grpCTC.add( this.btnCTCAllSerial ); + gbcCtc.insets.left = 50; + gbcCtc.insets.bottom = 0; + gbcCtc.gridy++; + this.tabCtc.add( this.btnCTCAllSerial, gbcCtc ); + + this.btnCTCM1ToClk2 = new JRadioButton( + "/M1 an Kanal 2, die anderen Kan\u00E4le in Reihe gekoppelt", + false ); + grpCTC.add( this.btnCTCM1ToClk2 ); + gbcCtc.insets.top = 0; + gbcCtc.insets.bottom = 5; + gbcCtc.gridy++; + this.tabCtc.add( this.btnCTCM1ToClk2, gbcCtc ); + + + // Tab Sonstiges this.tabEtc = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Sonstiges", this.tabEtc ); GridBagConstraints gbcEtc = new GridBagConstraints( 0, 0, GridBagConstraints.REMAINDER, 1, - 0.0, 0.0, + 1.0, 0.0, GridBagConstraints.WEST, - GridBagConstraints.NONE, + GridBagConstraints.HORIZONTAL, new Insets( 5, 5, 5, 5 ), 0, 0 ); this.btnPasteFast = new JCheckBox( - "Einf\u00FCgen von Text durch Abfangen des Systemaufrufs", - true ); + "Einf\u00FCgen von Text durch Abfangen des Systemaufrufs" ); this.tabEtc.add( this.btnPasteFast, gbcEtc ); - gbcEtc.fill = GridBagConstraints.HORIZONTAL; - gbcEtc.weightx = 1.0; + gbcEtc.insets.top = 10; + gbcEtc.insets.bottom = 10; gbcEtc.gridy++; this.tabEtc.add( new JSeparator(), gbcEtc ); @@ -223,6 +266,8 @@ public AC1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) settingsFrm, propPrefix + "os.", "Alternatives Monitorprogramm (0000h-0FFFh):" ); + gbcEtc.insets.top = 5; + gbcEtc.insets.bottom = 5; gbcEtc.gridy++; this.tabEtc.add( this.fldAltOS, gbcEtc ); @@ -234,6 +279,24 @@ public AC1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.tabEtc.add( this.fldAltFont, gbcEtc ); + // Tab AutoLoad + this.tabAutoLoad = new AutoLoadSettingsFld( + settingsFrm, + propPrefix, + DEFAULT_AUTO_ACTION_WAIT_MILLIS, + true ); + this.tabbedPane.addTab( "AutoLoad", this.tabAutoLoad ); + + + // Tab AutoInput + this.tabAutoInput = new AutoInputSettingsFld( + settingsFrm, + propPrefix, + AC1.getDefaultSwapKeyCharCase(), + DEFAULT_AUTO_ACTION_WAIT_MILLIS ); + this.tabbedPane.addTab( "AutoInput", this.tabAutoInput ); + + // Listener this.btnMon31_64x16.addActionListener( this ); this.btnMon31_64x32.addActionListener( this ); @@ -245,6 +308,8 @@ public AC1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.btnKCNet.addActionListener( this ); this.btnVDIP.addActionListener( this ); this.btnJoystick.addActionListener( this ); + this.btnCTCAllSerial.addActionListener( this ); + this.btnCTCM1ToClk2.addActionListener( this ); this.btnPasteFast.addActionListener( this ); } @@ -260,6 +325,7 @@ public void applyInput( try { tab = this.tabModel; + // Tab Modell String os = "3.1_64x32"; if( this.btnMon31_64x16.isSelected() ) { os = "3.1_64x16"; @@ -275,19 +341,24 @@ else if( this.btnMon2010.isSelected() ) { } props.setProperty( this.propPrefix + "os.version", os ); + // Tab RAM-Floppy tab = this.tabRF; this.tabRF.applyInput( props, selected ); + // Tab GIDE tab = this.tabGIDE; this.tabGIDE.applyInput( props, selected ); + // Tab SCCH-Modul 1 tab = this.tabSCCH; this.tabSCCH.applyInput( props, selected ); + // AC1-2010 tab = this.tab2010; this.fldAltPio2Rom2010.applyInput( props, selected ); this.fldRomBank2010.applyInput( props, selected ); + // Erweiterungen tab = this.tabExt; EmuUtil.setProperty( props, @@ -310,13 +381,26 @@ else if( this.btnMon2010.isSelected() ) { this.propPrefix + "joystick.enabled", this.btnJoystick.isSelected() ); + // Tab Sonstiges tab = this.tabEtc; + EmuUtil.setProperty( + props, + this.propPrefix + "ctc.m1_to_clk2", + this.btnCTCM1ToClk2.isSelected() ); EmuUtil.setProperty( props, this.propPrefix + "paste.fast", this.btnPasteFast.isSelected() ); this.fldAltOS.applyInput( props, selected ); this.fldAltFont.applyInput( props, selected ); + + // Tab AutoLoad + tab = this.tabAutoLoad; + this.tabAutoLoad.applyInput( props, selected ); + + // Tab AutoInput + tab = this.tabAutoInput; + this.tabAutoInput.applyInput( props, selected ); } catch( UserInputException ex ) { if( tab != null ) { @@ -345,14 +429,21 @@ protected boolean doAction( EventObject e ) upd2010FieldsEnabled(); updSCCHFieldsEnabled(); fireDataChanged(); - } else if( src instanceof AbstractButton ) { + } + if( !rv ) { + rv = this.tabGIDE.doAction( e ); + } + if( !rv ) { + rv = this.tabAutoLoad.doAction( e ); + } + if( !rv ) { + rv = this.tabAutoInput.doAction( e ); + } + if( !rv && (src instanceof AbstractButton) ) { rv = true; fireDataChanged(); } } - if( !rv ) { - rv = this.tabGIDE.doAction( e ); - } this.settingsFrm.setWaitCursor( false ); return rv; } @@ -361,9 +452,15 @@ protected boolean doAction( EventObject e ) @Override public void lookAndFeelChanged() { + this.fldAltOS.lookAndFeelChanged(); + this.fldAltFont.lookAndFeelChanged(); + this.fldAltPio2Rom2010.lookAndFeelChanged(); + this.fldRomBank2010.lookAndFeelChanged(); this.tabSCCH.lookAndFeelChanged(); this.tabRF.lookAndFeelChanged(); this.tabGIDE.lookAndFeelChanged(); + this.tabAutoLoad.lookAndFeelChanged(); + this.tabAutoInput.lookAndFeelChanged(); } @@ -383,8 +480,10 @@ public void updFields( Properties props ) this.btnMonSCCH1088.setSelected( true ); } this.tabRF.updFields( props ); - this.tabGIDE.updFields( props ); this.tabSCCH.updFields( props ); + this.tabGIDE.updFields( props ); + this.tabAutoLoad.updFields( props ); + this.tabAutoInput.updFields( props ); this.fldAltPio2Rom2010.updFields( props ); this.fldRomBank2010.updFields( props ); @@ -418,6 +517,16 @@ public void updFields( Properties props ) this.propPrefix + "joystick.enabled", false ) ); + if( EmuUtil.getBooleanProperty( + props, + this.propPrefix + "ctc.m1_to_clk2", + false ) ) + { + this.btnCTCM1ToClk2.setSelected( true ); + } else { + this.btnCTCAllSerial.setSelected( true ); + } + this.btnPasteFast.setSelected( EmuUtil.getBooleanProperty( props, diff --git a/src/jkcemu/emusys/ac1_llc2/AbstractSCCHSys.java b/src/jkcemu/emusys/ac1_llc2/AbstractSCCHSys.java index 6894da6..c6ba1e8 100644 --- a/src/jkcemu/emusys/ac1_llc2/AbstractSCCHSys.java +++ b/src/jkcemu/emusys/ac1_llc2/AbstractSCCHSys.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2013 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -8,43 +8,62 @@ package jkcemu.emusys.ac1_llc2; +import java.awt.event.KeyEvent; import java.lang.*; +import java.text.*; import java.util.Properties; import jkcemu.base.*; +import jkcemu.text.TextUtil; import z80emu.*; -public abstract class AbstractSCCHSys extends EmuSys +public abstract class AbstractSCCHSys + extends EmuSys + implements Z80PCListener { + // Einprung zum Lesen eines Zeichens von der Tastatur + private static int ADDR_INCH = 0x1802; + /* * Diese Tabelle mappt die Tokens des SCCH-BASIC-Interpreters * in ihre entsprechenden Texte. * Der Index fuer die Tabelle ergibt sich aus "Wert des Tokens - 0x80". + * + * Die Tokens ab PAUSE (Codes 0xD9 und hoeher) wurden von Rolf Weidlich + * im Zuge seiner Weiterentwicklung des LLCBASIC-Interpreters eingefuehrt. */ protected static final String[] scchTokens = { - "END", "FOR", "NEXT", "DATA", // 0x80 + "END", "FOR", "NEXT", "DATA", // 0x80 "INPUT", "DIM", "READ", "LET", "GOTO", "RUN", "IF", "RESTORE", "GOSUB", "RETURN", "REM", "STOP", - "OUT", "ON", "NULL", "WAIT", // 0x90 + "OUT", "ON", "NULL", "WAIT", // 0x90 "DEF", "POKE", "DOKE", "AUTO", "LINES", "CLS", "WIDTH", "BYE", "KEY", "CALL", "PRINT", "CONT", - "LIST", "CLEAR", "CLOAD", "CSAVE", // 0xA0 + "LIST", "CLEAR", "CLOAD", "CSAVE", // 0xA0 "NEW", "TAB(", "TO", "FN", "SPC(", "THEN", "NOT", "STEP", "+", "-", "*", "/", - "^", "AND", "OR", ">", // 0xB0 + "^", "AND", "OR", ">", // 0xB0 "=", "<", "SGN", "INT", "ABS", "USR", "FRE", "INP", "POS", "SQR", "RND", "LN", - "EXP", "COS", "SIN", "TAN", // 0xC0 + "EXP", "COS", "SIN", "TAN", // 0xC0 "ATN", "PEEK", "DEEK", "POINT", "LEN", "STR$", "VAL", "ASC", "CHR$", "LEFT$", "RIGHT$", "MID$", - "SET", "RESET", "RENUMBER", "LOCATE", // 0xD0 + "SET", "RESET", "RENUMBER", "LOCATE", // 0xD0 "SOUND", "INKEY", "MODE", "TRON", - "TROFF" }; + "TROFF", "PAUSE", "EDIT", "DIR", + "BLOAD", "LPRINT", "LLIST", "BACO", + "CONV", "TRANS", "ALIST", "AEDIT", // 0xE0 + "ASAVE", "ALOAD", "INSTR", "JOY", + "TICKS", "TIME$", "PI", "UPPER$", + "SCREEN", "GCLS", "PSET", "LINE", + "DRAWTO", "BOX", "CIRCLE", "TEXT", // 0xF0 + "PLOAD", "PSAVE", "DLOAD", "DSAVE", + "DGET", "DPUT", "CD" }; protected static final int[] scchCharToUnicode = { '\u0020', '\u2598', '\u259D', '\u2580', // 00h @@ -117,11 +136,212 @@ public abstract class AbstractSCCHSys extends EmuSys protected volatile boolean joystickSelected; protected int joystickValue; protected int keyboardValue; + protected boolean rf32KActive; + protected boolean rf32NegA15; + protected boolean rfReadEnabled; + protected boolean rfWriteEnabled; + protected int rfAddr16to19; + protected byte[] ramModule3; + protected byte[] scchBasicRomBytes; + protected byte[] scchPrgXRomBytes; + protected byte[] scchRomdiskBytes; + protected String scchBasicRomFile; + protected String scchPrgXRomFile; + protected String scchRomdiskFile; + protected boolean scchBasicRomEnabled; + protected int scchBasicRomBegAddr; + protected boolean scchPrgXRomEnabled; + protected boolean scchRomdiskEnabled; + protected int scchRomdiskBegAddr; + protected int scchRomdiskBankAddr; + protected volatile boolean pasteFast; + protected boolean v24BitOut; + protected int v24BitNum; + protected int v24ShiftBuf; + protected int v24TStateCounter; + protected int v24TStatesPerBit; + + private byte[] gsbasic; + + + protected AbstractSCCHSys( + EmuThread emuThread, + Properties props, + String propPrefix ) + { + super( emuThread, props, propPrefix ); + this.joystickEnabled = emulatesJoystick( props ); + this.ramModule3 = null; + this.scchBasicRomBegAddr = getScchBasicRomBegAddr( props ); + this.scchBasicRomBytes = null; + this.scchBasicRomFile = null; + this.scchPrgXRomBytes = null; + this.scchPrgXRomFile = null; + this.scchRomdiskBytes = null; + this.scchRomdiskFile = null; + this.scchRomdiskBegAddr = getScchRomdiskBegAddr( props ); + this.scchRomdiskBankAddr = 0; + this.pasteFast = false; + this.gsbasic = null; + } + + + protected synchronized void checkAddPCListener( Properties props ) + { + Z80CPU cpu = this.emuThread.getZ80CPU(); + if( cpu != null ) { + boolean pasteFast = EmuUtil.getBooleanProperty( + props, + this.propPrefix + "paste.fast", + false ); + if( pasteFast != this.pasteFast ) { + this.pasteFast = pasteFast; + if( pasteFast ) { + cpu.addPCListener( this, ADDR_INCH ); + } else { + cpu.removePCListener( this ); + } + } + } + } + + + protected boolean emulatesFloppyDisk( Properties props ) + { + return EmuUtil.getBooleanProperty( + props, + this.propPrefix + "floppydisk.enabled", + false ); + } - protected AbstractSCCHSys( EmuThread emuThread, Properties props ) + protected boolean emulatesJoystick( Properties props ) { - super( emuThread, props ); + return EmuUtil.getBooleanProperty( + props, + this.propPrefix + "joystick.enabled", + false ); + } + + + protected boolean emulatesKCNet( Properties props ) + { + return EmuUtil.getBooleanProperty( + props, + this.propPrefix + "kcnet.enabled", + false ); + } + + + protected boolean emulatesUSB( Properties props ) + { + return EmuUtil.getBooleanProperty( + props, + this.propPrefix + "vdip.enabled", + false ); + } + + + /* + * Lesen eines Bytes aus dem Speicher + * + * Rueckgabewert: + * >= 0: gelesenes Byte + * < 0: kein SCCH-Modul an der angegebenen Adresse aktiv + */ + protected int getScchMemByte( int addr, boolean m1 ) + { + int rv = 0xFF; + boolean done = false; + if( !m1 && this.rfReadEnabled && (this.ramModule3 != null) ) { + int idx = this.rfAddr16to19 | addr; + if( (idx >= 0) && (idx < this.ramModule3.length) ) { + rv = (int) this.ramModule3[ idx ] & 0xFF; + } + done = true; + } + if( !done && this.rf32KActive && (this.ramModule3 != null) + && (addr >= 0x4000) && (addr < 0xC000) ) + { + int idx = this.rfAddr16to19 | addr; + if( this.rf32NegA15 ) { + idx ^= 0x8000; + } + if( (idx >= 0) && (idx < this.ramModule3.length) ) { + rv = (int) this.ramModule3[ idx ] & 0xFF; + } + done = true; + } + if( !done && this.scchBasicRomEnabled + && (addr >= this.scchBasicRomBegAddr) && (addr < 0x6000) ) + { + if( this.scchBasicRomBytes != null ) { + int idx = addr - this.scchBasicRomBegAddr; + if( idx < this.scchBasicRomBytes.length ) { + rv = (int) this.scchBasicRomBytes[ idx ] & 0xFF; + } + } + done = true; + } + if( !done && this.scchRomdiskEnabled + && (addr >= this.scchRomdiskBegAddr) ) + { + if( this.scchRomdiskBytes != null ) { + int idx = this.scchRomdiskBankAddr + | (addr - this.scchRomdiskBegAddr); + if( idx < this.scchRomdiskBytes.length ) { + rv = (int) this.scchRomdiskBytes[ idx ] & 0xFF; + } + } + done = true; + } + if( !done && this.scchPrgXRomEnabled && (addr >= 0xE000) ) { + if( this.scchPrgXRomBytes != null ) { + int idx = addr - 0xE000; + if( idx < this.scchPrgXRomBytes.length ) { + rv = (int) this.scchPrgXRomBytes[ idx ] & 0xFF; + } + } + done = true; + } + return done ? rv : -1; + } + + + protected void loadROMs( Properties props, String basicResource ) + { + // SCCH BASIC-ROM + this.scchBasicRomFile = EmuUtil.getProperty( + props, + this.propPrefix + "scch.basic.file" ); + this.scchBasicRomBytes = readROMFile( + this.scchBasicRomFile, + this.scchBasicRomBegAddr < 0x4000 ? 0x4000 : 0x2000, + "BASIC" ); + if( this.scchBasicRomBytes == null ) { + if( this.gsbasic == null ) { + this.gsbasic = readResource( basicResource ); + } + this.scchBasicRomBytes = this.gsbasic; + } + + // SCCH Programmpaket X + this.scchPrgXRomFile = EmuUtil.getProperty( + props, + this.propPrefix + "scch.program_x.file" ); + this.scchPrgXRomBytes = readROMFile( + this.scchPrgXRomFile, + 0x2000, + "Programmpaket X" ); + + // SCCH ROM-Disk + this.scchRomdiskFile = EmuUtil.getProperty( + props, + this.propPrefix + "scch.romdisk.file" ); + this.scchRomdiskBytes = readROMFile( + this.scchRomdiskFile, + 0x40000, + "SCCH-Modul 1 ROM-Disk" ); } @@ -136,8 +356,102 @@ protected void setKeyboardValue( int value ) } + /* + * Setzen eines Bytes im Arbeitsspeicher + * + * Rueckgabewert: + * < 0: kein SCCH-Modul an der angegebenen Adresse aktiv + * = 0: SCCH-Modul an der Adresse zwar aktiv, aber kein Byte geaendert + * > 0: Byte im SCCH-modul geaendert + */ + protected int setScchMemByte( int addr, int value ) + { + int rv = -1; + if( this.rfWriteEnabled && (this.ramModule3 != null) ) { + int idx = this.rfAddr16to19 | addr; + if( (idx >= 0) && (idx < this.ramModule3.length) ) { + this.ramModule3[ idx ] = (byte) value; + rv = 1; + } else { + rv = 0; + } + } + if( (rv < 0) && this.rf32KActive && (this.ramModule3 != null) + && (addr >= 0x4000) && (addr < 0xC000) ) + { + int idx = this.rfAddr16to19 | addr; + if( this.rf32NegA15 ) { + idx ^= 0x8000; + } + if( (idx >= 0) && (idx < this.ramModule3.length) ) { + this.ramModule3[ idx ] = (byte) value; + rv = 1; + } else { + rv = 0; + } + } + return rv; + } + + + /* --- Z80PCListener --- */ + + @Override + public synchronized void z80PCChanged( Z80CPU cpu, int pc ) + { + if( this.pasteFast && (pc == ADDR_INCH) ) { + CharacterIterator iter = this.pasteIter; + if( iter != null ) { + char ch = iter.next(); + while( ch != CharacterIterator.DONE ) { + if( (ch > 0) && (ch < 0x7F) ) { + cpu.setRegA( ch == '\n' ? '\r' : ch ); + cpu.setRegPC( cpu.doPop() ); + break; + } + ch = iter.next(); + } + if( ch == CharacterIterator.DONE ) { + cancelPastingText(); + } + } + } + } + + /* --- ueberschriebene Methoden --- */ + @Override + public boolean canApplySettings( Properties props ) + { + boolean rv = (emulatesJoystick( props ) == this.joystickEnabled); + if( rv ) { + rv = TextUtil.equals( + this.scchBasicRomFile, + EmuUtil.getProperty( props, this.propPrefix + "scch.basic.file" ) ); + } + if( rv && (this.scchBasicRomFile != null)) { + rv = (this.scchBasicRomBegAddr == getScchBasicRomBegAddr( props )); + } + if( rv ) { + rv = TextUtil.equals( + this.scchPrgXRomFile, + EmuUtil.getProperty( + props, + this.propPrefix + "scch.program_x.file" ) ); + } + if( rv ) { + rv = TextUtil.equals( + this.scchRomdiskFile, + EmuUtil.getProperty( props, this.propPrefix + "scch.romdisk.file" ) ); + } + if( rv && (this.scchRomdiskFile != null)) { + rv = (this.scchRomdiskBegAddr == getScchRomdiskBegAddr( props )); + } + return rv; + } + + @Override public int getSupportedJoystickCount() { @@ -148,10 +462,10 @@ public int getSupportedJoystickCount() @Override public void openBasicProgram() { - String text = SourceUtil.getKCBasicStyleProgram( - this.emuThread, - 0x60F7, - scchTokens ); + String text = SourceUtil.getBasicProgram( + this.emuThread, + 0x60F7, + scchTokens ); if( text != null ) { this.screenFrm.openText( text ); } else { @@ -160,6 +474,94 @@ public void openBasicProgram() } + @Override + public boolean keyPressed( + int keyCode, + boolean ctrlDown, + boolean shiftDown ) + { + boolean rv = false; + int ch = 0; + switch( keyCode ) { + case KeyEvent.VK_LEFT: + ch = 8; + break; + + case KeyEvent.VK_RIGHT: + ch = 9; + break; + + case KeyEvent.VK_DOWN: + ch = 0x0A; + break; + + case KeyEvent.VK_UP: + ch = 0x0B; + break; + + case KeyEvent.VK_ENTER: + ch = 0x0D; + break; + + case KeyEvent.VK_SPACE: + ch = 0x20; + break; + + case KeyEvent.VK_BACK_SPACE: + ch = 0x7F; + break; + + case KeyEvent.VK_DELETE: + ch = 4; + break; + + case KeyEvent.VK_INSERT: + ch = 5; + break; + + case KeyEvent.VK_PAGE_UP: + ch = 0x11; + break; + + case KeyEvent.VK_PAGE_DOWN: + ch = 0x15; + break; + + case KeyEvent.VK_HOME: + ch = 1; + break; + + case KeyEvent.VK_END: + ch = 0x1A; + break; + } + if( ch > 0 ) { + setKeyboardValue( ch | 0x80 ); + rv = true; + } + return rv; + } + + + @Override + public void keyReleased() + { + setKeyboardValue( 0 ); + } + + + @Override + public boolean keyTyped( char ch ) + { + boolean rv = false; + if( (ch > 0) && (ch < 0x7F) ) { + setKeyboardValue( ch | 0x80 ); + rv = true; + } + return rv; + } + + @Override public int reassembleSysCall( Z80MemView memory, @@ -336,9 +738,68 @@ public int reassembleSysCall( @Override public void reset( EmuThread.ResetLevel resetLevel, Properties props ) { - this.joystickSelected = false; - this.joystickValue = 0; - this.keyboardValue = 0; + this.joystickSelected = false; + this.joystickValue = 0; + this.keyboardValue = 0; + this.rf32KActive = false; + this.rf32NegA15 = false; + this.rfReadEnabled = false; + this.rfWriteEnabled = false; + this.rfAddr16to19 = 0; + this.scchBasicRomEnabled = false; + this.scchPrgXRomEnabled = false; + this.scchRomdiskEnabled = false; + this.scchRomdiskBankAddr = 0; + this.v24BitOut = true; // V24: H-Pegel + this.v24BitNum = 0; + this.v24ShiftBuf = 0; + this.v24TStateCounter = 0; + } + + + @Override + public synchronized void startPastingText( String text ) + { + boolean done = false; + if( text != null ) { + if( !text.isEmpty() ) { + if( this.pasteFast ) { + CharacterIterator iter = new StringCharacterIterator( text ); + char ch = iter.first(); + if( ch != CharacterIterator.DONE ) { + if( ch == '\n' ) { + ch = '\r'; + } + /* + * Da sich die Programmausfuehrung i.d.R. bereits + * in der betreffenden Systemfunktion befindet, + * muss das erste Zeichen direkt an der Tastatur + * angelegt werden, + * damit der Systemaufruf beendet wird und somit + * der naechste Aufruf dann abgefangen werden kann. + */ + keyTyped( ch ); + long millis = (ch == '\r' ? + getDelayMillisAfterPasteEnter() + : getDelayMillisAfterPasteChar()); + if( millis > 0 ) { + try { + Thread.sleep( millis ); + } + catch( InterruptedException ex ) {} + } + this.pasteIter = iter; + done = true; + } + } else { + super.startPastingText( text ); + done = true; + } + } + } + if( !done ) { + this.screenFrm.firePastingTextFinished(); + } } @@ -347,5 +808,52 @@ public boolean supportsOpenBasic() { return true; } -} + + @Override + public boolean supportsPasteFromClipboard() + { + return true; + } + + + /* --- private Methoden --- */ + + private int getScchBasicRomBegAddr( Properties props ) + { + int rv = 0x4000; + if( props != null ) { + if( !EmuUtil.getProperty( + props, + this.propPrefix + "scch.basic.file" ).isEmpty() ) + { + String text = EmuUtil.getProperty( + props, + this.propPrefix + "scch.basicrom.address.begin" ); + if( text != null ) { + if( text.trim().startsWith( "2000" ) ) { + rv = 0x2000; + } + } + } + } + return rv; + } + + + private int getScchRomdiskBegAddr( Properties props ) + { + int rv = 0xC000; + if( props != null ) { + String text = EmuUtil.getProperty( + props, + this.propPrefix + "scch.romdisk.address.begin" ); + if( text != null ) { + if( text.trim().startsWith( "8000" ) ) { + rv = 0x8000; + } + } + } + return rv; + } +} diff --git a/src/jkcemu/emusys/ac1_llc2/LLC2SettingsFld.java b/src/jkcemu/emusys/ac1_llc2/LLC2SettingsFld.java index 45adf03..3fb776b 100644 --- a/src/jkcemu/emusys/ac1_llc2/LLC2SettingsFld.java +++ b/src/jkcemu/emusys/ac1_llc2/LLC2SettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2013 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -15,10 +15,13 @@ import javax.swing.*; import jkcemu.base.*; import jkcemu.disk.GIDESettingsFld; +import jkcemu.emusys.LLC2; public class LLC2SettingsFld extends AbstractSettingsFld { + private static final int DEFAULT_AUTO_ACTION_WAIT_MILLIS = 800; + private JTabbedPane tabbedPane; private SCCHModule1SettingsFld tabSCCH; private RAMFloppiesSettingsFld tabRF; @@ -26,10 +29,15 @@ public class LLC2SettingsFld extends AbstractSettingsFld private JCheckBox btnJoystick; private JCheckBox btnKCNet; private JCheckBox btnVDIP; + private JCheckBox btnPasteFast; + private JCheckBox btnRatio43; private ROMFileSettingsFld fldAltOS; private ROMFileSettingsFld fldAltFont; private GIDESettingsFld tabGIDE; + private JPanel tabExt; private JPanel tabEtc; + private AutoLoadSettingsFld tabAutoLoad; + private AutoInputSettingsFld tabAutoInput; public LLC2SettingsFld( SettingsFrm settingsFrm, String propPrefix ) @@ -41,31 +49,34 @@ public LLC2SettingsFld( SettingsFrm settingsFrm, String propPrefix ) add( this.tabbedPane, BorderLayout.CENTER ); - // SCCH-Modul 1 - this.tabSCCH = new SCCHModule1SettingsFld( settingsFrm, propPrefix ); + // Tab SCCH-Modul 1 + this.tabSCCH = new SCCHModule1SettingsFld( + settingsFrm, + propPrefix + "scch." ); this.tabbedPane.addTab( "SCCH-Modul 1", this.tabSCCH ); - // RAM-Floppies + // Tab RAM-Floppies this.tabRF = new RAMFloppiesSettingsFld( settingsFrm, propPrefix, - "RAM-Floppy nach MP 3/88 (256 KByte) an IO-Adressen D0h-D7h", + "RAM-Floppy nach MP 3/88 (256 KByte) an E/A-Adressen D0h-D7h", RAMFloppy.RFType.MP_3_1988, - "RAM-Floppy nach MP 3/88 (256 KByte) an IO-Adressen B0h-B7h", + "RAM-Floppy nach MP 3/88 (256 KByte) an E/A-Adressen B0h-B7h", RAMFloppy.RFType.MP_3_1988 ); this.tabbedPane.addTab( "RAM-Floppies", this.tabRF ); - // GIDE + // Tab GIDE this.tabGIDE = new GIDESettingsFld( settingsFrm, propPrefix ); this.tabbedPane.addTab( "GIDE", this.tabGIDE ); - // Sonstiges - this.tabEtc = new JPanel( new GridBagLayout() ); - this.tabbedPane.addTab( "Sonstiges", this.tabEtc ); - GridBagConstraints gbcEtc = new GridBagConstraints( + // Tab Erweiterungen + this.tabExt = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Erweiterungen", this.tabExt ); + + GridBagConstraints gbcExt = new GridBagConstraints( 0, 0, GridBagConstraints.REMAINDER, 1, 0.0, 0.0, @@ -75,30 +86,53 @@ public LLC2SettingsFld( SettingsFrm settingsFrm, String propPrefix ) 0, 0 ); this.btnFloppyDisk = new JCheckBox( "Floppy-Disk-Modul", false ); - gbcEtc.gridy++; - this.tabEtc.add( this.btnFloppyDisk, gbcEtc ); + gbcExt.gridy++; + this.tabExt.add( this.btnFloppyDisk, gbcExt ); - this.btnKCNet = new JCheckBox( - "KCNet-kompatible Netzwerkkarte", - false ); - gbcEtc.insets.top = 0; - gbcEtc.gridy++; - this.tabEtc.add( this.btnKCNet, gbcEtc ); + this.btnKCNet = new JCheckBox( "KCNet-kompatible Netzwerkkarte" ); + gbcExt.insets.top = 0; + gbcExt.gridy++; + this.tabExt.add( this.btnKCNet, gbcExt ); - this.btnVDIP = new JCheckBox( - "USB-Anschluss (Vinculum VDIP Modul)", - false ); - gbcEtc.gridy++; - this.tabEtc.add( this.btnVDIP, gbcEtc ); + this.btnVDIP = new JCheckBox( "USB-Anschluss (Vinculum VDIP Modul)" ); + gbcExt.gridy++; + this.tabExt.add( this.btnVDIP, gbcExt ); + + this.btnJoystick = new JCheckBox( "Joystick" ); + gbcExt.insets.bottom = 5; + gbcExt.gridy++; + this.tabExt.add( this.btnJoystick, gbcExt ); + + + // Tab Sonstiges + this.tabEtc = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Sonstiges", this.tabEtc ); - this.btnJoystick = new JCheckBox( "Joystick", false ); + GridBagConstraints gbcEtc = new GridBagConstraints( + 0, 0, + GridBagConstraints.REMAINDER, 1, + 1.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.HORIZONTAL, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + this.btnRatio43 = new JCheckBox( + "Bildschirmausgabe im Format 4:3", + false ); + this.tabEtc.add( this.btnRatio43, gbcEtc ); + + this.btnPasteFast = new JCheckBox( + "Einf\u00FCgen von Text durch Abfangen des Systemaufrufs" ); + gbcEtc.insets.top = 0; gbcEtc.insets.bottom = 5; gbcEtc.gridy++; - this.tabEtc.add( this.btnJoystick, gbcEtc ); + this.tabEtc.add( this.btnPasteFast, gbcEtc ); - gbcEtc.fill = GridBagConstraints.HORIZONTAL; - gbcEtc.weightx = 1.0; - gbcEtc.insets.top = 5; + gbcEtc.fill = GridBagConstraints.HORIZONTAL; + gbcEtc.weightx = 1.0; + gbcEtc.insets.top = 10; + gbcEtc.insets.bottom = 10; gbcEtc.gridy++; this.tabEtc.add( new JSeparator(), gbcEtc ); @@ -106,6 +140,8 @@ public LLC2SettingsFld( SettingsFrm settingsFrm, String propPrefix ) settingsFrm, propPrefix + "os.", "Alternatives Monitorprogramm (0000h-0FFFh):" ); + gbcEtc.insets.top = 5; + gbcEtc.insets.bottom = 5; gbcEtc.gridy++; this.tabEtc.add( this.fldAltOS, gbcEtc ); @@ -117,11 +153,31 @@ public LLC2SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.tabEtc.add( this.fldAltFont, gbcEtc ); + // Tab AutoLoad + this.tabAutoLoad = new AutoLoadSettingsFld( + settingsFrm, + propPrefix, + DEFAULT_AUTO_ACTION_WAIT_MILLIS, + true ); + this.tabbedPane.addTab( "AutoLoad", this.tabAutoLoad ); + + + // Tab AutoInput + this.tabAutoInput = new AutoInputSettingsFld( + settingsFrm, + propPrefix, + LLC2.getDefaultSwapKeyCharCase(), + DEFAULT_AUTO_ACTION_WAIT_MILLIS ); + this.tabbedPane.addTab( "AutoInput", this.tabAutoInput ); + + // Listener this.btnFloppyDisk.addActionListener( this ); this.btnKCNet.addActionListener( this ); this.btnVDIP.addActionListener( this ); this.btnJoystick.addActionListener( this ); + this.btnPasteFast.addActionListener( this ); + this.btnRatio43.addActionListener( this ); } @@ -134,15 +190,20 @@ public void applyInput( { Component tab = null; try { + + // Tab SCCH-Modul 1 tab = this.tabSCCH; this.tabSCCH.applyInput( props, selected ); + // Tab RAM-Floppies tab = this.tabRF; this.tabRF.applyInput( props, selected ); + // Tab GIDE tab = this.tabGIDE; this.tabGIDE.applyInput( props, selected ); + // Tab Sonstiges tab = this.tabEtc; EmuUtil.setProperty( props, @@ -160,8 +221,24 @@ public void applyInput( props, this.propPrefix + "joystick.enabled", this.btnJoystick.isSelected() ); + EmuUtil.setProperty( + props, + this.propPrefix + "paste.fast", + this.btnPasteFast.isSelected() ); + EmuUtil.setProperty( + props, + this.propPrefix + "screen.ratio", + this.btnRatio43.isSelected() ? "4:3" : "unscaled" ); this.fldAltOS.applyInput( props, selected ); this.fldAltFont.applyInput( props, selected ); + + // Tab AutoLoad + tab = this.tabAutoLoad; + this.tabAutoLoad.applyInput( props, selected ); + + // Tab AutoInput + tab = this.tabAutoInput; + this.tabAutoInput.applyInput( props, selected ); } catch( UserInputException ex ) { if( tab != null ) { @@ -180,14 +257,18 @@ protected boolean doAction( EventObject e ) boolean rv = false; Object src = e.getSource(); if( src != null ) { - if( src instanceof AbstractButton ) { + rv = this.tabGIDE.doAction( e ); + if( !rv ) { + rv = this.tabAutoLoad.doAction( e ); + } + if( !rv ) { + rv = this.tabAutoInput.doAction( e ); + } + if( !rv && (src instanceof AbstractButton) ) { rv = true; fireDataChanged(); } } - if( !rv ) { - rv = this.tabGIDE.doAction( e ); - } this.settingsFrm.setWaitCursor( false ); return rv; } @@ -196,9 +277,13 @@ protected boolean doAction( EventObject e ) @Override public void lookAndFeelChanged() { + this.fldAltOS.lookAndFeelChanged(); + this.fldAltFont.lookAndFeelChanged(); this.tabSCCH.lookAndFeelChanged(); this.tabRF.lookAndFeelChanged(); this.tabGIDE.lookAndFeelChanged(); + this.tabAutoLoad.lookAndFeelChanged(); + this.tabAutoInput.lookAndFeelChanged(); } @@ -208,6 +293,8 @@ public void updFields( Properties props ) this.tabSCCH.updFields( props ); this.tabRF.updFields( props ); this.tabGIDE.updFields( props ); + this.tabAutoLoad.updFields( props ); + this.tabAutoInput.updFields( props ); this.btnFloppyDisk.setSelected( EmuUtil.getBooleanProperty( @@ -233,6 +320,17 @@ public void updFields( Properties props ) this.propPrefix + "joystick.enabled", false ) ); + this.btnPasteFast.setSelected( + EmuUtil.getBooleanProperty( + props, + this.propPrefix + "paste.fast", + false ) ); + + this.btnRatio43.setSelected( + EmuUtil.getProperty( + props, + this.propPrefix + "screen.ratio" ).startsWith( "4" ) ); + this.fldAltOS.updFields( props ); this.fldAltFont.updFields( props ); } diff --git a/src/jkcemu/emusys/ac1_llc2/SCCHAudioDataStream.java b/src/jkcemu/emusys/ac1_llc2/SCCHAudioDataStream.java index 8f06da2..d648225 100644 --- a/src/jkcemu/emusys/ac1_llc2/SCCHAudioDataStream.java +++ b/src/jkcemu/emusys/ac1_llc2/SCCHAudioDataStream.java @@ -1,5 +1,5 @@ /* - * (c) 2011 Jens Mueller + * (c) 2011-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -30,12 +30,12 @@ public SCCHAudioDataStream( // 1000 Schwingungen Vorton for( int i = 0; i < 2000; i++ ) { - addSamples( 4 ); + addPhaseChangeSamples( 4 ); } // Trennschwingung: 2x 1-Bit for( int i = 0; i < 2; i++ ) { - addSamples( 2 ); + addPhaseChangeSamples( 2 ); } // Adresse Kopfpuffer @@ -116,12 +116,12 @@ public SCCHAudioDataStream( pre = false; } for( int i = 0; i < n; i++ ) { - addSamples( 4 ); + addPhaseChangeSamples( 4 ); } // Trennschwingung: 2x 1-Bit for( int i = 0; i < 2; i++ ) { - addSamples( 2 ); + addPhaseChangeSamples( 2 ); } // Blockadresse @@ -144,7 +144,7 @@ public SCCHAudioDataStream( // abschliessender Phasenwechsel if( getFrameLength() > 0 ) { - addSamples( 16 ); + addPhaseChangeSamples( 16 ); } } @@ -155,10 +155,10 @@ private void addWordSamples( int value ) { for( int i = 0; i < 16; i++ ) { if( (value & 0x01) != 0 ) { - addSamples( 2 ); + addPhaseChangeSamples( 2 ); } else { - addSamples( 1 ); - addSamples( 1 ); + addPhaseChangeSamples( 1 ); + addPhaseChangeSamples( 1 ); } value >>= 1; } diff --git a/src/jkcemu/emusys/ac1_llc2/SCCHModule1SettingsFld.java b/src/jkcemu/emusys/ac1_llc2/SCCHModule1SettingsFld.java index a1a0ed1..075527f 100644 --- a/src/jkcemu/emusys/ac1_llc2/SCCHModule1SettingsFld.java +++ b/src/jkcemu/emusys/ac1_llc2/SCCHModule1SettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2011 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -23,7 +23,10 @@ public class SCCHModule1SettingsFld { private ROMFileSettingsFld fldPrgX; private ROMFileSettingsFld fldRomDisk; - private ROMFileSettingsFld fldBasic; + private ROMFileSettingsFld fldBasicRom; + private JLabel labelBasicRomAddr; + private JRadioButton btnBasicRom2000; + private JRadioButton btnBasicRom4000; private JLabel labelRomDiskAddr; private JRadioButton btnRomDisk8000; private JRadioButton btnRomDiskC000; @@ -53,21 +56,17 @@ public SCCHModule1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) // ROM-Disk this.fldRomDisk = new ROMFileSettingsFld( - settingsFrm, - propPrefix + "romdisk.", - "ROM-Datei f\u00FCr ROM-Disk:" ); + settingsFrm, + propPrefix + "romdisk.", + "ROM-Datei f\u00FCr ROM-Disk" + + " (8000h-FFFFh bzw. C000h-FFFFh):" ); gbc.insets.bottom = 0; gbc.gridy++; add( this.fldRomDisk, gbc ); JPanel panelRomDiskAddr = new JPanel( new FlowLayout( FlowLayout.LEFT, 5, 0 ) ); - gbc.anchor = GridBagConstraints.WEST; - gbc.insets.top = 0; - gbc.insets.left = 45; - gbc.insets.bottom = 5; - gbc.gridwidth = GridBagConstraints.REMAINDER; - gbc.gridx = 0; + gbc.insets.top = 0; gbc.gridy++; add( panelRomDiskAddr, gbc ); @@ -83,21 +82,43 @@ public SCCHModule1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.btnRomDiskC000 = new JRadioButton( "C000h", true ); grpRomDiskAddr.add( this.btnRomDiskC000 ); panelRomDiskAddr.add( this.btnRomDiskC000 ); - updROMDiskAddrFieldsEnabled(); + updRomDiskAddrFieldsEnabled(); // Trennlinie - gbc.insets.top = 5; - gbc.insets.left = 5; + gbc.insets.top = 10; + gbc.insets.bottom = 10; gbc.gridy++; add( new JSeparator(), gbc ); // BASIC-ROM - this.fldBasic = new ROMFileSettingsFld( - settingsFrm, - propPrefix + "basic.", - "Alternativer BASIC-ROM (4000h-5FFFh):" ); + this.fldBasicRom = new ROMFileSettingsFld( + settingsFrm, + propPrefix + "basic.", + "Alternativer BASIC-ROM (2000h-5FFFh bzw. 4000h-5FFFh):" ); + gbc.insets.top = 5; + gbc.insets.bottom = 0; + gbc.gridy++; + add( this.fldBasicRom, gbc ); + + JPanel panelBasicRomAddr = new JPanel( + new FlowLayout( FlowLayout.LEFT, 5, 0 ) ); + gbc.insets.top = 0; gbc.gridy++; - add( this.fldBasic, gbc ); + add( panelBasicRomAddr, gbc ); + + this.labelBasicRomAddr = new JLabel( "BASIC-ROM einblenden ab Adresse:" ); + panelBasicRomAddr.add( this.labelBasicRomAddr ); + ButtonGroup grpBasicRomAddr = new ButtonGroup(); + + this.btnBasicRom2000 = new JRadioButton( "2000h", false ); + grpBasicRomAddr.add( this.btnBasicRom2000 ); + panelBasicRomAddr.add( this.btnBasicRom2000 ); + + this.btnBasicRom4000 = new JRadioButton( "4000h", true ); + grpBasicRomAddr.add( this.btnBasicRom4000 ); + panelBasicRomAddr.add( this.btnBasicRom4000 ); + updBasicRomAddrFieldsEnabled(); + // Sonstiges setEnabled( true ); @@ -107,6 +128,9 @@ public SCCHModule1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.fldRomDisk.addChangeListener( this ); this.btnRomDisk8000.addActionListener( this ); this.btnRomDiskC000.addActionListener( this ); + this.fldBasicRom.addChangeListener( this ); + this.btnBasicRom2000.addActionListener( this ); + this.btnBasicRom4000.addActionListener( this ); } @@ -115,8 +139,12 @@ public SCCHModule1SettingsFld( SettingsFrm settingsFrm, String propPrefix ) @Override public void stateChanged( ChangeEvent e ) { - if( e.getSource() == this.fldRomDisk ) - updROMDiskAddrFieldsEnabled(); + Object src = e.getSource(); + if( src == this.fldRomDisk ) { + updRomDiskAddrFieldsEnabled(); + } else if( src == this.fldBasicRom ) { + updBasicRomAddrFieldsEnabled(); + } } @@ -129,7 +157,11 @@ public void applyInput( { this.fldPrgX.applyInput( props, selected ); this.fldRomDisk.applyInput( props, selected ); - this.fldBasic.applyInput( props, selected ); + this.fldBasicRom.applyInput( props, selected ); + EmuUtil.setProperty( + props, + this.propPrefix + "basicrom.address.begin", + this.btnBasicRom2000.isSelected() ? "2000" : "4000" ); EmuUtil.setProperty( props, this.propPrefix + "romdisk.address.begin", @@ -158,8 +190,9 @@ public void setEnabled( boolean state ) super.setEnabled( state ); this.fldPrgX.setEnabled( state ); this.fldRomDisk.setEnabled( state ); - this.fldBasic.setEnabled( state ); - updROMDiskAddrFieldsEnabled(); + this.fldBasicRom.setEnabled( state ); + updBasicRomAddrFieldsEnabled(); + updRomDiskAddrFieldsEnabled(); } @@ -168,9 +201,19 @@ public void updFields( Properties props ) { this.fldPrgX.updFields( props ); this.fldRomDisk.updFields( props ); - this.fldBasic.updFields( props ); + this.fldBasicRom.updFields( props ); String text = EmuUtil.getProperty( + props, + this.propPrefix + "basicrom.address.begin" ); + if( text.startsWith( "2000" ) ) { + this.btnBasicRom2000.setSelected( true ); + } else { + this.btnBasicRom4000.setSelected( true ); + } + updBasicRomAddrFieldsEnabled(); + + text = EmuUtil.getProperty( props, this.propPrefix + "romdisk.address.begin" ); if( text.startsWith( "8000" ) ) { @@ -178,11 +221,23 @@ public void updFields( Properties props ) } else { this.btnRomDiskC000.setSelected( true ); } - updROMDiskAddrFieldsEnabled(); + updRomDiskAddrFieldsEnabled(); + } + + + private void updBasicRomAddrFieldsEnabled() + { + boolean state = this.fldBasicRom.isEnabled(); + if( state && (this.fldBasicRom.getFile() == null) ) { + state = false; + } + this.labelBasicRomAddr.setEnabled( state ); + this.btnBasicRom2000.setEnabled( state ); + this.btnBasicRom4000.setEnabled( state ); } - private void updROMDiskAddrFieldsEnabled() + private void updRomDiskAddrFieldsEnabled() { boolean state = this.fldRomDisk.isEnabled(); if( state && (this.fldRomDisk.getFile() == null) ) { diff --git a/src/jkcemu/emusys/etc/BCS3SettingsFld.java b/src/jkcemu/emusys/etc/BCS3SettingsFld.java new file mode 100644 index 0000000..846e1f2 --- /dev/null +++ b/src/jkcemu/emusys/etc/BCS3SettingsFld.java @@ -0,0 +1,232 @@ +/* + * (c) 2015-2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Komponente fuer die BCS3-Einstellungen + */ + +package jkcemu.emusys.etc; + +import java.awt.*; +import java.lang.*; +import java.util.*; +import javax.swing.*; +import jkcemu.base.*; +import jkcemu.emusys.BCS3; + + +public class BCS3SettingsFld extends AbstractSettingsFld +{ + private static final int DEFAULT_AUTO_ACTION_WAIT_MILLIS = 500; + + private JTabbedPane tabbedPane; + private JPanel tabModel; + private AutoLoadSettingsFld tabAutoLoad; + private AutoInputSettingsFld tabAutoInput; + private JRadioButton btnSE24_27; + private JRadioButton btnSE31_29; + private JRadioButton btnSE31_40; + private JRadioButton btnSP33_29; + private JRadioButton btnRam1k; + private JRadioButton btnRam17k; + + + public BCS3SettingsFld( SettingsFrm settingsFrm, String propPrefix ) + { + super( settingsFrm, propPrefix ); + + setLayout( new BorderLayout() ); + + this.tabbedPane = new JTabbedPane( JTabbedPane.TOP ); + add( this.tabbedPane, BorderLayout.CENTER ); + + + // Tab Modell + this.tabModel = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Modell", this.tabModel ); + + GridBagConstraints gbcModel = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.NORTHWEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + ButtonGroup grpOS = new ButtonGroup(); + + this.btnSE24_27 = new JRadioButton( + "2 KByte BASIC-SE 2.4, 2,5 MHz, 27 Zeichen pro Zeile", + true ); + this.btnSE24_27.addActionListener( this ); + grpOS.add( this.btnSE24_27 ); + this.tabModel.add( this.btnSE24_27, gbcModel ); + + this.btnSE31_29 = new JRadioButton( + "4 KByte BASIC-SE 3.1, 2,5 MHz, 29 Zeichen pro Zeile", + false ); + this.btnSE31_29.addActionListener( this ); + grpOS.add( this.btnSE31_29 ); + gbcModel.insets.top = 0; + gbcModel.gridy++; + this.tabModel.add( this.btnSE31_29, gbcModel ); + + this.btnSE31_40 = new JRadioButton( + "4 KByte BASIC-SE 3.1, 3,5 MHz, 40 Zeichen pro Zeile", + false ); + this.btnSE31_40.addActionListener( this ); + grpOS.add( this.btnSE31_40 ); + gbcModel.insets.top = 0; + gbcModel.gridy++; + this.tabModel.add( this.btnSE31_40, gbcModel ); + + this.btnSP33_29 = new JRadioButton( + "4 KByte S/P-BASIC V3.3, 2,5 MHz, 29 Zeichen pro Zeile", + false ); + this.btnSP33_29.addActionListener( this ); + grpOS.add( this.btnSP33_29 ); + gbcModel.gridy++; + this.tabModel.add( this.btnSP33_29, gbcModel ); + + ButtonGroup grpRam = new ButtonGroup(); + + this.btnRam1k = new JRadioButton( "1 KByte RAM", true ); + this.btnRam1k.addActionListener( this ); + grpRam.add( this.btnRam1k ); + gbcModel.insets.top = 10; + gbcModel.gridy++; + this.tabModel.add( this.btnRam1k, gbcModel ); + + this.btnRam17k = new JRadioButton( + "17 KByte RAM (16 KByte RAM-Erweiterung)", + false ); + this.btnRam17k.addActionListener( this ); + grpRam.add( this.btnRam17k ); + gbcModel.insets.top = 0; + gbcModel.insets.bottom = 5; + gbcModel.gridy++; + this.tabModel.add( this.btnRam17k, gbcModel ); + + + // Tab AutoLoad + this.tabAutoLoad = new AutoLoadSettingsFld( + settingsFrm, + propPrefix, + DEFAULT_AUTO_ACTION_WAIT_MILLIS, + true ); + this.tabbedPane.addTab( "AutoLoad", this.tabAutoLoad ); + + + // Tab AutoInput + this.tabAutoInput = new AutoInputSettingsFld( + settingsFrm, + propPrefix, + BCS3.getDefaultSwapKeyCharCase(), + DEFAULT_AUTO_ACTION_WAIT_MILLIS ); + this.tabbedPane.addTab( "AutoInput", this.tabAutoInput ); + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public void applyInput( + Properties props, + boolean selected ) throws UserInputException + { + // Tab Modell + String osVersion = "2.4"; + String charsPerLine = "27"; + if( this.btnSE31_29.isSelected() ) { + osVersion = "3.1"; + charsPerLine = "29"; + } else if( this.btnSE31_40.isSelected() ) { + osVersion = "3.1"; + charsPerLine = "40"; + } else if( this.btnSP33_29.isSelected() ) { + osVersion = "3.3"; + charsPerLine = "29"; + } + EmuUtil.setProperty( + props, + this.propPrefix + "os.version", + osVersion ); + EmuUtil.setProperty( + props, + this.propPrefix + "chars_per_line", + charsPerLine ); + EmuUtil.setProperty( + props, + this.propPrefix + "ram.kbyte", + this.btnRam17k.isSelected() ? "17" : "1" ); + + // Tab AutoLoad + this.tabAutoLoad.applyInput( props, selected ); + + // Tab AutoInput + this.tabAutoInput.applyInput( props, selected ); + } + + + @Override + protected boolean doAction( EventObject e ) + { + boolean rv = false; + Object src = e.getSource(); + if( src != null ) { + rv = this.tabAutoLoad.doAction( e ); + if( !rv ) { + rv = this.tabAutoInput.doAction( e ); + } + if( !rv && (src instanceof AbstractButton) ) { + fireDataChanged(); + rv = true; + } + } + return rv; + } + + + @Override + public void lookAndFeelChanged() + { + this.tabAutoLoad.lookAndFeelChanged(); + this.tabAutoInput.lookAndFeelChanged(); + } + + + @Override + public void updFields( Properties props ) + { + String osVersion = EmuUtil.getProperty( + props, + this.propPrefix + "os.version" ); + if( osVersion.equals( "3.1" ) ) { + if( EmuUtil.getProperty( + props, + this.propPrefix + "chars_per_line" ).equals( "40" ) ) + { + this.btnSE31_40.setSelected( true ); + } else { + this.btnSE31_29.setSelected( true ); + } + } else if( osVersion.equals( "3.3" ) ) { + this.btnSP33_29.setSelected( true ); + } else { + this.btnSE24_27.setSelected( true ); + } + if( EmuUtil.getProperty( + props, + this.propPrefix + "ram.kbyte" ).equals( "17" ) ) + { + this.btnRam17k.setSelected( true ); + } else { + this.btnRam1k.setSelected( true ); + } + + this.tabAutoLoad.updFields( props ); + this.tabAutoInput.updFields( props ); + } +} diff --git a/src/jkcemu/emusys/etc/NANOSSettingsFld.java b/src/jkcemu/emusys/etc/NANOSSettingsFld.java new file mode 100644 index 0000000..f968fe0 --- /dev/null +++ b/src/jkcemu/emusys/etc/NANOSSettingsFld.java @@ -0,0 +1,528 @@ +/* + * (c) 2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Komponente fuer die NANOS-Einstellungen + */ + +package jkcemu.emusys.etc; + +import java.awt.*; +import java.io.File; +import java.lang.*; +import java.util.*; +import javax.swing.*; +import jkcemu.base.*; +import jkcemu.disk.GIDESettingsFld; +import jkcemu.emusys.NANOS; + + +public class NANOSSettingsFld extends AbstractSettingsFld +{ + private static final int DEFAULT_AUTO_ACTION_WAIT_MILLIS = 2000; + + private JTabbedPane tabbedPane; + private JPanel tabGraphic; + private JRadioButton btnGraphic64x32; + private JRadioButton btnGraphic80x24; + private JRadioButton btnGraphic80x25; + private JRadioButton btnGraphicPoppe; + private JPanel tabKeyboard; + private JRadioButton btnKbPio00Ahs; + private JRadioButton btnKbPio00Abit7; + private JCheckBox btnKbSwapCase; + private JPanel tabRom; + private JRadioButton btnRomNanos; + private JRadioButton btnRomEpos; + private JRadioButton btnRomFile; + private FileNameFld fldRomFile; + private JButton btnRomFileSelect; + private JButton btnRomFileRemove; + private GIDESettingsFld tabGIDE; + private JPanel tabExt; + private JCheckBox btnKCNet; + private JCheckBox btnVDIP; + private ROMFileSettingsFld fldAltFont8x6; + private ROMFileSettingsFld fldAltFont8x8; + private AutoInputSettingsFld tabAutoInput; + + + public NANOSSettingsFld( SettingsFrm settingsFrm, String propPrefix ) + { + super( settingsFrm, propPrefix ); + + setLayout( new BorderLayout() ); + + this.tabbedPane = new JTabbedPane( JTabbedPane.TOP ); + add( this.tabbedPane, BorderLayout.CENTER ); + + + // Tab Grafik + this.tabGraphic = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Grafik", this.tabGraphic ); + + GridBagConstraints gbcGraphic = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + this.tabGraphic.add( new JLabel( "Grafikkarte:" ), gbcGraphic ); + + ButtonGroup grpGraphic = new ButtonGroup(); + + this.btnGraphic64x32 = new JRadioButton( + "Bildschirmsteuerung Video 2 mit 64x32 Zeichen" ); + grpGraphic.add( this.btnGraphic64x32 ); + gbcGraphic.insets.top = 0; + gbcGraphic.insets.left = 50; + gbcGraphic.gridy++; + this.tabGraphic.add( this.btnGraphic64x32, gbcGraphic ); + + this.btnGraphic80x24 = new JRadioButton( + "Bildschirmsteuerung Video 3 mit 80x24 Zeichen" ); + grpGraphic.add( this.btnGraphic80x24 ); + gbcGraphic.gridy++; + this.tabGraphic.add( this.btnGraphic80x24, gbcGraphic ); + + this.btnGraphic80x25 = new JRadioButton( + "Bildschirmsteuerung Video 3 mit 80x25 Zeichen" ); + grpGraphic.add( this.btnGraphic80x25 ); + gbcGraphic.gridy++; + this.tabGraphic.add( this.btnGraphic80x25, gbcGraphic ); + + this.btnGraphicPoppe = new JRadioButton( + "Farbgrafikkarte mit 64x32 und 80x24 Zeichen umschaltbar" ); + grpGraphic.add( this.btnGraphicPoppe ); + gbcGraphic.gridy++; + this.tabGraphic.add( this.btnGraphicPoppe, gbcGraphic ); + + gbcGraphic.fill = GridBagConstraints.HORIZONTAL; + gbcGraphic.weightx = 1.0; + gbcGraphic.insets.top = 10; + gbcGraphic.insets.left = 5; + gbcGraphic.gridy++; + this.tabGraphic.add( new JSeparator(), gbcGraphic ); + + this.fldAltFont8x8 = new ROMFileSettingsFld( + settingsFrm, + propPrefix + "font.8x8.", + "Alternativer Zeichensatz (8x8):" ); + gbcGraphic.insets.top = 10; + gbcGraphic.gridy++; + this.tabGraphic.add( this.fldAltFont8x8, gbcGraphic ); + + this.fldAltFont8x6 = new ROMFileSettingsFld( + settingsFrm, + propPrefix + "font.8x6.", + "Alternativer 8x6-Zeichensatz" + + " f\u00FCr 64x32-Modus der Farbgrafikkarte:" ); + gbcGraphic.insets.bottom = 5; + gbcGraphic.gridy++; + this.tabGraphic.add( this.fldAltFont8x6, gbcGraphic ); + + + // Tab Tastatur + this.tabKeyboard = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Tastatur", this.tabKeyboard ); + + GridBagConstraints gbcKeyboard = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + this.tabKeyboard.add( + new JLabel( "Tastatur ist angeschlossen an:" ), + gbcKeyboard ); + + ButtonGroup grpKeyboard = new ButtonGroup(); + + this.btnKbPio00Ahs = new JRadioButton( + "ZRE-PIO Port A mit Ready/Strobe-Handshake (NANOS 2.2)", + true ); + grpKeyboard.add( this.btnKbPio00Ahs ); + gbcKeyboard.insets.top = 0; + gbcKeyboard.insets.left = 50; + gbcKeyboard.gridy++; + this.tabKeyboard.add( this.btnKbPio00Ahs, gbcKeyboard ); + + this.btnKbPio00Abit7 = new JRadioButton( + "ZRE-PIO Port A mit Strobe an Bit 7 (EPOS 2.0)" ); + grpKeyboard.add( this.btnKbPio00Abit7 ); + gbcKeyboard.gridy++; + this.tabKeyboard.add( this.btnKbPio00Abit7, gbcKeyboard ); + + this.btnKbSwapCase = new JCheckBox( + "Gro\u00DF-/Kleinschreibung umkehren" ); + gbcKeyboard.insets.top = 10; + gbcKeyboard.insets.left = 5; + gbcKeyboard.insets.bottom = 5; + gbcKeyboard.gridy++; + this.tabKeyboard.add( this.btnKbSwapCase, gbcKeyboard ); + + + // Tab ROM + this.tabRom = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "ROM", this.tabRom ); + + GridBagConstraints gbcRom = new GridBagConstraints( + 0, 0, + GridBagConstraints.REMAINDER, 1, + 1.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.HORIZONTAL, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + ButtonGroup grpRom = new ButtonGroup(); + + this.btnRomNanos = new JRadioButton( + "Boot-ROM f\u00FCr NANOS 2.2", + true ); + grpRom.add( this.btnRomNanos ); + this.tabRom.add( this.btnRomNanos, gbcRom ); + + this.btnRomEpos = new JRadioButton( "Boot-ROM f\u00FCr EPOS 2.0" ); + grpRom.add( this.btnRomEpos ); + gbcRom.insets.top = 0; + gbcRom.gridy++; + this.tabRom.add( this.btnRomEpos, gbcRom ); + + this.btnRomFile = new JRadioButton( "ROM-Datei:" ); + grpRom.add( this.btnRomFile ); + gbcRom.gridy++; + this.tabRom.add( this.btnRomFile, gbcRom ); + + this.fldRomFile = new FileNameFld(); + gbcRom.insets.bottom = 5; + gbcRom.insets.left = 50; + gbcRom.gridwidth = 1; + gbcRom.gridy++; + this.tabRom.add( this.fldRomFile, gbcRom ); + + this.btnRomFileSelect = createImageButton( + "/images/file/open.png", + "ROM-Datei ausw\u00E4hlen" ); + gbcRom.fill = GridBagConstraints.NONE; + gbcRom.weightx = 0.0; + gbcRom.insets.left = 0; + gbcRom.gridx++; + this.tabRom.add( this.btnRomFileSelect, gbcRom ); + + this.btnRomFileRemove = createImageButton( + "/images/file/delete.png", + "ROM-Datei entfernen" ); + gbcRom.gridx++; + this.tabRom.add( this.btnRomFileRemove, gbcRom ); + + + // Tab GIDE + this.tabGIDE = new GIDESettingsFld( settingsFrm, propPrefix ); + this.tabbedPane.addTab( "GIDE", this.tabGIDE ); + + + // Tab Erweiterungen + this.tabExt = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Erweiterungen", this.tabExt ); + + GridBagConstraints gbcExt = new GridBagConstraints( + 0, 0, + GridBagConstraints.REMAINDER, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + this.btnKCNet = new JCheckBox( "KCNet-kompatible Netzwerkkarte" ); + gbcExt.gridy++; + this.tabExt.add( this.btnKCNet, gbcExt ); + + this.btnVDIP = new JCheckBox( "USB-Anschluss (Vinculum VDIP Modul)" ); + gbcExt.insets.top = 0; + gbcExt.insets.bottom = 5; + gbcExt.gridy++; + this.tabExt.add( this.btnVDIP, gbcExt ); + + + // Tab AutoInput + this.tabAutoInput = new AutoInputSettingsFld( + settingsFrm, + propPrefix, + NANOS.getDefaultSwapKeyCharCase(), + DEFAULT_AUTO_ACTION_WAIT_MILLIS ); + this.tabbedPane.addTab( "AutoInput", this.tabAutoInput ); + + + // Listener + this.btnGraphic64x32.addActionListener( this ); + this.btnGraphic80x24.addActionListener( this ); + this.btnGraphic80x25.addActionListener( this ); + this.btnGraphicPoppe.addActionListener( this ); + this.btnKbPio00Ahs.addActionListener( this ); + this.btnKbPio00Abit7.addActionListener( this ); + this.btnKbSwapCase.addActionListener( this ); + this.btnRomNanos.addActionListener( this ); + this.btnRomEpos.addActionListener( this ); + this.btnRomFile.addActionListener( this ); + this.btnKCNet.addActionListener( this ); + this.btnVDIP.addActionListener( this ); + + + // Sonstiges + updGraphicDependFields(); + updRomDependFields(); + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public void applyInput( + Properties props, + boolean selected ) throws + UserCancelException, + UserInputException + { + Component tab = null; + String text = null; + try { + + // Tab Grafik + tab = this.tabGraphic; + text = NANOS.PROP_GRAPHIC_VALUE_80X25; + if( this.btnGraphic64x32.isSelected() ) { + text = NANOS.PROP_GRAPHIC_VALUE_64X32; + } else if( this.btnGraphic80x24.isSelected() ) { + text = NANOS.PROP_GRAPHIC_VALUE_80X24; + } else if( this.btnGraphicPoppe.isSelected() ) { + text = NANOS.PROP_GRAPHIC_VALUE_POPPE; + } + EmuUtil.setProperty( + props, + this.propPrefix + NANOS.PROP_GRAPHIC_KEY, + text ); + this.fldAltFont8x8.applyInput( props, selected ); + this.fldAltFont8x6.applyInput( props, selected ); + + // Tab Tastatur + tab = this.tabKeyboard; + text = NANOS.PROP_KEYBOARD_VALUE_PIO00A_HS; + if( this.btnKbPio00Abit7.isSelected() ) { + text = NANOS.PROP_KEYBOARD_VALUE_PIO00A_BIT7; + } + EmuUtil.setProperty( + props, + this.propPrefix + NANOS.PROP_KEYBOARD_KEY, + text ); + EmuUtil.setProperty( + props, + this.propPrefix + NANOS.PROP_KEYBOARD_SWAP_CASE, + this.btnKbSwapCase.isSelected() ); + + // Tab ROM + tab = this.tabRom; + text = "nanos"; + if( this.btnRomEpos.isSelected() ) { + text = "epos"; + } else if( this.btnRomFile.isSelected() ) { + File file = this.fldRomFile.getFile(); + if( file == null ) { + if( selected ) { + this.tabbedPane.setSelectedComponent( this.tabRom ); + throw new UserInputException( + "ROM: Bitte w\u00E4hlen Sie eine ROM-Datei aus\n" + + "oder stellen Sie einen anderen Boot-ROM ein." ); + } + } + if( file != null ) { + text = "file:" + file.getPath(); + } + } + EmuUtil.setProperty( props, this.propPrefix + "rom", text ); + + // Tab GIDE + tab = this.tabGIDE; + this.tabGIDE.applyInput( props, selected ); + + // Tab Erweiterungen + tab = this.tabExt; + EmuUtil.setProperty( + props, + this.propPrefix + "kcnet.enabled", + this.btnKCNet.isSelected() ); + EmuUtil.setProperty( + props, + this.propPrefix + "vdip.enabled", + this.btnVDIP.isSelected() ); + + // Tab AutoInput + tab = this.tabAutoInput; + this.tabAutoInput.applyInput( props, selected ); + } + catch( UserInputException ex ) { + if( tab != null ) { + this.tabbedPane.setSelectedComponent( tab ); + } + throw ex; + } + } + + + @Override + protected boolean doAction( EventObject e ) + { + this.settingsFrm.setWaitCursor( true ); + + boolean rv = false; + Object src = e.getSource(); + if( src != null ) { + rv = this.tabGIDE.doAction( e ); + if( !rv ) { + rv = this.tabAutoInput.doAction( e ); + } + if( !rv ) { + if( (src == this.btnGraphic64x32) + || (src == this.btnGraphic80x24) + || (src == this.btnGraphic80x25) + || (src == this.btnGraphicPoppe) ) + { + updGraphicDependFields(); + fireDataChanged(); + rv = true; + } + else if( (src == this.btnRomNanos) + || (src == this.btnRomEpos) + || (src == this.btnRomFile) ) + { + updRomDependFields(); + fireDataChanged(); + rv = true; + } + else if( src == this.btnRomFileSelect ) { + File file = selectFile( + "ROM-Datei ausw\u00E4hlen", + "rom", + this.fldRomFile.getFile(), + EmuUtil.getROMFileFilter() ); + if( file != null ) { + if( this.fldRomFile.setFile( file ) ) { + this.btnRomFileRemove.setEnabled( true ); + fireDataChanged(); + } + } + rv = true; + } + else if( src == this.btnRomFileRemove ) { + if( this.fldRomFile.setFile( null ) ) { + this.btnRomFileRemove.setEnabled( false ); + fireDataChanged(); + } + rv = true; + } + else if( src instanceof AbstractButton ) { + fireDataChanged(); + rv = true; + } + } + } + this.settingsFrm.setWaitCursor( false ); + return rv; + } + + + @Override + public void lookAndFeelChanged() + { + this.fldAltFont8x8.lookAndFeelChanged(); + this.fldAltFont8x6.lookAndFeelChanged(); + this.tabGIDE.lookAndFeelChanged(); + this.tabAutoInput.lookAndFeelChanged(); + } + + + @Override + public void updFields( Properties props ) + { + switch( EmuUtil.getProperty( + props, + this.propPrefix + NANOS.PROP_GRAPHIC_KEY ) ) + { + case NANOS.PROP_GRAPHIC_VALUE_64X32: + this.btnGraphic64x32.setSelected( true ); + break; + case NANOS.PROP_GRAPHIC_VALUE_80X24: + this.btnGraphic80x24.setSelected( true ); + break; + case NANOS.PROP_GRAPHIC_VALUE_POPPE: + this.btnGraphicPoppe.setSelected( true ); + break; + default: + this.btnGraphic80x25.setSelected( true ); + } + this.fldAltFont8x8.updFields( props ); + this.fldAltFont8x6.updFields( props ); + if( EmuUtil.getProperty( + props, + this.propPrefix + NANOS.PROP_KEYBOARD_KEY ).equals( + NANOS.PROP_KEYBOARD_VALUE_PIO00A_BIT7 ) ) + { + this.btnKbPio00Abit7.setSelected( true ); + } else { + this.btnKbPio00Ahs.setSelected( true ); + } + this.btnKbSwapCase.setSelected( + EmuUtil.getBooleanProperty( + props, + this.propPrefix + NANOS.PROP_KEYBOARD_SWAP_CASE, + false ) ); + String valueText = EmuUtil.getProperty( props, this.propPrefix + "rom" ); + String upperText = valueText.toUpperCase(); + if( (upperText.length() > 5) && upperText.startsWith( "FILE:" ) ) { + this.btnRomFile.setSelected( true ); + this.fldRomFile.setFileName( valueText.substring( 5 ) ); + } else if( upperText.startsWith( "EPOS" ) ) { + this.btnRomEpos.setSelected( true ); + } else { + this.btnRomNanos.setSelected( true ); + } + this.btnKCNet.setSelected( + EmuUtil.getBooleanProperty( + props, + this.propPrefix + "kcnet.enabled", + false ) ); + this.btnVDIP.setSelected( + EmuUtil.getBooleanProperty( + props, + this.propPrefix + "vdip.enabled", + false ) ); + this.tabGIDE.updFields( props ); + this.tabAutoInput.updFields( props ); + updGraphicDependFields(); + updRomDependFields(); + } + + + private void updGraphicDependFields() + { + this.fldAltFont8x6.setEnabled( this.btnGraphicPoppe.isSelected() ); + } + + + private void updRomDependFields() + { + boolean stateFile = this.btnRomFile.isSelected(); + this.fldRomFile.setEnabled( stateFile ); + this.btnRomFileSelect.setEnabled( stateFile ); + this.btnRomFileRemove.setEnabled( + stateFile && (this.fldRomFile.getFile() != null) ); + } +} diff --git a/src/jkcemu/emusys/etc/PCMSettingsFld.java b/src/jkcemu/emusys/etc/PCMSettingsFld.java index 6adef19..4ded7ca 100644 --- a/src/jkcemu/emusys/etc/PCMSettingsFld.java +++ b/src/jkcemu/emusys/etc/PCMSettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2011 Jens Mueller + * (c) 2011-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -9,30 +9,44 @@ package jkcemu.emusys.etc; import java.awt.*; -import java.io.File; import java.lang.*; import java.util.*; import javax.swing.*; import jkcemu.base.*; +import jkcemu.emusys.PCM; public class PCMSettingsFld extends AbstractSettingsFld { - private JCheckBox btnAutoLoadBDOS; - private JRadioButton btnRF64x16; - private JRadioButton btnFDC64x16; - private JRadioButton btnFDC80x24; - private ROMFileSettingsFld fldAltROM; - private ROMFileSettingsFld fldAltFont; + private static final int DEFAULT_AUTO_ACTION_WAIT_MILLIS = 500; + + private JTabbedPane tabbedPane; + private JPanel tabModel; + private AutoLoadSettingsFld tabAutoLoad; + private AutoInputSettingsFld tabAutoInput; + private JCheckBox btnAutoLoadBDOS; + private JRadioButton btnRF64x16; + private JRadioButton btnFDC64x16; + private JRadioButton btnFDC80x24; + private ROMFileSettingsFld fldAltROM; + private ROMFileSettingsFld fldAltFont; public PCMSettingsFld( SettingsFrm settingsFrm, String propPrefix ) { super( settingsFrm, propPrefix ); - setLayout( new GridBagLayout() ); + setLayout( new BorderLayout() ); + + this.tabbedPane = new JTabbedPane( JTabbedPane.TOP ); + add( this.tabbedPane, BorderLayout.CENTER ); + + + // Tab Modell + this.tabModel = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Modell", this.tabModel ); - GridBagConstraints gbc = new GridBagConstraints( + GridBagConstraints gbcModel = new GridBagConstraints( 0, 0, GridBagConstraints.REMAINDER, 1, 0.0, 0.0, @@ -48,54 +62,75 @@ public PCMSettingsFld( SettingsFrm settingsFrm, String propPrefix ) true ); grpSys.add( this.btnRF64x16 ); this.btnRF64x16.addActionListener( this ); - add( this.btnRF64x16, gbc ); + this.tabModel.add( this.btnRF64x16, gbcModel ); this.btnAutoLoadBDOS = new JCheckBox( "Bei RESET automatisch BDOS laden", true ); this.btnAutoLoadBDOS.addActionListener( this ); - gbc.insets.top = 0; - gbc.insets.left = 50; - gbc.gridy++; - add( this.btnAutoLoadBDOS, gbc ); + gbcModel.insets.top = 0; + gbcModel.insets.left = 50; + gbcModel.gridy++; + this.tabModel.add( this.btnAutoLoadBDOS, gbcModel ); this.btnFDC64x16 = new JRadioButton( "Floppy-Disk-System, 64x16 Zeichen", false ); grpSys.add( this.btnFDC64x16 ); this.btnFDC64x16.addActionListener( this ); - gbc.insets.left = 5; - gbc.gridy++; - add( this.btnFDC64x16, gbc ); + gbcModel.insets.left = 5; + gbcModel.gridy++; + this.tabModel.add( this.btnFDC64x16, gbcModel ); this.btnFDC80x24 = new JRadioButton( "Floppy-Disk-System, 80x24 Zeichen", false ); grpSys.add( this.btnFDC80x24 ); this.btnFDC80x24.addActionListener( this ); - gbc.insets.bottom = 5; - gbc.gridy++; - add( this.btnFDC80x24, gbc ); + gbcModel.insets.bottom = 5; + gbcModel.gridy++; + this.tabModel.add( this.btnFDC80x24, gbcModel ); - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weightx = 1.0; - gbc.insets.top = 5; - gbc.gridy++; - add( new JSeparator(), gbc ); + gbcModel.fill = GridBagConstraints.HORIZONTAL; + gbcModel.weightx = 1.0; + gbcModel.insets.top = 10; + gbcModel.insets.bottom = 10; + gbcModel.gridy++; + this.tabModel.add( new JSeparator(), gbcModel ); this.fldAltROM = new ROMFileSettingsFld( settingsFrm, propPrefix + "rom.", "Alternativer ROM-Inhalt (Grundbetriebssystem):" ); - gbc.gridy++; - add( this.fldAltROM, gbc ); + gbcModel.insets.top = 5; + gbcModel.insets.bottom = 5; + gbcModel.gridy++; + this.tabModel.add( this.fldAltROM, gbcModel ); this.fldAltFont = new ROMFileSettingsFld( settingsFrm, propPrefix + "font.", "Alternativer Zeichensatz:" ); - gbc.gridy++; - add( this.fldAltFont, gbc ); + gbcModel.gridy++; + this.tabModel.add( this.fldAltFont, gbcModel ); + + + // Tab AutoLoad + this.tabAutoLoad = new AutoLoadSettingsFld( + settingsFrm, + propPrefix, + DEFAULT_AUTO_ACTION_WAIT_MILLIS, + true ); + this.tabbedPane.addTab( "AutoLoad", this.tabAutoLoad ); + + + // Tab AutoInput + this.tabAutoInput = new AutoInputSettingsFld( + settingsFrm, + propPrefix, + PCM.getDefaultSwapKeyCharCase(), + DEFAULT_AUTO_ACTION_WAIT_MILLIS ); + this.tabbedPane.addTab( "AutoInput", this.tabAutoInput ); } @@ -106,22 +141,31 @@ public void applyInput( Properties props, boolean selected ) throws UserInputException { + // Tab Modell boolean fdc64x16 = this.btnFDC64x16.isSelected(); boolean fdc80x24 = this.btnFDC80x24.isSelected(); EmuUtil.setProperty( props, - this.propPrefix + "floppydisk.enabled", + this.propPrefix + PCM.PROP_FDC_ENABLDED, fdc64x16 || fdc80x24 ); EmuUtil.setProperty( props, - this.propPrefix + "graphic", - fdc80x24 ? "80x24" : "64x16" ); + this.propPrefix + PCM.PROP_GRAPHIC_KEY, + fdc80x24 ? + PCM.PROP_GRAPHIC_VALUE_80X24 + : PCM.PROP_GRAPHIC_VALUE_64X32 ); EmuUtil.setProperty( props, - this.propPrefix + "auto_load_bdos", + this.propPrefix + PCM.PROP_AUTO_LOAD_BDOS, this.btnAutoLoadBDOS.isSelected() ); this.fldAltROM.applyInput( props, selected ); this.fldAltFont.applyInput( props, selected ); + + // Tab AutoLoad + this.tabAutoLoad.applyInput( props, selected ); + + // Tab AutoInput + this.tabAutoInput.applyInput( props, selected ); } @@ -131,31 +175,46 @@ protected boolean doAction( EventObject e ) boolean rv = false; Object src = e.getSource(); if( src != null ) { - if( src instanceof JRadioButton ) { - updAutoLoadBDOSFieldEnabled(); - fireDataChanged(); - rv = true; + rv = this.tabAutoLoad.doAction( e ); + if( !rv ) { + rv = this.tabAutoInput.doAction( e ); } - else if( src instanceof AbstractButton ) { - fireDataChanged(); - rv = true; + if( !rv ) { + if( src instanceof JRadioButton ) { + updAutoLoadBDOSFieldEnabled(); + fireDataChanged(); + rv = true; + } + else if( src instanceof AbstractButton ) { + fireDataChanged(); + rv = true; + } } } return rv; } + @Override + public void lookAndFeelChanged() + { + this.tabAutoLoad.lookAndFeelChanged(); + this.tabAutoInput.lookAndFeelChanged(); + } + + @Override public void updFields( Properties props ) { if( EmuUtil.getBooleanProperty( - props, - this.propPrefix + "floppydisk.enabled", - false ) ) + props, + this.propPrefix + PCM.PROP_FDC_ENABLDED, + false ) ) { if( EmuUtil.getProperty( - props, - this.propPrefix + "graphic" ).equals( "80x24" ) ) + props, + this.propPrefix + PCM.PROP_GRAPHIC_KEY ).equals( + PCM.PROP_GRAPHIC_VALUE_80X24 ) ) { this.btnFDC80x24.setSelected( true ); } else { @@ -165,13 +224,16 @@ public void updFields( Properties props ) this.btnRF64x16.setSelected( true ); } this.btnAutoLoadBDOS.setSelected( - EmuUtil.getBooleanProperty( + EmuUtil.getBooleanProperty( props, - this.propPrefix + "auto_load_bdos", + this.propPrefix + PCM.PROP_AUTO_LOAD_BDOS, true ) ); this.fldAltROM.updFields( props ); this.fldAltFont.updFields( props ); updAutoLoadBDOSFieldEnabled(); + + this.tabAutoLoad.updFields( props ); + this.tabAutoInput.updFields( props ); } @@ -182,4 +244,3 @@ private void updAutoLoadBDOSFieldEnabled() this.btnAutoLoadBDOS.setEnabled( this.btnRF64x16.isSelected() ); } } - diff --git a/src/jkcemu/emusys/huebler/AbstractHueblerMC.java b/src/jkcemu/emusys/huebler/AbstractHueblerMC.java index 4a1d205..d43c9a2 100644 --- a/src/jkcemu/emusys/huebler/AbstractHueblerMC.java +++ b/src/jkcemu/emusys/huebler/AbstractHueblerMC.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -8,6 +8,7 @@ package jkcemu.emusys.huebler; +import java.awt.EventQueue; import java.awt.event.KeyEvent; import java.lang.*; import java.text.*; @@ -26,13 +27,18 @@ public abstract class AbstractHueblerMC protected Z80CTC ctc; protected Z80PIO pio; - private int pasteStateNum; + private int pasteStateNum; + private volatile boolean pasteIgnoreMsgActive; - public AbstractHueblerMC( EmuThread emuThread, Properties props ) + public AbstractHueblerMC( + EmuThread emuThread, + Properties props, + String propPrefix ) { - super( emuThread, props ); - this.pcListenerAdded = false; + super( emuThread, props, propPrefix ); + this.pcListenerAdded = false; + this.pasteIgnoreMsgActive = false; } @@ -56,8 +62,8 @@ protected synchronized void checkAddPCListener( protected void createIOSystem() { Z80CPU cpu = this.emuThread.getZ80CPU(); - this.ctc = new Z80CTC( "CTC (IO-Adressen 14h-17h)" ); - this.pio = new Z80PIO( "PIO (IO-Adressen 0Ch-0Fh)" ); + this.ctc = new Z80CTC( "CTC (E/A-Adressen 14h-17h)" ); + this.pio = new Z80PIO( "PIO (E/A-Adressen 0Ch-0Fh)" ); cpu.setInterruptSources( this.ctc, this.pio ); cpu.addTStatesListener( this.ctc ); } @@ -139,7 +145,7 @@ public boolean keyTyped( char ch ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { port &= 0xFF; @@ -152,22 +158,36 @@ public int readIOByte( int port ) } --this.pasteStateNum; } else { - CharacterIterator iter = this.pasteIter; - if( iter != null ) { - char ch = iter.current(); - iter.next(); - if( ch == CharacterIterator.DONE ) { - cancelPastingText(); - } else { - ch = TextUtil.toISO646DE( ch ); - if( ch == '\n' ) { - ch = '\r'; - } - if( (ch == '\r') - || ((ch >= '\u0000') && (ch < '\u007F')) ) - { - this.keyChar = ch; - this.pasteStateNum = 8; + if( !this.pasteIgnoreMsgActive ) { + CharacterIterator iter = this.pasteIter; + if( iter != null ) { + char ch = iter.current(); + iter.next(); + if( ch == CharacterIterator.DONE ) { + cancelPastingText(); + } else { + ch = TextUtil.toISO646DE( ch ); + if( ch == '\n' ) { + ch = '\r'; + } + if( (ch == '\r') + || ((ch >= '\u0000') && (ch < '\u007F')) ) + { + this.keyChar = ch; + this.pasteStateNum = 8; + } else { + final char ch1 = ch; + this.pasteIgnoreMsgActive = true; + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + showPasteIgnoreMsg( ch1 ); + } + } ); + } } } } @@ -187,7 +207,7 @@ public int readIOByte( int port ) break; case 0x0C: - rv = this.pio.readPortA(); + rv = this.pio.readDataA(); break; case 0x0D: @@ -195,7 +215,7 @@ public int readIOByte( int port ) break; case 0x0E: - rv = this.pio.readPortB(); + rv = this.pio.readDataB(); break; case 0x0F: @@ -206,7 +226,7 @@ public int readIOByte( int port ) case 0x15: case 0x16: case 0x17: - rv = this.ctc.read( port & 0x03 ); + rv = this.ctc.read( port & 0x03, tStates ); break; } return rv; @@ -253,11 +273,11 @@ public boolean supportsPrinter() @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { switch( port & 0xFF ) { case 0x0C: - this.pio.writePortA( value ); + this.pio.writeDataA( value ); break; case 0x0D: @@ -265,7 +285,7 @@ public void writeIOByte( int port, int value ) break; case 0x0E: - this.pio.writePortB( value ); + this.pio.writeDataB( value ); break; case 0x0F: @@ -276,9 +296,28 @@ public void writeIOByte( int port, int value ) case 0x15: case 0x16: case 0x17: - this.ctc.write( port & 0x03, value ); + this.ctc.write( port & 0x03, value, tStates ); break; } } -} + + /* --- private Methoden --- */ + + private void showPasteIgnoreMsg( char ch ) + { + if( BasicDlg.showOptionDlg( + this.emuThread.getScreenFrm(), + String.format( + "Das Zeichen mit dem Code %02Xh kann nicht" + + " eingef\u00FCgt werden.", + (int) ch ), + "Einf\u00FCgen", + "Weiter", + "Abbrechen" ) != 0 ) + { + cancelPastingText(); + } + this.pasteIgnoreMsgActive = false; + } +} diff --git a/src/jkcemu/emusys/huebler/HueblerGraphicsMCSettingsFld.java b/src/jkcemu/emusys/huebler/HueblerGraphicsMCSettingsFld.java index 9718013..5cb73ef 100644 --- a/src/jkcemu/emusys/huebler/HueblerGraphicsMCSettingsFld.java +++ b/src/jkcemu/emusys/huebler/HueblerGraphicsMCSettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2013 Jens Mueller + * (c) 2013-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -13,14 +13,22 @@ import java.util.*; import javax.swing.*; import jkcemu.base.*; +import jkcemu.emusys.HueblerGraphicsMC; public class HueblerGraphicsMCSettingsFld extends AbstractSettingsFld { - private JCheckBox btnKCNet; - private JCheckBox btnVDIP; - private JCheckBox btnBasic; - private JCheckBox btnCatchPrintCalls; + private static final int DEFAULT_AUTO_ACTION_WAIT_MILLIS = 500; + + private JTabbedPane tabbedPane; + private JPanel tabExt; + private JPanel tabEtc; + private AutoLoadSettingsFld tabAutoLoad; + private AutoInputSettingsFld tabAutoInput; + private JCheckBox btnKCNet; + private JCheckBox btnVDIP; + private JCheckBox btnBasic; + private JCheckBox btnCatchPrintCalls; public HueblerGraphicsMCSettingsFld( @@ -29,9 +37,17 @@ public HueblerGraphicsMCSettingsFld( { super( settingsFrm, propPrefix ); - setLayout( new GridBagLayout() ); + setLayout( new BorderLayout() ); + + this.tabbedPane = new JTabbedPane( JTabbedPane.TOP ); + add( this.tabbedPane, BorderLayout.CENTER ); + + + // Tab Erweiterungen + this.tabExt = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Erweiterungen", this.tabExt ); - GridBagConstraints gbc = new GridBagConstraints( + GridBagConstraints gbcExt = new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, @@ -40,41 +56,63 @@ public HueblerGraphicsMCSettingsFld( new Insets( 5, 5, 0, 5 ), 0, 0 ); - add( new JLabel( "Erweiterungen:" ), gbc ); - this.btnKCNet = new JCheckBox( "KCNet-kompatible Netzwerkkarte", false ); - gbc.insets.top = 0; - gbc.insets.left = 50; - gbc.gridy++; - add( this.btnKCNet, gbc ); + this.tabExt.add( this.btnKCNet, gbcExt ); this.btnVDIP = new JCheckBox( "USB-Anschluss (Vinculum VDIP Modul)", false ); - gbc.gridy++; - add( this.btnVDIP, gbc ); + gbcExt.insets.top = 0; + gbcExt.insets.bottom = 5; + gbcExt.gridy++; + this.tabExt.add( this.btnVDIP, gbcExt ); + + + // Tab Sonstiges + this.tabEtc = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Sonstiges", this.tabEtc ); - gbc.insets.top = 20; - gbc.insets.left = 5; - gbc.gridy++; - add( new JLabel( "Sonstiges:" ), gbc ); + GridBagConstraints gbcEtc = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); this.btnBasic = new JCheckBox( "BASIC-Interpreter im ROM enthalten", true ); - gbc.insets.top = 0; - gbc.insets.left = 50; - gbc.gridy++; - add( this.btnBasic, gbc ); + this.tabEtc.add( this.btnBasic, gbcEtc ); this.btnCatchPrintCalls = new JCheckBox( "Betriebssystemaufrufe f\u00FCr Druckerausgaben abfangen", true ); - gbc.insets.bottom = 5; - gbc.gridy++; - add( this.btnCatchPrintCalls, gbc ); + gbcEtc.insets.top = 0; + gbcEtc.insets.bottom = 5; + gbcEtc.gridy++; + this.tabEtc.add( this.btnCatchPrintCalls, gbcEtc ); + + + // Tab AutoLoad + this.tabAutoLoad = new AutoLoadSettingsFld( + settingsFrm, + propPrefix, + DEFAULT_AUTO_ACTION_WAIT_MILLIS, + true ); + this.tabbedPane.addTab( "AutoLoad", this.tabAutoLoad ); + + + // Tab AutoInput + this.tabAutoInput = new AutoInputSettingsFld( + settingsFrm, + propPrefix, + HueblerGraphicsMC.getDefaultSwapKeyCharCase(), + DEFAULT_AUTO_ACTION_WAIT_MILLIS ); + this.tabbedPane.addTab( "AutoInput", this.tabAutoInput ); // Listener @@ -111,6 +149,9 @@ public void applyInput( props, this.propPrefix + "vdip.enabled", this.btnVDIP.isSelected() ); + + this.tabAutoLoad.applyInput( props, selected ); + this.tabAutoInput.applyInput( props, selected ); } @@ -120,7 +161,11 @@ protected boolean doAction( EventObject e ) boolean rv = false; Object src = e.getSource(); if( src != null ) { - if( src instanceof AbstractButton ) { + rv = this.tabAutoLoad.doAction( e ); + if( !rv ) { + rv = this.tabAutoInput.doAction( e ); + } + if( !rv && (src instanceof AbstractButton) ) { rv = true; fireDataChanged(); } @@ -129,6 +174,14 @@ protected boolean doAction( EventObject e ) } + @Override + public void lookAndFeelChanged() + { + this.tabAutoLoad.lookAndFeelChanged(); + this.tabAutoInput.lookAndFeelChanged(); + } + + @Override public void updFields( Properties props ) { @@ -155,5 +208,8 @@ public void updFields( Properties props ) props, this.propPrefix + "vdip.enabled", false ) ); + + this.tabAutoLoad.updFields( props ); + this.tabAutoInput.updFields( props ); } } diff --git a/src/jkcemu/emusys/kc85/AbstractKC85Module.java b/src/jkcemu/emusys/kc85/AbstractKC85Module.java index b978cff..bb780ba 100644 --- a/src/jkcemu/emusys/kc85/AbstractKC85Module.java +++ b/src/jkcemu/emusys/kc85/AbstractKC85Module.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -55,11 +55,15 @@ public void die() public boolean equalsModule( + String slot, String moduleName, String typeByteText, String fileName ) { - boolean rv = TextUtil.equals( getModuleName(), moduleName ); + boolean rv = String.valueOf( this.slot ).equals( slot ); + if( rv ) { + rv = TextUtil.equals( getModuleName(), moduleName ); + } if( rv ) { if( typeByteText != null ) { if( !typeByteText.isEmpty() ) { @@ -137,7 +141,7 @@ public boolean isEnabled() * Rueckgabewert: * -1: Modul bedient diesen Lesevorgang nicht. */ - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { return -1; } @@ -175,7 +179,7 @@ public boolean supportsPrinter() * Rueckgabewert: * false: Modul bedient diesen Schreibvorgang nicht. */ - public boolean writeIOByte( int port, int value ) + public boolean writeIOByte( int port, int value, int tStates ) { return false; } diff --git a/src/jkcemu/emusys/kc85/AbstractKC85UserPROMModule.java b/src/jkcemu/emusys/kc85/AbstractKC85UserPROMModule.java new file mode 100644 index 0000000..5ea337a --- /dev/null +++ b/src/jkcemu/emusys/kc85/AbstractKC85UserPROMModule.java @@ -0,0 +1,108 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Basisklasse fuer die Emulation von KC85-User-PROM-Modulen + */ + +package jkcemu.emusys.kc85; + +import java.awt.Component; +import java.lang.*; +import jkcemu.base.EmuUtil; + + +public abstract class AbstractKC85UserPROMModule extends AbstractKC85Module +{ + protected int begAddr; + protected int segMask; + + private int typeByte; + private int segSize; + private int fullSize; + private String moduleName; + private String fileName; + private byte[] rom; + + + public AbstractKC85UserPROMModule( + int slot, + int typeByte, + String moduleName, + int segCount, + int segSize, + Component owner, + String fileName ) + { + super( slot ); + this.typeByte = typeByte; + this.segSize = segSize; + this.fullSize = segCount * segSize; + this.moduleName = moduleName; + this.fileName = fileName; + this.begAddr = 0; + this.segMask = 0; + reload( owner ); + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public int getBegAddr() + { + return this.begAddr; + } + + + @Override + public String getFileName() + { + return this.fileName; + } + + + @Override + public String getModuleName() + { + return this.moduleName; + } + + + @Override + public int getTypeByte() + { + return this.typeByte; + } + + + @Override + public int readMemByte( int addr ) + { + int rv = -1; + if( this.enabled + && (addr >= this.begAddr) + && (addr < (this.begAddr + this.segSize)) + && (this.rom != null) ) + { + int idx = (addr - this.begAddr) | this.segMask; + if( idx < this.rom.length ) { + rv = (int) this.rom[ idx ] & 0xFF; + } + } + return rv; + } + + + @Override + public void reload( Component owner ) + { + this.rom = EmuUtil.readFile( + owner, + this.fileName, + true, + this.fullSize, + this.moduleName + " ROM-Datei" ); + } +} diff --git a/src/jkcemu/emusys/kc85/D004.java b/src/jkcemu/emusys/kc85/D004.java index e418613..5688cbf 100644 --- a/src/jkcemu/emusys/kc85/D004.java +++ b/src/jkcemu/emusys/kc85/D004.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -10,6 +10,7 @@ import java.lang.*; import java.util.Properties; +import jkcemu.Main; import jkcemu.base.*; import jkcemu.disk.FloppyDiskDrive; import z80emu.*; @@ -164,7 +165,7 @@ public int readMemByte( int addr ) @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = -1; if( this.connected ) { @@ -189,7 +190,7 @@ public void setStatus( int value ) @Override - public boolean writeIOByte( int port, int value ) + public boolean writeIOByte( int port, int value, int tStates ) { boolean rv = false; int portL = (port & 0xFF); @@ -242,7 +243,7 @@ else if( portL == 0xF4 ) { private synchronized void enableCPU() { if( this.thread == null ) { - Thread t = new Thread( this.procSys, "D004" ); + Thread t = new Thread( Main.getThreadGroup(), this.procSys, "D004" ); this.thread = t; t.start(); } diff --git a/src/jkcemu/emusys/kc85/D004ProcSys.java b/src/jkcemu/emusys/kc85/D004ProcSys.java index ca79f20..ebd7738 100644 --- a/src/jkcemu/emusys/kc85/D004ProcSys.java +++ b/src/jkcemu/emusys/kc85/D004ProcSys.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -19,7 +19,6 @@ public class D004ProcSys implements FDC8272.DriveSelector, Runnable, - Z80CTCListener, Z80IOSystem, Z80Memory, Z80TStatesListener @@ -66,7 +65,9 @@ public D004ProcSys( this.fdc = new FDC8272( this, 4 ); this.cpu = new Z80CPU( this, this ); this.ctc = new Z80CTC( "CTC (FCh-FFh)" ); - this.ctc.addCTCListener( this ); + this.ctc.setTimerConnection( 0, 1 ); + this.ctc.setTimerConnection( 1, 2 ); + this.ctc.setTimerConnection( 2, 3 ); this.cpu.setMaxSpeedKHz( 4000 ); this.cpu.setInterruptSources( this.ctc ); this.cpu.addMaxSpeedListener( this.fdc ); @@ -105,7 +106,6 @@ public void die() } this.cpu.removeTStatesListener( this ); this.cpu.removeMaxSpeedListener( this.fdc ); - this.ctc.removeCTCListener( this ); this.fdc.die(); } @@ -228,23 +228,10 @@ public void run() } - /* --- Z80CTCListener --- */ - - @Override - public void z80CTCUpdate( Z80CTC ctc, int timerNum ) - { - if( ctc == this.ctc ) { - if( (timerNum >= 0) && (timerNum <= 2) ) { - ctc.externalUpdate( timerNum + 1, 1 ); - } - } - } - - /* --- Z80IOSystem --- */ @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = 0xFF; if( ((port & 0xF0) == 0) && (this.gide != null) ) { @@ -297,7 +284,7 @@ public int readIOByte( int port ) case 0xFD: case 0xFE: case 0xFF: - rv = this.ctc.read( port & 0x03 ); + rv = this.ctc.read( port & 0x03, tStates ); break; } } @@ -306,7 +293,7 @@ public int readIOByte( int port ) @Override - public void writeIOByte( int port, int value ) + public void writeIOByte( int port, int value, int tStates ) { if( ((port & 0xF0) == 0) && (this.gide != null) ) { this.gide.write( port, value ); @@ -346,7 +333,7 @@ public void writeIOByte( int port, int value ) case 0xFD: case 0xFE: case 0xFF: - this.ctc.write( port & 0x03, value ); + this.ctc.write( port & 0x03, value, tStates ); break; } } diff --git a/src/jkcemu/emusys/kc85/KC85CharRecognizer.java b/src/jkcemu/emusys/kc85/KC85CharRecognizer.java index 92c6677..7d64faa 100644 --- a/src/jkcemu/emusys/kc85/KC85CharRecognizer.java +++ b/src/jkcemu/emusys/kc85/KC85CharRecognizer.java @@ -1,5 +1,5 @@ /* - * (c) 2013 Jens Mueller + * (c) 2013-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -943,7 +943,7 @@ public void setPixelByte( int col, int y, byte value ) private Map createCharMap( int[][] font ) { - Map map = new HashMap(); + Map map = new HashMap<>(); CRC32 crc = new CRC32(); for( int i = 0; i < font.length; i++ ) { crc.reset(); @@ -1040,7 +1040,7 @@ private Character getChar4xH( /* * Wenn Zeichen nicht erkannt wurde, * dann auf anderen Y-Positionen versuchen, - * wenn dies so geweunscht ist. + * wenn dies so gewuenscht ist. */ if( rv == null ) { for( int yDiff = -2; yDiff < 3; yDiff++ ) { diff --git a/src/jkcemu/emusys/kc85/KC85JoystickModule.java b/src/jkcemu/emusys/kc85/KC85JoystickModule.java index 07d040f..d1af797 100644 --- a/src/jkcemu/emusys/kc85/KC85JoystickModule.java +++ b/src/jkcemu/emusys/kc85/KC85JoystickModule.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2013 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -85,7 +85,7 @@ public void setJoystickAction( int joyNum, int actionMask ) @Override public void appendInterruptStatusHTMLTo( StringBuilder buf ) { - buf.append( "

    PIO (IO-Adressen 90-93)

    \n" ); + buf.append( "

    PIO (E/A-Adressen 90-93)

    \n" ); this.pio.appendInterruptStatusHTMLTo( buf ); } @@ -137,18 +137,18 @@ public int getTypeByte() @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = -1; port &= 0xFF; if( (port >= 0x90) && (port < 0x98) ) { switch( port & 0xFF ) { case 0x90: - rv = this.pio.readPortA(); + rv = this.pio.readDataA(); break; case 0x91: - rv = this.pio.readPortB(); + rv = this.pio.readDataB(); break; case 0x92: @@ -182,18 +182,18 @@ public String toString() @Override - public boolean writeIOByte( int port, int value ) + public boolean writeIOByte( int port, int value, int tStates ) { boolean rv = false; port &= 0xFF; if( (port >= 0x90) && (port < 0x98) ) { switch( port & 0xFF ) { case 0x90: - this.pio.writePortA( value ); + this.pio.writeDataA( value ); break; case 0x91: - this.pio.writePortB( value ); + this.pio.writeDataB( value ); break; case 0x92: diff --git a/src/jkcemu/emusys/kc85/KC85KeyboardFld.java b/src/jkcemu/emusys/kc85/KC85KeyboardFld.java index cf82537..399a6f5 100644 --- a/src/jkcemu/emusys/kc85/KC85KeyboardFld.java +++ b/src/jkcemu/emusys/kc85/KC85KeyboardFld.java @@ -134,7 +134,7 @@ public KC85KeyboardFld( KC85 kc85 ) addKey( "I", 64 ); addKey( "O", 54 ); addKey( "P", 38 ); - addKey( "^", 22 ); + addKey( "^", "\u00AC", 22 ); this.curX += KEY_COL_W; this.curX = xUp - (KEY_COL_W / 2); @@ -179,8 +179,8 @@ public KC85KeyboardFld( KC85 kc85 ) addKey( "J", 66 ); addKey( "K", 72 ); addKey( "L", 88 ); - addKey( "+", 104 ); - addKey( "_", 102 ); + addKey( "+", ";", 104 ); + addKey( "_", "|", 102 ); this.curX = xUp; addKey( this.curX, diff --git a/src/jkcemu/emusys/kc85/KC85ModuleTableModel.java b/src/jkcemu/emusys/kc85/KC85ModuleTableModel.java index f57e6e1..3fcb014 100644 --- a/src/jkcemu/emusys/kc85/KC85ModuleTableModel.java +++ b/src/jkcemu/emusys/kc85/KC85ModuleTableModel.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2010 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -24,7 +24,7 @@ public class KC85ModuleTableModel extends javax.swing.table.AbstractTableModel public KC85ModuleTableModel() { - this.rows = new ArrayList(); + this.rows = new ArrayList<>(); } diff --git a/src/jkcemu/emusys/kc85/KC85ROM8KModule.java b/src/jkcemu/emusys/kc85/KC85ROM8KModule.java index e0476c2..52b8446 100644 --- a/src/jkcemu/emusys/kc85/KC85ROM8KModule.java +++ b/src/jkcemu/emusys/kc85/KC85ROM8KModule.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -15,7 +15,7 @@ public class KC85ROM8KModule extends AbstractKC85Module { - private static Map map = new HashMap(); + private static Map map = new HashMap<>(); private String moduleName; private int begAddr; diff --git a/src/jkcemu/emusys/kc85/KC85SettingsFld.java b/src/jkcemu/emusys/kc85/KC85SettingsFld.java index fdc3208..95cf00c 100644 --- a/src/jkcemu/emusys/kc85/KC85SettingsFld.java +++ b/src/jkcemu/emusys/kc85/KC85SettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2013 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -26,31 +26,42 @@ public class KC85SettingsFld extends AbstractSettingsFld implements ListSelectionListener, MouseListener { + private static final int DEFAULT_AUTO_ACTION_WAIT_MILLIS_2 = 2600; + private static final int DEFAULT_AUTO_ACTION_WAIT_MILLIS_4 = 1200; + private static final String moduleAddPrefix = "kc85.module.add."; + private static final int MOD_IDX_TYPE = 0; + private static final int MOD_IDX_NAME = 1; + private static final int MOD_IDX_DESC = 2; + private static final String[][] modules = { - { "M003", "V.24 mit angeschlossenem Drucker" }, - { "M006", "BASIC" }, - { "M008", "Joystick" }, - { "M011", "64 KByte RAM" }, - { "M012", "TEXOR" }, - { "M021", "Joystick / Centronics mit angeschlossenem Drucker" }, - { "M022", "16 KByte Expander RAM" }, - { "M025", "8 KByte User PROM" }, - { "M026", "Forth" }, - { "M027", "Development" }, - { "M028", "16 KByte User PROM" }, - { "M032", "256 KByte Segmented RAM" }, - { "M033", "TypeStar" }, - { "M034", "512 KByte Segmented RAM" }, - { "M035", "1 MByte Segmented RAM" }, - { "M035x4", "4 MByte Segmented RAM" }, - { "M036", "128 KByte Segmented RAM" }, - { "M040", "8/16 KByte User PROM" }, - { "M052", "USB/Netzwerk" }, - { "M120", "8 KByte CMOS RAM" }, - { "M122", "16 KByte CMOS RAM" }, - { "M124", "32 KByte CMOS RAM" } }; + { "ETC", "M003", "V.24 mit angeschlossenem Drucker" }, + { "ROM", "M006", "BASIC" }, + { "ETC", "M008", "Joystick" }, + { "RAM", "M011", "64 KByte RAM" }, + { "ROM", "M012", "TEXOR" }, + { "ETC", "M021", "Joystick/Centronics mit angeschlossenem Drucker" }, + { "RAM", "M022", "16 KByte Expander RAM" }, + { "ROM", "M025", "8 KByte User PROM" }, + { "ROM", "M026", "Forth" }, + { "ROM", "M027", "Development" }, + { "ROM", "M028", "16 KByte User PROM" }, + { "RAM", "M032", "256 KByte Segmented RAM" }, + { "ROM", "M033", "TypeStar" }, + { "RAM", "M034", "512 KByte Segmented RAM" }, + { "RAM", "M035", "1 MByte Segmented RAM" }, + { "RAM", "M035x4", "4 MByte Segmented RAM" }, + { "RAM", "M036", "128 KByte Segmented RAM" }, + { "ROM", "M040", "8/16 KByte User PROM" }, + { "ROM", "M045", "4x8 KByte User PROM" }, + { "ROM", "M046", "8x8 KByte User PROM" }, + { "ROM", "M047", "16x8 KByte User PROM" }, + { "ROM", "M048", "16x16 KByte User PROM" }, + { "ETC", "M052", "USB/Netzwerk" }, + { "RAM", "M120", "8 KByte CMOS RAM" }, + { "RAM", "M122", "16 KByte CMOS RAM" }, + { "RAM", "M124", "32 KByte CMOS RAM" } }; private static final String[] altRomTitles2 = { "CAOS-ROM E000-E7FF", @@ -74,7 +85,7 @@ public class KC85SettingsFld private static final String[] altRomTitles4 = { "BASIC-ROM C000-DFFF", - "CAOS-ROM C000-CFFF", + "CAOS-ROM C000-CFFF (oder C000-DFFF)", "CAOS-ROM E000-FFFF", "M052-ROM (USB/Netzwerk)" }; @@ -107,7 +118,10 @@ public class KC85SettingsFld private JPanel tabModule; private JPanel tabD004; private JPanel tabROM; + private JPanel tabSound; private JPanel tabEtc; + private AutoLoadSettingsFld tabAutoLoad; + private AutoInputSettingsFld tabAutoInput; private JPopupMenu popupModule; private JButton btnModuleAdd; private JButton btnModuleEdit; @@ -115,7 +129,7 @@ public class KC85SettingsFld private JButton btnModuleUp; private JButton btnModuleDown; private JCheckBox btnD004Enabled; - private JComboBox comboD004Rom; + private JComboBox comboD004Rom; private JLabel labelD004Rom; private JLabel labelD004Speed; private JRadioButton btnD004Speed4MHz; @@ -125,12 +139,13 @@ public class KC85SettingsFld private JButton btnD004RomFileSelect; private JButton btnD004RomFileRemove; private GIDESettingsFld tabGIDE; + private JRadioButton btnSoundMono; + private JRadioButton btnSoundStereo; private FileNameFld[] altRomTextFlds; private JButton[] altRomSelectBtns; private JButton[] altRomRemoveBtns; - private JCheckBox btnCtrlKeysDirect; + private JCheckBox btnKeysDirectToBuf; private JCheckBox btnPasteFast; - private JCheckBox btnUmlautsTo2Codes; private JCheckBox btnVideoTiming; @@ -238,16 +253,36 @@ public KC85SettingsFld( } this.popupModule = new JPopupMenu(); + + JMenu mnuModRAM = new JMenu( "RAM-Module" ); + this.popupModule.add( mnuModRAM ); + + JMenu mnuModROM = new JMenu( "ROM-Module" ); + this.popupModule.add( mnuModROM ); + + JMenu mnuModEtc = new JMenu( "Sonstige Module" ); + this.popupModule.add( mnuModEtc ); + for( int i = 0; i < modules.length; i++ ) { - String modName = modules[ i ][ 0 ]; + String modName = modules[ i ][ MOD_IDX_NAME ]; if( (kcTypeNum < 3) || !modName.equals( "M006" ) ) { JMenuItem modItem = new JMenuItem( String.format( - "%s - %s", - modName, - modules[ i ][ 1 ] ) ); + "%s - %s", + modName, + modules[ i ][ MOD_IDX_DESC ] ) ); modItem.setActionCommand( moduleAddPrefix + modName ); modItem.addActionListener( this ); - this.popupModule.add( modItem ); + switch( modules[ i ][ MOD_IDX_TYPE ] ) { + case "RAM": + mnuModRAM.add( modItem ); + break; + case "ROM": + mnuModROM.add( modItem ); + break; + case "ETC": + mnuModEtc.add( modItem ); + break; + } } } @@ -278,7 +313,7 @@ public KC85SettingsFld( gbcDisk.gridy++; this.tabD004.add( this.labelD004Rom, gbcDisk ); - this.comboD004Rom = new JComboBox(); + this.comboD004Rom = new JComboBox<>(); this.comboD004Rom.setEditable( false ); this.comboD004Rom.addItem( "Version 2.0" ); this.comboD004Rom.addItem( "Version 3.3" ); @@ -406,6 +441,45 @@ public KC85SettingsFld( } + // Tab Tongeneratoren + this.tabSound = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Tongeneratoren", this.tabSound ); + + GridBagConstraints gbcSound = new GridBagConstraints( + 0, 0, + GridBagConstraints.REMAINDER, 1, + 1.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.HORIZONTAL, + new Insets( 5, 5, 5, 5 ), + 0, 0 ); + + this.tabSound.add( new JLabel( "Ausgabe der Tongeneratoren:" ), gbcSound ); + + ButtonGroup grpSoundChannels = new ButtonGroup(); + + this.btnSoundMono = new JRadioButton( + "Mono (beide Tongeneratoren gemischt" + + " mit Lautst\u00E4rkesteuerung)", + true ); + this.btnSoundMono.addActionListener( this ); + grpSoundChannels.add( this.btnSoundMono ); + gbcSound.insets.left = 50; + gbcSound.insets.top = 0; + gbcSound.insets.bottom = 0; + gbcSound.gridy++; + this.tabSound.add( this.btnSoundMono, gbcSound ); + + this.btnSoundStereo = new JRadioButton( + "Stereo (Tongeneratoren links und rechts getrennt" + + " ohne Lautst\u00E4rkesteuerung)" ); + this.btnSoundStereo.addActionListener( this ); + grpSoundChannels.add( this.btnSoundStereo ); + gbcSound.insets.bottom = 5; + gbcSound.gridy++; + this.tabSound.add( this.btnSoundStereo, gbcSound ); + + // Tab Sonstiges this.tabEtc = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Sonstiges", this.tabEtc ); @@ -419,25 +493,17 @@ public KC85SettingsFld( new Insets( 5, 5, 0, 5 ), 0, 0 ); - this.btnUmlautsTo2Codes = new JCheckBox( - "Tasten f\u00FCr \u00C4\u00D6\u00DC\u00E4\u00F6\u00FC[]{}\\~" - + " entsprechend MicroDOS-Mode der D005 behandeln", - false ); - this.btnUmlautsTo2Codes.addActionListener( this ); - this.tabEtc.add( this.btnUmlautsTo2Codes, gbcEtc ); - - this.btnCtrlKeysDirect = new JCheckBox( - "Ctrl-A...Z, ESC und TAB direkt in den" - + " Tastaturpuffer schreiben", - true ); - this.btnCtrlKeysDirect.addActionListener( this ); - gbcEtc.insets.top = 0; - gbcEtc.gridy++; - this.tabEtc.add( this.btnCtrlKeysDirect, gbcEtc ); + this.btnKeysDirectToBuf = new JCheckBox( + "Schnellere Tastatureingaben durch direktes" + + " Schreiben in den Tastaturpuffer", + false ); + this.btnKeysDirectToBuf.addActionListener( this ); + this.tabEtc.add( this.btnKeysDirectToBuf, gbcEtc ); this.btnPasteFast = new JCheckBox( "Einf\u00FCgen von Text direkt in den Tastaturpuffer", true ); + gbcEtc.insets.top = 0; gbcEtc.gridy++; this.btnPasteFast.addActionListener( this ); this.tabEtc.add( this.btnPasteFast, gbcEtc ); @@ -479,6 +545,28 @@ public KC85SettingsFld( this.tabEtc.add( this.btnVideoTiming, gbcEtc ); + // Tab AutoLoad + this.tabAutoLoad = new AutoLoadSettingsFld( + settingsFrm, + propPrefix, + this.kcTypeNum < 4 ? + DEFAULT_AUTO_ACTION_WAIT_MILLIS_2 + : DEFAULT_AUTO_ACTION_WAIT_MILLIS_4, + true ); + this.tabbedPane.addTab( "AutoLoad", this.tabAutoLoad ); + + + // Tab AutoInput + this.tabAutoInput = new AutoInputSettingsFld( + settingsFrm, + propPrefix, + KC85.getDefaultSwapKeyCharCase(), + this.kcTypeNum < 4 ? + DEFAULT_AUTO_ACTION_WAIT_MILLIS_2 + : DEFAULT_AUTO_ACTION_WAIT_MILLIS_4 ); + this.tabbedPane.addTab( "AutoInput", this.tabAutoInput ); + + // Drag&Drop ermoeglichen (new DropTarget( this.fldD004RomFile, this )).setActive( true ); for( int i = 0; i < this.altRomTextFlds.length; i++ ) { @@ -511,7 +599,11 @@ public void valueChanged( ListSelectionEvent e ) if( moduleName != null ) { if( moduleName.equals( "M025" ) || moduleName.equals( "M028" ) - || moduleName.equals( "M040" ) ) + || moduleName.equals( "M040" ) + || moduleName.equals( "M045" ) + || moduleName.equals( "M046" ) + || moduleName.equals( "M047" ) + || moduleName.equals( "M048" ) ) { stateEdit = true; } @@ -666,20 +758,31 @@ public void applyInput( file != null ? file.getPath() : "" ); } + // Tab Tongeneratoren + EmuUtil.setProperty( + props, + this.propPrefix + "sound.stereo", + this.btnSoundStereo.isSelected() ); + // Tab Sonstiges tab = this.tabEtc; props.setProperty( - this.propPrefix + "control_keys.direct", - Boolean.toString( this.btnCtrlKeysDirect.isSelected() ) ); + this.propPrefix + "keys.direct_to_buffer", + Boolean.toString( this.btnKeysDirectToBuf.isSelected() ) ); props.setProperty( this.propPrefix + "paste.fast", Boolean.toString( this.btnPasteFast.isSelected() ) ); - props.setProperty( - this.propPrefix + "umlauts_to_2_keycodes", - Boolean.toString( this.btnUmlautsTo2Codes.isSelected() ) ); props.setProperty( this.propPrefix + "emulate_video_timing", Boolean.toString( this.btnVideoTiming.isSelected() ) ); + + // Tab AutoLoad + tab = this.tabAutoLoad; + this.tabAutoLoad.applyInput( props, selected ); + + // Tab AutoInput + tab = this.tabAutoInput; + this.tabAutoInput.applyInput( props, selected ); } catch( UserInputException ex ) { if( tab != null ) { @@ -732,6 +835,10 @@ protected boolean doAction( EventObject e ) } else if( src == this.btnD004RomFileRemove ) { rv = true; doD004RomFileRemove(); + } else if( src == this.btnKeysDirectToBuf ) { + rv = true; + updPasteFastEnabled(); + fireDataChanged(); } else if( e instanceof ActionEvent ) { String cmd = ((ActionEvent) e).getActionCommand(); if( cmd != null ) { @@ -767,14 +874,20 @@ protected boolean doAction( EventObject e ) } } } + if( !rv ) { + rv = this.tabGIDE.doAction( e ); + } + if( !rv ) { + rv = this.tabAutoLoad.doAction( e ); + } + if( !rv ) { + rv = this.tabAutoInput.doAction( e ); + } if( !rv && (src instanceof AbstractButton) ) { rv = true; fireDataChanged(); } } - if( !rv ) { - rv = this.tabGIDE.doAction( e ); - } this.settingsFrm.setWaitCursor( false ); return rv; } @@ -831,6 +944,8 @@ protected boolean fileDropped( Component c, File file ) public void lookAndFeelChanged() { this.tabGIDE.lookAndFeelChanged(); + this.tabAutoLoad.lookAndFeelChanged(); + this.tabAutoInput.lookAndFeelChanged(); if( this.popupModule != null ) { SwingUtilities.updateComponentTreeUI( this.popupModule ); } @@ -913,27 +1028,40 @@ public void updFields( Properties props ) this.altRomRemoveBtns[ i ].setEnabled( file != null ); } + // Tab Tongenerator + if( EmuUtil.getBooleanProperty( + props, + this.propPrefix + "sound.stereo", + false ) ) + { + this.btnSoundStereo.setSelected( true ); + } else { + this.btnSoundMono.setSelected( true ); + } + // Tab Sonstiges - this.btnCtrlKeysDirect.setSelected( + this.btnKeysDirectToBuf.setSelected( EmuUtil.getBooleanProperty( props, - this.propPrefix + "control_keys.direct", + this.propPrefix + "keys.direct_to_buffer", false ) ); this.btnPasteFast.setSelected( EmuUtil.getBooleanProperty( props, this.propPrefix + "paste.fast", true ) ); - this.btnUmlautsTo2Codes.setSelected( - EmuUtil.getBooleanProperty( - props, - this.propPrefix + "umlauts_to_2_keycodes", - false ) ); this.btnVideoTiming.setSelected( EmuUtil.getBooleanProperty( props, this.propPrefix + "emulate_video_timing", KC85.getDefaultEmulateVideoTiming() ) ); + updPasteFastEnabled(); + + // Tab AutoLoad + this.tabAutoLoad.updFields( props ); + + // Tab AutoInput + this.tabAutoInput.updFields( props ); } @@ -985,7 +1113,11 @@ private void doModuleEdit() if( moduleName != null ) { if( moduleName.equals( "M025" ) || moduleName.equals( "M028" ) - || moduleName.equals( "M040" ) ) + || moduleName.equals( "M040" ) + || moduleName.equals( "M045" ) + || moduleName.equals( "M046" ) + || moduleName.equals( "M047" ) + || moduleName.equals( "M048" ) ) { KC85UserPROMSettingsDlg dlg = new KC85UserPROMSettingsDlg( this.settingsFrm, @@ -1091,7 +1223,7 @@ private boolean addModule( if( (moduleName != null) && (nRows < 60) ) { if( !moduleName.isEmpty() ) { for( int i = 0; i < modules.length; i++ ) { - String s = modules[ i ][ 0 ]; + String s = modules[ i ][ MOD_IDX_NAME ]; if( moduleName.equals( s ) ) { if( slotText == null ) { slotText = String.format( "%02X", (nRows + 2) * 4 ); @@ -1123,7 +1255,11 @@ private void addModule( String moduleName ) String fileName = null; if( moduleName.equals( "M025" ) || moduleName.equals( "M028" ) - || moduleName.equals( "M040" ) ) + || moduleName.equals( "M040" ) + || moduleName.equals( "M045" ) + || moduleName.equals( "M046" ) + || moduleName.equals( "M047" ) + || moduleName.equals( "M048" ) ) { boolean ok = false; KC85UserPROMSettingsDlg dlg = new KC85UserPROMSettingsDlg( @@ -1190,11 +1326,9 @@ else if( moduleName.equals( "M040" ) ) { } } if( moduleDesc == null ) { - for( String[] cells : modules ) { - if( cells.length > 1 ) { - if( cells[ 0 ].equals( moduleName ) ) { - moduleDesc = cells[ 1 ]; - } + for( int i = 0; i < modules.length; i++ ) { + if( modules[ i ][ MOD_IDX_NAME ].equals( moduleName ) ) { + moduleDesc = modules[ i ][ MOD_IDX_DESC ]; } } } @@ -1216,7 +1350,7 @@ private File selectRomFile( File oldFile, String title ) title, oldFile != null ? oldFile - : Main.getLastPathFile( "rom" ), + : Main.getLastDirFile( "rom" ), EmuUtil.getROMFileFilter() ); if( file != null ) { String msg = null; @@ -1266,4 +1400,14 @@ private void updD004FieldsEnabled() } this.btnD004RomFileRemove.setEnabled( state ); } + + + private void updPasteFastEnabled() + { + if( this.btnPasteFast != null ) { + this.btnPasteFast.setEnabled( + !this.btnKeysDirectToBuf.isSelected() ); + } + } } + diff --git a/src/jkcemu/emusys/kc85/KC85UserPROMSettingsDlg.java b/src/jkcemu/emusys/kc85/KC85UserPROMSettingsDlg.java index 7c6a9e7..11277c6 100644 --- a/src/jkcemu/emusys/kc85/KC85UserPROMSettingsDlg.java +++ b/src/jkcemu/emusys/kc85/KC85UserPROMSettingsDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2012 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -26,11 +26,17 @@ public class KC85UserPROMSettingsDlg { "M025", "F7: 8K User PROM/EPROM", "FB: 8K ROM" }, { "M028", "F8: 16K User PROM/EPROM", "FC: 16K ROM" }, { "M040", "01: Autostart", "F7: 8K User PROM/EPROM", - "F8: 16K User PROM/EPROM" } }; + "F8: 16K User PROM/EPROM" }, + { "M045", "70" }, + { "M046", "71" }, + { "M047", "72" }, + { "M048", "73" } }; private Frame owner; private String approvedFileName; private String approvedTypeByteText; + private String moduleName; + private String[] moduleTableRow; private JRadioButton[] typeByteBtns; private FileNameFld fileNameFld; private JButton btnSelect; @@ -45,7 +51,19 @@ public KC85UserPROMSettingsDlg( String fileName ) { super( owner, moduleName ); - this.owner = owner; + this.owner = owner; + this.approvedFileName = null; + this.approvedTypeByteText = null; + this.moduleName = moduleName; + this.moduleTableRow = null; + for( String[] moduleTableRow : this.moduleTable ) { + if( moduleTableRow.length > 1 ) { + if( moduleTableRow[ 0 ].equals( moduleName ) ) { + this.moduleTableRow = moduleTableRow; + } + } + } + // Fensterinhalt setLayout( new GridBagLayout() ); @@ -61,40 +79,39 @@ public KC85UserPROMSettingsDlg( // Strukturbyte this.typeByteBtns = null; - for( String[] moduleRow : this.moduleTable ) { - if( moduleRow.length > 1 ) { - if( moduleRow[ 0 ].equals( moduleName ) ) { - add( new JLabel( "Strukturbyte:" ), gbc ); - - ButtonGroup grpTypeByte = new ButtonGroup(); - boolean selected = false; - - gbc.insets.bottom = 0; - this.typeByteBtns = new JRadioButton[ moduleRow.length - 1 ]; - for( int i = 0; i < this.typeByteBtns.length; i++ ) { - String text = moduleRow[ i + 1 ]; - JRadioButton btn = new JRadioButton( text ); - grpTypeByte.add( btn ); - if( typeByteText != null ) { - if( text.startsWith( typeByteText ) ) { - btn.setSelected( true ); - selected = true; - } - } - gbc.insets.left = 50; - if( i > 0 ) { - gbc.insets.top = 0; + if( this.moduleTableRow != null ) { + if( this.moduleTableRow.length > 2 ) { + add( new JLabel( "Strukturbyte:" ), gbc ); + + ButtonGroup grpTypeByte = new ButtonGroup(); + boolean selected = false; + + gbc.insets.bottom = 0; + this.typeByteBtns = new JRadioButton[ + this.moduleTableRow.length - 1 ]; + for( int i = 0; i < this.typeByteBtns.length; i++ ) { + String text = this.moduleTableRow[ i + 1 ]; + JRadioButton btn = new JRadioButton( text ); + grpTypeByte.add( btn ); + if( typeByteText != null ) { + if( text.startsWith( typeByteText ) ) { + btn.setSelected( true ); + selected = true; } - if( i == (this.typeByteBtns.length - 1) ) { - gbc.insets.bottom = 5; - } - gbc.gridy++; - add( btn, gbc ); - this.typeByteBtns[ i ] = btn; } - if( !selected ) { - this.typeByteBtns[ 0 ].setSelected( true ); + gbc.insets.left = 50; + if( i > 0 ) { + gbc.insets.top = 0; + } + if( i == (this.typeByteBtns.length - 1) ) { + gbc.insets.bottom = 5; } + gbc.gridy++; + add( btn, gbc ); + this.typeByteBtns[ i ] = btn; + } + if( !selected ) { + this.typeByteBtns[ 0 ].setSelected( true ); } } } @@ -271,6 +288,12 @@ private void doApprove() break; } } + } else { + if( this.moduleTableRow != null ) { + if( this.moduleTableRow.length > 1 ) { + this.approvedTypeByteText = this.moduleTableRow[ 1 ]; + } + } } this.approvedFileName = file.getPath(); doClose(); @@ -286,7 +309,7 @@ private void doFileSelect() { File file = this.fileNameFld.getFile(); if( file == null ) { - file = Main.getLastPathFile( "rom" ); + file = Main.getLastDirFile( "rom" ); } file = EmuUtil.showFileOpenDlg( this.owner, @@ -300,4 +323,3 @@ private void doFileSelect() } } } - diff --git a/src/jkcemu/emusys/kc85/KCAudioDataStream.java b/src/jkcemu/emusys/kc85/KCAudioDataStream.java index 0895b12..4f973b0 100644 --- a/src/jkcemu/emusys/kc85/KCAudioDataStream.java +++ b/src/jkcemu/emusys/kc85/KCAudioDataStream.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -36,7 +36,7 @@ public KCAudioDataStream( int len ) throws IOException { super( 8000F, buf, offs, len ); - if( tapFmt && !skipString( FileInfo.KCTAP_HEADER ) ) { + if( tapFmt && !skipSourceString( FileInfo.KCTAP_HEADER ) ) { throw new IOException( "KC-TAP-Header erwartet" ); } @@ -50,7 +50,7 @@ public KCAudioDataStream( * Beginn einer neuen Teildatei innerhalb einer Multi-TAP-Datei? * Wenn ja, dann Header uerberspringen und langer Vorton */ - if( skipString( FileInfo.KCTAP_HEADER ) ) { + if( skipSourceString( FileInfo.KCTAP_HEADER ) ) { nHalf = 16000; } } @@ -58,28 +58,29 @@ public KCAudioDataStream( nHalf = 16000; // beim 1. Block immer langer Vortan } for( int i = 0; i < nHalf; i++ ) { - addSamples( 4 ); + addPhaseChangeSamples( 4 ); } firstBlk = false; // Trennschwingung - addSamples( 8 ); - addSamples( 8 ); + addPhaseChangeSamples( 8 ); + addPhaseChangeSamples( 8 ); - // Blocknummer und Datenbytes - int cks = 0; - int nBytes = 128; + // Blocknummer + int b = 0; if( tapFmt ) { - nBytes = 129; + b = readSourceByte(); } else { - addByteSamples( getAvailableSourceBytes() > 128 ? blkNum++ : 0xFF ); + b = (getAvailableSourceBytes() > 128 ? blkNum++ : 0xFF); } - for( int i = 0; i < 129; i++ ) { - int b = readSourceByte(); + addByteSamples( b ); + + // Datenbytes + int cks = 0; + for( int i = 0; i < 128; i++ ) { + b = readSourceByte(); addByteSamples( b ); - if( i > 0 ) { - cks = (cks + b) & 0xFF; - } + cks = (cks + b) & 0xFF; } // Pruefsumme @@ -88,7 +89,7 @@ public KCAudioDataStream( // abschliessender Phasenwechsel if( getFrameLength() > 0 ) { - addSamples( 40 ); + addPhaseChangeSamples( 40 ); } } @@ -108,15 +109,15 @@ private void addByteSamples( int value ) { for( int i = 0; i < 8; i++ ) { if( (value & 0x01) != 0 ) { - addSamples( 4 ); - addSamples( 4 ); + addPhaseChangeSamples( 4 ); + addPhaseChangeSamples( 4 ); } else { - addSamples( 2 ); - addSamples( 2 ); + addPhaseChangeSamples( 2 ); + addPhaseChangeSamples( 2 ); } value >>= 1; } - addSamples( 8 ); - addSamples( 8 ); + addPhaseChangeSamples( 8 ); + addPhaseChangeSamples( 8 ); } } diff --git a/src/jkcemu/emusys/kc85/M003.java b/src/jkcemu/emusys/kc85/M003.java index 2de9212..25d6893 100644 --- a/src/jkcemu/emusys/kc85/M003.java +++ b/src/jkcemu/emusys/kc85/M003.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -43,10 +43,10 @@ public M003( int slot, EmuThread emuThread ) @Override public void appendInterruptStatusHTMLTo( StringBuilder buf ) { - buf.append( "

    CTC (IO-Adressen 0C-0F)

    \n" ); + buf.append( "

    CTC (E/A-Adressen 0C-0F)

    \n" ); this.ctc.appendInterruptStatusHTMLTo( buf ); buf.append( "

    \n" - + "

    SIO (IO-Adressen 08-0B)

    \n" ); + + "

    SIO (E/A-Adressen 08-0B)

    \n" ); this.sio.appendInterruptStatusHTMLTo( buf ); } @@ -138,7 +138,7 @@ public int getTypeByte() @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = -1; if( this.enabled ) { @@ -163,7 +163,7 @@ public int readIOByte( int port ) case 0x0D: case 0x0E: case 0x0F: - rv = this.ctc.read( port & 0x03 ); + rv = this.ctc.read( port & 0x03, tStates ); break; } } @@ -186,7 +186,7 @@ public String toString() @Override - public boolean writeIOByte( int port, int value ) + public boolean writeIOByte( int port, int value, int tStates ) { boolean rv = false; if( this.enabled ) { @@ -215,7 +215,7 @@ public boolean writeIOByte( int port, int value ) case 0x0D: case 0x0E: case 0x0F: - this.ctc.write( port & 0x03, value ); + this.ctc.write( port & 0x03, value, tStates ); rv = true; break; } diff --git a/src/jkcemu/emusys/kc85/M021.java b/src/jkcemu/emusys/kc85/M021.java index 57c7378..811ea8e 100644 --- a/src/jkcemu/emusys/kc85/M021.java +++ b/src/jkcemu/emusys/kc85/M021.java @@ -1,5 +1,5 @@ /* - * (c) 2010 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -62,7 +62,7 @@ public boolean supportsPrinter() @Override - public boolean writeIOByte( int port, int value ) + public boolean writeIOByte( int port, int value, int tStates ) { boolean rv = false; port &= 0xFF; @@ -70,7 +70,7 @@ public boolean writeIOByte( int port, int value ) switch( port & 0xFF ) { case 0x90: { - this.pio.writePortA( value ); + this.pio.writeDataA( value ); PrintMngr pm = this.emuThread.getPrintMngr(); if( pm != null ) { boolean strobe = ((this.pio.fetchOutValuePortA( false ) @@ -94,7 +94,7 @@ public boolean writeIOByte( int port, int value ) break; case 0x91: - this.pio.writePortB( value ); + this.pio.writeDataB( value ); break; case 0x92: diff --git a/src/jkcemu/emusys/kc85/M025.java b/src/jkcemu/emusys/kc85/M025.java index 94b772e..9509efd 100644 --- a/src/jkcemu/emusys/kc85/M025.java +++ b/src/jkcemu/emusys/kc85/M025.java @@ -1,5 +1,5 @@ /* - * (c) 2011 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -10,54 +10,23 @@ import java.awt.Component; import java.lang.*; -import jkcemu.base.*; -public class M025 extends KC85ROM8KModule +public class M025 extends AbstractKC85UserPROMModule { - private int typeByte; - private String fileName; - - - public M025( - int slot, - EmuThread emuThread, - int typeByte, - Component owner, - String fileName ) + public M025( int slot, int typeByte, Component owner, String fileName ) { - super( slot, emuThread, "M025", loadFile( owner, fileName ) ); - this.typeByte = typeByte; - this.fileName = fileName; + super( slot, typeByte, "M025", 1, 0x2000, owner, fileName ); } - @Override - public String getFileName() - { - return this.fileName; - } - + /* --- ueberschriebene Methoden --- */ + // Steuerbyte: AAAxxxxM @Override - public int getTypeByte() + public void setStatus( int value ) { - return this.typeByte; - } - - - @Override - public void reload( Component owner ) - { - setROM( loadFile( owner, this.fileName ) ); - } - - - /* --- private Methoden --- */ - - private static byte[] loadFile( Component owner, String fileName ) - { - return EmuUtil.readFile( owner, fileName, 0x2000, "M025 ROM-Datei" ); + super.setStatus( value ); + this.begAddr = (value << 8) & 0xE000; } } - diff --git a/src/jkcemu/emusys/kc85/M028.java b/src/jkcemu/emusys/kc85/M028.java index a0eadb1..d112309 100644 --- a/src/jkcemu/emusys/kc85/M028.java +++ b/src/jkcemu/emusys/kc85/M028.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -10,92 +10,19 @@ import java.awt.Component; import java.lang.*; -import java.util.*; -import jkcemu.base.*; -public class M028 extends AbstractKC85Module +public class M028 extends AbstractKC85UserPROMModule { - private int typeByte; - private int begAddr; - private String fileName; - private byte[] rom; - - - public M028( - int slot, - EmuThread emuThread, - int typeByte, - Component owner, - String fileName ) + public M028( int slot, int typeByte, Component owner, String fileName ) { - super( slot ); - this.typeByte = typeByte; - this.begAddr = 0; - this.fileName = fileName; - reload( owner ); + super( slot, typeByte, "M028", 1, 0x4000, owner, fileName ); } /* --- ueberschriebene Methoden --- */ - @Override - public int getBegAddr() - { - return this.begAddr; - } - - - @Override - public String getFileName() - { - return this.fileName; - } - - - @Override - public String getModuleName() - { - return "M028"; - } - - - @Override - public int getTypeByte() - { - return this.typeByte; - } - - - @Override - public int readMemByte( int addr ) - { - int rv = -1; - if( this.enabled - && (addr >= this.begAddr) - && (addr < (this.begAddr + 0x4000)) - && (this.rom != null) ) - { - int idx = addr - this.begAddr; - if( idx < this.rom.length ) { - rv = (int) this.rom[ idx ] & 0xFF; - } - } - return rv; - } - - - @Override - public void reload( Component owner ) - { - this.rom = EmuUtil.readFile( - owner, - this.fileName, - 0x4000, - "M028 ROM-Datei" ); - } - - + // Steuerbyte: AAxxxxxM @Override public void setStatus( int value ) { diff --git a/src/jkcemu/emusys/kc85/M040.java b/src/jkcemu/emusys/kc85/M040.java index dcc224c..39310d3 100644 --- a/src/jkcemu/emusys/kc85/M040.java +++ b/src/jkcemu/emusys/kc85/M040.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -108,6 +108,7 @@ public void reload( Component owner ) this.rom = EmuUtil.readFile( owner, this.fileName, + true, 0x4000, "M040 ROM-Datei" ); if( (this.typeByte == 0x01) && (this.rom != null) ) { diff --git a/src/jkcemu/emusys/kc85/M045.java b/src/jkcemu/emusys/kc85/M045.java new file mode 100644 index 0000000..522c35e --- /dev/null +++ b/src/jkcemu/emusys/kc85/M045.java @@ -0,0 +1,33 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Emulation eines 32K-User-PROM-Moduls M045 (4x8 KByte) + */ + +package jkcemu.emusys.kc85; + +import java.awt.Component; +import java.lang.*; + + +public class M045 extends AbstractKC85UserPROMModule +{ + public M045( int slot, Component owner, String fileName ) + { + super( slot, 0x70, "M045", 4, 0x2000, owner, fileName ); + } + + + /* --- ueberschriebene Methoden --- */ + + // Steuerbyte: AASSxxxM + @Override + public void setStatus( int value ) + { + super.setStatus( value ); + this.begAddr = (value << 8) & 0xC000; + this.segMask = (value << 9) & 0x6000; + } +} diff --git a/src/jkcemu/emusys/kc85/M046.java b/src/jkcemu/emusys/kc85/M046.java new file mode 100644 index 0000000..5146acc --- /dev/null +++ b/src/jkcemu/emusys/kc85/M046.java @@ -0,0 +1,33 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Emulation eines 64K-User-PROM-Moduls M046 (8x8 KByte) + */ + +package jkcemu.emusys.kc85; + +import java.awt.Component; +import java.lang.*; + + +public class M046 extends AbstractKC85UserPROMModule +{ + public M046( int slot, Component owner, String fileName ) + { + super( slot, 0x71, "M046", 8, 0x2000, owner, fileName ); + } + + + /* --- ueberschriebene Methoden --- */ + + // Steuerbyte: AASSxSxM + @Override + public void setStatus( int value ) + { + super.setStatus( value ); + this.begAddr = (value << 8) & 0xC000; + this.segMask = ((value << 10) & 0xC000) | ((value << 11) & 0x2000); + } +} diff --git a/src/jkcemu/emusys/kc85/M047.java b/src/jkcemu/emusys/kc85/M047.java new file mode 100644 index 0000000..5dee2c2 --- /dev/null +++ b/src/jkcemu/emusys/kc85/M047.java @@ -0,0 +1,33 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Emulation eines 128K-User-PROM-Moduls M047 (16x8 KByte) + */ + +package jkcemu.emusys.kc85; + +import java.awt.Component; +import java.lang.*; + + +public class M047 extends AbstractKC85UserPROMModule +{ + public M047( int slot, Component owner, String fileName ) + { + super( slot, 0x72, "M047", 16, 0x2000, owner, fileName ); + } + + + /* --- ueberschriebene Methoden --- */ + + // Steuerbyte: AASSSSxM + @Override + public void setStatus( int value ) + { + super.setStatus( value ); + this.begAddr = (value << 8) & 0xC000; + this.segMask = (value << 11) & 0x1E000; + } +} diff --git a/src/jkcemu/emusys/kc85/M048.java b/src/jkcemu/emusys/kc85/M048.java new file mode 100644 index 0000000..822ee2a --- /dev/null +++ b/src/jkcemu/emusys/kc85/M048.java @@ -0,0 +1,33 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Emulation eines 256K-User-PROM-Moduls M048 (16x16 KByte) + */ + +package jkcemu.emusys.kc85; + +import java.awt.Component; +import java.lang.*; + + +public class M048 extends AbstractKC85UserPROMModule +{ + public M048( int slot, Component owner, String fileName ) + { + super( slot, 0x73, "M048", 16, 0x4000, owner, fileName ); + } + + + /* --- ueberschriebene Methoden --- */ + + // Steuerbyte: AASSSSxM + @Override + public void setStatus( int value ) + { + super.setStatus( value ); + this.begAddr = (value << 8) & 0xC000; + this.segMask = (value << 12) & 0x3C000; + } +} diff --git a/src/jkcemu/emusys/kc85/M052.java b/src/jkcemu/emusys/kc85/M052.java index 4c0ac49..55eaa11 100644 --- a/src/jkcemu/emusys/kc85/M052.java +++ b/src/jkcemu/emusys/kc85/M052.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -36,14 +36,18 @@ public class M052 extends AbstractKC85Module private byte[] rom; - public M052( int slot, Component owner, String fileName ) + public M052( + int slot, + Component owner, + FileTimesViewFactory fileTimesViewFactory, + String fileName ) { super( slot ); this.owner = owner; this.fileName = fileName; this.title = String.format( "M052 im Schacht %02X", slot ); this.kcNet = new KCNet( "Netzwerk-PIO" ); - this.vdip = new VDIP( "USB-PIO" ); + this.vdip = new VDIP( fileTimesViewFactory, "USB-PIO" ); this.ioEnabled = false; this.romOffs = 0; this.begAddr = 0; @@ -69,10 +73,10 @@ public VDIP getVDIP() @Override public void appendInterruptStatusHTMLTo( StringBuilder buf ) { - buf.append( "

    Netzwerk-PIO (IO-Adressen 28-2B)

    \n" ); + buf.append( "

    Netzwerk-PIO (E/A-Adressen 28-2B)

    \n" ); this.kcNet.appendInterruptStatusHTMLTo( buf ); - buf.append( "

    USB-PIO (IO-Adressen 2C-2F)

    \n" ); + buf.append( "

    USB-PIO (E/A-Adressen 2C-2F)

    \n" ); this.vdip.appendInterruptStatusHTMLTo( buf ); } @@ -194,7 +198,7 @@ public int getTypeByte() @Override - public int readIOByte( int port ) + public int readIOByte( int port, int tStates ) { int rv = -1; if( this.ioEnabled ) { @@ -244,6 +248,7 @@ public void reload( Component owner ) this.rom = EmuUtil.readFile( owner, this.fileName, + true, 0x8000, "M052 ROM-Datei" ); } @@ -269,7 +274,7 @@ public String toString() @Override - public boolean writeIOByte( int port, int value ) + public boolean writeIOByte( int port, int value, int tStates ) { boolean rv = false; if( this.ioEnabled ) { diff --git a/src/jkcemu/emusys/kccompact/KCcompactSettingsFld.java b/src/jkcemu/emusys/kccompact/KCcompactSettingsFld.java index 85c85be..98112a9 100644 --- a/src/jkcemu/emusys/kccompact/KCcompactSettingsFld.java +++ b/src/jkcemu/emusys/kccompact/KCcompactSettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2012 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -17,49 +17,133 @@ public class KCcompactSettingsFld extends AbstractSettingsFld { + private JTabbedPane tabbedPane; private JCheckBox btnFDC; private JCheckBox btnFixedScreenSize; - private ROMFileSettingsFld fldAltRomFDC; + private JRadioButton btnSoundMono; + private JRadioButton btnSoundStereo; + private JPanel tabFDC; + private JPanel tabSound; + private JPanel tabEtc; + private ROMFileSettingsFld fldAltFDC; + private ROMFileSettingsFld fldAltOS; + private ROMFileSettingsFld fldAltBasic; public KCcompactSettingsFld( SettingsFrm settingsFrm, String propPrefix ) { super( settingsFrm, propPrefix ); + setLayout( new BorderLayout() ); - setLayout( new GridBagLayout() ); + this.tabbedPane = new JTabbedPane( JTabbedPane.TOP ); + add( this.tabbedPane, BorderLayout.CENTER ); - GridBagConstraints gbc = new GridBagConstraints( + + // Tab Floppy-Disk-Station + this.tabFDC = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Floppy-Disk-Station", this.tabFDC ); + + GridBagConstraints gbcFDC = new GridBagConstraints( 0, 0, GridBagConstraints.REMAINDER, 1, - 0.0, 0.0, + 1.0, 0.0, GridBagConstraints.WEST, - GridBagConstraints.NONE, + GridBagConstraints.HORIZONTAL, new Insets( 5, 5, 5, 5 ), 0, 0 ); this.btnFDC = new JCheckBox( "Floppy-Disk-Station emulieren", false ); this.btnFDC.addActionListener( this ); - add( this.btnFDC, gbc ); + this.tabFDC.add( this.btnFDC, gbcFDC ); - this.fldAltRomFDC = new ROMFileSettingsFld( + this.fldAltFDC = new ROMFileSettingsFld( settingsFrm, - "jkcemu.kccompact.fdc.rom.", + this.propPrefix + "fdc.rom.", "Alternativer ROM in der Floppy-Disk-Station:" ); - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weightx = 1.0; - gbc.gridy++; - add( this.fldAltRomFDC, gbc ); + gbcFDC.fill = GridBagConstraints.HORIZONTAL; + gbcFDC.weightx = 1.0; + gbcFDC.insets.left = 50; + gbcFDC.gridy++; + this.tabFDC.add( this.fldAltFDC, gbcFDC ); + + + // Tab Sound + this.tabSound = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Sound", this.tabSound ); + + GridBagConstraints gbcSound = new GridBagConstraints( + 0, 0, + GridBagConstraints.REMAINDER, 1, + 1.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.HORIZONTAL, + new Insets( 5, 5, 5, 5 ), + 0, 0 ); + + this.tabSound.add( + new JLabel( "Tonausgabe des Sound-Generators:" ), + gbcSound ); + + ButtonGroup grpSoundChannels = new ButtonGroup(); + + this.btnSoundMono = new JRadioButton( "Mono (Kan\u00E4le: A+B+C)", true ); + this.btnSoundMono.addActionListener( this ); + grpSoundChannels.add( this.btnSoundMono ); + gbcSound.insets.left = 50; + gbcSound.insets.top = 0; + gbcSound.insets.bottom = 0; + gbcSound.gridy++; + this.tabSound.add( this.btnSoundMono, gbcSound ); + + this.btnSoundStereo = new JRadioButton( + "Stereo (Kan\u00E4le: Links=A+B/2, Rechts=C+B/2)" ); + this.btnSoundStereo.addActionListener( this ); + grpSoundChannels.add( this.btnSoundStereo ); + gbcSound.insets.bottom = 5; + gbcSound.gridy++; + this.tabSound.add( this.btnSoundStereo, gbcSound ); + - gbc.gridy++; - add( new JSeparator(), gbc ); + // Tab Sonstiges + this.tabEtc = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Sonstiges", this.tabEtc ); + + GridBagConstraints gbcEtc = new GridBagConstraints( + 0, 0, + GridBagConstraints.REMAINDER, 1, + 1.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.HORIZONTAL, + new Insets( 5, 5, 5, 5 ), + 0, 0 ); this.btnFixedScreenSize = new JCheckBox( "Gleiche Fenstergr\u00F6\u00DFe in allen Bildschirmmodi" ); this.btnFixedScreenSize.addActionListener( this ); - gbc.fill = GridBagConstraints.NONE; - gbc.weightx = 0.0; - gbc.gridy++; - add( this.btnFixedScreenSize, gbc ); + this.tabEtc.add( this.btnFixedScreenSize, gbcEtc ); + + gbcEtc.fill = GridBagConstraints.HORIZONTAL; + gbcEtc.weightx = 1.0; + gbcEtc.insets.top = 10; + gbcEtc.insets.bottom = 10; + gbcEtc.gridy++; + this.tabEtc.add( new JSeparator(), gbcEtc ); + + this.fldAltOS = new ROMFileSettingsFld( + settingsFrm, + propPrefix + "os.", + "Alternativer Betriebssystem-ROM:" ); + gbcEtc.insets.top = 5; + gbcEtc.insets.bottom = 5; + gbcEtc.gridy++; + this.tabEtc.add( this.fldAltOS, gbcEtc ); + + this.fldAltBasic = new ROMFileSettingsFld( + settingsFrm, + propPrefix + "basic.", + "Alternativer BASIC-ROM:" ); + gbcEtc.gridy++; + this.tabEtc.add( this.fldAltBasic, gbcEtc ); updFieldsEnabled(); } @@ -80,7 +164,13 @@ public void applyInput( props, this.propPrefix + "fixed_screen_size", this.btnFixedScreenSize.isSelected() ); - this.fldAltRomFDC.applyInput( props, selected ); + EmuUtil.setProperty( + props, + this.propPrefix + "sound.stereo", + this.btnSoundStereo.isSelected() ); + this.fldAltFDC.applyInput( props, selected ); + this.fldAltOS.applyInput( props, selected ); + this.fldAltBasic.applyInput( props, selected ); } @@ -100,6 +190,15 @@ protected boolean doAction( EventObject e ) } + @Override + public void lookAndFeelChanged() + { + this.fldAltOS.lookAndFeelChanged(); + this.fldAltFDC.lookAndFeelChanged(); + this.fldAltBasic.lookAndFeelChanged(); + } + + @Override public void updFields( Properties props ) { @@ -113,7 +212,18 @@ public void updFields( Properties props ) props, this.propPrefix + "fixed_screen_size", false ) ); - this.fldAltRomFDC.updFields( props ); + if( EmuUtil.getBooleanProperty( + props, + this.propPrefix + "sound.stereo", + false ) ) + { + this.btnSoundStereo.setSelected( true ); + } else { + this.btnSoundMono.setSelected( true ); + } + this.fldAltFDC.updFields( props ); + this.fldAltOS.updFields( props ); + this.fldAltBasic.updFields( props ); updFieldsEnabled(); } @@ -122,6 +232,6 @@ public void updFields( Properties props ) private void updFieldsEnabled() { - this.fldAltRomFDC.setEnabled( this.btnFDC.isSelected() ); + this.fldAltFDC.setEnabled( this.btnFDC.isSelected() ); } } diff --git a/src/jkcemu/emusys/lc80/LC80SettingsFld.java b/src/jkcemu/emusys/lc80/LC80SettingsFld.java index 5b91286..25f55f7 100644 --- a/src/jkcemu/emusys/lc80/LC80SettingsFld.java +++ b/src/jkcemu/emusys/lc80/LC80SettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -37,7 +37,7 @@ public LC80SettingsFld( SettingsFrm settingsFrm, String propPrefix ) add( this.tabbedPane, BorderLayout.CENTER ); - // Bereich Modell + // Tab Modell this.tabModel = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Modell", this.tabModel ); @@ -86,7 +86,7 @@ public LC80SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.tabModel.add( this.btnLC80e, gbcModel ); - // Bereich ROM + // Tab ROM this.tabRom = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "ROM", this.tabRom ); @@ -172,6 +172,14 @@ protected boolean doAction( EventObject e ) } + @Override + public void lookAndFeelChanged() + { + this.fldAltOS.lookAndFeelChanged(); + this.fldAltC000.lookAndFeelChanged(); + } + + @Override public void updFields( Properties props ) { diff --git a/src/jkcemu/emusys/poly880/Poly880SettingsFld.java b/src/jkcemu/emusys/poly880/Poly880SettingsFld.java index ee6a5d3..6ec3551 100644 --- a/src/jkcemu/emusys/poly880/Poly880SettingsFld.java +++ b/src/jkcemu/emusys/poly880/Poly880SettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -37,7 +37,7 @@ public Poly880SettingsFld( SettingsFrm settingsFrm, String propPrefix ) add( this.tabbedPane, BorderLayout.CENTER ); - // Bereich Erweiterungen + // Tab Erweiterungen this.tabExt = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Erweiterungen", this.tabExt ); @@ -54,27 +54,31 @@ public Poly880SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.btnRAM8000.addActionListener( this ); this.tabExt.add( this.btnRAM8000, gbcExt ); - gbcExt.fill = GridBagConstraints.HORIZONTAL; - gbcExt.weightx = 1.0; + gbcExt.fill = GridBagConstraints.HORIZONTAL; + gbcExt.weightx = 1.0; + gbcExt.insets.top = 10; + gbcExt.insets.bottom = 10; gbcExt.gridy++; this.tabExt.add( new JSeparator(), gbcExt ); this.fldROM2 = new ROMFileSettingsFld( - settingsFrm, - propPrefix + "rom_2000.", - "ROM-Erweiterung 2000h-23FFh:" ); + settingsFrm, + propPrefix + "rom_2000.", + "ROM-Erweiterung 2000h-23FFh:" ); + gbcExt.insets.top = 5; + gbcExt.insets.bottom = 5; gbcExt.gridy++; this.tabExt.add( this.fldROM2, gbcExt ); this.fldROM3 = new ROMFileSettingsFld( - settingsFrm, - propPrefix + "rom_3000.", - "ROM-Erweiterung 3000h-33FFh:" ); + settingsFrm, + propPrefix + "rom_3000.", + "ROM-Erweiterung 3000h-33FFh:" ); gbcExt.gridy++; this.tabExt.add( this.fldROM3, gbcExt ); - // Bereich Sonstiges + // Tab Sonstiges this.tabEtc = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Sonstiges", this.tabEtc ); @@ -150,6 +154,16 @@ protected boolean doAction( EventObject e ) } + @Override + public void lookAndFeelChanged() + { + this.fldAltROM0.lookAndFeelChanged(); + this.fldAltROM1.lookAndFeelChanged(); + this.fldROM2.lookAndFeelChanged(); + this.fldROM3.lookAndFeelChanged(); + } + + @Override public void updFields( Properties props ) { diff --git a/src/jkcemu/emusys/z1013/GraphicCCJ.java b/src/jkcemu/emusys/z1013/GraphicCCJ.java index 76f8123..5594306 100644 --- a/src/jkcemu/emusys/z1013/GraphicCCJ.java +++ b/src/jkcemu/emusys/z1013/GraphicCCJ.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -119,6 +119,7 @@ public void loadFont( String fontFileName ) fontBytes = EmuUtil.readFile( this.screenFrm, fontFileName, + true, 0x2000, "Zeichensatzdatei" ); } diff --git a/src/jkcemu/emusys/z1013/Z1013AudioDataStream.java b/src/jkcemu/emusys/z1013/Z1013AudioDataStream.java index 6368583..69b87ae 100644 --- a/src/jkcemu/emusys/z1013/Z1013AudioDataStream.java +++ b/src/jkcemu/emusys/z1013/Z1013AudioDataStream.java @@ -1,5 +1,5 @@ /* - * (c) 2011 Jens Mueller + * (c) 2011-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -57,7 +57,7 @@ public Z1013AudioDataStream( while( sourceByteAvailable() ) { // Vorton, 1. Halbschwingung, ab dem 2. Block vergroessert (kurze Pause) - addSamples( blkIdx > 0 ? 40 : 12 ); + addPhaseChangeSamples( blkIdx > 0 ? 40 : 12 ); // Vorton, restliche Halbschwingungen int nHalf = 27; // (14 * 2) - 1 @@ -65,12 +65,12 @@ public Z1013AudioDataStream( nHalf= 3999; // (2000 * 2) - 1 } for( int i = 0; i < nHalf; i++ ) { // weitere Halbschwingungen - addSamples( 12 ); + addPhaseChangeSamples( 12 ); } // Trennschwingung - addSamples( 6 ); - addSamples( 6 ); + addPhaseChangeSamples( 6 ); + addPhaseChangeSamples( 6 ); // Blocknummer int w = 0; @@ -102,7 +102,7 @@ public Z1013AudioDataStream( // abschliessender Phasenwechsel if( getFrameLength() > 0 ) { - addSamples( 40 ); + addPhaseChangeSamples( 40 ); } } @@ -119,10 +119,10 @@ private void addWordSamples( int value ) { for( int i = 0; i < 16; i++ ) { if( (value & 0x01) != 0 ) { - addSamples( 6 ); + addPhaseChangeSamples( 6 ); } else { - addSamples( 3 ); - addSamples( 3 ); + addPhaseChangeSamples( 3 ); + addPhaseChangeSamples( 3 ); } value >>= 1; } diff --git a/src/jkcemu/emusys/z1013/Z1013SettingsFld.java b/src/jkcemu/emusys/z1013/Z1013SettingsFld.java index ad50a9e..ae0fd47 100644 --- a/src/jkcemu/emusys/z1013/Z1013SettingsFld.java +++ b/src/jkcemu/emusys/z1013/Z1013SettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2013 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -9,16 +9,18 @@ package jkcemu.emusys.z1013; import java.awt.*; -import java.io.File; import java.lang.*; import java.util.*; import javax.swing.*; import jkcemu.base.*; import jkcemu.disk.GIDESettingsFld; +import jkcemu.emusys.Z1013; public class Z1013SettingsFld extends AbstractSettingsFld { + private static final int DEFAULT_AUTO_ACTION_WAIT_MILLIS = 500; + private JTabbedPane tabbedPane; private JRadioButton btnZ1013_01; private JRadioButton btnZ1013_12; @@ -61,6 +63,8 @@ public class Z1013SettingsFld extends AbstractSettingsFld private JPanel tabModROM; private JPanel tabExt; private JPanel tabEtc; + private AutoLoadSettingsFld tabAutoLoad; + private AutoInputSettingsFld tabAutoInput; public Z1013SettingsFld( SettingsFrm settingsFrm, String propPrefix ) @@ -271,9 +275,9 @@ public Z1013SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.tabRF = new RAMFloppiesSettingsFld( settingsFrm, propPrefix, - "RAM-Floppy nach MP 3/88 (256 KByte) an IO-Adressen 98h-9Fh", + "RAM-Floppy nach MP 3/88 (256 KByte) an E/A-Adressen 98h-9Fh", RAMFloppy.RFType.MP_3_1988, - "RAM-Floppy nach MP 3/88 (256 KByte) an IO-Adressen 58h-5Fh", + "RAM-Floppy nach MP 3/88 (256 KByte) an E/A-Adressen 58h-5Fh", RAMFloppy.RFType.MP_3_1988 ); this.tabbedPane.addTab( "RAM-Floppies", this.tabRF ); @@ -297,7 +301,7 @@ public Z1013SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.fldAltBasic = new ROMFileSettingsFld( settingsFrm, - this.propPrefix + "rom_basic.", + propPrefix + "rom_basic.", "Alternativer Inhalt des KC-BASIC-Moduls:" ); gbcModROM.fill = GridBagConstraints.HORIZONTAL; gbcModROM.weightx = 1.0; @@ -317,7 +321,7 @@ public Z1013SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.fldMegaROM = new ROMFileSettingsFld( settingsFrm, - this.propPrefix + "rom_mega.", + propPrefix + "rom_mega.", "Inhalt des Mega-ROM-Moduls:" ); gbcModROM.fill = GridBagConstraints.HORIZONTAL; gbcModROM.weightx = 1.0; @@ -341,13 +345,13 @@ public Z1013SettingsFld( SettingsFrm settingsFrm, String propPrefix ) this.tabbedPane.addTab( "Erweiterungen", this.tabExt ); GridBagConstraints gbcExt = new GridBagConstraints( - 0, 0, - 1, 1, - 0.0, 0.0, - GridBagConstraints.WEST, - GridBagConstraints.NONE, - new Insets( 5, 5, 0, 5 ), - 0, 0 ); + 0, 0, + GridBagConstraints.REMAINDER, 1, + 1.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.HORIZONTAL, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); this.btnFloppyDisk = new JCheckBox( "Floppy-Disk-Modul", false ); this.btnFloppyDisk.addActionListener( this ); @@ -368,7 +372,7 @@ public Z1013SettingsFld( SettingsFrm settingsFrm, String propPrefix ) gbcExt.gridy++; this.tabExt.add( this.btnVDIP, gbcExt ); - this.btnRTC = new JCheckBox( "Echtzeituhr RTC-72421", false ); + this.btnRTC = new JCheckBox( "Echtzeituhr", false ); this.btnRTC.addActionListener( this ); gbcExt.gridy++; this.tabExt.add( this.btnRTC, gbcExt ); @@ -388,17 +392,11 @@ public Z1013SettingsFld( SettingsFrm settingsFrm, String propPrefix ) gbcExt.gridy++; this.tabExt.add( this.btnGraphCCJ, gbcExt ); - gbcExt.fill = GridBagConstraints.HORIZONTAL; - gbcExt.weightx = 1.0; - gbcExt.insets.top = 5; - gbcExt.gridy++; - this.tabExt.add( new JSeparator(), gbcExt ); - this.fldAltGCCJFont = new ROMFileSettingsFld( - settingsFrm, - this.propPrefix + "graph_ccj.font.", - "Alternativer Zeichensatz f\u00FCr Grafikkarte" - + " des CC Jena (80x25 Zeichen):" ); + settingsFrm, + this.propPrefix + "graph_ccj.font.", + "Alternativer Zeichensatz:" ); + gbcExt.insets.left = 50; gbcExt.gridy++; this.tabExt.add( this.fldAltGCCJFont, gbcExt ); updAltGCCJFontFieldsEnabled(); @@ -439,9 +437,10 @@ public Z1013SettingsFld( SettingsFrm settingsFrm, String propPrefix ) gbcEtc.gridy++; this.tabEtc.add( this.btnPasteFast, gbcEtc ); - gbcEtc.fill = GridBagConstraints.HORIZONTAL; - gbcEtc.weightx = 1.0; - gbcEtc.insets.top = 5; + gbcEtc.fill = GridBagConstraints.HORIZONTAL; + gbcEtc.weightx = 1.0; + gbcEtc.insets.top = 10; + gbcEtc.insets.bottom = 10; gbcEtc.gridy++; this.tabEtc.add( new JSeparator(), gbcEtc ); @@ -449,15 +448,35 @@ public Z1013SettingsFld( SettingsFrm settingsFrm, String propPrefix ) settingsFrm, propPrefix + "os.", "Alternatives Monitorprogramm (F000h-FFFFh):" ); + gbcEtc.insets.top = 5; + gbcEtc.insets.bottom = 5; gbcEtc.gridy++; this.tabEtc.add( this.fldAltOS, gbcEtc ); this.fldAltFont = new ROMFileSettingsFld( - settingsFrm, - propPrefix + "font.", - "Alternativer Zeichensatz:" ); + settingsFrm, + propPrefix + "font.", + "Alternativer Zeichensatz:" ); gbcEtc.gridy++; this.tabEtc.add( this.fldAltFont, gbcEtc ); + + + // Tab AutoLoad + this.tabAutoLoad = new AutoLoadSettingsFld( + settingsFrm, + propPrefix, + DEFAULT_AUTO_ACTION_WAIT_MILLIS, + true ); + this.tabbedPane.addTab( "AutoLoad", this.tabAutoLoad ); + + + // Tab AutoInput + this.tabAutoInput = new AutoInputSettingsFld( + settingsFrm, + propPrefix, + Z1013.getDefaultSwapKeyCharCase(), + DEFAULT_AUTO_ACTION_WAIT_MILLIS ); + this.tabbedPane.addTab( "AutoInput", this.tabAutoInput ); } @@ -552,7 +571,7 @@ else if( this.btnBL4_K7659.isSelected() ) { stateMega ); this.fldMegaROM.applyInput( props, selected ); - // Tab-GIDE + // Tab GIDE tab = this.tabGIDE; this.tabGIDE.applyInput( props, selected ); @@ -601,6 +620,14 @@ else if( this.btnBL4_K7659.isSelected() ) { this.fldAltOS.applyInput( props, selected ); this.fldAltFont.applyInput( props, selected ); + // Tab AutoLoad + tab = this.tabAutoLoad; + this.tabAutoLoad.applyInput( props, selected ); + + // Tab AutoInput + tab = this.tabAutoInput; + this.tabAutoInput.applyInput( props, selected ); + // ggf. Warnung ausgeben if( this.btnMonA2.isSelected() && this.btnModBasic.isSelected() @@ -674,6 +701,12 @@ else if( src instanceof AbstractButton ) { if( !rv ) { rv = this.tabGIDE.doAction( e ); } + if( !rv ) { + rv = this.tabAutoLoad.doAction( e ); + } + if( !rv ) { + rv = this.tabAutoInput.doAction( e ); + } this.settingsFrm.setWaitCursor( false ); return rv; } @@ -682,7 +715,14 @@ else if( src instanceof AbstractButton ) { @Override public void lookAndFeelChanged() { + this.fldAltOS.lookAndFeelChanged(); + this.fldAltFont.lookAndFeelChanged(); + this.fldAltGCCJFont.lookAndFeelChanged(); + this.fldAltBasic.lookAndFeelChanged(); + this.fldMegaROM.lookAndFeelChanged(); this.tabGIDE.lookAndFeelChanged(); + this.tabAutoLoad.lookAndFeelChanged(); + this.tabAutoInput.lookAndFeelChanged(); } @@ -792,6 +832,9 @@ public void updFields( Properties props ) true ) ); this.fldAltOS.updFields( props ); this.fldAltFont.updFields( props ); + + this.tabAutoLoad.updFields( props ); + this.tabAutoInput.updFields( props ); } diff --git a/src/jkcemu/emusys/z9001/Z9001SettingsFld.java b/src/jkcemu/emusys/z9001/Z9001SettingsFld.java index 23b67c2..9aff9bd 100644 --- a/src/jkcemu/emusys/z9001/Z9001SettingsFld.java +++ b/src/jkcemu/emusys/z9001/Z9001SettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2012 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -14,10 +14,14 @@ import java.util.*; import javax.swing.*; import jkcemu.base.*; +import jkcemu.disk.GIDESettingsFld; +import jkcemu.emusys.Z9001; public class Z9001SettingsFld extends AbstractSettingsFld { + private static final int DEFAULT_AUTO_ACTION_WAIT_MILLIS = 2000; + private static final String DEFAULT_LABEL_ROM_MODULE = "Inhalt des ROM-Moduls:"; @@ -27,7 +31,10 @@ public class Z9001SettingsFld extends AbstractSettingsFld private JPanel tabGraph; private JPanel tabMem; private JPanel tabPrinter; + private GIDESettingsFld tabGIDE; private RAMFloppiesSettingsFld tabRF; + private AutoLoadSettingsFld tabAutoLoad; + private AutoInputSettingsFld tabAutoInput; private JRadioButton btnMonoGraphNone; private JRadioButton btnMonoGraphKRT; private JRadioButton btnColorGraphNone; @@ -53,6 +60,7 @@ public class Z9001SettingsFld extends AbstractSettingsFld private JCheckBox btnPlotter; private JCheckBox btnKCNet; private JCheckBox btnVDIP; + private JCheckBox btnRTC; private JCheckBox btnPasteFast; private ROMFileSettingsFld fldAltOS; private ROMFileSettingsFld fldAltBASIC; @@ -66,14 +74,14 @@ public Z9001SettingsFld( boolean kc87 ) { super( settingsFrm, propPrefix ); - this.switchOffMap = new HashMap(); + this.switchOffMap = new HashMap<>(); setLayout( new BorderLayout() ); this.tabbedPane = new JTabbedPane( JTabbedPane.TOP ); add( this.tabbedPane, BorderLayout.CENTER ); - // Bereich Grafik + // Tab Grafik this.tabGraph = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Grafik", this.tabGraph ); @@ -146,7 +154,7 @@ public Z9001SettingsFld( this.tabGraph.add( this.btnFixedScreenSize, gbcGraph ); - // Bereich Speichermodule + // Tab Speichermodule this.tabMem = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Speichermodule", this.tabMem ); @@ -313,18 +321,18 @@ public Z9001SettingsFld( updMemFieldsEnabled(); - // Bereich RAM-Floppies + // Tab RAM-Floppies this.tabRF = new RAMFloppiesSettingsFld( settingsFrm, propPrefix, - "RAM-Floppy an IO-Adressen 20h/21h", + "RAM-Floppy an E/A-Adressen 20h/21h", RAMFloppy.RFType.ADW, - "RAM-Floppy an IO-Adressen 24h/25h", + "RAM-Floppy an E/A-Adressen 24h/25h", RAMFloppy.RFType.ADW ); this.tabbedPane.addTab( "RAM-Floppies", this.tabRF ); - // Bereich Drucker + // Tab Drucker this.tabPrinter = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Drucker", this.tabPrinter ); @@ -362,7 +370,12 @@ public Z9001SettingsFld( this.tabPrinter.add( this.btnNoPrinter, gbcPrinter ); - // Bereich Erweiterungen + // Tab GIDE + this.tabGIDE = new GIDESettingsFld( settingsFrm, propPrefix ); + this.tabbedPane.addTab( "GIDE", this.tabGIDE ); + + + // Tab Erweiterungen this.tabExt = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Erweiterungen", this.tabExt ); @@ -390,12 +403,17 @@ public Z9001SettingsFld( this.btnVDIP = new JCheckBox( "USB-Anschluss (Vinculum VDIP Modul)", false ); - gbcExt.insets.bottom = 5; gbcExt.gridy++; this.tabExt.add( this.btnVDIP, gbcExt ); + this.btnRTC = new JCheckBox( "Echtzeituhr", false ); + this.btnRTC.addActionListener( this ); + gbcExt.insets.bottom = 5; + gbcExt.gridy++; + this.tabExt.add( this.btnRTC, gbcExt ); - // Bereich Sonstiges + + // Tab Sonstiges this.tabEtc = new JPanel( new GridBagLayout() ); this.tabbedPane.addTab( "Sonstiges", this.tabEtc ); @@ -414,9 +432,10 @@ public Z9001SettingsFld( gbcEtc.gridy++; this.tabEtc.add( this.btnPasteFast, gbcEtc ); - gbcEtc.fill = GridBagConstraints.HORIZONTAL; - gbcEtc.weightx = 1.0; - gbcEtc.insets.top = 5; + gbcEtc.fill = GridBagConstraints.HORIZONTAL; + gbcEtc.weightx = 1.0; + gbcEtc.insets.top = 10; + gbcEtc.insets.bottom = 10; gbcEtc.gridy++; this.tabEtc.add( new JSeparator(), gbcEtc ); @@ -424,6 +443,8 @@ public Z9001SettingsFld( settingsFrm, propPrefix + "os.", "Alternatives Betriebssystem (F000h-FFFFh):" ); + gbcEtc.insets.top = 5; + gbcEtc.insets.bottom = 5; gbcEtc.gridy++; this.tabEtc.add( this.fldAltOS, gbcEtc ); @@ -446,6 +467,24 @@ public Z9001SettingsFld( this.tabEtc.add( this.fldAltFont, gbcEtc ); + // Tab AutoLoad + this.tabAutoLoad = new AutoLoadSettingsFld( + settingsFrm, + propPrefix, + DEFAULT_AUTO_ACTION_WAIT_MILLIS, + true ); + this.tabbedPane.addTab( "AutoLoad", this.tabAutoLoad ); + + + // Tab AutoInput + this.tabAutoInput = new AutoInputSettingsFld( + settingsFrm, + propPrefix, + Z9001.getDefaultSwapKeyCharCase(), + DEFAULT_AUTO_ACTION_WAIT_MILLIS ); + this.tabbedPane.addTab( "AutoInput", this.tabAutoInput ); + + // Listener this.btnMonoGraphNone.addActionListener( this ); this.btnMonoGraphKRT.addActionListener( this ); @@ -485,7 +524,7 @@ public void applyInput( Component tab = null; try { - // Grafik + // Tab Grafik tab = this.tabGraph; boolean color = false; String graph = "none"; @@ -515,7 +554,7 @@ public void applyInput( this.propPrefix + "fixed_screen_size", this.btnFixedScreenSize.isSelected() ); - // Speichermodule + // Tab Speichermodule tab = this.tabMem; EmuUtil.setProperty( props, @@ -574,11 +613,11 @@ public void applyInput( this.propPrefix + "rom_mega.enabled", this.btnRomMega.isSelected() ); - // RAM-Floppies + // Tab RAM-Floppies tab = this.tabRF; this.tabRF.applyInput( props, selected ); - // Drucker + // Tab Drucker tab = this.tabPrinter; EmuUtil.setProperty( props, @@ -589,7 +628,11 @@ public void applyInput( this.propPrefix + "printer_module.enabled", this.btnPrinterModule.isSelected() ); - // Erweiterungen + // Tab GIDE + tab = this.tabGIDE; + this.tabGIDE.applyInput( props, selected ); + + // Tab Erweiterungen tab = this.tabExt; EmuUtil.setProperty( props, @@ -607,8 +650,12 @@ public void applyInput( props, this.propPrefix + "vdip.enabled", this.btnVDIP.isSelected() ); + EmuUtil.setProperty( + props, + this.propPrefix + "rtc.enabled", + this.btnRTC.isSelected() ); - // Sonstiges + // Tab Sonstiges tab = this.tabEtc; EmuUtil.setProperty( props, @@ -620,6 +667,14 @@ public void applyInput( this.fldAltBASIC.applyInput( props, selected ); } this.fldAltFont.applyInput( props, selected ); + + // Tab AutoLoad + tab = this.tabAutoLoad; + this.tabAutoLoad.applyInput( props, selected ); + + // Tab AutoInput + tab = this.tabAutoInput; + this.tabAutoInput.applyInput( props, selected ); } catch( UserInputException ex ) { if( tab != null ) { @@ -651,14 +706,38 @@ protected boolean doAction( EventObject e ) rv = true; } } + if( !rv ) { + rv = this.tabGIDE.doAction( e ); + } + if( !rv ) { + rv = this.tabAutoLoad.doAction( e ); + } + if( !rv ) { + rv = this.tabAutoInput.doAction( e ); + } return rv; } + @Override + public void lookAndFeelChanged() + { + this.fldRomModule.lookAndFeelChanged(); + this.fldAltOS.lookAndFeelChanged(); + if( this.fldAltBASIC != null ) { + this.fldAltBASIC.lookAndFeelChanged(); + } + this.fldAltFont.lookAndFeelChanged(); + this.tabGIDE.lookAndFeelChanged(); + this.tabAutoLoad.lookAndFeelChanged(); + this.tabAutoInput.lookAndFeelChanged(); + } + + @Override public void updFields( Properties props ) { - // Grafik + // Tab Grafik boolean color = EmuUtil.getBooleanProperty( props, this.propPrefix + "color", @@ -697,7 +776,7 @@ public void updFields( Properties props ) this.propPrefix + "fixed_screen_size", false ) ); - // Speichermodule + // Tab Speichermodule this.btnRam16k4000.setSelected( EmuUtil.getBooleanProperty( props, @@ -746,10 +825,10 @@ public void updFields( Properties props ) this.fldRomModule.updFields( props ); updMemFieldsEnabled(); - // RAM-Flopies + // Tab RAM-Flopies this.tabRF.updFields( props ); - // Drucker + // Tab Drucker if( EmuUtil.getBooleanProperty( props, this.propPrefix + "printer_module.enabled", @@ -766,7 +845,10 @@ public void updFields( Properties props ) this.btnNoPrinter.setSelected( true ); } - // Erweiterungen + // Tab GIDE + this.tabGIDE.updFields( props ); + + // Tab Erweiterungen this.btnFloppyDisk.setSelected( EmuUtil.getBooleanProperty( props, @@ -787,8 +869,12 @@ public void updFields( Properties props ) props, this.propPrefix + "vdip.enabled", false ) ); + this.btnRTC.setSelected( + EmuUtil.getBooleanProperty( + props, + this.propPrefix + "rtc.enabled", false ) ); - // Sonstiges + // Tab Sonstiges this.btnPasteFast.setSelected( EmuUtil.getBooleanProperty( props, @@ -799,6 +885,12 @@ public void updFields( Properties props ) this.fldAltBASIC.updFields( props ); } this.fldAltFont.updFields( props ); + + // Tab AutoLoad + this.tabAutoLoad.updFields( props ); + + // Tab AutoInput + this.tabAutoInput.updFields( props ); } diff --git a/src/jkcemu/emusys/zxspectrum/ZXSpectrumAudioDataStream.java b/src/jkcemu/emusys/zxspectrum/ZXSpectrumAudioDataStream.java new file mode 100644 index 0000000..02f28a7 --- /dev/null +++ b/src/jkcemu/emusys/zxspectrum/ZXSpectrumAudioDataStream.java @@ -0,0 +1,467 @@ +/* + * (c) 2014 Jens Mueller + * + * Kleincomputer-Emulator + * + * Lesen einer TZX-Datei als InputStream von Audio-Daten + */ + +package jkcemu.emusys.zxspectrum; + +import java.io.IOException; +import java.lang.*; +import jkcemu.base.*; + + +public class ZXSpectrumAudioDataStream extends EmuSysAudioDataStream +{ + private final static int SAMPLE_RATE = 44100; + private final static int T_STATES_PER_SAMPLE = 3500000 / SAMPLE_RATE; + + private boolean phase; + + + public ZXSpectrumAudioDataStream( + byte[] buf, + int offs, + int len ) throws IOException + { + super( SAMPLE_RATE, buf, offs, len ); + this.phase = false; + if( skipSourceString( FileInfo.CSW_HEADER ) ) { + processCswFile(); + } else if( skipSourceString( FileInfo.TZX_HEADER ) ) { + processTzxFile(); + } else { + processTapFile(); + } + } + + + /* --- private Methoden --- */ + + private void addPauseSamplesByMillis( int millis ) + { + if( millis > 0 ) { + addSamples( + -Math.round( (float) millis * (float) SAMPLE_RATE / 1000F ) ); + this.phase = false; + } + } + + + private void addPulseByTStates( int tStates ) + { + int v = Math.round( (float) tStates / (float) T_STATES_PER_SAMPLE ); + addSamples( this.phase ? v : -v ); + this.phase = !this.phase; + } + + + private void processCswData( + int dataLen, // -1: bis zum Ende + int compression, + boolean initialPhase ) throws IOException + { + boolean firstSample = true; + if( compression != 1 ) { + throw new IOException( + "CSW-Kompressionsmethode " + + String.valueOf( compression ) + + " nicht unterst\u00FCtzt" ); + } + while( (dataLen != 0) && sourceByteAvailable() ) { + int v = readSourceByte(); + if( dataLen > 0 ) { + --dataLen; + } + if( v == 0 ) { + v = readInt4(); + if( dataLen > 0 ) { + dataLen -= 4; + if( dataLen < 0 ) { + dataLen = 0; + } + } + } + if( firstSample ) { + if( initialPhase ) { + v = -v; + } + addSamples( v ); + firstSample = false; + } else { + addPhaseChangeSamples( v ); + } + } + } + + + private void processCswFile() throws IOException + { + int majorVersion = readSourceByte(); + readSourceByte(); // Unterversionsnummer + int sampleRate = 0; + int compression = 0; + int flags = 0; + if( majorVersion < 2 ) { + sampleRate = readWord(); + compression = readSourceByte(); + flags = readSourceByte(); + skipSourceBytes( 3 ); + } else { + sampleRate = readInt4(); + skipSourceBytes( 4 ); // Gesamtanzahl der Pulse + compression = readSourceByte(); + flags = readSourceByte(); + int nExt = readSourceByte(); + skipSourceBytes( 16 + nExt ); + } + if( sampleRate < 1 ) { + throw new IOException( + String.valueOf( compression ) + + " Hz: Ung\u00FCltige Abtastrate" ); + } + setSampleRate( sampleRate ); + processCswData( -1, compression, (flags & 0x01) != 0 ); + } + + + private void processTapBlock( + int pilotPulseLen, + int sync1PulseLen, + int sync2PulseLen, + int bit0PulseLen, + int bit1PulseLen, + int pilotPulseCnt0, + int pilotPulseCnt1, + int nBitsOfLastByte, + int pauseMillis, + int nBytes ) + { + if( nBytes > 0 ) { + int b = readSourceByte(); // Flag Byte + int pulseCnt = (b < 0x80 ? pilotPulseCnt0 : pilotPulseCnt1); + for( int i = 0; i < pulseCnt; i++ ) { + addPulseByTStates( pilotPulseLen ); + } + if( sync1PulseLen > 0 ) { + addPulseByTStates( sync1PulseLen ); + } + if( sync2PulseLen > 0 ) { + addPulseByTStates( sync2PulseLen ); + } + for(;;) { + --nBytes; + int nBits = (nBytes > 0 ? 8 : nBitsOfLastByte); + for( int i = 0; i < nBits; i++ ) { + int pulseLen = ((b & 0x80) != 0 ? bit1PulseLen : bit0PulseLen); + addPulseByTStates( pulseLen ); + addPulseByTStates( pulseLen ); + b <<= 1; + } + if( nBytes == 0 ) { + break; + } + b = readSourceByte(); + } + addPulseByTStates( 2 * bit1PulseLen ); + addPauseSamplesByMillis( pauseMillis ); + } + } + + + private void processTapBlock( + int pauseMillis, + int blockLen ) + { + processTapBlock( + 2168, + 667, + 735, + 855, + 1710, + 8063, + 3223, + 8, + pauseMillis, + blockLen ); + } + + + private void processTapFile() + { + int blockLen = readWord(); + while( sourceByteAvailable() ) { + processTapBlock( 1000, blockLen ); + blockLen = readWord(); + } + } + + + // Standard Speed Data Block + private void processTzxBlock10() + { + int pauseMillis = readWord(); + int blockLen = readWord(); + processTapBlock( + 2168, + 667, + 735, + 855, + 1710, + 8063, + 3223, + 8, + pauseMillis, + blockLen ); + } + + + // Turbo Speed Data Block + private void processTzxBlock11() + { + int pilotPulseLen = readWord(); + int sync1PulseLen = readWord(); + int sync2PulseLen = readWord(); + int bit0PulseLen = readWord(); + int bit1PulseLen = readWord(); + int pilotPulseCnt = readWord(); + int nBitsOfLastByte = readSourceByte(); + int pauseMillis = readWord(); + int blockLen = readInt3(); + processTapBlock( + pilotPulseLen, + sync1PulseLen, + sync2PulseLen, + bit0PulseLen, + bit1PulseLen, + pilotPulseCnt, + pilotPulseCnt, + nBitsOfLastByte, + pauseMillis, + blockLen ); + } + + + // Pure Tone + private void processTzxBlock12() + { + int pulseLen = readWord(); + int nPulses = readWord(); + for( int i = 0; i < nPulses; i++ ) { + addPulseByTStates( pulseLen ); + } + } + + + // Pure Sequence + private void processTzxBlock13() + { + int nPulses = readSourceByte(); + for( int i = 0; i < nPulses; i++ ) { + addPulseByTStates( readWord() ); + } + } + + + // Pure Data Block + private void processTzxBlock14() + { + int bit0PulseLen = readWord(); + int bit1PulseLen = readWord(); + int nBitsOfLastByte = readSourceByte(); + int pauseMillis = readWord(); + int blockLen = readInt3(); + processTapBlock( + 0, + 0, + 0, + bit0PulseLen, + bit1PulseLen, + 0, + 0, + nBitsOfLastByte, + pauseMillis, + blockLen ); + } + + + // Direct Recording + private void processTzxBlock15() + { + int tStatesPerSample = readWord(); + int pauseMillis = readWord(); + int nBitsOfLastByte = readSourceByte(); + int nBytes = readInt3(); + if( nBytes > 0 ) { + int sourceSamples = 0; + float tStatesFactor = (float) tStatesPerSample + / (float) T_STATES_PER_SAMPLE; + do { + int nBits = (nBytes > 1 ? 8 : Math.max( nBitsOfLastByte, 8 )); + int b = readSourceByte(); + for( int i = 0; i < nBits; i++ ) { + boolean p = ((b & 0x80) != 0); + if( (p != this.phase) && (sourceSamples > 0) ) { + int v = Math.round( (float) sourceSamples * tStatesFactor ); + addSamples( p ? v : -v ); + this.phase = p; + sourceSamples = 0; + } + sourceSamples++; + b <<= 1; + } + --nBytes; + } while( nBytes > 0 ); + if( sourceSamples > 0 ) { + int v = Math.round( (float) sourceSamples * tStatesFactor ); + addSamples( this.phase ? v : -v ); + } + addPauseSamplesByMillis( pauseMillis ); + } + } + + + // CSW Recording + private void processTzxBlock18() throws IOException + { + int blockLen = readInt4(); + int pauseMillis = readWord(); + int sampleRate = readInt3(); + if( sampleRate != SAMPLE_RATE ) { + throw new IOException( "Block-ID 18: Abtastrate " + + String.valueOf( sampleRate ) + + " Hz nicht unterst\u00FCtzt" ); + } + int compression = readSourceByte(); + skipSourceBytes( 4 ); // Gesamtanzahl Pulse + processCswData( blockLen - 10, compression, false ); + } + + + // Pause + private void processTzxBlock20() throws IOException + { + int millis = readWord(); + if( millis == 0 ) { + /* + * In dem Fall soll das Abspielen gestoppt werden, + * bis der Emulator es wieder startet. + * Da das aber bei einer Umwandlung in einen Audio-Datenstrom + * nicht moeglich ist, wird eine Pause von 3 Sekunden eingelegt. + */ + millis = 3000; + } + addPauseSamplesByMillis( millis ); + } + + + private void processTzxFile() throws IOException + { + skipSourceBytes( 2 ); // Versionsnummer ueberspringen + + // Bloecke + boolean firstBlk = true; + while( sourceByteAvailable() ) { + int blockID = readSourceByte(); + switch( blockID ) { + case 0x10: // Standard Speed Data Block + processTzxBlock10(); + break; + + case 0x11: // Turbo Speed Data Block + processTzxBlock11(); + break; + + case 0x12: // Pure Tone + processTzxBlock12(); + break; + + case 0x13: // Pure Sequence + processTzxBlock13(); + break; + + case 0x14: // Pure Data Block + processTzxBlock14(); + break; + + case 0x15: // Direct Recording + processTzxBlock15(); + break; + + case 0x18: // CSW Recording + processTzxBlock18(); + break; + + case 0x20: // Pause + processTzxBlock20(); + break; + + case 0x22: // Group End + // kein Blockinhalt + break; + + /* + * zu ueberspringende Bloecke, + * deren Blocklaenge mit einem Byte angegeben ist + */ + case 0x21: // Group Start + case 0x30: // Text Description + skipSourceBytes( readSourceByte() ); + break; + + /* + * zu ueberspringende Bloecke, + * deren Blocklaenge mit zwei Bytes angegeben ist + */ + case 0x32: // Archive Info Block + skipSourceBytes( readWord() ); + break; + + default: + throw new IOException( + String.format( + "Die TZX-Datei enth\u00E4lt mit ID %02X ein nicht" + + " unterst\u00FCtztes Blockformat.", + blockID ) ); + } + } + + // abschliessender Phasenwechsel + if( getFrameLength() > 0 ) { + addSamples( 40 ); + } + } + + + private int readInt3() + { + int b0 = readSourceByte(); + int b1 = readSourceByte(); + int b2 = readSourceByte(); + return ((b2 << 16) & 0xFF0000) | ((b1 << 8) & 0xFF00) | (b0 & 0xFF); + } + + + private int readInt4() + { + int b0 = readSourceByte(); + int b1 = readSourceByte(); + int b2 = readSourceByte(); + int b3 = readSourceByte(); + return ((b3 << 24) & 0xFF000000) + | ((b2 << 16) & 0x00FF0000) + | ((b1 << 8) & 0x0000FF00) + | (b0 & 0x000000FF); + } + + + private int readWord() + { + int b0 = readSourceByte(); + int b1 = readSourceByte(); + return ((b1 << 8) & 0xFF00) | (b0 & 0x00FF); + } +} + diff --git a/src/jkcemu/emusys/zxspectrum/ZXSpectrumSettingsFld.java b/src/jkcemu/emusys/zxspectrum/ZXSpectrumSettingsFld.java new file mode 100644 index 0000000..4ddbb39 --- /dev/null +++ b/src/jkcemu/emusys/zxspectrum/ZXSpectrumSettingsFld.java @@ -0,0 +1,125 @@ +/* + * (c) 2014-2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Komponente fuer die ZXSpectrum-Einstellungen + */ + +package jkcemu.emusys.zxspectrum; + +import java.awt.*; +import java.lang.*; +import java.util.*; +import javax.swing.*; +import jkcemu.base.*; + + +public class ZXSpectrumSettingsFld extends AbstractSettingsFld +{ + private JRadioButton btn48K; + private JRadioButton btn128K; + private ROMFileSettingsFld fldAltROM; + + + public ZXSpectrumSettingsFld( SettingsFrm settingsFrm, String propPrefix ) + { + super( settingsFrm, propPrefix ); + setLayout( new GridBagLayout() ); + + GridBagConstraints gbc = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + ButtonGroup grpModel = new ButtonGroup(); + + this.btn48K = new JRadioButton( "ZX Spectrum 48K", true ); + grpModel.add( this.btn48K ); + add( this.btn48K, gbc ); + + this.btn128K = new JRadioButton( "ZX Spectrum+ 128K", false ); + grpModel.add( this.btn128K ); + gbc.insets.top = 0; + gbc.insets.bottom = 5; + gbc.gridy++; + add( this.btn128K, gbc ); + + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.insets.top = 10; + gbc.insets.bottom = 10; + gbc.gridy++; + add( new JSeparator(), gbc ); + + this.fldAltROM = new ROMFileSettingsFld( + settingsFrm, + propPrefix + "rom.", + "Alternativer Betriebssystem-ROM:" ); + gbc.insets.top = 5; + gbc.insets.bottom = 5; + gbc.gridy++; + add( this.fldAltROM, gbc ); + + + // Listener + this.btn48K.addActionListener( this ); + this.btn128K.addActionListener( this ); + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public void applyInput( + Properties props, + boolean selected ) throws UserInputException + { + EmuUtil.setProperty( + props, + this.propPrefix + "model", + this.btn128K.isSelected() ? "128k" : "48k" ); + this.fldAltROM.applyInput( props, selected ); + } + + + @Override + protected boolean doAction( EventObject e ) + { + boolean rv = false; + Object src = e.getSource(); + if( src != null ) { + if( src instanceof AbstractButton ) { + rv = true; + fireDataChanged(); + } + } + return rv; + } + + + @Override + public void lookAndFeelChanged() + { + this.fldAltROM.lookAndFeelChanged(); + } + + + @Override + public void updFields( Properties props ) + { + if( EmuUtil.getProperty( + props, + this.propPrefix + "model" ).equals( "128k" ) ) + { + this.btn128K.setSelected( true ); + } else { + this.btn48K.setSelected( true ); + } + this.fldAltROM.updFields( props ); + } +} diff --git a/src/jkcemu/etc/CRC16.java b/src/jkcemu/etc/CRC16.java new file mode 100644 index 0000000..073f847 --- /dev/null +++ b/src/jkcemu/etc/CRC16.java @@ -0,0 +1,80 @@ +/* + * (c) 2010-2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Berechnung von CRC16 unter Angabe des Polynoms und des Startwerts + */ + +package jkcemu.etc; + +import java.lang.*; +import java.util.zip.Checksum; + + +public class CRC16 implements Checksum +{ + private int polynom; + private int initValue; + private int crcValue; + + + public CRC16( int polynom, int initValue ) + { + this.polynom = polynom; + this.initValue = initValue; + reset(); + } + + + /* + * CRC16-CCITT-Instanz mit Polynom x16 + x12 + x5 + 1 (0x1021) + * und Startwert -1 erzeugen + */ + public static CRC16 createCRC16CCITT() + { + return new CRC16( 0x1021, 0xFFFF ); + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public long getValue() + { + return this.crcValue & 0xFFFFL; + } + + + @Override + public void reset() + { + this.crcValue = this.initValue; + } + + + @Override + public void update( byte[] a, int offs, int len ) + { + while( len > 0 ) { + update( a[ offs++ ] ); + --len; + } + } + + + @Override + public void update( int b ) + { + b <<= 8; + for( int i = 0; i < 8; i++ ) { + if( ((b ^ this.crcValue) & 0x8000) != 0 ) { + this.crcValue <<= 1; + this.crcValue ^= this.polynom; + } else { + this.crcValue <<= 1; + } + b <<= 1; + } + } +} diff --git a/src/jkcemu/etc/CRC16CCITT.java b/src/jkcemu/etc/CRC16CCITT.java deleted file mode 100644 index 8c362ff..0000000 --- a/src/jkcemu/etc/CRC16CCITT.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * (c) 2010-2013 Jens Mueller - * - * Kleincomputer-Emulator - * - * Berechnung von CRC16-CCITT mit Polynom x16 + x12 + x5 + 1 - */ - -package jkcemu.etc; - -import java.lang.*; -import java.util.zip.Checksum; - - -public class CRC16CCITT implements Checksum -{ - private static final int POLYNOM = 0x1021; - - private int crc; - - - public CRC16CCITT() - { - reset(); - } - - - @Override - public long getValue() - { - return this.crc & 0xFFFFL; - } - - - @Override - public void reset() - { - this.crc = 0xFFFF; - } - - - @Override - public void update( byte[] a, int offs, int len ) - { - while( len > 0 ) { - update( a[ offs++ ] ); - --len; - } - } - - - @Override - public void update( int b ) - { - b <<= 8; - for( int i = 0; i < 8; i++ ) { - if( ((b ^ this.crc) & 0x8000) != 0 ) { - this.crc <<= 1; - this.crc ^= POLYNOM; - } else { - this.crc <<= 1; - } - b <<= 1; - } - } -} diff --git a/src/jkcemu/etc/CRTC6845.java b/src/jkcemu/etc/CRTC6845.java index 73179cb..5e522bf 100644 --- a/src/jkcemu/etc/CRTC6845.java +++ b/src/jkcemu/etc/CRTC6845.java @@ -1,9 +1,9 @@ /* - * (c) 2011 Jens Mueller + * (c) 2011-2016 Jens Mueller * * Kleincomputer-Emulator * - * Emulation der CRTC Controllers 6845 (Typ 0) + * Emulation des CRTC Controllers 6845 (Typ 0) */ package jkcemu.etc; diff --git a/src/jkcemu/etc/ChessboardFld.java b/src/jkcemu/etc/ChessboardFld.java index 0b88e78..2e996e8 100644 --- a/src/jkcemu/etc/ChessboardFld.java +++ b/src/jkcemu/etc/ChessboardFld.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2013 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -38,7 +38,7 @@ public ChessboardFld( EmuThread emuThread ) this.colorBlackSquare = new Color( 200, 150, 0 ); this.swapped = false; if( imgMap == null ) { - imgMap = new HashMap(); + imgMap = new HashMap<>(); Toolkit tk = getToolkit(); if( tk != null ) { addImage( imgMap, tk, EmuSys.Chessman.WHITE_PAWN, "pawn_w.png" ); diff --git a/src/jkcemu/etc/CksCalculator.java b/src/jkcemu/etc/CksCalculator.java index 902c5c8..be7d6bc 100644 --- a/src/jkcemu/etc/CksCalculator.java +++ b/src/jkcemu/etc/CksCalculator.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2013 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -198,7 +198,7 @@ public CksCalculator( String algorithm ) throws NoSuchAlgorithmException } else if( algorithm.equals( CKS_ADLER32 ) ) { this.checksum = new Adler32(); } else if( algorithm.equals( CKS_CRC16CCITT ) ) { - this.checksum = new CRC16CCITT(); + this.checksum = CRC16.createCRC16CCITT(); } else if( algorithm.equals( CKS_CRC32 ) ) { this.checksum = new CRC32(); } else { @@ -223,7 +223,7 @@ public String getValue() { if( this.value == null ) { if( this.checksum != null ) { - if( this.checksum instanceof CRC16CCITT ) { + if( this.checksum instanceof CRC16 ) { this.value = String.format( "%04X", this.checksum.getValue() ); } else { this.value = String.format( "%08X", this.checksum.getValue() ); diff --git a/src/jkcemu/etc/PSG8910.java b/src/jkcemu/etc/PSG8910.java index 590b2bf..53fdf1c 100644 --- a/src/jkcemu/etc/PSG8910.java +++ b/src/jkcemu/etc/PSG8910.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2012 Jens Mueller + * (c) 2011-2013 Jens Mueller * * Kleincomputer-Emulator * @@ -18,6 +18,7 @@ import java.lang.*; import java.util.Random; +import jkcemu.Main; public class PSG8910 extends Thread @@ -71,7 +72,7 @@ public interface Callback public PSG8910( int clockHz, int maxOutValue, Callback callback ) { - super( "JKCEMU PSG" ); + super( Main.getThreadGroup(), "JKCEMU PSG" ); this.clockHz = clockHz; this.callback = callback; this.sampleRate = 0; @@ -82,13 +83,12 @@ public PSG8910( int clockHz, int maxOutValue, Callback callback ) /* * Berechnung der Lautstaerkewerte, - * Laut Datenblatt YM2149 (kompatibel zu AY-3-8910) - * betraegt die Abstufung 0.841 pro Stufe. + * Laut Datenblatt AY-3-891X betraegt die Abstufung 0.707 pro Stufe. */ float normValue = 1.0F; for( int i = this.volumeValues.length - 1; i > 0; --i ) { this.volumeValues[ i ] = Math.round( normValue * (float) maxOutValue ); - normValue *= 0.841; + normValue *= 0.707; } this.volumeValues[ 0 ] = 0; reset(); diff --git a/src/jkcemu/etc/PlotterFrm.java b/src/jkcemu/etc/PlotterFrm.java index 62d1f12..4e7ef1f 100644 --- a/src/jkcemu/etc/PlotterFrm.java +++ b/src/jkcemu/etc/PlotterFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2012 Jens Mueller + * (c) 2011-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -121,7 +121,7 @@ public boolean applySettings( Properties props, boolean resizable ) this.mnuConfirmNewPage.setSelected( EmuUtil.parseBooleanProperty( props, - getSettingsPrefix() + ".confirm_new_page", + getSettingsPrefix() + "confirm_new_page", true ) ); return super.applySettings( props, resizable ); } @@ -208,7 +208,7 @@ else if( this.mnuPenThk5.isSelected() ) { else if( src == this.mnuConfirmNewPage ) { rv = true; Main.setProperty( - getSettingsPrefix() + ".confirm_new_page", + getSettingsPrefix() + "confirm_new_page", Boolean.toString( this.mnuConfirmNewPage.isSelected() ) ); } } diff --git a/src/jkcemu/etc/USBInterfaceFrm.java b/src/jkcemu/etc/USBInterfaceFrm.java index f364681..554682c 100644 --- a/src/jkcemu/etc/USBInterfaceFrm.java +++ b/src/jkcemu/etc/USBInterfaceFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2012 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -260,7 +260,16 @@ private USBInterfaceFrm( ScreenFrm screenFrm ) private void doDirSelect() { - File dirFile = DirSelectDlg.selectDirectory( this ); + File lastDir = this.dirFld.getFile(); + if( lastDir == null ) { + lastDir = Main.getLastDirFile( "usb" ); + } + if( lastDir != null ) { + if( !lastDir.isDirectory() ) { + lastDir = lastDir.getParentFile(); + } + } + File dirFile = DirSelectDlg.selectDirectory( this, lastDir ); if( dirFile != null ) { setDirFile( dirFile ); } @@ -279,6 +288,7 @@ private void setDirFile( File dirFile ) Main.setProperty( "jkcemu.usb.memstick.directory", dirFile.getPath() ); + Main.setLastFile( dirFile, "usb" ); setReadOnly( this.btnReadOnly.isSelected() ); setForceLowerCase( this.btnForceLowerCase.isSelected() ); } @@ -287,6 +297,7 @@ private void setDirFile( File dirFile ) emuSys.setUSBMemStickDirectory( null ); this.btnDirRemove.setEnabled( false ); Main.setProperty( "jkcemu.usb.memstick.directory", "" ); + Main.setLastFile( dirFile, "usb" ); } } } diff --git a/src/jkcemu/etc/VDIP.java b/src/jkcemu/etc/VDIP.java index 5d3d0b4..bc134a7 100644 --- a/src/jkcemu/etc/VDIP.java +++ b/src/jkcemu/etc/VDIP.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -22,7 +22,7 @@ import java.lang.*; import java.util.*; import jkcemu.Main; -import jkcemu.base.EmuUtil; +import jkcemu.base.*; import z80emu.*; @@ -90,6 +90,7 @@ private static enum IOCmd { // Standard-Zeitstempel entsprechend VDIP Firmware: 2004-12-04 00:00:00 private static final int DEFAULT_DATETIME_VALUE = 0x31940000; + private FileTimesViewFactory fileTimesViewFactory; private String title; private int debugLevel; private int bitMode; @@ -120,6 +121,7 @@ private static enum IOCmd { private volatile File rootDir; private File newRootDir; private Object lockObj; + private Calendar calendar; private byte[] cmdLineBytes; private int cmdLineLen; private int cmdArgPos; @@ -127,41 +129,43 @@ private static enum IOCmd { private Z80PIO pio; - public VDIP( String title ) - { - this.title = title; - this.debugLevel = 0; - this.binaryMode = true; - this.extendedMode = true; - this.forceCurTimestamp = true; - this.forceLowerCase = false; - this.readOnly = false; - this.readState = false; - this.resetState = false; - this.writeState = false; - this.writeEnabled = false; - this.fileWrite = false; - this.file = null; - this.fileMillis = null; - this.raf = null; - this.freeDiskSpace = null; - this.curDir = null; - this.rootDir = null; - this.rootDirChanged = false; - this.lockObj = new Object(); - this.cmdLineBytes = new byte[ 256 ]; - this.cmdLineLen = 0; - this.cmdArgPos = 0; - this.resultQueue = new ByteQueue( 1024 ); - this.pio = new Z80PIO( title ); - this.ioOut = null; - this.ioCmd = IOCmd.NONE; - this.ioFile = null; - this.ioFileMillis = null; - this.ioFileName = null; - this.ioCount = 0; - this.ioTaskEnabled = true; - this.ioTaskThread = new Thread( this, "JKCEMU VDIP" ); + public VDIP( FileTimesViewFactory fileTimesViewFactory, String title ) + { + this.fileTimesViewFactory = fileTimesViewFactory; + this.title = title; + this.debugLevel = 0; + this.binaryMode = true; + this.extendedMode = true; + this.forceCurTimestamp = true; + this.forceLowerCase = false; + this.readOnly = false; + this.readState = false; + this.resetState = false; + this.writeState = false; + this.writeEnabled = false; + this.fileWrite = false; + this.file = null; + this.fileMillis = null; + this.raf = null; + this.freeDiskSpace = null; + this.curDir = null; + this.rootDir = null; + this.rootDirChanged = false; + this.lockObj = new Object(); + this.calendar = Calendar.getInstance(); + this.cmdLineBytes = new byte[ 256 ]; + this.cmdLineLen = 0; + this.cmdArgPos = 0; + this.resultQueue = new ByteQueue( 1024 ); + this.pio = new Z80PIO( title ); + this.ioOut = null; + this.ioCmd = IOCmd.NONE; + this.ioFile = null; + this.ioFileMillis = null; + this.ioFileName = null; + this.ioCount = 0; + this.ioTaskEnabled = true; + this.ioTaskThread = new Thread( this, "JKCEMU VDIP" ); this.ioTaskThread.start(); this.pio.addPIOPortListener( this, Z80PIO.PortInfo.B ); @@ -239,14 +243,14 @@ public int read( int port ) int rv = -1; switch( port & 0x03 ) { case 0x00: - rv = this.pio.readPortA(); + rv = this.pio.readDataA(); if( this.debugLevel > 1 ) { System.out.printf( "VDIP: read PIO Port A: %02X\n", rv ); } break; case 0x01: - rv = this.pio.readPortB(); + rv = this.pio.readDataB(); if( this.debugLevel > 3 ) { System.out.printf( "VDIP: read PIO Port B: %02X\n", rv ); } @@ -308,7 +312,7 @@ public void write( int port, int value ) if( this.debugLevel > 1 ) { System.out.printf( "VDIP: write PIO Port A: %02X\n", value ); } - this.pio.writePortA( value ); + this.pio.writeDataA( value ); break; case 0x01: @@ -316,7 +320,7 @@ public void write( int port, int value ) if( this.debugLevel > 2 ) { System.out.printf( "VDIP: write PIO Port B: %02X\n", value ); } - this.pio.writePortB( value ); + this.pio.writeDataB( value ); int tmpValue = this.pio.fetchOutValuePortB( false ); boolean rdState = ((tmpValue & 0x04) == 0); // L-aktiv @@ -739,7 +743,7 @@ private void doCmdExecute() throws IncompleteCmdException } else if( (shortCmd == 0x12) || extCmd.equals( "FS" ) ) { execFreeSpace( false ); } else if( (shortCmd == 0x13) || extCmd.equals( "FWV" ) ) { - putString( "\rMAIN 03.68VDAPF\rRPRG 1.00R\r" ); + putString( "\rMAIN 03.69VDAPF\rRPRG 1.00R\r" ); doCmdFinish( true ); } else if( (shortCmd == 0x14) || extCmd.equals( "SBD" ) ) { putPrompt(); @@ -1158,6 +1162,11 @@ private void execDeviceSendSetupData() throws } + /* + * Kommando DIR: + * Entgegen der Dokumentation kommt bei einer geoeffneten Datei + * kein Fehler. + */ private void execDir() throws VdipException { String fName = null; @@ -1170,9 +1179,6 @@ private void execDir() throws VdipException } synchronized( this.lockObj ) { checkDisk(); - if( (this.raf != null) && this.fileWrite ) { - throwFileOpen(); - } File file = null; if( fName != null ) { file = findFile( fName ); @@ -1243,40 +1249,51 @@ private void execDirIO() throws VdipException } + /* + * Kommando DIRT: + * Entgegen der Dokumentation kommt bei einer geoeffneten Datei + * kein Fehler. + */ private void execDirT() throws VdipException { String fName = nextArgFileName( true ); synchronized( this.lockObj ) { putResultByte( '\r' ); // Leerzeile, auch bei Fehlermeldung checkDisk(); - if( (this.raf != null) && this.fileWrite ) { - throwFileOpen(); - } File file = findFile( fName ); if( file == null ) { throwCommandFailed(); } - long millis = file.lastModified(); - if( millis <= 0 ) { - throwCommandFailed(); - } - long dateTime = 0; - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis( millis ); - int year = cal.get( Calendar.YEAR ) - 1980; - if( year > 0 ) { - dateTime |= ((year << 25) & 0xFE000000L); + long creationDateTime = DEFAULT_DATETIME_VALUE; + long lastAccessDateTime = DEFAULT_DATETIME_VALUE; + long lastModifiedDateTime = DEFAULT_DATETIME_VALUE; + FileTimesView ftv = this.fileTimesViewFactory.getFileTimesView( file ); + if( ftv != null ) { + Long v = ftv.getLastModifiedMillis(); + if( v != null ) { + lastModifiedDateTime = getDateTimeByMillis( v.longValue() ); + /* + * Wenn die anderen beiden Zeitstempel nicht ermittelt + * werden konnten, + * sollen sie den Wert der letzten Aenderung haben. + */ + lastAccessDateTime = lastModifiedDateTime; + lastModifiedDateTime = lastModifiedDateTime; + } + v = ftv.getCreationMillis(); + if( v != null ) { + creationDateTime = getDateTimeByMillis( v.longValue() ); + } + v = ftv.getLastAccessMillis(); + if( v != null ) { + lastAccessDateTime = getDateTimeByMillis( v.longValue() ); + } } - dateTime |= (((cal.get( Calendar.MONTH ) + 1) << 21) & 0x01E00000L); - dateTime |= ((cal.get( Calendar.DAY_OF_MONTH ) << 16) & 0x001F0000L); - dateTime |= ((cal.get( Calendar.HOUR_OF_DAY ) << 11) & 0x0000F800L); - dateTime |= ((cal.get( Calendar.MINUTE ) << 5) & 0x000007E0L); - dateTime |= ((cal.get( Calendar.SECOND ) / 2) & 0x0000001FL); putString( fName ); putResultByte( '\u0020' ); - putNumber( dateTime, 4 ); // create date time - putNumber( dateTime >> 16, 2 ); // access date - putNumber( dateTime, 4 ); // modified date time + putNumber( creationDateTime, 4 ); + putNumber( lastAccessDateTime >> 16, 2 ); + putNumber( lastModifiedDateTime, 4 ); putResultByte( '\r' ); } doCmdFinish( true ); @@ -2027,6 +2044,26 @@ private void fireRunIOTask() } + private long getDateTimeByMillis( long millis ) + { + long v = 0; + synchronized( this.calendar ) { + this.calendar.clear(); + this.calendar.setTimeInMillis( millis ); + int year = this.calendar.get( Calendar.YEAR ) - 1980; + if( year > 0 ) { + v |= ((year << 25) & 0xFE000000L); + } + v |= (((this.calendar.get( Calendar.MONTH ) + 1) << 21) & 0x01E00000L); + v |= ((this.calendar.get( Calendar.DAY_OF_MONTH ) << 16) & 0x001F0000L); + v |= ((this.calendar.get( Calendar.HOUR_OF_DAY ) << 11) & 0x0000F800L); + v |= ((this.calendar.get( Calendar.MINUTE ) << 5) & 0x000007E0L); + v |= ((this.calendar.get( Calendar.SECOND ) / 2) & 0x0000001FL); + } + return v; + } + + private int getDebugLevel() { return this.debugLevel; @@ -2195,15 +2232,18 @@ private String nextArgFileName( boolean allowDotDirs ) throws VdipException { String rv = null; if( this.cmdArgPos >= 2 ) { - while( this.cmdArgPos < this.cmdLineLen ) { - if( this.cmdLineBytes[ this.cmdArgPos ] != (byte) 0x20 ) { - break; + // Es ist nur ein Leerzeichen vor dem Dateinamen erlaubt. + if( this.cmdArgPos < this.cmdLineLen ) { + if( this.cmdLineBytes[ this.cmdArgPos ] == (byte) 0x20 ) { + this.cmdArgPos++; } - this.cmdArgPos++; } if( this.cmdArgPos >= this.cmdLineLen ) { throwBadCommand(); } + if( this.cmdLineBytes[ this.cmdArgPos ] == (byte) 0x20 ) { + throwCommandFailed(); + } if( ((this.cmdArgPos + 1) == this.cmdLineLen) && (this.cmdLineBytes[ this.cmdArgPos ] == '.') ) { @@ -2230,7 +2270,8 @@ else if( ((this.cmdArgPos + 2) == this.cmdLineLen) int extPos = 0; boolean dot = false; while( this.cmdArgPos < this.cmdLineLen ) { - char ch = (char) ((int) this.cmdLineBytes[ this.cmdArgPos++ ] & 0xFF); + char ch = (char) ((int) this.cmdLineBytes[ this.cmdArgPos++ ] + & 0xFF); if( ch == '\u0020' ) { break; } @@ -2328,17 +2369,16 @@ private Long nextOptArgDatetimeInMillis() throws IncompleteCmdException } catch( VdipException ex ) {} if( !this.forceCurTimestamp ) { - Calendar cal = Calendar.getInstance(); - if( cal != null ) { - cal.clear(); - cal.set( + synchronized( this.calendar ) { + this.calendar.clear(); + this.calendar.set( (int) ((value >> 25) & 0x7F) + 1980, (int) ((value >> 21) & 0x0F) - 1, (int) (value >> 16) & 0x1F, (int) (value >> 11) & 0x1F, (int) (value >> 5) & 0x3F, (int) (value & 0x1F) * 2 ); - millis = new Long( cal.getTimeInMillis() ); + millis = new Long( this.calendar.getTimeInMillis() ); } } } diff --git a/src/jkcemu/filebrowser/ExtendedFileEntry.java b/src/jkcemu/filebrowser/ExtendedFileEntry.java index 1ba9801..ba29872 100644 --- a/src/jkcemu/filebrowser/ExtendedFileEntry.java +++ b/src/jkcemu/filebrowser/ExtendedFileEntry.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -92,7 +92,7 @@ public Object getInfo() { Object rv = null; if( this.tarEntry != null ) { - if( !this.tarEntry.isFile() ) { + if( !this.tarEntry.isRegularFile() ) { rv = this.tarEntry.getTypeText(); } } diff --git a/src/jkcemu/filebrowser/FileActionMngr.java b/src/jkcemu/filebrowser/FileActionMngr.java new file mode 100644 index 0000000..4242de4 --- /dev/null +++ b/src/jkcemu/filebrowser/FileActionMngr.java @@ -0,0 +1,1747 @@ +/* + * (c) 2014-2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Manager fuer Aktionen mit Dateien + */ + +package jkcemu.filebrowser; + +import java.awt.*; +import java.awt.datatransfer.*; +import java.awt.event.*; +import java.io.*; +import java.lang.*; +import java.net.*; +import java.nio.file.*; +import java.util.*; +import javax.swing.*; +import jkcemu.Main; +import jkcemu.audio.AudioPlayer; +import jkcemu.base.*; +import jkcemu.disk.*; +import jkcemu.emusys.ac1_llc2.*; +import jkcemu.emusys.kc85.KCAudioDataStream; +import jkcemu.emusys.z1013.Z1013AudioDataStream; +import jkcemu.image.ImageFrm; +import jkcemu.text.TextEditFrm; +import jkcemu.tools.fileconverter.FileConvertFrm; +import jkcemu.tools.hexdiff.HexDiffFrm; +import jkcemu.tools.hexedit.HexEditFrm; + + +public class FileActionMngr +{ + public enum FileActionResult { + NONE, + DONE, + FILE_RENAMED, + FILES_CHANGED }; + + public interface FileObject + { + public File getFile(); + public Path getPath(); + public FileCheckResult getCheckResult(); + public void setPath( Path path ); + }; + + public static final String ACTION_AUDIO_IN = "audio.in"; + public static final String ACTION_CHECKSUM = "checksum"; + public static final String ACTION_CONVERT = "convert"; + public static final String ACTION_COPY = "copy"; + public static final String ACTION_COPY_PATH = "copy.path"; + public static final String ACTION_COPY_URL = "copy.url"; + public static final String ACTION_DELETE = "delete"; + public static final String ACTION_DISK_VIEW = "disk.view"; + public static final String ACTION_EMU_LOAD_OPT = "emu.load_opt"; + public static final String ACTION_EMU_LOAD = "emu.load"; + public static final String ACTION_EMU_START = "emu.start"; + public static final String ACTION_HEX_DIFF = "hex.diff"; + public static final String ACTION_HEX_EDIT = "hex.edit"; + public static final String ACTION_IMAGE_VIEW = "image.view"; + public static final String ACTION_LAST_MODIFIED = "last_modified"; + public static final String ACTION_PACK_GZIP = "pack.gzip"; + public static final String ACTION_PACK_TAR = "pack.tar"; + public static final String ACTION_PACK_TGZ = "pack.tgz"; + public static final String ACTION_PACK_ZIP = "pack.zip"; + public static final String ACTION_PLAY = "play"; + public static final String ACTION_PLAY_AC1 = "play.ac1"; + public static final String ACTION_PLAY_AC1BASIC = "play.ac1basic"; + public static final String ACTION_PLAY_KC85 = "play.kc85"; + public static final String ACTION_PLAY_SCCH = "play.scch"; + public static final String ACTION_PLAY_Z1013 = "play.z1013"; + public static final String ACTION_PLAY_Z1013HS = "play.z1013hs"; + public static final String ACTION_PLAY_Z9001 = "play.z9001"; + public static final String ACTION_PROPERTIES = "properties"; + public static final String ACTION_RENAME = "rename"; + public static final String ACTION_RF1_LOAD = "rf1.load"; + public static final String ACTION_RF2_LOAD = "rf2.load"; + public static final String ACTION_TEXT_EDIT = "text.edit"; + public static final String ACTION_UNPACK = "unpack"; + + private BasicFrm owner; + private ScreenFrm screenFrm; + private AbstractFileWorker.PathListener pathListener; + private Map> actionCmd2Btn; + private java.util.List fileWorkers; + + + public FileActionMngr( + BasicFrm owner, + ScreenFrm screenFrm, + AbstractFileWorker.PathListener pathListener ) + { + this.owner = owner; + this.screenFrm = screenFrm; + this.pathListener = pathListener; + this.actionCmd2Btn = new HashMap<>(); + this.fileWorkers = new ArrayList<>(); + } + + + public FileActionResult actionPerformed( + String actionCmd, + java.util.List files ) + throws IOException + { + FileActionResult rv = FileActionResult.NONE; + if( (actionCmd != null) && (files != null) ) { + if( !files.isEmpty() ) { + rv = FileActionResult.DONE; + if( actionCmd.equals( ACTION_COPY_PATH ) ) { + doEditPathCopy( files ); + } else if( actionCmd.equals( ACTION_COPY_URL ) ) { + doEditURLCopy( files ); + } else if( actionCmd.equals( ACTION_COPY ) ) { + doEditFileCopy( files ); + } else if( actionCmd.equals( ACTION_EMU_LOAD_OPT ) ) { + doFileLoadIntoEmu( files, true, false ); + } else if( actionCmd.equals( ACTION_EMU_LOAD ) ) { + doFileLoadIntoEmu( files, false, false ); + } else if( actionCmd.equals( ACTION_EMU_START ) ) { + doFileLoadIntoEmu( files, false, true ); + } else if( actionCmd.equals( ACTION_RF1_LOAD ) ) { + if( this.screenFrm != null ) { + EmuThread emuThread = this.screenFrm.getEmuThread(); + if( emuThread != null ) { + doFileRAMFloppyLoad( files, emuThread.getRAMFloppy1() ); + } + } + } else if( actionCmd.equals( ACTION_RF2_LOAD ) ) { + if( this.screenFrm != null ) { + EmuThread emuThread = this.screenFrm.getEmuThread(); + if( emuThread != null ) { + doFileRAMFloppyLoad( files, emuThread.getRAMFloppy2() ); + } + } + } else if( actionCmd.equals( ACTION_TEXT_EDIT ) ) { + doFileEditText( files ); + } else if( actionCmd.equals( ACTION_IMAGE_VIEW ) ) { + doFileShowImage( files ); + } else if( actionCmd.equals( ACTION_DISK_VIEW ) ) { + doFileShowDisk( files ); + } else if( actionCmd.equals( ACTION_HEX_EDIT ) ) { + doFileEditHex( files ); + } else if( actionCmd.equals( ACTION_HEX_DIFF ) ) { + doFileDiffHex( files ); + } else if( actionCmd.equals( ACTION_CONVERT ) ) { + doFileConvert( files ); + } else if( actionCmd.equals( ACTION_AUDIO_IN ) ) { + doFileAudioIn( files ); + } else if( actionCmd.equals( ACTION_PLAY ) ) { + doFilePlay( files ); + } else if( actionCmd.equals( ACTION_PLAY_AC1 ) ) { + doFilePlayAC1( files ); + } else if( actionCmd.equals( ACTION_PLAY_AC1BASIC ) ) { + doFilePlayAC1Basic( files ); + } else if( actionCmd.equals( ACTION_PLAY_SCCH ) ) { + doFilePlaySCCH( files ); + } else if( actionCmd.equals( ACTION_PLAY_KC85 ) ) { + doFilePlayKC( files, 1 ); + } else if( actionCmd.equals( ACTION_PLAY_Z1013 ) ) { + doFilePlayZ1013( files, false ); + } else if( actionCmd.equals( ACTION_PLAY_Z1013HS ) ) { + doFilePlayZ1013( files, true ); + } else if( actionCmd.equals( ACTION_PLAY_Z9001 ) ) { + doFilePlayKC( files, 0 ); + } else if( actionCmd.equals( ACTION_PACK_TAR ) ) { + doFilePackTar( files, false ); + } else if( actionCmd.equals( ACTION_PACK_TGZ ) ) { + doFilePackTar( files, true ); + } else if( actionCmd.equals( ACTION_PACK_ZIP ) ) { + doFilePackZip( files ); + } else if( actionCmd.equals( ACTION_PACK_GZIP ) ) { + doFilePackGZip( files ); + } else if( actionCmd.equals( ACTION_UNPACK ) ) { + doFileUnpack( files ); + } else if( actionCmd.equals( ACTION_CHECKSUM ) ) { + doFileChecksum( files ); + } else if( actionCmd.equals( ACTION_LAST_MODIFIED ) ) { + if( LastModifiedDlg.open( + this.owner, + getFiles( files, false ) ) ) + { + rv = FileActionResult.FILES_CHANGED; + } + } else if( actionCmd.equals( ACTION_RENAME ) ) { + if( doFileRename( files ) ) { + rv = FileActionResult.FILE_RENAMED; + } + } else if( actionCmd.equals( ACTION_DELETE ) ) { + doFileDelete( files ); + } else if( actionCmd.equals( ACTION_PROPERTIES ) ) { + doFileProp( files ); + } else { + rv = FileActionResult.NONE; + } + } + } + return rv; + } + + + public void addCopyFileNameMenuItemsTo( JPopupMenu popup, JMenu menu ) + { + addJMenuItem( + "Vollst\u00E4ndige Datei-/Verzeichnisnamen kopieren", + ACTION_COPY_PATH, + popup, + menu ); + + addJMenuItem( + "Datei-/Verzeichnisnamen als URL kopieren", + ACTION_COPY_URL, + popup, + menu ); + } + + + public void addCopyFileMenuItemTo( JPopupMenu popup, JMenu menu ) + { + addJMenuItem( + "Dateien/Verzeichnisse kopieren", + ACTION_COPY, + popup, + menu ); + } + + + public void addFileMenuItemsTo( JPopupMenu popup, JMenu menu ) + { + addJMenuItem( + "Im Texteditor \u00F6ffnen...", + ACTION_TEXT_EDIT, + KeyEvent.VK_E, Event.CTRL_MASK, + popup, + menu ); + + addJMenuItem( + "Im Bildbetrachter anzeigen...", + ACTION_IMAGE_VIEW, + KeyEvent.VK_B, Event.CTRL_MASK, + popup, + menu ); + + addJMenuItem( + "Im Diskettenabbilddatei-Insprektor anzeigen...", + ACTION_DISK_VIEW, + popup, + menu ); + + addJMenuItem( + "Im Hex-Editor \u00F6ffnen...", + ACTION_HEX_EDIT, + popup, + menu ); + + addJMenuItem( + "Im Hex-Dateivergleicher \u00F6ffnen...", + ACTION_HEX_DIFF, + popup, + menu ); + + addJMenuItem( + "Im Dateikonverter \u00F6ffnen...", + ACTION_CONVERT, + popup, + menu ); + + addJMenuItem( + "In Audio/Kassette \u00F6ffnen...", + ACTION_AUDIO_IN, + popup, + menu ); + + addJMenuItem( + "Wiedergeben", + ACTION_PLAY, + popup, + menu ); + + JMenu menuPlayAs = null; + JMenu popupPlayAs = null; + if( menu != null ) { + menuPlayAs = new JMenu( "Wiedergeben im" ); + menu.add( menuPlayAs ); + } + if( popup != null ) { + popupPlayAs = new JMenu( "Wiedergeben im" ); + popup.add( popupPlayAs ); + } + + addJMenuItem( + "AC1-Format", + ACTION_PLAY_AC1, + null, + popupPlayAs, + menuPlayAs ); + + addJMenuItem( + "AC1-BASIC-Format", + ACTION_PLAY_AC1, + null, + popupPlayAs, + menuPlayAs ); + + addJMenuItem( + "AC1/LLC2-TurboSave-Format", + ACTION_PLAY_SCCH, + null, + popupPlayAs, + menuPlayAs ); + + addJMenuItem( + "KC-Format (HC900, KC85/2..5, KC-BASIC)", + ACTION_PLAY_KC85, + null, + popupPlayAs, + menuPlayAs ); + + addJMenuItem( + "KC-Format (KC85/1, KC87, Z9001)", + ACTION_PLAY_Z9001, + null, + popupPlayAs, + menuPlayAs ); + + addJMenuItem( + "Z1013-Format", + ACTION_PLAY_Z1013, + null, + popupPlayAs, + menuPlayAs ); + + addJMenuItem( + "Z1013-Headersave-Format", + ACTION_PLAY_Z1013HS, + null, + popupPlayAs, + menuPlayAs ); + + JMenu menuPack = null; + JMenu popupPack = null; + if( menu != null ) { + menuPack = new JMenu( "Packen in" ); + menu.add( menuPack ); + } + if( popup != null ) { + popupPack = new JMenu( "Packen in" ); + popup.add( popupPack ); + } + addJMenuItem( + "TAR-Archiv...", + ACTION_PACK_TAR, + null, + popupPack, + menuPack ); + + addJMenuItem( + "TGZ-Archiv...", + ACTION_PACK_TGZ, + null, + popupPack, + menuPack ); + + addJMenuItem( + "ZIP-Archiv...", + ACTION_PACK_ZIP, + null, + popupPack, + menuPack ); + addSeparator( null, popupPack, menuPack ); + + addJMenuItem( + "GZip-Datei...", + ACTION_PACK_GZIP, + null, + popupPack, + menuPack ); + + addJMenuItem( + "Entpacken...", + ACTION_UNPACK, + popup, + menu ); + addSeparator( popup, menu ); + + addJMenuItem( + "Pr\u00FCfsumme/Hash-Wert berechnen...", + ACTION_CHECKSUM, + popup, + menu ); + + addJMenuItem( + "\u00C4nderungszeitpunkt setzen...", + ACTION_LAST_MODIFIED, + popup, + menu ); + + addJMenuItem( + "Umbenennen...", + ACTION_RENAME, + popup, + menu ); + + addJMenuItem( + "L\u00F6schen", + ACTION_DELETE, + popup, + menu ); + addSeparator( popup, menu ); + + addJMenuItem( + "Eigenschaften...", + ACTION_PROPERTIES, + popup, + menu ); + } + + + public void addLoadIntoEmuMenuItemsTo( JPopupMenu popup, JMenu menu ) + { + addJMenuItem( + "In Emulator laden mit...", + ACTION_EMU_LOAD_OPT, + KeyEvent.VK_L, + Event.CTRL_MASK, + popup, + menu ); + + addJMenuItem( + "In Emulator laden", + ACTION_EMU_LOAD, + KeyEvent.VK_L, + Event.CTRL_MASK | Event.SHIFT_MASK, + popup, + menu ); + + addJMenuItem( + "Im Emulator starten", + ACTION_EMU_START, + KeyEvent.VK_R, + Event.CTRL_MASK, + popup, + menu ); + addSeparator( popup, menu ); + + addJMenuItem( + "In RAM-Floppy 1 laden", + ACTION_RF1_LOAD, + popup, + menu ); + + addJMenuItem( + "In RAM-Floppy 2 laden", + ACTION_RF2_LOAD, + popup, + menu ); + } + + + public JButton createEditTextButton() + { + return createAndRegisterImageButton( + "/images/file/edit.png", + "Im Texteditor \u00F6ffnen", + ACTION_TEXT_EDIT ); + } + + + public JButton createLoadIntoEmuButton() + { + return createAndRegisterImageButton( + "/images/file/load.png", + "In Emulator laden", + ACTION_EMU_LOAD ); + } + + + public JButton createPlayButton() + { + return createAndRegisterImageButton( + "/images/file/play.png", + "Wiedergeben", + ACTION_PLAY ); + } + + + public JButton createStartInEmuButton() + { + return createAndRegisterImageButton( + "/images/file/start.png", + "Im Emulator starten", + ACTION_EMU_START ); + } + + + public JButton createViewImageButton() + { + return createAndRegisterImageButton( + "/images/file/image.png", + "Im Bildbetrachter anzeigen", + ACTION_IMAGE_VIEW ); + } + + + public boolean doFileAction( FileObject fObj ) throws IOException + { + boolean done = false; + if( fObj != null ) { + File file = fObj.getFile(); + if( file != null ) { + if( file.isFile() ) { + FileCheckResult checkResult = fObj.getCheckResult(); + if( checkResult != null ) { + if( checkResult.isArchiveFile() + || checkResult.isCompressedFile() ) + { + doFileUnpack( fObj ); + done = true; + } else if( checkResult.isAudioFile() ) { + AudioPlayer.play( this.owner, file ); + done = true; + } else if( checkResult.isImageFile() ) { + ImageFrm.open( file ); + done = true; + } else if( checkResult.isTextFile() ) { + EmuThread emuThread = null; + if( this.screenFrm != null ) { + emuThread = this.screenFrm.getEmuThread(); + } + TextEditFrm.open( emuThread ).openFile( file ); + done = true; + } + } + String fName = file.getName(); + if( fName != null ) { + fName = fName.toLowerCase(); + } else { + fName = ""; + } + if( !done ) { + if( fName.endsWith( ".prj" ) ) { + Properties props = TextEditFrm.loadProject( file ); + if( props != null ) { + EmuThread emuThread = null; + if( this.screenFrm != null ) { + emuThread = this.screenFrm.getEmuThread(); + } + TextEditFrm.open( emuThread ).openProject( file, props ); + done = true; + } else { + throw new IOException( + "Die PRJ-Datei ist keine JKCEMU-Projektdatei." ); + } + } + } + if( !done && (checkResult != null) ) { + boolean loadable = false; + FileInfo fileInfo = checkResult.getFileInfo(); + if( fileInfo != null ) { + FileFormat fileFmt = fileInfo.getFileFormat(); + if( fileFmt != null ) { + if( !fileFmt.equals( FileFormat.BIN ) ) { + loadable = true; + } + } + } + if( fName.endsWith( ".bin" ) ) { + loadable = true; + } + if( loadable ) { + if( this.screenFrm != null ) { + LoadDlg.loadFile( + this.owner, + this.screenFrm, + file, + true, // interactive + true, // startEnabled + checkResult.isStartableFile() ); + done = true; + } + } + } + if( !done ) { + if( file.canRead() ) { + try { + if( Desktop.isDesktopSupported() ) { + Desktop desktop = Desktop.getDesktop(); + if( desktop != null ) { + if( desktop.isSupported( Desktop.Action.OPEN ) ) { + desktop.open( file ); + done = true; + } + } + } + } + catch( Exception ex ) { + BasicDlg.showErrorDlg( this.owner, ex ); + } + } + } + } + } + } + return done; + } + + + public java.util.List getFileWorkers() + { + return this.fileWorkers; + } + + + public void updActionButtonsEnabled( + java.util.List files ) + { + boolean isAudio = false; + boolean isDisk = false; + boolean isImage = false; + boolean isStartable = false; + boolean isUnpackable = false; + boolean isHS = false; + boolean isBin = false; + boolean isKCBasicHead = false; + boolean isKCBasic = false; + boolean isKCSys = false; + boolean isKC85Tap = false; + boolean isZ9001Tap = false; + boolean isBasic60F7 = false; + boolean stateOneEntry = false; + boolean stateOneFile = false; + boolean stateEntries = false; + boolean stateStartable = false; + boolean stateAudio = false; + boolean stateDisk = false; + boolean stateImage = false; + boolean stateText = false; + boolean stateUnpackable = false; + FileCheckResult checkResult = null; + + if( files != null ) { + int n = files.size(); + if( n == 1 ) { + FileObject fObj = files.get( 0 ); + File file = fObj.getFile(); + if( file != null ) { + stateOneFile = file.isFile(); + } + checkResult = fObj.getCheckResult(); + stateOneEntry = true; + } + if( n > 0 ) { + stateEntries = true; + } + } + if( checkResult != null ) { + isAudio = checkResult.isAudioFile(); + isDisk = (checkResult.isNonPlainDiskFile() + || checkResult.isPlainDiskFile()); + isImage = checkResult.isImageFile(); + isStartable = checkResult.isStartableFile(); + isUnpackable = (checkResult.isArchiveFile() + || checkResult.isCompressedFile() + || isDisk); + isHS = checkResult.isHeadersaveFile(); + isBin = checkResult.isBinFile(); + isKCBasicHead = checkResult.isKCBasicHeadFile(); + isKCBasic = checkResult.isKCBasicFile(); + isKCSys = checkResult.isKCSysFile(); + isKC85Tap = checkResult.isKC85TapFile(); + isZ9001Tap = checkResult.isZ9001TapFile(); + isBasic60F7 = false; + if( isHS ) { + FileInfo info = checkResult.getFileInfo(); + if( info != null ) { + if( (info.getBegAddr() == 0x60F7) + && (info.getFileType() == 'B') ) + { + isBasic60F7 = true; + } + } + } + stateStartable = (stateOneFile && isStartable); + stateAudio = (stateOneFile && isAudio); + stateDisk = (stateOneFile && isDisk); + stateImage = (stateOneFile && isImage); + stateText = (stateOneFile && !stateImage && !stateAudio); + stateUnpackable = (stateOneFile && isUnpackable); + } + + setActionBtnsEnabled( ACTION_COPY_PATH, stateEntries ); + setActionBtnsEnabled( ACTION_COPY_URL, stateEntries ); + setActionBtnsEnabled( ACTION_COPY, stateEntries ); + + setActionBtnsEnabled( ACTION_EMU_LOAD_OPT, stateOneFile ); + setActionBtnsEnabled( ACTION_EMU_LOAD, stateOneFile ); + setActionBtnsEnabled( ACTION_EMU_START, stateOneFile ); + + setActionBtnsEnabled( ACTION_RF1_LOAD, stateOneFile ); + setActionBtnsEnabled( ACTION_RF2_LOAD, stateOneFile ); + + setActionBtnsEnabled( ACTION_TEXT_EDIT, stateText ); + setActionBtnsEnabled( ACTION_IMAGE_VIEW, stateImage ); + setActionBtnsEnabled( ACTION_DISK_VIEW, stateDisk ); + setActionBtnsEnabled( ACTION_HEX_EDIT, stateOneFile ); + setActionBtnsEnabled( ACTION_HEX_DIFF, stateEntries ); + + setActionBtnsEnabled( + ACTION_AUDIO_IN, + isAudio || isKC85Tap || isZ9001Tap ); + setActionBtnsEnabled( ACTION_PLAY, stateAudio ); + setActionBtnsEnabled( ACTION_PLAY_AC1, isBin ); + setActionBtnsEnabled( ACTION_PLAY_AC1BASIC, isBasic60F7 ); + setActionBtnsEnabled( ACTION_PLAY_SCCH, isBin || isBasic60F7 ); + setActionBtnsEnabled( + ACTION_PLAY_KC85, + isKCBasicHead || isKCBasic || isKCSys || isKC85Tap ); + setActionBtnsEnabled( ACTION_PLAY_Z9001, isKCSys || isZ9001Tap ); + setActionBtnsEnabled( ACTION_PLAY_Z1013, isBin ); + setActionBtnsEnabled( ACTION_PLAY_Z1013HS, isHS ); + + setActionBtnsEnabled( ACTION_PACK_TAR, stateEntries ); + setActionBtnsEnabled( ACTION_PACK_TGZ, stateEntries ); + setActionBtnsEnabled( ACTION_PACK_ZIP, stateEntries ); + setActionBtnsEnabled( ACTION_PACK_GZIP, stateOneFile ); + setActionBtnsEnabled( ACTION_UNPACK, stateUnpackable ); + setActionBtnsEnabled( ACTION_CONVERT, stateOneFile ); + setActionBtnsEnabled( ACTION_CHECKSUM, stateEntries ); + setActionBtnsEnabled( ACTION_LAST_MODIFIED, stateEntries ); + setActionBtnsEnabled( ACTION_RENAME, stateOneEntry ); + setActionBtnsEnabled( ACTION_DELETE, stateEntries ); + setActionBtnsEnabled( ACTION_PROPERTIES, stateOneEntry ); + } + + + /* --- Aktionen --- */ + + private void doEditFileCopy( java.util.List files ) + { + try { + Toolkit tk = this.owner.getToolkit(); + if( tk != null ) { + Clipboard clipboard = tk.getSystemClipboard(); + if( clipboard != null ) { + java.util.List fileList = getFiles( files, false ); + if( !fileList.isEmpty() ) { + FileListSelection fls = new FileListSelection( fileList ); + clipboard.setContents( fls, fls ); + } + } + } + } + catch( IllegalStateException ex ) {} + } + + + private void doEditPathCopy( java.util.List files ) + { + int n = files.size(); + if( n > 0 ) { + try { + Toolkit tk = this.owner.getToolkit(); + if( tk != null ) { + Clipboard clipboard = tk.getSystemClipboard(); + if( clipboard != null ) { + boolean multi = false; + StringBuilder buf = new StringBuilder( n * 256 ); + for( FileObject fObj : files ) { + Path path = fObj.getPath(); + if( path != null ) { + String fName = path.toAbsolutePath().normalize().toString(); + if( fName != null ) { + if( !fName.isEmpty() ) { + if( buf.length() > 0 ) { + buf.append( (char) '\n' ); + multi = true; + } + buf.append( fName ); + } + } + } + } + if( buf.length() > 0 ) { + if( multi ) { + buf.append( (char) '\n' ); + } + StringSelection ss = new StringSelection( buf.toString() ); + clipboard.setContents( ss, ss ); + } + } + } + } + catch( IllegalStateException ex ) {} + } + } + + + private void doEditURLCopy( java.util.List files ) + { + int n = files.size(); + if( n > 0 ) { + try { + Toolkit tk = this.owner.getToolkit(); + if( tk != null ) { + Clipboard clipboard = tk.getSystemClipboard(); + if( clipboard != null ) { + boolean multi = false; + StringBuilder buf = new StringBuilder( n * 256 ); + for( FileObject fObj : files ) { + File file = fObj.getFile(); + if( file != null ) { + URI uri = file.toURI(); + if( uri != null ) { + URL url = uri.toURL(); + if( url != null ) { + String urlText = uri.toString(); + if( urlText != null ) { + if( !urlText.isEmpty() ) { + if( buf.length() > 0 ) { + buf.append( (char) '\n' ); + multi = true; + } + buf.append( urlText ); + } + } + } + } + } + } + if( buf.length() > 0 ) { + if( multi ) { + buf.append( (char) '\n' ); + } + StringSelection ss = new StringSelection( buf.toString() ); + clipboard.setContents( ss, ss ); + } + } + } + } + catch( IllegalStateException ex ) {} + catch( MalformedURLException ex ) { + BasicDlg.showErrorDlg( this.owner, ex ); + } + } + } + + + private void doFileAudioIn( + java.util.List files ) + { + if( (this.screenFrm != null) && (files.size() == 1) ) { + FileObject fObj = files.get( 0 ); + File file = fObj.getFile(); + FileCheckResult checkResult = fObj.getCheckResult(); + if( (file != null) && (checkResult != null) ) { + if( checkResult.isAudioFile() || checkResult.isTapeFile() ) { + this.screenFrm.openAudioInFile( file ); + } + } + } + } + + + private void doFileChecksum( + java.util.List files ) + { + java.util.List fileList = getFiles( files, true ); + if( !fileList.isEmpty() ) { + FileChecksumFrm.open( fileList ); + } + } + + + private void doFileConvert( + java.util.List files ) + { + File file = getFile( files, true ); + if( file != null ) { + if( file.isFile() ) { + FileConvertFrm.open( file ); + } + } + } + + + private void doFileDelete( + java.util.List files ) + { + java.util.List paths = getPaths( files ); + if( !paths.isEmpty() ) { + FileRemover.startRemove( + this.owner, + paths, + this.pathListener, + this.fileWorkers ); + } + } + + + private void doFileDiffHex( + java.util.List files ) + { + java.util.List fileList = getFiles( files, true ); + if( !fileList.isEmpty() ) { + HexDiffFrm.open().addFiles( fileList ); + } + } + + + private void doFileEditHex( + java.util.List files ) + { + File file = getFile( files, true ); + if( file != null ) { + if( file.isFile() ) { + HexEditFrm.open( file ); + } + } + } + + + private void doFileEditText( + java.util.List files ) + { + File file = getFile( files, true ); + if( file != null ) { + if( file.isFile() ) { + TextEditFrm.open( null ).openFile( file ); + } + } + } + + + private void doFileLoadIntoEmu( + java.util.List files, + boolean interactive, + boolean startSelected ) + { + if( this.screenFrm != null ) { + File file = getFile( files, true ); + if( file != null ) { + if( file.isFile() ) { + LoadDlg.loadFile( + this.owner, + this.screenFrm, + file, + interactive, + true, // startEnabled + startSelected ); + } + } + } + } + + + private void doFilePackGZip( + java.util.List files ) + { + File file = getFile( files, true ); + if( file != null ) { + String fileName = file.getName(); + if( fileName != null ) { + fileName += ".gz"; + } + File outFile = askForOutputFile( + file, + "GZip-Datei speichern", + fileName ); + if( outFile != null ) { + GZipPacker.packFile( this.owner, file, outFile ); + } + } + } + + + private void doFilePackTar( + java.util.List files, + boolean compression ) + { + java.util.List paths = getPaths( files ); + if( !paths.isEmpty() ) { + Path firstPath = paths.get( 0 ); + Path namePath = firstPath.getFileName(); + if( namePath != null ) { + String fileName = namePath.toString(); + if( fileName != null ) { + int pos = fileName.indexOf( '.' ); + if( (pos == 0) && (fileName.length() > 1) ) { + pos = fileName.indexOf( '.', 1 ); + } + if( pos >= 0 ) { + fileName = fileName.substring( 0, pos ); + } + if( !fileName.isEmpty() ) { + if( compression ) { + fileName += ".tgz"; + } else { + fileName += ".tar"; + } + } + try { + File outFile = askForOutputFile( + firstPath.toFile(), + compression ? + "TGZ-Datei speichern" + : "TAR-Datei speichern", + fileName ); + if( outFile != null ) { + TarPacker.packFiles( this.owner, paths, outFile, compression ); + } + } + catch( UnsupportedOperationException ex ) {} + } + } + } + } + + + private void doFilePackZip( + java.util.List files ) + { + java.util.List paths = getPaths( files ); + if( !paths.isEmpty() ) { + Path firstPath = paths.get( 0 ); + Path namePath = firstPath.getFileName(); + if( namePath != null ) { + String fileName = namePath.toString(); + if( fileName != null ) { + int pos = fileName.indexOf( '.' ); + if( (pos == 0) && (fileName.length() > 1) ) { + pos = fileName.indexOf( '.', 1 ); + } + if( pos >= 0 ) { + fileName = fileName.substring( 0, pos ); + } + if( !fileName.isEmpty() ) { + fileName += ".zip"; + } + try { + File outFile = askForOutputFile( + firstPath.toFile(), + "ZIP-Datei speichern", + fileName ); + if( outFile != null ) { + ZipPacker.packFiles( this.owner, paths, outFile ); + } + } + catch( UnsupportedOperationException ex ) {} + } + } + } + } + + + private void doFilePlay( + java.util.List files ) + { + if( files.size() == 1 ) { + FileObject fObj = files.get( 0 ); + File file = fObj.getFile(); + FileCheckResult checkResult = fObj.getCheckResult(); + if( (file != null) && (checkResult != null) ) { + if( checkResult.isAudioFile() ) { + AudioPlayer.play( this.owner, file ); + } + } + } + } + + + private void doFilePlayAC1( + java.util.List files ) + throws IOException + { + if( files.size() == 1 ) { + FileObject fObj = files.get( 0 ); + File file = fObj.getFile(); + FileCheckResult checkResult = fObj.getCheckResult(); + if( (file != null) && (checkResult != null) ) { + if( checkResult.isBinFile() ) { + String title = "AC1-Wiedergabe von " + file.getName(); + ReplyFileHeadDlg dlg = new ReplyFileHeadDlg( + this.owner, + file.getName(), + "Wiedergeben", + title, + ReplyFileHeadDlg.Option.BEGIN_ADDRESS, + ReplyFileHeadDlg.Option.START_ADDRESS, + ReplyFileHeadDlg.Option.FILE_NAME_16 ); + dlg.setVisible( true ); + if( dlg.wasApproved() ) { + int startAddr = dlg.getApprovedStartAddress(); + if( startAddr < 0 ) { + startAddr = 0; + } + AudioPlayer.play( + this.owner, + new AC1AudioDataStream( + false, + EmuUtil.readFile( file, true, 0x10000 ), + dlg.getApprovedFileName(), + dlg.getApprovedBeginAddress(), + startAddr ), + title + "..." ); + } + } + } + } + } + + + private void doFilePlayAC1Basic( + java.util.List files ) + throws IOException + { + if( files.size() == 1 ) { + FileObject fObj = files.get( 0 ); + File file = fObj.getFile(); + FileCheckResult checkResult = fObj.getCheckResult(); + if( (file != null) && (checkResult != null) ) { + if( checkResult.isBinFile() || checkResult.isHeadersaveFile() ) { + byte[] buf = EmuUtil.readFile( file, true, 0x10000 ); + int len = buf.length; + if( len > 0 ) { + String fileName = file.getName(); + int offs = 0; + if( len > 32 ) { + if( (buf[ 13 ] == (byte) 0xD3) + && (buf[ 14 ] == (byte) 0xD3) + && (buf[ 15 ] == (byte) 0xD3) ) + { + offs += 32; + len -= 32; + String s = EmuUtil.extractSingleAsciiLine( buf, 16, 16 ); + if( s != null ) { + fileName = s; + } + } + } + String title = "AC1-BASIC-Wiedergabe von " + file.getName(); + ReplyFileHeadDlg dlg = new ReplyFileHeadDlg( + this.owner, + fileName, + "Wiedergeben", + title, + ReplyFileHeadDlg.Option.FILE_NAME_6 ); + dlg.setVisible( true ); + if( dlg.wasApproved() ) { + AudioPlayer.play( + this.owner, + new AC1AudioDataStream( + true, + buf, + offs, + len, + dlg.getApprovedFileName(), + -1, + -1 ), + title + "..." ); + } + } + } + } + } + } + + + private void doFilePlayKC( + java.util.List files, + int firstBlkNum ) + throws IOException + { + if( files.size() == 1 ) { + FileObject fObj = files.get( 0 ); + File file = fObj.getFile(); + FileCheckResult checkResult = fObj.getCheckResult(); + if( (file != null) && (checkResult != null) ) { + String title = "KC-Wiedergabe von " + file.getName() + "..."; + if( checkResult.isKCBasicHeadFile() ) { + AudioPlayer.play( + this.owner, + new KCAudioDataStream( + false, + 1, + EmuUtil.readFile( file, true, 0x10000 ) ), + title ); + } + else if( checkResult.isKCBasicFile() ) { + byte[] fileBytes = EmuUtil.readFile( file, true, 0x10000 ); + if( fileBytes != null ) { + if( fileBytes.length > 0 ) { + ReplyFileHeadDlg dlg = new ReplyFileHeadDlg( + this.owner, + file.getName(), + "Wiedergeben", + title, + ReplyFileHeadDlg.Option.FILE_NAME_8 ); + dlg.setVisible( true ); + if( dlg.wasApproved() ) { + String name = dlg.getApprovedFileName(); + byte[] buf = new byte[ fileBytes.length + 11 ]; + int dst = 0; + buf[ dst++ ] = (byte) 0xD3; + buf[ dst++ ] = (byte) 0xD3; + buf[ dst++ ] = (byte) 0xD3; + if( name != null ) { + int len = name.length(); + int src = 0; + while( (dst < 11) && (src < len) ) { + buf[ dst++ ] = (byte) (name.charAt( src++ ) & 0x7F); + } + } + while( dst < 11 ) { + buf[ dst++ ] = (byte) 0x20; + } + AudioPlayer.play( + this.owner, + new KCAudioDataStream( false, 1, buf ), + title ); + } + } + } + } + else if( checkResult.isKCSysFile() ) { + AudioPlayer.play( + this.owner, + new KCAudioDataStream( + false, + firstBlkNum, + EmuUtil.readFile( file, true, 0x10000 ) ), + title ); + } + else if( (checkResult.isKC85TapFile() && (firstBlkNum == 1)) + || (checkResult.isZ9001TapFile() && (firstBlkNum == 0)) ) + { + AudioPlayer.play( + this.owner, + new KCAudioDataStream( + true, + 0, + EmuUtil.readFile( file, true, 0x10110 ) ), + title ); + } + } + } + } + + + private void doFilePlaySCCH( + java.util.List files ) + throws IOException + { + if( files.size() == 1 ) { + FileObject fObj = files.get( 0 ); + File file = fObj.getFile(); + FileCheckResult checkResult = fObj.getCheckResult(); + if( (file != null) && (checkResult != null) ) { + if( checkResult.isBinFile() || checkResult.isHeadersaveFile() ) { + byte[] buf = EmuUtil.readFile( file, true, 0x10000 ); + int len = buf.length; + if( len > 0 ) { + ReplyFileHeadDlg.Option[] options = null; + String fileName = file.getName(); + int offs = 0; + int begAddr = -1; + int endAddr = -1; + int fType = -1; + if( len > 32 ) { + if( (buf[ 13 ] == (byte) 0xD3) + && (buf[ 14 ] == (byte) 0xD3) + && (buf[ 15 ] == (byte) 0xD3) ) + { + offs += 32; + len -= 32; + begAddr = EmuUtil.getWord( buf, 0 ); + endAddr = EmuUtil.getWord( buf, 2 ); + if( (begAddr == 0x60F7) && (buf[ 12 ] == (byte) 'B') ) { + fType = 'B'; + options = new ReplyFileHeadDlg.Option[] { + ReplyFileHeadDlg.Option.FILE_NAME_16 }; + } else { + options = new ReplyFileHeadDlg.Option[] { + ReplyFileHeadDlg.Option.FILE_NAME_16, + ReplyFileHeadDlg.Option.SCCH_FILE_TYPE }; + } + String s = EmuUtil.extractSingleAsciiLine( buf, 16, 16 ); + if( s != null ) { + fileName = s; + } + } + } + if( options == null ) { + options = new ReplyFileHeadDlg.Option[] { + ReplyFileHeadDlg.Option.BEGIN_ADDRESS, + ReplyFileHeadDlg.Option.END_ADDRESS, + ReplyFileHeadDlg.Option.FILE_NAME_16, + ReplyFileHeadDlg.Option.SCCH_FILE_TYPE }; + } + String title = "AC1/LLC2-TurboSave-Wiedergabe von " + + file.getName(); + ReplyFileHeadDlg dlg = new ReplyFileHeadDlg( + this.owner, + fileName, + "Wiedergeben", + title, + options ); + dlg.setVisible( true ); + if( dlg.wasApproved() ) { + if( begAddr < 0 ) { + begAddr = dlg.getApprovedBeginAddress(); + } + if( endAddr < 0 ) { + endAddr = dlg.getApprovedEndAddress(); + if( endAddr < 0 ) { + endAddr = begAddr + len - 1; + } + } + if( fType < 0 ) { + fType = dlg.getApprovedSCCHFileType(); + } + AudioPlayer.play( + this.owner, + new SCCHAudioDataStream( + buf, + offs, + len, + dlg.getApprovedFileName(), + (char) fType, + begAddr, + endAddr ), + title + "..." ); + } + } + } + } + } + } + + + private void doFilePlayZ1013( + java.util.List files, + boolean headersave ) + throws IOException + { + if( files.size() == 1 ) { + FileObject fObj = files.get( 0 ); + File file = fObj.getFile(); + FileCheckResult checkResult = fObj.getCheckResult(); + if( (file != null) && (checkResult != null) ) { + if( checkResult.isBinFile() + || (headersave && checkResult.isHeadersaveFile()) ) + { + AudioPlayer.play( + this.owner, + new Z1013AudioDataStream( + headersave, + EmuUtil.readFile( file, true, 0x10020 ) ), + String.format( + "Z1013%s-Wiedergabe von %s...", + headersave ? "-Headersave" : "", + file.getName() ) ); + } + } + } + } + + + private void doFileProp( + java.util.List files ) + throws IOException + { + Path path = getPath( files ); + if( path != null ) { + (new FilePropDlg( this.owner, path )).setVisible( true ); + } + } + + + private void doFileRAMFloppyLoad( + java.util.List files, + RAMFloppy ramFloppy ) + { + File file = getFile( files, true ); + if( file != null ) { + try { + ramFloppy.load( file ); + Main.setLastFile( file, "ramfloppy" ); + } + catch( IOException ex ) { + BasicDlg.showErrorDlg( + this.owner, + "Die RAM-Floppy kann nicht geladen werden.\n\n" + + ex.getMessage() ); + } + } + } + + + private boolean doFileRename( + java.util.List files ) + { + boolean rv = false; + if( files.size() == 1 ) { + FileObject fObj = files.get( 0 ); + Path path = fObj.getPath(); + if( path != null ) { + path = EmuUtil.renamePath( this.owner, path ); + if( path != null ) { + fObj.setPath( path ); + rv = true; + } + } + } + return rv; + } + + + private void doFileShowDisk( + java.util.List files ) + { + if( files.size() == 1 ) { + FileObject fObj = files.get( 0 ); + File file = fObj.getFile(); + FileCheckResult checkResult = fObj.getCheckResult(); + if( (file != null) && (checkResult != null) ) { + if( checkResult.isNonPlainDiskFile() + || checkResult.isPlainDiskFile() ) + { + DiskImgViewFrm.open( file ); + } + } + } + } + + + private void doFileShowImage( + java.util.List files ) + { + if( files.size() == 1 ) { + FileObject fObj = files.get( 0 ); + File file = fObj.getFile(); + FileCheckResult checkResult = fObj.getCheckResult(); + if( (file != null) && (checkResult != null) ) { + if( checkResult.isImageFile() ) { + ImageFrm.open( file ); + } + } + } + } + + + private void doFileUnpack( FileObject fObj ) throws IOException + { + if( fObj != null ) { + File file = fObj.getFile(); + if( file != null ) { + String fileName = file.getName(); + if( (fileName != null) && file.isFile() ) { + String upperName = fileName.toUpperCase(); + if( upperName.endsWith( ".GZ" ) ) { + File outFile = askForOutputFile( + file, + "Entpackte Datei speichern", + fileName.substring( 0, fileName.length() - 3 ) ); + if( outFile != null ) { + GZipUnpacker.unpackFile( this.owner, file, outFile ); + } + } + else if( upperName.endsWith( ".TAR" ) + || upperName.endsWith( ".TGZ" ) ) + { + File outDir = EmuUtil.askForOutputDir( + this.owner, + file, + "Entpacken nach:", + "Archiv-Datei entpacken" ); + if( outDir != null ) { + TarUnpacker.unpackFile( + this.owner, + file, + outDir, + upperName.endsWith( ".TGZ" ) ); + } + } + else if( upperName.endsWith( ".JAR" ) + || upperName.endsWith( ".ZIP" ) ) + { + File outDir = EmuUtil.askForOutputDir( + this.owner, + file, + "Entpacken nach:", + "Archiv-Datei entpacken" ); + if( outDir != null ) { + ZipUnpacker.unpackFile( this.owner, file, outDir ); + } + } else { + FileCheckResult checkResult = fObj.getCheckResult(); + if( checkResult != null ) { + if( checkResult.isPlainDiskFile() ) { + DiskUtil.unpackPlainDiskFile( this.owner, file ); + } + else if( checkResult.isNonPlainDiskFile() ) { + AbstractFloppyDisk disk = DiskUtil.readNonPlainDiskFile( + this.owner, + file, + true ); + if( disk != null ) { + if( DiskUtil.checkAndConfirmWarning( this.owner, disk ) ) { + DiskUtil.unpackDisk( this.owner, file, disk, true ); + } + } + } + } + } + } + } + } + } + + + private void doFileUnpack( + java.util.List files ) + throws IOException + { + if( files.size() == 1 ) { + doFileUnpack( files.get( 0 ) ); + } + } + + + /* --- private Methoden --- */ + + private File askForOutputFile( + File srcFile, + String title, + String presetName ) + { + File preSelection = null; + File parentFile = srcFile.getParentFile(); + if( presetName != null ) { + if( parentFile != null ) { + preSelection = new File( parentFile, presetName ); + } else { + preSelection = new File( presetName ); + } + } else { + preSelection = parentFile; + } + File file = EmuUtil.showFileSaveDlg( this.owner, title, preSelection ); + if( file != null ) { + if( file.exists() ) { + if( file.equals( srcFile ) ) { + BasicDlg.showErrorDlg( + this.owner, + "Die Ausgabedatei kann nicht\n" + + "mit der Quelldatei identisch sein." ); + file = null; + } + else if( !file.isFile() ) { + BasicDlg.showErrorDlg( + this.owner, + file.getPath() + " existiert bereits\n" + + "und kann nicht als Datei angelegt werden." ); + file = null; + } + } + } + return file; + } + + + private void addJMenuItem( + String text, + String actionCmd, + JPopupMenu popup, + JMenu... menus ) + { + addJMenuItem( text, actionCmd, null, popup, menus ); + } + + + private void addJMenuItem( + String text, + String actionCmd, + int keyCode, + int modifiers, + JPopupMenu popup, + JMenu... menus ) + { + addJMenuItem( + text, + actionCmd, + KeyStroke.getKeyStroke( keyCode, modifiers ), + popup, + menus ); + } + + + private void addJMenuItem( + String text, + String actionCmd, + KeyStroke keyStroke, + JPopupMenu popup, + JMenu... menus ) + { + if( popup != null ) { + popup.add( createAndRegisterJMenuItem( text, actionCmd, keyStroke ) ); + } + if( menus != null ) { + for( JMenu menu : menus ) { + if( menu != null ) { + menu.add( + createAndRegisterJMenuItem( text, actionCmd, keyStroke ) ); + } + } + } + } + + + private static void addSeparator( JPopupMenu popup, JMenu... menus ) + { + if( popup != null ) { + popup.addSeparator(); + } + if( menus != null ) { + for( JMenu menu : menus ) { + if( menu != null ) { + menu.addSeparator(); + } + } + } + } + + + private JButton createAndRegisterImageButton( + String resource, + String text, + String actionCmd ) + { + JButton button = EmuUtil.createImageButton( this.owner, resource, text ); + button.setActionCommand( actionCmd ); + button.addActionListener( this.owner ); + registerButton( button, actionCmd ); + return button; + } + + + private JMenuItem createAndRegisterJMenuItem( + String text, + String actionCmd, + KeyStroke keyStroke ) + { + JMenuItem item = new JMenuItem( text ); + if( keyStroke != null ) { + item.setAccelerator( keyStroke ); + } + item.setActionCommand( actionCmd ); + item.addActionListener( this.owner ); + registerButton( item, actionCmd ); + return item; + } + + + private File getFile( + java.util.List files, + boolean regularFileOnly ) + { + File file = null; + if( files.size() == 1 ) { + file = files.get( 0 ).getFile(); + if( file != null ) { + if( regularFileOnly && !file.isFile() ) { + file = null; + } + } + } + return file; + } + + + private java.util.List getFiles( + java.util.List files, + boolean regularFilesOnly ) + { + int n = files.size(); + java.util.List rv = new ArrayList<>( n > 0 ? n : 1 ); + for( FileObject o : files ) { + File file = o.getFile(); + if( file != null ) { + if( !regularFilesOnly || file.isFile() ) { + rv.add( file ); + } + } + } + return rv; + } + + + private Path getPath( java.util.List files ) + { + Path path = null; + if( files.size() == 1 ) { + path = files.get( 0 ).getPath(); + } + return path; + } + + + private java.util.List getPaths( + java.util.List files ) + { + int n = files.size(); + java.util.List rv = new ArrayList<>( n > 0 ? n : 1 ); + for( FileObject o : files ) { + Path path = o.getPath(); + if( path != null ) { + rv.add( path ); + } + } + return rv; + } + + + private void registerButton( AbstractButton button, String actionCmd ) + { + Collection c = this.actionCmd2Btn.get( actionCmd ); + if( c == null ) { + c = new ArrayList<>(); + this.actionCmd2Btn.put( actionCmd, c ); + } + c.add( button ); + } + + + private void setActionBtnsEnabled( String actionCmd, boolean state ) + { + Collection c = this.actionCmd2Btn.get( actionCmd ); + if( c != null ) { + for( AbstractButton b : c ) { + b.setEnabled( state ); + } + } + } +} diff --git a/src/jkcemu/filebrowser/FileBrowserFrm.java b/src/jkcemu/filebrowser/FileBrowserFrm.java index d82c108..e7a3c00 100644 --- a/src/jkcemu/filebrowser/FileBrowserFrm.java +++ b/src/jkcemu/filebrowser/FileBrowserFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -15,6 +15,7 @@ import java.io.*; import java.lang.*; import java.net.*; +import java.nio.file.*; import java.util.*; import java.util.zip.*; import javax.sound.sampled.*; @@ -26,64 +27,40 @@ import jkcemu.audio.*; import jkcemu.base.*; import jkcemu.disk.*; -import jkcemu.emusys.ac1_llc2.*; -import jkcemu.emusys.kc85.KCAudioDataStream; -import jkcemu.emusys.z1013.Z1013AudioDataStream; -import jkcemu.image.ImageFrm; -import jkcemu.text.TextEditFrm; -import jkcemu.tools.fileconverter.FileConvertFrm; -import jkcemu.tools.hexdiff.HexDiffFrm; -import jkcemu.tools.hexedit.HexEditFrm; - - -public class FileBrowserFrm extends BasicFrm - implements - DragGestureListener, - FlavorListener, - FocusListener, - ListSelectionListener, - TreeSelectionListener, - TreeWillExpandListener + + +public class FileBrowserFrm + extends BasicFrm + implements + AbstractFileWorker.PathListener, + DragGestureListener, + DragSourceListener, + DropTargetListener, + FlavorListener, + FocusListener, + ListSelectionListener, + TreeSelectionListener, + TreeWillExpandListener { private static FileBrowserFrm instance = null; + private static final String PROP_SHOW_HIDDEN_FILES + = "jkcemu.filebrowser.show_hidden_files"; + private static final String PROP_SORT_CASE_SENSITIVE + = "jkcemu.filebrowser.sort_case_sensitive"; + private static final String PROP_PREVIEW_MAX_FILE_SIZE + = "jkcemu.filebrowser.preview.max_file_size"; + private static final String PROP_SPLIT_POSITION + = "jkcemu.filebrowser.split.position"; + private ScreenFrm screenFrm; - private JMenuItem mnuFileLoadIntoEmuOpt; - private JMenuItem mnuFileLoadIntoEmu; - private JMenuItem mnuFileStartInEmu; - private JMenuItem mnuFileEditText; - private JMenuItem mnuFileEditHex; - private JMenuItem mnuFileDiffHex; - private JMenuItem mnuFileConvert; - private JMenuItem mnuFileUnpack; - private JMenuItem mnuFilePackGZip; - private JMenuItem mnuFilePackZip; - private JMenuItem mnuFilePackTar; - private JMenuItem mnuFilePackTgz; - private JMenuItem mnuFileAudioIn; - private JMenuItem mnuFilePlay; - private JMenuItem mnuFilePlayAC1; - private JMenuItem mnuFilePlayAC1Basic; - private JMenuItem mnuFilePlaySCCH; - private JMenuItem mnuFilePlayKC85; - private JMenuItem mnuFilePlayZ1013; - private JMenuItem mnuFilePlayZ1013HS; - private JMenuItem mnuFilePlayZ9001; - private JMenuItem mnuFileShowImage; - private JMenuItem mnuFileRAMFloppy1Load; - private JMenuItem mnuFileRAMFloppy2Load; - private JMenuItem mnuFileChecksum; + private FileActionMngr fileActionMngr; private JMenuItem mnuFileCreateDir; - private JMenuItem mnuFileRename; - private JMenuItem mnuFileDelete; - private JMenuItem mnuFileLastModified; - private JMenuItem mnuFileProp; + private JMenuItem mnuFileFind; private JMenuItem mnuFileRefresh; private JMenuItem mnuFileClose; - private JMenuItem mnuEditPathCopy; - private JMenuItem mnuEditURLCopy; - private JMenuItem mnuEditFileCopy; - private JMenuItem mnuEditFilePaste; + private JMenuItem mnuEditCut; + private JMenuItem mnuEditPaste; private JCheckBoxMenuItem mnuHiddenFiles; private JCheckBoxMenuItem mnuSortCaseSensitive; private JRadioButtonMenuItem mnuNoPreview; @@ -94,54 +71,22 @@ public class FileBrowserFrm extends BasicFrm private JRadioButtonMenuItem mnuPreviewNoFileSizeLimit; private JMenuItem mnuHelpContent; private JPopupMenu mnuPopup; - private JMenuItem mnuPopupLoadIntoEmuOpt; - private JMenuItem mnuPopupLoadIntoEmu; - private JMenuItem mnuPopupStartInEmu; - private JMenuItem mnuPopupEditText; - private JMenuItem mnuPopupEditHex; - private JMenuItem mnuPopupDiffHex; - private JMenuItem mnuPopupConvert; - private JMenuItem mnuPopupAudioIn; - private JMenuItem mnuPopupPlay; - private JMenuItem mnuPopupPlayAC1; - private JMenuItem mnuPopupPlayAC1Basic; - private JMenuItem mnuPopupPlaySCCH; - private JMenuItem mnuPopupPlayKC85; - private JMenuItem mnuPopupPlayZ1013; - private JMenuItem mnuPopupPlayZ1013HS; - private JMenuItem mnuPopupPlayZ9001; - private JMenuItem mnuPopupUnpack; - private JMenuItem mnuPopupPackGZip; - private JMenuItem mnuPopupPackZip; - private JMenuItem mnuPopupPackTar; - private JMenuItem mnuPopupPackTgz; - private JMenuItem mnuPopupShowImage; - private JMenuItem mnuPopupRAMFloppy1Load; - private JMenuItem mnuPopupRAMFloppy2Load; - private JMenuItem mnuPopupChecksum; - private JMenuItem mnuPopupPathCopy; - private JMenuItem mnuPopupURLCopy; - private JMenuItem mnuPopupFileCopy; - private JMenuItem mnuPopupFilePaste; + private JMenuItem mnuPopupCut; + private JMenuItem mnuPopupPaste; private JMenuItem mnuPopupCreateDir; - private JMenuItem mnuPopupRename; - private JMenuItem mnuPopupDelete; - private JMenuItem mnuPopupLastModified; - private JMenuItem mnuPopupProp; - private JButton btnLoadIntoEmu; - private JButton btnStartInEmu; - private JButton btnEditText; - private JButton btnShowImage; - private JButton btnPlay; + private JMenuItem mnuPopupFind; private JSplitPane splitPane; private JTable table; private JTree tree; private DefaultTreeModel treeModel; + private int[] treeDragSelectionRows; private FileNode rootNode; private FilePreviewFld filePreviewFld; private Component lastActiveFld; private Clipboard clipboard; - private boolean filePasteState; + private ArrayList cutFiles; + private boolean ignoreFlavorEvent; + private boolean pasteState; public static void open( ScreenFrm screenFrm ) @@ -158,31 +103,62 @@ public static void open( ScreenFrm screenFrm ) } - public void fireDirectoryChanged( final File dirFile ) + public static void fireFileChanged( final Object file ) { - EventQueue.invokeLater( - new Runnable() - { - @Override - public void run() - { - refreshNodeFor( dirFile ); - } - } ); + if( file != null ) { + try { + Path path = null; + if( file instanceof Path ) { + path = (Path) file; + } else if( file instanceof File ) { + path = ((File) file).toPath(); + } + if( path != null ) { + if( !Files.exists( path, LinkOption.NOFOLLOW_LINKS ) + || !Files.isDirectory( path, LinkOption.NOFOLLOW_LINKS ) ) + { + path = path.getParent(); + } + } + if( path != null ) { + Frame[] frms = Frame.getFrames(); + if( frms != null ) { + for( final Frame f : frms ) { + if( f instanceof FileBrowserFrm ) { + ((FileBrowserFrm) f).fireRefreshNodeFor( path ); + } + } + } + } + } + catch( InvalidPathException ex ) {} + } } - public void showErrorMsg( final String msg ) + public static void fireFilesChanged( Collection files ) { - EventQueue.invokeLater( - new Runnable() - { - @Override - public void run() - { - showErrorMsgInternal( msg ); - } - } ); + if( files != null ) { + for( Object f : files ) { + fireFileChanged( f ); + } + } + } + + + /* --- AbstractFileWorker.PathListener --- */ + + @Override + public void pathsPasted( Set paths ) + { + fireRefreshParentNodesFor( paths ); + } + + + @Override + public void pathsRemoved( Set paths ) + { + fireRefreshParentNodesFor( paths ); } @@ -191,25 +167,311 @@ public void run() @Override public void dragGestureRecognized( DragGestureEvent e ) { - Collection files = getSelectedFiles(); - if( files != null ) { - if( !files.isEmpty() ) { + Collection fObjs = getSelectedFileObjects(); + if( fObjs != null ) { + int n = fObjs.size(); + if( n > 0 ) { + Collection files = new ArrayList<>( n ); + for( FileActionMngr.FileObject fObj : fObjs ) { + File file = fObj.getFile(); + if( file != null ) { + files.add( file ); + } + } + if( !files.isEmpty() ) { + this.treeDragSelectionRows = this.tree.getSelectionRows(); + try { + e.startDrag( null, new FileListSelection( files ), this ); + } + catch( InvalidDnDOperationException ex ) {} + } + } + } + } + + + /* --- DragSourceListener --- */ + + @Override + public void dragDropEnd( DragSourceDropEvent e ) + { + this.treeDragSelectionRows = null; + if( (e.getDropAction() & DnDConstants.ACTION_MOVE) != 0 ) { + DragSourceContext context = e.getDragSourceContext(); + if( context != null ) { try { - e.startDrag( null, new FileListSelection( files ) ); + Transferable t = context.getTransferable(); + if( t != null ) { + if( t.isDataFlavorSupported( DataFlavor.javaFileListFlavor ) ) { + Object o = t.getTransferData( DataFlavor.javaFileListFlavor ); + if( o != null ) { + if( o instanceof Collection ) { + fireRefreshParentNodesFor( (Collection) o ); + } + } + } + } + } + catch( IOException ex ) {} + catch( UnsupportedFlavorException ex ) {} + } + } + } + + + @Override + public void dragEnter( DragSourceDragEvent e ) + { + // leer + } + + + @Override + public void dragExit( DragSourceEvent e ) + { + /* + * Da die JTree-Komponente selbst auch ein DropTarget ist, + * wird die Methode aufgerufen, + * wenn man etwas aus der Komponente herauszieht. + */ + if( this.treeDragSelectionRows != null ) + this.tree.setSelectionRows( this.treeDragSelectionRows ); + } + + + @Override + public void dragOver( DragSourceDragEvent e ) + { + // leer + } + + + @Override + public void dropActionChanged( DragSourceDragEvent e ) + { + // leer + } + + + /* --- DropTargetListener --- */ + + @Override + public void dragEnter( DropTargetDragEvent e ) + { + dragInternal( e, true ); + } + + + @Override + public void dragExit( DropTargetEvent e ) + { + // leer + } + + + @Override + public void dragOver( DropTargetDragEvent e ) + { + dragInternal( e, false ); + } + + + @Override + public void drop( DropTargetDropEvent e ) + { + Path dstPath = null; + TreeNode treeNode = null; + int action = e.getDropAction(); + if( ((action == DnDConstants.ACTION_COPY) + || (action == DnDConstants.ACTION_MOVE)) + && ((action & e.getSourceActions()) != 0) + && e.isDataFlavorSupported( DataFlavor.javaFileListFlavor ) ) + { + treeNode = getSelectedFileNode(); + while( treeNode != null ) { + if( treeNode instanceof FileTreeNode ) { + Path path = ((FileTreeNode) treeNode).getPath(); + if( path != null ) { + if( Files.isDirectory( path, LinkOption.NOFOLLOW_LINKS ) ) { + dstPath = path; + break; + } + } + } + treeNode = treeNode.getParent(); + } + } + if( (dstPath != null) && (treeNode != null) ) { + boolean success = false; + try { + + /* + * Es wird hier bewusst ACTION_COPY_OR_MOVE akzeptiert + * und nicht nur ACTION_COPY bzw. ACTION_MOVE, + * da bei einer Verschiebeaktion die Drag-Quelle + * nicht auf die Idee kommen darf, die Quelldateien zu loeschen, + * da die eigentliche Datei-Operationen asynchron ausgefuehrt werden + * und somit in dem Zeitpunkt noch nicht abgeschlossen sind. + */ + e.acceptDrop( DnDConstants.ACTION_COPY_OR_MOVE ); + + // Datei-Operationen anstossen + Transferable t = e.getTransferable(); + if( t != null ) { + Object o = t.getTransferData( DataFlavor.javaFileListFlavor ); + if( o != null ) { + if( o instanceof Collection ) { + Collection files = (Collection) o; + + // Pruefung und Sicherheitsanfrage + boolean status = true; + File srcFile = null; + int nSrcFiles = 0; + int nSrcDirs = 0; + for( Object f : files ) { + if( f instanceof File ) { + srcFile = (File) f; + if( srcFile.isDirectory() ) { + nSrcDirs++; + } else { + nSrcFiles++; + } + } + } + if( (nSrcDirs > 0) || (nSrcFiles > 0) ) { + if( (action == DnDConstants.ACTION_MOVE) + && ((nSrcDirs + nSrcFiles) == 1) + && (srcFile != null) ) + { + File srcDir = (nSrcDirs > 0 ? + srcFile : srcFile.getParentFile()); + if( srcDir != null ) { + try { + if( EmuUtil.equals( srcDir, dstPath.toFile() ) ) { + BasicDlg.showErrorDlg( + this, + "Verschieben im selben Verzeichnis" + + " ist nicht m\u00F6glich." ); + status = false; + } + } + catch( UnsupportedOperationException ex ) {} + } + } + if( status ) { + StringBuilder buf = new StringBuilder( 512 ); + buf.append( "M\u00F6chten Sie" ); + if( (srcFile != null) + && (nSrcDirs == 1) + && (nSrcFiles == 0) ) + { + buf.append( " das Verzeichnis " ); + buf.append( srcFile.getPath() ); + } else if( (srcFile != null) + && (nSrcDirs == 0) + && (nSrcFiles == 1) ) + { + buf.append( " die Datei " ); + buf.append( srcFile.getPath() ); + } else { + if( nSrcDirs == 1 ) { + buf.append( " ein Verzeichnis" ); + } else if( nSrcDirs > 1 ) { + buf.append( + String.format( " %d Verzeichnisse", nSrcDirs ) ); + } + if( nSrcFiles > 0 ) { + if( nSrcDirs > 0 ) { + buf.append( " und" ); + } + if( nSrcFiles == 1 ) { + buf.append( " eine Datei" ); + } else { + buf.append( + String.format( " %d Dateien", nSrcFiles ) ); + } + } + } + buf.append( "\nnach " ); + buf.append( dstPath ); + if( action == DnDConstants.ACTION_MOVE ) { + buf.append( " verschieben?" ); + } else { + buf.append( " kopieren?" ); + } + status = BasicDlg.showYesNoDlg( this, buf.toString() ); + } + if( status ) { + if( action == DnDConstants.ACTION_MOVE ) { + (new FileMover( + this, + files, + dstPath, + this, + this.fileActionMngr.getFileWorkers() )).startWork(); + } else { + (new FileCopier( + this, + files, + dstPath, + this, + this.fileActionMngr.getFileWorkers() )).startWork(); + } + if( treeNode instanceof FileNode ) { + final FileNode fileNode = (FileNode) treeNode; + fireSelectNode( fileNode ); + } + + /* + * DropTargetDropEvent.dropComplete(...) + * muss in dieser Methode aufgerufen werden, + * damit die Erfolgsmeldung auch an die Drag-Quelle + * uebermittelt wird. + * Aus diesem Grund bleibt hier nichts anderes uebrig, + * als die Drop-Operation erfolgreich abzuschliessen, + * obwohl der eigentliche Kopier- bzw. Verschiebevorgang + * noch gar nicht zu Ende ist. + */ + success = true; + } + } + } + } } - catch( InvalidDnDOperationException ex ) {} } + catch( UnsupportedFlavorException ex ) {} + catch( IOException ex ) { + BasicDlg.showErrorDlg( this, ex ); + } + finally { + e.dropComplete( success ); + } + } else { + e.rejectDrop(); } } + @Override + public void dropActionChanged( DropTargetDragEvent e ) + { + // leer + } + + /* --- FlavorListener --- */ @Override public void flavorsChanged( FlavorEvent e ) { - if( e.getSource() == this.clipboard ) - updFilePasteState( false ); + if( e.getSource() == this.clipboard ) { + if( this.ignoreFlavorEvent ) { + this.ignoreFlavorEvent = false; + } else { + this.cutFiles.clear(); + updPasteState(); + } + } } @@ -247,8 +509,19 @@ public void valueChanged( ListSelectionEvent e ) public void valueChanged( TreeSelectionEvent e ) { if( e.getSource() == this.tree ) { - updPreview(); + TreePath[] paths = this.tree.getSelectionPaths(); + if( paths != null ) { + for( TreePath p : paths ) { + Object o = p.getLastPathComponent(); + if( o != null ) { + if( o instanceof FileNode ) { + refreshNode( (FileNode) o ); + } + } + } + } updActionButtons(); + updPreviewFld(); } } @@ -273,7 +546,17 @@ public void treeWillExpand( TreeExpansionEvent e ) Object o = treePath.getLastPathComponent(); if( o != null ) { if( o instanceof FileNode ) { - refreshNode( (FileNode) o, true ); + FileNode fileNode = (FileNode) o; + refreshNode( fileNode ); + int n = fileNode.getChildCount(); + for( int i = 0; i < n; i++ ) { + Object child = fileNode.getChildAt( i ); + if( child != null ) { + if( child instanceof FileNode ) { + refreshNode( (FileNode) child ); + } + } + } } } setWaitCursor( false ); @@ -291,17 +574,17 @@ public boolean applySettings( Properties props, boolean resizable ) this.mnuHiddenFiles.setSelected( EmuUtil.parseBooleanProperty( props, - "jkcemu.filebrowser.show_hidden_files", + PROP_SHOW_HIDDEN_FILES, false ) ); this.mnuSortCaseSensitive.setSelected( EmuUtil.parseBooleanProperty( props, - "jkcemu.filebrowser.sort_case_sensitive", + PROP_SORT_CASE_SENSITIVE, false ) ); String previewMaxFileSize = null; if( props != null ) { previewMaxFileSize = props.getProperty( - "jkcemu.filebrowser.preview.max_file_size" ); + PROP_PREVIEW_MAX_FILE_SIZE ); } if( previewMaxFileSize != null ) { if( previewMaxFileSize.equals( "no_preview" ) ) { @@ -322,7 +605,7 @@ public boolean applySettings( Properties props, boolean resizable ) } int splitPos = EmuUtil.parseIntProperty( props, - "jkcemu.filebrowser.split.position", + PROP_SPLIT_POSITION, -1, -1 ); if( splitPos >= 0 ) { @@ -334,14 +617,14 @@ public boolean applySettings( Properties props, boolean resizable ) Dimension size = this.tree.getPreferredSize(); if( size != null ) { int xTmp = (size.width * 3) + 50; - if( xTmp > xDiv ) + if( xTmp > xDiv ) { xDiv = xTmp; + } } this.splitPane.setDividerLocation( xDiv ); } } doFileRefresh(); - updPreview(); } return rv; } @@ -363,240 +646,81 @@ else if( src == this.mnuFileRefresh ) { rv = true; doFileRefresh(); } - else if( (src == this.mnuFileLoadIntoEmuOpt) - || (src == this.mnuPopupLoadIntoEmuOpt) ) - { - rv = true; - doFileLoadIntoEmu( true, false ); - } - else if( (src == this.mnuFileLoadIntoEmu) - || (src == this.mnuPopupLoadIntoEmu) - || (src == this.btnLoadIntoEmu) ) - { - rv = true; - doFileLoadIntoEmu( false, false ); - } - else if( (src == this.mnuFileStartInEmu) - || (src == this.mnuPopupStartInEmu) - || (src == this.btnStartInEmu) ) - { - rv = true; - doFileLoadIntoEmu( false, true ); - } - else if( (src == this.mnuFileEditText) - || (src == this.mnuPopupEditText) - || (src == this.btnEditText) ) - { - rv = true; - doFileEditText(); - } - else if( (src == this.mnuFileShowImage) - || (src == this.mnuPopupShowImage) - || (src == this.btnShowImage) ) - { - rv = true; - doFileShowImage(); - } - else if( (src == this.mnuFileEditHex) - || (src == this.mnuPopupEditHex) ) - { - rv = true; - doFileEditHex(); - } - else if( (src == this.mnuFileDiffHex) - || (src == this.mnuPopupDiffHex) ) - { - rv = true; - doFileDiffHex(); - } - else if( (src == this.mnuFileConvert) - || (src == this.mnuPopupConvert) ) - { - rv = true; - doFileConvert(); - } - else if( (src == this.mnuFileChecksum) - || (src == this.mnuPopupChecksum) ) - { - rv = true; - FileChecksumFrm.open( getSelectedFiles() ); - } - else if( (src == this.mnuFilePlay) - || (src == this.mnuPopupPlay) - || (src == this.btnPlay) ) - { - rv = true; - doFilePlay(); - } - else if( (src == this.mnuFilePlayAC1) - || (src == this.mnuPopupPlayAC1) ) - { - rv = true; - doFilePlayAC1(); - } - else if( (src == this.mnuFilePlayAC1Basic) - || (src == this.mnuPopupPlayAC1Basic) ) - { - rv = true; - doFilePlayAC1Basic(); - } - else if( (src == this.mnuFilePlaySCCH) - || (src == this.mnuPopupPlaySCCH) ) - { - rv = true; - doFilePlaySCCH(); - } - else if( (src == this.mnuFilePlayKC85) - || (src == this.mnuPopupPlayKC85) ) - { - rv = true; - doFilePlayKC( 1 ); - } - else if( (src == this.mnuFilePlayZ1013) - || (src == this.mnuPopupPlayZ1013) ) - { - rv = true; - doFilePlayZ1013( false ); - } - else if( (src == this.mnuFilePlayZ1013HS) - || (src == this.mnuPopupPlayZ1013HS) ) - { - rv = true; - doFilePlayZ1013( true ); - } - else if( (src == this.mnuFilePlayZ9001) - || (src == this.mnuPopupPlayZ9001) ) + else if( (src == this.mnuFileCreateDir) + || (src == this.mnuPopupCreateDir) ) { rv = true; - doFilePlayKC( 0 ); + doFileCreateDir(); } - else if( (src == this.mnuFileAudioIn) - || (src == this.mnuPopupAudioIn) ) + else if( (src == this.mnuFileFind) + || (src == this.mnuPopupFind) ) { rv = true; - doFileAudioIn(); + doFileFind(); } - else if( (src == this.mnuFileUnpack) - || (src == this.mnuPopupUnpack) ) - { + else if( src == this.mnuFileClose ) { rv = true; - doFileUnpack( null ); + doClose(); } - else if( (src == this.mnuFilePackGZip) - || (src == this.mnuPopupPackGZip) ) + else if( (src == this.mnuEditCut) + || (src == this.mnuPopupCut) ) { rv = true; - doFilePackGZip(); + doEditCut(); } - else if( (src == this.mnuFilePackTar) - || (src == this.mnuPopupPackTar) ) + else if( (src == this.mnuEditPaste) + || (src == this.mnuPopupPaste) ) { rv = true; - doFilePackTar( false ); + doEditPaste(); } - else if( (src == this.mnuFilePackTgz) - || (src == this.mnuPopupPackTgz) ) + else if( (src == this.mnuHiddenFiles) + || (src == this.mnuSortCaseSensitive) ) { rv = true; - doFilePackTar( true ); + doFileRefresh(); } - else if( (src == this.mnuFilePackZip) - || (src == this.mnuPopupPackZip) ) - { + else if( src == this.mnuHelpContent ) { rv = true; - doFilePackZip(); + HelpFrm.open( "/help/tools/filebrowser.htm" ); } - else if( (src == this.mnuFileRAMFloppy1Load) - || (src == this.mnuPopupRAMFloppy1Load) ) - { - rv = true; - if( this.screenFrm != null ) { - EmuThread emuThread = this.screenFrm.getEmuThread(); - if( emuThread != null ) { - doFileRAMFloppyLoad( emuThread.getRAMFloppy1() ); + if( !rv && (e instanceof ActionEvent) ) { + String actionCmd = ((ActionEvent) e).getActionCommand(); + if( actionCmd != null ) { + if( actionCmd.equals( FileActionMngr.ACTION_COPY ) + || actionCmd.equals( FileActionMngr.ACTION_COPY_PATH ) + || actionCmd.equals( FileActionMngr.ACTION_COPY_URL ) ) + { + this.cutFiles.clear(); + this.ignoreFlavorEvent = false; + updPasteState(); } - } - } - else if( (src == this.mnuFileRAMFloppy2Load) - || (src == this.mnuPopupRAMFloppy2Load) ) - { - rv = true; - if( this.screenFrm != null ) { - EmuThread emuThread = this.screenFrm.getEmuThread(); - if( emuThread != null ) { - doFileRAMFloppyLoad( emuThread.getRAMFloppy2() ); + java.util.List fileObjs + = getSelectedFileObjects(); + if( fileObjs != null ) { + if( !fileObjs.isEmpty() ) { + switch( this.fileActionMngr.actionPerformed( + actionCmd, + fileObjs ) ) + { + case DONE: + rv = true; + break; + case FILES_CHANGED: + case FILE_RENAMED: + for( FileActionMngr.FileObject o : fileObjs ) { + if( o instanceof TreeNode ) { + this.treeModel.nodeChanged( (TreeNode) o ); + } + } + fireUpdPreviewFld(); + rv = true; + break; + } + } } } } - else if( (src == this.mnuFileCreateDir) - || (src == this.mnuPopupCreateDir) ) - { - rv = true; - doFileCreateDir(); - } - else if( (src == this.mnuFileRename) - || (src == this.mnuPopupRename) ) - { - rv = true; - doFileRename(); - } - else if( (src == this.mnuFileDelete) - || (src == this.mnuPopupDelete) ) - { - rv = true; - doFileDelete(); - } - else if( (src == this.mnuFileLastModified) - || (src == this.mnuPopupLastModified) ) - { - rv = true; - doFileLastModified(); - } - else if( (src == this.mnuFileProp) - || (src == this.mnuPopupProp) ) - { - rv = true; - doFileProp(); - } - else if( src == this.mnuFileClose ) { - rv = true; - doClose(); - } - else if( (src == this.mnuEditPathCopy) - || (src == this.mnuPopupPathCopy) ) - { - rv = true; - doEditPathCopy(); - } - else if( (src == this.mnuEditURLCopy) - || (src == this.mnuPopupURLCopy) ) - { - rv = true; - doEditURLCopy(); - } - else if( (src == this.mnuEditFileCopy) - || (src == this.mnuPopupFileCopy) ) - { - rv = true; - doEditFileCopy(); - } - else if( (src == this.mnuEditFilePaste) - || (src == this.mnuPopupFilePaste) ) - { - rv = true; - doEditFilePaste(); - } - else if( (src == this.mnuHiddenFiles) - || (src == this.mnuSortCaseSensitive) ) - { - rv = true; - doFileRefresh(); - updPreview(); - } - else if( src == this.mnuHelpContent ) { - rv = true; - HelpFrm.open( "/help/tools/filebrowser.htm" ); - } } } } @@ -610,7 +734,12 @@ else if( src == this.mnuHelpContent ) { @Override public boolean doClose() { - boolean rv = super.doClose(); + boolean rv = AbstractFileWorker.checkWindowClosing( + this, + this.fileActionMngr.getFileWorkers() ); + if( rv ) { + rv = super.doClose(); + } if( rv ) { Main.checkQuit( this ); } @@ -632,10 +761,10 @@ public void putSettingsTo( Properties props ) if( props != null ) { super.putSettingsTo( props ); props.setProperty( - "jkcemu.filebrowser.show_hidden_files", + PROP_SHOW_HIDDEN_FILES, String.valueOf( this.mnuHiddenFiles.isSelected() ) ); props.setProperty( - "jkcemu.filebrowser.sort_case_sensitive", + PROP_SORT_CASE_SENSITIVE, String.valueOf( this.mnuSortCaseSensitive.isSelected() ) ); String previewMaxFileSize = "unlimited"; @@ -651,11 +780,11 @@ public void putSettingsTo( Properties props ) previewMaxFileSize = "100M"; } props.setProperty( - "jkcemu.filebrowser.preview.max_file_size", + PROP_PREVIEW_MAX_FILE_SIZE, previewMaxFileSize ); props.setProperty( - "jkcemu.filebrowser.split.position", + PROP_SPLIT_POSITION, String.valueOf( this.splitPane.getDividerLocation() ) ); } } @@ -671,7 +800,7 @@ public void mouseClicked( MouseEvent e ) if( (e.getClickCount() > 1) && (e.getButton() == MouseEvent.BUTTON1) ) { Component c = e.getComponent(); if( c != null ) { - if( (c == this.tree) || (c instanceof JTable) ) { + if( (c == this.tree) || (c == this.table) ) { try { doFileAction( c ); done = true; @@ -714,128 +843,94 @@ public void mouseReleased( MouseEvent e ) /* --- Aktionen --- */ - private void doEditFileCopy() + private void doEditCut() throws IOException { - try { - if( this.clipboard != null ) { - Collection files = getSelectedFiles(); - if( files != null ) { - if( !files.isEmpty() ) { - FileListSelection fls = new FileListSelection( files ); - this.clipboard.setContents( fls, fls ); - } - } - } - } - catch( IllegalStateException ex ) {} - } + this.cutFiles.clear(); - - private void doEditFilePaste() - { - if( this.clipboard != null ) { - File dstDir = null; - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) { - if( file.isDirectory() ) { - dstDir = file; - } - } - } - if( (fileNode != null) && (dstDir != null) ) { - boolean changed = false; - try { - Object o = this.clipboard.getData( DataFlavor.javaFileListFlavor ); - if( o != null ) { - if( o instanceof File ) { - changed = pasteFile( dstDir, (File) o ); - } - else if( o instanceof java.util.List ) { - for( Object e : (java.util.List) o ) { - if( e != null ) { - if( e instanceof File ) { - if( pasteFile( dstDir, (File) e ) ) { - changed = true; - } else { - break; - } - } - } - } - } + java.util.List fObjs + = getSelectedFileObjects(); + if( fObjs != null ) { + int n = fObjs.size(); + if( n > 0 ) { + this.cutFiles.ensureCapacity( n ); + for( FileActionMngr.FileObject fObj : fObjs ) { + File file = fObj.getFile(); + if( file != null ) { + this.cutFiles.add( file ); } } - catch( IllegalStateException ex ) {} - catch( UnsupportedFlavorException ex ) {} - catch( IOException ex ) {} - if( changed ) { - fileNode.refresh( - this.treeModel, - false, - this.mnuHiddenFiles.isSelected(), - getFileComparator() ); - } - } else { - BasicDlg.showErrorDlg( - this, - "Zielverzeichnis nicht ausgew\u00E4hlt" ); } } - } - - private void doEditPathCopy() - { + // Zwischenablage leeren try { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) { - String fileName = file.getPath(); - if( fileName != null ) { - if( !fileName.isEmpty() ) { - StringSelection ss = new StringSelection( fileName ); - this.clipboard.setContents( ss, ss ); - } - } - } + if( this.clipboard != null ) { + this.ignoreFlavorEvent = true; + StringSelection ss = new StringSelection( "" ); + this.clipboard.setContents( ss, ss ); } } catch( IllegalStateException ex ) {} + + // Einfuegen ermoeglichen + updPasteState(); } - private void doEditURLCopy() + private void doEditPaste() { - try { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) { - try { - URI uri = file.toURI(); - if( uri != null ) { - URL url = uri.toURL(); - if( url != null ) { - String urlText = uri.toString(); - if( urlText != null ) { - if( !urlText.isEmpty() ) { - StringSelection ss = new StringSelection( urlText ); - this.clipboard.setContents( ss, ss ); - } - } + Path dstPath = null; + FileNode fileNode = getSelectedFileNode(); + if( fileNode != null ) { + Path path = fileNode.getPath(); + if( path != null ) { + if( Files.isDirectory( path, LinkOption.NOFOLLOW_LINKS) ) { + dstPath = path; + } + } + } + if( dstPath != null ) { + try { + if( this.cutFiles.isEmpty() ) { + if( this.clipboard != null ) { + Object o = this.clipboard.getData( DataFlavor.javaFileListFlavor ); + if( o != null ) { + Collection files = null; + if( o instanceof File ) { + files = Collections.singletonList( (File) o ); + } else if( o instanceof Collection ) { + files = (Collection) o; + } + if( !files.isEmpty() ) { + (new FileCopier( + this, + files, + dstPath, + this, + this.fileActionMngr.getFileWorkers() )).startWork(); } } } - catch( MalformedURLException ex ) { - BasicDlg.showErrorDlg( this, ex ); - } + } else { + (new FileMover( + this, + this.cutFiles, + dstPath, + this, + this.fileActionMngr.getFileWorkers() )).startWork(); } } + catch( IllegalStateException ex ) {} + catch( UnsupportedFlavorException ex ) {} + catch( IOException ex ) { + BasicDlg.showErrorDlg( this, ex ); + } + } else { + BasicDlg.showErrorDlg( + this, + "Kein Verzeichnis ausgew\u00E4hlt," + + " in das eingef\u00FCgt werden soll" ); } - catch( IllegalStateException ex ) {} } @@ -847,1033 +942,131 @@ private void doFileAction( Object src ) throws IOException TreeNode[] path = this.treeModel.getPathToRoot( fileNode ); if( path != null ) { setWaitCursor( true ); - this.tree.setSelectionPath( new TreePath( path ) ); - refreshNode( fileNode, true ); - this.filePreviewFld.setFileNode( - fileNode, - getPreviewMaxFileSize(), - this.mnuSortCaseSensitive.isSelected() ); + refreshNode( fileNode ); + fireSelectNode( fileNode ); setWaitCursor( false ); } } else { - File file = fileNode.getFile(); - if( file != null ) { - if( file.isFile() ) { - if( fileNode.isArchiveFile() || fileNode.isCompressedFile() ) { - doFileUnpack( fileNode ); - } else if( fileNode.isAudioFile() ) { - AudioPlayer.play( this, file ); - } else if( fileNode.isImageFile() ) { - ImageFrm.open( file ); - } else if( fileNode.isTextFile() ) { - EmuThread emuThread = null; - if( this.screenFrm != null ) { - emuThread = this.screenFrm.getEmuThread(); - } - TextEditFrm.open( emuThread ).openFile( file ); - } else if( fileNode.fileNameEndsWith( ".prj" ) ) { - Properties props = TextEditFrm.loadProject( file ); - if( props != null ) { - EmuThread emuThread = null; - if( this.screenFrm != null ) { - emuThread = this.screenFrm.getEmuThread(); - } - TextEditFrm.open( emuThread ).openProject( file, props ); - } else { - throw new IOException( - "Die PRJ-Datei ist keine JKCEMU-Projektdatei." ); - } - } else { - boolean loadable = false; - FileInfo fileInfo = fileNode.getFileInfo(); - if( fileInfo != null ) { - String fileFmt = fileInfo.getFileFormat(); - if( fileFmt != null ) { - if( !fileFmt.isEmpty() && !fileFmt.equals( FileInfo.BIN ) ) { - loadable = true; - } - } - } - if( !loadable ) { - String fName = file.getName(); - if( fName != null ) { - if( fName.toLowerCase().endsWith( ".bin" ) ) { - loadable = true; - } - } - } - if( loadable ) { - if( this.screenFrm != null ) { - LoadDlg.loadFile( - this, // owner - this.screenFrm, - file, - true, // interactive - true, // startEnabled - fileNode.isStartableFile() ); - } - } else { - if( file.canRead() ) { - try { - if( Desktop.isDesktopSupported() ) { - Desktop desktop = Desktop.getDesktop(); - if( desktop != null ) { - if( desktop.isSupported( Desktop.Action.OPEN ) ) { - desktop.open( file ); - } - } - } - } - catch( Exception ex ) { - BasicDlg.showErrorDlg( this, ex ); - } - } - } - } - } - } + this.fileActionMngr.doFileAction( fileNode ); } } } - private void doFileLoadIntoEmu( boolean interactive, boolean startSelected ) + private void doFileCreateDir() { - if( this.screenFrm != null ) { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) { - if( file.isFile() ) { - LoadDlg.loadFile( - this, // owner - this.screenFrm, - file, - interactive, - true, // startEnabled - startSelected ); + FileNode fileNode = getSelectedFileNode(); + if( fileNode != null ) { + TreeNode parent = fileNode; + File file = fileNode.getFile(); + if( file != null ) { + if( !file.isDirectory() ) { + file = file.getParentFile(); + parent = fileNode.getParent(); + } + } + if( (parent != null) && (file != null) ) { + File dirFile = EmuUtil.createDir( this, file ); + if( dirFile != null ) { + if( parent instanceof FileNode ) { + refreshNode( (FileNode) parent ); } + fireSelectNode( parent, dirFile ); + updActionButtons(); } } } } - - private void doFileEditText() + private void doFileRefresh() { FileNode fileNode = getSelectedFileNode(); if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) { - if( file.isFile() ) { - TextEditFrm.open( null ).openFile( file ); - } - } + refreshNode( fileNode ); + } else { + refreshNode( this.rootNode ); } + updActionButtons(); } - private void doFileEditHex() + private void doFileFind() { + Path path = null; FileNode fileNode = getSelectedFileNode(); if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) { - if( file.isFile() ) { - HexEditFrm.open( file ); - } - } + path = fileNode.getPath(); } + FindFilesFrm.open( this.screenFrm, path ); } - private void doFileDiffHex() + /* --- Konstruktor --- */ + + private FileBrowserFrm( ScreenFrm screenFrm ) { - java.util.List files = getSelectedFiles(); - if( files != null ) { - int n = files.size(); - if( n > 0 ) { - HexDiffFrm.open().addFiles( files ); - } - } - } + this.screenFrm = screenFrm; + this.fileActionMngr = new FileActionMngr( this, screenFrm, this ); + this.ignoreFlavorEvent = false; + this.pasteState = false; + this.lastActiveFld = null; + this.treeDragSelectionRows = null; + this.clipboard = null; + this.cutFiles = new ArrayList<>(); + setTitle( "JKCEMU Datei-Browser" ); + Main.updIcon( this ); - private void doFileConvert() - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) { - if( file.isFile() ) { - FileConvertFrm.open( file ); - } - } - } - } + // Popup-Menu + this.mnuPopup = new JPopupMenu(); - private void doFileUnpack( FileNode fileNode ) throws IOException - { - if( fileNode == null ) { - fileNode = getSelectedFileNode(); - } - if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) { - String fileName = file.getName(); - if( (fileName != null) && file.isFile() ) { - String upperName = fileName.toUpperCase(); - if( upperName.endsWith( ".GZ" ) ) { - File outFile = askForOutputFile( - file, - "Entpackte Datei speichern", - fileName.substring( 0, fileName.length() - 3 ) ); - if( outFile != null ) { - GZipUnpacker.unpackFile( this, file, outFile ); - } - } - else if( upperName.endsWith( ".TAR" ) - || upperName.endsWith( ".TGZ" ) ) - { - File outDir = EmuUtil.askForOutputDir( - this, - file, - "Entpacken nach:", - "Archiv-Datei entpacken" ); - if( outDir != null ) { - TarUnpacker.unpackFile( - this, - file, - outDir, - upperName.endsWith( ".TGZ" ) ); - } - } - else if( upperName.endsWith( ".JAR" ) - || upperName.endsWith( ".ZIP" ) ) - { - File outDir = EmuUtil.askForOutputDir( - this, - file, - "Entpacken nach:", - "Archiv-Datei entpacken" ); - if( outDir != null ) { - ZipUnpacker.unpackFile( this, file, outDir ); - } - } - else if( fileNode.isPlainDiskFile() ) { - DiskUtil.unpackPlainDiskFile( this, file ); - } - else if( fileNode.isNonPlainDiskFile() ) { - AbstractFloppyDisk disk = DiskUtil.readNonPlainDiskFile( - this, - file ); - if( disk != null ) { - if( DiskUtil.checkAndConfirmWarning( this, disk ) ) { - DiskUtil.unpackDisk( this, file, disk, true ); - } - } - } - } - } - } - } + // Menu Bearbeiten + JMenu mnuEdit = new JMenu( "Bearbeiten" ); + mnuEdit.setMnemonic( KeyEvent.VK_B ); + + this.fileActionMngr.addCopyFileNameMenuItemsTo( this.mnuPopup, mnuEdit ); + mnuEdit.addSeparator(); + this.mnuPopup.addSeparator(); + this.mnuEditCut = createJMenuItem( + "Dateien/Verzeichnisse ausschneiden" ); + mnuEdit.add( this.mnuEditCut ); - private void doFilePackGZip() - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) { - if( file.isFile() ) { - String fileName = file.getName(); - if( fileName != null ) { - fileName += ".gz"; - } - File outFile = askForOutputFile( - file, - "GZip-Datei speichern", - fileName ); - if( outFile != null ) - GZipPacker.packFile( this, file, outFile ); - } - } - } - } + this.mnuPopupCut = createJMenuItem( + "Dateien/Verzeichnisse ausschneiden" ); + this.mnuPopup.add( this.mnuPopupCut ); + this.fileActionMngr.addCopyFileMenuItemTo( this.mnuPopup, mnuEdit ); - private void doFilePackTar( boolean compression ) - { - java.util.List files = getSelectedFiles(); - if( files != null ) { - if( !files.isEmpty() ) { - File firstFile = files.get( 0 ); - String fileName = firstFile.getName(); - if( fileName != null ) { - int pos = fileName.indexOf( '.' ); - if( (pos == 0) && (fileName.length() > 1) ) { - pos = fileName.indexOf( '.', 1 ); - } - if( pos >= 0 ) { - fileName = fileName.substring( 0, pos ); - } - if( fileName.length() > 0 ) { - if( compression ) { - fileName += ".tgz"; - } else { - fileName += ".tar"; - } - } - File outFile = askForOutputFile( - firstFile, - compression ? - "TGZ-Datei speichern" - : "TAR-Datei speichern", - fileName ); - if( outFile != null ) - TarPacker.packFiles( this, files, outFile, compression ); - } - } - } - } + this.mnuEditPaste = createJMenuItem( + "Dateien/Verzeichnisse einf\u00FCgen" ); + mnuEdit.add( this.mnuEditPaste ); + this.mnuPopupPaste = createJMenuItem( + "Dateien/Verzeichnisse einf\u00FCgen" ); + this.mnuPopup.add( this.mnuPopupPaste ); + this.mnuPopup.addSeparator(); - private void doFilePackZip() - { - java.util.List files = getSelectedFiles(); - if( files != null ) { - if( !files.isEmpty() ) { - File firstFile = files.get( 0 ); - String fileName = firstFile.getName(); - if( fileName != null ) { - int pos = fileName.indexOf( '.' ); - if( (pos == 0) && (fileName.length() > 1) ) { - pos = fileName.indexOf( '.', 1 ); - } - if( pos >= 0 ) { - fileName = fileName.substring( 0, pos ); - } - if( fileName.length() > 0 ) { - fileName += ".zip"; - } - File outFile = askForOutputFile( - firstFile, - "ZIP-Datei speichern", - fileName ); - if( outFile != null ) - ZipPacker.packFiles( this, files, outFile ); - } - } - } - } + // Menu Datei + JMenu mnuFile = new JMenu( "Datei" ); + mnuFile.setMnemonic( KeyEvent.VK_D ); - private void doFilePlay() - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - if( fileNode.isAudioFile() ) { - AudioPlayer.play( this, fileNode.getFile() ); - } + if( this.screenFrm != null ) { + this.fileActionMngr.addLoadIntoEmuMenuItemsTo( this.mnuPopup, mnuFile ); + mnuFile.addSeparator(); } - } + this.fileActionMngr.addFileMenuItemsTo( this.mnuPopup, mnuFile ); + this.mnuPopup.addSeparator(); + mnuFile.addSeparator(); - private void doFilePlayAC1() throws IOException - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - if( fileNode.isBinFile() ) { - File file = fileNode.getFile(); - if( file != null ) { - String title = "AC1-Wiedergabe von " + file.getName(); - ReplyFileHeadDlg dlg = new ReplyFileHeadDlg( - this, - file.getName(), - "Wiedergeben", - title, - ReplyFileHeadDlg.Option.BEGIN_ADDRESS, - ReplyFileHeadDlg.Option.START_ADDRESS, - ReplyFileHeadDlg.Option.FILE_NAME_16 ); - dlg.setVisible( true ); - if( dlg.wasApproved() ) { - int startAddr = dlg.getApprovedStartAddress(); - if( startAddr < 0 ) { - startAddr = 0; - } - AudioPlayer.play( - this, - new AC1AudioDataStream( - false, - EmuUtil.readFile( file, 0x10000 ), - dlg.getApprovedFileName(), - dlg.getApprovedBeginAddress(), - startAddr ), - title + "..." ); - } - } - } - } - } + this.mnuFileCreateDir = createJMenuItem( "Verzeichnis erstellen..." ); + mnuFile.add( this.mnuFileCreateDir ); - - private void doFilePlayAC1Basic() throws IOException - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - if( fileNode.isBinFile() || fileNode.isHeadersaveFile() ) { - File file = fileNode.getFile(); - if( file != null ) { - byte[] buf = EmuUtil.readFile( file, 0x10000 ); - int len = buf.length; - if( len > 0 ) { - String fileName = file.getName(); - int offs = 0; - if( len > 32 ) { - if( (buf[ 13 ] == (byte) 0xD3) - && (buf[ 14 ] == (byte) 0xD3) - && (buf[ 15 ] == (byte) 0xD3) ) - { - offs += 32; - len -= 32; - String s = EmuUtil.extractSingleAsciiLine( buf, 16, 16 ); - if( s != null ) { - fileName = s; - } - } - } - String title = "AC1-BASIC-Wiedergabe von " + file.getName(); - ReplyFileHeadDlg dlg = new ReplyFileHeadDlg( - this, - fileName, - "Wiedergeben", - title, - ReplyFileHeadDlg.Option.FILE_NAME_6 ); - dlg.setVisible( true ); - if( dlg.wasApproved() ) { - AudioPlayer.play( - this, - new AC1AudioDataStream( - true, - buf, - offs, - len, - dlg.getApprovedFileName(), - -1, - -1 ), - title + "..." ); - } - } - } - } - } - } - - - private void doFilePlaySCCH() throws IOException - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - if( fileNode.isBinFile() || fileNode.isHeadersaveFile() ) { - File file = fileNode.getFile(); - if( file != null ) { - byte[] buf = EmuUtil.readFile( file, 0x10000 ); - int len = buf.length; - if( len > 0 ) { - ReplyFileHeadDlg.Option[] options = null; - String fileName = file.getName(); - int offs = 0; - int begAddr = -1; - int endAddr = -1; - int fType = -1; - if( len > 32 ) { - if( (buf[ 13 ] == (byte) 0xD3) - && (buf[ 14 ] == (byte) 0xD3) - && (buf[ 15 ] == (byte) 0xD3) ) - { - offs += 32; - len -= 32; - begAddr = EmuUtil.getWord( buf, 0 ); - endAddr = EmuUtil.getWord( buf, 2 ); - if( (begAddr == 0x60F7) && (buf[ 12 ] == (byte) 'B') ) { - fType = 'B'; - options = new ReplyFileHeadDlg.Option[] { - ReplyFileHeadDlg.Option.FILE_NAME_16 }; - } else { - options = new ReplyFileHeadDlg.Option[] { - ReplyFileHeadDlg.Option.FILE_NAME_16, - ReplyFileHeadDlg.Option.SCCH_FILE_TYPE }; - } - String s = EmuUtil.extractSingleAsciiLine( buf, 16, 16 ); - if( s != null ) { - fileName = s; - } - } - } - if( options == null ) { - options = new ReplyFileHeadDlg.Option[] { - ReplyFileHeadDlg.Option.BEGIN_ADDRESS, - ReplyFileHeadDlg.Option.END_ADDRESS, - ReplyFileHeadDlg.Option.FILE_NAME_16, - ReplyFileHeadDlg.Option.SCCH_FILE_TYPE }; - } - String title = "AC1/LLC2-TurboSave-Wiedergabe von " - + file.getName(); - ReplyFileHeadDlg dlg = new ReplyFileHeadDlg( - this, - fileName, - "Wiedergeben", - title, - options ); - dlg.setVisible( true ); - if( dlg.wasApproved() ) { - if( begAddr < 0 ) { - begAddr = dlg.getApprovedBeginAddress(); - } - if( endAddr < 0 ) { - endAddr = dlg.getApprovedEndAddress(); - if( endAddr < 0 ) { - endAddr = begAddr + len - 1; - } - } - if( fType < 0 ) { - fType = dlg.getApprovedSCCHFileType(); - } - AudioPlayer.play( - this, - new SCCHAudioDataStream( - buf, - offs, - len, - dlg.getApprovedFileName(), - (char) fType, - begAddr, - endAddr ), - title + "..." ); - } - } - } - } - } - } - - - private void doFilePlayKC( int firstBlkNum ) throws IOException - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) { - String title = "KC-Wiedergabe von " + file.getName() + "..."; - if( fileNode.isKCBasicHeadFile() ) { - AudioPlayer.play( - this, - new KCAudioDataStream( - false, - 1, - EmuUtil.readFile( file, 0x10000 ) ), - title ); - } - else if( fileNode.isKCBasicFile() ) { - byte[] fileBytes = EmuUtil.readFile( file, 0x10000 ); - if( fileBytes != null ) { - if( fileBytes.length > 0 ) { - ReplyFileHeadDlg dlg = new ReplyFileHeadDlg( - this, - file.getName(), - "Wiedergeben", - title, - ReplyFileHeadDlg.Option.FILE_NAME_8 ); - dlg.setVisible( true ); - if( dlg.wasApproved() ) { - String name = dlg.getApprovedFileName(); - byte[] buf = new byte[ fileBytes.length + 11 ]; - int dst = 0; - buf[ dst++ ] = (byte) 0xD3; - buf[ dst++ ] = (byte) 0xD3; - buf[ dst++ ] = (byte) 0xD3; - if( name != null ) { - int len = name.length(); - int src = 0; - while( (dst < 11) && (src < len) ) { - buf[ dst++ ] = (byte) (name.charAt( src++ ) & 0x7F); - } - } - while( dst < 11 ) { - buf[ dst++ ] = (byte) 0x20; - } - AudioPlayer.play( - this, - new KCAudioDataStream( false, 1, buf ), - title ); - } - } - } - } - else if( fileNode.isKCSysFile() ) { - AudioPlayer.play( - this, - new KCAudioDataStream( - false, - firstBlkNum, - EmuUtil.readFile( file, 0x10000 ) ), - title ); - } - else if( (fileNode.isKC85TapFile() && (firstBlkNum == 1)) - || (fileNode.isZ9001TapFile() && (firstBlkNum == 0)) ) - { - AudioPlayer.play( - this, - new KCAudioDataStream( - true, - 0, - EmuUtil.readFile( file, 0x10110 ) ), - title ); - } - } - } - } - - - private void doFilePlayZ1013( boolean headersave ) throws IOException - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - if( fileNode.isBinFile() - || (headersave && fileNode.isHeadersaveFile()) ) - { - File file = fileNode.getFile(); - if( file != null ) { - AudioPlayer.play( - this, - new Z1013AudioDataStream( - headersave, - EmuUtil.readFile( file, 0x10020 ) ), - String.format( - "Z1013%s-Wiedergabe von %s...", - headersave ? "-Headersave" : "", - file.getName() ) ); - } - } - } - } - - - private void doFileAudioIn() - { - if( this.screenFrm != null ) { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - if( fileNode.isAudioFile() - || fileNode.isKC85TapFile() - || fileNode.isZ9001TapFile() ) - { - File file = fileNode.getFile(); - if( file != null ) { - this.screenFrm.openAudioInFile( file ); - } - } - } - } - } - - - private void doFileShowImage() - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - if( fileNode.isImageFile() ) { - ImageFrm.open( fileNode.getFile() ); - } - } - } - - - private void doFileRAMFloppyLoad( RAMFloppy ramFloppy ) - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) { - try { - ramFloppy.load( file ); - Main.setLastFile( file, "ramfloppy" ); - } - catch( IOException ex ) { - BasicDlg.showErrorDlg( - this, - "Die RAM-Floppy kann nicht geladen werden.\n\n" - + ex.getMessage() ); - } - } - } - } - - - private void doFileCreateDir() - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - TreeNode parent = fileNode; - File file = fileNode.getFile(); - if( file != null ) { - if( !file.isDirectory() ) { - file = file.getParentFile(); - parent = fileNode.getParent(); - } - } - if( (parent != null) && (file != null) ) { - File dirFile = EmuUtil.createDir( this, file ); - if( dirFile != null ) { - if( parent instanceof FileNode ) { - refreshNode( (FileNode) parent, false ); - } - selectNode( parent, dirFile ); - updActionButtons(); - } - } - } - } - - - private void doFileRename() - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - File file = EmuUtil.renameFile( this, fileNode.getFile() ); - if( file != null ) { - fileNode.setFile( file ); - this.treeModel.nodeChanged( fileNode ); - selectNode( fileNode ); - updActionButtons(); - } - } - } - - - private void doFileDelete() - { - if( this.lastActiveFld != null ) { - if( this.lastActiveFld == this.table ) { - TableModel tm = this.table.getModel(); - if( tm != null ) { - if( tm instanceof FileTableModel ) { - int[] rows = this.table.getSelectedRows(); - if( rows != null ) { - if( rows.length > 0 ) { - Arrays.sort( rows ); - int firstRow = rows[ 0 ]; - Set parents = new HashSet(); - File[] files = new File[ rows.length ]; - Arrays.fill( files, null ); - for( int i = 0; i < rows.length; i++ ) { - int mRow = this.table.convertRowIndexToModel( rows[ i ] ); - if( mRow >= 0 ) { - FileEntry entry = ((FileTableModel) tm).getRow( mRow ); - if( entry != null ) { - files[ i ] = entry.getFile(); - if( entry instanceof ExtendedFileEntry ) { - FileNode fileNode - = ((ExtendedFileEntry) entry).getFileNode(); - if( fileNode != null ) { - TreeNode parent = fileNode.getParent(); - if( parent != null ) { - if( parent instanceof FileNode ) { - parents.add( (FileNode) parent ); - } - } - files[ i ] = fileNode.getFile(); - } - } - } - } - } - if( EmuUtil.deleteFiles( this, files ) ) { - refreshNodes( parents ); - updPreview(); - this.table.clearSelection(); - updActionButtons(); - if( firstRow >= 0 ) { - final int row = firstRow; - EventQueue.invokeLater( - new Runnable() - { - @Override - public void run() - { - selectTableRow( row ); - } - } ); - } - } - } - } - } - } - } else { - Collection nodes = getSelectedTreeFileNodes(); - if( nodes != null ) { - int n = nodes.size(); - if( n > 0 ) { - int firstSelSubIdx = -1; - TreePath firstSelParent = null; - TreePath firstSelPath = this.tree.getSelectionPath(); - if( firstSelPath != null ) { - firstSelParent = firstSelPath.getParentPath(); - if( firstSelParent != null ) { - Object parent = firstSelParent.getLastPathComponent(); - Object child = firstSelPath.getLastPathComponent(); - if( (parent != null) && (child != null) ) { - if( parent instanceof FileNode ) { - if( !nodes.contains( parent ) ) { - firstSelSubIdx = this.treeModel.getIndexOfChild( - parent, - child ); - } - } - } - } - } - Set parents = new HashSet(); - Collection files = new ArrayList( n ); - for( FileNode node : nodes ) { - File file = node.getFile(); - if( file != null ) { - Object parent = node.getParent(); - if( parent != null ) { - if( parent instanceof FileNode ) { - parents.add( (FileNode) parent ); - } - } - files.add( file ); - } - } - n = files.size(); - if( n > 0 ) { - if( EmuUtil.deleteFiles( - this, - files.toArray( new File[ n ] ) ) ) - { - refreshNodes( parents ); - this.tree.clearSelection(); - if( (firstSelParent != null) && (firstSelSubIdx >= 0) ) { - Object o = firstSelParent.getLastPathComponent(); - if( o != null ) { - if( o instanceof TreeNode ) { - if( firstSelSubIdx < ((TreeNode) o).getChildCount() ) { - TreeNode child = ((TreeNode) o).getChildAt( - firstSelSubIdx ); - if( child != null ) { - this.tree.addSelectionPath( - firstSelParent.pathByAddingChild( child ) ); - } - } - } - } - } - updPreview(); - updActionButtons(); - } - } - } - } - } - } - } - - - private void doFileLastModified() - { - TreePath[] treePaths = this.tree.getSelectionPaths(); - if( treePaths != null ) { - java.util.List files = new ArrayList(); - for( int i = 0; i < treePaths.length; i++ ) { - Object o = treePaths[ i ].getLastPathComponent(); - if( o != null ) { - if( o instanceof FileNode ) { - File file = ((FileNode) o).getFile(); - if( file != null ) - files.add( file ); - } - } - } - (new LastModifiedDlg( this, files )).setVisible( true ); - } - } - - - private void doFileRefresh() - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - refreshNode( fileNode, false ); - } else { - refreshNode( this.rootNode, false ); - } - updActionButtons(); - } - - - private void doFileProp() - { - FileNode fileNode = getSelectedFileNode(); - if( fileNode != null ) { - File file = fileNode.getFile(); - if( file != null ) - (new FilePropDlg( this, file )).setVisible( true ); - } - } - - - /* --- Konstruktor --- */ - - private FileBrowserFrm( ScreenFrm screenFrm ) - { - this.screenFrm = screenFrm; - this.lastActiveFld = null; - this.clipboard = null; - this.filePasteState = false; - setTitle( "JKCEMU Datei-Browser" ); - Main.updIcon( this ); - - - // Menu Datei - JMenu mnuFile = new JMenu( "Datei" ); - mnuFile.setMnemonic( KeyEvent.VK_D ); - - if( this.screenFrm != null ) { - this.mnuFileLoadIntoEmuOpt = createJMenuItem( - "In Emulator laden mit...", - KeyStroke.getKeyStroke( KeyEvent.VK_L, Event.CTRL_MASK ) ); - mnuFile.add( this.mnuFileLoadIntoEmuOpt ); - - this.mnuFileLoadIntoEmu = createJMenuItem( - "In Emulator laden", - KeyStroke.getKeyStroke( - KeyEvent.VK_L, - Event.CTRL_MASK | Event.SHIFT_MASK) ); - mnuFile.add( this.mnuFileLoadIntoEmu ); - - this.mnuFileStartInEmu = createJMenuItem( - "Im Emulator starten", - KeyStroke.getKeyStroke( KeyEvent.VK_R, Event.CTRL_MASK ) ); - mnuFile.add( this.mnuFileStartInEmu ); - } else { - this.mnuFileLoadIntoEmuOpt = null; - this.mnuFileLoadIntoEmu = null; - this.mnuFileStartInEmu = null; - } - - this.mnuFileEditText = createJMenuItem( - "Im Texteditor \u00F6ffnen...", - KeyStroke.getKeyStroke( KeyEvent.VK_E, Event.CTRL_MASK ) ); - mnuFile.add( this.mnuFileEditText ); - - this.mnuFileShowImage = createJMenuItem( - "Im Bildbetrachter anzeigen...", - KeyStroke.getKeyStroke( KeyEvent.VK_B, Event.CTRL_MASK ) ); - mnuFile.add( this.mnuFileShowImage ); - - this.mnuFileEditHex = createJMenuItem( "Im Hex-Editor \u00F6ffnen..." ); - mnuFile.add( this.mnuFileEditHex ); - - this.mnuFileDiffHex = createJMenuItem( - "Im Hex-Dateivergleicher \u00F6ffnen..." ); - mnuFile.add( this.mnuFileDiffHex ); - - this.mnuFileConvert = createJMenuItem( - "Im Dateikonverter \u00F6ffnen..." ); - mnuFile.add( this.mnuFileConvert ); - - this.mnuFileChecksum = createJMenuItem( - "Pr\u00FCfsumme/Hash-Wert berechnen..." ); - mnuFile.add( this.mnuFileChecksum ); - - this.mnuFileAudioIn = createJMenuItem( - "In Audio/Kassette \u00F6ffnen..." ); - mnuFile.add( this.mnuFileAudioIn ); - - this.mnuFilePlay = createJMenuItem( "Wiedergeben" ); - mnuFile.add( this.mnuFilePlay ); - - JMenu mnuFilePlayAs = new JMenu( "Wiedergeben im" ); - mnuFile.add( mnuFilePlayAs ); - - this.mnuFilePlayAC1 = createJMenuItem( "AC1-Format" ); - mnuFilePlayAs.add( this.mnuFilePlayAC1 ); - - this.mnuFilePlayAC1Basic = createJMenuItem( "AC1-BASIC-Format" ); - mnuFilePlayAs.add( this.mnuFilePlayAC1Basic ); - - this.mnuFilePlaySCCH = createJMenuItem( "AC1/LLC2-TurboSave-Format" ); - mnuFilePlayAs.add( this.mnuFilePlaySCCH ); - - this.mnuFilePlayKC85 = createJMenuItem( - "KC-Format (HC900, KC85/2..5, KC-BASIC)" ); - mnuFilePlayAs.add( this.mnuFilePlayKC85 ); - - this.mnuFilePlayZ9001 = createJMenuItem( - "KC-Format (KC85/1, KC87, Z9001)" ); - mnuFilePlayAs.add( this.mnuFilePlayZ9001 ); - - this.mnuFilePlayZ1013 = createJMenuItem( "Z1013-Format" ); - mnuFilePlayAs.add( this.mnuFilePlayZ1013 ); - - this.mnuFilePlayZ1013HS = createJMenuItem( "Z1013-Headersave-Format" ); - mnuFilePlayAs.add( this.mnuFilePlayZ1013HS ); - - this.mnuFileUnpack = createJMenuItem( "Entpacken..." ); - mnuFile.add( this.mnuFileUnpack ); - - JMenu mnuFilePack = new JMenu( "Packen in" ); - mnuFile.add( mnuFilePack ); - - this.mnuFilePackTar = createJMenuItem( "TAR-Archiv..." ); - mnuFilePack.add( this.mnuFilePackTar ); - - this.mnuFilePackTgz = createJMenuItem( "TGZ-Archiv..." ); - mnuFilePack.add( this.mnuFilePackTgz ); - - this.mnuFilePackZip = createJMenuItem( "ZIP-Archiv..." ); - mnuFilePack.add( this.mnuFilePackZip ); - mnuFilePack.addSeparator(); - - this.mnuFilePackGZip = createJMenuItem( "GZip-Datei..." ); - mnuFilePack.add( this.mnuFilePackGZip ); - mnuFile.addSeparator(); - - if( this.screenFrm != null ) { - this.mnuFileRAMFloppy1Load = createJMenuItem( "In RAM-Floppy 1 laden" ); - mnuFile.add( this.mnuFileRAMFloppy1Load ); - - this.mnuFileRAMFloppy2Load = createJMenuItem( "In RAM-Floppy 2 laden" ); - mnuFile.add( this.mnuFileRAMFloppy2Load ); - mnuFile.addSeparator(); - } else { - this.mnuFileRAMFloppy1Load = null; - this.mnuFileRAMFloppy2Load = null; - } - - this.mnuFileCreateDir = createJMenuItem( "Verzeichnis erstellen..." ); - mnuFile.add( this.mnuFileCreateDir ); - - this.mnuFileRename = createJMenuItem( "Umbenennen..." ); - mnuFile.add( this.mnuFileRename ); - - this.mnuFileDelete = createJMenuItem( - "L\u00F6schen", - KeyStroke.getKeyStroke( KeyEvent.VK_DELETE, 0 ) ); - mnuFile.add( this.mnuFileDelete ); - - this.mnuFileLastModified = createJMenuItem( - "\u00C4nderungszeitpunkt setzen..." ); - mnuFile.add( this.mnuFileLastModified ); - mnuFile.addSeparator(); - - this.mnuFileProp = createJMenuItem( "Eigenschaften..." ); - mnuFile.add( this.mnuFileProp ); - mnuFile.addSeparator(); + this.mnuFileFind = createJMenuItem( "Im Verzeichnis suchen..." ); + mnuFile.add( this.mnuFileFind ); this.mnuFileRefresh = createJMenuItem( "Aktualisieren" ); mnuFile.add( this.mnuFileRefresh ); @@ -1882,25 +1075,11 @@ private FileBrowserFrm( ScreenFrm screenFrm ) this.mnuFileClose = createJMenuItem( "Schlie\u00DFen" ); mnuFile.add( this.mnuFileClose ); + this.mnuPopupCreateDir = createJMenuItem( "Verzeichnis erstellen..." ); + this.mnuPopup.add( this.mnuPopupCreateDir ); - // Menu Bearbeiten - JMenu mnuEdit = new JMenu( "Bearbeiten" ); - mnuEdit.setMnemonic( KeyEvent.VK_B ); - - this.mnuEditPathCopy = createJMenuItem( - "Vollst\u00E4ndiger Datei-/Verzeichnisname kopieren" ); - mnuEdit.add( this.mnuEditPathCopy ); - - this.mnuEditURLCopy = createJMenuItem( - "Datei-/Verzeichnisname als URL kopieren" ); - mnuEdit.add( this.mnuEditURLCopy ); - mnuEdit.addSeparator(); - - this.mnuEditFileCopy = createJMenuItem( "Kopieren" ); - mnuEdit.add( this.mnuEditFileCopy ); - - this.mnuEditFilePaste = createJMenuItem( "Einf\u00FCgen" ); - mnuEdit.add( this.mnuEditFilePaste ); + this.mnuPopupFind = createJMenuItem( "Im Verzeichnis suchen..." ); + this.mnuPopup.add( this.mnuPopupFind ); // Menu Einstellungen @@ -1960,157 +1139,17 @@ private FileBrowserFrm( ScreenFrm screenFrm ) // Menu Hilfe JMenu mnuHelp = new JMenu( "?" ); - this.mnuHelpContent = createJMenuItem( "Hilfe..." ); - mnuHelp.add( this.mnuHelpContent ); - - - // Menu zusammenbauen - JMenuBar mnuBar = new JMenuBar(); - mnuBar.add( mnuFile ); - mnuBar.add( mnuEdit ); - mnuBar.add( mnuSettings ); - mnuBar.add( mnuHelp ); - setJMenuBar( mnuBar ); - - - // Popup-Menu - this.mnuPopup = new JPopupMenu(); - - if( this.screenFrm != null ) { - this.mnuPopupLoadIntoEmuOpt = createJMenuItem( - "In Emulator laden mit..." ); - this.mnuPopup.add( this.mnuPopupLoadIntoEmuOpt ); - - this.mnuPopupLoadIntoEmu = createJMenuItem( "In Emulator laden" ); - this.mnuPopup.add( this.mnuPopupLoadIntoEmu ); - - this.mnuPopupStartInEmu = createJMenuItem( "Im Emulator starten" ); - this.mnuPopup.add( this.mnuPopupStartInEmu ); - } else { - this.mnuPopupLoadIntoEmuOpt = null; - this.mnuPopupLoadIntoEmu = null; - this.mnuPopupStartInEmu = null; - } - - this.mnuPopupEditText = createJMenuItem( "Im Texteditor \u00F6ffnen" ); - this.mnuPopup.add( this.mnuPopupEditText ); - - this.mnuPopupShowImage = createJMenuItem( "Im Bildbetrachter anzeigen" ); - this.mnuPopup.add( this.mnuPopupShowImage ); - - this.mnuPopupEditHex = createJMenuItem( "Im Hex-Editor \u00F6ffnen" ); - this.mnuPopup.add( this.mnuPopupEditHex ); - - this.mnuPopupDiffHex = createJMenuItem( - "Im Hex-Dateivergleicher \u00F6ffnen" ); - this.mnuPopup.add( this.mnuPopupDiffHex ); - - this.mnuPopupConvert = createJMenuItem( - "Im Dateikonverter \u00F6ffnen" ); - this.mnuPopup.add( this.mnuPopupConvert ); - - this.mnuPopupChecksum = createJMenuItem( - "Pr\u00FCfsumme/Hash-Wert berechnen..." ); - this.mnuPopup.add( this.mnuPopupChecksum ); - - this.mnuPopupAudioIn = createJMenuItem( - "In Audio/Kassette \u00F6ffnen..." ); - this.mnuPopup.add( this.mnuPopupAudioIn ); - - this.mnuPopupPlay = createJMenuItem( "Wiedergeben" ); - this.mnuPopup.add( this.mnuPopupPlay ); - - JMenu mnuPopupPlayAs = new JMenu( "Wiedergeben im" ); - this.mnuPopup.add( mnuPopupPlayAs ); - - this.mnuPopupPlayAC1 = createJMenuItem( "AC1-Format" ); - mnuPopupPlayAs.add( this.mnuPopupPlayAC1 ); - - this.mnuPopupPlayAC1Basic = createJMenuItem( "AC1-BASIC-Format" ); - mnuPopupPlayAs.add( this.mnuPopupPlayAC1Basic ); - - this.mnuPopupPlaySCCH = createJMenuItem( "AC1/LLC2-TurboSave-Format" ); - mnuPopupPlayAs.add( this.mnuPopupPlaySCCH ); - - this.mnuPopupPlayKC85 = createJMenuItem( - "KC-Format (HC900, KC85/2..5, KC-BASIC)" ); - mnuPopupPlayAs.add( this.mnuPopupPlayKC85 ); - - this.mnuPopupPlayZ9001 = createJMenuItem( - "KC-Format (KC85/1, KC87, Z9001)" ); - mnuPopupPlayAs.add( this.mnuPopupPlayZ9001 ); - - this.mnuPopupPlayZ1013 = createJMenuItem( "Z1013-Format" ); - mnuPopupPlayAs.add( this.mnuPopupPlayZ1013 ); - - this.mnuPopupPlayZ1013HS = createJMenuItem( "Z1013-Headersave-Format" ); - mnuPopupPlayAs.add( this.mnuPopupPlayZ1013HS ); - - this.mnuPopupUnpack = createJMenuItem( "Entpacken..." ); - this.mnuPopup.add( this.mnuPopupUnpack ); - - JMenu mnuPopupPack = new JMenu( "Packen in" ); - this.mnuPopup.add( mnuPopupPack ); - - this.mnuPopupPackTar = createJMenuItem( "TAR-Archiv..." ); - mnuPopupPack.add( this.mnuPopupPackTar ); - - this.mnuPopupPackTgz = createJMenuItem( "TGZ-Archiv..." ); - mnuPopupPack.add( this.mnuPopupPackTgz ); - - this.mnuPopupPackZip = createJMenuItem( "Zip-Archiv..." ); - mnuPopupPack.add( this.mnuPopupPackZip ); - mnuPopupPack.addSeparator(); - - this.mnuPopupPackGZip = createJMenuItem( "GZip-Datei..." ); - mnuPopupPack.add( this.mnuPopupPackGZip ); - this.mnuPopup.addSeparator(); - - if( this.screenFrm != null ) { - this.mnuPopupRAMFloppy1Load = createJMenuItem( "In RAM-Floppy 1 laden" ); - this.mnuPopup.add( this.mnuPopupRAMFloppy1Load ); - - this.mnuPopupRAMFloppy2Load = createJMenuItem( "In RAM-Floppy 2 laden" ); - this.mnuPopup.add( this.mnuPopupRAMFloppy2Load ); - this.mnuPopup.addSeparator(); - } else { - this.mnuPopupRAMFloppy1Load = null; - this.mnuPopupRAMFloppy2Load = null; - } - - this.mnuPopupPathCopy = createJMenuItem( - "Vollst\u00E4ndiger Datei-/Verzeichnisname kopieren" ); - mnuPopup.add( this.mnuPopupPathCopy ); - - this.mnuPopupURLCopy = createJMenuItem( - "Datei-/Verzeichnisname als URL kopieren" ); - mnuPopup.add( this.mnuPopupURLCopy ); - - this.mnuPopupFileCopy = createJMenuItem( "Kopieren" ); - mnuPopup.add( this.mnuPopupFileCopy ); - - this.mnuPopupFilePaste = createJMenuItem( "Einf\u00FCgen" ); - mnuPopup.add( this.mnuPopupFilePaste ); - this.mnuPopup.addSeparator(); - - this.mnuPopupCreateDir = createJMenuItem( "Verzeichnis erstellen..." ); - this.mnuPopup.add( this.mnuPopupCreateDir ); - - this.mnuPopupRename = createJMenuItem( "Umbenennen..." ); - this.mnuPopup.add( this.mnuPopupRename ); - - this.mnuPopupDelete = createJMenuItem( - "L\u00F6schen", - KeyStroke.getKeyStroke( KeyEvent.VK_DELETE, 0 ) ); - this.mnuPopup.add( this.mnuPopupDelete ); + this.mnuHelpContent = createJMenuItem( "Hilfe..." ); + mnuHelp.add( this.mnuHelpContent ); - this.mnuPopupLastModified = createJMenuItem( - "\u00C4nderungszeitpunkt setzen..." ); - this.mnuPopup.add( this.mnuPopupLastModified ); - this.mnuPopup.addSeparator(); - this.mnuPopupProp = createJMenuItem( "Eigenschaften..." ); - this.mnuPopup.add( this.mnuPopupProp ); + // Menu zusammenbauen + JMenuBar mnuBar = new JMenuBar(); + mnuBar.add( mnuFile ); + mnuBar.add( mnuEdit ); + mnuBar.add( mnuSettings ); + mnuBar.add( mnuHelp ); + setJMenuBar( mnuBar ); // Fensterinhalt @@ -2134,34 +1173,12 @@ private FileBrowserFrm( ScreenFrm screenFrm ) add( toolBar, gbc ); if( this.screenFrm != null ) { - this.btnLoadIntoEmu = createImageButton( - "/images/file/load.png", - "In Emulator laden" ); - toolBar.add( this.btnLoadIntoEmu ); - - this.btnStartInEmu = createImageButton( - "/images/file/start.png", - "Im Emulator starten" ); - toolBar.add( this.btnStartInEmu ); - } else { - this.btnLoadIntoEmu = null; - this.btnStartInEmu = null; + toolBar.add( this.fileActionMngr.createLoadIntoEmuButton() ); + toolBar.add( this.fileActionMngr.createStartInEmuButton() ); } - - this.btnEditText = createImageButton( - "/images/file/edit.png", - "Im Texteditor \u00F6ffnen" ); - toolBar.add( this.btnEditText ); - - this.btnShowImage = createImageButton( - "/images/file/image.png", - "Im Bildbetrachter anzeigen" ); - toolBar.add( this.btnShowImage ); - - this.btnPlay = createImageButton( - "/images/file/play.png", - "Wiedergeben" ); - toolBar.add( this.btnPlay ); + toolBar.add( this.fileActionMngr.createEditTextButton() ); + toolBar.add( this.fileActionMngr.createViewImageButton() ); + toolBar.add( this.fileActionMngr.createPlayButton() ); // Dateibaum @@ -2172,14 +1189,14 @@ private FileBrowserFrm( ScreenFrm screenFrm ) this.rootNode = new FileNode( null, null, true ); this.rootNode.refresh( null, - true, this.mnuHiddenFiles.isSelected(), - getFileComparator() ); + getFileTreeNodeComparator() ); this.treeModel = new DefaultTreeModel( this.rootNode ); this.tree = new JTree( this.treeModel ); this.tree.setSelectionModel( selModel ); this.tree.setEditable( false ); + this.tree.setExpandsSelectedPaths( false ); this.tree.setRootVisible( false ); this.tree.setScrollsOnExpand( true ); this.tree.setShowsRootHandles( true ); @@ -2232,12 +1249,26 @@ private FileBrowserFrm( ScreenFrm screenFrm ) } - // Dateibaum als Drag-Quelle - DragSource dragSource = DragSource.getDefaultDragSource(); - dragSource.createDefaultDragGestureRecognizer( - this.tree, - DnDConstants.ACTION_COPY, - this ); + // Dateibaum und Tabelle als Drag-Quelle + this.tree.setDragEnabled( false ); + DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer( + this.tree, + DnDConstants.ACTION_COPY_OR_MOVE, + this ); + if( this.table != null ) { + this.table.setDragEnabled( false ); + DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer( + this.table, + DnDConstants.ACTION_COPY | DnDConstants.ACTION_MOVE, + this ); + } + + + // Dateibaum und Tabelle als Drop-Ziel + (new DropTarget( this.tree, this )).setActive( true ); + if( this.table != null ) { + (new DropTarget( this.table, this )).setActive( true ); + } // Fenstergroesse @@ -2248,57 +1279,27 @@ private FileBrowserFrm( ScreenFrm screenFrm ) // sonstiges + updPasteState(); updActionButtons(); - updFilePasteState( true ); } /* --- private Methoden --- */ - private File askForOutputFile( - File srcFile, - String title, - String presetName ) + private boolean checkDropAction( DropTargetEvent e ) { - File preSelection = null; - File parentFile = srcFile.getParentFile(); - if( presetName != null ) { - if( parentFile != null ) { - preSelection = new File( parentFile, presetName ); - } else { - preSelection = new File( presetName ); - } - } else { - preSelection = parentFile; - } - File file = EmuUtil.showFileSaveDlg( this, title, preSelection ); - if( file != null ) { - if( file.exists() ) { - if( file.equals( srcFile ) ) { - BasicDlg.showErrorDlg( - this, - "Die Ausgabedatei kann nicht\n" - + "mit der Quelldatei identisch sein." ); - file = null; - } - else if( file.isFile() ) { - if( !BasicDlg.showYesNoDlg( - this, - file.getPath() + " existiert bereits.\n" - + "M\u00F6chten Sie die Datei \u00FCberschreiben?" ) ) - { - file = null; - } - } else { - BasicDlg.showErrorDlg( - this, - file.getPath() + " existiert bereits\n" - + "und kann nicht als Datei angelegt werden." ); - file = null; - } - } + int dropAction = 0; + int sourceActions = 0; + if( e instanceof DropTargetDragEvent ) { + dropAction = ((DropTargetDragEvent) e).getDropAction(); + sourceActions = ((DropTargetDragEvent) e).getSourceActions(); + } else if( e instanceof DropTargetDropEvent ) { + dropAction = ((DropTargetDropEvent) e).getDropAction(); + sourceActions = ((DropTargetDropEvent) e).getSourceActions(); } - return file; + return ((dropAction & sourceActions) != 0) + && ((dropAction == DnDConstants.ACTION_COPY) + || (dropAction == DnDConstants.ACTION_MOVE)); } @@ -2313,7 +1314,7 @@ private boolean checkPopup( MouseEvent e ) if( c != null ) { /* - * Wenn die Zeile unter der Click-Punkt selektiert ist, + * Wenn die Zeile unter dem Click-Punkt selektiert ist, * wird das Popupmenu angezeigt, * anderenfalls wird erst einmal selektiert. */ @@ -2354,11 +1355,176 @@ else if( c == this.tree ) { } - private FileComparator getFileComparator() + private void collapseNode( TreeNode node ) + { + if( node != null ) { + try { + TreeNode[] path = this.treeModel.getPathToRoot( node ); + if( path != null ) { + this.tree.collapsePath( new TreePath( path ) ); + } + } + catch( Exception ex ) {} + } + } + + + private void dragInternal( DropTargetDragEvent e, boolean dragEnter ) + { + boolean status = false; + if( checkDropAction( e ) ) { + DropTargetContext context = e.getDropTargetContext(); + if( context != null ) { + Component c = context.getComponent(); + if( c == this.tree ) { + if( dragEnter ) { + this.tree.clearSelection(); + } + Point point = e.getLocation(); + if( point != null ) { + TreePath tp = this.tree.getPathForLocation( point.x, point.y ); + if( tp != null ) { + TreePath dirPath = null; + while( tp != null ) { + Object o = tp.getLastPathComponent(); + if( o != null ) { + if( o instanceof FileNode ) { + Path path = ((FileNode) o).getPath(); + if( path != null ) { + if( Files.isDirectory( + path, + LinkOption.NOFOLLOW_LINKS ) ) + { + dirPath = tp; + break; + } + } + } + } + tp = tp.getParentPath(); + } + if( dirPath != null ) { + fireSelectPath( dirPath ); + status = true; + } + } + } + } else if( c == this.table ) { + if( this.tree.getSelectionCount() == 1 ) { + Object o = this.tree.getLastSelectedPathComponent(); + if( o != null ) { + if( o instanceof FileNode ) { + Path path = ((FileNode) o).getPath(); + if( path != null ) { + if( Files.isDirectory( path, LinkOption.NOFOLLOW_LINKS ) ) { + status = true; + } + } + } + } + } + } + } + } + if( status ) { + e.acceptDrag( DnDConstants.ACTION_COPY_OR_MOVE ); + } else { + e.rejectDrag(); + } + } + + + private void fireRefreshNodeFor( final Path path ) + { + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + refreshNodeFor( path ); + } + } ); + } + + + private void fireRefreshParentNodesFor( Collection files ) + { + if( files != null ) { + Set parents = EmuUtil.createPathSet(); + for( Object o : files ) { + if( o != null ) { + Path parent = null; + if( o instanceof File ) { + File parentFile = ((File) o).getParentFile(); + if( parentFile != null ) { + try { + parent = parentFile.toPath(); + } + catch( InvalidPathException ex ) {} + } + } else if( o instanceof Path ) { + parent = ((Path) o).getParent(); + } + if( parent != null ) { + parents.add( parent ); + } + } + } + for( Path parent : parents ) { + fireRefreshNodeFor( parent ); + } + } + } + + + private void fireSelectNode( TreeNode node ) + { + if( node != null ) { + TreeNode[] path = this.treeModel.getPathToRoot( node ); + if( path != null ) { + fireSelectPath( new TreePath( path ) ); + } + } + } + + + private void fireSelectPath( final TreePath path ) + { + if( path != null ) { + final JTree tree = this.tree; + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + tree.setSelectionPath( path ); + } + } ); + } + } + + + private void fireUpdPreviewFld() + { + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + updPreviewFld(); + } + } ); + } + + + private FileTreeNodeComparator getFileTreeNodeComparator() { return this.mnuSortCaseSensitive.isSelected() ? - FileComparator.getCaseSensitiveInstance() - : FileComparator.getIgnoreCaseInstance(); + FileTreeNodeComparator.getCaseSensitiveInstance() + : FileTreeNodeComparator.getIgnoreCaseInstance(); } @@ -2408,8 +1574,9 @@ private FileNode getSelectedFileNode() if( this.tree.getSelectionCount() == 1 ) { Object o = this.tree.getLastSelectedPathComponent(); if( o != null ) { - if( o instanceof FileNode ) + if( o instanceof FileNode ) { rv = (FileNode) o; + } } } } @@ -2418,60 +1585,14 @@ private FileNode getSelectedFileNode() } - private java.util.List getSelectedFileNodes() + private java.util.List getSelectedFileObjects() { - java.util.List rv = null; + java.util.List rv = null; if( this.lastActiveFld != null ) { if( this.lastActiveFld == this.table ) { - TableModel tm = this.table.getModel(); - if( tm != null ) { - if( tm instanceof FileTableModel ) { - int[] rowNums = this.table.getSelectedRows(); - if( rowNums != null ) { - if( rowNums.length > 0 ) { - rv = new ArrayList( rowNums.length ); - for( int i = 0; i < rowNums.length; i++ ) { - int modelRow = this.table.convertRowIndexToModel( - rowNums[ i ] ); - if( modelRow >= 0 ) { - FileEntry entry = ((FileTableModel) tm).getRow( modelRow ); - if( entry != null ) { - if( entry instanceof ExtendedFileEntry ) { - FileNode fileNode = - ((ExtendedFileEntry) entry).getFileNode(); - if( fileNode != null ) { - rv.add( fileNode ); - } - } - } - } - } - } - } - } - } + rv = getSelectedTableFileObjects(); } else { - rv = getSelectedTreeFileNodes(); - } - } - return rv; - } - - - private java.util.List getSelectedFiles() - { - java.util.List rv = null; - Collection nodes = getSelectedFileNodes(); - if( nodes != null ) { - int n = nodes.size(); - if( n > 0 ) { - rv = new ArrayList( n ); - for( FileNode node : nodes ) { - File file = node.getFile(); - if( file != null ) { - rv.add( file ); - } - } + rv = getSelectedTreeFileObjects(); } } return rv; @@ -2483,16 +1604,16 @@ private java.util.List getSelectedFiles() * Wenn Vater und Nachkommen ausgewaehlt sind, * wird nur der Vater ermittelt. */ - private java.util.List getSelectedTreeFileNodes() + private java.util.List + getSelectedTreeFileObjects() { - java.util.List rv = null; - TreePath[] selectedPaths = this.tree.getSelectionPaths(); - if( selectedPaths != null ) { - if( selectedPaths.length > 0 ) { - Collection treePaths = new ArrayList( - selectedPaths.length ); - for( int i = 0; i < selectedPaths.length; i++ ) { - TreePath path = selectedPaths[ i ]; + java.util.List rv = null; + + TreePath[] paths = this.tree.getSelectionPaths(); + if( paths != null ) { + if( paths.length > 0 ) { + Collection treePaths = new ArrayList<>( paths.length ); + for( TreePath path : paths ) { Iterator iter = treePaths.iterator(); if( (path != null) && (iter != null) ) { try { @@ -2515,12 +1636,13 @@ private java.util.List getSelectedTreeFileNodes() } int n = treePaths.size(); if( n > 0 ) { - rv = new ArrayList( n ); + rv = new ArrayList<>( n ); for( TreePath path : treePaths ) { Object o = path.getLastPathComponent(); if( o != null ) { - if( o instanceof FileNode ) - rv.add( (FileNode) o ); + if( o instanceof FileActionMngr.FileObject ) { + rv.add( (FileActionMngr.FileObject) o ); + } } } } @@ -2530,124 +1652,77 @@ private java.util.List getSelectedTreeFileNodes() } - private boolean pasteFile( File dstDir, File srcFile ) - { - boolean done = false; - /* - * Unter Linux wurde in bestimmten Faellen der Effekt beobachtet, - * dass die in die Zwischenablage kopierte Dateiliste - * ein zusaetzliches File-Objekt mit einem leeren Pfad enthielt. - * Aus diesem Grund wird hier geprueft, ob im Pfad etwas steht. - */ - if( !srcFile.getPath().isEmpty() ) { - if( srcFile.isFile() ) { - String orgName = srcFile.getName(); - if( orgName != null ) { - if( !orgName.isEmpty() ) { - String fileName = orgName; - File dstFile = null; - while( fileName != null ) { - dstFile = new File( dstDir, fileName ); - if( dstFile.exists() ) { - dstFile = null; - - String text = JOptionPane.showInputDialog( - this, - "Einf\u00FCgen von " + orgName + ":\n" - + "Es existiert bereits eine Datei oder" - + " ein Verzeichnis mit diesem Namen.\n" - + "Geben Sie deshalb bitte einen anderen" - + " Dateinamen ein.\n\n" - + "Name der Datei:", - "Einf\u00FCgen einer Datei", - JOptionPane.WARNING_MESSAGE ); - if( text != null ) { - if( text.isEmpty() ) { - fileName = orgName; - } else { - fileName = text; + private java.util.List + getSelectedTableFileObjects() + { + java.util.List rv = null; + + TableModel tm = this.table.getModel(); + if( tm != null ) { + if( tm instanceof FileTableModel ) { + int[] rows = this.table.getSelectedRows(); + if( rows != null ) { + if( rows.length > 0 ) { + rv = new ArrayList<>( rows.length ); + for( int i = 0; i < rows.length; i++ ) { + int modelRow = this.table.convertRowIndexToModel( rows[ i ] ); + if( modelRow >= 0 ) { + FileEntry entry = ((FileTableModel) tm).getRow( modelRow ); + if( entry != null ) { + if( entry instanceof ExtendedFileEntry ) { + FileNode fileNode = + ((ExtendedFileEntry) entry).getFileNode(); + if( fileNode != null ) { + rv.add( fileNode ); + } } - } else { - fileName = null; } - } else { - break; - } - } - if( dstFile != null ) { - try { - done = EmuUtil.copyFile( srcFile, dstFile ); - } - catch( Exception ex ) { - BasicDlg.showErrorDlg( this, ex ); } } } } - } else { - if( srcFile.isDirectory() ) { - BasicDlg.showErrorDlg( - this, - "Das Einf\u00FCgen von Verzeichnissen\n" - + "wird nicht unterst\u00FCtzt." ); - } else { - BasicDlg.showErrorDlg( - this, - "Nicht regul\u00E4re Dateien k\u00F6nnen nicht\n" - + "eingef\u00FCgt werden." ); - } } } - return done; + return rv; } - private void refreshNode( FileNode node, boolean forceLoadChildren ) + private void refreshNode( FileNode node ) { if( node != null ) { node.refresh( this.treeModel, - forceLoadChildren, this.mnuHiddenFiles.isSelected(), - getFileComparator() ); - } - } - - - private void refreshNodes( Collection nodes ) - { - if( nodes != null ) { - for( FileNode node : nodes ) { - refreshNode( node, false ); - } + getFileTreeNodeComparator() ); } } - private void refreshNodeFor( File file ) + private void refreshNodeFor( Path path ) { - if( file != null ) { - this.rootNode.refreshNodeFor( - file, - this.treeModel, - false, - this.mnuHiddenFiles.isSelected(), - getFileComparator() ); - } - } + if( path != null ) { + final FileNode node = this.rootNode.refreshNodeFor( + path, + this.treeModel, + false, + this.mnuHiddenFiles.isSelected(), + getFileTreeNodeComparator() ); - - private void selectNode( TreeNode node ) - { - if( node != null ) { - TreeNode[] path = this.treeModel.getPathToRoot( node ); - if( path != null ) - this.tree.setSelectionPath( new TreePath( path ) ); + if( node != null ) { + if( this.tree.getSelectionCount() == 1 ) { + Object o = this.tree.getLastSelectedPathComponent(); + if( o != null ) { + if( o.equals( node ) ) { + setPreviewedFileNode( node ); + } + } + } + } } } - private void selectNode( TreeNode parent, File file ) + private void fireSelectNode( TreeNode parent, File file ) { if( (parent != null) && (file != null) ) { Object childNode = null; @@ -2677,282 +1752,81 @@ private void selectNode( TreeNode parent, File file ) Object[] path = new Object[ parentPath.length + 1 ]; System.arraycopy( parentPath, 0, path, 0, parentPath.length ); path[ path.length - 1 ] = childNode; - this.tree.setSelectionPath( new TreePath( path ) ); + fireSelectPath( new TreePath( path ) ); } } } } - private void selectTableRow( int row ) + private void setPreviewedFileNode( FileNode fileNode ) { - if( (row >= 0) && (row < this.table.getRowCount()) ) { - this.table.addRowSelectionInterval( row, row ); - } + this.filePreviewFld.setFileNode( + fileNode, + getPreviewMaxFileSize(), + this.mnuSortCaseSensitive.isSelected() ); } - private void showErrorMsgInternal( String msg ) + private void updActionButtons() { - BasicDlg.showErrorDlg( this, msg ); + updActionButtons( false ); } - private void updActionButtons() + private void updActionButtons( boolean pasteOnly ) { - int nNodes = 0; - int nFiles = 0; - int nDirs = 0; - File file = null; - FileNode fileNode = null; - Collection fileNodes = getSelectedFileNodes(); - if( fileNodes != null ) { - nNodes = fileNodes.size(); - for( FileNode tmpNode : fileNodes ) { - fileNode = tmpNode; - File tmpFile = tmpNode.getFile(); - if( tmpFile != null ) { - if( tmpFile.isFile() ) { - file = tmpFile; - nFiles++; - } - else if( tmpFile.isDirectory() ) { - nDirs++; - } - } + boolean stateOneDir = false; + boolean stateEntries = false; + java.util.List fileObjs + = getSelectedFileObjects(); + if( fileObjs != null ) { + int n = fileObjs.size(); + if( n > 0 ) { + stateEntries = true; } - } - if( nNodes > 1 ) { - fileNode = null; - file = null; - } - if( nFiles > 1 ) { - file = null; - } - boolean stateEntries = (nNodes > 0); - boolean stateOneEntry = (nNodes == 1); - boolean stateOneDir = ((nDirs == 1) && (nDirs == nNodes)); - boolean stateOneFile = ((nFiles == 1) && (nFiles == nNodes)); - boolean stateFilesOnly = ((nFiles > 0) && (nFiles == nNodes)); - boolean stateDirsFiles = ((nNodes > 0) && ((nFiles + nDirs) == nNodes)); - boolean stateStartable = false; - boolean stateImage = false; - boolean stateAudio = false; - boolean stateText = false; - boolean stateUnpackable = false; - if( fileNode != null ) { - boolean isAudio = fileNode.isAudioFile(); - boolean isHS = fileNode.isHeadersaveFile(); - boolean isBin = fileNode.isBinFile(); - boolean isKCBasicHead = fileNode.isKCBasicHeadFile(); - boolean isKCBasic = fileNode.isKCBasicFile(); - boolean isKCSys = fileNode.isKCSysFile(); - boolean isKC85Tap = fileNode.isKC85TapFile(); - boolean isZ9001Tap = fileNode.isZ9001TapFile(); - boolean isBasic60F7 = false; - if( isHS ) { - FileInfo info = fileNode.getFileInfo(); - if( info != null ) { - if( (info.getBegAddr() == 0x60F7) && (info.getFileType() == 'B') ) { - isBasic60F7 = true; - } + if( n == 1 ) { + Path path = fileObjs.get( 0 ).getPath(); + if( path != null ) { + stateOneDir = Files.isDirectory( path, LinkOption.NOFOLLOW_LINKS ); } } + } - this.mnuFilePlayAC1.setEnabled( isBin ); - this.mnuPopupPlayAC1.setEnabled( isBin ); - - this.mnuFilePlayAC1Basic.setEnabled( isBasic60F7 ); - this.mnuPopupPlayAC1Basic.setEnabled( isBasic60F7 ); - - this.mnuFilePlaySCCH.setEnabled( isBin || isBasic60F7 ); - this.mnuPopupPlaySCCH.setEnabled( isBin || isBasic60F7 ); - - this.mnuFilePlayKC85.setEnabled( - isKCBasicHead || isKCBasic || isKCSys || isKC85Tap ); - this.mnuPopupPlayKC85.setEnabled( - isKCBasicHead || isKCBasic || isKCSys || isKC85Tap ); - - this.mnuFilePlayZ9001.setEnabled( isKCSys || isZ9001Tap ); - this.mnuPopupPlayZ9001.setEnabled( isKCSys || isZ9001Tap ); - - this.mnuFilePlayZ1013.setEnabled( isBin ); - this.mnuPopupPlayZ1013.setEnabled( isBin ); - - this.mnuFilePlayZ1013HS.setEnabled( isHS ); - this.mnuPopupPlayZ1013HS.setEnabled( isHS ); - - this.mnuFileAudioIn.setEnabled( isAudio || isKC85Tap || isZ9001Tap ); - this.mnuPopupAudioIn.setEnabled( isAudio || isKC85Tap || isZ9001Tap ); + this.mnuEditPaste.setEnabled( stateOneDir && this.pasteState ); + this.mnuPopupPaste.setEnabled( stateOneDir && this.pasteState ); + if( !pasteOnly ) { this.mnuFileCreateDir.setEnabled( stateOneDir ); this.mnuPopupCreateDir.setEnabled( stateOneDir ); - this.mnuFileRename.setEnabled( stateOneEntry ); - this.mnuPopupRename.setEnabled( stateOneEntry ); - - this.mnuFileProp.setEnabled( stateOneEntry ); - this.mnuPopupProp.setEnabled( stateOneEntry ); - - stateStartable = (stateOneFile && fileNode.isStartableFile()); - stateImage = (stateOneFile && fileNode.isImageFile()); - stateAudio = (stateOneFile && isAudio); - stateText = (stateOneFile && !stateImage && !stateAudio); - stateUnpackable = (stateOneFile - && (fileNode.isArchiveFile() - || fileNode.isCompressedFile() - || fileNode.isNonPlainDiskFile() - || fileNode.isPlainDiskFile())); - - } else { - - this.mnuFilePlayAC1.setEnabled( false ); - this.mnuPopupPlayAC1.setEnabled( false ); - - this.mnuFilePlayAC1Basic.setEnabled( false ); - this.mnuPopupPlayAC1Basic.setEnabled( false ); - - this.mnuFilePlaySCCH.setEnabled( false ); - this.mnuPopupPlaySCCH.setEnabled( false ); - - this.mnuFilePlayKC85.setEnabled( false ); - this.mnuPopupPlayKC85.setEnabled( false ); - - this.mnuFilePlayZ9001.setEnabled( false ); - this.mnuPopupPlayZ9001.setEnabled( false ); - - this.mnuFilePlayZ1013.setEnabled( false ); - this.mnuPopupPlayZ1013.setEnabled( false ); - - this.mnuFilePlayZ1013HS.setEnabled( false ); - this.mnuPopupPlayZ1013HS.setEnabled( false ); - - this.mnuFileAudioIn.setEnabled( false ); - this.mnuPopupAudioIn.setEnabled( false ); - - this.mnuFileCreateDir.setEnabled( false ); - this.mnuPopupCreateDir.setEnabled( false ); - - this.mnuFileRename.setEnabled( false ); - this.mnuPopupRename.setEnabled( false ); - - this.mnuFileProp.setEnabled( false ); - this.mnuPopupProp.setEnabled( false ); - } - if( this.mnuFileLoadIntoEmuOpt != null ) { - this.mnuFileLoadIntoEmuOpt.setEnabled( stateOneFile ); - } - if( this.mnuFileLoadIntoEmu != null ) { - this.mnuFileLoadIntoEmu.setEnabled( stateOneFile ); - } - if( this.mnuPopupLoadIntoEmuOpt != null ) { - this.mnuPopupLoadIntoEmuOpt.setEnabled( stateOneFile ); - } - if( this.mnuPopupLoadIntoEmu != null ) { - this.mnuPopupLoadIntoEmu.setEnabled( stateOneFile ); - } - if( this.btnLoadIntoEmu != null ) { - this.btnLoadIntoEmu.setEnabled( stateOneFile ); - } - if( this.mnuFileStartInEmu != null ) { - this.mnuFileStartInEmu.setEnabled( stateStartable ); - } - if( this.mnuPopupStartInEmu != null ) { - this.mnuPopupStartInEmu.setEnabled( stateStartable ); - } - if( this.btnStartInEmu != null ) { - this.btnStartInEmu.setEnabled( stateStartable ); - } - - this.mnuFileShowImage.setEnabled( stateImage ); - this.mnuPopupShowImage.setEnabled( stateImage ); - this.btnShowImage.setEnabled( stateImage ); - - this.mnuFilePlay.setEnabled( stateAudio ); - this.mnuPopupPlay.setEnabled( stateAudio ); - this.btnPlay.setEnabled( stateAudio ); - - this.mnuFileEditText.setEnabled( stateText ); - this.mnuPopupEditText.setEnabled( stateText ); - this.btnEditText.setEnabled( stateText ); - - this.mnuFileEditHex.setEnabled( stateOneFile ); - this.mnuPopupEditHex.setEnabled( stateOneFile ); - - this.mnuFileDiffHex.setEnabled( stateFilesOnly ); - this.mnuPopupDiffHex.setEnabled( stateFilesOnly ); - - this.mnuFileConvert.setEnabled( stateOneFile ); - this.mnuPopupConvert.setEnabled( stateOneFile ); - - this.mnuFileChecksum.setEnabled( stateFilesOnly ); - this.mnuPopupChecksum.setEnabled( stateFilesOnly ); - - this.mnuFilePackGZip.setEnabled( stateOneFile ); - this.mnuPopupPackGZip.setEnabled( stateOneFile ); - - this.mnuFilePackTar.setEnabled( stateDirsFiles ); - this.mnuPopupPackTar.setEnabled( stateDirsFiles ); + this.mnuFileFind.setEnabled( stateOneDir ); + this.mnuPopupFind.setEnabled( stateOneDir ); - this.mnuFilePackTgz.setEnabled( stateDirsFiles ); - this.mnuPopupPackTgz.setEnabled( stateDirsFiles ); + this.mnuEditCut.setEnabled( stateEntries ); + this.mnuPopupCut.setEnabled( stateEntries ); - this.mnuFilePackZip.setEnabled( stateDirsFiles ); - this.mnuPopupPackZip.setEnabled( stateDirsFiles ); - - this.mnuFileUnpack.setEnabled( stateUnpackable ); - this.mnuPopupUnpack.setEnabled( stateUnpackable ); - - if( this.mnuFileRAMFloppy1Load != null ) { - this.mnuFileRAMFloppy1Load.setEnabled( stateOneFile ); - } - if( this.mnuPopupRAMFloppy1Load != null ) { - this.mnuPopupRAMFloppy1Load.setEnabled( stateOneFile ); - } - if( this.mnuFileRAMFloppy2Load != null ) { - this.mnuFileRAMFloppy2Load.setEnabled( stateOneFile ); + this.fileActionMngr.updActionButtonsEnabled( fileObjs ); } - if( this.mnuPopupRAMFloppy2Load != null ) { - this.mnuPopupRAMFloppy2Load.setEnabled( stateOneFile ); - } - - this.mnuFileLastModified.setEnabled( stateEntries ); - this.mnuPopupLastModified.setEnabled( stateEntries ); - - this.mnuFileDelete.setEnabled( stateDirsFiles ); - this.mnuPopupDelete.setEnabled( stateDirsFiles ); - - this.mnuEditPathCopy.setEnabled( stateOneDir || stateOneFile ); - this.mnuPopupPathCopy.setEnabled( stateOneDir || stateOneFile ); - - this.mnuEditURLCopy.setEnabled( stateOneDir || stateOneFile ); - this.mnuPopupURLCopy.setEnabled( stateOneDir || stateOneFile ); - - this.mnuEditFileCopy.setEnabled( stateEntries ); - this.mnuPopupFileCopy.setEnabled( stateEntries ); - - this.mnuEditFilePaste.setEnabled( stateOneDir && this.filePasteState ); - this.mnuPopupFilePaste.setEnabled( stateOneDir && this.filePasteState ); } - private void updFilePasteState( boolean force ) + private void updPasteState() { - boolean state = false; - try { - if( this.clipboard != null ) { + boolean state = !this.cutFiles.isEmpty(); + if( !state && (this.clipboard != null)) { + try { state = this.clipboard.isDataFlavorAvailable( DataFlavor.javaFileListFlavor ); } + catch( Exception ex ) {} } - catch( IllegalStateException ex ) {} - this.filePasteState = state; - this.mnuEditFilePaste.setEnabled( state ); - this.mnuPopupFilePaste.setEnabled( state ); + this.pasteState = state; + updActionButtons( true ); } - private void updPreview() + private void updPreviewFld() { FileNode fileNode = null; if( this.tree.getSelectionCount() == 1 ) { @@ -2960,14 +1834,9 @@ private void updPreview() if( o != null ) { if( o instanceof FileNode ) { fileNode = (FileNode) o; - refreshNode( fileNode, true ); } } } - this.filePreviewFld.setFileNode( - fileNode, - getPreviewMaxFileSize(), - this.mnuSortCaseSensitive.isSelected() ); + setPreviewedFileNode( fileNode ); } } - diff --git a/src/jkcemu/filebrowser/FileCheckResult.java b/src/jkcemu/filebrowser/FileCheckResult.java new file mode 100644 index 0000000..e90326a --- /dev/null +++ b/src/jkcemu/filebrowser/FileCheckResult.java @@ -0,0 +1,404 @@ +/* + * (c) 2014-2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Informationen ueber eine Datei + */ + +package jkcemu.filebrowser; + +import java.io.*; +import java.lang.*; +import jkcemu.audio.AudioUtil; +import jkcemu.base.*; +import jkcemu.disk.*; +import jkcemu.image.ImgLoader; +import jkcemu.text.TextUtil; + + +public class FileCheckResult +{ + private int hsFileType; + private boolean audioFile; + private boolean archiveFile; + private boolean binFile; + private boolean compressedFile; + private boolean headersaveFile; + private boolean imageFile; + private boolean kcBasicHeadFile; + private boolean kcBasicFile; + private boolean kcSysFile; + private boolean nonPlainDiskFile; + private boolean plainDiskFile; + private boolean tapeFile; + private boolean textFile; + private boolean kc85TapFile; + private boolean z9001TapFile; + private boolean startableFile; + private FileInfo fileInfo; + + + public static FileCheckResult checkFile( File file ) + { + FileCheckResult rv = null; + if( file != null ) { + int hsFileType = -1; + boolean audioFile = false; + boolean archiveFile = false; + boolean binFile = false; + boolean compressedFile = false; + boolean headersaveFile = false; + boolean imageFile = false; + boolean kcBasicHeadFile = false; + boolean kcBasicFile = false; + boolean kcSysFile = false; + boolean kc85TapFile = false; + boolean z9001TapFile = false; + boolean nonPlainDiskFile = false; + boolean plainDiskFile = false; + boolean tapeFile = false; + boolean textFile = false; + boolean startableFile = false; + FileInfo fileInfo = null; + + if( file.isFile() && file.canRead() ) { + boolean done = false; + + // Sound-Datei pruefen + if( AudioUtil.isAudioFile( file ) ) { + audioFile = true; + done = true; + } + + // Bilddatei pruefen + if( ImgLoader.accepts( file ) ) { + imageFile = true; + done = true; + } + + /* + * Dateiextension pruefen, + * auch bei Audio und Bilddatei (z.B. wegen *.img und *.gz) + */ + String fName = file.getName(); + if( fName != null ) { + fName = fName.toLowerCase(); + if( TextUtil.endsWith( + fName, + EmuUtil.archiveFileExtensions ) ) + { + archiveFile = true; + done = true; + } + else if( TextUtil.endsWith( fName, DiskUtil.anaDiskFileExt ) + || TextUtil.endsWith( fName, DiskUtil.copyQMFileExt ) + || TextUtil.endsWith( fName, DiskUtil.dskFileExt ) + || TextUtil.endsWith( fName, DiskUtil.imageDiskFileExt ) + || TextUtil.endsWith( fName, DiskUtil.teleDiskFileExt ) ) + { + nonPlainDiskFile = true; + done = true; + } + else if( TextUtil.endsWith( fName, DiskUtil.plainDiskFileExt ) ) { + plainDiskFile = true; + done = true; + } + else if( TextUtil.endsWith( + fName, + DiskUtil.gzAnaDiskFileExt ) + || TextUtil.endsWith( + fName, + DiskUtil.gzCopyQMFileExt ) + || TextUtil.endsWith( + fName, + DiskUtil.gzDskFileExt ) + || TextUtil.endsWith( + fName, + DiskUtil.gzImageDiskFileExt ) + || TextUtil.endsWith( + fName, + DiskUtil.gzTeleDiskFileExt ) ) + { + nonPlainDiskFile = true; + compressedFile = true; + done = true; + } + else if( TextUtil.endsWith( + fName, + DiskUtil.gzPlainDiskFileExt ) ) + { + plainDiskFile = true; + compressedFile = true; + done = true; + } + else if( TextUtil.endsWith( + fName, + AudioUtil.tapeFileExtensions ) ) + { + tapeFile = true; + done = true; + } + else if( TextUtil.endsWith( + fName, + EmuUtil.textFileExtensions ) ) + { + textFile = true; + done = true; + } + else if( fName.endsWith( ".bin" ) ) { + binFile = true; + done = true; + } + else if( fName.endsWith( ".gz" ) ) { + compressedFile = true; + done = true; + } + } + + // Kopfdaten ermitteln + if( !done ) { + InputStream in = null; + try { + in = new FileInputStream( file ); + + byte[] header = new byte[ 40 ]; + int headerLen = EmuUtil.read( in, header ); + if( headerLen >= 3 ) { + if( AbstractFloppyDisk.isDiskFileHeader( header ) ) { + nonPlainDiskFile = true; + } else { + fileInfo = FileInfo.analyzeFile( header, headerLen, file ); + if( fileInfo != null ) { + if( fileInfo.equalsFileFormat( + FileFormat.KCBASIC_HEAD_PRG ) + || fileInfo.equalsFileFormat( + FileFormat.KCBASIC_HEAD_DATA ) + || fileInfo.equalsFileFormat( + FileFormat.KCBASIC_HEAD_ASC ) ) + { + kcBasicHeadFile = true; + } + else if( fileInfo.equalsFileFormat( + FileFormat.KCBASIC_PRG ) ) + { + kcBasicFile = true; + } + else if( fileInfo.equalsFileFormat( FileFormat.KCB ) + || fileInfo.equalsFileFormat( FileFormat.KCC ) ) + { + kcSysFile = true; + } + else if( fileInfo.equalsFileFormat( + FileFormat.KCTAP_KC85 ) + || fileInfo.equalsFileFormat( + FileFormat.KCTAP_BASIC_PRG ) + || fileInfo.equalsFileFormat( + FileFormat.KCTAP_BASIC_DATA ) + || fileInfo.equalsFileFormat( + FileFormat.KCTAP_BASIC_ASC ) ) + { + kc85TapFile = true; + } + else if( fileInfo.equalsFileFormat( + FileFormat.KCTAP_Z9001 ) ) + { + z9001TapFile = true; + } + int begAddr = fileInfo.getBegAddr(); + int endAddr = fileInfo.getEndAddr(); + int startAddr = fileInfo.getStartAddr(); + if( (startAddr >= 0) + && (startAddr >= begAddr) + && (startAddr <= endAddr) ) + { + startableFile = true; + } + FileFormat fileFmt = fileInfo.getFileFormat(); + if( fileFmt != null ) { + if( fileFmt.equals( FileFormat.HEADERSAVE ) ) { + headersaveFile = true; + hsFileType = fileInfo.getFileType(); + } + } + } + } + } + } + catch( Exception ex ) {} + finally { + EmuUtil.doClose( in ); + } + } + } + rv = new FileCheckResult( + hsFileType, + audioFile, + archiveFile, + binFile, + compressedFile, + headersaveFile, + imageFile, + kcBasicHeadFile, + kcBasicFile, + kcSysFile, + nonPlainDiskFile, + plainDiskFile, + textFile, + kc85TapFile, + z9001TapFile, + startableFile, + fileInfo ); + } + return rv; + } + + + public FileInfo getFileInfo() + { + return this.fileInfo; + } + + + public int getHeadersaveFileType() + { + return this.hsFileType; + } + + + public boolean isAudioFile() + { + return this.audioFile; + } + + + public boolean isArchiveFile() + { + return this.archiveFile; + } + + + public boolean isBinFile() + { + return this.binFile; + } + + + public boolean isCompressedFile() + { + return this.compressedFile; + } + + + public boolean isHeadersaveFile() + { + return this.headersaveFile; + } + + + public boolean isImageFile() + { + return this.imageFile; + } + + + public boolean isKC85TapFile() + { + return this.kc85TapFile; + } + + + public boolean isKCBasicHeadFile() + { + return this.kcBasicHeadFile; + } + + + public boolean isKCBasicFile() + { + return this.kcBasicFile; + } + + + public boolean isKCSysFile() + { + return this.kcSysFile; + } + + + public boolean isNonPlainDiskFile() + { + return this.nonPlainDiskFile; + } + + + public boolean isPlainDiskFile() + { + return this.plainDiskFile; + } + + + public boolean isStartableFile() + { + return this.startableFile; + } + + + public boolean isTapeFile() + { + return this.tapeFile; + } + + + public boolean isTextFile() + { + return this.textFile; + } + + + public boolean isZ9001TapFile() + { + return this.z9001TapFile; + } + + + /* --- Konstruktor --- */ + + private FileCheckResult( + int hsFileType, + boolean audioFile, + boolean archiveFile, + boolean binFile, + boolean compressedFile, + boolean headersaveFile, + boolean imageFile, + boolean kcBasicHeadFile, + boolean kcBasicFile, + boolean kcSysFile, + boolean nonPlainDiskFile, + boolean plainDiskFile, + boolean textFile, + boolean kc85TapFile, + boolean z9001TapFile, + boolean startableFile, + FileInfo fileInfo ) + { + this.hsFileType = hsFileType; + this.audioFile = audioFile; + this.archiveFile = archiveFile; + this.binFile = binFile; + this.compressedFile = compressedFile; + this.headersaveFile = headersaveFile; + this.imageFile = imageFile; + this.kcBasicHeadFile = kcBasicHeadFile; + this.kcBasicFile = kcBasicFile; + this.kcSysFile = kcSysFile; + this.nonPlainDiskFile = nonPlainDiskFile; + this.plainDiskFile = plainDiskFile; + this.textFile = textFile; + this.kc85TapFile = kc85TapFile; + this.z9001TapFile = z9001TapFile; + this.startableFile = startableFile; + this.fileInfo = fileInfo; + } +} diff --git a/src/jkcemu/filebrowser/FileChecksumFrm.java b/src/jkcemu/filebrowser/FileChecksumFrm.java index f3c029f..0488342 100644 --- a/src/jkcemu/filebrowser/FileChecksumFrm.java +++ b/src/jkcemu/filebrowser/FileChecksumFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -33,25 +33,25 @@ public class FileChecksumFrm extends BasicFrm private static FileChecksumFrm instance = null; - private JMenuItem mnuClose; - private JMenuItem mnuCopyUpper; - private JMenuItem mnuCopyLower; - private JMenuItem mnuCompare; - private JMenuItem mnuHelpContent; - private JPopupMenu mnuPopup; - private JMenuItem mnuPopupCopyUpper; - private JMenuItem mnuPopupCopyLower; - private JMenuItem mnuPopupCompare; - private JLabel labelAlgorithm; - private JComboBox comboAlgorithm; - private JButton btnAction; - private JTable table; - private FileTableModel tableModel; - private Thread thread; - private String algorithm; - private CksCalculator cks; - private volatile boolean cancelled; - private volatile boolean filesChanged; + private JMenuItem mnuClose; + private JMenuItem mnuCopyUpper; + private JMenuItem mnuCopyLower; + private JMenuItem mnuCompare; + private JMenuItem mnuHelpContent; + private JPopupMenu mnuPopup; + private JMenuItem mnuPopupCopyUpper; + private JMenuItem mnuPopupCopyLower; + private JMenuItem mnuPopupCompare; + private JLabel labelAlgorithm; + private JComboBox comboAlgorithm; + private JButton btnAction; + private JTable table; + private FileTableModel tableModel; + private Thread thread; + private String algorithm; + private CksCalculator cks; + private volatile boolean cancelled; + private volatile boolean filesChanged; public static void open() @@ -377,7 +377,7 @@ private FileChecksumFrm() this.labelAlgorithm.setEnabled( false ); add( this.labelAlgorithm, gbc ); - this.comboAlgorithm = new JComboBox( + this.comboAlgorithm = new JComboBox<>( CksCalculator.getAvailableAlgorithms() ); this.comboAlgorithm.setEditable( false ); this.comboAlgorithm.setEnabled( false ); @@ -560,6 +560,7 @@ private void doCalculate() this.cancelled = false; this.filesChanged = false; this.thread = new Thread( + Main.getThreadGroup(), this, "JKCEMU Checksum Calculator" ); this.thread.start(); diff --git a/src/jkcemu/filebrowser/FileComparator.java b/src/jkcemu/filebrowser/FileComparator.java deleted file mode 100644 index 20445d1..0000000 --- a/src/jkcemu/filebrowser/FileComparator.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * (c) 2008-2010 Jens Mueller - * - * Kleincomputer-Emulator - * - * Vergleicher fuer Dateien - * - * Beim Sortieren der Dateisystemwurzeln wird nach dem abstrakten Pfad sortiert. - * Ansonsten vergleicht dieser Comparator nur die Dateinamen - * und stellt sicher, dass Verzeichnisse vor den Dateien gestellt werden. - */ - -package jkcemu.filebrowser; - -import java.lang.*; -import java.io.File; - - -public class FileComparator implements java.util.Comparator -{ - private static FileComparator caseSensitiveInstance = null; - private static FileComparator ignoreCaseInstance = null; - - private boolean caseSensitive; - private boolean forFileSystemRoots; - - - public static FileComparator getCaseSensitiveInstance() - { - if( caseSensitiveInstance == null ) { - caseSensitiveInstance = new FileComparator( true ); - } - return caseSensitiveInstance; - } - - - public static FileComparator getIgnoreCaseInstance() - { - if( ignoreCaseInstance == null ) { - ignoreCaseInstance = new FileComparator( false ); - } - return ignoreCaseInstance; - } - - - public void setForFileSystemRoots( boolean state ) - { - this.forFileSystemRoots = state; - } - - - /* --- Comparator --- */ - - @Override - public int compare( File f1, File f2 ) - { - int rv = -1; - if( (f1 != null) && (f2 != null) ) { - if( this.forFileSystemRoots ) { - String s1 = f1.getPath(); - String s2 = f2.getPath(); - if( s1 == null ) { - s1 = ""; - } - if( s2 == null ) { - s2 = ""; - } - if( this.caseSensitive ) { - rv = s1.compareTo( s2 ); - } else { - rv = s1.compareToIgnoreCase( s2 ); - } - } else { - if( f1.isDirectory() && !f2.isDirectory() ) { - rv = -1; - } else if( !f1.isDirectory() && f2.isDirectory() ) { - rv = 1; - } else { - String s1 = f1.getName(); - String s2 = f2.getName(); - if( s1 == null ) { - s1 = ""; - } - if( s2 == null ) { - s2 = ""; - } - if( this.caseSensitive ) { - rv = s1.compareTo( s2 ); - } else { - rv = s1.compareToIgnoreCase( s2 ); - } - } - } - } - return rv; - } - - - @Override - public boolean equals( Object o ) - { - return o == this; - } - - - /* --- private Konstruktoren --- */ - - private FileComparator( boolean caseSensitive ) - { - this.caseSensitive = caseSensitive; - this.forFileSystemRoots = false; - } -} - diff --git a/src/jkcemu/filebrowser/FileInfoFld.java b/src/jkcemu/filebrowser/FileInfoFld.java index 66c6a56..80ec940 100644 --- a/src/jkcemu/filebrowser/FileInfoFld.java +++ b/src/jkcemu/filebrowser/FileInfoFld.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2011 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -19,6 +19,7 @@ public class FileInfoFld extends Component { public enum Item { NAME, + LINKED_TO, TYPE, FORMAT, DURATION, @@ -32,22 +33,22 @@ public enum Item { private static final int LABEL_VALUE_DISTANCE = 10; - private int minRows; - private Integer rowHeight; - private String[] labels; - private String[] values; - private String[] addonRows; - private DateFormat dateFmt; + private int minRows; + private Integer rowHeight; + private String[] labels; + private String[] values; + private String[] addonRows; + private DateFormat dateFmt; FileInfoFld( int minRows ) { - this.minRows = minRows; - this.rowHeight = null; - this.labels = null; - this.values = null; - this.addonRows = null; - this.dateFmt = DateFormat.getDateTimeInstance( + this.minRows = minRows; + this.rowHeight = null; + this.labels = null; + this.values = null; + this.addonRows = null; + this.dateFmt = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM ); } @@ -63,8 +64,8 @@ public void setValues( Map items, String[] addonRows ) { - Collection labels = new ArrayList(); - Collection values = new ArrayList(); + Collection labels = new ArrayList<>(); + Collection values = new ArrayList<>(); Object item = items.get( Item.NAME ); if( item != null ) { @@ -72,6 +73,11 @@ public void setValues( if( !s.isEmpty() ) { labels.add( "Name:" ); values.add( item.toString() ); + item = items.get( Item.LINKED_TO ); + if( item != null ) { + labels.add( "Symbolischer Link auf:" ); + values.add( item.toString() ); + } item = items.get( Item.TYPE ); if( item != null ) { labels.add( "Typ:" ); @@ -144,7 +150,7 @@ public void setValues( items, Item.LAST_MODIFIED ); if( lastModifiedText != null ) { - labels.add( "Ge\u00E4ndert:" ); + labels.add( "Zuletzt ge\u00E4ndert:" ); values.add( lastModifiedText ); } } diff --git a/src/jkcemu/filebrowser/FileListSelection.java b/src/jkcemu/filebrowser/FileListSelection.java index 65a2a3f..a4bf955 100644 --- a/src/jkcemu/filebrowser/FileListSelection.java +++ b/src/jkcemu/filebrowser/FileListSelection.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -21,7 +21,8 @@ public class FileListSelection implements ClipboardOwner, Transferable public FileListSelection( Collection files ) { - this.files = new Vector( files.size() ); + int n = files.size(); + this.files = new ArrayList<>( n > 0 ? n : 1 ); this.files.addAll( files ); } diff --git a/src/jkcemu/filebrowser/FileNode.java b/src/jkcemu/filebrowser/FileNode.java index bac47d6..4c16d0f 100644 --- a/src/jkcemu/filebrowser/FileNode.java +++ b/src/jkcemu/filebrowser/FileNode.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -10,6 +10,7 @@ import java.io.*; import java.lang.*; +import java.nio.file.*; import java.util.*; import javax.sound.sampled.*; import javax.swing.tree.*; @@ -22,54 +23,19 @@ public class FileNode extends FileTreeNode + implements FileActionMngr.FileObject { - private int hsFileType; - private boolean fileChecked; - private boolean audioFile; - private boolean archiveFile; - private boolean binFile; - private boolean compressedFile; - private boolean headersaveFile; - private boolean imageFile; - private boolean kcBasicHeadFile; - private boolean kcBasicFile; - private boolean kcSysFile; - private boolean nonPlainDiskFile; - private boolean plainDiskFile; - private boolean textFile; - private boolean kc85TapFile; - private boolean z9001TapFile; - private boolean startableFile; - private FileInfo fileInfo; - private Map fileToChild; + private FileCheckResult fileCheckResult; - public FileNode( TreeNode parent, File file, boolean fileSystemRoot ) + public FileNode( TreeNode parent, Path path, boolean fileSystemRoot ) { - super( parent, file, fileSystemRoot ); - this.hsFileType = -1; - this.fileChecked = false; - this.audioFile = false; - this.archiveFile = false; - this.binFile = false; - this.compressedFile = false; - this.headersaveFile = false; - this.imageFile = false; - this.kcBasicHeadFile = false; - this.kcBasicFile = false; - this.kcSysFile = false; - this.nonPlainDiskFile = false; - this.plainDiskFile = false; - this.textFile = false; - this.kc85TapFile = false; - this.z9001TapFile = false; - this.startableFile = false; - this.fileInfo = null; - this.fileToChild = null; + super( parent, path, fileSystemRoot ); + this.fileCheckResult = null; } - protected boolean fileNameEndsWith( String suffix ) + public boolean fileNameEndsWith( String suffix ) { boolean rv = false; if( (this.file != null) && (suffix != null) ) { @@ -82,478 +48,166 @@ protected boolean fileNameEndsWith( String suffix ) } - public FileInfo getFileInfo() + @Override + public FileCheckResult getCheckResult() { - ensureFileChecked(); - return this.fileInfo; - } - - - public int getHeadersaveFileType() - { - ensureFileChecked(); - return this.hsFileType; - } - - - public boolean isAudioFile() - { - ensureFileChecked(); - return this.audioFile; - } - - - public boolean isArchiveFile() - { - ensureFileChecked(); - return this.archiveFile; - } - - - public boolean isBinFile() - { - ensureFileChecked(); - return this.binFile; - } - - - public boolean isCompressedFile() - { - ensureFileChecked(); - return this.compressedFile; - } - - - public boolean isHeadersaveFile() - { - ensureFileChecked(); - return this.headersaveFile; - } - - - public boolean isImageFile() - { - ensureFileChecked(); - return this.imageFile; - } - - - public boolean isKC85TapFile() - { - ensureFileChecked(); - return this.kc85TapFile; - } - - - public boolean isKCBasicHeadFile() - { - ensureFileChecked(); - return this.kcBasicHeadFile; - } - - - public boolean isKCBasicFile() - { - ensureFileChecked(); - return this.kcBasicFile; - } - - - public boolean isKCSysFile() - { - ensureFileChecked(); - return this.kcSysFile; - } - - - public boolean isNonPlainDiskFile() - { - ensureFileChecked(); - return this.nonPlainDiskFile; - } - - - public boolean isPlainDiskFile() - { - ensureFileChecked(); - return this.plainDiskFile; - } - - - public boolean isStartableFile() - { - ensureFileChecked(); - return this.startableFile; - } - - - public boolean isTextFile() - { - ensureFileChecked(); - return this.textFile; - } - - - public boolean isZ9001TapFile() - { - ensureFileChecked(); - return this.z9001TapFile; - } - - - public void setFile( File file ) - { - super.setFile( file ); - this.fileChecked = false; + if( this.fileCheckResult == null ) { + try { + this.fileCheckResult = FileCheckResult.checkFile( + this.path.toFile() ); + } + catch( UnsupportedOperationException ex ) {} + } + return this.fileCheckResult; } public void refresh( - DefaultTreeModel model, - boolean forceLoadChildren, - boolean hiddenFiles, - FileComparator fileComparator ) + final DefaultTreeModel model, + boolean hiddenFiles, + FileTreeNodeComparator comparator ) { - this.fileChecked = false; - if( forceLoadChildren || this.childrenLoaded ) { - java.util.List oldChildren = this.vChildren; - this.vChildren = null; + Iterable entries = null; + try { + removeAllChildren(); boolean fileSystemRoots = false; - File[] entries = null; if( this.file != null ) { - if( this.file.isDirectory() ) { - entries = this.file.listFiles(); + try { + Path path = this.file.toPath(); + if( Files.isDirectory( path, LinkOption.NOFOLLOW_LINKS ) + && !Files.isSymbolicLink( path ) ) + { + entries = Files.newDirectoryStream( path ); + } } + catch( InvalidPathException ex ) {} + catch( IOException ex ) {} } else { fileSystemRoots = true; - entries = File.listRoots(); - } - - boolean changed = false; - int nChildren = 0; - int nOldChildren = 0; - if( oldChildren != null ) { - nOldChildren = oldChildren.size(); + entries = FileSystems.getDefault().getRootDirectories(); } if( entries != null ) { - if( entries.length > 0 ) { - fileComparator.setForFileSystemRoots( fileSystemRoots ); + for( Path entry : entries ) { try { - Arrays.sort( entries, fileComparator ); - } - catch( ClassCastException ex ) {} - - // Eintraege lesen und mit alten Eintraegen vergleichen - Map fileToChild = new HashMap(); - this.vChildren = new Vector( entries.length ); - for( int i = 0; i < entries.length; i++ ) { boolean ignore = false; - File entry = entries[ i ]; - String entryName = entry.getName(); + String entryName = null; + if( fileSystemRoots ) { + entryName = entry.toString(); + } else { + Path namePath = entry.getFileName(); + if( namePath != null ) { + entryName = namePath.toString(); + } + } if( entryName != null ) { if( entryName.equals( "." ) || entryName.equals( ".." ) ) { ignore = true; } - } - if( !ignore - && (this.fileSystemRoot || hiddenFiles || !entry.isHidden()) ) - { - boolean equals = false; - FileNode child = null; - if( this.fileToChild != null ) { - child = this.fileToChild.get( entry ); - } - if( child == null ) { - child = new FileNode( this, entry, fileSystemRoots ); - } - this.vChildren.add( child ); - fileToChild.put( entry, child ); - if( (oldChildren != null) && (nChildren < nOldChildren) ) { - if( oldChildren.get( nChildren ) == child ) { - equals = true; - } - } - if( !equals ) { - changed = true; - } - nChildren++; - } - } - this.fileToChild = fileToChild; - this.fileChecked = false; - - // untergeordnete Verzeichnisse aktualisieren - if( this.vChildren != null ) { - DefaultTreeModel tmpModel = null; - if( !changed ) { - tmpModel = model; - } - for( FileTreeNode child : this.vChildren ) { - if( child instanceof FileNode ) { - ((FileNode) child).refresh( - tmpModel, - false, - hiddenFiles, - fileComparator ); + if( !ignore && (hiddenFiles || !Files.isHidden( entry )) ) { + FileNode fn = new FileNode( this, entry, fileSystemRoots ); + fn.updNode(); + add( fn ); } } } + catch( IOException ex ) {} } + + // Sortieren + comparator.setForFileSystemRoots( fileSystemRoots ); + sort( comparator ); } // Aenderungen melden - if( nChildren != nOldChildren ) { - changed = true; - } - if( changed && (model != null) ) { + if( model != null ) { model.nodeStructureChanged( this ); model.reload( this ); } this.childrenLoaded = true; } + finally { + if( entries != null ) { + if( entries instanceof Closeable ) { + EmuUtil.doClose( (Closeable) entries ); + } + } + this.fileCheckResult = null; + } } - public void refreshNodeFor( - File file, - DefaultTreeModel model, - boolean forceLoadChildren, - boolean hiddenFiles, - FileComparator fileComparator ) + public FileNode refreshNodeFor( + Path path, + DefaultTreeModel model, + boolean forceLoadChildren, + boolean hiddenFiles, + FileTreeNodeComparator comparator ) { - if( (file != null) && this.childrenLoaded && (this.vChildren != null) ) { + FileNode node = null; + if( path != null ) { try { - boolean done = false; - boolean isParent = false; - String path = file.getCanonicalPath(); - if( this.file != null ) { - String tmpPath = this.file.getCanonicalPath(); - if( path.equals( tmpPath ) ) { - refresh( model, forceLoadChildren, hiddenFiles, fileComparator ); - done = true; - } - if( !done ) { - if( !tmpPath.endsWith( File.separator ) ) { - tmpPath += File.separator; - } - if( path.startsWith( tmpPath ) ) { - isParent = true; - } - } - } else { - isParent = true; - } - if( isParent ) { - for( FileTreeNode child : this.vChildren ) { - if( child instanceof FileNode ) { - ((FileNode) child).refreshNodeFor( - file, - model, - forceLoadChildren, - hiddenFiles, - fileComparator ); - } - } - } + node = refreshNodeFor( + path.toAbsolutePath().normalize(), + model, + hiddenFiles, + comparator ); } - catch( IOException ex ) {} + catch( Exception ex ) {} } + return node; } - /* --- private Methoden --- */ + /* --- ueberschriebene Methoden --- */ - private synchronized void ensureFileChecked() + @Override + public void setPath( Path path ) { - if( !this.fileSystemRoot && (this.file != null) ) { - if( !this.fileChecked ) { - if( this.file.isFile() && this.file.canRead() ) { - this.hsFileType = -1; - this.audioFile = false; - this.archiveFile = false; - this.binFile = false; - this.compressedFile = false; - this.headersaveFile = false; - this.imageFile = false; - this.kcBasicHeadFile = false; - this.kcBasicFile = false; - this.kcSysFile = false; - this.kc85TapFile = false; - this.z9001TapFile = false; - this.nonPlainDiskFile = false; - this.plainDiskFile = false; - this.textFile = false; - this.startableFile = false; - this.fileInfo = null; - - boolean done = false; + this.path = path; + this.file = null; + } - // Sound-Datei pruefen - if( AudioUtil.isAudioFile( this.file ) ) { - this.audioFile = true; - done = true; - } - // Bilddatei pruefen - if( ImgLoader.accepts( this.file ) ) { - this.imageFile = true; - done = true; - } + /* --- private Mathoden --- */ - /* - * Dateiextension pruefen, - * auch bei Audio und Bilddatei (z.B. wegen *.img und *.gz) - */ - String fName = this.file.getName(); - if( fName != null ) { - fName = fName.toLowerCase(); - if( TextUtil.endsWith( - fName, - EmuUtil.archiveFileExtensions ) ) - { - this.archiveFile = true; - done = true; - } - else if( TextUtil.endsWith( fName, DiskUtil.anaDiskFileExt ) - || TextUtil.endsWith( fName, DiskUtil.copyQMFileExt ) - || TextUtil.endsWith( fName, DiskUtil.dskFileExt ) - || TextUtil.endsWith( fName, DiskUtil.imageDiskFileExt ) - || TextUtil.endsWith( fName, DiskUtil.teleDiskFileExt ) ) - { - this.nonPlainDiskFile = true; - done = true; - } - else if( TextUtil.endsWith( fName, DiskUtil.plainDiskFileExt ) ) { - this.plainDiskFile = true; - done = true; - } - else if( TextUtil.endsWith( - fName, - DiskUtil.gzAnaDiskFileExt ) - || TextUtil.endsWith( - fName, - DiskUtil.gzCopyQMFileExt ) - || TextUtil.endsWith( - fName, - DiskUtil.gzDskFileExt ) - || TextUtil.endsWith( - fName, - DiskUtil.gzImageDiskFileExt ) - || TextUtil.endsWith( - fName, - DiskUtil.gzTeleDiskFileExt ) ) - { - this.nonPlainDiskFile = true; - this.compressedFile = true; - done = true; - } - else if( TextUtil.endsWith( - fName, - DiskUtil.gzPlainDiskFileExt ) ) - { - this.plainDiskFile = true; - this.compressedFile = true; - done = true; - } - else if( TextUtil.endsWith( - fName, - EmuUtil.textFileExtensions ) ) - { - this.textFile = true; - done = true; - } - else if( fName.endsWith( ".bin" ) ) { - this.binFile = true; - done = true; - } - else if( fName.endsWith( ".gz" ) ) { - this.compressedFile = true; - done = true; - } + private FileNode refreshNodeFor( + Path absolutePath, + DefaultTreeModel model, + boolean hiddenFiles, + FileTreeNodeComparator comparator ) + { + FileNode rv = null; + if( absolutePath != null ) { + try { + boolean recursive = true; + if( this.path != null ) { + Path myAbsPath = this.path.toAbsolutePath(); + if( myAbsPath.equals( absolutePath ) ) { + refresh( model, hiddenFiles, comparator ); + recursive = false; + rv = this; + } else if( myAbsPath.startsWith( absolutePath ) ) { + rv = this; } - - // Kopfdaten ermitteln - if( !done ) { - InputStream in = null; - try { - in = new FileInputStream( this.file ); - - byte[] header = new byte[ 40 ]; - int headerLen = EmuUtil.read( in, header ); - if( headerLen >= 3 ) { - if( AbstractFloppyDisk.isDiskFileHeader( header ) ) { - this.nonPlainDiskFile = true; - } else { - this.fileInfo = FileInfo.analyzeFile( - header, - headerLen, - this.file ); - if( this.fileInfo != null ) { - if( this.fileInfo.equalsFileFormat( - FileInfo.KCBASIC_HEAD_PRG ) - || this.fileInfo.equalsFileFormat( - FileInfo.KCBASIC_HEAD_DATA ) - || this.fileInfo.equalsFileFormat( - FileInfo.KCBASIC_HEAD_ASC ) ) - { - this.kcBasicHeadFile = true; - } - else if( this.fileInfo.equalsFileFormat( - FileInfo.KCBASIC_PRG ) ) - { - this.kcBasicFile = true; - } - else if( this.fileInfo.equalsFileFormat( FileInfo.KCB ) - || this.fileInfo.equalsFileFormat( - FileInfo.KCC ) ) - { - this.kcSysFile = true; - } - else if( this.fileInfo.equalsFileFormat( - FileInfo.KCTAP_KC85 ) - || this.fileInfo.equalsFileFormat( - FileInfo.KCTAP_BASIC_PRG ) - || this.fileInfo.equalsFileFormat( - FileInfo.KCTAP_BASIC_DATA ) - || this.fileInfo.equalsFileFormat( - FileInfo.KCTAP_BASIC_ASC ) ) - { - this.kc85TapFile = true; - } - else if( this.fileInfo.equalsFileFormat( - FileInfo.KCTAP_Z9001 ) ) - { - this.z9001TapFile = true; - } - int begAddr = this.fileInfo.getBegAddr(); - int endAddr = this.fileInfo.getEndAddr(); - int startAddr = this.fileInfo.getStartAddr(); - if( (startAddr >= 0) - && (startAddr >= begAddr) - && (startAddr <= endAddr) ) - { - this.startableFile = true; - } - String fileFmt = this.fileInfo.getFileFormat(); - if( fileFmt != null ) { - if( fileFmt.equals( FileInfo.HEADERSAVE ) ) { - this.headersaveFile = true; - this.hsFileType = this.fileInfo.getFileType(); - } - } - } - } + } + if( recursive && (this.vChildren != null) ) { + for( FileTreeNode child : this.vChildren ) { + if( child instanceof FileNode ) { + FileNode tmpNode = ((FileNode) child).refreshNodeFor( + absolutePath, + model, + hiddenFiles, + comparator ); + if( tmpNode != null ) { + rv = tmpNode; } } - catch( Exception ex ) {} - finally { - EmuUtil.doClose( in ); - } } } } - this.fileChecked = true; + catch( Exception ex ) {} } + return rv; } } diff --git a/src/jkcemu/filebrowser/FilePreviewFld.java b/src/jkcemu/filebrowser/FilePreviewFld.java index 50dfa30..cd5c834 100644 --- a/src/jkcemu/filebrowser/FilePreviewFld.java +++ b/src/jkcemu/filebrowser/FilePreviewFld.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -9,11 +9,11 @@ package jkcemu.filebrowser; import java.awt.*; -import java.awt.dnd.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.io.*; import java.lang.*; +import java.nio.file.*; import java.text.*; import java.util.*; import java.util.zip.*; @@ -21,6 +21,7 @@ import javax.swing.*; import javax.swing.event.ListSelectionListener; import javax.swing.table.*; +import jkcemu.Main; import jkcemu.audio.AudioUtil; import jkcemu.base.*; import jkcemu.disk.*; @@ -29,7 +30,6 @@ public class FilePreviewFld extends JPanel implements - DragGestureListener, MouseListener, Runnable { @@ -82,7 +82,7 @@ public FilePreviewFld( Frame owner ) getForeground(), 1 ) ); this.textArea.setEditable( false ); - this.textArea.setFont( new Font( "Monospaced", Font.PLAIN, 9 ) ); + this.textArea.setFont( new Font( Font.MONOSPACED, Font.PLAIN, 9 ) ); this.textArea.setPreferredSize( new Dimension( 1, 1 ) ); this.detailsFld.add( this.textArea, "text" ); @@ -116,12 +116,6 @@ public FilePreviewFld( Frame owner ) if( this.fileTableHeader != null ) { this.fileTableHeader.addMouseListener( this ); } - - DragSource dragSource = DragSource.getDefaultDragSource(); - dragSource.createDefaultDragGestureRecognizer( - this.fileTable, - DnDConstants.ACTION_COPY, - this ); } @@ -149,34 +143,6 @@ public void setFileNode( } - /* --- DragGestureListener --- */ - - @Override - public void dragGestureRecognized( DragGestureEvent e ) - { - int[] rowNums = this.fileTable.getSelectedRows(); - if( rowNums != null ) { - if( rowNums.length > 0 ) { - Collection files = new ArrayList(); - for( int i = 0; i < rowNums.length; i++ ) { - FileEntry entry = this.fileTableModel.getRow( rowNums[ i ] ); - if( entry != null ) { - File file = entry.getFile(); - if( file != null ) - files.add( file ); - } - } - if( !files.isEmpty() ) { - try { - e.startDrag( null, new FileListSelection( files ) ); - } - catch( InvalidDnDOperationException ex ) {} - } - } - } - } - - /* --- MouseListener --- */ @Override @@ -258,9 +224,8 @@ public void run() String cardName = null; // Datei auswerten - Map infoItems - = new HashMap(); - String[] addonLines = null; + Map infoItems = new HashMap<>(); + String[] addonLines = null; if( fileNode != null ) { File file = fileNode.getFile(); @@ -277,14 +242,23 @@ public void run() infoItems.put( FileInfoFld.Item.NAME, fName ); } } - if( !fileNode.isLeaf() ) { + if( fileNode.getAllowsChildren() ) { addDirectoryInfo( infoItems, fileNode.children(), sortCaseSensitive ); cardName = "file.table"; } - else if( file.isFile() ) { + Path path = fileNode.getPath(); + if( path != null ) { + if( Files.isSymbolicLink( path ) ) { + Path destPath = Files.readSymbolicLink( path ); + if( destPath != null ) { + infoItems.put( FileInfoFld.Item.LINKED_TO, destPath ); + } + } + } + if( file.isFile() ) { String fExt = null; if( fName != null ) { int pos = fName.lastIndexOf( '.' ); @@ -301,16 +275,17 @@ else if( file.isFile() ) { FileInfoFld.Item.SIZE, new Long( file.length() ) ); - if( fileNode.isAudioFile() ) { + FileCheckResult checkResult = fileNode.getCheckResult(); + if( checkResult.isAudioFile() ) { addAudioInfo( infoItems, file ); - } else if( fileNode.isImageFile() ) { + } else if( checkResult.isImageFile() ) { if( addImageInfo( infoItems, file, maxFileSize ) ) { cardName = "image"; } - } else if( fileNode.isPlainDiskFile() ) { + } else if( checkResult.isPlainDiskFile() ) { if( addPlainDiskInfo( infoItems, - fileNode.isCompressedFile() ? + checkResult.isCompressedFile() ? "Komprimierte einfache Abbilddatei" : "Einfache Abbilddatei", file, @@ -319,16 +294,17 @@ else if( file.isFile() ) { { cardName = "file.table"; } - } else if( fileNode.isNonPlainDiskFile() ) { + } else if( checkResult.isNonPlainDiskFile() ) { if( checkFileSize( file.length(), maxFileSize ) ) { try { AbstractFloppyDisk disk = DiskUtil.readNonPlainDiskFile( this.owner, - file ); + file, + true ); if( disk != null ) { addDiskInfo( infoItems, - fileNode.isCompressedFile(), + checkResult.isCompressedFile(), disk, sortCaseSensitive ); cardName = "file.table"; @@ -336,11 +312,11 @@ else if( file.isFile() ) { } catch( IOException ex ) {} } - } else if( fileNode.isTextFile() ) { + } else if( checkResult.isTextFile() ) { if( addTextInfo( infoItems, file, maxFileSize ) ) { cardName = "text"; } - } else if( fileNode.isArchiveFile() ) { + } else if( checkResult.isArchiveFile() ) { if( addArchiveInfo( infoItems, file, @@ -350,7 +326,7 @@ else if( file.isFile() ) { cardName = "file.table"; } } else { - FileInfo fileInfo = fileNode.getFileInfo(); + FileInfo fileInfo = checkResult.getFileInfo(); if( fileInfo != null ) { infoItems.put( FileInfoFld.Item.TYPE, @@ -402,8 +378,11 @@ public void addNotify() { super.addNotify(); if( this.thread == null ) { - Thread thread = new Thread( this, "JKCEMU File Browser Details Viewer" ); - this.thread = thread; + Thread thread = new Thread( + Main.getThreadGroup(), + this, + "JKCEMU File Browser Details Viewer" ); + this.thread = thread; thread.setDaemon( true ); thread.start(); } @@ -414,6 +393,14 @@ public void addNotify() public void removeNotify() { super.removeNotify(); + if( this.thread != null ) { + synchronized( this.lockMonitor ) { + try { + this.lockMonitor.notifyAll(); + } + catch( IllegalMonitorStateException ex ) {} + } + } this.thread = null; } diff --git a/src/jkcemu/filebrowser/FilePropDlg.java b/src/jkcemu/filebrowser/FilePropDlg.java index f3f83fd..ba45798 100644 --- a/src/jkcemu/filebrowser/FilePropDlg.java +++ b/src/jkcemu/filebrowser/FilePropDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -9,146 +9,317 @@ package jkcemu.filebrowser; import java.awt.*; -import java.io.File; +import java.io.IOException; import java.lang.*; +import java.nio.file.*; +import java.nio.file.attribute.*; import java.text.*; -import java.util.EventObject; +import java.util.*; import javax.swing.*; +import jkcemu.Main; import jkcemu.base.*; -public class FilePropDlg extends BasicDlg +public class FilePropDlg + extends BasicDlg + implements FileVisitor, Runnable { - private JButton btnOK; + private Path path; + private JButton btnOK; + private JLabel sizeLabel; + private long dirSize; + private volatile boolean threadEnabled; - public FilePropDlg( - Frame parent, - File file ) + public FilePropDlg( Frame parent, Path path ) throws IOException { super( parent, "Eigenschaften" ); - boolean isDir = false; - boolean isFile = false; + this.path = path; + this.sizeLabel = null; + this.dirSize = 0; + this.threadEnabled = true; try { - isDir = file.isDirectory(); - isFile = file.isFile(); - } - catch( Exception ex ) {} + // Dateiattribute lesen + UserPrincipal owner = null; + GroupPrincipal group = null; + String posixPerm = null; + BasicFileAttributes attrs = null; + try { + PosixFileAttributeView view = Files.getFileAttributeView( + path, + PosixFileAttributeView.class, + LinkOption.NOFOLLOW_LINKS ); + if( view != null ) { + PosixFileAttributes pAttrs = view.readAttributes(); + if( pAttrs != null ) { + attrs = pAttrs; + owner = pAttrs.owner(); + group = pAttrs.group(); + posixPerm = PosixFilePermissions.toString( pAttrs.permissions() ); + } + } + } + catch( Exception ex ) {} + if( attrs == null ) { + attrs = Files.readAttributes( + path, + BasicFileAttributes.class, + LinkOption.NOFOLLOW_LINKS ); + } + if( owner == null ) { + try { + owner = Files.getOwner( path, LinkOption.NOFOLLOW_LINKS ); + } + catch( Exception ex ) {} + } + + // Ausgabeliste aufbereiten + int sizeLineIdx = -1; + java.util.List lines = new ArrayList<>(); + String text = path.toString(); + Path namePath = path.getFileName(); + if( namePath != null ) { + String s = namePath.toString(); + if( s != null ) { + if( !s.isEmpty() ) { + text = s; + } + } + } + if( text != null ) { + if( !text.isEmpty() ) { + lines.add( new String[] { "Name:", text } ); + } + } + if( attrs.isRegularFile() ) { + long size = attrs.size(); + if( size >= 0 ) { + lines.add( new String[] { + "Gr\u00F6\u00DFe:", + EmuUtil.formatSize( size, false, true ) } ); + } + } + else if( attrs.isDirectory() ) { + sizeLineIdx = lines.size(); + lines.add( new String[] { + "Gr\u00F6\u00DFe aller Dateien:", + "wird berechnet..." } ); + } + else if( attrs.isSymbolicLink() ) { + try { + String s = null; + Path p = Files.readSymbolicLink( path ); + if( p != null ) { + s = p.toString(); + } + lines.add( new String[] { + "Symbolischer Link auf:", + s != null ? s : "" } ); + } + catch( Exception ex ) {} + } + try { + FileTime t = Files.getLastModifiedTime( path ); + if( t != null ) { + lines.add( new String[] { + "Zuletzt ge\u00E4ndert:", + DateFormat.getDateTimeInstance( + DateFormat.MEDIUM, + DateFormat.MEDIUM ).format( + new java.util.Date( t.toMillis() ) ) } ); + } + } + catch( Exception ex ) {} + if( owner != null ) { + String s = owner.getName(); + if( s != null ) { + if( !s.isEmpty() ) { + lines.add( new String[] { "Eigent\u00FCmer:", s } ); + } + } + } + if( group != null ) { + String s = group.getName(); + if( s != null ) { + if( !s.isEmpty() ) { + lines.add( new String[] { "Gruppe:", s } ); + } + } + } + if( posixPerm != null ) { + lines.add( new String[] { "Berechtigungen:", posixPerm } ); + } + try { + lines.add( new String[] { + "Lesezugriff:", + Files.isReadable( path ) ? "ja" : "nein" } ); + } + catch( Exception ex ) {} + try { + lines.add( new String[] { + "Schreibzugriff:", + Files.isWritable( path ) ? "ja" : "nein" } ); + } + catch( Exception ex ) {} + try { + lines.add( new String[] { + "Versteckt:", + Files.isHidden( path ) ? "ja" : "nein" } ); + } + catch( Exception ex ) {} - // Layout - setLayout( new GridBagLayout() ); - GridBagConstraints gbc = new GridBagConstraints( + // Layout + setLayout( new GridBagLayout() ); + + GridBagConstraints gbc = new GridBagConstraints( 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, - new Insets( 5, 5, 0, 5 ), + new Insets( 0, 5, 0, 5 ), 0, 0 ); - // Fensterinhalt - String text = file.getName(); - if( text != null ) { - if( text.length() > 0 ) { - if( isDir ) { - add( new JLabel( "Verzeichnisname:" ), gbc ); - } - else if( isFile ) { - add( new JLabel( "Dateiname:" ), gbc ); - } else { - add( new JLabel( "Name:" ), gbc ); - } - text = file.getName(); - if( text != null ) { - gbc.gridx++; - add( new JLabel( text ), gbc ); - } - } - } - gbc.insets.top = 0; - - if( isFile ) { - try { - long n = file.length(); - if( n >= 0 ) { + // Fensterinhalt + int nLines = lines.size(); + for( int i = 0; i < nLines; i++ ) { + String[] line = lines.get( i ); + if( line.length > 1 ) { gbc.gridx = 0; - gbc.gridy++; - add( new JLabel( "Gr\u00F6\u00DFe:" ), gbc ); + add( new JLabel( line[ 0 ] ), gbc ); gbc.gridx++; - add( - new JLabel( NumberFormat.getNumberInstance().format( n ) - + " Bytes" ), - gbc ); + JLabel label = new JLabel( line[ 1 ] ); + add( label, gbc ); + gbc.gridy++; + if( i == sizeLineIdx ) { + this.sizeLabel = label; + } } } - catch( Exception ex ) {} - } - try { - long t = file.lastModified(); - if( t > 0L ) { - gbc.gridx = 0; - gbc.gridy++; - add( new JLabel( "Zuletzt ge\u00E4ndert:" ), gbc ); - gbc.gridx++; - add( - new JLabel( DateFormat.getDateTimeInstance( - DateFormat.MEDIUM, - DateFormat.MEDIUM ).format( - new java.util.Date( t ) ) ), - gbc ); + // Knopf + this.btnOK = new JButton( "OK" ); + this.btnOK.addActionListener( this ); + + gbc.anchor = GridBagConstraints.CENTER; + gbc.insets.top = 10; + gbc.insets.bottom = 10; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx = 0; + gbc.gridy++; + add( this.btnOK, gbc ); + + + // Fenstergroesse und -position + pack(); + setParentCentered(); + setResizable( false ); + + + // Verzeichnisgroesse ermitteln + if( this.threadEnabled && (this.sizeLabel != null) ) { + (new Thread( + Main.getThreadGroup(), + this, + "JKCEMU directory size calculator" )).start(); } } - catch( Exception ex ) {} - - gbc.gridx = 0; - gbc.gridy++; - add( new JLabel( "Lesezugriff:" ), gbc ); - try { - gbc.gridx++; - add( new JLabel( file.canRead() ? "ja" : "nein" ), gbc ); + catch( UnsupportedOperationException ex ) { + throw new IOException( + "Dateiattribute k\u00E4nnen nicht gelesen werden.\n" ); } - catch( Exception ex ) {} + } - gbc.gridx = 0; - gbc.gridy++; - add( new JLabel( "Schreibzugriff:" ), gbc ); - try { - gbc.gridx++; - add( new JLabel( file.canWrite() ? "ja" : "nein" ), gbc ); + + /* --- FileVisistor --- */ + + @Override + public FileVisitResult postVisitDirectory( + Path dir, + IOException ex ) throws IOException + { + if( ex != null ) { + throw ex; } - catch( Exception ex ) {} + return this.threadEnabled ? + FileVisitResult.CONTINUE : FileVisitResult.TERMINATE; + } - gbc.insets.bottom = 5; - gbc.gridx = 0; - gbc.gridy++; - add( new JLabel( "Versteckt:" ), gbc ); - try { - gbc.gridx++; - add( new JLabel( file.isHidden() ? "ja" : "nein" ), gbc ); + + @Override + public FileVisitResult preVisitDirectory( + Path dir, + BasicFileAttributes attrs ) + { + return this.threadEnabled ? + FileVisitResult.CONTINUE : FileVisitResult.TERMINATE; + } + + + @Override + public FileVisitResult visitFile( Path file, BasicFileAttributes attrs ) + { + if( attrs.isRegularFile() ) { + long size = attrs.size(); + if( size > 0 ) { + this.dirSize += size; + } } - catch( Exception ex ) {} + return this.threadEnabled ? + FileVisitResult.CONTINUE : FileVisitResult.TERMINATE; + } - // Knopf - this.btnOK = new JButton( "OK" ); - this.btnOK.addActionListener( this ); + @Override + public FileVisitResult visitFileFailed( + Path file, + IOException ex ) throws IOException + { + if( (ex != null) + && (Files.isRegularFile( file, LinkOption.NOFOLLOW_LINKS ) + || Files.isDirectory( file, LinkOption.NOFOLLOW_LINKS )) ) + { + throw ex; + } + return this.threadEnabled ? + FileVisitResult.CONTINUE : FileVisitResult.TERMINATE; + } - gbc.anchor = GridBagConstraints.CENTER; - gbc.insets.top = 5; - gbc.gridwidth = GridBagConstraints.REMAINDER; - gbc.gridx = 0; - gbc.gridy++; - add( this.btnOK, gbc ); + /* --- Runnable --- */ - // Fenstergroesse und -position - pack(); - setParentCentered(); - setResizable( false ); + @Override + public void run() + { + if( this.threadEnabled && (this.sizeLabel != null) ) { + String sizeText = "unbekannt"; + try { + this.dirSize = 0; + Files.walkFileTree( this.path, this ); + if( this.threadEnabled ) { + sizeText = EmuUtil.formatSize( this.dirSize, false, true ); + } + } + catch( AccessDeniedException ex ) { + sizeText = "wegen fehlender Berechtigung nicht ermittelbar"; + } + catch( Exception ex ) { + sizeText = "konnte aufgrund eines Fehlers nicht ermittelt werden"; + } + final String text = sizeText; + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + setSizeText( text ); + } + } ); + } } @@ -169,5 +340,27 @@ protected boolean doAction( EventObject e ) } return rv; } -} + + @Override + public boolean doClose() + { + boolean rv = super.doClose(); + if( rv ) { + this.threadEnabled = false; + } + return rv; + } + + + /* --- private Methoden --- */ + + private void setSizeText( String text ) + { + if( this.sizeLabel != null ) { + this.sizeLabel.setText( text ); + pack(); + setParentCentered(); + } + } +} diff --git a/src/jkcemu/filebrowser/FindFilesCellRenderer.java b/src/jkcemu/filebrowser/FindFilesCellRenderer.java new file mode 100644 index 0000000..c4a57b4 --- /dev/null +++ b/src/jkcemu/filebrowser/FindFilesCellRenderer.java @@ -0,0 +1,108 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Renderer fuer die Ergbnisliste der Dateisuche + */ + +package jkcemu.filebrowser; + +import java.awt.*; +import java.lang.*; +import javax.swing.*; + + +public class FindFilesCellRenderer extends DefaultListCellRenderer +{ + public static class CodeEntry + { + private String text; + private FileActionMngr.FileObject file; + + public CodeEntry( String text, FileActionMngr.FileObject file ) + { + this.text = text; + this.file = file; + } + + public FileActionMngr.FileObject getFileObject() + { + return this.file; + } + + @Override + public String toString() + { + return this.text; + } + }; + + + public static class EmphasizedEntry + { + private String text; + + public EmphasizedEntry( String text ) + { + this.text = text; + } + + @Override + public String toString() + { + return this.text; + } + }; + + + private Font defaultFont; + private Font codeFont; + + + public FindFilesCellRenderer() + { + this.defaultFont = getFont(); + this.codeFont = null; + if( this.defaultFont != null ) { + this.codeFont = new Font( + Font.MONOSPACED, + Font.PLAIN, + this.defaultFont.getSize() ); + } + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus ) + { + Component c = super.getListCellRendererComponent( + list, + value, + index, + isSelected, + cellHasFocus ); + if( (c != null) && (this.defaultFont != null) ) { + c.setFont( this.defaultFont ); + c.setForeground( isSelected ? Color.WHITE : Color.BLACK ); + if( value != null ) { + if( value instanceof CodeEntry ) { + c.setForeground( isSelected ? Color.LIGHT_GRAY : Color.DARK_GRAY ); + if( this.codeFont != null ) { + c.setFont( this.codeFont ); + } + } else if( value instanceof EmphasizedEntry ) { + c.setForeground( isSelected ? Color.YELLOW : Color.RED ); + } + } + } + return c; + } +} diff --git a/src/jkcemu/filebrowser/FindFilesFrm.java b/src/jkcemu/filebrowser/FindFilesFrm.java new file mode 100644 index 0000000..a468c01 --- /dev/null +++ b/src/jkcemu/filebrowser/FindFilesFrm.java @@ -0,0 +1,1859 @@ +/* + * (c) 2014-2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Suchen von Dateien in einem Verzeichnis + */ + +package jkcemu.filebrowser; + +import java.awt.*; +import java.awt.datatransfer.*; +import java.awt.dnd.*; +import java.awt.event.*; +import java.io.*; +import java.lang.*; +import java.nio.charset.Charset; +import java.nio.file.*; +import java.nio.file.attribute.*; +import java.text.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.*; +import javax.swing.*; +import javax.swing.event.*; +import jkcemu.Main; +import jkcemu.base.*; + + +public class FindFilesFrm + extends BasicFrm + implements + AbstractFileWorker.PathListener, + DragGestureListener, + DragSourceListener, + DropTargetListener, + FileVisitor, + FocusListener, + ListSelectionListener, + Runnable +{ + public static class FileEntry implements FileActionMngr.FileObject + { + private Path path; + private boolean wasChecked; + private FileCheckResult checkResult; + + public FileEntry( Path path ) + { + this.path = path; + this.wasChecked = false; + this.checkResult = null; + } + + @Override + public File getFile() + { + File file = null; + try { + file = this.path.toFile(); + } + catch( UnsupportedOperationException ex ) {} + return file; + } + + @Override + public Path getPath() + { + return this.path; + } + + @Override + public FileCheckResult getCheckResult() + { + if( !this.wasChecked ) { + this.wasChecked = true; + try { + this.checkResult = FileCheckResult.checkFile( this.path.toFile() ); + } + catch( UnsupportedOperationException ex ) {} + } + return this.checkResult; + } + + @Override + public void setPath( Path path ) + { + this.path = path; + } + + @Override + public String toString() + { + return this.path.toString(); + } + }; + + + private static final int MAX_RESULT_ROWS = 2000; + private static final int MAX_ROW_LEN = 1024; + private static final String COPY_TEXT = "Zeilen als Text kopieren"; + + private static FindFilesFrm instance = null; + private static DateFormat dateFmtShort = null; + private static DateFormat dateFmtMedium = null; + + private ScreenFrm screenFrm; + private JPopupMenu popup; + private JMenuItem popupCopyText; + private JMenuItem mnuStart; + private JMenuItem mnuStop; + private JMenuItem mnuClose; + private JMenuItem mnuCopyText; + private JMenuItem mnuRemoveFromResult; + private JMenuItem mnuHelpContent; + private JButton btnStartStop; + private JButton btnRootDirSelect; + private JTextField fldRootDir; + private JTextField fldFileNameMask; + private JTextField fldFileSizeFrom; + private JTextField fldFileSizeTo; + private JTextField fldLastModified; + private JTextField fldLastModifiedTill; + private JTextField fldText; + private JTextField fldCurDir; + private JCheckBox btnSubTrees; + private JCheckBox btnCaseSensitive; + private JCheckBox btnPrintMatchedRows; + private JLabel labelCurDir; + private JList list; + private DefaultListModel listModel; + private Path dirPath; + private Collection findFileNamePatterns; + private Long findFileSizeFrom; + private Long findFileSizeTo; + private Long findLastModifiedFrom; + private Long findLastModifiedTill; + private String findText; + private boolean findIgnoreCase; + private boolean findPrintMatchedRows; + private boolean findSubTrees; + private FileActionMngr fileActionMngr; + private volatile FileVisitResult fileVisitResult; + private volatile Thread thread; + + + public static void open( ScreenFrm screenFrm ) + { + if( instance != null ) { + if( instance.getExtendedState() == Frame.ICONIFIED ) { + instance.setExtendedState( Frame.NORMAL ); + } + } else { + instance = new FindFilesFrm( screenFrm ); + } + instance.toFront(); + instance.setVisible( true ); + } + + + public static void open( ScreenFrm screenFrm, Path dirPath ) + { + open( screenFrm ); + instance.setDirectory( dirPath ); + } + + + public void setDirectory( Path dirPath ) + { + if( dirPath != null ) { + if( !Files.isDirectory( dirPath ) ) { + dirPath = null; + } + } + this.dirPath = dirPath; + this.fldRootDir.setText( dirPath != null ? dirPath.toString() : "" ); + } + + + /* --- AbstractFileWorker.PathListener --- */ + + @Override + public void pathsPasted( Set paths ) + { + // leer + } + + + @Override + public void pathsRemoved( Set paths ) + { + if( paths != null ) { + removeFromResult( paths ); + Set parents = EmuUtil.createPathSet(); + for( Path path : paths ) { + Path parent = path.getParent(); + if( parent != null ) { + FileBrowserFrm.fireFileChanged( parent ); + } + } + } + } + + + /* --- DragGestureListener --- */ + + @Override + public void dragGestureRecognized( DragGestureEvent e ) + { + java.util.List items = + getFileObjectList( this.list.getSelectedIndices() ); + if( items != null ) { + int n = items.size(); + if( n > 0 ) { + Collection files = new ArrayList<>( n ); + for( Object o : items ) { + if( o instanceof FileActionMngr.FileObject ) { + File file = ((FileActionMngr.FileObject) o).getFile(); + if( file != null ) { + files.add( file ); + } + } + } + if( !files.isEmpty() ) { + try { + e.startDrag( null, new FileListSelection( files ) ); + } + catch( InvalidDnDOperationException ex ) {} + } + } + } + } + + + /* --- DragSourceListener --- */ + + @Override + public void dragDropEnd( DragSourceDropEvent e ) + { + if( e.getDropAction() == DnDConstants.ACTION_MOVE ) { + DragSourceContext context = e.getDragSourceContext(); + if( context != null ) { + try { + Transferable t = context.getTransferable(); + if( t != null ) { + if( t.isDataFlavorSupported( DataFlavor.javaFileListFlavor ) ) { + Object o = t.getTransferData( DataFlavor.javaFileListFlavor ); + if( o != null ) { + if( o instanceof Collection ) { + removeFromResult( (Collection) o ); + } + } + } + } + } + catch( IOException ex ) {} + catch( UnsupportedFlavorException ex ) {} + } + } + } + + + @Override + public void dragEnter( DragSourceDragEvent e ) + { + // leer + } + + + @Override + public void dragExit( DragSourceEvent e ) + { + // leer + } + + + @Override + public void dragOver( DragSourceDragEvent e ) + { + // leer + } + + + @Override + public void dropActionChanged( DragSourceDragEvent e ) + { + // leer + } + + + /* --- DropTargetListener --- */ + + @Override + public void dragEnter( DropTargetDragEvent e ) + { + if( (this.thread != null) || !EmuUtil.isFileDrop( e ) ) + e.rejectDrag(); + } + + + @Override + public void dragExit( DropTargetEvent e ) + { + // leer + } + + + @Override + public void dragOver( DropTargetDragEvent e ) + { + // leer + } + + + @Override + public void drop( DropTargetDropEvent e ) + { + boolean done = false; + if( (this.thread == null) && EmuUtil.isFileDrop( e ) ) { + File file = EmuUtil.fileDrop( this, e ); + if( file != null ) { + if( file.isDirectory() ) { + try { + setDirectory( file.toPath() ); + } + catch( InvalidPathException ex ) {} + done = true; + } + } + } + if( done ) { + e.dropComplete( true ); + } else { + e.rejectDrop(); + } + } + + + @Override + public void dropActionChanged( DropTargetDragEvent e ) + { + if( (this.thread != null) || !EmuUtil.isFileDrop( e ) ) + e.rejectDrag(); + } + + + /* --- FileVisitor --- */ + + @Override + public FileVisitResult postVisitDirectory( Path dir, IOException ex ) + { + this.fldCurDir.setText( dir.toString() ); + if( ex != null ) { + appendErrorToResult( dir, ex ); + } + return this.fileVisitResult; + } + + + @Override + public FileVisitResult preVisitDirectory( + Path dir, + BasicFileAttributes attrs ) + { + return this.fileVisitResult; + } + + + @Override + public FileVisitResult visitFile( + final Path file, + BasicFileAttributes attrs ) + { + boolean matchesFile = true; + + // Dateiname pruefen + Collection fileNamePatterns = this.findFileNamePatterns; + if( fileNamePatterns != null ) { + if( !fileNamePatterns.isEmpty() ) { + matchesFile = false; + Path namePath = file.getFileName(); + if( namePath != null ) { + String fileName = namePath.toString(); + if( fileName != null ) { + for( Pattern fileNamePattern : fileNamePatterns ) { + if( fileNamePattern.matcher( fileName ).matches() ) { + matchesFile = true; + break; + } + } + } + } + } + } + + // Dateigroesse pruefen + if( matchesFile && (attrs != null) + && ((this.findFileSizeFrom != null) + || (this.findFileSizeTo != null)) ) + { + long fileSize = attrs.size(); + if( fileSize >= 0 ) { + Long sizeFrom = this.findFileSizeFrom; + if( sizeFrom != null ) { + if( fileSize < sizeFrom ) { + matchesFile = false; + } + } + Long sizeTo = this.findFileSizeTo; + if( sizeTo != null ) { + if( fileSize > sizeTo ) { + matchesFile = false; + } + } + } + } + + // Zeitpunkt der letzten Aktualisierung pruefen + if( matchesFile && (attrs != null) + && ((this.findLastModifiedFrom != null) + || (this.findLastModifiedTill != null)) ) + { + FileTime lastModified = attrs.lastModifiedTime(); + if( lastModified != null ) { + long fileMillis = lastModified.toMillis(); + Long lastModifiedFrom = this.findLastModifiedFrom; + if( lastModifiedFrom != null ) { + if( fileMillis < lastModifiedFrom.longValue() ) { + matchesFile = false; + } + } + Long lastModifiedTill = this.findLastModifiedTill; + if( lastModifiedTill != null ) { + if( fileMillis >= lastModifiedTill.longValue() ) { + matchesFile = false; + } + } + } + } + + // enthaltenen Text pruefen + java.util.List matchedRows = new ArrayList<>(); + if( matchesFile ) { + String findText = this.findText; + if( findText != null ) { + int textLen = findText.length(); + if( textLen > 0 ) { + PushbackInputStream in = null; + Reader reader = null; + try { + /* + * Wird die Datei mit Files.newBufferedReader(...) geoeffnet, + * werden beim Lesen UnmappableCharacterException geworfen, + * wenn der Zeichensatz nicht passt. + * Aus diesem Grund erfolgt hier die Umwandlung + * des Byte- in einen Char-Stream mittels InputStreamReader. + * + * Eine evtl. vorhandene Byte-Order-Markierung wird ausgewertet. + */ + in = new PushbackInputStream( Files.newInputStream( file ), 3 ); + + String enc = null; + byte[] bom = new byte[ 3 ]; + int nRead = in.read( bom ); + if( nRead == bom.length ) { + if( (bom[ 0 ] == (byte) 0xEF) + && (bom[ 1 ] == (byte) 0xBB) + && (bom[ 2 ] == (byte) 0xBF) ) + { + enc = "UTF-8"; + } + else if( (bom[ 0 ] == (byte) 0xFE) + && (bom[ 1 ] == (byte) 0xFF) ) + { + enc = "UTF-16BE"; + in.unread( bom[ 2 ] ); + } + else if( (bom[ 0 ] == (byte) 0xFF) + && (bom[ 1 ] == (byte) 0xFE) ) + { + enc = "UTF-16LE"; + in.unread( bom[ 2 ] ); + } + } else { + if( nRead > 0 ) { + in.unread( bom, 0, Math.max( nRead, bom.length ) ); + } + } + if( enc != null ) { + reader = new InputStreamReader( in, enc ); + } else { + reader = new InputStreamReader( in ); + } + matchesFile = false; + char[] buf = new char[ textLen ]; + if( reader.read( buf ) == textLen ) { + + // Puffer zur Ausgabe der betreffenden Zeile anlegen + StringBuilder rowBuf = null; + if( this.findPrintMatchedRows ) { + rowBuf = new StringBuilder( 256 ); + for( int i = 0; i < buf.length; i++ ) { + char ch = buf[ i ]; + if( ch <= 0x0D ) { + rowBuf.setLength( 0 ); + } else { + int rowLen = rowBuf.length(); + if( rowLen < MAX_ROW_LEN ) { + rowBuf.append( ch ); + } else if( rowLen == MAX_ROW_LEN ) { + rowBuf.append( "..." ); + } + } + } + } + + // Dateiinhalt pruefen + boolean matchesRow = false; + boolean notEOF = true; + int begPos = 0; + while( notEOF ) { + boolean matchesBuf = true; + for( int i = 0; i < textLen; i++ ) { + char ch = buf[ (begPos + i) % textLen ]; + if( this.findIgnoreCase ) { + ch = Character.toUpperCase( ch ); + } + if( ch != findText.charAt( i ) ) { + matchesBuf = false; + break; + } + } + if( matchesBuf ) { + matchesFile = true; + matchesRow = true; + if( rowBuf == null ) { + /* + * Wenn die betroffenen Zeilen ausgegeben werden sollen, + * muss die Datei auch volstaendig durchgegangen werden. + * Deshalb hier nur abbrechen bei rowBuf == null + */ + break; + } + } + int ch = reader.read(); + if( ch < 0 ) { + notEOF = false; + break; + } + buf[ begPos++ ] = (char) ch; + if( begPos >= textLen ) { + begPos = 0; + } + if( rowBuf != null ) { + if( ch <= 0x0D ) { + if( matchesRow && (rowBuf.length() > 0) ) { + matchedRows.add( rowBuf.toString() ); + } + rowBuf.setLength( 0 ); + matchesRow = false; + } else { + int rowLen = rowBuf.length(); + if( rowLen < MAX_ROW_LEN ) { + rowBuf.append( (char) ch ); + } else if( rowLen == MAX_ROW_LEN ) { + rowBuf.append( "..." ); + } + } + } + } + if( rowBuf != null ) { + if( matchesRow && (rowBuf.length() > 0) ) { + matchedRows.add( rowBuf.toString() ); + } + } + } + } + catch( IOException ex ) { + appendErrorToResult( file, ex ); + matchesFile = false; + } + finally { + EmuUtil.doClose( reader ); + EmuUtil.doClose( in ); + } + } + } + } + if( matchesFile ) { + final java.util.List rows = matchedRows; + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + appendToResult( new FileEntry( file ), rows ); + } + } ); + } + return this.fileVisitResult; + } + + + @Override + public FileVisitResult visitFileFailed( Path file, IOException ex ) + { + if( ex != null ) { + appendErrorToResult( file, ex ); + } + return this.fileVisitResult; + } + + + /* --- FocusListener --- */ + + @Override + public void focusGained( FocusEvent e ) + { + // leer + } + + + @Override + public void focusLost( FocusEvent e ) + { + if( !e.isTemporary() ) { + Component c = e.getComponent(); + if( c != null ) { + if( (c instanceof JTextField) + && ((c == this.fldLastModified) + || (c == this.fldLastModifiedTill)) ) + { + try { + parseTimestamp( (JTextField) c, null ); + } + catch( UserInputException ex ) {} + } + } + } + } + + + /* --- ListSelectionListener --- */ + + @Override + public void valueChanged( ListSelectionEvent e ) + { + boolean state = (this.list.getSelectedIndex() >= 0); + this.popupCopyText.setEnabled( state ); + this.mnuCopyText.setEnabled( state ); + this.mnuRemoveFromResult.setEnabled( state ); + this.fileActionMngr.updActionButtonsEnabled( + getFileObjectList( this.list.getSelectedIndices() ) ); + } + + + /* --- Runnable --- */ + + @Override + public void run() + { + try { + Path dirPath = this.dirPath; + if( dirPath != null ) { + this.fileVisitResult = FileVisitResult.CONTINUE; + Files.walkFileTree( + dirPath, + EnumSet.noneOf( FileVisitOption.class ), + this.findSubTrees ? Integer.MAX_VALUE : 1, + this ); + } + } + catch( final Exception ex ) { + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + showError( ex ); + } + } ); + } + finally { + synchronized( this ) { + this.thread = null; + } + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + searchFinished(); + } + } ); + } + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + protected boolean doAction( EventObject e ) + { + boolean rv = false; + try { + Object src = e.getSource(); + if( src == this.btnRootDirSelect ) { + rv = true; + doSelectRootDir(); + } + else if( src == this.btnStartStop ) { + rv = true; + if( this.thread == null ) { + doStart(); + } else { + doStop(); + } + } + else if( src == this.fldFileNameMask ) { + rv = true; + this.fldFileSizeFrom.requestFocus(); + } + else if( src == this.fldFileSizeFrom ) { + rv = true; + this.fldFileSizeTo.requestFocus(); + } + else if( src == this.fldFileSizeTo ) { + rv = true; + this.fldLastModified.requestFocus(); + } + else if( src == this.fldLastModified ) { + rv = true; + this.fldLastModifiedTill.requestFocus(); + } + else if( src == this.fldLastModifiedTill ) { + rv = true; + this.fldText.requestFocus(); + } + else if( (src == this.fldText) + || (src == this.mnuStart) ) + { + rv = true; + doStart(); + } + else if( src == this.mnuStop ) { + rv = true; + doStop(); + } + else if( src == this.mnuClose ) { + rv = true; + doClose(); + } + else if( (src == this.mnuCopyText) || (src == this.popupCopyText) ) { + rv = true; + doCopyText(); + } + else if( src == this.mnuRemoveFromResult ) { + rv = true; + doRemoveFromResult(); + } + else if( src == this.mnuHelpContent ) { + rv = true; + HelpFrm.open( "/help/tools/findfiles.htm" ); + } + else if( src == this.list ) { + rv = true; + int[] rows = this.list.getSelectedIndices(); + if( rows.length == 1 ) { + int row = rows[ 0 ]; + if( (row >= 0) && (row < this.listModel.getSize()) ) { + Object o = this.listModel.getElementAt( row ); + if( o != null ) { + FileActionMngr.FileObject fObj = null; + if( o instanceof FindFilesCellRenderer.CodeEntry ) { + fObj = ((FindFilesCellRenderer.CodeEntry) o).getFileObject(); + } else if( o instanceof FileActionMngr.FileObject ) { + fObj = (FileActionMngr.FileObject) o; + } + if( fObj != null ) { + rv = this.fileActionMngr.doFileAction( fObj ); + } + } + } + } + } + if( !rv && (e instanceof ActionEvent) ) { + String actionCmd = ((ActionEvent) e).getActionCommand(); + if( actionCmd != null ) { + int[] rows = this.list.getSelectedIndices(); + if( rows.length > 0 ) { + java.util.List files + = getFileObjectList( rows ); + if( !files.isEmpty() ) { + switch( this.fileActionMngr.actionPerformed( + actionCmd, + files ) ) + { + case FILES_CHANGED: + fireFilesChanged( files ); + rv = true; + break; + case DONE: + rv = true; + break; + case FILE_RENAMED: + this.list.setModel( this.listModel ); + fireFilesChanged( files ); + rv = true; + break; + } + } + } + } + } + } + catch( IOException ex ) { + BasicDlg.showErrorDlg( this, ex ); + } + return rv; + } + + + @Override + public boolean doClose() + { + boolean rv = AbstractFileWorker.checkWindowClosing( + this, + this.fileActionMngr.getFileWorkers() ); + if( rv ) { + rv = super.doClose(); + } + if( rv ) { + this.fileVisitResult = FileVisitResult.TERMINATE; + Thread thread = this.thread; + if( thread != null ) { + thread.interrupt(); + } + instance = null; + } + return rv; + } + + + @Override + public void lookAndFeelChanged() + { + if( this.popup != null ) + SwingUtilities.updateComponentTreeUI( this.popup ); + } + + + @Override + public void mouseClicked( MouseEvent e ) + { + if( checkPopup( e ) ) { + e.consume(); + } else { + super.mouseClicked( e ); + } + } + + + @Override + public void mousePressed( MouseEvent e ) + { + if( checkPopup( e ) ) { + e.consume(); + } else { + super.mousePressed( e ); + } + } + + + @Override + public void mouseReleased( MouseEvent e ) + { + if( checkPopup( e ) ) { + e.consume(); + } else { + super.mouseReleased( e ); + } + } + + + @Override + public void windowClosed( WindowEvent e ) + { + if( e.getWindow() == this ) { + this.fileVisitResult = FileVisitResult.TERMINATE; + } + } + + + @Override + public void windowOpened( WindowEvent e ) + { + if( (e.getWindow() == this) && (this.fldFileNameMask != null) ) { + this.fldFileNameMask.requestFocus(); + } + } + + + /* --- Konstruktor --- */ + + private FindFilesFrm( ScreenFrm screenFrm ) + { + setTitle( "JKCEMU Dateisuche" ); + Main.updIcon( this ); + this.screenFrm = screenFrm; + this.fileActionMngr = new FileActionMngr( this, screenFrm, this ); + this.fileVisitResult = FileVisitResult.TERMINATE; + this.thread = null; + this.dirPath = null; + this.findFileNamePatterns = null; + this.findFileSizeFrom = null; + this.findFileSizeTo = null; + this.findLastModifiedFrom = null; + this.findLastModifiedTill = null; + this.findText = null; + this.findIgnoreCase = false; + this.findPrintMatchedRows = false; + this.findSubTrees = false; + + + // Popup-Menu + this.popup = new JPopupMenu(); + + this.popupCopyText = createJMenuItem( COPY_TEXT ); + this.popupCopyText.setEnabled( false ); + this.popup.add( this.popupCopyText ); + this.popup.addSeparator(); + + + // Menu Bearbeiten + JMenu mnuEdit = new JMenu( "Bearbeiten" ); + mnuEdit.setMnemonic( KeyEvent.VK_B ); + + this.mnuCopyText = createJMenuItem( COPY_TEXT ); + this.mnuCopyText.setEnabled( false ); + mnuEdit.add( this.mnuCopyText ); + mnuEdit.addSeparator(); + + this.fileActionMngr.addCopyFileNameMenuItemsTo( this.popup, mnuEdit ); + this.fileActionMngr.addCopyFileMenuItemTo( this.popup, mnuEdit ); + this.popup.addSeparator(); + mnuEdit.addSeparator(); + + this.mnuRemoveFromResult = createJMenuItem( + "Aus Suchergebnis entfernen", + KeyStroke.getKeyStroke( KeyEvent.VK_DELETE, 0 ) ); + this.mnuRemoveFromResult.setEnabled( false ); + mnuEdit.add( this.mnuRemoveFromResult ); + + + // Menu Datei + JMenu mnuFile = new JMenu( "Datei" ); + mnuFile.setMnemonic( KeyEvent.VK_D ); + + this.mnuStart = createJMenuItem( "Suche starten" ); + mnuFile.add( this.mnuStart ); + + this.mnuStop = createJMenuItem( "Suche beenden" ); + mnuFile.add( this.mnuStop ); + mnuFile.addSeparator(); + + if( this.screenFrm != null ) { + this.fileActionMngr.addLoadIntoEmuMenuItemsTo( this.popup, mnuFile ); + this.popup.addSeparator(); + mnuFile.addSeparator(); + } + this.fileActionMngr.addFileMenuItemsTo( this.popup, mnuFile ); + mnuFile.addSeparator(); + + this.mnuClose = createJMenuItem( "Schlie\u00DFen" ); + mnuFile.add( this.mnuClose ); + + + // Menu Hilfe + JMenu mnuHelp = new JMenu( "?" ); + + this.mnuHelpContent = createJMenuItem( "Hilfe..." ); + mnuHelp.add( this.mnuHelpContent ); + + + // Menu zusammenbauen + JMenuBar mnuBar = new JMenuBar(); + mnuBar.add( mnuFile ); + mnuBar.add( mnuEdit ); + mnuBar.add( mnuHelp ); + setJMenuBar( mnuBar ); + + + // Fensterinhalt + setLayout( new GridBagLayout() ); + + GridBagConstraints gbc = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + add( new JLabel( "Suchen in:" ), gbc ); + + JPanel panelRootDir = new JPanel( new GridBagLayout() ); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx++; + add( panelRootDir, gbc ); + + GridBagConstraints gbcRootDir = new GridBagConstraints( + 0, 0, + 1, 1, + 1.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.HORIZONTAL, + new Insets( 0, 0, 0, 0 ), + 0, 0 ); + + this.fldRootDir = new JTextField(); + this.fldRootDir.setEnabled( false ); + panelRootDir.add( this.fldRootDir, gbcRootDir ); + + this.btnRootDirSelect = createImageButton( + "/images/file/open.png", + "Verzeichnis ausw\u00E4hlen" ); + gbcRootDir.insets = new Insets( 0, 5, 0, 0 ); + gbcRootDir.fill = GridBagConstraints.NONE; + gbcRootDir.weightx = 0.0; + gbcRootDir.gridx++; + panelRootDir.add( this.btnRootDirSelect, gbcRootDir ); + + this.btnSubTrees = new JCheckBox( + "Unterverzeichnisse durchsuchen", + true ); + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.gridx = 1; + gbc.gridy++; + add( this.btnSubTrees, gbc ); + + gbc.insets.top = 5; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy++; + add( new JLabel( "Dateinamensmaske:" ), gbc ); + + this.fldFileNameMask = new JTextField(); + this.fldFileNameMask.setEnabled( false ); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.weightx = 1.0; + gbc.gridx++; + add( this.fldFileNameMask, gbc ); + + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy++; + add( new JLabel( "Dateigr\u00F6\u00DFe (Bytes):" ), gbc ); + + this.fldFileSizeFrom = new JTextField(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 0.5; + gbc.gridx++; + add( this.fldFileSizeFrom, gbc ); + + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.gridx++; + add( new JLabel( "bis:" ), gbc ); + + this.fldFileSizeTo = new JTextField(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 0.5; + gbc.gridx++; + add( this.fldFileSizeTo, gbc ); + + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.gridx = 0; + gbc.gridy++; + add( new JLabel( "Zuletzt ge\u00E4ndert am:" ), gbc ); + + this.fldLastModified = new JTextField(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 0.5; + gbc.gridx++; + add( this.fldLastModified, gbc ); + + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.gridx++; + add( new JLabel( "bis:" ), gbc ); + + this.fldLastModifiedTill = new JTextField(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 0.5; + gbc.gridx++; + add( this.fldLastModifiedTill, gbc ); + + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy++; + add( new JLabel( "Enthaltener Text:" ), gbc ); + + this.fldText = new JTextField(); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx++; + add( this.fldText, gbc ); + + this.btnCaseSensitive = new JCheckBox( + "Gro\u00DF-/Kleinschreibung beachten", + false ); + gbc.fill = GridBagConstraints.NONE; + gbc.weightx = 0.0; + gbc.gridy++; + add( this.btnCaseSensitive, gbc ); + + this.btnPrintMatchedRows = new JCheckBox( + "Gefundene Textstellen ausgeben", + false ); + gbc.insets.top = 0; + gbc.gridy++; + add( this.btnPrintMatchedRows, gbc ); + + this.btnStartStop = new JButton( "Suche starten" ); + gbc.insets.top = 10; + gbc.gridy++; + add( this.btnStartStop, gbc ); + + this.labelCurDir = new JLabel( "Aktuell wird gesucht in:" ); + gbc.gridwidth = 1; + gbc.gridx = 0; + gbc.gridy++; + add( this.labelCurDir, gbc ); + + this.fldCurDir = new JTextField(); + this.fldCurDir.setEnabled( false ); + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.weightx = 1.0; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx++; + add( this.fldCurDir, gbc ); + + gbc.weightx = 0.0; + gbc.gridx = 0; + gbc.gridy++; + add( new JLabel( "Suchergebnis:" ), gbc ); + + this.listModel = new DefaultListModel<>(); + this.list = new JList<>( this.listModel ); + this.list.setCellRenderer( new FindFilesCellRenderer() ); + this.list.setSelectionMode( + ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); + gbc.insets.top = 0; + gbc.insets.bottom = 5; + gbc.fill = GridBagConstraints.BOTH; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridy++; + add( new JScrollPane( this.list ), gbc ); + + + // Fenstergroesse + if( !applySettings( Main.getProperties(), true ) ) { + this.list.setVisibleRowCount( 8 ); + pack(); + setScreenCentered(); + this.list.setVisibleRowCount( 1 ); + } + setResizable( true ); + updFieldsEnabled(); + + + // Drag&Drop + DragSource dragSource = DragSource.getDefaultDragSource(); + dragSource.createDefaultDragGestureRecognizer( + this.list, + DnDConstants.ACTION_COPY, + this ); + (new DropTarget( this.fldRootDir, this )).setActive( true ); + + + // Listener + this.fldFileNameMask.addActionListener( this ); + this.fldFileSizeFrom.addActionListener( this ); + this.fldFileSizeTo.addActionListener( this ); + this.fldLastModified.addActionListener( this ); + this.fldLastModified.addFocusListener( this ); + this.fldLastModifiedTill.addActionListener( this ); + this.fldLastModifiedTill.addFocusListener( this ); + this.fldText.addActionListener( this ); + this.btnStartStop.addActionListener( this ); + this.list.addKeyListener( this ); + this.list.addListSelectionListener( this ); + this.list.addMouseListener( this ); + + // Status der Schaltflaechen initialisieren + this.fileActionMngr.updActionButtonsEnabled( null ); + + // letztes Suchverzeichnis einstellen + File lastDir = Main.getLastDirFile( "find.dir" ); + if( lastDir != null ) { + try { + setDirectory( lastDir.toPath() ); + } + catch( InvalidPathException ex ) {} + } + } + + + /* --- Aktionen --- */ + + private void doCopyText() + { + Toolkit tk = getToolkit(); + if( tk != null ) { + try { + Clipboard clipboard = tk.getSystemClipboard(); + if( clipboard != null ) { + StringBuilder buf = new StringBuilder( 0x400 ); + synchronized( this.listModel ) { + int nRows = this.listModel.getSize(); + int[] rows = this.list.getSelectedIndices(); + for( int row : rows ) { + if( (row >= 0) && (row < nRows) ) { + Object o = this.listModel.getElementAt( row ); + if( o != null ) { + String s = o.toString(); + if( s != null ) { + if( !s.isEmpty() ) { + if( buf.length() > 0 ) { + buf.append( (char) '\n' ); + } + buf.append( s ); + } + } + } + } + } + } + if( buf.length() > 0 ) { + StringSelection ss = new StringSelection( buf.toString() ); + clipboard.setContents( ss, ss ); + } + } + } + catch( IllegalStateException ex ) {} + } + } + + + private void doSelectRootDir() + { + File preselection = null; + if( this.dirPath != null ) { + try { + preselection = this.dirPath.toFile(); + } + catch( UnsupportedOperationException ex ) {} + } + File dirFile = DirSelectDlg.selectDirectory( this, preselection ); + if( dirFile != null ) { + try { + setDirectory( dirFile.toPath() ); + } + catch( InvalidPathException ex ) {} + } + } + + + private void doStart() + { + if( this.thread == null ) { + try { + if( this.dirPath == null ) { + throw new UserInputException( + "Sie m\u00FCssen im Feld \'Suchen in:\'" + + " ein Verzeichnis ausw\u00E4hlen\n" + + "welches durchsucht werden soll!" ); + } + this.findFileNamePatterns = parseFileNameMask(); + this.findFileSizeFrom = parseFileSize( this.fldFileSizeFrom ); + this.findFileSizeTo = parseFileSize( this.fldFileSizeTo ); + + AtomicInteger lastFieldFrom = new AtomicInteger(); + this.findLastModifiedFrom = parseTimestamp( + this.fldLastModified, + lastFieldFrom ); + + AtomicInteger lastFieldTill = new AtomicInteger(); + this.findLastModifiedTill = parseTimestamp( + this.fldLastModifiedTill, + lastFieldTill ); + + if( this.findLastModifiedTill != null ) { + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.setTimeInMillis( this.findLastModifiedTill ); + cal.add( lastFieldTill.get(), 1 ); + this.findLastModifiedTill = cal.getTimeInMillis(); + } + if( (this.findLastModifiedFrom != null) + && (this.findLastModifiedTill == null) ) + { + Calendar cal = Calendar.getInstance(); + cal.clear(); + cal.setTimeInMillis( this.findLastModifiedFrom ); + cal.add( lastFieldFrom.get(), 1 ); + this.findLastModifiedTill = cal.getTimeInMillis(); + } + + String text = this.fldText.getText(); + if( text != null ) { + if( text.isEmpty() ) { + text = null; + } + } + + if( this.findFileNamePatterns == null ) { + throw new IOException( "Sie m\u00FCssen Suchkriterien eingeben,\n" + + "mindestens die Dateinamensmaske!" ); + } + + this.listModel.clear(); + this.findSubTrees = this.btnSubTrees.isSelected(); + this.findIgnoreCase = !this.btnCaseSensitive.isSelected(); + this.findPrintMatchedRows = this.btnPrintMatchedRows.isSelected(); + this.findText = text; + if( (text != null) && this.findIgnoreCase ) { + /* + * Bei der Umwandlung einer ganzen Zeichenkette in Grossbuchstaben + * wird aus einem sz ein SS. + * Da das hier aber stoerend ist, + * werden die Zeichen einzeln umgewandelt. + * Dadurch bleibt ein sz ein sz. + */ + char[] a = text.toCharArray(); + if( a != null ) { + for( int i = 0; i < a.length; i++ ) { + a[ i ] = Character.toUpperCase( a[ i ] ); + } + this.findText = new String( a ); + } else { + this.findText = text.toUpperCase(); + } + } + synchronized( this ) { + this.thread = new Thread( + Main.getThreadGroup(), + this, + "JKCEMU file search" ); + this.thread.start(); + } + updFieldsEnabled(); + this.btnStartStop.setText( "Suche beenden" ); + try { + Main.setLastFile( this.dirPath.toFile(), "find.dir" ); + } + catch( UnsupportedOperationException ex ) {} + } + catch( UserInputException ex ) { + BasicDlg.showErrorDlg( this, ex.getMessage(), "Eingabefehler" ); + } + catch( IOException | PatternSyntaxException ex ) { + BasicDlg.showErrorDlg( this, ex ); + } + } + } + + + private void doStop() + { + this.fileVisitResult = FileVisitResult.TERMINATE; + this.mnuStop.setEnabled( false ); + } + + + private void doRemoveFromResult() + { + synchronized( this.listModel ) { + removeFromResult( this.list.getSelectedIndices() ); + } + } + + + /* --- private Methoden --- */ + + private void appendErrorToResult( Path path, IOException ex ) + { + if( path != null ) { + String pathText = path.toString(); + if( pathText != null ) { + pathText = pathText.trim(); + if( !pathText.isEmpty() ) { + String msg = null; + if( ex != null ) { + msg = ex.getMessage(); + if( msg != null ) { + msg = msg.trim(); + if( msg.isEmpty() || msg.equalsIgnoreCase( pathText ) ) { + msg = null; + } + } + } + if( msg != null ) { + appendToResult( + new FindFilesCellRenderer.EmphasizedEntry( + "Fehler: " + pathText + ": " + msg ), + null ); + } else { + appendToResult( + new FindFilesCellRenderer.EmphasizedEntry( + "Fehler: \"" + pathText + + "\" kann nicht gelesen werden" ), + null ); + } + } + } + } + } + + + private void appendToResult( Object o, java.util.List rows ) + { + if( this.fileVisitResult == FileVisitResult.CONTINUE ) { + if( o != null ) { + synchronized( this.listModel ) { + this.listModel.addElement( o ); + if( rows != null ) { + for( String row : rows ) { + this.listModel.addElement( + new FindFilesCellRenderer.CodeEntry( + "\u0020\u0020\u0020\u0020" + row, + o instanceof FileEntry ? (FileEntry) o : null ) ); + } + } + if( this.listModel.getSize() >= MAX_RESULT_ROWS ) { + this.listModel.addElement( "" ); + this.listModel.addElement( + new FindFilesCellRenderer.EmphasizedEntry( + "Suche abgebrochen aufgrund zu vieler" + + " Eintr\u00E4ge im Suchergebnis" ) ); + doStop(); + } + } + } + } + } + + + private boolean checkPopup( MouseEvent e ) + { + boolean status = false; + if( e != null ) { + if( e.isPopupTrigger() ) { + Component c = e.getComponent(); + if( c != null ) { + this.popup.show( c, e.getX(), e.getY() ); + status = true; + } + } + } + return status; + } + + + private static void fireFilesChanged( + java.util.List files ) + { + Set paths = EmuUtil.createPathSet(); + for( FileActionMngr.FileObject f : files ) { + Path p = f.getPath(); + if( p != null ) { + paths.add( p ); + } + } + if( !paths.isEmpty() ) { + FileBrowserFrm.fireFilesChanged( paths ); + } + } + + + /* + * Die Methode liefert aus den in der Ergebnisliste selektierten Zeilen + * eine Liste mit FileObjects. + * Dabei wird ueber ein Set sichergestellt, + * dass kein FileObject doppelt in der Ergbenisliste enthalten. + * Wenn die Ergebnisliste auch Code-Eintraege enthaelt, + * ist es naemlich moeglich, das gleiche FileObject mehrmals auszuwaehlen. + */ + private java.util.List getFileObjectList( + int[] rows ) + { + int nRows = this.listModel.getSize(); + int size = (nRows > 1 ? nRows : 1); + java.util.List rv = new ArrayList<>( size ); + Set objs = new HashSet<>(); + for( int i = 0; i < rows.length; i++ ) { + int row = rows[ i ]; + if( (row >= 0) && (row < nRows) ) { + Object o = this.listModel.getElementAt( row ); + if( o != null ) { + FileActionMngr.FileObject f = null; + if( o instanceof FileActionMngr.FileObject ) { + f = (FileActionMngr.FileObject) o; + } else if( o instanceof FindFilesCellRenderer.CodeEntry ) { + f = ((FindFilesCellRenderer.CodeEntry) o).getFileObject(); + } + if( f != null ) { + if( objs.add( f ) ) { + rv.add( f ); + } + } + } + } + } + return rv; + } + + + private static Long parseFileSize( JTextField fld ) + throws UserInputException + { + Long rv = null; + String s = fld.getText(); + if( s != null ) { + s = s.trim(); + if( !s.isEmpty() ) { + try { + long v = Long.parseLong( s ); + if( v < 0 ) { + throw new UserInputException( + "Dateigr\u00F6\u00DFe kann nicht kleiner Null sein" ); + } + rv = new Long( v ); + } + catch( NumberFormatException ex ) { + throw new UserInputException( + "Ung\u00FCltige Dateigr\u00F6\u00DFe" ); + } + } + } + return rv; + } + + + private static Long parseTimestamp( + JTextField textFld, + AtomicInteger rvLastField ) throws UserInputException + { + Long rv = null; + String text = textFld.getText(); + if( text != null ) { + text = text.trim(); + if( !text.isEmpty() ) { + + // Eingabe zerlegen + String dateText = null; + String timeText = null; + try { + String[] items = text.split( "\\s" ); + if( items != null ) { + for( String item : items ) { + if( item != null ) { + if( !item.isEmpty() ) { + if( dateText == null ) { + dateText = item; + } else if( timeText == null ) { + timeText = item; + } else { + throw new UserInputException( + "Ung\u00FCltige Eingabe f\u00FCr Datem/Uhrzeit" ); + } + } + } + } + } + } + catch( PatternSyntaxException ex ) {} + if( dateText != null ) { + + // Vorbereitungen zum Parsen und neu Formatieren + if( dateFmtShort == null ) { + dateFmtShort = DateFormat.getDateInstance( DateFormat.SHORT ); + } + if( dateFmtMedium == null ) { + dateFmtMedium = DateFormat.getDateInstance( DateFormat.MEDIUM ); + } + StringBuilder buf = new StringBuilder( 20 ); + int lastField = Calendar.DAY_OF_MONTH; + + // Datum parsen + java.util.Date date = null; + try { + date = dateFmtShort.parse( dateText ); + } + catch( ParseException ex ) {} + if( date == null ) { + try { + date = dateFmtMedium.parse( dateText ); + } + catch( ParseException ex ) {} + } + if( date == null ) { + throw new UserInputException( "Ung\u00FCltiges Datum" ); + } + buf.append( dateFmtMedium.format( date ) ); + rv = new Long( date.getTime() ); + + // Uhrzeit parsen + if( timeText != null ) { + int hour = -1; + int minute = -1; + int second = -1; + try { + int len = timeText.length(); + int h1 = -1; + int h2 = -1; + int m1 = -1; + int m2 = -1; + int s1 = -1; + int s2 = -1; + if( len == 4 ) { + h1 = 0; + if( timeText.charAt( 1 ) == ':' ) { + h2 = 1; // h:mm + } else { + h2 = 2; // hhmm + } + m1 = 2; + m2 = 4; + } else if( len == 5 ) { + if( timeText.charAt( 2 ) == ':' ) { + h1 = 0; // hh:mm + h2 = 2; + m1 = 3; + m2 = 5; + } + } else if( len == 6 ) { + h1 = 0; // hhmmss + h2 = 2; + m1 = 2; + m2 = 4; + s1 = 4; + s2 = 6; + } else if( len == 7 ) { + if( (timeText.charAt( 1 ) == ':') + && (timeText.charAt( 4 ) == ':') ) + { + h1 = 0; // h:mm:ss + h2 = 1; + m1 = 2; + m2 = 4; + s1 = 5; + s2 = 7; + } + } else if( len == 8 ) { + if( (timeText.charAt( 2 ) == ':') + && (timeText.charAt( 5 ) == ':') ) + { + h1 = 0; // hh:mm:ss + h2 = 2; + m1 = 3; + m2 = 5; + s1 = 6; + s2 = 8; + } + } + if( (h1 >= 0) && (h2 > h1) + && (m1 >= 0) && (m2 > m1) ) + { + hour = Integer.parseInt( timeText.substring( h1, h2 ) ); + minute = Integer.parseInt( timeText.substring( m1, m2 ) ); + lastField = Calendar.MINUTE; + buf.append( String.format( " %02d:%02d", hour, minute ) ); + if( (s1 >= 0) && (s2 > s1) ) { + second = Integer.parseInt( timeText.substring( s1, s2 ) ); + lastField = Calendar.SECOND; + buf.append( String.format( ":%02d", second ) ); + } + } + } + catch( NumberFormatException ex ) { + hour = -1; + } + if( (hour >= 0) && (hour < 24) + && (minute >= 0) && (minute < 60) ) + { + rv = rv.longValue() + (((hour * 60) + minute) * 60000L); + if( (second >= 0) && (second < 60) ) { + rv = rv.longValue() + (second * 1000L); + } + } else { + throw new UserInputException( "Ung\u00FCltige Uhrzeit" ); + } + } + textFld.setText( buf.toString() ); + if( rvLastField != null ) { + rvLastField.set( lastField ); + } + } + } + } + return rv; + } + + + private void removeFromResult( Collection files ) + { + synchronized( this.listModel ) { + for( Object f : files ) { + String s = f.toString(); + int n = this.listModel.size(); + for( int i = (n - 1 ); i >= 0; --i ) { + if( this.listModel.get( i ).toString().equals( s ) ) { + this.listModel.remove( i ); + } + } + } + } + } + + + private void removeFromResult( int[] rows ) + { + if( rows != null ) { + if( rows.length > 0 ) { + Arrays.sort( rows ); + for( int i = (rows.length - 1); i >= 0; --i ) { + int rowNum = rows[ i ]; + if( (rowNum >= 0) && (rowNum < this.listModel.size()) ) { + this.listModel.removeElementAt( rowNum ); + } + } + } + } + } + + + public void searchFinished() + { + this.btnStartStop.setText( "Suche starten" ); + this.fldCurDir.setText( "" ); + if( (this.fileVisitResult == FileVisitResult.CONTINUE) + && this.listModel.isEmpty() ) + { + this.listModel.addElement( "Keine Datei gefunden" ); + } + updFieldsEnabled(); + } + + + private void showError( Exception ex ) + { + BasicDlg.showErrorDlg( this, ex ); + } + + + /* + * Die Methode parst die in Eingabefeld stehende Dateimaske + * und liefert entsprechende Pattern-Objekte zurueck. + * Wenn kein Pattern-Objekt generiert werden konnte, + * wird null zurueckgeliefert. + */ + private Collection parseFileNameMask() + { + Collection rv = null; + String mask = this.fldFileNameMask.getText(); + if( mask != null ) { + int len = mask.length(); + if( len > 0 ) { + StringBuilder buf = new StringBuilder( len ); + CharacterIterator iter = new StringCharacterIterator( mask ); + char ch = iter.first(); + while( ch != CharacterIterator.DONE ) { + + // Trennzeichen uebergehen + while( (ch == '\u0020') || (ch == ',') || (ch == ';') ) { + ch = iter.next(); + } + + // zu einer Maske gehoerende Zeichen verarbeiten + while( (ch != CharacterIterator.DONE) + && (ch != '\u0020') && (ch != ',') && (ch != ';') ) + { + if( ch == '\"' ) { + /* + * in doppelte Hochkommas eingeschlossene Zeichen + * unveraendert uebernehmen + */ + ch = iter.next(); + while( (ch != CharacterIterator.DONE) && (ch != '\"') ) { + buf.append( ch ); + ch = iter.next(); + } + if( ch == '\"' ) { + ch = iter.next(); + } + } else { + buf.append( ch ); + ch = iter.next(); + } + } + if( buf.length() > 0 ) { + try { + Pattern pattern = EmuUtil.compileFileNameMask( buf.toString() ); + if( pattern != null ) { + if( rv == null ) { + rv = new ArrayList<>(); + } + rv.add( pattern ); + } + } + catch( PatternSyntaxException ex ) {} + finally { + buf.setLength( 0 ); + } + } + } + } + } + return rv; + } + + + private void updFieldsEnabled() + { + boolean stateInput = (this.thread == null); + boolean stateRunning = !stateInput; + this.btnRootDirSelect.setEnabled( stateInput ); + this.fldFileNameMask.setEnabled( stateInput ); + this.fldFileSizeFrom.setEnabled( stateInput ); + this.fldFileSizeTo.setEnabled( stateInput ); + this.fldLastModified.setEnabled( stateInput ); + this.fldLastModifiedTill.setEnabled( stateInput ); + this.fldText.setEnabled( stateInput ); + this.btnCaseSensitive.setEnabled( stateInput ); + this.btnPrintMatchedRows.setEnabled( stateInput ); + this.btnSubTrees.setEnabled( stateInput ); + this.labelCurDir.setEnabled( stateRunning ); + this.fldCurDir.setEnabled( stateRunning ); + this.mnuStart.setEnabled( stateInput ); + this.mnuStop.setEnabled( stateRunning ); + } +} diff --git a/src/jkcemu/filebrowser/GZipPacker.java b/src/jkcemu/filebrowser/GZipPacker.java index 9821aac..f5c7c70 100644 --- a/src/jkcemu/filebrowser/GZipPacker.java +++ b/src/jkcemu/filebrowser/GZipPacker.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -12,22 +12,23 @@ import java.lang.*; import java.util.zip.*; import javax.swing.*; -import jkcemu.base.EmuUtil; +import jkcemu.Main; +import jkcemu.base.*; public class GZipPacker extends Thread { - private FileBrowserFrm fileBrowserFrm; - private File srcFile; - private File outFile; + private BasicFrm owner; + private File srcFile; + private File outFile; public static void packFile( - FileBrowserFrm fileBrowserFrm, - File srcFile, - File outFile ) + BasicFrm owner, + File srcFile, + File outFile ) { - (new GZipPacker( fileBrowserFrm, srcFile, outFile )).start(); + (new GZipPacker( owner, srcFile, outFile )).start(); } @@ -46,7 +47,7 @@ public void run() in = new BufferedInputStream( new FileInputStream( this.srcFile ) ); if( srcLen > 0 ) { ProgressMonitorInputStream pmIn = new ProgressMonitorInputStream( - this.fileBrowserFrm, + this.owner, "Packen von " + this.srcFile.getName() + "...", in ); ProgressMonitor pm = pmIn.getProgressMonitor(); @@ -61,7 +62,7 @@ public void run() out = new GZIPOutputStream( new BufferedOutputStream( new FileOutputStream( outFile ) ) ); - this.fileBrowserFrm.fireDirectoryChanged( this.outFile.getParentFile() ); + FileBrowserFrm.fireFileChanged( this.outFile.getParentFile() ); int b = in.read(); while( b != -1 ) { @@ -86,9 +87,9 @@ public void run() EmuUtil.doClose( in ); EmuUtil.doClose( out ); } - this.fileBrowserFrm.fireDirectoryChanged( this.outFile.getParentFile() ); + FileBrowserFrm.fireFileChanged( this.outFile.getParentFile() ); if( msg != null ) { - this.fileBrowserFrm.showErrorMsg( msg ); + this.owner.fireShowErrorMsg( msg ); } } @@ -96,14 +97,13 @@ public void run() /* --- private Konstruktoren --- */ private GZipPacker( - FileBrowserFrm fileBrowserFrm, - File srcFile, - File outFile ) + BasicFrm owner, + File srcFile, + File outFile ) { - super( "JKCEMU gzip packer" ); - this.fileBrowserFrm = fileBrowserFrm; - this.srcFile = srcFile; - this.outFile = outFile; + super( Main.getThreadGroup(), "JKCEMU gzip packer" ); + this.owner = owner; + this.srcFile = srcFile; + this.outFile = outFile; } } - diff --git a/src/jkcemu/filebrowser/GZipUnpacker.java b/src/jkcemu/filebrowser/GZipUnpacker.java index 6b8bc39..c4ff287 100644 --- a/src/jkcemu/filebrowser/GZipUnpacker.java +++ b/src/jkcemu/filebrowser/GZipUnpacker.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -12,22 +12,23 @@ import java.lang.*; import java.util.zip.*; import javax.swing.*; -import jkcemu.base.EmuUtil; +import jkcemu.Main; +import jkcemu.base.*; public class GZipUnpacker extends Thread { - private FileBrowserFrm fileBrowserFrm; - private File srcFile; - private File outFile; + private BasicFrm owner; + private File srcFile; + private File outFile; public static void unpackFile( - FileBrowserFrm fileBrowserFrm, - File srcFile, - File outFile ) + BasicFrm owner, + File srcFile, + File outFile ) { - (new GZipUnpacker( fileBrowserFrm, srcFile, outFile )).start(); + (new GZipUnpacker( owner, srcFile, outFile )).start(); } @@ -46,7 +47,7 @@ public void run() in = new BufferedInputStream( new FileInputStream( this.srcFile ) ); if( srcLen > 0 ) { ProgressMonitorInputStream pmIn = new ProgressMonitorInputStream( - this.fileBrowserFrm, + this.owner, "Entpacken von " + this.srcFile.getName() + "...", in ); ProgressMonitor pm = pmIn.getProgressMonitor(); @@ -61,7 +62,7 @@ public void run() gzipIn = new GZIPInputStream( in ); out = new BufferedOutputStream( new FileOutputStream( this.outFile ) ); - this.fileBrowserFrm.fireDirectoryChanged( this.outFile.getParentFile() ); + FileBrowserFrm.fireFileChanged( this.outFile.getParentFile() ); int b = gzipIn.read(); while( b != -1 ) { @@ -86,9 +87,9 @@ public void run() EmuUtil.doClose( in ); EmuUtil.doClose( out ); } - this.fileBrowserFrm.fireDirectoryChanged( this.outFile.getParentFile() ); + FileBrowserFrm.fireFileChanged( this.outFile.getParentFile() ); if( msg != null ) { - this.fileBrowserFrm.showErrorMsg( msg ); + this.owner.fireShowErrorMsg( msg ); } } @@ -96,14 +97,13 @@ public void run() /* --- private Konstruktoren --- */ private GZipUnpacker( - FileBrowserFrm fileBrowserFrm, - File srcFile, - File outFile ) + BasicFrm owner, + File srcFile, + File outFile ) { - super( "JKCEMU gzip unpacker" ); - this.fileBrowserFrm = fileBrowserFrm; - this.srcFile = srcFile; - this.outFile = outFile; + super( Main.getThreadGroup(), "JKCEMU gzip unpacker" ); + this.owner = owner; + this.srcFile = srcFile; + this.outFile = outFile; } } - diff --git a/src/jkcemu/filebrowser/LastModifiedDlg.java b/src/jkcemu/filebrowser/LastModifiedDlg.java index f4eb2a0..bb01e42 100644 --- a/src/jkcemu/filebrowser/LastModifiedDlg.java +++ b/src/jkcemu/filebrowser/LastModifiedDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -20,12 +20,12 @@ public class LastModifiedDlg extends BasicDlg { - private FileBrowserFrm fileBrowserFrm; private java.util.List files; private int numChanged; private int numUnchanged; private boolean suppressArchiveErrDlg; - private DateFormat dateFmt; + private DateFormat dateFmtStd; + private DateFormat dateFmtShort; private JRadioButton btnCurrentTime; private JRadioButton btnTimeInput; private JCheckBox btnRecursive; @@ -35,20 +35,75 @@ public class LastModifiedDlg extends BasicDlg private JButton btnCancel; - public LastModifiedDlg( - FileBrowserFrm fileBrowserFrm, + /* + * Rueckgabewert: + * true: Zeitstempel wurde geaendert + * false: Zeitstempel nicht geaendert + */ + public static boolean open( + Window owner, + java.util.List files ) + { + boolean rv = false; + if( files != null ) { + if( !files.isEmpty() ) { + LastModifiedDlg dlg = new LastModifiedDlg( owner, files ); + dlg.setVisible( true ); + if( dlg.numChanged > 0 ) { + rv = true; + } + } + } + return rv; + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + protected boolean doAction( EventObject e ) + { + boolean rv = false; + if( e != null ) { + Object src = e.getSource(); + if( src != null ) { + if( (src == this.btnCurrentTime) || (src == this.btnTimeInput) ) { + rv = true; + this.fldTime.setEditable( this.btnTimeInput.isSelected() ); + } + else if( src == this.btnOK ) { + rv = true; + doApply(); + } + else if( src == this.btnCancel ) { + rv = true; + doClose(); + } + } + } + return rv; + } + + + /* --- Konstruktor --- */ + + private LastModifiedDlg( + Window owner, java.util.List files ) { - super( fileBrowserFrm, "\u00C4nderungszeitpunkt" ); - this.fileBrowserFrm = fileBrowserFrm; + super( owner, "\u00C4nderungszeitpunkt" ); this.files = files; this.numChanged = 0; this.numUnchanged = 0; this.suppressArchiveErrDlg = false; - this.dateFmt = DateFormat.getDateTimeInstance( + this.dateFmtStd = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM ); - this.dateFmt.setLenient( false ); + this.dateFmtShort = DateFormat.getDateTimeInstance( + DateFormat.MEDIUM, + DateFormat.SHORT ); + this.dateFmtStd.setLenient( false ); + this.dateFmtShort.setLenient( false ); // Layout @@ -168,7 +223,7 @@ public LastModifiedDlg( if( date == null ) { date = new java.util.Date(); } - this.fldTime.setText( this.dateFmt.format( date ) ); + this.fldTime.setText( this.dateFmtStd.format( date ) ); // Fenstergroesse und -position @@ -178,33 +233,6 @@ public LastModifiedDlg( } - /* --- ueberschriebene Methoden --- */ - - @Override - protected boolean doAction( EventObject e ) - { - boolean rv = false; - if( e != null ) { - Object src = e.getSource(); - if( src != null ) { - if( (src == this.btnCurrentTime) || (src == this.btnTimeInput) ) { - rv = true; - this.fldTime.setEditable( this.btnTimeInput.isSelected() ); - } - else if( src == this.btnOK ) { - rv = true; - doApply(); - } - else if( src == this.btnCancel ) { - rv = true; - doClose(); - } - } - } - return rv; - } - - /* --- private Methoden --- */ private void doApply() @@ -217,7 +245,14 @@ private void doApply() else if( this.btnTimeInput.isSelected() ) { String text = this.fldTime.getText(); if( text != null ) { - java.util.Date date = this.dateFmt.parse( text ); + java.util.Date date = null; + try { + date = this.dateFmtStd.parse( text ); + } + catch( ParseException ex ) {} + if( date == null ) { + date = this.dateFmtShort.parse( text ); + } if( date != null ) { time = date.getTime(); } @@ -391,4 +426,3 @@ private void setLastModifiedInZIP( File file, long time ) } } } - diff --git a/src/jkcemu/filebrowser/TarEntry.java b/src/jkcemu/filebrowser/TarEntry.java index c7dc040..e99cd30 100644 --- a/src/jkcemu/filebrowser/TarEntry.java +++ b/src/jkcemu/filebrowser/TarEntry.java @@ -1,5 +1,5 @@ /* - * (c) 2008 Jens Mueller + * (c) 2008-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -10,18 +10,22 @@ import java.io.*; import java.lang.*; +import java.nio.file.attribute.PosixFilePermission; +import java.util.*; public class TarEntry { - private String entryName; - private String typeText; - private boolean typeFile; - private boolean typeDir; - private int entryMode; - private long entrySize; - private long entryTime; - private String errMsg; + public enum EntryType { DIRECTORY, SYMBOLIC_LINK, REGULAR_FILE, OTHER }; + + private String entryName; + private EntryType entryType; + private String typeText; + private String linkTarget; + private Set permissions; + private long entrySize; + private long entryTime; + private String errMsg; public String getErrorMsg() @@ -30,9 +34,9 @@ public String getErrorMsg() } - public int getMode() + public String getLinkTarget() { - return this.entryMode; + return this.linkTarget; } @@ -42,6 +46,12 @@ public String getName() } + public Set getPosixFilePermissions() + { + return this.permissions; + } + + public long getSize() { return this.entrySize; @@ -62,46 +72,86 @@ public String getTypeText() public boolean isDirectory() { - return this.typeDir; + return this.entryType.equals( EntryType.DIRECTORY ); + } + + + public boolean isRegularFile() + { + return this.entryType.equals( EntryType.REGULAR_FILE ); } - public boolean isFile() + public boolean isSymbolicLink() { - return this.typeFile; + return this.entryType.equals( EntryType.SYMBOLIC_LINK ); } public static TarEntry readEntryHeader( InputStream in ) throws IOException { - /* - * Kopfblock lesen - * Dabei Bloecke ueberlesen, die nur aus Null-Bytes bestehen - */ - byte[] headerBuf = new byte[ 512 ]; - int pos = 0; - boolean empty = true; - while( empty ) { - pos = 0; - while( pos < headerBuf.length ) { - int b = in.read(); - if( b == -1 ) { - if( pos > 0 ) { - throw new IOException( "Unerwartetes Ende der TAR-Datei" ); - } else { - return null; - } + String entryName = null; + byte[] headerBuf = new byte[ 0x200 ]; + boolean paxHeader = false; + do { + + // Block lesen + if( !readFilledBlock( in, headerBuf ) ) { + return null; + } + + // Pax-Header pruefen + int typeByte = (int) headerBuf[ 156 ] & 0xFF; + if( typeByte == 'g' ) { + // globaler Pax-Header -> Rest des Pax-Headers einfach ueberlesen + paxHeader = true; + in.skip( 0x200L ); + } else if( typeByte == 'x' ) { + /* + * lokaler Pax-Header -> Rest des Pax-Headers lesen und versuchen, + * daraus den Namen (Pfad) zu ermitteln + */ + paxHeader = true; + if( !readFilledBlock( in, headerBuf ) ) { + return null; } - if( b != 0 ) { - empty = false; + try { + int pos = 0; + while( pos < headerBuf.length ) { + int endPos = pos; + while( endPos < headerBuf.length ) { + byte b = headerBuf[ endPos ]; + if( (b == (byte) 0) + || (b == (byte) 0x0A) || (b == (byte) 0x0D) ) + { + break; + } + endPos++; + } + if( endPos > pos ) { + String text = new String( + headerBuf, + pos, + endPos - pos, + "ISO-8859-1" ); + int idx = text.indexOf( " path=" ); + if( (idx >= 0) && ((idx + 6) < text.length()) ) { + entryName = text.substring( idx + 6 ); + break; + } + } + pos = endPos + 1; + } } - headerBuf[ pos++ ] = (byte) b; + catch( Exception ex ) {} + } else { + paxHeader = false; } - } + } while( paxHeader ); // Pruefsumme ermitteln und vergleichen int chkSumOrg = parseOctalNumber( headerBuf, 148, 156 ); - pos = 148; + int pos = 148; while( pos < 156 ) { headerBuf[ pos++ ] = (byte) 0x20; } @@ -119,69 +169,63 @@ public static TarEntry readEntryHeader( InputStream in ) throws IOException } // Werte lesen - String errMsg = null; - String entryName = null; - String linkName = null; - String typeText = null; - boolean typeDir = false; - boolean typeFile = false; - - StringBuilder buf = new StringBuilder( 100 ); - pos = 0; - while( pos < 100 ) { - char ch = (char) ((int) headerBuf[ pos++ ] & 0xFF); - if( ch == 0 ) { - break; - } - if( ch >= '\u0020' ) { - buf.append( ch ); - } else { - errMsg = "Ung\u00FCltiges Zeichen im Namen des Eintrags"; + String errMsg = null; + if( entryName == null ) { + StringBuilder buf = new StringBuilder( 100 ); + pos = 0; + while( pos < 100 ) { + int b = (int) headerBuf[ pos++ ] & 0xFF; + if( b == 0 ) { + break; + } + if( b >= '\u0020' ) { + buf.append( (char) b ); + } else { + errMsg = "Ung\u00FCltiges Zeichen im Namen des Eintrags"; + } } - } - if( buf.length() > 0 ) { entryName = buf.toString(); - } else { - if( errMsg == null ) - errMsg = "Name des Eintrags fehlt"; } - int entryMode = parseOctalNumber( headerBuf, 100, 108 ); - long entrySize = parseOctalNumber( headerBuf, 124, 136 ); - long entryTime = parseOctalNumber( headerBuf, 136, 148 ) * 1000L; - int entryType = (int) headerBuf[ 156 ] & 0xFF; - switch( entryType ) { + if( entryName.isEmpty() && (errMsg == null) ) { + errMsg = "Name des Eintrags fehlt"; + } + String linkTarget = null; + String typeText = null; + EntryType entryType = EntryType.OTHER; + int entryMode = parseOctalNumber( headerBuf, 100, 108 ); + long entrySize = parseOctalNumber( headerBuf, 124, 136 ); + long entryTime = parseOctalNumber( headerBuf, 136, 148 ) * 1000L; + int typeByte = (int) headerBuf[ 156 ] & 0xFF; + switch( typeByte ) { case 0: case '0': - case '7': - typeText = "Datei"; - typeFile = true; + entryType = EntryType.REGULAR_FILE; + typeText = "Datei"; break; case '1': case '2': - buf = new StringBuilder( 128 ); - if( entryType == '2' ) { - buf.append( "Sym. Link" ); - } else { - buf.append( "Link" ); - } - int len = buf.length(); - buf.append( ": " ); - boolean hasLinkName = false; - pos = 157; - while( pos < 257 ) { - char ch = (char) ((int) headerBuf[ pos++ ] & 0xFF); - if( ch > 0 ) { - buf.append( ch ); - hasLinkName = true; + { + StringBuilder buf = new StringBuilder( 128 ); + pos = 157; + while( pos < 257 ) { + char ch = (char) ((int) headerBuf[ pos++ ] & 0xFF); + if( ch > 0 ) { + buf.append( ch ); + } else { + break; + } + } + if( buf.length() > 0 ) { + linkTarget = buf.toString(); + } + if( typeByte == '2' ) { + entryType = EntryType.SYMBOLIC_LINK; + typeText = "Sym. Link: " + buf.toString(); } else { - break; + typeText = "Link: " + buf.toString(); } } - if( !hasLinkName ) { - buf.setLength( len ); - } - typeText = buf.toString(); break; case '3': @@ -190,8 +234,8 @@ public static TarEntry readEntryHeader( InputStream in ) throws IOException break; case '5': - typeText = "Verzeichnis"; - typeDir = true; + entryType = EntryType.DIRECTORY; + typeText = "Verzeichnis"; break; case '6': @@ -199,18 +243,46 @@ public static TarEntry readEntryHeader( InputStream in ) throws IOException break; default: - if( entryType > 0x20 ) { - typeText = "Typ: " + Character.toString( (char) entryType ); + if( typeByte > 0x20 ) { + typeText = "Typ: " + Character.toString( (char) typeByte ); } else { - typeText = "Typ: " + Integer.toString( entryType ); + typeText = "Typ: " + Integer.toString( typeByte ); } } + Set permissions = new HashSet<>(); + if( (entryMode & 0x001) != 0 ) { + permissions.add( PosixFilePermission.OTHERS_EXECUTE ); + } + if( (entryMode & 0x002) != 0 ) { + permissions.add( PosixFilePermission.OTHERS_WRITE ); + } + if( (entryMode & 0x004) != 0 ) { + permissions.add( PosixFilePermission.OTHERS_READ ); + } + if( (entryMode & 0x008) != 0 ) { + permissions.add( PosixFilePermission.GROUP_EXECUTE ); + } + if( (entryMode & 0x010) != 0 ) { + permissions.add( PosixFilePermission.GROUP_WRITE ); + } + if( (entryMode & 0x020) != 0 ) { + permissions.add( PosixFilePermission.GROUP_READ ); + } + if( (entryMode & 0x040) != 0 ) { + permissions.add( PosixFilePermission.OWNER_EXECUTE ); + } + if( (entryMode & 0x080) != 0 ) { + permissions.add( PosixFilePermission.OWNER_WRITE ); + } + if( (entryMode & 0x100) != 0 ) { + permissions.add( PosixFilePermission.OWNER_READ ); + } return new TarEntry( entryName, + entryType, typeText, - typeFile, - typeDir, - entryMode, + linkTarget, + permissions, entrySize, entryTime, errMsg ); @@ -220,23 +292,23 @@ public static TarEntry readEntryHeader( InputStream in ) throws IOException /* --- private Konstruktoren und Methoden --- */ private TarEntry( - String entryName, - String typeText, - boolean typeFile, - boolean typeDir, - int entryMode, - long entrySize, - long entryTime, - String errMsg ) + String entryName, + EntryType entryType, + String typeText, + String linkTarget, + Set permissions, + long entrySize, + long entryTime, + String errMsg ) { - this.entryName = entryName; - this.typeText = typeText; - this.typeFile = typeFile; - this.typeDir = typeDir; - this.entryMode = entryMode; - this.entrySize = entrySize; - this.entryTime = entryTime; - this.errMsg = errMsg; + this.entryName = entryName; + this.entryType = entryType; + this.typeText = typeText; + this.linkTarget = linkTarget; + this.permissions = permissions; + this.entrySize = entrySize; + this.entryTime = entryTime; + this.errMsg = errMsg; } @@ -253,5 +325,35 @@ private static int parseOctalNumber( byte[] buf, int pos, int endPos ) } return value; } + + + /* + * Die Methode liest einen Block (512 Bytes). + * Bloecke, die nur Null-Bytes enthalten, werden dabei ueberlesen. + */ + private static boolean readFilledBlock( + InputStream in, + byte[] buf ) throws IOException + { + int pos = 0; + boolean empty = true; + while( empty ) { + pos = 0; + while( pos < buf.length ) { + int b = in.read(); + if( b < 0 ) { + if( empty ) { + return false; + } + throw new IOException( "Unerwartetes Ende der TAR-Datei" ); + } + if( b != 0 ) { + empty = false; + } + buf[ pos++ ] = (byte) b; + } + } + return true; + } } diff --git a/src/jkcemu/filebrowser/TarPacker.java b/src/jkcemu/filebrowser/TarPacker.java index 026fd19..07915c6 100644 --- a/src/jkcemu/filebrowser/TarPacker.java +++ b/src/jkcemu/filebrowser/TarPacker.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -11,25 +11,34 @@ import java.awt.*; import java.io.*; import java.lang.*; +import java.nio.file.*; +import java.nio.file.attribute.*; import java.util.*; import java.util.zip.GZIPOutputStream; import jkcemu.base.*; public class TarPacker extends AbstractThreadDlg + implements FileVisitor { - private Collection srcFiles; + private Collection srcPaths; + private String curEntryDir; + private Path curRootPath; + private Path outPath; private File outFile; + private OutputStream out; private boolean compression; + private boolean ownerEnabled; + private boolean posixEnabled; public static void packFiles( Window owner, - Collection srcFiles, + Collection srcPaths, File outFile, boolean compression ) { - Dialog dlg = new TarPacker( owner, srcFiles, outFile, compression ); + Dialog dlg = new TarPacker( owner, srcPaths, outFile, compression ); if( compression ) { dlg.setTitle( "TGZ-Datei packen" ); } else { @@ -39,33 +48,194 @@ public static void packFiles( } + /* --- FileVisitor --- */ + + @Override + public FileVisitResult postVisitDirectory( Path dir, IOException ex ) + { + return this.canceled ? FileVisitResult.TERMINATE + : FileVisitResult.CONTINUE; + } + + + @Override + public FileVisitResult preVisitDirectory( + Path dir, + BasicFileAttributes attrs ) + { + FileVisitResult rv = FileVisitResult.SKIP_SUBTREE; + if( (this.out == null) || this.canceled ) { + rv = FileVisitResult.TERMINATE; + } else { + if( (dir != null) && (attrs != null) ) { + if( attrs.isDirectory() ) { + this.curEntryDir = getEntryDir( dir ); + if( this.curEntryDir != null ) { + if( !this.curEntryDir.isEmpty() ) { + appendToLog( dir.toString() + "\n" ); + try { + writeTarHeader( + dir, + this.curEntryDir, + '5', // Typ: Directory + null, // verlinkter Name + 0, // Dateilaenge + attrs, + true ); // executable + rv = FileVisitResult.CONTINUE; + } + catch( IOException ex ) { + appendErrorToLog( ex ); + } + } + } + } + } + } + return rv; + } + + + @Override + public FileVisitResult visitFile( Path file, BasicFileAttributes attrs ) + { + FileVisitResult rv = FileVisitResult.SKIP_SIBLINGS; + if( (this.out == null) || this.canceled ) { + rv = FileVisitResult.TERMINATE; + } else { + if( (file != null) && (attrs != null) ) { + if( this.curEntryDir == null ) { + this.curEntryDir = getEntryDir( file.getParent() ); + } + if( this.curEntryDir != null ) { + int nameCnt = file.getNameCount(); + if( nameCnt > 0 ) { + appendToLog( file.toString() + "\n" ); + if( attrs.isRegularFile() ) { + FileProgressInputStream in = null; + try { + in = openInputFile( file.toFile(), 2 ); + long len = in.length(); + writeTarHeader( + file, + this.curEntryDir + + file.getName( nameCnt - 1 ).toString(), + '0', // Typ: Datei + null, // verlinkter Name + len, + attrs, + false ); + if( len > 0 ) { + long pos = 0; + int b = in.read(); + while( !this.canceled && (pos < len) && (b != -1) ) { + this.out.write( b ); + pos++; + b = in.read(); + } + long nBlocks = len / 512; + long fullLen = nBlocks * 512; + if( fullLen < len ) { + fullLen += 512; + } + while( !this.canceled && (pos < fullLen) ) { + this.out.write( 0 ); + pos++; + } + } + } + catch( IOException ex ) { + appendErrorToLog( ex ); + incErrorCount(); + } + catch( UnsupportedOperationException ex ) { + appendIgnoredToLog(); + } + finally { + EmuUtil.doClose( in ); + } + } else if( attrs.isSymbolicLink() ) { + try { + Path destPath = Files.readSymbolicLink( file ); + if( destPath != null ) { + writeTarHeader( + file, + this.curEntryDir + + file.getName( nameCnt - 1 ).toString(), + '2', // Typ: Link + destPath.toString(), // verlinkter Name + 0, // Dateilaenge + attrs, + true ); // executable + } + } + catch( Exception ex ) { + StringBuilder buf = new StringBuilder( 256 ); + buf.append( " Lesen des symbolischen Links" + + " nicht m\u00F6glich\n" ); + String msg = ex.getMessage(); + if( msg != null ) { + msg = msg.trim(); + if( msg.isEmpty() ) { + buf.append( ":\n" ); + buf.append( msg ); + } + } + buf.append( (char) '\n' ); + appendToLog( buf.toString() ); + disableAutoClose(); + } + } else { + appendIgnoredToLog(); + } + rv = FileVisitResult.CONTINUE; + } + } + } + } + return rv; + } + + + @Override + public FileVisitResult visitFileFailed( Path file, IOException ex ) + { + if( file != null ) { + appendToLog( file.toString() ); + appendErrorToLog( ex ); + incErrorCount(); + } + return this.canceled ? FileVisitResult.TERMINATE + : FileVisitResult.CONTINUE; + } + + /* --- ueberschriebene Methoden --- */ @Override protected void doProgress() { - boolean failed = false; - InputStream in = null; - OutputStream out = null; - GZIPOutputStream gzipOut = null; + boolean failed = false; + InputStream in = null; try { - out = new BufferedOutputStream( new FileOutputStream( this.outFile ) ); + this.out = new BufferedOutputStream( + new FileOutputStream( this.outFile ) ); if( this.compression ) { - gzipOut = new GZIPOutputStream( out ); - out = gzipOut; + this.out = new GZIPOutputStream( this.out ); } - fireDirectoryChanged( this.outFile.getParentFile() ); - for( File file : this.srcFiles ) { - packFile( this.outFile, out, file, "" ); + FileBrowserFrm.fireFileChanged( this.outFile.getParentFile() ); + for( Path path : this.srcPaths ) { + this.curRootPath = path.getParent(); + Files.walkFileTree( path, this ); } for( int i = 0; i < 1024; i++ ) { - out.write( 0 ); + this.out.write( 0 ); } - if( gzipOut != null ) { - gzipOut.finish(); + if( this.out instanceof GZIPOutputStream ) { + ((GZIPOutputStream) this.out).finish(); } - out.close(); - out = null; + this.out.close(); + this.out = null; appendToLog( "\nFertig\n" ); } catch( InterruptedIOException ex ) {} @@ -75,23 +245,23 @@ protected void doProgress() buf.append( this.outFile.getPath() ); String errMsg = ex.getMessage(); if( errMsg != null ) { - if( errMsg.length() > 0 ) { + if( !errMsg.isEmpty() ) { buf.append( ":\n" ); buf.append( errMsg ); } } buf.append( (char) '\n' ); appendToLog( buf.toString() ); - failed = true; incErrorCount(); + failed = true; } finally { - EmuUtil.doClose( out ); + EmuUtil.doClose( this.out ); } if( this.canceled || failed ) { this.outFile.delete(); } - fireDirectoryChanged( this.outFile.getParentFile() ); + FileBrowserFrm.fireFileChanged( this.outFile.getParentFile() ); } @@ -99,92 +269,50 @@ protected void doProgress() private TarPacker( Window owner, - Collection srcFiles, + Collection srcPaths, File outFile, boolean compression ) { super( owner, "JKCEMU tar packer", true ); - this.srcFiles = srcFiles; - this.outFile = outFile; - this.compression = compression; + this.srcPaths = srcPaths; + this.outPath = outFile.toPath(); + this.outFile = outFile; + this.out = null; + this.compression = compression; + this.ownerEnabled = true; + this.posixEnabled = true; } - private void packFile( - File outFile, - OutputStream out, - File file, - String path ) throws IOException + private String getEntryDir( Path dir ) { - if( this.canceled ) { - throw new InterruptedIOException(); - } - if( file != null ) { - appendToLog( file.getPath() + "\n" ); - String entryName = file.getName(); - if( !file.equals( outFile) && (entryName != null) ) { - if( !entryName.isEmpty() ) { - long millis = file.lastModified(); - if( file.isDirectory() ) { - String subPath = path + entryName + "/"; - writeTarHeader( out, subPath, true, false, 0, millis ); - File[] subFiles = file.listFiles(); - if( subFiles != null ) { - for( int i = 0; i < subFiles.length; i++ ) { - packFile( outFile, out, subFiles[ i ], subPath ); - } - } else { - appendErrorToLog( - "Fehler: Verzeichnis kann nicht gelesen werden.\n" ); - } - } - else if( file.isFile() ) { - boolean exec = file.canExecute(); - FileProgressInputStream in = null; - try { - in = openInputFile( file, 1 ); - long len = in.length(); - writeTarHeader( - out, - path + entryName, - false, - exec, - len, - millis ); - if( len > 0 ) { - long pos = 0; - int b = in.read(); - while( !this.canceled && (pos < len) && (b != -1) ) { - out.write( b ); - pos++; - b = in.read(); - } - long nBlocks = len / 512; - long fullLen = nBlocks * 512; - if( fullLen < len ) { - fullLen += 512; - } - while( !this.canceled && (pos < fullLen) ) { - out.write( 0 ); - pos++; - } - } - } - catch( IOException ex ) { - appendErrorToLog( ex ); - incErrorCount(); - } - finally { - EmuUtil.doClose( in ); + String rv = null; + if( this.curRootPath != null ) { + Path relPath = null; + if( dir != null ) { + try { + relPath = this.curRootPath.relativize( dir ); + } + catch( IllegalArgumentException ex ) {} + } + if( relPath == null ) { + relPath = this.curRootPath; + } + if( relPath.getNameCount() > 0 ) { + StringBuilder buf = new StringBuilder( 256 ); + for( Path p : relPath ) { + String s = p.toString(); + if( s != null ) { + if( !s.isEmpty() ) { + buf.append( s ); + buf.append( "/" ); } - } else { - appendToLog( " Ignoriert\n" ); } } - } else { - appendToLog( " Nicht gefunden\n" ); + rv = buf.toString(); } } + return rv; } @@ -192,40 +320,120 @@ private void writeASCII( byte[] buf, int pos, String text ) { if( text != null ) { int len = text.length(); - for( int i = 0; i < len; i++ ) + for( int i = 0; i < len; i++ ) { buf[ pos++ ] = (byte) (text.charAt( i ) & 0xFF ); + } } } private void writeTarHeader( - OutputStream out, - String entryName, - boolean isDir, - boolean exec, - long fileSize, - long fileTime ) throws IOException + Path path, + String entryName, + char entryType, + String linkedName, + long fileSize, + BasicFileAttributes attrs, + boolean defaultExec ) throws IOException { byte[] headerBuf = new byte[ 512 ]; Arrays.fill( headerBuf, (byte) 0 ); - // 0-100: Name - int pos = 0; + /* + * Benutzer, Gruppe und Posix-Dateiattribute ermitteln, + * sofern moeglich + */ + UserPrincipal owner = null; + GroupPrincipal group = null; + Set permissions = null; + PosixFileAttributes posixAttrs = null; + if( attrs instanceof PosixFileAttributes ) { + posixAttrs = (PosixFileAttributes) attrs; + } else { + if( this.posixEnabled ) { + try { + posixAttrs = (PosixFileAttributes) Files.readAttributes( + path, + PosixFileAttributes.class, + LinkOption.NOFOLLOW_LINKS ); + } + catch( UnsupportedOperationException ex ) { + this.posixEnabled = false; + } + catch( Exception ex ) {} + } + } + if( posixAttrs != null ) { + owner = posixAttrs.owner(); + group = posixAttrs.group(); + permissions = posixAttrs.permissions(); + } + if( owner == null ) { + try { + owner = Files.getOwner( path, LinkOption.NOFOLLOW_LINKS ); + } + catch( UnsupportedOperationException ex ) { + this.ownerEnabled = false; + } + catch( Exception ex ) {} + } + + // 0-99: Name int len = entryName.length(); if( len > 99 ) { throw new IOException( "Name des Eintrags zu lang" ); } - while( pos < len ) { - char ch = entryName.charAt( pos ); - if( ch > 0xFF ) { - throw new IOException( "Zeichen \'" + ch - + "\' im Namen eines Eintrags nicht erlaubt" ); - } - headerBuf[ pos++ ] = (byte) ch; + byte[] nameBytes = null; + try { + nameBytes = entryName.getBytes( "ISO-8859-1" ); } + catch( Exception ex ) {} + if( nameBytes == null ) { + nameBytes = entryName.getBytes(); + } + if( nameBytes.length != len ) { + throw new IOException( "Name enth\u00E4lt nicht erlaubte Zeichen" ); + } + System.arraycopy( nameBytes, 0, headerBuf, 0, nameBytes.length ); // 100-107: Mode - writeASCII( headerBuf, 100, (isDir || exec) ? "0000755" : "0000644" ); + String permissionsText = "0000644"; + if( permissions != null ) { + int bits = 0; + if( permissions.contains( PosixFilePermission.OWNER_READ ) ) { + bits |= 0x400; + } + if( permissions.contains( PosixFilePermission.OWNER_WRITE ) ) { + bits |= 0x200; + } + if( permissions.contains( PosixFilePermission.OWNER_EXECUTE ) ) { + bits |= 0x100; + } + if( permissions.contains( PosixFilePermission.GROUP_READ ) ) { + bits |= 0x040; + } + if( permissions.contains( PosixFilePermission.GROUP_WRITE ) ) { + bits |= 0x020; + } + if( permissions.contains( PosixFilePermission.GROUP_EXECUTE ) ) { + bits |= 0x010; + } + if( permissions.contains( PosixFilePermission.OTHERS_READ ) ) { + bits |= 0x004; + } + if( permissions.contains( PosixFilePermission.OTHERS_WRITE ) ) { + bits |= 0x002; + } + if( permissions.contains( PosixFilePermission.OTHERS_EXECUTE ) ) { + bits |= 0x001; + } + permissionsText = String.format( "%07X", bits ); + } else { + if( attrs.isDirectory() || defaultExec ) { + permissionsText = "0000755"; + } + } + writeASCII( headerBuf, 100, permissionsText ); // 108-115: Benutzer-ID writeASCII( headerBuf, 108, "0000000" ); @@ -234,38 +442,48 @@ private void writeTarHeader( writeASCII( headerBuf, 116, "0000000" ); // 124-135: Laenge - writeASCII( - headerBuf, - 124, - String.format( "%011o", new Long( fileSize ) ) ); + writeASCII( headerBuf, 124, String.format( "%011o", fileSize ) ); - // 136-147: Aenderungszeit - if( fileTime < 0 ) { - fileTime = System.currentTimeMillis(); - } - if( fileTime >= 0 ) { - writeASCII( - headerBuf, - 136, - String.format( "%011o", new Long( fileTime / 1000 ) ) ); - } else { - writeASCII( headerBuf, 136, "00000000000" ); + // 136-147: Aenderungszeitpunkt + long fileMillis = System.currentTimeMillis(); + FileTime fileTime = attrs.lastModifiedTime(); + if( fileTime != null ) { + fileMillis = fileTime.toMillis(); } + writeASCII( + headerBuf, + 136, + String.format( "%011o", fileMillis / 1000L ) ); // 148-155: Pruefsumme des Kopfblocks - pos = 148; + int pos = 148; while( pos < 156 ) { headerBuf[ pos++ ] = (byte) 0x20; // wird spaeter ersetzt } // 156: Typ - if( isDir ) { - headerBuf[ 156 ] = '5'; - } else { - headerBuf[ 156 ] = '0'; - } + headerBuf[ 156 ] = (byte) entryType; - // 157-256: Linkname (hier unbenutzt) + // 157-256: Verlinkter Name + if( linkedName != null ) { + len = linkedName.length(); + if( len > 99 ) { + throw new IOException( linkedName + ": Verlinkter Name zu lang" ); + } + nameBytes = null; + try { + nameBytes = linkedName.getBytes( "ISO-8859-1" ); + } + catch( Exception ex ) {} + if( nameBytes == null ) { + nameBytes = entryName.getBytes(); + } + if( nameBytes.length != len ) { + throw new IOException( linkedName + + ": Verlinkter Name enth\u00E4lt nicht erlaubte Zeichen" ); + } + System.arraycopy( nameBytes, 0, headerBuf, 157, nameBytes.length ); + } // 257-262: Magic writeASCII( headerBuf, 257, "ustar\u0020" ); @@ -273,11 +491,37 @@ private void writeTarHeader( // 263-264: Version headerBuf[ 263 ] = '\u0020'; - // 265-296: Benutzername - writeASCII( headerBuf, 265, "root" ); + // 265-296: Eigentuemername + String ownerName = "root"; + if( owner != null ) { + String s = owner.getName(); + if( s != null ) { + if( !s.isEmpty() ) { + int delimPos = s.lastIndexOf( '\\' ); + if( (delimPos >= 0) && ((delimPos + 1) < s.length()) ) { + ownerName = s.substring( delimPos + 1 ); + } else { + ownerName = s; + } + } + } + } + writeASCII( headerBuf, 265, ownerName ); // 297-328: Gruppenname - writeASCII( headerBuf, 297, "root" ); + String groupName = null; + if( group != null ) { + String s = group.getName(); + if( s != null ) { + if( !s.isEmpty() ) { + groupName = s; + } + } + } + if( groupName == null ) { + groupName = (ownerName.equals( "root" ) ? "root" : "users"); + } + writeASCII( headerBuf, 297, groupName ); // 329-512: restliche Felder hier unbenutzt @@ -293,7 +537,7 @@ private void writeTarHeader( headerBuf[ 154 ] = (byte) 0; // Kopfblock schreiben - out.write( headerBuf ); + this.out.write( headerBuf ); } } diff --git a/src/jkcemu/filebrowser/TarUnpacker.java b/src/jkcemu/filebrowser/TarUnpacker.java index fdc799e..67a771b 100644 --- a/src/jkcemu/filebrowser/TarUnpacker.java +++ b/src/jkcemu/filebrowser/TarUnpacker.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -11,6 +11,9 @@ import java.awt.*; import java.io.*; import java.lang.*; +import java.nio.file.*; +import java.nio.file.attribute.*; +import java.util.Set; import java.util.zip.GZIPInputStream; import jkcemu.base.*; @@ -20,6 +23,7 @@ public class TarUnpacker extends AbstractThreadDlg private File srcFile; private File outDir; private boolean compression; + private boolean posixEnabled; public static void unpackFile( @@ -55,93 +59,132 @@ protected void doProgress() while( !this.canceled && (entry != null) ) { String entryName = entry.getName(); if( entryName != null ) { + appendToLog( entryName + "\n" ); String errMsg = entry.getErrorMsg(); if( errMsg != null ) { - appendToLog( "Fehler bei Eintrag " + entryName + ":\n" + errMsg ); + appendErrorToLog( errMsg ); } - File outFile = null; - int len = entryName.length(); - int pos = 0; - while( pos < len ) { - String elem = null; - int delim = entryName.indexOf( '/', pos ); - if( delim >= pos ) { - elem = entryName.substring( pos, delim ); - pos = delim + 1; - } else { - elem = entryName.substring( pos ); - pos = len; - } - if( elem != null ) { - if( !elem.isEmpty() ) { - if( outFile != null ) { - outFile = new File( outFile, elem ); - } else { - outFile = new File( this.outDir, elem ); + File outFile = null; + String msg = " ignoriert"; + try { + int len = entryName.length(); + int pos = 0; + while( pos < len ) { + String elem = null; + int delim = entryName.indexOf( '/', pos ); + if( delim >= pos ) { + elem = entryName.substring( pos, delim ); + pos = delim + 1; + } else { + elem = entryName.substring( pos ); + pos = len; + } + if( elem != null ) { + if( !elem.isEmpty() ) { + if( outFile != null ) { + outFile = new File( outFile, elem ); + } else { + outFile = new File( this.outDir, elem ); + } } } } - } - if( outFile != null ) { - appendToLog( outFile.getPath() + "\n" ); - long millis = entry.getTime(); - if( entry.isDirectory() || entryName.endsWith( "/" ) ) { - outFile.mkdirs(); - if( !outFile.exists() ) { - throw new IOException( + if( outFile != null ) { + if( entry.isDirectory() ) { + outFile.mkdirs(); + if( !outFile.exists() ) { + throw new IOException( "Verzeichnis kann nicht angelegt werden" ); - } - } else { - File parent = outFile.getParentFile(); - if( parent != null ) { - parent.mkdirs(); - } - boolean failed = false; - OutputStream out = null; - try { - out = new BufferedOutputStream( + } + setAttributes( outFile, null, entry ); + msg = null; + } else if( entry.isSymbolicLink() ) { + String linkTarget = entry.getLinkTarget(); + if( linkTarget != null ) { + File parent = outFile.getParentFile(); + if( parent != null ) { + parent.mkdirs(); + } + boolean done = false; + Path path = outFile.toPath(); + if( this.posixEnabled ) { + try { + Files.createSymbolicLink( + path, + Paths.get( linkTarget ), + PosixFilePermissions.asFileAttribute( + entry.getPosixFilePermissions() ) ); + done = true; + } + catch( UnsupportedOperationException ex ) { + this.posixEnabled = false; + } + catch( Exception ex ) {} + } + if( !done ) { + Files.createSymbolicLink( path, Paths.get( linkTarget ) ); + } + try { + Files.setLastModifiedTime( + path, + FileTime.fromMillis( entry.getTime() ) ); + } + catch( Exception ex ) {} + msg = null; + } + } else if( entry.isRegularFile() ) { + File parent = outFile.getParentFile(); + if( parent != null ) { + parent.mkdirs(); + } + OutputStream out = null; + try { + out = new BufferedOutputStream( new FileOutputStream( outFile ) ); - long size = entry.getSize(); - if( size > 0 ) { - pos = 1; - int b = in.read(); - while( !this.canceled && (pos < size) && (b != -1) ) { - out.write( b ); - b = in.read(); - pos++; - } - if( (pos % 512) != 0 ) { - in.skip( ((pos + 512) & ~0x1FF) - pos ); + long size = entry.getSize(); + if( size > 0 ) { + pos = 1; + int b = in.read(); + while( !this.canceled && (pos < size) && (b != -1) ) { + out.write( b ); + b = in.read(); + pos++; + } + if( (pos % 512) != 0 ) { + in.skip( ((pos + 512) & ~0x1FF) - pos ); + } } + out.close(); + out = null; } - out.close(); - out = null; - } - catch( IOException ex ) { - appendErrorToLog( ex ); - incErrorCount(); - failed = true; - } - finally { - EmuUtil.doClose( out ); - } - if( failed ) { - outFile.delete(); - } else { - if( millis > 0 ) { - outFile.setLastModified( millis ); + finally { + EmuUtil.doClose( out ); } - int mode = entry.getMode(); - outFile.setExecutable( (mode & 0x001) != 0, false ); - outFile.setExecutable( (mode & 0x040) != 0, true ); - outFile.setReadable( (mode & 0x004) != 0, false ); - outFile.setReadable( (mode & 0x100) != 0, true ); - outFile.setWritable( (mode & 0x002) != 0, false ); - outFile.setWritable( (mode & 0x080) != 0, true ); + setAttributes( outFile, null, entry ); + msg = null; } } } + catch( Exception ex ) { + msg = "Fehler"; + String exMsg = ex.getMessage(); + if( exMsg != null ) { + exMsg = exMsg.trim(); + if( !exMsg.isEmpty() ) { + msg = exMsg; + } + } + incErrorCount(); + } + if( msg != null ) { + msg += "\n"; + appendToLog( msg ); + disableAutoClose(); + if( outFile != null ) { + outFile.delete(); + } + } } entry = TarEntry.readEntryHeader( in ); } @@ -152,19 +195,19 @@ protected void doProgress() buf.append( this.srcFile.getPath() ); String errMsg = ex.getMessage(); if( errMsg != null ) { - if( errMsg.length() > 0 ) { + if( !errMsg.isEmpty() ) { buf.append( ": " ); buf.append( errMsg ); } } buf.append( (char) '\n' ); - appendToLog( buf.toString() ); + appendErrorToLog( buf.toString() ); incErrorCount(); } finally { EmuUtil.doClose( in ); } - fireDirectoryChanged( + FileBrowserFrm.fireFileChanged( dirExists ? this.outDir : this.outDir.getParentFile() ); } @@ -178,9 +221,74 @@ private TarUnpacker( boolean compression ) { super( owner, "JKCEMU tar unpacker", true ); - this.srcFile = srcFile; - this.outDir = outDir; - this.compression = compression; + this.srcFile = srcFile; + this.outDir = outDir; + this.compression = compression; + this.posixEnabled = true; + } + + + private void setAttributes( File file, Path path, TarEntry entry ) + { + boolean done = false; + if( path == null ) { + try { + path = file.toPath(); + } + catch( InvalidPathException ex ) {} + } + + // Zeitpunkt der letzen Aenderung setzen + long millis = entry.getTime(); + try { + if( path != null ) { + Files.setLastModifiedTime( path, FileTime.fromMillis( millis ) ); + } else { + if( millis >= 0 ) { + file.setLastModified( millis ); + } + } + } + catch( Exception ex ) {} + + // Dateiberechtigungen setzen + Set perms = entry.getPosixFilePermissions(); + if( !this.posixEnabled ) { + try { + Files.setPosixFilePermissions( file.toPath(), perms ); + done = true; + } + catch( IOException ex ) {} + catch( Exception ex ) { + this.posixEnabled = false; + } + } + if( !done ) { + try { + file.setExecutable( + perms.contains( PosixFilePermission.OTHERS_EXECUTE ) + || perms.contains( PosixFilePermission.GROUP_EXECUTE ), + false ); + file.setExecutable( + perms.contains( PosixFilePermission.OWNER_EXECUTE ), + true ); + file.setWritable( + perms.contains( PosixFilePermission.OTHERS_WRITE ) + || perms.contains( PosixFilePermission.GROUP_WRITE ), + false ); + file.setWritable( + perms.contains( PosixFilePermission.OWNER_WRITE ), + true ); + file.setReadable( + perms.contains( PosixFilePermission.OTHERS_READ ) + || perms.contains( PosixFilePermission.GROUP_READ ), + false ); + file.setReadable( + perms.contains( PosixFilePermission.OWNER_READ ), + true ); + } + catch( Exception ex ) {} + } } } diff --git a/src/jkcemu/filebrowser/ZipPacker.java b/src/jkcemu/filebrowser/ZipPacker.java index 74e7c51..03111ee 100644 --- a/src/jkcemu/filebrowser/ZipPacker.java +++ b/src/jkcemu/filebrowser/ZipPacker.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -11,25 +11,175 @@ import java.awt.*; import java.io.*; import java.lang.*; +import java.nio.file.*; +import java.nio.file.attribute.*; import java.util.Collection; import java.util.zip.*; import jkcemu.base.*; public class ZipPacker extends AbstractThreadDlg + implements FileVisitor { - private Collection srcFiles; + private Collection srcPaths; + private String curEntryDir; + private Path curRootPath; + private Path outPath; private File outFile; + private ZipOutputStream out; public static void packFiles( Window owner, - Collection srcFiles, + Collection srcPaths, File outFile ) { - Dialog dlg = new ZipPacker( owner, srcFiles, outFile ); - dlg.setTitle( "ZIP-Datei packen" ); - dlg.setVisible( true ); + try { + Dialog dlg = new ZipPacker( owner, srcPaths, outFile ); + dlg.setTitle( "ZIP-Datei packen" ); + dlg.setVisible( true ); + } + catch( InvalidPathException ex ) {} + } + + + /* --- FileVisitor --- */ + + @Override + public FileVisitResult postVisitDirectory( Path dir, IOException ex ) + { + return this.canceled ? FileVisitResult.TERMINATE + : FileVisitResult.CONTINUE; + } + + + @Override + public FileVisitResult preVisitDirectory( + Path dir, + BasicFileAttributes attrs ) + { + FileVisitResult rv = FileVisitResult.SKIP_SUBTREE; + if( (this.out == null) || this.canceled ) { + rv = FileVisitResult.TERMINATE; + } else { + if( (dir != null) && (attrs != null) ) { + if( attrs.isDirectory() ) { + this.curEntryDir = getEntryDir( dir ); + if( this.curEntryDir != null ) { + if( !this.curEntryDir.isEmpty() ) { + appendToLog( dir.toString() + "\n" ); + try { + ZipEntry entry = new ZipEntry( this.curEntryDir ); + entry.setMethod( ZipEntry.STORED ); + entry.setCrc( 0 ); + entry.setCompressedSize( 0 ); + entry.setSize( 0 ); + FileTime lastModified = attrs.lastModifiedTime(); + if( lastModified != null ) { + entry.setTime( lastModified.toMillis() ); + } + this.out.putNextEntry( entry ); + this.out.closeEntry(); + rv = FileVisitResult.CONTINUE; + } + catch( IOException ex ) { + appendErrorToLog( ex ); + } + } + } + } + } + } + return rv; + } + + + @Override + public FileVisitResult visitFile( Path file, BasicFileAttributes attrs ) + { + FileVisitResult rv = FileVisitResult.SKIP_SIBLINGS; + if( (this.out == null) || this.canceled ) { + rv = FileVisitResult.TERMINATE; + } else { + if( (file != null) && (attrs != null) ) { + if( this.curEntryDir == null ) { + this.curEntryDir = getEntryDir( file.getParent() ); + } + if( this.curEntryDir != null ) { + int nameCnt = file.getNameCount(); + if( nameCnt > 0 ) { + appendToLog( file.toString() + "\n" ); + if( attrs.isRegularFile() ) { + FileProgressInputStream in = null; + try { + in = openInputFile( file.toFile(), 2 ); + + // CRC32-Pruefsumme und Dateigroesse ermitteln + CRC32 crc32 = new CRC32(); + long fSize = 0; + int b = in.read(); + while( !this.canceled && (b != -1) ) { + fSize++; + crc32.update( b ); + b = in.read(); + } + in.seek( 0 ); + + // ZIP-Eintrag anlegen + ZipEntry entry = new ZipEntry( this.curEntryDir + + file.getName( nameCnt - 1 ).toString() ); + entry.setCrc( crc32.getValue() ); + entry.setMethod( ZipEntry.DEFLATED ); + entry.setSize( fSize ); + FileTime lastModified = attrs.lastModifiedTime(); + if( lastModified != null ) { + entry.setTime( lastModified.toMillis() ); + } + this.out.putNextEntry( entry ); + + // Datei in Eintrag schreiben + b = in.read(); + while( !this.canceled && (b != -1) ) { + this.out.write( b ); + b = in.read(); + } + this.out.closeEntry(); + } + catch( IOException ex ) { + appendErrorToLog( ex ); + incErrorCount(); + } + catch( UnsupportedOperationException ex ) { + appendIgnoredToLog(); + } + finally { + EmuUtil.doClose( in ); + } + } else if( attrs.isSymbolicLink() ) { + appendToLog( " Symbolischer Link ignoriert\n" ); + disableAutoClose(); + } else { + appendIgnoredToLog(); + } + rv = FileVisitResult.CONTINUE; + } + } + } + } + return rv; + } + + + @Override + public FileVisitResult visitFileFailed( Path file, IOException ex ) + { + if( file != null ) { + appendToLog( file.toString() ); + appendErrorToLog( ex ); + incErrorCount(); + } + return this.canceled ? FileVisitResult.TERMINATE + : FileVisitResult.CONTINUE; } @@ -38,20 +188,20 @@ public static void packFiles( @Override protected void doProgress() { - boolean failed = false; - InputStream in = null; - ZipOutputStream out = null; + boolean failed = false; + InputStream in = null; try { - out = new ZipOutputStream( + this.out = new ZipOutputStream( new BufferedOutputStream( new FileOutputStream( this.outFile ) ) ); - fireDirectoryChanged( outFile.getParentFile() ); - for( File file : this.srcFiles ) { - packFile( outFile, out, file, "" ); + FileBrowserFrm.fireFileChanged( this.outFile.getParentFile() ); + for( Path path : this.srcPaths ) { + this.curRootPath = path.getParent(); + Files.walkFileTree( path, this ); } - out.finish(); - out.close(); - out = null; + this.out.finish(); + this.out.close(); + this.out = null; appendToLog( "\nFertig\n" ); } catch( InterruptedIOException ex ) {} @@ -72,114 +222,59 @@ protected void doProgress() failed = true; } finally { - EmuUtil.doClose( out ); + EmuUtil.doClose( this.out ); } if( this.canceled || failed ) { - outFile.delete(); + this.outFile.delete(); } - fireDirectoryChanged( outFile.getParentFile() ); + FileBrowserFrm.fireFileChanged( this.outFile.getParentFile() ); } - /* --- private Konstruktoren und Methoden --- */ + /* --- privater Konstruktor --- */ private ZipPacker( Window owner, - Collection srcFiles, - File outFile ) + Collection srcPaths, + File outFile ) throws InvalidPathException { super( owner, "JKCEMU zip packer", true ); - this.srcFiles = srcFiles; + this.srcPaths = srcPaths; + this.outPath = outFile.toPath(); this.outFile = outFile; + this.out = null; } - private void packFile( - File outFile, - ZipOutputStream out, - File file, - String path ) throws IOException + private String getEntryDir( Path dir ) { - if( this.canceled ) { - throw new InterruptedIOException(); - } - if( file != null ) { - appendToLog( file.getPath() + "\n" ); - String entryName = file.getName(); - if( !file.equals( outFile) && (entryName != null) ) { - if( entryName.length() > 0 ) { - long millis = file.lastModified(); - if( file.isDirectory() ) { - String subPath = path + entryName + "/"; - ZipEntry entry = new ZipEntry( subPath ); - entry.setMethod( ZipEntry.STORED ); - entry.setCrc( 0 ); - entry.setCompressedSize( 0 ); - entry.setSize( 0 ); - if( millis > 0 ) { - entry.setTime( millis ); - } - out.putNextEntry( entry ); - out.closeEntry(); - File[] subFiles = file.listFiles(); - if( subFiles != null ) { - for( int i = 0; i < subFiles.length; i++ ) { - packFile( outFile, out, subFiles[ i ], subPath ); - } - } else { - appendToLog( - "Fehler: Verzeichnis kann nicht gelesen werden.\n" ); - } - } - else if( file.isFile() ) { - FileProgressInputStream in = null; - try { - in = openInputFile( file, 2 ); - - // CRC32-Pruefsumme und Dateigroesse ermitteln - CRC32 crc32 = new CRC32(); - long fSize = 0; - int b = in.read(); - while( !this.canceled && (b != -1) ) { - fSize++; - crc32.update( b ); - b = in.read(); - } - in.seek( 0 ); - - // ZIP-Eintrag anlegen - ZipEntry entry = new ZipEntry( path + entryName ); - entry.setCrc( crc32.getValue() ); - entry.setMethod( ZipEntry.DEFLATED ); - entry.setSize( fSize ); - if( millis > 0 ) { - entry.setTime( millis ); - } - out.putNextEntry( entry ); - - // Datei in Eintrag schreiben - b = in.read(); - while( !this.canceled && (b != -1) ) { - out.write( b ); - b = in.read(); - } - out.closeEntry(); - } - catch( IOException ex ) { - appendErrorToLog( ex ); - incErrorCount(); - } - finally { - EmuUtil.doClose( in ); + String rv = null; + if( this.curRootPath != null ) { + Path relPath = null; + if( dir != null ) { + try { + relPath = this.curRootPath.relativize( dir ); + } + catch( IllegalArgumentException ex ) {} + } + if( relPath == null ) { + relPath = this.curRootPath; + } + if( relPath.getNameCount() > 0 ) { + StringBuilder buf = new StringBuilder( 256 ); + for( Path p : relPath ) { + String s = p.toString(); + if( s != null ) { + if( !s.isEmpty() ) { + buf.append( s ); + buf.append( "/" ); } - } else { - appendToLog( " Ignoriert\n" ); } } - } else { - appendToLog( " Nicht gefunden\n" ); + rv = buf.toString(); } } + return rv; } } diff --git a/src/jkcemu/filebrowser/ZipUnpacker.java b/src/jkcemu/filebrowser/ZipUnpacker.java index 59ed56f..852d30c 100644 --- a/src/jkcemu/filebrowser/ZipUnpacker.java +++ b/src/jkcemu/filebrowser/ZipUnpacker.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -49,7 +49,7 @@ protected void doProgress() int len = entryName.length(); int pos = 0; while( pos < len ) { - String elem = null; + String elem = null; int delim1 = entryName.indexOf( '/', pos ); int delim2 = entryName.indexOf( '\\', pos ); if( (delim1 < 0) || ((delim2 >= 0) && (delim2 < delim1)) ) { @@ -162,7 +162,7 @@ protected void doProgress() EmuUtil.doClose( in ); } } - fireDirectoryChanged( + FileBrowserFrm.fireFileChanged( dirExists ? this.outDir : this.outDir.getParentFile() ); } diff --git a/src/jkcemu/image/AbstractImageFrm.java b/src/jkcemu/image/AbstractImageFrm.java index 4c569fa..d93b2f4 100644 --- a/src/jkcemu/image/AbstractImageFrm.java +++ b/src/jkcemu/image/AbstractImageFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -29,8 +29,8 @@ public class AbstractImageFrm extends BasicFrm implements ComponentListener protected ImgFld imgFld; protected JScrollPane scrollPane; - private JComboBox comboScale; - private boolean scaleEnabled; + private JComboBox comboScale; + private boolean scaleEnabled; protected AbstractImageFrm() @@ -70,12 +70,12 @@ protected AbstractImageFrm() } - protected JComboBox createScaleComboBox( + protected JComboBox createScaleComboBox( int defaultScale, int... scaleFactors ) { String defaultItem = null; - this.comboScale = new JComboBox(); + this.comboScale = new JComboBox<>(); this.comboScale.setEditable( true ); for( int f : scaleFactors ) { String s = String.format( "%d %%", f ); diff --git a/src/jkcemu/image/AnimatedGIFWriter.java b/src/jkcemu/image/AnimatedGIFWriter.java index e8eec5e..49ff086 100644 --- a/src/jkcemu/image/AnimatedGIFWriter.java +++ b/src/jkcemu/image/AnimatedGIFWriter.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2011 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -40,7 +40,7 @@ public FrameData( /* - * Farbreduktion von 256 auf 6 Farben: + * Farbreduktion von 256 auf 6 Werte pro Primaerfarbe: * Der Farbwert (0-255) wird durch 43 dividiert (ergibt 0-5), * und anschliessend ueber die Tabelle wieder in den Wertebereich * 0-255 gemappt. @@ -103,7 +103,7 @@ public AnimatedGIFWriter( * sondern nur zwischengespeichert, * da erst beim naechsten Frame die Wartezeit dieses Frames * ermittelt werden kann. - * Des weiteren wird dieses Frame mit dem vorherigen verglichen. + * Des Weiteren wird dieses Frame mit dem vorherigen verglichen. * Wenn beide gleich sind, wird von diesem Frame nur die Anzeigezeit * auf das vorherige Frame addiert. */ @@ -223,7 +223,7 @@ private FrameData createFrameData( BufferedImage image ) * Farb- sowie eine Pixelindextabelle aufgebaut. * Bei der 257. Farbe wird das ganze noch einmal gemacht, * allerdings mit Farbreduktion auf einen 6:6:6-Wuerfel, - * sodass max. 6^3=216 Farben uebrig bleiben koennen. + * so dass max. 6^3=216 Farben uebrig bleiben koennen. * Wenn bei einem Frame die Farbreduktion notwendig ist, * wird davon ausgegangen, dass das auch bei den nachfolgenden * Frames so sein wird und deshalb aus Gruenden der Performance @@ -244,7 +244,7 @@ private FrameData createFrameData( BufferedImage image ) rgb = image.getRGB( x, y ); if( this.forceColorReduction ) { /* - * Jeder Farbe wird von 256 auf 6 moegliche Werte + * Jede Primaerfarbe wird von 256 auf 6 moegliche Werte * reduziert, indem durch 43 geteilt (ergibt 0-5) * und anschliessend ueber eine Tabelle wieder * auf den Wertebereich 0-255 gemappt wird. diff --git a/src/jkcemu/image/ImageFrm.java b/src/jkcemu/image/ImageFrm.java index 9fc2486..be6b530 100644 --- a/src/jkcemu/image/ImageFrm.java +++ b/src/jkcemu/image/ImageFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2012 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -137,6 +137,8 @@ public void flavorsChanged( FlavorEvent e ) } + /* --- MouseMotionListener --- */ + @Override public void mouseDragged( MouseEvent e ) { @@ -203,12 +205,12 @@ public boolean applySettings( Properties props, boolean resizable ) this.mnuAutoResize.setSelected( EmuUtil.parseBoolean( - props.getProperty( prefix + ".auto_resize" ), + props.getProperty( prefix + "auto_resize" ), false ) ); AbstractButton btn = this.mnuBgSystem; Color color = SystemColor.window; - String text = props.getProperty( prefix + ".background" ); + String text = props.getProperty( prefix + "background" ); if( text != null ) { if( text.trim().toLowerCase().equals( "black" ) ) { color = Color.black; @@ -329,7 +331,7 @@ public void putSettingsTo( Properties props ) String prefix = getSettingsPrefix(); props.setProperty( - prefix + ".auto_resize", + prefix + "auto_resize", String.valueOf( this.mnuAutoResize.isSelected() ) ); String colorText = "system"; @@ -339,7 +341,7 @@ public void putSettingsTo( Properties props ) else if( this.mnuBgWhite.isSelected() ) { colorText = "white"; } - props.setProperty( prefix + ".background", colorText ); + props.setProperty( prefix + "background", colorText ); } } @@ -359,7 +361,7 @@ private void doOpen() File file = EmuUtil.showFileOpenDlg( this, "Bilddatei \u00F6ffnen", - Main.getLastPathFile( "image" ), + Main.getLastDirFile( "image" ), ImgLoader.createFileFilter() ); if( file != null ) { if( showImageFile( file ) ) { @@ -584,6 +586,7 @@ private ImageFrm() if( this.clipboard != null ) { this.clipboard.addFlavorListener( this ); } + updPasteBtn(); } diff --git a/src/jkcemu/image/ImgSaver.java b/src/jkcemu/image/ImgSaver.java index 8b81e16..a88180e 100644 --- a/src/jkcemu/image/ImgSaver.java +++ b/src/jkcemu/image/ImgSaver.java @@ -1,5 +1,5 @@ /* - * (c) 2013 Jens Mueller + * (c) 2013-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -33,12 +33,11 @@ public static File saveImageAs( BufferedImage image, String filename ) { - File preSelection = null; + File preSelection = Main.getLastDirFile( "image" ); if( filename != null ) { if( !filename.isEmpty() ) { - File dirFile = Main.getLastPathFile( "image" ); - if( dirFile != null ) { - preSelection = new File( dirFile, filename ); + if( preSelection != null ) { + preSelection = new File( preSelection, filename ); } else { preSelection = new File( filename ); } @@ -98,7 +97,7 @@ public static File saveImageAs( // Fehlermeldung erzeugen if( !formatOK ) { - Set fmtList = new TreeSet(); + Set fmtList = new TreeSet<>(); // Grafikformate hinzufuegen if( formats != null ) { @@ -260,7 +259,7 @@ private static void saveImageA5105( tmpImg.flush(); // temporaeres Bild speichern - Map rgb2Idx = new HashMap(); + Map rgb2Idx = new HashMap<>(); out.write( 0xFD ); // Kennung Videospeicher out.write( 0x00 ); // Anfangsadresse Grafikseite 0 out.write( 0x40 ); diff --git a/src/jkcemu/image/VideoCaptureFrm.java b/src/jkcemu/image/VideoCaptureFrm.java index 5ddaa36..d2f18d8 100644 --- a/src/jkcemu/image/VideoCaptureFrm.java +++ b/src/jkcemu/image/VideoCaptureFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2012 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -27,47 +27,47 @@ public class VideoCaptureFrm extends BasicFrm implements Runnable private static VideoCaptureFrm instance = null; - private ScreenFrm screenFrm; - private volatile boolean pause; - private volatile boolean focusedWindowOnly; - private volatile boolean waitForReset; - private volatile boolean running; - private volatile long recordedMillis; - private int waitForWindowMillis; - private volatile int frameMillis; - private volatile int captureWidth; - private volatile int captureHeight; - private volatile boolean capturing; - private volatile Window captureWindow; - private boolean fileCheckEnabled; - private Robot robot; - private Thread thread; - private VideoPlayFrm videoPlayFrm; - private javax.swing.Timer statusTimer; - private JRadioButton btnCaptureEmuSysScreen; - private JRadioButton btnCaptureScreenFrm; - private JRadioButton btnCaptureOtherWindow; - private JLabel labelWinSelectTime; - private JLabel labelWinSelectUnit; - private JSpinner spinnerWinSelectSec; - private JLabel labelFramesPerSec; - private JComboBox comboFramesPerSec; - private JLabel labelColorReduction; - private JRadioButton btnColorReductionSmooth; - private JRadioButton btnColorReductionHard; - private JLabel labelPlayCnt; - private JRadioButton btnPlayOnce; - private JRadioButton btnPlayInfinite; - private JCheckBox btnStartAfterReset; - private JCheckBox btnFocusedWindowOnly; - private FileNameFld fldFile; - private JLabel labelStatus; - private JButton btnFileSelect; - private JButton btnRecord; - private JButton btnPause; - private JButton btnStop; - private JButton btnPlay; - private JButton btnClose; + private ScreenFrm screenFrm; + private volatile boolean pause; + private volatile boolean focusedWindowOnly; + private volatile boolean waitForReset; + private volatile boolean running; + private volatile long recordedMillis; + private int waitForWindowMillis; + private volatile int frameMillis; + private volatile int captureWidth; + private volatile int captureHeight; + private volatile boolean capturing; + private volatile Window captureWindow; + private boolean fileCheckEnabled; + private Robot robot; + private Thread thread; + private VideoPlayFrm videoPlayFrm; + private javax.swing.Timer statusTimer; + private JRadioButton btnCaptureEmuSysScreen; + private JRadioButton btnCaptureScreenFrm; + private JRadioButton btnCaptureOtherWindow; + private JLabel labelWinSelectTime; + private JLabel labelWinSelectUnit; + private JSpinner spinnerWinSelectSec; + private JLabel labelFramesPerSec; + private JComboBox comboFramesPerSec; + private JLabel labelColorReduction; + private JRadioButton btnColorReductionSmooth; + private JRadioButton btnColorReductionHard; + private JLabel labelPlayCnt; + private JRadioButton btnPlayOnce; + private JRadioButton btnPlayInfinite; + private JCheckBox btnStartAfterReset; + private JCheckBox btnFocusedWindowOnly; + private FileNameFld fldFile; + private JLabel labelStatus; + private JButton btnFileSelect; + private JButton btnRecord; + private JButton btnPause; + private JButton btnStop; + private JButton btnPlay; + private JButton btnClose; public static void open( ScreenFrm screenFrm ) @@ -408,7 +408,7 @@ public void actionPerformed( ActionEvent e ) panelOpt.add( this.labelFramesPerSec, gbcOpt ); Integer defaultFramesPerSec = new Integer( 10 ); - this.comboFramesPerSec = new JComboBox(); + this.comboFramesPerSec = new JComboBox<>(); this.comboFramesPerSec.setEditable( false ); this.comboFramesPerSec.addItem( new Integer( 5 ) ); this.comboFramesPerSec.addItem( new Integer( 7 ) ); @@ -821,7 +821,10 @@ else if( this.btnCaptureOtherWindow.isSelected() ) { this.btnPause.setEnabled( true ); } this.running = true; - this.thread = new Thread( this, "JKCEMU screen video recorder" ); + this.thread = new Thread( + Main.getThreadGroup(), + this, + "JKCEMU screen video recorder" ); this.thread.start(); } this.statusTimer.start(); diff --git a/src/jkcemu/joystick/JoystickFrm.java b/src/jkcemu/joystick/JoystickFrm.java index 54c94b1..38664b5 100644 --- a/src/jkcemu/joystick/JoystickFrm.java +++ b/src/jkcemu/joystick/JoystickFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2013 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -158,6 +158,24 @@ public void mouseReleased( MouseEvent e ) } + @Override + public void windowActivated( WindowEvent e ) + { + if( e.getWindow() == this ) { + Main.setWindowActivated( Main.WINDOW_MASK_JOYSTICK ); + } + } + + + @Override + public void windowDeactivated( WindowEvent e ) + { + if( e.getWindow() == this ) { + Main.setWindowDeactivated( Main.WINDOW_MASK_JOYSTICK ); + } + } + + /* --- Konstruktor --- */ private JoystickFrm( EmuThread emuThread ) diff --git a/src/jkcemu/joystick/JoystickThread.java b/src/jkcemu/joystick/JoystickThread.java index 3f2ebd1..10abf92 100644 --- a/src/jkcemu/joystick/JoystickThread.java +++ b/src/jkcemu/joystick/JoystickThread.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2011 Jens Mueller + * (c) 2010-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -11,6 +11,7 @@ import java.awt.*; import java.io.IOException; import java.lang.*; +import jkcemu.Main; import jkcemu.base.*; @@ -36,7 +37,9 @@ public JoystickThread( int joyNum, boolean interactive ) { - super( String.format( "JKCEMU joystick %d listener", joyNum ) ); + super( + Main.getThreadGroup(), + String.format( "JKCEMU joystick %d listener", joyNum ) ); this.emuThread = emuThread; this.joyNum = joyNum; this.interactive = interactive; @@ -73,28 +76,36 @@ public void run() js = DeviceIO.openJoystick( this.joyNum ); } if( js != null ) { - while( this.running && js.waitForEvent() ) { - int m = 0; - float x = js.getXAxis(); - float y = js.getYAxis(); - int b = js.getPressedButtons(); - if( x < -0.5F ) { - m |= LEFT_MASK; - } else if( x > 0.5F ) { - m |= RIGHT_MASK; + while( this.running ) { + if( Main.isWindowActive() && js.waitForEvent() ) { + int m = 0; + float x = js.getXAxis(); + float y = js.getYAxis(); + int b = js.getPressedButtons(); + if( x < -0.5F ) { + m |= LEFT_MASK; + } else if( x > 0.5F ) { + m |= RIGHT_MASK; + } + if( y < -0.5F ) { + m |= UP_MASK; + } else if( y > 0.5F ) { + m |= DOWN_MASK; + } + if( (b & 0x5555) != 0 ) { // Bit 0, 2, 4, ... + m |= BUTTON1_MASK; + } + if( (b & 0xAAAA) != 0 ) { // Bit 1, 3, 5, ... + m |= BUTTON2_MASK; + } + this.emuThread.setJoystickAction( this.joyNum, m ); + } else { + this.emuThread.setJoystickAction( this.joyNum, 0 ); + try { + Thread.sleep( 50 ); + } + catch( InterruptedException ex ) {} } - if( y < -0.5F ) { - m |= UP_MASK; - } else if( y > 0.5F ) { - m |= DOWN_MASK; - } - if( (b & 0x5555) != 0 ) { // Bit 0, 2, 4, ... - m |= BUTTON1_MASK; - } - if( (b & 0xAAAA) != 0 ) { // Bit 1, 3, 5, ... - m |= BUTTON2_MASK; - } - this.emuThread.setJoystickAction( this.joyNum, m ); } } else { if( this.interactive ) { diff --git a/src/jkcemu/net/DhcpProcess.java b/src/jkcemu/net/DhcpProcess.java new file mode 100644 index 0000000..3d10be3 --- /dev/null +++ b/src/jkcemu/net/DhcpProcess.java @@ -0,0 +1,374 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Simulation eines DHCP-Konfigurationsprozesses auf Server-Seite + */ + +package jkcemu.net; + +import java.io.*; +import java.lang.*; +import java.net.*; +import jkcemu.Main; +import jkcemu.base.EmuUtil; + + +public class DhcpProcess +{ + public static final int SERVER_PORT = 67; + public static final int CLIENT_PORT = 68; + + private static final int MT_DISCOVER = 1; + private static final int MT_OFFER = 2; + private static final int MT_REQUEST = 3; + private static final int MT_ACK = 5; + private static final int TTL_DEFAULT = 64; + private static final int TIMEOUT_MILLIS = 1000; + + private static final String SERVER_NAME = "jkcemu-dhcp-server"; + + private byte[] clientIpAddr; + private byte[] serverIpAddr; + private byte[] answerBytes; + private long begMillis; + private boolean answerValid; + private boolean requestReceived; + private boolean finished; + + + public static DhcpProcess checkDiscover( + W5100 w5100, + DatagramPacket packet ) + { + DhcpProcess dhcpProcess = null; + byte[] data = packet.getData(); + if( (data != null) && (packet.getPort() == SERVER_PORT) ) { + if( data.length > 240 ) { + if( data[ 0 ] == (byte) 1 ) { // message op code + int messageType = -1; + int parmReqListBeg = -1; + int parmReqListLen = -1; + if( (data[ 236 ] == (byte) 0x63) // options magic code + && (data[ 237 ] == (byte) 0x82) + && (data[ 238 ] == (byte) 0x53) + && (data[ 239 ] == (byte) 0x63) ) + { + int idx = 240; + while( idx < data.length ) { + int option = (int) data[ idx++ ] & 0xFF; + if( option == 0xFF ) { + break; + } + if( (option != 0) && (idx < data.length) ) { + int len = (int) data[ idx++ ] & 0xFF; + switch( option ) { + case 0x35: // message type + if( idx < data.length ) { + messageType = (int) data[ idx ] & 0xFF; + } + break; + case 0x37: + parmReqListBeg = idx; + parmReqListLen = len; + break; + } + idx += len; + } + } + } + if( messageType == MT_DISCOVER ) { + NetConfig netConfig = w5100.getNetConfig(); + if( netConfig != null ) { + byte[] clientIpAddr = netConfig.getIpAddr(); + byte[] subnetMask = netConfig.getSubnetMask(); + if( (clientIpAddr != null) && (subnetMask != null) ) { + if( (clientIpAddr.length == 4) && (subnetMask.length == 4) ) { + + // IP-Adresse des simulierten DHCP-Servers ermitteln + byte[] serverIpAddr = new byte[ 4 ]; + serverIpAddr[ 0 ] = clientIpAddr[ 0 ]; + serverIpAddr[ 1 ] = clientIpAddr[ 1 ]; + serverIpAddr[ 2 ] = clientIpAddr[ 2 ]; + if( clientIpAddr[ 3 ] == (byte) 99 ) { + serverIpAddr[ 3 ] = (byte) 111; + } else { + serverIpAddr[ 3 ] = (byte) 99; + } + + // DHCP_OFFER erzeugen + try { + ByteArrayOutputStream buf = new ByteArrayOutputStream( + 0x0400 ); + + // message op code: reply + buf.write( 2 ); + + // htype, hlen + buf.write( data[ 1 ] ); + buf.write( data[ 2 ] ); + + // hops + buf.write( 0 ); + + // xid + for( int i = 4; i < 8; i++ ) { + buf.write( data[ i ] ); + } + + // secs + buf.write( 0 ); + buf.write( 0 ); + + // flags + buf.write( data[ 10 ] ); + buf.write( data[ 11 ] ); + + // ciaddr + for( int i = 0; i < 4; i++ ) { + buf.write( 0 ); + } + + // "your" ip address + buf.write( clientIpAddr ); + + // server ip address + buf.write( serverIpAddr ); + + // giaddr, chaddr + for( int i = 24; i < 44; i++ ) { + buf.write( data[ i ] ); + } + + // sname + int len = SERVER_NAME.length(); + for( int i = 0; i < len; i++ ) { + buf.write( SERVER_NAME.charAt( i ) ); + } + for( int i = len; i < 64; i++ ) { + buf.write( 0 ); + } + + // file + for( int i = 0; i < 128; i++ ) { + buf.write( 0 ); + } + + // Optionen + writeBytesTo( + buf, + 0x63, 0x82, 0x53, 0x63, // options magic code + // options magic code: DHCP_OFFER + 0x35, 0x01, 0x02, + 0x36, 0x04 ); // server identifier + buf.write( serverIpAddr ); + writeBytesTo( + buf, + // ip address lease time: 86400 sec = 1 Tag + 0x33, 0x04, 0x00, 0x01, 0x51, 0x80, + // renewal time value: 43200 sec = 1/2 Tag + 0x3A, 0x04, 0x00, 0x00, 0xA8, 0xC0, + // rebinding time value: 21 Stunden + 0x3B, 0x04, 0x00, 0x01, 0x27, 0x50, + 0x01, 0x04 ); // subnet mask + buf.write( subnetMask ); + byte[] dnsServerIpAddr = netConfig.getDnsServerIpAddr(); + if( dnsServerIpAddr != null ) { + if( dnsServerIpAddr.length == 4 ) { + writeBytesTo( buf, 0x06, 0x04 ); + buf.write( dnsServerIpAddr ); + } + } + + // weitere Optionen entsprechend request list + if( (parmReqListBeg > 0) && (parmReqListLen > 0) ) { + int idx = parmReqListBeg; + while( parmReqListLen > 0 ) { + if( idx >= data.length ) { + break; + } + int option = (int) data[ idx++ ] & 0xFF; + switch( option ) { + case 23: // default ip time-to-live + case 37: // default tcp time-to-live + { + buf.write( option ); + buf.write( 1 ); + buf.write( TTL_DEFAULT ); + } + break; + } + --parmReqListLen; + } + } + + // Optionen abschliessen + buf.write( 0xFF ); + + // DHCP-Prozess einleiten + dhcpProcess = new DhcpProcess( + clientIpAddr, + serverIpAddr, + buf.toByteArray() ); + } + catch( IOException ex ) {} + } + } + } + } + } + } + } + return dhcpProcess; + } + + + public synchronized boolean fillAnswerToClientInto( + DatagramPacket packet ) throws IOException + { + boolean rv = false; + if( this.answerValid ) { + packet.setAddress( InetAddress.getByAddress( this.serverIpAddr ) ); + packet.setPort( SERVER_PORT ); + packet.setData( this.answerBytes ); + this.answerValid = false; + if( this.requestReceived ) { + this.finished = true; + } + rv = true; + } + return rv; + } + + + public boolean hasFinished() + { + return this.finished; + } + + + public boolean processRequest( DatagramPacket packet ) + { + byte[] data = packet.getData(); + if( (data != null) && (packet.getPort() == SERVER_PORT) ) { + if( data.length > 240 ) { + if( data[ 0 ] == (byte) 1 ) { // message op code + int messageType = -1; + int serverIdIdx = 20; + if( (data[ 236 ] == (byte) 0x63) // options magic code + && (data[ 237 ] == (byte) 0x82) + && (data[ 238 ] == (byte) 0x53) + && (data[ 239 ] == (byte) 0x63) ) + { + int idx = 240; + while( idx < data.length ) { + int option = (int) data[ idx++ ] & 0xFF; + if( option == 0xFF ) { + break; + } + if( (option != 0) && (idx < data.length) ) { + int len = (int) data[ idx++ ] & 0xFF; + switch( option ) { + case 0x35: // message type + if( idx < data.length ) { + messageType = (int) data[ idx ] & 0xFF; + } + break; + case 0x36: // server identifier + if( (idx + 3) < data.length ) { + if( len == 4 ) { + /* + * wenn siaddr nicht gesetzt ist, + * dann Parameter "server identifier" verwenden + */ + boolean hasIpAddr = false; + for( int i = 20; i < 24; i++ ) { + if( data[ i ] != (byte) 0 ) { + hasIpAddr = true; + break; + } + } + if( !hasIpAddr ) { + serverIdIdx = idx; + } + } + } + break; + } + idx += len; + } + } + } + if( (messageType == MT_REQUEST) + && EmuUtil.equalsRegion( + data, 4, + this.answerBytes, 4, + 4 ) // xid + && EmuUtil.equalsRegion( + data, serverIdIdx, + this.answerBytes, 20, + 4 ) // server id + && EmuUtil.equalsRegion( + data, 28, + this.answerBytes, 28, + 16 ) ) // chaddr + { + // flags und ciaddr kopieren + for( int i = 10; i < 16; i++ ) { + this.answerBytes[ i ] = data[ i ]; + } + + // giaddr kopieren + for( int i = 24; i < 28; i++ ) { + this.answerBytes[ i ] = data[ i ]; + } + + // message type + this.answerBytes[ 242 ] = MT_ACK; + + // request als empfangen vermerken + this.requestReceived = true; + this.answerValid = true; + } + } + } + } + return this.requestReceived; + } + + + public boolean isTimeout() + { + return (this.begMillis + TIMEOUT_MILLIS) < System.currentTimeMillis(); + } + + + /* --- Konstruktor --- */ + + private DhcpProcess( + byte[] clientIpAddr, + byte[] serverIpAddr, + byte[] answerBytes ) + { + this.clientIpAddr = clientIpAddr; + this.serverIpAddr = serverIpAddr; + this.answerBytes = answerBytes; + this.answerValid = true; + this.requestReceived = false; + this.finished = false; + this.begMillis = System.currentTimeMillis(); + } + + + /* --- private Methoden --- */ + + private static void writeBytesTo( + OutputStream out, + int... values ) throws IOException + { + for( int v : values ) { + out.write( v ); + } + } +} diff --git a/src/jkcemu/net/DhcpServer.java b/src/jkcemu/net/DhcpServer.java new file mode 100644 index 0000000..85d11c8 --- /dev/null +++ b/src/jkcemu/net/DhcpServer.java @@ -0,0 +1,70 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Wrapper-Klasse fuer DatagramSocket und MulticastSocket + * + * Der Sinn der Wrapper-Klasse besteht darin, + * DHCP-Pakete abfangen und simulieren zu koennen. + */ + +package jkcemu.net; + +import java.io.IOException; +import java.lang.*; +import java.net.DatagramPacket; + + +public class DhcpServer +{ + private W5100 w5100; + private DhcpProcess dhcpProcess; + + + public DhcpServer( W5100 w5100 ) + { + this.w5100 = w5100; + } + + + public synchronized boolean receive( DatagramPacket packet ) + throws IOException + { + boolean rv = false; + if( this.dhcpProcess != null ) { + if( this.dhcpProcess.isTimeout() ) { + this.dhcpProcess = null; + } + } + if( this.dhcpProcess != null ) { + if( this.dhcpProcess.fillAnswerToClientInto( packet ) ) { + if( this.dhcpProcess.hasFinished() ) { + this.dhcpProcess = null; + } + rv = true; + } + } + return rv; + } + + + public synchronized void send( DatagramPacket packet ) throws IOException + { + if( this.dhcpProcess != null ) { + if( this.dhcpProcess.isTimeout() ) { + this.dhcpProcess = null; + } + } + DhcpProcess dhcpProcess = DhcpProcess.checkDiscover( this.w5100, packet ); + if( dhcpProcess != null ) { + this.dhcpProcess = dhcpProcess; + } else { + if( this.dhcpProcess != null ) { + if( !this.dhcpProcess.processRequest( packet ) ) { + this.dhcpProcess = null; + } + } + } + } +} diff --git a/src/jkcemu/net/EmuDatagramSocket.java b/src/jkcemu/net/EmuDatagramSocket.java new file mode 100644 index 0000000..309ca9a --- /dev/null +++ b/src/jkcemu/net/EmuDatagramSocket.java @@ -0,0 +1,147 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Wrapper-Klasse fuer DatagramSocket und MulticastSocket + * + * Der Sinn der Wrapper-Klasse besteht darin, + * DHCP-Pakete abfangen und simulieren zu koennen. + */ + +package jkcemu.net; + +import java.io.*; +import java.lang.*; +import java.net.*; + + +public class EmuDatagramSocket implements AutoCloseable, Closeable +{ + private DatagramSocket datagramSocket; + private int port; + + + public static EmuDatagramSocket createDatagramSocket() + throws SocketException + { + return new EmuDatagramSocket( new DatagramSocket(), 0 ); + } + + + public static EmuDatagramSocket createDatagramSocket( int port ) + throws SocketException + { + EmuDatagramSocket ds = null; + if( port == DhcpProcess.CLIENT_PORT ) { + ds = new EmuDatagramSocket( null, port ); + } else { + ds = new EmuDatagramSocket( new DatagramSocket( port ), port ); + } + return ds; + } + + + public static EmuDatagramSocket createMulticastSocket() + throws IOException + { + return new EmuDatagramSocket( new MulticastSocket(), 0 ); + } + + + public static EmuDatagramSocket createMulticastSocket( int port ) + throws IOException + { + EmuDatagramSocket ds = null; + if( port == DhcpProcess.CLIENT_PORT ) { + ds = new EmuDatagramSocket( null, port ); + } else { + ds = new EmuDatagramSocket( new MulticastSocket( port ), port ); + } + return ds; + } + + + public int getLocalPort() + { + return this.datagramSocket != null ? + this.datagramSocket.getLocalPort() + : this.port; + } + + + public boolean isMulticastSocket() + { + boolean rv = false; + if( this.datagramSocket != null ) { + if( this.datagramSocket instanceof MulticastSocket ) { + rv = true; + } + } + return rv; + } + + + public void joinGroup( InetAddress multicastAddr ) throws IOException + { + if( this.datagramSocket != null ) { + if( this.datagramSocket instanceof MulticastSocket ) { + ((MulticastSocket) this.datagramSocket).joinGroup( multicastAddr ); + } + } + } + + + public boolean receive( + W5100 w5100, + DatagramPacket packet ) throws IOException + { + boolean rv = false; + if( this.datagramSocket != null ) { + this.datagramSocket.receive( packet ); + rv = true; + } else { + rv = w5100.getDhcpServer().receive( packet ); + } + return rv; + } + + + public void send( W5100 w5100, DatagramPacket packet ) throws IOException + { + if( this.datagramSocket != null ) { + this.datagramSocket.send( packet ); + } else { + w5100.getDhcpServer().send( packet ); + } + } + + + public void setTimeToLive( int ttl ) throws IOException + { + if( this.datagramSocket != null ) { + if( this.datagramSocket instanceof MulticastSocket ) { + ((MulticastSocket) this.datagramSocket).setTimeToLive( ttl ); + } + } + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public void close() + { + if( this.datagramSocket != null ) + this.datagramSocket.close(); + } + + + /* --- Konstruktor --- */ + + private EmuDatagramSocket( DatagramSocket datagramSocket, int port ) + { + this.datagramSocket = datagramSocket; + this.port = port; + } +} diff --git a/src/jkcemu/net/KCNet.java b/src/jkcemu/net/KCNet.java index a912e07..8c9b285 100644 --- a/src/jkcemu/net/KCNet.java +++ b/src/jkcemu/net/KCNet.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -35,6 +35,12 @@ public class KCNet implements + " WIZnet TCP/IP-Stack \r\n" + "### by JKCEMU ### \r\n"; + // Masken fuer Eigenschaft jkcemu.debug.net + private static final int DEBUG_MASK_MSG = 0x01; + private static final int DEBUG_MASK_CMD = 0x02; + private static final int DEBUG_MASK_READ = 0x04; + private static final int DEBUG_MASK_WRITE = 0x08; + private enum Command { NONE, WRITE_BYTES, @@ -72,7 +78,7 @@ private enum Command { private String title; private Command cmd; - private int debugLevel; + private int debugMask; private int[] args; private int argIdx; private int curAddr; @@ -86,10 +92,8 @@ private enum Command { private int resultPos; private byte[] resultBytes; private byte[] doubleByteBuf; - private byte[] emptyIPAddr; + private byte[] emptyIpAddr; private byte[][] ipAddrMem; - private byte[] dnsIPAddr; - private boolean dnsChecked; private W5100 w5100; private Z80PIO pio; @@ -97,27 +101,25 @@ private enum Command { public KCNet( String title ) { this.title = title; - this.debugLevel = 0; + this.debugMask = 0; this.portSeqNum = PORT_NUM_MIN; this.args = new int[ 5 ]; this.doubleByteBuf = new byte[ 2 ]; - this.emptyIPAddr = new byte[ 4 ]; - Arrays.fill( this.emptyIPAddr, (byte) 0 ); + this.emptyIpAddr = new byte[ 4 ]; + Arrays.fill( this.emptyIpAddr, (byte) 0 ); this.ipAddrMem = new byte[ 8 ][]; for( int i = 0; i < this.ipAddrMem.length; i++ ) { this.ipAddrMem[ i ] = new byte[ 4 ]; } - this.dnsChecked = false; - this.dnsIPAddr = null; - this.w5100 = new W5100(); - this.pio = new Z80PIO( title ); + this.w5100 = new W5100(); + this.pio = new Z80PIO( title ); this.pio.addPIOPortListener( this, Z80PIO.PortInfo.A ); String text = System.getProperty( "jkcemu.debug.net" ); if( text != null ) { try { - this.debugLevel = Integer.parseInt( text ); + this.debugMask = Integer.parseInt( text ); } catch( NumberFormatException ex ) {} } @@ -131,19 +133,25 @@ public void die() } + public static boolean getAutoConfig() + { + return Main.getBooleanProperty( "jkcemu.kcnet.auto_config", true ); + } + + public int read( int port ) { int rv = -1; switch( port & 0x03 ) { case 0x00: - rv = this.pio.readPortA(); - if( this.debugLevel > 2 ) { + rv = this.pio.readDataA(); + if( (this.debugMask & DEBUG_MASK_READ) != 0 ) { System.out.printf( "KCNet read: %02X\n", rv ); } break; case 0x01: - rv = this.pio.readPortB(); + rv = this.pio.readDataB(); break; case 0x02: @@ -162,14 +170,14 @@ public void write( int port, int value ) { switch( port & 0x03 ) { case 0x00: - if( this.debugLevel > 2 ) { + if( (this.debugMask & DEBUG_MASK_WRITE) != 0 ) { System.out.printf( "KCNet write: %02X\n", value ); } - this.pio.writePortA( value ); + this.pio.writeDataA( value ); break; case 0x01: - this.pio.writePortB( value ); + this.pio.writeDataB( value ); break; case 0x02: @@ -225,7 +233,7 @@ public boolean isInterruptRequested() @Override public void reset( boolean powerOn ) { - if( this.debugLevel > 0 ) { + if( (this.debugMask & DEBUG_MASK_MSG) != 0 ) { System.out.printf( "KCNet reset: power_on=%b\n", powerOn ); } this.pio.reset( powerOn ); @@ -238,26 +246,22 @@ public void reset( boolean powerOn ) for( byte[] ipAddr : this.ipAddrMem ) { Arrays.fill( ipAddr, (byte) 0 ); } - byte[] ipAddr = NetUtil.getIPAddr( - Main.getProperty( "jkcemu.kcnet.dns_server" ) ); - if( (ipAddr == null) - && Main.getBooleanProperty( "jkcemu.kcnet.auto_config", true ) ) - { - if( !this.dnsChecked ) { - this.dnsChecked = true; - this.dnsIPAddr = NetUtil.getDNSServerIPAddr(); - if( this.dnsIPAddr != null ) { - if( this.dnsIPAddr.length != 4 ) { - this.dnsIPAddr = null; - } - } + NetConfig netConfig = this.w5100.getNetConfig(); + if( netConfig != null ) { + byte[] dnsServerIpAddr = null; + if( KCNet.getAutoConfig() ) { + dnsServerIpAddr = netConfig.getDnsServerIpAddr(); + } else { + dnsServerIpAddr = netConfig.getManualDnsServerIpAddr(); } - ipAddr = this.dnsIPAddr; - } - if( ipAddr != null ) { - if( ipAddr.length == 4 ) { - for( int i = 0; i < ipAddr.length; i++ ) { - this.ipAddrMem[ 0 ][ i ] = ipAddr[ i ]; + if( dnsServerIpAddr != null ) { + if( dnsServerIpAddr.length != 4 ) { + dnsServerIpAddr = null; + } + if( dnsServerIpAddr != null ) { + for( int i = 0; i < dnsServerIpAddr.length; i++ ) { + this.ipAddrMem[ 0 ][ i ] = dnsServerIpAddr[ i ]; + } } } } @@ -289,7 +293,7 @@ public synchronized void z80TStatesProcessed( Z80CPU cpu, int tStates ) if( this.tStatesToTimeout > 0 ) { this.tStatesToTimeout -= tStates; if( this.tStatesToTimeout <= 0 ) { - if( this.debugLevel > 0 ) { + if( (this.debugMask & DEBUG_MASK_MSG) != 0 ) { System.out.println( "KCNet timeout" ); } this.errorCnt = (this.errorCnt + 1) & 0xFFFF; @@ -355,7 +359,7 @@ private void writeByte( int value ) this.argIdx = 0; if( (value >= 0) && (value < commands.length) ) { Command cmd = commands[ value ]; - if( this.debugLevel > 2 ) { + if( (this.debugMask & DEBUG_MASK_CMD) != 0 ) { System.out.print( " " ); System.out.println( cmd ); } @@ -509,7 +513,7 @@ private void writeByte( int value ) if( value < this.ipAddrMem.length ) { setResultBytes( this.ipAddrMem[ value ] ); } else { - setResultBytes( this.emptyIPAddr ); + setResultBytes( this.emptyIpAddr ); } } else { setIdle(); diff --git a/src/jkcemu/net/KCNetSettingsFld.java b/src/jkcemu/net/KCNetSettingsFld.java index e548b07..ec02a8f 100644 --- a/src/jkcemu/net/KCNetSettingsFld.java +++ b/src/jkcemu/net/KCNetSettingsFld.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2012 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -22,7 +22,7 @@ public class KCNetSettingsFld extends AbstractSettingsFld implements DocumentListener { - private JTextField fldIPAddr; + private JTextField fldIpAddr; private JTextField fldSubnetMask; private JTextField fldGateway; private JTextField fldDNSServer; @@ -68,12 +68,12 @@ public KCNetSettingsFld( SettingsFrm settingsFrm, String propPrefix ) gbc.gridy++; add( this.btnAutoConfig, gbc ); - this.fldIPAddr = createJTextField(); + this.fldIpAddr = createJTextField(); gbc.insets.left = 5; gbc.gridwidth = 1; gbc.gridy = 1; gbc.gridx++; - add( this.fldIPAddr, gbc ); + add( this.fldIpAddr, gbc ); this.fldSubnetMask = createJTextField(); gbc.gridy++; @@ -118,16 +118,16 @@ public void applyInput( { props.setProperty( "jkcemu.kcnet.ip_address", - parseIPAddrText( this.fldIPAddr, "IP-Adresse" ) ); + parseIpAddrText( this.fldIpAddr, "IP-Adresse" ) ); props.setProperty( "jkcemu.kcnet.subnet_mask", - parseIPAddrText( this.fldSubnetMask, "Subnetzmaske" ) ); + parseIpAddrText( this.fldSubnetMask, "Subnetzmaske" ) ); props.setProperty( "jkcemu.kcnet.gateway", - parseIPAddrText( this.fldGateway, "Gateway" ) ); + parseIpAddrText( this.fldGateway, "Gateway" ) ); props.setProperty( "jkcemu.kcnet.dns_server", - parseIPAddrText( this.fldDNSServer, "DNS-Server" ) ); + parseIpAddrText( this.fldDNSServer, "DNS-Server" ) ); EmuUtil.setProperty( props, "jkcemu.kcnet.auto_config", @@ -150,7 +150,7 @@ protected boolean doAction( EventObject e ) @Override public void updFields( Properties props ) { - this.fldIPAddr.setText( + this.fldIpAddr.setText( EmuUtil.getProperty( props, "jkcemu.kcnet.ip_address" ) ); this.fldSubnetMask.setText( @@ -183,7 +183,7 @@ private JTextField createJTextField() } - private String parseIPAddrText( + private String parseIpAddrText( JTextField fld, String fieldName ) throws UserInputException { diff --git a/src/jkcemu/net/NetConfig.java b/src/jkcemu/net/NetConfig.java new file mode 100644 index 0000000..daa968e --- /dev/null +++ b/src/jkcemu/net/NetConfig.java @@ -0,0 +1,368 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Ermittlung der Netzwerkkonfiguration + */ + +package jkcemu.net; + +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; +import java.util.regex.PatternSyntaxException; +import javax.naming.Context; +import javax.naming.directory.InitialDirContext; +import jkcemu.Main; +import jkcemu.base.EmuUtil; + + +public class NetConfig +{ + private byte[] hwAddr; + private byte[] ipAddr; + private byte[] subnetMask; + private byte[] dnsServerIpAddr; + private byte[] manualIpAddr; + private byte[] manualSubnetMask; + private byte[] manualGatewayIpAddr; + private byte[] manualDnsServerIpAddr; + + + public byte[] getDnsServerIpAddr() + { + return this.dnsServerIpAddr; + } + + + public byte[] getHardwareAddr() + { + return this.hwAddr; + } + + + public byte[] getIpAddr() + { + return this.ipAddr; + } + + + public byte[] getSubnetMask() + { + return this.subnetMask; + } + + + public byte[] getManualDnsServerIpAddr() + { + return this.manualDnsServerIpAddr; + } + + + public byte[] getManualGatewayIpAddr() + { + return this.manualGatewayIpAddr; + } + + + public byte[] getManualIpAddr() + { + return this.manualIpAddr; + } + + + public byte[] getManualSubnetMask() + { + return this.manualSubnetMask; + } + + + public static NetConfig readNetConfig() + { + NetConfig netConfig = null; + int debugLevel = 0; + String text = System.getProperty( "jkcemu.debug.net" ); + if( text != null ) { + try { + debugLevel = Integer.parseInt( text ); + } + catch( NumberFormatException ex ) {} + } + + /* + * Es wird ein Netzwerk-Interface mit IPv4-und Hardware-Adresse gesucht. + * Ist das nicht moeglich, + * wird nur nach einem Interface mit IPv4-Adresse gesucht. + */ + byte[] hwAddr = null; + byte[] otherHWAddr = null; + byte[] ipAddr = null; + short nwPrefixLen = 0; + try { + Enumeration nwInterfaces + = NetworkInterface.getNetworkInterfaces() ; + while( nwInterfaces.hasMoreElements() && (hwAddr == null) ) { + NetworkInterface nwIf = nwInterfaces.nextElement(); + if( !nwIf.isVirtual() ) { + boolean isUp = nwIf.isUp(); + if( isUp ) { + java.util.List ifAddrs + = nwIf.getInterfaceAddresses(); + if( ifAddrs != null ) { + for( InterfaceAddress ifAddr : ifAddrs ) { + InetAddress inetAddr = ifAddr.getAddress(); + if( inetAddr != null ) { + byte[] tmpIpAddr = inetAddr.getAddress(); + if( tmpIpAddr != null ) { + if( tmpIpAddr.length == 4 ) { + ipAddr = tmpIpAddr; + nwPrefixLen = ifAddr.getNetworkPrefixLength(); + byte[] tmpHWAddr = nwIf.getHardwareAddress(); + if( tmpHWAddr != null ) { + if( tmpHWAddr.length == 6 ) { + hwAddr = tmpHWAddr; + break; + } + } + } + } + } + } + } + } + if( (hwAddr == null) && (isUp || (otherHWAddr == null)) ) { + /* + * vorsorglich die MAC-Adresse merken, + * falls sich die passende nicht finden laesst. + * Die MAC-Adresse einer aktiven Schnittstelle wird dabei + * der MAC-Adresse einer passiven vorgezogen. + */ + byte[] tmpHWAddr = nwIf.getHardwareAddress(); + if( tmpHWAddr != null ) { + if( tmpHWAddr.length == 6 ) { + otherHWAddr = tmpHWAddr; + } + } + } + } + } + } + catch( IOException ex ) { + if( debugLevel > 0 ) { + ex.printStackTrace( System.out ); + } + } + if( hwAddr == null ) { + hwAddr = otherHWAddr; + } + + /* + * Netzwerkmaske ermitteln + * + * In gemischten IPv4- und IPv6-Umgebungen kann es vorkommen, + * dass die IPv6-Netzwerkmaske gefunden wurde. + * Aus diesem Grund wird hier geprueft, + * ob die Netzwerkmaske einer von IPv4 ueblichen entspricht. + * Wenn nein, wird einfach 255.255.255.0 gesetzt. + * Das ist moeglich, da in der Emulation die Netzwerkmaske + * nicht verwendet wird. + * Sie muss nur vorhanden sein, damit die Netzwerkprogramme + * ein konfiguriertes Netzwerk erkennen. + */ + byte[] subnetMask = new byte[ 4 ]; + if( (nwPrefixLen >= 16) && (nwPrefixLen < 32) ) { + long m = (0xFFFFFFFF00000000L >>> nwPrefixLen); + for( int i = 3; i >= 0; --i ) { + subnetMask[ i ] = (byte) (m & 0xFF); + m >>= 8; + } + } else { + subnetMask[ 0 ] = (byte) 255; + subnetMask[ 1 ] = (byte) 255; + subnetMask[ 2 ] = (byte) 255; + subnetMask[ 3 ] = (byte) 0; + } + + /* + * Ermittlung des Dns-Servers entsprechend der + * JNDI-Beschreibung von Java 1.5 + * + * Wenn ein Laptop in verschiedenen Netzwerkumgebungen verwendet wird, + * kann es vorkommen, dass auch DNS-Server einer gerade nicht + * verwendeten Netzwerkumgebung gefunden werden. + * Aus diesem Grund wird aus den gefundenen DNS-Server der ausgewaehlt, + * der am "naechsten" an der lokalen IP-Adresse liegt. + */ + byte[] dnsServerIpAddr = null; + try { + Hashtable env = new Hashtable<>(); + env.put( + Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.dns.DnsContextFactory" ); + Object o = (new InitialDirContext( env )).getEnvironment().get( + "java.naming.provider.url" ); + if( o != null ) { + String s = o.toString(); + if( s != null ) { + String[] dnsServerURLs = s.split( "\\s" ); + if( dnsServerURLs != null ) { + int lastEqualBytes = 0; + for( String url : dnsServerURLs ) { + if( (url.length() > 6) && url.startsWith( "dns://" ) ) { + byte[] tmpIpAddr = getIpAddr( url.substring( 6 ) ); + if( tmpIpAddr != null ) { + if( ipAddr != null ) { + int nEq = 0; + int len = Math.min( tmpIpAddr.length, ipAddr.length ); + for( int i = 0; i < len; i++ ) { + if( tmpIpAddr[ i ] != ipAddr[ i ] ) { + break; + } + nEq++; + } + if( (lastEqualBytes == 0) || (nEq > lastEqualBytes) ) { + dnsServerIpAddr = tmpIpAddr; + lastEqualBytes = nEq; + } + } else { + dnsServerIpAddr = tmpIpAddr; + } + } + } + } + } + } + } + } + catch( Exception ex ) {} + + /* + * Falls der Dns-Server nicht ermittelt werden konnte, + * dann auf einem Unix/Linux-System die Datei /etc/resolv.conf auslesen + */ + if( (dnsServerIpAddr == null) && Main.isUnixLikeOS() ) { + BufferedReader in = null; + try { + in = new BufferedReader( new FileReader( "/etc/resolv.conf" ) ); + String line = in.readLine(); + while( (dnsServerIpAddr == null) && (line != null) ) { + line = line.trim().toLowerCase(); + int eol = line.indexOf( '#' ); + if( eol != 0 ) { + if( eol > 0 ) { + line = line.substring( 0, eol ); + } + if( line.startsWith( "nameserver" ) ) { + String[] elems = line.split( "\\s" ); + if( elems != null ) { + for( int i = 1; i < elems.length; i++ ) { + dnsServerIpAddr = getIpAddr( elems[ i ] ); + if( dnsServerIpAddr != null ) { + break; + } + } + } + } + } + line = in.readLine(); + } + } + catch( IOException ex ) {} + catch( PatternSyntaxException ex ) {} + finally { + EmuUtil.doClose( in ); + } + } + + /* + * Manuelle Einstellungen lesen und uebernehmen + */ + byte[] manualIpAddr = getIpAddrByProp( "jkcemu.kcnet.ip_address" ); + if( manualIpAddr != null ) { + ipAddr = manualIpAddr; + } + byte[] manualSubnetMask = getIpAddrByProp( "jkcemu.kcnet.subnet_mask" ); + if( manualSubnetMask != null ) { + subnetMask = manualSubnetMask; + } + byte[] manualGatewayIpAddr = getIpAddrByProp( "jkcemu.kcnet.gateway" ); + byte[] manualDnsServerIpAddr = getIpAddrByProp( + "jkcemu.kcnet.dns_server" ); + if( manualDnsServerIpAddr != null ) { + dnsServerIpAddr = manualDnsServerIpAddr; + } + return new NetConfig( + hwAddr, + ipAddr, + subnetMask, + dnsServerIpAddr, + manualIpAddr, + manualSubnetMask, + manualGatewayIpAddr, + manualDnsServerIpAddr ); + } + + + public static byte[] getIpAddr( String text ) + { + byte[] ipAddr = null; + if( text != null ) { + text = text.trim(); + if( !text.isEmpty() ) { + try { + String[] elems = text.split( "\\.", 5 ); + if( elems != null ) { + if( elems.length == 4 ) { + ipAddr = new byte[ elems.length ]; + for( int i = 0; i < elems.length; i++ ) { + int v = Integer.parseInt( elems[ i ] ); + if( (v < 0) || (v > 255) ) { + ipAddr = null; + break; + } + ipAddr[ i ] = (byte) v; + } + } + } + } + catch( NumberFormatException ex ) {} + catch( PatternSyntaxException ex ) {} + } + } + return ipAddr; + } + + + /* --- Konstruktor --- */ + + private NetConfig( + byte[] hwAddr, + byte[] ipAddr, + byte[] subnetMask, + byte[] dnsServerIpAddr, + byte[] manualIpAddr, + byte[] manualSubnetMask, + byte[] manualGatewayIpAddr, + byte[] manualDnsServerIpAddr ) + { + this.hwAddr = hwAddr; + this.ipAddr = ipAddr; + this.subnetMask = subnetMask; + this.dnsServerIpAddr = dnsServerIpAddr; + this.manualIpAddr = manualIpAddr; + this.manualSubnetMask = manualSubnetMask; + this.manualGatewayIpAddr = manualGatewayIpAddr; + this.manualDnsServerIpAddr = manualDnsServerIpAddr; + } + + + /* --- private Methoden --- */ + + private static byte[] getIpAddrByProp( String propName ) + { + String text = Main.getProperty( propName ); + return text != null ? getIpAddr( text ) : null; + } +} diff --git a/src/jkcemu/net/NetUtil.java b/src/jkcemu/net/NetUtil.java deleted file mode 100644 index bbe70ca..0000000 --- a/src/jkcemu/net/NetUtil.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * (c) 2011-2012 Jens Mueller - * - * Kleincomputer-Emulator - * - * Hilfsfunktionen fuer Netzwerk - */ - -package jkcemu.net; - -import java.io.*; -import java.lang.*; -import java.util.Hashtable; -import java.util.regex.PatternSyntaxException; -import javax.naming.Context; -import javax.naming.directory.InitialDirContext; -import jkcemu.Main; -import jkcemu.base.EmuUtil; - - -public class NetUtil -{ - public static byte[] getDNSServerIPAddr() - { - byte[] ipAddr = null; - - /* - * Ermittlung des DNS-Servers entsprechend der - * JNDI-Beschreibung von Java 1.5 - */ - try { - Hashtable env = new Hashtable(); - env.put( - Context.INITIAL_CONTEXT_FACTORY, - "com.sun.jndi.dns.DnsContextFactory" ); - Object o = (new InitialDirContext( env )).getEnvironment().get( - "java.naming.provider.url" ); - if( o != null ) { - String s = o.toString(); - if( s != null ) { - String[] dnsServerURLs = s.split( "\\s" ); - if( dnsServerURLs != null ) { - for( String url : dnsServerURLs ) { - if( (url.length() > 6) && url.startsWith( "dns://" ) ) { - ipAddr = getIPAddr( url.substring( 6 ) ); - if( ipAddr != null ) { - break; - } - } - } - } - } - } - } - catch( Exception ex ) {} - - /* - * Falls der DNS-Server nicht ermittelt werden konnte, - * dann auf einem Unix/Linux-System die Datei /etc/resolv.conf auslesen - */ - if( (ipAddr == null) && Main.isUnixLikeOS() ) { - BufferedReader in = null; - try { - in = new BufferedReader( new FileReader( "/etc/resolv.conf" ) ); - String line = in.readLine(); - while( (ipAddr == null) && (line != null) ) { - line = line.trim().toLowerCase(); - int eol = line.indexOf( '#' ); - if( eol != 0 ) { - if( eol > 0 ) { - line = line.substring( 0, eol ); - } - if( line.startsWith( "nameserver" ) ) { - String[] elems = line.split( "\\s" ); - if( elems != null ) { - for( int i = 1; i < elems.length; i++ ) { - ipAddr = getIPAddr( elems[ i ] ); - if( ipAddr != null ) { - break; - } - } - } - } - } - line = in.readLine(); - } - } - catch( IOException ex ) {} - catch( PatternSyntaxException ex ) {} - finally { - EmuUtil.doClose( in ); - } - } - return ipAddr; - } - - - public static byte[] getIPAddr( String text ) - { - byte[] ipAddr = null; - if( text != null ) { - text = text.trim(); - if( !text.isEmpty() ) { - try { - String[] elems = text.split( "\\.", 5 ); - if( elems != null ) { - if( elems.length == 4 ) { - ipAddr = new byte[ elems.length ]; - for( int i = 0; i < elems.length; i++ ) { - int v = Integer.parseInt( elems[ i ] ); - if( (v < 0) || (v > 255) ) { - ipAddr = null; - break; - } - ipAddr[ i ] = (byte) v; - } - } - } - } - catch( NumberFormatException ex ) {} - catch( PatternSyntaxException ex ) {} - } - } - return ipAddr; - } - - - /* --- private Konstruktoren und Methoden --- */ - - private NetUtil() - { - // leer - } -} - diff --git a/src/jkcemu/net/Ping.java b/src/jkcemu/net/Ping.java index ac3d864..5f20edf 100644 --- a/src/jkcemu/net/Ping.java +++ b/src/jkcemu/net/Ping.java @@ -1,5 +1,5 @@ /* - * (c) 2011 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -23,6 +23,7 @@ import java.io.*; import java.lang.*; import java.net.*; +import jkcemu.Main; import jkcemu.base.EmuUtil; @@ -80,6 +81,7 @@ public synchronized void start() { if( this.thread2 == null ) { this.thread1 = new Thread( + Main.getThreadGroup(), new Runnable() { @Override @@ -87,9 +89,11 @@ public void run() { runThread1(); } - } ); + }, + "JKCEMU ping 1" ); this.thread1.start(); this.thread2 = new Thread( + Main.getThreadGroup(), new Runnable() { @Override @@ -97,7 +101,8 @@ public void run() { runThread2(); } - } ); + }, + "JKCEMU ping 2" ); this.thread2.start(); } } diff --git a/src/jkcemu/net/W5100.java b/src/jkcemu/net/W5100.java index 8e329c2..f101295 100644 --- a/src/jkcemu/net/W5100.java +++ b/src/jkcemu/net/W5100.java @@ -1,5 +1,5 @@ /* - * (c) 2011-2013 Jens Mueller + * (c) 2011-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -19,6 +19,12 @@ public class W5100 { + // Masken fuer Eigenschaft jkcemu.debug.net + private static final int DEBUG_MASK_MSG = 0x10; + private static final int DEBUG_MASK_STATUS = 0x20; + private static final int DEBUG_MASK_READ = 0x40; + private static final int DEBUG_MASK_WRITE = 0x80; + // Adressen der Register private static final int ADDR_MR = 0x0000; private static final int ADDR_GWR = 0x0001; @@ -47,7 +53,7 @@ public class SocketData private static final int CMD_SEND_KEEP = 0x22; private static final int CMD_RECV = 0x40; - // Socket-Stati + // Socket-Status private static final byte SOCK_CLOSED = (byte) 0x00; private static final byte SOCK_INIT = (byte) 0x13; private static final byte SOCK_LISTEN = (byte) 0x14; @@ -84,27 +90,27 @@ public class SocketData private static final int INT_SEND_OK_MASK = 0x10; - private int socketNum; - private int baseAddr; - private int lastStatus; - private int rxReadReg; - private int rxWriteReg; - private int txReadReg; - private int txWriteReg; - private boolean rxFilled; - private boolean nonIPv4MsgShown; - private boolean recvEnabled; - private volatile boolean threadsEnabled; - private volatile boolean cmdThreadNoWait; - private byte[] recvBuf; - private byte[] sendBuf; - private Thread cmdThread; - private Thread recvThread; - private DatagramSocket datagramSocket; - private ServerSocket serverSocket; - private Socket socket; - private InputStream recvStream; - private OutputStream sendStream; + private int socketNum; + private int baseAddr; + private int lastStatus; + private int rxReadReg; + private int rxWriteReg; + private int txReadReg; + private int txWriteReg; + private boolean rxFilled; + private boolean nonIPv4MsgShown; + private boolean recvEnabled; + private volatile boolean threadsEnabled; + private volatile boolean cmdThreadNoWait; + private byte[] recvBuf; + private byte[] sendBuf; + private Thread cmdThread; + private Thread recvThread; + private EmuDatagramSocket datagramSocket; + private ServerSocket serverSocket; + private Socket socket; + private InputStream recvStream; + private OutputStream sendStream; private SocketData( int socketNum, int baseAddr ) @@ -166,9 +172,9 @@ private void checkShowNonIPv4Msg( InetAddress inetAddr ) } - private void closeSocket() + private synchronized void closeSocket() { - DatagramSocket datagramSocket = this.datagramSocket; + EmuDatagramSocket datagramSocket = this.datagramSocket; if( datagramSocket != null ) { datagramSocket.close(); } @@ -191,12 +197,12 @@ private void closeSocket() } - private DatagramSocket createDatagramSocket( boolean forceCreation ) + private EmuDatagramSocket createDatagramSocket( boolean forceCreation ) throws IOException { - DatagramSocket ds = null; - boolean mc = ((getMemByte( this.baseAddr ) & 0x80) != 0); - int port = getMemWord( + EmuDatagramSocket ds = null; + boolean mc = ((getMemByte( this.baseAddr ) & 0x80) != 0); + int port = getMemWord( this.baseAddr + (mc ? Sn_DPORT : Sn_PORT) ); /* * Wenn eine Portnummer bekannt ist, dann schauen, @@ -218,32 +224,30 @@ private DatagramSocket createDatagramSocket( boolean forceCreation ) ds.close(); ds = null; } - MulticastSocket mcs = null; if( port != 0 ) { - mcs = new MulticastSocket( port ); + ds = EmuDatagramSocket.createMulticastSocket( port ); } else { if( forceCreation ) { - mcs = new MulticastSocket(); + ds = EmuDatagramSocket.createMulticastSocket(); } } - if( mcs != null ) { + if( ds != null ) { try { - mcs.setTimeToLive( getMemByte( this.baseAddr + Sn_TTL ) ); + ds.setTimeToLive( getMemByte( this.baseAddr + Sn_TTL ) ); } catch( IllegalArgumentException ex ) {} InetAddress iAddr = createInetAddrByMem( this.baseAddr + Sn_DIPR ); if( iAddr != null ) { - mcs.joinGroup( iAddr ); + ds.joinGroup( iAddr ); } - ds = mcs; } } else { if( ds == null ) { if( port != 0 ) { - ds = new DatagramSocket( port ); + ds = EmuDatagramSocket.createDatagramSocket( port ); } else { if( forceCreation ) { - ds = new DatagramSocket(); + ds = EmuDatagramSocket.createDatagramSocket(); } } } @@ -263,36 +267,52 @@ private void die() private void doSocketConnect() { if( getSR() == SOCK_INIT ) { - Socket socket = null; - SocketAddress socketAddr = null; + boolean done = false; int timeoutMillis = 0; - try { - socketAddr = new InetSocketAddress( + SocketAddress socketAddr = null; + Exception socketEx = null; + if( !isIpAddrConflict( this.baseAddr + Sn_DIPR ) ) { + Socket socket = null; + try { + socketAddr = new InetSocketAddress( createInetAddrByMem( this.baseAddr + Sn_DIPR ), getMemWord( this.baseAddr + Sn_DPORT ) ); - timeoutMillis = getTimeoutMillis(); - socket = createSocket(); - socket.connect( socketAddr, timeoutMillis ); - int bufSize = getTxBufSize( this.socketNum ); - if( bufSize > 0 ) { - this.sendStream = new BufferedOutputStream( + timeoutMillis = getTimeoutMillis(); + socket = createSocket(); + socket.connect( socketAddr, timeoutMillis ); + int bufSize = getTxBufSize( this.socketNum ); + if( bufSize > 0 ) { + this.sendStream = new BufferedOutputStream( socket.getOutputStream(), bufSize ); - } else { - this.sendStream = new BufferedOutputStream( + } else { + this.sendStream = new BufferedOutputStream( socket.getOutputStream() ); + } + this.recvStream = socket.getInputStream(); + this.socket = socket; + synchronized( this ) { + setSR( SOCK_ESTABLISHED ); + setSnIRBits( INT_CON_MASK ); + this.recvEnabled = true; + } + fireRunRecvThread(); + done = true; + + // Debug-Meldung + if( (getDebugMask() & DEBUG_MASK_MSG) != 0 ) { + System.out.printf( + "W5100 Socket %d: connected to %s\n", + this.socketNum, + socketAddr.toString() ); + } } - this.recvStream = socket.getInputStream(); - this.socket = socket; - synchronized( this ) { - setSR( SOCK_ESTABLISHED ); - setSnIRBits( INT_CON_MASK ); - this.recvEnabled = true; + catch( Exception ex ) { + socketEx = ex; } - fireRunRecvThread(); } - catch( Exception ex ) { - if( getDebugLevel() > 0 ) { + if( !done ) { + if( (getDebugMask() & DEBUG_MASK_MSG) != 0 ) { String s = null; if( socketAddr != null ) { s = socketAddr.toString(); @@ -301,7 +321,9 @@ private void doSocketConnect() "connect: %s, timeout=%dms\n", socketAddr, timeoutMillis ); - ex.printStackTrace( System.out ); + if( socketEx != null ) { + socketEx.printStackTrace( System.out ); + } } EmuUtil.doClose( socket ); closeSocket(); @@ -322,7 +344,9 @@ private void doSocketListen() getMemWord( this.baseAddr + Sn_PORT ), 1 ); this.serverSocket = serverSocket; - if( (serverSocket != null) && (getDebugLevel() > 1) ) { + if( (serverSocket != null) + && ((getDebugMask() & DEBUG_MASK_MSG) != 0) ) + { System.out.printf( "W5100 Socket %d: tcp server socket bound at port %d\n", this.socketNum, @@ -333,7 +357,7 @@ private void doSocketListen() this.sendStream = socket.getOutputStream(); this.recvStream = socket.getInputStream(); this.socket = socket; - if( !setMemIPAddr( + if( !setMemIpAddr( this.baseAddr + Sn_DIPR, socket.getInetAddress() ) ) { @@ -350,12 +374,12 @@ private void doSocketListen() catch( Exception ex ) { /* * Beim realen W5100-Chip kann ein LISTEN nicht fehlschlagen. - * Aus diesem Grund wird hier keine Fehler signalisiert, + * Aus diesem Grund wird hier kein Fehler signalisiert, * sondern weiterhin der Zustand SOCK_LISTEN vorgegaukelt. */ if( getCR() == CMD_LISTEN ) { checkPermissionDenied( ex ); - if( getDebugLevel() > 0 ) { + if( (getDebugMask() & DEBUG_MASK_MSG) != 0 ) { ex.printStackTrace( System.out ); } } @@ -420,6 +444,7 @@ private synchronized void fireRunCmdThread() } else { if( this.threadsEnabled ) { Thread t = new Thread( + Main.getThreadGroup(), new Runnable() { @Override @@ -451,6 +476,7 @@ private synchronized void fireRunRecvThread() if( this.recvThread == null ) { if( this.threadsEnabled ) { this.recvThread = new Thread( + Main.getThreadGroup(), new Runnable() { @Override @@ -517,12 +543,12 @@ private void initialize() private void logDatagramSocketBound() { - DatagramSocket ds = this.datagramSocket; + EmuDatagramSocket ds = this.datagramSocket; if( ds != null ) { System.out.printf( "W5100 Socket %d: %s socket bound at port %d\n", this.socketNum, - ds instanceof MulticastSocket ? "multicast" : "datagram", + ds.isMulticastSocket() ? "multicast" : "datagram", ds.getLocalPort() ); } } @@ -603,7 +629,7 @@ private int readMemByte( int addr ) break; } int rv = 0; - if( (getDebugLevel() == 2) + if( ((getDebugMask() & DEBUG_MASK_STATUS) != 0) && (addr == (this.baseAddr + Sn_SR)) ) { synchronized( getLoggingLockObj() ) { @@ -688,7 +714,7 @@ && getMemByte( this.baseAddr + Sn_PROTO ) == 0x01 ) if( (pkg.length >= 4) && ((pkg.length + 6) < nFree) ) { // W5100 IPRAW Header fuellen - setMemIPAddr( + setMemIpAddr( bufAddr + (wr & mask), usedPing.getInetAddress() ); wr += 4; @@ -724,7 +750,7 @@ && getMemByte( this.baseAddr + Sn_PROTO ) == 0x01 ) } if( !received ) { try { - Thread.sleep( 30 ); + Thread.sleep( 10 ); } catch( InterruptedException ex ) {} } @@ -782,7 +808,9 @@ private void receiveTCP() this.rxWriteReg = wr; setSnIRBits( INT_RECV_MASK ); } - if( (nRead > 0) && (getDebugLevel() > 1) ) { + if( (nRead > 0) + && ((getDebugMask() & DEBUG_MASK_MSG) != 0) ) + { System.out.printf( "W5100 Socket %d: %d bytes received\n", this.socketNum, @@ -790,7 +818,7 @@ private void receiveTCP() } } if( b < 0 ) { - if( getDebugLevel() > 1 ) { + if( (getDebugMask() & DEBUG_MASK_MSG) != 0 ) { System.out.printf( "W5100 Socket %d: tcp connection closed" + " by remote host\n", @@ -821,7 +849,8 @@ private void receiveTCP() private void receiveUDP() { - DatagramSocket dSocket = this.datagramSocket; + boolean received = false; + EmuDatagramSocket dSocket = this.datagramSocket; if( dSocket != null ) { int bufAddr = 0; int bufSize = 0; @@ -852,58 +881,58 @@ private void receiveUDP() DatagramPacket packet = new DatagramPacket( recvBuf, nFree - 8 ); - dSocket.receive( packet ); - int len = packet.getLength(); - if( (len > 0) && (len <= (nFree - 8)) && (len < bufSize) ) { - if( getDebugLevel() > 1 ) { - System.out.printf( - "W5100 Socket %d: %d bytes received\n", - this.socketNum, - len ); - } - - // W5100 UP Header fuellen - if( !setMemIPAddr( + if( dSocket.receive( getW5100(), packet ) ) { + int len = packet.getLength(); + if( len > 0 ) { + String msgAddon = ""; + if( (len <= bufSize) && (len <= (nFree - 8)) ) { + + // W5100 UP Header fuellen + if( !setMemIpAddr( bufAddr + (wr & mask), packet.getAddress() ) ) - { - checkShowNonIPv4Msg( packet.getAddress() ); - } - wr += 4; - setMemWord( bufAddr + (wr & mask), packet.getPort() ); - wr += 2; - setMemWord( bufAddr + (wr & mask), len ); - wr += 2; + { + checkShowNonIPv4Msg( packet.getAddress() ); + } + wr += 4; + setMemWord( bufAddr + (wr & mask), packet.getPort() ); + wr += 2; + setMemWord( bufAddr + (wr & mask), len ); + wr += 2; + + // Daten kopieren + byte[] data = packet.getData(); + if( data != null ) { + int pos = packet.getOffset(); + for( int i = 0; i < len; i++ ) { + int b = 0; + if( (pos >= 0) && (pos < data.length) ) { + b = data[ pos ] & 0xFF; + } + setMemByte( bufAddr + (wr & mask), b ); + pos++; + wr++; + } + } - // Daten kopieren - byte[] data = packet.getData(); - if( data != null ) { - int pos = packet.getOffset(); - for( int i = 0; i < len; i++ ) { - int b = 0; - if( (pos >= 0) && (pos < data.length) ) { - b = data[ pos ] & 0xFF; + // Empfang signalisieren + synchronized( this ) { + this.recvEnabled = false; + this.rxFilled = true; + this.rxWriteReg = wr & mask; + setSnIRBits( INT_RECV_MASK ); } - setMemByte( bufAddr + (wr & mask), b ); - pos++; - wr++; + received = true; + } else { + msgAddon = " but ignored due limited buffer size"; } - } - - // Empfang signalisieren - synchronized( this ) { - this.recvEnabled = false; - this.rxFilled = true; - this.rxWriteReg = wr & mask; - setSnIRBits( INT_RECV_MASK ); - } - } else { - if( getDebugLevel() > 1 ) { - System.out.printf( - "W5100 Socket %d: %d bytes received" - + " but ignored because packet length\n", + if( (getDebugMask() & DEBUG_MASK_MSG) != 0 ) { + System.out.printf( + "W5100 Socket %d: %d bytes received%s\n", this.socketNum, - len ); + len, + msgAddon ); + } } } } @@ -911,6 +940,12 @@ private void receiveUDP() } } } + if( !received ) { + try { + Thread.sleep( 10 ); + } + catch( InterruptedException ex ) {} + } } @@ -924,13 +959,13 @@ private void runCmdThread() if( this.datagramSocket == null ) { try { this.datagramSocket = createDatagramSocket( false ); - if( getDebugLevel() > 1 ) { + if( (getDebugMask() & DEBUG_MASK_STATUS) != 0 ) { logDatagramSocketBound(); } } catch( IOException ex ) { checkPermissionDenied( ex ); - if( getDebugLevel() > 0 ) { + if( (getDebugMask() & DEBUG_MASK_MSG) != 0 ) { ex.printStackTrace( System.out ); } closeSocket(); @@ -1038,7 +1073,7 @@ private void runRecvThread() if( thread != null ) { synchronized( thread ) { try { - thread.wait( 100 ); + thread.wait( 30 ); } catch( IllegalMonitorStateException ex ) {} catch( InterruptedException ex ) {} @@ -1166,7 +1201,9 @@ private void sendTCP() done = true; // Debug-Meldung - if( (nWritten > 0) && (getDebugLevel() > 1) ) { + if( (nWritten > 0) + && ((getDebugMask() & DEBUG_MASK_MSG) != 0) ) + { System.out.printf( "W5100 Socket %d: %d bytes sent\n", this.socketNum, @@ -1182,7 +1219,7 @@ private void sendTCP() } } catch( IOException ex ) { - if( getDebugLevel() > 0 ) { + if( (getDebugMask() & DEBUG_MASK_MSG) != 0 ) { ex.printStackTrace( System.out ); } setCR( CMD_NONE ); @@ -1229,25 +1266,17 @@ private void sendUDP() this.baseAddr + Sn_DIPR ); if( (len > 0) && (srcInetAddr != null) && (dstInetAddr != null) ) { try { - DatagramSocket dSocket = this.datagramSocket; + EmuDatagramSocket dSocket = this.datagramSocket; if( dSocket == null ) { dSocket = createDatagramSocket( true ); this.datagramSocket = dSocket; - if( getDebugLevel() > 1 ) { + if( (getDebugMask() & DEBUG_MASK_STATUS) != 0 ) { logDatagramSocketBound(); } this.recvEnabled = true; fireRunRecvThread(); } - /* - * Das Senden an die eigene Adresse fuehrt zu einem Timeout, - * wenn der entsprechende Port nicht empfangsbereit ist. - * Netzwerkkonfigurationsprogramme nutzen diesen Effekt - * zur Erkennung eines IP-Adress-Konflikts aus. - */ - if( !dstInetAddr.equals( srcInetAddr ) - || !isPortReserved( getMemWord( this.baseAddr + Sn_DPORT ) ) ) - { + if( !isIpAddrConflict( this.baseAddr + Sn_DIPR ) ) { byte[] sendBuf = getSendBuf( bufSize ); for( int i = 0; i < len; i++ ) { sendBuf[ i ] = (byte) getMemByte( @@ -1259,7 +1288,7 @@ private void sendUDP() len, dstInetAddr, dstPort ); - dSocket.send( packet ); + dSocket.send( getW5100(), packet ); // Daten als gesendet markieren this.txReadReg = wr; @@ -1267,7 +1296,9 @@ private void sendUDP() done = true; // Debug-Meldung - if( (len > 0) && (getDebugLevel() > 1) ) { + if( (len > 0) + && ((getDebugMask() & DEBUG_MASK_MSG) != 0) ) + { System.out.printf( "W5100 Socket %d: %d bytes sent to %s:%d\n", this.socketNum, @@ -1279,7 +1310,7 @@ private void sendUDP() } catch( IOException ex ) { checkPermissionDenied( ex ); - if( getDebugLevel() > 0 ) { + if( (getDebugMask() & DEBUG_MASK_MSG) != 0 ) { ex.printStackTrace( System.out ); } } @@ -1348,7 +1379,7 @@ private void setCR( int value ) private void setSR( int value ) { - if( getDebugLevel() > 0 ) { + if( (getDebugMask() & DEBUG_MASK_STATUS) != 0 ) { synchronized( getLoggingLockObj() ) { int addr = this.baseAddr + Sn_SR; int oldValue = getMemByte( addr ); @@ -1409,7 +1440,7 @@ private void setSR( int value ) private void writeCommand( int addr, int value ) { - if( getDebugLevel() > 0 ) { + if( (getDebugMask() & DEBUG_MASK_MSG) != 0 ) { String text = null; switch( value ) { case 0x01: @@ -1620,20 +1651,24 @@ private void writeMemByte( int addr, int value ) }; - private byte[] mem; - private Object loggingLockObj; - private SocketData[] sockets; - private java.util.List pings; - private java.util.List reservedDatagramSockets; - private ServerSocketFactory serverSocketFactory; - private SocketFactory socketFactory; - private boolean threadsEnabled; - private int debugLevel; + private byte[] localIpAddr; + private byte[] mem; + private Object loggingLockObj; + private SocketData[] sockets; + private java.util.List pings; + private java.util.List reservedDatagramSockets; + private ServerSocketFactory serverSocketFactory; + private SocketFactory socketFactory; + private DhcpServer dhcpServer; + private NetConfig netConfig; + private boolean threadsEnabled; + private int debugMask; public W5100() { - this.mem = new byte[ 0x8000 ]; + this.localIpAddr = null; + this.mem = new byte[ 0x8000 ]; Arrays.fill( this.mem, (byte) 0 ); this.sockets = new SocketData[ 4 ]; @@ -1644,15 +1679,17 @@ public W5100() this.loggingLockObj = new Object(); this.serverSocketFactory = ServerSocketFactory.getDefault(); this.socketFactory = SocketFactory.getDefault(); - this.reservedDatagramSockets = new ArrayList(); - this.pings = new ArrayList(); + this.dhcpServer = new DhcpServer( this ); + this.reservedDatagramSockets = new ArrayList<>(); + this.pings = new ArrayList<>(); + this.netConfig = null; this.threadsEnabled = true; - this.debugLevel = 0; + this.debugMask = 0; String text = System.getProperty( "jkcemu.debug.net" ); if( text != null ) { try { - this.debugLevel = Integer.parseInt( text ); + this.debugMask = Integer.parseInt( text ); } catch( NumberFormatException ex ) {} } @@ -1676,10 +1713,22 @@ public void die() } + public DhcpServer getDhcpServer() + { + return this.dhcpServer; + } + + + public synchronized NetConfig getNetConfig() + { + return this.netConfig; + } + + public int readMemByte( int addr ) { int rv = 0; - if( this.debugLevel > 2 ) { + if( (getDebugMask() & DEBUG_MASK_READ) != 0 ) { synchronized( getLoggingLockObj() ) { rv = readMemByteInternal( addr ); System.out.printf( @@ -1699,7 +1748,7 @@ public int reservePort() int port = -1; synchronized( this.reservedDatagramSockets ) { try { - DatagramSocket ds = new DatagramSocket(); + EmuDatagramSocket ds = EmuDatagramSocket.createDatagramSocket(); port = ds.getLocalPort(); this.reservedDatagramSockets.add( ds ); } @@ -1725,124 +1774,55 @@ public void reset( boolean powerOn ) if( powerOn ) { Arrays.fill( this.mem, (byte) 0 ); - /* - * Netzwerk-Konfiguration lesen - * - * Es wird ein Netzwerk-Interface mit IPv4- - * und Hardware-Adresse gesucht. - * Ist das nicht moeglich, - * wird nur nach einem Interface mit IPv4-Adresse gesucht. - * Ist auch das nicht moeglich, - * wird die Standard-IPv4-Loopback-Adresse genommen, - * sofern das Netzwerk ueberhaupt aktiv ist. - */ - byte[] hwAddr = null; - byte[] otherHWAddr = null; - byte[] ipAddr = null; - short nwPrefixLen = 0; - try { - Enumeration nwInterfaces - = NetworkInterface.getNetworkInterfaces() ; - while( nwInterfaces.hasMoreElements() && (hwAddr == null) ) { - NetworkInterface nwIf = nwInterfaces.nextElement(); - if( !nwIf.isVirtual() ) { - boolean isUp = nwIf.isUp(); - if( isUp ) { - java.util.List ifAddrs - = nwIf.getInterfaceAddresses(); - if( ifAddrs != null ) { - for( InterfaceAddress ifAddr : ifAddrs ) { - InetAddress inetAddr = ifAddr.getAddress(); - if( inetAddr != null ) { - byte[] tmpIPAddr = inetAddr.getAddress(); - if( tmpIPAddr != null ) { - if( tmpIPAddr.length == 4 ) { - ipAddr = tmpIPAddr; - nwPrefixLen = ifAddr.getNetworkPrefixLength(); - byte[] tmpHWAddr = nwIf.getHardwareAddress(); - if( tmpHWAddr != null ) { - if( tmpHWAddr.length == 6 ) { - hwAddr = tmpHWAddr; - break; - } - } - } - } - } - } - } + synchronized( this ) { + this.netConfig = NetConfig.readNetConfig(); + } + if( netConfig != null ) { + byte[] hwAddr = netConfig.getHardwareAddr(); + if( hwAddr != null ) { + if( hwAddr.length == 6 ) { + int addr = ADDR_SHAR; + for( byte b : hwAddr ) { + this.mem[ addr++ ] = b; } - if( (hwAddr == null) && (isUp || (otherHWAddr == null)) ) { - /* - * vorsorglich die MAC-Adresse merken, - * falls sich die passende nicht finden laesst. - * Die MAC-Adresse einer aktiven Schnittstelle wird dabei - * der MAC-Adresse einer passiven vorgezogen. - */ - byte[] tmpHWAddr = nwIf.getHardwareAddress(); - if( tmpHWAddr != null ) { - if( tmpHWAddr.length == 6 ) { - otherHWAddr = tmpHWAddr; - } - } + } + } + byte[] gatewayIpAddr = netConfig.getManualIpAddr(); + if( gatewayIpAddr != null ) { + if( gatewayIpAddr.length == 4 ) { + int addr = ADDR_GWR; + for( byte b : gatewayIpAddr ) { + this.mem[ addr++ ] = b; } } } - } - catch( IOException ex ) { - if( this.debugLevel > 0 ) { - ex.printStackTrace( System.out ); + this.localIpAddr = netConfig.getIpAddr(); + byte[] ipAddr = this.localIpAddr; + if( ipAddr == null ) { + ipAddr = new byte[] { (byte) 127, (byte) 0, (byte) 0, (byte) 1 }; } - } - if( hwAddr == null ) { - hwAddr = otherHWAddr; - } - if( hwAddr != null ) { - if( hwAddr.length == 6 ) { - int addr = ADDR_SHAR; - for( byte b : hwAddr ) { - this.mem[ addr++ ] = b; - } + byte[] subnetMask = netConfig.getSubnetMask(); + if( !KCNet.getAutoConfig() ) { + ipAddr = netConfig.getManualIpAddr(); + subnetMask = netConfig.getManualSubnetMask(); } - } - if( (ipAddr != null) - && Main.getBooleanProperty( "jkcemu.kcnet.auto_config", true ) ) - { - if( ipAddr.length == 4 ) { - int addr = ADDR_SIPR; - for( byte b : ipAddr ) { - this.mem[ addr++ ] = b; + if( ipAddr != null ) { + if( ipAddr.length == 4 ) { + int addr = ADDR_SIPR; + for( byte b : ipAddr ) { + this.mem[ addr++ ] = b; + } } - /* - * In gemischten IPv4- und IPv6-Umgebungen kann es vorkommen, - * dass die IPv6-Netzwerkmaske gefunden wurde. - * Aus diesem Grund wird hier geprueft, - * ob die Netzwerkmaske einer von IPv4 ueblichen entspricht. - * Wenn nein, wird einfach 255.255.255.0 gesetzt. - * Das ist moeglich, da in der Emulation die Netzwerkmaske - * nicht verwendet wird. - * Sie muss nur vorhanden sein, damit die Netzwerkprogramme - * ein konfiguriertes Netzwerk erkennen. - */ - if( (nwPrefixLen >= 16) && (nwPrefixLen < 32) ) { - long m = 0xFFFFFFFF00000000L >>> nwPrefixLen; - addr = ADDR_SUBR + 3; - for( int i = 0; i < 4; i++ ) { - this.mem[ addr ] = (byte) (m & 0xFF); - --addr; - m >>= 8; + } + if( subnetMask != null ) { + if( subnetMask.length == 4 ) { + int addr = ADDR_SUBR; + for( byte b : subnetMask ) { + this.mem[ addr++ ] = b; } - } else { - this.mem[ ADDR_SUBR ] = (byte) 255; - this.mem[ ADDR_SUBR + 1 ] = (byte) 255; - this.mem[ ADDR_SUBR + 2 ] = (byte) 255; - this.mem[ ADDR_SUBR + 3 ] = (byte) 0; } } } - trySetMemIPAddrByProp( ADDR_GWR, "jkcemu.kcnet.gateway" ); - trySetMemIPAddrByProp( ADDR_SUBR, "jkcemu.kcnet.subnet_mask" ); - trySetMemIPAddrByProp( ADDR_SIPR, "jkcemu.kcnet.ip_address" ); } else { Arrays.fill( this.mem, 0x0013, this.mem.length, (byte) 0 ); } @@ -1866,14 +1846,14 @@ public void writeMemByte( int addr, int value ) { addr &= 0xFFFF; value &= 0xFF; - if( this.debugLevel > 2 ) { + if( (getDebugMask() & DEBUG_MASK_WRITE) != 0 ) { System.out.printf( "W5100: write: addr=%04X value=%02X\n", addr, value ); } if( (addr == ADDR_MR) && ((value & 0x80) != 0) ) { - reset( false ); + reset( true ); } if( (addr >= 0) && (addr < this.mem.length) ) { if( addr == 0x0000 ) { @@ -1946,7 +1926,7 @@ private InetAddress createInetAddrByMem( int addr ) inetAddr = InetAddress.getByAddress( ipAddr ); } catch( Exception ex ) { - if( this.debugLevel > 0 ) { + if( (getDebugMask() & DEBUG_MASK_MSG) != 0 ) { ex.printStackTrace( System.out ); } } @@ -1983,14 +1963,14 @@ private ServerSocket createServerSocket( } - private DatagramSocket fetchReservedDatagramSocket( int port ) + private EmuDatagramSocket fetchReservedDatagramSocket( int port ) { - DatagramSocket ds = null; + EmuDatagramSocket ds = null; synchronized( this.reservedDatagramSockets ) { int len = this.reservedDatagramSockets.size(); int idx = 0; while( idx < len ) { - DatagramSocket tmpDS = this.reservedDatagramSockets.get( idx ); + EmuDatagramSocket tmpDS = this.reservedDatagramSockets.get( idx ); if( tmpDS.getLocalPort() == port ) { ds = tmpDS; break; @@ -2033,9 +2013,9 @@ private int getBufSize( int socketNum, int addr ) } - private int getDebugLevel() + private int getDebugMask() { - return this.debugLevel; + return this.debugMask; } @@ -2136,16 +2116,35 @@ private int getTxBufSize( int socketNum ) } - private boolean isPortReserved( int port ) + /* + * Die Methode dient zur Ermittlung der Referenz auf W5100 + * innerhalb von eingeschlossenen Klassen. + */ + private W5100 getW5100() + { + return this; + } + + + private synchronized boolean isIpAddrConflict( int dstAddrIdx ) { + /* + * Netzwerkkonfigurationsprogramme pruefen auf einen IP-Adresskonflikt, + * indem sie vor dem Setzen der IP-Adresse ein Datenpaket + * an genau diese schicken. + * Wenn im lokalen Netz keine Netzwerkkarte diese IP-Adresse hat, + * erfolgt keine ARP-Antwort und es tritt ein Timeout auf. + */ boolean rv = false; - synchronized( this.reservedDatagramSockets ) { - for( DatagramSocket ds : this.reservedDatagramSockets ) { - if( ds.getLocalPort() == port ) { - rv = true; - break; - } - } + if( this.localIpAddr != null ) { + rv = (!EmuUtil.equalsRegion( + this.localIpAddr, 0, + this.mem, ADDR_SIPR, + 4 ) + && EmuUtil.equalsRegion( + this.localIpAddr, 0, + this.mem, dstAddrIdx, + 4 )); } return rv; } @@ -2168,7 +2167,7 @@ private int readMemByteInternal( int addr ) private void releaseReservedDatagramSockets() { synchronized( this.reservedDatagramSockets ) { - for( DatagramSocket ds : this.reservedDatagramSockets ) { + for( EmuDatagramSocket ds : this.reservedDatagramSockets ) { ds.close(); } this.reservedDatagramSockets.clear(); @@ -2183,7 +2182,7 @@ private void setMemByte( int addr, int value ) } - private boolean setMemIPAddr( int addr, InetAddress inetAddr ) + private boolean setMemIpAddr( int addr, InetAddress inetAddr ) { boolean done = false; if( inetAddr != null ) { @@ -2224,19 +2223,6 @@ private void setMemWord( int addr, int value ) } - private void trySetMemIPAddrByProp( int addr, String propName ) - { - byte[] ipAddr = NetUtil.getIPAddr( Main.getProperty( propName ) ); - if( ipAddr != null ) { - if( ipAddr.length == 4 ) { - for( int i = 0; i < ipAddr.length; i++ ) { - this.mem[ addr + i ] = ipAddr[ i ]; - } - } - } - } - - private static void wakeUpThread( Thread thread, boolean interrupt ) { if( thread != null ) { diff --git a/src/jkcemu/print/PlainTextPrintable.java b/src/jkcemu/print/PlainTextPrintable.java index f9fab60..adaf5af 100644 --- a/src/jkcemu/print/PlainTextPrintable.java +++ b/src/jkcemu/print/PlainTextPrintable.java @@ -1,9 +1,9 @@ /* - * (c) 2009-2010 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * - * Drucken einer Textes + * Drucken eines Textes */ package jkcemu.print; @@ -68,7 +68,7 @@ public int print( if( (linesToSkip == 0) && (pos < len) ) { // Seite drucken - g.setFont( new Font( "Monospaced", Font.PLAIN, fontSize ) ); + g.setFont( new Font( Font.MONOSPACED, Font.PLAIN, fontSize ) ); g.setColor( Color.black ); int x = (int) pf.getImageableX(); @@ -103,7 +103,7 @@ public int print( } y = (int) (pf.getImageableY() + pf.getImageableHeight()); if( Main.getPrintFileName() && (this.fileName != null) ) { - g.setFont( new Font( "Monospaced", Font.PLAIN, fontSize ) ); + g.setFont( new Font( Font.MONOSPACED, Font.PLAIN, fontSize ) ); g.drawString( this.fileName, x, y ); if( Main.getPrintPageNum() ) { String s = String.valueOf( pageNum + 1 ); diff --git a/src/jkcemu/print/PrintData.java b/src/jkcemu/print/PrintData.java index 5f88a7c..12b2a9a 100644 --- a/src/jkcemu/print/PrintData.java +++ b/src/jkcemu/print/PrintData.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2010 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -34,8 +34,9 @@ public PrintData( int entryNum ) public synchronized byte[] getBytes() { if( this.byteArray == null ) { - if( this.byteStream != null ) + if( this.byteStream != null ) { this.byteArray = this.byteStream.toByteArray(); + } } return this.byteArray; } @@ -91,8 +92,9 @@ public int print( int pageNum ) throws PrinterException { byte[] dataBytes = getBytes(); - if( dataBytes == null ) + if( dataBytes == null ) { return pageNum == 0 ? PAGE_EXISTS : NO_SUCH_PAGE; + } PrintDataScanner scanner = new PrintDataScanner( dataBytes ); @@ -119,13 +121,14 @@ public int print( scanner.skipFormFeed(); --pagesToSkip; } - if( (pagesToSkip > 0) || scanner.endReached() ) + if( (pagesToSkip > 0) || scanner.endReached() ) { return NO_SUCH_PAGE; + } } // Seite drucken g.setColor( Color.black ); - g.setFont( new Font( "Monospaced", Font.PLAIN, fontSize ) ); + g.setFont( new Font( Font.MONOSPACED, Font.PLAIN, fontSize ) ); int x = (int) pf.getImageableX(); int y = (int) pf.getImageableY() + fontSize; @@ -150,4 +153,3 @@ public int print( return PAGE_EXISTS; } } - diff --git a/src/jkcemu/print/PrintListFrm.java b/src/jkcemu/print/PrintListFrm.java index e315dc0..4c3108c 100644 --- a/src/jkcemu/print/PrintListFrm.java +++ b/src/jkcemu/print/PrintListFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2012 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -104,7 +104,7 @@ else if( (src == this.mnuFileOpenText) doOpenText(); } else if( (src == this.mnuFileOpenHex) - || (src == this.mnuPopupOpenText) ) + || (src == this.mnuPopupOpenHex) ) { rv = true; doOpenHex(); @@ -253,7 +253,7 @@ private void doSave() File file = EmuUtil.showFileSaveDlg( this, "Druckauftrag speichern", - Main.getLastPathFile( "print" ), + Main.getLastDirFile( "print" ), EmuUtil.getTextFileFilter() ); if( file != null ) { data.saveToFile( file ); @@ -385,7 +385,7 @@ private PrintListFrm( ScreenFrm screenFrm ) this.mnuPopupOpenText = createJMenuItem( "Im Texteditor \u00F6ffnen..." ); mnuPopup.add( this.mnuPopupOpenText ); - this.mnuPopupOpenHex = createJMenuItem( "Im Texteditor \u00F6ffnen..." ); + this.mnuPopupOpenHex = createJMenuItem( "Im Hex-Editor \u00F6ffnen..." ); mnuPopup.add( this.mnuPopupOpenHex ); this.mnuPopupSave = createJMenuItem( "Speichern unter..." ); diff --git a/src/jkcemu/print/PrintMngr.java b/src/jkcemu/print/PrintMngr.java index e790c6e..df00f15 100644 --- a/src/jkcemu/print/PrintMngr.java +++ b/src/jkcemu/print/PrintMngr.java @@ -1,5 +1,5 @@ /* - * (c) 2009-2010 Jens Mueller + * (c) 2009-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -29,7 +29,7 @@ public class PrintMngr extends AbstractTableModel public PrintMngr() { this.nextEntryNum = 1; - this.entries = new ArrayList(); + this.entries = new ArrayList<>(); this.activeEntry = null; } @@ -166,6 +166,7 @@ public Object getValueAt( int row, int col ) } else { rv = "abgeschlossen"; } + break; } } return rv; diff --git a/src/jkcemu/print/PrintOptionsDlg.java b/src/jkcemu/print/PrintOptionsDlg.java index d9bbb56..18596f3 100644 --- a/src/jkcemu/print/PrintOptionsDlg.java +++ b/src/jkcemu/print/PrintOptionsDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -18,12 +18,12 @@ public class PrintOptionsDlg extends BasicDlg { - private boolean applied; - private JComboBox comboFontSize; - private JCheckBox btnFileName; - private JCheckBox btnPageNum; - private JButton btnOK; - private JButton btnCancel; + private boolean applied; + private JComboBox comboFontSize; + private JCheckBox btnFileName; + private JCheckBox btnPageNum; + private JButton btnOK; + private JButton btnCancel; public static boolean showPrintOptionsDlg( @@ -113,7 +113,7 @@ private PrintOptionsDlg( this.comboFontSize = null; if( askFontSize ) { panelOpt.add( new JLabel( "Schriftgr\u00F6\u00DFe:" ), gbcOpt ); - this.comboFontSize = new JComboBox(); + this.comboFontSize = new JComboBox<>(); this.comboFontSize.setEditable( false ); this.comboFontSize.addItem( "6" ); this.comboFontSize.addItem( "7" ); diff --git a/src/jkcemu/print/PrintUtil.java b/src/jkcemu/print/PrintUtil.java index a143f48..2a4ad45 100644 --- a/src/jkcemu/print/PrintUtil.java +++ b/src/jkcemu/print/PrintUtil.java @@ -1,5 +1,5 @@ /* - * (c) 2008 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -101,7 +101,7 @@ public static void printCenteredPageNum( String s = "- " + String.valueOf( pageNum ) + " -"; int w = g.getFontMetrics().stringWidth( s ); g.setColor( Color.black ); - g.setFont( new Font( "Monospaced", Font.PLAIN, fontSize ) ); + g.setFont( new Font( Font.MONOSPACED, Font.PLAIN, fontSize ) ); g.drawString( s, (int) (pf.getImageableX() + (pf.getImageableWidth() / 2)) diff --git a/src/jkcemu/programming/AbstractOptionsDlg.java b/src/jkcemu/programming/AbstractOptionsDlg.java index c8f19be..fa04281 100644 --- a/src/jkcemu/programming/AbstractOptionsDlg.java +++ b/src/jkcemu/programming/AbstractOptionsDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2012 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -24,13 +24,6 @@ public abstract class AbstractOptionsDlg extends BasicDlg protected PrgOptions appliedOptions; protected EmuThread emuThread; - private static String[] fileFmtItems = { - FileSaver.KCC, - FileSaver.KCTAP_0, - FileSaver.KCTAP_1, - FileSaver.HEADERSAVE, - FileSaver.INTELHEX, - FileSaver.BIN }; private Frame owner; @@ -282,7 +275,7 @@ private void doSelectFile() "Programmcode speichern", this.fldFileName.getFile(), EmuUtil.getKCSystemFileFilter(), - EmuUtil.getTapFileFilter(), + EmuUtil.getKCTapFileFilter(), EmuUtil.getHeadersaveFileFilter(), EmuUtil.getHexFileFilter(), EmuUtil.getBinaryFileFilter(), diff --git a/src/jkcemu/programming/CmdLineArgIterator.java b/src/jkcemu/programming/CmdLineArgIterator.java new file mode 100644 index 0000000..01b1f29 --- /dev/null +++ b/src/jkcemu/programming/CmdLineArgIterator.java @@ -0,0 +1,110 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Iterator ueber die Argumente einer Kommandozeile + */ + +package jkcemu.programming; + +import java.io.*; +import java.lang.*; + + +public class CmdLineArgIterator implements Closeable +{ + private Reader reader; + private String[] ary; + private int idx; + + + public static CmdLineArgIterator createFromStringArray( + String[] ary, + int idx ) + { + return new CmdLineArgIterator( null, ary, idx ); + } + + + public static CmdLineArgIterator createFromReader( Reader reader ) + { + return new CmdLineArgIterator( reader, null, 0 ); + } + + + public synchronized String next() throws IOException + { + String rv = null; + if( this.ary != null ) { + if( this.idx < this.ary.length ) { + rv = this.ary[ this.idx++ ]; + if( rv == null ) { + rv = ""; + } + } + } + else if( this.reader != null ) { + int ch = this.reader.read(); + while( (ch >= 0) && isWhitespace( ch ) ) { + ch = this.reader.read(); + } + if( ch >= 0 ) { + StringBuilder buf = new StringBuilder( 64 ); + if( ch == '\"' ) { + ch = this.reader.read(); + while( (ch >= 0) && (ch != '\"') && !isWhitespace( ch ) ) { + buf.append( (char) ch ); + ch = this.reader.read(); + } + if( ch != '\"' ) { + throw new IOException( + "In \'\"\' eingeschlossenes Argument nicht abgeschlossen" ); + } + } else { + buf.append( (char) ch ); + ch = this.reader.read(); + while( (ch >= 0) && !isWhitespace( ch ) ) { + buf.append( (char) ch ); + ch = this.reader.read(); + } + } + rv = buf.toString(); + } + } + return rv; + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public synchronized void close() throws IOException + { + try { + if( this.reader != null ) { + this.reader.close(); + } + } + finally { + this.reader = null; + this.ary = null; + } + } + + + /* --- private Methoden --- */ + + private static boolean isWhitespace( int ch ) + { + return (ch < 0x20) || Character.isWhitespace( ch ); + } + + + private CmdLineArgIterator( Reader reader, String[] ary, int idx ) + { + this.reader = reader; + this.ary = ary; + this.idx = idx; + } +} diff --git a/src/jkcemu/programming/PrgOptions.java b/src/jkcemu/programming/PrgOptions.java index a71cf2f..f7d5de0 100644 --- a/src/jkcemu/programming/PrgOptions.java +++ b/src/jkcemu/programming/PrgOptions.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -28,6 +28,7 @@ public class PrgOptions private File codeFile; private boolean forceRun; private boolean labelsToDebugger; + private boolean suppressLabelRecreateInDebugger; private boolean labelsToReass; private boolean formatSource; private boolean warnNonAsciiChars; @@ -35,19 +36,20 @@ public class PrgOptions public PrgOptions() { - this.asmSyntax = Z80Assembler.Syntax.ALL; - this.allowUndocInst = false; - this.labelsCaseSensitive = false; - this.printLabels = false; - this.codeToEmu = false; - this.codeToSecondSys = false; - this.codeToFile = false; - this.codeFile = null; - this.forceRun = false; - this.labelsToDebugger = false; - this.labelsToReass = false; - this.formatSource = false; - this.warnNonAsciiChars = true; + this.asmSyntax = Z80Assembler.Syntax.ALL; + this.allowUndocInst = false; + this.labelsCaseSensitive = false; + this.printLabels = false; + this.codeToEmu = false; + this.codeToSecondSys = false; + this.codeToFile = false; + this.codeFile = null; + this.forceRun = false; + this.labelsToDebugger = false; + this.suppressLabelRecreateInDebugger = false; + this.labelsToReass = false; + this.formatSource = false; + this.warnNonAsciiChars = true; } @@ -162,6 +164,10 @@ public static PrgOptions getPrgOptions( Properties props ) String codeFileName = props.getProperty( "jkcemu.programming.code.file.name" ); + Boolean suppressLabelRecreateInDebugger = getBoolean( + props, + "jkcemu.programming.suppress_label_recreate_in_debugger" ); + Boolean labelsToDebugger = getBoolean( props, "jkcemu.programming.labels_to_debugger" ); @@ -187,6 +193,7 @@ public static PrgOptions getPrgOptions( Properties props ) || (codeToFile != null) || (codeFileName != null) || (labelsToDebugger != null) + || (suppressLabelRecreateInDebugger != null) || (labelsToReass != null) || (formatSource != null) || (warnNonAsciiChars != null) ) @@ -230,6 +237,10 @@ public static PrgOptions getPrgOptions( Properties props ) if( labelsToDebugger != null ) { options.labelsToDebugger = labelsToDebugger.booleanValue(); } + if( suppressLabelRecreateInDebugger != null ) { + options.suppressLabelRecreateInDebugger + = suppressLabelRecreateInDebugger.booleanValue(); + } if( labelsToReass != null ) { options.labelsToReass = labelsToReass.booleanValue(); } @@ -246,6 +257,12 @@ public static PrgOptions getPrgOptions( Properties props ) } + public boolean getSuppressLabelRecreateInDebugger() + { + return this.suppressLabelRecreateInDebugger; + } + + public boolean getWarnNonAsciiChars() { return this.warnNonAsciiChars; @@ -306,6 +323,10 @@ public void putOptionsTo( Properties props ) "jkcemu.programming.labels_to_debugger", Boolean.toString( this.labelsToDebugger ) ); + props.setProperty( + "jkcemu.programming.suppress_label_recreate_in_debugger", + Boolean.toString( this.suppressLabelRecreateInDebugger ) ); + props.setProperty( "jkcemu.programming.labels_to_reassembler", Boolean.toString( this.labelsToReass ) ); @@ -388,6 +409,12 @@ public void setPrintLabels( boolean state ) } + public void setSuppressLabelRecreateInDebugger( boolean state ) + { + this.suppressLabelRecreateInDebugger = state; + } + + public void setWarnNonAsciiChars( boolean state ) { this.warnNonAsciiChars = state; @@ -448,9 +475,11 @@ public boolean equals( Object o ) && (options.codeToFile == this.codeToFile) && (options.forceRun == this.forceRun) && (options.labelsToDebugger == this.labelsToDebugger) + && (options.suppressLabelRecreateInDebugger + == this.suppressLabelRecreateInDebugger) && (options.labelsToReass == this.labelsToReass) && (options.formatSource == this.formatSource) - && (options.warnNonAsciiChars == this.warnNonAsciiChars) ) + && (options.warnNonAsciiChars == this.warnNonAsciiChars) ) { if( (options.codeFile != null) && (this.codeFile != null) ) { rv = options.codeFile.equals( this.codeFile ); diff --git a/src/jkcemu/programming/PrgSource.java b/src/jkcemu/programming/PrgSource.java new file mode 100644 index 0000000..9527001 --- /dev/null +++ b/src/jkcemu/programming/PrgSource.java @@ -0,0 +1,208 @@ +/* + * (c) 2014-2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Kapselung eines Quelltextes + */ + +package jkcemu.programming; + +import java.io.*; +import java.lang.*; +import java.util.*; +import java.util.regex.PatternSyntaxException; +import jkcemu.base.EmuUtil; + + +public class PrgSource +{ + private java.util.List lines; + private String text; + private String name; + private File file; + private int pos; + private int lineNum; + private Map lineNum2Addr; + + + public File getFile() + { + return this.file; + } + + + public static File getIncludeFile( + PrgSource baseSource, + String fileName ) + { + File file = null; + File dirFile = null; + if( baseSource != null ) { + File baseFile = baseSource.getFile(); + if( baseFile != null ) { + dirFile = baseFile.getParentFile(); + } + } + if( !fileName.startsWith( "/" ) + && (fileName.indexOf( '/' ) >= 0) + && ((File.separatorChar == '/') + || (fileName.indexOf( File.separatorChar ) < 0)) ) + { + // plattformunabhaengige relative Pfadangabe + try { + String[] items = fileName.split( "/" ); + if( items != null ) { + if( items.length > 0 ) { + file = dirFile; + for( String s : items ) { + if( file != null ) { + file = new File( file, s ); + } else { + file = new File( s ); + } + } + } + } + } + catch( PatternSyntaxException ex ) {} + } + if( file == null ) { + file = new File( fileName ); + if( (dirFile != null) && !file.isAbsolute() ) { + file = new File( dirFile, fileName ); + } + } + return file; + } + + + public Map getLineAddrMap() + { + return this.lineNum2Addr; + } + + + public int getLineNum() + { + return this.lineNum; + } + + + public String getName() + { + return this.name; + } + + + public String getText() + { + return this.text; + } + + + public static PrgSource readFile( File file ) throws IOException + { + return new PrgSource( + readLines( new FileReader( file ) ), + null, + file.getName(), + file ); + } + + + public static PrgSource readText( + String text, + String name, + File file ) + { + if( (name == null) && (file != null) ) { + name = file.getName(); + } + return new PrgSource( null, text, name, file ); + } + + + public String readLine() throws IOException + { + String rv = null; + if( this.lines != null ) { + if( (this.lineNum >= 0) && (this.lineNum < this.lines.size()) ) { + rv = this.lines.get( this.lineNum++ ); + } + } else if( this.text != null ) { + int len = this.text.length(); + if( this.pos < len ) { + int eol = this.text.indexOf( '\n', this.pos ); + if( eol >= this.pos ) { + rv = this.text.substring( this.pos, eol ); + this.pos = eol + 1; + } else { + rv = this.text.substring( this.pos ); + this.pos = len; + } + this.lineNum++; + } + } + return rv; + } + + + public void reset() + { + this.lineNum = 0; + this.pos = 0; + if( this.lineNum2Addr != null ) { + this.lineNum2Addr.clear(); + } + } + + + public void setLineAddr( int addr ) + { + if( this.lineNum2Addr == null ) { + this.lineNum2Addr = new HashMap<>(); + } + this.lineNum2Addr.put( this.lineNum, addr ); + } + + + /* --- Konstruktor --- */ + + private PrgSource( + java.util.List lines, + String text, + String name, + File file ) + { + this.lines = lines; + this.file = file; + this.text = text; + this.name = name; + this.pos = 0; + this.lineNum = 0; + this.lineNum2Addr = null; + } + + + /* --- private Methoden --- */ + + private static java.util.List readLines( Reader in ) + throws IOException + { + java.util.List lines = new ArrayList<>( 0x1000 ); + BufferedReader reader = null; + try { + reader = new BufferedReader( in ); + String line = reader.readLine(); + while( line != null ) { + lines.add( line ); + line = reader.readLine(); + } + } + finally { + EmuUtil.doClose( reader ); + } + return lines; + } +} diff --git a/src/jkcemu/programming/PrgThread.java b/src/jkcemu/programming/PrgThread.java index 3e42f13..d821570 100644 --- a/src/jkcemu/programming/PrgThread.java +++ b/src/jkcemu/programming/PrgThread.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2012 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -11,6 +11,7 @@ import java.awt.EventQueue; import java.io.IOException; import java.lang.*; +import jkcemu.Main; import jkcemu.base.*; import jkcemu.programming.assembler.Z80Assembler; import jkcemu.text.*; @@ -33,7 +34,7 @@ public PrgThread( PrgOptions options, Appendable logOut ) { - super( threadName ); + super( Main.getThreadGroup(), threadName ); this.emuThread = emuThread; this.editText = editText; this.options = options; @@ -83,7 +84,7 @@ public EditText getEditText() } - protected void writeCodeToEmu( Z80Assembler assembler ) + protected void writeCodeToEmu( Z80Assembler assembler, boolean logAddrs ) { boolean forceRun = this.options.getForceRun(); byte[] codeBytes = assembler.getCreatedCode(); @@ -105,16 +106,19 @@ protected void writeCodeToEmu( Z80Assembler assembler ) secondSysName = emuSys.getSecondSystemName(); } } - appendToLog( "Lade Programmcode in Arbeitsspeicher (" ); - if( secondSysName != null ) { - appendToLog( secondSysName ); - appendToLog( ", " ); - } - appendToLog( + appendToLog( "Lade Programmcode in Arbeitsspeicher" ); + if( logAddrs || (secondSysName != null) ) { + appendToLog( " (" ); + if( secondSysName != null ) { + appendToLog( secondSysName ); + appendToLog( ", " ); + } + appendToLog( String.format( "Bereich %04X-%04X)", begAddr, begAddr + codeBytes.length - 1) ); + } if( forceRun && (startAddr >= 0) ) { appendToLog( String.format( @@ -196,4 +200,3 @@ public void run() } } } - diff --git a/src/jkcemu/programming/assembler/AsmArg.java b/src/jkcemu/programming/assembler/AsmArg.java index 2922f17..3ebbc74 100644 --- a/src/jkcemu/programming/assembler/AsmArg.java +++ b/src/jkcemu/programming/assembler/AsmArg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -10,7 +10,7 @@ import java.lang.*; import java.text.*; -import jkcemu.programming.PrgException; +import jkcemu.programming.*; public class AsmArg @@ -23,27 +23,32 @@ public class AsmArg public AsmArg( String argText ) { - this.argText = argText; - if( (this.argText.indexOf( '\u0020' ) >= 0) - || (this.argText.indexOf( '\t' ) >= 0) ) - { - int len = this.argText.length(); - if( len > 0 ) { - StringBuilder buf = new StringBuilder( len ); - boolean quoted = false; - for( int i = 0; i < len; i++ ) { - char ch = this.argText.charAt( i ); - if( (ch == '\'') || (ch == '\"') ) { - buf.append( ch ); - quoted = !quoted; - } else { - if( quoted || ((ch != '\u0020') && (ch != '\t')) ) - buf.append( ch ); - } - } - this.argText = buf.toString(); + // weisse Leerzeichen am Anfang und Ende entfernen + int len = argText.length(); + int pos = 0; + while( pos < len ) { + if( !PrgUtil.isWhitespace( argText.charAt( pos ) ) ) { + break; } + pos++; } + if( pos > 0 ) { + argText = argText.substring( pos ); + } + len = argText.length(); + pos = len - 1; + while( pos >= 0 ) { + if( !PrgUtil.isWhitespace( argText.charAt( pos ) ) ) { + break; + } + --pos; + } + if( pos < 0 ) { + argText = ""; + } else if( pos < (len - 1) ) { + argText = argText.substring( 0, pos + 1 ); + } + this.argText = argText; this.argLen = this.argText.length(); this.upperText = this.argText.toUpperCase(); this.indirectText = null; @@ -259,19 +264,6 @@ public boolean isRegBtoL() } - public static boolean isFlagCondition( String text ) - { - return text.equals( "NZ" ) - || text.equals( "Z" ) - || text.equals( "NC" ) - || text.equals( "C" ) - || text.equals( "PO" ) - || text.equals( "PE" ) - || text.equals( "P" ) - || text.equals( "M" ); - } - - public static boolean isRegister( String text ) { return text.equals( "A" ) @@ -296,6 +288,19 @@ public static boolean isRegister( String text ) } + public static boolean isFlagCondition( String text ) + { + return text.equals( "NZ" ) + || text.equals( "Z" ) + || text.equals( "NC" ) + || text.equals( "C" ) + || text.equals( "PO" ) + || text.equals( "PE" ) + || text.equals( "P" ) + || text.equals( "M" ); + } + + public static boolean isUndocRegister( String text ) { return text.equals( "IXH" ) diff --git a/src/jkcemu/programming/assembler/AsmLabel.java b/src/jkcemu/programming/assembler/AsmLabel.java index 6d63735..d09000d 100644 --- a/src/jkcemu/programming/assembler/AsmLabel.java +++ b/src/jkcemu/programming/assembler/AsmLabel.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2011 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -11,11 +11,12 @@ import java.lang.*; -public class AsmLabel implements /*Comparable,*/ jkcemu.tools.Label +public class AsmLabel implements jkcemu.tools.Label { private String labelName; + private Object labelValue; private String hex16String; - private int labelValue; + private int varSize; public AsmLabel( String labelName, int labelValue ) @@ -23,21 +24,42 @@ public AsmLabel( String labelName, int labelValue ) this.labelName = labelName; this.labelValue = labelValue; this.hex16String = null; + this.varSize = -1; } - public void setLabelValue( int value ) + public Object getLabelValue() + { + return this.labelValue; + } + + + public static boolean isIdentifierPart( char ch ) + { + return (ch == '_') || (ch == '@') || (ch == '?') + || ((ch >= 'A') && (ch <= 'Z')) + || ((ch >= 'a') && (ch <= 'z')) + || ((ch >= '0') && (ch <= '9')); + } + + + public static boolean isIdentifierStart( char ch ) + { + return (ch == '_') || (ch == '@') + || ((ch >= 'A') && (ch <= 'Z')) + || ((ch >= 'a') && (ch <= 'z')); + } + + + public void setLabelValue( Object value ) { this.labelValue = value; } - public String toHex16String() + public void setVarSize( int varSize ) { - if( this.hex16String == null ) { - this.hex16String = String.format( "%04X", this.labelValue ); - } - return this.hex16String; + this.varSize = varSize; } @@ -51,9 +73,22 @@ public String getLabelName() @Override - public int getLabelValue() + public int getVarSize() { - return this.labelValue; + return this.varSize; + } + + + @Override + public int intValue() + { + int rv = 0; + if( this.labelValue != null ) { + if( this.labelValue instanceof Number ) { + rv = ((Number) this.labelValue).intValue(); + } + } + return rv; } @@ -62,6 +97,6 @@ public int getLabelValue() @Override public int compareTo( jkcemu.tools.Label label ) { - return label != null ? (this.labelValue - label.getLabelValue()) : -1; + return label != null ? (intValue() - label.intValue()) : -1; } } diff --git a/src/jkcemu/programming/assembler/AsmLine.java b/src/jkcemu/programming/assembler/AsmLine.java index a874a29..dcacce9 100644 --- a/src/jkcemu/programming/assembler/AsmLine.java +++ b/src/jkcemu/programming/assembler/AsmLine.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -46,18 +46,11 @@ public static AsmLine scanLine( } else { // Marke parsen - if( (ch == '_') - || ((ch >= 'A') && (ch <= 'Z')) - || ((ch >= 'a') && (ch <= 'z')) ) - { + if( AsmLabel.isIdentifierStart( ch ) ) { StringBuilder buf = new StringBuilder(); buf.append( ch ); ch = iter.next(); - while( (ch == '_') - || ((ch >= 'A') && (ch <= 'Z')) - || ((ch >= 'a') && (ch <= 'z')) - || ((ch >= '0') && (ch <= '9')) ) - { + while( AsmLabel.isIdentifierPart( ch ) ) { buf.append( ch ); ch = iter.next(); } @@ -67,24 +60,37 @@ public static AsmLine scanLine( { throwInvalidCharInLabel( ch ); } - if( ch == ':' ) { - ch = iter.next(); - } - label = buf.toString(); - String upperLabel = label.toUpperCase(); - if( !labelsCaseSensitive ) { - label = upperLabel; - } - if( AsmArg.isRegister( upperLabel ) - || AsmArg.isFlagCondition( upperLabel ) ) + // Pruefen auf ein Schluesselwort fuer eine Bedingung + String s = buf.toString().toUpperCase(); + if( s.equals( "IF" ) || s.equals( "IFT" ) + || s.equals( "IFE" ) || s.equals( "IFF" ) + || s.equals( "IF1" ) || s.equals( "IF2" ) + || s.equals( "IFDEF" ) || s.equals( "IFNDEF" ) + || s.equals( "ELSE" ) || s.equals( "ENDIF" ) ) { - asm.putWarning( "Marke \'" + label + "\': Name reserviert" - + " f\u00FCr Register oder Flagbedingung" ); - } - if( AsmArg.isUndocRegister( upperLabel ) ) { - asm.putWarning( "Marke \'" + label + "\': Name reserviert" - + " f\u00FCr Registerbezeichnung" - + " eines undokumentierten Befehls" ); + ch = iter.first(); + } else { + if( ch == ':' ) { + ch = iter.next(); + } + label = buf.toString(); + String upperLabel = label.toUpperCase(); + if( !labelsCaseSensitive ) { + label = upperLabel; + } + if( asm != null ) { + boolean reserved = (asm.isReservedWord( upperLabel ) + || AsmArg.isRegister( upperLabel ) + || AsmArg.isFlagCondition( upperLabel ) + || ExprParser.isReservedWord( upperLabel )); + if( !reserved && asm.getOptions().getAllowUndocInst() ) { + reserved = AsmArg.isUndocRegister( upperLabel ); + } + if( reserved ) { + asm.putWarning( + "Marke \'" + label + "\': Reserviertes Wort" ); + } + } } } else { if( (ch != CharacterIterator.DONE) @@ -116,7 +122,7 @@ public static AsmLine scanLine( String argText = nextArgText( iter ); while( argText != null ) { if( args == null ) { - args = new ArrayList(); + args = new ArrayList<>(); } args.add( new AsmArg( argText ) ); argText = nextArgText( iter ); @@ -167,14 +173,14 @@ public void appendFormattedTo( StringBuilder buf ) int tabsToComment = 3; boolean hasLabel = false; if( this.label != null ) { - if( this.label.length() > 0 ) { + if( !this.label.isEmpty() ) { buf.append( this.label ); buf.append( (char) ':' ); hasLabel = true; } } if( instruction != null ) { - if( instruction.length() > 0 ) { + if( !instruction.isEmpty() ) { buf.append( (char) '\t' ); buf.append( instruction ); --tabsToComment; @@ -193,7 +199,7 @@ public void appendFormattedTo( StringBuilder buf ) } } if( this.comment != null ) { - if( this.comment.length() > 0 ) { + if( !this.comment.isEmpty() ) { if( hasLabel || (tabsToComment < 3) || !this.commentAtStart ) { while( tabsToComment > 0 ) { buf.append( (char) '\t' ); @@ -214,7 +220,7 @@ public void checkEOL() throws PrgException StringBuilder buf = new StringBuilder( 32 ); String text = this.args[ this.argPos ].toString(); if( text != null ) { - if( text.length() > 0 ) { + if( !text.isEmpty() ) { buf.append( text ); buf.append( ": " ); } @@ -242,8 +248,9 @@ public boolean hasMoreArgs() { boolean rv = false; if( this.args != null ) { - if( this.argPos < this.args.length ) + if( this.argPos < this.args.length ) { rv = true; + } } return rv; } @@ -253,8 +260,9 @@ public AsmArg nextArg() throws PrgException { AsmArg arg = null; if( this.args != null ) { - if( this.argPos < this.args.length ) + if( this.argPos < this.args.length ) { arg = this.args[ this.argPos++ ]; + } } if( arg == null ) { throw new PrgException( "Argument erwartet" ); @@ -322,7 +330,7 @@ private static String nextArgText( CharacterIterator iter ) } String rv = null; if( buf != null ) { - rv = buf.toString().trim(); + rv = buf.toString(); if( rv.isEmpty() ) { rv = null; } @@ -343,4 +351,3 @@ private static void throwInvalidCharInLabel( char ch ) throws PrgException throw new PrgException( buf.toString() ); } } - diff --git a/src/jkcemu/programming/assembler/AsmOptionsDlg.java b/src/jkcemu/programming/assembler/AsmOptionsDlg.java index a3b27c4..0b1357e 100644 --- a/src/jkcemu/programming/assembler/AsmOptionsDlg.java +++ b/src/jkcemu/programming/assembler/AsmOptionsDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -26,8 +26,9 @@ public class AsmOptionsDlg extends AbstractOptionsDlg private JCheckBox btnAllowUndocInst; private JCheckBox btnLabelsCaseSensitive; private JCheckBox btnPrintLabels; - private JCheckBox btnLabelsToDebugger; private JCheckBox btnLabelsToReass; + private JCheckBox btnLabelsToDebugger; + private JCheckBox btnSuppressLabelRecreateInDebugger; private JCheckBox btnFormatSource; private JCheckBox btnWarnNonAsciiChars; @@ -100,11 +101,9 @@ public AsmOptionsDlg( Frame owner, EmuThread emuThread, PrgOptions options ) this.btnLabelsCaseSensitive = new JCheckBox( "Gro\u00DF-/Kleinschreibung bei Marken beachten" ); - this.btnLabelsCaseSensitive.addActionListener( this ); panelLabel.add( this.btnLabelsCaseSensitive ); this.btnPrintLabels = new JCheckBox( "Markentabelle ausgeben" ); - this.btnPrintLabels.addActionListener( this ); panelLabel.add( this.btnPrintLabels ); @@ -142,15 +141,21 @@ public AsmOptionsDlg( Frame owner, EmuThread emuThread, PrgOptions options ) panelEtc.add( this.btnFormatSource, gbcEtc ); this.btnLabelsToDebugger = new JCheckBox( - "Im Debugger Haltepunkte auf Marken anlegen" - + " (nur bei Programmcode in Emulator laden)" ); + "Im Debugger Halte-/Log-Punkte bzw. Variablen" + + " auf Marken anlegen" ); this.btnLabelsToDebugger.setEnabled( false ); gbcEtc.gridy++; panelEtc.add( this.btnLabelsToDebugger, gbcEtc ); + this.btnSuppressLabelRecreateInDebugger = new JCheckBox( + "Im Debugger manuell entfernte Halte-/Log-Punkte" + + " bzw. Variablen nicht wieder anlegen" ); + this.btnSuppressLabelRecreateInDebugger.setEnabled( false ); + gbcEtc.gridy++; + panelEtc.add( this.btnSuppressLabelRecreateInDebugger, gbcEtc ); + this.btnLabelsToReass = new JCheckBox( - "Marken im Reassembler verwenden" - + " (nur bei Programmcode in Emulator laden)" ); + "Marken im Reassembler verwenden" ); this.btnLabelsToReass.setEnabled( false ); gbcEtc.insets.bottom = 5; gbcEtc.gridy++; @@ -185,6 +190,8 @@ public AsmOptionsDlg( Frame owner, EmuThread emuThread, PrgOptions options ) this.btnWarnNonAsciiChars.setSelected( options.getWarnNonAsciiChars() ); this.btnFormatSource.setSelected( options.getFormatSource() ); this.btnLabelsToDebugger.setSelected( options.getLabelsToDebugger() ); + this.btnSuppressLabelRecreateInDebugger.setSelected( + options.getSuppressLabelRecreateInDebugger() ); this.btnLabelsToReass.setSelected( options.getLabelsToReassembler() ); updCodeDestFields( options, false ); } else { @@ -195,9 +202,15 @@ public AsmOptionsDlg( Frame owner, EmuThread emuThread, PrgOptions options ) this.btnWarnNonAsciiChars.setSelected( true ); this.btnFormatSource.setSelected( false ); this.btnLabelsToDebugger.setSelected( false ); + this.btnSuppressLabelRecreateInDebugger.setSelected( false ); this.btnLabelsToReass.setSelected( false ); updCodeDestFields( options, true ); } + updSuppressLabelRecreateInDebuggerEnabled(); + + + // Listener + this.btnLabelsToDebugger.addActionListener( this ); // Fenstergroesse und -position @@ -214,6 +227,19 @@ protected void codeToEmuChanged( boolean state ) { this.btnLabelsToDebugger.setEnabled( state ); this.btnLabelsToReass.setEnabled( state ); + updSuppressLabelRecreateInDebuggerEnabled(); + } + + + @Override + protected boolean doAction( EventObject e ) + { + boolean rv = super.doAction( e ); + if( !rv && (e.getSource() == this.btnLabelsToDebugger) ) { + updSuppressLabelRecreateInDebuggerEnabled(); + rv = true; + } + return rv; } @@ -231,18 +257,20 @@ else if( this.btnSyntaxRobotron.isSelected() ) { this.appliedOptions = new PrgOptions(); this.appliedOptions.setAsmSyntax( syntax ); this.appliedOptions.setAllowUndocInst( - this.btnAllowUndocInst.isSelected() ); + this.btnAllowUndocInst.isSelected() ); this.appliedOptions.setLabelsCaseSensitive( - this.btnLabelsCaseSensitive.isSelected() ); + this.btnLabelsCaseSensitive.isSelected() ); this.appliedOptions.setPrintLabels( this.btnPrintLabels.isSelected() ); this.appliedOptions.setLabelsToDebugger( - this.btnLabelsToDebugger.isSelected() ); + this.btnLabelsToDebugger.isSelected() ); + this.appliedOptions.setSuppressLabelRecreateInDebugger( + this.btnSuppressLabelRecreateInDebugger.isSelected() ); this.appliedOptions.setLabelsToReassembler( - this.btnLabelsToReass.isSelected() ); + this.btnLabelsToReass.isSelected() ); this.appliedOptions.setFormatSource( - this.btnFormatSource.isSelected() ); + this.btnFormatSource.isSelected() ); this.appliedOptions.setWarnNonAsciiChars( - this.btnWarnNonAsciiChars.isSelected() ); + this.btnWarnNonAsciiChars.isSelected() ); try { applyCodeDestOptionsTo( this.appliedOptions ); doClose(); @@ -255,5 +283,15 @@ else if( this.btnSyntaxRobotron.isSelected() ) { showErrorDlg( this, ex.getMessage() ); } } + + + /* --- private Methoden --- */ + + private void updSuppressLabelRecreateInDebuggerEnabled() + { + this.btnSuppressLabelRecreateInDebugger.setEnabled( + this.btnLabelsToDebugger.isEnabled() + && this.btnLabelsToDebugger.isSelected() ); + } } diff --git a/src/jkcemu/programming/assembler/AsmStackEntry.java b/src/jkcemu/programming/assembler/AsmStackEntry.java new file mode 100644 index 0000000..8a2f7c0 --- /dev/null +++ b/src/jkcemu/programming/assembler/AsmStackEntry.java @@ -0,0 +1,50 @@ +/* + * (c) 2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Eintrag im Assembler-Stack fuer bedingte Assemblierung + */ + +package jkcemu.programming.assembler; + +import java.lang.*; +import jkcemu.programming.PrgException; + + +public class AsmStackEntry +{ + private int lineNum; + private boolean asmEnabled; + private boolean elseProcessed; + + + public AsmStackEntry( int lineNum, boolean asmEnabled ) + { + this.lineNum = lineNum; + this.asmEnabled = asmEnabled; + this.elseProcessed = false; + } + + + public int getLineNum() + { + return this.lineNum; + } + + + public boolean isAssemblingEnabled() + { + return this.asmEnabled; + } + + + public void processELSE() throws PrgException + { + if( this.elseProcessed ) { + throw new PrgException( "ELSE ohne zugeh\u00F6riges IF..." ); + } + this.asmEnabled = !this.asmEnabled; + this.elseProcessed = true; + } +} diff --git a/src/jkcemu/programming/assembler/AsmThread.java b/src/jkcemu/programming/assembler/AsmThread.java index dc9dd3f..5ec3f3e 100644 --- a/src/jkcemu/programming/assembler/AsmThread.java +++ b/src/jkcemu/programming/assembler/AsmThread.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -9,7 +9,7 @@ package jkcemu.programming.assembler; import java.awt.EventQueue; -import java.io.IOException; +import java.io.*; import java.lang.*; import java.util.*; import jkcemu.Main; @@ -36,12 +36,19 @@ public AsmThread( this.assembler = new Z80Assembler( editText.getText(), null, + editText.getFile(), options, PrgLogger.createLogger( logOut ), true ); } + public Collection getPrgSources() + { + return this.assembler.getPrgSources(); + } + + /* --- ueberschriebene Methoden --- */ @Override @@ -66,7 +73,7 @@ public boolean execute() throws IOException if( this.options.getCodeToEmu() || this.options.getForceRun() ) { byte[] code = this.assembler.getCreatedCode(); if( code != null ) { - writeCodeToEmu( this.assembler ); + writeCodeToEmu( this.assembler, true ); if( this.options.getLabelsToDebugger() ) { labelsToDebugger( this.options.getCodeToSecondSystem() ); } @@ -112,7 +119,10 @@ private void labelsToDebugger( boolean secondSys ) debugFrm = screenFrm.openPrimaryDebugger(); } if( debugFrm != null ) { - debugFrm.setLabels( labels ); + debugFrm.setLabels( + labels, + this.options.getSuppressLabelRecreateInDebugger(), + this.options.getLabelsCaseSensitive() ); } } } @@ -152,4 +162,3 @@ private void labelsToReass( boolean secondSys ) } } } - diff --git a/src/jkcemu/programming/assembler/CmdLineAssembler.java b/src/jkcemu/programming/assembler/CmdLineAssembler.java index 3a4e92c..a07e5a4 100644 --- a/src/jkcemu/programming/assembler/CmdLineAssembler.java +++ b/src/jkcemu/programming/assembler/CmdLineAssembler.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -10,6 +10,8 @@ import java.io.*; import java.lang.*; +import java.text.*; +import java.util.*; import jkcemu.Main; import jkcemu.base.*; import jkcemu.programming.*; @@ -24,20 +26,26 @@ public class CmdLineAssembler " java -jar jkcemu.jar --assembler [Optionen] ", "", "Optionen:", - " -h diese Hilfe anzeigen", - " -l Markentabelle ausgeben", - " -o Ausgabedatei festlegen", - " -9 Ausgabedatei f\u00FCr Z9001, KC85/1 und KC87" + " -h diese Hilfe anzeigen", + " -f Kommandozeile aus Datei lesen", + " -l Markentabelle ausgeben", + " -o Ausgabedatei festlegen", + " -9 Ausgabedatei f\u00FCr Z9001, KC85/1 und KC87" + " erzeugen", - " -C Gro\u00DF-/Kleinschreibung bei Marken beachten", - " -U undokumentierte Befehle erlauben", - " -R nur Robotron-Syntax erlauben", - " -Z nur Zilog-Syntax erlauben", + " -C Gro\u00DF-/Kleinschreibung bei Marken beachten", + " -D Marke mit dem Wert -1 (alle Bits gesetzt)" + + " definieren", + " -D Marke mit angegebenen Wert definieren", + " -U undokumentierte Befehle erlauben", + " -R nur Robotron-Syntax erlauben", + " -Z nur Zilog-Syntax erlauben", "" }; public static boolean execute( String[] args, int argIdx ) { + java.util.List> labels = new ArrayList<>(); + boolean status = false; boolean caseFlag = false; boolean helpFlag = false; @@ -48,74 +56,122 @@ public static boolean execute( String[] args, int argIdx ) boolean forZ9001 = false; String outFileName = null; String srcFileName = null; + + CmdLineArgIterator backIter = null; + CmdLineArgIterator iter = CmdLineArgIterator.createFromStringArray( + args, + argIdx ); try { - while( argIdx < args.length ) { - String arg = args[ argIdx++ ]; - if( arg != null ) { - if( !arg.isEmpty() ) { - if( arg.charAt( 0 ) == '-' ) { - int len = arg.length(); - if( len < 2 ) { - throwWrongCmdLine(); - } - int pos = 1; - while( pos < len ) { - char ch = arg.charAt( pos++ ); - switch( ch ) { - case 'h': - case 'H': - helpFlag = true; - break; - case 'C': - caseFlag = true; - break; - case 'R': - robotronFlag = true; - break; - case 'Z': - zilogFlag = true; - break; - case 'U': - undocFlag = true; - break; - case 'l': - listFlag = true; - break; - case 'o': - if( outFileName != null ) { + String arg = iter.next(); + while( arg != null ) { + if( !arg.isEmpty() ) { + if( arg.charAt( 0 ) == '-' ) { + int len = arg.length(); + if( len < 2 ) { + throwWrongCmdLine(); + } + int pos = 1; + while( pos < len ) { + char ch = arg.charAt( pos++ ); + switch( ch ) { + case 'f': + { + if( backIter != null ) { + throw new IOException( + "Option -f in der Datei nicht erlaubt" ); + } + String fileName = null; + if( pos < len ) { + fileName = arg.substring( pos ); + pos = len; // Schleife verlassen + } else { + fileName = iter.next(); + } + if( fileName == null ) { throwWrongCmdLine(); } + try { + backIter = iter; + iter = CmdLineArgIterator.createFromReader( + new FileReader( fileName ) ); + } + catch( IOException ex ) { + iter = backIter; + backIter = null; + } + } + break; + case 'h': + case 'H': + helpFlag = true; + break; + case 'C': + caseFlag = true; + break; + case 'D': + { + String labelText = null; if( pos < len ) { - outFileName = arg.substring( pos ); + labelText = arg.substring( pos ); + pos = len; // Schleife verlassen } else { - if( argIdx < args.length ) { - outFileName = args[ argIdx++ ]; - } else { - throwWrongCmdLine(); - } + labelText = iter.next(); } - pos = len; // Schleife verlassen - break; - case '9': - forZ9001 = true; - break; - default: - throw new IOException( + parseLabel( labels, labelText ); + } + break; + case 'R': + robotronFlag = true; + break; + case 'Z': + zilogFlag = true; + break; + case 'U': + undocFlag = true; + break; + case 'l': + listFlag = true; + break; + case 'o': + if( outFileName != null ) { + throwWrongCmdLine(); + } + if( pos < len ) { + outFileName = arg.substring( pos ); + pos = len; // Schleife verlassen + } else { + outFileName = iter.next(); + } + if( outFileName == null ) { + throwWrongCmdLine(); + } + break; + case '9': + forZ9001 = true; + break; + default: + throw new IOException( String.format( "Unbekannte Option \'%c\'", ch ) ); - } - } - } else { - if( srcFileName != null ) { - throwWrongCmdLine(); } - srcFileName = arg; } + } else { + if( srcFileName != null ) { + throwWrongCmdLine(); + } + srcFileName = arg; } } + arg = iter.next(); + if( (arg == null) && (backIter != null) ) { + EmuUtil.doClose( iter ); + iter = backIter; + backIter = null; + arg = iter.next(); + } } if( helpFlag ) { EmuUtil.printlnOut(); - EmuUtil.printlnOut( Main.VERSION + " Assembler" ); + EmuUtil.printlnOut( Main.APPINFO + " Assembler" ); for( String s : usageLines ) { EmuUtil.printlnOut( s ); } @@ -145,12 +201,17 @@ public static boolean execute( String[] args, int argIdx ) options.setPrintLabels( listFlag ); // Assembler starten - status = assemble( srcFile, outFileName, forZ9001, options ); + status = assemble( + srcFile, + outFileName, + forZ9001, + labels, + options ); } } catch( IOException ex ) { EmuUtil.printlnErr(); - EmuUtil.printlnErr( Main.VERSION + " Assembler:" ); + EmuUtil.printlnErr( Main.APPINFO + " Assembler:" ); String msg = ex.getMessage(); if( msg != null ) { if( !msg.isEmpty() ) { @@ -162,6 +223,9 @@ public static boolean execute( String[] args, int argIdx ) } status = false; } + finally { + EmuUtil.doClose( iter ); + } return status; } @@ -169,10 +233,11 @@ public static boolean execute( String[] args, int argIdx ) /* --- private Methoden --- */ private static boolean assemble( - File srcFile, - String outFileName, - boolean forZ9001, - PrgOptions options ) + File srcFile, + String outFileName, + boolean forZ9001, + java.util.List> labels, + PrgOptions options ) { boolean status = false; try { @@ -201,12 +266,21 @@ private static boolean assemble( throw new IOException( "Quelltext- und Ausgabedatei sind identisch" ); } options.setCodeToFile( true, outFile ); - status = (new Z80Assembler( - EmuUtil.readTextFile( srcFile ), - srcFile.getName(), - options, - PrgLogger.createStandardLogger(), - false )).assemble( null, forZ9001 ); + Z80Assembler asm = new Z80Assembler( + null, + null, + srcFile, + options, + PrgLogger.createStandardLogger(), + false ); + for( Map.Entry label : labels ) { + String s = label.getKey(); + Integer v = label.getValue(); + if( !asm.addLabel( s, v != null ? v.intValue() : -1 ) ) { + throw new IOException( "Marke " + s + " bereits vorhanden" ); + } + } + status = asm.assemble( null, forZ9001 ); } catch( IOException ex ) { String msg = ex.getMessage(); @@ -220,9 +294,65 @@ private static boolean assemble( } + private static void parseLabel( + java.util.List> labels, + String text ) throws IOException + { + if( text == null ) { + throwWrongCmdLine(); + } + String labelName = text; + String valueText = null; + int pos = text.indexOf( '=' ); + if( pos >= 0 ) { + labelName = text.substring( 0, pos ); + valueText = text.substring( pos + 1 ); + } + if( labelName.isEmpty() ) { + throwWrongCmdLine(); + } + boolean status = AsmLabel.isIdentifierStart( text.charAt( 0 ) ); + if( status ) { + int len = labelName.length(); + if( len > 1 ) { + for( int i = 1; i < len; i++ ) { + if( !AsmLabel.isIdentifierPart( text.charAt( i ) ) ) { + status = false; + break; + } + } + } + } + if( !status ) { + throw new IOException( + labelName + ": Marke enth\u00E4lht ung\u00FCltige Zeichen" ); + } + int labelValue = -1; + if( valueText != null ) { + CharacterIterator iter = new StringCharacterIterator( valueText ); + if( iter.first() == CharacterIterator.DONE ) { + throw new IOException( + "Marke " + labelName + ": Wert fehlt" ); + } + try { + labelValue = ExprParser.parseNumber( iter ); + } + catch( PrgException ex ) { + throw new IOException( + "Marke " + labelName + ": " + ex.getMessage() ); + } + if( ExprParser.skipSpaces( iter ) != CharacterIterator.DONE ) { + throw new IOException( "Ung\u00FCltige Zahl bei Marke " + labelName ); + } + } + labels.add( new AbstractMap.SimpleImmutableEntry<>( + labelName, + new Integer( labelValue ) ) ); + } + + private static void throwWrongCmdLine() throws IOException { throw new IOException( "Kommandozeile fehlerhaft" ); } } - diff --git a/src/jkcemu/programming/assembler/ExprParser.java b/src/jkcemu/programming/assembler/ExprParser.java index 83f04f1..7c578b0 100644 --- a/src/jkcemu/programming/assembler/ExprParser.java +++ b/src/jkcemu/programming/assembler/ExprParser.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2009 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -9,86 +9,256 @@ package jkcemu.programming.assembler; import java.lang.*; -import java.util.Map; -import jkcemu.programming.PrgException; +import java.text.*; +import java.util.*; +import jkcemu.programming.*; public class ExprParser { - private String text; - private int len; - private int pos; + private static String[] sortedReservedWords = { + "AND", "EQ", "GE", "GT", "HIGH", "LE", "LOW", "LT", + "MOD", "NE", "NOT", "OR", "SHL", "SHR", "XOR" }; + + private CharacterIterator iter; + private int instBegAddr; private Map labels; private boolean checkLabels; private boolean labelsCaseSensitive; - public static int parse( + public static boolean isReservedWord( String text ) + { + return (Arrays.binarySearch( sortedReservedWords, text ) >= 0); + } + + + public static Integer parse( String text, + int instBegAddr, Map labels, boolean checkLabels, boolean labelsCaseSensitive ) throws PrgException { return (new ExprParser( - text, + new StringCharacterIterator( text ), + instBegAddr, labels, checkLabels, labelsCaseSensitive )).parse(); } - /* --- private Methoden --- */ + public static int parseNumber( CharacterIterator iter ) throws PrgException + { + int value = 0; + StringBuilder buf = new StringBuilder(); + boolean enclosedHex = checkAndParseToken( iter, "X\'" ); + char ch = skipSpaces( iter ); + while( ((ch >= '0') && (ch <= '9')) + || ((ch >= 'A') && (ch <= 'F')) + || ((ch >= 'a') && (ch <= 'f')) ) + { + buf.append( (char) ch ); + ch = iter.next(); + } + if( enclosedHex || (ch == 'H') || (ch == 'h') ) { + if( enclosedHex && (ch != '\'') ) { + throw new PrgException( + "\' als Ende der Hexadezimalzahl erwartet" ); + } + ch = iter.next(); + try { + value = Integer.parseInt( buf.toString(), 16 ); + } + catch( NumberFormatException ex ) { + throw new PrgException( + buf.toString() + ": Ung\u00FCltige Hexadezimalzahl" ); + } + } + else if( (ch == 'O') || (ch == 'o') || (ch == 'Q') || (ch == 'q') ) { + ch = iter.next(); + try { + value = Integer.parseInt( buf.toString(), 8 ); + } + catch( NumberFormatException ex ) { + throw new PrgException( + buf.toString() + ": Ung\u00FCltige Oktalzahl" ); + } + } else { + boolean done = false; + int len = buf.length(); + if( len > 1 ) { + ch = buf.charAt( len - 1 ); + if( (ch == 'B') || (ch == 'b') ) { + done = true; + buf.setLength( len - 1 ); + try { + value = Integer.parseInt( buf.toString(), 2 ); + } + catch( NumberFormatException ex ) { + throw new PrgException( + buf.toString() + ": Ung\u00FCltige Bin\u00E4rzahl" ); + } + } + } + if( !done ) { + try { + value = Integer.parseInt( buf.toString() ); + } + catch( NumberFormatException ex ) { + throw new PrgException( + buf.toString() + ": Ung\u00FCltige Zahl" ); + } + } + } + return value; + } + + + public static char skipSpaces( CharacterIterator iter ) + { + char ch = iter.current(); + while( (ch != CharacterIterator.DONE) + && PrgUtil.isWhitespace( ch ) ) + { + ch = iter.next(); + } + return ch; + } + + + /* --- Konstruktor --- */ private ExprParser( - String text, + CharacterIterator iter, + int instBegAddr, Map labels, boolean checkLabels, boolean labelsCaseSensitive ) { - this.text = (text != null ? text : ""); - this.len = text.length(); - this.pos = 0; + this.iter = iter; + this.instBegAddr = instBegAddr; this.labels = labels; this.checkLabels = checkLabels; this.labelsCaseSensitive = labelsCaseSensitive; } - private int parse() throws PrgException + /* --- private Methoden --- */ + + private boolean checkAndParseToken( String token ) + { + return checkAndParseToken( this.iter, token ); + } + + + private static boolean checkAndParseToken( + CharacterIterator iter, + String token ) + { + boolean rv = true; + int len = token.length(); + if( len > 0 ) { + char chSrc = skipSpaces( iter ); + char chToken = CharacterIterator.DONE; + int idx = iter.getIndex(); + for( int i = 0; i < len; i++ ) { + chToken = token.charAt( i ); + if( Character.toUpperCase( chSrc ) + != Character.toUpperCase( chToken ) ) + { + rv = false; + break; + } + chSrc = iter.next(); + } + /* + * Wenn das letzte Zeichen im Token ein Zeichen + * fuer einen Bezeichner war, + * darf das naechste Zeichen im Eingabestrom auch kein Zeichen + * eines Bezeichners sein. + */ + if( rv && AsmLabel.isIdentifierPart( chToken ) ) { + if( AsmLabel.isIdentifierPart( chSrc ) ) { + rv = false; + } + } + /* + * Wenn das letzte Zeichen im Token eine spitze Klammer war + * darf das naechste Zeichen im Eingabestrom keine spitze Klammer + * und auch kein Gleichheitszeichen sein. + */ + if( rv && ((chToken == '<') || (chToken == '>')) ) { + char ch = iter.current(); + if( (ch == '<') || (ch == '>') || (ch == '=') ) { + rv = false; + } + } + if( !rv ) { + iter.setIndex( idx ); + } + } + return rv; + } + + + private Integer parse() throws PrgException { - this.pos = 0; - int value = parseExpr(); - if( this.pos < this.len ) { - throw new PrgException( "\'" + this.text.charAt( this.pos ) + Integer value = parseExpr(); + char ch = skipSpaces(); + if( ch != CharacterIterator.DONE ) { + throw new PrgException( "\'" + ch + "\': Unerwartetes Zeichen hinter Ausdruck" ); } return value; } - private int parseExpr() throws PrgException + private Integer parseExpr() throws PrgException { - int value = 0; - int ch = 0; - if( this.pos < this.len ) { - ch = this.text.charAt( this.pos ); - if( (ch == '-') || (ch == '+') ) - this.pos++; + Integer value = parseXorExpr(); + for(;;) { + if( checkAndParseToken( "OR" ) ) { + Integer v2 = parseXorExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() | v2.intValue() ); + } + } else { + break; + } } - value = parseUnaryExpr(); - if( ch == '-' ) { - value = -value; + return value; + } + + + private Integer parseXorExpr() throws PrgException + { + Integer value = parseAndExpr(); + for(;;) { + if( checkAndParseToken( "XOR" ) ) { + Integer v2 = parseAndExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() ^ v2.intValue() ); + } + } else { + break; + } } - while( this.pos < this.len ) { - ch = this.text.charAt( this.pos ); - if( ch == '+' ) { - this.pos++; - value += parseUnaryExpr(); - } - else if( ch == '-' ) { - this.pos++; - value -= parseUnaryExpr(); + return value; + } + + + private Integer parseAndExpr() throws PrgException + { + Integer value = parseEqualityExpr(); + for(;;) { + if( checkAndParseToken( "AND" ) ) { + Integer v2 = parseEqualityExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() & v2.intValue() ); + } } else { break; } @@ -97,52 +267,213 @@ else if( ch == '-' ) { } - private int parseUnaryExpr() throws PrgException + private Integer parseEqualityExpr() throws PrgException { - int value = 0; - if( this.pos >= this.len ) { - throw new PrgException( "Unerwartetes Ende des Arguments" ); + Integer value = parseRelationalExpr(); + for(;;) { + if( checkAndParseToken( "=" ) || checkAndParseToken( "EQ" ) ) { + Integer v2 = parseRelationalExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() == v2.intValue() ? -1 : 0 ); + } + } + else if( checkAndParseToken( "<>" ) || checkAndParseToken( "NE" ) ) { + Integer v2 = parseRelationalExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() != v2.intValue() ? 0 : -1 ); + } + } else { + break; + } } - if( this.text.regionMatches( true, this.pos, "LOW(", 0, 4 ) ) { - this.pos += 4; - value = parseExpr() & 0xFF; + return value; + } + + + private Integer parseRelationalExpr() throws PrgException + { + Integer value = parseShiftExpr(); + for(;;) { + if( checkAndParseToken( "<=" ) || checkAndParseToken( "LE" ) ) { + Integer v2 = parseShiftExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() <= v2.intValue() ? -1 : 0 ); + } + } + else if( checkAndParseToken( "<" ) || checkAndParseToken( "LT" ) ) { + Integer v2 = parseShiftExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() < v2.intValue() ? -1 : 0 ); + } + } + else if( checkAndParseToken( ">=" ) || checkAndParseToken( "GE" ) ) { + Integer v2 = parseShiftExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() >= v2.intValue() ? -1 : 0 ); + } + } + else if( checkAndParseToken( ">" ) || checkAndParseToken( "GT" ) ) { + Integer v2 = parseShiftExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() > v2.intValue() ? -1 : 0 ); + } + } else { + break; + } + } + return value; + } + + + private Integer parseShiftExpr() throws PrgException + { + Integer value = parseAddExpr(); + for(;;) { + if( checkAndParseToken( "<<" ) || checkAndParseToken( "SHL" ) ) { + Integer v2 = parseAddExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() << v2.intValue() ); + } + } + else if( checkAndParseToken( ">>" ) || checkAndParseToken( "SHR" ) ) { + Integer v2 = parseAddExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() >> v2.intValue() ); + } + } else { + break; + } + } + return value; + } + + + private Integer parseAddExpr() throws PrgException + { + Integer value = parseMulExpr(); + for(;;) { + if( checkAndParseToken( "+" ) ) { + Integer v2 = parseMulExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() + v2.intValue() ); + } + } else if( checkAndParseToken( "-" ) ) { + Integer v2 = parseMulExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() - v2.intValue() ); + } + } else { + break; + } + } + return value; + } + + + private Integer parseMulExpr() throws PrgException + { + Integer value = parseUnaryExpr(); + for(;;) { + if( checkAndParseToken( "*" ) ) { + Integer v2 = parseUnaryExpr(); + if( (value != null) && (v2 != null) ) { + value = new Integer( value.intValue() * v2.intValue() ); + } + } + else if( checkAndParseToken( "/" ) ) { + Integer v2 = parseUnaryExpr(); + if( (value != null) && (v2 != null) ) { + if( v2.intValue() == 0 ) { + throw new PrgException( "Division durch 0" ); + } + value = new Integer( value.intValue() / v2.intValue() ); + } + + + value /= parseUnaryExpr(); + } + else if( checkAndParseToken( "MOD" ) ) { + Integer v2 = parseUnaryExpr(); + if( (value != null) && (v2 != null) ) { + if( v2.intValue() == 0 ) { + throw new PrgException( "Modulo 0" ); + } + value = new Integer( value.intValue() % v2.intValue() ); + } + } else { + break; + } + } + return value; + } + + + private Integer parseUnaryExpr() throws PrgException + { + Integer value = null; + if( checkAndParseToken( "+" ) ) { + value = parsePrimExpr(); + } else if( checkAndParseToken( "-" ) ) { + Integer v2 = parsePrimExpr(); + if( v2 != null ) { + value = new Integer( -v2.intValue() ); + } + } else if( checkAndParseToken( "LOW" ) ) { + parseToken( '(' ); + Integer v2 = parseExpr(); + if( v2 != null ) { + value = new Integer( v2.intValue() & 0xFF ); + } parseToken( ')' ); } - else if( this.text.regionMatches( true, this.pos, "L(", 0, 2 ) ) { - this.pos += 2; - value = parseExpr() & 0xFF; + else if( checkAndParseToken( "HIGH" ) ) { + parseToken( '(' ); + Integer v2 = parseExpr(); + if( v2 != null ) { + value = new Integer( (v2.intValue() >> 8) & 0xFF ); + } parseToken( ')' ); } - else if( this.text.regionMatches( true, this.pos, "HIGH(", 0, 5 ) ) { - this.pos += 5; - value = (parseExpr() >> 8) & 0xFF; + else if( checkAndParseToken( "NOT" ) ) { + Integer v2 = parseExpr(); + if( v2 != null ) { + value = new Integer( ~v2.intValue() ); + } parseToken( ')' ); } - else if( this.text.regionMatches( true, this.pos, "H(", 0, 2 ) ) { - this.pos += 2; - value = (parseExpr() >> 8) & 0xFF; + else if( checkAndParseToken( "(" ) ) { + value = parseExpr(); parseToken( ')' ); + } else { + value = parsePrimExpr(); } - else if( this.text.regionMatches( this.pos, "\'", 0, 1 ) ) { - this.pos++; - if( this.pos + 1 >= this.len ) { - throw new PrgException( "Ung\u00FCltiges Zeichenliteral" ); + return value; + } + + + private Integer parsePrimExpr() throws PrgException + { + Integer value = null; + if( checkAndParseToken( "$" ) ) { + value = this.instBegAddr; + } else if( checkAndParseToken( "\'" ) ) { + char ch = this.iter.current(); + if( ch == CharacterIterator.DONE ) { + throw new PrgException( "Unerwartetes Ende des Zeichenliterals" ); } - value = this.text.charAt( this.pos++ ); - char ch = this.text.charAt( this.pos++ ); + value = new Integer( ch ); + ch = this.iter.next(); if( ch != '\'' ) { throw new PrgException( - "\'" + ch + "\': Unerwartetes Zeichen im Zeichenliteral" ); + "\' als Ende des Zeichenliterals erwartet" ); } + this.iter.next(); } else { - char ch = this.text.charAt( this.pos ); + char ch = skipSpaces(); if( (ch >= '0') && (ch <= '9') ) { - value = parseNumber(); + value = parseNumber( this.iter ); } - else if( ((ch >= 'A') && (ch <= 'Z')) - || ((ch >= 'a') && (ch <= 'z')) - || (ch == '_') ) - { + else if( AsmLabel.isIdentifierStart( ch ) ) { value = parseLabel(); } else { throw new PrgException( @@ -153,32 +484,23 @@ else if( ((ch >= 'A') && (ch <= 'Z')) } - private int parseLabel() throws PrgException + private Integer parseLabel() throws PrgException { - int value = 0; - - StringBuilder buf = null; + Integer value = null; + StringBuilder buf = null; if( this.labels != null ) { buf = new StringBuilder(); } - while( this.pos < this.len ) { - char ch = this.text.charAt( this.pos ); - if( ((ch >= 'A') && (ch <= 'Z')) - || ((ch >= 'a') && (ch <= 'z')) - || ((ch >= '0') && (ch <= '9')) - || (ch == '_') || (ch == '$') ) - { - this.pos++; - if( buf != null ) { - if( this.labelsCaseSensitive ) { - buf.append( ch ); - } else { - buf.append( Character.toUpperCase( ch ) ); - } + char ch = skipSpaces(); + while( AsmLabel.isIdentifierPart( ch ) ) { + if( buf != null ) { + if( this.labelsCaseSensitive ) { + buf.append( ch ); + } else { + buf.append( Character.toUpperCase( ch ) ); } - } else { - break; } + ch = this.iter.next(); } if( buf != null ) { String labelText = buf.toString(); @@ -194,97 +516,33 @@ private int parseLabel() throws PrgException } AsmLabel label = this.labels.get( labelText ); if( label != null ) { - value = label.getLabelValue(); + Object o = label.getLabelValue(); + if( o != null ) { + if( o instanceof Integer ) { + value = (Integer) o; + } + } } else { - if( this.checkLabels ) + if( this.checkLabels ) { throw new PrgException( buf.toString() + ": Unbekannte Marke" ); + } } } return value; } - private int parseNumber() throws PrgException + private void parseToken( char token ) throws PrgException { - int value = 0; - int ch = 0; - - StringBuilder buf = new StringBuilder(); - while( this.pos < this.len ) { - ch = this.text.charAt( this.pos ); - if( ((ch >= '0') && (ch <= '9')) - || ((ch >= 'A') && (ch <= 'F')) - || ((ch >= 'a') && (ch <= 'f')) ) - { - this.pos++; - buf.append( (char) ch ); - } else { - break; - } - } - ch = 0; - if( this.pos < this.len ) { - ch = this.text.charAt( this.pos ); - } - if( (ch == 'O') || (ch == 'o') || (ch == 'Q') || (ch == 'q') ) { - this.pos++; - try { - value = Integer.parseInt( buf.toString(), 8 ); - } - catch( NumberFormatException ex ) { - throw new PrgException( - buf.toString() + ": Ung\u00FCltige Oktalzahl" ); - } - } - else if( (ch == 'H') || (ch == 'h') ) { - this.pos++; - try { - value = Integer.parseInt( buf.toString(), 16 ); - } - catch( NumberFormatException ex ) { - throw new PrgException( - buf.toString() + ": Ung\u00FCltige Hexadezimalzahl" ); - } - } else { - boolean done = false; - int len = buf.length(); - if( len > 1 ) { - ch = buf.charAt( len - 1 ); - if( (ch == 'B') || (ch == 'b') ) { - done = true; - buf.setLength( len - 1 ); - try { - value = Integer.parseInt( buf.toString(), 2 ); - } - catch( NumberFormatException ex ) { - throw new PrgException( - buf.toString() + ": Ung\u00FCltige Bin\u00E4rzahl" ); - } - } - } - if( !done ) { - try { - value = Integer.parseInt( buf.toString() ); - } - catch( NumberFormatException ex ) { - throw new PrgException( - buf.toString() + ": Ung\u00FCltige Zahl" ); - } - } + if( skipSpaces() != token ) { + throw new PrgException( "\'" + token + "\' erwartet" ); } - return value; + iter.next(); } - private void parseToken( char token ) throws PrgException + private char skipSpaces() { - if( this.pos < this.len ) { - if( this.text.charAt( this.pos ) == token ) { - this.pos++; - return; - } - } - throw new PrgException( "\'" + token + "\' erwartet" ); + return skipSpaces( this.iter ); } } - diff --git a/src/jkcemu/programming/assembler/Z80Assembler.java b/src/jkcemu/programming/assembler/Z80Assembler.java index 620d2e6..d8630f6 100644 --- a/src/jkcemu/programming/assembler/Z80Assembler.java +++ b/src/jkcemu/programming/assembler/Z80Assembler.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -21,10 +21,41 @@ public class Z80Assembler { public static enum Syntax { ALL, ZILOG_ONLY, ROBOTRON_ONLY }; - private String srcText; - private String srcName; + private static String[] sortedReservedWords = { + "ADC", "ADD", "AND", "BINCLUDE", "BIT", + "CALL", "CCF", "CPD", "CPDR", "CPI", "CPIR", "CPL", "CPU", + "DA", "DAA", "DB", "DEC", "DEFA", "DEFB", "DEFH", + "DEFM", "DEFS", "DEFW", + "DFB", "DFH", "DFS", "DFW", "DI", "DJNZ", "DW", + "EI", "ELSE", "END", "ENDIF", "ENT", "EQU", "EX", "EXX", + "HALT", "HEX", + "IF1", "IF2", "IFDEF", "IFE", "IFF", "IFNDEF", "IM", + "IN", "INC", "INCLUDE", "IND", "INDR", "INI", "INIR", + "JP", "JR", + "LD", "LDD", "LDDR", "LDI", "LDIR", + "NEG", "NOP", + "OR", "ORG", "OTDR", "OTIR", "OUT", "OUTD", "OUTI", + "POP", "PUSH", + "RES", "RET", "RETI", "RETN", "RL", "RLA", "RLC", "RLCA", "RLD", + "RR", "RRA", "RRC", "RRCA", "RRD", "RST", + "SBC", "SCF", "SET", "SLA", "SRA", "SRL", "SUB", + "XOR", "Z80" }; + + private static String[] sortedReservedRobotronWords = { + "CAC", "CAM", "CANC", "CANZ", "CAP", "CAPE", "CAPO", "CAZ", "CMP", + "EXAF", "INF", "JMP", "JPC", "JPM", "JPNC", "JPNZ", "JPP", + "JPPE", "JPPO", "JPZ", "JRC", "JRNC", "JRNZ", "JRZ", + "RC", "RM", "RNC", "RNZ", "RP", "RPE", "RPO", "RZ" }; + + private static String BUILT_IN_LABEL = "__JKCEMU__"; + + private PrgSource curSource; + private PrgSource mainSource; private PrgOptions options; private PrgLogger logger; + private Stack stack; + private Map file2Bytes; + private Map file2Source; private Map labels; private AsmLabel[] sortedLabels; private StringBuilder srcOut; @@ -33,14 +64,16 @@ public static enum Syntax { ALL, ZILOG_ONLY, ROBOTRON_ONLY }; private boolean addrOverflow; private boolean endReached; private boolean interactive; + private boolean orgOverlapped; private boolean relJumpsTooLong; + private boolean suppressLineAddr; private boolean status; private volatile boolean execEnabled; private Integer entryAddr; private int begAddr; private int endAddr; private int curAddr; - private int curLineNum; + private int instBegAddr; private int passNum; private int errCnt; @@ -48,38 +81,81 @@ public static enum Syntax { ALL, ZILOG_ONLY, ROBOTRON_ONLY }; public Z80Assembler( String srcText, String srcName, + File srcFile, PrgOptions options, PrgLogger logger, boolean interactive ) { - this.srcText = srcText; - this.srcName = srcName; - this.options = options; - this.logger = logger; - this.interactive = interactive; - this.labels = new Hashtable(); - this.sortedLabels = null; - this.srcOut = null; - this.codeBuf = null; - this.codeOut = null; - this.addrOverflow = false; - this.endReached = false; - this.relJumpsTooLong = false; - this.status = true; - this.execEnabled = true; - this.entryAddr = null; - this.begAddr = -1; - this.endAddr = -1; - this.curAddr = 0; - this.curLineNum = 0; - this.passNum = 0; - this.errCnt = 0; - if( this.options.getFormatSource() && (this.srcText != null) ) { + this.curSource = null; + this.mainSource = null; + this.options = options; + this.logger = logger; + this.interactive = interactive; + this.stack = new Stack<>(); + this.file2Bytes = new HashMap<>(); + this.file2Source = new HashMap<>(); + this.labels = new HashMap<>(); + this.sortedLabels = null; + this.srcOut = null; + this.codeBuf = null; + this.codeOut = null; + this.addrOverflow = false; + this.endReached = false; + this.orgOverlapped = false; + this.relJumpsTooLong = false; + this.suppressLineAddr = false; + this.status = true; + this.execEnabled = true; + this.entryAddr = null; + this.begAddr = -1; + this.endAddr = -1; + this.curAddr = 0; + this.instBegAddr = 0; + this.passNum = 0; + this.errCnt = 0; + if( this.options.getFormatSource() && (srcText != null) ) { this.srcOut = new StringBuilder( Math.max( srcText.length(), 16 ) ); } if( this.options.getCreateCode() ) { this.codeBuf = new ByteArrayOutputStream( 0x8000 ); } + + // vordefinierte Marke + this.labels.put( BUILT_IN_LABEL, new AsmLabel( BUILT_IN_LABEL, 1 ) ); + + // Quelltext oeffnen + if( srcText != null ) { + this.mainSource = PrgSource.readText( srcText, srcName, srcFile ); + } else { + if( srcFile != null ) { + try { + this.mainSource = PrgSource.readFile( srcFile ); + } + catch( IOException ex ) { + String msg = ex.getMessage(); + if( msg != null ) { + if( msg.trim().isEmpty() ) { + msg = null; + } + } + if( msg == null ) { + msg = "Datei kann nicht ge\u00F6ffnet werden"; + } + appendToErrLog( srcFile.getPath() + ": " + msg ); + } + } + } + } + + + public boolean addLabel( String labelName, int value ) + { + if( !options.getLabelsCaseSensitive() ) { + labelName = labelName.toUpperCase(); + } + return (this.labels.put( + labelName, + new AsmLabel( labelName, value ) ) == null); } @@ -91,11 +167,17 @@ public boolean assemble( this.passNum = 1; this.execEnabled = true; this.endReached = false; + this.curSource = this.mainSource; try { parseAsm(); + computeMissingLabelValues(); if( this.execEnabled && this.status ) { + if( this.mainSource != null ) { + this.mainSource.reset(); + } this.passNum = 2; this.endReached = false; + this.curSource = this.mainSource; parseAsm(); if( this.codeBuf != null ) { this.codeBuf.close(); @@ -161,36 +243,123 @@ public String getFormattedSourceText() } + public Integer getLabelValue( String labelName ) + { + Integer rv = null; + if( labelName != null ) { + AsmLabel label = this.labels.get( labelName ); + if( label != null ) { + Object o = label.getLabelValue(); + if( o != null ) { + if( o instanceof Integer ) { + rv = (Integer) o; + } + } + } + } + return rv; + } + + + public boolean getOrgOverlapped() + { + return this.orgOverlapped; + } + + + public PrgOptions getOptions() + { + return this.options; + } + + + public Collection getPrgSources() + { + java.util.List sources + = new ArrayList<>( this.file2Source.size() + 1 ); + if( this.mainSource != null ) { + sources.add( this.mainSource ); + } + sources.addAll( this.file2Source.values() ); + return sources; + } + + public boolean getRelJumpsTooLong() { return this.relJumpsTooLong; } + /* + * Sortierte Ausgabe der Markentabelle, + * Eingebaute Marken sind nicht enthalten. + */ public AsmLabel[] getSortedLabels() { AsmLabel[] rv = this.sortedLabels; if( rv == null ) { - try { - Collection c = this.labels.values(); - if( c != null ) { - int n = c.size(); - if( n > 0 ) { - rv = c.toArray( new AsmLabel[ n ] ); - if( rv != null ) { - Arrays.sort( rv ); + int nSrc = this.labels.size(); + if( nSrc > 0 ) { + Map labelMap = this.labels; + try { + labelMap = new HashMap<>( nSrc ); + labelMap.putAll( this.labels ); + labelMap.remove( BUILT_IN_LABEL ); + } + catch( UnsupportedOperationException + | ClassCastException + | IllegalArgumentException ex ) + { + labelMap = this.labels; + } + try { + Collection c = labelMap.values(); + if( c != null ) { + int nAry = c.size(); + if( nAry > 0 ) { + rv = c.toArray( new AsmLabel[ nAry ] ); + if( rv != null ) { + Arrays.sort( rv ); + } + } else { + rv = new AsmLabel[ 0 ]; } - } else { - rv = new AsmLabel[ 0 ]; } } + catch( ArrayStoreException ex ) {} + catch( ClassCastException ex ) {} + finally { + this.sortedLabels = rv; + } + } + } + return rv; + } + + + public boolean isReservedWord( String upperText ) + { + boolean rv = (Arrays.binarySearch( + sortedReservedWords, + upperText ) >= 0); + if( !rv ) { + Syntax syntax = this.options.getAsmSyntax(); + if( (syntax == Syntax.ALL) || (syntax == Syntax.ROBOTRON_ONLY) ) { + if( Arrays.binarySearch( + sortedReservedRobotronWords, + upperText ) >= 0 ) + { + rv = true; + } } - catch( ArrayStoreException ex ) {} - catch( ClassCastException ex ) {} - finally { - this.sortedLabels = rv; + if( (syntax == Syntax.ALL) || (syntax == Syntax.ZILOG_ONLY) ) { + rv |= upperText.equals( "CP" ); } } + if( this.options.getAllowUndocInst() ) { + rv |= upperText.equals( "SLL" ); + } return rv; } @@ -207,24 +376,24 @@ public void putWarning( String msg ) public void appendLineNumMsgToErrLog( String msg, String msgType ) { StringBuilder buf = new StringBuilder( 128 ); - if( this.curLineNum > 0 ) { - if( this.srcName != null ) { - buf.append( this.srcName ); - buf.append( (char) ':' ); - buf.append( this.curLineNum ); - if( msgType != null ) { - buf.append( ": " ); - buf.append( msgType ); + if( this.curSource != null ) { + int lineNum = this.curSource.getLineNum(); + if( lineNum > 0 ) { + String srcName = this.curSource.getName(); + if( srcName != null ) { + if( !srcName.isEmpty() ) { + buf.append( srcName ); + buf.append( ": " ); + } } - } else { if( msgType != null ) { buf.append( msgType ); buf.append( " in " ); } buf.append( "Zeile " ); - buf.append( this.curLineNum ); + buf.append( lineNum ); + buf.append( ": " ); } - buf.append( ": " ); } if( msg != null ) { buf.append( msg ); @@ -250,6 +419,15 @@ private void appendToOutLog( String text ) } + private void checkPrint16BitWarning( int value ) + { + if( (value < ~0x7FFF) || (value > 0xFFFF) ) { + putWarning( "Numerischer Wert au\u00DFerhalb 16-Bit-Bereich:" + + "Bits gehen verloren" ); + } + } + + private void checkAddr() throws PrgException { /* @@ -265,35 +443,127 @@ private void checkAddr() throws PrgException } + private void computeMissingLabelValues() + { + boolean computed = false; + boolean failed = false; + do { + for( AsmLabel label : this.labels.values() ) { + Object o = label.getLabelValue(); + if( o != null ) { + if( !(o instanceof Integer) ) { + String text = o.toString(); + if( text != null ) { + try { + Integer v = ExprParser.parse( + text, + this.instBegAddr, + this.labels, + false, + this.options.getLabelsCaseSensitive() ); + if( v != null ) { + label.setLabelValue( v ); + computed = true; + } else { + failed = true; + } + } + catch( PrgException ex ) {} + } + } + } + } + } while( computed && failed ); + } + + + private boolean isAssemblingEnabled() + { + boolean rv = true; + for( AsmStackEntry e : this.stack ) { + if( !e.isAssemblingEnabled() ) { + rv = false; + break; + } + } + return rv; + } + + private void parseAsm() throws IOException, TooManyErrorsException { - this.begAddr = -1; - this.endAddr = -1; - this.curAddr = 0; - this.curLineNum = 0; - if( this.srcText != null ) { - BufferedReader reader = new BufferedReader( - new StringReader( this.srcText ) ); - String line = reader.readLine(); - while( this.execEnabled && !this.endReached && (line != null) ) { - this.curLineNum++; + this.begAddr = -1; + this.endAddr = -1; + this.curAddr = 0; + this.instBegAddr = 0; + this.stack.clear(); + while( this.execEnabled + && !this.endReached + && (this.curSource != null) ) + { + String line = this.curSource.readLine(); + if( line != null ) { parseLine( line ); - line = reader.readLine(); + } else { + if( this.curSource != this.mainSource ) { + this.curSource = this.mainSource; + } else { + this.curSource = null; + } + } + } + if( !this.stack.isEmpty() ) { + try { + int lineNum = this.stack.peek().getLineNum(); + StringBuilder buf = new StringBuilder( 32 ); + buf.append( "Bedingung" ); + if( lineNum > 0 ) { + buf.append( " in Zeile " ); + buf.append( lineNum ); + } + buf.append( " nicht geschlossen (ENDIF fehlt)" ); + appendLineNumMsgToErrLog( buf.toString(), "Fehler" ); + this.status = false; + this.errCnt++; + } + catch( EmptyStackException ex ) {} + } + } + + + private int parseExpr( String text ) throws PrgException + { + int rv = 0; + Integer value = ExprParser.parse( + text, + this.instBegAddr, + this.labels, + this.passNum == 2, + this.options.getLabelsCaseSensitive() ); + if( value != null ) { + rv = value.intValue(); + } else { + if( this.passNum == 2 ) { + throw new PrgException( "Wert nicht ermittelbar" ); } } + return rv; } private void parseLine( String line ) throws IOException, TooManyErrorsException { + this.instBegAddr = this.curAddr; + this.suppressLineAddr = false; + String labelName = null; try { AsmLine asmLine = AsmLine.scanLine( this, line, this.options.getLabelsCaseSensitive() ); if( asmLine != null ) { - String labelName = asmLine.getLabel(); + labelName = asmLine.getLabel(); if( labelName != null ) { if( this.passNum == 1 ) { if( this.labels.containsKey( labelName ) ) { @@ -308,481 +578,531 @@ private void parseLine( String line ) } String instruction = asmLine.getInstruction(); if( instruction != null ) { - if( instruction.length() > 0 ) { - if( instruction.equals( "Z80" ) - || instruction.equals( ".Z80" ) ) + if( !instruction.isEmpty() ) { + if( instruction.equals( "IF" ) + || instruction.equals( "IFT" ) ) { - // leer - } - else if( instruction.equals( "ADD" ) ) { - parseADD( asmLine ); - } - else if( instruction.equals( "ADC" ) ) { - parseADC_SBC( asmLine, 0x88, 0x4A ); - } - else if( instruction.equals( "AND" ) ) { - parseBiOp8( asmLine, 0xA0 ); - } - else if( instruction.equals( "BIT" ) ) { - parseSingleBit( asmLine, 0x40 ); - } - else if( instruction.equals( "CAC" ) ) { - parseInstDirectAddr( asmLine, 0xDC ); - robotronMnemonic(); + parseIF( asmLine, true ); } - else if( instruction.equals( "CALL" ) ) { - parseCALL( asmLine ); - } - else if( instruction.equals( "CAM" ) ) { - parseInstDirectAddr( asmLine, 0xFC ); - robotronMnemonic(); - } - else if( instruction.equals( "CANC" ) ) { - parseInstDirectAddr( asmLine, 0xD4 ); - robotronMnemonic(); - } - else if( instruction.equals( "CANZ" ) ) { - parseInstDirectAddr( asmLine, 0xC4 ); - robotronMnemonic(); - } - else if( instruction.equals( "CAP" ) ) { - parseInstDirectAddr( asmLine, 0xF4 ); - robotronMnemonic(); - } - else if( instruction.equals( "CAPE" ) ) { - parseInstDirectAddr( asmLine, 0xEC ); - robotronMnemonic(); - } - else if( instruction.equals( "CAPO" ) ) { - parseInstDirectAddr( asmLine, 0xE4 ); - robotronMnemonic(); - } - else if( instruction.equals( "CAZ" ) ) { - parseInstDirectAddr( asmLine, 0xCC ); - robotronMnemonic(); - } - else if( instruction.equals( "CCF" ) ) { - asmLine.checkEOL(); - putCode( 0x3F ); - } - else if( instruction.equals( "CMP" ) ) { - parseBiOp8( asmLine, 0xB8 ); - robotronMnemonic(); - } - else if( instruction.equals( "CP" ) ) { - parseBiOp8( asmLine, 0xB8 ); - zilogMnemonic(); - } - else if( instruction.equals( "CPD" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xA9 ); - } - else if( instruction.equals( "CPDR" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xB9 ); - } - else if( instruction.equals( "CPI" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xA1 ); - } - else if( instruction.equals( "CPIR" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xB1 ); - } - else if( instruction.equals( "CPL" ) ) { - asmLine.checkEOL(); - putCode( 0x2F ); - } - else if( instruction.equals( "CPU" ) - || instruction.equals( ".CPU" ) ) - { - parseCPU( asmLine ); - } - else if( instruction.equals( "DAA" ) ) { - asmLine.checkEOL(); - putCode( 0x27 ); - } - else if( instruction.equals( "DEC" ) ) { - parseINC_DEC( asmLine, 0x05, 0x0B ); - } - else if( instruction.equals( "DEFB" ) - || instruction.equals( ".DEFB" ) - || instruction.equals( "DEFM" ) - || instruction.equals( ".DEFM" ) - || instruction.equals( "DB" ) - || instruction.equals( ".DB" ) ) - { - parseDEFB( asmLine ); - } - else if( instruction.equals( "DEFH" ) - || instruction.equals( ".DEFH" ) - || instruction.equals( "HEX" ) - || instruction.equals( ".HEX" ) ) - { - parseDEFH( asmLine ); - } - else if( instruction.equals( "DEFS" ) - || instruction.equals( ".DEFS" ) - || instruction.equals( "DS" ) - || instruction.equals( ".DS" ) ) - { - parseDEFS( asmLine ); - } - else if( instruction.equals( "DEFW" ) - || instruction.equals( ".DEFW" ) - || instruction.equals( "DA" ) - || instruction.equals( ".DA" ) - || instruction.equals( "DW" ) - || instruction.equals( ".DW" ) ) - { - parseDEFW( asmLine ); - } - else if( instruction.equals( "DI" ) ) { - asmLine.checkEOL(); - putCode( 0xF3 ); - } - else if( instruction.equals( "DJNZ" ) ) { - int d = getAddrDiff( asmLine.nextArg() ); - asmLine.checkEOL(); - putCode( 0x10 ); - putCode( d ); - } - else if( instruction.equals( "EI" ) ) { - asmLine.checkEOL(); - putCode( 0xFB ); - } - else if( instruction.equals( "END" ) - || instruction.equals( ".END" ) ) + else if( instruction.equals( "IFE" ) + || instruction.equals( "IFF" ) ) { - parseEND( asmLine ); - } - else if( instruction.equals( "ENT" ) - || instruction.equals( ".ENT" ) ) - { - parseENT( asmLine ); - } - else if( instruction.equals( "EQU" ) - || instruction.equals( ".EQU" ) ) - { - parseEQU( asmLine ); - } - else if( instruction.equals( "EX" ) ) { - parseEX( asmLine ); - } - else if( instruction.equals( "EXAF" ) ) { - asmLine.checkEOL(); - putCode( 0x08 ); - robotronMnemonic(); - } - else if( instruction.equals( "EXX" ) ) { - asmLine.checkEOL(); - putCode( 0xD9 ); - } - else if( instruction.equals( "HALT" ) ) { - asmLine.checkEOL(); - putCode( 0x76 ); - } - else if( instruction.equals( "IM" ) ) { - parseIM( asmLine ); - } - else if( instruction.equals( "IN" ) ) { - parseIN( asmLine ); - } - else if( instruction.equals( "INC" ) ) { - parseINC_DEC( asmLine, 0x04, 0x03 ); - } - else if( instruction.equals( "INF" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0x70 ); - robotronMnemonic(); - } - else if( instruction.equals( "IND" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xAA ); - } - else if( instruction.equals( "INDR" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xBA ); - } - else if( instruction.equals( "INI" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xA2 ); - } - else if( instruction.equals( "INIR" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xB2 ); - } - else if( instruction.equals( "JMP" ) ) { - parseJMP( asmLine ); - } - else if( instruction.equals( "JP" ) ) { - parseJP( asmLine ); - } - else if( instruction.equals( "JPC" ) ) { - parseInstDirectAddr( asmLine, 0xDA ); - robotronMnemonic(); - } - else if( instruction.equals( "JPM" ) ) { - parseInstDirectAddr( asmLine, 0xFA ); - robotronMnemonic(); - } - else if( instruction.equals( "JPNC" ) ) { - parseInstDirectAddr( asmLine, 0xD2 ); - robotronMnemonic(); - } - else if( instruction.equals( "JPNZ" ) ) { - parseInstDirectAddr( asmLine, 0xC2 ); - robotronMnemonic(); - } - else if( instruction.equals( "JPP" ) ) { - parseInstDirectAddr( asmLine, 0xF2 ); - robotronMnemonic(); - } - else if( instruction.equals( "JPPE" ) ) { - parseInstDirectAddr( asmLine, 0xEA ); - robotronMnemonic(); - } - else if( instruction.equals( "JPPO" ) ) { - parseInstDirectAddr( asmLine, 0xE2 ); - robotronMnemonic(); - } - else if( instruction.equals( "JPZ" ) ) { - parseInstDirectAddr( asmLine, 0xCA ); - robotronMnemonic(); - } - else if( instruction.equals( "JR" ) ) { - parseJR( asmLine ); - } - else if( instruction.equals( "JRC" ) ) { - int d = getAddrDiff( asmLine.nextArg() ); - asmLine.checkEOL(); - putCode( 0x38 ); - putCode( d ); - robotronMnemonic(); - } - else if( instruction.equals( "JRNC" ) ) { - int d = getAddrDiff( asmLine.nextArg() ); - asmLine.checkEOL(); - putCode( 0x30 ); - putCode( d ); - robotronMnemonic(); - } - else if( instruction.equals( "JRNZ" ) ) { - int d = getAddrDiff( asmLine.nextArg() ); - asmLine.checkEOL(); - putCode( 0x20 ); - putCode( d ); - robotronMnemonic(); + parseIF( asmLine, false ); } - else if( instruction.equals( "JRZ" ) ) { - int d = getAddrDiff( asmLine.nextArg() ); - asmLine.checkEOL(); - putCode( 0x28 ); - putCode( d ); - robotronMnemonic(); + else if( instruction.equals( "IF1" ) ) { + parseIfInPass( asmLine, 1 ); } - else if( instruction.equals( "LD" ) ) { - parseLD( asmLine ); + else if( instruction.equals( "IF2" ) ) { + parseIfInPass( asmLine, 2 ); } - else if( instruction.equals( "LDD" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xA8 ); + else if( instruction.equals( "IFDEF" ) ) { + parseIFDEF( asmLine ); } - else if( instruction.equals( "LDDR" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xB8 ); + else if( instruction.equals( "IFNDEF" ) ) { + parseIFNDEF( asmLine ); } - else if( instruction.equals( "LDI" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xA0 ); + else if( instruction.equals( "ELSE" ) ) { + parseELSE( asmLine ); } - else if( instruction.equals( "LDIR" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xB0 ); - } - else if( instruction.equals( "NEG" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0x44 ); - } - else if( instruction.equals( "NOP" ) ) { - asmLine.checkEOL(); - putCode( 0x00 ); - } - else if( instruction.equals( "OR" ) ) { - parseBiOp8( asmLine, 0xB0 ); - } - else if( instruction.equals( "ORG" ) - || instruction.equals( ".ORG" ) ) - { - parseORG( asmLine ); - } - else if( instruction.equals( "OTDR" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xBB ); - } - else if( instruction.equals( "OTIR" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xB3 ); - } - else if( instruction.equals( "OUT" ) ) { - parseOUT( asmLine ); - } - else if( instruction.equals( "OUTD" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xAB ); - } - else if( instruction.equals( "OUTI" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0xA3 ); - } - else if( instruction.equals( "POP" ) ) { - parsePUSH_POP( asmLine, 0xC1 ); - } - else if( instruction.equals( "PUSH" ) ) { - parsePUSH_POP( asmLine, 0xC5 ); - } - else if( instruction.equals( "RC" ) ) { - asmLine.checkEOL(); - putCode( 0xD8 ); - robotronMnemonic(); - } - else if( instruction.equals( "RES" ) ) { - parseSingleBit( asmLine, 0x80 ); - } - else if( instruction.equals( "RET" ) ) { - parseRET( asmLine ); - } - else if( instruction.equals( "RETI" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0x4D ); - } - else if( instruction.equals( "RETN" ) ) { - asmLine.checkEOL(); - putCode( 0xED ); - putCode( 0x45 ); - } - else if( instruction.equals( "RM" ) ) { - asmLine.checkEOL(); - putCode( 0xF8 ); - robotronMnemonic(); - } - else if( instruction.equals( "RNC" ) ) { - asmLine.checkEOL(); - putCode( 0xD0 ); - robotronMnemonic(); - } - else if( instruction.equals( "RNZ" ) ) { - asmLine.checkEOL(); - putCode( 0xC0 ); - robotronMnemonic(); - } - else if( instruction.equals( "RL" ) ) { - parseRotShift( asmLine, 0x10 ); - } - else if( instruction.equals( "RLA" ) ) { - asmLine.checkEOL(); - putCode( 0x17 ); - } - else if( instruction.equals( "RLC" ) ) { - parseRotShift( asmLine, 0x00 ); - } - else if( instruction.equals( "RLCA" ) ) { - asmLine.checkEOL(); - putCode( 0x07 ); - } - else if( instruction.equals( "RLD" ) ) { - parseRXD( asmLine, 0x6F ); - } - else if( instruction.equals( "RP" ) ) { - asmLine.checkEOL(); - putCode( 0xF0 ); - robotronMnemonic(); - } - else if( instruction.equals( "RPE" ) ) { - asmLine.checkEOL(); - putCode( 0xE8 ); - robotronMnemonic(); - } - else if( instruction.equals( "RPO" ) ) { - asmLine.checkEOL(); - putCode( 0xE0 ); - robotronMnemonic(); - } - else if( instruction.equals( "RR" ) ) { - parseRotShift( asmLine, 0x18 ); - } - else if( instruction.equals( "RRA" ) ) { - asmLine.checkEOL(); - putCode( 0x1F ); - } - else if( instruction.equals( "RRC" ) ) { - parseRotShift( asmLine, 0x08 ); - } - else if( instruction.equals( "RRCA" ) ) { - asmLine.checkEOL(); - putCode( 0x0F ); - } - else if( instruction.equals( "RRD" ) ) { - parseRXD( asmLine, 0x67 ); - } - else if( instruction.equals( "RST" ) ) { - parseRST( asmLine ); - } - else if( instruction.equals( "RZ" ) ) { - asmLine.checkEOL(); - putCode( 0xC8 ); - robotronMnemonic(); - } - else if( instruction.equals( "SBC" ) ) { - parseADC_SBC( asmLine, 0x98, 0x42 ); - } - else if( instruction.equals( "SCF" ) ) { - asmLine.checkEOL(); - putCode( 0x37 ); - } - else if( instruction.equals( "SET" ) ) { - parseSingleBit( asmLine, 0xC0 ); - } - else if( instruction.equals( "SLA" ) ) { - parseRotShift( asmLine, 0x20 ); - } - else if( instruction.equals( "SLL" ) ) { - parseRotShift( asmLine, 0x30 ); - undocInst(); - } - else if( instruction.equals( "SRA" ) ) { - parseRotShift( asmLine, 0x28 ); - } - else if( instruction.equals( "SRL" ) ) { - parseRotShift( asmLine, 0x38 ); - } - else if( instruction.equals( "SUB" ) ) { - parseBiOp8( asmLine, 0x90 ); - } - else if( instruction.equals( "XOR" ) ) { - parseBiOp8( asmLine, 0xA8 ); + else if( instruction.equals( "ENDIF" ) ) { + parseENDIF( asmLine ); } else { - throw new PrgException( - "\'" + instruction + "\': Unbekannte Mnemonik" ); + if( isAssemblingEnabled() ) { + if( instruction.equals( "Z80" ) + || instruction.equals( ".Z80" ) ) + { + // leer + } + else if( instruction.equals( "ADD" ) ) { + parseADD( asmLine ); + } + else if( instruction.equals( "ADC" ) ) { + parseADC_SBC( asmLine, 0x88, 0x4A ); + } + else if( instruction.equals( "AND" ) ) { + parseBiOp8( asmLine, 0xA0 ); + } + else if( instruction.equals( "BINCLUDE" ) + || instruction.equals( ".BINCLUDE" ) ) + { + parseBINCLUDE( asmLine ); + } + else if( instruction.equals( "BIT" ) ) { + parseSingleBit( asmLine, 0x40 ); + } + else if( instruction.equals( "CAC" ) ) { + parseInstDirectAddr( asmLine, 0xDC ); + robotronMnemonic(); + } + else if( instruction.equals( "CALL" ) ) { + parseCALL( asmLine ); + } + else if( instruction.equals( "CAM" ) ) { + parseInstDirectAddr( asmLine, 0xFC ); + robotronMnemonic(); + } + else if( instruction.equals( "CANC" ) ) { + parseInstDirectAddr( asmLine, 0xD4 ); + robotronMnemonic(); + } + else if( instruction.equals( "CANZ" ) ) { + parseInstDirectAddr( asmLine, 0xC4 ); + robotronMnemonic(); + } + else if( instruction.equals( "CAP" ) ) { + parseInstDirectAddr( asmLine, 0xF4 ); + robotronMnemonic(); + } + else if( instruction.equals( "CAPE" ) ) { + parseInstDirectAddr( asmLine, 0xEC ); + robotronMnemonic(); + } + else if( instruction.equals( "CAPO" ) ) { + parseInstDirectAddr( asmLine, 0xE4 ); + robotronMnemonic(); + } + else if( instruction.equals( "CAZ" ) ) { + parseInstDirectAddr( asmLine, 0xCC ); + robotronMnemonic(); + } + else if( instruction.equals( "CCF" ) ) { + asmLine.checkEOL(); + putCode( 0x3F ); + } + else if( instruction.equals( "CMP" ) ) { + parseBiOp8( asmLine, 0xB8 ); + robotronMnemonic(); + } + else if( instruction.equals( "CP" ) ) { + parseBiOp8( asmLine, 0xB8 ); + zilogMnemonic(); + } + else if( instruction.equals( "CPD" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xA9 ); + } + else if( instruction.equals( "CPDR" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xB9 ); + } + else if( instruction.equals( "CPI" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xA1 ); + } + else if( instruction.equals( "CPIR" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xB1 ); + } + else if( instruction.equals( "CPL" ) ) { + asmLine.checkEOL(); + putCode( 0x2F ); + } + else if( instruction.equals( "CPU" ) + || instruction.equals( ".CPU" ) ) + { + parseCPU( asmLine ); + } + else if( instruction.equals( "DAA" ) ) { + asmLine.checkEOL(); + putCode( 0x27 ); + } + else if( instruction.equals( "DEC" ) ) { + parseINC_DEC( asmLine, 0x05, 0x0B ); + } + else if( instruction.equals( "DEFB" ) + || instruction.equals( ".DEFB" ) + || instruction.equals( "DEFM" ) + || instruction.equals( ".DEFM" ) + || instruction.equals( "DFB" ) + || instruction.equals( ".DFB" ) + || instruction.equals( "DB" ) + || instruction.equals( ".DB" ) ) + { + parseDEFB( asmLine ); + } + else if( instruction.equals( "DEFH" ) + || instruction.equals( ".DEFH" ) + || instruction.equals( "DFH" ) + || instruction.equals( ".DFH" ) + || instruction.equals( "HEX" ) + || instruction.equals( ".HEX" ) ) + { + parseDEFH( asmLine ); + } + else if( instruction.equals( "DEFS" ) + || instruction.equals( ".DEFS" ) + || instruction.equals( "DFS" ) + || instruction.equals( ".DFS" ) + || instruction.equals( "DS" ) + || instruction.equals( ".DS" ) ) + { + parseDEFS( asmLine ); + } + else if( instruction.equals( "DEFW" ) + || instruction.equals( ".DEFW" ) + || instruction.equals( "DFW" ) + || instruction.equals( ".DFW" ) + || instruction.equals( "DA" ) + || instruction.equals( ".DA" ) + || instruction.equals( "DW" ) + || instruction.equals( ".DW" ) ) + { + parseDEFW( asmLine ); + } + else if( instruction.equals( "DI" ) ) { + asmLine.checkEOL(); + putCode( 0xF3 ); + } + else if( instruction.equals( "DJNZ" ) ) { + int d = getAddrDiff( asmLine.nextArg() ); + asmLine.checkEOL(); + putCode( 0x10 ); + putCode( d ); + } + else if( instruction.equals( "EI" ) ) { + asmLine.checkEOL(); + putCode( 0xFB ); + } + else if( instruction.equals( "END" ) + || instruction.equals( ".END" ) ) + { + parseEND( asmLine ); + } + else if( instruction.equals( "ENT" ) + || instruction.equals( ".ENT" ) ) + { + parseENT( asmLine ); + } + else if( instruction.equals( "EQU" ) + || instruction.equals( ".EQU" ) ) + { + parseEQU( asmLine ); + } + else if( instruction.equals( "EX" ) ) { + parseEX( asmLine ); + } + else if( instruction.equals( "EXAF" ) ) { + asmLine.checkEOL(); + putCode( 0x08 ); + robotronMnemonic(); + } + else if( instruction.equals( "EXX" ) ) { + asmLine.checkEOL(); + putCode( 0xD9 ); + } + else if( instruction.equals( "HALT" ) ) { + asmLine.checkEOL(); + putCode( 0x76 ); + } + else if( instruction.equals( "IM" ) ) { + parseIM( asmLine ); + } + else if( instruction.equals( "IN" ) ) { + parseIN( asmLine ); + } + else if( instruction.equals( "INC" ) ) { + parseINC_DEC( asmLine, 0x04, 0x03 ); + } + else if( instruction.equals( "INCLUDE" ) + || instruction.equals( ".INCLUDE" ) ) + { + parseINCLUDE( asmLine ); + } + else if( instruction.equals( "INF" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0x70 ); + robotronMnemonic(); + } + else if( instruction.equals( "IND" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xAA ); + } + else if( instruction.equals( "INDR" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xBA ); + } + else if( instruction.equals( "INI" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xA2 ); + } + else if( instruction.equals( "INIR" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xB2 ); + } + else if( instruction.equals( "JMP" ) ) { + parseJMP( asmLine ); + robotronMnemonic(); + } + else if( instruction.equals( "JP" ) ) { + parseJP( asmLine ); + } + else if( instruction.equals( "JPC" ) ) { + parseInstDirectAddr( asmLine, 0xDA ); + robotronMnemonic(); + } + else if( instruction.equals( "JPM" ) ) { + parseInstDirectAddr( asmLine, 0xFA ); + robotronMnemonic(); + } + else if( instruction.equals( "JPNC" ) ) { + parseInstDirectAddr( asmLine, 0xD2 ); + robotronMnemonic(); + } + else if( instruction.equals( "JPNZ" ) ) { + parseInstDirectAddr( asmLine, 0xC2 ); + robotronMnemonic(); + } + else if( instruction.equals( "JPP" ) ) { + parseInstDirectAddr( asmLine, 0xF2 ); + robotronMnemonic(); + } + else if( instruction.equals( "JPPE" ) ) { + parseInstDirectAddr( asmLine, 0xEA ); + robotronMnemonic(); + } + else if( instruction.equals( "JPPO" ) ) { + parseInstDirectAddr( asmLine, 0xE2 ); + robotronMnemonic(); + } + else if( instruction.equals( "JPZ" ) ) { + parseInstDirectAddr( asmLine, 0xCA ); + robotronMnemonic(); + } + else if( instruction.equals( "JR" ) ) { + parseJR( asmLine ); + } + else if( instruction.equals( "JRC" ) ) { + int d = getAddrDiff( asmLine.nextArg() ); + asmLine.checkEOL(); + putCode( 0x38 ); + putCode( d ); + robotronMnemonic(); + } + else if( instruction.equals( "JRNC" ) ) { + int d = getAddrDiff( asmLine.nextArg() ); + asmLine.checkEOL(); + putCode( 0x30 ); + putCode( d ); + robotronMnemonic(); + } + else if( instruction.equals( "JRNZ" ) ) { + int d = getAddrDiff( asmLine.nextArg() ); + asmLine.checkEOL(); + putCode( 0x20 ); + putCode( d ); + robotronMnemonic(); + } + else if( instruction.equals( "JRZ" ) ) { + int d = getAddrDiff( asmLine.nextArg() ); + asmLine.checkEOL(); + putCode( 0x28 ); + putCode( d ); + robotronMnemonic(); + } + else if( instruction.equals( "LD" ) ) { + parseLD( asmLine ); + } + else if( instruction.equals( "LDD" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xA8 ); + } + else if( instruction.equals( "LDDR" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xB8 ); + } + else if( instruction.equals( "LDI" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xA0 ); + } + else if( instruction.equals( "LDIR" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xB0 ); + } + else if( instruction.equals( "NEG" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0x44 ); + } + else if( instruction.equals( "NOP" ) ) { + asmLine.checkEOL(); + putCode( 0x00 ); + } + else if( instruction.equals( "OR" ) ) { + parseBiOp8( asmLine, 0xB0 ); + } + else if( instruction.equals( "ORG" ) + || instruction.equals( ".ORG" ) ) + { + parseORG( asmLine ); + } + else if( instruction.equals( "OTDR" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xBB ); + } + else if( instruction.equals( "OTIR" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xB3 ); + } + else if( instruction.equals( "OUT" ) ) { + parseOUT( asmLine ); + } + else if( instruction.equals( "OUTD" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xAB ); + } + else if( instruction.equals( "OUTI" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0xA3 ); + } + else if( instruction.equals( "POP" ) ) { + parsePUSH_POP( asmLine, 0xC1 ); + } + else if( instruction.equals( "PUSH" ) ) { + parsePUSH_POP( asmLine, 0xC5 ); + } + else if( instruction.equals( "RC" ) ) { + asmLine.checkEOL(); + putCode( 0xD8 ); + robotronMnemonic(); + } + else if( instruction.equals( "RES" ) ) { + parseSingleBit( asmLine, 0x80 ); + } + else if( instruction.equals( "RET" ) ) { + parseRET( asmLine ); + } + else if( instruction.equals( "RETI" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0x4D ); + } + else if( instruction.equals( "RETN" ) ) { + asmLine.checkEOL(); + putCode( 0xED ); + putCode( 0x45 ); + } + else if( instruction.equals( "RM" ) ) { + asmLine.checkEOL(); + putCode( 0xF8 ); + robotronMnemonic(); + } + else if( instruction.equals( "RNC" ) ) { + asmLine.checkEOL(); + putCode( 0xD0 ); + robotronMnemonic(); + } + else if( instruction.equals( "RNZ" ) ) { + asmLine.checkEOL(); + putCode( 0xC0 ); + robotronMnemonic(); + } + else if( instruction.equals( "RL" ) ) { + parseRotShift( asmLine, 0x10 ); + } + else if( instruction.equals( "RLA" ) ) { + asmLine.checkEOL(); + putCode( 0x17 ); + } + else if( instruction.equals( "RLC" ) ) { + parseRotShift( asmLine, 0x00 ); + } + else if( instruction.equals( "RLCA" ) ) { + asmLine.checkEOL(); + putCode( 0x07 ); + } + else if( instruction.equals( "RLD" ) ) { + parseRXD( asmLine, 0x6F ); + } + else if( instruction.equals( "RP" ) ) { + asmLine.checkEOL(); + putCode( 0xF0 ); + robotronMnemonic(); + } + else if( instruction.equals( "RPE" ) ) { + asmLine.checkEOL(); + putCode( 0xE8 ); + robotronMnemonic(); + } + else if( instruction.equals( "RPO" ) ) { + asmLine.checkEOL(); + putCode( 0xE0 ); + robotronMnemonic(); + } + else if( instruction.equals( "RR" ) ) { + parseRotShift( asmLine, 0x18 ); + } + else if( instruction.equals( "RRA" ) ) { + asmLine.checkEOL(); + putCode( 0x1F ); + } + else if( instruction.equals( "RRC" ) ) { + parseRotShift( asmLine, 0x08 ); + } + else if( instruction.equals( "RRCA" ) ) { + asmLine.checkEOL(); + putCode( 0x0F ); + } + else if( instruction.equals( "RRD" ) ) { + parseRXD( asmLine, 0x67 ); + } + else if( instruction.equals( "RST" ) ) { + parseRST( asmLine ); + } + else if( instruction.equals( "RZ" ) ) { + asmLine.checkEOL(); + putCode( 0xC8 ); + robotronMnemonic(); + } + else if( instruction.equals( "SBC" ) ) { + parseADC_SBC( asmLine, 0x98, 0x42 ); + } + else if( instruction.equals( "SCF" ) ) { + asmLine.checkEOL(); + putCode( 0x37 ); + } + else if( instruction.equals( "SET" ) ) { + parseSingleBit( asmLine, 0xC0 ); + } + else if( instruction.equals( "SLA" ) ) { + parseRotShift( asmLine, 0x20 ); + } + else if( instruction.equals( "SLL" ) ) { + parseRotShift( asmLine, 0x30 ); + undocInst(); + } + else if( instruction.equals( "SRA" ) ) { + parseRotShift( asmLine, 0x28 ); + } + else if( instruction.equals( "SRL" ) ) { + parseRotShift( asmLine, 0x38 ); + } + else if( instruction.equals( "SUB" ) ) { + parseBiOp8( asmLine, 0x90 ); + } + else if( instruction.equals( "XOR" ) ) { + parseBiOp8( asmLine, 0xA8 ); + } else { + throw new PrgException( + "\'" + instruction + "\': Unbekannte Mnemonik" ); + } + } } } } @@ -804,6 +1124,31 @@ else if( instruction.equals( "XOR" ) ) { throw new TooManyErrorsException(); } } + finally { + if( this.interactive + && !this.suppressLineAddr + && (this.curSource != null) + && (this.passNum == 2) ) + { + if( this.instBegAddr < this.curAddr ) { + this.curSource.setLineAddr( this.instBegAddr ); + } else { + if( labelName != null ) { + AsmLabel label = this.labels.get( labelName ); + if( label != null ) { + Object value = label.getLabelValue(); + if( value != null ) { + if( value instanceof Integer ) { + if( ((Integer) value).intValue() == this.instBegAddr ) { + this.curSource.setLineAddr( this.instBegAddr ); + } + } + } + } + } + } + } + } } @@ -914,6 +1259,38 @@ else if( a2.equalsUpper( "SP" ) ) { } + private void parseBINCLUDE( AsmLine asmLine ) throws PrgException + { + File file = getIncludeFile( asmLine ); + asmLine.checkEOL(); + byte[] fileBytes = this.file2Bytes.get( file ); + try { + if( fileBytes == null ) { + fileBytes = EmuUtil.readFile( file, false, 0x10000 ); + this.file2Bytes.put( file, fileBytes ); + } + if( fileBytes == null ) { + throw new IOException(); + } + for( byte b : fileBytes ) { + putCode( b ); + } + } + catch( IOException ex ) { + String msg = ex.getMessage(); + if( msg != null ) { + if( msg.trim().isEmpty() ) { + msg = null; + } + } + if( msg == null ) { + msg = "Datei kann nicht ge\u00F6ffnet werden."; + } + throw new PrgException( msg ); + } + } + + private void parseCALL( AsmLine asmLine ) throws PrgException { AsmArg a1 = asmLine.nextArg(); @@ -1056,7 +1433,19 @@ private void parseDEFH( AsmLine asmLine ) throws PrgException private void parseDEFS( AsmLine asmLine ) throws PrgException { do { - skipCode( nextWordArg( asmLine ) ); + int nBytes = nextWordArg( asmLine ); + if( nBytes > 0 ) { + String labelName = asmLine.getLabel(); + if( labelName != null ) { + AsmLabel label = this.labels.get( labelName ); + if( label != null ) { + if( label.getVarSize() <= 0 ) { + label.setVarSize( nBytes ); + } + } + } + } + skipCode( nBytes ); } while( asmLine.hasMoreArgs() ); } @@ -1069,6 +1458,19 @@ private void parseDEFW( AsmLine asmLine ) throws PrgException } + private void parseELSE( AsmLine asmLine ) throws PrgException + { + if( this.stack.isEmpty() ) { + throw new PrgException( "ELSE ohne zugeh\u00F6riges IF..." ); + } + try { + this.stack.peek().processELSE(); + } + catch( EmptyStackException ex ) {} + asmLine.checkEOL(); + } + + private void parseEND( AsmLine asmLine ) throws PrgException { this.endReached = true; @@ -1076,6 +1478,19 @@ private void parseEND( AsmLine asmLine ) throws PrgException } + private void parseENDIF( AsmLine asmLine ) throws PrgException + { + if( this.stack.isEmpty() ) { + throw new PrgException( "ENDIF ohne zugeh\u00F6riges IF..." ); + } + try { + this.stack.pop(); + } + catch( EmptyStackException ex ) {} + asmLine.checkEOL(); + } + + private void parseENT( AsmLine asmLine ) throws PrgException { if( this.passNum == 1 ) { @@ -1090,6 +1505,8 @@ private void parseENT( AsmLine asmLine ) throws PrgException private void parseEQU( AsmLine asmLine ) throws PrgException { + this.suppressLineAddr = true; + String labelName = asmLine.getLabel(); if( labelName != null ) { /* @@ -1099,7 +1516,14 @@ private void parseEQU( AsmLine asmLine ) throws PrgException */ AsmLabel label = this.labels.get( labelName ); if( label != null ) { - label.setLabelValue( nextWordArg( asmLine ) ); + String argText = asmLine.nextArg().toString(); + Integer value = ExprParser.parse( + argText, + this.instBegAddr, + this.labels, + this.passNum == 2, + this.options.getLabelsCaseSensitive() ); + label.setLabelValue( value != null ? value : argText ); } asmLine.checkEOL(); } else { @@ -1148,6 +1572,20 @@ else if( a1.equalsUpper( "DE" ) ) { } + private void parseIFDEF( AsmLine asmLine ) throws PrgException + { + AsmArg a = asmLine.nextArg(); + this.stack.push( + new AsmStackEntry( + this.curSource.getLineNum(), + this.labels.containsKey( + this.options.getLabelsCaseSensitive() ? + a.toString() + : a.toUpperString() ) ) ); + asmLine.checkEOL(); + } + + private void parseIM( AsmLine asmLine ) throws PrgException { String s = asmLine.nextArg().toString(); @@ -1307,11 +1745,42 @@ else if( a.equalsUpper( "IYL" ) || a.equalsUpper( "LY" ) ) { } + private void parseINCLUDE( AsmLine asmLine ) throws PrgException + { + File file = getIncludeFile( asmLine ); + if( this.curSource != this.mainSource ) { + throw new PrgException( + "In sich geschachtelte INCLUDE-Befehle nicht erlaubt" ); + } + asmLine.checkEOL(); + this.curSource = this.file2Source.get( file ); + if( this.curSource != null ) { + this.curSource.reset(); + } else { + try { + this.curSource = PrgSource.readFile( file ); + this.file2Source.put( file, this.curSource ); + } + catch( IOException ex ) { + String msg = ex.getMessage(); + if( msg != null ) { + if( msg.trim().isEmpty() ) { + msg = null; + } + } + if( msg == null ) { + msg = "Datei kann nicht ge\u00F6ffnet werden."; + } + throw new PrgException( msg ); + } + } + } + + private void parseJMP( AsmLine asmLine ) throws PrgException { AsmArg a = asmLine.nextArg(); asmLine.checkEOL(); - robotronMnemonic(); if( a.isIndirectHL() ) { putCode( 0xE9 ); } @@ -1808,11 +2277,61 @@ else if( ((preCode == 0xDD) } + private void parseIF( + AsmLine asmLine, + boolean condValue ) throws PrgException + { + Integer v = ExprParser.parse( + asmLine.nextArg().toString(), + this.instBegAddr, + this.labels, + true, + this.options.getLabelsCaseSensitive() ); + if( v == null ) { + throw new PrgException( "Wert nicht ermittelbar" + + " (bei IF sind keine Vorw\u00E4rtsreferenzen" + + " auf Marken erlaubt.)" ); + } + this.stack.push( + new AsmStackEntry( + this.curSource.getLineNum(), + condValue == (v.intValue() != 0) ) ); + asmLine.checkEOL(); + } + + + private void parseIFNDEF( AsmLine asmLine ) throws PrgException + { + AsmArg a = asmLine.nextArg(); + this.stack.push( + new AsmStackEntry( + this.curSource.getLineNum(), + !this.labels.containsKey( + this.options.getLabelsCaseSensitive() ? + a.toString() + : a.toUpperString() ) ) ); + asmLine.checkEOL(); + } + + + private void parseIfInPass( AsmLine asmLine, int passNum ) throws PrgException + { + this.stack.push( + new AsmStackEntry( + this.curSource.getLineNum(), + passNum == this.passNum ) ); + asmLine.checkEOL(); + } + + private void parseORG( AsmLine asmLine ) throws PrgException { + this.suppressLineAddr = true; + int a = nextWordArg( asmLine ); asmLine.checkEOL(); if( a < this.curAddr ) { + this.orgOverlapped = true; throw new PrgException( "Zur\u00FCcksetzen des" + " Addressz\u00E4hlers nicht erlaubt" ); } @@ -1964,30 +2483,12 @@ private void parseRXD( AsmLine asmLine, int baseCode ) throws PrgException private void parseRST( AsmLine asmLine ) throws PrgException { - boolean isHex = false; - String text = asmLine.nextArg().toUpperString(); + int value = parseExpr( asmLine.nextArg().toString() ); asmLine.checkEOL(); - if( text.endsWith( "H" ) ) { - isHex = true; - text = text.substring( 0, text.length() - 1 ); - } - try { - int v = Integer.parseInt( text, 16 ); - if( (v & ~0x38) != 0 ) { - throwNoSuchInstArgs(); - } - putCode( 0xC7 | v ); - if( isHex ) { - zilogSyntax(); - } else { - if( (this.options.getAsmSyntax() == Syntax.ZILOG_ONLY) && (v > 9) ) { - putWarning( "Hexadezimalkonstante endet nicht mit \'H\'" ); - } - } - } - catch( NumberFormatException ex ) { - throw new PrgException( "Hexadezimalkonstante erwartet" ); + if( (value & ~0x38) != 0 ) { + throwNoSuchInstArgs(); } + putCode( 0xC7 | value ); } @@ -2037,7 +2538,8 @@ private void parseBiOp8( AsmArg a, int baseCode ) throws PrgException } - private void parseBiOp8Internal( AsmArg a, int baseCode ) throws PrgException + private void parseBiOp8Internal( AsmArg a, int baseCode ) + throws PrgException { if( a.isRegAtoL() ) { putCode( baseCode + a.getReg8Code() ); @@ -2160,15 +2662,7 @@ private void parseSingleBit( a3 = asmLine.nextArg(); asmLine.checkEOL(); } - char ch = '\u0020'; - String bitText = a1.toString(); - if( bitText.length() == 1 ) { - ch = bitText.charAt( 0 ); - } - if( (ch < '0') || (ch > '7') ) { - throw new PrgException( "\'0\' bis \'7\' als Bitabgabe erwartet" ); - } - int bitCode = (ch - '0') << 3; + int bitCode = (parseExpr( a1.toString() ) << 3); if( a3 != null ) { // bei SET und RES sind drei Argumente moeglich @@ -2239,7 +2733,11 @@ private int getAddrDiff( AsmArg asmArg ) throws PrgException { int v = 0; if( this.passNum == 2 ) { - v = getWord( asmArg ) - ((this.curAddr + 2) & 0xFFFF); + String s = asmArg.toString(); + if( s.endsWith( "-#" ) ) { + s = s.substring( 0, s.length() - 2 ); + } + v = getWord( s ) - ((this.curAddr + 2) & 0xFFFF); if( (v < ~0x7F) || (v > 0x7F) ) { this.relJumpsTooLong = true; throw new PrgException( "Relative Sprungdistanz zu gro\u00DF" ); @@ -2249,17 +2747,41 @@ private int getAddrDiff( AsmArg asmArg ) throws PrgException } + private File getIncludeFile( AsmLine asmLine ) throws PrgException + { + String fileName = null; + String text = asmLine.nextArg().toString(); + if( text != null ) { + int len = text.length(); + if( len > 0 ) { + char ch = text.charAt( 0 ); + if( (ch == '\'') || (ch == '\"') ) { + if( (len < 2) || (text.charAt( len - 1 ) != ch) ) { + throw new PrgException( + "Dateiname nicht mit " + ch + " abgeschlossen" ); + } + if( len > 2 ) { + fileName = text.substring( 1, len - 1 ); + } + } else { + fileName = text; + } + } + } + if( fileName == null ) { + throw new PrgException( "Dateiname erwartet" ); + } + return PrgSource.getIncludeFile( this.curSource, fileName ); + } + + private int getIndirectIXYDist( AsmArg a ) throws PrgException { int v = 0; String text = a.getIndirectIXYDist(); if( text != null ) { if( !text.isEmpty() ) { - v = ExprParser.parse( - text, - this.labels, - this.passNum == 2, - this.options.getLabelsCaseSensitive() ); + v = parseExpr( text ); if( (v < ~0x7F) || (v > 0xFF) ) { throw new PrgException( "Distanzangabe zu gro\u00DF" ); } @@ -2281,11 +2803,7 @@ private int getByte( AsmArg asmArg ) throws PrgException private int getByte( String text ) throws PrgException { - int v = ExprParser.parse( - text, - this.labels, - this.passNum == 2, - this.options.getLabelsCaseSensitive() ); + int v = parseExpr( text ); if( (v < ~0x7F) || (v > 0xFF) ) { putWarningOutOf8Bits(); } @@ -2301,15 +2819,8 @@ private int getWord( AsmArg asmArg ) throws PrgException private int getWord( String text ) throws PrgException { - int v = ExprParser.parse( - text, - this.labels, - this.passNum == 2, - this.options.getLabelsCaseSensitive() ); - if( (v < ~0x7FFF) || (v > 0xFFFF) ) { - putWarning( "Numerischer Wert au\u00DFerhalb 16-Bit-Bereich:" - + "Bits gehen verloren" ); - } + int v = parseExpr( text ); + checkPrint16BitWarning( v ); return v; } @@ -2450,10 +2961,19 @@ private void printLabels() firstLabel = false; appendToOutLog( "\nMarkentabelle:\n" ); } + String vText = "????"; + Object value = labels[ i ].getLabelValue(); + if( value != null ) { + if( value instanceof Integer ) { + vText = String.format( + "%04X", + ((Integer) value).intValue() & 0xFFFF ); + } + } appendToOutLog( String.format( - " %04Xh %s\n", - labels[ i ].getLabelValue(), + " %s %s\n", + vText, labels[ i ].getLabelName() ) ); } } @@ -2468,7 +2988,7 @@ private void printLabels() private void putWarningOutOf8Bits() { putWarning( "Numerischer Wert au\u00DFerhalb 8-Bit-Bereich:" - + "Bits gehen verloren" ); + + " Bits gehen verloren" ); } @@ -2489,9 +3009,10 @@ private boolean writeCodeToFile( String appName, boolean forZ9001 ) if( sAddr == null ) { sAddr = new Integer( begAddr ); } - String fileFmt = FileSaver.BIN; - String fileDesc = ""; - String fileName = file.getName(); + FileFormat fileFmt = FileFormat.BIN; + String fileType = null; + String fileDesc = ""; + String fileName = file.getName(); if( fileName != null ) { String upperName = fileName.toUpperCase(); if( fileDesc.isEmpty() ) { @@ -2501,19 +3022,27 @@ private boolean writeCodeToFile( String appName, boolean forZ9001 ) } } if( upperName.endsWith( ".HEX" ) ) { - fileFmt = FileSaver.INTELHEX; + fileFmt = FileFormat.INTELHEX; } else if( upperName.endsWith( ".KCC" ) || upperName.endsWith( ".KCM" ) ) { - fileFmt = FileSaver.KCC; + fileFmt = FileFormat.KCC; } else if( upperName.endsWith( ".TAP" ) ) { - fileFmt = (forZ9001 ? FileSaver.KCTAP_0 : FileSaver.KCTAP_1); + if( forZ9001 ) { + fileFmt = FileFormat.KCTAP_Z9001; + if( sAddr != null ) { + fileType = "COM"; + } + } else { + fileFmt = FileFormat.KCTAP_KC85; + } } else if( upperName.endsWith( ".Z80" ) ) { - fileFmt = FileSaver.HEADERSAVE; + fileFmt = FileFormat.HEADERSAVE; + fileType = (sAddr != null ? "C" : "M"); } - if( fileFmt.equals( FileSaver.KCC ) - || fileFmt.equals( FileSaver.KCTAP_0 ) - || fileFmt.equals( FileSaver.KCTAP_1 ) ) + if( fileFmt.equals( FileFormat.KCC ) + || fileFmt.equals( FileFormat.KCTAP_KC85 ) + || fileFmt.equals( FileFormat.KCTAP_Z9001 ) ) { /* * Wenn ein Programmname bekannt ist, @@ -2528,29 +3057,6 @@ private boolean writeCodeToFile( String appName, boolean forZ9001 ) fileDesc = appName; } } - if( forZ9001 ) { - /* - * Beim Z9001 muss ein ausfuehrbares Programm - * im KC-Systemformat den Dateityp COM haben. - */ - if( !fileDesc.isEmpty() ) { - StringBuilder descBuf = new StringBuilder( 11 ); - if( fileDesc.length() > 8 ) { - descBuf.append( fileDesc.substring( 0, 8 ) ); - } else { - descBuf.append( fileDesc ); - } - while( descBuf.length() < 8 ) { - /* - * Die evtl. Luecke zwischen Programmname und Dateityp muss - * mit Null-Bytes statt mit Leereichen aufgefuellt werden. - */ - descBuf.append( (char) '\u0000' ); - } - descBuf.append( "COM" ); - fileDesc = descBuf.toString(); - } - } } } try { @@ -2571,11 +3077,10 @@ private boolean writeCodeToFile( String appName, boolean forZ9001 ) this.begAddr, begAddr + codeBytes.length - 1, false, - false, this.begAddr, sAddr, - 'C', fileDesc, + fileType, null ); status = true; } @@ -2603,4 +3108,3 @@ private boolean writeCodeToFile( String appName, boolean forZ9001 ) return status; } } - diff --git a/src/jkcemu/programming/basic/AbstractTarget.java b/src/jkcemu/programming/basic/AbstractTarget.java index 7c212ed..6cba525 100644 --- a/src/jkcemu/programming/basic/AbstractTarget.java +++ b/src/jkcemu/programming/basic/AbstractTarget.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -9,23 +9,86 @@ package jkcemu.programming.basic; import java.lang.*; -import java.util.Set; +import java.util.*; import jkcemu.base.EmuSys; +import jkcemu.programming.basic.target.*; public abstract class AbstractTarget { protected boolean xoutchAppended; - protected boolean usesXOUTST; - protected boolean usesX_MPEN; + protected boolean usesX_M_PEN; + + private static final String[] targetDefinedConstants = { + "BLINKING", "BLUE", "CYAN", "GREEN", "MAGENTA", + "RED", "WHITE", "YELLOW", + "GRAPHICSCREEN", "LASTSCREEN", + "JOYST_BUTTON1", "JOYST_BUTTON2", + "JOYST_LEFT", "JOYST_RIGHT", "JOYST_UP", "JOYST_DOWN", + AC1Target.BASIC_TARGET_NAME, + CPMTarget.BASIC_TARGET_NAME, + HueblerGraphicsMCTarget.BASIC_TARGET_NAME, + KC85Target.BASIC_TARGET_NAME, + KC854Target.BASIC_TARGET_NAME, + KramerMCTarget.BASIC_TARGET_NAME, + LLC2HIRESTarget.BASIC_TARGET_NAME, + SCCHTarget.BASIC_TARGET_NAME, + Z1013Target.BASIC_TARGET_NAME, + Z1013PetersTarget.BASIC_TARGET_NAME, + Z9001Target.BASIC_TARGET_NAME, + Z9001KRTTarget.BASIC_TARGET_NAME }; + + private Map namedValues; protected AbstractTarget() { + this.namedValues = new HashMap<>(); + setNamedValue( CPMTarget.BASIC_TARGET_NAME, 100 ); + setNamedValue( HueblerGraphicsMCTarget.BASIC_TARGET_NAME, 300 ); + setNamedValue( KC85Target.BASIC_TARGET_NAME, 400 ); + setNamedValue( KC854Target.BASIC_TARGET_NAME, 410 ); + setNamedValue( KramerMCTarget.BASIC_TARGET_NAME, 600 ); + setNamedValue( SCCHTarget.BASIC_TARGET_NAME, 700 ); + setNamedValue( AC1Target.BASIC_TARGET_NAME, 710 ); + setNamedValue( LLC2HIRESTarget.BASIC_TARGET_NAME, 720 ); + setNamedValue( Z1013Target.BASIC_TARGET_NAME, 800 ); + setNamedValue( Z1013PetersTarget.BASIC_TARGET_NAME, 810 ); + setNamedValue( Z9001Target.BASIC_TARGET_NAME, 900 ); + setNamedValue( Z9001KRTTarget.BASIC_TARGET_NAME, 910 ); + setNamedValue( "GRAPHICSCREEN", -1 ); + for( String name : new String[] { + "LASTSCREEN", "BLACK", + "JOYST_LEFT", "JOYST_RIGHT", + "JOYST_UP", "JOYST_DOWN", + "JOYST_BUTTON1", "JOYST_BUTTON2" } ) + { + setNamedValue( name, 0 ); + } + for( String name : new String[] { + "BLINKING", "BLUE", "CYAN", "GREEN", + "MAGENTA", "RED", "WHITE", "YELLOW" } ) + { + setNamedValue( name, 1 ); + } reset(); } + protected static String[] add( String[] a, String s ) + { + String[] rv = null; + if( a != null ) { + rv = new String[ a.length + 1 ]; + rv[ 0 ] = s; + System.arraycopy( a, 0, rv, 1, a.length ); + } else { + rv = new String[] { s }; + } + return rv; + } + + public void appendDataTo( AsmCodeBuf buf ) { // leer @@ -34,38 +97,44 @@ public void appendDataTo( AsmCodeBuf buf ) public void appendBssTo( AsmCodeBuf buf ) { - if( this.usesX_MPEN ) { - buf.append( "X_MPEN:\tDS\t1\n" ); + if( this.usesX_M_PEN ) { + buf.append( "X_M_PEN:\tDS\t1\n" ); } } - public void appendEnableKCNet( AsmCodeBuf buf ) + public void appendEnableVdipTo( AsmCodeBuf buf ) { // leer } - public void appendEnableVdip( AsmCodeBuf buf ) + public void appendEtcPreXOutTo( AsmCodeBuf buf ) { // leer } - public void appendEtc( AsmCodeBuf buf ) + public void appendEtcPastXOutTo( AsmCodeBuf buf ) { // leer } - public abstract void appendExit( AsmCodeBuf buf ); + public abstract void appendExitTo( AsmCodeBuf buf ); + + + public void appendPreExitTo( AsmCodeBuf buf ) + { + // leer + } - protected void appendExitNoGraphicsScreen( + protected void appendExitNoGraphicsScreenTo( AsmCodeBuf buf, BasicCompiler compiler ) { - appendSwitchToTextScreen( buf ); + appendSwitchToTextScreenTo( buf ); buf.append( "\tCALL\tXOUTST\n" ); if( compiler.isLangCode( "DE" ) ) { buf.append( "\tDB\t\'Kein Grafik-SCREEN\'\n" ); @@ -73,22 +142,32 @@ protected void appendExitNoGraphicsScreen( buf.append( "\tDB\t\'No graphics screen\'\n" ); } buf.append( "\tDB\t00H\n" ); - if( compiler.usesLibItem( BasicLibrary.LibItem.E_EXIT ) ) { + if( compiler.getBasicOptions().getPrintLineNumOnAbort() + && compiler.usesLibItem( BasicLibrary.LibItem.E_EXIT ) ) + { buf.append( "\tJP\tE_EXIT\n" ); } else { - buf.append( "\tJP\tXEXIT\n" ); + buf.append( "\tCALL\tXOUTNL\n" + + "\tJP\tXEXIT\n" ); + compiler.getLibItems().add( BasicLibrary.LibItem.XOUTNL ); } - this.usesXOUTST = true; + compiler.getLibItems().add( BasicLibrary.LibItem.XOUTST ); + } + + + public void appendFileHandler( BasicCompiler compiler ) + { + // leer } - public void appendHChar( AsmCodeBuf buf ) + public void appendHCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0FFFFH\n" ); // -1: Anzahl Zeilen unbekannt } - public void appendHPixel( AsmCodeBuf buf ) + public void appendHPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0000H\n" ); // 0: keine Grafik } @@ -96,52 +175,60 @@ public void appendHPixel( AsmCodeBuf buf ) public void appendInitTo( AsmCodeBuf buf ) { - if( this.usesX_MPEN ) { + if( this.usesX_M_PEN ) { buf.append( "\tLD\tA,01H\n" - + "\tLD\t(X_MPEN),A\n" ); + + "\tLD\t(X_M_PEN),A\n" ); } } - public abstract void appendInput( + /* + * Die Methode fuegt die Routinen zur Abfrage der Tastatur hinzu. + * + * Parameter: + * xckbrk: Routine XCKBRK (Test auf CTRL-C) muss hinzugefuegt werden. + * kein Rueckgabewert + * XINKEY: Routine XINKEY (Tastaturabfrage ohne warten) + * muss hinzugefuegt werden. + * Rueckgabewert: Tastencode in A, 0: keine Taste gedrueckt + * XINCH : Routine XINCH (Tastaturabfrage mit warten) + * muss hinzugefuegt werden. + * Rueckgabewert: Tastencode in A + * + * Allgemein gilt: + * Wenn die Parameter xckbrk oder canBreakOnInput gesetzt sind, + * muss beim Druecken von CTRL-C das Programm abgebrochen werden. + */ + public abstract void appendInputTo( AsmCodeBuf buf, boolean xckbrk, boolean xinkey, - boolean xinchar, + boolean xinch, boolean canBreakOnInput ); - public void appendMenuItem( - BasicCompiler compiler, + public void appendPrologTo( AsmCodeBuf buf, - String appName ) - { - // leer - } - - - public void appendProlog( BasicCompiler compiler, - AsmCodeBuf buf, String appName ) { // leer } - public void appendSwitchToTextScreen( AsmCodeBuf buf ) + public void appendSwitchToTextScreenTo( AsmCodeBuf buf ) { // leer } - public void appendWChar( AsmCodeBuf buf ) + public void appendWCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0FFFFH\n" ); // -1: Anzahl Spalten unbekannt } - public void appendWPixel( AsmCodeBuf buf ) + public void appendWPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0000H\n" ); // 0: keine Grafik } @@ -152,13 +239,13 @@ public void appendWPixel( AsmCodeBuf buf ) * Parameter: * HL: Farbe */ - public void appendXBORDER( AsmCodeBuf buf ) + public void appendXBorderTo( AsmCodeBuf buf ) { // leer } - public void appendXCLS( AsmCodeBuf buf ) + public void appendXClsTo( AsmCodeBuf buf ) { // leer } @@ -170,7 +257,7 @@ public void appendXCLS( AsmCodeBuf buf ) * HL: Vordergrundfarbe * DE: Hintergrundfarbe */ - public void appendXCOLOR( AsmCodeBuf buf ) + public void appendXColorTo( AsmCodeBuf buf ) { // leer } @@ -182,20 +269,20 @@ public void appendXCOLOR( AsmCodeBuf buf ) * HL: 0: Cursor ausschalten * <>0: Cursor einschalten */ - public void appendXCURS( AsmCodeBuf buf ) + public void appendXCursTo( AsmCodeBuf buf ) { // leer } /* - * Zeichnen einer horizontaler Linie + * Zeichnen einer horizontalen Linie * Parameter: * BC: Laenge - 1 - * DE: linke X-Koordinate + * DE: linke X-Koordinate, nicht kleiner 0 * HL: Y-Koordinate */ - public void appendXHLINE( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXHLineTo( AsmCodeBuf buf, BasicCompiler compiler ) { // leer } @@ -206,7 +293,7 @@ public void appendXHLINE( AsmCodeBuf buf, BasicCompiler compiler ) * Parameter: * HL: Farbe */ - public void appendXINK( AsmCodeBuf buf ) + public void appendXInkTo( AsmCodeBuf buf ) { // leer } @@ -218,14 +305,8 @@ public void appendXINK( AsmCodeBuf buf ) * HL: Nummer des Joysticks (0: erster Joystick) * Rueckgabewert: * HL: Joystickstatus - * Bit 0: links - * Bit 1: rechts - * Bit 2: runter - * Bit 3: hoch - * Bit 4: erster Aktionsknopf - * Bit 5: zweiter Aktionsknopf */ - public void appendXJOY( AsmCodeBuf buf ) + public void appendXJoyTo( AsmCodeBuf buf ) { // leer } @@ -237,7 +318,7 @@ public void appendXJOY( AsmCodeBuf buf ) * DE: Zeile, >= 0 * HL: Spalte, >= 0 */ - public void appendXLOCATE( AsmCodeBuf buf ) + public void appendXLocateTo( AsmCodeBuf buf ) { // leer } @@ -246,22 +327,22 @@ public void appendXLOCATE( AsmCodeBuf buf ) /* * Ausgabe eines Zeichens auf dem Drucker */ - public void appendXLPTCH( AsmCodeBuf buf ) + public void appendXLPtchTo( AsmCodeBuf buf ) { buf.append( "XLPTCH:\tRET\n" ); } /* - * Ausgabe eines Zeichens auf dem Bildschirm + * Ausgabe des Zeichens in A auf dem Bildschirm */ - public abstract void appendXOUTCH( AsmCodeBuf buf ); + public abstract void appendXOutchTo( AsmCodeBuf buf ); /* * Ausgabe eines Zeilenumbruchs */ - public void appendXOUTNL( AsmCodeBuf buf ) + public void appendXOutnlTo( AsmCodeBuf buf ) { buf.append( "XOUTNL:\tLD\tA,0DH\n" + "\tCALL\tXOUTCH\n" @@ -269,7 +350,7 @@ public void appendXOUTNL( AsmCodeBuf buf ) if( this.xoutchAppended ) { buf.append( "\tJP\tXOUTCH\n" ); } else { - appendXOUTCH( buf ); + appendXOutchTo( buf ); } } @@ -279,7 +360,7 @@ public void appendXOUTNL( AsmCodeBuf buf ) * Parameter: * HL: Farbe */ - public void appendXPAPER( AsmCodeBuf buf ) + public void appendXPaperTo( AsmCodeBuf buf ) { // leer } @@ -290,11 +371,68 @@ public void appendXPAPER( AsmCodeBuf buf ) * Parameter: * A: Stift (0: Ignorieren, 1: Normal, 2: Loeschen, 3: XOR-Mode) */ - public void appendXPEN( AsmCodeBuf buf ) + public void appendXPenTo( AsmCodeBuf buf ) + { + buf.append( "XPEN:\tLD\t(X_M_PEN),A\n" + + "\tRET\n" ); + this.usesX_M_PEN = true; + } + + + /* + * Wenn die Methode supportsXPAINT_LEFT_RIGHT() true liefert, + * muss diese Methode hier die Routinen XPAINT_LEFT und XPAINT_RIGHT + * implementieren, anderenfalls XPAINT, + * sofern das Zielsystem Grafik unterstuetzt. + * + * XPAINT: + * Setzen eines Pixels ohne Beruecksichtigung des eingestellten Stiftes + * bei gleichzeitigem Test, ob dieser schon gesetzt ist + * Parameter: + * DE: X-Koordinate + * HL: Y-Koordinate + * Rueckgabe: + * CY=1: Pixel bereits gesetzt oder ausserhalb des sichtbaren Bereichs + * + * XPAINT_LEFT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach links, + * Der Startpunkt selbst wird nicht geprueft + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * (PAINT_M_X1): X-Endkoordinate der gefuellten Linie, + * kleiner oder gleich X-Startpunkt + * + * XPAINT_RIGHT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach rechts + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * CY=1: Pixel im Startpunkt bereits gesetzt (gefuellt) + * oder ausserhalb des sichtbaren Bereichs + * (PAINT_M_X2): X-Endkoordinate der gefuellten Linie, nur bei CY=0 + */ + public void appendXPaintTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPEN:\tLD\t(X_MPEN),A\n" + // leer + } + + + /* + * Farbe eines Pixels ermitteln + * Parameter: + * DE: X-Koordinate (0...255) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * HL >= 0: Farbcode des Pixels + * HL=-1: Pixel exisitiert nicht + */ + public void appendXPointTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPOINT:\tLD\tHL,0FFFFH\n" + "\tRET\n" ); - this.usesX_MPEN = true; } @@ -304,19 +442,19 @@ public void appendXPEN( AsmCodeBuf buf ) * DE: X-Koordinate * HL: Y-Koordinate */ - public void appendXPRES( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPResTo( AsmCodeBuf buf, BasicCompiler compiler ) { // leer } /* - * Setzen eines Pixels + * Setzen eines Pixels unter Beruecksichtigung des eingestellten Stiftes * Parameter: * DE: X-Koordinate * HL: Y-Koordinate */ - public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPSetTo( AsmCodeBuf buf, BasicCompiler compiler ) { // leer } @@ -332,7 +470,7 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) * HL=1: Pixel gesetzt * HL=-1: Pixel exisitiert nicht */ - public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPTestTo( AsmCodeBuf buf, BasicCompiler compiler ) { buf.append( "XPTEST:\tLD\tHL,0FFFFH\n" + "\tRET\n" ); @@ -345,9 +483,10 @@ public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) * Rueckgabe: * CY=1: Screen-Nummer nicht unterstuetzt */ - public void appendXSCRS( AsmCodeBuf buf ) + public void appendXScreenTo( AsmCodeBuf buf ) { - buf.append( "XSCRS:\tLD\tA,H\n" + buf.append( "XSCREEN:\n" + + "\tLD\tA,H\n" + "\tOR\tL\n" // CY=0 + "\tRET\tZ\n" + "\tSCF\n" @@ -355,91 +494,75 @@ public void appendXSCRS( AsmCodeBuf buf ) } - /* - * Target-ID-String - */ - public abstract void appendXTARID( AsmCodeBuf buf ); - - - public boolean createsCodeFor( EmuSys emuSys ) - { - return false; - } - - - public abstract int get100msLoopCount(); - public abstract int getDefaultBegAddr(); - - - public int getColorBlack() - { - return 0; - } + public abstract int get100msLoopCount(); + public abstract String[] getBasicTargetNames(); + public abstract int getDefaultBegAddr(); - public int getColorBlinking() + /* + * Die Methode besagt, inwieweit der von dem Zielsystem erzeugte + * Programmcode auf dem uebergebenen emulierten System lauffaehig ist: + * 0: nicht lauffaehig + * 1: nur Basisfunktionen lauffaehig + * 2: bis auf ein paar spezielle Funktionen weitgehend lauffaehig + * 3: voll lauffaehig + */ + public int getCompatibilityLevel( EmuSys emuSys ) { return 0; } - public int getColorBlue() - { - return 1; - } - - - public int getColorCyan() - { - return 1; - } - - - public int getColorGreen() - { - return 1; - } - - - public int getColorMagenta() + public String getFileHandlerLabel() { - return 1; + return null; } - public int getColorRed() + public int getFileIOChannelSize() { - return 1; + return 0; } - public int getColorWhite() + public String getHostName() { - return 1; + return null; } - public int getColorYellow() + public int getMaxAppNameLen() { - return 1; + return 0; } - public int getGraphicScreenNum() + public int getNamedValue( String name ) { - return -1; + Integer value = this.namedValues.get( name ); + return value != null ? value.intValue() : 0; } - public int getLastScreenNum() + public String getStartCmd( EmuSys emuSys, String appName, int begAddr ) { - return 0; + return null; } - public int getKCNetBaseIOAddr() + public int[] getTargetIDs() { - return -1; + int[] rv = null; + String[] targetNames = getBasicTargetNames(); + if( targetNames != null ) { + rv = new int[ targetNames.length ]; + for( int i = 0; i < targetNames.length; i++ ) { + rv[ i ] = getNamedValue( targetNames[ i ] ); + } + } else { + rv = new int[ 0 ]; + } + return rv; } @@ -449,9 +572,16 @@ public int[] getVdipBaseIOAddresses() } - public boolean needsEnableKCNet() + public static boolean isReservedWord( String name ) { - return false; + boolean rv = false; + for( String s : targetDefinedConstants ) { + if( s.equalsIgnoreCase( name ) ) { + rv = true; + break; + } + } + return rv; } @@ -461,9 +591,9 @@ public boolean needsEnableVdip() } - public boolean needsXOUTST() + public void preAppendLibraryCode( BasicCompiler compiler ) { - return this.usesXOUTST; + // leer } @@ -474,12 +604,17 @@ public boolean needsXOUTST() public void reset() { this.xoutchAppended = false; - this.usesXOUTST = false; - this.usesX_MPEN = false; + this.usesX_M_PEN = false; + } + + + protected void setNamedValue( String name, int value ) + { + this.namedValues.put( name, value ); } - public boolean supportsAppName() + public boolean startsWithFileDevice( String fileName ) { return false; } @@ -538,9 +673,14 @@ public boolean supportsXLOCAT() } + public boolean supportsXPAINT_LEFT_RIGHT() + { + return false; + } + + public boolean supportsXLPTCH() { return false; } } - diff --git a/src/jkcemu/programming/basic/AsmCodeBuf.java b/src/jkcemu/programming/basic/AsmCodeBuf.java index a1b6fd4..7866648 100644 --- a/src/jkcemu/programming/basic/AsmCodeBuf.java +++ b/src/jkcemu/programming/basic/AsmCodeBuf.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -8,6 +8,9 @@ package jkcemu.programming.basic; +import java.lang.*; +import java.util.*; + public class AsmCodeBuf { @@ -101,7 +104,7 @@ public void appendStringLiteral( CharSequence text, String termByteStr ) } this.buf.append( ch ); nCh++; - if( nCh >= 16 ) { + if( nCh >= 40 ) { this.buf.append( "\'\n" ); nCh = 0; } @@ -152,6 +155,16 @@ public void append_LD_BC_nn( int value ) } + public void append_LD_BC_xx( String xx ) + { + if( this.enabled && (xx != null) ) { + buf.append( "\tLD\tBC," ); + buf.append( xx ); + buf.append( (char) '\n'); + } + } + + public void append_LD_DE_nn( int value ) { if( this.enabled ) { @@ -274,7 +287,8 @@ public String cut( int pos ) public void delete( int begPos, int endPos ) { - this.buf.delete( begPos, endPos ); + if( this.enabled ) + this.buf.delete( begPos, endPos ); } @@ -297,13 +311,63 @@ public int getLastLinePos() } + public java.util.List getLinesAsList( int begPos ) + { + ArrayList lines = new ArrayList<>(); + synchronized( this.buf ) { + int len = this.buf.length(); + int initSize = len / 4; + lines.ensureCapacity( initSize > 10 ? initSize : 10 ); + if( begPos < 0 ) { + begPos = 0; + } + while( begPos < len ) { + int eol = this.buf.indexOf( "\n", begPos ); + if( eol < begPos ) { + lines.add( this.buf.substring( begPos ) ); + break; + } + eol++; + lines.add( this.buf.substring( begPos, eol ) ); + begPos = eol; + } + } + return lines; + } + + + public int getNumOccurences( String text ) + { + int n = 0; + if( text != null ) { + int len = this.buf.length(); + int curPos = 0; + while( curPos < len ) { + int foundPos = this.buf.indexOf( text, curPos ); + if( foundPos < 0 ) { + break; + } + n++; + curPos = foundPos + 1; + } + } + return n; + } + + public void insert( int pos, CharSequence text) { - if( (text != null) && this.enabled ) + if( this.enabled && (text != null) ) this.buf.insert( pos, text ); } + public boolean isEnabled() + { + return this.enabled; + } + + public int length() { return this.buf.length(); @@ -317,6 +381,26 @@ public void newLine() } + public void removeAllOccurences( String text ) + { + if( this.enabled && (text != null) ) { + int textLen = text.length(); + if( textLen > 0 ) { + int codeLen = this.buf.length(); + int curPos = 0; + while( curPos < codeLen ) { + int foundPos = this.buf.indexOf( text, curPos ); + if( foundPos < 0 ) { + break; + } + this.buf.delete( foundPos, foundPos + textLen ); + curPos = foundPos; + } + } + } + } + + public void replace( int startPos, int endPos, String text ) { if( this.enabled ) @@ -337,6 +421,19 @@ public void setLength( int len ) } + public void setLines( java.util.List lines ) + { + if( this.enabled ) { + this.buf.setLength( 0 ); + if( lines != null ) { + for( String line : lines ) { + this.buf.append( line ); + } + } + } + } + + public String substring( int pos ) { return this.buf.substring( pos ); diff --git a/src/jkcemu/programming/basic/AsmCodeOptimizer.java b/src/jkcemu/programming/basic/AsmCodeOptimizer.java new file mode 100644 index 0000000..315f4bb --- /dev/null +++ b/src/jkcemu/programming/basic/AsmCodeOptimizer.java @@ -0,0 +1,310 @@ +/* + * (c) 2014-2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Optimierung des vom BASIC-Compiler erzeugten Programmcodes + * + * Achtung! Dieser Optimierer ist speziell auf den + * vom JKCEMU-BASIC-Compiler erzeugten Assemblercode abgestimmt + * und kann deshalb nicht allgemein zum Optimieren + * von beliebigen Z80-Assemblercode eingesetzt werden. + */ + +package jkcemu.programming.basic; + +import java.lang.*; +import java.util.*; +import jkcemu.programming.*; +import jkcemu.programming.assembler.AsmLine; + + +public class AsmCodeOptimizer +{ + private static final String CODE_CALL_SCREEN = "\tCALL\tSCREEN\n"; + private static final String CODE_SCREEN_0 = "\tLD\tHL,0000H\n" + + "\tCALL\tSCREEN\n"; + + + // Optimierungen fuer das eigentliche Programms ohne Bibliothek. + public static void optimize1( BasicCompiler compiler ) + { + AsmCodeBuf buf = compiler.getCodeBuf(); + if( buf != null ) { + buf.setEnabled( true ); + /* + * Entfernen der SCREEN-Anweisungen, + * wenn diese immer "SCREEN 0" sind + */ + int nScreenX = buf.getNumOccurences( CODE_CALL_SCREEN ); + int nScreen0 = buf.getNumOccurences( CODE_SCREEN_0 ); + if( (nScreenX > 0) && (nScreenX == nScreen0) ) { + buf.removeAllOccurences( CODE_SCREEN_0 ); + compiler.getLibItems().remove( BasicLibrary.LibItem.SCREEN ); + } + java.util.List lines = buf.getLinesAsList( 0 ); + removeUnusedLineLabels( lines, compiler ); + buf.setLines( lines ); + } + } + + + // Optimierungen fuer den gesamten Programmcode inkl. Bibliothek. + public static void optimize2( BasicCompiler compiler ) + { + AsmCodeBuf buf = compiler.getCodeBuf(); + if( buf != null ) { + buf.setEnabled( true ); + java.util.List lines = buf.getLinesAsList( 0 ); + removeUselessJumpsAndUnreachableCode( lines ); + removeUselessLoadReg( lines ); + buf.setLines( lines ); + } + } + + + /* --- private Methoden --- */ + + private static void removeUnusedLineLabels( + java.util.List lines, + BasicCompiler compiler ) + { + Collection allLineLabels = compiler.getAllLineLabels(); + Collection usedLineLabels = compiler.getUsedLineLabels(); + if( (allLineLabels != null) && (usedLineLabels != null) ) { + int idx = 0; + while( idx < lines.size() ) { + String line = lines.get( idx++ ); + int lineLen = line.length(); + int prefixLen = BasicCompiler.LINE_LABEL_PREFIX.length(); + if( (lineLen > (prefixLen + 2) ) + && line.startsWith( BasicCompiler.LINE_LABEL_PREFIX ) + && line.endsWith( ":\n" ) ) + { + String label = line.substring( prefixLen, lineLen - 2 ); + if( allLineLabels.contains( label ) + && !usedLineLabels.contains( label ) ) + { + --idx; + lines.remove( idx ); + } + } + } + } + } + + + private static void removeUselessJumpsAndUnreachableCode( + java.util.List lines ) + { + boolean enabled = false; + int idx = 0; + while( idx < lines.size() ) { + try { + AsmLine asmLine = AsmLine.scanLine( null, lines.get( idx++ ), true ); + if( asmLine != null ) { + String label = asmLine.getLabel(); + if( !enabled && (label != null) ) { + if( label.equals( BasicCompiler.START_LABEL ) ) { + enabled = true; + } + } + if( enabled ) { + String instr = asmLine.getInstruction(); + if( instr != null ) { + boolean uncondJump = false; + String dstLabel = null; + if( instr.equals( "JP" ) || instr.equals( "JR" ) ) { + if( asmLine.hasMoreArgs() ) { + uncondJump = true; + dstLabel = asmLine.nextArg().toString(); + if( asmLine.hasMoreArgs() ) { + uncondJump = false; + dstLabel = asmLine.nextArg().toString(); + } + } + } else if( instr.equals( "RET" ) ) { + if( !asmLine.hasMoreArgs() ) { + uncondJump = true; + } + } + if( uncondJump || (dstLabel != null) ) { + + /* + * naechste Programmzeile mit einer Instruktion suchen + * + * Wenn es sich um einen unbedingten Sprung handelt, + * dann alle unmittelbar nachfolgenden Programmzeilen + * entfernen, die keine Marke haben, + * da dieser Programmcode nicht erreichbar ist. + */ + String nextLabel = null; + int idx2 = idx; + while( (nextLabel == null) & (idx2 < lines.size()) ) { + String instr2 = null; + AsmLine asmLine2 = AsmLine.scanLine( + null, + lines.get( idx2++ ), + true ); + if( asmLine2 != null ) { + instr2 = asmLine2.getInstruction(); + nextLabel = asmLine2.getLabel(); + if( (nextLabel == null) + && uncondJump + && (instr2 != null) ) + { + --idx2; + lines.remove( idx2 ); + instr2 = null; + } + } + if( (nextLabel != null) || (instr2 != null) ) { + break; + } + } + + /* + * Wenn die naechste Programmzeile das Ziel des Sprungs + * ist, kann der Sprungbefehl entfernt werden. + */ + if( (dstLabel != null) && (nextLabel != null) ) { + if( dstLabel.equals( nextLabel ) ) { + if( label != null ) { + lines.set( idx - 1, label + ":\n" ); + } else { + --idx; + lines.remove( idx ); + } + } + } + } + } + } + } + } + catch( PrgException ex ) {} + } + } + + + private static void removeUselessLoadReg( java.util.List lines ) + { + /* + * In "aValues " und "hlValues" werden alle Ausdruecke gehalten, + * die aktuell den gleichen Wert haben wie das A- bzw. HL-Register. + * Die Ausdruecke koennen ein Direktwert sein oder + * eine indirekte Adressierung einer Speicherzelle. + */ + Set aValues = new TreeSet<>(); + Set hlValues = new TreeSet<>(); + int idx = 0; + while( idx < lines.size() ) { + String line = lines.get( idx++ ); + if( !line.isEmpty() + && !line.startsWith( "\n" ) + && !line.startsWith( ";" ) + && !line.startsWith( "\t" ) ) + { + // Marke -> moeglicher Einsprungpunkt -> Registerwerte ungueltig + aValues.clear(); + hlValues.clear(); + + // Marke uebergehen + int tabPos = line.indexOf( '\t' ); + if( (tabPos > 0) && (line.indexOf( ';' ) < 0) ) { + line = line.substring( tabPos ); + } + } + if( !line.isEmpty() + && !line.startsWith( "\n" ) + && !line.startsWith( ";" ) ) + { + if( line.startsWith( "\tLD\t" ) ) { + int len = line.length(); + if( line.startsWith( "\tLD\tA," ) ) { + if( (len > 7) && line.endsWith( "\n" ) + && !line.startsWith( "\tLD\tA,(HL)" ) + && !line.startsWith( "\tLD\tA,(IX)" ) + && !line.startsWith( "\tLD\tA,(IX+" ) + && !line.startsWith( "\tLD\tA,(IX-" ) + && !line.startsWith( "\tLD\tA,(IY)" ) + && !line.startsWith( "\tLD\tA,(IY+" ) + && !line.startsWith( "\tLD\tA,(IY-" ) ) + { + String value = line.substring( 6, len - 1 ); + if( aValues.contains( value ) ) { + // gleicher Wert -> Zuweisung entfernen + --idx; + lines.remove( idx ); + } else { + // neuer Wert -> merken + aValues.clear(); + aValues.add( value ); + } + } else { + aValues.clear(); + } + } + else if( line.startsWith( "\tLD\tHL," ) ) { + if( (len > 8) && line.endsWith( "\n" ) ) { + String value = line.substring( 7, len - 1 ); + if( hlValues.contains( value ) ) { + // gleicher Wert -> Zuweisung entfernen + --idx; + lines.remove( idx ); + } else { + // neuer Wert -> merken + hlValues.clear(); + hlValues.add( value ); + } + } + } + else if( line.startsWith( "\tLD\tH," ) + || line.startsWith( "\tLD\tL," ) ) + { + hlValues.clear(); + } + else if( line.startsWith( "\tLD\t(" ) + && line.endsWith( "),A\n" ) + && (len > 8) ) + { + if( !line.startsWith( "\tLD\t(HL)" ) + && !line.startsWith( "\tLD\t(IX)" ) + && !line.startsWith( "\tLD\t(IX+" ) + && !line.startsWith( "\tLD\t(IX-" ) + && !line.startsWith( "\tLD\t(IY)" ) + && !line.startsWith( "\tLD\t(IY+" ) + && !line.startsWith( "\tLD\t(IY-" ) ) + { + aValues.add( line.substring( 4, len - 4 ) ); + } + } + else if( line.startsWith( "\tLD\t(" ) + && line.endsWith( "),HL\n" ) + && (len > 9) ) + { + hlValues.add( line.substring( 4, len - 4 ) ); + } + } else if( line.startsWith( "\tXOR\tA\n" ) ) { + String value = "00H"; + if( aValues.contains( value ) ) { + // gleicher Wert -> Zuweisung entfernen + --idx; + lines.remove( idx ); + } else { + // neuer Wert -> merken + aValues.clear(); + aValues.add( value ); + } + } else { + /* + * Alle anderen Befehle veraendern moeglicherweise + * den Inhalt der Register A und HL. + */ + aValues.clear(); + hlValues.clear(); + } + } + } + } +} diff --git a/src/jkcemu/programming/basic/BasicCompiler.java b/src/jkcemu/programming/basic/BasicCompiler.java index 4374d43..b54a570 100644 --- a/src/jkcemu/programming/basic/BasicCompiler.java +++ b/src/jkcemu/programming/basic/BasicCompiler.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -20,478 +20,205 @@ public class BasicCompiler { - public static final int DATA_INT1 = 0x01; - public static final int DATA_INT2 = 0x02; - public static final int DATA_STRING = 0x11; - public static final int MAGIC_GOSUB = 'G'; - public static final int MAX_VALUE = 32767; - public static final int MAX_STR_LEN = 255; - public static final String ERR_POS_TEXT = " ??? "; + public static final int DATA_INT1 = 0x01; + public static final int DATA_INT2 = 0x02; + public static final int DATA_STRING = 0x11; + public static final int MAGIC_GOSUB = 'G'; + public static final int MAX_INT_VALUE = 32767; + public static final int MAX_STR_LEN = 255; + public static final String ERR_POS_TEXT = " ??? "; + public static final String DATA_LABEL_PREFIX = "D_"; + public static final String LINE_LABEL_PREFIX = "L_"; + public static final String START_LABEL = "MSTART"; + public static final String TOP_LABEL = "MTOP"; public static enum DataType { INTEGER, STRING }; + public static enum IODriver { CRT, LPT, FILE, VDIP, ALL }; - - // sortierte Liste der reservierten Schluesselwoeter + /* + * sortierte Liste der reservierten Schluesselwoeter + * ohne zielsystemabhaengigen Konstanten + */ private static final String[] sortedReservedWords = { - "ABS", "ACCEPT", "ADD", "AND", "APPEND", "AS", "ASC", "ASM", - "AT", "AVAILABLE", - "BIN$", "BLACK", "BLINKING", "BLUE", "BORDER", "BSS", + "ABS", "ADD", "AND", "APPEND", "AS", "ASC", "ASM", "AT", + "BIN$", "BINARY", "BORDER", "BSS", "CALL", "CASE", "CHR$", "CIRCLE", "CLOSE", "CLS", - "CODE", "COLOR", "CURSOR", "CYAN", - "DATA", "DATAGRAM", "DECLARE", "DEEK", "DEF", "DEFUSR", + "CODE", "COLOR", "CURSOR", + "DATA", "DECLARE", "DEEK", "DEF", "DEFUSR", "DEFUSR0", "DEFUSR1", "DEFUSR2", "DEFUSR3", "DEFUSR4", "DEFUSR5", "DEFUSR6", "DEFUSR7", "DEFUSR8", "DEFUSR9", - "DIM", "DNSSERVER", "DNSSERVER$", "DO", "DOKE", "DRAW", "DRAWR", + "DIM", "DO", "DOKE", "DRAW", "DRAWR", "ELSE", "ELSEIF", "END", "ENDIF", "EOF", "ERR", "ERR$", "EXIT", - "E_CHANNEL_ALREADY_OPEN", "E_CHANNEL_CLOSED", "E_CONNECT_FAILED", + "E_CHANNEL_ALREADY_OPEN", "E_CHANNEL_CLOSED", "E_DEVICE_LOCKED", "E_DEVICE_NOT_CONFIGURED", "E_DEVICE_NOT_FOUND", - "E_DISK_FULL", "E_DNS_ERROR", "E_DNS_NOT_CONFIGURED", + "E_DISK_FULL", "E_EOF", "E_ERROR", "E_FILE_NOT_FOUND", "E_INVALID", - "E_IO_ERROR", "E_IO_MODE", - "E_MTU_EXCEEDED", "E_NO_DISK", "E_OK", "E_OVERFLOW", - "E_READY_ONLY", "E_SOCKET_STATUS", "E_TIMEOUT", "E_UNKNOWN_HOST", - "FALSE", "FLUSH", "FOR", "FUNCTION", - "GATEWAY", "GATEWAY$", "GOSUB", "GOTO", "GRAPHICSCREEN", "GREEN", - "HEX$", "HIBYTE", "HOSTBYNAME$", "H_CHAR", "H_PIXEL", + "E_IO_ERROR", "E_IO_MODE", "E_NO_DISK", "E_OK", "E_OVERFLOW", + "E_PATH_NOT_FOUND", + "E_READ_ONLY", "E_SOCKET_STATUS", "E_UNKNOWN_HOST", + "FALSE", "FOR", "FUNCTION", + "GOSUB", "GOTO", + "HEX$", "HIBYTE", "H_CHAR", "H_PIXEL", "IF", "IN", "INCLUDE", "INK", "INKEY$", - "INP", "INPUT", "INPUT$", "INSTR", - "JOYST", - "LABEL", "LASTSCREEN", "LCASE$", "LEFT$", "LEN", "LET", "LINE", - "LOBYTE", "LOCAL", "LOCALADDR", "LOCALADDR$", "LOCALPORT", - "LOCATE", "LOOP", "LOWER$", + "INP", "INPUT", "INPUT$", "INSTR", "JOYST", "JOYST_BUTTONS", + "LABEL", "LCASE$", "LEFT$", "LEN", "LET", "LINE", + "LOBYTE", "LOCAL", "LOCATE", "LOOP", "LOWER$", "LPRINT", "LTRIM$", - "MACADDR$", "MAGENTA", "MAX", "MEMSTR$", "MID$", "MIN", "MIRROR$", + "MAX", "MEMSTR$", "MID$", "MIN", "MIRROR$", "MOD", "MOVE", "MOVER", - "NETMASK", "NETMASK$", "NEXT", "NOT", + "NEXT", "NOT", "ON", "OPEN", "OR", "OUT", "OUTPUT", - "PAPER", "PASSWORD", "PAUSE", "PEEK", "PEN", + "PAINT", "PAPER", "PASSWORD", "PAUSE", "PEEK", + "PEN", "PEN_NONE", "PEN_NORMAL", "PEN_RUBBER", "PEN_XOR", "PLOT", "PLOTR", "POINT", "POKE", "PRESET", "PRINT", "PSET", "PTEST", - "READ", "RED", "REM", "REMOTEADDR$", "REMOTEPORT", - "RESTORE", "RETURN", "RIGHT$", "RND", "RTRIM$", - "SCREEN", "SELECT", "SEND", "SET", "SGN", "SHL", "SHR", "SPACE$", + "READ", "REM", "RESTORE", "RETURN", "RIGHT$", "RND", "RTRIM$", + "SCREEN", "SELECT", "SGN", "SHL", "SHR", "SPACE$", "SQR", "STEP", "STOP", "STR$", "STRING$", "SUB", - "TARGETADDR", "TARGETID$", "TEXT", "THEN", "TO", "TOP", - "TRIM$", "TRUE", + "TEXT", "THEN", "TO", "TOP", "TRIM$", "TRUE", "UCASE$", "UNTIL", "UPPER$", "USING", "USR", "USR0", "USR1", "USR2", "USR3", "USR4", "USR5", "USR6", "USR7", "USR8", "USR9", "VAL", "VDIP", - "WAIT", "WEND", "WHILE", "WHITE", "WRITE", "W_CHAR", "W_PIXEL", - "XOR", "XPOS", - "YELLOW", "YPOS" }; - - private String srcText; - private AbstractTarget target; - private BasicOptions options; - private PrgLogger logger; - private Set libItems; - private Set basicLines; - private Collection destBasicLines; - private Set dataBasicLines; - private Collection restoreBasicLines; - private Stack structureStack; - private Map name2Callable; - private Map name2GlobalVar; - private Map str2Label; - private Set usrLabels; - private AsmCodeBuf asmOut; - private AsmCodeBuf dataOut; - private StringBuilder userData; - private StringBuilder userBSS; - private boolean codeGenEnabled; - private boolean gcRequired; - private boolean execEnabled; - private boolean mainPrg; - private boolean separatorChecked; - private boolean suppressExit; - private boolean tmpStrBufUsed; - private int errCnt; - private int labelNum; - private int codePosAtBegOfSrcLine; - private int curSourceLineNum; - private long curBasicLineNum; - private long lastBasicLineNum; - private String lastBasicLineExpr; + "WAIT", "WEND", "WHILE", "WRITE", "W_CHAR", "W_PIXEL", + "XOR", "XPOS", "YPOS" }; + + private PrgSource curSource; + private PrgSource mainSource; + private AbstractTarget target; + private BasicOptions options; + private PrgLogger logger; + private Map> ioDriverModes; + private Set libItems; + private SortedSet basicLines; + private Collection destBasicLines; + private Set dataBasicLines; + private Collection restoreBasicLines; + private Stack structureStack; + private Map name2Callable; + private Map name2GlobalVar; + private Map str2Label; + private SortedSet usrLabels; + private AsmCodeBuf asmOut; + private AsmCodeBuf dataOut; + private StringBuilder userData; + private StringBuilder userBSS; + private boolean gcRequired; + private boolean execEnabled; + private boolean mainPrg; + private boolean separatorChecked; + private boolean tmpStrBufUsed; + private int errCnt; + private int labelNum; + private int codeCreationDisabledLevel; + private int codePosAtBegOfSrcLine; + private long curBasicLineNum; + private long lastBasicLineNum; + private String lastBasicLineExpr; public BasicCompiler( String srcText, + File srcFile, BasicOptions options, PrgLogger logger ) { - this.srcText = srcText; - this.target = options.getTarget(); - this.options = options; - this.logger = logger; - this.libItems = new HashSet(); - this.basicLines = new TreeSet(); - this.destBasicLines = new ArrayList( 1024 ); - this.dataBasicLines = null; - this.restoreBasicLines = null; - this.structureStack = new Stack(); - this.name2Callable = new HashMap(); - this.str2Label = new HashMap(); - this.name2GlobalVar = new HashMap(); - this.usrLabels = new TreeSet(); - this.asmOut = new AsmCodeBuf( 4 * this.srcText.length() ); - this.dataOut = null; - this.userData = null; - this.userBSS = null; - this.codeGenEnabled = true; - this.execEnabled = true; - this.mainPrg = true; - this.gcRequired = false; - this.separatorChecked = false; - this.suppressExit = false; - this.tmpStrBufUsed = false; - this.errCnt = 0; - this.labelNum = 1; - this.curSourceLineNum = 0; - this.curBasicLineNum = -1; - this.lastBasicLineNum = -1; - this.lastBasicLineExpr = null; - } - - - public void cancel() - { - this.execEnabled = false; - } - - - public String compile() throws IOException - { - String asmText = null; - this.codeGenEnabled = true; - this.execEnabled = true; - this.errCnt = 0; - this.target.reset(); - try { - if( this.options.getShowAssemblerText() ) { - this.asmOut.append( ";\n" - + ";Dieser Quelltext wurde vom" - + " JKCEMU-BASIC-Compiler erzeugt.\n" - + ";http://www.jens-mueller.org/jkcemu/\n" - + ";\n" - + "\n" ); - } - this.asmOut.append( "\tORG\t" ); - this.asmOut.appendHex4( this.options.getCodeBegAddr() ); - this.asmOut.newLine(); - this.target.appendProlog( - this, - this.asmOut, - this.options.getAppName() ); - this.asmOut.append( "\tJP\tMINIT\n" ); - this.target.appendMenuItem( - this, - this.asmOut, - this.options.getAppName() ); - if( this.options.getShowAssemblerText() ) { - this.asmOut.append( "\n;Programmstart\n" ); - } - this.asmOut.append( "MSTART:\n" ); - parseSourceText(); - if( this.execEnabled ) { - BasicLibrary.appendCodeTo( this ); - BasicLibrary.appendInitTo( - this, - this.name2GlobalVar, - this.usrLabels ); - this.asmOut.append( "\tJP\tMSTART\n" ); - if( libItems.contains( BasicLibrary.LibItem.DATA ) ) { - if( this.options.getShowAssemblerText() ) { - this.asmOut.append( "\n;DATA-Zeilen\n" ); + this.mainSource = null; + this.target = options.getTarget(); + this.options = options; + this.logger = logger; + this.ioDriverModes = new HashMap<>(); + this.libItems = new HashSet<>(); + this.basicLines = new TreeSet<>(); + this.destBasicLines = new ArrayList<>( 1024 ); + this.dataBasicLines = null; + this.restoreBasicLines = null; + this.structureStack = new Stack<>(); + this.name2Callable = new HashMap<>(); + this.str2Label = new HashMap<>(); + this.name2GlobalVar = new HashMap<>(); + this.usrLabels = new TreeSet<>(); + this.asmOut = new AsmCodeBuf( 0x8000 ); + this.dataOut = null; + this.userData = null; + this.userBSS = null; + this.execEnabled = true; + this.mainPrg = true; + this.gcRequired = false; + this.separatorChecked = false; + this.tmpStrBufUsed = false; + this.codeCreationDisabledLevel = 0; + this.errCnt = 0; + this.labelNum = 1; + this.curBasicLineNum = -1; + this.lastBasicLineNum = -1; + this.lastBasicLineExpr = null; + + // Quelltext oeffnen + if( srcText != null ) { + this.mainSource = PrgSource.readText( srcText, null, srcFile ); + } else { + if( srcFile != null ) { + try { + this.mainSource = PrgSource.readFile( srcFile ); + } + catch( IOException ex ) { + String msg = ex.getMessage(); + if( msg != null ) { + msg = msg.trim(); + if( msg.isEmpty() ) { + msg = null; + } } - this.asmOut.append( "DBEG:\n" ); - if( this.dataOut != null ) { - this.asmOut.append( this.dataOut ); - this.asmOut.append( "\tDB\t00H\n" ); + if( msg == null ) { + msg = "Datei kann nicht ge\u00F6ffnet werden"; } + appendToErrLog( srcFile.getPath() + ": " + msg ); } - BasicLibrary.appendDataTo( - this, - this.str2Label, - this.userData ); - BasicLibrary.appendBssTo( - this, - this.name2GlobalVar, - this.usrLabels, - this.userBSS ); - if( this.errCnt == 0 ) { - asmText = this.asmOut.toString(); - } - } - } - catch( TooManyErrorsException ex ) { - appendToErrLog( "\nAbgebrochen aufgrund zu vieler Fehler\n" ); - } - if( this.execEnabled && (this.errCnt > 0) ) { - appendToErrLog( String.format( "%d Fehler\n", this.errCnt ) ); - } - return asmText; - } - - - public BasicOptions getBasicOptions() - { - return this.options; - } - - - public AsmCodeBuf getCodeBuf() - { - return this.asmOut; - } - - - public int getIOChannelSize() - { - int rv = BasicLibrary.IOCTB_DRIVER_OFFS; - if( usesLibItem( BasicLibrary.LibItem.KCNET_BASE ) ) { - rv = Math.max( rv, KCNetLibrary.IOCTB_CHANNEL_SIZE ); - } - return rv; - } - - - public Set getLibItems() - { - return this.libItems; - } - - - public String getStringLiteralLabel( String text ) - { - String label = this.str2Label.get( text ); - if( label == null ) { - label = nextLabel(); - this.str2Label.put( text, label ); - } - return label; - } - - - public AbstractTarget getTarget() - { - return this.target; - } - - - public boolean isLangCode( String langCode ) - { - boolean rv = false; - if( langCode != null ) { - String curLangCode = this.options.getLangCode(); - if( curLangCode != null ) { - rv = langCode.equalsIgnoreCase( curLangCode ); - } - } - return rv; - } - - - public boolean usesLibItem( BasicLibrary.LibItem libItem ) - { - return this.libItems.contains( libItem ); - } - - - /* --- private Methoden --- */ - - private void appendLineNotFoundToErrLog( - BasicLineExpr lineExpr, - String trailingMsg ) throws TooManyErrorsException - { - if( lineExpr != null ) { - StringBuilder buf = new StringBuilder( 128 ); - buf.append( "Fehler" ); - if( lineExpr.getSourceLineNum() > 0 ) { - buf.append( " in Zeile " ); - buf.append( lineExpr.getSourceLineNum() ); - if( lineExpr.getSourceBasicLineNum() >= 0 ) { - buf.append( " (BASIC-Zeilennummer " ); - buf.append( lineExpr.getSourceBasicLineNum() ); - buf.append( (char) ')' ); - } - } - buf.append( ": " ); - if( lineExpr.isLabel() ) { - buf.append( "Marke \'" ); - buf.append( lineExpr.getExprText() ); - buf.append( (char) '\'' ); - } else { - buf.append( "BASIC-Zeilennummer " ); - buf.append( lineExpr.getExprText() ); - } - buf.append( " nicht gefunden" ); - if( trailingMsg != null ) { - buf.append( trailingMsg ); - } - buf.append( (char) '\n' ); - appendToErrLog( buf.toString() ); - incErrorCount(); - } - } - - - private void appendLineNumMsgToErrLog( - int sourceLineNum, - long basicLineNum, - String msg, - String msgType ) - { - StringBuilder buf = new StringBuilder( 128 ); - if( (sourceLineNum > 0) || (basicLineNum >= 0) ) { - if( msgType != null ) { - buf.append( msgType ); - buf.append( " in " ); - } - if( sourceLineNum > 0 ) { - buf.append( "Zeile " ); - buf.append( sourceLineNum ); - if( basicLineNum >= 0 ) { - buf.append( " (BASIC-Zeilennummer " ); - buf.append( basicLineNum ); - buf.append( (char) ')' ); - } - } - else if( basicLineNum >= 0 ) { - buf.append( "BASIC-Zeilennummer " ); - buf.append( basicLineNum ); - } - buf.append( ": " ); - } - buf.append( msg ); - if( !msg.endsWith( "\n" ) ) { - buf.append( (char) '\n' ); - } - appendToErrLog( buf.toString() ); - } - - - private void appendLineNumMsgToErrLog( String msg, String msgType ) - { - appendLineNumMsgToErrLog( - this.curSourceLineNum, - this.curBasicLineNum, - msg, - msgType ); - } - - - private void appendToErrLog( String text ) - { - if( this.logger != null ) - this.logger.appendToErrLog( text ); - } - - - private boolean checkInstruction( CharacterIterator iter, String keyword ) - { - return checkKeyword( iter, keyword, true, true ); - } - - - private boolean checkKeyword( CharacterIterator iter, String keyword ) - { - return checkKeyword( iter, keyword, true, false ); - } - - - private boolean checkKeyword( - CharacterIterator iter, - String keyword, - boolean fmtSource, - boolean space ) - { - boolean rv = true; - char ch = skipSpaces( iter ); - int begPos = iter.getIndex(); - int len = keyword.length(); - for( int i = 0; i < len; i++ ) { - if( keyword.charAt( i ) != Character.toUpperCase( ch ) ) { - iter.setIndex( begPos ); - rv = false; - break; - } - ch = iter.next(); - } - if( rv ) { - if( ((ch >= 'A') && (ch <= 'Z')) - || ((ch >= 'a') && (ch <= 'z')) - || (ch == '$') ) - { - rv = false; } } - return rv; + this.curSource = this.mainSource; } - private Integer checkSignedNumber( CharacterIterator iter ) - throws PrgException + public void addLibItem( BasicLibrary.LibItem libItem ) { - boolean neg = false; - char ch = skipSpaces( iter ); - int pos = iter.getIndex(); - if( ch == '+' ) { - iter.next(); - } else if( ch == '-' ) { - iter.next(); - neg = true; - } - Integer rv = readNumber( iter ); - if( (rv != null) && neg ) { - rv = new Integer( -rv.intValue() ); - } - if( rv == null ) { - iter.setIndex( pos ); - } - return rv; + if( this.asmOut.isEnabled() ) + this.libItems.add( libItem ); } - private SimpleVarInfo checkVariable( - CharacterIterator iter ) throws PrgException + public void cancel() { - int begPos = iter.getIndex(); - SimpleVarInfo varInfo = checkVariable( - iter, - checkIdentifier( iter ) ); - if( varInfo == null ) { - iter.setIndex( begPos ); - } - return varInfo; + this.execEnabled = false; } /* * Die Methode prueft, ob der uebergebene Name eine Variable ist - * bzw. als einfach Variable gewertet werden kann (implizite Deklaration). + * bzw. als einfache Variable gewertet werden kann (implizite Deklaration). * Wenn ja, wird die Variable vollstaendig geparst (evtl. Indexe) * und ein VarInfo-Objekt zurueckgeliefert. * Ist in dem Objekt das Attribute "addrExpr" gefuellt, - * bescheibt dieses die Adresse der Variablen. + * bescheibt dieses die Adresse der Variable. * Im dem Fall wurde keine Code erzeugt. * Ist dagegen "addrExpr" null, wurde Code erzeugt, - * der im HL-Register die Adresse der Variablen enthaelt. + * der im HL-Register die Adresse der Variable enthaelt. */ - private SimpleVarInfo checkVariable( + public SimpleVarInfo checkVariable( CharacterIterator iter, String varName ) throws PrgException { SimpleVarInfo varInfo = null; if( varName != null ) { if( !varName.isEmpty() ) { - if( Arrays.binarySearch( sortedReservedWords, varName ) < 0 ) { + if( !isReservedWord( varName ) ) { boolean done = false; String addrExpr = null; Integer iyOffs = null; // auf lokale Variable prufen - CallableEntry callableEntry = getCallableEntry(); + CallableEntry callableEntry = getEnclosingCallableEntry(); if( callableEntry != null ) { if( !varName.equals( callableEntry.getName() ) ) { iyOffs = callableEntry.getIYOffs( varName ); @@ -511,14 +238,15 @@ private SimpleVarInfo checkVariable( if( varDecl != null ) { varDecl.setUsed(); if( varDecl.getDimCount() > 0 ) { - parseToken( iter, '(' ); + BasicUtil.parseToken( iter, '(' ); if( varDecl.getDimCount() == 1 ) { int pos = this.asmOut.length(); - parseExpr( iter ); - Integer idx = removeLastCodeIfConstExpr( pos ); + BasicExprParser.parseExpr( this, iter ); + Integer idx = BasicUtil.removeLastCodeIfConstExpr( + this, pos ); if( idx != null ) { if( (idx.intValue() < 0) || (idx > varDecl.getDim1()) ) { - throwIndexOutOfRange(); + BasicUtil.throwIndexOutOfRange(); } if( idx.intValue() > 0 ) { if( idx.intValue() >= 0xA000 ) { @@ -549,13 +277,14 @@ private SimpleVarInfo checkVariable( } } else if( varDecl.getDimCount() == 2 ) { int pos = this.asmOut.length(); - parseExpr( iter ); - Integer idx1 = removeLastCodeIfConstExpr( pos ); + BasicExprParser.parseExpr( this, iter ); + Integer idx1 = BasicUtil.removeLastCodeIfConstExpr( + this, pos ); if( idx1 != null ) { if( (idx1.intValue() < 0) || (idx1.intValue() > varDecl.getDim1()) ) { - throwIndexOutOfRange(); + BasicUtil.throwIndexOutOfRange(); } } else { if( this.options.getCheckBounds() ) { @@ -564,15 +293,16 @@ private SimpleVarInfo checkVariable( addLibItem( BasicLibrary.LibItem.CKIDX ); } } - parseToken( iter, ',' ); + BasicUtil.parseToken( iter, ',' ); pos = this.asmOut.length(); - parseExpr( iter ); - Integer idx2 = removeLastCodeIfConstExpr( pos ); + BasicExprParser.parseExpr( this, iter ); + Integer idx2 = BasicUtil.removeLastCodeIfConstExpr( + this, pos ); if( idx2 != null ) { if( (idx2.intValue() < 0) || (idx2.intValue() > varDecl.getDim2()) ) { - throwIndexOutOfRange(); + BasicUtil.throwIndexOutOfRange(); } if( idx1 != null ) { this.asmOut.append( "\tLD\tHL," ); @@ -639,7 +369,7 @@ private SimpleVarInfo checkVariable( + "\tADD\tHL,DE\n" ); } } - parseToken( iter, ')' ); + BasicUtil.parseToken( iter, ')' ); } else { addrExpr = varDecl.getLabel(); } @@ -650,7 +380,7 @@ private SimpleVarInfo checkVariable( } // implizite Variablendeklaration, aber nur im Hauptprogramm varDecl = new VarDecl( - this.curSourceLineNum, + this.curSource, this.curBasicLineNum, varName ); varDecl.setUsed(); @@ -679,4253 +409,3261 @@ private SimpleVarInfo checkVariable( } - private static String createBasicLineLabel( String lineExprText ) - { - return "L_" + lineExprText.toUpperCase(); - } - - - private static String createDataLineLabel( String lineExprText ) + public String compile() throws IOException { - return "D_" + lineExprText.toUpperCase(); - } - + String asmText = null; + this.execEnabled = true; + this.errCnt = 0; + setCodeCreationDisabledLevel( 0 ); + this.target.reset(); + try { + if( this.options.getShowAssemblerText() ) { + this.asmOut.append( "\n;Eigentliches Programm\n" ); + } + parseSourceText(); + if( this.execEnabled ) { + AsmCodeOptimizer.optimize1( this ); + this.target.preAppendLibraryCode( this ); + BasicLibrary.appendCodeTo( this ); - private void incErrorCount() throws TooManyErrorsException - { - this.errCnt++; - if( this.errCnt >= 100 ) { - throw new TooManyErrorsException(); + // Initialisierungen + AsmCodeBuf initBuf = new AsmCodeBuf( 0x0400 ); + int bssBegAddr = this.options.getBssBegAddr(); + if( (bssBegAddr < 0) + || (bssBegAddr > this.options.getCodeBegAddr()) ) + { + appendHeadCommentTo( initBuf ); + } + initBuf.append( "\n" + + "\tORG\t" ); + initBuf.appendHex4( this.options.getCodeBegAddr() ); + initBuf.newLine(); + this.target.appendPrologTo( + initBuf, + this, + this.options.getAppName() ); + BasicLibrary.appendInitTo( + initBuf, + this, + this.name2GlobalVar, + this.usrLabels ); + initBuf.append( this.asmOut ); + this.asmOut = initBuf; + AsmCodeOptimizer.optimize2( this ); + if( libItems.contains( BasicLibrary.LibItem.DATA ) ) { + if( this.options.getShowAssemblerText() ) { + this.asmOut.append( "\n;DATA-Zeilen\n" ); + } + this.asmOut.append( "DBEG:\n" ); + if( this.dataOut != null ) { + this.asmOut.append( this.dataOut ); + this.asmOut.append( "\tDB\t00H\n" ); + } + } + BasicLibrary.appendDataTo( + this, + this.str2Label, + this.userData ); + AsmCodeBuf bssBuf = this.asmOut; + if( bssBegAddr >= 0 ) { + if( bssBegAddr < this.options.getCodeBegAddr() ) { + bssBuf = new AsmCodeBuf( 0x0400 ); + appendHeadCommentTo( bssBuf ); + } + bssBuf.append( "\n" + + "\tORG\t" ); + bssBuf.appendHex4( bssBegAddr ); + bssBuf.append( "\n" ); + } + BasicLibrary.appendBssTo( + bssBuf, + this, + this.name2GlobalVar, + this.usrLabels, + this.userBSS ); + if( bssBuf != this.asmOut ) { + bssBuf.append( "\n" ); + bssBuf.append( this.asmOut ); + this.asmOut = bssBuf; + } + if( this.errCnt == 0 ) { + asmText = this.asmOut.toString(); + } + } } + catch( TooManyErrorsException ex ) { + appendToErrLog( "\nAbgebrochen aufgrund zu vieler Fehler\n" ); + } + if( this.execEnabled && (this.errCnt > 0) ) { + appendToErrLog( String.format( "%d Fehler\n", this.errCnt ) ); + } + return asmText; } - private void parseSourceText() - throws IOException, TooManyErrorsException + public SortedSet getAllLineLabels() { - this.structureStack.clear(); - this.mainPrg = true; - this.gcRequired = false; - this.curSourceLineNum = 0; - if( this.srcText != null ) { - BufferedReader reader = new BufferedReader( - new StringReader( this.srcText ) ); - String line = reader.readLine(); - while( this.execEnabled && (line != null) ) { - this.curSourceLineNum++; - parseSourceLine( line ); - line = reader.readLine(); - } - } - if( this.mainPrg && !this.suppressExit ) { - if( this.options.getShowAssemblerText() ) { - this.asmOut.append( "\n;Programmende\n" ); + return this.basicLines; + } + + + public BasicOptions getBasicOptions() + { + return this.options; + } + + + public CallableEntry getCallableEntry( String name ) + { + return name != null ? this.name2Callable.get( name ) : null; + } + + + public AsmCodeBuf getCodeBuf() + { + return this.asmOut; + } + + + /* + * Ermittlung der umgebenden Funktion/Prozedur + * + * Diese kann nur der unterste Eintrag auf dem Stack sein. + */ + public CallableEntry getEnclosingCallableEntry() + { + CallableEntry rv = null; + if( !this.structureStack.isEmpty() ) { + BasicSourcePos entry = this.structureStack.get( 0 ); + if( entry instanceof CallableEntry ) { + rv = (CallableEntry) entry; } - this.asmOut.append( "\tJP\tXEXIT\n" ); } + return rv; + } - // Pruefen, ob alle Strukturen geschlossen wurden - for( StructureEntry entry : this.structureStack ) { - appendLineNumMsgToErrLog( - entry.getSourceLineNum(), - entry.getBasicLineNum(), - entry.toString() + " nicht abgeschlossen", - "Fehler" ); - this.errCnt++; + + public int getIOChannelSize() + { + int rv = BasicLibrary.IOCTB_DRIVER_OFFS; + if( usesLibItem( BasicLibrary.LibItem.IO_FILE_HANDLER ) ) { + rv = Math.max( rv, this.target.getFileIOChannelSize() ); } + return rv; + } - // Vorhandensein der Sprungziele und RESTORE-Positionen pruefen + + /* + * Die Methode gibt Auskunft, welche Betriebsarten + * ein konkreter Treiber zur Verfuegung stellen muss. + * Dabei muessen auch die Betriebsarten zurueckgeliefert werden + * waehrend der Compilier-Zeit keinem konkreten Treiber + * zugeorndet werden koennen. + */ + public Set getIODriverModes( IODriver driver ) + { + Set rv = new TreeSet<>(); + if( driver != null ) { + Set modes1 = this.ioDriverModes.get( driver ); + if( modes1 != null ) { + rv.addAll( modes1 ); + } + if( driver != IODriver.ALL ) { + Set modes2 = this.ioDriverModes.get( IODriver.ALL ); + if( modes2 != null ) { + rv.addAll( modes2 ); + } + } + } + return rv; + } + + + public Set getLibItems() + { + return this.libItems; + } + + + public String getStringLiteralLabel( String text ) + { + String label = this.str2Label.get( text ); + if( label == null ) { + label = nextLabel(); + this.str2Label.put( text, label ); + } + return label; + } + + + public AbstractTarget getTarget() + { + return this.target; + } + + + public SortedSet getUsedLineLabels() + { + SortedSet rv = new TreeSet<>(); if( this.destBasicLines != null ) { for( BasicLineExpr lineExpr : this.destBasicLines ) { - if( !this.basicLines.contains( - lineExpr.getExprText().toUpperCase() ) ) - { - appendLineNotFoundToErrLog( lineExpr, null ); - } + rv.add( lineExpr.getExprText().toUpperCase() ); } } if( this.restoreBasicLines != null ) { for( BasicLineExpr lineExpr : this.restoreBasicLines ) { - boolean found = false; - if( this.dataBasicLines != null ) { - found = this.dataBasicLines.contains( - lineExpr.getExprText().toUpperCase() ); - } - if( !found ) { - appendLineNotFoundToErrLog( - lineExpr, - " oder enth\u00E4lt keine DATA-Anweisung" ); - } + rv.add( lineExpr.getExprText().toUpperCase() ); } } + return rv; + } - // benutzerdefinierte Funktionen und Prozeduren pruefen - Collection callables = this.name2Callable.values(); - if( callables != null ) { - for( CallableEntry entry : callables ) { - if( !entry.isCalled() - && (this.errCnt == 0) - && this.options.getWarnUnusedItems() ) - { - putWarning( - entry.getSourceLineNum(), - entry.getBasicLineNum(), - entry.toString() + " wird nicht aufgerufen" ); - } - if( entry.isImplemented() ) { - if( this.options.getWarnUnusedItems() ) { - int nVars = entry.getVarCount(); - for( int i = 0; i < nVars; i++ ) { - String varName = entry.getVarName( i ); - if( varName != null ) { - if( !entry.isVarUsed( varName ) ) { - putWarning( - entry.getVarSourceLineNum( varName ), - entry.getVarBasicLineNum( varName ), - "Lokale Variable " + varName - + " wird nicht verwendet" ); - } - } - } - } - } else { - long basicLineNum = entry.getBasicLineNum(); - int sourceLineNum = entry.getSourceLineNum(); - int callSourceLineNum = entry.getFirstCallSourceLineNum(); - if( callSourceLineNum > 0 ) { - sourceLineNum = callSourceLineNum; - basicLineNum = entry.getFirstCallBasicLineNum(); - } - appendLineNumMsgToErrLog( - sourceLineNum, - basicLineNum, - entry.toString() + " nicht implementiert", - "Fehler" ); - incErrorCount(); - } + + public String getUsrLabel( int usrNum ) + { + String rv = String.format( "M_USR%d", usrNum ); + this.usrLabels.add( rv ); + return rv; + } + + + public boolean isLangCode( String langCode ) + { + boolean rv = false; + if( langCode != null ) { + String curLangCode = this.options.getLangCode(); + if( curLangCode != null ) { + rv = langCode.equalsIgnoreCase( curLangCode ); } } + return rv; + } - // globale Variablen pruefen - if( this.options.getWarnUnusedItems() ) { - Collection globalVars = name2GlobalVar.values(); - if( globalVars != null ) { - for( VarDecl varDecl : globalVars ) { - if( !varDecl.isUsed() ) { - putWarning( - varDecl.getSourceLineNum(), - varDecl.getBasicLineNum(), - varDecl.toString() + " wird nicht verwendet" ); - } - } - } + + public boolean needsDriver( IODriver driver ) + { + return this.ioDriverModes.containsKey( driver ) + || this.ioDriverModes.containsKey( IODriver.ALL ); + } + + + public void lockTmpStrBuf() throws PrgException + { + if( this.tmpStrBufUsed ) { + throw new PrgException( "String-Funktion hier nicht erlaubt," + + " da der interne String-Puffer bereits durch" + + " eine andere String-Funktion belegt ist\n" + + "Weisen Sie bitte den String-Ausdruck einer Variablen zu" + + " und verwenden Sie diese hier." ); } + this.tmpStrBufUsed = true; } - private void parseSourceLine( String lineText ) throws - IOException, - TooManyErrorsException + public String nextLabel() { - /* - * Pruefen, ob im Fall eines impliziten Endes des Hauptprogramms - * auf Code zur Beendigung des Programms verzichtet werden kann. - */ - this.suppressExit = false; - int lastLinePos = this.asmOut.getLastLinePos(); - String lastInst = this.asmOut.substring( lastLinePos ); - int tabPos = lastInst.indexOf( (char) '\t' ); - if( tabPos > 0 ) { - lastInst = lastInst.substring( tabPos ); + return String.format( "M%d", this.labelNum++ ); + } + + + public void parseASM( + CharacterIterator iter, + boolean isFunc ) throws PrgException + { + if( isFunc ) { + BasicUtil.parseToken( iter, '(' ); } - if( lastInst.startsWith( "\tJP" ) || lastInst.startsWith( "\tJR" ) ) { - if( lastInst.indexOf( (char) ',' ) < 0 ) { - // unbedingter Sprungbefehl - this.suppressExit = true; + do { + boolean data = false; + boolean bss = false; + if( !isFunc && !BasicUtil.checkKeyword( iter, "CODE" ) ) { + if( BasicUtil.checkKeyword( iter, "DATA" ) ) { + data = true; + } else if( BasicUtil.checkKeyword( iter, "BSS" ) ) { + bss = true; + } + } + String text = parseStringLiteral( iter ); + if( data ) { + if( this.userData == null ) { + this.userData = new StringBuilder( 0x0800 ); + } + this.userData.append( text ); + this.userData.append( (char) '\n' ); + } else if( bss ) { + if( this.userBSS == null ) { + this.userBSS = new StringBuilder( 0x0800 ); + } + this.userBSS.append( text ); + this.userBSS.append( (char) '\n' ); + } else { + this.asmOut.append( text ); + this.asmOut.newLine(); } - } else if( lastInst.equals( "\tRET\n" ) ) { - this.suppressExit = true; + } while( BasicUtil.checkToken( iter, ',' ) ); + if( isFunc ) { + BasicUtil.parseToken( iter, ')' ); } + } - // Zeile verarbeiten - CharacterIterator iter = null; - try { - iter = new StringCharacterIterator( lineText ); - this.curBasicLineNum = -1; - // Quelltextzeile als Kommentar - if( this.options.getShowAssemblerText() ) { - char ch = skipSpaces( iter ); - if( ch != CharacterIterator.DONE ) { - this.asmOut.append( "\n;" ); - while( ch != CharacterIterator.DONE ) { - this.asmOut.append( ch ); - ch = iter.next(); - } - this.asmOut.newLine(); - } - } - iter.setIndex( 0 ); - - /* - * auf Marke prufen, - * Eine Marke wird mit einem Doppelpunkt abgeschlossen - * und muss einzeln auf einer Zeile stehen, damit sie - * syntaktisch von einer Anweisung unterschieden werden kann. - */ - String labelText = null; - char ch = skipSpaces( iter ); - int begPos = iter.getIndex(); - int destLinePos = this.asmOut.length(); - if( ((ch >= 'A') && (ch <= 'Z')) - || ((ch >= 'a') && (ch <= 'z')) ) - { - ch = iter.next(); - while( ((ch >= 'A') && (ch <= 'Z')) - || ((ch >= 'a') && (ch <= 'z')) - || ((ch >= '0') && (ch <= '9')) - || (ch == '_') ) - { - ch = iter.next(); + public void parseCallableCall( + CharacterIterator iter, + CallableEntry entry ) throws PrgException + { + if( this.options.getCheckStack() + && (this.options.getStackSize() > 0) ) + { + this.asmOut.append_LD_DE_nn( entry.getTotalArgSize() + + entry.getTotalVarSize() ); + this.asmOut.append( "\tCALL\tCKSTKN\n" ); + addLibItem( BasicLibrary.LibItem.CKSTK ); + } + int nArgs = entry.getArgCount(); + if( nArgs > 0 ) { + BasicUtil.parseToken( iter, '(' ); + for( int i = 0; i < nArgs; i++ ) { + if( i > 0 ) { + BasicUtil.parseToken( iter, ',' ); } - if( ch == ':' ) { - iter.next(); - if( skipSpaces( iter ) == CharacterIterator.DONE ) { - int endPos = iter.getIndex() - 1; - StringBuilder buf = new StringBuilder( endPos - begPos ); - iter.setIndex( begPos ); - while( iter.getIndex() < endPos ) { - buf.append( (char) iter.current() ); - iter.next(); - } - labelText = buf.toString(); - String upperLabel = labelText.toUpperCase(); - if( Arrays.binarySearch( sortedReservedWords, upperLabel ) >= 0 ) { - throw new PrgException( "Reserviertes Schl\u00FCsselwort" - + " als Marke nicht erlaubt" ); - } - if( this.basicLines.contains( upperLabel ) ) { - appendLineNumMsgToErrLog( - "\'" + labelText + "\': Marke bereits vorhanden", - "Fehler" ); - incErrorCount(); + if( entry.getArgType( i ) == DataType.STRING ) { + String text = BasicUtil.checkStringLiteral( this, iter ); + if( text != null ) { + this.asmOut.append( "\tLD\tHL," ); + this.asmOut.append( getStringLiteralLabel( text ) ); + this.asmOut.append( "\n" + + "\tPUSH\tHL\n" ); + } else { + SimpleVarInfo srcVar = checkVariable( iter ); + if( srcVar != null ) { + srcVar.ensureValueInDE( this.asmOut ); + this.asmOut.append( "\tCALL\tSVDUP\n" + + "\tPUSH\tDE\n" ); + addLibItem( BasicLibrary.LibItem.SVDUP ); + } else { + BasicExprParser.parseStringPrimExpr( this, iter ); + this.asmOut.append( "\tCALL\tSMACP\n" + + "\tPUSH\tHL\n" ); + addLibItem( BasicLibrary.LibItem.SMACP ); } - this.basicLines.add( upperLabel ); - this.lastBasicLineExpr = upperLabel; - this.asmOut.append( createBasicLineLabel( upperLabel ) ); - this.asmOut.append( ":\n" ); - destLinePos = this.asmOut.length(); } + } else { + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tPUSH\tHL\n" ); } } - if( labelText == null ) { - - // Zeilenummer - iter.setIndex( 0 ); - ch = skipSpaces( iter ); - Long lineNum = readLineNum( iter ); - if( lineNum != null ) { - this.lastBasicLineExpr = lineNum.toString(); - if( this.basicLines.contains( this.lastBasicLineExpr ) ) { - appendLineNumMsgToErrLog( - String.format( - "BASIC-Zeilennummer %s bereits vorhanden", - this.lastBasicLineExpr ), - "Fehler" ); - incErrorCount(); - } - this.curBasicLineNum = lineNum.longValue(); - if( this.curBasicLineNum <= this.lastBasicLineNum ) { - putWarning( - "BASIC-Zeilennummer nicht in aufsteigender Reihenfolge" ); - } - this.basicLines.add( this.lastBasicLineExpr ); - this.lastBasicLineNum = this.curBasicLineNum; - this.asmOut.append( createBasicLineLabel( this.lastBasicLineExpr ) ); - this.asmOut.append( ":\n" ); - destLinePos = this.asmOut.length(); - } - - /* - * Zeilennummer ggf. im Programm vermerken, - * aber nur, wenn dieser Code auch durchlaufen wuerde - */ - int begLineDebugDestPos = -1; - if( !this.suppressExit ) { - if( this.options.getPrintLineNumOnAbort() - && (this.curSourceLineNum < MAX_VALUE) ) - { - begLineDebugDestPos = this.asmOut.length(); - this.asmOut.append_LD_HL_nn( this.curSourceLineNum ); - this.asmOut.append( "\tLD\t(M_SRLN),HL\n" ); - if( (this.curBasicLineNum >= 0) - && (this.curBasicLineNum < MAX_VALUE) ) - { - this.asmOut.append_LD_HL_nn( (int) this.curBasicLineNum ); - this.asmOut.append( "\tLD\t(M_BALN),HL\n" ); - } - } - } - - // Anweisungen - int destInstPos = this.asmOut.length(); - parseInstructions( iter, begLineDebugDestPos ); - if( this.gcRequired ) { - this.gcRequired = false; - this.asmOut.append( "\tCALL\tMRGC\n" ); - addLibItem( BasicLibrary.LibItem.MRGC ); - } - - /* - * Wenn kein Code erzeugt wurde, - * muss auch die Zeilennummer nicht gemerkt werden. - */ - if( (this.asmOut.length() == destInstPos) - && (destInstPos > destLinePos) ) - { - this.asmOut.setLength( destLinePos ); - } + BasicUtil.parseToken( iter, ')' ); + } else { + if( BasicUtil.checkToken( iter, '(' ) ) { + BasicUtil.parseToken( iter, ')' ); } } - catch( PrgException ex ) { - String msg = ex.getMessage(); - if( msg == null ) { - msg = "Unbekannter Fehler"; - } - appendLineNumMsgToErrLog( msg, "Fehler" ); - if( iter != null ) { - StringBuilder buf = new StringBuilder( iter.getEndIndex() + 16 ); - buf.append( " " ); - int pos = iter.getIndex(); - char ch = iter.first(); - boolean done = false; - while( ch != CharacterIterator.DONE ) { - buf.append( ch ); - if( iter.getIndex() == pos ) { - buf.append( ERR_POS_TEXT ); - done = true; - } - ch = iter.next(); - } - if( !done ) { - buf.append( ERR_POS_TEXT ); + this.asmOut.append( "\tCALL\t" ); + this.asmOut.append( entry.getLabel() ); + this.asmOut.newLine(); + if( nArgs > 0 ) { + if( nArgs > 5 ) { + this.asmOut.append_LD_HL_nn( -nArgs ); + this.asmOut.append( "\tADD\tHL,SP\n" + + "\tLD\tSP,HL\n" ); + } else { + for( int i = 0; i < nArgs; i++ ) { + this.asmOut.append( "\tPOP\tHL\n" ); } - buf.append( (char) '\n' ); - appendToErrLog( buf.toString() ); } - incErrorCount(); } - finally { - // einzeilige IF-Anweisungen schliessen - while( !this.structureStack.isEmpty() ) { - StructureEntry entry = this.structureStack.peek(); - if( !(entry instanceof IfEntry) ) { - break; - } - IfEntry ifEntry = (IfEntry) entry; - if( ifEntry.isMultiLine() ) { - break; - } - this.structureStack.pop(); - String elseLabel = ifEntry.getElseLabel(); - if( elseLabel != null ) { - this.asmOut.append( elseLabel ); - this.asmOut.append( ":\n" ); - } - this.asmOut.append( ifEntry.getEndifLabel() ); - this.asmOut.append( ":\n" ); + if( entry instanceof FunctionEntry ) { + this.asmOut.append( "\tLD\tHL,(M_FRET)\n" ); + addLibItem( BasicLibrary.LibItem.M_FRET ); + if( ((FunctionEntry) entry).getReturnType() == DataType.STRING ) { + this.gcRequired = true; } } + entry.putCallPos( this.curSource, this.curBasicLineNum ); } - private void parseInstructions( - CharacterIterator iter, - int begLineDebugDestPos ) throws PrgException + /* + * Die Methode parst eine Kanalnummer. + * Die Anfangsadresse des Kanalzeigerfeldes steht dann in HL. + * Das Doppelkreuz ist zu dem Zeitpunkt bereits geparst. + */ + public void parseIOChannelNumToPtrFldAddrInHL( + CharacterIterator iter, + AtomicBoolean constOut ) throws PrgException { - if( skipSpaces( iter ) != CharacterIterator.DONE ) { - for(;;) { - this.separatorChecked = false; - parseInstruction( iter, begLineDebugDestPos ); - char ch = skipSpaces( iter ); - if( ch == CharacterIterator.DONE ) { + Integer channel = BasicUtil.readNumber( iter ); + if( channel != null ) { + switch( channel.intValue() ) { + case 1: + this.asmOut.append( "\tLD\tHL,IOCTB1\n" ); + addLibItem( BasicLibrary.LibItem.IOCTB1 ); break; - } - if( !this.separatorChecked ) { - if( ch != ':' ) { - throwUnexpectedChar( ch ); - } - iter.next(); - } - begLineDebugDestPos = -1; + case 2: + this.asmOut.append( "\tLD\tHL,IOCTB2\n" ); + addLibItem( BasicLibrary.LibItem.IOCTB2 ); + break; + default: + throwIOChannelNumOutOfRange(); + } + if( constOut != null ) { + constOut.set( true ); + } + } else { + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tCALL\tIOCADR\n" ); + addLibItem( BasicLibrary.LibItem.IOCADR ); + addLibItem( BasicLibrary.LibItem.IOCTB1 ); + addLibItem( BasicLibrary.LibItem.IOCTB2 ); + if( constOut != null ) { + constOut.set( false ); } } } - private void parseInstruction( - CharacterIterator iter, - int begLineDebugDestPos ) throws PrgException + public void putWarningNonAsciiChar( char ch ) { - this.tmpStrBufUsed = false; + + putWarning( + String.format( + "\'%c\': kein ASCII-Zeichen," + + " kann im Zielsystem ein anderes Zeichen sein", + ch ) ); + } - char ch = skipSpaces( iter ); - int begPos = iter.getIndex(); - if( (ch == '!') || (ch == '\'') ) { // Kommentar - iter.next(); - parseREM( iter ); - } - else if( ch == '?' ) { - iter.next(); - checkMainPrgScope(); - checkCreateStackFrame(); - parsePRINT( iter ); - } else { - boolean done = false; - String name = checkIdentifier( iter ); - if( name != null ) { - done = true; - if( name.equals( "ASM" ) ) { - checkCreateStackFrame(); - if( (begLineDebugDestPos >= 0) - && (begLineDebugDestPos < this.asmOut.length()) ) - { - this.asmOut.setLength( begLineDebugDestPos ); - begLineDebugDestPos = -1; - } - parseASM( iter, false ); - } else if( name.equals( "DECLARE" ) ) { - parseDECLARE( iter ); - } else if( name.equals( "REM" ) ) { - parseREM( iter ); - } else if( name.equals( "FUNCTION" ) ) { - parseCallableImpl( iter, true ); - } else if( name.equals( "LOCAL" ) ) { - parseLOCAL( iter ); - } else if( name.equals( "SUB" ) ) { - parseCallableImpl( iter, false ); - } else { - /* - * Alle weiteren Anweisungen duerfen nur im Hauptprogramm - * oder in einer Funktion bzw. Prozedur vorkommen. - * Ausserdem muss im Fall einer Funktion bzw. Prozedur - * der Stack-Rahmen angelegt sein. - */ - checkMainPrgScope(); - checkCreateStackFrame(); - if( name.equals( "ACCEPT" ) ) { - parseACCEPT( iter ); - } else if( name.equals( "BORDER" ) ) { - parseBORDER( iter ); - } else if( name.equals( "CALL" ) ) { - parseCALL( iter ); - } else if( name.equals( "CIRCLE" ) ) { - parseCIRCLE( iter ); - } else if( name.equals( "CLOSE" ) ) { - parseCLOSE( iter ); - } else if( name.equals( "CLS" ) ) { - parseCLS(); - } else if( name.equals( "COLOR" ) ) { - parseCOLOR( iter ); - } else if( name.equals( "CONNECT" ) ) { - parseCONNECT( iter ); - } else if( name.equals( "CURSOR" ) ) { - parseCURSOR( iter ); - } else if( name.equals( "DATA" ) ) { - parseDATA( iter ); - } else if( name.equals( "DATAGRAM" ) ) { - parseDATAGRAM( iter ); - } else if( name.equals( "DEF" ) ) { - parseDEF( iter ); - } else if( name.equals( "DEFUSR" ) ) { - parseDEFUSR( iter ); - } else if( name.equals( "DEFUSR0" ) ) { - parseDEFUSR( iter, 0 ); - } else if( name.equals( "DEFUSR1" ) ) { - parseDEFUSR( iter, 1 ); - } else if( name.equals( "DEFUSR2" ) ) { - parseDEFUSR( iter, 2 ); - } else if( name.equals( "DEFUSR3" ) ) { - parseDEFUSR( iter, 3 ); - } else if( name.equals( "DEFUSR4" ) ) { - parseDEFUSR( iter, 4 ); - } else if( name.equals( "DEFUSR5" ) ) { - parseDEFUSR( iter, 5 ); - } else if( name.equals( "DEFUSR6" ) ) { - parseDEFUSR( iter, 6 ); - } else if( name.equals( "DEFUSR7" ) ) { - parseDEFUSR( iter, 7 ); - } else if( name.equals( "DEFUSR8" ) ) { - parseDEFUSR( iter, 8 ); - } else if( name.equals( "DEFUSR9" ) ) { - parseDEFUSR( iter, 9 ); - } else if( name.equals( "DIM" ) ) { - parseDIM( iter ); - } else if( name.equals( "DOKE" ) ) { - parseDOKE( iter ); - } else if( name.equals( "DO" ) ) { - parseDO( iter ); - } else if( name.equals( "DRAW" ) ) { - parseDRAW( iter ); - } else if( name.equals( "DRAWR" ) ) { - parseDRAWR( iter ); - } else if( name.equals( "ELSE" ) ) { - parseELSE( iter ); - } else if( name.equals( "ELSEIF" ) ) { - parseELSEIF( iter ); - } else if( name.equals( "END" ) ) { - parseEND( iter ); - } else if( name.equals( "ENDIF" ) ) { - parseENDIF( iter ); - } else if( name.equals( "EXIT" ) ) { - parseEXIT( iter ); - } else if( name.equals( "FLUSH" ) ) { - parseFLUSH( iter ); - } else if( name.equals( "FOR" ) ) { - parseFOR( iter ); - } else if( name.equals( "GOSUB" ) ) { - parseGOSUB( iter ); - } else if( name.equals( "GOTO" ) ) { - parseGOTO( iter ); - } else if( name.equals( "IF" ) ) { - parseIF( iter ); - } else if( name.equals( "INK" ) ) { - parseINK( iter ); - } else if( name.equals( "INPUT" ) ) { - parseINPUT( iter ); - } else if( name.equals( "LABEL" ) ) { - parseLABEL( iter ); - } else if( name.equals( "LET" ) ) { - parseLET( iter ); - } else if( name.equals( "LINE" ) ) { - parseLINE( iter ); - } else if( name.equals( "LOCATE" ) ) { - parseLOCATE( iter ); - } else if( name.equals( "LOOP" ) ) { - parseLOOP( iter ); - } else if( name.equals( "LPRINT" ) ) { - parseLPRINT( iter ); - } else if( name.equals( "MOVE" ) ) { - parseMOVE( iter ); - } else if( name.equals( "MOVER" ) ) { - parseMOVER( iter ); - } else if( name.equals( "NEXT" ) ) { - parseNEXT( iter ); - } else if( name.equals( "ON" ) ) { - parseON( iter ); - } else if( name.equals( "OPEN" ) ) { - parseOPEN( iter ); - } else if( name.equals( "OUT" ) ) { - parseOUT( iter ); - } else if( name.equals( "PAPER" ) ) { - parsePAPER( iter ); - } else if( name.equals( "PASSWORD" ) ) { - parsePASSWORD( iter ); - } else if( name.equals( "PAUSE" ) ) { - parsePAUSE( iter ); - } else if( name.equals( "PEN" ) ) { - parsePEN( iter ); - } else if( name.equals( "PLOT" ) ) { - parsePLOT( iter ); - } else if( name.equals( "PLOTR" ) ) { - parsePLOTR( iter ); - } else if( name.equals( "POKE" ) ) { - parsePOKE( iter ); - } else if( name.equals( "PRESET" ) ) { - parsePRESET( iter ); - } else if( name.equals( "PRINT" ) ) { - parsePRINT( iter ); - } else if( name.equals( "PSET" ) ) { - parsePSET( iter ); - } else if( name.equals( "READ" ) ) { - parseREAD( iter ); - } else if( name.equals( "REM" ) ) { - parseREM( iter ); - } else if( name.equals( "RESTORE" ) ) { - parseRESTORE( iter ); - } else if( name.equals( "RETURN" ) ) { - parseRETURN(); - } else if( name.equals( "SCREEN" ) ) { - parseSCREEN( iter ); - } else if( name.equals( "SEND" ) ) { - parseSEND( iter ); - } else if( name.equals( "SET" ) ) { - parseSET( iter ); - } else if( name.equals( "WAIT" ) ) { - parseWAIT( iter ); - } else if( name.equals( "WEND" ) ) { - parseWEND( iter ); - } else if( name.equals( "WHILE" ) ) { - parseWHILE( iter ); - } else { - if( !checkReturnValueAssignment( iter, name ) ) { - CallableEntry entry = this.name2Callable.get( name ); - if( entry != null ) { - parseCallableCall( iter, entry ); - } else { - SimpleVarInfo varInfo = checkVariable( iter, name ); - if( varInfo != null ) { - parseAssignment( iter, varInfo ); - } else { - done = false; - } - } - } - } - } - } - if( !done ) { - throw new PrgException( - "Anweisung, Prozedur oder Variable erwartet" ); - } - } + + public void putWarningOutOfRange() + { + putWarning( "Wert au\u00DFerhalb des Wertebereiches" ); } - private void parseASM( - CharacterIterator iter, - boolean isFunc ) throws PrgException + public boolean usesErrorVars() { - if( isFunc ) { - parseToken( iter, '(' ); - } - do { - boolean data = false; - boolean bss = false; - if( !isFunc && !checkKeyword( iter, "CODE" ) ) { - if( checkKeyword( iter, "DATA" ) ) { - data = true; - } else if( checkKeyword( iter, "BSS" ) ) { - bss = true; - } - } - String text = checkStringLiteral( iter ); - if( text == null ) { - throw new PrgException( "String-Literal erwartet" ); - } - if( data ) { - if( this.userData == null ) { - this.userData = new StringBuilder( 0x0800 ); - } - this.userData.append( text ); - this.userData.append( (char) '\n' ); - } else if( bss ) { - if( this.userBSS == null ) { - this.userBSS = new StringBuilder( 0x0800 ); - } - this.userBSS.append( text ); - this.userBSS.append( (char) '\n' ); - } else { - this.asmOut.append( text ); - this.asmOut.newLine(); - } - } while( checkToken( iter, ',' ) ); - if( isFunc ) { - parseToken( iter, ')' ); - } + return this.libItems.contains( BasicLibrary.LibItem.M_ERN ) + || this.libItems.contains( BasicLibrary.LibItem.M_ERT ); } - private void parseACCEPT( CharacterIterator iter ) throws PrgException + public boolean usesLibItem( BasicLibrary.LibItem libItem ) { - checkKCNetSupported(); - parseExpr( iter ); - this.asmOut.append( "\tLD\t(M_PORT),HL\n" ); - parseKeywordAS( iter ); - checkToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, true, null ); - this.asmOut.append( "\tCALL\tACCEPT\n" ); - addLibItem( BasicLibrary.LibItem.M_PORT ); - addLibItem( BasicLibrary.LibItem.ACCEPT ); + return this.libItems.contains( libItem ); } - private void parseBORDER( CharacterIterator iter ) throws PrgException + /* --- private Methoden --- */ + + private void appendHeadCommentTo( AsmCodeBuf buf ) { - if( this.target.supportsBorderColor() ) { - parseExpr( iter ); - this.asmOut.append( "\tCALL\tXBORDER\n" ); - addLibItem( BasicLibrary.LibItem.XBORDER ); - } else { - try { - setCodeGenEnabled( false ); - parseExpr( iter ); - } - finally { - setCodeGenEnabled( true ); - } + if( this.options.getShowAssemblerText() ) { + buf.append( ";\n" + + ";Dieser Quelltext wurde vom" + + " JKCEMU-BASIC-Compiler erzeugt.\n" + + ";http://www.jens-mueller.org/jkcemu/\n" + + ";\n" ); } } - private void parseCALL( CharacterIterator iter ) throws PrgException + private void appendLineNotFoundToErrLog( + BasicLineExpr lineExpr, + String trailingMsg ) throws TooManyErrorsException { - if( checkToken( iter, '*' ) ) { - skipSpaces( iter ); - Integer value = readHex( iter ); - if( value == null ) { - throwHexDigitExpected(); + if( lineExpr != null ) { + StringBuilder buf = new StringBuilder( 128 ); + if( lineExpr.appendMsgPrefixTo( "Fehler", buf ) ) { + buf.append( ": " ); } - this.asmOut.append_LD_HL_nn( value.intValue() ); - } else { - parseExpr( iter ); - } - boolean insideCallable = (getCallableEntry() != null); - if( insideCallable ) { - this.asmOut.append( "\tPUSH\tIY\n" ); - } - this.asmOut.append( "\tCALL\tJP_HL\n" ); - if( insideCallable ) { - this.asmOut.append( "\tPOP\tIY\n" ); + if( lineExpr.isLabel() ) { + buf.append( "Marke \'" ); + buf.append( lineExpr.getExprText() ); + buf.append( (char) '\'' ); + } else { + buf.append( "BASIC-Zeilennummer " ); + buf.append( lineExpr.getExprText() ); + } + buf.append( " nicht gefunden" ); + if( trailingMsg != null ) { + buf.append( trailingMsg ); + } + buf.append( (char) '\n' ); + appendToErrLog( buf.toString() ); + incErrorCount(); } - addLibItem( BasicLibrary.LibItem.JP_HL ); } - private void parseCIRCLE( CharacterIterator iter ) throws PrgException + private void appendLineNumMsgToErrLog( + BasicSourcePos sourcePos, + String msg, + String msgType ) { - checkGraphicsSupported(); - parsePointToMem( iter, "M_CIMX", "M_CIMY" ); - parseToken( iter, ',' ); - parseExpr( iter ); - this.asmOut.append( "\tLD\t(M_CIRA),HL\n" - + "\tCALL\tCIRCLE\n" ); - addLibItem( BasicLibrary.LibItem.CIRCLE ); + StringBuilder buf = new StringBuilder( 128 ); + if( sourcePos != null ) { + if( sourcePos.appendMsgPrefixTo( msgType, buf ) ) { + buf.append( ": " ); + } + } + buf.append( msg ); + if( !msg.endsWith( "\n" ) ) { + buf.append( (char) '\n' ); + } + appendToErrLog( buf.toString() ); } - private void parseCLOSE( CharacterIterator iter ) throws PrgException + private void appendLineNumMsgToErrLog( String msg, String msgType ) { - parseToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, false, null ); - this.asmOut.append( "\tCALL\tIOCLOSE\n" ); - addLibItem( BasicLibrary.LibItem.IOCLOSE ); + appendLineNumMsgToErrLog( + new BasicSourcePos( this.curSource, this.curBasicLineNum ), + msg, + msgType ); } - private void parseCLS() + private void appendToErrLog( String text ) { - if( this.target.supportsXCLS() ) { - this.asmOut.append( "\tCALL\tXCLS\n" ); - addLibItem( BasicLibrary.LibItem.XCLS ); - } else { - this.asmOut.append( "\tLD\tA,0CH\n" - + "\tCALL\tXOUTCH\n" ); - addLibItem( BasicLibrary.LibItem.XOUTCH ); - } + if( this.logger != null ) + this.logger.appendToErrLog( text ); } - private void parseCOLOR( CharacterIterator iter ) throws PrgException + private boolean checkInstruction( CharacterIterator iter, String keyword ) { - if( this.target.supportsColors() ) { - parseExpr( iter ); - if( checkToken( iter, ',' ) ) { - int pos = this.asmOut.length(); - parseExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tEX\tHL,DE\n" - + "\tPOP\tHL\n" ); - } - this.asmOut.append( "\tCALL\tXCOLOR\n" ); - addLibItem( BasicLibrary.LibItem.XCOLOR ); - } else { - this.asmOut.append( "\tCALL\tXINK\n" ); - addLibItem( BasicLibrary.LibItem.XINK ); - } - } else { - try { - setCodeGenEnabled( false ); - parseExpr( iter ); - if( checkToken( iter, ',' ) ) { - parseExpr( iter ); - } - } - finally { - setCodeGenEnabled( true ); - } - } - if( checkToken( iter, ',' ) ) { - parseBORDER( iter ); - } + return BasicUtil.checkKeyword( iter, keyword, true, true ); } - private void parseCONNECT( CharacterIterator iter ) throws PrgException + private void checkPutWarningFileDriverDisabled() { - checkKCNetSupported(); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tLD\t(M_HOST),HL\n" ); - parseToken( iter, ',' ); - parseExpr( iter ); - this.asmOut.append( "\tLD\t(M_PORT),HL\n" ); - parseKeywordAS( iter ); - checkToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, true, null ); - this.asmOut.append( "\tCALL\tCONNECT\n" ); - addLibItem( BasicLibrary.LibItem.M_HOST ); - addLibItem( BasicLibrary.LibItem.M_PORT ); - addLibItem( BasicLibrary.LibItem.CONNECT ); + if( !this.options.isOpenFileEnabled() ) { + putWarning( "Dateisystemtreiber in den Compiler-Optionen deaktiviert" ); + } } - private void parseCURSOR( CharacterIterator iter ) throws PrgException + private void checkPutWarningVdipDriverDisabled() { - if( this.target.supportsXCURS() ) { - parseExpr( iter ); - this.asmOut.append( "\tCALL\tXCURS\n" ); - addLibItem( BasicLibrary.LibItem.XCURS ); - } else { - try { - setCodeGenEnabled( false ); - parseExpr( iter ); - } - finally { - setCodeGenEnabled( true ); - } + if( !this.options.isOpenVdipEnabled() ) { + putWarning( "VDIP-Treiber in den Compiler-Optionen deaktiviert" ); } } - private void parseDATA( CharacterIterator iter ) throws PrgException + private Integer checkSignedNumber( CharacterIterator iter ) + throws PrgException { - if( this.dataOut == null ) { - this.dataOut = new AsmCodeBuf( 0x400 ); + boolean neg = false; + char ch = BasicUtil.skipSpaces( iter ); + int pos = iter.getIndex(); + if( ch == '+' ) { + iter.next(); + } else if( ch == '-' ) { + iter.next(); + neg = true; } - if( this.lastBasicLineExpr != null ) { - if( this.dataBasicLines == null ) { - this.dataBasicLines = new TreeSet(); - } - if( this.dataBasicLines.add( this.lastBasicLineExpr ) ) { - this.dataOut.append( createDataLineLabel( this.lastBasicLineExpr ) ); - this.dataOut.append( ":\n" ); - } + Integer rv = BasicUtil.readNumber( iter ); + if( (rv != null) && neg ) { + rv = new Integer( -rv.intValue() ); } - do { - String text = checkStringLiteral( iter ); - if( text != null ) { - this.dataOut.append( "\tDB\t" ); - this.dataOut.appendHex2( DATA_STRING ); - this.dataOut.newLine(); - this.dataOut.appendStringLiteral( text ); - } else { - Integer value = checkSignedNumber( iter ); - if( value == null ) { - throw new PrgException( "Integer- oder String-Literal erwartet" ); - } - if( (value.intValue() >= 0) && (value.intValue() < 0x0100) ) { - this.dataOut.append( "\tDB\t" ); - this.dataOut.appendHex2( DATA_INT1 ); - this.dataOut.append( (char) ',' ); - this.dataOut.appendHex2( value.intValue() ); - this.dataOut.newLine(); - } else { - this.dataOut.append( "\tDB\t" ); - this.dataOut.appendHex2( DATA_INT2 ); - this.dataOut.newLine(); - this.dataOut.append( "\tDW\t" ); - this.dataOut.appendHex4( value.intValue() ); - this.dataOut.newLine(); - } - } - } while( checkToken( iter, ',' ) ); - addLibItem( BasicLibrary.LibItem.DATA ); - } - - - private void parseDATAGRAM( CharacterIterator iter ) throws PrgException - { - checkKCNetSupported(); - parseExpr( iter ); - this.asmOut.append( "\tLD\t(M_PORT),HL\n" ); - parseKeywordAS( iter ); - checkToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, true, null ); - this.asmOut.append( "\tCALL\tDATAGRAM\n" ); - addLibItem( BasicLibrary.LibItem.M_PORT ); - addLibItem( BasicLibrary.LibItem.DATAGRAM ); + if( rv == null ) { + iter.setIndex( pos ); + } + return rv; } - private void parseDECLARE( CharacterIterator iter ) throws PrgException + private SimpleVarInfo checkVariable( + CharacterIterator iter ) throws PrgException { - CallableEntry entry = null; - if( checkKeyword( iter, "FUNCTION" ) ) { - parseCallableDecl( iter, true, false ); - } else if( checkKeyword( iter, "SUB" ) ) { - parseCallableDecl( iter, false, false ); - } else { - throw new PrgException( "FUNCTION oder SUB erwartet" ); + int begPos = iter.getIndex(); + SimpleVarInfo varInfo = checkVariable( + iter, + BasicUtil.checkIdentifier( iter ) ); + if( varInfo == null ) { + iter.setIndex( begPos ); } + return varInfo; } - private void parseDEF( CharacterIterator iter ) throws PrgException + private static String createBasicLineLabel( String lineExprText ) { - if( !checkKeyword( iter, "USR" ) ) { - throw new PrgException( "USR erwartet" ); - } - int usrNum = parseUsrNum( iter ); - parseDEFUSR( iter, usrNum ); + return LINE_LABEL_PREFIX + lineExprText.toUpperCase(); } - private void parseDEFUSR( CharacterIterator iter ) throws PrgException + private static String createDataLineLabel( String lineExprText ) { - int usrNum = parseUsrNum( iter ); - parseDEFUSR( iter, usrNum ); + return DATA_LABEL_PREFIX + lineExprText.toUpperCase(); } - private void parseDEFUSR( - CharacterIterator iter, - int usrNum ) throws PrgException + private void incErrorCount() throws TooManyErrorsException { - parseToken( iter, '=' ); - parseExpr( iter ); - this.asmOut.append( "\tLD\t(" ); - this.asmOut.append( getUsrLabel( usrNum ) ); - this.asmOut.append( "),HL\n" ); - addLibItem( BasicLibrary.LibItem.E_USR ); + this.errCnt++; + if( this.errCnt >= 100 ) { + throw new TooManyErrorsException(); + } } - private void parseDIM( CharacterIterator iter ) throws PrgException + private void parseSourceText() + throws IOException, TooManyErrorsException { - CallableEntry entry = getCallableEntry(); - if( entry != null ) { - throw new PrgException( "Anweisung in einer Funktion/Prozedur" - + " nicht zul\u00E4ssig" ); - } - do { - String varName = checkIdentifier( iter ); - if( varName == null ) { - throwVarNameExpected(); + this.structureStack.clear(); + this.mainPrg = true; + this.gcRequired = false; + + while( this.curSource != null ) { + String line = this.curSource.readLine(); + if( line != null ) { + parseSourceLine( line ); + } else { + if( this.curSource != this.mainSource ) { + this.curSource = this.mainSource; + if( (this.curSource != null) + && this.libItems.contains( BasicLibrary.LibItem.M_SRNM ) ) + { + this.asmOut.append( "\tLD\tHL,0000H\n" + + "\tLD\t(M_SRNM),HL\n" ); + } + } else { + this.curSource = null; + } } - checkVarName( varName ); - parseToken( iter, '(' ); - VarDecl var = null; - int dim1 = parseNumber( iter ); - if( dim1 < 1 ) { - throwDimTooSmall(); + } + if( this.mainPrg ) { + if( this.options.getShowAssemblerText() ) { + this.asmOut.append( "\n;Programmende\n" ); } - if( checkToken( iter, ',' ) ) { - int dim2 = parseNumber( iter ); - if( dim2 < 1 ) { - throwDimTooSmall(); + this.asmOut.append( "\tJP\tXEXIT\n" ); + } + + // Pruefen, ob alle Strukturen geschlossen wurden + for( BasicSourcePos entry : this.structureStack ) { + appendLineNumMsgToErrLog( + entry, + entry.toString() + " nicht abgeschlossen", + "Fehler" ); + this.errCnt++; + } + + // Vorhandensein der Sprungziele und RESTORE-Positionen pruefen + if( this.destBasicLines != null ) { + for( BasicLineExpr lineExpr : this.destBasicLines ) { + if( !this.basicLines.contains( + lineExpr.getExprText().toUpperCase() ) ) + { + appendLineNotFoundToErrLog( lineExpr, null ); } - var = new VarDecl( - this.curSourceLineNum, - this.curBasicLineNum, - varName, - dim1, - dim2 ); - } else { - var = new VarDecl( - this.curSourceLineNum, - this.curBasicLineNum, - varName, - dim1 ); } - VarDecl tmpVar = this.name2GlobalVar.get( varName ); - if( tmpVar != null ) { - if( tmpVar.getDimCount() > 0 ) { - throw new PrgException( "Feldvariable bereits deklariert" ); - } else { - throw new PrgException( "Variable implizit als einfache" - + " Variable bereits deklariert" ); + } + if( this.restoreBasicLines != null ) { + for( BasicLineExpr lineExpr : this.restoreBasicLines ) { + boolean found = false; + if( this.dataBasicLines != null ) { + found = this.dataBasicLines.contains( + lineExpr.getExprText().toUpperCase() ); + } + if( !found ) { + appendLineNotFoundToErrLog( + lineExpr, + " oder enth\u00E4lt keine DATA-Anweisung" ); } } - parseToken( iter, ')' ); - this.name2GlobalVar.put( varName, var ); - } while( checkToken( iter, ',' ) ); - } - + } - private void parseDO( CharacterIterator iter ) throws PrgException - { - String loopLabel = nextLabel(); - this.asmOut.append( loopLabel ); - this.asmOut.append( ":\n" ); - checkAppendBreakCheck(); - this.structureStack.push( - new DoEntry( - this.curSourceLineNum, - this.curBasicLineNum, - loopLabel, - nextLabel() ) ); - } - - - private void parseDOKE( CharacterIterator iter ) throws PrgException - { - int pos = this.asmOut.length(); - parseExpr( iter ); - parseToken( iter, ',' ); - Integer addr = removeLastCodeIfConstExpr( pos ); - if( addr != null ) { - parseExpr( iter ); - this.asmOut.append( "\tLD\t(" ); - this.asmOut.appendHex4( addr.intValue() ); - this.asmOut.append( "),HL\n" ); - } else { - pos = this.asmOut.length(); - parseExpr( iter ); - Integer value = removeLastCodeIfConstExpr( pos ); - if( value != null ) { - this.asmOut.append( "\tLD\t(HL)," ); - this.asmOut.appendHex2( value.intValue() ); - this.asmOut.append( "\n" - + "\tINC\tHL\n" - + "\tLD\t(HL)," ); - this.asmOut.appendHex2( value.intValue() >> 8 ); - this.asmOut.newLine(); - } else { - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); + // benutzerdefinierte Funktionen und Prozeduren pruefen + Collection callables = this.name2Callable.values(); + if( callables != null ) { + for( CallableEntry entry : callables ) { + if( !entry.isCalled() + && (this.errCnt == 0) + && this.options.getWarnUnusedItems() ) + { + putWarning( entry, entry.toString() + " wird nicht aufgerufen" ); + } + if( entry.isImplemented() ) { + if( this.options.getWarnUnusedItems() ) { + int nVars = entry.getVarCount(); + for( int i = 0; i < nVars; i++ ) { + String varName = entry.getVarName( i ); + if( varName != null ) { + if( !entry.isVarUsed( varName ) ) { + putWarning( + entry.getVarSourcePos( varName ), + "Lokale Variable " + varName + + " wird nicht verwendet" ); + } + } + } + } } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tEX\tDE,HL\n" - + "\tPOP\tHL\n" ); + BasicSourcePos callSourcePos = entry.getFirstCallSourcePos(); + appendLineNumMsgToErrLog( + callSourcePos != null ? callSourcePos : entry, + entry.toString() + " nicht implementiert", + "Fehler" ); + incErrorCount(); } - this.asmOut.append( "\tLD\t(HL),E\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),D\n" ); } } - } - - private void parseDRAW( CharacterIterator iter ) throws PrgException - { - if( checkKeyword( iter, "STEP" ) ) { - parseDRAWR( iter ); - } else { - checkGraphicsSupported(); - checkKeyword( iter, "TO" ); - parsePointToMem( iter, "M_LNEX", "M_LNEY" ); - this.asmOut.append( "\tCALL\tDRAW\n" ); - addLibItem( BasicLibrary.LibItem.DRAW ); + // globale Variablen pruefen + if( this.options.getWarnUnusedItems() ) { + Collection globalVars = name2GlobalVar.values(); + if( globalVars != null ) { + for( VarDecl varDecl : globalVars ) { + if( !varDecl.isUsed() ) { + putWarning( + varDecl, + varDecl.toString() + " wird nicht verwendet" ); + } + } + } } } - private void parseDRAWR( CharacterIterator iter ) throws PrgException + private void parseSourceLine( String lineText ) throws + IOException, + TooManyErrorsException { - checkGraphicsSupported(); - parsePointTo_DE_HL( iter ); - this.asmOut.append( "\tCALL\tDRAWR\n" ); - addLibItem( BasicLibrary.LibItem.DRAWR ); - } - + // Zeile verarbeiten + CharacterIterator iter = null; + try { + iter = new StringCharacterIterator( lineText ); + this.curBasicLineNum = -1; - private void parseELSE( CharacterIterator iter ) throws PrgException - { - for(;;) { - IfEntry ifEntry = null; - if( !this.structureStack.isEmpty() ) { - StructureEntry entry = this.structureStack.peek(); - if( entry instanceof IfEntry ) { - ifEntry = (IfEntry) entry; + // Quelltextzeile als Kommentar + if( this.options.getShowAssemblerText() + && this.options.getIncludeBasicLines() ) + { + char ch = BasicUtil.skipSpaces( iter ); + if( ch != CharacterIterator.DONE ) { + boolean codeCreationEnabled = this.asmOut.isEnabled(); + this.asmOut.setEnabled( true ); + this.asmOut.append( "\n;" ); + while( ch != CharacterIterator.DONE ) { + this.asmOut.append( ch ); + ch = iter.next(); + } + this.asmOut.newLine(); + this.asmOut.setEnabled( codeCreationEnabled ); } } - if( ifEntry == null ) { - throw new PrgException( "ELSE ohne IF" ); - } - String elseLabel = ifEntry.getElseLabel(); - if( elseLabel != null ) { - if( !ifEntry.isMultiLine() - && this.options.getPreferRelativeJumps() ) + iter.setIndex( 0 ); + + /* + * auf Marke prufen, + * Eine Marke wird mit einem Doppelpunkt abgeschlossen + * und muss einzeln auf einer Zeile stehen, damit sie + * syntaktisch von einer Anweisung unterschieden werden kann. + */ + String labelText = null; + char ch = BasicUtil.skipSpaces( iter ); + int begPos = iter.getIndex(); + int destLinePos = this.asmOut.length(); + if( ((ch >= 'A') && (ch <= 'Z')) + || ((ch >= 'a') && (ch <= 'z')) ) + { + ch = iter.next(); + while( ((ch >= 'A') && (ch <= 'Z')) + || ((ch >= 'a') && (ch <= 'z')) + || ((ch >= '0') && (ch <= '9')) + || (ch == '_') ) { - this.asmOut.append( "\tJR\t" ); - } else { - this.asmOut.append( "\tJP\t" ); + ch = iter.next(); + } + if( ch == ':' ) { + iter.next(); + if( BasicUtil.skipSpaces( iter ) == CharacterIterator.DONE ) { + int endPos = iter.getIndex() - 1; + StringBuilder buf = new StringBuilder( endPos - begPos ); + iter.setIndex( begPos ); + while( iter.getIndex() < endPos ) { + buf.append( (char) iter.current() ); + iter.next(); + } + labelText = buf.toString(); + String upperLabel = labelText.toUpperCase(); + if( isReservedWord( upperLabel ) ) { + throw new PrgException( "Reserviertes Schl\u00FCsselwort" + + " als Marke nicht erlaubt" ); + } + if( this.basicLines.contains( upperLabel ) ) { + appendLineNumMsgToErrLog( + "\'" + labelText + "\': Marke bereits vorhanden", + "Fehler" ); + incErrorCount(); + } + this.basicLines.add( upperLabel ); + this.lastBasicLineExpr = upperLabel; + this.asmOut.append( createBasicLineLabel( upperLabel ) ); + this.asmOut.append( ":\n" ); + destLinePos = this.asmOut.length(); + } } - this.asmOut.append( ifEntry.getEndifLabel() ); - this.asmOut.newLine(); - this.asmOut.append( elseLabel ); - this.asmOut.append( ":\n" ); - ifEntry.setElseLabel( null ); // ELSE als geparst markieren - break; - } else { - /* - * ELSE wurde schon geparst. - * Demzufolge muss sich das ELSE auf das vorherige IF beziehen. - * Deshalb wird hier der ELSE-Zweig der aktuellen IF-Anweisung - * geschlossen und im naechsten Schleifendurchlauf - * das ELSE der vorherigen IF-Anweisung verarbeitet. - */ - this.asmOut.append( ifEntry.getEndifLabel() ); - this.asmOut.append( ":\n" ); - this.structureStack.pop(); } - } - } + if( labelText == null ) { + // Zeilenummer + iter.setIndex( 0 ); + ch = BasicUtil.skipSpaces( iter ); + Long lineNum = readLineNum( iter ); + if( lineNum != null ) { + this.lastBasicLineExpr = lineNum.toString(); + if( this.basicLines.contains( this.lastBasicLineExpr ) ) { + appendLineNumMsgToErrLog( + String.format( + "BASIC-Zeilennummer %s bereits vorhanden", + this.lastBasicLineExpr ), + "Fehler" ); + incErrorCount(); + } + this.curBasicLineNum = lineNum.longValue(); + if( this.curBasicLineNum <= this.lastBasicLineNum ) { + putWarning( + "BASIC-Zeilennummer nicht in aufsteigender Reihenfolge" ); + } + this.basicLines.add( this.lastBasicLineExpr ); + this.lastBasicLineNum = this.curBasicLineNum; + this.asmOut.append( createBasicLineLabel( this.lastBasicLineExpr ) ); + this.asmOut.append( ":\n" ); + destLinePos = this.asmOut.length(); + } - private void parseELSEIF( CharacterIterator iter ) throws PrgException - { - IfEntry ifEntry = null; - if( !this.structureStack.isEmpty() ) { - StructureEntry entry = this.structureStack.peek(); - if( entry instanceof IfEntry ) { - ifEntry = (IfEntry) entry; + // Zeilennummer ggf. im Programm vermerken, + int begLineDebugDestPos = -1; + if( this.options.getPrintLineNumOnAbort() ) { + begLineDebugDestPos = this.asmOut.length(); + PrgSource curSource = this.curSource; + if( curSource != null ) { + int srcLineNum = curSource.getLineNum(); + if( srcLineNum < MAX_INT_VALUE ) { + this.asmOut.append_LD_HL_nn( srcLineNum ); + this.asmOut.append( "\tLD\t(M_SRLN),HL\n" ); + } + } + if( (this.curBasicLineNum >= 0) + && (this.curBasicLineNum < MAX_INT_VALUE) ) + { + this.asmOut.append_LD_HL_nn( (int) this.curBasicLineNum ); + this.asmOut.append( "\tLD\t(M_BALN),HL\n" ); + this.libItems.add( BasicLibrary.LibItem.M_BALN ); + } + } + + // Anweisungen + int destInstPos = this.asmOut.length(); + parseInstructions( iter, begLineDebugDestPos ); + if( this.gcRequired ) { + this.gcRequired = false; + this.asmOut.append( "\tCALL\tMRGC\n" ); + addLibItem( BasicLibrary.LibItem.MRGC ); + } + + /* + * Wenn kein Code erzeugt wurde, + * muss auch die Zeilennummer nicht gemerkt werden. + */ + if( (this.asmOut.length() == destInstPos) + && (destInstPos > destLinePos) ) + { + this.asmOut.setLength( destLinePos ); + } } } - if( ifEntry == null ) { - throw new PrgException( "ELSE ohne IF" ); - } - String elseLabel = ifEntry.getElseLabel(); - if( elseLabel == null ) { - throw new PrgException( "ELSEIF hinter ELSE nicht zul\u00E4ssig" ); - } - if( !ifEntry.isMultiLine() && this.options.getPreferRelativeJumps() ) { - this.asmOut.append( "\tJR\t" ); - } else { - this.asmOut.append( "\tJP\t" ); - } - this.asmOut.append( ifEntry.getEndifLabel() ); - this.asmOut.newLine(); - this.asmOut.append( elseLabel ); - this.asmOut.append( ":\n" ); - ifEntry.setElseLabel( nextLabel() ); // neue ELSE-Marke erzeugen - parseExpr( iter ); - this.asmOut.append( "\tLD\tA,H\n" - + "\tOR\tL\n" ); - checkKeyword( iter, "THEN" ); - if( !ifEntry.isMultiLine() && this.options.getPreferRelativeJumps() ) { - this.asmOut.append( "\tJR\tZ," ); - } else { - this.asmOut.append( "\tJP\tZ," ); - } - this.asmOut.append( ifEntry.getElseLabel() ); - this.asmOut.newLine(); - if( isEndOfInstr( iter ) ) { - this.separatorChecked = true; - } - } - - - private void parseEND( CharacterIterator iter ) throws PrgException - { - CallableEntry callableEntry = getCallableEntry(); - if( checkKeyword( iter, "FUNCTION" ) ) { - boolean ok = false; - if( callableEntry != null ) { - if( callableEntry instanceof FunctionEntry ) { - ok = true; - } - } - if( !ok ) { - throw new PrgException( - "Anweisung nur am Ende einer Funktion erlaubt" ); + catch( PrgException ex ) { + String msg = ex.getMessage(); + if( msg == null ) { + msg = "Unbekannter Fehler"; } - } else if( checkKeyword( iter, "SUB" ) ) { - boolean ok = false; - if( callableEntry != null ) { - if( callableEntry instanceof SubEntry ) { - ok = true; + appendLineNumMsgToErrLog( msg, "Fehler" ); + if( iter != null ) { + StringBuilder buf = new StringBuilder( iter.getEndIndex() + 16 ); + buf.append( " " ); + int pos = iter.getIndex(); + char ch = iter.first(); + boolean done = false; + while( ch != CharacterIterator.DONE ) { + buf.append( ch ); + if( iter.getIndex() == pos ) { + buf.append( ERR_POS_TEXT ); + done = true; + } + ch = iter.next(); } + if( !done ) { + buf.append( ERR_POS_TEXT ); + } + buf.append( (char) '\n' ); + appendToErrLog( buf.toString() ); } - if( !ok ) { - throw new PrgException( - "Anweisung nur am Ende einer Prozedur erlaubt" ); - } + incErrorCount(); } - if( callableEntry != null ) { - if( callableEntry instanceof FunctionEntry ) { - this.asmOut.append_LD_HL_IndirectIY( - ((FunctionEntry) callableEntry).getReturnVarIYOffs() ); - this.asmOut.append( "\tLD\t(M_FRET),HL\n" ); - addLibItem( BasicLibrary.LibItem.M_FRET ); - } - - // alle Strukturen abgeschlossen? + finally { + // einzeilige IF-Anweisungen schliessen while( !this.structureStack.isEmpty() ) { - StructureEntry entry = this.structureStack.pop(); - if( entry != callableEntry ) { - appendLineNumMsgToErrLog( - entry.getSourceLineNum(), - entry.getBasicLineNum(), - entry.toString() + " nicht abgeschlossen", - "Fehler" ); - this.errCnt++; - } - } - if( callableEntry.hasStackFrame() ) { - int nVars = callableEntry.getVarCount(); - // lokale String-Variablen freigeben - int nArgs = callableEntry.getArgCount(); - if( nArgs > 0 ) { - for( int i = 0; i < nArgs; i++ ) { - if( callableEntry.getArgType( i ) == DataType.STRING ) { - this.asmOut.append_LD_DE_IndirectIY( - callableEntry.getArgIYOffs( i, nArgs ) ); - this.asmOut.append( "\tCALL\tMFREE\n" ); - addLibItem( BasicLibrary.LibItem.MFREE ); - } - } + BasicSourcePos entry = this.structureStack.peek(); + if( !(entry instanceof IfEntry) ) { + break; } - if( nVars > 0 ) { - for( int i = 0; i < nVars; i++ ) { - if( callableEntry.getVarType( i ) == DataType.STRING ) { - boolean done = false; - int iyOffs = callableEntry.getVarIYOffs( i ); - if( callableEntry instanceof FunctionEntry ) { - if( ((FunctionEntry) callableEntry).getReturnVarIYOffs() - == iyOffs ) - { - this.asmOut.append_LD_DE_IndirectIY( iyOffs ); - this.asmOut.append( "\tCALL\tMMGC\n" ); - addLibItem( BasicLibrary.LibItem.MMGC ); - done = true; - } - } - if( !done ) { - this.asmOut.append_LD_DE_IndirectIY( iyOffs ); - this.asmOut.append( "\tCALL\tMFREE\n" ); - addLibItem( BasicLibrary.LibItem.MFREE ); - } - } - } - this.asmOut.append( "\tLD\tSP,IY\n" ); + IfEntry ifEntry = (IfEntry) entry; + if( ifEntry.isMultiLine() ) { + break; } - this.asmOut.append( "\tPOP\tIY\n" ); - } - this.asmOut.append( "\tRET\n" ); - } else { - this.asmOut.append( "\tJP\tXEXIT\n" ); - this.suppressExit = true; - } - } - - - private void parseENDIF( CharacterIterator iter ) throws PrgException - { - IfEntry ifEntry = null; - if( !this.structureStack.isEmpty() ) { - StructureEntry entry = this.structureStack.peek(); - if( entry instanceof IfEntry ) { - ifEntry = (IfEntry) entry; this.structureStack.pop(); + setCodeCreationDisabledLevel( + ifEntry.getCodeCreationDisabledLevel() ); + String elseLabel = ifEntry.getElseLabel(); + if( elseLabel != null ) { + this.asmOut.append( elseLabel ); + this.asmOut.append( ":\n" ); + } + this.asmOut.append( ifEntry.getEndifLabel() ); + this.asmOut.append( ":\n" ); } } - if( ifEntry == null ) { - throw new PrgException( "ENDIF ohne IF" ); - } - String elseLabel = ifEntry.getElseLabel(); - if( elseLabel != null ) { - this.asmOut.append( elseLabel ); - this.asmOut.append( ":\n" ); - } - this.asmOut.append( ifEntry.getEndifLabel() ); - this.asmOut.append( ":\n" ); } - private void parseEXIT( CharacterIterator iter ) throws PrgException + private void parseInstructions( + CharacterIterator iter, + int begLineDebugDestPos ) throws PrgException { - LoopEntry loopEntry = null; - int idx = this.structureStack.size() - 1; - while( idx >= 0 ) { - StructureEntry entry = this.structureStack.get( idx ); - if( entry instanceof LoopEntry ) { - loopEntry = (LoopEntry) entry; - break; - } - if( !(entry instanceof IfEntry) ) { - break; - } - --idx; - } - if( loopEntry == null ) { - throw new PrgException( "EXIT aus\u00DFerhalb einer Schleife" ); - } - if( !isEndOfInstr( iter ) ) { - if( !checkKeyword( iter, loopEntry.getLoopBegKeyword() ) ) { - throw new PrgException( loopEntry.getLoopBegKeyword() - + " oder Ende der Anweisung erwartet" ); + if( BasicUtil.skipSpaces( iter ) != CharacterIterator.DONE ) { + for(;;) { + this.separatorChecked = false; + parseInstruction( iter, begLineDebugDestPos ); + char ch = BasicUtil.skipSpaces( iter ); + if( ch == CharacterIterator.DONE ) { + break; + } + if( !this.separatorChecked ) { + if( ch != ':' ) { + BasicUtil.throwUnexpectedChar( ch ); + } + iter.next(); + } + begLineDebugDestPos = -1; } } - this.asmOut.append( "\tJP\t" ); - this.asmOut.append( loopEntry.getExitLabel() ); - this.asmOut.newLine(); } - private void parseFLUSH( CharacterIterator iter ) throws PrgException + private void parseInstruction( + CharacterIterator iter, + int begLineDebugDestPos ) throws PrgException { - parseToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, false, null ); - this.asmOut.append( "\tCALL\tIOFLUSH\n" ); - addLibItem( BasicLibrary.LibItem.IOFLUSH ); - } - + this.tmpStrBufUsed = false; - private void parseFOR( CharacterIterator iter ) throws PrgException - { - SimpleVarInfo varInfo = checkVariable( iter ); - if( varInfo != null ) { - if( varInfo.getDataType() != DataType.INTEGER ) { - varInfo = null; - } - } - if( varInfo == null ) { - throw new PrgException( "Integer-Variable erwartet" ); - } - if( !varInfo.hasStaticAddr() ) { - throw new PrgException( - "Laufvariable darf keine Feldvariable mit variablen" - + " Indexangaben sein." ); - } - parseAssignment( iter, varInfo ); - if( !checkInstruction( iter, "TO" ) ) { - throw new PrgException( "TO erwartet" ); - } - int toPos = this.asmOut.length(); - parseExpr( iter ); - Integer toValue = removeLastCodeIfConstExpr( toPos ); - if( toValue == null ) { - this.asmOut.append( "\tPUSH\tHL\n" ); + char ch = BasicUtil.skipSpaces( iter ); + int begPos = iter.getIndex(); + if( (ch == '!') || (ch == '\'') ) { // Kommentar + iter.next(); + parseREM( iter ); } - Integer stepValue = null; - if( checkInstruction( iter, "STEP" ) ) { - int stepPos = this.asmOut.length(); - parseExpr( iter ); - stepValue = removeLastCodeIfConstExpr( stepPos ); - if( stepValue == null ) { - this.asmOut.append( "\tPUSH\tHL\n" ); - } + else if( ch == '?' ) { + iter.next(); + checkMainPrgScope(); + checkCreateStackFrame(); + parsePRINT( iter ); } else { - stepValue = new Integer( 1 ); - } - String loopLabel = nextLabel(); - this.asmOut.append( loopLabel ); - this.asmOut.append( (char) ':' ); - checkAppendBreakCheck(); - this.asmOut.newLine(); - this.structureStack.push( - new ForEntry( - this.curSourceLineNum, - this.curBasicLineNum, - loopLabel, - nextLabel(), - varInfo, - toValue, - stepValue ) ); - } - - - private void parseGOSUB( CharacterIterator iter ) throws PrgException - { - checkAppendBreakCheck(); - if( this.options.getCheckStack() - && (this.options.getStackSize() > 0) ) - { - this.asmOut.append( "\tCALL\tCKSTK\n" ); - addLibItem( BasicLibrary.LibItem.CKSTK ); - } - String destLabel = parseDestLineExpr( iter ); - if( this.options.getCheckStack() ) { - String endOfInstLabel = nextLabel(); - this.asmOut.append( "\tLD\tHL," ); - this.asmOut.append( endOfInstLabel ); - this.asmOut.append( "\n" - + "\tPUSH\tHL\n" ); - this.asmOut.append_LD_A_n( MAGIC_GOSUB ); - this.asmOut.append( "\tPUSH\tAF\n" - + "\tJP\t" ); - this.asmOut.append( destLabel ); - this.asmOut.newLine(); - this.asmOut.append( endOfInstLabel ); - this.asmOut.append( ":\n" ); - } else { - this.asmOut.append( "\tCALL\t" ); - this.asmOut.append( destLabel ); - this.asmOut.newLine(); - } - } - - - private void parseGOTO( CharacterIterator iter ) throws PrgException - { - checkAppendBreakCheck(); - String destLabel = parseDestLineExpr( iter ); - this.asmOut.append( "\tJP\t" ); - this.asmOut.append( destLabel ); - this.asmOut.newLine(); - } - - - private void parseIF( CharacterIterator iter ) throws PrgException - { - parseExpr( iter ); - this.asmOut.append( "\tLD\tA,H\n" - + "\tOR\tL\n" ); - - // auf GOTO und mehrzeiliges IF pruefen - BasicLineExpr gotoLineExpr = null; - boolean multiLine = false; - if( checkKeyword( iter, "THEN" ) ) { - if( isEndOfInstr( iter ) ) { - multiLine = true; - } else { - char ch = skipSpaces( iter ); - if( (ch >= '0') && (ch <= '9') ) { - gotoLineExpr = BasicLineExpr.checkBasicLineExpr( - iter, - this.curSourceLineNum, - this.curBasicLineNum ); - } - } - } else { - if( isEndOfInstr( iter ) ) { - multiLine = true; - } - } - if( !multiLine && (gotoLineExpr == null) ) { - if( checkKeyword( iter, "GOTO" ) ) { - gotoLineExpr = BasicLineExpr.checkBasicLineExpr( - iter, - this.curSourceLineNum, - this.curBasicLineNum ); - if( gotoLineExpr == null ) { - throwBasicLineExprExpected(); - } - } - } - - /* - * Wenn hinter der Bedingung nur noch ein GOTO folgt - * und die Tastatur auch nicht auf Programmabbruch abgefragt werden muss, - * kann der Programmcode optimiert werden. - */ - if( (gotoLineExpr != null) - && !this.options.canBreakAlways() - && isEndOfInstr( iter ) ) - { - this.asmOut.append( "\tJP\tNZ," ); - this.asmOut.append( - createBasicLineLabel( gotoLineExpr.getExprText() ) ); - this.asmOut.newLine(); - this.destBasicLines.add( gotoLineExpr ); - } else { - IfEntry ifEntry = new IfEntry( - this.curSourceLineNum, - this.curBasicLineNum, - multiLine, - nextLabel(), - nextLabel() ); - this.structureStack.push( ifEntry ); - if( !multiLine && this.options.getPreferRelativeJumps() ) { - this.asmOut.append( "\tJR\tZ," ); - } else { - this.asmOut.append( "\tJP\tZ," ); - } - this.asmOut.append( ifEntry.getElseLabel() ); - this.asmOut.newLine(); - if( gotoLineExpr != null ) { - checkAppendBreakCheck(); - this.asmOut.append( "\tJP\t" ); - this.asmOut.append( - createBasicLineLabel( gotoLineExpr.getExprText() ) ); - this.asmOut.newLine(); - this.destBasicLines.add( gotoLineExpr ); - } else { - this.separatorChecked = true; - } - } - } - - - private void parseINK( CharacterIterator iter ) throws PrgException - { - if( this.target.supportsColors() ) { - parseExpr( iter ); - this.asmOut.append( "\tCALL\tXINK\n" ); - addLibItem( BasicLibrary.LibItem.XINK ); - } else { - try { - setCodeGenEnabled( false ); - parseExpr( iter ); - } - finally { - setCodeGenEnabled( true ); - } - } - } - - - private void parseINPUT( CharacterIterator iter ) throws PrgException - { - if( checkToken( iter, '#' ) ) { - parseIOChannelNumToPtrFldAddrInHL( iter, false, null ); - int begPos_M_IOCA = this.asmOut.length(); - this.asmOut.append( "\tLD\t(M_IOCA),HL\n" ); - int endPos_M_IOCA = this.asmOut.length(); - parseToken( iter, ',' ); - boolean firstIntVar = true; - boolean multiVars = false; - for(;;) { - this.asmOut.append( "\tLD\tC,2CH\n" // Komma als Trennzeichen - + "\tCALL\tIOINL\n" ); - addLibItem( BasicLibrary.LibItem.IOINL ); - int pos = this.asmOut.length(); - SimpleVarInfo varInfo = checkVariable( iter ); - if( varInfo == null ) { - throwVarExpected(); - } - varInfo.ensureAddrInHL( this.asmOut ); - String oldVarCode = this.asmOut.cut( pos ); - if( varInfo.getDataType() == DataType.INTEGER ) { - if( firstIntVar ) { - this.asmOut.append( "\tCALL\tF_VLI\n" ); - firstIntVar = false; - } else { - this.asmOut.append( "\tCALL\tF_VLI1\n" ); - } - if( isSingleInst_LD_HL_xx( oldVarCode ) ) { - this.asmOut.append( "\tEX\tDE,HL\n" ); - this.asmOut.append( oldVarCode ); - } else { - this.asmOut.append( "\tCALL\tF_VLI\n" - + "\tPUSH\tHL\n" ); - this.asmOut.append( oldVarCode ); - this.asmOut.append( "\tPOP\tDE\n" ); - } - this.asmOut.append( "\tLD\t(HL),E\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),D\n" ); - addLibItem( BasicLibrary.LibItem.F_VLI ); - } else if( varInfo.getDataType() == DataType.STRING ) { - String newVarCode = convertCodeToValueInDE( oldVarCode ); - if( newVarCode != null ) { - this.asmOut.append( newVarCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldVarCode ); - this.asmOut.append( "\tEX\tDE,HL\n" - + "\tPOP\tHL\n" ); - } - this.asmOut.append( "\tCALL\tASGSM\n" ); - addLibItem( BasicLibrary.LibItem.ASGSM ); - } - if( !checkToken( iter, ',' ) ) { - break; - } - this.asmOut.append( "\tLD\tHL,(M_IOCA)\n" ); - multiVars = true; - } - if( !multiVars ) { - this.asmOut.delete( begPos_M_IOCA, endPos_M_IOCA ); - } - - } else { - - // Eingabe von Tastatur - do { - String retryLabel = nextLabel(); - this.asmOut.append( retryLabel ); - this.asmOut.append( ":\n" ); - - // Prompt - String text = checkStringLiteral( iter ); - if( text != null ) { - // String-Literal evtl. bereits vorhanden? - String label = this.str2Label.get( text ); - if( label != null ) { - this.asmOut.append( "\tLD\tHL," ); - this.asmOut.append( label ); - this.asmOut.append( "\n" - + "\tCALL\tXOUTS\n" ); - addLibItem( BasicLibrary.LibItem.XOUTS ); - } else { - this.asmOut.append( "\tCALL\tXOUTST\n" ); - this.asmOut.appendStringLiteral( text ); - addLibItem( BasicLibrary.LibItem.XOUTST ); - } - checkToken( iter, ';' ); - } else { - this.asmOut.append_LD_A_n( '?' ); - this.asmOut.append( "\tCALL\tXOUTCH\n" ); - addLibItem( BasicLibrary.LibItem.XOUTCH ); - } - - // Variable - SimpleVarInfo varInfo = checkVariable( iter ); - if( varInfo == null ) { - if( text != null ) { - throwVarExpected(); - } - throwStringLitOrVarExpected(); - } - if( varInfo.getDataType() == DataType.INTEGER ) { - varInfo.ensureAddrInHL( this.asmOut ); - this.asmOut.append( "\tCALL\tINIV\n" ); - if( this.options.getPreferRelativeJumps() ) { - this.asmOut.append( "\tJR\tC," ); - } else { - this.asmOut.append( "\tJP\tC," ); - } - this.asmOut.append( retryLabel ); - this.asmOut.newLine(); - addLibItem( BasicLibrary.LibItem.INIV ); - } else if( varInfo.getDataType() == DataType.STRING ) { - varInfo.ensureAddrInHL( this.asmOut ); - this.asmOut.append( "\tCALL\tINSV\n" ); - addLibItem( BasicLibrary.LibItem.INSV ); - } - } while( checkToken( iter, ';' ) ); - } - } - - - private void parseLET( CharacterIterator iter ) throws PrgException - { - String varName = checkIdentifier( iter ); - if( varName == null ) { - throwVarExpected(); - } - if( !checkReturnValueAssignment( iter, varName ) ) { - SimpleVarInfo varInfo = checkVariable( iter, varName ); - if( varInfo != null ) { - parseAssignment( iter, varInfo ); - } else { - throwVarExpected(); - } - } - } - - - private void parseLABEL( CharacterIterator iter ) throws PrgException - { - checkGraphicsSupported(); - for(;;) { - String text = checkStringLiteral( iter ); - if( text != null ) { - if( !text.isEmpty() ) { - // String-Literal evtl. bereits vorhanden? - String label = this.str2Label.get( text ); - if( label != null ) { - this.asmOut.append( "\tLD\tHL," ); - this.asmOut.append( label ); - this.asmOut.append( "\n" - + "\tCALL\tDRLBL\n" ); - addLibItem( BasicLibrary.LibItem.DRLBL ); - } else { - this.asmOut.append( "\tCALL\tDRLBLT\n" ); - this.asmOut.appendStringLiteral( text ); - addLibItem( BasicLibrary.LibItem.DRLBLT ); + boolean done = false; + String name = BasicUtil.checkIdentifier( iter ); + if( name != null ) { + done = true; + if( name.equals( "ASM" ) ) { + checkCreateStackFrame(); + if( (begLineDebugDestPos >= 0) + && (begLineDebugDestPos < this.asmOut.length()) ) + { + this.asmOut.setLength( begLineDebugDestPos ); + begLineDebugDestPos = -1; } - } - } else { - if( !checkParseStringPrimVarExpr( iter ) ) { - throwStringExprExpected(); - } - this.asmOut.append( "\tCALL\tDRLBL\n" ); - addLibItem( BasicLibrary.LibItem.DRLBL ); - } - if( skipSpaces( iter ) != '+' ) { - break; - } - iter.next(); - } - } - - - private void parseLINE( CharacterIterator iter ) throws PrgException - { - if( checkKeyword( iter, "INPUT" ) ) { - - // Eingabe einer ganzen Zeile - if( checkToken( iter, '#' ) ) { - parseInputLineIO( iter ); - } else { - parseInputLine( iter, false ); - } - - } else { - - // Zeichnen einer Linie oder eines Rechtecks - checkGraphicsSupported(); - boolean enclosed = checkToken( iter, '(' ); - parseExpr( iter ); - this.asmOut.append( "\tLD\t(M_LNBX),HL\n" ); - parseToken( iter, ',' ); - parseExpr( iter ); - this.asmOut.append( "\tLD\t(M_LNBY),HL\n" ); - if( enclosed ) { - parseToken( iter, ')' ); - parseToken( iter, '-' ); - parseToken( iter, '(' ); - } else { - parseToken( iter, ',' ); - } - parseExpr( iter ); - this.asmOut.append( "\tLD\t(M_LNEX),HL\n" ); - parseToken( iter, ',' ); - parseExpr( iter ); - this.asmOut.append( "\tLD\t(M_LNEY),HL\n" ); - if( enclosed ) { - parseToken( iter, ')' ); - } - if( checkToken( iter, ',' ) ) { - if( checkKeyword( iter, "BF" ) ) { - this.asmOut.append( "\tCALL\tDRBOXF\n" ); - addLibItem( BasicLibrary.LibItem.DRBOXF ); + parseASM( iter, false ); + } else if( name.equals( "DECLARE" ) ) { + parseDECLARE( iter ); + } else if( name.equals( "REM" ) ) { + parseREM( iter ); + } else if( name.equals( "FUNCTION" ) ) { + parseCallableImpl( iter, true ); + } else if( name.equals( "LOCAL" ) ) { + parseLOCAL( iter ); + } else if( name.equals( "SUB" ) ) { + parseCallableImpl( iter, false ); } else { - if( !checkKeyword( iter, "B" ) ) { - throw new PrgException( "B oder BF erwartet" ); + /* + * Alle weiteren Anweisungen duerfen nur im Hauptprogramm + * oder in einer Funktion bzw. Prozedur vorkommen. + * Ausserdem muss im Fall einer Funktion bzw. Prozedur + * der Stack-Rahmen angelegt sein. + */ + checkMainPrgScope(); + checkCreateStackFrame(); + if( name.equals( "BORDER" ) ) { + parseBORDER( iter ); + } else if( name.equals( "CALL" ) ) { + parseCALL( iter ); + } else if( name.equals( "CIRCLE" ) ) { + parseCIRCLE( iter ); + } else if( name.equals( "CLOSE" ) ) { + parseCLOSE( iter ); + } else if( name.equals( "CLS" ) ) { + parseCLS(); + } else if( name.equals( "COLOR" ) ) { + parseCOLOR( iter ); + } else if( name.equals( "CURSOR" ) ) { + parseCURSOR( iter ); + } else if( name.equals( "DATA" ) ) { + parseDATA( iter ); + } else if( name.equals( "DEF" ) ) { + parseDEF( iter ); + } else if( name.equals( "DEFUSR" ) ) { + parseDEFUSR( iter ); + } else if( name.equals( "DEFUSR0" ) ) { + parseDEFUSR( iter, 0 ); + } else if( name.equals( "DEFUSR1" ) ) { + parseDEFUSR( iter, 1 ); + } else if( name.equals( "DEFUSR2" ) ) { + parseDEFUSR( iter, 2 ); + } else if( name.equals( "DEFUSR3" ) ) { + parseDEFUSR( iter, 3 ); + } else if( name.equals( "DEFUSR4" ) ) { + parseDEFUSR( iter, 4 ); + } else if( name.equals( "DEFUSR5" ) ) { + parseDEFUSR( iter, 5 ); + } else if( name.equals( "DEFUSR6" ) ) { + parseDEFUSR( iter, 6 ); + } else if( name.equals( "DEFUSR7" ) ) { + parseDEFUSR( iter, 7 ); + } else if( name.equals( "DEFUSR8" ) ) { + parseDEFUSR( iter, 8 ); + } else if( name.equals( "DEFUSR9" ) ) { + parseDEFUSR( iter, 9 ); + } else if( name.equals( "DIM" ) ) { + parseDIM( iter ); + } else if( name.equals( "DOKE" ) ) { + parseDOKE( iter ); + } else if( name.equals( "DO" ) ) { + parseDO( iter ); + } else if( name.equals( "DRAW" ) ) { + parseDRAW( iter ); + } else if( name.equals( "DRAWR" ) ) { + parseDRAWR( iter ); + } else if( name.equals( "ELSE" ) ) { + parseELSE( iter ); + } else if( name.equals( "ELSEIF" ) ) { + parseELSEIF( iter ); + } else if( name.equals( "END" ) ) { + parseEND( iter ); + } else if( name.equals( "ENDIF" ) ) { + parseENDIF( iter ); + } else if( name.equals( "EXIT" ) ) { + parseEXIT( iter ); + } else if( name.equals( "FOR" ) ) { + parseFOR( iter ); + } else if( name.equals( "GOSUB" ) ) { + parseGOSUB( iter ); + } else if( name.equals( "GOTO" ) ) { + parseGOTO( iter ); + } else if( name.equals( "IF" ) ) { + parseIForELSEIF( iter, null ); + } else if( name.equals( "INCLUDE" ) ) { + parseINCLUDE( iter ); + } else if( name.equals( "INK" ) ) { + parseINK( iter ); + } else if( name.equals( "INPUT" ) ) { + parseINPUT( iter ); + } else if( name.equals( "LABEL" ) ) { + parseLABEL( iter ); + } else if( name.equals( "LET" ) ) { + parseLET( iter ); + } else if( name.equals( "LINE" ) ) { + parseLINE( iter ); + } else if( name.equals( "LOCATE" ) ) { + parseLOCATE( iter ); + } else if( name.equals( "LOOP" ) ) { + parseLOOP( iter ); + } else if( name.equals( "LPRINT" ) ) { + parseLPRINT( iter ); + } else if( name.equals( "MOVE" ) ) { + parseMOVE( iter ); + } else if( name.equals( "MOVER" ) ) { + parseMOVER( iter ); + } else if( name.equals( "NEXT" ) ) { + parseNEXT( iter ); + } else if( name.equals( "ON" ) ) { + parseON( iter ); + } else if( name.equals( "OPEN" ) ) { + parseOPEN( iter ); + } else if( name.equals( "OUT" ) ) { + parseOUT( iter ); + } else if( name.equals( "PAINT" ) ) { + parsePAINT( iter ); + } else if( name.equals( "PAPER" ) ) { + parsePAPER( iter ); + } else if( name.equals( "PASSWORD" ) ) { + parsePASSWORD( iter ); + } else if( name.equals( "PAUSE" ) ) { + parsePAUSE( iter ); + } else if( name.equals( "PEN" ) ) { + parsePEN( iter ); + } else if( name.equals( "PLOT" ) ) { + parsePLOT( iter ); + } else if( name.equals( "PLOTR" ) ) { + parsePLOTR( iter ); + } else if( name.equals( "POKE" ) ) { + parsePOKE( iter ); + } else if( name.equals( "PRESET" ) ) { + parsePRESET( iter ); + } else if( name.equals( "PRINT" ) ) { + parsePRINT( iter ); + } else if( name.equals( "PSET" ) ) { + parsePSET( iter ); + } else if( name.equals( "READ" ) ) { + parseREAD( iter ); + } else if( name.equals( "REM" ) ) { + parseREM( iter ); + } else if( name.equals( "RESTORE" ) ) { + parseRESTORE( iter ); + } else if( name.equals( "RETURN" ) ) { + parseRETURN(); + } else if( name.equals( "SCREEN" ) ) { + parseSCREEN( iter ); + } else if( name.equals( "WAIT" ) ) { + parseWAIT( iter ); + } else if( name.equals( "WEND" ) ) { + parseWEND( iter ); + } else if( name.equals( "WHILE" ) ) { + parseWHILE( iter ); + } else { + if( !checkReturnValueAssignment( iter, name ) ) { + CallableEntry entry = this.name2Callable.get( name ); + if( entry != null ) { + parseCallableCall( iter, entry ); + } else { + SimpleVarInfo varInfo = checkVariable( iter, name ); + if( varInfo != null ) { + parseAssignment( iter, varInfo ); + } else { + done = false; + } + } + } } - this.asmOut.append( "\tCALL\tDRBOX\n" ); - addLibItem( BasicLibrary.LibItem.DRBOX ); } - } else { - this.asmOut.append( "\tCALL\tDRLINE\n" ); - addLibItem( BasicLibrary.LibItem.DRLINE ); - } - } - } - - - private void parseLOCAL( CharacterIterator iter ) throws PrgException - { - CallableEntry entry = getCallableEntry(); - if( entry == null ) { - throw new PrgException( - "Anweisung nur in einer Funktion/Prozedur erlaubt" ); - } - do { - String varName = checkIdentifier( iter ); - if( varName == null ) { - throwVarNameExpected(); - } - checkVarName( varName ); - if( varName.equals( entry.getName() ) ) { - throw new PrgException( "Name der Funktion/Prozedur" - + " als Variablenname nicht zul\u00E4ssig" ); - } - entry.addVar( this.curSourceLineNum, this.curBasicLineNum, varName ); - } while( checkToken( iter, ',' ) ); - } - - - private void parseLOCATE( CharacterIterator iter ) throws PrgException - { - parse2ArgsTo_DE_HL( iter ); - if( this.target.supportsXLOCAT() ) { - this.asmOut.append( "\tCALL\tLOCATE\n" ); - addLibItem( BasicLibrary.LibItem.LOCATE ); - } else { - throw new PrgException( "LOCATE-Anweisung f\u00FCr das" - + " Zielsystem nicht unterst\u00FCtzt" ); - } - } - - - private void parseLOOP( CharacterIterator iter ) throws PrgException - { - DoEntry doEntry = null; - if( !this.structureStack.isEmpty() ) { - StructureEntry entry = this.structureStack.peek(); - if( entry instanceof DoEntry ) { - doEntry = (DoEntry) entry; - this.structureStack.pop(); } - } - if( doEntry == null ) { - throw new PrgException( "LOOP ohne DO" ); - } - if( checkKeyword( iter, "UNTIL" ) ) { - parseExpr( iter ); - this.asmOut.append( "\tLD\tA,H\n" - + "\tOR\tL\n" - + "\tJP\tZ," + doEntry.getLoopLabel() + "\n" ); - } else if( checkKeyword( iter, "WHILE" ) ) { - parseExpr( iter ); - this.asmOut.append( "\tLD\tA,H\n" - + "\tOR\tL\n" - + "\tJP\tNZ," + doEntry.getLoopLabel() + "\n" ); - } else { - if( !isEndOfInstr( iter ) ) { + if( !done ) { throw new PrgException( - "UNTIL, WHILE oder Ende der Anweisung erwartet" ); + "Anweisung, Prozedur oder Variable erwartet" ); } - this.asmOut.append( "\tJP\t" ); - this.asmOut.append( doEntry.getLoopLabel() ); - this.asmOut.newLine(); - } - this.asmOut.append( doEntry.getExitLabel() ); - this.asmOut.append( ":\n" ); - } - - - private void parseLPRINT( CharacterIterator iter ) throws PrgException - { - if( !this.target.supportsXLPTCH() ) { - throw new PrgException( - "Druckerausgaben f\u00FCr das Zielsystem" - + " nicht unterst\u00FCtzt" ); - } - this.asmOut.append( "\tLD\tHL,XLPTCH\n" - + "\tLD\t(M_IO_COUT),HL\n" ); - addLibItem( BasicLibrary.LibItem.XLPTCH ); - addLibItem( BasicLibrary.LibItem.M_IO_COUT ); - parsePrint( iter, false ); - } - - - private void parseMOVE( CharacterIterator iter ) throws PrgException - { - if( checkKeyword( iter, "STEP" ) ) { - parseMOVER( iter ); - } else { - checkGraphicsSupported(); - checkKeyword( iter, "TO" ); - parsePointToMem( iter, "M_XPOS", "M_YPOS" ); - addLibItem( BasicLibrary.LibItem.M_XYPO ); } } - private void parseMOVER( CharacterIterator iter ) throws PrgException - { - checkGraphicsSupported(); - parsePointTo_DE_HL( iter ); - this.asmOut.append( "\tCALL\tMOVER\n" ); - addLibItem( BasicLibrary.LibItem.MOVER ); - } - - - private void parseNEXT( CharacterIterator iter ) throws PrgException + private void parseBORDER( CharacterIterator iter ) throws PrgException { - ForEntry forEntry = null; - if( !this.structureStack.isEmpty() ) { - StructureEntry entry = this.structureStack.peek(); - if( entry instanceof ForEntry ) { - forEntry = (ForEntry) entry; - this.structureStack.pop(); - } - } - if( forEntry == null ) { - throw new PrgException( "NEXT ohne FOR" ); - } - if( !isEndOfInstr( iter ) ) { - int len = this.asmOut.length(); - SimpleVarInfo varInfo = checkVariable( iter ); - this.asmOut.setLength( len ); - if( varInfo == null ) { - throw new PrgException( "Variable oder Ende der Anweisung erwartet" ); - } - if( !varInfo.equals( forEntry.getSimpleVarInfo() ) ) { - throw new PrgException( "Variable stimmt nicht mit der bei der" - + " FOR-Anweisung angegebenen \u00FCberein." ); - } - } - forEntry.getSimpleVarInfo().ensureValueInHL( this.asmOut ); - Integer stepValue = forEntry.getStepValue(); - if( stepValue != null ) { - if( stepValue.intValue() == -1 ) { - this.asmOut.append( "\tCALL\tO_DEC\n" ); - addLibItem( BasicLibrary.LibItem.O_DEC ); - } else if( stepValue.intValue() == 1 ) { - this.asmOut.append( "\tCALL\tO_INC\n" ); - addLibItem( BasicLibrary.LibItem.O_INC ); - } else { - this.asmOut.append_LD_DE_nn( stepValue ); - this.asmOut.append( "\tCALL\tO_ADD\n" ); - addLibItem( BasicLibrary.LibItem.O_ADD ); - } - } else { - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tO_ADD\n" ); - addLibItem( BasicLibrary.LibItem.O_ADD ); - } - forEntry.getSimpleVarInfo().writeCode_LD_Var_HL( this.asmOut ); - Integer toValue = forEntry.getToValue(); - if( stepValue != null ) { - if( toValue != null ) { - /* - * Diese FOR-Schleife legt nichts auf den Stack. - * Es kann somit direkt zum Anfang der Schleife gesprungen werden. - */ - this.asmOut.append_LD_DE_nn( toValue ); - this.asmOut.append( "\tCALL\tCPHLDE\n" ); - addLibItem( BasicLibrary.LibItem.CPHLDE ); - if( stepValue.intValue() < 0 ) { - this.asmOut.append( "\tJP\tNC," ); - } else { - this.asmOut.append( "\tJP\tZ," ); - this.asmOut.append( forEntry.getLoopLabel() ); - this.asmOut.append( "\n" - + "\tJP\tC," ); - } - this.asmOut.append( forEntry.getLoopLabel() ); - this.asmOut.newLine(); - } else { - this.asmOut.append( "\tPOP\tDE\n" ); - this.asmOut.append( "\tCALL\tCPHLDE\n" ); - addLibItem( BasicLibrary.LibItem.CPHLDE ); - String exitLabel = nextLabel(); - if( stepValue.intValue() < 0 ) { - this.asmOut.append( "\tJR\tC," ); - this.asmOut.append( exitLabel ); - this.asmOut.newLine(); - } else { - String tmpLabel = nextLabel(); - this.asmOut.append( "\tJR\tZ," ); - this.asmOut.append( tmpLabel ); - this.asmOut.append( "\n" - + "\tJR\tNC," ); - this.asmOut.append( exitLabel ); - this.asmOut.newLine(); - this.asmOut.append( tmpLabel ); - this.asmOut.append( (char) ':' ); - } - this.asmOut.append( "\tPUSH\tDE\n" - + "\tJP\t" ); - this.asmOut.append( forEntry.getLoopLabel() ); - this.asmOut.newLine(); - this.asmOut.append( exitLabel ); - this.asmOut.append( ":\n" ); - } + if( this.target.supportsBorderColor() ) { + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tCALL\tXBORDER\n" ); + addLibItem( BasicLibrary.LibItem.XBORDER ); } else { - this.asmOut.append( "\tLD\tB,D\n" // Schrittweite - + "\tLD\tC,E\n" ); - if( toValue != null ) { - this.asmOut.append_LD_DE_nn( toValue.intValue() ); - } else { - this.asmOut.append( "\tPOP\tDE\n" ); - } - this.asmOut.append( "\tLD\tA,B\n" - + "\tOR\tA\n" - + "\tJP\tM," ); - String subLabel = nextLabel(); - this.asmOut.append( subLabel ); - this.asmOut.append( "\n" - + "\tCALL\tCPHLDE\n" ); - addLibItem( BasicLibrary.LibItem.CPHLDE ); - String tmpLabel = nextLabel(); - this.asmOut.append( "\tJR\tZ," ); - this.asmOut.append( tmpLabel ); - this.asmOut.append( "\n" - + "\tJR\tC," ); - this.asmOut.append( tmpLabel ); - this.asmOut.append( "\n" - + "\tJR\t" ); - this.asmOut.append( forEntry.getExitLabel() ); - this.asmOut.newLine(); - this.asmOut.append( subLabel ); - this.asmOut.append( ":\n" - + "\tCALL\tCPHLDE\n" - + "\tJR\tC," ); - this.asmOut.append( forEntry.getExitLabel() ); - this.asmOut.newLine(); - this.asmOut.append( tmpLabel ); - this.asmOut.append( ":\n" ); - if( toValue == null ) { - this.asmOut.append( "\tPUSH\tDE\n" ); + try { + pushCodeCreationDisabled(); + BasicExprParser.parseExpr( this, iter ); + } + finally { + popCodeCreationDisabled(); } - this.asmOut.append( "\tPUSH\tBC\n" - + "\tJP\t" ); - this.asmOut.append( forEntry.getLoopLabel() ); - this.asmOut.newLine(); } - this.asmOut.append( forEntry.getExitLabel() ); - this.asmOut.append( ":\n" ); } - private void parseON( CharacterIterator iter ) throws PrgException + private void parseCALL( CharacterIterator iter ) throws PrgException { - parseExpr( iter ); - if( checkKeyword( iter, "GOSUB" ) ) { - this.asmOut.append( "\tCALL\tONGOAD\n" ); - parseLineExprList( iter ); - String label = nextLabel(); - this.asmOut.append( "\tJR\tZ," ); - this.asmOut.append( label ); - this.asmOut.append( "\n" - + "\tLD\tDE," ); - this.asmOut.append( label ); - this.asmOut.append( "\n" - + "\tPUSH\tDE\n" ); - if( this.options.getCheckStack() ) { - this.asmOut.append_LD_A_n( MAGIC_GOSUB ); - this.asmOut.append( "\tPUSH\tAF\n" ); + if( BasicUtil.checkToken( iter, '*' ) ) { + BasicUtil.skipSpaces( iter ); + Integer value = BasicUtil.readHex( iter ); + if( value == null ) { + BasicUtil.throwHexDigitExpected(); } - this.asmOut.append( "\tJP\t(HL)\n" ); - this.asmOut.append( label ); - this.asmOut.append( ":\n" ); - addLibItem( BasicLibrary.LibItem.ONGOAD ); - } else if( checkKeyword( iter, "GOTO" ) ) { - this.asmOut.append( "\tCALL\tONGOAD\n" ); - parseLineExprList( iter ); - String label = nextLabel(); - this.asmOut.append( "\tJR\tZ," ); - this.asmOut.append( label ); - this.asmOut.append( "\n" - + "\tJP\t(HL)\n" ); - this.asmOut.append( label ); - this.asmOut.append( ":\n" ); - addLibItem( BasicLibrary.LibItem.ONGOAD ); + this.asmOut.append_LD_HL_nn( value.intValue() ); } else { - throw new PrgException( "GOSUB oder GOTO erwartet" ); + BasicExprParser.parseExpr( this, iter ); + } + boolean insideCallable = (getEnclosingCallableEntry() != null); + if( insideCallable ) { + this.asmOut.append( "\tPUSH\tIY\n" ); + } + this.asmOut.append( "\tCALL\tJP_HL\n" ); + if( insideCallable ) { + this.asmOut.append( "\tPOP\tIY\n" ); } + addLibItem( BasicLibrary.LibItem.JP_HL ); } - private void parseOPEN( CharacterIterator iter ) throws PrgException + private void parseCIRCLE( CharacterIterator iter ) throws PrgException { - int ioMode = 0; - parseStringPrimExpr( iter ); - this.asmOut.append( "\tLD\t(M_IONM),HL\n" ); - if( checkKeyword( iter, "FOR" ) ) { - if( checkKeyword( iter, "INPUT" ) ) { - ioMode = BasicLibrary.IOMODE_INPUT; - } else if( checkKeyword( iter, "OUTPUT" ) ) { - ioMode = BasicLibrary.IOMODE_OUTPUT; - } else if( checkKeyword( iter, "APPEND" ) ) { - ioMode = BasicLibrary.IOMODE_APPEND; - } else { - throw new PrgException( "INPUT, OUTPUT oder APPEND erwartet" ); - } - } - parseKeywordAS( iter ); - this.asmOut.append_LD_A_n( ioMode ); - this.asmOut.append( "\tLD\t(M_IOAC),A\n" ); - checkToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, false, null ); - this.asmOut.append( "\tCALL\tIOOPEN\n" ); - addLibItem( BasicLibrary.LibItem.IOOPEN ); + checkGraphicsSupported(); + parsePointToMem( iter, "CIRCLE_M_X", "CIRCLE_M_Y" ); + BasicUtil.parseToken( iter, ',' ); + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tLD\t(CIRCLE_M_R),HL\n" + + "\tCALL\tCIRCLE\n" ); + addLibItem( BasicLibrary.LibItem.CIRCLE ); } - private void parseOUT( CharacterIterator iter ) throws PrgException + private void parseCLOSE( CharacterIterator iter ) throws PrgException { - boolean enclosed = checkToken( iter, '(' ); - int pos = this.asmOut.length(); - parseExpr( iter ); - String tmpPortCode = this.asmOut.cut( pos ); - String newPortCode = convertCodeToValueInBC( tmpPortCode ); - if( enclosed ) { - parseToken( iter, ')' ); - } - if( enclosed ) { - if( !checkToken( iter, '=' ) ) { - parseToken( iter, ',' ); - } + BasicUtil.parseToken( iter, '#' ); + parseIOChannelNumToPtrFldAddrInHL( iter, null ); + this.asmOut.append( "\tCALL\tIOCLOSE\n" ); + addLibItem( BasicLibrary.LibItem.IOCLOSE ); + } + + + private void parseCLS() + { + if( this.target.supportsXCLS() ) { + this.asmOut.append( "\tCALL\tXCLS\n" ); + addLibItem( BasicLibrary.LibItem.XCLS ); } else { - parseToken( iter, ',' ); + this.asmOut.append( "\tLD\tA,0CH\n" + + "\tCALL\tXOUTCH\n" ); + addLibItem( BasicLibrary.LibItem.XOUTCH ); } - parseExpr( iter ); - Integer value = removeLastCodeIfConstExpr( pos ); - if( value != null ) { - if( newPortCode != null ) { - this.asmOut.append( newPortCode ); + } + + + private void parseCOLOR( CharacterIterator iter ) throws PrgException + { + if( this.target.supportsColors() ) { + BasicExprParser.parseExpr( this, iter ); + if( BasicUtil.checkToken( iter, ',' ) ) { + int pos = this.asmOut.length(); + BasicExprParser.parseExpr( this, iter ); + String oldCode = this.asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + this.asmOut.append( newCode ); + } else { + this.asmOut.append( "\tPUSH\tHL\n" ); + this.asmOut.append( oldCode ); + this.asmOut.append( "\tEX\tHL,DE\n" + + "\tPOP\tHL\n" ); + } + this.asmOut.append( "\tCALL\tXCOLOR\n" ); + addLibItem( BasicLibrary.LibItem.XCOLOR ); } else { - this.asmOut.append( tmpPortCode ); - this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" ); + this.asmOut.append( "\tCALL\tXINK\n" ); + addLibItem( BasicLibrary.LibItem.XINK ); } - this.asmOut.append_LD_A_n( value.intValue() ); - this.asmOut.append( "\tOUT\t(C),A\n" ); } else { - if( newPortCode != null ) { - this.asmOut.append( newPortCode ); - } else { - String valueCode = this.asmOut.cut( pos ); - if( isOnly_LD_HL_xx( valueCode ) ) { - if( newPortCode != null ) { - this.asmOut.append( newPortCode ); - } else { - this.asmOut.append( tmpPortCode ); - this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" ); - } - this.asmOut.append( valueCode ); - } else { - this.asmOut.append( tmpPortCode ); - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( valueCode ); - this.asmOut.append( "\tPOP\tBC\n" ); + try { + pushCodeCreationDisabled(); + BasicExprParser.parseExpr( this, iter ); + if( BasicUtil.checkToken( iter, ',' ) ) { + BasicExprParser.parseExpr( this, iter ); } } - this.asmOut.append( "\tOUT\t(C),L\n" ); + finally { + popCodeCreationDisabled(); + } + } + if( BasicUtil.checkToken( iter, ',' ) ) { + parseBORDER( iter ); } } - private void parsePAPER( CharacterIterator iter ) throws PrgException + private void parseCURSOR( CharacterIterator iter ) throws PrgException { - if( this.target.supportsColors() ) { - parseExpr( iter ); - this.asmOut.append( "\tCALL\tXPAPER\n" ); - addLibItem( BasicLibrary.LibItem.XPAPER ); + if( this.target.supportsXCURS() ) { + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tCALL\tXCURS\n" ); + addLibItem( BasicLibrary.LibItem.XCURS ); } else { try { - setCodeGenEnabled( false ); - parseExpr( iter ); + pushCodeCreationDisabled(); + BasicExprParser.parseExpr( this, iter ); } finally { - setCodeGenEnabled( true ); + popCodeCreationDisabled(); } } } - private void parsePASSWORD( CharacterIterator iter ) throws PrgException + private void parseDATA( CharacterIterator iter ) throws PrgException { - if( !checkKeyword( iter, "INPUT" ) ) { - throw new PrgException( "INPUT erwartet" ); + if( this.dataOut == null ) { + this.dataOut = new AsmCodeBuf( 0x400 ); } - parseInputLine( iter, true ); + if( this.lastBasicLineExpr != null ) { + if( this.dataBasicLines == null ) { + this.dataBasicLines = new TreeSet<>(); + } + if( this.dataBasicLines.add( this.lastBasicLineExpr ) ) { + this.dataOut.append( createDataLineLabel( this.lastBasicLineExpr ) ); + this.dataOut.append( ":\n" ); + } + } + do { + String text = BasicUtil.checkStringLiteral( this, iter ); + if( text != null ) { + this.dataOut.append( "\tDB\t" ); + this.dataOut.appendHex2( DATA_STRING ); + this.dataOut.newLine(); + this.dataOut.appendStringLiteral( text ); + } else { + Integer value = checkSignedNumber( iter ); + if( value == null ) { + throw new PrgException( "Integer- oder String-Literal erwartet" ); + } + if( (value.intValue() >= 0) && (value.intValue() < 0x0100) ) { + this.dataOut.append( "\tDB\t" ); + this.dataOut.appendHex2( DATA_INT1 ); + this.dataOut.append( (char) ',' ); + this.dataOut.appendHex2( value.intValue() ); + this.dataOut.newLine(); + } else { + this.dataOut.append( "\tDB\t" ); + this.dataOut.appendHex2( DATA_INT2 ); + this.dataOut.newLine(); + this.dataOut.append( "\tDW\t" ); + this.dataOut.appendHex4( value.intValue() ); + this.dataOut.newLine(); + } + } + } while( BasicUtil.checkToken( iter, ',' ) ); + addLibItem( BasicLibrary.LibItem.DATA ); } - private void parsePAUSE( CharacterIterator iter ) throws PrgException + private void parseDECLARE( CharacterIterator iter ) throws PrgException { - int pos = this.asmOut.length(); - parseExpr( iter ); - Integer value = removeLastCodeIfConstExpr( pos ); - if( value != null ) { - if( (value.intValue() < 0) || (value.intValue() > 0x7FFF) ) { - putWarningOutOfRange(); - } - this.asmOut.append_LD_HL_nn( value.intValue() ); + CallableEntry entry = null; + if( BasicUtil.checkKeyword( iter, "FUNCTION" ) ) { + parseCallableDecl( iter, true, false ); + } else if( BasicUtil.checkKeyword( iter, "SUB" ) ) { + parseCallableDecl( iter, false, false ); } else { - parseExpr( iter ); + throw new PrgException( "FUNCTION oder SUB erwartet" ); + } + } + + + private void parseDEF( CharacterIterator iter ) throws PrgException + { + if( !BasicUtil.checkKeyword( iter, "USR" ) ) { + throw new PrgException( "USR erwartet" ); + } + int usrNum = BasicUtil.parseUsrNum( iter ); + parseDEFUSR( iter, usrNum ); + } + + + private void parseDEFUSR( CharacterIterator iter ) throws PrgException + { + int usrNum = BasicUtil.parseUsrNum( iter ); + parseDEFUSR( iter, usrNum ); + } + + + private void parseDEFUSR( + CharacterIterator iter, + int usrNum ) throws PrgException + { + BasicUtil.parseToken( iter, '=' ); + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tLD\t(" ); + this.asmOut.append( getUsrLabel( usrNum ) ); + this.asmOut.append( "),HL\n" ); + } + + + private void parseDIM( CharacterIterator iter ) throws PrgException + { + CallableEntry entry = getEnclosingCallableEntry(); + if( entry != null ) { + throw new PrgException( "Anweisung in einer Funktion/Prozedur" + + " nicht zul\u00E4ssig" ); } - this.asmOut.append( "\tCALL\tPAUSE\n" ); - addLibItem( BasicLibrary.LibItem.PAUSE ); + do { + String varName = BasicUtil.checkIdentifier( iter ); + if( varName == null ) { + throwVarNameExpected(); + } + checkVarName( varName ); + BasicUtil.parseToken( iter, '(' ); + VarDecl var = null; + int dim1 = BasicUtil.parseNumber( iter ); + if( dim1 < 1 ) { + throwDimTooSmall(); + } + if( BasicUtil.checkToken( iter, ',' ) ) { + int dim2 = BasicUtil.parseNumber( iter ); + if( dim2 < 1 ) { + throwDimTooSmall(); + } + var = new VarDecl( + this.curSource, + this.curBasicLineNum, + varName, + dim1, + dim2 ); + } else { + var = new VarDecl( + this.curSource, + this.curBasicLineNum, + varName, + dim1 ); + } + VarDecl tmpVar = this.name2GlobalVar.get( varName ); + if( tmpVar != null ) { + if( tmpVar.getDimCount() > 0 ) { + throw new PrgException( "Feldvariable bereits deklariert" ); + } else { + throw new PrgException( "Variable implizit als einfache" + + " Variable bereits deklariert" ); + } + } + BasicUtil.parseToken( iter, ')' ); + this.name2GlobalVar.put( varName, var ); + } while( BasicUtil.checkToken( iter, ',' ) ); + } + + + private void parseDO( CharacterIterator iter ) throws PrgException + { + String loopLabel = nextLabel(); + this.asmOut.append( loopLabel ); + this.asmOut.append( ":\n" ); + checkAppendBreakCheck(); + this.structureStack.push( + new DoEntry( + this.curSource, + this.curBasicLineNum, + loopLabel, + nextLabel() ) ); } - private void parsePOKE( CharacterIterator iter ) throws PrgException + private void parseDOKE( CharacterIterator iter ) throws PrgException { int pos = this.asmOut.length(); - parseExpr( iter ); - Integer addr = removeLastCodeIfConstExpr( pos ); - parseToken( iter, ',' ); - pos = this.asmOut.length(); - parseExpr( iter ); + BasicExprParser.parseExpr( this, iter ); + BasicUtil.parseToken( iter, ',' ); + Integer addr = BasicUtil.removeLastCodeIfConstExpr( this, pos ); if( addr != null ) { - Integer value = removeLastCodeIfConstExpr( pos ); - if( value != null ) { - if( (value & 0xFF) == 0 ) { - this.asmOut.append( "\tXOR\tA\n" ); - } else { - this.asmOut.append( "\tLD\tA," ); - this.asmOut.appendHex2( value.intValue() ); - this.asmOut.newLine(); - } - } else { - this.asmOut.append( "\tLD\tA,L\n" ); - } + BasicExprParser.parseExpr( this, iter ); this.asmOut.append( "\tLD\t(" ); this.asmOut.appendHex4( addr.intValue() ); - this.asmOut.append( "),A\n" ); + this.asmOut.append( "),HL\n" ); } else { - Integer value = removeLastCodeIfConstExpr( pos ); + pos = this.asmOut.length(); + BasicExprParser.parseExpr( this, iter ); + Integer value = BasicUtil.removeLastCodeIfConstExpr( this, pos ); if( value != null ) { this.asmOut.append( "\tLD\t(HL)," ); this.asmOut.appendHex2( value.intValue() ); + this.asmOut.append( "\n" + + "\tINC\tHL\n" + + "\tLD\t(HL)," ); + this.asmOut.appendHex2( value.intValue() >> 8 ); this.asmOut.newLine(); } else { String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); if( newCode != null ) { this.asmOut.append( newCode ); - this.asmOut.append( "\tLD\t(HL),E\n" ); } else { this.asmOut.append( "\tPUSH\tHL\n" ); this.asmOut.append( oldCode ); - this.asmOut.append( "\tLD\tA,L\n" - + "\tPOP\tHL\n" - + "\tLD\t(HL),A\n" ); + this.asmOut.append( "\tEX\tDE,HL\n" + + "\tPOP\tHL\n" ); } + this.asmOut.append( "\tLD\t(HL),E\n" + + "\tINC\tHL\n" + + "\tLD\t(HL),D\n" ); } } } - private void parsePEN( CharacterIterator iter ) throws PrgException - { - int pos = this.asmOut.length(); - parseExpr( iter ); - Integer value = removeLastCodeIfConstExpr( pos ); - if( value != null ) { - if( (value.intValue() < 0) || (value.intValue() > 3) ) { - throw new PrgException( "Ung\u00FCltiger Wert bei PEN" ); - } - this.asmOut.append_LD_A_n( value.intValue() ); - this.asmOut.append( "\tCALL\tXPEN\n" ); - addLibItem( BasicLibrary.LibItem.XPEN ); - } else { - this.asmOut.append( "\tCALL\tPEN\n" ); - addLibItem( BasicLibrary.LibItem.PEN ); - } - } - - - private void parsePLOT( CharacterIterator iter ) throws PrgException + private void parseDRAW( CharacterIterator iter ) throws PrgException { - if( checkKeyword( iter, "STEP" ) ) { - parsePLOTR( iter ); + if( BasicUtil.checkKeyword( iter, "STEP" ) ) { + parseDRAWR( iter ); } else { checkGraphicsSupported(); - checkKeyword( iter, "TO" ); - parsePointTo_DE_HL( iter ); - this.asmOut.append( "\tLD\t(M_XPOS),DE\n" - + "\tLD\t(M_YPOS),HL\n" - + "\tCALL\tXPSET\n" ); - addLibItem( BasicLibrary.LibItem.XPSET ); + String text = BasicUtil.checkStringLiteral( this, iter ); + if( text != null ) { + this.asmOut.append( "\tCALL\tDRAWST\n" ); + this.asmOut.appendStringLiteral( text ); + addLibItem( BasicLibrary.LibItem.DRAWST ); + } else if( BasicExprParser.checkParseStringPrimVarExpr( this, iter ) ) { + this.asmOut.append( "\tCALL\tDRAWS\n" ); + addLibItem( BasicLibrary.LibItem.DRAWS ); + } else { + BasicUtil.checkKeyword( iter, "TO" ); + parsePointToMem( iter, "LINE_M_EX", "LINE_M_EY" ); + this.asmOut.append( "\tCALL\tDRAW\n" ); + addLibItem( BasicLibrary.LibItem.DRAW ); + } } } - private void parsePLOTR( CharacterIterator iter ) throws PrgException - { - checkGraphicsSupported(); - parsePointTo_DE_HL( iter ); - this.asmOut.append( "\tCALL\tPLOTR\n" ); - addLibItem( BasicLibrary.LibItem.PLOTR ); - } - - - private void parsePRESET( CharacterIterator iter ) throws PrgException + private void parseDRAWR( CharacterIterator iter ) throws PrgException { checkGraphicsSupported(); parsePointTo_DE_HL( iter ); - this.asmOut.append( "\tCALL\tXPRES\n" ); - addLibItem( BasicLibrary.LibItem.XPRES ); + this.asmOut.append( "\tCALL\tDRAWR\n" ); + addLibItem( BasicLibrary.LibItem.DRAWR ); } - private void parsePRINT( CharacterIterator iter ) throws PrgException + private void parseELSE( CharacterIterator iter ) throws PrgException { - if( checkToken( iter, '#' ) ) { - parseIOChannelNumToWriteRoutineInHL( iter ); - this.asmOut.append( "\tCALL\tIO_SET_COUT\n" ); - addLibItem( BasicLibrary.LibItem.IO_SET_COUT ); - if( this.options.getPreferRelativeJumps() ) { - this.asmOut.append( "\tJR\tC," ); - } else { - this.asmOut.append( "\tJP\tC," ); + for(;;) { + IfEntry ifEntry = null; + if( !this.structureStack.isEmpty() ) { + BasicSourcePos entry = this.structureStack.peek(); + if( entry instanceof IfEntry ) { + ifEntry = (IfEntry) entry; + } } - String endOfInstLabel = nextLabel(); - this.asmOut.append( endOfInstLabel ); - this.asmOut.newLine(); - if( isEndOfInstr( iter ) ) { - this.asmOut.append( "\tCALL\tPS_NL\n" ); - addLibItem( BasicLibrary.LibItem.PS_NL ); + if( ifEntry == null ) { + throw new PrgException( "ELSE ohne IF" ); + } + if( ifEntry.isIfCodeCreationDisabled() ) { + ifEntry.setIfCodeCreationDisabled( false ); + popCodeCreationDisabled(); + } + String elseLabel = ifEntry.getElseLabel(); + if( elseLabel != null ) { + if( ifEntry.isElseCodeCreationDisabled() ) { + pushCodeCreationDisabled(); + } else { + if( !ifEntry.isMultiLine() + && this.options.getPreferRelativeJumps() ) + { + this.asmOut.append( "\tJR\t" ); + } else { + this.asmOut.append( "\tJP\t" ); + } + this.asmOut.append( ifEntry.getEndifLabel() ); + this.asmOut.newLine(); + this.asmOut.append( elseLabel ); + this.asmOut.append( ":\n" ); + } + ifEntry.setElseLabel( null ); // ELSE als geparst markieren + break; } else { - parseToken( iter, ',' ); - parsePrint( iter, false ); + /* + * ELSE wurde schon geparst. + * Demzufolge muss sich das ELSE auf das vorherige IF beziehen. + * Deshalb wird hier der ELSE-Zweig der aktuellen IF-Anweisung + * geschlossen und im naechsten Schleifendurchlauf + * das ELSE der vorherigen IF-Anweisung verarbeitet. + */ + this.asmOut.append( ifEntry.getEndifLabel() ); + this.asmOut.append( ":\n" ); + this.structureStack.pop(); } - this.asmOut.append( endOfInstLabel ); - this.asmOut.append( ":\n" ); - } else { - parsePrint( iter, true ); } } - private void parsePSET( CharacterIterator iter ) throws PrgException + private void parseELSEIF( CharacterIterator iter ) throws PrgException { - checkGraphicsSupported(); - parsePointTo_DE_HL( iter ); - this.asmOut.append( "\tCALL\tXPSET\n" ); - addLibItem( BasicLibrary.LibItem.XPSET ); + IfEntry ifEntry = null; + if( !this.structureStack.isEmpty() ) { + BasicSourcePos entry = this.structureStack.peek(); + if( entry instanceof IfEntry ) { + ifEntry = (IfEntry) entry; + } + } + if( ifEntry == null ) { + throw new PrgException( "ELSE ohne IF" ); + } + String elseLabel = ifEntry.getElseLabel(); + if( elseLabel == null ) { + throw new PrgException( "ELSEIF hinter ELSE nicht zul\u00E4ssig" ); + } + setCodeCreationDisabledLevel( ifEntry.getCodeCreationDisabledLevel() ); + if( ifEntry.isIfCodeCreationDisabled() ) { + ifEntry.setIfCodeCreationDisabled( false ); + } else { + if( ifEntry.isElseCodeCreationDisabled() ) { + pushCodeCreationDisabled(); + } else { + if( !ifEntry.isMultiLine() && this.options.getPreferRelativeJumps() ) { + this.asmOut.append( "\tJR\t" ); + } else { + this.asmOut.append( "\tJP\t" ); + } + this.asmOut.append( ifEntry.getEndifLabel() ); + this.asmOut.newLine(); + this.asmOut.append( elseLabel ); + this.asmOut.append( ":\n" ); + ifEntry.setElseLabel( nextLabel() ); // neue ELSE-Marke erzeugen + } + } + parseIForELSEIF( iter, ifEntry ); } - private void parseREM( CharacterIterator iter ) + private void parseEND( CharacterIterator iter ) throws PrgException { - iter.setIndex( iter.getEndIndex() ); - } - + CallableEntry callableEntry = getEnclosingCallableEntry(); + if( BasicUtil.checkKeyword( iter, "FUNCTION" ) ) { + boolean ok = false; + if( callableEntry != null ) { + if( callableEntry instanceof FunctionEntry ) { + ok = true; + } + } + if( !ok ) { + throw new PrgException( + "Anweisung nur am Ende einer Funktion erlaubt" ); + } + } else if( BasicUtil.checkKeyword( iter, "SUB" ) ) { + boolean ok = false; + if( callableEntry != null ) { + if( callableEntry instanceof SubEntry ) { + ok = true; + } + } + if( !ok ) { + throw new PrgException( + "Anweisung nur am Ende einer Prozedur erlaubt" ); + } + } + if( callableEntry != null ) { + if( callableEntry instanceof FunctionEntry ) { + this.asmOut.append_LD_HL_IndirectIY( + ((FunctionEntry) callableEntry).getReturnVarIYOffs() ); + this.asmOut.append( "\tLD\t(M_FRET),HL\n" ); + addLibItem( BasicLibrary.LibItem.M_FRET ); + } - private void parseREAD( CharacterIterator iter ) throws PrgException - { - iter.previous(); - do { - iter.next(); - SimpleVarInfo varInfo = checkVariable( iter ); - if( varInfo != null ) { - switch( varInfo.getDataType() ) { - case INTEGER: - varInfo.ensureAddrInHL( this.asmOut ); - this.asmOut.append( "\tCALL\tDREADI\n" ); - addLibItem( BasicLibrary.LibItem.DREADI ); - break; - case STRING: - varInfo.ensureAddrInHL( this.asmOut ); - this.asmOut.append( "\tCALL\tDREADS\n" ); - addLibItem( BasicLibrary.LibItem.DREADS ); - break; + // alle Strukturen abgeschlossen? + while( !this.structureStack.isEmpty() ) { + BasicSourcePos entry = this.structureStack.pop(); + if( entry != callableEntry ) { + appendLineNumMsgToErrLog( + entry, + entry.toString() + " nicht abgeschlossen", + "Fehler" ); + this.errCnt++; + } + } + if( callableEntry.hasStackFrame() ) { + int nVars = callableEntry.getVarCount(); + // lokale String-Variablen freigeben + int nArgs = callableEntry.getArgCount(); + if( nArgs > 0 ) { + for( int i = 0; i < nArgs; i++ ) { + if( callableEntry.getArgType( i ) == DataType.STRING ) { + this.asmOut.append_LD_DE_IndirectIY( + callableEntry.getArgIYOffs( i, nArgs ) ); + this.asmOut.append( "\tCALL\tMFREE\n" ); + addLibItem( BasicLibrary.LibItem.MFREE ); + } + } + } + if( nVars > 0 ) { + for( int i = 0; i < nVars; i++ ) { + if( callableEntry.getVarType( i ) == DataType.STRING ) { + boolean done = false; + int iyOffs = callableEntry.getVarIYOffs( i ); + if( callableEntry instanceof FunctionEntry ) { + if( ((FunctionEntry) callableEntry).getReturnVarIYOffs() + == iyOffs ) + { + this.asmOut.append_LD_DE_IndirectIY( iyOffs ); + this.asmOut.append( "\tCALL\tMMGC\n" ); + addLibItem( BasicLibrary.LibItem.MMGC ); + done = true; + } + } + if( !done ) { + this.asmOut.append_LD_DE_IndirectIY( iyOffs ); + this.asmOut.append( "\tCALL\tMFREE\n" ); + addLibItem( BasicLibrary.LibItem.MFREE ); + } + } + } + this.asmOut.append( "\tLD\tSP,IY\n" ); } - } else { - throwVarExpected(); - } - } while( skipSpaces( iter ) == ',' ); - } - - - private void parseRESTORE( CharacterIterator iter ) throws PrgException - { - BasicLineExpr lineExpr = BasicLineExpr.checkBasicLineExpr( - iter, - this.curSourceLineNum, - this.curBasicLineNum ); - if( lineExpr != null ) { - if( this.restoreBasicLines == null ) { - this.restoreBasicLines = new ArrayList( 128 ); + this.asmOut.append( "\tPOP\tIY\n" ); } - this.restoreBasicLines.add( lineExpr ); - this.asmOut.append_LD_HL_xx( - createDataLineLabel( lineExpr.getExprText() ) ); - this.asmOut.append( "\tLD\t(M_READ),HL\n" ); + this.asmOut.append( "\tRET\n" ); } else { - if( !isEndOfInstr( iter ) ) { - throwBasicLineExprExpected(); - } - this.asmOut.append( "\tCALL\tDINIT\n" ); + this.asmOut.append( "\tJP\tXEXIT\n" ); } - addLibItem( BasicLibrary.LibItem.DATA ); } - private void parseRETURN() + private void parseENDIF( CharacterIterator iter ) throws PrgException { - if( this.options.getCheckStack() ) { - this.asmOut.append( "\tPOP\tAF\n" - + "\tCP\t" ); - this.asmOut.appendHex2( MAGIC_GOSUB ); - this.asmOut.append( "\n" - + "\tJP\tNZ,E_REWG\n" ); - addLibItem( BasicLibrary.LibItem.E_REWG ); + IfEntry ifEntry = null; + if( !this.structureStack.isEmpty() ) { + BasicSourcePos entry = this.structureStack.peek(); + if( entry instanceof IfEntry ) { + ifEntry = (IfEntry) entry; + this.structureStack.pop(); + } } - this.asmOut.append( "\tRET\n" ); - this.suppressExit = true; + if( ifEntry == null ) { + throw new PrgException( "ENDIF ohne IF" ); + } + setCodeCreationDisabledLevel( ifEntry.getCodeCreationDisabledLevel() ); + String elseLabel = ifEntry.getElseLabel(); + if( elseLabel != null ) { + this.asmOut.append( elseLabel ); + this.asmOut.append( ":\n" ); + } + this.asmOut.append( ifEntry.getEndifLabel() ); + this.asmOut.append( ":\n" ); } - private void parseSCREEN( CharacterIterator iter ) throws PrgException + private void parseEXIT( CharacterIterator iter ) throws PrgException { - parseExpr( iter ); - this.asmOut.append( "\tCALL\tSCREEN\n" ); - addLibItem( BasicLibrary.LibItem.SCREEN ); - } - - - private void parseSEND( CharacterIterator iter ) throws PrgException - { - checkKCNetSupported(); - parseToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, true, null ); - this.asmOut.append( "\tLD\t(KCNET_M_SOCKET),A\n" - + "\tLD\t(M_IOCA),HL\n" ); - parseToken( iter, ',' ); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tLD\t(M_HOST),HL\n" ); - parseToken( iter, ',' ); - parseExpr( iter ); - this.asmOut.append( "\tLD\t(M_PORT),HL\n" ); - this.asmOut.append( "\tCALL\tSEND\n" ); - addLibItem( BasicLibrary.LibItem.KCNET_BASE ); - addLibItem( BasicLibrary.LibItem.M_HOST ); - addLibItem( BasicLibrary.LibItem.M_PORT ); - addLibItem( BasicLibrary.LibItem.SEND ); - } - - - private void parseSET( CharacterIterator iter ) throws PrgException - { - if( checkKeyword( iter, "DNSSERVER" ) ) { - checkKCNetSupported(); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tCALL\tKCNET_SET_DNSSERVER\n" ); - addLibItem( BasicLibrary.LibItem.KCNET_SET_DNSSERVER ); - } else if( checkKeyword( iter, "GATEWAY" ) ) { - checkKCNetSupported(); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tCALL\tKCNET_SET_GATEWAY\n" ); - addLibItem( BasicLibrary.LibItem.KCNET_SET_GATEWAY ); - } else if( checkKeyword( iter, "LOCALADDR" ) ) { - checkKCNetSupported(); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tCALL\tKCNET_SET_LOCALADDR\n" ); - addLibItem( BasicLibrary.LibItem.KCNET_SET_LOCALADDR ); - } else if( checkKeyword( iter, "NETMASK" ) ) { - checkKCNetSupported(); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tCALL\tKCNET_SET_NETMASK\n" ); - addLibItem( BasicLibrary.LibItem.KCNET_SET_NETMASK ); - } else { - throw new PrgException( - "DNSSERVER, GATEWAY, LOCALADDR oder NETMASK erwartet" ); + LoopEntry loopEntry = null; + int idx = this.structureStack.size() - 1; + while( idx >= 0 ) { + BasicSourcePos entry = this.structureStack.get( idx ); + if( entry instanceof LoopEntry ) { + loopEntry = (LoopEntry) entry; + break; + } + if( !(entry instanceof IfEntry) ) { + break; + } + --idx; + } + if( loopEntry == null ) { + throw new PrgException( "EXIT aus\u00DFerhalb einer Schleife" ); + } + if( !isEndOfInstr( iter ) ) { + if( !BasicUtil.checkKeyword( iter, loopEntry.getLoopBegKeyword() ) ) { + throw new PrgException( loopEntry.getLoopBegKeyword() + + " oder Ende der Anweisung erwartet" ); + } } + this.asmOut.append( "\tJP\t" ); + this.asmOut.append( loopEntry.getExitLabel() ); + this.asmOut.newLine(); } - private void parseWAIT( CharacterIterator iter ) throws PrgException + private void parseFOR( CharacterIterator iter ) throws PrgException { - // Code fuer erstes Argument erzeugen (in BC) - int pos = this.asmOut.length(); - parseExpr( iter ); - String oldCode1 = this.asmOut.cut( pos ); - String newCode1 = convertCodeToValueInBC( oldCode1 ); - - // Code fuer zweites Argument erzeugen (in E) - parseToken( iter, ',' ); - parseExpr( iter ); - String oldCode2 = this.asmOut.substring( pos ); - String newCode2 = null; - Integer mask = removeLastCodeIfConstExpr( pos ); - this.asmOut.setLength( pos ); - if( mask != null ) { - int v = mask.intValue() & 0xFF; - newCode2 = String.format( - "\tLD\tE,%s%02XH\n", - v >= 0xA0 ? "0" : "", - v ); - } else { - if( isOnly_LD_HL_xx( oldCode2 ) ) { - newCode2 = oldCode2 + "\tLD\tE,L\n"; + SimpleVarInfo varInfo = checkVariable( iter ); + if( varInfo != null ) { + if( varInfo.getDataType() != DataType.INTEGER ) { + varInfo = null; } } - - // Code fuer optionales drittes Argument erzeugen (in L) - String oldCode3 = null; - String newCode3 = null; - if( checkToken( iter, ',' ) ) { - parseExpr( iter ); - oldCode3 = this.asmOut.substring( pos ); - Integer inv = removeLastCodeIfConstExpr( pos ); - this.asmOut.setLength( pos ); - if( inv != null ) { - int v = inv.intValue() & 0xFF; - newCode3 = String.format( - "\tLD\tL,%s%02XH\n", - v >= 0xA0 ? "0" : "", - v ); - } else { - if( isOnly_LD_HL_xx( oldCode3 ) ) { - newCode3 = oldCode3; - } - } + if( varInfo == null ) { + throw new PrgException( "Integer-Variable erwartet" ); } - - /* - * Gesamtcode erzeugen - * BC: Port - * E: Maske - * L: Inversion - */ - if( (newCode1 != null) - && (newCode2 != null) - && ((oldCode3 == null) || (newCode3 != null)) ) - { - this.asmOut.append( newCode1 ); - this.asmOut.append( newCode2 ); - if( newCode3 != null ) { - this.asmOut.append( newCode3 ); - } - } else { - this.asmOut.append( oldCode1 ); + if( !varInfo.hasStaticAddr() ) { + throw new PrgException( + "Laufvariable darf keine Feldvariable mit variablen" + + " Indexangaben sein." ); + } + parseAssignment( iter, varInfo ); + if( !checkInstruction( iter, "TO" ) ) { + throw new PrgException( "TO erwartet" ); + } + int toPos = this.asmOut.length(); + BasicExprParser.parseExpr( this, iter ); + Integer toValue = BasicUtil.removeLastCodeIfConstExpr( this, toPos ); + if( toValue == null ) { this.asmOut.append( "\tPUSH\tHL\n" ); - if( oldCode3 != null ) { - if( newCode3 != null ) { - if( newCode2 != null ) { - this.asmOut.append( newCode2 ); - } else { - this.asmOut.append( oldCode2 ); - this.asmOut.append( "\tLD\tE,L\n" ); - } - this.asmOut.append( newCode3 ); - } else { - this.asmOut.append( oldCode2 ); - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode3 ); - this.asmOut.append( "\tPOP\tDE\n" ); - } - } else { - if( newCode2 != null ) { - this.asmOut.append( newCode2 ); - } else { - this.asmOut.append( oldCode2 ); - this.asmOut.append( "\tLD\tE,L\n" ); - } + } + Integer stepValue = null; + if( checkInstruction( iter, "STEP" ) ) { + int stepPos = this.asmOut.length(); + BasicExprParser.parseExpr( this, iter ); + stepValue = BasicUtil.removeLastCodeIfConstExpr( this, stepPos ); + if( stepValue == null ) { + this.asmOut.append( "\tPUSH\tHL\n" ); } - this.asmOut.append( "\tPOP\tBC\n" ); + } else { + stepValue = new Integer( 1 ); } String loopLabel = nextLabel(); this.asmOut.append( loopLabel ); - this.asmOut.append( ":\tIN\tA,(C)\n" ); - if( oldCode3 != null ) { - this.asmOut.append( "\tXOR\tL\n" ); - } - this.asmOut.append( "\tAND\tE\n" ); - if( this.options.canBreakAlways() ) { - String exitLabel = nextLabel(); - this.asmOut.append( "\tJR\tNZ," ); - this.asmOut.append( exitLabel ); - this.asmOut.append( "\n" - + "\tCALL\tXCKBRK\n" - + "\tJR\t" ); - this.asmOut.append( loopLabel ); - this.asmOut.newLine(); - this.asmOut.append( exitLabel ); - this.asmOut.append( ":\n" ); - addLibItem( BasicLibrary.LibItem.XCKBRK ); - } else { - this.asmOut.append( "\tJR\tZ," ); - this.asmOut.append( loopLabel ); - this.asmOut.newLine(); - } + this.asmOut.append( (char) ':' ); + checkAppendBreakCheck(); + this.asmOut.newLine(); + this.structureStack.push( + new ForEntry( + this.curSource, + this.curBasicLineNum, + loopLabel, + nextLabel(), + varInfo, + toValue, + stepValue ) ); } - private void parseWEND( CharacterIterator iter ) throws PrgException + private void parseGOSUB( CharacterIterator iter ) throws PrgException { - WhileEntry whileEntry = null; - if( !this.structureStack.isEmpty() ) { - StructureEntry entry = this.structureStack.peek(); - if( entry instanceof WhileEntry ) { - whileEntry = (WhileEntry) entry; - this.structureStack.pop(); - } + checkAppendBreakCheck(); + if( this.options.getCheckStack() + && (this.options.getStackSize() > 0) ) + { + this.asmOut.append( "\tCALL\tCKSTK\n" ); + addLibItem( BasicLibrary.LibItem.CKSTK ); } - if( whileEntry == null ) { - throw new PrgException( "WEND ohne WHILE" ); + String destLabel = parseDestLineExpr( iter ); + if( this.options.getCheckStack() ) { + String endOfInstLabel = nextLabel(); + this.asmOut.append( "\tLD\tHL," ); + this.asmOut.append( endOfInstLabel ); + this.asmOut.append( "\n" + + "\tPUSH\tHL\n" ); + this.asmOut.append_LD_A_n( MAGIC_GOSUB ); + this.asmOut.append( "\tPUSH\tAF\n" + + "\tJP\t" ); + this.asmOut.append( destLabel ); + this.asmOut.newLine(); + this.asmOut.append( endOfInstLabel ); + this.asmOut.append( ":\n" ); + } else { + this.asmOut.append( "\tCALL\t" ); + this.asmOut.append( destLabel ); + this.asmOut.newLine(); } - this.asmOut.append( "\tJP\t" ); - this.asmOut.append( whileEntry.getLoopLabel() ); - this.asmOut.newLine(); - this.asmOut.append( whileEntry.getExitLabel() ); - this.asmOut.append( ":\n" ); } - private void parseWHILE( CharacterIterator iter ) throws PrgException + private void parseGOTO( CharacterIterator iter ) throws PrgException { - String loopLabel = nextLabel(); - String exitLabel = nextLabel(); - - this.asmOut.append( loopLabel ); - this.asmOut.append( ":\n" ); - parseExpr( iter ); - checkKeyword( iter, "DO" ); - this.asmOut.append( "\tLD\tA,H\n" - + "\tOR\tL\n" - + "\tJP\tZ," + exitLabel + "\n" ); checkAppendBreakCheck(); - this.structureStack.push( - new WhileEntry( - this.curSourceLineNum, - this.curBasicLineNum, - loopLabel, - exitLabel ) ); + String destLabel = parseDestLineExpr( iter ); + this.asmOut.append( "\tJP\t" ); + this.asmOut.append( destLabel ); + this.asmOut.newLine(); } - /* --- Parsen der BASIC-Funktionen --- */ - - private void parseABS( CharacterIterator iter ) throws PrgException - { - parseEnclosedExpr( iter ); - this.asmOut.append( "\tCALL\tABSHL\n" ); - addLibItem( BasicLibrary.LibItem.ABS_NEG_HL ); - } + private void parseIForELSEIF( + CharacterIterator iter, + IfEntry ifEntry ) throws PrgException + { + boolean codeCreationDisabled = false; + if( ifEntry != null ) { + codeCreationDisabled = ifEntry.isElseCodeCreationDisabled(); + } + Integer condValue = null; + if( !codeCreationDisabled ) { + condValue = BasicExprParser.checkParseConstExpr( this, iter ); + } + Set condLibItems = null; + int condBegPos = this.asmOut.length(); + boolean condOptimized = false; + if( condValue == null ) { + Set tmpLibItems = this.libItems; + this.libItems = new HashSet<>(); + BasicExprParser.parseExpr( this, iter ); + condLibItems = this.libItems; + this.libItems = tmpLibItems; + this.asmOut.append( "\tLD\tA,H\n" + + "\tOR\tL\n" ); + } + // auf GOTO und mehrzeiliges IF pruefen + BasicLineExpr gotoLineExpr = null; + boolean multiLine = false; + if( BasicUtil.checkKeyword( iter, "THEN" ) ) { + if( isEndOfInstr( iter ) ) { + multiLine = true; + } else { + char ch = BasicUtil.skipSpaces( iter ); + if( (ch >= '0') && (ch <= '9') ) { + gotoLineExpr = BasicLineExpr.checkBasicLineExpr( + iter, + this.curSource, + this.curBasicLineNum ); + } + } + } else { + if( isEndOfInstr( iter ) ) { + multiLine = true; + } + } + if( !multiLine && (gotoLineExpr == null) ) { + if( BasicUtil.checkKeyword( iter, "GOTO" ) ) { + gotoLineExpr = BasicLineExpr.checkBasicLineExpr( + iter, + this.curSource, + this.curBasicLineNum ); + if( gotoLineExpr == null ) { + throwBasicLineExprExpected(); + } + } + } - private void parseASC( CharacterIterator iter ) throws PrgException - { - parseToken( iter, '(' ); - parseStringPrimExpr( iter ); - parseToken( iter, ')' ); - this.asmOut.append( "\tLD\tL,(HL)\n" - + "\tLD\tH,00H\n" ); + /* + * Wenn hinter der Bedingung nur noch ein GOTO folgt + * und die Tastatur auch nicht auf Programmabbruch abgefragt werden muss, + * kann der Programmcode optimiert werden. + */ + if( (condValue == null) + && (gotoLineExpr != null) + && !this.options.canBreakAlways() + && isEndOfInstr( iter ) ) + { + this.asmOut.append( "\tJP\tNZ," ); + this.asmOut.append( + createBasicLineLabel( gotoLineExpr.getExprText() ) ); + this.asmOut.newLine(); + this.destBasicLines.add( gotoLineExpr ); + } else { + boolean ifCodeCreationDisabled = false; + boolean elseCodeCreationDisabled = codeCreationDisabled; + if( condValue != null ) { + if( condValue.intValue() == 0 ) { + ifCodeCreationDisabled = true; + } else { + elseCodeCreationDisabled = true; + } + } + if( ifEntry != null ) { + ifEntry.setIfCodeCreationDisabled( ifCodeCreationDisabled ); + if( elseCodeCreationDisabled ) { + ifEntry.setElseCodeCreationDisabled( true ); + } + } else { + ifEntry = new IfEntry( + this.curSource, + this.curBasicLineNum, + multiLine, + nextLabel(), + nextLabel(), + this.codeCreationDisabledLevel, + ifCodeCreationDisabled, + elseCodeCreationDisabled ); + this.structureStack.push( ifEntry ); + } + if( !codeCreationDisabled && ifCodeCreationDisabled ) { + pushCodeCreationDisabled(); + } else { + String elseLabel = ifEntry.getElseLabel(); + String jmpInstr = "\tJP\t"; + if( !multiLine && this.options.getPreferRelativeJumps() ) { + jmpInstr = "\tJR\t"; + } + if( !elseCodeCreationDisabled ) { + java.util.List lines = this.asmOut.getLinesAsList( + condBegPos ); + if( lines.size() == 5 ) { + String line0 = lines.get( 0 ); + String line1 = lines.get( 1 ); + String line2 = lines.get( 2 ); + if( line0.startsWith( "\tLD\tHL," ) + && line1.startsWith( "\tLD\tDE," ) ) + { + if( line2.equals( "\tCALL\tO_LT\n" ) ) { + if( line1.equals( "\tLD\tDE,0000H\n" ) ) { + this.asmOut.cut( condBegPos ); + this.asmOut.append( line0 ); + this.asmOut.append( "\tBIT\t7,H\n" ); + this.asmOut.append( jmpInstr ); + this.asmOut.append( "Z," ); + this.asmOut.append( elseLabel ); + this.asmOut.newLine(); + condOptimized = true; + } + } + else if( line2.equals( "\tCALL\tO_GE\n" ) ) { + if( line1.equals( "\tLD\tDE,0000H\n" ) ) { + this.asmOut.cut( condBegPos ); + this.asmOut.append( line0 ); + this.asmOut.append( "\tBIT\t7,H\n" ); + this.asmOut.append( jmpInstr ); + this.asmOut.append( "NZ," ); + this.asmOut.append( elseLabel ); + this.asmOut.newLine(); + condOptimized = true; + } + } + else if( line2.equals( "\tCALL\tO_EQ\n" ) ) { + this.asmOut.cut( condBegPos ); + this.asmOut.append( line0 ); + if( line1.equals( "\tLD\tDE,0000H\n" ) ) { + this.asmOut.append( "\tLD\tA,H\n" + + "\tOR\tL\n" ); + } else { + this.asmOut.append( line1 ); + this.asmOut.append( "\tOR\tA\n" + + "\tSBC\tHL,DE\n" ); + } + this.asmOut.append( jmpInstr ); + this.asmOut.append( "NZ," ); + this.asmOut.append( elseLabel ); + this.asmOut.newLine(); + condOptimized = true; + } + else if( line2.equals( "\tCALL\tO_NE\n" ) ) { + this.asmOut.cut( condBegPos ); + this.asmOut.append( line0 ); + if( line1.equals( "\tLD\tDE,0000H\n" ) ) { + this.asmOut.append( "\tLD\tA,H\n" + + "\tOR\tL\n" ); + } else { + this.asmOut.cut( condBegPos ); + this.asmOut.append( line0 ); + this.asmOut.append( line1 ); + this.asmOut.append( "\tOR\tA\n" + + "\tSBC\tHL,DE\n" ); + } + this.asmOut.append( jmpInstr ); + this.asmOut.append( "Z," ); + this.asmOut.append( elseLabel ); + this.asmOut.newLine(); + condOptimized = true; + } + } + } + if( !condOptimized ) { + this.asmOut.append( jmpInstr ); + this.asmOut.append( "Z," ); + this.asmOut.append( ifEntry.getElseLabel() ); + this.asmOut.newLine(); + } + } + if( gotoLineExpr != null ) { + checkAppendBreakCheck(); + this.asmOut.append( "\tJP\t" ); + this.asmOut.append( + createBasicLineLabel( gotoLineExpr.getExprText() ) ); + this.asmOut.newLine(); + this.destBasicLines.add( gotoLineExpr ); + } + } + if( gotoLineExpr == null ) { + this.separatorChecked = true; + } + } + if( !condOptimized && (condLibItems != null) ) { + this.libItems.addAll( condLibItems ); + } } - private void parseAVAILABLE( CharacterIterator iter ) throws PrgException + private void parseINCLUDE( CharacterIterator iter ) throws PrgException { - parseToken( iter, '(' ); - checkToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, false, null ); - this.asmOut.append( "\tCALL\tIOAVAILABLE\n" ); - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.IOAVAILABLE ); + File file = PrgSource.getIncludeFile( + this.curSource, + parseStringLiteral( iter ) ); + /* + * Hinter INCLUDE darf in der Zeile keine weitere BASIC-Anweisung + * mehr folgen. + */ + char ch = BasicUtil.skipSpaces( iter ); + if( ch != CharacterIterator.DONE ) { + BasicUtil.throwUnexpectedChar( ch ); + } + if( this.curSource != this.mainSource ) { + throw new PrgException( + "In sich geschachtelte INCLUDE-Anweisungen nicht erlaubt" ); + } + try { + this.curSource = PrgSource.readFile( file ); + } + catch( IOException ex ) { + String msg = ex.getMessage(); + if( msg != null ) { + msg = msg.trim(); + if( msg.isEmpty() ) { + msg = null; + } + } + if( msg == null ) { + msg = "Datei kann nicht ge\u00F6ffnet werden."; + } + throw new PrgException( msg ); + } + if( this.options.getPrintLineNumOnAbort() ) { + String srcName = this.curSource.getName(); + if( srcName != null ) { + if( !srcName.isEmpty() ) { + this.asmOut.append_LD_HL_xx( getStringLiteralLabel( srcName ) ); + this.asmOut.append( "\tLD\t(M_SRNM),HL\n" ); + this.libItems.add( BasicLibrary.LibItem.M_SRNM ); + } + } + } } - private void parseDEEK( CharacterIterator iter ) throws PrgException + private void parseINK( CharacterIterator iter ) throws PrgException { - int pos = this.asmOut.length(); - parseEnclosedExpr( iter ); - Integer addr = removeLastCodeIfConstExpr( pos ); - if( addr != null ) { - this.asmOut.append( "\tLD\tHL,(" ); - this.asmOut.appendHex4( addr.intValue() ); - this.asmOut.append( ")\n" ); + if( this.target.supportsColors() ) { + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tCALL\tXINK\n" ); + addLibItem( BasicLibrary.LibItem.XINK ); } else { - this.asmOut.append( "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\tH,(HL)\n" - + "\tLD\tL,A\n" ); + try { + pushCodeCreationDisabled(); + BasicExprParser.parseExpr( this, iter ); + } + finally { + popCodeCreationDisabled(); + } } } - private void parseEOF( CharacterIterator iter ) throws PrgException + private void parseINPUT( CharacterIterator iter ) throws PrgException { - parseToken( iter, '(' ); - checkToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, false, null ); - this.asmOut.append( "\tCALL\tIOEOF\n" ); - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.IOEOF ); - } + if( BasicUtil.checkToken( iter, '#' ) ) { + parseIOChannelNumToPtrFldAddrInHL( iter, null ); + int begPos_IO_M_CADDR = this.asmOut.length(); + this.asmOut.append( "\tLD\t(IO_M_CADDR),HL\n" ); + int endPos_IO_M_CADDR = this.asmOut.length(); + BasicUtil.parseToken( iter, ',' ); + BasicLibrary.appendResetErrorUseBC( this ); + boolean multiVars = false; + for(;;) { + this.asmOut.append( "\tLD\tC,2CH\n" // Komma als Trennzeichen + + "\tCALL\tIOINL\n" ); + addLibItem( BasicLibrary.LibItem.IOINL ); + int pos = this.asmOut.length(); + SimpleVarInfo varInfo = checkVariable( iter ); + if( varInfo == null ) { + throwVarExpected(); + } + varInfo.ensureAddrInHL( this.asmOut ); + String oldVarCode = this.asmOut.cut( pos ); + if( varInfo.getDataType() == DataType.INTEGER ) { + this.asmOut.append( "\tCALL\tF_VLI1\n" ); + if( BasicUtil.isSingleInst_LD_HL_xx( oldVarCode ) ) { + this.asmOut.append( "\tEX\tDE,HL\n" ); + this.asmOut.append( oldVarCode ); + } else { + this.asmOut.append( "\tPUSH\tHL\n" ); + this.asmOut.append( oldVarCode ); + this.asmOut.append( "\tPOP\tDE\n" ); + } + this.asmOut.append( "\tLD\t(HL),E\n" + + "\tINC\tHL\n" + + "\tLD\t(HL),D\n" ); + addLibItem( BasicLibrary.LibItem.F_VLI ); + } else if( varInfo.getDataType() == DataType.STRING ) { + String newVarCode = BasicUtil.convertCodeToValueInDE( oldVarCode ); + if( newVarCode != null ) { + this.asmOut.append( newVarCode ); + } else { + this.asmOut.append( "\tPUSH\tHL\n" ); + this.asmOut.append( oldVarCode ); + this.asmOut.append( "\tEX\tDE,HL\n" + + "\tPOP\tHL\n" ); + } + this.asmOut.append( "\tCALL\tASSIGN_STR_TO_NEW_MEM_VS\n" ); + addLibItem( BasicLibrary.LibItem.ASSIGN_STR_TO_NEW_MEM_VS ); + } + if( !BasicUtil.checkToken( iter, ',' ) ) { + break; + } + this.asmOut.append( "\tLD\tHL,(IO_M_CADDR)\n" ); + multiVars = true; + } + if( !multiVars ) { + this.asmOut.delete( begPos_IO_M_CADDR, endPos_IO_M_CADDR ); + } + } else { - private void parseHIBYTE( CharacterIterator iter ) throws PrgException - { - parseEnclosedExpr( iter ); - this.asmOut.append( "\tLD\tL,H\n" - + "\tLD\tH,00H\n" ); - } + // Eingabe von Tastatur + do { + String retryLabel = nextLabel(); + this.asmOut.append( retryLabel ); + this.asmOut.append( ":\n" ); + // Prompt + String text = BasicUtil.checkStringLiteral( this, iter ); + if( text != null ) { + // String-Literal evtl. bereits vorhanden? + String label = this.str2Label.get( text ); + if( label != null ) { + this.asmOut.append( "\tLD\tHL," ); + this.asmOut.append( label ); + this.asmOut.append( "\n" + + "\tCALL\tXOUTS\n" ); + addLibItem( BasicLibrary.LibItem.XOUTS ); + } else { + this.asmOut.append( "\tCALL\tXOUTST\n" ); + this.asmOut.appendStringLiteral( text ); + addLibItem( BasicLibrary.LibItem.XOUTST ); + } + BasicUtil.checkToken( iter, ';' ); + } else { + this.asmOut.append_LD_A_n( '?' ); + this.asmOut.append( "\tCALL\tXOUTCH\n" ); + addLibItem( BasicLibrary.LibItem.XOUTCH ); + } - private void parseIN( CharacterIterator iter ) throws PrgException - { - parseToken( iter, '(' ); - parseExpr( iter ); - parseToken( iter, ')' ); - if( !replaceLastCodeFrom_LD_HL_To_BC() ) { - this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" ); + // Variable + SimpleVarInfo varInfo = checkVariable( iter ); + if( varInfo == null ) { + if( text != null ) { + throwVarExpected(); + } + throwStringLitOrVarExpected(); + } + if( varInfo.getDataType() == DataType.INTEGER ) { + varInfo.ensureAddrInHL( this.asmOut ); + this.asmOut.append( "\tCALL\tINIV\n" ); + if( this.options.getPreferRelativeJumps() ) { + this.asmOut.append( "\tJR\tC," ); + } else { + this.asmOut.append( "\tJP\tC," ); + } + this.asmOut.append( retryLabel ); + this.asmOut.newLine(); + addLibItem( BasicLibrary.LibItem.INIV ); + } else if( varInfo.getDataType() == DataType.STRING ) { + varInfo.ensureAddrInHL( this.asmOut ); + this.asmOut.append( "\tCALL\tINSV\n" ); + addLibItem( BasicLibrary.LibItem.INSV ); + } + } while( BasicUtil.checkToken( iter, ';' ) ); } - this.asmOut.append( "\tIN\tL,(C)\n" - + "\tLD\tH,0\n" ); } - private void parseINSTR( CharacterIterator iter ) throws PrgException + private void parseLET( CharacterIterator iter ) throws PrgException { - boolean hasStartPos = false; - Integer startPosValue = null; - String startPosText = null; - parseToken( iter, '(' ); - String text = checkStringLiteral( iter ); - if( text == null ) { - hasStartPos = !checkParseStringPrimExpr( iter ); - } - if( hasStartPos ) { - int pos = this.asmOut.length(); - parseExpr( iter ); - startPosValue = removeLastCodeIfConstExpr( pos ); - if( startPosValue != null ) { - if( startPosValue.intValue() <= 0 ) { - throwIndexOutOfRange(); - } - } else { - String oldCode = this.asmOut.cut( pos ); - startPosText = convertCodeToValueInBC( oldCode ); - if( startPosText == null ) { - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPUSH\tHL\n" ); - } - } - parseToken( iter, ',' ); - text = checkStringLiteral( iter ); - if( text == null ) { - parseStringPrimExpr( iter ); - } - } - parseToken( iter, ',' ); - String pattern = checkStringLiteral( iter ); - if( pattern != null ) { - if( text != null ) { - this.asmOut.append( "\tLD\tHL," ); - this.asmOut.append( getStringLiteralLabel( text ) ); - this.asmOut.newLine(); - } - this.asmOut.append( "\tLD\tDE," ); - this.asmOut.append( getStringLiteralLabel( pattern ) ); - this.asmOut.newLine(); - } else { - if( text == null ) { - this.asmOut.append( "\tPUSH\tHL\n" ); - } - parseStringPrimExpr( iter ); - this.asmOut.append( "\tEX\tDE,HL\n" ); - if( text != null ) { - this.asmOut.append( "\tLD\tHL," ); - this.asmOut.append( getStringLiteralLabel( text ) ); - this.asmOut.newLine(); - } else { - this.asmOut.append( "\tPOP\tHL\n" ); - } + String varName = BasicUtil.checkIdentifier( iter ); + if( varName == null ) { + throwVarExpected(); } - if( hasStartPos ) { - if( startPosValue != null ) { - this.asmOut.append_LD_BC_nn( startPosValue.intValue() ); - } else if( startPosText != null ) { - this.asmOut.append( startPosText ); + if( !checkReturnValueAssignment( iter, varName ) ) { + SimpleVarInfo varInfo = checkVariable( iter, varName ); + if( varInfo != null ) { + parseAssignment( iter, varInfo ); } else { - this.asmOut.append( "\tPOP\tBC\n" ); + throwVarExpected(); } - this.asmOut.append( "\tCALL\tF_INSTRN\n" ); - addLibItem( BasicLibrary.LibItem.F_INSTRN ); - } else { - this.asmOut.append( "\tCALL\tF_INSTR\n" ); - addLibItem( BasicLibrary.LibItem.F_INSTR ); } - parseToken( iter, ')' ); - } - - - private void parseJOYST( CharacterIterator iter ) throws PrgException - { - parseEnclosedExpr( iter ); - this.asmOut.append( "\tCALL\tF_JOY\n" ); - addLibItem( BasicLibrary.LibItem.F_JOY ); } - private void parseLEN( CharacterIterator iter ) throws PrgException + private void parseLABEL( CharacterIterator iter ) throws PrgException { - parseToken( iter, '(' ); - String text = checkStringLiteral( iter ); - if( text != null ) { - this.asmOut.append_LD_HL_nn( text.length() ); - } else { - parseStringPrimExpr( iter ); - this.asmOut.append( "\tCALL\tF_LEN\n" ); - addLibItem( BasicLibrary.LibItem.F_LEN ); + checkGraphicsSupported(); + for(;;) { + String text = BasicUtil.checkStringLiteral( this, iter ); + if( text != null ) { + if( !text.isEmpty() ) { + // String-Literal evtl. bereits vorhanden? + String label = this.str2Label.get( text ); + if( label != null ) { + this.asmOut.append( "\tLD\tHL," ); + this.asmOut.append( label ); + this.asmOut.append( "\n" + + "\tCALL\tDRLBL\n" ); + addLibItem( BasicLibrary.LibItem.DRLBL ); + } else { + this.asmOut.append( "\tCALL\tDRLBLT\n" ); + this.asmOut.appendStringLiteral( text ); + addLibItem( BasicLibrary.LibItem.DRLBLT ); + } + } + } else { + if( !BasicExprParser.checkParseStringPrimVarExpr( this, iter ) ) { + BasicUtil.throwStringExprExpected(); + } + this.asmOut.append( "\tCALL\tDRLBL\n" ); + addLibItem( BasicLibrary.LibItem.DRLBL ); + } + if( BasicUtil.skipSpaces( iter ) != '+' ) { + break; + } + iter.next(); } - parseToken( iter, ')' ); } - private void parseLOBYTE( CharacterIterator iter ) throws PrgException + private void parseLINE( CharacterIterator iter ) throws PrgException { - parseEnclosedExpr( iter ); - this.asmOut.append( "\tLD\tH,00H\n" ); - } - + if( BasicUtil.checkKeyword( iter, "INPUT" ) ) { - private void parseLOCALPORT( CharacterIterator iter ) throws PrgException - { - checkKCNetSupported(); - parseToken( iter, '(' ); - checkToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, false, null ); - this.asmOut.append( "\tCALL\tF_LOCALPORT\n" ); - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.F_LOCALPORT ); - } + // Eingabe einer ganzen Zeile + if( BasicUtil.checkToken( iter, '#' ) ) { + parseInputLineIO( iter ); + } else { + parseInputLine( iter, false ); + } + } else { - private void parseMAX( CharacterIterator iter ) throws PrgException - { - parseToken( iter, '(' ); - parseExpr( iter ); - while( checkToken( iter, ',' ) ) { - this.asmOut.append( "\tPUSH\tHL\n" ); - parseExpr( iter ); - String label = nextLabel(); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tCPHLDE\n" - + "\tJR\tNC," ); - this.asmOut.append( label ); - this.asmOut.append( "\n" - + "\tEX\tDE,HL\n" ); - this.asmOut.append( label ); - this.asmOut.append( ":\n" ); + // Zeichnen einer Linie oder eines Rechtecks + checkGraphicsSupported(); + boolean enclosed = BasicUtil.checkToken( iter, '(' ); + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tLD\t(LINE_M_BX),HL\n" ); + BasicUtil.parseToken( iter, ',' ); + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tLD\t(LINE_M_BY),HL\n" ); + if( enclosed ) { + BasicUtil.parseToken( iter, ')' ); + BasicUtil.parseToken( iter, '-' ); + BasicUtil.parseToken( iter, '(' ); + } else { + BasicUtil.parseToken( iter, ',' ); + } + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tLD\t(LINE_M_EX),HL\n" ); + BasicUtil.parseToken( iter, ',' ); + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tLD\t(LINE_M_EY),HL\n" ); + if( enclosed ) { + BasicUtil.parseToken( iter, ')' ); + } + if( BasicUtil.checkToken( iter, ',' ) ) { + if( BasicUtil.checkKeyword( iter, "BF" ) ) { + this.asmOut.append( "\tCALL\tDRBOXF\n" ); + addLibItem( BasicLibrary.LibItem.DRBOXF ); + } else { + if( !BasicUtil.checkKeyword( iter, "B" ) ) { + throw new PrgException( "B oder BF erwartet" ); + } + this.asmOut.append( "\tCALL\tDRBOX\n" ); + addLibItem( BasicLibrary.LibItem.DRBOX ); + } + } else { + this.asmOut.append( "\tCALL\tDRAW_LINE\n" ); + addLibItem( BasicLibrary.LibItem.DRAW_LINE ); + } } - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.CPHLDE ); } - private void parseMIN( CharacterIterator iter ) throws PrgException + private void parseLOCAL( CharacterIterator iter ) throws PrgException { - parseToken( iter, '(' ); - parseExpr( iter ); - while( checkToken( iter, ',' ) ) { - this.asmOut.append( "\tPUSH\tHL\n" ); - parseExpr( iter ); - String label = nextLabel(); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tCPHLDE\n" - + "\tJR\tC," ); - this.asmOut.append( label ); - this.asmOut.append( "\n" - + "\tEX\tDE,HL\n" ); - this.asmOut.append( label ); - this.asmOut.append( ":\n" ); + CallableEntry entry = getEnclosingCallableEntry(); + if( entry == null ) { + throw new PrgException( + "Anweisung nur in einer Funktion/Prozedur erlaubt" ); } - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.CPHLDE ); - } - - - private void parsePEEK( CharacterIterator iter ) throws PrgException - { - parseEnclosedExpr( iter ); - this.asmOut.append( "\tLD\tL,(HL)\n" - + "\tLD\tH,0\n" ); - } - - - private void parseREMOTEPORT( CharacterIterator iter ) throws PrgException - { - checkKCNetSupported(); - parseToken( iter, '(' ); - checkToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, false, null ); - this.asmOut.append( "\tCALL\tF_REMOTEPORT\n" ); - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.F_REMOTEPORT ); + do { + String varName = BasicUtil.checkIdentifier( iter ); + if( varName == null ) { + throwVarNameExpected(); + } + checkVarName( varName ); + if( varName.equals( entry.getName() ) ) { + throw new PrgException( "Name der Funktion/Prozedur" + + " als Variablenname nicht zul\u00E4ssig" ); + } + entry.addVar( this.curSource, this.curBasicLineNum, varName ); + } while( BasicUtil.checkToken( iter, ',' ) ); } - private void parseRND( CharacterIterator iter ) throws PrgException + private void parseLOCATE( CharacterIterator iter ) throws PrgException { - parseEnclosedExpr( iter ); - this.asmOut.append( "\tCALL\tF_RND\n" ); - addLibItem( BasicLibrary.LibItem.F_RND ); + BasicExprParser.parse2ArgsTo_DE_HL( this, iter ); + if( this.target.supportsXLOCAT() ) { + this.asmOut.append( "\tCALL\tLOCATE\n" ); + addLibItem( BasicLibrary.LibItem.LOCATE ); + } else { + throw new PrgException( "LOCATE-Anweisung f\u00FCr das" + + " Zielsystem nicht unterst\u00FCtzt" ); + } } - private void parsePTEST( CharacterIterator iter ) throws PrgException + private void parseLOOP( CharacterIterator iter ) throws PrgException { - parseToken( iter, '(' ); - parse2ArgsTo_DE_HL( iter ); - parseToken( iter, ')' ); - this.asmOut.append( "\tCALL\tXPTEST\n" ); - addLibItem( BasicLibrary.LibItem.XPTEST ); + DoEntry doEntry = null; + if( !this.structureStack.isEmpty() ) { + BasicSourcePos entry = this.structureStack.peek(); + if( entry instanceof DoEntry ) { + doEntry = (DoEntry) entry; + this.structureStack.pop(); + } + } + if( doEntry == null ) { + throw new PrgException( "LOOP ohne DO" ); + } + if( BasicUtil.checkKeyword( iter, "UNTIL" ) ) { + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tLD\tA,H\n" + + "\tOR\tL\n" + + "\tJP\tZ," + doEntry.getLoopLabel() + "\n" ); + } else if( BasicUtil.checkKeyword( iter, "WHILE" ) ) { + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tLD\tA,H\n" + + "\tOR\tL\n" + + "\tJP\tNZ," + doEntry.getLoopLabel() + "\n" ); + } else { + if( !isEndOfInstr( iter ) ) { + throw new PrgException( + "UNTIL, WHILE oder Ende der Anweisung erwartet" ); + } + this.asmOut.append( "\tJP\t" ); + this.asmOut.append( doEntry.getLoopLabel() ); + this.asmOut.newLine(); + } + this.asmOut.append( doEntry.getExitLabel() ); + this.asmOut.append( ":\n" ); } - private void parseSGN( CharacterIterator iter ) throws PrgException + private void parseLPRINT( CharacterIterator iter ) throws PrgException { - parseEnclosedExpr( iter ); - this.asmOut.append( "\tCALL\tF_SGN\n" ); - addLibItem( BasicLibrary.LibItem.F_SGN ); + if( !this.target.supportsXLPTCH() ) { + throw new PrgException( + "Druckerausgaben f\u00FCr das Zielsystem" + + " nicht unterst\u00FCtzt" ); + } + this.asmOut.append( "\tLD\tHL,XLPTCH\n" + + "\tLD\t(IO_M_COUT),HL\n" ); + addLibItem( BasicLibrary.LibItem.XLPTCH ); + addLibItem( BasicLibrary.LibItem.IO_M_COUT ); + parsePrint( iter, false ); } - private void parseSQR( CharacterIterator iter ) throws PrgException + private void parseMOVE( CharacterIterator iter ) throws PrgException { - int pos = this.asmOut.length(); - parseEnclosedExpr( iter ); - Integer value = removeLastCodeIfConstExpr( pos ); - if( value != null ) { - if( value.intValue() < 0 ) { - throw new PrgException( - "Wurzel aus negativer Zahl nicht m\u00F6glich" ); - } - this.asmOut.append_LD_HL_nn( - (int) Math.floor( Math.sqrt( value.doubleValue() ) ) ); + if( BasicUtil.checkKeyword( iter, "STEP" ) ) { + parseMOVER( iter ); } else { - this.asmOut.append( "\tCALL\tF_SQR\n" ); - addLibItem( BasicLibrary.LibItem.F_SQR ); + checkGraphicsSupported(); + BasicUtil.checkKeyword( iter, "TO" ); + parsePointToMem( iter, "M_XPOS", "M_YPOS" ); + addLibItem( BasicLibrary.LibItem.M_XYPO ); } } - private void parseUSR( CharacterIterator iter ) throws PrgException + private void parseMOVER( CharacterIterator iter ) throws PrgException { - int usrNum = parseUsrNum( iter ); - parseUSR( iter, usrNum ); + checkGraphicsSupported(); + parsePointTo_DE_HL( iter ); + this.asmOut.append( "\tCALL\tMOVER\n" ); + addLibItem( BasicLibrary.LibItem.MOVER ); } - private void parseUSR( - CharacterIterator iter, - int usrNum ) throws PrgException + private void parseNEXT( CharacterIterator iter ) throws PrgException { - parseEnclosedExpr( iter ); - if( !replaceLastCodeFrom_LD_HL_To_DE() ) { - this.asmOut.append( "\tEX\tDE,HL\n" ); + ForEntry forEntry = null; + if( !this.structureStack.isEmpty() ) { + BasicSourcePos entry = this.structureStack.peek(); + if( entry instanceof ForEntry ) { + forEntry = (ForEntry) entry; + this.structureStack.pop(); + } } - boolean insideCallable = (getCallableEntry() != null); - if( insideCallable ) { - this.asmOut.append( "\tPUSH\tIY\n" ); + if( forEntry == null ) { + throw new PrgException( "NEXT ohne FOR" ); } - this.asmOut.append( "\tLD\tHL,(" ); - this.asmOut.append( getUsrLabel( usrNum ) ); - this.asmOut.append( ")\n" - + "\tCALL\tJP_HL\n" ); - if( insideCallable ) { - this.asmOut.append( "\tPOP\tIY\n" ); + if( !isEndOfInstr( iter ) ) { + int len = this.asmOut.length(); + SimpleVarInfo varInfo = checkVariable( iter ); + this.asmOut.setLength( len ); + if( varInfo == null ) { + throw new PrgException( "Variable oder Ende der Anweisung erwartet" ); + } + if( !varInfo.equals( forEntry.getSimpleVarInfo() ) ) { + throw new PrgException( "Variable stimmt nicht mit der bei der" + + " FOR-Anweisung angegebenen \u00FCberein." ); + } } - addLibItem( BasicLibrary.LibItem.JP_HL ); - addLibItem( BasicLibrary.LibItem.E_USR ); - } - - - private void parseVAL( CharacterIterator iter ) throws PrgException - { - parseToken( iter, '(' ); - parseStringPrimExpr( iter ); - if( checkToken( iter, ',' ) ) { - int pos = this.asmOut.length(); - parseExpr( iter ); - Integer radix = removeLastCodeIfConstExpr( pos ); - if( radix != null ) { - switch( radix.intValue() ) { - case 2: - this.asmOut.append( "\tCALL\tF_VLB\n" ); - addLibItem( BasicLibrary.LibItem.F_VLB ); - break; - case 10: - this.asmOut.append( "\tCALL\tF_VLI\n" ); - addLibItem( BasicLibrary.LibItem.F_VLI ); - break; - case 16: - this.asmOut.append( "\tCALL\tF_VLH\n" ); - addLibItem( BasicLibrary.LibItem.F_VLH ); - break; - default: - throw new PrgException( - "Zahlenbasis in VAL-Funktion nicht unterst\u00FCtzt" ); + forEntry.getSimpleVarInfo().ensureValueInHL( this.asmOut ); + Integer stepValue = forEntry.getStepValue(); + if( stepValue != null ) { + if( stepValue.intValue() == -1 ) { + this.asmOut.append( "\tCALL\tO_DEC\n" ); + addLibItem( BasicLibrary.LibItem.O_DEC ); + } else if( stepValue.intValue() == 1 ) { + this.asmOut.append( "\tCALL\tO_INC\n" ); + addLibItem( BasicLibrary.LibItem.O_INC ); + } else { + this.asmOut.append_LD_DE_nn( stepValue ); + this.asmOut.append( "\tCALL\tO_ADD\n" ); + addLibItem( BasicLibrary.LibItem.O_ADD ); + } + } else { + this.asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tO_ADD\n" ); + addLibItem( BasicLibrary.LibItem.O_ADD ); + } + forEntry.getSimpleVarInfo().writeCode_LD_Var_HL( this.asmOut ); + Integer toValue = forEntry.getToValue(); + if( stepValue != null ) { + if( toValue != null ) { + /* + * Diese FOR-Schleife legt nichts auf den Stack. + * Es kann somit direkt zum Anfang der Schleife gesprungen werden. + */ + this.asmOut.append_LD_DE_nn( toValue ); + this.asmOut.append( "\tCALL\tCPHLDE\n" ); + addLibItem( BasicLibrary.LibItem.CPHLDE ); + if( stepValue.intValue() < 0 ) { + this.asmOut.append( "\tJP\tNC," ); + } else { + this.asmOut.append( "\tJP\tZ," ); + this.asmOut.append( forEntry.getLoopLabel() ); + this.asmOut.append( "\n" + + "\tJP\tC," ); } + this.asmOut.append( forEntry.getLoopLabel() ); + this.asmOut.newLine(); } else { - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); + this.asmOut.append( "\tPOP\tDE\n" ); + this.asmOut.append( "\tCALL\tCPHLDE\n" ); + addLibItem( BasicLibrary.LibItem.CPHLDE ); + String exitLabel = nextLabel(); + if( stepValue.intValue() < 0 ) { + this.asmOut.append( "\tJR\tC," ); + this.asmOut.append( exitLabel ); + this.asmOut.newLine(); } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tEX\tDE,HL\n" - + "\tPOP\tHL\n" ); + String tmpLabel = nextLabel(); + this.asmOut.append( "\tJR\tZ," ); + this.asmOut.append( tmpLabel ); + this.asmOut.append( "\n" + + "\tJR\tNC," ); + this.asmOut.append( exitLabel ); + this.asmOut.newLine(); + this.asmOut.append( tmpLabel ); + this.asmOut.append( (char) ':' ); } - this.asmOut.append( "\tCALL\tF_VAL\n" ); - addLibItem( BasicLibrary.LibItem.F_VAL ); + this.asmOut.append( "\tPUSH\tDE\n" + + "\tJP\t" ); + this.asmOut.append( forEntry.getLoopLabel() ); + this.asmOut.newLine(); + this.asmOut.append( exitLabel ); + this.asmOut.append( ":\n" ); } } else { - this.asmOut.append( "\tCALL\tF_VLI\n" ); - addLibItem( BasicLibrary.LibItem.F_VLI ); - } - parseToken( iter, ')' ); - } - - - private void parseStrBIN( CharacterIterator iter ) throws PrgException - { - lockTmpStrBuf(); - parseToken( iter, '(' ); - parseExpr( iter ); - if( checkToken( iter, ',' ) ) { - int pos = this.asmOut.length(); - parseExpr( iter ); - Integer value = removeLastCodeIfConstExpr( pos ); - if( value != null ) { - if( value.intValue() < 0 ) { - putWarningOutOfRange(); - } - this.asmOut.append_LD_BC_nn( value.intValue() ); + this.asmOut.append( "\tLD\tB,D\n" // Schrittweite + + "\tLD\tC,E\n" ); + if( toValue != null ) { + this.asmOut.append_LD_DE_nn( toValue.intValue() ); } else { - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInBC( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" - + "\tPOP\tHL\n" ); - } + this.asmOut.append( "\tPOP\tDE\n" ); } - this.asmOut.append( "\tCALL\tS_BINN\n" ); - } else { - this.asmOut.append( "\tCALL\tS_BIN\n" ); - } - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.S_BIN ); - } - - - private void parseStrCHR( CharacterIterator iter ) throws PrgException - { - lockTmpStrBuf(); - int pos = this.asmOut.length(); - parseEnclosedExpr( iter ); - Integer value = removeLastCodeIfConstExpr( pos ); - if( value != null ) { - this.asmOut.append_LD_A_n( value.intValue() ); - this.asmOut.append( "\tCALL\tS_CHRA\n" ); - } else { - this.asmOut.append( "\tCALL\tS_CHRL\n" ); + this.asmOut.append( "\tLD\tA,B\n" + + "\tOR\tA\n" + + "\tJP\tM," ); + String subLabel = nextLabel(); + this.asmOut.append( subLabel ); + this.asmOut.append( "\n" + + "\tCALL\tCPHLDE\n" ); + addLibItem( BasicLibrary.LibItem.CPHLDE ); + String tmpLabel = nextLabel(); + this.asmOut.append( "\tJR\tZ," ); + this.asmOut.append( tmpLabel ); + this.asmOut.append( "\n" + + "\tJR\tC," ); + this.asmOut.append( tmpLabel ); + this.asmOut.append( "\n" + + "\tJR\t" ); + this.asmOut.append( forEntry.getExitLabel() ); + this.asmOut.newLine(); + this.asmOut.append( subLabel ); + this.asmOut.append( ":\n" + + "\tCALL\tCPHLDE\n" + + "\tJR\tC," ); + this.asmOut.append( forEntry.getExitLabel() ); + this.asmOut.newLine(); + this.asmOut.append( tmpLabel ); + this.asmOut.append( ":\n" ); + if( toValue == null ) { + this.asmOut.append( "\tPUSH\tDE\n" ); + } + this.asmOut.append( "\tPUSH\tBC\n" + + "\tJP\t" ); + this.asmOut.append( forEntry.getLoopLabel() ); + this.asmOut.newLine(); } - addLibItem( BasicLibrary.LibItem.S_CHR ); - } - - - private void parseStrDNSSERVER( CharacterIterator iter ) - throws PrgException - { - checkKCNetSupported(); - lockTmpStrBuf(); - this.asmOut.append( "\tCALL\tKCNET_S_DNSSERVER\n" ); - addLibItem( BasicLibrary.LibItem.KCNET_S_DNSSERVER ); - } - - - private void parseStrERR( CharacterIterator iter ) throws PrgException - { - this.asmOut.append( "\tLD\tHL,(M_ERT)\n" ); - addLibItem( BasicLibrary.LibItem.M_ERT ); - } - - - private void parseStrGATEWAY( CharacterIterator iter ) - throws PrgException - { - checkKCNetSupported(); - lockTmpStrBuf(); - this.asmOut.append( "\tCALL\tKCNET_S_GATEWAY\n" ); - addLibItem( BasicLibrary.LibItem.KCNET_S_GATEWAY ); + this.asmOut.append( forEntry.getExitLabel() ); + this.asmOut.append( ":\n" ); } - private void parseStrHEX( CharacterIterator iter ) throws PrgException + private void parseON( CharacterIterator iter ) throws PrgException { - lockTmpStrBuf(); - parseToken( iter, '(' ); - parseExpr( iter ); - if( checkToken( iter, ',' ) ) { - int pos = this.asmOut.length(); - parseExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInBC( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" - + "\tPOP\tHL\n" ); + BasicExprParser.parseExpr( this, iter ); + if( BasicUtil.checkKeyword( iter, "GOSUB" ) ) { + this.asmOut.append( "\tCALL\tONGOAD\n" ); + parseLineExprList( iter ); + String label = nextLabel(); + this.asmOut.append( "\tJR\tZ," ); + this.asmOut.append( label ); + this.asmOut.append( "\n" + + "\tLD\tDE," ); + this.asmOut.append( label ); + this.asmOut.append( "\n" + + "\tPUSH\tDE\n" ); + if( this.options.getCheckStack() ) { + this.asmOut.append_LD_A_n( MAGIC_GOSUB ); + this.asmOut.append( "\tPUSH\tAF\n" ); } - this.asmOut.append( "\tCALL\tS_HEXN\n" ); - addLibItem( BasicLibrary.LibItem.S_HEXN ); + this.asmOut.append( "\tJP\t(HL)\n" ); + this.asmOut.append( label ); + this.asmOut.append( ":\n" ); + addLibItem( BasicLibrary.LibItem.ONGOAD ); + } else if( BasicUtil.checkKeyword( iter, "GOTO" ) ) { + this.asmOut.append( "\tCALL\tONGOAD\n" ); + parseLineExprList( iter ); + String label = nextLabel(); + this.asmOut.append( "\tJR\tZ," ); + this.asmOut.append( label ); + this.asmOut.append( "\n" + + "\tJP\t(HL)\n" ); + this.asmOut.append( label ); + this.asmOut.append( ":\n" ); + addLibItem( BasicLibrary.LibItem.ONGOAD ); } else { - this.asmOut.append( "\tCALL\tS_HEX\n" ); - addLibItem( BasicLibrary.LibItem.S_HEX ); + throw new PrgException( "GOSUB oder GOTO erwartet" ); } - parseToken( iter, ')' ); - } - - - private void parseStrHOSTBYNAME( CharacterIterator iter ) - throws PrgException - { - lockTmpStrBuf(); - parseToken( iter, '(' ); - parseStringPrimExpr( iter ); - parseToken( iter, ')' ); - this.asmOut.append( "\tCALL\tS_HOSTBYNAME\n" ); - addLibItem( BasicLibrary.LibItem.S_HOSTBYNAME ); - } - - - private void parseStrINKEY( CharacterIterator iter ) throws PrgException - { - this.asmOut.append( "\tCALL\tS_INKY\n" ); - addLibItem( BasicLibrary.LibItem.S_INKY ); } - private void parseStrINPUT( CharacterIterator iter ) throws PrgException + private void parseOPEN( CharacterIterator iter ) throws PrgException { - lockTmpStrBuf(); - parseToken( iter, '(' ); - int pos = this.asmOut.length(); - parseExpr( iter ); - if( checkToken( iter, ',' ) ) { - String oldCntCode = this.asmOut.cut( pos ); - String newCntCode = convertCodeToValueInBC( oldCntCode ); - AtomicBoolean isConstChannelNum = new AtomicBoolean(); - checkToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, false, isConstChannelNum ); - if( newCntCode != null ) { - this.asmOut.append( newCntCode ); + int ioMode = BasicLibrary.IOMODE_TXT_DEFAULT; + IODriver driver = IODriver.ALL; + boolean asParsed = false; + String text = BasicUtil.checkStringLiteral( this, iter ); + if( text != null ) { + String label = getStringLiteralLabel( text ); + asmOut.append( "\tLD\tHL," ); + asmOut.append( label ); + asmOut.newLine(); + text = text.trim().toUpperCase(); + if( text.startsWith( "CRT:" ) ) { + driver = IODriver.CRT; + if( !this.options.isOpenCrtEnabled() ) { + putWarning( "CRT-Treiber in den Compiler-Optionen deaktiviert" ); + } + } else if( text.startsWith( "LPT:" ) ) { + driver = IODriver.LPT; + if( !this.options.isOpenLptEnabled() ) { + putWarning( "LPT-Treiber in den Compiler-Optionen deaktiviert" ); + } + } else if( text.startsWith( "V:" ) ) { + driver = IODriver.VDIP; + checkPutWarningVdipDriverDisabled(); + } else if( this.target.startsWithFileDevice( text ) ) { + driver = IODriver.FILE; + checkPutWarningFileDriverDisabled(); } else { - String channelCode = this.asmOut.cut( pos ); - if( isConstChannelNum.get() ) { - this.asmOut.append( oldCntCode ); - this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" ); - this.asmOut.append( channelCode ); + int pos = text.indexOf( ':' ); + if( pos >= 0 ) { + putWarning( "Unbekanntes Ger\u00E4t" ); } else { - this.asmOut.append( oldCntCode ); - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( channelCode ); - this.asmOut.append( "\tPOP\tBC\n" ); + /* + * nur Dateiname: + * wenn vorhanden dann den Dateisystemtreiber nehmen, + * ansonsten den VDIP-Treiber + */ + if( this.target.getFileHandlerLabel() != null ) { + driver = IODriver.FILE; + checkPutWarningFileDriverDisabled(); + } else { + int[] vdipIOAddrs = this.target.getVdipBaseIOAddresses(); + if( vdipIOAddrs != null ) { + if( vdipIOAddrs.length > 0 ) { + driver = IODriver.VDIP; + checkPutWarningVdipDriverDisabled(); + } + } + } } } - this.asmOut.append( "\tCALL\tIOINX\n" ); - addLibItem( BasicLibrary.LibItem.IOINX ); } else { - Integer cnt = removeLastCodeIfConstExpr( pos ); - if( cnt != null ) { - if( cnt.intValue() == 1 ) { - this.asmOut.append( "\tCALL\tS_INCH\n" ); - addLibItem( BasicLibrary.LibItem.S_INCH ); - } else { - this.asmOut.append( "\tCALL\tS_INP\n" ); - addLibItem( BasicLibrary.LibItem.S_INP ); - } + if( !BasicExprParser.checkParseStringPrimVarExpr( this, iter ) ) { + BasicUtil.throwStringExprExpected(); + } + } + if( (driver == IODriver.ALL) + && !this.options.isOpenCrtEnabled() + && !this.options.isOpenLptEnabled() + && !this.options.isOpenFileEnabled() + && !this.options.isOpenVdipEnabled() ) + { + putWarning( "Alle betreffenden Treiber" + + " in den Compiler-Optionen deaktiviert" ); + } + this.asmOut.append( "\tLD\t(IO_M_NAME),HL\n" ); + if( BasicUtil.checkKeyword( iter, "FOR" ) ) { + boolean binMode = BasicUtil.checkKeyword( iter, "BINARY" ); + if( BasicUtil.checkKeyword( iter, "INPUT" ) ) { + ioMode = (binMode ? + BasicLibrary.IOMODE_BIN_INPUT + : BasicLibrary.IOMODE_TXT_INPUT); + } else if( BasicUtil.checkKeyword( iter, "OUTPUT" ) ) { + ioMode = (binMode ? + BasicLibrary.IOMODE_BIN_OUTPUT + : BasicLibrary.IOMODE_TXT_OUTPUT); + } else if( BasicUtil.checkKeyword( iter, "APPEND" ) ) { + ioMode = (binMode ? + BasicLibrary.IOMODE_BIN_APPEND + : BasicLibrary.IOMODE_TXT_APPEND); + } else if( BasicUtil.checkKeyword( iter, "AS" ) ) { + asParsed = true; + ioMode = (binMode ? + BasicLibrary.IOMODE_BIN_DEFAULT + : BasicLibrary.IOMODE_TXT_DEFAULT); } else { - this.asmOut.append( "\tCALL\tS_INP\n" ); - addLibItem( BasicLibrary.LibItem.S_INP ); + throw new PrgException( "INPUT, OUTPUT oder APPEND erwartet" ); } } - parseToken( iter, ')' ); + if( !asParsed ) { + parseKeywordAS( iter ); + } + this.asmOut.append_LD_A_n( ioMode ); + this.asmOut.append( "\tLD\t(IO_M_ACCESS),A\n" ); + BasicUtil.checkToken( iter, '#' ); + parseIOChannelNumToPtrFldAddrInHL( iter, null ); + this.asmOut.append( "\tCALL\tIOOPEN\n" ); + if( ((driver == IODriver.CRT) || (driver == IODriver.LPT)) + && ((ioMode == BasicLibrary.IOMODE_TXT_OUTPUT) + || (ioMode == BasicLibrary.IOMODE_TXT_APPEND) + || (ioMode == BasicLibrary.IOMODE_BIN_OUTPUT) + || (ioMode == BasicLibrary.IOMODE_BIN_APPEND)) ) + { + putWarning( "Ger\u00E4t kann nicht mit der angegebenen Betriebsart" + + " ge\u00F6ffnet werden" ); + } + Set driverModes = this.ioDriverModes.get( driver ); + if( driverModes == null ) { + driverModes = new TreeSet<>(); + } + driverModes.add( ioMode ); + this.ioDriverModes.put( driver, driverModes ); + addLibItem( BasicLibrary.LibItem.IOOPEN ); } - private void parseStrLEFT( CharacterIterator iter ) throws PrgException + private void parseOUT( CharacterIterator iter ) throws PrgException { - lockTmpStrBuf(); - parseToken( iter, '(' ); - parseStringPrimExpr( iter ); - parseToken( iter, ',' ); - int pos = this.asmOut.length(); - parseExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInBC( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); + boolean enclosed = BasicUtil.checkToken( iter, '(' ); + int pos = this.asmOut.length(); + BasicExprParser.parseExpr( this, iter ); + String tmpPortCode = this.asmOut.cut( pos ); + String newPortCode = BasicUtil.convertCodeToValueInBC( tmpPortCode ); + if( enclosed ) { + BasicUtil.parseToken( iter, ')' ); + } + if( enclosed ) { + if( !BasicUtil.checkToken( iter, '=' ) ) { + BasicUtil.parseToken( iter, ',' ); + } } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" - + "\tPOP\tHL\n" ); + BasicUtil.parseToken( iter, ',' ); } - this.asmOut.append( "\tCALL\tS_LEFT\n" ); - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.S_LEFT ); - } - - - private void parseStrLOCALADDR( CharacterIterator iter ) - throws PrgException - { - checkKCNetSupported(); - lockTmpStrBuf(); - this.asmOut.append( "\tCALL\tKCNET_S_LOCALADDR\n" ); - addLibItem( BasicLibrary.LibItem.KCNET_S_LOCALADDR ); - } - - - private void parseStrLOWER( CharacterIterator iter ) throws PrgException - { - lockTmpStrBuf(); - parseToken( iter, '(' ); - parseStringPrimExpr( iter ); - parseToken( iter, ')' ); - this.asmOut.append( "\tCALL\tS_LWR\n" ); - addLibItem( BasicLibrary.LibItem.S_LWR ); - } - - - private void parseStrLTRIM( CharacterIterator iter ) throws PrgException - { - parseToken( iter, '(' ); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tCALL\tS_LTRIM\n" ); - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.S_LTRIM ); - } - - - private void parseStrMACADDR( CharacterIterator iter ) throws PrgException - { - checkKCNetSupported(); - lockTmpStrBuf(); - this.asmOut.append( "\tCALL\tKCNET_S_MACADDR\n" ); - addLibItem( BasicLibrary.LibItem.KCNET_S_MACADDR ); - } - - - private void parseStrMEMSTR( CharacterIterator iter ) throws PrgException - { - parseEnclosedExpr( iter ); - } - - - private void parseStrMIRROR( CharacterIterator iter ) throws PrgException - { - parseToken( iter, '(' ); - String text = checkStringLiteral( iter ); - if( text != null ) { - if( text.isEmpty() ) { - this.asmOut.append_LD_HL_xx( "D_EMPT" ); - addLibItem( BasicLibrary.LibItem.D_EMPT ); + BasicExprParser.parseExpr( this, iter ); + Integer value = BasicUtil.removeLastCodeIfConstExpr( this, pos ); + if( value != null ) { + if( newPortCode != null ) { + this.asmOut.append( newPortCode ); } else { - this.asmOut.appendStringLiteral( - (new StringBuilder( text ).reverse()).toString(), - "00H" ); + this.asmOut.append( tmpPortCode ); + this.asmOut.append( "\tLD\tB,H\n" + + "\tLD\tC,L\n" ); } + this.asmOut.append_LD_A_n( value.intValue() ); + this.asmOut.append( "\tOUT\t(C),A\n" ); } else { - lockTmpStrBuf(); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tCALL\tS_MIRR\n" ); - addLibItem( BasicLibrary.LibItem.S_MIRR ); - } - parseToken( iter, ')' ); - } - - - private void parseStrMID( CharacterIterator iter ) throws PrgException - { - lockTmpStrBuf(); - parseToken( iter, '(' ); - parseStringPrimExpr( iter ); - parseToken( iter, ',' ); - int pos = this.asmOut.length(); - parseExpr( iter ); - String oldCode2 = this.asmOut.cut( pos ); - String newCode2 = convertCodeToValueInDE( oldCode2 ); - if( checkToken( iter, ',' ) ) { - parseExpr( iter ); - String oldCode3 = this.asmOut.cut( pos ); - String newCode3 = convertCodeToValueInBC( oldCode3 ); - if( (newCode2 != null) && (newCode3 != null) ) { - this.asmOut.append( newCode2 ); - this.asmOut.append( newCode3 ); + if( newPortCode != null ) { + this.asmOut.append( newPortCode ); } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - if( newCode2 != null ) { - this.asmOut.append( oldCode3 ); - this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" ); - this.asmOut.append( newCode2 ); - } else { - if( newCode3 != null ) { - this.asmOut.append( oldCode2 ); - this.asmOut.append( "\tEX\tDE,HL\n" ); - this.asmOut.append( newCode3 ); + String valueCode = this.asmOut.cut( pos ); + if( BasicUtil.isOnly_LD_HL_xx( valueCode ) ) { + if( newPortCode != null ) { + this.asmOut.append( newPortCode ); } else { - this.asmOut.append( oldCode2 ); - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode3 ); + this.asmOut.append( tmpPortCode ); this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" - + "\tPOP\tDE\n" ); + + "\tLD\tC,L\n" ); } + this.asmOut.append( valueCode ); + } else { + this.asmOut.append( tmpPortCode ); + this.asmOut.append( "\tPUSH\tHL\n" ); + this.asmOut.append( valueCode ); + this.asmOut.append( "\tPOP\tBC\n" ); } - this.asmOut.append( "\tPOP\tHL\n" ); - } - this.asmOut.append( "\tCALL\tS_MIDN\n" ); - addLibItem( BasicLibrary.LibItem.S_MIDN ); - } else { - if( newCode2 != null ) { - this.asmOut.append( newCode2 ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode2 ); - this.asmOut.append( "\tEX\tDE,HL\n" - + "\tPOP\tHL\n" ); } - this.asmOut.append( "\tCALL\tS_MID\n" ); - addLibItem( BasicLibrary.LibItem.S_MID ); + this.asmOut.append( "\tOUT\t(C),L\n" ); } - parseToken( iter, ')' ); } - private void parseStrNETMASK( CharacterIterator iter ) - throws PrgException - { - checkKCNetSupported(); - lockTmpStrBuf(); - this.asmOut.append( "\tCALL\tKCNET_S_NETMASK\n" ); - addLibItem( BasicLibrary.LibItem.KCNET_S_NETMASK ); - } - - - private void parseStrREMOTEADDR( CharacterIterator iter ) - throws PrgException - { - checkKCNetSupported(); - lockTmpStrBuf(); - parseToken( iter, '(' ); - checkToken( iter, '#' ); - parseIOChannelNumToPtrFldAddrInHL( iter, false, null ); - this.asmOut.append( "\tCALL\tS_REMOTEADDR\n" ); - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.S_REMOTEADDR ); - } - - - private void parseStrRIGHT( CharacterIterator iter ) throws PrgException - { - parseToken( iter, '(' ); - parseStringPrimExpr( iter ); - parseToken( iter, ',' ); - int pos = this.asmOut.length(); - parseExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInBC( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" - + "\tPOP\tHL\n" ); - } - this.asmOut.append( "\tCALL\tS_RIGHT\n" ); - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.S_RIGHT ); + private void parsePAINT( CharacterIterator iter ) throws PrgException + { + checkGraphicsSupported(); + parsePointToMem( iter, "PAINT_M_X", "PAINT_M_Y" ); + this.asmOut.append( "\tCALL\tPAINT\n" ); + addLibItem( BasicLibrary.LibItem.PAINT ); } - private void parseStrRTRIM( CharacterIterator iter ) throws PrgException + private void parsePAPER( CharacterIterator iter ) throws PrgException { - lockTmpStrBuf(); - parseToken( iter, '(' ); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tCALL\tS_RTRIM\n" ); - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.S_RTRIM ); + if( this.target.supportsColors() ) { + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tCALL\tXPAPER\n" ); + addLibItem( BasicLibrary.LibItem.XPAPER ); + } else { + try { + pushCodeCreationDisabled(); + BasicExprParser.parseExpr( this, iter ); + } + finally { + popCodeCreationDisabled(); + } + } } - private void parseStrSPACE( CharacterIterator iter ) throws PrgException + private void parsePASSWORD( CharacterIterator iter ) throws PrgException { - lockTmpStrBuf(); - int pos = this.asmOut.length(); - parseEnclosedExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInBC( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( oldCode ); - this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" ); + if( !BasicUtil.checkKeyword( iter, "INPUT" ) ) { + throw new PrgException( "INPUT erwartet" ); } - this.asmOut.append( "\tLD\tL,20H\n" - + "\tCALL\tS_STC\n" ); - addLibItem( BasicLibrary.LibItem.S_STC ); + parseInputLine( iter, true ); } - private void parseStrSTR( CharacterIterator iter ) throws PrgException + private void parsePAUSE( CharacterIterator iter ) throws PrgException { - parseEnclosedExpr( iter ); - this.asmOut.append( "\tCALL\tS_STR\n" ); - addLibItem( BasicLibrary.LibItem.S_STR ); + if( isEndOfInstr( iter ) ) { + this.asmOut.append( "\tCALL\tPAUSE\n" ); + addLibItem( BasicLibrary.LibItem.PAUSE ); + } else { + int pos = this.asmOut.length(); + BasicExprParser.parseExpr( this, iter ); + Integer value = BasicUtil.removeLastCodeIfConstExpr( this, pos ); + if( value != null ) { + if( value.intValue() < 0 ) { + putWarningOutOfRange(); + } + this.asmOut.append_LD_HL_nn( value.intValue() ); + } + this.asmOut.append( "\tCALL\tPAUSE_N\n" ); + addLibItem( BasicLibrary.LibItem.PAUSE_N ); + } } - private void parseStrSTRING( CharacterIterator iter ) throws PrgException + private void parsePOKE( CharacterIterator iter ) throws PrgException { - lockTmpStrBuf(); - parseToken( iter, '(' ); int pos = this.asmOut.length(); - parseExpr( iter ); - parseToken( iter, ',' ); - Integer cnt = removeLastCodeIfConstExpr( pos ); - if( cnt != null ) { - if( checkParseStringPrimExpr( iter ) ) { - this.asmOut.append_LD_BC_nn( cnt.intValue() ); - this.asmOut.append( "\tCALL\tS_STS\n" ); - addLibItem( BasicLibrary.LibItem.S_STS ); + BasicExprParser.parseExpr( this, iter ); + Integer addr = BasicUtil.removeLastCodeIfConstExpr( this, pos ); + BasicUtil.parseToken( iter, ',' ); + pos = this.asmOut.length(); + BasicExprParser.parseExpr( this, iter ); + if( addr != null ) { + Integer value = BasicUtil.removeLastCodeIfConstExpr( this, pos ); + if( value != null ) { + if( (value & 0xFF) == 0 ) { + this.asmOut.append( "\tXOR\tA\n" ); + } else { + this.asmOut.append( "\tLD\tA," ); + this.asmOut.appendHex2( value.intValue() ); + this.asmOut.newLine(); + } } else { - parseExpr( iter ); - this.asmOut.append_LD_BC_nn( cnt.intValue() ); - this.asmOut.append( "\tCALL\tS_STC\n" ); - addLibItem( BasicLibrary.LibItem.S_STC ); + this.asmOut.append( "\tLD\tA,L\n" ); } + this.asmOut.append( "\tLD\t(" ); + this.asmOut.appendHex4( addr.intValue() ); + this.asmOut.append( "),A\n" ); } else { - String oldCode1 = this.asmOut.cut( pos ); - String newCode1 = convertCodeToValueInBC( oldCode1 ); - boolean isStr = false; - if( checkParseStringPrimExpr( iter ) ) { - isStr = true; - } else { - parseExpr( iter ); - } - String code2 = this.asmOut.cut( pos ); - if( isOnly_LD_HL_xx( code2 ) ) { - if( newCode1 != null ) { - this.asmOut.append( newCode1 ); - } else { - this.asmOut.append( oldCode1 ); - this.asmOut.append( "\tLD\tB,H\n" - + "\tLD\tC,L\n" ); - } - this.asmOut.append( code2 ); + Integer value = BasicUtil.removeLastCodeIfConstExpr( this, pos ); + if( value != null ) { + this.asmOut.append( "\tLD\t(HL)," ); + this.asmOut.appendHex2( value.intValue() ); + this.asmOut.newLine(); } else { - if( newCode1 != null ) { - this.asmOut.append( code2 ); - this.asmOut.append( newCode1 ); + String oldCode = this.asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + this.asmOut.append( newCode ); + this.asmOut.append( "\tLD\t(HL),E\n" ); } else { - this.asmOut.append( oldCode1 ); this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( code2 ); - this.asmOut.append( "\tPOP\tBC\n" ); + this.asmOut.append( oldCode ); + this.asmOut.append( "\tLD\tA,L\n" + + "\tPOP\tHL\n" + + "\tLD\t(HL),A\n" ); } } - if( isStr ) { - this.asmOut.append( "\tCALL\tS_STS\n" ); - addLibItem( BasicLibrary.LibItem.S_STS ); - } else { - this.asmOut.append( "\tCALL\tS_STC\n" ); - addLibItem( BasicLibrary.LibItem.S_STC ); - } } - parseToken( iter, ')' ); - } - - - private void parseStrTARGETID( CharacterIterator iter ) throws PrgException - { - this.asmOut.append( "\tLD\tHL,XTARID\n" ); - addLibItem( BasicLibrary.LibItem.XTARID ); - } - - - private void parseStrTRIM( CharacterIterator iter ) throws PrgException - { - lockTmpStrBuf(); - parseToken( iter, '(' ); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tCALL\tS_TRIM\n" ); - parseToken( iter, ')' ); - addLibItem( BasicLibrary.LibItem.S_TRIM ); - } - - - private void parseStrUPPER( CharacterIterator iter ) throws PrgException - { - lockTmpStrBuf(); - parseToken( iter, '(' ); - parseStringPrimExpr( iter ); - parseToken( iter, ')' ); - this.asmOut.append( "\tCALL\tS_UPR\n" ); - addLibItem( BasicLibrary.LibItem.S_UPR ); } - /* --- Parsen von Ausdruecke --- */ - - private boolean checkParseStringPrimExpr( CharacterIterator iter ) - throws PrgException + private void parsePEN( CharacterIterator iter ) throws PrgException { - boolean rv = false; - String text = checkStringLiteral( iter ); - if( text != null ) { - this.asmOut.append( "\tLD\tHL," ); - this.asmOut.append( getStringLiteralLabel( text ) ); - this.asmOut.newLine(); - rv = true; + int pos = this.asmOut.length(); + BasicExprParser.parseExpr( this, iter ); + Integer value = BasicUtil.removeLastCodeIfConstExpr( this, pos ); + if( value != null ) { + if( (value.intValue() < 0) || (value.intValue() > 3) ) { + throw new PrgException( "Ung\u00FCltiger Wert bei PEN" ); + } + this.asmOut.append_LD_A_n( value.intValue() ); + this.asmOut.append( "\tCALL\tXPEN\n" ); + addLibItem( BasicLibrary.LibItem.XPEN ); } else { - rv = checkParseStringPrimVarExpr( iter ); + this.asmOut.append( "\tCALL\tPEN\n" ); + addLibItem( BasicLibrary.LibItem.PEN ); } - return rv; } - private boolean checkParseStringPrimVarExpr( CharacterIterator iter ) - throws PrgException + private void parsePLOT( CharacterIterator iter ) throws PrgException { - boolean rv = false; - int begPos = iter.getIndex(); - String name = checkIdentifier( iter ); - if( name != null ) { - if( name.endsWith( "$" ) ) { - if( name.equals( "BIN$" ) ) { - parseStrBIN( iter ); - } else if( name.equals( "CHR$" ) ) { - parseStrCHR( iter ); - } else if( name.equals( "DNSSERVER$" ) ) { - parseStrDNSSERVER( iter ); - } else if( name.equals( "ERR$" ) ) { - parseStrERR( iter ); - } else if( name.equals( "GATEWAY$" ) ) { - parseStrGATEWAY( iter ); - } else if( name.equals( "HEX$" ) ) { - parseStrHEX( iter ); - } else if( name.equals( "HOSTBYNAME$" ) ) { - parseStrHOSTBYNAME( iter ); - } else if( name.equals( "INKEY$" ) ) { - parseStrINKEY( iter ); - } else if( name.equals( "INPUT$" ) ) { - parseStrINPUT( iter ); - } else if( name.equals( "LEFT$" ) ) { - parseStrLEFT( iter ); - } else if( name.equals( "LOCALADDR$" ) ) { - parseStrLOCALADDR( iter ); - } else if( name.equals( "LOWER$" ) || name.equals( "LCASE$" ) ) { - parseStrLOWER( iter ); - } else if( name.equals( "LTRIM$" ) ) { - parseStrLTRIM( iter ); - } else if( name.equals( "MACADDR$" ) ) { - parseStrMACADDR( iter ); - } else if( name.equals( "MEMSTR$" ) ) { - parseStrMEMSTR( iter ); - } else if( name.equals( "MID$" ) ) { - parseStrMID( iter ); - } else if( name.equals( "MIRROR$" ) ) { - parseStrMIRROR( iter ); - } else if( name.equals( "NETMASK$" ) ) { - parseStrNETMASK( iter ); - } else if( name.equals( "REMOTEADDR$" ) ) { - parseStrREMOTEADDR( iter ); - } else if( name.equals( "RIGHT$" ) ) { - parseStrRIGHT( iter ); - } else if( name.equals( "RTRIM$" ) ) { - parseStrRTRIM( iter ); - } else if( name.equals( "SPACE$" ) ) { - parseStrSPACE( iter ); - } else if( name.equals( "STR$" ) ) { - parseStrSTR( iter ); - } else if( name.equals( "STRING$" ) ) { - parseStrSTRING( iter ); - } else if( name.equals( "TARGETID$" ) ) { - parseStrTARGETID( iter ); - } else if( name.equals( "TRIM$" ) ) { - parseStrTRIM( iter ); - } else if( name.equals( "UPPER$" ) || name.equals( "UCASE$" ) ) { - parseStrUPPER( iter ); - } else { - CallableEntry entry = this.name2Callable.get( name ); - if( entry != null ) { - if( entry instanceof FunctionEntry ) { - if( ((FunctionEntry) entry).getReturnType() - != DataType.STRING ) - { - throw new PrgException( "String-Ausdruck erwartet" ); - } - parseCallableCall( iter, entry ); - } else { - throw new PrgException( - "Aufruf einer Prozedur an der Stelle nicht erlaubt" ); - } - } else { - parseVariableExpr( iter, name, DataType.STRING ); - } - } - rv = true; - } else { - iter.setIndex( begPos ); - } + if( BasicUtil.checkKeyword( iter, "STEP" ) ) { + parsePLOTR( iter ); + } else { + checkGraphicsSupported(); + BasicUtil.checkKeyword( iter, "TO" ); + parsePointTo_DE_HL( iter ); + this.asmOut.append( "\tLD\t(M_XPOS),DE\n" + + "\tLD\t(M_YPOS),HL\n" + + "\tCALL\tXPSET\n" ); + addLibItem( BasicLibrary.LibItem.XPSET ); } - return rv; } - private String checkStringLiteral( CharacterIterator iter ) - throws PrgException + private void parsePLOTR( CharacterIterator iter ) throws PrgException { - StringBuilder buf = null; - char delimiter = skipSpaces( iter ); - if( delimiter == '\"' ) { - buf = new StringBuilder( 64 ); - char ch = iter.next(); - while( (ch != CharacterIterator.DONE) && (ch != delimiter) ) { - if( ch > 0xFF ) { - throw16BitChar( ch ); - } - if( this.options.getWarnNonAsciiChars() - && ((ch < '\u0020') || (ch > '\u007F')) ) - { - putWarningNonAsciiChar( ch ); - } - buf.append( ch ); - ch = iter.next(); - } - if( ch != delimiter ) { - putWarning( "String-Literal nicht geschlossen" ); - } - iter.next(); - } - return buf != null ? buf.toString() : null; + checkGraphicsSupported(); + parsePointTo_DE_HL( iter ); + this.asmOut.append( "\tCALL\tPLOTR\n" ); + addLibItem( BasicLibrary.LibItem.PLOTR ); } - private void parseEnclosedExpr( CharacterIterator iter ) throws PrgException + private void parsePRESET( CharacterIterator iter ) throws PrgException { - parseToken( iter, '(' ); - parseExpr( iter ); - parseToken( iter, ')' ); + checkGraphicsSupported(); + parsePointTo_DE_HL( iter ); + this.asmOut.append( "\tCALL\tXPRES\n" ); + addLibItem( BasicLibrary.LibItem.XPRES ); } - private void parseExpr( CharacterIterator iter ) throws PrgException + private void parsePRINT( CharacterIterator iter ) throws PrgException { - parseNotExpr( iter ); - for(;;) { - if( checkKeyword( iter, "AND" ) ) { - int pos = this.asmOut.length(); - parseNotExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" ); - } - this.asmOut.append( "\tCALL\tO_AND\n" ); - addLibItem( BasicLibrary.LibItem.O_AND ); - } else if( checkKeyword( iter, "OR" ) ) { - int pos = this.asmOut.length(); - parseNotExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - if( !oldCode.equals( "\tLD\tHL,0000H\n" ) ) { - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" ); - } - this.asmOut.append( "\tCALL\tO_OR\n" ); - addLibItem( BasicLibrary.LibItem.O_OR ); - } - } else if( checkKeyword( iter, "XOR" ) ) { - int pos = this.asmOut.length(); - parseNotExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" ); - } - this.asmOut.append( "\tCALL\tO_XOR\n" ); - addLibItem( BasicLibrary.LibItem.O_XOR ); + if( BasicUtil.checkToken( iter, '#' ) ) { + parseIOChannelNumToWriteRoutineInHL( iter ); + this.asmOut.append( "\tCALL\tIO_SET_COUT\n" ); + addLibItem( BasicLibrary.LibItem.IO_SET_COUT ); + if( this.options.getPreferRelativeJumps() ) { + this.asmOut.append( "\tJR\tC," ); } else { - break; + this.asmOut.append( "\tJP\tC," ); + } + String endOfInstLabel = nextLabel(); + this.asmOut.append( endOfInstLabel ); + this.asmOut.newLine(); + if( isEndOfInstr( iter ) ) { + this.asmOut.append( "\tCALL\tPS_NL\n" ); + addLibItem( BasicLibrary.LibItem.PS_NL ); + } else { + BasicUtil.parseToken( iter, ',' ); + parsePrint( iter, false ); } + this.asmOut.append( endOfInstLabel ); + this.asmOut.append( ":\n" ); + } else { + parsePrint( iter, true ); } } - private void parseNotExpr( CharacterIterator iter ) throws PrgException + private void parsePSET( CharacterIterator iter ) throws PrgException { - if( checkKeyword( iter, "NOT" ) ) { - parseCondExpr( iter ); - this.asmOut.append( "\tCALL\tO_NOT\n" ); - addLibItem( BasicLibrary.LibItem.O_NOT ); - } else { - parseCondExpr( iter ); - } + checkGraphicsSupported(); + parsePointTo_DE_HL( iter ); + this.asmOut.append( "\tCALL\tXPSET\n" ); + addLibItem( BasicLibrary.LibItem.XPSET ); } - private void parseCondExpr( CharacterIterator iter ) throws PrgException + private void parseREM( CharacterIterator iter ) { - if( checkParseStringPrimExpr( iter ) ) { - - // String-Vergleich - char ch = skipSpaces( iter ); - if( ch == '<' ) { - ch = iter.next(); - if( ch == '=' ) { - iter.next(); - this.asmOut.append( "\tPUSH\tHL\n" ); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tO_STLE\n" ); - addLibItem( BasicLibrary.LibItem.O_STLE ); - } else if( ch == '>' ) { - iter.next(); - this.asmOut.append( "\tPUSH\tHL\n" ); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tO_STNE\n" ); - addLibItem( BasicLibrary.LibItem.O_STNE ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tO_STLT\n" ); - addLibItem( BasicLibrary.LibItem.O_STLT ); - } - } else if( ch == '=' ) { - iter.next(); - this.asmOut.append( "\tPUSH\tHL\n" ); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tO_STEQ\n" ); - addLibItem( BasicLibrary.LibItem.O_STEQ ); - } else if( ch == '>' ) { - ch = iter.next(); - if( ch == '=' ) { - iter.next(); - this.asmOut.append( "\tPUSH\tHL\n" ); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tO_STGE\n" ); - addLibItem( BasicLibrary.LibItem.O_STGE ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - parseStringPrimExpr( iter ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tO_STGT\n" ); - addLibItem( BasicLibrary.LibItem.O_STGT ); - } - } else { - throw new PrgException( "Vergleichsoperator erwartet" ); - } + iter.setIndex( iter.getEndIndex() ); + } - } else { - // prufen auf numerischen Vergleich - parseShiftExpr( iter ); - char ch = skipSpaces( iter ); - if( ch == '<' ) { - ch = iter.next(); - if( ch == '=' ) { - iter.next(); - int pos = this.asmOut.length(); - parseShiftExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - this.asmOut.append( "\tCALL\tO_LE\n" ); - addLibItem( BasicLibrary.LibItem.O_LE ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tO_GE\n" ); - addLibItem( BasicLibrary.LibItem.O_GE ); - } - } else if( ch == '>' ) { - iter.next(); - int pos = this.asmOut.length(); - parseShiftExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" ); - } - this.asmOut.append( "\tCALL\tO_NE\n" ); - addLibItem( BasicLibrary.LibItem.O_NE ); - } else { - int pos = this.asmOut.length(); - parseShiftExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - this.asmOut.append( "\tCALL\tO_LT\n" ); - addLibItem( BasicLibrary.LibItem.O_LT ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tO_GT\n" ); - addLibItem( BasicLibrary.LibItem.O_GT ); - } + private void parseREAD( CharacterIterator iter ) throws PrgException + { + iter.previous(); + do { + iter.next(); + SimpleVarInfo varInfo = checkVariable( iter ); + if( varInfo != null ) { + switch( varInfo.getDataType() ) { + case INTEGER: + varInfo.ensureAddrInHL( this.asmOut ); + this.asmOut.append( "\tCALL\tDREADI\n" ); + addLibItem( BasicLibrary.LibItem.DREADI ); + break; + case STRING: + varInfo.ensureAddrInHL( this.asmOut ); + this.asmOut.append( "\tCALL\tDREADS\n" ); + addLibItem( BasicLibrary.LibItem.DREADS ); + break; } + } else { + throwVarExpected(); } - else if( ch == '=' ) { - iter.next(); - int pos = this.asmOut.length(); - parseShiftExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" ); - } - this.asmOut.append( "\tCALL\tO_EQ\n" ); - addLibItem( BasicLibrary.LibItem.O_EQ ); + } while( BasicUtil.skipSpaces( iter ) == ',' ); + } + + + private void parseRESTORE( CharacterIterator iter ) throws PrgException + { + BasicLineExpr lineExpr = BasicLineExpr.checkBasicLineExpr( + iter, + this.curSource, + this.curBasicLineNum ); + if( lineExpr != null ) { + if( this.restoreBasicLines == null ) { + this.restoreBasicLines = new ArrayList<>( 128 ); } - else if( ch == '>' ) { - ch = iter.next(); - if( ch == '=' ) { - iter.next(); - int pos = this.asmOut.length(); - parseShiftExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - this.asmOut.append( "\tCALL\tO_GE\n" ); - addLibItem( BasicLibrary.LibItem.O_GE ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tO_LE\n" ); - addLibItem( BasicLibrary.LibItem.O_LE ); - } - } else { - int pos = this.asmOut.length(); - parseShiftExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - this.asmOut.append( "\tCALL\tO_GT\n" ); - addLibItem( BasicLibrary.LibItem.O_GT ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tCALL\tO_LT\n" ); - addLibItem( BasicLibrary.LibItem.O_LT ); - } - } + this.restoreBasicLines.add( lineExpr ); + this.asmOut.append_LD_HL_xx( + createDataLineLabel( lineExpr.getExprText() ) ); + this.asmOut.append( "\tLD\t(M_READ),HL\n" ); + } else { + if( !isEndOfInstr( iter ) ) { + throwBasicLineExprExpected(); } + this.asmOut.append( "\tCALL\tDINIT\n" ); } + addLibItem( BasicLibrary.LibItem.DATA ); } - private void parseShiftExpr( CharacterIterator iter ) throws PrgException + private void parseRETURN() { - parseAddExpr( iter ); - for(;;) { - if( checkKeyword( iter, "SHL" ) ) { - int pos = this.asmOut.length(); - parseAddExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { - this.asmOut.append( "\tSLA\tL\n" - + "\tRL\tH\n" ); - } else { - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tEX\tDE,HL\n" - + "\tPOP\tHL\n" ); - } - this.asmOut.append( "\tCALL\tO_SHL\n" ); - addLibItem( BasicLibrary.LibItem.O_SHL ); - } - } else if( checkKeyword( iter, "SHR" ) ) { - int pos = this.asmOut.length(); - parseAddExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { - this.asmOut.append( "\tSRL\tH\n" - + "\tRR\tL\n" ); - } else { - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tEX\tDE,HL\n" - + "\tPOP\tHL\n" ); - } - this.asmOut.append( "\tCALL\tO_SHR\n" ); - addLibItem( BasicLibrary.LibItem.O_SHR ); - } - } else { - break; - } + if( this.options.getCheckStack() ) { + this.asmOut.append( "\tPOP\tAF\n" + + "\tCP\t" ); + this.asmOut.appendHex2( MAGIC_GOSUB ); + this.asmOut.append( "\n" + + "\tJP\tNZ,E_REWG\n" ); + addLibItem( BasicLibrary.LibItem.E_REWG ); } + this.asmOut.append( "\tRET\n" ); } - private void parseAddExpr( CharacterIterator iter ) throws PrgException + private void parseSCREEN( CharacterIterator iter ) throws PrgException { - parseMulExpr( iter ); - for(;;) { - char ch = skipSpaces( iter ); - if( ch == '+' ) { - iter.next(); - int pos = this.asmOut.length(); - parseMulExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - if( !oldCode.equals( "\tLD\tHL,0000H\n" ) ) { - if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { - this.asmOut.append( "\tCALL\tO_INC\n" ); - addLibItem( BasicLibrary.LibItem.O_INC ); - } else if( oldCode.equals( "\tLD\tHL,0FFFFH\n" ) ) { - this.asmOut.append( "\tCALL\tO_DEC\n" ); - addLibItem( BasicLibrary.LibItem.O_DEC ); - } else { - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" ); - } - this.asmOut.append( "\tCALL\tO_ADD\n" ); - addLibItem( BasicLibrary.LibItem.O_ADD ); - } - } - } else if( ch == '-' ) { - iter.next(); - int pos = this.asmOut.length(); - parseMulExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - if( !oldCode.equals( "\tLD\tHL,0000H\n" ) ) { - if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { - this.asmOut.append( "\tCALL\tO_DEC\n" ); - addLibItem( BasicLibrary.LibItem.O_DEC ); - } else if( oldCode.equals( "\tLD\tHL,0FFFFH\n" ) ) { - this.asmOut.append( "\tCALL\tO_INC\n" ); - addLibItem( BasicLibrary.LibItem.O_INC ); - } else { - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tEX\tDE,HL\n" ); - } - this.asmOut.append( "\tCALL\tO_SUB\n" ); - addLibItem( BasicLibrary.LibItem.O_SUB ); - } - } - } else if( checkKeyword( iter, "ADD" ) ) { - int pos = this.asmOut.length(); - parseMulExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - if( !oldCode.equals( "\tLD\tHL,0000H\n" ) ) { - if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { - this.asmOut.append( "\tINC\tHL\n" ); - } else if( oldCode.equals( "\tLD\tHL,0FFFFH\n" ) ) { - this.asmOut.append( "\tDEC\tHL\n" ); - } else { - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" ); - } - this.asmOut.append( "\tADD\tHL,DE\n" ); - } + BasicExprParser.parseExpr( this, iter ); + this.asmOut.append( "\tCALL\tSCREEN\n" ); + addLibItem( BasicLibrary.LibItem.SCREEN ); + } + + + private void parseWAIT( CharacterIterator iter ) throws PrgException + { + // Code fuer erstes Argument erzeugen (in BC) + int pos = this.asmOut.length(); + BasicExprParser.parseExpr( this, iter ); + String oldCode1 = this.asmOut.cut( pos ); + String newCode1 = BasicUtil.convertCodeToValueInBC( oldCode1 ); + + // Code fuer zweites Argument erzeugen (in E) + BasicUtil.parseToken( iter, ',' ); + BasicExprParser.parseExpr( this, iter ); + String oldCode2 = this.asmOut.substring( pos ); + String newCode2 = null; + Integer mask = BasicUtil.removeLastCodeIfConstExpr( this, pos ); + this.asmOut.setLength( pos ); + if( mask != null ) { + int v = mask.intValue() & 0xFF; + newCode2 = String.format( + "\tLD\tE,%s%02XH\n", + v >= 0xA0 ? "0" : "", + v ); + } else { + if( BasicUtil.isOnly_LD_HL_xx( oldCode2 ) ) { + newCode2 = oldCode2 + "\tLD\tE,L\n"; + } + } + + // Code fuer optionales drittes Argument erzeugen (in L) + String oldCode3 = null; + String newCode3 = null; + if( BasicUtil.checkToken( iter, ',' ) ) { + BasicExprParser.parseExpr( this, iter ); + oldCode3 = this.asmOut.substring( pos ); + Integer inv = BasicUtil.removeLastCodeIfConstExpr( this, pos ); + this.asmOut.setLength( pos ); + if( inv != null ) { + int v = inv.intValue() & 0xFF; + newCode3 = String.format( + "\tLD\tL,%s%02XH\n", + v >= 0xA0 ? "0" : "", + v ); + } else { + if( BasicUtil.isOnly_LD_HL_xx( oldCode3 ) ) { + newCode3 = oldCode3; } - } else if( checkKeyword( iter, "SUB" ) ) { - int pos = this.asmOut.length(); - parseMulExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - if( !oldCode.equals( "\tLD\tHL,0000H\n" ) ) { - if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { - this.asmOut.append( "\tDEC\tHL\n" ); - } else if( oldCode.equals( "\tLD\tHL,0FFFFH\n" ) ) { - this.asmOut.append( "\tINC\tHL\n" ); + } + } + + /* + * Gesamtcode erzeugen + * BC: Port + * E: Maske + * L: Inversion + */ + if( (newCode1 != null) + && (newCode2 != null) + && ((oldCode3 == null) || (newCode3 != null)) ) + { + this.asmOut.append( newCode1 ); + this.asmOut.append( newCode2 ); + if( newCode3 != null ) { + this.asmOut.append( newCode3 ); + } + } else { + this.asmOut.append( oldCode1 ); + this.asmOut.append( "\tPUSH\tHL\n" ); + if( oldCode3 != null ) { + if( newCode3 != null ) { + if( newCode2 != null ) { + this.asmOut.append( newCode2 ); } else { - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tEX\tDE,HL\n" ); - } - this.asmOut.append( "\tOR\tA\n" - + "\tSBC\tHL,DE\n" ); + this.asmOut.append( oldCode2 ); + this.asmOut.append( "\tLD\tE,L\n" ); } + this.asmOut.append( newCode3 ); + } else { + this.asmOut.append( oldCode2 ); + this.asmOut.append( "\tPUSH\tHL\n" ); + this.asmOut.append( oldCode3 ); + this.asmOut.append( "\tPOP\tDE\n" ); } } else { - break; + if( newCode2 != null ) { + this.asmOut.append( newCode2 ); + } else { + this.asmOut.append( oldCode2 ); + this.asmOut.append( "\tLD\tE,L\n" ); + } } + this.asmOut.append( "\tPOP\tBC\n" ); + } + String loopLabel = nextLabel(); + this.asmOut.append( loopLabel ); + this.asmOut.append( ":\tIN\tA,(C)\n" ); + if( oldCode3 != null ) { + this.asmOut.append( "\tXOR\tL\n" ); + } + this.asmOut.append( "\tAND\tE\n" ); + if( this.options.canBreakAlways() ) { + String exitLabel = nextLabel(); + this.asmOut.append( "\tJR\tNZ," ); + this.asmOut.append( exitLabel ); + this.asmOut.append( "\n" + + "\tCALL\tXCKBRK\n" + + "\tJR\t" ); + this.asmOut.append( loopLabel ); + this.asmOut.newLine(); + this.asmOut.append( exitLabel ); + this.asmOut.append( ":\n" ); + addLibItem( BasicLibrary.LibItem.XCKBRK ); + } else { + this.asmOut.append( "\tJR\tZ," ); + this.asmOut.append( loopLabel ); + this.asmOut.newLine(); } } - private void parseMulExpr( CharacterIterator iter ) throws PrgException + private void parseWEND( CharacterIterator iter ) throws PrgException { - parseUnaryExpr( iter ); - for(;;) { - char ch = skipSpaces( iter ); - if( ch == '*' ) { - iter.next(); - int pos = this.asmOut.length(); - parseUnaryExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" ); - } - this.asmOut.append( "\tCALL\tO_MUL\n" ); - addLibItem( BasicLibrary.LibItem.O_MUL ); - } else if( ch == '/' ) { - iter.next(); - int pos = this.asmOut.length(); - parseUnaryExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tEX\tDE,HL\n" ); - } - this.asmOut.append( "\tCALL\tO_DIV\n" ); - addLibItem( BasicLibrary.LibItem.O_DIV ); - } else if( checkKeyword( iter, "MOD" ) ) { - int pos = this.asmOut.length(); - parseUnaryExpr( iter ); - String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); - if( newCode != null ) { - this.asmOut.append( newCode ); - } else { - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( oldCode ); - this.asmOut.append( "\tPOP\tDE\n" - + "\tEX\tDE,HL\n" ); - } - this.asmOut.append( "\tCALL\tO_MOD\n" ); - addLibItem( BasicLibrary.LibItem.O_MOD ); - } else { - break; + WhileEntry whileEntry = null; + if( !this.structureStack.isEmpty() ) { + BasicSourcePos entry = this.structureStack.peek(); + if( entry instanceof WhileEntry ) { + whileEntry = (WhileEntry) entry; + this.structureStack.pop(); } } + if( whileEntry == null ) { + throw new PrgException( "WEND ohne WHILE" ); + } + this.asmOut.append( "\tJP\t" ); + this.asmOut.append( whileEntry.getLoopLabel() ); + this.asmOut.newLine(); + this.asmOut.append( whileEntry.getExitLabel() ); + this.asmOut.append( ":\n" ); } - private void parseUnaryExpr( CharacterIterator iter ) throws PrgException + private void parseWHILE( CharacterIterator iter ) throws PrgException { - char ch = skipSpaces( iter ); - if( ch == '-' ) { - iter.next(); - Integer value = readNumber( iter ); - if( value != null ) { - this.asmOut.append_LD_HL_nn( -value.intValue() ); - } else { - parsePrimExpr( iter ); - this.asmOut.append( "\tCALL\tNEGHL\n" ); - addLibItem( BasicLibrary.LibItem.ABS_NEG_HL ); - } - } else { - if( ch == '+' ) { - iter.next(); - } - parsePrimExpr( iter ); - } + String loopLabel = nextLabel(); + String exitLabel = nextLabel(); + + this.asmOut.append( loopLabel ); + this.asmOut.append( ":\n" ); + BasicExprParser.parseExpr( this, iter ); + BasicUtil.checkKeyword( iter, "DO" ); + this.asmOut.append( "\tLD\tA,H\n" + + "\tOR\tL\n" + + "\tJP\tZ," + exitLabel + "\n" ); + checkAppendBreakCheck(); + this.structureStack.push( + new WhileEntry( + this.curSource, + this.curBasicLineNum, + loopLabel, + exitLabel ) ); } - private void parsePrimExpr( CharacterIterator iter ) throws PrgException + /* --- Parsen von Ausdruecke --- */ + + private void parseExpr( CharacterIterator iter ) throws PrgException { - char ch = skipSpaces( iter ); - if( ch == '(' ) { - iter.next(); - parseExpr( iter ); - parseToken( iter, ')' ); - } - else if( (ch >= '0') && (ch <= '9') ) { - this.asmOut.append_LD_HL_nn( parseNumber( iter ) ); - } - else if( ch == '&' ) { - ch = iter.next(); - if( (ch == 'B') || (ch == 'b') ) { - ch = iter.next(); - if( (ch == '0') || (ch == '1') ) { - int value = 0; - while( (ch == '0') || (ch == '1') ) { - value <<= 1; - if( ch == '1' ) { - value |= 1; - } - ch = iter.next(); - } - this.asmOut.append_LD_HL_nn( value ); - } else { - throw new PrgException( "0 oder 1 erwartet" ); - } - } else if( (ch == 'H') || (ch == 'h') ) { - iter.next(); - Integer value = readHex( iter ); - if( value == null ) { - throwHexDigitExpected(); - } - this.asmOut.append_LD_HL_nn( value ); - } else { - throw new PrgException( "B oder H erwartet" ); - } - } - else if( ch == '\'' ) { - ch = iter.next(); - iter.next(); - if( this.options.getWarnNonAsciiChars() - && ((ch < '\u0020') || (ch > '\u007F')) ) - { - putWarningNonAsciiChar( ch ); - } - parseToken( iter, '\'' ); - check8BitChar( ch ); - this.asmOut.append_LD_HL_nn( ch ); - } else { - String name = checkIdentifier( iter ); - if( name != null ) { - // auf Konstante pruefen - if( !checkAppend_LD_HL_Constant( name ) ) { - // auf Systemvariable pruefen - if( name.equals( "ERR" ) ) { - this.asmOut.append( "\tLD\tHL,(M_ERN)\n" ); - addLibItem( BasicLibrary.LibItem.M_ERN ); - } else if( name.equals( "H_CHAR" ) ) { - this.target.appendHChar( this.asmOut ); - } else if( name.equals( "H_PIXEL" ) ) { - this.target.appendHPixel( this.asmOut ); - } else if( name.equals( "W_CHAR" ) ) { - this.target.appendWChar( this.asmOut ); - } else if( name.equals( "W_PIXEL" ) ) { - this.target.appendWPixel( this.asmOut ); - } else if( name.equals( "XPOS" ) ) { - this.asmOut.append( "\tLD\tHL,(M_XPOS)\n" ); - addLibItem( BasicLibrary.LibItem.M_XYPO ); - } else if( name.equals( "YPOS" ) ) { - this.asmOut.append( "\tLD\tHL,(M_YPOS)\n" ); - addLibItem( BasicLibrary.LibItem.M_XYPO ); - // auf Systemfunktion pruefen - } else if( name.equals( "ABS" ) ) { - parseABS( iter ); - } else if( name.equals( "ASC" ) ) { - parseASC( iter ); - } else if( name.equals( "ASM" ) ) { - parseASM( iter, true ); - } else if( name.equals( "AVAILABLE" ) ) { - parseAVAILABLE( iter ); - } else if( name.equals( "DEEK" ) ) { - parseDEEK( iter ); - } else if( name.equals( "EOF" ) ) { - parseEOF( iter ); - } else if( name.equals( "HIBYTE" ) ) { - parseHIBYTE( iter ); - } else if( name.equals( "IN" ) || name.equals( "INP" ) ) { - parseIN( iter ); - } else if( name.equals( "INSTR" ) ) { - parseINSTR( iter ); - } else if( name.equals( "JOYST" ) ) { - parseJOYST( iter ); - } else if( name.equals( "LEN" ) ) { - parseLEN( iter ); - } else if( name.equals( "LOBYTE" ) ) { - parseLOBYTE( iter ); - } else if( name.equals( "LOCALPORT" ) ) { - parseLOCALPORT( iter ); - } else if( name.equals( "MAX" ) ) { - parseMAX( iter ); - } else if( name.equals( "MIN" ) ) { - parseMIN( iter ); - } else if( name.equals( "PEEK" ) ) { - parsePEEK( iter ); - } else if( name.equals( "POINT" ) || name.equals( "PTEST" ) ) { - parsePTEST( iter ); - } else if( name.equals( "REMOTEPORT" ) ) { - parseREMOTEPORT( iter ); - } else if( name.equals( "RND" ) ) { - parseRND( iter ); - } else if( name.equals( "SGN" ) ) { - parseSGN( iter ); - } else if( name.equals( "SQR" ) ) { - parseSQR( iter ); - } else if( name.equals( "USR" ) ) { - parseUSR( iter ); - } else if( name.equals( "USR0" ) ) { - parseUSR( iter, 0 ); - } else if( name.equals( "USR1" ) ) { - parseUSR( iter, 1 ); - } else if( name.equals( "USR2" ) ) { - parseUSR( iter, 2 ); - } else if( name.equals( "USR3" ) ) { - parseUSR( iter, 3 ); - } else if( name.equals( "USR4" ) ) { - parseUSR( iter, 4 ); - } else if( name.equals( "USR5" ) ) { - parseUSR( iter, 5 ); - } else if( name.equals( "USR6" ) ) { - parseUSR( iter, 6 ); - } else if( name.equals( "USR7" ) ) { - parseUSR( iter, 7 ); - } else if( name.equals( "USR8" ) ) { - parseUSR( iter, 8 ); - } else if( name.equals( "USR9" ) ) { - parseUSR( iter, 9 ); - } else if( name.equals( "VAL" ) ) { - parseVAL( iter ); - } else { - // auf benutzerdefinierte Funktion pruefen - CallableEntry entry = this.name2Callable.get( name ); - if( entry != null ) { - if( entry instanceof FunctionEntry ) { - if( ((FunctionEntry) entry).getReturnType() - != DataType.INTEGER ) - { - throwIntExprExpected(); - } - parseCallableCall( iter, entry ); - } else { - throw new PrgException( - "Aufruf einer Prozedur an der Stelle nicht erlaubt" ); - } - } else { - if( name.endsWith( "$" ) ) { - throwIntExprExpected(); - } - parseVariableExpr( iter, name, DataType.INTEGER ); - } - } - } - } else { - throwUnexpectedChar( skipSpaces( iter ) ); - } - } + BasicExprParser.parseExpr( this, iter ); } private void parseLineExprList( CharacterIterator iter ) throws PrgException { - java.util.List labels = new ArrayList( 32 ); + java.util.List labels = new ArrayList<>( 32 ); labels.add( parseDestLineExpr( iter ) ); - while( checkToken( iter, ',' ) ) { + while( BasicUtil.checkToken( iter, ',' ) ) { labels.add( parseDestLineExpr( iter ) ); } int n = labels.size(); @@ -4943,69 +3681,11 @@ private void parseLineExprList( CharacterIterator iter ) throws PrgException } - private int parseNumber( CharacterIterator iter ) throws PrgException - { - Integer value = readNumber( iter ); - if( value == null ) { - throw new PrgException( "Zahl erwartet" ); - } - return value.intValue(); - } - - - private void parseStringPrimExpr( CharacterIterator iter ) - throws PrgException - { - String text = checkStringLiteral( iter ); - if( text != null ) { - String label = getStringLiteralLabel( text ); - this.asmOut.append( "\tLD\tHL," ); - this.asmOut.append( label ); - this.asmOut.newLine(); - } else { - if( !checkParseStringPrimVarExpr( iter ) ) { - throwStringExprExpected(); - } - } - } - - - private Integer readHex( CharacterIterator iter ) throws PrgException - { - Integer rv = null; - char ch = iter.current(); - if( ((ch >= '0') && (ch <= '9')) - || ((ch >= 'A') && (ch <= 'F')) - || ((ch >= 'a') && (ch <= 'f')) ) - { - int value = 0; - while( ((ch >= '0') && (ch <= '9')) - || ((ch >= 'A') && (ch <= 'F')) - || ((ch >= 'a') && (ch <= 'f')) ) - { - value <<= 4; - if( (ch >= '0') && (ch <= '9') ) { - value |= (ch - '0'); - } - else if( (ch >= 'A') && (ch <= 'F') ) { - value |= (ch - 'A' + 10); - } - else if( (ch >= 'a') && (ch <= 'f') ) { - value |= (ch - 'a' + 10); - } - value &= 0xFFFF; - ch = iter.next(); - } - rv = new Integer( value ); - } - return rv; - } - - - private Long readLineNum( CharacterIterator iter ) throws PrgException + private static Long readLineNum( CharacterIterator iter ) + throws PrgException { Long rv = null; - char ch = skipSpaces( iter ); + char ch = BasicUtil.skipSpaces( iter ); if( (ch >= '0') && (ch <= '9') ) { long lineNum = ch - '0'; ch = iter.next(); @@ -5022,26 +3702,6 @@ private Long readLineNum( CharacterIterator iter ) throws PrgException } - private Integer readNumber( CharacterIterator iter ) throws PrgException - { - Integer rv = null; - char ch = skipSpaces( iter ); - if( (ch >= '0') && (ch <= '9') ) { - int value = ch - '0'; - ch = iter.next(); - while( (ch >= '0') && (ch <= '9') ) { - value = (value * 10) + (ch - '0'); - if( value > MAX_VALUE ) { - throw new PrgException( "Zahl zu gro\u00DF" ); - } - ch = iter.next(); - } - rv = new Integer( value ); - } - return rv; - } - - /* * Die Methode parst eine Zuweisung. * Die Variable, der der Wert zugewiesen wird, @@ -5051,15 +3711,15 @@ private void parseAssignment( CharacterIterator iter, SimpleVarInfo dstVar ) throws PrgException { - parseToken( iter, '=' ); + BasicUtil.parseToken( iter, '=' ); if( dstVar.getDataType() == DataType.INTEGER ) { if( dstVar.hasStaticAddr() ) { - parseExpr( iter ); + BasicExprParser.parseExpr( this, iter ); dstVar.writeCode_LD_Var_HL( this.asmOut ); } else { int pos = this.asmOut.length(); - parseExpr( iter ); - Integer value = removeLastCodeIfConstExpr( pos ); + BasicExprParser.parseExpr( this, iter ); + Integer value = BasicUtil.removeLastCodeIfConstExpr( this, pos ); if( value != null ) { this.asmOut.append( "\tLD\t(HL)," ); this.asmOut.appendHex2( value.intValue() ); @@ -5070,7 +3730,7 @@ private void parseAssignment( this.asmOut.newLine(); } else { String oldCode = this.asmOut.cut( pos ); - String newCode = convertCodeToValueInDE( oldCode ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); if( newCode != null ) { this.asmOut.append( newCode ); } else { @@ -5092,22 +3752,22 @@ private void parseAssignment( int dstPos = this.asmOut.length(); // pruefen, ob nur ein Literal zugewiesen wird - String text = checkStringLiteral( iter ); + String text = BasicUtil.checkStringLiteral( this, iter ); if( text != null ) { - char ch = skipSpaces( iter ); + char ch = BasicUtil.skipSpaces( iter ); if( isEndOfInstr( iter ) ) { if( dstVar.hasStaticAddr() ) { dstVar.ensureStaticAddrInDE( this.asmOut, false ); } else { - if( !replaceLastCodeFrom_LD_HL_To_DE() ) { + if( !BasicUtil.replaceLastCodeFrom_LD_HL_To_DE( this ) ) { this.asmOut.append( "\tEX\tDE,HL\n" ); } } this.asmOut.append( "\tLD\tHL," ); this.asmOut.append( getStringLiteralLabel( text ) ); this.asmOut.append( "\n" - + "\tCALL\tASGSL\n" ); - addLibItem( BasicLibrary.LibItem.ASGSL ); + + "\tCALL\tASSIGN_STR_TO_VS\n" ); + addLibItem( BasicLibrary.LibItem.ASSIGN_STR_TO_VS ); done = true; } } @@ -5118,7 +3778,7 @@ private void parseAssignment( this.asmOut.setLength( dstPos ); SimpleVarInfo srcVar = checkVariable( iter ); if( srcVar != null ) { - char ch = skipSpaces( iter ); + char ch = BasicUtil.skipSpaces( iter ); if( isEndOfInstr( iter ) ) { if( srcVar.hasStaticAddr() ) { srcVar.ensureAddrInHL( this.asmOut ); @@ -5128,8 +3788,8 @@ private void parseAssignment( srcVar.ensureAddrInHL( this.asmOut ); this.asmOut.append( "\tPOP\tDE\n" ); } - this.asmOut.append( "\tCALL\tASGSV\n" ); - addLibItem( BasicLibrary.LibItem.ASGSV ); + this.asmOut.append( "\tCALL\tASSIGN_VS_TO_VS\n" ); + addLibItem( BasicLibrary.LibItem.ASSIGN_VS_TO_VS ); done = true; } } @@ -5142,15 +3802,15 @@ private void parseAssignment( if( !dstVar.hasStaticAddr() ) { this.asmOut.append( "\tPUSH\tHL\n" ); } - if( checkParseStringPrimExpr( iter ) ) { + if( BasicExprParser.checkParseStringPrimExpr( this, iter ) ) { if( isEndOfInstr( iter ) ) { if( dstVar.hasStaticAddr() ) { dstVar.ensureStaticAddrInDE( this.asmOut, true ); } else { this.asmOut.append( "\tPOP\tDE\n" ); } - this.asmOut.append( "\tCALL\tASGSM\n" ); - addLibItem( BasicLibrary.LibItem.ASGSM ); + this.asmOut.append( "\tCALL\tASSIGN_STR_TO_NEW_MEM_VS\n" ); + addLibItem( BasicLibrary.LibItem.ASSIGN_STR_TO_NEW_MEM_VS ); done = true; } } @@ -5168,18 +3828,18 @@ private void parseAssignment( this.asmOut.append_LD_BC_nn( MAX_STR_LEN ); addLibItem( BasicLibrary.LibItem.MFIND ); for(;;) { - text = checkStringLiteral( iter ); + text = BasicUtil.checkStringLiteral( this, iter ); if( text != null ) { this.asmOut.append( "\tLD\tHL," ); this.asmOut.append( getStringLiteralLabel( text ) ); this.asmOut.newLine(); } else { int pos = this.asmOut.length(); - if( !checkParseStringPrimVarExpr( iter ) ) { - throwStringExprExpected(); + if( !BasicExprParser.checkParseStringPrimVarExpr( this, iter ) ) { + BasicUtil.throwStringExprExpected(); } String tmpCode = this.asmOut.cut( pos ); - if( isOnly_LD_HL_xx( tmpCode ) ) { + if( BasicUtil.isOnly_LD_HL_xx( tmpCode ) ) { this.asmOut.append( tmpCode ); } else { this.asmOut.append( "\tPUSH\tBC\n" @@ -5191,7 +3851,7 @@ private void parseAssignment( } this.asmOut.append( "\tCALL\tSTNCP\n" ); addLibItem( BasicLibrary.LibItem.STNCP ); - if( skipSpaces( iter ) != '+' ) { + if( BasicUtil.skipSpaces( iter ) != '+' ) { break; } iter.next(); @@ -5204,8 +3864,8 @@ private void parseAssignment( } else { this.asmOut.append( "\tPOP\tDE\n" ); } - this.asmOut.append( "\tCALL\tASGSL\n" ); - addLibItem( BasicLibrary.LibItem.ASGSL ); + this.asmOut.append( "\tCALL\tASSIGN_STR_TO_VS\n" ); + addLibItem( BasicLibrary.LibItem.ASSIGN_STR_TO_VS ); } } } @@ -5213,22 +3873,6 @@ private void parseAssignment( /* --- Hilfsfunktionen --- */ - private void addLibItem( BasicLibrary.LibItem libItem ) - { - if( this.codeGenEnabled ) - this.libItems.add( libItem ); - } - - - private static void check8BitChar( char ch ) throws PrgException - { - if( (ch == 0) || (ch > 0xFF) ) { - throw new PrgException( "Zeichen \'" + ch - + "\' au\u00DFerhalb des 8-Bit-Wertebereiches" ); - } - } - - private void checkAppendBreakCheck() { if( this.options.canBreakAlways() ) { @@ -5238,105 +3882,9 @@ private void checkAppendBreakCheck() } - private boolean checkAppend_LD_HL_Constant( String name ) - { - boolean rv = false; - if( name != null ) { - rv = true; - if( name.equals( "TOP" ) ) { - this.asmOut.append( "\tLD\tHL,M_TOP\n" ); - addLibItem( BasicLibrary.LibItem.M_TOP ); - } else { - int value = 0; - if( name.equals( "BLACK" ) ) { - value = this.target.getColorBlack(); - } else if( name.equals( "BLINKING" ) ) { - value = this.target.getColorBlinking(); - } else if( name.equals( "BLUE" ) ) { - value = this.target.getColorBlue(); - } else if( name.equals( "CYAN" ) ) { - value = this.target.getColorCyan(); - } else if( name.equals( "E_CHANNEL_ALREADY_OPEN" ) ) { - value = BasicLibrary.E_CHANNEL_ALREADY_OPEN; - } else if( name.equals( "E_CHANNEL_CLOSED" ) ) { - value = BasicLibrary.E_CHANNEL_CLOSED; - } else if( name.equals( "E_CONNECT_FAILED" ) ) { - value = BasicLibrary.E_CONNECT_FAILED; - } else if( name.equals( "E_DEVICE_LOCKED" ) ) { - value = BasicLibrary.E_DEVICE_LOCKED; - } else if( name.equals( "E_DEVICE_NOT_CONFIGURED" ) ) { - value = BasicLibrary.E_DEVICE_NOT_CONFIGURED; - } else if( name.equals( "E_DEVICE_NOT_FOUND" ) ) { - value = BasicLibrary.E_DEVICE_NOT_FOUND; - } else if( name.equals( "E_DNS_ERROR" ) ) { - value = BasicLibrary.E_DNS_ERROR; - } else if( name.equals( "E_DNS_NOT_CONFIGURED" ) ) { - value = BasicLibrary.E_DNS_NOT_CONFIGURED; - } else if( name.equals( "E_DISK_FULL" ) ) { - value = BasicLibrary.E_DISK_FULL; - } else if( name.equals( "E_EOF" ) ) { - value = BasicLibrary.E_EOF; - } else if( name.equals( "E_ERROR" ) ) { - value = BasicLibrary.E_ERROR; - } else if( name.equals( "E_FILE_NOT_FOUND" ) ) { - value = BasicLibrary.E_FILE_NOT_FOUND; - } else if( name.equals( "E_INVALID" ) ) { - value = BasicLibrary.E_INVALID; - } else if( name.equals( "E_IO_ERROR" ) ) { - value = BasicLibrary.E_IO_ERROR; - } else if( name.equals( "E_IO_MODE" ) ) { - value = BasicLibrary.E_IO_MODE; - } else if( name.equals( "E_MTU_EXCEEDED" ) ) { - value = BasicLibrary.E_MTU_EXCEEDED; - } else if( name.equals( "E_NO_DISK" ) ) { - value = BasicLibrary.E_NO_DISK; - } else if( name.equals( "E_OK" ) ) { - value = BasicLibrary.E_OK; - } else if( name.equals( "E_OVERFLOW" ) ) { - value = BasicLibrary.E_OVERFLOW; - } else if( name.equals( "E_READ_ONLY" ) ) { - value = BasicLibrary.E_READ_ONLY; - } else if( name.equals( "E_SOCKET_STATUS" ) ) { - value = BasicLibrary.E_SOCKET_STATUS; - } else if( name.equals( "E_TIMEOUT" ) ) { - value = BasicLibrary.E_TIMEOUT; - } else if( name.equals( "E_UNKNOWN_HOST" ) ) { - value = BasicLibrary.E_UNKNOWN_HOST; - } else if( name.equals( "FALSE" ) ) { - value = 0; - } else if( name.equals( "GRAPHICSCREEN" ) ) { - value = this.target.getGraphicScreenNum(); - } else if( name.equals( "GREEN" ) ) { - value = this.target.getColorGreen(); - } else if( name.equals( "LASTSCREEN" ) ) { - value = this.target.getLastScreenNum(); - } else if( name.equals( "MAGENTA" ) ) { - value = this.target.getColorMagenta(); - } else if( name.equals( "RED" ) ) { - value = this.target.getColorRed(); - } else if( name.equals( "TARGETADDR" ) ) { - value = this.options.getCodeBegAddr(); - } else if( name.equals( "TRUE" ) ) { - value = 0xFFFF; - } else if( name.equals( "WHITE" ) ) { - value = this.target.getColorWhite(); - } else if( name.equals( "YELLOW" ) ) { - value = this.target.getColorYellow(); - } else { - rv = false; - } - if( rv ) { - this.asmOut.append_LD_HL_nn( value ); - } - } - } - return rv; - } - - private void checkCreateStackFrame() { - CallableEntry entry = getCallableEntry(); + CallableEntry entry = getEnclosingCallableEntry(); if( entry != null ) { int nVars = entry.getVarCount(); if( !entry.hasStackFrame() @@ -5358,8 +3906,8 @@ private void checkCreateStackFrame() for( int i = 0; i < nVars; i++ ) { if( entry.getVarType( i ) == DataType.STRING ) { if( !hlLoaded ) { - this.asmOut.append_LD_HL_xx( "D_EMPT" ); - addLibItem( BasicLibrary.LibItem.D_EMPT ); + this.asmOut.append_LD_HL_xx( BasicLibrary.EMPTY_STRING_LABEL ); + addLibItem( BasicLibrary.LibItem.EMPTY_STRING ); hlLoaded = true; } this.asmOut.append_LD_IndirectIY_HL( entry.getVarIYOffs( i ) ); @@ -5381,53 +3929,10 @@ private void checkGraphicsSupported() throws PrgException } - private String checkIdentifier( CharacterIterator iter ) - { - String rv = null; - char ch = skipSpaces( iter ); - if( ((ch >= 'A') && (ch <= 'Z')) - || ((ch >= 'a') && (ch <= 'z')) ) - { - int pos = iter.getIndex(); - ch = iter.next(); - while( ((ch >= 'A') && (ch <= 'Z')) - || ((ch >= 'a') && (ch <= 'z')) - || ((ch >= '0') && (ch <= '9')) - || (ch == '_') ) - { - ch = iter.next(); - } - int len = iter.getIndex() - pos; - if( ch == '$' ) { - len++; - } - StringBuilder buf = new StringBuilder( len ); - iter.setIndex( pos ); - ch = iter.current(); - do { - buf.append( Character.toUpperCase( ch ) ); - ch = iter.next(); - --len; - } while( len > 0 ); - rv = buf.toString(); - } - return rv; - } - - - private void checkKCNetSupported() throws PrgException - { - if( this.target.getKCNetBaseIOAddr() < 0 ) { - throw new PrgException( "Netzwerkanweisungen f\u00FCr" - + " das Zielsystem nicht unterst\u00FCtzt" ); - } - } - - private void checkMainPrgScope() throws PrgException { if( !this.mainPrg ) { - CallableEntry entry = getCallableEntry(); + CallableEntry entry = getEnclosingCallableEntry(); if( entry == null ) { throw new PrgException( "Anweisung nur im Hauptprogramm" + " oder in einer Funktion/Prozedur zul\u00E4ssig" ); @@ -5441,7 +3946,7 @@ private boolean checkReturnValueAssignment( String name ) throws PrgException { boolean rv = false; - CallableEntry entry = getCallableEntry(); + CallableEntry entry = getEnclosingCallableEntry(); if( entry != null ) { if( entry.getName().equals( name ) ) { if( entry instanceof FunctionEntry ) { @@ -5459,218 +3964,26 @@ private boolean checkReturnValueAssignment( } - private static boolean checkToken( CharacterIterator iter, char ch ) - { - boolean rv = false; - if( skipSpaces( iter ) == ch ) { - iter.next(); - rv = true; - } - return rv; - } - - - private static void checkVarName( String varName ) throws PrgException - { - if( Arrays.binarySearch( sortedReservedWords, varName ) >= 0 ) { - throw new PrgException( "Reserviertes Schl\u00FCsselwort" - + " als Variablenname nicht erlaubt" ); - } - } - - - private String convertCodeToValueInBC( String text ) - { - return convertCodeToValueInRR( text, "BC" ); - } - - - private String convertCodeToValueInDE( String text ) - { - return convertCodeToValueInRR( text, "DE" ); - } - - - private String convertCodeToValueInRR( String text, String rr ) - { - String rv = null; - if( (text != null) && (rr.length() == 2) ) { - String label = ""; - int tabPos = text.indexOf( '\t' ); - if( tabPos > 0 ) { - label = text.substring( 0, tabPos ); - text = text.substring( tabPos ); - } - if( text.startsWith( "\tLD\tHL," ) ) { - if( text.indexOf( '\n' ) == (text.length() - 1) ) { - rv = String.format( - "%s\tLD\t%s%s", - label, - rr, - text.substring( 6 ) ); - } - } - else if( text.startsWith( "\tLD\tH," ) - || text.startsWith( "\tLD\tL," ) ) - { - int len = text.length(); - int eol = text.indexOf( '\n' ); - if( (eol > 0) && ((eol + 1) < len) ) { - String line1 = text.substring( 0, eol + 1 ); - String line2 = text.substring( eol + 1 ); - if( line2.indexOf( '\n' ) == (line2.length() - 1) ) { - if( line1.startsWith( "\tLD\tH,(IY" ) - && line2.startsWith( "\tLD\tL,(IY" ) ) - { - rv = String.format( - "%s\tLD\t%c%s\tLD\t%c%s", - label, - rr.charAt( 0 ), - line1.substring( 5 ), - rr.charAt( 1 ), - line2.substring( 5 ) ); - } - else if( line1.startsWith( "\tLD\tL,(IY" ) - && line2.startsWith( "\tLD\tH,(IY" ) ) - { - rv = String.format( - "%s\tLD\t%c%s\tLD\t%c%s", - label, - rr.charAt( 1 ), - line1.substring( 5 ), - rr.charAt( 0 ), - line2.substring( 5 ) ); - } - } - } - } - } - return rv; - } - - - /* - * Ermittlung der umgebenden Funktion/Prozedur - * - * Diese kann nur der unterste Eintrag auf dem Stack sein. - */ - private CallableEntry getCallableEntry() - { - CallableEntry rv = null; - if( !this.structureStack.isEmpty() ) { - StructureEntry entry = this.structureStack.get( 0 ); - if( entry instanceof CallableEntry ) { - rv = (CallableEntry) entry; - } - } - return rv; - } - - - private String getUsrLabel( int usrNum ) - { - String rv = String.format( "M_USR%d", usrNum ); - this.usrLabels.add( rv ); - return rv; - } - - - private boolean isEndOfInstr( CharacterIterator iter ) - { - char ch = skipSpaces( iter ); - return (ch == CharacterIterator.DONE) || (ch == ':'); - } - - - private static boolean isOnly_LD_HL_xx( String text ) - { - boolean rv = false; - if( text != null ) { - int tabPos = text.indexOf( '\t' ); - if( tabPos > 0 ) { - text = text.substring( tabPos ); - } - if( text.startsWith( "\tLD\tHL," ) ) { - if( text.indexOf( '\n' ) == (text.length() - 1) ) { - rv = true; - } - } - else if( text.startsWith( "\tLD\tH," ) - || text.startsWith( "\tLD\tL," ) ) - { - int len = text.length(); - int eol = text.indexOf( '\n' ); - if( (eol > 0) && ((eol + 1) < len) ) { - String line1 = text.substring( 0, eol + 1 ); - String line2 = text.substring( eol + 1 ); - if( line2.indexOf( '\n' ) == (line2.length() - 1) ) { - if( line1.startsWith( "\tLD\tH,(IY" ) - && line2.startsWith( "\tLD\tL,(IY" ) ) - { - rv = true; - } - else if( line1.startsWith( "\tLD\tL,(IY" ) - && line2.startsWith( "\tLD\tH,(IY" ) ) - { - rv = true; - } - } - } - } - } - return rv; - } - - - private void lockTmpStrBuf() throws PrgException - { - if( this.tmpStrBufUsed ) { - throw new PrgException( "String-Funktion hier nicht erlaubt," - + " da der interne String-Puffer bereits durch" - + " eine andere String-Funktion belegt ist\n" - + "Weisen Sie bitte den String-Ausdruck einer Variablen zu" - + " und verwenden Sie diese hier." ); + private static void checkVarName( String varName ) throws PrgException + { + if( isReservedWord( varName ) ) { + throw new PrgException( "Reserviertes Schl\u00FCsselwort" + + " als Variablenname nicht erlaubt" ); } - this.tmpStrBufUsed = true; } - private String nextLabel() + private boolean isEndOfInstr( CharacterIterator iter ) { - return String.format( "M%d", this.labelNum++ ); + char ch = BasicUtil.skipSpaces( iter ); + return (ch == CharacterIterator.DONE) || (ch == ':'); } - private void parse2ArgsTo_DE_HL( CharacterIterator iter ) - throws PrgException + private static boolean isReservedWord( String name ) { - int pos = this.asmOut.length(); - parseExpr( iter ); - parseToken( iter, ',' ); - Integer v1 = removeLastCodeIfConstExpr( pos ); - if( v1 != null ) { - parseExpr( iter ); - this.asmOut.append_LD_DE_nn( v1.intValue() ); - } else { - String oldCode1 = this.asmOut.cut( pos ); - String newCode1 = convertCodeToValueInDE( oldCode1 ); - parseExpr( iter ); - String code2 = this.asmOut.cut( pos ); - if( isOnly_LD_HL_xx( code2 ) ) { - if( newCode1 != null ) { - this.asmOut.append( newCode1 ); - } else { - this.asmOut.append( oldCode1 ); - this.asmOut.append( "\tEX\tDE,HL\n" ); - } - this.asmOut.append( code2 ); - } else { - this.asmOut.append( oldCode1 ); - this.asmOut.append( "\tPUSH\tHL\n" ); - this.asmOut.append( code2 ); - this.asmOut.append( "\tPOP\tDE\n" ); - } - } + return (Arrays.binarySearch( sortedReservedWords, name ) >= 0) + || AbstractTarget.isReservedWord( name ); } @@ -5684,11 +3997,11 @@ private CallableEntry parseCallableDecl( } // Name parsen - String name = checkIdentifier( iter ); + String name = BasicUtil.checkIdentifier( iter ); if( name == null ) { throw new PrgException( "Name der Funktion/Prozedur erwartet" ); } - if( Arrays.binarySearch( sortedReservedWords, name ) >= 0 ) { + if( isReservedWord( name ) ) { throw new PrgException( "Reserviertes Schl\u00FCsselwort als" + " Name einer Funktion/Prozedur nicht erlaubt" ); } @@ -5708,12 +4021,12 @@ private CallableEntry parseCallableDecl( } // Argumente parsen - Set argSet = new TreeSet(); - java.util.List argList = new ArrayList(); - if( checkToken( iter, '(' ) ) { - if( !checkToken( iter, ')' ) ) { + Set argSet = new TreeSet<>(); + java.util.List argList = new ArrayList<>(); + if( BasicUtil.checkToken( iter, '(' ) ) { + if( !BasicUtil.checkToken( iter, ')' ) ) { do { - String argName = checkIdentifier( iter ); + String argName = BasicUtil.checkIdentifier( iter ); if( argName == null ) { throwVarExpected(); } @@ -5745,8 +4058,8 @@ private CallableEntry parseCallableDecl( throw new PrgException( "Anzahl der maximal m\u00F6glichen" + " Argumente \u00FCberschritten" ); } - } while( checkToken( iter, ',' ) ); - parseToken( iter, ')' ); + } while( BasicUtil.checkToken( iter, ',' ) ); + BasicUtil.parseToken( iter, ')' ); } } @@ -5767,14 +4080,11 @@ private CallableEntry parseCallableDecl( if( entry == null ) { if( isFunc ) { entry = new FunctionEntry( - this.curSourceLineNum, + this.curSource, this.curBasicLineNum, name ); } else { - entry = new SubEntry( - this.curSourceLineNum, - this.curBasicLineNum, - name ); + entry = new SubEntry( this.curSource, this.curBasicLineNum, name ); } if( argList != null ) { entry.setArgs( argList ); @@ -5798,10 +4108,7 @@ private void parseCallableImpl( // ggf. Haupprogramm beenden if( this.mainPrg ) { - if( !this.suppressExit ) { - this.asmOut.append( "\tJP\tXEXIT\n" ); - this.suppressExit = true; - } + this.asmOut.append( "\tJP\tXEXIT\n" ); this.mainPrg = false; } @@ -5811,132 +4118,6 @@ private void parseCallableImpl( } - private void parseCallableCall( - CharacterIterator iter, - CallableEntry entry ) throws PrgException - { - if( this.options.getCheckStack() - && (this.options.getStackSize() > 0) ) - { - this.asmOut.append_LD_DE_nn( entry.getTotalArgSize() - + entry.getTotalVarSize() ); - this.asmOut.append( "\tCALL\tCKSTKN\n" ); - addLibItem( BasicLibrary.LibItem.CKSTK ); - } - int nArgs = entry.getArgCount(); - if( nArgs > 0 ) { - parseToken( iter, '(' ); - for( int i = 0; i < nArgs; i++ ) { - if( i > 0 ) { - parseToken( iter, ',' ); - } - if( entry.getArgType( i ) == DataType.STRING ) { - String text = checkStringLiteral( iter ); - if( text != null ) { - this.asmOut.append( "\tLD\tHL," ); - this.asmOut.append( getStringLiteralLabel( text ) ); - this.asmOut.append( "\n" - + "\tPUSH\tHL\n" ); - } else { - SimpleVarInfo srcVar = checkVariable( iter ); - if( srcVar != null ) { - srcVar.ensureValueInDE( this.asmOut ); - this.asmOut.append( "\tCALL\tSVDUP\n" - + "\tPUSH\tDE\n" ); - addLibItem( BasicLibrary.LibItem.SVDUP ); - } else { - parseStringPrimExpr( iter ); - this.asmOut.append( "\tCALL\tSMACP\n" - + "\tPUSH\tHL\n" ); - addLibItem( BasicLibrary.LibItem.SMACP ); - } - } - } else { - parseExpr( iter ); - this.asmOut.append( "\tPUSH\tHL\n" ); - } - } - parseToken( iter, ')' ); - } else { - if( checkToken( iter, '(' ) ) { - parseToken( iter, ')' ); - } - } - this.asmOut.append( "\tCALL\t" ); - this.asmOut.append( entry.getLabel() ); - this.asmOut.newLine(); - if( nArgs > 0 ) { - if( nArgs > 5 ) { - this.asmOut.append_LD_HL_nn( -nArgs ); - this.asmOut.append( "\tADD\tHL,SP\n" - + "\tLD\tSP,HL\n" ); - } else { - for( int i = 0; i < nArgs; i++ ) { - this.asmOut.append( "\tPOP\tHL\n" ); - } - } - } - if( entry instanceof FunctionEntry ) { - this.asmOut.append( "\tLD\tHL,(M_FRET)\n" ); - addLibItem( BasicLibrary.LibItem.M_FRET ); - if( ((FunctionEntry) entry).getReturnType() == DataType.STRING ) { - this.gcRequired = true; - } - } - entry.putCallLineNum( this.curSourceLineNum, this.curBasicLineNum ); - } - - - /* - * Die Methode parst eine Kanalnummer. - * Die Anfangsadresse des Kanalzeigerfeldes steht dann in HL. - * Das Doppelkreuz ist zu dem Zeitpunkt bereits geparst. - */ - private void parseIOChannelNumToPtrFldAddrInHL( - CharacterIterator iter, - boolean socketNumInA, - AtomicBoolean constOut ) throws PrgException - { - Integer channel = readNumber( iter ); - if( channel != null ) { - switch( channel.intValue() ) { - case 1: - this.asmOut.append( "\tLD\tHL,IOCTB1\n" ); - addLibItem( BasicLibrary.LibItem.IOCTB1 ); - break; - case 2: - this.asmOut.append( "\tLD\tHL,IOCTB2\n" ); - addLibItem( BasicLibrary.LibItem.IOCTB2 ); - break; - default: - throwIOChannelNumOutOfRange(); - } - if( socketNumInA ) { - this.asmOut.append_LD_A_n( channel.intValue() - 1 ); - } - if( constOut != null ) { - constOut.set( true ); - } - } else { - parseExpr( iter ); - if( socketNumInA ) { - this.asmOut.append( "\tPUSH\tHL\n" ); - } - this.asmOut.append( "\tCALL\tIOCADR\n" ); - addLibItem( BasicLibrary.LibItem.IOCADR ); - addLibItem( BasicLibrary.LibItem.IOCTB1 ); - addLibItem( BasicLibrary.LibItem.IOCTB2 ); - if( socketNumInA ) { - this.asmOut.append( "\tPUSH\tBC\n" - + "\tLD\tA,C\n" ); - } - if( constOut != null ) { - constOut.set( false ); - } - } - } - - /* * Die Methode parst eine Kanalnummer. * Das Doppelkreuz ist zu dem Zeitpunkt bereits geparst. @@ -5945,12 +4126,12 @@ private void parseIOChannelNumToPtrFldAddrInHL( private void parseIOChannelNumToWriteRoutineInHL( CharacterIterator iter ) throws PrgException { - Integer channel = readNumber( iter ); + Integer channel = BasicUtil.readNumber( iter ); if( channel != null ) { switch( channel.intValue() ) { case 1: this.asmOut.append( "\tLD\tHL,IOCTB1\n" - + "\tLD\t(M_IOCA),HL\n" + + "\tLD\t(IO_M_CADDR),HL\n" + "\tLD\tHL,(IOCTB1+" ); this.asmOut.appendHex2( BasicLibrary.IOCTB_WRITE_OFFS ); this.asmOut.append( ")\n" ); @@ -5958,7 +4139,7 @@ private void parseIOChannelNumToWriteRoutineInHL( break; case 2: this.asmOut.append( "\tLD\tHL,IOCTB2\n" - + "\tLD\t(M_IOCA),HL\n" + + "\tLD\t(IO_M_CADDR),HL\n" + "\tLD\tHL,(IOCTB2+" ); this.asmOut.appendHex2( BasicLibrary.IOCTB_WRITE_OFFS ); this.asmOut.append( ")\n" ); @@ -5968,9 +4149,9 @@ private void parseIOChannelNumToWriteRoutineInHL( throwIOChannelNumOutOfRange(); } } else { - parseExpr( iter ); + BasicExprParser.parseExpr( this, iter ); this.asmOut.append( "\tCALL\tIOCADR\n" - + "\tLD\t(M_IOCA),HL\n" ); + + "\tLD\t(IO_M_CADDR),HL\n" ); this.asmOut.append_LD_DE_nn( BasicLibrary.IOCTB_WRITE_OFFS ); this.asmOut.append( "\tADD\tHL,DE\n" + "\tLD\tA,(HL)\n" @@ -5989,7 +4170,7 @@ private String parseDestLineExpr( CharacterIterator iter ) { BasicLineExpr lineExpr = BasicLineExpr.checkBasicLineExpr( iter, - this.curSourceLineNum, + this.curSource, this.curBasicLineNum ); if( lineExpr == null ) { throwBasicLineExprExpected(); @@ -6003,7 +4184,7 @@ private void parseInputLine( CharacterIterator iter, boolean password ) throws PrgException { - String text = checkStringLiteral( iter ); + String text = BasicUtil.checkStringLiteral( this, iter ); if( text != null ) { // String-Literal evtl. bereits vorhanden? String label = this.str2Label.get( text ); @@ -6018,7 +4199,7 @@ private void parseInputLine( this.asmOut.appendStringLiteral( text ); addLibItem( BasicLibrary.LibItem.XOUTST ); } - parseToken( iter, ';' ); + BasicUtil.parseToken( iter, ';' ); } SimpleVarInfo varInfo = checkVariable( iter ); if( varInfo != null ) { @@ -6045,8 +4226,8 @@ private void parseInputLine( private void parseInputLineIO( CharacterIterator iter ) throws PrgException { - parseIOChannelNumToPtrFldAddrInHL( iter, false, null ); - parseToken( iter, ',' ); + parseIOChannelNumToPtrFldAddrInHL( iter, null ); + BasicUtil.parseToken( iter, ',' ); this.asmOut.append( "\tLD\tC,00H\n" + "\tCALL\tIOINL\n" ); int pos = this.asmOut.length(); @@ -6059,7 +4240,7 @@ private void parseInputLineIO( CharacterIterator iter ) throws PrgException } varInfo.ensureAddrInHL( this.asmOut ); String oldVarCode = this.asmOut.cut( pos ); - String newVarCode = convertCodeToValueInDE( oldVarCode ); + String newVarCode = BasicUtil.convertCodeToValueInDE( oldVarCode ); if( newVarCode != null ) { this.asmOut.append( newVarCode ); } else { @@ -6068,15 +4249,15 @@ private void parseInputLineIO( CharacterIterator iter ) throws PrgException this.asmOut.append( "\tEX\tDE,HL\n" + "\tPOP\tHL\n" ); } - this.asmOut.append( "\tCALL\tASGSM\n" ); + this.asmOut.append( "\tCALL\tASSIGN_STR_TO_NEW_MEM_VS\n" ); addLibItem( BasicLibrary.LibItem.IOINL ); - addLibItem( BasicLibrary.LibItem.ASGSM ); + addLibItem( BasicLibrary.LibItem.ASSIGN_STR_TO_NEW_MEM_VS ); } private void parseKeywordAS( CharacterIterator iter ) throws PrgException { - if( !checkKeyword( iter, "AS" ) ) { + if( !BasicUtil.checkKeyword( iter, "AS" ) ) { throw new PrgException( "AS erwartet" ); } } @@ -6085,10 +4266,10 @@ private void parseKeywordAS( CharacterIterator iter ) throws PrgException private void parsePointTo_DE_HL( CharacterIterator iter ) throws PrgException { - boolean enclosed = checkToken( iter, '(' ); - parse2ArgsTo_DE_HL( iter ); + boolean enclosed = BasicUtil.checkToken( iter, '(' ); + BasicExprParser.parse2ArgsTo_DE_HL( this, iter ); if( enclosed ) { - parseToken( iter, ')' ); + BasicUtil.parseToken( iter, ')' ); } } @@ -6098,18 +4279,18 @@ private void parsePointToMem( String labelX, String labelY ) throws PrgException { - boolean enclosed = checkToken( iter, '(' ); - parseExpr( iter ); + boolean enclosed = BasicUtil.checkToken( iter, '(' ); + BasicExprParser.parseExpr( this, iter ); this.asmOut.append( "\tLD\t(" ); this.asmOut.append( labelX ); this.asmOut.append( "),HL\n" ); - parseToken( iter, ',' ); - parseExpr( iter ); + BasicUtil.parseToken( iter, ',' ); + BasicExprParser.parseExpr( this, iter ); this.asmOut.append( "\tLD\t(" ); this.asmOut.append( labelY ); this.asmOut.append( "),HL\n" ); if( enclosed ) { - parseToken( iter, ')' ); + BasicUtil.parseToken( iter, ')' ); } } @@ -6121,7 +4302,7 @@ private void parsePrint( boolean newLine = true; boolean space = false; boolean formatted = false; - char ch = skipSpaces( iter ); + char ch = BasicUtil.skipSpaces( iter ); while( !isEndOfInstr( iter ) ) { this.tmpStrBufUsed = false; newLine = true; @@ -6140,11 +4321,12 @@ private void parsePrint( */ boolean done = false; int srcPos = iter.getIndex(); - if( checkKeyword( iter, "CHR$" ) ) { - if( checkToken( iter, '(' ) ) { + if( BasicUtil.checkKeyword( iter, "CHR$" ) ) { + if( BasicUtil.checkToken( iter, '(' ) ) { int dstPos = this.asmOut.length(); - parseExpr( iter ); - Integer value = removeLastCodeIfConstExpr( dstPos ); + BasicExprParser.parseExpr( this, iter ); + Integer value = BasicUtil.removeLastCodeIfConstExpr( + this, dstPos ); if( value != null ) { this.asmOut.append_LD_A_n( value.intValue() ); } else { @@ -6157,14 +4339,25 @@ private void parsePrint( this.asmOut.append( "\tCALL\tIO_COUT\n" ); addLibItem( BasicLibrary.LibItem.IO_COUT ); } - parseToken( iter, ')' ); + BasicUtil.parseToken( iter, ')' ); hasSeg = true; done = true; } + } else if( BasicUtil.checkKeyword( iter, "SPC" ) ) { + BasicExprParser.parseEnclosedExpr( this, iter ); + if( toScreen ) { + this.asmOut.append( "\tCALL\tPRINT_SPC\n" ); + addLibItem( BasicLibrary.LibItem.PRINT_SPC ); + } else { + this.asmOut.append( "\tCALL\tIO_PRINT_SPC\n" ); + addLibItem( BasicLibrary.LibItem.IO_PRINT_SPC ); + } + hasSeg = true; + done = true; } if( !done ) { iter.setIndex( srcPos ); - String text = checkStringLiteral( iter ); + String text = BasicUtil.checkStringLiteral( this, iter ); if( text != null ) { // String-Literal evtl. bereits vorhanden? String label = this.str2Label.get( text ); @@ -6190,7 +4383,9 @@ private void parsePrint( this.asmOut.appendStringLiteral( text ); } hasSeg = true; - } else if( checkParseStringPrimVarExpr( iter ) ) { + } else if( BasicExprParser.checkParseStringPrimVarExpr( + this, iter ) ) + { if( toScreen ) { this.asmOut.append( "\tCALL\tXOUTS\n" ); addLibItem( BasicLibrary.LibItem.XOUTS ); @@ -6201,11 +4396,11 @@ private void parsePrint( hasSeg = true; } else { if( mandatory ) { - throwStringExprExpected(); + BasicUtil.throwStringExprExpected(); } } } - if( skipSpaces( iter ) != '+' ) { + if( BasicUtil.skipSpaces( iter ) != '+' ) { break; } iter.next(); @@ -6214,7 +4409,7 @@ private void parsePrint( if( !hasSeg ) { // kann nur noch numerisches Segment sein - parseExpr( iter ); + BasicExprParser.parseExpr( this, iter ); if( formatted ) { if( toScreen ) { this.asmOut.append( "\tCALL\tP_IF\n" ); @@ -6236,7 +4431,7 @@ private void parsePrint( } // weiteres Segment? - ch = skipSpaces( iter ); + ch = BasicUtil.skipSpaces( iter ); formatted = (ch == ','); if( (ch != ';') && (ch != ',') ) { break; @@ -6252,7 +4447,7 @@ private void parsePrint( } newLine = false; iter.next(); - ch = skipSpaces( iter ); + ch = BasicUtil.skipSpaces( iter ); } if( newLine ) { if( toScreen ) { @@ -6266,57 +4461,26 @@ private void parsePrint( } - private void parseToken( - CharacterIterator iter, - char ch ) throws PrgException + private String parseStringLiteral( CharacterIterator iter ) + throws PrgException { - if( skipSpaces( iter ) != ch ) { - throw new PrgException( String.format( "\'%c\' erwartet", ch ) ); + String text = BasicUtil.checkStringLiteral( this, iter ); + if( text == null ) { + throw new PrgException( "String-Literal erwartet" ); } - iter.next(); + return text; } - private int parseUsrNum( CharacterIterator iter ) throws PrgException + private void popCodeCreationDisabled() { - Integer usrNum = readNumber( iter ); - if( usrNum == null ) { - throw new PrgException( "Nummer der USR-Funktion erwartet" ); - } - if( (usrNum.intValue() < 0) || (usrNum.intValue() > 9) ) { - throw new PrgException( - "Ung\u00FCltige USR-Funktionsnummer (0...9 erlaubt)" ); - } - return usrNum.intValue(); + setCodeCreationDisabledLevel( this.codeCreationDisabledLevel - 1 ); } - /* - * Die Methode parst eine Variable. - * Der Variablenname wurde zu dem Zeitpunkt bereits gelesen, - * weshalb er uebergeben wird. - */ - private void parseVariableExpr( - CharacterIterator iter, - String varName, - DataType dataType ) throws PrgException + private void pushCodeCreationDisabled() { - SimpleVarInfo varInfo = checkVariable( iter, varName ); - if( varInfo != null ) { - if( varInfo.getDataType() != dataType ) { - varInfo = null; - } - } - if( varInfo == null ) { - if( dataType == DataType.STRING ) { - throw new PrgException( - "String-Variable oder String-Funktion erwartet" ); - } else { - throw new PrgException( - "Numerische Variable oder Funktion erwartet" ); - } - } - varInfo.ensureValueInHL( this.asmOut ); + setCodeCreationDisabledLevel( this.codeCreationDisabledLevel + 1 ); } @@ -6326,176 +4490,16 @@ public void putWarning( String msg ) } - public void putWarning( int sourceLineNum, long basicLineNum, String msg ) - { - appendLineNumMsgToErrLog( sourceLineNum, basicLineNum, msg, "Warnung" ); - } - - - private void putWarningNonAsciiChar( char ch ) - { - - putWarning( - String.format( - "\'%c\': kein ASCII-Zeichen," - + " kann im Zielsystem ein anderes Zeichen sein", - ch ) ); - } - - - private void putWarningOutOfRange() - { - putWarning( "Wert au\u00DFerhalb des Wertebereiches" ); - } - - - /* - * Die Methode prueft, ob der uebergebene Text - * eine einzelne "LD_HL,..."-Anweisung ist. - */ - private boolean isSingleInst_LD_HL_xx( String instText ) - { - boolean rv = false; - if( instText != null ) { - int tabPos = instText.indexOf( '\t' ); - if( tabPos > 0 ) { - instText = instText.substring( tabPos ); - } - if( instText.startsWith( "\tLD\tHL," ) ) { - int pos = instText.indexOf( '\n' ); - if( pos >= 0 ) { - if( pos == (instText.length() - 1) ) { - rv = true; - } - } else { - rv = true; - } - } - } - return rv; - } - - - /* - * Wenn die letzte erzeugte Code-Zeile das Laden - * des HL-Registers mit einem konstanten Wert darstellt, - * wird die Code-Zeile geloescht und der Wert zurueckgeliefert. - */ - private Integer removeLastCodeIfConstExpr( int pos ) - { - Integer rv = null; - String instText = this.asmOut.substring( pos ); - int tabPos = instText.indexOf( '\t' ); - if( tabPos > 0 ) { - pos = tabPos; - instText = instText.substring( tabPos ); - } - if( instText.startsWith( "\tLD\tHL," ) && instText.endsWith( "H\n" ) ) { - try { - int v = Integer.parseInt( - instText.substring( 7, instText.length() - 2 ), - 16 ); - if( (v & 0x8000) != 0 ) { - // negativer Wert - v = -(0x10000 - (v & 0xFFFF)); - } - rv = new Integer( v ); - } - catch( NumberFormatException ex ) {} - } - if( rv != null ) { - this.asmOut.setLength( pos ); - } - return rv; - } - - - private boolean replaceLastCodeFrom_LD_HL_To_BC() - { - return replaceLastCodeFrom_LD_HL_To_RR( "BC" ); - } - - - private boolean replaceLastCodeFrom_LD_HL_To_DE() - { - return replaceLastCodeFrom_LD_HL_To_RR( "DE" ); - } - - - private boolean replaceLastCodeFrom_LD_HL_To_RR( String rr ) - { - boolean rv = false; - int pos = this.asmOut.getLastLinePos(); - String instText = this.asmOut.substring( pos ); - int tabPos = instText.indexOf( '\t' ); - if( tabPos > 0 ) { - pos = tabPos; - instText = instText.substring( tabPos ); - } - if( instText.startsWith( "\tLD\tHL," ) ) { - this.asmOut.replace( pos + 4, pos + 6, rr ); - rv = true; - } else { - // Zugriff auf lokale Variable? - if( (rr.length() == 2) - && (instText.startsWith( "\tLD\tH," ) - || instText.startsWith( "\tLD\tL," )) ) - { - String line2 = this.asmOut.cut( pos ); - String line1 = this.asmOut.cut( this.asmOut.getLastLinePos() ); - if( (line1.indexOf( "\tLD\tH,(IY" ) >= 0) - && (line2.indexOf( "\tLD\tL,(IY" ) >= 0) ) - { - line1 = line1.replace( - "\tLD\tH,(IY", - String.format( "\tLD\t%c,(IY", rr.charAt( 0 ) ) ); - line2 = line2.replace( - "\tLD\tL,(IY", - String.format( "\tLD\t%c,(IY", rr.charAt( 1 ) ) ); - rv = true; - } - else if( (line1.indexOf( "\tLD\tL,(IY" ) >= 0) - && (line2.indexOf( "\tLD\tH,(IY" ) >= 0) ) - { - line1 = line1.replace( - "\tLD\tL,(IY", - String.format( "\tLD\t%c,(IY", rr.charAt( 1 ) ) ); - line2 = line2.replace( - "\tLD\tH,(IY", - String.format( "\tLD\t%c,(IY", rr.charAt( 0 ) ) ); - rv = true; - } - this.asmOut.append( line1 ); - this.asmOut.append( line2 ); - } - } - return rv; - } - - - private void setCodeGenEnabled( boolean state ) + public void putWarning( BasicSourcePos sourcePos, String msg ) { - this.codeGenEnabled = state; - this.asmOut.setEnabled( state ); + appendLineNumMsgToErrLog( sourcePos, msg, "Warnung" ); } - private static char skipSpaces( CharacterIterator iter ) + private void setCodeCreationDisabledLevel( int level ) { - char ch = iter.current(); - while( (ch != CharacterIterator.DONE) && PrgUtil.isWhitespace( ch ) ) { - ch = iter.next(); - } - return ch; - } - - - private static void throw16BitChar( char ch ) throws PrgException - { - throw new PrgException( - String.format( - "\'%c\': 16-Bit-Unicodezeichen nicht erlaubt", - ch ) ); + this.codeCreationDisabledLevel = (level > 0 ? level : 0); + this.asmOut.setEnabled( this.codeCreationDisabledLevel == 0 ); } @@ -6517,25 +4521,6 @@ private static void throwDivisionByZero() throws PrgException } - private static void throwHexDigitExpected() throws PrgException - { - throw new PrgException( "Hexadezimalziffer erwartet" ); - } - - - private static void throwIndexOutOfRange() throws PrgException - { - throw new PrgException( - "Index au\u00DFerhalb des g\u00FCltigen Bereichs" ); - } - - - private static void throwIntExprExpected() throws PrgException - { - throw new PrgException( "Integer-Ausdruck erwartet" ); - } - - private static void throwIOChannelNumOutOfRange() throws PrgException { throw new PrgException( @@ -6543,12 +4528,6 @@ private static void throwIOChannelNumOutOfRange() throws PrgException } - private static void throwStringExprExpected() throws PrgException - { - throw new PrgException( "String-Ausdruck erwartet" ); - } - - private static void throwStringLitOrVarExpected() throws PrgException { throw new PrgException( "String-Literal oder String-Variable erwartet" ); @@ -6561,22 +4540,6 @@ private static void throwStringVarExpected() throws PrgException } - private static void throwUnexpectedChar( char ch ) throws PrgException - { - if( ch == CharacterIterator.DONE ) { - throw new PrgException( "Unerwartetes Ende der Zeile" ); - } - StringBuilder buf = new StringBuilder( 32 ); - if( ch >= '\u0020' ) { - buf.append( (char) '\'' ); - buf.append( ch ); - buf.append( "\': " ); - } - buf.append( "Unerwartetes Zeichen" ); - throw new PrgException( buf.toString() ); - } - - private static void throwVarExpected() throws PrgException { throw new PrgException( "Variable erwartet" ); @@ -6588,4 +4551,3 @@ private static void throwVarNameExpected() throws PrgException throw new PrgException( "Name einer Variable erwartet" ); } } - diff --git a/src/jkcemu/programming/basic/BasicCompilerThread.java b/src/jkcemu/programming/basic/BasicCompilerThread.java index 1392c1f..92a9f7e 100644 --- a/src/jkcemu/programming/basic/BasicCompilerThread.java +++ b/src/jkcemu/programming/basic/BasicCompilerThread.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -45,6 +45,7 @@ public BasicCompilerThread( this.logger = PrgLogger.createLogger( logOut ); this.compiler = new BasicCompiler( editText.getText(), + editText.getFile(), options, this.logger ); } @@ -83,24 +84,75 @@ public void run() Z80Assembler assembler = new Z80Assembler( asmText, "Assembler-Quelltext", + null, this.options, this.logger, true ); status = assembler.assemble( - target.supportsAppName() ? + target.getMaxAppNameLen() > 0 ? this.basicOptions.getAppName() : null, (target instanceof Z9001Target) || (target instanceof Z9001KRTTarget) ); + if( (this.basicOptions.getBssBegAddr() >= 0) + && assembler.getOrgOverlapped() ) + { + appendToLog( "\nProgrammcode und Bereich f\u00FCr" + + " Variablen/Speicherzellen \u00FCberschneiden sich.\n" + + "Bitte w\u00E4hlen Sie eine andere Anfangsadresse f\u00FCr" + + " Variablen/Speicherzellen!" ); + } if( assembler.getRelJumpsTooLong() ) { - appendToLog( "Compilieren Sie bitte mit ausgeschalteter Option" - + " \'Relative Spr\u00FCnge bevorzugen\'" ); + appendToLog( "\nCompilieren Sie bitte mit ausgeschalteter Option" + + " \'Relative Spr\u00FCnge bevorzugen\'!" ); } - if( status - && (this.options.getCodeToEmu() || this.options.getForceRun()) ) - { + if( status ) { byte[] code = assembler.getCreatedCode(); if( code != null ) { - writeCodeToEmu( assembler ); + if( code.length > 0 ) { + int codeBegAddr = this.basicOptions.getCodeBegAddr(); + appendToLog( "Speicherbelegung:\n" ); + appendToLog( String.format( + " %04X-%04X: Programmcode\n", + codeBegAddr, + codeBegAddr + code.length - 1 ) ); + Integer topAddr = assembler.getLabelValue( + BasicCompiler.TOP_LABEL ); + if( topAddr != null ) { + int bssBegAddr = this.basicOptions.getBssBegAddr(); + if( bssBegAddr < 0 ) { + bssBegAddr = this.basicOptions.getCodeBegAddr() + code.length; + } + if( (bssBegAddr >= 0) && (bssBegAddr < topAddr.intValue()) ) { + appendToLog( String.format( + " %04X-%04X: Variablen, Speicherzellen%s\n", + bssBegAddr, + topAddr.intValue() - 1, + this.basicOptions.getStackSize() > 0 ? + ", Stack" : "" ) ); + } + } + if( this.options.getCodeToEmu() + || this.options.getForceRun() ) + { + writeCodeToEmu( assembler, false ); + if( !this.options.getForceRun() && (this.emuThread != null) ) { + EmuSys emuSys = this.emuThread.getEmuSys(); + if( emuSys != null ) { + String startCmd = target.getStartCmd( + emuSys, + this.basicOptions.getAppName(), + this.basicOptions.getCodeBegAddr() ); + if( startCmd != null ) { + if( !startCmd.isEmpty() ) { + appendToLog( "Kommando zum Starten des Programms: " ); + appendToLog( startCmd ); + appendToLog( "\n" ); + } + } + } + } + } + } } } } diff --git a/src/jkcemu/programming/basic/BasicExprParser.java b/src/jkcemu/programming/basic/BasicExprParser.java new file mode 100644 index 0000000..e6c6d20 --- /dev/null +++ b/src/jkcemu/programming/basic/BasicExprParser.java @@ -0,0 +1,1053 @@ +/* + * (c) 2008-2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Parser fuer BASIC-Ausdruecke + */ + +package jkcemu.programming.basic; + +import java.lang.*; +import java.text.CharacterIterator; +import java.util.Arrays; +import jkcemu.programming.PrgException; + + +public class BasicExprParser +{ + public static Integer checkIntConstant( + BasicCompiler compiler, + String name ) + { + Integer rv = null; + if( name != null ) { + boolean status = true; + int value = 0; + if( name.equals( "E_CHANNEL_ALREADY_OPEN" ) ) { + value = BasicLibrary.E_CHANNEL_ALREADY_OPEN; + } else if( name.equals( "E_CHANNEL_CLOSED" ) ) { + value = BasicLibrary.E_CHANNEL_CLOSED; + } else if( name.equals( "E_DEVICE_LOCKED" ) ) { + value = BasicLibrary.E_DEVICE_LOCKED; + } else if( name.equals( "E_DEVICE_NOT_CONFIGURED" ) ) { + value = BasicLibrary.E_DEVICE_NOT_CONFIGURED; + } else if( name.equals( "E_DEVICE_NOT_FOUND" ) ) { + value = BasicLibrary.E_DEVICE_NOT_FOUND; + } else if( name.equals( "E_DISK_FULL" ) ) { + value = BasicLibrary.E_DISK_FULL; + } else if( name.equals( "E_EOF" ) ) { + value = BasicLibrary.E_EOF; + } else if( name.equals( "E_ERROR" ) ) { + value = BasicLibrary.E_ERROR; + } else if( name.equals( "E_FILE_NOT_FOUND" ) ) { + value = BasicLibrary.E_FILE_NOT_FOUND; + } else if( name.equals( "E_INVALID" ) ) { + value = BasicLibrary.E_INVALID; + } else if( name.equals( "E_IO_ERROR" ) ) { + value = BasicLibrary.E_IO_ERROR; + } else if( name.equals( "E_IO_MODE" ) ) { + value = BasicLibrary.E_IO_MODE; + } else if( name.equals( "E_NO_DISK" ) ) { + value = BasicLibrary.E_NO_DISK; + } else if( name.equals( "E_OK" ) ) { + value = BasicLibrary.E_OK; + } else if( name.equals( "E_OVERFLOW" ) ) { + value = BasicLibrary.E_OVERFLOW; + } else if( name.equals( "E_PATH_NOT_FOUND" ) ) { + value = BasicLibrary.E_PATH_NOT_FOUND; + } else if( name.equals( "E_READ_ONLY" ) ) { + value = BasicLibrary.E_READ_ONLY; + } else if( name.equals( "FALSE" ) ) { + value = 0; + } else if( name.equals( "JOYST_BUTTONS" ) ) { + value = (compiler.getTarget().getNamedValue( "JOYST_BUTTON1" ) + | compiler.getTarget().getNamedValue( "JOYST_BUTTON2" )); + } else if( name.equals( "PEN_NONE" ) ) { + value = 0; + } else if( name.equals( "PEN_NORMAL" ) ) { + value = 1; + } else if( name.equals( "PEN_RUBBER" ) ) { + value = 2; + } else if( name.equals( "PEN_XOR" ) ) { + value = 3; + } else if( name.equals( "TRUE" ) ) { + value = 0xFFFF; + } else if( AbstractTarget.isReservedWord( name ) ) { + value = compiler.getTarget().getNamedValue( name ); + } else { + status = false; + } + if( status ) { + rv = new Integer( value ); + } + } + return rv; + } + + + /* + * Diese Methode prueft, ob ein Ausdruck einen konstanten Wert hat. + * Wenn ja, ist der Ausdruck nach Rueckkehr aus der Methode geparst. + * + * In dieser Methode wird nicht der gesamte Syntaxbaum abgebildet werden. + * Es reicht, nur die Elemente zu pruefen, + * die sinnvollerweise vorkommen koennen. + * Das sind: + * Zahlen-Literal + * Konstante + * Vergleich einer Konstante mit einer Zahl oder einer anderen Konstante + * + * Wenn andere Faelle, die hier nicht erkannt werden, + * auch einen konstanten Wert zurueckliefern sollten, + * wird dafuer normaler Programmcode erzeugt. + */ + public static Integer checkParseConstExpr( + BasicCompiler compiler, + CharacterIterator iter ) + { + Integer rv = null; + int begPos = iter.getIndex(); + try { + int value = parseConstNotExpr( compiler, iter ); + for(;;) { + if( BasicUtil.checkKeyword( iter, "AND" ) ) { + rv &= parseConstNotExpr( compiler, iter ); + } else if( BasicUtil.checkKeyword( iter, "OR" ) ) { + rv |= parseConstNotExpr( compiler, iter ); + } else if( BasicUtil.checkKeyword( iter, "XOR" ) ) { + rv ^= parseConstNotExpr( compiler, iter ); + } else { + break; + } + value &= 0xFFFF; + } + rv = new Integer( value ); + } + catch( PrgException ex ) { + rv = null; + } + finally { + if( rv == null ) { + iter.setIndex( begPos ); + } + } + return rv; + } + + + public static boolean checkParseStringPrimExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + boolean rv = false; + String text = BasicUtil.checkStringLiteral( compiler, iter ); + if( text != null ) { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + asmOut.append( "\tLD\tHL," ); + asmOut.append( compiler.getStringLiteralLabel( text ) ); + asmOut.newLine(); + rv = true; + } else { + rv = checkParseStringPrimVarExpr( compiler, iter ); + } + return rv; + } + + + public static boolean checkParseStringPrimVarExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + boolean rv = false; + int begPos = iter.getIndex(); + String name = BasicUtil.checkIdentifier( iter ); + if( name != null ) { + rv = BasicFuncParser.checkParseStringFunction( compiler, iter, name ); + if( !rv ) { + if( name.endsWith( "$" ) ) { + CallableEntry entry = compiler.getCallableEntry( name ); + if( entry != null ) { + if( entry instanceof FunctionEntry ) { + if( ((FunctionEntry) entry).getReturnType() + != BasicCompiler.DataType.STRING ) + { + BasicUtil.throwStringExprExpected(); + } + compiler.parseCallableCall( iter, entry ); + } else { + throw new PrgException( + "Aufruf einer Prozedur an der Stelle nicht erlaubt" ); + } + } else { + BasicExprParser.parseVariableExpr( + compiler, + iter, + name, + BasicCompiler.DataType.STRING ); + } + rv = true; + } + } + } + if( !rv ) { + iter.setIndex( begPos ); + } + return rv; + } + + + public static void parse2ArgsTo_DE_HL( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + int pos = asmOut.length(); + BasicExprParser.parseExpr( compiler, iter ); + BasicUtil.parseToken( iter, ',' ); + Integer v1 = BasicUtil.removeLastCodeIfConstExpr( compiler, pos ); + if( v1 != null ) { + BasicExprParser.parseExpr( compiler, iter ); + asmOut.append_LD_DE_nn( v1.intValue() ); + } else { + String oldCode1 = asmOut.cut( pos ); + String newCode1 = BasicUtil.convertCodeToValueInDE( oldCode1 ); + BasicExprParser.parseExpr( compiler, iter ); + String code2 = asmOut.cut( pos ); + if( BasicUtil.isOnly_LD_HL_xx( code2 ) ) { + if( newCode1 != null ) { + asmOut.append( newCode1 ); + } else { + asmOut.append( oldCode1 ); + asmOut.append( "\tEX\tDE,HL\n" ); + } + asmOut.append( code2 ); + } else { + asmOut.append( oldCode1 ); + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( code2 ); + asmOut.append( "\tPOP\tDE\n" ); + } + } + } + + + public static void parseEnclosedExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicUtil.parseToken( iter, '(' ); + parseExpr( compiler, iter ); + BasicUtil.parseToken( iter, ')' ); + } + + + public static void parseExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + parseNotExpr( compiler, iter ); + for(;;) { + if( BasicUtil.checkKeyword( iter, "AND" ) ) { + int pos = asmOut.length(); + parseNotExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" ); + } + asmOut.append( "\tCALL\tO_AND\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_AND ); + } else if( BasicUtil.checkKeyword( iter, "OR" ) ) { + int pos = asmOut.length(); + parseNotExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + if( !oldCode.equals( "\tLD\tHL,0000H\n" ) ) { + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" ); + } + asmOut.append( "\tCALL\tO_OR\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_OR ); + } + } else if( BasicUtil.checkKeyword( iter, "XOR" ) ) { + int pos = asmOut.length(); + parseNotExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" ); + } + asmOut.append( "\tCALL\tO_XOR\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_XOR ); + } else { + break; + } + } + } + + + public static void parseStringPrimExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + String text = BasicUtil.checkStringLiteral( compiler, iter ); + if( text != null ) { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + String label = compiler.getStringLiteralLabel( text ); + asmOut.append( "\tLD\tHL," ); + asmOut.append( label ); + asmOut.newLine(); + } else { + if( !checkParseStringPrimVarExpr( compiler, iter ) ) { + BasicUtil.throwStringExprExpected(); + } + } + } + + + /* + * Die Methode parst eine Variable. + * Der Variablenname wurde zu dem Zeitpunkt bereits gelesen, + * weshalb er uebergeben wird. + */ + public static void parseVariableExpr( + BasicCompiler compiler, + CharacterIterator iter, + String varName, + BasicCompiler.DataType dataType ) throws PrgException + { + SimpleVarInfo varInfo = compiler.checkVariable( iter, varName ); + if( varInfo != null ) { + if( varInfo.getDataType() != dataType ) { + varInfo = null; + } + } + if( varInfo == null ) { + if( dataType == BasicCompiler.DataType.STRING ) { + throw new PrgException( + "String-Variable oder String-Funktion erwartet" ); + } else { + throw new PrgException( + "Numerische Variable oder Funktion erwartet" ); + } + } + varInfo.ensureValueInHL( compiler.getCodeBuf() ); + } + + + /* --- private Methoden --- */ + + private static void check8BitChar( char ch ) throws PrgException + { + if( (ch == 0) || (ch > 0xFF) ) { + throw new PrgException( "Zeichen \'" + ch + + "\' au\u00DFerhalb des 8-Bit-Wertebereiches" ); + } + } + + + private static boolean checkAppend_LD_HL_Constant( + BasicCompiler compiler, + String name ) + { + boolean rv = false; + if( name != null ) { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + if( name.equals( "TOP" ) ) { + rv = true; + asmOut.append( "\tLD\tHL,MTOP\n" ); + compiler.addLibItem( BasicLibrary.LibItem.MTOP ); + } else { + Integer value = BasicExprParser.checkIntConstant( compiler, name ); + if( value != null ) { + asmOut.append_LD_HL_nn( value.intValue() ); + rv = true; + } + } + } + return rv; + } + + + private static Integer checkIntLiteral( + BasicCompiler compiler, + CharacterIterator iter, + boolean enableWarnings ) + throws PrgException + + { + Integer rv = null; + char ch = BasicUtil.skipSpaces( iter ); + if( (ch >= '0') && (ch <= '9') ) { + rv = BasicUtil.readNumber( iter ); + if( rv == null ) { + BasicUtil.throwNumberExpected(); + } + } + else if( ch == '&' ) { + ch = iter.next(); + if( (ch == 'B') || (ch == 'b') ) { + ch = iter.next(); + if( (ch == '0') || (ch == '1') ) { + int value = 0; + while( (ch == '0') || (ch == '1') ) { + value <<= 1; + if( ch == '1' ) { + value |= 1; + } + ch = iter.next(); + } + rv = new Integer( value ); + } else { + throw new PrgException( "0 oder 1 erwartet" ); + } + } else if( (ch == 'H') || (ch == 'h') ) { + iter.next(); + rv = BasicUtil.readHex( iter ); + if( rv == null ) { + BasicUtil.throwHexDigitExpected(); + } + } else { + throw new PrgException( "B oder H erwartet" ); + } + } + else if( ch == '\'' ) { + ch = iter.next(); + iter.next(); + if( enableWarnings + && compiler.getBasicOptions().getWarnNonAsciiChars() + && ((ch < '\u0020') || (ch > '\u007F')) ) + { + compiler.putWarningNonAsciiChar( ch ); + } + BasicUtil.parseToken( iter, '\'' ); + check8BitChar( ch ); + rv = new Integer( ch ); + } + return rv; + } + + + private static int parseConstNotExpr( + BasicCompiler compiler, + CharacterIterator iter ) + throws PrgException + { + int rv = 0; + if( BasicUtil.checkKeyword( iter, "NOT" ) ) { + rv = (~parseConstCondExpr( compiler, iter ) & 0xFFFF); + } else { + rv = parseConstCondExpr( compiler, iter ); + } + return rv; + } + + + private static int parseConstCondExpr( + BasicCompiler compiler, + CharacterIterator iter ) + throws PrgException + { + int rv = parseConstShiftExpr( compiler, iter ); + char ch = BasicUtil.skipSpaces( iter ); + if( ch == '<' ) { + ch = iter.next(); + if( ch == '=' ) { + iter.next(); + rv = (rv <= parseConstShiftExpr( compiler, iter ) ? -1 : 0); + } else if( ch == '>' ) { + iter.next(); + rv = (rv != parseConstShiftExpr( compiler, iter ) ? -1 : 0); + } else { + rv = (rv < parseConstShiftExpr( compiler, iter ) ? -1 : 0); + } + } + else if( ch == '=' ) { + iter.next(); + rv = (rv == parseConstShiftExpr( compiler, iter ) ? -1 : 0); + } + else if( ch == '>' ) { + ch = iter.next(); + if( ch == '=' ) { + iter.next(); + rv = (rv >= parseConstShiftExpr( compiler, iter ) ? -1 : 0); + } else { + rv = (rv > parseConstShiftExpr( compiler, iter ) ? -1 : 0); + } + } + return rv & 0xFFFF; + } + + + private static int parseConstShiftExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + /* + * Der Einfachheit halber wird hier nicht der ganze Syntax-Baum + * abgebildet, sondern nur die sinnvollen Faelle. + * Das sind an dieser Stelle: + * Zahlen-Literal + * Konstante + */ + Integer value = checkIntLiteral( compiler, iter, false ); + if( value == null ) { + String name = BasicUtil.checkIdentifier( iter ); + if( name != null ) { + value = checkIntConstant( compiler, name ); + } + } + if( value == null ) { + throwNoConstExpr(); + } + return value.intValue(); + } + + + private static void parseNotExpr( + BasicCompiler compiler, + CharacterIterator iter ) + throws PrgException + { + if( BasicUtil.checkKeyword( iter, "NOT" ) ) { + parseCondExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tCALL\tO_NOT\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_NOT ); + } else { + parseCondExpr( compiler, iter ); + } + } + + + private static void parseCondExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + if( checkParseStringPrimExpr( compiler, iter ) ) { + + // String-Vergleich + char ch = BasicUtil.skipSpaces( iter ); + if( ch == '<' ) { + ch = iter.next(); + if( ch == '=' ) { + iter.next(); + asmOut.append( "\tPUSH\tHL\n" ); + parseStringPrimExpr( compiler, iter ); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tO_STLE\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_STLE ); + } else if( ch == '>' ) { + iter.next(); + asmOut.append( "\tPUSH\tHL\n" ); + parseStringPrimExpr( compiler, iter ); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tO_STNE\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_STNE ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + parseStringPrimExpr( compiler, iter ); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tO_STLT\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_STLT ); + } + } else if( ch == '=' ) { + iter.next(); + asmOut.append( "\tPUSH\tHL\n" ); + parseStringPrimExpr( compiler, iter ); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tO_STEQ\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_STEQ ); + } else if( ch == '>' ) { + ch = iter.next(); + if( ch == '=' ) { + iter.next(); + asmOut.append( "\tPUSH\tHL\n" ); + parseStringPrimExpr( compiler, iter ); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tO_STGE\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_STGE ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + parseStringPrimExpr( compiler, iter ); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tO_STGT\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_STGT ); + } + } else { + throwCondOpExpected(); + } + + } else { + + // prufen auf numerischen Vergleich + parseShiftExpr( compiler, iter ); + char ch = BasicUtil.skipSpaces( iter ); + if( ch == '<' ) { + ch = iter.next(); + if( ch == '=' ) { + iter.next(); + int pos = asmOut.length(); + parseShiftExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + asmOut.append( "\tCALL\tO_LE\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_LE ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tO_GE\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_GE ); + } + } else if( ch == '>' ) { + iter.next(); + int pos = asmOut.length(); + parseShiftExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" ); + } + asmOut.append( "\tCALL\tO_NE\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_NE ); + } else { + int pos = asmOut.length(); + parseShiftExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + asmOut.append( "\tCALL\tO_LT\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_LT ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tO_GT\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_GT ); + } + } + } + else if( ch == '=' ) { + iter.next(); + int pos = asmOut.length(); + parseShiftExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" ); + } + asmOut.append( "\tCALL\tO_EQ\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_EQ ); + } + else if( ch == '>' ) { + ch = iter.next(); + if( ch == '=' ) { + iter.next(); + int pos = asmOut.length(); + parseShiftExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + asmOut.append( "\tCALL\tO_GE\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_GE ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tO_LE\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_LE ); + } + } else { + int pos = asmOut.length(); + parseShiftExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + asmOut.append( "\tCALL\tO_GT\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_GT ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tO_LT\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_LT ); + } + } + } + } + } + + + private static void parseShiftExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + parseAddExpr( compiler, iter ); + for(;;) { + if( BasicUtil.checkKeyword( iter, "SHL" ) ) { + int pos = asmOut.length(); + parseAddExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { + asmOut.append( "\tSLA\tL\n" + + "\tRL\tH\n" ); + } else { + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tEX\tDE,HL\n" + + "\tPOP\tHL\n" ); + } + asmOut.append( "\tCALL\tO_SHL\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_SHL ); + } + } else if( BasicUtil.checkKeyword( iter, "SHR" ) ) { + int pos = asmOut.length(); + parseAddExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { + asmOut.append( "\tSRL\tH\n" + + "\tRR\tL\n" ); + } else { + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tEX\tDE,HL\n" + + "\tPOP\tHL\n" ); + } + asmOut.append( "\tCALL\tO_SHR\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_SHR ); + } + } else { + break; + } + } + } + + + private static void parseAddExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + parseMulExpr( compiler, iter ); + for(;;) { + char ch = BasicUtil.skipSpaces( iter ); + if( ch == '+' ) { + iter.next(); + int pos = asmOut.length(); + parseMulExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + if( !oldCode.equals( "\tLD\tHL,0000H\n" ) ) { + if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { + asmOut.append( "\tCALL\tO_INC\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_INC ); + } else if( oldCode.equals( "\tLD\tHL,0FFFFH\n" ) ) { + asmOut.append( "\tCALL\tO_DEC\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_DEC ); + } else { + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" ); + } + asmOut.append( "\tCALL\tO_ADD\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_ADD ); + } + } + } else if( ch == '-' ) { + iter.next(); + int pos = asmOut.length(); + parseMulExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + if( !oldCode.equals( "\tLD\tHL,0000H\n" ) ) { + if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { + asmOut.append( "\tCALL\tO_DEC\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_DEC ); + } else if( oldCode.equals( "\tLD\tHL,0FFFFH\n" ) ) { + asmOut.append( "\tCALL\tO_INC\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_INC ); + } else { + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" + + "\tEX\tDE,HL\n" ); + } + asmOut.append( "\tCALL\tO_SUB\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_SUB ); + } + } + } else if( BasicUtil.checkKeyword( iter, "ADD" ) ) { + int pos = asmOut.length(); + parseMulExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + if( !oldCode.equals( "\tLD\tHL,0000H\n" ) ) { + if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { + asmOut.append( "\tINC\tHL\n" ); + } else if( oldCode.equals( "\tLD\tHL,0FFFFH\n" ) ) { + asmOut.append( "\tDEC\tHL\n" ); + } else { + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" ); + } + asmOut.append( "\tADD\tHL,DE\n" ); + } + } + } else if( BasicUtil.checkKeyword( iter, "SUB" ) ) { + int pos = asmOut.length(); + parseMulExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + if( !oldCode.equals( "\tLD\tHL,0000H\n" ) ) { + if( oldCode.equals( "\tLD\tHL,0001H\n" ) ) { + asmOut.append( "\tDEC\tHL\n" ); + } else if( oldCode.equals( "\tLD\tHL,0FFFFH\n" ) ) { + asmOut.append( "\tINC\tHL\n" ); + } else { + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" + + "\tEX\tDE,HL\n" ); + } + asmOut.append( "\tOR\tA\n" + + "\tSBC\tHL,DE\n" ); + } + } + } else { + break; + } + } + } + + + private static void parseMulExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + parseUnaryExpr( compiler, iter ); + for(;;) { + char ch = BasicUtil.skipSpaces( iter ); + if( ch == '*' ) { + iter.next(); + int pos = asmOut.length(); + parseUnaryExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" ); + } + asmOut.append( "\tCALL\tO_MUL\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_MUL ); + } else if( ch == '/' ) { + iter.next(); + int pos = asmOut.length(); + parseUnaryExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" + + "\tEX\tDE,HL\n" ); + } + asmOut.append( "\tCALL\tO_DIV\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_DIV ); + } else if( BasicUtil.checkKeyword( iter, "MOD" ) ) { + int pos = asmOut.length(); + parseUnaryExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tPOP\tDE\n" + + "\tEX\tDE,HL\n" ); + } + asmOut.append( "\tCALL\tO_MOD\n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_MOD ); + } else { + break; + } + } + } + + + private static void parseUnaryExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + char ch = BasicUtil.skipSpaces( iter ); + if( ch == '-' ) { + iter.next(); + AsmCodeBuf asmOut = compiler.getCodeBuf(); + Integer value = BasicUtil.readNumber( iter ); + if( value != null ) { + asmOut.append_LD_HL_nn( -value.intValue() ); + } else { + parsePrimExpr( compiler, iter ); + asmOut.append( "\tCALL\tNEGHL\n" ); + compiler.addLibItem( BasicLibrary.LibItem.ABS_NEG_HL ); + } + } else { + if( ch == '+' ) { + iter.next(); + } + parsePrimExpr( compiler, iter ); + } + } + + + private static void parsePrimExpr( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + char ch = BasicUtil.skipSpaces( iter ); + if( ch == '(' ) { + iter.next(); + parseExpr( compiler, iter ); + BasicUtil.parseToken( iter, ')' ); + } else { + Integer value = checkIntLiteral( compiler, iter, true ); + if( value != null ) { + asmOut.append_LD_HL_nn( value.intValue() ); + } else { + String name = BasicUtil.checkIdentifier( iter ); + if( name != null ) { + // auf Konstante pruefen + if( !checkAppend_LD_HL_Constant( compiler, name ) ) { + // auf Systemvariable pruefen + AbstractTarget target = compiler.getTarget(); + if( name.equals( "ERR" ) ) { + asmOut.append( "\tLD\tHL,(M_ERN)\n" ); + compiler.addLibItem( BasicLibrary.LibItem.M_ERN ); + } else if( name.equals( "H_CHAR" ) ) { + target.appendHCharTo( asmOut ); + } else if( name.equals( "H_PIXEL" ) ) { + target.appendHPixelTo( asmOut ); + } else if( name.equals( "W_CHAR" ) ) { + target.appendWCharTo( asmOut ); + } else if( name.equals( "W_PIXEL" ) ) { + target.appendWPixelTo( asmOut ); + } else if( name.equals( "XPOS" ) ) { + asmOut.append( "\tLD\tHL,(M_XPOS)\n" ); + compiler.addLibItem( BasicLibrary.LibItem.M_XYPO ); + } else if( name.equals( "YPOS" ) ) { + asmOut.append( "\tLD\tHL,(M_YPOS)\n" ); + compiler.addLibItem( BasicLibrary.LibItem.M_XYPO ); + } else { + // auf Systemfunktion pruefen + if( !BasicFuncParser.checkParseIntFunction( + compiler, iter, name ) ) + { + // auf benutzerdefinierte Funktion pruefen + CallableEntry entry = compiler.getCallableEntry( name ); + if( entry != null ) { + if( entry instanceof FunctionEntry ) { + if( ((FunctionEntry) entry).getReturnType() + != BasicCompiler.DataType.INTEGER ) + { + throwIntExprExpected(); + } + compiler.parseCallableCall( iter, entry ); + } else { + throw new PrgException( + "Aufruf einer Prozedur an der Stelle nicht erlaubt" ); + } + } else { + if( name.endsWith( "$" ) ) { + throwIntExprExpected(); + } + parseVariableExpr( + compiler, + iter, + name, + BasicCompiler.DataType.INTEGER ); + } + } + } + } + } else { + BasicUtil.throwUnexpectedChar( BasicUtil.skipSpaces( iter ) ); + } + } + } + } + + + private static void throwCondOpExpected() throws PrgException + { + throw new PrgException( "Vergleichsoperator erwartet" ); + } + + + private static void throwIntExprExpected() throws PrgException + { + throw new PrgException( "Integer-Ausdruck erwartet" ); + } + + + private static void throwNoConstExpr() throws PrgException + { + throw new PrgException( "Kein konstanter Ausdruck" ); + } +} diff --git a/src/jkcemu/programming/basic/BasicFuncParser.java b/src/jkcemu/programming/basic/BasicFuncParser.java new file mode 100644 index 0000000..2fabcd7 --- /dev/null +++ b/src/jkcemu/programming/basic/BasicFuncParser.java @@ -0,0 +1,1069 @@ +/* + * (c) 2008-2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * Parser fuer BASIC-Ausdruecke + */ + +package jkcemu.programming.basic; + +import java.lang.*; +import java.text.CharacterIterator; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; +import jkcemu.programming.PrgException; + + +public class BasicFuncParser +{ + public static boolean checkParseIntFunction( + BasicCompiler compiler, + CharacterIterator iter, + String name ) throws PrgException + { + boolean rv = false; + if( name != null ) { + rv = true; + if( name.equals( "ABS" ) ) { + parseABS( compiler, iter ); + } else if( name.equals( "ASC" ) ) { + parseASC( compiler, iter ); + } else if( name.equals( "ASM" ) ) { + compiler.parseASM( iter, true ); + } else if( name.equals( "DEEK" ) ) { + parseDEEK( compiler, iter ); + } else if( name.equals( "EOF" ) ) { + parseEOF( compiler, iter ); + } else if( name.equals( "HIBYTE" ) ) { + parseHIBYTE( compiler, iter ); + } else if( name.equals( "IN" ) || name.equals( "INP" ) ) { + parseIN( compiler, iter ); + } else if( name.equals( "INSTR" ) ) { + parseINSTR( compiler, iter ); + } else if( name.equals( "IS_TARGET" ) ) { + parseIS_TARGET( compiler, iter ); + } else if( name.equals( "JOYST" ) ) { + parseJOYST( compiler, iter ); + } else if( name.equals( "LEN" ) ) { + parseLEN( compiler, iter ); + } else if( name.equals( "LOBYTE" ) ) { + parseLOBYTE( compiler, iter ); + } else if( name.equals( "MAX" ) ) { + parseMAX( compiler, iter ); + } else if( name.equals( "MIN" ) ) { + parseMIN( compiler, iter ); + } else if( name.equals( "PEEK" ) ) { + parsePEEK( compiler, iter ); + } else if( name.equals( "POINT" ) ) { + parsePOINT( compiler, iter ); + } else if( name.equals( "PTEST" ) ) { + parsePTEST( compiler, iter ); + } else if( name.equals( "RND" ) ) { + parseRND( compiler, iter ); + } else if( name.equals( "SGN" ) ) { + parseSGN( compiler, iter ); + } else if( name.equals( "SQR" ) ) { + parseSQR( compiler, iter ); + } else if( name.equals( "USR" ) ) { + parseUSR( compiler, iter ); + } else if( name.equals( "USR0" ) ) { + parseUSR( compiler, iter, 0 ); + } else if( name.equals( "USR1" ) ) { + parseUSR( compiler, iter, 1 ); + } else if( name.equals( "USR2" ) ) { + parseUSR( compiler, iter, 2 ); + } else if( name.equals( "USR3" ) ) { + parseUSR( compiler, iter, 3 ); + } else if( name.equals( "USR4" ) ) { + parseUSR( compiler, iter, 4 ); + } else if( name.equals( "USR5" ) ) { + parseUSR( compiler, iter, 5 ); + } else if( name.equals( "USR6" ) ) { + parseUSR( compiler, iter, 6 ); + } else if( name.equals( "USR7" ) ) { + parseUSR( compiler, iter, 7 ); + } else if( name.equals( "USR8" ) ) { + parseUSR( compiler, iter, 8 ); + } else if( name.equals( "USR9" ) ) { + parseUSR( compiler, iter, 9 ); + } else if( name.equals( "VAL" ) ) { + parseVAL( compiler, iter ); + } else { + rv = false; + } + } + return rv; + } + + + public static boolean checkParseStringFunction( + BasicCompiler compiler, + CharacterIterator iter, + String name ) throws PrgException + { + boolean rv = false; + if( name != null ) { + rv = true; + if( name.equals( "BIN$" ) ) { + parseStrBIN( compiler, iter ); + } else if( name.equals( "CHR$" ) ) { + parseStrCHR( compiler, iter ); + } else if( name.equals( "ERR$" ) ) { + parseStrERR( compiler, iter ); + } else if( name.equals( "HEX$" ) ) { + parseStrHEX( compiler, iter ); + } else if( name.equals( "INKEY$" ) ) { + parseStrINKEY( compiler, iter ); + } else if( name.equals( "INPUT$" ) ) { + parseStrINPUT( compiler, iter ); + } else if( name.equals( "LEFT$" ) ) { + parseStrLEFT( compiler, iter ); + } else if( name.equals( "LOWER$" ) || name.equals( "LCASE$" ) ) { + parseStrLOWER( compiler, iter ); + } else if( name.equals( "LTRIM$" ) ) { + parseStrLTRIM( compiler, iter ); + } else if( name.equals( "MEMSTR$" ) ) { + parseStrMEMSTR( compiler, iter ); + } else if( name.equals( "MID$" ) ) { + parseStrMID( compiler, iter ); + } else if( name.equals( "MIRROR$" ) ) { + parseStrMIRROR( compiler, iter ); + } else if( name.equals( "RIGHT$" ) ) { + parseStrRIGHT( compiler, iter ); + } else if( name.equals( "RTRIM$" ) ) { + parseStrRTRIM( compiler, iter ); + } else if( name.equals( "SPACE$" ) ) { + parseStrSPACE( compiler, iter ); + } else if( name.equals( "STR$" ) ) { + parseStrSTR( compiler, iter ); + } else if( name.equals( "STRING$" ) ) { + parseStrSTRING( compiler, iter ); + } else if( name.equals( "TRIM$" ) ) { + parseStrTRIM( compiler, iter ); + } else if( name.equals( "UPPER$" ) || name.equals( "UCASE$" ) ) { + parseStrUPPER( compiler, iter ); + } else { + rv = false; + } + } + return rv; + } + + + /* --- private Methoden --- */ + + private static void parseABS( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicExprParser.parseEnclosedExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tCALL\tABSHL\n" ); + compiler.addLibItem( BasicLibrary.LibItem.ABS_NEG_HL ); + } + + + private static void parseASC( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseStringPrimExpr( compiler, iter ); + BasicUtil.parseToken( iter, ')' ); + compiler.getCodeBuf().append( "\tLD\tL,(HL)\n" + + "\tLD\tH,00H\n" ); + } + + + private static void parseDEEK( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + int pos = asmOut.length(); + BasicExprParser.parseEnclosedExpr( compiler, iter ); + Integer addr = BasicUtil.removeLastCodeIfConstExpr( compiler, pos ); + if( addr != null ) { + asmOut.append( "\tLD\tHL,(" ); + asmOut.appendHex4( addr.intValue() ); + asmOut.append( ")\n" ); + } else { + asmOut.append( "\tLD\tA,(HL)\n" + + "\tINC\tHL\n" + + "\tLD\tH,(HL)\n" + + "\tLD\tL,A\n" ); + } + } + + + private static void parseEOF( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicUtil.parseToken( iter, '(' ); + BasicUtil.checkToken( iter, '#' ); + compiler.parseIOChannelNumToPtrFldAddrInHL( iter, null ); + compiler.getCodeBuf().append( "\tCALL\tIOEOF\n" ); + BasicUtil.parseToken( iter, ')' ); + compiler.addLibItem( BasicLibrary.LibItem.IOEOF ); + } + + + private static void parseHIBYTE( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicExprParser.parseEnclosedExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tLD\tL,H\n" + + "\tLD\tH,00H\n" ); + } + + + private static void parseIN( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseExpr( compiler, iter ); + BasicUtil.parseToken( iter, ')' ); + if( !BasicUtil.replaceLastCodeFrom_LD_HL_To_BC( compiler ) ) { + asmOut.append( "\tLD\tB,H\n" + + "\tLD\tC,L\n" ); + } + asmOut.append( "\tIN\tL,(C)\n" + + "\tLD\tH,0\n" ); + } + + + private static void parseINSTR( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + boolean hasStartPos = false; + Integer startPosValue = null; + String startPosText = null; + BasicUtil.parseToken( iter, '(' ); + String text = BasicUtil.checkStringLiteral( compiler, iter ); + if( text == null ) { + hasStartPos = !BasicExprParser.checkParseStringPrimExpr( + compiler, iter ); + } + if( hasStartPos ) { + int pos = asmOut.length(); + BasicExprParser.parseExpr( compiler, iter ); + startPosValue = BasicUtil.removeLastCodeIfConstExpr( compiler, pos ); + if( startPosValue != null ) { + if( startPosValue.intValue() <= 0 ) { + BasicUtil.throwIndexOutOfRange(); + } + } else { + String oldCode = asmOut.cut( pos ); + startPosText = BasicUtil.convertCodeToValueInBC( oldCode ); + if( startPosText == null ) { + asmOut.append( oldCode ); + asmOut.append( "\tPUSH\tHL\n" ); + } + } + BasicUtil.parseToken( iter, ',' ); + text = BasicUtil.checkStringLiteral( compiler, iter ); + if( text == null ) { + BasicExprParser.parseStringPrimExpr( compiler, iter ); + } + } + BasicUtil.parseToken( iter, ',' ); + String pattern = BasicUtil.checkStringLiteral( compiler, iter ); + if( pattern != null ) { + if( text != null ) { + asmOut.append( "\tLD\tHL," ); + asmOut.append( compiler.getStringLiteralLabel( text ) ); + asmOut.newLine(); + } + asmOut.append( "\tLD\tDE," ); + asmOut.append( compiler.getStringLiteralLabel( pattern ) ); + asmOut.newLine(); + } else { + if( text == null ) { + asmOut.append( "\tPUSH\tHL\n" ); + } + BasicExprParser.parseStringPrimExpr( compiler, iter ); + asmOut.append( "\tEX\tDE,HL\n" ); + if( text != null ) { + asmOut.append( "\tLD\tHL," ); + asmOut.append( compiler.getStringLiteralLabel( text ) ); + asmOut.newLine(); + } else { + asmOut.append( "\tPOP\tHL\n" ); + } + } + if( hasStartPos ) { + if( startPosValue != null ) { + asmOut.append_LD_BC_nn( startPosValue.intValue() ); + } else if( startPosText != null ) { + asmOut.append( startPosText ); + } else { + asmOut.append( "\tPOP\tBC\n" ); + } + asmOut.append( "\tCALL\tF_INSTRN\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_INSTRN ); + } else { + asmOut.append( "\tCALL\tF_INSTR\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_INSTR ); + } + BasicUtil.parseToken( iter, ')' ); + } + + + private static void parseIS_TARGET( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + int[] targetIDs = compiler.getTarget().getTargetIDs(); + AsmCodeBuf asmOut = compiler.getCodeBuf(); + int pos = asmOut.length(); + BasicExprParser.parseEnclosedExpr( compiler, iter ); + Integer value = BasicUtil.removeLastCodeIfConstExpr( compiler, pos ); + if( value != null ) { + int rv = 0; + if( targetIDs != null ) { + for( int targetID : targetIDs ) { + if( targetID == value.intValue() ) { + rv = -1; + break; + } + } + } + asmOut.append_LD_HL_nn( rv ); + } else { + if( targetIDs.length == 1 ) { + asmOut.append_LD_DE_nn( targetIDs[ 0 ] ); + asmOut.append( "\tCALL\tO_EQ \n" ); + compiler.addLibItem( BasicLibrary.LibItem.O_EQ ); + } else if( targetIDs.length > 1 ) { + asmOut.append( "\tCALL\tF_IS_TARGET\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_IS_TARGET ); + } + } + } + + + private static void parseJOYST( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicExprParser.parseEnclosedExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tCALL\tF_JOY\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_JOY ); + } + + + private static void parseLEN( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + BasicUtil.parseToken( iter, '(' ); + String text = BasicUtil.checkStringLiteral( compiler, iter ); + if( text != null ) { + asmOut.append_LD_HL_nn( text.length() ); + } else { + BasicExprParser.parseStringPrimExpr( compiler, iter ); + asmOut.append( "\tCALL\tF_LEN\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_LEN ); + } + BasicUtil.parseToken( iter, ')' ); + } + + + private static void parseLOBYTE( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicExprParser.parseEnclosedExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tLD\tH,00H\n" ); + } + + + private static void parseMAX( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseExpr( compiler, iter ); + while( BasicUtil.checkToken( iter, ',' ) ) { + asmOut.append( "\tPUSH\tHL\n" ); + BasicExprParser.parseExpr( compiler, iter ); + String label = compiler.nextLabel(); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tCPHLDE\n" + + "\tJR\tNC," ); + asmOut.append( label ); + asmOut.append( "\n" + + "\tEX\tDE,HL\n" ); + asmOut.append( label ); + asmOut.append( ":\n" ); + } + BasicUtil.parseToken( iter, ')' ); + compiler.addLibItem( BasicLibrary.LibItem.CPHLDE ); + } + + + private static void parseMIN( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseExpr( compiler, iter ); + while( BasicUtil.checkToken( iter, ',' ) ) { + asmOut.append( "\tPUSH\tHL\n" ); + BasicExprParser.parseExpr( compiler, iter ); + String label = compiler.nextLabel(); + asmOut.append( "\tPOP\tDE\n" + + "\tCALL\tCPHLDE\n" + + "\tJR\tC," ); + asmOut.append( label ); + asmOut.append( "\n" + + "\tEX\tDE,HL\n" ); + asmOut.append( label ); + asmOut.append( ":\n" ); + } + BasicUtil.parseToken( iter, ')' ); + compiler.addLibItem( BasicLibrary.LibItem.CPHLDE ); + } + + + private static void parsePEEK( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicExprParser.parseEnclosedExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tLD\tL,(HL)\n" + + "\tLD\tH,0\n" ); + } + + + private static void parseRND( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicExprParser.parseEnclosedExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tCALL\tF_RND\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_RND ); + } + + + private static void parsePOINT( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parse2ArgsTo_DE_HL( compiler, iter ); + BasicUtil.parseToken( iter, ')' ); + compiler.getCodeBuf().append( "\tCALL\tXPOINT\n" ); + compiler.addLibItem( BasicLibrary.LibItem.XPOINT ); + } + + + private static void parsePTEST( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parse2ArgsTo_DE_HL( compiler, iter ); + BasicUtil.parseToken( iter, ')' ); + compiler.getCodeBuf().append( "\tCALL\tXPTEST\n" ); + compiler.addLibItem( BasicLibrary.LibItem.XPTEST ); + } + + + private static void parseSGN( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicExprParser.parseEnclosedExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tCALL\tF_SGN\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_SGN ); + } + + + private static void parseSQR( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + int pos = asmOut.length(); + BasicExprParser.parseEnclosedExpr( compiler, iter ); + Integer value = BasicUtil.removeLastCodeIfConstExpr( compiler, pos ); + if( value != null ) { + if( value.intValue() < 0 ) { + throw new PrgException( + "Wurzel aus negativer Zahl nicht m\u00F6glich" ); + } + asmOut.append_LD_HL_nn( + (int) Math.floor( Math.sqrt( value.doubleValue() ) ) ); + } else { + asmOut.append( "\tCALL\tF_SQR\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_SQR ); + } + } + + + private static void parseUSR( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + int usrNum = BasicUtil.parseUsrNum( iter ); + parseUSR( compiler, iter, usrNum ); + } + + + private static void parseUSR( + BasicCompiler compiler, + CharacterIterator iter, + int usrNum ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + BasicExprParser.parseEnclosedExpr( compiler, iter ); + if( !BasicUtil.replaceLastCodeFrom_LD_HL_To_DE( compiler ) ) { + asmOut.append( "\tEX\tDE,HL\n" ); + } + boolean insideCallable = (compiler.getEnclosingCallableEntry() != null); + if( insideCallable ) { + asmOut.append( "\tPUSH\tIY\n" ); + } + asmOut.append( "\tLD\tHL,(" ); + asmOut.append( compiler.getUsrLabel( usrNum ) ); + asmOut.append( ")\n" + + "\tCALL\tJP_HL\n" ); + if( insideCallable ) { + asmOut.append( "\tPOP\tIY\n" ); + } + compiler.addLibItem( BasicLibrary.LibItem.JP_HL ); + compiler.addLibItem( BasicLibrary.LibItem.E_USR ); + } + + + private static void parseVAL( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseStringPrimExpr( compiler, iter ); + if( BasicUtil.checkToken( iter, ',' ) ) { + int pos = asmOut.length(); + BasicExprParser.parseExpr( compiler, iter ); + Integer radix = BasicUtil.removeLastCodeIfConstExpr( compiler, pos ); + if( radix != null ) { + switch( radix.intValue() ) { + case 2: + asmOut.append( "\tCALL\tF_VLB\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_VLB ); + break; + case 10: + asmOut.append( "\tCALL\tF_VLI\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_VLI ); + break; + case 16: + asmOut.append( "\tCALL\tF_VLH\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_VLH ); + break; + default: + throw new PrgException( + "Zahlenbasis in VAL-Funktion nicht unterst\u00FCtzt" ); + } + } else { + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInDE( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tEX\tDE,HL\n" + + "\tPOP\tHL\n" ); + } + asmOut.append( "\tCALL\tF_VAL\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_VAL ); + } + } else { + asmOut.append( "\tCALL\tF_VLI\n" ); + compiler.addLibItem( BasicLibrary.LibItem.F_VLI ); + } + BasicUtil.parseToken( iter, ')' ); + } + + + private static void parseStrBIN( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + compiler.lockTmpStrBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseExpr( compiler, iter ); + if( BasicUtil.checkToken( iter, ',' ) ) { + int pos = asmOut.length(); + BasicExprParser.parseExpr( compiler, iter ); + Integer value = BasicUtil.removeLastCodeIfConstExpr( compiler, pos ); + if( value != null ) { + if( value.intValue() < 0 ) { + compiler.putWarningOutOfRange(); + } + asmOut.append_LD_BC_nn( value.intValue() ); + } else { + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInBC( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tLD\tB,H\n" + + "\tLD\tC,L\n" + + "\tPOP\tHL\n" ); + } + } + asmOut.append( "\tCALL\tS_BINN\n" ); + } else { + asmOut.append( "\tCALL\tS_BIN\n" ); + } + BasicUtil.parseToken( iter, ')' ); + compiler.addLibItem( BasicLibrary.LibItem.S_BIN ); + } + + + private static void parseStrCHR( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + compiler.lockTmpStrBuf(); + int pos = asmOut.length(); + BasicExprParser.parseEnclosedExpr( compiler, iter ); + Integer value = BasicUtil.removeLastCodeIfConstExpr( compiler, pos ); + if( value != null ) { + asmOut.append_LD_A_n( value.intValue() ); + asmOut.append( "\tCALL\tS_CHRA\n" ); + } else { + asmOut.append( "\tCALL\tS_CHRL\n" ); + } + compiler.addLibItem( BasicLibrary.LibItem.S_CHR ); + } + + + private static void parseStrERR( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + compiler.getCodeBuf().append( "\tLD\tHL,(M_ERT)\n" ); + compiler.addLibItem( BasicLibrary.LibItem.M_ERT ); + } + + + private static void parseStrHEX( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + compiler.lockTmpStrBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseExpr( compiler, iter ); + if( BasicUtil.checkToken( iter, ',' ) ) { + int pos = asmOut.length(); + BasicExprParser.parseExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInBC( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tLD\tB,H\n" + + "\tLD\tC,L\n" + + "\tPOP\tHL\n" ); + } + asmOut.append( "\tCALL\tS_HEXN\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_HEXN ); + } else { + asmOut.append( "\tCALL\tS_HEX\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_HEX ); + } + BasicUtil.parseToken( iter, ')' ); + } + + + private static void parseStrINKEY( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + compiler.getCodeBuf().append( "\tCALL\tS_INKY\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_INKY ); + } + + + private static void parseStrINPUT( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + compiler.lockTmpStrBuf(); + BasicUtil.parseToken( iter, '(' ); + int pos = asmOut.length(); + BasicExprParser.parseExpr( compiler, iter ); + if( BasicUtil.checkToken( iter, ',' ) ) { + String oldCntCode = asmOut.cut( pos ); + String newCntCode = BasicUtil.convertCodeToValueInBC( oldCntCode ); + AtomicBoolean isConstChannelNum = new AtomicBoolean(); + BasicUtil.checkToken( iter, '#' ); + compiler.parseIOChannelNumToPtrFldAddrInHL( iter, isConstChannelNum ); + if( newCntCode != null ) { + asmOut.append( newCntCode ); + } else { + String channelCode = asmOut.cut( pos ); + if( isConstChannelNum.get() ) { + asmOut.append( oldCntCode ); + asmOut.append( "\tLD\tB,H\n" + + "\tLD\tC,L\n" ); + asmOut.append( channelCode ); + } else { + asmOut.append( oldCntCode ); + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( channelCode ); + asmOut.append( "\tPOP\tBC\n" ); + } + } + asmOut.append( "\tCALL\tIOINX\n" ); + compiler.addLibItem( BasicLibrary.LibItem.IOINX ); + } else { + Integer cnt = BasicUtil.removeLastCodeIfConstExpr( compiler, pos ); + if( cnt != null ) { + if( cnt.intValue() == 1 ) { + asmOut.append( "\tCALL\tS_INCH\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_INCH ); + } else { + asmOut.append( "\tCALL\tS_INP\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_INP ); + } + } else { + asmOut.append( "\tCALL\tS_INP\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_INP ); + } + } + BasicUtil.parseToken( iter, ')' ); + } + + + private static void parseStrLEFT( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + compiler.lockTmpStrBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseStringPrimExpr( compiler, iter ); + BasicUtil.parseToken( iter, ',' ); + int pos = asmOut.length(); + BasicExprParser.parseExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInBC( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tLD\tB,H\n" + + "\tLD\tC,L\n" + + "\tPOP\tHL\n" ); + } + asmOut.append( "\tCALL\tS_LEFT\n" ); + BasicUtil.parseToken( iter, ')' ); + compiler.addLibItem( BasicLibrary.LibItem.S_LEFT ); + } + + + private static void parseStrLOWER( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + compiler.lockTmpStrBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseStringPrimExpr( compiler, iter ); + BasicUtil.parseToken( iter, ')' ); + compiler.getCodeBuf().append( "\tCALL\tS_LWR\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_LWR ); + } + + + private static void parseStrLTRIM( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseStringPrimExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tCALL\tS_LTRIM\n" ); + BasicUtil.parseToken( iter, ')' ); + compiler.addLibItem( BasicLibrary.LibItem.S_LTRIM ); + } + + + private static void parseStrMEMSTR( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicExprParser.parseEnclosedExpr( compiler, iter ); + } + + + private static void parseStrMIRROR( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + BasicUtil.parseToken( iter, '(' ); + String text = BasicUtil.checkStringLiteral( compiler, iter ); + if( text != null ) { + if( text.isEmpty() ) { + asmOut.append_LD_HL_xx( BasicLibrary.EMPTY_STRING_LABEL ); + compiler.addLibItem( BasicLibrary.LibItem.EMPTY_STRING ); + } else { + asmOut.appendStringLiteral( + (new StringBuilder( text ).reverse()).toString(), + "00H" ); + } + } else { + compiler.lockTmpStrBuf(); + BasicExprParser.parseStringPrimExpr( compiler, iter ); + asmOut.append( "\tCALL\tS_MIRR\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_MIRR ); + } + BasicUtil.parseToken( iter, ')' ); + } + + + private static void parseStrMID( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + compiler.lockTmpStrBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseStringPrimExpr( compiler, iter ); + BasicUtil.parseToken( iter, ',' ); + int pos = asmOut.length(); + BasicExprParser.parseExpr( compiler, iter ); + String oldCode2 = asmOut.cut( pos ); + String newCode2 = BasicUtil.convertCodeToValueInDE( oldCode2 ); + if( BasicUtil.checkToken( iter, ',' ) ) { + BasicExprParser.parseExpr( compiler, iter ); + String oldCode3 = asmOut.cut( pos ); + String newCode3 = BasicUtil.convertCodeToValueInBC( oldCode3 ); + if( (newCode2 != null) && (newCode3 != null) ) { + asmOut.append( newCode2 ); + asmOut.append( newCode3 ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + if( newCode2 != null ) { + asmOut.append( oldCode3 ); + asmOut.append( "\tLD\tB,H\n" + + "\tLD\tC,L\n" ); + asmOut.append( newCode2 ); + } else { + if( newCode3 != null ) { + asmOut.append( oldCode2 ); + asmOut.append( "\tEX\tDE,HL\n" ); + asmOut.append( newCode3 ); + } else { + asmOut.append( oldCode2 ); + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode3 ); + asmOut.append( "\tLD\tB,H\n" + + "\tLD\tC,L\n" + + "\tPOP\tDE\n" ); + } + } + asmOut.append( "\tPOP\tHL\n" ); + } + asmOut.append( "\tCALL\tS_MIDN\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_MIDN ); + } else { + if( newCode2 != null ) { + asmOut.append( newCode2 ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode2 ); + asmOut.append( "\tEX\tDE,HL\n" + + "\tPOP\tHL\n" ); + } + asmOut.append( "\tCALL\tS_MID\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_MID ); + } + BasicUtil.parseToken( iter, ')' ); + } + + + private static void parseStrRIGHT( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseStringPrimExpr( compiler, iter ); + BasicUtil.parseToken( iter, ',' ); + int pos = asmOut.length(); + BasicExprParser.parseExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInBC( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( oldCode ); + asmOut.append( "\tLD\tB,H\n" + + "\tLD\tC,L\n" + + "\tPOP\tHL\n" ); + } + asmOut.append( "\tCALL\tS_RIGHT\n" ); + BasicUtil.parseToken( iter, ')' ); + compiler.addLibItem( BasicLibrary.LibItem.S_RIGHT ); + } + + + private static void parseStrRTRIM( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + compiler.lockTmpStrBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseStringPrimExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tCALL\tS_RTRIM\n" ); + BasicUtil.parseToken( iter, ')' ); + compiler.addLibItem( BasicLibrary.LibItem.S_RTRIM ); + } + + + private static void parseStrSPACE( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + compiler.lockTmpStrBuf(); + int pos = asmOut.length(); + BasicExprParser.parseEnclosedExpr( compiler, iter ); + String oldCode = asmOut.cut( pos ); + String newCode = BasicUtil.convertCodeToValueInBC( oldCode ); + if( newCode != null ) { + asmOut.append( newCode ); + } else { + asmOut.append( oldCode ); + asmOut.append( "\tLD\tB,H\n" + + "\tLD\tC,L\n" ); + } + asmOut.append( "\tLD\tL,20H\n" + + "\tCALL\tS_STC\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_STC ); + } + + + private static void parseStrSTR( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + BasicExprParser.parseEnclosedExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tCALL\tS_STR\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_STR ); + } + + + private static void parseStrSTRING( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + AsmCodeBuf asmOut = compiler.getCodeBuf(); + compiler.lockTmpStrBuf(); + BasicUtil.parseToken( iter, '(' ); + int pos = asmOut.length(); + BasicExprParser.parseExpr( compiler, iter ); + BasicUtil.parseToken( iter, ',' ); + Integer cnt = BasicUtil.removeLastCodeIfConstExpr( compiler, pos ); + if( cnt != null ) { + if( BasicExprParser.checkParseStringPrimExpr( compiler, iter ) ) { + asmOut.append_LD_BC_nn( cnt.intValue() ); + asmOut.append( "\tCALL\tS_STS\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_STS ); + } else { + BasicExprParser.parseExpr( compiler, iter ); + asmOut.append_LD_BC_nn( cnt.intValue() ); + asmOut.append( "\tCALL\tS_STC\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_STC ); + } + } else { + String oldCode1 = asmOut.cut( pos ); + String newCode1 = BasicUtil.convertCodeToValueInBC( oldCode1 ); + boolean isStr = false; + if( BasicExprParser.checkParseStringPrimExpr( compiler, iter ) ) { + isStr = true; + } else { + BasicExprParser.parseExpr( compiler, iter ); + } + String code2 = asmOut.cut( pos ); + if( BasicUtil.isOnly_LD_HL_xx( code2 ) ) { + if( newCode1 != null ) { + asmOut.append( newCode1 ); + } else { + asmOut.append( oldCode1 ); + asmOut.append( "\tLD\tB,H\n" + + "\tLD\tC,L\n" ); + } + asmOut.append( code2 ); + } else { + if( newCode1 != null ) { + asmOut.append( code2 ); + asmOut.append( newCode1 ); + } else { + asmOut.append( oldCode1 ); + asmOut.append( "\tPUSH\tHL\n" ); + asmOut.append( code2 ); + asmOut.append( "\tPOP\tBC\n" ); + } + } + if( isStr ) { + asmOut.append( "\tCALL\tS_STS\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_STS ); + } else { + asmOut.append( "\tCALL\tS_STC\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_STC ); + } + } + BasicUtil.parseToken( iter, ')' ); + } + + + private static void parseStrTRIM( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + compiler.lockTmpStrBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseStringPrimExpr( compiler, iter ); + compiler.getCodeBuf().append( "\tCALL\tS_TRIM\n" ); + BasicUtil.parseToken( iter, ')' ); + compiler.addLibItem( BasicLibrary.LibItem.S_TRIM ); + } + + + private static void parseStrUPPER( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + compiler.lockTmpStrBuf(); + BasicUtil.parseToken( iter, '(' ); + BasicExprParser.parseStringPrimExpr( compiler, iter ); + BasicUtil.parseToken( iter, ')' ); + compiler.getCodeBuf().append( "\tCALL\tS_UPR\n" ); + compiler.addLibItem( BasicLibrary.LibItem.S_UPR ); + } +} diff --git a/src/jkcemu/programming/basic/BasicLibrary.java b/src/jkcemu/programming/basic/BasicLibrary.java index c236181..f09303b 100644 --- a/src/jkcemu/programming/basic/BasicLibrary.java +++ b/src/jkcemu/programming/basic/BasicLibrary.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -23,58 +23,57 @@ public static enum LibItem { INIV, INSV, INRSV, INLNB, INLNR, INPWV, P_I, P_IF, PS_I, PS_IF, PS_S, PS_ST, PS_SP, PS_NL, - ACCEPT, CIRCLE, CONNECT, - DATA, DATAGRAM, DREADI, DREADS, - DRAW, DRAWR, DRBOX, DRBOXF, H_BOX, DRHLIN, DRLINE, - DRLBL, DRLBLT, MOVER, LOCATE, - PAUSE, PEN, ONGOAD, PLOTR, SCREEN, - IOCLOSE, IOOPEN, IOEOF, IOAVAILABLE, - IOINL, IOINX, IORDB, IOFLUSH, IO_SET_COUT, + CIRCLE, + DATA, DREADI, DREADS, + DRAW, DRAWR, DRAW_LINE, DRAWS, DRAWST, + DRBOX, DRBOXF, H_BOX, DRAW_HLINE, + DRLBL, DRLBLT, MOVER, LOCATE, ONGOAD, + PAINT, PAINT_M_HPIX, PAINT_M_WPIX, + PAUSE, PAUSE_N, PEN, PLOTR, + PRINT_SPC, SCREEN, + IO_PRINT_SPC, + IOCLOSE, IOOPEN, IOEOF, + IOINL, IOINX, IORDB, IO_SET_COUT, IO_CRT_HANDLER, IO_LPT_HANDLER, - IO_SIMPLE_OUT_HANDLER, IO_VDIP_HANDLER, - IO_COUT, IOCADR, IOCTB1, IOCTB2, + IO_SIMPLE_OUT_HANDLER, + IO_FILE_HANDLER, IO_VDIP_HANDLER, + IO_COUT, IO_M_COUT, IOCADR, IOCTB1, IOCTB2, CHECK_OPEN_NET_CHANNEL, - KCNET_S_MACADDR, KCNET_S_DNSSERVER, KCNET_S_GATEWAY, - KCNET_S_LOCALADDR, KCNET_S_NETMASK, - KCNET_S_GET_IPADDR, KCNET_S_IPADDR, - KCNET_SET_DNSSERVER, KCNET_SET_GATEWAY, - KCNET_SET_LOCALADDR, KCNET_SET_NETMASK, - KCNET_SET_BASE, KCNET_BASE_HW, - KCNET_HOSTBYNAME, KCNET_PARSE_IPADDR, - KCNET_TCP, KCNET_UDP, KCNET_FLUSH1, - KCNET_SOCK, KCNET_BASE, - VDIP_M_IOADDR, - F_INSTR, F_INSTRN, F_JOY, F_LEN, - F_LOCALPORT, F_REMOTEPORT, + VDIP_DATA_RDPTRS, VDIP_INIT, VDIP_M_IOADDR, + F_INSTR, F_INSTRN, F_IS_TARGET, F_JOY, F_LEN, F_RND, F_SGN, F_SQR, F_VAL, F_VLB, F_VLH, F_VLI, S_BIN, S_CHR, S_HEX, S_HEXN, S_HXHL, S_HXA, - S_HOSTBYNAME, S_INP, S_INCH, S_INKY, + S_INP, S_INCH, S_INKY, S_LEFT, S_LWR, S_LTRIM, - S_MID, S_MIDN, S_MIRR, S_NETMASK, S_REMOTEADDR, + S_MID, S_MIDN, S_MIRR, S_NETMASK, S_RIGHT, S_RTRIM, - S_STC, S_STS, S_STR, S_TRIM, S_UPR, SEND, + S_STC, S_STS, S_STR, S_TRIM, S_UPR, ARYADR, CKIDX, O_AND, O_NOT, O_OR, O_XOR, O_LT, O_LE, O_GT, O_GE, O_EQ, O_NE, O_ADD, O_SUB, O_MUL, O_MOD, O_DIV, O_INC, O_DEC, O_SHL, O_SHR, O_STEQ, O_STNE, O_STGE, O_STGT, O_STLE, O_STLT, - ASGSM, ASGSL, ASGSV, - MFIND, MALLOC, MMGC, MRGC, MFREE, HEAP, ABS_NEG_HL, + ASSIGN_STR_TO_NEW_MEM_VS, ASSIGN_STR_TO_VS, + ASSIGN_VS_TO_VS, + MFIND, MALLOC, MMGC, MRGC, MFREE, + CHECK_DE_WITHIN_HEAP, HEAP, ABS_NEG_HL, CPHLDE, CKSTK, JP_HL, SMACP, SVDUP, STCMP, STNCP, - C_UPR, D_EMPT, + C_UPR, + EMPTY_STRING, E_DATA, E_IDX, E_PARM, E_NOV, E_NXWF, E_REWG, E_USR, - E_TYPE, E_EXIT, OUTSP, FNT5P7, - M_ERN, M_ERT, M_FRET, M_INKB, M_IO_COUT, - M_HOST, M_PORT, - M_STMP, M_TOP, M_XYPO, + E_TYPE, E_EXIT, OUTSP, FONT_5X7, + M_BALN, M_SRNM, M_ERN, M_ERT, + M_FRET, M_INKB, M_HOST, M_PORT, + M_STMP, M_XYPO, MTOP, XCKBRK, XINCH, XINKEY, XBREAK, XBORDER, XCLS, XCOLOR, XCURS, XHLINE, XINK, XPAPER, XOUTST, XOUTS, XOUTNL, XOUTCH, XLOCATE, XLPTCH, - XPAUSE, XPEN, XPSET, XPRES, XPTEST, - XSCRS, XTARID, XJOY }; + XPEN, XPAINT, XPOINT, XPSET, XPRES, XPTEST, + XSCREEN, XJOY }; + // Fehlercodes public static final int E_OK = 0; public static final int E_ERROR = -1; public static final int E_INVALID = -2; @@ -84,32 +83,73 @@ public static enum LibItem { public static final int E_DEVICE_NOT_FOUND = -21; public static final int E_DEVICE_NOT_CONFIGURED = -22; public static final int E_DEVICE_LOCKED = -23; - public static final int E_NO_DISK = -24; - public static final int E_FILE_NOT_FOUND = -25; - public static final int E_IO_MODE = -26; - public static final int E_IO_ERROR = -27; - public static final int E_EOF = -28; - public static final int E_READ_ONLY = -29; - public static final int E_DISK_FULL = -30; - public static final int E_MTU_EXCEEDED = -31; - public static final int E_DNS_NOT_CONFIGURED = -32; - public static final int E_DNS_ERROR = -33; - public static final int E_UNKNOWN_HOST = -34; - public static final int E_CONNECT_FAILED = -35; - public static final int E_SOCKET_STATUS = -36; - public static final int E_TIMEOUT = -37; + public static final int E_HARDWARE = -24; + public static final int E_NO_DISK = -25; + public static final int E_FILE_NOT_FOUND = -26; + public static final int E_PATH_NOT_FOUND = -27; + public static final int E_IO_MODE = -28; + public static final int E_IO_ERROR = -29; + public static final int E_EOF = -30; + public static final int E_READ_ONLY = -31; + public static final int E_DIR_FULL = -32; + public static final int E_DISK_FULL = -33; + public static final int E_MEDIA_CHANGED = -34; - public static final int IOCTB_EOF_OFFS = 2; - public static final int IOCTB_AVAILABLE_OFFS = 4; - public static final int IOCTB_READ_OFFS = 6; - public static final int IOCTB_WRITE_OFFS = 8; - public static final int IOCTB_FLUSH_OFFS = 10; - public static final int IOCTB_BBUF_OFFS = 12; - public static final int IOCTB_DRIVER_OFFS = 14; + /* + * Offsets innerhalb des Kanalzeigerfelds + * + * An erster Stelle (Offset 0) steht der Zeiger zur CLOSE-Routine. + * Anhand des Vorhandensein dieses Zeigers wird erkannt, + * ob der Kanal offen oder geschlossen ist. + */ + public static final int IOCTB_EOF_OFFS = 2; + public static final int IOCTB_READ_OFFS = 4; + public static final int IOCTB_WRITE_OFFS = 6; + public static final int IOCTB_BBUF_OFFS = 8; + public static final int IOCTB_DRIVER_OFFS = 10; - public static final int IOMODE_INPUT = 1; - public static final int IOMODE_OUTPUT = 2; - public static final int IOMODE_APPEND = 4; + /* + * Betriebsmodi eines Kanals: + * Bit 0: Default + * Bit 1: Input + * Bit 2: Output + * Bit 3: Append + * Bit 4: Text + * Bit 5: Binaer + */ + public static final int IOMODE_DEFAULT_MASK = 0x01; + public static final int IOMODE_INPUT_MASK = 0x02; + public static final int IOMODE_OUTPUT_MASK = 0x04; + public static final int IOMODE_APPEND_MASK = 0x08; + public static final int IOMODE_TXT_MASK = 0x10; + public static final int IOMODE_BIN_MASK = 0x20; + + public static final int IOMODE_TXT_DEFAULT = IOMODE_TXT_MASK + | IOMODE_DEFAULT_MASK; + + public static final int IOMODE_TXT_INPUT = IOMODE_TXT_MASK + | IOMODE_INPUT_MASK; + + public static final int IOMODE_TXT_OUTPUT = IOMODE_TXT_MASK + | IOMODE_OUTPUT_MASK; + + public static final int IOMODE_TXT_APPEND = IOMODE_TXT_MASK + | IOMODE_APPEND_MASK; + + public static final int IOMODE_BIN_DEFAULT = IOMODE_BIN_MASK + | IOMODE_DEFAULT_MASK; + + public static final int IOMODE_BIN_INPUT = IOMODE_BIN_MASK + | IOMODE_INPUT_MASK; + + public static final int IOMODE_BIN_OUTPUT = IOMODE_BIN_MASK + | IOMODE_OUTPUT_MASK; + + public static final int IOMODE_BIN_APPEND = IOMODE_BIN_MASK + | IOMODE_APPEND_MASK; + + // sonstige Konstanten + public static final String EMPTY_STRING_LABEL = "D_EMPT"; public static void appendCodeTo( BasicCompiler compiler ) @@ -190,9 +230,9 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tCALL\tINLNB\n" + "\tLD\tA,(HL)\n" + "\tOR\tA\n" - + "\tJR\tNZ,INSV1\n" - + "\tLD\tDE,D_EMPT\n" - + "\tJR\tINSV3\n" + + "\tJR\tNZ,INSV1\n" ); + buf.append_LD_DE_xx( EMPTY_STRING_LABEL ); + buf.append( "\tJR\tINSV3\n" + "INSV1:\tPUSH\tHL\n" + "\tCALL\tMFIND\n" + "\tPOP\tHL\n" @@ -217,7 +257,7 @@ public static void appendCodeTo( BasicCompiler compiler ) libItems.add( LibItem.MFIND ); libItems.add( LibItem.MALLOC ); libItems.add( LibItem.MFREE ); - libItems.add( LibItem.D_EMPT ); + libItems.add( LibItem.EMPTY_STRING ); } if( libItems.contains( LibItem.INRSV ) ) { /* @@ -484,6 +524,50 @@ public static void appendCodeTo( BasicCompiler compiler ) libItems.add( LibItem.S_STR ); libItems.add( LibItem.PS_S ); } + if( libItems.contains( LibItem.PRINT_SPC ) ) { + /* + * Ausgabe von Leerzeichen auf dem Bildschirm + * + * Parameter: + * HL: Anzahl Leerzeichen + */ + buf.append( "PRINT_SPC:\n" + + "\tBIT\t7,H\n" + + "\tRET\tNZ\n" + + "PRINT_SPC1:\n" + + "\tLD\tA,H\n" + + "\tOR\tL\n" + + "\tRET\tZ\n" + + "\tPUSH\tHL\n" + + "\tLD\tA,20H\n" + + "\tCALL\tXOUTCH\n" + + "\tPOP\tHL\n" + + "\tDEC\tHL\n" + + "\tJR\tPRINT_SPC1\n" ); + libItems.add( LibItem.XOUTCH ); + } + if( libItems.contains( LibItem.IO_PRINT_SPC ) ) { + /* + * Ausgabe von Leerzeichen auf dem Ausgabekanal + * + * Parameter: + * HL: Anzahl Leerzeichen + */ + buf.append( "IO_PRINT_SPC:\n" + + "\tBIT\t7,H\n" + + "\tRET\tNZ\n" + + "IO_PRINT_SPC1:\n" + + "\tLD\tA,H\n" + + "\tOR\tL\n" + + "\tRET\tZ\n" + + "\tPUSH\tHL\n" + + "\tLD\tA,20H\n" + + "\tCALL\tIO_COUT\n" + + "\tPOP\tHL\n" + + "\tDEC\tHL\n" + + "\tJR\tIO_PRINT_SPC1\n" ); + libItems.add( LibItem.IO_COUT ); + } if( libItems.contains( LibItem.PS_IF ) ) { /* * Formatierte Ausgabe einer Integer-Zahl auf einem Ausgabekanal @@ -543,9 +627,9 @@ public static void appendCodeTo( BasicCompiler compiler ) } if( libItems.contains( LibItem.IO_COUT ) ) { buf.append( "IO_COUT:\n" - + "\tLD\tHL,(M_IO_COUT)\n" + + "\tLD\tHL,(IO_M_COUT)\n" + "\tJP\t(HL)\n" ); - libItems.add( LibItem.M_IO_COUT ); + libItems.add( LibItem.IO_M_COUT ); } if( libItems.contains( LibItem.IO_SET_COUT ) ) { /* @@ -559,7 +643,7 @@ public static void appendCodeTo( BasicCompiler compiler ) * CY=1: Adresse ist Null */ buf.append( "IO_SET_COUT:\n" - + "\tLD\t(M_IO_COUT),HL\n" ); + + "\tLD\t(IO_M_COUT),HL\n" ); appendResetErrorUseBC( compiler ); buf.append( "\tLD\tA,H\n" + "\tOR\tL\n" @@ -567,267 +651,30 @@ public static void appendCodeTo( BasicCompiler compiler ) appendSetErrorChannelClosed( compiler ); buf.append( "\tSCF\n" + "\tRET\n" ); - libItems.add( LibItem.M_IO_COUT ); - } - if( libItems.contains( LibItem.ACCEPT ) ) { - /* - * Socket in TCP-Server-Mode schalten - * - * Parameter: - * (M_PORT): Portnummer - * A: Socketnummer (entspricht Kanalnummer-1) - * HL: Anfangsadresse des Kanalzeigerfeldes - */ - buf.append( "ACCEPT:\tLD\t(M_IOCA),HL\n" - + "\tLD\t(KCNET_M_SOCKET),A\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tOR\t(HL)\n" - + "\tJR\tZ,ACCEPT1\n" ); - appendSetErrorChannelAlreadyOpen( compiler ); - buf.append( "\tRET\n" - + "ACCEPT1:\n" - + "\tDEC\tHL\n" ); - buf.append_LD_DE_nn( KCNetLibrary.IOCTB_SOCKET_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(KCNET_M_SOCKET)\n" - + "\tLD\t(HL),A\n" ); - appendResetErrorUseHL( compiler ); - buf.append( "\tCALL\tKCNET_INIT\n" - + "\tRET\tC\n" - + "ACCEPT2:\n" - + "\tLD\tE,01H\n" // TCP - + "\tLD\tHL,(M_PORT)\n" - + "\tCALL\tKCNET_SOCK_OPEN\n" - + "\tLD\tBC,0201H\n" // Sn_CR=LISTEN - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - + "\tCALL\tKCNET_SOCK_GET_STATUS\n" - + "\tCP\t14H\n" // SOCK_LISTEN? - + "\tJR\tNZ,ACCEPT2\n" - // Kanalzeigerfeld fuellen - + "\tCALL\tKCNET_SET_IOPTRS_TCP\n" - // auf Verbindung warten - + "ACCEPT3:\n" ); - if( options.canBreakAlways() ) { - buf.append( "\tCALL\tXCKBRK\n" ); - libItems.add( LibItem.XCKBRK ); - } - buf.append( "\tCALL\tKCNET_SOCK_GET_STATUS\n" - + "\tCP\t14H\n" // SOCK_LISTEN? - + "\tJR\tZ,ACCEPT3\n" - + "\tCP\t01H\n" // SOCK_ARP? - + "\tJR\tZ,ACCEPT3\n" - + "\tCP\t15H\n" // SOCK_SYNSENT? - + "\tJR\tZ,ACCEPT3\n" - + "\tCP\t16H\n" // SOCK_SYNRECV? - + "\tJR\tZ,ACCEPT3\n" - + "\tCP\t17H\n" // SOCK_ESTABLISHED? - + "\tJR\tNZ,ACCEPT2\n" - + "\tCALL\tKCNET_IOCTB_INIT_TXRX\n" - + "\tJP\tKCNET_IOCTB_FILL_REMOTE\n" ); - libItems.add( LibItem.KCNET_TCP ); - libItems.add( LibItem.KCNET_BASE ); - } - if( libItems.contains( LibItem.CONNECT ) ) { - /* - * TCP-Verbindung aufbauen - * - * Parameter: - * (M_HOST): Hostname oder IP-Adresse (textuell) - * (M_PORT): Portnummer - * A: Socketnummer (entspricht Kanalnummer-1) - * HL: Anfangsadresse des Kanalzeigerfeldes - */ - buf.append( "CONNECT:\n" - + "\tLD\t(M_IOCA),HL\n" - + "\tLD\t(KCNET_M_SOCKET),A\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tOR\t(HL)\n" - + "\tJR\tZ,CONNECT1\n" ); - appendSetErrorChannelAlreadyOpen( compiler ); - buf.append( "\tRET\n" - + "CONNECT1:\n" - + "\tDEC\tHL\n" ); - buf.append_LD_DE_nn( KCNetLibrary.IOCTB_SOCKET_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(KCNET_M_SOCKET)\n" - + "\tLD\t(HL),A\n" ); - appendResetErrorUseHL( compiler ); - buf.append( "\tCALL\tKCNET_INIT\n" - + "\tRET\tC\n" - + "\tLD\tHL,(M_HOST)\n" - + "\tCALL\tKCNET_HOSTBYNAME\n" - + "\tRET\tC\n" - + "\tLD\tE,01H\n" // TCP - + "\tCALL\tKCNET_SOCK_GET_PORT_AND_OPEN\n" - + "\tLD\tHL,KCNET_M_IPADDR\n" - + "\tLD\tBC,040CH\n" // 4 Durchlaeufe, Sn_DIPR - + "CONNECT2:\n" - + "\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tLD\tB,(HL)\n" - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tINC\tHL\n" - + "\tINC\tC\n" - + "\tDJNZ\tCONNECT2\n" - + "\tLD\tBC,(M_PORT)\n" - + "\tLD\tL,10H\n" // Sn_DPORT - + "\tCALL\tKCNET_SOCK_SET_WORD\n" - + "\tLD\tBC,0401H\n" // Sn_CR=CONNECT - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - + "CONNECT3:\n" - + "\tLD\tL,03H\n" // Sn_SR - + "\tCALL\tKCNET_SOCK_GET_BYTE\n" - + "\tCP\t17H\n" // SOCK_ESTABLISHED - + "\tJP\tZ,CONNECT5\n" - + "\tOR\tA\n" ); // SOCK_CLOSED? - if( options.canBreakAlways() ) { - buf.append( "\tJR\tZ,CONNECT4\n" - + "\tCALL\tXCKBRK\n" - + "\tJR\tCONNECT3\n" - + "CONNECT4:\n" ); - libItems.add( LibItem.XCKBRK ); - } else { - buf.append( "\tJR\tNZ,CONNECT3\n" ); - } - appendSetError( - compiler, - E_CONNECT_FAILED, - "Verbindungsaufbau fehlgeschlagen", - "Connect failed" ); - buf.append( "\tSCF\n" - + "\tRET\n" - + "CONNECT5:\n" - + "\tCALL\tKCNET_SET_IOPTRS_TCP\n" - + "\tCALL\tKCNET_IOCTB_INIT_TXRX\n" - + "\tJP\tKCNET_IOCTB_FILL_REMOTE\n" ); - libItems.add( LibItem.KCNET_HOSTBYNAME ); - libItems.add( LibItem.KCNET_TCP ); - libItems.add( LibItem.KCNET_BASE ); - } - if( libItems.contains( LibItem.DATAGRAM ) ) { - /* - * Socket in UDP-Mode oeffnen - * - * Parameter: - * (M_PORT): Portnummer - * A: Socketnummer (entspricht Kanalnummer-1) - * HL: Anfangsadresse des Kanalzeigerfeldes - */ - buf.append( "DATAGRAM:\n" - + "\tLD\t(M_IOCA),HL\n" - + "\tLD\t(KCNET_M_SOCKET),A\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tOR\t(HL)\n" - + "\tJR\tZ,DATAGRAM1\n" ); - appendSetErrorChannelAlreadyOpen( compiler ); - buf.append( "\tRET\n" - + "DATAGRAM1:\n" - + "\tDEC\tHL\n" ); - buf.append_LD_DE_nn( KCNetLibrary.IOCTB_SOCKET_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(KCNET_M_SOCKET)\n" - + "\tLD\t(HL),A\n" ); - appendResetErrorUseHL( compiler ); - buf.append( "\tCALL\tKCNET_INIT\n" - + "\tRET\tC\n" - + "\tLD\tE,02H\n" // UDP - + "\tLD\tHL,(M_PORT)\n" - + "\tCALL\tKCNET_SOCK_OPEN\n" - // Kanalzeigerfeld fuellen - + "\tLD\tBC,000AH\n" // ohne FLUSH-Routine - + "\tLD\tDE,(M_IOCA)\n" - + "\tLD\tHL,KCNET_DATA_IOPTRS\n" - + "\tLDIR\n" - + "\tXOR\tA\n" - + "\tLD\tB,04H\n" // FLUSH- und Zeichenpuffer - + "DATAGRAM2:\n" - + "\tLD\t(DE),A\n" - + "\tINC\tDE\n" - + "\tDJNZ\tDATAGRAM2\n" - + "\tLD\tA,(KCNET_M_SOCKET)\n" - + "\tLD\t(DE),A\n" // IOCTB_SOCKET_OFFS - + "\tINC\tDE\n" - + "\tLD\tA,\'U\'\n" - + "\tLD\t(DE),A\n" // IOCTB_PROTO_OFFS - + "\tINC\tDE\n" - + "\tLD\tB," ); - buf.appendHex2( KCNetLibrary.IOCTB_CHANNEL_SIZE - - KCNetLibrary.IOCTB_RX_CNT_OFFS ); - buf.append( "\n" - + "\tXOR\tA\n" - + "DATAGRAM3:\n" - + "\tINC\tDE\n" - + "\tLD\t(DE),A\n" - + "\tDJNZ\tDATAGRAM3\n" - + "\tJP\tKCNET_IOCTB_INIT_TXRX\n" ); - libItems.add( LibItem.KCNET_UDP ); - libItems.add( LibItem.KCNET_BASE ); - } - if( libItems.contains( LibItem.SEND ) ) { - /* - * Senden eines UDP-Pakets - * - * Parameter: - * (KCNET_M_SOCKET): Socketnummer - * (M_IOCA): Anfangsadresse Kanalzeigerfeld - * (M_HOST): Hostname oder IP-Adresse (textuell) - * (M_PORT): Portnummer - */ - buf.append( "SEND:\tLD\tHL,(M_IOCA)\n" - + "\tCALL\tCHECK_OPEN_NET_CHANNEL\n" - + "\tRET\tC\n" - + "\tCP\t\'U\'\n" - + "\tJR\tZ,SEND1\n" ); - appendSetErrorIOMode( compiler ); - buf.append( "\tRET\n" - + "SEND1:\tLD\tBC,040CH\n" // 4 Durchlaeufe und Sn_DIPR - + "\tLD\tHL,KCNET_M_IPADDR\n" - + "SEND2:\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tLD\tB,(HL)\n" - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tINC\tC\n" - + "\tINC\tHL\n" - + "\tDJNZ\tSEND2\n" - + "\tLD\tL,C\n" // L nun Sn_DPORT - + "\tLD\tBC,(M_PORT)\n" - + "\tCALL\tKCNET_SOCK_SET_WORD\n" - + "\tJP\tKCNET_FLUSH1\n" ); - libItems.add( LibItem.CHECK_OPEN_NET_CHANNEL ); - libItems.add( LibItem.KCNET_HOSTBYNAME ); - libItems.add( LibItem.KCNET_UDP ); - libItems.add( LibItem.KCNET_FLUSH1 ); - libItems.add( LibItem.KCNET_BASE ); + libItems.add( LibItem.IO_M_COUT ); } if( libItems.contains( LibItem.CIRCLE ) ) { /* * Zeichnen eines Kreises * * Parameter: - * M_CIMX: X-Koordinate Kreismittelpunkt (Xm) - * M_CIMY: Y-Koordinate Kreismittelpunkt (Ym) - * M_CIRA: Radius + * CIRCLE_M_X: X-Koordinate Kreismittelpunkt (Xm) + * CIRCLE_M_Y: Y-Koordinate Kreismittelpunkt (Ym) + * CIRCLE_M_R: Radius * Hilfszellen: - * M_CIER: Fehlerglied - * M_CIRX: relative X-Koordinate (Xr) - * M_CIRY: relative Y-Koordinate (Yr) - * M_CXMX: Koordinate Xm - Xr - * M_CXPX: Koordinate Xm + Xr - * M_CXMY: Koordinate Xm - Yr - * M_CXPY: Koordinate Xm + Yr - * M_CYMX: Koordinate Ym - Xr - * M_CYPX: Koordinate Ym + Xr - * M_CYMY: Koordinate Ym - Yr - * M_CYPY: Koordinate Ym + Yr - */ - buf.append( "CIRCLE:\tLD\tDE,(M_CIRA)\n" + * CIRCLE_M_ER: Fehlerglied + * CIRCLE_M_RX: relative X-Koordinate (Xr) + * CIRCLE_M_RY: relative Y-Koordinate (Yr) + * CIRCLE_M_XMX: Koordinate Xm - Xr + * CIRCLE_M_XPX: Koordinate Xm + Xr + * CIRCLE_M_XMY: Koordinate Xm - Yr + * CIRCLE_M_XPY: Koordinate Xm + Yr + * CIRCLE_M_YMX: Koordinate Ym - Xr + * CIRCLE_M_YPX: Koordinate Ym + Xr + * CIRCLE_M_YMY: Koordinate Ym - Yr + * CIRCLE_M_YPY: Koordinate Ym + Yr + */ + buf.append( "CIRCLE:\tLD\tDE,(CIRCLE_M_R)\n" + "\tLD\tA,D\n" + "\tOR\tA\n" + "\tJP\tM,E_PARM\n" // bei R<0 @@ -835,89 +682,91 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tRET\tZ\n" // bei R=0 + "\tLD\tHL,0001\n" // Fehlerglied: 1-R + "\tSBC\tHL,DE\n" // CY ist 0 - + "\tLD\t(M_CIER),HL\n" + + "\tLD\t(CIRCLE_M_ER),HL\n" + "\tLD\tHL,0000H\n" // (Xr,Yr) = (0,R) - + "\tLD\t(M_CIRX),HL\n" - + "\tLD\t(M_CIRY),DE\n" - + "\tLD\tHL,(M_CIMX)\n" - + "\tLD\t(M_CXMX),HL\n" - + "\tLD\t(M_CXPX),HL\n" + + "\tLD\t(CIRCLE_M_RX),HL\n" + + "\tLD\t(CIRCLE_M_RY),DE\n" + + "\tLD\tHL,(CIRCLE_M_X)\n" + + "\tLD\t(CIRCLE_M_XMX),HL\n" + + "\tLD\t(CIRCLE_M_XPX),HL\n" + "\tOR\tA\n" + "\tSBC\tHL,DE\n" - + "\tLD\t(M_CXMY),HL\n" + + "\tLD\t(CIRCLE_M_XMY),HL\n" + "\tADD\tHL,DE\n" + "\tADD\tHL,DE\n" - + "\tLD\t(M_CXPY),HL\n" - + "\tLD\tHL,(M_CIMY)\n" - + "\tLD\t(M_CYMX),HL\n" - + "\tLD\t(M_CYPX),HL\n" + + "\tLD\t(CIRCLE_M_XPY),HL\n" + + "\tLD\tHL,(CIRCLE_M_Y)\n" + + "\tLD\t(CIRCLE_M_YMX),HL\n" + + "\tLD\t(CIRCLE_M_YPX),HL\n" + "\tOR\tA\n" + "\tSBC\tHL,DE\n" - + "\tLD\t(M_CYMY),HL\n" + + "\tLD\t(CIRCLE_M_YMY),HL\n" + "\tADD\tHL,DE\n" + "\tADD\tHL,DE\n" - + "\tLD\t(M_CYPY),HL\n" - + "CIRC2:\tLD\tHL,(M_CIRY)\n" // Ende erreicht? + + "\tLD\t(CIRCLE_M_YPY),HL\n" + + "CIRCLE1:\n" + + "\tLD\tHL,(CIRCLE_M_RY)\n" // Ende erreicht? + "\tLD\tB,H\n" + "\tLD\tC,L\n" - + "\tLD\tDE,(M_CIRX)\n" + + "\tLD\tDE,(CIRCLE_M_RX)\n" + "\tOR\tA\n" + "\tSBC\tHL,DE\n" + "\tRET\tC\n" + "\tPUSH\tAF\n" + "\tPUSH\tDE\n" - + "\tCALL\tCIRC4\n" // Punkte setzen + + "\tCALL\tCIRCLE3\n" // Punkte setzen + "\tPOP\tDE\n" + "\tPOP\tAF\n" + "\tRET\tZ\n" + "\tINC\tDE\n" // Xr = Xr + 1 - + "\tLD\t(M_CIRX),DE\n" - + "\tLD\tHL,(M_CXMX)\n" // Xm-Xr anpassen + + "\tLD\t(CIRCLE_M_RX),DE\n" + + "\tLD\tHL,(CIRCLE_M_XMX)\n" // Xm-Xr anpassen + "\tDEC\tHL\n" - + "\tLD\t(M_CXMX),HL\n" - + "\tLD\tHL,(M_CXPX)\n" // Xm+Xr anpassen + + "\tLD\t(CIRCLE_M_XMX),HL\n" + + "\tLD\tHL,(CIRCLE_M_XPX)\n" // Xm+Xr anpassen + "\tINC\tHL\n" - + "\tLD\t(M_CXPX),HL\n" - + "\tLD\tHL,(M_CYMX)\n" // Xm-Xr anpassen + + "\tLD\t(CIRCLE_M_XPX),HL\n" + + "\tLD\tHL,(CIRCLE_M_YMX)\n" // Xm-Xr anpassen + "\tDEC\tHL\n" - + "\tLD\t(M_CYMX),HL\n" - + "\tLD\tHL,(M_CYPX)\n" // Xm+Xr anpassen + + "\tLD\t(CIRCLE_M_YMX),HL\n" + + "\tLD\tHL,(CIRCLE_M_YPX)\n" // Xm+Xr anpassen + "\tINC\tHL\n" - + "\tLD\t(M_CYPX),HL\n" - + "\tLD\tHL,(M_CIER)\n" // F >= 0 -> CIRC3 + + "\tLD\t(CIRCLE_M_YPX),HL\n" + + "\tLD\tHL,(CIRCLE_M_ER)\n" // F >= 0 -> CIRCLE2 + "\tBIT\t7,H\n" - + "\tJR\tZ,CIRC3\n" + + "\tJR\tZ,CIRCLE2\n" + "\tSLA\tE\n" // F = F + 2*X - 1 + "\tRL\tD\n" + "\tADD\tHL,DE\n" + "\tDEC\tHL\n" - + "\tLD\t(M_CIER),HL\n" - + "\tJR\tCIRC2\n" - + "CIRC3:\tLD\tB,H\n" + + "\tLD\t(CIRCLE_M_ER),HL\n" + + "\tJR\tCIRCLE1\n" + + "CIRCLE2:\n" + + "\tLD\tB,H\n" + "\tLD\tC,L\n" - + "\tLD\tHL,(M_CIRY)\n" // F = F + 2*(X-Y) + + "\tLD\tHL,(CIRCLE_M_RY)\n" // F = F + 2*(X-Y) + "\tEX\tDE,HL\n" + "\tOR\tA\n" + "\tSBC\tHL,DE\n" + "\tADD\tHL,HL\n" + "\tADD\tHL,BC\n" - + "\tLD\t(M_CIER),HL\n" - + "\tLD\tHL,(M_CIRY)\n" // Yr = Yr - 1 + + "\tLD\t(CIRCLE_M_ER),HL\n" + + "\tLD\tHL,(CIRCLE_M_RY)\n" // Yr = Yr - 1 + "\tDEC\tHL\n" - + "\tLD\t(M_CIRY),HL\n" - + "\tLD\tHL,(M_CXMY)\n" // Xm-Xr anpassen + + "\tLD\t(CIRCLE_M_RY),HL\n" + + "\tLD\tHL,(CIRCLE_M_XMY)\n" // Xm-Xr anpassen + "\tINC\tHL\n" - + "\tLD\t(M_CXMY),HL\n" - + "\tLD\tHL,(M_CXPY)\n" // Xm+Xr anpassen + + "\tLD\t(CIRCLE_M_XMY),HL\n" + + "\tLD\tHL,(CIRCLE_M_XPY)\n" // Xm+Xr anpassen + "\tDEC\tHL\n" - + "\tLD\t(M_CXPY),HL\n" - + "\tLD\tHL,(M_CYMY)\n" // Xm-Xr anpassen + + "\tLD\t(CIRCLE_M_XPY),HL\n" + + "\tLD\tHL,(CIRCLE_M_YMY)\n" // Xm-Xr anpassen + "\tINC\tHL\n" - + "\tLD\t(M_CYMY),HL\n" - + "\tLD\tHL,(M_CYPY)\n" // Xm+Xr anpassen + + "\tLD\t(CIRCLE_M_YMY),HL\n" + + "\tLD\tHL,(CIRCLE_M_YPY)\n" // Xm+Xr anpassen + "\tDEC\tHL\n" - + "\tLD\t(M_CYPY),HL\n" - + "\tJP\tCIRC2\n" + + "\tLD\t(CIRCLE_M_YPY),HL\n" + + "\tJP\tCIRCLE1\n" /* * Setzen eines Punktes in allen 8 Oktanden * @@ -925,44 +774,52 @@ public static void appendCodeTo( BasicCompiler compiler ) * BC: relative Y-Koordinate * Z=1: Xr == Yr */ - + "CIRC4:\tJR\tZ,CIRC6\n" + + "CIRCLE3:\n" + + "\tJR\tZ,CIRCLE5\n" + "\tLD\tA,D\n" + "\tOR\tE\n" + "\tEXX\n" - + "\tJR\tZ,CIRC5\n" - + "\tLD\tDE,(M_CXMX)\n" - + "\tCALL\tCIRC10\n" - + "CIRC5:\tLD\tDE,(M_CXPX)\n" - + "\tCALL\tCIRC10\n" + + "\tJR\tZ,CIRCLE4\n" + + "\tLD\tDE,(CIRCLE_M_XMX)\n" + + "\tCALL\tCIRCLE9\n" + + "CIRCLE4:\n" + + "\tLD\tDE,(CIRCLE_M_XPX)\n" + + "\tCALL\tCIRCLE9\n" + "\tEXX\n" - + "CIRC6:\tLD\tA,B\n" + + "CIRCLE5:\n" + + "\tLD\tA,B\n" + "\tOR\tC\n" + "\tEXX\n" - + "\tJR\tZ,CIRC7\n" - + "\tLD\tDE,(M_CXMY)\n" - + "\tCALL\tCIRC8\n" - + "CIRC7:\tLD\tDE,(M_CXPY)\n" - + "CIRC8:\tEXX\n" + + "\tJR\tZ,CIRCLE6\n" + + "\tLD\tDE,(CIRCLE_M_XMY)\n" + + "\tCALL\tCIRCLE7\n" + + "CIRCLE6:\n" + + "\tLD\tDE,(CIRCLE_M_XPY)\n" + + "CIRCLE7:\n" + + "\tEXX\n" + "\tLD\tA,D\n" + "\tOR\tE\n" + "\tEXX\n" - + "\tJR\tZ,CIRC9\n" - + "\tLD\tHL,(M_CYMX)\n" + + "\tJR\tZ,CIRCLE8\n" + + "\tLD\tHL,(CIRCLE_M_YMX)\n" + "\tPUSH\tDE\n" + "\tCALL\tXPSET\n" + "\tPOP\tDE\n" - + "CIRC9:\tLD\tHL,(M_CYPX)\n" + + "CIRCLE8:\n" + + "\tLD\tHL,(CIRCLE_M_YPX)\n" + "\tJP\tXPSET\n" - + "CIRC10:\tEXX\n" + + "CIRCLE9:\n" + + "\tEXX\n" + "\tLD\tA,B\n" + "\tOR\tC\n" + "\tEXX\n" - + "\tJR\tZ,CIRC11\n" - + "\tLD\tHL,(M_CYMY)\n" + + "\tJR\tZ,CIRCLE10\n" + + "\tLD\tHL,(CIRCLE_M_YMY)\n" + "\tPUSH\tDE\n" + "\tCALL\tXPSET\n" + "\tPOP\tDE\n" - + "CIRC11:\tLD\tHL,(M_CYPY)\n" + + "CIRCLE10:\n" + + "\tLD\tHL,(CIRCLE_M_YPY)\n" + "\tJP\tXPSET\n" ); libItems.add( LibItem.E_PARM ); libItems.add( LibItem.XPSET ); @@ -1032,26 +889,6 @@ public static void appendCodeTo( BasicCompiler compiler ) libItems.add( LibItem.E_PARM ); libItems.add( LibItem.XLOCATE ); } - if( libItems.contains( LibItem.MOVER ) ) { - /* - * Grafikkursor relativ positionieren - * - * Parameter: - * DE: X-Koordinate relativ - * HL: Y-Koordinate relativ - */ - buf.append( "MOVER:\tPUSH\tHL\n" - + "\tLD\tHL,(M_XPOS)\n" - + "\tCALL\tO_ADD\n" - + "\tLD\t(M_XPOS),HL\n" - + "\tPOP\tDE\n" - + "\tLD\tHL,(M_YPOS)\n" - + "\tCALL\tO_ADD\n" - + "\tLD\t(M_YPOS),HL\n" - + "\tRET\n" ); - libItems.add( LibItem.O_ADD ); - libItems.add( LibItem.M_XYPO ); - } if( libItems.contains( LibItem.DRLBLT ) ) { buf.append( "DRLBLT:\tEX\t(SP),HL\n" + "\tCALL\tDRLBL\n" @@ -1078,7 +915,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tADD\tHL,HL\n" + "\tADD\tHL,BC\n" + "\tADD\tHL,HL\n" - + "\tLD\tBC,FNT5P7\n" + + "\tLD\tBC,FONT_5X7\n" + "\tADD\tHL,BC\n" + "\tLD\tDE,(M_XPOS)\n" + "\tLD\tA,(HL)\n" @@ -1115,29 +952,279 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tINC\tDE\n" + "\tINC\tDE\n" + "\tJR\tDRLBL4\n" ); - libItems.add( LibItem.FNT5P7 ); + libItems.add( LibItem.FONT_5X7 ); libItems.add( LibItem.M_XYPO ); libItems.add( LibItem.XPSET ); } + if( libItems.contains( LibItem.DRAWST ) ) { + /* + * DRAW-Macro ausfuehren, + * Der Macro-String folgt direkt hinter dem Aufruf. + * + * Parameter: + * DE: relative X-Koordinate + * HL: relative Y-Koordinate + */ + buf.append( "DRAWST:\tEX\t(SP),HL\n" + + "\tCALL\tDRAWS\n" + + "\tEX\tDE,HL\n" + + "\tEX\t(SP),HL\n" + + "\tRET\n" ); + libItems.add( LibItem.DRAWS ); + } + if( libItems.contains( LibItem.DRAWS ) ) { + /* + * DRAW-Macro ausfuehren + * + * Parameter: + * HL: Zeiger auf Macro-String + * Rueckgabe: + * DE: 1. Zeichen hinter der Zeichenkette + */ + buf.append( "DRAWS:\tEX\tDE,HL\n" + + "DRAWS1:\tXOR\tA\n" // Modus zuruecksetzen + + "\tLD\t(M_DRSM),A\n" + + "DRAWS2:\tLD\tA,(DE)\n" + + "\tINC\tDE\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" + + "\tCALL\tC_UPR\n" + + "\tCP\t42H\n" // B + + "\tJP\tZ,DRAWS_MOVE\n" + + "\tCP\t44H\n" // D + + "\tJR\tZ,DRAWS_DOWN\n" + + "\tCP\t45H\n" // E + + "\tJP\tZ,DRAWS_RIGHT_UP\n" + + "\tCP\t46H\n" // F + + "\tJR\tZ,DRAWS_RIGHT_DOWN\n" + + "\tCP\t47H\n" // G + + "\tJR\tZ,DRAWS_LEFT_DOWN\n" + + "\tCP\t48H\n" // H + + "\tJR\tZ,DRAWS_LEFT_UP\n" + + "\tCP\t4CH\n" // L + + "\tJR\tZ,DRAWS_LEFT\n" + + "\tCP\t4DH\n" // M + + "\tJP\tZ,DRAWS_TO\n" + + "\tCP\t52H\n" // R + + "\tJR\tZ,DRAWS_RIGHT\n" + + "\tCP\t55H\n" // U + + "\tJR\tZ,DRAWS_UP\n" + + "\tJP\tE_PARM\n" + + "DRAWS_DOWN:\n" + + "\tCALL\tDRAWS_NUM\n" + + "\tPUSH\tDE\n" + + "\tCALL\tNEGHL\n" + + "\tLD\tDE,0000H\n" + + "\tCALL\tDRAWS_REL\n" + + "\tPOP\tDE\n" + + "\tJR\tDRAWS1\n" + + "DRAWS_LEFT:\n" + + "\tCALL\tDRAWS_NUM\n" + + "\tPUSH\tDE\n" + + "\tCALL\tNEGHL\n" + + "\tEX\tDE,HL\n" + + "\tLD\tHL,0000H\n" + + "\tCALL\tDRAWS_REL\n" + + "\tPOP\tDE\n" + + "\tJR\tDRAWS1\n" + + "DRAWS_LEFT_DOWN:\n" + + "\tCALL\tDRAWS_NUM\n" + + "\tPUSH\tDE\n" + + "\tCALL\tNEGHL\n" + + "\tLD\tD,H\n" + + "\tLD\tE,L\n" + + "\tCALL\tDRAWS_REL\n" + + "\tPOP\tDE\n" + + "\tJR\tDRAWS1\n" + + "DRAWS_LEFT_UP:\n" + + "\tCALL\tDRAWS_NUM\n" + + "\tPUSH\tDE\n" + + "\tLD\tD,H\n" + + "\tLD\tE,L\n" + + "\tCALL\tNEGHL\n" + + "\tEX\tDE,HL\n" + + "\tCALL\tDRAWS_REL\n" + + "\tPOP\tDE\n" + + "\tJR\tDRAWS1\n" + + "DRAWS_RIGHT:\n" + + "\tCALL\tDRAWS_NUM\n" + + "\tPUSH\tDE\n" + + "\tEX\tDE,HL\n" + + "\tLD\tHL,0000H\n" + + "\tCALL\tDRAWS_REL\n" + + "\tPOP\tDE\n" + + "\tJP\tDRAWS1\n" + + "DRAWS_RIGHT_DOWN:\n" + + "\tCALL\tDRAWS_NUM\n" + + "\tPUSH\tDE\n" + + "\tLD\tD,H\n" + + "\tLD\tE,L\n" + + "\tCALL\tNEGHL\n" + + "\tCALL\tDRAWS_REL\n" + + "\tPOP\tDE\n" + + "\tJP\tDRAWS1\n" + + "DRAWS_RIGHT_UP:\n" + + "\tCALL\tDRAWS_NUM\n" + + "\tPUSH\tDE\n" + + "\tLD\tD,H\n" + + "\tLD\tE,L\n" + + "\tCALL\tDRAWS_REL\n" + + "\tPOP\tDE\n" + + "\tJP\tDRAWS1\n" + + "DRAWS_UP:\n" + + "\tCALL\tDRAWS_NUM\n" + + "\tPUSH\tDE\n" + + "\tLD\tDE,0000H\n" + + "\tCALL\tDRAWS_REL\n" + + "\tPOP\tDE\n" + + "\tJP\tDRAWS1\n" + + "DRAWS_TO:\n" + + "\tLD\tA,(DE)\n" + + "\tCP\t2BH\n" // + + + "\tJR\tZ,DRAWS_TO_REL\n" + + "\tCP\t2DH\n" // - + + "\tJR\tZ,DRAWS_TO_REL\n" + + "\tCALL\tDRAWS_PARSE_POINT\n" + + "\tLD\tA,(M_DRSM)\n" + + "\tOR\tA\n" + + "\tJR\tNZ,DRAWS_TO1\n" + + "\tPUSH\tDE\n" + + "\tLD\t(LINE_M_EX),BC\n" + + "\tLD\t(LINE_M_EY),HL\n" + + "\tCALL\tDRAW\n" + + "\tPOP\tDE\n" + + "\tJP\tDRAWS1\n" + + "DRAWS_TO1:\n" + + "\tLD\t(M_XPOS),BC\n" + + "\tLD\t(M_YPOS),HL\n" + + "\tJP\tDRAWS1\n" + + "DRAWS_TO_REL:\n" + + "\tCALL\tDRAWS_PARSE_POINT\n" + + "\tPUSH\tDE\n" + + "\tLD\tD,B\n" + + "\tLD\tE,C\n" + + "\tCALL\tDRAWS_REL\n" + + "\tPOP\tDE\n" + + "\tJP\tDRAWS1\n" + + "DRAWS_MOVE:\n" + + "\tLD\tA,01H\n" + + "\tLD\t(M_DRSM),A\n" + + "\tJP\tDRAWS2\n" + /* + * Parsen einer X/Y-Koordinate + * + * Parameter: + * DE: Zeiger auf die aktuelle Position im Macro-String + * Rueckgabe: + * BC: geparste X-Koordinate + * DE: Zeiger auf das erste Zeichen hinter der Zahl + * HL: geparste Y-Koordinate + */ + + "DRAWS_PARSE_POINT:\n" + + "\tCALL\tDRAWS_SIGNED_NUM\n" + + "\tPUSH\tHL\n" + + "\tLD\tA,(DE)\n" + + "\tCP\t2CH\n" // Komma + + "\tJP\tNZ,E_PARM\n" + + "\tINC\tDE\n" + + "\tCALL\tDRAWS_SIGNED_NUM\n" + + "\tPOP\tBC\n" + + "\tRET\n" + + "DRAWS_SIGNED_NUM:\n" + + "\tLD\tA,(DE)\n" + + "\tCP\t2BH\n" // + + + "\tJR\tNZ,DRAWS_SIGNED_NUM1\n" + + "\tINC\tDE\n" + + "\tJR\tDRAWS_SIGNED_NUM2\n" + + "DRAWS_SIGNED_NUM1:\n" + + "\tCP\t2DH\n" // - + + "\tJR\tNZ,DRAWS_SIGNED_NUM2\n" + + "\tINC\tDE\n" + + "\tCALL\tDRAWS_SIGNED_NUM2\n" + + "\tCALL\tNEGHL\n" + + "\tRET\n" + + "DRAWS_SIGNED_NUM2:\n" + + "\tLD\tA,(DE)\n" + + "\tSUB\t30H\n" + + "\tJP\tC,E_PARM\n" + + "\tCP\t0AH\n" + + "\tJP\tNC,E_PARM\n" + + "\tJR\tDRAWS_NUM1\n" + /* + * Parsen einer evtl. vorhandenen Zahl, + * Ist keine Zahl vorhanden, wird 1 zurueckgeliefert. + * + * Parameter: + * DE: Zeiger auf die aktuelle Position im Macro-String + * Rueckgabe: + * DE: Zeiger auf das erste Zeichen hinter der Zahl + * HL: geparste Zahl oder 1, wenn keine vorhanden ist + */ + + "DRAWS_NUM:\n" + + "\tLD\tA,(DE)\n" + + "\tSUB\t30H\n" + + "\tJR\tC,DRAWS_NUM3\n" + + "\tCP\t0AH\n" + + "\tJR\tNC,DRAWS_NUM3\n" + + "DRAWS_NUM1:\n" + + "\tLD\tL,A\n" + + "\tLD\tH,00H\n" + + "DRAWS_NUM2:\n" + + "\tINC\tDE\n" + + "\tLD\tA,(DE)\n" + + "\tSUB\t30H\n" + + "\tRET\tC\n" + + "\tCP\t0AH\n" + + "\tRET\tNC\n" + + "\tLD\tB,H\n" + + "\tLD\tC,L\n" + + "\tADD\tHL,HL\n" + + "\tADD\tHL,HL\n" + + "\tADD\tHL,BC\n" + + "\tADD\tHL,HL\n" + + "\tLD\tC,A\n" + + "\tLD\tB,00H\n" + + "\tADD\tHL,BC\n" + + "\tBIT\t7,H\n" + + "\tJP\tNZ,E_NOV\n" + + "\tJR\tDRAWS_NUM2\n" + + "DRAWS_NUM3:\n" + + "\tLD\tHL,0001H\n" + + "\tRET\n" + + "DRAWS_REL:\n" + + "\tLD\tA,(M_DRSM)\n" + + "\tOR\tA\n" + + "\tJR\tZ,DRAWR\n" + + "\tJP\tMOVER\n" ); + libItems.add( LibItem.C_UPR ); + libItems.add( LibItem.ABS_NEG_HL ); + libItems.add( LibItem.DRAW ); + libItems.add( LibItem.DRAWR ); + libItems.add( LibItem.MOVER ); + libItems.add( LibItem.E_NOV ); + libItems.add( LibItem.E_PARM ); + } if( libItems.contains( LibItem.DRAWR ) ) { /* * Linie zum relativ angegebenen Punkt zeichnen, * Das Pixel auf der aktuellen Position wird nicht gesetzt. * * Parameter: - * DE: rekative X-Koordinate - * HL: rekative Y-Koordinate + * M_XPOS: X-Koordinate Startpunkt + * M_YPOS: Y-Koordinate Startpunkt + * DE: relative X-Koordinate Endpunkt + * HL: relative Y-Koordinate Endpunt */ buf.append( "DRAWR:\tPUSH\tHL\n" + "\tLD\tHL,(M_XPOS)\n" - + "\tLD\t(M_LNBX),HL\n" + + "\tLD\t(LINE_M_BX),HL\n" + "\tCALL\tO_ADD\n" - + "\tLD\t(M_LNEX),HL\n" + + "\tLD\t(LINE_M_EX),HL\n" + "\tPOP\tDE\n" + "\tLD\tHL,(M_YPOS)\n" - + "\tLD\t(M_LNBY),HL\n" + + "\tLD\t(LINE_M_BY),HL\n" + "\tCALL\tO_ADD\n" - + "\tLD\t(M_LNEY),HL\n" + + "\tLD\t(LINE_M_EY),HL\n" + "\tJR\tDRAW1\n" ); libItems.add( LibItem.M_XYPO ); libItems.add( LibItem.O_ADD ); @@ -1145,76 +1232,219 @@ public static void appendCodeTo( BasicCompiler compiler ) } if( libItems.contains( LibItem.DRAW ) ) { /* - * Linie zum Punkt (M_LNXE,M_LNYE) zeichnen, + * Linie zum Punkt (LINE_M_EX,LINE_M_EY) zeichnen, * Das Pixel auf der aktuellen Position wird nicht gesetzt. * * Parameter: - * DE: rekative X-Koordinate - * HL: rekative Y-Koordinate + * M_XPOS: X-Koordinate Startpunkt + * M_YPOS: Y-Koordinate Startpunkt + * LINE_M_EX: X-Koordinate Endpunkt + * LINE_M_EY: Y-Koordinate Endpunkt */ buf.append( "DRAW:\tLD\tHL,(M_XPOS)\n" - + "\tLD\t(M_LNBX),HL\n" + + "\tLD\t(LINE_M_BX),HL\n" + "\tLD\tHL,(M_YPOS)\n" - + "\tLD\t(M_LNBY),HL\n" - + "DRAW1:\tCALL\tDRLIN1\n" - + "\tLD\tHL,(M_LNEX)\n" + + "\tLD\t(LINE_M_BY),HL\n" + + "DRAW1:\tCALL\tDRAW_LINE2\n" + + "\tLD\tHL,(LINE_M_EX)\n" + "\tLD\t(M_XPOS),HL\n" - + "\tLD\tHL,(M_LNEY)\n" + + "\tLD\tHL,(LINE_M_EY)\n" + "\tLD\t(M_YPOS),HL\n" + "\tRET\n" ); libItems.add( LibItem.M_XYPO ); - libItems.add( LibItem.DRLINE ); + libItems.add( LibItem.DRAW_LINE ); + } + if( libItems.contains( LibItem.DRAW_LINE ) ) { + /* + * Linie zeichnen + * + * Parameter: + * (LINE_M_BX): X-Koordinate Anfang + * (LINE_M_BY): Y-Koordinate Anfang + * (LINE_M_EX): X-Koordinate Ende + * (LINE_M_EY): Y-Koordinate Ende + * Hilfszellen: + * (LINE_M_SX): X-Schrittweite + * (LINE_M_SY): Y-Schrittweite + */ + buf.append( "DRAW_LINE:\n" ); + if( target.supportsXHLINE() ) { + buf.append( "\tLD\tHL,(LINE_M_EY)\n" + + "\tLD\tDE,(LINE_M_BY)\n" + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tJR\tNZ,DRAW_LINE1\n" + + "\tCALL\tH_BOX\n" + + "\tRET\tC\n" + + "\tJP\tDRAW_HLINE\n" + + "DRAW_LINE1:\n" ); + libItems.add( LibItem.H_BOX ); + libItems.add( LibItem.DRAW_HLINE ); + } + buf.append( "\tLD\tDE,(LINE_M_BX)\n" // Anfangspixel setzen + + "\tLD\tHL,(LINE_M_BY)\n" + + "\tCALL\tXPSET\n" + + "DRAW_LINE2:\n" + + "\tLD\tBC,0001H\n" // sx=1 + + "\tLD\tHL,(LINE_M_EX)\n" // dx=xe-xa + + "\tLD\tDE,(LINE_M_BX)\n" + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tBIT\t7,H\n" + + "\tJR\tZ,DRAW_LINE3\n" // if dx<0 then dx=-dx:sx=-1 + + "\tCALL\tNEGHL\n" + + "\tLD\tBC,0FFFFH\n" + + "DRAW_LINE3:\n" + + "\tLD\t(LINE_M_SX),BC\n" + + "\tPUSH\tHL\n" // dx + + "\tPUSH\tHL\n" // dx + + "\tLD\tBC,0001H\n" // sy=1 + + "\tLD\tHL,(LINE_M_EY)\n" // dy=ye-ya + + "\tLD\tDE,(LINE_M_BY)\n" + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tBIT\t7,H\n" + + "\tJR\tZ,DRAW_LINE4\n" // if dx<0 then dx=-dx:sx=-1 + + "\tCALL\tNEGHL\n" + + "\tLD\tBC,0FFFFH\n" + + "DRAW_LINE4:\n" + + "\tLD\t(LINE_M_SY),BC\n" + + "\tPUSH\tHL\n" // dy + + "\tEXX\n" + + "\tPOP\tBC\n" // BC': dy + + "\tPOP\tDE\n" // DE': dx + + "\tEXX\n" + + "\tEX\tDE,HL\n" // dy -> DE + + "\tPOP\tHL\n" // dx + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" // if dx= ABS(dy) + * 2. Registersatz: + * BC: dy + * DE: dx + * HL: Fehlerglied + */ + + "\tEXX\n" + + "\tLD\tH,D\n" // Fehlerglied mit + + "\tLD\tL,E\n" // dx/2 initialisieren + + "\tSRA\tH\n" + + "\tRR\tL\n" + + "\tEXX\n" + + "\tLD\tDE,(LINE_M_BX)\n" + + "DRAW_LINE5:\n" // Ende erreicht? + + "\tLD\tHL,(LINE_M_EX)\n" + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tRET\tZ\n" + + "\tEXX\n" // Test auf Y-Schritt + + "\tOR\tA\n" // Fehler = Fehler - dy + + "\tSBC\tHL,BC\n" + + "\tEXX\n" + + "\tJR\tNC,DRAW_LINE6\n" // wenn Fehler < 0 + + "\tEXX\n" + + "\tADD\tHL,DE\n" // Fehler = Fehler + dx + + "\tEXX\n" + + "\tLD\tHL,(LINE_M_BY)\n" // Y-Schritt + + "\tLD\tDE,(LINE_M_SY)\n" + + "\tADD\tHL,DE\n" + + "\tLD\t(LINE_M_BY),HL\n" + + "DRAW_LINE6:\n" // X-Schritt + + "\tLD\tHL,(LINE_M_BX)\n" + + "\tLD\tDE,(LINE_M_SX)\n" + + "\tADD\tHL,DE\n" + + "\tLD\t(LINE_M_BX),HL\n" + + "\tPUSH\tHL\n" + + "\tEX\tDE,HL\n" // Pixel setzen + + "\tLD\tHL,(LINE_M_BY)\n" + + "\tCALL\tXPSET\n" + + "\tPOP\tDE\n" + + "\tJR\tDRAW_LINE5\n" + /* + * ABS(dx) < ABS(dy) + * 2. Registersatz: + * BC: dy + * DE: dx + * HL: Fehlerglied + */ + + "DRAW_LINE7:\n" + + "\tEXX\n" + + "\tLD\tH,B\n" // Fehlerglied mit + + "\tLD\tL,C\n" // dy/2 initialisieren + + "\tSRA\tH\n" + + "\tRR\tL\n" + + "\tEXX\n" + + "\tLD\tDE,(LINE_M_BY)\n" + + "DRAW_LINE8:\n" // Ende erreicht? + + "\tLD\tHL,(LINE_M_EY)\n" + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tRET\tZ\n" + + "\tEXX\n" // Test auf X-Schritt + + "\tOR\tA\n" // Fehler = Fehler - dx + + "\tSBC\tHL,DE\n" + + "\tEXX\n" + + "\tJR\tNC,DRAW_LINE9\n" // wenn Fehler < 0 + + "\tEXX\n" + + "\tADD\tHL,BC\n" // Fehler = Fehler + dy + + "\tEXX\n" + + "\tLD\tHL,(LINE_M_BX)\n" // X-Schritt + + "\tLD\tDE,(LINE_M_SX)\n" + + "\tADD\tHL,DE\n" + + "\tLD\t(LINE_M_BX),HL\n" + + "DRAW_LINE9:\n" // Y-Schritt + + "\tLD\tHL,(LINE_M_BY)\n" + + "\tLD\tDE,(LINE_M_SY)\n" + + "\tADD\tHL,DE\n" + + "\tLD\t(LINE_M_BY),HL\n" + + "\tPUSH\tHL\n" + + "\tLD\tDE,(LINE_M_BX)\n" + + "\tCALL\tXPSET\n" // Pixel setzen + + "\tPOP\tDE\n" + + "\tJR\tDRAW_LINE8\n" ); + libItems.add( LibItem.ABS_NEG_HL ); + libItems.add( LibItem.M_XYPO ); + libItems.add( LibItem.XPSET ); } if( libItems.contains( LibItem.DRBOX ) ) { /* * Rechteck zeichnen * * Parameter: - * (M_LNBX): X-Koordinate Anfang - * (M_LNBY): Y-Koordinate Anfang - * (M_LNEX): X-Koordinate Ende - * (M_LNEY): Y-Koordinate Ende - * Hilfszellen: - * (M_LNSX): X-Schrittweite - * (M_LNSY): Y-Schrittweite + * (LINE_M_BX): X-Koordinate Anfang + * (LINE_M_BY): Y-Koordinate Anfang + * (LINE_M_EX): X-Koordinate Ende + * (LINE_M_EY): Y-Koordinate Ende */ buf.append( "DRBOX:\tCALL\tH_BOX\n" - + "\tPUSH\tBC\n" // oberste Line zeichen + + "\tRET\tC\n" + + "\tPUSH\tBC\n" + "\tPUSH\tDE\n" - + "\tPUSH\tHL\n" ); - if( target.supportsXHLINE() ) { - buf.append( "\tCALL\tXHLINE\n" ); - libItems.add( LibItem.XHLINE ); - } else { - buf.append( "\tCALL\tDRHLIN\n" ); - libItems.add( LibItem.DRHLIN ); - } - buf.append( "\tPOP\tHL\n" + + "\tPUSH\tHL\n" + + "\tCALL\tDRAW_HLINE\n" + + "\tPOP\tHL\n" + "\tPOP\tDE\n" + "\tPOP\tBC\n" - + "\tLD\tA,(M_LNEY)\n" + + "\tLD\tA,(LINE_M_EY)\n" + "\tCP\tL\n" + "\tJR\tNZ,DRBOX1\n" - + "\tLD\tA,(M_LNEY+1)\n" + + "\tLD\tA,(LINE_M_EY+1)\n" + "\tCP\tH\n" + "\tRET\tZ\n" + "DRBOX1:\tINC\tHL\n" - + "\tLD\tA,(M_LNEY)\n" + + "\tLD\tA,(LINE_M_EY)\n" + "\tCP\tL\n" + "\tJR\tNZ,DRBOX2\n" - + "\tLD\tA,(M_LNEY+1)\n" - + "\tCP\tH\n" ); - if( target.supportsXHLINE() ) { - buf.append( "\tJP\tZ,XHLINE\n" ); - } else { - buf.append( "\tJP\tZ,DRHLIN\n" ); - } - buf.append( "DRBOX2:\tPUSH\tBC\n" + + "\tLD\tA,(LINE_M_EY+1)\n" + + "\tCP\tH\n" + + "\tJP\tZ,DRAW_HLINE\n" + + "DRBOX2:\tPUSH\tBC\n" + "\tPUSH\tDE\n" + "\tPUSH\tHL\n" + "\tCALL\tXPSET\n" + "\tPOP\tHL\n" - + "\tLD\tDE,(M_LNEX)\n" + + "\tLD\tDE,(LINE_M_EX)\n" + "\tPUSH\tHL\n" + "\tCALL\tXPSET\n" + "\tPOP\tHL\n" @@ -1222,6 +1452,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tPOP\tBC\n" + "\tJR\tDRBOX1\n" ); libItems.add( LibItem.H_BOX ); + libItems.add( LibItem.DRAW_HLINE ); libItems.add( LibItem.XPSET ); } if( libItems.contains( LibItem.DRBOXF ) ) { @@ -1229,259 +1460,576 @@ public static void appendCodeTo( BasicCompiler compiler ) * ausgefuelltes Rechteck zeichnen * * Parameter: - * (M_LNBX): X-Koordinate Anfang - * (M_LNBY): Y-Koordinate Anfang - * (M_LNEX): X-Koordinate Ende - * (M_LNEY): Y-Koordinate Ende - * Hilfszellen: - * (M_LNSX): X-Schrittweite - * (M_LNSY): Y-Schrittweite + * (LINE_M_BX): X-Koordinate Anfang + * (LINE_M_BY): Y-Koordinate Anfang + * (LINE_M_EX): X-Koordinate Ende + * (LINE_M_EY): Y-Koordinate Ende */ buf.append( "DRBOXF:CALL\tH_BOX\n" - + "DRBXF1:\tPUSH\tBC\n" // oberste Line zeichen + + "\tRET\tC\n" + + "\tCALL\tDRAW_HLINE_CHECK_X\n" + + "DRBXF1:\tPUSH\tBC\n" + "\tPUSH\tDE\n" + "\tPUSH\tHL\n" ); if( target.supportsXHLINE() ) { buf.append( "\tCALL\tXHLINE\n" ); libItems.add( LibItem.XHLINE ); } else { - buf.append( "\tCALL\tDRHLIN\n" ); - libItems.add( LibItem.DRHLIN ); + buf.append( "\tCALL\tDRAW_HLINE1\n" ); } buf.append( "\tPOP\tHL\n" + "\tPOP\tDE\n" + "\tPOP\tBC\n" - + "\tLD\tA,(M_LNEY)\n" + + "\tLD\tA,(LINE_M_EY)\n" + "\tCP\tL\n" + "\tJR\tNZ,DRFBX2\n" - + "\tLD\tA,(M_LNEY+1)\n" + + "\tLD\tA,(LINE_M_EY+1)\n" + "\tCP\tH\n" + "\tRET\tZ\n" + "DRFBX2:\tINC\tHL\n" + "\tJR\tDRBXF1\n" ); libItems.add( LibItem.H_BOX ); + libItems.add( LibItem.DRAW_HLINE ); } if( libItems.contains( LibItem.H_BOX ) ) { /* * Anpassung der Koordinaten fuer das Zeichen eines Rechtecks * * Parameter: - * (M_LNBX): X-Koordinate Anfang - * (M_LNBY): Y-Koordinate Anfang - * (M_LNEX): X-Koordinate Ende - * (M_LNEY): Y-Koordinate Ende + * (LINE_M_BX): X-Koordinate Anfang + * (LINE_M_BY): Y-Koordinate Anfang + * (LINE_M_EX): X-Koordinate Ende + * (LINE_M_EY): Y-Koordinate Ende * Rueckgabewerte: - * (M_LNBX): linke X-Koordinate - * (M_LNBY): untere Y-Koordinate - * (M_LNEX): rechte X-Koordinate - * (M_LNEY): ober Y-Koordinate + * (LINE_M_BX): linke X-Koordinate + * (LINE_M_BY): untere Y-Koordinate + * (LINE_M_EX): rechte X-Koordinate + * (LINE_M_EY): ober Y-Koordinate + * DE: linke X-Koordinate + * HL: untere Y-Koordinate + * BC: Breite des Rechtecks - 1 + * CY=1: rechte X- oder obere Y-Koordinate kleiner 0, + * d.h., das Rechteck liegt volstaendig ausserhalb + * des sichtbaren Bereichs + */ + buf.append( "H_BOX:\tLD\tHL,(LINE_M_EY)\n" + + "\tLD\tDE,(LINE_M_BY)\n" + + "\tCALL\tCPHLDE\n" + + "\tJR\tNC,H_BOX1\n" + + "\tEX\tDE,HL\n" + + "\tLD\t(LINE_M_BY),DE\n" + + "\tLD\t(LINE_M_EY),HL\n" + + "H_BOX1:\tBIT\t7,H\n" + + "\tJR\tNZ,H_BOX3\n" + + "\tLD\tHL,(LINE_M_EX)\n" + + "\tLD\tDE,(LINE_M_BX)\n" + + "\tCALL\tCPHLDE\n" + + "\tJR\tNC,H_BOX2\n" + + "\tEX\tDE,HL\n" + + "\tLD\t(LINE_M_BX),DE\n" + + "\tLD\t(LINE_M_EX),HL\n" + + "H_BOX2:\tBIT\t7,H\n" + + "\tJR\tNZ,H_BOX3\n" + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tLD\tB,H\n" // BC = X2 - X1 + + "\tLD\tC,L\n" + + "\tLD\tHL,(LINE_M_BY)\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" + + "H_BOX3:\tSCF\n" + + "\tRET\n" ); + libItems.add( LibItem.CPHLDE ); + } + if( libItems.contains( LibItem.DRAW_HLINE ) ) { + /* + * Horizontale Linie zeichnen + * + * Parameter: + * BC: Laenge - 1 + * DE: linke X-Koordinate + * HL: Y-Koordinate + */ + buf.append( "DRAW_HLINE:\n" + + "\tCALL\tDRAW_HLINE_CHECK_X\n" + + "DRAW_HLINE1:\n" + + "\tBIT\t7,H\n" // Y pruefen + + "\tRET\tNZ\n" + + "\tBIT\t7,B\n" // Laenge pruefen + + "\tRET\tNZ\n" ); + if( target.supportsXHLINE() ) { + buf.append( "\tJP\tXHLINE\n" ); + libItems.add( LibItem.XHLINE ); + } else { + buf.append( "DRAW_HLINE2:\n" + + "\tPUSH\tBC\n" + + "\tPUSH\tDE\n" + + "\tPUSH\tHL\n" + + "\tCALL\tXPSET\n" + + "\tPOP\tHL\n" + + "\tPOP\tDE\n" + + "\tPOP\tBC\n" + + "\tLD\tA,B\n" + + "\tOR\tC\n" + + "\tRET\tZ\n" + + "\tDEC\tBC\n" + + "\tINC\tDE\n" + + "\tJR\tDRAW_HLINE2\n" ); + } + /* + * Sicherstellen, dass die X-Koordinate nicht kleiner 0 ist + * + * Parameter: + * BC: Laenge - 1 * DE: linke X-Koordinate - * HL: untere Y-Koordinate - * BC: Breite des Rechtecks - 1 */ - buf.append( "H_BOX:\tLD\tHL,(M_LNEY)\n" - + "\tLD\tDE,(M_LNBY)\n" + buf.append( "DRAW_HLINE_CHECK_X:\n" + + "\tBIT\t7,D\n" + + "\tRET\tZ\n" + + "\tEX\tDE,HL\n" + + "\tADD\tHL,BC\n" + + "\tLD\tB,H\n" + + "\tLD\tC,L\n" + + "\tEX\tDE,HL\n" + + "\tLD\tDE,0000H\n" + + "\tRET\n" ); + libItems.add( LibItem.XPSET ); + } + if( libItems.contains( LibItem.MOVER ) ) { + /* + * Grafikkursor relativ positionieren + * + * Parameter: + * DE: X-Koordinate relativ + * HL: Y-Koordinate relativ + */ + buf.append( "MOVER:\tPUSH\tHL\n" + + "\tLD\tHL,(M_XPOS)\n" + + "\tCALL\tO_ADD\n" + + "\tLD\t(M_XPOS),HL\n" + + "\tPOP\tDE\n" + + "\tLD\tHL,(M_YPOS)\n" + + "\tCALL\tO_ADD\n" + + "\tLD\t(M_YPOS),HL\n" + + "\tRET\n" ); + libItems.add( LibItem.O_ADD ); + libItems.add( LibItem.M_XYPO ); + } + if( libItems.contains( LibItem.PAINT ) ) { + /* + * Fuellen einer Flaeche + * + * Der Algorithmus arbeitet mit einer Tabelle, + * in der die zu pruefenden Linien einschliesslich + * der weiteren Suchrichtung eingetragen werden. + * Ausgehend vom Startpunkt wird die erste Linie gefuellt + * und im Erfolgsfall der gefundene X-Bereich fuer Y-1 und Y+1 + * in die Tabelle eingetragen. + * Anschliessend wird die Tabelle durchgegangen und pro Eintrag + * der eingetragene X-Bereich fuer die Y-Position gefuellt. + * Konnte wieder eine Linie gefuellt werden, wird der entsprechende + * X-Bereich mit der naechsten Y-Koordinate in Suchrichtung + * wieder in die Tabelle eingetragen usw. + * Wenn eine gefundene zu fuellende Linie links oder rechts + * mehr als einen Pixel ueber die vorherige gefuellte Linie + * hinausragt, wird der ueber das eine Pixel hinausragende Bereich + * mit umgekehrter Suchrichtung in die Tabelle eingetragen, + * um auch um Ecken herum zu fuellen. + * + * Fuer die Tabelle wird ein Block aus dem Heap genommen. + * Dieser Block wird jedoch nicht allokiert, + * da er nur waehrend der PAINT-Routine benoetigt wird und + * in dieser Zeit keine weiteren speicherallokierenden + * Funktionen aufgerufen werden. + * + * Die Tabelle ist als Stack realisiert. + * + * Ein Eintrag in der Tabelle hat folgenden Aufbau: + * 1. Byte: L-Teil der X-Koordinate des Suchanfangs + * 2. Byte: Bit 0-6: H-Teil der X-Koordinate des Suchanfangs + * Bit 7: Suchrichtung: + * 0: nach oben (Y aufsteigend) + * 1: nach unten (Y absteigend) + * 3. Byte: L-Teil der X-Koordinate des Suchendes + * 4. Byte: H-Teil der X-Koordinate des Suchendes + * 5. Byte: Y-Koordinate (nur 8 Bit) + * + * Parameter: + * PAINT_M_X: X-Koordinate des Ausgangspunktes + * PAINT_M_Y: Y-Koordinate des Ausgangspunktes + * Hilfszellen: + * PAINT_M_TAD: Anfangsadresse der Tabelle + * PAINT_M_TSZ: Max. Groesse der Tabelle + * PAINT_M_TIX: Index des ersten freien Eintrags in der Tabelle + */ + buf.append( "PAINT:" ); + // schneller Zugriff auf Bildschirmbreite + int pos = buf.length(); + target.appendWPixelTo( buf ); + String inst_LD_HL_wPix = buf.cut( pos ); + if( !BasicUtil.isSingleInst_LD_HL_xx( inst_LD_HL_wPix ) ) { + buf.append( inst_LD_HL_wPix ); + buf.append( "\tLD\t(PAINT_M_WPIX),HL\n" ); + inst_LD_HL_wPix = "\tLD\tHL,(PAINT_M_WPIX)\n"; + libItems.add( LibItem.PAINT_M_WPIX ); + } + // schneller Zugriff auf Bildschirmhoehe + pos = buf.length(); + target.appendHPixelTo( buf ); + String inst_LD_HL_hPix = buf.cut( pos ); + if( !BasicUtil.isSingleInst_LD_HL_xx( inst_LD_HL_hPix ) ) { + buf.append( inst_LD_HL_hPix ); + buf.append( "\tLD\t(PAINT_M_HPIX),HL\n" ); + inst_LD_HL_hPix = "\tLD\tHL,(PAINT_M_HPIX)\n"; + libItems.add( LibItem.PAINT_M_HPIX ); + } + if( target.supportsXPAINT_LEFT_RIGHT() ) { + buf.append( "\tCALL\tXPAINT_RIGHT\n" + + "\tRET\tC\n" + + "\tCALL\tXPAINT_LEFT\n" ); + } else { + buf.append( "\tCALL\tPAINT_RIGHT\n" + + "\tRET\tC\n" + + "\tCALL\tPAINT_LEFT\n" ); + } + // Startpunkt konnte gefuellt werden -> Speicher fuer Tabelle holen + buf.append( "\tCALL\tMFIND\n" + + "\tEX\tDE,HL\n" + + "\tLD\t(PAINT_M_TAD),HL\n" + // Groesse des Blocks ermitteln + + "\tDEC\tHL\n" + + "\tDEC\tHL\n" + + "\tLD\tD,(HL)\n" + + "\tDEC\tHL\n" + + "\tLD\tE,(HL)\n" + + "\tEX\tDE,HL\n" + // Anzahl der Eintraege ermitteln + + "\tLD\tDE,0005H\n" + + "\tCALL\tO_DIV\n" + + "\tLD\tA,H\n" + + "\tOR\tA\n" + + "\tJR\tZ,PAINT1\n" + + "\tLD\tL,00H\n" + + "\tJP\tM,PAINT1\n" + + "\tDEC\tL\n" + + "PAINT1:\tLD\tA,L\n" + + "\tOR\tA\n" + + "\tJP\tZ,E_OUT_OF_MEM\n" + + "\tLD\t(PAINT_M_TSZ),A\n" + + "\tXOR\tA\n" + + "\tLD\t(PAINT_M_TIX),A\n" + // Bereich ueber und unter der gefuellten Linie anhaengen + + "\tCALL\tPAINT_ADD\n" // A=0 -> nach oben suchen + + "\tLD\tA,80H\n" // nach unten suchen + + "\tCALL\tPAINT_ADD\n" + // Tabelle durchgehen + + "PAINT2:\tLD\tA,(PAINT_M_TIX)\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" // kein weiterer Eintrag + // Eintrag holen + + "\tDEC\tA\n" + + "\tLD\t(PAINT_M_TIX),A\n" + + "\tINC\tA\n" + + "\tCALL\tPAINT_GET_ENTRY_ADDR\n" + + "\tDEC\tHL\n" + + "\tLD\tE,(HL)\n" + + "\tLD\tD,00H\n" + + "\tLD\t(PAINT_M_Y),DE\n" + + "\tDEC\tHL\n" + + "\tLD\tD,(HL)\n" + + "\tDEC\tHL\n" + + "\tLD\tE,(HL)\n" + + "\tLD\t(PAINT_M_SX2),DE\n" + + "\tDEC\tHL\n" + + "\tLD\tA,(HL)\n" + + "\tLD\t(PAINT_M_SDIR),A\n" // weitere Suchrichtung + + "\tDEC\tHL\n" + + "\tLD\tL,(HL)\n" + + "\tAND\t7FH\n" + + "\tLD\tH,A\n" + + "\tLD\t(PAINT_M_X),HL\n" + /* + * Eckpunkte berechnen, ab denen auch in die andere Richtung + * gesucht werden muss + */ + + "\tLD\tA,H\n" + + "\tOR\tL\n" + + "\tJR\tZ,PAINT3\n" + + "\tDEC\tHL\n" + + "PAINT3:\tLD\t(PAINT_M_CX1),HL\n" + + "\tINC\tDE\n" ); + buf.append( inst_LD_HL_wPix ); + buf.append( "\tEX\tDE,HL\n" + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tADD\tHL,DE\n" + + "\tJR\tC,PAINT4\n" + + "\tDEC\tHL\n" + + "PAINT4:\tLD\t(PAINT_M_CX2),HL\n" ); + // am Startpunkt nach links und rechts suchen + if( target.supportsXPAINT_LEFT_RIGHT() ) { + buf.append( "\tCALL\tXPAINT_RIGHT\n" + + "\tJR\tC,PAINT8\n" // naechste X-Koordinate + + "\tCALL\tXPAINT_LEFT\n" ); + } else { + buf.append( "\tCALL\tPAINT_RIGHT\n" + + "\tJR\tC,PAINT8\n" // naechste X-Koordinate + + "\tCALL\tPAINT_LEFT\n" ); + } + buf.append( "\tLD\tA,(PAINT_M_SDIR)\n" + + "\tCALL\tPAINT_ADD\n" + // X1 < CX1 ? + + "\tLD\tHL,(PAINT_M_X2)\n" + + "\tPUSH\tHL\n" + + "\tLD\tHL,(PAINT_M_X1)\n" + + "\tLD\tDE,(PAINT_M_CX1)\n" + "\tOR\tA\n" + "\tSBC\tHL,DE\n" - + "\tJR\tNC,H_BOX1\n" - + "\tADD\tHL,DE\n" // Y-Koordinaten tauschen - + "\tLD\t(M_LNBY),HL\n" - + "\tLD\t(M_LNEY),DE\n" - + "H_BOX1:\tLD\tHL,(M_LNEX)\n" - + "\tLD\tDE,(M_LNBX)\n" + + "\tJR\tNC,PAINT5\n" + + "\tDEC\tDE\n" + + "\tLD\t(PAINT_M_X2),DE\n" + + "\tLD\tA,(PAINT_M_SDIR)\n" + + "\tXOR\t80H\n" + + "\tCALL\tPAINT_ADD\n" + // X2 > CX2 ? + + "PAINT5:\tPOP\tDE\n" // X2 + + "PAINT6:\tLD\tHL,(PAINT_M_CX2)\n" + "\tOR\tA\n" + "\tSBC\tHL,DE\n" - + "\tLD\tB,H\n" // BC = X2 - X1 - + "\tLD\tC,L\n" - + "\tJR\tNC,H_BOX2\n" - + "\tADD\tHL,DE\n" // X-Koordinaten tauschen - + "\tLD\t(M_LNBX),HL\n" - + "\tLD\t(M_LNEX),DE\n" - + "\tEX\tDE,HL\n" // DE = X1 - + "\tLD\tHL,0000H\n" // BC anpassen - + "\tSBC\tHL,BC\n" - + "\tLD\tB,H\n" - + "\tLD\tC,L\n" - + "H_BOX2:\tLD\tHL,(M_LNBY)\n" - + "\tRET\n" ); - } - if( libItems.contains( LibItem.DRHLIN ) ) { - /* - * Horizontale Linie zeichnen - * - * Parameter: - * BC: Laenge - 1 - * DE: linke X-Koordinate - * HL: Y-Koordinate - */ - buf.append( "DRHLIN:\tBIT\t7,B\n" // Laenge pruefen - + "\tRET\tNZ\n" - + "\tBIT\t7,H\n" // Y pruefen - + "\tRET\tNZ\n" - + "DRHLN1:\tPUSH\tBC\n" + + "\tJR\tNC,PAINT7\n" + + "\tADD\tHL,DE\n" + + "\tINC\tHL\n" + + "\tLD\t(PAINT_M_X1),HL\n" + + "\tLD\t(PAINT_M_X1),DE\n" + + "\tLD\tA,(PAINT_M_SDIR)\n" + + "\tXOR\t80H\n" + + "\tCALL\tPAINT_ADD\n" + // bei X2+2 weitersuchen + + "PAINT7:\tLD\tDE,(PAINT_M_X2)\n" + + "\tINC\tDE\n" + + "\tJR\tPAINT9\n" + + "PAINT8:\tLD\tDE,(PAINT_M_X)\n" + + "PAINT9:\tINC\tDE\n" + + "\tLD\tHL,(PAINT_M_SX2)\n" + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tJP\tC,PAINT2\n" + + "\tLD\t(PAINT_M_X),DE\n" ); + // von der neuen X-Koordinate aus nur noch nach rechts suchen + if( target.supportsXPAINT_LEFT_RIGHT() ) { + buf.append( "\tCALL\tXPAINT_RIGHT\n" ); + } else { + buf.append( "\tCALL\tPAINT_RIGHT\n" ); + } + buf.append( "\tJR\tC,PAINT8\n" + + "\tLD\tHL,(PAINT_M_X)\n" + + "\tLD\t(PAINT_M_X1),HL\n" + + "\tLD\tA,(PAINT_M_SDIR)\n" + + "\tCALL\tPAINT_ADD\n" + + "\tLD\tDE,(PAINT_M_X2)\n" + + "\tJR\tPAINT6\n" ); + if( !target.supportsXPAINT_LEFT_RIGHT() ) { + /* + * Fuellen einer Linie vom Startpunkt aus nach links, + * Der Startpunkt selbst wird nicht geprueft, + * da die Routine hinter einem erfolgreichen PAINT_RIGHT + * aufgerufen wird und somit der Startpunkt schon gefuellt wurde. + * Ebenso erfolgt keine Bereichsruefung der Startkoordinaten. + * + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabewerte: + * PAINT_M_X1: linke X-Koordinate der gefuellten Linie + */ + buf.append( "PAINT_LEFT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "PAINT_LEFT1:\n" + + "\tLD\tA,D\n" + + "\tOR\tE\n" + + "\tJR\tZ,PAINT_LEFT2\n" + + "\tDEC\tDE\n" + "\tPUSH\tDE\n" - + "\tPUSH\tHL\n" - + "\tCALL\tXPSET\n" - + "\tPOP\tHL\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tCALL\tXPAINT\n" + "\tPOP\tDE\n" - + "\tPOP\tBC\n" - + "\tLD\tA,B\n" - + "\tOR\tC\n" - + "\tRET\tZ\n" - + "\tDEC\tBC\n" + + "\tJR\tNC,PAINT_LEFT1\n" + + "PAINT_LEFT2:\n" + "\tINC\tDE\n" - + "\tJR\tDRHLN1\n" ); - libItems.add( LibItem.XPSET ); - } - if( libItems.contains( LibItem.DRLINE ) ) { + + "\tLD\t(PAINT_M_X1),DE\n" + + "\tRET\n" + /* + * Fuellen einer Linie vom Startpunkt aus nach rechts, + * Der Startpunkt selbst wird als erstes geprueft und gefuellt. + * + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabewerte: + * CY=1: Startpunkt schon gefuellt oder ausserhalb + * des sichtbaren Bereichs + * PAINT_M_X1: linke X-Koordinate der gefuellten Linie + */ + + "PAINT_RIGHT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tPUSH\tDE\n" + + "\tCALL\tXPAINT\n" + + "\tPOP\tDE\n" + + "\tRET\tC\n" + + "PAINT_RIGHT1:\n" + + "\tINC\tDE\n" ); + buf.append( inst_LD_HL_wPix ); + buf.append( "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tJR\tZ,PAINT_RIGHT2\n" + + "\tJR\tC,PAINT_RIGHT2\n" + + "\tPUSH\tDE\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tCALL\tXPAINT\n" + + "\tPOP\tDE\n" + + "\tJR\tNC,PAINT_RIGHT1\n" + + "PAINT_RIGHT2:\n" + + "\tDEC\tDE\n" + + "\tLD\t(PAINT_M_X2),DE\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + } /* - * Linie zeichnen + * Anhaengen eines horizentalen Suchbereichs an die Tabelle, + * Dabei erfolgt auch eine Bereichspruefung auf die Y-Koordinate. * * Parameter: - * (M_LNBX): X-Koordinate Anfang - * (M_LNBY): Y-Koordinate Anfang - * (M_LNEX): X-Koordinate Ende - * (M_LNEY): Y-Koordinate Ende - * Hilfszellen: - * (M_LNSX): X-Schrittweite - * (M_LNSY): Y-Schrittweite + * A: Bit 7: Suchrichtung: 0=hochwaerts, 1=runterwaerts + * PAINT_M_X1: linke X-Koordinate des Suchbereichs + * PAINT_M_X2: rechte X-Koordinate des Suchbereichs + * PAINT_M_Y: Ausgangs-Y-Koordinate des Suchbereichs + * hinzugefuegt wird entweder Y-1 oder Y+1 + * PAINT_M_TAD: Anfangsadresse der Tabelle + * PAINT_M_TSZ: maximale Groesse (Anzahl Eintraege) der Tabelle + * PAINT_M_TIX: Index in der Tabelle + * Rueckgabewerte: + * PAINT_M_TIX: neuer Index in der Tabelle */ - buf.append( "DRLINE:\tLD\tDE,(M_LNBX)\n" // Anfangspixel setzen - + "\tLD\tHL,(M_LNBY)\n" - + "\tCALL\tXPSET\n" - + "DRLIN1:\tLD\tBC,0001H\n" // sx=1 - + "\tLD\tHL,(M_LNEX)\n" // dx=xe-xa - + "\tLD\tDE,(M_LNBX)\n" - + "\tOR\tA\n" - + "\tSBC\tHL,DE\n" - + "\tJR\tNC,DRLIN2\n" // if dx<0 then dx=-dx:sx=-1 - + "\tCALL\tNEGHL\n" - + "\tLD\tBC,0FFFFH\n" - + "DRLIN2:\tLD\t(M_LNSX),BC\n" - + "\tPUSH\tHL\n" // dx - + "\tPUSH\tHL\n" // dx - + "\tLD\tBC,0001H\n" // sy=1 - + "\tLD\tHL,(M_LNEY)\n" // dy=ye-ya - + "\tLD\tDE,(M_LNBY)\n" - + "\tOR\tA\n" + buf.append( "PAINT_ADD:\n" + + "\tAND\t80H\n" + + "\tLD\tB,A\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tBIT\t7,B\n" + + "\tJR\tNZ,PAINT_ADD1\n" + // Y=Y+1 + + "\tINC\tHL\n" + + "\tEX\tDE,HL\n" ); + buf.append( inst_LD_HL_hPix ); + buf.append( "\tOR\tA\n" + "\tSBC\tHL,DE\n" - + "\tJR\tNC,DRLIN3\n" // if dy<0 then dy=-dy:sy=-1 - + "\tCALL\tNEGHL\n" - + "\tLD\tBC,0FFFFH\n" - + "DRLIN3:\tLD\t(M_LNSY),BC\n" - + "\tPUSH\tHL\n" // dx - + "\tEXX\n" - + "\tPOP\tBC\n" // BC': dy - + "\tPOP\tDE\n" // DE': dx - + "\tEXX\n" - + "\tEX\tDE,HL\n" // dy -> DE - + "\tPOP\tHL\n" // dx - + "\tOR\tA\n" - + "\tSBC\tHL,DE\n" // if dx= ABS(dy) - * 2. Registersatz: - * BC: dy - * DE: dx - * HL: Fehlerglied - */ - + "\tEXX\n" - + "\tLD\tH,D\n" // Fehlerglied mit - + "\tLD\tL,E\n" // dx/2 initialisieren - + "\tSRA\tH\n" - + "\tRR\tL\n" - + "\tEXX\n" - + "DRLIN4:\tEXX\n" // Test auf Y-Schritt - + "\tOR\tA\n" // Fehler = Fehler - dy - + "\tSBC\tHL,BC\n" - + "\tEXX\n" - + "\tJR\tNC,DRLIN5\n" // wenn Fehler < 0 - + "\tEXX\n" - + "\tADD\tHL,DE\n" // Fehler = Fehler + dx - + "\tEXX\n" - + "\tLD\tHL,(M_LNBY)\n" // Y-Schritt - + "\tLD\tDE,(M_LNSY)\n" - + "\tADD\tHL,DE\n" - + "\tLD\t(M_LNBY),HL\n" - + "DRLIN5:\tLD\tHL,(M_LNBX)\n" // X-Schritt - + "\tLD\tDE,(M_LNSX)\n" - + "\tADD\tHL,DE\n" - + "\tLD\t(M_LNBX),HL\n" + + "\tRET\tZ\n" + + "\tRET\tC\n" + + "\tEX\tDE,HL\n" + + "\tJR\tPAINT_ADD2\n" + // Y=Y-1 + + "PAINT_ADD1:\n" + + "\tLD\tA,H\n" + + "\tOR\tL\n" + + "\tRET\tZ\n" + + "\tDEC\tHL\n" + // Tabelleneintrag schreiben + + "PAINT_ADD2:\n" + + "\tLD\tA,(PAINT_M_TSZ)\n" + + "\tLD\tC,A\n" + + "\tLD\tA,(PAINT_M_TIX)\n" + + "\tCP\tC\n" + + "\tJP\tNC,E_OUT_OF_MEM\n" + + "\tLD\tC,A\n" + "\tPUSH\tHL\n" - + "\tEX\tDE,HL\n" // Pixel setzen - + "\tLD\tHL,(M_LNBY)\n" - + "\tCALL\tXPSET\n" - + "\tPOP\tDE\n" // Ende erreicht? - + "\tLD\tHL,(M_LNEX)\n" - + "\tOR\tA\n" - + "\tSBC\tHL,DE\n" - + "\tJR\tNZ,DRLIN4\n" + + "\tCALL\tPAINT_GET_ENTRY_ADDR\n" + // X-Koordinate Suchanfang und weitere Suchrichtung + + "\tLD\tDE,(PAINT_M_X1)\n" + + "\tLD\t(HL),E\n" + + "\tINC\tHL\n" + + "\tLD\tA,D\n" + + "\tOR\tB\n" + + "\tLD\t(HL),A\n" + // X-Koordinate Suchende + + "\tINC\tHL\n" + + "\tLD\tDE,(PAINT_M_X2)\n" + + "\tLD\t(HL),E\n" + + "\tINC\tHL\n" + + "\tLD\t(HL),D\n" + // Y-Koordinate + + "\tINC\tHL\n" + + "\tPOP\tDE\n" + + "\tLD\t(HL),E\n" + // Schreibindex inkrementieren + + "\tLD\tA,C\n" + + "\tINC\tA\n" + + "\tLD\t(PAINT_M_TIX),A\n" + "\tRET\n" /* - * ABS(dx) < ABS(dy) - * 2. Registersatz: - * BC: dy - * DE: dx - * HL: Fehlerglied + * Adresse eines Eintrags in der Tabelle berechnen + * + * Parameter: + * PAINT_M_TAD: Anfangsadresse der Tabelle + * A: Index des Eintrags in der Tabelle */ - + "DRLIN6:\tEXX\n" - + "\tLD\tH,B\n" // Fehlerglied mit - + "\tLD\tL,C\n" // dy/2 initialisieren - + "\tSRA\tH\n" - + "\tRR\tL\n" - + "\tEXX\n" - + "DRLIN7:\tEXX\n" // Test auf X-Schritt - + "\tOR\tA\n" // Fehler = Fehler - dx - + "\tSBC\tHL,DE\n" - + "\tEXX\n" - + "\tJR\tNC,DRLIN8\n" // wenn Fehler < 0 - + "\tEXX\n" - + "\tADD\tHL,BC\n" // Fehler = Fehler + dy - + "\tEXX\n" - + "\tLD\tHL,(M_LNBX)\n" // X-Schritt - + "\tLD\tDE,(M_LNSX)\n" + + "PAINT_GET_ENTRY_ADDR:\n" + + "\tLD\tL,A\n" + + "\tLD\tH,00H\n" + // Index in HL mit 5 multiplizieren + + "\tLD\tD,H\n" + + "\tLD\tE,L\n" + + "\tADD\tHL,HL\n" + + "\tADD\tHL,HL\n" + "\tADD\tHL,DE\n" - + "\tLD\t(M_LNBX),HL\n" - + "DRLIN8:\tLD\tHL,(M_LNBY)\n" // Y-Schritt - + "\tLD\tDE,(M_LNSY)\n" + // Anfangsadresse addieren + + "\tLD\tDE,(PAINT_M_TAD)\n" + "\tADD\tHL,DE\n" - + "\tLD\t(M_LNBY),HL\n" - + "\tPUSH\tHL\n" - + "\tLD\tDE,(M_LNBX)\n" - + "\tCALL\tXPSET\n" // Pixel setzen - + "\tPOP\tDE\n" // Ende erreicht? - + "\tLD\tHL,(M_LNEY)\n" - + "\tOR\tA\n" - + "\tSBC\tHL,DE\n" - + "\tJR\tNZ,DRLIN7\n" + "\tRET\n" ); - libItems.add( LibItem.ABS_NEG_HL ); - libItems.add( LibItem.M_XYPO ); - libItems.add( LibItem.XPSET ); + libItems.add( LibItem.MFIND ); + libItems.add( LibItem.O_DIV ); + libItems.add( LibItem.XPAINT ); } if( libItems.contains( LibItem.PAUSE ) ) { + /* + * Warten auf Druecken der Leertaste + */ + buf.append( "PAUSE:\tCALL\tXINCH\n" + + "\tCP\t20H\n" + + "\tJR\tNZ,PAUSE\n" + + "\tRET\n" ); + libItems.add( LibItem.XINCH ); + } + if( libItems.contains( LibItem.PAUSE_N ) ) { /* * Warten * * Parameter: * HL: Wartezeit in 1/10 Sekunen */ - buf.append( "PAUSE:\tBIT\t7,H\n" + buf.append( "PAUSE_N:\n" + + "\tBIT\t7,H\n" + "\tRET\tNZ\n" - + "PAUSE1:\tLD\tA,H\n" + + "PAUSE_N1:\n" + + "\tLD\tA,H\n" + "\tOR\tL\n" + "\tRET\tZ\n" + "\tDEC\tHL\n" - + "\tPUSH\tHL\n" - + "\tCALL\tXPAUSE\n" + + "\tPUSH\tHL\n" ); + buf.append_LD_DE_nn( target.get100msLoopCount() ); + buf.append( "PAUSE_N2:\n" + + "\tLD\tB,0\n" + + "PAUSE_N3:\n" + + "\tDJNZ\tPAUSE_N3\n" + + "\tDEC\tDE\n" + + "\tLD\tA,D\n" + + "\tOR\tE\n" + + "\tJR\tNZ,PAUSE_N2\n" + "\tCALL\tXINKEY\n" + "\tPOP\tHL\n" + "\tCP\t20H\n" - + "\tJR\tNZ,PAUSE1\n" + + "\tJR\tNZ,PAUSE_N1\n" + "\tRET\n" ); libItems.add( LibItem.XINKEY ); - libItems.add( LibItem.XPAUSE ); } if( libItems.contains( LibItem.PEN ) ) { /* @@ -1512,11 +2060,11 @@ public static void appendCodeTo( BasicCompiler compiler ) buf.append( "PLOTR:\tPUSH\tHL\n" + "\tLD\tHL,(M_XPOS)\n" + "\tCALL\tO_ADD\n" - + "\tLD\t(M_LNEX),HL\n" + + "\tLD\t(LINE_M_EX),HL\n" + "\tPOP\tDE\n" + "\tLD\tHL,(M_YPOS)\n" + "\tCALL\tO_ADD\n" - + "\tLD\t(M_LNEY),HL\n" + + "\tLD\t(LINE_M_EY),HL\n" + "\tLD\tDE,(M_XPOS)\n" + "\tJP\tXPSET\n" ); libItems.add( LibItem.M_XYPO ); @@ -1535,10 +2083,12 @@ public static void appendCodeTo( BasicCompiler compiler ) if( stackSize <= 0 ) { buf.append( "\tRET\n" ); } else { - buf.append( "\tLD\tHL,M_TOP-" ); + buf.append( "\tLD\tHL," ); + buf.append( BasicCompiler.TOP_LABEL ); + buf.append( (char) '-' ); buf.appendHex4( stackSize ); buf.append( "+10H" ); - libItems.add( LibItem.M_TOP ); + libItems.add( LibItem.MTOP ); buf.append( "\n" + "\tADD\tHL,DE\n" + "\tOR\tA\n" @@ -1714,26 +2264,6 @@ public static void appendCodeTo( BasicCompiler compiler ) libItems.add( LibItem.S_HXHL ); libItems.add( LibItem.M_STMP ); } - if( libItems.contains( LibItem.S_HOSTBYNAME ) ) { - /* - * Ermittlung der IP-Adresse eines Rechners - * - * Parameter: - * HL: Zeiger auf den Rechnernamen - * Rueckgabewert: - * HL: Zeiger auf die Zeichenkette mit der IP-Adresse - */ - buf.append( "S_HOSTBYNAME:\n" ); - appendResetErrorUseBC( compiler ); - buf.append( "\tCALL\tKCNET_HOSTBYNAME\n" - + "\tLD\tHL,KCNET_M_IPADDR\n" - + "\tJP\tNC,KCNET_S_IPADDR\n" - + "\tLD\tHL,D_EMPT\n" - + "\tRET\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_HOSTBYNAME ); - libItems.add( BasicLibrary.LibItem.KCNET_S_IPADDR ); - libItems.add( BasicLibrary.LibItem.D_EMPT ); - } if( libItems.contains( LibItem.S_INP ) ) { /* * Lesen einer bestimmten Anzahl von Zeichen von der Tastatur @@ -1927,27 +2457,6 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tRET\n" ); libItems.add( LibItem.M_STMP ); } - if( libItems.contains( LibItem.F_LOCALPORT ) ) { - /* - * Ermitteln der lokalen Portnummer - * - * Parameter: - * DE: Anfangsadresse Kanalzeigerfeld - * Rueckgabe: - * HL: Portnummer - */ - buf.append( "F_LOCALPORT:\n" - + "\tCALL\tCHECK_OPEN_NET_CHANNEL\n" - + "\tLD\tHL,0000H\n" - + "\tRET\tC\n" - + "\tCALL\tKCNET_INIT\n" - + "\tLD\tHL,0000H\n" - + "\tRET\tC\n" - + "\tLD\tL,04H\n" // Sn_PORT - + "\tJP\tKCNET_SOCK_GET_WORD\n" ); - libItems.add( LibItem.CHECK_OPEN_NET_CHANNEL ); - libItems.add( LibItem.KCNET_SOCK ); - } if( libItems.contains( LibItem.S_LTRIM ) ) { /* * Weisse Leerzeichen am Anfang einer Zeichenkette abschneiden @@ -2057,26 +2566,6 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tRET\n" ); libItems.add( LibItem.M_STMP ); } - if( libItems.contains( LibItem.S_REMOTEADDR ) ) { - /* - * Ermitteln der IP-Adresse der Gegenstelle - * - * Parameter: - * HL: Anfangsadresse Kanalzeigerfeld - * Rueckgabe: - * HL: Zeiger auf die Zeichenkette mit der IP-Adresse - */ - buf.append( "S_REMOTEADDR:\n" - + "\tCALL\tCHECK_OPEN_NET_CHANNEL\n" - + "\tLD\tHL,D_EMPT\n" - + "\tRET\tC\n" ); - buf.append_LD_HL_nn( KCNetLibrary.IOCTB_REMOTE_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tJP\tKCNET_S_IPADDR\n" ); - libItems.add( LibItem.CHECK_OPEN_NET_CHANNEL ); - libItems.add( LibItem.KCNET_S_IPADDR ); - libItems.add( LibItem.D_EMPT ); - } if( libItems.contains( LibItem.S_RIGHT ) ) { /* * Ende (rechter Teil) einer Zeichenkette zurueckliefern @@ -2123,9 +2612,9 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tOR\tA\n" + "\tJP\tM,E_PARM\n" + "\tOR\tC\n" - + "\tJR\tNZ,S_STC1\n" - + "\tLD\tHL,D_EMPT\n" - + "\tRET\n" + + "\tJR\tNZ,S_STC1\n" ); + buf.append_LD_HL_xx( EMPTY_STRING_LABEL ); + buf.append( "\tRET\n" + "S_STC1:\tLD\tE,L\n" + "\tLD\tHL," ); buf.appendHex4( BasicCompiler.MAX_STR_LEN ); @@ -2148,7 +2637,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tPOP\tHL\n" + "\tRET\n" ); libItems.add( LibItem.E_PARM ); - libItems.add( LibItem.D_EMPT ); + libItems.add( LibItem.EMPTY_STRING ); libItems.add( LibItem.M_STMP ); } if( libItems.contains( LibItem.S_STS ) ) { @@ -2165,9 +2654,9 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tOR\tA\n" + "\tJP\tM,E_PARM\n" + "\tOR\tC\n" - + "\tJR\tNZ,S_STS1\n" - + "\tLD\tHL,D_EMPT\n" - + "\tRET\n" + + "\tJR\tNZ,S_STS1\n" ); + buf.append_LD_HL_xx( EMPTY_STRING_LABEL ); + buf.append( "\tRET\n" + "S_STS1:\tPUSH\tBC\n" + "\tPUSH\tHL\n" + "\tEXX\n" @@ -2194,7 +2683,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tRET\n" ); libItems.add( LibItem.STNCP ); libItems.add( LibItem.E_PARM ); - libItems.add( LibItem.D_EMPT ); + libItems.add( LibItem.EMPTY_STRING ); libItems.add( LibItem.M_STMP ); } if( libItems.contains( LibItem.S_UPR ) ) { @@ -2251,16 +2740,17 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tDEC\tBC\n" + "\tLD\tA,B\n" + "\tOR\tC\n" - + "\tJR\tNZ,F_INSTRN2\n" - + "\tPOP\tBC\n" - + "\tDEC\tBC\n" - + "\tJR\tF_INSTR1\n" - + "F_INSTRN2:\n" + + "\tJR\tZ,F_INSTRN2\n" + "\tLD\tA,(HL)\n" - + "\tOR\tA\n" - + "\tJR\tZ,F_INSTR5\n" + "\tINC\tHL\n" - + "\tJR\tF_INSTRN1\n" ); + + "\tOR\tA\n" + + "\tJR\tNZ,F_INSTRN1\n" + + "\tPOP\tBC\n" + + "\tJR\tF_INSTR5\n" + + "F_INSTRN2:\n" + + "\tPOP\tBC\n" + + "\tDEC\tBC\n" + + "\tJR\tF_INSTR1\n" ); libItems.add( LibItem.F_INSTR ); libItems.add( LibItem.E_PARM ); } @@ -2313,6 +2803,34 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tLD\tHL,0000H\n" + "\tRET\n" ); } + if( libItems.contains( LibItem.F_IS_TARGET ) ) { + /* + * Pruefen, ob der Wert in HL dem aktuellen Zielsystem entspricht. + * Dabei werden auch uebergeordnete Zielsysteme erkannt. + * + * Parameter: + * HL: Wert des zu testendes Zielsystems + * Rueckgabe: + * HL: 0: Wert entspricht nicht dem Zielsystem + * FFFFh: Wert entspricht dem Zielsystem + */ + buf.append( "F_IS_TARGET:\n" + + "\tEX\tDE,HL\n" ); + int[] targetIDs = compiler.getTarget().getTargetIDs(); + if( targetIDs != null ) { + for( int targetID : targetIDs ) { + buf.append_LD_HL_nn( targetID ); + buf.append( "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tJR\tZ,F_IS_TARGET1\n" ); + } + } + buf.append( "\tLD\tHL,0000H\n" + + "\tRET\n" + + "F_IS_TARGET1:\n" + + "\tDEC\tHL\n" + + "\tRET\n" ); + } if( libItems.contains( LibItem.F_JOY ) ) { buf.append( "F_JOY:\tLD\tA,H\n" + "\tOR\tA\n" @@ -2345,35 +2863,13 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tEX\tDE,HL\n" + "\tRET\n" ); } - if( libItems.contains( LibItem.F_REMOTEPORT ) ) { - /* - * Ermitteln der Portnummer der Gegenstelle - * - * Parameter: - * HL: Anfangsadresse Kanalzeigerfeld - * Rueckgabe: - * HL: Portnummer der Gegenstelle oder 0 im Fehlerfall - */ - buf.append( "F_REMOTEPORT:\n" - + "\tCALL\tCHECK_OPEN_NET_CHANNEL\n" - + "\tLD\tHL,0000H\n" - + "\tRET\tC\n" ); - buf.append_LD_HL_nn( KCNetLibrary.IOCTB_REMOTE_OFFS + 4 ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\tH,(HL)\n" - + "\tLD\tL,A\n" - + "\tRET\n" ); - libItems.add( LibItem.CHECK_OPEN_NET_CHANNEL ); - } if( libItems.contains( LibItem.F_RND ) ) { /* * Ermitteln einer Zufallszahl * * Die RND-Funktion verknuepft mehrere Algorithmen miteinander, * um die jeweilgen Nachteile gegenseitig zu kompensieren: - * 1. Lesen der Programmcodebytes zwischen MSTART und MINIT + * 1. Lesen der Programmcodebytes * 2. XOR-Verknuepfung mit dem Refresh-Register * 3. XOR-Verknuepfung mit dem aktuellen Wert eines * rueckgekoppelten Schieberegister-Pseudozufallsgenerators, @@ -2392,11 +2888,12 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tJP\tZ,E_PARM\n" + "\tPUSH\tHL\n" + "\tLD\tDE,(M_RNDA)\n" // aktuelle Leseadresse - + "\tLD\tHL,MINIT\n" // MINIT erreicht? + + "\tLD\tHL,XEXIT\n" // XEXIT erreicht? + "\tSBC\tHL,DE\n" - + "\tJR\tNC,F_RND2\n" - + "\tLD\tDE,MSTART\n" // aus MSTART zurueckstellen - + "\tLD\tA,(M_RNDX)\n" // und rueckgekoppelten + + "\tJR\tNC,F_RND2\n" ); + // auf Anfang zurueckstellen + buf.append_LD_DE_xx( BasicCompiler.START_LABEL ); + buf.append( "\tLD\tA,(M_RNDX)\n" // und rueckgekoppelten + "\tLD\tB,A\n" // Schieberegister- + "\tAND\t8EH\n" // Pseudozufallsgenerator + "\tJP\tPE,F_RND1\n" // (M_RNDX) weiterstellen @@ -2648,7 +3145,7 @@ public static void appendCodeTo( BasicCompiler compiler ) * Rueckgabe: * HL: gelesene Zahl oder 0 bei Fehler */ - buf.append( "F_VLI:" ); + buf.append( "F_VLI:\n" ); BasicLibrary.appendResetErrorUseBC( compiler ); buf.append( "F_VLI1:\tLD\tB,H\n" + "\tLD\tC,L\n" @@ -2986,10 +3483,10 @@ public static void appendCodeTo( BasicCompiler compiler ) * O_ADD muss direkt hinter O_SUB folgen! */ buf.append( "O_ADD:\tLD\tA,H\n" - + "\tXOR\tD\n" - + "\tLD\tA,D\n" + "\tADD\tHL,DE\n" + + "\tXOR\tD\n" + "\tRET\tM\n" + + "\tLD\tA,D\n" + "\tXOR\tH\n" + "\tRET\tP\n" + "\tJP\tE_NOV\n" ); @@ -3095,10 +3592,10 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tLD\tE,A\n" + "\tRET\n" + "E_DIV0:" ); - target.appendSwitchToTextScreen( buf ); + target.appendSwitchToTextScreenTo( buf ); buf.append( "\tCALL\tXOUTST\n" ); if( compiler.isLangCode( "DE" ) ) { - buf.append( "\tDB\t\'Division durch Null\'\n" ); + buf.append( "\tDB\t\'Division durch 0\'\n" ); } else { buf.append( "\tDB\t\'Division by zero\'\n" ); } @@ -3359,10 +3856,10 @@ public static void appendCodeTo( BasicCompiler compiler ) libItems.add( LibItem.E_TYPE ); } if( libItems.contains( LibItem.SCREEN ) ) { - buf.append( "SCREEN:\tCALL\tXSCRS\n" + buf.append( "SCREEN:\tCALL\tXSCREEN\n" + "\tJP\tC,E_PARM\n" + "\tRET\n" ); - libItems.add( LibItem.XSCRS ); + libItems.add( LibItem.XSCREEN ); libItems.add( LibItem.E_PARM ); } if( libItems.contains( LibItem.IOEOF ) ) { @@ -3397,52 +3894,6 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tRET\n" + "IOEOF3:\tJP\t(HL)\n" ); } - if( libItems.contains( LibItem.IOAVAILABLE ) ) { - /* - * Anzahl der verfuegbaren Bytes ermitteln - * - * Parameter: - * HL: Adresse des Kanalzeigerfeldes - */ - buf.append( "IOAVAILABLE:\n" - + "\tLD\tD,H\n" - + "\tLD\tE,L\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tOR\t(HL)\n" - + "\tJR\tNZ,IOAVAILABLE1\n" ); - appendSetErrorChannelClosed( compiler ); - buf.append( "\tJR\tIOAVAILABLE2\n" - + "IOAVAILABLE1:\n" ); - appendResetErrorUseBC( compiler ); - for( int i = 1; i < IOCTB_AVAILABLE_OFFS; i++ ) { - buf.append( "\tINC\tHL\n" ); - } - buf.append( "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\tH,(HL)\n" - + "\tLD\tL,A\n" - + "\tOR\tH\n" - + "\tJR\tNZ,IOAVAILABLE3\n" ); - appendSetErrorIOMode( compiler ); - buf.append( "IOAVAILABLE2:\n" - + "\tLD\tHL,0000H\n" - + "\tRET\n" - + "IOAVAILABLE3:\n" - + "\tPUSH\tDE\n" - + "\tCALL\tJP_HL\n" - + "\tEX\tDE,HL\n" - + "\tPOP\tBC\n" ); - buf.append_LD_HL_nn( IOCTB_BBUF_OFFS ); - buf.append( "\tADD\tHL,BC\n" - + "\tLD\tA,(HL)\n" - + "\tEX\tDE,HL\n" - + "\tOR\tA\n" - + "\tRET\tZ\n" - + "\tINC\tHL\n" - + "\tRET\n" ); - libItems.add( LibItem.JP_HL ); - } if( libItems.contains( LibItem.IOINL ) ) { /* * Lesen einer Zeile aus einem Eingabekanal @@ -3458,8 +3909,8 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tOR\t(HL)\n" + "\tJR\tNZ,IOINL1\n" ); appendSetErrorChannelClosed( compiler ); - buf.append( "\tLD\tHL,D_EMPT\n" - + "\tRET\n" + buf.append_LD_HL_xx( EMPTY_STRING_LABEL ); + buf.append( "\tRET\n" + "IOINL1:\tDEC\tHL\n" ); appendResetErrorUseDE( compiler ); buf.append( "\tLD\tB," ); @@ -3505,7 +3956,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tLD\t(HL),A\n" + "\tJR\tIOINL3\n" ); libItems.add( LibItem.IORDB ); - libItems.add( LibItem.D_EMPT ); + libItems.add( LibItem.EMPTY_STRING ); libItems.add( LibItem.M_STMP ); } if( libItems.contains( LibItem.IOINX ) ) { @@ -3526,8 +3977,8 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tOR\t(HL)\n" + "\tJR\tNZ,IOINX1\n" ); appendSetErrorChannelClosed( compiler ); - buf.append( "\tLD\tHL,D_EMPT\n" - + "\tRET\n" + buf.append_LD_HL_xx( EMPTY_STRING_LABEL ); + buf.append( "\tRET\n" + "IOINX1:\tDEC\tHL\n" ); appendResetErrorUseDE( compiler ); buf.append( "\tLD\tDE,M_STMP\n" @@ -3550,7 +4001,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tRET\n" ); libItems.add( LibItem.IORDB ); libItems.add( LibItem.E_PARM ); - libItems.add( LibItem.D_EMPT ); + libItems.add( LibItem.EMPTY_STRING ); libItems.add( LibItem.M_STMP ); } if( libItems.contains( LibItem.IORDB ) ) { @@ -3587,43 +4038,6 @@ public static void appendCodeTo( BasicCompiler compiler ) buf.append( "\tRET\n" + "IORDB2:\tJP\t(HL)\n" ); } - if( libItems.contains( LibItem.IOFLUSH ) ) { - /* - * Gepufferte Daten schreiben - * - * Parameter: - * HL: Adresse des Kanalzeigerfeldes - */ - buf.append( "IOFLUSH:\n" - + "\tLD\tD,H\n" - + "\tLD\tE,L\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tOR\t(HL)\n" - + "\tJR\tNZ,IOFLUSH1\n" ); - appendSetErrorChannelClosed( compiler ); - buf.append( "\tRET\n" - + "IOFLUSH1:\n" ); - appendResetErrorUseBC( compiler ); - buf.append_LD_BC_nn( IOCTB_FLUSH_OFFS - 1 ); - buf.append( "\tADD\tHL,BC\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\tH,(HL)\n" - + "\tLD\tL,A\n" - + "\tOR\tH\n" - + "\tJR\tZ,IOFLUSH2\n" - + "\tJP\t(HL)\n" - + "IOFLUSH2:\n" ); - buf.append_LD_HL_nn( IOCTB_WRITE_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tOR\t(HL)\n" - + "\tRET\tNZ\n" ); - appendSetErrorIOMode( compiler ); - buf.append( "\tRET\n" ); - } if( libItems.contains( LibItem.IOCLOSE ) ) { /* * Schliessen eines IO-Kanals @@ -3664,12 +4078,12 @@ public static void appendCodeTo( BasicCompiler compiler ) * Oeffnen eines IO-Kanals * * Parameter: - * (M_IONM): Zeiger auf Geraete-/Dateiname - * (M_IOAC): Zugriffsmode - * HL: Anfangsadresse des Kanalzeigerfeldes + * (IO_M_NAME): Zeiger auf Geraete-/Dateiname + * (IO_M_ACCESS): Zugriffsmode + * HL: Anfangsadresse des Kanalzeigerfeldes */ buf.append( "IOOPEN:\n" - + "\tLD\t(M_IOCA),HL\n" + + "\tLD\t(IO_M_CADDR),HL\n" + "\tLD\tA,(HL)\n" + "\tINC\tHL\n" + "\tOR\t(HL)\n" @@ -3678,7 +4092,7 @@ public static void appendCodeTo( BasicCompiler compiler ) buf.append( "\tRET\n" + "IOOPEN1:\n" ); appendResetErrorUseHL( compiler ); - buf.append( "\tLD\tHL,IOHNTB\n" + buf.append( "\tLD\tHL,IO_HANDLER_TAB\n" + "IOOPEN2:\n" + "\tLD\tE,(HL)\n" // Handler-Adresse holen + "\tINC\tHL\n" @@ -3700,7 +4114,7 @@ public static void appendCodeTo( BasicCompiler compiler ) * verantwortlich gefuehlt. * Pruefen, ob der Kanal geoeffnet wurde. */ - buf.append( "\tLD\tHL,(M_IOCA)\n" + buf.append( "\tLD\tHL,(IO_M_CADDR)\n" + "\tLD\tA,(HL)\n" + "\tINC\tHL\n" + "\tOR\t(HL)\n" @@ -3723,55 +4137,52 @@ public static void appendCodeTo( BasicCompiler compiler ) // Keine passenden Handler gefunden appendSetErrorDeviceNotFound( compiler ); buf.append( "\tRET\n" ); - libItems.add( LibItem.IO_CRT_HANDLER ); - if( target.supportsXLPTCH() ) { - libItems.add( LibItem.IO_LPT_HANDLER ); - } - int[] ioAddrs = target.getVdipBaseIOAddresses(); - if( ioAddrs != null ) { - if( ioAddrs.length > 0 ) { - libItems.add( LibItem.IO_VDIP_HANDLER ); - } - } - libItems.add( LibItem.JP_HL ); - } - if( libItems.contains( LibItem.IO_VDIP_HANDLER ) ) { - VdipLibrary.appendCodeTo( compiler ); - } - if( libItems.contains( LibItem.IO_CRT_HANDLER ) ) { - buf.append( "IO_CRT_HANDLER:\n" + if( compiler.needsDriver( BasicCompiler.IODriver.CRT ) + && options.isOpenCrtEnabled() ) + { + buf.append( "IO_CRT_HANDLER:\n" + "\tLD\tHL,D_IOCRT\n" + "\tLD\tDE,XOUTCH\n" + "\tJR\tIO_SIMPLE_OUT_HANDLER\n" ); - libItems.add( LibItem.IO_SIMPLE_OUT_HANDLER ); - libItems.add( LibItem.XOUTCH ); - } - if( libItems.contains( LibItem.IO_LPT_HANDLER ) ) { - buf.append( "IO_LPT_HANDLER:\n" + libItems.add( LibItem.IO_CRT_HANDLER ); + libItems.add( LibItem.IO_SIMPLE_OUT_HANDLER ); + libItems.add( LibItem.XOUTCH ); + } + if( compiler.needsDriver( BasicCompiler.IODriver.LPT ) + && options.isOpenLptEnabled() + && target.supportsXLPTCH() ) + { + buf.append( "IO_LPT_HANDLER:\n" + "\tLD\tHL,D_IOLPT\n" + "\tLD\tDE,XLPTCH\n" + "\tJR\tIO_SIMPLE_OUT_HANDLER\n" ); - libItems.add( LibItem.IO_SIMPLE_OUT_HANDLER ); - libItems.add( LibItem.XLPTCH ); - } - if( libItems.contains( LibItem.IO_SIMPLE_OUT_HANDLER ) ) { - /* - * Handler fuer einfachen Ausgabekanal - * - * Parameter: - * DE: Adresse der Ausgaberoutine - * HL: Geraetename, auf den der Handler reagiert - * (M_IONM): Zeiger auf Geraetename (wird mit dem in HL verglichen) - * (M_IOCA): Anfangsadresse des Kanalzeigerfeldes - * (M_IOAC): Zugriffsmode (muss 0 oder 2 sein) - * Rueckgabewert: - * CY=1: Handler ist nicht zustaendig - * CY=0: Handler ist zustaendig - * -> keine weiteren Handler testen - */ - buf.append( "IO_SIMPLE_OUT_HANDLER:\n" + libItems.add( LibItem.IO_LPT_HANDLER ); + libItems.add( LibItem.IO_SIMPLE_OUT_HANDLER ); + libItems.add( LibItem.XLPTCH ); + } + if( libItems.contains( LibItem.IO_SIMPLE_OUT_HANDLER ) ) { + /* + * Handler fuer einfachen Ausgabekanal + * + * Der Code steht direkt hinter + * IO_CRT_HANDLER bzw. IO_LPT_HANDLER, + * damit die relative Sprungdistanz nicht zu gross wird. + * + * Parameter: + * DE: Adresse der Ausgaberoutine + * HL: Geraetename, auf den der Handler reagiert + * (IO_M_NAME): Zeiger auf Geraetename + * (wird mit dem in HL verglichen) + * (IO_M_CADDR): Anfangsadresse des Kanalzeigerfeldes + * (IO_M_ACCESS): Zugriffsmode (muss 0 oder 2 sein) + * Rueckgabewert: + * CY=1: Handler ist nicht zustaendig + * CY=0: Handler ist zustaendig + * -> keine weiteren Handler testen + */ + buf.append( "IO_SIMPLE_OUT_HANDLER:\n" + "\tPUSH\tDE\n" - + "\tLD\tDE,(M_IONM)\n" + + "\tLD\tDE,(IO_M_NAME)\n" + "IO_SIMPLE_OUT_H1:\n" + "\tLD\tA,(DE)\n" + "\tCALL\tC_UPR\n" @@ -3781,19 +4192,20 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tINC\tHL\n" + "\tCP\t3AH\n" + "\tJR\tNZ,IO_SIMPLE_OUT_H1\n" - + "\tLD\tA,(M_IOAC)\n" // Betriebsart pruefen - + "\tOR\tA\n" - + "\tJR\tZ,IO_SIMPLE_OUT_H2\n" - + "\tCP\t" ); - buf.appendHex2( IOMODE_OUTPUT ); - buf.append( "\n" + + "\tLD\tA,(IO_M_ACCESS)\n" // Betriebsart pruefen + + "\tAND\t" ); + buf.appendHex2( ~(IOMODE_DEFAULT_MASK + | IOMODE_OUTPUT_MASK + | IOMODE_TXT_MASK + | IOMODE_BIN_MASK) ); + buf.append( "\n" + "\tJR\tZ,IO_SIMPLE_OUT_H2\n" ); - appendSetErrorIOMode( compiler ); // Betriebsart nicht moeglich - buf.append( "\tPOP\tDE\n" + appendSetErrorIOMode( compiler ); // Betriebsart nicht moeglich + buf.append( "\tPOP\tDE\n" + "\tOR\tA\n" // C=0 + "\tRET\n" + "IO_SIMPLE_OUT_H2:\n" - + "\tLD\tHL,(M_IOCA)\n" + + "\tLD\tHL,(IO_M_CADDR)\n" + "\tLD\tDE,IO_SIMPLE_CLOSE\n" + "\tLD\t(HL),E\n" + "\tINC\tHL\n" @@ -3801,8 +4213,8 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tINC\tHL\n" + "\tXOR\tA\n" + "\tLD\tB," ); - buf.appendHex2( IOCTB_WRITE_OFFS - 2 ); - buf.append( "\n" + buf.appendHex2( IOCTB_WRITE_OFFS - 2 ); + buf.append( "\n" + "IO_SIMPLE_OUT_H3:\n" + "\tLD\t(HL),A\n" + "\tINC\tHL\n" @@ -3813,8 +4225,8 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tLD\t(HL),D\n" + "\tINC\tHL\n" + "\tLD\tB," ); - buf.appendHex2( compiler.getIOChannelSize() - IOCTB_WRITE_OFFS - 2 ); - buf.append( "\n" + buf.appendHex2( compiler.getIOChannelSize() - IOCTB_WRITE_OFFS - 2 ); + buf.append( "\n" + "IO_SIMPLE_OUT_H4:\n" + "\tLD\t(HL),A\n" + "\tINC\tHL\n" @@ -3825,7 +4237,31 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tSCF\n" + "IO_SIMPLE_CLOSE:\n" + "\tRET\n" ); - libItems.add( LibItem.C_UPR ); + libItems.add( LibItem.C_UPR ); + } + if( compiler.needsDriver( BasicCompiler.IODriver.FILE ) + && options.isOpenFileEnabled() ) + { + String fileHandlerLabel = compiler.getTarget().getFileHandlerLabel(); + if( fileHandlerLabel != null ) { + if( !fileHandlerLabel.isEmpty() ) { + compiler.getTarget().appendFileHandler( compiler ); + libItems.add( LibItem.IO_FILE_HANDLER ); + } + } + } + if( compiler.needsDriver( BasicCompiler.IODriver.VDIP ) + && options.isOpenVdipEnabled() ) + { + int[] ioAddrs = target.getVdipBaseIOAddresses(); + if( ioAddrs != null ) { + if( ioAddrs.length > 0 ) { + VdipLibrary.appendCodeTo( compiler ); + libItems.add( LibItem.IO_VDIP_HANDLER ); + } + } + } + libItems.add( LibItem.JP_HL ); } if( libItems.contains( LibItem.IOCADR ) ) { /* @@ -3859,44 +4295,6 @@ public static void appendCodeTo( BasicCompiler compiler ) libItems.add( LibItem.IOCTB1 ); libItems.add( LibItem.IOCTB2 ); } - if( libItems.contains( LibItem.CHECK_OPEN_NET_CHANNEL ) ) { - /* - * Pruefen, ob der Kanal mit KCNet (TCP oder UDP) geoeffnet ist - * Die Fehlervariablen werden gesetzt. - * - * Parameter: - * HL: Anfangsadresse Kanalzeigerfeld - * Rueckgabe: - * CY=0: OK, A: Protokoll, DE: Anfangsadresse Kanalzeigerfeld - * CY=1: nicht als KCNet-Kanal geoeffnet - */ - buf.append( "CHECK_OPEN_NET_CHANNEL:\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tOR\t(HL)\n" - + "\tJR\tNZ,CHECK_OPEN_NET_CHANNEL1\n" ); - appendSetErrorChannelClosed( compiler ); - buf.append( "\tJR\tCHECK_OPEN_NET_CHANNEL2\n" - + "CHECK_OPEN_NET_CHANNEL1:\n" - + "\tDEC\tHL\n" - + "\tEX\tDE,HL\n" ); - buf.append_LD_HL_nn( KCNetLibrary.IOCTB_PROTO_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tCP\t\'T\'\n" - + "\tJR\tZ,CHECK_OPEN_NET_CHANNEL3\n" - + "\tCP\t\'U\'\n" - + "\tJR\tZ,CHECK_OPEN_NET_CHANNEL3\n" ); - appendSetErrorIOMode( compiler ); - buf.append( "CHECK_OPEN_NET_CHANNEL2:\n" - + "\tSCF\n" - + "\tRET\n" - + "CHECK_OPEN_NET_CHANNEL3:\n" ); - appendResetErrorUseHL( compiler ); - buf.append( "\tOR\tA\n" // CY=0 - + "\tRET\n" ); - } - KCNetLibrary.appendCodeTo( compiler ); if( libItems.contains( LibItem.S_HXHL ) ) { /* * Umwandlung eines numerischen Wertes in eine Zeichenkette @@ -3953,7 +4351,7 @@ public static void appendCodeTo( BasicCompiler compiler ) } if( libItems.contains( LibItem.E_DATA ) ) { buf.append( "E_DATA:" ); - target.appendSwitchToTextScreen( buf ); + target.appendSwitchToTextScreenTo( buf ); buf.append( "\tCALL\tXOUTST\n" ); if( compiler.isLangCode( "DE" ) ) { buf.append( "\tDB\t\'Keine Daten mehr\'\n" ); @@ -3967,7 +4365,7 @@ public static void appendCodeTo( BasicCompiler compiler ) } if( libItems.contains( LibItem.E_IDX ) ) { buf.append( "E_IDX:" ); - target.appendSwitchToTextScreen( buf ); + target.appendSwitchToTextScreenTo( buf ); buf.append( "\tCALL\tXOUTST\n" ); if( compiler.isLangCode( "DE" ) ) { buf.append( "\tDB\t\'Index ausserhalb des Bereichs\'\n" ); @@ -3981,7 +4379,7 @@ public static void appendCodeTo( BasicCompiler compiler ) } if( libItems.contains( LibItem.E_NOV ) ) { buf.append( "E_NOV:" ); - target.appendSwitchToTextScreen( buf ); + target.appendSwitchToTextScreenTo( buf ); buf.append( "\tCALL\tXOUTST\n" ); if( compiler.isLangCode( "DE" ) ) { buf.append( "\tDB\t\'Numerischer Ueberlauf\'\n" ); @@ -3995,7 +4393,7 @@ public static void appendCodeTo( BasicCompiler compiler ) } if( libItems.contains( LibItem.E_NXWF ) ) { buf.append( "E_NXWF:" ); - target.appendSwitchToTextScreen( buf ); + target.appendSwitchToTextScreenTo( buf ); buf.append( "\tCALL\tXOUTST\n" ); if( compiler.isLangCode( "DE" ) ) { buf.append( "\tDB\t\'NEXT ohne FOR\'\n" ); @@ -4009,12 +4407,12 @@ public static void appendCodeTo( BasicCompiler compiler ) } if( libItems.contains( LibItem.E_PARM ) ) { buf.append( "E_PARM:" ); - target.appendSwitchToTextScreen( buf ); + target.appendSwitchToTextScreenTo( buf ); buf.append( "\tCALL\tXOUTST\n" ); if( compiler.isLangCode( "DE" ) ) { - buf.append( "\tDB\t\'Parameter ausserhalb des Wertebereichs\'\n" ); + buf.append( "\tDB\t\'Ungueltiger Parameter\'\n" ); } else { - buf.append( "\tDB\t\'Parameter out of range\'\n" ); + buf.append( "\tDB\t\'Invalid parameter\'\n" ); } buf.append( "\tDB\t00H\n" + "\tJP\tE_EXIT\n" ); @@ -4023,7 +4421,7 @@ public static void appendCodeTo( BasicCompiler compiler ) } if( libItems.contains( LibItem.E_REWG ) ) { buf.append( "E_REWG:" ); - target.appendSwitchToTextScreen( buf ); + target.appendSwitchToTextScreenTo( buf ); buf.append( "\tCALL\tXOUTST\n" ); if( compiler.isLangCode( "DE" ) ) { buf.append( "\tDB\t\'RETURN ohne GOSUB\'\n" ); @@ -4037,7 +4435,7 @@ public static void appendCodeTo( BasicCompiler compiler ) } if( libItems.contains( LibItem.E_TYPE ) ) { buf.append( "E_TYPE:" ); - target.appendSwitchToTextScreen( buf ); + target.appendSwitchToTextScreenTo( buf ); buf.append( "\tCALL\tXOUTST\n" ); if( compiler.isLangCode( "DE" ) ) { buf.append( "\tDB\t\'Falscher Datentyp\'\n" ); @@ -4049,7 +4447,7 @@ public static void appendCodeTo( BasicCompiler compiler ) libItems.add( LibItem.E_EXIT ); libItems.add( LibItem.XOUTST ); } - if( libItems.contains( LibItem.ASGSM ) ) { + if( libItems.contains( LibItem.ASSIGN_STR_TO_NEW_MEM_VS ) ) { /* * Eine Zeichenkette einer String-Variable zuweisen, * wobei diese in einen neu allokierten Speicherberich kopiert wird @@ -4058,14 +4456,15 @@ public static void appendCodeTo( BasicCompiler compiler ) * DE: Zeiger auf die String-Variable * HL: Zeiger auf die Zeichenkette */ - buf.append( "ASGSM:\tPUSH\tDE\n" + buf.append( "ASSIGN_STR_TO_NEW_MEM_VS:\n" + + "\tPUSH\tDE\n" + "\tCALL\tSMACP\n" + "\tPOP\tDE\n" ); libItems.add( LibItem.SMACP ); - libItems.add( LibItem.ASGSL ); - // direkt weiter mit ASGSL + libItems.add( LibItem.ASSIGN_STR_TO_VS ); + // direkt weiter mit ASSIGN_STR_TO_VS } - if( libItems.contains( LibItem.ASGSL ) ) { + if( libItems.contains( LibItem.ASSIGN_STR_TO_VS ) ) { /* * Eine Zeichenkette einer String-Variable zuweisen * @@ -4073,7 +4472,8 @@ public static void appendCodeTo( BasicCompiler compiler ) * DE: Zeiger auf die String-Variable * HL: Zeiger auf die Zeichenkette */ - buf.append( "ASGSL:\tPUSH\tDE\n" + buf.append( "ASSIGN_STR_TO_VS:\n" + + "\tPUSH\tDE\n" + "\tPUSH\tHL\n" + "\tEX\tDE,HL\n" + "\tLD\tE,(HL)\n" @@ -4088,7 +4488,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tRET\n" ); libItems.add( LibItem.MFREE ); } - if( libItems.contains( LibItem.ASGSV ) ) { + if( libItems.contains( LibItem.ASSIGN_VS_TO_VS ) ) { /* * Eine String-Variable einer andern zuweisen, * Wenn die Zeichenkette der Quellvariablen im Heap liegt, @@ -4100,13 +4500,15 @@ public static void appendCodeTo( BasicCompiler compiler ) * DE: Zeiger auf die Zielvariable * HL: Zeiger auf die Quellvariable */ - buf.append( "ASGSV:\tLD\tA,H\n" + buf.append( "ASSIGN_VS_TO_VS:\n" + + "\tLD\tA,H\n" + "\tCP\tD\n" - + "\tJR\tNZ,ASGSV1\n" + + "\tJR\tNZ,ASSIGN_VS_TO_VS1\n" + "\tLD\tA,L\n" + "\tCP\tE\n" + "\tRET\tZ\n" - + "ASGSV1:\tPUSH\tDE\n" + + "ASSIGN_VS_TO_VS1:\n" + + "\tPUSH\tDE\n" + "\tLD\tE,(HL)\n" + "\tINC\tHL\n" + "\tLD\tD,(HL)\n" @@ -4136,9 +4538,9 @@ public static void appendCodeTo( BasicCompiler compiler ) */ buf.append( "SMACP:\tLD\tA,(HL)\n" + "\tOR\tA\n" - + "\tJR\tNZ,SMACP1\n" - + "\tLD\tDE,D_EMPT\n" - + "\tRET\n" + + "\tJR\tNZ,SMACP1\n" ); + buf.append_LD_DE_xx( EMPTY_STRING_LABEL ); + buf.append( "\tRET\n" + "SMACP1:\tPUSH\tHL\n" + "\tCALL\tMFIND\n" + "\tPOP\tHL\n" @@ -4153,7 +4555,7 @@ public static void appendCodeTo( BasicCompiler compiler ) libItems.add( LibItem.MFIND ); libItems.add( LibItem.MALLOC ); libItems.add( LibItem.STNCP ); - libItems.add( LibItem.D_EMPT ); + libItems.add( LibItem.EMPTY_STRING ); } if( libItems.contains( LibItem.SVDUP ) ) { /* @@ -4169,12 +4571,9 @@ public static void appendCodeTo( BasicCompiler compiler ) * Rueckgabewert: * DE: Zeiger auf evtl. kopierte Zeichenkette */ - buf.append( "SVDUP:\tLD\tHL,HEAPB\n" - + "\tOR\tA\n" - + "\tSBC\tHL,DE\n" - + "\tRET\tNC\n" - + "\tADD\tHL,DE\n" - + "\tJR\tSVDUP2\n" + buf.append( "SVDUP:\tCALL\tCHECK_DE_WITHIN_HEAP\n" + + "\tRET\tC\n" + + "\tJR\tSVDUP2\n" // HL: Heap-Anfang + "SVDUP1:\tLD\tA,B\n" + "\tOR\tC\n" + "\tRET\tZ\n" @@ -4211,6 +4610,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tRET\n" ); libItems.add( LibItem.MFIND ); libItems.add( LibItem.MALLOC ); + libItems.add( LibItem.CHECK_DE_WITHIN_HEAP ); libItems.add( LibItem.HEAP ); libItems.add( LibItem.STNCP ); } @@ -4313,13 +4713,14 @@ public static void appendCodeTo( BasicCompiler compiler ) + "MFIND2:\tEX\tDE,HL\n" // naechster Block + "\tLD\tA,H\n" + "\tOR\tL\n" - + "\tJR\tNZ,MFIND1\n" ); - target.appendSwitchToTextScreen( buf ); + + "\tJR\tNZ,MFIND1\n" + + "E_OUT_OF_MEM:\n" ); + target.appendSwitchToTextScreenTo( buf ); buf.append( "\tCALL\tXOUTST\n" ); if( compiler.isLangCode( "DE" ) ) { - buf.append( "\tDB\t\'Zeichenkettenspeicher voll\'\n" ); + buf.append( "\tDB\t\'Speicher voll\'\n" ); } else { - buf.append( "\tDB\t\'Out of string memory\'\n" ); + buf.append( "\tDB\t\'Out of memory\'\n" ); } buf.append( "\tDB\t00H\n" + "\tJP\tE_EXIT\n" ); @@ -4399,12 +4800,9 @@ public static void appendCodeTo( BasicCompiler compiler ) * Parameter: * DE: Zeiger auf den allokierten Speicherbereich */ - buf.append( "MMGC:\tLD\tHL,HEAPB\n" - + "\tOR\tA\n" - + "\tSBC\tHL,DE\n" - + "\tRET\tNC\n" // Blockadresse < Heap - + "\tPUSH\tIY\n" - + "\tADD\tHL,DE\n" // HL: Heap-Anfang + buf.append( "MMGC:\tCALL\tCHECK_DE_WITHIN_HEAP\n" + + "\tRET\tC\n" + + "\tPUSH\tIY\n" // HL: Heap-Anfang + "\tDEC\tDE\n" + "\tDEC\tDE\n" + "\tDEC\tDE\n" @@ -4429,6 +4827,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tSET\t7,(HL)\n" // Block markieren + "MMGC3:\tPOP\tIY\n" + "\tRET\n" ); + libItems.add( LibItem.CHECK_DE_WITHIN_HEAP ); } if( libItems.contains( LibItem.MRGC ) ) { /* @@ -4478,12 +4877,9 @@ public static void appendCodeTo( BasicCompiler compiler ) * Parameter: * DE: Zeiger auf den allokierten Speicherbereich */ - buf.append( "MFREE:\tLD\tHL,HEAPB\n" - + "\tOR\tA\n" - + "\tSBC\tHL,DE\n" - + "\tRET\tNC\n" // Blockadresse < Heap - + "\tPUSH\tIY\n" - + "\tADD\tHL,DE\n" // HL: Heap-Anfang + buf.append( "MFREE:\tCALL\tCHECK_DE_WITHIN_HEAP\n" + + "\tRET\tC\n" + + "\tPUSH\tIY\n" // HL: Heap-Anfang + "\tDEC\tDE\n" + "\tDEC\tDE\n" + "\tDEC\tDE\n" @@ -4559,10 +4955,38 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tLD\t(IY+2),L\n" + "\tLD\t(IY+3),H\n" + "\tRET\n" ); + libItems.add( LibItem.CHECK_DE_WITHIN_HEAP ); libItems.add( LibItem.HEAP ); } + if( libItems.contains( LibItem.CHECK_DE_WITHIN_HEAP ) ) { + /* + * Pruefen, ob die in DE enthaltene Adresse im Heap liegt, + * DE bleibt erhalten + * + * Parameter: + * DE: Zeiger auf den zu pruefenden Speicherbereich + * + * Rueckgabewert: + * + * CY=0: Adresse im Heap, HL: Heap-Anfang + * CY=1: Adresse ausserhalb des Heaps + */ + buf.append( "CHECK_DE_WITHIN_HEAP:\n" + + "\tLD\tHL,HEAPB\n" + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tJR\tZ,CHECK_DE_WITHIN_HEAP1\n" + + "\tCCF\n" + + "\tRET\tC\n" + + "\tLD\tHL,HEAPE\n" + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "CHECK_DE_WITHIN_HEAP1:\n" + + "\tLD\tHL,HEAPB\n" + + "\tRET\n" ); + } if( libItems.contains( LibItem.E_USR ) ) { - target.appendSwitchToTextScreen( buf ); + target.appendSwitchToTextScreenTo( buf ); buf.append( "E_USR:\tCALL\tXOUTST\n" ); if( compiler.isLangCode( "DE" ) ) { buf.append( "\tDB\t\'USR-Funktion nicht definiert\'\n" ); @@ -4582,35 +5006,57 @@ public static void appendCodeTo( BasicCompiler compiler ) if( options.getPrintLineNumOnAbort() ) { buf.append( "\tLD\tHL,(M_SRLN)\n" + "\tBIT\t7,H\n" - + "\tJR\tNZ,E_EXI1\n" + + "\tJR\tNZ,E_EXIT2\n" + "\tPUSH\tHL\n" + "\tCALL\tXOUTST\n" ); - if( compiler.isLangCode( "DE" ) ) { - buf.append( "\tDB\t\' in Zeile\'\n" ); + if( libItems.contains( LibItem.M_SRNM ) ) { + buf.append( "\tDB\t\' in \'\n" + + "\tDB\t00H\n" + + "\tLD\tHL,(M_SRNM)\n" + + "\tLD\tA,H\n" + + "\tOR\tL\n" + + "\tJR\tZ,E_EXIT1\n" + + "\tCALL\tXOUTS\n" + + "\tCALL\tXOUTST\n" + + "\tDB\t\', \'\n" + + "\tDB\t00H\n" + + "E_EXIT1:\n" + + "\tCALL\tXOUTST\n" ); + if( compiler.isLangCode( "DE" ) ) { + buf.append( "\tDB\t\'Zeile\'\n" ); + } else { + buf.append( "\tDB\t\'line\'\n" ); + } } else { - buf.append( "\tDB\t\' in line\'\n" ); + if( compiler.isLangCode( "DE" ) ) { + buf.append( "\tDB\t\' in Zeile\'\n" ); + } else { + buf.append( "\tDB\t\' in line\'\n" ); + } } buf.append( "\tDB\t00H\n" + "\tPOP\tHL\n" + "\tCALL\tS_STR\n" - + "\tCALL\tXOUTS\n" - + "\tLD\tHL,(M_BALN)\n" + + "\tCALL\tXOUTS\n" ); + if( libItems.contains( LibItem.M_BALN ) ) { + buf.append( "\tLD\tHL,(M_BALN)\n" + "\tBIT\t7,H\n" - + "\tJR\tNZ,E_EXI1\n" + + "\tJR\tNZ,E_EXIT2\n" + "\tPUSH\tHL\n" + "\tCALL\tXOUTST\n" ); - if( compiler.isLangCode( "DE" ) ) { - buf.append( "\tDB\t\' (BASIC-Zeile\'\n" ); - } else { - buf.append( "\tDB\t\' (BASIC line\'\n" ); - } - buf.append( "\tDB\t00H\n" + if( compiler.isLangCode( "DE" ) ) { + buf.append( "\tDB\t\' (BASIC-Zeile\'\n" ); + } else { + buf.append( "\tDB\t\' (BASIC line\'\n" ); + } + buf.append( "\tDB\t00H\n" + "\tPOP\tHL\n" + "\tCALL\tS_STR\n" + "\tCALL\tXOUTS\n" + "\tLD\tA,29H\n" - + "\tCALL\tXOUTCH\n" - + "E_EXI1:" ); + + "\tCALL\tXOUTCH\n" ); + } + buf.append( "E_EXIT2:\n" ); libItems.add( LibItem.S_STR ); libItems.add( LibItem.XOUTCH ); libItems.add( LibItem.XOUTS ); @@ -4635,51 +5081,48 @@ public static void appendCodeTo( BasicCompiler compiler ) buf.append( "S_STR:\tLD\tA,H\n" + "\tXOR\t80H\n" + "\tOR\tL\n" - + "\tJR\tNZ,S_STR1\n" - + "\tLD\tHL,S_STR5\n" - + "\tRET\n" - + "S_STR1:\tLD\tA,H\n" - + "\tOR\tA\n" + + "\tJR\tZ,S_STR4\n" + "\tLD\tA,20H\n" - + "\tJP\tP,S_STR2\n" + + "\tBIT\t7,H\n" + + "\tJR\tZ,S_STR1\n" + "\tEX\tDE,HL\n" + "\tXOR\tA\n" // CY=0 + "\tLD\tH,A\n" + "\tLD\tL,A\n" + "\tSBC\tHL,DE\n" - + "\tLD\tA,'-'\n" - + "S_STR2:\tEXX\n" + + "\tLD\tA,2DH\n" // Minuszeichen + + "S_STR1:\tEXX\n" + "\tLD\tHL,M_STR\n" + "\tLD\t(HL),A\n" + "\tINC\tHL\n" + "\tEXX\n" + "\tLD\tB,00H\n" // keine Nullen + "\tLD\tDE,2710H\n" // 10000 - + "\tCALL\tS_STR3\n" + + "\tCALL\tS_STR2\n" + "\tLD\tDE,03E8H\n" // 1000 - + "\tCALL\tS_STR3\n" + + "\tCALL\tS_STR2\n" + "\tLD\tDE,0064H\n" // 100 - + "\tCALL\tS_STR3\n" + + "\tCALL\tS_STR2\n" + "\tLD\tDE,000AH\n" // 10 - + "\tCALL\tS_STR3\n" + + "\tCALL\tS_STR2\n" + "\tLD\tDE,0001H\n" // 1 + "\tLD\tB,01H\n" // Null ausgeben - + "\tCALL\tS_STR3\n" + + "\tCALL\tS_STR2\n" + "\tXOR\tA\n" + "\tEXX\n" + "\tLD\t(HL),A\n" + "\tLD\tHL,M_STR\n" + "\tRET\n" - + "S_STR3:\tLD\tA,0FFH\n" + + "S_STR2:\tLD\tA,0FFH\n" + "\tOR\tA\n" // CY=0 - + "S_STR4:\tINC\tA\n" // CY unveraendert + + "S_STR3:\tINC\tA\n" // CY unveraendert + "\tSBC\tHL,DE\n" - + "\tJR\tNC,S_STR4\n" + + "\tJR\tNC,S_STR3\n" + "\tADD\tHL,DE\n" + "\tLD\tC,A\n" + "\tOR\tB\n" + "\tRET\tZ\n" - + "\tLD\tA,'0'\n" + + "\tLD\tA,30H\n" // 0 + "\tLD\tB,A\n" + "\tADD\tA,C\n" + "\tEXX\n" @@ -4687,6 +5130,8 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tINC\tHL\n" + "\tEXX\n" + "\tRET\n" + + "S_STR4:\tLD\tHL,S_STR5\n" + + "\tRET\n" + "S_STR5:\tDB\t'-32768'\n" + "\tDB\t00H\n" ); } @@ -4724,8 +5169,9 @@ public static void appendCodeTo( BasicCompiler compiler ) buf.append( "\tLD\tHL,IOCTB2\n" + "\tCALL\tCKCLOS\n" ); } + target.appendPreExitTo( buf ); buf.append( "\tLD\tSP,(M_STCK)\n" ); - target.appendExit( buf ); + target.appendExitTo( buf ); if( libItems.contains( LibItem.IOCTB1 ) || libItems.contains( LibItem.IOCTB2 ) ) { @@ -4739,7 +5185,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tRET\tZ\n" + "\tJP\t(HL)\n" ); } - target.appendInput( + target.appendInputTo( buf, libItems.contains( LibItem.XCKBRK ), libItems.contains( LibItem.XINKEY ), @@ -4754,62 +5200,57 @@ public static void appendCodeTo( BasicCompiler compiler ) libItems.add( LibItem.XOUTST ); libItems.add( LibItem.XOUTNL ); } + if( libItems.contains( LibItem.XSCREEN ) ) { + target.appendXScreenTo( buf ); + } if( libItems.contains( LibItem.XBORDER ) ) { - target.appendXBORDER( buf ); + target.appendXBorderTo( buf ); } if( libItems.contains( LibItem.XCOLOR ) ) { - target.appendXCOLOR( buf ); + target.appendXColorTo( buf ); } if( libItems.contains( LibItem.XINK ) ) { - target.appendXINK( buf ); + target.appendXInkTo( buf ); } if( libItems.contains( LibItem.XPAPER ) ) { - target.appendXPAPER( buf ); + target.appendXPaperTo( buf ); } if( libItems.contains( LibItem.XCLS ) ) { - target.appendXCLS( buf ); + target.appendXClsTo( buf ); } if( libItems.contains( LibItem.XLOCATE ) ) { - target.appendXLOCATE( buf ); + target.appendXLocateTo( buf ); } if( libItems.contains( LibItem.XCURS ) ) { - target.appendXCURS( buf ); - } - if( libItems.contains( LibItem.XPAUSE ) ) { - buf.append( "XPAUSE:\tLD\tDE," ); - buf.appendHex4( target.get100msLoopCount() ); - buf.append( "\n" - + "XPAUS1:\tLD\tB,0\n" - + "XPAUS2:\tDJNZ\tXPAUS2\n" - + "\tDEC\tDE\n" - + "\tLD\tA,D\n" - + "\tOR\tE\n" - + "\tJR\tNZ,XPAUS1\n" - + "\tRET\n" ); + target.appendXCursTo( buf ); } // XPEN vor den anderen Grafikroutinen hinzufuegen! if( libItems.contains( LibItem.XPEN ) ) { - target.appendXPEN( buf ); + target.appendXPenTo( buf ); } if( libItems.contains( LibItem.XHLINE ) ) { - target.appendXHLINE( buf, compiler ); + target.appendXHLineTo( buf, compiler ); + } + if( libItems.contains( LibItem.XPAINT ) ) { + target.appendXPaintTo( buf, compiler ); } if( libItems.contains( LibItem.XPRES ) ) { - target.appendXPRES( buf, compiler ); + target.appendXPResTo( buf, compiler ); } if( libItems.contains( LibItem.XPSET ) ) { - target.appendXPSET( buf, compiler ); + target.appendXPSetTo( buf, compiler ); } if( libItems.contains( LibItem.XPTEST ) ) { - target.appendXPTEST( buf, compiler ); + target.appendXPTestTo( buf, compiler ); } - if( libItems.contains( LibItem.XSCRS ) ) { - target.appendXSCRS( buf ); + if( libItems.contains( LibItem.XPOINT ) ) { + target.appendXPointTo( buf, compiler ); } if( libItems.contains( LibItem.XJOY ) ) { - target.appendXJOY( buf ); + target.appendXJoyTo( buf ); } - if( libItems.contains( LibItem.XOUTST ) || target.needsXOUTST() ) { + target.appendEtcPreXOutTo( buf ); + if( libItems.contains( LibItem.XOUTST ) ) { /* * Ausgabe eines mit einem Null-Byte abgeschlossenen Textes * auf dem Bildschirm, @@ -4842,16 +5283,16 @@ public static void appendCodeTo( BasicCompiler compiler ) libItems.add( LibItem.XOUTCH ); } if( libItems.contains( LibItem.XOUTNL ) ) { - target.appendXOUTNL( buf ); + target.appendXOutnlTo( buf ); libItems.add( LibItem.XOUTCH ); } if( libItems.contains( LibItem.XOUTCH ) ) { - target.appendXOUTCH( buf ); + target.appendXOutchTo( buf ); } if( libItems.contains( LibItem.XLPTCH ) ) { - target.appendXLPTCH( buf ); + target.appendXLPtchTo( buf ); } - target.appendEtc( buf ); + target.appendEtcPastXOutTo( buf ); } @@ -4868,7 +5309,17 @@ public static void appendDataTo( buf.append( "\n;Datenbereich\n" ); } if( libItems.contains( LibItem.IOOPEN ) ) { - buf.append( "IOHNTB:\n" ); + buf.append( "IO_HANDLER_TAB:\n" ); + if( libItems.contains( LibItem.IO_FILE_HANDLER ) ) { + String fileHandlerLabel = compiler.getTarget().getFileHandlerLabel(); + if( fileHandlerLabel != null ) { + if( !fileHandlerLabel.isEmpty() ) { + buf.append( "\tDW\t" ); + buf.append( fileHandlerLabel ); + buf.newLine(); + } + } + } if( libItems.contains( LibItem.IO_VDIP_HANDLER ) ) { buf.append( "\tDW\tIO_VDIP_HANDLER\n" ); } @@ -4896,9 +5347,6 @@ public static void appendDataTo( } } } - if( libItems.contains( LibItem.XTARID ) ) { - target.appendXTARID( buf ); - } if( libItems.contains( LibItem.IO_CRT_HANDLER ) ) { buf.append( "D_IOCRT:\n" + "\tDB\t\'CRT:\'\n" ); @@ -4910,15 +5358,20 @@ public static void appendDataTo( if( libItems.contains( LibItem.IO_VDIP_HANDLER ) ) { VdipLibrary.appendDataTo( compiler ); } - KCNetLibrary.appendDataTo( compiler ); - if( libItems.contains( LibItem.D_EMPT ) ) { - buf.append( "D_EMPT:\tDB\t00H\n" ); + if( libItems.contains( LibItem.EMPTY_STRING ) ) { + buf.append( EMPTY_STRING_LABEL ); + buf.append( (char) ':' ); + if( EMPTY_STRING_LABEL.length() > 6 ) { + buf.append( (char) '\n' ); + } + buf.append( "\tDB\t00H\n" ); } - if( libItems.contains( LibItem.FNT5P7 ) ) { + if( libItems.contains( LibItem.FONT_5X7 ) ) { if( options.getShowAssemblerText() ) { buf.append( "\n;Zeichensatz\n" ); } - buf.append( "FNT5P7:\tDB\t01H,0FAH,00H,00H,00H,00H\n" // ! + buf.append( "FONT_5X7:\n" + + "\tDB\t01H,0FAH,00H,00H,00H,00H\n" // ! + "\tDB\t03H,0E0H,00H,0E0H,00H,00H\n" // " + "\tDB\t05H,28H,0FEH,28H,0FEH,28H\n" // # + "\tDB\t05H,24H,54H,0FEH,54H,48H\n" // $ @@ -5025,21 +5478,26 @@ public static void appendDataTo( public static void appendBssTo( + AsmCodeBuf buf, BasicCompiler compiler, Map varDecls, Set usrLabels, StringBuilder userBSS ) { - AsmCodeBuf buf = compiler.getCodeBuf(); Set libItems = compiler.getLibItems(); BasicOptions options = compiler.getBasicOptions(); AbstractTarget target = compiler.getTarget(); if( options.getShowAssemblerText() ) { buf.append( "\n;Speicherzellen\n" ); } + if( libItems.contains( LibItem.M_SRNM ) ) { + buf.append( "M_SRNM:\tDS\t2\n" ); + } if( options.getPrintLineNumOnAbort() ) { - buf.append( "M_SRLN:\tDS\t2\n" - + "M_BALN:\tDS\t2\n" ); + buf.append( "M_SRLN:\tDS\t2\n" ); + } + if( libItems.contains( LibItem.M_BALN ) ) { + buf.append( "M_BALN:\tDS\t2\n" ); } if( libItems.contains( LibItem.DATA ) ) { /* @@ -5054,18 +5512,19 @@ public static void appendBssTo( buf.append( "M_RNDA:\tDS\t2\n" + "M_RNDX:\tDS\t1\n" ); } - KCNetLibrary.appendBssTo( compiler ); if( libItems.contains( LibItem.IO_VDIP_HANDLER ) ) { - VdipLibrary.appendBssTo( compiler ); + VdipLibrary.appendBssTo( buf, compiler ); } - if( libItems.contains( LibItem.KCNET_BASE ) - || libItems.contains( LibItem.IOOPEN ) + if( libItems.contains( LibItem.IOOPEN ) || libItems.contains( LibItem.IOCTB1 ) || libItems.contains( LibItem.IOCTB2 ) ) { - buf.append( "M_IONM:\tDS\t02H\n" - + "M_IOCA:\tDS\t02H\n" - + "M_IOAC:\tDS\t01H\n" ); + buf.append( "IO_M_NAME:\tDS\t2\n" + + "IO_M_CADDR:\tDS\t2\n" + + "IO_M_ACCESS:\tDS\t1\n" ); + } + if( libItems.contains( LibItem.IO_M_COUT ) ) { + buf.append( "IO_M_COUT:\tDS\t2\n" ); } if( libItems.contains( LibItem.IOCTB1 ) ) { /* @@ -5082,20 +5541,15 @@ public static void appendBssTo( * +2: EOF-Routine * In: DE: Anfangsadresse Kanalzeigerfeld * Out: HL=-1: EOF erreicht - * +4: AVAILABLE-Routine - * In: DE: Anfangsadresse Kanalzeigerfeld - * Out: HL: Anzahl der verfuegbaren Bytes - * +6: READ-Routine + * +4: READ-Routine * In: DE: Anfangsadresse Kanalzeigerfeld * Out: A: gelesenes Zeichen bzw. 00h ab EOF - * +8: WRITE-Routine - * In: (M_IOCA): Anfangsadresse Kanalzeigerfeld - * A: zu schreibendes Byte - * +10: FLUSH-Routine - * In: DE: Anfangsadresse Kanalzeigerfeld - * +12: Status Zeichenpuffer - * +13: Zeichenpuffer - * +14: Treiber-spezifisches Feld + * +6: WRITE-Routine + * In: (IO_M_CADDR): Anfangsadresse Kanalzeigerfeld + * A: zu schreibendes Byte + * +8: Status Zeichenpuffer + * +9: Zeichenpuffer + * +10: Treiber-spezifisches Feld */ buf.append( "IOCTB1:\tDS\t" ); buf.appendHex2( compiler.getIOChannelSize() ); @@ -5114,32 +5568,54 @@ public static void appendBssTo( buf.append( "M_XPOS:\tDS\t2\n" + "M_YPOS:\tDS\t2\n" ); } + if( libItems.contains( LibItem.DRAWS ) ) { + buf.append( "M_DRSM:\tDS\t1\n" ); + } if( libItems.contains( LibItem.DRBOX ) || libItems.contains( LibItem.DRBOXF ) - || libItems.contains( LibItem.DRLINE ) ) + || libItems.contains( LibItem.DRAW_LINE ) ) { - buf.append( "M_LNBX:\tDS\t2\n" - + "M_LNBY:\tDS\t2\n" - + "M_LNEX:\tDS\t2\n" - + "M_LNEY:\tDS\t2\n" - + "M_LNSX:\tDS\t2\n" - + "M_LNSY:\tDS\t2\n" ); + buf.append( "LINE_M_BX:\tDS\t2\n" + + "LINE_M_BY:\tDS\t2\n" + + "LINE_M_EX:\tDS\t2\n" + + "LINE_M_EY:\tDS\t2\n" + + "LINE_M_SX:\tDS\t2\n" + + "LINE_M_SY:\tDS\t2\n" ); } if( libItems.contains( LibItem.CIRCLE ) ) { - buf.append( "M_CIMX:\tDS\t2\n" - + "M_CIMY:\tDS\t2\n" - + "M_CIRA:\tDS\t2\n" - + "M_CIER:\tDS\t2\n" - + "M_CIRX:\tDS\t2\n" - + "M_CIRY:\tDS\t2\n" - + "M_CXMX:\tDS\t2\n" - + "M_CXPX:\tDS\t2\n" - + "M_CXMY:\tDS\t2\n" - + "M_CXPY:\tDS\t2\n" - + "M_CYMX:\tDS\t2\n" - + "M_CYPX:\tDS\t2\n" - + "M_CYMY:\tDS\t2\n" - + "M_CYPY:\tDS\t2\n" ); + buf.append( "CIRCLE_M_X:\tDS\t2\n" + + "CIRCLE_M_Y:\tDS\t2\n" + + "CIRCLE_M_R:\tDS\t2\n" + + "CIRCLE_M_ER:\tDS\t2\n" + + "CIRCLE_M_RX:\tDS\t2\n" + + "CIRCLE_M_RY:\tDS\t2\n" + + "CIRCLE_M_XMX:\tDS\t2\n" + + "CIRCLE_M_XPX:\tDS\t2\n" + + "CIRCLE_M_XMY:\tDS\t2\n" + + "CIRCLE_M_XPY:\tDS\t2\n" + + "CIRCLE_M_YMX:\tDS\t2\n" + + "CIRCLE_M_YPX:\tDS\t2\n" + + "CIRCLE_M_YMY:\tDS\t2\n" + + "CIRCLE_M_YPY:\tDS\t2\n" ); + } + if( libItems.contains( LibItem.PAINT ) ) { + buf.append( "PAINT_M_X:\tDS\t2\n" + + "PAINT_M_Y:\tDS\t2\n" + + "PAINT_M_X1:\tDS\t2\n" + + "PAINT_M_X2:\tDS\t2\n" + + "PAINT_M_SX2:\tDS\t2\n" + + "PAINT_M_SDIR:\tDS\t1\n" + + "PAINT_M_CX1:\tDS\t2\n" + + "PAINT_M_CX2:\tDS\t2\n" + + "PAINT_M_TAD:\tDS\t2\n" + + "PAINT_M_TSZ:\tDS\t1\n" + + "PAINT_M_TIX:\tDS\t1\n" ); + } + if( libItems.contains( LibItem.PAINT_M_HPIX ) ) { + buf.append( "PAINT_M_HPIX:\tDS\t2\n" ); + } + if( libItems.contains( LibItem.PAINT_M_WPIX ) ) { + buf.append( "PAINT_M_WPIX:\tDS\t2\n" ); } if( libItems.contains( LibItem.M_FRET ) ) { buf.append( "M_FRET:\tDS\t2\n" ); @@ -5147,10 +5623,6 @@ public static void appendBssTo( if( libItems.contains( LibItem.M_INKB ) ) { buf.append( "M_INKB:\tDS\t2\n" ); } - if( libItems.contains( LibItem.M_IO_COUT ) ) { - buf.append( "M_IO_COUT:\n" - + "\tDS\t2\n" ); - } if( libItems.contains( LibItem.M_HOST ) ) { buf.append( "M_HOST:\tDS\t2\n" ); } @@ -5251,50 +5723,86 @@ public static void appendBssTo( } buf.append( "\tDS\t" ); buf.appendHex4( stackSize ); - buf.append( "\n" - + "M_TOP:\n" ); - } else { - if( libItems.contains( LibItem.M_TOP ) ) { - buf.append( "M_TOP:\n" ); - } + buf.newLine(); } + buf.append( BasicCompiler.TOP_LABEL ); + buf.append( ":\n" ); } - public static void appendInitTo( + AsmCodeBuf buf, BasicCompiler compiler, Map varDecls, - Set usrLabels ) + SortedSet usrLabels ) { - AsmCodeBuf buf = compiler.getCodeBuf(); Set libItems = compiler.getLibItems(); BasicOptions options = compiler.getBasicOptions(); if( options.getShowAssemblerText() ) { buf.append( "\n;Initialisierungen\n" ); } - buf.append( "MINIT:\tLD\t(M_STCK),SP\n" ); + buf.append( BasicCompiler.START_LABEL ); + buf.append( ":\tLD\t(M_STCK),SP\n" ); if( options.getStackSize() > 0 ) { - buf.append( "\tLD\tSP,M_TOP\n" ); - libItems.add( LibItem.M_TOP ); + buf.append( "\tLD\tSP," ); + buf.append( BasicCompiler.TOP_LABEL ); + buf.newLine(); + libItems.add( LibItem.MTOP ); } if( options.getPrintLineNumOnAbort() ) { buf.append( "\tLD\tHL,0FFFFH\n" - + "\tLD\t(M_SRLN),HL\n" - + "\tLD\t(M_BALN),HL\n" ); + + "\tLD\t(M_SRLN),HL\n" ); + if( libItems.contains( LibItem.M_BALN ) ) { + buf.append( "\tLD\t(M_BALN),HL\n" ); + } + } + if( libItems.contains( LibItem.M_SRNM ) ) { + buf.append( "\tLD\tHL,0000H\n" + + "\tLD\t(M_SRNM),HL\n" ); } if( libItems.contains( LibItem.F_RND ) ) { - buf.append( "\tLD\tHL,MSTART\n" - + "\tLD\t(M_RNDA),HL\n" + buf.append_LD_HL_xx( BasicCompiler.START_LABEL ); + buf.append( "\tLD\t(M_RNDA),HL\n" + "\tLD\tA,46H\n" + "\tLD\t(M_RNDX),A\n" ); } if( libItems.contains( LibItem.DATA ) ) { buf.append( "\tCALL\tDINIT\n" ); } - KCNetLibrary.appendInitTo( compiler ); + if( libItems.contains( LibItem.E_USR ) && (usrLabels != null) ) { + int nUsrLabels = usrLabels.size(); + if( nUsrLabels > 0 ) { + if( nUsrLabels > 4 ) { + String firstUsrLabel = usrLabels.first(); + String loopLabel = compiler.nextLabel(); + buf.append( "\tLD\tDE,E_USR\n" + + "\tLD\tHL," ); + buf.append( firstUsrLabel ); + buf.append( "\n" + + "\tLD\tB," ); + buf.appendHex2( nUsrLabels ); + buf.append( (char) '\n' ); + buf.append( loopLabel ); + buf.append( ":\n" + + "\tLD\t(HL),E\n" + + "\tINC\tHL\n" + + "\tLD\t(HL),D\n" + + "\tINC\tHL\n" + + "\tDJNZ\t" ); + buf.append( loopLabel ); + buf.append( (char) '\n' ); + } else { + buf.append( "\tLD\tHL,E_USR\n" ); + for( String label : usrLabels ) { + buf.append( "\tLD\t(" ); + buf.append( label ); + buf.append( "),HL\n" ); + } + } + } + } if( libItems.contains( LibItem.IO_VDIP_HANDLER ) ) { - VdipLibrary.appendInitTo( compiler ); + VdipLibrary.appendInitTo( buf ); } /* * Zeigerfelder der einzelnen Kanaele initialisieren @@ -5336,9 +5844,9 @@ public static void appendInitTo( buf.append( "\tLD\t(M_ERN),HL\n" ); } if( libItems.contains( LibItem.M_ERT ) ) { - buf.append( "\tLD\tHL,D_EMPT\n" - + "\tLD\t(M_ERT),HL\n" ); - libItems.add( LibItem.D_EMPT ); + buf.append_LD_HL_xx( EMPTY_STRING_LABEL ); + buf.append( "\tLD\t(M_ERT),HL\n" ); + libItems.add( LibItem.EMPTY_STRING ); } Collection varNames = varDecls.keySet(); if( varNames != null ) { @@ -5374,9 +5882,9 @@ public static void appendInitTo( buf.append( "\tLD\tB," ); buf.appendHex2( nStrVars ); } - buf.append( "\n" - + "\tLD\tDE,D_EMPT\n" - + "\tLD\tHL," ); + buf.append( (char) '\n' ); + buf.append_LD_HL_xx( EMPTY_STRING_LABEL ); + buf.append( "\tLD\tHL," ); buf.append( firstStrVarLabel ); buf.append( "\n" + "MINIST:\tLD\t(HL),E\n" @@ -5391,10 +5899,10 @@ public static void appendInitTo( } else { buf.append( "\tDJNZ\tMINIST\n" ); } - libItems.add( LibItem.D_EMPT ); + libItems.add( LibItem.EMPTY_STRING ); } } else { - buf.append( "\tLD\tHL,D_EMPT\n" ); + buf.append_LD_HL_xx( EMPTY_STRING_LABEL ); for( int i = 0; i< a.length; i++ ) { if( a[ i ].endsWith( "$" ) ) { VarDecl var = varDecls.get( a[ i ] ); @@ -5405,7 +5913,7 @@ public static void appendInitTo( } } } - libItems.add( LibItem.D_EMPT ); + libItems.add( LibItem.EMPTY_STRING ); } } } @@ -5438,9 +5946,10 @@ public static void appendResetErrorUseBC( BasicCompiler compiler ) + "\tLD\t(M_ERN),BC\n" ); } if( compiler.usesLibItem( BasicLibrary.LibItem.M_ERT ) ) { - compiler.getCodeBuf().append( "\tLD\tBC,D_EMPT\n" - + "\tLD\t(M_ERT),BC\n" ); - compiler.getLibItems().add( BasicLibrary.LibItem.D_EMPT ); + AsmCodeBuf buf = compiler.getCodeBuf(); + buf.append_LD_BC_xx( EMPTY_STRING_LABEL ); + buf.append( "\tLD\t(M_ERT),BC\n" ); + compiler.getLibItems().add( BasicLibrary.LibItem.EMPTY_STRING ); } } @@ -5452,9 +5961,10 @@ public static void appendResetErrorUseDE( BasicCompiler compiler ) + "\tLD\t(M_ERN),DE\n" ); } if( compiler.usesLibItem( BasicLibrary.LibItem.M_ERT ) ) { - compiler.getCodeBuf().append( "\tLD\tDE,D_EMPT\n" - + "\tLD\t(M_ERT),DE\n" ); - compiler.getLibItems().add( BasicLibrary.LibItem.D_EMPT ); + AsmCodeBuf buf = compiler.getCodeBuf(); + buf.append_LD_DE_xx( EMPTY_STRING_LABEL ); + buf.append( "\tLD\t(M_ERT),DE\n" ); + compiler.getLibItems().add( BasicLibrary.LibItem.EMPTY_STRING ); } } @@ -5466,9 +5976,10 @@ public static void appendResetErrorUseHL( BasicCompiler compiler ) + "\tLD\t(M_ERN),HL\n" ); } if( compiler.usesLibItem( BasicLibrary.LibItem.M_ERT ) ) { - compiler.getCodeBuf().append( "\tLD\tHL,D_EMPT\n" - + "\tLD\t(M_ERT),HL\n" ); - compiler.getLibItems().add( BasicLibrary.LibItem.D_EMPT ); + AsmCodeBuf buf = compiler.getCodeBuf(); + buf.append_LD_HL_xx( EMPTY_STRING_LABEL ); + buf.append( "\tLD\t(M_ERT),HL\n" ); + compiler.getLibItems().add( BasicLibrary.LibItem.EMPTY_STRING ); } } @@ -5519,6 +6030,16 @@ public static void appendSetErrorChannelClosed( BasicCompiler compiler ) } + public static void appendSetErrorDirFull( BasicCompiler compiler ) + { + appendSetError( + compiler, + BasicLibrary.E_DIR_FULL, + "Directory voll", + "Directory full" ); + } + + public static void appendSetErrorDiskFull( BasicCompiler compiler ) { appendSetError( @@ -5579,6 +6100,16 @@ public static void appendSetErrorFileReadOnly( BasicCompiler compiler ) } + public static void appendSetErrorHardware( BasicCompiler compiler ) + { + appendSetError( + compiler, + E_HARDWARE, + "Hardware-Fehler", + "Hardware error" ); + } + + public static void appendSetErrorIOError( BasicCompiler compiler ) { appendSetError( @@ -5599,6 +6130,16 @@ public static void appendSetErrorInvalidChars( BasicCompiler compiler ) } + public static void appendSetErrorInvalidFileName( BasicCompiler compiler ) + { + appendSetError( + compiler, + E_INVALID, + "Ungueltiger Dateiname", + "Invalid filename" ); + } + + public static void appendSetErrorIOMode( BasicCompiler compiler ) { appendSetError( @@ -5619,6 +6160,16 @@ public static void appendSetErrorNoDisk( BasicCompiler compiler ) } + public static void appendSetErrorMediaChanged( BasicCompiler compiler ) + { + appendSetError( + compiler, + E_MEDIA_CHANGED, + "Medium gewechselt", + "Media changed" ); + } + + public static void appendSetErrorNumericOverflow( BasicCompiler compiler ) { appendSetError( @@ -5628,4 +6179,3 @@ public static void appendSetErrorNumericOverflow( BasicCompiler compiler ) "Numeric overflow" ); } } - diff --git a/src/jkcemu/programming/basic/BasicLineExpr.java b/src/jkcemu/programming/basic/BasicLineExpr.java index a665678..641e562 100644 --- a/src/jkcemu/programming/basic/BasicLineExpr.java +++ b/src/jkcemu/programming/basic/BasicLineExpr.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -10,21 +10,19 @@ import java.lang.*; import java.text.CharacterIterator; -import jkcemu.programming.PrgUtil; +import jkcemu.programming.*; -public class BasicLineExpr +public class BasicLineExpr extends BasicSourcePos { - private int sourceLineNum; - private long sourceBasicLineNum; private String exprText; private boolean label; public static BasicLineExpr checkBasicLineExpr( CharacterIterator iter, - int sourceLineNum, - long sourceBasicLineNum ) + PrgSource source, + long basicLineNum ) { BasicLineExpr rv = null; char ch = iter.current(); @@ -47,11 +45,7 @@ public static BasicLineExpr checkBasicLineExpr( buf.append( ch ); ch = iter.next(); } - rv = new BasicLineExpr( - sourceLineNum, - sourceBasicLineNum, - buf.toString(), - label ); + rv = new BasicLineExpr( source, basicLineNum, buf.toString(), label ); } return rv; } @@ -63,18 +57,6 @@ public String getExprText() } - public long getSourceBasicLineNum() - { - return this.sourceBasicLineNum; - } - - - public int getSourceLineNum() - { - return this.sourceLineNum; - } - - public boolean isLabel() { return this.label; @@ -84,15 +66,14 @@ public boolean isLabel() /* --- Konstruktor --- */ private BasicLineExpr( - int sourceLineNum, - long sourceBasicLineNum, - String exprText, - boolean label ) + PrgSource source, + long basicLineNum, + String exprText, + boolean label ) { - this.sourceLineNum = sourceLineNum; - this.sourceBasicLineNum = sourceBasicLineNum; - this.exprText = exprText; - this.label = label; + super( source, basicLineNum ); + this.exprText = exprText; + this.label = label; } } diff --git a/src/jkcemu/programming/basic/BasicOptions.java b/src/jkcemu/programming/basic/BasicOptions.java index 040e6a7..1b76996 100644 --- a/src/jkcemu/programming/basic/BasicOptions.java +++ b/src/jkcemu/programming/basic/BasicOptions.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -39,6 +39,11 @@ public static enum BreakOption { NEVER, INPUT, ALWAYS }; private int stackSize; private boolean checkStack; private boolean checkBounds; + private boolean openCrtEnabled; + private boolean openLptEnabled; + private boolean openFileEnabled; + private boolean openVdipEnabled; + private boolean inclBasicLines; private boolean preferRelJumps; private boolean printLineNumOnAbort; private boolean showAsmText; @@ -59,6 +64,11 @@ public BasicOptions() this.stackSize = DEFAULT_STACK_SIZE; this.checkStack = true; this.checkBounds = true; + this.openCrtEnabled = true; + this.openLptEnabled = true; + this.openFileEnabled = true; + this.openVdipEnabled = true; + this.inclBasicLines = true; this.preferRelJumps = true; this.printLineNumOnAbort = true; this.showAsmText = false; @@ -127,6 +137,22 @@ public static BasicOptions getBasicOptions( Properties props ) props, "jkcemu.programming.basic.bounds.check" ); + Boolean openCrtEnabled = getBoolean( + props, + "jkcemu.programming.basic.open.crt.enabled" ); + + Boolean openLptEnabled = getBoolean( + props, + "jkcemu.programming.basic.open.lpt.enabled" ); + + Boolean openFileEnabled = getBoolean( + props, + "jkcemu.programming.basic.open.file.enabled" ); + + Boolean openVdipEnabled = getBoolean( + props, + "jkcemu.programming.basic.open.vdip.enabled" ); + Boolean preferRelJumps = getBoolean( props, "jkcemu.programming.basic.prefer_relative_jumps" ); @@ -139,6 +165,10 @@ public static BasicOptions getBasicOptions( Properties props ) props, "jkcemu.programming.basic.show_assembler_source" ); + Boolean inclBasicLines = getBoolean( + props, + "jkcemu.programming.basic.include_basic_lines" ); + Boolean warnUnusedItems = getBoolean( props, "jkcemu.programming.basic.warn_unused_items" ); @@ -155,9 +185,14 @@ public static BasicOptions getBasicOptions( Properties props ) || (stackSize != null) || (checkStack != null) || (checkBounds != null) + || (openCrtEnabled != null) + || (openLptEnabled != null) + || (openFileEnabled != null) + || (openVdipEnabled != null) || (preferRelJumps != null) || (printLineNumOnAbort != null) || (showAsmText != null) + || (inclBasicLines != null) || (warnUnusedItems != null) || (breakOptionText != null) ) { @@ -190,6 +225,18 @@ public static BasicOptions getBasicOptions( Properties props ) if( checkBounds != null ) { options.checkBounds = checkBounds.booleanValue(); } + if( openCrtEnabled != null ) { + options.openCrtEnabled = openCrtEnabled.booleanValue(); + } + if( openLptEnabled != null ) { + options.openLptEnabled = openLptEnabled.booleanValue(); + } + if( openFileEnabled != null ) { + options.openFileEnabled = openFileEnabled.booleanValue(); + } + if( openVdipEnabled != null ) { + options.openVdipEnabled = openVdipEnabled.booleanValue(); + } if( preferRelJumps != null ) { options.preferRelJumps = preferRelJumps.booleanValue(); } @@ -199,6 +246,9 @@ public static BasicOptions getBasicOptions( Properties props ) if( showAsmText != null ) { options.showAsmText = showAsmText.booleanValue(); } + if( inclBasicLines != null ) { + options.inclBasicLines = inclBasicLines.booleanValue(); + } if( warnUnusedItems != null ) { options.warnUnusedItems = warnUnusedItems.booleanValue(); } @@ -253,6 +303,12 @@ public EmuSys getEmuSys() } + public boolean getIncludeBasicLines() + { + return this.inclBasicLines; + } + + public int getHeapSize() { return this.heapSize; @@ -307,6 +363,30 @@ public boolean getWarnUnusedItems() } + public boolean isOpenCrtEnabled() + { + return this.openCrtEnabled; + } + + + public boolean isOpenFileEnabled() + { + return this.openFileEnabled; + } + + + public boolean isOpenLptEnabled() + { + return this.openLptEnabled; + } + + + public boolean isOpenVdipEnabled() + { + return this.openVdipEnabled; + } + + public void setAppName( String appName ) { this.appName = appName; @@ -355,12 +435,42 @@ public void setHeapSize( int value ) } + public void setIncludeBasicLines( boolean state ) + { + this.inclBasicLines = state; + } + + public void setLangCode( String langCode ) { this.langCode = langCode; } + public void setOpenCrtEnabled( boolean state ) + { + this.openCrtEnabled = state; + } + + + public void setOpenLptEnabled( boolean state ) + { + this.openLptEnabled = state; + } + + + public void setOpenFileEnabled( boolean state ) + { + this.openFileEnabled = state; + } + + + public void setOpenVdipEnabled( boolean state ) + { + this.openVdipEnabled = state; + } + + public void setTarget( AbstractTarget target ) { this.target = target; @@ -416,6 +526,11 @@ public boolean equals( Object o ) && (options.stackSize == this.stackSize) && (options.checkStack == this.checkStack) && (options.checkBounds == this.checkBounds) + && (options.openCrtEnabled == this.openCrtEnabled) + && (options.openLptEnabled == this.openLptEnabled) + && (options.openFileEnabled == this.openFileEnabled) + && (options.openVdipEnabled == this.openVdipEnabled) + && (options.inclBasicLines == this.inclBasicLines) && (options.preferRelJumps == this.preferRelJumps) && (options.printLineNumOnAbort == this.printLineNumOnAbort) && (options.showAsmText == this.showAsmText) @@ -460,16 +575,32 @@ public void putOptionsTo( Properties props ) Integer.toString( this.heapSize ) ); props.setProperty( - "jkcemu.programming.basic.bounds.check", - Boolean.toString( this.checkBounds ) ); + "jkcemu.programming.basic.stack.size", + Integer.toString( this.stackSize ) ); props.setProperty( "jkcemu.programming.basic.stack.check", Boolean.toString( this.checkStack ) ); props.setProperty( - "jkcemu.programming.basic.stack.size", - Integer.toString( this.stackSize ) ); + "jkcemu.programming.basic.bounds.check", + Boolean.toString( this.checkBounds ) ); + + props.setProperty( + "jkcemu.programming.basic.open.crt.enabled", + Boolean.toString( this.openCrtEnabled ) ); + + props.setProperty( + "jkcemu.programming.basic.open.lpt.enabled", + Boolean.toString( this.openLptEnabled ) ); + + props.setProperty( + "jkcemu.programming.basic.open.file.enabled", + Boolean.toString( this.openFileEnabled ) ); + + props.setProperty( + "jkcemu.programming.basic.open.vdip.enabled", + Boolean.toString( this.openVdipEnabled ) ); props.setProperty( "jkcemu.programming.basic.prefer_relative_jumps", @@ -483,6 +614,10 @@ public void putOptionsTo( Properties props ) "jkcemu.programming.basic.show_assembler_source", Boolean.toString( this.showAsmText ) ); + props.setProperty( + "jkcemu.programming.basic.include_basic_lines", + Boolean.toString( this.inclBasicLines ) ); + props.setProperty( "jkcemu.programming.warn_unused_items", Boolean.toString( this.warnUnusedItems ) ); @@ -502,4 +637,3 @@ public void putOptionsTo( Properties props ) } } } - diff --git a/src/jkcemu/programming/basic/BasicOptionsDlg.java b/src/jkcemu/programming/basic/BasicOptionsDlg.java index a85fd05..7461ad4 100644 --- a/src/jkcemu/programming/basic/BasicOptionsDlg.java +++ b/src/jkcemu/programming/basic/BasicOptionsDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -24,10 +24,8 @@ public class BasicOptionsDlg extends AbstractOptionsDlg { private static final String textCodeBegAddr = "Anfangsadresse Programmcode:"; - private static final String textBssBegAddr = - "Anfangsadresse Speicherzelle:"; private static final String textHeapSize = - "Gr\u00F6\u00DFe des Zeichenkettenspeichers:"; + "Gr\u00F6\u00DFe Zeichenkettenspeicher:"; private static final AbstractTarget[] targets = { new CPMTarget(), @@ -38,47 +36,51 @@ public class BasicOptionsDlg extends AbstractOptionsDlg new Z9001Target(), new Z9001KRTTarget(), new KC85Target(), + new KC854Target(), new KramerMCTarget(), new Z1013Target(), new Z1013PetersTarget() }; - private EmuSys emuSys; - private JTabbedPane tabbedPane; - private JRadioButton btnStackSystem; - private JRadioButton btnStackSeparate; - private JRadioButton btnLangDE; - private JRadioButton btnLangEN; - private JRadioButton btnCheckAll; - private JRadioButton btnCheckNone; - private JRadioButton btnCheckCustom; - private JRadioButton btnBreakAlways; - private JRadioButton btnBreakInput; - private JRadioButton btnBreakNever; - private JComboBox comboTarget; - private JCheckBox btnCheckBounds; - private JCheckBox btnCheckStack; - private JCheckBox btnPreferRelJumps; - private JCheckBox btnPrintLineNumOnAbort; - private JCheckBox btnShowAsm; - private JCheckBox btnWarnNonAsciiChars; - private JCheckBox btnWarnUnusedItems; - private JTextField fldAppName; - private JTextField fldCodeBegAddr; - private JTextField fldBssBegAddr; - private JTextField fldHeapSize; - private JTextField fldStackSize; - private JLabel labelAppName; - private JLabel labelCodeBegAddr; - private JLabel labelCodeBegAddrUnit; - private JLabel labelBssBegAddr; - private JLabel labelBssBegAddrUnit; - private JLabel labelStackSize; - private JLabel labelStackUnit; - private LimitedDocument docAppName; - private HexDocument docCodeBegAddr; - private HexDocument docBssBegAddr; - private IntegerDocument docHeapSize; - private IntegerDocument docStackSize; + private EmuSys emuSys; + private JTabbedPane tabbedPane; + private JRadioButton btnStackSystem; + private JRadioButton btnStackSeparate; + private JRadioButton btnBssTrailed; + private JRadioButton btnBssBegAddr; + private JRadioButton btnLangDE; + private JRadioButton btnLangEN; + private JRadioButton btnCheckAll; + private JRadioButton btnCheckNone; + private JRadioButton btnCheckCustom; + private JRadioButton btnBreakAlways; + private JRadioButton btnBreakInput; + private JRadioButton btnBreakNever; + private JComboBox comboTarget; + private JCheckBox btnCheckBounds; + private JCheckBox btnCheckStack; + private JCheckBox btnOpenCrtEnabled; + private JCheckBox btnOpenLptEnabled; + private JCheckBox btnOpenFileEnabled; + private JCheckBox btnOpenVdipEnabled; + private JCheckBox btnInclBasicLines; + private JCheckBox btnPreferRelJumps; + private JCheckBox btnPrintLineNumOnAbort; + private JCheckBox btnShowAsm; + private JCheckBox btnWarnNonAsciiChars; + private JCheckBox btnWarnUnusedItems; + private JTextField fldAppName; + private JTextField fldCodeBegAddr; + private JTextField fldBssBegAddr; + private JTextField fldHeapSize; + private JTextField fldStackSize; + private JLabel labelAppName; + private JLabel labelBssBegAddrUnit; + private JLabel labelStackUnit; + private LimitedDocument docAppName; + private HexDocument docCodeBegAddr; + private HexDocument docBssBegAddr; + private IntegerDocument docHeapSize; + private IntegerDocument docStackSize; public BasicOptionsDlg( @@ -153,14 +155,19 @@ public BasicOptionsDlg( this.labelAppName = new JLabel( "Name des Programms:" ); panelGeneral.add( this.labelAppName, gbcGeneral ); gbcGeneral.gridy++; - this.labelCodeBegAddr = new JLabel( textCodeBegAddr ); - panelGeneral.add( this.labelCodeBegAddr, gbcGeneral ); + panelGeneral.add( new JLabel( textCodeBegAddr ), gbcGeneral ); gbcGeneral.gridy++; - panelGeneral.add( - new JLabel( "Gr\u00F6\u00DFe Zeichenkettenspeicher:" ), - gbcGeneral ); + panelGeneral.add( new JLabel( "Variablen/Speicherzellen:" ), gbcGeneral ); + gbcGeneral.gridy++; + gbcGeneral.gridy++; + panelGeneral.add( new JLabel( textHeapSize ), gbcGeneral ); gbcGeneral.gridy++; panelGeneral.add( new JLabel( "Stack:" ), gbcGeneral ); + gbcGeneral.gridy++; + gbcGeneral.gridy++; + panelGeneral.add( + new JLabel( "Sprache der Laufzeitausschriften:" ), + gbcGeneral ); boolean forceCodeToEmu = false; int presetTargetIdx = -1; @@ -172,19 +179,21 @@ public BasicOptionsDlg( } } } else if( emuSys != null ) { + int lastLevel = 0; for( int i = 0; i < targets.length; i++ ) { - if( targets[ i ].createsCodeFor( emuSys ) ) { + int level = targets[ i ].getCompatibilityLevel( emuSys ); + if( level > lastLevel ) { presetTargetIdx = i; forceCodeToEmu = true; - break; + lastLevel = level; } } } if( presetTargetIdx >= 0 ) { - this.comboTarget = new JComboBox( targets ); + this.comboTarget = new JComboBox<>( (Object[]) targets ); this.comboTarget.setEditable( false ); } else { - this.comboTarget = new JComboBox(); + this.comboTarget = new JComboBox<>(); this.comboTarget.setEditable( false ); this.comboTarget.addItem( "Bitte ausw\u00E4hlen" ); for( AbstractTarget target : targets ) { @@ -196,7 +205,6 @@ public BasicOptionsDlg( this.comboTarget.setSelectedIndex( presetTargetIdx ); } catch( IllegalArgumentException ex ) {} - this.comboTarget.addActionListener( this ); gbcGeneral.anchor = GridBagConstraints.WEST; gbcGeneral.insets.left = 0; gbcGeneral.gridwidth = GridBagConstraints.REMAINDER; @@ -204,37 +212,62 @@ public BasicOptionsDlg( gbcGeneral.gridx++; panelGeneral.add( this.comboTarget, gbcGeneral ); - this.docAppName = new LimitedDocument( 8 ); - this.fldAppName = new JTextField( this.docAppName, "", 0 ); - gbcGeneral.fill = GridBagConstraints.HORIZONTAL; - gbcGeneral.gridwidth = 2; + this.docAppName = new LimitedDocument( 8 ); + this.docAppName.setAsciiOnly( true ); + this.fldAppName = new JTextField( this.docAppName, "", 9 ); gbcGeneral.gridy++; panelGeneral.add( this.fldAppName, gbcGeneral ); - this.docCodeBegAddr = new HexDocument( 4, textCodeBegAddr ); - this.fldCodeBegAddr = new JTextField( this.docCodeBegAddr, "", 0 ); - this.fldCodeBegAddr.addActionListener( this ); + this.docCodeBegAddr = new HexDocument( 4 ); + this.fldCodeBegAddr = new JTextField( this.docCodeBegAddr, "", 5 ); gbcGeneral.gridwidth = 1; gbcGeneral.gridy++; panelGeneral.add( this.fldCodeBegAddr, gbcGeneral ); - this.labelCodeBegAddrUnit = new JLabel( "hex" ); - gbcGeneral.fill = GridBagConstraints.NONE; gbcGeneral.gridx++; - panelGeneral.add( this.labelCodeBegAddrUnit, gbcGeneral ); + panelGeneral.add( new JLabel( "hex" ), gbcGeneral ); + + ButtonGroup grpBss = new ButtonGroup(); - JTextField fldHeapSize = new JTextField(); - fldHeapSize.addActionListener( this ); + this.btnBssTrailed = new JRadioButton( + "direkt hinter Programmcode", + true ); + grpBss.add( this.btnBssTrailed ); + gbcGeneral.gridwidth = GridBagConstraints.REMAINDER; + gbcGeneral.gridx = 1; + gbcGeneral.gridy++; + panelGeneral.add( this.btnBssTrailed, gbcGeneral ); + + JPanel panelBssBegAddr = new JPanel(); + panelBssBegAddr.setLayout( + new BoxLayout( panelBssBegAddr, BoxLayout.X_AXIS ) ); + gbcGeneral.insets.top = 0; + gbcGeneral.gridy++; + panelGeneral.add( panelBssBegAddr, gbcGeneral ); + + this.btnBssBegAddr = new JRadioButton( "Ab Adresse:" ); + grpBss.add( this.btnBssBegAddr ); + panelBssBegAddr.add( this.btnBssBegAddr ); + panelBssBegAddr.add( Box.createRigidArea( new Dimension( 5, 0 ) ) ); + + this.docBssBegAddr = new HexDocument( 4 ); + this.fldBssBegAddr = new JTextField( this.docBssBegAddr, "", 5 ); + panelBssBegAddr.add( this.fldBssBegAddr ); + panelBssBegAddr.add( Box.createRigidArea( new Dimension( 5, 0 ) ) ); + + this.labelBssBegAddrUnit = new JLabel( "hex" ); + panelBssBegAddr.add( this.labelBssBegAddrUnit ); + + this.fldHeapSize = new JTextField( 5 ); this.docHeapSize = new IntegerDocument( fldHeapSize, new Integer( BasicOptions.MIN_HEAP_SIZE ), new Integer( BasicOptions.MAX_HEAP_SIZE ) ); - gbcGeneral.fill = GridBagConstraints.HORIZONTAL; - gbcGeneral.gridx = 1; + gbcGeneral.insets.top = 5; + gbcGeneral.gridx = 1; gbcGeneral.gridy++; - panelGeneral.add( fldHeapSize, gbcGeneral ); - gbcGeneral.fill = GridBagConstraints.NONE; - gbcGeneral.insets.right = 50; + panelGeneral.add( this.fldHeapSize, gbcGeneral ); + gbcGeneral.gridx++; panelGeneral.add( new JLabel( "Bytes" ), gbcGeneral ); @@ -243,88 +276,53 @@ public BasicOptionsDlg( this.btnStackSystem = new JRadioButton( "System-Stack verwenden", true ); - this.btnStackSystem.addActionListener( this ); grpStack.add( this.btnStackSystem ); - gbcGeneral.insets.right = 5; - gbcGeneral.gridwidth = GridBagConstraints.REMAINDER; - gbcGeneral.gridx = 1; + gbcGeneral.gridwidth = GridBagConstraints.REMAINDER; + gbcGeneral.gridx = 1; gbcGeneral.gridy++; panelGeneral.add( this.btnStackSystem, gbcGeneral ); + JPanel panelStackSeparate = new JPanel(); + panelStackSeparate.setLayout( + new BoxLayout( panelStackSeparate, BoxLayout.X_AXIS ) ); + gbcGeneral.insets.top = 0; + gbcGeneral.gridy++; + panelGeneral.add( panelStackSeparate, gbcGeneral ); + this.btnStackSeparate = new JRadioButton( "Eigener Stack-Bereich:", false ); - this.btnStackSeparate.addActionListener( this ); grpStack.add( this.btnStackSeparate ); - gbcGeneral.insets.top = 0; - gbcGeneral.gridy++; - panelGeneral.add( this.btnStackSeparate, gbcGeneral ); + panelStackSeparate.add( this.btnStackSeparate ); + panelStackSeparate.add( Box.createRigidArea( new Dimension( 5, 0 ) ) ); - this.labelStackSize = new JLabel( "Gr\u00F6\u00DFe:" ); - gbcGeneral.anchor = GridBagConstraints.EAST; - gbcGeneral.insets.left = 50; - gbcGeneral.gridwidth = 1; - gbcGeneral.gridy++; - panelGeneral.add( this.labelStackSize, gbcGeneral ); - - this.fldStackSize = new JTextField(); - this.fldStackSize.addActionListener( this ); + this.fldStackSize = new JTextField( 5 ); this.docStackSize = new IntegerDocument( this.fldStackSize, new Integer( BasicOptions.MIN_STACK_SIZE ), null ); - gbcGeneral.anchor = GridBagConstraints.WEST; - gbcGeneral.fill = GridBagConstraints.HORIZONTAL; - gbcGeneral.insets.left = 0; - gbcGeneral.gridwidth = 2; - gbcGeneral.gridx++; - panelGeneral.add( this.fldStackSize, gbcGeneral ); + panelStackSeparate.add( this.fldStackSize ); + panelStackSeparate.add( Box.createRigidArea( new Dimension( 5, 0 ) ) ); this.labelStackUnit = new JLabel( "Bytes" ); - gbcGeneral.gridx++; - panelGeneral.add( this.labelStackUnit, gbcGeneral ); - - gbcGeneral.anchor = GridBagConstraints.EAST; - gbcGeneral.fill = GridBagConstraints.NONE; - gbcGeneral.insets.top = 5; - gbcGeneral.insets.left = 5; - gbcGeneral.insets.bottom = 5; - gbcGeneral.gridwidth = 1; - gbcGeneral.gridx = 0; - gbcGeneral.gridy++; - panelGeneral.add( - new JLabel( "Sprache der Laufzeitausschriften:" ), - gbcGeneral ); + panelStackSeparate.add( this.labelStackUnit ); - JPanel panelLang = new JPanel( new GridBagLayout() ); - gbcGeneral.anchor = GridBagConstraints.WEST; - gbcGeneral.insets.left = 0; - gbcGeneral.gridwidth = GridBagConstraints.REMAINDER; - gbcGeneral.gridx++; + JPanel panelLang = new JPanel(); + panelLang.setLayout( new BoxLayout( panelLang, BoxLayout.X_AXIS ) ); + gbcGeneral.insets.top = 5; + gbcGeneral.gridx = 1; + gbcGeneral.gridy++; panelGeneral.add( panelLang, gbcGeneral ); - GridBagConstraints gbcLang = new GridBagConstraints( - 0, 0, - 1, 1, - 0.0, 0.0, - GridBagConstraints.WEST, - GridBagConstraints.NONE, - new Insets( 0, 0, 0, 0 ), - 0, 0 ); - ButtonGroup grpLang = new ButtonGroup(); this.btnLangDE = new JRadioButton( "Deutsch" ); grpLang.add( this.btnLangDE ); - this.btnLangDE.addActionListener( this ); - panelLang.add( this.btnLangDE, gbcLang ); + panelLang.add( this.btnLangDE ); this.btnLangEN = new JRadioButton( "Englisch" ); grpLang.add( this.btnLangEN ); - this.btnLangEN.addActionListener( this ); - gbcLang.insets.left = 5; - gbcLang.gridx++; - panelLang.add( this.btnLangEN, gbcLang ); + panelLang.add( this.btnLangEN ); // Bereich Laufzeiteigenschaften @@ -345,21 +343,18 @@ public BasicOptionsDlg( this.btnCheckAll = new JRadioButton( "Compilieren f\u00FCr Test und Debugging", true ); - this.btnCheckAll.addActionListener( this ); grpCheck.add( this.btnCheckAll ); panelCheck.add( this.btnCheckAll, gbcCheck ); this.btnCheckNone = new JRadioButton( "Compilieren f\u00FCr Produktiveinsatz", false ); - this.btnCheckNone.addActionListener( this ); grpCheck.add( this.btnCheckNone ); gbcCheck.insets.top = 0; gbcCheck.gridy++; panelCheck.add( this.btnCheckNone, gbcCheck ); this.btnCheckCustom = new JRadioButton( "Benutzerdefiniert", false ); - this.btnCheckCustom.addActionListener( this ); grpCheck.add( this.btnCheckCustom ); gbcCheck.insets.bottom = 5; gbcCheck.gridy++; @@ -411,6 +406,53 @@ public BasicOptionsDlg( panelCheck.add( this.btnPrintLineNumOnAbort, gbcCheck ); + // Bereich Treiber + JPanel panelDriver = new JPanel( new GridBagLayout() ); + this.tabbedPane.addTab( "Treiber", panelDriver ); + + GridBagConstraints gbcDriver = new GridBagConstraints( + 0, 0, + 1, 1, + 0.0, 0.0, + GridBagConstraints.WEST, + GridBagConstraints.NONE, + new Insets( 5, 5, 0, 5 ), + 0, 0 ); + + // Unterbereich OPEN-Anweisung + panelDriver.add( + new JLabel( "Bei Verwendung der OPEN-Anweisung" + + " folgende Treiber einbinden:" ), + gbcDriver ); + + this.btnOpenCrtEnabled = new JCheckBox( + "CRT-Treiber (Ausgabekanal auf Bildschirm)" ); + gbcDriver.insets.top = 0; + gbcDriver.insets.left = 50; + gbcDriver.gridy++; + panelDriver.add( this.btnOpenCrtEnabled, gbcDriver ); + + this.btnOpenLptEnabled = new JCheckBox( + "LPT-Treiber (Ausgabekanal auf Drucker, nur relevant" + + " wenn vom Zielsystem unterst\u00FCtzt)" ); + gbcDriver.insets.bottom = 0; + gbcDriver.gridy++; + panelDriver.add( this.btnOpenLptEnabled, gbcDriver ); + + this.btnOpenFileEnabled = new JCheckBox( + "FILE-Treiber (Zugriff auf Dateisystem, nur relevant" + + " bei Zielsystem \'CP/M-kompatibel\')" ); + gbcDriver.gridy++; + panelDriver.add( this.btnOpenFileEnabled, gbcDriver ); + + this.btnOpenVdipEnabled = new JCheckBox( + "VDIP-Treiber (Zugriff auf USB-Speicher, nur relevant" + + " wenn vom Zielsystem unterst\u00FCtzt)" ); + gbcDriver.insets.bottom = 5; + gbcDriver.gridy++; + panelDriver.add( this.btnOpenVdipEnabled, gbcDriver ); + + // Bereich Erzeugter Programmcode this.tabbedPane.addTab( "Erzeugter Programmcode", @@ -468,12 +510,17 @@ public BasicOptionsDlg( panelEtc.add( new JLabel( "Assembler-Code:" ), gbcEtc ); this.btnShowAsm = new JCheckBox( "Erzeugten Assembler-Code anzeigen" ); - gbcEtc.insets.top = 0; - gbcEtc.insets.left = 50; - gbcEtc.insets.bottom = 5; + gbcEtc.insets.top = 0; + gbcEtc.insets.left = 50; gbcEtc.gridy++; panelEtc.add( this.btnShowAsm, gbcEtc ); + this.btnInclBasicLines = new JCheckBox( + "BASIC-Zeilen als Kommentare einf\u00FCgen" ); + gbcEtc.insets.bottom = 5; + gbcEtc.gridy++; + panelEtc.add( this.btnInclBasicLines, gbcEtc ); + // Bereich Knoepfe gbc.fill = GridBagConstraints.NONE; @@ -511,11 +558,17 @@ public BasicOptionsDlg( } this.btnCheckBounds.setSelected( basicOptions.getCheckBounds() ); this.btnCheckStack.setSelected( basicOptions.getCheckStack() ); + this.btnOpenCrtEnabled.setSelected( basicOptions.isOpenCrtEnabled() ); + this.btnOpenLptEnabled.setSelected( basicOptions.isOpenLptEnabled() ); + this.btnOpenFileEnabled.setSelected( basicOptions.isOpenFileEnabled() ); + this.btnOpenVdipEnabled.setSelected( basicOptions.isOpenVdipEnabled() ); this.btnPreferRelJumps.setSelected( basicOptions.getPreferRelativeJumps() ); this.btnPrintLineNumOnAbort.setSelected( basicOptions.getPrintLineNumOnAbort() ); this.btnShowAsm.setSelected( basicOptions.getShowAssemblerText() ); + this.btnInclBasicLines.setSelected( + basicOptions.getIncludeBasicLines() ); this.btnWarnNonAsciiChars.setSelected( basicOptions.getWarnNonAsciiChars() ); this.btnWarnUnusedItems.setSelected( @@ -526,9 +579,14 @@ public BasicOptionsDlg( this.btnBreakAlways.setSelected( true ); this.btnCheckBounds.setSelected( true ); this.btnCheckStack.setSelected( true ); + this.btnOpenCrtEnabled.setSelected( true ); + this.btnOpenLptEnabled.setSelected( true ); + this.btnOpenFileEnabled.setSelected( true ); + this.btnOpenVdipEnabled.setSelected( true ); this.btnPreferRelJumps.setSelected( true ); this.btnPrintLineNumOnAbort.setSelected( true ); this.btnShowAsm.setSelected( false ); + this.btnInclBasicLines.setSelected( true ); this.btnWarnNonAsciiChars.setSelected( true ); this.btnWarnUnusedItems.setSelected( true ); resetBegAddrs = true; @@ -568,10 +626,30 @@ else if( this.btnBreakNever.isSelected() if( resetBegAddrs ) { updBegAddrsFromSelectedTarget(); } + updCodeToEmuFields(); updCodeDestFields( options, forceCodeToEmu ); updAppNameFieldsEnabled(); updCheckFieldsEnabled(); updStackFieldsEnabled(); + updInclBasicLinesEnabled(); + + + // Listener + this.comboTarget.addActionListener( this ); + this.fldCodeBegAddr.addActionListener( this ); + this.btnBssTrailed.addActionListener( this ); + this.btnBssBegAddr.addActionListener( this ); + this.fldBssBegAddr.addActionListener( this ); + this.fldHeapSize.addActionListener( this ); + this.btnStackSystem.addActionListener( this ); + this.btnStackSeparate.addActionListener( this ); + this.fldStackSize.addActionListener( this ); + this.btnLangDE.addActionListener( this ); + this.btnLangEN.addActionListener( this ); + this.btnCheckAll.addActionListener( this ); + this.btnCheckNone.addActionListener( this ); + this.btnCheckCustom.addActionListener( this ); + this.btnShowAsm.addActionListener( this ); // Fenstergroesse und -position @@ -601,11 +679,17 @@ protected void doApply() int codeBegAddr = this.docCodeBegAddr.intValue(); int actualAddr = codeBegAddr; - labelText = textHeapSize; - int heapSize = this.docHeapSize.intValue(); + labelText = "Anfangsadresse Variablen/Speicherzellen:"; + int bssBegAddr = -1; + if( this.btnBssBegAddr.isSelected() ) { + bssBegAddr = this.docBssBegAddr.intValue(); + } - labelText = "Stack-Gr\u00F6\u00DFe:"; - int stackSize = 0; + labelText = textHeapSize; + int heapSize = this.docHeapSize.intValue(); + + labelText = "Stack-Gr\u00F6\u00DFe:"; + int stackSize = 0; if( this.btnStackSeparate.isSelected() ) { stackSize = this.docStackSize.intValue(); } @@ -619,20 +703,27 @@ else if( this.btnBreakNever.isSelected() ) { breakOption = BasicOptions.BreakOption.NEVER; } + String appName = this.fldAppName.getText(); BasicOptions options = new BasicOptions(); options.setTarget( target ); - options.setAppName( this.fldAppName.getText() ); + options.setAppName( appName != null ? appName.trim() : null ); options.setCodeBegAddr( codeBegAddr ); + options.setBssBegAddr( bssBegAddr ); options.setHeapSize( heapSize ); options.setStackSize( stackSize ); options.setLangCode( this.btnLangDE.isSelected() ? "DE" : "EN" ); options.setBreakOption( breakOption ); options.setCheckBounds( this.btnCheckBounds.isSelected() ); options.setCheckStack( this.btnCheckStack.isSelected() ); + options.setOpenCrtEnabled( this.btnOpenCrtEnabled.isSelected() ); + options.setOpenLptEnabled( this.btnOpenLptEnabled.isSelected() ); + options.setOpenFileEnabled( this.btnOpenFileEnabled.isSelected() ); + options.setOpenVdipEnabled( this.btnOpenVdipEnabled.isSelected() ); options.setPreferRelativeJumps( this.btnPreferRelJumps.isSelected() ); options.setPrintLineNumOnAbort( this.btnPrintLineNumOnAbort.isSelected() ); options.setShowAssemblerText( this.btnShowAsm.isSelected() ); + options.setIncludeBasicLines( this.btnInclBasicLines.isSelected() ); options.setWarnNonAsciiChars( this.btnWarnNonAsciiChars.isSelected() ); options.setWarnUnusedItems( this.btnWarnUnusedItems.isSelected() ); try { @@ -671,6 +762,12 @@ protected boolean doAction( EventObject e ) updBegAddrsFromSelectedTarget(); updCodeToEmuFields(); } + else if( (src == this.btnBssTrailed) + || (src == this.btnBssBegAddr) ) + { + rv = true; + updBssFieldsEnabled(); + } else if( (src == this.btnStackSystem) || (src == this.btnStackSeparate) ) { @@ -696,6 +793,10 @@ else if( src == this.btnCheckNone ) { else if( src == this.btnCheckCustom ) { rv = true; updCheckFieldsEnabled(); + } + else if( src == this.btnShowAsm ) { + rv = true; + updInclBasicLinesEnabled(); } else { rv = true; if( src instanceof JTextField ) { @@ -728,7 +829,9 @@ public void settingsChanged() Object o = this.comboTarget.getItemAt( i ); if( o != null ) { if( o instanceof AbstractTarget ) { - if( ((AbstractTarget) o).createsCodeFor( emuSys ) ) { + if( ((AbstractTarget) o).getCompatibilityLevel( + emuSys ) > 0 ) + { targetIdx = i; break; } @@ -753,7 +856,9 @@ private void updAppNameFieldsEnabled() Object obj = this.comboTarget.getSelectedItem(); if( obj != null ) { if( obj instanceof AbstractTarget ) { - state = ((AbstractTarget) obj).supportsAppName(); + if( ((AbstractTarget) obj).getMaxAppNameLen() > 0 ) { + state = true; + } } } this.labelAppName.setEnabled( state ); @@ -770,6 +875,14 @@ private boolean updBegAddrs( int codeBegAddr, int bssBegAddr ) } else { this.fldCodeBegAddr.setText( "" ); } + if( bssBegAddr >= 0 ) { + this.docBssBegAddr.setValue( bssBegAddr, 4 ); + this.btnBssBegAddr.setSelected( true ); + } else { + this.fldBssBegAddr.setText( "" ); + this.btnBssTrailed.setSelected( true ); + } + updBssFieldsEnabled(); return rv; } @@ -787,6 +900,14 @@ private void updBegAddrsFromSelectedTarget() } + private void updBssFieldsEnabled() + { + boolean state = this.btnBssBegAddr.isSelected(); + this.fldBssBegAddr.setEnabled( state ); + this.labelBssBegAddrUnit.setEnabled( state ); + } + + private void updCheckFieldsEnabled() { boolean state = this.btnCheckCustom.isSelected(); @@ -806,7 +927,9 @@ private void updCodeToEmuFields() Object o = this.comboTarget.getSelectedItem(); if( o != null ) { if( o instanceof AbstractTarget ) { - codeToEmu = ((AbstractTarget) o).createsCodeFor( this.emuSys ); + if( ((AbstractTarget) o).getCompatibilityLevel( this.emuSys ) > 0 ) { + codeToEmu = true; + } } } } @@ -820,12 +943,16 @@ private void updCodeToEmuFields() } + private void updInclBasicLinesEnabled() + { + this.btnInclBasicLines.setEnabled( this.btnShowAsm.isSelected() ); + } + + private void updStackFieldsEnabled() { boolean state = this.btnStackSeparate.isSelected(); this.fldStackSize.setEnabled( state ); - this.labelStackSize.setEnabled( state ); this.labelStackUnit.setEnabled( state ); } } - diff --git a/src/jkcemu/programming/basic/BasicSourcePos.java b/src/jkcemu/programming/basic/BasicSourcePos.java new file mode 100644 index 0000000..85a178d --- /dev/null +++ b/src/jkcemu/programming/basic/BasicSourcePos.java @@ -0,0 +1,77 @@ +/* + * (c) 2014 Jens Mueller + * + * Kleincomputer-Emulator + * + * Abstraktion einer BASIC-Quelltextstelle + */ + +package jkcemu.programming.basic; + +import java.lang.*; +import jkcemu.programming.PrgSource; + + +public class BasicSourcePos +{ + private PrgSource source; + private long basicLineNum; + + + public BasicSourcePos( PrgSource source, long basicLineNum ) + { + this.source = source; + this.basicLineNum = basicLineNum; + } + + + public boolean appendMsgPrefixTo( String msgType, StringBuilder buf ) + { + boolean appended = false; + String srcName = null; + int srcLineNum = 0; + if( this.source != null ) { + srcLineNum = this.source.getLineNum(); + srcName = this.source.getName(); + } + if( (srcLineNum > 0) || (this.basicLineNum >= 0) ) { + if( srcName != null ) { + if( !srcName.isEmpty() ) { + buf.append( srcName ); + buf.append( ": " ); + } + } + if( msgType != null ) { + buf.append( msgType ); + buf.append( " in " ); + } + if( srcLineNum > 0 ) { + buf.append( "Zeile " ); + buf.append( srcLineNum ); + if( this.basicLineNum >= 0 ) { + buf.append( " (BASIC-Zeilennummer " ); + buf.append( this.basicLineNum ); + buf.append( (char) ')' ); + } + } else if( this.basicLineNum >= 0 ) { + buf.append( " BASIC-Zeile " ); + buf.append( this.basicLineNum ); + } + appended = true; + } + return appended; + } + + + public long getBasicLineNum() + { + return this.basicLineNum; + } + + + public PrgSource getSource() + { + return this.source; + } +} + diff --git a/src/jkcemu/programming/basic/BasicUtil.java b/src/jkcemu/programming/basic/BasicUtil.java new file mode 100644 index 0000000..0304cb1 --- /dev/null +++ b/src/jkcemu/programming/basic/BasicUtil.java @@ -0,0 +1,524 @@ +/* + * (c) 2008-2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * Hilfsfunktionen fuer BASIC-Compiler + */ + +package jkcemu.programming.basic; + +import java.lang.*; +import java.text.CharacterIterator; +import jkcemu.programming.*; + + +public class BasicUtil +{ + public static String checkIdentifier( CharacterIterator iter ) + { + String rv = null; + char ch = skipSpaces( iter ); + if( ((ch >= 'A') && (ch <= 'Z')) + || ((ch >= 'a') && (ch <= 'z')) ) + { + int pos = iter.getIndex(); + ch = iter.next(); + while( ((ch >= 'A') && (ch <= 'Z')) + || ((ch >= 'a') && (ch <= 'z')) + || ((ch >= '0') && (ch <= '9')) + || (ch == '_') ) + { + ch = iter.next(); + } + int len = iter.getIndex() - pos; + if( ch == '$' ) { + len++; + } + StringBuilder buf = new StringBuilder( len ); + iter.setIndex( pos ); + ch = iter.current(); + do { + buf.append( Character.toUpperCase( ch ) ); + ch = iter.next(); + --len; + } while( len > 0 ); + rv = buf.toString(); + } + return rv; + } + + + public static boolean checkKeyword( + CharacterIterator iter, + String keyword ) + { + return checkKeyword( iter, keyword, true, false ); + } + + + public static boolean checkKeyword( + CharacterIterator iter, + String keyword, + boolean fmtSource, + boolean space ) + { + boolean rv = true; + char ch = skipSpaces( iter ); + int begPos = iter.getIndex(); + int len = keyword.length(); + for( int i = 0; i < len; i++ ) { + if( keyword.charAt( i ) != Character.toUpperCase( ch ) ) { + iter.setIndex( begPos ); + rv = false; + break; + } + ch = iter.next(); + } + if( rv ) { + if( ((ch >= 'A') && (ch <= 'Z')) + || ((ch >= 'a') && (ch <= 'z')) + || (ch == '$') ) + { + rv = false; + } + } + return rv; + } + + + public static String checkStringLiteral( + BasicCompiler compiler, + CharacterIterator iter ) throws PrgException + { + StringBuilder buf = null; + char delimiter = skipSpaces( iter ); + if( delimiter == '\"' ) { + buf = new StringBuilder( 64 ); + char ch = iter.next(); + while( (ch != CharacterIterator.DONE) && (ch != delimiter) ) { + if( ch > 0xFF ) { + throw new PrgException( + String.format( + "\'%c\': 16-Bit-Unicodezeichen nicht erlaubt", + ch ) ); + } + if( compiler.getBasicOptions().getWarnNonAsciiChars() + && ((ch < '\u0020') || (ch > '\u007F')) ) + { + compiler.putWarningNonAsciiChar( ch ); + } + buf.append( ch ); + ch = iter.next(); + } + if( ch != delimiter ) { + compiler.putWarning( "String-Literal nicht geschlossen" ); + } + iter.next(); + } + return buf != null ? buf.toString() : null; + } + + + public static boolean checkToken( CharacterIterator iter, char ch ) + { + boolean rv = false; + if( skipSpaces( iter ) == ch ) { + iter.next(); + rv = true; + } + return rv; + } + + + public static String convertCodeToValueInBC( String text ) + { + return convertCodeToValueInRR( text, "BC" ); + } + + + public static String convertCodeToValueInDE( String text ) + { + return convertCodeToValueInRR( text, "DE" ); + } + + + public static boolean isOnly_LD_HL_xx( String text ) + { + boolean rv = false; + if( text != null ) { + int tabPos = text.indexOf( '\t' ); + if( tabPos > 0 ) { + text = text.substring( tabPos ); + } + if( text.startsWith( "\tLD\tHL," ) ) { + if( text.indexOf( '\n' ) == (text.length() - 1) ) { + rv = true; + } + } + else if( text.startsWith( "\tLD\tH," ) + || text.startsWith( "\tLD\tL," ) ) + { + int len = text.length(); + int eol = text.indexOf( '\n' ); + if( (eol > 0) && ((eol + 1) < len) ) { + String line1 = text.substring( 0, eol + 1 ); + String line2 = text.substring( eol + 1 ); + if( line2.indexOf( '\n' ) == (line2.length() - 1) ) { + if( line1.startsWith( "\tLD\tH,(IY" ) + && line2.startsWith( "\tLD\tL,(IY" ) ) + { + rv = true; + } + else if( line1.startsWith( "\tLD\tL,(IY" ) + && line2.startsWith( "\tLD\tH,(IY" ) ) + { + rv = true; + } + } + } + } + } + return rv; + } + + + /* + * Die Methode prueft, ob der uebergebene Text + * eine einzelne "LD_HL,..."-Anweisung ist. + */ + public static boolean isSingleInst_LD_HL_xx( String instText ) + { + boolean rv = false; + if( instText != null ) { + int tabPos = instText.indexOf( '\t' ); + if( tabPos > 0 ) { + instText = instText.substring( tabPos ); + } + if( instText.startsWith( "\tLD\tHL," ) ) { + int pos = instText.indexOf( '\n' ); + if( pos >= 0 ) { + if( pos == (instText.length() - 1) ) { + rv = true; + } + } else { + rv = true; + } + } + } + return rv; + } + + + public static void parseToken( + CharacterIterator iter, + char ch ) throws PrgException + { + if( skipSpaces( iter ) != ch ) { + throw new PrgException( String.format( "\'%c\' erwartet", ch ) ); + } + iter.next(); + } + + + public static int parseNumber( CharacterIterator iter ) + throws PrgException + { + Integer value = readNumber( iter ); + if( value == null ) { + throwNumberExpected(); + } + return value.intValue(); + } + + + public static int parseUsrNum( CharacterIterator iter ) throws PrgException + { + Integer usrNum = readNumber( iter ); + if( usrNum == null ) { + throw new PrgException( "Nummer der USR-Funktion erwartet" ); + } + if( (usrNum.intValue() < 0) || (usrNum.intValue() > 9) ) { + throw new PrgException( + "Ung\u00FCltige USR-Funktionsnummer (0...9 erlaubt)" ); + } + return usrNum.intValue(); + } + + + public static Integer readHex( CharacterIterator iter ) + throws PrgException + { + Integer rv = null; + char ch = iter.current(); + if( ((ch >= '0') && (ch <= '9')) + || ((ch >= 'A') && (ch <= 'F')) + || ((ch >= 'a') && (ch <= 'f')) ) + { + int value = 0; + while( ((ch >= '0') && (ch <= '9')) + || ((ch >= 'A') && (ch <= 'F')) + || ((ch >= 'a') && (ch <= 'f')) ) + { + value <<= 4; + if( (ch >= '0') && (ch <= '9') ) { + value |= (ch - '0'); + } + else if( (ch >= 'A') && (ch <= 'F') ) { + value |= (ch - 'A' + 10); + } + else if( (ch >= 'a') && (ch <= 'f') ) { + value |= (ch - 'a' + 10); + } + value &= 0xFFFF; + ch = iter.next(); + } + rv = new Integer( value ); + } + return rv; + } + + + public static Integer readNumber( CharacterIterator iter ) + throws PrgException + { + Integer rv = null; + char ch = skipSpaces( iter ); + + if( (ch >= '0') && (ch <= '9') ) { + int value = ch - '0'; + ch = iter.next(); + while( (ch >= '0') && (ch <= '9') ) { + value = (value * 10) + (ch - '0'); + if( value > BasicCompiler.MAX_INT_VALUE ) { + throw new PrgException( "Zahl zu gro\u00DF" ); + } + ch = iter.next(); + } + rv = new Integer( value ); + } + return rv; + } + + + /* + * Wenn die letzte erzeugte Code-Zeile das Laden + * des HL-Registers mit einem konstanten Wert darstellt, + * wird die Code-Zeile geloescht und der Wert zurueckgeliefert. + */ + public static Integer removeLastCodeIfConstExpr( + BasicCompiler compiler, + int pos ) + { + Integer rv = null; + AsmCodeBuf codeBuf = compiler.getCodeBuf(); + if( codeBuf != null ) { + String instText = codeBuf.substring( pos ); + int tabPos = instText.indexOf( '\t' ); + if( tabPos > 0 ) { + pos = tabPos; + instText = instText.substring( tabPos ); + } + if( instText.startsWith( "\tLD\tHL," ) + && instText.endsWith( "H\n" ) ) + { + try { + int v = Integer.parseInt( + instText.substring( 7, instText.length() - 2 ), + 16 ); + if( (v & 0x8000) != 0 ) { + // negativer Wert + v = -(0x10000 - (v & 0xFFFF)); + } + rv = new Integer( v ); + } + catch( NumberFormatException ex ) {} + } + if( rv != null ) { + codeBuf.setLength( pos ); + } + } + return rv; + } + + + public static boolean replaceLastCodeFrom_LD_HL_To_BC( + BasicCompiler compiler ) + { + return replaceLastCodeFrom_LD_HL_To_RR( compiler, "BC" ); + } + + + public static boolean replaceLastCodeFrom_LD_HL_To_DE( + BasicCompiler compiler ) + { + return replaceLastCodeFrom_LD_HL_To_RR( compiler, "DE" ); + } + + + public static char skipSpaces( CharacterIterator iter ) + { + char ch = iter.current(); + while( (ch != CharacterIterator.DONE) && PrgUtil.isWhitespace( ch ) ) { + ch = iter.next(); + } + return ch; + } + + + public static void throwHexDigitExpected() throws PrgException + { + throw new PrgException( "Hexadezimalziffer erwartet" ); + } + + + public static void throwIndexOutOfRange() throws PrgException + { + throw new PrgException( + "Index au\u00DFerhalb des g\u00FCltigen Bereichs" ); + } + + + public static void throwNumberExpected() throws PrgException + { + throw new PrgException( "Zahl erwartet" ); + } + + + public static void throwStringExprExpected() throws PrgException + { + throw new PrgException( "String-Ausdruck erwartet" ); + } + + + public static void throwUnexpectedChar( char ch ) throws PrgException + { + if( ch == CharacterIterator.DONE ) { + throw new PrgException( "Unerwartetes Ende der Zeile" ); + } + StringBuilder buf = new StringBuilder( 32 ); + if( ch >= '\u0020' ) { + buf.append( (char) '\'' ); + buf.append( ch ); + buf.append( "\': " ); + } + buf.append( "Unerwartetes Zeichen" ); + throw new PrgException( buf.toString() ); + } + + + /* --- private Methoden --- */ + + private static String convertCodeToValueInRR( String text, String rr ) + { + String rv = null; + if( (text != null) && (rr.length() == 2) ) { + String label = ""; + int tabPos = text.indexOf( '\t' ); + if( tabPos > 0 ) { + label = text.substring( 0, tabPos ); + text = text.substring( tabPos ); + } + if( text.startsWith( "\tLD\tHL," ) ) { + if( text.indexOf( '\n' ) == (text.length() - 1) ) { + rv = String.format( + "%s\tLD\t%s%s", + label, + rr, + text.substring( 6 ) ); + } + } + else if( text.startsWith( "\tLD\tH," ) + || text.startsWith( "\tLD\tL," ) ) + { + int len = text.length(); + int eol = text.indexOf( '\n' ); + if( (eol > 0) && ((eol + 1) < len) ) { + String line1 = text.substring( 0, eol + 1 ); + String line2 = text.substring( eol + 1 ); + if( line2.indexOf( '\n' ) == (line2.length() - 1) ) { + if( line1.startsWith( "\tLD\tH,(IY" ) + && line2.startsWith( "\tLD\tL,(IY" ) ) + { + rv = String.format( + "%s\tLD\t%c%s\tLD\t%c%s", + label, + rr.charAt( 0 ), + line1.substring( 5 ), + rr.charAt( 1 ), + line2.substring( 5 ) ); + } + else if( line1.startsWith( "\tLD\tL,(IY" ) + && line2.startsWith( "\tLD\tH,(IY" ) ) + { + rv = String.format( + "%s\tLD\t%c%s\tLD\t%c%s", + label, + rr.charAt( 1 ), + line1.substring( 5 ), + rr.charAt( 0 ), + line2.substring( 5 ) ); + } + } + } + } + } + return rv; + } + + + private static boolean replaceLastCodeFrom_LD_HL_To_RR( + BasicCompiler compiler, + String rr ) + { + boolean rv = false; + AsmCodeBuf codeBuf = compiler.getCodeBuf(); + if( codeBuf != null ) { + int pos = codeBuf.getLastLinePos(); + String instText = codeBuf.substring( pos ); + int tabPos = instText.indexOf( '\t' ); + if( tabPos > 0 ) { + pos = tabPos; + instText = instText.substring( tabPos ); + } + if( instText.startsWith( "\tLD\tHL," ) ) { + codeBuf.replace( pos + 4, pos + 6, rr ); + rv = true; + } else { + // Zugriff auf lokale Variable? + if( (rr.length() == 2) + && (instText.startsWith( "\tLD\tH," ) + || instText.startsWith( "\tLD\tL," )) ) + { + String line2 = codeBuf.cut( pos ); + String line1 = codeBuf.cut( codeBuf.getLastLinePos() ); + if( (line1.indexOf( "\tLD\tH,(IY" ) >= 0) + && (line2.indexOf( "\tLD\tL,(IY" ) >= 0) ) + { + line1 = line1.replace( + "\tLD\tH,(IY", + String.format( "\tLD\t%c,(IY", rr.charAt( 0 ) ) ); + line2 = line2.replace( + "\tLD\tL,(IY", + String.format( "\tLD\t%c,(IY", rr.charAt( 1 ) ) ); + rv = true; + } + else if( (line1.indexOf( "\tLD\tL,(IY" ) >= 0) + && (line2.indexOf( "\tLD\tH,(IY" ) >= 0) ) + { + line1 = line1.replace( + "\tLD\tL,(IY", + String.format( "\tLD\t%c,(IY", rr.charAt( 1 ) ) ); + line2 = line2.replace( + "\tLD\tH,(IY", + String.format( "\tLD\t%c,(IY", rr.charAt( 0 ) ) ); + rv = true; + } + codeBuf.append( line1 ); + codeBuf.append( line2 ); + } + } + } + return rv; + } +} diff --git a/src/jkcemu/programming/basic/CallableEntry.java b/src/jkcemu/programming/basic/CallableEntry.java index c580684..234fa9e 100644 --- a/src/jkcemu/programming/basic/CallableEntry.java +++ b/src/jkcemu/programming/basic/CallableEntry.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -10,56 +10,54 @@ import java.lang.*; import java.util.*; +import jkcemu.programming.PrgSource; -public abstract class CallableEntry extends StructureEntry +public abstract class CallableEntry extends BasicSourcePos { - private String name; - private String label; - private int firstCallSourceLineNum; - private long firstCallBasicLineNum; - private java.util.List args; - private java.util.List vars; - private Map var2SourceLineNum; - private Map var2BasicLineNum; - private Set usedVars; - private Map name2iyOffs; - private boolean implemented; - private boolean stackFrame; + private String name; + private String label; + private BasicSourcePos firstCallSourcePos; + private java.util.List args; + private java.util.List vars; + private Map var2SourcePos; + private Set usedVars; + private Map name2iyOffs; + private boolean implemented; + private boolean stackFrame; protected CallableEntry( - int sourceLineNum, - long basicLineNum, - String name, - String label ) + PrgSource source, + long basicLineNum, + String name, + String label ) { - super( sourceLineNum, basicLineNum ); - this.name = name; - this.label = label; - this.firstCallSourceLineNum = 0; - this.firstCallBasicLineNum = -1; - this.args = new ArrayList(); - this.vars = new ArrayList(); - this.var2SourceLineNum = new HashMap(); - this.var2BasicLineNum = new HashMap(); - this.usedVars = new TreeSet(); - this.name2iyOffs = new HashMap(); - this.implemented = false; - this.stackFrame = false; + super( source, basicLineNum ); + this.name = name; + this.label = label; + this.firstCallSourcePos = null; + this.args = new ArrayList<>(); + this.vars = new ArrayList<>(); + this.var2SourcePos = new HashMap<>(); + this.usedVars = new TreeSet<>(); + this.name2iyOffs = new HashMap<>(); + this.implemented = false; + this.stackFrame = false; } public void addVar( - int sourceLineNum, - long basicLineNum, - String varName ) + PrgSource source, + long basicLineNum, + String varName ) { this.name2iyOffs.put( varName, new Integer( getVarIYOffs( this.vars.size() ) ) ); - this.var2SourceLineNum.put( varName, new Integer( sourceLineNum ) ); - this.var2BasicLineNum.put( varName, new Long( basicLineNum ) ); + this.var2SourcePos.put( + varName, + new BasicSourcePos( source, basicLineNum ) ); this.vars.add( varName ); } @@ -92,15 +90,9 @@ public BasicCompiler.DataType getArgType( int idx ) } - public long getFirstCallBasicLineNum() + public BasicSourcePos getFirstCallSourcePos() { - return this.firstCallBasicLineNum; - } - - - public int getFirstCallSourceLineNum() - { - return this.firstCallSourceLineNum; + return this.firstCallSourcePos; } @@ -146,16 +138,9 @@ public int getVarIYOffs( int idx ) } - public long getVarBasicLineNum( String varName ) + public BasicSourcePos getVarSourcePos( String varName ) { - long rv = -1; - if( varName != null ) { - Long lineObj = this.var2BasicLineNum.get( varName ); - if( lineObj != null ) { - rv = lineObj.longValue(); - } - } - return rv; + return varName != null ? this.var2SourcePos.get( varName ) : null; } @@ -167,19 +152,6 @@ public String getVarName( int idx ) } - public int getVarSourceLineNum( String varName ) - { - int rv = -1; - if( varName != null ) { - Integer lineObj = this.var2SourceLineNum.get( varName ); - if( lineObj != null ) { - rv = lineObj.intValue(); - } - } - return rv; - } - - public BasicCompiler.DataType getVarType( int idx ) { return this.vars.get( idx ).endsWith( "$" ) ? @@ -196,7 +168,7 @@ public boolean hasStackFrame() public boolean isCalled() { - return (this.firstCallSourceLineNum > 0); + return (this.firstCallSourcePos != null); } @@ -212,14 +184,12 @@ public boolean isVarUsed( String varName ) } - public void putCallLineNum( - int sourceLineNum, - long basicLineNum ) + public void putCallPos( + PrgSource source, + long basicLineNum ) { - if( this.firstCallSourceLineNum <= 0 ) { - this.firstCallSourceLineNum = sourceLineNum; - this.firstCallBasicLineNum = basicLineNum; - } + if( this.firstCallSourcePos == null ) + this.firstCallSourcePos = new BasicSourcePos( source, basicLineNum ); } diff --git a/src/jkcemu/programming/basic/CmdLineBasicCompiler.java b/src/jkcemu/programming/basic/CmdLineBasicCompiler.java index fbd37bd..3e82e24 100644 --- a/src/jkcemu/programming/basic/CmdLineBasicCompiler.java +++ b/src/jkcemu/programming/basic/CmdLineBasicCompiler.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -11,6 +11,7 @@ import java.io.*; import java.lang.*; import java.util.*; +import java.util.regex.PatternSyntaxException; import jkcemu.Main; import jkcemu.base.*; import jkcemu.programming.*; @@ -28,13 +29,20 @@ public class CmdLineBasicCompiler "", "Optionen:", " -h diese Hilfe anzeigen", + " -f Kommandozeile aus Datei lesen", " -g bei Abbruch aufgrund eines Fehlers" + " Zeilennummer ausgeben", " -o Ausgabedatei festlegen", " -t Zielsystem festlegen (ac1, cpm, huebler, kc85," - + " kramer,", - " llc2_hires, scch, z1013, z9001, z9001_krt)", - " -A Anfangsadresse festlegen (hexadezimal)", + + " kc85_4,", + " kramer, llc2_hires, scch, z1013, z1013_64x16,", + " z9001, z9001_krt)", + " -A Anfangsadresse festlegen (hexadezimal)", + " -A Anfangsadressen f\u00FCr Programmcode (AAdr) und", + " BSS-Bereich (BAdr) festlegen (hexadezimal)", + " -D einzubindende Treiber festlegen" + + " (z.B.: -D \"CRT,LPT\")", + " vorhandene Treiber: CRT, LPT, FILE, VDIP", " -L Sprache der Laufzeitausschriften festlegen" + " (de, en)", " -M Stack-Gr\u00F6\u00DFe festlegen" @@ -74,89 +82,116 @@ public static boolean execute( String[] args, int argIdx ) boolean debugFlag = false; int optimizerLevel = 0; String srcFileName = null; - Map optToArg = new HashMap(); + Map optToArg = new HashMap<>(); BasicOptions.BreakOption breakOption = BasicOptions.BreakOption.ALWAYS; + + CmdLineArgIterator backIter = null; + CmdLineArgIterator iter = CmdLineArgIterator.createFromStringArray( + args, + argIdx ); try { - while( argIdx < args.length ) { - String arg = args[ argIdx++ ]; - if( arg != null ) { - int len = arg.length(); - if( len > 0 ) { - if( arg.charAt( 0 ) == '-' ) { - if( len < 2 ) { - throwWrongCmdLine(); - } - int pos = 1; - while( pos < len ) { - char ch = arg.charAt( pos++ ); - switch( ch ) { - case 'h': - case 'H': - helpFlag = true; - break; - case 'g': - debugFlag = true; - break; - case 'S': - asmFlag = true; - break; - case 'B': - if( pos < len ) { - switch( arg.charAt( pos++ ) ) { - case '0': - breakOption = BasicOptions.BreakOption.NEVER; - break; - case '1': - breakOption = BasicOptions.BreakOption.INPUT; - break; - case '2': - breakOption = BasicOptions.BreakOption.ALWAYS; - break; - default: - throw new IOException( - String.format( - "Option \'B%c\' nicht unterst\u00FCtzt", - ch ) ); - } - } else { + String arg = iter.next(); + while( arg != null ) { + int len = arg.length(); + if( len > 0 ) { + if( arg.charAt( 0 ) == '-' ) { + if( len < 2 ) { + throwWrongCmdLine(); + } + int pos = 1; + while( pos < len ) { + char ch = arg.charAt( pos++ ); + switch( ch ) { + case 'f': + { + if( backIter != null ) { throw new IOException( - "Option \'B\' hat falsches Format" ); + "Option -f in der Datei nicht erlaubt" ); } - break; - case 'O': + String fileName = null; if( pos < len ) { - char ch1 = arg.charAt( pos++ ); - if( (ch1 < '0') || (ch1 > '4') ) { - throw new IOException( - String.format( - "Option O%c nicht unterst\u00FCtzt", - ch1 ) ); - } - optimizerLevel = ch1 - '0'; + fileName = arg.substring( pos ); + pos = len; // Schleife verlassen } else { - optimizerLevel = 2; + fileName = iter.next(); + } + if( fileName == null ) { + throwWrongCmdLine(); + } + try { + backIter = iter; + iter = CmdLineArgIterator.createFromReader( + new FileReader( fileName ) ); + } + catch( IOException ex ) { + iter = backIter; + backIter = null; + } + } + break; + case 'h': + case 'H': + helpFlag = true; + break; + case 'g': + debugFlag = true; + break; + case 'S': + asmFlag = true; + break; + case 'B': + if( pos < len ) { + switch( arg.charAt( pos++ ) ) { + case '0': + breakOption = BasicOptions.BreakOption.NEVER; + break; + case '1': + breakOption = BasicOptions.BreakOption.INPUT; + break; + case '2': + breakOption = BasicOptions.BreakOption.ALWAYS; + break; + default: + throw new IOException( + String.format( + "Option \'B%c\' nicht unterst\u00FCtzt", + ch ) ); } - break; - case 'o': - case 't': - case 'A': - case 'L': - case 'M': - case 'N': - case 'T': - case 'W': + } else { + throw new IOException( + "Option \'B\' hat falsches Format" ); + } + break; + case 'O': + if( pos < len ) { + char ch1 = arg.charAt( pos++ ); + if( (ch1 < '0') || (ch1 > '4') ) { + throw new IOException( + String.format( + "Option O%c nicht unterst\u00FCtzt", + ch1 ) ); + } + optimizerLevel = ch1 - '0'; + } else { + optimizerLevel = 2; + } + break; + case 'o': + case 't': + case 'A': + case 'D': + case 'L': + case 'M': + case 'N': + case 'T': + case 'W': + { String optArg = null; if( pos < len ) { optArg = arg.substring( pos ); + pos = len; // Schleife verlassen } else { - while( (optArg == null) && (argIdx < args.length) ) { - String s = args[ argIdx++ ]; - if( s != null ) { - if( !s.isEmpty() ) { - optArg = s; - } - } - } + optArg = iter.next(); } if( optArg == null ) { throwWrongCmdLine(); @@ -166,24 +201,31 @@ public static boolean execute( String[] args, int argIdx ) throwWrongCmdLine(); } optToArg.put( optKey, optArg ); - break; - default: - throw new IOException( + } + break; + default: + throw new IOException( String.format( "Unbekannte Option \'%c\'", ch ) ); - } } - } else { - if( srcFileName != null ) { - throwWrongCmdLine(); - } - srcFileName = arg; } + } else { + if( srcFileName != null ) { + throwWrongCmdLine(); + } + srcFileName = arg; } } + arg = iter.next(); + if( (arg == null) && (backIter != null) ) { + EmuUtil.doClose( iter ); + iter = backIter; + backIter = null; + arg = iter.next(); + } } if( helpFlag ) { EmuUtil.printlnOut(); - EmuUtil.printlnOut( Main.VERSION + " BASIC-Compiler" ); + EmuUtil.printlnOut( Main.APPINFO + " BASIC-Compiler" ); for( String s : usageLines ) { EmuUtil.printlnOut( s ); } @@ -218,6 +260,9 @@ else if( sysName.equalsIgnoreCase( "CPM" ) ) { else if( sysName.equalsIgnoreCase( "KC85" ) ) { target = new KC85Target(); } + else if( sysName.equalsIgnoreCase( "KC85_4" ) ) { + target = new KC854Target(); + } else if( sysName.equalsIgnoreCase( "HUEBLER" ) ) { target = new HueblerGraphicsMCTarget(); } @@ -238,6 +283,9 @@ else if( sysName.equalsIgnoreCase( "LLC2_HIRES" ) ) { else if( sysName.equalsIgnoreCase( "Z1013" ) ) { target = new Z1013Target(); } + else if( sysName.equalsIgnoreCase( "Z1013_64X16" ) ) { + target = new Z1013PetersTarget(); + } } if( target == null ) { throw new IOException( @@ -265,12 +313,67 @@ else if( sysName.equalsIgnoreCase( "Z1013" ) ) { } // Anfangsadresse - int begAddr = getHex4Arg( optToArg, "A" ); - if( begAddr >= 0 ) { - options.setCodeBegAddr( begAddr ); + int codeBegAddr = -1; + int bssBegAddr = -1; + String opt = "A"; + String addrText = optToArg.get( opt ); + if( addrText != null ) { + int delimPos = addrText.indexOf( ':' ); + if( delimPos >= 0 ) { + codeBegAddr = getHex4( addrText.substring( 0, delimPos ), opt ); + bssBegAddr = getHex4( addrText.substring( delimPos + 1 ), opt ); + } else { + codeBegAddr = getHex4( addrText, opt ); + } + } + if( codeBegAddr >= 0 ) { + options.setCodeBegAddr( codeBegAddr ); } else { options.setCodeBegAddr( target.getDefaultBegAddr() ); } + if( bssBegAddr >= 0 ) { + options.setBssBegAddr( bssBegAddr ); + } + + // einzubindende Treiber + String driverNames = optToArg.get( "D" ); + if( driverNames != null ) { + boolean crtDrv = false; + boolean lptDrv = false; + boolean fileDrv = false; + boolean vdipDrv = false; + int argLen = driverNames.length(); + if( argLen > 1 ) { + if( driverNames.startsWith( "\"" ) + && driverNames.endsWith( "\"" ) ) + { + driverNames = driverNames.substring( 1, argLen - 1 ); + } + } + try { + String[] drivers = driverNames.split( "," ); + if( drivers != null ) { + for( String driver : drivers ) { + if( driver.equalsIgnoreCase( "CRT" ) ) { + crtDrv = true; + } else if( driver.equalsIgnoreCase( "LPT" ) ) { + lptDrv = true; + } else if( driver.equalsIgnoreCase( "FILE" ) ) { + fileDrv = true; + } else if( driver.equalsIgnoreCase( "VDIP" ) ) { + vdipDrv = true; + } else { + throw new IOException( driver + ": Unbekannter Treiber" ); + } + } + } + } + catch( PatternSyntaxException ex ) {} + options.setOpenCrtEnabled( crtDrv ); + options.setOpenLptEnabled( lptDrv ); + options.setOpenFileEnabled( fileDrv ); + options.setOpenVdipEnabled( vdipDrv ); + } // Heap-Groesse int heapSize = getIntArg( optToArg, "T" ); @@ -278,7 +381,7 @@ else if( sysName.equalsIgnoreCase( "Z1013" ) ) { if( heapSize < BasicOptions.MIN_HEAP_SIZE ) { throw new IOException( "Option \'T\': Wert zu klein" ); } - if( heapSize > BasicCompiler.MAX_VALUE ) { + if( heapSize > BasicCompiler.MAX_INT_VALUE ) { throw new IOException( "Option \'T\': Wert zu gro\u00DF" ); } options.setHeapSize( heapSize ); @@ -295,7 +398,7 @@ else if( sysName.equalsIgnoreCase( "Z1013" ) ) { throw new IOException( "Option \'T\': Wert entweder 0 oder >= " + Integer.toString( BasicOptions.MIN_STACK_SIZE ) ); } - if( heapSize > BasicCompiler.MAX_VALUE ) { + if( heapSize > BasicCompiler.MAX_INT_VALUE ) { throw new IOException( "Option \'T\': Wert zu gro\u00DF" ); } options.setStackSize( stackSize ); @@ -305,7 +408,7 @@ else if( sysName.equalsIgnoreCase( "Z1013" ) ) { // Programmname String appName = optToArg.get( "N" ); - if( appName == null ) { + if( (appName == null) && (target.getMaxAppNameLen() >= 6) ) { String fName = srcFile.getName(); if( fName != null ) { int pos = fName.indexOf( '.' ); @@ -355,7 +458,7 @@ else if( sysName.equalsIgnoreCase( "Z1013" ) ) { status = compile( srcFile, outFileName, - target.supportsAppName() ? appName : null, + appName, forZ9001, options, asmFlag ); @@ -363,7 +466,7 @@ else if( sysName.equalsIgnoreCase( "Z1013" ) ) { } catch( IOException ex ) { EmuUtil.printlnErr(); - EmuUtil.printlnErr( Main.VERSION + " BASIC-Compiler:" ); + EmuUtil.printlnErr( Main.APPINFO + " BASIC-Compiler:" ); String msg = ex.getMessage(); if( msg != null ) { if( !msg.isEmpty() ) { @@ -375,6 +478,9 @@ else if( sysName.equalsIgnoreCase( "Z1013" ) ) { } status = false; } + finally { + EmuUtil.doClose( iter ); + } return status; } @@ -410,7 +516,7 @@ private static boolean compile( if( (options.getTarget() instanceof CPMTarget) && (options.getCodeBegAddr() == 0x0100) ) { - fName += "*.com"; + fName += ".com"; } else { fName += ".bin"; } @@ -432,7 +538,8 @@ private static boolean compile( } PrgLogger logger = PrgLogger.createStandardLogger(); BasicCompiler compiler = new BasicCompiler( - EmuUtil.readTextFile( srcFile ), + null, + srcFile, options, logger ); String asmText = compiler.compile(); @@ -461,6 +568,7 @@ private static boolean compile( Z80Assembler assembler = new Z80Assembler( asmText, "Assembler-Quelltext", + null, options, logger, false ); @@ -485,12 +593,9 @@ private static boolean compile( } - private static int getHex4Arg( - Map optToArg, - String opt ) throws IOException + private static int getHex4( String arg, String opt ) throws IOException { - int rv = -1; - String arg = optToArg.get( opt ); + int rv = -1; if( arg != null ) { try { int len = arg.length(); @@ -518,6 +623,14 @@ private static int getHex4Arg( } + private static int getHex4Arg( + Map optToArg, + String opt ) throws IOException + { + return getHex4( optToArg.get( opt ), opt ); + } + + private static int getIntArg( Map optToArg, String opt ) throws IOException diff --git a/src/jkcemu/programming/basic/DoEntry.java b/src/jkcemu/programming/basic/DoEntry.java index aeb9053..32fc565 100644 --- a/src/jkcemu/programming/basic/DoEntry.java +++ b/src/jkcemu/programming/basic/DoEntry.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -9,6 +9,7 @@ package jkcemu.programming.basic; import java.lang.*; +import jkcemu.programming.PrgSource; public class DoEntry extends LoopEntry @@ -17,12 +18,12 @@ public class DoEntry extends LoopEntry public DoEntry( - int sourceLineNum, - long basicLineNum, - String loopLabel, - String exitLabel ) + PrgSource source, + long basicLineNum, + String loopLabel, + String exitLabel ) { - super( sourceLineNum, basicLineNum, loopLabel, exitLabel ); + super( source, basicLineNum, loopLabel, exitLabel ); } diff --git a/src/jkcemu/programming/basic/ForEntry.java b/src/jkcemu/programming/basic/ForEntry.java index 08c4667..ca10a80 100644 --- a/src/jkcemu/programming/basic/ForEntry.java +++ b/src/jkcemu/programming/basic/ForEntry.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -9,6 +9,7 @@ package jkcemu.programming.basic; import java.lang.*; +import jkcemu.programming.PrgSource; public class ForEntry extends LoopEntry @@ -19,7 +20,7 @@ public class ForEntry extends LoopEntry public ForEntry( - int sourceLineNum, + PrgSource source, long basicLineNum, String loopLabel, String exitLabel, @@ -27,7 +28,7 @@ public ForEntry( Integer toValue, Integer stepValue ) { - super( sourceLineNum, basicLineNum, loopLabel, exitLabel ); + super( source, basicLineNum, loopLabel, exitLabel ); this.varInfo = varInfo; this.toValue = toValue; this.stepValue = stepValue; diff --git a/src/jkcemu/programming/basic/FunctionEntry.java b/src/jkcemu/programming/basic/FunctionEntry.java index 853e516..7486cce 100644 --- a/src/jkcemu/programming/basic/FunctionEntry.java +++ b/src/jkcemu/programming/basic/FunctionEntry.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -9,6 +9,7 @@ package jkcemu.programming.basic; import java.lang.*; +import jkcemu.programming.PrgSource; public class FunctionEntry extends CallableEntry @@ -18,12 +19,12 @@ public class FunctionEntry extends CallableEntry public FunctionEntry( - int sourceLineNum, - long basicLineNum, - String name ) + PrgSource source, + long basicLineNum, + String name ) { super( - sourceLineNum, + source, basicLineNum, name, name.endsWith( "$" ) ? @@ -31,7 +32,7 @@ public FunctionEntry( : ("UFI_" + name) ); // Pseudovariable fuer Rueckgabewert - addVar( sourceLineNum, basicLineNum, name ); + addVar( source, basicLineNum, name ); setVarUsed( name ); this.retVarIYOffs = getVarIYOffs( getVarCount() - 1); this.retVarInfo = new SimpleVarInfo( diff --git a/src/jkcemu/programming/basic/IfEntry.java b/src/jkcemu/programming/basic/IfEntry.java index 314227a..5dfd475 100644 --- a/src/jkcemu/programming/basic/IfEntry.java +++ b/src/jkcemu/programming/basic/IfEntry.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -9,25 +9,42 @@ package jkcemu.programming.basic; import java.lang.*; +import jkcemu.programming.PrgSource; -public class IfEntry extends StructureEntry +public class IfEntry extends BasicSourcePos { private boolean multiLine; private String elseLabel; private String endifLabel; + private int codeCreationDisabledLevel; + private boolean ifCodeCreationDisabled; + private boolean elseCodeCreationDisabled; + public IfEntry( - int sourceLineNum, - long basicLineNum, - boolean multiLine, - String elseLabel, - String endifLabel ) + PrgSource source, + long basicLineNum, + boolean multiLine, + String elseLabel, + String endifLabel, + int codeCreationDisabledLevel, + boolean ifCodeCreationDisabled, + boolean elseCodeCreationDisabled ) + { + super( source, basicLineNum ); + this.multiLine = multiLine; + this.elseLabel = elseLabel; + this.endifLabel = endifLabel; + this.codeCreationDisabledLevel = codeCreationDisabledLevel; + this.ifCodeCreationDisabled = ifCodeCreationDisabled; + this.elseCodeCreationDisabled = elseCodeCreationDisabled; + } + + + public int getCodeCreationDisabledLevel() { - super( sourceLineNum, basicLineNum ); - this.multiLine = multiLine; - this.elseLabel = elseLabel; - this.endifLabel = endifLabel; + return this.codeCreationDisabledLevel; } @@ -43,6 +60,18 @@ public String getEndifLabel() } + public boolean isElseCodeCreationDisabled() + { + return this.elseCodeCreationDisabled; + } + + + public boolean isIfCodeCreationDisabled() + { + return this.ifCodeCreationDisabled; + } + + public boolean isMultiLine() { return this.multiLine; @@ -55,6 +84,18 @@ public void setElseLabel( String elseLabel ) } + public void setElseCodeCreationDisabled( boolean state ) + { + this.elseCodeCreationDisabled = state; + } + + + public void setIfCodeCreationDisabled( boolean state ) + { + this.ifCodeCreationDisabled = state; + } + + /* --- ueberschriebene Methoden --- */ @Override @@ -63,4 +104,3 @@ public String toString() return "IF-Anweisung"; } } - diff --git a/src/jkcemu/programming/basic/KCNetLibrary.java b/src/jkcemu/programming/basic/KCNetLibrary.java deleted file mode 100644 index fdfacde..0000000 --- a/src/jkcemu/programming/basic/KCNetLibrary.java +++ /dev/null @@ -1,1903 +0,0 @@ -/* - * (c) 2013 Jens Mueller - * - * Kleincomputer-Emulator - * - * KCNet-Bibliothek - */ - -package jkcemu.programming.basic; - -import java.lang.*; -import java.util.Set; -import jkcemu.net.W5100; - - -public class KCNetLibrary -{ - public static final int IOCTB_SOCKET_OFFS = BasicLibrary.IOCTB_DRIVER_OFFS; - public static final int IOCTB_PROTO_OFFS = IOCTB_SOCKET_OFFS + 1; - public static final int IOCTB_RX_CNT_OFFS = IOCTB_PROTO_OFFS + 1; - public static final int IOCTB_RX_RMN_OFFS = IOCTB_RX_CNT_OFFS + 2; - public static final int IOCTB_RX_POS_OFFS = IOCTB_RX_RMN_OFFS + 2; - public static final int IOCTB_TX_FSR_OFFS = IOCTB_RX_POS_OFFS + 2; - public static final int IOCTB_TX_WR_OFFS = IOCTB_TX_FSR_OFFS + 2; - public static final int IOCTB_REMOTE_OFFS = IOCTB_TX_WR_OFFS + 2; - public static final int IOCTB_CHANNEL_SIZE = IOCTB_REMOTE_OFFS + 6; - - - public static void appendCodeTo( BasicCompiler compiler ) - { - AsmCodeBuf buf = compiler.getCodeBuf(); - Set libItems = compiler.getLibItems(); - if( libItems.contains( BasicLibrary.LibItem.KCNET_SET_DNSSERVER ) ) { - /* - * Setzen des DNS-Servers, - * Fehlervariablen werden gesetzt - * Parameter: - * HL: Zeiger auf die textuelle IP-Adresse - * Rueckgabewert: - * CY=0: OK - * CY=1: Fehler - */ - buf.append( "KCNET_SET_DNSSERVER:\n" ); - BasicLibrary.appendResetErrorUseBC( compiler ); - buf.append( "\tCALL\tKCNET_PARSE_IPADDR\n" - + "\tJR\tC,KCNET_SET_ERR\n" - + "\tCALL\tKCNET_INIT_HW\n" - + "\tRET\tC\n" - + "\tLD\tA,06H\n" // IP-Adresse schreiben - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tXOR\tA\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tJR\tKCNET_SET_IPADDR1\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_PARSE_IPADDR ); - libItems.add( BasicLibrary.LibItem.KCNET_SET_BASE ); - libItems.add( BasicLibrary.LibItem.KCNET_BASE_HW ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_SET_GATEWAY ) ) { - /* - * Setzen des Gateways - * Fehlervariablen werden gesetzt - * Parameter: - * HL: Zeiger auf die textuelle IP-Adresse - * Rueckgabewert: - * CY=0: OK - * CY=1: Fehler - */ - buf.append( "KCNET_SET_GATEWAY:\n" - + "\tLD\tDE,8001H\n" - + "\tJR\tKCNET_SET_IPADDR\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_SET_BASE ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_SET_LOCALADDR ) ) { - /* - * Setzen der lokalen IP-Adresse - * Fehlervariablen werden gesetzt - * Parameter: - * HL: Zeiger auf die textuelle IP-Adresse - * Rueckgabewert: - * CY=0: OK - * CY=1: Fehler - */ - buf.append( "KCNET_SET_LOCALADDR:\n" - + "\tLD\tDE,800FH\n" - + "\tJR\tKCNET_SET_IPADDR\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_SET_BASE ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_SET_NETMASK ) ) { - /* - * Setzen der Netzwerkmaske - * Fehlervariablen werden gesetzt - * Parameter: - * HL: Zeiger auf die textuelle Netzwerkmaske - * Rueckgabewert: - * CY=0: OK - * CY=1: Fehler - */ - buf.append( "KCNET_SET_NETMASK:\n" - + "\tLD\tDE,8005H\n" ); - // direkt weiter mit KCNET_SET_IPADDR - libItems.add( BasicLibrary.LibItem.KCNET_SET_BASE ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_SET_BASE ) ) { - /* - * Schreiben einer IP-Adresse in den W5100-Adressraum - * Parameter: - * DE: Zieladresse im KCNet - * HL: Zeiger auf die textuelle IP-Adresse - */ - buf.append( "KCNET_SET_IPADDR:\n" ); - BasicLibrary.appendResetErrorUseBC( compiler ); - buf.append( "\tPUSH\tDE\n" - + "\tCALL\tKCNET_PARSE_IPADDR\n" - + "\tPOP\tDE\n" - + "\tJR\tC,KCNET_SET_ERR\n" - + "\tPUSH\tDE\n" - + "\tCALL\tKCNET_INIT_HW\n" - + "\tPOP\tDE\n" - + "\tRET\tC\n" - + "\tLD\tA,02H\n" // Adresszeiger schreiben - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,E\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,D\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tXOR\tA\n" // Bytes schreiben - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,04H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tXOR\tA\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "KCNET_SET_IPADDR1:\n" - + "\tLD\tDE,KCNET_M_IPADDR\n" - + "\tLD\tB,04H\n" - + "KCNET_SET_IPADDR2:\n" - + "\tLD\tA,(DE)\n" - + "\tINC\tDE\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tDJNZ\tKCNET_SET_IPADDR2\n" - + "\tOR\tA\n" - + "\tRET\n" - + "KCNET_SET_ERR:\n" ); - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_INVALID, - "Ungueltige IP-Adresse/Maske", - "Invalid ip address / mask" ); - buf.append( "\tSCF\n" - + "\tRET\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_PARSE_IPADDR ); - libItems.add( BasicLibrary.LibItem.KCNET_BASE_HW ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_HOSTBYNAME ) ) { - /* - * Parsen einer IP-Adresse oder eines Hostnamens - * und Eintragen der IP-Adresse in KCNET_M_IPADDR - * Parameter: - * HL: Zeiger auf den Namen - * Rueckgabewert: - * CY=0: OK, IP-Adresse steht in (KCNET_M_IPADDR) - * CY=1: Fehler - */ - buf.append( "KCNET_HOSTBYNAME:\n" - + "\tCALL\tKCNET_PARSE_IPADDR\n" - + "\tRET\tNC\n" - // IP-Adresse konnte nicht geparst werden -> DNS-Anfrage - + "\tLD\tA,03\n" - + "\tLD\t(KCNET_M_SOCKET),A\n" - + "\tCALL\tKCNET_INIT\n" - + "\tRET\tC\n" - + "\tLD\tE,02H\n" // UDP - + "\tCALL\tKCNET_SOCK_GET_PORT_AND_OPEN\n" - + "\tLD\tL,24H\n" // Sn_TX_WR - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tLD\t(KCNET_M_DNS_TX_WR),HL\n" - // ID (2 Bytes) - + "\tLD\tHL,KCNET_M_DNS_ID\n" - + "\tLD\tA,R\n" - + "\tRLD\n" - + "\tLD\tL,(HL)\n" - + "\tLD\tH,A\n" - + "\tLD\t(KCNET_M_DNS_ID),HL\n" - + "\tPUSH\tHL\n" - + "\tLD\tB,H\n" - + "\tCALL\tKCNET_DNS_WRITE\n" - + "\tPOP\tHL\n" - + "\tLD\tB,L\n" - + "\tCALL\tKCNET_DNS_WRITE\n" - // Rest des Headers (10 Bytes) - + "\tLD\tHL,KCNET_DATA_DNSQ\n" - + "\tLD\tB,0AH\n" - + "KCNET_HOSTBYNAME1:\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tLD\tB,A\n" - + "\tCALL\tKCNET_DNS_WRITE\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tDJNZ\tKCNET_HOSTBYNAME1\n" - // Hostname auswerten und an das Datenpaket anhaengen - + "\tEXX\n" - + "\tLD\tBC,0000H\n" - + "\tEXX\n" - + "\tLD\tHL,(KCNET_M_HOST)\n" - + "\tLD\tC,00H\n" // Zaehler fuer alle Zeichen - + "KCNET_HOSTBYNAME2:\n" // neues Label - + "\tLD\tB,00H\n" // Zaehler fuer Zeichen eines Labels - + "KCNET_HOSTBYNAME3:\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tOR\tA\n" - + "\tJR\tZ,KCNET_HOSTBYNAME5\n" - + "\tCP\t20H\n" - + "\tJR\tNZ,KCNET_HOSTBYNAME6\n" - + "KCNET_HOSTBYNAME4:\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tCP\t20H\n" - + "\tJR\tZ,KCNET_HOSTBYNAME4\n" - + "\tOR\tA\n" - + "\tJR\tNZ,KCNET_HOSTBYNAME8\n" - + "KCNET_HOSTBYNAME5:\n" - + "\tCALL\tKCNET_HOSTBYNAME9\n" - + "\tJR\tKCNET_HOSTBYNAME11\n" - + "KCNET_HOSTBYNAME6:\n" - + "\tJR\tC,KCNET_HOSTBYNAME8\n" - + "\tCP\t7FH\n" - + "\tJR\tNC,KCNET_HOSTBYNAME8\n" - + "\tINC\tC\n" - + "\tJR\tZ,KCNET_HOSTBYNAME8\n" - + "\tCP\t2EH\n" - + "\tJR\tNZ,KCNET_HOSTBYNAME7\n" - + "\tLD\tA,B\n" - + "\tOR\tA\n" - + "\tJR\tZ,KCNET_HOSTBYNAME8\n" - + "\tCALL\tKCNET_HOSTBYNAME9\n" - + "\tINC\tHL\n" - + "\tJR\tKCNET_HOSTBYNAME2\n" - + "KCNET_HOSTBYNAME7:\n" - + "\tINC\tB\n" - + "\tLD\tA,B\n" - + "\tCP\t41H\n" // max. 64 Zeichen pro Label - + "\tJR\tC,KCNET_HOSTBYNAME3\n" - + "KCNET_HOSTBYNAME8:\n" - + "\tCALL\tKCNET_SOCK_CLOSE\n" ); - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_INVALID, - "Ungueltiger Hostname", - "Invalid hostname" ); - buf.append( "\tSCF\n" - + "\tRET\n" - + "KCNET_HOSTBYNAME9:\n" - // Anzahl Zeichen schreiben - + "\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tCALL\tKCNET_DNS_WRITE\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tPUSH\tBC\n" - + "\tLD\tC,B\n" - + "\tLD\tB,00H\n" - + "\tOR\tA\n" - + "\tSBC\tHL,BC\n" - + "\tDEC\tHL\n" - + "\tPOP\tBC\n" - // Zeichen schreiben - + "KCNET_HOSTBYNAME10:\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tLD\tB,A\n" - + "\tCALL\tKCNET_DNS_WRITE\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tDJNZ\tKCNET_HOSTBYNAME10\n" - + "\tRET\n" - + "KCNET_HOSTBYNAME11:\n" - // Label-Endezeichen schreiben - + "\tLD\tB,00H\n" - + "\tCALL\tKCNET_DNS_WRITE\n" - // DNS-Anfrage abschliessen (QTYPE und QCLASS, jeweils 1) - + "\tLD\tB,02H\n" - + "KCNET_HOSTBYNAME12:\n" - + "\tPUSH\tBC\n" - + "\tLD\tB,00H\n" - + "\tCALL\tKCNET_DNS_WRITE\n" - + "\tLD\tB,01H\n" - + "\tCALL\tKCNET_DNS_WRITE\n" - + "\tPOP\tBC\n" - + "\tDJNZ\tKCNET_HOSTBYNAME12\n" - + "\tEXX\n" - + "\tLD\t(KCNET_M_DNS_QDLEN),BC\n" - + "\tEXX\n" - // IP-Adresse und Portnummer setzen - + "\tLD\tA,07H\n" // IP-Addresse lesen - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tXOR\tA\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tBC,0400H\n" - + "KCNET_HOSTBYNAME13:\n" - + "\tCALL\tKCNET_RD_BYTE\n" - + "\tPUSH\tAF\n" - + "\tOR\tC\n" - + "\tLD\tC,A\n" - + "\tDJNZ\tKCNET_HOSTBYNAME13\n" - + "\tJR\tNZ,KCNET_HOSTBYNAME14\n" - + "\tPOP\tAF\n" - + "\tPOP\tAF\n" - + "\tPOP\tAF\n" - + "\tPOP\tAF\n" ); - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_DNS_NOT_CONFIGURED, - "DNS nicht konfiguriert", - "DNS not configured" ); - buf.append( "\tJP\tKCNET_HOSTBYNAME24\n" - + "KCNET_HOSTBYNAME14:\n" - + "\tLD\tDE,040FH\n" // 4 Durchlaeufe und Sn_DIPR+3 - + "KCNET_HOSTBYNAME15:\n" - + "\tPOP\tBC\n" - + "\tLD\tC,E\n" - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - + "\tDEC\tE\n" - + "\tDEC\tD\n" - + "\tJR\tNZ,KCNET_HOSTBYNAME15\n" - + "\tLD\tBC,0035H\n" // Port 53 - + "\tLD\tL,10H\n" // Sn_DPORT - + "\tCALL\tKCNET_SOCK_SET_WORD\n" - + "\tLD\tBC,(KCNET_M_DNS_TX_WR)\n" - + "\tCALL\tKCNET_SOCK_SEND\n" - + "\tJP\tC,KCNET_HOSTBYNAME24\n" - + "\tLD\tDE,0000H\n" // Timeout-Zaehler - + "\tJR\tKCNET_HOSTBYNAME17\n" - // neuer Empfang aktivieren - + "KCNET_HOSTBYNAME16:\n" - + "\tLD\tL,26H\n" // Sn_RX_RSR - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tPUSH\tHL\n" - + "\tLD\tL,28H\n" // Sn_RX_RD - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tPOP\tBC\n" - + "\tADD\tHL,BC\n" - + "\tLD\tA,H\n" - + "\tAND\t07H\n" - + "\tLD\tB,A\n" - + "\tLD\tC,L\n" - + "\tLD\tL,28H\n" // Sn_RX_RD - + "\tCALL\tKCNET_SOCK_SET_WORD\n" - + "\tLD\tBC,4001H\n" // Sn_CR=RECV - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - // auf Antwort warten - + "KCNET_HOSTBYNAME17:\n" - + "\tLD\tL,26H\n" // Sn_RX_RSR - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tLD\tA,H\n" - + "\tAND\t07H\n" - + "\tLD\tH,A\n" - + "\tOR\tL\n" - + "\tJR\tNZ,KCNET_HOSTBYNAME18\n" - + "\tDEC\tDE\n" - + "\tLD\tA,D\n" - + "\tOR\tE\n" - + "\tJR\tNZ,KCNET_HOSTBYNAME17\n" ); - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_TIMEOUT, - "Keine DNS-Antwort", - "No DNS response" ); - buf.append( "\tJP\tKCNET_HOSTBYNAME24\n" - + "KCNET_HOSTBYNAME18:\n" - + "\tLD\t(KCNET_M_DNS_RX_RMN),HL\n" - + "\tLD\tL,28H\n" // Sn_RX_RD - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tLD\t(KCNET_M_DNS_RX_RD),HL\n" - // UDP-Header ueberlesen - + "\tLD\tBC,0008H\n" - + "\tCALL\tKCNET_DNS_SKIP\n" - + "\tJR\tC,KCNET_HOSTBYNAME16\n" - // ID lesen und vergleichen - + "\tCALL\tKCNET_DNS_READ_WORD\n" - + "\tJR\tC,KCNET_HOSTBYNAME16\n" - + "\tLD\tBC,(KCNET_M_DNS_ID)\n" - + "\tOR\tA\n" - + "\tSBC\tHL,BC\n" - + "\tJR\tNZ,KCNET_HOSTBYNAME16\n" - // DNS-Antwort? - + "\tCALL\tKCNET_DNS_READ_BYTE\n" - + "\tJR\tC,KCNET_HOSTBYNAME16\n" - + "\tAND\t80H\n" - + "\tJR\tZ,KCNET_HOSTBYNAME16\n" - // DNS-Fehler? - + "\tCALL\tKCNET_DNS_READ_BYTE\n" - + "\tJR\tC,KCNET_HOSTBYNAME19\n" - + "\tAND\t0FH\n" - + "\tJR\tZ,KCNET_HOSTBYNAME20\n" - + "\tCP\t02H\n" - + "\tJR\tZ,KCNET_HOSTBYNAME23\n" - + "\tCP\t03H\n" - + "\tJR\tZ,KCNET_HOSTBYNAME23\n" - + "KCNET_HOSTBYNAME19:\n" ); - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_DNS_ERROR, - "DNS-Anfrage fehlgeschlagen", - "DNS error" ); - buf.append( "\tJR\tKCNET_HOSTBYNAME24\n" - + "KCNET_HOSTBYNAME20:\n" - // QDCOUNT auswerten - + "\tCALL\tKCNET_DNS_READ_WORD\n" - + "\tJR\tC,KCNET_HOSTBYNAME19\n" - + "\tLD\tA,H\n" - + "\tOR\tA\n" - + "\tJR\tNZ,KCNET_HOSTBYNAME19\n" - + "\tLD\tA,L\n" - + "\tCP\t01H\n" - + "\tJR\tZ,KCNET_HOSTBYNAME21\n" - + "\tOR\tA\n" - + "\tJR\tNZ,KCNET_HOSTBYNAME19\n" - + "\tLD\tHL,0000H\n" // kein QD-Feld - + "\tLD\t(KCNET_M_DNS_QDLEN),HL\n" - + "KCNET_HOSTBYNAME21:\n" - // ANCOUNT auswerten - + "\tCALL\tKCNET_DNS_READ_WORD\n" - + "\tJR\tC,KCNET_HOSTBYNAME19\n" - + "\tLD\tA,H\n" - + "\tOR\tL\n" - + "\tJR\tZ,KCNET_HOSTBYNAME19\n" - // NSCOUNT und ARCOUNT ueberlesen - + "\tLD\tBC,0004H\n" - + "\tCALL\tKCNET_DNS_SKIP\n" - + "\tJR\tC,KCNET_HOSTBYNAME19\n" - // QD-Feld ueberlesen - + "\tLD\tBC,(KCNET_M_DNS_QDLEN)\n" - + "\tCALL\tKCNET_DNS_SKIP\n" - + "\tJR\tC,KCNET_HOSTBYNAME19\n" - // Antwortfeld - + "\tLD\tBC,000AH\n" - + "\tCALL\tKCNET_DNS_SKIP\n" - + "\tJR\tC,KCNET_HOSTBYNAME19\n" - + "\tCALL\tKCNET_DNS_READ_WORD\n" - + "\tJR\tC,KCNET_HOSTBYNAME19\n" - + "\tLD\tBC,0004H\n" - + "\tOR\tA\n" - + "\tSBC\tHL,BC\n" - + "\tJR\tNZ,KCNET_HOSTBYNAME19\n" - + "\tLD\tHL,KCNET_M_IPADDR\n" - + "\tLD\tB,04H\n" - + "KCNET_HOSTBYNAME22:\n" - + "\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tCALL\tKCNET_DNS_READ_BYTE\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tLD\t(HL),A\n" - + "\tINC\tHL\n" - + "\tDJNZ\tKCNET_HOSTBYNAME22\n" - + "\tCALL\tKCNET_SOCK_CLOSE\n" - + "\tOR\tA\n" // CY=0 - + "\tRET\n" - + "KCNET_HOSTBYNAME23:\n" ); - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_UNKNOWN_HOST, - "Host nicht gefunden", - "Host not found" ); - buf.append( "KCNET_HOSTBYNAME24:\n" - + "\tCALL\tKCNET_SOCK_CLOSE\n" - + "\tSCF\n" - + "\tRET\n" - /* - * Ueberspringen von empfangenen Bytes - * Parameter: - * BC: Anzahl der zu ueberspringenen Bytes - * Rueckgabewert: - * CY=0: OK - * CY=1: Ende uebersprungen - */ - + "KCNET_DNS_SKIP:\n" - + "\tLD\tHL,(KCNET_M_DNS_RX_RMN)\n" - + "\tOR\tA\n" - + "\tSBC\tHL,BC\n" - + "\tRET\tC\n" - + "\tLD\t(KCNET_M_DNS_RX_RMN),HL\n" - + "\tLD\tHL,(KCNET_M_DNS_RX_RD)\n" - + "\tADD\tHL,BC\n" - + "\tLD\t(KCNET_M_DNS_RX_RD),HL\n" - + "\tRET\n" - /* - * Lesen eines Bytes - * Rueckgabewert: - * CY=0: OK, gelesenes Byte in A - * CY=1: kein Byte mehr verfuegen - */ - + "KCNET_DNS_READ_BYTE:\n" - + "\tLD\tHL,(KCNET_M_DNS_RX_RMN)\n" - + "\tLD\tA,H\n" - + "\tOR\tL\n" - + "\tSCF\n" - + "\tRET\tZ\n" - + "\tDEC\tHL\n" - + "\tLD\t(KCNET_M_DNS_RX_RMN),HL\n" - + "\tLD\tHL,(KCNET_M_DNS_RX_RD)\n" - + "\tPUSH\tHL\n" - + "\tLD\tA,H\n" - + "\tAND\t07H\n" - + "\tOR\t0F8H\n" - + "\tLD\tH,A\n" - + "\tCALL\tKCNET_GET_BYTE\n" - + "\tEX\tAF,AF\'\n" - + "\tPOP\tHL\n" - + "\tINC\tHL\n" - + "\tLD\t(KCNET_M_DNS_RX_RD),HL\n" - + "\tEX\tAF,AF\'\n" - + "\tOR\tA\n" // CY=0 - + "\tRET\n" - /* - * Lesen eines Wortes - * Rueckgabewert: - * CY=0: OK, gelesenes in HL - * CY=1: kein Byte mehr verfuegen - */ - + "KCNET_DNS_READ_WORD:\n" - + "\tCALL\tKCNET_DNS_READ_BYTE\n" - + "\tRET\tC\n" - + "\tPUSH\tAF\n" - + "\tCALL\tKCNET_DNS_READ_BYTE\n" - + "\tPOP\tHL\n" - + "\tLD\tL,A\n" - + "\tRET\n" - /* - * Schreiben eines Bytes in DNS-Socket - * Parameter: - * B: Byte - */ - + "KCNET_DNS_WRITE:\n" - + "\tLD\tHL,(KCNET_M_DNS_TX_WR)\n" - + "\tPUSH\tHL\n" - + "\tLD\tA,H\n" - + "\tAND\t07H\n" - + "\tOR\t0D8H\n" - + "\tLD\tH,A\n" - + "\tCALL\tKCNET_SET_BYTE\n" - + "\tPOP\tHL\n" - + "\tINC\tHL\n" - + "\tLD\tA,H\n" - + "\tAND\t07H\n" - + "\tLD\tH,A\n" - + "\tLD\t(KCNET_M_DNS_TX_WR),HL\n" - + "\tEXX\n" - + "\tINC\tBC\n" - + "\tEXX\n" - + "\tRET\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_PARSE_IPADDR ); - libItems.add( BasicLibrary.LibItem.KCNET_SOCK ); - libItems.add( BasicLibrary.LibItem.KCNET_BASE ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_PARSE_IPADDR ) ) { - /* - * Parsen einer IP-Adresse und Eintragen dieser in KCNET_M_IPADDR - * Parameter: - * HL: Zeiger auf den Namen - * Rueckgabewert: - * CY=0: OK, IP-Adresse steht in (KCNET_M_IPADDR) - * CY=1: Fehler - */ - buf.append( "KCNET_PARSE_IPADDR:\n" - // Leerzeichen uebergehen - + "\tDEC\tHL\n" - + "KCNET_PARSE_IPADDR1:\n" - + "\tINC\tHL\n" - + "\tLD\tA,(HL)\n" - + "\tCP\t20H\n" - + "\tJR\tZ,KCNET_PARSE_IPADDR1\n" - + "\tLD\t(KCNET_M_HOST),HL\n" - + "\tLD\tDE,KCNET_M_IPADDR\n" - + "KCNET_PARSE_IPADDR2:\n" - // einzelne Zahl parsen - // B: Wert der Zahl - // C: Zaehler fuer Anzahl Ziffern - + "\tLD\tBC,0000H\n" - + "KCNET_PARSE_IPADDR3:\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tOR\tA\n" - + "\tJR\tZ,KCNET_PARSE_IPADDR4\n" - + "\tCP\t2EH\n" - + "\tJR\tZ,KCNET_PARSE_IPADDR4\n" - + "\tCP\t20H\n" - + "\tJR\tZ,KCNET_PARSE_IPADDR5\n" - + "\tSUB\t30H\n" - + "\tRET\tC\n" - + "\tCP\t0AH\n" - + "\tJR\tNC,KCNET_PARSE_IPADDR6\n" - + "\tEX\tAF,AF\'\n" // D=D*10 - + "\tLD\tA,B\n" - + "\tADD\tA,A\n" - + "\tRET\tC\n" - + "\tADD\tA,A\n" - + "\tRET\tC\n" - + "\tADD\tA,B\n" - + "\tRET\tC\n" - + "\tADD\tA,A\n" - + "\tRET\tC\n" - + "\tLD\tB,A\n" - + "\tEX\tAF,AF\'\n" - + "\tADD\tA,B\n" - + "\tRET\tC\n" - + "\tLD\tB,A\n" - + "\tINC\tC\n" - + "\tJR\tKCNET_PARSE_IPADDR3\n" - + "KCNET_PARSE_IPADDR4:\n" - + "\tLD\tA,C\n" - + "\tOR\tC\n" - + "\tJR\tZ,KCNET_PARSE_IPADDR6\n" - + "\tLD\tA,B\n" - + "\tLD\t(DE),A\n" - + "\tINC\tDE\n" - + "\tLD\tA,LOW(KCNET_M_IPADDR+4)\n" - + "\tCP\tE\n" - + "\tJR\tNZ,KCNET_PARSE_IPADDR2\n" - // IP-Adresse geparst, dahinter darf nichts mehr kommen - + "\tDEC\tHL\n" - + "KCNET_PARSE_IPADDR5:\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tOR\tA\n" - + "\tRET\tZ\n" - + "\tCP\t20H\n" - + "\tJR\tZ,KCNET_PARSE_IPADDR5\n" - // IP-Adresse konnte nicht geparst werden - + "KCNET_PARSE_IPADDR6:\n" - + "\tSCF\n" - + "\tRET\n" ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_S_MACADDR ) ) { - /* - * MAC-Adresse als String zurueckliefern - * Rueckgabewert: - * HL: Zeiger auf die Zeichenkette mit der MAC-Adresse - */ - buf.append( "KCNET_S_MACADDR:\n" ); - BasicLibrary.appendResetErrorUseHL( compiler ); - buf.append( "\tCALL\tKCNET_INIT_HW\n" - + "\tLD\tHL,D_EMPT\n" - + "\tRET\tC\n" - + "\tLD\tA,02H\n" // Adresszeiger schreiben - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,09H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,80H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,01H\n" // Bytes lesen - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,06H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tXOR\tA\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tDE,M_STMP\n" - + "\tLD\tB,06H\n" - + "\tJR\tKCNET_S_MACADDR2\n" - + "KCNET_S_MACADDR1:\n" - + "\tLD\tA,3AH\n" - + "\tLD\t(DE),A\n" - + "\tINC\tDE\n" - + "KCNET_S_MACADDR2:\n" - + "\tCALL\tKCNET_RD_BYTE\n" - + "\tCALL\tS_HXA\n" - + "\tDJNZ\tKCNET_S_MACADDR1\n" - + "\tXOR\tA\n" - + "\tLD\t(DE),A\n" - + "\tLD\tHL,M_STMP\n" - + "\tRET\n" ); - libItems.add( BasicLibrary.LibItem.S_HXA ); - libItems.add( BasicLibrary.LibItem.KCNET_BASE_HW ); - libItems.add( BasicLibrary.LibItem.D_EMPT ); - libItems.add( BasicLibrary.LibItem.M_STMP ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_S_DNSSERVER ) ) { - /* - * IP-Adresse des DNS-Servers als String zurueckliefern - * Rueckgabewert: - * HL: Zeiger auf die Zeichenkette mit der IP-Adresse - */ - buf.append( "KCNET_S_DNSSERVER:\n" ); - BasicLibrary.appendResetErrorUseHL( compiler ); - buf.append( "\tCALL\tKCNET_INIT_HW\n" - + "\tLD\tHL,D_EMPT\n" - + "\tRET\tC\n" - + "\tLD\tA,07H\n" - + "\tJR\tKCNET_S_GET_IPADDR1\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_S_GET_IPADDR ); - libItems.add( BasicLibrary.LibItem.KCNET_BASE_HW ); - libItems.add( BasicLibrary.LibItem.D_EMPT ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_S_GATEWAY ) ) { - /* - * Gateway-Adresse als String zurueckliefern - * Rueckgabewert: - * HL: Zeiger auf die Netzwerkmaske mit der IP-Adresse - */ - buf.append( "KCNET_S_GATEWAY:\n" - + "\tLD\tDE,8001H\n" - + "\tJR\tKCNET_S_GET_IPADDR\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_S_GET_IPADDR ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_S_NETMASK ) ) { - /* - * Netzwerkmaske als String zurueckliefern - * Rueckgabewert: - * HL: Zeiger auf die Netzwerkmaske mit der IP-Adresse - */ - buf.append( "KCNET_S_NETMASK:\n" - + "\tLD\tDE,8005H\n" - + "\tJR\tKCNET_S_GET_IPADDR\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_S_GET_IPADDR ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_S_LOCALADDR ) ) { - /* - * Eigene IP-Adresse als String zurueckliefern - * Rueckgabewert: - * HL: Zeiger auf die Zeichenkette mit der IP-Adresse - */ - buf.append( "KCNET_S_LOCALADDR:\n" - + "\tLD\tDE,800FH\n" ); - // direkt weiter mit KCNET_S_GET_IPADDR - libItems.add( BasicLibrary.LibItem.KCNET_S_GET_IPADDR ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_S_GET_IPADDR ) ) { - /* - * IP-Adresse lesen und als String zurueckliefern - * Parameter: - * DE: Adresse im KCNet-Adressraum - * Rueckgabewert: - * HL: Zeiger auf die Zeichenkette mit der IP-Adresse - */ - buf.append( "KCNET_S_GET_IPADDR:\n" - + "\tPUSH\tDE\n" - + "\tCALL\tKCNET_INIT_HW\n" - + "\tPOP\tDE\n" - + "\tLD\tHL,D_EMPT\n" - + "\tRET\tC\n" - + "\tLD\tA,02H\n" // Adresszeiger schreiben - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,E\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,D\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,01H\n" // Bytes lesen - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,04H\n" - + "KCNET_S_GET_IPADDR1:\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tXOR\tA\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tHL,KCNET_M_IPADDR\n" - + "\tLD\tB,04H\n" - + "KCNET_S_GET_IPADDR2:\n" - + "\tCALL\tKCNET_RD_BYTE\n" - + "\tLD\t(HL),A\n" - + "\tINC\tHL\n" - + "\tDJNZ\tKCNET_S_GET_IPADDR2\n" - + "\tLD\tHL,KCNET_M_IPADDR\n" ); - // direkt weiter mit KCNET_S_IPADDR - libItems.add( BasicLibrary.LibItem.KCNET_S_IPADDR ); - libItems.add( BasicLibrary.LibItem.KCNET_BASE_HW ); - libItems.add( BasicLibrary.LibItem.D_EMPT ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_S_IPADDR ) ) { - /* - * Umwandlung einer IP-Adresse in eine Zeichenkette - * Parameter: - * HL: Zeiger auf IP-Adresse (4 Bytes) - * Rueckgabewert: - * HL: Zeiger auf die Zeichenkette - */ - buf.append( "KCNET_S_IPADDR:\n" - + "\tLD\tDE,M_STMP\n" - + "\tLD\tB,04H\n" - + "\tJR\tKCNET_S_IPADDR2\n" - + "KCNET_S_IPADDR1:\n" - + "\tLD\tA,2EH\n" - + "\tLD\t(DE),A\n" - + "\tINC\tDE\n" - + "KCNET_S_IPADDR2:\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tPUSH\tHL\n" - + "\tLD\tHL,0064H\n" // 100 - + "\tCALL\tKCNET_S_IPADDR3\n" - + "\tLD\tL,0AH\n" // 10 - + "\tCALL\tKCNET_S_IPADDR3\n" - + "\tLD\tHL,0FF01H\n" // 1 - + "\tCALL\tKCNET_S_IPADDR3\n" - + "\tPOP\tHL\n" - + "\tDJNZ\tKCNET_S_IPADDR1\n" - + "\tXOR\tA\n" - + "\tLD\t(DE),A\n" - + "\tLD\tHL,M_STMP\n" - + "\tRET\n" - + "KCNET_S_IPADDR3:\n" - + "\tLD\tC,0FFH\n" - + "KCNET_S_IPADDR4:\n" - + "\tINC\tC\n" - + "\tSUB\tL\n" - + "\tJR\tNC,KCNET_S_IPADDR4\n" - + "\tADD\tA,L\n" - + "\tLD\tL,A\n" - + "\tLD\tA,C\n" - + "\tOR\tH\n" - + "\tLD\tA,L\n" - + "\tRET\tZ\n" - + "\tLD\tH,C\n" - + "\tLD\tA,C\n" - + "\tADD\tA,30H\n" - + "\tLD\t(DE),A\n" - + "\tINC\tDE\n" - + "\tLD\tA,L\n" - + "\tRET\n" ); - libItems.add( BasicLibrary.LibItem.M_STMP ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_TCP ) ) { - /* - * Setzen des Kanalzeigerfeldes - * Parameter: - * (M_IOCA): Anfangsadresse Kanalzeigerfeld - */ - buf.append( "KCNET_SET_IOPTRS_TCP:\n" - + "\tLD\tBC,000CH\n" - + "\tLD\tDE,(M_IOCA)\n" - + "\tLD\tHL,KCNET_DATA_IOPTRS\n" - + "\tLDIR\n" - + "\tXOR\tA\n" - + "\tLD\t(DE),A\n" // Status Zeichenpuffer - + "\tINC\tDE\n" - + "\tLD\t(DE),A\n" // Zeichen im Puffer - + "\tINC\tDE\n" - + "\tLD\tA,(KCNET_M_SOCKET)\n" - + "\tLD\t(DE),A\n" // IOCTB_SOCKET_OFFS - + "\tINC\tDE\n" - + "\tLD\tA,\'T\'\n" - + "\tLD\t(DE),A\n" // IOCTB_PROTO_OFFS - + "\tINC\tDE\n" - + "\tLD\tB," ); - buf.appendHex2( IOCTB_CHANNEL_SIZE - IOCTB_RX_CNT_OFFS ); - buf.append( "\n" - + "\tXOR\tA\n" - + "KCNET_SET_IOPTRS_TCP1:\n" - + "\tINC\tDE\n" - + "\tLD\t(DE),A\n" - + "\tDJNZ\tKCNET_SET_IOPTRS_TCP1\n" - + "\tRET\n" ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_TCP ) - || libItems.contains( BasicLibrary.LibItem.KCNET_UDP ) ) - { - /* - * IP-Adresse und Portnummer der Gegenstelle lesen - * und im Kanalzeigerfeld eintragen - * Parameter: - * (M_IOCA): Anfangsadresse Kanalzeigerfeld - */ - buf.append( "KCNET_IOCTB_FILL_REMOTE:\n" - + "\tLD\tDE,(M_IOCA)\n" ); - buf.append_LD_HL_nn( IOCTB_SOCKET_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tLD\t(KCNET_M_SOCKET),A\n" ); - buf.append_LD_HL_nn( IOCTB_REMOTE_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tEX\tDE,HL\n" - + "\tLD\tHL,040CH\n" // 4 Durchlaeufe und Sn_DIPR - + "KCNET_IOCTB_FILL_REMOTE1:\n" - + "\tPUSH\tHL\n" - + "\tCALL\tKCNET_SOCK_GET_BYTE\n" - + "\tLD\t(DE),A\n" - + "\tINC\tDE\n" - + "\tPOP\tHL\n" - + "\tINC\tL\n" - + "\tDEC\tH\n" - + "\tJR\tNZ,KCNET_IOCTB_FILL_REMOTE1\n" - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tEX\tDE,HL\n" - + "\tLD\t(HL),E\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),D\n" - + "\tRET\n" - /* - * Felder fuer Lese- und Schreiboperationen initialisieren - * Parameter: - * (M_IOCA): Anfangsadresse Kanalzeigerfeld - * (KCNET_M_SOCKET): Socket - */ - + "KCNET_IOCTB_INIT_TXRX:\n" - + "\tLD\tDE,(M_IOCA)\n" ); - buf.append_LD_HL_nn( IOCTB_RX_POS_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tEX\tDE,HL\n" - + "\tLD\tL,28H\n" // Sn_RX_RD - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tLD\tA,L\n" - + "\tLD\t(DE),A\n" - + "\tINC\tDE\n" - + "\tLD\tA,H\n" - + "\tLD\t(DE),A\n" - + "\tINC\tDE\n" - + "\tINC\tDE\n" - + "\tINC\tDE\n" // DE: IOCTB_TX_WR_OFFS - + "\tLD\tL,24H\n" // Sn_TX_WR - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tLD\tA,L\n" - + "\tLD\t(DE),A\n" - + "\tINC\tDE\n" - + "\tLD\tA,H\n" - + "\tLD\t(DE),A\n" - + "\tRET\n" ); - /* - * CLOSE-Routine - * Parameter: - * DE: Anfangsadresse Kanalzeigerfeld - */ - buf.append( "KCNET_CLOSE:\n" ); - buf.append_LD_HL_nn( IOCTB_SOCKET_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tLD\t(KCNET_M_SOCKET),A\n" ); - if( libItems.contains( BasicLibrary.LibItem.KCNET_TCP ) ) { - buf.append( "\tCALL\tKCNET_SOCK_GET_STATUS\n" - + "\tCP\t17H\n" // SOCK_ESTABLISHED - + "\tCALL\tZ,KCNET_FLUSH1\n" ); - } - buf.append( "\tJP\tKCNET_SOCK_CLOSE\n" - /* - * EOF-Routine - * Parameter: - * DE: Anfangsadresse Kanalzeigerfeld - * Rueckgabewert: - * HL: 0: Socket offen (TCP- oder UDP-Mode) - * -1: Socket geschlossen - */ - + "KCNET_EOF:\n" ); - buf.append_LD_HL_nn( IOCTB_SOCKET_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tLD\t(KCNET_M_SOCKET),A\n" - + "\tCALL\tKCNET_SOCK_CHECK_STATUS\n" - + "\tLD\tHL,0000H\n" - + "\tRET\tNC\n" - + "\tDEC\tHL\n" - + "\tRET\n" - /* - * AVAILABLE-Routine - * Parameter: - * DE: Anfangsadresse Kanalzeigerfeld - * Rueckgabewert: - * HL: Anzahl der empfangenen und noch nicht gelesenen Bytes - */ - + "KCNET_AVAILABLE:\n" ); - buf.append_LD_HL_nn( IOCTB_SOCKET_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tLD\t(KCNET_M_SOCKET),A\n" - + "\tCALL\tKCNET_SOCK_CHECK_STATUS\n" ); - if( libItems.contains( BasicLibrary.LibItem.KCNET_TCP ) - && libItems.contains( BasicLibrary.LibItem.KCNET_UDP ) ) - { - buf.append( "\tEX\tAF,AF\'\n" ); - } - buf.append( "\tLD\tHL,0000H\n" - + "\tRET\tC\n" ); - buf.append_LD_HL_nn( IOCTB_RX_RMN_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\tH,(HL)\n" - + "\tLD\tL,A\n" - + "\tOR\tH\n" - + "\tRET\tNZ\n" - + "\tLD\tL,26H\n" ); // Sn_RX_RSR - if( libItems.contains( BasicLibrary.LibItem.KCNET_UDP ) ) { - if( libItems.contains( BasicLibrary.LibItem.KCNET_TCP ) ) { - // TCP und UDP - buf.append( "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tEX\tAF,AF\'\n" - + "\tCP\t17H\n" // SOCK_ESTABLISHED - + "\tRET\tZ\n" - + "\tLD\tBC,0008H\n" - + "\tOR\tA\n" - + "\tSBC\tHL,BC\n" - + "\tRET\tNC\n" - + "\tLD\tHL,0000H\n" - + "\tRET\n" ); - } else { - // nur UDP - buf.append( "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tLD\tBC,0008H\n" - + "\tOR\tA\n" - + "\tSBC\tHL,BC\n" - + "\tRET\tNC\n" - + "\tLD\tHL,0000H\n" - + "\tRET\n" ); - } - } else { - buf.append( "\tJP\tKCNET_SOCK_GET_WORD\n" ); - } - /* - * READ-Routine - * Parameter: - * DE: Anfangsadresse Kanalzeigerfeld - * Rueckgabewert: - * A: gelesenes Zeichen - */ - buf.append( "KCNET_READ:\n" ); - buf.append_LD_HL_nn( IOCTB_SOCKET_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tLD\t(KCNET_M_SOCKET),A\n" - + "\tCALL\tKCNET_SOCK_CHECK_STATUS\n" - + "\tRET\tC\n" ); - // noch Bytes zu lesen? - buf.append_LD_HL_nn( IOCTB_RX_RMN_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\tH,(HL)\n" - + "\tLD\tL,A\n" - + "\tOR\tH\n" - + "\tJR\tNZ,KCNET_READ5\n" - // keine Bytes mehr zu lesen -> auf neue Bytes warten - + "KCNET_READ1:\n" ); - if( compiler.getBasicOptions().canBreakAlways() ) { - buf.append( "\tPUSH\tDE\n" - + "\tCALL\tXCKBRK\n" - + "\tPOP\tDE\n" ); - libItems.add( BasicLibrary.LibItem.XCKBRK ); - } - buf.append( "\tCALL\tKCNET_SOCK_CHECK_STATUS\n" - + "\tRET\tC\n" ); - if( libItems.contains( BasicLibrary.LibItem.KCNET_UDP ) ) { - buf.append( "\tEX\tAF,AF\'\n" ); - } - buf.append( "\tLD\tL,26H\n" // Sn_RX_RSR - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tLD\tA,H\n" - + "\tOR\tL\n" - + "\tJR\tZ,KCNET_READ1\n" - // neue Bytes empfangen -> Anzahl in IOCTB_RREM_OFFS eintragen - + "\tPUSH\tHL\n" - + "\tLD\tB,H\n" - + "\tLD\tC,L\n" ); - buf.append_LD_HL_nn( IOCTB_RX_RMN_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\t(HL),C\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),B\n" - + "\tINC\tHL\n" - // Leseposition in IOCTB_RPTR_OFFS eintragen - + "\tPUSH\tHL\n" - + "\tLD\tL,28H\n" // Sn_RX_RD - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tLD\tB,H\n" - + "\tLD\tC,L\n" - + "\tPOP\tHL\n" - + "\tLD\t(HL),C\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),B\n" - + "\tPOP\tBC\n" ); // BC: Anzahl Bytes - if( libItems.contains( BasicLibrary.LibItem.KCNET_UDP ) ) { - // bei UDP Header beachten - buf.append( "\tEX\tAF,AF\'\n" - + "\tCP\t22H\n" // SOCK_UDP - + "\tJR\tNZ,KCNET_READ5\n" - + "\tLD\tHL,0008H\n" - + "\tOR\tA\n" - + "\tSBC\tHL,BC\n" - + "\tJR\tC,KCNET_READ3\n" - // nur 8 oder weniger Bytes -> keine Nutzbytes -> Bytes ueberlesen - // duerfte nie vorkommen - + "\tLD\tB,C\n" - + "KCNET_READ2:\n" - + "\tPUSH\tBC\n" - + "\tCALL\tKCNET_READ5\n" - + "\tPOP\tBC\n" - + "\tDJNZ\tKCNET_READ2\n" - + "\tJR\tKCNET_READ1\n" - // UDP-Header lesen und auswerten - + "KCNET_READ3:\n" ); - buf.append_LD_HL_nn( IOCTB_REMOTE_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tB,04H\n" // IP-Adresse lesen - + "KCNET_READ4:\n" - + "\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tCALL\tKCNET_READ5\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tLD\t(HL),A\n" - + "\tINC\tHL\n" - + "\tDJNZ\tKCNET_READ4\n" - + "\tCALL\tKCNET_READ6\n" ); // Portnummer lesen - buf.append_LD_HL_nn( IOCTB_RX_RMN_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tCALL\tKCNET_READ6\n" ); // Restlaenge lesen - } - // naechstes Byte vom W5100-Lesepuffer holen - buf.append( "KCNET_READ5:\n" ); - buf.append_LD_HL_nn( IOCTB_RX_POS_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tPUSH\tHL\n" - + "\tLD\tC,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\tA,(HL)\n" - + "\tAND\t07H\n" // Maske fuer 2K Puffergroesse - + "\tLD\tH,A\n" - + "\tLD\tL,C\n" // HL: logischer Leseposition - + "\tPUSH\tHL\n" - + "\tLD\tA,(KCNET_M_SOCKET)\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tOR\t0E0H\n" // 8000h | 6000h - + "\tOR\tH\n" // Leseadresse - + "\tLD\tH,A\n" - + "\tCALL\tKCNET_GET_BYTE\n" - + "\tLD\tB,A\n" - + "\tPOP\tHL\n" - // logische Leseposition inkrementieren und in IOCTB_RPTR_OFFS merken - + "\tINC\tHL\n" - + "\tLD\tC,L\n" - + "\tLD\tA,H\n" - + "\tAND\t07H\n" - + "\tPOP\tHL\n" - + "\tLD\t(HL),C\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),A\n" - // Anzahl der Bytes verringern - + "\tDEC\tHL\n" - + "\tDEC\tHL\n" - + "\tDEC\tHL\n" - + "\tLD\tA,(HL)\n" - + "\tSUB\t01H\n" - + "\tLD\t(HL),A\n" - + "\tLD\tC,A\n" - + "\tINC\tHL\n" - + "\tLD\tA,(HL)\n" - + "\tSBC\tA,00H\n" - + "\tLD\t(HL),A\n" - + "\tOR\tC\n" - + "\tLD\tA,B\n" - + "\tRET\tNZ\n" - // letztes Byte gelesen - + "\tPUSH\tAF\n" - // Leseoperation abschliessen: (Sn_RX_RD) += (Sn_RX_RSR) - + "\tLD\tL,26H\n" // Sn_RX_RSR - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tPUSH\tHL\n" - + "\tLD\tL,28H\n" // Sn_RX_RD - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tPOP\tBC\n" - + "\tADD\tHL,BC\n" - + "\tPUSH\tHL\n" - + "\tLD\tA,H\n" - + "\tAND\t07H\n" - + "\tLD\tB,A\n" - + "\tLD\tC,28H\n" // Sn_RX_RD - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - + "\tPOP\tBC\n" - + "\tLD\tB,C\n" - + "\tLD\tC,29H\n" // Sn_RX_RD+1 - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - // Empfang freigeben - + "\tLD\tBC,4001H\n" // (Sn_CR)=RECV - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - // gelesenes Byte zurueckgeben - + "\tPOP\tAF\n" - + "\tRET\n" - // Lesen eines Wortes und nach (HL) schreiben - + "KCNET_READ6:\n" - + "\tPUSH\tHL\n" - + "\tCALL\tKCNET_READ5\n" // High(Portnummer) - + "\tPUSH\tAF\n" - + "\tCALL\tKCNET_READ5\n" // Low(Portnummer) - + "\tPOP\tBC\n" - + "\tPOP\tHL\n" - + "\tLD\t(HL),A\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),B\n" - + "\tRET\n" - /* - * WRITE-Routine - * Parameter: - * (M_IOCA): Anfangsadresse Kanalzeigerfeld - * A: zu schreibendes Byte - */ - + "KCNET_WRITE:\n" - + "\tLD\t(KCNET_M_BBUF),A\n" - + "\tLD\tDE,(M_IOCA)\n" ); - buf.append_LD_HL_nn( IOCTB_SOCKET_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tLD\t(KCNET_M_SOCKET),A\n" - + "KCNET_WRITE1:\n" - + "\tCALL\tKCNET_SOCK_CHECK_STATUS\n" - + "\tRET\tC\n" ); - if( libItems.contains( BasicLibrary.LibItem.KCNET_TCP ) ) { - buf.append( "\tEX\tAF,AF\'\n" ); // Status merken - } - // noch Platz im Puffer? - buf.append_LD_HL_nn( IOCTB_TX_FSR_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tC,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\tB,(HL)\n" - + "\tINC\tHL\n" // HL: IOCTB_TX_WR_OFFS - + "\tLD\tA,B\n" - + "\tOR\tC\n" - + "\tJR\tNZ,KCNET_WRITE4\n" - // Groesse des Schreibpuffers pruefen - + "\tPUSH\tHL\n" - + "\tLD\tL,20H\n" // Sn_TX_FSR - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tLD\tB,H\n" - + "\tLD\tC,L\n" - + "\tPOP\tHL\n" - + "\tLD\tA,B\n" - + "\tOR\tC\n" - + "\tJR\tNZ,KCNET_WRITE3\n" ); - /* - * Schreibpuffer zu klein, - * Bei TCP wird gewartet, bis wieder Platz ist - * (vorheriges SEND noch nicht abgeschlossen, - * duerft aber nicht vorkommen). - * Bei UDP fuehrt das zu einem Fehler. - */ - if( libItems.contains( BasicLibrary.LibItem.KCNET_TCP ) ) { - buf.append( "\tEX\tAF,AF\'\n" - + "\tCP\t17H\n" // SOCK_ESTABLISHED - + "\tJR\tZ,KCNET_WRITE2\n" ); - } - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_MTU_EXCEEDED, - "MTU exceeded", - "MTU ueberschritten" ); - buf.append( "\tRET\n" ); - if( libItems.contains( BasicLibrary.LibItem.KCNET_TCP ) ) { - buf.append( "KCNET_WRITE2:\n" - + "\tEX\tAF,AF\'\n" ); - if( compiler.getBasicOptions().canBreakAlways() ) { - buf.append( "\tCALL\tXCKBRK\n" ); - libItems.add( BasicLibrary.LibItem.XCKBRK ); - } - buf.append( "\tJR\tKCNET_WRITE1\n" ); - } - buf.append( "KCNET_WRITE3:\n" - // Groesse Schreibpuffer nach IOCTB_TX_FSR_OFFS schreiben - + "\tDEC\tHL\n" - + "\tLD\t(HL),B\n" - + "\tDEC\tHL\n" - + "\tLD\t(HL),C\n" - + "\tINC\tHL\n" - + "\tINC\tHL\n" // HL: IOCTB_TX_WR_OFFS - // logische Schreibposition holen - + "\tPUSH\tHL\n" - + "\tLD\tL,24H\n" // Sn_TX_WR - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tLD\tB,H\n" - + "\tLD\tC,L\n" - + "\tPOP\tHL\n" - + "\tLD\t(HL),C\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),B\n" - + "\tDEC\tHL\n" - + "KCNET_WRITE4:\n" // HL: IOCTB_TX_WR_OFFS - // Schreibadresse berechnen - + "\tPUSH\tHL\n" - + "\tLD\tC,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\tA,(HL)\n" - + "\tAND\t07H\n" // Maske fuer 2K Puffergroesse - + "\tLD\tH,A\n" - + "\tLD\tL,C\n" - + "\tPUSH\tHL\n" // logische Schreibposition - + "\tLD\tA,(KCNET_M_SOCKET)\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tOR\t0C0H\n" // 8000h | 4000h - + "\tOR\tH\n" - + "\tLD\tH,A\n" // HL: Schreibadresse - // Byte in den W5100-Adressraum schreiben - + "\tLD\tA,(KCNET_M_BBUF)\n" - + "\tLD\tB,A\n" - + "\tCALL\tKCNET_SET_BYTE\n" - // logische Schreibposition inkrementieren - + "\tPOP\tBC\n" - + "\tINC\tBC\n" - + "\tLD\tA,B\n" - + "\tAND\t07H\n" - // neue Schreibposition zurueckschreiben - + "\tPOP\tHL\n" // HL: IOCTB_TX_WR_OFFS - + "\tLD\t(HL),C\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),A\n" - // Anzahl freie Bytes dekrementieren - + "\tDEC\tHL\n" - + "\tDEC\tHL\n" - + "\tLD\tB,(HL)\n" - + "\tDEC\tHL\n" // HL: IOCTB_FSR_OFFS - + "\tLD\tC,(HL)\n" - + "\tDEC\tBC\n" - + "\tLD\t(HL),C\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),B\n" ); - if( !libItems.contains( BasicLibrary.LibItem.KCNET_TCP ) ) { - buf.append( "\tRET\n" ); - } else { - // Puffer voll? - buf.append( "\tLD\tA,B\n" - + "\tOR\tC\n" - + "\tRET\tNZ\n" - // Puffer voll, bei TCP Senden - + "\tEX\tAF,AF\'\n" - + "\tCP\t17H\n" // SOCK_ESTABLISHED - + "\tRET\tNZ\n" - + "\tJR\tKCNET_FLUSH2\n" - /* - * FLUSH-Routine - * Parameter: - * DE: Anfangsadresse Kanalzeigerfeld - */ - + "KCNET_FLUSH:\n" ); - buf.append_LD_HL_nn( IOCTB_SOCKET_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tA,(HL)\n" - + "\tLD\t(KCNET_M_SOCKET),A\n" - + "\tCALL\tKCNET_SOCK_CHECK_STATUS\n" - + "\tRET\tC\n" - + "\tCP\t17H\n" // SOCK_ESTABLISHED - + "\tRET\tNZ\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_FLUSH1 ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_FLUSH1 ) ) { - // Pruefen, ob ueberhaupt Bytes zu senden sind - buf.append( "KCNET_FLUSH1:\n" - + "\tLD\tL,24H\n" // Sn_TX_WR - + "\tCALL\tKCNET_SOCK_GET_WORD\n" - + "\tPUSH\tHL\n" ); - buf.append_LD_HL_nn( IOCTB_TX_WR_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tC,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\tB,(HL)\n" - + "\tPOP\tHL\n" - + "\tOR\tA\n" - + "\tSBC\tHL,BC\n" - + "\tRET\tZ\n" - // eigentliches Senden, HL: IOCTB_TX_WR_OFFS - + "KCNET_FLUSH2:\n" ); - buf.append_LD_HL_nn( IOCTB_TX_WR_OFFS ); - buf.append( "\tADD\tHL,DE\n" - + "\tLD\tC,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\tB,(HL)\n" ); - // direkt weiter mit KCNET_SOCK_SEND - } - libItems.add( BasicLibrary.LibItem.KCNET_SOCK ); - libItems.add( BasicLibrary.LibItem.KCNET_BASE ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_SOCK ) ) { - /* - * Senden der Bytes im Socket - * Parameter: - * BC: TX_WR - */ - buf.append( "KCNET_SOCK_SEND:\n" - + "\tLD\tL,24H\n" // Sn_TX_WR - + "\tCALL\tKCNET_SOCK_SET_WORD\n" - + "\tLD\tBC,2001H\n" // (Sn_CR)=SEND - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - // auf Ergebnis warten - + "KCNET_SOCK_SEND1:\n" - + "\tLD\tL,02H\n" // Sn_IR - + "\tCALL\tKCNET_SOCK_GET_BYTE\n" - + "\tLD\tB,A\n" - + "\tAND\t18H\n" // SEND+TIMEOUT - + "\tJR\tNZ,KCNET_SOCK_SEND2\n" ); - if( compiler.getBasicOptions().canBreakAlways() ) { - buf.append( "\tPUSH\tDE\n" - + "\tCALL\tXCKBRK\n" - + "\tPOP\tDE\n" ); - libItems.add( BasicLibrary.LibItem.XCKBRK ); - } - buf.append( "\tCALL\tKCNET_SOCK_CHECK_STATUS\n" - + "\tRET\tC\n" - + "\tJR\tKCNET_SOCK_SEND1\n" - + "KCNET_SOCK_SEND2:\n" - + "\tEX\tAF,AF\'\n" - + "\tLD\tC,02H\n" // Sn_IR - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" // zuruecksetzen - + "\tEX\tAF,AF\'\n" - + "\tAND\t08H\n" // SEND/TIMEOUT - + "\tRET\tZ\n" ); - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_TIMEOUT, - "Senden fehlgeschlagen", - "Send failed" ); - buf.append( "\tRET\n" - /* - * Holen der Portnummer und Oeffnen eines Sockets - * Parameter: - * (KCNET_M_SOCKET): Socket-Nummer - * E: Betriebsart (1: TCP, 2: UDP) - */ - + "KCNET_SOCK_GET_PORT_AND_OPEN:\n" - + "\tLD\tA,08H\n" // Portnummer holen - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tCALL\tKCNET_RD_BYTE\n" - + "\tLD\tH,A\n" - + "\tCALL\tKCNET_RD_BYTE\n" - + "\tLD\tL,A\n" - /* - * Oeffnen eines Sockets - * Parameter: - * (KCNET_M_SOCKET): Socket-Nummer - * E: Betriebsart (1: TCP, 2: UDP) - * HL: Portnummer - */ - + "KCNET_SOCK_OPEN:\n" - + "\tPUSH\tHL\n" - + "\tCALL\tKCNET_SOCK_CLOSE\n" - + "KCNET_SOCK_OPEN1:\n" - + "\tLD\tB,E\n" - + "\tLD\tC,00\n" // Sn_MR - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - + "\tPOP\tBC\n" - + "\tPUSH\tBC\n" - + "\tLD\tL,04H\n" // Sn_PORT - + "\tCALL\tKCNET_SOCK_SET_WORD\n" - + "\tLD\tBC,0101H\n" // Sn_CR=OPEN - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - + "KCNET_SOCK_OPEN2:\n" - + "\tCALL\tKCNET_SOCK_GET_STATUS\n" - + "\tOR\tA\n" // SOCK_CLOSED? - + "\tJR\tZ,KCNET_SOCK_OPEN2\n" - + "\tCP\t13H\n" // SOCK_INIT? - + "\tJR\tZ,KCNET_SOCK_OPEN3\n" - + "\tCP\t22H\n" // SOCK_UDP? - + "\tJR\tZ,KCNET_SOCK_OPEN3\n" - + "\tLD\tBC,1001H\n" // Sn_CR=CLOSE - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - + "\tJR\tKCNET_SOCK_OPEN1\n" - + "KCNET_SOCK_OPEN3:\n" - + "\tPOP\tHL\n" - + "\tRET\n" - /* - * Socket schliessen - * Parameter: - * (M_SOCKET): Socketnummer - */ - + "KCNET_SOCK_CLOSE:\n" - + "\tCALL\tKCNET_SOCK_GET_STATUS\n" - + "\tOR\tA\n" // SOCK_CLOSED? - + "\tRET\tZ\n" - + "\tLD\tBC,0801H\n" // (Sn_CR)=DISCON - + "\tCP\t17H\n" // SOCK_ESTABLISHED? - + "\tJP\tZ,KCNET_SOCK_CLOSE1\n" - + "\tLD\tB,10H\n" // (Sn_CR)=CLOSE - + "KCNET_SOCK_CLOSE1:\n" - + "\tCALL\tKCNET_SOCK_SET_BYTE\n" - + "\tLD\tL,02H\n" // Sn_IR - + "\tCALL\tKCNET_SOCK_GET_BYTE\n" - + "\tOR\tA\n" - + "\tRET\tZ\n" - + "\tLD\tL,02H\n" // Sn_IR - + "\tJR\tKCNET_SOCK_SET_BYTE\n" // zuruecksetzen - /* - * Lesen eines 16-Bit-Wertes vom Socket - * Parameter: - * (KCNET_M_SOCKET): Socket-Nummer - * L: Offset - * Rueckgabewert: - * HL: gelesenes Wort - */ - + "KCNET_SOCK_GET_WORD:\n" - + "\tLD\tA,02H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,L\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,(KCNET_M_SOCKET)\n" - + "\tADD\tA,84H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,01H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,02H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tXOR\tA\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tCALL\tKCNET_RD_BYTE\n" - + "\tLD\tH,A\n" - + "\tCALL\tKCNET_RD_BYTE\n" - + "\tLD\tL,A\n" - + "\tRET\n" - /* - * Pruefen, ob der Socket-Status dem eines offenen IO-Kanals entspricht - * (SOCK_ESTABLISHED oder SOCK_UPD). Wenn ja, wird CY=0 zurueckgegeben. - * Bei Status CLOSED oder wenn der Socket gerade geschlossen wird - * (Verbindungsabbau durch die Gegenstelle), - * wird die Fehlervariable auf E_EOF gesetzt mit CY=1 zurueckgekehrt. - * Bei allen anderen Stati, die eigentich nicht auftreten duerften, - * wird die Fehlervariable auf E_SOCKET_STATUS gesetzt und - * mit CY=1 zurueckgekehrt. - * - * Parameter: - * (KCNET_M_SOCKET): Socket-Nummer - * Rueckgabewert: - * CY=0: OK, Socketstatus in A - * CY=1: falscher Status -> Aktion abbrechen - */ - + "KCNET_SOCK_CHECK_STATUS:\n" - + "\tCALL\tKCNET_SOCK_GET_STATUS\n" - + "\tOR\tA\n" // SOCK_CLOSED? - + "\tJR\tZ,KCNET_SOCK_CHECK_STATUS2\n" - + "\tCP\t17H\n" // SOCK_ESTABLISHED? - + "\tRET\tZ\n" - + "\tCP\t22H\n" // SOCK_UDP? - + "\tRET\tZ\n" - /* - * Stati 0x18 bis 0x1D signalisieren den Verbindungsabbau. - * 0x18: SOCK_FIN_WAIT_1 - * 0x19: SOCK_FIN_WAIT_2 - * 0x1A: SOCK_CLOSING - * 0x1B: SOCK_TIME_WAIT - * 0x1C: SOCK_CLOSE_WAIT - * 0x1D: SOCK_LAST_ACK - */ - + "\tCP\t18H\n" - + "\tJR\tC,KCNET_SOCK_CHECK_STATUS1\n" - + "\tCP\t1EH\n" - + "\tJR\tC,KCNET_SOCK_CHECK_STATUS2\n" - + "KCNET_SOCK_CHECK_STATUS1:\n" ); - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_SOCKET_STATUS, - "Ungueltiger Socket-Status", - "Invalid socket status" ); - buf.append( "\tJR\tKCNET_SOCK_CHECK_STATUS3\n" - + "KCNET_SOCK_CHECK_STATUS2:\n" ); - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_EOF, - "Socket geschlossen", - "Socket closed" ); - buf.append( "KCNET_SOCK_CHECK_STATUS3:\n" - + "\tSCF\n" - + "\tRET\n" - /* - * Lesen Socket-Status - * Parameter: - * (KCNET_M_SOCKET): Socket-Nummer - * Rueckgabewert: - * A: gelesenes Byte - */ - + "KCNET_SOCK_GET_STATUS:\n" - + "\tLD\tL,03H\n" - // direkt weiter mit KCNET_SOCK_GET_BYTE - /* - * Lesen eines Bytes vom Socket - * Parameter: - * (KCNET_M_SOCKET): Socket-Nummer - * L: Offset - * Rueckgabewert: - * A: gelesenes Byte - */ - + "KCNET_SOCK_GET_BYTE:\n" - + "\tLD\tA,(KCNET_M_SOCKET)\n" - + "\tADD\tA,84H\n" - + "\tLD\tH,A\n" - + "\tJP\tKCNET_GET_BYTE\n" - /* - * Setzen eines Wortes im Socket - * Parameter: - * (KCNET_M_SOCKET): Socket-Nummer - * BC: zu setzendes Wort - * L: Offset - */ - + "KCNET_SOCK_SET_WORD:\n" - + "\tLD\tA,(KCNET_M_SOCKET)\n" - + "\tADD\tA,84H\n" - + "\tLD\tH,A\n" - + "\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tCALL\tKCNET_SET_BYTE\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tINC\tL\n" - + "\tLD\tB,C\n" - + "\tJP\tKCNET_SET_BYTE\n" - /* - * Setzen eines Bytes im Socket - * Parameter: - * (KCNET_M_SOCKET): Socket-Nummer - * B: zu setzendes Byte - * C: Offset - */ - + "KCNET_SOCK_SET_BYTE:\n" - + "\tLD\tA,(KCNET_M_SOCKET)\n" - + "\tADD\tA,84H\n" - + "\tLD\tH,A\n" - + "\tLD\tL,C\n" - + "\tJP\tKCNET_SET_BYTE\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_BASE ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_BASE ) ) { - /* - * KCNet initialisieren - * Rueckgabewert: - * CY=0: OK - * CY=1: Fehler, Fehlervariablen gesetzt - */ - buf.append( "KCNET_INIT:\n" - + "\tCALL\tKCNET_INIT_HW\n" - + "\tRET\tC\n" - + "\tLD\tA,(KCNET_M_INIT)\n" - + "\tAND\t02H\n" - + "\tRET\tNZ\n" - // Subnetzmaske pruefen - + "\tLD\tHL,8005H\n" - + "\tCALL\tKCNET_CHECK_IPADDR\n" - + "\tRET\tC\n" - // IP-Adresse pruefen - + "\tLD\tHL,800FH\n" - + "\tCALL\tKCNET_CHECK_IPADDR\n" - + "\tRET\tC\n" - // W5100 initialisieren - + "\tLD\tHL,KCNET_DATA_MODULE_INIT\n" - + "\tLD\tB,10H\n" - + "KCNET_INIT1:\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tDJNZ\tKCNET_INIT1\n" - // Initialisierung abgeschlossen - + "\tLD\tA,03H\n" // HW+CFG - + "\tLD\t(KCNET_M_INIT),A\n" - + "\tOR\tA\n" - + "\tRET\n" - /* - * Vorhandensein einer IP-Adresse pruefen, - * Parameter: - * HL: Adresse im KCNet-Modul - * Rueckgabewert: - * CY=0: OK - * CY=1: IP-Adresse nicht vorhanden - */ - + "KCNET_CHECK_IPADDR:\n" - + "\tLD\tA,02H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,L\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,01H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,04H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tXOR\tA\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tCALL\tKCNET_RD_BYTE\n" - + "\tLD\tC,A\n" - + "\tLD\tB,03H\n" - + "KCNET_CHECK_IPADDR1:\n" - + "\tCALL\tKCNET_RD_BYTE\n" - + "\tOR\tC\n" - + "\tLD\tC,A\n" - + "\tDJNZ\tKCNET_CHECK_IPADDR1\n" - + "\tRET\tNZ\n" ); - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_DEVICE_NOT_CONFIGURED, - "KCNet nicht konfiguriert", - "KCNet not configured" ); - buf.append( "\tSCF\n" - + "\tRET\n" ); - libItems.add( BasicLibrary.LibItem.KCNET_BASE_HW ); - } - if( libItems.contains( BasicLibrary.LibItem.KCNET_BASE_HW ) ) { - /* - * KCNet-Hardware initialisieren - * Rueckgabewert: - * CY=0: OK - * CY=1: Fehler, Fehlervariablen gesetzt - */ - AbstractTarget target = compiler.getTarget(); - int baseIOAddr = target.getKCNetBaseIOAddr(); - buf.append( "KCNET_INIT_HW:\n" - + "\tLD\tA,(KCNET_M_INIT)\n" - + "\tAND\t01H\n" - + "\tRET\tNZ\n" ); - if( target.needsEnableKCNet() ) { - target.appendEnableKCNet( buf ); - buf.append( "\tJR\tC,KCNET_INIT_ERR\n" ); - } - buf.append( "\tLD\tHL,KCNET_DATA_PIO_INIT\n" ); - buf.append_LD_BC_nn( (2 << 8) | (baseIOAddr + 2) ); - buf.append( "\tOTIR\n" ); - buf.append_LD_BC_nn( (4 << 8) | (baseIOAddr + 3) ); - buf.append( "\tOTIR\n" - + "\tIN\tA,(" ); - buf.appendHex2( baseIOAddr ); - buf.append( ")\n" - + "\tIN\tA,(" ); - buf.appendHex2( baseIOAddr + 1 ); - buf.append( ")\n" - + "\tRLCA\n" - + "\tJR\tC,KCNET_INIT_ERR\n" - // Hardware-Version pruefen - + "\tLD\tA,0AH\n" - + "\tCALL\tKCNET_CHECK_VERSION\n" - + "\tRET\tC\n" - // Software-Version pruefen - + "\tLD\tA,09H\n" - + "\tCALL\tKCNET_CHECK_VERSION\n" - + "\tRET\tC\n" - + "\tLD\tA,01H\n" - + "\tLD\t(KCNET_M_INIT),A\n" - + "\tRET\n" - /* - * KCNet-Version pruefen - * Parameter: - * A: KCNet-Kommando (9: Software-Version, 10: Hardware-Version) - * Rueckgabewert: - * CY=0: OK - * CY=1: Version zu alt - */ - + "KCNET_CHECK_VERSION:\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tCALL\tKCNET_RD_BYTE\n" - + "\tLD\tL,A\n" - + "\tCALL\tKCNET_RD_BYTE\n" - + "\tLD\tH,A\n" - + "\tLD\tBC,0102H\n" - + "\tOR\tA\n" - + "\tSBC\tHL,BC\n" - + "\tRET\tNC\n" - // Fehler - + "KCNET_INIT_ERR:\n" ); - BasicLibrary.appendSetError( - compiler, - BasicLibrary.E_DEVICE_NOT_FOUND, - "KCNet nicht gefunden", - "KCNet not found" ); - buf.append( "\tSCF\n" - + "\tRET\n" - /* - * Setzen eines Bytes im KCNet-Adressraum - * Parameter: - * HL: Adresse - * B: zu setzendes Byte - */ - + "KCNET_SET_BYTE:\n" - + "\tLD\tA,04H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,L\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,B\n" - // direkt weiter mit KCNET_WR_BYTE - /* - * Schreiben eines Bytes in das KCNet-Modul: - * Parameter: - * A: zu schreibendes Byte - */ - + "KCNET_WR_BYTE:\n" - + "\tOUT\t(" ); - buf.appendHex2( baseIOAddr ); - buf.append( "),A\n" - + "KCNET_WR_BYTE1:\n" - + "\tIN\tA,(" ); - buf.appendHex2( baseIOAddr + 1 ); - buf.append( ")\n" - + "\tRRCA\n" - + "\tJR\tC,KCNET_WR_BYTE1\n" - + "\tRET\n" - /* - * Lesen eines Bytes vom KCNet-Adressraum - * Parameter: - * HL: Adresse - * Rueckgabewert: - * A: gelesenes Byte - */ - + "KCNET_GET_BYTE:\n" - + "\tLD\tA,05H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,L\n" - + "\tCALL\tKCNET_WR_BYTE\n" - + "\tLD\tA,H\n" - + "\tCALL\tKCNET_WR_BYTE\n" - // direkt weiter mit KCNET_RD_BYTE - /* - * Lesen eines Bytes vom KCNet-Modul: - * Rueckgabewert: - * A: gelesenes Byte - */ - + "KCNET_RD_BYTE:\n" - + "\tIN\tA,(" ); - buf.appendHex2( baseIOAddr + 1 ); - buf.append( ")\n" - + "\tRLCA\n" - + "\tJR\tNC,KCNET_RD_BYTE\n" - + "\tIN\tA,(" ); - buf.appendHex2( baseIOAddr ); - buf.append( ")\n" - + "\tRET\n" ); - } - } - - - public static void appendDataTo( BasicCompiler compiler ) - { - AsmCodeBuf buf = compiler.getCodeBuf(); - if( compiler.usesLibItem( BasicLibrary.LibItem.KCNET_HOSTBYNAME ) ) { - buf.append( "KCNET_DATA_DNSQ:\n" - + "\tDB\t01H,00H,00H,01H,00H,00H,00H,00H,00H,00H\n" ); - } - if( compiler.usesLibItem( BasicLibrary.LibItem.KCNET_TCP ) - || compiler.usesLibItem( BasicLibrary.LibItem.KCNET_UDP ) ) - { - buf.append( "KCNET_DATA_IOPTRS:\n" - + "\tDW\tKCNET_CLOSE\n" - + "\tDW\tKCNET_EOF\n" - + "\tDW\tKCNET_AVAILABLE\n" - + "\tDW\tKCNET_READ\n" - + "\tDW\tKCNET_WRITE\n" ); - if( compiler.usesLibItem( BasicLibrary.LibItem.KCNET_TCP ) ) { - buf.append( "\tDW\tKCNET_FLUSH\n" ); - } - } - if( compiler.usesLibItem( BasicLibrary.LibItem.KCNET_BASE ) ) { - buf.append( "KCNET_DATA_MODULE_INIT:\n" - + "\tDB\t04H,00H,80H,00H\n" // MR=0 - + "\tDB\t04H,16H,80H,00H\n" // IMR=0 - + "\tDB\t04H,1AH,80H,55H\n" // RMSR=55h - + "\tDB\t04H,1BH,80H,55H\n" ); // TMSR=55h - } - if( compiler.usesLibItem( BasicLibrary.LibItem.KCNET_BASE_HW ) ) { - buf.append( "KCNET_DATA_PIO_INIT:\n" - + "\tDB\t8FH,07H,0CFH,0FFH,17H,0FFH\n" ); - } - } - - - public static void appendBssTo( BasicCompiler compiler ) - { - if( compiler.usesLibItem( BasicLibrary.LibItem.KCNET_HOSTBYNAME ) ) { - compiler.getCodeBuf().append( "KCNET_M_DNS_ID:\n" - + "\tDS\t2\n" - + "KCNET_M_DNS_QDLEN:\n" - + "\tDS\t2\n" - + "KCNET_M_DNS_TX_WR:\n" - + "\tDS\t2\n" - + "KCNET_M_DNS_RX_RMN:\n" - + "\tDS\t2\n" - + "KCNET_M_DNS_RX_RD:\n" - + "\tDS\t2\n" ); - } - if( compiler.usesLibItem( BasicLibrary.LibItem.KCNET_PARSE_IPADDR ) ) { - compiler.getCodeBuf().append( "KCNET_M_HOST:\n" - + "\tDS\t2\n" ); - } - if( compiler.usesLibItem( BasicLibrary.LibItem.KCNET_TCP ) - || compiler.usesLibItem( BasicLibrary.LibItem.KCNET_UDP ) ) - { - compiler.getCodeBuf().append( "KCNET_M_BBUF:\n" - + "\tDS\t1\n" ); - } - if( compiler.usesLibItem( BasicLibrary.LibItem.KCNET_SOCK ) ) { - compiler.getCodeBuf().append( "KCNET_M_SOCKET:\n" - + "\tDS\t1\n" ); - } - if( compiler.usesLibItem( BasicLibrary.LibItem.KCNET_BASE_HW ) ) { - compiler.getCodeBuf().append( "KCNET_M_INIT:\n" - + "\tDS\t1\n" // Bit 0: HW, Bit 1: CFG - + "KCNET_M_IPADDR:\n" - + "\tDS\t4\n" ); - } - } - - - public static void appendInitTo( BasicCompiler compiler ) - { - if( compiler.usesLibItem( BasicLibrary.LibItem.KCNET_BASE_HW ) ) { - compiler.getCodeBuf().append( "\tXOR\tA\n" - + "\tLD\t(KCNET_M_INIT),A\n" ); - } - } -} - diff --git a/src/jkcemu/programming/basic/LoopEntry.java b/src/jkcemu/programming/basic/LoopEntry.java index c88bfc3..6af9134 100644 --- a/src/jkcemu/programming/basic/LoopEntry.java +++ b/src/jkcemu/programming/basic/LoopEntry.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -9,21 +9,22 @@ package jkcemu.programming.basic; import java.lang.*; +import jkcemu.programming.PrgSource; -public abstract class LoopEntry extends StructureEntry +public abstract class LoopEntry extends BasicSourcePos { private String loopLabel; private String exitLabel; protected LoopEntry( - int sourceLineNum, - long basicLineNum, - String loopLabel, - String exitLabel ) + PrgSource source, + long basicLineNum, + String loopLabel, + String exitLabel ) { - super( sourceLineNum, basicLineNum ); + super( source, basicLineNum ); this.loopLabel = loopLabel; this.exitLabel = exitLabel; } diff --git a/src/jkcemu/programming/basic/StructureEntry.java b/src/jkcemu/programming/basic/StructureEntry.java deleted file mode 100644 index 28de598..0000000 --- a/src/jkcemu/programming/basic/StructureEntry.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * (c) 2012 Jens Mueller - * - * Kleincomputer-Emulator - * - * Abstrakte Klasse fuer den Stack-Eintrag einer Programmcodestruktur - */ - -package jkcemu.programming.basic; - -import java.lang.*; - - -public abstract class StructureEntry -{ - private long basicLineNum; - private int sourceLineNum; - - - protected StructureEntry( int sourceLineNum, long basicLineNum ) - { - this.sourceLineNum = sourceLineNum; - this.basicLineNum = basicLineNum; - } - - - public long getBasicLineNum() - { - return this.basicLineNum; - } - - - public int getSourceLineNum() - { - return this.sourceLineNum; - } -} - diff --git a/src/jkcemu/programming/basic/SubEntry.java b/src/jkcemu/programming/basic/SubEntry.java index 6055db2..5177cc1 100644 --- a/src/jkcemu/programming/basic/SubEntry.java +++ b/src/jkcemu/programming/basic/SubEntry.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -9,16 +9,17 @@ package jkcemu.programming.basic; import java.lang.*; +import jkcemu.programming.PrgSource; public class SubEntry extends CallableEntry { public SubEntry( - int sourceLineNum, - long basicLineNum, - String name ) + PrgSource source, + long basicLineNum, + String name ) { - super( sourceLineNum, basicLineNum, name, "UP_" + name ); + super( source, basicLineNum, name, "UP_" + name ); } @@ -30,3 +31,4 @@ public String toString() return "Prozedur " + getName(); } } + diff --git a/src/jkcemu/programming/basic/VarDecl.java b/src/jkcemu/programming/basic/VarDecl.java index e1b5a3c..f232bec 100644 --- a/src/jkcemu/programming/basic/VarDecl.java +++ b/src/jkcemu/programming/basic/VarDecl.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -10,12 +10,11 @@ import java.lang.*; import java.util.Map; +import jkcemu.programming.PrgSource; -public class VarDecl +public class VarDecl extends BasicSourcePos { - private int sourceLineNum; - private long basicLineNum; private String varName; private int dim1; private int dim2; @@ -27,20 +26,19 @@ public class VarDecl public VarDecl( - int sourceLineNum, - long basicLineNum, - String varName, - int dim1, - int dim2 ) + PrgSource source, + long basicLineNum, + String varName, + int dim1, + int dim2 ) { - this.sourceLineNum = sourceLineNum; - this.basicLineNum = basicLineNum; - this.varName = varName; - this.dim1 = dim1; - this.dim2 = dim2; - this.nDims = 0; - this.size = 2; - this.used = false; + super( source, basicLineNum ); + this.varName = varName; + this.dim1 = dim1; + this.dim2 = dim2; + this.nDims = 0; + this.size = 2; + this.used = false; if( dim1 > 0 ) { this.nDims++; this.size *= (dim1 + 1); @@ -64,27 +62,21 @@ public VarDecl( public VarDecl( - int sourceLineNum, - long basicLineNum, - String varName, - int dim1 ) + PrgSource source, + long basicLineNum, + String varName, + int dim1 ) { - this( sourceLineNum, basicLineNum, varName, dim1, 0 ); + this( source, basicLineNum, varName, dim1, 0 ); } public VarDecl( - int sourceLineNum, - long basicLineNum, - String varName ) + PrgSource source, + long basicLineNum, + String varName ) { - this( sourceLineNum, basicLineNum, varName, 0, 0 ); - } - - - public long getBasicLineNum() - { - return this.basicLineNum; + this( source, basicLineNum, varName, 0, 0 ); } @@ -118,12 +110,6 @@ public int getSize() } - public int getSourceLineNum() - { - return this.sourceLineNum; - } - - public boolean isUsed() { return this.used; diff --git a/src/jkcemu/programming/basic/VdipLibrary.java b/src/jkcemu/programming/basic/VdipLibrary.java index a145720..5eaec4d 100644 --- a/src/jkcemu/programming/basic/VdipLibrary.java +++ b/src/jkcemu/programming/basic/VdipLibrary.java @@ -1,5 +1,5 @@ /* - * (c) 2013 Jens Mueller + * (c) 2013-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -16,250 +16,280 @@ public class VdipLibrary { public static void appendCodeTo( BasicCompiler compiler ) { + AbstractTarget target = compiler.getTarget(); AsmCodeBuf buf = compiler.getCodeBuf(); Set libItems = compiler.getLibItems(); - AbstractTarget target = compiler.getTarget(); /* * VDIP-Handler testen * * Parameter: - * (M_IONM): Zeiger auf Geraete-/Dateiname - * (M_IOCA): Anfangsadresse des Kanalzeigerfeldes - * (M_IOAC): Zugriffsmode + * (IO_M_NAME): Zeiger auf Geraete-/Dateiname + * (IO_M_CADDR): Anfangsadresse des Kanalzeigerfeldes + * (IO_M_ACCESS): Zugriffsmode * Rueckgabewert: - * CY=1: Handler ist nicht zustaendig - * CY=0: Handler ist zustaendig - * -> keine weiteren Handler testen + * CY=1: Handler ist nicht zustaendig + * CY=0: Handler ist zustaendig + * -> keine weiteren Handler testen */ buf.append( "IO_VDIP_HANDLER:\n" ); - boolean done = false; - int[] baseIOAddrs = target.getVdipBaseIOAddresses(); - if( baseIOAddrs != null ) { - if( baseIOAddrs.length > 0 ) { - int baseIOAddr = -1; + boolean done = false; + int[] baseIOAddrs = target.getVdipBaseIOAddresses(); + Set modes = compiler.getIODriverModes( + BasicCompiler.IODriver.VDIP ); + if( (baseIOAddrs != null) && (modes != null) ) { + if( (baseIOAddrs.length > 0) && !modes.isEmpty() ) { + boolean needsVDIP_RD_FSIZE = false; + int baseIOAddr = -1; if( baseIOAddrs.length == 1 ) { baseIOAddr = baseIOAddrs[ 0 ]; } else { compiler.getLibItems().add( BasicLibrary.LibItem.VDIP_M_IOADDR ); } + boolean txtAppend = modes.contains( BasicLibrary.IOMODE_TXT_APPEND ); + boolean txtOutput = modes.contains( BasicLibrary.IOMODE_TXT_OUTPUT ); + boolean txtInput = (modes.contains( BasicLibrary.IOMODE_TXT_INPUT ) + || modes.contains( BasicLibrary.IOMODE_TXT_DEFAULT )); + boolean binAppend = modes.contains( BasicLibrary.IOMODE_BIN_APPEND ); + boolean binOutput = modes.contains( BasicLibrary.IOMODE_BIN_OUTPUT ); + boolean binInput = (modes.contains( BasicLibrary.IOMODE_BIN_INPUT ) + || modes.contains( BasicLibrary.IOMODE_BIN_DEFAULT )); + // Geraetename am Dateianfang testen - buf.append( "\tLD\tDE,(M_IONM)\n" - + "\tLD\tA,(DE)\n" - + "\tCALL\tC_UPR\n" - + "\tCP\t\'V\'\n" - + "\tJR\tNZ,VDIP_HANDLER1\n" - + "\tINC\tDE\n" - + "\tLD\tA,(DE)\n" - + "\tINC\tDE\n" - + "\tCP\t3AH\n" - + "\tJR\tZ,VDIP_HANDLER4\n" - + "\tDEC\tDE\n" - + "\tDEC\tDE\n" + buf.append( "\tLD\tDE,(IO_M_NAME)\n" + + "\tLD\tA,(DE)\n" + + "\tCALL\tC_UPR\n" + + "\tCP\t56H\n" + + "\tJR\tNZ,VDIP_HANDLER2\n" + + "\tINC\tDE\n" + + "\tLD\tA,(DE)\n" + + "\tINC\tDE\n" + + "\tCP\t3AH\n" + + "\tJR\tZ,VDIP_HANDLER4\n" + + "\tDEC\tDE\n" + + "\tDEC\tDE\n" /* * Am Anfang des Dateinamens steht nicht "V:". * Pruefen, ob ein anderer Geraetename dort steht. */ - + "VDIP_HANDLER1:\n" - + "\tDEC\tDE\n" - + "VDIP_HANDLER2:\n" - + "\tINC\tDE\n" - + "\tLD\tA,(DE)\n" - + "\tCP\t3AH\n" - + "\tJR\tNZ,VDIP_HANDLER3\n" - + "\tSCF\n" // anderes Geraet - + "\tRET\n" - + "VDIP_HANDLER3:\n" - + "\tOR\tA\n" - + "\tJR\tNZ,VDIP_HANDLER2\n" - + "\tLD\tDE,(M_IONM)\n" + + "\tDEC\tDE\n" + + "VDIP_HANDLER1:\n" + + "\tINC\tDE\n" + + "\tLD\tA,(DE)\n" + + "VDIP_HANDLER2:\n" + + "\tCP\t3AH\n" + + "\tJR\tNZ,VDIP_HANDLER3\n" + + "\tSCF\n" // anderes Geraet + + "\tRET\n" + + "VDIP_HANDLER3:\n" + + "\tOR\tA\n" + + "\tJR\tNZ,VDIP_HANDLER1\n" + + "\tLD\tDE,(IO_M_NAME)\n" // Handler zustaendig, DE zeigt auf den Pfad-/Dateinamen - + "VDIP_HANDLER4:\n" - + "\tLD\t(VDIP_M_FNAME),DE\n" + + "VDIP_HANDLER4:\n" + + "\tLD\t(VDIP_M_FNAME),DE\n" // Geraet bereits in Benutzung? - + "\tLD\tA,(VDIP_M_LOCKED)\n" - + "\tOR\tA\n" - + "\tJR\tZ,VDIP_HANDLER5\n" ); + + "\tLD\tA,(VDIP_M_LOCKED)\n" + + "\tOR\tA\n" + + "\tJR\tZ,VDIP_HANDLER5\n" ); BasicLibrary.appendSetErrorDeviceLocked( compiler ); - buf.append( "\tOR\tA\n" // RET mit CY=0! - + "\tRET\n" + buf.append( "\tOR\tA\n" // RET mit CY=0! + + "\tRET\n" // VDIP initialisieren - + "VDIP_HANDLER5:\n" ); + + "VDIP_HANDLER5:\n" ); if( baseIOAddr >= 0 ) { buf.append( "\tCALL\tVDIP_INIT\n" - + "\tCCF\n" - + "\tRET\tNC\n" ); // RET mit CY=0! + + "\tCCF\n" + + "\tRET\tNC\n" ); // RET mit CY=0! } else { for( int i = 0; i < (baseIOAddrs.length - 1); i++ ) { buf.append_LD_A_n( baseIOAddrs[ i ] ); buf.append( "\tLD\t(VDIP_M_IOADDR),A\n" - + "\tCALL\tVDIP_INIT\n" - + "\tJR\tNC,VDIP_HANDLER6\n" ); + + "\tCALL\tVDIP_INIT\n" + + "\tJR\tNC,VDIP_HANDLER6\n" ); } buf.append_LD_A_n( baseIOAddrs[ baseIOAddrs.length - 1 ] ); buf.append( "\tLD\t(VDIP_M_IOADDR),A\n" - + "\tCALL\tVDIP_INIT\n" - + "\tCCF\n" - + "\tRET\tNC\n" // RET mit CY=0! - + "VDIP_HANDLER6:\n" ); + + "\tCALL\tVDIP_INIT\n" + + "\tCCF\n" + + "\tRET\tNC\n" // RET mit CY=0! + + "VDIP_HANDLER6:\n" ); } // Speichermedium vorhanden? buf.append( "\tCALL\tVDIP_CHECK_DISK\n" - + "\tCCF\n" - + "\tRET\tNC\n" // RET mit CY=0! -// TODO: Pfad auswerten und aktuelles Verzeichnis einstellen + + "\tCCF\n" + + "\tRET\tNC\n" // RET mit CY=0! + // ggf. Pfad auswerten und Verzeichnis einstellen + + "\tCALL\tVDIP_SET_DIR\n" + + "\tCCF\n" + + "\tRET\tNC\n" // RET mit CY=0! + + "\tLD\tHL,IO_M_ACCESS\n" ); // beim Lesen und Anhaengen muss die Dateilaenge bekannt sein - + "\tLD\tA,(M_IOAC)\n" - + "\tCP\t" ); - buf.appendHex2( BasicLibrary.IOMODE_OUTPUT ); - buf.append( "\n" - + "\tJR\tZ,VDIP_HANDLER7\n" - + "\tCALL\tVDIP_RD_FSIZE\n" - + "\tCCF\n" - + "\tRET\tNC\n" // RET mit CY=0! + if( txtInput || txtAppend || binInput || binAppend ) { + buf.append_LD_A_n( BasicLibrary.IOMODE_DEFAULT_MASK + | BasicLibrary.IOMODE_INPUT_MASK + | BasicLibrary.IOMODE_APPEND_MASK ); + buf.append( "\tAND\t(HL)\n" + + "\tJR\tZ,VDIP_HANDLER7\n" + + "\tPUSH\tHL\n" + + "\tCALL\tVDIP_RD_FSIZE\n" + + "\tPOP\tHL\n" + + "\tCCF\n" + + "\tRET\tNC\n" ); // RET mit CY=0! + needsVDIP_RD_FSIZE = true; + } + // Datei oeffnen - + "VDIP_HANDLER7:\n" - + "\tLD\tA,(M_IOAC)\n" - + "\tCP\t" ); - buf.appendHex2( BasicLibrary.IOMODE_OUTPUT ); - buf.append( "\n" - + "\tJR\tNC,VDIP_HANDLER10\n" // Ausgabe - // Datei im VDIP oeffnen - + "\tLD\tA,0EH\n" - + "\tCALL\tVDIP_WR_FNAME_CMD\n" ); - buf.append( "\tCALL\tVDIP_CHECK_RESULT\n" - + "\tJR\tNC,VDIP_HANDLER8\n" - + "\tLD\tHL,(M_ERN)\n" ); - buf.append_LD_DE_nn( BasicLibrary.E_ERROR ); - buf.append( "\tOR\tA\n" - + "\tSBC\tHL,DE\n" - + "\tSCF\n" - + "\tCCF\n" // CY=0, Z beibehalten - + "\tRET\tNZ\n" ); - BasicLibrary.appendSetErrorFileNotFound( compiler ); - buf.append( "\tRET\n" - // Datei zum Lesen geoeffnet - + "VDIP_HANDLER8:\n" - + "\tCALL\tVDIP_LOCK_INIT_BUF\n" - // Kanalzeigerfeld fuellen - + "\tLD\tDE,(M_IOCA)\n" - + "\tLD\tHL,VDIP_DATA_RDPTRS\n" - + "\tLD\tBC,0008H\n" - + "\tLDIR\n" - + "\tXOR\tA\n" - + "\tLD\tB," ); - buf.appendHex2( compiler.getIOChannelSize() - - BasicLibrary.IOCTB_READ_OFFS - - 2 ); - buf.append( "\n" - + "VDIP_HANDLER9:\n" - + "\tLD\t(DE),A\n" - + "\tINC\tDE\n" - + "\tDJNZ\tVDIP_HANDLER9\n" - + "\tOR\tA\n" - + "\tRET\n" - // Datei zum Schreiben oeffnen - + "VDIP_HANDLER10:\n" - + "\tJR\tZ,VDIP_HANDLER11\n" - + "\tCP\t" ); - buf.appendHex2( BasicLibrary.IOMODE_APPEND ); - buf.append( "\n" - + "\tJR\tZ,VDIP_HANDLER11\n" ); + buf.append( "VDIP_HANDLER7:\n" ); + if( txtInput || binInput ) { + buf.append_LD_A_n( BasicLibrary.IOMODE_DEFAULT_MASK + | BasicLibrary.IOMODE_INPUT_MASK ); + buf.append( "\tAND\t(HL)\n" + + "\tJR\tNZ,VDIP_HANDLER8\n" ); + } + if( txtOutput || txtAppend || binOutput || binAppend ) { + buf.append_LD_A_n( BasicLibrary.IOMODE_OUTPUT_MASK + | BasicLibrary.IOMODE_APPEND_MASK ); + buf.append( "\tAND\t(HL)\n" + + "\tJR\tNZ,VDIP_HANDLER11\n" ); + } BasicLibrary.appendSetErrorIOMode( compiler ); - buf.append( "\tOR\tA\n" // RET mit CY=0! - + "\tRET\n" - + "VDIP_HANDLER11:\n" - // Datei im VDIP oeffnen - + "\tLD\tA,09H\n" - + "\tCALL\tVDIP_WR_FNAME_CMD\n" - + "\tCALL\tVDIP_CHECK_RESULT\n" - + "\tCCF\n" - + "\tRET\tNC\n" // RET mit CY=0! - + "\tLD\tA,(M_IOAC)\n" - + "\tCP\t" ); - buf.appendHex2( BasicLibrary.IOMODE_APPEND ); - buf.append( "\n" - + "\tJR\tZ,VDIP_HANDLER12\n" - /* - * Betriebsart OUTPUT - * Dateizeiger auf den Dateianfang setzen - */ - + "\tCALL\tVDIP_WR_CMD\n" - + "\tDB\t28H,20H,00H,00H,00H,00H,0DH\n" - + "\tJR\tVDIP_HANDLER14\n" - /* - * Betriebsart APPEND - * Dateizeiger auf das Dateiende setzen, - * hoechstwertiges Byte zuerst!!! - */ - + "VDIP_HANDLER12:\n" - + "\tLD\tA,28H\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tLD\tA,20H\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tLD\tHL,VDIP_M_FBYTES+4\n" - + "\tLD\tB,04H\n" - + "VDIP_HANDLER13:\n" - + "\tDEC\tHL\n" - + "\tLD\tA,(HL)\n" - + "\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tDJNZ\tVDIP_HANDLER13\n" - + "\tLD\tA,0DH\n" - + "\tCALL\tVDIP_WR_BYTE\n" - // Dateizeiger gesetzt -> Ergebnis pruefen - + "VDIP_HANDLER14:\n" - + "\tCALL\tVDIP_CHECK_RESULT\n" - + "\tCCF\n" - + "\tRET\tNC\n" // RET mit CY=0! - // Datei zum Schreiben geoeffnet - + "\tCALL\tVDIP_LOCK_INIT_BUF\n" - // Kanalzeigerfeld fuellen - + "\tXOR\tA\n" - + "\tLD\tHL,(M_IOCA)\n" - + "\tLD\tDE,VDIP_CLOSE\n" - + "\tLD\t(HL),E\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),D\n" - + "\tINC\tHL\n" - + "\tLD\tB," ); - buf.appendHex2( BasicLibrary.IOCTB_WRITE_OFFS - 2 ); - buf.append( "\n" - + "VDIP_HANDLER15:\n" - + "\tLD\t(HL),A\n" - + "\tINC\tHL\n" - + "\tDJNZ\tVDIP_HANDLER15\n" - + "\tLD\tDE,VDIP_WRITE\n" - + "\tLD\t(HL),E\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),D\n" - + "\tINC\tHL\n" - + "\tLD\tDE,VDIP_FLUSH\n" - + "\tLD\t(HL),E\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),D\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),A\n" - + "\tINC\tHL\n" - + "\tLD\t(HL),A\n" - + "\tOR\tA\n" - + "\tRET\n" + buf.append( "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + + // Datei zum Lesen oeffnen + if( txtInput || binInput ) { + buf.append( "VDIP_HANDLER8:\n" + + "\tLD\tA,0EH\n" + + "\tCALL\tVDIP_WR_FNAME_CMD\n" ); + buf.append( "\tCALL\tVDIP_CHECK_RESULT\n" + + "\tJR\tNC,VDIP_HANDLER9\n" + + "\tLD\tHL,(M_ERN)\n" ); + buf.append_LD_DE_nn( BasicLibrary.E_ERROR ); + buf.append( "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tSCF\n" + + "\tCCF\n" // CY=0, Z beibehalten + + "\tRET\tNZ\n" ); + BasicLibrary.appendSetErrorFileNotFound( compiler ); + buf.append( "\tRET\n" + // Datei zum Lesen geoeffnet + + "VDIP_HANDLER9:\n" + + "\tCALL\tVDIP_LOCK_INIT_BUF\n" + // Kanalzeigerfeld fuellen + + "\tLD\tDE,(IO_M_CADDR)\n" + + "\tLD\tHL,VDIP_DATA_RDPTRS\n" ); + buf.append_LD_BC_nn( BasicLibrary.IOCTB_WRITE_OFFS ); + buf.append( "\tLDIR\n" + + "\tXOR\tA\n" + + "\tLD\tB," ); + buf.appendHex2( compiler.getIOChannelSize() + - BasicLibrary.IOCTB_WRITE_OFFS ); + buf.append( "\n" + + "VDIP_HANDLER10:\n" + + "\tLD\t(DE),A\n" + + "\tINC\tDE\n" + + "\tDJNZ\tVDIP_HANDLER10\n" + + "\tOR\tA\n" + + "\tRET\n" ); + libItems.add( BasicLibrary.LibItem.VDIP_DATA_RDPTRS ); + } + + // Datei zum Schreiben oeffnen + if( txtOutput || txtAppend || binOutput || binAppend ) { + buf.append( "VDIP_HANDLER11:\n" + + "\tLD\tA,09H\n" + + "\tCALL\tVDIP_WR_FNAME_CMD\n" + + "\tCALL\tVDIP_CHECK_RESULT\n" + + "\tCCF\n" + + "\tRET\tNC\n" ); // RET mit CY=0! + if( (txtOutput || binOutput) && (txtAppend || binAppend) ) { + buf.append( "\tLD\tA,(IO_M_ACCESS)\n" + + "\tAND\t" ); + buf.appendHex2( BasicLibrary.IOMODE_TXT_APPEND + | BasicLibrary.IOMODE_BIN_APPEND ); + buf.append( "\n" + + "\tJR\tNZ,VDIP_HANDLER12\n" ); + } + if( txtOutput || binOutput ) { + // Dateizeiger auf den Dateianfang setzen + buf.append( "\tCALL\tVDIP_WR_CMD\n" + + "\tDB\t28H,20H,00H,00H,00H,00H,0DH\n" ); + } + if( (txtOutput || binOutput) && (txtAppend || binAppend) ) { + buf.append( "\tJR\tVDIP_HANDLER14\n" + + "VDIP_HANDLER12:\n" ); + } + if( txtAppend || binAppend ) { + /* + * Dateizeiger auf das Dateiende setzen + * hoechstwertiges Byte zuerst!!! + */ + buf.append( "\tLD\tA,28H\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tLD\tA,20H\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tLD\tHL,VDIP_M_FBYTES+4\n" + + "\tLD\tB,04H\n" + + "VDIP_HANDLER13:\n" + + "\tDEC\tHL\n" + + "\tLD\tA,(HL)\n" + + "\tPUSH\tBC\n" + + "\tPUSH\tHL\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tPOP\tHL\n" + + "\tPOP\tBC\n" + + "\tDJNZ\tVDIP_HANDLER13\n" + + "\tLD\tA,0DH\n" + + "\tCALL\tVDIP_WR_BYTE\n" ); + } + + // Dateizeiger gesetzt -> Ergebnis pruefen + buf.append( "VDIP_HANDLER14:\n" + + "\tCALL\tVDIP_CHECK_RESULT\n" + + "\tCCF\n" + + "\tRET\tNC\n" // RET mit CY=0! + // Datei zum Schreiben geoeffnet + + "\tCALL\tVDIP_LOCK_INIT_BUF\n" + // Kanalzeigerfeld fuellen + + "\tLD\tHL,(IO_M_CADDR)\n" + + "\tLD\tDE,VDIP_CLOSE\n" + + "\tLD\t(HL),E\n" + + "\tINC\tHL\n" + + "\tLD\t(HL),D\n" + + "\tINC\tHL\n" + + "\tXOR\tA\n" + + "\tLD\tB," ); + buf.appendHex2( BasicLibrary.IOCTB_WRITE_OFFS - 2 ); + buf.append( "\n" + + "VDIP_HANDLER15:\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tDJNZ\tVDIP_HANDLER15\n" + + "\tLD\tDE,VDIP_WRITE\n" + + "\tLD\t(HL),E\n" + + "\tINC\tHL\n" + + "\tLD\t(HL),D\n" + + "\tINC\tHL\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tLD\t(HL),A\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + } /* * VDIP initialisieren * Rueckgabewert: * CY=0: OK * CY=1: Fehler, Fehlervariablen auf E_DEVICE_NOT_FOUND gesetzt */ - + "VDIP_INIT:\n" - + "\tLD\tA,(VDIP_M_INIT)\n" - + "\tOR\tA\n" - + "\tRET\tNZ\n" ); + buf.append( "VDIP_INIT:\n" + + "\tLD\tA,(VDIP_M_INIT)\n" + + "\tOR\tA\n" + + "\tRET\tNZ\n" ); // VDIP aktiviern if( target.needsEnableVdip() ) { - target.appendEnableVdip( buf ); + target.appendEnableVdipTo( buf ); buf.append( "\tJR\tC,VDIP_INIT_ERR\n" ); } // PIO initialisieren @@ -268,83 +298,97 @@ public static void appendCodeTo( BasicCompiler compiler ) buf.append_LD_BC_nn( 0x0500 | (baseIOAddr + 3) ); } else { buf.append( "\tLD\tA,(VDIP_M_IOADDR)\n" - + "\tADD\tA,03H\n" - + "\tLD\tC,A\n" - + "\tLD\tB,05H\n" ); + + "\tADD\tA,03H\n" + + "\tLD\tC,A\n" + + "\tLD\tB,05H\n" ); } buf.append( "\tOTIR\n" - + "\tDEC\tC\n" - + "\tDEC\tC\n" - + "\tLD\tB,01H\n" - + "\tOTIR\n" - + "\tINC\tC\n" - + "\tLD\tB,02H\n" - + "\tOTIR\n" + + "\tDEC\tC\n" + + "\tDEC\tC\n" + + "\tLD\tB,01H\n" + + "\tOTIR\n" + + "\tINC\tC\n" + + "\tLD\tB,02H\n" + + "\tOTIR\n" // VDIP-Anfangsmeldung ueberlesen - + "VDIP_INIT1:\n" - + "\tCALL\tVDIP_RD_BYTE_T\n" - + "\tJR\tNC,VDIP_INIT1\n" + + "VDIP_INIT1:\n" + + "\tCALL\tVDIP_RD_BYTE_T\n" + + "\tJR\tNC,VDIP_INIT1\n" // Echo testen - + "\tLD\tA,\'E\'\n" - + "\tCALL\tVDIP_WR_BYTE_T\n" - + "\tJR\tC,VDIP_INIT_ERR\n" - + "\tLD\tA,0DH\n" - + "\tCALL\tVDIP_WR_BYTE_T\n" - + "\tJR\tC,VDIP_INIT_ERR\n" - + "\tCALL\tVDIP_RD_BYTE_T\n" - + "\tJR\tC,VDIP_INIT_ERR\n" - + "\tCP\t\'E\'\n" - + "\tJR\tNZ,VDIP_INIT_ERR\n" - + "\tCALL\tVDIP_RD_BYTE_T\n" - + "\tJR\tC,VDIP_INIT_ERR\n" - + "\tCP\t0DH\n" - + "\tJR\tNZ,VDIP_INIT_ERR\n" + + "\tLD\tA,\'E\'\n" + + "\tCALL\tVDIP_WR_BYTE_T\n" + + "\tJR\tC,VDIP_INIT_ERR\n" + + "\tLD\tA,0DH\n" + + "\tCALL\tVDIP_WR_BYTE_T\n" + + "\tJR\tC,VDIP_INIT_ERR\n" + + "\tCALL\tVDIP_RD_BYTE_T\n" + + "\tJR\tC,VDIP_INIT_ERR\n" + + "\tCP\t\'E\'\n" + + "\tJR\tNZ,VDIP_INIT_ERR\n" + + "\tCALL\tVDIP_RD_BYTE_T\n" + + "\tJR\tC,VDIP_INIT_ERR\n" + + "\tCP\t0DH\n" + + "\tJR\tNZ,VDIP_INIT_ERR\n" // Short Command Set einstellen - + "\tCALL\tVDIP_WR_CMD\n" - + "\tDB\t10H,0DH\n" - + "\tCALL\tVDIP_INIT2\n" - + "\tRET\tC\n" + + "\tCALL\tVDIP_WR_CMD\n" + + "\tDB\t10H,0DH\n" + + "\tCALL\tVDIP_INIT2\n" + + "\tRET\tC\n" // Binaer-Mode einstellen - + "\tCALL\tVDIP_WR_CMD\n" - + "\tDB\t91H,0DH\n" + + "\tCALL\tVDIP_WR_CMD\n" + + "\tDB\t91H,0DH\n" // Prompt pruefen - + "VDIP_INIT2:\n" - + "\tCALL\tVDIP_RD_BYTE\n" - + "\tCP\t3EH\n" - + "\tJR\tNZ,VDIP_INIT_ERR\n" - + "\tCALL\tVDIP_RD_BYTE\n" - + "\tCP\t0DH\n" - + "\tRET\tZ\n" - + "VDIP_INIT_ERR:\n" ); + + "VDIP_INIT2:\n" + + "\tCALL\tVDIP_RD_BYTE\n" + + "\tCP\t3EH\n" + + "\tJR\tNZ,VDIP_INIT_ERR\n" + + "\tCALL\tVDIP_RD_BYTE\n" + + "\tCP\t0DH\n" + + "\tJR\tNZ,VDIP_INIT_ERR\n" + + "\tLD\tA,01H\n" + + "\tLD\t(VDIP_M_INIT),A\n" + + "\tRET\n" + + "VDIP_INIT_ERR:\n" ); BasicLibrary.appendSetErrorDeviceNotFound( compiler ); buf.append( "\tSCF\n" - + "\tRET\n" + + "\tRET\n" // VDIP sperren und Puffer initialisieren - + "VDIP_LOCK_INIT_BUF:\n" - + "\tLD\tHL,VDIP_BUF\n" - + "\tLD\t(VDIP_M_BPTR),HL\n" - + "\tXOR\tA\n" - + "\tLD\t(VDIP_M_BBYTES),A\n" - + "\tINC\tA\n" - + "\tLD\t(VDIP_M_LOCKED),A\n" - + "\tRET\n" + + "VDIP_LOCK_INIT_BUF:\n" + + "\tLD\tHL,VDIP_BUF\n" + + "\tLD\t(VDIP_M_BPTR),HL\n" + + "\tXOR\tA\n" + + "\tLD\t(VDIP_M_BBYTES),A\n" + + "\tINC\tA\n" + + "\tLD\t(VDIP_M_LOCKED),A\n" + + "\tRET\n" /* * CLOSE-Routine * Parameter: * DE: Anfangsadresse Kanalzeigerfeld */ - + "VDIP_CLOSE:\n" - + "\tLD\tA,(VDIP_M_LOCKED)\n" + + "VDIP_CLOSE:\n" + + "\tLD\tA,(VDIP_M_LOCKED)\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" ); + if( txtOutput || txtAppend || binOutput || binAppend ) { + if( txtInput || binInput ) { + buf.append_LD_HL_nn( BasicLibrary.IOCTB_WRITE_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\tA,(HL)\n" + + "\tINC\tHL\n" + "\tOR\tA\n" - + "\tRET\tZ\n" - + "\tCALL\tVDIP_FLUSH\n" - + "\tLD\tA,0AH\n" - + "\tCALL\tVDIP_WR_FNAME_CMD\n" - + "\tCALL\tVDIP_CHECK_RESULT\n" - + "\tRET\tC\n" - + "\tXOR\tA\n" - + "\tLD\t(VDIP_M_LOCKED),A\n" - + "\tRET\n" + + "\tJR\tZ,VDIP_CLOSE1\n" ); + } + buf.append( "\tCALL\tVDIP_FLUSH\n" + + "VDIP_CLOSE1:\n" ); + } + buf.append( "\tLD\tA,0AH\n" + + "\tCALL\tVDIP_WR_FNAME_CMD\n" + + "\tCALL\tVDIP_CHECK_RESULT\n" + + "\tRET\tC\n" + + "\tXOR\tA\n" + + "\tLD\t(VDIP_M_LOCKED),A\n" + + "\tRET\n" /* * EOF-Routine * Parameter: @@ -352,34 +396,26 @@ public static void appendCodeTo( BasicCompiler compiler ) * Rueckgabewert: * HL: -1: Dateiende erreicht */ - + "VDIP_EOF:\n" - + "\tLD\tA,(VDIP_M_BBYTES)\n" - + "\tOR\tA\n" - + "\tJR\tNZ,VDIP_EOF2\n" - + "\tLD\tHL,VDIP_M_FBYTES\n" - + "\tLD\tB,04H\n" - + "VDIP_EOF1:\n" - + "\tOR\t(HL)\n" - + "\tINC\tHL\n" - + "\tDJNZ\tVDIP_EOF1\n" - + "VDIP_EOF2:\n" - + "\tLD\tHL,0000H\n" - + "\tOR\tA\n" - + "\tRET\tNZ\n" - + "\tDEC\tHL\n" - + "\tRET\n" - /* - * AVAILABLE-Routine - * Parameter: - * DE: Anfangsadresse Kanalzeigerfeld - * Rueckgabewert: - * HL: Anzahl der Bytes im Puffer - */ - + "VDIP_AVAILABLE:\n" - + "\tLD\tA,(VDIP_M_BBYTES)\n" - + "\tLD\tL,A\n" - + "\tLD\tH,00H\n" - + "\tRET\n" + + "VDIP_EOF:\n" ); + if( txtInput || binInput ) { + buf.append( "\tLD\tA,(VDIP_M_BBYTES)\n" + + "\tOR\tA\n" + + "\tJR\tNZ,VDIP_EOF2\n" + + "\tLD\tHL,VDIP_M_FBYTES\n" + + "\tLD\tB,04H\n" + + "VDIP_EOF1:\n" + + "\tOR\t(HL)\n" + + "\tINC\tHL\n" + + "\tDJNZ\tVDIP_EOF1\n" + + "VDIP_EOF2:\n" + + "\tLD\tHL,0000H\n" + + "\tOR\tA\n" + + "\tRET\tNZ\n" + + "\tDEC\tHL\n" ); + } else { + buf.append( "\tLD\tHL,0FFFFH\n" ); + } + buf.append( "\tRET\n" /* * READ-Routine * Parameter: @@ -387,152 +423,160 @@ public static void appendCodeTo( BasicCompiler compiler ) * Rueckgabewert: * A: gelesenes Zeichen */ - + "VDIP_READ:\n" - // noch Bytes im Puffer? - + "\tLD\tA,(VDIP_M_BBYTES)\n" - + "\tOR\tA\n" - + "\tJR\tNZ,VDIP_READ7\n" - /* - * keine Bytes mehr im Puffer - * 128 von der Anzahl der restlichen Dateibytes subtrahieren - */ - + "\tLD\tHL,VDIP_M_FBYTES\n" - + "\tLD\tA,(HL)\n" - + "\tLD\tD,A\n" - + "\tSUB\t80H\n" - + "\tLD\t(HL),A\n" - + "\tJR\tNC,VDIP_READ3\n" // kein Ueberlauf - + "\tLD\tBC,0300H\n" - + "VDIP_READ1:\n" - + "\tINC\tHL\n" - + "\tLD\tA,(HL)\n" - + "\tSBC\tA,C\n" - + "\tLD\t(HL),A\n" - + "\tDJNZ\tVDIP_READ1\n" - + "\tJR\tNC,VDIP_READ3\n" // kein Ueberlauf - /* - * weniger als 128 Bytes verfuegbar - * Anzahl in D - */ - + "\tLD\tHL,VDIP_M_FBYTES\n" - + "\tXOR\tA\n" - + "\tLD\tB,04H\n" - + "VDIP_READ2:\n" - + "\tLD\t(HL),A\n" - + "\tINC\tHL\n" - + "\tDJNZ\tVDIP_READ2\n" - + "\tLD\tA,D\n" - + "\tOR\tA\n" - + "\tJR\tNZ,VDIP_READ4\n" - // keine restlichen Bytes mehr - + "\tLD\t(VDIP_M_BBYTES),A\n" ); - BasicLibrary.appendSetErrorEOF( compiler ); + + "VDIP_READ:\n" ); + if( txtInput || binInput ) { + // noch Bytes im Puffer? + buf.append( "\tLD\tA,(VDIP_M_BBYTES)\n" + + "\tOR\tA\n" + + "\tJR\tNZ,VDIP_READ7\n" + /* + * keine Bytes mehr im Puffer + * 128 von der Anzahl der restlichen Dateibytes subtrahieren + */ + + "\tLD\tHL,VDIP_M_FBYTES\n" + + "\tLD\tA,(HL)\n" + + "\tLD\tD,A\n" + + "\tSUB\t80H\n" + + "\tLD\t(HL),A\n" + + "\tJR\tNC,VDIP_READ3\n" // kein Ueberlauf + + "\tLD\tBC,0300H\n" + + "VDIP_READ1:\n" + + "\tINC\tHL\n" + + "\tLD\tA,(HL)\n" + + "\tSBC\tA,C\n" + + "\tLD\t(HL),A\n" + + "\tDJNZ\tVDIP_READ1\n" + + "\tJR\tNC,VDIP_READ3\n" // kein Ueberlauf + /* + * weniger als 128 Bytes verfuegbar + * Anzahl in D + */ + + "\tLD\tHL,VDIP_M_FBYTES\n" + + "\tXOR\tA\n" + + "\tLD\tB,04H\n" + + "VDIP_READ2:\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tDJNZ\tVDIP_READ2\n" + + "\tLD\tA,D\n" + + "\tOR\tA\n" + + "\tJR\tNZ,VDIP_READ4\n" + // keine restlichen Bytes mehr + + "\tLD\t(VDIP_M_BBYTES),A\n" ); + BasicLibrary.appendSetErrorEOF( compiler ); + buf.append( "\tRET\n" + // 128 Bytes lesen + + "VDIP_READ3:\n" + + "\tLD\tA,80H\n" + // Bytes lesen, Anzahl in A + + "VDIP_READ4:\n" + + "\tLD\t(VDIP_M_BBYTES),A\n" + + "\tEX\tAF,AF\'\n" + + "\tLD\tA,0BH\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tLD\tA,20H\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tLD\tB,03H\n" + + "VDIP_READ5:\n" + + "\tPUSH\tBC\n" + + "\tXOR\tA\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tPOP\tBC\n" + + "\tDJNZ\tVDIP_READ5\n" + + "\tEX\tAF,AF\'\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tLD\tA,0DH\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tLD\tHL,VDIP_BUF\n" + + "\tLD\t(VDIP_M_BPTR),HL\n" + + "\tLD\tA,(VDIP_M_BBYTES)\n" + + "\tLD\tB,A\n" + + "VDIP_READ6:\n" + + "\tPUSH\tBC\n" + + "\tPUSH\tHL\n" + + "\tCALL\tVDIP_RD_BYTE\n" + + "\tPOP\tHL\n" + + "\tPOP\tBC\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tDJNZ\tVDIP_READ6\n" + + "\tCALL\tVDIP_CHECK_RESULT\n" + + "\tLD\tA,(VDIP_M_BBYTES)\n" + + "VDIP_READ7:\n" + + "\tDEC\tA\n" + + "\tLD\t(VDIP_M_BBYTES),A\n" + + "\tLD\tHL,(VDIP_M_BPTR)\n" + + "\tLD\tA,(HL)\n" + + "\tINC\tHL\n" + + "\tLD\t(VDIP_M_BPTR),HL\n" ); + } else { + buf.append( "\tXOR\tA\n" ); + } buf.append( "\tRET\n" - // 128 Bytes lesen - + "VDIP_READ3:\n" - + "\tLD\tA,80H\n" - // Bytes lesen, Anzahl in A - + "VDIP_READ4:\n" - + "\tLD\t(VDIP_M_BBYTES),A\n" - + "\tEX\tAF,AF\'\n" - + "\tLD\tA,0BH\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tLD\tA,20H\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tLD\tB,03H\n" - + "VDIP_READ5:\n" - + "\tPUSH\tBC\n" - + "\tXOR\tA\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tPOP\tBC\n" - + "\tDJNZ\tVDIP_READ5\n" - + "\tEX\tAF,AF\'\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tLD\tA,0DH\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tLD\tHL,VDIP_BUF\n" - + "\tLD\t(VDIP_M_BPTR),HL\n" - + "\tLD\tA,(VDIP_M_BBYTES)\n" - + "\tLD\tB,A\n" - + "VDIP_READ6:\n" - + "\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tCALL\tVDIP_RD_BYTE\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tLD\t(HL),A\n" - + "\tINC\tHL\n" - + "\tDJNZ\tVDIP_READ6\n" - + "\tCALL\tVDIP_CHECK_RESULT\n" - + "\tLD\tA,(VDIP_M_BBYTES)\n" - + "VDIP_READ7:\n" - + "\tDEC\tA\n" - + "\tLD\t(VDIP_M_BBYTES),A\n" - + "\tLD\tHL,(VDIP_M_BPTR)\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tLD\t(VDIP_M_BPTR),HL\n" - + "\tRET\n" /* * WRITE-Routine * Parameter: - * (M_IOCA): Anfangsadresse Kanalzeigerfeld - * A: zu schreibendes Byte + * (IO_M_CADDR): Anfangsadresse Kanalzeigerfeld + * A: zu schreibendes Byte */ - + "VDIP_WRITE:\n" - + "\tLD\tHL,(VDIP_M_BPTR)\n" - + "\tLD\t(HL),A\n" - + "\tINC\tHL\n" - + "\tLD\t(VDIP_M_BPTR),HL\n" - + "\tLD\tA,(VDIP_M_BBYTES)\n" - + "\tINC\tA\n" - + "\tLD\t(VDIP_M_BBYTES),A\n" - + "\tRET\tP\n" - // direkt weiter mit der FLUSH-Routine! + + "VDIP_WRITE:\n" ); + if( txtOutput || txtAppend || binOutput || binAppend ) { + buf.append( "\tLD\tHL,(VDIP_M_BPTR)\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tLD\t(VDIP_M_BPTR),HL\n" + + "\tLD\tA,(VDIP_M_BBYTES)\n" + + "\tINC\tA\n" + + "\tLD\t(VDIP_M_BBYTES),A\n" + + "\tRET\tP\n" ); + // direkt weiter mit der FLUSH-Routine! + } /* * FLUSH-Routine * Parameter: * DE: Anfangsadresse Kanalzeigerfeld */ - + "VDIP_FLUSH:\n" - + "\tLD\tA,(VDIP_M_BBYTES)\n" - + "\tOR\tA\n" - + "\tRET\tZ\n" - + "\tPUSH\tAF\n" - + "\tLD\tA,08H\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tLD\tA,20H\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tLD\tB,03H\n" - + "VDIP_FLUSH1:\n" - + "\tPUSH\tBC\n" - + "\tXOR\tA\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tPOP\tBC\n" - + "\tDJNZ\tVDIP_FLUSH1\n" - + "\tPOP\tAF\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tLD\tA,0DH\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tLD\tHL,VDIP_BUF\n" - + "\tLD\tA,(VDIP_M_BBYTES)\n" - + "\tLD\tB,A\n" - + "VDIP_FLUSH2:\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tCALL\tVDIP_WR_BYTE\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tDJNZ\tVDIP_FLUSH2\n" - + "\tCALL\tVDIP_CHECK_RESULT\n" - + "\tRET\tC\n" - + "\tXOR\tA\n" - + "\tLD\t(VDIP_M_BBYTES),A\n" - + "\tLD\tHL,VDIP_BUF\n" - + "\tLD\t(VDIP_M_BPTR),HL\n" - + "\tRET\n" + buf.append( "VDIP_FLUSH:\n" ); + if( txtOutput || txtAppend || binOutput || binAppend ) { + buf.append( "\tLD\tA,(VDIP_M_BBYTES)\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" + + "\tPUSH\tAF\n" + + "\tLD\tA,08H\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tLD\tA,20H\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tLD\tB,03H\n" + + "VDIP_FLUSH1:\n" + + "\tPUSH\tBC\n" + + "\tXOR\tA\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tPOP\tBC\n" + + "\tDJNZ\tVDIP_FLUSH1\n" + + "\tPOP\tAF\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tLD\tA,0DH\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tLD\tHL,VDIP_BUF\n" + + "\tLD\tA,(VDIP_M_BBYTES)\n" + + "\tLD\tB,A\n" + + "VDIP_FLUSH2:\n" + + "\tLD\tA,(HL)\n" + + "\tINC\tHL\n" + + "\tPUSH\tBC\n" + + "\tPUSH\tHL\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tPOP\tHL\n" + + "\tPOP\tBC\n" + + "\tDJNZ\tVDIP_FLUSH2\n" + + "\tCALL\tVDIP_CHECK_RESULT\n" + + "\tRET\tC\n" + + "\tXOR\tA\n" + + "\tLD\t(VDIP_M_BBYTES),A\n" + + "\tLD\tHL,VDIP_BUF\n" + + "\tLD\t(VDIP_M_BPTR),HL\n" ); + } + buf.append( "\tRET\n" ); /* * Lesen der Dateigroesse * Paramater: @@ -541,71 +585,73 @@ public static void appendCodeTo( BasicCompiler compiler ) * CY=0: OK, Dateigroesse steht in (VDIP_M_FBYTES) * CY=1: Fehler */ - + "VDIP_RD_FSIZE:\n" - + "\tLD\tA,01H\n" // Kommando DIR - + "\tCALL\tVDIP_WR_FNAME_CMD\n" - // Leerzeile pruefen - + "\tCALL\tVDIP_RD_BYTE\n" - + "\tCP\t0DH\n" - + "\tJR\tNZ,VDIP_RD_FSIZE4\n" - // vom DIR-Kommando zurueckgelieferten Dateinamen vergleichen - + "\tLD\tHL,(VDIP_M_FNAME)\n" - + "VDIP_RD_FSIZE1:\n" - + "\tLD\tA,(HL)\n" - + "\tINC\tHL\n" - + "\tOR\tA\n" - + "\tJR\tZ,VDIP_RD_FSIZE2\n" - + "\tCALL\tC_UPR\n" - + "\tPUSH\tAF\n" - + "\tCALL\tVDIP_RD_BYTE\n" - + "\tCALL\tC_UPR\n" - + "\tLD\tB,A\n" - + "\tPOP\tAF\n" - + "\tCP\tB\n" - + "\tJR\tZ,VDIP_RD_FSIZE1\n" ); - // Dateiname stimmt nicht ueberein - BasicLibrary.appendSetErrorFileNotFound( compiler ); - buf.append( "\tJR\tVDIP_RD_FSIZE5\n" - // Leerzeichen hinter Dateinamen pruefen - + "VDIP_RD_FSIZE2:\n" - + "\tCALL\tVDIP_RD_BYTE\n" - + "\tCP\t20H\n" - + "\tJR\tNZ,VDIP_RD_FSIZE4\n" - // Dateigroesse lesen - + "\tLD\tHL,VDIP_M_FBYTES\n" - + "\tLD\tB,04H\n" - + "VDIP_RD_FSIZE3:\n" - + "\tPUSH\tBC\n" - + "\tPUSH\tHL\n" - + "\tCALL\tVDIP_RD_BYTE\n" - + "\tPOP\tHL\n" - + "\tPOP\tBC\n" - + "\tLD\t(HL),A\n" - + "\tINC\tHL\n" - + "\tDJNZ\tVDIP_RD_FSIZE3\n" - // Zeilenendezeichen lesen - + "\tCALL\tVDIP_RD_BYTE\n" - + "\tCP\t0DH\n" - + "\tJR\tNZ,VDIP_RD_FSIZE4\n" - // Kommando erfolgreich? - + "\tJR\tVDIP_CHECK_RESULT\n" - // Fehler beim DIR-Kommando -> restliche Bytes lesen und Abbruch - + "VDIP_RD_FSIZE4:\n" ); - BasicLibrary.appendSetErrorIOError( compiler ); - buf.append( "VDIP_RD_FSIZE5:\n" - + "\tCALL\tVDIP_RD_BYTE_T\n" - + "\tRET\tC\n" - + "\tCP\t0DH\n" - + "\tJR\tNZ,VDIP_RD_FSIZE5\n" - + "\tSCF\n" - + "\tRET\n" + if( needsVDIP_RD_FSIZE ) { + buf.append( "VDIP_RD_FSIZE:\n" + + "\tLD\tA,01H\n" // Kommando DIR + + "\tCALL\tVDIP_WR_FNAME_CMD\n" + // Leerzeile pruefen + + "\tCALL\tVDIP_RD_BYTE\n" + + "\tCP\t0DH\n" + + "\tJR\tNZ,VDIP_RD_FSIZE4\n" + // vom DIR-Kommando zurueckgelieferten Dateinamen vergleichen + + "\tLD\tHL,(VDIP_M_FNAME)\n" + + "VDIP_RD_FSIZE1:\n" + + "\tLD\tA,(HL)\n" + + "\tINC\tHL\n" + + "\tOR\tA\n" + + "\tJR\tZ,VDIP_RD_FSIZE2\n" + + "\tCALL\tC_UPR\n" + + "\tPUSH\tAF\n" + + "\tCALL\tVDIP_RD_BYTE\n" + + "\tCALL\tC_UPR\n" + + "\tLD\tB,A\n" + + "\tPOP\tAF\n" + + "\tCP\tB\n" + + "\tJR\tZ,VDIP_RD_FSIZE1\n" ); + // Dateiname stimmt nicht ueberein + BasicLibrary.appendSetErrorFileNotFound( compiler ); + buf.append( "\tJR\tVDIP_RD_FSIZE5\n" + // Leerzeichen hinter Dateinamen pruefen + + "VDIP_RD_FSIZE2:\n" + + "\tCALL\tVDIP_RD_BYTE\n" + + "\tCP\t20H\n" + + "\tJR\tNZ,VDIP_RD_FSIZE4\n" + // Dateigroesse lesen + + "\tLD\tHL,VDIP_M_FBYTES\n" + + "\tLD\tB,04H\n" + + "VDIP_RD_FSIZE3:\n" + + "\tPUSH\tBC\n" + + "\tPUSH\tHL\n" + + "\tCALL\tVDIP_RD_BYTE\n" + + "\tPOP\tHL\n" + + "\tPOP\tBC\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tDJNZ\tVDIP_RD_FSIZE3\n" + // Zeilenendezeichen lesen + + "\tCALL\tVDIP_RD_BYTE\n" + + "\tCP\t0DH\n" + + "\tJR\tNZ,VDIP_RD_FSIZE4\n" + // Kommando erfolgreich? + + "\tJR\tVDIP_CHECK_RESULT\n" + // Fehler beim DIR-Kommando -> restliche Bytes lesen und Abbruch + + "VDIP_RD_FSIZE4:\n" ); + BasicLibrary.appendSetErrorIOError( compiler ); + buf.append( "VDIP_RD_FSIZE5:\n" + + "\tCALL\tVDIP_RD_BYTE_T\n" + + "\tRET\tC\n" + + "\tCP\t0DH\n" + + "\tJR\tNZ,VDIP_RD_FSIZE5\n" + + "\tSCF\n" + + "\tRET\n" ); + } /* * Vorhandensein des Speichermediums pruefen * Rueckgabewert: * CY=0: OK * CY=1: Fehler, Fehlervariablen gesetzt */ - + "VDIP_CHECK_DISK:\n" + buf.append( "VDIP_CHECK_DISK:\n" + "\tCALL\tVDIP_WR_CMD\n" + "\tDB\t0DH\n" + "\tCALL\tVDIP_RD_BYTE\n" @@ -619,7 +665,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tJR\tNZ,VDIP_CHECK_DISK1\n" + "\tEX\tAF,AF\'\n" + "\tCP\t3EH\n" - + "\tRET\tZ\n" // Disk vorhanden + + "\tRET\tZ\n" // Disk vorhanden + "\tCP\t4EH\n" + "\tJR\tNZ,VDIP_CHECK_DISK2\n" ); BasicLibrary.appendSetErrorNoDisk( compiler ); @@ -700,6 +746,101 @@ public static void appendCodeTo( BasicCompiler compiler ) BasicLibrary.appendSetError( compiler ); buf.append( "\tSCF\n" + "\tRET\n" + /* + * Pfad des vollstaendigen Dateinamens einstellen + * Parameter: + * (VDIP_M_FNAME): Dateiname inkl. optionalen Pfad + * Rueckgabewerte: + * (VDIP_M_FNAME): Dateiname ohne Pfad + * CY=1: Fehler + */ + + "VDIP_SET_DIR:\n" + + "\tLD\tDE,(VDIP_M_FNAME)\n" + // absolute Pfadangabe? + + "\tLD\tA,(DE)\n" + + "\tCP\t2FH\n" // Slash + + "\tJR\tZ,VDIP_SET_DIR1\n" + + "\tCP\t5CH\n" // Backslash + + "\tJR\tNZ,VDIP_SET_DIR4\n" + // in das oberste Verzeichnis wechseln + + "VDIP_SET_DIR1:\n" + + "\tINC\tDE\n" + + "VDIP_SET_DIR2:\n" + + "\tCALL\tVDIP_WR_CMD\n" // CD .. + + "\tDB\t02H,20H,2EH,2EH,0DH\n" + + "\tCALL\tVDIP_RD_BYTE\n" // Ergebnis auswerten + + "\tCP\t3EH\n" + + "\tJR\tNZ,VDIP_SET_DIR3\n" + + "\tCALL\tVDIP_RD_BYTE\n" // Ergebnis auswerten + + "\tCP\t0DH\n" + + "\tJR\tZ,VDIP_SET_DIR2\n" + + "VDIP_SET_DIR3:\n" // Fehler -> Bytes lesen + + "\tCP\t0DH\n" + + "\tJR\tZ,VDIP_SET_DIR4\n" + + "\tCALL\tVDIP_RD_BYTE\n" + + "\tJR\tVDIP_SET_DIR3\n" + // in Verzeichnis wechseln + + "VDIP_SET_DIR4:\n" + + "\tLD\tB,0FFH\n" + + "\tLD\tH,D\n" + + "\tLD\tL,E\n" + + "VDIP_SET_DIR5:\n" + + "\tLD\tA,(HL)\n" + + "\tINC\tHL\n" + + "\tINC\tB\n" + + "\tOR\tA\n" + + "\tJR\tZ,VDIP_SET_DIR10\n" // kein Pfadelement + + "\tCP\t0DH\n" // Zeilenendezeichen + + "\tJR\tZ,VDIP_SET_DIR8\n" // nicht erlaubt + + "\tCP\t2FH\n" // Slash + + "\tJR\tZ,VDIP_SET_DIR6\n" + + "\tCP\t5CH\n" // Backslash + + "\tJR\tNZ,VDIP_SET_DIR5\n" + + "VDIP_SET_DIR6:\n" + + "\tXOR\tA\n" + + "\tOR\tB\n" + + "\tJR\tZ,VDIP_SET_DIR8\n" // leeres Pfadelement + + "\tPUSH\tBC\n" + + "\tLD\tA,02H\n" // Kommando CD + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tLD\tA,20H\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tPOP\tBC\n" + + "VDIP_SET_DIR7:\n" + + "\tLD\tA,(DE)\n" + + "\tINC\tDE\n" + + "\tPUSH\tBC\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tPOP\tBC\n" + + "\tDJNZ\tVDIP_SET_DIR7\n" + + "\tINC\tDE\n" // Trennzeichen uebergehen + + "\tLD\tA,0DH\n" + + "\tCALL\tVDIP_WR_BYTE\n" + + "\tPUSH\tDE\n" + + "\tCALL\tVDIP_CHECK_RESULT\n" + + "\tPOP\tDE\n" + + "\tJR\tNC,VDIP_SET_DIR4\n" ); + // bei Command Failed (E_ERROR) wurde Pfad nicht gefunden + if( compiler.usesLibItem( BasicLibrary.LibItem.M_ERN ) ) { + buf.append_LD_DE_nn( BasicLibrary.E_ERROR ); + buf.append( "\tLD\tHL,(M_ERN)\n" + + "\tOR\tA\n" + + "\tSBC\tHL,DE\n" + + "\tJR\tNZ,VDIP_SET_DIR9\n" ); + } + buf.append( "VDIP_SET_DIR8:\n" ); // Pfad nicht gefunden + BasicLibrary.appendSetError( + compiler, + BasicLibrary.E_PATH_NOT_FOUND, + "Pfad nicht gefunden", + "Path not found" ); + buf.append( "VDIP_SET_DIR9:\n" + + "\tSCF\n" + + "\tRET\n" + + "VDIP_SET_DIR10:\n" + + "\tLD\t(VDIP_M_FNAME),DE\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" /* * Kommando mit angehaengtem Dateinamen senden * Parameter: @@ -771,7 +912,7 @@ public static void appendCodeTo( BasicCompiler compiler ) * Lesen eines Bytes vom VDIP-Modul mit Timeout-Check * Rueckgabewerte: * CY=0: OK, gelesenes Byte in A - * CY=1: Fehler (Timeout ca. 100 ms bei " Mhz Taktfrequenz) + * CY=1: Fehler (Timeout) */ + "VDIP_RD_BYTE_T:\n" ); if( baseIOAddr >= 0 ) { @@ -783,7 +924,7 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tINC\tA\n" + "\tLD\tC,A\n" ); } - buf.append( "\tLD\tDE,1000H\n" + buf.append( "\tLD\tDE,0000H\n" + "VDIP_RD_BYTE_T1:\n" + "\tIN\tA,(C)\n" + "\tRRCA\n" @@ -807,7 +948,7 @@ public static void appendCodeTo( BasicCompiler compiler ) buf.append( "\n" ); } else { buf.append( "\tLD\tA,(VDIP_M_IOADDR)\n" - + "\tLD\tC,A\n" ); + + "\tLD\tC,A\n" ); } buf.append( "\tOUT\t(C),B\n" + "\tINC\tC\n" @@ -829,7 +970,7 @@ public static void appendCodeTo( BasicCompiler compiler ) * A: Byte * Rueckgabewerte: * CY=0: OK, gelesenes Byte in A - * CY=1: Fehler (Timeout ca. 100 ms bei 2 MHz Taktfrequenz) + * CY=1: Fehler (Timeout) */ + "VDIP_WR_BYTE_T:\n" + "\tLD\tB,A\n" ); @@ -855,8 +996,10 @@ public static void appendCodeTo( BasicCompiler compiler ) + "\tJR\tNZ,VDIP_WR_BYTE_T1\n" + "\tSCF\n" // CY=1: Timeout + "\tRET\n" ); + libItems.add( BasicLibrary.LibItem.VDIP_INIT ); libItems.add( BasicLibrary.LibItem.C_UPR ); libItems.add( BasicLibrary.LibItem.M_ERN ); + libItems.add( BasicLibrary.LibItem.M_ERN ); done = true; } } @@ -869,47 +1012,43 @@ public static void appendCodeTo( BasicCompiler compiler ) public static void appendDataTo( BasicCompiler compiler ) { - compiler.getCodeBuf().append( "VDIP_DATA_PIO_INIT:\n" + if( compiler.usesLibItem( BasicLibrary.LibItem.VDIP_INIT ) ) { + compiler.getCodeBuf().append( "VDIP_DATA_PIO_INIT:\n" + "\tDB\t0CFH,23H,0D0H,17H,0FFH\n" // PIO B Ctrl + "\tDB\t0D4H\n" // PIO B Data - + "\tDB\t8FH,07H\n" // PIO A Ctrl - + "VDIP_DATA_RDPTRS:\n" + + "\tDB\t8FH,07H\n" ); // PIO A Ctrl + } + if( compiler.usesLibItem( BasicLibrary.LibItem.VDIP_INIT ) ) { + compiler.getCodeBuf().append( "VDIP_DATA_RDPTRS:\n" + "\tDW\tVDIP_CLOSE\n" + "\tDW\tVDIP_EOF\n" - + "\tDW\tVDIP_AVAILABLE\n" + "\tDW\tVDIP_READ\n" ); + } } - public static void appendBssTo( BasicCompiler compiler ) + public static void appendBssTo( + AsmCodeBuf buf, + BasicCompiler compiler ) { - AsmCodeBuf buf = compiler.getCodeBuf(); if( compiler.usesLibItem( BasicLibrary.LibItem.VDIP_M_IOADDR ) ) { buf.append( "VDIP_M_IOADDR:\n" + "\tDS\t1\n" ); } - buf.append( "VDIP_M_INIT:\n" - + "\tDS\t1\n" - + "VDIP_M_LOCKED:\n" - + "\tDS\t1\n" - + "VDIP_M_FNAME:\n" - + "\tDS\t2\n" - + "VDIP_M_FBYTES:\n" - + "\tDS\t4\n" - + "VDIP_M_BBYTES:\n" - + "\tDS\t1\n" - + "VDIP_M_BPTR:\n" - + "\tDS\t2\n" - + "VDIP_BUF:\n" - + "\tDS\t80H\n" ); + buf.append( "VDIP_M_INIT:\tDS\t1\n" + + "VDIP_M_LOCKED:\tDS\t1\n" + + "VDIP_M_FNAME:\tDS\t2\n" + + "VDIP_M_FBYTES:\tDS\t4\n" + + "VDIP_M_BBYTES:\tDS\t1\n" + + "VDIP_M_BPTR:\tDS\t2\n" + + "VDIP_BUF:\tDS\t80H\n" ); } - public static void appendInitTo( BasicCompiler compiler ) + public static void appendInitTo( AsmCodeBuf buf ) { - compiler.getCodeBuf().append( "\tXOR\tA\n" + buf.append( "\tXOR\tA\n" + "\tLD\t(VDIP_M_INIT),A\n" + "\tLD\t(VDIP_M_LOCKED),A\n" ); } } - diff --git a/src/jkcemu/programming/basic/WhileEntry.java b/src/jkcemu/programming/basic/WhileEntry.java index bff2677..2aecfb9 100644 --- a/src/jkcemu/programming/basic/WhileEntry.java +++ b/src/jkcemu/programming/basic/WhileEntry.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -9,6 +9,7 @@ package jkcemu.programming.basic; import java.lang.*; +import jkcemu.programming.PrgSource; public class WhileEntry extends LoopEntry @@ -18,12 +19,12 @@ public class WhileEntry extends LoopEntry public WhileEntry( - int sourceLineNum, - long basicLineNum, - String loopLabel, - String exitLabel ) + PrgSource source, + long basicLineNum, + String loopLabel, + String exitLabel ) { - super( sourceLineNum, basicLineNum, loopLabel, exitLabel ); + super( source, basicLineNum, loopLabel, exitLabel ); } diff --git a/src/jkcemu/programming/basic/target/AC1Target.java b/src/jkcemu/programming/basic/target/AC1Target.java index d04e3b5..238d2c2 100644 --- a/src/jkcemu/programming/basic/target/AC1Target.java +++ b/src/jkcemu/programming/basic/target/AC1Target.java @@ -1,5 +1,5 @@ /* - * (c) 2013 Jens Mueller + * (c) 2013-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -12,11 +12,14 @@ import java.lang.*; import jkcemu.base.EmuSys; import jkcemu.emusys.AC1; +import jkcemu.emusys.ac1_llc2.AbstractSCCHSys; import jkcemu.programming.basic.*; public class AC1Target extends SCCHTarget { + public static final String BASIC_TARGET_NAME = "TARGET_AC1"; + private boolean usesColors; private boolean pixUtilAppended; private boolean xclsAppended; @@ -25,7 +28,15 @@ public class AC1Target extends SCCHTarget public AC1Target() { - reset(); + setNamedValue( "GRAPHICSCREEN", 0 ); + setNamedValue( "BLACK", 0 ); + setNamedValue( "BLUE", 0x04 ); + setNamedValue( "CYAN", 0x06 ); + setNamedValue( "GREEN", 0x02 ); + setNamedValue( "MAGENTA", 0x05 ); + setNamedValue( "RED", 0x01 ); + setNamedValue( "WHITE", 0x07 ); + setNamedValue( "YELLOW", 0x03 ); } @@ -41,7 +52,7 @@ public void appendBssTo( AsmCodeBuf buf ) @Override - public void appendXCLS( AsmCodeBuf buf ) + public void appendXClsTo( AsmCodeBuf buf ) { if( !this.xclsAppended ) { buf.append( "XCLS:" ); @@ -76,7 +87,7 @@ public void appendXCLS( AsmCodeBuf buf ) if( this.xoutchAppended ) { buf.append( "\tJR\tXOUTCH\n" ); } else { - appendXOUTCH( buf ); + appendXOutchTo( buf ); } } this.xclsAppended = true; @@ -85,7 +96,7 @@ public void appendXCLS( AsmCodeBuf buf ) @Override - public void appendHPixel( AsmCodeBuf buf ) + public void appendHPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0040H\n" ); } @@ -132,7 +143,7 @@ public void appendInitTo( AsmCodeBuf buf ) @Override - public void appendWPixel( AsmCodeBuf buf ) + public void appendWPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0080H\n" ); } @@ -145,7 +156,7 @@ public void appendWPixel( AsmCodeBuf buf ) * DE: Hintergrundfarbe */ @Override - public void appendXCOLOR( AsmCodeBuf buf ) + public void appendXColorTo( AsmCodeBuf buf ) { buf.append( "XCOLOR:\tLD\tA,E\n" + "\tSLA\tA\n" @@ -164,7 +175,7 @@ public void appendXCOLOR( AsmCodeBuf buf ) @Override - public void appendXINK( AsmCodeBuf buf ) + public void appendXInkTo( AsmCodeBuf buf ) { buf.append( "XINK:\tLD\tA,(X_MCOV)\n" + "\tAND\t0F0H\n" @@ -185,7 +196,7 @@ public void appendXINK( AsmCodeBuf buf ) * HL: Spalte, >= 0 */ @Override - public void appendXLOCATE( AsmCodeBuf buf ) + public void appendXLocateTo( AsmCodeBuf buf ) { if( this.usesColors ) { buf.append( "XLOCATE:\n" @@ -220,13 +231,13 @@ public void appendXLOCATE( AsmCodeBuf buf ) + "\tLD\t(1846H),A\n" + "\tRET\n" ); } else { - super.appendXLOCATE( buf ); + super.appendXLocateTo( buf ); } } @Override - public void appendXOUTCH( AsmCodeBuf buf ) + public void appendXOutchTo( AsmCodeBuf buf ) { if( !this.xoutchAppended ) { if( this.usesColors ) { @@ -323,10 +334,10 @@ public void appendXOUTCH( AsmCodeBuf buf ) + "\tDEC\tHL\n" + "\tJR\tXOUTC2\n" ); if( !this.xclsAppended ) { - appendXCLS( buf ); + appendXClsTo( buf ); } } else { - super.appendXOUTCH( buf ); + super.appendXOutchTo( buf ); } this.xoutchAppended = true; } @@ -334,19 +345,19 @@ public void appendXOUTCH( AsmCodeBuf buf ) @Override - public void appendXOUTNL( AsmCodeBuf buf ) + public void appendXOutnlTo( AsmCodeBuf buf ) { if( this.usesColors ) { buf.append( "XOUTNL:\tLD\tA,0DH\n" + "\tJP\tXOUTCH\n" ); } else { - super.appendXOUTNL( buf ); + super.appendXOutnlTo( buf ); } } @Override - public void appendXPAPER( AsmCodeBuf buf ) + public void appendXPaperTo( AsmCodeBuf buf ) { buf.append( "XPAPER:\tLD\tA,L\n" + "\tSLA\tA\n" @@ -365,18 +376,83 @@ public void appendXPAPER( AsmCodeBuf buf ) /* - * Zuruecksetzen eines Pixels + * Setzen eines Pixels ohne Beruecksichtigung des eingestellten Stiftes + * bei gleichzeitigem Test, ob dieser schon gesetzt ist * Parameter: * DE: X-Koordinate * HL: Y-Koordinate + * Rueckgabe: + * CY=1: Pixel bereits gesetzt oder ausserhalb des sichtbaren Bereichs */ @Override - public void appendXPRES( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPaintTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPRES:\tCALL\tX_PCK\n" + buf.append( "XPAINT:\tCALL\tX_PST\n" + "\tRET\tC\n" - + "\tCALL\tX_PST\n" ); - if( this.usesX_MPEN ) { + + "\tLD\tA,B\n" + + "\tAND\tC\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tCALL\tXPSET2\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + appendXPSetTo( buf, compiler ); + } + + + /* + * Farbe eines Pixels ermitteln + * Parameter: + * DE: X-Koordinate (0...255) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * HL >= 0: Farbcode des Pixels + * HL=-1: Pixel exisitiert nicht + */ + public void appendXPointTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPOINT:\tCALL\tX_PST\n" + + "\tJR\tC,XPOINT2\n" + // Farbbyte lesen + + "\tIN\tA,(0F0H)\n" + + "\tOR\t04H\n" + + "\tOUT\t(0F0H),A\n" + + "\tLD\tD,(HL)\n" + + "\tAND\t0FBH\n" + + "\tOUT\t(0F0H),A\n" + // Pixel auswerten + + "\tLD\tA,B\n" + + "\tAND\tC\n" + + "\tLD\tA,D\n" + + "\tJR\tNZ,XPOINT1\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "XPOINT1:\n" + + "\tAND\t0FH\n" + + "\tLD\tL,A\n" + + "\tLD\tH,00H\n" + + "\tRET\n" + + "XPOINT2:\n" + + "\tLD\tHL,0FFFFH\n" + + "\tRET\n" ); + appendPixUtilTo( buf ); + } + + + /* + * Zuruecksetzen eines Pixels + * Parameter: + * DE: X-Koordinate + * HL: Y-Koordinate + */ + @Override + public void appendXPResTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPRES:\tCALL\tX_PST\n" + + "\tRET\tC\n" ); + if( this.usesX_M_PEN ) { buf.append( "\tJR\tXPSET1\n" ); } else { buf.append( "\tLD\tA,C\n" @@ -384,25 +460,24 @@ public void appendXPRES( AsmCodeBuf buf, BasicCompiler compiler ) + "\tAND\tB\n" + "\tJR\tXPSET3\n" ); } - appendXPSET( buf, compiler ); + appendXPSetTo( buf, compiler ); } /* - * Setzen eines Pixels + * Setzen eines Pixels unter Beruecksichtigung des eingestellten Stiftes * Parameter: * DE: X-Koordinate * HL: Y-Koordinate */ @Override - public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPSetTo( AsmCodeBuf buf, BasicCompiler compiler ) { if( !this.xpsetAppended ) { - buf.append( "XPSET:\tCALL\tX_PCK\n" - + "\tRET\tC\n" - + "\tCALL\tX_PST\n" ); - if( this.usesX_MPEN ) { - buf.append( "\tLD\tA,(X_MPEN)\n" + buf.append( "XPSET:\tCALL\tX_PST\n" + + "\tRET\tC\n" ); + if( this.usesX_M_PEN ) { + buf.append( "\tLD\tA,(X_M_PEN)\n" + "\tDEC\tA\n" + "\tJR\tZ,XPSET2\n" // Stift 1 (Normal) + "\tDEC\tA\n" @@ -435,7 +510,7 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) buf.append( "\tLD\t(HL),A\n" ); } buf.append( "\tRET\n" ); - appendPixUtil( buf ); + appendPixUtilTo( buf ); this.xpsetAppended = true; } } @@ -452,43 +527,44 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) * HL=-1: Pixel existiert nicht */ @Override - public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPTestTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPTEST:\tCALL\tX_PCK\n" - + "\tJR\tNC,X_PTST1\n" - + "\tLD\tHL,0FFFFH\n" - + "\tRET\n" - + "X_PTST1:\n" - + "\tCALL\tX_PST\n" + buf.append( "XPTEST:\tCALL\tX_PST\n" + + "\tJR\tC,X_PTEST1\n" + "\tLD\tA,B\n" + "\tAND\tC\n" + "\tLD\tHL,0000H\n" + "\tRET\tZ\n" + "\tINC\tHL\n" + + "\tRET\n" + + "X_PTEST1:\n" + + "\tLD\tHL,0FFFFH\n" + "\tRET\n" ); - appendPixUtil( buf ); + appendPixUtilTo( buf ); } - /* - * Target-ID-String - */ @Override - public void appendXTARID( AsmCodeBuf buf ) + public String[] getBasicTargetNames() { - buf.append( "XTARID:\tDB\t\'AC1\'\n" - + "\tDB\t00H\n" ); + return add( super.getBasicTargetNames(), BASIC_TARGET_NAME ); } @Override - public boolean createsCodeFor( EmuSys emuSys ) + public int getCompatibilityLevel( EmuSys emuSys ) { - boolean rv = false; + int rv = 0; if( emuSys != null ) { if( emuSys instanceof AC1 ) { - rv = (((AC1) emuSys).emulates2010Mode() - || ((AC1) emuSys).emulatesSCCHMode()); + rv = 2; + if( ((AC1) emuSys).emulates2010Mode() + || ((AC1) emuSys).emulatesSCCHMode() ) + { + rv = 3; + } + } else if( emuSys instanceof AbstractSCCHSys ) { + rv = 1; } } return rv; @@ -496,65 +572,16 @@ public boolean createsCodeFor( EmuSys emuSys ) @Override - public int getColorBlack() - { - return 0; - } - - - @Override - public int getColorBlue() - { - return 0x04; - } - - - @Override - public int getColorCyan() - { - return 0x06; - } - - - @Override - public int getColorGreen() - { - return 0x02; - } - - - @Override - public int getColorMagenta() + public String getHostName() { - return 0x05; + return "AC1"; } @Override - public int getColorRed() + public int[] getVdipBaseIOAddresses() { - return 0x01; - } - - - @Override - public int getColorWhite() - { - return 0x07; - } - - - @Override - public int getColorYellow() - { - return 0x03; - } - - - @Override - public int getGraphicScreenNum() - { - return 0; + return new int[] { 0xDC, 0xFC, 0x08 }; } @@ -606,45 +633,39 @@ public String toString() /* --- private Methoden --- */ - private void appendPixUtil( AsmCodeBuf buf ) + private void appendPixUtilTo( AsmCodeBuf buf ) { if( !this.pixUtilAppended ) { buf.append( /* - * Pruefen der Parameter + * Pruefen der Parameter und + * ermitteln von Informationen zu einem Pixel + * Parameter: * DE: X-Koordinate (0...127) * HL: Y-Koordinate (0...63) * Rueckgabe: * CY=1: Pixel ausserhalb des gueltigen Bereichs + * B: Aktuelles Bitmuster (gesetzte Pixel) in der Speicherzelle, + * Bitanordnung innerhalb einer Zeichenposition: + * +---+ + * |0 1| + * |2 3| + * +---+ + * C: Bitmuster mit einem gesetzten Bit, + * dass das Pixel in der Speicherzelle beschreibt + * HL: Speicherzelle, in der sich das Pixel befindet */ - "X_PCK:\tLD\tA,D\n" + "X_PST:\tLD\tA,D\n" + "\tOR\tH\n" - + "\tJR\tZ,X_PCK1\n" + "\tSCF\n" - + "\tRET\n" - + "X_PCK1:\tLD\tA,7FH\n" + + "\tRET\tNZ\n" + + "\tLD\tA,7FH\n" + "\tCP\tE\n" + "\tRET\tC\n" + "\tLD\tA,3FH\n" + "\tCP\tL\n" - + "\tRET\n" - /* - * Ermitteln von Informationen zu einem Pixel - * Parameter: - * DE: X-Koordinate (0...127) - * HL: Y-Koordinate (0...63) - * Rueckgabe: - * B: Aktuelles Bitmuster (gesetzte Pixel) in der Speicherzelle, - * Bitanordnung innerhalb einer Zeichenposition: - * +---+ - * |0 1| - * |2 3| - * +---+ - * C: Bitmuster mit einem gesetzten Bit, - * dass das Pixel in der Speicherzelle beschreibt - * HL: Speicherzelle, in der sich das Pixel befindet - */ - + "X_PST:\tLD\tA,01H\n" + + "\tRET\tC\n" + + "\tLD\tA,01H\n" + "\tSRL\tL\n" + "\tJR\tC,X_PST1\n" + "\tSLA\tA\n" @@ -668,6 +689,7 @@ private void appendPixUtil( AsmCodeBuf buf ) + "\tCP\t10H\n" + "\tRET\tNC\n" + "\tLD\tB,A\n" + + "\tOR\tA\n" // CY=0 + "\tRET\n" ); this.pixUtilAppended = true; } diff --git a/src/jkcemu/programming/basic/target/CPMTarget.java b/src/jkcemu/programming/basic/target/CPMTarget.java index a7ecbcf..224e157 100644 --- a/src/jkcemu/programming/basic/target/CPMTarget.java +++ b/src/jkcemu/programming/basic/target/CPMTarget.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -9,13 +9,32 @@ package jkcemu.programming.basic.target; import java.lang.*; +import java.util.Set; import jkcemu.base.EmuSys; +import jkcemu.emusys.NANOS; import jkcemu.emusys.PCM; import jkcemu.programming.basic.*; public class CPMTarget extends AbstractTarget { + public static final String BASIC_TARGET_NAME = "TARGET_CPM"; + + private static final int IOCTB_M_BIN_OFFS = BasicLibrary.IOCTB_DRIVER_OFFS; + private static final int IOCTB_M_ERR_OFFS = IOCTB_M_BIN_OFFS + 1; + private static final int IOCTB_M_POS_OFFS = IOCTB_M_ERR_OFFS + 1; + private static final int IOCTB_FCB_OFFS = IOCTB_M_POS_OFFS + 1; + private static final int IOCTB_FCB_R0_OFFS = IOCTB_FCB_OFFS + 33; + private static final int IOCTB_FNAME_OFFS = IOCTB_FCB_OFFS + 1; + private static final int IOCTB_FTYPE_OFFS = IOCTB_FNAME_OFFS + 8; + private static final int IOCTB_DMA_OFFS = IOCTB_FCB_OFFS + 36; + private static final int IOCTB_CHANNEL_SIZE = IOCTB_DMA_OFFS + 128; + + private boolean usesFileRead; + private boolean usesFileWrite; + private boolean usesX_M_INKEY; + + public CPMTarget() { // leer @@ -23,31 +42,853 @@ public CPMTarget() @Override - public void appendExit( AsmCodeBuf buf ) + public void appendBssTo( AsmCodeBuf buf ) + { + super.appendBssTo( buf ); + if( this.usesFileRead || this.usesFileWrite ) { + buf.append( "X_CPM_FILE_M_DRIVE:\tDS\t1\n" + + "X_CPM_FILE_M_FNAME:\tDS\t2\n" ); + } + if( this.usesX_M_INKEY ) { + buf.append( "X_M_INKEY:\tDS\t1\n" ); + } + } + + + @Override + public void appendDataTo( AsmCodeBuf buf ) + { + if( this.usesFileRead || this.usesFileWrite ) { + buf.append( "X_CPM_FILE_DATA_VALID_CHR:\n" + + "\tDB\t21H,23H,24H,25H,26H,27H,28H,29H,2BH\n" + + "\tDB\t2DH,3DH,5EH,5FH,60H,7BH,7DH,7EH,00H\n" ); + } + if( this.usesFileRead ) { + buf.append( "X_CPM_FILE_DATA_RDPTRS:\n" + + "\tDW\tX_CPM_FILE_CLOSE\n" + + "\tDW\tX_CPM_FILE_EOF\n" + + "\tDW\tX_CPM_FILE_READ\n" ); + } + } + + + @Override + public void appendExitTo( AsmCodeBuf buf ) { buf.append( "\tJP\t0000H\n" ); } @Override - public void appendInput( + public void appendFileHandler( BasicCompiler compiler ) + { + boolean done = false; + AsmCodeBuf buf = compiler.getCodeBuf(); + Set libItems = compiler.getLibItems(); + AbstractTarget target = compiler.getTarget(); + + /* + * CP/M-Datei-Handler testen + * + * Parameter: + * (IO_M_NAME): Zeiger auf Geraete-/Dateiname + * (IO_M_CADDR): Anfangsadresse des Kanalzeigerfeldes + * (IO_M_ACCESS): Zugriffsmode + * Rueckgabewert: + * CY=1: Handler ist nicht zustaendig + * CY=0: Handler ist zustaendig + * -> keine weiteren Handler testen + */ + buf.append( getFileHandlerLabel() ); + buf.append( ":\n" ); + Set modes = compiler.getIODriverModes( + BasicCompiler.IODriver.FILE ); + if( modes != null ) { + if( !modes.isEmpty() ) { + boolean txtAppend = modes.contains( BasicLibrary.IOMODE_TXT_APPEND ); + boolean txtOutput = modes.contains( BasicLibrary.IOMODE_TXT_OUTPUT ); + boolean txtInput = (modes.contains( BasicLibrary.IOMODE_TXT_INPUT ) + || modes.contains( BasicLibrary.IOMODE_TXT_DEFAULT )); + boolean binAppend = modes.contains( BasicLibrary.IOMODE_BIN_APPEND ); + boolean binOutput = modes.contains( BasicLibrary.IOMODE_BIN_OUTPUT ); + boolean binInput = (modes.contains( BasicLibrary.IOMODE_BIN_INPUT ) + || modes.contains( BasicLibrary.IOMODE_BIN_DEFAULT )); + + // Geraetename am Dateianfang testen + buf.append( "\tLD\tDE,(IO_M_NAME)\n" + + "\tLD\tA,(DE)\n" + + "\tCALL\tC_UPR\n" + + "\tCP\t41H\n" // A + + "\tJR\tC,X_CPM_FILE_HANDLER2\n" + + "\tCP\t51H\n" // Q + + "\tJR\tNC,X_CPM_FILE_HANDLER2\n" + + "\tLD\tB,A\n" // Laufwerksbuchstabe + + "\tINC\tDE\n" + + "\tLD\tA,(DE)\n" + // Geraetename oder User-Nummer? + + "\tCP\t30H\n" // 0 + + "\tJR\tC,X_CPM_FILE_HANDLER2\n" + + "\tCP\t3AH\n" + + "\tJR\tZ,X_CPM_FILE_HANDLER5\n" // Geraetename ohne User-Nr + + "\tJR\tNC,X_CPM_FILE_HANDLER2\n" + // User-Nr parsen + + "\tSUB\t30H\n" + + "X_CPM_FILE_HANDLER1:\n" + + "\tLD\tC,A\n" + + "\tINC\tDE\n" + + "\tLD\tA,(DE)\n" + + "\tSUB\t30H\n" + + "\tJR\tC,X_CPM_FILE_HANDLER2\n" + + "\tCP\t0AH\n" + + "\tJR\tZ,X_CPM_FILE_HANDLER6\n" // Geraetename mit User-Nr + + "\tJR\tNC,X_CPM_FILE_HANDLER2\n" + + "\tPUSH\tAF\n" // C=(C*10)+A + + "\tLD\tA,C\n" + + "\tADD\tA,A\n" + + "\tADD\tA,A\n" + + "\tADD\tA,C\n" + + "\tADD\tA,A\n" + + "\tLD\tC,A\n" + + "\tPOP\tAF\n" + + "\tADD\tA,C\n" + + "\tJR\tNC,X_CPM_FILE_HANDLER1\n" // naechste Ziffer + // Ueberlauf in User-Nr. -> User-Nr. ungueltig + + "X_CPM_FILE_HANDLER2:\n" + /* + * kein passendes Laufwerk oder ungueltige User-Nr + * -> pruefen, ob ein Geraetename vorhanden ist + */ + + "\tLD\tDE,(IO_M_NAME)\n" + + "X_CPM_FILE_HANDLER3:\n" + + "\tLD\tA,(DE)\n" + + "\tOR\tA\n" + + "\tJR\tZ,X_CPM_FILE_HANDLER4\n" // nur Dateiname + + "\tINC\tDE\n" + + "\tCP\t3AH\n" + + "\tJR\tNZ,X_CPM_FILE_HANDLER3\n" + + "\tSCF\n" // anderes Geraet + + "\tRET\n" + /* + * nur Dateiname gefunden + * -> auf dem aktuellen Laufwerk oeffnen + */ + + "X_CPM_FILE_HANDLER4:\n" + + "\tXOR\tA\n" // aktuellen LW + + "\tLD\t(X_CPM_FILE_M_DRIVE),A\n" + + "\tLD\tHL,(IO_M_NAME)\n" + + "\tLD\t(X_CPM_FILE_M_FNAME),HL\n" + + "\tJR\tX_CPM_FILE_HANDLER7\n" + /* + * Geraetename ohne User-Nr. gefunden, + * DE: Dateiname - 1, B: Laufwerksbuchstabe + */ + + "X_CPM_FILE_HANDLER5:\n" + + "\tLD\tA,B\n" + + "\tSUB\t40H\n" // LW A: 1 usw. + + "\tLD\t(X_CPM_FILE_M_DRIVE),A\n" + + "\tINC\tDE\n" + + "\tLD\t(X_CPM_FILE_M_FNAME),DE\n" + + "\tJR\tX_CPM_FILE_HANDLER7\n" + /* + * Geraetename mit User-Nr. gefunden, + * DE: Dateiname - 1, B: Laufwerksbuchstabe, C: User-Nr + */ + + "X_CPM_FILE_HANDLER6:\n" + + "\tLD\tA,B\n" + + "\tSUB\t40H\n" // LW A: 1 usw. + + "\tLD\t(X_CPM_FILE_M_DRIVE),A\n" + + "\tINC\tDE\n" + + "\tLD\t(X_CPM_FILE_M_FNAME),DE\n" + // User-Nr. setzen + + "\tLD\tE,C\n" + + "\tLD\tC,20H\n" // F_USERNUM + + "\tCALL\t0005H\n" // User-Nr. setzen + // FCB fuellen + + "X_CPM_FILE_HANDLER7:\n" + + "\tCALL\tX_CPM_FILE_FILL_FCB\n" + + "\tCCF\n" + + "\tRET\tNC\n" // unglueltiger Dateiname + // Betriebsart pruefen + + "\tLD\tHL,IO_M_ACCESS\n" ); + if( txtInput || binInput) { + buf.append_LD_A_n( BasicLibrary.IOMODE_DEFAULT_MASK + | BasicLibrary.IOMODE_INPUT_MASK ); + buf.append( "\tAND\t(HL)\n" + + "\tJR\tNZ,X_CPM_FILE_HANDLER8\n" ); + } + if( txtOutput || binOutput ) { + buf.append_LD_A_n( BasicLibrary.IOMODE_OUTPUT_MASK ); + buf.append( "\tAND\t(HL)\n" + + "\tJR\tNZ,X_CPM_FILE_HANDLER10\n" ); + } + if( txtAppend || binAppend ) { + buf.append_LD_A_n( BasicLibrary.IOMODE_APPEND_MASK ); + buf.append( "\tAND\t(HL)\n" + + "\tJR\tNZ,X_CPM_FILE_HANDLER13\n" ); + } + BasicLibrary.appendSetErrorIOMode( compiler ); + buf.append( "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + /* + * Datei zum Lesen oeffnen + */ + if( txtInput || binInput ) { + buf.append( "X_CPM_FILE_HANDLER8:\n" + + "\tLD\tC,0FH\n" // F_OPEN + + "\tCALL\tX_CPM_FILE_BDOS\n" + + "\tCP\t04H\n" + + "\tJR\tC,X_CPM_FILE_HANDLER9\n" ); + BasicLibrary.appendSetErrorFileNotFound( compiler ); + buf.append( "\tOR\tA\n" // CY=0 + + "\tRET\n" + + "X_CPM_FILE_HANDLER9:\n" + // Kanalzeigerfeld fuellen + + "\tCALL\tX_CPM_FILE_CLEAR_CHANNEL\n" + + "\tLD\tDE,(IO_M_CADDR)\n" + + "\tLD\tHL,X_CPM_FILE_DATA_RDPTRS\n" + + "\tLD\tBC,0008H\n" + + "\tLDIR\n" + // Position im Puffer auf FFh setzen + + "\tLD\tHL,(IO_M_CADDR)\n" ); + buf.append_LD_DE_nn( IOCTB_M_POS_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\t(HL),0FFH\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + this.usesFileRead = true; + } + /* + * Datei zum Schreiben oeffnen + */ + if( txtOutput || binOutput ) { + buf.append( "X_CPM_FILE_HANDLER10:\n" + + "\tLD\tC,13H\n" // F_DELETE + + "\tCALL\tX_CPM_FILE_BDOS\n" ); + } + if( txtOutput || binOutput || txtAppend || binAppend ) { + buf.append( "X_CPM_FILE_HANDLER11:\n" + + "\tCALL\tX_CPM_FILE_FILL_FCB\n" + + "\tCCF\n" + + "\tRET\tNC\n" + + "\tLD\tC,16H\n" // F_MAKE + + "\tCALL\tX_CPM_FILE_BDOS\n" + + "\tCP\t04H\n" + + "\tJR\tC,X_CPM_FILE_HANDLER12\n" ); + BasicLibrary.appendSetErrorDirFull( compiler ); + buf.append( "\tOR\tA\n" // CY=0 + + "\tRET\n" + // Kanalzeigerfeld fuellen + + "X_CPM_FILE_HANDLER12:\n" + + "\tCALL\tX_CPM_FILE_CLEAR_CHANNEL\n" + + "\tLD\tHL,(IO_M_CADDR)\n" + + "\tPUSH\tHL\n" + + "\tLD\t(HL),LOW(X_CPM_FILE_CLOSE)\n" + + "\tINC\tHL\n" + + "\tLD\t(HL),HIGH(X_CPM_FILE_CLOSE)\n" + + "\tPOP\tHL\n" ); + buf.append_LD_DE_nn( BasicLibrary.IOCTB_WRITE_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\t(HL),LOW(X_CPM_FILE_WRITE)\n" + + "\tINC\tHL\n" + + "\tLD\t(HL),HIGH(X_CPM_FILE_WRITE)\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + this.usesFileWrite = true; + } + /* + * Datei zum Anhaengen oeffnen + */ + if( txtAppend || binAppend ) { + buf.append( "X_CPM_FILE_HANDLER13:\n" + + "\tLD\tC,0FH\n" // F_OPEN + + "\tCALL\tX_CPM_FILE_BDOS\n" + + "\tCP\t04H\n" + + "\tJR\tNC,X_CPM_FILE_HANDLER11\n" ); + if( txtAppend && binAppend ) { + buf.append( "\tLD\tDE,(IO_M_CADDR)\n" ); + buf.append_LD_HL_nn( IOCTB_M_BIN_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\tA,(HL)\n" + + "\tJR\tNZ,X_CPM_FILE_HANDLER18\n" ); + } + if( txtAppend ) { + /* + * Dateiende der Textdatei ermitteln, + * Dazu wird die Datei mit F_READRAND gelesen, + * da diese die Dateiposition fuer den sequenziellen Zugriff + * auf den mit F_READRAND gesetzten Record setzt + * und somit der naechste sequenzielle Schreibzugriff + * den gleichen Record betrifft. + */ + buf.append( "X_CPM_FILE_HANDLER14:\n" + + "\tCALL\tX_CPM_FILE_SET_DMA_ADDR\n" + + "\tLD\tC,21H\n" // F_READRAND + + "\tCALL\tX_CPM_FILE_BDOS\n" + + "\tOR\tA\n" + + "\tJR\tZ,X_CPM_FILE_HANDLER15\n" // Record gelesen + + "\tCP\t07H\n" + + "\tJR\tC,X_CPM_FILE_HANDLER12\n" // EOF erreicht + + "\tJR\tX_CPM_FILE_HANDLER19\n" // Fehler + // Byte 00 oder 1A suchen + + "X_CPM_FILE_HANDLER15:\n" + + "\tLD\tDE,(IO_M_CADDR)\n" ); + buf.append_LD_HL_nn( IOCTB_DMA_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\tC,00H\n" + + "X_CPM_FILE_HANDLER16:\n" + + "\tLD\tA,(HL)\n" + + "\tOR\tA\n" + + "\tJR\tZ,X_CPM_FILE_HANDLER17\n" + + "\tCP\t1AH\n" + + "\tJR\tZ,X_CPM_FILE_HANDLER17\n" + + "\tINC\tHL\n" + + "\tINC\tC\n" + + "\tJP\tP,X_CPM_FILE_HANDLER16\n" + // Record Counter inkremtieren + + "\tLD\tDE,(IO_M_CADDR)\n" ); + buf.append_LD_HL_nn( IOCTB_FCB_R0_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\tA,01H\n" + + "\tADD\tA,(HL)\n" + + "\tLD\t(HL),A\n" + + "\tJR\tNC,X_CPM_FILE_HANDLER14\n" + + "\tINC\tHL\n" + + "\tINC\t(HL)\n" + + "\tJR\tX_CPM_FILE_HANDLER14\n" + // Dateiendezeichen gefunden + + "X_CPM_FILE_HANDLER17:\n" + + "\tPUSH\tBC\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_CPM_FILE_HANDLER12\n" + + "\tPOP\tDE\n" ); + buf.append_LD_HL_nn( IOCTB_M_POS_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tPOP\tBC\n" + + "\tLD\t(HL),C\n" + + "\tRET\n" ); + } + if( binAppend ) { + // Dateiende der Binaerdatei ermitteln + buf.append( "X_CPM_FILE_HANDLER18:\n" + + "\tCALL\tX_CPM_FILE_SET_DMA_ADDR\n" + + "\tLD\tC,14H\n" // F_READ + + "\tCALL\tX_CPM_FILE_BDOS\n" + + "\tOR\tA\n" + + "\tJR\tZ,X_CPM_FILE_HANDLER18\n" // Record gelesen + + "\tCP\t01H\n" + + "\tJR\tZ,X_CPM_FILE_HANDLER12\n" ); // EOF erreicht + } + // Fehler + buf.append( "X_CPM_FILE_HANDLER19:\n" + + "\tINC\tA\n" + + "\tJR\tNZ,X_CPM_FILE_HANDLER20\n" ); + BasicLibrary.appendSetErrorHardware( compiler ); + buf.append( "\tOR\tA\n" // CY=0 + + "\tRET\n" + + "X_CPM_FILE_HANDLER20:\n" ); + BasicLibrary.appendSetError( compiler ); + buf.append( "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + } + + /* + * CLOSE-Routine + * Parameter: + * DE: Anfangsadresse Kanalzeigerfeld + */ + buf.append( "X_CPM_FILE_CLOSE:\n" ); + if( txtOutput || txtAppend || binOutput || binAppend ) { + if( txtInput || binInput ) { + // Datei zum Schreiben geoeffnet? + buf.append_LD_HL_nn( BasicLibrary.IOCTB_WRITE_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\tA,(HL)\n" + + "\tINC\tHL\n" + + "\tOR\tA\n" + + "\tJR\tZ,X_CPM_FILE_CLOSE2\n" ); + } + if( txtOutput || txtAppend ) { + /* + * bei Textdateien, die zum Schreiben geoeffnet sind, + * Dateiendekennung 1Ah anhaengen + */ + if( binOutput || binAppend ) { + buf.append_LD_HL_nn( IOCTB_M_BIN_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\tA,(HL)\n" + + "\tOR\tA\n" + + "\tJR\tNZ,X_CPM_FILE_CLOSE1\n" ); + } + buf.append( "\tPUSH\tDE\n" + + "\tLD\tA,1AH\n" + + "\tCALL\tX_CPM_FILE_WRITE\n" + + "\tPOP\tDE\n" + + "X_CPM_FILE_CLOSE1:\n" ); + } + buf.append_LD_HL_nn( IOCTB_M_POS_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\tA,(HL)\n" + + "\tOR\tA\n" + + "\tCALL\tNZ,X_CPM_FILE_WRITE2\n" + + "X_CPM_FILE_CLOSE2:\n" ); + } + buf.append( "\tLD\tC,10H\n" // F_CLOSE + + "\tCALL\tX_CPM_FILE_BDOS\n" + + "\tCP\t04H\n" + + "\tRET\tC\n" ); + BasicLibrary.appendSetError( compiler ); + buf.append( "\tRET\n" ); + + /* + * EOF-Routine + * Parameter: + * DE: Anfangsadresse Kanalzeigerfeld + * Rueckgabewert: + * HL=-1: EOF erreicht + */ + if( txtInput || binInput ) { + buf.append( "X_CPM_FILE_EOF:\n" ); + buf.append_LD_HL_nn( IOCTB_M_ERR_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\tA,(HL)\n" + + "\tLD\tHL,0000H\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" + + "\tDEC\tHL\n" + + "\tRET\n" ); + } + + /* + * READ-Routine + * Parameter: + * DE: Anfangsadresse Kanalzeigerfeld + * Rueckgabewert: + * A: gelesenes Zeichen oder 0 bei EOF bzw. vorherigem Fehler + */ + if( txtInput || binInput ) { + buf.append( "X_CPM_FILE_READ:\n" ); + buf.append_LD_HL_nn( IOCTB_M_ERR_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\tA,(HL)\n" + + "\tCALL\tX_CPM_FILE_READ_CHECK_ERR\n" + + "\tLD\tA,00H\n" + + "\tRET\tC\n" + + "\tINC\tHL\n" // HL: IOCTB_M_POS + + "\tLD\tA,(HL)\n" + + "\tOR\tA\n" + + "\tJP\tP,X_CPM_FILE_READ1\n" + // naechstes Segment lesen + + "\tPUSH\tDE\n" + + "\tPUSH\tHL\n" + + "\tCALL\tX_CPM_FILE_SET_DMA_ADDR\n" + + "\tLD\tC,14H\n" // F_READ + + "\tCALL\tX_CPM_FILE_BDOS\n" + + "\tPOP\tHL\n" + + "\tPOP\tDE\n" + + "\tDEC\tHL\n" // HL: IOCTB_M_ERR + + "\tLD\t(HL),A\n" + + "\tCALL\tX_CPM_FILE_READ_CHECK_ERR\n" + + "\tLD\tA,00H\n" + + "\tRET\tC\n" + + "\tINC\tHL\n" // HL: IOCTB_M_POS + + "X_CPM_FILE_READ1:\n" + + "\tINC\tA\n" + + "\tLD\t(HL),A\n" + + "\tDEC\tA\n" ); + buf.append_LD_HL_nn( IOCTB_DMA_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tADD\tA,L\n" + + "\tLD\tL,A\n" + + "\tLD\tA,H\n" + + "\tADC\tA,00H\n" + + "\tLD\tH,A\n" + + "\tLD\tA,(HL)\n" ); + if( txtInput ) { + buf.append( "\tOR\tA\n" // Dateiende-Byte? + + "\tJR\tZ,X_CPM_FILE_READ2\n" + + "\tCP\t1AH\n" + + "\tRET\tNZ\n" + + "X_CPM_FILE_READ2:\n" ); + if( binInput ) { + buf.append( "\tLD\tB,A\n" ); + buf.append_LD_HL_nn( IOCTB_M_BIN_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tOR\tA\n" + + "\tLD\tA,B\n" + + "\tRET\tNZ\n" + + "\tINC\tHL\n" ); // HL: IOCTB_M_ERR_OFFS + } else { + buf.append_LD_HL_nn( IOCTB_M_ERR_OFFS ); + buf.append( "\tADD\tHL,DE\n" ); + } + buf.append( "\tLD\tA,01H\n" // BDOS-Code fuer EOF + + "\tLD\t(HL),A\n" + + "\tCALL\tX_CPM_FILE_READ_CHECK_ERR\n" + + "\tXOR\tA\n" ); + } + buf.append( "\tRET\n" + /* + * F_READ-Fehlercode auswerten und BASIC-Fehlervariablen setzen + * DE und HL bleiben erhalten + * Parameter: + * A: Rueckgabewert der Funktion F_READ + * Rueckgabewert: + * CY=0: OK + * CY=1: Fehler, BASIC-Fehlervariablen gesetzt + */ + + "X_CPM_FILE_READ_CHECK_ERR:\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" ); // CY=0 + if( compiler.usesErrorVars() ) { + buf.append( "\tPUSH\tHL\n" + + "\tCP\t01H\n" + + "\tJR\tZ,X_CPM_FILE_READ_CHECK_ERR1\n" + + "\tCP\t0AH\n" + + "\tJR\tZ,X_CPM_FILE_READ_CHECK_ERR2\n" + + "\tCP\t0FFH\n" + + "\tJR\tZ,X_CPM_FILE_READ_CHECK_ERR3\n" ); + BasicLibrary.appendSetError( compiler ); + buf.append( "\tJR\tX_CPM_FILE_READ_CHECK_ERR4\n" + + "X_CPM_FILE_READ_CHECK_ERR1:\n" ); + BasicLibrary.appendSetErrorEOF( compiler ); + buf.append( "\tJR\tX_CPM_FILE_READ_CHECK_ERR4\n" + + "X_CPM_FILE_READ_CHECK_ERR2:\n" ); + BasicLibrary.appendSetErrorMediaChanged( compiler ); + buf.append( "\tJR\tX_CPM_FILE_READ_CHECK_ERR4\n" + + "X_CPM_FILE_READ_CHECK_ERR3:\n" ); + BasicLibrary.appendSetErrorHardware( compiler ); + buf.append( "X_CPM_FILE_READ_CHECK_ERR4:\n" + + "\tPOP\tHL\n" ); + } + buf.append( "\tSCF\n" + + "\tRET\n" ); + } + + /* + * WRITE-Routine + * Parameter: + * (IO_M_CADDR): Anfangsadresse Kanalzeigerfeld + * A: zu schreibendes Byte + */ + buf.append( "X_CPM_FILE_WRITE:\n" ); + if( txtOutput || txtAppend || binOutput || binAppend ) { + buf.append( "\tLD\tB,A\n" + + "\tLD\tDE,(IO_M_CADDR)\n" ); + buf.append_LD_HL_nn( IOCTB_M_ERR_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\tA,(HL)\n" + + "\tCALL\tX_CPM_FILE_WRITE_CHECK_ERR\n" + + "\tRET\tC\n" + + "\tINC\tHL\n" // HL: IOCTB_M_POS_OFFS + + "\tLD\tC,(HL)\n" + + "\tPUSH\tHL\n" ); + buf.append_LD_HL_nn( IOCTB_DMA_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\tA,L\n" + + "\tADD\tA,C\n" + + "\tLD\tL,A\n" + + "\tLD\tA,H\n" + + "\tADC\tA,00H\n" + + "\tLD\tH,A\n" + + "\tLD\t(HL),B\n" + + "\tPOP\tHL\n" + + "\tINC\tC\n" + + "\tLD\t(HL),C\n" + + "\tRET\tP\n" + // Segment voll -> auf Datentraeger schreiben + + "\tLD\t(HL),00H\n" + + "X_CPM_FILE_WRITE2:\n" + + "\tCALL\tX_CPM_FILE_SET_DMA_ADDR\n" + + "\tLD\tC,15H\n" // F_WRITE + + "\tJP\tX_CPM_FILE_BDOS\n" ); + buf.append_LD_HL_nn( IOCTB_M_ERR_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tLD\t(HL),A\n" + // direkt weiter mit X_CPM_FILE_WRITE_CHECK_ERR + /* + * F_WRITE-Fehlercode auswerten und BASIC-Fehlervariablen setzen + * DE und HL bleiben erhalten + * Parameter: + * A: Rueckgabewert der Funktion F_WRITE + * Rueckgabewert: + * CY=0: OK + * CY=1: Fehler, BASIC-Fehlervariablen gesetzt + */ + + "X_CPM_FILE_WRITE_CHECK_ERR:\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" ); // CY=0 + if( compiler.usesErrorVars() ) { + buf.append( "\tPUSH\tHL\n" + + "\tCP\t01H\n" + + "\tJR\tZ,X_CPM_FILE_WRITE_CHECK_ERR1\n" + + "\tCP\t02H\n" + + "\tJR\tZ,X_CPM_FILE_WRITE_CHECK_ERR2\n" + + "\tCP\t0AH\n" + + "\tJR\tZ,X_CPM_FILE_WRITE_CHECK_ERR3\n" + + "\tCP\t0FFH\n" + + "\tJR\tZ,X_CPM_FILE_WRITE_CHECK_ERR4\n" ); + BasicLibrary.appendSetError( compiler ); + buf.append( "\tJR\tX_CPM_FILE_WRITE_CHECK_ERR5\n" + + "X_CPM_FILE_WRITE_CHECK_ERR1:\n" ); + BasicLibrary.appendSetErrorDirFull( compiler ); + buf.append( "\tJR\tX_CPM_FILE_WRITE_CHECK_ERR5\n" + + "X_CPM_FILE_WRITE_CHECK_ERR2:\n" ); + BasicLibrary.appendSetErrorDiskFull( compiler ); + buf.append( "\tJR\tX_CPM_FILE_WRITE_CHECK_ERR5\n" + + "X_CPM_FILE_WRITE_CHECK_ERR3:\n" ); + BasicLibrary.appendSetErrorMediaChanged( compiler ); + buf.append( "\tJR\tX_CPM_FILE_WRITE_CHECK_ERR5\n" + + "X_CPM_FILE_WRITE_CHECK_ERR4:\n" ); + BasicLibrary.appendSetErrorHardware( compiler ); + buf.append( "X_CPM_FILE_WRITE_CHECK_ERR5:\n" + + "\tPOP\tHL\n" ); + } + buf.append( "\tSCF\n" + + "\tRET\n" ); + } + + /* + * Kanalzeigerfeld leeren + * Der FCB ist zu dem Zeitpunkt schon gefuellt und wird nicht geleert. + * + * Parameter: + * (IO_M_CADDR): Anfangsadresse Kanalzeigerfeld + */ + buf.append( "X_CPM_FILE_CLEAR_CHANNEL:\n" + + "\tLD\tHL,(IO_M_CADDR)\n" + + "\tLD\tB," ); + buf.appendHex2( IOCTB_FCB_OFFS ); + buf.append( "\n" + + "\tXOR\tA\n" + + "X_CPM_FILE_CLEAR_CHANNEL1:\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tDJNZ\tX_CPM_FILE_CLEAR_CHANNEL1\n" + + "\tRET\n" + + /* + * FCB fuellen + * Parameter: + * (X_CPM_FILE_M_FNAME): Zeiger auf Dateiname + * (X_CPM_FILE_M_DRIVE): Laufwerk (0: aktuelles, 1: A, usw.) + * Rueckgabe: + * CY=0: OK + * CY=1: ungueltiger Name -> Fehlervariablen gesetzt + */ + + "X_CPM_FILE_FILL_FCB:\n" + + "\tLD\tHL,(IO_M_CADDR)\n" ); + buf.append_LD_BC_nn( IOCTB_FCB_OFFS ); + buf.append( "\tADD\tHL,BC\n" + + "\tLD\tA,(X_CPM_FILE_M_DRIVE)\n" + + "\tLD\t(HL),A\n" // Laufwerk + + "\tINC\tHL\n" + /* + * Dateinamesfeld mit Leerzeichen + * und der Rest mit Null-Bytes vorbelegen + */ + + "\tPUSH\tHL\n" + + "\tLD\tA,20H\n" + + "\tLD\tB,0BH\n" + + "X_CPM_FILE_FILL_FCB1:\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tDJNZ\tX_CPM_FILE_FILL_FCB1\n" + + "\tXOR\tA\n" + + "\tLD\tB,18H\n" + + "X_CPM_FILE_FILL_FCB2:\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tDJNZ\tX_CPM_FILE_FILL_FCB2\n" + + "\tPOP\tHL\n" + // Dateiname muss mindestens ein Zeichen haben + + "\tLD\tDE,(X_CPM_FILE_M_FNAME)\n" + + "\tLD\tA,(DE)\n" + + "\tOR\tA\n" + + "\tJR\tZ,X_CPM_FILE_FILL_FCB6\n" + // Dateiname uebertragen + + "\tLD\tB,08H\n" + + "X_CPM_FILE_FILL_FCB3:\n" + + "\tLD\tA,(DE)\n" + + "\tINC\tDE\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" // Dateiname ohne Endung + + "\tCP\t2EH\n" + + "\tJR\tZ,X_CPM_FILE_FILL_FCB4\n" + + "\tCALL\tX_CPM_FILE_TO_VALID_CHR\n" + + "\tJR\tC,X_CPM_FILE_FILL_FCB6\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tDJNZ\tX_CPM_FILE_FILL_FCB3\n" + // 8 Zeichen uebertragen + + "\tLD\tA,(DE)\n" + + "\tINC\tDE\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" // Dateiname ohne Endung + + "\tCP\t2EH\n" + + "\tJR\tNZ,X_CPM_FILE_FILL_FCB6\n" + + "X_CPM_FILE_FILL_FCB4:\n" + // Dateierweiterung uebertragen + + "\tLD\tHL,(IO_M_CADDR)\n" ); + buf.append_LD_BC_nn( IOCTB_FTYPE_OFFS ); + buf.append( "\tADD\tHL,BC\n" + + "\tLD\tB,03H\n" + + "X_CPM_FILE_FILL_FCB5:\n" + + "\tLD\tA,(DE)\n" + + "\tINC\tDE\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" + + "\tCALL\tX_CPM_FILE_TO_VALID_CHR\n" + + "\tJR\tC,X_CPM_FILE_FILL_FCB6\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tDJNZ\tX_CPM_FILE_FILL_FCB5\n" + + "\tLD\tA,(DE)\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" + + "X_CPM_FILE_FILL_FCB6:\n" ); + BasicLibrary.appendSetErrorInvalidFileName( compiler ); + buf.append( "\tSCF\n" + + "\tRET\n" + + /* + * Zeichen auf Gueltigkeit in einem Dateinamen pruefen + * und in Grossbuchstaben umwandeln + * Parameter: + * A: Zeichen + * Rueckgabe: + * CY=0: OK + * CY=1: ungueltiges Zeichen + */ + + "X_CPM_FILE_TO_VALID_CHR:\n" + // auf Ziffern pruefen + + "\tCP\t30H\n" + + "\tJR\tC,X_CPM_FILE_TO_VALID_CHR1\n" + + "\tCP\t3AH\n" + + "\tCCF\n" + + "\tRET\tNC\n" + // auf @ und Grossbuchstaben pruefen + + "\tCP\t40H\n" + + "\tJR\tC,X_CPM_FILE_TO_VALID_CHR1\n" + + "\tCP\t5BH\n" + + "\tCCF\n" + + "\tRET\tNC\n" + // auf Kleinbuchstaben pruefen + + "\tCP\t61H\n" + + "\tJR\tC,X_CPM_FILE_TO_VALID_CHR1\n" + + "\tCP\t7BH\n" + + "\tJR\tNC,X_CPM_FILE_TO_VALID_CHR1\n" + + "\tSUB\t20H\n" + + "\tRET\n" + // auf Sonderzeichen pruefen + + "X_CPM_FILE_TO_VALID_CHR1:\n" + + "\tLD\tHL,X_CPM_FILE_DATA_VALID_CHR\n" + + "\tLD\tB,A\n" + + "X_CPM_FILE_TO_VALID_CHR2:\n" + + "\tLD\tA,(HL)\n" + + "\tINC\tHL\n" + + "\tOR\tA\n" + + "\tSCF\n" + + "\tRET\tZ\n" + + "\tCP\tB\n" + + "\tJR\tNZ,X_CPM_FILE_TO_VALID_CHR2\n" + + "\tRET\n" + + /* + * BDOS-Aufruf mit FCB-Adresse in DE + * Paramater: + * (IO_M_CADDR): Anfangsadresse Kanalzeigerfeld + * C: Nummer der BDOS-Funktion + */ + + "X_CPM_FILE_BDOS:\n" + + "\tLD\tHL,(IO_M_CADDR)\n" ); + buf.append_LD_DE_nn( IOCTB_FCB_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tEX\tDE,HL\n" + + "\tJP\t0005H\n" + /* + * Setzen der DMA-Adresse im BDOS + * Parameter: + * (IO_M_CADDR): Anfangsadresse Kanalzeigerfeld + */ + + "X_CPM_FILE_SET_DMA_ADDR:\n" + + "\tLD\tHL,(IO_M_CADDR)\n" ); + buf.append_LD_DE_nn( IOCTB_DMA_OFFS ); + buf.append( "\tADD\tHL,DE\n" + + "\tEX\tDE,HL\n" + + "\tLD\tC,1AH\n" + + "\tJP\t0005H\n" ); + compiler.addLibItem( BasicLibrary.LibItem.C_UPR ); + done = true; + } + } + if( !done ) { + buf.append( "\tSCF\n" + + "\tRET\n" ); + } + } + + + public void appendInitTo( AsmCodeBuf buf ) + { + if( this.usesX_M_INKEY ) { + buf.append( "\tXOR\tA\n" + + "\tLD\t(X_M_INKEY),A\n" ); + } + } + + + @Override + public void appendInputTo( AsmCodeBuf buf, boolean xckbrk, boolean xinkey, boolean xinch, boolean canBreakOnInput ) { - if( xinch ) { - buf.append( "XINCH:\tCALL\tXINKEY\n" + if( xckbrk ) { + if( xinch ) { + buf.append( "XINCH:\tXOR\tA\n" + + "\tLD\t(X_M_INKEY),A\n" + + "XINCH1:\tCALL\tXINKE1\n" + + "\tOR\tA\n" + + "\tJR\tZ,XINCH1\n" + + "\tRET\n" ); + xinkey = true; + } + buf.append( "XCKBRK:\tLD\tC,06H\n" + + "\tLD\tE,0FFH\n" + + "\tCALL\t0005H\n" + + "\tCP\t03H\n" ); + if( xinkey ) { + buf.append( "\tJR\tZ,XBREAK\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" + + "\tLD\t(X_M_INKEY),A\n" + + "\tRET\n" + + "XINKEY:\tLD\tA,(X_M_INKEY)\n" + + "\tOR\tA\n" + + "\tJR\tZ,XINKE1\n" + + "\tPUSH\tAF\n" + + "\tXOR\tA\n" + + "\tLD\t(X_M_INKEY),A\n" + + "\tPOP\tAF\n" + + "\tRET\n" + + "XINKE1:\tLD\tC,06H\n" + + "\tLD\tE,0FFH\n" + + "\tCALL\t0005H\n" + + "\tCP\t03H\n" ); + this.usesX_M_INKEY = true; + } + buf.append( "\tRET\tNZ\n" + + "\tJR\tXBREAK\n" ); + } else { + if( xinch ) { + buf.append( "XINCH:\tCALL\tXINKEY\n" + "\tOR\tA\n" + "\tJR\tZ,XINCH\n" + "\tRET\n" ); - xinkey = true; - } - if( xckbrk ) { - buf.append( "XCKBRK:\n" ); - } - if( xckbrk || xinkey) { + xinkey = true; + } buf.append( "XINKEY:\tLD\tC,06H\n" + "\tLD\tE,0FFH\n" ); if( canBreakOnInput ) { @@ -55,15 +896,14 @@ public void appendInput( + "\tCP\t03H\n" + "\tJR\tZ,XBREAK\n" + "\tRET\n" ); - } else { - buf.append( "\tJP\t0005H\n" ); } + buf.append( "\tJP\t0005H\n" ); } } @Override - public void appendXLOCATE( AsmCodeBuf buf ) + public void appendXLocateTo( AsmCodeBuf buf ) { buf.append( "XLOCATE:\n" + "\tPUSH\tHL\n" @@ -79,12 +919,12 @@ public void appendXLOCATE( AsmCodeBuf buf ) + "\tOR\t80H\n" + "\tCALL\tXOUTCH\n" + "\tRET\n" ); - appendXOUTCH( buf ); + appendXOutchTo( buf ); } @Override - public void appendXLPTCH( AsmCodeBuf buf ) + public void appendXLPtchTo( AsmCodeBuf buf ) { buf.append( "XLPTCH:\tLD\tC,5\n" + "\tLD\tE,A\n" @@ -93,7 +933,7 @@ public void appendXLPTCH( AsmCodeBuf buf ) @Override - public void appendXOUTCH( AsmCodeBuf buf ) + public void appendXOutchTo( AsmCodeBuf buf ) { if( !this.xoutchAppended ) { buf.append( "XOUTCH:\tLD\tC,02H\n" @@ -105,7 +945,7 @@ public void appendXOUTCH( AsmCodeBuf buf ) @Override - public void appendXOUTNL( AsmCodeBuf buf ) + public void appendXOutnlTo( AsmCodeBuf buf ) { buf.append( "XOUTNL:\tLD\tC,02H\n" + "\tLD\tE,0DH\n" @@ -116,28 +956,32 @@ public void appendXOUTNL( AsmCodeBuf buf ) } - /* - * Target-ID-String - */ @Override - public void appendXTARID( AsmCodeBuf buf ) + public int get100msLoopCount() { - buf.append( "XTARID:\tDB\t\'CP/M\'\n" - + "\tDB\t00H\n" ); + return 69; } @Override - public boolean createsCodeFor( EmuSys emuSys ) + public String[] getBasicTargetNames() { - return emuSys != null ? (emuSys instanceof PCM) : false; + return new String[] { BASIC_TARGET_NAME }; } @Override - public int get100msLoopCount() + public int getCompatibilityLevel( EmuSys emuSys ) { - return 69; + int rv = 0; + if( emuSys != null ) { + if( (emuSys instanceof NANOS) + || (emuSys instanceof PCM) ) + { + rv = 3; + } + } + return rv; } @@ -149,9 +993,23 @@ public int getDefaultBegAddr() @Override - public int getKCNetBaseIOAddr() + public String getFileHandlerLabel() + { + return "X_CPM_FILE_HANDLER"; + } + + + @Override + public int getFileIOChannelSize() + { + return IOCTB_CHANNEL_SIZE; + } + + + @Override + public String getHostName() { - return 0xC0; + return "CP-M"; } @@ -162,6 +1020,45 @@ public int[] getVdipBaseIOAddresses() } + @Override + public void reset() + { + super.reset(); + this.usesFileRead = false; + this.usesFileWrite = false; + this.usesX_M_INKEY = false; + } + + + @Override + public boolean startsWithFileDevice( String fileName ) + { + boolean rv = false; + if( fileName != null ) { + int len = fileName.length(); + if( len > 1 ) { + char ch = fileName.charAt( 0 ); + if( ((ch >= 'A') && (ch <= 'P')) + || ((ch >= 'a') && (ch <= 'p')) ) + { + int pos = 1; + while( pos < len ) { + ch = fileName.charAt( pos++ ); + if( ch == ':' ) { + rv = true; + break; + } + if( (ch < '0') || (ch > '9') ) { + break; + } + } + } + } + } + return rv; + } + + @Override public boolean supportsXLOCAT() { diff --git a/src/jkcemu/programming/basic/target/HueblerGraphicsMCTarget.java b/src/jkcemu/programming/basic/target/HueblerGraphicsMCTarget.java index 6c7ec3b..2f4ad6d 100644 --- a/src/jkcemu/programming/basic/target/HueblerGraphicsMCTarget.java +++ b/src/jkcemu/programming/basic/target/HueblerGraphicsMCTarget.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -11,43 +11,68 @@ import java.lang.*; import jkcemu.base.EmuSys; import jkcemu.emusys.HueblerGraphicsMC; +import jkcemu.emusys.huebler.AbstractHueblerMC; import jkcemu.programming.basic.*; public class HueblerGraphicsMCTarget extends AbstractTarget { + public static final String BASIC_TARGET_NAME = "TARGET_HUEBLER"; + + private boolean usesX_M_INKEY; private boolean pixUtilAppended; + private boolean xpsetAppended; + private boolean xptestAppended; public HueblerGraphicsMCTarget() { - reset(); + setNamedValue( "GRAPHICSCREEN", 0 ); + } + + + public void appendBssTo( AsmCodeBuf buf ) + { + super.appendBssTo( buf ); + if( this.usesX_M_INKEY ) { + buf.append( "X_M_INKEY:\n" + + "\tDS\t1\n" ); + } } @Override - public void appendExit( AsmCodeBuf buf ) + public void appendExitTo( AsmCodeBuf buf ) { buf.append( "\tJP\t0F01EH\n" ); } @Override - public void appendHChar( AsmCodeBuf buf ) + public void appendHCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0019H\n" ); } @Override - public void appendHPixel( AsmCodeBuf buf ) + public void appendHPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0100H\n" ); } + public void appendInitTo( AsmCodeBuf buf ) + { + if( this.usesX_M_INKEY ) { + buf.append( "\tXOR\tA\n" + + "\tLD\t(X_M_INKEY),A\n" ); + } + } + + @Override - public void appendInput( + public void appendInputTo( AsmCodeBuf buf, boolean xckbrk, boolean xinkey, @@ -55,46 +80,79 @@ public void appendInput( boolean canBreakOnInput ) { if( xckbrk ) { - buf.append( "XCKBRK:\n" ); - } - if( xckbrk || xinkey) { - buf.append( "XINKEY:\tCALL\t0F012H\n" + buf.append( "XCKBRK:\tCALL\t0F012H\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" + + "\tCALL\t0F003H\n" + + "\tCP\t03H\n" ); + if( xinkey ) { + buf.append( "\tJR\tZ,XBREAK\n" + + "\tLD\t(X_M_INKEY),A\n" + + "\tRET\n" + + "XINKEY:\tLD\tA,(X_M_INKEY)\n" + + "\tOR\tA\n" + + "\tJR\tZ,XINKE1\n" + + "\tPUSH\tAF\n" + + "\tXOR\tA\n" + + "\tLD\t(X_M_INKEY),A\n" + + "\tPOP\tAF\n" + + "\tRET\n" + + "XINKE1:\tCALL\t0F012H\n" + "\tOR\tA\n" + "\tRET\tZ\n" ); - if( canBreakOnInput ) { - buf.append( "\tCALL\t0F003H\n" - + "\tCP\t03H\n" - + "\tJR\tZ,XBREAK\n" - + "\tRET\n" ); + this.usesX_M_INKEY = true; } else { - buf.append( "\tJP\t0F003H\n" ); + buf.append( "\tRET\tNZ\n" + + "\tJR\tXBREAK\n" ); } - } - if( xinch ) { - if( canBreakOnInput ) { - buf.append( "XINCH:\tCALL\t0F003H\n" + if( xinch ) { + buf.append( "XINCH:\n" ); + if( this.usesX_M_INKEY ) { + buf.append( "\tXOR\tA\n" + + "\tLD\t(X_M_INKEY),A\n" ); + } + buf.append( "XINCH1:\tCALL\t0F003H\n" + "\tCP\t03H\n" + "\tJR\tZ,XBREAK\n" + + "\tRET\tNZ\n" + + "\tOR\tA\n" + + "\tJR\tZ,XINCH1\n" + "\tRET\n" ); - } else { - buf.append( "XINCH:\tJP\t0F003H\n" ); + } + } else { + if( xinkey ) { + buf.append( "XINKEY:\tCALL\t0F012H\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" ); + } + if( xinkey || xinch ) { + if( canBreakOnInput ) { + buf.append( "XINCH:\tCALL\t0F003H\n" + + "\tCP\t03H\n" + + "\tRET\tNZ\n" + + "\tJR\tXBREAK\n" ); + } else { + buf.append( "XINCH:\tJP\t0F003H\n" ); + } } } } @Override - public void appendMenuItem( - BasicCompiler compiler, + public void appendPrologTo( AsmCodeBuf buf, + BasicCompiler compiler, String appName ) { boolean done = false; if( appName != null ) { if( !appName.isEmpty() ) { - buf.append( "\tDB\t0EDH,0FFH\n" ); + buf.append( "\tJP\t" ); + buf.append( BasicCompiler.START_LABEL ); + buf.append( "\n" + + "\tDB\t0EDH,0FFH\n" ); buf.appendStringLiteral( appName ); - buf.append( "\tJP\tMINIT\n" ); done = true; } } @@ -108,14 +166,14 @@ public void appendMenuItem( @Override - public void appendWChar( AsmCodeBuf buf ) + public void appendWCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0020H\n" ); } @Override - public void appendWPixel( AsmCodeBuf buf ) + public void appendWPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0100H\n" ); } @@ -128,7 +186,7 @@ public void appendWPixel( AsmCodeBuf buf ) * <>0: Cursor einschalten */ @Override - public void appendXCURS( AsmCodeBuf buf ) + public void appendXCursTo( AsmCodeBuf buf ) { buf.append( "XCURS:\tPUSH\tHL\n" + "\tLD\tC,1BH\n" @@ -143,8 +201,54 @@ public void appendXCURS( AsmCodeBuf buf ) } + /* + * Zeichnen einer horizontalen Linie + * Parameter: + * BC: Laenge - 1 + * DE: linke X-Koordinate, nicht kleiner 0 + * HL: Y-Koordinate + */ + public void appendXHLineTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XHLINE:\tBIT\t7,B\n" + + "\tRET\tNZ\n" + + "\tPUSH\tBC\n" + + "\tCALL\tX_PST\n" + + "\tPOP\tBC\n" + + "\tRET\tC\n" + + "\tLD\tD,00H\n" + + "XHLINE1:\n" + + "\tOR\tD\n" + + "\tLD\tD,A\n" + + "\tSLA\tA\n" + + "\tJR\tNC,XHLINE2\n" + + "\tLD\tA,D\n" ); + if( this.usesX_M_PEN ) { + buf.append( "\tCALL\tXPSET_A\n" ); + } else { + buf.append( "\tOR\t(HL)\n" + + "\tLD\t(HL),A\n" ); + } + buf.append( "\tINC\tHL\n" + + "\tLD\tA,L\n" + + "\tAND\t1FH\n" + + "\tRET\tZ\n" + + "\tLD\tA,01H\n" + + "\tLD\tD,00H\n" + + "XHLINE2:\n" + + "\tDEC\tBC\n" + + "\tBIT\t7,B\n" + + "\tJR\tZ,XHLINE1\n" + + "\tLD\tA,D\n" + + "\tOR\tA\n" + + "\tJR\tNZ,XPSET_A\n" + + "\tRET\n" ); + appendXPSetTo( buf, compiler ); + } + + @Override - public void appendXLOCATE( AsmCodeBuf buf ) + public void appendXLocateTo( AsmCodeBuf buf ) { buf.append( "XLOCATE:\n" + "\tLD\tA,1BH\n" @@ -155,12 +259,12 @@ public void appendXLOCATE( AsmCodeBuf buf ) + "\tLD\tA,L\n" + "\tOR\t80H\n" + "\tJR\tXOUTCH\n" ); - appendXOUTCH( buf ); + appendXOutchTo( buf ); } @Override - public void appendXLPTCH( AsmCodeBuf buf ) + public void appendXLPtchTo( AsmCodeBuf buf ) { buf.append( "XLPTCH:\tLD\tC,A\n" + "\tJP\t0F00FH\n" ); @@ -168,7 +272,7 @@ public void appendXLPTCH( AsmCodeBuf buf ) @Override - public void appendXOUTCH( AsmCodeBuf buf ) + public void appendXOutchTo( AsmCodeBuf buf ) { if( !this.xoutchAppended ) { buf.append( "XOUTCH:\tLD\tC,A\n" @@ -179,7 +283,7 @@ public void appendXOUTCH( AsmCodeBuf buf ) @Override - public void appendXOUTNL( AsmCodeBuf buf ) + public void appendXOutnlTo( AsmCodeBuf buf ) { buf.append( "XOUTNL:\tLD\tC,0DH\n" + "\tCALL\t0F009H\n" @@ -188,6 +292,131 @@ public void appendXOUTNL( AsmCodeBuf buf ) } + /* + * XPAINT_LEFT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach links, + * Der Startpunkt selbst wird nicht geprueft + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * (PAINT_M_X1): X-Endkoordinate der gefuellten Linie, + * kleiner oder gleich X-Startpunkt + * + * XPAINT_RIGHT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach rechts + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * CY=1: Pixel im Startpunkt bereits gesetzt (gefuellt) + * oder ausserhalb des sichtbaren Bereichs + * (PAINT_M_X2): X-Endkoordinate der gefuellten Linie, nur bei CY=0 + */ + @Override + public void appendXPaintTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPAINT_LEFT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "\tLD\tA,D\n" + + "\tOR\tE\n" + + "\tJR\tZ,XPAINT_LEFT5\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tDEC\tDE\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_PST\n" + + "\tPOP\tDE\n" + + "\tJR\tC,XPAINT_LEFT4\n" + + "\tLD\tC,A\n" + + "\tLD\tB,(HL)\n" + + "XPAINT_LEFT1:\n" + + "\tLD\tA,B\n" + + "XPAINT_LEFT2:\n" + + "\tAND\tC\n" + + "\tJR\tNZ,XPAINT_LEFT3\n" + + "\tLD\tA,B\n" + + "\tOR\tC\n" + + "\tLD\tB,A\n" + + "\tDEC\tDE\n" + + "\tSRL\tC\n" + + "\tJR\tNC,XPAINT_LEFT2\n" + + "\tLD\t(HL),B\n" + + "\tDEC\tHL\n" + + "\tLD\tA,E\n" + + "\tINC\tA\n" + + "\tJR\tZ,XPAINT_LEFT4\n" + + "\tLD\tB,(HL)\n" + + "\tLD\tC,80H\n" + + "\tJR\tXPAINT_LEFT1\n" + + "XPAINT_LEFT3:\n" + + "\tLD\t(HL),B\n" + + "XPAINT_LEFT4:\n" + + "\tINC\tDE\n" + + "XPAINT_LEFT5:\n" + + "\tLD\t(PAINT_M_X1),DE\n" + + "\tRET\n" + + "XPAINT_RIGHT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_PST\n" + + "\tPOP\tDE\n" + + "\tRET\tC\n" + + "\tLD\tC,A\n" + + "\tLD\tB,(HL)\n" + + "\tAND\tB\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tJR\tXPAINT_RIGHT3\n" + + "XPAINT_RIGHT1:\n" + + "\tLD\tA,B\n" + + "XPAINT_RIGHT2:\n" + + "\tAND\tC\n" + + "\tJR\tNZ,XPAINT_RIGHT4\n" + + "XPAINT_RIGHT3:\n" + + "\tLD\tA,B\n" + + "\tOR\tC\n" + + "\tLD\tB,A\n" + + "\tINC\tDE\n" + + "\tSLA\tC\n" + + "\tJR\tNC,XPAINT_RIGHT2\n" + + "\tLD\t(HL),B\n" + + "\tINC\tHL\n" + + "\tLD\tA,E\n" + + "\tOR\tA\n" + + "\tJR\tZ,XPAINT_RIGHT5\n" + + "\tLD\tB,(HL)\n" + + "\tLD\tC,01H\n" + + "\tJR\tXPAINT_RIGHT1\n" + + "XPAINT_RIGHT4:\n" + + "\tLD\t(HL),B\n" + + "XPAINT_RIGHT5:\n" + + "\tDEC\tDE\n" + + "\tLD\t(PAINT_M_X2),DE\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + appendPixUtilTo( buf ); + } + + + /* + * Farbe eines Pixels ermitteln, + * Die Rueckgabewerte sind identisch zur Funktion XPTEST + * + * Parameter: + * DE: X-Koordinate (0...255) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * HL >= 0: Farbcode des Pixels + * HL=-1: Pixel exisitiert nicht + */ + @Override + public void appendXPointTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + appendXPTestTo( buf, compiler ); + } + + /* * Zuruecksetzen eines Pixels * Parameter: @@ -195,34 +424,34 @@ public void appendXOUTNL( AsmCodeBuf buf ) * HL: Y-Koordinate */ @Override - public void appendXPRES( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPResTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPRES:\tCALL\tX_PCK\n" + buf.append( "XPRES:\tCALL\tX_PST\n" + "\tRET\tC\n" - + "\tCALL\tX_PST\n" + "\tCPL\n" + "\tAND\t(HL)\n" + "\tLD\t(HL),A\n" + "\tRET\n" ); - appendPixUtil( buf ); + appendPixUtilTo( buf ); } /* - * Setzen eines Pixels + * Setzen eines Pixels unter Beruecksichtigung des eingestellten Stiftes * Parameter: * DE: X-Koordinate * HL: Y-Koordinate */ @Override - public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPSetTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPSET:\tCALL\tX_PCK\n" + if( !this.xpsetAppended ) { + buf.append( "XPSET:\tCALL\tX_PST\n" + "\tRET\tC\n" - + "\tCALL\tX_PST\n" ); - if( this.usesX_MPEN ) { - buf.append( "\tLD\tB,A\n" - + "\tLD\tA,(X_MPEN)\n" + + "XPSET_A:\n" ); + if( this.usesX_M_PEN ) { + buf.append( "\tLD\tE,A\n" + + "\tLD\tA,(X_M_PEN)\n" + "\tDEC\tA\n" + "\tJR\tZ,XPSET2\n" // Stift 1 (Normal) + "\tDEC\tA\n" @@ -230,20 +459,22 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + "\tDEC\tA\n" + "\tRET\tNZ\n" + "\tLD\tA,(HL)\n" // Stift 3 (XOR-Mode) - + "\tXOR\tB\n" + + "\tXOR\tE\n" + "\tLD\t(HL),A\n" + "\tRET\n" - + "XPSET1:\tLD\tA,B\n" // Pixel loeschen + + "XPSET1:\tLD\tA,E\n" // Pixel loeschen + "\tCPL\n" + "\tAND\t(HL)\n" + "\tLD\t(HL),A\n" + "\tRET\n" - + "XPSET2:\tLD\tA,B\n" ); // Pixel setzen - } - buf.append( "\tOR\t(HL)\n" + + "XPSET2:\tLD\tA,E\n" ); // Pixel setzen + } + buf.append( "\tOR\t(HL)\n" + "\tLD\t(HL),A\n" + "\tRET\n" ); - appendPixUtil( buf ); + appendPixUtilTo( buf ); + this.xpsetAppended = true; + } } @@ -258,45 +489,52 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) * HL=-1: Pixel exisistiert nicht */ @Override - public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPTestTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPTEST:\tCALL\tX_PCK\n" - + "\tJR\tNC,X_PTST1\n" - + "\tLD\tHL,0FFFFH\n" - + "\tRET\n" - + "X_PTST1:\n" - + "\tCALL\tX_PST\n" + if( !this.xptestAppended ) { + buf.append( "XPOINT:\n" + + "XPTEST:\tCALL\tX_PST\n" + + "\tJR\tC,X_PTEST1\n" + "\tAND\t(HL)\n" + "\tLD\tHL,0000H\n" + "\tRET\tZ\n" + "\tINC\tHL\n" + + "\tRET\n" + + "X_PTEST1:\n" + + "\tLD\tHL,0FFFFH\n" + "\tRET\n" ); - appendPixUtil( buf ); + appendPixUtilTo( buf ); + this.xptestAppended = true; + } } - /* - * Target-ID-String - */ @Override - public void appendXTARID( AsmCodeBuf buf ) + public int get100msLoopCount() { - buf.append( "XTARID:\tDB\t\'HUEBLER\'\n" - + "\tDB\t00H\n" ); + return 42; } @Override - public boolean createsCodeFor( EmuSys emuSys ) + public String[] getBasicTargetNames() { - return emuSys != null ? (emuSys instanceof HueblerGraphicsMC) : false; + return new String[] { BASIC_TARGET_NAME }; } @Override - public int get100msLoopCount() + public int getCompatibilityLevel( EmuSys emuSys ) { - return 42; + int rv = 0; + if( emuSys != null ) { + if( emuSys instanceof HueblerGraphicsMC ) { + rv = 3; + } else if( emuSys instanceof AbstractHueblerMC ) { + rv = 1; + } + } + return rv; } @@ -308,16 +546,29 @@ public int getDefaultBegAddr() @Override - public int getGraphicScreenNum() + public String getHostName() + { + return "Huebler-MC"; + } + + + @Override + public int getMaxAppNameLen() { - return 0; + return 14; } @Override - public int getKCNetBaseIOAddr() + public String getStartCmd( EmuSys emuSys, String appName, int begAddr ) { - return 0xC0; + String rv = null; + if( emuSys != null ) { + if( emuSys instanceof AbstractHueblerMC ) { + rv = appName; + } + } + return rv; } @@ -332,26 +583,29 @@ public int[] getVdipBaseIOAddresses() public void reset() { super.reset(); + this.usesX_M_INKEY = false; this.pixUtilAppended = false; + this.xpsetAppended = false; + this.xptestAppended = false; } @Override - public boolean supportsAppName() + public boolean supportsGraphics() { return true; } @Override - public boolean supportsGraphics() + public boolean supportsXCURS() { return true; } @Override - public boolean supportsXCURS() + public boolean supportsXHLINE() { return true; } @@ -371,6 +625,13 @@ public boolean supportsXLPTCH() } + @Override + public boolean supportsXPAINT_LEFT_RIGHT() + { + return true; + } + + @Override public String toString() { @@ -380,33 +641,27 @@ public String toString() /* --- private Methoden --- */ - private void appendPixUtil( AsmCodeBuf buf ) + private void appendPixUtilTo( AsmCodeBuf buf ) { if( !this.pixUtilAppended ) { buf.append( /* - * Pruefen der Parameter + * Pruefen der Parameter und + * ermitteln von Informationen zu einem Pixel + * Parameter: * DE: X-Koordinate (0...255) * HL: Y-Koordinate (0...255) * Rueckgabe: * CY=1: Fehler + * A: Bitmuster mit einem gesetzten Bit, + * dass das Pixel in der Speicherzelle beschreibt + * HL: Speicherzelle, in der sich das Pixel befindet */ - "X_PCK:\tLD\tA,D\n" + "X_PST:\tLD\tA,D\n" + "\tOR\tH\n" - + "\tRET\tZ\n" + "\tSCF\n" - + "\tRET\n" - /* - * Ermitteln von Informationen zu einem Pixel - * Parameter: - * DE: X-Koordinate (0...255) - * HL: Y-Koordinate (0...255) - * Rueckgabe: - * A: Bitmuster mit einem gesetzten Bit, - * dass das Pixel in der Speicherzelle beschreibt - * HL: Speicherzelle, in der sich das Pixel befindet - */ - + "X_PST:\tLD\tA,E\n" + + "\tRET\tNZ\n" + + "\tLD\tA,E\n" + "\tAND\t07H\n" + "\tLD\tB,A\n" + "\tLD\tA,01H\n" @@ -425,9 +680,10 @@ private void appendPixUtil( AsmCodeBuf buf ) + "\tADD\tHL,HL\n" + "\tLD\tDE,0DFE0H\n" + "\tEX\tDE,HL\n" - + "\tOR\tA\n" // CY=0, A unveraendert + + "\tOR\tA\n" // CY=0 + "\tSBC\tHL,DE\n" + "\tADD\tHL,BC\n" + + "\tOR\tA\n" // CY=0 + "\tRET\n" ); this.pixUtilAppended = true; } diff --git a/src/jkcemu/programming/basic/target/KC854Target.java b/src/jkcemu/programming/basic/target/KC854Target.java new file mode 100644 index 0000000..17fadb1 --- /dev/null +++ b/src/jkcemu/programming/basic/target/KC854Target.java @@ -0,0 +1,523 @@ +/* + * (c) 2014-2015 Jens Mueller + * + * Kleincomputer-Emulator + * + * KC85/4..5-spezifische Code-Erzeugung des BASIC-Compilers + * mit direktem Hardwarezugriff bei den Grafikfunktionen + */ + +package jkcemu.programming.basic.target; + +import java.lang.*; +import jkcemu.base.EmuSys; +import jkcemu.emusys.KC85; +import jkcemu.programming.basic.*; + + +public class KC854Target extends KC85Target +{ + public static final String BASIC_TARGET_NAME = "TARGET_KC85_4"; + + private boolean usesScreens; + + + public KC854Target() + { + setNamedValue( "LASTSCREEN", 1 ); + } + + + @Override + public void appendBssTo( AsmCodeBuf buf ) + { + super.appendBssTo( buf ); + if( this.usesScreens ) { + buf.append( "X_M_SCRWIN:\n" + + "\tDS\t2\n" ); + } + } + + + @Override + public void appendInitTo( AsmCodeBuf buf ) + { + super.appendInitTo( buf ); + if( this.usesScreens ) { + buf.append( "\tLD\tA,(0B79BH)\n" // Nr. aktuelles Fenster + + "\tLD\tHL,X_M_SCRWIN\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tINC\tA\n" + + "\tCP\t0AH\n" + + "\tJR\tC,X_IWN3\n" + + "\tXOR\tA\n" + + "X_IWN3:\tLD\t(HL),A\n" ); + } + } + + + @Override + public void appendPreExitTo( AsmCodeBuf buf ) + { + super.appendPreExitTo( buf ); + appendSwitchToTextScreenTo( buf ); + } + + + @Override + public void appendSwitchToTextScreenTo( AsmCodeBuf buf ) + { + if( this.usesScreens ) { + buf.append( "\tLD\tA,(X_M_SCRWIN)\n" + + "\tLD\tL,A\n" + + "\tCALL\tXSCREEN\n" ); + } + } + + + /* + * Zeichnen einer horizontalen Linie + * Parameter: + * BC: Laenge - 1 + * DE: linke X-Koordinate, nicht kleiner 0 + * HL: Y-Koordinate + */ + public void appendXHLineTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XHLINE:\tBIT\t7,B\n" + + "\tRET\tNZ\n" + + "\tPUSH\tBC\n" + + "\tPUSH\tDE\n" + + "\tPUSH\tHL\n" + + "\tCALL\tX_PST\n" + + "\tEXX\n" + + "\tPOP\tHL\n" + + "\tPOP\tDE\n" + + "\tPOP\tBC\n" + + "\tRET\tC\n" + + "\tLD\tD,00H\n" + + "XHLINE1:\n" + + "\tOR\tD\n" + + "\tLD\tD,A\n" + + "\tSRL\tA\n" + + "\tJR\tNC,XHLINE2\n" + + "\tLD\tA,D\n" + + "\tEXX\n" + + "\tCALL\tXPSET_A\n" + + "\tINC\tH\n" + + "\tLD\tA,H\n" + + "\tCP\t0A8H\n" + + "\tRET\tNC\n" + + "\tEXX\n" + + "\tLD\tA,80H\n" + + "\tLD\tD,00H\n" + + "XHLINE2:\n" + + "\tDEC\tBC\n" + + "\tBIT\t7,B\n" + + "\tJR\tZ,XHLINE1\n" + + "\tLD\tA,D\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" + + "\tEXX\n" + + "\tJR\tXPSET_A\n" ); + appendXPSetTo( buf, compiler ); + } + + + /* + * XPAINT_LEFT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach links, + * Der Startpunkt selbst wird nicht geprueft + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * (PAINT_M_X1): X-Endkoordinate der gefuellten Linie, + * kleiner oder gleich X-Startpunkt + * + * XPAINT_RIGHT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach rechts + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * CY=1: Pixel im Startpunkt bereits gesetzt (gefuellt) + * oder ausserhalb des sichtbaren Bereichs + * (PAINT_M_X2): X-Endkoordinate der gefuellten Linie, nur bei CY=0 + */ + @Override + public void appendXPaintTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPAINT_LEFT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "\tLD\tA,D\n" + + "\tOR\tE\n" + + "\tJR\tZ,XPAINT_LEFT6\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tDEC\tDE\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_PST\n" + + "\tPOP\tDE\n" + + "\tJR\tC,XPAINT_LEFT5\n" + + "\tLD\tC,A\n" + + "\tLD\tB,(HL)\n" + + "XPAINT_LEFT1:\n" + + "\tLD\tA,B\n" + + "XPAINT_LEFT2:\n" + + "\tAND\tC\n" + + "\tJR\tNZ,XPAINT_LEFT4\n" + + "\tLD\tA,B\n" + + "\tOR\tC\n" + + "\tLD\tB,A\n" + + "\tDEC\tDE\n" + + "\tSLA\tC\n" + + "\tJR\tNC,XPAINT_LEFT2\n" + + "\tLD\t(HL),B\n" + + "\tLD\tC,84H\n" + + "\tCALL\tXPSET_WR_COLOR\n" + + "\tDEC\tH\n" + + "\tBIT\t0,D\n" + + "\tJR\tZ,XPAINT_LEFT3\n" + + "\tLD\tA,E\n" + + "\tINC\tA\n" + + "\tJR\tZ,XPAINT_LEFT5\n" + + "XPAINT_LEFT3:\n" + + "\tLD\tB,(HL)\n" + + "\tLD\tC,01H\n" + + "\tJR\tXPAINT_LEFT1\n" + + "XPAINT_LEFT4:\n" + + "\tLD\t(HL),B\n" + + "\tLD\tC,84H\n" + + "\tCALL\tXPSET_WR_COLOR\n" + + "XPAINT_LEFT5:\n" + + "\tINC\tDE\n" + + "XPAINT_LEFT6:\n" + + "\tLD\t(PAINT_M_X1),DE\n" + + "\tRET\n" + + "XPAINT_RIGHT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_PST\n" + + "\tPOP\tDE\n" + + "\tRET\tC\n" + + "\tLD\tC,A\n" + + "\tLD\tB,(HL)\n" + + "\tAND\tB\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tJR\tXPAINT_RIGHT3\n" + + "XPAINT_RIGHT1:\n" + + "\tLD\tA,B\n" + + "XPAINT_RIGHT2:\n" + + "\tAND\tC\n" + + "\tJR\tNZ,XPAINT_RIGHT5\n" + + "XPAINT_RIGHT3:\n" + + "\tLD\tA,B\n" + + "\tOR\tC\n" + + "\tLD\tB,A\n" + + "\tINC\tDE\n" + + "\tSRL\tC\n" + + "\tJR\tNC,XPAINT_RIGHT2\n" + + "\tLD\t(HL),B\n" + + "\tLD\tC,84H\n" + + "\tCALL\tXPSET_WR_COLOR\n" + + "\tINC\tH\n" + + "\tLD\tA,E\n" + + "\tCP\t40H\n" + + "\tJR\tNZ,XPAINT_RIGHT4\n" + + "\tLD\tA,D\n" + + "\tDEC\tA\n" + + "\tJR\tZ,XPAINT_RIGHT6\n" + + "XPAINT_RIGHT4:\n" + + "\tLD\tB,(HL)\n" + + "\tLD\tC,80H\n" + + "\tJR\tXPAINT_RIGHT1\n" + + "XPAINT_RIGHT5:\n" + + "\tLD\t(HL),B\n" + + "\tLD\tC,84H\n" + + "\tCALL\tXPSET_WR_COLOR\n" + + "XPAINT_RIGHT6:\n" + + "\tDEC\tDE\n" + + "\tLD\t(PAINT_M_X2),DE\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + appendXPSetTo( buf, compiler ); + } + + + /* + * Farbe eines Pixels ermitteln + * Parameter: + * DE: X-Koordinate (0...255) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * HL >= 0: Farbcode des Pixels + * HL=-1: Pixel exisitiert nicht + */ + @Override + public void appendXPointTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPOINT:\tCALL\tX_PST\n" + + "\tJR\tC,XPOINT3\n" + // Farbbyte + + "\tDB\t0DDH,0CBH,01H,0C8H\t;SET 1,(IX+01H),B\n" + + "\tOUT\t(C),B\n" + + "\tLD\tD,(HL)\n" + + "\tDB\t0DDH,0CBH,01H,88H\t;RES 1,(IX+01H),B\n" + + "\tOUT\t(C),B\n" + // Pixel auswerten + + "\tAND\t(HL)\n" + + "\tLD\tA,D\n" + + "\tJR\tZ,XPOINT1\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tAND\t1FH\n" + + "\tJR\tXPOINT2\n" + + "XPOINT1:\n" + + "\tAND\t07H\n" + + "XPOINT2:\n" + + "\tLD\tL,A\n" + + "\tLD\tH,00H\n" + + "\tRET\n" + + "XPOINT3:\n" + + "\tLD\tHL,0FFFFH\n" + + "\tRET\n" ); + appendPixUtilTo( buf ); + } + + + /* + * Setzen eines Pixels unter Beruecksichtigung des eingestellten Stiftes + * Parameter: + * DE: X-Koordinate + * HL: Y-Koordinate + */ + @Override + public void appendXPSetTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + if( !this.xpsetAppended ) { + buf.append( "XPSET:\tCALL\tX_PST\n" + + "\tRET\tC\n" + + "XPSET_A:\n" ); + if( this.usesX_M_PEN ) { + buf.append( "\tLD\tD,A\n" + + "\tLD\tA,(X_M_PEN)\n" + + "\tDEC\tA\n" + + "\tJR\tZ,XPSET2\n" // Stift 1 (Normal) + + "\tDEC\tA\n" + + "\tJR\tZ,XPSET1\n" // Stift 2 (Loeschen) + + "\tDEC\tA\n" + + "\tRET\tNZ\n" + + "\tLD\tA,(HL)\n" // Stift 3 (XOR-Mode) + + "\tXOR\tD\n" + + "\tJR\tXPSET_WR_A\n" + + "XPSET1:\tLD\tA,D\n" // Pixel loeschen + + "\tCPL\n" + + "\tAND\t(HL)\n" + + "\tJR\tXPSET_WR_A\n" + + "XPSET2:\tLD\tA,D\n" ); // Pixel setzen + } + buf.append( "XPSET_OR_A:\n" + + "\tOR\t(HL)\n" + + "XPSET_WR_A:\n" + + "\tLD\t(HL),A\n" + + "XPSET_WR_COLOR:\n" + + "\tDB\t0DDH,0CBH,01H,0C8H\t;SET 1,(IX+01H),B\n" + + "\tOUT\t(C),B\n" + + "\tLD\tA,(0B7A3H)\n" + + "\tLD\t(HL),A\n" + + "\tDB\t0DDH,0CBH,01H,88H\t;RES 1,(IX+01H),B\n" + + "\tOUT\t(C),B\n" + + "\tRET\n" ); + appendPixUtilTo( buf ); + this.needsFullWindow = true; + this.xpsetAppended = true; + } + } + + + /* + * Bildschirmmode einstellen + * HL: Screen-Nummer + * 0: IRM Bank 0 + * 1: IRM Bank 1 + * Rueckgabe: + * CY=1: Screen-Nummer nicht unterstuetzt + */ + @Override + public void appendXScreenTo( AsmCodeBuf buf ) + { + if( this.usesScreens ) { + buf.append( "XSCREEN:\n" + + "\tLD\tA,L\n" + + "\tAND\t0FEH\n" + + "\tOR\tH\n" + + "\tJR\tNZ,XSCRN2\n" + + "\tLD\tB,(IX+01H)\n" + + "\tLD\tA,B\n" + + "\tXOR\tL\n" + + "\tAND\t01H\n" + + "\tRET\tZ\n" + + "\tLD\tA,B\n" + + "\tBIT\t0,L\n" + + "\tLD\tHL,X_M_SCRWIN\n" + + "\tJR\tZ,XSCRN1\n" + // Bank 0 -> 1 + + "\tOR\t05H\n" + + "\tLD\t(IX+01),A\n" + + "\tOUT\t(84H),A\n" + + "\tINC\tHL\n" + + "\tLD\tA,(HL)\n" + + "\tBIT\t7,A\n" + + "\tJR\tZ,XSCRN2\n" + // Fenster intialisieren + + "\tAND\t7FH\n" + + "\tLD\t(HL),A\n" + + "\tLD\tHL,0000H\n" // Fensteranfang + + "\tLD\tDE,2028H\n" // Fenstergroesse + + "\tLD\tBC,0000H\n" // bei CAOS 4.1 notwendig + + "\tCALL\t0F003H\n" + + "\tDB\t3CH\n" + + "\tRET\n" + // Bank 1 -> 0 + + "XSCRN1:\tAND\t0FAH\n" + + "\tLD\t(IX+01),A\n" + + "\tOUT\t(84H),A\n" + + "\tLD\tA,(HL)\n" + // Fenster in A aufrufen + + "XSCRN2:\tCALL\t0F003H\n" + + "\tDB\t3DH\n" + + "\tRET\n" + + "XSCRN3:\tSCF\n" + + "\tRET\n" ); + } + } + + + @Override + public String[] getBasicTargetNames() + { + return add( super.getBasicTargetNames(), BASIC_TARGET_NAME ); + } + + + @Override + public int getCompatibilityLevel( EmuSys emuSys ) + { + int rv = 0; + if( emuSys != null ) { + if( emuSys instanceof KC85 ) { + rv = 1; + if( ((KC85) emuSys).getKCTypeNum() >= 4 ) { + rv = 3; + } + } + } + return rv; + } + + + @Override + public void preAppendLibraryCode( BasicCompiler compiler ) + { + super.preAppendLibraryCode( compiler ); + if( compiler.usesLibItem( BasicLibrary.LibItem.SCREEN ) + || compiler.usesLibItem( BasicLibrary.LibItem.XSCREEN ) ) + { + this.usesScreens = true; + } + } + + + @Override + public void reset() + { + super.reset(); + this.usesScreens = false; + } + + + @Override + public boolean supportsXHLINE() + { + return true; + } + + + @Override + public boolean supportsXPAINT_LEFT_RIGHT() + { + return true; + } + + + @Override + public String toString() + { + return "KC85/4..5 mit Unterst\u00FCtzung beider Bildspeicher"; + } + + + /* --- Hilfsfunktionen --- */ + + protected void appendPixUtilTo( AsmCodeBuf buf ) + { + if( !this.pixUtilAppended ) { + buf.append( + /* + * Pruefen der Parameter, einschalten der Pixelebene + * und ermitteln von Informationen zu einem Pixel + * + * Parameter: + * DE: X-Koordinate (0...319) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * CY=1: Pixel ausserhalb des gueltigen Bereichs + * A: Bitmuster mit einem gesetzten Bit, + * dass das Pixel in der Speicherzelle beschreibt + * C: 84h + * HL: Adresse im Pixel-/Farbspeicher + */ + "X_PST:\tLD\tA,H\n" + + "\tOR\tA\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tLD\tA,D\n" + + "\tOR\tA\n" + + "\tJR\tZ,X_PST1\n" + + "\tCP\t02H\n" + + "\tCCF\n" + + "\tRET\tC\n" + + "\tLD\tA,3FH\n" + + "\tCP\tE\n" + + "\tRET\tC\n" + + "X_PST1:\tLD\tA,L\n" + + "\tCPL\n" + + "\tLD\tL,A\n" + + "\tLD\tA,E\n" + + "\tAND\t07H\n" + + "\tLD\tB,A\n" + + "\tLD\tA,80H\n" + + "\tJR\tZ,X_PST3\n" + + "X_PST2:\tSRL\tA\n" + + "\tDJNZ\tX_PST2\n" + + "X_PST3:\tSRL\tD\n" + + "\tRR\tE\n" + + "\tSRL\tE\n" + + "\tSRL\tE\n" + + "\tLD\tD,E\n" + + "\tLD\tE,00H\n" + + "\tADD\tHL,DE\n" + + "\tSET\t7,H\n" + + "\tDB\t0DDH,0CBH,01H,88H\t;RES 1,(IX+01H),B\n" + + "\tLD\tC,84H\n" + + "\tOUT\t(C),B\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + this.pixUtilAppended = true; + } + } +} diff --git a/src/jkcemu/programming/basic/target/KC85Target.java b/src/jkcemu/programming/basic/target/KC85Target.java index 8f3a954..7aec847 100644 --- a/src/jkcemu/programming/basic/target/KC85Target.java +++ b/src/jkcemu/programming/basic/target/KC85Target.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -17,29 +17,38 @@ public class KC85Target extends AbstractTarget { - private boolean needsFullWindow; - private boolean usesColors; + public static final String BASIC_TARGET_NAME = "TARGET_KC85"; + + protected boolean needsFullWindow; + protected boolean pixUtilAppended; + protected boolean xpsetAppended; + + private boolean usesXCOLOR; + private boolean usesXINK; + private boolean usesXPAPER; private boolean usesJoystick; - private boolean usesOSVersion; private boolean usesM052; private boolean xpresAppended; - private boolean xpsetAppended; public KC85Target() { - reset(); - } - - - @Override - public void appendDataTo( AsmCodeBuf buf ) - { - super.appendDataTo( buf ); - if( this.usesOSVersion ) { - buf.append( "X_CAOS:\tDB\t'CAOS '\n" - + "\tDB\t00H\n" ); - } + setNamedValue( "GRAPHICSCREEN", 0 ); + setNamedValue( "BLACK", 0 ); + setNamedValue( "BLINKING", 0x10 ); + setNamedValue( "BLUE", 0x01 ); + setNamedValue( "CYAN", 0x05 ); + setNamedValue( "GREEN", 0x04 ); + setNamedValue( "MAGENTA", 0x03 ); + setNamedValue( "RED", 0x02 ); + setNamedValue( "WHITE", 0x07 ); + setNamedValue( "YELLOW", 0x06 ); + setNamedValue( "JOYST_LEFT", 0x04 ); + setNamedValue( "JOYST_RIGHT", 0x08 ); + setNamedValue( "JOYST_DOWN", 0x02 ); + setNamedValue( "JOYST_UP", 0x01 ); + setNamedValue( "JOYST_BUTTON1", 0x20 ); + setNamedValue( "JOYST_BUTTON2", 0x10 ); } @@ -47,26 +56,16 @@ public void appendDataTo( AsmCodeBuf buf ) public void appendBssTo( AsmCodeBuf buf ) { super.appendBssTo( buf ); - if( this.usesColors ) { - buf.append( "X_M_INK:\n" - + "\tDS\t1\n" ); - } - if( this.usesOSVersion ) { - buf.append( "X_M_OS:\tDS\t1\n" ); - } if( this.usesM052 ) { - buf.append( "X_M_M052SLOT:\n" - + "\tDS\t1\n" - + "X_M_M052STAT:\n" - + "\tDS\t1\n" - + "X_M_M052USED:\n" - + "\tDS\t1\n" ); + buf.append( "X_M_M052SLOT:\tDS\t1\n" + + "X_M_M052STAT:\tDS\t1\n" + + "X_M_M052USED:\tDS\t1\n" ); } } @Override - public void appendEnableKCNet( AsmCodeBuf buf ) + public void appendEnableVdipTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_M052\n" ); this.usesM052 = true; @@ -74,17 +73,36 @@ public void appendEnableKCNet( AsmCodeBuf buf ) @Override - public void appendEnableVdip( AsmCodeBuf buf ) + public void appendEtcPreXOutTo( AsmCodeBuf buf ) { - buf.append( "\tCALL\tX_M052\n" ); - this.usesM052 = true; - } - - - @Override - public void appendEtc( AsmCodeBuf buf ) - { - super.appendEtc( buf ); + if( this.usesXINK ) { + buf.append( "XINK:\tLD\tA,01H\n" ); + if( this.usesXCOLOR || this.usesXPAPER ) { + buf.append( "\tJR\tXCOLO1\n" ); + } else { + buf.append( "\tLD\t(0B781H),A\n" + + "\tCALL\t0F003H\n" + + "\tDB\t0FH\n" + + "\tRET\n" ); + } + } + if( this.usesXPAPER ) { + buf.append( "XPAPER:\tLD\tE,L\n" + + "\tLD\tA,(0B7A3H)\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tLD\tL,A\n" ); + this.usesXCOLOR = true; + // unmittelbar weiter mit XCOLOR! + } + if( this.usesXCOLOR ) { + buf.append( "XCOLOR:\tLD\tA,02H\n" + + "XCOLO1:\tLD\t(0B781H),A\n" + + "\tCALL\t0F003H\n" + + "\tDB\t0FH\n" + + "\tRET\n" ); + } if( this.usesM052 ) { /* * Aktivieren des Moduls M052 @@ -125,34 +143,40 @@ public void appendEtc( AsmCodeBuf buf ) @Override - public void appendExit( AsmCodeBuf buf ) + public void appendPreExitTo( AsmCodeBuf buf ) { if( this.usesM052 ) { buf.append( "\tLD\tA,(X_M_M052USED)\n" + "\tOR\tA\n" - + "\tRET\tZ\n" + + "\tJR\tZ,X_EXIT1\n" + "\tLD\tA,(X_M_M052STAT)\n" + "\tLD\tD,A\n" + "\tLD\tA,(X_M_M052SLOT)\n" + "\tLD\tL,A\n" + "\tLD\tA,02H\n" + "\tCALL\t0F003H\n" - + "\tDB\t26H\n" ); + + "\tDB\t26H\n" + + "X_EXIT1:\n" ); } - buf.append( "X_EXIT1:\n" - + "\tRET\n" ); } @Override - public void appendHChar( AsmCodeBuf buf ) + public void appendExitTo( AsmCodeBuf buf ) + { + buf.append( "\tRET\n" ); + } + + + @Override + public void appendHCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0020H\n" ); } @Override - public void appendHPixel( AsmCodeBuf buf ) + public void appendHPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0100H\n" ); } @@ -172,42 +196,11 @@ public void appendInitTo( AsmCodeBuf buf ) + "\tLD\tA,7FH\n" + "\tOUT\t(92H),A\n" ); } - if( this.usesOSVersion ) { - /* - * Zeichenkette "CAOS " ab F000h suchen und das naechste - * ACSII-Zeichen als Hauptversion interpretieren, - * wenn es eine Ziffer ist. - */ + if( this.usesM052 ) { buf.append( "\tXOR\tA\n" - + "\tLD\t(X_M_OS),A\n" - + "\tLD\tBC,0EFFFH\n" - + "X_IOS1:\tINC\tBC\n" - + "\tLD\tA,B\n" - + "\tOR\tA\n" - + "\tJR\tZ,X_IOS4\n" // nicht gefunden - + "\tLD\tH,B\n" - + "\tLD\tL,C\n" - + "\tLD\tDE,X_CAOS\n" - + "X_IOS2:\tLD\tA,(DE)\n" - + "\tINC\tDE\n" - + "\tOR\tA\n" - + "\tJR\tZ,X_IOS3\n" // gefunden - + "\tCP\t(HL)\n" - + "\tJR\tNZ,X_IOS1\n" - + "\tINC\tHL\n" - + "\tJR\tX_IOS2\n" - + "X_IOS3:\tLD\tA,(HL)\n" - + "\tSUB\t30H\n" - + "\tJR\tC,X_IOS4\n" - + "\tCP\t0A0H\n" - + "\tJR\tNC,X_IOS4\n" - + "\tLD\t(X_M_OS),A\n" - + "X_IOS4:\n" ); - } - if( this.usesColors ) { - buf.append( "\tLD\tA,(0B7A3H)\n" - + "\tAND\t0F8H\n" - + "\tLD\t(X_M_INK),A\n" ); + + "\tLD\t(X_M_M052SLOT),A\n" + + "\tLD\t(X_M_M052STAT),A\n" + + "\tLD\t(X_M_M052USED),A\n" ); } if( this.needsFullWindow ) { // Fenstergroesse pruefen und ggf. auf Maximalewerte setzen @@ -250,17 +243,11 @@ public void appendInitTo( AsmCodeBuf buf ) + "\tDB\t00H\n" + "X_IWN2:\n" ); } - if( this.usesM052 ) { - buf.append( "\tXOR\tA\n" - + "\tLD\t(X_M_M052SLOT),A\n" - + "\tLD\t(X_M_M052STAT),A\n" - + "\tLD\t(X_M_M052USED),A\n" ); - } } @Override - public void appendInput( + public void appendInputTo( AsmCodeBuf buf, boolean xckbrk, boolean xinkey, @@ -302,9 +289,9 @@ public void appendInput( @Override - public void appendProlog( - BasicCompiler compiler, + public void appendPrologTo( AsmCodeBuf buf, + BasicCompiler compiler, String appName ) { boolean done = false; @@ -325,21 +312,21 @@ public void appendProlog( @Override - public void appendWChar( AsmCodeBuf buf ) + public void appendWCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0028H\n" ); } @Override - public void appendWPixel( AsmCodeBuf buf ) + public void appendWPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0140H\n" ); } @Override - public void appendXCLS( AsmCodeBuf buf ) + public void appendXClsTo( AsmCodeBuf buf ) { buf.append( "XCLS:\tLD\tA,0CH\n" + "\tCALL\t0F003H\n" @@ -356,122 +343,62 @@ public void appendXCLS( AsmCodeBuf buf ) * DE: Hintergrundfarbe */ @Override - public void appendXCOLOR( AsmCodeBuf buf ) + public void appendXColorTo( AsmCodeBuf buf ) { - buf.append( "XCOLOR:\tLD\tA,L\n" - + "\tAND\t1FH\n" - + "\tLD\tL,A\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tLD\t(X_M_INK),A\n" - + "\tLD\tA,E\n" - + "\tAND\t07H\n" - + "\tLD\tE,A\n" - + "\tLD\tA,02H\n" - + "\tLD\t(0B781H),A\n" - + "\tCALL\t0F003H\n" - + "\tDB\t0FH\n" - + "\tRET\n" ); - this.usesColors = true; + this.usesXCOLOR = true; } /* - * Zeichnen einer horizontaler Linie, - * Bei CAOS 4 oder hoeher wird die LINE-Funktion von CAOS verwendet, - * da diese etwas schneller als die JKCEMU-Routine ist. - * Die LINE-Funktion im CAOS 3 ist dagegen wesentlich langsamer - * und wird deshalb nicht verwendet. + * Zeichnen einer horizontalen Linie * Parameter: * BC: Laenge - 1 - * DE: linke X-Koordinate + * DE: linke X-Koordinate, nicht kleiner 0 * HL: Y-Koordinate */ - @Override - public void appendXHLINE( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXHLineTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XHLINE:" ); - if( this.usesX_MPEN ) { - buf.append( "\tLD\tA,(X_MPEN)\n" - + "\tOR\tA\n" - + "\tRET\tZ\n" - + "\tEX\tAF,AF\'\n" ); - } - buf.append( "\tBIT\t7,B\n" // Laenge pruefen + buf.append( "XHLINE:\tBIT\t7,B\n" + "\tRET\tNZ\n" - + "\tBIT\t7,H\n" // Y pruefen - + "\tRET\tNZ\n" - + "\tLD\tA,(X_M_OS)\n" - + "\tCP\t04H\n" - + "\tJR\tC,XHLIN2\n" - + "\tLD\t(0B782H),DE\n" - + "\tLD\t(0B784H),HL\n" - + "\tLD\t(0B788H),HL\n" - + "\tEX\tDE,HL\n" - + "\tADD\tHL,BC\n" - + "\tLD\t(0B786H),HL\n" - + "\tEXX\n" + "\tPUSH\tBC\n" + "\tPUSH\tDE\n" - + "\tPUSH\tHL\n" ); - if( this.usesX_MPEN ) { - buf.append( "\tLD\tB,02H\n" - + "\tEX\tAF,AF\'\n" - + "\tCP\t02H\n" - + "\tJR\tZ,XHLIN1\n" - + "\tDEC\tB\n" - + "\tCP\t03H\n" - + "\tJR\tZ,XHLIN1\n" - + "\tDEC\tB\n" - + "XHLIN1:\tLD\tA,(X_M_INK)\n" - + "\tOR\tB\n" ); - } else { - buf.append( "\tLD\tA,(X_M_INK)\n" ); - } - buf.append( "\tLD\t(0B7D6H),A\n" - + "\tCALL\t0F003H\n" - + "\tDB\t3EH\n" - + "\tPOP\tHL\n" - + "\tPOP\tDE\n" - + "\tPOP\tBC\n" - + "\tEXX\n" - + "\tRET\n" - + "XHLIN2:\tPUSH\tBC\n" - + "\tPUSH\tDE\n" + "\tPUSH\tHL\n" - + "\tCALL\tXPSET\n" + + "\tCALL\tX_PST\n" + + "\tEXX\n" + "\tPOP\tHL\n" + "\tPOP\tDE\n" + "\tPOP\tBC\n" - + "\tLD\tA,B\n" - + "\tOR\tC\n" - + "\tRET\tZ\n" + + "\tRET\tC\n" + + "\tLD\tH,00H\n" + + "XHLINE1:\n" + + "\tOR\tH\n" + + "\tLD\tH,A\n" + + "\tSRL\tA\n" + + "\tJR\tC,XHLINE2\n" + + "\tINC\tDE\n" + "\tDEC\tBC\n" + + "\tBIT\t7,B\n" + + "\tJR\tZ,XHLINE1\n" + + "\tLD\tA,H\n" + + "\tEXX\n" + + "\tJR\tXPSET_A\n" + + "XHLINE2:\n" + + "\tLD\tA,H\n" + + "\tEXX\n" + + "\tCALL\tXPSET_A\n" + + "\tEXX\n" + "\tINC\tDE\n" - + "\tJR\tXHLIN2\n" ); - appendXPSET( buf, compiler ); - this.needsFullWindow = true; - this.usesOSVersion = true; + + "\tDEC\tBC\n" + + "\tLD\tH,00H\n" + + "\tJR\tXHLINE\n" ); + appendXPSetTo( buf, compiler ); } @Override - public void appendXINK( AsmCodeBuf buf ) + public void appendXInkTo( AsmCodeBuf buf ) { - buf.append( "XINK:\tLD\tA,L\n" - + "\tAND\t1FH\n" - + "\tLD\tL,A\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tLD\t(X_M_INK),A\n" - + "\tLD\tA,01H\n" - + "\tLD\t(0B781H),A\n" - + "\tCALL\t0F003H\n" - + "\tDB\t0FH\n" - + "\tRET\n" ); - this.usesColors = true; + this.usesXINK = true; } @@ -481,45 +408,15 @@ public void appendXINK( AsmCodeBuf buf ) * HL: Nummer des Joysticks (0: erster Joystick) * Rueckgabewert: * HL: Joystickstatus - * Bit 0: links - * Bit 1: rechts - * Bit 2: runter - * Bit 3: hoch - * Bit 4: Aktionsknopf */ - public void appendXJOY( AsmCodeBuf buf ) + public void appendXJoyTo( AsmCodeBuf buf ) { buf.append( "XJOY:\tLD\tA,H\n" + "\tOR\tL\n" + "\tJR\tNZ,XJOY1\n" + "\tIN\tA,(90H)\n" + "\tCPL\n" - + "\tLD\tB,A\n" - + "\tSRL\tA\n" - + "\tSRL\tA\n" - + "\tAND\t03H\n" // links und rechts maskieren - + "\tLD\tC,A\n" - + "\tLD\tA,B\n" - + "\tSLA\tA\n" - + "\tAND\t04H\n" // runter maskieren - + "\tOR\tC\n" - + "\tLD\tC,A\n" - + "\tLD\tA,B\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tAND\t08H\n" // hoch maskieren - + "\tOR\tC\n" - + "\tLD\tC,A\n" - + "\tLD\tA,B\n" - + "\tSRL\tA\n" - + "\tAND\t10H\n" // Aktionsknopf 1 maskieren - + "\tOR\tC\n" - + "\tLD\tC,A\n" - + "\tLD\tA,B\n" - + "\tSLA\tA\n" - + "\tAND\t20H\n" // Aktionsknopf 2 maskieren - + "\tOR\tC\n" + + "\tAND\t3FH\n" + "\tLD\tL,A\n" + "\tLD\tH,00H\n" + "\tRET\n" @@ -530,7 +427,7 @@ public void appendXJOY( AsmCodeBuf buf ) @Override - public void appendXLOCATE( AsmCodeBuf buf ) + public void appendXLocateTo( AsmCodeBuf buf ) { buf.append( "XLOCATE:\n" + "\tLD\tH,E\n" @@ -541,7 +438,7 @@ public void appendXLOCATE( AsmCodeBuf buf ) @Override - public void appendXLPTCH( AsmCodeBuf buf ) + public void appendXLPtchTo( AsmCodeBuf buf ) { buf.append( "XLPTCH:\tCALL\t0F003H\n" + "\tDB\t02H\n" @@ -550,7 +447,7 @@ public void appendXLPTCH( AsmCodeBuf buf ) @Override - public void appendXOUTCH( AsmCodeBuf buf ) + public void appendXOutchTo( AsmCodeBuf buf ) { if( !this.xoutchAppended ) { buf.append( "XOUTCH:\tCALL\t0F003H\n" @@ -561,23 +458,186 @@ public void appendXOUTCH( AsmCodeBuf buf ) } + /* + * XPAINT_LEFT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach links, + * Der Startpunkt selbst wird nicht geprueft + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * (PAINT_M_X1): X-Endkoordinate der gefuellten Linie, + * kleiner oder gleich X-Startpunkt + * + * XPAINT_RIGHT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach rechts + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * CY=1: Pixel im Startpunkt bereits gesetzt (gefuellt) + * oder ausserhalb des sichtbaren Bereichs + * (PAINT_M_X2): X-Endkoordinate der gefuellten Linie, nur bei CY=0 + */ @Override - public void appendXPAPER( AsmCodeBuf buf ) + public void appendXPaintTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPAPER:\tLD\tA,L\n" - + "\tAND\t07H\n" - + "\tLD\tE,A\n" - + "\tLD\tA,(X_M_INK)\n" + buf.append( "XPAINT_LEFT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "\tLD\tA,D\n" + + "\tOR\tE\n" + + "\tJR\tZ,XPAINT_LEFT5\n" + + "\tDEC\tDE\n" + + "XPAINT_LEFT1:\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_PST\n" + + "\tPUSH\tHL\n" + + "\tEXX\n" + + "\tPOP\tHL\n" + + "\tPOP\tDE\n" + + "\tJR\tC,XPAINT_LEFT4\n" + + "\tLD\tC,A\n" + + "\tLD\tB,(HL)\n" + + "\tLD\tA,B\n" + + "XPAINT_LEFT2:\n" + + "\tAND\tC\n" + + "\tJR\tNZ,XPAINT_LEFT3\n" + + "\tLD\tA,B\n" + + "\tOR\tC\n" + + "\tLD\tB,A\n" + + "\tDEC\tDE\n" + + "\tSLA\tC\n" + + "\tJR\tNC,XPAINT_LEFT2\n" + + "\tLD\t(HL),B\n" + + "\tEXX\n" + + "\tCALL\tXPSET_WR_COLOR\n" + + "\tEXX\n" + + "\tJR\tXPAINT_LEFT1\n" + + "XPAINT_LEFT3:\n" + + "\tLD\t(HL),B\n" + + "\tEXX\n" + + "\tCALL\tXPSET_WR_COLOR\n" + + "\tEXX\n" + + "XPAINT_LEFT4:\n" + + "\tINC\tDE\n" + + "XPAINT_LEFT5:\n" + + "\tLD\t(PAINT_M_X1),DE\n" + + "\tRET\n" + + "XPAINT_RIGHT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_PST\n" + + "\tPUSH\tHL\n" + + "\tEXX\n" + + "\tPOP\tHL\n" + + "\tPOP\tDE\n" + + "\tRET\tC\n" + + "\tLD\tC,A\n" + + "\tLD\tB,(HL)\n" + + "\tAND\tB\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tJR\tXPAINT_RIGHT2\n" + + "XPAINT_RIGHT1:\n" + + "\tAND\tC\n" + + "\tJR\tNZ,XPAINT_RIGHT3\n" + + "XPAINT_RIGHT2:\n" + + "\tLD\tA,B\n" + + "\tOR\tC\n" + + "\tLD\tB,A\n" + + "\tINC\tDE\n" + + "\tSRL\tC\n" + + "\tJR\tNC,XPAINT_RIGHT1\n" + + "\tLD\t(HL),B\n" + + "\tEXX\n" + + "\tCALL\tXPSET_WR_COLOR\n" + + "\tEXX\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_PST\n" + + "\tPUSH\tHL\n" + + "\tEXX\n" + + "\tPOP\tHL\n" + + "\tPOP\tDE\n" + + "\tJR\tC,XPAINT_RIGHT4\n" + + "\tLD\tC,A\n" + + "\tLD\tB,(HL)\n" + + "\tAND\tB\n" + + "\tJR\tNZ,XPAINT_RIGHT4\n" + + "\tJR\tXPAINT_RIGHT2\n" + + "XPAINT_RIGHT3:\n" + + "\tLD\t(HL),B\n" + + "\tEXX\n" + + "\tCALL\tXPSET_WR_COLOR\n" + + "\tEXX\n" + + "XPAINT_RIGHT4:\n" + + "\tDEC\tDE\n" + + "\tLD\t(PAINT_M_X2),DE\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + appendXPSetTo( buf, compiler ); + } + + + @Override + public void appendXPaperTo( AsmCodeBuf buf ) + { + this.usesXPAPER = true; + } + + + /* + * Farbe eines Pixels ermitteln + * Parameter: + * DE: X-Koordinate (0...255) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * HL >= 0: Farbcode des Pixels + * HL=-1: Pixel exisitiert nicht + */ + @Override + public void appendXPointTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPOINT:\tCALL\tX_PST\n" + + "\tJR\tC,XPOINT5\n" + + "\tLD\tB,A\n" + + "\tLD\tA,D\n" + + "\tOR\tE\n" + + "\tJR\tNZ,XPOINT1\n" + // Farbbyte lesen bei KC85/4..5 + + "\tDB\t0DDH,0CBH,01H,0CFH\t;SET 1,(IX+01H),A\n" + + "\tOUT\t(84H),A\n" + + "\tLD\tC,(HL)\n" + + "\tDB\t0DDH,0CBH,01H,8FH\t;RES 1,(IX+01H),A\n" + + "\tOUT\t(84H),A\n" + + "\tJR\tXPOINT2\n" + // Farbbyte lesen bei KC85/2..3 + + "XPOINT1:\n" + + "\tLD\tA,(DE)\n" + + "\tLD\tC,A\n" + // Pixel auswerten + + "XPOINT2:\n" + + "\tLD\tA,B\n" + + "\tAND\t(HL)\n" + + "\tLD\tA,C\n" + + "\tJR\tZ,XPOINT3\n" + "\tSRL\tA\n" + "\tSRL\tA\n" + "\tSRL\tA\n" + + "\tAND\t1FH\n" + + "\tJR\tXPOINT4\n" + + "XPOINT3:\n" + + "\tAND\t07H\n" + + "XPOINT4:\n" + "\tLD\tL,A\n" - + "\tLD\tA,02H\n" - + "\tLD\t(0B781H),A\n" - + "\tCALL\t0F003H\n" - + "\tDB\t0FH\n" + + "\tLD\tH,00H\n" + + "\tRET\n" + + "XPOINT5:\n" + + "\tLD\tHL,0FFFFH\n" + "\tRET\n" ); - this.usesColors = true; + appendPixUtilTo( buf ); } @@ -588,74 +648,83 @@ public void appendXPAPER( AsmCodeBuf buf ) * HL: Y-Koordinate */ @Override - public void appendXPRES( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPResTo( AsmCodeBuf buf, BasicCompiler compiler ) { - if( !this.xpresAppended ) { - buf.append( "XPRES:\tLD\tA,H\n" - + "\tOR\tA\n" - + "\tRET\tNZ\n" - + "XPRES1:\tLD\tA,L\n" - + "\tLD\t(0B7D5H),A\n" - + "\tLD\t(0B7D3H),DE\n" - + "\tCALL\t0F003H\n" - + "\tDB\t2FH\n" - + "\tRET\n" ); - this.needsFullWindow = true; - this.xpresAppended = true; - } + buf.append( "XPRES:\tCALL\tX_PST\n" + + "\tRET\tC\n" + + "\tCPL\n" + + "\tAND\t(HL)\n" + + "\tJR\tXPSET_WR_A\n" ); + appendXPSetTo( buf, compiler ); + appendPixUtilTo( buf ); } /* - * Setzen eines Pixels + * Setzen eines Pixels unter Beruecksichtigung des eingestellten Stiftes * Parameter: * DE: X-Koordinate * HL: Y-Koordinate */ @Override - public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPSetTo( AsmCodeBuf buf, BasicCompiler compiler ) { if( !this.xpsetAppended ) { - buf.append( "XPSET:\tLD\tA,H\n" - + "\tOR\tA\n" - + "\tRET\tNZ\n" ); - if( this.usesX_MPEN ) { - buf.append( "\tLD\tB,00H\n" - + "\tLD\tA,(X_MPEN)\n" + buf.append( "XPSET:\tCALL\tX_PST\n" + + "\tRET\tC\n" + + "XPSET_A:\n" ); + if( this.usesX_M_PEN ) { + buf.append( "\tLD\tB,A\n" + + "\tLD\tA,(X_M_PEN)\n" + "\tDEC\tA\n" - + "\tJR\tZ,XPSET1\n" - + "\tRET\tM\n" + + "\tJR\tZ,XPSET2\n" // Stift 1 (Normal) + + "\tDEC\tA\n" + + "\tJR\tZ,XPSET1\n" // Stift 2 (Loeschen) + "\tDEC\tA\n" - + "\tJR\tZ,XPRES1\n" - + "\tINC\tB\n" - + "\tLD\tA,(X_M_OS)\n" - + "\tCP\t04H\n" - + "\tJR\tC,XPSET3\n" - + "XPSET1:" ); - this.usesOSVersion = true; - } - buf.append( "\tLD\tA,L\n" - + "\tLD\t(0B7D5H),A\n" - + "\tLD\t(0B7D3H),DE\n" - + "\tLD\tA,(X_M_INK)\n" ); - if( this.usesX_MPEN ) { - buf.append( "\tOR\tB\n" - + "XPSET2:" ); - } - buf.append( "\tLD\t(0B7D6H),A\n" - + "\tCALL\t0F003H\n" - + "\tDB\t30H\n" - + "\tRET\n" ); - if( this.usesX_MPEN ) { - buf.append( "XPSET3:\tCALL\tXPRES1\n" - + "\tRET\tC\n" + "\tRET\tNZ\n" - + "\tLD\tA,(X_M_INK)\n" - + "\tJR\tXPSET2\n" ); - appendXPRES( buf, compiler ); + + "\tLD\tA,(HL)\n" // Stift 3 (XOR-Mode) + + "\tXOR\tB\n" + + "\tJR\tXPSET_WR_A\n" + + "XPSET1:\tLD\tA,B\n" // Pixel loeschen + + "\tCPL\n" + + "\tAND\t(HL)\n" + + "\tJR\tXPSET_WR_A\n" + + "XPSET2:\tLD\tA,B\n" ); // Pixel setzen } + buf.append( "XPSET_OR_A:\n" + + "\tOR\t(HL)\n" + + "XPSET_WR_A:\n" + + "\tLD\t(HL),A\n" + // Farbbyte schreiben + + "XPSET_WR_COLOR:\n" + + "\tLD\tA,D\n" + + "\tOR\tE\n" + + "\tJR\tNZ,XPSET3\n" + /* + * bei KC85/4..5 Farbebene einschalten, Farbe setzen + * und wieder Pixelebene einschalten + */ + + "\tDB\t0DDH,0CBH,01H,0CFH\t;SET 1,(IX+01H),A\n" + + "\tOUT\t(84H),A\n" + + "\tLD\tA,(0B7A3H)\n" + + "\tLD\t(HL),A\n" + + "\tDB\t0DDH,0CBH,01H,8FH\t;RES 1,(IX+01H),A\n" + + "\tOUT\t(84H),A\n" + + "\tRET\n" + /* + * KC85/2..3: Zur Sicherheit pruefen, + * ob DE eine Adresse im Farbspeicher enthaelt + */ + + "XPSET3:\tLD\tA,D\n" + + "\tCP\t0A8H\n" + + "\tRET\tC\n" + + "\tCP\t0B2H\n" + + "\tRET\tNC\n" + + "\tLD\tA,(0B7A3H)\n" + + "\tLD\t(DE),A\n" + + "\tRET\n" ); + appendPixUtilTo( buf ); this.needsFullWindow = true; - this.usesColors = true; this.xpsetAppended = true; } } @@ -667,56 +736,24 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) * DE: X-Koordinate * HL: Y-Koordinate * Rueckgabe: - * HL=0: Pixel nicht gesetzt - * HL=1: Pixel gesetzt + * HL=0: Pixel nicht gesetzt + * HL=1: Pixel gesetzt + * HL=-1: Pixel exisistiert nicht */ @Override - public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPTestTo( AsmCodeBuf buf, BasicCompiler compiler ) { - /* - * Da es keine Systemfunktion zum Testen eines Pixels gibt, - * aber die benoetigte Information mit der Funktion zum Loeschen - * eines Pixels gewonnen werden kann, - * wird das Pixel geloescht und, sofern es gesetzt war, - * wieder mit der gleichen Vordergrundfarbe neu gesetzt. - */ - buf.append( "XPTEST:\tLD\tA,H\n" - + "\tOR\tA\n" - + "\tJR\tNZ,XPTST1\n" - + "\tLD\tA,L\n" - + "\tLD\t(0B7D5H),A\n" - + "\tLD\t(0B7D3H),DE\n" - + "\tCALL\t0F003H\n" - + "\tDB\t2FH\n" - + "\tJR\tC,XPTST1\n" + buf.append( "XPTEST:\tCALL\tX_PST\n" + + "\tJR\tC,X_PTEST1\n" + + "\tAND\t(HL)\n" + "\tLD\tHL,0000H\n" + "\tRET\tZ\n" - + "\tAND\t0F8H\n" - + "\tLD\t(0B7D6H),A\n" - + "\tCALL\t0F003H\n" - + "\tDB\t30H\n" - + "\tLD\tHL,0001H\n" + + "\tINC\tHL\n" + "\tRET\n" - + "XPTST1:\tLD\tHL,0FFFFH\n" + + "X_PTEST1:\n" + + "\tLD\tHL,0FFFFH\n" + "\tRET\n" ); - } - - - /* - * Target-ID-String - */ - @Override - public void appendXTARID( AsmCodeBuf buf ) - { - buf.append( "XTARID:\tDB\t\'KC85\'\n" - + "\tDB\t00H\n" ); - } - - - @Override - public boolean createsCodeFor( EmuSys emuSys ) - { - return emuSys != null ? (emuSys instanceof KC85) : false; + appendPixUtilTo( buf ); } @@ -728,65 +765,22 @@ public int get100msLoopCount() @Override - public int getColorBlack() - { - return 0; - } - - - @Override - public int getColorBlinking() - { - return 0x10; - } - - - @Override - public int getColorBlue() - { - return 0x01; - } - - - @Override - public int getColorCyan() - { - return 0x05; - } - - - @Override - public int getColorGreen() - { - return 0x04; - } - - - @Override - public int getColorMagenta() + public String[] getBasicTargetNames() { - return 0x03; + return new String[] { BASIC_TARGET_NAME }; } @Override - public int getColorRed() + public int getCompatibilityLevel( EmuSys emuSys ) { - return 0x02; - } - - - @Override - public int getColorWhite() - { - return 0x07; - } - - - @Override - public int getColorYellow() - { - return 0x06; + int rv = 0; + if( emuSys != null ) { + if( emuSys instanceof KC85 ) { + rv = 3; + } + } + return rv; } @@ -798,30 +792,36 @@ public int getDefaultBegAddr() @Override - public int getGraphicScreenNum() + public String getHostName() { - return 0; + return "KC85"; } @Override - public int getKCNetBaseIOAddr() + public int getMaxAppNameLen() { - return 0x28; + return 38; } @Override - public int[] getVdipBaseIOAddresses() + public String getStartCmd( EmuSys emuSys, String appName, int begAdAdr ) { - return new int[] { 0x2C }; + String rv = null; + if( emuSys != null ) { + if( emuSys instanceof KC85 ) { + rv = appName; + } + } + return appName; } @Override - public boolean needsEnableKCNet() + public int[] getVdipBaseIOAddresses() { - return true; + return new int[] { 0x2C }; } @@ -837,22 +837,16 @@ public void reset() { super.reset(); this.needsFullWindow = false; - this.usesColors = false; this.usesJoystick = false; - this.usesOSVersion = false; this.usesM052 = false; - this.xpresAppended = false; + this.usesXCOLOR = false; + this.usesXINK = false; + this.usesXPAPER = false; + this.pixUtilAppended = false; this.xpsetAppended = false; } - @Override - public boolean supportsAppName() - { - return true; - } - - @Override public boolean supportsColors() { @@ -902,9 +896,84 @@ public boolean supportsXLPTCH() } + @Override + public boolean supportsXPAINT_LEFT_RIGHT() + { + return true; + } + + @Override public String toString() { return "KC85/2..5, HC900"; } + + + /* --- Hilfsfunktionen --- */ + + protected void appendPixUtilTo( AsmCodeBuf buf ) + { + if( !this.pixUtilAppended ) { + buf.append( + /* + * Pruefen der Parameter, ermitteln von Informationen zu einem Pixel + * und bei KC85/4..5 einschalten der Pixelebene + * + * Parameter: + * DE: X-Koordinate (0...319) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * CY=1: Pixel ausserhalb des gueltigen Bereichs + * A: Bitmuster mit einem gesetzten Bit, + * dass das Pixel in der Speicherzelle beschreibt + * C: 84h (nur bei DE=0) + * DE: Adresse im Farbspeicher (KC85/2..3) oder 0 (KC85/4..5) + * HL: Adresse im Pixelspeicher + */ + "X_PST:\tLD\tA,H\n" + + "\tOR\tA\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tLD\tA,D\n" + + "\tOR\tA\n" + + "\tJR\tZ,X_PST1\n" + + "\tCP\t02H\n" + + "\tCCF\n" + + "\tRET\tC\n" + + "\tLD\tA,3FH\n" + + "\tCP\tE\n" + + "\tRET\tC\n" + + "X_PST1:\tLD\tA,L\n" + + "\tCPL\n" + + "\tLD\tH,A\n" + + "\tLD\tA,E\n" + + "\tAND\t07H\n" + + "\tLD\tB,A\n" + + "\tLD\tC,80H\n" + + "\tJR\tZ,X_PST3\n" + + "X_PST2:\tSRL\tC\n" + + "\tDJNZ\tX_PST2\n" + + "X_PST3:\tPUSH\tBC\n" + + "\tSRL\tD\n" + + "\tRR\tE\n" + + "\tSRL\tE\n" + + "\tSRL\tE\n" + + "\tLD\tL,E\n" + + "\tLD\tDE,0000H\n" + + "\tCALL\t0F003H\n" + + "\tDB\t34H\n" + + "\tPOP\tBC\n" + // bei KC85/4..5 Pixelebene einschalten + + "\tLD\tA,D\n" + + "\tOR\tE\n" // CY=0 + + "\tLD\tA,C\n" + + "\tRET\tNZ\n" // KC85/2..3 + + "\tDB\t0DDH,0CBH,01H,88H\t;RES 1,(IX+01H),B\n" + + "\tLD\tC,84H\n" + + "\tOUT\t(C),B\n" + + "\tRET\n" ); + this.pixUtilAppended = true; + } + } } diff --git a/src/jkcemu/programming/basic/target/KramerMCTarget.java b/src/jkcemu/programming/basic/target/KramerMCTarget.java index e2d1b10..1dfec3d 100644 --- a/src/jkcemu/programming/basic/target/KramerMCTarget.java +++ b/src/jkcemu/programming/basic/target/KramerMCTarget.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -16,6 +16,8 @@ public class KramerMCTarget extends AbstractTarget { + public static final String BASIC_TARGET_NAME = "TARGET_KRAMER"; + public KramerMCTarget() { // leer @@ -23,21 +25,21 @@ public KramerMCTarget() @Override - public void appendExit( AsmCodeBuf buf ) + public void appendExitTo( AsmCodeBuf buf ) { buf.append( "\tJP\t0000H\n" ); } @Override - public void appendHChar( AsmCodeBuf buf ) + public void appendHCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0010H\n" ); } @Override - public void appendInput( + public void appendInputTo( AsmCodeBuf buf, boolean xckbrk, boolean xinkey, @@ -66,14 +68,14 @@ public void appendInput( @Override - public void appendWChar( AsmCodeBuf buf ) + public void appendWCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0040H\n" ); } @Override - public void appendXLPTCH( AsmCodeBuf buf ) + public void appendXLPtchTo( AsmCodeBuf buf ) { buf.append( "XLPTCH:\tLD\tC,A\n" + "\tJP\t00ECH\n" ); @@ -81,7 +83,7 @@ public void appendXLPTCH( AsmCodeBuf buf ) @Override - public void appendXOUTCH( AsmCodeBuf buf ) + public void appendXOutchTo( AsmCodeBuf buf ) { if( !this.xoutchAppended ) { buf.append( "XOUTCH:\tLD\tC,A\n" @@ -92,7 +94,7 @@ public void appendXOUTCH( AsmCodeBuf buf ) @Override - public void appendXOUTNL( AsmCodeBuf buf ) + public void appendXOutnlTo( AsmCodeBuf buf ) { buf.append( "XOUTNL:\tLD\tC,0DH\n" + "\tCALL\t00E6H\n" @@ -101,28 +103,30 @@ public void appendXOUTNL( AsmCodeBuf buf ) } - /* - * Target-ID-String - */ @Override - public void appendXTARID( AsmCodeBuf buf ) + public int get100msLoopCount() { - buf.append( "XTARID:\tDB\t\'KRAMER\'\n" - + "\tDB\t00H\n" ); + return 42; } @Override - public boolean createsCodeFor( EmuSys emuSys ) + public String[] getBasicTargetNames() { - return emuSys != null ? (emuSys instanceof KramerMC) : false; + return new String[] { BASIC_TARGET_NAME }; } @Override - public int get100msLoopCount() + public int getCompatibilityLevel( EmuSys emuSys ) { - return 42; + int rv = 0; + if( emuSys != null ) { + if( emuSys instanceof KramerMC ) { + rv = 3; + } + } + return rv; } @@ -133,6 +137,26 @@ public int getDefaultBegAddr() } + @Override + public String getHostName() + { + return "Kramer-MC"; + } + + + @Override + public String getStartCmd( EmuSys emuSys, String appName, int begAddr ) + { + String rv = null; + if( (emuSys != null) && (begAddr >= 0) ) { + if( emuSys instanceof KramerMC ) { + rv = String.format( "G%04X", begAddr ); + } + } + return rv; + } + + @Override public boolean supportsXLPTCH() { diff --git a/src/jkcemu/programming/basic/target/LLC2HIRESTarget.java b/src/jkcemu/programming/basic/target/LLC2HIRESTarget.java index 7d58dc8..174fdc5 100644 --- a/src/jkcemu/programming/basic/target/LLC2HIRESTarget.java +++ b/src/jkcemu/programming/basic/target/LLC2HIRESTarget.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -17,19 +17,26 @@ import java.util.Set; import jkcemu.base.EmuSys; import jkcemu.emusys.LLC2; +import jkcemu.emusys.ac1_llc2.AbstractSCCHSys; import jkcemu.programming.basic.*; public class LLC2HIRESTarget extends SCCHTarget { + public static final String BASIC_TARGET_NAME = "TARGET_LLC2"; + private boolean needsScreenSizeChar; private boolean needsScreenSizePixel; private boolean pixUtilAppended; + private boolean xpsetAppended; + private boolean xptestAppended; + private boolean usesScreens; - public void LLC2HIRESTarget() + public LLC2HIRESTarget() { - reset(); + setNamedValue( "GRAPHICSCREEN", 1 ); + setNamedValue( "LASTSCREEN", 1 ); } @@ -37,47 +44,63 @@ public void LLC2HIRESTarget() public void appendBssTo( AsmCodeBuf buf ) { super.appendBssTo( buf ); - buf.append( "X_MSCR:\tDS\t1\n" ); + if( this.usesScreens ) { + buf.append( "X_M_SCREEN:\tDS\t1\n" ); + } } @Override - public void appendEtc( AsmCodeBuf buf ) + public void appendEtcPastXOutTo( AsmCodeBuf buf ) { if( this.needsScreenSizeChar ) { - buf.append( "X_HCHR:\tLD\tHL,0020H\n" + if( this.usesScreens ) { + buf.append( "X_HCHR:\tLD\tHL,0020H\n" + "\tJR\tX_SSZC\n" + "X_WCHR:\tLD\tHL,0040H\n" - + "X_SSZC:\tLD\tA,(X_MSCR)\n" + + "X_SSZC:\tLD\tA,(X_M_SCREEN)\n" + "\tOR\tA\n" + "\tRET\tZ\n" + "\tLD\tL,00H\n" + "\tRET\n" ); + } else { + buf.append( "X_HCHR:\tLD\tHL,0020H\n" + + "\tRET\n" + + "X_WCHR:\tLD\tHL,0040H\n" + + "\tRET\n" ); + } } if( this.needsScreenSizePixel ) { - buf.append( "X_HPIX:\tLD\tHL,0100H\n" + if( this.usesScreens ) { + buf.append( "X_HPIX:\tLD\tHL,0100H\n" + "\tJR\tX_SSZP\n" + "X_WPIX:\tLD\tHL,0200H\n" - + "X_SSZP:\tLD\tA,(X_MSCR)\n" + + "X_SSZP:\tLD\tA,(X_M_SCREEN)\n" + "\tDEC\tA\n" + "\tRET\tZ\n" + "\tLD\tHL,00H\n" + "\tRET\n" ); + } else { + buf.append( "X_HPIX:\n" + + "X_WPIX:\tLD\tHL,0000H\n" + + "\tRET\n" ); + } } } @Override - public void appendExit( AsmCodeBuf buf ) + public void appendPreExitTo( AsmCodeBuf buf ) { - buf.append( "\tXOR\tA\n" - + "\tOUT\t(0EEH),A\n" - + "\tJP\t07FDH\n" ); + if( this.usesScreens ) { + buf.append( "\tXOR\tA\n" + + "\tOUT\t(0EEH),A\n" ); + } } @Override - public void appendHChar( AsmCodeBuf buf ) + public void appendHCharTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_HCHR\n" ); this.needsScreenSizeChar = true; @@ -85,7 +108,7 @@ public void appendHChar( AsmCodeBuf buf ) @Override - public void appendHPixel( AsmCodeBuf buf ) + public void appendHPixelTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_HPIX\n" ); this.needsScreenSizePixel = true; @@ -96,23 +119,27 @@ public void appendHPixel( AsmCodeBuf buf ) public void appendInitTo( AsmCodeBuf buf ) { super.appendInitTo( buf ); - buf.append( "\tXOR\tA\n" - + "\tLD\t(X_MSCR),A\n" + if( this.usesScreens ) { + buf.append( "\tXOR\tA\n" + + "\tLD\t(X_M_SCREEN),A\n" + "\tOUT\t(0EEH),A\n" ); + } } @Override - public void appendSwitchToTextScreen( AsmCodeBuf buf ) + public void appendSwitchToTextScreenTo( AsmCodeBuf buf ) { - buf.append( "\tXOR\tA\n" + if( this.usesScreens ) { + buf.append( "\tXOR\tA\n" + "\tOUT\t(0EEH),A\n" - + "\tLD\t(X_MSCR),A\n" ); + + "\tLD\t(X_M_SCREEN),A\n" ); + } } @Override - public void appendWChar( AsmCodeBuf buf ) + public void appendWCharTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_WCHR\n" ); this.needsScreenSizeChar = true; @@ -120,7 +147,7 @@ public void appendWChar( AsmCodeBuf buf ) @Override - public void appendWPixel( AsmCodeBuf buf ) + public void appendWPixelTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_WPIX\n" ); this.needsScreenSizePixel = true; @@ -128,9 +155,10 @@ public void appendWPixel( AsmCodeBuf buf ) @Override - public void appendXCLS( AsmCodeBuf buf ) + public void appendXClsTo( AsmCodeBuf buf ) { - buf.append( "XCLS:\tLD\tA,(X_MSCR)\n" + if( this.usesScreens ) { + buf.append( "XCLS:\tLD\tA,(X_M_SCREEN)\n" + "\tCP\t01\n" + "\tJR\tZ,XCLS1\n" + "\tLD\tA,0CH\n" @@ -145,7 +173,186 @@ public void appendXCLS( AsmCodeBuf buf ) + "\tDEC\tC\n" + "\tJR\tNZ,XCLS2\n" + "\tRET\n" ); - appendXOUTCH( buf ); + } else { + buf.append( "XCLS:\tLD\tA,0CH\n" + + "\tJR\tXOUTCH\n" ); + } + appendXOutchTo( buf ); + } + + + /* + * Zeichnen einer horizontalen Linie + * Parameter: + * BC: Laenge - 1 + * DE: linke X-Koordinate, nicht kleiner 0 + * HL: Y-Koordinate + */ + public void appendXHLineTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XHLINE:\tBIT\t7,B\n" + + "\tRET\tNZ\n" + + "\tPUSH\tBC\n" + + "\tCALL\tX_PST\n" + + "\tPOP\tBC\n" + + "\tRET\tC\n" + + "\tLD\tD,00H\n" + + "XHLINE1:\n" + + "\tOR\tD\n" + + "\tLD\tD,A\n" + + "\tSRL\tA\n" + + "\tJR\tNC,XHLINE2\n" + + "\tLD\tA,D\n" ); + if( this.usesX_M_PEN ) { + buf.append( "\tCALL\tXPSET_A\n" ); + } else { + buf.append( "\tOR\t(HL)\n" + + "\tLD\t(HL),A\n" ); + } + buf.append( "\tINC\tHL\n" + + "\tLD\tA,L\n" + + "\tAND\t3FH\n" + + "\tRET\tZ\n" + + "\tLD\tA,80H\n" + + "\tLD\tD,00H\n" + + "XHLINE2:\n" + + "\tDEC\tBC\n" + + "\tBIT\t7,B\n" + + "\tJR\tZ,XHLINE1\n" + + "\tLD\tA,D\n" + + "\tOR\tA\n" + + "\tJR\tNZ,XPSET_A\n" + + "\tRET\n" ); + appendXPSetTo( buf, compiler ); + } + + + /* + * XPAINT_LEFT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach links, + * Der Startpunkt selbst wird nicht geprueft + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * (PAINT_M_X1): X-Endkoordinate der gefuellten Linie, + * kleiner oder gleich X-Startpunkt + * + * XPAINT_RIGHT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach rechts + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * CY=1: Pixel im Startpunkt bereits gesetzt (gefuellt) + * oder ausserhalb des sichtbaren Bereichs + * (PAINT_M_X2): X-Endkoordinate der gefuellten Linie, nur bei CY=0 + */ + @Override + public void appendXPaintTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPAINT_LEFT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "\tLD\tA,D\n" + + "\tOR\tE\n" + + "\tJR\tZ,XPAINT_LEFT6\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tDEC\tDE\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_PST\n" + + "\tPOP\tDE\n" + + "\tJR\tC,XPAINT_LEFT5\n" + + "\tLD\tC,A\n" + + "\tLD\tB,(HL)\n" + + "XPAINT_LEFT1:\n" + + "\tLD\tA,B\n" + + "XPAINT_LEFT2:\n" + + "\tAND\tC\n" + + "\tJR\tNZ,XPAINT_LEFT4\n" + + "\tLD\tA,B\n" + + "\tOR\tC\n" + + "\tLD\tB,A\n" + + "\tDEC\tDE\n" + + "\tSLA\tC\n" + + "\tJR\tNC,XPAINT_LEFT2\n" + + "\tLD\t(HL),B\n" + + "\tDEC\tHL\n" + + "\tBIT\t0,D\n" + + "\tJR\tZ,XPAINT_LEFT3\n" + + "\tLD\tA,E\n" + + "\tINC\tA\n" + + "\tJR\tZ,XPAINT_LEFT5\n" + + "XPAINT_LEFT3:\n" + + "\tLD\tB,(HL)\n" + + "\tLD\tC,01H\n" + + "\tJR\tXPAINT_LEFT1\n" + + "XPAINT_LEFT4:\n" + + "\tLD\t(HL),B\n" + + "XPAINT_LEFT5:\n" + + "\tINC\tDE\n" + + "XPAINT_LEFT6:\n" + + "\tLD\t(PAINT_M_X1),DE\n" + + "\tRET\n" + + "XPAINT_RIGHT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_PST\n" + + "\tPOP\tDE\n" + + "\tRET\tC\n" + + "\tLD\tC,A\n" + + "\tLD\tB,(HL)\n" + + "\tAND\tB\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tJR\tXPAINT_RIGHT3\n" + + "XPAINT_RIGHT1:\n" + + "\tLD\tA,B\n" + + "XPAINT_RIGHT2:\n" + + "\tAND\tC\n" + + "\tJR\tNZ,XPAINT_RIGHT4\n" + + "XPAINT_RIGHT3:\n" + + "\tLD\tA,B\n" + + "\tOR\tC\n" + + "\tLD\tB,A\n" + + "\tINC\tDE\n" + + "\tSRL\tC\n" + + "\tJR\tNC,XPAINT_RIGHT2\n" + + "\tLD\t(HL),B\n" + + "\tINC\tHL\n" + + "\tLD\tA,D\n" + + "\tAND\t01H\n" + + "\tOR\tE\n" + + "\tJR\tZ,XPAINT_RIGHT5\n" + + "\tLD\tB,(HL)\n" + + "\tLD\tC,80H\n" + + "\tJR\tXPAINT_RIGHT1\n" + + "XPAINT_RIGHT4:\n" + + "\tLD\t(HL),B\n" + + "XPAINT_RIGHT5:\n" + + "\tDEC\tDE\n" + + "\tLD\t(PAINT_M_X2),DE\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + appendPixUtilTo( buf, compiler ); + } + + + /* + * Farbe eines Pixels ermitteln, + * Die Rueckgabewerte sind identisch zur Funktion XPTEST + * + * Parameter: + * DE: X-Koordinate (0...255) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * HL >= 0: Farbcode des Pixels + * HL=-1: Pixel exisitiert nicht + */ + @Override + public void appendXPointTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + appendXPTestTo( buf, compiler ); } @@ -156,34 +363,34 @@ public void appendXCLS( AsmCodeBuf buf ) * HL: Y-Koordinate */ @Override - public void appendXPRES( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPResTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPRES:\tCALL\tX_PCK\n" + buf.append( "XPRES:\tCALL\tX_PST\n" + "\tRET\tC\n" - + "\tCALL\tX_PST\n" + "\tCPL\n" + "\tAND\t(HL)\n" + "\tLD\t(HL),A\n" + "\tRET\n" ); - appendPixUtil( buf, compiler ); + appendPixUtilTo( buf, compiler ); } /* - * Setzen eines Pixels + * Setzen eines Pixels unter Beruecksichtigung des eingestellten Stiftes * Parameter: * DE: X-Koordinate * HL: Y-Koordinate */ @Override - public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPSetTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPSET:\tCALL\tX_PCK\n" + if( !this.xpsetAppended ) { + buf.append( "XPSET:\tCALL\tX_PST\n" + "\tRET\tC\n" - + "\tCALL\tX_PST\n" ); - if( this.usesX_MPEN ) { - buf.append( "\tLD\tB,A\n" - + "\tLD\tA,(X_MPEN)\n" + + "XPSET_A:\n" ); + if( this.usesX_M_PEN ) { + buf.append( "\tLD\tE,A\n" + + "\tLD\tA,(X_M_PEN)\n" + "\tDEC\tA\n" + "\tJR\tZ,XPSET2\n" // Stift 1 (Normal) + "\tDEC\tA\n" @@ -191,20 +398,22 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + "\tDEC\tA\n" + "\tRET\tNZ\n" + "\tLD\tA,(HL)\n" // Stift 3 (XOR-Mode) - + "\tXOR\tB\n" + + "\tXOR\tE\n" + "\tLD\t(HL),A\n" + "\tRET\n" - + "XPSET1:\tLD\tA,B\n" // Pixel loeschen + + "XPSET1:\tLD\tA,E\n" // Pixel loeschen + "\tCPL\n" + "\tAND\t(HL)\n" + "\tLD\t(HL),A\n" + "\tRET\n" - + "XPSET2\tLD\tA,B\n" ); // Pixel setzen - } - buf.append( "\tOR\t(HL)\n" + + "XPSET2\tLD\tA,E\n" ); // Pixel setzen + } + buf.append( "\tOR\t(HL)\n" + "\tLD\t(HL),A\n" + "\tRET\n" ); - appendPixUtil( buf, compiler ); + appendPixUtilTo( buf, compiler ); + this.xpsetAppended = true; + } } @@ -219,22 +428,30 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) * HL=-1: Pixel existiert nicht */ @Override - public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPTestTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPTEST:\tLD\tA,(X_MSCR)\n" + if( !this.xptestAppended ) { + if( this.usesScreens ) { + buf.append( "XPOINT:\n" + + "XPTEST:\tLD\tA,(X_M_SCREEN)\n" + "\tCP\t01H\n" - + "\tJR\tNZ,XPTST1\n" - + "\tCALL\tX_PCK1\n" - + "\tJR\tC,XPTST1\n" - + "\tCALL\tX_PST\n" + + "\tJR\tNZ,XPTEST1\n" + + "\tCALL\tX_PST1\n" + + "\tJR\tC,XPTEST1\n" + "\tAND\t(HL)\n" + "\tLD\tHL,0000H\n" + "\tRET\tZ\n" + "\tINC\tHL\n" + "\tRET\n" - + "XPTST1:\tLD\tHL,0FFFFH\n" + + "XPTEST1:\n" + + "\tLD\tHL,0FFFFH\n" + "\tRET\n" ); - appendPixUtil( buf, compiler ); + appendPixUtilTo( buf, compiler ); + } else { + super.appendXPTestTo( buf, compiler ); + } + this.xptestAppended = true; + } } @@ -247,66 +464,85 @@ public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) * CY=1: Screen-Nummer nicht unterstuetzt */ @Override - public void appendXSCRS( AsmCodeBuf buf ) + public void appendXScreenTo( AsmCodeBuf buf ) { - buf.append( "XSCRS:\tLD\tA,H\n" + if( this.usesScreens ) { + buf.append( "XSCREEN:\n" + + "\tLD\tA,H\n" + "\tOR\tA\n" - + "\tJR\tNZ,XSCRS1\n" - + "\tLD\tA,(X_MSCR)\n" + + "\tJR\tNZ,XSCRN1\n" + + "\tLD\tA,(X_M_SCREEN)\n" + "\tCP\tL\n" + "\tRET\tZ\n" + "\tLD\tA,L\n" + "\tOR\tA\n" - + "\tJR\tZ,XSCRS3\n" + + "\tJR\tZ,XSCRN3\n" + "\tDEC\tA\n" - + "\tJR\tZ,XSCRS2\n" - + "XSCRS1:\tSCF\n" + + "\tJR\tZ,XSCRN2\n" + + "XSCRN1:\tSCF\n" + "\tRET\n" - + "XSCRS2:\tLD\tA,50H\n" // HIRES, 8000h - + "XSCRS3:\tOUT\t(0EEH),A\n" + + "XSCRN2:\tLD\tA,50H\n" // HIRES, 8000h + + "XSCRN3:\tOUT\t(0EEH),A\n" + "\tLD\tA,L\n" - + "\tLD\t(X_MSCR),A\n" + + "\tLD\t(X_M_SCREEN),A\n" + "\tOR\tA\n" // CY=0 + "\tRET\n" ); + } } - /* - * Target-ID-String - */ @Override - public void appendXTARID( AsmCodeBuf buf ) + public int get100msLoopCount() { - buf.append( "XTARID:\tDB\t\'LLC2_HIRES\'\n" - + "\tDB\t00H\n" ); + return 85; } @Override - public boolean createsCodeFor( EmuSys emuSys ) + public String[] getBasicTargetNames() { - return emuSys != null ? (emuSys instanceof LLC2) : false; + return add( super.getBasicTargetNames(), BASIC_TARGET_NAME ); } @Override - public int get100msLoopCount() + public int getCompatibilityLevel( EmuSys emuSys ) { - return 85; + int rv = 0; + if( emuSys != null ) { + if( emuSys instanceof LLC2 ) { + rv = 3; + } else if( emuSys instanceof AbstractSCCHSys ) { + rv = 1; + } + } + return rv; } @Override - public int getGraphicScreenNum() + public String getHostName() { - return 1; + return "LLC2"; } @Override - public int getLastScreenNum() + public int[] getVdipBaseIOAddresses() { - return 1; + return new int[] { 0xDC, 0xFC, 0xE4 }; + } + + + @Override + public void preAppendLibraryCode( BasicCompiler compiler ) + { + super.preAppendLibraryCode( compiler ); + if( compiler.usesLibItem( BasicLibrary.LibItem.SCREEN ) + || compiler.usesLibItem( BasicLibrary.LibItem.XSCREEN ) ) + { + this.usesScreens = true; + } } @@ -317,6 +553,9 @@ public void reset() this.needsScreenSizeChar = false; this.needsScreenSizePixel = false; this.pixUtilAppended = false; + this.xpsetAppended = false; + this.xptestAppended = false; + this.usesScreens = false; } @@ -334,6 +573,20 @@ public boolean supportsXCLS() } + @Override + public boolean supportsXHLINE() + { + return true; + } + + + @Override + public boolean supportsXPAINT_LEFT_RIGHT() + { + return true; + } + + @Override public String toString() { @@ -343,48 +596,43 @@ public String toString() /* --- private Methoden --- */ - private void appendPixUtil( AsmCodeBuf buf, BasicCompiler compiler ) + private void appendPixUtilTo( AsmCodeBuf buf, BasicCompiler compiler ) { if( !this.pixUtilAppended ) { - buf.append( - /* - * Pruefen der Parameter - * DE: X-Koordinate (0...512) - * HL: Y-Koordinate (0...255) - * Rueckgabe: - * CY=1: Pixel ausserhalb des gueltigen Bereichs - */ - "X_PCK:\tLD\tA,(X_MSCR)\n" + /* + * Pruefen der Parameter und + * ermitteln von Informationen zu einem Pixel + * Parameter: + * DE: X-Koordinate (0...512) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * CY=1: Pixel ausserhalb des gueltigen Bereichs + * A: Bitmuster mit einem gesetzten Bit, + * dass das Pixel in der Speicherzelle beschreibt + * HL: Speicherzelle, in der sich das Pixel befindet + */ + if( this.usesScreens ) { + buf.append( "X_PST:\tLD\tA,(X_M_SCREEN)\n" + "\tCP\t01H\n" - + "\tJR\tNZ,X_PCK3\n" - + "X_PCK1:\tLD\tA,D\n" + + "\tJR\tNZ,X_PST7\n" + + "X_PST1:\tLD\tA,H\n" + + "\tOR\tA\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tLD\tA,D\n" + + "\tOR\tA\n" + + "\tJR\tZ,X_PST2\n" + "\tCP\t02H\n" - + "\tJR\tNC,X_PCK2\n" - + "\tLD\tA,H\n" - + "\tOR\tA\n" // CY=0 - + "\tRET\tZ\n" - + "X_PCK2:\tSCF\n" - + "\tRET\n" - + "X_PCK3:" ); - appendExitNoGraphicsScreen( buf, compiler ); - /* - * Ermitteln von Informationen zu einem Pixel - * Parameter: - * DE: X-Koordinate (0...512) - * HL: Y-Koordinate (0...255) - * Rueckgabe: - * A: Bitmuster mit einem gesetzten Bit, - * dass das Pixel in der Speicherzelle beschreibt - * HL: Speicherzelle, in der sich das Pixel befindet - */ - buf.append( "X_PST:\tLD\tA,E\n" + + "\tCCF\n" + + "\tRET\tC\n" + + "X_PST2:\tLD\tA,E\n" + "\tAND\t07H\n" + "\tLD\tB,A\n" + "\tLD\tC,80H\n" - + "\tJR\tZ,X_PST2\n" - + "X_PST1:\tSRL\tC\n" - + "\tDJNZ\tX_PST1\n" - + "X_PST2:\tSRL\tD\n" + + "\tJR\tZ,X_PST4\n" + + "X_PST3:\tSRL\tC\n" + + "\tDJNZ\tX_PST3\n" + + "X_PST4:\tSRL\tD\n" + "\tRR\tE\n" + "\tSRL\tE\n" + "\tSRL\tE\n" @@ -393,12 +641,12 @@ private void appendPixUtil( AsmCodeBuf buf, BasicCompiler compiler ) + "\tLD\tA,L\n" + "\tLD\tHL,0000H\n" + "\tAND\t07H\n" - + "\tJR\tZ,X_PST4\n" + + "\tJR\tZ,X_PST6\n" + "\tLD\tDE,0800H\n" - + "X_PST3:\tADD\tHL,DE\n" + + "X_PST5:\tADD\tHL,DE\n" + "\tDEC\tA\n" - + "\tJR\tNZ,X_PST3\n" - + "X_PST4:\tEX\tDE,HL\n" + + "\tJR\tNZ,X_PST5\n" + + "X_PST6:\tEX\tDE,HL\n" + "\tPOP\tHL\n" + "\tLD\tA,L\n" + "\tAND\t0F8H\n" @@ -409,12 +657,18 @@ private void appendPixUtil( AsmCodeBuf buf, BasicCompiler compiler ) + "\tADD\tHL,DE\n" + "\tEX\tDE,HL\n" + "\tLD\tHL,0BFC0H\n" - + "\tOR\tA\n" // CY=0, A unveraendert + + "\tOR\tA\n" // CY=0 + "\tSBC\tHL,DE\n" + "\tPOP\tDE\n" + "\tADD\tHL,DE\n" + "\tLD\tA,C\n" - + "\tRET\n" ); + + "\tOR\tA\n" // CY=0 + + "\tRET\n" + + "X_PST7:\n" ); + } else { + buf.append( "X_PST:\n" ); + } + appendExitNoGraphicsScreenTo( buf, compiler ); this.pixUtilAppended = true; } } diff --git a/src/jkcemu/programming/basic/target/SCCHTarget.java b/src/jkcemu/programming/basic/target/SCCHTarget.java index 5990f07..226ecdb 100644 --- a/src/jkcemu/programming/basic/target/SCCHTarget.java +++ b/src/jkcemu/programming/basic/target/SCCHTarget.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -20,28 +20,35 @@ public class SCCHTarget extends AbstractTarget { + public static final String BASIC_TARGET_NAME = "TARGET_SCCH"; + + public SCCHTarget() { - // leer + setNamedValue( "JOYST_LEFT", 0x04 ); + setNamedValue( "JOYST_RIGHT", 0x08 ); + setNamedValue( "JOYST_DOWN", 0x02 ); + setNamedValue( "JOYST_UP", 0x01 ); + setNamedValue( "JOYST_BUTTON1", 0x10 ); } @Override - public void appendExit( AsmCodeBuf buf ) + public void appendExitTo( AsmCodeBuf buf ) { buf.append( "\tJP\t07FDH\n" ); } @Override - public void appendHChar( AsmCodeBuf buf ) + public void appendHCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0020H\n" ); } @Override - public void appendInput( + public void appendInputTo( AsmCodeBuf buf, boolean xckbrk, boolean xinkey, @@ -74,9 +81,9 @@ public void appendInput( @Override - public void appendProlog( - BasicCompiler compiler, + public void appendPrologTo( AsmCodeBuf buf, + BasicCompiler compiler, String appName ) { if( appName != null ) { @@ -89,11 +96,12 @@ public void appendProlog( || ((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z')) ) { - buf.append( "\tJR\tXSTART\n" + buf.append( "\tJR\t" ); + buf.append( BasicCompiler.START_LABEL ); + buf.append( "\n" + "\tDB\t00H,09H,\'" ); buf.append( ch ); - buf.append( "\',0DH\n" - + "XSTART:" ); + buf.append( "\',0DH\n" ); done = true; } } @@ -109,7 +117,7 @@ public void appendProlog( @Override - public void appendWChar( AsmCodeBuf buf ) + public void appendWCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0040H\n" ); } @@ -121,38 +129,13 @@ public void appendWChar( AsmCodeBuf buf ) * HL: Nummer des Joysticks (0: erster Joystick) * Rueckgabewert: * HL: Joystickstatus - * Bit 0: links - * Bit 1: rechts - * Bit 2: runter - * Bit 3: hoch - * Bit 4: Aktionsknopf */ - public void appendXJOY( AsmCodeBuf buf ) + public void appendXJoyTo( AsmCodeBuf buf ) { buf.append( "XJOY:\tLD\tA,H\n" + "\tOR\tL\n" + "\tJR\tNZ,XJOY1\n" + "\tCALL\t0EB4H\n" - + "\tLD\tB,A\n" - + "\tSRL\tA\n" - + "\tSRL\tA\n" - + "\tAND\t03H\n" // links und rechts maskieren - + "\tLD\tC,A\n" - + "\tLD\tA,B\n" - + "\tSLA\tA\n" - + "\tAND\t04H\n" // runter maskieren - + "\tOR\tC\n" - + "\tLD\tC,A\n" - + "\tLD\tA,B\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tSLA\tA\n" - + "\tAND\t08H\n" // hoch maskieren - + "\tOR\tC\n" - + "\tLD\tC,A\n" - + "\tLD\tA,B\n" - + "\tAND\t10H\n" // Aktionsknopf maskieren - + "\tOR\tC\n" + "\tLD\tL,A\n" + "\tLD\tH,00H\n" + "\tRET\n" @@ -162,7 +145,7 @@ public void appendXJOY( AsmCodeBuf buf ) @Override - public void appendXLOCATE( AsmCodeBuf buf ) + public void appendXLocateTo( AsmCodeBuf buf ) { buf.append( "XLOCATE:\n" + "\tLD\tA,D\n" @@ -201,7 +184,7 @@ public void appendXLOCATE( AsmCodeBuf buf ) @Override - public void appendXLPTCH( AsmCodeBuf buf ) + public void appendXLPtchTo( AsmCodeBuf buf ) { buf.append( "XLPTCH:\tLD\tB,A\n" + "\tLD\tA,(1821H)\n" @@ -218,7 +201,7 @@ public void appendXLPTCH( AsmCodeBuf buf ) @Override - public void appendXOUTCH( AsmCodeBuf buf ) + public void appendXOutchTo( AsmCodeBuf buf ) { if( !this.xoutchAppended ) { buf.append( "XOUTCH:\tCP\t0AH\n" @@ -233,34 +216,41 @@ public void appendXOUTCH( AsmCodeBuf buf ) * Ausgabe eines Zeilenumbruchs */ @Override - public void appendXOUTNL( AsmCodeBuf buf ) + public void appendXOutnlTo( AsmCodeBuf buf ) { buf.append( "XOUTNL:\tLD\tA,0DH\n" + "\tJP\t0010H\n" ); } - /* - * Target-ID-String - */ @Override - public void appendXTARID( AsmCodeBuf buf ) + public int get100msLoopCount() + { + return 58; + } + + + @Override + public String[] getBasicTargetNames() { - buf.append( "XTARID:\tDB\t\'SCCH\'\n" - + "\tDB\t00H\n" ); + return new String[] { BASIC_TARGET_NAME }; } @Override - public boolean createsCodeFor( EmuSys emuSys ) + public int getCompatibilityLevel( EmuSys emuSys ) { - boolean rv = false; + int rv = 0; if( emuSys != null ) { if( emuSys instanceof AC1 ) { - rv = (((AC1) emuSys).emulates2010Mode() - || ((AC1) emuSys).emulatesSCCHMode()); + rv = 2; + if( ((AC1) emuSys).emulates2010Mode() + || ((AC1) emuSys).emulatesSCCHMode() ) + { + rv = 3; + } } else if( emuSys instanceof LLC2 ) { - rv = true; + rv = 3; } } return rv; @@ -268,37 +258,43 @@ public boolean createsCodeFor( EmuSys emuSys ) @Override - public int get100msLoopCount() + public int getDefaultBegAddr() { - return 58; + return 0x2000; } @Override - public int getDefaultBegAddr() + public String getHostName() { - return 0x2000; + return "AC1-LLC2"; } @Override - public int getKCNetBaseIOAddr() + public int getMaxAppNameLen() { - return 0xC0; + return 1; } @Override - public int[] getVdipBaseIOAddresses() + public String getStartCmd( EmuSys emuSys, String appName, int begAddr ) { - return new int[] { 0xDC, 0xFC }; + String rv = null; + if( (emuSys != null) && (begAddr >= 0) ) { + if( (emuSys instanceof AC1) || (emuSys instanceof LLC2) ) { + rv = String.format( "J %04X", begAddr ); + } + } + return rv; } @Override - public boolean supportsAppName() + public int[] getVdipBaseIOAddresses() { - return true; + return new int[] { 0xDC, 0xFC }; } diff --git a/src/jkcemu/programming/basic/target/Z1013PetersTarget.java b/src/jkcemu/programming/basic/target/Z1013PetersTarget.java index 57c2383..f861964 100644 --- a/src/jkcemu/programming/basic/target/Z1013PetersTarget.java +++ b/src/jkcemu/programming/basic/target/Z1013PetersTarget.java @@ -1,5 +1,5 @@ /* - * (c) 2013 Jens Mueller + * (c) 2013-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -15,73 +15,106 @@ public class Z1013PetersTarget extends Z1013Target { + public static final String BASIC_TARGET_NAME = "TARGET_Z1013_64X16"; + private boolean needsScreenSizeHChar; private boolean needsScreenSizeWChar; private boolean needsScreenSizePixel; + private boolean usesScreens; public Z1013PetersTarget() { - reset(); + setNamedValue( "LASTSCREEN", 1 ); } + /* --- ueberschriebene Methoden --- */ + @Override public void appendBssTo( AsmCodeBuf buf ) { super.appendBssTo( buf ); - buf.append( "X_MSCR:\tDS\t1\n" ); + if( this.usesScreens ) { + buf.append( "X_M_SCREEN:\tDS\t1\n" ); + } + } + + + @Override + protected void appendCheckGraphicScreenTo( + AsmCodeBuf buf, + BasicCompiler compiler ) + { + if( this.usesScreens ) { + buf.append( "\tLD\tA,(X_M_SCREEN)\n" + + "\tOR\tA\n" + + "\tJR\tZ,X_PST1\n" ); + appendExitNoGraphicsScreenTo( buf, compiler ); + } } @Override - public void appendEtc( AsmCodeBuf buf ) + public void appendEtcPastXOutTo( AsmCodeBuf buf ) { - buf.append( "X_SCR0:\n" + if( this.usesScreens ) { + buf.append( "X_SCREEN0:\n" + "\tIN\tA,(04H)\n" + "\tAND\t7FH\n" + "\tOUT\t(04H),A\n" + "\tXOR\tA\n" - + "\tLD\t(X_MSCR),A\n" - + "\tRET\n" ); - if( this.needsScreenSizeHChar ) { - buf.append( "X_HCHR:\tLD\tHL,0010H\n" - + "\tLD\tA,(X_MSCR)\n" - + "\tDEC\tA\n" - + "\tRET\tZ\n" - + "\tLD\tL,20H\n" + + "\tLD\t(X_M_SCREEN),A\n" + "\tRET\n" ); } - if( this.needsScreenSizeWChar ) { - buf.append( "X_WCHR:\tLD\tHL,0040H\n" - + "\tLD\tA,(X_MSCR)\n" + if( this.needsScreenSizeHChar || this.needsScreenSizeWChar ) { + if( this.usesScreens ) { + if( this.needsScreenSizeHChar ) { + buf.append( "X_HCHR:\tLD\tHL,0010H\n" ); + if( this.needsScreenSizeWChar ) { + buf.append( "\tJR\tX_HWCHR\n" ); + } + } + if( this.needsScreenSizeWChar ) { + buf.append( "X_WCHR:\tLD\tHL,0040H\n" ); + } + buf.append( "X_HWCHR:\n" + + "\tLD\tA,(X_M_SCREEN)\n" + "\tDEC\tA\n" + "\tRET\tZ\n" + "\tLD\tL,0020H\n" + "\tRET\n" ); + } else { + buf.append( "X_HCHR:\n" + + "X_WCHR:\tLD\tHL,0020H\n" + + "\tRET\n" ); + } } if( this.needsScreenSizePixel ) { buf.append( "X_HPIX:\n" - + "X_WPIX:\tLD\tHL,0040H\n" - + "\tLD\tA,(X_MSCR)\n" + + "X_WPIX:\tLD\tHL,0040H\n" ); + if( this.usesScreens ) { + buf.append( "\tLD\tA,(X_M_SCREEN)\n" + "\tOR\tA\n" + "\tRET\tZ\n" - + "\tLD\tL,00H\n" - + "\tRET\n" ); + + "\tLD\tL,00H\n" ); + } + buf.append( "\tRET\n" ); } } @Override - public void appendExit( AsmCodeBuf buf ) + public void appendPreExitTo( AsmCodeBuf buf ) { - buf.append( "\tCALL\tX_SCR0\n" ); - super.appendExit( buf ); + if( this.usesScreens ) { + buf.append( "\tCALL\tX_SCREEN0\n" ); + } } @Override - public void appendHChar( AsmCodeBuf buf ) + public void appendHCharTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_HCHR\n" ); this.needsScreenSizeHChar = true; @@ -89,7 +122,7 @@ public void appendHChar( AsmCodeBuf buf ) @Override - public void appendHPixel( AsmCodeBuf buf ) + public void appendHPixelTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_HPIX\n" ); this.needsScreenSizePixel = true; @@ -100,21 +133,25 @@ public void appendHPixel( AsmCodeBuf buf ) public void appendInitTo( AsmCodeBuf buf ) { super.appendInitTo( buf ); - buf.append( "\tXOR\tA\n" - + "\tLD\t(X_MSCR),A\n" - + "\tCALL\tX_SCR0\n" ); + if( this.usesScreens ) { + buf.append( "\tXOR\tA\n" + + "\tLD\t(X_M_SCREEN),A\n" + + "\tCALL\tX_SCREEN0\n" ); + } } @Override - public void appendSwitchToTextScreen( AsmCodeBuf buf ) + public void appendSwitchToTextScreenTo( AsmCodeBuf buf ) { - buf.append( "\tCALL\tX_SCR0\n" ); + if( this.usesScreens ) { + buf.append( "\tCALL\tX_SCREEN0\n" ); + } } @Override - public void appendWChar( AsmCodeBuf buf ) + public void appendWCharTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_WCHR\n" ); this.needsScreenSizeWChar = true; @@ -122,7 +159,7 @@ public void appendWChar( AsmCodeBuf buf ) @Override - public void appendWPixel( AsmCodeBuf buf ) + public void appendWPixelTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_WPIX\n" ); this.needsScreenSizePixel = true; @@ -136,10 +173,11 @@ public void appendWPixel( AsmCodeBuf buf ) * HL: Spalte, >= 0 */ @Override - public void appendXLOCATE( AsmCodeBuf buf ) + public void appendXLocateTo( AsmCodeBuf buf ) { - buf.append( "XLOCATE:\n" - + "\tLD\tA,(X_MSCR)\n" + if( this.usesScreens ) { + buf.append( "XLOCATE:\n" + + "\tLD\tA,(X_M_SCREEN)\n" + "\tDEC\tA\n" + "\tJR\tZ,XLOCATE1\n" + "\tLD\tA,1FH\n" @@ -179,24 +217,28 @@ public void appendXLOCATE( AsmCodeBuf buf ) + "\tPOP\tHL\n" + "\tEX\tDE,HL\n" + "\tRET\n" ); + } else { + super.appendXLocateTo( buf ); + } } @Override - public void appendXOUTCH( AsmCodeBuf buf ) + public void appendXOutchTo( AsmCodeBuf buf ) { - if( !this.xoutchAppended ) { - buf.append( "XOUTCH:\tCP\t0AH\n" + if( this.usesScreens ) { + if( !this.xoutchAppended ) { + buf.append( "XOUTCH:\tCP\t0AH\n" + "\tRET\tZ\n" + "\tLD\tD,A\n" - + "\tLD\tA,(X_MSCR)\n" + + "\tLD\tA,(X_M_SCREEN)\n" + "\tDEC\tA\n" + "\tJR\tZ,XOUTC1\n" + "\tLD\tA,D\n" + "\tRST\t20H\n" + "\tDB\t00H\n" + "\tRET\n" - // Bildschirmroutine fuer 64x16-Modus + // Bildschirmroutine fuer 64x16-Modus + "XOUTC1:\tLD\tA,(001FH)\n" + "\tLD\tHL,(002BH)\n" + "\tLD\t(HL),A\n" @@ -264,22 +306,50 @@ public void appendXOUTCH( AsmCodeBuf buf ) + "\tAND\t03FH\n" + "\tJR\tNZ,XOUTC10\n" + "\tJR\tXOUTC3\n" ); - this.xoutchAppended = true; + this.xoutchAppended = true; + } + } else { + super.appendXOutchTo( buf ); } } /* - * Ausgabe eines Zeilenumbruchs + * Testen eines Pixels + * Parameter: + * DE: X-Koordinate + * HL: Y-Koordinate + * Rueckgabe: + * HL=0: Pixel nicht gesetzt + * HL=1: Pixel gesetzt + * HL=-1: Pixel existiert nicht */ @Override - public void appendXOUTNL( AsmCodeBuf buf ) + public void appendXPTestTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XOUTNL:\tLD\tA,0DH\n" ); - if( this.xoutchAppended ) { - buf.append( "\tJR\tXOUTCH\n" ); + if( this.usesScreens ) { + if( !this.xptestAppended ) { + buf.append( "XPOINT:\n" + + "XPTEST:\tLD\tA,(X_M_SCREEN)\n" + + "\tOR\tA\n" + + "\tSCF\n" + + "\tRET\tZ\n" + + "\tCALL\tX_PST1\n" + + "\tJR\tC,X_PTEST1\n" + + "\tLD\tA,B\n" + + "\tAND\tC\n" + + "\tLD\tHL,0000H\n" + + "\tRET\tZ\n" + + "\tINC\tHL\n" + + "\tRET\n" + + "X_PTEST1:\n" + + "\tLD\tHL,0FFFFH\n" + + "\tRET\n" ); + appendPixUtilTo( buf, compiler ); + this.xptestAppended = true; + } } else { - appendXOUTCH( buf ); + super.appendXPTestTo( buf, compiler ); } } @@ -293,78 +363,52 @@ public void appendXOUTNL( AsmCodeBuf buf ) * CY=1: Screen-Nummer nicht unterstuetzt */ @Override - public void appendXSCRS( AsmCodeBuf buf ) + public void appendXScreenTo( AsmCodeBuf buf ) { - buf.append( "XSCRS:\tLD\tA,H\n" + if( this.usesScreens ) { + buf.append( "XSCREEN:\n" + + "\tLD\tA,H\n" + "\tOR\tA\n" - + "\tJR\tNZ,XSCRS1\n" - + "\tLD\tA,(X_MSCR)\n" + + "\tJR\tNZ,XSCRN1\n" + + "\tLD\tA,(X_M_SCREEN)\n" + "\tCP\tL\n" + "\tRET\tZ\n" + "\tLD\tC,04H\n" + "\tIN\tB,(C)\n" + "\tLD\tA,L\n" + "\tOR\tA\n" - + "\tJR\tZ,XSCRS2\n" + + "\tJR\tZ,XSCRN2\n" + "\tCP\t01H\n" - + "\tJR\tZ,XSCRS3\n" - + "XSCRS1:\tSCF\n" + + "\tJR\tZ,XSCRN3\n" + + "XSCRN1:\tSCF\n" + "\tRET\n" - + "XSCRS2:\tRES\t7,B\n" // SCREEN 0 - + "\tJR\tXSCRS4\n" - + "XSCRS3:\tSET\t7,B\n" // SCREEN 1 - + "XSCRS4:\tOUT\t(C),B\n" - + "\tLD\t(X_MSCR),A\n" + + "XSCRN2:\tRES\t7,B\n" // SCREEN 0 + + "\tJR\tXSCRN4\n" + + "XSCRN3:\tSET\t7,B\n" // SCREEN 1 + + "XSCRN4:\tOUT\t(C),B\n" + + "\tLD\t(X_M_SCREEN),A\n" + "\tOR\tA\n" // CY=0 + "\tRET\n" ); - } - - - /* - * Target-ID-String - */ - @Override - public void appendXTARID( AsmCodeBuf buf ) - { - buf.append( "XTARID:\tDB\t\'Z1013_64X16\'\n" - + "\tDB\t00H\n" ); + } } @Override - protected void appendX_PCK( AsmCodeBuf buf, BasicCompiler compiler ) + public String[] getBasicTargetNames() { - buf.append( "X_PCK:" - // Pruefen, ob der Grafik-Screen eingestellt ist - + "\tLD\tA,(X_MSCR)\n" - + "\tOR\tA\n" - + "\tJR\tNZ,X_PCK2\n" - /* - * Pruefen der Parameter - * DE: X-Koordinate (0...63) - * HL: Y-Koordinate (0...63) - * Rueckgabe: - * CY=1: Pixel ausserhalb des gueltigen Bereichs - */ - + "\tLD\tA,D\n" - + "\tOR\tH\n" - + "\tJR\tZ,X_PCK1\n" - + "\tSCF\n" - + "\tRET\n" - + "X_PCK1:\tLD\tA,3FH\n" - + "\tCP\tE\n" - + "\tRET\tC\n" - + "\tCP\tL\n" - + "\tRET\n" - + "X_PCK2:" ); - appendExitNoGraphicsScreen( buf, compiler ); + return add( super.getBasicTargetNames(), BASIC_TARGET_NAME ); } @Override - public int getLastScreenNum() + public void preAppendLibraryCode( BasicCompiler compiler ) { - return 1; + super.preAppendLibraryCode( compiler ); + if( compiler.usesLibItem( BasicLibrary.LibItem.SCREEN ) + || compiler.usesLibItem( BasicLibrary.LibItem.XSCREEN ) ) + { + this.usesScreens = true; + } } @@ -375,6 +419,7 @@ public void reset() this.needsScreenSizeHChar = false; this.needsScreenSizeWChar = false; this.needsScreenSizePixel = false; + this.usesScreens = false; } diff --git a/src/jkcemu/programming/basic/target/Z1013Target.java b/src/jkcemu/programming/basic/target/Z1013Target.java index 5d0d9a9..7be4f4d 100644 --- a/src/jkcemu/programming/basic/target/Z1013Target.java +++ b/src/jkcemu/programming/basic/target/Z1013Target.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -17,39 +17,50 @@ public class Z1013Target extends AbstractTarget { + public static final String BASIC_TARGET_NAME = "TARGET_Z1013"; + + protected boolean xptestAppended; + private boolean pixUtilAppended; private boolean xpsetAppended; public Z1013Target() { - reset(); + setNamedValue( "GRAPHICSCREEN", 0 ); + setNamedValue( "JOYST_LEFT", 0x01 ); + setNamedValue( "JOYST_RIGHT", 0x02 ); + setNamedValue( "JOYST_DOWN", 0x04 ); + setNamedValue( "JOYST_UP", 0x08 ); + setNamedValue( "JOYST_BUTTON1", 0x10 ); } + /* --- ueberschriebene Methoden --- */ + @Override - public void appendExit( AsmCodeBuf buf ) + public void appendExitTo( AsmCodeBuf buf ) { buf.append( "\tJP\t0038H\n" ); } @Override - public void appendHChar( AsmCodeBuf buf ) + public void appendHCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0020H\n" ); } @Override - public void appendHPixel( AsmCodeBuf buf ) + public void appendHPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0040H\n" ); } @Override - public void appendInput( + public void appendInputTo( AsmCodeBuf buf, boolean xckbrk, boolean xinkey, @@ -83,14 +94,14 @@ public void appendInput( @Override - public void appendWChar( AsmCodeBuf buf ) + public void appendWCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0020H\n" ); } @Override - public void appendWPixel( AsmCodeBuf buf ) + public void appendWPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0040H\n" ); } @@ -103,7 +114,7 @@ public void appendWPixel( AsmCodeBuf buf ) * <>0: Cursor einschalten */ @Override - public void appendXCURS( AsmCodeBuf buf ) + public void appendXCursTo( AsmCodeBuf buf ) { buf.append( "XCURS:\tLD\tA,H\n" + "\tOR\tL\n" @@ -128,14 +139,9 @@ public void appendXCURS( AsmCodeBuf buf ) * HL: Nummer des Joysticks (0: erster Joystick) * Rueckgabewert: * HL: Joystickstatus - * Bit 0: links - * Bit 1: rechts - * Bit 2: runter - * Bit 3: hoch - * Bit 4: Aktionsknopf */ @Override - public void appendXJOY( AsmCodeBuf buf ) + public void appendXJoyTo( AsmCodeBuf buf ) { buf.append( "XJOY:\tLD\tA,H\n" + "\tOR\tA\n" @@ -162,7 +168,7 @@ public void appendXJOY( AsmCodeBuf buf ) @Override - public void appendXLOCATE( AsmCodeBuf buf ) + public void appendXLocateTo( AsmCodeBuf buf ) { buf.append( "XLOCATE:\n" + "\tLD\tA,1FH\n" @@ -192,7 +198,7 @@ public void appendXLOCATE( AsmCodeBuf buf ) @Override - public void appendXLPTCH( AsmCodeBuf buf ) + public void appendXLPtchTo( AsmCodeBuf buf ) { buf.append( "XLPTCH:\tLD\tB,A\n" + "\tLD\tA,(0FFE8H)\n" @@ -204,7 +210,7 @@ public void appendXLPTCH( AsmCodeBuf buf ) @Override - public void appendXOUTCH( AsmCodeBuf buf ) + public void appendXOutchTo( AsmCodeBuf buf ) { if( !this.xoutchAppended ) { buf.append( "XOUTCH:\tCP\t0AH\n" @@ -221,12 +227,57 @@ public void appendXOUTCH( AsmCodeBuf buf ) * Ausgabe eines Zeilenumbruchs */ @Override - public void appendXOUTNL( AsmCodeBuf buf ) + public void appendXOutnlTo( AsmCodeBuf buf ) { - buf.append( "XOUTNL:\tLD\tA,0DH\n" - + "\tRST\t20H\n" - + "\tDB\t00H\n" + buf.append( "XOUTNL:\tLD\tA,0DH\n" ); + if( this.xoutchAppended ) { + buf.append( "\tJR\tXOUTCH\n" ); + } else { + appendXOutchTo( buf ); + } + } + + + /* + * Setzen eines Pixels ohne Beruecksichtigung des eingestellten Stiftes + * bei gleichzeitigem Test, ob dieser schon gesetzt ist + * Parameter: + * DE: X-Koordinate + * HL: Y-Koordinate + * Rueckgabe: + * CY=1: Pixel bereits gesetzt oder ausserhalb des sichtbaren Bereichs + */ + @Override + public void appendXPaintTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPAINT:\tCALL\tX_PST\n" + + "\tRET\tC\n" + + "\tLD\tA,B\n" + + "\tAND\tC\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tCALL\tXPSET2\n" + + "\tOR\tA\n" // CY=0 + "\tRET\n" ); + appendXPSetTo( buf, compiler ); + } + + + /* + * Farbe eines Pixels ermitteln, + * Die Rueckgabewerte sind identisch zur Funktion XPTEST + * + * Parameter: + * DE: X-Koordinate (0...255) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * HL >= 0: Farbcode des Pixels + * HL=-1: Pixel exisitiert nicht + */ + @Override + public void appendXPointTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + appendXPTestTo( buf, compiler ); } @@ -237,12 +288,11 @@ public void appendXOUTNL( AsmCodeBuf buf ) * HL: Y-Koordinate */ @Override - public void appendXPRES( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPResTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPRES:\tCALL\tX_PCK\n" - + "\tRET\tC\n" - + "\tCALL\tX_PST\n" ); - if( this.usesX_MPEN ) { + buf.append( "XPRES:\tCALL\tX_PST\n" + + "\tRET\tC\n" ); + if( this.usesX_M_PEN ) { buf.append( "\tJR\tXPSET1\n" ); } else { buf.append( "\tLD\tA,C\n" @@ -250,25 +300,24 @@ public void appendXPRES( AsmCodeBuf buf, BasicCompiler compiler ) + "\tAND\tB\n" + "\tJR\tXPSET3\n" ); } - appendXPSET( buf, compiler ); + appendXPSetTo( buf, compiler ); } /* - * Setzen eines Pixels + * Setzen eines Pixels unter Beruecksichtigung des eingestellten Stiftes * Parameter: * DE: X-Koordinate * HL: Y-Koordinate */ @Override - public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPSetTo( AsmCodeBuf buf, BasicCompiler compiler ) { if( !this.xpsetAppended ) { - buf.append( "XPSET:\tCALL\tX_PCK\n" - + "\tRET\tC\n" - + "\tCALL\tX_PST\n" ); - if( this.usesX_MPEN ) { - buf.append( "\tLD\tA,(X_MPEN)\n" + buf.append( "XPSET:\tCALL\tX_PST\n" + + "\tRET\tC\n" ); + if( this.usesX_M_PEN ) { + buf.append( "\tLD\tA,(X_M_PEN)\n" + "\tDEC\tA\n" + "\tJR\tZ,XPSET2\n" // Stift 1 (Normal) + "\tDEC\tA\n" @@ -288,12 +337,16 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + "XPSET3:\tEX\tDE,HL\n" + "\tLD\tB,0\n" + "\tLD\tC,A\n" - + "\tLD\tHL,X_PST4\n" + + "\tLD\tHL,XPSET_TAB\n" + "\tADD\tHL,BC\n" + "\tLD\tA,(HL)\n" + "\tLD\t(DE),A\n" - + "\tRET\n" ); - appendPixUtil( buf, compiler ); + + "\tRET\n" + // Umcodierungstabelle von Bitmuster zu Zeichen + + "XPSET_TAB:\n" + + "\tDB\t20H,0B3H,0B2H,0B7H,0B0H,0B4H,0B8H,0BBH\n" + + "\tDB\t0B1H,0B9H,0B5H,0BAH,0B6H,0BCH,0BDH,0FFH\n" ); + appendPixUtilTo( buf, compiler ); this.xpsetAppended = true; } } @@ -310,72 +363,51 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) * HL=-1: Pixel existiert nicht */ @Override - public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPTestTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPTEST:\tCALL\tX_PCK\n" - + "\tJR\tNC,X_PTST1\n" - + "\tLD\tHL,0FFFFH\n" - + "\tRET\n" - + "X_PTST1:\n" - + "\tCALL\tX_PST\n" + if( !this.xptestAppended ) { + buf.append( "XPOINT:\n" + + "XPTEST:\tCALL\tX_PST\n" + + "\tJR\tC,X_PTEST1\n" + "\tLD\tA,B\n" + "\tAND\tC\n" + "\tLD\tHL,0000H\n" + "\tRET\tZ\n" + "\tINC\tHL\n" + + "\tRET\n" + + "X_PTEST1:\n" + + "\tLD\tHL,0FFFFH\n" + "\tRET\n" ); - appendPixUtil( buf, compiler ); + appendPixUtilTo( buf, compiler ); + this.xptestAppended = true; + } } - /* - * Target-ID-String - */ @Override - public void appendXTARID( AsmCodeBuf buf ) + public int get100msLoopCount() { - buf.append( "XTARID:\tDB\t\'Z1013\'\n" - + "\tDB\t00H\n" ); - } - - - /* - * Der Programmcode der Routine X_PCK liegt in einer separaten Methode, - * damit er in abgeleiteten Klassen ueberschrieben werden kann. - */ - protected void appendX_PCK( AsmCodeBuf buf, BasicCompiler compiler ) - { - /* - * Pruefen der Parameter - * DE: X-Koordinate (0...63) - * HL: Y-Koordinate (0...63) - * Rueckgabe: - * CY=1: Pixel ausserhalb des gueltigen Bereichs - */ - buf.append( "X_PCK:\tLD\tA,D\n" - + "\tOR\tH\n" - + "\tJR\tZ,X_PCK1\n" - + "\tSCF\n" - + "\tRET\n" - + "X_PCK1:\tLD\tA,3FH\n" - + "\tCP\tE\n" - + "\tRET\tC\n" - + "\tCP\tL\n" - + "\tRET\n" ); + return 55; } @Override - public boolean createsCodeFor( EmuSys emuSys ) + public String[] getBasicTargetNames() { - return emuSys != null ? (emuSys instanceof Z1013) : false; + return new String[] { BASIC_TARGET_NAME }; } @Override - public int get100msLoopCount() + public int getCompatibilityLevel( EmuSys emuSys ) { - return 55; + int rv = 0; + if( emuSys != null ) { + if( emuSys instanceof Z1013 ) { + rv = 3; + } + } + return rv; } @@ -387,16 +419,22 @@ public int getDefaultBegAddr() @Override - public int getGraphicScreenNum() + public String getHostName() { - return 0; + return "Z1013"; } @Override - public int getKCNetBaseIOAddr() + public String getStartCmd( EmuSys emuSys, String appName, int begAddr ) { - return 0xC0; + String rv = null; + if( (emuSys != null) && (begAddr >= 0) ) { + if( emuSys instanceof Z1013 ) { + rv = String.format( "J %04X", begAddr ); + } + } + return rv; } @@ -413,6 +451,7 @@ public void reset() super.reset(); this.pixUtilAppended = false; this.xpsetAppended = false; + this.xptestAppended = false; } @@ -458,38 +497,58 @@ public String toString() } - /* --- private Methoden --- */ + /* --- Hilfsfunktionen --- */ - private void appendPixUtil( AsmCodeBuf buf, BasicCompiler compiler ) + protected void appendCheckGraphicScreenTo( + AsmCodeBuf buf, + BasicCompiler compiler ) { - if( !this.pixUtilAppended ) { - appendX_PCK( buf, compiler ); + // leer + } + + protected void appendPixUtilTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + if( !this.pixUtilAppended ) { /* - * Ermitteln von Informationen zu einem Pixel + * Pruefen der Parameter und + * ermitteln von Informationen zu einem Pixel + * * Parameter: * DE: X-Koordinate (0...63) * HL: Y-Koordinate (0...63) + * * Rueckgabe: - * B: Aktuelles Bitmuster (gesetzte Pixel) in der Speicherzelle, - * Bitanordnung innerhalb einer Zeichenposition: - * +---+ - * |2 3| - * |0 1| - * +---+ - * C: Bitmuster mit einem gesetzten Bit, - * dass das Pixel in der Speicherzelle beschreibt - * HL: Speicherzelle, in der sich das Pixel befindet + * CY=1: Pixel ausserhalb des gueltigen Bereichs + * B: Aktuelles Bitmuster (gesetzte Pixel) in der Speicherzelle, + * Bitanordnung innerhalb einer Zeichenposition: + * +---+ + * |2 3| + * |0 1| + * +---+ + * C: Bitmuster mit einem gesetzten Bit, + * dass das Pixel in der Speicherzelle beschreibt + * HL: Speicherzelle, in der sich das Pixel befindet */ - buf.append( "X_PST:\tLD\tA,01H\n" + buf.append( "X_PST:\n" ); + appendCheckGraphicScreenTo( buf, compiler ); + buf.append( "X_PST1:\tLD\tA,D\n" + + "\tOR\tH\n" + + "\tJR\tNZ,X_PST4\n" + + "\tLD\tA,3FH\n" + + "\tCP\tE\n" + + "\tRET\tC\n" + + "\tCP\tL\n" + + "\tRET\tC\n" + + "\tLD\tA,01H\n" + "\tSRL\tL\n" - + "\tJR\tNC,X_PST1\n" + + "\tJR\tNC,X_PST2\n" + "\tSLA\tA\n" + "\tSLA\tA\n" - + "X_PST1:\tSRL\tE\n" - + "\tJR\tNC,X_PST2\n" + + "X_PST2:\tSRL\tE\n" + + "\tJR\tNC,X_PST3\n" + "\tSLA\tA\n" - + "X_PST2:\tADD\tHL,HL\n" + + "X_PST3:\tADD\tHL,HL\n" + "\tADD\tHL,HL\n" + "\tADD\tHL,HL\n" + "\tADD\tHL,HL\n" @@ -503,29 +562,32 @@ private void appendPixUtil( AsmCodeBuf buf, BasicCompiler compiler ) + "\tLD\tA,(HL)\n" + "\tLD\tB,0FH\n" + "\tCP\t0EH\n" - + "\tRET\tC\n" + + "\tCCF\n" + + "\tRET\tNC\n" + "\tCP\t0FFH\n" // 0FFh: 4 gesetzte Pixel + "\tRET\tZ\n" + "\tLD\tB,0\n" + "\tSUB\t0B0H\n" // < 0B0h: 4 nicht gesetzte Pixel - + "\tRET\tC\n" + + "\tCCF\n" + + "\tRET\tNC\n" + "\tCP\t0EH\n" // >= 0BEh: 4 nicht gesetzte Pixel + "\tRET\tNC\n" + "\tPUSH\tHL\n" - + "\tLD\tHL,X_PST3\n" + + "\tLD\tHL,X_PST_TAB\n" + "\tLD\tD,0\n" + "\tLD\tE,A\n" + "\tADD\tHL,DE\n" + "\tLD\tA,(HL)\n" + "\tPOP\tHL\n" + "\tLD\tB,A\n" + + "\tOR\tA\n" + "\tRET\n" - // Umcodierungstabelle von (Zeichen - 0B0H) zu Bitmuster - + "X_PST3:\tDB\t04H,08H,02H,01H,05H,0AH,0CH,03H\n" - + "\tDB\t06H,09H,0BH,07H,0DH,0EH\n" - // Umcodierungstabelle von Bitmuster zu Zeichen - + "X_PST4:\tDB\t20H,0B3H,0B2H,0B7H,0B0H,0B4H,0B8H,0BBH\n" - + "\tDB\t0B1H,0B9H,0B5H,0BAH,0B6H,0BCH,0BDH,0FFH\n" ); + + "X_PST4:\tSCF\n" + + "\tRET\n" + // Umcodierungstabelle von (Zeichen - 0B0H) zu Bitmuster + + "X_PST_TAB:\n" + + "\tDB\t04H,08H,02H,01H,05H,0AH,0CH,03H\n" + + "\tDB\t06H,09H,0BH,07H,0DH,0EH\n" ); this.pixUtilAppended = true; } } diff --git a/src/jkcemu/programming/basic/target/Z9001KRTTarget.java b/src/jkcemu/programming/basic/target/Z9001KRTTarget.java index 09053a1..cbda6fe 100644 --- a/src/jkcemu/programming/basic/target/Z9001KRTTarget.java +++ b/src/jkcemu/programming/basic/target/Z9001KRTTarget.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -18,14 +18,18 @@ public class Z9001KRTTarget extends Z9001Target { + public static final String BASIC_TARGET_NAME = "TARGET_Z9001_KRT"; + private boolean needsScreenSizeChar; private boolean needsScreenSizePixel; - private boolean xpsetAppended; + private boolean usesScreens; + private boolean usesPaint; - public void Z9001KRTTarget() + public Z9001KRTTarget() { - // leer + setNamedValue( "GRAPHICSCREEN", 1 ); + setNamedValue( "LASTSCREEN", 1 ); } @@ -33,78 +37,67 @@ public void Z9001KRTTarget() public void appendBssTo( AsmCodeBuf buf ) { super.appendBssTo( buf ); - buf.append( "X_MSCR:\tDS\t1\n" ); + if( this.usesPaint ) { + buf.append( "X_M_PBANK:\tDS\t1\n" ); + } + if( this.usesScreens ) { + buf.append( "X_M_SCREEN:\tDS\t1\n" ); + } } @Override - public void appendEtc( AsmCodeBuf buf ) + public void appendEtcPastXOutTo( AsmCodeBuf buf ) { - buf.append( "XSCRS:\tLD\tA,H\n" - + "\tOR\tA\n" - + "\tJR\tNZ,XSCRS2\n" - + "XSCRS1:\tLD\tA,(X_MSCR)\n" - + "\tCP\tL\n" - + "\tRET\tZ\n" - + "\tLD\tA,L\n" - + "\tOR\tA\n" - + "\tJR\tZ,XSCRS3\n" - + "\tCP\t01H\n" - + "\tJR\tZ,XSCRS4\n" - + "XSCRS2:\tSCF\n" - + "\tRET\n" - + "XSCRS3:\tLD\tDE,0800H\n" // KRT -> STD - + "\tJR\tXSCRS5\n" - + "XSCRS4:\tLD\tC,29\n" // Cursor abschalten - + "\tCALL\t0005H\n" - + "\tLD\tDE,0008H\n" // STD -> KRT - + "XSCRS5:\tLD\t(X_MSCR),A\n" - + "\tLD\tBC,40B8H\n" - + "\tLD\tHL,0EFC0H\n" - + "\tDI\n" - + "XSCRS6:\tOUT\t(C),D\n" - + "\tLD\tA,(HL)\n" - + "\tOUT\t(C),E\n" - + "\tLD\t(HL),A\n" - + "\tINC\tHL\n" - + "\tDJNZ\tXSCRS6\n" - + "\tEI\n" - + "\tOR\tA\n" // CY=0 - + "\tRET\n" ); if( this.needsScreenSizeChar ) { - buf.append( "X_HCHR:\tLD\tHL,0018H\n" + if( this.usesScreens ) { + buf.append( "X_HCHR:\tLD\tHL,001BH\n" + "\tJR\tX_SSZC\n" + "X_WCHR:\tLD\tHL,0028H\n" - + "X_SSZC:\tLD\tA,(X_MSCR)\n" + + "X_SSZC:\tLD\tA,(X_M_SCREEN)\n" + "\tOR\tA\n" + "\tRET\tZ\n" - + "\tLD\tHL,0000H\n" + + "\tLD\tL,00H\n" + "\tRET\n" ); + } else { + buf.append( "X_HCHR:\tLD\tHL,001BH\n" + + "\tRET\n" + + "X_WCHR:\tLD\tHL,0028H\n" + + "\tRET\n" ); + } } if( this.needsScreenSizePixel ) { - buf.append( "X_HPIX:\tLD\tHL,00C0H\n" - + "\tJR\tX_SSZP\n" + if( this.usesScreens ) { + buf.append( "X_HPIX:\tLD\tHL,00C0H\n" + + "\tJR\tX_HWPIX1\n" + "X_WPIX:\tLD\tHL,0140H\n" - + "X_SSZP:\tLD\tA,(X_MSCR)\n" + + "X_HWPIX1:\n" + + "\tLD\tA,(X_M_SCREEN)\n" + "\tDEC\tA\n" + "\tRET\tZ\n" + "\tLD\tHL,0000H\n" + "\tRET\n" ); + } else { + buf.append( "X_HPIX:\n" + + "X_WPIX:\tLD\tHL,0000H\n" + + "\tRET\n" ); + } } } @Override - public void appendExit( AsmCodeBuf buf ) + public void appendPreExitTo( AsmCodeBuf buf ) { - buf.append( "\tLD\tL,00H\n" - + "\tCALL\tXSCRS1\n" - + "\tJP\t0000H\n" ); + if( this.usesScreens ) { + buf.append( "\tLD\tL,00H\n" + + "\tCALL\tXSCRN1\n" ); + } } @Override - public void appendHChar( AsmCodeBuf buf ) + public void appendHCharTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_HCHR\n" ); this.needsScreenSizeChar = true; @@ -112,7 +105,7 @@ public void appendHChar( AsmCodeBuf buf ) @Override - public void appendHPixel( AsmCodeBuf buf ) + public void appendHPixelTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_HPIX\n" ); this.needsScreenSizePixel = true; @@ -123,24 +116,26 @@ public void appendHPixel( AsmCodeBuf buf ) public void appendInitTo( AsmCodeBuf buf ) { super.appendInitTo( buf ); - buf.append( "\tXOR\tA\n" - + "\tLD\t(X_MSCR),A\n" + if( this.usesScreens ) { + buf.append( "\tXOR\tA\n" + + "\tLD\t(X_M_SCREEN),A\n" + "\tOUT\t(0B8H),A\n" ); + } } @Override - public void appendSwitchToTextScreen( AsmCodeBuf buf ) + public void appendSwitchToTextScreenTo( AsmCodeBuf buf ) { - if( buf != null ) { + if( this.usesScreens ) { buf.append( "\tLD\tL,00H\n" - + "\tCALL\tXSCRS1\n" ); + + "\tCALL\tXSCRN1\n" ); } } @Override - public void appendWChar( AsmCodeBuf buf ) + public void appendWCharTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_WCHR\n" ); this.needsScreenSizeChar = true; @@ -148,7 +143,7 @@ public void appendWChar( AsmCodeBuf buf ) @Override - public void appendWPixel( AsmCodeBuf buf ) + public void appendWPixelTo( AsmCodeBuf buf ) { buf.append( "\tCALL\tX_WPIX\n" ); this.needsScreenSizePixel = true; @@ -156,9 +151,10 @@ public void appendWPixel( AsmCodeBuf buf ) @Override - public void appendXCLS( AsmCodeBuf buf ) + public void appendXClsTo( AsmCodeBuf buf ) { - buf.append( "XCLS:\tLD\tA,(X_MSCR)\n" + if( this.usesScreens ) { + buf.append( "XCLS:\tLD\tA,(X_M_SCREEN)\n" + "\tCP\t01\n" + "\tJR\tZ,XCLS1\n" + "\tLD\tA,0CH\n" @@ -180,35 +176,296 @@ public void appendXCLS( AsmCodeBuf buf ) + "\tLD\tA,08\n" + "\tOUT\t(0B8H),A\n" // Bank 0 einstellen + "\tEI\n" ); - if( this.usesColors ) { - buf.append( "\tLD\tHL,0E800H\n" + if( this.usesColors ) { + buf.append( "\tLD\tHL,0E800H\n" + "\tLD\tA,(0027H)\n" + "\tLD\t(HL),A\n" + "\tLD\tDE,0E801H\n" + "\tLD\tBC,03BFH\n" + "\tLDIR\n" ); + } + buf.append( "\tRET\n" ); + } else { + buf.append( "XCLS:\tLD\tA,0CH\n" + + "\tJR\tXOUTCH\n" ); } - buf.append( "\tRET\n" ); - appendXOUTCH( buf ); + appendXOutchTo( buf ); + } + + + /* + * Zeichnen einer horizontalen Linie + * Parameter: + * BC: Laenge - 1 + * DE: linke X-Koordinate, nicht kleiner 0 + * HL: Y-Koordinate + */ + public void appendXHLineTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XHLINE:\tBIT\t7,B\n" + + "\tRET\tNZ\n" + + "\tPUSH\tBC\n" + + "\tPUSH\tDE\n" + + "\tPUSH\tHL\n" + + "\tCALL\tX_PST\n" + + "\tLD\tA,B\n" + + "\tEXX\n" + + "\tPOP\tHL\n" + + "\tPOP\tDE\n" + + "\tPOP\tBC\n" + + "\tRET\tC\n" + + "\tLD\tD,00H\n" + + "XHLINE1:\n" + + "\tOR\tD\n" + + "\tLD\tD,A\n" + + "\tSRL\tA\n" + + "\tJR\tNC,XHLINE2\n" + + "\tLD\tA,D\n" + + "\tEXX\n" + + "\tLD\tB,A\n" + + "\tCALL\tXPSET_B\n" ); + if( this.usesColors ) { + /* + * Bei Verwendung von Farben zeigt HL nach Aufruf von XPSET_B + * nicht auf den Pixel- sondern den Farbspeicher + * und muss deshalb korrigiert werden. + */ + buf.append( "\tSET\t2,H\n" ); + } + buf.append( "\tINC\tHL\n" + + "\tEXX\n" + + "\tLD\tA,80H\n" + + "\tLD\tD,00H\n" + + "XHLINE2:\n" + + "\tDEC\tBC\n" + + "\tBIT\t7,B\n" + + "\tJR\tZ,XHLINE1\n" + + "\tLD\tA,D\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" + + "\tEXX\n" + + "\tLD\tB,A\n" + + "\tJR\tXPSET_B\n" ); + appendXPSetTo( buf, compiler ); } @Override - public void appendXOUTCH( AsmCodeBuf buf ) + public void appendXOutchTo( AsmCodeBuf buf ) { if( !this.xoutchAppended ) { - buf.append( "XOUTCH:\tLD\tE,A\n" - + "\tLD\tA,(X_MSCR)\n" + buf.append( "XOUTCH:\tLD\tE,A\n" ); + if( this.usesScreens ) { + buf.append( "\tLD\tA,(X_M_SCREEN)\n" + "\tOR\tA\n" - + "\tRET\tNZ\n" - + "\tLD\tC,2\n" - + "\tLD\tA,E\n" + + "\tRET\tNZ\n" ); + } + buf.append( "\tLD\tC,2\n" + "\tJP\t0005H\n" ); this.xoutchAppended = true; } } + /* + * XPAINT_LEFT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach links, + * Der Startpunkt selbst wird nicht geprueft + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * (PAINT_M_X1): X-Endkoordinate der gefuellten Linie, + * kleiner oder gleich X-Startpunkt + * + * XPAINT_RIGHT: + * Fuellen einer Linie ab dem uebergebenen Punkt nach rechts + * Parameter: + * PAINT_M_X: X-Koordinate Startpunkt + * PAINT_M_Y: Y-Koordinate Startpunkt + * Rueckgabe: + * CY=1: Pixel im Startpunkt bereits gesetzt (gefuellt) + * oder ausserhalb des sichtbaren Bereichs + * (PAINT_M_X2): X-Endkoordinate der gefuellten Linie, nur bei CY=0 + */ + @Override + public void appendXPaintTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPAINT_LEFT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "\tLD\tA,D\n" + + "\tOR\tE\n" + + "\tJR\tZ,XPAINT_LEFT6\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tDEC\tDE\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_PST\n" + + "\tLD\tA,C\n" + + "\tLD\t(X_M_PBANK),A\n" + + "\tPOP\tDE\n" + + "\tJR\tC,XPAINT_LEFT5\n" + + "\tLD\tC,B\n" + + "\tCALL\tXPAINT_RD_B\n" + + "XPAINT_LEFT1:\n" + + "\tLD\tA,B\n" + + "XPAINT_LEFT2:\n" + + "\tAND\tC\n" + + "\tJR\tNZ,XPAINT_LEFT4\n" + + "\tLD\tA,B\n" + + "\tOR\tC\n" + + "\tLD\tB,A\n" + + "\tDEC\tDE\n" + + "\tSLA\tC\n" + + "\tJR\tNC,XPAINT_LEFT2\n" + + "\tCALL\tXPAINT_WR_B\n" + + "\tDEC\tHL\n" + + "\tBIT\t0,D\n" + + "\tJR\tZ,XPAINT_LEFT3\n" + + "\tLD\tA,E\n" + + "\tINC\tA\n" + + "\tJR\tZ,XPAINT_LEFT5\n" + + "XPAINT_LEFT3:\n" + + "\tCALL\tXPAINT_RD_B\n" + + "\tLD\tC,01H\n" + + "\tJR\tXPAINT_LEFT1\n" + + "XPAINT_LEFT4:\n" + + "\tCALL\tXPAINT_WR_B\n" + + "XPAINT_LEFT5:\n" + + "\tINC\tDE\n" + + "XPAINT_LEFT6:\n" + + "\tLD\t(PAINT_M_X1),DE\n" + + "\tRET\n" + + "XPAINT_RIGHT:\n" + + "\tLD\tDE,(PAINT_M_X)\n" + + "\tLD\tHL,(PAINT_M_Y)\n" + + "\tPUSH\tDE\n" + + "\tCALL\tX_PST\n" + + "\tPOP\tDE\n" + + "\tRET\tC\n" + + "\tLD\tA,C\n" + + "\tLD\t(X_M_PBANK),A\n" + + "\tLD\tC,B\n" + + "\tCALL\tXPAINT_RD_B\n" + + "\tLD\tA,C\n" + + "\tAND\tB\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tJR\tXPAINT_RIGHT3\n" + + "XPAINT_RIGHT1:\n" + + "\tLD\tA,B\n" + + "XPAINT_RIGHT2:\n" + + "\tAND\tC\n" + + "\tJR\tNZ,XPAINT_RIGHT5\n" + + "XPAINT_RIGHT3:\n" + + "\tLD\tA,B\n" + + "\tOR\tC\n" + + "\tLD\tB,A\n" + + "\tINC\tDE\n" + + "\tSRL\tC\n" + + "\tJR\tNC,XPAINT_RIGHT2\n" + + "\tCALL\tXPAINT_WR_B\n" + + "\tINC\tHL\n" + + "\tLD\tA,E\n" + + "\tCP\t40H\n" + + "\tJR\tNZ,XPAINT_RIGHT4\n" + + "\tLD\tA,D\n" + + "\tDEC\tA\n" + + "\tJR\tZ,XPAINT_RIGHT6\n" + + "XPAINT_RIGHT4:\n" + + "\tCALL\tXPAINT_RD_B\n" + + "\tLD\tC,80H\n" + + "\tJR\tXPAINT_RIGHT1\n" + + "XPAINT_RIGHT5:\n" + + "\tCALL\tXPAINT_WR_B\n" + + "XPAINT_RIGHT6:\n" + + "\tDEC\tDE\n" + + "\tLD\t(PAINT_M_X2),DE\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" + + "XPAINT_RD_B:\n" + + "\tDI\n" + + "\tLD\tA,(X_M_PBANK)\n" + + "\tOUT\t(0B8H),A\n" + + "\tLD\tB,(HL)\n" + + "\tLD\tA,08H\n" + + "\tOUT\t(0B8H),A\n" + + "\tEI\n" + + "\tRET\n" + + "XPAINT_WR_B:\n" + + "\tDI\n" + + "\tLD\tA,(X_M_PBANK)\n" + + "\tOUT\t(0B8H),A\n" + + "\tLD\t(HL),B\n" + + "\tLD\tA,08H\n" + + "\tOUT\t(0B8H),A\n" + + "\tEI\n" ); + if( this.usesColors ) { + buf.append( "\tRES\t2,H\n" + + "\tLD\tA,(0027H)\n" + + "\tLD\t(HL),A\n" + + "\tSET\t2,H\n" ); + } + buf.append( "\tRET\n" ); + appendPixUtilTo( buf, compiler ); + this.usesPaint = true; + } + + + /* + * Farbe eines Pixels ermitteln + * Parameter: + * DE: X-Koordinate (0...255) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * HL >= 0: Farbcode des Pixels + * HL=-1: Pixel exisitiert nicht + */ + public void appendXPointTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + if( this.usesScreens ) { + buf.append( "XPOINT:\tLD\tA,(X_M_SCREEN)\n" + + "\tCP\t01H\n" + + "\tJR\tNZ,XPOINT2\n" + + "\tCALL\tX_PST1\n" + + "\tJR\tC,XPOINT2\n" + // Pixel lesen + + "\tDI\n" + + "\tLD\tA,C\n" + + "\tOUT\t(0B8H),A\n" + + "\tLD\tA,B\n" + + "\tAND\t(HL)\n" + + "\tLD\tD,A\n" + + "\tLD\tA,08H\n" + + "\tOUT\t(0B8H),A\n" + + "\tEI\n" + // Farbbyte lesen und Pixel auswerten + + "\tRES\t2,H\n" + + "\tLD\tA,D\n" + + "\tOR\tA\n" + + "\tLD\tA,(HL)\n" + + "\tLD\tE,07H\n" + + "\tJR\tZ,XPOINT1\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tLD\tE,0FH\n" + + "XPOINT1:\n" + + "\tAND\tE\n" + + "\tLD\tL,A\n" + + "\tLD\tH,00H\n" + + "\tRET\n" + + "XPOINT2:\n" + + "\tLD\tHL,0FFFFH\n" + + "\tRET\n" ); + appendPixUtilTo( buf, compiler ); + } else { + buf.append( "XPOINT:\tLD\tHL,0FFFFH\n" + + "\tRET\n" ); + } + } + + /* * Zuruecksetzen eines Pixels * Parameter: @@ -216,41 +473,40 @@ public void appendXOUTCH( AsmCodeBuf buf ) * HL: Y-Koordinate */ @Override - public void appendXPRES( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPResTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPRES:\tCALL\tX_PCK\n" - + "\tRET\tC\n" - + "\tCALL\tX_PST\n" ); - if( this.usesX_MPEN ) { + buf.append( "XPRES:\tCALL\tX_PST\n" + + "\tRET\tC\n" ); + if( this.usesX_M_PEN ) { buf.append( "\tJR\tXPSET1\n" ); } else { buf.append( "\tDI\n" - + "\tLD\tA,B\n" - + "\tOUT\t(0B8H),A\n" + "\tLD\tA,C\n" + + "\tOUT\t(0B8H),A\n" + + "\tLD\tA,B\n" + "\tCPL\n" + "\tAND\t(HL)\n" - + "\tJR\tXPSET3\n" ); + + "\tJR\tXPSET_WR_A\n" ); } - appendXPSET( buf, compiler ); + appendXPSetTo( buf, compiler ); } /* - * Setzen eines Pixels + * Setzen eines Pixels unter Beruecksichtigung des eingestellten Stiftes * Parameter: * DE: X-Koordinate * HL: Y-Koordinate */ @Override - public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPSetTo( AsmCodeBuf buf, BasicCompiler compiler ) { if( !this.xpsetAppended ) { - buf.append( "XPSET:\tCALL\tX_PCK\n" + buf.append( "XPSET:\tCALL\tX_PST\n" + "\tRET\tC\n" - + "\tCALL\tX_PST\n" ); - if( this.usesX_MPEN ) { - buf.append( "\tLD\tA,(X_MPEN)\n" + + "XPSET_B:\n" ); + if( this.usesX_M_PEN ) { + buf.append( "\tLD\tA,(X_M_PEN)\n" + "\tDEC\tA\n" + "\tJR\tZ,XPSET2\n" // Stift 1 (Normal) + "\tDEC\tA\n" @@ -258,36 +514,36 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + "\tDEC\tA\n" + "\tRET\tNZ\n" + "\tDI\n" // Stift 3 (XOR-Mode) - + "\tLD\tA,B\n" - + "\tOUT\t(0B8H),A\n" + "\tLD\tA,C\n" + + "\tOUT\t(0B8H),A\n" + + "\tLD\tA,B\n" + "\tXOR\t(HL)\n" - + "\tJR\tXPSET3\n" + + "\tJR\tXPSET_WR_A\n" + "XPSET1:\tDI\n" // Pixel loeschen - + "\tLD\tA,B\n" - + "\tOUT\t(0B8H),A\n" + "\tLD\tA,C\n" + + "\tOUT\t(0B8H),A\n" + + "\tLD\tA,B\n" + "\tCPL\n" + "\tAND\t(HL)\n" - + "\tJR\tXPSET3\n" ); + + "\tJR\tXPSET_WR_A\n" ); } buf.append( "XPSET2:\tDI\n" // Pixel setzen - + "\tLD\tA,B\n" - + "\tOUT\t(0B8H),A\n" + "\tLD\tA,C\n" + + "\tOUT\t(0B8H),A\n" + + "\tLD\tA,B\n" + "\tOR\t(HL)\n" - + "XPSET3:\tLD\t(HL),A\n" + + "XPSET_WR_A:\n" + + "\tLD\t(HL),A\n" + "\tLD\tA,08H\n" + "\tOUT\t(0B8H),A\n" + "\tEI\n" ); if( this.usesColors ) { - buf.append( "\tLD\tDE,0FC00H\n" - + "\tADD\tHL,DE\n" + buf.append( "\tRES\t2,H\n" + "\tLD\tA,(0027H)\n" + "\tLD\t(HL),A\n" ); } buf.append( "\tRET\n" ); - appendPixUtil( buf, compiler ); + appendPixUtilTo( buf, compiler ); this.xpsetAppended = true; } } @@ -304,18 +560,18 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) * HL=-1: Pixel existiert nicht */ @Override - public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPTestTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPTEST:\tLD\tA,(X_MSCR)\n" + if( this.usesScreens ) { + buf.append( "XPTEST:\tLD\tA,(X_M_SCREEN)\n" + "\tCP\t01H\n" + "\tJR\tNZ,XPTST1\n" - + "\tCALL\tX_PCK1\n" + + "\tCALL\tX_PST1\n" + "\tJR\tC,XPTST1\n" - + "\tCALL\tX_PST\n" + "\tDI\n" - + "\tLD\tA,B\n" - + "\tOUT\t(0B8H),A\n" + "\tLD\tA,C\n" + + "\tOUT\t(0B8H),A\n" + + "\tLD\tA,B\n" + "\tAND\t(HL)\n" + "\tLD\tA,08H\n" + "\tOUT\t(0B8H),A\n" @@ -326,7 +582,11 @@ public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) + "\tRET\n" + "XPTST1:\tLD\tHL,0FFFFH\n" + "\tRET\n" ); - appendPixUtil( buf, compiler ); + appendPixUtilTo( buf, compiler ); + } else { + buf.append( "XPTEST:\tLD\tHL,0FFFFH\n" + + "\tRET\n" ); + } } @@ -339,35 +599,63 @@ public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) * CY=1: Screen-Nummer nicht unterstuetzt */ @Override - public void appendXSCRS( AsmCodeBuf buf ) + public void appendXScreenTo( AsmCodeBuf buf ) { - /* - * Der Programmcode wird bei appendExit(...) erzeugt. - * Deshalb ist diese Methode hier leer. - * Die Methode muss aber ueberschrieben werden, - * damit der Standardcode nicht generiert wird. - */ + if( this.usesScreens ) { + buf.append( "XSCREEN:\n" + + "\tLD\tA,H\n" + + "\tOR\tA\n" + + "\tJR\tNZ,XSCRN2\n" + + "XSCRN1:\tLD\tA,(X_M_SCREEN)\n" + + "\tCP\tL\n" + + "\tRET\tZ\n" + + "\tLD\tA,L\n" + + "\tOR\tA\n" + + "\tJR\tZ,XSCRN3\n" + + "\tCP\t01H\n" + + "\tJR\tZ,XSCRN4\n" + + "XSCRN2:\tSCF\n" + + "\tRET\n" + + "XSCRN3:\tLD\tDE,0800H\n" // KRT -> STD + + "\tJR\tXSCRN5\n" + + "XSCRN4:\tLD\tC,29\n" // Cursor abschalten + + "\tCALL\t0005H\n" + + "\tLD\tDE,0008H\n" // STD -> KRT + // SCREEN-Nr. setzen und Systemzellen umkopieren + + "XSCRN5:\tLD\t(X_M_SCREEN),A\n" + + "\tLD\tBC,40B8H\n" + + "\tLD\tHL,0EFC0H\n" + + "\tDI\n" + + "XSCRN6:\tOUT\t(C),D\n" + + "\tLD\tA,(HL)\n" + + "\tOUT\t(C),E\n" + + "\tLD\t(HL),A\n" + + "\tINC\tHL\n" + + "\tDJNZ\tXSCRN6\n" + + "\tEI\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + } } - /* - * Target-ID-String - */ @Override - public void appendXTARID( AsmCodeBuf buf ) + public String[] getBasicTargetNames() { - buf.append( "XTARID:\tDB\t\'Z9001_KRT\'\n" - + "\tDB\t00H\n" ); + return add( super.getBasicTargetNames(), BASIC_TARGET_NAME ); } @Override - public boolean createsCodeFor( EmuSys emuSys ) + public int getCompatibilityLevel( EmuSys emuSys ) { - boolean rv = false; + int rv = 0; if( emuSys != null ) { if( emuSys instanceof Z9001 ) { - rv = ((Z9001) emuSys).emulatesGraphicsKRT(); + rv = 1; + if( ((Z9001) emuSys).emulatesGraphicsKRT() ) { + rv = 3; + } } } return rv; @@ -375,16 +663,14 @@ public boolean createsCodeFor( EmuSys emuSys ) @Override - public int getGraphicScreenNum() - { - return 1; - } - - - @Override - public int getLastScreenNum() + public void preAppendLibraryCode( BasicCompiler compiler ) { - return 1; + super.preAppendLibraryCode( compiler ); + if( compiler.usesLibItem( BasicLibrary.LibItem.SCREEN ) + || compiler.usesLibItem( BasicLibrary.LibItem.XSCREEN ) ) + { + this.usesScreens = true; + } } @@ -394,7 +680,8 @@ public void reset() super.reset(); this.needsScreenSizeChar = false; this.needsScreenSizePixel = false; - this.xpsetAppended = false; + this.usesScreens = false; + this.usesPaint = false; } @@ -412,6 +699,20 @@ public boolean supportsXCLS() } + @Override + public boolean supportsXHLINE() + { + return true; + } + + + @Override + public boolean supportsXPAINT_LEFT_RIGHT() + { + return true; + } + + @Override public String toString() { @@ -421,72 +722,62 @@ public String toString() /* --- private Methoden --- */ - private void appendPixUtil( AsmCodeBuf buf, BasicCompiler compiler ) + private void appendPixUtilTo( AsmCodeBuf buf, BasicCompiler compiler ) { if( !this.pixUtilAppended ) { - buf.append( - /* - * Pruefen der Parameter - * DE: X-Koordinate (0...320) - * HL: Y-Koordinate (0...192) - * Rueckgabe: - * CY=1: Pixel ausserhalb des gueltigen Bereichs - */ - "X_PCK:\tLD\tA,(X_MSCR)\n" + /* + * Pruefen der Parameter + * Ermitteln von Informationen zu einem Pixel + * Parameter: + * DE: X-Koordinate (0...320) + * HL: Y-Koordinate (0...192) + * Rueckgabe: + * CY=1: Pixel ausserhalb des gueltigen Bereichs + * B: Bitmuster mit einem gesetzten Bit, + * dass das Pixel in der Speicherzelle beschreibt + * C: Nr. der Speicherbank inkl. gesetzten Bit 3 + * HL: Speicherzelle, in der sich das Pixel befindet + */ + if( this.usesScreens ) { + buf.append( "X_PST:\tLD\tA,(X_M_SCREEN)\n" + "\tCP\t01H\n" - + "\tJR\tNZ,X_PCK4\n" - + "X_PCK1:\tLD\tA,D\n" + + "\tJR\tNZ,X_PST5\n" + + "X_PST1:\tLD\tA,D\n" + "\tOR\tA\n" - + "\tJR\tZ,X_PCK2\n" + + "\tJR\tZ,X_PST2\n" + "\tCP\t02H\n" - + "\tJR\tNC,X_PCK3\n" - + "\tLD\tA,E\n" - + "\tCP\t40H\n" - + "\tJR\tNC,X_PCK3\n" - + "X_PCK2:\tLD\tA,H\n" + + "\tCCF\n" + + "\tRET\tC\n" + + "\tLD\tA,3FH\n" + + "\tCP\tE\n" + + "\tRET\tC\n" + + "X_PST2:\tLD\tA,H\n" + "\tOR\tA\n" - + "\tJR\tNZ,X_PCK3\n" - + "\tLD\tA,L\n" - + "\tCP\t0C0H\n" - + "\tJR\tNC,X_PCK3\n" - + "\tOR\tA\n" // CY=0 - + "\tRET\n" - + "X_PCK3:\tSCF\n" - + "\tRET\n" - + "X_PCK4:" ); - appendExitNoGraphicsScreen( buf, compiler ); - /* - * Ermitteln von Informationen zu einem Pixel - * Die entsprechende Speicher-Bank der KRT-Grafik - * wird eingeblendet. - * Parameter: - * DE: X-Koordinate (0...320) - * HL: Y-Koordinate (0...192) - * Rueckgabe: - * B: Nr. der Speicherbank inkl. gesetzten Bit 3 - * C: Bitmuster mit einem gesetzten Bit, - * dass das Pixel in der Speicherzelle beschreibt - * HL: Speicherzelle, in der sich das Pixel befindet - */ - buf.append( "X_PST:\tLD\tA,E\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tLD\tA,0BFH\n" + + "\tCP\tL\n" + + "\tRET\tC\n" + + "\tLD\tA,E\n" + "\tAND\t07H\n" + "\tLD\tB,A\n" + "\tLD\tC,80H\n" - + "\tJR\tZ,X_PST2\n" - + "X_PST1:\tSRL\tC\n" - + "\tDJNZ\tX_PST1\n" - + "X_PST2:\tSRL\tD\n" + + "\tJR\tZ,X_PST4\n" + + "X_PST3:\tSRL\tC\n" + + "\tDJNZ\tX_PST3\n" + + "X_PST4:\tSRL\tD\n" + "\tRR\tE\n" + "\tSRL\tE\n" + "\tSRL\tE\n" + "\tLD\tA,L\n" + "\tAND\t07H\n" - + "\tLD\tB,A\n" + + "\tLD\tB,C\n" + + "\tLD\tC,A\n" + "\tLD\tA,0FH\n" - + "\tSUB\tB\n" - + "\tLD\tB,A\n" + + "\tSUB\tC\n" + + "\tLD\tC,A\n" + "\tPUSH\tBC\n" - + "\tSRL\tL\n" // H=0 + + "\tSRL\tL\n" // H=0 + "\tSRL\tL\n" + "\tSRL\tL\n" + "\tLD\tB,H\n" @@ -501,10 +792,16 @@ private void appendPixUtil( AsmCodeBuf buf, BasicCompiler compiler ) + "\tLD\tC,L\n" + "\tLD\tHL,0EF98H\n" + "\tADD\tHL,DE\n" - + "\tOR\tA\n" // CY=0, A unveraendert + + "\tOR\tA\n" + "\tSBC\tHL,BC\n" + "\tPOP\tBC\n" - + "\tRET\n" ); + + "\tOR\tA\n" // CY=0 + + "\tRET\n" + + "X_PST5:\n" ); + } else { + buf.append( "X_PST:\n" ); + } + appendExitNoGraphicsScreenTo( buf, compiler ); this.pixUtilAppended = true; } } diff --git a/src/jkcemu/programming/basic/target/Z9001Target.java b/src/jkcemu/programming/basic/target/Z9001Target.java index f9389e4..95d05d8 100644 --- a/src/jkcemu/programming/basic/target/Z9001Target.java +++ b/src/jkcemu/programming/basic/target/Z9001Target.java @@ -1,5 +1,5 @@ /* - * (c) 2012-2013 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -18,40 +18,78 @@ public class Z9001Target extends AbstractTarget { + public static final String BASIC_TARGET_NAME = "TARGET_Z9001"; + protected boolean usesColors; protected boolean pixUtilAppended; protected boolean xpsetAppended; + private boolean usesX_M_INKEY; + public Z9001Target() { - reset(); + setNamedValue( "GRAPHICSCREEN", 0 ); + setNamedValue( "BLACK", 0 ); + setNamedValue( "BLINKING", 0x08 ); + setNamedValue( "BLUE", 0x04 ); + setNamedValue( "CYAN", 0x06 ); + setNamedValue( "GREEN", 0x02 ); + setNamedValue( "MAGENTA", 0x05 ); + setNamedValue( "RED", 0x01 ); + setNamedValue( "WHITE", 0x07 ); + setNamedValue( "YELLOW", 0x03 ); + setNamedValue( "JOYST_LEFT", 0x01 ); + setNamedValue( "JOYST_RIGHT", 0x02 ); + setNamedValue( "JOYST_DOWN", 0x04 ); + setNamedValue( "JOYST_UP", 0x08 ); + setNamedValue( "JOYST_BUTTON1", 0x10 ); + } + + + public void appendBssTo( AsmCodeBuf buf ) + { + super.appendBssTo( buf ); + if( this.usesX_M_INKEY ) { + buf.append( "X_M_INKEY:\n" + + "\tDS\t1\n" ); + } } @Override - public void appendExit( AsmCodeBuf buf ) + public void appendExitTo( AsmCodeBuf buf ) { buf.append( "\tJP\t0000H\n" ); } @Override - public void appendHChar( AsmCodeBuf buf ) + public void appendHCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0018H\n" ); } @Override - public void appendHPixel( AsmCodeBuf buf ) + public void appendHPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0030H\n" ); } + public void appendInitTo( AsmCodeBuf buf ) + { + super.appendInitTo( buf ); + if( this.usesX_M_INKEY ) { + buf.append( "\tXOR\tA\n" + + "\tLD\t(X_M_INKEY),A\n" ); + } + } + + @Override - public void appendInput( + public void appendInputTo( AsmCodeBuf buf, boolean xckbrk, boolean xinkey, @@ -60,43 +98,85 @@ public void appendInput( { if( xckbrk ) { buf.append( "XCKBRK:\n" ); - } - if( xckbrk || xinkey) { - buf.append( "XINKEY:\tLD\tC,0BH\n" - + "\tCALL\t0005H\n" - + "\tJR\tNC,XINKE2\n" - + "XINKE1:\tXOR\tA\n" + if( xinkey ) { + buf.append( "\tCALL\tXINKE1\n" + + "\tRET\tZ\n" + + "\tLD\t(X_M_INKEY),A\n" + "\tRET\n" - + "XINKE2:\tOR\tA\n" + + "XINKEY:\tLD\tA,(X_M_INKEY)\n" + + "\tOR\tA\n" + + "\tJR\tZ,XINKE1\n" + + "\tPUSH\tAF\n" + + "\tXOR\tA\n" + + "\tLD\t(X_M_INKEY),A\n" + + "\tPOP\tAF\n" + + "\tRET\n" ); + this.usesX_M_INKEY = true; + } + buf.append( "XINKE1:\tLD\tC,0BH\n" + + "\tCALL\t0005H\n" + + "\tJR\tC,XINKE2\n" + + "\tOR\tA\n" + "\tRET\tZ\n" + "\tLD\tC,01H\n" + "\tCALL\t0005H\n" - + "\tJR\tC,XINKE1\n" ); - if( canBreakOnInput ) { - buf.append( "\tCP\t03H\n" - + "\tJR\tZ,XBREAK\n" ); + + "\tJR\tC,XINKE2\n" + + "\tCP\t03H\n" + + "\tJR\tZ,XBREAK\n" + + "\tOR\tA\n" + + "\tRET\n" + + "XINKE2:\tXOR\tA\n" + + "\tRET\n" ); + } else { + if( xinkey ) { + buf.append( "XINKEY:\tLD\tC,0BH\n" + + "\tCALL\t0005H\n" + + "\tJR\tC,XINKE1\n" + + "\tOR\tA\n" + + "\tRET\tZ\n" + + "\tLD\tC,01H\n" + + "\tCALL\t0005H\n" ); + if( canBreakOnInput ) { + buf.append( "\tJR\tC,XINKE1\n" + + "\tCP\t03H\n" + + "\tJR\tZ,XBREAK\n" + + "\tRET\n" ); + } else { + buf.append( "\tRET\tNC\n" ); + } + buf.append( "XINKE1:\tXOR\tA\n" + + "\tRET\n" ); } - buf.append( "\tRET\n" ); } if( xinch ) { - buf.append( "XINCH:\tLD\tC,1\n" + buf.append( "XINCH:\n" ); + if( this.usesX_M_INKEY ) { + buf.append( "\tXOR\tA\n" + + "\tLD\t(X_M_INKEY),A\n" ); + } + buf.append( "XINCH1:\tLD\tC,01H\n" + "\tCALL\t0005H\n" - + "\tJR\tC,XINCH\n" ); - if( canBreakOnInput ) { + + "\tJR\tC,XINCH1\n" ); + if( xckbrk || canBreakOnInput ) { buf.append( "\tCP\t03H\n" - + "\tJR\tZ,XBREAK\n" ); + + "\tRET\tNZ\n" + + "\tJR\tXBREAK\n" ); + } else { + buf.append( "\tRET\n" ); } - buf.append( "\tRET\n" ); } } @Override - public void appendMenuItem( - BasicCompiler compiler, + public void appendPrologTo( AsmCodeBuf buf, + BasicCompiler compiler, String appName ) { + buf.append( "\tJP\t" ); + buf.append( BasicCompiler.START_LABEL ); + buf.newLine(); if( appName == null ) { appName = ""; } @@ -122,21 +202,21 @@ public void appendMenuItem( @Override - public void appendWChar( AsmCodeBuf buf ) + public void appendWCharTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0028H\n" ); } @Override - public void appendWPixel( AsmCodeBuf buf ) + public void appendWPixelTo( AsmCodeBuf buf ) { buf.append( "\tLD\tHL,0050H\n" ); } @Override - public void appendXBORDER( AsmCodeBuf buf ) + public void appendXBorderTo( AsmCodeBuf buf ) { buf.append( "XBORDER:\n" + "\tLD\tA,05H\n" // Code fuer Randfarbe @@ -147,7 +227,7 @@ public void appendXBORDER( AsmCodeBuf buf ) if( this.xoutchAppended ) { buf.append( "\tJR\tXOUTCH\n" ); } else { - appendXOUTCH( buf ); + appendXOutchTo( buf ); } this.usesColors = true; } @@ -160,7 +240,7 @@ public void appendXBORDER( AsmCodeBuf buf ) * DE: Hintergrundfarbe */ @Override - public void appendXCOLOR( AsmCodeBuf buf ) + public void appendXColorTo( AsmCodeBuf buf ) { buf.append( "XCOLOR:\tLD\tA,L\n" + "\tSLA\tA\n" @@ -179,7 +259,7 @@ public void appendXCOLOR( AsmCodeBuf buf ) @Override - public void appendXCURS( AsmCodeBuf buf ) + public void appendXCursTo( AsmCodeBuf buf ) { buf.append( "XCURS:\tLD\tC,1DH\n" // DCU (Cursor loeschen) + "\tLD\tA,H\n" @@ -191,7 +271,7 @@ public void appendXCURS( AsmCodeBuf buf ) @Override - public void appendXINK( AsmCodeBuf buf ) + public void appendXInkTo( AsmCodeBuf buf ) { buf.append( "XINK:\tLD\tA,L\n" + "\tSLA\tA\n" @@ -215,13 +295,8 @@ public void appendXINK( AsmCodeBuf buf ) * HL: Nummer des Joysticks (0: erster Joystick) * Rueckgabewert: * HL: Joystickstatus - * Bit 0: links - * Bit 1: rechts - * Bit 2: runter - * Bit 3: hoch - * Bit 4: Aktionsknopf */ - public void appendXJOY( AsmCodeBuf buf ) + public void appendXJoyTo( AsmCodeBuf buf ) { buf.append( "XJOY:\tLD\tA,H\n" + "\tOR\tA\n" @@ -243,7 +318,7 @@ public void appendXJOY( AsmCodeBuf buf ) @Override - public void appendXLOCATE( AsmCodeBuf buf ) + public void appendXLocateTo( AsmCodeBuf buf ) { buf.append( "XLOCATE:\n" + "\tLD\tC,18\n" @@ -256,7 +331,7 @@ public void appendXLOCATE( AsmCodeBuf buf ) @Override - public void appendXLPTCH( AsmCodeBuf buf ) + public void appendXLPtchTo( AsmCodeBuf buf ) { buf.append( "XLPTCH:\tLD\tC,5\n" + "\tLD\tE,A\n" @@ -265,7 +340,7 @@ public void appendXLPTCH( AsmCodeBuf buf ) @Override - public void appendXOUTCH( AsmCodeBuf buf ) + public void appendXOutchTo( AsmCodeBuf buf ) { if( !this.xoutchAppended ) { buf.append( "XOUTCH:\tLD\tC,2\n" @@ -277,7 +352,7 @@ public void appendXOUTCH( AsmCodeBuf buf ) @Override - public void appendXOUTNL( AsmCodeBuf buf ) + public void appendXOutnlTo( AsmCodeBuf buf ) { buf.append( "XOUTNL:\tLD\tC,02H\n" + "\tLD\tE,0DH\n" @@ -289,7 +364,7 @@ public void appendXOUTNL( AsmCodeBuf buf ) @Override - public void appendXPAPER( AsmCodeBuf buf ) + public void appendXPaperTo( AsmCodeBuf buf ) { buf.append( "XPAPER:\tLD\tA,L\n" + "\tAND\t07H\n" @@ -304,18 +379,81 @@ public void appendXPAPER( AsmCodeBuf buf ) /* - * Zuruecksetzen eines Pixels + * Setzen eines Pixels ohne Beruecksichtigung des eingestellten Stiftes + * bei gleichzeitigem Test, ob dieser schon gesetzt ist * Parameter: * DE: X-Koordinate * HL: Y-Koordinate + * Rueckgabe: + * CY=1: Pixel bereits gesetzt oder ausserhalb des sichtbaren Bereichs */ @Override - public void appendXPRES( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPaintTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPRES:\tCALL\tX_PCK\n" + buf.append( "XPAINT:\tCALL\tX_PST\n" + "\tRET\tC\n" - + "\tCALL\tX_PST\n" ); - if( this.usesX_MPEN ) { + + "\tLD\tA,B\n" + + "\tAND\tC\n" + + "\tSCF\n" + + "\tRET\tNZ\n" + + "\tCALL\tXPSET2\n" + + "\tOR\tA\n" // CY=0 + + "\tRET\n" ); + appendXPSetTo( buf, compiler ); + } + + + /* + * Farbe eines Pixels ermitteln + * Parameter: + * DE: X-Koordinate (0...255) + * HL: Y-Koordinate (0...255) + * Rueckgabe: + * HL >= 0: Farbcode des Pixels + * HL=-1: Pixel exisitiert nicht + */ + public void appendXPointTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPOINT:\tCALL\tX_PST\n" + + "\tJR\tC,XPOINT2\n" + // Farbbyte lesen + + "\tRES\t2,H\n" + + "\tLD\tD,(HL)\n" + // Pixel auswerten + + "\tLD\tA,B\n" + + "\tAND\tC\n" + + "\tLD\tA,D\n" + + "\tLD\tE,07H\n" + + "\tJR\tZ,XPOINT1\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tSRL\tA\n" + + "\tLD\tE,0FH\n" + + "XPOINT1:\n" + + "\tAND\tE\n" + + "\tLD\tL,A\n" + + "\tLD\tH,00H\n" + + "\tRET\n" + + "XPOINT2:\n" + + "\tLD\tHL,0FFFFH\n" + + "\tRET\n" ); + appendPixUtilTo( buf ); + } + + + /* + * Zuruecksetzen eines Pixels + * Parameter: + * DE: X-Koordinate + * HL: Y-Koordinate + */ + @Override + public void appendXPResTo( AsmCodeBuf buf, BasicCompiler compiler ) + { + buf.append( "XPRES:\tCALL\tX_PST\n" + + "\tRET\tC\n" ); + if( this.usesX_M_PEN ) { buf.append( "\tJR\tXPSET1\n" ); } else { buf.append( "\tLD\tA,C\n" @@ -323,25 +461,24 @@ public void appendXPRES( AsmCodeBuf buf, BasicCompiler compiler ) + "\tAND\tB\n" + "\tJR\tXPSET3\n" ); } - appendXPSET( buf, compiler ); + appendXPSetTo( buf, compiler ); } /* - * Setzen eines Pixels + * Setzen eines Pixels unter Beruecksichtigung des eingestellten Stiftes * Parameter: * DE: X-Koordinate * HL: Y-Koordinate */ @Override - public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPSetTo( AsmCodeBuf buf, BasicCompiler compiler ) { if( !this.xpsetAppended ) { - buf.append( "XPSET:\tCALL\tX_PCK\n" - + "\tRET\tC\n" - + "\tCALL\tX_PST\n" ); - if( this.usesX_MPEN ) { - buf.append( "\tLD\tA,(X_MPEN)\n" + buf.append( "XPSET:\tCALL\tX_PST\n" + + "\tRET\tC\n" ); + if( this.usesX_M_PEN ) { + buf.append( "\tLD\tA,(X_M_PEN)\n" + "\tDEC\tA\n" + "\tJR\tZ,XPSET2\n" // Stift 1 (Normal) + "\tDEC\tA\n" @@ -361,18 +498,21 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) + "XPSET3:\tEX\tDE,HL\n" + "\tLD\tB,0\n" + "\tLD\tC,A\n" - + "\tLD\tHL,X_PST4\n" + + "\tLD\tHL,XPSET_TAB\n" + "\tADD\tHL,BC\n" + "\tLD\tA,(HL)\n" + "\tLD\t(DE),A\n" ); if( this.usesColors ) { - buf.append( "\tLD\tHL,0FC00H\n" - + "\tADD\tHL,DE\n" + buf.append( "\tRES\t2,D\n" + "\tLD\tA,(0027H)\n" - + "\tLD\t(HL),A\n" ); + + "\tLD\t(DE),A\n" ); } - buf.append( "\tRET\n" ); - appendPixUtil( buf ); + buf.append( "\tRET\n" + // Umcodierungstabelle von Bitmuster zu Zeichen + + "XPSET_TAB:\n" + + "\tDB\t20H,0B3H,0B2H,0B7H,0B0H,0B4H,0B8H,0BBH\n" + + "\tDB\t0B1H,0B9H,0B5H,0BAH,0B6H,0BCH,0BDH,0FFH\n" ); + appendPixUtilTo( buf ); this.xpsetAppended = true; } } @@ -388,39 +528,20 @@ public void appendXPSET( AsmCodeBuf buf, BasicCompiler compiler ) * HL=1: Pixel gesetzt */ @Override - public void appendXPTEST( AsmCodeBuf buf, BasicCompiler compiler ) + public void appendXPTestTo( AsmCodeBuf buf, BasicCompiler compiler ) { - buf.append( "XPTEST:\tCALL\tX_PCK\n" - + "\tJR\tNC,X_PTST1\n" - + "\tLD\tHL,0FFFFH\n" - + "\tRET\n" - + "X_PTST1:\n" - + "\tCALL\tX_PST\n" + buf.append( "XPTEST:\tCALL\tX_PST\n" + + "\tJR\tC,X_PTEST1\n" + "\tLD\tA,B\n" + "\tAND\tC\n" + "\tLD\tHL,0000H\n" + "\tRET\tZ\n" + "\tINC\tHL\n" + + "\tRET\n" + + "X_PTEST1:\n" + + "\tLD\tHL,0FFFFH\n" + "\tRET\n" ); - appendPixUtil( buf ); - } - - - /* - * Target-ID-String - */ - @Override - public void appendXTARID( AsmCodeBuf buf ) - { - buf.append( "XTARID:\tDB\t\'Z9001\'\n" - + "\tDB\t00H\n" ); - } - - - @Override - public boolean createsCodeFor( EmuSys emuSys ) - { - return emuSys != null ? (emuSys instanceof Z9001) : false; + appendPixUtilTo( buf ); } @@ -432,86 +553,59 @@ public int get100msLoopCount() @Override - public int getColorBlack() - { - return 0; - } - - - @Override - public int getColorBlinking() - { - return 0x08; - } - - - @Override - public int getColorBlue() - { - return 0x04; - } - - - @Override - public int getColorCyan() - { - return 0x06; - } - - - @Override - public int getColorGreen() + public String[] getBasicTargetNames() { - return 0x02; + return new String[] { BASIC_TARGET_NAME }; } @Override - public int getColorMagenta() + public int getCompatibilityLevel( EmuSys emuSys ) { - return 0x05; - } - - - @Override - public int getColorRed() - { - return 0x01; - } - - - @Override - public int getColorWhite() - { - return 0x07; + int rv = 0; + if( emuSys != null ) { + if( emuSys instanceof Z9001 ) { + rv = 3; + } + } + return rv; } @Override - public int getColorYellow() + public int getDefaultBegAddr() { - return 0x03; + return 0x0300; } @Override - public int getDefaultBegAddr() + public String getHostName() { - return 0x0300; + return "Z9001"; } @Override - public int getGraphicScreenNum() + public int getMaxAppNameLen() { - return 0; + return 8; } @Override - public int getKCNetBaseIOAddr() + public String getStartCmd( EmuSys emuSys, String appName, int begAdAdr ) { - return 0xC0; + String rv = null; + if( (emuSys != null) && (appName != null) ) { + if( emuSys instanceof Z9001 ) { + if( appName.length() > 8 ) { + appName = appName.substring( 0, 8 ); + } + rv = appName; + } + } + return rv; } @@ -526,19 +620,13 @@ public int[] getVdipBaseIOAddresses() public void reset() { super.reset(); + this.usesX_M_INKEY = false; this.usesColors = false; this.pixUtilAppended = false; this.xpsetAppended = false; } - @Override - public boolean supportsAppName() - { - return true; - } - - @Override public boolean supportsBorderColor() { @@ -598,45 +686,39 @@ public String toString() /* --- private Methoden --- */ - private void appendPixUtil( AsmCodeBuf buf ) + private void appendPixUtilTo( AsmCodeBuf buf ) { if( !this.pixUtilAppended ) { - buf.append( - /* - * Pruefen der Parameter - * DE: X-Koordinate (0...79) - * HL: Y-Koordinate (0...47) - * Rueckgabe: - * CY=1: Pixel ausserhalb des gueltigen Bereichs - */ - "X_PCK:\tLD\tA,D\n" + /* + * Pruefen der Parameter und + * ermitteln von Informationen zu einem Pixel + * + * Parameter: + * DE: X-Koordinate (0...79) + * HL: Y-Koordinate (0...47) + * + * Rueckgabe: + * CY=1: Pixel ausserhalb des gueltigen Bereichs + * B: Aktuelles Bitmuster (gesetzte Pixel) in der Speicherzelle, + * Bitanordnung innerhalb einer Zeichenposition: + * +---+ + * |2 3| + * |0 1| + * +---+ + * C: Bitmuster mit einem gesetzten Bit, + * dass das Pixel in der Speicherzelle beschreibt + * HL: Speicherzelle, in der sich das Pixel befindet + */ + buf.append( "X_PST:\tLD\tA,D\n" + "\tOR\tH\n" - + "\tJR\tZ,X_PCK1\n" - + "\tSCF\n" - + "\tRET\n" - + "X_PCK1:\tLD\tA,4FH\n" + + "\tJR\tNZ,X_PST3\n" + + "\tLD\tA,4FH\n" + "\tCP\tE\n" + "\tRET\tC\n" + "\tLD\tA,2FH\n" + "\tCP\tL\n" - + "\tRET\n" - /* - * Ermitteln von Informationen zu einem Pixel - * Parameter: - * DE: X-Koordinate (0...79) - * HL: Y-Koordinate (0...47) - * Rueckgabe: - * B: Aktuelles Bitmuster (gesetzte Pixel) in der Speicherzelle, - * Bitanordnung innerhalb einer Zeichenposition: - * +---+ - * |2 3| - * |0 1| - * +---+ - * C: Bitmuster mit einem gesetzten Bit, - * dass das Pixel in der Speicherzelle beschreibt - * HL: Speicherzelle, in der sich das Pixel befindet - */ - + "X_PST:\tLD\tA,01H\n" + + "\tRET\tC\n" + + "\tLD\tA,01H\n" + "\tSRL\tL\n" + "\tJR\tNC,X_PST1\n" + "\tSLA\tA\n" @@ -664,29 +746,32 @@ private void appendPixUtil( AsmCodeBuf buf ) + "\tLD\tB,0FH\n" + "\tCP\t20H\n" + "\tCP\t0EH\n" - + "\tRET\tC\n" + + "\tCCF\n" + + "\tRET\tNC\n" + "\tCP\t0FFH\n" // 0FFh: 4 gesetzte Pixel + "\tRET\tZ\n" + "\tLD\tB,0\n" + "\tSUB\t0B0H\n" // < 0B0h: 4 nicht gesetzte Pixel - + "\tRET\tC\n" + + "\tCCF\n" + + "\tRET\tNC\n" + "\tCP\t0EH\n" // >= 0BEh: 4 nicht gesetzte Pixel + "\tRET\tNC\n" + "\tPUSH\tHL\n" - + "\tLD\tHL,X_PST3\n" + + "\tLD\tHL,X_PST_TAB\n" + "\tLD\tD,0\n" + "\tLD\tE,A\n" + "\tADD\tHL,DE\n" + "\tLD\tA,(HL)\n" + "\tPOP\tHL\n" + "\tLD\tB,A\n" + + "\tOR\tA\n" // CY=0 + "\tRET\n" - // Umcodierungstabelle von (Zeichen - 0B0H) zu Bitmuster - + "X_PST3:\tDB\t04H,08H,02H,01H,05H,0AH,0CH,03H\n" - + "\tDB\t06H,09H,0BH,07H,0DH,0EH\n" - // Umcodierungstabelle von Bitmuster zu Zeichen - + "X_PST4:\tDB\t20H,0B3H,0B2H,0B7H,0B0H,0B4H,0B8H,0BBH\n" - + "\tDB\t0B1H,0B9H,0B5H,0BAH,0B6H,0BCH,0BDH,0FFH\n" ); + + "X_PST3:\tSCF\n" + + "\tRET\n" + // Umcodierungstabelle von (Zeichen - 0B0H) zu Bitmuster + + "X_PST_TAB:\n" + + "\tDB\t04H,08H,02H,01H,05H,0AH,0CH,03H\n" + + "\tDB\t06H,09H,0BH,07H,0DH,0EH\n" ); this.pixUtilAppended = true; } } diff --git a/src/jkcemu/text/CharConverter.java b/src/jkcemu/text/CharConverter.java index 0dc42ce..e71873a 100644 --- a/src/jkcemu/text/CharConverter.java +++ b/src/jkcemu/text/CharConverter.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -21,6 +21,8 @@ public static enum Encoding { CP850, LATIN1 }; + public static final char REPLACEMENT_CHAR = '\uFFFD'; + private static final String cp437ToUnicode = "\u0000\u263A\u263B\u2665\u2666\u2663\u2660\u2022" @@ -85,7 +87,7 @@ public CharConverter( Encoding encoding ) case ISO646DE: this.encodingDisplayText = "Deutsche Variante von ISO-646" - + " (Umlauten anstelle von [\\]{|}~)"; + + " (Umlaute anstelle von [\\]{|}~)"; break; case CP437: @@ -129,7 +131,7 @@ public String getEncodingName() public char toUnicode( int ch ) { - char rv = '\u0000'; + char rv = REPLACEMENT_CHAR; if( this.encoding == Encoding.ASCII_7BIT ) { if( (ch > 0) && (ch < 0x7F) ) { rv = (char) ch; @@ -166,7 +168,7 @@ else if( encoding == Encoding.ISO646DE ) { break; default: - rv = '\u0000'; + rv = REPLACEMENT_CHAR; if( (ch > 0) && (ch < 0x7F) ) { rv = (char) ch; } @@ -272,4 +274,3 @@ public String toString() return this.encodingDisplayText; } } - diff --git a/src/jkcemu/text/EditText.java b/src/jkcemu/text/EditText.java index 700b2fa..84c8f3e 100644 --- a/src/jkcemu/text/EditText.java +++ b/src/jkcemu/text/EditText.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2013 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -20,7 +20,7 @@ import jkcemu.Main; import jkcemu.base.*; import jkcemu.emusys.*; -import jkcemu.programming.PrgOptions; +import jkcemu.programming.*; import jkcemu.programming.basic.BasicOptions; import jkcemu.print.*; import z80emu.Z80MemView; @@ -33,7 +33,13 @@ public class EditText implements { public static final String TEXT_WITH_BOM = " mit Byte-Order-Markierung"; + public static final String PROP_PROPERTIES_TYPE + = "jkcemu.properties.type"; + public static final String PROP_PRG_SOURCE_FILE_NAME + = "jkcemu.programming.source.file.name"; + private boolean used; + private boolean charsLostOnOpen; private boolean dataChanged; private boolean prjChanged; private boolean saved; @@ -137,6 +143,12 @@ public CharConverter getCharConverter() } + public boolean getCharsLostOnOpen() + { + return this.charsLostOnOpen; + } + + public String getEncodingDescription() { return this.encodingDesc; @@ -202,8 +214,9 @@ public int getTabSize() int rv = 8; if( this.textArea != null ) { rv = this.textArea.getTabSize(); - if( rv < 1 ) + if( rv < 1 ) { rv = 8; + } } return rv; } @@ -279,6 +292,23 @@ public boolean hasProjectChanged() } + public boolean hasRowHeader() + { + boolean rv = false; + JScrollPane sp = getJScrollPane(); + if( sp != null ) { + JViewport vp = sp.getRowHeader(); + if( vp != null ) { + Component c = vp.getView(); + if( c != null ) { + rv = true; + } + } + } + return rv; + } + + public boolean isSameFile( File file ) { return ((this.file != null) && (file != null)) ? @@ -287,6 +317,22 @@ public boolean isSameFile( File file ) } + public boolean isSameText( PrgSource src ) + { + boolean rv = false; + if( src != null ) { + String srcText = src.getText(); + if( srcText != null ) { + rv = (!srcText.isEmpty() && srcText.equals( getText() )); + } + if( !rv ) { + rv = isSameFile( src.getFile() ); + } + } + return rv; + } + + public boolean isSaved() { return this.saved; @@ -319,25 +365,36 @@ public void loadFile( FileInfo fileInfo = null; String text = null; String info = null; - byte[] fileBytes = EmuUtil.readFile( file, Integer.MAX_VALUE ); + byte[] fileBytes = EmuUtil.readFile( file, true, Integer.MAX_VALUE ); if( fileBytes != null ) { if( (charConverter == null) && (encodingName == null) ) { // Speicherabbilddatei? - fileInfo = FileInfo.analyzeFile( fileBytes, fileBytes.length, file ); + fileInfo = FileInfo.analyzeFile( + fileBytes, + fileBytes.length, + file ); if( fileInfo != null ) { - String fileFmt = fileInfo.getFileFormat(); + FileFormat fileFmt = fileInfo.getFileFormat(); if( fileFmt != null ) { try { - if( fileFmt.equals( FileInfo.BIN ) ) { + LoadData basicLoadData = null; + if( fileFmt.equals( FileFormat.BIN ) ) { info = "BCS3-BASIC-Programm"; text = BCS3.getBasicProgram( fileBytes ); } - else if( fileFmt.equals( FileInfo.HEADERSAVE ) ) { + else if( fileFmt.equals( FileFormat.BASIC_PRG ) ) { + basicLoadData = FileInfo.createLoadData( + fileBytes, + fileFmt ); + } + else if( fileFmt.equals( FileFormat.HEADERSAVE ) ) { LoadData loadData = null; switch( fileInfo.getFileType() ) { case 'A': - loadData = FileInfo.createLoadData( fileBytes, fileFmt ); + loadData = FileInfo.createLoadData( + fileBytes, + fileFmt ); if( loadData != null ) { info = "EDAS*4-Quelltext"; text = SourceUtil.getEDAS4Text( @@ -347,54 +404,15 @@ else if( fileFmt.equals( FileInfo.HEADERSAVE ) ) { break; case 'B': - loadData = FileInfo.createLoadData( fileBytes, fileFmt ); - if( loadData != null ) { - int addr = fileInfo.getBegAddr(); - switch( addr ) { - case 0x03C0: - case 0x0400: - case 0x0401: - info = "KC-BASIC-Programm"; - text = getKCBasicProgram( loadData, 0x0401 ); - break; - - case 0x1001: - info = "KramerMC-BASIC-Programm"; - text = KramerMC.getBasicProgram( loadData ); - break; - - case 0x2BC0: - case 0x2C00: - case 0x2C01: - info = "KC-BASIC-Programm"; - text = getKCBasicProgram( loadData, 0x2C01 ); - break; - - case 0x60F7: - case 0x6FB7: - /* - * Das SCCH-BASIC fuer den LLC2 ist bzgl. - * des BASIC-Dialekts und - * der Adressen des Quelltextes - * identisch zu der Version fuer den AC1. - * Aus diesem Grund gibt es hier keine - * spezielle Behandlung fuer LLC2-BASIC-Programme. - */ - info = "AC1/LLC2-BASIC-Programm"; - text = AC1.getBasicProgram( - this.textEditFrm, - loadData ); - break; - - default: - info = "BCS3-BASIC-Programm"; - text = BCS3.getBasicProgram( fileBytes ); - } - } + basicLoadData = FileInfo.createLoadData( + fileBytes, + fileFmt ); break; case 'b': - loadData = FileInfo.createLoadData( fileBytes, fileFmt ); + loadData = FileInfo.createLoadData( + fileBytes, + fileFmt ); if( loadData != null ) { switch( loadData.getBegAddr() ) { case 0x1000: @@ -447,19 +465,61 @@ else if( (b == '\t') || (b >= 0x20) ) { break; } } - else if( fileFmt.equals( FileInfo.KCB ) - || fileFmt.equals( FileInfo.KCTAP_BASIC_PRG ) - || fileFmt.equals( FileInfo.KCBASIC_HEAD_PRG ) - || fileFmt.equals( FileInfo.KCBASIC_PRG ) ) + else if( fileFmt.equals( FileFormat.KCB ) + || fileFmt.equals( FileFormat.KCTAP_BASIC_PRG ) + || fileFmt.equals( FileFormat.KCBASIC_HEAD_PRG ) + || fileFmt.equals( FileFormat.KCBASIC_PRG ) ) { LoadData loadData = FileInfo.createLoadData( fileBytes, fileFmt ); if( loadData != null ) { - text = getKCBasicProgram( loadData, fileInfo.getBegAddr() ); + text = getKCBasicProgram( + loadData, + fileInfo.getBegAddr() ); info = "KC-BASIC-Programm"; } } + if( basicLoadData != null ) { + int addr = fileInfo.getBegAddr(); + switch( addr ) { + case 0x03C0: + case 0x0400: + case 0x0401: + info = "KC-BASIC-Programm"; + text = getKCBasicProgram( basicLoadData, 0x0401 ); + break; + + case 0x1001: + info = "KramerMC-BASIC-Programm"; + text = KramerMC.getBasicProgram( basicLoadData ); + break; + + case 0x2BC0: + case 0x2C00: + case 0x2C01: + info = "KC-BASIC-Programm"; + text = getKCBasicProgram( basicLoadData, 0x2C01 ); + break; + + case 0x60F7: + case 0x6300: + case 0x6FB7: + /* + * Das SCCH-BASIC fuer den LLC2 ist bzgl. + * des BASIC-Dialekts und + * der Adressen des Quelltextes + * identisch zu der Version fuer den AC1. + * Aus diesem Grund gibt es hier keine + * spezielle Behandlung fuer LLC2-BASIC-Programme. + */ + info = "AC1/LLC2 BASIC-Programm"; + text = AC1.getBasicProgram( + this.textEditFrm, + basicLoadData ); + break; + } + } } catch( IOException ex ) { text = null; @@ -467,45 +527,21 @@ else if( fileFmt.equals( FileInfo.KCB ) } } if( text != null ) { + StringBuilder buf = new StringBuilder( 512 ); if( info != null ) { - info += ":\n"; - } else { - info = ""; + buf.append( info ); + if( !info.endsWith( ":" ) ) { + buf.append( (char) ':' ); + } + buf.append( (char) '\n' ); } - BasicDlg.showInfoDlg( - this.textEditFrm, - info + "Die Datei ist keine reine Textdatei und kann\n" + buf.append( "Die Datei ist keine reine Textdatei und kann\n" + "deshalb auch nicht als solche ge\u00F6ffnet" + " werden.\n" + "Der in der Datei enthaltene Text wird aber\n" + "extrahiert und als neue Textdatei" + " ge\u00F6ffnet." ); - } - - // WordStar-Datei? - if( (text == null) && (file != null) && (fileBytes.length > 2) ) { - if( fileBytes[ 0 ] == '.' ) { - int b1 = (int) fileBytes[ 1 ] & 0xFF; - if( ((b1 >= '0') && (b1 <= '9')) - || ((b1 >= 'a') && (b1 <= 'z')) ) - { - if( BasicDlg.showYesNoDlg( - this.textEditFrm, - "Die Datei scheint eine WordStar-Datei zu sein.\n" - + "Sie k\u00F6nnen die Datei als" - + " WordStar-Datei importieren oder als" - + " Textdatei \u00F6ffnen.\n" - + "Beim Importieren wird nur der Text" - + " ohne Formatierungen als neue Textdatei" - + " ge\u00F6ffnet.\n\n" - + "M\u00F6chten Sie die Datei als" - + " WordStar-Datei importieren?", - "WordStar-Datei importieren" ) ) - { - text = TextUtil.wordStarToPlainText( fileBytes ); - } - } - } + BasicDlg.showInfoDlg( this.textEditFrm, buf.toString() ); } } if( text != null ) { @@ -587,10 +623,8 @@ else if( fileFmt.equals( FileInfo.KCB ) && (ch != '\n') && (ch != '\r') && (ch != '\u001E') ) { - if( ch == 0 ) { - if( charConverter != null ) { - charsLost = true; - } + if( ch == CharConverter.REPLACEMENT_CHAR ) { + charsLost = true; } else { textBuf.append( (char) ch ); } @@ -606,7 +640,7 @@ else if( fileFmt.equals( FileInfo.KCB ) // kommt noch ein LF? ch = readChar( reader, inStream, charConverter ); - if( (ch == 0) && (charConverter != null) ) { + if( ch == CharConverter.REPLACEMENT_CHAR ) { charsLost = true; } else if( ch == '\n' ) { @@ -638,7 +672,7 @@ else if( ch == '\u001E' ) { boolean wasCR = false; ch = readChar( reader, inStream, charConverter ); while( ch >= 0 ) { - if( (ch == 0) && (charConverter != null) ) { + if( ch == CharConverter.REPLACEMENT_CHAR ) { charsLost = true; } else if( ch == '\r' ) { @@ -692,12 +726,12 @@ else if( (ch != 0) } } } - this.used = true; - this.byteOrderMark = hasBOM; - this.trailing1A = false; - this.charConverter = charConverter; - this.encodingName = encodingName; - this.encodingDesc = encodingDesc; + this.used = true; + this.byteOrderMark = hasBOM; + this.trailing1A = false; + this.charConverter = charConverter; + this.encodingName = encodingName; + this.encodingDesc = encodingDesc; if( text != null ) { int len = text.length(); int pos = len - 1; @@ -734,15 +768,34 @@ else if( (ch != 0) this.textEditFrm.updCaretButtons(); this.textEditFrm.updTitle(); + this.charsLostOnOpen = charsLost; if( charsLost ) { - BasicDlg.showWarningDlg( - this.textEditFrm, - "Die Datei enth\u00E4lt Zeichen, die in dem ausgew\u00E4hlten" - + " Zeichensatz nicht existieren.\n" - + "Diese Zeichen fehlen in dem angezeigten Text." - + " Sie sollten deshalb versuchen,\n" - + "die Datei mit einem anderen Zeichensatz" - + " zu \u00F6ffnen." ); + StringBuilder buf = new StringBuilder( 512 ); + buf.append( "Die Datei enth\u00E4lt Bytes bzw. Bytefolgen," + + " die sich nicht\n" + + "als Zeichen im" ); + if( (charConverter == null) && (encodingName == null) ) { + buf.append( " Systemzeichensatz" ); + } else { + buf.append( " dem ausgew\u00E4hlten Zeichensatz" ); + } + buf.append( " abbilden lassen.\n" + + "Diese Bytes wurden ignoriert. Sie sollten evtl. versuchen,\n" + + "die Datei mit einem anderen Zeichensatz" + + " zu \u00F6ffnen\n" + + "(siehe Men\u00FCpunkt" + + " \'\u00D6ffnen mit Zeichensatz...\')." ); + final String msg = buf.toString(); + final Component owner = this.textEditFrm; + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + BasicDlg.showWarningDlg( owner, msg ); + } + } ); } } catch( UnsupportedEncodingException ex ) { @@ -757,6 +810,15 @@ else if( (ch != 0) } + public void removeRowHeader() + { + JScrollPane sp = getJScrollPane(); + if( sp != null ) { + sp.setRowHeader( null ); + } + } + + public void replaceText( String text ) { boolean docChanged = false; @@ -832,9 +894,9 @@ public void saveFile( lineEndBytes[ 1 ] = (byte) '\n'; } - FileOutputStream outStream = null; + OutputStream outStream = null; try { - outStream = new FileOutputStream( file ); + outStream = EmuUtil.createOptionalGZipOutputStream( file ); BufferedWriter outWriter = null; if( charConverter == null ) { @@ -973,9 +1035,11 @@ public void saveFile( BasicDlg.showWarningDlg( owner, "Der Text enth\u00E4lt Zeichen, die in dem gew\u00FCnschten\n" - + "Zeichensatz nicht existieren und somit auch nicht\n" + + "Zeichensatz nicht existieren und somit auch" + + " nicht\n" + "gespeichert werden k\u00F6nnen, d.h.,\n" - + "diese Zeichen fehlen in der gespeicherten Datei." ); + + "diese Zeichen fehlen in der gespeicherten" + + " Datei." ); } } catch( UnsupportedEncodingException ex ) { @@ -1025,7 +1089,7 @@ public boolean saveProject( Frame frame, boolean askFileName ) out = new FileOutputStream( prjFile ); Properties props = new Properties(); - props.setProperty( "jkcemu.properties.type", "project" ); + props.setProperty( PROP_PROPERTIES_TYPE, "project" ); // Pfad relativ zur Projektdatei String filename = this.file.getPath(); @@ -1042,9 +1106,7 @@ public boolean saveProject( Frame frame, boolean askFileName ) filename = filename.substring( prjDirLen ); } } - props.setProperty( - "jkcemu.programming.source.file.name", - filename ); + props.setProperty( PROP_PRG_SOURCE_FILE_NAME, filename ); if( this.prgOptions == null ) { this.prgOptions = new BasicOptions(); @@ -1062,6 +1124,7 @@ public boolean saveProject( Frame frame, boolean askFileName ) this.prjFile = prjFile; this.prjChanged = true; setProjectChanged( false ); + Main.setLastFile( prjFile, "project" ); } catch( IOException ex ) { BasicDlg.showErrorDlg( @@ -1070,12 +1133,7 @@ public boolean saveProject( Frame frame, boolean askFileName ) + ex.getMessage() ); } finally { - if( out != null ) { - try { - out.close(); - } - catch( IOException ex ) {} - } + EmuUtil.doClose( out ); } } } else { @@ -1231,16 +1289,16 @@ public void changedUpdate( DocumentEvent e ) setDataChanged(); this.used = true; } - - - @Override + + + @Override public void insertUpdate( DocumentEvent e ) { setDataChanged(); this.used = true; } - - + + @Override public void removeUpdate( DocumentEvent e ) { @@ -1250,7 +1308,7 @@ public void removeUpdate( DocumentEvent e ) /* --- UndoableEditListener --- */ - + @Override public void undoableEditHappened( UndoableEditEvent e ) { @@ -1279,6 +1337,7 @@ private void init( TextEditFrm textEditFrm ) this.dropTarget1 = null; this.dropTarget2 = null; this.used = false; + this.charsLostOnOpen = false; this.dataChanged = false; this.prjChanged = false; this.saved = true; @@ -1310,17 +1369,17 @@ private String getKCBasicProgram( { String rv = null; - String kc85Basic = SourceUtil.getKCBasicStyleProgram( + String kc85Basic = SourceUtil.getBasicProgram( loadData, begAddr, KC85.basicTokens ); - String z9001Basic = SourceUtil.getKCBasicStyleProgram( + String z9001Basic = SourceUtil.getBasicProgram( loadData, begAddr, Z9001.basicTokens ); - String z1013Basic = SourceUtil.getKCBasicStyleProgram( + String z1013Basic = SourceUtil.getBasicProgram( loadData, begAddr, Z1013.basicTokens ); @@ -1341,8 +1400,8 @@ private String getKCBasicProgram( || (z1013Basic != null) || (z9001Basic != null) ) { - java.util.List optionTexts = new ArrayList( 3 ); - Map basicTexts = new HashMap(); + java.util.List optionTexts = new ArrayList<>( 3 ); + Map basicTexts = new HashMap<>(); if( kc85Basic != null ) { String optionText = "KC85/2...5"; optionTexts.add( optionText ); @@ -1400,6 +1459,21 @@ else if( n > 1 ) { } + private JScrollPane getJScrollPane() + { + JScrollPane sp = null; + Component c = this.tabComponent; + while( (sp == null) && (c != null) ) { + if( c instanceof JScrollPane ) { + sp = (JScrollPane) c; + break; + } + c = c.getParent(); + } + return sp; + } + + private int readChar( Reader reader, InputStream inStream, @@ -1420,9 +1494,10 @@ private int readChar( private void setDataUnchanged( final boolean saved ) { - final EditText editText = this; - editText.dataChanged = false; - editText.saved = saved; + final EditText editText = this; + editText.dataChanged = false; + editText.saved = saved; + editText.charsLostOnOpen = false; EventQueue.invokeLater( new Runnable() { @@ -1460,10 +1535,10 @@ private void writeLineEnd( BufferedWriter outWriter, String lineEnd ) throws IOException { - if( lineEnd != null ) + if( lineEnd != null ) { outWriter.write( lineEnd ); - else + } else { outWriter.newLine(); + } } } - diff --git a/src/jkcemu/text/LineAddrRowHeader.java b/src/jkcemu/text/LineAddrRowHeader.java new file mode 100644 index 0000000..d0672ab --- /dev/null +++ b/src/jkcemu/text/LineAddrRowHeader.java @@ -0,0 +1,232 @@ +/* + * (c) 2016 Jens Mueller + * + * Kleincomputer-Emulator + * + * RowHeader-Komponente zur Anzeige von Adressen + */ + +package jkcemu.text; + +import java.awt.*; +import java.awt.event.*; +import java.lang.*; +import java.util.*; +import javax.swing.*; +import javax.swing.text.BadLocationException; +import jkcemu.Main; + + +public class LineAddrRowHeader extends JComponent +{ + private static final int MARGIN = 5; + + private JTextArea textArea; + private Map lineAddrMap; + private Image closeImg; + private int closeX; + private int closeY; + private int closeW; + private int closeH; + + + public LineAddrRowHeader( + JTextArea textArea, + Map lineAddrMap ) + { + this.textArea = textArea; + this.lineAddrMap = lineAddrMap; + this.closeImg = Main.getImage( this, "/images/file/close.png" ); + this.closeX = 0; + this.closeY = 0; + this.closeW = 0; + this.closeH = 0; + setToolTipText( "Adressen im Arbeitsspeicher" ); + addMouseListener( + new MouseAdapter() + { + @Override + public void mouseClicked( MouseEvent e ) + { + mouseClickedInternal( e ); + } + } ); + } + + + /* --- ueberschriebene Methoden --- */ + + @Override + public Dimension getPreferredSize() + { + Dimension rv = null; + if( isPreferredSizeSet() ) { + rv = super.getPreferredSize(); + } else { + int h = 0; + int w = 0; + Font font = this.textArea.getFont(); + if( (font != null) && (this.lineAddrMap != null) ) { + FontMetrics fm = getFontMetrics( font ); + if( fm != null ) { + w = fm.stringWidth( "8888" ); + if( w > 0 ) { + w += (2 * MARGIN); + } + } + } + Dimension size = this.textArea.getPreferredSize(); + if( size != null ) { + h = size.height; + } + rv = new Dimension( w, h ); + } + return rv; + } + + + @Override + public String getToolTipText( MouseEvent e ) + { + return isOverClose( e ) ? + "Adressspalte ausblenden" + : super.getToolTipText( e ); + } + + + @Override + public void paintComponent( Graphics g ) + { + int w = getWidth(); + + // sichtbarer Bereich ermitteln + int yVisible = 0; + int hVisible = getHeight(); + if( (w > 0) && (hVisible > 0) ) { + + // sichtbarer Bereich ermitteln + Component parent = getParent(); + if( parent != null ) { + if( parent instanceof JViewport ) { + Rectangle r = ((JViewport) parent).getViewRect(); + if( r != null ) { + yVisible += r.y; + hVisible = r.height; + } + } + } + + // Hintergrund loeschen + g.setColor( getBackground() ); + g.setPaintMode(); + g.fillRect( 0, yVisible, w, yVisible + hVisible ); + g.setColor( Color.gray ); + g.drawLine( w - 1, yVisible, w - 1, yVisible + hVisible ); + + // Adressliste + try { + g.setColor( SystemColor.textText ); + Font font = this.textArea.getFont(); + if( (font != null) && (this.lineAddrMap != null) ) { + g.setFont( font ); + + // Bereich der sichtbaren Zeilen ermitteln + Insets insets = this.textArea.getInsets(); + int x = (insets != null ? insets.left : 0); + int begLine = 0; + int endLine = 0; + + int pos = this.textArea.viewToModel( new Point( x, yVisible ) ); + if( pos >= 0 ) { + begLine = this.textArea.getLineOfOffset( pos ); + } + pos = this.textArea.viewToModel( + new Point( x, yVisible + hVisible) ); + if( pos >= 0 ) { + endLine = this.textArea.getLineOfOffset( pos ) + 1; + } + + // sichtbare Zeilen zeichnen + int nLines = this.textArea.getLineCount(); + if( endLine >= nLines ) { + endLine = nLines - 1; + } + for( int line = begLine; line < endLine; line++ ) { + Integer addr = this.lineAddrMap.get( line + 1 ); + if( addr != null ) { + Rectangle r = this.textArea.modelToView( + this.textArea.getLineStartOffset( line ) ); + if( r != null ) { + int y = r.y + r.height - 5; + if( (y >= yVisible) + && ((y - font.getSize()) < (yVisible + hVisible)) ) + { + g.drawString( + String.format( "%04X", addr ), MARGIN, y ); + } + } + } + } + } + } + catch( BadLocationException ex ) {} + + // Close-Button + if( this.closeImg != null ) { + int closeW = this.closeImg.getWidth( this ); + int closeH = this.closeImg.getHeight( this ); + if( (closeW > 0) && (closeH > 0) && (closeW < (w - (2 * MARGIN))) ) { + this.closeX = w - MARGIN - closeW - 1; + this.closeY = yVisible + 1; + this.closeW = closeW; + this.closeH = closeH; + g.setColor( getBackground() ); + g.fillRect( + this.closeX - 1, + this.closeY - 1, + this.closeW + 2, + this.closeH + 2 ); + g.drawImage( this.closeImg, this.closeX, this.closeY, this ); + } + } + } + } + + + /* --- private Methoden --- */ + + private boolean isOverClose( MouseEvent e ) + { + boolean rv = false; + if( (this.closeW > 0) && (this.closeH > 0) ) { + int x = e.getX(); + int y = e.getY(); + if( (x >= this.closeX) && (x < (this.closeX + this.closeW)) + && (y >= this.closeY) && (y < (this.closeY + this.closeH)) ) + { + rv = true; + } + } + return rv; + } + + + private void mouseClickedInternal( MouseEvent e ) + { + if( isOverClose( e ) ) { + JScrollPane sp = null; + Component c = this; + while( (sp == null) && (c != null) ) { + if( c instanceof JScrollPane ) { + sp = (JScrollPane) c; + break; + } + c = c.getParent(); + } + if( sp != null ) { + sp.setRowHeader( null ); + } + e.consume(); + } + } +} diff --git a/src/jkcemu/text/LogFrm.java b/src/jkcemu/text/LogFrm.java index 0925f18..f5debd1 100644 --- a/src/jkcemu/text/LogFrm.java +++ b/src/jkcemu/text/LogFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2012 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -10,6 +10,7 @@ import java.awt.*; import java.awt.event.*; +import java.io.File; import java.lang.*; import java.util.EventObject; import javax.swing.*; @@ -81,9 +82,9 @@ public LogFrm( EditText correspondingEditText, String title ) Font font = this.fldText.getFont(); if( font != null ) { this.fldText.setFont( - new Font( "Monospaced", font.getStyle(), font.getSize() ) ); + new Font( Font.MONOSPACED, font.getStyle(), font.getSize() ) ); } else { - this.fldText.setFont( new Font( "Monospaced", Font.PLAIN, 12 ) ); + this.fldText.setFont( new Font( Font.MONOSPACED, Font.PLAIN, 12 ) ); } @@ -248,36 +249,59 @@ public void run() * Diese Methode wird ausgefuehrt, * wenn auf die uebergebene Zeile doppelt geklickt wurde. */ - private void processLineAction( String text ) + private void processLineAction( String line ) { EditText editText = this.correspondingEditText; - if( (editText != null) && (text != null) ) { - int pos = text.indexOf( "Zeile" ); + if( (editText != null) && (line != null) ) { + int pos = line.indexOf( "Zeile" ); if( pos >= 0 ) { - pos += 5; // Position hinter dem Wort Zeile - int len = text.length(); - while( pos < len ) { - if( !Character.isWhitespace( text.charAt( pos ) ) ) { - break; + /* + * Wenn vor dem Wort 'Zeile' ein Doppelpunkt steht, + * enthaelt die Zeile einen Dateinamen, d.h., + * der Fehler befindet sich in einer inkludierten Quelltextdatei. + * In dem Fall nur dann zu der entsprechenden Zeile + * im aktuellen Quelltext springen, + * wenn der Dateiname uebereinstimmt. + */ + boolean fileOK = true; + int dpPos = line.indexOf( ':' ); + if( (dpPos >= 0) && (dpPos < pos) ) { + fileOK = false; + if( dpPos > 0 ) { + File file = editText.getFile(); + if( file != null ) { + String fileName = file.getName(); + if( fileName != null ) { + fileOK = fileName.equals( line.substring( 0, dpPos ) ); + } + } } - pos++; } - if( pos < len ) { - char ch = text.charAt( pos++ ); - if( (ch >= '0') && (ch <= '9') ) { - int lineNum = ch - '0'; - while( pos < len ) { - ch = text.charAt( pos++ ); - if( (ch < '0') || (ch > '9') ) { - break; + if( fileOK ) { + pos += 5; // Position hinter dem Wort Zeile + int len = line.length(); + while( pos < len ) { + if( !Character.isWhitespace( line.charAt( pos ) ) ) { + break; + } + pos++; + } + if( pos < len ) { + char ch = line.charAt( pos++ ); + if( (ch >= '0') && (ch <= '9') ) { + int lineNum = ch - '0'; + while( pos < len ) { + ch = line.charAt( pos++ ); + if( (ch < '0') || (ch > '9') ) { + break; + } + lineNum = (lineNum * 10) + (ch - '0'); } - lineNum = (lineNum * 10) + (ch - '0'); + editText.gotoLine( lineNum ); } - editText.gotoLine( lineNum ); } } } } } } - diff --git a/src/jkcemu/text/ReplyShiftWidthDlg.java b/src/jkcemu/text/ReplyShiftWidthDlg.java index 8635a47..e343a52 100644 --- a/src/jkcemu/text/ReplyShiftWidthDlg.java +++ b/src/jkcemu/text/ReplyShiftWidthDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2010-2012 Jens Mueller + * (c) 2010-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -142,12 +142,12 @@ private void doApply() if( shiftWidth > 0 ) { this.textEditFrm.setShiftWidth( shiftWidth ); Main.setProperty( - "jkcemu.texteditor.shift.width", + TextEditFrm.PROP_SHIFT_WIDTH, value.toString() ); boolean useTabs = this.tglUseTabs.isSelected(); this.textEditFrm.setShiftUseTabs( useTabs ); Main.setProperty( - "jkcemu.texteditor.shift.use_tabs", + TextEditFrm.PROP_SHIFT_USE_TABS, Boolean.toString( useTabs ) ); doClose(); } diff --git a/src/jkcemu/text/ReplyTabSizeDlg.java b/src/jkcemu/text/ReplyTabSizeDlg.java index ff64db4..da0d4a9 100644 --- a/src/jkcemu/text/ReplyTabSizeDlg.java +++ b/src/jkcemu/text/ReplyTabSizeDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2010 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -141,7 +141,7 @@ private void doApply() this.textArea.setTabSize( tabSize ); if( this.tglAsDefault.isSelected() ) { Main.setProperty( - "jkcemu.texteditor.tabsize", + TextEditFrm.PROP_TABSIZE, String.valueOf( tabSize ) ); } } diff --git a/src/jkcemu/text/SaveTextDlg.java b/src/jkcemu/text/SaveTextDlg.java index c8e555c..2dc7caa 100644 --- a/src/jkcemu/text/SaveTextDlg.java +++ b/src/jkcemu/text/SaveTextDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2012 Jens Mueller + * (c) 2008-2015 Jens Mueller * * Kleincomputer-Emulator * @@ -19,15 +19,15 @@ public class SaveTextDlg extends BasicDlg { - private JCheckBox btnTrailing1A; - private JCheckBox btnTrimLines; - private JComboBox comboEncoding; - private JComboBox comboLineEnd; - private JButton btnSave; - private JButton btnCancel; - private File file; - private EditText editText; - private boolean fileSaved; + private JCheckBox btnTrailing1A; + private JCheckBox btnTrimLines; + private JComboBox comboEncoding; + private JComboBox comboLineEnd; + private JButton btnSave; + private JButton btnCancel; + private File file; + private EditText editText; + private boolean fileSaved; public SaveTextDlg( File file, EditText editText ) @@ -80,7 +80,7 @@ public SaveTextDlg( File file, EditText editText ) panelProp.add( new JLabel( "Zeichensatz:" ), gbcProp ); - this.comboEncoding = new JComboBox(); + this.comboEncoding = new JComboBox<>(); this.comboEncoding.addItem( "Systemzeichensatz" ); this.comboEncoding.addItem( new CharConverter( CharConverter.Encoding.ASCII_7BIT ) ); @@ -110,7 +110,7 @@ public SaveTextDlg( File file, EditText editText ) gbcProp.gridy++; panelProp.add( new JLabel( "Zeilenende:" ), gbcProp ); - this.comboLineEnd = new JComboBox(); + this.comboLineEnd = new JComboBox<>(); this.comboLineEnd.addItem( new TextLineSeparator( "\r\n" ) ); this.comboLineEnd.addItem( new TextLineSeparator( "\n" ) ); this.comboLineEnd.addItem( new TextLineSeparator( "\r" ) ); @@ -208,7 +208,7 @@ public static boolean saveFile( editText.getTextEditFrm(), "Textdatei speichern", preSelection != null ? - preSelection : Main.getLastPathFile( "text" ), + preSelection : Main.getLastDirFile( "text" ), TextEditFrm.getTextFileFilters() ); if( file != null ) { @@ -305,8 +305,9 @@ private void doSave() String lineEnd = "\r\n"; Object lineEndObj = this.comboLineEnd.getSelectedItem(); if( lineEndObj != null ) { - if( lineEndObj instanceof TextLineSeparator ) + if( lineEndObj instanceof TextLineSeparator ) { lineEnd = ((TextLineSeparator) lineEndObj).getLineEnd(); + } } // Datei schreiben diff --git a/src/jkcemu/text/SelectEncodingDlg.java b/src/jkcemu/text/SelectEncodingDlg.java index df15e48..85e81e1 100644 --- a/src/jkcemu/text/SelectEncodingDlg.java +++ b/src/jkcemu/text/SelectEncodingDlg.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2012 Jens Mueller + * (c) 2008-2014 Jens Mueller * * Kleincomputer-Emulator * @@ -17,13 +17,13 @@ public class SelectEncodingDlg extends BasicDlg { - private boolean applied; - private CharConverter charConverter; - private String encodingName; - private String encodingDisplayText; - private JComboBox comboEncoding; - private JButton btnOK; - private JButton btnCancel; + private boolean applied; + private CharConverter charConverter; + private String encodingName; + private String encodingDisplayText; + private JComboBox comboEncoding; + private JButton btnOK; + private JButton btnCancel; public SelectEncodingDlg( Frame parent ) @@ -58,7 +58,7 @@ public SelectEncodingDlg( Frame parent ) gbc ); // Auswahlfeld - this.comboEncoding = new JComboBox(); + this.comboEncoding = new JComboBox<>(); this.comboEncoding.addItem( "Systemzeichensatz" ); this.comboEncoding.addItem( new CharConverter( CharConverter.Encoding.ASCII_7BIT ) ); diff --git a/src/jkcemu/text/TextEditFrm.java b/src/jkcemu/text/TextEditFrm.java index 64b3381..b206bdc 100644 --- a/src/jkcemu/text/TextEditFrm.java +++ b/src/jkcemu/text/TextEditFrm.java @@ -1,5 +1,5 @@ /* - * (c) 2008-2012 Jens Mueller + * (c) 2008-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -33,6 +33,15 @@ public class TextEditFrm extends BasicFrm implements DropTargetListener, FlavorListener { + public static final String PROP_SHOW_LINE_ADDRS + = "jkcemu.texteditor.show_line_addrs"; + public static final String PROP_TABSIZE + = "jkcemu.texteditor.tabsize"; + public static final String PROP_SHIFT_WIDTH + = "jkcemu.texteditor.shift.width"; + public static final String PROP_SHIFT_USE_TABS + = "jkcemu.texteditor.shift.use_tabs"; + private static final String DEFAULT_STATUS_TEXT = "Bereit"; private static TextEditFrm instance = null; @@ -62,6 +71,7 @@ public class TextEditFrm extends BasicFrm implements private JMenuItem mnuEditShiftWidth; private JMenuItem mnuEditUmlautGerToUni; private JMenuItem mnuEditUmlautDosToUni; + private JMenuItem mnuEditRemoveWordstarFmt; private JMenuItem mnuEditFind; private JMenuItem mnuEditFindNext; private JMenuItem mnuEditReplace; @@ -71,6 +81,8 @@ public class TextEditFrm extends BasicFrm implements private JMenuItem mnuPrgAssemble; private JMenuItem mnuPrgAssembleRun; private JMenuItem mnuPrgAssembleOpt; + private JCheckBoxMenuItem mnuPrgLineAddrs; + private JMenuItem mnuRemoveRowHeader; private JMenuItem mnuPrgCompile; private JMenuItem mnuPrgCompileRun; private JMenuItem mnuPrgCompileOpt; @@ -277,7 +289,7 @@ public static Properties loadProject( File file ) throws IOException EmuUtil.doClose( in ); } if( props != null ) { - String s = props.getProperty( "jkcemu.properties.type" ); + String s = props.getProperty( EditText.PROP_PROPERTIES_TYPE ); if( s != null ) { if( !s.equals( "project" ) ) { props = null; @@ -331,11 +343,19 @@ public void openProject( File file, Properties props ) { if( props != null ) { String srcFileName = props.getProperty( - "jkcemu.programming.source.file.name" ); + EditText.PROP_PRG_SOURCE_FILE_NAME ); if( srcFileName != null ) { - EditText editText = openTextFile( new File( srcFileName ) ); + File srcFile = new File( srcFileName ); + if( !srcFile.isAbsolute() ) { + File parent = file.getParentFile(); + if( parent != null ) { + srcFile = new File( parent, srcFileName ); + } + } + EditText editText = openTextFile( srcFile ); if( editText != null ) { editText.setProject( file, props ); + Main.setLastFile( file, "project" ); } } } @@ -383,11 +403,12 @@ public void setShiftWidth( int shiftWidth ) } - public void threadTerminated( Thread thread ) + public void threadTerminated( final Thread thread ) { if( (thread != null) && (thread == this.prgThread) ) { this.prgThread = null; - final JMenuItem mnuPrgCancel = this.mnuPrgCancel; + final JMenuItem mnuPrgCancel = this.mnuPrgCancel; + final JCheckBoxMenuItem mnuPrgLineAddr = this.mnuPrgLineAddrs; EventQueue.invokeLater( new Runnable() { @@ -396,6 +417,11 @@ public void run() { mnuPrgCancel.setEnabled( false ); updFileButtons(); + if( (thread instanceof AsmThread) + && mnuPrgLineAddr.isSelected() ) + { + setLineAddrs( ((AsmThread) thread).getPrgSources() ); + } } } ); } @@ -480,8 +506,9 @@ public void dragOver( DropTargetDragEvent e ) public void drop( DropTargetDropEvent e ) { File file = EmuUtil.fileDrop( this, e ); - if( file != null ) + if( file != null ) { openFile( file ); + } } @@ -508,11 +535,17 @@ public void flavorsChanged( FlavorEvent e ) @Override public boolean applySettings( Properties props, boolean resizable ) { - boolean rv = super.applySettings( props, resizable ); + boolean rv = super.applySettings( props, resizable ); AbstractOptionsDlg dlg = this.prgOptionsDlg; if( dlg != null ) { dlg.settingsChanged(); } + this.mnuPrgLineAddrs.setSelected( + EmuUtil.parseBooleanProperty( + props, + PROP_SHOW_LINE_ADDRS, + true ) ); + updShowLineAddrs(); return rv; } @@ -624,6 +657,10 @@ else if( src == this.mnuEditUmlautDosToUni ) { rv = true; doEditUmlautDosToUni(); } + else if( src == this.mnuEditRemoveWordstarFmt ) { + rv = true; + doEditRemoveWordstarFmt(); + } else if( (src == this.mnuEditFind) || (src == this.mnuPopupFind) || (src == this.btnFind) ) @@ -667,6 +704,14 @@ else if( src == this.mnuPrgAssembleOpt ) { rv = true; doPrgAssemble( true, false ); } + else if( src == this.mnuPrgLineAddrs ) { + rv = true; + updShowLineAddrs(); + } + else if( src == this.mnuRemoveRowHeader ) { + rv = true; + doRemoveRowHeader(); + } else if( src == this.mnuPrgCompile ) { rv = true; doPrgCompile( false, false ); @@ -738,6 +783,25 @@ public void lookAndFeelChanged() } + @Override + public void putSettingsTo( Properties props ) + { + if( props != null ) { + super.putSettingsTo( props ); + props.setProperty( + PROP_SHOW_LINE_ADDRS, + String.valueOf( this.mnuPrgLineAddrs.isSelected() ) ); + } + } + + + @Override + public void resetFired() + { + removeRowHeaders(); + } + + @Override protected boolean showPopup( MouseEvent e ) { @@ -772,10 +836,11 @@ private void doFileOpen() File file = EmuUtil.showFileOpenDlg( this, "Textdatei \u00F6ffnen", - Main.getLastPathFile( "text" ), + Main.getLastDirFile( "text" ), getTextFileFilters() ); - if( file != null ) + if( file != null ) { openFile( file ); + } } @@ -784,7 +849,7 @@ private void doFileOpenCharset() File file = EmuUtil.showFileOpenDlg( this, "Textdatei \u00F6ffnen mit Zeichensatz", - Main.getLastPathFile( "text" ), + Main.getLastDirFile( "text" ), getTextFileFilters() ); if( file != null ) { if( checkFileAlreadyOpen( file ) == null ) { @@ -860,8 +925,9 @@ private void doFilePrint() private void doFileProperties() { EditText editText = getSelectedEditText(); - if( editText != null ) + if( editText != null ) { (new TextPropDlg( this, editText )).setVisible( true ); + } } @@ -876,8 +942,6 @@ private boolean doFileClose() prjChanged = editText.hasProjectChanged(); } if( textChanged || prjChanged ) { - setState( Frame.NORMAL ); - toFront(); String[] options = { "Speichern", "Verwerfen", "Abbrechen" }; JOptionPane pane = new JOptionPane( String.format( @@ -930,12 +994,20 @@ else if( value.equals( options[ 1 ] ) ) { } this.editTexts.remove( editText ); editText.die(); - updTitle( null ); - updUndoButtons(); - updCaretButtons(); - updPasteButtons(); - updFileButtons(); - updStatusBar(); + EventQueue.invokeLater( + new Runnable() + { + @Override + public void run() + { + updTitle( getSelectedEditText() ); + updUndoButtons(); + updCaretButtons(); + updPasteButtons(); + updFileButtons(); + updStatusBar(); + } + } ); } } else { rv = super.doClose(); @@ -959,24 +1031,27 @@ private void doEditUndo() private void doEditCut() { JTextArea textArea = getSelectedJTextArea(); - if( textArea != null ) + if( textArea != null ) { textArea.cut(); + } } private void doEditCopy() { JTextArea textArea = getSelectedJTextArea(); - if( textArea != null ) + if( textArea != null ) { textArea.copy(); + } } private void doEditPaste() { JTextArea textArea = getSelectedJTextArea(); - if( textArea != null ) + if( textArea != null ) { textArea.paste(); + } } @@ -986,8 +1061,9 @@ private void doEditUpper() if( textArea != null ) { String s = textArea.getSelectedText(); if( s != null ) { - if( s.length() > 0 ) + if( !s.isEmpty() ) { textArea.replaceSelection( s.toUpperCase() ); + } } } } @@ -999,8 +1075,9 @@ private void doEditLower() if( textArea != null ) { String s = textArea.getSelectedText(); if( s != null ) { - if( s.length() > 0 ) + if( !s.isEmpty() ) { textArea.replaceSelection( s.toLowerCase() ); + } } } } @@ -1009,8 +1086,9 @@ private void doEditLower() private void doEditTabSize() { JTextArea textArea = getSelectedJTextArea(); - if( textArea != null ) + if( textArea != null ) { (new ReplyTabSizeDlg( this, textArea )).setVisible( true ); + } } @@ -1071,8 +1149,9 @@ private void doEditTabToSpaces() editText.setDataChanged(); } showConvResult( cnt ); - if( newCaretPos >= 0 ) + if( newCaretPos >= 0 ) { editText.setCaretPosition( newCaretPos ); + } } } } @@ -1318,6 +1397,66 @@ private void doEditUmlautDosToUni() } + private void doEditRemoveWordstarFmt() + { + EditText editText = getSelectedEditText(); + if( editText != null ) { + String msg = "M\u00F6chten Sie die WordStar-Formatierungen entfernen\n" + + "und so aus der Datei eine reine Textdatei machen?"; + if( editText.getCharsLostOnOpen() ) { + msg = "Beim \u00D6ffnen der Datei konnten nicht alle Bytes" + + " in Zeichen gemappt werden.\n" + + "Dadurch k\u00F6nnen Zeichen fehlen oder der Text" + + " anderweitig zerst\u00F6rt sein.\n" + + "Aus diesem Grund sollten Sie die Datei zuerst" + + " mit dem Zeichensatz\n" + + "ISO-8859-1 (Latin 1) \u00F6ffnen (Men\u00FCpunkt" + + " \'Datei \u00F6ffnen mit Zeichensatz...\')\n" + + "und dann erst die WordStar-Formatierungen entfernen.\n\n" + + msg; + } + if( BasicDlg.showYesNoDlg( this, msg ) ) { + String oldText = editText.getText(); + if( oldText != null ) { + int oldCrs = editText.getCaretPosition(); + int newCrs = -1; + int len = oldText.length(); + int pos = 0; + boolean bol = true; // Zeilenanfang + boolean ign = false; // Zeile ignorieren + StringBuilder buf = new StringBuilder( len ); + while( pos < len ) { + if( (newCrs < 0) && (oldCrs == pos) ) { + newCrs = buf.length(); + } + char ch = oldText.charAt( pos++ ); + if( bol && (ch == '.') ) { + ign = true; + } + ch &= 0x7F; + if( (ch == '\n') || (ch == '\t') || (ch >= 0x20) ) { + if( !ign ) { + buf.append( ch ); + } + if( ch <= 0x20 ) { + ign = false; + } + if( ch == '\n' ) { + bol = true; + } else { + bol = false; + } + } + } + editText.replaceText( buf.toString() ); + editText.setDataChanged(); + editText.setCaretPosition( newCrs ); + } + } + } + } + + private void doEditFind() { EditText editText = getSelectedEditText(); @@ -1412,8 +1551,9 @@ private void doEditReplace() { JTextArea textArea = getSelectedJTextArea(); if( textArea != null ) { - if( this.textReplace != null ) + if( this.textReplace != null ) { replaceText( textArea ); + } } } @@ -1496,8 +1636,9 @@ private void doEditGoto() lineNum = dlg.getReply(); // zur Zeile springen - if( lineNum != null ) + if( lineNum != null ) { gotoLine( textArea, lineNum.intValue() ); + } } } @@ -1624,7 +1765,7 @@ private void doPrjOpen() File file = EmuUtil.showFileOpenDlg( this, "Projekt \u00F6ffnen", - Main.getLastPathFile( "text" ), + Main.getLastDirFile( "text" ), EmuUtil.getProjectFileFilter() ); if( file != null ) { try { @@ -1647,8 +1788,19 @@ private void doPrjOpen() private void doPrjSave( boolean askFileName ) { EditText editText = getSelectedEditText(); - if( editText != null ) + if( editText != null ) { editText.saveProject( this, askFileName ); + } + } + + + private void doRemoveRowHeader() + { + EditText editText = getSelectedEditText(); + if( editText != null ) { + editText.removeRowHeader(); + updRemoveRowHeaderButtons(); + } } @@ -1657,7 +1809,7 @@ private void doPrjSave( boolean askFileName ) private TextEditFrm( EmuThread emuThread ) { this.emuThread = emuThread; - this.editTexts = new ArrayList(); + this.editTexts = new ArrayList<>(); this.hasTextFind = false; this.findIgnoreCase = true; this.textFind = null; @@ -1667,11 +1819,9 @@ private TextEditFrm( EmuThread emuThread ) this.prgThread = null; this.prgOptionsDlg = null; this.lastNewTextNum = 0; - this.shiftWidth = Main.getIntProperty( - "jkcemu.texteditor.shift.width", - 2 ); + this.shiftWidth = Main.getIntProperty( PROP_SHIFT_WIDTH, 2 ); this.shiftUseTabs = Main.getBooleanProperty( - "jkcemu.texteditor.shift.use_tabs", + PROP_SHIFT_USE_TABS, true ); Main.updIcon( this ); @@ -1812,6 +1962,10 @@ private TextEditFrm( EmuThread emuThread ) mnuEditUmlaut.add( this.mnuEditUmlautDosToUni ); mnuEdit.add( mnuEditUmlaut ); + + this.mnuEditRemoveWordstarFmt = createJMenuItem( + "WordStar-Formatierungen entfernen" ); + mnuEdit.add( this.mnuEditRemoveWordstarFmt ); mnuEdit.addSeparator(); this.mnuEditFind = createJMenuItem( @@ -1878,6 +2032,18 @@ private TextEditFrm( EmuThread emuThread ) mnuPrg.add( this.mnuPrgAssembleOpt ); mnuPrg.addSeparator(); + this.mnuPrgLineAddrs = new JCheckBoxMenuItem( + "Adressspalte nach Assemblieren anzeigen", + Main.getBooleanProperty( + PROP_SHOW_LINE_ADDRS, + true ) ); + this.mnuPrgLineAddrs.addActionListener( this ); + mnuPrg.add( this.mnuPrgLineAddrs ); + + this.mnuRemoveRowHeader = createJMenuItem( "Adressspalte ausblenden" ); + mnuPrg.add( this.mnuRemoveRowHeader ); + mnuPrg.addSeparator(); + this.mnuPrgCancel = createJMenuItem( "Assembler/Compiler abbrechen", KeyStroke.getKeyStroke( KeyEvent.VK_F7, 0 ) ); @@ -1987,7 +2153,7 @@ private TextEditFrm( EmuThread emuThread ) // Schliessenknopf fuer einzelne Tabs this.btnClose = createImageButton( - "/images/file/closetab.png", + "/images/file/close.png", "Unterfenster schlie\u00DFen" ); this.btnClose.setBorder( BorderFactory.createEmptyBorder() ); gbc.anchor = GridBagConstraints.EAST; @@ -2112,13 +2278,14 @@ private JTextArea createJTextArea() JTextArea textArea = new JTextArea(); textArea.setMargin( new Insets( 5, 5, 5, 5 ) ); - String tabSizeText = Main.getProperty( "jkcemu.texteditor.tabsize" ); + String tabSizeText = Main.getProperty( PROP_TABSIZE ); if( tabSizeText != null ) { - if( tabSizeText.length() > 0 ) { + if( !tabSizeText.isEmpty() ) { try { int tabSize = Integer.parseInt( tabSizeText ); - if( (tabSize > 0) && (tabSize < 100) ) + if( (tabSize > 0) && (tabSize < 100) ) { textArea.setTabSize( tabSize ); + } } catch( NumberFormatException ex ) {} } @@ -2127,9 +2294,9 @@ private JTextArea createJTextArea() Font font = textArea.getFont(); if( font != null ) { textArea.setFont( - new Font( "Monospaced", font.getStyle(), font.getSize() ) ); + new Font( Font.MONOSPACED, font.getStyle(), font.getSize() ) ); } else { - textArea.setFont( new Font( "Monospaced", Font.PLAIN, 12 ) ); + textArea.setFont( new Font( Font.MONOSPACED, Font.PLAIN, 12 ) ); } textArea.addMouseListener( this ); return textArea; @@ -2152,8 +2319,9 @@ private Integer findBracket( } else if( ch == bracket2 ) { --level; - if( level <= 0 ) + if( level <= 0 ) { return new Integer( pos ); + } } pos += step; } @@ -2216,8 +2384,9 @@ private EditText getSelectedEditText() Component c = this.tabbedPane.getSelectedComponent(); if( c != null ) { for( EditText editText : this.editTexts ) { - if( editText.getTabComponent() == c ) + if( editText.getTabComponent() == c ) { return editText; + } } } return null; @@ -2347,16 +2516,26 @@ private Appendable openLog( EditText editText, String subTitle ) } + private void removeRowHeaders() + { + for( EditText editText : this.editTexts ) { + editText.removeRowHeader(); + } + updRemoveRowHeaderButtons(); + } + + private boolean replaceText( JTextArea textArea ) { int selBeg = textArea.getSelectionStart(); int selEnd = textArea.getSelectionEnd(); - if( (selBeg < 0) || (selBeg >= selEnd) ) + if( (selBeg < 0) || (selBeg >= selEnd) ) { return false; + } - textArea.replaceSelection( this.textReplace != null ? - this.textReplace : "" ); + textArea.replaceSelection( + this.textReplace != null ? this.textReplace : "" ); return true; } @@ -2388,6 +2567,7 @@ private void setFileBtnsEnabled( boolean state ) this.mnuEditTabToSpaces.setEnabled( state ); this.mnuEditUmlautGerToUni.setEnabled( state ); this.mnuEditUmlautDosToUni.setEnabled( state ); + this.mnuEditRemoveWordstarFmt.setEnabled( state ); this.mnuEditFind.setEnabled( state ); this.mnuEditBracket.setEnabled( state ); this.mnuEditGoto.setEnabled( state ); @@ -2413,6 +2593,32 @@ private void setFindNextBtnsEnabled( boolean state ) } + private void setLineAddrs( Collection prgSources ) + { + for( EditText editText : this.editTexts ) { + Component tabComponent = editText.getTabComponent(); + JTextArea textArea = editText.getJTextArea(); + if( (tabComponent != null) && (textArea != null) ) { + if( tabComponent instanceof JScrollPane ) { + Component rowHeader = null; + if( prgSources != null ) { + for( PrgSource prgSource : prgSources ) { + if( editText.isSameText( prgSource ) ) { + Map lineAddrMap = prgSource.getLineAddrMap(); + if( lineAddrMap != null ) { + rowHeader = new LineAddrRowHeader( textArea, lineAddrMap ); + } + } + } + } + ((JScrollPane) tabComponent).setRowHeaderView( rowHeader ); + } + } + } + updRemoveRowHeaderButtons(); + } + + private void setPasteBtnsEnabled( boolean state ) { this.mnuEditPaste.setEnabled( state ); @@ -2473,10 +2679,6 @@ private void showConvResult( int cnt ) private void showTextNotFound() { - Toolkit tk = getToolkit(); - if( tk != null ) - tk.beep(); - BasicDlg.showInfoDlg( this, "Text nicht gefunden!", @@ -2498,6 +2700,7 @@ private void updFileButtons() setSavePrjBtnsEnabled( false ); setFindNextBtnsEnabled( false ); } + updRemoveRowHeaderButtons(); } @@ -2518,6 +2721,24 @@ private void updPasteButtons() } + private void updRemoveRowHeaderButtons() + { + EditText editText = getSelectedEditText(); + if( editText != null ) { + this.mnuRemoveRowHeader.setEnabled( editText.hasRowHeader() ); + } else { + this.mnuRemoveRowHeader.setEnabled( false ); + } + } + + + private void updShowLineAddrs() + { + if( !this.mnuPrgLineAddrs.isSelected() ) + removeRowHeaders(); + } + + private void updStatusBar() { if( this.labelStatus != null ) { @@ -2572,4 +2793,3 @@ private void updTitle( EditText editText ) setTitle( title ); } } - diff --git a/src/jkcemu/text/TextUtil.java b/src/jkcemu/text/TextUtil.java index 5d83b35..e962fca 100644 --- a/src/jkcemu/text/TextUtil.java +++ b/src/jkcemu/text/TextUtil.java @@ -1,5 +1,5 @@ /* - * (c) 2012 Jens Mueller + * (c) 2012-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -16,15 +16,7 @@ public class TextUtil public static String emptyToNull( String text ) { if( text != null ) { - boolean empty = true; - int len = text.length(); - for( int i = 0; i < len; i++ ) { - if( !Character.isWhitespace( text.charAt( i ) ) ) { - empty = false; - break; - } - } - if( empty ) { + if( text.trim().isEmpty() ) { text = null; } } @@ -67,45 +59,17 @@ public static boolean equalsIgnoreCase( String s1, String s2 ) public static char toISO646DE( char ch ) { - switch( ch ) { - case '\u00A7': // Paragraf-Zeichen - ch = '@'; - break; - - case '\u00C4': // Ae - ch = '['; - break; - - case '\u00D6': // Oe - ch = '\\'; - break; - - case '\u00DC': // Ue - ch = ']'; - break; - - case '\u00E4': // ae - ch = '{'; - break; - - case '\u00F6': // oe - ch = '|'; - break; - - case '\u00FC': // ue - ch = '}'; - break; - - case '\u00DF': // sz - ch = '~'; - break; + if( ch == '\u00A7' ) { // Paragraf-Zeichen + ch = '@'; + } else { + ch = umlautToISO646DE( ch ); } return ch; } /* - * Umwandlung eines Textes in Grossbuchstaben, + * Umwandlung eines Textes in Kleinbuchstaben, * wobei sichergestellt ist, * dass sich die Anzahl der Zeichen nicht aendert. */ @@ -125,6 +89,17 @@ public static String toLowerCase( String text ) } + public static char toReverseCase( char ch ) + { + if( Character.isUpperCase( ch ) ) { + ch = Character.toLowerCase( ch ); + } else if( Character.isLowerCase( ch ) ) { + ch = Character.toUpperCase( ch ); + } + return ch; + } + + public static String toReverseCase( String text ) { if( text != null ) { @@ -132,14 +107,7 @@ public static String toReverseCase( String text ) if( len > 0 ) { char[] buf = new char[ len ]; for( int i = 0; i < len; i++ ) { - char ch = text.charAt( i ); - if( Character.isUpperCase( ch ) ) { - buf[ i ] = Character.toLowerCase( ch ); - } else if( Character.isLowerCase( ch ) ) { - buf[ i ] = Character.toUpperCase( ch ); - } else { - buf[ i ] = ch; - } + buf[ i ] = toReverseCase( text.charAt( i ) ); } text = new String( buf ); } @@ -148,6 +116,41 @@ public static String toReverseCase( String text ) } + public static char umlautToISO646DE( char ch ) + { + switch( ch ) { + case '\u00C4': // Ae + ch = '['; + break; + + case '\u00D6': // Oe + ch = '\\'; + break; + + case '\u00DC': // Ue + ch = ']'; + break; + + case '\u00E4': // ae + ch = '{'; + break; + + case '\u00F6': // oe + ch = '|'; + break; + + case '\u00FC': // ue + ch = '}'; + break; + + case '\u00DF': // sz + ch = '~'; + break; + } + return ch; + } + + /* * Umwandlung eines Textes in Grossbuchstaben, * wobei sichergestellt ist, @@ -169,49 +172,6 @@ public static String toUpperCase( String text ) } - public static String wordStarToPlainText( byte[] fileBytes ) - { - String text = null; - if( fileBytes != null ) { - if( fileBytes.length > 0 ) { - StringBuilder buf = new StringBuilder( fileBytes.length ); - int pos = 0; - boolean bol = true; // Zeilenanfang - boolean ign = false; // Zeile ignorieren - while( pos < fileBytes.length ) { - int b = 0; - if( fileBytes != null ) { - b = (int) fileBytes[ pos ] & 0xFF; - } - else if( text != null ) { - b = text.charAt( pos ); - } - if( bol && (b == '.') ) { - ign = true; - } - b &= 0x7F; - if( (b == '\n') || (b == '\t') || (b >= 0x20) ) { - if( !ign ) { - buf.append( (char) b ); - } - if( b == '\n' ) { - ign = false; - bol = true; - } else { - bol = false; - } - } - pos++; - } - text = buf.toString(); - } else { - text = ""; - } - } - return text; - } - - /* --- privater Konstruktor --- */ private TextUtil() diff --git a/src/jkcemu/tools/Label.java b/src/jkcemu/tools/Label.java index 8ac0e0b..3f48daa 100644 --- a/src/jkcemu/tools/Label.java +++ b/src/jkcemu/tools/Label.java @@ -1,5 +1,5 @@ /* - * (c) 2011 Jens Mueller + * (c) 2011-2016 Jens Mueller * * Kleincomputer-Emulator * @@ -14,6 +14,6 @@ public interface Label extends Comparable