OSDN Git Service

modify log message about user-image loading.
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / config / AppSetting.java
1 /*
2  * application settings
3  *
4  * License : The MIT License
5  * Copyright(c) 2008 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.config;
9
10 import java.awt.Font;
11 import java.awt.Rectangle;
12 import java.awt.image.BufferedImage;
13 import java.io.File;
14 import java.io.IOException;
15 import java.nio.file.Path;
16 import java.nio.file.Paths;
17 import java.text.MessageFormat;
18 import java.util.HashMap;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.logging.Logger;
22 import javax.imageio.ImageIO;
23 import jp.sfjp.jindolf.data.Avatar;
24 import jp.sfjp.jindolf.data.DialogPref;
25 import jp.sfjp.jindolf.glyph.Font2Json;
26 import jp.sfjp.jindolf.glyph.FontInfo;
27 import jp.sfjp.jindolf.net.ProxyInfo;
28 import jp.sfjp.jindolf.view.AvatarPics;
29 import jp.sfjp.jindolf.view.LocalAvatarImg;
30 import jp.sourceforge.jovsonz.JsBoolean;
31 import jp.sourceforge.jovsonz.JsObject;
32 import jp.sourceforge.jovsonz.JsPair;
33 import jp.sourceforge.jovsonz.JsString;
34 import jp.sourceforge.jovsonz.JsTypes;
35 import jp.sourceforge.jovsonz.JsValue;
36
37 /**
38  * アプリケーションの各種設定。
39  */
40 public class AppSetting{
41
42     // デフォルトのウィンドウサイズ
43     private static final int DEF_WIDTH  = 800;
44     private static final int DEF_HEIGHT = 600;
45
46     private static final String HASH_FONT        = "font";
47     private static final String HASH_USEBODYICON = "useBodyIcon";
48     private static final String HASH_USEMONOTOMB = "useMonoTomb";
49     private static final String HASH_SIMPLEMODE  = "isSimpleMode";
50     private static final String HASH_ALIGNBALOON = "alignBaloonWidth";
51     private static final String HASH_PROXY       = "proxy";
52
53     private static final String MSG_NOIMG =
54             "画像ファイル{0}が読み込めないため"
55             + "{1}の表示に代替イメージを使います。";
56
57     private static final Logger LOGGER = Logger.getAnonymousLogger();
58
59
60     private final OptionInfo optInfo;
61     private final ConfigStore configStore;
62     private final Rectangle frameRect;
63
64     private FontInfo fontInfo;
65
66     private ProxyInfo proxyInfo = ProxyInfo.DEFAULT;
67
68     private DialogPref dialogPref = new DialogPref();
69
70     private JsValue loadedNetConfig;
71     private JsValue loadedTalkConfig;
72
73     private final Map<String, BufferedImage> avatarFaceMap = new HashMap<>();
74     private final Map<String, BufferedImage> avatarBodyMap = new HashMap<>();
75
76
77     /**
78      * コンストラクタ。
79      * @param info コマンドライン引数
80      */
81     public AppSetting(OptionInfo info){
82         super();
83
84         this.optInfo = info;
85         this.configStore = parseConfigStore(this.optInfo);
86         this.frameRect = parseGeometrySetting(this.optInfo);
87
88         return;
89     }
90
91
92     /**
93      * 設定格納ディレクトリ関係の解析。
94      * @param option コマンドラインオプション情報
95      * @return 設定ディレクトリ情報
96      */
97     private static ConfigStore parseConfigStore(OptionInfo option){
98         CmdOption opt = option.getExclusiveOption(CmdOption.OPT_CONFDIR,
99                                                   CmdOption.OPT_NOCONF );
100
101         boolean useConfig;
102         boolean isImplicitPath;
103         File configPath;
104
105         if(opt == CmdOption.OPT_NOCONF){
106             useConfig = false;
107             isImplicitPath = true;
108             configPath = null;
109         }else if(opt == CmdOption.OPT_CONFDIR){
110             useConfig = true;
111             isImplicitPath = false;
112             String optPath = option.getStringArg(opt);
113             configPath = FileUtils.supplyFullPath(new File(optPath));
114         }else{
115             useConfig = true;
116             isImplicitPath = true;
117             configPath = ConfigFile.getImplicitConfigDirectory();
118         }
119
120         ConfigStore result =
121                 new ConfigStore(useConfig, isImplicitPath, configPath);
122
123         return result;
124     }
125
126     /**
127      * ウィンドウジオメトリ関係の設定。
128      * @param option コマンドラインオプション情報
129      * @return ウィンドウ矩形。
130      */
131     private static Rectangle parseGeometrySetting(OptionInfo option){
132         Rectangle result = new Rectangle(Integer.MIN_VALUE,
133                                          Integer.MIN_VALUE,
134                                          DEF_WIDTH,
135                                          DEF_HEIGHT );
136
137         Integer ival;
138
139         ival = option.initialFrameWidth();
140         if(ival != null) result.width = ival;
141
142         ival = option.initialFrameHeight();
143         if(ival != null) result.height = ival;
144
145         ival = option.initialFrameXpos();
146         if(ival != null) result.x = ival;
147
148         ival = option.initialFrameYpos();
149         if(ival != null) result.y = ival;
150
151         return result;
152     }
153
154
155     /**
156      * フォントオプションの解析。
157      * @param baseFont 元のフォント設定。
158      * @return コマンドライン設定で補正されたフォント設定
159      */
160     private FontInfo parseFontOption(FontInfo baseFont){
161         FontInfo result;
162
163         String fontName = this.optInfo.getStringArg(CmdOption.OPT_INITFONT);
164         if(fontName != null){
165             Font font = Font.decode(fontName);
166             result = baseFont.deriveFont(font);
167         }else{
168             result = baseFont;
169         }
170
171         Boolean useAntiAlias =
172                 this.optInfo.getBooleanArg(CmdOption.OPT_ANTIALIAS);
173         if(useAntiAlias == null){
174             useAntiAlias = baseFont.isAntiAliased();
175         }
176
177         Boolean useFractional =
178                 this.optInfo.getBooleanArg(CmdOption.OPT_FRACTIONAL);
179         if(useFractional == null){
180             useFractional = baseFont.usesFractionalMetrics();
181         }
182
183         result = result.deriveRenderContext(useAntiAlias,
184                                             useFractional );
185
186         return result;
187     }
188
189     /**
190      * コマンドラインオプション情報を返す。
191      * @return コマンドラインオプション情報
192      */
193     public OptionInfo getOptionInfo(){
194         return this.optInfo;
195     }
196
197     /**
198      * 設定格納情報を返す。
199      * @return 設定格納情報
200      */
201     public ConfigStore getConfigStore(){
202         return this.configStore;
203     }
204
205     /**
206      * 初期のフレーム幅を返す。
207      * @return 初期のフレーム幅
208      */
209     public int initialFrameWidth(){
210         int width = this.frameRect.width;
211         return width;
212     }
213
214     /**
215      * 初期のフレーム高を返す。
216      * @return 初期のフレーム高
217      */
218     public int initialFrameHeight(){
219         int height = this.frameRect.height;
220         return height;
221     }
222
223     /**
224      * 初期のフレーム位置のX座標を返す。
225      * 特に指示されていなければInteger.MIN_VALUEを返す。
226      * @return 初期のフレーム位置のX座標
227      */
228     public int initialFrameXpos(){
229         int xPos = this.frameRect.x;
230         return xPos;
231     }
232
233     /**
234      * 初期のフレーム位置のY座標を返す。
235      * 特に指示されていなければInteger.MIN_VALUEを返す。
236      * @return 初期のフレーム位置のY座標
237      */
238     public int initialFrameYpos(){
239         int yPos = this.frameRect.y;
240         return yPos;
241     }
242
243     /**
244      * フォント設定を返す。
245      * @return フォント設定
246      */
247     public FontInfo getFontInfo(){
248         if(this.fontInfo == null){
249             this.fontInfo = parseFontOption(FontInfo.DEFAULT_FONTINFO);
250         }
251         return this.fontInfo;
252     }
253
254     /**
255      * フォント設定を更新する。
256      * @param fontInfo フォント設定
257      */
258     public void setFontInfo(FontInfo fontInfo){
259         this.fontInfo = fontInfo;
260         return;
261     }
262
263     /**
264      * プロクシ設定を返す。
265      * @return プロクシ設定
266      */
267     public ProxyInfo getProxyInfo(){
268         return this.proxyInfo;
269     }
270
271     /**
272      * プロクシ設定を更新する。
273      * @param proxyInfo プロクシ設定。nullならプロクシなしと解釈される。
274      */
275     public void setProxyInfo(ProxyInfo proxyInfo){
276         if(proxyInfo == null) this.proxyInfo = ProxyInfo.DEFAULT;
277         else                  this.proxyInfo = proxyInfo;
278         return;
279     }
280
281     /**
282      * 発言表示設定を返す。
283      * @return 表示設定
284      */
285     public DialogPref getDialogPref(){
286         return this.dialogPref;
287     }
288
289     /**
290      * 発言表示設定を返す。
291      * @param pref 表示設定
292      */
293     public void setDialogPref(DialogPref pref){
294         if(pref == null) this.dialogPref = new DialogPref();
295         else             this.dialogPref = pref;
296         return;
297     }
298
299     /**
300      * ネットワーク設定をロードする。
301      */
302     private void loadNetConfig(){
303         JsObject root = this.configStore.loadNetConfig();
304         if(root == null) return;
305         this.loadedNetConfig = root;
306
307         JsValue value = root.getValue(HASH_PROXY);
308         if( ! (value instanceof JsObject) ) return;
309         JsObject proxy = (JsObject) value;
310
311         ProxyInfo info = ProxyInfo.decodeJson(proxy);
312
313         setProxyInfo(info);
314
315         return;
316     }
317
318     /**
319      * 会話表示設定をロードする。
320      */
321     private void loadTalkConfig(){
322         JsObject root = this.configStore.loadTalkConfig();
323         if(root == null) return;
324         this.loadedTalkConfig = root;
325
326         JsValue value = root.getValue(HASH_FONT);
327         if(value instanceof JsObject){
328             JsObject font = (JsObject) value;
329             FontInfo info = Font2Json.decodeJson(font);
330             info = parseFontOption(info);
331             setFontInfo(info);
332         }
333
334         DialogPref pref = new DialogPref();
335         JsBoolean boolValue;
336         value = root.getValue(HASH_USEBODYICON);
337         if(value instanceof JsBoolean){
338             boolValue = (JsBoolean) value;
339             pref.setBodyImageSetting(boolValue.booleanValue());
340         }
341         value = root.getValue(HASH_USEMONOTOMB);
342         if(value instanceof JsBoolean){
343             boolValue = (JsBoolean) value;
344             pref.setMonoImageSetting(boolValue.booleanValue());
345         }
346         value = root.getValue(HASH_SIMPLEMODE);
347         if(value instanceof JsBoolean){
348             boolValue = (JsBoolean) value;
349             pref.setSimpleMode(boolValue.booleanValue());
350         }
351         value = root.getValue(HASH_ALIGNBALOON);
352         if(value instanceof JsBoolean){
353             boolValue = (JsBoolean) value;
354             pref.setAlignBalooonWidthSetting(boolValue.booleanValue());
355         }
356         setDialogPref(pref);
357
358         return;
359     }
360
361     /**
362      * JSONをパースしAvatar-Image間マップに反映させる。
363      *
364      * @param json JSON構造
365      * @param map マップ
366      */
367     private void parseImgMap(JsObject json, Map<String, BufferedImage> map){
368         Path imgDir = this.configStore.getLocalImgDir();
369
370         List<JsPair> pairList = json.getPairList();
371         for(JsPair pair : pairList){
372             String avatarId = pair.getName();
373             JsValue value = pair.getValue();
374
375             if(value.getJsTypes() != JsTypes.STRING) continue;
376             JsString sVal = (JsString)value;
377             String imgName = sVal.toRawString();
378
379             Path imgPath = Paths.get(imgName);
380             Path full = imgDir.resolve(imgPath);
381             File file = full.toFile();
382             if(        ! file.isAbsolute()
383                     || ! file.exists()
384                     || ! file.isFile()
385                     || ! file.canRead() ){
386                 String msg = MessageFormat.format(
387                         MSG_NOIMG, file.getPath(), avatarId
388                 );
389                 LOGGER.info(msg);
390                 continue;
391             }
392
393             BufferedImage image;
394             try {
395                 image = ImageIO.read(file);
396             }catch(IOException e){
397                 String msg = MessageFormat.format(
398                         MSG_NOIMG, file.getPath(), avatarId
399                 );
400                 LOGGER.info(msg);
401                 continue;
402             }
403
404             map.put(avatarId, image);
405         }
406
407         return;
408     }
409
410     /**
411      * ローカル画像設定をロードする。
412      */
413     private void loadLocalImageConfig(){
414         JsObject root = this.configStore.loadLocalImgConfig();
415         if(root == null) return;
416
417         JsValue faceConfig = root.getValue("avatarFace");
418         JsValue bodyConfig = root.getValue("avatarBody");
419         if(faceConfig.getJsTypes() != JsTypes.OBJECT) return;
420         if(bodyConfig.getJsTypes() != JsTypes.OBJECT) return;
421
422         JsObject jsonFace = (JsObject) faceConfig;
423         parseImgMap(jsonFace, this.avatarFaceMap);
424
425
426         JsObject jsonBody = (JsObject) bodyConfig;
427         parseImgMap(jsonBody, this.avatarBodyMap);
428
429         return;
430     }
431
432     /**
433      * ローカル代替イメージを画像キャッシュに反映させる。
434      *
435      * @param avatarPics 画像キャッシュ
436      */
437     public void applyLocalImage(AvatarPics avatarPics){
438         BufferedImage graveImage     = this.avatarFaceMap.get("tomb");
439         BufferedImage graveBodyImage = this.avatarBodyMap.get("tomb");
440
441         if(graveImage == null){
442             graveImage = LocalAvatarImg.getGraveImage();
443         }
444         if(graveBodyImage == null){
445             graveBodyImage = LocalAvatarImg.getGraveBodyImage();
446         }
447
448         avatarPics.setGraveImage(graveImage);
449         avatarPics.setGraveBodyImage(graveBodyImage);
450
451         for(Avatar avatar : Avatar.getPredefinedAvatarList()){
452             String avatarId = avatar.getIdentifier();
453
454             BufferedImage faceImage = this.avatarFaceMap.get(avatarId);
455             BufferedImage bodyImage = this.avatarBodyMap.get(avatarId);
456
457             if(faceImage == null){
458                 faceImage = LocalAvatarImg.getAvatarFaceImage(avatarId);
459             }
460             if(bodyImage == null){
461                 bodyImage = LocalAvatarImg.getAvatarBodyImage(avatarId);
462             }
463
464             avatarPics.setAvatarFaceImage(avatar, faceImage);
465             avatarPics.setAvatarBodyImage(avatar, bodyImage);
466         }
467
468         return;
469     }
470
471     /**
472      * ネットワーク設定をセーブする。
473      */
474     private void saveNetConfig(){
475         if( ! getConfigStore().useStoreFile() ) return;
476
477         JsObject root = new JsObject();
478         JsObject proxy = ProxyInfo.buildJson(getProxyInfo());
479         root.putValue(HASH_PROXY, proxy);
480
481         if(this.loadedNetConfig != null){
482             if(this.loadedNetConfig.equals(root)) return;
483         }
484
485         this.configStore.saveNetConfig(root);
486
487         return;
488     }
489
490     /**
491      * 会話表示設定をセーブする。
492      */
493     private void saveTalkConfig(){
494         if( ! getConfigStore().useStoreFile() ) return;
495
496         JsObject root = new JsObject();
497
498         JsObject font = Font2Json.buildJson(getFontInfo());
499         root.putValue(HASH_FONT, font);
500
501         DialogPref pref = getDialogPref();
502         JsPair useBodyIcon =
503                 new JsPair(HASH_USEBODYICON, pref.useBodyImage());
504         JsPair useMonoTomb =
505                 new JsPair(HASH_USEMONOTOMB, pref.useMonoImage());
506         JsPair isSimple =
507                 new JsPair(HASH_SIMPLEMODE, pref.isSimpleMode());
508         JsPair alignBaloon =
509                 new JsPair(HASH_ALIGNBALOON, pref.alignBaloonWidth());
510         root.putPair(useBodyIcon);
511         root.putPair(useMonoTomb);
512         root.putPair(isSimple);
513         root.putPair(alignBaloon);
514
515         if(this.loadedTalkConfig != null){
516             if(this.loadedTalkConfig.equals(root)) return;
517         }
518
519         this.configStore.saveTalkConfig(root);
520
521         return;
522     }
523
524     /**
525      * 各種設定を設定格納ディレクトリからロードする。
526      */
527     public void loadConfig(){
528         loadNetConfig();
529         loadTalkConfig();
530         loadLocalImageConfig();
531         return;
532     }
533
534     /**
535      * 各種設定を設定格納ディレクトリへセーブする。
536      */
537     public void saveConfig(){
538         saveNetConfig();
539         saveTalkConfig();
540         return;
541     }
542
543 }