OSDN Git Service

2d1cbf997603bb0b3f6a845ccc2e425492a0bb3a
[charactermanaj/CharacterManaJ.git] / src / charactermanaj / ui / MainFrame.java
1 package charactermanaj.ui;\r
2 \r
3 import static java.lang.Math.max;\r
4 import static java.lang.Math.min;\r
5 \r
6 import java.awt.BorderLayout;\r
7 import java.awt.Color;\r
8 import java.awt.Component;\r
9 import java.awt.Container;\r
10 import java.awt.Cursor;\r
11 import java.awt.Dimension;\r
12 import java.awt.Font;\r
13 import java.awt.Frame;\r
14 import java.awt.GraphicsEnvironment;\r
15 import java.awt.Point;\r
16 import java.awt.Rectangle;\r
17 import java.awt.Toolkit;\r
18 import java.awt.dnd.DropTarget;\r
19 import java.awt.event.ActionEvent;\r
20 import java.awt.event.ActionListener;\r
21 import java.awt.event.MouseWheelEvent;\r
22 import java.awt.event.MouseWheelListener;\r
23 import java.awt.event.WindowAdapter;\r
24 import java.awt.event.WindowEvent;\r
25 import java.awt.image.BufferedImage;\r
26 import java.io.File;\r
27 import java.io.IOException;\r
28 import java.lang.reflect.InvocationTargetException;\r
29 import java.net.URI;\r
30 import java.util.ArrayList;\r
31 import java.util.Collections;\r
32 import java.util.List;\r
33 import java.util.Map;\r
34 import java.util.Properties;\r
35 import java.util.TreeMap;\r
36 import java.util.UUID;\r
37 import java.util.logging.Level;\r
38 import java.util.logging.Logger;\r
39 \r
40 import javax.swing.Box;\r
41 import javax.swing.BoxLayout;\r
42 import javax.swing.JCheckBox;\r
43 import javax.swing.JColorChooser;\r
44 import javax.swing.JFrame;\r
45 import javax.swing.JMenu;\r
46 import javax.swing.JMenuBar;\r
47 import javax.swing.JMenuItem;\r
48 import javax.swing.JOptionPane;\r
49 import javax.swing.JPanel;\r
50 import javax.swing.JPopupMenu;\r
51 import javax.swing.JScrollBar;\r
52 import javax.swing.JScrollPane;\r
53 import javax.swing.JSeparator;\r
54 import javax.swing.JSplitPane;\r
55 import javax.swing.JViewport;\r
56 import javax.swing.SwingUtilities;\r
57 import javax.swing.event.AncestorEvent;\r
58 import javax.swing.event.AncestorListener;\r
59 import javax.swing.event.MenuEvent;\r
60 import javax.swing.event.MenuListener;\r
61 \r
62 import charactermanaj.Main;\r
63 import charactermanaj.clipboardSupport.ClipboardUtil;\r
64 import charactermanaj.graphics.AsyncImageBuilder;\r
65 import charactermanaj.graphics.ColorConvertedImageCachedLoader;\r
66 import charactermanaj.graphics.ImageBuildJobAbstractAdaptor;\r
67 import charactermanaj.graphics.ImageBuilder.ImageOutput;\r
68 import charactermanaj.graphics.io.ImageSaveHelper;\r
69 import charactermanaj.graphics.io.OutputOption;\r
70 import charactermanaj.graphics.io.UkagakaImageSaveHelper;\r
71 import charactermanaj.model.AppConfig;\r
72 import charactermanaj.model.CharacterData;\r
73 import charactermanaj.model.ColorGroup;\r
74 import charactermanaj.model.IndependentPartsSetInfo;\r
75 import charactermanaj.model.PartsCategory;\r
76 import charactermanaj.model.PartsColorInfo;\r
77 import charactermanaj.model.PartsColorManager;\r
78 import charactermanaj.model.PartsIdentifier;\r
79 import charactermanaj.model.PartsSet;\r
80 import charactermanaj.model.RecommendationURL;\r
81 import charactermanaj.model.WorkingSet;\r
82 import charactermanaj.model.WorkingSet2;\r
83 import charactermanaj.model.io.CharacterDataPersistent;\r
84 import charactermanaj.model.io.PartsImageDirectoryWatchAgent;\r
85 import charactermanaj.model.io.PartsImageDirectoryWatchAgentFactory;\r
86 import charactermanaj.model.io.PartsImageDirectoryWatchEvent;\r
87 import charactermanaj.model.io.PartsImageDirectoryWatchListener;\r
88 import charactermanaj.model.io.RecentDataPersistent;\r
89 import charactermanaj.model.io.WorkingSetPersist;\r
90 import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelEvent;\r
91 import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelListener;\r
92 import charactermanaj.ui.ManageFavoriteDialog.FavoriteManageCallback;\r
93 import charactermanaj.ui.PreviewPanel.PreviewPanelEvent;\r
94 import charactermanaj.ui.PreviewPanel.PreviewPanelListener;\r
95 import charactermanaj.ui.model.ColorChangeEvent;\r
96 import charactermanaj.ui.model.ColorChangeListener;\r
97 import charactermanaj.ui.model.ColorGroupCoordinator;\r
98 import charactermanaj.ui.model.FavoritesChangeEvent;\r
99 import charactermanaj.ui.model.FavoritesChangeListener;\r
100 import charactermanaj.ui.model.FavoritesChangeObserver;\r
101 import charactermanaj.ui.model.PartsColorCoordinator;\r
102 import charactermanaj.ui.model.PartsSelectionManager;\r
103 import charactermanaj.ui.model.WallpaperFactory;\r
104 import charactermanaj.ui.model.WallpaperFactoryErrorRecoverHandler;\r
105 import charactermanaj.ui.model.WallpaperFactoryException;\r
106 import charactermanaj.ui.model.WallpaperInfo;\r
107 import charactermanaj.ui.scrollablemenu.JScrollableMenu;\r
108 import charactermanaj.ui.util.FileDropTarget;\r
109 import charactermanaj.ui.util.WindowAdjustLocationSupport;\r
110 import charactermanaj.util.DesktopUtilities;\r
111 import charactermanaj.util.ErrorMessageHelper;\r
112 import charactermanaj.util.LocalizedResourcePropertyLoader;\r
113 import charactermanaj.util.SystemUtil;\r
114 import charactermanaj.util.UIHelper;\r
115 \r
116 \r
117 /**\r
118  * メインフレーム.<br>\r
119  * アプリケーションがアクティブである場合は最低でも1つのメインフレームが表示されている.<br>\r
120  * \r
121  * @author seraphy\r
122  */\r
123 public class MainFrame extends JFrame implements FavoritesChangeListener {\r
124 \r
125         private static final long serialVersionUID = 1L;\r
126 \r
127         private static final Logger logger = Logger.getLogger(MainFrame.class.getName());\r
128 \r
129 \r
130         protected static final String STRINGS_RESOURCE = "languages/mainframe";\r
131 \r
132         protected static final String MENU_STRINGS_RESOURCE = "menu/menu";\r
133 \r
134         /**\r
135          * メインフレームのアイコン.<br>\r
136          */\r
137         protected BufferedImage icon;\r
138 \r
139 \r
140         /**\r
141          * 現在アクティブなメインフレーム.<br>\r
142          * フォーカスが切り替わるたびにアクティブフレームを追跡する.<br>\r
143          * Mac OS XのAbout/Preferences/Quitのシステムメニューからよびだされた場合に\r
144          * オーナーたるべきメインフレームを識別するためのもの.<br>\r
145          */\r
146         private static volatile MainFrame activedMainFrame;\r
147 \r
148 \r
149         /**\r
150          * このメインフレームが対象とするキャラクターデータ.<br>\r
151          */\r
152         protected CharacterData characterData;\r
153 \r
154 \r
155         /**\r
156          * プレビューペイン\r
157          */\r
158         private PreviewPanel previewPane;\r
159 \r
160         /**\r
161          * パーツ選択マネージャ\r
162          */\r
163         protected PartsSelectionManager partsSelectionManager;\r
164 \r
165         /**\r
166          * パネルの最小化モード\r
167          */\r
168         private boolean minimizeMode;\r
169 \r
170 \r
171         /**\r
172          * パーツ選択パネルリスト\r
173          */\r
174         protected ImageSelectPanelList imageSelectPanels;\r
175 \r
176         /**\r
177          * パーツ選択パネルを納めるスクロールペイン\r
178          */\r
179         protected JScrollPane imgSelectPanelsPanelSp;\r
180 \r
181         /**\r
182          * カラーグループのマネージャ\r
183          */\r
184         protected ColorGroupCoordinator colorGroupCoordinator;\r
185 \r
186         /**\r
187          * パーツカラーのマネージャ\r
188          */\r
189         protected PartsColorCoordinator partsColorCoordinator;\r
190 \r
191 \r
192         /**\r
193          * キャッシュつきのイメージローダ.<br>\r
194          */\r
195         private ColorConvertedImageCachedLoader imageLoader;\r
196 \r
197         /**\r
198          * パーツを組み立てて1つのプレビュー可能なイメージを構築するためのビルダ\r
199          */\r
200         private AsyncImageBuilder imageBuilder;\r
201 \r
202 \r
203         /**\r
204          * パーツイメージを画像として保存する場合のヘルパー.<br>\r
205          * 最後に使ったディレクトリを保持するためのメンバ変数としている.<br>\r
206          */\r
207         private ImageSaveHelper imageSaveHelper = new ImageSaveHelper();\r
208 \r
209         /**\r
210          * 伺か用出力ヘルパ.<br>\r
211          * 最後に使ったディレクトリ、ファイル名、モードなどを保持するためのメンバ変数としている.<br>\r
212          */\r
213         private UkagakaImageSaveHelper ukagakaImageSaveHelper = new UkagakaImageSaveHelper();\r
214 \r
215         /**\r
216          * パーツディレクトリを定期的にチェックし、パーツイメージが変更・追加・削除されている場合に パーツリストを更新するためのウォッチャー\r
217          */\r
218         private PartsImageDirectoryWatchAgent watchAgent;\r
219 \r
220         /**\r
221          * デフォルトのパーツセット表示名\r
222          */\r
223         private String defaultPartsSetTitle;\r
224 \r
225         /**\r
226          * 最後に使用したプリセット.<br>\r
227          * (一度もプリセットを使用していなければnull).\r
228          */\r
229         private PartsSet lastUsePresetParts;\r
230 \r
231         /**\r
232          * 最後に使用した検索ダイアログ.<br>\r
233          * nullであれば一度も使用していない.<br>\r
234          * (nullでなくとも閉じられている可能性がある.)<br>\r
235          */\r
236         private SearchPartsDialog lastUseSearchPartsDialog;\r
237 \r
238         /**\r
239          * 最後に使用したお気に入りダイアログ.<br>\r
240          * nullであれば一度も使用していない.<br>\r
241          * (nullでなくとも閉じられている可能性がある.)\r
242          */\r
243         private ManageFavoriteDialog lastUseManageFavoritesDialog;\r
244 \r
245         /**\r
246          * 最後に使用した壁紙情報\r
247          */\r
248         private WallpaperInfo wallpaperInfo;\r
249 \r
250 \r
251         /**\r
252          * アクティブなメインフレームを設定する.\r
253          * \r
254          * @param mainFrame\r
255          *            メインフレーム\r
256          */\r
257         public static void setActivedMainFrame(MainFrame mainFrame) {\r
258                 if (mainFrame == null) {\r
259                         throw new IllegalArgumentException();\r
260                 }\r
261                 activedMainFrame = mainFrame;\r
262         }\r
263 \r
264         /**\r
265          * 現在アクティブなメインフレームを取得する. まだメインフレームが開かれていない場合はnull.<br>\r
266          * 最後のメインフレームが破棄中、もしくは破棄済みであれば破棄されたフレームを示すことに注意.<br>\r
267          * \r
268          * @return メインフレーム、もしくはnull\r
269          */\r
270         public static MainFrame getActivedMainFrame() {\r
271                 return activedMainFrame;\r
272         }\r
273 \r
274         /**\r
275          * インポートされパーツが増減した可能性がある場合に呼び出される.\r
276          * \r
277          * @param cd\r
278          *            対象\r
279          * @param newCd\r
280          *            インポートされたcd\r
281          * @param caller\r
282          *            呼び出しもとメインフレーム\r
283          * @throws IOException\r
284          *             例外\r
285          */\r
286         public static void notifyImportedPartsOrFavorites(\r
287                         final CharacterData cd,\r
288                         final CharacterData newCd,\r
289                         final Component caller\r
290                         ) throws IOException {\r
291                 if (cd == null || newCd == null || caller == null) {\r
292                         throw new IllegalArgumentException();\r
293                 }\r
294 \r
295                 if (!cd.isValid() || !newCd.isValid()) {\r
296                         // 変更前もしくは変更後が無効なキャラクターデータであれば\r
297                         // 反映する必要ない.\r
298                         return;\r
299                 }\r
300                 logger.log(Level.FINE, "parts imported for active profiles: " + newCd);\r
301 \r
302 \r
303                 if ( !cd.isSameStructure(newCd)) {\r
304                         // キャラクターデータそのものが変更されている場合\r
305                         notifyChangeCharacterData(cd, newCd, caller);\r
306 \r
307                 } else {\r
308                         // パーツ構成は変更されていない場合\r
309 \r
310                         // Frameのうち、ネイティブリソースと関連づけられているアクティブなフレームを調査\r
311                         for (Frame frame : JFrame.getFrames()) {\r
312                                 if (frame.isDisplayable() && frame instanceof MainFrame) {\r
313                                         final MainFrame mainFrame = (MainFrame) frame;\r
314                                         if (mainFrame.characterData == null || !mainFrame.characterData.isValid()) {\r
315                                                 // 無効なキャラクターデータを保持している場合は、そのメインフレームは処理対象外\r
316                                                 continue;\r
317                                         }\r
318                                         SwingUtilities.invokeLater(new Runnable() {\r
319                                                 public void run() {\r
320                                                         // パーツ及びお気に入りを再取得する場合.\r
321                                                         try {\r
322                                                                 Cursor oldCur = mainFrame.getCursor();\r
323                                                                 mainFrame.setCursor(Cursor\r
324                                                                                 .getPredefinedCursor(Cursor.WAIT_CURSOR));\r
325                                                                 try {\r
326                                                                         mainFrame.reloadPartsAndFavorites(newCd, true);\r
327 \r
328                                                                 } finally {\r
329                                                                         mainFrame.setCursor(oldCur != null ? oldCur : Cursor.getDefaultCursor());\r
330                                                                 }\r
331 \r
332                                                         } catch (Exception ex) {\r
333                                                                 ErrorMessageHelper.showErrorDialog(mainFrame, ex);\r
334                                                         }\r
335                                                 }\r
336                                         });\r
337                                 }\r
338                         }\r
339                 }\r
340         }\r
341 \r
342         /**\r
343          * キャラクターデータが変更されたことを通知される.<br>\r
344          * \r
345          * @param cd\r
346          *            キャラクターデータ(変更前)\r
347          * @param newCd\r
348          *            キャラクターデータ(変更後)\r
349          * @param caller\r
350          *            呼び出しもとコンポーネント(ウェイトカーソル表示用)\r
351          * @param structureCompatible\r
352          *            構造が同一であるか?\r
353          * @throws IOException\r
354          *             新しいキャラクターデータのパーツセットのロードに失敗した場合 (メインフレームは変更されていません.)\r
355          */\r
356         public static void notifyChangeCharacterData(\r
357                         final CharacterData cd,\r
358                         final CharacterData newCd,\r
359                         final Component caller\r
360                         ) throws IOException {\r
361                 if (cd == null || newCd == null || caller == null) {\r
362                         throw new IllegalArgumentException();\r
363                 }\r
364 \r
365                 if (!cd.isValid() || !newCd.isValid()) {\r
366                         // 変更前もしくは変更後が無効なキャラクターデータであれば\r
367                         // 反映する必要ない.\r
368                         return;\r
369                 }\r
370                 logger.log(Level.FINE, "change active profile: " + newCd);\r
371 \r
372                 if (!ProfileListManager.isUsingCharacterData(cd)) {\r
373                         // 使用中のプロファイルではないので何もしない.\r
374                         return;\r
375                 }\r
376 \r
377                 caller.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
378                 try {\r
379                         // キャラクターデータが、まだ読み込まれていなければ読み込む.\r
380                         if (!newCd.isPartsLoaded()) {\r
381                                 ProfileListManager.loadCharacterData(newCd);\r
382                                 ProfileListManager.loadFavorites(newCd);\r
383                         }\r
384 \r
385                         // Frameのうち、ネイティブリソースと関連づけられているアクティブなフレームを調査\r
386                         for (Frame frame : JFrame.getFrames()) {\r
387                                 if (frame.isDisplayable() && frame instanceof MainFrame) {\r
388                                         final MainFrame mainFrame = (MainFrame) frame;\r
389                                         if (mainFrame.characterData == null || !mainFrame.characterData.isValid()) {\r
390                                                 // 無効なキャラクターデータを保持している場合は、そのメインフレームは処理対象外\r
391                                                 continue;\r
392                                         }\r
393 \r
394                                         // メインフレームが保持しているキャラクターデータのdocbaseと\r
395                                         // 変更対象となったキャラクターデータのdocbaseが等しければ、メインフレームを更新する必要がある.\r
396                                         String docbaseOrg = cd.getDocBase().toString();\r
397                                         String docbaseMine = mainFrame.characterData.getDocBase().toString();\r
398                                         if (docbaseOrg.equals(docbaseMine)) {\r
399                                                 SwingUtilities.invokeLater(new Runnable() {\r
400                                                         public void run() {\r
401                                                                 try {\r
402                                                                         Cursor oldCur = mainFrame.getCursor();\r
403                                                                         mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
404                                                                         try {\r
405                                                                                 // 現在情報の保存\r
406                                                                                 mainFrame.saveWorkingSet();\r
407 \r
408                                                                                 // 画面構成の再構築\r
409                                                                                 mainFrame.initComponent(newCd);\r
410 \r
411                                                                         } finally {\r
412                                                                                 mainFrame.setCursor(oldCur != null ? oldCur : Cursor.getDefaultCursor());\r
413                                                                         }\r
414 \r
415                                                                 } catch (RuntimeException ex) {\r
416                                                                         ErrorMessageHelper.showErrorDialog(mainFrame, ex);\r
417                                                                 }\r
418                                                         }\r
419                                                 });\r
420                                         }\r
421                                 }\r
422                         }\r
423 \r
424                 } finally {\r
425                         caller.setCursor(Cursor.getDefaultCursor());\r
426                 }\r
427         }\r
428 \r
429         /**\r
430          * お気に入りデータが変更された場合に通知される.\r
431          * \r
432          * @param e\r
433          */\r
434         public void notifyChangeFavorites(FavoritesChangeEvent e) {\r
435                 CharacterData cd = e.getCharacterData();\r
436                 if (cd.getDocBase().equals(MainFrame.this.characterData.getDocBase())) {\r
437                         if (!MainFrame.this.equals(e.getSource())) {\r
438                                 // お気に入りを最新化する.\r
439                                 // (ただし、自分自身から送信したイベントの場合はリロードの必要はない)\r
440                                 refreshFavorites();\r
441                         }\r
442 \r
443                         // お気に入り管理ダイアログ上のお気に入り一覧を最新に更新する.\r
444                         if (lastUseManageFavoritesDialog != null\r
445                                         && lastUseManageFavoritesDialog.isDisplayable()) {\r
446                                 lastUseManageFavoritesDialog.initListModel();\r
447                         }\r
448                 }\r
449         }\r
450 \r
451         /**\r
452          * メインフレームを構築する.\r
453          * \r
454          * @param characterData\r
455          *            キャラクターデータ\r
456          */\r
457         public MainFrame(CharacterData characterData) {\r
458                 try {\r
459                         if (characterData == null) {\r
460                                 throw new IllegalArgumentException();\r
461                         }\r
462 \r
463                         setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);\r
464                         addWindowListener(new WindowAdapter() {\r
465                                 @Override\r
466                                 public void windowClosing(WindowEvent e) {\r
467                                         onCloseProfile();\r
468                                 }\r
469                                 @Override\r
470                                 public void windowClosed(WindowEvent e) {\r
471                                         stopAgents();\r
472                                 }\r
473                                 @Override\r
474                                 public void windowActivated(WindowEvent e) {\r
475                                         setActivedMainFrame(MainFrame.this);\r
476                                 }\r
477                                 @Override\r
478                                 public void windowOpened(WindowEvent e) {\r
479                                         // do nothing.\r
480                                 }\r
481                         });\r
482 \r
483                         // アイコンの設定\r
484                         icon = UIHelper.getInstance().getImage("icons/icon.png");\r
485                         setIconImage(icon);\r
486 \r
487                         // 画面コンポーネント作成\r
488                         initComponent(characterData);\r
489                         JMenuBar menuBar = createMenuBar();\r
490                         setJMenuBar(menuBar);\r
491 \r
492                         // お気に入り変更通知を受け取る\r
493                         FavoritesChangeObserver.getDefault().addFavoritesChangeListener(\r
494                                         this);\r
495 \r
496                         // メインスクリーンサイズを取得する.\r
497                         GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();\r
498                         Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)\r
499                         logger.log(Level.CONFIG, "desktopSize=" + desktopSize);\r
500 \r
501                         Dimension imageSize = characterData.getImageSize();\r
502                         // 画像サイズ300x400を基準サイズとして、それ以下にはならない.\r
503                         // アプリケーション設定の最大サイズ以上の場合はウィンドウサイズは固定してスクロールバーに任せる\r
504                         AppConfig appConfig = AppConfig.getInstance();\r
505                         int maxWidth = min(desktopSize.width, appConfig.getMainFrameMaxWidth());\r
506                         int maxHeight = min(desktopSize.height, appConfig.getMainFrameMaxHeight());\r
507                         int imageWidth = min(maxWidth, max(300, imageSize != null ? imageSize.width : 0));\r
508                         int imageHeight = min(maxHeight, max(400, imageSize != null ? imageSize.height : 0));\r
509                         // 300x400の画像の場合にメインフレームが600x550だとちょうどいい感じ.\r
510                         // それ以上大きい画像の場合は増えた分だけフレームを大きくしておく.\r
511                         setSize(imageWidth - 300 + 600, imageHeight - 400 + 550);\r
512 \r
513                         // 次回表示時にプラットフォーム固有位置に表示するように予約\r
514                         setLocationByPlatform(true);\r
515 \r
516                 } catch (RuntimeException ex) {\r
517                         logger.log(Level.SEVERE, "メインフレームの構築中に予期せぬ例外が発生しました。", ex);\r
518                         dispose(); // コンストラクタが呼ばれた時点でJFrameは構築済みなのでdisposeの必要がある.\r
519                         throw ex;\r
520                 } catch (Error ex) {\r
521                         logger.log(Level.SEVERE, "メインフレームの構築中に致命的な例外が発生しました。", ex);\r
522                         dispose(); // コンストラクタが呼ばれた時点でJFrameは構築済みなのでdisposeの必要がある.\r
523                         throw ex;\r
524                 }\r
525         }\r
526 \r
527         /**\r
528          * メインフレームを表示する.<br>\r
529          * デスクトップ領域からはみ出した場合は位置を補正する.<br>\r
530          */\r
531         public void showMainFrame() {\r
532                 // メインスクリーンサイズを取得する.\r
533                 GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();\r
534                 Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)\r
535                 logger.log(Level.CONFIG, "desktopSize=" + desktopSize);\r
536 \r
537                 // プラットフォーム固有の位置あわせで表示する.\r
538                 // 表示した結果、はみ出している場合は0,0に補正する.\r
539                 setVisible(true);\r
540                 Point loc = getLocation();\r
541                 logger.log(Level.CONFIG, "windowLocation=" + loc);\r
542                 Dimension windowSize = getSize();\r
543                 if (loc.y + windowSize.height >= desktopSize.height) {\r
544                         loc.y = 0;\r
545                 }\r
546                 if (loc.x + windowSize.width >= desktopSize.width) {\r
547                         loc.x = 0;\r
548                 }\r
549                 if (loc.x == 0 || loc.y == 0) {\r
550                         setLocation(loc);\r
551                 }\r
552 \r
553                 // デスクトップよりも大きい場合は小さくする.\r
554                 boolean resize = false;\r
555                 Dimension dim = getSize();\r
556                 if (dim.height > desktopSize.height) {\r
557                         dim.height = desktopSize.height;\r
558                         resize = true;\r
559                 }\r
560                 if (dim.width > desktopSize.width) {\r
561                         dim.width = desktopSize.width;\r
562                         resize = true;\r
563                 }\r
564                 if (resize) {\r
565                         setSize(dim);\r
566                 }\r
567         }\r
568 \r
569         /**\r
570          * このメインフレームに関連づけられているエージェントスレッドを停止します.<br>\r
571          * すでに停止している場合は何もしません。\r
572          */\r
573         protected void stopAgents() {\r
574                 // エージェントを停止\r
575                 if (watchAgent != null) {\r
576                         try {\r
577                                 watchAgent.disconnect();\r
578 \r
579                         } catch (Throwable ex) {\r
580                                 logger.log(Level.SEVERE, "フォルダ監視スレッドの停止に失敗しました。", ex);\r
581                         }\r
582                         watchAgent = null;\r
583                 }\r
584                 // イメージビルダを停止\r
585                 if (imageBuilder != null) {\r
586                         try {\r
587                                 imageBuilder.stop();\r
588 \r
589                         } catch (Throwable ex) {\r
590                                 logger.log(Level.SEVERE, "非同期イメージビルダスレッドの停止に失敗しました。", ex);\r
591                         }\r
592                         imageBuilder = null;\r
593                 }\r
594         }\r
595 \r
596         /**\r
597          * メインフレームを破棄します.<br>\r
598          */\r
599         @Override\r
600         public void dispose() {\r
601                 FavoritesChangeObserver.getDefault()\r
602                                 .removeFavoritesChangeListener(this);\r
603             imageLoader.close();\r
604                 stopAgents();\r
605                 super.dispose();\r
606         }\r
607 \r
608         /**\r
609          * 画面コンポーネントを設定します.<br>\r
610          * すでに設定されている場合は一旦削除されたのちに再作成されます.<br>\r
611          */\r
612         private void initComponent(CharacterData characterData) {\r
613 \r
614                 CharacterData oldCd;\r
615                 synchronized (this) {\r
616                         oldCd = this.characterData;\r
617                         if (oldCd != null) {\r
618                                 // 使用中のキャラクターデータであることを登録解除する。\r
619                                 ProfileListManager.unregisterUsedCharacterData(oldCd);\r
620                         }\r
621                         this.characterData = characterData;\r
622 \r
623                         // 使用中のキャラクターデータであることを登録する.\r
624                         ProfileListManager.registerUsedCharacterData(characterData);\r
625                 }\r
626 \r
627                 // 設定まわり準備\r
628                 AppConfig appConfig = AppConfig.getInstance();\r
629                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
630                                 .getLocalizedProperties(STRINGS_RESOURCE);\r
631 \r
632                 // タイトル表示\r
633                 String title;\r
634                 if (Main.isMacOSX()) {\r
635                         // Mac OS Xの場合はウィンドウにタイトルはつけない。\r
636                         title = "";\r
637                 } else {\r
638                         title = strings.getProperty("title");\r
639                 }\r
640                 setTitle(title + characterData.getName());\r
641 \r
642                 // デフォルトのパーツセット表示名\r
643                 defaultPartsSetTitle = strings.getProperty("defaultPartsSetTitle");\r
644 \r
645                 // エージェントの停止\r
646                 stopAgents();\r
647 \r
648                 // コンポーネント配置\r
649                 Container contentPane = getContentPane();\r
650 \r
651                 // すでにあるコンポーネントを削除\r
652                 for (Component comp : contentPane.getComponents()) {\r
653                         contentPane.remove(comp);\r
654                 }\r
655                 // 開いている検索ダイアログを閉じる\r
656                 closeSearchDialog();\r
657 \r
658                 // 開いているお気に入り管理ダイアログを閉じる\r
659                 closeManageFavoritesDialog();\r
660 \r
661                 PartsColorManager partsColorManager = characterData.getPartsColorManager();\r
662 \r
663                 // デフォルトの背景色の設定\r
664                 Color bgColor = appConfig.getDefaultImageBgColor();\r
665                 wallpaperInfo = new WallpaperInfo();\r
666                 wallpaperInfo.setBackgroundColor(bgColor);\r
667 \r
668                 if (imageLoader != null) {\r
669                     imageLoader.close();\r
670                 }\r
671                 imageLoader = new ColorConvertedImageCachedLoader();\r
672                 imageBuilder = new AsyncImageBuilder(imageLoader);\r
673                 partsSelectionManager = new PartsSelectionManager(partsColorManager,\r
674                                 new PartsSelectionManager.ImageBgColorProvider() {\r
675                         public Color getImageBgColor() {\r
676                                 return wallpaperInfo.getBackgroundColor();\r
677                         }\r
678                         public void setImageBgColor(Color imageBgColor) {\r
679                                 applyBackgroundColorOnly(imageBgColor);\r
680                         }\r
681                 });\r
682                 colorGroupCoordinator = new ColorGroupCoordinator(partsSelectionManager, partsColorManager);\r
683                 partsColorCoordinator = new PartsColorCoordinator(characterData, partsColorManager, colorGroupCoordinator);\r
684                 PartsImageDirectoryWatchAgentFactory agentFactory = PartsImageDirectoryWatchAgentFactory.getFactory();\r
685                 watchAgent = agentFactory.getAgent(characterData);\r
686 \r
687                 previewPane = new PreviewPanel();\r
688                 previewPane.setTitle(defaultPartsSetTitle);\r
689                 previewPane.addPreviewPanelListener(new PreviewPanelListener() {\r
690                         public void addFavorite(PreviewPanelEvent e) {\r
691                                 if (!e.isShiftKeyPressed()) {\r
692                                         // お気に入り登録\r
693                                         onRegisterFavorite();\r
694 \r
695                                 } else {\r
696                                         // シフトキーにて、お気に入りの管理を開く\r
697                                         onManageFavorites();\r
698                                 }\r
699                         }\r
700                         public void changeBackgroundColor(PreviewPanelEvent e) {\r
701                                 if ( !e.isShiftKeyPressed()) {\r
702                                         // 壁紙選択\r
703                                         onChangeWallpaper();\r
704 \r
705                                 } else {\r
706                                         // シフトキーにて背景色変更\r
707                                         onChangeBgColor();\r
708                                 }\r
709                         }\r
710                         public void copyPicture(PreviewPanelEvent e) {\r
711                                 onCopy(e.isShiftKeyPressed());\r
712                         }\r
713                         public void savePicture(PreviewPanelEvent e) {\r
714                                 if ( !e.isShiftKeyPressed()) {\r
715                                         // 画像出力\r
716                                         onSavePicture();\r
717 \r
718                                 } else {\r
719                                         // シフトキーにて「伺か」用出力\r
720                                         onSaveAsUkagaka();\r
721                                 }\r
722                         }\r
723                         public void showInformation(PreviewPanelEvent e) {\r
724                                 onInformation();\r
725                         }\r
726                         public void flipHorizontal(PreviewPanelEvent e) {\r
727                                 onFlipHolizontal();\r
728                         }\r
729                 });\r
730 \r
731                 imageSelectPanels = new ImageSelectPanelList();\r
732 \r
733                 JPanel imgSelectPanelsPanel = new JPanel();\r
734                 BoxLayout bl = new BoxLayout(imgSelectPanelsPanel, BoxLayout.PAGE_AXIS);\r
735                 imgSelectPanelsPanel.setLayout(bl);\r
736                 for (PartsCategory category : characterData.getPartsCategories()) {\r
737                         final ImageSelectPanel imageSelectPanel = new ImageSelectPanel(category, characterData);\r
738                         imgSelectPanelsPanel.add(imageSelectPanel);\r
739                         imageSelectPanels.add(imageSelectPanel);\r
740                         partsSelectionManager.register(imageSelectPanel);\r
741                 }\r
742 \r
743                 imgSelectPanelsPanelSp = new JScrollPane(imgSelectPanelsPanel) {\r
744                         private static final long serialVersionUID = 1L;\r
745                         @Override\r
746                         public JScrollBar createVerticalScrollBar() {\r
747                                 JScrollBar sb = super.createVerticalScrollBar();\r
748                                 sb.setUnitIncrement(12);\r
749                                 return sb;\r
750                         }\r
751                 };\r
752                 imgSelectPanelsPanelSp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);\r
753 \r
754                 JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, imgSelectPanelsPanelSp, previewPane);\r
755                 contentPane.add(splitPane, BorderLayout.CENTER);\r
756 \r
757 \r
758                 imgSelectPanelsPanelSp.requestFocus();\r
759 \r
760                 ArrayList<ColorGroup> colorGroups = new ArrayList<ColorGroup>();\r
761                 colorGroups.addAll(characterData.getColorGroups());\r
762 \r
763                 final ColorChangeListener colorChangeListener = new ColorChangeListener() {\r
764                         public void onColorGroupChange(ColorChangeEvent event) {\r
765                                 // do nothing.\r
766                         }\r
767                         public void onColorChange(ColorChangeEvent event) {\r
768                                 MainFrame.this.requestPreview();\r
769                         }\r
770                 };\r
771                 colorGroupCoordinator.addColorChangeListener(colorChangeListener);\r
772 \r
773                 for (int idx = 0; idx < imageSelectPanels.size(); idx++) {\r
774                         ImageSelectPanel imageSelectPanel = imageSelectPanels.get(idx);\r
775                         final PartsCategory partsCategory = imageSelectPanel.getPartsCategory();\r
776                         final ColorDialog colorDialog = new ColorDialog(this, partsCategory, colorGroups);\r
777                         colorGroupCoordinator.registerColorDialog(colorDialog);\r
778                         partsColorCoordinator.register(imageSelectPanel, colorDialog);\r
779                         final int curidx = idx;\r
780                         imageSelectPanel.addImageSelectListener(new ImageSelectPanelListener() {\r
781                                 public void onChangeColor(ImageSelectPanelEvent event) {\r
782                                                         WindowAdjustLocationSupport.alignRight(\r
783                                                                         MainFrame.this, colorDialog, curidx, false);\r
784                                         colorDialog.setVisible(!colorDialog.isVisible());\r
785                                 }\r
786                                 public void onPreferences(ImageSelectPanelEvent event) {\r
787                                         // do nothing. (not supported)\r
788                                 }\r
789                                 public void onChange(ImageSelectPanelEvent event) {\r
790                                         MainFrame.this.requestPreview();\r
791                                 }\r
792                                 public void onSelectChange(ImageSelectPanelEvent event) {\r
793                                         // do nothing.\r
794                                 }\r
795                                 public void onTitleClick(ImageSelectPanelEvent event) {\r
796                                         PartsCategory partsCategory = (event != null) ?\r
797                                                         event.getImageSelectPanel().getPartsCategory() : null;\r
798                                         MainFrame.this.onClickPartsCategoryTitle(partsCategory, false);\r
799                                 }\r
800                                 public void onTitleDblClick(ImageSelectPanelEvent event) {\r
801                                         PartsCategory partsCategory = (event != null) ?\r
802                                                         event.getImageSelectPanel().getPartsCategory() : null;\r
803                                         MainFrame.this.onClickPartsCategoryTitle(partsCategory, true);\r
804                                 }\r
805                         });\r
806                         imageSelectPanel.addAncestorListener(new AncestorListener() {\r
807                                 public void ancestorAdded(AncestorEvent event) {\r
808                                 }\r
809                                 public void ancestorMoved(AncestorEvent event) {\r
810                                 }\r
811                                 public void ancestorRemoved(AncestorEvent event) {\r
812                                         // パネルもしくは、その親が削除されたときにダイアログも非表示とする。\r
813                                         colorDialog.setVisible(false);\r
814                                 }\r
815                         });\r
816                 }\r
817 \r
818                 // 全パーツのロード\r
819                 partsSelectionManager.loadParts();\r
820 \r
821                 // 保存されているワーキングセットを復元する.\r
822                 // 復元できなかった場合はパーツセットを初期選択する.\r
823                 if ( !loadWorkingSet()) {\r
824                         if (showDefaultParts(true)) {\r
825                                 requestPreview();\r
826                         }\r
827                 }\r
828 \r
829                 // 選択されているパーツを見える状態にする\r
830                 scrollToSelectedParts();\r
831 \r
832                 // 非同期イメージローダの処理開始\r
833                 if (!imageBuilder.isAlive()) {\r
834                         imageBuilder.start();\r
835                 }\r
836 \r
837                 // ドロップターゲットの設定\r
838                 new DropTarget(imgSelectPanelsPanelSp, new FileDropTarget() {\r
839                         @Override\r
840                         protected void onDropFiles(final List<File> dropFiles) {\r
841                                 if (dropFiles == null || dropFiles.isEmpty()) {\r
842                                         return;\r
843                                 }\r
844                                 // インポートダイアログを開く.\r
845                                 // ドロップソースの処理がブロッキングしないように、\r
846                                 // ドロップハンドラの処理を終了してからインポートダイアログが開くようにする.\r
847                                 SwingUtilities.invokeLater(new Runnable() {\r
848                                         public void run() {\r
849                                                 onImport(dropFiles);\r
850                                         }\r
851                                 });\r
852                         }\r
853                         @Override\r
854                         protected void onException(Exception ex) {\r
855                                 ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);\r
856                         }\r
857                 });\r
858 \r
859                 // ディレクトリを監視し変更があった場合にパーツをリロードするリスナ\r
860                 watchAgent.addPartsImageDirectoryWatchListener(new PartsImageDirectoryWatchListener() {\r
861                         public void detectPartsImageChange(PartsImageDirectoryWatchEvent e) {\r
862                                 Runnable refreshJob = new Runnable() {\r
863                                         public void run() {\r
864                                                 onDetectPartsImageChange();\r
865                                         }\r
866                                 };\r
867                                 if (SwingUtilities.isEventDispatchThread()) {\r
868                                         refreshJob.run();\r
869                                 } else {\r
870                                         SwingUtilities.invokeLater(refreshJob);\r
871                                 }\r
872                         }\r
873                 });\r
874 \r
875                 // 監視が有効であれば、ディレクトリの監視をスタートする\r
876                 if (appConfig.isEnableDirWatch() && characterData.isWatchDirectory()) {\r
877                         watchAgent.connect();\r
878                 }\r
879 \r
880                 // パーツカテゴリの自動縮小が設定されている場合\r
881                 minimizeMode = false;\r
882                 if (appConfig.isEnableAutoShrinkPanel()) {\r
883                         onClickPartsCategoryTitle(null, true);\r
884                 }\r
885 \r
886                 // コンポーネントの再構築の場合\r
887                 if (oldCd != null) {\r
888                         validate();\r
889                 }\r
890         }\r
891 \r
892         /**\r
893          * パーツが変更されたことを検知した場合.<br>\r
894          * パーツデータをリロードし、各カテゴリのパーツ一覧を再表示させ、プレビューを更新する.<br>\r
895          */\r
896         protected void onDetectPartsImageChange() {\r
897                 try {\r
898                         reloadPartsAndFavorites(null, true);\r
899 \r
900                 } catch (IOException ex) {\r
901                         logger.log(Level.SEVERE, "parts reload failed. " + characterData, ex);\r
902                 }\r
903         }\r
904 \r
905         /**\r
906          * すべてのカテゴリのリストで選択中のアイテムが見えるようにスクロールする.\r
907          */\r
908         protected void scrollToSelectedParts() {\r
909                 partsSelectionManager.scrollToSelectedParts();\r
910         }\r
911 \r
912         /**\r
913          * 指定したパーツカテゴリ以外のパーツ選択パネルを最小化する.\r
914          * \r
915          * @param partsCategory\r
916          *            パーツカテゴリ、nullの場合は全て最小化する.\r
917          * @param dblClick\r
918          *            ダブルクリック\r
919          */\r
920         protected void onClickPartsCategoryTitle(PartsCategory partsCategory, boolean dblClick) {\r
921                 if (logger.isLoggable(Level.FINE)) {\r
922                         logger.log(Level.FINE, "onClickPartsCategoryTitle category="\r
923                                         + partsCategory + "/clickCount=" + dblClick);\r
924                 }\r
925                 if (dblClick) {\r
926                         minimizeMode = !minimizeMode;\r
927                         if (!minimizeMode) {\r
928                                 partsSelectionManager.setMinimizeModeIfOther(null, false);\r
929                                 return;\r
930                         }\r
931                 }\r
932                 if (minimizeMode) {\r
933                         if (partsSelectionManager.isNotMinimizeModeJust(partsCategory)) {\r
934                                 partsSelectionManager.setMinimizeModeIfOther(null, true); // 全部縮小\r
935 \r
936                         } else {\r
937                                 partsSelectionManager.setMinimizeModeIfOther(partsCategory, true);\r
938                                 if (partsCategory != null) {\r
939                                         // 対象のパネルがスクロールペイン内に見える用にスクロールする.\r
940                                         // スクロールバーの位置指定などの座標系の操作は「要求」であり、実際に適用されるまで本当の位置は分らない。\r
941                                         // よって以下の処理は非同期に行い、先に座標を確定させたものに対して行う必要がある。\r
942                                         final ImageSelectPanel panel = imageSelectPanels.findByPartsCategory(partsCategory);\r
943                                         SwingUtilities.invokeLater(new Runnable() {\r
944                                                 public void run() {\r
945                                                         final Point pt = panel.getLocation();\r
946                                                         JViewport viewPort = imgSelectPanelsPanelSp.getViewport();\r
947                                                         viewPort.setViewPosition(pt);\r
948                                                         viewPort.revalidate();\r
949                                                 }\r
950                                         });\r
951                                 }\r
952                         }\r
953                 }\r
954         }\r
955 \r
956         /**\r
957          * デフォルトパーツを選択する.<br>\r
958          * デフォルトパーツがなければお気に入りの最初のものを選択する.<br>\r
959          * それもなければ空として表示する.<br>\r
960          * パーツの適用に失敗した場合はfalseを返します.(例外は返されません.)<br>\r
961          * \r
962          * @param force\r
963          *            すでに選択があっても選択しなおす場合はtrue、falseの場合は選択があれば何もしない.\r
964          * @return パーツ選択された場合。force=trueの場合はエラーがなければ常にtrueとなります。\r
965          */\r
966         protected boolean showDefaultParts(boolean force) {\r
967                 try {\r
968                         if (!force) {\r
969                                 // 現在選択中のパーツを取得する.(なければ空)\r
970                                 PartsSet sel = partsSelectionManager.createPartsSet();\r
971                                 if (!sel.isEmpty()) {\r
972                                         // 強制選択でない場合、すでに選択済みのパーツがあれば何もしない.\r
973                                         return false;\r
974                                 }\r
975                         }\r
976 \r
977                         // デフォルトのパーツセットを取得する\r
978                         String defaultPresetId = characterData.getDefaultPartsSetId();\r
979                         PartsSet partsSet = null;\r
980                         if (defaultPresetId != null) {\r
981                                 partsSet = characterData.getPartsSets().get(defaultPresetId);\r
982                         }\r
983 \r
984                         // デフォルトのパーツセットがなければ、お気に入りの最初を選択する.\r
985                         if (partsSet == null) {\r
986                                 List<PartsSet> partssets = getPartsSetList();\r
987                                 if (!partssets.isEmpty()) {\r
988                                         partsSet = partssets.get(0);\r
989                                 }\r
990                         }\r
991 \r
992                         // パーツセットがあれば、それを表示要求する.\r
993                         // パーツセットがなければカラーダイアログを初期化するのみ\r
994                         if (partsSet == null) {\r
995                                 partsColorCoordinator.initColorDialog();\r
996 \r
997                         } else {\r
998                                 selectPresetParts(partsSet);\r
999                         }\r
1000 \r
1001                 } catch (Exception ex) {\r
1002                         logger.log(Level.WARNING, "パーツのデフォルト適用に失敗しました。", ex);\r
1003                         return false;\r
1004                 }\r
1005                 return true;\r
1006         }\r
1007 \r
1008         /**\r
1009          * プリセットを適用しキャラクターイメージを再構築します.<br>\r
1010          * 実行時エラーは画面のレポートされます.<br>\r
1011          * \r
1012          * @param presetParts\r
1013          *            パーツセット, nullの場合は何もしない.\r
1014          */\r
1015         protected void selectPresetParts(PartsSet presetParts) {\r
1016                 if (presetParts == null) {\r
1017                         return;\r
1018                 }\r
1019                 try {\r
1020                         // 最後に使用したプリセットとして記憶する.\r
1021                         lastUsePresetParts = presetParts;\r
1022                         // プリセットパーツで選択を変える\r
1023                         partsSelectionManager.selectPartsSet(presetParts);\r
1024                         // カラーパネルを選択されているアイテムをもとに再設定する\r
1025                         partsColorCoordinator.initColorDialog();\r
1026                         // 再表示\r
1027                         requestPreview();\r
1028 \r
1029                 } catch (Exception ex) {\r
1030                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1031                 }\r
1032         }\r
1033 \r
1034         /**\r
1035          * プリセットとお気に入りを表示順に並べて返す.\r
1036          * \r
1037          * @return プリセットとお気に入りのリスト(表示順)\r
1038          */\r
1039         protected List<PartsSet> getPartsSetList() {\r
1040                 ArrayList<PartsSet> partssets = new ArrayList<PartsSet>();\r
1041                 partssets.addAll(characterData.getPartsSets().values());\r
1042                 Collections.sort(partssets, PartsSet.DEFAULT_COMPARATOR);\r
1043                 return partssets;\r
1044         }\r
1045 \r
1046         protected static final class TreeLeaf implements Comparable<TreeLeaf> {\r
1047 \r
1048                 public enum TreeLeafType {\r
1049                         NODE, LEAF\r
1050                 }\r
1051 \r
1052                 private String name;\r
1053 \r
1054                 private TreeLeafType typ;\r
1055 \r
1056                 public TreeLeaf(TreeLeafType typ, String name) {\r
1057                         if (name == null) {\r
1058                                 name = "";\r
1059                         }\r
1060                         this.typ = typ;\r
1061                         this.name = name;\r
1062                 }\r
1063 \r
1064                 public String getName() {\r
1065                         return name;\r
1066                 }\r
1067 \r
1068                 public TreeLeafType getTyp() {\r
1069                         return typ;\r
1070                 }\r
1071 \r
1072                 @Override\r
1073                 public boolean equals(Object obj) {\r
1074                         if (obj != null && obj instanceof TreeLeaf) {\r
1075                                 TreeLeaf o = (TreeLeaf) obj;\r
1076                                 return typ == o.typ && name.equals(o.name);\r
1077                         }\r
1078                         return false;\r
1079                 }\r
1080 \r
1081                 @Override\r
1082                 public int hashCode() {\r
1083                         return typ.hashCode() ^ name.hashCode();\r
1084                 }\r
1085 \r
1086                 public int compareTo(TreeLeaf o) {\r
1087                         int ret = name.compareTo(o.name);\r
1088                         if (ret == 0) {\r
1089                                 ret = (typ.ordinal() - o.typ.ordinal());\r
1090                         }\r
1091                         return ret;\r
1092                 }\r
1093 \r
1094                 @Override\r
1095                 public String toString() {\r
1096                         return name;\r
1097                 }\r
1098         }\r
1099 \r
1100         protected TreeMap<TreeLeaf, Object> buildFavoritesItemTree(\r
1101                         List<PartsSet> partssets) {\r
1102                 if (partssets == null) {\r
1103                         partssets = Collections.emptyList();\r
1104                 }\r
1105                 TreeMap<TreeLeaf, Object> favTree = new TreeMap<TreeLeaf, Object>();\r
1106                 for (PartsSet partsSet : partssets) {\r
1107                         String flatname = partsSet.getLocalizedName();\r
1108                         String[] tokens = flatname.split("\\|");\r
1109                         if (tokens.length == 0) {\r
1110                                 continue;\r
1111                         }\r
1112 \r
1113                         TreeMap<TreeLeaf, Object> r = favTree;\r
1114                         for (int idx = 0; idx < tokens.length - 1; idx++) {\r
1115                                 String name = tokens[idx];\r
1116                                 TreeLeaf leafName = new TreeLeaf(TreeLeaf.TreeLeafType.NODE,\r
1117                                                 name);\r
1118                                 @SuppressWarnings("unchecked")\r
1119                                 TreeMap<TreeLeaf, Object> n = (TreeMap<TreeLeaf, Object>) r\r
1120                                                 .get(leafName);\r
1121                                 if (n == null) {\r
1122                                         n = new TreeMap<TreeLeaf, Object>();\r
1123                                         r.put(leafName, n);\r
1124                                 }\r
1125                                 r = n;\r
1126                         }\r
1127                         String lastName = tokens[tokens.length - 1];\r
1128                         TreeLeaf lastLeafName = new TreeLeaf(TreeLeaf.TreeLeafType.LEAF,\r
1129                                         lastName);\r
1130                         @SuppressWarnings("unchecked")\r
1131                         List<PartsSet> leafValue = (List<PartsSet>) r.get(lastLeafName);\r
1132                         if (leafValue == null) {\r
1133                                 leafValue = new ArrayList<PartsSet>();\r
1134                                 r.put(lastLeafName, leafValue);\r
1135                         }\r
1136                         leafValue.add(partsSet);\r
1137                 }\r
1138                 return favTree;\r
1139         }\r
1140 \r
1141         protected interface FavoriteMenuItemBuilder {\r
1142                 JMenuItem createFavoriteMenuItem(String name, PartsSet partsSet);\r
1143                 JMenu createSubMenu(String name);\r
1144         }\r
1145 \r
1146         private void buildFavoritesMenuItems(List<JMenuItem> menuItems,\r
1147                         FavoriteMenuItemBuilder favMenuItemBuilder,\r
1148                         TreeMap<TreeLeaf, Object> favTree) {\r
1149                 for (Map.Entry<TreeLeaf, Object> entry : favTree.entrySet()) {\r
1150                         TreeLeaf treeLeaf = entry.getKey();\r
1151                         String name = treeLeaf.getName();\r
1152                         if (treeLeaf.getTyp() == TreeLeaf.TreeLeafType.LEAF) {\r
1153                                 // 葉ノードには、JMenuItemを設定する.\r
1154                                 @SuppressWarnings("unchecked")\r
1155                                 List<PartsSet> leafValue = (List<PartsSet>) entry.getValue();\r
1156                                 for (final PartsSet partsSet : leafValue) {\r
1157                                         JMenuItem favoriteMenu = favMenuItemBuilder\r
1158                                                         .createFavoriteMenuItem(name, partsSet);\r
1159                                         menuItems.add(favoriteMenu);\r
1160                                 }\r
1161 \r
1162                         } else if (treeLeaf.getTyp() == TreeLeaf.TreeLeafType.NODE) {\r
1163                                 // 枝ノードは、サブメニューを作成し、子ノードを設定する\r
1164                                 @SuppressWarnings("unchecked")\r
1165                                 TreeMap<TreeLeaf, Object> childNode = (TreeMap<TreeLeaf, Object>) entry\r
1166                                                 .getValue();\r
1167                                 JMenu subMenu = favMenuItemBuilder.createSubMenu(name);\r
1168                                 menuItems.add(subMenu);\r
1169                                 ArrayList<JMenuItem> subMenuItems = new ArrayList<JMenuItem>();\r
1170                                 buildFavoritesMenuItems(subMenuItems, favMenuItemBuilder, childNode);\r
1171                                 for (JMenuItem subMenuItem : subMenuItems) {\r
1172                                         subMenu.add(subMenuItem);\r
1173                                 }\r
1174 \r
1175                         } else {\r
1176                                 throw new RuntimeException("unknown type: " + treeLeaf);\r
1177                         }\r
1178                 }\r
1179         }\r
1180 \r
1181         /**\r
1182          * お気に入りのJMenuItemを作成するファンクションオブジェクト\r
1183          */\r
1184         private FavoriteMenuItemBuilder favMenuItemBuilder = new FavoriteMenuItemBuilder() {\r
1185                 private MenuBuilder menuBuilder = new MenuBuilder();\r
1186 \r
1187                 /**\r
1188                  * お気に入りメニューの作成\r
1189                  */\r
1190                 public JMenuItem createFavoriteMenuItem(final String name,\r
1191                                 final PartsSet partsSet) {\r
1192                         JMenuItem favoriteMenu = menuBuilder.createJMenuItem();\r
1193                         favoriteMenu.setName(partsSet.getPartsSetId());\r
1194                         favoriteMenu.setText(name);\r
1195                         if (partsSet.isPresetParts()) {\r
1196                                 Font font = favoriteMenu.getFont();\r
1197                                 favoriteMenu.setFont(font.deriveFont(Font.BOLD));\r
1198                         }\r
1199                         favoriteMenu.addActionListener(new ActionListener() {\r
1200                                 public void actionPerformed(ActionEvent e) {\r
1201                                         selectPresetParts(partsSet);\r
1202                                 }\r
1203                         });\r
1204 \r
1205                         // メニューアイテム上でマウスホイールを動かした場合は上下にスクロールさせる.\r
1206                         // (ただし、OSXのスクリーンメニュー使用時は無視する.)\r
1207                         addMouseWheelListener(favoriteMenu);\r
1208 \r
1209                         return favoriteMenu;\r
1210                 }\r
1211 \r
1212                 /**\r
1213                  * サブメニューの作成\r
1214                  */\r
1215                 public JMenu createSubMenu(String name) {\r
1216                         JMenu menu = menuBuilder.createJMenu();\r
1217                         menu.setText(name);\r
1218 \r
1219                         // メニューアイテム上でマウスホイールを動かした場合は上下にスクロールさせる.\r
1220                         // (ただし、OSXのスクリーンメニュー使用時は無視する.)\r
1221                         addMouseWheelListener(menu);\r
1222 \r
1223                         return menu;\r
1224                 }\r
1225 \r
1226                 /**\r
1227                  * メニューアイテム上でホイールを上下させたときにメニューをスクロールさせるためのホイールハンドラを設定する.\r
1228                  * \r
1229                  * @param favoriteMenu\r
1230                  */\r
1231                 protected void addMouseWheelListener(final JMenuItem favoriteMenu) {\r
1232                         if (JScrollableMenu.isScreenMenu()) {\r
1233                                 return;\r
1234                         }\r
1235                         favoriteMenu.addMouseWheelListener(new MouseWheelListener() {\r
1236                                 public void mouseWheelMoved(MouseWheelEvent e) {\r
1237                                         int rotation = e.getWheelRotation();\r
1238                                         JPopupMenu popupMenu = (JPopupMenu) favoriteMenu\r
1239                                                         .getParent();\r
1240                                         JMenu parentMenu = (JMenu) popupMenu.getInvoker();\r
1241                                         if (parentMenu != null\r
1242                                                         && parentMenu instanceof JScrollableMenu) {\r
1243                                                 final JScrollableMenu favMenu = (JScrollableMenu) parentMenu;\r
1244                                                 favMenu.doScroll(rotation < 0);\r
1245                                         }\r
1246                                         e.consume();\r
1247                                 }\r
1248                         });\r
1249                 }\r
1250         };\r
1251 \r
1252         /**\r
1253          * お気に入りメニューが開いたとき\r
1254          * \r
1255          * @param menu\r
1256          */\r
1257         protected void onSelectedFavoriteMenu(JMenu menu) {\r
1258                 // 表示順にソート\r
1259                 List<PartsSet> partssets = getPartsSetList();\r
1260                 TreeMap<TreeLeaf, Object> favTree = buildFavoritesItemTree(partssets);\r
1261 \r
1262                 // メニューの再構築\r
1263                 ArrayList<JMenuItem> favoritesMenuItems = new ArrayList<JMenuItem>();\r
1264                 buildFavoritesMenuItems(favoritesMenuItems, favMenuItemBuilder, favTree);\r
1265 \r
1266                 if (menu instanceof JScrollableMenu) {\r
1267                         // スクロールメニューの場合\r
1268                         JScrollableMenu favMenu = (JScrollableMenu) menu;\r
1269 \r
1270                         // スクロールメニューの初期化\r
1271                         favMenu.initScroller();\r
1272 \r
1273                         // スクロールメニューアイテムの設定\r
1274                         favMenu.setScrollableItems(favoritesMenuItems);\r
1275 \r
1276                         // 高さを補正する\r
1277                         // お気に入りメニューが選択された場合、\r
1278                         // お気に入りアイテム一覧を表示するよりも前に\r
1279                         // 表示可能なアイテム数を現在のウィンドウの高さから算定する.\r
1280                         Toolkit tk = Toolkit.getDefaultToolkit();\r
1281                         Dimension scrsiz = tk.getScreenSize();\r
1282                         int height = scrsiz.height; // MainFrame.this.getHeight();\r
1283                         favMenu.adjustMaxVisible(height);\r
1284                         logger.log(Level.FINE,\r
1285                                         "scrollableMenu maxVisible=" + favMenu.getMaxVisible());\r
1286 \r
1287                 } else {\r
1288                         // 通常メニューの場合\r
1289                         // 既存メニューの位置をセパレータより判断する.\r
1290                         int mx = menu.getMenuComponentCount();\r
1291                         int separatorIdx = -1;\r
1292                         for (int idx = 0; idx < mx; idx++) {\r
1293                                 Component item = menu.getMenuComponent(idx);\r
1294                                 if (item instanceof JSeparator) {\r
1295                                         separatorIdx = idx;\r
1296                                         break;\r
1297                                 }\r
1298                         }\r
1299                         // 既存メニューの削除\r
1300                         if (separatorIdx > 0) {\r
1301                                 while (menu.getMenuComponentCount() > separatorIdx + 1) {\r
1302                                         menu.remove(separatorIdx + 1);\r
1303                                 }\r
1304                         }\r
1305 \r
1306                         // お気に入りアイテムのメニューを登録する.\r
1307                         for (JMenuItem menuItem : favoritesMenuItems) {\r
1308                                 menu.add(menuItem);\r
1309                         }\r
1310                 }\r
1311 \r
1312         }\r
1313 \r
1314         /**\r
1315          * ヘルプメニューを開いたときにお勧めメニューを構築する.\r
1316          * \r
1317          * @param menu\r
1318          */\r
1319         protected void onSelectedRecommendationMenu(JMenu mnuRecomendation) {\r
1320                 // 現在のお勧めメニューを一旦削除\r
1321                 while (mnuRecomendation.getMenuComponentCount() > 0) {\r
1322                         mnuRecomendation.remove(0);\r
1323                 }\r
1324 \r
1325                 // お勧めリンクの定義がない場合はデフォルトを用いる.(明示的な空の場合は何もしない.)\r
1326                 CharacterDataPersistent persist = CharacterDataPersistent.getInstance();\r
1327                 persist.compensateRecommendationList(characterData);\r
1328 \r
1329                 // お勧めリンクメニューを作成する.\r
1330                 List<RecommendationURL> recommendations = characterData.getRecommendationURLList();\r
1331                 if (recommendations != null) {\r
1332                         MenuBuilder menuBuilder = new MenuBuilder();\r
1333                         for (RecommendationURL recommendation : recommendations) {\r
1334                                 String displayName = recommendation.getDisplayName();\r
1335                                 String url = recommendation.getUrl();\r
1336 \r
1337                                 JMenuItem mnuItem = menuBuilder.createJMenuItem();\r
1338                                 mnuItem.setText(displayName);\r
1339                                 mnuItem.addActionListener(\r
1340                                                 DesktopUtilities.createBrowseAction(MainFrame.this, url, displayName)\r
1341                                                 );\r
1342                                 mnuRecomendation.add(mnuItem);\r
1343                         }\r
1344                 }\r
1345 \r
1346                 // お勧めリンクメニューのリストがnullでなく空でもない場合は有効、そうでなければ無効にする.\r
1347                 mnuRecomendation.setEnabled(recommendations != null && !recommendations.isEmpty());\r
1348         }\r
1349 \r
1350 \r
1351         /**\r
1352          * 最後に選択されたお気に入りと同じ構成であれば、 このお気に入りの名前をプレビューペインのタイトルに設定する.<br>\r
1353          * そうでなければデフォルトのパーツセット名(no titleとか)を表示する.<br>\r
1354          * 色情報が異なる場合に末尾に「*」マークがつけられる.<br>\r
1355          * \r
1356          * @param requestPartsSet\r
1357          *            表示するパーツセット(名前は設定されていなくて良い。お気に入り側を使うので。), nullの場合はデフォルトのパーツ名\r
1358          */\r
1359         protected void showPresetName(PartsSet requestPartsSet) {\r
1360                 String title = getSuggestPartsSetName(requestPartsSet, true);\r
1361                 if (title == null) {\r
1362                         title = defaultPartsSetTitle;\r
1363                 }\r
1364                 previewPane.setTitle(title);\r
1365         }\r
1366 \r
1367         /**\r
1368          * パーツセット名を推定する.<br>\r
1369          * 最後に選択されたお気に入りと同じ構成であれば、 このお気に入りの名前を返す.<br>\r
1370          * お気に入りが選択されていないか構成が異なる場合、お気に入りに名前がない場合はnullを返す.<br>\r
1371          * \r
1372          * @param requestPartsSet\r
1373          *            表示するパーツセット(名前は設定されていなくて良い。お気に入り側を使うので。)\r
1374          * @param markColorChange\r
1375          *            色情報が異なる場合に末尾に「*」マークをつける場合はtrue\r
1376          */\r
1377         private String getSuggestPartsSetName(PartsSet requestPartsSet, boolean markColorChange) {\r
1378                 String partsSetTitle = null;\r
1379                 if (lastUsePresetParts != null &&\r
1380                                 PartsSet.isSameStructure(requestPartsSet, lastUsePresetParts)) {\r
1381                         partsSetTitle = lastUsePresetParts.getLocalizedName();\r
1382                         if (markColorChange && !PartsSet.isSameColor(requestPartsSet, lastUsePresetParts)) {\r
1383                                 if (partsSetTitle != null) {\r
1384                                         partsSetTitle += "*";\r
1385                                 }\r
1386                         }\r
1387                 }\r
1388                 if (partsSetTitle != null && partsSetTitle.trim().length() > 0) {\r
1389                         return partsSetTitle;\r
1390                 }\r
1391                 return null;\r
1392         }\r
1393 \r
1394         /**\r
1395          * プレビューの更新を要求する. 更新は非同期に行われる.\r
1396          */\r
1397         protected void requestPreview() {\r
1398                 if (!characterData.isValid()) {\r
1399                         return;\r
1400                 }\r
1401 \r
1402                 // 選択されているパーツの各イメージを取得しレイヤー順に並び替えて合成する.\r
1403                 // 合成は別スレッドにて非同期に行われる.\r
1404                 // リクエストは随時受け付けて、最新のリクエストだけが処理される.\r
1405                 // (処理がはじまる前に新しいリクエストで上書きされた場合、前のリクエストは単に捨てられる.)\r
1406                 imageBuilder.requestJob(new ImageBuildJobAbstractAdaptor(characterData) {\r
1407 \r
1408                                         /**\r
1409                                          * 構築するパーツセット情報\r
1410                                          */\r
1411                         private PartsSet requestPartsSet;\r
1412 \r
1413                                         /**\r
1414                                          * 非同期のイメージ構築要求の番号.<br>\r
1415                                          */\r
1416                         private long ticket;\r
1417 \r
1418                         @Override\r
1419                         public void onQueueing(long ticket) {\r
1420                                 this.ticket = ticket;\r
1421                                 previewPane.setLoadingRequest(ticket);\r
1422                         }\r
1423                         @Override\r
1424                         public void buildImage(ImageOutput output) {\r
1425                                                 // 合成結果のイメージを引数としてイメージビルダから呼び出される.\r
1426                                 final BufferedImage img = output.getImageOutput();\r
1427                                 Runnable refreshJob = new Runnable() {\r
1428                                         public void run() {\r
1429                                                 previewPane.setPreviewImage(img);\r
1430                                                 previewPane.setLoadingComplete(ticket);\r
1431                                                 showPresetName(requestPartsSet);\r
1432                                         }\r
1433                                 };\r
1434                                 if (SwingUtilities.isEventDispatchThread()) {\r
1435                                         refreshJob.run();\r
1436                                 } else {\r
1437                                         try {\r
1438                                                 SwingUtilities.invokeAndWait(refreshJob);\r
1439                                         } catch (Exception ex) {\r
1440                                                 logger.log(Level.WARNING, "build image failed.", ex);\r
1441                                         }\r
1442                                 }\r
1443                         }\r
1444                         @Override\r
1445                         public void handleException(final Throwable ex) {\r
1446                                                 // 合成中に例外が発生した場合、イメージビルダから呼び出される.\r
1447                                 Runnable showExceptionJob = new Runnable() {\r
1448                                         public void run() {\r
1449                                                 ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);\r
1450                                         }\r
1451                                 };\r
1452                                 if (SwingUtilities.isEventDispatchThread()) {\r
1453                                         showExceptionJob.run();\r
1454                                 } else {\r
1455                                         SwingUtilities.invokeLater(showExceptionJob);\r
1456                                 }\r
1457                         }\r
1458                         @Override\r
1459                         protected PartsSet getPartsSet() {\r
1460                                                 // 合成できる状態になった時点でイメージビルダから呼び出される.\r
1461                                 final PartsSet[] result = new PartsSet[1];\r
1462                                 Runnable collectPartsSetJob = new Runnable() {\r
1463                                         public void run() {\r
1464                                                 PartsSet partsSet = partsSelectionManager.createPartsSet();\r
1465                                                 result[0] = partsSet;\r
1466                                         }\r
1467                                 };\r
1468                                 if (SwingUtilities.isEventDispatchThread()) {\r
1469                                         collectPartsSetJob.run();\r
1470                                 } else {\r
1471                                         try {\r
1472                                                                 // スレッドによるSwingのイベントディスパッチスレッド以外からの呼び出しの場合、\r
1473                                                                 // Swingディスパッチスレッドでパーツの選択状態を取得する.\r
1474                                                 SwingUtilities.invokeAndWait(collectPartsSetJob);\r
1475 \r
1476                                         } catch (InvocationTargetException e) {\r
1477                                                 throw new RuntimeException(e.getMessage(), e);\r
1478                                         } catch (InterruptedException e) {\r
1479                                                 throw new RuntimeException("interrupted:" + e, e);\r
1480                                         }\r
1481                                 }\r
1482                                 if (logger.isLoggable(Level.FINE)) {\r
1483                                         logger.log(Level.FINE, "preview: " + result[0]);\r
1484                                 }\r
1485                                 requestPartsSet = result[0];\r
1486                                 return requestPartsSet;\r
1487                         }\r
1488                 });\r
1489         }\r
1490 \r
1491         /**\r
1492          * プロファイルを開く\r
1493          */\r
1494         protected void onOpenProfile() {\r
1495                 try {\r
1496                         MainFrame main2 = ProfileListManager.openProfile(this);\r
1497                         if (main2 != null) {\r
1498                                 main2.showMainFrame();\r
1499                         }\r
1500 \r
1501                 } catch (Exception ex) {\r
1502                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1503                 }\r
1504         }\r
1505 \r
1506         /**\r
1507          * 背景色を変更する.\r
1508          */\r
1509         protected void onChangeBgColor() {\r
1510                 getJMenuBar().setEnabled(false);\r
1511                 try {\r
1512                         Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
1513                                         .getLocalizedProperties(STRINGS_RESOURCE);\r
1514 \r
1515                         Color color = wallpaperInfo.getBackgroundColor();\r
1516                         color = JColorChooser.showDialog(this, strings.getProperty("chooseBgColor"), color);\r
1517                         if (color != null) {\r
1518                                 applyBackgroundColorOnly(color);\r
1519                         }\r
1520                 } finally {\r
1521                         getJMenuBar().setEnabled(true);\r
1522                 }\r
1523         }\r
1524 \r
1525         /**\r
1526          * 壁紙を変更する.\r
1527          */\r
1528         protected void onChangeWallpaper() {\r
1529                 try {\r
1530                         WallpaperDialog wallpaperDialog = new WallpaperDialog(this);\r
1531 \r
1532                         // 最後に使用した壁紙情報でダイアログを設定する.\r
1533                         wallpaperDialog.setWallpaperInfo(this.wallpaperInfo);\r
1534 \r
1535                         // 壁紙情報を設定するモーダルダイアログを開く\r
1536                         WallpaperInfo wallpaperInfo = wallpaperDialog.showDialog();\r
1537                         if (wallpaperInfo == null) {\r
1538                                 return;\r
1539                         }\r
1540 \r
1541                         // 壁紙情報を保存し、その情報をもとに背景を再描画する.\r
1542                         applyWallpaperInfo(wallpaperInfo, false);\r
1543 \r
1544                 } catch (WallpaperFactoryException ex) {\r
1545                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1546 \r
1547                 } catch (RuntimeException ex) {\r
1548                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1549                 }\r
1550         }\r
1551 \r
1552         /**\r
1553          * 背景色のみ変更し、背景を再描画する.<br>\r
1554          * 壁紙情報全体の更新よりも効率化するためのメソッドである.<br>\r
1555          * \r
1556          * @param bgColor\r
1557          *            背景色\r
1558          */\r
1559         protected void applyBackgroundColorOnly(Color bgColor) {\r
1560                 wallpaperInfo.setBackgroundColor(bgColor);\r
1561                 previewPane.getWallpaper()\r
1562                         .setBackgroundColor(wallpaperInfo.getBackgroundColor());\r
1563         }\r
1564 \r
1565         /**\r
1566          * 壁紙情報を保存し、その情報をもとに背景を再描画する.<br>\r
1567          * ignoreErrorがtrueである場合、適用に失敗した場合はログに記録するのみで、 壁紙情報は保存されず、壁紙も更新されない.<br>\r
1568          * \r
1569          * @param wallpaperInfo\r
1570          *            壁紙情報、null不可\r
1571          * @param ignoreError\r
1572          *            失敗を無視する場合\r
1573          * @throws IOException\r
1574          *             失敗\r
1575          */\r
1576         protected void applyWallpaperInfo(WallpaperInfo wallpaperInfo, boolean ignoreError) throws WallpaperFactoryException {\r
1577                 if (wallpaperInfo == null) {\r
1578                         throw new IllegalArgumentException();\r
1579                 }\r
1580                 // 壁紙情報から壁紙インスタンスを生成する.\r
1581                 WallpaperFactory wallpaperFactory = WallpaperFactory.getInstance();\r
1582                 Wallpaper wallpaper = null;\r
1583 \r
1584                 try {\r
1585                         // 壁紙情報の構築時に問題が発生した場合、\r
1586                         // 回復処理をして継続するかエラーとするか?\r
1587                         WallpaperFactoryErrorRecoverHandler handler = null;\r
1588                         if (ignoreError) {\r
1589                                 handler = new WallpaperFactoryErrorRecoverHandler();\r
1590                         }\r
1591 \r
1592                         // 壁紙情報\r
1593                         wallpaper = wallpaperFactory.createWallpaper(wallpaperInfo, handler);\r
1594 \r
1595                 } catch (WallpaperFactoryException ex) {\r
1596                         logger.log(Level.WARNING, "壁紙情報の適用に失敗しました。", ex);\r
1597                         if ( !ignoreError) {\r
1598                                 throw ex;\r
1599                         }\r
1600 \r
1601                 } catch (RuntimeException ex) {\r
1602                         logger.log(Level.WARNING, "壁紙情報の適用に失敗しました。", ex);\r
1603                         if ( !ignoreError) {\r
1604                                 throw ex;\r
1605                         }\r
1606                 }\r
1607 \r
1608                 if (wallpaper == null) {\r
1609                         return;\r
1610                 }\r
1611 \r
1612                 // 壁紙を更新する.\r
1613                 previewPane.setWallpaper(wallpaper);\r
1614 \r
1615                 // 壁紙情報として記憶する.\r
1616                 this.wallpaperInfo = wallpaperInfo;\r
1617         }\r
1618 \r
1619         /**\r
1620          * プリビューしている画像をファイルに保存する。 サポートしているのはPNG/JPEGのみ。\r
1621          */\r
1622         protected void onSavePicture() {\r
1623                 Toolkit tk = Toolkit.getDefaultToolkit();\r
1624                 BufferedImage img = previewPane.getPreviewImage();\r
1625                 Color imgBgColor = wallpaperInfo.getBackgroundColor();\r
1626                 if (img == null) {\r
1627                         tk.beep();\r
1628                         return;\r
1629                 }\r
1630 \r
1631                 try {\r
1632                         // 出力オプションの調整\r
1633                         OutputOption outputOption = imageSaveHelper.getOutputOption();\r
1634                         outputOption.setZoomFactor(previewPane.getZoomFactor());\r
1635                         outputOption.changeRecommend();\r
1636                         imageSaveHelper.setOutputOption(outputOption);\r
1637 \r
1638                         // ファイルダイアログ表示\r
1639                         File outFile = imageSaveHelper.showSaveFileDialog(this);\r
1640                         if (outFile == null) {\r
1641                                 return;\r
1642                         }\r
1643                         logger.log(Level.FINE, "savePicture: " + outFile);\r
1644                         logger.log(Level.FINE, "outputOption: " + outputOption);\r
1645 \r
1646                         // 画像のファイルへの出力\r
1647                         StringBuilder warnings = new StringBuilder();\r
1648 \r
1649                         setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
1650                         try {\r
1651                                 imageSaveHelper.savePicture(img, imgBgColor, outFile, warnings);\r
1652 \r
1653                         } finally {\r
1654                                 setCursor(Cursor.getDefaultCursor());\r
1655                         }\r
1656                         if (warnings.length() > 0) {\r
1657                                 JOptionPane.showMessageDialog(this, warnings.toString(), "WARNINGS", JOptionPane.WARNING_MESSAGE);\r
1658                         }\r
1659 \r
1660                 } catch (Exception ex) {\r
1661                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1662                 }\r
1663         }\r
1664 \r
1665         /**\r
1666          * 伺か用PNG/PNAの出力.\r
1667          */\r
1668         protected void onSaveAsUkagaka() {\r
1669                 BufferedImage img = previewPane.getPreviewImage();\r
1670                 Color bgColor = wallpaperInfo.getBackgroundColor();\r
1671                 if (img == null) {\r
1672                         Toolkit tk = Toolkit.getDefaultToolkit();\r
1673                         tk.beep();\r
1674                         return;\r
1675                 }\r
1676 \r
1677                 try {\r
1678                         ukagakaImageSaveHelper.save(this, img, bgColor);\r
1679 \r
1680                 } catch (IOException ex) {\r
1681                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1682                 }\r
1683         }\r
1684 \r
1685         /**\r
1686          * 伺か用PNG/PNAの変換\r
1687          */\r
1688         protected void onConvertUkagaka() {\r
1689                 try {\r
1690                         Color colorKey = wallpaperInfo.getBackgroundColor();\r
1691                         ukagakaImageSaveHelper.convertChooseFiles(this, colorKey);\r
1692 \r
1693                 } catch (IOException ex) {\r
1694                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1695                 }\r
1696         }\r
1697 \r
1698         /**\r
1699          * プロファイルの場所を開く\r
1700          */\r
1701         protected void onBrowseProfileDir() {\r
1702                 if (!characterData.isValid()) {\r
1703                         Toolkit tk = Toolkit.getDefaultToolkit();\r
1704                         tk.beep();\r
1705                         return;\r
1706                 }\r
1707                 try {\r
1708                         DesktopUtilities.browseBaseDir(characterData.getDocBase());\r
1709 \r
1710                 } catch (Exception ex) {\r
1711                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1712                 }\r
1713         }\r
1714 \r
1715         /**\r
1716          * このプロファイルを編集する.\r
1717          */\r
1718         protected void onEditProfile() {\r
1719                 if (!characterData.isValid()) {\r
1720                         Toolkit tk = Toolkit.getDefaultToolkit();\r
1721                         tk.beep();\r
1722                         return;\r
1723                 }\r
1724                 try {\r
1725                         CharacterData cd = this.characterData;\r
1726                         CharacterData newCd = ProfileListManager.editProfile(this, cd);\r
1727                         if (newCd != null) {\r
1728                                 MainFrame.notifyChangeCharacterData(cd, newCd, this);\r
1729                         }\r
1730 \r
1731                 } catch (Exception ex) {\r
1732                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1733                 }\r
1734         }\r
1735 \r
1736         /**\r
1737          * パーツの管理ダイアログを開く.<br>\r
1738          */\r
1739         protected void onManageParts() {\r
1740                 if (!characterData.isValid()) {\r
1741                         Toolkit tk = Toolkit.getDefaultToolkit();\r
1742                         tk.beep();\r
1743                         return;\r
1744                 }\r
1745 \r
1746                 PartsManageDialog mrgDlg = new PartsManageDialog(this, characterData);\r
1747                 mrgDlg.setVisible(true);\r
1748 \r
1749                 if (mrgDlg.isUpdated()) {\r
1750                         // パーツ管理情報が更新された場合、\r
1751                         // パーツデータをリロードする.\r
1752                         if (characterData.reloadPartsData()) {\r
1753                                 partsSelectionManager.loadParts();\r
1754                                 requestPreview();\r
1755                         }\r
1756                 }\r
1757         }\r
1758 \r
1759         /**\r
1760          * 「パーツ検索」ダイアログを開く.<br>\r
1761          * すでに開いているダイアログがあれば、それにフォーカスを当てる.<br>\r
1762          */\r
1763         protected void openSearchDialog() {\r
1764                 if (!characterData.isValid()) {\r
1765                         Toolkit tk = Toolkit.getDefaultToolkit();\r
1766                         tk.beep();\r
1767                         return;\r
1768                 }\r
1769 \r
1770                 if (lastUseSearchPartsDialog != null) {\r
1771                         // 開いているダイアログがあれば、それにフォーカスを当てる.\r
1772                         if (lastUseSearchPartsDialog.isDisplayable() && lastUseSearchPartsDialog.isVisible()) {\r
1773                                 lastUseSearchPartsDialog.requestFocus();\r
1774                                 return;\r
1775                         }\r
1776                 }\r
1777 \r
1778                 SearchPartsDialog searchPartsDlg = new SearchPartsDialog(this, characterData, partsSelectionManager);\r
1779                 WindowAdjustLocationSupport.alignRight(this, searchPartsDlg, 0, true);\r
1780                 searchPartsDlg.setVisible(true);\r
1781                 lastUseSearchPartsDialog = searchPartsDlg;\r
1782         }\r
1783 \r
1784         /**\r
1785          * 「パーツ検索」ダイアログを閉じる.<br>\r
1786          */\r
1787         protected void closeSearchDialog() {\r
1788                 lastUseSearchPartsDialog = null;\r
1789                 for (SearchPartsDialog dlg : SearchPartsDialog.getDialogs()) {\r
1790                         if (dlg != null && dlg.isDisplayable() && dlg.getParent() == this) {\r
1791                                 dlg.dispose();\r
1792                         }\r
1793                 }\r
1794         }\r
1795 \r
1796         /**\r
1797          * 「お気に入りの管理」ダイアログを閉じる\r
1798          */\r
1799         protected void closeManageFavoritesDialog() {\r
1800                 if (lastUseManageFavoritesDialog != null) {\r
1801                         if (lastUseManageFavoritesDialog.isDisplayable()) {\r
1802                                 lastUseManageFavoritesDialog.dispose();\r
1803                         }\r
1804                         lastUseManageFavoritesDialog = null;\r
1805                 }\r
1806         }\r
1807 \r
1808         /**\r
1809          * クリップボードにコピー\r
1810          * \r
1811          * @param screenImage\r
1812          *            スクリーンイメージ\r
1813          */\r
1814         protected void onCopy(boolean screenImage) {\r
1815                 try {\r
1816                         BufferedImage img = previewPane.getPreviewImage();\r
1817                         if (img == null) {\r
1818                                 Toolkit tk = Toolkit.getDefaultToolkit();\r
1819                                 tk.beep();\r
1820                                 return;\r
1821                         }\r
1822 \r
1823                         if (screenImage) {\r
1824                                 // 表示している内容をそのままコピーする.\r
1825                                 img = previewPane.getScreenImage();\r
1826                         }\r
1827 \r
1828                         Color imgBgColor = wallpaperInfo.getBackgroundColor();\r
1829                         ClipboardUtil.setImage(img, imgBgColor);\r
1830 \r
1831                 } catch (Exception ex) {\r
1832                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1833                 }\r
1834         }\r
1835 \r
1836         /**\r
1837          * アプリケーションの設定ダイアログを開く\r
1838          */\r
1839         protected void onPreferences() {\r
1840                 AppConfigDialog appConfigDlg = new AppConfigDialog(this);\r
1841                 appConfigDlg.setVisible(true);\r
1842         }\r
1843 \r
1844         /**\r
1845          * 新規モードでインポートウィザードを実行する.<br>\r
1846          */\r
1847         protected void onImportNew() {\r
1848                 if (!characterData.isValid()) {\r
1849                         Toolkit tk = Toolkit.getDefaultToolkit();\r
1850                         tk.beep();\r
1851                         return;\r
1852                 }\r
1853 \r
1854                 try {\r
1855                         // インポートウィザードの実行(新規モード)\r
1856                         ImportWizardDialog importWizDialog = new ImportWizardDialog(this, null, null);\r
1857                         importWizDialog.setVisible(true);\r
1858                         int exitCode = importWizDialog.getExitCode();\r
1859                         if (exitCode == ImportWizardDialog.EXIT_PROFILE_CREATED) {\r
1860                                 CharacterData cd = importWizDialog.getImportedCharacterData();\r
1861                                 if (cd != null && cd.isValid()) {\r
1862                                         // インポートしたキャラクターデータのプロファイルを開く.\r
1863                                         setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
1864                                         try {\r
1865                                                 MainFrame mainFrame = ProfileListManager.openProfile(cd);\r
1866                                                 mainFrame.setVisible(true);\r
1867 \r
1868                                         } finally {\r
1869                                                 setCursor(Cursor.getDefaultCursor());\r
1870                                         }\r
1871                                 }\r
1872                         }\r
1873 \r
1874                 } catch (Exception ex) {\r
1875                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1876                 }\r
1877         }\r
1878 \r
1879         /**\r
1880          * 現在のプロファイルに対するインポートウィザードを実行する.<br>\r
1881          * インポートが実行された場合は、パーツをリロードする.<br>\r
1882          * インポートウィザード表示中は監視スレッドは停止される.<br>\r
1883          * \r
1884          * @param initFile\r
1885          *            アーカイブファィルまたはディレクトリ、指定がなければnull\r
1886          */\r
1887         protected void onImport(List<File> initFiles) {\r
1888                 if (!characterData.isValid()) {\r
1889                         Toolkit tk = Toolkit.getDefaultToolkit();\r
1890                         tk.beep();\r
1891                         return;\r
1892                 }\r
1893 \r
1894                 try {\r
1895                         watchAgent.suspend();\r
1896                         try {\r
1897                                 // インポートウィザードの実行\r
1898                                 ImportWizardDialog importWizDialog = new ImportWizardDialog(this, characterData, initFiles);\r
1899                                 importWizDialog.setVisible(true);\r
1900 \r
1901                                 if (importWizDialog.getExitCode() == ImportWizardDialog.EXIT_PROFILE_UPDATED) {\r
1902                                         CharacterData importedCd = importWizDialog.getImportedCharacterData();\r
1903                                         notifyImportedPartsOrFavorites(characterData, importedCd, this);\r
1904                                 }\r
1905 \r
1906                         } finally {\r
1907                                 watchAgent.resume();\r
1908                         }\r
1909 \r
1910                 } catch (Exception ex) {\r
1911                         ErrorMessageHelper.showErrorDialog(this, ex);\r
1912                 }\r
1913         }\r
1914 \r
1915         /**\r
1916          * パーツとお気に入りをリロードする.<br>\r
1917          * まだロードされていない場合はあらたにロードする.<br>\r
1918          * 引数newCdが指定されている場合は、現在のキャラクター定義の説明文を更新する.<br>\r
1919          * (説明文の更新以外には使用されない.)<br>\r
1920          * \r
1921          * @param newCd\r
1922          *            説明文更新のための更新されたキャラクターデータを指定する。null可\r
1923          * @param forceRepaint\r
1924          *            必ず再描画する場合\r
1925          * @throws IOException\r
1926          *             失敗\r
1927          */\r
1928         protected synchronized void reloadPartsAndFavorites(CharacterData newCd,\r
1929                         boolean forceRepaint) throws IOException {\r
1930                 if (newCd != null) {\r
1931                         // (インポート画面では説明文のみ更新するので、それだけ取得)\r
1932                         characterData.setDescription(newCd.getDescription());\r
1933                 }\r
1934 \r
1935                 if ( !characterData.isPartsLoaded()) {\r
1936                         // キャラクターデータが、まだ読み込まれていなければ読み込む.\r
1937                         ProfileListManager.loadCharacterData(characterData);\r
1938                         ProfileListManager.loadFavorites(characterData);\r
1939                         partsSelectionManager.loadParts();\r
1940 \r
1941                 } else {\r
1942                         // パーツデータをリロードする.\r
1943                         if (characterData.reloadPartsData()) {\r
1944                                 partsSelectionManager.loadParts();\r
1945                         }\r
1946 \r
1947                         // お気に入りをリロードする.\r
1948                         CharacterDataPersistent persiste = CharacterDataPersistent.getInstance();\r
1949                         persiste.loadFavorites(characterData);\r
1950 \r
1951                         // お気に入りが更新されたことを通知する.\r
1952                         FavoritesChangeObserver.getDefault().notifyFavoritesChange(\r
1953                                         MainFrame.this, characterData);\r
1954                 }\r
1955 \r
1956                 // 現在選択されているパーツセットがない場合はデフォルトのパーツセットを選択する.\r
1957                 if (showDefaultParts(false) || forceRepaint) {\r
1958                         requestPreview();\r
1959                 }\r
1960         }\r
1961 \r
1962         protected void onExport() {\r
1963                 if (!characterData.isValid()) {\r
1964                         Toolkit tk = Toolkit.getDefaultToolkit();\r
1965                         tk.beep();\r
1966                         return;\r
1967                 }\r
1968                 ExportWizardDialog exportWizDlg = new ExportWizardDialog(this, characterData, previewPane.getPreviewImage());\r
1969                 exportWizDlg.setVisible(true);\r
1970         }\r
1971 \r
1972         protected void onResetColor() {\r
1973                 if (!characterData.isValid()) {\r
1974                         Toolkit tk = Toolkit.getDefaultToolkit();\r
1975                         tk.beep();\r
1976                         return;\r
1977                 }\r
1978 \r
1979                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
1980                                 .getLocalizedProperties(STRINGS_RESOURCE);\r
1981 \r
1982                 if (JOptionPane.showConfirmDialog(this, strings.get("confirm.resetcolors"), strings.getProperty("confirm"),\r
1983                                 JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) {\r
1984                         return;\r
1985                 }\r
1986                 characterData.getPartsColorManager().resetPartsColorInfo();\r
1987                 partsColorCoordinator.initColorDialog();\r
1988                 requestPreview();\r
1989         }\r
1990 \r
1991         /**\r
1992          * プロファイルを閉じる.\r
1993          */\r
1994         protected void onCloseProfile() {\r
1995                 saveWorkingSet();\r
1996                 ProfileListManager.unregisterUsedCharacterData(characterData);\r
1997 \r
1998                 if (characterData.isValid()) {\r
1999 \r
2000                         // 最後に使用したキャラクターデータとして記憶する.\r
2001                         try {\r
2002                                 RecentDataPersistent recentPersist = RecentDataPersistent.getInstance();\r
2003                                 recentPersist.saveRecent(characterData);\r
2004 \r
2005                         } catch (Exception ex) {\r
2006                                 logger.log(Level.WARNING, "recent data saving failed.", ex);\r
2007                                 // recent情報の記録に失敗しても致命的ではないので、これは無視する.\r
2008                         }\r
2009                 }\r
2010 \r
2011                 // イメージビルダスレッド・ディレクトリ監視スレッドを停止する.\r
2012                 stopAgents();\r
2013 \r
2014                 // フレームウィンドウを破棄する.\r
2015                 dispose();\r
2016 \r
2017                 // 破棄されたことをロギングする.\r
2018                 logger.log(Level.FINE, "dispose mainframe.");\r
2019         }\r
2020 \r
2021         /**\r
2022          * 開いている、すべてのプロファイルを閉じる.<br>\r
2023          * (Mac OS Xのcmd+Qで閉じる場合などで使用される.)<br>\r
2024          */\r
2025         public static void closeAllProfiles() {\r
2026                 // ウィンドウが閉じられることでアクティブなフレームが切り替わる場合を想定し、\r
2027                 // 現在のアクティブなウィンドウをあらかじめ記憶しておく\r
2028                 MainFrame mainFrame = activedMainFrame;\r
2029 \r
2030                 // gcをかけてファイナライズを促進させる\r
2031                 SystemUtil.gc();\r
2032 \r
2033                 // ファイナライズされていないFrameのうち、ネイティブリソースと関連づけられている\r
2034                 // フレームについて、それがMainFrameのインスタンスであれば閉じる.\r
2035                 // ただし、現在アクティブなものは除く\r
2036                 for (Frame frame : JFrame.getFrames()) {\r
2037                         try {\r
2038                                 if (frame.isDisplayable()) {\r
2039                                         // ネイティブリソースと関連づけられているフレーム\r
2040                                         if (frame instanceof MainFrame && frame != mainFrame) {\r
2041                                                 // MainFrameのインスタンスであるので閉じる処理が可能.\r
2042                                                 // (現在アクティブなメインフレームは最後に閉じるため、ここでは閉じない.)\r
2043                                                 ((MainFrame) frame).onCloseProfile();\r
2044                                         }\r
2045                                 }\r
2046 \r
2047                         } catch (Throwable ex) {\r
2048                                 logger.log(Level.SEVERE, "mainframe closing failed.", ex);\r
2049                                 // フレームを閉じるときに失敗した場合、通常、致命的問題だが\r
2050                                 // クローズ処理は継続しなければならない.\r
2051                         }\r
2052                 }\r
2053 \r
2054                 // 現在アクティブなフレームを閉じる.\r
2055                 // 最後に閉じることで「最後に使ったプロファイル」として記憶させる.\r
2056                 if (activedMainFrame != null && activedMainFrame.isDisplayable()) {\r
2057                         try {\r
2058                                 activedMainFrame.onCloseProfile();\r
2059 \r
2060                         } catch (Throwable ex) {\r
2061                                 logger.log(Level.SEVERE, "mainframe closing failed.", ex);\r
2062                                 // フレームを閉じるときに失敗した場合、通常、致命的問題だが\r
2063                                 // クローズ処理は継続しなければならない.\r
2064                         }\r
2065                 }\r
2066         }\r
2067 \r
2068         /**\r
2069          * 画面の作業状態を保存する.\r
2070          */\r
2071         protected void saveWorkingSet() {\r
2072                 if (!characterData.isValid()) {\r
2073                         return;\r
2074                 }\r
2075                 try {\r
2076                         // ワーキングセットの作成\r
2077                         WorkingSet workingSet = new WorkingSet();\r
2078                         workingSet.setCharacterDocBase(characterData.getDocBase());\r
2079                         workingSet.setCharacterDataRev(characterData.getRev());\r
2080                         PartsSet partsSet = partsSelectionManager.createPartsSet();\r
2081                         workingSet.setPartsSet(partsSet);\r
2082                         workingSet.setPartsColorInfoMap(characterData\r
2083                                         .getPartsColorManager().getPartsColorInfoMap());\r
2084                         workingSet.setLastUsedSaveDir(imageSaveHelper.getLastUsedSaveDir());\r
2085                         workingSet.setLastUsedExportDir(ExportWizardDialog.getLastUsedDir());\r
2086                         workingSet.setLastUsePresetParts(lastUsePresetParts);\r
2087                         workingSet\r
2088                                         .setCharacterData(characterData.duplicateBasicInfo(false)); // パーツセットは保存しない.\r
2089                         workingSet.setWallpaperInfo(wallpaperInfo);\r
2090 \r
2091                         // XML形式でのワーキングセットの保存\r
2092                         WorkingSetPersist workingSetPersist = WorkingSetPersist\r
2093                                         .getInstance();\r
2094                         workingSetPersist.saveWorkingSet(workingSet);\r
2095 \r
2096                 } catch (Exception ex) {\r
2097                         ErrorMessageHelper.showErrorDialog(this, ex);\r
2098                 }\r
2099         }\r
2100 \r
2101         /**\r
2102          * 画面の作業状態を復元する.\r
2103          * \r
2104          * @return ワーキングセットを読み込んだ場合はtrue、そうでなければfalse\r
2105          */\r
2106         protected boolean loadWorkingSet() {\r
2107                 if (!characterData.isValid()) {\r
2108                         return false;\r
2109                 }\r
2110                 try {\r
2111                         WorkingSetPersist workingSetPersist = WorkingSetPersist\r
2112                                         .getInstance();\r
2113                         WorkingSet2 workingSet2 = workingSetPersist\r
2114                                         .loadWorkingSet(characterData);\r
2115                         if (workingSet2 == null) {\r
2116                                 // ワーキングセットがない場合.\r
2117                                 return false;\r
2118                         }\r
2119 \r
2120                         URI docBase = characterData.getDocBase();\r
2121                         if (docBase != null\r
2122                                         && !docBase.equals(workingSet2.getCharacterDocBase())) {\r
2123                                 // docBaseが一致せず\r
2124                                 return false;\r
2125                         }\r
2126                         String sig = characterData.toSignatureString();\r
2127                         if (!sig.equals(workingSet2.getCharacterDataSig())) {\r
2128                                 // 構造が一致せず.\r
2129                                 return false;\r
2130                         }\r
2131 \r
2132                         // パーツの色情報を復元する.\r
2133                         Map<PartsIdentifier, PartsColorInfo> partsColorInfoMap = characterData\r
2134                                         .getPartsColorManager().getPartsColorInfoMap();\r
2135                         workingSet2.createCompatible(characterData, partsColorInfoMap);\r
2136 \r
2137                         // 選択されているパーツの復元\r
2138                         IndependentPartsSetInfo partsSetInfo = workingSet2\r
2139                                         .getCurrentPartsSet();\r
2140                         if (partsSetInfo != null) {\r
2141                                 PartsSet partsSet = IndependentPartsSetInfo.convertPartsSet(\r
2142                                                 partsSetInfo, characterData, false);\r
2143                                 selectPresetParts(partsSet);\r
2144 \r
2145                                 // 最後に選択したお気に入り情報の復元\r
2146                                 IndependentPartsSetInfo lastUsePresetPartsInfo = workingSet2\r
2147                                                 .getLastUsePresetParts();\r
2148                                 if (lastUsePresetPartsInfo != null\r
2149                                                 && lastUsePresetPartsInfo.getId() != null\r
2150                                                 && lastUsePresetPartsInfo.getId().trim().length() > 0) {\r
2151                                         PartsSet lastUsePresetParts = IndependentPartsSetInfo\r
2152                                                         .convertPartsSet(lastUsePresetPartsInfo,\r
2153                                                                         characterData, false);\r
2154                                         if (lastUsePresetParts.isSameStructure(partsSet)) {\r
2155                                                 this.lastUsePresetParts = lastUsePresetParts;\r
2156                                                 showPresetName(lastUsePresetParts);\r
2157                                         }\r
2158                                 }\r
2159                         }\r
2160 \r
2161                         // 最後に保存したディレクトリを復元する.\r
2162                         imageSaveHelper.setLastUseSaveDir(workingSet2.getLastUsedSaveDir());\r
2163                         ExportWizardDialog.setLastUsedDir(workingSet2\r
2164                                         .getLastUsedExportDir());\r
2165 \r
2166                         // 壁紙情報を取得する.\r
2167                         WallpaperInfo wallpaperInfo = workingSet2.getWallpaperInfo();\r
2168                         if (wallpaperInfo != null) {\r
2169                                 // 壁紙情報を保存し、その情報をもとに背景を再描画する.\r
2170                                 // (適用に失敗した場合はエラーは無視し、壁紙情報は保存しない.)\r
2171                                 applyWallpaperInfo(wallpaperInfo, true);\r
2172                         }\r
2173                         return true;\r
2174 \r
2175                 } catch (Exception ex) {\r
2176                         ErrorMessageHelper.showErrorDialog(this, ex);\r
2177                 }\r
2178                 return false;\r
2179         }\r
2180 \r
2181 \r
2182         protected void onAbout() {\r
2183                 try {\r
2184                         AboutBox aboutBox = new AboutBox(this);\r
2185                         aboutBox.showAboutBox();\r
2186 \r
2187                 } catch (Exception ex) {\r
2188                         ErrorMessageHelper.showErrorDialog(this, ex);\r
2189                 }\r
2190         }\r
2191 \r
2192         protected void onHelp() {\r
2193                 Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
2194                         .getLocalizedProperties(STRINGS_RESOURCE);\r
2195                 String helpURL = strings.getProperty("help.url");\r
2196                 String helpDescription = strings.getProperty("help.show");\r
2197                 DesktopUtilities.browse(this, helpURL, helpDescription);\r
2198         }\r
2199 \r
2200         protected void onFlipHolizontal() {\r
2201                 if (!characterData.isValid()) {\r
2202                         Toolkit tk = Toolkit.getDefaultToolkit();\r
2203                         tk.beep();\r
2204                         return;\r
2205                 }\r
2206 \r
2207                 double[] affineTransformParameter = partsSelectionManager.getAffineTransformParameter();\r
2208                 if (affineTransformParameter == null) {\r
2209                         // 左右フリップするアフィン変換パラメータを構築する.\r
2210                         Dimension siz = characterData.getImageSize();\r
2211                         if (siz != null) {\r
2212                                 affineTransformParameter = new double[] {-1., 0, 0, 1., siz.width, 0};\r
2213                         }\r
2214                 } else {\r
2215                         // アフィン変換パラメータをクリアする.\r
2216                         affineTransformParameter = null;\r
2217                 }\r
2218                 partsSelectionManager.setAffineTransformParameter(affineTransformParameter);\r
2219                 requestPreview();\r
2220         }\r
2221 \r
2222         protected void onSetDefaultPicture() {\r
2223                 if (!characterData.isValid()) {\r
2224                         Toolkit tk = Toolkit.getDefaultToolkit();\r
2225                         tk.beep();\r
2226                         return;\r
2227                 }\r
2228                 try {\r
2229                         BufferedImage samplePicture = previewPane.getPreviewImage();\r
2230                         if (samplePicture != null) {\r
2231                                 CharacterDataPersistent persist = CharacterDataPersistent.getInstance();\r
2232                                 persist.saveSamplePicture(characterData, samplePicture);\r
2233                         }\r
2234 \r
2235                 } catch (Exception ex) {\r
2236                         ErrorMessageHelper.showErrorDialog(this, ex);\r
2237                 }\r
2238         }\r
2239 \r
2240         protected void onInformation() {\r
2241                 if (!characterData.isValid()) {\r
2242                         Toolkit tk = Toolkit.getDefaultToolkit();\r
2243                         tk.beep();\r
2244                         return;\r
2245                 }\r
2246 \r
2247                 PartsSet partsSet = partsSelectionManager.createPartsSet();\r
2248                 InformationDialog infoDlg = new InformationDialog(this, characterData, partsSet);\r
2249                 infoDlg.setVisible(true);\r
2250         }\r
2251 \r
2252         protected void onManageFavorites() {\r
2253                 if (!characterData.isValid()) {\r
2254                         Toolkit tk = Toolkit.getDefaultToolkit();\r
2255                         tk.beep();\r
2256                         return;\r
2257                 }\r
2258 \r
2259                 if (lastUseManageFavoritesDialog != null) {\r
2260                         // 開いているダイアログがあれば、それにフォーカスを当てる.\r
2261                         if (lastUseManageFavoritesDialog.isDisplayable()\r
2262                                         && lastUseManageFavoritesDialog.isVisible()) {\r
2263                                 lastUseManageFavoritesDialog.requestFocus();\r
2264                                 return;\r
2265                         }\r
2266                 }\r
2267 \r
2268                 // お気に入り編集ダイアログを開く\r
2269                 ManageFavoriteDialog dlg = new ManageFavoriteDialog(this, characterData);\r
2270                 dlg.setFavoriteManageCallback(new FavoriteManageCallback() {\r
2271 \r
2272                         public void refreshFavorites(CharacterData cd) {\r
2273                                 // お気に入りの状態を最新にリフレッシュする.\r
2274                                 MainFrame.this.refreshFavorites();\r
2275                         }\r
2276 \r
2277                         public void selectFavorites(PartsSet partsSet) {\r
2278                                 // お気に入り編集ダイアログで選択されたパーツを選択表示する.\r
2279                                 selectPresetParts(partsSet);\r
2280                         }\r
2281 \r
2282                         public void updateFavorites(CharacterData characterData,\r
2283                                         boolean savePreset) {\r
2284                                 // お気に入りを登録する.\r
2285                                 try {\r
2286                                         setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
2287                                         try {\r
2288                                                 CharacterDataPersistent persiste = CharacterDataPersistent\r
2289                                                                 .getInstance();\r
2290                                                 if (savePreset) {\r
2291                                                         persiste.updateProfile(characterData);\r
2292                                                 }\r
2293 \r
2294                                                 persiste.saveFavorites(characterData);\r
2295 \r
2296                                                 // お気に入りが更新されたことを通知する.\r
2297                                                 FavoritesChangeObserver.getDefault()\r
2298                                                                 .notifyFavoritesChange(MainFrame.this,\r
2299                                                                                 characterData);\r
2300 \r
2301                                         } finally {\r
2302                                                 setCursor(Cursor.getDefaultCursor());\r
2303                                         }\r
2304 \r
2305                                 } catch (Exception ex) {\r
2306                                         ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);\r
2307                                 }\r
2308                         }\r
2309                 });\r
2310                 WindowAdjustLocationSupport.alignRight(this, dlg, 0, true);\r
2311                 dlg.setVisible(true);\r
2312                 lastUseManageFavoritesDialog = dlg;\r
2313         }\r
2314 \r
2315         /**\r
2316          * 最新のお気に入りの状態を取り出す.<br>\r
2317          */\r
2318         protected void refreshFavorites() {\r
2319                 logger.log(Level.FINE, "refresh Favorites.: " + characterData);\r
2320                 try {\r
2321                         setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
2322                         try {\r
2323                                 CharacterDataPersistent persiste = CharacterDataPersistent.getInstance();\r
2324                                 characterData.clearPartsSets(true);\r
2325                                 persiste.loadFavorites(characterData);\r
2326                         } finally {\r
2327                                 setCursor(Cursor.getDefaultCursor());\r
2328                         }\r
2329 \r
2330                 } catch (Exception ex) {\r
2331                         logger.log(Level.WARNING, "can't refresh favorites: " + characterData, ex);\r
2332                 }\r
2333         }\r
2334 \r
2335         protected void onRegisterFavorite() {\r
2336                 if (!characterData.isValid()) {\r
2337                         Toolkit tk = Toolkit.getDefaultToolkit();\r
2338                         tk.beep();\r
2339                         return;\r
2340                 }\r
2341                 try {\r
2342                         // パーツセットを生成\r
2343                         PartsSet partsSet = partsSelectionManager.createPartsSet();\r
2344                         if (partsSet.isEmpty()) {\r
2345                                 // 空のパーツセットは登録しない.\r
2346                                 return;\r
2347                         }\r
2348 \r
2349                         Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
2350                                         .getLocalizedProperties(STRINGS_RESOURCE);\r
2351 \r
2352                         // お気に入りに登録するパーツセットが最後に使用したお気に入りと同じ構成であれば、\r
2353                         // そのお気に入り名を使用する.\r
2354                         String initName = getSuggestPartsSetName(partsSet, false);\r
2355 \r
2356                         // カラー情報の有無のチェックボックス.\r
2357                         JCheckBox chkColorInfo = new JCheckBox(strings.getProperty("input.favoritesColorInfo"));\r
2358                         chkColorInfo.setSelected(true);\r
2359                         String partsSetId = null;\r
2360                         if (initName != null && lastUsePresetParts != null) {\r
2361                                 partsSetId = lastUsePresetParts.getPartsSetId();\r
2362                         }\r
2363 \r
2364                         // 上書き保存の可否のチェックボックス\r
2365                         JCheckBox chkOverwrite = new JCheckBox(strings.getProperty("input.favoritesOverwrite"));\r
2366                         chkOverwrite.setSelected(partsSetId != null && partsSetId.length() > 0);\r
2367                         chkOverwrite.setEnabled(partsSetId != null && partsSetId.length() > 0);\r
2368 \r
2369                         // チェックボックスパネル\r
2370                         Box checkboxsPanel = new Box(BoxLayout.PAGE_AXIS);\r
2371                         checkboxsPanel.add(chkColorInfo);\r
2372                         checkboxsPanel.add(chkOverwrite);\r
2373 \r
2374                         // 入力ダイアログを開く\r
2375                         String name = (String) JOptionPane.showInputDialog(this,\r
2376                                         checkboxsPanel,\r
2377                                         strings.getProperty("input.favorites"),\r
2378                                         JOptionPane.QUESTION_MESSAGE,\r
2379                                         null,\r
2380                                         null,\r
2381                                         initName == null ? "" : initName);\r
2382                         if (name == null || name.trim().length() == 0) {\r
2383                                 return;\r
2384                         }\r
2385 \r
2386                         boolean includeColorInfo = chkColorInfo.isSelected();\r
2387                         if (!includeColorInfo) {\r
2388                                 // カラー情報を除去する.\r
2389                                 partsSet.removeColorInfo();\r
2390                         }\r
2391 \r
2392                         // 新規の場合、もしくは上書きしない場合はIDを設定する.\r
2393                         if (partsSetId == null || !chkOverwrite.isSelected()) {\r
2394                                 partsSetId = "ps" + UUID.randomUUID().toString();\r
2395                         }\r
2396                         partsSet.setPartsSetId(partsSetId);\r
2397 \r
2398                         // 名前を設定する.\r
2399                         partsSet.setLocalizedName(name);\r
2400 \r
2401                         // ファイルに保存\r
2402                         setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
2403                         try {\r
2404                                 CharacterDataPersistent persiste = CharacterDataPersistent.getInstance();\r
2405                                 // 現在の最新情報を取り出す.\r
2406                                 characterData.clearPartsSets(true);\r
2407                                 persiste.loadFavorites(characterData);\r
2408 \r
2409                                 // お気に入りコレクションに登録\r
2410                                 characterData.addPartsSet(partsSet);\r
2411 \r
2412                                 persiste.saveFavorites(characterData);\r
2413 \r
2414                                 // お気に入りが更新されたことを通知する.\r
2415                                 FavoritesChangeObserver.getDefault().notifyFavoritesChange(\r
2416                                                 MainFrame.this, characterData);\r
2417 \r
2418                         } finally {\r
2419                                 setCursor(Cursor.getDefaultCursor());\r
2420                         }\r
2421 \r
2422                         // 最後に選択したお気に入りにする\r
2423                         lastUsePresetParts = partsSet;\r
2424                         showPresetName(partsSet);\r
2425 \r
2426                 } catch (Exception ex) {\r
2427                         ErrorMessageHelper.showErrorDialog(this, ex);\r
2428                 }\r
2429         }\r
2430 \r
2431         /**\r
2432          * すべての解除可能なパーツの選択を解除する。\r
2433          */\r
2434         protected void onDeselectAll() {\r
2435                 partsSelectionManager.deselectAll();\r
2436         }\r
2437 \r
2438         /**\r
2439          * 単一選択カテゴリのパーツの解除を許可する。\r
2440          */\r
2441         protected void onDeselectableAllCategory() {\r
2442                 partsSelectionManager\r
2443                                 .setDeselectableSingleCategory( !partsSelectionManager\r
2444                                                 .isDeselectableSingleCategory());\r
2445         }\r
2446 \r
2447         /**\r
2448          * プレビューのズームボックスの表示制御\r
2449          */\r
2450         protected void onEnableZoom() {\r
2451                 previewPane.setVisibleZoomBox( !previewPane.isVisibleZoomBox());\r
2452         }\r
2453 \r
2454         /**\r
2455          * メニューバーを構築します.\r
2456          * \r
2457          * @return メニューバー\r
2458          */\r
2459         protected JMenuBar createMenuBar() {\r
2460                 final Properties strings = LocalizedResourcePropertyLoader\r
2461                                 .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);\r
2462 \r
2463                 MenuDataFactory[] menus = new MenuDataFactory[] {\r
2464                                 new MenuDataFactory("menu.file", new MenuDataFactory[] {\r
2465                                                 new MenuDataFactory("file.openProfile", new ActionListener() {\r
2466                                                         public void actionPerformed(ActionEvent e) {\r
2467                                                                 onOpenProfile();\r
2468                                                         }\r
2469                                                 }),\r
2470                                                 new MenuDataFactory("file.savePicture", new ActionListener() {\r
2471                                                         public void actionPerformed(ActionEvent e) {\r
2472                                                                 onSavePicture();\r
2473                                                         }\r
2474                                                 }),\r
2475                                                 new MenuDataFactory("file.ukagaka", new MenuDataFactory[] {\r
2476                                                                 new MenuDataFactory("file.saveAsUkagaka", new ActionListener() {\r
2477                                                                         public void actionPerformed(ActionEvent e) {\r
2478                                                                                 onSaveAsUkagaka();\r
2479                                                                         };\r
2480                                                                 }),\r
2481                                                                 new MenuDataFactory("file.convertUkagaka", new ActionListener() {\r
2482                                                                         public void actionPerformed(ActionEvent e) {\r
2483                                                                                 onConvertUkagaka();\r
2484                                                                         };\r
2485                                                                 }),\r
2486                                                 }),\r
2487                                                 null,\r
2488                                                 new MenuDataFactory("file.editprofile", new ActionListener() {\r
2489                                                         public void actionPerformed(ActionEvent e) {\r
2490                                                                 onEditProfile();\r
2491                                                         }\r
2492                                                 }),\r
2493                                                 new MenuDataFactory("file.opendir", new ActionListener() {\r
2494                                                         public void actionPerformed(ActionEvent e) {\r
2495                                                                 onBrowseProfileDir();\r
2496                                                         }\r
2497                                                 }),\r
2498                                                 new MenuDataFactory("file.import", new MenuDataFactory[] {\r
2499                                                                 new MenuDataFactory("file.importMe", new ActionListener() {\r
2500                                                                         public void actionPerformed(ActionEvent e) {\r
2501                                                                                 onImport(null);\r
2502                                                                         };\r
2503                                                                 }),\r
2504                                                                 new MenuDataFactory("file.importNew", new ActionListener() {\r
2505                                                                         public void actionPerformed(ActionEvent e) {\r
2506                                                                                 onImportNew();\r
2507                                                                         };\r
2508                                                                 }),\r
2509                                                 }),\r
2510                                                 new MenuDataFactory("file.export", new ActionListener() {\r
2511                                                         public void actionPerformed(ActionEvent e) {\r
2512                                                                 onExport();\r
2513                                                         };\r
2514                                                 }),\r
2515                                                 new MenuDataFactory("file.manageParts", new ActionListener() {\r
2516                                                         public void actionPerformed(ActionEvent e) {\r
2517                                                                 onManageParts();\r
2518                                                         }\r
2519                                                 }),\r
2520                                                 new MenuDataFactory("file.preferences", new ActionListener() {\r
2521                                                         public void actionPerformed(ActionEvent e) {\r
2522                                                                 onPreferences();\r
2523                                                         };\r
2524                                                 }),\r
2525                                                 null,\r
2526                                                 new MenuDataFactory("file.closeProfile", new ActionListener() {\r
2527                                                         public void actionPerformed(ActionEvent e) {\r
2528                                                                 onCloseProfile();\r
2529                                                         }\r
2530                                                 }),\r
2531                                 }),\r
2532                                 new MenuDataFactory("menu.edit", new MenuDataFactory[] {\r
2533                                                 new MenuDataFactory("edit.search", new ActionListener() {\r
2534                                                         public void actionPerformed(ActionEvent e) {\r
2535                                                                 openSearchDialog();\r
2536                                                         }\r
2537                                                 }),\r
2538                                                 new MenuDataFactory("edit.copy", new ActionListener() {\r
2539                                                         public void actionPerformed(ActionEvent e) {\r
2540                                                                 onCopy((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0);\r
2541                                                         }\r
2542                                                 }),\r
2543                                                 new MenuDataFactory("edit.flipHorizontal", new ActionListener() {\r
2544                                                         public void actionPerformed(ActionEvent e) {\r
2545                                                                 onFlipHolizontal();\r
2546                                                         }\r
2547                                                 }),\r
2548                                                 new MenuDataFactory("edit.resetcolor", new ActionListener() {\r
2549                                                         public void actionPerformed(ActionEvent e) {\r
2550                                                                 onResetColor();\r
2551                                                         }\r
2552                                                 }),\r
2553                                                 null,\r
2554                                                 new MenuDataFactory("edit.setDefaultPicture", new ActionListener() {\r
2555                                                         public void actionPerformed(ActionEvent e) {\r
2556                                                                 onSetDefaultPicture();\r
2557                                                         }\r
2558                                                 }),\r
2559                                                 new MenuDataFactory("edit.information", new ActionListener() {\r
2560                                                         public void actionPerformed(ActionEvent e) {\r
2561                                                                 onInformation();\r
2562                                                         }\r
2563                                                 }),\r
2564                                                 null,\r
2565                                                 new MenuDataFactory("edit.deselectall", new ActionListener() {\r
2566                                                         public void actionPerformed(ActionEvent e) {\r
2567                                                                 onDeselectAll();\r
2568                                                         }\r
2569                                                 }),\r
2570                                                 new MenuDataFactory("edit.deselectparts", true, new ActionListener() {\r
2571                                                         public void actionPerformed(ActionEvent e) {\r
2572                                                                 onDeselectableAllCategory();\r
2573                                                         }\r
2574                                                 }),\r
2575                                                 new MenuDataFactory("edit.enableAutoShrink", true, new ActionListener() {\r
2576                                                         public void actionPerformed(ActionEvent e) {\r
2577                                                                 onClickPartsCategoryTitle(null, true);\r
2578                                                         }\r
2579                                                 }),\r
2580                                                 null,\r
2581                                                 new MenuDataFactory("edit.enableZoomBox", true, new ActionListener() {\r
2582                                                         public void actionPerformed(ActionEvent e) {\r
2583                                                                 onEnableZoom();\r
2584                                                         }\r
2585                                                 }),\r
2586                                                 null,\r
2587                                                 new MenuDataFactory("edit.changeBgColor", new ActionListener() {\r
2588                                                         public void actionPerformed(ActionEvent e) {\r
2589                                                                 onChangeBgColor();\r
2590                                                         }\r
2591                                                 }),\r
2592                                                 new MenuDataFactory("edit.changeWallpaper", new ActionListener() {\r
2593                                                         public void actionPerformed(ActionEvent e) {\r
2594                                                                 onChangeWallpaper();\r
2595                                                         }\r
2596                                                 }),\r
2597 \r
2598                                 }),\r
2599                                 new MenuDataFactory("menu.favorite", new MenuDataFactory[] {\r
2600                                                 new MenuDataFactory("favorite.register", new ActionListener() {\r
2601                                                         public void actionPerformed(ActionEvent e) {\r
2602                                                                 onRegisterFavorite();\r
2603                                                         }\r
2604                                                 }),\r
2605                                                 new MenuDataFactory("favorite.manage", new ActionListener() {\r
2606                                                         public void actionPerformed(ActionEvent e) {\r
2607                                                                 onManageFavorites();\r
2608                                                         }\r
2609                                                 }),\r
2610                                                 null,\r
2611                                 }),\r
2612                                 new MenuDataFactory("menu.help", new MenuDataFactory[] {\r
2613                                                 new MenuDataFactory("help.recommendations", (ActionListener) null),\r
2614                                                 null,\r
2615                                                 new MenuDataFactory("help.help", new ActionListener() {\r
2616                                                         public void actionPerformed(ActionEvent e) {\r
2617                                                                 onHelp();\r
2618                                                         }\r
2619                                                 }),\r
2620                                                 new MenuDataFactory("help.forum",\r
2621                                                                 DesktopUtilities.createBrowseAction(\r
2622                                                                                 MainFrame.this,\r
2623                                                                                 strings.getProperty("help.forum.url"),\r
2624                                                                                 strings.getProperty("help.forum.description"))\r
2625                                                 ),\r
2626                                                 new MenuDataFactory("help.bugreport",\r
2627                                                                 DesktopUtilities.createBrowseAction(\r
2628                                                                                 MainFrame.this,\r
2629                                                                                 strings.getProperty("help.reportbugs.url"),\r
2630                                                                                 strings.getProperty("help.reportbugs.description"))\r
2631                                                 ),\r
2632                                                 new MenuDataFactory("help.about", new ActionListener() {\r
2633                                                         public void actionPerformed(ActionEvent e) {\r
2634                                                                 onAbout();\r
2635                                                         }\r
2636                                                 }),\r
2637                                 }), };\r
2638 \r
2639                 final MenuBuilder menuBuilder = new MenuBuilder();\r
2640 \r
2641                 JMenuBar menuBar = menuBuilder.createMenuBar(menus);\r
2642 \r
2643                 menuBuilder.getJMenu("menu.edit").addMenuListener(new MenuListener() {\r
2644                         public void menuCanceled(MenuEvent e) {\r
2645                                 // do nothing.\r
2646                         }\r
2647                         public void menuDeselected(MenuEvent e) {\r
2648                                 // do nothing.\r
2649                         }\r
2650                         public void menuSelected(MenuEvent e) {\r
2651                                 menuBuilder.getJMenuItem("edit.copy").setEnabled(previewPane.getPreviewImage() != null);\r
2652                                 menuBuilder.getJMenuItem("edit.deselectparts").setSelected(\r
2653                                                 partsSelectionManager.isDeselectableSingleCategory());\r
2654                                 menuBuilder.getJMenuItem("edit.enableAutoShrink").setSelected(minimizeMode);\r
2655                                 menuBuilder.getJMenuItem("edit.enableZoomBox").setSelected(previewPane.isVisibleZoomBox());\r
2656                         }\r
2657                 });\r
2658                 final JMenu mnuFavorites = menuBuilder.getJMenu("menu.favorite");\r
2659                 mnuFavorites.addMenuListener(new MenuListener() {\r
2660                         public void menuCanceled(MenuEvent e) {\r
2661                                 // do nothing.\r
2662                         }\r
2663                         public void menuDeselected(MenuEvent e) {\r
2664                                 // do nothing.\r
2665                         }\r
2666                         public void menuSelected(MenuEvent e) {\r
2667                                 onSelectedFavoriteMenu(mnuFavorites);\r
2668                         }\r
2669                 });\r
2670 \r
2671                 // J2SE5の場合は「パーツディレクトリを開く」コマンドは使用不可とする.\r
2672                 if (System.getProperty("java.version").startsWith("1.5")) {\r
2673                         menuBuilder.getJMenuItem("file.opendir").setEnabled(false);\r
2674                 }\r
2675 \r
2676                 // お勧めサイトメニュー構築\r
2677                 final JMenu mnuRecomendation = menuBuilder.getJMenu("help.recommendations");\r
2678                 JMenu mnuHelp = menuBuilder.getJMenu("menu.help");\r
2679                 mnuHelp.addMenuListener(new MenuListener() {\r
2680                         public void menuCanceled(MenuEvent e) {\r
2681                                 // do nothing.\r
2682                         }\r
2683                         public void menuDeselected(MenuEvent e) {\r
2684                                 // do nothing.\r
2685                         }\r
2686                         public void menuSelected(MenuEvent e) {\r
2687                                 onSelectedRecommendationMenu(mnuRecomendation);\r
2688                         }\r
2689                 });\r
2690 \r
2691                 return menuBar;\r
2692         }\r
2693 \r
2694 }\r