1 package charactermanaj;
5 import java.awt.GraphicsEnvironment;
6 import java.beans.PropertyChangeEvent;
7 import java.beans.PropertyChangeListener;
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;
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;
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;
46 public final class Main implements Runnable {
51 private static final Logger logger = Logger.getLogger(Main.class.getName());
56 private static final boolean isMacOSX;
61 private static final boolean isLinux;
65 * 実行環境に関する定数を取得・設定する.<br>
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;
81 private static void initLogger() {
85 // ローカルファイルシステム上のユーザ定義ディレクトリから
86 // ログの設定を読み取る.(OSにより、設定ファイルの位置が異なることに注意)
87 ApplicationLoggerConfigurator.configure();
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() {
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);
109 // SwingのEDT内の例外ハンドラの設定 (ロギングするだけ)
110 // (ただし、unofficial trickである.)
111 System.setProperty("sun.awt.exception.handler",
112 AWTExceptionLoggingHandler.class.getName());
115 } catch (Throwable ex) {
116 // ロガーの準備に失敗した場合はロガーがないかもなので
118 ex.printStackTrace();
119 logger.log(Level.SEVERE, "logger initiation failed. " + ex, ex);
130 private static void setupUIManager(final AppConfig appConfig) throws Exception {
131 // System.setProperty("swing.aatext", "true");
132 // System.setProperty("awt.useSystemAAFontSettings", "on");
135 // MacOSXであれば、スクリーンメニューを有効化
136 System.setProperty("apple.laf.useScreenMenuBar", "true");
138 "com.apple.mrj.application.apple.menu.about.name",
141 // Java7以降であればノーマライズをセットアップする.
142 if (JavaVersionUtils.getJavaVersion() >= 1.7) {
143 charactermanaj.util.FileNameNormalizer.setupNFCNormalizer();
147 // 実行プラットフォームのネイティブな外観にする.
148 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
150 // JSpliderのvalueを非表示 (GTKではデフォルトで有効のため)
151 UIManager.put("Slider.paintValue", Boolean.FALSE);
154 setupDefaultFont(appConfig);
156 // アプリケーション設定でデフォルトフォントサイズが変更された場合は
157 // 現在表示されている、すべてのフレームに再適用を試行する。
158 appConfig.addPropertyChangeListener(AppConfig.DEFAULT_FONT_SIZE, new PropertyChangeListener() {
160 public void propertyChange(PropertyChangeEvent evt) {
161 setupDefaultFont(appConfig);
163 for (Frame frame : Frame.getFrames()) {
164 SwingUtilities.updateComponentTreeUI(frame);
166 } catch (Exception ex) {
167 logger.log(Level.WARNING, "failed revalidate frames", ex);
173 private static void setupDefaultFont(AppConfig appConfig) {
175 // 優先するフォントファミリ中の実在するフォントファミリのセット(大文字小文字の区別なし)
176 TreeSet<String> availablePriorityFontSets = new TreeSet<String>(
177 String.CASE_INSENSITIVE_ORDER);
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);
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);
213 logger.log(Level.INFO, "このフォントはメニュー表示に使用できません: "
214 + selectedFontFamily + "/codepoint=0x" + Integer.toHexString(codepoint));
219 if (selectedFontFamily == null) {
220 // 最初に見つかったメニューを表示可能な優先フォント
221 selectedFontFamily = availableFontFamily;
223 // メニューを表示可能なフォントのみ候補に入れる
224 availablePriorityFontSets.add(fontFamily);
230 if (selectedFontFamily == null) {
231 // フォールバック用フォントとして「Dialog」を用いる.
232 // 仮想フォントファミリである「Dialog」は日本語も表示可能である.
233 selectedFontFamily = "Dialog";
237 // デフォルトのフォントサイズ、0以下の場合はシステム標準のまま
238 int defFontSize = appConfig.getDefaultFontSize();
240 // UIデフォルトのフォント設定で、優先フォント以外のフォントファミリが指定されているものを
241 // すべて最優先フォントファミリに設定する.
242 // また、設定されたフォントサイズが0よりも大きければ、そのサイズに設定する.
243 for (java.util.Map.Entry<?, ?> entry : UIManager.getDefaults()
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();
252 if (defFontSize > 0) {
253 fontSize = defFontSize;
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);
264 fontFamily = selectedFontFamily;
267 fontUIResource = new FontUIResource(fontFamily,
268 fontUIResource.getStyle(), fontSize);
269 UIManager.put(entry.getKey(), fontUIResource);
275 * 初期処理およびメインフレームを構築する.<br>
276 * SwingのUIスレッドで実行される.<br>
281 AppConfig appConfig = AppConfig.getInstance();
282 appConfig.loadConfig();
286 setupUIManager(appConfig);
288 } catch (Exception ex) {
289 // UIManagerの設定に失敗した場合はログに書いて継続する.
290 ex.printStackTrace();
291 logger.log(Level.WARNING, "UIManager setup failed.", ex);
295 if (!ImageSelection.setupSystemFlavorMap()) {
296 logger.log(Level.WARNING,
297 "failed to set the clipboard-support.");
300 // LANG, またはLC_CTYPEが設定されていない場合はエラーを表示する
301 // OSXのJava7(Oracle)を実行する場合、環境変数LANGまたはLC_CTYPEに正しくファイル名の文字コードが設定されていないと
302 // ファイル名を正しく取り扱えず文字化けするため、実行前に確認し警告を表示する。
303 // ただし、この挙動はJava7u60では修正されているので、それ以降であれば除外する.
304 int[] versions = JavaVersionUtils.getJavaVersions();
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)) {
314 "\"LANG\" or \"LC_CTYPE\" environment variable must be set.",
315 "Configuration Error",
316 JOptionPane.ERROR_MESSAGE);
321 // ver0.999ではキャラクターデータディレクトリに依存しない初期化部しかないので最初に移動する。
322 // (APPDATAからLOCALAPPDATAへの移動処理などがあるため、先に行う必要がある。)
323 StartupSupport.getInstance().doStartup();
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;
335 if (currentCharacterDir == null) {
336 // キャラクターセットディレクトリの選択
337 File defaultCharacterDir = ConfigurationDirUtilities
338 .getDefaultCharactersDir();
339 currentCharacterDir = SelectCharatersDirDialog
340 .getCharacterDir(defaultCharacterDir);
341 if (currentCharacterDir == null) {
343 logger.info("luncher canceled.");
349 DirectoryConfig.getInstance().setCharactersDir(currentCharacterDir);
352 // (最後に使ったプロファイルがあれば、それが開かれる.)
353 final MainFrame mainFrame = ProfileListManager.openDefaultProfile();
356 if (JavaVersionUtils.getJavaVersion() >= 9) {
357 // OSXでJava9以降であればOracle実装でスクリーンメニュー類を設定する.
358 MainFramePartialForMacOSX9.setupScreenMenu(mainFrame);
361 // Java9未満であればeawtでスクリーンメニュー類を設定する.
362 // MacOSXであればスクリーンメニューからのイベントをハンドルできるようにする.
363 // OSXにしか存在しないクラスを利用するためリフレクションとしている.
364 // ただしJDKによっては、Apple Java Extensionsがないことも予想されるので、
367 .forName("charactermanaj.ui.MainFramePartialForMacOSX");
368 Method mtd = clz.getMethod("setupScreenMenu",
370 mtd.invoke(null, mainFrame);
373 } catch (Throwable ex) {
374 logger.log(Level.CONFIG, "Failed to setup the screen menu.", ex);
379 mainFrame.showMainFrame();
381 } catch (Throwable ex) {
382 // なんらかの致命的な初期化エラーがあった場合、ログとコンソールに表示
383 // ダイアログが表示されるかどうかは状況次第.
384 ex.printStackTrace();
385 logger.log(Level.SEVERE, "Application initiation failed.", ex);
386 ErrorMessageHelper.showErrorDialog(null, ex);
389 MainFrame.closeAllProfiles();
395 * 最初のメインフレームを開いたときにMac OS Xであればスクリーンメニューの登録も行う.<br>
400 public static void main(String[] args) {
406 ImageCacheMBeanImpl.setupMBean();
408 } catch (JMException ex) {
410 logger.log(Level.SEVERE, ex.getMessage(), ex);
413 // フレームの生成等は、SwingのEDTで実行する.
414 SwingUtilities.invokeLater(new Main());
420 * @return Max OS X上であればtrue
422 public static boolean isMacOSX() {
427 * Mac OS X、もしくはlinuxで動作しているか?
429 * @return Mac OS X、もしくはlinuxで動作していればtrue
431 public static boolean isLinuxOrMacOSX() {
432 return isLinux || isMacOSX;