Settings.java
     1: //========================================================================================
     2: //  Settings.java
     3: //    en:Settings
     4: //    ja:設定
     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: //----------------------------------------------------------------------------------------
    14: //
    15: //  設定ファイル
    16: //    XEiJの動作環境を指示する「設定」が1つ以上記述されているファイル
    17: //    場所
    18: //      Windowsのとき(os.nameがWindowsを含むとき)
    19: //        %APPDATA%\XEiJ\XEiJ.ini    C:\Users\%USERNAME%\AppData\Roaming\XEiJ\XEiJ.iniなど。%~%は環境変数
    20: //      その他
    21: //        ~/XEiJ.ini                 ~はuser.home
    22: //        ./XEiJ.ini                 .はuser.dir
    23: //    内容
    24: //      [タイトルA]
    25: //      キーワードA1=値A1
    26: //      キーワードA2=値A2
    27: //          :
    28: //      [タイトルB]
    29: //      キーワードB1=値B1
    30: //      キーワードB2=値B2
    31: //          :
    32: //
    33: //  設定の種類
    34: //    設定にはデフォルト設定とユーザ設定がある
    35: //    デフォルト設定(default)
    36: //      タイトルがdefaultの設定
    37: //      プログラムに埋め込まれている定数にhomeとlangを加えたもの
    38: //      デフォルト設定にないキーワードは指定できない
    39: //      デフォルト設定と同じ値のパラメータは保存されない
    40: //    ユーザ設定
    41: //      タイトルがdefault以外の設定
    42: //      saveonexit=onのとき設定ファイルに保存される
    43: //      無題設定と仮設定とその他のユーザ設定がある
    44: //      無題設定(notitle)
    45: //        [notitle]のユーザ設定
    46: //        起動時にユーザ設定が1つもないときデフォルト設定が無題設定にコピーされて現在の設定になる
    47: //        自動で作られることを除けば普通のユーザ設定
    48: //        設定ファイルの先頭に配置される
    49: //        設定ファイルの最初の設定が無題設定のとき[notitle]を省略できる
    50: //      仮設定(temporary)
    51: //        [temporary]のユーザ設定
    52: //        -config=defaultを指定するとデフォルト設定が仮設定にコピーされて現在の設定になる
    53: //        -config=defaultを指定するとデフォルト設定に巻き戻されるので常用は避けた方が良い
    54: //        設定ファイルの無題設定の後に配置される
    55: //      その他のユーザ設定
    56: //        設定ファイルの無題設定と仮設定の後に配置される
    57: //        その他のユーザ設定の順序はユーザが自由に並べ替えることができる
    58: //
    59: //  パラメータ
    60: //    設定ファイル
    61: //      キーワード=値
    62: //      1行に1組ずつ書く
    63: //      キーワードと値の前後に余分な空白があってはならない
    64: //    コマンドライン
    65: //      -キーワード=値
    66: //      1引数に1組ずつ書く
    67: //      キーワードと値の前後に余分な空白があってはならない
    68: //    キーワード
    69: //      英小文字で始まり英小文字と数字で構成される文字列
    70: //    値
    71: //      空白以外で始まり空白以外で終わる文字列
    72: //      空列または空白を含む文字列は"~"で囲む
    73: //      デフォルト設定で値がonまたはoffのパラメータはonまたはoffだけ指定できる
    74: //      デフォルト設定で値がyesまたはnoのパラメータはyesまたはnoだけ指定できる
    75: //      デフォルト設定で値が符号と数字だけのパラメータは符号と数字だけ指定できる
    76: //      キーワードがdataで終わるパラメータ
    77: //        値がgzip+base64で圧縮されている
    78: //        値が非常に大きい場合があり、原則として文字列の状態では編集できない
    79: //        バイナリデータとして編集する機能を付けてもよい
    80: //
    81: //  デフォルト設定で環境に合わせて作られるパラメータ
    82: //    home=path
    83: //      ホームディレクトリ。末尾の区切り文字は含まない
    84: //      ファイルチューザーが最初に開くディレクトリ
    85: //    lang=en
    86: //    lang=ja
    87: //      表示言語
    88: //
    89: //  設定ファイルの特別なパラメータ
    90: //    [タイトル]
    91: //      新しい設定の開始。設定のタイトル
    92: //      []、[default]は不可
    93: //      -config=タイトルで設定を選択するときに使う
    94: //      タイトルが同じ設定が複数存在してはならない
    95: //      改行を含むことはできない
    96: //      [、]を除く文字は何でも書けるが、"、'、`、$などはコマンドラインで選択するときに書きにくいので避けた方が良い
    97: //      設定ファイルの最初の設定が無題設定のとき[notitle]を省略できる
    98: //    current=yes
    99: //    current=no
   100: //      現在の設定かどうか
   101: //      current=yesのユーザ設定が1つだけ存在する
   102: //    description=説明
   103: //      設定の説明
   104: //
   105: //  コマンドラインの特別なパラメータ
   106: //    -config=~はコマンドラインの最後に書いても他のパラメータよりも優先して解釈される
   107: //    -config=default
   108: //      デフォルト設定が仮設定にコピーされて現在の設定になる
   109: //      一時的にデフォルト設定で起動したいときに使う
   110: //      他のパラメータはデフォルト設定がコピーされた仮設定に上書きされる
   111: //      仮設定は-config=defaultを指定すると消えてしまうので、仮設定の常用は避けた方が良い
   112: //      仮設定以外の設定はcurrent=yesがcurrent=noになるものを除いて変化しない。すべての設定が初期化されるわけではない
   113: //    -config=タイトル
   114: //      タイトルで選択されたユーザ設定(無題設定、仮設定を含む)が現在の設定になる
   115: //      他のパラメータは選択されたユーザ設定に上書きされる
   116: //
   117: //  その他の特筆すべきパラメータ
   118: //    saveonexit=on
   119: //    saveonexit=off
   120: //      終了時に設定ファイルを更新するかどうか
   121: //      現在の設定がsaveonexit=offのとき終了時に設定ファイルが更新されない
   122: //      設定ファイルにエラーがあったとき
   123: //        設定ファイルを更新してしまうとエラーの箇所やそれ以降に書かれていた内容が消えてしまう恐れがある
   124: //        現在の設定がsaveonexit=onでも終了時に設定ファイルを更新せず手動で修正することを促す
   125: //
   126: //----------------------------------------------------------------------------------------
   127: 
   128: package xeij;
   129: 
   130: import java.awt.*;  //BasicStroke,BorderLayout,BoxLayout,Color,Component,Container,Cursor,Desktop,Dimension,Font,Frame,Graphics,Graphics2D,GraphicsDevice,GraphicsEnvironment,GridLayout,Image,Insets,Paint,Point,Rectangle,RenderingHints,Robot,Shape,Stroke,TexturePaint,Toolkit
   131: import java.awt.event.*;  //ActionEvent,ActionListener,ComponentAdapter,ComponentEvent,ComponentListener,FocusAdapter,FocusEvent,FocusListener,InputEvent,KeyAdapter,KeyEvent,KeyListener,MouseAdapter,MouseEvent,MouseListener,MouseMotionAdapter,MouseWheelEvent,WindowAdapter,WindowEvent,WindowListener,WindowStateListener
   132: import java.io.*;  //BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,File,FileInputStream,FileNotFoundException,FileReader,InputStream,InputStreamReader,IOException,OutputStreamWriter,RandomAccessFile
   133: import java.lang.*;  //Boolean,Character,Class,Comparable,Double,Exception,Float,IllegalArgumentException,Integer,Long,Math,Number,Object,Runnable,SecurityException,String,StringBuilder,System
   134: import java.net.*;  //MalformedURLException,URI,URL
   135: import java.util.*;  //ArrayList,Arrays,Calendar,GregorianCalendar,HashMap,Map,Map.Entry,Timer,TimerTask,TreeMap
   136: import java.util.regex.*;  //Matcher,Pattern
   137: //import javax.jnlp.*;  //BasicService,PersistenceService,ServiceManager,UnavailableServiceException
   138: import javax.swing.*;  //AbstractSpinnerModel,Box,ButtonGroup,DefaultListModel,ImageIcon,JButton,JCheckBox,JCheckBoxMenuItem,JDialog,JFileChooser,JFrame,JLabel,JList,JMenu,JMenuBar,JMenuItem,JPanel,JRadioButton,JScrollPane,JSpinner,JTextArea,JTextField,JTextPane,JViewport,ScrollPaneConstants,SpinnerListModel,SpinnerNumberModel,SwingConstants,SwingUtilities,UIManager,UIDefaults,UnsupportedLookAndFeelException
   139: import javax.swing.event.*;  //CaretListener,ChangeEvent,ChangeListener,DocumentEvent,DocumentListener,ListSelectionListener
   140: import netscape.javascript.*;  //JSException,JSObject。jfxrt.jarではなくplugin.jarを使うこと
   141: 
   142: public class Settings {
   143: 
   144:   //RestorableFrameのキーの一覧
   145:   //  ウインドウの位置とサイズ(~rect)、状態(~stat)、開くかどうか(~open)のパラメータで使う
   146:   public static final String SGS_ACM_FRAME_KEY = "acm";  //ACM アドレス変換キャッシュモニタ
   147:   public static final String SGS_ATW_FRAME_KEY = "atw";  //ATW 論理空間モニタ
   148:   public static final String SGS_BFN_FRAME_KEY = "bfn";  //BFN ボタン機能
   149:   public static final String SGS_BLG_FRAME_KEY = "blg";  //BLG 分岐ログ
   150:   public static final String SGS_DBP_FRAME_KEY = "dbp";  //DBP データブレークポイント
   151:   public static final String SGS_DDP_FRAME_KEY = "ddp";  //DDP 逆アセンブルリスト
   152:   public static final String SGS_DGT_FRAME_KEY = "dgt";  //DGT コンソールウインドウ
   153:   public static final String SGS_DMP_FRAME_KEY = "dmp";  //DMP メモリダンプリスト
   154:   public static final String SGS_DRP_FRAME_KEY = "drp";  //DRP レジスタ
   155:   public static final String SGS_FNT_FRAME_KEY = "fnt";  //FNT フォントエディタ
   156:   public static final String SGS_FRM_FRAME_KEY = "frm";  //FRM メインウインドウ
   157:   public static final String SGS_GRS_FRAME_KEY = "grs";
   158:   public static final String SGS_GSA_FRAME_KEY = "gsa";
   159:   public static final String SGS_KBS_FRAME_KEY = "kbs";  //KBS キーボード設定
   160:   public static final String SGS_OLG_FRAME_KEY = "olg";  //OLG OPMログ
   161:   public static final String SGS_PAA_FRAME_KEY = "paa";  //PAA 物理空間モニタ
   162:   public static final String SGS_PFF_FRAME_KEY = "pff";  //PFF プロファイリング
   163:   public static final String SGS_PFV_FRAME_KEY = "pfv";  //PFV プログラムフロービジュアライザ
   164:   public static final String SGS_PPI_FRAME_KEY = "ppi";  //PPI ジョイスティック
   165:   public static final String SGS_PRN_FRAME_KEY = "prn";  //PRN プリンタ
   166:   public static final String SGS_RBP_FRAME_KEY = "rbp";  //RBP ラスタブレークポイント
   167:   public static final String SGS_RTL_FRAME_KEY = "rtl";  //RTL ルートポインタリスト
   168:   public static final String SGS_SMN_FRAME_KEY = "smn";  //SMN 音声モニタ
   169:   public static final String SGS_SMT_FRAME_KEY = "smt";  //SMT 表示モードテスト
   170:   public static final String SGS_SPV_FRAME_KEY = "spv";  //SPV スプライトパターンビュア
   171:   public static final String SGS_TRM_FRAME_KEY = "trm";  //TRM ターミナル
   172:   public static final String[] SGS_FRAME_KEYS = {
   173:     SGS_ACM_FRAME_KEY,
   174:     SGS_ATW_FRAME_KEY,
   175:     SGS_BFN_FRAME_KEY,
   176:     SGS_BLG_FRAME_KEY,
   177:     SGS_DBP_FRAME_KEY,
   178:     SGS_DDP_FRAME_KEY,
   179:     SGS_DGT_FRAME_KEY,
   180:     SGS_DMP_FRAME_KEY,
   181:     SGS_DRP_FRAME_KEY,
   182:     SGS_FNT_FRAME_KEY,
   183:     SGS_FRM_FRAME_KEY,
   184:     SGS_GRS_FRAME_KEY,
   185:     SGS_GSA_FRAME_KEY,
   186:     SGS_KBS_FRAME_KEY,
   187:     SGS_OLG_FRAME_KEY,
   188:     SGS_PAA_FRAME_KEY,
   189:     SGS_PFF_FRAME_KEY,
   190:     SGS_PFV_FRAME_KEY,
   191:     SGS_PPI_FRAME_KEY,
   192:     SGS_PRN_FRAME_KEY,
   193:     SGS_RBP_FRAME_KEY,
   194:     SGS_RTL_FRAME_KEY,
   195:     SGS_SMN_FRAME_KEY,
   196:     SGS_SMT_FRAME_KEY,
   197:     SGS_SPV_FRAME_KEY,
   198:     SGS_TRM_FRAME_KEY,
   199:   };
   200: 
   201:   //デフォルト設定のベース
   202:   //  キーワードと値の配列
   203:   //  キーワードはnullではない。値もnullではない
   204:   //  キーワードは""ではない。値が""のときは指定されていないものとみなす
   205:   //  デフォルト設定で値がonまたはoffのパラメータはonまたはoffだけ指定できる
   206:   //  デフォルト設定で値がyesまたはnoのパラメータはyesまたはnoだけ指定できる
   207:   //  デフォルト設定で値が符号と数字だけのパラメータは符号と数字だけ指定できる
   208:   //      キーワードがdataで終わるパラメータ
   209:   //        値がgzip+base64で圧縮されている
   210: 
   211:   public static final String SGS_DEFAULT_PARAMETERS = (
   212:     //PRG
   213:     "verbose;on;" +  //冗長表示(on/off)
   214:     //SGS
   215:     //"_;;" +  //この設定の設定名。ここには書かない
   216:     "saveonexit;on;" +  //終了時に設定を保存(on/off)
   217:     "xxxopen;on;" +  //開いていたウインドウを復元する(on/off)
   218:     "config;;" +  //復元する設定の設定名。パラメータで指定する。設定ファイルには出力しない
   219:     "lang;en;" +  //言語(en/ja)。初期値は動作環境の言語
   220:     "home;;" +  //ホームディレクトリ。初期値は動作環境のホームディレクトリ
   221:     "dir;;" +  //カレントディレクトリ。初期値は動作環境のカレントディレクトリ
   222:     //LNF
   223:     "hsb;240,240,240,70,50,30,0,50,100;" +  //配色
   224:     "hhssbb;none;" +  //配色(廃止)
   225:     "fontsize;14;" +  //文字の大きさ(10/12/14/16/18)
   226:     //KBD
   227:     "keyboard;standard;" +  //キーボードの種類(none,standard,compact)
   228:     "hidekeyboard;on;" +  //全画面表示のときキーボードを隠す(on/off)
   229:     "keymapus;off;" +  //USレイアウト(on/off)
   230:     "keymap;;" +  //Zキーボード以外のキーマップ
   231:     "zkeyboard;off;" +  //Zキーボード(on/off)
   232:     "zkeymap;;" +  //Zキーボードのキーマップ
   233:     //BFN
   234:     "f11key;fullscreen;" +  //F11 全画面表示の切り替え
   235:     "shiftf11key;maximized;" +  //Shift+F11 最大化の切り替え
   236:     "ctrlf11key;donothing;" +  //Ctrl+F11
   237:     "altf11key;donothing;" +  //Alt+F11
   238:     "f12key;seamless;" +  //F12 シームレスマウスの切り替え
   239:     "shiftf12key;donothing;" +  //Shift+F12
   240:     "ctrlf12key;donothing;" +  //Ctrl+F12
   241:     "altf12key;donothing;" +  //Alt+F12
   242:     "wheel;seamless;" +  //WheelButton シームレスマウスの切り替え
   243:     "shiftwheel;donothing;" +  //Shift+WheelButton
   244:     "ctrlwheel;donothing;" +  //Ctrl+WheelButton
   245:     "altwheel;donothing;" +  //Alt+WheelButton
   246:     "wheelup;trace1;" +  //WheelScrollUp トレース1回
   247:     "shiftwheelup;trace10;" +  //Shift+WheelScrollUp トレース10回
   248:     "ctrlwheelup;trace100;" +  //Ctrl+WheelScrollUp トレース100回
   249:     "altwheelup;donothing;" +  //Alt+WheelScrollUp
   250:     "wheeldown;step1;" +  //WheelScrollDown ステップ1回
   251:     "shiftwheeldown;step10;" +  //Shift+WheelScrollDown ステップ10回
   252:     "ctrlwheeldown;step100;" +  //Ctrl+WheelScrollDown ステップ100回
   253:     "altwheeldown;return;" +  //Alt+WheelScrollDown ステップアンティルリターン
   254:     "button4;donothing;" +  //Button4
   255:     "shiftbutton4;donothing;" +  //Shift+Button4
   256:     "ctrlbutton4;donothing;" +  //Ctrl+Button4
   257:     "altbutton4;donothing;" +  //Alt+Button4
   258:     "button5;donothing;" +  //Button5
   259:     "shiftbutton5;donothing;" +  //Shift+Button5
   260:     "ctrlbutton5;donothing;" +  //Ctrl+Button5
   261:     "altbutton5;donothing;" +  //Alt+Button5
   262:     "mousewheel;;" +
   263:     //GIF
   264:     "gifwaitingtime;;" +  //GIFアニメーション録画の待ち時間(s)
   265:     "gifrecordingtime;10;" +  //GIFアニメーション録画の録画時間(s)
   266:     "gifmagnification;100;" +  //GIFアニメーション録画の倍率(%)
   267:     "gifinterpolation;bilinear;" +  //アニメーション録画の補間アルゴリズム(nearest,bilinear,bicubic)
   268:     //PNL
   269:     "scaling;fitinwindow;" +  //画面スケーリングモード(fullscreen,maximized,fitinwindow,fixedscale)
   270:     "fixedscale;100;" +  //固定倍率
   271:     "aspectratio256x256;4:3;" +  //アスペクト比256x256
   272:     "aspectratio384x256;4:3;" +  //アスペクト比384x256
   273:     "aspectratio512x512;4:3;" +  //アスペクト比512x512
   274:     "aspectratio768x512;3:2;" +  //アスペクト比768x512
   275:     "interpolation;bilinear;" +  //補間アルゴリズム(nearest,bilinear,bicubic)
   276:     "refreshrate;none;" +  //ホストのリフレッシュレート
   277:     "adjustvsync;off;" +  //垂直同期周波数をホストのリフレッシュレートに合わせる(on/off)
   278:     //PNL
   279:     "usecanvas;on;" +  //キャンバスを使う(on/off)
   280:     "rotation;0;" +  //回転(0/1/2/3)
   281:     //MUS
   282:     "seamless;on;" +  //シームレス/エクスクルーシブ(on/off)
   283:     "followscroll;on;" +  //スクロールに追従する(on/off)
   284:     "ctrlright;off;" +  //Ctrlキー+左ボタンを右ボタンとみなす(on/off)
   285:     "edgeaccel;off;" +  //縁部加速
   286:     "mousespeed;;" +  //マウスカーソルの速度(0~40)
   287:     "hostspixelunits;off;" +  //ホストの画素単位で動く(on/off)
   288:     //TXC
   289:     "textcopyarea;display;" +  //範囲モード(display/c_width/vram/enclosed)
   290:     "textcopy;off;" +  //マウスで都度選択(on/off)
   291:     //MDL
   292:     "model;Hybrid;" +  //機種(Shodai|ACE|EXPERT|PRO|SUPER|XVI|Xellent30|Compact|Hybrid|X68030|030Compact|060turbo)
   293:     "mpu010;off;" +  //機種のMPUが68000のとき68010に変更する(on/off)
   294:     "mpu;none;" +  //MPUの種類(-1=機種に従う/0=MC68000/1=MC68010/3=MC68EC030/6=MC68060)
   295:     "clock;none;" +  //MPUの動作周波数(-1=機種に従う,1..1000,Shodai,ACE,EXPERT,PRO,SUPER,XVI,Xellent30,Compact,Hybrid,X68030,030Compact,060turbo,060turboPRO)
   296:     "mhz;200;" +  //任意の周波数(1~1000)。任意の負荷率がonのときの周波数のスピナーの値
   297:     "util;off;" +  //任意の負荷率(on/off)
   298:     "ratio;100;" +  //任意の負荷率(1~100)
   299:     "waitcycles;on;" +  //ウェイトサイクル(on/off)
   300:     //FPU
   301:     "copro0;2;" +  //マザーボードコプロセッサ。0=なし,1=MC68881,2=MC68882,7=フルスペック,+8=三倍精度
   302:     "copro1;2;" +  //拡張コプロセッサ#1。0=なし,1=MC68881,2=MC68882,7=フルスペック,+8=三倍精度
   303:     "copro2;2;" +  //拡張コプロセッサ#2。0=なし,1=MC68881,2=MC68882,7=フルスペック,+8=三倍精度
   304:     "onchipfpu;7;" +  //オンチップFPU。0=なし,6=MC68060,7=フルスペック,+8=三倍精度
   305:     //FPK
   306:     "fefunc;on;" +  //FEファンクション命令(on/off)
   307:     "rejectfloat;off;" +  //FLOATn.Xを組み込まない(on/off)
   308:     //BUS
   309:     "himem68000;off;" +  //060turboのハイメモリはX68000でも有効(on/off)
   310:     "highmemory;;" +  //X68030のハイメモリのサイズ(MB)(0/16)
   311:     "highmemorysave;off;" +  //X68030のハイメモリの内容を保存する(on/off)
   312:     "highmemorydata;;" +  //X68030のハイメモリの内容(gzip+base64)
   313:     "highmemory060turbo;off;" +  //060turboのローカルメモリを使う(on/off)
   314:     "localmemory;128;" +  //060turboのローカルメモリのサイズ(MB)(0/16/32/64/128/256)
   315:     "localmemorysave;off;" +  //060turboのローカルメモリの内容を保存する(on/off)
   316:     "localmemorydata;;" +  //060turboのローカルメモリの内容(gzip+base64)
   317:     "cutfc2pin;off;" +  //FC2ピンをカットする(on/off)
   318:     //MMR
   319:     "memory;12;" +  //メインメモリのサイズ(0/1/2/4/6/8/10/12)。0は既定値
   320:     "memorysave;on;" +  //メインメモリの内容を保存する(on/off)
   321:     "memorydata;;" +  //メインメモリの内容(gzip+base64)
   322:     //CON
   323:     "pastepipe;off;" +  //貼り付けパイプと制御パイプを使う(on/off)
   324:     "pipeencoding;sjis;" +  //貼り付けパイプと制御パイプの文字コード(sjis/utf8)
   325:     //CRT
   326:     "intermittent;0;" +  //間欠描画(0~4)
   327:     "stereoscopic;off;" +  //立体視(on/off)
   328:     "stereoscopicmethod;nakedeyecrossing;" +  //立体視の方法(nakedeyecrossing/nakedeyeparallel/sidebyside/topandbottom)
   329:     "extendedgraphic;off;" +  //拡張グラフィック画面(on/off)
   330:     "scanline;medium;" + //走査線エフェクト(off/weak/medium/strong/black)
   331:     "dotclock;,,;" +  //ドットクロックオシレータ(low,high,vga)
   332:     "eleventhbit;off;" +  //1024ドットノンインターレース(on/off)
   333:     "sphericalscrolling;off;" +  //球面スクロール(on/off)
   334:     "r00bit0zero;off;" +  //CRTC R00のビット0(on/off)
   335:     //SPR
   336:     "sprras;32;" +  //ラスタあたりのスプライトの枚数(0~/*2040*/1016)
   337:     "dblspr;off;" +  //256枚のスプライト(on/off)(廃止)
   338:     "numspr;128;" +  //スプライトの枚数(128/256/504/1016/* /2040*/)
   339:     "sprbank;off;" +  //4096個のパターン(on/off)
   340:     "spr768x512;off;" +  //768x512でスプライトを表示(on/off)
   341:     "spr512bg1;off;" +  //512x512でBG1を表示(on/off)
   342:     "sprrcscroll;off;" +  //行スクロールと列スクロール(on/off)
   343:     //SND
   344:     "sound;on;" +  //音声出力(on/off)
   345:     "volume;20;" +  //ボリューム(0~40)
   346:     "soundinterpolation;linear;" +  //音声補間(thinning/linear/constant-area/linear-area)
   347:     //OPM
   348:     "opmoutput;on;" +  //OPM出力
   349:     //PCM
   350:     "pcmoutput;on;" +  //PCM出力
   351:     "pcminterpolation;linear;" +  //PCM補間(constant/linear/hermite)
   352:     "pcmoscfreq;0;" +  //PCM原発振周波数(0=8MHz/4MHz,1=8MHz/16MHz)
   353:     //FDC
   354:     //  ユニットとヒストリは後で設定する
   355:     "fdreadonly;off;" +  //書き込み禁止(on/off)
   356:     "fdappreboot;off;" +  //ここから再起動(on/off)
   357:     //HDC
   358:     //  ユニットとヒストリは後で設定する
   359:     "sareadonly;off;" +  //書き込み禁止(on/off)
   360:     "saappreboot;off;" +  //ここから再起動(on/off)
   361:     //SPC
   362:     //  ユニットとヒストリは後で設定する
   363:     "screadonly;off;" +  //SCSI HDを書き込み禁止で開く(on/off)
   364:     "scappreboot;off;" +  //ここから再起動(on/off)
   365:     //HCD ホストCD-ROM
   366:     "hcddebug;off;" +  //デバッグ情報(on/off)
   367:     "hcdconnect;off;" +  //接続する(on/off)
   368:     "hcdscsiid;6;" +  //SCSI ID(0~15)
   369:     "hcdvolume;25;" +  //音量(0~100)
   370:     //HFS
   371:     //  ユニットとヒストリは後で設定する
   372:     "hfreadonly;off;" +  //書き込み禁止(on/off)
   373:     "hfappreboot;off;" +  //ここから再起動(on/off)
   374:     "utf8warning;off;" +  //HFSで開いたファイルがUTF-8のとき警告する
   375:     //PPI
   376:     "joykey;on;" +  //キーボードの一部をジョイスティックとみなす
   377:     "joyauto;on;" +  //ジョイスティックポートが連続的に読み出されている間だけ有効
   378:     "joyblock;on;" +  //ジョイスティック入力データとして処理されたキーボード入力データを取り除く
   379:     "normal2button1;;" +  //ノーマル2ボタンパッド#1の設定
   380:     "normal2button2;;" +  //ノーマル2ボタンパッド#2の設定
   381:     "megadrive3button1;;" +  //メガドラ3ボタンパッド#1の設定
   382:     "megadrive3button2;;" +  //メガドラ3ボタンパッド#2の設定
   383:     "megadrive6button1;;" +  //メガドラ6ボタンパッド#1の設定
   384:     "megadrive6button2;;" +  //メガドラ6ボタンパッド#2の設定
   385:     "cyberstickdigital1;;" +  //サイバースティック(デジタルモード)#1の設定
   386:     "cyberstickdigital2;;" +  //サイバースティック(デジタルモード)#2の設定
   387:     "cyberstickanalog1;;" +  //サイバースティック(アナログモード)#1の設定
   388:     "cyberstickanalog2;;" +  //サイバースティック(アナログモード)#2の設定
   389:     "shiromadokun1;;" +  //白窓君1の仕様
   390:     "shiromadokun2;;" +  //白窓君2の仕様
   391:     "joystick1;normal2button1;" +  //ジョイスティックポート1に接続するデバイス
   392:     "joystick2;normal2button2;" +  //ジョイスティックポート2に接続するデバイス
   393:     "xinput;off;" +  //XInput(on/off)
   394:     //EXS
   395:     "scsiex;off;" +  //on=拡張SCSIポートを有効にする
   396:     //SMR
   397:     "boot;default;" +  //起動デバイス(default/std/fdN/hdN/scN/hfN/rom$X/ram$X)
   398:     "keydly;-1;" +  //リピートディレイ(-1=既定/200+100*n)
   399:     "keyrep;-1;" +  //リピートインターバル(-1=既定/30+5*n^2)
   400:     "sram;none;" +  //SRAMイメージファイル名
   401:     "sramdata;;" +  //SRAMの内容(gzip+base64)
   402:     "sramsize;16;" +  //SRAMの容量(16/32/64)
   403:     "romdb;none;" +  //ROMデバッガ起動フラグ
   404:     "modifymemorysize;on;" +  //SRAMにあるメモリサイズを修正する
   405:     "srambuserror;off;" +  //SRAMへの書き込み時のバスエラー
   406:     //BNK
   407:     "bankdata;;" +  //バンクメモリの内容(gzip+base64)
   408:     //ROM
   409:     "rom;none;" +  //ROMのイメージファイル名
   410:     "cgrom;none;" + //CGROMのイメージファイル名
   411:     "rom30;none;" +  //X68030のIPLROMのイメージファイル名
   412:     "scsiinrom;none;" +  //SCSIINROMのイメージファイル名
   413:     "scsiexrom;none;" +  //SCSIEXROMのイメージファイル名
   414:     "iplrom;none;" +  //IPLROMのイメージファイル名
   415:     "x68000logo;none;" +  //X68000ロゴファイル名
   416:     "iplrom16style;7;" +  //IPLROM 1.6のメッセージのスタイル
   417:     "iplrom256k;off;" +  //X68000のIPLROMを256KBに改造する
   418:     "iplrom16;off;" +  //IPLROM 1.6を使う
   419:     "omusubi;off;" +  //おむすびフォントを使う
   420:     //PRN
   421:     "prnauto;off;" +  //自動保存
   422:     "prnpath;printer;" +  //ディレクトリ
   423:     "prndipsw;;" +  //ディップスイッチ
   424:     "prnsize;A4;" +  //用紙のサイズ
   425:     "prnorientation;portrait;" +  //用紙の方向
   426:     "prntopmargin;11;" +  //印字不可領域
   427:     "prnleftmargin;14;" +
   428:     "prnrightmargin;14;" +
   429:     "prnbottommargin;11;" +
   430:     "prnrotation;;" +  //回転
   431:     "prndarkmode;off;" +  //ダークモード
   432:     "prnonline;on;" +  //オンライン
   433:     "prnsinglecolor;;" +  //単色インクリボンの色
   434:     "prnscalefactor;;" +  //表示倍率
   435:     //SCC
   436:     "sccfreq;5000000;" +  //SCCの動作周波数
   437:     //TRM
   438:     "rs232cconnection;Terminal+%E2%87%94+AUX;" +  //接続。textをURLEncoderにかけて/で連結
   439:     "terminalsettings;38400/B8/PN/S1/RTS;" +  //ターミナルの通信設定。38400などを/で連結
   440:     "additionalport;none;" +  //追加ポート。portDescriptorを,で連結。エンコード
   441:     "tcpiphost;0.0.0.0;" +  //TCP/IPホスト
   442:     "tcpipport;54321;" +  //TCP/IPポート番号
   443:     "tcpiputf8;off;" +  //SJIS⇔UTF-8変換
   444:     //XT3
   445:     "xt3dipsw;3;" +  //Xellent30のDIPSW(0~3)
   446:     "xt3memorykb;256;" +  //Xellent30のメモリのサイズ(256/1024)
   447:     "xt3memorydata;;" +  //Xellent30のメモリの内容(gzip+base64)
   448:     "xt3memorysave;off;"  //Xellent30のメモリの保存フラグ(on/off)
   449:     );
   450: 
   451:   public static final String SGS_APPDATA_FOLDER = "XEiJ";  //Windowsのみ。AppData/Roamingフォルダに掘るフォルダの名前
   452:   public static final String SGS_INI = "XEiJ.ini";  //設定ファイル名
   453: 
   454:   public static final Pattern SGS_BOOT_DEVICE_PATTERN = Pattern.compile ("^(?:default|std|(?:fd|hd|sc|hf)\\d+|r[oa]m\\$[0-9A-Fa-f]+)$", Pattern.CASE_INSENSITIVE);  //-bootに指定できる起動デバイス名
   455: 
   456:   public static String sgsAppDataRoamingFolder;  //Windowsのみ。AppData/Roamingフォルダ。1人のユーザが複数のPCで同期して利用できるファイルを入れる
   457:   public static String sgsAppDataLocalFolder;  //Windowsのみ。AppData/Localフォルダ。1人のユーザがこのPCだけで利用できるファイルを入れる
   458:   public static String sgsHomeDirectory;  //ホームディレクトリ
   459:   public static String sgsCurrentDirectory;  //カレントディレクトリ
   460: 
   461:   public static File sgsIniParentDirectory;  //Windowsのみ。設定ファイルの親ディレクトリ。なければnull。設定ファイルを-iniで指定したときもnull
   462:   public static String sgsIniParentPath;  //Windowsのみ。設定ファイルの親ディレクトリのパス。なければnull
   463:   public static File sgsIniFile;  //設定ファイル
   464:   public static String sgsIniPath;  //設定ファイルのパス
   465: 
   466:   public static boolean sgsSaveOnExit;  //true=終了時に設定を保存する
   467:   public static boolean sgsXxxOpen;  //開いていたウインドウを復元する
   468:   public static JCheckBoxMenuItem sgsSaveOnExitCheckBox;
   469:   public static String sgsSaveiconValue;
   470:   public static String sgsIrbbenchValue;
   471: 
   472:   public static HashMap<String,String> sgsDefaultMap;  //デフォルトの設定。SGS_DEFAULT_PARAMETERSを変換したもの
   473:   public static HashMap<String,String> sgsStartMap;  //開始時の設定。デフォルトの設定に言語などを加えたもの。これと異なる値を持つキーだけ保存する
   474:   public static HashMap<String,String> sgsCurrentMap;  //現在の設定
   475:   public static HashMap<String,HashMap<String,String>> sgsRootMap;  //保存されているすべての設定。タイトル→設定
   476: 
   477:   public static JMenu sgsMenu;  //設定メニュー
   478: 
   479:   //DictionaryComparator
   480:   //  辞書順コンパレータ
   481:   //  大文字と小文字を区別しない
   482:   //  数字の並びを数の大小で比較する
   483:   //  一致したときは改めて大文字と小文字を区別して数字を特別扱いしないで比較し直す
   484:   public static final Comparator<String> DictionaryComparator = new Comparator<String> () {
   485:     @Override public int compare (String s1, String s2) {
   486:       int l1 = s1.length ();
   487:       int l2 = s2.length ();
   488:       int b1, b2;  //部分文字列の開始位置(このインデックスを含む)
   489:       int e1, e2;  //部分文字列の終了位置(このインデックスを含まない)
   490:       int f = 0;  //比較結果
   491:     compare:
   492:       {
   493:         for (b1 = 0, b2 = 0; b1 < l1 && b2 < l2; b1 = e1, b2 = e2) {
   494:           int c1, c2;
   495:           //数字と数字以外の境目を探して部分文字列の終了位置にする
   496:           e1 = b1;
   497:           c1 = s1.charAt (e1);
   498:           c1 = ('0' - 1) - c1 & c1 - ('9' + 1);  //(c1<0)==isdigit(c1)
   499:           for (e1++; e1 < l1; e1++) {
   500:             c2 = s1.charAt (e1);
   501:             c2 = ('0' - 1) - c2 & c2 - ('9' + 1);  //(c2<0)==isdigit(c2)
   502:             if ((c1 ^ c2) < 0) {  //数字と数字以外の境目
   503:               break;
   504:             }
   505:             c1 = c2;
   506:           }
   507:           e2 = b2;
   508:           c1 = s2.charAt (e2);
   509:           c1 = ('0' - 1) - c1 & c1 - ('9' + 1);  //(c1<0)==isdigit(c1)
   510:           for (e2++; e2 < l2; e2++) {
   511:             c2 = s2.charAt (e2);
   512:             c2 = ('0' - 1) - c2 & c2 - ('9' + 1);  //(c2<0)==isdigit(c2)
   513:             if ((c1 ^ c2) < 0) {  //数字と数字以外の境目
   514:               break;
   515:             }
   516:             c1 = c2;
   517:           }
   518:           c1 = s1.charAt (b1);
   519:           c2 = s2.charAt (b2);
   520:           if ((('0' - 1) - c1 & c1 - ('9' + 1) & ('0' - 1) - c2 & c2 - ('9' + 1)) < 0) {  //両方数字のとき
   521:             //ゼロサプレスする
   522:             for (; b1 < e1 && s1.charAt (b1) == '0'; b1++) {
   523:             }
   524:             for (; b2 < e2 && s2.charAt (b2) == '0'; b2++) {
   525:             }
   526:             //桁数を比較する
   527:             f = (e1 - b1) - (e2 - b2);
   528:             if (f != 0) {
   529:               break compare;
   530:             }
   531:             //数字を比較する
   532:             for (; b1 < e1 && b2 < e2; b1++, b2++) {
   533:               f = s1.charAt (b1) - s2.charAt (b2);
   534:               if (f != 0) {
   535:                 break compare;
   536:               }
   537:             }
   538:           } else {  //どちらかが数字ではないとき
   539:             //大文字と小文字を区別しないで比較する
   540:             //  小文字化してから比較する
   541:             for (; b1 < e1 && b2 < e2; b1++, b2++) {
   542:               c1 = s1.charAt (b1);
   543:               c2 = s2.charAt (b2);
   544:               f = ((c1 + ((('A' - 1) - c1 & c1 - ('Z' + 1)) >> 31 & 'a' - 'A')) -
   545:                    (c2 + ((('A' - 1) - c2 & c2 - ('Z' + 1)) >> 31 & 'a' - 'A')));
   546:               if (f != 0) {
   547:                 break compare;
   548:               }
   549:             }
   550:             if (b1 < e1 || b2 < e2) {  //部分文字列が片方だけ残っているとき
   551:               //  一致したまま片方だけ残るのは両方数字以外のときだけ
   552:               //  部分文字列が先に終わった方は文字列が終わっているか数字が続いている
   553:               //  部分文字列が残っている方は数字ではないので1文字比較するだけで大小関係がはっきりする
   554:               //f = (b1 < l1 ? s1.charAt (b1) : -1) - (b2 < l2 ? s2.charAt (b2) : -1);
   555:               f = (e1 - b1) - (e2 - b2);  //部分文字列が片方だけ残っているときは残っている方が大きい
   556:               break compare;
   557:             }
   558:           }  //if 両方数字のとき/どちらかが数字ではないとき
   559:         }  //for b1,b2
   560:         f = (l1 - b1) - (l2 - b2);  //文字列が片方だけ残っているときは残っている方が大きい
   561:         //一致したときは改めて大文字と小文字を区別して数字を特別扱いしないで比較し直す
   562:         if (f == 0) {
   563:           for (b1 = 0, b2 = 0; b1 < l1 && b2 < l2; b1++, b2++) {
   564:             f = s1.charAt (b1) - s2.charAt (b2);
   565:             if (f != 0) {
   566:               break compare;
   567:             }
   568:           }
   569:         }
   570:       }  //compare
   571:       return (f >> 31) - (-f >> 31);
   572:     }  //compare(String,String)
   573:   };  //DictionaryComparator
   574: 
   575:   //sgsInit ()
   576:   //  設定の初期化
   577:   public static void sgsInit () {
   578: 
   579:     sgsAppDataRoamingFolder = null;
   580:     sgsAppDataLocalFolder = null;
   581:     sgsHomeDirectory = null;
   582:     sgsCurrentDirectory = null;
   583: 
   584:     sgsIniParentDirectory = null;
   585:     sgsIniParentPath = null;
   586:     sgsIniFile = null;
   587:     sgsIniPath = null;
   588: 
   589:     sgsSaveOnExit = true;  //終了時に設定を保存する
   590:     sgsSaveOnExitCheckBox = null;
   591: 
   592:     sgsSaveiconValue = null;
   593:     sgsIrbbenchValue = null;
   594: 
   595:     //デフォルトの設定
   596:     //  SGS_DEFAULT_PARAMETERSを分解してデフォルトの設定sgsDefaultMapを作る
   597:     //  デフォルトの設定sgsDefaultMapには設定名を表すキー"_"が存在しない
   598:     sgsDefaultMap = new HashMap<String,String> ();
   599:     {
   600:       String[] a = SGS_DEFAULT_PARAMETERS.split (";");
   601:       for (int i = 0, l = a.length; i < l; i += 2) {
   602:         String key = a[i];
   603:         String value = i + 1 < l ? a[i + 1] : "";  //splitで末尾の空要素が削除されるのでa[i+1]が存在しないとき""とみなす
   604:         sgsDefaultMap.put (key, value);
   605:       }
   606:     }
   607:     //  ユニット
   608:     //  SCSI HDのイメージファイル
   609:     //    拡張  内蔵  sc[0-7]  sc[8-15]
   610:     //    -----------------------------
   611:     //    有効  有効    拡張     内蔵
   612:     //    有効  無効    拡張     無効
   613:     //    無効  有効    内蔵     無効
   614:     //    無効  無効    無効     無効
   615:     for (int i = 0; i < FDC.FDC_MAX_UNITS; i++) {
   616:       sgsDefaultMap.put ("fd" + i, "none");  //FD
   617:     }
   618:     for (int i = 0; i < 16; i++) {
   619:       sgsDefaultMap.put ("hd" + i, "none");  //SASIまたはSCSI
   620:       sgsDefaultMap.put ("sa" + i, "none");  //SASI
   621:       sgsDefaultMap.put ("sc" + i, "none");  //SCSI
   622:     }
   623:     for (int i = 0; i < HFS.HFS_MAX_UNITS; i++) {
   624:       sgsDefaultMap.put ("hf" + i, "none");  //HFS
   625:     }
   626:     //  ヒストリ
   627:     for (int i = 0; i < JFileChooser2.MAXIMUM_HISTORY_COUNT; i++) {
   628:       sgsDefaultMap.put ("fdhistory" + i, "none");
   629:       sgsDefaultMap.put ("sahistory" + i, "none");
   630:       sgsDefaultMap.put ("schistory" + i, "none");
   631:       sgsDefaultMap.put ("hfhistory" + i, "none");
   632:     }
   633:     //  ウインドウの位置とサイズと状態
   634:     for (String key : SGS_FRAME_KEYS) {
   635:       sgsDefaultMap.put (key + "rect", "");  //ウインドウの位置とサイズ(x,y,width,height)
   636:       sgsDefaultMap.put (key + "stat", "normal");  //ウインドウの状態(iconified/maximized/h-maximized/v-maximized/normal)
   637:       sgsDefaultMap.put (key + "open", "off");  //ウインドウが開いているかどうか。メインのフレームも終了時にはoffになっている
   638:     }
   639: 
   640:     //開始時の設定
   641:     //  デフォルトの設定sgsDefaultMapのコピーに言語やホームディレクトリを追加して開始時の設定sgsStartMapを作る
   642:     //  開始時の設定sgsStartMapには設定名を表すキー"_"が存在しない
   643:     sgsStartMap = new HashMap<String,String> (sgsDefaultMap);
   644: 
   645:     //hf0の初期値はカレントディレクトリ
   646:     sgsStartMap.put ("hf0", sgsHomeDirectory != null ? sgsHomeDirectory : HFS.HFS_DUMMY_UNIT_NAME);
   647: 
   648:     //言語
   649:     sgsStartMap.put ("lang", Multilingual.mlnLang);
   650: 
   651:     if (false) {
   652:       //すべての環境変数を表示する
   653:       System.out.println ("\n[System.getenv()]");
   654:       new TreeMap<String,String> (System.getenv ()).forEach ((k, v) -> System.out.println (k + " = " + v));  //System.getenv()はMap<String,String>
   655:     }
   656:     if (false) {
   657:       //すべてのプロパティを表示する
   658:       System.out.println ("\n[System.getProperties()]");
   659:       TreeMap<String,String> m = new TreeMap<String,String> ();
   660:       System.getProperties ().forEach ((k, v) -> m.put (k.toString (), v.toString ()));  //System.getProperties()はHashtable<Object,Object>
   661:       m.forEach ((k, v) -> System.out.println (k + " = " + v));
   662:     }
   663: 
   664:     //AppDataフォルダ
   665:     boolean isWindows = System.getProperty ("os.name").indexOf ("Windows") >= 0;  //true=Windows
   666:     sgsAppDataRoamingFolder = isWindows ? System.getenv ("APPDATA") : null;
   667:     sgsAppDataLocalFolder = isWindows ? System.getenv ("LOCALAPPDATA") : null;
   668:     //ホームディレクトリ
   669:     //  new File("")
   670:     sgsHomeDirectory = System.getProperty ("user.home");
   671:     //カレントディレクトリ
   672:     //  new File(".")
   673:     sgsCurrentDirectory = System.getProperty ("user.dir");
   674: 
   675:     //デフォルトの設定ファイルの場所
   676:     if (sgsAppDataRoamingFolder != null) {  //Windows
   677:       sgsIniParentPath = new File (sgsAppDataRoamingFolder + File.separator + SGS_APPDATA_FOLDER).getAbsolutePath ();
   678:       sgsIniParentDirectory = new File (sgsIniParentPath);
   679:       sgsIniPath = sgsIniParentPath + File.separator + SGS_INI;
   680:       sgsIniFile = new File (sgsIniPath);
   681:     } else {  //Windows以外
   682:       sgsIniParentPath = null;
   683:       sgsIniParentDirectory = null;
   684:       sgsIniPath = new File ((sgsHomeDirectory != null ? sgsHomeDirectory :
   685:                               sgsCurrentDirectory != null ? sgsCurrentDirectory :
   686:                               ".") + File.separator + SGS_INI).getAbsolutePath ();
   687:       sgsIniFile = new File (sgsIniPath);
   688:     }
   689: 
   690:     //現在の設定
   691:     //  開始時の設定sgsStartMapをコピーして現在の設定sgsCurrentMapを作る
   692:     //  ここで初めて設定名を表すキー"_"を追加する
   693:     sgsCurrentMap = new HashMap<String,String> (sgsStartMap);
   694:     sgsCurrentMap.put ("_", "");
   695: 
   696:     //保存されているすべての設定のマップ
   697:     sgsRootMap = new HashMap<String,HashMap<String,String>> ();
   698:     sgsRootMap.put ("", sgsCurrentMap);
   699: 
   700:     //コマンドラインのパラメータを読み取る
   701:     //  -iniで設定ファイルが変更される場合がある
   702:     HashMap<String,String> argumentMap = new HashMap<String,String> ();
   703:     int fdNumber = 0;
   704:     int hdNumber = 0;
   705:     int scNumber = 0;
   706:     int hfNumber = 0;
   707:     for (int i = 0; i < XEiJ.prgArgs.length; i++) {
   708:       String key = null;  //キー
   709:       String value = XEiJ.prgArgs[i];  //引数。nullではないはず
   710:     arg:
   711:       {
   712:         boolean boot = false;  //true=valueは-bootの値
   713:         if (value.startsWith ("-")) {  //引数が"-"で始まっている
   714:           //!!! 値が必要なものと必要でないものを区別したい
   715:           int k = value.indexOf ('=', 1);
   716:           if (k >= 0) {  //引数が"-"で始まっていて2文字目以降に"="がある
   717:             key = value.substring (1, k);  //"-"の後ろから"="の手前まではキー
   718:             value = value.substring (k + 1);  //"="の後ろは値
   719:           } else {  //引数が"-"で始まっていて2文字目以降に"="がない
   720:             //!!! "-"で始まる引数はすべてキーとみなされるので"-キー 値"の形では値に負の数値を書くことはできない
   721:             key = value.substring (1);  //"-"の後ろはキー
   722:             value = (i + 1 < XEiJ.prgArgs.length && !XEiJ.prgArgs[i + 1].startsWith ("-") ?  //次の引数があって次の引数が"-"で始まっていない
   723:                      XEiJ.prgArgs[++i]  //次の引数は値
   724:                      :  //次の引数がないまたは次の引数が"-"で始まっている
   725:                      "1");  //値は"1"
   726:           }
   727:           if (!key.equalsIgnoreCase ("boot")) {  //-bootではない
   728:             break arg;
   729:           }
   730:           boot = true;
   731:         }
   732:         //引数が"-"で始まっていないまたは-bootの値
   733:         if (SGS_BOOT_DEVICE_PATTERN.matcher (value).matches ()) {  //起動デバイス名のとき
   734:           //ファイルやディレクトリを探さず起動デバイスだけ設定する
   735:           key = "boot";
   736:           break arg;
   737:         }
   738:         String valueWithoutColonR = value.endsWith (":R") ? value.substring (0, value.length () - 2) : value;  //末尾の":R"を取り除いた部分
   739:         File file = new File (valueWithoutColonR);
   740:         if (file.isDirectory ()) {  //ディレクトリがある
   741:           key = "hf" + hfNumber++;  //HFS
   742:         } else if (file.isFile ()) {  //ファイルがある
   743:           //FDMedia.fdmPathToMediaが大きすぎるファイルを処理できないので、
   744:           //先に拡張子でハードディスクを区別する
   745:           if (valueWithoutColonR.toUpperCase ().endsWith (".HDF")) {
   746:             key = "sa" + hdNumber++;  //SASI ハードディスク
   747:           } else if (valueWithoutColonR.toUpperCase ().endsWith (".HDS")) {
   748:             key = "sc" + scNumber++;  //SCSI ハードディスク/CD-ROM
   749:           } else if (FDMedia.fdmPathToMedia (valueWithoutColonR, null) != null) {
   750:             key = "fd" + fdNumber++;  //フロッピーディスク
   751:           } else if (HDMedia.hdmPathToMedia (valueWithoutColonR, null) != null) {
   752:             key = "sa" + hdNumber++;  //SASI ハードディスク
   753:           } else {
   754:             key = "sc" + scNumber++;  //SCSI ハードディスク/CD-ROM
   755:           }
   756:         } else {  //ファイルもディレクトリもない
   757:           String[] zipSplittedName = XEiJ.RSC_ZIP_SEPARATOR.split (valueWithoutColonR, 2);  //*.zip/entryを*.zipとentryに分ける
   758:           if (zipSplittedName.length == 2 &&  //*.zip/entryで
   759:               new File (zipSplittedName[0]).isFile ()) {  //*.zipがある
   760:             if (FDMedia.fdmPathToMedia (valueWithoutColonR, null) != null) {
   761:               key = "fd" + fdNumber++;  //フロッピーディスク
   762:             } else if (HDMedia.hdmPathToMedia (valueWithoutColonR, null) != null) {
   763:               key = "sa" + hdNumber++;  //SASI ハードディスク
   764:             } else {
   765:               System.out.println (Multilingual.mlnJapanese ? value + " は不明な起動デバイスです" :
   766:                                   value + " is unknown boot device");
   767:               continue;
   768:             }
   769:           } else {  //*.zip.entryでないか*.zipがない
   770:             System.out.println (Multilingual.mlnJapanese ? value + " は不明な起動デバイスです" :
   771:                                 value + " is unknown boot device");
   772:             continue;
   773:           }
   774:         }
   775:         if (boot) {  //-bootの値のとき
   776:           sgsPutParameter (argumentMap, "boot", key);  //起動デバイスを設定する
   777:         }
   778:       }  //arg
   779:       //その他のオプション
   780:       switch (key) {
   781:       case "ini":  //設定ファイル
   782:         sgsIniParentPath = null;
   783:         sgsIniParentDirectory = null;
   784:         sgsIniPath = new File (value).getAbsolutePath ();
   785:         sgsIniFile = new File (sgsIniPath);
   786:         break;
   787:       case "saveicon":
   788:         sgsSaveiconValue = value;
   789:         break;
   790:       case "irbbench":
   791:         sgsIrbbenchValue = value;
   792:         break;
   793:       default:
   794:         sgsPutParameter (argumentMap, key, value);  //パラメータを設定する
   795:       }
   796:     }
   797:     System.out.println (Multilingual.mlnJapanese ? "設定ファイルは " + sgsIniPath + " です" :
   798:                         "INI file is " + sgsIniPath);
   799: 
   800:     //設定ファイルを読み込んですべての設定sgsRootMapに格納する
   801:     sgsDecodeRootMap (sgsLoadIniFile ());
   802: 
   803:     //コマンドラインなどのパラメータで使用する設定を選択する
   804:     if (argumentMap.containsKey ("config")) {  //キー"config"が指定されているとき
   805:       String name = argumentMap.get ("config");  //使用する設定名
   806:       if (name.equals ("default")) {  //デフォルトの設定
   807:         sgsCurrentMap.clear ();  //古いマップを消しておく
   808:         sgsCurrentMap = new HashMap<String,String> (sgsStartMap);  //開始時の設定を現在の設定にコピーする
   809:         sgsCurrentMap.put ("_", "");  //設定名を加える
   810:         sgsRootMap.put ("", sgsCurrentMap);  //新しいマップを繋ぎ直す
   811:       } else if (name.length () != 0 &&  //使用する設定名が""以外で
   812:                  sgsRootMap.containsKey (name)) {  //存在するとき
   813:         sgsCurrentMap.clear ();  //古いマップを消しておく
   814:         sgsCurrentMap = new HashMap<String,String> (sgsRootMap.get (name));  //指定された設定を現在の設定にコピーする
   815:         sgsCurrentMap.put ("_", "");  //設定名を元に戻す
   816:         sgsRootMap.put ("", sgsCurrentMap);  //新しいマップを繋ぎ直す
   817:       }
   818:       argumentMap.remove ("config");  //キー"config"は指定されなかったことにする
   819:     }
   820: 
   821:     //コマンドラインなどのパラメータを現在の設定に上書きする
   822:     //argumentMap.forEach ((k, v) -> sgsCurrentMap.put (k, v));
   823:     for (String key : argumentMap.keySet ()) {
   824:       sgsCurrentMap.put (key, argumentMap.get (key));
   825:     }
   826: 
   827:     //PRG
   828:     String paramLang = sgsCurrentMap.get ("lang").toLowerCase ();
   829:     Multilingual.mlnChange (paramLang.equals ("ja") ? "ja" : "en");
   830:     XEiJ.prgVerbose = sgsGetOnOff ("verbose");  //冗長表示
   831:     //SGS
   832:     sgsSaveOnExit = sgsGetOnOff ("saveonexit");  //終了時に設定を保存するか
   833:     sgsXxxOpen = sgsGetOnOff ("xxxopen");  //開いていたウインドウを復元する
   834:     //LNF
   835:     //  色
   836:     //KBD
   837:     //MUS
   838:     //MPU
   839:     //FPK
   840:     //BUS
   841:     //MMR
   842:     //CRT
   843:     if (CRTC.CRT_ENABLE_INTERMITTENT) {
   844:       CRTC.crtIntermittentInterval = XEiJ.fmtParseInt (sgsCurrentMap.get ("intermittent"), 0, 0, 4, 0);  //間欠描画
   845:     }
   846:     if (CRTC.CRT_EXTENDED_GRAPHIC) {
   847:       CRTC.crtExtendedGraphicRequest = sgsGetOnOff ("extendedgraphic");  //拡張グラフィック画面
   848:     }
   849:     //SND
   850:     SoundSource.sndPlayOn = sgsGetOnOff ("sound");  //音声出力
   851:     SoundSource.sndVolume = XEiJ.fmtParseInt (sgsCurrentMap.get ("volume"), 0, 0, SoundSource.SND_VOLUME_MAX, SoundSource.SND_VOLUME_DEFAULT);  //ボリューム
   852:     {
   853:       String s = sgsCurrentMap.get ("soundinterpolation").toLowerCase ();
   854:       SoundSource.sndRateConverter = (s.equals ("thinning") ? SoundSource.SND_CHANNELS == 1 ? SoundSource.SNDRateConverter.THINNING_MONO : SoundSource.SNDRateConverter.THINNING_STEREO :  //間引き
   855:                                       s.equals ("linear") ? SoundSource.SND_CHANNELS == 1 ? SoundSource.SNDRateConverter.LINEAR_MONO : SoundSource.SNDRateConverter.LINEAR_STEREO :  //線形補間
   856:                                       s.equals ("constant-area") ? SoundSource.SNDRateConverter.CONSTANT_AREA_STEREO_48000 :  //区分定数面積補間
   857:                                       s.equals ("linear-area") ? SoundSource.SNDRateConverter.LINEAR_AREA_STEREO_48000 :  //線形面積補間
   858:                                       SoundSource.SND_CHANNELS == 1 ? SoundSource.SNDRateConverter.LINEAR_MONO : SoundSource.SNDRateConverter.LINEAR_STEREO);  //線形補間
   859:     }
   860:     //OPM
   861:     OPM.opmOutputMask = sgsGetOnOff ("opmoutput") ? -1 : 0;  //OPM出力
   862:     //PCM
   863:     {
   864:       ADPCM.pcmOutputOn = sgsGetOnOff ("pcmoutput");  //PCM出力
   865:       String s = sgsCurrentMap.get ("pcminterpolation").toLowerCase ();
   866:       ADPCM.pcmInterpolationAlgorithm = (s.equals ("constant") ? ADPCM.PCM_INTERPOLATION_CONSTANT :  //区分定数補間
   867:                                          s.equals ("linear") ? ADPCM.PCM_INTERPOLATION_LINEAR :  //線形補間
   868:                                          s.equals ("hermite") ? ADPCM.PCM_INTERPOLATION_HERMITE :  //エルミート補間
   869:                                          ADPCM.PCM_INTERPOLATION_LINEAR);  //線形補間
   870:       ADPCM.pcmOSCFreqRequest = XEiJ.fmtParseInt (sgsCurrentMap.get ("pcmoscfreq"), 0, 0, 1, 0);  //原発振周波数
   871:     }
   872:     //FDC
   873:     //fdNはFDCで解釈する
   874:     //HDC
   875:     //hdNはHDCで解釈する
   876:     //SPC
   877:     //scNはSPCで解釈する
   878:     //HFS
   879:     //hfNはHFSで解釈する
   880:     //PPI
   881:     //SMR
   882:     //BNK
   883:     //FNT
   884:     //PRN
   885: 
   886:     //ウインドウの位置とサイズと状態と開いているか
   887:     for (String key : SGS_FRAME_KEYS) {
   888:       //位置とサイズ
   889:       int[] rect = sgsGetIntArray (key + "rect", 4, 0);
   890:       //状態
   891:       String s = sgsGetString (key + "stat").toLowerCase ();
   892:       int state = (s.equals ("iconified") ? Frame.ICONIFIED :  //アイコン化する
   893:                    s.equals ("maximized") ? Frame.MAXIMIZED_BOTH :  //最大化する
   894:                    s.equals ("h-maximized") ? Frame.MAXIMIZED_HORIZ :  //水平方向だけ最大化する
   895:                    s.equals ("v-maximized") ? Frame.MAXIMIZED_VERT :  //垂直方向だけ最大化する
   896:                    Frame.NORMAL);  //通常表示
   897:       //開いているか
   898:       boolean opened = sgsXxxOpen && sgsGetOnOff (key + "open");
   899:       RestorableFrame.rfmSet (key, rect, state, opened);
   900:     }
   901: 
   902:     if (sgsIrbbenchValue != null) {
   903:       InstructionBenchmark.irbBench (sgsIrbbenchValue);
   904:       System.exit (0);
   905:     }
   906: 
   907:   }  //sgsInit()
   908: 
   909:   //sgsTini ()
   910:   //  後始末
   911:   public static void sgsTini () {
   912:     if (sgsSaveOnExit) {  //終了時に設定を保存する
   913:       sgsSaveAllSettings ();
   914:     }
   915:   }  //sgsTini()
   916: 
   917: 
   918: 
   919:   //value = sgsGetString (key)
   920:   //  現在の設定を読み出す。前後の空白を取り除く。noneを""とみなす
   921:   public static String sgsGetString (String key) {
   922:     String value = sgsCurrentMap.get (key);
   923:     if (value == null) {
   924:       System.err.println ("sgsGetString: undefined key " + key);
   925:       return "";
   926:     }
   927:     value = value.trim ();  //前後の空白を取り除く
   928:     return value.equalsIgnoreCase ("none") ? "" : value;  //noneを""とみなす
   929:   }
   930: 
   931:   //sgsPutString (key, value)
   932:   //  現在の設定に書き込む。前後の空白を取り除く。""をnoneとみなす
   933:   public static void sgsPutString (String key, String value) {
   934:     if (!sgsCurrentMap.containsKey (key)) {
   935:       System.err.println ("sgsPutString: undefined key " + key);
   936:       return;
   937:     }
   938:     value = value.trim ();  //前後の空白を取り除く
   939:     sgsCurrentMap.put (key, value.length () == 0 ? "none" : value);  //""をnoneとみなす
   940:   }
   941: 
   942:   //b = sgsGetOnOff (key)
   943:   //  現在の設定を読み出す。前後の空白を取り除く。1とonとyesをtrue、それ以外をfalseとみなす
   944:   public static boolean sgsGetOnOff (String key) {
   945:     String value = sgsCurrentMap.get (key);
   946:     if (value == null) {
   947:       System.err.println ("sgsGetOnOff: undefined key " + key);
   948:       return false;
   949:     }
   950:     value = value.trim ();  //前後の空白を取り除く
   951:     return value.equals ("1") || value.equalsIgnoreCase ("on") || value.equalsIgnoreCase ("yes");  //1とonとyesをtrue、それ以外をfalseとみなす
   952:   }
   953: 
   954:   //sgsPutOnOff (key, b)
   955:   //  現在の設定に書き込む。trueをon、falseをoffとみなす
   956:   public static void sgsPutOnOff (String key, boolean b) {
   957:     if (!sgsCurrentMap.containsKey (key)) {
   958:       System.err.println ("sgsPutOnOff: undefined key " + key);
   959:       return;
   960:     }
   961:     sgsCurrentMap.put (key, b ? "on" : "off");  //trueをon、falseをoffとみなす
   962:   }
   963: 
   964:   //i = sgsGetInt (key)
   965:   //i = sgsGetInt (key, i0)
   966:   //  現在の設定から整数を読み出す。""のときi0になる
   967:   public static int sgsGetInt (String key) {
   968:     return sgsGetInt (key, 0);
   969:   }
   970:   public static int sgsGetInt (String key, int i0) {
   971:     String value = sgsCurrentMap.get (key);
   972:     if (value == null) {
   973:       System.err.println ("sgsGetInt: undefined key " + key);
   974:       return i0;
   975:     }
   976:     value = value.trim ();  //前後の空白を取り除く
   977:     if (value.length () != 0) {
   978:       try {
   979:         return Integer.parseInt (value, 10);
   980:       } catch (NumberFormatException nfe) {
   981:       }
   982:     }
   983:     return i0;
   984:   }
   985: 
   986:   //sgsPutInt (key, i)
   987:   //sgsPutInt (key, i, i0)
   988:   //  現在の設定に整数を書き込む。i0のとき""になる
   989:   public static void sgsPutInt (String key, int i) {
   990:     sgsPutInt (key, i, 0);
   991:   }
   992:   public static void sgsPutInt (String key, int i, int i0) {
   993:     if (!sgsCurrentMap.containsKey (key)) {
   994:       System.err.println ("sgsPutInt: undefined key " + key);
   995:       return;
   996:     }
   997:     sgsCurrentMap.put (key, i == i0 ? "" : String.valueOf (i));
   998:   }
   999: 
  1000:   //ia = sgsGetIntArray (key)
  1001:   //ia = sgsGetIntArray (key, n)
  1002:   //ia = sgsGetIntArray (key, n, v)
  1003:   //  現在の設定を読み出す。','で区切る。前後の空白を取り除く。""を0に、それ以外の文字列を10進数とみなして整数に変換する。失敗したときは0
  1004:   //  nは要素の数。-1=可変
  1005:   //  vは指定されなかった要素の値
  1006:   public static int[] sgsGetIntArray (String key) {
  1007:     return sgsGetIntArray (key, -1, 0);
  1008:   }
  1009:   public static int[] sgsGetIntArray (String key, int n) {
  1010:     return sgsGetIntArray (key, n, 0);
  1011:   }
  1012:   public static int[] sgsGetIntArray (String key, int n, int v) {
  1013:     String value = sgsCurrentMap.get (key);
  1014:     if (value == null) {
  1015:       System.err.println ("sgsGetIntArray: undefined key " + key);
  1016:       value = "";
  1017:     }
  1018:     String[] sa = value.length () == 0 ? new String[0] : value.split (",");  //','で区切る。"".split(",").length==1であることに注意
  1019:     if (n < 0) {  //可変
  1020:       n = sa.length;  //要素の数
  1021:     }
  1022:     int[] ia = new int[n];
  1023:     Arrays.fill (ia, v);
  1024:     for (int i = 0; i < n && i < sa.length; i++) {
  1025:       String s = sa[i].trim ();  //前後の空白を取り除く
  1026:       if (s.length () != 0) {
  1027:         try {
  1028:           ia[i] = Integer.parseInt (s, 10);  //10進数とみなして整数に変換する
  1029:         } catch (NumberFormatException nfe) {
  1030:         }
  1031:       }
  1032:     }
  1033:     return ia;
  1034:   }
  1035: 
  1036:   //sgsPutIntArray (key, ia)
  1037:   //sgsPutIntArray (key, ia, v)
  1038:   //  現在の設定に書き込む。vを""に、それ以外の整数を10進数の文字列に変換する。','で区切って並べる。末尾のvの並びを省略する
  1039:   public static void sgsPutIntArray (String key, int[] ia) {
  1040:     sgsPutIntArray (key, ia, 0);
  1041:   }
  1042:   public static void sgsPutIntArray (String key, int[] ia, int v) {
  1043:     if (!sgsCurrentMap.containsKey (key)) {
  1044:       System.err.println ("sgsPutIntArray: undefined key " + key);
  1045:       return;
  1046:     }
  1047:     int n = ia.length;
  1048:     while (0 < n && ia[n - 1] == v) {
  1049:       n--;  //末尾のvの並びを省略する
  1050:     }
  1051:     StringBuilder sb = new StringBuilder ();
  1052:     for (int i = 0; i < n; i++) {
  1053:       if (i != 0) {
  1054:         sb.append (',');  //','で区切って並べる
  1055:       }
  1056:       if (ia[i] != v) {  //vを""に、それ以外の整数を10進数の文字列に変換する
  1057:         sb.append (ia[i]);
  1058:       }
  1059:     }
  1060:     sgsCurrentMap.put (key, sb.toString ());
  1061:   }
  1062: 
  1063:   //array = sgsGetData (key)
  1064:   //  現在の設定を読み出す。gzip+base64で解凍する
  1065:   //  ""のときは長さが0の配列を返す
  1066:   public static byte[] sgsGetData (String key) {
  1067:     String value = sgsCurrentMap.get (key);
  1068:     if (value == null) {
  1069:       System.err.println ("sgsGetData: undefined key " + key);
  1070:       value = "";
  1071:     }
  1072:     return value.length () == 0 ? new byte[0] : ByteArray.byaDecodeGzip (ByteArray.byaDecodeBase64 (value));
  1073:   }
  1074: 
  1075:   //sgsPutData (key, array)
  1076:   //sgsPutData (key, array, offset, length)
  1077:   //  現在の設定に書き込む。gzip+base64で圧縮する
  1078:   //  中途半端に短くすると長さで種類を見分けることができなくなるので末尾の0は切り捨てない
  1079:   //  すべて0のときは""を書き込む
  1080:   public static void sgsPutData (String key, byte[] array) {
  1081:     sgsPutData (key, array, 0, array.length);
  1082:   }
  1083:   public static void sgsPutData (String key, byte[] array, int offset, int length) {
  1084:     if (!sgsCurrentMap.containsKey (key)) {
  1085:       System.err.println ("sgsPutData: undefined key " + key);
  1086:       return;
  1087:     }
  1088:     String value = "";
  1089:     for (int i = 0; i < length; i++) {
  1090:       if (array[offset + i] != 0) {  //0ではない
  1091:         value = ByteArray.byaEncodeBase64 (ByteArray.byaEncodeGzip (array, offset, length));
  1092:         break;
  1093:       }
  1094:     }
  1095:     sgsCurrentMap.put (key, value);
  1096:   }
  1097: 
  1098: 
  1099: 
  1100:   //sgsMakeMenu ()
  1101:   //  「設定」メニューを作る
  1102:   public static void sgsMakeMenu () {
  1103:     //アクションリスナー
  1104:     ActionListener listener = new ActionListener () {
  1105:       @Override public void actionPerformed (ActionEvent ae) {
  1106:         Object source = ae.getSource ();
  1107:         String command = ae.getActionCommand ();
  1108:         switch (command) {
  1109:         case "Save settings on exit":  //終了時に設定を保存する
  1110:           sgsSaveOnExit = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();  //終了時に設定を保存するか
  1111:           break;
  1112:         case "Restore windows that were open":  //開いていたウインドウを復元する
  1113:           sgsXxxOpen = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
  1114:           break;
  1115:         case "Delete all settings":  //すべての設定を消去する
  1116:           sgsDeleteAllSettings ();
  1117:           break;
  1118:         }
  1119:       }
  1120:     };
  1121:     //メニュー
  1122:     sgsMenu = Multilingual.mlnText (
  1123:       ComponentFactory.createMenu (
  1124:         "Configuration file",
  1125:         sgsSaveOnExitCheckBox =
  1126:         Multilingual.mlnText (
  1127:           ComponentFactory.createCheckBoxMenuItem (sgsSaveOnExit, "Save settings on exit", listener),
  1128:           "ja", "終了時に設定を保存する"),
  1129:         Multilingual.mlnText (
  1130:           ComponentFactory.createCheckBoxMenuItem (sgsXxxOpen, "Restore windows that were open", listener),
  1131:           "ja", "開いていたウインドウを復元する"),
  1132:         ComponentFactory.createHorizontalSeparator (),
  1133:         Multilingual.mlnText (
  1134:           ComponentFactory.createMenuItem ("Delete all settings", listener),
  1135:           "ja", "すべての設定を消去する")
  1136:         ),
  1137:       "ja", "設定ファイル");
  1138:   }  //sgsMakeMenu()
  1139: 
  1140:   //sgsSaveAllSettings ()
  1141:   //  設定ファイルを保存する
  1142:   public static void sgsSaveAllSettings () {
  1143:     //MLN
  1144:     sgsCurrentMap.put ("lang", Multilingual.mlnLang);  //言語
  1145:     //PRG
  1146:     sgsCurrentMap.put ("verbose", XEiJ.prgVerbose ? "on" : "off");  //冗長表示
  1147:     //SGS
  1148:     sgsCurrentMap.put ("saveonexit", sgsSaveOnExit ? "on" : "off");  //終了時に設定を保存する
  1149:     sgsCurrentMap.put ("xxxopen", sgsXxxOpen ? "on" : "off");  //開いていたウインドウを復元する
  1150:     //PNL
  1151:     //MUS
  1152:     //FPK
  1153:     sgsCurrentMap.put ("fefunc", FEFunction.fpkOn ? "on" : "off");  //FEファンクション命令
  1154:     sgsCurrentMap.put ("rejectfloat", FEFunction.fpkRejectFloatOn ? "on" : "off");  //FLOATn.Xを組み込まない
  1155:     //BUS
  1156:     //MMR
  1157:     sgsCurrentMap.put ("memory", String.valueOf (MainMemory.mmrMemorySizeRequest >>> 20));  //メインメモリのサイズ
  1158:     sgsCurrentMap.put ("memorysave", MainMemory.mmrMemorySaveOn ? "on" : "off");  //メインメモリの内容を保存する
  1159:     sgsCurrentMap.put ("memorydata", MainMemory.mmrMemorySaveOn ? ByteArray.byaEncodeBase64 (ByteArray.byaEncodeGzip (MainMemory.mmrM8, 0x00000000, MainMemory.mmrMemorySizeCurrent)) : "");  //メインメモリの内容
  1160:     //CRT
  1161:     if (CRTC.CRT_ENABLE_INTERMITTENT) {
  1162:       sgsCurrentMap.put ("intermittent", String.valueOf (CRTC.crtIntermittentInterval));  //間欠描画
  1163:     }
  1164:     if (CRTC.CRT_EXTENDED_GRAPHIC) {
  1165:       sgsCurrentMap.put ("extendedgraphic", CRTC.crtExtendedGraphicRequest ? "on" : "off");  //拡張グラフィック画面
  1166:     }
  1167:     //SND
  1168:     sgsCurrentMap.put ("sound", SoundSource.sndPlayOn ? "on" : "off");  //音声出力
  1169:     sgsCurrentMap.put ("volume", String.valueOf (SoundSource.sndVolume));  //ボリューム
  1170:     sgsCurrentMap.put ("soundinterpolation",
  1171:                        SoundSource.sndRateConverter == (SoundSource.SND_CHANNELS == 1 ? SoundSource.SNDRateConverter.THINNING_MONO : SoundSource.SNDRateConverter.THINNING_STEREO) ? "thinning" :  //間引き
  1172:                        SoundSource.sndRateConverter == (SoundSource.SND_CHANNELS == 1 ? SoundSource.SNDRateConverter.LINEAR_MONO : SoundSource.SNDRateConverter.LINEAR_STEREO) ? "linear" :  //線形補間
  1173:                        SoundSource.sndRateConverter == SoundSource.SNDRateConverter.CONSTANT_AREA_STEREO_48000 ? "constant-area" :  //区分定数面積補間
  1174:                        SoundSource.sndRateConverter == SoundSource.SNDRateConverter.LINEAR_AREA_STEREO_48000 ? "linear-area" :  //線形面積補間
  1175:                        "linear");  //線形補間
  1176:     //OPM
  1177:     sgsCurrentMap.put ("opmoutput", OPM.opmOutputMask != 0 ? "on" : "off");  //OPM出力
  1178:     //PCM
  1179:     sgsCurrentMap.put ("pcmoutput", ADPCM.pcmOutputOn ? "on" : "off");  //PCM出力
  1180:     sgsCurrentMap.put ("pcminterpolation",
  1181:                        ADPCM.pcmInterpolationAlgorithm == ADPCM.PCM_INTERPOLATION_CONSTANT ? "constant" :  //区分定数補間
  1182:                        ADPCM.pcmInterpolationAlgorithm == ADPCM.PCM_INTERPOLATION_LINEAR ? "linear" :  //線形補間
  1183:                        ADPCM.pcmInterpolationAlgorithm == ADPCM.PCM_INTERPOLATION_HERMITE ? "hermite" :  //エルミート補間
  1184:                        "linear");  //線形補間
  1185:     sgsCurrentMap.put ("pcmoscfreq", String.valueOf (ADPCM.pcmOSCFreqRequest));  //原発振周波数
  1186:     //PPI
  1187:     //SMR
  1188:     //BNK
  1189:     //FNT
  1190:     //PRN
  1191: 
  1192:     //ウインドウの位置とサイズと状態
  1193:     for (String key : SGS_FRAME_KEYS) {
  1194:       //位置とサイズ
  1195:       sgsPutIntArray (key + "rect", RestorableFrame.rfmGetRect (key), 0);
  1196:       //状態
  1197:       int state = RestorableFrame.rfmGetState (key);
  1198:       sgsPutString (key + "stat",
  1199:                     (state & Frame.ICONIFIED) == Frame.ICONIFIED ? "iconified" :  //アイコン化されている
  1200:                     (state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH ? "maximized" :  //最大化されている
  1201:                     (state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_HORIZ ? "h-maximized" :  //水平方向だけ最大化されている
  1202:                     (state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_VERT ? "v-maximized" :  //垂直方向だけ最大化されている
  1203:                     "normal");  //通常表示
  1204:       //開いているか
  1205:       sgsPutOnOff (key + "open", sgsXxxOpen && RestorableFrame.rfmGetOpened (key));
  1206:     }
  1207: 
  1208:     //保存する
  1209:     sgsSaveIniFile (sgsEncodeRootMap ());
  1210: 
  1211:   }  //sgsSaveSettings()
  1212: 
  1213:   //sgsDecodeRootMap (text)
  1214:   //  テキストをsgsRootMapに変換する
  1215:   public static void sgsDecodeRootMap (String text) {
  1216:     sgsRootMap.clear ();  //すべての設定を消す
  1217:     sgsCurrentMap.clear ();  //古いマップを消しておく
  1218:     sgsCurrentMap = new HashMap<String,String> (sgsStartMap);  //開始時の設定を現在の設定にコピーする
  1219:     sgsCurrentMap.put ("_", "");  //設定名を加える
  1220:     sgsRootMap.put ("", sgsCurrentMap);  //新しいマップを繋ぎ直す
  1221:     HashMap<String,String> map = sgsCurrentMap;  //現在変換中の設定は現在の設定
  1222:     for (String line : text.split ("\n")) {
  1223:       line = line.trim ();  //キーの前の空白と値の後の空白を取り除く
  1224:       if (line.length () == 0 ||  //空行
  1225:           line.startsWith ("#")) {  //注釈
  1226:         continue;
  1227:       }
  1228:       int i = line.indexOf ('=');
  1229:       if (i < 0) {  //'='がない
  1230:         continue;
  1231:       }
  1232:       String key = line.substring (0, i).trim ().toLowerCase ();  //キー。後('='の前)の空白を取り除いて小文字化する
  1233:       String value = line.substring (i + 1).trim ();  //値。前('='の後)の空白を取り除く
  1234:       if (key.equals ("_")) {  //設定名。新しい設定の最初の行
  1235:         if (sgsRootMap.containsKey (value)) {  //同じ設定名が2回出てきたとき
  1236:           if (false) {
  1237:             map = null;  //新しい設定名が指定されるまで読み飛ばす(最初に書いた設定が残る)
  1238:           } else {
  1239:             map = sgsRootMap.get (value);  //既存の設定に上書きする(最後に書いた設定が残る)
  1240:           }
  1241:         } else {  //新しい設定
  1242:           map = new HashMap<String,String> (sgsStartMap);  //開始時の設定をコピーする
  1243:           map.put (key, value);  //sgsPutParameterは設定名のキー"_"を受け付けないことに注意
  1244:           sgsRootMap.put (value, map);
  1245:         }
  1246:         continue;
  1247:       }
  1248:       if (map == null) {  //新しい設定名が指定されるまで読み飛ばす
  1249:         continue;
  1250:       }
  1251:       sgsPutParameter (map, key, value);
  1252:     }  //for line
  1253:   }  //sgsDecodeRootMap()
  1254: 
  1255:   //strings = sgsEncodeRootMap ()
  1256:   //  sgsRootMapを文字列のリストに変換する
  1257:   public static ArrayList<String> sgsEncodeRootMap () {
  1258:     ArrayList<String> strings = new ArrayList<String> ();  //StringBuilderは大きすぎると失敗する
  1259:     String[] nameArray = sgsRootMap.keySet ().toArray (new String[0]);  //設定名の配列
  1260:     Arrays.sort (nameArray, DictionaryComparator);  //設定名をソートする。設定名が""の現在の設定が先頭に来る
  1261:     for (String name : nameArray) {
  1262:       HashMap<String,String> map = sgsRootMap.get (name);  //個々の設定
  1263:       if (map != sgsCurrentMap) {  //(先頭の)現在の設定でないとき
  1264:         strings.add ("\n");  //1行空ける
  1265:       }
  1266:       String[] keyArray = map.keySet ().toArray (new String[0]);  //キーの配列
  1267:       Arrays.sort (keyArray, DictionaryComparator);  //キーをソートする。設定名以外のキーはすべて英小文字で始まるので設定名のキー"_"が先頭に来る
  1268:       for (String key : keyArray) {
  1269:         String value = map.get (key);
  1270:         if (!(map == sgsCurrentMap && key.equals ("_")) &&  //現在の設定の設定名でない
  1271:             !key.equals ("config") &&  //キー"config"は設定ファイルに出力しない
  1272:             !value.equals (sgsStartMap.get (key))) {  //開始時の設定にないか、開始時の設定と異なる
  1273:           strings.add (key);
  1274:           strings.add ("=");
  1275:           strings.add (value);  //これが極端に大きい場合がある
  1276:           strings.add ("\n");
  1277:         }
  1278:       }
  1279:     }
  1280:     return strings;
  1281:   }  //sgsEncodeRootMap()
  1282: 
  1283:   //sgsDeleteAllSettings ()
  1284:   //  すべての設定を削除する
  1285:   public static void sgsDeleteAllSettings () {
  1286:     XEiJ.pnlExitFullScreen (true);
  1287:     if (JOptionPane.showConfirmDialog (
  1288:       XEiJ.frmFrame,
  1289:       Multilingual.mlnJapanese ? "すべての設定を消去しますか?" : "Do you want to delete all settings?",
  1290:       Multilingual.mlnJapanese ? "確認" : "Confirmation",
  1291:       JOptionPane.YES_NO_OPTION,
  1292:       JOptionPane.PLAIN_MESSAGE) == JOptionPane.YES_OPTION) {
  1293:       sgsDeleteIniFile ();  //設定ファイルを削除する
  1294:       sgsSaveOnExit = false;  //終了時に設定を保存しない
  1295:       sgsSaveOnExitCheckBox.setSelected (sgsSaveOnExit);
  1296:     }
  1297:   }  //sgsDeleteAllSettings()
  1298: 
  1299:   //sgsPutParameter (map, key, value)
  1300:   //  マップにパラメータを追加する
  1301:   //  デフォルトの設定sgsDefaultMapにないパラメータは無視される。設定名のキー"_"を受け付けないことに注意
  1302:   //  デフォルトの値が"off"または"on"のパラメータの値は"0","no","off"を指定すると"off"、それ以外は"on"に読み替えられる
  1303:   public static void sgsPutParameter (HashMap<String,String> map, String key, String value) {
  1304:     if (sgsDefaultMap.containsKey (key)) {  //設定できるパラメータ
  1305:       String defaultValue = sgsDefaultMap.get (key);  //デフォルトの値
  1306:       if (defaultValue.equals ("off") || defaultValue.equals ("on")) {  //デフォルトの値が"off"または"on"のとき
  1307:         value = (value.equals ("0") ||
  1308:                  value.equalsIgnoreCase ("no") ||
  1309:                  value.equalsIgnoreCase ("off") ? "off" : "on");  //"0","no","off"を"off"にそれ以外を"on"に読み替える
  1310:       }
  1311:       map.put (key, value);  //マップに追加する
  1312:     }
  1313:   }  //sgsPutParameter(HashMap<String,String>,String,String)
  1314: 
  1315: 
  1316: 
  1317:   //text = sgsLoadIniFile ()
  1318:   //  設定ファイルを読み込む
  1319:   public static String sgsLoadIniFile () {
  1320:     return XEiJ.rscGetTextFile (sgsIniPath);
  1321:   }  //sgsLoadIniFile()
  1322: 
  1323:   //sgsSaveIniFile ()
  1324:   //  設定ファイルに書き出す
  1325:   public static void sgsSaveIniFile (ArrayList<String> strings) {
  1326:     XEiJ.rscPutTextFile (sgsIniPath, strings);
  1327:   }
  1328: 
  1329:   //sgsDeleteIniFile ()
  1330:   //  設定ファイルを削除する
  1331:   public static void sgsDeleteIniFile () {
  1332:     if (sgsIniFile.isFile ()) {  //XEiJ.iniがある
  1333:       if (sgsIniFile.delete ()) {  //XEiJ.iniを削除する。削除できた
  1334:         System.out.println (sgsIniPath + (Multilingual.mlnJapanese ? " を削除しました" : " was removed"));
  1335:       } else {  //削除できない
  1336:         System.out.println (sgsIniPath + (Multilingual.mlnJapanese ? " を削除できません" : " cannot be removed"));
  1337:         return;
  1338:       }
  1339:     }
  1340:     String bakPath = sgsIniPath + ".bak";
  1341:     File bakFile = new File (bakPath);
  1342:     if (bakFile.isFile ()) {  //XEiJ.ini.bakがある
  1343:       if (bakFile.delete ()) {  //XEiJ.ini.bakを削除する。削除できた
  1344:         System.out.println (bakPath + (Multilingual.mlnJapanese ? " を削除しました" : " was removed"));
  1345:       } else {  //削除できない
  1346:         System.out.println (bakPath + (Multilingual.mlnJapanese ? " を削除できません" : " cannot be removed"));
  1347:         return;
  1348:       }
  1349:     }
  1350:     String tmpPath = sgsIniPath + ".tmp";
  1351:     File tmpFile = new File (tmpPath);
  1352:     if (tmpFile.isFile ()) {  //XEiJ.ini.tmpがある
  1353:       if (tmpFile.delete ()) {  //XEiJ.ini.tmpを削除する。削除できた
  1354:         System.out.println (tmpPath + (Multilingual.mlnJapanese ? " を削除しました" : " was removed"));
  1355:       } else {  //削除できない
  1356:         System.out.println (tmpPath + (Multilingual.mlnJapanese ? " を削除できません" : " cannot be removed"));
  1357:         return;
  1358:       }
  1359:     }
  1360:     if (sgsIniParentDirectory != null &&
  1361:         sgsIniParentDirectory.isDirectory ()) {  //AppData/Roaming/XEiJがある
  1362:       if (sgsIniParentDirectory.delete ()) {  //AppData/Roaming/XEiJの削除を試みる。ディレクトリが空でなければ失敗する。削除できた
  1363:         System.out.println (sgsIniParentPath + (Multilingual.mlnJapanese ? " を削除しました" : " was removed"));
  1364:       } else {  //削除できない
  1365:         System.out.println (sgsIniParentPath + (Multilingual.mlnJapanese ? " を削除できません" : " cannot be removed"));
  1366:       }
  1367:     }
  1368:   }  //sgsDeleteIniFile()
  1369: 
  1370: 
  1371: 
  1372: }  //class Settings
  1373: 
  1374: 
  1375: