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