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