OSDN Git Service

レイヤー順序カスタマイズの追加
authorseraphy <seraphy@users.osdn.me>
Thu, 29 Nov 2018 15:37:09 +0000 (00:37 +0900)
committerseraphy <seraphy@users.osdn.me>
Thu, 29 Nov 2018 15:44:54 +0000 (00:44 +0900)
25 files changed:
src/main/java/charactermanaj/graphics/ImageBuildJobAbstractAdaptor.java
src/main/java/charactermanaj/graphics/ImageBuilder.java
src/main/java/charactermanaj/model/CustomLayerOrder.java [new file with mode: 0644]
src/main/java/charactermanaj/model/ListChangeListener.java [new file with mode: 0644]
src/main/java/charactermanaj/model/ObservableList.java [new file with mode: 0644]
src/main/java/charactermanaj/model/io/CustomLayerOrderPersist.java [new file with mode: 0644]
src/main/java/charactermanaj/model/io/CustomLayerOrderXMLReader.java [new file with mode: 0644]
src/main/java/charactermanaj/model/io/CustomLayerOrderXMLWriter.java [new file with mode: 0644]
src/main/java/charactermanaj/model/io/PartsImageCollectionParser.java
src/main/java/charactermanaj/ui/InformationDialog.java
src/main/java/charactermanaj/ui/LayerOrderCustomizeDialog.java [new file with mode: 0644]
src/main/java/charactermanaj/ui/MainFrame.java
src/main/java/charactermanaj/ui/ProfileSelectorDialog.java
src/main/java/charactermanaj/ui/model/CustomLayerPatternMgr.java [new file with mode: 0644]
src/main/java/charactermanaj/ui/model/SimpleComboBoxModel.java [new file with mode: 0644]
src/main/resources/languages/informationdialog.xml
src/main/resources/languages/informationdialog_ja.xml
src/main/resources/languages/informationdialog_zh.xml
src/main/resources/languages/layerordercustomizedialog.xml [new file with mode: 0644]
src/main/resources/languages/layerordercustomizedialog_ja.xml [new file with mode: 0644]
src/main/resources/languages/layerordercustomizedialog_zh.xml [new file with mode: 0644]
src/main/resources/menu/menu.xml
src/main/resources/menu/menu_ja.xml
src/main/resources/menu/menu_zh.xml
src/main/resources/schema/customlayerorder.xsd [new file with mode: 0644]

index c001c53..ef50afe 100644 (file)
@@ -11,6 +11,7 @@ import charactermanaj.model.PartsIdentifier;
 import charactermanaj.model.PartsSet;\r
 import charactermanaj.model.PartsSpecResolver;\r
 import charactermanaj.model.io.PartsImageCollectionParser;\r
+import charactermanaj.model.io.PartsImageCollectionParser.LayerOrderMapper;\r
 \r
 /**\r
  * 非同期に複合画像を生成するイメージビルダに引き渡すジョブを生成するためのアダプタクラス.<br>\r
@@ -21,20 +22,21 @@ import charactermanaj.model.io.PartsImageCollectionParser;
 public abstract class ImageBuildJobAbstractAdaptor implements AsyncImageBuilder.AsyncImageBuildJob {\r
 \r
        protected PartsImageCollectionParser partsImageCollectorParser;\r
-       \r
+\r
        public ImageBuildJobAbstractAdaptor(PartsSpecResolver partsSpecResolver) {\r
                if (partsSpecResolver == null) {\r
                        throw new IllegalArgumentException();\r
                }\r
                this.partsImageCollectorParser = new PartsImageCollectionParser(partsSpecResolver);\r
        }\r
-       \r
+\r
        public void loadParts(final ImageSourceCollector collector) throws IOException {\r
                if (collector == null) {\r
                        throw new IllegalArgumentException("collector is null");\r
                }\r
 \r
                PartsSet partsSet = getPartsSet();\r
+               LayerOrderMapper layerOrderMapper = getLayerOrderMapper();\r
                if (partsSet == null) {\r
                        throw new RuntimeException("PartsSet is null");\r
                }\r
@@ -42,31 +44,34 @@ public abstract class ImageBuildJobAbstractAdaptor implements AsyncImageBuilder.
                collector.setSize(partsImageCollectorParser.getPartsSpecResolver().getImageSize());\r
                collector.setImageBgColor(partsSet.getBgColor());\r
                collector.setAffineTramsform(partsSet.getAffineTransformParameter());\r
-               partsImageCollectorParser.parse(partsSet, new PartsImageCollectionParser.PartsImageCollectionHandler() {\r
+               partsImageCollectorParser.parse(partsSet, layerOrderMapper,\r
+                               new PartsImageCollectionParser.PartsImageCollectionHandler() {\r
                        public void detectImageSource(PartsIdentifier partsIdentifier,\r
-                                       Layer layer, ImageResource imageResource,\r
+                                       Layer layer, int layerOrder, ImageResource imageResource,\r
                                        ColorConvertParameter param) {\r
                                if (param == null) {\r
                                        param = new ColorConvertParameter();\r
                                }\r
-                               collector.setImageSource(layer, imageResource, param);\r
+                               collector.setImageSource(layer, layerOrder, imageResource, param);\r
                        }\r
                });\r
                collector.setComplite();\r
        }\r
-       \r
+\r
        protected abstract PartsSet getPartsSet() throws IOException;\r
-       \r
+\r
+       protected abstract LayerOrderMapper getLayerOrderMapper();\r
+\r
        public abstract void buildImage(ImageOutput output);\r
 \r
        public abstract void handleException(Throwable ex);\r
-       \r
+\r
        public void onAbandoned() {\r
                // なにもしない\r
        }\r
-       \r
+\r
        public void onQueueing(long ticket) {\r
                // なにもしない\r
        }\r
-       \r
+\r
 }\r
index 2f50090..303a82d 100644 (file)
@@ -26,7 +26,7 @@ import charactermanaj.model.Layer;
 \r
 /**\r
  * 各パーツの各レイヤーごとの画像を色変換したのちレイヤーの順序に従い重ね合わせ合成する。\r
- * \r
+ *\r
  * @author seraphy\r
  */\r
 public class ImageBuilder {\r
@@ -39,32 +39,32 @@ public class ImageBuilder {
        /**\r
         * 各パーツ情報を設定するためのインターフェイス.<br>\r
         * パーツ登録が完了したら、{@link #setComplite()}を呼び出す必要がある.<br>\r
-        * \r
+        *\r
         * @author seraphy\r
-        * \r
+        *\r
         */\r
        public interface ImageSourceCollector {\r
-               \r
+\r
                /**\r
                 * 画像サイズを設定する.<br>\r
-                * \r
+                *\r
                 * @param size\r
                 *            サイズ\r
                 */\r
                void setSize(Dimension size);\r
-               \r
+\r
                /**\r
                 * 画像の背景色を設定する.<br>\r
                 * 画像生成処理そのものは背景色を使用しないが、画像の生成完了と同じタイミングで背景色を変えるために ホルダーとして用いることを想定している.<br>\r
-                * \r
+                *\r
                 * @param color\r
                 */\r
                void setImageBgColor(Color color);\r
-               \r
+\r
                /**\r
                 * アフィン変換処理のためのパラメータを指定する.<br>\r
                 * 配列サイズは4または6でなければならない.<br>\r
-                * \r
+                *\r
                 * @param param\r
                 *            パラメータ、変換しない場合はnull\r
                 */\r
@@ -74,15 +74,17 @@ public class ImageBuilder {
                 * 各パーツを登録する.<br>\r
                 * 複数パーツある場合は、これを繰り返し呼び出す.<br>\r
                 * すべて呼び出したらsetCompliteを呼び出す.<br>\r
-                * \r
+                *\r
                 * @param layer\r
                 *            レイヤー\r
+                * @param layerOrder\r
+                *            レイヤーの補正済み順序\r
                 * @param imageResource\r
                 *            イメージソース\r
                 * @param param\r
                 *            色変換情報\r
                 */\r
-               void setImageSource(Layer layer, ImageResource imageResource, ColorConvertParameter param);\r
+               void setImageSource(Layer layer, int layerOrder, ImageResource imageResource, ColorConvertParameter param);\r
 \r
                /**\r
                 * パーツの登録が完了したことを通知する。\r
@@ -92,46 +94,46 @@ public class ImageBuilder {
 \r
        /**\r
         * 合成が完了した画像を通知するインターフェイス\r
-        * \r
+        *\r
         * @author seraphy\r
         */\r
        public interface ImageOutput {\r
-               \r
+\r
                /**\r
                 * 画像の背景色を取得する.\r
-                * \r
+                *\r
                 * @return 背景色\r
                 */\r
                Color getImageBgColor();\r
-               \r
+\r
                /**\r
                 *  画像を取得する.\r
-                * \r
+                *\r
                 * @return 画像\r
                 */\r
                BufferedImage getImageOutput();\r
-               \r
+\r
        }\r
 \r
        /**\r
         * イメージを構築するためのジョブ定義.<br>\r
         * イメージを構築するためのパーツを登録するハンドラと、合成されたイメージを取り出すハンドラ、および エラーハンドラからなる.<br>\r
-        * \r
+        *\r
         * @author seraphy\r
         */\r
        public interface ImageBuildJob {\r
 \r
                /**\r
                 * 合成する、各パーツを登録するためのハンドラ.<br>\r
-                * \r
+                *\r
                 * @param collector\r
                 *            登録するためのインターフェイス\r
                 */\r
                void loadParts(ImageSourceCollector collector) throws IOException;\r
-               \r
+\r
                /**\r
                 * 合成されたイメージを取得するハンドラ\r
-                * \r
+                *\r
                 * @param output\r
                 *            イメージを取得するためのインターフェイス\r
                 */\r
@@ -139,70 +141,70 @@ public class ImageBuilder {
 \r
                /**\r
                 * 例外ハンドラ\r
-                * \r
+                *\r
                 * @param ex\r
                 *            例外\r
                 */\r
                void handleException(Throwable ex);\r
        }\r
-       \r
+\r
        /**\r
         * イメージ構築に使用したパーツ情報\r
-        * \r
+        *\r
         * @author seraphy\r
         */\r
        private static final class BuildedPartsInfo {\r
-               \r
+\r
                private final ImageBuildPartsInfo partsInfo;\r
-               \r
+\r
                private final long lastModified;\r
-               \r
+\r
                public BuildedPartsInfo(ImageBuildPartsInfo partsInfo, LoadedImage loadedImage) {\r
                        this.partsInfo = partsInfo;\r
                        this.lastModified = loadedImage.getLastModified();\r
                }\r
-               \r
+\r
                public ImageBuildPartsInfo getPartsInfo() {\r
                        return partsInfo;\r
                }\r
-               \r
+\r
                public long getLastModified() {\r
                        return lastModified;\r
                }\r
        }\r
-       \r
+\r
        /**\r
         * イメージ構築用情報.<br>\r
         * イメージ構築結果も格納される.<br>\r
-        * \r
+        *\r
         * @author seraphy\r
         */\r
        private static final class ImageBuildInfo {\r
-               \r
+\r
                private ArrayList<ImageBuildPartsInfo> partsInfos = new ArrayList<ImageBuildPartsInfo>();\r
-               \r
-               private ArrayList<BuildedPartsInfo> buildPartsInfos = new ArrayList<BuildedPartsInfo>(); \r
-               \r
+\r
+               private ArrayList<BuildedPartsInfo> buildPartsInfos = new ArrayList<BuildedPartsInfo>();\r
+\r
                private BufferedImage canvas;\r
 \r
                private Rectangle rct = new Rectangle(0, 0, 0, 0);\r
-               \r
+\r
                private Color imageBgColor;\r
-               \r
+\r
                private double[] affineParamHolder;\r
-               \r
+\r
                private boolean sorted;\r
-               \r
+\r
                @Override\r
                public int hashCode() {\r
                        return partsInfos.hashCode();\r
                }\r
-               \r
+\r
                @Override\r
                public boolean equals(Object obj) {\r
                        if (obj != null && obj instanceof ImageBuildInfo) {\r
                                ImageBuildInfo other = (ImageBuildInfo) obj;\r
-                               \r
+\r
                                if (!getPartsInfos().equals(other.getPartsInfos())) {\r
                                        // パーツ情報を重ね順をあわせて比較している.\r
                                        return false;\r
@@ -222,12 +224,12 @@ public class ImageBuilder {
                        }\r
                        return false;\r
                }\r
-               \r
+\r
                /**\r
                 * このイメージ構築情報と、すでに構築したイメージ情報を比較し、 同一であるか判定する.<br>\r
                 * 引数に指定したイメージ構築情報が、まだ構築されていない場合は常にfalseとなる.<br>\r
                 * イメージリソースが更新されていれば同一構成であってもfalseとなる.<br>\r
-                * \r
+                *\r
                 * @param usedInfo\r
                 *            すでに構築済みのイメージ情報(結果が入っているもの)\r
                 * @return 同一であればtrue、そうでなければfalse\r
@@ -260,13 +262,13 @@ public class ImageBuilder {
                                        return false;\r
                                }\r
                        }\r
-                       \r
+\r
                        return true;\r
                }\r
-               \r
+\r
                /**\r
                 * イメージ構築に使用したパーツ情報を記録する.\r
-                * \r
+                *\r
                 * @param partsInfo\r
                 *            パーツ情報\r
                 * @param loadedImage\r
@@ -275,56 +277,56 @@ public class ImageBuilder {
                public void addUsedPartsInfo(ImageBuildPartsInfo partsInfo, LoadedImage loadedImage) {\r
                        buildPartsInfos.add(new BuildedPartsInfo(partsInfo, loadedImage));\r
                }\r
-               \r
+\r
                /**\r
                 * イメージ構築結果を取得する.\r
-                * \r
+                *\r
                 * @return イメージ構築結果\r
                 */\r
                public BufferedImage getCanvas() {\r
                        return canvas;\r
                }\r
-               \r
+\r
                /**\r
                 * イメージ構築結果を格納する.\r
-                * \r
+                *\r
                 * @param canvas\r
                 *            イメージ構築結果\r
                 */\r
                public void setCanvas(BufferedImage canvas) {\r
                        this.canvas = canvas;\r
                }\r
-               \r
+\r
                public double[] getAffineParamHolder() {\r
                        return affineParamHolder;\r
                }\r
-               \r
+\r
                public void setAffineParamHolder(double[] affineParamHolder) {\r
                        this.affineParamHolder = affineParamHolder;\r
                }\r
-               \r
+\r
                public Color getImageBgColor() {\r
                        return imageBgColor;\r
                }\r
-               \r
+\r
                public void setImageBgColor(Color imageBgColor) {\r
                        this.imageBgColor = imageBgColor;\r
                }\r
-               \r
+\r
                public Rectangle getRct() {\r
                        return rct;\r
                }\r
-               \r
+\r
                public void setRect(int w, int h) {\r
                        rct.width = w;\r
                        rct.height = h;\r
                }\r
-               \r
+\r
                /**\r
                 * パーツのリストを取得する.<Br>\r
                 * 取得された時点で、パーツ情報は重ね合わせ順にソートされている.<br>\r
                 * リストは変更不可です.<br>\r
-                * \r
+                *\r
                 * @return パーツ情報のリスト\r
                 */\r
                public List<ImageBuildPartsInfo> getPartsInfos() {\r
@@ -334,31 +336,31 @@ public class ImageBuilder {
                        }\r
                        return Collections.unmodifiableList(partsInfos);\r
                }\r
-               \r
+\r
                public void add(ImageBuildPartsInfo imageBuildPartsInfo) {\r
                        sorted = false;\r
                        partsInfos.add(imageBuildPartsInfo);\r
                }\r
-               \r
+\r
                public int getPartsCount() {\r
                        return partsInfos.size();\r
                }\r
        }\r
-       \r
+\r
 \r
        /**\r
         * イメージのローダー\r
         */\r
        private ColorConvertedImageCachedLoader imageLoader;\r
-       \r
+\r
        /**\r
         * 最後に使用したイメージビルド情報.(初回ならばnull)\r
         */\r
        private ImageBuildInfo lastUsedImageBuildInfo;\r
-       \r
+\r
        /**\r
         * イメージのローダーを指定して構築します.<br>\r
-        * \r
+        *\r
         * @param imageLoader\r
         *            イメージローダー\r
         */\r
@@ -369,10 +371,10 @@ public class ImageBuilder {
                this.imageLoader = imageLoader;\r
        }\r
 \r
-       \r
+\r
        /**\r
         * イメージビルドジョブより、構築すべきイメージの情報を取得する.\r
-        * \r
+        *\r
         * @param imageBuildJob\r
         *            イメージビルドジョブ\r
         * @return 取得されたイメージ構築情報\r
@@ -381,7 +383,8 @@ public class ImageBuilder {
         * @throws InterruptedException\r
         *             割り込み\r
         */\r
-       protected ImageBuildInfo getPartsInfo(ImageBuildJob imageBuildJob) throws IOException, InterruptedException {\r
+       protected ImageBuildInfo getPartsInfo(ImageBuildJob imageBuildJob)\r
+                       throws IOException, InterruptedException {\r
                final ImageBuildInfo imageBuildInfo = new ImageBuildInfo();\r
                final Semaphore compliteLock = new Semaphore(0);\r
                // ジョブリクエスト側に合成するイメージの情報を引き渡すように要求する.\r
@@ -407,10 +410,11 @@ public class ImageBuilder {
                                }\r
                        }\r
                        // ジョブリクエスト側よりパーツの登録として呼び出される\r
-                       public void setImageSource(Layer layer, ImageResource imageResource, ColorConvertParameter param) {\r
+                       public void setImageSource(Layer layer, int layerOrder, ImageResource imageResource,\r
+                                       ColorConvertParameter param) {\r
                                synchronized (imageBuildInfo) {\r
                                        imageBuildInfo.add(new ImageBuildPartsInfo(\r
-                                                       imageBuildInfo.getPartsCount(), layer, imageResource, param));\r
+                                                       imageBuildInfo.getPartsCount(), layer, layerOrder, imageResource, param));\r
                                }\r
                        }\r
                        // ジョブリクエスト側よりイメージサイズとパーツの登録が完了したことを通知される.\r
@@ -426,10 +430,10 @@ public class ImageBuilder {
                }\r
                return imageBuildInfo;\r
        }\r
-       \r
+\r
        /**\r
         * イメージビルド情報をもとにイメージを構築して返す.\r
-        * \r
+        *\r
         * @param imageBuildInfo\r
         *            イメージビルド情報と、その結果\r
         * @throws IOException\r
@@ -478,11 +482,11 @@ public class ImageBuilder {
                                BufferedImage img = loadedImage.getImage();\r
                                g.drawImage(img, 0, 0, w, h, 0, 0, w, h, null);\r
                        }\r
-               \r
+\r
                } finally {\r
                        g.dispose();\r
                }\r
-       \r
+\r
                // アフィン処理を行う.(パラメータが指定されていれば)\r
                final BufferedImage affineTransformedCanvas;\r
                double[] affineTransformParameter = imageBuildInfo.getAffineParamHolder();\r
@@ -499,11 +503,11 @@ public class ImageBuilder {
                // 最終的にできあがったキャンバスを結果として格納する.\r
                imageBuildInfo.setCanvas(affineTransformedCanvas);\r
        }\r
-       \r
+\r
        /**\r
         * イメージ構築ジョブを要求します.<br>\r
         * 戻り値がtrueである場合は、ただちに完了したことを示します.<br>\r
-        * \r
+        *\r
         * @param imageBuildJob\r
         *            イメージを構築するジョブ\r
         * @return 画像がただちに得られた場合はtrue、そうでなければfalse\r
@@ -512,7 +516,7 @@ public class ImageBuilder {
                if (imageBuildJob == null) {\r
                        throw new IllegalArgumentException();\r
                }\r
-               \r
+\r
                // 合成する画像パーツの取得処理\r
                final ImageBuildInfo imageBuildInfo;\r
                try {\r
@@ -523,12 +527,12 @@ public class ImageBuilder {
                        imageBuildJob.handleException(ex);\r
                        return false;\r
                }\r
-               \r
+\r
                try {\r
                        synchronized (imageBuildInfo) {\r
 \r
                                final BufferedImage canvas;\r
-                               \r
+\r
                                // 前回構築したパーツと同じであれば再構築せず、以前のものを使う\r
                                if (imageBuildInfo.isAlreadyLoaded(lastUsedImageBuildInfo)) {\r
                                        canvas = lastUsedImageBuildInfo.getCanvas();\r
@@ -539,7 +543,7 @@ public class ImageBuilder {
                                        canvas = imageBuildInfo.getCanvas();\r
                                        lastUsedImageBuildInfo = imageBuildInfo;\r
                                }\r
-                               \r
+\r
                                // 完成したカンバスを合成結果として通知する.\r
                                imageBuildJob.buildImage(new ImageOutput() {\r
                                        public BufferedImage getImageOutput() {\r
@@ -565,31 +569,35 @@ public class ImageBuilder {
 /**\r
  * 合成する個々のイメージ情報 .<br>\r
  * レイヤー順に順序づけられており、同一レイヤーであればOrder順に順序づけられます.<br>\r
- * \r
+ *\r
  * @author seraphy\r
  */\r
 final class ImageBuildPartsInfo implements Comparable<ImageBuildPartsInfo> {\r
 \r
        private int order;\r
-       \r
+\r
+       private int layerOrder;\r
+\r
        private Layer layer;\r
-       \r
+\r
        private ImageResource imageResource;\r
-       \r
+\r
        private ColorConvertParameter colorParam;\r
 \r
-       public ImageBuildPartsInfo(int order, Layer layer, ImageResource imageResource, ColorConvertParameter colorParam) {\r
+       public ImageBuildPartsInfo(int order, Layer layer, int layerOrder, ImageResource imageResource,\r
+                       ColorConvertParameter colorParam) {\r
                this.order = order;\r
                this.layer = layer;\r
+               this.layerOrder = layerOrder;\r
                this.imageResource = imageResource;\r
                this.colorParam = colorParam;\r
        }\r
-       \r
+\r
        @Override\r
        public int hashCode() {\r
                return order ^ layer.hashCode() ^ imageResource.hashCode();\r
        }\r
-       \r
+\r
        @Override\r
        public boolean equals(Object obj) {\r
                if (obj == this) {\r
@@ -602,10 +610,14 @@ final class ImageBuildPartsInfo implements Comparable<ImageBuildPartsInfo> {
                }\r
                return false;\r
        }\r
-       \r
+\r
        public int compareTo(ImageBuildPartsInfo o) {\r
-               // レイヤー順\r
-               int ret = layer.compareTo(o.layer);\r
+               // 補正済みレイヤー順\r
+               int ret = layerOrder - o.layerOrder;\r
+               if (ret == 0) {\r
+                       // レイヤー順(フォールバック用。layerOrderが設定されていれば必要ない)\r
+                       ret = layer.compareTo(o.layer);\r
+               }\r
                if (ret == 0) {\r
                        // 同一レイヤーであれば定義順\r
                        ret = order - o.order;\r
@@ -620,15 +632,19 @@ final class ImageBuildPartsInfo implements Comparable<ImageBuildPartsInfo> {
        public int getOrder() {\r
                return order;\r
        }\r
-       \r
+\r
        public Layer getLayer() {\r
                return layer;\r
        }\r
-       \r
+\r
+       public int getLayerOrder() {\r
+               return layerOrder;\r
+       }\r
+\r
        public ColorConvertParameter getColorParam() {\r
                return colorParam;\r
        }\r
-       \r
+\r
        public ImageResource getFile() {\r
                return imageResource;\r
        }\r
diff --git a/src/main/java/charactermanaj/model/CustomLayerOrder.java b/src/main/java/charactermanaj/model/CustomLayerOrder.java
new file mode 100644 (file)
index 0000000..6f30f2a
--- /dev/null
@@ -0,0 +1,129 @@
+package charactermanaj.model;\r
+\r
+import java.beans.PropertyChangeListener;\r
+import java.beans.PropertyChangeSupport;\r
+\r
+public class CustomLayerOrder {\r
+\r
+       public static final String CATEGORY = "category";\r
+\r
+       public static final String LAYER = "layer";\r
+\r
+       public static final String LAYER_ORDER = "layerOrder";\r
+\r
+       private final PropertyChangeSupport propChangeSupport = new PropertyChangeSupport(this);\r
+\r
+       private PartsCategory category;\r
+\r
+       private Layer layer;\r
+\r
+       private int layerOrder;\r
+\r
+       public static CustomLayerOrder valueOf(PartsCategory category, Layer layer, int layerOrder) {\r
+               CustomLayerOrder inst = new CustomLayerOrder();\r
+               inst.setCategory(category);\r
+               inst.setLayer(layer);\r
+               inst.setLayerOrder(layerOrder);\r
+               return inst;\r
+       }\r
+\r
+       public CustomLayerOrder copy() {\r
+               CustomLayerOrder inst = new CustomLayerOrder();\r
+               inst.setCategory(category);\r
+               inst.setLayer(layer);\r
+               inst.setLayerOrder(layerOrder);\r
+               return inst;\r
+       }\r
+\r
+       public PartsCategory getCategory() {\r
+               return category;\r
+       }\r
+\r
+       public void setCategory(PartsCategory category) {\r
+               PartsCategory old = this.category;\r
+               if (old == null ? category != null : !old.equals(category)) {\r
+                       this.category = category;\r
+                       propChangeSupport.firePropertyChange(CATEGORY, old, category);\r
+               }\r
+       }\r
+\r
+       public Layer getLayer() {\r
+               return layer;\r
+       }\r
+\r
+       public void setLayer(Layer layer) {\r
+               Layer old = this.layer;\r
+               this.layer = layer;\r
+               if (old == null ? layer != null : !old.equals(layer)) {\r
+                       this.layer = layer;\r
+                       propChangeSupport.firePropertyChange(LAYER, old, layer);\r
+               }\r
+       }\r
+\r
+       public int getLayerOrder() {\r
+               return layerOrder;\r
+       }\r
+\r
+       public void setLayerOrder(int layerOrder) {\r
+               int old = this.layerOrder;\r
+               if (old != layerOrder) {\r
+                       this.layerOrder = layerOrder;\r
+                       propChangeSupport.firePropertyChange(LAYER_ORDER, old, layerOrder);\r
+               }\r
+       }\r
+\r
+       @Override\r
+       public int hashCode() {\r
+               final int prime = 31;\r
+               int result = 1;\r
+               result = prime * result + ((category == null) ? 0 : category.hashCode());\r
+               result = prime * result + ((layer == null) ? 0 : layer.hashCode());\r
+               result = prime * result + layerOrder;\r
+               return result;\r
+       }\r
+\r
+       @Override\r
+       public boolean equals(Object obj) {\r
+               if (this == obj)\r
+                       return true;\r
+               if (obj == null)\r
+                       return false;\r
+               if (getClass() != obj.getClass())\r
+                       return false;\r
+               CustomLayerOrder other = (CustomLayerOrder) obj;\r
+               if (category == null) {\r
+                       if (other.category != null)\r
+                               return false;\r
+               } else if (!category.equals(other.category))\r
+                       return false;\r
+               if (layer == null) {\r
+                       if (other.layer != null)\r
+                               return false;\r
+               } else if (!layer.equals(other.layer))\r
+                       return false;\r
+               if (layerOrder != other.layerOrder)\r
+                       return false;\r
+               return true;\r
+       }\r
+\r
+       public void addPropertyChangeListener(PropertyChangeListener listener) {\r
+               propChangeSupport.addPropertyChangeListener(listener);\r
+       }\r
+\r
+       public void removePropertyChangeListener(PropertyChangeListener listener) {\r
+               propChangeSupport.removePropertyChangeListener(listener);\r
+       }\r
+\r
+       public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {\r
+               propChangeSupport.addPropertyChangeListener(propertyName, listener);\r
+       }\r
+\r
+       public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {\r
+               propChangeSupport.removePropertyChangeListener(propertyName, listener);\r
+       }\r
+\r
+       @Override\r
+       public String toString() {\r
+               return "CustomLayerOrder [category=" + category + ", layer=" + layer + ", layerOrder=" + layerOrder + "]";\r
+       }\r
+}
\ No newline at end of file
diff --git a/src/main/java/charactermanaj/model/ListChangeListener.java b/src/main/java/charactermanaj/model/ListChangeListener.java
new file mode 100644 (file)
index 0000000..882a86b
--- /dev/null
@@ -0,0 +1,103 @@
+package charactermanaj.model;\r
+\r
+import java.util.EventListener;\r
+import java.util.EventObject;\r
+\r
+/**\r
+ * リストの変更通知イベントのリスナ\r
+ *\r
+ * @param <E>\r
+ */\r
+public interface ListChangeListener<E> extends EventListener {\r
+\r
+       /**\r
+        * イベントのタイプ\r
+        */\r
+       public enum ChangeType {\r
+               ADD, MODIFIY, REMOVE\r
+       }\r
+\r
+       /**\r
+        * リストの変更通知イベント\r
+        *\r
+        * @param <E>\r
+        */\r
+       public class Change<E> extends EventObject {\r
+               private static final long serialVersionUID = 3685161122703901464L;\r
+\r
+               /**\r
+                * イベントのタイプ\r
+                */\r
+               private ChangeType type;\r
+\r
+               /**\r
+                * 行インデックス\r
+                */\r
+               private int index;\r
+\r
+               /**\r
+                * 変更前の値\r
+                * addの場合はnull\r
+                */\r
+               private E oldValue;\r
+\r
+               /**\r
+                * 変更後の値\r
+                * removeの場合はnull\r
+                */\r
+               private E newValue;\r
+\r
+               /**\r
+                * 変更イベントを構築する\r
+                * @param source イベント元\r
+                * @param type イベントの種類\r
+                * @param index リストのインデックス\r
+                * @param oldValue 前の値\r
+                * @param newValue 現在の値\r
+                */\r
+               public Change(ObservableList<E> source, ChangeType type, int index, E oldValue, E newValue) {\r
+                       super(source);\r
+                       this.type = type;\r
+                       this.index = index;\r
+                       this.oldValue = oldValue;\r
+                       this.newValue = newValue;\r
+               }\r
+\r
+               /**\r
+                * Gets the event type\r
+                * @return either ADD, REMOVE, or MODIFY\r
+                */\r
+               public ChangeType getType() {\r
+                       return type;\r
+               }\r
+\r
+               public int getIndex() {\r
+                       return index;\r
+               }\r
+\r
+               public E getOldValue() {\r
+                       return oldValue;\r
+               }\r
+\r
+               public E getNewValue() {\r
+                       return newValue;\r
+               }\r
+\r
+               @SuppressWarnings("unchecked")\r
+               public ObservableList<E> getList() {\r
+                       return (ObservableList<E>) getSource();\r
+               }\r
+\r
+               @Override\r
+               public String toString() {\r
+                       return "Change [type=" + type + ", index=" + index + ", oldValue=" + oldValue + ", newValue=" + newValue\r
+                                       + "]";\r
+               }\r
+       }\r
+\r
+       /**\r
+        * リストに変更があったことを通知される\r
+        * @param c 変更イベント\r
+        */\r
+       void onChanged(Change<? extends E> c);\r
+}
\ No newline at end of file
diff --git a/src/main/java/charactermanaj/model/ObservableList.java b/src/main/java/charactermanaj/model/ObservableList.java
new file mode 100644 (file)
index 0000000..7daf642
--- /dev/null
@@ -0,0 +1,191 @@
+package charactermanaj.model;\r
+\r
+import java.util.AbstractList;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import javax.swing.event.EventListenerList;\r
+\r
+/**\r
+ * 監視可能なリスト\r
+ *\r
+ * @param <E>\r
+ */\r
+public class ObservableList<E> extends AbstractList<E> {\r
+\r
+       /**\r
+        * 要素がリストに追加または削除されるときに呼び出されるフック\r
+        * @param <E>\r
+        */\r
+       public interface Hook<E> {\r
+\r
+               /**\r
+                * リストに追加された場合\r
+                * @param item\r
+                */\r
+               void add(E item);\r
+\r
+               /**\r
+                * リストから除去された場合\r
+                * @param item\r
+                */\r
+               void remove(E item);\r
+       }\r
+\r
+       /**\r
+        * リストの実体\r
+        */\r
+       private final List<E> parent;\r
+\r
+       /**\r
+        * フック\r
+        */\r
+       private Hook<E> hook;\r
+\r
+       /**\r
+        * イベントリスナのリスト\r
+        */\r
+       private final EventListenerList listeners = new EventListenerList();\r
+\r
+       /**\r
+        * 空のArrayListを使って構築する\r
+        */\r
+       public ObservableList() {\r
+               this(new ArrayList<E>());\r
+       }\r
+\r
+       /**\r
+        * 指定したリストを使って構築する\r
+        * @param parent 元となるリスト\r
+        */\r
+       public ObservableList(List<E> parent) {\r
+               if (parent == null) {\r
+                       throw new NullPointerException();\r
+               }\r
+               this.parent = parent;\r
+       }\r
+\r
+       /**\r
+        * 指定したリストを使って構築した監視可能リストを返す\r
+        * @param parent 元となるリスト\r
+        * @return 監視可能リスト\r
+        */\r
+       public static <E> ObservableList<E> wrap(List<E> parent) {\r
+               return new ObservableList<E>(parent);\r
+       }\r
+\r
+       /**\r
+        * 現在のフックを取得する。なればnull\r
+        * @return\r
+        */\r
+       public Hook<E> getHook() {\r
+               return hook;\r
+       }\r
+\r
+       /**\r
+        * フックを設定する。解除する場合はnull\r
+        * @param hook\r
+        */\r
+       public void setHook(Hook<E> hook) {\r
+               this.hook = hook;\r
+       }\r
+\r
+       /**\r
+        * 元となるリストを取得する\r
+        * @return 元となるリスト\r
+        */\r
+       public List<E> unwrap() {\r
+               return parent;\r
+       }\r
+\r
+       /**\r
+        * 変更通知リスナを登録する\r
+        * @param l リスナ\r
+        */\r
+       public void addListChangeListener(ListChangeListener<E> l) {\r
+               listeners.add(ListChangeListener.class, l);\r
+       }\r
+\r
+       /**\r
+        * 変更通知リスナを登録解除する\r
+        * @param l リスナ\r
+        */\r
+       public void removeListChangeListener(ListChangeListener<E> l) {\r
+               listeners.remove(ListChangeListener.class, l);\r
+       }\r
+\r
+       /**\r
+        * 全ての変更通知リスナに対して変更イベントを通知する。\r
+        * @param type 変更タイプ\r
+        * @param index リストのインデックス\r
+        */\r
+       @SuppressWarnings("unchecked")\r
+       public void fireEvent(ListChangeListener.ChangeType type, int index, E oldValue, E newValue) {\r
+               // Guaranteed to return a non-null array\r
+               Object[] ll = listeners.getListenerList();\r
+               // Process the listeners last to first, notifying\r
+               // those that are interested in this event\r
+               // ※ 逆順で通知するのがSwingの作法らしい。\r
+               ListChangeListener.Change<E> event = null;\r
+               for (int i = ll.length - 2; i >= 0; i -= 2) {\r
+                       if (ll[i] == ListChangeListener.class) {\r
+                               // Lazily create the event:\r
+                               if (event == null) {\r
+                                       event = new ListChangeListener.Change<E>(this, type, index, oldValue, newValue);\r
+                               }\r
+                               ((ListChangeListener<E>) ll[i + 1]).onChanged(event);\r
+                       }\r
+               }\r
+       }\r
+\r
+       @Override\r
+       public E get(int index) {\r
+               return parent.get(index);\r
+       }\r
+\r
+       @Override\r
+       public int size() {\r
+               return parent.size();\r
+       }\r
+\r
+       @Override\r
+       public E set(int index, E element) {\r
+               E ret = parent.set(index, element);\r
+               fireEvent(ListChangeListener.ChangeType.MODIFIY, index, ret, element);\r
+               if (hook != null) {\r
+                       if (ret != null && !ret.equals(element)) {\r
+                               hook.remove(ret);\r
+                       }\r
+                       if (element != null && !element.equals(ret)) {\r
+                               hook.add(element);\r
+                       }\r
+               }\r
+               return ret;\r
+       }\r
+\r
+       public void setAll(List<E> items) {\r
+               clear();\r
+               if (items != null) {\r
+                       addAll(items);\r
+               }\r
+       }\r
+\r
+       @Override\r
+       public void add(int index, E element) {\r
+               parent.add(index, element);\r
+               if (element != null && hook != null) {\r
+                       hook.add(element);\r
+               }\r
+               fireEvent(ListChangeListener.ChangeType.ADD, index, null, element);\r
+       }\r
+\r
+       @Override\r
+       public E remove(int index) {\r
+               E ret = parent.remove(index);\r
+               fireEvent(ListChangeListener.ChangeType.REMOVE, index, ret, null);\r
+               if (ret != null && hook != null) {\r
+                       hook.remove(ret);\r
+               }\r
+               return ret;\r
+       }\r
+}
\ No newline at end of file
diff --git a/src/main/java/charactermanaj/model/io/CustomLayerOrderPersist.java b/src/main/java/charactermanaj/model/io/CustomLayerOrderPersist.java
new file mode 100644 (file)
index 0000000..85a1af5
--- /dev/null
@@ -0,0 +1,163 @@
+package charactermanaj.model.io;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.net.URI;\r
+import java.util.EventListener;\r
+import java.util.EventObject;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Queue;\r
+import java.util.concurrent.ConcurrentHashMap;\r
+import java.util.concurrent.ConcurrentLinkedQueue;\r
+\r
+import charactermanaj.model.CharacterData;\r
+import charactermanaj.model.CustomLayerOrder;\r
+import charactermanaj.model.io.CustomLayerOrderPersist.CustomLayerOrderPersistListener.Change;\r
+import charactermanaj.util.FileUserData;\r
+import charactermanaj.util.UserData;\r
+\r
+public abstract class CustomLayerOrderPersist {\r
+\r
+       public interface CustomLayerOrderPersistListener extends EventListener {\r
+\r
+               public static class Change extends EventObject {\r
+                       private static final long serialVersionUID = 9176040093651262447L;\r
+\r
+                       private Map<String, List<CustomLayerOrder>> customLayerOrderMap;\r
+\r
+                       public Change(CharacterData characterData, Map<String, List<CustomLayerOrder>> customLayerOrderMap) {\r
+                               super(characterData);\r
+                               this.customLayerOrderMap = customLayerOrderMap;\r
+                       }\r
+\r
+                       @Override\r
+                       public CharacterData getSource() {\r
+                               return (CharacterData) super.getSource();\r
+                       }\r
+\r
+                       public Map<String, List<CustomLayerOrder>> getCustomLayerOrderMap() {\r
+                               return customLayerOrderMap;\r
+                       }\r
+               }\r
+\r
+               void notifyChangeCustomLayerOrder(Change e);\r
+       }\r
+\r
+       protected final CharacterData characterData;\r
+\r
+       protected CustomLayerOrderPersist(CharacterData characterData) {\r
+               if (characterData == null) {\r
+                       throw new NullPointerException();\r
+               }\r
+               this.characterData = characterData;\r
+       }\r
+\r
+       public CharacterData getCharacterData() {\r
+               return characterData;\r
+       }\r
+\r
+       public static CustomLayerOrderPersist newInstance(CharacterData characterData) {\r
+               return new CustomLayerOrderXMLPersist(characterData);\r
+       }\r
+\r
+       public abstract void save(Map<String, List<CustomLayerOrder>> map) throws IOException;\r
+\r
+       public abstract Map<String, List<CustomLayerOrder>> load() throws IOException;\r
+\r
+       private static final Map<URI, Queue<CustomLayerOrderPersistListener>> listenersMap =\r
+                       new ConcurrentHashMap<URI, Queue<CustomLayerOrderPersistListener>>();\r
+\r
+       public void addCustomLayerOrderPersistListener(CustomLayerOrderPersistListener l) {\r
+               URI uri = characterData.getDocBase();\r
+               if (l != null && uri != null) {\r
+                       synchronized (listenersMap) {\r
+                               Queue<CustomLayerOrderPersistListener> listeners = listenersMap.get(uri);\r
+                               if (listeners == null) {\r
+                                       listeners = new ConcurrentLinkedQueue<CustomLayerOrderPersistListener>();\r
+                                       listenersMap.put(uri, listeners);\r
+                               }\r
+                               listeners.add(l);\r
+                       }\r
+               }\r
+       }\r
+\r
+       public void removeCustomLayerOrderPersistListener(CustomLayerOrderPersistListener l) {\r
+               URI uri = characterData.getDocBase();\r
+               if (l != null && uri != null) {\r
+                       synchronized (listenersMap) {\r
+                               Queue<CustomLayerOrderPersistListener> listeners = listenersMap.get(uri);\r
+                               if (listeners != null) {\r
+                                       listeners.remove(l);\r
+                                       if (listeners.isEmpty()) {\r
+                                               listenersMap.remove(uri);\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       protected void fireEvent(Map<String, List<CustomLayerOrder>> map) {\r
+               URI uri = characterData.getDocBase();\r
+               if (uri != null) {\r
+                       Queue<CustomLayerOrderPersistListener> listeners = listenersMap.get(uri);\r
+                       if (listeners != null) {\r
+                               Change e = new Change(characterData, map);\r
+                               for (CustomLayerOrderPersistListener l : listeners) {\r
+                                       l.notifyChangeCustomLayerOrder(e);\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+class CustomLayerOrderXMLPersist extends CustomLayerOrderPersist {\r
+\r
+       /**\r
+        * レイヤーパターンのXMLファイル名\r
+        */\r
+       public static final String CUSTOM_LAYER_ORDERS_XML_FILE = "customlayerorders.xml";\r
+\r
+       public CustomLayerOrderXMLPersist(CharacterData characterData) {\r
+               super(characterData);\r
+       }\r
+\r
+       private UserData getCustomLayerOrdersUserData() {\r
+               // xml形式の場合、キャラクターディレクトリ上に設定する.\r
+               URI docBase = characterData.getDocBase();\r
+               File characterDir = new File(docBase).getParentFile();\r
+               return new FileUserData(new File(characterDir, CUSTOM_LAYER_ORDERS_XML_FILE));\r
+       }\r
+\r
+       @Override\r
+       public void save(Map<String, List<CustomLayerOrder>> map) throws IOException {\r
+               UserData xmlData = getCustomLayerOrdersUserData();\r
+               OutputStream outstm = xmlData.getOutputStream();\r
+               try {\r
+                       CustomLayerOrderXMLWriter xmlWriter = new CustomLayerOrderXMLWriter();\r
+                       xmlWriter.write(map, outstm);\r
+               } finally {\r
+                       outstm.close();\r
+               }\r
+               // 変更通知\r
+               fireEvent(map);\r
+       }\r
+\r
+       @Override\r
+       public Map<String, List<CustomLayerOrder>> load() throws IOException {\r
+               UserData xmlData = getCustomLayerOrdersUserData();\r
+               if (xmlData.exists()) {\r
+                       InputStream is = xmlData.openStream();\r
+                       try {\r
+                               CustomLayerOrderXMLReader xmlReader = new CustomLayerOrderXMLReader(characterData);\r
+                               return xmlReader.read(is);\r
+\r
+                       } finally {\r
+                               is.close();\r
+                       }\r
+               }\r
+               return null;\r
+       }\r
+}
\ No newline at end of file
diff --git a/src/main/java/charactermanaj/model/io/CustomLayerOrderXMLReader.java b/src/main/java/charactermanaj/model/io/CustomLayerOrderXMLReader.java
new file mode 100644 (file)
index 0000000..9267f26
--- /dev/null
@@ -0,0 +1,100 @@
+package charactermanaj.model.io;\r
+\r
+import static charactermanaj.util.XMLUtilities.*;\r
+\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.Locale;\r
+import java.util.Map;\r
+import java.util.TreeMap;\r
+\r
+import org.w3c.dom.Document;\r
+import org.w3c.dom.Element;\r
+\r
+import charactermanaj.model.CharacterData;\r
+import charactermanaj.model.CustomLayerOrder;\r
+import charactermanaj.model.Layer;\r
+import charactermanaj.model.PartsCategory;\r
+\r
+public class CustomLayerOrderXMLReader {\r
+\r
+       private CharacterData cd;\r
+\r
+       public CustomLayerOrderXMLReader(CharacterData cd) {\r
+               if (cd == null) {\r
+                       throw new NullPointerException("categories is required.");\r
+               }\r
+               this.cd = cd;\r
+       }\r
+\r
+       public Map<String, List<CustomLayerOrder>> read(InputStream is) throws IOException {\r
+               if (is == null) {\r
+                       throw new IllegalArgumentException();\r
+               }\r
+\r
+               Map<String, List<CustomLayerOrder>> map = new TreeMap<String, List<CustomLayerOrder>>();\r
+\r
+               Document doc = loadDocument(is);\r
+               String lang = Locale.getDefault().getLanguage();\r
+\r
+               try {\r
+                       Element docElm = doc.getDocumentElement();\r
+                       if (!"custom-layer-orders".equals(docElm.getNodeName())) {\r
+                               throw new IOException("Invalid Format.");\r
+                       }\r
+                       String ns = docElm.getNamespaceURI();\r
+                       if (ns == null || !ns.startsWith(CustomLayerOrderXMLWriter.NS)) {\r
+                               throw new IOException("unsupported xml format");\r
+                       }\r
+\r
+                       String docVersion = docElm.getAttribute("version").trim();\r
+                       if (!CustomLayerOrderXMLWriter.VERSION_SIG_1_0.equals(docVersion)) {\r
+                               throw new IOException("unsupported version: " + docVersion);\r
+                       }\r
+\r
+                       for (Element elmPattern : getChildElements(docElm, "pattern")) {\r
+                               String patternName = elmPattern.getAttribute("name");\r
+\r
+                               for (Element elmLocalizedName : getChildElements(elmPattern, "localized-name")) {\r
+                                       String localizedLang = elmLocalizedName.getAttribute("lang");\r
+                                       String localizedName = elmLocalizedName.getAttribute("name");\r
+                                       if (lang.equals(localizedLang)) {\r
+                                               // langが一致すれば、その名前を優先する\r
+                                               patternName = localizedName;\r
+                                       }\r
+                               }\r
+\r
+                               List<CustomLayerOrder> orders = new ArrayList<CustomLayerOrder>();\r
+                               for (Element elmMapping : getChildElements(elmPattern, "mapping")) {\r
+                                       String categoryId = elmMapping.getAttribute("category");\r
+                                       String layerId = elmMapping.getAttribute("layer");\r
+                                       int layerOrder = Integer.parseInt(elmMapping.getAttribute("order"));\r
+\r
+                                       PartsCategory category = cd.getPartsCategory(categoryId);\r
+                                       if (category != null) {\r
+                                               Layer layer = category.getLayer(layerId);\r
+                                               if (layer != null) {\r
+                                                       CustomLayerOrder order = new CustomLayerOrder();\r
+                                                       order.setCategory(category);\r
+                                                       order.setLayer(layer);\r
+                                                       order.setLayerOrder(layerOrder);\r
+                                                       orders.add(order);\r
+                                               }\r
+                                       }\r
+                               }\r
+\r
+                               map.put(patternName, orders);\r
+                       }\r
+\r
+               } catch (RuntimeException ex) {\r
+                       IOException ex2 = new IOException("CustomLayerOrderXML invalid format.");\r
+                       ex2.initCause(ex);\r
+                       throw ex2;\r
+               }\r
+\r
+               return map;\r
+       }\r
+\r
+}\r
diff --git a/src/main/java/charactermanaj/model/io/CustomLayerOrderXMLWriter.java b/src/main/java/charactermanaj/model/io/CustomLayerOrderXMLWriter.java
new file mode 100644 (file)
index 0000000..2e5232a
--- /dev/null
@@ -0,0 +1,133 @@
+package charactermanaj.model.io;\r
+\r
+import java.io.IOException;\r
+import java.io.OutputStream;\r
+import java.io.OutputStreamWriter;\r
+import java.nio.charset.Charset;\r
+import java.util.Collections;\r
+import java.util.List;\r
+import java.util.Locale;\r
+import java.util.Map;\r
+\r
+import javax.xml.XMLConstants;\r
+import javax.xml.parsers.DocumentBuilder;\r
+import javax.xml.parsers.DocumentBuilderFactory;\r
+import javax.xml.parsers.ParserConfigurationException;\r
+import javax.xml.transform.OutputKeys;\r
+import javax.xml.transform.Transformer;\r
+import javax.xml.transform.TransformerConfigurationException;\r
+import javax.xml.transform.TransformerException;\r
+import javax.xml.transform.TransformerFactory;\r
+import javax.xml.transform.dom.DOMSource;\r
+import javax.xml.transform.stream.StreamResult;\r
+\r
+import org.w3c.dom.Document;\r
+import org.w3c.dom.Element;\r
+\r
+import charactermanaj.model.CustomLayerOrder;\r
+import charactermanaj.model.Layer;\r
+import charactermanaj.model.PartsCategory;\r
+\r
+public class CustomLayerOrderXMLWriter {\r
+\r
+       /**\r
+        * WorkingSetのバージョン\r
+        */\r
+       public static final String VERSION_SIG_1_0 = "1.0";\r
+\r
+       /**\r
+        * WorkingSetのXMLファイルの名前空間\r
+        */\r
+       public static final String NS = "http://charactermanaj.osdn.jp/schema/customlayerorder.xsd";\r
+\r
+       public void write(Map<String, List<CustomLayerOrder>> map, OutputStream outstm) throws IOException {\r
+               Document doc = createDocument(map);\r
+\r
+               // output xml\r
+               TransformerFactory txFactory = TransformerFactory.newInstance();\r
+               txFactory.setAttribute("indent-number", Integer.valueOf(4));\r
+               Transformer tfmr;\r
+               try {\r
+                       tfmr = txFactory.newTransformer();\r
+               } catch (TransformerConfigurationException ex) {\r
+                       throw new RuntimeException("JAXP Configuration Failed.", ex);\r
+               }\r
+               tfmr.setOutputProperty(OutputKeys.INDENT, "yes");\r
+\r
+               // JDK-4504745 : javax.xml.transform.Transformer encoding does not work properly\r
+               // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4504745\r
+               final String encoding = "UTF-8";\r
+               tfmr.setOutputProperty("encoding", encoding);\r
+               try {\r
+                       tfmr.transform(new DOMSource(doc), new StreamResult(\r
+                                       new OutputStreamWriter(outstm, Charset.forName(encoding))));\r
+\r
+               } catch (TransformerException ex) {\r
+                       IOException ex2 = new IOException("XML Convert failed.");\r
+                       ex2.initCause(ex);\r
+                       throw ex2;\r
+               }\r
+       }\r
+\r
+       public Document createDocument(Map<String, List<CustomLayerOrder>> map) {\r
+               if (map == null) {\r
+                       map = Collections.emptyMap();\r
+               }\r
+\r
+               Document doc;\r
+               try {\r
+                       DocumentBuilderFactory factory = DocumentBuilderFactory\r
+                                       .newInstance();\r
+                       factory.setNamespaceAware(true);\r
+                       DocumentBuilder builder = factory.newDocumentBuilder();\r
+                       doc = builder.newDocument();\r
+               } catch (ParserConfigurationException ex) {\r
+                       throw new RuntimeException("JAXP Configuration failed.", ex);\r
+               }\r
+\r
+               Locale locale = Locale.getDefault();\r
+               String lang = locale.getLanguage();\r
+\r
+               // ルートの登録\r
+               Element root = doc.createElementNS(NS, "custom-layer-orders");\r
+               root.setAttribute("version", VERSION_SIG_1_0);\r
+\r
+               root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:xsi",\r
+                               XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);\r
+               root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:xml",\r
+                               XMLConstants.XML_NS_URI);\r
+               root.setAttribute("xsi:schemaLocation", NS + " customlayerorder.xsd");\r
+\r
+               for (Map.Entry<String, List<CustomLayerOrder>> entry : map.entrySet()) {\r
+                       String name = entry.getKey();\r
+                       List<CustomLayerOrder> orders = entry.getValue();\r
+\r
+                       // パターンの登録\r
+                       Element elmPattern = doc.createElementNS(NS, "pattern");\r
+                       elmPattern.setAttribute("name", name);\r
+                       root.appendChild(elmPattern);\r
+\r
+                       // ローカライズされた名前\r
+                       Element elmLocalizedName = doc.createElementNS(NS, "localized-name");\r
+                       elmLocalizedName.setAttribute("lang", lang);\r
+                       elmLocalizedName.setAttribute("name", name);\r
+                       elmPattern.appendChild(elmLocalizedName);\r
+\r
+                       // レイヤーマッピング\r
+                       for (CustomLayerOrder order : orders) {\r
+                               Element elmMapping = doc.createElementNS(NS, "mapping");\r
+                               elmPattern.appendChild(elmMapping);\r
+                               PartsCategory category = order.getCategory();\r
+                               Layer layer = order.getLayer();\r
+                               int layerOrder = order.getLayerOrder();\r
+\r
+                               elmMapping.setAttribute("category", category.getCategoryId());\r
+                               elmMapping.setAttribute("layer", layer.getId());\r
+                               elmMapping.setAttribute("order", Integer.toString(layerOrder));\r
+                       }\r
+               }\r
+\r
+               doc.appendChild(root);\r
+               return doc;\r
+       }\r
+}\r
index b28c377..0aab9a5 100644 (file)
@@ -26,25 +26,34 @@ public class PartsImageCollectionParser {
         * @author seraphy\r
         */\r
        public interface PartsImageCollectionHandler {\r
-               \r
+\r
                /**\r
                 * 個々のイメージリソースとカラー情報を受け取るハンドラ.<br>\r
-                *  \r
+                *\r
                 * @param partsIdentifier 対象のパーツ識別子\r
                 * @param layer パーツのレイヤー\r
+                * @param layerOrder レイヤーの補正済み重ね順序\r
                 * @param imageResource パーツのレイヤーの画像リソース\r
                 * @param param カラー情報、設定されていない場合はnull\r
                 */\r
                void detectImageSource(PartsIdentifier partsIdentifier,\r
-                               Layer layer, ImageResource imageResource,\r
+                               Layer layer, int layerOrder, ImageResource imageResource,\r
                                ColorConvertParameter param);\r
        }\r
-       \r
+\r
+       /**\r
+        * レイヤーの補正済み順序を取得する\r
+        */\r
+       public interface LayerOrderMapper {\r
+\r
+               int getLayerOrder(Layer layer);\r
+       }\r
+\r
        /**\r
         * パーツ設定を解決するためのインターフェイス\r
         */\r
        protected PartsSpecResolver partsSpecResolver;\r
-       \r
+\r
        /**\r
         * パーツ設定のリゾルバを指定して構築する\r
         * @param partsSpecResolver パーツ設定のリゾルバ\r
@@ -55,7 +64,7 @@ public class PartsImageCollectionParser {
                }\r
                this.partsSpecResolver = partsSpecResolver;\r
        }\r
-       \r
+\r
        public PartsSpecResolver getPartsSpecResolver() {\r
                return this.partsSpecResolver;\r
        }\r
@@ -64,10 +73,12 @@ public class PartsImageCollectionParser {
         * パーツセットを指定して複合画像を生成するために必要なイメージソースおよびカラー設定を解決する.<br>\r
         * protectedなので派生クラスで呼び出すか、publicに昇格させる.<br>\r
         * @param partsSet パーツセット\r
+        * @param layerOrderMapper レイヤーの補正済み順序を取得するためのマッパー、nullの場合は既定\r
+        * @param handler 抽出された複合画像イメージの個々のイメージソースと、カラー情報を受け取るハンドラ\r
         */\r
-       public void parse(PartsSet partsSet, PartsImageCollectionHandler listener) {\r
-               if (listener == null) {\r
-                       throw new IllegalArgumentException("listener is null");\r
+       public void parse(PartsSet partsSet, LayerOrderMapper layerOrderMapper, PartsImageCollectionHandler handler) {\r
+               if (handler == null) {\r
+                       throw new IllegalArgumentException("handler is null");\r
                }\r
                if (partsSet == null) {\r
                        throw new IllegalArgumentException("PartsSet is null");\r
@@ -81,19 +92,23 @@ public class PartsImageCollectionParser {
                                        PartsFiles partsFiles = partsSpec.getPartsFiles();\r
                                        for (Map.Entry<Layer, ImageResource> entry : partsFiles.entrySet()) {\r
                                                Layer layer = entry.getKey();\r
-                                               ImageResource file = entry.getValue();\r
-                                               ColorConvertParameter param = null;\r
-                                               if (partsColorInfo != null) {\r
-                                                       ColorInfo colorInfo = partsColorInfo.get(layer);\r
-                                                       if (colorInfo != null) {\r
-                                                               param = colorInfo.getColorParameter();\r
+                                               int layerOrder = (layerOrderMapper != null) ? layerOrderMapper.getLayerOrder(layer)\r
+                                                               : layer.getOrder();\r
+                                               if (layerOrder >= 0) { // レイヤー順が負の場合は画像を構成しない。\r
+                                                       ImageResource file = entry.getValue();\r
+                                                       ColorConvertParameter param = null;\r
+                                                       if (partsColorInfo != null) {\r
+                                                               ColorInfo colorInfo = partsColorInfo.get(layer);\r
+                                                               if (colorInfo != null) {\r
+                                                                       param = colorInfo.getColorParameter();\r
+                                                               }\r
                                                        }\r
+                                                       handler.detectImageSource(partsIdentifier, layer, layerOrder, file, param);\r
                                                }\r
-                                               listener.detectImageSource(partsIdentifier, layer, file, param);\r
                                        }\r
                                }\r
                        }\r
                }\r
        }\r
-       \r
+\r
 }\r
index 3ad4b53..98c6981 100644 (file)
@@ -1,6 +1,6 @@
 package charactermanaj.ui;\r
 \r
-import static java.lang.Math.max;\r
+import static java.lang.Math.*;\r
 \r
 import java.awt.BorderLayout;\r
 import java.awt.Component;\r
@@ -58,6 +58,7 @@ import charactermanaj.model.PartsIdentifier;
 import charactermanaj.model.PartsSet;\r
 import charactermanaj.model.PartsSpecResolver;\r
 import charactermanaj.model.io.PartsImageCollectionParser;\r
+import charactermanaj.model.io.PartsImageCollectionParser.LayerOrderMapper;\r
 import charactermanaj.model.io.PartsImageCollectionParser.PartsImageCollectionHandler;\r
 import charactermanaj.ui.model.AbstractTableModelWithComboBoxModel;\r
 import charactermanaj.util.DesktopUtilities;\r
@@ -71,30 +72,31 @@ import charactermanaj.util.LocalizedResourcePropertyLoader;
 public class InformationDialog extends JDialog {\r
 \r
        private static final long serialVersionUID = 1L;\r
-       \r
+\r
        private static final Logger logger = Logger.getLogger(InformationDialog.class.getName());\r
-       \r
+\r
        protected static final String STRINGS_RESOURCE = "languages/informationdialog";\r
 \r
        private JTable informationTable;\r
-       \r
+\r
        private InformationTableModel informationTableModel;\r
-       \r
+\r
        private boolean modeOpen;\r
 \r
-       public InformationDialog(JFrame parent, PartsSpecResolver partsSpecResolver, PartsSet partsSet) {\r
+       public InformationDialog(JFrame parent, PartsSpecResolver partsSpecResolver, PartsSet partsSet,\r
+                       LayerOrderMapper layerOrderMapper) {\r
                super(parent, true);\r
-               \r
+\r
                AppConfig appConfig = AppConfig.getInstance();\r
                modeOpen = appConfig.isInformationDialogOpenMethod();\r
-               \r
+\r
                if (partsSpecResolver == null) {\r
                        throw new IllegalArgumentException("partsSpecResolver is null");\r
                }\r
                if (partsSet == null) {\r
                        throw new IllegalArgumentException("partsSet is null");\r
                }\r
-               \r
+\r
                setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);\r
                addWindowListener(new WindowAdapter() {\r
                        @Override\r
@@ -102,21 +104,21 @@ public class InformationDialog extends JDialog {
                                onClose();\r
                        }\r
                });\r
-               \r
+\r
                final Properties strings = LocalizedResourcePropertyLoader\r
                                .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);\r
-               \r
+\r
                setTitle(strings.getProperty("title"));\r
-               \r
+\r
                informationTableModel = new InformationTableModel();\r
-               \r
+\r
                final PNGFileImageHeaderReader pngHeaderReader = PNGFileImageHeaderReader.getInstance();\r
-               \r
+\r
                PartsImageCollectionParser parser = new PartsImageCollectionParser(partsSpecResolver);\r
-               parser.parse(partsSet, new PartsImageCollectionHandler() {\r
-                       public void detectImageSource(PartsIdentifier partsIdentifier, Layer layer,\r
+               parser.parse(partsSet, layerOrderMapper, new PartsImageCollectionHandler() {\r
+                       public void detectImageSource(PartsIdentifier partsIdentifier, Layer layer, int layerOrder,\r
                                        final ImageResource imageResource, ColorConvertParameter param) {\r
-                               \r
+\r
                                AbstractAction act = new AbstractAction(strings.getProperty(modeOpen ? "btn.edit.open" : "btn.edit.edit")) {\r
                                        private static final long serialVersionUID = 1L;\r
                                        public void actionPerformed(ActionEvent e) {\r
@@ -130,7 +132,7 @@ public class InformationDialog extends JDialog {
                                } else {\r
                                        act.setEnabled(false);\r
                                }\r
-                               \r
+\r
                                PNGFileImageHeader pngHeader;\r
                                try {\r
                                        pngHeader = pngHeaderReader.readHeader(uri);\r
@@ -139,12 +141,13 @@ public class InformationDialog extends JDialog {
                                        pngHeader = null;\r
                                }\r
 \r
-                               InformationModel information = new InformationModel(partsIdentifier, layer, imageResource, param, pngHeader, act);\r
+                               InformationModel information = new InformationModel(partsIdentifier, layer, layerOrder, imageResource,\r
+                                               param, pngHeader, act);\r
                                informationTableModel.addRow(information);\r
                        }\r
                });\r
                informationTableModel.sort();\r
-               \r
+\r
                informationTable = new JTable(informationTableModel) {\r
                        private static final long serialVersionUID = 1L;\r
                        // セルの幅を大きいものにあわせる\r
@@ -167,10 +170,10 @@ public class InformationDialog extends JDialog {
                informationTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);\r
                informationTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);\r
                informationTable.setRowHeight(informationTable.getRowHeight() + 4);\r
-               \r
+\r
                informationTable.setDefaultRenderer(JButton.class, new ButtonCellRender());\r
                informationTable.setDefaultEditor(JButton.class, new ButtonCellEditor());\r
-               \r
+\r
                // セルデータの幅にあわせる(事前に)\r
                for (int row = 0; row < informationTable.getRowCount(); row++) {\r
                        for (int col = 0; col < informationTable.getColumnCount(); col++) {\r
@@ -178,7 +181,7 @@ public class InformationDialog extends JDialog {
                                informationTable.prepareRenderer(renderer, row, col);\r
                        }\r
                }\r
-               \r
+\r
                final JPopupMenu popupMenu = new JPopupMenu();\r
                popupMenu.add(new AbstractAction(strings.getProperty("popupmenu.copyPath")) {\r
                        private static final long serialVersionUID = 1L;\r
@@ -186,9 +189,9 @@ public class InformationDialog extends JDialog {
                                onCopyFilePath();\r
                        }\r
                });\r
-               \r
+\r
                informationTable.setComponentPopupMenu(popupMenu);\r
-               \r
+\r
                Container contentPane = getContentPane();\r
                contentPane.setLayout(new BorderLayout());\r
                JScrollPane informationTableSP = new JScrollPane(informationTable);\r
@@ -203,12 +206,12 @@ public class InformationDialog extends JDialog {
                                onClose();\r
                        }\r
                };\r
-               \r
+\r
                JPanel btnPanel = new JPanel();\r
                btnPanel.setBorder(BorderFactory.createEmptyBorder(3, 5, 3, 45));\r
                GridBagLayout btnPanelLayout = new GridBagLayout();\r
                btnPanel.setLayout(btnPanelLayout);\r
-               \r
+\r
                GridBagConstraints gbc = new GridBagConstraints();\r
                gbc.gridx = 0;\r
                gbc.gridy = 0;\r
@@ -222,34 +225,34 @@ public class InformationDialog extends JDialog {
                gbc.weightx = 1.;\r
                gbc.weighty = 0.;\r
                btnPanel.add(Box.createHorizontalGlue(), gbc);\r
-               \r
+\r
                gbc.gridx = 1;\r
                gbc.gridy = 0;\r
                gbc.weightx = 0.;\r
                gbc.weighty = 0.;\r
                JButton btnClose = new JButton(actClose);\r
                btnPanel.add(btnClose, gbc);\r
-               \r
+\r
                contentPane.add(btnPanel, BorderLayout.SOUTH);\r
-               \r
+\r
                Toolkit tk = Toolkit.getDefaultToolkit();\r
                JRootPane rootPane = getRootPane();\r
                rootPane.setDefaultButton(btnClose);\r
-               \r
+\r
                InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);\r
                ActionMap am = rootPane.getActionMap();\r
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "closeInformationDialog");\r
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeInformationDialog");\r
                am.put("closeInformationDialog", actClose);\r
-               \r
+\r
                pack();\r
                setLocationRelativeTo(parent);\r
        }\r
-       \r
+\r
        protected void onClose() {\r
                dispose();\r
        }\r
-       \r
+\r
        protected void onCopyFilePath() {\r
                StringWriter sw = new StringWriter();\r
                PrintWriter pw = new PrintWriter(sw);\r
@@ -271,7 +274,7 @@ public class InformationDialog extends JDialog {
                Clipboard cb = tk.getSystemClipboard();\r
                cb.setContents(textSelection, null);\r
        }\r
-       \r
+\r
        protected void onOpen(ImageResource imageResource) {\r
                try {\r
                        URI uri = imageResource.getURI();\r
@@ -287,138 +290,157 @@ public class InformationDialog extends JDialog {
 }\r
 \r
 class InformationTableModel extends AbstractTableModelWithComboBoxModel<InformationModel> {\r
-       \r
+\r
+       private enum ColumnDef {\r
+               PARTS_NAME("column.partsName", String.class) {\r
+                       @Override\r
+                       public Object getValue(InformationModel information) {\r
+                               return information.getPartsName();\r
+                       }\r
+               },\r
+               CATEGORY("column.categoryName", String.class){\r
+                       @Override\r
+                       public Object getValue(InformationModel information) {\r
+                               return information.getCategoryName();\r
+                       }\r
+               },\r
+               LAYER("column.layerName", String.class){\r
+                       @Override\r
+                       public Object getValue(InformationModel information) {\r
+                               return information.getLayerName();\r
+                       }\r
+               },\r
+               DEFAULT_LAYER_ORDER("column.defaultLayerOrder", Integer.class){\r
+                       @Override\r
+                       public Object getValue(InformationModel information) {\r
+                               return information.getDefaultLayerOrder();\r
+                       }\r
+               },\r
+               ACTUAL_LAYER_ORDER("column.layerOrder", Integer.class){\r
+                       @Override\r
+                       public Object getValue(InformationModel information) {\r
+                               return information.getLayerOrder();\r
+                       }\r
+               },\r
+               IMAGE_SIZE("column.imagesize", String.class){\r
+                       @Override\r
+                       public Object getValue(InformationModel information) {\r
+                               return information.getImageSizeStr();\r
+                       }\r
+               },\r
+               COLOR_TYPE("column.colortype", String.class){\r
+                       @Override\r
+                       public Object getValue(InformationModel information) {\r
+                               return information.getColorTypeStr();\r
+                       }\r
+               },\r
+               EDIT_BUTTON("column.editbtn", JButton.class){\r
+                       @Override\r
+                       public Object getValue(InformationModel information) {\r
+                               return information.getButton();\r
+                       }\r
+               };\r
+\r
+               private final String resource;\r
+\r
+               private final Class<?> cls;\r
+\r
+               ColumnDef(String resource, Class<?> cls) {\r
+                       this.resource = resource;\r
+                       this.cls = cls;\r
+               }\r
+\r
+               public String getResource() {\r
+                       return resource;\r
+               }\r
+\r
+               public Class<?> getType() {\r
+                       return cls;\r
+               }\r
+\r
+               public abstract Object getValue(InformationModel information);\r
+       }\r
+\r
+       private static final ColumnDef[] COLUMNS = ColumnDef.values();\r
+\r
        private static final long serialVersionUID = 1L;\r
 \r
-       private static final String[] columnNames;\r
-       \r
-       private static final int[] columnWidths;\r
-       \r
-       public static final int COLUMN_BUTTON;\r
-       \r
+       private static final String[] columnNames = new String[COLUMNS.length];\r
+\r
+       private static final int[] columnWidths = new int[COLUMNS.length];\r
+\r
        static {\r
                final Properties strings = LocalizedResourcePropertyLoader\r
                                .getCachedInstance().getLocalizedProperties(InformationDialog.STRINGS_RESOURCE);\r
-\r
-               columnNames = new String[] {\r
-                               strings.getProperty("column.partsName"),\r
-                               strings.getProperty("column.categoryName"),\r
-                               strings.getProperty("column.layerName"),\r
-                               strings.getProperty("column.layerOrder"),\r
-                               strings.getProperty("column.imagesize"),\r
-                               strings.getProperty("column.colortype"),\r
-                               strings.getProperty("column.imageName"),\r
-                               strings.getProperty("column.editbtn"),\r
-               };\r
-               COLUMN_BUTTON = 7;\r
-               columnWidths = new int[] {\r
-                               Integer.parseInt(strings.getProperty("column.partsName.width")),\r
-                               Integer.parseInt(strings.getProperty("column.categoryName.width")),\r
-                               Integer.parseInt(strings.getProperty("column.layerName.width")),\r
-                               Integer.parseInt(strings.getProperty("column.layerOrder.width")),\r
-                               Integer.parseInt(strings.getProperty("column.layerOrder.imagesize.width")),\r
-                               Integer.parseInt(strings.getProperty("column.layerOrder.colortype.width")),\r
-                               Integer.parseInt(strings.getProperty("column.imageName.width")),\r
-                               Integer.parseInt(strings.getProperty("column.editbtn.width")),\r
-               };\r
+               for (int idx = 0; idx < COLUMNS.length; idx++) {\r
+                       columnNames[idx] = strings.getProperty(COLUMNS[idx].getResource());\r
+                       columnWidths[idx] = Integer.parseInt(strings.getProperty(COLUMNS[idx].getResource() + ".width"));\r
+               }\r
        }\r
-       \r
+\r
        public void adjustColumnModel(TableColumnModel columnModel) {\r
-               for (int idx = 0; idx < columnWidths.length; idx++) {\r
+               for (int idx = 0; idx < COLUMNS.length; idx++) {\r
                        columnModel.getColumn(idx).setPreferredWidth(columnWidths[idx]);\r
                }\r
        }\r
 \r
        public int getColumnCount() {\r
-               return columnNames.length;\r
+               return COLUMNS.length;\r
        }\r
-       \r
+\r
        @Override\r
        public String getColumnName(int column) {\r
                return columnNames[column];\r
        }\r
-       \r
+\r
        @Override\r
        public Class<?> getColumnClass(int columnIndex) {\r
-               switch (columnIndex) {\r
-               case 0:\r
-                       return String.class;\r
-               case 1:\r
-                       return String.class;\r
-               case 2:\r
-                       return String.class;\r
-               case 3:\r
-                       return Integer.class;\r
-               case 4:\r
-                       return String.class;\r
-               case 5:\r
-                       return String.class;\r
-               case 6:\r
-                       return String.class;\r
-               case 7:\r
-                       return JButton.class;\r
-               }\r
-               return String.class;\r
+               return COLUMNS[columnIndex].getType();\r
        }\r
-       \r
+\r
        public Object getValueAt(int rowIndex, int columnIndex) {\r
                InformationModel information = getRow(rowIndex);\r
-               switch (columnIndex) {\r
-               case 0:\r
-                       return information.getPartsName();\r
-               case 1:\r
-                       return information.getCategoryName();\r
-               case 2:\r
-                       return information.getLayerName();\r
-               case 3:\r
-                       return information.getLayerOrder();\r
-               case 4:\r
-                       return information.getImageSizeStr();\r
-               case 5:\r
-                       return information.getColorTypeStr();\r
-               case 6:\r
-                       return information.getImageResourceName();\r
-               case 7:\r
-                       return information.getButton();\r
-               }\r
-               return "";\r
+               return COLUMNS[columnIndex].getValue(information);\r
        }\r
-       \r
+\r
        public void sort() {\r
                Collections.sort(elements);\r
                fireTableDataChanged();\r
        }\r
-       \r
+\r
        @Override\r
        public boolean isCellEditable(int rowIndex, int columnIndex) {\r
-               InformationModel information = getRow(rowIndex);\r
-               if (columnIndex == COLUMN_BUTTON) {\r
+               if (JButton.class.equals(COLUMNS[columnIndex].getType())) {\r
+                       InformationModel information = getRow(rowIndex);\r
                        return information.getButton().isEnabled();\r
                }\r
                return false;\r
        }\r
-       \r
 }\r
 \r
 class InformationModel implements Comparable<InformationModel> {\r
-       \r
+\r
        private PartsIdentifier partsIdentifier;\r
 \r
        private Layer layer;\r
-       \r
+\r
+       private int layerOrder;\r
+\r
        private ImageResource imageResource;\r
-       \r
+\r
        private JButton btnOpen;\r
-       \r
+\r
        private PNGFileImageHeader pngHeader;\r
-       \r
+\r
        public InformationModel(PartsIdentifier partsIdentifier, Layer layer,\r
+                       int layerOrder,\r
                        ImageResource imageResource,\r
                        ColorConvertParameter colorConvertParameter,\r
                        PNGFileImageHeader pngHeader,\r
                        AbstractAction actOpen) {\r
                this.partsIdentifier = partsIdentifier;\r
                this.layer = layer;\r
+               this.layerOrder = layerOrder;\r
                this.imageResource = imageResource;\r
                this.pngHeader = pngHeader;\r
                this.btnOpen = new JButton(actOpen) {\r
@@ -430,12 +452,12 @@ class InformationModel implements Comparable<InformationModel> {
                        }\r
                };\r
        }\r
-       \r
+\r
        @Override\r
        public int hashCode() {\r
                return partsIdentifier.hashCode();\r
        }\r
-       \r
+\r
        @Override\r
        public boolean equals(Object obj) {\r
                if (obj == this) {\r
@@ -448,7 +470,7 @@ class InformationModel implements Comparable<InformationModel> {
                }\r
                return false;\r
        }\r
-       \r
+\r
        public int compareTo(InformationModel o) {\r
                int ret = partsIdentifier.compareTo(o.partsIdentifier);\r
                if (ret == 0) {\r
@@ -463,41 +485,45 @@ class InformationModel implements Comparable<InformationModel> {
        public String getPartsName() {\r
                return this.partsIdentifier.getLocalizedPartsName();\r
        }\r
-       \r
+\r
        public String getCategoryName() {\r
                return this.partsIdentifier.getPartsCategory().getLocalizedCategoryName();\r
        }\r
-       \r
+\r
        public String getLayerName() {\r
                return this.layer.getLocalizedName();\r
        }\r
-       \r
-       public int getLayerOrder() {\r
+\r
+       public int getDefaultLayerOrder() {\r
                return this.layer.getOrder();\r
        }\r
-       \r
+\r
+       public int getLayerOrder() {\r
+               return this.layerOrder;\r
+       }\r
+\r
        public String getImageResourceName() {\r
                return this.imageResource.getFullName();\r
        }\r
-       \r
+\r
        public JButton getButton() {\r
                return btnOpen;\r
        }\r
-       \r
+\r
        public String getImageSizeStr() {\r
                if (pngHeader == null) {\r
                        return "INVALID";\r
                }\r
                return pngHeader.getWidth() + "x" + pngHeader.getHeight();\r
        }\r
-       \r
+\r
        public String getColorTypeStr() {\r
                if (pngHeader == null) {\r
                        return "INVALID";\r
                }\r
 \r
                StringBuilder buf = new StringBuilder();\r
-               \r
+\r
                int colorType = pngHeader.getColorType();\r
                if ((colorType & 0x01) != 0) {\r
                        buf.append("Indexed ");\r
@@ -515,7 +541,7 @@ class InformationModel implements Comparable<InformationModel> {
                }\r
 \r
                buf.append(pngHeader.getBitDepth() + "bit");\r
-               \r
+\r
                return buf.toString().trim();\r
        }\r
 }\r
@@ -562,6 +588,6 @@ class ButtonCellEditor extends AbstractCellEditor implements TableCellEditor {
        public Object getCellEditorValue() {\r
                return null;\r
        }\r
-       \r
+\r
 }\r
 \r
diff --git a/src/main/java/charactermanaj/ui/LayerOrderCustomizeDialog.java b/src/main/java/charactermanaj/ui/LayerOrderCustomizeDialog.java
new file mode 100644 (file)
index 0000000..09145b3
--- /dev/null
@@ -0,0 +1,1739 @@
+package charactermanaj.ui;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Color;\r
+import java.awt.Component;\r
+import java.awt.Container;\r
+import java.awt.Dimension;\r
+import java.awt.GridBagConstraints;\r
+import java.awt.GridBagLayout;\r
+import java.awt.Insets;\r
+import java.awt.Point;\r
+import java.awt.Toolkit;\r
+import java.awt.Window;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.awt.event.KeyEvent;\r
+import java.awt.event.WindowAdapter;\r
+import java.awt.event.WindowEvent;\r
+import java.beans.PropertyChangeEvent;\r
+import java.beans.PropertyChangeListener;\r
+import java.beans.PropertyChangeSupport;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.EventListener;\r
+import java.util.EventObject;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Properties;\r
+import java.util.concurrent.atomic.AtomicInteger;\r
+\r
+import javax.swing.AbstractAction;\r
+import javax.swing.ActionMap;\r
+import javax.swing.BorderFactory;\r
+import javax.swing.Box;\r
+import javax.swing.InputMap;\r
+import javax.swing.JButton;\r
+import javax.swing.JComboBox;\r
+import javax.swing.JComponent;\r
+import javax.swing.JDialog;\r
+import javax.swing.JLabel;\r
+import javax.swing.JList;\r
+import javax.swing.JOptionPane;\r
+import javax.swing.JPanel;\r
+import javax.swing.JRootPane;\r
+import javax.swing.JScrollPane;\r
+import javax.swing.JTable;\r
+import javax.swing.JTextField;\r
+import javax.swing.KeyStroke;\r
+import javax.swing.ListCellRenderer;\r
+import javax.swing.ListSelectionModel;\r
+import javax.swing.UIManager;\r
+import javax.swing.event.DocumentEvent;\r
+import javax.swing.event.DocumentListener;\r
+import javax.swing.event.EventListenerList;\r
+import javax.swing.event.TableModelEvent;\r
+import javax.swing.event.TableModelListener;\r
+import javax.swing.plaf.basic.BasicComboBoxEditor;\r
+import javax.swing.table.AbstractTableModel;\r
+import javax.swing.table.TableColumnModel;\r
+\r
+import charactermanaj.Main;\r
+import charactermanaj.model.CustomLayerOrder;\r
+import charactermanaj.model.Layer;\r
+import charactermanaj.model.ListChangeListener;\r
+import charactermanaj.model.ObservableList;\r
+import charactermanaj.model.PartsCategory;\r
+import charactermanaj.ui.model.SimpleComboBoxModel;\r
+import charactermanaj.util.LocalizedResourcePropertyLoader;\r
+\r
+/**\r
+ * レイヤーのカスタマイズを行う編集ダイアログ\r
+ */\r
+public class LayerOrderCustomizeDialog extends JDialog {\r
+\r
+       private static final long serialVersionUID = 525988497443897372L;\r
+\r
+       private static final String STRINGS_RESOURCE = "languages/layerordercustomizedialog";\r
+\r
+       /**\r
+        * レイヤーの編集がされたときに通知されるリスナ\r
+        */\r
+       public interface LayerOrderCustomizeListener extends EventListener {\r
+\r
+               public static class Change extends EventObject {\r
+                       private static final long serialVersionUID = -6203020622537017109L;\r
+\r
+                       public Change(LayerOrderCustomizeDialog source) {\r
+                               super(source);\r
+                       }\r
+\r
+                       @Override\r
+                       public LayerOrderCustomizeDialog getSource() {\r
+                               return (LayerOrderCustomizeDialog) super.getSource();\r
+                       }\r
+               }\r
+\r
+               void onChange(Change e);\r
+       }\r
+\r
+       /**\r
+        * カテゴリのリスト\r
+        */\r
+       private List<PartsCategory> categories;\r
+\r
+       /**\r
+        * コンストラクタ\r
+        * @param window 親ウィンドウ\r
+        * @param categories カテゴリのリスト\r
+        */\r
+       public LayerOrderCustomizeDialog(Window window, List<PartsCategory> categories) {\r
+               super(window);\r
+               try {\r
+                       setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);\r
+                       addWindowListener(new WindowAdapter() {\r
+                               @Override\r
+                               public void windowClosing(WindowEvent e) {\r
+                                       onClosing();\r
+                               }\r
+                       });\r
+\r
+                       this.categories = categories;\r
+                       initLayout();\r
+\r
+               } catch (RuntimeException ex) {\r
+                       dispose();\r
+                       throw ex;\r
+               }\r
+       }\r
+\r
+       /**\r
+        * イベントリスナ\r
+        */\r
+       private final EventListenerList listeners = new EventListenerList();\r
+\r
+       /**\r
+        * このダイアログのデータモデル\r
+        */\r
+       private LayerOrderCustomizeDialogModel model;\r
+\r
+       /**\r
+        * 変更通知リスナを登録する\r
+        * @param l リスナ\r
+        */\r
+       public void addLayerOrderCustomizeListener(LayerOrderCustomizeListener l) {\r
+               listeners.add(LayerOrderCustomizeListener.class, l);\r
+       }\r
+\r
+       /**\r
+        * 変更通知リスナを登録解除する\r
+        * @param l リスナ\r
+        */\r
+       public void removeLayerOrderCustomizeListener(LayerOrderCustomizeListener l) {\r
+               listeners.remove(LayerOrderCustomizeListener.class, l);\r
+       }\r
+\r
+       /**\r
+        * 全ての変更通知リスナに対して変更イベントを通知する。\r
+        * @param type 変更タイプ\r
+        * @param name 名前\r
+        */\r
+       protected void fireEvent() {\r
+               // Guaranteed to return a non-null array\r
+               Object[] ll = listeners.getListenerList();\r
+               // Process the listeners last to first, notifying\r
+               // those that are interested in this event\r
+               // ※ 逆順で通知するのがSwingの作法らしい。\r
+               LayerOrderCustomizeListener.Change event = null;\r
+               for (int i = ll.length - 2; i >= 0; i -= 2) {\r
+                       if (ll[i] == LayerOrderCustomizeListener.class) {\r
+                               // Lazily create the event:\r
+                               if (event == null) {\r
+                                       event = new LayerOrderCustomizeListener.Change(this);\r
+                               }\r
+                               ((LayerOrderCustomizeListener) ll[i + 1]).onChange(event);\r
+                       }\r
+               }\r
+       }\r
+\r
+       public LayerOrderCustomizeDialogModel getModel() {\r
+               return model;\r
+       }\r
+\r
+       /**\r
+        * LayerOrderCustomizeDialogModelの更新リスナ\r
+        */\r
+       private final LayerOrderCustomizeDialogModel.ChangeListener modelChangeListener =\r
+                       new LayerOrderCustomizeDialogModel.ChangeListener() {\r
+               @Override\r
+               public void onChange(Change change) {\r
+                       String name = change.getName();\r
+                       if (name == null || name.isEmpty()) {\r
+                               // ※空文字の場合はcurrentプロパティの変更\r
+                               return;\r
+                       }\r
+\r
+                       switch (change.getChangeType()) {\r
+                       case ADD:\r
+                               // レイヤーパターンが追加された場合\r
+                               if (patternsModel.indexOf(name) < 0) {\r
+                                       patternsModel.add(name);\r
+                                       patternsModel.setSelectedItem(name); // 登録された名前で選択しなおす為\r
+                               }\r
+                               break;\r
+\r
+                       case MODIFY:\r
+                               // レイヤーパターンが更新された場合\r
+                               break;\r
+\r
+                       case REMOVE:\r
+                               // レイヤーパターンが削除された場合\r
+                               if (name != null) {\r
+                                       patternsModel.remove(name);\r
+                                       // 削除時は現在の選択もクリアする。(現在選択のものを削除しているので)\r
+                                       patternsModel.setSelectedItem(null);\r
+                               }\r
+                               break;\r
+                       }\r
+               }\r
+       };\r
+\r
+       public void setModel(LayerOrderCustomizeDialogModel model) {\r
+               LayerOrderCustomizeDialogModel old = this.model;\r
+               if (old != null) {\r
+                       old.removeListChangeListener(modelChangeListener);\r
+               }\r
+\r
+               this.model = model;\r
+               loadModel();\r
+\r
+               if (model != null) {\r
+                       model.addListChangeListener(modelChangeListener);\r
+               }\r
+\r
+               if (old == null ? model != null : !old.equals(model)) {\r
+                       firePropertyChange("model", old, model);\r
+               }\r
+       }\r
+\r
+       private void loadModel() {\r
+               patternsModel.clear();\r
+               if (model == null) {\r
+                       return;\r
+               }\r
+\r
+               patternsModel.addAll(model.getPatternNames());\r
+               patternsModel.setSelectedItem(null);\r
+\r
+               dataModel.setList(model.getCurrentList());\r
+               lastPatternName = null;\r
+       }\r
+\r
+       private JComboBox cmbPatternName = new JComboBox();\r
+\r
+       private SimpleComboBoxModel<String> patternsModel = new SimpleComboBoxModel<String>();\r
+\r
+       private final JTable tblLayerOrder = new JTable();\r
+\r
+       private final LayerOrderTableModel dataModel = new LayerOrderTableModel();\r
+\r
+       private String lastPatternName;\r
+\r
+       private AbstractAction actRemove;\r
+\r
+       private AbstractAction actSave;\r
+\r
+       private void initLayout() {\r
+               final Properties strings = LocalizedResourcePropertyLoader\r
+                               .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);\r
+\r
+               setTitle(strings.getProperty("title"));\r
+               Container container = getContentPane();\r
+               container.setLayout(new BorderLayout());\r
+\r
+               JPanel selectorPanel = new JPanel(new BorderLayout(3, 3));\r
+               selectorPanel.add(new JLabel(strings.getProperty("patternName")), BorderLayout.WEST);\r
+\r
+               cmbPatternName.setEditable(true);\r
+               selectorPanel.add(cmbPatternName, BorderLayout.CENTER);\r
+\r
+               cmbPatternName.setModel(patternsModel);\r
+               cmbPatternName.setSelectedItem(null);\r
+\r
+               cmbPatternName.addActionListener(new AbstractAction() {\r
+                       private static final long serialVersionUID = 1L;\r
+\r
+                       @Override\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               String name = (String) cmbPatternName.getSelectedItem();\r
+                               if (patternsModel.contains(name)) {\r
+                                       onSelect(name);\r
+                               }\r
+                       }\r
+               });\r
+\r
+               Box selectorBtns = Box.createHorizontalBox();\r
+\r
+               actSave = new AbstractAction(strings.getProperty("btnSave")) {\r
+                       private static final long serialVersionUID = 1L;\r
+\r
+                       @Override\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               String name = (String) cmbPatternName.getSelectedItem();\r
+                               if (name != null && name.trim().length() > 0) {\r
+                                       onSave(name.trim());\r
+\r
+                                       // 現在の入力でパターン名が増えたので、\r
+                                       // このパターンを削除可能にするためUI更新を呼び出す\r
+                                       onChangeComboText();\r
+                               }\r
+                       }\r
+               };\r
+               actRemove = new AbstractAction(strings.getProperty("btnRemove")) {\r
+                       private static final long serialVersionUID = 1L;\r
+\r
+                       @Override\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               String name = (String) cmbPatternName.getSelectedItem();\r
+                               onRemove(name);\r
+                       }\r
+               };\r
+\r
+               // コンボボックスのテキストが既存のパターン名と合致するかによって\r
+               // ADD/REMOVEボタンを制御するためのリスナ\r
+               final BasicComboBoxEditor cmbEditor = (BasicComboBoxEditor) cmbPatternName.getEditor();\r
+               JTextField cmbEditorField = (JTextField) cmbEditor.getEditorComponent();\r
+               cmbEditorField.getDocument().addDocumentListener(new DocumentListener() {\r
+                       @Override\r
+                       public void removeUpdate(DocumentEvent e) {\r
+                               changed(e);\r
+                       }\r
+\r
+                       @Override\r
+                       public void insertUpdate(DocumentEvent e) {\r
+                               changed(e);\r
+                       }\r
+\r
+                       @Override\r
+                       public void changedUpdate(DocumentEvent e) {\r
+                               changed(e);\r
+                       }\r
+\r
+                       protected void changed(DocumentEvent e) {\r
+                               onChangeComboText();\r
+                       }\r
+               });\r
+\r
+               selectorBtns.add(new JButton(actSave));\r
+               selectorBtns.add(new JButton(actRemove));\r
+\r
+               selectorPanel.add(selectorBtns, BorderLayout.EAST);\r
+               selectorPanel.setBorder(BorderFactory.createTitledBorder(\r
+                               strings.getProperty("pattern.groupTitle")));\r
+\r
+               container.add(selectorPanel, BorderLayout.NORTH);\r
+\r
+               // セルの編集はフォーカスの移動でコミットする\r
+               tblLayerOrder.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);\r
+               tblLayerOrder.setModel(dataModel);\r
+\r
+               dataModel.addTableModelListener(new TableModelListener() {\r
+                       @Override\r
+                       public void tableChanged(TableModelEvent e) {\r
+                               fireEvent();\r
+                       }});\r
+\r
+               JPanel tablePanel = new JPanel(new BorderLayout(3, 3));\r
+               tablePanel.setBorder(BorderFactory.createTitledBorder(\r
+                               strings.getProperty("edittable.groupTitle")));\r
+               JScrollPane scr = new JScrollPane(tblLayerOrder);\r
+               scr.setPreferredSize(new Dimension(450, 250));\r
+               tablePanel.add(scr);\r
+               tblLayerOrder.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);\r
+               tblLayerOrder.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\r
+\r
+               dataModel.adjustColumnModel(tblLayerOrder.getColumnModel());\r
+\r
+               AbstractAction actAddLayer = new AbstractAction(strings.getProperty("btnAdd")) {\r
+                       private static final long serialVersionUID = 1L;\r
+\r
+                       @Override\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               onAddLayer();\r
+                       }\r
+               };\r
+               AbstractAction actDeleteLayer = new AbstractAction(strings.getProperty("btnDelete")) {\r
+                       private static final long serialVersionUID = 1L;\r
+\r
+                       @Override\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               onDeleteLayer();\r
+                       }\r
+               };\r
+\r
+               JButton btnOK = new JButton(actAddLayer);\r
+               JButton btnCancel = new JButton(actDeleteLayer);\r
+\r
+               JPanel layerOpeBtns = new JPanel(new GridBagLayout());\r
+               GridBagConstraints gbc = new GridBagConstraints();\r
+               gbc.gridx = 0;\r
+               gbc.gridy = 0;\r
+               gbc.weightx = 1;\r
+               gbc.fill = GridBagConstraints.BOTH;\r
+               layerOpeBtns.add(btnOK, gbc);\r
+               gbc.gridx = 0;\r
+               gbc.gridy = 1;\r
+               layerOpeBtns.add(btnCancel, gbc);\r
+\r
+               gbc.gridx = 0;\r
+               gbc.gridy = 2;\r
+               gbc.weighty = 1;\r
+               layerOpeBtns.add(Box.createGlue(), gbc);\r
+\r
+               tablePanel.add(layerOpeBtns, BorderLayout.EAST);\r
+\r
+               container.add(tablePanel, BorderLayout.CENTER);\r
+\r
+               AbstractAction actClose = new AbstractAction(strings.getProperty("btnClose")) {\r
+                       private static final long serialVersionUID = 1L;\r
+\r
+                       @Override\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               onClosing();\r
+                       }\r
+               };\r
+               JButton btnClose = new JButton(actClose);\r
+\r
+               Box btns = Box.createHorizontalBox();\r
+               btns.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 42));\r
+               btns.add(Box.createHorizontalGlue());\r
+               btns.add(btnClose);\r
+               container.add(btns, BorderLayout.SOUTH);\r
+\r
+               Toolkit tk = Toolkit.getDefaultToolkit();\r
+               JRootPane rootPane = getRootPane();\r
+               rootPane.setDefaultButton(btnOK);\r
+\r
+               InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);\r
+               ActionMap am = rootPane.getActionMap();\r
+               im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeLayerOrderCustomizeDialog");\r
+               im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeLayerOrderCustomizeDialog");\r
+               am.put("closeLayerOrderCustomizeDialog", actClose);\r
+\r
+               setModel(new LayerOrderCustomizeDialogModel());\r
+\r
+               // コンボボックスの状態からボタンのUI状態を更新する。\r
+               onChangeComboText();\r
+\r
+               pack();\r
+       }\r
+\r
+       /**\r
+        * パターン名コンボボックスのテキストが入力された場合に\r
+        * ADD/REMOVEボタンのUI状態を更新するためのハンドラ\r
+        */\r
+       protected void onChangeComboText() {\r
+               BasicComboBoxEditor cmbEditor = (BasicComboBoxEditor) cmbPatternName.getEditor();\r
+               String inpName = (String) cmbEditor.getItem();\r
+               if (inpName == null || inpName.trim().length() == 0) {\r
+                       // 空の場合\r
+                       actRemove.setEnabled(false);\r
+                       actSave.setEnabled(false);\r
+               } else {\r
+                       actRemove.setEnabled(patternsModel.contains(inpName)); // 既存のパターン名と合致すれば削除可\r
+                       actSave.setEnabled(true); // 文字が入力されていれば登録・更新可\r
+               }\r
+       }\r
+\r
+       /**\r
+        * カラム定義\r
+        */\r
+       private enum ColumnDef {\r
+               CATEGORY("column.category", String.class, 120) {\r
+                       @Override\r
+                       public Object getValue(CustomLayerOrder item) {\r
+                               return item.getCategory().getLocalizedCategoryName();\r
+                       }\r
+               },\r
+               LAYER("column.layer", String.class, 120) {\r
+                       @Override\r
+                       public Object getValue(CustomLayerOrder item) {\r
+                               return item.getLayer().getLocalizedName();\r
+                       }\r
+               },\r
+               DEFAULT_ORDER("column.defaultOrder", Integer.class, 80) {\r
+                       @Override\r
+                       public Object getValue(CustomLayerOrder item) {\r
+                               return item.getLayer().getOrder();\r
+                       }\r
+               },\r
+               CUSTOM_ORDER("column.order", Integer.class, 80, true) {\r
+                       @Override\r
+                       public Object getValue(CustomLayerOrder item) {\r
+                               return item.getLayerOrder();\r
+                       }\r
+               };\r
+\r
+               private final String resourceKey;\r
+\r
+               private final Class<?> dataType;\r
+\r
+               private final int prefWidth;\r
+\r
+               private final boolean editable;\r
+\r
+               ColumnDef(String resourceKey, Class<?> dataType, int prefWidth) {\r
+                       this(resourceKey, dataType, prefWidth, false);\r
+               }\r
+\r
+               ColumnDef(String resourceKey, Class<?> dataType, int prefWidth, boolean editable) {\r
+                       this.resourceKey = resourceKey;\r
+                       this.dataType = dataType;\r
+                       this.prefWidth = prefWidth;\r
+                       this.editable = editable;\r
+               }\r
+\r
+               public String getResourceKey() {\r
+                       return resourceKey;\r
+               }\r
+\r
+               public int getPrefWidth() {\r
+                       return prefWidth;\r
+               }\r
+\r
+               public abstract Object getValue(CustomLayerOrder item);\r
+\r
+               public Class<?> getDataType() {\r
+                       return dataType;\r
+               }\r
+\r
+               public boolean isEditable() {\r
+                       return editable;\r
+               }\r
+       }\r
+\r
+       /**\r
+        * レイヤー編集テーブルのテーブルモデル\r
+        */\r
+       protected static class LayerOrderTableModel extends AbstractTableModel {\r
+\r
+               private static final long serialVersionUID = 1L;\r
+\r
+               private final Properties strings = LocalizedResourcePropertyLoader\r
+                               .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);\r
+\r
+               /**\r
+                * カラム定義リスト\r
+                */\r
+               private static final ColumnDef[] columns = ColumnDef.values();\r
+\r
+               /**\r
+                * テーブルに設定されているレイヤーリスト。\r
+                * このリストでデータの追加更新削除を行い、変更リスナによってUI更新を行わせる。\r
+                */\r
+               private ObservableList<CustomLayerOrder> layerOrderList;\r
+\r
+               /**\r
+                * テーブルに設定されているレイヤーリストが変更された場合に\r
+                * テーブルモデルが更新されたことをUIに通知するための接続用リスナ。\r
+                */\r
+               private final ListChangeListener<CustomLayerOrder> changeListener = new ListChangeListener<CustomLayerOrder>() {\r
+                       @Override\r
+                       public void onChanged(Change<? extends CustomLayerOrder> c) {\r
+                               int idx = c.getIndex();\r
+                               if (idx >= 0) {\r
+                                       switch (c.getType()) {\r
+                                       case ADD:\r
+                                               fireTableRowsInserted(idx, idx);\r
+                                               return;\r
+\r
+                                       case MODIFIY:\r
+                                               fireTableRowsUpdated(idx, idx);\r
+                                               return;\r
+\r
+                                       case REMOVE:\r
+                                               fireTableRowsDeleted(idx, idx);\r
+                                               return;\r
+\r
+                                       default:\r
+                                               break;\r
+                                       }\r
+                               }\r
+                               fireTableDataChanged();\r
+                       }\r
+               };\r
+\r
+               /**\r
+                * コンストラクタ\r
+                */\r
+               public LayerOrderTableModel() {\r
+                       setList(new ObservableList<CustomLayerOrder>());\r
+               }\r
+\r
+               /**\r
+                * カラム幅を設定する。\r
+                * @param columnModel\r
+                */\r
+               public void adjustColumnModel(TableColumnModel columnModel) {\r
+                       for (int idx = 0; idx < columns.length; idx++) {\r
+                               columnModel.getColumn(idx).setPreferredWidth(\r
+                                               columns[idx].getPrefWidth());\r
+                       }\r
+               }\r
+\r
+               /**\r
+                * 現在編集中のテーブル上のレイヤーリストを差し替える。\r
+                * レイヤーリストはcategory, layerの順序となるようにソートされる。\r
+                * @param layerOrderList\r
+                */\r
+               public final void setList(ObservableList<CustomLayerOrder> layerOrderList) {\r
+                       if (this.layerOrderList != null) {\r
+                               this.layerOrderList.removeListChangeListener(changeListener);\r
+                       }\r
+\r
+                       // テーブルには既定の順序で並べる\r
+                       Collections.sort(layerOrderList, new Comparator<CustomLayerOrder>() {\r
+                               @Override\r
+                               public int compare(CustomLayerOrder o1, CustomLayerOrder o2) {\r
+                                       int ret = o1.getCategory().compareTo(o2.getCategory());\r
+                                       if (ret == 0) {\r
+                                               ret = o1.getLayer().compareTo(o2.getLayer());\r
+                                       }\r
+                                       if (ret == 0) {\r
+                                               ret = o1.getLayerOrder() - o2.getLayerOrder();\r
+                                       }\r
+                                       return ret;\r
+                               }\r
+                       });\r
+\r
+                       this.layerOrderList = layerOrderList;\r
+                       layerOrderList.addListChangeListener(changeListener);\r
+\r
+                       fireTableDataChanged();\r
+               }\r
+\r
+               /**\r
+                * 現在編集中のテーブル上のレイヤーリストを取得する。\r
+                * @return\r
+                */\r
+               public final List<CustomLayerOrder> getList() {\r
+                       return layerOrderList;\r
+               }\r
+\r
+               /**\r
+                * レイヤーを追加する。\r
+                * レイヤーは既存のレイヤーのリストに対してcategory, layerの順序となるように挿入される。\r
+                * @param category\r
+                * @param layer\r
+                * @param layerOrder\r
+                */\r
+               public void add(PartsCategory category, Layer layer, int layerOrder) {\r
+                       if (category == null || layer == null) {\r
+                               throw new NullPointerException("category, layerにnullは指定できません。");\r
+                       }\r
+\r
+                       CustomLayerOrder item = null;\r
+                       int idx = 0;\r
+                       for (; idx < layerOrderList.size(); idx++) {\r
+                               CustomLayerOrder selItem = layerOrderList.get(idx);\r
+\r
+                               PartsCategory selCategory = selItem.getCategory();\r
+                               Layer selLayer = selItem.getLayer();\r
+\r
+                               int ret = category.compareTo(selCategory);\r
+                               if (ret == 0) {\r
+                                       ret = layer.compareTo(selLayer);\r
+                               }\r
+\r
+                               if (ret == 0) {\r
+                                       // 一致するアイテムが発見された。\r
+                                       item = selItem;\r
+                                       break;\r
+\r
+                               } else if (ret < 0) {\r
+                                       // 自分より大きなアイテムを発見\r
+                                       break;\r
+                               }\r
+                       }\r
+\r
+                       if (item == null) {\r
+                               item = new CustomLayerOrder();\r
+                               item.setCategory(category);\r
+                               item.setLayer(layer);\r
+                               item.setLayerOrder(layerOrder);\r
+                               layerOrderList.add(idx, item);\r
+\r
+                       } else {\r
+                               item.setLayerOrder(layerOrder);\r
+                       }\r
+               }\r
+\r
+               public void remove(int rowIndex) {\r
+                       layerOrderList.remove(rowIndex);\r
+               }\r
+\r
+               @Override\r
+               public int getRowCount() {\r
+                       return layerOrderList.size();\r
+               }\r
+\r
+               @Override\r
+               public int getColumnCount() {\r
+                       return columns.length;\r
+               }\r
+\r
+               @Override\r
+               public String getColumnName(int column) {\r
+                       return strings.getProperty(columns[column].getResourceKey());\r
+               }\r
+\r
+               @Override\r
+               public Class<?> getColumnClass(int columnIndex) {\r
+                       return columns[columnIndex].getDataType();\r
+               }\r
+\r
+               @Override\r
+               public Object getValueAt(int rowIndex, int columnIndex) {\r
+                       CustomLayerOrder item = layerOrderList.get(rowIndex);\r
+                       return columns[columnIndex].getValue(item);\r
+               }\r
+\r
+               @Override\r
+               public void setValueAt(Object aValue, int rowIndex, int columnIndex) {\r
+                       if (columns[columnIndex].isEditable()) {\r
+                               CustomLayerOrder item = layerOrderList.get(rowIndex);\r
+                               item.setLayerOrder(((Number) aValue).intValue());\r
+                       }\r
+               }\r
+\r
+               @Override\r
+               public boolean isCellEditable(int rowIndex, int columnIndex) {\r
+                       return columns[columnIndex].isEditable();\r
+               }\r
+       }\r
+\r
+       /**\r
+        * ダイアログを閉じる\r
+        */\r
+       protected void onClosing() {\r
+               if (canDiscardChanges()) {\r
+                       dispose();\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 現在編集中のカスタムレイヤーパターンを取得する。\r
+        * @return\r
+        */\r
+       public List<CustomLayerOrder> getEdittingCustomLayerOrderList() {\r
+               return dataModel.getList();\r
+       }\r
+\r
+       /**\r
+        * 現在の編集を破棄してよいか確認する。\r
+        * パターンをロードしている場合は変更があれば保存するか問い合わせる。\r
+        * パターンをロードしていない場合は破棄してよいか問い合わせる。\r
+        * @return 破棄して良い場合(もしくは保存した場合)\r
+        */\r
+       private boolean canDiscardChanges() {\r
+               final Properties strings = LocalizedResourcePropertyLoader\r
+                               .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);\r
+\r
+               if (lastPatternName == null || lastPatternName.isEmpty()) {\r
+                       // パターンをロードしていない場合\r
+                       ObservableList<CustomLayerOrder> oldLayerOrderList = model.getCurrentList();\r
+                       if (!oldLayerOrderList.equals(dataModel.getList())) {\r
+                               int ret = JOptionPane.showConfirmDialog(this,\r
+                                               strings.getProperty("confirm.discardChanges.message"),\r
+                                               strings.getProperty("confirm.discardChanges.title"),\r
+                                               JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);\r
+                               if (ret != JOptionPane.YES_OPTION) {\r
+                                       // コンボボックスの選択を空にして戻す\r
+                                       patternsModel.setSelectedItem(null);\r
+                                       return false; // 破棄不可\r
+                               }\r
+                       }\r
+               } else {\r
+                       // すでにパターンをロードしており、且つ、レイヤーリストを修正している場合\r
+                       // 新たに選択したパターンのロード前に、現在の編集を保存するか確認する。\r
+                       ObservableList<CustomLayerOrder> oldLayerOrderList = model.getCopy(lastPatternName);\r
+                       if (oldLayerOrderList == null) {\r
+                               oldLayerOrderList = model.createObservableList();\r
+                       }\r
+                       if (!oldLayerOrderList.equals(dataModel.getList())) {\r
+                               // パターンを編集中の場合、保存するか問い合わせる\r
+                               int ret = JOptionPane.showConfirmDialog(this,\r
+                                               strings.getProperty("confirm.savechanges.message") + lastPatternName,\r
+                                               strings.getProperty("confirm.savechanges.title"),\r
+                                               JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);\r
+                               if (ret == JOptionPane.YES_OPTION) {\r
+                                       onSave(lastPatternName); // 現在の編集を保存する\r
+                               }\r
+                       }\r
+               }\r
+               return true; // 現在の編集は破棄して良い\r
+       }\r
+\r
+       /**\r
+        * レイヤーパターン名を選択した場合\r
+        * @param name\r
+        */\r
+       protected void onSelect(String name) {\r
+               if (canDiscardChanges()) {\r
+                       // 選択したパターンのロード\r
+                       ObservableList<CustomLayerOrder> layerOrderList = model.getCopy(name);\r
+                       if (layerOrderList == null) {\r
+                               layerOrderList = model.createObservableList();\r
+                       }\r
+\r
+                       dataModel.setList(layerOrderList);\r
+                       String old = this.lastPatternName;\r
+                       this.lastPatternName = name;\r
+\r
+                       firePropertyChange("lastPatternName", old, name);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 最後に選択したパターン名を取得する\r
+        * @return\r
+        */\r
+       public String getLastPatternName() {\r
+               return lastPatternName;\r
+       }\r
+\r
+       /**\r
+        * 現在の編集をパターン名をつけて保存し、現在の選択パターン名とする。\r
+        * @param name\r
+        */\r
+       protected void onSave(String name) {\r
+               model.put(name, dataModel.getList());\r
+\r
+               String old = this.lastPatternName;\r
+               if (old == null ? name != null : !old.equals(name)) {\r
+                       this.lastPatternName = name;\r
+                       firePropertyChange("lastPatternName", old, name);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 指定したパターン名を削除して、選択状態を解除する。\r
+        * @param name\r
+        */\r
+       protected void onRemove(String name) {\r
+               final Properties strings = LocalizedResourcePropertyLoader\r
+                               .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);\r
+\r
+               int ret = JOptionPane.showConfirmDialog(this,\r
+                               strings.getProperty("confirm.removepattern.message") + lastPatternName,\r
+                               strings.getProperty("confirm.removepattern.title"),\r
+                               JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);\r
+               if (ret != JOptionPane.YES_OPTION) {\r
+                       return;\r
+               }\r
+\r
+               model.remove(name);\r
+\r
+               String old = this.lastPatternName;\r
+               if (old != null) {\r
+                       this.lastPatternName = null;\r
+                       firePropertyChange("lastPatternName", old, null);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * レイヤーの追加ダイアログのデータモデル\r
+        */\r
+       public static class AddLayerDialogModel {\r
+\r
+               public static final String PARTS_CATEGORIES = "partsCategories";\r
+\r
+               public static final String RESULT = "result";\r
+\r
+               public static final String SELECTED_PARTS_CATEGORY = "partsCategory";\r
+\r
+               public static final String SELECTED_LAYER = "layer";\r
+\r
+               public static final String DEFAULT_ORDER = "defaultOrder";\r
+\r
+               public static final String ORDER = "order";\r
+\r
+               private final PropertyChangeSupport propChangeSupport = new PropertyChangeSupport(this);\r
+\r
+               /**\r
+                * コンボボックスの候補とするカテゴリのリスト\r
+                */\r
+               private List<PartsCategory> partsCategories = new ArrayList<PartsCategory>();\r
+\r
+               /**\r
+                * ダイアログを閉じたときのOK/CANCEL状態\r
+                */\r
+               private boolean result;\r
+\r
+               /**\r
+                * 選択されているカテゴリ、なければnull\r
+                */\r
+               private PartsCategory selectedPartsCategory;\r
+\r
+               /**\r
+                * 選択されているレイヤー、なければnull\r
+                */\r
+               private Layer selectedLayer;\r
+\r
+               /**\r
+                * デフォルト順序\r
+                */\r
+               private int defaultOrder;\r
+\r
+               /**\r
+                * 順序\r
+                */\r
+               private int order;\r
+\r
+               /**\r
+                * 入力確定可能であるか?\r
+                * @return\r
+                */\r
+               public boolean isValid() {\r
+                       return selectedPartsCategory != null && selectedLayer != null;\r
+               }\r
+\r
+               public List<PartsCategory> getPartsCategories() {\r
+                       return partsCategories;\r
+               }\r
+\r
+               public void setPartsCategories(List<PartsCategory> partsCategories) {\r
+                       List<PartsCategory> old = this.partsCategories;\r
+                       this.partsCategories = partsCategories;\r
+                       propChangeSupport.firePropertyChange(PARTS_CATEGORIES, old, partsCategories);\r
+               }\r
+\r
+               public void setResult(boolean result) {\r
+                       boolean old = this.result;\r
+                       this.result = result;\r
+                       propChangeSupport.firePropertyChange(RESULT, old, result);\r
+               }\r
+\r
+               public boolean isResult() {\r
+                       return result;\r
+               }\r
+\r
+               public void setSelectedPartsCategory(PartsCategory selectedPartsCategory) {\r
+                       PartsCategory old = this.selectedPartsCategory;\r
+                       if (!(old == null && selectedPartsCategory == null) || (old != null && !old.equals(selectedPartsCategory))) {\r
+                               this.selectedPartsCategory = selectedPartsCategory;\r
+                               propChangeSupport.firePropertyChange(SELECTED_PARTS_CATEGORY, old, selectedPartsCategory);\r
+                       }\r
+               }\r
+\r
+               public PartsCategory getSelectedPartsCategory() {\r
+                       return selectedPartsCategory;\r
+               }\r
+\r
+               public void setSelectedLayer(Layer selectedLayer) {\r
+                       Layer old = this.selectedLayer;\r
+                       if (!(old == null && selectedLayer == null) || (old != null && !old.equals(selectedLayer))) {\r
+                               this.selectedLayer = selectedLayer;\r
+                               propChangeSupport.firePropertyChange(SELECTED_LAYER, old, selectedLayer);\r
+                       }\r
+               }\r
+\r
+               public Layer getSelectedLayer() {\r
+                       return selectedLayer;\r
+               }\r
+\r
+               public void setOrder(int order) {\r
+                       int old = this.order;\r
+                       if (old != order) {\r
+                               this.order = order;\r
+                               propChangeSupport.firePropertyChange(ORDER, old, order);\r
+                       }\r
+               }\r
+\r
+               public int getOrder() {\r
+                       return order;\r
+               }\r
+\r
+               public void setDefaultOrder(int defaultOrder) {\r
+                       int old = this.defaultOrder;\r
+                       this.defaultOrder = defaultOrder;\r
+                       if (old != defaultOrder) {\r
+                               this.defaultOrder = defaultOrder;\r
+                               propChangeSupport.firePropertyChange(DEFAULT_ORDER, old, defaultOrder);\r
+                       }\r
+               }\r
+\r
+               public int getDefaultOrder() {\r
+                       return defaultOrder;\r
+               }\r
+\r
+               public void addPropertyChangeListener(PropertyChangeListener listener) {\r
+                       propChangeSupport.addPropertyChangeListener(listener);\r
+               }\r
+\r
+               public void removePropertyChangeListener(PropertyChangeListener listener) {\r
+                       propChangeSupport.removePropertyChangeListener(listener);\r
+               }\r
+\r
+               public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {\r
+                       propChangeSupport.addPropertyChangeListener(propertyName, listener);\r
+               }\r
+\r
+               public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {\r
+                       propChangeSupport.removePropertyChangeListener(propertyName, listener);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * レイヤーの追加ダイアログ\r
+        */\r
+       public static class AddLayerDialog extends JDialog {\r
+               private static final long serialVersionUID = 1L;\r
+\r
+               public static final String MODEL = "model";\r
+\r
+               public AddLayerDialog(JDialog parent, boolean modal) {\r
+                       super(parent, modal);\r
+                       try {\r
+                               initLayout();\r
+\r
+                       } catch (RuntimeException ex) {\r
+                               dispose();\r
+                               throw ex;\r
+                       }\r
+               }\r
+\r
+               private SimpleComboBoxModel<Layer> cmbLayerModel = new SimpleComboBoxModel<Layer>();\r
+\r
+               private SimpleComboBoxModel<PartsCategory> cmbCategoryModel = new SimpleComboBoxModel<PartsCategory>();\r
+\r
+               private JTextField txtDefaultOrder = new JTextField();\r
+\r
+               private JTextField txtOrder = new JTextField();\r
+\r
+               private AbstractAction actOK;\r
+\r
+               private void initLayout() {\r
+                       final Properties strings = LocalizedResourcePropertyLoader\r
+                                       .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);\r
+\r
+                       setTitle(strings.getProperty("addLayerDialog.title"));\r
+\r
+                       Container container = getContentPane();\r
+                       container.setLayout(new BorderLayout());\r
+\r
+                       JPanel editPanel = new JPanel(new GridBagLayout());\r
+                       GridBagConstraints gbc = new GridBagConstraints();\r
+                       gbc.insets = new Insets(3, 3, 3, 3);\r
+\r
+                       JComboBox cmbCategory = new JComboBox(cmbCategoryModel);\r
+\r
+                       JComboBox cmbLayer = new JComboBox(cmbLayerModel);\r
+                       cmbLayer.setRenderer(new ListCellRenderer() {\r
+\r
+                               private JLabel label = new JLabel();\r
+\r
+                               @Override\r
+                               public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,\r
+                                               boolean cellHasFocus) {\r
+                                       Layer selectedLayer = (Layer) value;\r
+\r
+                                       // 背景色透過制御\r
+                                       label.setOpaque(isSelected && index >= 0);\r
+\r
+                                       if (isSelected) {\r
+                                               label.setBackground(list.getSelectionBackground());\r
+                                               label.setForeground(list.getSelectionForeground());\r
+                                       } else {\r
+                                               label.setBackground(list.getBackground());\r
+                                               label.setForeground(list.getForeground());\r
+                                       }\r
+\r
+                                       if (selectedLayer == null) {\r
+                                               label.setText("");\r
+                                       } else {\r
+                                               label.setFont(list.getFont());\r
+                                               label.setText(selectedLayer.getLocalizedName());\r
+                                       }\r
+                                       return label;\r
+                               }\r
+                       });\r
+\r
+                       gbc.gridx = 0;\r
+                       gbc.gridy = 0;\r
+                       editPanel.add(new JLabel(strings.getProperty("column.category")), gbc);\r
+\r
+                       gbc.gridx = 0;\r
+                       gbc.gridy = 1;\r
+                       editPanel.add(new JLabel(strings.getProperty("column.layer")), gbc);\r
+\r
+                       gbc.gridx = 0;\r
+                       gbc.gridy = 2;\r
+                       editPanel.add(new JLabel(strings.getProperty("column.defaultOrder")), gbc);\r
+\r
+                       gbc.gridx = 0;\r
+                       gbc.gridy = 3;\r
+                       editPanel.add(new JLabel(strings.getProperty("column.order")), gbc);\r
+\r
+                       gbc.gridx = 1;\r
+                       gbc.gridy = 0;\r
+                       gbc.weightx = 1;\r
+                       gbc.fill = GridBagConstraints.BOTH;\r
+                       editPanel.add(cmbCategory, gbc);\r
+\r
+                       gbc.gridx = 1;\r
+                       gbc.gridy = 1;\r
+                       editPanel.add(cmbLayer, gbc);\r
+\r
+                       gbc.gridx = 1;\r
+                       gbc.gridy = 2;\r
+                       editPanel.add(txtDefaultOrder, gbc);\r
+                       txtDefaultOrder.setEditable(false);\r
+\r
+                       gbc.gridx = 1;\r
+                       gbc.gridy = 3;\r
+                       editPanel.add(txtOrder, gbc);\r
+\r
+                       gbc.gridx = 0;\r
+                       gbc.gridy = 4;\r
+                       gbc.weighty = 1;\r
+                       editPanel.add(Box.createGlue(), gbc);\r
+\r
+                       container.add(editPanel, BorderLayout.CENTER);\r
+\r
+                       actOK = new AbstractAction(strings.getProperty("btnOK")) {\r
+                               private static final long serialVersionUID = 1L;\r
+\r
+                               @Override\r
+                               public void actionPerformed(ActionEvent e) {\r
+                                       onOK();\r
+                               }\r
+                       };\r
+\r
+                       AbstractAction actCancel = new AbstractAction(strings.getProperty("btnCancel")) {\r
+                               private static final long serialVersionUID = 1L;\r
+\r
+                               @Override\r
+                               public void actionPerformed(ActionEvent e) {\r
+                                       onCancel();\r
+                               }\r
+                       };\r
+\r
+                       JButton btnOK = new JButton(actOK);\r
+                       JButton btnCancel = new JButton(actCancel);\r
+\r
+                       Box btnPanel = Box.createHorizontalBox();\r
+                       btnPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 42));\r
+                       btnPanel.add(Box.createHorizontalGlue());\r
+\r
+                       if (Main.isLinuxOrMacOSX()) {\r
+                               btnPanel.add(btnCancel);\r
+                               btnPanel.add(btnOK);\r
+                       } else {\r
+                               btnPanel.add(btnOK);\r
+                               btnPanel.add(btnCancel);\r
+                       }\r
+\r
+                       container.add(btnPanel, BorderLayout.SOUTH);\r
+\r
+                       // カタログの選択が変更された場合、初期化中のイベントでなければハンドラに渡す\r
+                       cmbCategory.addActionListener(new ActionListener() {\r
+                               @Override\r
+                               public void actionPerformed(ActionEvent e) {\r
+                                       if (updaingCmbCategoryModel.get() == 0) {\r
+                                               onChangeSelectCategory((PartsCategory) cmbCategoryModel.getSelectedItem());\r
+                                       }\r
+                               }\r
+                       });\r
+\r
+                       // レイヤーの選択が変更された場合、初期渦中のイベントでなければハンドラに渡す\r
+                       cmbLayer.addActionListener(new ActionListener() {\r
+                               @Override\r
+                               public void actionPerformed(ActionEvent e) {\r
+                                       if (updaingCmbLayerModel.get() == 0) {\r
+                                               onChangeSelectLayer((Layer) cmbLayerModel.getSelectedItem());\r
+                                       }\r
+                               }\r
+                       });\r
+\r
+                       // レイヤー順序テキストボックスの入力イベントが初期化中のイベントでなければ\r
+                       // UIモデルの更新のハンドラに引き渡す。\r
+                       txtOrder.getDocument().addDocumentListener(new DocumentListener() {\r
+                               @Override\r
+                               public void removeUpdate(DocumentEvent e) {\r
+                                       onChange(e);\r
+                               }\r
+\r
+                               @Override\r
+                               public void insertUpdate(DocumentEvent e) {\r
+                                       onChange(e);\r
+                               }\r
+\r
+                               @Override\r
+                               public void changedUpdate(DocumentEvent e) {\r
+                                       onChange(e);\r
+                               }\r
+\r
+                               protected void onChange(DocumentEvent e) {\r
+                                       if (updaingTxtOrderModel.get() == 0) {\r
+                                               onChangeTxtOrder(txtOrder.getText());\r
+                                       }\r
+                               }\r
+                       });\r
+\r
+                       // AddLayerDialogModelのプロパティ変更を監視し、対応するUIを更新するためのリスナ\r
+                       modelPropChangeListener = new PropertyChangeListener() {\r
+                               @Override\r
+                               public void propertyChange(PropertyChangeEvent evt) {\r
+                                       String propName = evt.getPropertyName();\r
+                                       if (AddLayerDialogModel.SELECTED_PARTS_CATEGORY.equals(propName)) {\r
+                                               updateCmbCategory();\r
+                                       } else if (AddLayerDialogModel.SELECTED_LAYER.equals(propName)) {\r
+                                               updateCmbLayer();\r
+                                       } else if (AddLayerDialogModel.DEFAULT_ORDER.equals(propName)) {\r
+                                               updateTxtDefaultOrder();\r
+                                       } else if (AddLayerDialogModel.ORDER.equals(propName)) {\r
+                                               updateTxtOrder();\r
+                                       }\r
+\r
+                                       // モデルの状態をOKボタンの状態に反映する\r
+                                       updateButtonState();\r
+                               }\r
+                       };\r
+\r
+                       // デフォルトキーの設定\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       JRootPane rootPane = getRootPane();\r
+                       rootPane.setDefaultButton(btnOK);\r
+\r
+                       InputMap im = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);\r
+                       ActionMap am = rootPane.getActionMap();\r
+                       im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeAddLayerDialog");\r
+                       im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "closeAddLayerDialog");\r
+                       am.put("closeAddLayerDialog", actCancel);\r
+\r
+                       // コンボボックスの推奨幅を明示的に指定する。(言語によっては幅が小さくなりすぎるため)\r
+                       Dimension prefSize = cmbCategory.getPreferredSize();\r
+                       prefSize.width = 200;\r
+                       cmbCategory.setPreferredSize(prefSize);\r
+                       pack();\r
+\r
+                       // 初期化モデルの設定(dummy)\r
+                       setModel(new AddLayerDialogModel());\r
+               }\r
+\r
+               /**\r
+                * AddLayerDialogModelのプロパティ変更を監視するリスナ\r
+                */\r
+               private PropertyChangeListener modelPropChangeListener;\r
+\r
+               /**\r
+                * レイヤー追加ダイアログのモデル\r
+                */\r
+               private AddLayerDialogModel model = new AddLayerDialogModel();\r
+\r
+               public AddLayerDialogModel getModel() {\r
+                       return model;\r
+               }\r
+\r
+               public void setModel(AddLayerDialogModel model) {\r
+                       if (model == null) {\r
+                               throw new NullPointerException();\r
+                       }\r
+\r
+                       AddLayerDialogModel old = this.model;\r
+                       if (old.equals(model)) {\r
+                               // 同じなので何もしない\r
+                               return;\r
+                       }\r
+\r
+                       old.removePropertyChangeListener(modelPropChangeListener);\r
+                       this.model = model;\r
+                       model.addPropertyChangeListener(modelPropChangeListener);\r
+\r
+                       // モデルの内容でUIを更新する。\r
+                       // ※ UI更新中はイベントの発生を無視させる\r
+                       initCmbCategory();\r
+                       initCmbLayer();\r
+                       initDefaultOrder();\r
+                       initOrder();\r
+\r
+                       updateButtonState();\r
+\r
+                       firePropertyChange(MODEL, old, model);\r
+               }\r
+\r
+               /**\r
+                * cmbCategoryModelのUIの初期化中であることを表す。\r
+                * UIのイベントリスナで初期化中のイベントは別コンポーネントに連携させないようにする。\r
+                */\r
+               private final AtomicInteger updaingCmbCategoryModel = new AtomicInteger();\r
+\r
+               protected void initCmbCategory() {\r
+                       updaingCmbCategoryModel.incrementAndGet();\r
+                       try {\r
+                               List<PartsCategory> categories = model.getPartsCategories();\r
+                               cmbCategoryModel.setAll(categories);\r
+                               cmbCategoryModel.setSelectedItem(model.getSelectedPartsCategory());\r
+                       } finally {\r
+                               updaingCmbCategoryModel.decrementAndGet();\r
+                       }\r
+               }\r
+\r
+               protected void onChangeSelectCategory(PartsCategory selectedCategory) {\r
+                       model.setSelectedPartsCategory(selectedCategory);\r
+                       model.setSelectedLayer(null);\r
+               }\r
+\r
+               /**\r
+                * モデルのCategory現在値が変更されたので画面に反映する\r
+                */\r
+               protected void updateCmbCategory() {\r
+                       PartsCategory selCategory = model.getSelectedPartsCategory();\r
+                       cmbCategoryModel.setSelectedItem(selCategory);\r
+                       initCmbLayer();\r
+               }\r
+\r
+               /**\r
+                * cmbLayerModelのUIの初期化中であることを表す。\r
+                * UIのイベントリスナで初期化中のイベントは別コンポーネントに連携させないようにする。\r
+                */\r
+               private final AtomicInteger updaingCmbLayerModel = new AtomicInteger();\r
+\r
+               protected void initCmbLayer() {\r
+                       updaingCmbLayerModel.incrementAndGet();\r
+                       try {\r
+                               PartsCategory selCategory = model.getSelectedPartsCategory();\r
+                               if (selCategory != null) {\r
+                                       cmbLayerModel.setAll(selCategory.getLayers());\r
+                               } else {\r
+                                       cmbLayerModel.setAll(null);\r
+                               }\r
+                               cmbLayerModel.setSelectedItem(model.getSelectedLayer());\r
+\r
+                       } finally {\r
+                               updaingCmbLayerModel.decrementAndGet();\r
+                       }\r
+               }\r
+\r
+               protected void onChangeSelectLayer(Layer selectedLayer) {\r
+                       model.setSelectedLayer(selectedLayer);\r
+\r
+                       // レイヤーの既定の順序を設定する\r
+                       int defaultOrder = 0;\r
+                       if (selectedLayer != null) {\r
+                               defaultOrder = selectedLayer.getOrder();\r
+                       }\r
+                       model.setDefaultOrder(defaultOrder);\r
+                       model.setOrder(defaultOrder);\r
+               }\r
+\r
+               /**\r
+                * モデルのLayer現在値が変更されたので画面に反映する\r
+                */\r
+               protected void updateCmbLayer() {\r
+                       Layer selLayer = model.getSelectedLayer();\r
+                       cmbLayerModel.setSelectedItem(selLayer);\r
+               }\r
+\r
+               private void initDefaultOrder() {\r
+                       // txtDefaultOrderは編集不可のテキストボックスでイベントは監視していない。\r
+                       if (model.getSelectedLayer() != null) {\r
+                               txtDefaultOrder.setText(Integer.toString(model.getDefaultOrder()));\r
+                       } else {\r
+                               txtDefaultOrder.setText("");\r
+                       }\r
+               }\r
+\r
+               /**\r
+                * txtOrderのUIの初期化中であることを表す。\r
+                * UIのイベントリスナで初期化中のイベントは別コンポーネントに連携させないようにする。\r
+                */\r
+               private final AtomicInteger updaingTxtOrderModel = new AtomicInteger();\r
+\r
+               /**\r
+                * txtOrderを初期設定する\r
+                * この処理期間中は{@link #updaingCmbLayerModel}を0以上とすることで\r
+                * コンボボックスからのイベントが、この初期設定によるものであることを判別できる。\r
+                */\r
+               private void initOrder() {\r
+                       updaingTxtOrderModel.incrementAndGet();\r
+                       try {\r
+                               txtOrder.setText(Integer.toString(model.getOrder()));\r
+                               updateTxtOrderState(false);\r
+\r
+                       } finally {\r
+                               updaingTxtOrderModel.decrementAndGet();\r
+                       }\r
+               }\r
+\r
+               private void updateTxtOrderState(boolean error) {\r
+                       Color color;\r
+                       if (error) {\r
+                               color = Color.RED;\r
+                       } else {\r
+                               color = UIManager.getColor("TextField.background");\r
+                       }\r
+                       txtOrder.setBackground(color);\r
+               }\r
+\r
+               /**\r
+                * ユーザー操作によりテキストボックスが変更された場合\r
+                * @param text\r
+                */\r
+               protected void onChangeTxtOrder(String text) {\r
+                       boolean error = true;\r
+                       try {\r
+                               if (text != null && text.length() > 0) {\r
+                                       int order = Integer.parseInt(text);\r
+                                       model.setOrder(order);\r
+                                       error = false;\r
+                               }\r
+                       } catch (RuntimeException ex) {\r
+                               // なにもしない\r
+                       }\r
+\r
+                       updateTxtOrderState(error);\r
+               }\r
+\r
+               protected void updateTxtDefaultOrder() {\r
+                       // DefaultOrderテキストボックスは編集はしないのでinitと同じで良い\r
+                       initDefaultOrder();\r
+               }\r
+\r
+               /**\r
+                * モデルのorderが変更されたので画面に反映する\r
+                */\r
+               protected void updateTxtOrder() {\r
+                       int order = model.getOrder();\r
+                       Integer old;\r
+                       try {\r
+                               old = Integer.parseInt(txtOrder.getText());\r
+                       } catch (RuntimeException ex) {\r
+                               old = null;\r
+                       }\r
+                       if (old == null || old != order) {\r
+                               // 現在の入力している値と数値的に異なる場合のみ再設定する\r
+                               // (自分のテキストボックス変更イベントによりモデルが変更され、その結果、\r
+                               // テキストボックスへの値変更が呼び出された場合に状態エラーが発生する。)\r
+                               txtOrder.setText(Integer.toString(order));\r
+                               updateTxtOrderState(false);\r
+                       }\r
+               }\r
+\r
+               protected void updateButtonState() {\r
+                       actOK.setEnabled(model.isValid());\r
+               }\r
+\r
+               protected void onOK() {\r
+                       if (!model.isValid()) {\r
+                               return;\r
+                       }\r
+                       model.setResult(true);\r
+                       dispose();\r
+               }\r
+\r
+               protected void onCancel() {\r
+                       model.setResult(false);\r
+                       dispose();\r
+               }\r
+       }\r
+\r
+       /**\r
+        * レイヤーの追加ダイアログを開く\r
+        */\r
+       protected void onAddLayer() {\r
+               AddLayerDialogModel model = new AddLayerDialogModel();\r
+               model.setPartsCategories(categories);\r
+\r
+               final AddLayerDialog dlg = new AddLayerDialog(this, true);\r
+               dlg.setModel(model);\r
+\r
+               // 中央に配置\r
+               Point loc = getLocationOnScreen();\r
+               int centerX = loc.x + getWidth() / 2;\r
+               int centerY = loc.y + getHeight() / 2;\r
+\r
+               int x = centerX - dlg.getWidth() / 2;\r
+               int y = centerY - dlg.getHeight() / 2;\r
+               dlg.setLocation(x, y);\r
+               dlg.setVisible(true);\r
+\r
+               if (model.isResult()) {\r
+                       PartsCategory category = model.getSelectedPartsCategory();\r
+                       Layer layer = model.getSelectedLayer();\r
+                       int layerOrder = model.getOrder();\r
+                       dataModel.add(category, layer, layerOrder);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 選択レイヤーを削除する\r
+        */\r
+       protected void onDeleteLayer() {\r
+               int selrow = tblLayerOrder.getSelectedRow();\r
+               if (selrow >= 0) {\r
+                       dataModel.remove(selrow);\r
+               }\r
+       }\r
+}\r
+\r
+/**\r
+ * レイヤーカスタマイズダイアログのデータモデル\r
+ */\r
+class LayerOrderCustomizeDialogModel {\r
+\r
+       /**\r
+        * 保持しているレイヤーパターンの登録・変更・削除を通知されるリスナ\r
+        */\r
+       public interface ChangeListener extends EventListener {\r
+\r
+               /**\r
+                * 通知タイプ\r
+                */\r
+               public enum ChangeType {\r
+                       ADD, MODIFY, REMOVE\r
+               }\r
+\r
+               /**\r
+                * イベント\r
+                */\r
+               public class Change extends EventObject {\r
+\r
+                       private static final long serialVersionUID = -4578290841626577210L;\r
+\r
+                       /**\r
+                        * レイヤーパターン名\r
+                        */\r
+                       private String name;\r
+\r
+                       /**\r
+                        * 変更タイプ\r
+                        */\r
+                       private ChangeType changeType;\r
+\r
+                       public Change(LayerOrderCustomizeDialogModel source, String name, ChangeType changeType) {\r
+                               super(source);\r
+                               this.name = name;\r
+                               this.changeType = changeType;\r
+                       }\r
+\r
+                       public String getName() {\r
+                               return name;\r
+                       }\r
+\r
+                       public ChangeType getChangeType() {\r
+                               return changeType;\r
+                       }\r
+\r
+                       @Override\r
+                       public LayerOrderCustomizeDialogModel getSource() {\r
+                               return (LayerOrderCustomizeDialogModel) super.getSource();\r
+                       }\r
+\r
+                       @Override\r
+                       public String toString() {\r
+                               return "Change [name=" + name + ", changeType=" + changeType + "]";\r
+                       }\r
+               }\r
+\r
+               void onChange(Change change);\r
+       }\r
+\r
+       /**\r
+        * イベントリスナのリスト\r
+        */\r
+       private final EventListenerList listeners = new EventListenerList();\r
+\r
+       /**\r
+        * レイヤー名パターンを保持する。(patternsMapから導出され、以後、監視される。)\r
+        */\r
+       private final ObservableList<String> patternNames = new ObservableList<String>();\r
+\r
+       /**\r
+        * レイヤーパターン名をキーとして、レイヤーの構成リストを値とするマップ。\r
+        * キーはpatternNamesの監視リストと連携する。\r
+        */\r
+       private final Map<String, List<CustomLayerOrder>> patternsMap = new HashMap<String, List<CustomLayerOrder>>();\r
+\r
+       /**\r
+        * レイヤー編集画面の初期値\r
+        */\r
+       private List<CustomLayerOrder> currentList = Collections.emptyList();\r
+\r
+       /**\r
+        * コンストラクタ\r
+        */\r
+       public LayerOrderCustomizeDialogModel() {\r
+               patternNames.addListChangeListener(new ListChangeListener<String>() {\r
+                       @Override\r
+                       public void onChanged(Change<? extends String> c) {\r
+                               switch (c.getType()) {\r
+                               case ADD:\r
+                                       // パターン名が追加された場合は、あわせて導出元のマップも追加する\r
+                                       String newName = c.getNewValue();\r
+                                       if (!patternsMap.containsKey(newName)) {\r
+                                               // まだマップに存在しなければ空のリストを作成しておく\r
+                                               patternsMap.put(newName, new ArrayList<CustomLayerOrder>());\r
+                                               fireEvent(ChangeListener.ChangeType.ADD, newName);\r
+                                       }\r
+                                       break;\r
+\r
+                               case REMOVE:\r
+                                       // パターン名が削除された場合は、導出元のマップも削除する。\r
+                                       String oldName = c.getOldValue();\r
+                                       patternsMap.remove(oldName);\r
+                                       fireEvent(ChangeListener.ChangeType.REMOVE, oldName);\r
+                                       break;\r
+                               }\r
+                       }\r
+               });\r
+       }\r
+\r
+       /**\r
+        * 変更通知リスナを登録する\r
+        * @param l リスナ\r
+        */\r
+       public void addListChangeListener(ChangeListener l) {\r
+               listeners.add(ChangeListener.class, l);\r
+       }\r
+\r
+       /**\r
+        * 変更通知リスナを登録解除する\r
+        * @param l リスナ\r
+        */\r
+       public void removeListChangeListener(ChangeListener l) {\r
+               listeners.remove(ChangeListener.class, l);\r
+       }\r
+\r
+       /**\r
+        * 全ての変更通知リスナに対して変更イベントを通知する。\r
+        * @param type 変更タイプ\r
+        * @param name 名前\r
+        */\r
+       protected void fireEvent(ChangeListener.ChangeType type, String name) {\r
+               // Guaranteed to return a non-null array\r
+               Object[] ll = listeners.getListenerList();\r
+               // Process the listeners last to first, notifying\r
+               // those that are interested in this event\r
+               // ※ 逆順で通知するのがSwingの作法らしい。\r
+               ChangeListener.Change event = null;\r
+               for (int i = ll.length - 2; i >= 0; i -= 2) {\r
+                       if (ll[i] == ChangeListener.class) {\r
+                               // Lazily create the event:\r
+                               if (event == null) {\r
+                                       event = new ChangeListener.Change(this, name, type);\r
+                               }\r
+                               ((ChangeListener) ll[i + 1]).onChange(event);\r
+                       }\r
+               }\r
+       }\r
+\r
+       public ObservableList<CustomLayerOrder> getCurrentList() {\r
+               return copyWithoutListeners(currentList);\r
+       }\r
+\r
+       public void setCurrentList(List<CustomLayerOrder> currentList) {\r
+               if (!currentList.equals(this.currentList)) {\r
+                       this.currentList = copyWithoutListeners(\r
+                                       currentList != null ? currentList : new ArrayList<CustomLayerOrder>());\r
+                       fireEvent(ChangeListener.ChangeType.MODIFY, "");\r
+               }\r
+       }\r
+\r
+       public ObservableList<String> getPatternNames() {\r
+               return patternNames;\r
+       }\r
+\r
+       public ObservableList<CustomLayerOrder> getCopy(String name) {\r
+               return copyWithoutListeners(patternsMap.get(name));\r
+       }\r
+\r
+       public void put(String name, List<CustomLayerOrder> layerOrderList) {\r
+               boolean exist = patternsMap.containsKey(name);\r
+               patternsMap.put(name, copyWithoutListeners(layerOrderList));\r
+               if (!exist) {\r
+                       patternNames.add(name);\r
+                       fireEvent(ChangeListener.ChangeType.ADD, name);\r
+               } else {\r
+                       fireEvent(ChangeListener.ChangeType.MODIFY, name);\r
+               }\r
+       }\r
+\r
+       public void remove(String name) {\r
+               if (patternNames.contains(name)) {\r
+                       // 名前の削除によりリスナーでマップも削除される\r
+                       patternNames.remove(name);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 監視可能なリストをコピーして返す。ただし、既存のリスナーはコピーされない。\r
+        * 返される可能可能リストは要素の変更を監視してリストの変更通知として返すように設定される。\r
+        * @param src\r
+        * @return\r
+        */\r
+       public ObservableList<CustomLayerOrder> copyWithoutListeners(List<CustomLayerOrder> src) {\r
+               if (src == null) {\r
+                       return null;\r
+               }\r
+               ObservableList<CustomLayerOrder> list = createObservableList();\r
+               for (CustomLayerOrder item : src) {\r
+                       list.add(item.copy());\r
+               }\r
+               return list;\r
+       }\r
+\r
+       /**\r
+        * 内包する要素の変更通知をリストの更新通知に接続する要素を含めた監視可能なリストを作成して返す。\r
+        * @return\r
+        */\r
+       public ObservableList<CustomLayerOrder> createObservableList() {\r
+               final List<CustomLayerOrder> rawList = new ArrayList<CustomLayerOrder>();\r
+               final ObservableList<CustomLayerOrder> obsList = new ObservableList<CustomLayerOrder>(rawList);\r
+               final PropertyChangeListener listener = new PropertyChangeListener() {\r
+                       @Override\r
+                       public void propertyChange(PropertyChangeEvent evt) {\r
+                               CustomLayerOrder source = (CustomLayerOrder) evt.getSource();\r
+                               int index = rawList.indexOf(source);\r
+                               obsList.fireEvent(ListChangeListener.ChangeType.MODIFIY, index, null, source);\r
+                       }\r
+               };\r
+\r
+               ObservableList.Hook<CustomLayerOrder> hook = new ObservableList.Hook<CustomLayerOrder>() {\r
+                       @Override\r
+                       public void add(CustomLayerOrder item) {\r
+                               item.addPropertyChangeListener(listener);\r
+                       }\r
+                       @Override\r
+                       public void remove(CustomLayerOrder item) {\r
+                               item.removePropertyChangeListener(listener);\r
+                       }\r
+               };\r
+               obsList.setHook(hook);\r
+\r
+               return obsList;\r
+       }\r
+}\r
index a6c094e..2b0e55f 100644 (file)
-package charactermanaj.ui;
-
-import static java.lang.Math.*;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Container;
-import java.awt.Cursor;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.Frame;
-import java.awt.GraphicsEnvironment;
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.Toolkit;
-import java.awt.dnd.DropTarget;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.MouseWheelEvent;
-import java.awt.event.MouseWheelListener;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.TreeMap;
-import java.util.UUID;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.JCheckBox;
-import javax.swing.JColorChooser;
-import javax.swing.JFrame;
-import javax.swing.JMenu;
-import javax.swing.JMenuBar;
-import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JPopupMenu;
-import javax.swing.JScrollBar;
-import javax.swing.JScrollPane;
-import javax.swing.JSeparator;
-import javax.swing.JSplitPane;
-import javax.swing.JViewport;
-import javax.swing.SwingUtilities;
-import javax.swing.event.AncestorEvent;
-import javax.swing.event.AncestorListener;
-import javax.swing.event.MenuEvent;
-import javax.swing.event.MenuListener;
-
-import charactermanaj.Main;
-import charactermanaj.clipboardSupport.ClipboardUtil;
-import charactermanaj.graphics.AsyncImageBuilder;
-import charactermanaj.graphics.ColorConvertedImageCachedLoader;
-import charactermanaj.graphics.ImageBuildJobAbstractAdaptor;
-import charactermanaj.graphics.ImageBuilder.ImageOutput;
-import charactermanaj.graphics.io.ImageSaveHelper;
-import charactermanaj.graphics.io.OutputOption;
-import charactermanaj.graphics.io.UkagakaImageSaveHelper;
-import charactermanaj.model.AppConfig;
-import charactermanaj.model.CharacterData;
-import charactermanaj.model.CharacterDataChangeEvent;
-import charactermanaj.model.CharacterDataChangeListener;
-import charactermanaj.model.CharacterDataChangeObserver;
-import charactermanaj.model.ColorGroup;
-import charactermanaj.model.IndependentPartsSetInfo;
-import charactermanaj.model.PartsCategory;
-import charactermanaj.model.PartsColorInfo;
-import charactermanaj.model.PartsColorManager;
-import charactermanaj.model.PartsIdentifier;
-import charactermanaj.model.PartsSet;
-import charactermanaj.model.RecommendationURL;
-import charactermanaj.model.WorkingSet;
-import charactermanaj.model.WorkingSet2;
-import charactermanaj.model.io.CharacterDataPersistent;
-import charactermanaj.model.io.PartsImageDirectoryWatchAgent;
-import charactermanaj.model.io.PartsImageDirectoryWatchAgentFactory;
-import charactermanaj.model.io.PartsImageDirectoryWatchEvent;
-import charactermanaj.model.io.PartsImageDirectoryWatchListener;
-import charactermanaj.model.io.RecentDataPersistent;
-import charactermanaj.model.io.WorkingSetPersist;
-import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelEvent;
-import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelListener;
-import charactermanaj.ui.ManageFavoriteDialog.FavoriteManageCallback;
-import charactermanaj.ui.PreviewPanel.PreviewPanelEvent;
-import charactermanaj.ui.PreviewPanel.PreviewPanelListener;
-import charactermanaj.ui.model.ColorChangeEvent;
-import charactermanaj.ui.model.ColorChangeListener;
-import charactermanaj.ui.model.ColorGroupCoordinator;
-import charactermanaj.ui.model.FavoritesChangeEvent;
-import charactermanaj.ui.model.FavoritesChangeListener;
-import charactermanaj.ui.model.FavoritesChangeObserver;
-import charactermanaj.ui.model.PartsColorCoordinator;
-import charactermanaj.ui.model.PartsSelectionManager;
-import charactermanaj.ui.model.WallpaperFactory;
-import charactermanaj.ui.model.WallpaperFactoryErrorRecoverHandler;
-import charactermanaj.ui.model.WallpaperFactoryException;
-import charactermanaj.ui.model.WallpaperInfo;
-import charactermanaj.ui.scrollablemenu.JScrollableMenu;
-import charactermanaj.ui.util.FileDropTarget;
-import charactermanaj.ui.util.WindowAdjustLocationSupport;
-import charactermanaj.util.DesktopUtilities;
-import charactermanaj.util.ErrorMessageHelper;
-import charactermanaj.util.LocalizedResourcePropertyLoader;
-import charactermanaj.util.SystemUtil;
-import charactermanaj.util.UIHelper;
-
-
-/**
- * メインフレーム.<br>
- * アプリケーションがアクティブである場合は最低でも1つのメインフレームが表示されている.<br>
- *
- * @author seraphy
- */
-public class MainFrame extends JFrame
-               implements
-                       FavoritesChangeListener,
-                       CharacterDataChangeListener {
-
-       private static final long serialVersionUID = 1L;
-
-       private static final Logger logger = Logger.getLogger(MainFrame.class.getName());
-
-
-       protected static final String STRINGS_RESOURCE = "languages/mainframe";
-
-       protected static final String MENU_STRINGS_RESOURCE = "menu/menu";
-
-       /**
-        * メインフレームのアイコン.<br>
-        */
-       protected BufferedImage icon;
-
-
-       /**
-        * 現在アクティブなメインフレーム.<br>
-        * フォーカスが切り替わるたびにアクティブフレームを追跡する.<br>
-        * Mac OS XのAbout/Preferences/Quitのシステムメニューからよびだされた場合に
-        * オーナーたるべきメインフレームを識別するためのもの.<br>
-        */
-       private static volatile MainFrame activedMainFrame;
-
-
-       /**
-        * このメインフレームが対象とするキャラクターデータ.<br>
-        */
-       protected CharacterData characterData;
-
-
-       /**
-        * プレビューペイン
-        */
-       private PreviewPanel previewPane;
-
-       /**
-        * パーツ選択マネージャ
-        */
-       protected PartsSelectionManager partsSelectionManager;
-
-       /**
-        * パネルの最小化モード
-        */
-       private boolean minimizeMode;
-
-
-       /**
-        * パーツ選択パネルリスト
-        */
-       protected ImageSelectPanelList imageSelectPanels;
-
-       /**
-        * パーツ選択パネルを納めるスクロールペイン
-        */
-       protected JScrollPane imgSelectPanelsPanelSp;
-
-       /**
-        * カラーグループのマネージャ
-        */
-       protected ColorGroupCoordinator colorGroupCoordinator;
-
-       /**
-        * パーツカラーのマネージャ
-        */
-       protected PartsColorCoordinator partsColorCoordinator;
-
-
-       /**
-        * キャッシュつきのイメージローダ.<br>
-        */
-       private ColorConvertedImageCachedLoader imageLoader;
-
-       /**
-        * パーツを組み立てて1つのプレビュー可能なイメージを構築するためのビルダ
-        */
-       private AsyncImageBuilder imageBuilder;
-
-
-       /**
-        * パーツイメージを画像として保存する場合のヘルパー.<br>
-        * 最後に使ったディレクトリを保持するためのメンバ変数としている.<br>
-        */
-       private ImageSaveHelper imageSaveHelper = new ImageSaveHelper();
-
-       /**
-        * 伺か用出力ヘルパ.<br>
-        * 最後に使ったディレクトリ、ファイル名、モードなどを保持するためのメンバ変数としている.<br>
-        */
-       private UkagakaImageSaveHelper ukagakaImageSaveHelper = new UkagakaImageSaveHelper();
-
-       /**
-        * パーツディレクトリを定期的にチェックし、パーツイメージが変更・追加・削除されている場合に パーツリストを更新するためのウォッチャー
-        */
-       private PartsImageDirectoryWatchAgent watchAgent;
-
-       /**
-        * デフォルトのパーツセット表示名
-        */
-       private String defaultPartsSetTitle;
-
-       /**
-        * 最後に使用したプリセット.<br>
-        * (一度もプリセットを使用していなければnull).
-        */
-       private PartsSet lastUsePresetParts;
-
-       /**
-        * 最後に使用した検索ダイアログ.<br>
-        * nullであれば一度も使用していない.<br>
-        * (nullでなくとも閉じられている可能性がある.)<br>
-        */
-       private SearchPartsDialog lastUseSearchPartsDialog;
-
-       /**
-        * 最後に使用したお気に入りダイアログ.<br>
-        * nullであれば一度も使用していない.<br>
-        * (nullでなくとも閉じられている可能性がある.)
-        */
-       private ManageFavoriteDialog lastUseManageFavoritesDialog;
-
-       /**
-        * 最後に使用したパーツのランダム選択ダイアログ.<br>
-        * nullであれば一度も使用していない.<br>
-        * (nullでなくとも閉じられている可能性がある.)
-        */
-       private PartsRandomChooserDialog lastUsePartsRandomChooserDialog;
-
-       /**
-        * 最後に使用した壁紙情報
-        */
-       private WallpaperInfo wallpaperInfo;
-
-
-       /**
-        * アクティブなメインフレームを設定する.
-        *
-        * @param mainFrame
-        *            メインフレーム
-        */
-       public static void setActivedMainFrame(MainFrame mainFrame) {
-               if (mainFrame == null) {
-                       throw new IllegalArgumentException();
-               }
-               activedMainFrame = mainFrame;
-       }
-
-       /**
-        * 現在アクティブなメインフレームを取得する. まだメインフレームが開かれていない場合はnull.<br>
-        * 最後のメインフレームが破棄中、もしくは破棄済みであれば破棄されたフレームを示すことに注意.<br>
-        *
-        * @return メインフレーム、もしくはnull
-        */
-       public static MainFrame getActivedMainFrame() {
-               return activedMainFrame;
-       }
-
-       /**
-        * キャラクターデータが変更された場合に通知される.
-        */
-       public void notifyChangeCharacterData(final CharacterDataChangeEvent e) {
-               final CharacterData cd = e.getCharacterData();
-               if (cd != null
-                               && cd.getDocBase().equals(
-                                               MainFrame.this.characterData.getDocBase())) {
-                       SwingUtilities.invokeLater(new Runnable() {
-                               public void run() {
-                                       try {
-                                               Cursor oldCur = getCursor();
-                                               setCursor(Cursor
-                                                               .getPredefinedCursor(Cursor.WAIT_CURSOR));
-                                               try {
-                                                       if (e.isChangeStructure()) {
-                                                               // 現在情報の保存
-                                                               saveWorkingSet();
-
-                                                               // 画面構成の再構築
-                                                               initComponent(cd);
-                                                       }
-
-                                                       if (e.isReloadPartsAndFavorites()) {
-                                                               // パーツとお気に入りのリロード
-                                                               reloadPartsAndFavorites(cd, true);
-                                                       }
-
-                                               } finally {
-                                                       setCursor(oldCur != null ? oldCur : Cursor
-                                                                       .getDefaultCursor());
-                                               }
-
-                                       } catch (Exception ex) {
-                                               ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);
-                                       }
-                               }
-                       });
-               }
-       }
-
-       /**
-        * お気に入りデータが変更された場合に通知される.
-        *
-        * @param e
-        */
-       public void notifyChangeFavorites(FavoritesChangeEvent e) {
-               CharacterData cd = e.getCharacterData();
-               if (cd != null
-                               && cd.getDocBase().equals(
-                                               MainFrame.this.characterData.getDocBase())) {
-                       if (!MainFrame.this.equals(e.getSource())
-                                       && !characterData.equals(cd)) {
-                               // プリセットとお気に入りを最新化する.
-                               // ただし、自分自身から送信したイベントの場合は最新化は不要.
-                               characterData.clearPartsSets(false);
-                               for (Map.Entry<String, PartsSet> entry : cd.getPartsSets()
-                                               .entrySet()) {
-                                       PartsSet partsSet = entry.getValue();
-                                       characterData.addPartsSet(partsSet);
-                               }
-                       }
-
-                       // お気に入り管理ダイアログ上のお気に入り一覧を最新に更新する.
-                       if (lastUseManageFavoritesDialog != null
-                                       && lastUseManageFavoritesDialog.isDisplayable()) {
-                               lastUseManageFavoritesDialog.initListModel();
-                       }
-               }
-       }
-
-       /**
-        * メインフレームを構築する.
-        *
-        * @param characterData
-        *            キャラクターデータ
-        */
-       public MainFrame(CharacterData characterData) {
-               try {
-                       if (characterData == null) {
-                               throw new IllegalArgumentException();
-                       }
-
-                       setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
-                       addWindowListener(new WindowAdapter() {
-                               @Override
-                               public void windowClosing(WindowEvent e) {
-                                       onCloseProfile();
-                               }
-                               @Override
-                               public void windowClosed(WindowEvent e) {
-                                       stopAgents();
-                               }
-                               @Override
-                               public void windowActivated(WindowEvent e) {
-                                       setActivedMainFrame(MainFrame.this);
-                               }
-                               @Override
-                               public void windowOpened(WindowEvent e) {
-                                       // do nothing.
-                               }
-                       });
-
-                       // アイコンの設定
-                       icon = UIHelper.getInstance().getImage("icons/icon.png");
-                       setIconImage(icon);
-
-                       // 画面コンポーネント作成
-                       initComponent(characterData);
-                       JMenuBar menuBar = createMenuBar();
-                       setJMenuBar(menuBar);
-
-                       // お気に入り変更通知を受け取る
-                       FavoritesChangeObserver.getDefault().addFavoritesChangeListener(
-                                       this);
-                       // キャラクターデータの変更通知を受け取る
-                       CharacterDataChangeObserver.getDefault()
-                                       .addCharacterDataChangeListener(this);
-
-               } catch (RuntimeException ex) {
-                       logger.log(Level.SEVERE, "メインフレームの構築中に予期せぬ例外が発生しました。", ex);
-                       dispose(); // コンストラクタが呼ばれた時点でJFrameは構築済みなのでdisposeの必要がある.
-                       throw ex;
-               } catch (Error ex) {
-                       logger.log(Level.SEVERE, "メインフレームの構築中に致命的な例外が発生しました。", ex);
-                       dispose(); // コンストラクタが呼ばれた時点でJFrameは構築済みなのでdisposeの必要がある.
-                       throw ex;
-               }
-       }
-
-       /**
-        * デフォルトのウィンドウ位置とサイズの設定
-        */
-       private void setDefaultWindowLocation() {
-               // メインスクリーンサイズを取得する.
-               GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
-               Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)
-               logger.log(Level.CONFIG, "desktopSize=" + desktopSize);
-
-               Dimension imageSize = characterData.getImageSize();
-               // 画像サイズ300x400を基準サイズとして、それ以下にはならない.
-               // アプリケーション設定の最大サイズ以上の場合はウィンドウサイズは固定してスクロールバーに任せる
-               AppConfig appConfig = AppConfig.getInstance();
-               int maxWidth = min(desktopSize.width, appConfig.getMainFrameMaxWidth());
-               int maxHeight = min(desktopSize.height, appConfig.getMainFrameMaxHeight());
-               int imageWidth = min(maxWidth, max(300, imageSize != null ? imageSize.width : 0));
-               int imageHeight = min(maxHeight, max(400, imageSize != null ? imageSize.height : 0));
-               // 300x400の画像の場合にメインフレームが600x550だとちょうどいい感じ.
-               // それ以上大きい画像の場合は増えた分だけフレームを大きくしておく.
-               setSize(imageWidth - 300 + 600, imageHeight - 400 + 550);
-
-               // 次回表示時にプラットフォーム固有位置に表示するように予約
-               setLocationByPlatform(true);
-       }
-
-       /**
-        * メインフレームを表示する.<br>
-        * デスクトップ領域からはみ出した場合は位置を補正する.<br>
-        */
-       public void showMainFrame() {
-               // メインスクリーンサイズを取得する.
-               GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
-               Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)
-               logger.log(Level.CONFIG, "desktopSize=" + desktopSize);
-
-               // プラットフォーム固有の位置あわせで表示する.
-               // 表示した結果、はみ出している場合は0,0に補正する.
-               setVisible(true);
-               Point loc = getLocation();
-               logger.log(Level.CONFIG, "windowLocation=" + loc);
-               Dimension windowSize = getSize();
-               if (loc.y + windowSize.height >= desktopSize.height) {
-                       loc.y = 0;
-               }
-               if (loc.x + windowSize.width >= desktopSize.width) {
-                       loc.x = 0;
-               }
-               if (loc.x == 0 || loc.y == 0) {
-                       setLocation(loc);
-               }
-
-               // デスクトップよりも大きい場合は小さくする.
-               boolean resize = false;
-               Dimension dim = getSize();
-               if (dim.height > desktopSize.height) {
-                       dim.height = desktopSize.height;
-                       resize = true;
-               }
-               if (dim.width > desktopSize.width) {
-                       dim.width = desktopSize.width;
-                       resize = true;
-               }
-               if (resize) {
-                       setSize(dim);
-               }
-       }
-
-       /**
-        * このメインフレームに関連づけられているエージェントスレッドを停止します.<br>
-        * すでに停止している場合は何もしません。
-        */
-       protected void stopAgents() {
-               // エージェントを停止
-               if (watchAgent != null) {
-                       try {
-                               watchAgent.disconnect();
-
-                       } catch (Throwable ex) {
-                               logger.log(Level.SEVERE, "フォルダ監視スレッドの停止に失敗しました。", ex);
-                       }
-                       watchAgent = null;
-               }
-               // イメージビルダを停止
-               if (imageBuilder != null) {
-                       try {
-                               imageBuilder.stop();
-
-                       } catch (Throwable ex) {
-                               logger.log(Level.SEVERE, "非同期イメージビルダスレッドの停止に失敗しました。", ex);
-                       }
-                       imageBuilder = null;
-               }
-       }
-
-       /**
-        * メインフレームを破棄します.<br>
-        */
-       @Override
-       public void dispose() {
-               FavoritesChangeObserver.getDefault()
-                               .removeFavoritesChangeListener(this);
-               CharacterDataChangeObserver.getDefault()
-                               .removeCharacterDataChangeListener(this);
-           imageLoader.close();
-               stopAgents();
-               super.dispose();
-       }
-
-       /**
-        * 画面コンポーネントを設定します.<br>
-        * すでに設定されている場合は一旦削除されたのちに再作成されます.<br>
-        */
-       private void initComponent(CharacterData characterData) {
-
-               CharacterData oldCd;
-               synchronized (this) {
-                       oldCd = this.characterData;
-                       if (oldCd != null) {
-                               // 使用中のキャラクターデータであることを登録解除する。
-                               ProfileListManager.unregisterUsedCharacterData(oldCd);
-                       }
-                       this.characterData = characterData;
-
-                       // 使用中のキャラクターデータであることを登録する.
-                       ProfileListManager.registerUsedCharacterData(characterData);
-               }
-
-               // 設定まわり準備
-               AppConfig appConfig = AppConfig.getInstance();
-               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
-                               .getLocalizedProperties(STRINGS_RESOURCE);
-
-               // タイトル表示
-               String title;
-               if (Main.isMacOSX()) {
-                       // Mac OS Xの場合はウィンドウにタイトルはつけない。
-                       title = "";
-               } else {
-                       title = strings.getProperty("title");
-               }
-               setTitle(title + characterData.getName());
-
-               // デフォルトのパーツセット表示名
-               defaultPartsSetTitle = strings.getProperty("defaultPartsSetTitle");
-
-               // エージェントの停止
-               stopAgents();
-
-               // コンポーネント配置
-               Container contentPane = getContentPane();
-
-               // すでにあるコンポーネントを削除
-               for (Component comp : contentPane.getComponents()) {
-                       contentPane.remove(comp);
-               }
-               // 開いている検索ダイアログを閉じる
-               closeSearchDialog();
-
-               // 開いているお気に入り管理ダイアログを閉じる
-               closeManageFavoritesDialog();
-
-               // 開いているランダム選択ダイアログを閉じる.
-               closePartsRandomChooserDialog();
-
-               PartsColorManager partsColorManager = characterData.getPartsColorManager();
-
-               // デフォルトの背景色の設定
-               Color bgColor = appConfig.getDefaultImageBgColor();
-               wallpaperInfo = new WallpaperInfo();
-               wallpaperInfo.setBackgroundColor(bgColor);
-
-               if (imageLoader != null) {
-                   imageLoader.close();
-               }
-               imageLoader = new ColorConvertedImageCachedLoader();
-               imageBuilder = new AsyncImageBuilder(imageLoader);
-               partsSelectionManager = new PartsSelectionManager(partsColorManager,
-                               new PartsSelectionManager.ImageBgColorProvider() {
-                       public Color getImageBgColor() {
-                               return wallpaperInfo.getBackgroundColor();
-                       }
-                       public void setImageBgColor(Color imageBgColor) {
-                               applyBackgroundColorOnly(imageBgColor);
-                       }
-               });
-               colorGroupCoordinator = new ColorGroupCoordinator(partsSelectionManager, partsColorManager);
-               partsColorCoordinator = new PartsColorCoordinator(characterData, partsColorManager, colorGroupCoordinator);
-               PartsImageDirectoryWatchAgentFactory agentFactory = PartsImageDirectoryWatchAgentFactory.getFactory();
-               watchAgent = agentFactory.getAgent(characterData);
-
-               previewPane = new PreviewPanel();
-               previewPane.setTitle(defaultPartsSetTitle);
-               previewPane.addPreviewPanelListener(new PreviewPanelListener() {
-                       public void addFavorite(PreviewPanelEvent e) {
-                               if (!e.isShiftKeyPressed()) {
-                                       // お気に入り登録
-                                       onRegisterFavorite();
-
-                               } else {
-                                       // シフトキーにて、お気に入りの管理を開く
-                                       onManageFavorites();
-                               }
-                       }
-                       public void changeBackgroundColor(PreviewPanelEvent e) {
-                               if ( !e.isShiftKeyPressed()) {
-                                       // 壁紙選択
-                                       onChangeWallpaper();
-
-                               } else {
-                                       // シフトキーにて背景色変更
-                                       onChangeBgColor();
-                               }
-                       }
-                       public void copyPicture(PreviewPanelEvent e) {
-                               onCopy(e.isShiftKeyPressed());
-                       }
-                       public void savePicture(PreviewPanelEvent e) {
-                               if ( !e.isShiftKeyPressed()) {
-                                       // 画像出力
-                                       onSavePicture();
-
-                               } else {
-                                       // シフトキーにて「伺か」用出力
-                                       onSaveAsUkagaka();
-                               }
-                       }
-                       public void showInformation(PreviewPanelEvent e) {
-                               onInformation();
-                       }
-                       public void flipHorizontal(PreviewPanelEvent e) {
-                               onFlipHolizontal();
-                       }
-               });
-
-               imageSelectPanels = new ImageSelectPanelList();
-
-               JPanel imgSelectPanelsPanel = new JPanel();
-               BoxLayout bl = new BoxLayout(imgSelectPanelsPanel, BoxLayout.PAGE_AXIS);
-               imgSelectPanelsPanel.setLayout(bl);
-               for (PartsCategory category : characterData.getPartsCategories()) {
-                       final ImageSelectPanel imageSelectPanel = new ImageSelectPanel(category, characterData);
-                       imgSelectPanelsPanel.add(imageSelectPanel);
-                       imageSelectPanels.add(imageSelectPanel);
-                       partsSelectionManager.register(imageSelectPanel);
-               }
-
-               imgSelectPanelsPanelSp = new JScrollPane(imgSelectPanelsPanel) {
-                       private static final long serialVersionUID = 1L;
-                       @Override
-                       public JScrollBar createVerticalScrollBar() {
-                               JScrollBar sb = super.createVerticalScrollBar();
-                               sb.setUnitIncrement(12);
-                               return sb;
-                       }
-               };
-               imgSelectPanelsPanelSp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
-
-               JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, imgSelectPanelsPanelSp, previewPane);
-               contentPane.add(splitPane, BorderLayout.CENTER);
-
-
-               imgSelectPanelsPanelSp.requestFocus();
-
-               ArrayList<ColorGroup> colorGroups = new ArrayList<ColorGroup>();
-               colorGroups.addAll(characterData.getColorGroups());
-
-               final ColorChangeListener colorChangeListener = new ColorChangeListener() {
-                       public void onColorGroupChange(ColorChangeEvent event) {
-                               // do nothing.
-                       }
-                       public void onColorChange(ColorChangeEvent event) {
-                               MainFrame.this.requestPreview();
-                       }
-               };
-               colorGroupCoordinator.addColorChangeListener(colorChangeListener);
-
-               for (int idx = 0; idx < imageSelectPanels.size(); idx++) {
-                       ImageSelectPanel imageSelectPanel = imageSelectPanels.get(idx);
-                       final PartsCategory partsCategory = imageSelectPanel.getPartsCategory();
-                       final ColorDialog colorDialog = new ColorDialog(this, partsCategory, colorGroups);
-                       colorGroupCoordinator.registerColorDialog(colorDialog);
-                       partsColorCoordinator.register(imageSelectPanel, colorDialog);
-                       final int curidx = idx;
-                       imageSelectPanel.addImageSelectListener(new ImageSelectPanelListener() {
-                               public void onChangeColor(ImageSelectPanelEvent event) {
-                                                       WindowAdjustLocationSupport.alignRight(
-                                                                       MainFrame.this, colorDialog, curidx, false);
-                                       colorDialog.setVisible(!colorDialog.isVisible());
-                               }
-                               public void onPreferences(ImageSelectPanelEvent event) {
-                                       // do nothing. (not supported)
-                               }
-                               public void onChange(ImageSelectPanelEvent event) {
-                                       MainFrame.this.requestPreview();
-                               }
-                               public void onSelectChange(ImageSelectPanelEvent event) {
-                                       // do nothing.
-                               }
-                               public void onTitleClick(ImageSelectPanelEvent event) {
-                                       PartsCategory partsCategory = (event != null) ?
-                                                       event.getImageSelectPanel().getPartsCategory() : null;
-                                       MainFrame.this.onClickPartsCategoryTitle(partsCategory, false);
-                               }
-                               public void onTitleDblClick(ImageSelectPanelEvent event) {
-                                       PartsCategory partsCategory = (event != null) ?
-                                                       event.getImageSelectPanel().getPartsCategory() : null;
-                                       MainFrame.this.onClickPartsCategoryTitle(partsCategory, true);
-                               }
-                       });
-                       imageSelectPanel.addAncestorListener(new AncestorListener() {
-                               public void ancestorAdded(AncestorEvent event) {
-                               }
-                               public void ancestorMoved(AncestorEvent event) {
-                               }
-                               public void ancestorRemoved(AncestorEvent event) {
-                                       // パネルもしくは、その親が削除されたときにダイアログも非表示とする。
-                                       colorDialog.setVisible(false);
-                               }
-                       });
-               }
-
-               // 全パーツのロード
-               partsSelectionManager.loadParts();
-
-               // 保存されているワーキングセットを復元する.
-               // 復元できなかった場合はパーツセットを初期選択する.
-               if ( !loadWorkingSet()) {
-                       // ワーキングセットがない場合は
-                       // デフォルトのウィンドウ位置とサイズ
-                       setDefaultWindowLocation();
-
-                       // デフォルトのパーツセットの表示
-                       if (showDefaultParts(true)) {
-                               requestPreview();
-                       }
-               }
-
-               // 選択されているパーツを見える状態にする
-               scrollToSelectedParts();
-
-               // 非同期イメージローダの処理開始
-               if (!imageBuilder.isAlive()) {
-                       imageBuilder.start();
-               }
-
-               // ドロップターゲットの設定
-               new DropTarget(imgSelectPanelsPanelSp, new FileDropTarget() {
-                       @Override
-                       protected void onDropFiles(final List<File> dropFiles) {
-                               if (dropFiles == null || dropFiles.isEmpty()) {
-                                       return;
-                               }
-                               // インポートダイアログを開く.
-                               // ドロップソースの処理がブロッキングしないように、
-                               // ドロップハンドラの処理を終了してからインポートダイアログが開くようにする.
-                               SwingUtilities.invokeLater(new Runnable() {
-                                       public void run() {
-                                               onImport(dropFiles);
-                                       }
-                               });
-                       }
-                       @Override
-                       protected void onException(Exception ex) {
-                               ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);
-                       }
-               });
-
-               // ディレクトリを監視し変更があった場合にパーツをリロードするリスナ
-               watchAgent.addPartsImageDirectoryWatchListener(new PartsImageDirectoryWatchListener() {
-                       public void detectPartsImageChange(PartsImageDirectoryWatchEvent e) {
-                               Runnable refreshJob = new Runnable() {
-                                       public void run() {
-                                               onDetectPartsImageChange();
-                                       }
-                               };
-                               if (SwingUtilities.isEventDispatchThread()) {
-                                       refreshJob.run();
-                               } else {
-                                       SwingUtilities.invokeLater(refreshJob);
-                               }
-                       }
-               });
-
-               // 監視が有効であれば、ディレクトリの監視をスタートする
-               if (appConfig.isEnableDirWatch() && characterData.isWatchDirectory()) {
-                       watchAgent.connect();
-               }
-
-               // パーツカテゴリの自動縮小が設定されている場合
-               minimizeMode = false;
-               if (appConfig.isEnableAutoShrinkPanel()) {
-                       onClickPartsCategoryTitle(null, true);
-               }
-
-               // コンポーネントの再構築の場合
-               if (oldCd != null) {
-                       validate();
-               }
-       }
-
-       /**
-        * パーツが変更されたことを検知した場合.<br>
-        * パーツデータをリロードし、各カテゴリのパーツ一覧を再表示させ、プレビューを更新する.<br>
-        */
-       protected void onDetectPartsImageChange() {
-               try {
-                       reloadPartsAndFavorites(null, true);
-
-               } catch (IOException ex) {
-                       logger.log(Level.SEVERE, "parts reload failed. " + characterData, ex);
-               }
-       }
-
-       /**
-        * すべてのカテゴリのリストで選択中のアイテムが見えるようにスクロールする.
-        */
-       protected void scrollToSelectedParts() {
-               partsSelectionManager.scrollToSelectedParts();
-       }
-
-       /**
-        * 指定したパーツカテゴリ以外のパーツ選択パネルを最小化する.
-        *
-        * @param partsCategory
-        *            パーツカテゴリ、nullの場合は全て最小化する.
-        * @param dblClick
-        *            ダブルクリック
-        */
-       protected void onClickPartsCategoryTitle(PartsCategory partsCategory, boolean dblClick) {
-               if (logger.isLoggable(Level.FINE)) {
-                       logger.log(Level.FINE, "onClickPartsCategoryTitle category="
-                                       + partsCategory + "/clickCount=" + dblClick);
-               }
-               if (dblClick) {
-                       minimizeMode = !minimizeMode;
-                       if (!minimizeMode) {
-                               partsSelectionManager.setMinimizeModeIfOther(null, false);
-                               return;
-                       }
-               }
-               if (minimizeMode) {
-                       if (partsSelectionManager.isNotMinimizeModeJust(partsCategory)) {
-                               partsSelectionManager.setMinimizeModeIfOther(null, true); // 全部縮小
-
-                       } else {
-                               partsSelectionManager.setMinimizeModeIfOther(partsCategory, true);
-                               if (partsCategory != null) {
-                                       // 対象のパネルがスクロールペイン内に見える用にスクロールする.
-                                       // スクロールバーの位置指定などの座標系の操作は「要求」であり、実際に適用されるまで本当の位置は分らない。
-                                       // よって以下の処理は非同期に行い、先に座標を確定させたものに対して行う必要がある。
-                                       final ImageSelectPanel panel = imageSelectPanels.findByPartsCategory(partsCategory);
-                                       SwingUtilities.invokeLater(new Runnable() {
-                                               public void run() {
-                                                       final Point pt = panel.getLocation();
-                                                       JViewport viewPort = imgSelectPanelsPanelSp.getViewport();
-                                                       viewPort.setViewPosition(pt);
-                                                       viewPort.revalidate();
-                                               }
-                                       });
-                               }
-                       }
-               }
-       }
-
-       /**
-        * デフォルトパーツを選択する.<br>
-        * デフォルトパーツがなければお気に入りの最初のものを選択する.<br>
-        * それもなければ空として表示する.<br>
-        * パーツの適用に失敗した場合はfalseを返します.(例外は返されません.)<br>
-        *
-        * @param force
-        *            すでに選択があっても選択しなおす場合はtrue、falseの場合は選択があれば何もしない.
-        * @return パーツ選択された場合。force=trueの場合はエラーがなければ常にtrueとなります。
-        */
-       protected boolean showDefaultParts(boolean force) {
-               try {
-                       if (!force) {
-                               // 現在選択中のパーツを取得する.(なければ空)
-                               PartsSet sel = partsSelectionManager.createPartsSet();
-                               if (!sel.isEmpty()) {
-                                       // 強制選択でない場合、すでに選択済みのパーツがあれば何もしない.
-                                       return false;
-                               }
-                       }
-
-                       // デフォルトのパーツセットを取得する
-                       String defaultPresetId = characterData.getDefaultPartsSetId();
-                       PartsSet partsSet = null;
-                       if (defaultPresetId != null) {
-                               partsSet = characterData.getPartsSets().get(defaultPresetId);
-                       }
-
-                       // デフォルトのパーツセットがなければ、お気に入りの最初を選択する.
-                       if (partsSet == null) {
-                               List<PartsSet> partssets = getPartsSetList();
-                               if (!partssets.isEmpty()) {
-                                       partsSet = partssets.get(0);
-                               }
-                       }
-
-                       // パーツセットがあれば、それを表示要求する.
-                       // パーツセットがなければカラーダイアログを初期化するのみ
-                       if (partsSet == null) {
-                               partsColorCoordinator.initColorDialog();
-
-                       } else {
-                               selectPresetParts(partsSet);
-                       }
-
-               } catch (Exception ex) {
-                       logger.log(Level.WARNING, "パーツのデフォルト適用に失敗しました。", ex);
-                       return false;
-               }
-               return true;
-       }
-
-       /**
-        * プリセットを適用しキャラクターイメージを再構築します.<br>
-        * 実行時エラーは画面のレポートされます.<br>
-        *
-        * @param presetParts
-        *            パーツセット, nullの場合は何もしない.
-        */
-       protected void selectPresetParts(PartsSet presetParts) {
-               if (presetParts == null) {
-                       return;
-               }
-               try {
-                       // 最後に使用したプリセットとして記憶する.
-                       lastUsePresetParts = presetParts;
-                       // プリセットパーツで選択を変える
-                       partsSelectionManager.selectPartsSet(presetParts);
-                       // カラーパネルを選択されているアイテムをもとに再設定する
-                       partsColorCoordinator.initColorDialog();
-                       // 再表示
-                       requestPreview();
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * プリセットとお気に入りを表示順に並べて返す.
-        *
-        * @return プリセットとお気に入りのリスト(表示順)
-        */
-       protected List<PartsSet> getPartsSetList() {
-               ArrayList<PartsSet> partssets = new ArrayList<PartsSet>();
-               partssets.addAll(characterData.getPartsSets().values());
-               Collections.sort(partssets, PartsSet.DEFAULT_COMPARATOR);
-               return partssets;
-       }
-
-       protected static final class TreeLeaf implements Comparable<TreeLeaf> {
-
-               public enum TreeLeafType {
-                       NODE, LEAF
-               }
-
-               private String name;
-
-               private TreeLeafType typ;
-
-               public TreeLeaf(TreeLeafType typ, String name) {
-                       if (name == null) {
-                               name = "";
-                       }
-                       this.typ = typ;
-                       this.name = name;
-               }
-
-               public String getName() {
-                       return name;
-               }
-
-               public TreeLeafType getTyp() {
-                       return typ;
-               }
-
-               @Override
-               public boolean equals(Object obj) {
-                       if (obj != null && obj instanceof TreeLeaf) {
-                               TreeLeaf o = (TreeLeaf) obj;
-                               return typ == o.typ && name.equals(o.name);
-                       }
-                       return false;
-               }
-
-               @Override
-               public int hashCode() {
-                       return typ.hashCode() ^ name.hashCode();
-               }
-
-               public int compareTo(TreeLeaf o) {
-                       int ret = name.compareTo(o.name);
-                       if (ret == 0) {
-                               ret = (typ.ordinal() - o.typ.ordinal());
-                       }
-                       return ret;
-               }
-
-               @Override
-               public String toString() {
-                       return name;
-               }
-       }
-
-       protected TreeMap<TreeLeaf, Object> buildFavoritesItemTree(
-                       List<PartsSet> partssets) {
-               if (partssets == null) {
-                       partssets = Collections.emptyList();
-               }
-               TreeMap<TreeLeaf, Object> favTree = new TreeMap<TreeLeaf, Object>();
-               for (PartsSet partsSet : partssets) {
-                       String flatname = partsSet.getLocalizedName();
-                       String[] tokens = flatname.split("\\|");
-                       if (tokens.length == 0) {
-                               continue;
-                       }
-
-                       TreeMap<TreeLeaf, Object> r = favTree;
-                       for (int idx = 0; idx < tokens.length - 1; idx++) {
-                               String name = tokens[idx];
-                               TreeLeaf leafName = new TreeLeaf(TreeLeaf.TreeLeafType.NODE,
-                                               name);
-                               @SuppressWarnings("unchecked")
-                               TreeMap<TreeLeaf, Object> n = (TreeMap<TreeLeaf, Object>) r
-                                               .get(leafName);
-                               if (n == null) {
-                                       n = new TreeMap<TreeLeaf, Object>();
-                                       r.put(leafName, n);
-                               }
-                               r = n;
-                       }
-                       String lastName = tokens[tokens.length - 1];
-                       TreeLeaf lastLeafName = new TreeLeaf(TreeLeaf.TreeLeafType.LEAF,
-                                       lastName);
-                       @SuppressWarnings("unchecked")
-                       List<PartsSet> leafValue = (List<PartsSet>) r.get(lastLeafName);
-                       if (leafValue == null) {
-                               leafValue = new ArrayList<PartsSet>();
-                               r.put(lastLeafName, leafValue);
-                       }
-                       leafValue.add(partsSet);
-               }
-               return favTree;
-       }
-
-       protected interface FavoriteMenuItemBuilder {
-               JMenuItem createFavoriteMenuItem(String name, PartsSet partsSet);
-               JMenu createSubMenu(String name);
-       }
-
-       private void buildFavoritesMenuItems(List<JMenuItem> menuItems,
-                       FavoriteMenuItemBuilder favMenuItemBuilder,
-                       TreeMap<TreeLeaf, Object> favTree) {
-               for (Map.Entry<TreeLeaf, Object> entry : favTree.entrySet()) {
-                       TreeLeaf treeLeaf = entry.getKey();
-                       String name = treeLeaf.getName();
-                       if (treeLeaf.getTyp() == TreeLeaf.TreeLeafType.LEAF) {
-                               // 葉ノードには、JMenuItemを設定する.
-                               @SuppressWarnings("unchecked")
-                               List<PartsSet> leafValue = (List<PartsSet>) entry.getValue();
-                               for (final PartsSet partsSet : leafValue) {
-                                       JMenuItem favoriteMenu = favMenuItemBuilder
-                                                       .createFavoriteMenuItem(name, partsSet);
-                                       menuItems.add(favoriteMenu);
-                               }
-
-                       } else if (treeLeaf.getTyp() == TreeLeaf.TreeLeafType.NODE) {
-                               // 枝ノードは、サブメニューを作成し、子ノードを設定する
-                               @SuppressWarnings("unchecked")
-                               TreeMap<TreeLeaf, Object> childNode = (TreeMap<TreeLeaf, Object>) entry
-                                               .getValue();
-                               JMenu subMenu = favMenuItemBuilder.createSubMenu(name);
-                               menuItems.add(subMenu);
-                               ArrayList<JMenuItem> subMenuItems = new ArrayList<JMenuItem>();
-                               buildFavoritesMenuItems(subMenuItems, favMenuItemBuilder, childNode);
-                               for (JMenuItem subMenuItem : subMenuItems) {
-                                       subMenu.add(subMenuItem);
-                               }
-
-                       } else {
-                               throw new RuntimeException("unknown type: " + treeLeaf);
-                       }
-               }
-       }
-
-       /**
-        * お気に入りのJMenuItemを作成するファンクションオブジェクト
-        */
-       private FavoriteMenuItemBuilder favMenuItemBuilder = new FavoriteMenuItemBuilder() {
-               private MenuBuilder menuBuilder = new MenuBuilder();
-
-               /**
-                * お気に入りメニューの作成
-                */
-               public JMenuItem createFavoriteMenuItem(final String name,
-                               final PartsSet partsSet) {
-                       JMenuItem favoriteMenu = menuBuilder.createJMenuItem();
-                       favoriteMenu.setName(partsSet.getPartsSetId());
-                       favoriteMenu.setText(name);
-                       if (partsSet.isPresetParts()) {
-                               Font font = favoriteMenu.getFont();
-                               favoriteMenu.setFont(font.deriveFont(Font.BOLD));
-                       }
-                       favoriteMenu.addActionListener(new ActionListener() {
-                               public void actionPerformed(ActionEvent e) {
-                                       selectPresetParts(partsSet);
-                               }
-                       });
-
-                       // メニューアイテム上でマウスホイールを動かした場合は上下にスクロールさせる.
-                       // (ただし、OSXのスクリーンメニュー使用時は無視する.)
-                       addMouseWheelListener(favoriteMenu);
-
-                       return favoriteMenu;
-               }
-
-               /**
-                * サブメニューの作成
-                */
-               public JMenu createSubMenu(String name) {
-                       JMenu menu = menuBuilder.createJMenu();
-                       menu.setText(name);
-
-                       // メニューアイテム上でマウスホイールを動かした場合は上下にスクロールさせる.
-                       // (ただし、OSXのスクリーンメニュー使用時は無視する.)
-                       addMouseWheelListener(menu);
-
-                       return menu;
-               }
-
-               /**
-                * メニューアイテム上でホイールを上下させたときにメニューをスクロールさせるためのホイールハンドラを設定する.
-                *
-                * @param favoriteMenu
-                */
-               protected void addMouseWheelListener(final JMenuItem favoriteMenu) {
-                       if (JScrollableMenu.isScreenMenu()) {
-                               return;
-                       }
-                       favoriteMenu.addMouseWheelListener(new MouseWheelListener() {
-                               public void mouseWheelMoved(MouseWheelEvent e) {
-                                       int rotation = e.getWheelRotation();
-                                       JPopupMenu popupMenu = (JPopupMenu) favoriteMenu
-                                                       .getParent();
-                                       JMenu parentMenu = (JMenu) popupMenu.getInvoker();
-                                       if (parentMenu != null
-                                                       && parentMenu instanceof JScrollableMenu) {
-                                               final JScrollableMenu favMenu = (JScrollableMenu) parentMenu;
-                                               favMenu.doScroll(rotation < 0);
-                                       }
-                                       e.consume();
-                               }
-                       });
-               }
-       };
-
-       /**
-        * お気に入りメニューが開いたとき
-        *
-        * @param menu
-        */
-       protected void onSelectedFavoriteMenu(JMenu menu) {
-               // 表示順にソート
-               List<PartsSet> partssets = getPartsSetList();
-               TreeMap<TreeLeaf, Object> favTree = buildFavoritesItemTree(partssets);
-
-               // メニューの再構築
-               ArrayList<JMenuItem> favoritesMenuItems = new ArrayList<JMenuItem>();
-               buildFavoritesMenuItems(favoritesMenuItems, favMenuItemBuilder, favTree);
-
-               if (menu instanceof JScrollableMenu) {
-                       // スクロールメニューの場合
-                       JScrollableMenu favMenu = (JScrollableMenu) menu;
-
-                       // スクロールメニューの初期化
-                       favMenu.initScroller();
-
-                       // スクロールメニューアイテムの設定
-                       favMenu.setScrollableItems(favoritesMenuItems);
-
-                       // 高さを補正する
-                       // お気に入りメニューが選択された場合、
-                       // お気に入りアイテム一覧を表示するよりも前に
-                       // 表示可能なアイテム数を現在のウィンドウの高さから算定する.
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       Dimension scrsiz = tk.getScreenSize();
-                       int height = scrsiz.height; // MainFrame.this.getHeight();
-                       favMenu.adjustMaxVisible(height);
-                       logger.log(Level.FINE,
-                                       "scrollableMenu maxVisible=" + favMenu.getMaxVisible());
-
-               } else {
-                       // 通常メニューの場合
-                       // 既存メニューの位置をセパレータより判断する.
-                       int mx = menu.getMenuComponentCount();
-                       int separatorIdx = -1;
-                       for (int idx = 0; idx < mx; idx++) {
-                               Component item = menu.getMenuComponent(idx);
-                               if (item instanceof JSeparator) {
-                                       separatorIdx = idx;
-                                       break;
-                               }
-                       }
-                       // 既存メニューの削除
-                       if (separatorIdx > 0) {
-                               while (menu.getMenuComponentCount() > separatorIdx + 1) {
-                                       menu.remove(separatorIdx + 1);
-                               }
-                       }
-
-                       // お気に入りアイテムのメニューを登録する.
-                       for (JMenuItem menuItem : favoritesMenuItems) {
-                               menu.add(menuItem);
-                       }
-               }
-
-       }
-
-       /**
-        * ヘルプメニューを開いたときにお勧めメニューを構築する.
-        *
-        * @param menu
-        */
-       protected void onSelectedRecommendationMenu(JMenu mnuRecomendation) {
-               // 現在のお勧めメニューを一旦削除
-               while (mnuRecomendation.getMenuComponentCount() > 0) {
-                       mnuRecomendation.remove(0);
-               }
-
-               // お勧めリンクの定義がない場合はデフォルトを用いる.(明示的な空の場合は何もしない.)
-               CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
-               persist.compensateRecommendationList(characterData);
-
-               // お勧めリンクメニューを作成する.
-               List<RecommendationURL> recommendations = characterData.getRecommendationURLList();
-               if (recommendations != null) {
-                       MenuBuilder menuBuilder = new MenuBuilder();
-                       for (RecommendationURL recommendation : recommendations) {
-                               String displayName = recommendation.getDisplayName();
-                               String url = recommendation.getUrl();
-
-                               JMenuItem mnuItem = menuBuilder.createJMenuItem();
-                               mnuItem.setText(displayName);
-                               mnuItem.addActionListener(
-                                               DesktopUtilities.createBrowseAction(MainFrame.this, url, displayName)
-                                               );
-                               mnuRecomendation.add(mnuItem);
-                       }
-               }
-
-               // お勧めリンクメニューのリストがnullでなく空でもない場合は有効、そうでなければ無効にする.
-               mnuRecomendation.setEnabled(recommendations != null && !recommendations.isEmpty());
-       }
-
-
-       /**
-        * 最後に選択されたお気に入りと同じ構成であれば、 このお気に入りの名前をプレビューペインのタイトルに設定する.<br>
-        * そうでなければデフォルトのパーツセット名(no titleとか)を表示する.<br>
-        * 色情報が異なる場合に末尾に「*」マークがつけられる.<br>
-        *
-        * @param requestPartsSet
-        *            表示するパーツセット(名前は設定されていなくて良い。お気に入り側を使うので。), nullの場合はデフォルトのパーツ名
-        */
-       protected void showPresetName(PartsSet requestPartsSet) {
-               String title = getSuggestPartsSetName(requestPartsSet, true);
-               if (title == null) {
-                       title = defaultPartsSetTitle;
-               }
-               previewPane.setTitle(title);
-       }
-
-       /**
-        * パーツセット名を推定する.<br>
-        * 最後に選択されたお気に入りと同じ構成であれば、 このお気に入りの名前を返す.<br>
-        * お気に入りが選択されていないか構成が異なる場合、お気に入りに名前がない場合はnullを返す.<br>
-        *
-        * @param requestPartsSet
-        *            表示するパーツセット(名前は設定されていなくて良い。お気に入り側を使うので。)
-        * @param markColorChange
-        *            色情報が異なる場合に末尾に「*」マークをつける場合はtrue
-        */
-       private String getSuggestPartsSetName(PartsSet requestPartsSet, boolean markColorChange) {
-               String partsSetTitle = null;
-               if (lastUsePresetParts != null &&
-                               PartsSet.isSameStructure(requestPartsSet, lastUsePresetParts)) {
-                       partsSetTitle = lastUsePresetParts.getLocalizedName();
-                       if (markColorChange && !PartsSet.isSameColor(requestPartsSet, lastUsePresetParts)) {
-                               if (partsSetTitle != null) {
-                                       partsSetTitle += "*";
-                               }
-                       }
-               }
-               if (partsSetTitle != null && partsSetTitle.trim().length() > 0) {
-                       return partsSetTitle;
-               }
-               return null;
-       }
-
-       /**
-        * プレビューの更新を要求する. 更新は非同期に行われる.
-        */
-       protected void requestPreview() {
-               if (!characterData.isValid()) {
-                       return;
-               }
-
-               // 選択されているパーツの各イメージを取得しレイヤー順に並び替えて合成する.
-               // 合成は別スレッドにて非同期に行われる.
-               // リクエストは随時受け付けて、最新のリクエストだけが処理される.
-               // (処理がはじまる前に新しいリクエストで上書きされた場合、前のリクエストは単に捨てられる.)
-               imageBuilder.requestJob(new ImageBuildJobAbstractAdaptor(characterData) {
-
-                                       /**
-                                        * 構築するパーツセット情報
-                                        */
-                       private PartsSet requestPartsSet;
-
-                                       /**
-                                        * 非同期のイメージ構築要求の番号.<br>
-                                        */
-                       private long ticket;
-
-                       @Override
-                       public void onQueueing(long ticket) {
-                               this.ticket = ticket;
-                               previewPane.setLoadingRequest(ticket);
-                       }
-                       @Override
-                       public void buildImage(ImageOutput output) {
-                                               // 合成結果のイメージを引数としてイメージビルダから呼び出される.
-                               final BufferedImage img = output.getImageOutput();
-                               Runnable refreshJob = new Runnable() {
-                                       public void run() {
-                                               previewPane.setPreviewImage(img);
-                                               previewPane.setLoadingComplete(ticket);
-                                               showPresetName(requestPartsSet);
-                                       }
-                               };
-                               if (SwingUtilities.isEventDispatchThread()) {
-                                       refreshJob.run();
-                               } else {
-                                       try {
-                                               SwingUtilities.invokeAndWait(refreshJob);
-                                       } catch (Exception ex) {
-                                               logger.log(Level.WARNING, "build image failed.", ex);
-                                       }
-                               }
-                       }
-                       @Override
-                       public void handleException(final Throwable ex) {
-                                               // 合成中に例外が発生した場合、イメージビルダから呼び出される.
-                               Runnable showExceptionJob = new Runnable() {
-                                       public void run() {
-                                               ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);
-                                       }
-                               };
-                               if (SwingUtilities.isEventDispatchThread()) {
-                                       showExceptionJob.run();
-                               } else {
-                                       SwingUtilities.invokeLater(showExceptionJob);
-                               }
-                       }
-                       @Override
-                       protected PartsSet getPartsSet() {
-                                               // 合成できる状態になった時点でイメージビルダから呼び出される.
-                               final PartsSet[] result = new PartsSet[1];
-                               Runnable collectPartsSetJob = new Runnable() {
-                                       public void run() {
-                                               PartsSet partsSet = partsSelectionManager.createPartsSet();
-                                               result[0] = partsSet;
-                                       }
-                               };
-                               if (SwingUtilities.isEventDispatchThread()) {
-                                       collectPartsSetJob.run();
-                               } else {
-                                       try {
-                                                               // スレッドによるSwingのイベントディスパッチスレッド以外からの呼び出しの場合、
-                                                               // Swingディスパッチスレッドでパーツの選択状態を取得する.
-                                               SwingUtilities.invokeAndWait(collectPartsSetJob);
-
-                                       } catch (InvocationTargetException e) {
-                                               throw new RuntimeException(e.getMessage(), e);
-                                       } catch (InterruptedException e) {
-                                               throw new RuntimeException("interrupted:" + e, e);
-                                       }
-                               }
-                               if (logger.isLoggable(Level.FINE)) {
-                                       logger.log(Level.FINE, "preview: " + result[0]);
-                               }
-                               requestPartsSet = result[0];
-                               return requestPartsSet;
-                       }
-               });
-       }
-
-       /**
-        * プロファイルを開く
-        */
-       protected void onOpenProfile() {
-               try {
-                       MainFrame main2 = ProfileListManager.openProfile(this);
-                       if (main2 != null) {
-                               main2.showMainFrame();
-                       }
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * 背景色を変更する.
-        */
-       protected void onChangeBgColor() {
-               getJMenuBar().setEnabled(false);
-               try {
-                       Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
-                                       .getLocalizedProperties(STRINGS_RESOURCE);
-
-                       Color color = wallpaperInfo.getBackgroundColor();
-                       color = JColorChooser.showDialog(this, strings.getProperty("chooseBgColor"), color);
-                       if (color != null) {
-                               applyBackgroundColorOnly(color);
-                       }
-               } finally {
-                       getJMenuBar().setEnabled(true);
-               }
-       }
-
-       /**
-        * 壁紙を変更する.
-        */
-       protected void onChangeWallpaper() {
-               try {
-                       WallpaperDialog wallpaperDialog = new WallpaperDialog(this);
-
-                       // 最後に使用した壁紙情報でダイアログを設定する.
-                       wallpaperDialog.setWallpaperInfo(this.wallpaperInfo);
-
-                       // 壁紙情報を設定するモーダルダイアログを開く
-                       WallpaperInfo wallpaperInfo = wallpaperDialog.showDialog();
-                       if (wallpaperInfo == null) {
-                               return;
-                       }
-
-                       // 壁紙情報を保存し、その情報をもとに背景を再描画する.
-                       applyWallpaperInfo(wallpaperInfo, false);
-
-               } catch (WallpaperFactoryException ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-
-               } catch (RuntimeException ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * 背景色のみ変更し、背景を再描画する.<br>
-        * 壁紙情報全体の更新よりも効率化するためのメソッドである.<br>
-        *
-        * @param bgColor
-        *            背景色
-        */
-       protected void applyBackgroundColorOnly(Color bgColor) {
-               wallpaperInfo.setBackgroundColor(bgColor);
-               previewPane.getWallpaper()
-                       .setBackgroundColor(wallpaperInfo.getBackgroundColor());
-       }
-
-       /**
-        * 壁紙情報を保存し、その情報をもとに背景を再描画する.<br>
-        * ignoreErrorがtrueである場合、適用に失敗した場合はログに記録するのみで、 壁紙情報は保存されず、壁紙も更新されない.<br>
-        *
-        * @param wallpaperInfo
-        *            壁紙情報、null不可
-        * @param ignoreError
-        *            失敗を無視する場合
-        * @throws IOException
-        *             失敗
-        */
-       protected void applyWallpaperInfo(WallpaperInfo wallpaperInfo, boolean ignoreError) throws WallpaperFactoryException {
-               if (wallpaperInfo == null) {
-                       throw new IllegalArgumentException();
-               }
-               // 壁紙情報から壁紙インスタンスを生成する.
-               WallpaperFactory wallpaperFactory = WallpaperFactory.getInstance();
-               Wallpaper wallpaper = null;
-
-               try {
-                       // 壁紙情報の構築時に問題が発生した場合、
-                       // 回復処理をして継続するかエラーとするか?
-                       WallpaperFactoryErrorRecoverHandler handler = null;
-                       if (ignoreError) {
-                               handler = new WallpaperFactoryErrorRecoverHandler();
-                       }
-
-                       // 壁紙情報
-                       wallpaper = wallpaperFactory.createWallpaper(wallpaperInfo, handler);
-
-               } catch (WallpaperFactoryException ex) {
-                       logger.log(Level.WARNING, "壁紙情報の適用に失敗しました。", ex);
-                       if ( !ignoreError) {
-                               throw ex;
-                       }
-
-               } catch (RuntimeException ex) {
-                       logger.log(Level.WARNING, "壁紙情報の適用に失敗しました。", ex);
-                       if ( !ignoreError) {
-                               throw ex;
-                       }
-               }
-
-               if (wallpaper == null) {
-                       return;
-               }
-
-               // 壁紙を更新する.
-               previewPane.setWallpaper(wallpaper);
-
-               // 壁紙情報として記憶する.
-               this.wallpaperInfo = wallpaperInfo;
-       }
-
-       /**
-        * プレビューしている画像をファイルに保存する。 サポートしているのはPNG/JPEGのみ。
-        */
-       protected void onSavePicture() {
-               Toolkit tk = Toolkit.getDefaultToolkit();
-               BufferedImage img = previewPane.getPreviewImage();
-               Color imgBgColor = wallpaperInfo.getBackgroundColor();
-               if (img == null) {
-                       tk.beep();
-                       return;
-               }
-
-               try {
-                       // 出力オプションの調整
-                       OutputOption outputOption = imageSaveHelper.getOutputOption();
-                       outputOption.setZoomFactor(previewPane.getZoomFactor());
-                       outputOption.changeRecommend();
-                       imageSaveHelper.setOutputOption(outputOption);
-
-                       // ファイルダイアログ表示
-                       File outFile = imageSaveHelper.showSaveFileDialog(this);
-                       if (outFile == null) {
-                               return;
-                       }
-                       logger.log(Level.FINE, "savePicture: " + outFile);
-                       logger.log(Level.FINE, "outputOption: " + outputOption);
-
-                       // 画像のファイルへの出力
-                       StringBuilder warnings = new StringBuilder();
-
-                       setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
-                       try {
-                               imageSaveHelper.savePicture(img, imgBgColor, outFile, warnings);
-
-                       } finally {
-                               setCursor(Cursor.getDefaultCursor());
-                       }
-                       if (warnings.length() > 0) {
-                               JOptionPane.showMessageDialog(this, warnings.toString(), "WARNINGS", JOptionPane.WARNING_MESSAGE);
-                       }
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * 伺か用PNG/PNAの出力.
-        */
-       protected void onSaveAsUkagaka() {
-               BufferedImage img = previewPane.getPreviewImage();
-               Color bgColor = wallpaperInfo.getBackgroundColor();
-               if (img == null) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-
-               try {
-                       ukagakaImageSaveHelper.save(this, img, bgColor);
-
-               } catch (IOException ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * 伺か用PNG/PNAの変換
-        */
-       protected void onConvertUkagaka() {
-               try {
-                       Color colorKey = wallpaperInfo.getBackgroundColor();
-                       ukagakaImageSaveHelper.convertChooseFiles(this, colorKey);
-
-               } catch (IOException ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * プロファイルの場所を開く
-        */
-       protected void onBrowseProfileDir() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-               try {
-                       DesktopUtilities.browseBaseDir(characterData.getDocBase());
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * このプロファイルを編集する.
-        */
-       protected void onEditProfile() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-               try {
-                       CharacterData cd = this.characterData;
-                       CharacterData newCd = ProfileListManager.editProfile(this, cd);
-                       if (newCd != null) {
-                               CharacterDataChangeObserver.getDefault()
-                                               .notifyCharacterDataChange(this, newCd, true, true);
-                       }
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * パーツの管理ダイアログを開く.<br>
-        */
-       protected void onManageParts() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-
-               PartsManageDialog mrgDlg = new PartsManageDialog(this, characterData);
-               mrgDlg.setVisible(true);
-
-               if (mrgDlg.isUpdated()) {
-                       // パーツ管理情報が更新された場合、
-                       // パーツデータをリロードする.
-                       if (characterData.reloadPartsData()) {
-                               partsSelectionManager.loadParts();
-                               requestPreview();
-                       }
-               }
-       }
-
-       /**
-        * 「パーツ検索」ダイアログを開く.<br>
-        * すでに開いているダイアログがあれば、それにフォーカスを当てる.<br>
-        */
-       protected void openSearchDialog() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-
-               if (lastUseSearchPartsDialog != null) {
-                       // 開いているダイアログがあれば、それにフォーカスを当てる.
-                       if (lastUseSearchPartsDialog.isDisplayable() && lastUseSearchPartsDialog.isVisible()) {
-                               lastUseSearchPartsDialog.requestFocus();
-                               return;
-                       }
-               }
-
-               SearchPartsDialog searchPartsDlg = new SearchPartsDialog(this, characterData, partsSelectionManager);
-               WindowAdjustLocationSupport.alignRight(this, searchPartsDlg, 0, true);
-               searchPartsDlg.setVisible(true);
-               lastUseSearchPartsDialog = searchPartsDlg;
-       }
-
-       /**
-        * 「パーツ検索」ダイアログを閉じる.<br>
-        */
-       protected void closeSearchDialog() {
-               lastUseSearchPartsDialog = null;
-               for (SearchPartsDialog dlg : SearchPartsDialog.getDialogs()) {
-                       if (dlg != null && dlg.isDisplayable() && dlg.getParent() == this) {
-                               dlg.dispose();
-                       }
-               }
-       }
-
-       /**
-        * 「お気に入りの管理」ダイアログを閉じる
-        */
-       protected void closeManageFavoritesDialog() {
-               if (lastUseManageFavoritesDialog != null) {
-                       if (lastUseManageFavoritesDialog.isDisplayable()) {
-                               lastUseManageFavoritesDialog.dispose();
-                       }
-                       lastUseManageFavoritesDialog = null;
-               }
-       }
-
-       /**
-        * 「パーツのランダム選択ダイアログ」を閉じる
-        */
-       protected void closePartsRandomChooserDialog() {
-               if (lastUsePartsRandomChooserDialog != null) {
-                       if (lastUsePartsRandomChooserDialog.isDisplayable()) {
-                               lastUsePartsRandomChooserDialog.dispose();
-                       }
-                       lastUsePartsRandomChooserDialog = null;
-               }
-       }
-
-       /**
-        * クリップボードにコピー
-        *
-        * @param screenImage
-        *            スクリーンイメージ
-        */
-       protected void onCopy(boolean screenImage) {
-               try {
-                       BufferedImage img = previewPane.getPreviewImage();
-                       if (img == null) {
-                               Toolkit tk = Toolkit.getDefaultToolkit();
-                               tk.beep();
-                               return;
-                       }
-
-                       if (screenImage) {
-                               // 表示している内容をそのままコピーする.
-                               img = previewPane.getScreenImage();
-                       }
-
-                       Color imgBgColor = wallpaperInfo.getBackgroundColor();
-                       ClipboardUtil.setImage(img, imgBgColor);
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * アプリケーションの設定ダイアログを開く
-        */
-       public void onPreferences() {
-               AppConfigDialog appConfigDlg = new AppConfigDialog(this);
-               appConfigDlg.setVisible(true);
-       }
-
-       /**
-        * 新規モードでインポートウィザードを実行する.<br>
-        */
-       protected void onImportNew() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-
-               try {
-                       // インポートウィザードの実行(新規モード)
-                       ImportWizardDialog importWizDialog = new ImportWizardDialog(this, null, null);
-                       importWizDialog.setVisible(true);
-                       int exitCode = importWizDialog.getExitCode();
-                       if (exitCode == ImportWizardDialog.EXIT_PROFILE_CREATED) {
-                               CharacterData cd = importWizDialog.getImportedCharacterData();
-                               if (cd != null && cd.isValid()) {
-                                       // インポートしたキャラクターデータのプロファイルを開く.
-                                       setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
-                                       try {
-                                               MainFrame mainFrame = ProfileListManager.openProfile(cd);
-                                               mainFrame.setVisible(true);
-
-                                       } finally {
-                                               setCursor(Cursor.getDefaultCursor());
-                                       }
-                               }
-                       }
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * 現在のプロファイルに対するインポートウィザードを実行する.<br>
-        * インポートが実行された場合は、パーツをリロードする.<br>
-        * インポートウィザード表示中は監視スレッドは停止される.<br>
-        *
-        * @param initFile
-        *            アーカイブファィルまたはディレクトリ、指定がなければnull
-        */
-       protected void onImport(List<File> initFiles) {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-
-               try {
-                       watchAgent.suspend();
-                       try {
-                               // インポートウィザードの実行
-                               ImportWizardDialog importWizDialog = new ImportWizardDialog(this, characterData, initFiles);
-                               importWizDialog.setVisible(true);
-
-                               if (importWizDialog.getExitCode() == ImportWizardDialog.EXIT_PROFILE_UPDATED) {
-                                       CharacterData importedCd = importWizDialog.getImportedCharacterData();
-                                       CharacterDataChangeObserver.getDefault()
-                                                       .notifyCharacterDataChange(this, importedCd,
-                                                                       false, true);
-                               }
-
-                       } finally {
-                               watchAgent.resume();
-                       }
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * パーツとお気に入りをリロードする.<br>
-        * まだロードされていない場合はあらたにロードする.<br>
-        * 引数newCdが指定されている場合は、現在のキャラクター定義の説明文を更新する.<br>
-        * (説明文の更新以外には使用されない.)<br>
-        *
-        * @param newCd
-        *            説明文更新のための更新されたキャラクターデータを指定する。null可
-        * @param forceRepaint
-        *            必ず再描画する場合
-        * @throws IOException
-        *             失敗
-        */
-       protected synchronized void reloadPartsAndFavorites(CharacterData newCd,
-                       boolean forceRepaint) throws IOException {
-               if (newCd != null) {
-                       // (インポート画面では説明文のみ更新するので、それだけ取得)
-                       characterData.setDescription(newCd.getDescription());
-               }
-
-               if ( !characterData.isPartsLoaded()) {
-                       // キャラクターデータが、まだ読み込まれていなければ読み込む.
-                       ProfileListManager.loadCharacterData(characterData);
-                       ProfileListManager.loadFavorites(characterData);
-                       partsSelectionManager.loadParts();
-
-               } else {
-                       // パーツデータをリロードする.
-                       if (characterData.reloadPartsData()) {
-                               partsSelectionManager.loadParts();
-                       }
-
-                       // お気に入りをリロードする.
-                       CharacterDataPersistent persiste = CharacterDataPersistent.getInstance();
-                       persiste.loadFavorites(characterData);
-
-                       // お気に入りが更新されたことを通知する.
-                       FavoritesChangeObserver.getDefault().notifyFavoritesChange(
-                                       MainFrame.this, characterData);
-               }
-
-               // 現在選択されているパーツセットがない場合はデフォルトのパーツセットを選択する.
-               if (showDefaultParts(false) || forceRepaint) {
-                       requestPreview();
-               }
-       }
-
-       protected void onExport() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-               ExportWizardDialog exportWizDlg = new ExportWizardDialog(this, characterData, previewPane.getPreviewImage());
-               exportWizDlg.setVisible(true);
-       }
-
-       protected void onResetColor() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-
-               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
-                               .getLocalizedProperties(STRINGS_RESOURCE);
-
-               if (JOptionPane.showConfirmDialog(this, strings.get("confirm.resetcolors"), strings.getProperty("confirm"),
-                               JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) {
-                       return;
-               }
-               characterData.getPartsColorManager().resetPartsColorInfo();
-               partsColorCoordinator.initColorDialog();
-               requestPreview();
-       }
-
-       /**
-        * プロファイルを閉じる.
-        */
-       protected void onCloseProfile() {
-               saveWorkingSet();
-               ProfileListManager.unregisterUsedCharacterData(characterData);
-
-               if (characterData.isValid()) {
-
-                       // 最後に使用したキャラクターデータとして記憶する.
-                       try {
-                               RecentDataPersistent recentPersist = RecentDataPersistent.getInstance();
-                               recentPersist.saveRecent(characterData);
-
-                       } catch (Exception ex) {
-                               logger.log(Level.WARNING, "recent data saving failed.", ex);
-                               // recent情報の記録に失敗しても致命的ではないので、これは無視する.
-                       }
-               }
-
-               // イメージビルダスレッド・ディレクトリ監視スレッドを停止する.
-               stopAgents();
-
-               // フレームウィンドウを破棄する.
-               dispose();
-
-               // 破棄されたことをロギングする.
-               logger.log(Level.FINE, "dispose mainframe.");
-       }
-
-       /**
-        * 開いている、すべてのプロファイルを閉じる.<br>
-        * (Mac OS Xのcmd+Qで閉じる場合などで使用される.)<br>
-        */
-       public static void closeAllProfiles() {
-               // ウィンドウが閉じられることでアクティブなフレームが切り替わる場合を想定し、
-               // 現在のアクティブなウィンドウをあらかじめ記憶しておく
-               MainFrame mainFrame = activedMainFrame;
-
-               // gcをかけてファイナライズを促進させる
-               SystemUtil.gc();
-
-               // ファイナライズされていないFrameのうち、ネイティブリソースと関連づけられている
-               // フレームについて、それがMainFrameのインスタンスであれば閉じる.
-               // ただし、現在アクティブなものは除く
-               for (Frame frame : JFrame.getFrames()) {
-                       try {
-                               if (frame.isDisplayable()) {
-                                       // ネイティブリソースと関連づけられているフレーム
-                                       if (frame instanceof MainFrame && frame != mainFrame) {
-                                               // MainFrameのインスタンスであるので閉じる処理が可能.
-                                               // (現在アクティブなメインフレームは最後に閉じるため、ここでは閉じない.)
-                                               ((MainFrame) frame).onCloseProfile();
-                                       }
-                               }
-
-                       } catch (Throwable ex) {
-                               logger.log(Level.SEVERE, "mainframe closing failed.", ex);
-                               // フレームを閉じるときに失敗した場合、通常、致命的問題だが
-                               // クローズ処理は継続しなければならない.
-                       }
-               }
-
-               // 現在アクティブなフレームを閉じる.
-               // 最後に閉じることで「最後に使ったプロファイル」として記憶させる.
-               if (activedMainFrame != null && activedMainFrame.isDisplayable()) {
-                       try {
-                               activedMainFrame.onCloseProfile();
-
-                       } catch (Throwable ex) {
-                               logger.log(Level.SEVERE, "mainframe closing failed.", ex);
-                               // フレームを閉じるときに失敗した場合、通常、致命的問題だが
-                               // クローズ処理は継続しなければならない.
-                       }
-               }
-       }
-
-       /**
-        * 画面の作業状態を保存する.
-        */
-       protected void saveWorkingSet() {
-               if (!characterData.isValid()) {
-                       return;
-               }
-               try {
-                       // ワーキングセットの作成
-                       WorkingSet workingSet = new WorkingSet();
-                       workingSet.setCharacterDocBase(characterData.getDocBase());
-                       workingSet.setCharacterDataRev(characterData.getRev());
-                       PartsSet partsSet = partsSelectionManager.createPartsSet();
-                       workingSet.setPartsSet(partsSet);
-                       workingSet.setPartsColorInfoMap(characterData
-                                       .getPartsColorManager().getPartsColorInfoMap());
-                       workingSet.setLastUsedSaveDir(imageSaveHelper.getLastUsedSaveDir());
-                       workingSet.setLastUsedExportDir(ExportWizardDialog.getLastUsedDir());
-                       workingSet.setLastUsePresetParts(lastUsePresetParts);
-                       workingSet
-                                       .setCharacterData(characterData.duplicateBasicInfo(false)); // パーツセットは保存しない.
-                       workingSet.setWallpaperInfo(wallpaperInfo);
-
-                       workingSet.setZoomFactor(previewPane.getZoomFactor());
-                       workingSet.setViewPosition(previewPane.getViewPosition());
-
-                       Dimension windowSize = getSize();
-                       Point windowPos = getLocation();
-                       Rectangle windowRect = new Rectangle(windowPos, windowSize);
-                       workingSet.setWindowRect(windowRect);
-
-                       // XML形式でのワーキングセットの保存
-                       WorkingSetPersist workingSetPersist = WorkingSetPersist
-                                       .getInstance();
-                       workingSetPersist.saveWorkingSet(workingSet);
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * 画面の作業状態を復元する.
-        *
-        * @return ワーキングセットを読み込んだ場合はtrue、そうでなければfalse
-        */
-       protected boolean loadWorkingSet() {
-               if (!characterData.isValid()) {
-                       return false;
-               }
-               try {
-                       WorkingSetPersist workingSetPersist = WorkingSetPersist
-                                       .getInstance();
-                       WorkingSet2 workingSet2 = workingSetPersist
-                                       .loadWorkingSet(characterData);
-                       if (workingSet2 == null) {
-                               // ワーキングセットがない場合.
-                               return false;
-                       }
-
-                       AppConfig appConfig = AppConfig.getInstance();
-                       Rectangle windowRect = workingSet2.getWindowRect();
-                       if (appConfig.isEnableRestoreWindow() && windowRect != null) {
-                               // 位置の復元
-                               GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
-                               Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)
-                               Point windowPos = windowRect.getLocation();
-                               if (desktopSize.contains(windowPos)) {
-                                       setLocation(windowPos);
-                               }
-
-                               // サイズの復元
-                               Dimension dim = windowRect.getSize();
-                               if (dim.width < 100) {
-                                       dim.width = 100;
-                               }
-                               if (dim.height < 100) {
-                                       dim.height = 100;
-                               }
-                               setSize(dim);
-                       } else {
-                               // デフォルトのウィンドウ位置とサイズ
-                               setDefaultWindowLocation();
-                       }
-
-                       URI docBase = characterData.getDocBase();
-                       if (docBase != null
-                                       && !docBase.equals(workingSet2.getCharacterDocBase())) {
-                               // docBaseが一致せず
-                               return false;
-                       }
-                       String sig = characterData.toSignatureString();
-                       if (!sig.equals(workingSet2.getCharacterDataSig())) {
-                               // 構造が一致せず.
-                               return false;
-                       }
-
-                       // パーツの色情報を復元する.
-                       Map<PartsIdentifier, PartsColorInfo> partsColorInfoMap = characterData
-                                       .getPartsColorManager().getPartsColorInfoMap();
-                       workingSet2.createCompatible(characterData, partsColorInfoMap);
-
-                       // 選択されているパーツの復元
-                       IndependentPartsSetInfo partsSetInfo = workingSet2
-                                       .getCurrentPartsSet();
-                       if (partsSetInfo != null) {
-                               PartsSet partsSet = IndependentPartsSetInfo.convertPartsSet(
-                                               partsSetInfo, characterData, false);
-                               selectPresetParts(partsSet);
-
-                               // 最後に選択したお気に入り情報の復元
-                               IndependentPartsSetInfo lastUsePresetPartsInfo = workingSet2
-                                               .getLastUsePresetParts();
-                               if (lastUsePresetPartsInfo != null
-                                               && lastUsePresetPartsInfo.getId() != null
-                                               && lastUsePresetPartsInfo.getId().trim().length() > 0) {
-                                       PartsSet lastUsePresetParts = IndependentPartsSetInfo
-                                                       .convertPartsSet(lastUsePresetPartsInfo,
-                                                                       characterData, false);
-                                       if (lastUsePresetParts.isSameStructure(partsSet)) {
-                                               this.lastUsePresetParts = lastUsePresetParts;
-                                               showPresetName(lastUsePresetParts);
-                                       }
-                               }
-                       }
-
-                       // 最後に保存したディレクトリを復元する.
-                       imageSaveHelper.setLastUseSaveDir(workingSet2.getLastUsedSaveDir());
-                       ExportWizardDialog.setLastUsedDir(workingSet2
-                                       .getLastUsedExportDir());
-
-                       // 壁紙情報を取得する.
-                       WallpaperInfo wallpaperInfo = workingSet2.getWallpaperInfo();
-                       if (wallpaperInfo != null) {
-                               // 壁紙情報を保存し、その情報をもとに背景を再描画する.
-                               // (適用に失敗した場合はエラーは無視し、壁紙情報は保存しない.)
-                               applyWallpaperInfo(wallpaperInfo, true);
-                       }
-
-                       // ズーム状態を復元する
-                       Double zoomFactor = workingSet2.getZoomFactor();
-                       if (appConfig.isEnableRestoreWindow() && zoomFactor != null) {
-                               previewPane.setZoomFactor(zoomFactor);
-                               final Point viewPosition = workingSet2.getViewPosition();
-                               if (viewPosition != null) {
-                                       previewPane.setViewPosition(viewPosition);
-                               }
-                       }
-
-                       return true;
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-               return false;
-       }
-
-
-       public void onAbout() {
-               try {
-                       AboutBox aboutBox = new AboutBox(this);
-                       aboutBox.showAboutBox();
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       protected void onHelp() {
-               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
-                       .getLocalizedProperties(STRINGS_RESOURCE);
-               String helpURL = strings.getProperty("help.url");
-               String helpDescription = strings.getProperty("help.show");
-               DesktopUtilities.browse(this, helpURL, helpDescription);
-       }
-
-       protected void onFlipHolizontal() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-
-               double[] affineTransformParameter = partsSelectionManager.getAffineTransformParameter();
-               if (affineTransformParameter == null) {
-                       // 左右フリップするアフィン変換パラメータを構築する.
-                       Dimension siz = characterData.getImageSize();
-                       if (siz != null) {
-                               affineTransformParameter = new double[] {-1., 0, 0, 1., siz.width, 0};
-                       }
-               } else {
-                       // アフィン変換パラメータをクリアする.
-                       affineTransformParameter = null;
-               }
-               partsSelectionManager.setAffineTransformParameter(affineTransformParameter);
-               requestPreview();
-       }
-
-       protected void onSetDefaultPicture() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-               try {
-                       BufferedImage samplePicture = previewPane.getPreviewImage();
-                       if (samplePicture != null) {
-                               CharacterDataPersistent persist = CharacterDataPersistent.getInstance();
-                               persist.saveSamplePicture(characterData, samplePicture);
-                       }
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       protected void onInformation() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-
-               PartsSet partsSet = partsSelectionManager.createPartsSet();
-               InformationDialog infoDlg = new InformationDialog(this, characterData, partsSet);
-               infoDlg.setVisible(true);
-       }
-
-       protected void onManageFavorites() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-
-               if (lastUseManageFavoritesDialog != null) {
-                       // 開いているダイアログがあれば、それにフォーカスを当てる.
-                       if (lastUseManageFavoritesDialog.isDisplayable()
-                                       && lastUseManageFavoritesDialog.isVisible()) {
-                               lastUseManageFavoritesDialog.requestFocus();
-                               return;
-                       }
-               }
-
-               // お気に入り編集ダイアログを開く
-               ManageFavoriteDialog dlg = new ManageFavoriteDialog(this, characterData);
-               dlg.setFavoriteManageCallback(new FavoriteManageCallback() {
-
-                       public void selectFavorites(PartsSet partsSet) {
-                               // お気に入り編集ダイアログで選択されたパーツを選択表示する.
-                               selectPresetParts(partsSet);
-                       }
-
-                       public void updateFavorites(CharacterData characterData,
-                                       boolean savePreset) {
-                               // お気に入りを登録する.
-                               try {
-                                       setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
-                                       try {
-                                               CharacterDataPersistent persiste = CharacterDataPersistent
-                                                               .getInstance();
-                                               if (savePreset) {
-                                                       persiste.updateProfile(characterData);
-                                               }
-
-                                               persiste.saveFavorites(characterData);
-
-                                               // お気に入りが更新されたことを通知する.
-                                               FavoritesChangeObserver.getDefault()
-                                                               .notifyFavoritesChange(MainFrame.this,
-                                                                               characterData);
-
-                                       } finally {
-                                               setCursor(Cursor.getDefaultCursor());
-                                       }
-
-                               } catch (Exception ex) {
-                                       ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);
-                               }
-                       }
-               });
-               WindowAdjustLocationSupport.alignRight(this, dlg, 0, true);
-               dlg.setVisible(true);
-               lastUseManageFavoritesDialog = dlg;
-       }
-
-       protected void onRegisterFavorite() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-               try {
-                       // パーツセットを生成
-                       PartsSet partsSet = partsSelectionManager.createPartsSet();
-                       if (partsSet.isEmpty()) {
-                               // 空のパーツセットは登録しない.
-                               return;
-                       }
-
-                       Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()
-                                       .getLocalizedProperties(STRINGS_RESOURCE);
-
-                       // お気に入りに登録するパーツセットが最後に使用したお気に入りと同じ構成であれば、
-                       // そのお気に入り名を使用する.
-                       String initName = getSuggestPartsSetName(partsSet, false);
-
-                       // カラー情報の有無のチェックボックス.
-                       JCheckBox chkColorInfo = new JCheckBox(strings.getProperty("input.favoritesColorInfo"));
-                       chkColorInfo.setSelected(true);
-                       String partsSetId = null;
-                       if (initName != null && lastUsePresetParts != null) {
-                               partsSetId = lastUsePresetParts.getPartsSetId();
-                       }
-
-                       // 上書き保存の可否のチェックボックス
-                       JCheckBox chkOverwrite = new JCheckBox(strings.getProperty("input.favoritesOverwrite"));
-                       chkOverwrite.setSelected(partsSetId != null && partsSetId.length() > 0);
-                       chkOverwrite.setEnabled(partsSetId != null && partsSetId.length() > 0);
-
-                       // チェックボックスパネル
-                       Box checkboxsPanel = new Box(BoxLayout.PAGE_AXIS);
-                       checkboxsPanel.add(chkColorInfo);
-                       checkboxsPanel.add(chkOverwrite);
-
-                       // 入力ダイアログを開く
-                       String name = (String) JOptionPane.showInputDialog(this,
-                                       checkboxsPanel,
-                                       strings.getProperty("input.favorites"),
-                                       JOptionPane.QUESTION_MESSAGE,
-                                       null,
-                                       null,
-                                       initName == null ? "" : initName);
-                       if (name == null || name.trim().length() == 0) {
-                               return;
-                       }
-
-                       boolean includeColorInfo = chkColorInfo.isSelected();
-                       if (!includeColorInfo) {
-                               // カラー情報を除去する.
-                               partsSet.removeColorInfo();
-                       }
-
-                       // 新規の場合、もしくは上書きしない場合はIDを設定する.
-                       if (partsSetId == null || !chkOverwrite.isSelected()) {
-                               partsSetId = "ps" + UUID.randomUUID().toString();
-                       }
-                       partsSet.setPartsSetId(partsSetId);
-
-                       // 名前を設定する.
-                       partsSet.setLocalizedName(name);
-
-                       // ファイルに保存
-                       setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
-                       try {
-                               CharacterDataPersistent persiste = CharacterDataPersistent.getInstance();
-                               // 現在の最新情報を取り出す.
-                               characterData.clearPartsSets(true);
-                               persiste.loadFavorites(characterData);
-
-                               // お気に入りコレクションに登録
-                               characterData.addPartsSet(partsSet);
-
-                               persiste.saveFavorites(characterData);
-
-                               // お気に入りが更新されたことを通知する.
-                               FavoritesChangeObserver.getDefault().notifyFavoritesChange(
-                                               MainFrame.this, characterData);
-
-                       } finally {
-                               setCursor(Cursor.getDefaultCursor());
-                       }
-
-                       // 最後に選択したお気に入りにする
-                       lastUsePresetParts = partsSet;
-                       showPresetName(partsSet);
-
-               } catch (Exception ex) {
-                       ErrorMessageHelper.showErrorDialog(this, ex);
-               }
-       }
-
-       /**
-        * ランダム選択ダイアログを開く.
-        */
-       protected void onToolRandom() {
-               if (!characterData.isValid()) {
-                       Toolkit tk = Toolkit.getDefaultToolkit();
-                       tk.beep();
-                       return;
-               }
-
-               if (lastUsePartsRandomChooserDialog != null) {
-                       // 開いているダイアログがあれば、それにフォーカスを当てる.
-                       if (lastUsePartsRandomChooserDialog.isDisplayable()
-                                       && lastUsePartsRandomChooserDialog.isVisible()) {
-                               lastUsePartsRandomChooserDialog.requestFocus();
-                               return;
-                       }
-               }
-
-               // お気に入り編集ダイアログを開く
-               PartsRandomChooserDialog dlg = new PartsRandomChooserDialog(this,
-                               characterData,
-                               new PartsRandomChooserDialog.PartsSetSynchronizer() {
-                                       public PartsSet getCurrentPartsSet() {
-                                               // 現在のパーツセットを生成
-                                               return partsSelectionManager.createPartsSet();
-                                       }
-
-                                       public void setPartsSet(PartsSet partsSet) {
-                                               selectPresetParts(partsSet);
-                                       }
-
-                                       public boolean
-                                                       isExcludePartsIdentifier(PartsIdentifier partsIdentifier) {
-                                               Boolean exclude = randomExcludePartsIdentifierMap
-                                                               .get(partsIdentifier);
-                                               return exclude != null && exclude.booleanValue();
-                                       }
-
-                                       public void
-                                                       setExcludePartsIdentifier(PartsIdentifier partsIdentifier,
-                                                                       boolean exclude) {
-                                               randomExcludePartsIdentifierMap.put(partsIdentifier,
-                                                               exclude);
-                                       }
-                               });
-
-               WindowAdjustLocationSupport.alignRight(this, dlg, 0, true);
-               dlg.setVisible(true);
-               lastUsePartsRandomChooserDialog = dlg;
-       }
-
-       /**
-        * ランダム選択パーツで選択候補から除外するパーツのマップ.
-        */
-       private HashMap<PartsIdentifier, Boolean> randomExcludePartsIdentifierMap =
-                       new HashMap<PartsIdentifier, Boolean>();
-
-       /**
-        * すべての解除可能なパーツの選択を解除する。
-        */
-       protected void onDeselectAll() {
-               partsSelectionManager.deselectAll();
-       }
-
-       /**
-        * 単一選択カテゴリのパーツの解除を許可する。
-        */
-       protected void onDeselectableAllCategory() {
-               partsSelectionManager
-                               .setDeselectableSingleCategory( !partsSelectionManager
-                                               .isDeselectableSingleCategory());
-       }
-
-       /**
-        * プレビューのズームボックスの表示制御
-        */
-       protected void onEnableZoom() {
-               previewPane.setVisibleZoomBox( !previewPane.isVisibleZoomBox());
-       }
-
-       /**
-        * メニューバーを構築します.
-        *
-        * @return メニューバー
-        */
-       protected JMenuBar createMenuBar() {
-               final Properties strings = LocalizedResourcePropertyLoader
-                               .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);
-
-               MenuDataFactory[] menus = new MenuDataFactory[] {
-                               new MenuDataFactory("menu.file", new MenuDataFactory[] {
-                                               new MenuDataFactory("file.openProfile", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onOpenProfile();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("file.savePicture", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onSavePicture();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("file.ukagaka", new MenuDataFactory[] {
-                                                               new MenuDataFactory("file.saveAsUkagaka", new ActionListener() {
-                                                                       public void actionPerformed(ActionEvent e) {
-                                                                               onSaveAsUkagaka();
-                                                                       };
-                                                               }),
-                                                               new MenuDataFactory("file.convertUkagaka", new ActionListener() {
-                                                                       public void actionPerformed(ActionEvent e) {
-                                                                               onConvertUkagaka();
-                                                                       };
-                                                               }),
-                                               }),
-                                               null,
-                                               new MenuDataFactory("file.editprofile", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onEditProfile();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("file.opendir", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onBrowseProfileDir();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("file.import", new MenuDataFactory[] {
-                                                               new MenuDataFactory("file.importMe", new ActionListener() {
-                                                                       public void actionPerformed(ActionEvent e) {
-                                                                               onImport(null);
-                                                                       };
-                                                               }),
-                                                               new MenuDataFactory("file.importNew", new ActionListener() {
-                                                                       public void actionPerformed(ActionEvent e) {
-                                                                               onImportNew();
-                                                                       };
-                                                               }),
-                                               }),
-                                               new MenuDataFactory("file.export", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onExport();
-                                                       };
-                                               }),
-                                               new MenuDataFactory("file.manageParts", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onManageParts();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("file.preferences", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onPreferences();
-                                                       };
-                                               }),
-                                               null,
-                                               new MenuDataFactory("file.closeProfile", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onCloseProfile();
-                                                       }
-                                               }),
-                               }),
-                               new MenuDataFactory("menu.edit", new MenuDataFactory[] {
-                                               new MenuDataFactory("edit.search", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               openSearchDialog();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("edit.copy", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onCopy((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0);
-                                                       }
-                                               }),
-                                               new MenuDataFactory("edit.flipHorizontal", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onFlipHolizontal();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("edit.resetcolor", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onResetColor();
-                                                       }
-                                               }),
-                                               null,
-                                               new MenuDataFactory("edit.setDefaultPicture", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onSetDefaultPicture();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("edit.information", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onInformation();
-                                                       }
-                                               }),
-                                               null,
-                                               new MenuDataFactory("edit.deselectall", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onDeselectAll();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("edit.deselectparts", true, new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onDeselectableAllCategory();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("edit.enableAutoShrink", true, new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onClickPartsCategoryTitle(null, true);
-                                                       }
-                                               }),
-                                               null,
-                                               new MenuDataFactory("edit.enableZoomBox", true, new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onEnableZoom();
-                                                       }
-                                               }),
-                                               null,
-                                               new MenuDataFactory("edit.changeBgColor", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onChangeBgColor();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("edit.changeWallpaper", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onChangeWallpaper();
-                                                       }
-                                               }),
-                               }),
-                               new MenuDataFactory("menu.favorite", new MenuDataFactory[] {
-                                               new MenuDataFactory("favorite.register", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onRegisterFavorite();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("favorite.manage", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onManageFavorites();
-                                                       }
-                                               }),
-                                               null,
-                               }),
-                               new MenuDataFactory("menu.tool",
-                                               new MenuDataFactory[]{new MenuDataFactory(
-                                                               "tool.random", new ActionListener() {
-                                                                       public void actionPerformed(ActionEvent e) {
-                                                                               onToolRandom();
-                                                                       }
-                                                               }),}),
-                               new MenuDataFactory("menu.help", new MenuDataFactory[] {
-                                               new MenuDataFactory("help.recommendations", (ActionListener) null),
-                                               null,
-                                               new MenuDataFactory("help.help", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onHelp();
-                                                       }
-                                               }),
-                                               new MenuDataFactory("help.forum",
-                                                               DesktopUtilities.createBrowseAction(
-                                                                               MainFrame.this,
-                                                                               strings.getProperty("help.forum.url"),
-                                                                               strings.getProperty("help.forum.description"))
-                                               ),
-                                               new MenuDataFactory("help.bugreport",
-                                                               DesktopUtilities.createBrowseAction(
-                                                                               MainFrame.this,
-                                                                               strings.getProperty("help.reportbugs.url"),
-                                                                               strings.getProperty("help.reportbugs.description"))
-                                               ),
-                                               new MenuDataFactory("help.about", new ActionListener() {
-                                                       public void actionPerformed(ActionEvent e) {
-                                                               onAbout();
-                                                       }
-                                               }),
-                               }), };
-
-               final MenuBuilder menuBuilder = new MenuBuilder();
-
-               JMenuBar menuBar = menuBuilder.createMenuBar(menus);
-
-               menuBuilder.getJMenu("menu.edit").addMenuListener(new MenuListener() {
-                       public void menuCanceled(MenuEvent e) {
-                               // do nothing.
-                       }
-                       public void menuDeselected(MenuEvent e) {
-                               // do nothing.
-                       }
-                       public void menuSelected(MenuEvent e) {
-                               menuBuilder.getJMenuItem("edit.copy").setEnabled(previewPane.getPreviewImage() != null);
-                               menuBuilder.getJMenuItem("edit.deselectparts").setSelected(
-                                               partsSelectionManager.isDeselectableSingleCategory());
-                               menuBuilder.getJMenuItem("edit.enableAutoShrink").setSelected(minimizeMode);
-                               menuBuilder.getJMenuItem("edit.enableZoomBox").setSelected(previewPane.isVisibleZoomBox());
-                       }
-               });
-               final JMenu mnuFavorites = menuBuilder.getJMenu("menu.favorite");
-               mnuFavorites.addMenuListener(new MenuListener() {
-                       public void menuCanceled(MenuEvent e) {
-                               // do nothing.
-                       }
-                       public void menuDeselected(MenuEvent e) {
-                               // do nothing.
-                       }
-                       public void menuSelected(MenuEvent e) {
-                               onSelectedFavoriteMenu(mnuFavorites);
-                       }
-               });
-
-               // J2SE5の場合は「パーツディレクトリを開く」コマンドは使用不可とする.
-               if (System.getProperty("java.version").startsWith("1.5")) {
-                       menuBuilder.getJMenuItem("file.opendir").setEnabled(false);
-               }
-
-               // お勧めサイトメニュー構築
-               final JMenu mnuRecomendation = menuBuilder.getJMenu("help.recommendations");
-               JMenu mnuHelp = menuBuilder.getJMenu("menu.help");
-               mnuHelp.addMenuListener(new MenuListener() {
-                       public void menuCanceled(MenuEvent e) {
-                               // do nothing.
-                       }
-                       public void menuDeselected(MenuEvent e) {
-                               // do nothing.
-                       }
-                       public void menuSelected(MenuEvent e) {
-                               onSelectedRecommendationMenu(mnuRecomendation);
-                       }
-               });
-
-               return menuBar;
-       }
-
-}
+package charactermanaj.ui;\r
+\r
+import static java.lang.Math.*;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.Color;\r
+import java.awt.Component;\r
+import java.awt.Container;\r
+import java.awt.Cursor;\r
+import java.awt.Dialog.ModalityType;\r
+import java.awt.Dimension;\r
+import java.awt.Font;\r
+import java.awt.Frame;\r
+import java.awt.GraphicsEnvironment;\r
+import java.awt.Point;\r
+import java.awt.Rectangle;\r
+import java.awt.Toolkit;\r
+import java.awt.dnd.DropTarget;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.awt.event.MouseWheelEvent;\r
+import java.awt.event.MouseWheelListener;\r
+import java.awt.event.WindowAdapter;\r
+import java.awt.event.WindowEvent;\r
+import java.awt.image.BufferedImage;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.lang.reflect.InvocationTargetException;\r
+import java.net.URI;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Properties;\r
+import java.util.TreeMap;\r
+import java.util.UUID;\r
+import java.util.logging.Level;\r
+import java.util.logging.Logger;\r
+\r
+import javax.swing.Box;\r
+import javax.swing.BoxLayout;\r
+import javax.swing.JCheckBox;\r
+import javax.swing.JCheckBoxMenuItem;\r
+import javax.swing.JColorChooser;\r
+import javax.swing.JFrame;\r
+import javax.swing.JMenu;\r
+import javax.swing.JMenuBar;\r
+import javax.swing.JMenuItem;\r
+import javax.swing.JOptionPane;\r
+import javax.swing.JPanel;\r
+import javax.swing.JPopupMenu;\r
+import javax.swing.JScrollBar;\r
+import javax.swing.JScrollPane;\r
+import javax.swing.JSeparator;\r
+import javax.swing.JSplitPane;\r
+import javax.swing.JViewport;\r
+import javax.swing.SwingUtilities;\r
+import javax.swing.event.AncestorEvent;\r
+import javax.swing.event.AncestorListener;\r
+import javax.swing.event.MenuEvent;\r
+import javax.swing.event.MenuListener;\r
+\r
+import charactermanaj.Main;\r
+import charactermanaj.clipboardSupport.ClipboardUtil;\r
+import charactermanaj.graphics.AsyncImageBuilder;\r
+import charactermanaj.graphics.ColorConvertedImageCachedLoader;\r
+import charactermanaj.graphics.ImageBuildJobAbstractAdaptor;\r
+import charactermanaj.graphics.ImageBuilder.ImageOutput;\r
+import charactermanaj.graphics.io.ImageSaveHelper;\r
+import charactermanaj.graphics.io.OutputOption;\r
+import charactermanaj.graphics.io.UkagakaImageSaveHelper;\r
+import charactermanaj.model.AppConfig;\r
+import charactermanaj.model.CharacterData;\r
+import charactermanaj.model.CharacterDataChangeEvent;\r
+import charactermanaj.model.CharacterDataChangeListener;\r
+import charactermanaj.model.CharacterDataChangeObserver;\r
+import charactermanaj.model.ColorGroup;\r
+import charactermanaj.model.CustomLayerOrder;\r
+import charactermanaj.model.IndependentPartsSetInfo;\r
+import charactermanaj.model.PartsCategory;\r
+import charactermanaj.model.PartsColorInfo;\r
+import charactermanaj.model.PartsColorManager;\r
+import charactermanaj.model.PartsIdentifier;\r
+import charactermanaj.model.PartsSet;\r
+import charactermanaj.model.RecommendationURL;\r
+import charactermanaj.model.WorkingSet;\r
+import charactermanaj.model.WorkingSet2;\r
+import charactermanaj.model.io.CharacterDataPersistent;\r
+import charactermanaj.model.io.CustomLayerOrderPersist;\r
+import charactermanaj.model.io.CustomLayerOrderPersist.CustomLayerOrderPersistListener;\r
+import charactermanaj.model.io.PartsImageCollectionParser.LayerOrderMapper;\r
+import charactermanaj.model.io.PartsImageDirectoryWatchAgent;\r
+import charactermanaj.model.io.PartsImageDirectoryWatchAgentFactory;\r
+import charactermanaj.model.io.PartsImageDirectoryWatchEvent;\r
+import charactermanaj.model.io.PartsImageDirectoryWatchListener;\r
+import charactermanaj.model.io.RecentDataPersistent;\r
+import charactermanaj.model.io.WorkingSetPersist;\r
+import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelEvent;\r
+import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelListener;\r
+import charactermanaj.ui.LayerOrderCustomizeDialog.LayerOrderCustomizeListener;\r
+import charactermanaj.ui.ManageFavoriteDialog.FavoriteManageCallback;\r
+import charactermanaj.ui.PreviewPanel.PreviewPanelEvent;\r
+import charactermanaj.ui.PreviewPanel.PreviewPanelListener;\r
+import charactermanaj.ui.model.ColorChangeEvent;\r
+import charactermanaj.ui.model.ColorChangeListener;\r
+import charactermanaj.ui.model.ColorGroupCoordinator;\r
+import charactermanaj.ui.model.CustomLayerPatternMgr;\r
+import charactermanaj.ui.model.FavoritesChangeEvent;\r
+import charactermanaj.ui.model.FavoritesChangeListener;\r
+import charactermanaj.ui.model.FavoritesChangeObserver;\r
+import charactermanaj.ui.model.PartsColorCoordinator;\r
+import charactermanaj.ui.model.PartsSelectionManager;\r
+import charactermanaj.ui.model.WallpaperFactory;\r
+import charactermanaj.ui.model.WallpaperFactoryErrorRecoverHandler;\r
+import charactermanaj.ui.model.WallpaperFactoryException;\r
+import charactermanaj.ui.model.WallpaperInfo;\r
+import charactermanaj.ui.scrollablemenu.JScrollableMenu;\r
+import charactermanaj.ui.util.FileDropTarget;\r
+import charactermanaj.ui.util.WindowAdjustLocationSupport;\r
+import charactermanaj.util.DesktopUtilities;\r
+import charactermanaj.util.ErrorMessageHelper;\r
+import charactermanaj.util.LocalizedResourcePropertyLoader;\r
+import charactermanaj.util.SystemUtil;\r
+import charactermanaj.util.UIHelper;\r
+\r
+\r
+/**\r
+ * メインフレーム.<br>\r
+ * アプリケーションがアクティブである場合は最低でも1つのメインフレームが表示されている.<br>\r
+ *\r
+ * @author seraphy\r
+ */\r
+public class MainFrame extends JFrame\r
+               implements\r
+                       FavoritesChangeListener,\r
+                       CharacterDataChangeListener,\r
+                       CustomLayerOrderPersistListener {\r
+\r
+       private static final long serialVersionUID = 1L;\r
+\r
+       private static final Logger logger = Logger.getLogger(MainFrame.class.getName());\r
+\r
+\r
+       protected static final String STRINGS_RESOURCE = "languages/mainframe";\r
+\r
+       protected static final String MENU_STRINGS_RESOURCE = "menu/menu";\r
+\r
+       /**\r
+        * メインフレームのアイコン.<br>\r
+        */\r
+       protected BufferedImage icon;\r
+\r
+\r
+       /**\r
+        * 現在アクティブなメインフレーム.<br>\r
+        * フォーカスが切り替わるたびにアクティブフレームを追跡する.<br>\r
+        * Mac OS XのAbout/Preferences/Quitのシステムメニューからよびだされた場合に\r
+        * オーナーたるべきメインフレームを識別するためのもの.<br>\r
+        */\r
+       private static volatile MainFrame activedMainFrame;\r
+\r
+\r
+       /**\r
+        * このメインフレームが対象とするキャラクターデータ.<br>\r
+        */\r
+       protected CharacterData characterData;\r
+\r
+\r
+       /**\r
+        * プレビューペイン\r
+        */\r
+       private PreviewPanel previewPane;\r
+\r
+       /**\r
+        * パーツ選択マネージャ\r
+        */\r
+       protected PartsSelectionManager partsSelectionManager;\r
+\r
+       /**\r
+        * パネルの最小化モード\r
+        */\r
+       private boolean minimizeMode;\r
+\r
+\r
+       /**\r
+        * パーツ選択パネルリスト\r
+        */\r
+       protected ImageSelectPanelList imageSelectPanels;\r
+\r
+       /**\r
+        * パーツ選択パネルを納めるスクロールペイン\r
+        */\r
+       protected JScrollPane imgSelectPanelsPanelSp;\r
+\r
+       /**\r
+        * カラーグループのマネージャ\r
+        */\r
+       protected ColorGroupCoordinator colorGroupCoordinator;\r
+\r
+       /**\r
+        * パーツカラーのマネージャ\r
+        */\r
+       protected PartsColorCoordinator partsColorCoordinator;\r
+\r
+\r
+       /**\r
+        * キャッシュつきのイメージローダ.<br>\r
+        */\r
+       private ColorConvertedImageCachedLoader imageLoader;\r
+\r
+       /**\r
+        * パーツを組み立てて1つのプレビュー可能なイメージを構築するためのビルダ\r
+        */\r
+       private AsyncImageBuilder imageBuilder;\r
+\r
+\r
+       /**\r
+        * パーツイメージを画像として保存する場合のヘルパー.<br>\r
+        * 最後に使ったディレクトリを保持するためのメンバ変数としている.<br>\r
+        */\r
+       private ImageSaveHelper imageSaveHelper = new ImageSaveHelper();\r
+\r
+       /**\r
+        * 伺か用出力ヘルパ.<br>\r
+        * 最後に使ったディレクトリ、ファイル名、モードなどを保持するためのメンバ変数としている.<br>\r
+        */\r
+       private UkagakaImageSaveHelper ukagakaImageSaveHelper = new UkagakaImageSaveHelper();\r
+\r
+       /**\r
+        * パーツディレクトリを定期的にチェックし、パーツイメージが変更・追加・削除されている場合に パーツリストを更新するためのウォッチャー\r
+        */\r
+       private PartsImageDirectoryWatchAgent watchAgent;\r
+\r
+       /**\r
+        * デフォルトのパーツセット表示名\r
+        */\r
+       private String defaultPartsSetTitle;\r
+\r
+       /**\r
+        * 最後に使用したプリセット.<br>\r
+        * (一度もプリセットを使用していなければnull).\r
+        */\r
+       private PartsSet lastUsePresetParts;\r
+\r
+       /**\r
+        * 最後に使用した検索ダイアログ.<br>\r
+        * nullであれば一度も使用していない.<br>\r
+        * (nullでなくとも閉じられている可能性がある.)<br>\r
+        */\r
+       private SearchPartsDialog lastUseSearchPartsDialog;\r
+\r
+       /**\r
+        * 最後に使用したお気に入りダイアログ.<br>\r
+        * nullであれば一度も使用していない.<br>\r
+        * (nullでなくとも閉じられている可能性がある.)\r
+        */\r
+       private ManageFavoriteDialog lastUseManageFavoritesDialog;\r
+\r
+       /**\r
+        * 最後に使用したパーツのランダム選択ダイアログ.<br>\r
+        * nullであれば一度も使用していない.<br>\r
+        * (nullでなくとも閉じられている可能性がある.)\r
+        */\r
+       private PartsRandomChooserDialog lastUsePartsRandomChooserDialog;\r
+\r
+       /**\r
+        * 最後に使用した壁紙情報\r
+        */\r
+       private WallpaperInfo wallpaperInfo;\r
+\r
+       /**\r
+        * カスタムレイヤーパターンのリストを管理する\r
+        */\r
+       private final CustomLayerPatternMgr customLayerPatternMgr = new CustomLayerPatternMgr();\r
+\r
+       /**\r
+        * アクティブなメインフレームを設定する.\r
+        *\r
+        * @param mainFrame\r
+        *            メインフレーム\r
+        */\r
+       public static void setActivedMainFrame(MainFrame mainFrame) {\r
+               if (mainFrame == null) {\r
+                       throw new IllegalArgumentException();\r
+               }\r
+               activedMainFrame = mainFrame;\r
+       }\r
+\r
+       /**\r
+        * 現在アクティブなメインフレームを取得する. まだメインフレームが開かれていない場合はnull.<br>\r
+        * 最後のメインフレームが破棄中、もしくは破棄済みであれば破棄されたフレームを示すことに注意.<br>\r
+        *\r
+        * @return メインフレーム、もしくはnull\r
+        */\r
+       public static MainFrame getActivedMainFrame() {\r
+               return activedMainFrame;\r
+       }\r
+\r
+       /**\r
+        * キャラクターデータが変更された場合に通知される.\r
+        */\r
+       public void notifyChangeCharacterData(final CharacterDataChangeEvent e) {\r
+               final CharacterData cd = e.getCharacterData();\r
+               if (cd != null\r
+                               && cd.getDocBase().equals(\r
+                                               MainFrame.this.characterData.getDocBase())) {\r
+                       SwingUtilities.invokeLater(new Runnable() {\r
+                               public void run() {\r
+                                       try {\r
+                                               Cursor oldCur = getCursor();\r
+                                               setCursor(Cursor\r
+                                                               .getPredefinedCursor(Cursor.WAIT_CURSOR));\r
+                                               try {\r
+                                                       if (e.isChangeStructure()) {\r
+                                                               // 現在情報の保存\r
+                                                               saveWorkingSet();\r
+\r
+                                                               // 画面構成の再構築\r
+                                                               initComponent(cd);\r
+                                                       }\r
+\r
+                                                       if (e.isReloadPartsAndFavorites()) {\r
+                                                               // パーツとお気に入りのリロード\r
+                                                               reloadPartsAndFavorites(cd, true);\r
+                                                       }\r
+\r
+                                               } finally {\r
+                                                       setCursor(oldCur != null ? oldCur : Cursor\r
+                                                                       .getDefaultCursor());\r
+                                               }\r
+\r
+                                       } catch (Exception ex) {\r
+                                               ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);\r
+                                       }\r
+                               }\r
+                       });\r
+               }\r
+       }\r
+\r
+       /**\r
+        * お気に入りデータが変更された場合に通知される.\r
+        *\r
+        * @param e\r
+        */\r
+       @Override\r
+       public void notifyChangeFavorites(FavoritesChangeEvent e) {\r
+               CharacterData cd = e.getCharacterData();\r
+               if (cd != null\r
+                               && cd.getDocBase().equals(\r
+                                               MainFrame.this.characterData.getDocBase())) {\r
+                       if (!MainFrame.this.equals(e.getSource())\r
+                                       && !characterData.equals(cd)) {\r
+                               // プリセットとお気に入りを最新化する.\r
+                               // ただし、自分自身から送信したイベントの場合は最新化は不要.\r
+                               characterData.clearPartsSets(false);\r
+                               for (Map.Entry<String, PartsSet> entry : cd.getPartsSets()\r
+                                               .entrySet()) {\r
+                                       PartsSet partsSet = entry.getValue();\r
+                                       characterData.addPartsSet(partsSet);\r
+                               }\r
+                       }\r
+\r
+                       // お気に入り管理ダイアログ上のお気に入り一覧を最新に更新する.\r
+                       if (lastUseManageFavoritesDialog != null\r
+                                       && lastUseManageFavoritesDialog.isDisplayable()) {\r
+                               lastUseManageFavoritesDialog.initListModel();\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 外部でカスタムレイヤーの定義が変更された場合に呼び出される\r
+        * @param e\r
+        */\r
+       @Override\r
+       public void notifyChangeCustomLayerOrder(CustomLayerOrderPersistListener.Change e) {\r
+               if (!e.getSource().equals(characterData)) { // 自分が送信元の場合は何もしない\r
+                       Map<String, List<CustomLayerOrder>> map = e.getCustomLayerOrderMap();\r
+                       if (map != null) {\r
+                               customLayerPatternMgr.setMap(map);\r
+                               requestPreview();\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * メインフレームを構築する.\r
+        *\r
+        * @param characterData\r
+        *            キャラクターデータ\r
+        */\r
+       public MainFrame(CharacterData characterData) {\r
+               try {\r
+                       if (characterData == null) {\r
+                               throw new IllegalArgumentException();\r
+                       }\r
+\r
+                       setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);\r
+                       addWindowListener(new WindowAdapter() {\r
+                               @Override\r
+                               public void windowClosing(WindowEvent e) {\r
+                                       onCloseProfile();\r
+                               }\r
+                               @Override\r
+                               public void windowClosed(WindowEvent e) {\r
+                                       stopAgents();\r
+                               }\r
+                               @Override\r
+                               public void windowActivated(WindowEvent e) {\r
+                                       setActivedMainFrame(MainFrame.this);\r
+                               }\r
+                               @Override\r
+                               public void windowOpened(WindowEvent e) {\r
+                                       // do nothing.\r
+                               }\r
+                       });\r
+\r
+                       // アイコンの設定\r
+                       icon = UIHelper.getInstance().getImage("icons/icon.png");\r
+                       setIconImage(icon);\r
+\r
+                       // 画面コンポーネント作成\r
+                       initComponent(characterData);\r
+                       JMenuBar menuBar = createMenuBar();\r
+                       setJMenuBar(menuBar);\r
+\r
+                       // お気に入り変更通知を受け取る\r
+                       FavoritesChangeObserver.getDefault().addFavoritesChangeListener(\r
+                                       this);\r
+                       // キャラクターデータの変更通知を受け取る\r
+                       CharacterDataChangeObserver.getDefault()\r
+                                       .addCharacterDataChangeListener(this);\r
+\r
+                       // レイヤーパターンの変更通知を受け取る\r
+                       CustomLayerOrderPersist.newInstance(characterData)\r
+                                       .addCustomLayerOrderPersistListener(this);\r
+\r
+               } catch (RuntimeException ex) {\r
+                       logger.log(Level.SEVERE, "メインフレームの構築中に予期せぬ例外が発生しました。", ex);\r
+                       dispose(); // コンストラクタが呼ばれた時点でJFrameは構築済みなのでdisposeの必要がある.\r
+                       throw ex;\r
+               } catch (Error ex) {\r
+                       logger.log(Level.SEVERE, "メインフレームの構築中に致命的な例外が発生しました。", ex);\r
+                       dispose(); // コンストラクタが呼ばれた時点でJFrameは構築済みなのでdisposeの必要がある.\r
+                       throw ex;\r
+               }\r
+       }\r
+\r
+       /**\r
+        * デフォルトのウィンドウ位置とサイズの設定\r
+        */\r
+       private void setDefaultWindowLocation() {\r
+               // メインスクリーンサイズを取得する.\r
+               GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();\r
+               Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)\r
+               logger.log(Level.CONFIG, "desktopSize=" + desktopSize);\r
+\r
+               Dimension imageSize = characterData.getImageSize();\r
+               // 画像サイズ300x400を基準サイズとして、それ以下にはならない.\r
+               // アプリケーション設定の最大サイズ以上の場合はウィンドウサイズは固定してスクロールバーに任せる\r
+               AppConfig appConfig = AppConfig.getInstance();\r
+               int maxWidth = min(desktopSize.width, appConfig.getMainFrameMaxWidth());\r
+               int maxHeight = min(desktopSize.height, appConfig.getMainFrameMaxHeight());\r
+               int imageWidth = min(maxWidth, max(300, imageSize != null ? imageSize.width : 0));\r
+               int imageHeight = min(maxHeight, max(400, imageSize != null ? imageSize.height : 0));\r
+               // 300x400の画像の場合にメインフレームが600x550だとちょうどいい感じ.\r
+               // それ以上大きい画像の場合は増えた分だけフレームを大きくしておく.\r
+               setSize(imageWidth - 300 + 600, imageHeight - 400 + 550);\r
+\r
+               // 次回表示時にプラットフォーム固有位置に表示するように予約\r
+               setLocationByPlatform(true);\r
+       }\r
+\r
+       /**\r
+        * メインフレームを表示する.<br>\r
+        * デスクトップ領域からはみ出した場合は位置を補正する.<br>\r
+        */\r
+       public void showMainFrame() {\r
+               // メインスクリーンサイズを取得する.\r
+               GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();\r
+               Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)\r
+               logger.log(Level.CONFIG, "desktopSize=" + desktopSize);\r
+\r
+               // プラットフォーム固有の位置あわせで表示する.\r
+               // 表示した結果、はみ出している場合は0,0に補正する.\r
+               setVisible(true);\r
+               Point loc = getLocation();\r
+               logger.log(Level.CONFIG, "windowLocation=" + loc);\r
+               Dimension windowSize = getSize();\r
+               if (loc.y + windowSize.height >= desktopSize.height) {\r
+                       loc.y = 0;\r
+               }\r
+               if (loc.x + windowSize.width >= desktopSize.width) {\r
+                       loc.x = 0;\r
+               }\r
+               if (loc.x == 0 || loc.y == 0) {\r
+                       setLocation(loc);\r
+               }\r
+\r
+               // デスクトップよりも大きい場合は小さくする.\r
+               boolean resize = false;\r
+               Dimension dim = getSize();\r
+               if (dim.height > desktopSize.height) {\r
+                       dim.height = desktopSize.height;\r
+                       resize = true;\r
+               }\r
+               if (dim.width > desktopSize.width) {\r
+                       dim.width = desktopSize.width;\r
+                       resize = true;\r
+               }\r
+               if (resize) {\r
+                       setSize(dim);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * このメインフレームに関連づけられているエージェントスレッドを停止します.<br>\r
+        * すでに停止している場合は何もしません。\r
+        */\r
+       protected void stopAgents() {\r
+               // エージェントを停止\r
+               if (watchAgent != null) {\r
+                       try {\r
+                               watchAgent.disconnect();\r
+\r
+                       } catch (Throwable ex) {\r
+                               logger.log(Level.SEVERE, "フォルダ監視スレッドの停止に失敗しました。", ex);\r
+                       }\r
+                       watchAgent = null;\r
+               }\r
+               // イメージビルダを停止\r
+               if (imageBuilder != null) {\r
+                       try {\r
+                               imageBuilder.stop();\r
+\r
+                       } catch (Throwable ex) {\r
+                               logger.log(Level.SEVERE, "非同期イメージビルダスレッドの停止に失敗しました。", ex);\r
+                       }\r
+                       imageBuilder = null;\r
+               }\r
+       }\r
+\r
+       /**\r
+        * メインフレームを破棄します.<br>\r
+        */\r
+       @Override\r
+       public void dispose() {\r
+               FavoritesChangeObserver.getDefault()\r
+                               .removeFavoritesChangeListener(this);\r
+               CharacterDataChangeObserver.getDefault()\r
+                               .removeCharacterDataChangeListener(this);\r
+               CustomLayerOrderPersist.newInstance(characterData)\r
+                               .removeCustomLayerOrderPersistListener(this);\r
+\r
+           imageLoader.close();\r
+               stopAgents();\r
+               super.dispose();\r
+       }\r
+\r
+       /**\r
+        * 画面コンポーネントを設定します.<br>\r
+        * すでに設定されている場合は一旦削除されたのちに再作成されます.<br>\r
+        */\r
+       private void initComponent(CharacterData characterData) {\r
+\r
+               CharacterData oldCd;\r
+               synchronized (this) {\r
+                       oldCd = this.characterData;\r
+                       if (oldCd != null) {\r
+                               // 使用中のキャラクターデータであることを登録解除する。\r
+                               ProfileListManager.unregisterUsedCharacterData(oldCd);\r
+                       }\r
+                       this.characterData = characterData;\r
+\r
+                       // 使用中のキャラクターデータであることを登録する.\r
+                       ProfileListManager.registerUsedCharacterData(characterData);\r
+               }\r
+\r
+               // 設定まわり準備\r
+               AppConfig appConfig = AppConfig.getInstance();\r
+               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
+                               .getLocalizedProperties(STRINGS_RESOURCE);\r
+\r
+               // タイトル表示\r
+               String title;\r
+               if (Main.isMacOSX()) {\r
+                       // Mac OS Xの場合はウィンドウにタイトルはつけない。\r
+                       title = "";\r
+               } else {\r
+                       title = strings.getProperty("title");\r
+               }\r
+               setTitle(title + characterData.getName());\r
+\r
+               // デフォルトのパーツセット表示名\r
+               defaultPartsSetTitle = strings.getProperty("defaultPartsSetTitle");\r
+\r
+               // エージェントの停止\r
+               stopAgents();\r
+\r
+               // コンポーネント配置\r
+               Container contentPane = getContentPane();\r
+\r
+               // すでにあるコンポーネントを削除\r
+               for (Component comp : contentPane.getComponents()) {\r
+                       contentPane.remove(comp);\r
+               }\r
+               // 開いている検索ダイアログを閉じる\r
+               closeSearchDialog();\r
+\r
+               // 開いているお気に入り管理ダイアログを閉じる\r
+               closeManageFavoritesDialog();\r
+\r
+               // 開いているランダム選択ダイアログを閉じる.\r
+               closePartsRandomChooserDialog();\r
+\r
+               PartsColorManager partsColorManager = characterData.getPartsColorManager();\r
+\r
+               // デフォルトの背景色の設定\r
+               Color bgColor = appConfig.getDefaultImageBgColor();\r
+               wallpaperInfo = new WallpaperInfo();\r
+               wallpaperInfo.setBackgroundColor(bgColor);\r
+\r
+               if (imageLoader != null) {\r
+                   imageLoader.close();\r
+               }\r
+               imageLoader = new ColorConvertedImageCachedLoader();\r
+               imageBuilder = new AsyncImageBuilder(imageLoader);\r
+               partsSelectionManager = new PartsSelectionManager(partsColorManager,\r
+                               new PartsSelectionManager.ImageBgColorProvider() {\r
+                       public Color getImageBgColor() {\r
+                               return wallpaperInfo.getBackgroundColor();\r
+                       }\r
+                       public void setImageBgColor(Color imageBgColor) {\r
+                               applyBackgroundColorOnly(imageBgColor);\r
+                       }\r
+               });\r
+               colorGroupCoordinator = new ColorGroupCoordinator(partsSelectionManager, partsColorManager);\r
+               partsColorCoordinator = new PartsColorCoordinator(characterData, partsColorManager, colorGroupCoordinator);\r
+               PartsImageDirectoryWatchAgentFactory agentFactory = PartsImageDirectoryWatchAgentFactory.getFactory();\r
+               watchAgent = agentFactory.getAgent(characterData);\r
+\r
+               previewPane = new PreviewPanel();\r
+               previewPane.setTitle(defaultPartsSetTitle);\r
+               previewPane.addPreviewPanelListener(new PreviewPanelListener() {\r
+                       public void addFavorite(PreviewPanelEvent e) {\r
+                               if (!e.isShiftKeyPressed()) {\r
+                                       // お気に入り登録\r
+                                       onRegisterFavorite();\r
+\r
+                               } else {\r
+                                       // シフトキーにて、お気に入りの管理を開く\r
+                                       onManageFavorites();\r
+                               }\r
+                       }\r
+                       public void changeBackgroundColor(PreviewPanelEvent e) {\r
+                               if ( !e.isShiftKeyPressed()) {\r
+                                       // 壁紙選択\r
+                                       onChangeWallpaper();\r
+\r
+                               } else {\r
+                                       // シフトキーにて背景色変更\r
+                                       onChangeBgColor();\r
+                               }\r
+                       }\r
+                       public void copyPicture(PreviewPanelEvent e) {\r
+                               onCopy(e.isShiftKeyPressed());\r
+                       }\r
+                       public void savePicture(PreviewPanelEvent e) {\r
+                               if ( !e.isShiftKeyPressed()) {\r
+                                       // 画像出力\r
+                                       onSavePicture();\r
+\r
+                               } else {\r
+                                       // シフトキーにて「伺か」用出力\r
+                                       onSaveAsUkagaka();\r
+                               }\r
+                       }\r
+                       public void showInformation(PreviewPanelEvent e) {\r
+                               onInformation();\r
+                       }\r
+                       public void flipHorizontal(PreviewPanelEvent e) {\r
+                               onFlipHolizontal();\r
+                       }\r
+               });\r
+\r
+               imageSelectPanels = new ImageSelectPanelList();\r
+\r
+               JPanel imgSelectPanelsPanel = new JPanel();\r
+               BoxLayout bl = new BoxLayout(imgSelectPanelsPanel, BoxLayout.PAGE_AXIS);\r
+               imgSelectPanelsPanel.setLayout(bl);\r
+               for (PartsCategory category : characterData.getPartsCategories()) {\r
+                       final ImageSelectPanel imageSelectPanel = new ImageSelectPanel(category, characterData);\r
+                       imgSelectPanelsPanel.add(imageSelectPanel);\r
+                       imageSelectPanels.add(imageSelectPanel);\r
+                       partsSelectionManager.register(imageSelectPanel);\r
+               }\r
+\r
+               imgSelectPanelsPanelSp = new JScrollPane(imgSelectPanelsPanel) {\r
+                       private static final long serialVersionUID = 1L;\r
+                       @Override\r
+                       public JScrollBar createVerticalScrollBar() {\r
+                               JScrollBar sb = super.createVerticalScrollBar();\r
+                               sb.setUnitIncrement(12);\r
+                               return sb;\r
+                       }\r
+               };\r
+               imgSelectPanelsPanelSp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);\r
+\r
+               JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, imgSelectPanelsPanelSp, previewPane);\r
+               contentPane.add(splitPane, BorderLayout.CENTER);\r
+\r
+\r
+               imgSelectPanelsPanelSp.requestFocus();\r
+\r
+               ArrayList<ColorGroup> colorGroups = new ArrayList<ColorGroup>();\r
+               colorGroups.addAll(characterData.getColorGroups());\r
+\r
+               final ColorChangeListener colorChangeListener = new ColorChangeListener() {\r
+                       public void onColorGroupChange(ColorChangeEvent event) {\r
+                               // do nothing.\r
+                       }\r
+                       public void onColorChange(ColorChangeEvent event) {\r
+                               MainFrame.this.requestPreview();\r
+                       }\r
+               };\r
+               colorGroupCoordinator.addColorChangeListener(colorChangeListener);\r
+\r
+               for (int idx = 0; idx < imageSelectPanels.size(); idx++) {\r
+                       ImageSelectPanel imageSelectPanel = imageSelectPanels.get(idx);\r
+                       final PartsCategory partsCategory = imageSelectPanel.getPartsCategory();\r
+                       final ColorDialog colorDialog = new ColorDialog(this, partsCategory, colorGroups);\r
+                       colorGroupCoordinator.registerColorDialog(colorDialog);\r
+                       partsColorCoordinator.register(imageSelectPanel, colorDialog);\r
+                       final int curidx = idx;\r
+                       imageSelectPanel.addImageSelectListener(new ImageSelectPanelListener() {\r
+                               public void onChangeColor(ImageSelectPanelEvent event) {\r
+                                                       WindowAdjustLocationSupport.alignRight(\r
+                                                                       MainFrame.this, colorDialog, curidx, false);\r
+                                       colorDialog.setVisible(!colorDialog.isVisible());\r
+                               }\r
+                               public void onPreferences(ImageSelectPanelEvent event) {\r
+                                       // do nothing. (not supported)\r
+                               }\r
+                               public void onChange(ImageSelectPanelEvent event) {\r
+                                       MainFrame.this.requestPreview();\r
+                               }\r
+                               public void onSelectChange(ImageSelectPanelEvent event) {\r
+                                       // do nothing.\r
+                               }\r
+                               public void onTitleClick(ImageSelectPanelEvent event) {\r
+                                       PartsCategory partsCategory = (event != null) ?\r
+                                                       event.getImageSelectPanel().getPartsCategory() : null;\r
+                                       MainFrame.this.onClickPartsCategoryTitle(partsCategory, false);\r
+                               }\r
+                               public void onTitleDblClick(ImageSelectPanelEvent event) {\r
+                                       PartsCategory partsCategory = (event != null) ?\r
+                                                       event.getImageSelectPanel().getPartsCategory() : null;\r
+                                       MainFrame.this.onClickPartsCategoryTitle(partsCategory, true);\r
+                               }\r
+                       });\r
+                       imageSelectPanel.addAncestorListener(new AncestorListener() {\r
+                               public void ancestorAdded(AncestorEvent event) {\r
+                               }\r
+                               public void ancestorMoved(AncestorEvent event) {\r
+                               }\r
+                               public void ancestorRemoved(AncestorEvent event) {\r
+                                       // パネルもしくは、その親が削除されたときにダイアログも非表示とする。\r
+                                       colorDialog.setVisible(false);\r
+                               }\r
+                       });\r
+               }\r
+\r
+               // 全パーツのロード\r
+               partsSelectionManager.loadParts();\r
+\r
+               // 登録されているカスタムレイヤーパターンのロード\r
+               loadCustomLayerOrder();\r
+\r
+               // 保存されているワーキングセットを復元する.\r
+               // 復元できなかった場合はパーツセットを初期選択する.\r
+               if ( !loadWorkingSet()) {\r
+                       // ワーキングセットがない場合は\r
+                       // デフォルトのウィンドウ位置とサイズ\r
+                       setDefaultWindowLocation();\r
+\r
+                       // デフォルトのパーツセットの表示\r
+                       if (showDefaultParts(true)) {\r
+                               requestPreview();\r
+                       }\r
+               }\r
+\r
+               // 選択されているパーツを見える状態にする\r
+               scrollToSelectedParts();\r
+\r
+               // 非同期イメージローダの処理開始\r
+               if (!imageBuilder.isAlive()) {\r
+                       imageBuilder.start();\r
+               }\r
+\r
+               // ドロップターゲットの設定\r
+               new DropTarget(imgSelectPanelsPanelSp, new FileDropTarget() {\r
+                       @Override\r
+                       protected void onDropFiles(final List<File> dropFiles) {\r
+                               if (dropFiles == null || dropFiles.isEmpty()) {\r
+                                       return;\r
+                               }\r
+                               // インポートダイアログを開く.\r
+                               // ドロップソースの処理がブロッキングしないように、\r
+                               // ドロップハンドラの処理を終了してからインポートダイアログが開くようにする.\r
+                               SwingUtilities.invokeLater(new Runnable() {\r
+                                       public void run() {\r
+                                               onImport(dropFiles);\r
+                                       }\r
+                               });\r
+                       }\r
+                       @Override\r
+                       protected void onException(Exception ex) {\r
+                               ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);\r
+                       }\r
+               });\r
+\r
+               // ディレクトリを監視し変更があった場合にパーツをリロードするリスナ\r
+               watchAgent.addPartsImageDirectoryWatchListener(new PartsImageDirectoryWatchListener() {\r
+                       public void detectPartsImageChange(PartsImageDirectoryWatchEvent e) {\r
+                               Runnable refreshJob = new Runnable() {\r
+                                       public void run() {\r
+                                               onDetectPartsImageChange();\r
+                                       }\r
+                               };\r
+                               if (SwingUtilities.isEventDispatchThread()) {\r
+                                       refreshJob.run();\r
+                               } else {\r
+                                       SwingUtilities.invokeLater(refreshJob);\r
+                               }\r
+                       }\r
+               });\r
+\r
+               // 監視が有効であれば、ディレクトリの監視をスタートする\r
+               if (appConfig.isEnableDirWatch() && characterData.isWatchDirectory()) {\r
+                       watchAgent.connect();\r
+               }\r
+\r
+               // パーツカテゴリの自動縮小が設定されている場合\r
+               minimizeMode = false;\r
+               if (appConfig.isEnableAutoShrinkPanel()) {\r
+                       onClickPartsCategoryTitle(null, true);\r
+               }\r
+\r
+               // コンポーネントの再構築の場合\r
+               if (oldCd != null) {\r
+                       validate();\r
+               }\r
+       }\r
+\r
+       /**\r
+        * パーツが変更されたことを検知した場合.<br>\r
+        * パーツデータをリロードし、各カテゴリのパーツ一覧を再表示させ、プレビューを更新する.<br>\r
+        */\r
+       protected void onDetectPartsImageChange() {\r
+               try {\r
+                       reloadPartsAndFavorites(null, true);\r
+\r
+               } catch (IOException ex) {\r
+                       logger.log(Level.SEVERE, "parts reload failed. " + characterData, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * すべてのカテゴリのリストで選択中のアイテムが見えるようにスクロールする.\r
+        */\r
+       protected void scrollToSelectedParts() {\r
+               partsSelectionManager.scrollToSelectedParts();\r
+       }\r
+\r
+       /**\r
+        * 指定したパーツカテゴリ以外のパーツ選択パネルを最小化する.\r
+        *\r
+        * @param partsCategory\r
+        *            パーツカテゴリ、nullの場合は全て最小化する.\r
+        * @param dblClick\r
+        *            ダブルクリック\r
+        */\r
+       protected void onClickPartsCategoryTitle(PartsCategory partsCategory, boolean dblClick) {\r
+               if (logger.isLoggable(Level.FINE)) {\r
+                       logger.log(Level.FINE, "onClickPartsCategoryTitle category="\r
+                                       + partsCategory + "/clickCount=" + dblClick);\r
+               }\r
+               if (dblClick) {\r
+                       minimizeMode = !minimizeMode;\r
+                       if (!minimizeMode) {\r
+                               partsSelectionManager.setMinimizeModeIfOther(null, false);\r
+                               return;\r
+                       }\r
+               }\r
+               if (minimizeMode) {\r
+                       if (partsSelectionManager.isNotMinimizeModeJust(partsCategory)) {\r
+                               partsSelectionManager.setMinimizeModeIfOther(null, true); // 全部縮小\r
+\r
+                       } else {\r
+                               partsSelectionManager.setMinimizeModeIfOther(partsCategory, true);\r
+                               if (partsCategory != null) {\r
+                                       // 対象のパネルがスクロールペイン内に見える用にスクロールする.\r
+                                       // スクロールバーの位置指定などの座標系の操作は「要求」であり、実際に適用されるまで本当の位置は分らない。\r
+                                       // よって以下の処理は非同期に行い、先に座標を確定させたものに対して行う必要がある。\r
+                                       final ImageSelectPanel panel = imageSelectPanels.findByPartsCategory(partsCategory);\r
+                                       SwingUtilities.invokeLater(new Runnable() {\r
+                                               public void run() {\r
+                                                       final Point pt = panel.getLocation();\r
+                                                       JViewport viewPort = imgSelectPanelsPanelSp.getViewport();\r
+                                                       viewPort.setViewPosition(pt);\r
+                                                       viewPort.revalidate();\r
+                                               }\r
+                                       });\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * デフォルトパーツを選択する.<br>\r
+        * デフォルトパーツがなければお気に入りの最初のものを選択する.<br>\r
+        * それもなければ空として表示する.<br>\r
+        * パーツの適用に失敗した場合はfalseを返します.(例外は返されません.)<br>\r
+        *\r
+        * @param force\r
+        *            すでに選択があっても選択しなおす場合はtrue、falseの場合は選択があれば何もしない.\r
+        * @return パーツ選択された場合。force=trueの場合はエラーがなければ常にtrueとなります。\r
+        */\r
+       protected boolean showDefaultParts(boolean force) {\r
+               try {\r
+                       if (!force) {\r
+                               // 現在選択中のパーツを取得する.(なければ空)\r
+                               PartsSet sel = partsSelectionManager.createPartsSet();\r
+                               if (!sel.isEmpty()) {\r
+                                       // 強制選択でない場合、すでに選択済みのパーツがあれば何もしない.\r
+                                       return false;\r
+                               }\r
+                       }\r
+\r
+                       // デフォルトのパーツセットを取得する\r
+                       String defaultPresetId = characterData.getDefaultPartsSetId();\r
+                       PartsSet partsSet = null;\r
+                       if (defaultPresetId != null) {\r
+                               partsSet = characterData.getPartsSets().get(defaultPresetId);\r
+                       }\r
+\r
+                       // デフォルトのパーツセットがなければ、お気に入りの最初を選択する.\r
+                       if (partsSet == null) {\r
+                               List<PartsSet> partssets = getPartsSetList();\r
+                               if (!partssets.isEmpty()) {\r
+                                       partsSet = partssets.get(0);\r
+                               }\r
+                       }\r
+\r
+                       // パーツセットがあれば、それを表示要求する.\r
+                       // パーツセットがなければカラーダイアログを初期化するのみ\r
+                       if (partsSet == null) {\r
+                               partsColorCoordinator.initColorDialog();\r
+\r
+                       } else {\r
+                               selectPresetParts(partsSet);\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       logger.log(Level.WARNING, "パーツのデフォルト適用に失敗しました。", ex);\r
+                       return false;\r
+               }\r
+               return true;\r
+       }\r
+\r
+       /**\r
+        * プリセットを適用しキャラクターイメージを再構築します.<br>\r
+        * 実行時エラーは画面のレポートされます.<br>\r
+        *\r
+        * @param presetParts\r
+        *            パーツセット, nullの場合は何もしない.\r
+        */\r
+       protected void selectPresetParts(PartsSet presetParts) {\r
+               if (presetParts == null) {\r
+                       return;\r
+               }\r
+               try {\r
+                       // 最後に使用したプリセットとして記憶する.\r
+                       lastUsePresetParts = presetParts;\r
+                       // プリセットパーツで選択を変える\r
+                       partsSelectionManager.selectPartsSet(presetParts);\r
+                       // カラーパネルを選択されているアイテムをもとに再設定する\r
+                       partsColorCoordinator.initColorDialog();\r
+                       // 再表示\r
+                       requestPreview();\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * プリセットとお気に入りを表示順に並べて返す.\r
+        *\r
+        * @return プリセットとお気に入りのリスト(表示順)\r
+        */\r
+       protected List<PartsSet> getPartsSetList() {\r
+               ArrayList<PartsSet> partssets = new ArrayList<PartsSet>();\r
+               partssets.addAll(characterData.getPartsSets().values());\r
+               Collections.sort(partssets, PartsSet.DEFAULT_COMPARATOR);\r
+               return partssets;\r
+       }\r
+\r
+       protected static final class TreeLeaf implements Comparable<TreeLeaf> {\r
+\r
+               public enum TreeLeafType {\r
+                       NODE, LEAF\r
+               }\r
+\r
+               private String name;\r
+\r
+               private TreeLeafType typ;\r
+\r
+               public TreeLeaf(TreeLeafType typ, String name) {\r
+                       if (name == null) {\r
+                               name = "";\r
+                       }\r
+                       this.typ = typ;\r
+                       this.name = name;\r
+               }\r
+\r
+               public String getName() {\r
+                       return name;\r
+               }\r
+\r
+               public TreeLeafType getTyp() {\r
+                       return typ;\r
+               }\r
+\r
+               @Override\r
+               public boolean equals(Object obj) {\r
+                       if (obj != null && obj instanceof TreeLeaf) {\r
+                               TreeLeaf o = (TreeLeaf) obj;\r
+                               return typ == o.typ && name.equals(o.name);\r
+                       }\r
+                       return false;\r
+               }\r
+\r
+               @Override\r
+               public int hashCode() {\r
+                       return typ.hashCode() ^ name.hashCode();\r
+               }\r
+\r
+               public int compareTo(TreeLeaf o) {\r
+                       int ret = name.compareTo(o.name);\r
+                       if (ret == 0) {\r
+                               ret = (typ.ordinal() - o.typ.ordinal());\r
+                       }\r
+                       return ret;\r
+               }\r
+\r
+               @Override\r
+               public String toString() {\r
+                       return name;\r
+               }\r
+       }\r
+\r
+       protected TreeMap<TreeLeaf, Object> buildFavoritesItemTree(\r
+                       List<PartsSet> partssets) {\r
+               if (partssets == null) {\r
+                       partssets = Collections.emptyList();\r
+               }\r
+               TreeMap<TreeLeaf, Object> favTree = new TreeMap<TreeLeaf, Object>();\r
+               for (PartsSet partsSet : partssets) {\r
+                       String flatname = partsSet.getLocalizedName();\r
+                       String[] tokens = flatname.split("\\|");\r
+                       if (tokens.length == 0) {\r
+                               continue;\r
+                       }\r
+\r
+                       TreeMap<TreeLeaf, Object> r = favTree;\r
+                       for (int idx = 0; idx < tokens.length - 1; idx++) {\r
+                               String name = tokens[idx];\r
+                               TreeLeaf leafName = new TreeLeaf(TreeLeaf.TreeLeafType.NODE,\r
+                                               name);\r
+                               @SuppressWarnings("unchecked")\r
+                               TreeMap<TreeLeaf, Object> n = (TreeMap<TreeLeaf, Object>) r\r
+                                               .get(leafName);\r
+                               if (n == null) {\r
+                                       n = new TreeMap<TreeLeaf, Object>();\r
+                                       r.put(leafName, n);\r
+                               }\r
+                               r = n;\r
+                       }\r
+                       String lastName = tokens[tokens.length - 1];\r
+                       TreeLeaf lastLeafName = new TreeLeaf(TreeLeaf.TreeLeafType.LEAF,\r
+                                       lastName);\r
+                       @SuppressWarnings("unchecked")\r
+                       List<PartsSet> leafValue = (List<PartsSet>) r.get(lastLeafName);\r
+                       if (leafValue == null) {\r
+                               leafValue = new ArrayList<PartsSet>();\r
+                               r.put(lastLeafName, leafValue);\r
+                       }\r
+                       leafValue.add(partsSet);\r
+               }\r
+               return favTree;\r
+       }\r
+\r
+       protected interface FavoriteMenuItemBuilder {\r
+               JMenuItem createFavoriteMenuItem(String name, PartsSet partsSet);\r
+               JMenu createSubMenu(String name);\r
+       }\r
+\r
+       private void buildFavoritesMenuItems(List<JMenuItem> menuItems,\r
+                       FavoriteMenuItemBuilder favMenuItemBuilder,\r
+                       TreeMap<TreeLeaf, Object> favTree) {\r
+               for (Map.Entry<TreeLeaf, Object> entry : favTree.entrySet()) {\r
+                       TreeLeaf treeLeaf = entry.getKey();\r
+                       String name = treeLeaf.getName();\r
+                       if (treeLeaf.getTyp() == TreeLeaf.TreeLeafType.LEAF) {\r
+                               // 葉ノードには、JMenuItemを設定する.\r
+                               @SuppressWarnings("unchecked")\r
+                               List<PartsSet> leafValue = (List<PartsSet>) entry.getValue();\r
+                               for (final PartsSet partsSet : leafValue) {\r
+                                       JMenuItem favoriteMenu = favMenuItemBuilder\r
+                                                       .createFavoriteMenuItem(name, partsSet);\r
+                                       menuItems.add(favoriteMenu);\r
+                               }\r
+\r
+                       } else if (treeLeaf.getTyp() == TreeLeaf.TreeLeafType.NODE) {\r
+                               // 枝ノードは、サブメニューを作成し、子ノードを設定する\r
+                               @SuppressWarnings("unchecked")\r
+                               TreeMap<TreeLeaf, Object> childNode = (TreeMap<TreeLeaf, Object>) entry\r
+                                               .getValue();\r
+                               JMenu subMenu = favMenuItemBuilder.createSubMenu(name);\r
+                               menuItems.add(subMenu);\r
+                               ArrayList<JMenuItem> subMenuItems = new ArrayList<JMenuItem>();\r
+                               buildFavoritesMenuItems(subMenuItems, favMenuItemBuilder, childNode);\r
+                               for (JMenuItem subMenuItem : subMenuItems) {\r
+                                       subMenu.add(subMenuItem);\r
+                               }\r
+\r
+                       } else {\r
+                               throw new RuntimeException("unknown type: " + treeLeaf);\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * お気に入りのJMenuItemを作成するファンクションオブジェクト\r
+        */\r
+       private FavoriteMenuItemBuilder favMenuItemBuilder = new FavoriteMenuItemBuilder() {\r
+               private MenuBuilder menuBuilder = new MenuBuilder();\r
+\r
+               /**\r
+                * お気に入りメニューの作成\r
+                */\r
+               public JMenuItem createFavoriteMenuItem(final String name,\r
+                               final PartsSet partsSet) {\r
+                       JMenuItem favoriteMenu = menuBuilder.createJMenuItem();\r
+                       favoriteMenu.setName(partsSet.getPartsSetId());\r
+                       favoriteMenu.setText(name);\r
+                       if (partsSet.isPresetParts()) {\r
+                               Font font = favoriteMenu.getFont();\r
+                               favoriteMenu.setFont(font.deriveFont(Font.BOLD));\r
+                       }\r
+                       favoriteMenu.addActionListener(new ActionListener() {\r
+                               public void actionPerformed(ActionEvent e) {\r
+                                       selectPresetParts(partsSet);\r
+                               }\r
+                       });\r
+\r
+                       // メニューアイテム上でマウスホイールを動かした場合は上下にスクロールさせる.\r
+                       // (ただし、OSXのスクリーンメニュー使用時は無視する.)\r
+                       addMouseWheelListener(favoriteMenu);\r
+\r
+                       return favoriteMenu;\r
+               }\r
+\r
+               /**\r
+                * サブメニューの作成\r
+                */\r
+               public JMenu createSubMenu(String name) {\r
+                       JMenu menu = menuBuilder.createJMenu();\r
+                       menu.setText(name);\r
+\r
+                       // メニューアイテム上でマウスホイールを動かした場合は上下にスクロールさせる.\r
+                       // (ただし、OSXのスクリーンメニュー使用時は無視する.)\r
+                       addMouseWheelListener(menu);\r
+\r
+                       return menu;\r
+               }\r
+\r
+               /**\r
+                * メニューアイテム上でホイールを上下させたときにメニューをスクロールさせるためのホイールハンドラを設定する.\r
+                *\r
+                * @param favoriteMenu\r
+                */\r
+               protected void addMouseWheelListener(final JMenuItem favoriteMenu) {\r
+                       if (JScrollableMenu.isScreenMenu()) {\r
+                               return;\r
+                       }\r
+                       favoriteMenu.addMouseWheelListener(new MouseWheelListener() {\r
+                               public void mouseWheelMoved(MouseWheelEvent e) {\r
+                                       int rotation = e.getWheelRotation();\r
+                                       JPopupMenu popupMenu = (JPopupMenu) favoriteMenu\r
+                                                       .getParent();\r
+                                       JMenu parentMenu = (JMenu) popupMenu.getInvoker();\r
+                                       if (parentMenu != null\r
+                                                       && parentMenu instanceof JScrollableMenu) {\r
+                                               final JScrollableMenu favMenu = (JScrollableMenu) parentMenu;\r
+                                               favMenu.doScroll(rotation < 0);\r
+                                       }\r
+                                       e.consume();\r
+                               }\r
+                       });\r
+               }\r
+       };\r
+\r
+       /**\r
+        * お気に入りメニューが開いたとき\r
+        *\r
+        * @param menu\r
+        */\r
+       protected void onSelectedFavoriteMenu(JMenu menu) {\r
+               // 表示順にソート\r
+               List<PartsSet> partssets = getPartsSetList();\r
+               TreeMap<TreeLeaf, Object> favTree = buildFavoritesItemTree(partssets);\r
+\r
+               // メニューの再構築\r
+               ArrayList<JMenuItem> favoritesMenuItems = new ArrayList<JMenuItem>();\r
+               buildFavoritesMenuItems(favoritesMenuItems, favMenuItemBuilder, favTree);\r
+\r
+               replaceMenuItems(menu, favoritesMenuItems);\r
+       }\r
+\r
+       /**\r
+        * スクロール可能JMenu/通常JMenuのメニューアイテムの差し替え\r
+        * @param menu\r
+        * @param items\r
+        */\r
+       private void replaceMenuItems(JMenu menu, List<JMenuItem> items) {\r
+               if (menu instanceof JScrollableMenu) {\r
+                       // スクロールメニューの場合\r
+                       JScrollableMenu favMenu = (JScrollableMenu) menu;\r
+\r
+                       // スクロールメニューの初期化\r
+                       favMenu.initScroller();\r
+\r
+                       // スクロールメニューアイテムの設定\r
+                       favMenu.setScrollableItems(items);\r
+\r
+                       // 高さを補正する\r
+                       // お気に入りメニューが選択された場合、\r
+                       // お気に入りアイテム一覧を表示するよりも前に\r
+                       // 表示可能なアイテム数を現在のウィンドウの高さから算定する.\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       Dimension scrsiz = tk.getScreenSize();\r
+                       int height = scrsiz.height; // MainFrame.this.getHeight();\r
+                       favMenu.adjustMaxVisible(height);\r
+                       logger.log(Level.FINE,\r
+                                       "scrollableMenu maxVisible=" + favMenu.getMaxVisible());\r
+\r
+               } else {\r
+                       // 通常メニューの場合\r
+                       // 既存メニューの位置をセパレータより判断する.\r
+                       int mx = menu.getMenuComponentCount();\r
+                       int separatorIdx = -1;\r
+                       for (int idx = 0; idx < mx; idx++) {\r
+                               Component item = menu.getMenuComponent(idx);\r
+                               if (item instanceof JSeparator) {\r
+                                       separatorIdx = idx;\r
+                                       break;\r
+                               }\r
+                       }\r
+                       // 既存メニューの削除\r
+                       if (separatorIdx > 0) {\r
+                               while (menu.getMenuComponentCount() > separatorIdx + 1) {\r
+                                       menu.remove(separatorIdx + 1);\r
+                               }\r
+                       }\r
+\r
+                       // メニューを登録する.\r
+                       for (JMenuItem menuItem : items) {\r
+                               menu.add(menuItem);\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * カスタムレイヤーメニューを開いたとき\r
+        * @param menu\r
+        */\r
+       protected void onSelectedCustomLayerMenu(JMenu menu) {\r
+               // メニューの再構築\r
+               ArrayList<JMenuItem> menuItems = new ArrayList<JMenuItem>();\r
+               for (Map.Entry<String, List<CustomLayerOrder>> entry : customLayerPatternMgr.getMap().entrySet()) {\r
+                       final String name = entry.getKey();\r
+                       final JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(name);\r
+                       menuItem.setSelected(customLayerPatternMgr.isSelected(name));\r
+                       menuItem.addActionListener(new ActionListener() {\r
+                               @Override\r
+                               public void actionPerformed(ActionEvent e) {\r
+                                       customLayerPatternMgr.setSelected(name, menuItem.isSelected());\r
+                                       requestPreview();\r
+                               }\r
+                       });\r
+                       menuItems.add(menuItem);\r
+               }\r
+               replaceMenuItems(menu, menuItems);\r
+       }\r
+\r
+       /**\r
+        * ヘルプメニューを開いたときにお勧めメニューを構築する.\r
+        *\r
+        * @param menu\r
+        */\r
+       protected void onSelectedRecommendationMenu(JMenu mnuRecomendation) {\r
+               // 現在のお勧めメニューを一旦削除\r
+               while (mnuRecomendation.getMenuComponentCount() > 0) {\r
+                       mnuRecomendation.remove(0);\r
+               }\r
+\r
+               // お勧めリンクの定義がない場合はデフォルトを用いる.(明示的な空の場合は何もしない.)\r
+               CharacterDataPersistent persist = CharacterDataPersistent.getInstance();\r
+               persist.compensateRecommendationList(characterData);\r
+\r
+               // お勧めリンクメニューを作成する.\r
+               List<RecommendationURL> recommendations = characterData.getRecommendationURLList();\r
+               if (recommendations != null) {\r
+                       MenuBuilder menuBuilder = new MenuBuilder();\r
+                       for (RecommendationURL recommendation : recommendations) {\r
+                               String displayName = recommendation.getDisplayName();\r
+                               String url = recommendation.getUrl();\r
+\r
+                               JMenuItem mnuItem = menuBuilder.createJMenuItem();\r
+                               mnuItem.setText(displayName);\r
+                               mnuItem.addActionListener(\r
+                                               DesktopUtilities.createBrowseAction(MainFrame.this, url, displayName)\r
+                                               );\r
+                               mnuRecomendation.add(mnuItem);\r
+                       }\r
+               }\r
+\r
+               // お勧めリンクメニューのリストがnullでなく空でもない場合は有効、そうでなければ無効にする.\r
+               mnuRecomendation.setEnabled(recommendations != null && !recommendations.isEmpty());\r
+       }\r
+\r
+\r
+       /**\r
+        * 最後に選択されたお気に入りと同じ構成であれば、 このお気に入りの名前をプレビューペインのタイトルに設定する.<br>\r
+        * そうでなければデフォルトのパーツセット名(no titleとか)を表示する.<br>\r
+        * 色情報が異なる場合に末尾に「*」マークがつけられる.<br>\r
+        *\r
+        * @param requestPartsSet\r
+        *            表示するパーツセット(名前は設定されていなくて良い。お気に入り側を使うので。), nullの場合はデフォルトのパーツ名\r
+        */\r
+       protected void showPresetName(PartsSet requestPartsSet) {\r
+               String title = getSuggestPartsSetName(requestPartsSet, true);\r
+               if (title == null) {\r
+                       title = defaultPartsSetTitle;\r
+               }\r
+               previewPane.setTitle(title);\r
+       }\r
+\r
+       /**\r
+        * パーツセット名を推定する.<br>\r
+        * 最後に選択されたお気に入りと同じ構成であれば、 このお気に入りの名前を返す.<br>\r
+        * お気に入りが選択されていないか構成が異なる場合、お気に入りに名前がない場合はnullを返す.<br>\r
+        *\r
+        * @param requestPartsSet\r
+        *            表示するパーツセット(名前は設定されていなくて良い。お気に入り側を使うので。)\r
+        * @param markColorChange\r
+        *            色情報が異なる場合に末尾に「*」マークをつける場合はtrue\r
+        */\r
+       private String getSuggestPartsSetName(PartsSet requestPartsSet, boolean markColorChange) {\r
+               String partsSetTitle = null;\r
+               if (lastUsePresetParts != null &&\r
+                               PartsSet.isSameStructure(requestPartsSet, lastUsePresetParts)) {\r
+                       partsSetTitle = lastUsePresetParts.getLocalizedName();\r
+                       if (markColorChange && !PartsSet.isSameColor(requestPartsSet, lastUsePresetParts)) {\r
+                               if (partsSetTitle != null) {\r
+                                       partsSetTitle += "*";\r
+                               }\r
+                       }\r
+               }\r
+               if (partsSetTitle != null && partsSetTitle.trim().length() > 0) {\r
+                       return partsSetTitle;\r
+               }\r
+               return null;\r
+       }\r
+\r
+       /**\r
+        * プレビューの更新を要求する. 更新は非同期に行われる.\r
+        */\r
+       protected void requestPreview() {\r
+               if (!characterData.isValid()) {\r
+                       return;\r
+               }\r
+\r
+               // 選択されているパーツの各イメージを取得しレイヤー順に並び替えて合成する.\r
+               // 合成は別スレッドにて非同期に行われる.\r
+               // リクエストは随時受け付けて、最新のリクエストだけが処理される.\r
+               // (処理がはじまる前に新しいリクエストで上書きされた場合、前のリクエストは単に捨てられる.)\r
+               imageBuilder.requestJob(new ImageBuildJobAbstractAdaptor(characterData) {\r
+\r
+                       /**\r
+                        * 構築するパーツセット情報\r
+                        */\r
+                       private PartsSet requestPartsSet;\r
+\r
+                       /**\r
+                        * 非同期のイメージ構築要求の番号.<br>\r
+                        */\r
+                       private long ticket;\r
+\r
+                       @Override\r
+                       public void onQueueing(long ticket) {\r
+                               this.ticket = ticket;\r
+                               previewPane.setLoadingRequest(ticket);\r
+                       }\r
+                       @Override\r
+                       public void buildImage(ImageOutput output) {\r
+                               // 合成結果のイメージを引数としてイメージビルダから呼び出される.\r
+                               final BufferedImage img = output.getImageOutput();\r
+                               Runnable refreshJob = new Runnable() {\r
+                                       public void run() {\r
+                                               previewPane.setPreviewImage(img);\r
+                                               previewPane.setLoadingComplete(ticket);\r
+                                               showPresetName(requestPartsSet);\r
+                                       }\r
+                               };\r
+                               if (SwingUtilities.isEventDispatchThread()) {\r
+                                       refreshJob.run();\r
+                               } else {\r
+                                       try {\r
+                                               SwingUtilities.invokeAndWait(refreshJob);\r
+                                       } catch (Exception ex) {\r
+                                               logger.log(Level.WARNING, "build image failed.", ex);\r
+                                       }\r
+                               }\r
+                       }\r
+                       @Override\r
+                       public void handleException(final Throwable ex) {\r
+                               // 合成中に例外が発生した場合、イメージビルダから呼び出される.\r
+                               Runnable showExceptionJob = new Runnable() {\r
+                                       public void run() {\r
+                                               ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);\r
+                                       }\r
+                               };\r
+                               if (SwingUtilities.isEventDispatchThread()) {\r
+                                       showExceptionJob.run();\r
+                               } else {\r
+                                       SwingUtilities.invokeLater(showExceptionJob);\r
+                               }\r
+                       }\r
+                       @Override\r
+                       protected PartsSet getPartsSet() {\r
+                               // 合成できる状態になった時点でイメージビルダから呼び出される.\r
+                               final PartsSet[] result = new PartsSet[1];\r
+                               Runnable collectPartsSetJob = new Runnable() {\r
+                                       public void run() {\r
+                                               PartsSet partsSet = partsSelectionManager.createPartsSet();\r
+                                               result[0] = partsSet;\r
+                                       }\r
+                               };\r
+                               if (SwingUtilities.isEventDispatchThread()) {\r
+                                       collectPartsSetJob.run();\r
+                               } else {\r
+                                       try {\r
+                                               // スレッドによるSwingのイベントディスパッチスレッド以外からの呼び出しの場合、\r
+                                               // Swingディスパッチスレッドでパーツの選択状態を取得する.\r
+                                               SwingUtilities.invokeAndWait(collectPartsSetJob);\r
+\r
+                                       } catch (InvocationTargetException e) {\r
+                                               throw new RuntimeException(e.getMessage(), e);\r
+                                       } catch (InterruptedException e) {\r
+                                               throw new RuntimeException("interrupted:" + e, e);\r
+                                       }\r
+                               }\r
+                               if (logger.isLoggable(Level.FINE)) {\r
+                                       logger.log(Level.FINE, "preview: " + result[0]);\r
+                               }\r
+                               requestPartsSet = result[0];\r
+                               return requestPartsSet;\r
+                       }\r
+                       @Override\r
+                       protected LayerOrderMapper getLayerOrderMapper() {\r
+                               return customLayerPatternMgr;\r
+                       }\r
+               });\r
+       }\r
+\r
+       /**\r
+        * プロファイルを開く\r
+        */\r
+       protected void onOpenProfile() {\r
+               try {\r
+                       MainFrame main2 = ProfileListManager.openProfile(this);\r
+                       if (main2 != null) {\r
+                               main2.showMainFrame();\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 背景色を変更する.\r
+        */\r
+       protected void onChangeBgColor() {\r
+               getJMenuBar().setEnabled(false);\r
+               try {\r
+                       Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
+                                       .getLocalizedProperties(STRINGS_RESOURCE);\r
+\r
+                       Color color = wallpaperInfo.getBackgroundColor();\r
+                       color = JColorChooser.showDialog(this, strings.getProperty("chooseBgColor"), color);\r
+                       if (color != null) {\r
+                               applyBackgroundColorOnly(color);\r
+                       }\r
+               } finally {\r
+                       getJMenuBar().setEnabled(true);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 壁紙を変更する.\r
+        */\r
+       protected void onChangeWallpaper() {\r
+               try {\r
+                       WallpaperDialog wallpaperDialog = new WallpaperDialog(this);\r
+\r
+                       // 最後に使用した壁紙情報でダイアログを設定する.\r
+                       wallpaperDialog.setWallpaperInfo(this.wallpaperInfo);\r
+\r
+                       // 壁紙情報を設定するモーダルダイアログを開く\r
+                       WallpaperInfo wallpaperInfo = wallpaperDialog.showDialog();\r
+                       if (wallpaperInfo == null) {\r
+                               return;\r
+                       }\r
+\r
+                       // 壁紙情報を保存し、その情報をもとに背景を再描画する.\r
+                       applyWallpaperInfo(wallpaperInfo, false);\r
+\r
+               } catch (WallpaperFactoryException ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+\r
+               } catch (RuntimeException ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 背景色のみ変更し、背景を再描画する.<br>\r
+        * 壁紙情報全体の更新よりも効率化するためのメソッドである.<br>\r
+        *\r
+        * @param bgColor\r
+        *            背景色\r
+        */\r
+       protected void applyBackgroundColorOnly(Color bgColor) {\r
+               wallpaperInfo.setBackgroundColor(bgColor);\r
+               previewPane.getWallpaper()\r
+                       .setBackgroundColor(wallpaperInfo.getBackgroundColor());\r
+       }\r
+\r
+       /**\r
+        * 壁紙情報を保存し、その情報をもとに背景を再描画する.<br>\r
+        * ignoreErrorがtrueである場合、適用に失敗した場合はログに記録するのみで、 壁紙情報は保存されず、壁紙も更新されない.<br>\r
+        *\r
+        * @param wallpaperInfo\r
+        *            壁紙情報、null不可\r
+        * @param ignoreError\r
+        *            失敗を無視する場合\r
+        * @throws IOException\r
+        *             失敗\r
+        */\r
+       protected void applyWallpaperInfo(WallpaperInfo wallpaperInfo, boolean ignoreError) throws WallpaperFactoryException {\r
+               if (wallpaperInfo == null) {\r
+                       throw new IllegalArgumentException();\r
+               }\r
+               // 壁紙情報から壁紙インスタンスを生成する.\r
+               WallpaperFactory wallpaperFactory = WallpaperFactory.getInstance();\r
+               Wallpaper wallpaper = null;\r
+\r
+               try {\r
+                       // 壁紙情報の構築時に問題が発生した場合、\r
+                       // 回復処理をして継続するかエラーとするか?\r
+                       WallpaperFactoryErrorRecoverHandler handler = null;\r
+                       if (ignoreError) {\r
+                               handler = new WallpaperFactoryErrorRecoverHandler();\r
+                       }\r
+\r
+                       // 壁紙情報\r
+                       wallpaper = wallpaperFactory.createWallpaper(wallpaperInfo, handler);\r
+\r
+               } catch (WallpaperFactoryException ex) {\r
+                       logger.log(Level.WARNING, "壁紙情報の適用に失敗しました。", ex);\r
+                       if ( !ignoreError) {\r
+                               throw ex;\r
+                       }\r
+\r
+               } catch (RuntimeException ex) {\r
+                       logger.log(Level.WARNING, "壁紙情報の適用に失敗しました。", ex);\r
+                       if ( !ignoreError) {\r
+                               throw ex;\r
+                       }\r
+               }\r
+\r
+               if (wallpaper == null) {\r
+                       return;\r
+               }\r
+\r
+               // 壁紙を更新する.\r
+               previewPane.setWallpaper(wallpaper);\r
+\r
+               // 壁紙情報として記憶する.\r
+               this.wallpaperInfo = wallpaperInfo;\r
+       }\r
+\r
+       /**\r
+        * プレビューしている画像をファイルに保存する。 サポートしているのはPNG/JPEGのみ。\r
+        */\r
+       protected void onSavePicture() {\r
+               Toolkit tk = Toolkit.getDefaultToolkit();\r
+               BufferedImage img = previewPane.getPreviewImage();\r
+               Color imgBgColor = wallpaperInfo.getBackgroundColor();\r
+               if (img == null) {\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+\r
+               try {\r
+                       // 出力オプションの調整\r
+                       OutputOption outputOption = imageSaveHelper.getOutputOption();\r
+                       outputOption.setZoomFactor(previewPane.getZoomFactor());\r
+                       outputOption.changeRecommend();\r
+                       imageSaveHelper.setOutputOption(outputOption);\r
+\r
+                       // ファイルダイアログ表示\r
+                       File outFile = imageSaveHelper.showSaveFileDialog(this);\r
+                       if (outFile == null) {\r
+                               return;\r
+                       }\r
+                       logger.log(Level.FINE, "savePicture: " + outFile);\r
+                       logger.log(Level.FINE, "outputOption: " + outputOption);\r
+\r
+                       // 画像のファイルへの出力\r
+                       StringBuilder warnings = new StringBuilder();\r
+\r
+                       setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
+                       try {\r
+                               imageSaveHelper.savePicture(img, imgBgColor, outFile, warnings);\r
+\r
+                       } finally {\r
+                               setCursor(Cursor.getDefaultCursor());\r
+                       }\r
+                       if (warnings.length() > 0) {\r
+                               JOptionPane.showMessageDialog(this, warnings.toString(), "WARNINGS", JOptionPane.WARNING_MESSAGE);\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 伺か用PNG/PNAの出力.\r
+        */\r
+       protected void onSaveAsUkagaka() {\r
+               BufferedImage img = previewPane.getPreviewImage();\r
+               Color bgColor = wallpaperInfo.getBackgroundColor();\r
+               if (img == null) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+\r
+               try {\r
+                       ukagakaImageSaveHelper.save(this, img, bgColor);\r
+\r
+               } catch (IOException ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 伺か用PNG/PNAの変換\r
+        */\r
+       protected void onConvertUkagaka() {\r
+               try {\r
+                       Color colorKey = wallpaperInfo.getBackgroundColor();\r
+                       ukagakaImageSaveHelper.convertChooseFiles(this, colorKey);\r
+\r
+               } catch (IOException ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * プロファイルの場所を開く\r
+        */\r
+       protected void onBrowseProfileDir() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+               try {\r
+                       DesktopUtilities.browseBaseDir(characterData.getDocBase());\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * このプロファイルを編集する.\r
+        */\r
+       protected void onEditProfile() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+               try {\r
+                       CharacterData cd = this.characterData;\r
+                       CharacterData newCd = ProfileListManager.editProfile(this, cd);\r
+                       if (newCd != null) {\r
+                               CharacterDataChangeObserver.getDefault()\r
+                                               .notifyCharacterDataChange(this, newCd, true, true);\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * パーツの管理ダイアログを開く.<br>\r
+        */\r
+       protected void onManageParts() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+\r
+               PartsManageDialog mrgDlg = new PartsManageDialog(this, characterData);\r
+               mrgDlg.setVisible(true);\r
+\r
+               if (mrgDlg.isUpdated()) {\r
+                       // パーツ管理情報が更新された場合、\r
+                       // パーツデータをリロードする.\r
+                       if (characterData.reloadPartsData()) {\r
+                               partsSelectionManager.loadParts();\r
+                               requestPreview();\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 「パーツ検索」ダイアログを開く.<br>\r
+        * すでに開いているダイアログがあれば、それにフォーカスを当てる.<br>\r
+        */\r
+       protected void openSearchDialog() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+\r
+               if (lastUseSearchPartsDialog != null) {\r
+                       // 開いているダイアログがあれば、それにフォーカスを当てる.\r
+                       if (lastUseSearchPartsDialog.isDisplayable() && lastUseSearchPartsDialog.isVisible()) {\r
+                               lastUseSearchPartsDialog.requestFocus();\r
+                               return;\r
+                       }\r
+               }\r
+\r
+               SearchPartsDialog searchPartsDlg = new SearchPartsDialog(this, characterData, partsSelectionManager);\r
+               WindowAdjustLocationSupport.alignRight(this, searchPartsDlg, 0, true);\r
+               searchPartsDlg.setVisible(true);\r
+               lastUseSearchPartsDialog = searchPartsDlg;\r
+       }\r
+\r
+       /**\r
+        * 「パーツ検索」ダイアログを閉じる.<br>\r
+        */\r
+       protected void closeSearchDialog() {\r
+               lastUseSearchPartsDialog = null;\r
+               for (SearchPartsDialog dlg : SearchPartsDialog.getDialogs()) {\r
+                       if (dlg != null && dlg.isDisplayable() && dlg.getParent() == this) {\r
+                               dlg.dispose();\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 「お気に入りの管理」ダイアログを閉じる\r
+        */\r
+       protected void closeManageFavoritesDialog() {\r
+               if (lastUseManageFavoritesDialog != null) {\r
+                       if (lastUseManageFavoritesDialog.isDisplayable()) {\r
+                               lastUseManageFavoritesDialog.dispose();\r
+                       }\r
+                       lastUseManageFavoritesDialog = null;\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 「パーツのランダム選択ダイアログ」を閉じる\r
+        */\r
+       protected void closePartsRandomChooserDialog() {\r
+               if (lastUsePartsRandomChooserDialog != null) {\r
+                       if (lastUsePartsRandomChooserDialog.isDisplayable()) {\r
+                               lastUsePartsRandomChooserDialog.dispose();\r
+                       }\r
+                       lastUsePartsRandomChooserDialog = null;\r
+               }\r
+       }\r
+\r
+       /**\r
+        * クリップボードにコピー\r
+        *\r
+        * @param screenImage\r
+        *            スクリーンイメージ\r
+        */\r
+       protected void onCopy(boolean screenImage) {\r
+               try {\r
+                       BufferedImage img = previewPane.getPreviewImage();\r
+                       if (img == null) {\r
+                               Toolkit tk = Toolkit.getDefaultToolkit();\r
+                               tk.beep();\r
+                               return;\r
+                       }\r
+\r
+                       if (screenImage) {\r
+                               // 表示している内容をそのままコピーする.\r
+                               img = previewPane.getScreenImage();\r
+                       }\r
+\r
+                       Color imgBgColor = wallpaperInfo.getBackgroundColor();\r
+                       ClipboardUtil.setImage(img, imgBgColor);\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * アプリケーションの設定ダイアログを開く\r
+        */\r
+       public void onPreferences() {\r
+               AppConfigDialog appConfigDlg = new AppConfigDialog(this);\r
+               appConfigDlg.setVisible(true);\r
+       }\r
+\r
+       /**\r
+        * 新規モードでインポートウィザードを実行する.<br>\r
+        */\r
+       protected void onImportNew() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+\r
+               try {\r
+                       // インポートウィザードの実行(新規モード)\r
+                       ImportWizardDialog importWizDialog = new ImportWizardDialog(this, null, null);\r
+                       importWizDialog.setVisible(true);\r
+                       int exitCode = importWizDialog.getExitCode();\r
+                       if (exitCode == ImportWizardDialog.EXIT_PROFILE_CREATED) {\r
+                               CharacterData cd = importWizDialog.getImportedCharacterData();\r
+                               if (cd != null && cd.isValid()) {\r
+                                       // インポートしたキャラクターデータのプロファイルを開く.\r
+                                       setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
+                                       try {\r
+                                               MainFrame mainFrame = ProfileListManager.openProfile(cd);\r
+                                               mainFrame.setVisible(true);\r
+\r
+                                       } finally {\r
+                                               setCursor(Cursor.getDefaultCursor());\r
+                                       }\r
+                               }\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 現在のプロファイルに対するインポートウィザードを実行する.<br>\r
+        * インポートが実行された場合は、パーツをリロードする.<br>\r
+        * インポートウィザード表示中は監視スレッドは停止される.<br>\r
+        *\r
+        * @param initFile\r
+        *            アーカイブファィルまたはディレクトリ、指定がなければnull\r
+        */\r
+       protected void onImport(List<File> initFiles) {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+\r
+               try {\r
+                       watchAgent.suspend();\r
+                       try {\r
+                               // インポートウィザードの実行\r
+                               ImportWizardDialog importWizDialog = new ImportWizardDialog(this, characterData, initFiles);\r
+                               importWizDialog.setVisible(true);\r
+\r
+                               if (importWizDialog.getExitCode() == ImportWizardDialog.EXIT_PROFILE_UPDATED) {\r
+                                       CharacterData importedCd = importWizDialog.getImportedCharacterData();\r
+                                       CharacterDataChangeObserver.getDefault()\r
+                                                       .notifyCharacterDataChange(this, importedCd,\r
+                                                                       false, true);\r
+                               }\r
+\r
+                       } finally {\r
+                               watchAgent.resume();\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * パーツとお気に入りをリロードする.<br>\r
+        * まだロードされていない場合はあらたにロードする.<br>\r
+        * 引数newCdが指定されている場合は、現在のキャラクター定義の説明文を更新する.<br>\r
+        * (説明文の更新以外には使用されない.)<br>\r
+        *\r
+        * @param newCd\r
+        *            説明文更新のための更新されたキャラクターデータを指定する。null可\r
+        * @param forceRepaint\r
+        *            必ず再描画する場合\r
+        * @throws IOException\r
+        *             失敗\r
+        */\r
+       protected synchronized void reloadPartsAndFavorites(CharacterData newCd,\r
+                       boolean forceRepaint) throws IOException {\r
+               if (newCd != null) {\r
+                       // (インポート画面では説明文のみ更新するので、それだけ取得)\r
+                       characterData.setDescription(newCd.getDescription());\r
+               }\r
+\r
+               if ( !characterData.isPartsLoaded()) {\r
+                       // キャラクターデータが、まだ読み込まれていなければ読み込む.\r
+                       ProfileListManager.loadCharacterData(characterData);\r
+                       ProfileListManager.loadFavorites(characterData);\r
+                       partsSelectionManager.loadParts();\r
+\r
+               } else {\r
+                       // パーツデータをリロードする.\r
+                       if (characterData.reloadPartsData()) {\r
+                               partsSelectionManager.loadParts();\r
+                       }\r
+\r
+                       // お気に入りをリロードする.\r
+                       CharacterDataPersistent persiste = CharacterDataPersistent.getInstance();\r
+                       persiste.loadFavorites(characterData);\r
+\r
+                       // お気に入りが更新されたことを通知する.\r
+                       FavoritesChangeObserver.getDefault().notifyFavoritesChange(\r
+                                       MainFrame.this, characterData);\r
+               }\r
+\r
+               // 現在選択されているパーツセットがない場合はデフォルトのパーツセットを選択する.\r
+               if (showDefaultParts(false) || forceRepaint) {\r
+                       requestPreview();\r
+               }\r
+       }\r
+\r
+       protected void onExport() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+               ExportWizardDialog exportWizDlg = new ExportWizardDialog(this, characterData, previewPane.getPreviewImage());\r
+               exportWizDlg.setVisible(true);\r
+       }\r
+\r
+       protected void onResetColor() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+\r
+               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
+                               .getLocalizedProperties(STRINGS_RESOURCE);\r
+\r
+               if (JOptionPane.showConfirmDialog(this, strings.get("confirm.resetcolors"), strings.getProperty("confirm"),\r
+                               JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.YES_OPTION) {\r
+                       return;\r
+               }\r
+               characterData.getPartsColorManager().resetPartsColorInfo();\r
+               partsColorCoordinator.initColorDialog();\r
+               requestPreview();\r
+       }\r
+\r
+       /**\r
+        * プロファイルを閉じる.\r
+        */\r
+       protected void onCloseProfile() {\r
+               saveWorkingSet();\r
+               ProfileListManager.unregisterUsedCharacterData(characterData);\r
+\r
+               if (characterData.isValid()) {\r
+\r
+                       // 最後に使用したキャラクターデータとして記憶する.\r
+                       try {\r
+                               RecentDataPersistent recentPersist = RecentDataPersistent.getInstance();\r
+                               recentPersist.saveRecent(characterData);\r
+\r
+                       } catch (Exception ex) {\r
+                               logger.log(Level.WARNING, "recent data saving failed.", ex);\r
+                               // recent情報の記録に失敗しても致命的ではないので、これは無視する.\r
+                       }\r
+               }\r
+\r
+               // イメージビルダスレッド・ディレクトリ監視スレッドを停止する.\r
+               stopAgents();\r
+\r
+               // フレームウィンドウを破棄する.\r
+               dispose();\r
+\r
+               // 破棄されたことをロギングする.\r
+               logger.log(Level.FINE, "dispose mainframe.");\r
+       }\r
+\r
+       /**\r
+        * 開いている、すべてのプロファイルを閉じる.<br>\r
+        * (Mac OS Xのcmd+Qで閉じる場合などで使用される.)<br>\r
+        */\r
+       public static void closeAllProfiles() {\r
+               // ウィンドウが閉じられることでアクティブなフレームが切り替わる場合を想定し、\r
+               // 現在のアクティブなウィンドウをあらかじめ記憶しておく\r
+               MainFrame mainFrame = activedMainFrame;\r
+\r
+               // gcをかけてファイナライズを促進させる\r
+               SystemUtil.gc();\r
+\r
+               // ファイナライズされていないFrameのうち、ネイティブリソースと関連づけられている\r
+               // フレームについて、それがMainFrameのインスタンスであれば閉じる.\r
+               // ただし、現在アクティブなものは除く\r
+               for (Frame frame : JFrame.getFrames()) {\r
+                       try {\r
+                               if (frame.isDisplayable()) {\r
+                                       // ネイティブリソースと関連づけられているフレーム\r
+                                       if (frame instanceof MainFrame && frame != mainFrame) {\r
+                                               // MainFrameのインスタンスであるので閉じる処理が可能.\r
+                                               // (現在アクティブなメインフレームは最後に閉じるため、ここでは閉じない.)\r
+                                               ((MainFrame) frame).onCloseProfile();\r
+                                       }\r
+                               }\r
+\r
+                       } catch (Throwable ex) {\r
+                               logger.log(Level.SEVERE, "mainframe closing failed.", ex);\r
+                               // フレームを閉じるときに失敗した場合、通常、致命的問題だが\r
+                               // クローズ処理は継続しなければならない.\r
+                       }\r
+               }\r
+\r
+               // 現在アクティブなフレームを閉じる.\r
+               // 最後に閉じることで「最後に使ったプロファイル」として記憶させる.\r
+               if (activedMainFrame != null && activedMainFrame.isDisplayable()) {\r
+                       try {\r
+                               activedMainFrame.onCloseProfile();\r
+\r
+                       } catch (Throwable ex) {\r
+                               logger.log(Level.SEVERE, "mainframe closing failed.", ex);\r
+                               // フレームを閉じるときに失敗した場合、通常、致命的問題だが\r
+                               // クローズ処理は継続しなければならない.\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 画面の作業状態を保存する.\r
+        */\r
+       protected void saveWorkingSet() {\r
+               if (!characterData.isValid()) {\r
+                       return;\r
+               }\r
+               try {\r
+                       // ワーキングセットの作成\r
+                       WorkingSet workingSet = new WorkingSet();\r
+                       workingSet.setCharacterDocBase(characterData.getDocBase());\r
+                       workingSet.setCharacterDataRev(characterData.getRev());\r
+                       PartsSet partsSet = partsSelectionManager.createPartsSet();\r
+                       workingSet.setPartsSet(partsSet);\r
+                       workingSet.setPartsColorInfoMap(characterData\r
+                                       .getPartsColorManager().getPartsColorInfoMap());\r
+                       workingSet.setLastUsedSaveDir(imageSaveHelper.getLastUsedSaveDir());\r
+                       workingSet.setLastUsedExportDir(ExportWizardDialog.getLastUsedDir());\r
+                       workingSet.setLastUsePresetParts(lastUsePresetParts);\r
+                       workingSet\r
+                                       .setCharacterData(characterData.duplicateBasicInfo(false)); // パーツセットは保存しない.\r
+                       workingSet.setWallpaperInfo(wallpaperInfo);\r
+\r
+                       workingSet.setZoomFactor(previewPane.getZoomFactor());\r
+                       workingSet.setViewPosition(previewPane.getViewPosition());\r
+\r
+                       Dimension windowSize = getSize();\r
+                       Point windowPos = getLocation();\r
+                       Rectangle windowRect = new Rectangle(windowPos, windowSize);\r
+                       workingSet.setWindowRect(windowRect);\r
+\r
+                       // XML形式でのワーキングセットの保存\r
+                       WorkingSetPersist workingSetPersist = WorkingSetPersist\r
+                                       .getInstance();\r
+                       workingSetPersist.saveWorkingSet(workingSet);\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 保存されているカスタムレイヤーパターンをロードする\r
+        */\r
+       protected void loadCustomLayerOrder() {\r
+               try {\r
+                       CustomLayerOrderPersist persist = CustomLayerOrderPersist.newInstance(characterData);\r
+                       Map<String, List<CustomLayerOrder>> map = persist.load();\r
+                       if (map != null) {\r
+                               customLayerPatternMgr.setMap(map);\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 画面の作業状態を復元する.\r
+        *\r
+        * @return ワーキングセットを読み込んだ場合はtrue、そうでなければfalse\r
+        */\r
+       protected boolean loadWorkingSet() {\r
+               if (!characterData.isValid()) {\r
+                       return false;\r
+               }\r
+               try {\r
+                       WorkingSetPersist workingSetPersist = WorkingSetPersist\r
+                                       .getInstance();\r
+                       WorkingSet2 workingSet2 = workingSetPersist\r
+                                       .loadWorkingSet(characterData);\r
+                       if (workingSet2 == null) {\r
+                               // ワーキングセットがない場合.\r
+                               return false;\r
+                       }\r
+\r
+                       AppConfig appConfig = AppConfig.getInstance();\r
+                       Rectangle windowRect = workingSet2.getWindowRect();\r
+                       if (appConfig.isEnableRestoreWindow() && windowRect != null) {\r
+                               // 位置の復元\r
+                               GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();\r
+                               Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)\r
+                               Point windowPos = windowRect.getLocation();\r
+                               if (desktopSize.contains(windowPos)) {\r
+                                       setLocation(windowPos);\r
+                               }\r
+\r
+                               // サイズの復元\r
+                               Dimension dim = windowRect.getSize();\r
+                               if (dim.width < 100) {\r
+                                       dim.width = 100;\r
+                               }\r
+                               if (dim.height < 100) {\r
+                                       dim.height = 100;\r
+                               }\r
+                               setSize(dim);\r
+                       } else {\r
+                               // デフォルトのウィンドウ位置とサイズ\r
+                               setDefaultWindowLocation();\r
+                       }\r
+\r
+                       URI docBase = characterData.getDocBase();\r
+                       if (docBase != null\r
+                                       && !docBase.equals(workingSet2.getCharacterDocBase())) {\r
+                               // docBaseが一致せず\r
+                               return false;\r
+                       }\r
+                       String sig = characterData.toSignatureString();\r
+                       if (!sig.equals(workingSet2.getCharacterDataSig())) {\r
+                               // 構造が一致せず.\r
+                               return false;\r
+                       }\r
+\r
+                       // パーツの色情報を復元する.\r
+                       Map<PartsIdentifier, PartsColorInfo> partsColorInfoMap = characterData\r
+                                       .getPartsColorManager().getPartsColorInfoMap();\r
+                       workingSet2.createCompatible(characterData, partsColorInfoMap);\r
+\r
+                       // 選択されているパーツの復元\r
+                       IndependentPartsSetInfo partsSetInfo = workingSet2\r
+                                       .getCurrentPartsSet();\r
+                       if (partsSetInfo != null) {\r
+                               PartsSet partsSet = IndependentPartsSetInfo.convertPartsSet(\r
+                                               partsSetInfo, characterData, false);\r
+                               selectPresetParts(partsSet);\r
+\r
+                               // 最後に選択したお気に入り情報の復元\r
+                               IndependentPartsSetInfo lastUsePresetPartsInfo = workingSet2\r
+                                               .getLastUsePresetParts();\r
+                               if (lastUsePresetPartsInfo != null\r
+                                               && lastUsePresetPartsInfo.getId() != null\r
+                                               && lastUsePresetPartsInfo.getId().trim().length() > 0) {\r
+                                       PartsSet lastUsePresetParts = IndependentPartsSetInfo\r
+                                                       .convertPartsSet(lastUsePresetPartsInfo,\r
+                                                                       characterData, false);\r
+                                       if (lastUsePresetParts.isSameStructure(partsSet)) {\r
+                                               this.lastUsePresetParts = lastUsePresetParts;\r
+                                               showPresetName(lastUsePresetParts);\r
+                                       }\r
+                               }\r
+                       }\r
+\r
+                       // 最後に保存したディレクトリを復元する.\r
+                       imageSaveHelper.setLastUseSaveDir(workingSet2.getLastUsedSaveDir());\r
+                       ExportWizardDialog.setLastUsedDir(workingSet2\r
+                                       .getLastUsedExportDir());\r
+\r
+                       // 壁紙情報を取得する.\r
+                       WallpaperInfo wallpaperInfo = workingSet2.getWallpaperInfo();\r
+                       if (wallpaperInfo != null) {\r
+                               // 壁紙情報を保存し、その情報をもとに背景を再描画する.\r
+                               // (適用に失敗した場合はエラーは無視し、壁紙情報は保存しない.)\r
+                               applyWallpaperInfo(wallpaperInfo, true);\r
+                       }\r
+\r
+                       // ズーム状態を復元する\r
+                       Double zoomFactor = workingSet2.getZoomFactor();\r
+                       if (appConfig.isEnableRestoreWindow() && zoomFactor != null) {\r
+                               previewPane.setZoomFactor(zoomFactor);\r
+                               final Point viewPosition = workingSet2.getViewPosition();\r
+                               if (viewPosition != null) {\r
+                                       previewPane.setViewPosition(viewPosition);\r
+                               }\r
+                       }\r
+\r
+                       return true;\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+               return false;\r
+       }\r
+\r
+\r
+       public void onAbout() {\r
+               try {\r
+                       AboutBox aboutBox = new AboutBox(this);\r
+                       aboutBox.showAboutBox();\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       protected void onHelp() {\r
+               Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
+                       .getLocalizedProperties(STRINGS_RESOURCE);\r
+               String helpURL = strings.getProperty("help.url");\r
+               String helpDescription = strings.getProperty("help.show");\r
+               DesktopUtilities.browse(this, helpURL, helpDescription);\r
+       }\r
+\r
+       protected void onFlipHolizontal() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+\r
+               double[] affineTransformParameter = partsSelectionManager.getAffineTransformParameter();\r
+               if (affineTransformParameter == null) {\r
+                       // 左右フリップするアフィン変換パラメータを構築する.\r
+                       Dimension siz = characterData.getImageSize();\r
+                       if (siz != null) {\r
+                               affineTransformParameter = new double[] {-1., 0, 0, 1., siz.width, 0};\r
+                       }\r
+               } else {\r
+                       // アフィン変換パラメータをクリアする.\r
+                       affineTransformParameter = null;\r
+               }\r
+               partsSelectionManager.setAffineTransformParameter(affineTransformParameter);\r
+               requestPreview();\r
+       }\r
+\r
+       protected void onSetDefaultPicture() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+               try {\r
+                       BufferedImage samplePicture = previewPane.getPreviewImage();\r
+                       if (samplePicture != null) {\r
+                               CharacterDataPersistent persist = CharacterDataPersistent.getInstance();\r
+                               persist.saveSamplePicture(characterData, samplePicture);\r
+                       }\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       protected void onInformation() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+\r
+               PartsSet partsSet = partsSelectionManager.createPartsSet();\r
+               InformationDialog infoDlg = new InformationDialog(this, characterData, partsSet, customLayerPatternMgr);\r
+               infoDlg.setVisible(true);\r
+       }\r
+\r
+       protected void onManageCustomLayer() {\r
+               List<PartsCategory> categories = characterData.getPartsCategories();\r
+\r
+               // レイヤー編集ダイアログの構築\r
+               final LayerOrderCustomizeDialog layerOrderCustomDlg = new LayerOrderCustomizeDialog(this, categories);\r
+               layerOrderCustomDlg.setModalityType(ModalityType.APPLICATION_MODAL);\r
+               layerOrderCustomDlg.addLayerOrderCustomizeListener(new LayerOrderCustomizeListener() {\r
+                       @Override\r
+                       public void onChange(charactermanaj.ui.LayerOrderCustomizeDialog.LayerOrderCustomizeListener.Change e) {\r
+                               // レイヤーパターンの編集がされた場合、プレビューする\r
+                               customLayerPatternMgr.applyCustomLayerOrder(layerOrderCustomDlg.getEdittingCustomLayerOrderList());\r
+                               requestPreview();\r
+                       }\r
+               });\r
+\r
+               // 現在保持しているレイヤーパターンの一覧を取得する\r
+               final LayerOrderCustomizeDialogModel layerOrderCustomDialogModel = new LayerOrderCustomizeDialogModel();\r
+               for (Map.Entry<String, List<CustomLayerOrder>> entry : customLayerPatternMgr.getMap().entrySet()) {\r
+                       String patternName = entry.getKey();\r
+                       List<CustomLayerOrder> customLayerOrderList = entry.getValue();\r
+                       layerOrderCustomDialogModel.put(patternName, customLayerOrderList);\r
+               }\r
+\r
+               // 現在選択しているアクティブなレイヤーパターンを取得する\r
+               List<CustomLayerOrder> currentList = customLayerPatternMgr.getMergedCustomLayerOrderList();\r
+               layerOrderCustomDialogModel.setCurrentList(currentList);\r
+\r
+               layerOrderCustomDlg.setModel(layerOrderCustomDialogModel);\r
+\r
+               // パターンの保存時\r
+               layerOrderCustomDialogModel.addListChangeListener(new LayerOrderCustomizeDialogModel.ChangeListener() {\r
+                       @Override\r
+                       public void onChange(Change change) {\r
+                               try {\r
+                                       // 編集されたレイヤーパターンの一覧を保存する。\r
+                                       Map<String, List<CustomLayerOrder>> customLayerPatternMap =\r
+                                                       new HashMap<String, List<CustomLayerOrder>>();\r
+                                       for (String patternName : layerOrderCustomDialogModel.getPatternNames()) {\r
+                                               List<CustomLayerOrder> customLayerOrderList = layerOrderCustomDialogModel.getCopy(patternName);\r
+                                               if (customLayerOrderList != null) {\r
+                                                       customLayerPatternMap.put(patternName, customLayerOrderList);\r
+                                               }\r
+                                       }\r
+\r
+                                       // この画面のカスタムレイヤー管理を更新する\r
+                                       customLayerPatternMgr.setMap(customLayerPatternMap);\r
+\r
+                                       // 設定したレイヤーパターンをファイルに永続化する\r
+                                       CustomLayerOrderPersist persist = CustomLayerOrderPersist.newInstance(characterData);\r
+                                       persist.save(customLayerPatternMap);\r
+\r
+                               } catch (Exception ex) {\r
+                                       ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);\r
+                               }\r
+                       }\r
+               });\r
+\r
+               // モーダルでダイアログを表示する\r
+               layerOrderCustomDlg.setLocationByPlatform(true);\r
+               layerOrderCustomDlg.setVisible(true);\r
+\r
+               // 最後に選択または保存したパターン名をデフォルトで選択状態とする\r
+               String lastPatternName = layerOrderCustomDlg.getLastPatternName();\r
+               if (lastPatternName != null && lastPatternName.trim().length() > 0) {\r
+                       customLayerPatternMgr.setSelected(lastPatternName, true);\r
+               }\r
+\r
+               requestPreview();\r
+       }\r
+\r
+       protected void onManageFavorites() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+\r
+               if (lastUseManageFavoritesDialog != null) {\r
+                       // 開いているダイアログがあれば、それにフォーカスを当てる.\r
+                       if (lastUseManageFavoritesDialog.isDisplayable()\r
+                                       && lastUseManageFavoritesDialog.isVisible()) {\r
+                               lastUseManageFavoritesDialog.requestFocus();\r
+                               return;\r
+                       }\r
+               }\r
+\r
+               // お気に入り編集ダイアログを開く\r
+               ManageFavoriteDialog dlg = new ManageFavoriteDialog(this, characterData);\r
+               dlg.setFavoriteManageCallback(new FavoriteManageCallback() {\r
+\r
+                       public void selectFavorites(PartsSet partsSet) {\r
+                               // お気に入り編集ダイアログで選択されたパーツを選択表示する.\r
+                               selectPresetParts(partsSet);\r
+                       }\r
+\r
+                       public void updateFavorites(CharacterData characterData,\r
+                                       boolean savePreset) {\r
+                               // お気に入りを登録する.\r
+                               try {\r
+                                       setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
+                                       try {\r
+                                               CharacterDataPersistent persiste = CharacterDataPersistent\r
+                                                               .getInstance();\r
+                                               if (savePreset) {\r
+                                                       persiste.updateProfile(characterData);\r
+                                               }\r
+\r
+                                               persiste.saveFavorites(characterData);\r
+\r
+                                               // お気に入りが更新されたことを通知する.\r
+                                               FavoritesChangeObserver.getDefault()\r
+                                                               .notifyFavoritesChange(MainFrame.this,\r
+                                                                               characterData);\r
+\r
+                                       } finally {\r
+                                               setCursor(Cursor.getDefaultCursor());\r
+                                       }\r
+\r
+                               } catch (Exception ex) {\r
+                                       ErrorMessageHelper.showErrorDialog(MainFrame.this, ex);\r
+                               }\r
+                       }\r
+               });\r
+               WindowAdjustLocationSupport.alignRight(this, dlg, 0, true);\r
+               dlg.setVisible(true);\r
+               lastUseManageFavoritesDialog = dlg;\r
+       }\r
+\r
+       protected void onRegisterFavorite() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+               try {\r
+                       // パーツセットを生成\r
+                       PartsSet partsSet = partsSelectionManager.createPartsSet();\r
+                       if (partsSet.isEmpty()) {\r
+                               // 空のパーツセットは登録しない.\r
+                               return;\r
+                       }\r
+\r
+                       Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
+                                       .getLocalizedProperties(STRINGS_RESOURCE);\r
+\r
+                       // お気に入りに登録するパーツセットが最後に使用したお気に入りと同じ構成であれば、\r
+                       // そのお気に入り名を使用する.\r
+                       String initName = getSuggestPartsSetName(partsSet, false);\r
+\r
+                       // カラー情報の有無のチェックボックス.\r
+                       JCheckBox chkColorInfo = new JCheckBox(strings.getProperty("input.favoritesColorInfo"));\r
+                       chkColorInfo.setSelected(true);\r
+                       String partsSetId = null;\r
+                       if (initName != null && lastUsePresetParts != null) {\r
+                               partsSetId = lastUsePresetParts.getPartsSetId();\r
+                       }\r
+\r
+                       // 上書き保存の可否のチェックボックス\r
+                       JCheckBox chkOverwrite = new JCheckBox(strings.getProperty("input.favoritesOverwrite"));\r
+                       chkOverwrite.setSelected(partsSetId != null && partsSetId.length() > 0);\r
+                       chkOverwrite.setEnabled(partsSetId != null && partsSetId.length() > 0);\r
+\r
+                       // チェックボックスパネル\r
+                       Box checkboxsPanel = new Box(BoxLayout.PAGE_AXIS);\r
+                       checkboxsPanel.add(chkColorInfo);\r
+                       checkboxsPanel.add(chkOverwrite);\r
+\r
+                       // 入力ダイアログを開く\r
+                       String name = (String) JOptionPane.showInputDialog(this,\r
+                                       checkboxsPanel,\r
+                                       strings.getProperty("input.favorites"),\r
+                                       JOptionPane.QUESTION_MESSAGE,\r
+                                       null,\r
+                                       null,\r
+                                       initName == null ? "" : initName);\r
+                       if (name == null || name.trim().length() == 0) {\r
+                               return;\r
+                       }\r
+\r
+                       boolean includeColorInfo = chkColorInfo.isSelected();\r
+                       if (!includeColorInfo) {\r
+                               // カラー情報を除去する.\r
+                               partsSet.removeColorInfo();\r
+                       }\r
+\r
+                       // 新規の場合、もしくは上書きしない場合はIDを設定する.\r
+                       if (partsSetId == null || !chkOverwrite.isSelected()) {\r
+                               partsSetId = "ps" + UUID.randomUUID().toString();\r
+                       }\r
+                       partsSet.setPartsSetId(partsSetId);\r
+\r
+                       // 名前を設定する.\r
+                       partsSet.setLocalizedName(name);\r
+\r
+                       // ファイルに保存\r
+                       setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
+                       try {\r
+                               CharacterDataPersistent persiste = CharacterDataPersistent.getInstance();\r
+                               // 現在の最新情報を取り出す.\r
+                               characterData.clearPartsSets(true);\r
+                               persiste.loadFavorites(characterData);\r
+\r
+                               // お気に入りコレクションに登録\r
+                               characterData.addPartsSet(partsSet);\r
+\r
+                               persiste.saveFavorites(characterData);\r
+\r
+                               // お気に入りが更新されたことを通知する.\r
+                               FavoritesChangeObserver.getDefault().notifyFavoritesChange(\r
+                                               MainFrame.this, characterData);\r
+\r
+                       } finally {\r
+                               setCursor(Cursor.getDefaultCursor());\r
+                       }\r
+\r
+                       // 最後に選択したお気に入りにする\r
+                       lastUsePresetParts = partsSet;\r
+                       showPresetName(partsSet);\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * ランダム選択ダイアログを開く.\r
+        */\r
+       protected void onToolRandom() {\r
+               if (!characterData.isValid()) {\r
+                       Toolkit tk = Toolkit.getDefaultToolkit();\r
+                       tk.beep();\r
+                       return;\r
+               }\r
+\r
+               if (lastUsePartsRandomChooserDialog != null) {\r
+                       // 開いているダイアログがあれば、それにフォーカスを当てる.\r
+                       if (lastUsePartsRandomChooserDialog.isDisplayable()\r
+                                       && lastUsePartsRandomChooserDialog.isVisible()) {\r
+                               lastUsePartsRandomChooserDialog.requestFocus();\r
+                               return;\r
+                       }\r
+               }\r
+\r
+               // お気に入り編集ダイアログを開く\r
+               PartsRandomChooserDialog dlg = new PartsRandomChooserDialog(this,\r
+                               characterData,\r
+                               new PartsRandomChooserDialog.PartsSetSynchronizer() {\r
+                                       public PartsSet getCurrentPartsSet() {\r
+                                               // 現在のパーツセットを生成\r
+                                               return partsSelectionManager.createPartsSet();\r
+                                       }\r
+\r
+                                       public void setPartsSet(PartsSet partsSet) {\r
+                                               selectPresetParts(partsSet);\r
+                                       }\r
+\r
+                                       public boolean\r
+                                                       isExcludePartsIdentifier(PartsIdentifier partsIdentifier) {\r
+                                               Boolean exclude = randomExcludePartsIdentifierMap\r
+                                                               .get(partsIdentifier);\r
+                                               return exclude != null && exclude.booleanValue();\r
+                                       }\r
+\r
+                                       public void\r
+                                                       setExcludePartsIdentifier(PartsIdentifier partsIdentifier,\r
+                                                                       boolean exclude) {\r
+                                               randomExcludePartsIdentifierMap.put(partsIdentifier,\r
+                                                               exclude);\r
+                                       }\r
+                               });\r
+\r
+               WindowAdjustLocationSupport.alignRight(this, dlg, 0, true);\r
+               dlg.setVisible(true);\r
+               lastUsePartsRandomChooserDialog = dlg;\r
+       }\r
+\r
+       /**\r
+        * ランダム選択パーツで選択候補から除外するパーツのマップ.\r
+        */\r
+       private HashMap<PartsIdentifier, Boolean> randomExcludePartsIdentifierMap =\r
+                       new HashMap<PartsIdentifier, Boolean>();\r
+\r
+       /**\r
+        * すべての解除可能なパーツの選択を解除する。\r
+        */\r
+       protected void onDeselectAll() {\r
+               partsSelectionManager.deselectAll();\r
+       }\r
+\r
+       /**\r
+        * 単一選択カテゴリのパーツの解除を許可する。\r
+        */\r
+       protected void onDeselectableAllCategory() {\r
+               partsSelectionManager\r
+                               .setDeselectableSingleCategory( !partsSelectionManager\r
+                                               .isDeselectableSingleCategory());\r
+       }\r
+\r
+       /**\r
+        * プレビューのズームボックスの表示制御\r
+        */\r
+       protected void onEnableZoom() {\r
+               previewPane.setVisibleZoomBox( !previewPane.isVisibleZoomBox());\r
+       }\r
+\r
+       /**\r
+        * メニューバーを構築します.\r
+        *\r
+        * @return メニューバー\r
+        */\r
+       protected JMenuBar createMenuBar() {\r
+               final Properties strings = LocalizedResourcePropertyLoader\r
+                               .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);\r
+\r
+               MenuDataFactory[] menus = new MenuDataFactory[] {\r
+                               new MenuDataFactory("menu.file", new MenuDataFactory[] {\r
+                                               new MenuDataFactory("file.openProfile", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onOpenProfile();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("file.savePicture", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onSavePicture();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("file.ukagaka", new MenuDataFactory[] {\r
+                                                               new MenuDataFactory("file.saveAsUkagaka", new ActionListener() {\r
+                                                                       public void actionPerformed(ActionEvent e) {\r
+                                                                               onSaveAsUkagaka();\r
+                                                                       };\r
+                                                               }),\r
+                                                               new MenuDataFactory("file.convertUkagaka", new ActionListener() {\r
+                                                                       public void actionPerformed(ActionEvent e) {\r
+                                                                               onConvertUkagaka();\r
+                                                                       };\r
+                                                               }),\r
+                                               }),\r
+                                               null,\r
+                                               new MenuDataFactory("file.editprofile", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onEditProfile();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("file.opendir", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onBrowseProfileDir();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("file.import", new MenuDataFactory[] {\r
+                                                               new MenuDataFactory("file.importMe", new ActionListener() {\r
+                                                                       public void actionPerformed(ActionEvent e) {\r
+                                                                               onImport(null);\r
+                                                                       };\r
+                                                               }),\r
+                                                               new MenuDataFactory("file.importNew", new ActionListener() {\r
+                                                                       public void actionPerformed(ActionEvent e) {\r
+                                                                               onImportNew();\r
+                                                                       };\r
+                                                               }),\r
+                                               }),\r
+                                               new MenuDataFactory("file.export", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onExport();\r
+                                                       };\r
+                                               }),\r
+                                               new MenuDataFactory("file.manageParts", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onManageParts();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("file.preferences", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onPreferences();\r
+                                                       };\r
+                                               }),\r
+                                               null,\r
+                                               new MenuDataFactory("file.closeProfile", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onCloseProfile();\r
+                                                       }\r
+                                               }),\r
+                               }),\r
+                               new MenuDataFactory("menu.edit", new MenuDataFactory[] {\r
+                                               new MenuDataFactory("edit.search", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               openSearchDialog();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("edit.copy", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onCopy((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0);\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("edit.flipHorizontal", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onFlipHolizontal();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("edit.resetcolor", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onResetColor();\r
+                                                       }\r
+                                               }),\r
+                                               null,\r
+                                               new MenuDataFactory("edit.setDefaultPicture", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onSetDefaultPicture();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("edit.information", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onInformation();\r
+                                                       }\r
+                                               }),\r
+                                               null,\r
+                                               new MenuDataFactory("edit.deselectall", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onDeselectAll();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("edit.deselectparts", true, new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onDeselectableAllCategory();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("edit.enableAutoShrink", true, new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onClickPartsCategoryTitle(null, true);\r
+                                                       }\r
+                                               }),\r
+                                               null,\r
+                                               new MenuDataFactory("edit.enableZoomBox", true, new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onEnableZoom();\r
+                                                       }\r
+                                               }),\r
+                                               null,\r
+                                               new MenuDataFactory("edit.changeBgColor", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onChangeBgColor();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("edit.changeWallpaper", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onChangeWallpaper();\r
+                                                       }\r
+                                               }),\r
+                               }),\r
+                               new MenuDataFactory("menu.favorite", new MenuDataFactory[] {\r
+                                               new MenuDataFactory("favorite.register", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onRegisterFavorite();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("favorite.manage", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onManageFavorites();\r
+                                                       }\r
+                                               }),\r
+                                               null,\r
+                               }),\r
+                               new MenuDataFactory("menu.customlayer", new MenuDataFactory[] {\r
+                                               new MenuDataFactory("customlayer.manage", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onManageCustomLayer();\r
+                                                       }\r
+                                               }),\r
+                                               null,\r
+                               }),\r
+                               new MenuDataFactory("menu.tool",\r
+                                               new MenuDataFactory[]{new MenuDataFactory(\r
+                                                               "tool.random", new ActionListener() {\r
+                                                                       public void actionPerformed(ActionEvent e) {\r
+                                                                               onToolRandom();\r
+                                                                       }\r
+                                                               }),}),\r
+                               new MenuDataFactory("menu.help", new MenuDataFactory[] {\r
+                                               new MenuDataFactory("help.recommendations", (ActionListener) null),\r
+                                               null,\r
+                                               new MenuDataFactory("help.help", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onHelp();\r
+                                                       }\r
+                                               }),\r
+                                               new MenuDataFactory("help.forum",\r
+                                                               DesktopUtilities.createBrowseAction(\r
+                                                                               MainFrame.this,\r
+                                                                               strings.getProperty("help.forum.url"),\r
+                                                                               strings.getProperty("help.forum.description"))\r
+                                               ),\r
+                                               new MenuDataFactory("help.bugreport",\r
+                                                               DesktopUtilities.createBrowseAction(\r
+                                                                               MainFrame.this,\r
+                                                                               strings.getProperty("help.reportbugs.url"),\r
+                                                                               strings.getProperty("help.reportbugs.description"))\r
+                                               ),\r
+                                               new MenuDataFactory("help.about", new ActionListener() {\r
+                                                       public void actionPerformed(ActionEvent e) {\r
+                                                               onAbout();\r
+                                                       }\r
+                                               }),\r
+                               }), };\r
+\r
+               final MenuBuilder menuBuilder = new MenuBuilder();\r
+\r
+               JMenuBar menuBar = menuBuilder.createMenuBar(menus);\r
+\r
+               menuBuilder.getJMenu("menu.edit").addMenuListener(new MenuListener() {\r
+                       public void menuCanceled(MenuEvent e) {\r
+                               // do nothing.\r
+                       }\r
+                       public void menuDeselected(MenuEvent e) {\r
+                               // do nothing.\r
+                       }\r
+                       public void menuSelected(MenuEvent e) {\r
+                               menuBuilder.getJMenuItem("edit.copy").setEnabled(previewPane.getPreviewImage() != null);\r
+                               menuBuilder.getJMenuItem("edit.deselectparts").setSelected(\r
+                                               partsSelectionManager.isDeselectableSingleCategory());\r
+                               menuBuilder.getJMenuItem("edit.enableAutoShrink").setSelected(minimizeMode);\r
+                               menuBuilder.getJMenuItem("edit.enableZoomBox").setSelected(previewPane.isVisibleZoomBox());\r
+                       }\r
+               });\r
+               final JMenu mnuFavorites = menuBuilder.getJMenu("menu.favorite");\r
+               mnuFavorites.addMenuListener(new MenuListener() {\r
+                       public void menuCanceled(MenuEvent e) {\r
+                               // do nothing.\r
+                       }\r
+                       public void menuDeselected(MenuEvent e) {\r
+                               // do nothing.\r
+                       }\r
+                       public void menuSelected(MenuEvent e) {\r
+                               onSelectedFavoriteMenu(mnuFavorites);\r
+                       }\r
+               });\r
+               final JMenu mnuCustomLayer = menuBuilder.getJMenu("menu.customlayer");\r
+               mnuCustomLayer.addMenuListener(new MenuListener() {\r
+                       public void menuCanceled(MenuEvent e) {\r
+                               // do nothing.\r
+                       }\r
+                       public void menuDeselected(MenuEvent e) {\r
+                               // do nothing.\r
+                       }\r
+                       public void menuSelected(MenuEvent e) {\r
+                               onSelectedCustomLayerMenu(mnuCustomLayer);\r
+                       }\r
+               });\r
+\r
+               // J2SE5の場合は「パーツディレクトリを開く」コマンドは使用不可とする.\r
+               if (System.getProperty("java.version").startsWith("1.5")) {\r
+                       menuBuilder.getJMenuItem("file.opendir").setEnabled(false);\r
+               }\r
+\r
+               // お勧めサイトメニュー構築\r
+               final JMenu mnuRecomendation = menuBuilder.getJMenu("help.recommendations");\r
+               JMenu mnuHelp = menuBuilder.getJMenu("menu.help");\r
+               mnuHelp.addMenuListener(new MenuListener() {\r
+                       public void menuCanceled(MenuEvent e) {\r
+                               // do nothing.\r
+                       }\r
+                       public void menuDeselected(MenuEvent e) {\r
+                               // do nothing.\r
+                       }\r
+                       public void menuSelected(MenuEvent e) {\r
+                               onSelectedRecommendationMenu(mnuRecomendation);\r
+                       }\r
+               });\r
+\r
+               return menuBar;\r
+       }\r
+\r
+}\r
index f6f7f7a..1955114 100644 (file)
@@ -96,7 +96,7 @@ import charactermanaj.util.UIHelper;
 \r
 /**\r
  * プロファイルを選択するためのダイアログ、およびデフォルトプロファイルを開く\r
- * \r
+ *\r
  * @author seraphy\r
  */\r
 public class ProfileSelectorDialog extends JDialog {\r
@@ -171,7 +171,7 @@ public class ProfileSelectorDialog extends JDialog {
 \r
        /**\r
         * プロファイルの選択ダイアログを構築する.\r
-        * \r
+        *\r
         * @param parent\r
         *            親フレーム、もしくはnull\r
         * @param characterDatas\r
@@ -730,7 +730,7 @@ public class ProfileSelectorDialog extends JDialog {
 \r
        /**\r
         * サンプルピクチャパネルにドロップされた画像ファイルをサンプルピクチャとしてコピーします.<br>\r
-        * \r
+        *\r
         * @param dtde\r
         *            ドロップイベント\r
         */\r
@@ -823,7 +823,7 @@ public class ProfileSelectorDialog extends JDialog {
 \r
        /**\r
         * プロファイルの作成\r
-        * \r
+        *\r
         * @param makeDefault\r
         *            デフォルトのプロファイルで作成する場合\r
         */\r
diff --git a/src/main/java/charactermanaj/ui/model/CustomLayerPatternMgr.java b/src/main/java/charactermanaj/ui/model/CustomLayerPatternMgr.java
new file mode 100644 (file)
index 0000000..e45eed9
--- /dev/null
@@ -0,0 +1,176 @@
+package charactermanaj.ui.model;\r
+\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.HashSet;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
+import java.util.TreeMap;\r
+\r
+import charactermanaj.model.CustomLayerOrder;\r
+import charactermanaj.model.Layer;\r
+import charactermanaj.model.io.PartsImageCollectionParser.LayerOrderMapper;\r
+\r
+/**\r
+ * カスタムレイヤーパターンのリストを管理する。\r
+ */\r
+public class CustomLayerPatternMgr implements LayerOrderMapper {\r
+\r
+       /**\r
+        * カスタムレイヤーパターンを保持する\r
+        */\r
+       private final Map<String, List<CustomLayerOrder>> customLayerPatternMap = new TreeMap<String, List<CustomLayerOrder>>();\r
+\r
+       /**\r
+        * 現在有効にしているカスタムレイヤーパターン名を保持する\r
+        */\r
+       private final Set<String> activeCustomLayers = new HashSet<String>();\r
+\r
+       /**\r
+        * カスタムレイヤーパターンと有効にしているパターン名から現在有効なレイヤーマッピングを導出する。\r
+        * まだ導出されていないかリセットされた場合はnull\r
+        */\r
+       private Map<Layer, Integer> customLayerOrderMap;\r
+\r
+       /**\r
+        * 現在有功にしているカスタムレイヤーの順序をマッピングする\r
+        */\r
+       private void initCustomLayerOrder() {\r
+               customLayerOrderMap = new HashMap<Layer, Integer>();\r
+               for (String patternName : activeCustomLayers) {\r
+                       initCustomLayerOrder(customLayerPatternMap.get(patternName));\r
+               }\r
+       }\r
+\r
+       private void initCustomLayerOrder(List<CustomLayerOrder> customLayerOrderList) {\r
+               if (customLayerOrderList != null) {\r
+                       for (CustomLayerOrder customLayerOrder : customLayerOrderList) {\r
+                               Layer layer = customLayerOrder.getLayer();\r
+                               int layerOrder = customLayerOrder.getLayerOrder();\r
+                               customLayerOrderMap.put(layer, layerOrder);\r
+                       }\r
+               }\r
+       }\r
+\r
+       public void applyCustomLayerOrder(List<CustomLayerOrder> customLayerOrderList) {\r
+               customLayerOrderMap = new HashMap<Layer, Integer>();\r
+               initCustomLayerOrder(customLayerOrderList);\r
+       }\r
+\r
+       @Override\r
+       public int getLayerOrder(Layer layer) {\r
+               if (customLayerOrderMap == null) {\r
+                       initCustomLayerOrder();\r
+               }\r
+               Integer layerOrder = customLayerOrderMap.get(layer);\r
+               if (layerOrder == null) {\r
+                       layerOrder = layer.getOrder();\r
+               }\r
+               return layerOrder;\r
+       }\r
+\r
+       /**\r
+        * レイヤーパターンの一覧を取得する。\r
+        *\r
+        * @return map パターン名をキーとし、カスタムレイヤー順のリストを値とするマップ\r
+        */\r
+       public Map<String, List<CustomLayerOrder>> getMap() {\r
+               return customLayerPatternMap;\r
+       }\r
+\r
+       /**\r
+        * レイヤーパターンの一覧を設定する。\r
+        * (現在のパターン一覧はクリアされる。)\r
+        * @param map パターン名をキーとし、カスタムレイヤー順のリストを値とするマップ\r
+        */\r
+       public void setMap(Map<String, List<CustomLayerOrder>> map) {\r
+               customLayerPatternMap.clear();\r
+               customLayerPatternMap.putAll(map);\r
+               activeCustomLayers.retainAll(customLayerPatternMap.keySet()); // 現在存在するパターン名に絞り込む\r
+               initCustomLayerOrder();\r
+       }\r
+\r
+       /**\r
+        * レイヤーパターンの選択状態を取得する\r
+        * @param name\r
+        * @return\r
+        */\r
+       public boolean isSelected(String name) {\r
+               return activeCustomLayers.contains(name);\r
+       }\r
+\r
+       /**\r
+        * レイヤーパターンの選択状態を設定する\r
+        * @param name\r
+        * @param selected\r
+        */\r
+       public void setSelected(String name, boolean selected) {\r
+               if (customLayerPatternMap.containsKey(name)) {\r
+                       if (selected) {\r
+                               unselectConflict(name);\r
+                               activeCustomLayers.add(name);\r
+                       } else {\r
+                               activeCustomLayers.remove(name);\r
+                       }\r
+                       initCustomLayerOrder();\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 選択するレイヤーパターンと重複するレイヤーをもつパターンがすでに選択されている場合、\r
+        * その選択を解除する。\r
+        * @param name 選択するレイヤーパターン\r
+        */\r
+       private void unselectConflict(String name) {\r
+               // 指定されたパターン名が使用しているレイヤー一覧を作成する。\r
+               Set<Layer> targetLayers = new HashSet<Layer>();\r
+               List<CustomLayerOrder> targetLayerOrders = customLayerPatternMap.get(name);\r
+               if (targetLayerOrders != null) {\r
+                       for (CustomLayerOrder layerOrder : targetLayerOrders) {\r
+                               targetLayers.add(layerOrder.getLayer());\r
+                       }\r
+               }\r
+\r
+               // 現在選択しているレイヤーパターンから、\r
+               // これから選択しようとしているレイヤーを含んでいる(衝突)パターンを検出する\r
+               ArrayList<String> conflictPatterns = new ArrayList<String>();\r
+               for (String patternName : activeCustomLayers) {\r
+                       if (!patternName.equals(name)) { // 自分と同一名であれば検査の必要なし\r
+                               List<CustomLayerOrder> layerOrders = customLayerPatternMap.get(patternName);\r
+                               if (layerOrders != null) {\r
+                                       for (CustomLayerOrder layerOrder : layerOrders) {\r
+                                               Layer layer = layerOrder.getLayer();\r
+                                               if (targetLayers.contains(layer)) {\r
+                                                       // ターゲットにレイヤーが含まれている場合は、このパターンの選択は解除する\r
+                                                       conflictPatterns.add(patternName);\r
+                                                       break;\r
+                                               }\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+               // 衝突のあるパターン名を現在の選択から除外する\r
+               activeCustomLayers.removeAll(conflictPatterns);\r
+       }\r
+\r
+       /**\r
+        * 現在有効なすべてのレイヤーパターンのレイヤーマッピングを重ねたリストを取得する。\r
+        * (順序は不定である。)\r
+        * @return\r
+        */\r
+       public List<CustomLayerOrder> getMergedCustomLayerOrderList() {\r
+               List<CustomLayerOrder> mergedList = new ArrayList<CustomLayerOrder>();\r
+               for (String patternName : activeCustomLayers) {\r
+                       List<CustomLayerOrder> list = customLayerPatternMap.get(patternName);\r
+                       if (list != null) {\r
+                               for (CustomLayerOrder layerOrder : list) {\r
+                                       if (!mergedList.contains(layerOrder)) {\r
+                                               mergedList.add(layerOrder);\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+               return mergedList;\r
+       }\r
+}
\ No newline at end of file
diff --git a/src/main/java/charactermanaj/ui/model/SimpleComboBoxModel.java b/src/main/java/charactermanaj/ui/model/SimpleComboBoxModel.java
new file mode 100644 (file)
index 0000000..c7822f2
--- /dev/null
@@ -0,0 +1,160 @@
+package charactermanaj.ui.model;\r
+\r
+import java.util.AbstractList;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import javax.swing.ComboBoxModel;\r
+import javax.swing.event.EventListenerList;\r
+import javax.swing.event.ListDataEvent;\r
+import javax.swing.event.ListDataListener;\r
+\r
+/**\r
+ * コンボボックスのリストへの追加によって自動的に現在選択が設定されないシンプルな動きをするコンボボックスのモデル。\r
+ * リストの削除時に現在選択中のものが削除される場合は、現在選択は解除される。\r
+ *\r
+ * @param <S>\r
+ */\r
+public class SimpleComboBoxModel<S> extends AbstractList<S> implements ComboBoxModel {\r
+\r
+       private EventListenerList listeners = new EventListenerList();\r
+\r
+       private List<S> items;\r
+\r
+       private Object selectedItem;\r
+\r
+       public SimpleComboBoxModel() {\r
+               this(new ArrayList<S>());\r
+       }\r
+\r
+       public SimpleComboBoxModel(List<S> items) {\r
+               this.items = items;\r
+       }\r
+\r
+       public void setAll(List<S> items) {\r
+               this.items.clear();\r
+               if (items != null) {\r
+                       this.items.addAll(items);\r
+               }\r
+\r
+               ListDataListener[] ls = listeners.getListeners(ListDataListener.class);\r
+               if (ls.length > 0) {\r
+                       ListDataEvent evt = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, this.items.size());\r
+                       for (ListDataListener l : ls) {\r
+                               l.contentsChanged(evt);\r
+                       }\r
+               }\r
+       }\r
+\r
+       @Override\r
+       public S get(int index) {\r
+               return items.get(index);\r
+       }\r
+\r
+       @Override\r
+       public int size() {\r
+               return items.size();\r
+       }\r
+\r
+       @Override\r
+       public int getSize() {\r
+               return size();\r
+       }\r
+\r
+       @Override\r
+       public Object getElementAt(int index) {\r
+               return get(index);\r
+       }\r
+\r
+       @SuppressWarnings("unchecked")\r
+       public boolean add(S element) {\r
+               int found = -1;\r
+\r
+               // 自然ソート順で挿入位置を求める\r
+               int len = items.size();\r
+               for (int idx = 0; idx < len; idx++) {\r
+                       S v = items.get(idx);\r
+                       int comp = ((Comparable<S>) element).compareTo(v);\r
+                       if (comp < 0) {\r
+                               break;\r
+                       }\r
+                       found = idx;\r
+               }\r
+               add(found + 1, element);\r
+\r
+               return true;\r
+       }\r
+\r
+       @Override\r
+       public void add(int index, S element) {\r
+               items.add(index, element);\r
+\r
+               ListDataListener[] ls = listeners.getListeners(ListDataListener.class);\r
+               if (ls.length > 0) {\r
+                       ListDataEvent evt = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index);\r
+                       for (ListDataListener l : ls) {\r
+                               l.intervalAdded(evt);\r
+                       }\r
+               }\r
+       }\r
+\r
+       @Override\r
+       public S set(int index, S element) {\r
+               S old = items.set(index, element);\r
+\r
+               ListDataListener[] ls = listeners.getListeners(ListDataListener.class);\r
+               if (ls.length > 0) {\r
+                       ListDataEvent evt = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index);\r
+                       for (ListDataListener l : ls) {\r
+                               l.contentsChanged(evt);\r
+                       }\r
+               }\r
+               return old;\r
+       }\r
+\r
+       @Override\r
+       public S remove(int index) {\r
+               S old = items.remove(index);\r
+\r
+               ListDataListener[] ls = listeners.getListeners(ListDataListener.class);\r
+               if (ls.length > 0) {\r
+                       ListDataEvent evt = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index);\r
+                       for (ListDataListener l : ls) {\r
+                               l.intervalRemoved(evt);\r
+                       }\r
+               }\r
+\r
+               if (old != null && old.equals(selectedItem)) {\r
+                       setSelectedItem(null);\r
+               }\r
+\r
+               return old;\r
+       }\r
+\r
+       @Override\r
+       public void addListDataListener(ListDataListener l) {\r
+               listeners.add(ListDataListener.class, l);\r
+       }\r
+\r
+       @Override\r
+       public void removeListDataListener(ListDataListener l) {\r
+               listeners.remove(ListDataListener.class, l);\r
+       }\r
+\r
+       @Override\r
+       public void setSelectedItem(Object anItem) {\r
+               this.selectedItem = anItem;\r
+               ListDataListener[] ls = listeners.getListeners(ListDataListener.class);\r
+               if (ls.length > 0) {\r
+                       ListDataEvent evt = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, -1, -1);\r
+                       for (ListDataListener l : ls) {\r
+                               l.contentsChanged(evt);\r
+                       }\r
+               }\r
+       }\r
+\r
+       @Override\r
+       public Object getSelectedItem() {\r
+               return selectedItem;\r
+       }\r
+}
\ No newline at end of file
index 694aa1a..a1d2083 100644 (file)
@@ -5,7 +5,8 @@
        <entry key="column.partsName">Parts</entry>\r
        <entry key="column.categoryName">Category</entry>\r
        <entry key="column.layerName">Layer</entry>\r
-       <entry key="column.layerOrder">Order</entry>\r
+       <entry key="column.defaultLayerOrder">Default Order</entry>\r
+       <entry key="column.layerOrder">Actual Order</entry>\r
        <entry key="column.imagesize">Size</entry>\r
        <entry key="column.colortype">Color Mode</entry>\r
        <entry key="column.imageName">Image</entry>\r
        <entry key="column.partsName.width">80</entry>\r
        <entry key="column.categoryName.width">80</entry>\r
        <entry key="column.layerName.width">80</entry>\r
+       <entry key="column.defaultLayerOrder.width">50</entry>\r
        <entry key="column.layerOrder.width">50</entry>\r
-       <entry key="column.layerOrder.imagesize.width">50</entry>\r
-       <entry key="column.layerOrder.colortype.width">50</entry>\r
+       <entry key="column.imagesize.width">50</entry>\r
+       <entry key="column.colortype.width">50</entry>\r
        <entry key="column.imageName.width">150</entry>\r
        <entry key="column.editbtn.width">80</entry>\r
        <entry key="popupmenu.copyPath">Copy the file path</entry>\r
index dc79bf7..4e59ab8 100644 (file)
@@ -5,7 +5,8 @@
        <entry key="column.partsName">パーツ名</entry>\r
        <entry key="column.categoryName">カテゴリ名</entry>\r
        <entry key="column.layerName">レイヤー</entry>\r
-       <entry key="column.layerOrder">順序</entry>\r
+       <entry key="column.defaultLayerOrder">既定の順序</entry>\r
+       <entry key="column.layerOrder">実際の順序</entry>\r
        <entry key="column.imagesize">サイズ</entry>\r
        <entry key="column.colortype">カラーモード</entry>\r
        <entry key="column.imageName">画像ファイル</entry>\r
@@ -14,8 +15,8 @@
        <entry key="column.categoryName.width">80</entry>\r
        <entry key="column.layerName.width">80</entry>\r
        <entry key="column.layerOrder.width">50</entry>\r
-       <entry key="column.layerOrder.imagesize.width">50</entry>\r
-       <entry key="column.layerOrder.colortype.width">50</entry>\r
+       <entry key="column.imagesize.width">50</entry>\r
+       <entry key="column.colortype.width">50</entry>\r
        <entry key="column.imageName.width">150</entry>\r
        <entry key="column.editbtn.width">80</entry>\r
        <entry key="popupmenu.copyPath">ファイルパスをクリップボードにコピー</entry>\r
index 1049b28..04f3e84 100644 (file)
@@ -5,7 +5,8 @@
        <entry key="column.partsName">部件</entry>\r
        <entry key="column.categoryName">分类</entry>\r
        <entry key="column.layerName">图层</entry>\r
-       <entry key="column.layerOrder">排序</entry>\r
+       <entry key="column.defaultLayerOrder">默認排序</entry>\r
+       <entry key="column.layerOrder">實際排序</entry>\r
        <entry key="column.imagesize">大小</entry>\r
        <entry key="column.colortype">颜色模式</entry>\r
        <entry key="column.imageName">图片路径</entry>\r
@@ -14,8 +15,8 @@
        <entry key="column.categoryName.width">80</entry>\r
        <entry key="column.layerName.width">80</entry>\r
        <entry key="column.layerOrder.width">50</entry>\r
-       <entry key="column.layerOrder.imagesize.width">50</entry>\r
-       <entry key="column.layerOrder.colortype.width">50</entry>\r
+       <entry key="column.imagesize.width">50</entry>\r
+       <entry key="column.colortype.width">50</entry>\r
        <entry key="column.imageName.width">150</entry>\r
        <entry key="column.editbtn.width">80</entry>\r
        <entry key="popupmenu.copyPath">复制文件路径</entry>\r
diff --git a/src/main/resources/languages/layerordercustomizedialog.xml b/src/main/resources/languages/layerordercustomizedialog.xml
new file mode 100644 (file)
index 0000000..07f264d
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">\r
+<properties version="1.0">\r
+       <entry key="title">Layer Order Customize</entry>\r
+       <entry key="pattern.groupTitle">Layer Order Patterns</entry>\r
+       <entry key="patternName">Pattern Name</entry>\r
+       <entry key="btnSave">Save</entry>\r
+       <entry key="btnRemove">Remove</entry>\r
+       <entry key="edittable.groupTitle">Layer Order</entry>\r
+\r
+       <entry key="btnAdd">Add</entry>\r
+       <entry key="btnDelete">Delete</entry>\r
+       <entry key="btnClose">Close</entry>\r
+\r
+       <entry key="column.category">Category</entry>\r
+       <entry key="column.layer">Layer</entry>\r
+       <entry key="column.defaultOrder">Default Order</entry>\r
+       <entry key="column.order">Customized Order</entry>\r
+\r
+       <entry key="addLayerDialog.title">Add Layer</entry>\r
+       <entry key="btnOK">OK</entry>\r
+       <entry key="btnCancel">Cancel</entry>\r
+\r
+       <entry key="confirm.discardChanges.title">Discard?</entry>\r
+       <entry key="confirm.discardChanges.message">Are you sure you want to discard the changes?</entry>\r
+\r
+       <entry key="confirm.savechanges.message">Do you want to save changes? </entry>\r
+       <entry key="confirm.savechanges.title">Save?</entry>\r
+\r
+       <entry key="confirm.removepattern.message">Are you sure you want to remove the pattern? </entry>\r
+       <entry key="confirm.removepattern.title">Remove?</entry>\r
+</properties>\r
diff --git a/src/main/resources/languages/layerordercustomizedialog_ja.xml b/src/main/resources/languages/layerordercustomizedialog_ja.xml
new file mode 100644 (file)
index 0000000..69c7e7f
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">\r
+<properties version="1.0">\r
+       <entry key="title">レイヤー順序のカスタマイズ</entry>\r
+       <entry key="pattern.groupTitle">レイヤーパターン</entry>\r
+       <entry key="patternName">パターン名</entry>\r
+       <entry key="btnSave">保存</entry>\r
+       <entry key="btnRemove">削除</entry>\r
+       <entry key="edittable.groupTitle">レイヤー順序</entry>\r
+\r
+       <entry key="btnAdd">追加</entry>\r
+       <entry key="btnDelete">削除</entry>\r
+       <entry key="btnClose">閉じる</entry>\r
+\r
+       <entry key="column.category">カテゴリ</entry>\r
+       <entry key="column.layer">レイヤー</entry>\r
+       <entry key="column.defaultOrder">既定の順序</entry>\r
+       <entry key="column.order">実際の順序</entry>\r
+\r
+       <entry key="addLayerDialog.title">レイヤーの追加</entry>\r
+       <entry key="btnOK">OK</entry>\r
+       <entry key="btnCancel">キャンセル</entry>\r
+\r
+       <entry key="confirm.discardChanges.title">破棄の確認</entry>\r
+       <entry key="confirm.discardChanges.message">変更を破棄してもよろしいですか?</entry>\r
+\r
+       <entry key="confirm.savechanges.message">変更を保存しますか? </entry>\r
+       <entry key="confirm.savechanges.title">変更の確認</entry>\r
+\r
+       <entry key="confirm.removepattern.message">パターンを削除してもよろしいですか? </entry>\r
+       <entry key="confirm.removepattern.title">パターン削除の確認</entry>\r
+</properties>\r
diff --git a/src/main/resources/languages/layerordercustomizedialog_zh.xml b/src/main/resources/languages/layerordercustomizedialog_zh.xml
new file mode 100644 (file)
index 0000000..7f7055d
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">\r
+<properties version="1.0">\r
+       <entry key="title">定制图层</entry>\r
+       <entry key="pattern.groupTitle">圖案</entry>\r
+       <entry key="patternName">名称</entry>\r
+       <entry key="btnSave">保存</entry>\r
+       <entry key="btnRemove">移除</entry>\r
+       <entry key="edittable.groupTitle">配置</entry>\r
+\r
+       <entry key="btnAdd">增加</entry>\r
+       <entry key="btnDelete">移除</entry>\r
+       <entry key="btnClose">关闭</entry>\r
+\r
+       <entry key="column.category">分类</entry>\r
+       <entry key="column.layer">图层</entry>\r
+       <entry key="column.defaultOrder">默認排序</entry>\r
+       <entry key="column.order">實際排序</entry>\r
+\r
+       <entry key="addLayerDialog.title">添加圖图层</entry>\r
+       <entry key="btnOK">OK</entry>\r
+       <entry key="btnCancel">取消</entry>\r
+\r
+       <entry key="confirm.discardChanges.title">确认</entry>\r
+       <entry key="confirm.discardChanges.message">你确定要取消么?</entry>\r
+\r
+       <entry key="confirm.savechanges.message">你想保存更改嗎?</entry>\r
+       <entry key="confirm.savechanges.title">确认</entry>\r
+\r
+       <entry key="confirm.removepattern.message">您確定要刪除該圖案嗎? </entry>\r
+       <entry key="confirm.removepattern.title">确认</entry>\r
+</properties>\r
index 471d0b0..2b4282d 100644 (file)
        <entry key="menu.favorite.mnemonicDisp"></entry>\r
        <entry key="menu.favorite.ignoreMacOSX">false</entry>\r
 \r
+       <entry key="menu.customlayer.text">Custom Layer</entry>\r
+       <entry key="menu.customlayer.mnemonic">L</entry>\r
+       <entry key="menu.customlayer.mnemonicDisp"></entry>\r
+       <entry key="menu.customlayer.ignoreMacOSX">false</entry>\r
+\r
        <entry key="menu.tool.text">Tool</entry>\r
        <entry key="menu.tool.mnemonic">T</entry>\r
        <entry key="menu.tool.mnemonicDisp"></entry>\r
        <entry key="edit.copy.mnemonicDisp"></entry>\r
        <entry key="edit.copy.ignoreMacOSX">false</entry>\r
        <entry key="edit.copy.shortcut-key">control C</entry>\r
-       \r
+\r
        <entry key="edit.flipHorizontal.text">Flip Horizontal</entry>\r
        <entry key="edit.flipHorizontal.mnemonic">H</entry>\r
        <entry key="edit.flipHorizontal.mnemonicDisp"></entry>\r
 \r
        <entry key="edit.enableAutoShrink.text">Auto shrink category panels</entry>\r
        <entry key="edit.enableAutoShrink.ignoreMacOSX">false</entry>\r
-       \r
+\r
        <entry key="edit.enableZoomBox.text">Zoom</entry>\r
        <entry key="edit.enableZoomBox.mnemonic">Z</entry>\r
        <entry key="edit.enableZoomBox.mnemonicDisp"></entry>\r
        <entry key="favorite.manage.ignoreMacOSX">false</entry>\r
        <entry key="favorite.manage.shortcut-key">? M</entry>\r
 \r
+       <entry key="customlayer.manage.text">Manage Custom Layer</entry>\r
+       <entry key="customlayer.manage.mnemonic">M</entry>\r
+       <entry key="customlayer.manage.mnemonicDisp"></entry>\r
+       <entry key="customlayer.manage.ignoreMacOSX">false</entry>\r
+       <entry key="customlayer.manage.shortcut-key">? L</entry>\r
 \r
        <entry key="tool.random.text">Random</entry>\r
        <entry key="tool.random.mnemonic">R</entry>\r
index 6f1654e..6abcfdd 100644 (file)
        <entry key="menu.favorite.mnemonic">A</entry>\r
        <entry key="menu.favorite.mnemonicDisp">(A)</entry>\r
 \r
+       <entry key="menu.customlayer.text">レイヤー</entry>\r
+       <entry key="menu.customlayer.mnemonic">L</entry>\r
+       <entry key="menu.customlayer.mnemonicDisp">(L)</entry>\r
+\r
        <entry key="menu.tool.text">ツール</entry>\r
        <entry key="menu.tool.mnemonic">T</entry>\r
        <entry key="menu.tool.mnemonicDisp">(T)</entry>\r
@@ -92,7 +96,7 @@
        <entry key="edit.copy.text">画像をクリップボードにコピーする</entry>\r
        <entry key="edit.copy.mnemonic">C</entry>\r
        <entry key="edit.copy.mnemonicDisp">(C)</entry>\r
-       \r
+\r
        <entry key="edit.flipHorizontal.text">画像を左右反転する</entry>\r
        <entry key="edit.flipHorizontal.mnemonic">H</entry>\r
        <entry key="edit.flipHorizontal.mnemonicDisp">(H)</entry>\r
        <entry key="edit.information.text">情報</entry>\r
        <entry key="edit.information.mnemonic">I</entry>\r
        <entry key="edit.information.mnemonicDisp">(I)</entry>\r
-       \r
+\r
        <entry key="edit.deselectall.text">パーツの選択を解除する</entry>\r
        <entry key="edit.deselectparts.text">単一選択カテゴリのパーツを解除可能にする</entry>\r
 \r
        <entry key="favorite.manage.mnemonic">M</entry>\r
        <entry key="favorite.manage.mnemonicDisp">(M)</entry>\r
 \r
+       <entry key="customlayer.manage.text">レイヤーパターンの管理</entry>\r
+       <entry key="customlayer.manage.mnemonic">M</entry>\r
+       <entry key="customlayer.manage.mnemonicDisp">(M)</entry>\r
 \r
        <entry key="tool.random.text">ランダム</entry>\r
        <entry key="tool.random.mnemonic">R</entry>\r
        <entry key="help.help.text">ヘルプ</entry>\r
        <entry key="help.help.mnemonic">H</entry>\r
        <entry key="help.help.mnemonicDisp">(H)</entry>\r
-       \r
+\r
        <entry key="help.bugreport.text">バグレポート</entry>\r
        <entry key="help.bugreport.mnemonic">R</entry>\r
        <entry key="help.bugreport.mnemonicDisp">(R)</entry>\r
index 1e0ca98..3a4ed9f 100644 (file)
        <entry key="menu.favorite.mnemonicDisp"></entry>\r
        <entry key="menu.favorite.ignoreMacOSX">false</entry>\r
 \r
+       <entry key="menu.customlayer.text">定制图层</entry>\r
+       <entry key="menu.customlayer.mnemonic">L</entry>\r
+       <entry key="menu.customlayer.mnemonicDisp"></entry>\r
+\r
        <entry key="menu.tool.text">工具</entry>\r
        <entry key="menu.tool.mnemonic">T</entry>\r
        <entry key="menu.tool.mnemonicDisp"></entry>\r
        <entry key="edit.copy.mnemonicDisp"></entry>\r
        <entry key="edit.copy.ignoreMacOSX">false</entry>\r
        <entry key="edit.copy.shortcut-key">control C</entry>\r
-       \r
+\r
        <entry key="edit.flipHorizontal.text">水平翻转</entry>\r
        <entry key="edit.flipHorizontal.mnemonic">H</entry>\r
        <entry key="edit.flipHorizontal.mnemonicDisp"></entry>\r
 \r
        <entry key="edit.enableAutoShrink.text">自动缩放项目栏</entry>\r
        <entry key="edit.enableAutoShrink.ignoreMacOSX">false</entry>\r
-       \r
+\r
        <entry key="edit.enableZoomBox.text">缩放</entry>\r
        <entry key="edit.enableZoomBox.mnemonic">Z</entry>\r
        <entry key="edit.enableZoomBox.mnemonicDisp"></entry>\r
        <entry key="favorite.manage.ignoreMacOSX">false</entry>\r
        <entry key="favorite.manage.shortcut-key">? M</entry>\r
 \r
+       <entry key="customlayer.manage.text">管理定制图层</entry>\r
+       <entry key="customlayer.manage.mnemonic">M</entry>\r
+       <entry key="customlayer.manage.mnemonicDisp"></entry>\r
 \r
        <entry key="tool.random.text">随机生成</entry>\r
        <entry key="tool.random.mnemonic">R</entry>\r
diff --git a/src/main/resources/schema/customlayerorder.xsd b/src/main/resources/schema/customlayerorder.xsd
new file mode 100644 (file)
index 0000000..495faf2
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" \r
+       xmlns:xs="http://www.w3.org/2001/XMLSchema">\r
+       <xs:element name="custom-layer-orders">\r
+               <xs:complexType>\r
+                       <xs:sequence>\r
+                               <xs:element name="pattern" maxOccurs="unbounded" minOccurs="0">\r
+                                       <xs:complexType>\r
+                                               <xs:sequence>\r
+                                                       <xs:element name="localized-name" maxOccurs="unbounded" minOccurs="0">\r
+                                                               <xs:complexType>\r
+                                                                       <xs:simpleContent>\r
+                                                                               <xs:extension base="xs:string">\r
+                                                                                       <xs:attribute type="xs:string" name="lang" use="required"/>\r
+                                                                                       <xs:attribute type="xs:string" name="name" use="required"/>\r
+                                                                               </xs:extension>\r
+                                                                       </xs:simpleContent>\r
+                                                               </xs:complexType>\r
+                                                       </xs:element>\r
+                                                       <xs:element name="mapping" maxOccurs="unbounded" minOccurs="0">\r
+                                                               <xs:complexType>\r
+                                                                       <xs:simpleContent>\r
+                                                                               <xs:extension base="xs:string">\r
+                                                                                       <xs:attribute type="xs:string" name="category" use="required"/>\r
+                                                                                       <xs:attribute type="xs:string" name="layer" use="required"/>\r
+                                                                                       <xs:attribute type="xs:byte" name="order" use="required"/>\r
+                                                                               </xs:extension>\r
+                                                                       </xs:simpleContent>\r
+                                                               </xs:complexType>\r
+                                                       </xs:element>\r
+                                               </xs:sequence>\r
+                                               <xs:attribute type="xs:string" name="name" use="required"/>\r
+                                       </xs:complexType>\r
+                               </xs:element>\r
+                       </xs:sequence>\r
+               </xs:complexType>\r
+       </xs:element>\r
+</xs:schema>
\ No newline at end of file