RS232CTerminal.java
     1: //========================================================================================
     2: //  RS232CTerminal.java
     3: //    en:RS-232C settings and terminal
     4: //    ja:RS-232C設定とターミナル
     5: //  Copyright (C) 2003-2025 Makoto Kamada
     6: //
     7: //  This file is part of the XEiJ (X68000 Emulator in Java).
     8: //  You can use, modify and redistribute the XEiJ if the conditions are met.
     9: //  Read the XEiJ License for more details.
    10: //  https://stdkmd.net/xeij/
    11: //========================================================================================
    12: 
    13: package xeij;
    14: 
    15: import java.awt.datatransfer.*;  //DataFlavor
    16: import java.awt.event.*;  //ActionListener
    17: import java.io.*;  //UnsupportedEncodingException
    18: import java.net.*;  //URLEncoder
    19: import java.nio.*;  //ByteBuffer
    20: import java.nio.channels.*;  //AsynchronousServerSocketChannel
    21: import java.nio.charset.*;  //Charset
    22: import java.util.*;  //HashSet
    23: import java.util.zip.*;  //CRC32
    24: import javax.swing.*;
    25: import javax.swing.event.*;  //DocumentListener
    26: 
    27: import com.fazecast.jSerialComm.*;  //SerialPort
    28: 
    29: public class RS232CTerminal {
    30: 
    31:   public static final int TRM_MAX_OUTPUT_LENGTH = 1024 * 256;  //出力の上限を256KBとする
    32:   public static final int TRM_CUT_OUTPUT_LENGTH = TRM_MAX_OUTPUT_LENGTH + 1024 * 16;  //出力が上限よりも16KB以上長くなったら上限でカットする
    33: 
    34:   //コンポーネント
    35:   public static JFrame trmFrame;  //ウインドウ
    36:   public static ScrollTextArea trmBoard;  //テキストエリア
    37:   public static JPopupMenu trmPopupMenu;  //ポップアップメニュー
    38:   public static JMenuItem trmPopupCutMenuItem;  //切り取り
    39:   public static JMenuItem trmPopupCopyMenuItem;  //コピー
    40:   public static JMenuItem trmPopupPasteMenuItem;  //貼り付け
    41:   public static JMenuItem trmPopupSelectAllMenuItem;  //すべて選択
    42:   public static JMenuItem trmPopupSendCtrlCMenuItem;  //^C送信
    43:   public static StringBuilder trmOutputBuilder;  //ターミナルを最初に開くまでに出力された文字列を貯めておくバッファ
    44:   public static int trmOutputEnd;  //出力された文字列の末尾。リターンキーが押されたらこれ以降に書かれた文字列をまとめて入力する
    45:   public static int trmOutputSJIS1;  //出力するときに繰り越したSJISの1バイト目
    46: 
    47:   //SerialPort
    48:   public static SerialPort[] trmPortArray;  //[row-1]=SerialPort。SerialPortの配列
    49:   public static int trmNumberOfPorts;  //SerialPortの数
    50:   //行
    51:   //  Terminal,SerialPort,SerialPort,…,TCP/IP
    52:   public static int trmRows;  //行数。1+trmNumberOfPorts+1
    53:   public static String[] trmRowName;  //行の名前
    54:   public static int[] trmRowToCol;  //行に接続している列。なければ-1
    55:   //AUX*
    56:   public static int trmNumberOfAUXs;  //AUX*の数
    57:   //列
    58:   //  Terminal,AUX,AUX2,…
    59:   public static int trmCols;  //列数。1+trmNumberOfAUXs
    60:   public static String[] trmColName;  //列の名前
    61:   public static int[] trmColToRow;  //列に接続している行。なければ-1
    62: 
    63: 
    64: 
    65:   //接続
    66:   //  Terminalから送信するとき。TerminalでENTERキーが押されてそれまでに入力/貼り付けされた文字列を送信するとき
    67:   //    KeyListenerでENTERキーが押されたとき
    68:   //      それまでに入力/貼り付けされた文字列を取り出す(trmEnter)
    69:   //        SJISに変換してキューに書き込む(trmSendString)
    70:   //  Terminalが受信するとき。入力/貼り付け以外でTerminalに文字列を表示するとき
    71:   //    スレッドでキューをポーリングする
    72:   //      取り出してSJIS逆変換してテキストエリアへ書き込む
    73:   //      テキストエリアが大きくなりすぎたときは先頭を削って短くする
    74:   //  AUXから送信するとき。SCCのデータレジスタへライトされたとき
    75:   //    キューに書き込むだけ
    76:   //    OUT232CはTx Buffer Emptyが1になるのを待ってからデータポートへライトするが、
    77:   //    Tx Buffer Emptyが1になるのを待たずにライトしても送信される
    78:   //    Tx Buffer Emptyの動作についてはtrmAUXSendTickerを参照
    79:   //  AUXが受信するとき。SCCのデータレジスタからリードされたとき
    80:   //    レジスタのデータを返すだけ
    81:   //    ポーリング間隔またはボーレートなどから決めた受信間隔でティッカーを繰り返す
    82:   //    (受信間隔は短くてもよいがデバッガの誤動作をどうにかしなければならない)
    83:   //      キューが空でなくReset Highest IUSコマンド(WR0=$38)によりIUS(Interrupt Under Service)がリセットされているとき
    84:   //        キューからデータを取り出してレジスタを更新して割り込みを要求する
    85:   //  SerialPortから送信するとき。jSerialCommが受信するとき
    86:   //    SerialPortDataListenerでSerialPortからreadBytesしてキューへ書き込む
    87:   //    キューが満杯になることは事実上ないのでreadBytesが長く滞ることはないはず
    88:   //  SerialPortが受信するとき。jSerialCommが送信するとき
    89:   //    スレッドでキューをポーリングする
    90:   //      データがあれば取り出してwriteBytesでjSerialCommに渡す
    91:   //      writeBytesがブロックするとポーリングが止まる
    92:   static class Connection implements SerialPortDataListener {
    93:     int row;  //行。Terminal,SerialPort,SerialPort,…
    94:     int col;  //列。Terminal,AUX,AUX2,…。row==0&&col==0は不可
    95:     int index;  //trmConnectionArrayとtrmConnectionBoxでのインデックス。trmCols*row+col-1
    96:     String text;  //checkBoxのtext兼actionCommand
    97:     boolean connected;  //接続している
    98:     JCheckBox checkBox;  //接続チェックボックス
    99:     Box box;  //checkBoxを入れる箱
   100: 
   101:     //キュー
   102:     ByteQueue row2colQueue;  //Terminal/SerialPort→Terminal/AUX
   103:     ByteQueue col2rowQueue;  //Terminal/AUX→Terminal/SerialPort
   104:     boolean row2colReset;  //true=row2colQueueをクリアする
   105:     boolean col2rowReset;  //true=col2rowQueueをクリアする
   106: 
   107:     //シリアルポートデータリスナー。row2col。SerialPort→?
   108:     @Override public int getListeningEvents () {
   109:       return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
   110:     }
   111:     @Override public void serialEvent (SerialPortEvent spe) {
   112:       if (spe.getEventType () != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
   113:         return;
   114:       }
   115:       SerialPort port = spe.getSerialPort ();
   116:       for (;;) {
   117:         int k = Math.min (port.bytesAvailable (),  //ポートから読み出せるバイト数と
   118:                           row2colQueue.unused ());  //キューへ書き込めるバイト数の小さい方
   119:         if (k == 0) {
   120:           break;
   121:         }
   122:         byte[] b = new byte[k];
   123:         port.readBytes (b, k);  //ポートから読み出す
   124:         row2colQueue.write (b, 0, k);  //キューへ書き込む
   125:       }
   126:     }
   127: 
   128:     //ポーリングスレッド
   129:     boolean polling;  //false=ポーリング終了
   130:     Thread row2colThread;  //Terminal/SerialPort→Terminal/AUX。TerminalThread
   131:     Thread col2rowThread;  //Terminal/AUX→Terminal/SerialPort。TerminalThreadまたはSerialPortThread
   132: 
   133:     //  ?→Terminalポーリングスレッド
   134:     class TerminalThread extends Thread {
   135:       @Override public void run () {
   136:         ByteQueue queue = (col == 0 ? row2colQueue : col2rowQueue);
   137:         while (polling) {
   138:           if (col == 0 ? row2colReset : col2rowReset) {
   139:             if (col == 0) {
   140:               row2colReset = false;
   141:             } else {
   142:               col2rowReset = false;
   143:             }
   144:             queue.clear ();
   145:           } else {
   146:             for (int k = queue.used (); k != 0; k = queue.used ()) {  //キューが空になるまでスリープしない
   147:               byte[] b = new byte[k];
   148:               queue.read (b, 0, k);  //キューから読み出して
   149:               for (int i = 0; i < k; i++) {
   150:                 trmPrintSJIS (b[i] & 0xff);  //ターミナルへ書き込む
   151:               }
   152:             }
   153:           }
   154:           try {
   155:             Thread.sleep (100);
   156:           } catch (InterruptedException ie) {
   157:           }
   158:         }  //while polling
   159:       }  //run
   160:     }  //class TerminalThread
   161: 
   162:     //  ?→SerialPortポーリングスレッド
   163:     class SerialPortThread extends Thread {
   164:       @Override public void run () {
   165:         SerialPort port = trmPortArray[row - 1];
   166:         ByteQueue queue = col2rowQueue;
   167:         while (polling) {
   168:           if (col2rowReset) {
   169:             col2rowReset = false;
   170:             queue.clear ();
   171:             port.flushIOBuffers ();
   172:           } else {
   173:             for (int k = queue.used (); k != 0; k = queue.used ()) {  //キューが空になるまでスリープしない
   174:               byte[] b = new byte[k];
   175:               queue.read (b, 0, k);  //キューから読み出して
   176:               port.writeBytes (b, k);  //シリアルポートへ書き込む。ブロックすることがある
   177:             }
   178:           }
   179:           try {
   180:             Thread.sleep (10);
   181:           } catch (InterruptedException ie) {
   182:           }
   183:         }  //while polling
   184:       }  //run
   185:     }  //class SerialPortThread
   186: 
   187:     //  ?→TCP/IPポーリングスレッド
   188:     class TCPIPThread extends Thread {
   189:       @Override public void run () {
   190:         ByteQueue queue = col2rowQueue;
   191:         while (polling) {
   192:           if (col2rowReset) {
   193:             col2rowReset = false;
   194:             queue.clear ();
   195:           } else {
   196:             for (int k = queue.used (); k != 0; k = queue.used ()) {  //キューが空になるまでスリープしない
   197:               byte[] b = new byte[k];
   198:               queue.read (b, 0, k);  //キューから読み出して
   199:               trmTcpipWrite (b, 0, k);  //TCP/IPポートへ書き込む。ブロックすることがある
   200:             }
   201:           }
   202:           try {
   203:             Thread.sleep (10);
   204:           } catch (InterruptedException ie) {
   205:           }
   206:         }  //while polling
   207:       }  //run
   208:     }  //class SerialPortThread
   209: 
   210:   }  //class Connection
   211: 
   212: 
   213: 
   214:   //TCP/IPポート
   215:   //  参考
   216:   //    https://x.com/yunkya2/status/1940779786017951890
   217:   //    https://github.com/yunkya2/xeij/commit/cba358a58197a05b25b64a4ceaa90d110a564aec
   218:   public static final String TRM_TCPIP_DEFAULT_HOST = "0.0.0.0";  //デフォルトのホスト
   219:   public static final int TRM_TCPIP_DEFAULT_PORT = 54321;  //デフォルトのポート番号
   220:   public static String trmTcpipHost;  //次に開くホスト
   221:   public static int trmTcpipPort;  //次に開くポート番号
   222:   public static boolean trmTcpipUTF8;  //SJIS⇔UTF-8変換
   223:   public static DecimalSpinner trmTcpipSpinner;  //ポート番号スピナー
   224:   public static JCheckBox trmTcpipCheckBox;  //UTF-8チェックボックス
   225:   public static ByteBuffer trmTcpipReadBuffer;  //受信バッファ
   226:   public static CompletionHandler<Integer, Void> trmTcpipReadHander;  //受信ハンドラ
   227:   public static CompletionHandler<AsynchronousSocketChannel, Void> trmTcpipAcceptHandler;  //接続ハンドラ
   228:   public static ByteQueue trmTcpipReadQueue;  //受信キュー
   229:   public static SocketAddress trmTcpipPortAddress;  //ポートアドレス
   230:   public static AsynchronousServerSocketChannel trmTcpipPortChannel;  //ポートチャネル
   231:   public static final int TRM_TCPIP_BUFFER_SIZE = 4096;  //受信バッファのサイズ
   232:   public static AsynchronousSocketChannel trmTcpipSessionChannel;  //セッションチャネル
   233:   public static SocketAddress trmTcpipClientAddress;  //クライアントアドレス
   234:   public static int trmTcpipToUTF8Pool;  //SJIS→UTF-8変換プール
   235:   public static int trmTcpipToSJISPool;  //UTF-8→SJIS変換プール
   236: 
   237:   //trmTcpipInit ()
   238:   //  初期化。ここでは開かない
   239:   public static void trmTcpipInit () {
   240:     //パラメータを復元する
   241:     trmTcpipHost = Settings.sgsGetString ("tcpiphost");
   242:     if (trmTcpipHost.equals ("")) {
   243:       trmTcpipHost = TRM_TCPIP_DEFAULT_HOST;
   244:     }
   245:     trmTcpipPort = Settings.sgsGetInt ("tcpipport", TRM_TCPIP_DEFAULT_PORT);
   246:     if (trmTcpipPort < 0 || 65535 < trmTcpipPort) {
   247:       trmTcpipPort = TRM_TCPIP_DEFAULT_PORT;
   248:     }
   249:     trmTcpipUTF8 = Settings.sgsGetOnOff ("tcpiputf8");
   250:     //ポート番号スピナーを作る
   251:     trmTcpipSpinner = ComponentFactory.createDecimalSpinner (
   252:       trmTcpipPort, 0, 65535, 1, 0,
   253:       new ChangeListener () {
   254:         @Override public void stateChanged (ChangeEvent ce) {
   255:           trmTcpipPort = trmTcpipSpinner.getIntValue ();
   256:         }
   257:       });
   258:     //UTF-8チェックボックスを作る
   259:     trmTcpipCheckBox = ComponentFactory.createCheckBox (
   260:       trmTcpipUTF8, "UTF-8",
   261:       new ActionListener () {
   262:         @Override public void actionPerformed (ActionEvent ae) {
   263:           trmTcpipUTF8 = trmTcpipCheckBox.isSelected ();
   264:         }
   265:       });
   266:     //受信バッファを作る
   267:     trmTcpipReadBuffer = ByteBuffer.allocate (TRM_TCPIP_BUFFER_SIZE);
   268:     //受信ハンドラを作る
   269:     trmTcpipReadHander = new CompletionHandler<Integer, Void> () {
   270:       //  受信成功
   271:       @Override public void completed (Integer bytesRead, Void a) {
   272:         int l = (int) bytesRead;  //受信したバイト数
   273:         if (l == -1) {  //切断されたとき
   274:           trmTcpipCloseSession (true);  //セッションを閉じて次の接続を待つ
   275:           return;
   276:         }
   277:         byte[] b = new byte[l];
   278:         int o = 0;
   279:         trmTcpipReadBuffer.flip ();
   280:         trmTcpipReadBuffer.get (b, o, l);  //ポートから読み出す
   281:         if (false) {
   282:           for (int i = 0; i < l; i++) {
   283:             System.out.printf ("%02x ", 0xff & b[o + i]);
   284:           }
   285:           System.out.println ();
   286:         }
   287:         byte[] bb = b;
   288:         int ll = l;
   289:         if (trmTcpipUTF8) {
   290:           //UTF-8→SJIS
   291:           int pool = trmTcpipToSJISPool;
   292:           bb = new byte[2 * l];
   293:           ll = 0;
   294:           for (int i = 0; i < l; i++) {
   295:             int u = (pool << 8) | (0xff & b[o + i]);
   296:             if ((u & 0xffffffe0) == 0x000000c0 ||  //2バイトコードの1バイト目
   297:                 (u & 0xfffffff0) == 0x000000e0 ||  //3バイトコードの1バイト目
   298:                 (u & 0xfffff0c0) == 0x0000e080 ||  //3バイトコードの2バイト目
   299:                 (u & 0xfffffff8) == 0x000000f0 ||  //4バイトコードの1バイト目
   300:                 (u & 0xfffff8c0) == 0x0000f080 ||  //4バイトコードの2バイト目
   301:                 (u & 0xfff8c0c0) == 0x00f08080) {  //4バイトコードの3バイト目
   302:               pool = u;
   303:               continue;
   304:             }
   305:             pool = 0;
   306:             if ((u & 0xffffff80) == 0x00000000) {  //1バイトコード 7bit
   307:             } else if ((u & 0xffffe0c0) == 0x0000c080) {  //2バイトコード
   308:               u = ((u & 0x00001f00) >> 2) | (u & 0x0000003f);  //5+6=11bit
   309:             } else if ((u & 0xfff0c0c0) == 0x00e08080) {  //3バイトコード
   310:               u = ((u & 0x000f0000) >> 4) | ((u & 0x00003f00) >> 2) | (u & 0x0000003f);  //4+6+6=16bit
   311:             } else if ((u & 0xf8c0c0c0) == 0xf0808080) {  //4バイトコード
   312:               u = ((u & 0x07000000) >> 6) | ((u & 0x003f0000) >> 4) | ((u & 0x00003f00) >> 2) | (u & 0x0000003f);  //3+6+6+6=21bit
   313:               if (0x10ffff < u) {  //不正なコード
   314:                 u = '※';
   315:               } else if (0x00ffff < u) {  //サロゲートペア
   316:                 //u -= 0x10000;
   317:                 //int v = u & 0x3ff;
   318:                 //u >>= 10;
   319:                 //u += 0xd800;
   320:                 //v += 0xdc00;
   321:                 u = '※';
   322:               }
   323:             } else {  //不正なコード
   324:               u = '※';
   325:             }
   326:             int s = CharacterCode.chrCharToSJIS[u];  //UTF-16(サロゲートペアなし)→SJIS変換
   327:             if (s == 0 && u != 0) {  //0でないコードが0になった
   328:               s = 0x81a6;  //対応なし'※'
   329:             }
   330:             if (0x00ff < s) {
   331:               bb[ll++] = (byte) (s >> 8);
   332:             }
   333:             bb[ll++] = (byte) s;
   334:           }  //for i
   335:           trmTcpipToSJISPool = pool;
   336:         }
   337:         trmTcpipReadQueue.write (bb, 0, ll);  //キューへ書き込む。キューへ書き込めるバイト数の確認は省略
   338:         //次の受信を開始する
   339:         if (trmTcpipSessionChannel != null) {
   340:           trmTcpipReadBuffer.clear ();
   341:           trmTcpipSessionChannel.read (trmTcpipReadBuffer, null, trmTcpipReadHander);
   342:         }
   343:       }
   344:       //  受信失敗
   345:       @Override public void failed (Throwable t, Void a) {
   346:         String message = t.getMessage ();
   347:         if (message != null) {
   348:           System.err.println (message);
   349:         }
   350:         trmTcpipCloseSession (true);  //セッションを閉じて次の接続を待つ
   351:       }
   352:     };
   353:     //接続ハンドラを作る
   354:     trmTcpipAcceptHandler = new CompletionHandler<AsynchronousSocketChannel, Void> () {
   355:       //  接続成功
   356:       @Override public void completed (AsynchronousSocketChannel sessionChannel, Void a) {
   357:         trmTcpipSessionChannel = sessionChannel;
   358:         //セッションを開く
   359:         trmTcpipToUTF8Pool = 0;
   360:         trmTcpipToSJISPool = 0;
   361:         try {
   362:           trmTcpipClientAddress = trmTcpipSessionChannel.getRemoteAddress ();
   363:         } catch (IOException ioe) {
   364:           ioe.printStackTrace ();
   365:         }
   366:         System.out.println (Multilingual.mlnJapanese ?
   367:                             trmTcpipClientAddress + " と接続しました" :
   368:                             trmTcpipClientAddress + " connected");
   369:         //受信を開始する
   370:         if (trmTcpipSessionChannel != null) {
   371:           trmTcpipReadBuffer.clear ();
   372:           trmTcpipSessionChannel.read (trmTcpipReadBuffer, null, trmTcpipReadHander);
   373:         }
   374:       }
   375:       //  接続失敗
   376:       @Override public void failed (Throwable t, Void a) {
   377:         String message = t.getMessage ();
   378:         if (message != null) {
   379:           System.err.println (message);
   380:         }
   381:         trmTcpipClosePort ();
   382:       }
   383:     };
   384:   }  //trmTcpipInit
   385: 
   386:   //trmTcpipTini ()
   387:   //  後始末。ここでは閉じない
   388:   public static void trmTcpipTini () {
   389:     //パラメータを保存する
   390:     Settings.sgsPutString ("tcpiphost", trmTcpipHost);
   391:     Settings.sgsPutInt ("tcpipport", trmTcpipPort);
   392:     Settings.sgsPutOnOff ("tcpiputf8", trmTcpipUTF8);
   393:   }  //trmTcpipTini
   394: 
   395:   //success = trmTcpipOpenPort (readQueue)
   396:   //  ポートを開く
   397:   //  readQueue  受信キュー
   398:   public static boolean trmTcpipOpenPort (ByteQueue readQueue) {
   399:     trmTcpipReadQueue = readQueue;
   400:     trmTcpipPortAddress = new InetSocketAddress (trmTcpipHost, trmTcpipPort);
   401:     //ポートを開く
   402:     try {
   403:       trmTcpipPortChannel = AsynchronousServerSocketChannel.open ().bind (trmTcpipPortAddress);
   404:     } catch (IOException ioe) {
   405:       ioe.printStackTrace ();
   406:       System.out.println (Multilingual.mlnJapanese ?
   407:                           trmTcpipPortAddress + " を開けません" :
   408:                           trmTcpipPortAddress + " not opened");
   409:       return false;
   410:     }
   411:     System.out.println (Multilingual.mlnJapanese ?
   412:                         trmTcpipPortAddress + " を開きました" :
   413:                         trmTcpipPortAddress + " opened");
   414:     //接続を待つ
   415:     if (trmTcpipPortChannel != null) {
   416:       trmTcpipPortChannel.accept (null, trmTcpipAcceptHandler);
   417:     }
   418:     return true;
   419:   }  //trmTcpipOpenPort
   420: 
   421:   //trmTcpipClosePort ()
   422:   //  ポートを閉じる
   423:   public static void trmTcpipClosePort () {
   424:     if (trmTcpipPortAddress != null) {
   425:       System.out.println (Multilingual.mlnJapanese ?
   426:                           trmTcpipPortAddress + " を閉じました" :
   427:                           trmTcpipPortAddress + " closed");
   428:       trmTcpipPortAddress = null;
   429:     }
   430:     //ポートを閉じる
   431:     if (trmTcpipPortChannel != null &&
   432:         trmTcpipPortChannel.isOpen ()) {
   433:       try {
   434:         trmTcpipPortChannel.close ();
   435:       } catch (IOException ioe) {
   436:         ioe.printStackTrace ();
   437:       }
   438:     }
   439:     trmTcpipPortChannel = null;
   440:     //セッションを閉じる
   441:     trmTcpipCloseSession (false);  //セッションを閉じる。次の接続を待たない
   442:   }  //trmTcpipClosePort
   443: 
   444:   //trmTcpipWrite (b, o, l)
   445:   //  送信する
   446:   //  b  配列
   447:   //  o  オフセット
   448:   //  l  長さ
   449:   public static void trmTcpipWrite (byte[] b, int o, int l) {
   450:     if (trmTcpipSessionChannel != null) {
   451:       byte[] bb = b;
   452:       int ll = l;
   453:       if (trmTcpipUTF8) {
   454:         //SJIS→UTF-8
   455:         int pool = trmTcpipToUTF8Pool;
   456:         bb = new byte[3 * l];
   457:         ll = 0;
   458:         for (int i = 0; i < l; i++) {
   459:           int s = (pool << 8) | (0xff & b[o + i]);
   460:           if ((0x0080 <= s && s <= 0x009f) || (0x00e0 <= s && s <= 0x00ff)) {  //2バイトコードの1バイト目
   461:             pool = s;
   462:             continue;
   463:           }
   464:           pool = 0;
   465:           int u = CharacterCode.chrSJISToChar[s];  //SJIS→UTF-16(サロゲートペアなし)変換
   466:           if (u == 0 && s != 0) {  //0でないコードが0になった
   467:             u = '\ufffd';  //対応なし
   468:           }
   469:           if (u <= 0x007f) {
   470:             bb[ll++] = (byte) u;
   471:           } else if (u <= 0x07ff) {
   472:             bb[ll++] = (byte) (0xc0 | (u >> 6));
   473:             bb[ll++] = (byte) (0x80 | (u & 0x3f));
   474:           } else {
   475:             bb[ll++] = (byte) (0xe0 | (u >> 12));
   476:             bb[ll++] = (byte) (0x80 | ((u >> 6) & 0x3f));
   477:             bb[ll++] = (byte) (0x80 | (u & 0x3f));
   478:           }
   479:         }  //for i
   480:         trmTcpipToUTF8Pool = pool;
   481:       }
   482:       ByteBuffer buffer = ByteBuffer.allocate (ll);
   483:       buffer.put (bb, 0, ll);
   484:       buffer.flip ();
   485:       try {
   486:         trmTcpipSessionChannel.write (buffer).get ();
   487:       } catch (Throwable t) {
   488:         String message = t.getMessage ();
   489:         if (message != null) {
   490:           System.err.println (message);
   491:         }
   492:         trmTcpipCloseSession (true);  //セッションを閉じて次の接続を待つ
   493:       }
   494:     }
   495:   }  //trmTcpipWrite
   496: 
   497:   //trmTcpipCloseSession (reconnect)
   498:   //  セッションを閉じる
   499:   //  reconnect  次の接続を待つか
   500:   public static void trmTcpipCloseSession (boolean reconnect) {
   501:     if (trmTcpipSessionChannel == null) {
   502:       return;
   503:     }
   504:     if (trmTcpipClientAddress != null) {
   505:       System.out.println (Multilingual.mlnJapanese ?
   506:                           trmTcpipClientAddress + " との接続を閉じました" :
   507:                           trmTcpipClientAddress + " disconnected");
   508:       trmTcpipClientAddress = null;
   509:     }
   510:     //セッションを閉じる
   511:     try {
   512:       trmTcpipSessionChannel.close ();
   513:     } catch (IOException ioe) {
   514:       ioe.printStackTrace ();
   515:     }
   516:     trmTcpipSessionChannel = null;
   517:     if (reconnect) {
   518:       //次の接続を待つ
   519:       if (trmTcpipPortChannel != null) {
   520:         trmTcpipPortChannel.accept (null, trmTcpipAcceptHandler);
   521:       }
   522:     }
   523:   }  //trmTcpipCloseSession
   524: 
   525: 
   526: 
   527:   //AUXフロー制御
   528:   //  X68000のRS-232Cのフロー制御は通信ドライバが処理している
   529:   //  プログラムが連続的に動作する実機と違い、間欠的に動作するエミュレータではフロー制御が適切なタイミングで行われない
   530:   //  SerialPort側のフロー制御をjSerialCommに任せ、通信ドライバ側のフロー制御をSCCで完結させることで、取りこぼしを防ぐ
   531:   //  フロー制御がXONでバッファの3/4が埋まって通信ドライバがXOFF$13を送信したとき
   532:   //  フロー制御がRTSでバッファの3/4が埋まって通信ドライバがWR5 bit1 0=/RTS is Highにしたとき
   533:   //    直ちにSCCの受信を停止する。瞬時に止まるのでどんなに速く通信していても通信ドライバの受信バッファは溢れない
   534:   //  フロー制御がXONでバッファの3/4が空いて通信ドライバがXON$11を送信したとき
   535:   //  フロー制御がRTSでバッファの3/4が空いて通信ドライバがWR5 bit1 1=/RTS is Lowにしたとき
   536:   //    直ちにSCCの受信を再開する。
   537:   //!!! 反応が速すぎると誤動作するドライバがあるかも?
   538:   //  フロー制御の設定はどこにも接続されていないときも有効でなければならない
   539:   public static boolean trmAUXFlowControlRTS;  //true=AUXのフロー制御はRTS。接続するときに使うので接続していなくても有効
   540:   public static boolean trmAUXFlowControlXON;  //true=AUXのフロー制御はXON。接続するときに使うので接続していなくても有効
   541:   public static boolean trmAUXNotReceiving;  //false=受信可($11=XONまたは1=/RTS is Low),true=受信不可($13=XOFFまたは0=/RTS is High)
   542: 
   543:   //AUX受信データバッファ
   544:   public static int trmAUXDataBuffer;  //0xffでマスクしておくこと
   545:   public static boolean trmAUXDataAvailable;  //ReadCommandのbit0:Rx Character Available。DataBufferの更新でセット、ReadDataでクリア
   546: 
   547:   //data = trmAUXReadData ()
   548:   //  AUXリードデータ
   549:   public static int trmAUXReadData () {
   550:     trmAUXDataAvailable = false;
   551:     return trmAUXDataBuffer;
   552:   }  //trmAUXReadData
   553: 
   554:   //trmAUXWriteData (data)
   555:   //  AUXライトデータ
   556:   public static void trmAUXWriteData (int data) {
   557:     int col = 1;
   558:     int row = trmColToRow[col];
   559:     if (row < 0) {
   560:       return;
   561:     }
   562:     data &= 0xff;  //!!! ビット長
   563:     if (trmAUXFlowControlXON) {
   564:       if (data == 0x11) {  //$11=XON
   565:         trmAUXNotReceiving = false;  //受信可
   566:         return;
   567:       } else if (data == 0x13) {  //$13=XOFF
   568:         trmAUXNotReceiving = true;  //受信不可
   569:         return;
   570:       }
   571:     }
   572:     trmConnectionArray[trmCols * row + col - 1].col2rowQueue.write (data);  //AUX→Terminal/SerialPort
   573:     //AUX送信割り込み
   574:     trmAUXSendEmpty = false;  //送信バッファ空フラグをクリア
   575:     TickerQueue.tkqAdd (trmAUXSendTicker, XEiJ.mpuClockTime + Z8530.scc1aInterval);  //現在時刻+間隔の時刻に送信ティッカーを設定
   576:   }  //trmAUXWriteData
   577: 
   578:   //trmAUXSetNotReceiving (notReceiving)
   579:   //  notReceiving  false  WR5 bit1 1=/RTS is Low   受信可
   580:   //                true   WR5 bit1 0=/RTS is High  受信不可
   581:   public static void trmAUXSetNotReceiving (boolean notReceiving) {
   582:     trmAUXNotReceiving = notReceiving;
   583:   }  //trmAUXSetNotReceiving
   584: 
   585:   //command = trmAUXReadCommand ()
   586:   //  リードコマンド
   587:   //  bit5  CTS 送信許可
   588:   //  bit3  DCD キャリア検出
   589:   //  bit2  Tx Buffer Empty
   590:   //  bit0  Rx Character Available
   591:   public static int trmAUXReadCommand () {
   592:     return (trmAUXConnection == null ?
   593:             (0 << 5 |
   594:              0 << 3 |
   595:              0 << 2 |
   596:              0 << 0) :
   597:             (1 << 5 |  //CTS。キューは容量無制限なので常に1
   598:              1 << 3 |  //DCD。常に1
   599:              (trmAUXSendEmpty ? 1 << 2 : 0 << 2) |  //Tx Buffer Empty
   600:              (trmAUXDataAvailable ? 1 << 0 : 0 << 0)));  //Rx Character Available
   601:   }  //trmAUXReadCommand
   602: 
   603:   //AUX送信ティッカー
   604:   //  送信
   605:   //    データをキューに追加
   606:   //    送信ティッカーを消去
   607:   //    送信バッファ空フラグをクリア
   608:   //    現在時刻+間隔の時刻に送信ティッカーを設定
   609:   //  送信ティッカー
   610:   //    送信バッファ空フラグをセット
   611:   //    送信割り込みが許可されているとき
   612:   //      送信割り込み発生
   613:   public static boolean trmAUXSendEmpty;  //true=送信バッファ空
   614:   public static final TickerQueue.Ticker trmAUXSendTicker = new TickerQueue.Ticker () {
   615:     @Override protected void tick () {
   616:       trmAUXSendEmpty = true;  //送信バッファ空フラグをセット
   617:       if (Z8530.scc1aSendMask != 0) {  //送信割り込みが許可されているとき
   618:         Z8530.scc1aSendRR3 = Z8530.SCC_1A_SEND_MASK;  //送信割り込み発生
   619:         Z8530.scc1aSendRequest = Z8530.SCC_1A_SEND_MASK;
   620:         XEiJ.mpuIRR |= XEiJ.MPU_SCC_INTERRUPT_MASK;
   621:       }
   622:     }  //tick
   623:   };  //trmAUXSendTicker
   624: 
   625:   //AUXポーリングティッカー
   626:   public static Connection trmAUXConnection;  //?→AUXの接続。null=なし
   627:   public static final TickerQueue.Ticker trmAUXTicker = new TickerQueue.Ticker () {
   628:     @Override protected void tick () {
   629:       long interval = XEiJ.TMR_FREQ / 1000 * 1;  //1ms
   630:       ByteQueue queue = trmAUXConnection.row2colQueue;  //?→AUX キュー
   631:       if (trmAUXConnection.row2colReset) {  //?→AUX リセット
   632:         trmAUXConnection.row2colReset = false;
   633:         queue.clear ();
   634:       } else if (!trmAUXNotReceiving &&  //受信可で
   635:                  queue.used () != 0) {  //キューが空でないとき
   636:         trmAUXDataBuffer = queue.read ();  //キューから読み出してDataBufferへ書き込む
   637:         trmAUXDataAvailable = true;  //DataBufferは有効
   638:         if (Z8530.scc1aReceiveMask != 0 &&  //割り込みが許可されていて
   639:             Z8530.scc1aReceiveRR3 == 0) {  //割り込み発生からIUSリセットまでではないとき
   640:           Z8530.scc1aReceiveRR3 = Z8530.SCC_1A_RECEIVE_MASK;  //割り込み発生
   641:           Z8530.scc1aReceiveRequest = Z8530.SCC_1A_RECEIVE_MASK;
   642:           XEiJ.mpuIRR |= XEiJ.MPU_SCC_INTERRUPT_MASK;
   643:         }
   644:         interval = Z8530.scc1aInterval;
   645:       }
   646:       TickerQueue.tkqAdd (trmAUXTicker, XEiJ.mpuClockTime + interval);
   647:     }  //tick
   648:   };  //trmAUXTicker
   649: 
   650:   public static int trmRSDRV202Head;  //RSDRV.SYS 2.02の先頭アドレス
   651:   public static int trmTMSIO031Head;  //tmsio.x 0.31の先頭アドレス
   652:   public static int trmBSIO021Head;  //bsio.x 0.21の先頭アドレス
   653: 
   654:   //trmAUXFlowControlTicker
   655:   //  フロー制御をSerialPortに反映させる
   656:   //  ボーレートジェネレータが動き始めるとき通信設定をSerialPortに反映させるが、
   657:   //  その時点でワークエリアが更新されていないためフロー制御を反映させることができない
   658:   //  フロー制御だけ少し遅れて反映させることにする
   659:   //  遅らせすぎると最初のデータに間に合わない可能性がある
   660:   //  バッファが一杯になるまではフロー制御は行われないと仮定すれば間に合わなくても問題ないかも知れない
   661:   public static final TickerQueue.Ticker trmAUXFlowControlTicker = new TickerQueue.Ticker () {
   662:     @Override protected void tick () {
   663:       int set232c = MC68060.mmuPeekLongData (0x04c0, 1);  //IOCS _SET232Cベクタ
   664:       int modeAddress = (0x00fc0000 <= set232c && set232c < 0x01000000 ? 0x0926 :  //IPLROM
   665:                          set232c == trmRSDRV202Head + 0x03ba ? trmRSDRV202Head + 0x0ab2 :  //RSDRV.SYS 2.02
   666:                          set232c == trmTMSIO031Head + 0x0210 ? trmTMSIO031Head + 0x0a42 :  //tmsio.x 0.31
   667:                          set232c == trmBSIO021Head + 0x013A ? trmBSIO021Head + 0x074a :  //bsio.x 0.21
   668:                          0);  //不明
   669:       if (modeAddress == 0) {
   670:         return;
   671:       }
   672:       int mode = MC68060.mmuPeekWordZeroData (modeAddress, 1);  //通信設定
   673:       if (mode == 0x0000 || mode == 0xffff) {
   674:         return;
   675:       }
   676:       boolean rts = (mode & 0x0080) != 0;
   677:       boolean xon = !rts && (mode & 0x0200) != 0;
   678:       if (trmAUXFlowControlRTS == rts &&
   679:           trmAUXFlowControlXON == xon) {
   680:         return;
   681:       }
   682:       trmAUXFlowControlRTS = rts;
   683:       trmAUXFlowControlXON = xon;
   684:       if (false) {
   685:         System.out.printf ("flowcontrol=%s\n", rts ? "rts" : xon ? "xon" : "none");
   686:       }
   687:       int row = trmColToRow[1];
   688:       SerialPort port = row < 1 || trmNumberOfPorts < row ? null : trmPortArray[row - 1];
   689:       if (port != null) {
   690:         port.setFlowControl (rts ? (SerialPort.FLOW_CONTROL_RTS_ENABLED |
   691:                                     SerialPort.FLOW_CONTROL_CTS_ENABLED) :
   692:                              xon ? (SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED |
   693:                                     SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED) :
   694:                              SerialPort.FLOW_CONTROL_DISABLED);
   695:       }
   696:     }  //tick
   697:   };  //trmAUXFlowControlTicker
   698: 
   699:   //trmAUXReset ()
   700:   //  AUXリセット
   701:   public static void trmAUXReset () {
   702:     if (trmAUXConnection != null) {  //接続しているとき
   703:       TickerQueue.tkqRemove (trmAUXFlowControlTicker);
   704:       //キューをクリアする
   705:       trmAUXConnection.row2colReset = true;
   706:       trmAUXConnection.col2rowReset = true;
   707:     }
   708:   } //trmAUXReset
   709: 
   710: 
   711:   public static Connection[] trmConnectionArray;  //Connectionの配列
   712:   public static Box trmConnectionBox;  //Connectionのboxを入れる箱
   713:   public static ActionListener trmConnectionListener;  //ConnectionのcheckBoxのactionListener
   714:   public static boolean trmRefreshEnabled;  //更新ボタンは有効か
   715:   public static JButton trmRefreshButton;  //更新ボタン
   716:   //通信設定
   717:   public static boolean trmSettingsEnabled;  //通信設定は有効か
   718:   public static String[] trmBaudRateArray;  //ボーレートの選択肢
   719:   public static int trmBaudRateIndex;  //ボーレートのインデックス
   720:   public static String[] trmDataBitsArray;  //データビットの選択肢
   721:   public static int trmDataBitsIndex;  //データビットのインデックス
   722:   public static String[] trmParityArray;  //パリティの選択肢
   723:   public static int trmParityIndex;  //パリティのインデックス
   724:   public static String[] trmStopBitsArray;  //ストップビットの選択肢
   725:   public static int trmStopBitsIndex;  //ストップビットのインデックス
   726:   public static String[] trmFlowControlArray;  //フロー制御の選択肢
   727:   public static int trmFlowControlIndex;  //フロー制御のインデックス
   728:   public static JComboBox<String> trmBaudRateComboBox;  //ボーレート
   729:   public static JComboBox<String> trmDataBitsComboBox;  //データビット
   730:   public static JComboBox<String> trmParityComboBox;  //パリティ
   731:   public static JComboBox<String> trmStopBitsComboBox;  //ストップビット
   732:   public static JComboBox<String> trmFlowControlComboBox;  //フロー制御
   733:   //ファイル転送
   734:   public static boolean trmSendEnabled;  //送信ボタンは有効か
   735:   public static JButton trmSendButton;  //送信ボタン
   736:   public static JFileChooser trmSendFileChooser;  //送信ダイアログのファイルチューザー
   737:   public static JDialog trmSendDialog;  //送信ダイアログ
   738:   public static SendThread trmSendThread;  //送信スレッド
   739:   //追加ポート
   740:   public static JTextField trmAdditionalTextField;
   741: 
   742:   //trmInitConnection ()
   743:   //  接続を初期化する
   744:   public static void trmInitConnection () {
   745:     //通信設定
   746:     trmBaudRateArray = new String[] { "75", "150", "300", "600", "1200", "2400", "4800", "9600", "19200", "31250", "38400", "50000", "57600", "76800", "115200", "230400" };
   747:     trmBaudRateIndex = 10;
   748:     trmDataBitsArray = new String[] { "B5", "B6", "B7", "B8" };
   749:     trmDataBitsIndex = 3;
   750:     trmParityArray = new String[] { "PN", "PO", "PE" };
   751:     trmParityIndex = 0;
   752:     trmStopBitsArray = new String[] { "S1", "S1.5", "S2" };
   753:     trmStopBitsIndex = 0;
   754:     trmFlowControlArray = new String[] { "NONE", "XON", "RTS" };
   755:     trmFlowControlIndex = 2;
   756:     trmBaudRateComboBox = null;
   757:     trmDataBitsComboBox = null;
   758:     trmParityComboBox = null;
   759:     trmStopBitsComboBox = null;
   760:     trmFlowControlComboBox = null;
   761:     //通信設定を復元する
   762:     for (String keyword : Settings.sgsGetString ("terminalsettings").split ("/")) {
   763:       for (int i = 0; i < trmBaudRateArray.length; i++) {
   764:         if (trmBaudRateArray[i].equals (keyword)) {
   765:           trmBaudRateIndex = i;
   766:           break;
   767:         }
   768:       }
   769:       for (int i = 0; i < trmDataBitsArray.length; i++) {
   770:         if (trmDataBitsArray[i].equals (keyword)) {
   771:           trmDataBitsIndex = i;
   772:           break;
   773:         }
   774:       }
   775:       for (int i = 0; i < trmParityArray.length; i++) {
   776:         if (trmParityArray[i].equals (keyword)) {
   777:           trmParityIndex = i;
   778:           break;
   779:         }
   780:       }
   781:       for (int i = 0; i < trmStopBitsArray.length; i++) {
   782:         if (trmStopBitsArray[i].equals (keyword)) {
   783:           trmStopBitsIndex = i;
   784:           break;
   785:         }
   786:       }
   787:       for (int i = 0; i < trmFlowControlArray.length; i++) {
   788:         if (trmFlowControlArray[i].equals (keyword)) {
   789:           trmFlowControlIndex = i;
   790:           break;
   791:         }
   792:       }
   793:     }  //for keyword
   794:     //ファイル転送
   795:     trmSendButton = null;
   796:     trmSendFileChooser = null;
   797:     trmSendDialog = null;
   798:     trmSendThread = null;
   799:     //SerialPort
   800:     trmPortArray = new SerialPort[0];
   801:     trmNumberOfPorts = 0;
   802:     //行
   803:     trmRows = 1;
   804:     trmRowName = new String[1];
   805:     trmRowName[0] = "Terminal";
   806:     trmRowToCol = new int[1];
   807:     Arrays.fill (trmRowToCol, -1);
   808:     //AUX*
   809:     trmNumberOfAUXs = 0;
   810:     //列
   811:     trmCols = 1;
   812:     trmColName = new String[1];
   813:     trmColName[0] = "Terminal";
   814:     trmColToRow = new int[1];
   815:     Arrays.fill (trmColToRow, -1);
   816:     //追加ポート
   817:     {
   818:       String text = Settings.sgsGetString ("additionalport");
   819:       try {
   820:         text = URLDecoder.decode (text, "UTF-8");
   821:       } catch (UnsupportedEncodingException uee) {
   822:         text = "";
   823:       }
   824:       trmAdditionalTextField = ComponentFactory.createTextField (text, 30);
   825:     }
   826:     //TCP/IPポート
   827:     trmTcpipInit ();
   828:     //接続
   829:     trmConnectionArray = new Connection[0];
   830:     trmConnectionBox = ComponentFactory.createVerticalBox (
   831:       Box.createVerticalGlue (),
   832:       ComponentFactory.createHorizontalBox (
   833:         Multilingual.mlnText (
   834:           ComponentFactory.createLabel ("Additional port "),
   835:           "ja", "追加ポート "
   836:           ),
   837:         trmAdditionalTextField,
   838:         Multilingual.mlnText (
   839:           ComponentFactory.createLabel (" TCP/IP port "),
   840:           "ja", " TCP/IP ポート "
   841:           ),
   842:         trmTcpipSpinner,
   843:         Box.createHorizontalStrut (5),
   844:         trmTcpipCheckBox,
   845:         Box.createHorizontalGlue ()
   846:         )
   847:       );
   848:     trmConnectionListener = new ActionListener () {
   849:       @Override public void actionPerformed (ActionEvent ae) {
   850:         String command = ae.getActionCommand ();
   851:         for (Connection connection : trmConnectionArray) {
   852:           if (connection.text.equals (command)) {
   853:             if (connection.connected) {
   854:               trmDisconnect (connection);
   855:             } else {
   856:               trmConnect (connection);
   857:             }
   858:             break;
   859:           }
   860:         }
   861:       }
   862:     };
   863:     trmRefreshEnabled = false;
   864:     trmRefreshButton = null;
   865:     trmSettingsEnabled = false;
   866:     trmSendEnabled = false;
   867:     //接続を更新する
   868:     trmUpdateConnection ();
   869:     //接続を復元する
   870:     HashSet<String> map = new HashSet<String> ();
   871:     for (String encodedText : Settings.sgsGetString ("rs232cconnection").split ("/")) {
   872:       try {
   873:         map.add (URLDecoder.decode (encodedText, "UTF-8"));
   874:       } catch (UnsupportedEncodingException uee) {
   875:       }
   876:     }
   877:     for (Connection connection : trmConnectionArray) {
   878:       if (map.contains (connection.text)) {
   879:         trmConnect (connection);
   880:       }
   881:     }
   882:   }  //trmInitConnection
   883: 
   884:   //trmTiniConnection ()
   885:   //  接続を後始末する
   886:   public static void trmTiniConnection () {
   887:     //接続を保存する
   888:     {
   889:       StringBuilder sb = new StringBuilder ();
   890:       for (Connection connection : trmConnectionArray) {
   891:         if (connection.connected) {  //接続している
   892:           if (sb.length () != 0) {
   893:             sb.append ('/');
   894:           }
   895:           try {
   896:             sb.append (URLEncoder.encode (connection.text, "UTF-8"));
   897:           } catch (UnsupportedEncodingException uee) {
   898:           }
   899:         }
   900:       }
   901:       Settings.sgsPutString ("rs232cconnection", sb.toString ());
   902:     }
   903:     //追加ポート
   904:     {
   905:       String text = trmAdditionalTextField.getText ();
   906:       try {
   907:         text = URLEncoder.encode (text, "UTF-8");
   908:       } catch (UnsupportedEncodingException uee) {
   909:         text = "";
   910:       }
   911:       Settings.sgsPutString ("additionalport", text);
   912:     }
   913:     //TCP/IPポート
   914:     trmTcpipTini ();
   915:     //通信設定を保存する
   916:     {
   917:       StringBuilder sb = new StringBuilder ();
   918:       sb.append (trmBaudRateArray[trmBaudRateIndex]);
   919:       sb.append ('/');
   920:       sb.append (trmDataBitsArray[trmDataBitsIndex]);
   921:       sb.append ('/');
   922:       sb.append (trmParityArray[trmParityIndex]);
   923:       sb.append ('/');
   924:       sb.append (trmStopBitsArray[trmStopBitsIndex]);
   925:       sb.append ('/');
   926:       sb.append (trmFlowControlArray[trmFlowControlIndex]);
   927:       Settings.sgsPutString ("terminalsettings", sb.toString ());
   928:     }
   929:     //すべて切断する
   930:     for (Connection connection : trmConnectionArray) {
   931:       trmDisconnect (connection);
   932:     }
   933:   }  //trmTiniConnection
   934: 
   935:   //trmIsConnectionUpdatable ()
   936:   //  接続を更新できるか
   937:   public static boolean trmIsConnectionUpdatable () {
   938:     for (Connection connection : trmConnectionArray) {
   939:       if (connection.row != 0 &&  //SerialPortに接続している
   940:           connection.connected) {  //接続している
   941:         return false;  //更新できない
   942:       }
   943:     }
   944:     return true;  //更新できる
   945:   }  //trmIsConnectionUpdatable
   946: 
   947:   //trmUpdateConnection ()
   948:   //  接続を更新する
   949:   public static void trmUpdateConnection () {
   950:     //更新できないときは何もしない
   951:     if (!trmIsConnectionUpdatable ()) {
   952:       return;
   953:     }
   954:     //SerialPort
   955:     ArrayList<SerialPort> portList = new ArrayList<SerialPort> ();
   956:     for (SerialPort port : SerialPort.getCommPorts ()) {
   957:       int vid = port.getVendorID ();
   958:       int pid = port.getProductID ();
   959:       portList.add (port);
   960:     }
   961:     for (String descriptor : trmAdditionalTextField.getText ().split (",")) {  //追加ポート
   962:       descriptor = descriptor.trim ();
   963:       if (!descriptor.equals ("")) {
   964:         try {
   965:           SerialPort port = SerialPort.getCommPort (descriptor);
   966:           if (port != null) {
   967:             if (false) {  //既にリストにあるポートを追加できないようにする。getDescriptivePortName()はおそらく適切でない
   968:               for (SerialPort anotherPort : portList) {
   969:                 if (port.getDescriptivePortName ().equals (anotherPort.getDescriptivePortName ())) {  //port.equals(anotherPort)は不可
   970:                   port = null;
   971:                   break;
   972:                 }
   973:               }
   974:             }
   975:             if (port != null) {
   976:               portList.add (port);
   977:             }
   978:           } else {
   979:             System.out.println (descriptor + " not found");
   980:           }
   981:         } catch (SerialPortInvalidPortException spipe) {
   982:           System.out.println (spipe.toString ());
   983:         }
   984:       }
   985:     }
   986:     trmNumberOfPorts = portList.size ();
   987:     trmPortArray = portList.toArray (new SerialPort[trmNumberOfPorts]);
   988:     //行
   989:     trmRows = 1 + trmNumberOfPorts + 1;
   990:     trmRowName = new String[trmRows];
   991:     trmRowName[0] = "Terminal";
   992:     for (int row = 1; row < trmRows - 1; row++) {
   993:       SerialPort port = trmPortArray[row - 1];
   994:       trmRowName[row] = port.getSystemPortName () + "(" + port.getPortDescription () + ")";
   995:     }
   996:     trmRowName[trmRows - 1] = "TCP/IP";
   997:     trmRowToCol = new int[trmRows];
   998:     Arrays.fill (trmRowToCol, -1);
   999:     //AUX*
  1000:     trmNumberOfAUXs = 1;
  1001:     //列
  1002:     trmCols = 1 + trmNumberOfAUXs;
  1003:     trmColName = new String[trmCols];
  1004:     trmColName[0] = "Terminal";
  1005:     for (int col = 1; col < trmCols; col++) {
  1006:       trmColName[col] = col == 1 ? "AUX" : "AUX" + col;
  1007:     }
  1008:     trmColToRow = new int[trmCols];
  1009:     Arrays.fill (trmColToRow, -1);
  1010:     //接続
  1011:     for (int index = trmConnectionArray.length - 1; 0 <= index; index--) {
  1012:       trmConnectionBox.remove (index);
  1013:     }
  1014:     trmConnectionArray = new Connection[trmCols * trmRows - 1];
  1015:     for (int row = 0; row < trmRows; row++) {
  1016:       for (int col = 0; col < trmCols; col++) {
  1017:         if (col == 0 && row == 0) {
  1018:           continue;
  1019:         }
  1020:         Connection connection = new Connection ();
  1021:         connection.row = row;
  1022:         connection.col = col;
  1023:         connection.index = trmCols * row + col - 1;
  1024:         connection.text = trmRowName[row] + " ⇔ " + trmColName[col];
  1025:         connection.connected = false;
  1026:         connection.checkBox =
  1027:           ComponentFactory.createCheckBox (connection.connected, connection.text, trmConnectionListener);
  1028:         connection.box =
  1029:           ComponentFactory.createHorizontalBox (
  1030:             connection.checkBox,
  1031:             Box.createHorizontalGlue ());
  1032:         trmConnectionArray[connection.index] = connection;
  1033:         trmConnectionBox.add (connection.box, connection.index);
  1034:       }  //for col
  1035:     }  //for row
  1036:     trmUpdateComponents ();
  1037:     trmConnectionBox.validate ();
  1038:   }  //trmUpdateConnection
  1039: 
  1040:   //trmUpdateComponents ()
  1041:   //  接続できないConnectionのcheckBoxを無効にする
  1042:   //  SerialPortに接続しているConnectionがあるとき更新ボタンを無効にする
  1043:   //  Terminal-SerialPortがないとき通信設定を表示しない
  1044:   public static void trmUpdateComponents () {
  1045:     boolean updatable = true;  //更新できる
  1046:     boolean configurable = false;  //設定できない
  1047:     boolean transferable = false;  //転送できない
  1048:     for (Connection connection : trmConnectionArray) {
  1049:       if (connection.connected) {  //接続している
  1050:         connection.checkBox.setEnabled (true);  //切断できる
  1051:         if (connection.row != trmRows - 1 &&
  1052:             0 < connection.row) {  //SerialPortを接続している
  1053:           updatable = false;  //更新できない
  1054:           if (connection.col == 0) {  //TerminalとSerialPortを接続している
  1055:             configurable = true;  //設定できる
  1056:           }
  1057:         }
  1058:         if (connection.row == 0 ||
  1059:             connection.col == 0) {  //Terminalを接続している
  1060:           transferable = true;  //転送できる
  1061:         }
  1062:       } else {  //接続していない
  1063:         connection.checkBox.setEnabled (trmIsConnectable (connection));  //接続できるときだけ有効
  1064:       }
  1065:     }
  1066:     trmRefreshEnabled = updatable;
  1067:     if (trmRefreshButton != null) {
  1068:       trmRefreshButton.setEnabled (updatable);
  1069:     }
  1070:     trmSettingsEnabled = configurable;
  1071:     trmSendEnabled = transferable;
  1072:     if (trmBaudRateComboBox != null) {
  1073:       trmBaudRateComboBox.setEnabled (configurable);
  1074:       trmDataBitsComboBox.setEnabled (configurable);
  1075:       trmParityComboBox.setEnabled (configurable);
  1076:       trmStopBitsComboBox.setEnabled (configurable);
  1077:       trmFlowControlComboBox.setEnabled (configurable);
  1078:       trmSendButton.setEnabled (transferable);
  1079:     }
  1080:   }  //trmUpdateComponents
  1081: 
  1082:   //connectable = trmIsConnectable (connection)
  1083:   //  接続できるか
  1084:   public static boolean trmIsConnectable (Connection connection) {
  1085:     if (!connection.connected) {  //自分が接続していないとき
  1086:       for (Connection connection2 : trmConnectionArray) {
  1087:         if (connection != connection2 &&  //自分以外で
  1088:             connection2.connected &&  //接続していて
  1089:             (((connection.row == 0 || connection.col == 0) &&
  1090:               (connection2.row == 0 || connection2.col == 0)) ||  //Terminalが衝突しているまたは
  1091:              connection.row == connection2.row ||  //SerialPortが衝突しているまたは
  1092:              connection.col == connection2.col)) {  //AUX*が衝突しているとき
  1093:           return false;  //接続できない
  1094:         }
  1095:       }
  1096:     }
  1097:     return true;  //接続できる
  1098:   }  //trmIsConnectable
  1099: 
  1100:   //trmConnect (connection)
  1101:   //  接続する
  1102:   public static void trmConnect (Connection connection) {
  1103:     //接続しているか接続できないときは何もしない
  1104:     if (connection.connected ||  //接続している
  1105:         !trmIsConnectable (connection)) {  //接続できない
  1106:       return;
  1107:     }
  1108:     //接続する
  1109:     connection.connected = true;
  1110:     connection.checkBox.setSelected (true);
  1111:     trmRowToCol[connection.row] = connection.col;
  1112:     trmColToRow[connection.col] = connection.row;
  1113:     trmUpdateComponents ();
  1114:     //キューを作る
  1115:     connection.row2colQueue = new ByteQueue ();
  1116:     connection.col2rowQueue = new ByteQueue ();
  1117:     if (connection.row == trmRows - 1) {  //TCP/IP→?
  1118:       //TCP/IPポートを開く
  1119:       if (!trmTcpipOpenPort (connection.row2colQueue)) {
  1120:         //開けなかった
  1121:         //!!!
  1122:       }
  1123:     } else if (0 < connection.row) {  //SerialPort→?
  1124:       //シリアルポートを開く
  1125:       SerialPort port = trmPortArray[connection.row - 1];
  1126:       port.openPort ();
  1127:       System.out.println (Multilingual.mlnJapanese ?
  1128:                           connection.text + " を開きました" :
  1129:                           connection.text + " opened");
  1130:       port.setComPortTimeouts (SerialPort.TIMEOUT_WRITE_BLOCKING | SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);
  1131:       port.setFlowControl (SerialPort.FLOW_CONTROL_DISABLED);
  1132:       //通信設定をSerialPortに反映させる
  1133:       trmReflectSettings (connection.col);
  1134:       //シリアルポートデータリスナーを設定する
  1135:       port.addDataListener (connection);
  1136:     }
  1137:     //ポーリングスレッドを開始する
  1138:     connection.polling = true;
  1139:     connection.row2colThread = (connection.col == 0 ? connection.new TerminalThread () :  //?→Terminal
  1140:                                 null);  //?→AUX
  1141:     connection.col2rowThread = (connection.row == 0 ? connection.new TerminalThread () :  //Terminal→?
  1142:                                 connection.row == trmRows - 1 ? connection.new TCPIPThread () :  //TCP/IP→?
  1143:                                 connection.new SerialPortThread ());  //SerialPort→?
  1144:     for (int i = 0; i < 2; i++) {
  1145:       Thread thread = (i == 0 ? connection.row2colThread : connection.col2rowThread);
  1146:       if (thread != null) {
  1147:         thread.start ();
  1148:       }
  1149:     }
  1150:     //ポーリングティッカーを開始する
  1151:     if (connection.col == 1) {  //?→AUX
  1152:       trmAUXNotReceiving = false;  //受信可
  1153:       trmAUXDataBuffer = 0;
  1154:       trmAUXDataAvailable = false;
  1155:       trmAUXConnection = connection;
  1156:       TickerQueue.tkqAdd (trmAUXTicker, XEiJ.mpuClockTime + XEiJ.TMR_FREQ / 1000 * 1);  //1ms
  1157:       //AUX送信割り込み
  1158:       trmAUXSendEmpty = true;  //送信バッファ空フラグをセット
  1159:     }
  1160:   }  //trmConnect
  1161: 
  1162:   //trmDisconnect (connection)
  1163:   //  切断する
  1164:   public static void trmDisconnect (Connection connection) {
  1165:     //接続していないときは何もしない
  1166:     if (!connection.connected) {
  1167:       return;
  1168:     }
  1169:     //切断する
  1170:     connection.connected = false;
  1171:     connection.checkBox.setSelected (connection.connected);
  1172:     trmRowToCol[connection.row] = -1;
  1173:     trmColToRow[connection.col] = -1;
  1174:     trmUpdateComponents ();
  1175:     //ポーリングティッカーを終了する
  1176:     if (trmAUXConnection != null) {
  1177:       TickerQueue.tkqRemove (trmAUXTicker);
  1178:       trmAUXConnection = null;
  1179:     }
  1180:     //ポーリングスレッドを停止する
  1181:     connection.polling = false;
  1182:     for (int i = 0; i < 2; i++) {
  1183:       Thread thread = (i == 0 ? connection.row2colThread : connection.col2rowThread);
  1184:       if (thread != null) {
  1185:         connection.row2colThread = null;
  1186:         if (thread.isAlive ()) {  //スレッドがある
  1187:           thread.interrupt ();  //割り込む
  1188:           try {
  1189:             thread.join ();  //止まるまで待つ
  1190:           } catch (InterruptedException ie) {
  1191:           }
  1192:         }
  1193:       }
  1194:     }
  1195:     //AUX送信割り込み
  1196:     if (connection.col == 1) {  //?→AUX
  1197:       TickerQueue.tkqRemove (trmAUXSendTicker);  //送信ティッカーを消去
  1198:     }
  1199:     if (connection.row == trmRows - 1) {
  1200:       //TCP/IPポートを閉じる
  1201:       trmTcpipClosePort ();
  1202:     } else if (0 < connection.row) {  //SerialPort→?
  1203:       SerialPort port = trmPortArray[connection.row - 1];
  1204:       //シリアルポートデータリスナーを削除する
  1205:       port.removeDataListener ();
  1206:       //シリアルポートを閉じる
  1207:       port.closePort ();
  1208:       System.out.println (Multilingual.mlnJapanese ?
  1209:                           connection.text + " を閉じました" :
  1210:                           connection.text + " closed");
  1211:     }
  1212:     //キューを消す
  1213:     connection.row2colQueue.clear ();
  1214:     connection.col2rowQueue.clear ();
  1215:     connection.row2colQueue = null;
  1216:     connection.col2rowQueue = null;
  1217:   }  //trmDisconnect
  1218: 
  1219:   //trmSetBaudRate (index)
  1220:   //  ボーレートを設定する
  1221:   public static void trmSetBaudRate (int index) {
  1222:     if (0 <= index && index < trmBaudRateArray.length) {
  1223:       trmBaudRateIndex = index;
  1224:       trmReflectSettings (0);
  1225:     }
  1226:   }  //trmSetBaudRate
  1227: 
  1228:   //trmSetDataBits (index)
  1229:   //  データビットを設定する
  1230:   public static void trmSetDataBits (int index) {
  1231:     if (0 <= index && index < trmDataBitsArray.length) {
  1232:       trmDataBitsIndex = index;
  1233:       trmReflectSettings (0);
  1234:     }
  1235:   }  //trmSetDataBits
  1236: 
  1237:   //trmSetParity (index)
  1238:   //  パリティを設定する
  1239:   public static void trmSetParity (int index) {
  1240:     if (0 <= index && index < trmParityArray.length) {
  1241:       trmParityIndex = index;
  1242:       trmReflectSettings (0);
  1243:     }
  1244:   }  //trmSetParity
  1245: 
  1246:   //trmSetStopBits (index)
  1247:   //  ストップビットを設定する
  1248:   public static void trmSetStopBits (int index) {
  1249:     if (0 <= index && index < trmStopBitsArray.length) {
  1250:       trmStopBitsIndex = index;
  1251:       trmReflectSettings (0);
  1252:     }
  1253:   }  //trmSetStopBits
  1254: 
  1255:   //trmSetFlowControl (index)
  1256:   //  フロー制御を設定する
  1257:   public static void trmSetFlowControl (int index) {
  1258:     if (0 <= index && index < trmFlowControlArray.length) {
  1259:       trmFlowControlIndex = index;
  1260:       trmReflectSettings (0);
  1261:     }
  1262:   }  //trmSetFlowControl
  1263: 
  1264:   //trmReflectSettings (col)
  1265:   //  通信設定をSerialPortに反映させる
  1266:   public static void trmReflectSettings (int col) {
  1267:     int row = trmColToRow[col];
  1268:     SerialPort port = row < 1 || trmNumberOfPorts < row ? null : trmPortArray[row - 1];
  1269:     if (col == 0) {  //Terminal
  1270:       String baudRate = trmBaudRateArray[trmBaudRateIndex];
  1271:       String dataBits = trmDataBitsArray[trmDataBitsIndex];
  1272:       String stopBits = trmStopBitsArray[trmStopBitsIndex];
  1273:       String parity = trmParityArray[trmParityIndex];
  1274:       String flowControl = trmFlowControlArray[trmFlowControlIndex];
  1275:       if (port != null) {
  1276:         port.setComPortParameters (Integer.parseInt (baudRate, 10),
  1277:                                    Integer.parseInt (dataBits.substring (1), 10),
  1278:                                    stopBits.equals ("S1.5") ? SerialPort.ONE_POINT_FIVE_STOP_BITS :
  1279:                                    stopBits.equals ("S2") ? SerialPort.TWO_STOP_BITS : SerialPort.ONE_STOP_BIT,
  1280:                                    parity.equals ("PO") ? SerialPort.ODD_PARITY :
  1281:                                    parity.equals ("PE") ? SerialPort.EVEN_PARITY : SerialPort.NO_PARITY);
  1282:         port.setFlowControl (flowControl.equals ("RTS") ? (SerialPort.FLOW_CONTROL_RTS_ENABLED |
  1283:                                                            SerialPort.FLOW_CONTROL_CTS_ENABLED) :
  1284:                              flowControl.equals ("XON") ? (SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED |
  1285:                                                            SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED) :
  1286:                              SerialPort.FLOW_CONTROL_DISABLED);
  1287:       }
  1288:     } else if (col == 1) {  //AUX
  1289:       double rate = Z8530.sccFreq / (double) ((Z8530.scc1aBaudRateGen + 2) << (Z8530.scc1aClockModeShift + 1));
  1290:       double bits = (1.0 +  //start
  1291:                      (Z8530.scc1aRxBits == 0 ? 5.0 :
  1292:                       Z8530.scc1aRxBits == 1 ? 7.0 :
  1293:                       Z8530.scc1aRxBits == 2 ? 6.0 : 8.0) +  //data
  1294:                      ((Z8530.scc1aParity & 1) == 0 ? 0.0 : 1.0) +  //parity
  1295:                      (Z8530.scc1aStop == 0 ? 0.0 :
  1296:                       Z8530.scc1aStop == 1 ? 1.0 :
  1297:                       Z8530.scc1aStop == 2 ? 1.5 : 2.0));  //stop
  1298:       double interval = bits / rate;
  1299:       if (false) {
  1300:         System.out.printf ("%08x baudrate=%.3fbps interval=%.3fus\n", XEiJ.regPC0, rate, interval * 1e+6);
  1301:       }
  1302:       Z8530.scc1aInterval = Math.round (interval * (double) XEiJ.TMR_FREQ);
  1303:       //
  1304:       if (port != null) {
  1305:         port.setComPortParameters ((int) Math.round (rate),
  1306:                                    Z8530.scc1aRxBits == 0b00 ? 5 :
  1307:                                    Z8530.scc1aRxBits == 0b01 ? 7 :
  1308:                                    Z8530.scc1aRxBits == 0b10 ? 6 : 8,
  1309:                                    Z8530.scc1aStop == 0b10 ? SerialPort.ONE_POINT_FIVE_STOP_BITS :
  1310:                                    Z8530.scc1aStop == 0b11 ? SerialPort.TWO_STOP_BITS : SerialPort.ONE_STOP_BIT,
  1311:                                    Z8530.scc1aParity == 0b01 ? SerialPort.ODD_PARITY :
  1312:                                    Z8530.scc1aParity == 0b11 ? SerialPort.EVEN_PARITY : SerialPort.NO_PARITY);
  1313:       }
  1314:       //
  1315:       //フロー制御をSerialPortに反映させる
  1316:       TickerQueue.tkqAdd (trmAUXFlowControlTicker, XEiJ.mpuClockTime + XEiJ.TMR_FREQ * 500 / 1000000);  //500us後
  1317:     } else {  //AUX2~
  1318:       //!!! 未対応
  1319:     }
  1320:   }  //trmReflectSettings
  1321: 
  1322: 
  1323:   //trmInit ()
  1324:   //  ターミナルウインドウを初期化する
  1325:   public static void trmInit () {
  1326:     trmFrame = null;
  1327:     trmBoard = null;
  1328:     trmPopupMenu = null;
  1329:     trmPopupCutMenuItem = null;
  1330:     trmPopupCopyMenuItem = null;
  1331:     trmPopupPasteMenuItem = null;
  1332:     trmPopupSelectAllMenuItem = null;
  1333:     trmPopupSendCtrlCMenuItem = null;
  1334:     trmOutputBuilder = new StringBuilder ();
  1335:     trmOutputEnd = 0;
  1336:     trmOutputSJIS1 = 0;
  1337:     //
  1338:     trmInitConnection ();
  1339:     trmReset ();
  1340:   }
  1341: 
  1342:   public static void trmReset () {
  1343:     trmRSDRV202Head = 0;
  1344:     trmTMSIO031Head = 0;
  1345:     trmBSIO021Head = 0;
  1346:     trmAUXFlowControlXON = false;
  1347:     trmAUXFlowControlRTS = false;
  1348:   }
  1349: 
  1350:   //trmTini ()
  1351:   //  後始末
  1352:   public static void trmTini () {
  1353:     trmTiniConnection ();
  1354:   }  //trmTini
  1355: 
  1356:   //trmMake ()
  1357:   //  ターミナルウインドウを作る
  1358:   //  ここでは開かない
  1359:   public static void trmMake () {
  1360: 
  1361:     //テキストエリア
  1362:     trmBoard = ComponentFactory.createScrollTextArea (
  1363:       trmOutputBuilder.toString (),  //作る前に出力されていた文字列を設定する
  1364:       650, 350,
  1365:       true);
  1366:     trmOutputBuilder = null;  //これはもういらない
  1367:     trmBoard.setUnderlineCursorOn (true);
  1368:     trmBoard.setLineWrap (true);  //行を折り返す
  1369:     trmBoard.addDocumentListener (new DocumentListener () {
  1370:       @Override public void changedUpdate (DocumentEvent de) {
  1371:       }
  1372:       @Override public void insertUpdate (DocumentEvent de) {
  1373:         if (de.getOffset () < trmOutputEnd) {
  1374:           trmOutputEnd += de.getLength ();  //出力された文字列の末尾を調整する
  1375:         }
  1376:       }
  1377:       @Override public void removeUpdate (DocumentEvent de) {
  1378:         if (de.getOffset () < trmOutputEnd) {
  1379:           trmOutputEnd -= Math.min (de.getLength (), trmOutputEnd - de.getOffset ());  //出力された文字列の末尾を調整する
  1380:         }
  1381:       }
  1382:     });
  1383:     trmBoard.addKeyListener (new KeyAdapter () {
  1384:       @Override public void keyPressed (KeyEvent ke) {
  1385:         int keyCode = ke.getKeyCode ();
  1386:         if (keyCode == KeyEvent.VK_ENTER) {  //ENTERキーが押された
  1387:           ke.consume ();  //ENTERキーをキャンセルする
  1388:           trmEnter ();  //ENTERキーを処理する
  1389:         } else if (keyCode == KeyEvent.VK_PAUSE) {  //Pauseキーが押された
  1390:           ke.consume ();  //Pauseキーをキャンセルする
  1391:           trmSendString ("\u0003");  //^Cを送信する
  1392:         }
  1393:       }
  1394:     });
  1395: 
  1396:     //ポップアップメニュー
  1397:     ActionListener popupActionListener = new ActionListener () {
  1398:       @Override public void actionPerformed (ActionEvent ae) {
  1399:         switch (ae.getActionCommand ()) {
  1400:         case "Cut":  //切り取り
  1401:           trmCut ();
  1402:           break;
  1403:         case "Copy":  //コピー
  1404:           trmCopy ();
  1405:           break;
  1406:         case "Paste":  //貼り付け
  1407:           trmPaste ();
  1408:           break;
  1409:         case "Select All":  //すべて選択
  1410:           trmSelectAll ();
  1411:           break;
  1412:         case "Send ^C":  //^C 送信
  1413:           trmSendString ("\u0003");  //^Cを送信する
  1414:           break;
  1415:         }
  1416:       }
  1417:     };
  1418:     trmPopupMenu = ComponentFactory.createPopupMenu (
  1419:       trmPopupCutMenuItem = Multilingual.mlnText (
  1420:         ComponentFactory.createMenuItem ("Cut", 'T', popupActionListener),
  1421:         "ja", "切り取り"),
  1422:       trmPopupCopyMenuItem = Multilingual.mlnText (
  1423:         ComponentFactory.createMenuItem ("Copy", 'C', popupActionListener),
  1424:         "ja", "コピー"),
  1425:       trmPopupPasteMenuItem = Multilingual.mlnText (
  1426:         ComponentFactory.createMenuItem ("Paste", 'P', popupActionListener),
  1427:         "ja", "貼り付け"),
  1428:       ComponentFactory.createHorizontalSeparator (),
  1429:       trmPopupSelectAllMenuItem = Multilingual.mlnText (
  1430:         ComponentFactory.createMenuItem ("Select All", 'A', popupActionListener),
  1431:         "ja", "すべて選択"),
  1432:       ComponentFactory.createHorizontalSeparator (),
  1433:       trmPopupSendCtrlCMenuItem = Multilingual.mlnText (
  1434:         ComponentFactory.createMenuItem ("Send ^C", popupActionListener),
  1435:         "ja", "^C 送信")
  1436:       );
  1437:     trmBoard.addMouseListener (new MouseAdapter () {
  1438:       @Override public void mousePressed (MouseEvent me) {
  1439:         trmShowPopup (me);
  1440:       }
  1441:       @Override public void mouseReleased (MouseEvent me) {
  1442:         trmShowPopup (me);
  1443:       }
  1444:     });
  1445: 
  1446:     //アクションリスナー
  1447:     ActionListener listener = new ActionListener () {
  1448:       @Override public void actionPerformed (ActionEvent ae) {
  1449:         Object source = ae.getSource ();
  1450:         String command = ae.getActionCommand ();
  1451:         switch (command) {
  1452:         case "Refresh":
  1453:           trmUpdateConnection ();
  1454:           break;
  1455:         case "Baud rate":
  1456:           trmSetBaudRate (((JComboBox) source).getSelectedIndex ());
  1457:           break;
  1458:         case "Data bits":
  1459:           trmSetDataBits (((JComboBox) source).getSelectedIndex ());
  1460:           break;
  1461:         case "Parity":
  1462:           trmSetParity (((JComboBox) source).getSelectedIndex ());
  1463:           break;
  1464:         case "Stop bits":
  1465:           trmSetStopBits (((JComboBox) source).getSelectedIndex ());
  1466:           break;
  1467:         case "Flow control":
  1468:           trmSetFlowControl (((JComboBox) source).getSelectedIndex ());
  1469:           break;
  1470:         case "Send file":
  1471:           trmSendFile ();
  1472:           break;
  1473:         case "7.3728MHz":
  1474:           Z8530.sccFreq = ((JCheckBox) source).isSelected () ? 7372800.0 : 5000000.0;
  1475:           break;
  1476:         default:
  1477:           System.out.println ("unknown action command " + command);
  1478:         }
  1479:       }
  1480:     };
  1481: 
  1482:     //ボタンとコンボボックス
  1483:     trmRefreshButton =
  1484:       ComponentFactory.setEnabled (
  1485:         Multilingual.mlnText (
  1486:           ComponentFactory.createButton ("Refresh", listener),
  1487:           "ja", "更新"),
  1488:         trmRefreshEnabled);
  1489:     trmBaudRateComboBox =
  1490:       ComponentFactory.setEnabled (
  1491:         Multilingual.mlnToolTipText (
  1492:           ComponentFactory.createComboBox (
  1493:             trmBaudRateIndex, "Baud rate", listener, trmBaudRateArray),
  1494:           "ja", "ボーレート"),
  1495:         trmSettingsEnabled);
  1496:     trmDataBitsComboBox =
  1497:       ComponentFactory.setEnabled (
  1498:         Multilingual.mlnToolTipText (
  1499:           ComponentFactory.createComboBox (
  1500:             trmDataBitsIndex, "Data bits", listener, trmDataBitsArray),
  1501:           "ja", "データビット"),
  1502:         trmSettingsEnabled);
  1503:     trmParityComboBox =
  1504:       ComponentFactory.setEnabled (
  1505:         Multilingual.mlnToolTipText (
  1506:           ComponentFactory.createComboBox (
  1507:             trmParityIndex, "Parity", listener, trmParityArray),
  1508:           "ja", "パリティ"),
  1509:         trmSettingsEnabled);
  1510:     trmStopBitsComboBox =
  1511:       ComponentFactory.setEnabled (
  1512:         Multilingual.mlnToolTipText (
  1513:           ComponentFactory.createComboBox (
  1514:             trmStopBitsIndex, "Stop bits", listener, trmStopBitsArray),
  1515:           "ja", "ストップビット"),
  1516:         trmSettingsEnabled);
  1517:     trmFlowControlComboBox =
  1518:       ComponentFactory.setEnabled (
  1519:         Multilingual.mlnToolTipText (
  1520:           ComponentFactory.createComboBox (
  1521:             trmFlowControlIndex, "Flow control", listener, trmFlowControlArray),
  1522:           "ja", "フロー制御"),
  1523:         trmSettingsEnabled);
  1524:     trmSendButton =
  1525:       ComponentFactory.setEnabled (
  1526:         Multilingual.mlnText (
  1527:           ComponentFactory.createButton ("Send file", listener),
  1528:           "ja", "ファイル送信"),
  1529:         trmSendEnabled);
  1530: 
  1531:     //ウインドウ
  1532:     trmFrame = Multilingual.mlnTitle (
  1533:       ComponentFactory.createRestorableSubFrame (
  1534:         Settings.SGS_TRM_FRAME_KEY,
  1535:         "RS-232C and terminal",
  1536:         null,
  1537:         ComponentFactory.createVerticalSplitPane (
  1538:           ComponentFactory.createVerticalBox (
  1539:             Multilingual.mlnTitledBorder (
  1540:               ComponentFactory.setTitledLineBorder (
  1541:                 ComponentFactory.createHorizontalBox (
  1542:                   Box.createHorizontalStrut (5),
  1543:                   ComponentFactory.createVerticalBox (
  1544:                     Box.createVerticalGlue (),
  1545:                     trmRefreshButton,
  1546:                     Box.createVerticalGlue ()
  1547:                     ),
  1548:                   Box.createHorizontalStrut (10),
  1549:                   trmConnectionBox,
  1550:                   Box.createHorizontalGlue ()
  1551:                   ),
  1552:                 "Connection"),
  1553:               "ja", "接続"),
  1554:             ComponentFactory.createHorizontalBox (
  1555:               Multilingual.mlnTitledBorder (
  1556:                 ComponentFactory.setTitledLineBorder (
  1557:                   ComponentFactory.createHorizontalBox (
  1558:                     Box.createHorizontalStrut (5),
  1559:                     trmBaudRateComboBox,
  1560:                     trmDataBitsComboBox,
  1561:                     trmParityComboBox,
  1562:                     trmStopBitsComboBox,
  1563:                     trmFlowControlComboBox,
  1564:                     Box.createHorizontalStrut (5)
  1565:                     ),  //createHorizontalBox
  1566:                   "Communication Settings"),  //setTitledLineBorder
  1567:                 "ja", "通信設定"),  //mlnTitledBorder
  1568:               Multilingual.mlnTitledBorder (
  1569:                 ComponentFactory.setTitledLineBorder (
  1570:                   ComponentFactory.createHorizontalBox (
  1571:                     Box.createHorizontalStrut (5),
  1572:                     trmSendButton,
  1573:                     Box.createHorizontalStrut (5)
  1574:                     ),  //createHorizontalBox
  1575:                   "Transfer"),  //setTitledLineBorder
  1576:                 "ja", "転送"),  //mlnTitledBorder
  1577:               Box.createHorizontalGlue (),
  1578:               Multilingual.mlnTitledBorder (
  1579:                 ComponentFactory.setTitledLineBorder (
  1580:                   ComponentFactory.createHorizontalBox (
  1581:                     Box.createHorizontalStrut (5),
  1582:                     ComponentFactory.createCheckBox (Z8530.sccFreq == 7372800.0, "7.3728MHz", listener),
  1583:                     Box.createHorizontalStrut (5)
  1584:                     ),  //createHorizontalBox
  1585:                   "Modification"),  //setTitledLineBorder
  1586:                 "ja", "改造")  //mlnTitledBorder
  1587:               )  //createHorizontalBox
  1588:             ),  //createVerticalBox
  1589:           ComponentFactory.createVerticalBox (
  1590:             Multilingual.mlnTitledBorder (
  1591:               ComponentFactory.setTitledLineBorder (trmBoard, "Terminal"),
  1592:               "ja", "ターミナル")
  1593:             )  //createVerticalBox
  1594:           )  //createVerticalSplitPane
  1595:         ),  //createRestorableSubFrame
  1596:       "ja", "RS-232C とターミナル");
  1597: 
  1598:   }  //trmMake()
  1599: 
  1600:   //trmShowPopup (me)
  1601:   //  ポップアップメニューを表示する
  1602:   //  テキストエリアのマウスリスナーが呼び出す
  1603:   public static void trmShowPopup (MouseEvent me) {
  1604:     if (me.isPopupTrigger ()) {
  1605:       //選択範囲があれば切り取りとコピーが有効
  1606:       boolean enableCutAndCopy = XEiJ.clpClipboard != null && trmBoard.getSelectionStart () != trmBoard.getSelectionEnd ();
  1607:       ComponentFactory.setEnabled (trmPopupCutMenuItem, enableCutAndCopy);
  1608:       ComponentFactory.setEnabled (trmPopupCopyMenuItem, enableCutAndCopy);
  1609:       //クリップボードに文字列があれば貼り付けが有効
  1610:       ComponentFactory.setEnabled (trmPopupPasteMenuItem, XEiJ.clpClipboard != null && XEiJ.clpClipboard.isDataFlavorAvailable (DataFlavor.stringFlavor));
  1611:       //クリップボードがあればすべて選択が有効
  1612:       ComponentFactory.setEnabled (trmPopupSelectAllMenuItem, XEiJ.clpClipboard != null);
  1613:       //Terminalが接続していれば^C送信が有効
  1614:       ComponentFactory.setEnabled (trmPopupSendCtrlCMenuItem,
  1615:                                    trmRowToCol[0] == 1 ||  //Terminal→AUX
  1616:                                    0 < trmColToRow[0]);  //Terminal→SerialPort
  1617:       //ポップアップメニューを表示する
  1618:       trmPopupMenu.show (me.getComponent (), me.getX (), me.getY ());
  1619:     }
  1620:   }  //trmShowPopup(MouseEvent)
  1621: 
  1622:   //trmCut ()
  1623:   //  切り取り
  1624:   public static void trmCut () {
  1625:     if (XEiJ.clpClipboard != null) {
  1626:       //選択範囲の文字列をコピーする
  1627:       XEiJ.clpClipboardString = trmBoard.getSelectedText ();
  1628:       try {
  1629:         XEiJ.clpClipboard.setContents (XEiJ.clpStringContents, XEiJ.clpClipboardOwner);
  1630:         XEiJ.clpIsClipboardOwner = true;  //自分がコピーした
  1631:       } catch (Exception e) {
  1632:         return;
  1633:       }
  1634:       //選択範囲の文字列を削除する
  1635:       trmBoard.replaceRange ("", trmBoard.getSelectionStart (), trmBoard.getSelectionEnd ());
  1636:     }
  1637:   }  //trmCut()
  1638: 
  1639:   //trmCopy ()
  1640:   //  コピー
  1641:   public static void trmCopy () {
  1642:     if (XEiJ.clpClipboard != null) {
  1643:       //選択範囲の文字列をコピーする
  1644:       String selectedText = trmBoard.getSelectedText ();
  1645:       if (selectedText != null) {
  1646:         XEiJ.clpClipboardString = selectedText;
  1647:         try {
  1648:           XEiJ.clpClipboard.setContents (XEiJ.clpStringContents, XEiJ.clpClipboardOwner);
  1649:           XEiJ.clpIsClipboardOwner = true;  //自分がコピーした
  1650:         } catch (Exception e) {
  1651:           return;
  1652:         }
  1653:       }
  1654:     }
  1655:   }  //trmCopy()
  1656: 
  1657:   //trmPaste ()
  1658:   //  貼り付け
  1659:   public static void trmPaste () {
  1660:     if (XEiJ.clpClipboard != null) {
  1661:       //クリップボードから文字列を取り出す
  1662:       String string = null;
  1663:       try {
  1664:         string = (String) XEiJ.clpClipboard.getData (DataFlavor.stringFlavor);
  1665:       } catch (Exception e) {
  1666:         return;
  1667:       }
  1668:       //選択範囲の文字列を置換する
  1669:       trmBoard.replaceRange (string, trmBoard.getSelectionStart (), trmBoard.getSelectionEnd ());
  1670:     }
  1671:   }  //trmPaste()
  1672: 
  1673:   //trmSelectAll ()
  1674:   //  すべて選択
  1675:   public static void trmSelectAll () {
  1676:     if (XEiJ.clpClipboard != null) {
  1677:       //すべて選択する
  1678:       trmBoard.selectAll ();
  1679:     }
  1680:   }  //trmSelectAll()
  1681: 
  1682:   //trmStart ()
  1683:   public static void trmStart () {
  1684:     if (RestorableFrame.rfmGetOpened (Settings.SGS_TRM_FRAME_KEY)) {
  1685:       trmOpen ();
  1686:     }
  1687:   }  //trmStart()
  1688: 
  1689:   //trmOpen ()
  1690:   //  ターミナルウインドウを開く
  1691:   public static void trmOpen () {
  1692:     if (trmFrame == null) {
  1693:       trmMake ();
  1694:     }
  1695:     XEiJ.pnlExitFullScreen (false);
  1696:     trmFrame.setVisible (true);
  1697:   }  //trmOpen()
  1698: 
  1699:   //trmPrintSJIS (d)
  1700:   //  SJISで1バイト追加する
  1701:   //  SJISの1バイト目は繰り越して2バイト目が来たときに表示する
  1702:   public static void trmPrintSJIS (int d) {
  1703:     d &= 0xff;
  1704:     if (trmOutputSJIS1 != 0) {  //前回SJISの1バイト目を繰り越した
  1705:       if (0x40 <= d && d != 0x7f && d <= 0xfc) {  //SJISの2バイト目が来た
  1706:         int c = CharacterCode.chrSJISToChar[trmOutputSJIS1 << 8 | d];  //2バイトで変換する
  1707:         if (c != 0) {  //2バイトで変換できた
  1708:           trmPrintChar (c);  //1文字表示する
  1709:         } else {  //2バイトで変換できなかった
  1710:           //2バイトで変換できなかったがSJISの1バイト目と2バイト目であることはわかっているので2バイト分のコードを表示する
  1711:           trmPrintChar ('[');
  1712:           trmPrintChar (XEiJ.fmtHexc (trmOutputSJIS1 >> 4));
  1713:           trmPrintChar (XEiJ.fmtHexc (trmOutputSJIS1 & 15));
  1714:           trmPrintChar (XEiJ.fmtHexc (d >> 4));
  1715:           trmPrintChar (XEiJ.fmtHexc (d & 15));
  1716:           trmPrintChar (']');
  1717:         }
  1718:         trmOutputSJIS1 = 0;
  1719:         return;
  1720:       }
  1721:       //SJISの2バイト目が来なかった
  1722:       //前回繰り越したSJISの1バイト目を吐き出す
  1723:       trmPrintChar ('[');
  1724:       trmPrintChar (XEiJ.fmtHexc (trmOutputSJIS1 >> 4));
  1725:       trmPrintChar (XEiJ.fmtHexc (trmOutputSJIS1 & 15));
  1726:       trmPrintChar (']');
  1727:       trmOutputSJIS1 = 0;
  1728:     }
  1729:     if (0x81 <= d && d <= 0x9f || 0xe0 <= d && d <= 0xef) {  //SJISの1バイト目が来た
  1730:       trmOutputSJIS1 = d;  //次回に繰り越す
  1731:     } else {  //SJISの1バイト目が来なかった
  1732:       int c = CharacterCode.chrSJISToChar[d];  //1バイトで変換する
  1733:       if (c != 0) {  //1バイトで変換できた
  1734:         trmPrintChar (c);  //1文字表示する
  1735:       } else {  //1バイトで変換できなかった
  1736:         //1バイトで変換できなかったがSJISの1バイト目でないことはわかっているので1バイト分のコードを表示する
  1737:         trmPrintChar ('[');
  1738:         trmPrintChar (XEiJ.fmtHexc (d >> 4));
  1739:         trmPrintChar (XEiJ.fmtHexc (d & 15));
  1740:         trmPrintChar (']');
  1741:       }
  1742:     }
  1743:   }  //trmPrintSJIS(int)
  1744: 
  1745:   //trmPrintChar (c)
  1746:   //  末尾に1文字追加する
  1747:   public static void trmPrintChar (int c) {
  1748:     if (c == 0x08) {  //バックスペース
  1749:       if (trmOutputEnd > 0) {
  1750:         if (trmBoard != null) {
  1751:           trmBoard.replaceRange ("", trmOutputEnd - 1, trmOutputEnd);  //1文字削除
  1752:           trmOutputEnd--;
  1753:           trmBoard.setCaretPosition (trmOutputEnd);
  1754:         } else {
  1755:           trmOutputBuilder.delete (trmOutputEnd - 1, trmOutputEnd);  //1文字削除
  1756:           trmOutputEnd--;
  1757:         }
  1758:       }
  1759:     } else if (c >= 0x20 && c != 0x7f || c == 0x09 || c == 0x0a) {  //タブと改行以外の制御コードを除く
  1760:       if (trmBoard != null) {
  1761:         trmBoard.insert (String.valueOf ((char) c), trmOutputEnd);  //1文字追加
  1762:         trmOutputEnd++;
  1763:         if (trmOutputEnd >= TRM_CUT_OUTPUT_LENGTH) {
  1764:           trmBoard.replaceRange ("", 0, trmOutputEnd - TRM_MAX_OUTPUT_LENGTH);  //先頭を削って短くする
  1765:           trmOutputEnd = TRM_MAX_OUTPUT_LENGTH;
  1766:         }
  1767:         trmBoard.setCaretPosition (trmOutputEnd);
  1768:       } else {
  1769:         trmOutputBuilder.append ((char) c);  //1文字追加
  1770:         trmOutputEnd++;
  1771:         if (trmOutputEnd >= TRM_CUT_OUTPUT_LENGTH) {
  1772:           trmOutputBuilder.delete (0, trmOutputEnd - TRM_MAX_OUTPUT_LENGTH);  //先頭を削って短くする
  1773:           trmOutputEnd = TRM_MAX_OUTPUT_LENGTH;
  1774:         }
  1775:       }
  1776:     }
  1777:   }  //trmPrintChar(int)
  1778: 
  1779:   //trmPrint (s)
  1780:   //  末尾に文字列を追加する
  1781:   //  情報表示用
  1782:   //  制御コードを処理しないのでタブと改行以外の制御コードを含めないこと
  1783:   public static void trmPrint (String s) {
  1784:     if (s == null) {
  1785:       return;
  1786:     }
  1787:     if (trmFrame != null) {
  1788:       trmBoard.insert (s, trmOutputEnd);  //文字列追加
  1789:       trmOutputEnd += s.length ();
  1790:       if (trmOutputEnd >= TRM_CUT_OUTPUT_LENGTH) {
  1791:         trmBoard.replaceRange ("", 0, trmOutputEnd - TRM_MAX_OUTPUT_LENGTH);  //先頭を削って短くする
  1792:         trmOutputEnd = TRM_MAX_OUTPUT_LENGTH;
  1793:       }
  1794:       trmBoard.setCaretPosition (trmOutputEnd);
  1795:     } else {
  1796:       trmOutputBuilder.append (s);  //文字列追加
  1797:       trmOutputEnd += s.length ();
  1798:       if (trmOutputEnd >= TRM_CUT_OUTPUT_LENGTH) {
  1799:         trmOutputBuilder.delete (0, trmOutputEnd - TRM_MAX_OUTPUT_LENGTH);  //先頭を削って短くする
  1800:         trmOutputEnd = TRM_MAX_OUTPUT_LENGTH;
  1801:       }
  1802:     }
  1803:   }  //trmPrint(String)
  1804: 
  1805:   //trmPrintln (s)
  1806:   //  末尾に文字列と改行を追加する
  1807:   //  情報表示用
  1808:   //  制御コードを処理しないのでタブと改行以外の制御コードを含めないこと
  1809:   public static void trmPrintln (String s) {
  1810:     trmPrint (s);
  1811:     trmPrintChar ('\n');
  1812:   }  //trmPrintln(String)
  1813: 
  1814:   //trmEnter ()
  1815:   //  ENTERキーを処理する
  1816:   public static void trmEnter () {
  1817:     String text = trmBoard.getText ();  //テキスト全体
  1818:     int length = text.length ();  //テキスト全体の長さ
  1819:     int outputLineStart = text.lastIndexOf ('\n', trmOutputEnd - 1) + 1;  //出力の末尾の行の先頭。プロンプトの先頭
  1820:     int caretLineStart = text.lastIndexOf ('\n', trmBoard.getCaretPosition () - 1) + 1;  //キャレットがある行の先頭
  1821:     if (outputLineStart <= caretLineStart) {  //出力の末尾の行の先頭以降でENTERキーが押された
  1822:       trmBoard.replaceRange ("", trmOutputEnd, length);  //入力された文字列を一旦削除する
  1823:       trmSendString (text.substring (trmOutputEnd, length) + "\r");  //入力された文字列を送信する
  1824:     } else if (outputLineStart < trmOutputEnd) {  //出力の末尾の行の先頭よりも手前でENTERキーが押されて、出力の末尾の行にプロンプトがあるとき
  1825:       String prompt = text.substring (outputLineStart, trmOutputEnd);  //出力の末尾の行のプロンプト
  1826:       int caretLineEnd = text.indexOf ('\n', caretLineStart);  //キャレットがある行の末尾
  1827:       if (caretLineEnd == -1) {
  1828:         caretLineEnd = length;
  1829:       }
  1830:       String line = text.substring (caretLineStart, caretLineEnd);  //キャレットがある行
  1831:       int start = line.indexOf (prompt);  //キャレットがある行のプロンプトの先頭
  1832:       if (start >= 0) {  //キャレットがある行にプロンプトがあるとき
  1833:         trmOutputEnd = length;  //入力された文字列を無効化する
  1834:         if (text.charAt (trmOutputEnd - 1) != '\n') {  //改行で終わっていないとき
  1835:           trmBoard.insert ("\n", trmOutputEnd);  //末尾にENTERを追加する
  1836:           trmOutputEnd++;
  1837:           if (trmOutputEnd >= TRM_CUT_OUTPUT_LENGTH) {
  1838:             trmBoard.replaceRange ("", 0, trmOutputEnd - TRM_MAX_OUTPUT_LENGTH);  //先頭を削って短くする
  1839:             trmOutputEnd = TRM_MAX_OUTPUT_LENGTH;
  1840:           }
  1841:         }
  1842:         trmBoard.setCaretPosition (trmOutputEnd);
  1843:         trmSendString (line.substring (start + prompt.length ()) + "\r");  //プロンプトの後ろから行の末尾までを送信する
  1844:       }
  1845:     }
  1846:   }  //trmEnter()
  1847: 
  1848:   //trmSendString (s)
  1849:   //  文字列をSJISに変換してAUXまたはSerialPortへ送信する
  1850:   public static void trmSendString (String s) {
  1851:     int l = s.length ();
  1852:     if (l == 0) {
  1853:       return;
  1854:     }
  1855:     byte[] b = new byte[l * 2];
  1856:     int k = 0;
  1857:     for (int i = 0; i < l; i++) {
  1858:       int c = CharacterCode.chrCharToSJIS[s.charAt (i)];
  1859:       if (0x00ff < c) {
  1860:         b[k++] = (byte) (c >> 8);
  1861:       }
  1862:       b[k++] = (byte) c;
  1863:     }
  1864:     if (trmRowToCol[0] == 1) {  //Terminal→AUX。row2col
  1865:       int row = 0;
  1866:       int col = 1;
  1867:       trmConnectionArray[trmCols * row + col - 1].row2colQueue.write (b, 0, k);
  1868:     } else if (0 < trmColToRow[0]) {  //Terminal→SerialPort。col2row
  1869:       int row = trmColToRow[0];
  1870:       int col = 0;
  1871:       trmConnectionArray[trmCols * row + col - 1].col2rowQueue.write (b, 0, k);
  1872:     }
  1873:   }  //trmSendString
  1874: 
  1875: 
  1876: 
  1877:   //trmSendFile ()
  1878:   //  ファイル送信ボタンが押された
  1879:   public static void trmSendFile () {
  1880:     if (trmSendDialog == null) {
  1881:       ActionListener listener = new ActionListener () {
  1882:         @Override public void actionPerformed (ActionEvent ae) {
  1883:           switch (ae.getActionCommand ()) {
  1884:           case JFileChooser.APPROVE_SELECTION:
  1885:           case "Send":  //送信
  1886:             trmSendDialog.setVisible (false);
  1887:             File file = trmSendFileChooser.getSelectedFile ();
  1888:             if (file != null) {
  1889:               trmSendThread = new SendThread (file);
  1890:               trmSendThread.start ();
  1891:             }
  1892:             break;
  1893:           case JFileChooser.CANCEL_SELECTION:
  1894:           case "Cancel":  //キャンセル
  1895:             trmSendDialog.setVisible (false);
  1896:             break;
  1897:           }  //switch
  1898:         }  //actionPerformed
  1899:       };  //ActionListener
  1900:       trmSendFileChooser = new JFileChooser (new File ("a.txt"));
  1901:       trmSendFileChooser.setMultiSelectionEnabled (false);  //複数選択不可
  1902:       trmSendFileChooser.setControlButtonsAreShown (false);  //デフォルトのボタンを消す
  1903:       trmSendFileChooser.addActionListener (listener);
  1904:       trmSendDialog =
  1905:         Multilingual.mlnTitle (
  1906:           ComponentFactory.createModalDialog (
  1907:             trmFrame,
  1908:             "Send file",
  1909:             ComponentFactory.createBorderPanel (
  1910:               0, 0,
  1911:               ComponentFactory.createVerticalBox (
  1912:                 trmSendFileChooser,
  1913:                 ComponentFactory.createHorizontalBox (
  1914:                   Box.createHorizontalStrut (12),
  1915:                   Box.createHorizontalGlue (),
  1916:                   Multilingual.mlnText (
  1917:                     ComponentFactory.createLabel (
  1918:                       "Prepare COMMAND.X with CTTY AUX"),
  1919:                     "ja", "COMMAND.X を CTTY AUX で準備して"),
  1920:                   Box.createHorizontalStrut (12),
  1921:                   Multilingual.mlnText (
  1922:                     ComponentFactory.createButton ("Send", KeyEvent.VK_S, listener),
  1923:                     "ja", "送信"),
  1924:                   Box.createHorizontalStrut (12),
  1925:                   Multilingual.mlnText (
  1926:                     ComponentFactory.createButton ("Cancel", KeyEvent.VK_C, listener),
  1927:                     "ja", "キャンセル"),
  1928:                   Box.createHorizontalStrut (12)
  1929:                   ),  //createHorizontalBox
  1930:                 Box.createVerticalStrut (12)
  1931:                 )  //createVerticalBox
  1932:               )  //createBorderPanel
  1933:             ),  //createModalDialog
  1934:           "ja", "ファイル送信");  //mlnTitle
  1935:     }  //if
  1936:     XEiJ.pnlExitFullScreen (true);
  1937:     trmSendDialog.setVisible (true);
  1938:   }  //trmSendFile
  1939: 
  1940:   //class SendThread
  1941:   //  送信スレッド
  1942:   //  ファイルをa.rに変換する
  1943:   //    a.rは実行ファイルだが終端の$1A以外に$00~$1Fを含まない
  1944:   //  a.rをcopy aux a.rとa.rで挟んで送信する
  1945:   //  X68000でa.rが作られて実行されてファイルが復元される
  1946:   static class SendThread extends Thread {
  1947:     File file;
  1948:     SendThread (File file) {
  1949:       this.file = file;
  1950:     }
  1951:     @Override public void run () {
  1952:       trmSendProcess (file);
  1953:     }
  1954:   }  //class SendThread
  1955: 
  1956:   //buf = load (file)
  1957:   //  ファイルを読み込む
  1958:   static byte[] load (File file) {
  1959:     if (!file.isFile ()) {
  1960:       return null;
  1961:     }
  1962:     int len = (int) file.length ();
  1963:     if (len == 0) {
  1964:       return null;
  1965:     }
  1966:     byte[] buf = new byte[len];
  1967:     try (BufferedInputStream bis = new BufferedInputStream (new FileInputStream (file))) {
  1968:       if (bis.read (buf) != len) {
  1969:         return null;
  1970:       }
  1971:     } catch (IOException ioe) {
  1972:       return null;
  1973:     }
  1974:     return buf;
  1975:   }  //load
  1976: 
  1977:   //class BitWriter
  1978:   //  ビットライタ
  1979:   static class BitWriter {
  1980: 
  1981:     byte[] buffer;  //バッファ
  1982:     int byteIndex;  //バイト書き込み位置=現在の長さ
  1983:     int bitIndex;  //ビット書き込み位置
  1984:     int bitCount;  //[bitIndex]の残りビット数。上位から書く。下位bitCountビットが空いている
  1985: 
  1986:     //new BitWriter ()
  1987:     //  コンストラクタ
  1988:     BitWriter () {
  1989:       buffer = new byte[16];
  1990:       byteIndex = 0;
  1991:       bitIndex = -1;
  1992:       bitCount = 0;
  1993:     }  //BitWriter
  1994: 
  1995:     //writeByte (data)
  1996:     //  バイト書き込み
  1997:     //  data  データ。下位8ビットだけ使う
  1998:     void writeByte (int data) {
  1999:       if (byteIndex == buffer.length) {  //bufferが満杯なので長さを2倍にする
  2000:         byte[] temporary = new byte[byteIndex * 2];
  2001:         System.arraycopy (buffer, 0,  //from
  2002:                           temporary, 0,  //to
  2003:                           byteIndex);  //length
  2004:         buffer = temporary;
  2005:       }
  2006:       buffer[byteIndex++] = (byte) data;
  2007:     }  //writeByte
  2008: 
  2009:     //writeBits (width, data)
  2010:     //  ビット書き込み
  2011:     //  width  ビット数。0~32
  2012:     //  data   データ。下位widthビットだけ使う
  2013:     void writeBits (int width, int data) {
  2014:       while (width != 0) {  //書き込む残りビット数
  2015:         if (bitCount == 0) {  //[bitIndex]が満杯なので新しい[bitIndex]を用意する
  2016:           if (byteIndex == buffer.length) {  //bufferが満杯なので長さを2倍にする
  2017:             byte[] temporary = new byte[byteIndex * 2];
  2018:             System.arraycopy (buffer, 0,  //from
  2019:                               temporary, 0,  //to
  2020:                               byteIndex);  //length
  2021:             buffer = temporary;
  2022:           }
  2023:           bitIndex = byteIndex;
  2024:           bitCount = 8;
  2025:           buffer[byteIndex++] = 0;
  2026:         }
  2027:         data &= (1 << width) - 1;  //dataのゴミを消す
  2028:         int n = Math.min (bitCount, width);  //今回書き込むビット数
  2029:         bitCount -= n;  //今回書き込んだ後の[bitIndex]の残りビット数
  2030:         width -= n;  //今回書き込んだ後の書き込む残りビット数
  2031:         buffer[bitIndex] |= (byte) ((data >>> width) << bitCount);  //符号なし右シフト
  2032:       }  //while
  2033:     }  //writeBits
  2034: 
  2035:     //getBuffer ()
  2036:     //  バッファを返す
  2037:     byte[] getBuffer () {
  2038:       return buffer;
  2039:     }  //getBuffer
  2040: 
  2041:     //getLength ()
  2042:     //  現在の長さを返す
  2043:     int getLength () {
  2044:       return byteIndex;
  2045:     }  //getLength
  2046: 
  2047:   }  //class BitWriter
  2048: 
  2049:   static class DIC {
  2050:     int ptr;  //開始位置
  2051:     int len;  //長さ
  2052:   }
  2053: 
  2054:   //outbuf = compress (inpbuf, dicbit)
  2055:   //  圧縮
  2056:   //  inpbuf  入力バッファ
  2057:   //  dicbit  辞書のページ数のビット数。1~15
  2058:   static byte[] compress (byte[] inpbuf, int dicbit) {
  2059:     int dicsiz = 1 << dicbit;  //辞書のページ数
  2060:     //辞書を初期化する
  2061:     DIC[] dicbuf = new DIC[dicsiz];  //辞書
  2062:     for (int pag = 0; pag < dicsiz; pag++) {
  2063:       dicbuf[pag] = new DIC ();
  2064:       dicbuf[pag].ptr = 0;
  2065:       dicbuf[pag].len = 0;
  2066:     }
  2067:     int dicent = 0;  //辞書エントリ
  2068:     //入力長さ
  2069:     int inplen = inpbuf.length;
  2070:     BitWriter bw = new BitWriter ();
  2071:     bw.writeBits (24, inplen);
  2072:     //辞書のページ数のビット数
  2073:     bw.writeBits (4, dicbit);
  2074:     //圧縮ループ
  2075:     int inpptr = 0;  //入力位置
  2076:     while (inpptr < inplen) {
  2077:       //辞書から探す
  2078:       int dicpag = -1;  //辞書にある単語のページ番号
  2079:       int dicptr = -1;  //辞書にある単語の開始位置
  2080:       int diclen = 0;  //辞書にある単語の長さ
  2081:       int fstchr = inpbuf[inpptr] & 255;  //1文字目
  2082:       for (int pag = 0; pag < dicsiz; pag++) {
  2083:         int len = dicbuf[pag].len;  //辞書にある単語の長さ
  2084:         if (diclen < len &&  //これまでより長い
  2085:             inpptr + len + 1 <= inplen) {  //1文字伸ばしてもはみ出さない
  2086:           int ptr = dicbuf[pag].ptr;  //辞書にある単語の開始位置
  2087:         cmp:
  2088:           if (fstchr == (inpbuf[ptr] & 255)) {
  2089:             for (int i = 1; i < len; i++) {
  2090:               if (inpbuf[inpptr + i] != inpbuf[ptr + i]) {
  2091:                 break cmp;
  2092:               }
  2093:             }
  2094:             dicpag = pag;
  2095:             dicptr = ptr;
  2096:             diclen = len;
  2097:           }
  2098:         }
  2099:       }  //for pag
  2100:       if (diclen == 0) {  //辞書にない
  2101:         bw.writeBits (1, 0);
  2102:       } else {  //辞書にある
  2103:         bw.writeBits (1, 1);
  2104:         bw.writeBits (dicbit, dicpag);
  2105:       }
  2106:       int chr = inpbuf[inpptr + diclen] & 255;  //今回の文字
  2107:       //文字を出力する
  2108:       bw.writeByte (chr);
  2109:       //1文字伸ばす
  2110:       diclen++;
  2111:       //新しい単語を辞書に登録する
  2112:       dicbuf[dicent].ptr = inpptr;
  2113:       dicbuf[dicent].len = diclen;
  2114:       dicent++;
  2115:       if (dicent == dicsiz) {
  2116:         dicent = 0;
  2117:       }
  2118:       //次の文字へ
  2119:       inpptr += diclen;
  2120:     }  //while
  2121:     //出力バッファを返す
  2122:     byte[] outbuf = bw.getBuffer ();
  2123:     int outlen = bw.getLength ();
  2124:     if (outbuf.length != outlen) {
  2125:       byte[] tmpbuf = new byte[outlen];
  2126:       System.arraycopy (outbuf, 0, tmpbuf, 0, outlen);
  2127:       outbuf = tmpbuf;
  2128:     }
  2129:     return outbuf;
  2130:   }  //compress
  2131: 
  2132:   //dst = encode (src)
  2133:   //  エンコード
  2134:   //    Pxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  2135:   //    Qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  2136:   //    Rxxxxxxxxxxxxxxx
  2137:   //      ↓
  2138:   //    0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  2139:   //    0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  2140:   //    0xxxxxxxxxxxxxxxPQR0000000000000
  2141:   public static byte[] encode (byte[] src) {
  2142:     int r = src.length;
  2143:     int q = r / 62;
  2144:     r -= 62 * q;
  2145:     if (r != 0) {  //62で割り切れないとき
  2146:       r = ((r + 2 + 3) & -4) - 2;  //余りを4*n+2に切り上げる
  2147:       if (src.length < 62 * q + r) {
  2148:         byte[] tmp = new byte[62 * q + r];
  2149:         System.arraycopy (src, 0, tmp, 0, src.length);
  2150:         src = tmp;
  2151:       }
  2152:     }
  2153:     byte[] dst = new byte[64 * q + (r == 0 ? 0 : r + 2)];
  2154:     int[] w = new int[16];
  2155:     for (int i = 0; 64 * i < dst.length; i++) {  //ブロックの番号
  2156:       int n = Math.min (64, dst.length - 64 * i) / 4;  //ブロックに含まれるintの数
  2157:       //intを集める
  2158:       for (int k = 0; k < n - 1; k++) {
  2159:         w[k] = ((src[62 * i + 4 * k + 0] & 255) << 24 |
  2160:                 (src[62 * i + 4 * k + 1] & 255) << 16 |
  2161:                 (src[62 * i + 4 * k + 2] & 255) << 8 |
  2162:                 (src[62 * i + 4 * k + 3] & 255));
  2163:       }
  2164:       w[n - 1] = ((src[62 * i + 4 * (n - 1) + 0] & 255) << 24 |
  2165:                   (src[62 * i + 4 * (n - 1) + 1] & 255) << 16);
  2166:       //intの最上位ビットを最後のintの下位2バイトに移して31ビット整数にする
  2167:       for (int k = 0; k < n; k++) {
  2168:         if ((w[k] & 0x80000000) != 0) {
  2169:           w[k] &= 0x7fffffff;
  2170:           w[n - 1] |= 1 << (15 - k);
  2171:         }
  2172:       }
  2173:       //31ビット整数を4桁の224進数に変換して各桁に32を加えて出力する
  2174:       for (int k = 0; k < n; k++) {
  2175:         int t3 = w[k];
  2176:         int t2 = t3 / 224;
  2177:         t3 -= 224 * t2;
  2178:         int t1 = t2 / 224;
  2179:         t2 -= 224 * t1;
  2180:         int t0 = t1 / 224;
  2181:         t1 -= 224 * t0;
  2182:         dst[64 * i + 4 * k + 0] = (byte) (32 + t0);
  2183:         dst[64 * i + 4 * k + 1] = (byte) (32 + t1);
  2184:         dst[64 * i + 4 * k + 2] = (byte) (32 + t2);
  2185:         dst[64 * i + 4 * k + 3] = (byte) (32 + t3);
  2186:       }
  2187:     }
  2188:     return dst;
  2189:   }  //encode
  2190: 
  2191:   //trmSendProcess (file)
  2192:   //  送信処理
  2193:   public static void trmSendProcess (File file) {
  2194:     //ファイルを読み込む
  2195:     byte[] fileData = load (file);  //本体
  2196:     if (fileData == null) {
  2197:       return;
  2198:     }
  2199:     if (0x00ffff00 < fileData.length) {
  2200:       return;
  2201:     }
  2202:     //------------------------------------------------
  2203:     //step3_data
  2204:     //byte[] adotr3 = load ("adotr3.r");
  2205:     byte[] step3Data = new byte[adotr3.length +  //adotr3
  2206:                                 24 +  //ファイル名
  2207:                                 4 +  //日時
  2208:                                 4 +  //本体のCRC32
  2209:                                 4 +  //本体の長さ
  2210:                                 fileData.length];  //本体
  2211:     int index3 = 0;
  2212:     //adotr3
  2213:     System.arraycopy (adotr3, 0,
  2214:                       step3Data, index3,
  2215:                       adotr3.length);
  2216:     index3 += adotr3.length;
  2217:     //ファイル名
  2218:     //  ・SJISに変換できない
  2219:     //  ・SJISに変換できたが、Human68kの標準のファイル名に使えない文字がある
  2220:     //  ・SJISに変換できたが、/^[^\.]{1,18}(?:\.[^\.]{1,3})?$/にマッチしない
  2221:     //  はすべてエラー
  2222:     String name = file.getName ();
  2223:     {
  2224:       byte[] b = new byte[2 * name.length ()];
  2225:       int k = 0;
  2226:       int p = -1;
  2227:       for (int i = 0; i < name.length (); i++) {
  2228:         int c = CharacterCode.chrCharToSJIS[name.charAt (i)];
  2229:         if (c == 0) {  //変換できない文字がある
  2230:           return;
  2231:         }
  2232:         if (c <= ' ' || c == ':' || c == '*' || c == '?' ||
  2233:             c == '\\' || c == '/' || (c == '-' && k == 0) ||
  2234:             c == '"' || c == '\'' || c == '+' || c == ';' ||
  2235:             c == '<' || c == '=' || c == '>' ||
  2236:             c == '[' || c == ']' || c == '|') {  //Human68kの標準のファイル名に使えない文字がある
  2237:           return;
  2238:         }
  2239:         if (c == '.') {  //'.'
  2240:           if (p < 0) {  //初回
  2241:             p = k;
  2242:           } else {  //'.'が2つある
  2243:             return;
  2244:           }
  2245:         }
  2246:         if (0x00ff < c) {  //2バイト
  2247:           b[k++] = (byte) (c >> 8);
  2248:         }
  2249:         b[k++] = (byte) c;
  2250:         if (p < 0 ? 18 < k : p + 1 + 3 < k) {  //18+3文字に収まっていない
  2251:           return;
  2252:         }
  2253:       }
  2254:       for (int i = 0; i < k; i++) {
  2255:         step3Data[index3++] = b[i];
  2256:       }
  2257:       for (int i = k; i < 24; i++) {
  2258:         step3Data[index3++] = 0;
  2259:       }
  2260:     }
  2261:     //日時
  2262:     {
  2263:       long dttm = DnT.dntDttmCmil (file.lastModified () + RP5C15.rtcCmilGap);  //西暦年<<42|月<<38|月通日<<32|時<<22|分<<16|秒<<10|ミリ秒。FCBの日時はRTCの日時なのでオフセットを加える
  2264:       int date = DnT.dntYearDttm (dttm) - 1980 << 9 | DnT.dntMontDttm (dttm) << 5 | DnT.dntMdayDttm (dttm);  //(西暦年-1980)<<9|月<<5|月通日
  2265:       int time = DnT.dntHourDttm (dttm) << 11 | DnT.dntMinuDttm (dttm) << 5 | DnT.dntSecoDttm (dttm) >> 1;  //時<<11|分<<5|秒/2
  2266:       step3Data[index3++] = (byte) (date >> 8);
  2267:       step3Data[index3++] = (byte) date;
  2268:       step3Data[index3++] = (byte) (time >> 8);
  2269:       step3Data[index3++] = (byte) time;
  2270:     }
  2271:     //本体のCRC32
  2272:     {
  2273:       CRC32 crc32 = new CRC32 ();
  2274:       crc32.update (fileData, 0, fileData.length);
  2275:       int t = (int) crc32.getValue ();
  2276:       step3Data[index3++] = (byte) (t >> 24);
  2277:       step3Data[index3++] = (byte) (t >> 16);
  2278:       step3Data[index3++] = (byte) (t >> 8);
  2279:       step3Data[index3++] = (byte) (t);
  2280:     }
  2281:     //本体の長さ
  2282:     {
  2283:       int t = fileData.length;
  2284:       step3Data[index3++] = (byte) (t >> 24);
  2285:       step3Data[index3++] = (byte) (t >> 16);
  2286:       step3Data[index3++] = (byte) (t >> 8);
  2287:       step3Data[index3++] = (byte) (t);
  2288:     }
  2289:     //本体
  2290:     System.arraycopy (fileData, 0,
  2291:                       step3Data, index3,
  2292:                       fileData.length);
  2293:     index3 += fileData.length;
  2294:     //------------------------------------------------
  2295:     //step2_data
  2296:     //byte[] adotr2 = load ("adotr2.r");
  2297:     byte[] step3DataCompressed = null;
  2298:     int dicbit = 0;
  2299:     //辞書のサイズのビット数
  2300:     //  圧縮しにくいときは小さい方がよいが縮まらず捨てられるビット数を試しても意味がない
  2301:     //  圧縮しやすいときは大きい方がよいが圧縮に時間がかかる
  2302:     //  9に固定してもよい
  2303:     for (int i = 8; i <= 10; i++) {
  2304:       byte[] t = compress (step3Data, i);
  2305:       if (step3DataCompressed == null ||
  2306:           t.length < step3DataCompressed.length) {
  2307:         step3DataCompressed = t;
  2308:         dicbit = i;
  2309:       }
  2310:     }
  2311:     byte[] step2Data = new byte[adotr2.length + step3DataCompressed.length];
  2312:     int index2 = 0;
  2313:     //adotr2
  2314:     System.arraycopy (adotr2, 0,
  2315:                       step2Data, index2,
  2316:                       adotr2.length);
  2317:     index2 += adotr2.length;
  2318:     //本体
  2319:     System.arraycopy (step3DataCompressed, 0,
  2320:                       step2Data, index2,
  2321:                       step3DataCompressed.length);
  2322:     index2 += step3DataCompressed.length;
  2323:     //------------------------------------------------
  2324:     //step1_data
  2325:     //byte[] adotr1 = load ("adotr1.r");
  2326:     byte[] step3DataEncoded = encode (step3Data);  //圧縮なし
  2327:     byte[] step2DataEncoded = encode (step2Data);  //圧縮あり
  2328:     byte[] step1Data;
  2329:     if (step3DataEncoded.length <= step2DataEncoded.length) {  //圧縮効果なし
  2330:       step1Data = new byte[adotr1.length + step3DataEncoded.length + 1];
  2331:       int index1 = 0;
  2332:       System.arraycopy (adotr1, 0,
  2333:                         step1Data, index1,
  2334:                         adotr1.length);
  2335:       index1 += adotr1.length;
  2336:       System.arraycopy (step3DataEncoded, 0,
  2337:                         step1Data, index1,
  2338:                         step3DataEncoded.length);
  2339:       index1 += step3DataEncoded.length;
  2340:       step1Data[index1++] = 0x1a;
  2341:       if (false) {
  2342:         System.out.printf ("                 original: %d bytes (%.1f%%)\n",
  2343:                            fileData.length,
  2344:                            100.0);
  2345:         System.out.printf ("   generator concatenated: %d bytes (%.1f%%)\n",
  2346:                            step3Data.length,
  2347:                            100.0 * (double) step3Data.length / (double) fileData.length);
  2348:         System.out.printf ("                  encoded: %d bytes (%.1f%%)\n",
  2349:                            step3DataEncoded.length,
  2350:                            100.0 * (double) step3DataEncoded.length / (double) fileData.length);
  2351:         System.out.printf ("     decoder concatenated: %d bytes (%.1f%%)\n",
  2352:                            step1Data.length,
  2353:                            100.0 * (double) step1Data.length / (double) fileData.length);
  2354:       }
  2355:     } else {  //圧縮効果あり
  2356:       step1Data = new byte[adotr1.length + step2DataEncoded.length + 1];
  2357:       int index1 = 0;
  2358:       System.arraycopy (adotr1, 0,
  2359:                         step1Data, index1,
  2360:                         adotr1.length);
  2361:       index1 += adotr1.length;
  2362:       System.arraycopy (step2DataEncoded, 0,
  2363:                         step1Data, index1,
  2364:                         step2DataEncoded.length);
  2365:       index1 += step2DataEncoded.length;
  2366:       step1Data[index1++] = 0x1a;
  2367:       if (false) {
  2368:         System.out.printf ("                 original: %d bytes (%.1f%%)\n",
  2369:                            fileData.length,
  2370:                            100.0);
  2371:         System.out.printf ("   generator concatenated: %d bytes (%.1f%%)\n",
  2372:                            step3Data.length,
  2373:                            100.0 * (double) step3Data.length / (double) fileData.length);
  2374:         System.out.printf ("               compressed: %d bytes (%.1f%%) (%d bits dictionary)\n",
  2375:                            step3DataCompressed.length,
  2376:                            100.0 * (double) step3DataCompressed.length / (double) fileData.length,
  2377:                            dicbit);
  2378:         System.out.printf ("decompressor concatenated: %d bytes (%.1f%%)\n",
  2379:                            step2Data.length,
  2380:                            100.0 * (double) step2Data.length / (double) fileData.length);
  2381:         System.out.printf ("                  encoded: %d bytes (%.1f%%)\n",
  2382:                            step2DataEncoded.length,
  2383:                            100.0 * (double) step2DataEncoded.length / (double) fileData.length);
  2384:         System.out.printf ("     decoder concatenated: %d bytes (%.1f%%)\n",
  2385:                            step1Data.length,
  2386:                            100.0 * (double) step1Data.length / (double) fileData.length);
  2387:       }
  2388:     }
  2389:     if (false) {
  2390:       int l = step1Data.length;
  2391:       System.out.printf ("step1Data %d bytes\n", l);
  2392:       for (int i = 0; i < l; i++) {
  2393:         if ((i & 15) == 0) {
  2394:           System.out.printf ("%08x", i);
  2395:         }
  2396:         System.out.printf (" %02x", step1Data[i] & 255);
  2397:         if ((i & 15) == 15) {
  2398:           System.out.println ();
  2399:         }
  2400:       }
  2401:       if ((l & 15) != 0) {
  2402:         System.out.println ();
  2403:       }
  2404:     }
  2405:     //copy aux a.rとa.rで挟んで送信する
  2406:     trmSendString (String.format (
  2407:       "rem a.r/%s %d/%d %.1f%%\rcopy aux a.r\r",
  2408:       name, step1Data.length, fileData.length,
  2409:       100.0 * (double) step1Data.length / (double) fileData.length));
  2410:     try {
  2411:       Thread.sleep (1000);
  2412:     } catch (InterruptedException ie) {
  2413:     }
  2414:     if (trmRowToCol[0] == 1) {  //Terminal→AUX。row2col
  2415:       int row = 0;
  2416:       int col = 1;
  2417:       trmConnectionArray[trmCols * row + col - 1].row2colQueue.write (step1Data, 0, step1Data.length);
  2418:     } else if (0 < trmColToRow[0]) {  //Terminal→SerialPort。col2row
  2419:       int row = trmColToRow[0];
  2420:       int col = 0;
  2421:       trmConnectionArray[trmCols * row + col - 1].col2rowQueue.write (step1Data, 0, step1Data.length);
  2422:     }
  2423:     try {
  2424:       Thread.sleep (1000);
  2425:     } catch (InterruptedException ie) {
  2426:     }
  2427:     trmSendString ("a.r\r");
  2428:   }  //trmSendProcess
  2429: 
  2430:   //perl misc/ftob.pl adotr1 misc/adotr1.r
  2431:   public static final byte[] adotr1 = "B\202G\373 h\324\211\304|\377\374(B\"Kx@\330\213B\200\320\214\220\204\223\201\300\201\330\200$I\"<\337\337\337\340\322\233B\200v( @\347\210\220\210\353\210\341\231\320\201Q\301\220\201\342Kd\354\"\300\267\304e\3322!B\200\322A\342\220\321\232\265\311e\364\267\314e\266A\372\377\370t\370N\360\" BAVAp\254NO".getBytes (XEiJ.ISO_8859_1);
  2432:   //perl misc/ftob.pl adotr2 misc/adotr2.r
  2433:   public static final byte[] adotr2 = "p\1\320\211\300|\377\376\"@|\0~\0E\372\0\372r\30a\0\0\322&\0\326\211p\1\320\203\300|\377\376*@r\4a\0\0\276g\0\0\246\30\0z\b\351\255\332\215\377\201\"\5\222\200/\1/\0\377JP\217J\200ktp\0 M \300 \300\261\305e\370(I,M\265\311dvr\1a\0\0\206f\b,\314p\1,\300`0\22\4av\347\210 u\b\0 5\b\4gVR\200\"\0\322\214\262\203bL,\314,\300U\200H@H@\30\330Q\310\377\374H@Q\310\377\364\30\332\275\305f\2,M\271\303e\260p\372N\373\2\16r\3p\254NO IN\320\377\t\377\0Hz\0\4`\366out of memory\r\n\0Hz\0\4`\340data error\r\n\0\0p\0J\7f\4\34\32P\7\24\1\264\7c\2\24\7\20\6\345\250\345.\236\2\222\2f\344\340\210Nu".getBytes (XEiJ.ISO_8859_1);
  2434:   //perl misc/ftob.pl adotr3 misc/adotr3.r
  2435:   public static final byte[] adotr3 = "E\372\1\2&*\0 G\352\0$ \13\320\203R\200\300|\377\376(@K\354\4\0\377\201\"\r\222\200/\1/\0\377JP\217J\200k\0\0\230 Lr\0 \1t\7\342\210d\6\n\200\355\270\203 Q\312\377\364 \300R\1d\350\"Lp\0\"\3 KF\200`\22HAt\0\24\30\261\2\340\210\345J$1(\0\265\200Q\311\377\356HAQ\311\377\346F\200\260\252\0\34f`?<\0 /\n\377<\\\217J\200kd/\3/\13?\0\377@\"\0/j\0\30\0\2\377\207\377>O\357\0\n\262\203g\b/\n\377AX\217`@/\n\377\tHz\0\6\377\t\377\0 created\r\n\0\0Hz\0\4`\352out of memory\r\n\0Hz\0\4`\324crc error\r\n\0Hz\0\4`\302cannot write\r\n\0\0".getBytes (XEiJ.ISO_8859_1);
  2436: 
  2437: }  //class RS232CTerminal