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