OSDN Git Service

932ab9bffc86a0f3aa6258d4bf7fa5e3d31c5a4e
[charactermanaj/CharacterManaJ.git] / src / main / java / charactermanaj / Main.java
1 package charactermanaj;
2
3 import java.awt.Font;
4 import java.awt.Frame;
5 import java.awt.GraphicsEnvironment;
6 import java.beans.PropertyChangeEvent;
7 import java.beans.PropertyChangeListener;
8 import java.io.File;
9 import java.lang.Thread.UncaughtExceptionHandler;
10 import java.lang.reflect.Method;
11 import java.util.Enumeration;
12 import java.util.HashSet;
13 import java.util.Locale;
14 import java.util.Properties;
15 import java.util.TreeSet;
16 import java.util.logging.Level;
17 import java.util.logging.Logger;
18
19 import javax.management.JMException;
20 import javax.swing.JOptionPane;
21 import javax.swing.SwingUtilities;
22 import javax.swing.UIManager;
23 import javax.swing.plaf.FontUIResource;
24
25 import charactermanaj.clipboardSupport.ImageSelection;
26 import charactermanaj.graphics.io.ImageCacheMBeanImpl;
27 import charactermanaj.model.AppConfig;
28 import charactermanaj.model.util.StartupSupport;
29 import charactermanaj.ui.MainFrame;
30 import charactermanaj.ui.MainFramePartialForMacOSX9;
31 import charactermanaj.ui.ProfileListManager;
32 import charactermanaj.ui.SelectCharatersDirDialog;
33 import charactermanaj.util.AWTExceptionLoggingHandler;
34 import charactermanaj.util.ApplicationLoggerConfigurator;
35 import charactermanaj.util.ConfigurationDirUtilities;
36 import charactermanaj.util.DirectoryConfig;
37 import charactermanaj.util.ErrorMessageHelper;
38 import charactermanaj.util.JavaVersionUtils;
39 import charactermanaj.util.LocalizedResourcePropertyLoader;
40
41 /**
42  * エントリポイント用クラス
43  *
44  * @author seraphy
45  */
46 public final class Main implements Runnable {
47
48         /**
49          * ロガー.<br>
50          */
51         private static final Logger logger = Logger.getLogger(Main.class.getName());
52
53         /**
54          * Mac OS Xであるか?
55          */
56         private static final boolean isMacOSX;
57
58         /**
59          * Linuxであるか?
60          */
61         private static final boolean isLinux;
62
63         /**
64          * クラスイニシャライザ.<br>
65          * 実行環境に関する定数を取得・設定する.<br>
66          */
67         static {
68                 // Mac OS Xでの実行判定
69                 // システムプロパティos.nameは、すべてのJVM実装に存在する.
70                 // 基本ディレクトリの位置の決定に使うため、
71                 // なによりも、まず、これを判定しないとダメ.(順序が重要)
72                 String lcOS = System.getProperty("os.name").toLowerCase();
73                 isMacOSX = lcOS.startsWith("mac os x");
74                 isLinux = lcOS.indexOf("linux") >= 0;
75         }
76
77         /**
78          * ロガーの初期化.<br>
79          * 失敗しても継続する.<br>
80          */
81         private static void initLogger() {
82                 try {
83                         // ロガーの準備
84
85                         // ローカルファイルシステム上のユーザ定義ディレクトリから
86                         // ログの設定を読み取る.(OSにより、設定ファイルの位置が異なることに注意)
87                         ApplicationLoggerConfigurator.configure();
88
89                         if (JavaVersionUtils.getJavaVersion() >= 1.7) {
90                                 // java7以降は、sun.awt.exception.handlerが使えないので、
91                                 // EDTスレッドで未処理例外ハンドラを明示的に設定する.
92                                 final AWTExceptionLoggingHandler logHandler = new AWTExceptionLoggingHandler();
93                                 SwingUtilities.invokeLater(new Runnable() {
94                                         public void run() {
95                                                 final UncaughtExceptionHandler handler = Thread
96                                                                 .getDefaultUncaughtExceptionHandler();
97                                                 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
98                                                         public void uncaughtException(Thread t, Throwable ex) {
99                                                                 logHandler.handle(ex);
100                                                                 if (handler != null) {
101                                                                         handler.uncaughtException(t, ex);
102                                                                 }
103                                                         }
104                                                 });
105                                         }
106                                 });
107
108                         } else {
109                                 // SwingのEDT内の例外ハンドラの設定 (ロギングするだけ)
110                                 // (ただし、unofficial trickである.)
111                                 System.setProperty("sun.awt.exception.handler",
112                                                 AWTExceptionLoggingHandler.class.getName());
113                         }
114
115                 } catch (Throwable ex) {
116                         // ロガーの準備に失敗した場合はロガーがないかもなので
117                         // コンソールに出力する.
118                         ex.printStackTrace();
119                         logger.log(Level.SEVERE, "logger initiation failed. " + ex, ex);
120                 }
121         }
122
123
124         /**
125          * UIをセットアップする.
126          *
127          * @throws Exception
128          *             いろいろな失敗
129          */
130         private static void setupUIManager(final AppConfig appConfig) throws Exception {
131                 // System.setProperty("swing.aatext", "true");
132                 // System.setProperty("awt.useSystemAAFontSettings", "on");
133
134                 if (isMacOSX()) {
135                         // MacOSXであれば、スクリーンメニューを有効化
136                         System.setProperty("apple.laf.useScreenMenuBar", "true");
137                         System.setProperty(
138                                         "com.apple.mrj.application.apple.menu.about.name",
139                                         "CharacterManaJ");
140
141                         // Java7以降であればノーマライズをセットアップする.
142                         if (JavaVersionUtils.getJavaVersion() >= 1.7) {
143                                 charactermanaj.util.FileNameNormalizer.setupNFCNormalizer();
144                         }
145                 }
146
147                 // 実行プラットフォームのネイティブな外観にする.
148                 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
149
150                 // JSpliderのvalueを非表示 (GTKではデフォルトで有効のため)
151                 UIManager.put("Slider.paintValue", Boolean.FALSE);
152
153                 // デフォルトフォントを設定する
154                 setupDefaultFont(appConfig);
155
156                 // アプリケーション設定でデフォルトフォントサイズが変更された場合は
157                 // 現在表示されている、すべてのフレームに再適用を試行する。
158                 appConfig.addPropertyChangeListener(AppConfig.DEFAULT_FONT_SIZE, new PropertyChangeListener() {
159                         @Override
160                         public void propertyChange(PropertyChangeEvent evt) {
161                                 setupDefaultFont(appConfig);
162                                 try {
163                                         for (Frame frame : Frame.getFrames()) {
164                                                 SwingUtilities.updateComponentTreeUI(frame);
165                                         }
166                                 } catch (Exception ex) {
167                                         logger.log(Level.WARNING, "failed revalidate frames", ex);
168                                 }
169                         }
170                 });
171         }
172
173         private static void setupDefaultFont(AppConfig appConfig) {
174
175                 // 優先するフォントファミリ中の実在するフォントファミリのセット(大文字小文字の区別なし)
176                 TreeSet<String> availablePriorityFontSets = new TreeSet<String>(
177                                 String.CASE_INSENSITIVE_ORDER);
178
179                 // 少なくともメニューが表示できるようなフォントを選択する
180                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
181                                 .getLocalizedProperties("menu/menu");
182                 HashSet<Integer> useCodePoints = new HashSet<Integer>();
183                 Enumeration<?> enmStrings = strings.propertyNames();
184                 while (enmStrings.hasMoreElements()) {
185                         String propertyName = (String) enmStrings.nextElement();
186                         String propertyValue = strings.getProperty(propertyName);
187                         int len = propertyValue.length();
188                         for (int idx = 0; idx < len;) {
189                                 int codepoint = propertyValue.codePointAt(idx);
190                                 useCodePoints.add(codepoint);
191                                 idx += Character.charCount(codepoint);
192                         }
193                 }
194
195                 // 優先するフォントファミリの実在チェックと、もっとも優先されるフォントファミリの確定
196                 String selectedFontFamily = null;
197                 String fontPriorityStr = appConfig.getFontPriority();
198                 if (fontPriorityStr.trim().length() > 0) {
199                         String[] fontPriority = fontPriorityStr.split(",");
200                         for (String availableFontFamily : GraphicsEnvironment
201                                         .getLocalGraphicsEnvironment().getAvailableFontFamilyNames(Locale.ENGLISH)) {
202                                 for (String fontFamily : fontPriority) {
203                                         fontFamily = fontFamily.trim();
204                                         if (fontFamily.length() > 0) {
205                                                 if (availableFontFamily.equalsIgnoreCase(fontFamily)) {
206                                                         // 見つかった実在フォントが、現在のロケールのメニューを正しく表示できるか?
207                                                         Font font = Font.decode(availableFontFamily);
208                                                         logger.log(Level.INFO, "実在するフォントの確認:" + availableFontFamily);
209                                                         boolean canDisplay = false;
210                                                         for (Integer codepoint : useCodePoints) {
211                                                                 canDisplay = font.canDisplay(codepoint);
212                                                                 if (!canDisplay) {
213                                                                         logger.log(Level.INFO, "このフォントはメニュー表示に使用できません: "
214                                                                                         + selectedFontFamily + "/codepoint=0x" + Integer.toHexString(codepoint));
215                                                                         break;
216                                                                 }
217                                                         }
218                                                         if (canDisplay) {
219                                                                 if (selectedFontFamily == null) {
220                                                                         // 最初に見つかったメニューを表示可能な優先フォント
221                                                                         selectedFontFamily = availableFontFamily;
222                                                                 }
223                                                                 // メニューを表示可能なフォントのみ候補に入れる
224                                                                 availablePriorityFontSets.add(fontFamily);
225                                                         }
226                                                 }
227                                         }
228                                 }
229                         }
230                         if (selectedFontFamily == null) {
231                                 // フォールバック用フォントとして「Dialog」を用いる.
232                                 // 仮想フォントファミリである「Dialog」は日本語も表示可能である.
233                                 selectedFontFamily = "Dialog";
234                         }
235                 }
236
237                 // デフォルトのフォントサイズ、0以下の場合はシステム標準のまま
238                 int defFontSize = appConfig.getDefaultFontSize();
239
240                 // UIデフォルトのフォント設定で、優先フォント以外のフォントファミリが指定されているものを
241                 // すべて最優先フォントファミリに設定する.
242                 // また、設定されたフォントサイズが0よりも大きければ、そのサイズに設定する.
243                 for (java.util.Map.Entry<?, ?> entry : UIManager.getDefaults()
244                                 .entrySet()) {
245                         Object key = entry.getKey();
246                         Object val = UIManager.get(key);
247                         if (val instanceof FontUIResource) {
248                                 FontUIResource fontUIResource = (FontUIResource) val;
249                                 int fontSize = fontUIResource.getSize();
250                                 String fontFamily = fontUIResource.getFamily();
251
252                                 if (defFontSize > 0) {
253                                         fontSize = defFontSize;
254                                 }
255
256                                 if (selectedFontFamily != null
257                                                 && !availablePriorityFontSets.contains(fontFamily)) {
258                                         // 現在のデフォルトUIに指定された優先フォント以外が設定されており、
259                                         // 且つ、優先フォントの指定があれば、優先フォントに差し替える.
260                                         if (logger.isLoggable(Level.FINE)) {
261                                                 logger.log(Level.FINE, "UIDefaultFont: " + key +
262                                                                 "= " + fontFamily + " -> " + selectedFontFamily);
263                                         }
264                                         fontFamily = selectedFontFamily;
265                                 }
266
267                                 fontUIResource = new FontUIResource(fontFamily,
268                                                 fontUIResource.getStyle(), fontSize);
269                                 UIManager.put(entry.getKey(), fontUIResource);
270                         }
271                 }
272         }
273
274         /**
275          * 初期処理およびメインフレームを構築する.<br>
276          * SwingのUIスレッドで実行される.<br>
277          */
278         public void run() {
279                 try {
280                         // アプリケーション設定の読み込み
281                         AppConfig appConfig = AppConfig.getInstance();
282                         appConfig.loadConfig();
283
284                         // UIManagerのセットアップ.
285                         try {
286                                 setupUIManager(appConfig);
287
288                         } catch (Exception ex) {
289                                 // UIManagerの設定に失敗した場合はログに書いて継続する.
290                                 ex.printStackTrace();
291                                 logger.log(Level.WARNING, "UIManager setup failed.", ex);
292                         }
293
294                         // クリップボードサポートの設定
295                         if (!ImageSelection.setupSystemFlavorMap()) {
296                                 logger.log(Level.WARNING,
297                                                 "failed to set the clipboard-support.");
298                         }
299
300                         // LANG, またはLC_CTYPEが設定されていない場合はエラーを表示する
301                         // OSXのJava7(Oracle)を実行する場合、環境変数LANGまたはLC_CTYPEに正しくファイル名の文字コードが設定されていないと
302                         // ファイル名を正しく取り扱えず文字化けするため、実行前に確認し警告を表示する。
303                         // ただし、この挙動はJava7u60では修正されているので、それ以降であれば除外する.
304                         int[] versions = JavaVersionUtils.getJavaVersions();
305                         if (isMacOSX()
306                                         && (versions[0] == 1 && versions[1] == 7 && versions[3] < 60)) {
307                                 String lang = System.getenv("LANG");
308                                 String lcctype = System.getenv("LC_CTYPE");
309                                 if ((lang == null || lang.trim().length() == 0)
310                                                 && (lcctype == null || lcctype.trim().length() == 0)) {
311                                         JOptionPane
312                                                         .showMessageDialog(
313                                                                         null,
314                                                                         "\"LANG\" or \"LC_CTYPE\" environment variable must be set.",
315                                                                         "Configuration Error",
316                                                                         JOptionPane.ERROR_MESSAGE);
317                                 }
318                         }
319
320                         // スタートアップ時の初期化
321                         // ver0.999ではキャラクターデータディレクトリに依存しない初期化部しかないので最初に移動する。
322                         // (APPDATAからLOCALAPPDATAへの移動処理などがあるため、先に行う必要がある。)
323                         StartupSupport.getInstance().doStartup();
324
325                         // 起動時のシステムプロパティでキャラクターディレクトリが指定されていて実在すれば、それを優先する.
326                         File currentCharacterDir = null;
327                         String charactersDir = System.getProperty("charactersDir");
328                         if (charactersDir != null && charactersDir.length() > 0) {
329                                 File charsDir = new File(charactersDir);
330                                 if (charsDir.exists() && charsDir.isDirectory()) {
331                                         currentCharacterDir = charsDir;
332                                 }
333                         }
334
335                         if (currentCharacterDir == null) {
336                                 // キャラクターセットディレクトリの選択
337                                 File defaultCharacterDir = ConfigurationDirUtilities
338                                                 .getDefaultCharactersDir();
339                                 currentCharacterDir = SelectCharatersDirDialog
340                                                 .getCharacterDir(defaultCharacterDir);
341                                 if (currentCharacterDir == null) {
342                                         // キャンセルされたので終了する.
343                                         logger.info("luncher canceled.");
344                                         return;
345                                 }
346                         }
347
348                         // キャラクターデータフォルダの設定
349                         DirectoryConfig.getInstance().setCharactersDir(currentCharacterDir);
350
351                         // デフォルトのプロファイルを開く.
352                         // (最後に使ったプロファイルがあれば、それが開かれる.)
353                         final MainFrame mainFrame = ProfileListManager.openDefaultProfile();
354                         if (isMacOSX()) {
355                                 try {
356                                         if (JavaVersionUtils.getJavaVersion() >= 9) {
357                                                 // OSXでJava9以降であればOracle実装でスクリーンメニュー類を設定する.
358                                                 MainFramePartialForMacOSX9.setupScreenMenu(mainFrame);
359
360                                         } else {
361                                                 // Java9未満であればeawtでスクリーンメニュー類を設定する.
362                                                 // MacOSXであればスクリーンメニューからのイベントをハンドルできるようにする.
363                                                 // OSXにしか存在しないクラスを利用するためリフレクションとしている.
364                                                 // ただしJDKによっては、Apple Java Extensionsがないことも予想されるので、
365                                                 // その場合はエラーにしない。
366                                                 Class<?> clz = Class
367                                                                 .forName("charactermanaj.ui.MainFramePartialForMacOSX");
368                                                 Method mtd = clz.getMethod("setupScreenMenu",
369                                                                 MainFrame.class);
370                                                 mtd.invoke(null, mainFrame);
371                                         }
372
373                                 } catch (Throwable ex) {
374                                         logger.log(Level.CONFIG, "Failed to setup the screen menu.", ex);
375                                 }
376                         }
377
378                         // 表示(および位置あわせ)
379                         mainFrame.showMainFrame();
380
381                 } catch (Throwable ex) {
382                         // なんらかの致命的な初期化エラーがあった場合、ログとコンソールに表示
383                         // ダイアログが表示されるかどうかは状況次第.
384                         ex.printStackTrace();
385                         logger.log(Level.SEVERE, "Application initiation failed.", ex);
386                         ErrorMessageHelper.showErrorDialog(null, ex);
387
388                         // メインフレームを破棄します.
389                         MainFrame.closeAllProfiles();
390                 }
391         }
392
393         /**
394          * エントリポイント.<br>
395          * 最初のメインフレームを開いたときにMac OS Xであればスクリーンメニューの登録も行う.<br>
396          *
397          * @param args
398          *            引数(未使用)
399          */
400         public static void main(String[] args) {
401                 // ロガー等の初期化
402                 initLogger();
403
404                 // MBeanのセットアップ
405                 try {
406                         ImageCacheMBeanImpl.setupMBean();
407
408                 } catch (JMException ex) {
409                         // 失敗しても無視して継続する.
410                         logger.log(Level.SEVERE, ex.getMessage(), ex);
411                 }
412
413                 // フレームの生成等は、SwingのEDTで実行する.
414                 SwingUtilities.invokeLater(new Main());
415         }
416
417         /**
418          * Mac OS Xで動作しているか?
419          *
420          * @return Max OS X上であればtrue
421          */
422         public static boolean isMacOSX() {
423                 return isMacOSX;
424         }
425
426         /**
427          * Mac OS X、もしくはlinuxで動作しているか?
428          *
429          * @return Mac OS X、もしくはlinuxで動作していればtrue
430          */
431         public static boolean isLinuxOrMacOSX() {
432                 return isLinux || isMacOSX;
433         }
434 }