OSDN Git Service

お気に入りメニューをスクロール可能にした。
authorseraphy <seraphy@5b6e9025-a2e8-4882-b233-f889982098c5>
Mon, 30 Dec 2013 22:02:44 +0000 (22:02 +0000)
committerseraphy <seraphy@5b6e9025-a2e8-4882-b233-f889982098c5>
Mon, 30 Dec 2013 22:02:44 +0000 (22:02 +0000)
お気に入りの管理をテーブル表示とし、プリセットも合わせて管理可能にした。
プロファイル編集画面のお気に入り、プリセットの表示順を名前でソート
お気に入りメニューの階層化サポート
古いキャッシュファイルの自動削除対応
お気に入り管理ダイアログで右クリックによる選択に対応
キャラクターデータフォルダ選択時に(開始前に)ワーキングセットの全クリアを可能
ウィンドウ位置調整部の共通化、
    および、お気に入り管理ダイアログをメインウィンドウの左側に設定
お気に入りの管理ダイアログをモードレスダイアログに変更。
お気に入りの変更イベント管理用のオブザーバーを独立させた。
インポート時にウェイトカーソルのまま戻らない問題の修正(おそらく)
新規インポート時に、パーツもプリセットも指定しない場合に空の構造を作成できる

git-svn-id: https://svn.sourceforge.jp/svnroot/charactermanaj/trunk@92 5b6e9025-a2e8-4882-b233-f889982098c5

36 files changed:
build.xml
dist/CharacterManaJ.app/Contents/Resources/Java/CharacterManaJ.jar
dist/CharacterManaJ.jar
dist/charactermanaj.exe
dist/java7mac/CharacterManaJ.app/Contents/Java/CharacterManaJ.jar
resources/languages/previewpanel_ja.xml
resources/languages/profileselectordialog.xml
resources/languages/profileselectordialog_ja.xml
resources/languages/selectCharatersDirDialog.xml
resources/languages/selectCharatersDirDialog_ja.xml
src/charactermanaj/model/PartsSet.java
src/charactermanaj/model/io/CharacterDataDefaultProvider.java
src/charactermanaj/model/io/CharacterDataDirectoryFile.java
src/charactermanaj/model/io/CharacterDataPersistent.java
src/charactermanaj/model/io/FilePartsDataLoader.java
src/charactermanaj/model/io/PartsImageDirectoryWatchAgentThread.java
src/charactermanaj/model/io/WorkingSetPersist.java [new file with mode: 0644]
src/charactermanaj/model/util/StartupSupport.java
src/charactermanaj/ui/ColorDialog.java
src/charactermanaj/ui/ImportWizardDialog.java
src/charactermanaj/ui/MainFrame.java
src/charactermanaj/ui/ManageFavoriteDialog.java
src/charactermanaj/ui/MenuBuilder.java
src/charactermanaj/ui/ProfileEditDialog.java
src/charactermanaj/ui/SearchPartsDialog.java
src/charactermanaj/ui/SelectCharatersDirDialog.java
src/charactermanaj/ui/model/FavoritesChangeEvent.java [new file with mode: 0644]
src/charactermanaj/ui/model/FavoritesChangeListener.java [new file with mode: 0644]
src/charactermanaj/ui/model/FavoritesChangeObserver.java [new file with mode: 0644]
src/charactermanaj/ui/scrollablemenu/JScrollableMenu.java [new file with mode: 0644]
src/charactermanaj/ui/scrollablemenu/JScrollerMenuItem.java [new file with mode: 0644]
src/charactermanaj/ui/scrollablemenu/ScrollableMenuEvent.java [new file with mode: 0644]
src/charactermanaj/ui/scrollablemenu/ScrollableMenuEventListener.java [new file with mode: 0644]
src/charactermanaj/ui/scrollablemenu/arrow-down.png [new file with mode: 0644]
src/charactermanaj/ui/scrollablemenu/arrow-up.png [new file with mode: 0644]
src/charactermanaj/ui/util/WindowAdjustLocationSupport.java [new file with mode: 0644]

index 49c88b3..66acac9 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -41,6 +41,8 @@
                                <include name="**/*.jar"/>\r
                        </fileset>\r
                </classpath>\r
+               <compilerarg value="-Xlint:deprecation" />\r
+               <compilerarg value="-Xlint:unchecked" />\r
        </javac>\r
 \r
                <!-- リソースをコピーする -->\r
                </fileset>\r
        </copy>\r
 \r
+               <!-- ソース上のリソースをコピーする -->\r
+               <copy todir="work">\r
+               <fileset dir="src">\r
+                       <exclude name="**/*.java"/>\r
+               </fileset>\r
+       </copy>\r
+\r
                <!-- JARを作成する -->\r
                <jar jarfile="${distdir}/CharacterManaJ.jar"\r
                         basedir="work"\r
index 13cd8b1..2295ec7 100755 (executable)
Binary files a/dist/CharacterManaJ.app/Contents/Resources/Java/CharacterManaJ.jar and b/dist/CharacterManaJ.app/Contents/Resources/Java/CharacterManaJ.jar differ
index 7f27c08..2295ec7 100755 (executable)
Binary files a/dist/CharacterManaJ.jar and b/dist/CharacterManaJ.jar differ
index 7e2a411..403ace1 100644 (file)
Binary files a/dist/charactermanaj.exe and b/dist/charactermanaj.exe differ
index 13cd8b1..2295ec7 100755 (executable)
Binary files a/dist/java7mac/CharacterManaJ.app/Contents/Java/CharacterManaJ.jar and b/dist/java7mac/CharacterManaJ.app/Contents/Java/CharacterManaJ.jar differ
index 08ac4b4..5cc8aa3 100644 (file)
@@ -5,7 +5,7 @@
        <entry key="tooltip.copy"><![CDATA[<html>画像をクリップボードにコピーする<br><small>(シフトキーでスクリーンイメージ取得)<small></html>]]></entry>\r
        <entry key="tooltip.changeBgColor"><![CDATA[<html>背景を設定する<br><small>(シフトキーで背景色のみ変更)</small></html>]]></entry>\r
        <entry key="tooltip.showInformation">情報を表示する</entry>\r
-       <entry key="tooltip.registerFavorites">お気に入りに追加する</entry>\r
+       <entry key="tooltip.registerFavorites"><![CDATA[<html>お気に入りに追加する<br><small>(シフトキーで「お気に入りの管理」)</small></html>]]></entry>\r
        <entry key="tooltip.flipHorizontal">画像を左右反転する</entry>\r
 \r
        <entry key="tooltip.zoompanel.pinning">固定する</entry>\r
index 206d48c..e5936e3 100644 (file)
@@ -17,7 +17,7 @@
        <entry key="description">Description</entry>\r
        <entry key="profiles">Profiles</entry>\r
        <entry key="sample-image">Sample</entry>\r
-       <entry key="dividerLocation">200</entry>\r
+       <entry key="dividerLocation">300</entry>\r
        <entry key="btn.select">Open</entry>\r
        <entry key="btn.cancel">Cancel</entry>\r
        <entry key="dropHere">dropHere</entry>\r
index ea4468a..5d466bf 100644 (file)
@@ -17,7 +17,7 @@
        <entry key="description">プロファイルの説明</entry>\r
        <entry key="profiles">プロファイル一覧</entry>\r
        <entry key="sample-image">サンプルピクチャ</entry>\r
-       <entry key="dividerLocation">200</entry>\r
+       <entry key="dividerLocation">300</entry>\r
        <entry key="btn.select">プロファイルを開く</entry>\r
        <entry key="btn.cancel">キャンセル</entry>\r
        <entry key="dropHere">ここにピクチャをドロップします</entry>\r
index 9770bc6..63bdff6 100644 (file)
@@ -8,6 +8,9 @@
 <entry key="btn.cancel">Cancel</entry>\r
 <entry key="btn.chooseDir">Browse</entry>\r
 <entry key="btn.clearRecentList">Clear recent list</entry>\r
+<entry key="btn.clearWorkingSets">Clear cache</entry>\r
 <entry key="chk.doNotAskAgein">Use this as the default and do not ask again.</entry>\r
+<entry key="confirm.clearWorkingSets">Are you sure you want to delete all cache?</entry>\r
+<entry key="confirm.clearWorkingSets.title">CONFIRM</entry>\r
 </properties>\r
 \r
index aa80b5c..a91b67d 100644 (file)
@@ -7,5 +7,8 @@
 <entry key="btn.cancel">キャンセル</entry>\r
 <entry key="btn.chooseDir">参照</entry>\r
 <entry key="btn.clearRecentList">履歴のクリア</entry>\r
+<entry key="btn.clearWorkingSets">キャッシュのクリア</entry>\r
 <entry key="chk.doNotAskAgein">次回から、このフォルダを使用する.</entry>\r
+<entry key="confirm.clearWorkingSets">全てのキャッシュをクリアしてもよろしいですか?</entry>\r
+<entry key="confirm.clearWorkingSets.title">確認</entry>\r
 </properties>\r
index 41bfe92..181e78f 100644 (file)
@@ -5,6 +5,7 @@ import java.io.Serializable;
 import java.util.AbstractMap;\r
 import java.util.ArrayList;\r
 import java.util.Arrays;\r
+import java.util.Comparator;\r
 import java.util.HashMap;\r
 import java.util.List;\r
 import java.util.Map;\r
@@ -14,8 +15,9 @@ import java.util.Set;
  * パーツセット.<br>\r
  * 各カテゴリの選択パーツと、そのパーツの色情報、および背景色をセットにしたもの.<br>\r
  * 保存する必要がなければIDおよび表示名は使用されないため、nullとなりえる.<br>\r
+ * \r
  * @author seraphy\r
- *\r
+ * \r
  */\r
 public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentifier>> implements Serializable, Cloneable {\r
 \r
@@ -25,6 +27,23 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
        private static final long serialVersionUID = 5972528889825451761L;\r
 \r
        /**\r
+        * PartsSet用のデフォルトのコンパレータ.<br>\r
+        * 名前順、ID順にソートする.<br>\r
+        */\r
+       public static final Comparator<PartsSet> DEFAULT_COMPARATOR = new Comparator<PartsSet>() {\r
+               public int compare(PartsSet o1, PartsSet o2) {\r
+                       int ret = o1.getLocalizedName().compareTo(o2.getLocalizedName());\r
+                       if (ret == 0) {\r
+                               ret = o1.getPartsSetId().compareTo(o2.getPartsSetId());\r
+                       }\r
+                       if (ret == 0) {\r
+                               ret = o1.hashCode() - o2.hashCode();\r
+                       }\r
+                       return ret;\r
+               }\r
+       };\r
+\r
+       /**\r
         * パーツセットID.<br>\r
         * 一時的な名前なしのパーツセットとして使われる場合はnull\r
         */\r
@@ -73,9 +92,13 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
 \r
        /**\r
         * 名前つきパーツセットを作成する.<br>\r
-        * @param partsSetId パーツセットID\r
-        * @param localizedName 表示名\r
-        * @param presetParts プリセットフラグ \r
+        * \r
+        * @param partsSetId\r
+        *            パーツセットID\r
+        * @param localizedName\r
+        *            表示名\r
+        * @param presetParts\r
+        *            プリセットフラグ\r
         */\r
        public PartsSet(String partsSetId, String localizedName, boolean presetParts) {\r
                this.partsSetId = partsSetId;\r
@@ -87,8 +110,11 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
         * パーツセットをディープコピーする.<br>\r
         * 現在のキャラクターデータのカテゴリインスタンスに関連づけて再生させる場合は、\r
         * resolverに現在のキャラクターデータのカテゴリリゾルバを指定します.<br>\r
-        * @param org 元オブジェクト\r
-        * @param resolver パーツセットのカテゴリを再生するためのリゾルバ、再生する必要がなければnull可\r
+        * \r
+        * @param org\r
+        *            元オブジェクト\r
+        * @param resolver\r
+        *            パーツセットのカテゴリを再生するためのリゾルバ、再生する必要がなければnull可\r
         */\r
        protected PartsSet(PartsSet org, PartsCategoryResolver resolver) {\r
                if (org == null) {\r
@@ -149,7 +175,9 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
         * パーツセットはキャラクターデータとは独立してロード・セーブされることがあるため、同じ情報でも異なるインスタンスとなる場合があり、\r
         * これを是正するために、このメソッドを使用します.<br>\r
         * リゾルバから取得できないパーツカテゴリは除去されます.<br>\r
-        * @param resolver リゾルバ\r
+        * \r
+        * @param resolver\r
+        *            リゾルバ\r
         * @return 互換性のあるパーツセット\r
         */\r
        public PartsSet createCompatible(PartsCategoryResolver resolver) {\r
@@ -162,6 +190,7 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
 \r
        /**\r
         * パーツセットをディープコピーする.<br>\r
+        * \r
         * @return コピーされたパーツセット\r
         */\r
        @Override\r
@@ -208,8 +237,8 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
        }\r
        \r
        /**\r
-        * プリセット用パーツセットであるか?\r
-        * これがfalseの場合は一時的なパーツセットか、もしくはお気に入り用である.<br>\r
+        * プリセット用パーツセットであるか? これがfalseの場合は一時的なパーツセットか、もしくはお気に入り用である.<br>\r
+        * \r
         * @return プリセット用パーツセットである場合\r
         */\r
        public boolean isPresetParts() {\r
@@ -223,6 +252,7 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
        /**\r
         * バックグラウンドカラーを取得する.<br>\r
         * 設定されていなければnull.<br>\r
+        * \r
         * @return バックグラウンドカラー、もしくはnull\r
         */\r
        public Color getBgColor() {\r
@@ -233,7 +263,9 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
         * アフィン変換用パラメータを指定する.<br>\r
         * 配列は4または6でなければならない.<br>\r
         * アフィン変換しない場合はnull\r
-        * @param affineTransformParameter 変換パラメータ(4または6個の要素)、もしくはnull\r
+        * \r
+        * @param affineTransformParameter\r
+        *            変換パラメータ(4または6個の要素)、もしくはnull\r
         */\r
        public void setAffineTransformParameter(double[] affineTransformParameter) {\r
                if (affineTransformParameter != null && !(affineTransformParameter.length == 4 || affineTransformParameter.length == 6)) {\r
@@ -245,6 +277,7 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
        /**\r
         * アフィン変換用のパラメータを取得する.<br>\r
         * 変換しない場合はnull.<br>\r
+        * \r
         * @return アフィン変換用のパラメータ、またはnull\r
         */\r
        public double[] getAffineTransformParameter() {\r
@@ -262,6 +295,7 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
        /**\r
         * プリセットIDを取得する.<br>\r
         * 一時的なパーツセットである場合はnull\r
+        * \r
         * @return プリセットID、またはnull\r
         */\r
        public String getPartsSetId() {\r
@@ -271,6 +305,7 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
        /**\r
         * プリセット名を取得する.<br>\r
         * 一時的なパーツセットである場合はnull\r
+        * \r
         * @return プリセット名、またはnull\r
         */\r
        public String getLocalizedName() {\r
@@ -288,7 +323,9 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
        /**\r
         * パーツカラー情報を取得する.<br>\r
         * カラー情報が関連づけられていない場合はnullが返される.<br>\r
-        * @param partsIdentifier パーツ識別子\r
+        * \r
+        * @param partsIdentifier\r
+        *            パーツ識別子\r
         * @return カラー情報、もしくはnull\r
         */\r
        public PartsColorInfo getColorInfo(PartsIdentifier partsIdentifier) {\r
@@ -301,9 +338,13 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
         * partsNameがnullまたは空文字の場合はカテゴリのみ登録する.<br>\r
         * これにより、そのカテゴリに選択がないことを示す.<br>\r
         * 複数選択可能なカテゴリの場合、複数回の呼び出しで登録する.(登録順)<br>\r
-        * @param category カテゴリ\r
-        * @param partsIdentifier パーツ識別子、またはnull\r
-        * @param partsColorInfo パーツの色情報、なければnull可\r
+        * \r
+        * @param category\r
+        *            カテゴリ\r
+        * @param partsIdentifier\r
+        *            パーツ識別子、またはnull\r
+        * @param partsColorInfo\r
+        *            パーツの色情報、なければnull可\r
         */\r
        public void appendParts(PartsCategory category, PartsIdentifier partsIdentifier, PartsColorInfo partsColorInfo) {\r
                if (category == null) {\r
@@ -332,7 +373,9 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
        /**\r
         * パーツセットが構造的に一致するか検証します.<br>\r
         * nullの場合は常にfalseとなります.<br>\r
-        * @param other 比較対象、null可\r
+        * \r
+        * @param other\r
+        *            比較対象、null可\r
         * @return パーツ構成が一致すればtrue、そうでなければfalse\r
         */\r
        public boolean isSameStructure(PartsSet other) {\r
@@ -356,8 +399,11 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
         * 2つのパーツセットが構造的に一致するか検証します.<br>\r
         * いずれか一方がnullであればfalseを返します.双方がnullであればtrueを返します.<br>\r
         * 双方がnullでなければ{@link #isSameStructure(PartsSet)}で判定されます.<br>\r
-        * @param a 比較対象1、null可\r
-        * @param b 比較対象2、null可\r
+        * \r
+        * @param a\r
+        *            比較対象1、null可\r
+        * @param b\r
+        *            比較対象2、null可\r
         * @return パーツ構成が一致すればtrue、そうでなければfalse\r
         */\r
        public static boolean isSameStructure(PartsSet a, PartsSet b) {\r
@@ -372,11 +418,12 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
 \r
        /**\r
         * 保持しているパーツ識別子のカラー情報と同一のカラー情報をもっているか判定します.<br>\r
-        * 相手側はカテゴリや順序を問わず、少なくとも自分と同じパーツ識別子をもっていれば足りるため、\r
-        * パーツ構成が同一であるかの判定は行いません.<br>\r
+        * 相手側はカテゴリや順序を問わず、少なくとも自分と同じパーツ識別子をもっていれば足りるため、 パーツ構成が同一であるかの判定は行いません.<br>\r
         * パーツ構造を含めて判定を行う場合は事前に{@link #isSameStructure(PartsSet)}を呼び出します.<br>\r
         * nullの場合は常にfalseとなります.<br>\r
-        * @param other 判定先、null可\r
+        * \r
+        * @param other\r
+        *            判定先、null可\r
         * @return 同一であればtrue、そうでなければfalse\r
         */\r
        public boolean isSameColor(PartsSet other) {\r
@@ -397,13 +444,15 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
        \r
        /**\r
         * 引数aが保持しているパーツ識別子のカラー情報と同一のカラー情報を引数bがもっているか判定します.<br>\r
-        * 引数b側はカテゴリや順序を問わず、少なくとも引数aと同じパーツ識別子をもっていれば足りるため、\r
-        * パーツ構成が同一であるかの判定は行いません.<br>\r
+        * 引数b側はカテゴリや順序を問わず、少なくとも引数aと同じパーツ識別子をもっていれば足りるため、 パーツ構成が同一であるかの判定は行いません.<br>\r
         * パーツ構造を含めて判定を行う場合は事前に{@link #isSameStructure(PartsSet, PartsSet)}を呼び出します.<br>\r
         * 双方がnullであればtrueとなります.<br>\r
         * いずれか一方がnullの場合はfalseとなります.<br>\r
-        * @param a 対象1、null可\r
-        * @param b 対象2、null可\r
+        * \r
+        * @param a\r
+        *            対象1、null可\r
+        * @param b\r
+        *            対象2、null可\r
         * @return 同一であればtrue、そうでなければfalse\r
         */\r
        public static boolean isSameColor(PartsSet a, PartsSet b) {\r
@@ -418,6 +467,7 @@ public final class PartsSet extends AbstractMap<PartsCategory, List<PartsIdentif
        \r
        /**\r
         * このパーツセットが名前をもっているか?\r
+        * \r
         * @return 名前がある場合はtrue、設定されていないか空文字の場合はfalse\r
         */\r
        public boolean hasName() {\r
index 84c794c..f902a35 100644 (file)
@@ -189,21 +189,22 @@ public class CharacterDataDefaultProvider {
                                                return pathname.isFile() && name.endsWith(".xml");\r
                                        }\r
                                });\r
+                               if (files == null) {\r
+                                       files = new File[0];\r
+                               }\r
                                CharacterDataPersistent persist = CharacterDataPersistent\r
                                                .getInstance();\r
-                               if (files != null) {\r
-                                       for (File file : files) {\r
-                                               try {\r
-                                                       URI docBase = file.toURI();\r
-                                                       CharacterData cd = persist.loadProfile(docBase);\r
-                                                       if (cd != null && cd.isValid()) {\r
-                                                               String name = file.getName();\r
-                                                               templateNameMap.put(name, cd.getName());\r
-                                                       }\r
-                                               } catch (IOException ex) {\r
-                                                       logger.log(Level.WARNING,\r
-                                                                       "failed to read templatedir." + file, ex);\r
+                               for (File file : files) {\r
+                                       try {\r
+                                               URI docBase = file.toURI();\r
+                                               CharacterData cd = persist.loadProfile(docBase);\r
+                                               if (cd != null && cd.isValid()) {\r
+                                                       String name = file.getName();\r
+                                                       templateNameMap.put(name, cd.getName());\r
                                                }\r
+                                       } catch (IOException ex) {\r
+                                               logger.log(Level.WARNING, "failed to read templatedir."\r
+                                                               + file, ex);\r
                                        }\r
                                }\r
                        }\r
index 23c87bb..453bcb6 100644 (file)
@@ -15,6 +15,7 @@ import charactermanaj.util.FileNameNormalizer;
 \r
 /**\r
  * ディレクトリをアーカイブと見立てる\r
+ * \r
  * @author seraphy\r
  */\r
 public class CharacterDataDirectoryFile extends AbstractCharacterDataArchiveFile {\r
@@ -68,9 +69,12 @@ public class CharacterDataDirectoryFile extends AbstractCharacterDataArchiveFile
        \r
        /**\r
         * アーカイブファイルをベースとしたURIを返す.<br>\r
-        * @param name コンテンツ名\r
+        * \r
+        * @param name\r
+        *            コンテンツ名\r
         * @return URI\r
-        * @throws IOException URIを生成できない場合\r
+        * @throws IOException\r
+        *             URIを生成できない場合\r
         */\r
        protected URI getContentURI(String name) throws IOException {\r
                return new File(baseDir, name).toURI();\r
@@ -89,9 +93,10 @@ public class CharacterDataDirectoryFile extends AbstractCharacterDataArchiveFile
        }\r
        \r
        /**\r
-        * このディレクトリに対してターゲットプロファイルのディレクトリがかぶっているか?\r
-        * つまり、ターゲット自身のディレクトリをソースとしていないか?\r
-        * @param characterData ソースプロファイル\r
+        * このディレクトリに対してターゲットプロファイルのディレクトリがかぶっているか? つまり、ターゲット自身のディレクトリをソースとしていないか?\r
+        * \r
+        * @param characterData\r
+        *            ソースプロファイル\r
         * @return 被っている場合はtrue、被っていない場合はfalse\r
         */\r
        protected boolean isOverlapped(CharacterData characterData) {\r
@@ -134,14 +139,17 @@ public class CharacterDataDirectoryFile extends AbstractCharacterDataArchiveFile
                // ファイル名をノーマライズする\r
                FileNameNormalizer normalizer = FileNameNormalizer.getDefault();\r
                \r
-               for (File file : dir.listFiles()) {\r
-                       String name = normalizer.normalize(file.getName());\r
-                       String entryName = prefix + name;\r
-                       if (file.isDirectory()) {\r
-                               // エントリ名の区切り文字は常に「/」とする. (ZIP/JARのentry互換のため)\r
-                               load(file, entryName + "/");\r
-                       } else {\r
-                               addEntry(new DirFileContent(file, entryName));\r
+               File[] files = dir.listFiles();\r
+               if (files != null) {\r
+                       for (File file : files) {\r
+                               String name = normalizer.normalize(file.getName());\r
+                               String entryName = prefix + name;\r
+                               if (file.isDirectory()) {\r
+                                       // エントリ名の区切り文字は常に「/」とする. (ZIP/JARのentry互換のため)\r
+                                       load(file, entryName + "/");\r
+                               } else {\r
+                                       addEntry(new DirFileContent(file, entryName));\r
+                               }\r
                        }\r
                }\r
        }\r
index 38fb6b7..8f7aed0 100644 (file)
@@ -41,12 +41,10 @@ import charactermanaj.model.CharacterData;
 import charactermanaj.model.Layer;\r
 import charactermanaj.model.PartsCategory;\r
 import charactermanaj.model.io.CharacterDataDefaultProvider.DefaultCharacterDataVersion;\r
-import charactermanaj.ui.MainFrame;\r
 import charactermanaj.util.DirectoryConfig;\r
 import charactermanaj.util.FileNameNormalizer;\r
 import charactermanaj.util.FileUserData;\r
 import charactermanaj.util.UserData;\r
-import charactermanaj.util.UserDataFactory;\r
 \r
 public class CharacterDataPersistent {\r
 \r
@@ -352,7 +350,11 @@ public class CharacterDataPersistent {
                        return;\r
                }\r
 \r
-               for (File dir : dataDir.listFiles()) {\r
+               File[] dirs = dataDir.listFiles();\r
+               if (dirs == null) {\r
+                       dirs = new File[0];\r
+               }\r
+               for (File dir : dirs) {\r
                        if (!dir.isDirectory()) {\r
                                continue;\r
                        }\r
@@ -439,7 +441,7 @@ public class CharacterDataPersistent {
                                .newFixedThreadPool(numOfProcessors);\r
                try {\r
                        // キャラクターデータ対象ディレクトリを列挙し、並列に解析する\r
-                       for (File dir : baseDir.listFiles(new FileFilter() {\r
+                       File[] dirs = baseDir.listFiles(new FileFilter() {\r
                                public boolean accept(File pathname) {\r
                                        boolean accept = pathname.isDirectory()\r
                                                        && !pathname.getName().startsWith(".");\r
@@ -449,7 +451,11 @@ public class CharacterDataPersistent {
                                        }\r
                                        return accept;\r
                                }\r
-                       })) {\r
+                       });\r
+                       if (dirs == null) {\r
+                               dirs = new File[0];\r
+                       }\r
+                       for (File dir : dirs) {\r
                                String path = normalizer.normalize(dir.getPath());\r
                                final File normDir = new File(path);\r
 \r
@@ -721,13 +727,9 @@ public class CharacterDataPersistent {
                }\r
 \r
                // ワーキングセットの削除\r
-               UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
-               UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(\r
-                               cd.getDocBase(), MainFrame.WORKINGSET_FILE_SUFFIX);\r
-               if (workingSetXmlData != null && workingSetXmlData.exists()) {\r
-                       logger.log(Level.INFO, "remove file: " + workingSetXmlData);\r
-                       workingSetXmlData.delete();\r
-               }\r
+               // XML形式でのワーキングセットの保存\r
+               WorkingSetPersist workingSetPersist = WorkingSetPersist.getInstance();\r
+               workingSetPersist.removeWorkingSet(cd);\r
 \r
                // xmlファイルの拡張子を変更することでキャラクター定義として認識させない.\r
                // (削除に失敗するケースに備えて先にリネームする.)\r
@@ -771,8 +773,11 @@ public class CharacterDataPersistent {
                        return;\r
                }\r
                if (file.isDirectory()) {\r
-                       for (File child : file.listFiles()) {\r
-                               removeRecursive(child);\r
+                       File[] children = file.listFiles();\r
+                       if (children != null) {\r
+                               for (File child : children) {\r
+                                       removeRecursive(child);\r
+                               }\r
                        }\r
                }\r
                if (!file.delete()) {\r
index e3e422f..924080f 100644 (file)
@@ -15,8 +15,9 @@ import charactermanaj.util.FileNameNormalizer;
 \r
 /**\r
  * ディレクトリを指定して、そこからキャラクターのパーツデータをロードするローダー.<br>\r
+ * \r
  * @author seraphy\r
- *\r
+ * \r
  */\r
 public class FilePartsDataLoader implements PartsDataLoader {\r
 \r
@@ -49,7 +50,7 @@ public class FilePartsDataLoader implements PartsDataLoader {
                        if (!searchDir.exists() || !searchDir.isDirectory()) {\r
                                continue;\r
                        }\r
-                       for (File imgFile : searchDir.listFiles(new FileFilter() {\r
+                       File[] imgFiles = searchDir.listFiles(new FileFilter() {\r
                                public boolean accept(File pathname) {\r
                                        if (pathname.isFile()) {\r
                                                String lcfname = pathname.getName().toLowerCase();\r
@@ -57,7 +58,11 @@ public class FilePartsDataLoader implements PartsDataLoader {
                                        }\r
                                        return false;\r
                                }\r
-                       })) {\r
+                       });\r
+                       if (imgFiles == null) {\r
+                               imgFiles = new File[0];\r
+                       }\r
+                       for (File imgFile : imgFiles) {\r
                                String partsName = normalizer.normalize(imgFile.getName());\r
 \r
                                int extpos = partsName.lastIndexOf(".");\r
index 9c51fcf..bec6494 100644 (file)
@@ -153,6 +153,7 @@ public class PartsImageDirectoryWatchAgentThread implements Runnable {
        \r
        /**\r
         * 監視を停止する.<br>\r
+        * \r
         * @return 停止した場合はtrue、すでに停止していたか開始されていない場合はfalse\r
         */\r
        private boolean stop() {\r
@@ -214,8 +215,11 @@ public class PartsImageDirectoryWatchAgentThread implements Runnable {
        /**\r
         * 監視を行う.<br>\r
         * 停止フラグが設定されるか、割り込みされた場合は処理を中断してInterruptedException例外を返して終了する.<br>\r
-        * @param notifier 通知するためのオブジェクト\r
-        * @throws InterruptedException 割り込みされた場合\r
+        * \r
+        * @param notifier\r
+        *            通知するためのオブジェクト\r
+        * @throws InterruptedException\r
+        *             割り込みされた場合\r
         */\r
        protected void watch(Runnable notifier) throws InterruptedException {\r
                if (baseDir == null || !baseDir.exists() || !baseDir.isDirectory()) {\r
@@ -232,11 +236,15 @@ public class PartsImageDirectoryWatchAgentThread implements Runnable {
                                File watchDir = new File(baseDir, layer.getDir());\r
                                ArrayList<String> files = new ArrayList<String>();\r
                                if (watchDir.exists() && watchDir.isDirectory()) {\r
-                                       for (File file : watchDir.listFiles(new FileFilter() {\r
+                                       File[] list = watchDir.listFiles(new FileFilter() {\r
                                                public boolean accept(File pathname) {\r
                                                        return pathname.isFile() && pathname.getName().toLowerCase().endsWith(".png");\r
                                                }\r
-                                       })) {\r
+                                       });\r
+                                       if (list == null) {\r
+                                               list = new File[0];\r
+                                       }\r
+                                       for (File file : list) {\r
                                                if (Thread.interrupted() || stopFlag) {\r
                                                        throw new InterruptedException();\r
                                                }\r
@@ -278,7 +286,9 @@ public class PartsImageDirectoryWatchAgentThread implements Runnable {
        \r
        /**\r
         * イベントリスナを登録する\r
-        * @param l リスナ\r
+        * \r
+        * @param l\r
+        *            リスナ\r
         */\r
        public void addPartsImageDirectoryWatchListener(PartsImageDirectoryWatchListener l) {\r
                if (l != null) {\r
@@ -291,7 +301,9 @@ public class PartsImageDirectoryWatchAgentThread implements Runnable {
        \r
        /**\r
         * イベントリスナを登録解除する\r
-        * @param l リスナ\r
+        * \r
+        * @param l\r
+        *            リスナ\r
         */\r
        public void removePartsImageDirectoryWatchListener(PartsImageDirectoryWatchListener l) {\r
                if (l != null) {\r
diff --git a/src/charactermanaj/model/io/WorkingSetPersist.java b/src/charactermanaj/model/io/WorkingSetPersist.java
new file mode 100644 (file)
index 0000000..2106bec
--- /dev/null
@@ -0,0 +1,150 @@
+package charactermanaj.model.io;\r
+\r
+import java.io.File;\r
+import java.io.FileFilter;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.util.logging.Level;\r
+import java.util.logging.Logger;\r
+\r
+import charactermanaj.model.CharacterData;\r
+import charactermanaj.model.WorkingSet;\r
+import charactermanaj.model.WorkingSet2;\r
+import charactermanaj.util.UserData;\r
+import charactermanaj.util.UserDataFactory;\r
+\r
+/**\r
+ * ワーキングセットの保存と復元.<br>\r
+ * \r
+ * @author seraphy\r
+ */\r
+public class WorkingSetPersist {\r
+\r
+       /**\r
+        * ロガー\r
+        */\r
+       private static final Logger logger = Logger\r
+                       .getLogger(WorkingSetPersist.class.getName());\r
+\r
+       /**\r
+        * ワーキングセットのサフィックス.\r
+        */\r
+       public static final String WORKINGSET_FILE_SUFFIX = "workingset.xml";\r
+\r
+       private static final WorkingSetPersist singletion = new WorkingSetPersist();\r
+\r
+       public static WorkingSetPersist getInstance() {\r
+               return singletion;\r
+       }\r
+\r
+       /**\r
+        * すべてのワーキングセットをクリアする.<br>\r
+        */\r
+       public void removeAllWorkingSet() {\r
+               UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
+               File dir = userDataFactory.getSpecialDataDir("foo-"\r
+                               + WORKINGSET_FILE_SUFFIX);\r
+               if (dir.exists() && dir.isDirectory()) {\r
+                       File[] files = dir.listFiles(new FileFilter() {\r
+                               public boolean accept(File pathname) {\r
+                                       return pathname.isFile()\r
+                                                       && pathname.getName().endsWith(\r
+                                                                       WORKINGSET_FILE_SUFFIX);\r
+                               }\r
+                       });\r
+                       if (files == null) {\r
+                               logger.log(Level.WARNING, "dir access failed. " + dir);\r
+                               return;\r
+                       }\r
+                       for (File file : files) {\r
+                               boolean success = file.delete();\r
+                               logger.log(Level.INFO, "remove file: " + file + " /success="\r
+                                               + success);\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * ワーキングセットを削除する.\r
+        * \r
+        * @param cd\r
+        *            対象のキャラクターデータ\r
+        */\r
+       public void removeWorkingSet(CharacterData cd) {\r
+               UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
+               UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(\r
+                               cd.getDocBase(), WORKINGSET_FILE_SUFFIX);\r
+               if (workingSetXmlData != null && workingSetXmlData.exists()) {\r
+                       logger.log(Level.INFO, "remove file: " + workingSetXmlData);\r
+                       workingSetXmlData.delete();\r
+               }\r
+       }\r
+\r
+       /**\r
+        * ワーキングセットを保存する.<br>\r
+        * ワーキングセットインスタンスには、あらかじめ全て設定しておく必要がある.<br>\r
+        * \r
+        * @param workingSet\r
+        *            ワーキングセット\r
+        * @throws IOException\r
+        *             失敗\r
+        */\r
+       public void saveWorkingSet(WorkingSet workingSet) throws IOException {\r
+               if (workingSet == null) {\r
+                       throw new IllegalArgumentException();\r
+               }\r
+               CharacterData characterData = workingSet.getCharacterData();\r
+               if (characterData == null) {\r
+                       throw new IllegalArgumentException("character-data must be set.");\r
+               }\r
+\r
+               // XML形式でのワーキングセットの保存\r
+               UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
+               UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(\r
+                               characterData.getDocBase(), WORKINGSET_FILE_SUFFIX);\r
+               OutputStream outstm = workingSetXmlData.getOutputStream();\r
+               try {\r
+                       WorkingSetXMLWriter workingSetXmlWriter = new WorkingSetXMLWriter();\r
+                       workingSetXmlWriter.writeWorkingSet(workingSet, outstm);\r
+               } finally {\r
+                       outstm.close();\r
+               }\r
+       }\r
+\r
+       /**\r
+        * ワーキングセットを取得する.<br>\r
+        * \r
+        * @param characterData\r
+        *            対象のキャラクターデータ\r
+        * @return ワーキングセット、なければnull\r
+        * @throws IOException\r
+        *             読み込みに失敗した場合\r
+        */\r
+       public WorkingSet2 loadWorkingSet(CharacterData characterData)\r
+                       throws IOException {\r
+               if (characterData == null) {\r
+                       throw new IllegalArgumentException();\r
+               }\r
+               // XML形式でのワーキングセットの復元\r
+               UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
+               UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(\r
+                               characterData.getDocBase(), WORKINGSET_FILE_SUFFIX);\r
+               if (workingSetXmlData == null || !workingSetXmlData.exists()) {\r
+                       // 保存されていない場合\r
+                       return null;\r
+               }\r
+               WorkingSet2 workingSet2;\r
+\r
+               InputStream is = workingSetXmlData.openStream();\r
+               try {\r
+                       WorkingSetXMLReader WorkingSetXMLReader = new WorkingSetXMLReader();\r
+                       workingSet2 = WorkingSetXMLReader.loadWorkingSet(is);\r
+\r
+               } finally {\r
+                       is.close();\r
+               }\r
+\r
+               return workingSet2;\r
+       }\r
+}\r
index 3f8c12c..d44005b 100644 (file)
@@ -9,8 +9,8 @@ import java.util.logging.Logger;
 \r
 import charactermanaj.model.AppConfig;\r
 import charactermanaj.model.WorkingSet;\r
+import charactermanaj.model.io.WorkingSetPersist;\r
 import charactermanaj.model.io.WorkingSetXMLWriter;\r
-import charactermanaj.ui.MainFrame;\r
 import charactermanaj.ui.RecentCharactersDir;\r
 import charactermanaj.util.FileUserData;\r
 import charactermanaj.util.UserData;\r
@@ -42,6 +42,7 @@ public abstract class StartupSupport {
                                                        new PurgeOldLogs(),\r
                                                        new ConvertRecentCharDirsSerToXmlProps(),\r
                                                        new ConvertWorkingSetSerToXml(),\r
+                                                       new PurgeOldCaches(),\r
                                        };\r
                                        for (StartupSupport startup : startups) {\r
                                                logger.log(Level.FINE, "startup operation start. class="\r
@@ -119,51 +120,54 @@ class ConvertWorkingSetSerToXml extends StartupSupport {
 \r
        @Override\r
        public void doStartup() {\r
-               // 旧形式(ver0.991以前)\r
-               UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
                final String FILENAME = "workingset.ser";\r
-               File dir = userDataFactory.getSpecialDataDir(FILENAME);\r
                try {\r
+                       UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
+                       File dir = userDataFactory.getSpecialDataDir(FILENAME);\r
+                       if (!dir.exists()) {\r
+                               return;\r
+                       }\r
                        File[] files = dir.listFiles(new FileFilter() {\r
                                public boolean accept(File pathname) {\r
                                        String name = pathname.getName();\r
                                        return name.endsWith(FILENAME);\r
                                }\r
                        });\r
-                       if (files != null) {\r
-                               WorkingSetXMLWriter wr = new WorkingSetXMLWriter();\r
-                               for (File file : files) {\r
-                                       FileUserData fileData = new FileUserData(file);\r
-                                       if (fileData.exists()) {\r
-                                               try {\r
-                                                       // serファイルをデシリアライズする.\r
-                                                       WorkingSet ws = (WorkingSet) fileData.load();\r
-                                                       URI docBase = ws.getCharacterDocBase();\r
-                                                       if (docBase != null) {\r
-                                                               // XML形式で保存しなおす.\r
-                                                               UserData workingSetXmlData = userDataFactory\r
-                                                                               .getMangledNamedUserData(\r
-                                                                                               docBase,\r
-                                                                                               MainFrame.WORKINGSET_FILE_SUFFIX);\r
-                                                               if (!workingSetXmlData.exists()) {\r
-                                                                       // XML形式データがまだない場合のみ保存しなおす.\r
-                                                                       OutputStream outstm = workingSetXmlData\r
-                                                                                       .getOutputStream();\r
-                                                                       try {\r
-                                                                               wr.writeWorkingSet(ws, outstm);\r
-                                                                       } finally {\r
-                                                                               outstm.close();\r
-                                                                       }\r
+                       if (files == null) {\r
+                               logger.log(Level.WARNING, "cache-dir access failed. " + dir);\r
+                               return;\r
+                       }\r
+                       WorkingSetXMLWriter wr = new WorkingSetXMLWriter();\r
+                       for (File file : files) {\r
+                               FileUserData fileData = new FileUserData(file);\r
+                               if (fileData.exists()) {\r
+                                       try {\r
+                                               // serファイルをデシリアライズする.\r
+                                               WorkingSet ws = (WorkingSet) fileData.load();\r
+                                               URI docBase = ws.getCharacterDocBase();\r
+                                               if (docBase != null) {\r
+                                                       // XML形式で保存しなおす.\r
+                                                       UserData workingSetXmlData = userDataFactory\r
+                                                                       .getMangledNamedUserData(docBase,\r
+                                                                                       WorkingSetPersist.WORKINGSET_FILE_SUFFIX);\r
+                                                       if (!workingSetXmlData.exists()) {\r
+                                                               // XML形式データがまだない場合のみ保存しなおす.\r
+                                                               OutputStream outstm = workingSetXmlData\r
+                                                                               .getOutputStream();\r
+                                                               try {\r
+                                                                       wr.writeWorkingSet(ws, outstm);\r
+                                                               } finally {\r
+                                                                       outstm.close();\r
                                                                }\r
                                                        }\r
+                                               }\r
 \r
-                                                       // serファイルは削除する.\r
-                                                       fileData.delete();\r
+                                               // serファイルは削除する.\r
+                                               fileData.delete();\r
 \r
-                                               } catch (Exception ex) {\r
-                                                       logger.log(Level.WARNING, FILENAME\r
-                                                                       + " convert failed.", ex);\r
-                                               }\r
+                                       } catch (Exception ex) {\r
+                                               logger.log(Level.WARNING,\r
+                                                               FILENAME + " convert failed.", ex);\r
                                        }\r
                                }\r
                        }\r
@@ -194,13 +198,21 @@ class PurgeOldLogs extends StartupSupport {
                        AppConfig appConfig = AppConfig.getInstance();\r
                        long purgeOldLogsMillSec = appConfig.getPurgeLogDays() * 24L * 3600L * 1000L;\r
                        if (purgeOldLogsMillSec > 0) {\r
-                               long purgeThresold = System.currentTimeMillis() - purgeOldLogsMillSec;\r
-                               for (File file : logsDir.listFiles()) {\r
+                               File[] files = logsDir.listFiles();\r
+                               if (files == null) {\r
+                                       logger.log(Level.WARNING, "log-dir access failed.");\r
+                                       return;\r
+                               }\r
+                               long purgeThresold = System.currentTimeMillis()\r
+                                               - purgeOldLogsMillSec;\r
+                               for (File file : files) {\r
                                        try {\r
                                                String name = file.getName();\r
-                                               if (file.isFile() && file.canWrite() && name.endsWith(".log")) {\r
+                                               if (file.isFile() && file.canWrite()\r
+                                                               && name.endsWith(".log")) {\r
                                                        long lastModified = file.lastModified();\r
-                                                       if (lastModified > 0 && lastModified < purgeThresold) {\r
+                                                       if (lastModified > 0\r
+                                                                       && lastModified < purgeThresold) {\r
                                                                boolean result = file.delete();\r
                                                                logger.log(Level.INFO, "remove file " + file\r
                                                                                + "/succeeded=" + result);\r
@@ -208,8 +220,59 @@ class PurgeOldLogs extends StartupSupport {
                                                }\r
 \r
                                        } catch (Exception ex) {\r
-                                               logger.log(Level.WARNING, "remove file failed. " + file, ex);\r
+                                               logger.log(Level.WARNING,\r
+                                                               "remove file failed. " + file, ex);\r
+                                       }\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+/**\r
+ * 古いキャッシュファイルを消去する.<br>\r
+ * -character.xml-cache.ser, -favorites.serは、直接xmlでの読み込みになったため、 ただちに消去しても問題ない.<br>\r
+ * recent-character.serは、使用されなくなったため、ただちに消去して良い.<br>\r
+ * mangled_info.xmlは、*.serを消去したあとには不要となるため、消去する.<br>\r
+ * (今後使われることはない)\r
+ * \r
+ * @author seraphy\r
+ */\r
+class PurgeOldCaches extends StartupSupport {\r
+\r
+       /**\r
+        * ロガー\r
+        */\r
+       private final Logger logger = Logger.getLogger(getClass().getName());\r
+\r
+       @Override\r
+       public void doStartup() {\r
+               UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
+               File cacheDir = userDataFactory.getSpecialDataDir(".ser");\r
+               if (cacheDir.exists()) {\r
+                       File[] files = cacheDir.listFiles();\r
+                       if (files == null) {\r
+                               logger.log(Level.WARNING, "cache-dir access failed.");\r
+                               return;\r
+                       }\r
+                       for (File file : files) {\r
+                               try {\r
+                                       if (!file.isFile() || !file.canWrite()) {\r
+                                               // ファイルでないか、書き込み不可の場合はスキップする.\r
+                                               continue;\r
                                        }\r
+                                       String name = file.getName();\r
+                                       if (name.endsWith("-character.xml-cache.ser")\r
+                                                       || name.endsWith("-favorites.ser")\r
+                                                       || name.equals("recent-character.ser")\r
+                                                       || name.equals("mangled_info.xml")) {\r
+                                               boolean result = file.delete();\r
+                                               logger.log(Level.INFO, "remove file " + file\r
+                                                               + "/succeeded=" + result);\r
+                                       }\r
+\r
+                               } catch (Exception ex) {\r
+                                       logger.log(Level.WARNING, "remove file failed. " + file, ex);\r
                                }\r
                        }\r
                }\r
index 84c69ad..38d3015 100644 (file)
@@ -2,13 +2,10 @@ package charactermanaj.ui;
 \r
 import java.awt.BorderLayout;\r
 import java.awt.Container;\r
-import java.awt.GraphicsEnvironment;\r
 import java.awt.GridBagConstraints;\r
 import java.awt.GridBagLayout;\r
 import java.awt.GridLayout;\r
 import java.awt.Insets;\r
-import java.awt.Point;\r
-import java.awt.Rectangle;\r
 import java.awt.Toolkit;\r
 import java.awt.event.ActionEvent;\r
 import java.awt.event.ActionListener;\r
@@ -53,6 +50,7 @@ import javax.swing.SpinnerNumberModel;
 import javax.swing.event.ChangeEvent;\r
 import javax.swing.event.ChangeListener;\r
 \r
+import charactermanaj.Main;\r
 import charactermanaj.graphics.colormodel.ColorModel;\r
 import charactermanaj.graphics.colormodel.ColorModels;\r
 import charactermanaj.graphics.filters.ColorConv;\r
@@ -71,6 +69,7 @@ import charactermanaj.util.LocalizedResourcePropertyLoader;
 /**\r
  * カラーダイアログ.<br>\r
  * カラーダイアログはカテゴリ別に関連づけられており、カテゴリ内の各レイヤーに対応するタブを持つ.<br>\r
+ * \r
  * @author seraphy\r
  */\r
 public class ColorDialog extends JDialog {\r
@@ -130,9 +129,13 @@ public class ColorDialog extends JDialog {
        \r
        /**\r
         * コンストラクタ\r
-        * @param parent 親フレーム\r
-        * @param partsCategory カテゴリ\r
-        * @param colorGroups 選択可能なカラーグループのコレクション\r
+        * \r
+        * @param parent\r
+        *            親フレーム\r
+        * @param partsCategory\r
+        *            カテゴリ\r
+        * @param colorGroups\r
+        *            選択可能なカラーグループのコレクション\r
         */\r
        public ColorDialog(JFrame parent, PartsCategory partsCategory, Collection<ColorGroup> colorGroups) {\r
                super(parent);\r
@@ -200,7 +203,7 @@ public class ColorDialog extends JDialog {
                        });\r
                        tabContainer.addPropertyChangeListener("colorConvertParameter", new PropertyChangeListener() {\r
                                public void propertyChange(PropertyChangeEvent evt) {\r
-                                       // レイヤーの情報が変るたびにリセットボタンの状態を更新する\r
+                                                       // レイヤーの情報が変るたびにリセットボタンの状態を更新する\r
                                        updateResetButton(tabContainer);\r
                                }\r
                        });\r
@@ -304,10 +307,11 @@ public class ColorDialog extends JDialog {
        }\r
        \r
        /**\r
-        * 各レイヤーのカラー情報のタブが開かれた場合、もしくはカラーの設定値が変更されるたびに\r
-        * 呼び出されて、リセットボタンの状態を変更します.<br>\r
+        * 各レイヤーのカラー情報のタブが開かれた場合、もしくはカラーの設定値が変更されるたびに 呼び出されて、リセットボタンの状態を変更します.<br>\r
         * 現在のタブが選択しているパネルと異なるパネルからの要求については無視されます.<br>\r
-        * @param panel 色情報が変更された、もしくは開かれた対象パネル\r
+        * \r
+        * @param panel\r
+        *            色情報が変更された、もしくは開かれた対象パネル\r
         */\r
        protected void updateResetButton(ColorDialogTabPanel panel) {\r
                ColorDialogTabPanel currentPanel = (ColorDialogTabPanel) tabbedPane.getSelectedComponent();\r
@@ -317,35 +321,8 @@ public class ColorDialog extends JDialog {
        }\r
        \r
        /**\r
-        * ダイアログの表示位置を調整する.<br>\r
-        * 横位置Xはメインフレームの右側とし、縦位置Yはメインフレームの上位置からのoffset_yを加えた位置とする.<br>\r
-        * @param offset_y オフセットY\r
-        */\r
-       public void adjustLocation(int offset_y) {\r
-               // メインウィンドウよりも左側に位置づけする.\r
-               // 縦位置はメインウィンドウの上端からオフセットを加えたものとする.\r
-               Point pt = getParent().getLocation();\r
-               Insets insets = getParent().getInsets();\r
-               pt.x += getParent().getWidth();\r
-               pt.y += (offset_y * insets.top);\r
-\r
-               // メインスクリーンサイズを取得する.\r
-               GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();\r
-               Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)\r
-               \r
-               // メインスクリーンサイズを超えた場合は、はみ出た分を移動する.\r
-               if ((pt.x + getWidth()) > desktopSize.width) {\r
-                       pt.x -= ((pt.x + getWidth()) - desktopSize.width);\r
-               }\r
-               if ((pt.y + getHeight()) > desktopSize.height) {\r
-                       pt.y -= ((pt.y + getHeight()) - desktopSize.height);\r
-               }\r
-               \r
-               setLocation(pt);\r
-       }\r
-\r
-       /**\r
         * このカラーダイアログが対応するパーツカテゴリを取得する.<br>\r
+        * \r
         * @return パーツカテゴリ\r
         */\r
        public PartsCategory getPartsCategory() {\r
@@ -355,7 +332,9 @@ public class ColorDialog extends JDialog {
        /**\r
         * 指定したレイヤーのカラーグループが「連動」しているか?<br>\r
         * カテゴリに属していないレイヤーを指定した場合は常にfalseを返す.<br>\r
-        * @param layer レイヤー\r
+        * \r
+        * @param layer\r
+        *            レイヤー\r
         * @return 連動している場合はtrue、そうでなければfalse\r
         */\r
        public boolean isSyncColorGroup(Layer layer) {\r
@@ -369,8 +348,11 @@ public class ColorDialog extends JDialog {
        /**\r
         * 指定したレイヤーのカラーグループの連動フラグを設定する.<br>\r
         * カテゴリに属していないレイヤーを指定した場合は何もしない.<br>\r
-        * @param layer レイヤー\r
-        * @param selected 連動フラグ\r
+        * \r
+        * @param layer\r
+        *            レイヤー\r
+        * @param selected\r
+        *            連動フラグ\r
         */\r
        public void setSyncColorGroup(Layer layer, boolean selected) {\r
                ColorDialogTabPanel tab = tabs.get(layer);\r
@@ -382,7 +364,9 @@ public class ColorDialog extends JDialog {
        /**\r
         * レイヤーごとの色情報のマップを指定して、各レイヤーに色情報を設定する.<br>\r
         * カテゴリに属していないレイヤーが含まれる場合は例外となる.<br>\r
-        * @param params レイヤーと色情報のマップ\r
+        * \r
+        * @param params\r
+        *            レイヤーと色情報のマップ\r
         */\r
        public void setColorConvertParameters(Map<Layer, ColorConvertParameter> params) {\r
                if (params == null) {\r
@@ -401,7 +385,9 @@ public class ColorDialog extends JDialog {
         * 対象となるパーツ識別子を指定する。<br>\r
         * カラーダイアログのキャプションにパーツ名を設定される.<br>\r
         * nullを指定した場合はキャプションからパーツ名が除去される.<br>\r
-        * @param partsIdentifier パーツ識別子、もしくはnull\r
+        * \r
+        * @param partsIdentifier\r
+        *            パーツ識別子、もしくはnull\r
         */\r
        public void setPartsIdentifier(PartsIdentifier partsIdentifier) {\r
                this.partsIdentifier = partsIdentifier;\r
@@ -415,6 +401,7 @@ public class ColorDialog extends JDialog {
        /**\r
         * 対象となるパーツ識別子を取得する.<br>\r
         * 設定されていなければnullが返される.<br>\r
+        * \r
         * @return パーツ識別子、もしくはnull\r
         */\r
        public PartsIdentifier getPartsIdentifier() {\r
@@ -425,7 +412,9 @@ public class ColorDialog extends JDialog {
         * 各レイヤーのタブの有効・無効状態を設定します.<br>\r
         * カテゴリに属さないレイヤーは無視されます.<br>\r
         * nullを指定した場合は、すべてのレイヤーが「有効」となります.<br>\r
-        * @param layers 有効とするレイヤーのコレクション、もしくはnull\r
+        * \r
+        * @param layers\r
+        *            有効とするレイヤーのコレクション、もしくはnull\r
         */\r
        public void setEnableLayers(Collection<Layer> layers) {\r
                for (Map.Entry<Layer, ColorDialogTabPanel> entry : tabs.entrySet()) {\r
@@ -433,8 +422,16 @@ public class ColorDialog extends JDialog {
                        boolean enabled = (layers == null) || layers.contains(layer);\r
                        Integer tabIndex = tabbedPaneIndexMap.get(layer);\r
                        if (tabIndex != null) {\r
+                               if (Main.isMacOSX()) {\r
+                                       // OSXの場合、タブをディセーブルにしても表示が変化ないので\r
+                                       // タブタイトルを変更することでディセーブルを示す.\r
+                                       // (html3で表現しようとしたところ、かなりバギーだったため採用せず)\r
+                                       tabbedPane.setTitleAt(tabIndex,\r
+                                                       enabled ? layer.getLocalizedName() : "-");\r
+                               }\r
+\r
                                tabbedPane.setEnabledAt(tabIndex, enabled);\r
-                               \r
+\r
                                if (logger.isLoggable(Level.FINEST)) {\r
                                        logger.log(Level.FINEST, "setEnableLayers(" + layer + ")=" + enabled);\r
                                }\r
@@ -444,6 +441,7 @@ public class ColorDialog extends JDialog {
 \r
        /**\r
         * 「すべてに適用」フラグを取得する.<br>\r
+        * \r
         * @return すべてに適用フラグ\r
         */\r
        public boolean isApplyAll() {\r
@@ -452,6 +450,7 @@ public class ColorDialog extends JDialog {
        \r
        /**\r
         * 各レイヤーと、その色情報をマップとして取得する.<br>\r
+        * \r
         * @return 各レイヤーと、その色情報のマップ\r
         */\r
        public Map<Layer, ColorConvertParameter> getColorConvertParameters() {\r
@@ -467,8 +466,11 @@ public class ColorDialog extends JDialog {
        /**\r
         * レイヤーを指定して、色情報を設定する.<br>\r
         * カテゴリに属していないレイヤーを指定した場合は例外となる.<br>\r
-        * @param layer レイヤー\r
-        * @param param 色情報\r
+        * \r
+        * @param layer\r
+        *            レイヤー\r
+        * @param param\r
+        *            色情報\r
         */\r
        public void setColorConvertParameter(Layer layer, ColorConvertParameter param) {\r
                if (layer == null || param == null) {\r
@@ -484,7 +486,9 @@ public class ColorDialog extends JDialog {
        /**\r
         * 指定したレイヤーの色情報を取得する.<br>\r
         * カテゴリに属していないレイヤーを指定した場合は例外となる.<br>\r
-        * @param layer レイヤー\r
+        * \r
+        * @param layer\r
+        *            レイヤー\r
         * @return 色情報\r
         */\r
        public ColorConvertParameter getColorConvertParameter(Layer layer) {\r
@@ -501,7 +505,9 @@ public class ColorDialog extends JDialog {
        /**\r
         * 指定したレイヤーのカラーグループを取得する.<br>\r
         * カテゴリに属さないレイヤーを指定した場合は例外となる.<br>\r
-        * @param layer レイヤー\r
+        * \r
+        * @param layer\r
+        *            レイヤー\r
         * @return カラーグループ\r
         */\r
        public ColorGroup getColorGroup(Layer layer) {\r
@@ -518,8 +524,11 @@ public class ColorDialog extends JDialog {
        /**\r
         * 指定したレイヤーのカラーグループを設定する.<br>\r
         * カテゴリに属さないレイヤーを指定した場合は例外となる.<br>\r
-        * @param layer レイヤー\r
-        * @param colorGroup カラーグループ\r
+        * \r
+        * @param layer\r
+        *            レイヤー\r
+        * @param colorGroup\r
+        *            カラーグループ\r
         */\r
        public void setColorGroup(Layer layer, ColorGroup colorGroup) {\r
                if (layer == null || colorGroup == null) {\r
@@ -533,7 +542,9 @@ public class ColorDialog extends JDialog {
        \r
        /**\r
         * 色ダイアログが変更された場合に通知を受けるリスナーを登録する.<br>\r
-        * @param listener リスナー\r
+        * \r
+        * @param listener\r
+        *            リスナー\r
         */\r
        public void addColorChangeListener(ColorChangeListener listener) {\r
                if (listener == null) {\r
@@ -544,6 +555,7 @@ public class ColorDialog extends JDialog {
 \r
        /**\r
         * 色ダイアログが変更された場合に通知を受けるリスナーを登録解除する.<br>\r
+        * \r
         * @param listener\r
         */\r
        public void removeColorChangeListener(ColorChangeListener listener) {\r
@@ -569,8 +581,11 @@ public class ColorDialog extends JDialog {
        /**\r
         * 指定したレイヤーに対するカラー変更イベントを通知する.<br>\r
         * ただし、force引数がfalseである場合、アプリケーション設定で即時プレビューが指定されていない場合は通知しない.<br>\r
-        * @param layer レイヤー\r
-        * @param force アプリケーション設定に関わらず送信する場合はtrue\r
+        * \r
+        * @param layer\r
+        *            レイヤー\r
+        * @param force\r
+        *            アプリケーション設定に関わらず送信する場合はtrue\r
         */\r
        protected void fireColorChangeEvent(Layer layer, boolean force) {\r
                if (layer == null) {\r
@@ -590,7 +605,9 @@ public class ColorDialog extends JDialog {
 \r
        /**\r
         * 色グループが変更されたことを通知する.<br>\r
-        * @param layer レイヤー\r
+        * \r
+        * @param layer\r
+        *            レイヤー\r
         */\r
        protected void fireColorGroupChangeEvent(Layer layer) {\r
                if (layer == null) {\r
@@ -887,7 +904,8 @@ class ColorDialogTabPanel extends JPanel {
                                .getItemTitle(1)), JLabel.CENTER)); // Saturation 彩度\r
                colorTunePanel.add(new JLabel(strings.getProperty(colorModel\r
                                .getItemTitle(2)), JLabel.CENTER)); // Brightness 明度\r
-               colorTunePanel.add(new JLabel(strings.getProperty("contrast"), JLabel.CENTER)); // Contrast コントラスト\r
+               colorTunePanel.add(new JLabel(strings.getProperty("contrast"),\r
+                               JLabel.CENTER)); // Contrast コントラスト\r
 \r
                SpinnerNumberModel hsbModelH = new SpinnerNumberModel(0., -1., 1., 0.001);\r
                SpinnerNumberModel hsbModelS = new SpinnerNumberModel(0., -1., 1., 0.001);\r
@@ -1027,8 +1045,7 @@ class ColorDialogTabPanel extends JPanel {
        \r
        /**\r
         * このパネルで変更された色情報の状態をリセットする.<br>\r
-        * 最後に{@link #setColorConvertParameter(ColorConvertParameter)}された値で\r
-        * 設定し直す.<br>\r
+        * 最後に{@link #setColorConvertParameter(ColorConvertParameter)}された値で 設定し直す.<br>\r
         */\r
        public void resetColor() {\r
                setColorConvertParameter(paramOrg);\r
@@ -1102,6 +1119,7 @@ class ColorDialogTabPanel extends JPanel {
        \r
        /**\r
         * カラー設定が変更されているか?\r
+        * \r
         * @return 変更されている場合はtrue、そうでなければfalse\r
         */\r
        public boolean isColorConvertParameterModified() {\r
index 502e466..45c1213 100644 (file)
@@ -1234,6 +1234,8 @@ class ImportTypeSelectPanel extends ImportWizardCardPanel {
 \r
        public static final String PANEL_NAME = "importTypeSelectPanel";\r
        \r
+       private ImportWizardDialog parent;\r
+\r
        private SamplePicturePanel samplePicturePanel;\r
        \r
        private JTextField txtCharacterId;\r
@@ -1463,6 +1465,8 @@ class ImportTypeSelectPanel extends ImportWizardCardPanel {
 \r
        @Override\r
        public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) {\r
+               this.parent = parent;\r
+\r
                if (previousPanel == parent.importPartsSelectPanel) {\r
                        return;\r
                }\r
@@ -1654,8 +1658,10 @@ class ImportTypeSelectPanel extends ImportWizardCardPanel {
        @Override\r
        public boolean isReadyFinish() {\r
                if (!isImportPartsImages() && !isImportPreset()) {\r
-                       if (isImportSampleImage()) {\r
-                               // 新規プロファイル作成かサンプルイメージの更新のみでイメージもパーツセットもいらなければ、ただちに作成可能.\r
+                       if ((parent != null && parent.current == null)\r
+                                       || isImportSampleImage()) {\r
+                               // 新規プロファイル作成か、サンプルイメージの更新のみで\r
+                               // イメージもパーツセットもいらなければ、ただちに作成可能.\r
                                return true;\r
                        }\r
                }\r
index 085cad0..2d1cbf9 100644 (file)
@@ -18,21 +18,21 @@ import java.awt.Toolkit;
 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.io.InputStream;\r
-import java.io.OutputStream;\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.Comparator;\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
@@ -47,6 +47,7 @@ import javax.swing.JMenuBar;
 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
@@ -85,8 +86,7 @@ import charactermanaj.model.io.PartsImageDirectoryWatchAgentFactory;
 import charactermanaj.model.io.PartsImageDirectoryWatchEvent;\r
 import charactermanaj.model.io.PartsImageDirectoryWatchListener;\r
 import charactermanaj.model.io.RecentDataPersistent;\r
-import charactermanaj.model.io.WorkingSetXMLReader;\r
-import charactermanaj.model.io.WorkingSetXMLWriter;\r
+import charactermanaj.model.io.WorkingSetPersist;\r
 import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelEvent;\r
 import charactermanaj.ui.ImageSelectPanel.ImageSelectPanelListener;\r
 import charactermanaj.ui.ManageFavoriteDialog.FavoriteManageCallback;\r
@@ -95,20 +95,23 @@ import charactermanaj.ui.PreviewPanel.PreviewPanelListener;
 import charactermanaj.ui.model.ColorChangeEvent;\r
 import charactermanaj.ui.model.ColorChangeListener;\r
 import charactermanaj.ui.model.ColorGroupCoordinator;\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
-import charactermanaj.util.UserData;\r
-import charactermanaj.util.UserDataFactory;\r
 \r
 \r
 /**\r
@@ -117,7 +120,7 @@ import charactermanaj.util.UserDataFactory;
  * \r
  * @author seraphy\r
  */\r
-public class MainFrame extends JFrame {\r
+public class MainFrame extends JFrame implements FavoritesChangeListener {\r
 \r
        private static final long serialVersionUID = 1L;\r
 \r
@@ -128,8 +131,6 @@ public class MainFrame extends JFrame {
 \r
        protected static final String MENU_STRINGS_RESOURCE = "menu/menu";\r
 \r
-       public static final String WORKINGSET_FILE_SUFFIX = "workingset.xml";\r
-\r
        /**\r
         * メインフレームのアイコン.<br>\r
         */\r
@@ -235,6 +236,13 @@ public class MainFrame extends JFrame {
        private SearchPartsDialog lastUseSearchPartsDialog;\r
 \r
        /**\r
+        * 最後に使用したお気に入りダイアログ.<br>\r
+        * nullであれば一度も使用していない.<br>\r
+        * (nullでなくとも閉じられている可能性がある.)\r
+        */\r
+       private ManageFavoriteDialog lastUseManageFavoritesDialog;\r
+\r
+       /**\r
         * 最後に使用した壁紙情報\r
         */\r
        private WallpaperInfo wallpaperInfo;\r
@@ -312,7 +320,8 @@ public class MainFrame extends JFrame {
                                                        // パーツ及びお気に入りを再取得する場合.\r
                                                        try {\r
                                                                Cursor oldCur = mainFrame.getCursor();\r
-                                                               caller.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
+                                                               mainFrame.setCursor(Cursor\r
+                                                                               .getPredefinedCursor(Cursor.WAIT_CURSOR));\r
                                                                try {\r
                                                                        mainFrame.reloadPartsAndFavorites(newCd, true);\r
 \r
@@ -418,30 +427,23 @@ public class MainFrame extends JFrame {
        }\r
 \r
        /**\r
-        * お気に入りデータが変更されたことを通知される.\r
+        * お気に入りデータが変更された場合に通知される.\r
         * \r
-        * @param cd\r
-        *            キャラクターデータ\r
+        * @param e\r
         */\r
-       public static void notifyChangeFavorites(CharacterData cd) {\r
-               if (cd == null) {\r
-                       throw new IllegalArgumentException();\r
-               }\r
-\r
-               // Frameのうち、ネイティブリソースと関連づけられているアクティブなフレームを調査\r
-               for (Frame frame : JFrame.getFrames()) {\r
-                       if (frame.isDisplayable() && frame instanceof MainFrame) {\r
-                               MainFrame mainFrame = (MainFrame) frame;\r
-                               if (cd.getDocBase().equals(mainFrame.characterData.getDocBase())) {\r
-                                       mainFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
-                                       try {\r
-                                               // お気に入りを最新の状態に読み直し\r
-                                               mainFrame.refreshFavorites();\r
+       public void notifyChangeFavorites(FavoritesChangeEvent e) {\r
+               CharacterData cd = e.getCharacterData();\r
+               if (cd.getDocBase().equals(MainFrame.this.characterData.getDocBase())) {\r
+                       if (!MainFrame.this.equals(e.getSource())) {\r
+                               // お気に入りを最新化する.\r
+                               // (ただし、自分自身から送信したイベントの場合はリロードの必要はない)\r
+                               refreshFavorites();\r
+                       }\r
 \r
-                                       } finally {\r
-                                               mainFrame.setCursor(Cursor.getDefaultCursor());\r
-                                       }\r
-                               }\r
+                       // お気に入り管理ダイアログ上のお気に入り一覧を最新に更新する.\r
+                       if (lastUseManageFavoritesDialog != null\r
+                                       && lastUseManageFavoritesDialog.isDisplayable()) {\r
+                               lastUseManageFavoritesDialog.initListModel();\r
                        }\r
                }\r
        }\r
@@ -487,6 +489,10 @@ public class MainFrame extends JFrame {
                        JMenuBar menuBar = createMenuBar();\r
                        setJMenuBar(menuBar);\r
 \r
+                       // お気に入り変更通知を受け取る\r
+                       FavoritesChangeObserver.getDefault().addFavoritesChangeListener(\r
+                                       this);\r
+\r
                        // メインスクリーンサイズを取得する.\r
                        GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();\r
                        Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)\r
@@ -592,6 +598,8 @@ public class MainFrame extends JFrame {
         */\r
        @Override\r
        public void dispose() {\r
+               FavoritesChangeObserver.getDefault()\r
+                               .removeFavoritesChangeListener(this);\r
            imageLoader.close();\r
                stopAgents();\r
                super.dispose();\r
@@ -647,6 +655,9 @@ public class MainFrame extends JFrame {
                // 開いている検索ダイアログを閉じる\r
                closeSearchDialog();\r
 \r
+               // 開いているお気に入り管理ダイアログを閉じる\r
+               closeManageFavoritesDialog();\r
+\r
                PartsColorManager partsColorManager = characterData.getPartsColorManager();\r
 \r
                // デフォルトの背景色の設定\r
@@ -677,7 +688,14 @@ public class MainFrame extends JFrame {
                previewPane.setTitle(defaultPartsSetTitle);\r
                previewPane.addPreviewPanelListener(new PreviewPanelListener() {\r
                        public void addFavorite(PreviewPanelEvent e) {\r
-                               onRegisterFavorite();\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
@@ -761,7 +779,8 @@ public class MainFrame extends JFrame {
                        final int curidx = idx;\r
                        imageSelectPanel.addImageSelectListener(new ImageSelectPanelListener() {\r
                                public void onChangeColor(ImageSelectPanelEvent event) {\r
-                                       colorDialog.adjustLocation(curidx);\r
+                                                       WindowAdjustLocationSupport.alignRight(\r
+                                                                       MainFrame.this, colorDialog, curidx, false);\r
                                        colorDialog.setVisible(!colorDialog.isVisible());\r
                                }\r
                                public void onPreferences(ImageSelectPanelEvent event) {\r
@@ -1020,64 +1039,276 @@ public class MainFrame extends JFrame {
        protected List<PartsSet> getPartsSetList() {\r
                ArrayList<PartsSet> partssets = new ArrayList<PartsSet>();\r
                partssets.addAll(characterData.getPartsSets().values());\r
-               Collections.sort(partssets, new Comparator<PartsSet>() {\r
-                       public int compare(PartsSet o1, PartsSet o2) {\r
-                               int ret = o1.getLocalizedName().compareTo(o2.getLocalizedName());\r
-                               if (ret == 0) {\r
-                                       ret = o1.getPartsSetId().compareTo(o2.getPartsSetId());\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
-                               if (ret == 0) {\r
-                                       ret = o1.hashCode() - o2.hashCode();\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
-                               return ret;\r
+\r
+                       } else {\r
+                               throw new RuntimeException("unknown type: " + treeLeaf);\r
                        }\r
-               });\r
-               return partssets;\r
+               }\r
        }\r
 \r
        /**\r
-        * お気に入りメニューが開いたとき\r
-        * \r
-        * @param menu\r
+        * お気に入りのJMenuItemを作成するファンクションオブジェクト\r
         */\r
-       protected void onSelectedFavoriteMenu(JMenu menu) {\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
+       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
-               if (separatorIdx > 0) {\r
-                       while (menu.getMenuComponentCount() > separatorIdx + 1) {\r
-                               menu.remove(separatorIdx + 1);\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
-               MenuBuilder menuBuilder = new MenuBuilder();\r
-               for (final PartsSet presetParts : partssets) {\r
-                       JMenuItem favoriteMenu = menuBuilder.createJMenuItem();\r
-                       favoriteMenu.setName(presetParts.getPartsSetId());\r
-                       favoriteMenu.setText(presetParts.getLocalizedName());\r
-                       if (presetParts.isPresetParts()) {\r
-                               Font font = favoriteMenu.getFont();\r
-                               favoriteMenu.setFont(font.deriveFont(Font.BOLD));\r
+               if (menu instanceof JScrollableMenu) {\r
+                       // スクロールメニューの場合\r
+                       JScrollableMenu favMenu = (JScrollableMenu) menu;\r
+\r
+                       // スクロールメニューの初期化\r
+                       favMenu.initScroller();\r
+\r
+                       // スクロールメニューアイテムの設定\r
+                       favMenu.setScrollableItems(favoritesMenuItems);\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
-                       favoriteMenu.addActionListener(new ActionListener() {\r
-                               public void actionPerformed(ActionEvent e) {\r
-                                       selectPresetParts(presetParts);\r
+                       // 既存メニューの削除\r
+                       if (separatorIdx > 0) {\r
+                               while (menu.getMenuComponentCount() > separatorIdx + 1) {\r
+                                       menu.remove(separatorIdx + 1);\r
                                }\r
-                       });\r
-                       menu.add(favoriteMenu);\r
+                       }\r
+\r
+                       // お気に入りアイテムのメニューを登録する.\r
+                       for (JMenuItem menuItem : favoritesMenuItems) {\r
+                               menu.add(menuItem);\r
+                       }\r
                }\r
+\r
        }\r
 \r
        /**\r
@@ -1545,7 +1776,7 @@ public class MainFrame extends JFrame {
                }\r
 \r
                SearchPartsDialog searchPartsDlg = new SearchPartsDialog(this, characterData, partsSelectionManager);\r
-               searchPartsDlg.adjustLocation(0);\r
+               WindowAdjustLocationSupport.alignRight(this, searchPartsDlg, 0, true);\r
                searchPartsDlg.setVisible(true);\r
                lastUseSearchPartsDialog = searchPartsDlg;\r
        }\r
@@ -1554,7 +1785,7 @@ public class MainFrame extends JFrame {
         * 「パーツ検索」ダイアログを閉じる.<br>\r
         */\r
        protected void closeSearchDialog() {\r
-               lastUsePresetParts = null;\r
+               lastUseSearchPartsDialog = null;\r
                for (SearchPartsDialog dlg : SearchPartsDialog.getDialogs()) {\r
                        if (dlg != null && dlg.isDisplayable() && dlg.getParent() == this) {\r
                                dlg.dispose();\r
@@ -1563,6 +1794,18 @@ public class MainFrame extends JFrame {
        }\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
         * @param screenImage\r
@@ -1704,7 +1947,10 @@ public class MainFrame extends JFrame {
                        // お気に入りをリロードする.\r
                        CharacterDataPersistent persiste = CharacterDataPersistent.getInstance();\r
                        persiste.loadFavorites(characterData);\r
-                       notifyChangeFavorites(characterData);\r
+\r
+                       // お気に入りが更新されたことを通知する.\r
+                       FavoritesChangeObserver.getDefault().notifyFavoritesChange(\r
+                                       MainFrame.this, characterData);\r
                }\r
 \r
                // 現在選択されているパーツセットがない場合はデフォルトのパーツセットを選択する.\r
@@ -1843,17 +2089,10 @@ public class MainFrame extends JFrame {
                        workingSet.setWallpaperInfo(wallpaperInfo);\r
 \r
                        // XML形式でのワーキングセットの保存\r
-                       UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
-                       UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(\r
-                                       characterData.getDocBase(), WORKINGSET_FILE_SUFFIX);\r
-                       OutputStream outstm = workingSetXmlData.getOutputStream();\r
-                       try {\r
-                               WorkingSetXMLWriter workingSetXmlWriter = new WorkingSetXMLWriter();\r
-                               workingSetXmlWriter.writeWorkingSet(workingSet, outstm);\r
-                       } finally {\r
-                               outstm.close();\r
-                       }\r
-                       \r
+                       WorkingSetPersist workingSetPersist = WorkingSetPersist\r
+                                       .getInstance();\r
+                       workingSetPersist.saveWorkingSet(workingSet);\r
+\r
                } catch (Exception ex) {\r
                        ErrorMessageHelper.showErrorDialog(this, ex);\r
                }\r
@@ -1862,31 +2101,21 @@ public class MainFrame extends JFrame {
        /**\r
         * 画面の作業状態を復元する.\r
         * \r
-        * @return\r
+        * @return ワーキングセットを読み込んだ場合はtrue、そうでなければfalse\r
         */\r
        protected boolean loadWorkingSet() {\r
                if (!characterData.isValid()) {\r
                        return false;\r
                }\r
                try {\r
-                       // XML形式でのワーキングセットの復元\r
-                       UserDataFactory userDataFactory = UserDataFactory.getInstance();\r
-                       UserData workingSetXmlData = userDataFactory.getMangledNamedUserData(\r
-                                       characterData.getDocBase(), WORKINGSET_FILE_SUFFIX);\r
-                       if (workingSetXmlData == null || !workingSetXmlData.exists()) {\r
-                               // 保存されていない場合\r
+                       WorkingSetPersist workingSetPersist = WorkingSetPersist\r
+                                       .getInstance();\r
+                       WorkingSet2 workingSet2 = workingSetPersist\r
+                                       .loadWorkingSet(characterData);\r
+                       if (workingSet2 == null) {\r
+                               // ワーキングセットがない場合.\r
                                return false;\r
                        }\r
-                       WorkingSet2 workingSet2;\r
-\r
-                       InputStream is = workingSetXmlData.openStream();\r
-                       try {\r
-                               WorkingSetXMLReader WorkingSetXMLReader = new WorkingSetXMLReader();\r
-                               workingSet2 = WorkingSetXMLReader.loadWorkingSet(is);\r
-\r
-                       } finally {\r
-                               is.close();\r
-                       }\r
 \r
                        URI docBase = characterData.getDocBase();\r
                        if (docBase != null\r
@@ -2027,38 +2256,60 @@ public class MainFrame extends JFrame {
                        return;\r
                }\r
 \r
-               // お気に入りの状態を最新にリフレッシュする.\r
-               refreshFavorites();\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 refreshFavorites(CharacterData cd) {\r
+                               // お気に入りの状態を最新にリフレッシュする.\r
+                               MainFrame.this.refreshFavorites();\r
+                       }\r
+\r
                        public void selectFavorites(PartsSet partsSet) {\r
                                // お気に入り編集ダイアログで選択されたパーツを選択表示する.\r
                                selectPresetParts(partsSet);\r
                        }\r
-               });\r
-               dlg.setVisible(true);\r
-               if (!dlg.isModified()) {\r
-                       return;\r
-               }\r
 \r
-               // お気に入りを登録する.\r
-               try {\r
-                       setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));\r
-                       try {\r
-                               CharacterDataPersistent persiste = CharacterDataPersistent.getInstance();\r
-                               persiste.saveFavorites(characterData);\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
-                               notifyChangeFavorites(characterData);\r
+                                               persiste.saveFavorites(characterData);\r
 \r
-                       } finally {\r
-                               setCursor(Cursor.getDefaultCursor());\r
-                       }\r
+                                               // お気に入りが更新されたことを通知する.\r
+                                               FavoritesChangeObserver.getDefault()\r
+                                                               .notifyFavoritesChange(MainFrame.this,\r
+                                                                               characterData);\r
 \r
-               } catch (Exception ex) {\r
-                       ErrorMessageHelper.showErrorDialog(this, ex);\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
        /**\r
@@ -2160,7 +2411,9 @@ public class MainFrame extends JFrame {
 \r
                                persiste.saveFavorites(characterData);\r
 \r
-                               notifyChangeFavorites(characterData);\r
+                               // お気に入りが更新されたことを通知する.\r
+                               FavoritesChangeObserver.getDefault().notifyFavoritesChange(\r
+                                               MainFrame.this, characterData);\r
 \r
                        } finally {\r
                                setCursor(Cursor.getDefaultCursor());\r
index a0ae6e8..80a701f 100644 (file)
@@ -1,11 +1,11 @@
 package charactermanaj.ui;\r
 \r
 import java.awt.BorderLayout;\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.Point;\r
 import java.awt.Toolkit;\r
 import java.awt.event.ActionEvent;\r
 import java.awt.event.KeyEvent;\r
@@ -14,9 +14,10 @@ import java.awt.event.MouseEvent;
 import java.awt.event.WindowAdapter;\r
 import java.awt.event.WindowEvent;\r
 import java.util.ArrayList;\r
+import java.util.Arrays;\r
 import java.util.Collections;\r
-import java.util.Comparator;\r
 import java.util.Iterator;\r
+import java.util.List;\r
 import java.util.Map;\r
 import java.util.Properties;\r
 \r
@@ -25,21 +26,25 @@ import javax.swing.Action;
 import javax.swing.ActionMap;\r
 import javax.swing.BorderFactory;\r
 import javax.swing.Box;\r
-import javax.swing.DefaultListCellRenderer;\r
-import javax.swing.DefaultListModel;\r
 import javax.swing.InputMap;\r
 import javax.swing.JButton;\r
 import javax.swing.JComponent;\r
 import javax.swing.JDialog;\r
 import javax.swing.JFrame;\r
-import javax.swing.JList;\r
+import javax.swing.JMenuItem;\r
 import javax.swing.JOptionPane;\r
 import javax.swing.JPanel;\r
+import javax.swing.JPopupMenu;\r
 import javax.swing.JRootPane;\r
 import javax.swing.JScrollPane;\r
+import javax.swing.JTable;\r
 import javax.swing.KeyStroke;\r
-import javax.swing.ListCellRenderer;\r
+import javax.swing.ListSelectionModel;\r
+import javax.swing.SwingUtilities;\r
 import javax.swing.UIManager;\r
+import javax.swing.event.ListSelectionEvent;\r
+import javax.swing.event.ListSelectionListener;\r
+import javax.swing.table.AbstractTableModel;\r
 \r
 import charactermanaj.model.CharacterData;\r
 import charactermanaj.model.PartsSet;\r
@@ -59,20 +64,149 @@ public class ManageFavoriteDialog extends JDialog {
 \r
        private CharacterData characterData;\r
        \r
-       private DefaultListModel listModel;\r
-       \r
-       private JList list;\r
-       \r
-       private boolean dirty;\r
+       private PartsSetListTableModel partsSetListModel;\r
        \r
+       private JTable partsSetList;\r
+\r
        private FavoriteManageCallback callback;\r
 \r
+       private Action actSelect;\r
+\r
+       private Action actDelete;\r
+\r
+       private Action actRename;\r
+\r
+       public static class PartsSetListTableModel extends AbstractTableModel {\r
+\r
+               /**\r
+                * シリアライズバージョンID\r
+                */\r
+               private static final long serialVersionUID = 3012538368342673506L;\r
+\r
+               /**\r
+                * パーツセットのリスト\r
+                */\r
+               private List<PartsSet> partsSetList = Collections.emptyList();\r
+\r
+               private enum Columns {\r
+                       DISPLAY_NAME("Name") {\r
+                               @Override\r
+                               public Object getValue(PartsSet partsSet) {\r
+                                       if (partsSet != null) {\r
+                                               return partsSet.getLocalizedName();\r
+                                       }\r
+                                       return null;\r
+                               }\r
+                       },\r
+                       IS_PRESET("Type") {\r
+                               @Override\r
+                               public Object getValue(PartsSet partsSet) {\r
+                                       if (partsSet != null) {\r
+                                               return partsSet.isPresetParts()\r
+                                                               ? "Preset"\r
+                                                               : "Favorites";\r
+                                       }\r
+                                       return null;\r
+                               }\r
+                       };\r
+\r
+                       private String columnName;\r
+\r
+                       private Columns(String columnName) {\r
+                               this.columnName = columnName;\r
+                       }\r
+\r
+                       public Class<?> getColumnClass() {\r
+                               return String.class;\r
+                       }\r
+\r
+                       public String getColumnName() {\r
+                               return columnName;\r
+                       }\r
+\r
+                       public abstract Object getValue(PartsSet partsSet);\r
+               }\r
+\r
+               private static Columns[] columns = Columns.values();\r
+\r
+               public int getColumnCount() {\r
+                       return columns.length;\r
+               }\r
+\r
+               public int getRowCount() {\r
+                       return partsSetList.size();\r
+               }\r
+\r
+               public Object getValueAt(int rowIndex, int columnIndex) {\r
+                       PartsSet partsSet = getRow(rowIndex);\r
+                       return columns[columnIndex].getValue(partsSet);\r
+               }\r
+\r
+               @Override\r
+               public Class<?> getColumnClass(int columnIndex) {\r
+                       return columns[columnIndex].getColumnClass();\r
+               }\r
+\r
+               @Override\r
+               public String getColumnName(int column) {\r
+                       return columns[column].getColumnName();\r
+               }\r
+\r
+               public PartsSet getRow(int rowIndex) {\r
+                       return partsSetList.get(rowIndex);\r
+               }\r
+\r
+               public void updateRow(int rowIndex, PartsSet partsSet) {\r
+                       partsSetList.set(rowIndex, partsSet);\r
+                       fireTableRowsUpdated(rowIndex, rowIndex);\r
+               }\r
+\r
+               public List<PartsSet> getPartsSetList() {\r
+                       return new ArrayList<PartsSet>(partsSetList);\r
+               }\r
+\r
+               public void setPartsSetList(List<PartsSet> partsSetList) {\r
+                       if (partsSetList == null) {\r
+                               partsSetList = Collections.emptyList();\r
+                       }\r
+                       this.partsSetList = new ArrayList<PartsSet>(partsSetList);\r
+                       fireTableDataChanged();\r
+               }\r
+       }\r
+\r
+       /**\r
+        * パーツセットの選択および保存を行うためのコールバック.\r
+        */\r
        public interface FavoriteManageCallback {\r
+\r
+               /**\r
+                * キャラクターデータのお気に入り状態を最新にする.\r
+                * \r
+                * @param cd\r
+                */\r
+               void refreshFavorites(CharacterData cd);\r
+\r
+               /**\r
+                * 引数で指定されたパーツセットを表示する.\r
+                * \r
+                * @param partsSet\r
+                */\r
                void selectFavorites(PartsSet partsSet);\r
+\r
+               /**\r
+                * 指定したキャラクターデータのお気に入りを保存する.<br>\r
+                * presetを変更した場合はcharacter.xmlを更新するためにsavePreset引数をtrueとする.<br>\r
+                * \r
+                * @param characterData\r
+                *            お気に入りを保存するキャラクターデータ\r
+                * @param savePreset\r
+                *            character.xmlを更新する場合(presetの更新)\r
+                */\r
+               void updateFavorites(CharacterData characterData, boolean savePreset);\r
        }\r
 \r
        public ManageFavoriteDialog(JFrame parent, CharacterData characterData) {\r
-               super(parent, true);\r
+               super(parent, false);\r
                if (characterData == null) {\r
                        throw new IllegalArgumentException();\r
                }\r
@@ -98,34 +232,35 @@ public class ManageFavoriteDialog extends JDialog {
                Container contentPane = getContentPane();\r
                contentPane.setLayout(new BorderLayout());\r
                \r
-               listModel = new DefaultListModel();\r
-               list = new JList(listModel);\r
+               partsSetListModel = new PartsSetListTableModel();\r
+               partsSetList = new JTable(partsSetListModel);\r
+               partsSetList.setRowSelectionAllowed(true);\r
+               partsSetList\r
+                               .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);\r
+\r
+               partsSetList.setTableHeader(null);\r
+               partsSetList.getColumnModel().getColumn(1).setMaxWidth(150);\r
                \r
-               ListCellRenderer listCellRenderer = new DefaultListCellRenderer() {\r
-                       private static final long serialVersionUID = 1L;\r
-                       @Override\r
-                       public Component getListCellRendererComponent(JList list,\r
-                                       Object value, int index, boolean isSelected,\r
-                                       boolean cellHasFocus) {\r
-                               Object dispayValue = ((PartsSet) value).getLocalizedName();\r
-                               return super.getListCellRendererComponent(list, dispayValue, index, isSelected,\r
-                                               cellHasFocus);\r
-                       }\r
-               };\r
-               list.setCellRenderer(listCellRenderer);\r
-               AbstractAction actSelect = new AbstractAction(strings.getProperty("select")) {\r
+               partsSetList.getSelectionModel().addListSelectionListener(\r
+                               new ListSelectionListener() {\r
+                                       public void valueChanged(ListSelectionEvent e) {\r
+                                               updateButtonUI();\r
+                                       }\r
+                               });\r
+\r
+               actSelect = new AbstractAction(strings.getProperty("select")) {\r
                        private static final long serialVersionUID = 1L;\r
                        public void actionPerformed(ActionEvent e) {\r
                                onSelect();\r
                        }\r
                };\r
-               AbstractAction actDelete = new AbstractAction(strings.getProperty("remove")) {\r
+               actDelete = new AbstractAction(strings.getProperty("remove")) {\r
                        private static final long serialVersionUID = 1L;\r
                        public void actionPerformed(ActionEvent e) {\r
                                onDelete();\r
                        }\r
                };\r
-               AbstractAction actRename = new AbstractAction(strings.getProperty("rename")) {\r
+               actRename = new AbstractAction(strings.getProperty("rename")) {\r
                        private static final long serialVersionUID = 1L;\r
                        public void actionPerformed(ActionEvent e) {\r
                                onRename();\r
@@ -169,7 +304,7 @@ public class ManageFavoriteDialog extends JDialog {
                JButton btnClose = new JButton(actCancel);\r
                panel2.add(btnClose, BorderLayout.EAST);\r
 \r
-               JScrollPane scr = new JScrollPane(list);\r
+               JScrollPane scr = new JScrollPane(partsSetList);\r
                scr.setBorder(BorderFactory.createEtchedBorder());\r
                scr.setPreferredSize(new Dimension(300, 150));\r
                \r
@@ -188,52 +323,105 @@ public class ManageFavoriteDialog extends JDialog {
                am.put("deleteFav", actDelete);\r
                am.put("closeManageFavoriteDialog", actCancel);\r
 \r
-               pack();\r
+               setSize(400, 500);\r
                setLocationRelativeTo(parent);\r
                \r
-               list.addMouseListener(new MouseAdapter() {\r
+               final JPopupMenu popupMenu = new JPopupMenu();\r
+               popupMenu.add(new JMenuItem(actSelect));\r
+               popupMenu.add(new JMenuItem(actRename));\r
+               popupMenu.add(new JMenuItem(actDelete));\r
+\r
+               partsSetList.addMouseListener(new MouseAdapter() {\r
                        @Override\r
                        public void mouseClicked(MouseEvent e) {\r
                                if (e.getClickCount() == 2) {\r
                                        onSelect();\r
                                }\r
                        }\r
+                       @Override\r
+                       public void mousePressed(MouseEvent e) {\r
+                               if (SwingUtilities.isRightMouseButton(e)) {\r
+                                       // 右クリックによる選択\r
+                                       Point pt = e.getPoint();\r
+                                       int rowIndex = partsSetList.rowAtPoint(pt);\r
+                                       if (rowIndex >= 0) {\r
+                                               int[] selrows = partsSetList.getSelectedRows();\r
+                                               if (!Arrays.asList(selrows).contains(rowIndex)) {\r
+                                                       // 現在の選択行以外を右クリックした場合、その行を選択行とする.\r
+                                                       ListSelectionModel selModel = partsSetList\r
+                                                                       .getSelectionModel();\r
+                                                       selModel.setSelectionInterval(rowIndex, rowIndex);\r
+                                               }\r
+                                       }\r
+                               }\r
+                               evaluatePopup(e);\r
+                       }\r
+                       @Override\r
+                       public void mouseReleased(MouseEvent e) {\r
+                               evaluatePopup(e);\r
+                       }\r
+                       private void evaluatePopup(MouseEvent e) {\r
+                               if (e.isPopupTrigger()) {\r
+                                       popupMenu.show(partsSetList, e.getX(), e.getY());\r
+                               }\r
+                       }\r
                });\r
 \r
+               if (callback != null) {\r
+                       callback.refreshFavorites(this.characterData);\r
+               }\r
+\r
                initListModel();\r
-               list.repaint();\r
+\r
+               updateButtonUI();\r
        }\r
        \r
-       protected void initListModel() {\r
+       /**\r
+        * 現在のキャラクターデータの最新の状態でお気に入り一覧を更新する.\r
+        */\r
+       public void initListModel() {\r
                ArrayList<PartsSet> partssets = new ArrayList<PartsSet>();\r
                for (PartsSet partsset : characterData.getPartsSets().values()) {\r
-                       if (!partsset.isPresetParts()) {\r
-                               partssets.add(partsset);\r
-                       }\r
+                       partssets.add(partsset);\r
                }\r
-               Collections.sort(partssets, new Comparator<PartsSet>() {\r
-                       public int compare(PartsSet o1, PartsSet o2) {\r
-                               int ret = o1.getLocalizedName().compareTo(o2.getLocalizedName());\r
-                               if (ret == 0) {\r
-                                       ret = o1.getPartsSetId().compareTo(o2.getPartsSetId());\r
-                               }\r
-                               if (ret == 0) {\r
-                                       ret = o1.hashCode() - o2.hashCode();\r
-                               }\r
-                               return ret;\r
-                       }\r
-               });\r
-               list.setSelectedIndices(new int[0]);\r
-               listModel.removeAllElements();\r
-               for (PartsSet partsset : partssets) {\r
-                       listModel.addElement(partsset);\r
+               Collections.sort(partssets, PartsSet.DEFAULT_COMPARATOR);\r
+               partsSetListModel.setPartsSetList(partssets);\r
+       }\r
+\r
+       protected void updateButtonUI() {\r
+               int[] rows = partsSetList.getSelectedRows();\r
+               actSelect.setEnabled(rows.length == 1);\r
+               actRename.setEnabled(rows.length == 1);\r
+               actDelete.setEnabled(rows.length >= 1);\r
+       }\r
+\r
+       /**\r
+        * 選択されている「お気に入り」のパーツセットの一覧を取得する.<br>\r
+        * プリセットが選択されている場合、それは除外される.<br>\r
+        * \r
+        * @param beep\r
+        *            プリセットが選択されている場合にビープを鳴らすか?\r
+        * @return お気に入りのパーツセットのリスト、選択がなければ空のリスト.\r
+        */\r
+       protected List<PartsSet> getSelectedPartsSet() {\r
+               ArrayList<PartsSet> selectedPartsSet = new ArrayList<PartsSet>();\r
+               int[] rows = partsSetList.getSelectedRows();\r
+               for (int row : rows) {\r
+                       PartsSet partsSet = partsSetListModel.getRow(row);\r
+                       selectedPartsSet.add(partsSet);\r
                }\r
+               return selectedPartsSet;\r
        }\r
 \r
        /**\r
         * お気に入りの削除\r
         */\r
        protected void onDelete() {\r
+               List<PartsSet> removePartsSet = getSelectedPartsSet();\r
+               if (removePartsSet.isEmpty() || callback == null) {\r
+                       return;\r
+               }\r
+\r
                // 削除の確認ダイアログ\r
                Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
                                .getLocalizedProperties(STRINGS_RESOURCE);\r
@@ -264,40 +452,49 @@ public class ManageFavoriteDialog extends JDialog {
                }\r
 \r
                // お気に入りリストから削除する.\r
+               boolean dirty = false;\r
+               boolean deletePreset = false;\r
                Map<String, PartsSet> partsSetMap = characterData.getPartsSets();\r
-               for (Object value : list.getSelectedValues()) {\r
-                       PartsSet partsSet = (PartsSet) value;\r
-                       \r
+               for (PartsSet partsSet : removePartsSet) {\r
                        Iterator<Map.Entry<String, PartsSet>> ite = partsSetMap.entrySet().iterator();\r
                        while (ite.hasNext()) {\r
                                Map.Entry<String, PartsSet> entry = ite.next();\r
                                PartsSet target = entry.getValue();\r
                                if (target == partsSet) {\r
                                        dirty = true;\r
+                                       if (target.isPresetParts()) {\r
+                                               // presetを削除した場合はcharacter.xmlの更新が必要\r
+                                               deletePreset = true;\r
+                                       }\r
                                        ite.remove();\r
                                }\r
                        }\r
                }\r
-               initListModel();\r
-               list.repaint();\r
+               if (dirty) {\r
+                       callback.updateFavorites(characterData, deletePreset);\r
+                       initListModel();\r
+               }\r
        }\r
        \r
        /**\r
         * お気に入りのリネーム\r
         */\r
        protected void onRename() {\r
-               PartsSet partsSet = (PartsSet) list.getSelectedValue();\r
-               if (partsSet != null) {\r
-                       Properties strings = LocalizedResourcePropertyLoader.getCachedInstance()\r
-                                       .getLocalizedProperties(STRINGS_RESOURCE);\r
-\r
-                       String localizedName = JOptionPane.showInputDialog(this, strings\r
-                                       .getProperty("inputName"), partsSet.getLocalizedName());\r
-                       if (localizedName != null) {\r
-                               partsSet.setLocalizedName(localizedName);\r
-                               dirty = true;\r
-                               list.repaint();\r
-                       }\r
+               int row = partsSetList.getSelectedRow();\r
+               if (row < 0 || callback == null) {\r
+                       return;\r
+               }\r
+               PartsSet partsSet = partsSetListModel.getRow(row);\r
+\r
+               Properties strings = LocalizedResourcePropertyLoader\r
+                               .getCachedInstance().getLocalizedProperties(STRINGS_RESOURCE);\r
+\r
+               String localizedName = JOptionPane.showInputDialog(this,\r
+                               strings.getProperty("inputName"), partsSet.getLocalizedName());\r
+               if (localizedName != null) {\r
+                       partsSet.setLocalizedName(localizedName);\r
+                       callback.updateFavorites(characterData, partsSet.isPresetParts());\r
+                       initListModel();\r
                }\r
        }\r
        \r
@@ -305,11 +502,13 @@ public class ManageFavoriteDialog extends JDialog {
         * 選択したお気に入りを表示する.\r
         */\r
        protected void onSelect() {\r
-               PartsSet partsSet = (PartsSet) list.getSelectedValue();\r
-               if (partsSet != null) {\r
-                       if (callback != null) {\r
-                               callback.selectFavorites(partsSet);\r
-                       }\r
+               int row = partsSetList.getSelectedRow();\r
+               if (row < 0) {\r
+                       return;\r
+               }\r
+               PartsSet partsSet = partsSetListModel.getRow(row);\r
+               if (callback != null) {\r
+                       callback.selectFavorites(partsSet);\r
                }\r
        }\r
 \r
@@ -317,10 +516,6 @@ public class ManageFavoriteDialog extends JDialog {
                dispose();\r
        }\r
        \r
-       public boolean isModified() {\r
-               return dirty;\r
-       }\r
-\r
        public void setFavoriteManageCallback(FavoriteManageCallback callback) {\r
                this.callback = callback;\r
        }\r
index a00d6fe..afd564a 100644 (file)
@@ -12,10 +12,12 @@ import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;\r
 import javax.swing.JSeparator;\r
 \r
+import charactermanaj.ui.scrollablemenu.JScrollableMenu;\r
 import charactermanaj.util.LocalizedResourcePropertyLoader;\r
 \r
 /**\r
  * メニューを構築します.\r
+ * \r
  * @author seraphy\r
  */\r
 public class MenuBuilder {\r
@@ -33,6 +35,7 @@ public class MenuBuilder {
        /**\r
         * メニュー項目のアンチエイリアスが必要か判定する.<br>\r
         * java.specification.versionが1.5で始まる場合は必要とみなす.<br>\r
+        * \r
         * @return アンチエイリアスが必要であればtrue\r
         */\r
        private static boolean isNeedAntialias() {\r
@@ -52,7 +55,9 @@ public class MenuBuilder {
        /**\r
         * 生成されたメニューを名前を指定して取得します.<br>\r
         * 存在しない場合は実行時例外が発生します.<br>\r
-        * @param name メニュー名\r
+        * \r
+        * @param name\r
+        *            メニュー名\r
         * @return メニュー\r
         */\r
        public JMenu getJMenu(String name) {\r
@@ -66,7 +71,9 @@ public class MenuBuilder {
        /**\r
         * 生成されたメニュー項目を名前を指定して取得します.<br>\r
         * 存在しない場合は実行時例外が発生します.<br>\r
-        * @param name メニュー項目名\r
+        * \r
+        * @param name\r
+        *            メニュー項目名\r
         * @return メニュー項目\r
         */\r
        public JMenuItem getJMenuItem(String name) {\r
@@ -79,9 +86,11 @@ public class MenuBuilder {
        \r
        /**\r
         * メニュー設定に従いメニューバーを構築して返します.<br>\r
-        * 生成したメニューとメニュー項目は、{@link #getJMenu(String)}, \r
-        * {@link #getJMenuItem(String)}で取得できます.<br>\r
-        * @param menus メニュー設定\r
+        * 生成したメニューとメニュー項目は、{@link #getJMenu(String)}, {@link #getJMenuItem(String)}\r
+        * で取得できます.<br>\r
+        * \r
+        * @param menus\r
+        *            メニュー設定\r
         * @return 構築されたメニューバー\r
         */\r
        public JMenuBar createMenuBar(MenuDataFactory[] menus) {\r
@@ -155,6 +164,7 @@ public class MenuBuilder {
        /**\r
         * JMenuBarを構築します.<br>\r
         * アンチエイリアスが必要な場合はアンチエイリアスが設定されます.<br>\r
+        * \r
         * @return JMenuBar\r
         */\r
        public JMenuBar createJMenuBar() {\r
@@ -162,11 +172,7 @@ public class MenuBuilder {
                        private static final long serialVersionUID = 1L;\r
                        @Override\r
                        public void paint(Graphics g) {\r
-                               if (needAntiAlias) {\r
-                                       ((Graphics2D) g).setRenderingHint(\r
-                                                       RenderingHints.KEY_TEXT_ANTIALIASING,\r
-                                                       RenderingHints.VALUE_TEXT_ANTIALIAS_ON);\r
-                               }\r
+                               setAntiAlias(g);\r
                                super.paint(g);\r
                        }\r
                };\r
@@ -175,26 +181,35 @@ public class MenuBuilder {
        /**\r
         * JMenuを構築します.<br>\r
         * アンチエイリアスが必要な場合はアンチエイリアスが設定されます.<br>\r
+        * \r
         * @return JMenu\r
         */\r
        public JMenu createJMenu() {\r
-               return new JMenu() {\r
-                       private static final long serialVersionUID = 1L;\r
-                       @Override\r
-                       public void paint(Graphics g) {\r
-                               if (needAntiAlias) {\r
-                                       ((Graphics2D) g).setRenderingHint(\r
-                                                       RenderingHints.KEY_TEXT_ANTIALIASING,\r
-                                                       RenderingHints.VALUE_TEXT_ANTIALIAS_ON);\r
+               if (JScrollableMenu.isScreenMenu()) {\r
+                       return new JMenu() {\r
+                               private static final long serialVersionUID = 1L;\r
+                               @Override\r
+                               public void paint(Graphics g) {\r
+                                       setAntiAlias(g);\r
+                                       super.paint(g);\r
                                }\r
-                               super.paint(g);\r
-                       }\r
-               };\r
+                       };\r
+               } else {\r
+                       return new JScrollableMenu() {\r
+                               private static final long serialVersionUID = 1L;\r
+                               @Override\r
+                               public void paint(Graphics g) {\r
+                                       setAntiAlias(g);\r
+                                       super.paint(g);\r
+                               }\r
+                       };\r
+               }\r
        }\r
        \r
        /**\r
         * JCheckBoxMenuItemを構築します.<br>\r
         * アンチエイリアスが必要な場合はアンチエイリアスが設定されます.<br>\r
+        * \r
         * @return JCheckBoxMenuItem\r
         */\r
        public JCheckBoxMenuItem createJCheckBoxMenuItem() {\r
@@ -202,11 +217,7 @@ public class MenuBuilder {
                        private static final long serialVersionUID = 1L;\r
                        @Override\r
                        public void paint(Graphics g) {\r
-                               if (needAntiAlias) {\r
-                                       ((Graphics2D) g).setRenderingHint(\r
-                                                       RenderingHints.KEY_TEXT_ANTIALIASING,\r
-                                                       RenderingHints.VALUE_TEXT_ANTIALIAS_ON);\r
-                               }\r
+                               setAntiAlias(g);\r
                                super.paint(g);\r
                        }\r
                };\r
@@ -215,6 +226,7 @@ public class MenuBuilder {
        /**\r
         * JMenuItemを構築します.<br>\r
         * アンチエイリアスが必要な場合はアンチエイリアスが設定されます.<br>\r
+        * \r
         * @return JMenuItem\r
         */\r
        public JMenuItem createJMenuItem() {\r
@@ -222,14 +234,22 @@ public class MenuBuilder {
                        private static final long serialVersionUID = 1L;\r
                        @Override\r
                        public void paint(Graphics g) {\r
-                               if (needAntiAlias) {\r
-                                       ((Graphics2D) g).setRenderingHint(\r
-                                                       RenderingHints.KEY_TEXT_ANTIALIASING,\r
-                                                       RenderingHints.VALUE_TEXT_ANTIALIAS_ON);\r
-                               }\r
+                               setAntiAlias(g);\r
                                super.paint(g);\r
                        }\r
                };\r
        }\r
        \r
+       /**\r
+        * アンチエイリアスを有効にする.\r
+        * \r
+        * @param g\r
+        */\r
+       private static void setAntiAlias(Graphics g) {\r
+               if (needAntiAlias) {\r
+                       ((Graphics2D) g).setRenderingHint(\r
+                                       RenderingHints.KEY_TEXT_ANTIALIASING,\r
+                                       RenderingHints.VALUE_TEXT_ANTIALIAS_ON);\r
+               }\r
+       }\r
 }\r
index 94c4680..c54828b 100644 (file)
@@ -1172,7 +1172,10 @@ public class ProfileEditDialog extends JDialog {
                chkWatchDir.setSelected(original.isWatchDirectory());\r
                \r
                // パーツセット\r
-               for (PartsSet partsSet : original.getPartsSets().values()) {\r
+               ArrayList<PartsSet> partsSets = new ArrayList<PartsSet>();\r
+               partsSets.addAll(original.getPartsSets().values());\r
+               Collections.sort(partsSets, PartsSet.DEFAULT_COMPARATOR);\r
+               for (PartsSet partsSet : partsSets) {\r
                        partssetsTableModel.addRow(new PresetsTableRow(partsSet));\r
                }\r
                partssetsTableModel.setDefaultPartsSetId(original.getDefaultPartsSetId());\r
index 9dec58a..6e567cc 100644 (file)
@@ -2,13 +2,9 @@ package charactermanaj.ui;
 \r
 import java.awt.BorderLayout;\r
 import java.awt.Container;\r
-import java.awt.Dimension;\r
-import java.awt.GraphicsEnvironment;\r
 import java.awt.GridBagConstraints;\r
 import java.awt.GridBagLayout;\r
 import java.awt.Insets;\r
-import java.awt.Point;\r
-import java.awt.Rectangle;\r
 import java.awt.Toolkit;\r
 import java.awt.event.ActionEvent;\r
 import java.awt.event.ActionListener;\r
@@ -23,8 +19,8 @@ import java.util.Comparator;
 import java.util.HashSet;\r
 import java.util.List;\r
 import java.util.Map;\r
-import java.util.Properties;\r
 import java.util.Map.Entry;\r
+import java.util.Properties;\r
 import java.util.WeakHashMap;\r
 \r
 import javax.swing.AbstractAction;\r
@@ -342,39 +338,6 @@ public class SearchPartsDialog extends JDialog {
        }\r
        \r
        /**\r
-        * ダイアログの表示位置を調整する.<br>\r
-        * 横位置Xはメインフレームの右側とし、縦位置Yはメインフレームの上位置からのoffset_yを加えた位置とする.<br>\r
-        * @param offset_y オフセットY\r
-        */\r
-       public void adjustLocation(int offset_y) {\r
-               // メインウィンドウよりも左側に位置づけする.\r
-               // 縦位置はメインウィンドウの上端からオフセットを加えたものとする.\r
-               Point pt = getParent().getLocation();\r
-               Insets insets = getParent().getInsets();\r
-               pt.x += getParent().getWidth();\r
-               pt.y += (offset_y * insets.top);\r
-\r
-               // メインスクリーンサイズを取得する.\r
-               GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();\r
-               Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)\r
-\r
-               // メインスクリーンサイズを超えた場合は、はみ出た分を移動する.\r
-               if ((pt.x + getWidth()) > desktopSize.width) {\r
-                       pt.x -= ((pt.x + getWidth()) - desktopSize.width);\r
-               }\r
-               if ((pt.y + getHeight()) > desktopSize.height) {\r
-                       pt.y -= ((pt.y + getHeight()) - desktopSize.height);\r
-               }\r
-\r
-               setLocation(pt);\r
-\r
-               // 高さはメインフレームと同じにする.\r
-               Dimension siz = getSize();\r
-               siz.height = getParent().getHeight() - offset_y;\r
-               setSize(siz);\r
-       }\r
-\r
-       /**\r
         * 「選択」ボタンまたはテーブルのダブルクリックのハンドラ.<br>\r
         * 選択されている行のパーツ識別子をもとに、パーツにフォーカスをあてる.<br>\r
         */\r
index 697ae2d..b7fa873 100644 (file)
@@ -33,11 +33,13 @@ import javax.swing.JDialog;
 import javax.swing.JFileChooser;\r
 import javax.swing.JFrame;\r
 import javax.swing.JLabel;\r
+import javax.swing.JOptionPane;\r
 import javax.swing.JPanel;\r
 import javax.swing.JRootPane;\r
 import javax.swing.KeyStroke;\r
 \r
 import charactermanaj.Main;\r
+import charactermanaj.model.io.WorkingSetPersist;\r
 import charactermanaj.ui.util.FileDropTarget;\r
 import charactermanaj.util.ErrorMessageHelper;\r
 import charactermanaj.util.LocalizedResourcePropertyLoader;\r
@@ -171,6 +173,15 @@ public class SelectCharatersDirDialog extends JDialog {
                        }\r
                };\r
 \r
+               AbstractAction actRemoveWorkingSets = new AbstractAction(\r
+                               strings.getProperty("btn.clearWorkingSets")) {\r
+                       private static final long serialVersionUID = 1L;\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               onRemoveWorkingSets();\r
+                       }\r
+               };\r
+\r
+               final JButton btnRemoveWorkingSets = new JButton(actRemoveWorkingSets);\r
                final JButton btnRemoveRecent = new JButton(actRemoveRecent);\r
                final JButton btnOK = new JButton(actOk);\r
                final JButton btnCancel = new JButton(actClose);\r
@@ -195,6 +206,7 @@ public class SelectCharatersDirDialog extends JDialog {
                im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, tk.getMenuShortcutKeyMask()), "close");\r
                rootPane.getActionMap().put("close", actClose);\r
                \r
+               btnRemoveWorkingSets.addFocusListener(focusAdapter);\r
                btnRemoveRecent.addFocusListener(focusAdapter);\r
                btnOK.addFocusListener(focusAdapter);\r
                btnCancel.addFocusListener(focusAdapter);\r
@@ -256,12 +268,20 @@ public class SelectCharatersDirDialog extends JDialog {
                gbc.gridy = 1;\r
                gbc.gridwidth = 1;\r
                gbc.gridheight = 1;\r
+               gbc.weightx = 0.;\r
+               gbc.weighty = 0.;\r
+               btnPanel.add(btnRemoveWorkingSets, gbc);\r
+\r
+               gbc.gridx = 2;\r
+               gbc.gridy = 1;\r
+               gbc.gridwidth = 1;\r
+               gbc.gridheight = 1;\r
                gbc.weightx = 1.;\r
                gbc.weighty = 0.;\r
                \r
                btnPanel.add(Box.createGlue(), gbc);\r
 \r
-               gbc.gridx = Main.isLinuxOrMacOSX() ? 3 : 2;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 4 : 3;\r
                gbc.gridy = 1;\r
                gbc.gridwidth = 1;\r
                gbc.gridheight = 1;\r
@@ -271,7 +291,7 @@ public class SelectCharatersDirDialog extends JDialog {
                btnPanel.add(btnOK, gbc);\r
 \r
 \r
-               gbc.gridx = Main.isLinuxOrMacOSX() ? 2 : 3;\r
+               gbc.gridx = Main.isLinuxOrMacOSX() ? 3 : 4;\r
                gbc.gridy = 1;\r
                gbc.gridwidth = 1;\r
                gbc.gridheight = 1;\r
@@ -279,7 +299,7 @@ public class SelectCharatersDirDialog extends JDialog {
                gbc.weighty = 0.;\r
                btnPanel.add(btnCancel, gbc);\r
                \r
-               gbc.gridx = 4;\r
+               gbc.gridx = 5;\r
                gbc.gridy = 1;\r
                gbc.gridwidth = 1;\r
                gbc.gridheight = 1;\r
@@ -389,6 +409,30 @@ public class SelectCharatersDirDialog extends JDialog {
                        ErrorMessageHelper.showErrorDialog(this, ex);\r
                }\r
        }\r
+\r
+       protected void onRemoveWorkingSets() {\r
+               try {\r
+                       Properties strings = LocalizedResourcePropertyLoader\r
+                                       .getCachedInstance().getLocalizedProperties(\r
+                                                       "languages/selectCharatersDirDialog");\r
+\r
+                       // 削除の確認ダイアログ\r
+                       if (JOptionPane.showConfirmDialog(this,\r
+                                       strings.getProperty("confirm.clearWorkingSets"),\r
+                                       strings.getProperty("confirm.clearWorkingSets.title"),\r
+                                       JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) != JOptionPane.YES_OPTION) {\r
+                               return;\r
+                       }\r
+\r
+                       // 全てのワーキングセットをクリアする.\r
+                       WorkingSetPersist workingSetPersist = WorkingSetPersist\r
+                                       .getInstance();\r
+                       workingSetPersist.removeAllWorkingSet();\r
+\r
+               } catch (Exception ex) {\r
+                       ErrorMessageHelper.showErrorDialog(this, ex);\r
+               }\r
+       }\r
        \r
        protected void onRemoveRecent() {\r
                try {\r
diff --git a/src/charactermanaj/ui/model/FavoritesChangeEvent.java b/src/charactermanaj/ui/model/FavoritesChangeEvent.java
new file mode 100644 (file)
index 0000000..565170e
--- /dev/null
@@ -0,0 +1,29 @@
+package charactermanaj.ui.model;\r
+\r
+import java.util.EventObject;\r
+\r
+import charactermanaj.model.CharacterData;\r
+\r
+/**\r
+ * お気に入り変更イベント.<br>\r
+ * \r
+ * @author seraphy\r
+ */\r
+public class FavoritesChangeEvent extends EventObject {\r
+\r
+       /**\r
+        * シリアライズバージョンID\r
+        */\r
+       private static final long serialVersionUID = 3206827658882098336L;\r
+\r
+       private CharacterData characterData;\r
+\r
+       public FavoritesChangeEvent(Object src, CharacterData characterData) {\r
+               super(src);\r
+               this.characterData = characterData;\r
+       }\r
+\r
+       public CharacterData getCharacterData() {\r
+               return characterData;\r
+       }\r
+}\r
diff --git a/src/charactermanaj/ui/model/FavoritesChangeListener.java b/src/charactermanaj/ui/model/FavoritesChangeListener.java
new file mode 100644 (file)
index 0000000..59e61ec
--- /dev/null
@@ -0,0 +1,9 @@
+package charactermanaj.ui.model;\r
+\r
+import java.util.EventListener;\r
+\r
+public interface FavoritesChangeListener extends EventListener {\r
+\r
+       void notifyChangeFavorites(FavoritesChangeEvent e);\r
+\r
+}\r
diff --git a/src/charactermanaj/ui/model/FavoritesChangeObserver.java b/src/charactermanaj/ui/model/FavoritesChangeObserver.java
new file mode 100644 (file)
index 0000000..05a7ac5
--- /dev/null
@@ -0,0 +1,61 @@
+package charactermanaj.ui.model;\r
+\r
+import javax.swing.event.EventListenerList;\r
+\r
+import charactermanaj.model.CharacterData;\r
+\r
+\r
+/**\r
+ * お気に入りが変更されたことを通知するためのメカニズム.<br>\r
+ * \r
+ * @author seraphy\r
+ * \r
+ */\r
+public abstract class FavoritesChangeObserver {\r
+\r
+       private static FavoritesChangeObserver defobj = new FavoritesChangeObserverImpl();\r
+\r
+       public static FavoritesChangeObserver getDefault() {\r
+               return defobj;\r
+       }\r
+\r
+       public abstract void addFavoritesChangeListener(FavoritesChangeListener l);\r
+\r
+       public abstract void removeFavoritesChangeListener(FavoritesChangeListener l);\r
+\r
+       public abstract void notifyFavoritesChange(FavoritesChangeEvent e);\r
+\r
+       public void notifyFavoritesChange(Object wnd, CharacterData cd) {\r
+               if (cd == null) {\r
+                       throw new IllegalArgumentException();\r
+               }\r
+               notifyFavoritesChange(new FavoritesChangeEvent(wnd, cd));\r
+       }\r
+}\r
+\r
+class FavoritesChangeObserverImpl extends FavoritesChangeObserver {\r
+\r
+       private EventListenerList listeners = new EventListenerList();\r
+\r
+       @Override\r
+       public void addFavoritesChangeListener(FavoritesChangeListener l) {\r
+               listeners.add(FavoritesChangeListener.class, l);\r
+       }\r
+\r
+       @Override\r
+       public void removeFavoritesChangeListener(FavoritesChangeListener l) {\r
+               listeners.remove(FavoritesChangeListener.class, l);\r
+       }\r
+\r
+       @Override\r
+       public void notifyFavoritesChange(FavoritesChangeEvent e) {\r
+               if (e == null) {\r
+                       throw new IllegalArgumentException();\r
+               }\r
+               FavoritesChangeListener[] lst = listeners\r
+                               .getListeners(FavoritesChangeListener.class);\r
+               for (FavoritesChangeListener l : lst) {\r
+                       l.notifyChangeFavorites(e);\r
+               }\r
+       }\r
+}\r
diff --git a/src/charactermanaj/ui/scrollablemenu/JScrollableMenu.java b/src/charactermanaj/ui/scrollablemenu/JScrollableMenu.java
new file mode 100644 (file)
index 0000000..1ae8c43
--- /dev/null
@@ -0,0 +1,449 @@
+package charactermanaj.ui.scrollablemenu;\r
+\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.net.URL;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+\r
+import javax.swing.ImageIcon;\r
+import javax.swing.JMenu;\r
+import javax.swing.JMenuItem;\r
+import javax.swing.Timer;\r
+import javax.swing.event.MenuEvent;\r
+import javax.swing.event.MenuListener;\r
+\r
+/**\r
+ * スクロール可能メニュー. メニュー項目を設定したあと、{@link #initScroller() }でスクローラーを初期化します. つぎに、\r
+ * {@link #setScrollableItems(java.util.Collection) }で、スクロールさせる メニュー項目を設定します。\r
+ * 表示可能なアイテム数を調整するために、このメニューオブジェクトのselectedイベントの タイミングで、\r
+ * {@link #adjustMaxVisible(int) }を呼び出して表示項目数を調整します。\r
+ * \r
+ * @author seraphy\r
+ */\r
+public class JScrollableMenu extends JMenu {\r
+\r
+       /**\r
+        * シリアライズバージョンID\r
+        */\r
+       private static final long serialVersionUID = -5174737355715398136L;\r
+\r
+       /**\r
+        * 自動スクロールの既定の間隔(mSec).\r
+        */\r
+       public static final int DEFAULT_REPEAT_DELAY = 200;\r
+\r
+       /**\r
+        * 高速自動スクロールの既定の間隔(mSec).\r
+        */\r
+       public static final int DEFAULT_FAST_REPEAT_DELAY = 80;\r
+\r
+       /**\r
+        * 既定の最大表示アイテム数.\r
+        */\r
+       public static final int DEFAULT_MAX_VISIBLE = 10;\r
+\r
+       /**\r
+        * リピートの閾値. スクロール数が、この数値を超えた場合に高速スクロール化する.\r
+        */\r
+       public static final int DEFAULT_REPEAT_THRESHOLD = 3;\r
+\r
+       /**\r
+        * スクロールするアイテムのメニューの開始位置.\r
+        */\r
+       private int _startPos;\r
+\r
+       /**\r
+        * 現在表示されている最初のアイテムのオフセット.\r
+        */\r
+       private int _offset;\r
+\r
+       /**\r
+        * スクロールするメニュー項目のリスト.\r
+        */\r
+       private ArrayList<JMenuItem> _menus = new ArrayList<JMenuItem>();\r
+\r
+       /**\r
+        * 自動スクロールのためのタイマー.\r
+        */\r
+       private Timer _timer;\r
+\r
+       /**\r
+        * 自動スクロールしたカウント.\r
+        */\r
+       private int _scrollCount;\r
+\r
+       /**\r
+        * スクローラー(上).\r
+        */\r
+       private JScrollerMenuItem _upButton;\r
+\r
+       /**\r
+        * スクローラー(下).\r
+        */\r
+       private JScrollerMenuItem _downButton;\r
+\r
+       /**\r
+        * 通常スクロール時の自動スクロールの間隔.\r
+        */\r
+       private int _delay = DEFAULT_REPEAT_DELAY;\r
+\r
+       /**\r
+        * 高速スクロール時の自動スクロールの間隔.\r
+        */\r
+       private int _delayFast = DEFAULT_FAST_REPEAT_DELAY;\r
+\r
+       /**\r
+        * リピートの閾値. スクロール数が、この数値を超えた場合に高速スクロール化する.\r
+        */\r
+       private int _repeat_threshold = DEFAULT_REPEAT_THRESHOLD;\r
+\r
+       /**\r
+        * 現在のスクロール方向を示すフラグ. タイマーハンドラの中で判定するため. nullの場合はスクロールしていないを示す.\r
+        */\r
+       private Boolean _directionUp;\r
+\r
+       /**\r
+        * 最大表示アイテム数.\r
+        */\r
+       private int maxVisible = DEFAULT_MAX_VISIBLE;\r
+\r
+       /**\r
+        * 表示名を省略してメニューを構築する.\r
+        */\r
+       public JScrollableMenu() {\r
+               this("");\r
+       }\r
+\r
+       /**\r
+        * 表示名を指定してメニューを構築する.\r
+        * \r
+        * @param name\r
+        */\r
+       public JScrollableMenu(String name) {\r
+               super(name);\r
+               initScrollableMenu();\r
+       }\r
+\r
+       /**\r
+        * スクロール可能メニューの基本状態を設定する.\r
+        */\r
+       private void initScrollableMenu() {\r
+               // 自動スクロールのためのタイマ\r
+               this._timer = new Timer(_delay, new ActionListener() {\r
+                       public void actionPerformed(ActionEvent e) {\r
+                               // スクロール\r
+                               doScroll();\r
+\r
+                               // スクロール数をカウントアップ\r
+                               _scrollCount++;\r
+\r
+                               // スクロール数が閾値を超えたら高速化\r
+                               if (_scrollCount >= _repeat_threshold) {\r
+                                       ((Timer) e.getSource()).setDelay(_delayFast);\r
+                               }\r
+                       }\r
+               });\r
+               addMenuListener(new MenuListener() {\r
+                       public void menuCanceled(MenuEvent e) {\r
+                               // このメニューがキャンセルされたときにスクロールを停止する\r
+                               JScrollableMenu.this._timer.stop();\r
+                               _directionUp = null;\r
+                       }\r
+\r
+                       public void menuDeselected(MenuEvent e) {\r
+                               // このメニューが非選択状態になったときスクロールを停止する\r
+                               JScrollableMenu.this._timer.stop();\r
+                               _directionUp = null;\r
+                       }\r
+\r
+                       public void menuSelected(MenuEvent e) {\r
+                               // 何もしない\r
+                       }\r
+               });\r
+       }\r
+\r
+       /**\r
+        * スクローラーを初期化します. スクロールしない固定のメニュー項目などを設定したあとで、このメソッドを呼び出します.\r
+        * すでに初期化されている場合は何もしません.\r
+        */\r
+       public void initScroller() {\r
+               if (_upButton != null || _downButton != null) {\r
+                       // すでに初期化済み\r
+                       removeAllScrollableItems();\r
+                       return;\r
+               }\r
+\r
+               // スクローラー用ボタンアイコンを、このクラスからの相対パスで取得する.\r
+               // (派生クラスからでもリソースの相対位置を変えないようにするためクラス名は固定とする)\r
+               Class<?> cls = JScrollableMenu.class;\r
+               URL downPngURL = cls.getResource("arrow-down.png");\r
+               URL upPngURL = cls.getResource("arrow-up.png");\r
+               if (downPngURL == null || upPngURL == null) {\r
+                       throw new RuntimeException("png resource not found.");\r
+               }\r
+               ImageIcon iconDown = new ImageIcon(downPngURL);\r
+               ImageIcon iconUp = new ImageIcon(upPngURL);\r
+\r
+               // スクローラー用メニュー項目\r
+               _upButton = new JScrollerMenuItem(iconUp);\r
+               _downButton = new JScrollerMenuItem(iconDown);\r
+\r
+               // スクローラーのマウスイベントを受け取る\r
+               final ScrollableMenuEventListener sc = new ScrollableMenuEventListener() {\r
+                       public void start(ScrollableMenuEvent e) {\r
+                               Boolean direction;\r
+                               if (e.getSource().equals(_upButton)) {\r
+                                       // 上スクロール\r
+                                       direction = Boolean.TRUE;\r
+                               } else {\r
+                                       // 下スクロール\r
+                                       direction = Boolean.FALSE;\r
+                               }\r
+\r
+                               // マウスクリックに対するスクロール\r
+                               doScroll(direction);\r
+\r
+                               // 自動スクロール開始\r
+                               _scrollCount = 0;\r
+                               _timer.setDelay(_delay);\r
+                               _timer.start();\r
+                       }\r
+\r
+                       public void end(ScrollableMenuEvent e) {\r
+                               // 自動スクロール停止\r
+                               _timer.stop();\r
+                               _directionUp = null;\r
+                       }\r
+               };\r
+\r
+               _upButton.addScrollableMenuEventListener(sc);\r
+               _downButton.addScrollableMenuEventListener(sc);\r
+\r
+               add(_upButton);\r
+               _startPos = getItemCount(); // upButtonの次のインデックス\r
+               add(_downButton);\r
+\r
+               // Mac OS Xのスクリーンメニューはスクロール可能なので、\r
+               // スクローラー用アイテムは非表示にして、デフォルトの機能に任せる。\r
+               // (逆に、スクリーンメニューではカスタムメニューは、うまく機能しない。)\r
+               if (isScreenMenu()) {\r
+                       _upButton.setVisible(false);\r
+                       _downButton.setVisible(false);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 1行スクロールする\r
+        * \r
+        * @param direction\r
+        *            上方向の場合はtrue、下の場合はfalse、停止はnull\r
+        */\r
+       public void doScroll(Boolean direction) {\r
+               _directionUp = direction;\r
+               doScroll();\r
+       }\r
+       \r
+       /**\r
+        * スクロールする.\r
+        */\r
+       protected void doScroll() {\r
+               // 現在の方向に応じて処理内容を分岐する.\r
+               if (_directionUp != null) {\r
+                       if (_directionUp.booleanValue()) {\r
+                               scrollDown();\r
+                       } else {\r
+                               scrollUp();\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * Mac OS Xのスクリーンメニューを使用しているか?\r
+        * \r
+        * @return 使用している場合はtrue\r
+        */\r
+       public static boolean isScreenMenu() {\r
+               String macScreenMenu = System.getProperty("apple.laf.useScreenMenuBar");\r
+               if (macScreenMenu != null && macScreenMenu.toLowerCase().equals("true")) {\r
+                       return true;\r
+               }\r
+               return false;\r
+       }\r
+\r
+       /**\r
+        * 表示可能な最大行数を設定する.\r
+        * \r
+        * @param maxVisible\r
+        *            最大行数\r
+        */\r
+       public void setMaxVisible(int maxVisible) {\r
+               this.maxVisible = maxVisible;\r
+       }\r
+\r
+       /**\r
+        * 表示可能な最大行数を取得する.\r
+        * \r
+        * @return 表示可能な最大行数\r
+        */\r
+       public int getMaxVisible() {\r
+               return this.maxVisible;\r
+       }\r
+\r
+       /**\r
+        * 画面の高さを指定して、表示可能なスクロールのアイテム数を算定し、 スクロールを表示し直す.\r
+        * \r
+        * @param height\r
+        *            画面の高さを示す(px)\r
+        */\r
+       public void adjustMaxVisible(int height) {\r
+               int numOfItems = 0;\r
+               if (_menus.size() > 0) {\r
+                       int heightPerItem = _menus.get(0).getPreferredSize().height;\r
+                       if (heightPerItem <= 0) {\r
+                               // 調整できないので何もしない.\r
+                               return;\r
+                       }\r
+                       numOfItems = height / heightPerItem;\r
+               }\r
+               numOfItems = numOfItems - (_startPos + 1 + 2); // 既存 + up/downボタン分 +\r
+                                                                                                               // 上下余白を差し引く\r
+               if (numOfItems < 0) {\r
+                       numOfItems = 1;\r
+               }\r
+               this.maxVisible = numOfItems;\r
+               updateScrollableMenus();\r
+       }\r
+\r
+       /**\r
+        * 通常スクロールの間隔を取得する.\r
+        * \r
+        * @return 通常スクロールの間隔(mSec)\r
+        */\r
+       public int getRepeatDelay() {\r
+               return this._delay;\r
+       }\r
+\r
+       /**\r
+        * 高速スクロールの間隔を取得する.\r
+        * \r
+        * @return 高速スクロールの間隔(mSec)\r
+        */\r
+       public int getRepeatDelayFast() {\r
+               return this._delayFast;\r
+       }\r
+\r
+       /**\r
+        * 通常スクロールの間隔を設定する.\r
+        * \r
+        * @param delay\r
+        *            通常スクロールの間隔(mSec)\r
+        */\r
+       public void setRepeatDelay(int delay) {\r
+               this._delay = delay;\r
+       }\r
+\r
+       /**\r
+        * 高速スクロールの間隔を設定する.\r
+        * \r
+        * @param delayFast\r
+        *            高速スクロールの間隔(mSec)\r
+        */\r
+       public void setRepeatDelayFast(int delayFast) {\r
+               this._delayFast = delayFast;\r
+       }\r
+\r
+       /**\r
+        * スクロール可能アイテムを設定します. 既存のアイテムがある場合は、すべて登録解除されます. 事前にスクローラーは初期化済みでなければなりません.\r
+        * \r
+        * @param menus\r
+        *            メニューリスト\r
+        */\r
+       public void setScrollableItems(Collection<? extends JMenuItem> menus) {\r
+               if (_upButton == null || _downButton == null) {\r
+                       throw new IllegalStateException("initScrollerを先に呼び出してください");\r
+               }\r
+               removeAllScrollableItems();\r
+\r
+               if (menus != null) {\r
+                       for (JMenuItem item : menus) {\r
+                               int idx = _startPos + _menus.size();\r
+                               this.add(item, idx);\r
+                               _menus.add(item);\r
+                       }\r
+               }\r
+\r
+               updateScrollableMenus();\r
+       }\r
+\r
+       /**\r
+        * 現在のスクロール可能アイテムをすべて除去します.\r
+        */\r
+       public void removeAllScrollableItems() {\r
+               for (JMenuItem item : _menus) {\r
+                       this.remove(item);\r
+               }\r
+               _menus.clear();\r
+               _offset = 0;\r
+       }\r
+\r
+       /**\r
+        * 現在のスクロール範囲でスクロール可能項目を表示します.\r
+        */\r
+       public void updateScrollableMenus() {\r
+               boolean screenMenu = isScreenMenu();\r
+               int numOfItems = _menus.size();\r
+               for (int idx = 0; idx < numOfItems; idx++) {\r
+                       boolean visible = false;\r
+                       if (idx >= _offset && idx < (_offset + maxVisible) || screenMenu) {\r
+                               // メニュー項目が表示範囲内であれば表示、範囲外であれび非表示とする。\r
+                               // ただし、Mac OS Xのスクリーンメニューであれば無条件にすべて表示。\r
+                               visible = true;\r
+                       }\r
+                       _menus.get(idx).setVisible(visible);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * 現在表示されているスクロール項目のオフセットを取得する.\r
+        * \r
+        * @return 現在のオフセット\r
+        */\r
+       public int getOffset() {\r
+               return _offset;\r
+       }\r
+\r
+       /**\r
+        * 上方向にスクロールします. これ以上スクロールできない場合は何もしません. その場合、自動スクロール中であればスクロールは停止します.\r
+        */\r
+       public void scrollUp() {\r
+               int numOfItems = _menus.size();\r
+               int limit = numOfItems - maxVisible;\r
+               if (limit < 0) {\r
+                       limit = 0;\r
+               }\r
+\r
+               _offset++;\r
+\r
+               if (_offset >= limit) {\r
+                       _offset = limit;\r
+                       _timer.stop();\r
+                       _directionUp = null;\r
+               }\r
+\r
+               updateScrollableMenus();\r
+       }\r
+\r
+       /**\r
+        * 下方向にスクロールします. これ以上スクロールできない場合は何もしません。 その場合、自動スクロール中であればスクロールは停止します。\r
+        */\r
+       public void scrollDown() {\r
+               _offset--;\r
+               if (_offset < 0) {\r
+                       _offset = 0;\r
+                       _timer.stop();\r
+                       _directionUp = null;\r
+               }\r
+               updateScrollableMenus();\r
+       }\r
+}\r
diff --git a/src/charactermanaj/ui/scrollablemenu/JScrollerMenuItem.java b/src/charactermanaj/ui/scrollablemenu/JScrollerMenuItem.java
new file mode 100644 (file)
index 0000000..93e60c1
--- /dev/null
@@ -0,0 +1,93 @@
+package charactermanaj.ui.scrollablemenu;
+
+import java.awt.event.MouseEvent;
+
+import javax.swing.Icon;
+import javax.swing.JMenuItem;
+import javax.swing.event.EventListenerList;
+
+/**
+ * スクローラブルメニューのスクローラーアイテムのメニュー項目
+ * 
+ * @author seraphy
+ */
+public class JScrollerMenuItem extends JMenuItem {
+
+       /**
+        * シリアライズバージョンID
+        */
+       private static final long serialVersionUID = -1749741596476938310L;
+       /**
+        * イベントリスナのコレクション
+        */
+       protected EventListenerList _listeners = new EventListenerList();
+
+       /**
+        * スクローラーのアイコンを指定してスクローラーアイテムのメニュー項目を構築します.
+        * 
+        * @param icon
+        *            アイコン
+        */
+       public JScrollerMenuItem(Icon icon) {
+               setIcon(icon);
+       }
+
+       /**
+        * スクローラブルメニューイベントのイベントリスナを登録します.
+        * 
+        * @param l
+        *            リスナー
+        */
+       public void addScrollableMenuEventListener(ScrollableMenuEventListener l) {
+               _listeners.add(ScrollableMenuEventListener.class, l);
+       }
+
+       /**
+        * スクローラブルメニューイベントのイベントリスナを登録解除します.
+        * 
+        * @param l
+        *            リスナー
+        */
+       public void removeScrollableMenuEventListener(ScrollableMenuEventListener l) {
+               _listeners.remove(ScrollableMenuEventListener.class, l);
+       }
+
+       /**
+        * マウスクリックでメニューアイテムとしてのイベントが発生しないように、 マウスイベントをキャプチャして、スクローラブルメニューイベントに変換する。
+        * 
+        * @param e
+        */
+       @Override
+       protected void processMouseEvent(MouseEvent e) {
+               ScrollableMenuEvent ee = null;
+               int mouseEventId = e.getID();
+               if (mouseEventId == MouseEvent.MOUSE_PRESSED) {
+                       // マウスダウン時、スクロール開始
+                       ee = new ScrollableMenuEvent(this, true);
+               }
+               if (mouseEventId == MouseEvent.MOUSE_RELEASED) {
+                       // マウスアップされた場合、スクロール停止
+                       ee = new ScrollableMenuEvent(this, false);
+               }
+               if (ee != null) {
+                       fireScrollableMenuEvent(ee);
+               }
+       }
+
+       /**
+        * スクローラブルメニューイベントを送信する
+        * 
+        * @param e
+        *            メニューイベント
+        */
+       protected void fireScrollableMenuEvent(ScrollableMenuEvent e) {
+               for (ScrollableMenuEventListener l : _listeners
+                               .getListeners(ScrollableMenuEventListener.class)) {
+                       if (e.isScrolling()) {
+                               l.start(e);
+                       } else {
+                               l.end(e);
+                       }
+               }
+       }
+}
diff --git a/src/charactermanaj/ui/scrollablemenu/ScrollableMenuEvent.java b/src/charactermanaj/ui/scrollablemenu/ScrollableMenuEvent.java
new file mode 100644 (file)
index 0000000..d88b34f
--- /dev/null
@@ -0,0 +1,59 @@
+package charactermanaj.ui.scrollablemenu;
+
+import java.util.EventObject;
+
+/**
+ * スクローラブルメニューのイベント
+ * 
+ * @author seraphy
+ */
+public class ScrollableMenuEvent extends EventObject {
+
+       /**
+        * シリアライズバージョンID
+        */
+       private static final long serialVersionUID = 5686533260565824649L;
+
+       /**
+        * スクロール中フラグ
+        */
+       private boolean _scrolling;
+
+       /**
+        * イベントのコンストラクタ
+        * 
+        * @param s
+        *            イベントソース
+        * @param scrolling
+        *            スクロール中フラグ
+        */
+       public ScrollableMenuEvent(JScrollerMenuItem s, boolean scrolling) {
+               super(s);
+               this._scrolling = scrolling;
+       }
+
+       /**
+        * スクロール中か?
+        * 
+        * @return スクロール中であればtrue
+        */
+       public boolean isScrolling() {
+               return _scrolling;
+       }
+
+       /**
+        * 診断用
+        * 
+        * @return 診断用文字列
+        */
+       @Override
+       public String toString() {
+               StringBuilder buf = new StringBuilder();
+               buf.append(getClass().getSimpleName());
+               buf.append("[");
+               buf.append(this.source);
+               buf.append(",scrolling=").append(this._scrolling);
+               buf.append("]");
+               return buf.toString();
+       }
+}
diff --git a/src/charactermanaj/ui/scrollablemenu/ScrollableMenuEventListener.java b/src/charactermanaj/ui/scrollablemenu/ScrollableMenuEventListener.java
new file mode 100644 (file)
index 0000000..3e9870a
--- /dev/null
@@ -0,0 +1,27 @@
+package charactermanaj.ui.scrollablemenu;
+
+import java.util.EventListener;
+
+/**
+ * スクローラブルメニューのイベントリスナ
+ * 
+ * @author seraphy
+ */
+public interface ScrollableMenuEventListener extends EventListener {
+
+       /**
+        * スクロール開始を通知する.
+        * 
+        * @param e
+        *            イベント
+        */
+       void start(ScrollableMenuEvent e);
+
+       /**
+        * スクロール終了を通知する.
+        * 
+        * @param e
+        *            イベント
+        */
+       void end(ScrollableMenuEvent e);
+}
diff --git a/src/charactermanaj/ui/scrollablemenu/arrow-down.png b/src/charactermanaj/ui/scrollablemenu/arrow-down.png
new file mode 100644 (file)
index 0000000..b818207
Binary files /dev/null and b/src/charactermanaj/ui/scrollablemenu/arrow-down.png differ
diff --git a/src/charactermanaj/ui/scrollablemenu/arrow-up.png b/src/charactermanaj/ui/scrollablemenu/arrow-up.png
new file mode 100644 (file)
index 0000000..9edad9f
Binary files /dev/null and b/src/charactermanaj/ui/scrollablemenu/arrow-up.png differ
diff --git a/src/charactermanaj/ui/util/WindowAdjustLocationSupport.java b/src/charactermanaj/ui/util/WindowAdjustLocationSupport.java
new file mode 100644 (file)
index 0000000..8f7cc7d
--- /dev/null
@@ -0,0 +1,70 @@
+package charactermanaj.ui.util;\r
+\r
+import java.awt.Dimension;\r
+import java.awt.GraphicsEnvironment;\r
+import java.awt.Insets;\r
+import java.awt.Point;\r
+import java.awt.Rectangle;\r
+import java.awt.Window;\r
+\r
+import javax.swing.JFrame;\r
+\r
+/**\r
+ * ウィンドウの位置を調整するサポートクラス.<br>\r
+ * \r
+ * @author seraphy\r
+ */\r
+public final class WindowAdjustLocationSupport {\r
+\r
+       /**\r
+        * プライベートコンストラクタ\r
+        */\r
+       private WindowAdjustLocationSupport() {\r
+               super();\r
+       }\r
+\r
+       /**\r
+        * ウィンドウの表示位置をメインウィンドウの右側に調整する.<br>\r
+        * 横位置Xはメインフレームの右側とし、縦位置Yはメインフレームの上位置からのoffset_yを加えた位置とする.<br>\r
+        * \r
+        * @param mainWindow\r
+        *            基準位置となるメインウィンドウ\r
+        * @param window\r
+        *            位置を調整するウィンドウ\r
+        * @param offset_y\r
+        *            表示のYオフセット\r
+        * @param sameHeight\r
+        *            高さをメインウィンドウにそろえるか?\r
+        */\r
+       public static void alignRight(JFrame mainWindow, Window window,\r
+                       int offset_y, boolean sameHeight) {\r
+               // メインウィンドウよりも左側に位置づけする.\r
+               // 縦位置はメインウィンドウの上端からオフセットを加えたものとする.\r
+               Point pt = mainWindow.getLocation();\r
+               Insets insets = mainWindow.getInsets();\r
+               pt.x += mainWindow.getWidth();\r
+               pt.y += (offset_y * insets.top);\r
+\r
+               // メインスクリーンサイズを取得する.\r
+               GraphicsEnvironment genv = GraphicsEnvironment\r
+                               .getLocalGraphicsEnvironment();\r
+               Rectangle desktopSize = genv.getMaximumWindowBounds(); // メインスクリーンのサイズ(デスクトップ領域のみ)\r
+\r
+               // メインスクリーンサイズを超えた場合は、はみ出た分を移動する.\r
+               if ((pt.x + window.getWidth()) > desktopSize.width) {\r
+                       pt.x -= ((pt.x + window.getWidth()) - desktopSize.width);\r
+               }\r
+               if ((pt.y + window.getHeight()) > desktopSize.height) {\r
+                       pt.y -= ((pt.y + window.getHeight()) - desktopSize.height);\r
+               }\r
+\r
+               window.setLocation(pt);\r
+\r
+               // 高さはメインフレームと同じにする.\r
+               if (sameHeight) {\r
+                       Dimension siz = window.getSize();\r
+                       siz.height = mainWindow.getHeight() - offset_y;\r
+                       window.setSize(siz);\r
+               }\r
+       }\r
+}\r