<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
<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
<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
<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
<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
<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
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
* パーツセット.<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
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
\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
* パーツセットをディープコピーする.<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
* パーツセットはキャラクターデータとは独立してロード・セーブされることがあるため、同じ情報でも異なるインスタンスとなる場合があり、\r
* これを是正するために、このメソッドを使用します.<br>\r
* リゾルバから取得できないパーツカテゴリは除去されます.<br>\r
- * @param resolver リゾルバ\r
+ * \r
+ * @param resolver\r
+ * リゾルバ\r
* @return 互換性のあるパーツセット\r
*/\r
public PartsSet createCompatible(PartsCategoryResolver resolver) {\r
\r
/**\r
* パーツセットをディープコピーする.<br>\r
+ * \r
* @return コピーされたパーツセット\r
*/\r
@Override\r
}\r
\r
/**\r
- * プリセット用パーツセットであるか?\r
- * これがfalseの場合は一時的なパーツセットか、もしくはお気に入り用である.<br>\r
+ * プリセット用パーツセットであるか? これがfalseの場合は一時的なパーツセットか、もしくはお気に入り用である.<br>\r
+ * \r
* @return プリセット用パーツセットである場合\r
*/\r
public boolean isPresetParts() {\r
/**\r
* バックグラウンドカラーを取得する.<br>\r
* 設定されていなければnull.<br>\r
+ * \r
* @return バックグラウンドカラー、もしくはnull\r
*/\r
public Color getBgColor() {\r
* アフィン変換用パラメータを指定する.<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
/**\r
* アフィン変換用のパラメータを取得する.<br>\r
* 変換しない場合はnull.<br>\r
+ * \r
* @return アフィン変換用のパラメータ、またはnull\r
*/\r
public double[] getAffineTransformParameter() {\r
/**\r
* プリセットIDを取得する.<br>\r
* 一時的なパーツセットである場合はnull\r
+ * \r
* @return プリセットID、またはnull\r
*/\r
public String getPartsSetId() {\r
/**\r
* プリセット名を取得する.<br>\r
* 一時的なパーツセットである場合はnull\r
+ * \r
* @return プリセット名、またはnull\r
*/\r
public String getLocalizedName() {\r
/**\r
* パーツカラー情報を取得する.<br>\r
* カラー情報が関連づけられていない場合はnullが返される.<br>\r
- * @param partsIdentifier パーツ識別子\r
+ * \r
+ * @param partsIdentifier\r
+ * パーツ識別子\r
* @return カラー情報、もしくはnull\r
*/\r
public PartsColorInfo getColorInfo(PartsIdentifier partsIdentifier) {\r
* 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
/**\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
* 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
\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
\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
\r
/**\r
* このパーツセットが名前をもっているか?\r
+ * \r
* @return 名前がある場合はtrue、設定されていないか空文字の場合はfalse\r
*/\r
public boolean hasName() {\r
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
\r
/**\r
* ディレクトリをアーカイブと見立てる\r
+ * \r
* @author seraphy\r
*/\r
public class CharacterDataDirectoryFile extends AbstractCharacterDataArchiveFile {\r
\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
}\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
// ファイル名をノーマライズする\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
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
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
.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
}\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
}\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
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
\r
/**\r
* ディレクトリを指定して、そこからキャラクターのパーツデータをロードするローダー.<br>\r
+ * \r
* @author seraphy\r
- *\r
+ * \r
*/\r
public class FilePartsDataLoader implements PartsDataLoader {\r
\r
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
}\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
\r
/**\r
* 監視を停止する.<br>\r
+ * \r
* @return 停止した場合はtrue、すでに停止していたか開始されていない場合はfalse\r
*/\r
private boolean stop() {\r
/**\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
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
\r
/**\r
* イベントリスナを登録する\r
- * @param l リスナ\r
+ * \r
+ * @param l\r
+ * リスナ\r
*/\r
public void addPartsImageDirectoryWatchListener(PartsImageDirectoryWatchListener l) {\r
if (l != null) {\r
\r
/**\r
* イベントリスナを登録解除する\r
- * @param l リスナ\r
+ * \r
+ * @param l\r
+ * リスナ\r
*/\r
public void removePartsImageDirectoryWatchListener(PartsImageDirectoryWatchListener l) {\r
if (l != null) {\r
--- /dev/null
+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
\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
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
\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
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
}\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
\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
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
/**\r
* カラーダイアログ.<br>\r
* カラーダイアログはカテゴリ別に関連づけられており、カテゴリ内の各レイヤーに対応するタブを持つ.<br>\r
+ * \r
* @author seraphy\r
*/\r
public class ColorDialog extends JDialog {\r
\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
});\r
tabContainer.addPropertyChangeListener("colorConvertParameter", new PropertyChangeListener() {\r
public void propertyChange(PropertyChangeEvent evt) {\r
- // レイヤーの情報が変るたびにリセットボタンの状態を更新する\r
+ // レイヤーの情報が変るたびにリセットボタンの状態を更新する\r
updateResetButton(tabContainer);\r
}\r
});\r
}\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
}\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
/**\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
/**\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
/**\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
* 対象となるパーツ識別子を指定する。<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
/**\r
* 対象となるパーツ識別子を取得する.<br>\r
* 設定されていなければnullが返される.<br>\r
+ * \r
* @return パーツ識別子、もしくはnull\r
*/\r
public PartsIdentifier getPartsIdentifier() {\r
* 各レイヤーのタブの有効・無効状態を設定します.<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
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
\r
/**\r
* 「すべてに適用」フラグを取得する.<br>\r
+ * \r
* @return すべてに適用フラグ\r
*/\r
public boolean isApplyAll() {\r
\r
/**\r
* 各レイヤーと、その色情報をマップとして取得する.<br>\r
+ * \r
* @return 各レイヤーと、その色情報のマップ\r
*/\r
public Map<Layer, ColorConvertParameter> getColorConvertParameters() {\r
/**\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
/**\r
* 指定したレイヤーの色情報を取得する.<br>\r
* カテゴリに属していないレイヤーを指定した場合は例外となる.<br>\r
- * @param layer レイヤー\r
+ * \r
+ * @param layer\r
+ * レイヤー\r
* @return 色情報\r
*/\r
public ColorConvertParameter getColorConvertParameter(Layer layer) {\r
/**\r
* 指定したレイヤーのカラーグループを取得する.<br>\r
* カテゴリに属さないレイヤーを指定した場合は例外となる.<br>\r
- * @param layer レイヤー\r
+ * \r
+ * @param layer\r
+ * レイヤー\r
* @return カラーグループ\r
*/\r
public ColorGroup getColorGroup(Layer layer) {\r
/**\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
\r
/**\r
* 色ダイアログが変更された場合に通知を受けるリスナーを登録する.<br>\r
- * @param listener リスナー\r
+ * \r
+ * @param listener\r
+ * リスナー\r
*/\r
public void addColorChangeListener(ColorChangeListener listener) {\r
if (listener == null) {\r
\r
/**\r
* 色ダイアログが変更された場合に通知を受けるリスナーを登録解除する.<br>\r
+ * \r
* @param listener\r
*/\r
public void removeColorChangeListener(ColorChangeListener listener) {\r
/**\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
\r
/**\r
* 色グループが変更されたことを通知する.<br>\r
- * @param layer レイヤー\r
+ * \r
+ * @param layer\r
+ * レイヤー\r
*/\r
protected void fireColorGroupChangeEvent(Layer layer) {\r
if (layer == null) {\r
.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
\r
/**\r
* このパネルで変更された色情報の状態をリセットする.<br>\r
- * 最後に{@link #setColorConvertParameter(ColorConvertParameter)}された値で\r
- * 設定し直す.<br>\r
+ * 最後に{@link #setColorConvertParameter(ColorConvertParameter)}された値で 設定し直す.<br>\r
*/\r
public void resetColor() {\r
setColorConvertParameter(paramOrg);\r
\r
/**\r
* カラー設定が変更されているか?\r
+ * \r
* @return 変更されている場合はtrue、そうでなければfalse\r
*/\r
public boolean isColorConvertParameterModified() {\r
\r
public static final String PANEL_NAME = "importTypeSelectPanel";\r
\r
+ private ImportWizardDialog parent;\r
+\r
private SamplePicturePanel samplePicturePanel;\r
\r
private JTextField txtCharacterId;\r
\r
@Override\r
public void onActive(ImportWizardDialog parent, ImportWizardCardPanel previousPanel) {\r
+ this.parent = parent;\r
+\r
if (previousPanel == parent.importPartsSelectPanel) {\r
return;\r
}\r
@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
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
import javax.swing.JMenuItem;\r
import javax.swing.JOptionPane;\r
import javax.swing.JPanel;\r
+import javax.swing.JPopupMenu;\r
import javax.swing.JScrollBar;\r
import javax.swing.JScrollPane;\r
import javax.swing.JSeparator;\r
import 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
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
* \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
\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
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
// パーツ及びお気に入りを再取得する場合.\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
}\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
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
*/\r
@Override\r
public void dispose() {\r
+ FavoritesChangeObserver.getDefault()\r
+ .removeFavoritesChangeListener(this);\r
imageLoader.close();\r
stopAgents();\r
super.dispose();\r
// 開いている検索ダイアログを閉じる\r
closeSearchDialog();\r
\r
+ // 開いているお気に入り管理ダイアログを閉じる\r
+ closeManageFavoritesDialog();\r
+\r
PartsColorManager partsColorManager = characterData.getPartsColorManager();\r
\r
// デフォルトの背景色の設定\r
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
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
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
}\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
* 「パーツ検索」ダイアログを閉じる.<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
}\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
// お気に入りをリロードする.\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
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
/**\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
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
\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
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
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
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
\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
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
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
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
}\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
* 選択したお気に入りを表示する.\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
dispose();\r
}\r
\r
- public boolean isModified() {\r
- return dirty;\r
- }\r
-\r
public void setFavoriteManageCallback(FavoriteManageCallback callback) {\r
this.callback = callback;\r
}\r
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
/**\r
* メニュー項目のアンチエイリアスが必要か判定する.<br>\r
* java.specification.versionが1.5で始まる場合は必要とみなす.<br>\r
+ * \r
* @return アンチエイリアスが必要であればtrue\r
*/\r
private static boolean isNeedAntialias() {\r
/**\r
* 生成されたメニューを名前を指定して取得します.<br>\r
* 存在しない場合は実行時例外が発生します.<br>\r
- * @param name メニュー名\r
+ * \r
+ * @param name\r
+ * メニュー名\r
* @return メニュー\r
*/\r
public JMenu getJMenu(String name) {\r
/**\r
* 生成されたメニュー項目を名前を指定して取得します.<br>\r
* 存在しない場合は実行時例外が発生します.<br>\r
- * @param name メニュー項目名\r
+ * \r
+ * @param name\r
+ * メニュー項目名\r
* @return メニュー項目\r
*/\r
public JMenuItem getJMenuItem(String name) {\r
\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
/**\r
* JMenuBarを構築します.<br>\r
* アンチエイリアスが必要な場合はアンチエイリアスが設定されます.<br>\r
+ * \r
* @return JMenuBar\r
*/\r
public JMenuBar createJMenuBar() {\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
- }\r
+ setAntiAlias(g);\r
super.paint(g);\r
}\r
};\r
/**\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
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
* JMenuItemを構築します.<br>\r
* アンチエイリアスが必要な場合はアンチエイリアスが設定されます.<br>\r
+ * \r
* @return JMenuItem\r
*/\r
public JMenuItem createJMenuItem() {\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
- }\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
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
\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
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
}\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
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
}\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
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
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
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
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
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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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);
+ }
+ }
+ }
+}
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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);
+}
--- /dev/null
+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