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