1 package charactermanaj;
4 import java.awt.GraphicsEnvironment;
6 import java.lang.Thread.UncaughtExceptionHandler;
7 import java.lang.reflect.Method;
8 import java.util.Enumeration;
9 import java.util.HashSet;
10 import java.util.Locale;
11 import java.util.Properties;
12 import java.util.TreeSet;
13 import java.util.logging.Level;
14 import java.util.logging.Logger;
16 import javax.management.JMException;
17 import javax.swing.JOptionPane;
18 import javax.swing.SwingUtilities;
19 import javax.swing.UIManager;
20 import javax.swing.plaf.FontUIResource;
22 import charactermanaj.clipboardSupport.ImageSelection;
23 import charactermanaj.graphics.io.ImageCacheMBeanImpl;
24 import charactermanaj.model.AppConfig;
25 import charactermanaj.model.util.StartupSupport;
26 import charactermanaj.ui.MainFrame;
27 import charactermanaj.ui.MainFramePartialForMacOSX9;
28 import charactermanaj.ui.ProfileListManager;
29 import charactermanaj.ui.SelectCharatersDirDialog;
30 import charactermanaj.util.AWTExceptionLoggingHandler;
31 import charactermanaj.util.ApplicationLoggerConfigurator;
32 import charactermanaj.util.ConfigurationDirUtilities;
33 import charactermanaj.util.DirectoryConfig;
34 import charactermanaj.util.ErrorMessageHelper;
35 import charactermanaj.util.JavaVersionUtils;
36 import charactermanaj.util.LocalizedResourcePropertyLoader;
43 public final class Main implements Runnable {
48 private static final Logger logger = Logger.getLogger(Main.class.getName());
53 private static final boolean isMacOSX;
58 private static final boolean isLinux;
62 * 実行環境に関する定数を取得・設定する.<br>
66 // システムプロパティos.nameは、すべてのJVM実装に存在する.
67 // 基本ディレクトリの位置の決定に使うため、
68 // なによりも、まず、これを判定しないとダメ.(順序が重要)
69 String lcOS = System.getProperty("os.name").toLowerCase();
70 isMacOSX = lcOS.startsWith("mac os x");
71 isLinux = lcOS.indexOf("linux") >= 0;
78 private static void initLogger() {
82 // ローカルファイルシステム上のユーザ定義ディレクトリから
83 // ログの設定を読み取る.(OSにより、設定ファイルの位置が異なることに注意)
84 ApplicationLoggerConfigurator.configure();
86 if (JavaVersionUtils.getJavaVersion() >= 1.7) {
87 // java7以降は、sun.awt.exception.handlerが使えないので、
88 // EDTスレッドで未処理例外ハンドラを明示的に設定する.
89 final AWTExceptionLoggingHandler logHandler = new AWTExceptionLoggingHandler();
90 SwingUtilities.invokeLater(new Runnable() {
92 final UncaughtExceptionHandler handler = Thread
93 .getDefaultUncaughtExceptionHandler();
94 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
95 public void uncaughtException(Thread t, Throwable ex) {
96 logHandler.handle(ex);
97 if (handler != null) {
98 handler.uncaughtException(t, ex);
106 // SwingのEDT内の例外ハンドラの設定 (ロギングするだけ)
107 // (ただし、unofficial trickである.)
108 System.setProperty("sun.awt.exception.handler",
109 AWTExceptionLoggingHandler.class.getName());
112 } catch (Throwable ex) {
113 // ロガーの準備に失敗した場合はロガーがないかもなので
115 ex.printStackTrace();
116 logger.log(Level.SEVERE, "logger initiation failed. " + ex, ex);
127 private static void setupUIManager(AppConfig appConfig) throws Exception {
128 // System.setProperty("swing.aatext", "true");
129 // System.setProperty("awt.useSystemAAFontSettings", "on");
132 // MacOSXであれば、スクリーンメニューを有効化
133 System.setProperty("apple.laf.useScreenMenuBar", "true");
135 "com.apple.mrj.application.apple.menu.about.name",
138 // Java7以降であればノーマライズをセットアップする.
139 if (JavaVersionUtils.getJavaVersion() >= 1.7) {
140 charactermanaj.util.FileNameNormalizer.setupNFCNormalizer();
144 // 実行プラットフォームのネイティブな外観にする.
145 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
147 // JSpliderのvalueを非表示 (GTKではデフォルトで有効のため)
148 UIManager.put("Slider.paintValue", Boolean.FALSE);
151 // 優先するフォントファミリ中の実在するフォントファミリのセット(大文字小文字の区別なし)
152 TreeSet<String> availablePriorityFontSets = new TreeSet<String>(
153 String.CASE_INSENSITIVE_ORDER);
155 // 少なくともメニューが表示できるようなフォントを選択する
156 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
157 .getLocalizedProperties("menu/menu");
158 HashSet<Character> useChars = new HashSet<Character>();
159 Enumeration<?> enmStrings = strings.propertyNames();
160 while (enmStrings.hasMoreElements()) {
161 String propertyName = (String) enmStrings.nextElement();
162 String propertyValue = strings.getProperty(propertyName);
163 for (char ch : propertyValue.toCharArray()) {
168 // 優先するフォントファミリの実在チェックと、もっとも優先されるフォントファミリの確定
169 String selectedFontFamily = null;
170 String fontPriorityStr = appConfig.getFontPriority();
171 if (fontPriorityStr.trim().length() > 0) {
172 String[] fontPriority = fontPriorityStr.split(",");
173 for (String availableFontFamily : GraphicsEnvironment
174 .getLocalGraphicsEnvironment().getAvailableFontFamilyNames(Locale.ENGLISH)) {
175 for (String fontFamily : fontPriority) {
176 fontFamily = fontFamily.trim();
177 if (fontFamily.length() > 0) {
178 if (availableFontFamily.equalsIgnoreCase(fontFamily)) {
179 // 見つかった実在フォントが、現在のロケールのメニューを正しく表示できるか?
180 Font font = Font.decode(availableFontFamily);
181 logger.log(Level.INFO, "実在するフォントの確認:" + availableFontFamily);
182 boolean canDisplay = false;
183 for (char ch : useChars) {
184 canDisplay = font.canDisplay(ch);
186 logger.log(Level.INFO,
187 "このフォントはメニュー表示に使用できません: "
188 + selectedFontFamily + "/ch=" + ch);
193 if (selectedFontFamily == null) {
194 // 最初に見つかったメニューを表示可能な優先フォント
195 selectedFontFamily = availableFontFamily;
197 // メニューを表示可能なフォントのみ候補に入れる
198 availablePriorityFontSets.add(fontFamily);
204 if (selectedFontFamily == null) {
205 // フォールバック用フォントとして「Dialog」を用いる.
206 // 仮想フォントファミリである「Dialog」は日本語も表示可能である.
207 selectedFontFamily = "Dialog";
211 // デフォルトのフォントサイズ、0以下の場合はシステム標準のまま
212 int defFontSize = appConfig.getDefaultFontSize();
214 // UIデフォルトのフォント設定で、優先フォント以外のフォントファミリが指定されているものを
215 // すべて最優先フォントファミリに設定する.
216 // また、設定されたフォントサイズが0よりも大きければ、そのサイズに設定する.
217 for (java.util.Map.Entry<?, ?> entry : UIManager.getDefaults()
219 Object key = entry.getKey();
220 Object val = UIManager.get(key);
221 if (val instanceof FontUIResource) {
222 FontUIResource fontUIResource = (FontUIResource) val;
223 int fontSize = fontUIResource.getSize();
224 String fontFamily = fontUIResource.getFamily();
226 if (defFontSize > 0) {
227 fontSize = defFontSize;
230 if (selectedFontFamily != null
231 && !availablePriorityFontSets.contains(fontFamily)) {
232 // 現在のデフォルトUIに指定された優先フォント以外が設定されており、
233 // 且つ、優先フォントの指定があれば、優先フォントに差し替える.
234 if (logger.isLoggable(Level.FINE)) {
235 logger.log(Level.FINE, "UIDefaultFont: " + key +
236 "= " + fontFamily + " -> " + selectedFontFamily);
238 fontFamily = selectedFontFamily;
241 fontUIResource = new FontUIResource(fontFamily,
242 fontUIResource.getStyle(), fontSize);
243 UIManager.put(entry.getKey(), fontUIResource);
249 * 初期処理およびメインフレームを構築する.<br>
250 * SwingのUIスレッドで実行される.<br>
255 AppConfig appConfig = AppConfig.getInstance();
256 appConfig.loadConfig();
260 setupUIManager(appConfig);
262 } catch (Exception ex) {
263 // UIManagerの設定に失敗した場合はログに書いて継続する.
264 ex.printStackTrace();
265 logger.log(Level.WARNING, "UIManager setup failed.", ex);
269 if (!ImageSelection.setupSystemFlavorMap()) {
270 logger.log(Level.WARNING,
271 "failed to set the clipboard-support.");
274 // LANG, またはLC_CTYPEが設定されていない場合はエラーを表示する
275 // OSXのJava7(Oracle)を実行する場合、環境変数LANGまたはLC_CTYPEに正しくファイル名の文字コードが設定されていないと
276 // ファイル名を正しく取り扱えず文字化けするため、実行前に確認し警告を表示する。
277 // ただし、この挙動はJava7u60では修正されているので、それ以降であれば除外する.
278 int[] versions = JavaVersionUtils.getJavaVersions();
280 && (versions[0] == 1 && versions[1] == 7 && versions[3] < 60)) {
281 String lang = System.getenv("LANG");
282 String lcctype = System.getenv("LC_CTYPE");
283 if ((lang == null || lang.trim().length() == 0)
284 && (lcctype == null || lcctype.trim().length() == 0)) {
288 "\"LANG\" or \"LC_CTYPE\" environment variable must be set.",
289 "Configuration Error",
290 JOptionPane.ERROR_MESSAGE);
294 // 起動時のシステムプロパティでキャラクターディレクトリが指定されていて実在すれば、それを優先する.
295 File currentCharacterDir = null;
296 String charactersDir = System.getProperty("charactersDir");
297 if (charactersDir != null && charactersDir.length() > 0) {
298 File charsDir = new File(charactersDir);
299 if (charsDir.exists() && charsDir.isDirectory()) {
300 currentCharacterDir = charsDir;
304 if (currentCharacterDir == null) {
305 // キャラクターセットディレクトリの選択
306 File defaultCharacterDir = ConfigurationDirUtilities
307 .getDefaultCharactersDir();
308 currentCharacterDir = SelectCharatersDirDialog
309 .getCharacterDir(defaultCharacterDir);
310 if (currentCharacterDir == null) {
312 logger.info("luncher canceled.");
318 DirectoryConfig.getInstance().setCharactersDir(currentCharacterDir);
321 StartupSupport.getInstance().doStartup();
324 // (最後に使ったプロファイルがあれば、それが開かれる.)
325 final MainFrame mainFrame = ProfileListManager.openDefaultProfile();
328 if (JavaVersionUtils.getJavaVersion() >= 9) {
329 // OSXでJava9以降であればOracle実装でスクリーンメニュー類を設定する.
330 MainFramePartialForMacOSX9.setupScreenMenu(mainFrame);
333 // Java9未満であればeawtでスクリーンメニュー類を設定する.
334 // MacOSXであればスクリーンメニューからのイベントをハンドルできるようにする.
335 // OSXにしか存在しないクラスを利用するためリフレクションとしている.
336 // ただしJDKによっては、Apple Java Extensionsがないことも予想されるので、
339 .forName("charactermanaj.ui.MainFramePartialForMacOSX");
340 Method mtd = clz.getMethod("setupScreenMenu",
342 mtd.invoke(null, mainFrame);
345 } catch (Throwable ex) {
346 logger.log(Level.CONFIG, "Failed to setup the screen menu.", ex);
351 mainFrame.showMainFrame();
353 } catch (Throwable ex) {
354 // なんらかの致命的な初期化エラーがあった場合、ログとコンソールに表示
355 // ダイアログが表示されるかどうかは状況次第.
356 ex.printStackTrace();
357 logger.log(Level.SEVERE, "Application initiation failed.", ex);
358 ErrorMessageHelper.showErrorDialog(null, ex);
361 MainFrame.closeAllProfiles();
367 * 最初のメインフレームを開いたときにMac OS Xであればスクリーンメニューの登録も行う.<br>
372 public static void main(String[] args) {
378 ImageCacheMBeanImpl.setupMBean();
380 } catch (JMException ex) {
382 logger.log(Level.SEVERE, ex.getMessage(), ex);
385 // フレームの生成等は、SwingのEDTで実行する.
386 SwingUtilities.invokeLater(new Main());
392 * @return Max OS X上であればtrue
394 public static boolean isMacOSX() {
399 * Mac OS X、もしくはlinuxで動作しているか?
401 * @return Mac OS X、もしくはlinuxで動作していればtrue
403 public static boolean isLinuxOrMacOSX() {
404 return isLinux || isMacOSX;